decoration.js
1 const { Emitter } = require('event-kit'); 2 3 let idCounter = 0; 4 const nextId = () => idCounter++; 5 6 const normalizeDecorationProperties = function(decoration, decorationParams) { 7 decorationParams.id = decoration.id; 8 9 if ( 10 decorationParams.type === 'line-number' && 11 decorationParams.gutterName == null 12 ) { 13 decorationParams.gutterName = 'line-number'; 14 } 15 16 if (decorationParams.order == null) { 17 decorationParams.order = Infinity; 18 } 19 20 return decorationParams; 21 }; 22 23 // Essential: Represents a decoration that follows a {DisplayMarker}. A decoration is 24 // basically a visual representation of a marker. It allows you to add CSS 25 // classes to line numbers in the gutter, lines, and add selection-line regions 26 // around marked ranges of text. 27 // 28 // {Decoration} objects are not meant to be created directly, but created with 29 // {TextEditor::decorateMarker}. eg. 30 // 31 // ```coffee 32 // range = editor.getSelectedBufferRange() # any range you like 33 // marker = editor.markBufferRange(range) 34 // decoration = editor.decorateMarker(marker, {type: 'line', class: 'my-line-class'}) 35 // ``` 36 // 37 // Best practice for destroying the decoration is by destroying the {DisplayMarker}. 38 // 39 // ```coffee 40 // marker.destroy() 41 // ``` 42 // 43 // You should only use {Decoration::destroy} when you still need or do not own 44 // the marker. 45 module.exports = class Decoration { 46 // Private: Check if the `decorationProperties.type` matches `type` 47 // 48 // * `decorationProperties` {Object} eg. `{type: 'line-number', class: 'my-new-class'}` 49 // * `type` {String} type like `'line-number'`, `'line'`, etc. `type` can also 50 // be an {Array} of {String}s, where it will return true if the decoration's 51 // type matches any in the array. 52 // 53 // Returns {Boolean} 54 // Note: 'line-number' is a special subtype of the 'gutter' type. I.e., a 55 // 'line-number' is a 'gutter', but a 'gutter' is not a 'line-number'. 56 static isType(decorationProperties, type) { 57 // 'line-number' is a special case of 'gutter'. 58 if (Array.isArray(decorationProperties.type)) { 59 if (decorationProperties.type.includes(type)) { 60 return true; 61 } 62 63 if ( 64 type === 'gutter' && 65 decorationProperties.type.includes('line-number') 66 ) { 67 return true; 68 } 69 70 return false; 71 } else { 72 if (type === 'gutter') { 73 return ['gutter', 'line-number'].includes(decorationProperties.type); 74 } else { 75 return type === decorationProperties.type; 76 } 77 } 78 } 79 80 /* 81 Section: Construction and Destruction 82 */ 83 84 constructor(marker, decorationManager, properties) { 85 this.marker = marker; 86 this.decorationManager = decorationManager; 87 this.emitter = new Emitter(); 88 this.id = nextId(); 89 this.setProperties(properties); 90 this.destroyed = false; 91 this.markerDestroyDisposable = this.marker.onDidDestroy(() => 92 this.destroy() 93 ); 94 } 95 96 // Essential: Destroy this marker decoration. 97 // 98 // You can also destroy the marker if you own it, which will destroy this 99 // decoration. 100 destroy() { 101 if (this.destroyed) { 102 return; 103 } 104 this.markerDestroyDisposable.dispose(); 105 this.markerDestroyDisposable = null; 106 this.destroyed = true; 107 this.decorationManager.didDestroyMarkerDecoration(this); 108 this.emitter.emit('did-destroy'); 109 return this.emitter.dispose(); 110 } 111 112 isDestroyed() { 113 return this.destroyed; 114 } 115 116 /* 117 Section: Event Subscription 118 */ 119 120 // Essential: When the {Decoration} is updated via {Decoration::update}. 121 // 122 // * `callback` {Function} 123 // * `event` {Object} 124 // * `oldProperties` {Object} the old parameters the decoration used to have 125 // * `newProperties` {Object} the new parameters the decoration now has 126 // 127 // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. 128 onDidChangeProperties(callback) { 129 return this.emitter.on('did-change-properties', callback); 130 } 131 132 // Essential: Invoke the given callback when the {Decoration} is destroyed 133 // 134 // * `callback` {Function} 135 // 136 // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. 137 onDidDestroy(callback) { 138 return this.emitter.once('did-destroy', callback); 139 } 140 141 /* 142 Section: Decoration Details 143 */ 144 145 // Essential: An id unique across all {Decoration} objects 146 getId() { 147 return this.id; 148 } 149 150 // Essential: Returns the marker associated with this {Decoration} 151 getMarker() { 152 return this.marker; 153 } 154 155 // Public: Check if this decoration is of type `type` 156 // 157 // * `type` {String} type like `'line-number'`, `'line'`, etc. `type` can also 158 // be an {Array} of {String}s, where it will return true if the decoration's 159 // type matches any in the array. 160 // 161 // Returns {Boolean} 162 isType(type) { 163 return Decoration.isType(this.properties, type); 164 } 165 166 /* 167 Section: Properties 168 */ 169 170 // Essential: Returns the {Decoration}'s properties. 171 getProperties() { 172 return this.properties; 173 } 174 175 // Essential: Update the marker with new Properties. Allows you to change the decoration's class. 176 // 177 // ## Examples 178 // 179 // ```coffee 180 // decoration.setProperties({type: 'line-number', class: 'my-new-class'}) 181 // ``` 182 // 183 // * `newProperties` {Object} eg. `{type: 'line-number', class: 'my-new-class'}` 184 setProperties(newProperties) { 185 if (this.destroyed) { 186 return; 187 } 188 const oldProperties = this.properties; 189 this.properties = normalizeDecorationProperties(this, newProperties); 190 if (newProperties.type != null) { 191 this.decorationManager.decorationDidChangeType(this); 192 } 193 this.decorationManager.emitDidUpdateDecorations(); 194 return this.emitter.emit('did-change-properties', { 195 oldProperties, 196 newProperties 197 }); 198 } 199 200 /* 201 Section: Utility 202 */ 203 204 inspect() { 205 return `<Decoration ${this.id}>`; 206 } 207 208 /* 209 Section: Private methods 210 */ 211 212 matchesPattern(decorationPattern) { 213 if (decorationPattern == null) { 214 return false; 215 } 216 for (let key in decorationPattern) { 217 const value = decorationPattern[key]; 218 if (this.properties[key] !== value) { 219 return false; 220 } 221 } 222 return true; 223 } 224 225 flash(klass, duration) { 226 if (duration == null) { 227 duration = 500; 228 } 229 this.properties.flashRequested = true; 230 this.properties.flashClass = klass; 231 this.properties.flashDuration = duration; 232 this.decorationManager.emitDidUpdateDecorations(); 233 return this.emitter.emit('did-flash'); 234 } 235 };