UNPKG

@dcloudio/uni-debugger

Version:

uni-app debugger

407 lines (371 loc) 15 kB
/* * Copyright (C) 2013 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ Sources.CSSPlugin = class extends Sources.UISourceCodeFrame.Plugin { /** * @param {!SourceFrame.SourcesTextEditor} textEditor */ constructor(textEditor) { super(); this._textEditor = textEditor; this._swatchPopoverHelper = new InlineEditor.SwatchPopoverHelper(); this._muteSwatchProcessing = false; this._hadSwatchChange = false; /** @type {?InlineEditor.BezierEditor} */ this._bezierEditor = null; /** @type {?TextUtils.TextRange} */ this._editedSwatchTextRange = null; /** @type {?ColorPicker.Spectrum} */ this._spectrum = null; /** @type {?Element} */ this._currentSwatch = null; this._textEditor.configureAutocomplete( {suggestionsCallback: this._cssSuggestions.bind(this), isWordChar: this._isWordChar.bind(this)}); this._textEditor.addEventListener( SourceFrame.SourcesTextEditor.Events.ScrollChanged, this._textEditorScrolled, this); this._textEditor.addEventListener(UI.TextEditor.Events.TextChanged, this._onTextChanged, this); this._updateSwatches(0, this._textEditor.linesCount - 1); this._shortcuts = {}; this._registerShortcuts(); this._boundHandleKeyDown = this._handleKeyDown.bind(this); this._textEditor.element.addEventListener('keydown', this._boundHandleKeyDown, false); } /** * @override * @param {!Workspace.UISourceCode} uiSourceCode * @return {boolean} */ static accepts(uiSourceCode) { return uiSourceCode.contentType().isStyleSheet(); } _registerShortcuts() { const shortcutKeys = UI.ShortcutsScreen.SourcesPanelShortcuts; for (const descriptor of shortcutKeys.IncreaseCSSUnitByOne) this._shortcuts[descriptor.key] = this._handleUnitModification.bind(this, 1); for (const descriptor of shortcutKeys.DecreaseCSSUnitByOne) this._shortcuts[descriptor.key] = this._handleUnitModification.bind(this, -1); for (const descriptor of shortcutKeys.IncreaseCSSUnitByTen) this._shortcuts[descriptor.key] = this._handleUnitModification.bind(this, 10); for (const descriptor of shortcutKeys.DecreaseCSSUnitByTen) this._shortcuts[descriptor.key] = this._handleUnitModification.bind(this, -10); } /** * @param {!Event} event */ _handleKeyDown(event) { const shortcutKey = UI.KeyboardShortcut.makeKeyFromEvent(/** @type {!KeyboardEvent} */ (event)); const handler = this._shortcuts[shortcutKey]; if (handler && handler()) event.consume(true); } _textEditorScrolled() { if (this._swatchPopoverHelper.isShowing()) this._swatchPopoverHelper.hide(true); } /** * @param {string} unit * @param {number} change * @return {?string} */ _modifyUnit(unit, change) { const unitValue = parseInt(unit, 10); if (isNaN(unitValue)) return null; const tail = unit.substring((unitValue).toString().length); return String.sprintf('%d%s', unitValue + change, tail); } /** * @param {number} change * @return {boolean} */ _handleUnitModification(change) { const selection = this._textEditor.selection().normalize(); let token = this._textEditor.tokenAtTextPosition(selection.startLine, selection.startColumn); if (!token) { if (selection.startColumn > 0) token = this._textEditor.tokenAtTextPosition(selection.startLine, selection.startColumn - 1); if (!token) return false; } if (token.type !== 'css-number') return false; const cssUnitRange = new TextUtils.TextRange(selection.startLine, token.startColumn, selection.startLine, token.endColumn); const cssUnitText = this._textEditor.text(cssUnitRange); const newUnitText = this._modifyUnit(cssUnitText, change); if (!newUnitText) return false; this._textEditor.editRange(cssUnitRange, newUnitText); selection.startColumn = token.startColumn; selection.endColumn = selection.startColumn + newUnitText.length; this._textEditor.setSelection(selection); return true; } /** * @param {number} startLine * @param {number} endLine */ _updateSwatches(startLine, endLine) { const swatches = []; const swatchPositions = []; const regexes = [SDK.CSSMetadata.VariableRegex, SDK.CSSMetadata.URLRegex, UI.Geometry.CubicBezier.Regex, Common.Color.Regex]; const handlers = new Map(); handlers.set(Common.Color.Regex, this._createColorSwatch.bind(this)); handlers.set(UI.Geometry.CubicBezier.Regex, this._createBezierSwatch.bind(this)); for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++) { const line = this._textEditor.line(lineNumber).substring(0, Sources.CSSPlugin.maxSwatchProcessingLength); const results = TextUtils.TextUtils.splitStringByRegexes(line, regexes); for (let i = 0; i < results.length; i++) { const result = results[i]; if (result.regexIndex === -1 || !handlers.has(regexes[result.regexIndex])) continue; const delimiters = /[\s:;,(){}]/; const positionBefore = result.position - 1; const positionAfter = result.position + result.value.length; if (positionBefore >= 0 && !delimiters.test(line.charAt(positionBefore)) || positionAfter < line.length && !delimiters.test(line.charAt(positionAfter))) continue; const swatch = handlers.get(regexes[result.regexIndex])(result.value); if (!swatch) continue; swatches.push(swatch); swatchPositions.push(TextUtils.TextRange.createFromLocation(lineNumber, result.position)); } } this._textEditor.operation(putSwatchesInline.bind(this)); /** * @this {Sources.CSSPlugin} */ function putSwatchesInline() { const clearRange = new TextUtils.TextRange(startLine, 0, endLine, this._textEditor.line(endLine).length); this._textEditor.bookmarks(clearRange, Sources.CSSPlugin.SwatchBookmark).forEach(marker => marker.clear()); for (let i = 0; i < swatches.length; i++) { const swatch = swatches[i]; const swatchPosition = swatchPositions[i]; const bookmark = this._textEditor.addBookmark( swatchPosition.startLine, swatchPosition.startColumn, swatch, Sources.CSSPlugin.SwatchBookmark); swatch[Sources.CSSPlugin.SwatchBookmark] = bookmark; } } } /** * @param {string} text * @return {?InlineEditor.ColorSwatch} */ _createColorSwatch(text) { const color = Common.Color.parse(text); if (!color) return null; const swatch = InlineEditor.ColorSwatch.create(); swatch.setColor(color); swatch.iconElement().title = Common.UIString('Open color picker.'); swatch.iconElement().addEventListener('click', this._swatchIconClicked.bind(this, swatch), false); swatch.hideText(true); return swatch; } /** * @param {string} text * @return {?InlineEditor.BezierSwatch} */ _createBezierSwatch(text) { if (!UI.Geometry.CubicBezier.parse(text)) return null; const swatch = InlineEditor.BezierSwatch.create(); swatch.setBezierText(text); swatch.iconElement().title = Common.UIString('Open cubic bezier editor.'); swatch.iconElement().addEventListener('click', this._swatchIconClicked.bind(this, swatch), false); swatch.hideText(true); return swatch; } /** * @param {!Element} swatch * @param {!Event} event */ _swatchIconClicked(swatch, event) { event.consume(true); this._hadSwatchChange = false; this._muteSwatchProcessing = true; const swatchPosition = swatch[Sources.CSSPlugin.SwatchBookmark].position(); this._textEditor.setSelection(swatchPosition); this._editedSwatchTextRange = swatchPosition.clone(); this._editedSwatchTextRange.endColumn += swatch.textContent.length; this._currentSwatch = swatch; if (swatch instanceof InlineEditor.ColorSwatch) this._showSpectrum(swatch); else if (swatch instanceof InlineEditor.BezierSwatch) this._showBezierEditor(swatch); } /** * @param {!InlineEditor.ColorSwatch} swatch */ _showSpectrum(swatch) { if (!this._spectrum) { this._spectrum = new ColorPicker.Spectrum(); this._spectrum.addEventListener(ColorPicker.Spectrum.Events.SizeChanged, this._spectrumResized, this); this._spectrum.addEventListener(ColorPicker.Spectrum.Events.ColorChanged, this._spectrumChanged, this); } this._spectrum.setColor(swatch.color(), swatch.format()); this._swatchPopoverHelper.show(this._spectrum, swatch.iconElement(), this._swatchPopoverHidden.bind(this)); } /** * @param {!Common.Event} event */ _spectrumResized(event) { this._swatchPopoverHelper.reposition(); } /** * @param {!Common.Event} event */ _spectrumChanged(event) { const colorString = /** @type {string} */ (event.data); const color = Common.Color.parse(colorString); if (!color) return; this._currentSwatch.setColor(color); this._changeSwatchText(colorString); } /** * @param {!InlineEditor.BezierSwatch} swatch */ _showBezierEditor(swatch) { if (!this._bezierEditor) { this._bezierEditor = new InlineEditor.BezierEditor(); this._bezierEditor.addEventListener(InlineEditor.BezierEditor.Events.BezierChanged, this._bezierChanged, this); } let cubicBezier = UI.Geometry.CubicBezier.parse(swatch.bezierText()); if (!cubicBezier) { cubicBezier = /** @type {!UI.Geometry.CubicBezier} */ (UI.Geometry.CubicBezier.parse('linear')); } this._bezierEditor.setBezier(cubicBezier); this._swatchPopoverHelper.show(this._bezierEditor, swatch.iconElement(), this._swatchPopoverHidden.bind(this)); } /** * @param {!Common.Event} event */ _bezierChanged(event) { const bezierString = /** @type {string} */ (event.data); this._currentSwatch.setBezierText(bezierString); this._changeSwatchText(bezierString); } /** * @param {string} text */ _changeSwatchText(text) { this._hadSwatchChange = true; this._textEditor.editRange( /** @type {!TextUtils.TextRange} */ (this._editedSwatchTextRange), text, '*swatch-text-changed'); this._editedSwatchTextRange.endColumn = this._editedSwatchTextRange.startColumn + text.length; } /** * @param {boolean} commitEdit */ _swatchPopoverHidden(commitEdit) { this._muteSwatchProcessing = false; if (!commitEdit && this._hadSwatchChange) this._textEditor.undo(); } /** * @param {!Common.Event} event */ _onTextChanged(event) { if (!this._muteSwatchProcessing) this._updateSwatches(event.data.newRange.startLine, event.data.newRange.endLine); } /** * @param {string} char * @return {boolean} */ _isWordChar(char) { return TextUtils.TextUtils.isWordChar(char) || char === '.' || char === '-' || char === '$'; } /** * @param {!TextUtils.TextRange} prefixRange * @param {!TextUtils.TextRange} substituteRange * @return {?Promise.<!UI.SuggestBox.Suggestions>} */ _cssSuggestions(prefixRange, substituteRange) { const prefix = this._textEditor.text(prefixRange); if (prefix.startsWith('$')) return null; const propertyToken = this._backtrackPropertyToken(prefixRange.startLine, prefixRange.startColumn - 1); if (!propertyToken) return null; const line = this._textEditor.line(prefixRange.startLine); const tokenContent = line.substring(propertyToken.startColumn, propertyToken.endColumn); const propertyValues = SDK.cssMetadata().propertyValues(tokenContent); return Promise.resolve(propertyValues.filter(value => value.startsWith(prefix)).map(value => ({text: value}))); } /** * @param {number} lineNumber * @param {number} columnNumber * @return {?{startColumn: number, endColumn: number, type: string}} */ _backtrackPropertyToken(lineNumber, columnNumber) { const backtrackDepth = 10; let tokenPosition = columnNumber; const line = this._textEditor.line(lineNumber); let seenColon = false; for (let i = 0; i < backtrackDepth && tokenPosition >= 0; ++i) { const token = this._textEditor.tokenAtTextPosition(lineNumber, tokenPosition); if (!token) return null; if (token.type === 'css-property') return seenColon ? token : null; if (token.type && !(token.type.indexOf('whitespace') !== -1 || token.type.startsWith('css-comment'))) return null; if (!token.type && line.substring(token.startColumn, token.endColumn) === ':') { if (!seenColon) seenColon = true; else return null; } tokenPosition = token.startColumn - 1; } return null; } /** * @override */ dispose() { if (this._swatchPopoverHelper.isShowing()) this._swatchPopoverHelper.hide(true); this._textEditor.removeEventListener( SourceFrame.SourcesTextEditor.Events.ScrollChanged, this._textEditorScrolled, this); this._textEditor.removeEventListener(UI.TextEditor.Events.TextChanged, this._onTextChanged, this); this._textEditor.bookmarks(this._textEditor.fullRange(), Sources.CSSPlugin.SwatchBookmark) .forEach(marker => marker.clear()); this._textEditor.element.removeEventListener('keydown', this._boundHandleKeyDown, false); } }; /** @type {number} */ Sources.CSSPlugin.maxSwatchProcessingLength = 300; /** @type {symbol} */ Sources.CSSPlugin.SwatchBookmark = Symbol('swatch');