| 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 |