OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2013 Google Inc. All rights reserved. | 2 * Copyright (C) 2013 Google Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
6 * met: | 6 * met: |
7 * | 7 * |
8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
11 * copyright notice, this list of conditions and the following disclaimer | 11 * copyright notice, this list of conditions and the following disclaimer |
12 * in the documentation and/or other materials provided with the | 12 * in the documentation and/or other materials provided with the |
13 * distribution. | 13 * distribution. |
14 * * Neither the name of Google Inc. nor the names of its | 14 * * Neither the name of Google Inc. nor the names of its |
15 * contributors may be used to endorse or promote products derived from | 15 * contributors may be used to endorse or promote products derived from |
16 * this software without specific prior written permission. | 16 * this software without specific prior written permission. |
17 * | 17 * |
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 */ | 29 */ |
30 | |
31 /** | 30 /** |
32 * @constructor | 31 * @unrestricted |
33 * @extends {WebInspector.UISourceCodeFrame} | |
34 * @param {!WebInspector.UISourceCode} uiSourceCode | |
35 */ | 32 */ |
36 WebInspector.CSSSourceFrame = function(uiSourceCode) | 33 WebInspector.CSSSourceFrame = class extends WebInspector.UISourceCodeFrame { |
37 { | 34 /** |
38 WebInspector.UISourceCodeFrame.call(this, uiSourceCode); | 35 * @param {!WebInspector.UISourceCode} uiSourceCode |
| 36 */ |
| 37 constructor(uiSourceCode) { |
| 38 super(uiSourceCode); |
39 this._registerShortcuts(); | 39 this._registerShortcuts(); |
40 this._swatchPopoverHelper = new WebInspector.SwatchPopoverHelper(); | 40 this._swatchPopoverHelper = new WebInspector.SwatchPopoverHelper(); |
41 this._muteSwatchProcessing = false; | 41 this._muteSwatchProcessing = false; |
42 this.configureAutocomplete({ | 42 this.configureAutocomplete( |
43 suggestionsCallback: this._cssSuggestions.bind(this), | 43 {suggestionsCallback: this._cssSuggestions.bind(this), isWordChar: this.
_isWordChar.bind(this)}); |
44 isWordChar: this._isWordChar.bind(this) | 44 this.textEditor.addEventListener(WebInspector.SourcesTextEditor.Events.Scrol
lChanged, () => { |
| 45 if (this._swatchPopoverHelper.isShowing()) |
| 46 this._swatchPopoverHelper.hide(true); |
45 }); | 47 }); |
46 this.textEditor.addEventListener(WebInspector.SourcesTextEditor.Events.Scrol
lChanged, () => { | 48 } |
47 if (this._swatchPopoverHelper.isShowing()) | 49 |
48 this._swatchPopoverHelper.hide(true); | 50 _registerShortcuts() { |
49 }); | 51 var shortcutKeys = WebInspector.ShortcutsScreen.SourcesPanelShortcuts; |
| 52 for (var i = 0; i < shortcutKeys.IncreaseCSSUnitByOne.length; ++i) |
| 53 this.addShortcut(shortcutKeys.IncreaseCSSUnitByOne[i].key, this._handleUni
tModification.bind(this, 1)); |
| 54 for (var i = 0; i < shortcutKeys.DecreaseCSSUnitByOne.length; ++i) |
| 55 this.addShortcut(shortcutKeys.DecreaseCSSUnitByOne[i].key, this._handleUni
tModification.bind(this, -1)); |
| 56 for (var i = 0; i < shortcutKeys.IncreaseCSSUnitByTen.length; ++i) |
| 57 this.addShortcut(shortcutKeys.IncreaseCSSUnitByTen[i].key, this._handleUni
tModification.bind(this, 10)); |
| 58 for (var i = 0; i < shortcutKeys.DecreaseCSSUnitByTen.length; ++i) |
| 59 this.addShortcut(shortcutKeys.DecreaseCSSUnitByTen[i].key, this._handleUni
tModification.bind(this, -10)); |
| 60 } |
| 61 |
| 62 /** |
| 63 * @param {string} unit |
| 64 * @param {number} change |
| 65 * @return {?string} |
| 66 */ |
| 67 _modifyUnit(unit, change) { |
| 68 var unitValue = parseInt(unit, 10); |
| 69 if (isNaN(unitValue)) |
| 70 return null; |
| 71 var tail = unit.substring((unitValue).toString().length); |
| 72 return String.sprintf('%d%s', unitValue + change, tail); |
| 73 } |
| 74 |
| 75 /** |
| 76 * @param {number} change |
| 77 * @return {boolean} |
| 78 */ |
| 79 _handleUnitModification(change) { |
| 80 var selection = this.textEditor.selection().normalize(); |
| 81 var token = this.textEditor.tokenAtTextPosition(selection.startLine, selecti
on.startColumn); |
| 82 if (!token) { |
| 83 if (selection.startColumn > 0) |
| 84 token = this.textEditor.tokenAtTextPosition(selection.startLine, selecti
on.startColumn - 1); |
| 85 if (!token) |
| 86 return false; |
| 87 } |
| 88 if (token.type !== 'css-number') |
| 89 return false; |
| 90 |
| 91 var cssUnitRange = |
| 92 new WebInspector.TextRange(selection.startLine, token.startColumn, selec
tion.startLine, token.endColumn); |
| 93 var cssUnitText = this.textEditor.text(cssUnitRange); |
| 94 var newUnitText = this._modifyUnit(cssUnitText, change); |
| 95 if (!newUnitText) |
| 96 return false; |
| 97 this.textEditor.editRange(cssUnitRange, newUnitText); |
| 98 selection.startColumn = token.startColumn; |
| 99 selection.endColumn = selection.startColumn + newUnitText.length; |
| 100 this.textEditor.setSelection(selection); |
| 101 return true; |
| 102 } |
| 103 |
| 104 /** |
| 105 * @param {number} startLine |
| 106 * @param {number} endLine |
| 107 */ |
| 108 _updateSwatches(startLine, endLine) { |
| 109 var swatches = []; |
| 110 var swatchPositions = []; |
| 111 |
| 112 var regexes = [ |
| 113 WebInspector.CSSMetadata.VariableRegex, WebInspector.CSSMetadata.URLRegex, |
| 114 WebInspector.Geometry.CubicBezier.Regex, WebInspector.Color.Regex |
| 115 ]; |
| 116 var handlers = new Map(); |
| 117 handlers.set(WebInspector.Color.Regex, this._createColorSwatch.bind(this)); |
| 118 handlers.set(WebInspector.Geometry.CubicBezier.Regex, this._createBezierSwat
ch.bind(this)); |
| 119 |
| 120 for (var lineNumber = startLine; lineNumber <= endLine; lineNumber++) { |
| 121 var line = this.textEditor.line(lineNumber).substring(0, WebInspector.CSSS
ourceFrame.maxSwatchProcessingLength); |
| 122 var results = WebInspector.TextUtils.splitStringByRegexes(line, regexes); |
| 123 for (var i = 0; i < results.length; i++) { |
| 124 var result = results[i]; |
| 125 if (result.regexIndex === -1 || !handlers.has(regexes[result.regexIndex]
)) |
| 126 continue; |
| 127 var delimiters = /[\s:;,(){}]/; |
| 128 var positionBefore = result.position - 1; |
| 129 var positionAfter = result.position + result.value.length; |
| 130 if (positionBefore >= 0 && !delimiters.test(line.charAt(positionBefore))
|| |
| 131 positionAfter < line.length && !delimiters.test(line.charAt(position
After))) |
| 132 continue; |
| 133 var swatch = handlers.get(regexes[result.regexIndex])(result.value); |
| 134 if (!swatch) |
| 135 continue; |
| 136 swatches.push(swatch); |
| 137 swatchPositions.push(WebInspector.TextRange.createFromLocation(lineNumbe
r, result.position)); |
| 138 } |
| 139 } |
| 140 this.textEditor.operation(putSwatchesInline.bind(this)); |
| 141 |
| 142 /** |
| 143 * @this {WebInspector.CSSSourceFrame} |
| 144 */ |
| 145 function putSwatchesInline() { |
| 146 var clearRange = new WebInspector.TextRange(startLine, 0, endLine, this.te
xtEditor.line(endLine).length); |
| 147 this.textEditor.bookmarks(clearRange, WebInspector.CSSSourceFrame.SwatchBo
okmark) |
| 148 .forEach(marker => marker.clear()); |
| 149 |
| 150 for (var i = 0; i < swatches.length; i++) { |
| 151 var swatch = swatches[i]; |
| 152 var swatchPosition = swatchPositions[i]; |
| 153 var bookmark = this.textEditor.addBookmark( |
| 154 swatchPosition.startLine, swatchPosition.startColumn, swatch, WebIns
pector.CSSSourceFrame.SwatchBookmark); |
| 155 swatch[WebInspector.CSSSourceFrame.SwatchBookmark] = bookmark; |
| 156 } |
| 157 } |
| 158 } |
| 159 |
| 160 /** |
| 161 * @param {string} text |
| 162 * @return {?WebInspector.ColorSwatch} |
| 163 */ |
| 164 _createColorSwatch(text) { |
| 165 var color = WebInspector.Color.parse(text); |
| 166 if (!color) |
| 167 return null; |
| 168 var swatch = WebInspector.ColorSwatch.create(); |
| 169 swatch.setColor(color); |
| 170 swatch.iconElement().title = WebInspector.UIString('Open color picker.'); |
| 171 swatch.iconElement().addEventListener('click', this._swatchIconClicked.bind(
this, swatch), false); |
| 172 swatch.hideText(true); |
| 173 return swatch; |
| 174 } |
| 175 |
| 176 /** |
| 177 * @param {string} text |
| 178 * @return {?WebInspector.BezierSwatch} |
| 179 */ |
| 180 _createBezierSwatch(text) { |
| 181 if (!WebInspector.Geometry.CubicBezier.parse(text)) |
| 182 return null; |
| 183 var swatch = WebInspector.BezierSwatch.create(); |
| 184 swatch.setBezierText(text); |
| 185 swatch.iconElement().title = WebInspector.UIString('Open cubic bezier editor
.'); |
| 186 swatch.iconElement().addEventListener('click', this._swatchIconClicked.bind(
this, swatch), false); |
| 187 swatch.hideText(true); |
| 188 return swatch; |
| 189 } |
| 190 |
| 191 /** |
| 192 * @param {!Element} swatch |
| 193 * @param {!Event} event |
| 194 */ |
| 195 _swatchIconClicked(swatch, event) { |
| 196 event.consume(true); |
| 197 this._hadSwatchChange = false; |
| 198 this._muteSwatchProcessing = true; |
| 199 var swatchPosition = swatch[WebInspector.CSSSourceFrame.SwatchBookmark].posi
tion(); |
| 200 this.textEditor.setSelection(swatchPosition); |
| 201 this._editedSwatchTextRange = swatchPosition.clone(); |
| 202 this._editedSwatchTextRange.endColumn += swatch.textContent.length; |
| 203 this._currentSwatch = swatch; |
| 204 |
| 205 if (swatch instanceof WebInspector.ColorSwatch) |
| 206 this._showSpectrum(swatch); |
| 207 else if (swatch instanceof WebInspector.BezierSwatch) |
| 208 this._showBezierEditor(swatch); |
| 209 } |
| 210 |
| 211 /** |
| 212 * @param {!WebInspector.ColorSwatch} swatch |
| 213 */ |
| 214 _showSpectrum(swatch) { |
| 215 if (!this._spectrum) { |
| 216 this._spectrum = new WebInspector.Spectrum(); |
| 217 this._spectrum.addEventListener(WebInspector.Spectrum.Events.SizeChanged,
this._spectrumResized, this); |
| 218 this._spectrum.addEventListener(WebInspector.Spectrum.Events.ColorChanged,
this._spectrumChanged, this); |
| 219 } |
| 220 this._spectrum.setColor(swatch.color(), swatch.format()); |
| 221 this._swatchPopoverHelper.show(this._spectrum, swatch.iconElement(), this._s
watchPopoverHidden.bind(this)); |
| 222 } |
| 223 |
| 224 /** |
| 225 * @param {!WebInspector.Event} event |
| 226 */ |
| 227 _spectrumResized(event) { |
| 228 this._swatchPopoverHelper.reposition(); |
| 229 } |
| 230 |
| 231 /** |
| 232 * @param {!WebInspector.Event} event |
| 233 */ |
| 234 _spectrumChanged(event) { |
| 235 var colorString = /** @type {string} */ (event.data); |
| 236 var color = WebInspector.Color.parse(colorString); |
| 237 if (!color) |
| 238 return; |
| 239 this._currentSwatch.setColor(color); |
| 240 this._changeSwatchText(colorString); |
| 241 } |
| 242 |
| 243 /** |
| 244 * @param {!WebInspector.BezierSwatch} swatch |
| 245 */ |
| 246 _showBezierEditor(swatch) { |
| 247 if (!this._bezierEditor) { |
| 248 this._bezierEditor = new WebInspector.BezierEditor(); |
| 249 this._bezierEditor.addEventListener(WebInspector.BezierEditor.Events.Bezie
rChanged, this._bezierChanged, this); |
| 250 } |
| 251 var cubicBezier = WebInspector.Geometry.CubicBezier.parse(swatch.bezierText(
)); |
| 252 if (!cubicBezier) |
| 253 cubicBezier = |
| 254 /** @type {!WebInspector.Geometry.CubicBezier} */ (WebInspector.Geomet
ry.CubicBezier.parse('linear')); |
| 255 this._bezierEditor.setBezier(cubicBezier); |
| 256 this._swatchPopoverHelper.show(this._bezierEditor, swatch.iconElement(), thi
s._swatchPopoverHidden.bind(this)); |
| 257 } |
| 258 |
| 259 /** |
| 260 * @param {!WebInspector.Event} event |
| 261 */ |
| 262 _bezierChanged(event) { |
| 263 var bezierString = /** @type {string} */ (event.data); |
| 264 this._currentSwatch.setBezierText(bezierString); |
| 265 this._changeSwatchText(bezierString); |
| 266 } |
| 267 |
| 268 /** |
| 269 * @param {string} text |
| 270 */ |
| 271 _changeSwatchText(text) { |
| 272 this._hadSwatchChange = true; |
| 273 this._textEditor.editRange(this._editedSwatchTextRange, text, '*swatch-text-
changed'); |
| 274 this._editedSwatchTextRange.endColumn = this._editedSwatchTextRange.startCol
umn + text.length; |
| 275 } |
| 276 |
| 277 /** |
| 278 * @param {boolean} commitEdit |
| 279 */ |
| 280 _swatchPopoverHidden(commitEdit) { |
| 281 this._muteSwatchProcessing = false; |
| 282 if (!commitEdit && this._hadSwatchChange) |
| 283 this.textEditor.undo(); |
| 284 } |
| 285 |
| 286 /** |
| 287 * @override |
| 288 */ |
| 289 onTextEditorContentSet() { |
| 290 super.onTextEditorContentSet(); |
| 291 if (!this._muteSwatchProcessing) |
| 292 this._updateSwatches(0, this.textEditor.linesCount - 1); |
| 293 } |
| 294 |
| 295 /** |
| 296 * @override |
| 297 * @param {!WebInspector.TextRange} oldRange |
| 298 * @param {!WebInspector.TextRange} newRange |
| 299 */ |
| 300 onTextChanged(oldRange, newRange) { |
| 301 super.onTextChanged(oldRange, newRange); |
| 302 if (!this._muteSwatchProcessing) |
| 303 this._updateSwatches(newRange.startLine, newRange.endLine); |
| 304 } |
| 305 |
| 306 /** |
| 307 * @param {string} char |
| 308 * @return {boolean} |
| 309 */ |
| 310 _isWordChar(char) { |
| 311 return WebInspector.TextUtils.isWordChar(char) || char === '.' || char === '
-' || char === '$'; |
| 312 } |
| 313 |
| 314 /** |
| 315 * @param {!WebInspector.TextRange} prefixRange |
| 316 * @param {!WebInspector.TextRange} substituteRange |
| 317 * @return {?Promise.<!WebInspector.SuggestBox.Suggestions>} |
| 318 */ |
| 319 _cssSuggestions(prefixRange, substituteRange) { |
| 320 var prefix = this._textEditor.text(prefixRange); |
| 321 if (prefix.startsWith('$')) |
| 322 return null; |
| 323 |
| 324 var propertyToken = this._backtrackPropertyToken(prefixRange.startLine, pref
ixRange.startColumn - 1); |
| 325 if (!propertyToken) |
| 326 return null; |
| 327 |
| 328 var line = this._textEditor.line(prefixRange.startLine); |
| 329 var tokenContent = line.substring(propertyToken.startColumn, propertyToken.e
ndColumn); |
| 330 var propertyValues = WebInspector.cssMetadata().propertyValues(tokenContent)
; |
| 331 return Promise.resolve(propertyValues.filter(value => value.startsWith(prefi
x)).map(value => ({title: value}))); |
| 332 } |
| 333 |
| 334 /** |
| 335 * @param {number} lineNumber |
| 336 * @param {number} columnNumber |
| 337 * @return {?{startColumn: number, endColumn: number, type: string}} |
| 338 */ |
| 339 _backtrackPropertyToken(lineNumber, columnNumber) { |
| 340 var backtrackDepth = 10; |
| 341 var tokenPosition = columnNumber; |
| 342 var line = this._textEditor.line(lineNumber); |
| 343 var seenColon = false; |
| 344 |
| 345 for (var i = 0; i < backtrackDepth && tokenPosition >= 0; ++i) { |
| 346 var token = this._textEditor.tokenAtTextPosition(lineNumber, tokenPosition
); |
| 347 if (!token) |
| 348 return null; |
| 349 if (token.type === 'css-property') |
| 350 return seenColon ? token : null; |
| 351 if (token.type && !(token.type.indexOf('whitespace') !== -1 || token.type.
startsWith('css-comment'))) |
| 352 return null; |
| 353 |
| 354 if (!token.type && line.substring(token.startColumn, token.endColumn) ===
':') { |
| 355 if (!seenColon) |
| 356 seenColon = true; |
| 357 else |
| 358 return null; |
| 359 } |
| 360 tokenPosition = token.startColumn - 1; |
| 361 } |
| 362 return null; |
| 363 } |
50 }; | 364 }; |
51 | 365 |
52 /** @type {number} */ | 366 /** @type {number} */ |
53 WebInspector.CSSSourceFrame.maxSwatchProcessingLength = 300; | 367 WebInspector.CSSSourceFrame.maxSwatchProcessingLength = 300; |
54 /** @type {symbol} */ | 368 /** @type {symbol} */ |
55 WebInspector.CSSSourceFrame.SwatchBookmark = Symbol("swatch"); | 369 WebInspector.CSSSourceFrame.SwatchBookmark = Symbol('swatch'); |
56 | |
57 WebInspector.CSSSourceFrame.prototype = { | |
58 _registerShortcuts: function() | |
59 { | |
60 var shortcutKeys = WebInspector.ShortcutsScreen.SourcesPanelShortcuts; | |
61 for (var i = 0; i < shortcutKeys.IncreaseCSSUnitByOne.length; ++i) | |
62 this.addShortcut(shortcutKeys.IncreaseCSSUnitByOne[i].key, this._han
dleUnitModification.bind(this, 1)); | |
63 for (var i = 0; i < shortcutKeys.DecreaseCSSUnitByOne.length; ++i) | |
64 this.addShortcut(shortcutKeys.DecreaseCSSUnitByOne[i].key, this._han
dleUnitModification.bind(this, -1)); | |
65 for (var i = 0; i < shortcutKeys.IncreaseCSSUnitByTen.length; ++i) | |
66 this.addShortcut(shortcutKeys.IncreaseCSSUnitByTen[i].key, this._han
dleUnitModification.bind(this, 10)); | |
67 for (var i = 0; i < shortcutKeys.DecreaseCSSUnitByTen.length; ++i) | |
68 this.addShortcut(shortcutKeys.DecreaseCSSUnitByTen[i].key, this._han
dleUnitModification.bind(this, -10)); | |
69 }, | |
70 | |
71 /** | |
72 * @param {string} unit | |
73 * @param {number} change | |
74 * @return {?string} | |
75 */ | |
76 _modifyUnit: function(unit, change) | |
77 { | |
78 var unitValue = parseInt(unit, 10); | |
79 if (isNaN(unitValue)) | |
80 return null; | |
81 var tail = unit.substring((unitValue).toString().length); | |
82 return String.sprintf("%d%s", unitValue + change, tail); | |
83 }, | |
84 | |
85 /** | |
86 * @param {number} change | |
87 * @return {boolean} | |
88 */ | |
89 _handleUnitModification: function(change) | |
90 { | |
91 var selection = this.textEditor.selection().normalize(); | |
92 var token = this.textEditor.tokenAtTextPosition(selection.startLine, sel
ection.startColumn); | |
93 if (!token) { | |
94 if (selection.startColumn > 0) | |
95 token = this.textEditor.tokenAtTextPosition(selection.startLine,
selection.startColumn - 1); | |
96 if (!token) | |
97 return false; | |
98 } | |
99 if (token.type !== "css-number") | |
100 return false; | |
101 | |
102 var cssUnitRange = new WebInspector.TextRange(selection.startLine, token
.startColumn, selection.startLine, token.endColumn); | |
103 var cssUnitText = this.textEditor.text(cssUnitRange); | |
104 var newUnitText = this._modifyUnit(cssUnitText, change); | |
105 if (!newUnitText) | |
106 return false; | |
107 this.textEditor.editRange(cssUnitRange, newUnitText); | |
108 selection.startColumn = token.startColumn; | |
109 selection.endColumn = selection.startColumn + newUnitText.length; | |
110 this.textEditor.setSelection(selection); | |
111 return true; | |
112 }, | |
113 | |
114 /** | |
115 * @param {number} startLine | |
116 * @param {number} endLine | |
117 */ | |
118 _updateSwatches: function(startLine, endLine) | |
119 { | |
120 var swatches = []; | |
121 var swatchPositions = []; | |
122 | |
123 var regexes = [WebInspector.CSSMetadata.VariableRegex, WebInspector.CSSM
etadata.URLRegex, WebInspector.Geometry.CubicBezier.Regex, WebInspector.Color.Re
gex]; | |
124 var handlers = new Map(); | |
125 handlers.set(WebInspector.Color.Regex, this._createColorSwatch.bind(this
)); | |
126 handlers.set(WebInspector.Geometry.CubicBezier.Regex, this._createBezier
Swatch.bind(this)); | |
127 | |
128 for (var lineNumber = startLine; lineNumber <= endLine; lineNumber++) { | |
129 var line = this.textEditor.line(lineNumber).substring(0, WebInspecto
r.CSSSourceFrame.maxSwatchProcessingLength); | |
130 var results = WebInspector.TextUtils.splitStringByRegexes(line, rege
xes); | |
131 for (var i = 0; i < results.length; i++) { | |
132 var result = results[i]; | |
133 if (result.regexIndex === -1 || !handlers.has(regexes[result.reg
exIndex])) | |
134 continue; | |
135 var delimiters = /[\s:;,(){}]/; | |
136 var positionBefore = result.position - 1; | |
137 var positionAfter = result.position + result.value.length; | |
138 if (positionBefore >= 0 && !delimiters.test(line.charAt(position
Before)) | |
139 || positionAfter < line.length && !delimiters.test(line.char
At(positionAfter))) | |
140 continue; | |
141 var swatch = handlers.get(regexes[result.regexIndex])(result.val
ue); | |
142 if (!swatch) | |
143 continue; | |
144 swatches.push(swatch); | |
145 swatchPositions.push(WebInspector.TextRange.createFromLocation(l
ineNumber, result.position)); | |
146 } | |
147 } | |
148 this.textEditor.operation(putSwatchesInline.bind(this)); | |
149 | |
150 /** | |
151 * @this {WebInspector.CSSSourceFrame} | |
152 */ | |
153 function putSwatchesInline() | |
154 { | |
155 var clearRange = new WebInspector.TextRange(startLine, 0, endLine, t
his.textEditor.line(endLine).length); | |
156 this.textEditor.bookmarks(clearRange, WebInspector.CSSSourceFrame.Sw
atchBookmark).forEach(marker => marker.clear()); | |
157 | |
158 for (var i = 0; i < swatches.length; i++) { | |
159 var swatch = swatches[i]; | |
160 var swatchPosition = swatchPositions[i]; | |
161 var bookmark = this.textEditor.addBookmark(swatchPosition.startL
ine, swatchPosition.startColumn, swatch, WebInspector.CSSSourceFrame.SwatchBookm
ark); | |
162 swatch[WebInspector.CSSSourceFrame.SwatchBookmark] = bookmark; | |
163 } | |
164 } | |
165 }, | |
166 | |
167 /** | |
168 * @param {string} text | |
169 * @return {?WebInspector.ColorSwatch} | |
170 */ | |
171 _createColorSwatch: function(text) | |
172 { | |
173 var color = WebInspector.Color.parse(text); | |
174 if (!color) | |
175 return null; | |
176 var swatch = WebInspector.ColorSwatch.create(); | |
177 swatch.setColor(color); | |
178 swatch.iconElement().title = WebInspector.UIString("Open color picker.")
; | |
179 swatch.iconElement().addEventListener("click", this._swatchIconClicked.b
ind(this, swatch), false); | |
180 swatch.hideText(true); | |
181 return swatch; | |
182 }, | |
183 | |
184 /** | |
185 * @param {string} text | |
186 * @return {?WebInspector.BezierSwatch} | |
187 */ | |
188 _createBezierSwatch: function(text) | |
189 { | |
190 if (!WebInspector.Geometry.CubicBezier.parse(text)) | |
191 return null; | |
192 var swatch = WebInspector.BezierSwatch.create(); | |
193 swatch.setBezierText(text); | |
194 swatch.iconElement().title = WebInspector.UIString("Open cubic bezier ed
itor."); | |
195 swatch.iconElement().addEventListener("click", this._swatchIconClicked.b
ind(this, swatch), false); | |
196 swatch.hideText(true); | |
197 return swatch; | |
198 }, | |
199 | |
200 /** | |
201 * @param {!Element} swatch | |
202 * @param {!Event} event | |
203 */ | |
204 _swatchIconClicked: function(swatch, event) | |
205 { | |
206 event.consume(true); | |
207 this._hadSwatchChange = false; | |
208 this._muteSwatchProcessing = true; | |
209 var swatchPosition = swatch[WebInspector.CSSSourceFrame.SwatchBookmark].
position(); | |
210 this.textEditor.setSelection(swatchPosition); | |
211 this._editedSwatchTextRange = swatchPosition.clone(); | |
212 this._editedSwatchTextRange.endColumn += swatch.textContent.length; | |
213 this._currentSwatch = swatch; | |
214 | |
215 if (swatch instanceof WebInspector.ColorSwatch) | |
216 this._showSpectrum(swatch); | |
217 else if (swatch instanceof WebInspector.BezierSwatch) | |
218 this._showBezierEditor(swatch); | |
219 }, | |
220 | |
221 /** | |
222 * @param {!WebInspector.ColorSwatch} swatch | |
223 */ | |
224 _showSpectrum: function(swatch) | |
225 { | |
226 if (!this._spectrum) { | |
227 this._spectrum = new WebInspector.Spectrum(); | |
228 this._spectrum.addEventListener(WebInspector.Spectrum.Events.SizeCha
nged, this._spectrumResized, this); | |
229 this._spectrum.addEventListener(WebInspector.Spectrum.Events.ColorCh
anged, this._spectrumChanged, this); | |
230 } | |
231 this._spectrum.setColor(swatch.color(), swatch.format()); | |
232 this._swatchPopoverHelper.show(this._spectrum, swatch.iconElement(), thi
s._swatchPopoverHidden.bind(this)); | |
233 }, | |
234 | |
235 /** | |
236 * @param {!WebInspector.Event} event | |
237 */ | |
238 _spectrumResized: function(event) | |
239 { | |
240 this._swatchPopoverHelper.reposition(); | |
241 }, | |
242 | |
243 /** | |
244 * @param {!WebInspector.Event} event | |
245 */ | |
246 _spectrumChanged: function(event) | |
247 { | |
248 var colorString = /** @type {string} */ (event.data); | |
249 var color = WebInspector.Color.parse(colorString); | |
250 if (!color) | |
251 return; | |
252 this._currentSwatch.setColor(color); | |
253 this._changeSwatchText(colorString); | |
254 }, | |
255 | |
256 /** | |
257 * @param {!WebInspector.BezierSwatch} swatch | |
258 */ | |
259 _showBezierEditor: function(swatch) | |
260 { | |
261 if (!this._bezierEditor) { | |
262 this._bezierEditor = new WebInspector.BezierEditor(); | |
263 this._bezierEditor.addEventListener(WebInspector.BezierEditor.Events
.BezierChanged, this._bezierChanged, this); | |
264 } | |
265 var cubicBezier = WebInspector.Geometry.CubicBezier.parse(swatch.bezierT
ext()); | |
266 if (!cubicBezier) | |
267 cubicBezier = /** @type {!WebInspector.Geometry.CubicBezier} */ (Web
Inspector.Geometry.CubicBezier.parse("linear")); | |
268 this._bezierEditor.setBezier(cubicBezier); | |
269 this._swatchPopoverHelper.show(this._bezierEditor, swatch.iconElement(),
this._swatchPopoverHidden.bind(this)); | |
270 }, | |
271 | |
272 /** | |
273 * @param {!WebInspector.Event} event | |
274 */ | |
275 _bezierChanged: function(event) | |
276 { | |
277 var bezierString = /** @type {string} */ (event.data); | |
278 this._currentSwatch.setBezierText(bezierString); | |
279 this._changeSwatchText(bezierString); | |
280 }, | |
281 | |
282 /** | |
283 * @param {string} text | |
284 */ | |
285 _changeSwatchText: function(text) | |
286 { | |
287 this._hadSwatchChange = true; | |
288 this._textEditor.editRange(this._editedSwatchTextRange, text, "*swatch-t
ext-changed"); | |
289 this._editedSwatchTextRange.endColumn = this._editedSwatchTextRange.star
tColumn + text.length; | |
290 }, | |
291 | |
292 /** | |
293 * @param {boolean} commitEdit | |
294 */ | |
295 _swatchPopoverHidden: function(commitEdit) | |
296 { | |
297 this._muteSwatchProcessing = false; | |
298 if (!commitEdit && this._hadSwatchChange) | |
299 this.textEditor.undo(); | |
300 }, | |
301 | |
302 /** | |
303 * @override | |
304 */ | |
305 onTextEditorContentSet: function() | |
306 { | |
307 WebInspector.UISourceCodeFrame.prototype.onTextEditorContentSet.call(thi
s); | |
308 if (!this._muteSwatchProcessing) | |
309 this._updateSwatches(0, this.textEditor.linesCount - 1); | |
310 }, | |
311 | |
312 /** | |
313 * @override | |
314 * @param {!WebInspector.TextRange} oldRange | |
315 * @param {!WebInspector.TextRange} newRange | |
316 */ | |
317 onTextChanged: function(oldRange, newRange) | |
318 { | |
319 WebInspector.UISourceCodeFrame.prototype.onTextChanged.call(this, oldRan
ge, newRange); | |
320 if (!this._muteSwatchProcessing) | |
321 this._updateSwatches(newRange.startLine, newRange.endLine); | |
322 }, | |
323 | |
324 /** | |
325 * @param {string} char | |
326 * @return {boolean} | |
327 */ | |
328 _isWordChar: function(char) | |
329 { | |
330 return WebInspector.TextUtils.isWordChar(char) || char === "." || char =
== "-" || char === "$"; | |
331 }, | |
332 | |
333 /** | |
334 * @param {!WebInspector.TextRange} prefixRange | |
335 * @param {!WebInspector.TextRange} substituteRange | |
336 * @return {?Promise.<!WebInspector.SuggestBox.Suggestions>} | |
337 */ | |
338 _cssSuggestions: function(prefixRange, substituteRange) | |
339 { | |
340 var prefix = this._textEditor.text(prefixRange); | |
341 if (prefix.startsWith("$")) | |
342 return null; | |
343 | |
344 var propertyToken = this._backtrackPropertyToken(prefixRange.startLine,
prefixRange.startColumn - 1); | |
345 if (!propertyToken) | |
346 return null; | |
347 | |
348 var line = this._textEditor.line(prefixRange.startLine); | |
349 var tokenContent = line.substring(propertyToken.startColumn, propertyTok
en.endColumn); | |
350 var propertyValues = WebInspector.cssMetadata().propertyValues(tokenCont
ent); | |
351 return Promise.resolve(propertyValues.filter(value => value.startsWith(p
refix)).map(value => ({title: value}))); | |
352 }, | |
353 | |
354 /** | |
355 * @param {number} lineNumber | |
356 * @param {number} columnNumber | |
357 * @return {?{startColumn: number, endColumn: number, type: string}} | |
358 */ | |
359 _backtrackPropertyToken: function(lineNumber, columnNumber) | |
360 { | |
361 var backtrackDepth = 10; | |
362 var tokenPosition = columnNumber; | |
363 var line = this._textEditor.line(lineNumber); | |
364 var seenColon = false; | |
365 | |
366 for (var i = 0; i < backtrackDepth && tokenPosition >= 0; ++i) { | |
367 var token = this._textEditor.tokenAtTextPosition(lineNumber, tokenPo
sition); | |
368 if (!token) | |
369 return null; | |
370 if (token.type === "css-property") | |
371 return seenColon ? token : null; | |
372 if (token.type && !(token.type.indexOf("whitespace") !== -1 || token
.type.startsWith("css-comment"))) | |
373 return null; | |
374 | |
375 if (!token.type && line.substring(token.startColumn, token.endColumn
) === ":") { | |
376 if (!seenColon) | |
377 seenColon = true; | |
378 else | |
379 return null; | |
380 } | |
381 tokenPosition = token.startColumn - 1; | |
382 } | |
383 return null; | |
384 }, | |
385 | |
386 __proto__: WebInspector.UISourceCodeFrame.prototype | |
387 }; | |
OLD | NEW |