text-editor-element.js
1 const { Emitter, Range } = require('atom'); 2 const Grim = require('grim'); 3 const TextEditorComponent = require('./text-editor-component'); 4 const dedent = require('dedent'); 5 6 class TextEditorElement extends HTMLElement { 7 initialize(component) { 8 this.component = component; 9 return this; 10 } 11 12 get shadowRoot() { 13 Grim.deprecate(dedent` 14 The contents of \`atom-text-editor\` elements are no longer encapsulated 15 within a shadow DOM boundary. Please, stop using \`shadowRoot\` and access 16 the editor contents directly instead. 17 `); 18 19 return this; 20 } 21 22 get rootElement() { 23 Grim.deprecate(dedent` 24 The contents of \`atom-text-editor\` elements are no longer encapsulated 25 within a shadow DOM boundary. Please, stop using \`rootElement\` and access 26 the editor contents directly instead. 27 `); 28 29 return this; 30 } 31 32 createdCallback() { 33 this.emitter = new Emitter(); 34 this.initialText = this.textContent; 35 if (this.tabIndex == null) this.tabIndex = -1; 36 this.addEventListener('focus', event => 37 this.getComponent().didFocus(event) 38 ); 39 this.addEventListener('blur', event => this.getComponent().didBlur(event)); 40 } 41 42 attachedCallback() { 43 this.getComponent().didAttach(); 44 this.emitter.emit('did-attach'); 45 } 46 47 detachedCallback() { 48 this.emitter.emit('did-detach'); 49 this.getComponent().didDetach(); 50 } 51 52 attributeChangedCallback(name, oldValue, newValue) { 53 if (this.component) { 54 switch (name) { 55 case 'mini': 56 this.getModel().update({ mini: newValue != null }); 57 break; 58 case 'placeholder-text': 59 this.getModel().update({ placeholderText: newValue }); 60 break; 61 case 'gutter-hidden': 62 this.getModel().update({ lineNumberGutterVisible: newValue == null }); 63 break; 64 case 'readonly': 65 this.getModel().update({ readOnly: newValue != null }); 66 break; 67 } 68 } 69 } 70 71 // Extended: Get a promise that resolves the next time the element's DOM 72 // is updated in any way. 73 // 74 // This can be useful when you've made a change to the model and need to 75 // be sure this change has been flushed to the DOM. 76 // 77 // Returns a {Promise}. 78 getNextUpdatePromise() { 79 return this.getComponent().getNextUpdatePromise(); 80 } 81 82 getModel() { 83 return this.getComponent().props.model; 84 } 85 86 setModel(model) { 87 this.getComponent().update({ model }); 88 this.updateModelFromAttributes(); 89 } 90 91 updateModelFromAttributes() { 92 const props = { mini: this.hasAttribute('mini') }; 93 if (this.hasAttribute('placeholder-text')) 94 props.placeholderText = this.getAttribute('placeholder-text'); 95 if (this.hasAttribute('gutter-hidden')) 96 props.lineNumberGutterVisible = false; 97 98 this.getModel().update(props); 99 if (this.initialText) this.getModel().setText(this.initialText); 100 } 101 102 onDidAttach(callback) { 103 return this.emitter.on('did-attach', callback); 104 } 105 106 onDidDetach(callback) { 107 return this.emitter.on('did-detach', callback); 108 } 109 110 measureDimensions() { 111 this.getComponent().measureDimensions(); 112 } 113 114 setWidth(width) { 115 this.style.width = 116 this.getComponent().getGutterContainerWidth() + width + 'px'; 117 } 118 119 getWidth() { 120 return this.getComponent().getScrollContainerWidth(); 121 } 122 123 setHeight(height) { 124 this.style.height = height + 'px'; 125 } 126 127 getHeight() { 128 return this.getComponent().getScrollContainerHeight(); 129 } 130 131 onDidChangeScrollLeft(callback) { 132 return this.emitter.on('did-change-scroll-left', callback); 133 } 134 135 onDidChangeScrollTop(callback) { 136 return this.emitter.on('did-change-scroll-top', callback); 137 } 138 139 // Deprecated: get the width of an `x` character displayed in this element. 140 // 141 // Returns a {Number} of pixels. 142 getDefaultCharacterWidth() { 143 return this.getComponent().getBaseCharacterWidth(); 144 } 145 146 // Extended: get the width of an `x` character displayed in this element. 147 // 148 // Returns a {Number} of pixels. 149 getBaseCharacterWidth() { 150 return this.getComponent().getBaseCharacterWidth(); 151 } 152 153 getMaxScrollTop() { 154 return this.getComponent().getMaxScrollTop(); 155 } 156 157 getScrollHeight() { 158 return this.getComponent().getScrollHeight(); 159 } 160 161 getScrollWidth() { 162 return this.getComponent().getScrollWidth(); 163 } 164 165 getVerticalScrollbarWidth() { 166 return this.getComponent().getVerticalScrollbarWidth(); 167 } 168 169 getHorizontalScrollbarHeight() { 170 return this.getComponent().getHorizontalScrollbarHeight(); 171 } 172 173 getScrollTop() { 174 return this.getComponent().getScrollTop(); 175 } 176 177 setScrollTop(scrollTop) { 178 const component = this.getComponent(); 179 component.setScrollTop(scrollTop); 180 component.scheduleUpdate(); 181 } 182 183 getScrollBottom() { 184 return this.getComponent().getScrollBottom(); 185 } 186 187 setScrollBottom(scrollBottom) { 188 return this.getComponent().setScrollBottom(scrollBottom); 189 } 190 191 getScrollLeft() { 192 return this.getComponent().getScrollLeft(); 193 } 194 195 setScrollLeft(scrollLeft) { 196 const component = this.getComponent(); 197 component.setScrollLeft(scrollLeft); 198 component.scheduleUpdate(); 199 } 200 201 getScrollRight() { 202 return this.getComponent().getScrollRight(); 203 } 204 205 setScrollRight(scrollRight) { 206 return this.getComponent().setScrollRight(scrollRight); 207 } 208 209 // Essential: Scrolls the editor to the top. 210 scrollToTop() { 211 this.setScrollTop(0); 212 } 213 214 // Essential: Scrolls the editor to the bottom. 215 scrollToBottom() { 216 this.setScrollTop(Infinity); 217 } 218 219 hasFocus() { 220 return this.getComponent().focused; 221 } 222 223 // Extended: Converts a buffer position to a pixel position. 224 // 225 // * `bufferPosition` A {Point}-like object that represents a buffer position. 226 // 227 // Be aware that calling this method with a column that does not translate 228 // to column 0 on screen could cause a synchronous DOM update in order to 229 // measure the requested horizontal pixel position if it isn't already 230 // cached. 231 // 232 // Returns an {Object} with two values: `top` and `left`, representing the 233 // pixel position. 234 pixelPositionForBufferPosition(bufferPosition) { 235 const screenPosition = this.getModel().screenPositionForBufferPosition( 236 bufferPosition 237 ); 238 return this.getComponent().pixelPositionForScreenPosition(screenPosition); 239 } 240 241 // Extended: Converts a screen position to a pixel position. 242 // 243 // * `screenPosition` A {Point}-like object that represents a buffer position. 244 // 245 // Be aware that calling this method with a non-zero column value could 246 // cause a synchronous DOM update in order to measure the requested 247 // horizontal pixel position if it isn't already cached. 248 // 249 // Returns an {Object} with two values: `top` and `left`, representing the 250 // pixel position. 251 pixelPositionForScreenPosition(screenPosition) { 252 screenPosition = this.getModel().clipScreenPosition(screenPosition); 253 return this.getComponent().pixelPositionForScreenPosition(screenPosition); 254 } 255 256 screenPositionForPixelPosition(pixelPosition) { 257 return this.getComponent().screenPositionForPixelPosition(pixelPosition); 258 } 259 260 pixelRectForScreenRange(range) { 261 range = Range.fromObject(range); 262 263 const start = this.pixelPositionForScreenPosition(range.start); 264 const end = this.pixelPositionForScreenPosition(range.end); 265 const lineHeight = this.getComponent().getLineHeight(); 266 267 return { 268 top: start.top, 269 left: start.left, 270 height: end.top + lineHeight - start.top, 271 width: end.left - start.left 272 }; 273 } 274 275 pixelRangeForScreenRange(range) { 276 range = Range.fromObject(range); 277 return { 278 start: this.pixelPositionForScreenPosition(range.start), 279 end: this.pixelPositionForScreenPosition(range.end) 280 }; 281 } 282 283 getComponent() { 284 if (!this.component) { 285 this.component = new TextEditorComponent({ 286 element: this, 287 mini: this.hasAttribute('mini'), 288 updatedSynchronously: this.updatedSynchronously, 289 readOnly: this.hasAttribute('readonly') 290 }); 291 this.updateModelFromAttributes(); 292 } 293 294 return this.component; 295 } 296 297 setUpdatedSynchronously(updatedSynchronously) { 298 this.updatedSynchronously = updatedSynchronously; 299 if (this.component) 300 this.component.updatedSynchronously = updatedSynchronously; 301 return updatedSynchronously; 302 } 303 304 isUpdatedSynchronously() { 305 return this.component 306 ? this.component.updatedSynchronously 307 : this.updatedSynchronously; 308 } 309 310 // Experimental: Invalidate the passed block {Decoration}'s dimensions, 311 // forcing them to be recalculated and the surrounding content to be adjusted 312 // on the next animation frame. 313 // 314 // * {blockDecoration} A {Decoration} representing the block decoration you 315 // want to update the dimensions of. 316 invalidateBlockDecorationDimensions() { 317 this.getComponent().invalidateBlockDecorationDimensions(...arguments); 318 } 319 320 setFirstVisibleScreenRow(row) { 321 this.getModel().setFirstVisibleScreenRow(row); 322 } 323 324 getFirstVisibleScreenRow() { 325 return this.getModel().getFirstVisibleScreenRow(); 326 } 327 328 getLastVisibleScreenRow() { 329 return this.getModel().getLastVisibleScreenRow(); 330 } 331 332 getVisibleRowRange() { 333 return this.getModel().getVisibleRowRange(); 334 } 335 336 intersectsVisibleRowRange(startRow, endRow) { 337 return !( 338 endRow <= this.getFirstVisibleScreenRow() || 339 this.getLastVisibleScreenRow() <= startRow 340 ); 341 } 342 343 selectionIntersectsVisibleRowRange(selection) { 344 const { start, end } = selection.getScreenRange(); 345 return this.intersectsVisibleRowRange(start.row, end.row + 1); 346 } 347 348 setFirstVisibleScreenColumn(column) { 349 return this.getModel().setFirstVisibleScreenColumn(column); 350 } 351 352 getFirstVisibleScreenColumn() { 353 return this.getModel().getFirstVisibleScreenColumn(); 354 } 355 } 356 357 module.exports = document.registerElement('atom-text-editor', { 358 prototype: TextEditorElement.prototype 359 });