UNPKG

monaca-lib

Version:

Monaca cloud API bindings for JavaScript

1,417 lines (1,283 loc) 74.4 kB
/* * Copyright (C) 2012 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. */ /** * @constructor * @extends {WebInspector.VBox} * @param {?string} url * @param {!WebInspector.TextEditorDelegate} delegate */ WebInspector.CodeMirrorTextEditor = function(url, delegate) { WebInspector.VBox.call(this); this._delegate = delegate; this._url = url; this.registerRequiredCSS("cm/codemirror.css"); this.registerRequiredCSS("source_frame/cmdevtools.css"); this.element.appendChild(WebInspector.CodeMirrorUtils.createThemeStyle()); this._codeMirror = new window.CodeMirror(this.element, { lineNumbers: true, gutters: ["CodeMirror-linenumbers"], matchBrackets: true, smartIndent: false, styleSelectedText: true, electricChars: false }); this._codeMirrorElement = this.element.lastElementChild; this._codeMirror._codeMirrorTextEditor = this; CodeMirror.keyMap["devtools-common"] = { "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentLess", "Enter": "smartNewlineAndIndent", "Ctrl-Space": "autocomplete", "Esc": "dismissMultipleSelections", "Ctrl-M": "gotoMatchingBracket" }; CodeMirror.keyMap["devtools-pc"] = { "Ctrl-A": "selectAll", "Ctrl-Z": "undoAndReveal", "Shift-Ctrl-Z": "redoAndReveal", "Ctrl-Y": "redo", "Ctrl-Home": "goDocStart", "Ctrl-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "moveCamelLeft", "Alt-Right": "moveCamelRight", "Shift-Alt-Left": "selectCamelLeft", "Shift-Alt-Right": "selectCamelRight", "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-/": "toggleComment", "Ctrl-D": "selectNextOccurrence", "Ctrl-U": "undoLastSelection", fallthrough: "devtools-common" }; CodeMirror.keyMap["devtools-mac"] = { "Cmd-A" : "selectAll", "Cmd-Z" : "undoAndReveal", "Shift-Cmd-Z": "redoAndReveal", "Cmd-Up": "goDocStart", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", "Alt-Right": "goGroupRight", "Ctrl-Left": "moveCamelLeft", "Ctrl-Right": "moveCamelRight", "Shift-Ctrl-Left": "selectCamelLeft", "Shift-Ctrl-Right": "selectCamelRight", "Cmd-Left": "goLineStartSmart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore", "Alt-Delete": "delGroupAfter", "Cmd-/": "toggleComment", "Cmd-D": "selectNextOccurrence", "Cmd-U": "undoLastSelection", fallthrough: "devtools-common" }; WebInspector.settings.textEditorIndent.addChangeListener(this._updateEditorIndentation, this); WebInspector.settings.textEditorAutoDetectIndent.addChangeListener(this._updateEditorIndentation, this); this._updateEditorIndentation(); WebInspector.settings.showWhitespacesInEditor.addChangeListener(this._updateCodeMirrorMode, this); WebInspector.settings.textEditorBracketMatching.addChangeListener(this._enableBracketMatchingIfNeeded, this); this._enableBracketMatchingIfNeeded(); this._codeMirror.setOption("keyMap", WebInspector.isMac() ? "devtools-mac" : "devtools-pc"); CodeMirror.commands.maybeAvoidSmartSingleQuotes = this._maybeAvoidSmartQuotes.bind(this, "'"); CodeMirror.commands.maybeAvoidSmartDoubleQuotes = this._maybeAvoidSmartQuotes.bind(this, "\""); this._codeMirror.addKeyMap({ "'": "maybeAvoidSmartSingleQuotes", "'\"'": "maybeAvoidSmartDoubleQuotes" }); this._codeMirror.setOption("flattenSpans", false); this._codeMirror.setOption("maxHighlightLength", WebInspector.CodeMirrorTextEditor.maxHighlightLength); this._codeMirror.setOption("mode", null); this._codeMirror.setOption("crudeMeasuringFrom", 1000); this._shouldClearHistory = true; this._lineSeparator = "\n"; this._autocompleteController = new WebInspector.TextEditorAutocompleteController(this, this._codeMirror); this._tokenHighlighter = new WebInspector.CodeMirrorTextEditor.TokenHighlighter(this, this._codeMirror); this._blockIndentController = new WebInspector.CodeMirrorTextEditor.BlockIndentController(this._codeMirror); this._fixWordMovement = new WebInspector.CodeMirrorTextEditor.FixWordMovement(this._codeMirror); this._selectNextOccurrenceController = new WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController(this, this._codeMirror); WebInspector.settings.textEditorAutocompletion.addChangeListener(this._enableAutocompletionIfNeeded, this); this._enableAutocompletionIfNeeded(); this._codeMirror.on("changes", this._changes.bind(this)); this._codeMirror.on("gutterClick", this._gutterClick.bind(this)); this._codeMirror.on("cursorActivity", this._cursorActivity.bind(this)); this._codeMirror.on("beforeSelectionChange", this._beforeSelectionChange.bind(this)); this._codeMirror.on("scroll", this._scroll.bind(this)); this._codeMirror.on("focus", this._focus.bind(this)); this._codeMirror.on("keyHandled", this._onKeyHandled.bind(this)); this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false); /** * @this {WebInspector.CodeMirrorTextEditor} */ function updateAnticipateJumpFlag(value) { this._isHandlingMouseDownEvent = value; } this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, true), true); this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, false), false); this.element.style.overflow = "hidden"; this._codeMirrorElement.classList.add("source-code"); this._codeMirrorElement.classList.add("fill"); this._elementToWidget = new Map(); this._nestedUpdatesCounter = 0; this.element.addEventListener("focus", this._handleElementFocus.bind(this), false); this.element.addEventListener("keydown", this._handleKeyDown.bind(this), true); this.element.addEventListener("keydown", this._handlePostKeyDown.bind(this), false); this.element.tabIndex = 0; this._setupWhitespaceHighlight(); } WebInspector.CodeMirrorTextEditor.maxHighlightLength = 1000; /** * @param {!CodeMirror} codeMirror */ WebInspector.CodeMirrorTextEditor.autocompleteCommand = function(codeMirror) { codeMirror._codeMirrorTextEditor._autocompleteController.autocomplete(); } CodeMirror.commands.autocomplete = WebInspector.CodeMirrorTextEditor.autocompleteCommand; /** * @param {!CodeMirror} codeMirror */ WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand = function(codeMirror) { codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.undoLastSelection(); } CodeMirror.commands.undoLastSelection = WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand; /** * @param {!CodeMirror} codeMirror */ WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand = function(codeMirror) { codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.selectNextOccurrence(); } CodeMirror.commands.selectNextOccurrence = WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand; /** * @param {boolean} shift * @param {!CodeMirror} codeMirror */ WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand = function(shift, codeMirror) { codeMirror._codeMirrorTextEditor._doCamelCaseMovement(-1, shift); } CodeMirror.commands.moveCamelLeft = WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand.bind(null, false); CodeMirror.commands.selectCamelLeft = WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand.bind(null, true); /** * @param {boolean} shift * @param {!CodeMirror} codeMirror */ WebInspector.CodeMirrorTextEditor.moveCamelRightCommand = function(shift, codeMirror) { codeMirror._codeMirrorTextEditor._doCamelCaseMovement(1, shift); } CodeMirror.commands.moveCamelRight = WebInspector.CodeMirrorTextEditor.moveCamelRightCommand.bind(null, false); CodeMirror.commands.selectCamelRight = WebInspector.CodeMirrorTextEditor.moveCamelRightCommand.bind(null, true); /** * @param {!CodeMirror} codeMirror */ CodeMirror.commands.smartNewlineAndIndent = function(codeMirror) { codeMirror.operation(innerSmartNewlineAndIndent.bind(null, codeMirror)); function innerSmartNewlineAndIndent(codeMirror) { var selections = codeMirror.listSelections(); var replacements = []; for (var i = 0; i < selections.length; ++i) { var selection = selections[i]; var cur = CodeMirror.cmpPos(selection.head, selection.anchor) < 0 ? selection.head : selection.anchor; var line = codeMirror.getLine(cur.line); var indent = WebInspector.TextUtils.lineIndent(line); replacements.push("\n" + indent.substring(0, Math.min(cur.ch, indent.length))); } codeMirror.replaceSelections(replacements); codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces(); } } /** * @param {!CodeMirror} codeMirror */ CodeMirror.commands.gotoMatchingBracket = function(codeMirror) { var updatedSelections = []; var selections = codeMirror.listSelections(); for (var i = 0; i < selections.length; ++i) { var selection = selections[i]; var cursor = selection.head; var matchingBracket = codeMirror.findMatchingBracket(cursor, false, { maxScanLines: 10000 }); var updatedHead = cursor; if (matchingBracket && matchingBracket.match) { var columnCorrection = CodeMirror.cmpPos(matchingBracket.from, cursor) === 0 ? 1 : 0; updatedHead = new CodeMirror.Pos(matchingBracket.to.line, matchingBracket.to.ch + columnCorrection); } updatedSelections.push({ anchor: updatedHead, head: updatedHead }); } codeMirror.setSelections(updatedSelections); } /** * @param {!CodeMirror} codemirror */ CodeMirror.commands.undoAndReveal = function(codemirror) { var scrollInfo = codemirror.getScrollInfo(); codemirror.execCommand("undo"); var cursor = codemirror.getCursor("start"); codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo); codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete(); } /** * @param {!CodeMirror} codemirror */ CodeMirror.commands.redoAndReveal = function(codemirror) { var scrollInfo = codemirror.getScrollInfo(); codemirror.execCommand("redo"); var cursor = codemirror.getCursor("start"); codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo); codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete(); } /** * @return {!Object|undefined} */ CodeMirror.commands.dismissMultipleSelections = function(codemirror) { var selections = codemirror.listSelections(); var selection = selections[0]; if (selections.length === 1) { if (codemirror._codeMirrorTextEditor._isSearchActive()) return CodeMirror.Pass; if (WebInspector.CodeMirrorUtils.toRange(selection.anchor, selection.head).isEmpty()) return CodeMirror.Pass; codemirror.setSelection(selection.anchor, selection.anchor, {scroll: false}); codemirror._codeMirrorTextEditor._revealLine(selection.anchor.line); return; } codemirror.setSelection(selection.anchor, selection.head, {scroll: false}); codemirror._codeMirrorTextEditor._revealLine(selection.anchor.line); } WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold = 2000; WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan = 16; WebInspector.CodeMirrorTextEditor.MaxEditableTextSize = 1024 * 1024 * 10; WebInspector.CodeMirrorTextEditor.prototype = { /** * @param {string=} additionalWordChars * @return {!WebInspector.CodeMirrorDictionary} */ createTextDictionary: function(additionalWordChars) { return new WebInspector.CodeMirrorDictionary(this._codeMirror, additionalWordChars); }, _enableAutocompletionIfNeeded: function() { this._autocompleteController.setEnabled(WebInspector.settings.textEditorAutocompletion.get()); }, /** * @param {string} quoteCharacter * @return {*} */ _maybeAvoidSmartQuotes: function(quoteCharacter) { if (!WebInspector.settings.textEditorBracketMatching.get()) return CodeMirror.Pass; var selections = this.selections(); if (selections.length !== 1 || !selections[0].isEmpty()) return CodeMirror.Pass; var selection = selections[0]; var token = this.tokenAtTextPosition(selection.startLine, selection.startColumn); if (!token || !token.type || token.type.indexOf("string") === -1) return CodeMirror.Pass; var line = this.line(selection.startLine); var tokenValue = line.substring(token.startColumn, token.endColumn); if (tokenValue[0] === tokenValue[tokenValue.length - 1] && (tokenValue[0] === "'" || tokenValue[0] === "\"")) return CodeMirror.Pass; this._codeMirror.replaceSelection(quoteCharacter); }, _onKeyHandled: function() { WebInspector.shortcutRegistry.dismissPendingShortcutAction(); }, _onAutoAppendedSpaces: function() { this._autoAppendedSpaces = this._autoAppendedSpaces || []; for (var i = 0; i < this._autoAppendedSpaces.length; ++i) { var position = this._autoAppendedSpaces[i].resolve(); if (!position) continue; var line = this.line(position.lineNumber); if (line.length === position.columnNumber && WebInspector.TextUtils.lineIndent(line).length === line.length) this._codeMirror.replaceRange("", new CodeMirror.Pos(position.lineNumber, 0), new CodeMirror.Pos(position.lineNumber, position.columnNumber)); } this._autoAppendedSpaces = []; var selections = this.selections(); for (var i = 0; i < selections.length; ++i) { var selection = selections[i]; this._autoAppendedSpaces.push(this.textEditorPositionHandle(selection.startLine, selection.startColumn)); } }, /** * @param {number} lineNumber * @param {number} lineLength * @param {number} charNumber * @return {{lineNumber: number, columnNumber: number}} */ _normalizePositionForOverlappingColumn: function(lineNumber, lineLength, charNumber) { var linesCount = this._codeMirror.lineCount(); var columnNumber = charNumber; if (charNumber < 0 && lineNumber > 0) { --lineNumber; columnNumber = this.line(lineNumber).length; } else if (charNumber >= lineLength && lineNumber < linesCount - 1) { ++lineNumber; columnNumber = 0; } else { columnNumber = Number.constrain(charNumber, 0, lineLength); } return { lineNumber: lineNumber, columnNumber: columnNumber }; }, /** * @param {number} lineNumber * @param {number} columnNumber * @param {number} direction * @return {{lineNumber: number, columnNumber: number}} */ _camelCaseMoveFromPosition: function(lineNumber, columnNumber, direction) { /** * @param {number} charNumber * @param {number} length * @return {boolean} */ function valid(charNumber, length) { return charNumber >= 0 && charNumber < length; } /** * @param {string} text * @param {number} charNumber * @return {boolean} */ function isWordStart(text, charNumber) { var position = charNumber; var nextPosition = charNumber + 1; return valid(position, text.length) && valid(nextPosition, text.length) && WebInspector.TextUtils.isWordChar(text[position]) && WebInspector.TextUtils.isWordChar(text[nextPosition]) && WebInspector.TextUtils.isUpperCase(text[position]) && WebInspector.TextUtils.isLowerCase(text[nextPosition]); } /** * @param {string} text * @param {number} charNumber * @return {boolean} */ function isWordEnd(text, charNumber) { var position = charNumber; var prevPosition = charNumber - 1; return valid(position, text.length) && valid(prevPosition, text.length) && WebInspector.TextUtils.isWordChar(text[position]) && WebInspector.TextUtils.isWordChar(text[prevPosition]) && WebInspector.TextUtils.isUpperCase(text[position]) && WebInspector.TextUtils.isLowerCase(text[prevPosition]); } /** * @param {number} lineNumber * @param {number} lineLength * @param {number} columnNumber * @return {{lineNumber: number, columnNumber: number}} */ function constrainPosition(lineNumber, lineLength, columnNumber) { return { lineNumber: lineNumber, columnNumber: Number.constrain(columnNumber, 0, lineLength) }; } var text = this.line(lineNumber); var length = text.length; if ((columnNumber === length && direction === 1) || (columnNumber === 0 && direction === -1)) return this._normalizePositionForOverlappingColumn(lineNumber, length, columnNumber + direction); var charNumber = direction === 1 ? columnNumber : columnNumber - 1; // Move through initial spaces if any. while (valid(charNumber, length) && WebInspector.TextUtils.isSpaceChar(text[charNumber])) charNumber += direction; if (!valid(charNumber, length)) return constrainPosition(lineNumber, length, charNumber); if (WebInspector.TextUtils.isStopChar(text[charNumber])) { while (valid(charNumber, length) && WebInspector.TextUtils.isStopChar(text[charNumber])) charNumber += direction; if (!valid(charNumber, length)) return constrainPosition(lineNumber, length, charNumber); return { lineNumber: lineNumber, columnNumber: direction === -1 ? charNumber + 1 : charNumber }; } charNumber += direction; while (valid(charNumber, length) && !isWordStart(text, charNumber) && !isWordEnd(text, charNumber) && WebInspector.TextUtils.isWordChar(text[charNumber])) charNumber += direction; if (!valid(charNumber, length)) return constrainPosition(lineNumber, length, charNumber); if (isWordStart(text, charNumber) || isWordEnd(text, charNumber)) { return { lineNumber: lineNumber, columnNumber: charNumber }; } return { lineNumber: lineNumber, columnNumber: direction === -1 ? charNumber + 1 : charNumber }; }, /** * @param {number} direction * @param {boolean} shift */ _doCamelCaseMovement: function(direction, shift) { var selections = this.selections(); for (var i = 0; i < selections.length; ++i) { var selection = selections[i]; var move = this._camelCaseMoveFromPosition(selection.endLine, selection.endColumn, direction); selection.endLine = move.lineNumber; selection.endColumn = move.columnNumber; if (!shift) selections[i] = selection.collapseToEnd(); } this.setSelections(selections); }, dispose: function() { WebInspector.settings.textEditorIndent.removeChangeListener(this._updateEditorIndentation, this); WebInspector.settings.textEditorAutoDetectIndent.removeChangeListener(this._updateEditorIndentation, this); WebInspector.settings.showWhitespacesInEditor.removeChangeListener(this._updateCodeMirrorMode, this); WebInspector.settings.textEditorBracketMatching.removeChangeListener(this._enableBracketMatchingIfNeeded, this); WebInspector.settings.textEditorAutocompletion.removeChangeListener(this._enableAutocompletionIfNeeded, this); }, _enableBracketMatchingIfNeeded: function() { this._codeMirror.setOption("autoCloseBrackets", WebInspector.settings.textEditorBracketMatching.get() ? { explode: false } : false); }, /** * @override */ wasShown: function() { if (this._wasOnceShown) return; this._wasOnceShown = true; this._codeMirror.refresh(); }, /** * @override */ willHide: function() { delete this._editorSizeInSync; }, _guessIndentationLevel: function() { var tabRegex = /^\t+/; var tabLines = 0; var indents = {}; var maxScanLines = 1000; function processLine(lineHandle) { var text = lineHandle.text; if (text.length === 0 || !WebInspector.TextUtils.isSpaceChar(text[0])) return; if (tabRegex.test(text)) { ++tabLines; return; } var i = 0; while (i < text.length && WebInspector.TextUtils.isSpaceChar(text[i])) ++i; if (i % 2 !== 0) return; indents[i] = 1 + (indents[i] || 0); } this._codeMirror.eachLine(0, maxScanLines, processLine); var linesCountPerIndentThreshold = 3 * Math.min(maxScanLines, this.linesCount) / 100; if (tabLines && tabLines > linesCountPerIndentThreshold) return "\t"; var minimumIndent = Infinity; for (var i in indents) { if (indents[i] < linesCountPerIndentThreshold) continue; var indent = parseInt(i, 10); if (minimumIndent > indent) minimumIndent = indent; } if (minimumIndent === Infinity) return WebInspector.settings.textEditorIndent.get(); return new Array(minimumIndent + 1).join(" "); }, _updateEditorIndentation: function() { var extraKeys = {}; var indent = WebInspector.settings.textEditorIndent.get(); if (WebInspector.settings.textEditorAutoDetectIndent.get()) indent = this._guessIndentationLevel(); if (indent === WebInspector.TextUtils.Indent.TabCharacter) { this._codeMirror.setOption("indentWithTabs", true); this._codeMirror.setOption("indentUnit", 4); } else { this._codeMirror.setOption("indentWithTabs", false); this._codeMirror.setOption("indentUnit", indent.length); extraKeys.Tab = function(codeMirror) { if (codeMirror.somethingSelected()) return CodeMirror.Pass; var pos = codeMirror.getCursor("head"); codeMirror.replaceRange(indent.substring(pos.ch % indent.length), codeMirror.getCursor()); } } this._codeMirror.setOption("extraKeys", extraKeys); this._indentationLevel = indent; }, /** * @return {string} */ indent: function() { return this._indentationLevel; }, /** * @return {boolean} */ _isSearchActive: function() { return !!this._tokenHighlighter.highlightedRegex(); }, /** * @param {!RegExp} regex * @param {?WebInspector.TextRange} range */ highlightSearchResults: function(regex, range) { /** * @this {WebInspector.CodeMirrorTextEditor} */ function innerHighlightRegex() { if (range) { this._revealLine(range.startLine); if (range.endColumn > WebInspector.CodeMirrorTextEditor.maxHighlightLength) this.setSelection(range); else this.setSelection(WebInspector.TextRange.createFromLocation(range.startLine, range.startColumn)); } else { // Collapse selection to end on search start so that we jump to next occurrence on the first enter press. this.setSelection(this.selection().collapseToEnd()); } this._tokenHighlighter.highlightSearchResults(regex, range); } if (!this._selectionBeforeSearch) this._selectionBeforeSearch = this.selection(); this._codeMirror.operation(innerHighlightRegex.bind(this)); }, cancelSearchResultsHighlight: function() { this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter)); if (this._selectionBeforeSearch) { this._reportJump(this._selectionBeforeSearch, this.selection()); delete this._selectionBeforeSearch; } }, undo: function() { this._codeMirror.undo(); }, redo: function() { this._codeMirror.redo(); }, _setupWhitespaceHighlight: function() { var doc = this.element.ownerDocument; if (doc._codeMirrorWhitespaceStyleInjected || !WebInspector.settings.showWhitespacesInEditor.get()) return; doc._codeMirrorWhitespaceStyleInjected = true; const classBase = ".show-whitespaces .CodeMirror .cm-whitespace-"; const spaceChar = "·"; var spaceChars = ""; var rules = ""; for (var i = 1; i <= WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan; ++i) { spaceChars += spaceChar; var rule = classBase + i + "::before { content: '" + spaceChars + "';}\n"; rules += rule; } var style = doc.createElement("style"); style.textContent = rules; doc.head.appendChild(style); }, _handleKeyDown: function(e) { if (this._autocompleteController.keyDown(e)) e.consume(true); }, _handlePostKeyDown: function(e) { if (e.defaultPrevented) e.consume(true); }, /** * @param {!WebInspector.TextEditorAutocompleteDelegate} delegate */ setAutocompleteDelegate: function(delegate) { this._autocompleteController.setDelegate(delegate); }, /** * @param {number} lineNumber * @param {number} column * @return {?{x: number, y: number, height: number}} */ cursorPositionToCoordinates: function(lineNumber, column) { if (lineNumber >= this._codeMirror.lineCount() || lineNumber < 0 || column < 0 || column > this._codeMirror.getLine(lineNumber).length) return null; var metrics = this._codeMirror.cursorCoords(new CodeMirror.Pos(lineNumber, column)); return { x: metrics.left, y: metrics.top, height: metrics.bottom - metrics.top }; }, /** * @param {number} x * @param {number} y * @return {?WebInspector.TextRange} */ coordinatesToCursorPosition: function(x, y) { var element = this.element.ownerDocument.elementFromPoint(x, y); if (!element || !element.isSelfOrDescendant(this._codeMirror.getWrapperElement())) return null; var gutterBox = this._codeMirror.getGutterElement().boxInWindow(); if (x >= gutterBox.x && x <= gutterBox.x + gutterBox.width && y >= gutterBox.y && y <= gutterBox.y + gutterBox.height) return null; var coords = this._codeMirror.coordsChar({left: x, top: y}); return WebInspector.CodeMirrorUtils.toRange(coords, coords); }, /** * @param {number} lineNumber * @param {number} column * @return {?{startColumn: number, endColumn: number, type: string}} */ tokenAtTextPosition: function(lineNumber, column) { if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount()) return null; var token = this._codeMirror.getTokenAt(new CodeMirror.Pos(lineNumber, (column || 0) + 1)); if (!token) return null; return { startColumn: token.start, endColumn: token.end, type: token.type }; }, /** * @param {!WebInspector.TextRange} textRange * @return {string} */ copyRange: function(textRange) { var pos = WebInspector.CodeMirrorUtils.toPos(textRange.normalize()); return this._codeMirror.getRange(pos.start, pos.end); }, /** * @return {boolean} */ isClean: function() { return this._codeMirror.isClean(); }, markClean: function() { this._codeMirror.markClean(); }, _hasLongLines: function() { function lineIterator(lineHandle) { if (lineHandle.text.length > WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold) hasLongLines = true; return hasLongLines; } var hasLongLines = false; this._codeMirror.eachLine(lineIterator); return hasLongLines; }, /** * @param {string} mimeType * @return {string} */ _whitespaceOverlayMode: function(mimeType) { var modeName = CodeMirror.mimeModes[mimeType] ? (CodeMirror.mimeModes[mimeType].name || CodeMirror.mimeModes[mimeType]) : CodeMirror.mimeModes["text/plain"]; modeName += "+whitespaces"; if (CodeMirror.modes[modeName]) return modeName; function modeConstructor(config, parserConfig) { function nextToken(stream) { if (stream.peek() === " ") { var spaces = 0; while (spaces < WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan && stream.peek() === " ") { ++spaces; stream.next(); } return "whitespace whitespace-" + spaces; } while (!stream.eol() && stream.peek() !== " ") stream.next(); return null; } var whitespaceMode = { token: nextToken }; return CodeMirror.overlayMode(CodeMirror.getMode(config, mimeType), whitespaceMode, false); } CodeMirror.defineMode(modeName, modeConstructor); return modeName; }, _enableLongLinesMode: function() { this._codeMirror.setOption("styleSelectedText", false); this._longLinesMode = true; }, _disableLongLinesMode: function() { this._codeMirror.setOption("styleSelectedText", true); this._longLinesMode = false; }, _updateCodeMirrorMode: function() { this._setupWhitespaceHighlight(); var showWhitespaces = WebInspector.settings.showWhitespacesInEditor.get(); this.element.classList.toggle("show-whitespaces", showWhitespaces); this._codeMirror.setOption("mode", showWhitespaces ? this._whitespaceOverlayMode(this._mimeType) : this._mimeType); }, /** * @param {string} mimeType */ setMimeType: function(mimeType) { this._mimeType = mimeType; if (this._hasLongLines()) this._enableLongLinesMode(); else this._disableLongLinesMode(); this._updateCodeMirrorMode(); }, /** * @param {boolean} readOnly */ setReadOnly: function(readOnly) { this.element.classList.toggle("CodeMirror-readonly", readOnly); this._codeMirror.setOption("readOnly", readOnly); }, /** * @return {boolean} */ readOnly: function() { return !!this._codeMirror.getOption("readOnly"); }, /** * @param {!Object} highlightDescriptor */ removeHighlight: function(highlightDescriptor) { highlightDescriptor.clear(); }, /** * @param {!WebInspector.TextRange} range * @param {string} cssClass * @return {!Object} */ highlightRange: function(range, cssClass) { cssClass = "CodeMirror-persist-highlight " + cssClass; var pos = WebInspector.CodeMirrorUtils.toPos(range); ++pos.end.ch; return this._codeMirror.markText(pos.start, pos.end, { className: cssClass, startStyle: cssClass + "-start", endStyle: cssClass + "-end" }); }, /** * @override * @return {!Element} */ defaultFocusedElement: function() { return this.element; }, focus: function() { this._codeMirror.focus(); }, _handleElementFocus: function() { this._codeMirror.focus(); }, beginUpdates: function() { ++this._nestedUpdatesCounter; }, endUpdates: function() { if (!--this._nestedUpdatesCounter) this._codeMirror.refresh(); }, /** * @param {number} lineNumber */ _revealLine: function(lineNumber) { this._innerRevealLine(lineNumber, this._codeMirror.getScrollInfo()); }, /** * @param {number} lineNumber * @param {!{left: number, top: number, width: number, height: number, clientWidth: number, clientHeight: number}} scrollInfo */ _innerRevealLine: function(lineNumber, scrollInfo) { var topLine = this._codeMirror.lineAtHeight(scrollInfo.top, "local"); var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local"); var linesPerScreen = bottomLine - topLine + 1; if (lineNumber < topLine) { var topLineToReveal = Math.max(lineNumber - (linesPerScreen / 2) + 1, 0) | 0; this._codeMirror.scrollIntoView(new CodeMirror.Pos(topLineToReveal, 0)); } else if (lineNumber > bottomLine) { var bottomLineToReveal = Math.min(lineNumber + (linesPerScreen / 2) - 1, this.linesCount - 1) | 0; this._codeMirror.scrollIntoView(new CodeMirror.Pos(bottomLineToReveal, 0)); } }, _gutterClick: function(instance, lineNumber, gutter, event) { this.dispatchEventToListeners(WebInspector.CodeMirrorTextEditor.Events.GutterClick, { lineNumber: lineNumber, event: event }); }, _contextMenu: function(event) { var contextMenu = new WebInspector.ContextMenu(event); var target = event.target.enclosingNodeOrSelfWithClass("CodeMirror-gutter-elt"); if (target) this._delegate.populateLineGutterContextMenu(contextMenu, parseInt(target.textContent, 10) - 1); else this._delegate.populateTextAreaContextMenu(contextMenu, 0); contextMenu.appendApplicableItems(this); contextMenu.show(); }, /** * @param {number} lineNumber * @param {boolean} disabled * @param {boolean} conditional */ addBreakpoint: function(lineNumber, disabled, conditional) { if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount()) return; var className = "cm-breakpoint" + (conditional ? " cm-breakpoint-conditional" : "") + (disabled ? " cm-breakpoint-disabled" : ""); this._codeMirror.addLineClass(lineNumber, "wrap", className); }, /** * @param {number} lineNumber */ removeBreakpoint: function(lineNumber) { if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount()) return; var wrapClasses = this._codeMirror.getLineHandle(lineNumber).wrapClass; if (!wrapClasses) return; var classes = wrapClasses.split(" "); for (var i = 0; i < classes.length; ++i) { if (classes[i].startsWith("cm-breakpoint")) this._codeMirror.removeLineClass(lineNumber, "wrap", classes[i]); } }, /** * @param {number} lineNumber */ setExecutionLine: function(lineNumber) { this.clearPositionHighlight(); this._executionLine = this._codeMirror.getLineHandle(lineNumber); if (!this._executionLine) return; this._codeMirror.addLineClass(this._executionLine, "wrap", "cm-execution-line"); }, clearExecutionLine: function() { this.clearPositionHighlight(); if (this._executionLine) this._codeMirror.removeLineClass(this._executionLine, "wrap", "cm-execution-line"); delete this._executionLine; }, /** * @param {number} lineNumber * @param {string} className * @param {boolean} toggled */ toggleLineClass: function(lineNumber, className, toggled) { if (this.hasLineClass(lineNumber, className) === toggled) return; var lineHandle = this._codeMirror.getLineHandle(lineNumber); if (!lineHandle) return; if (toggled) this._codeMirror.addLineClass(lineHandle, "wrap", className); else this._codeMirror.removeLineClass(lineHandle, "wrap", className); }, /** * @param {number} lineNumber * @param {string} className * @return {boolean} */ hasLineClass: function(lineNumber, className) { var lineInfo = this._codeMirror.lineInfo(lineNumber); var wrapClass = lineInfo.wrapClass || ""; var classNames = wrapClass.split(" "); return classNames.indexOf(className) !== -1; }, /** * @param {number} lineNumber * @param {!Element} element */ addDecoration: function(lineNumber, element) { var widget = this._codeMirror.addLineWidget(lineNumber, element); this._elementToWidget.set(element, widget); }, /** * @param {number} lineNumber * @param {!Element} element */ removeDecoration: function(lineNumber, element) { var widget = this._elementToWidget.remove(element); if (widget) this._codeMirror.removeLineWidget(widget); }, /** * @param {number} lineNumber 0-based * @param {number=} columnNumber * @param {boolean=} shouldHighlight */ revealPosition: function(lineNumber, columnNumber, shouldHighlight) { lineNumber = Number.constrain(lineNumber, 0, this._codeMirror.lineCount() - 1); if (typeof columnNumber !== "number") columnNumber = 0; columnNumber = Number.constrain(columnNumber, 0, this._codeMirror.getLine(lineNumber).length); this.clearPositionHighlight(); this._highlightedLine = this._codeMirror.getLineHandle(lineNumber); if (!this._highlightedLine) return; this._revealLine(lineNumber); if (shouldHighlight) { this._codeMirror.addLineClass(this._highlightedLine, null, "cm-highlight"); this._clearHighlightTimeout = setTimeout(this.clearPositionHighlight.bind(this), 2000); } this.setSelection(WebInspector.TextRange.createFromLocation(lineNumber, columnNumber)); }, clearPositionHighlight: function() { if (this._clearHighlightTimeout) clearTimeout(this._clearHighlightTimeout); delete this._clearHighlightTimeout; if (this._highlightedLine) this._codeMirror.removeLineClass(this._highlightedLine, null, "cm-highlight"); delete this._highlightedLine; }, /** * @override * @return {!Array.<!Element>} */ elementsToRestoreScrollPositionsFor: function() { return []; }, /** * @param {number} width * @param {number} height */ _updatePaddingBottom: function(width, height) { var scrollInfo = this._codeMirror.getScrollInfo(); var newPaddingBottom; var linesElement = this._codeMirrorElement.querySelector(".CodeMirror-lines"); var lineCount = this._codeMirror.lineCount(); if (lineCount <= 1) newPaddingBottom = 0; else newPaddingBottom = Math.max(scrollInfo.clientHeight - this._codeMirror.getLineHandle(this._codeMirror.lastLine()).height, 0); newPaddingBottom += "px"; linesElement.style.paddingBottom = newPaddingBottom; this._codeMirror.setSize(width, height); }, _resizeEditor: function() { var parentElement = this.element.parentElement; if (!parentElement || !this.isShowing()) return; var scrollLeft = this._codeMirror.doc.scrollLeft; var scrollTop = this._codeMirror.doc.scrollTop; var width = parentElement.offsetWidth; var height = parentElement.offsetHeight - this.element.offsetTop; this._codeMirror.setSize(width, height); this._updatePaddingBottom(width, height); this._codeMirror.scrollTo(scrollLeft, scrollTop); }, /** * @override */ onResize: function() { this._autocompleteController.finishAutocomplete(); this._resizeEditor(); this._editorSizeInSync = true; if (this._selectionSetScheduled) { delete this._selectionSetScheduled; this.setSelection(this._lastSelection); } }, /** * @param {!WebInspector.TextRange} range * @param {string} text * @return {!WebInspector.TextRange} */ editRange: function(range, text) { var pos = WebInspector.CodeMirrorUtils.toPos(range); this._codeMirror.replaceRange(text, pos.start, pos.end); var newRange = WebInspector.CodeMirrorUtils.toRange(pos.start, this._codeMirror.posFromIndex(this._codeMirror.indexFromPos(pos.start) + text.length)); this._delegate.onTextChanged(range, newRange); if (WebInspector.settings.textEditorAutoDetectIndent.get()) this._updateEditorIndentation(); return newRange; }, /** * @param {number} lineNumber * @param {number} column * @param {function(string):boolean} isWordChar * @return {!WebInspector.TextRange} */ wordRangeForCursorPosition: function(lineNumber, column, isWordChar) { var line = this.line(lineNumber); var wordStart = column; if (column !== 0 && isWordChar(line.charAt(column - 1))) { wordStart = column - 1; while (wordStart > 0 && isWordChar(line.charAt(wordStart - 1))) --wordStart; } var wordEnd = column; while (wordEnd < line.length && isWordChar(line.charAt(wordEnd))) ++wordEnd; return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, wordEnd); }, /** * @param {!CodeMirror} codeMirror * @param {!Array.<!CodeMirror.ChangeObject>} changes */ _changes: function(codeMirror, changes) { if (!changes.length) return; // We do not show "scroll beyond end of file" span for one line documents, so we need to check if "document has one line" changed. var hasOneLine = this._codeMirror.lineCount() === 1; if (hasOneLine !== this._hasOneLine) this._resizeEditor(); this._hasOneLine = hasOneLine; var widgets = this._elementToWidget.valuesArray(); for (var i = 0; i < widgets.length; ++i) this._codeMirror.removeLineWidget(widgets[i]); this._elementToWidget.clear(); for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) { var changeObject = changes[changeIndex]; var editInfo = WebInspector.CodeMirrorUtils.changeObjectToEditOperation(changeObject); if (!this._muteTextChangedEvent) this._delegate.onTextChanged(editInfo.oldRange, editInfo.newRange); } }, _cursorActivity: function() { var start = this._codeMirror.getCursor("anchor"); var end = this._codeMirror.getCursor("head"); this._delegate.selectionChanged(WebInspector.CodeMirrorUtils.toRange(start, end)); if (!this._isSearchActive()) this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter)); }, /** * @param {!CodeMirror} codeMirror * @param {{ranges: !Array.<{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>}} selection */ _beforeSelectionChange: function(codeMirror, selection) { this._selectNextOccurrenceController.selectionWillChange(); if (!this._isHandlingMouseDownEvent) return; if (!selection.ranges.length) return; var primarySelection = selection.ranges[0]; this._reportJump(this.selection(), WebInspector.CodeMirrorUtils.toRange(primarySelection.anchor, primarySelection.head)); }, /** * @param {?WebInspector.TextRange} from * @param {?WebInspector.TextRange} to */ _reportJump: function(from, to) { if (from && to && from.equal(to)) return; this._delegate.onJumpToPosition(from, to); }, _scroll: function() { if (this._scrollTimer) clearTimeout(this._scrollTimer); var topmostLineNumber = this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local"); this._scrollTimer = setTimeout(this._delegate.scrollChanged.bind(this._delegate, topmostLineNumber), 100); }, _focus: function() { this._delegate.editorFocused(); }, /** * @param {number} lineNumber */ scrollToLine: function(lineNumber) { var pos = new CodeMirror.Pos(lineNumber, 0); var coords = this._codeMirror.charCoords(pos, "local"); this._codeMirror.scrollTo(0, coords.top); }, /** * @return {number} */ firstVisibleLine: function() { return this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local"); }, /** * @return {number} */ lastVisibleLine: function() { var scrollInfo = this._codeMirror.getScrollInfo(); return this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local"); }, /** * @return {!WebInspector.TextRange} */ selection: function() { var start = this._codeMirror.getCursor("anchor"); var end = this._codeMirror.getCursor("head"); return WebInspector.CodeMirrorUtils.toRange(start, end); }, /** * @return {!Array.<!WebInspector.TextRange>} */ selections: function() { var selectionList = this._codeMirror.listSelections(); var result = []; for (var i = 0; i < selectionList.length; ++i) { var selection = selectionList[i]; result.push(WebInspector.CodeMirrorUtils.toRange(selection.anchor, selection.head)); } return result; }, /** * @return {?WebInspector.TextRange} */ lastSelection: function() { return this._lastSelection; }, /** * @param {!WebInspector.TextRange} textRange */ setSelection: function(textRange) { this._lastSelection = textRange; if (!this._editorSizeInSync) { this._selectionSetScheduled = true; return; } var pos = WebInspector.CodeMirrorUtils.toPos(textRange); this._codeMirror.setSelection(pos.start, pos.end); }, /** * @param {!Array.<!WebInspector.TextRange>} ranges * @param {number=} primarySelectionIndex */ setSelections: function(ranges, primarySelectionIndex) { var selections = []; for (var i = 0; i < ranges.length; ++i) { var selection = WebInspector.CodeMirrorUtils.toPos(ranges[i]); selections.push({ anchor: selection.start, head: selection.end }); } primarySelectionIndex = primarySelectionIndex || 0; this._codeMirror.se