| /* |
| * 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} |
| * @implements {WebInspector.TextEditor} |
| * @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("cmdevtools.css"); |
| |
| this._codeMirror = new window.CodeMirror(this.element, { |
| lineNumbers: true, |
| gutters: ["CodeMirror-linenumbers"], |
| matchBrackets: true, |
| smartIndent: false, |
| styleSelectedText: true, |
| electricChars: false |
| }); |
| 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"); |
| 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 = WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy; |
| 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); |
| |
| 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.element.firstChild.classList.add("source-code"); |
| this.element.firstChild.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(); |
| } |
| |
| /** @typedef {{canceled: boolean, from: !CodeMirror.Pos, to: !CodeMirror.Pos, text: string, origin: string, cancel: function()}} */ |
| WebInspector.CodeMirrorTextEditor.BeforeChangeObject; |
| |
| /** @typedef {{from: !CodeMirror.Pos, to: !CodeMirror.Pos, origin: string, text: !Array.<string>, removed: !Array.<string>}} */ |
| WebInspector.CodeMirrorTextEditor.ChangeObject; |
| |
| 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 = { |
| _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); |
| }, |
| |
| _enableBracketMatchingIfNeeded: function() |
| { |
| this._codeMirror.setOption("autoCloseBrackets", WebInspector.settings.textEditorBracketMatching.get() ? { explode: false } : false); |
| }, |
| |
| wasShown: function() |
| { |
| if (this._wasOnceShown) |
| return; |
| this._wasOnceShown = true; |
| this._codeMirror.refresh(); |
| }, |
| |
| _guessIndentationLevel: function() |
| { |
| var tabRegex = /^\t+/; |
| var tabLines = 0; |
| var indents = {}; |
| 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, 1000, processLine); |
| |
| var onePercentFilterThreshold = this.linesCount / 100; |
| if (tabLines && tabLines > onePercentFilterThreshold) |
| return "\t"; |
| var minimumIndent = Infinity; |
| for (var i in indents) { |
| if (indents[i] < onePercentFilterThreshold) |
| 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() |
| { |
| if (WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected || !WebInspector.settings.showWhitespacesInEditor.get()) |
| return; |
| WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected = 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 = document.createElement("style"); |
| style.textContent = rules; |
| document.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.CompletionDictionary} dictionary |
| */ |
| setCompletionDictionary: function(dictionary) |
| { |
| this._autocompleteController.dispose(); |
| if (dictionary) |
| this._autocompleteController = new WebInspector.CodeMirrorTextEditor.AutocompleteController(this, this._codeMirror, dictionary); |
| else |
| this._autocompleteController = WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy; |
| }, |
| |
| /** |
| * @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 = document.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 || !token.type) |
| 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(); |
| this._autocompleteController.setMimeType(mimeType); |
| }, |
| |
| /** |
| * @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" |
| }); |
| }, |
| |
| /** |
| * @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.TextEditor.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) |
| { |
| 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 {!Element} element |
| */ |
| addDecoration: function(lineNumber, element) |
| { |
| var widget = this._codeMirror.addLineWidget(lineNumber, element); |
| this._elementToWidget.put(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 |
| * @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; |
| }, |
| |
| /** |
| * @return {!Array.<!Element>} |
| */ |
| elementsToRestoreScrollPositionsFor: function() |
| { |
| return []; |
| }, |
| |
| /** |
| * @param {!WebInspector.TextEditor} textEditor |
| */ |
| inheritScrollPositions: function(textEditor) |
| { |
| }, |
| |
| /** |
| * @param {number} width |
| * @param {number} height |
| */ |
| _updatePaddingBottom: function(width, height) |
| { |
| var scrollInfo = this._codeMirror.getScrollInfo(); |
| var newPaddingBottom; |
| var linesElement = this.element.firstElementChild.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); |
| }, |
| |
| onResize: function() |
| { |
| this._autocompleteController.finishAutocomplete(); |
| this._resizeEditor(); |
| }, |
| |
| /** |
| * @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 {!WebInspector.CodeMirrorTextEditor.ChangeObject} changeObject |
| * @return {{oldRange: !WebInspector.TextRange, newRange: !WebInspector.TextRange}} |
| */ |
| _changeObjectToEditOperation: function(changeObject) |
| { |
| var oldRange = WebInspector.CodeMirrorUtils.toRange(changeObject.from, changeObject.to); |
| var newRange = oldRange.clone(); |
| var linesAdded = changeObject.text.length; |
| if (linesAdded === 0) { |
| newRange.endLine = newRange.startLine; |
| newRange.endColumn = newRange.startColumn; |
| } else if (linesAdded === 1) { |
| newRange.endLine = newRange.startLine; |
| newRange.endColumn = newRange.startColumn + changeObject.text[0].length; |
| } else { |
| newRange.endLine = newRange.startLine + linesAdded - 1; |
| newRange.endColumn = changeObject.text[linesAdded - 1].length; |
| } |
| return { |
| oldRange: oldRange, |
| newRange: newRange |
| }; |
| }, |
| |
| /** |
| * @param {!CodeMirror} codeMirror |
| * @param {!Array.<!WebInspector.CodeMirrorTextEditor.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.values(); |
| 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 = this._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; |
| 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.setSelections(selections, primarySelectionIndex, { scroll: false }); |
| }, |
| |
| /** |
| * @param {string} text |
| */ |
| _detectLineSeparator: function(text) |
| { |
| this._lineSeparator = text.indexOf("\r\n") >= 0 ? "\r\n" : "\n"; |
| }, |
| |
| /** |
| * @param {string} text |
| */ |
| setText: function(text) |
| { |
| this._muteTextChangedEvent = true; |
| if (text.length > WebInspector.CodeMirrorTextEditor.MaxEditableTextSize) { |
| this._autocompleteController.setEnabled(false); |
| this.setReadOnly(true); |
| } |
| this._codeMirror.setValue(text); |
| this._updateEditorIndentation(); |
| if (this._shouldClearHistory) { |
| this._codeMirror.clearHistory(); |
| this._shouldClearHistory = false; |
| } |
| this._detectLineSeparator(text); |
| delete this._muteTextChangedEvent; |
| }, |
| |
| /** |
| * @return {string} |
| */ |
| text: function() |
| { |
| return this._codeMirror.getValue().replace(/\n/g, this._lineSeparator); |
| }, |
| |
| /** |
| * @return {!WebInspector.TextRange} |
| */ |
| range: function() |
| { |
| var lineCount = this.linesCount; |
| var lastLine = this._codeMirror.getLine(lineCount - 1); |
| return WebInspector.CodeMirrorUtils.toRange(new CodeMirror.Pos(0, 0), new CodeMirror.Pos(lineCount - 1, lastLine.length)); |
| }, |
| |
| /** |
| * @param {number} lineNumber |
| * @return {string} |
| */ |
| line: function(lineNumber) |
| { |
| return this._codeMirror.getLine(lineNumber); |
| }, |
| |
| /** |
| * @return {number} |
| */ |
| get linesCount() |
| { |
| return this._codeMirror.lineCount(); |
| }, |
| |
| /** |
| * @param {number} line |
| * @param {string} name |
| * @param {?Object} value |
| */ |
| setAttribute: function(line, name, value) |
| { |
| if (line < 0 || line >= this._codeMirror.lineCount()) |
| return; |
| var handle = this._codeMirror.getLineHandle(line); |
| if (handle.attributes === undefined) handle.attributes = {}; |
| handle.attributes[name] = value; |
| }, |
| |
| /** |
| * @param {number} line |
| * @param {string} name |
| * @return {?Object} value |
| */ |
| getAttribute: function(line, name) |
| { |
| if (line < 0 || line >= this._codeMirror.lineCount()) |
| return null; |
| var handle = this._codeMirror.getLineHandle(line); |
| return handle.attributes && handle.attributes[name] !== undefined ? handle.attributes[name] : null; |
| }, |
| |
| /** |
| * @param {number} line |
| * @param {string} name |
| */ |
| removeAttribute: function(line, name) |
| { |
| if (line < 0 || line >= this._codeMirror.lineCount()) |
| return; |
| var handle = this._codeMirror.getLineHandle(line); |
| if (handle && handle.attributes) |
| delete handle.attributes[name]; |
| }, |
| |
| /** |
| * @param {number} lineNumber |
| * @param {number} columnNumber |
| * @return {!WebInspector.TextEditorPositionHandle} |
| */ |
| textEditorPositionHandle: function(lineNumber, columnNumber) |
| { |
| return new WebInspector.CodeMirrorPositionHandle(this._codeMirror, new CodeMirror.Pos(lineNumber, columnNumber)); |
| }, |
| |
| __proto__: WebInspector.VBox.prototype |
| } |
| |
| /** |
| * @constructor |
| * @implements {WebInspector.TextEditorPositionHandle} |
| * @param {!CodeMirror} codeMirror |
| * @param {!CodeMirror.Pos} pos |
| */ |
| WebInspector.CodeMirrorPositionHandle = function(codeMirror, pos) |
| { |
| this._codeMirror = codeMirror; |
| this._lineHandle = codeMirror.getLineHandle(pos.line); |
| this._columnNumber = pos.ch; |
| } |
| |
| WebInspector.CodeMirrorPositionHandle.prototype = { |
| /** |
| * @return {?{lineNumber: number, columnNumber: number}} |
| */ |
| resolve: function() |
| { |
| var lineNumber = this._codeMirror.getLineNumber(this._lineHandle); |
| if (typeof lineNumber !== "number") |
| return null; |
| return { |
| lineNumber: lineNumber, |
| columnNumber: this._columnNumber |
| }; |
| }, |
| |
| /** |
| * @param {!WebInspector.TextEditorPositionHandle} positionHandle |
| * @return {boolean} |
| */ |
| equal: function(positionHandle) |
| { |
| return positionHandle._lineHandle === this._lineHandle && positionHandle._columnNumber == this._columnNumber && positionHandle._codeMirror === this._codeMirror; |
| } |
| } |
| |
| /** |
| * @constructor |
| * @param {!WebInspector.CodeMirrorTextEditor} textEditor |
| * @param {!CodeMirror} codeMirror |
| */ |
| WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(textEditor, codeMirror) |
| { |
| this._textEditor = textEditor; |
| this._codeMirror = codeMirror; |
| } |
| |
| WebInspector.CodeMirrorTextEditor.TokenHighlighter.prototype = { |
| /** |
| * @param {!RegExp} regex |
| * @param {?WebInspector.TextRange} range |
| */ |
| highlightSearchResults: function(regex, range) |
| { |
| var oldRegex = this._highlightRegex; |
| this._highlightRegex = regex; |
| this._highlightRange = range; |
| if (this._searchResultMarker) { |
| this._searchResultMarker.clear(); |
| delete this._searchResultMarker; |
| } |
| if (this._highlightDescriptor && this._highlightDescriptor.selectionStart) |
| this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection"); |
| var selectionStart = this._highlightRange ? new CodeMirror.Pos(this._highlightRange.startLine, this._highlightRange.startColumn) : null; |
| if (selectionStart) |
| this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection"); |
| if (this._highlightRegex === oldRegex) { |
| // Do not re-add overlay mode if regex did not change for better performance. |
| if (this._highlightDescriptor) |
| this._highlightDescriptor.selectionStart = selectionStart; |
| } else { |
| this._removeHighlight(); |
| this._setHighlighter(this._searchHighlighter.bind(this, this._highlightRegex), selectionStart); |
| } |
| if (this._highlightRange) { |
| var pos = WebInspector.CodeMirrorUtils.toPos(this._highlightRange); |
| this._searchResultMarker = this._codeMirror.markText(pos.start, pos.end, {className: "cm-column-with-selection"}); |
| } |
| }, |
| |
| /** |
| * @return {!RegExp|undefined} |
| */ |
| highlightedRegex: function() |
| { |
| return this._highlightRegex; |
| }, |
| |
| highlightSelectedTokens: function() |
| { |
| delete this._highlightRegex; |
| delete this._highlightRange; |
| |
| if (this._highlightDescriptor && this._highlightDescriptor.selectionStart) |
| this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection"); |
| this._removeHighlight(); |
| var selectionStart = this._codeMirror.getCursor("start"); |
| var selectionEnd = this._codeMirror.getCursor("end"); |
| if (selectionStart.line !== selectionEnd.line) |
| return; |
| if (selectionStart.ch === selectionEnd.ch) |
| return; |
| |
| var selections = this._codeMirror.getSelections(); |
| if (selections.length > 1) |
| return; |
| var selectedText = selections[0]; |
| if (this._isWord(selectedText, selectionStart.line, selectionStart.ch, selectionEnd.ch)) { |
| if (selectionStart) |
| this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection") |
| this._setHighlighter(this._tokenHighlighter.bind(this, selectedText, selectionStart), selectionStart); |
| } |
| }, |
| |
| /** |
| * @param {string} selectedText |
| * @param {number} lineNumber |
| * @param {number} startColumn |
| * @param {number} endColumn |
| */ |
| _isWord: function(selectedText, lineNumber, startColumn, endColumn) |
| { |
| var line = this._codeMirror.getLine(lineNumber); |
| var leftBound = startColumn === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(startColumn - 1)); |
| var rightBound = endColumn === line.length || !WebInspector.TextUtils.isWordChar(line.charAt(endColumn)); |
| return leftBound && rightBound && WebInspector.TextUtils.isWord(selectedText); |
| }, |
| |
| _removeHighlight: function() |
| { |
| if (this._highlightDescriptor) { |
| this._codeMirror.removeOverlay(this._highlightDescriptor.overlay); |
| delete this._highlightDescriptor; |
| } |
| }, |
| |
| /** |
| * @param {!RegExp} regex |
| * @param {!CodeMirror.StringStream} stream |
| */ |
| _searchHighlighter: function(regex, stream) |
| { |
| if (stream.column() === 0) |
| delete this._searchMatchLength; |
| if (this._searchMatchLength) { |
| if (this._searchMatchLength > 2) { |
| for (var i = 0; i < this._searchMatchLength - 2; ++i) |
| stream.next(); |
| this._searchMatchLength = 1; |
| return "search-highlight"; |
| } else { |
| stream.next(); |
| delete this._searchMatchLength; |
| return "search-highlight search-highlight-end"; |
| } |
| } |
| var match = stream.match(regex, false); |
| if (match) { |
| stream.next(); |
| var matchLength = match[0].length; |
| if (matchLength === 1) |
| return "search-highlight search-highlight-full"; |
| this._searchMatchLength = matchLength; |
| return "search-highlight search-highlight-start"; |
| } |
| |
| while (!stream.match(regex, false) && stream.next()) {}; |
| }, |
| |
| /** |
| * @param {string} token |
| * @param {!CodeMirror.Pos} selectionStart |
| * @param {!CodeMirror.StringStream} stream |
| */ |
| _tokenHighlighter: function(token, selectionStart, stream) |
| { |
| var tokenFirstChar = token.charAt(0); |
| if (stream.match(token) && (stream.eol() || !WebInspector.TextUtils.isWordChar(stream.peek()))) |
| return stream.column() === selectionStart.ch ? "token-highlight column-with-selection" : "token-highlight"; |
| |
| var eatenChar; |
| do { |
| eatenChar = stream.next(); |
| } while (eatenChar && (WebInspector.TextUtils.isWordChar(eatenChar) || stream.peek() !== tokenFirstChar)); |
| }, |
| |
| /** |
| * @param {function(!CodeMirror.StringStream)} highlighter |
| * @param {?CodeMirror.Pos} selectionStart |
| */ |
| _setHighlighter: function(highlighter, selectionStart) |
| { |
| var overlayMode = { |
| token: highlighter |
| }; |
| this._codeMirror.addOverlay(overlayMode); |
| this._highlightDescriptor = { |
| overlay: overlayMode, |
| selectionStart: selectionStart |
| }; |
| } |
| } |
| |
| /** |
| * @constructor |
| * @param {!CodeMirror} codeMirror |
| */ |
| WebInspector.CodeMirrorTextEditor.BlockIndentController = function(codeMirror) |
| { |
| codeMirror.addKeyMap(this); |
| } |
| |
| WebInspector.CodeMirrorTextEditor.BlockIndentController.prototype = { |
| name: "blockIndentKeymap", |
| |
| /** |
| * @return {*} |
| */ |
| Enter: function(codeMirror) |
| { |
| var selections = codeMirror.listSelections(); |
| var replacements = []; |
| var allSelectionsAreCollapsedBlocks = false; |
| for (var i = 0; i < selections.length; ++i) { |
| var selection = selections[i]; |
| var start = CodeMirror.cmpPos(selection.head, selection.anchor) < 0 ? selection.head : selection.anchor; |
| var line = codeMirror.getLine(start.line); |
| var indent = WebInspector.TextUtils.lineIndent(line); |
| var indentToInsert = "\n" + indent + codeMirror._codeMirrorTextEditor.indent(); |
| var isCollapsedBlock = false; |
| if (selection.head.ch === 0) |
| return CodeMirror.Pass; |
| if (line.substr(selection.head.ch - 1, 2) === "{}") { |
| indentToInsert += "\n" + indent; |
| isCollapsedBlock = true; |
| } else if (line.substr(selection.head.ch - 1, 1) !== "{") { |
| return CodeMirror.Pass; |
| } |
| if (i > 0 && allSelectionsAreCollapsedBlocks !== isCollapsedBlock) |
| return CodeMirror.Pass; |
| replacements.push(indentToInsert); |
| allSelectionsAreCollapsedBlocks = isCollapsedBlock; |
| } |
| codeMirror.replaceSelections(replacements); |
| if (!allSelectionsAreCollapsedBlocks) { |
| codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces(); |
| return; |
| } |
| selections = codeMirror.listSelections(); |
| var updatedSelections = []; |
| for (var i = 0; i < selections.length; ++i) { |
| var selection = selections[i]; |
| var line = codeMirror.getLine(selection.head.line - 1); |
| var position = new CodeMirror.Pos(selection.head.line - 1, line.length); |
| updatedSelections.push({ |
| head: position, |
| anchor: position |
| }); |
| } |
| codeMirror.setSelections(updatedSelections); |
| codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces(); |
| }, |
| |
| /** |
| * @return {*} |
| */ |
| "'}'": function(codeMirror) |
| { |
| if (codeMirror.somethingSelected()) |
| return CodeMirror.Pass; |
| |
| var selections = codeMirror.listSelections(); |
| var replacements = []; |
| for (var i = 0; i < selections.length; ++i) { |
| var selection = selections[i]; |
| var line = codeMirror.getLine(selection.head.line); |
| if (line !== WebInspector.TextUtils.lineIndent(line)) |
| return CodeMirror.Pass; |
| replacements.push("}"); |
| } |
| |
| codeMirror.replaceSelections(replacements); |
| selections = codeMirror.listSelections(); |
| replacements = []; |
| var updatedSelections = []; |
| for (var i = 0; i < selections.length; ++i) { |
| var selection = selections[i]; |
| var matchingBracket = codeMirror.findMatchingBracket(selection.head); |
| if (!matchingBracket || !matchingBracket.match) |
| return; |
| updatedSelections.push({ |
| head: selection.head, |
| anchor: new CodeMirror.Pos(selection.head.line, 0) |
| }); |
| var line = codeMirror.getLine(matchingBracket.to.line); |
| var indent = WebInspector.TextUtils.lineIndent(line); |
| replacements.push(indent + "}"); |
| } |
| codeMirror.setSelections(updatedSelections); |
| codeMirror.replaceSelections(replacements); |
| } |
| } |
| |
| /** |
| * @constructor |
| * @param {!CodeMirror} codeMirror |
| */ |
| WebInspector.CodeMirrorTextEditor.FixWordMovement = function(codeMirror) |
| { |
| function moveLeft(shift, codeMirror) |
| { |
| codeMirror.setExtending(shift); |
| var cursor = codeMirror.getCursor("head"); |
| codeMirror.execCommand("goGroupLeft"); |
| var newCursor = codeMirror.getCursor("head"); |
| if (newCursor.ch === 0 && newCursor.line !== 0) { |
| codeMirror.setExtending(false); |
| return; |
| } |
| |
| var skippedText = codeMirror.getRange(newCursor, cursor, "#"); |
| if (/^\s+$/.test(skippedText)) |
| codeMirror.execCommand("goGroupLeft"); |
| codeMirror.setExtending(false); |
| } |
| |
| function moveRight(shift, codeMirror) |
| { |
| codeMirror.setExtending(shift); |
| var cursor = codeMirror.getCursor("head"); |
| codeMirror.execCommand("goGroupRight"); |
| var newCursor = codeMirror.getCursor("head"); |
| if (newCursor.ch === 0 && newCursor.line !== 0) { |
| codeMirror.setExtending(false); |
| return; |
| } |
| |
| var skippedText = codeMirror.getRange(cursor, newCursor, "#"); |
| if (/^\s+$/.test(skippedText)) |
| codeMirror.execCommand("goGroupRight"); |
| codeMirror.setExtending(false); |
| } |
| |
| var modifierKey = WebInspector.isMac() ? "Alt" : "Ctrl"; |
| var leftKey = modifierKey + "-Left"; |
| var rightKey = modifierKey + "-Right"; |
| var keyMap = {}; |
| keyMap[leftKey] = moveLeft.bind(null, false); |
| keyMap[rightKey] = moveRight.bind(null, false); |
| keyMap["Shift-" + leftKey] = moveLeft.bind(null, true); |
| keyMap["Shift-" + rightKey] = moveRight.bind(null, true); |
| codeMirror.addKeyMap(keyMap); |
| } |
| |
| /** |
| * @interface |
| */ |
| WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI = function() {} |
| |
| WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI.prototype = { |
| dispose: function() { }, |
| |
| /** |
| * @param {boolean} enabled |
| */ |
| setEnabled: function(enabled) { }, |
| |
| /** |
| * @param {string} mimeType |
| */ |
| setMimeType: function(mimeType) { }, |
| |
| autocomplete: function() { }, |
| |
| finishAutocomplete: function() { }, |
| |
| /** |
| * @param {!Event} e |
| * @return {boolean} |
| */ |
| keyDown: function(e) { } |
| } |
| |
| /** |
| * @constructor |
| * @implements {WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI} |
| */ |
| WebInspector.CodeMirrorTextEditor.DummyAutocompleteController = function() { } |
| |
| WebInspector.CodeMirrorTextEditor.DummyAutocompleteController.prototype = { |
| dispose: function() { }, |
| |
| /** |
| * @param {boolean} enabled |
| */ |
| setEnabled: function(enabled) { }, |
| |
| /** |
| * @param {string} mimeType |
| */ |
| setMimeType: function(mimeType) { }, |
| |
| autocomplete: function() { }, |
| |
| finishAutocomplete: function() { }, |
| |
| /** |
| * @param {!Event} e |
| * @return {boolean} |
| */ |
| keyDown: function(e) |
| { |
| return false; |
| } |
| } |
| |
| /** |
| * @constructor |
| * @implements {WebInspector.SuggestBoxDelegate} |
| * @implements {WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI} |
| * @param {!WebInspector.CodeMirrorTextEditor} textEditor |
| * @param {!CodeMirror} codeMirror |
| * @param {?WebInspector.CompletionDictionary} dictionary |
| */ |
| WebInspector.CodeMirrorTextEditor.AutocompleteController = function(textEditor, codeMirror, dictionary) |
| { |
| this._textEditor = textEditor; |
| this._codeMirror = codeMirror; |
| |
| this._onScroll = this._onScroll.bind(this); |
| this._onCursorActivity = this._onCursorActivity.bind(this); |
| this._changes = this._changes.bind(this); |
| this._beforeChange = this._beforeChange.bind(this); |
| this._blur = this._blur.bind(this); |
| this._codeMirror.on("scroll", this._onScroll); |
| this._codeMirror.on("cursorActivity", this._onCursorActivity); |
| this._codeMirror.on("changes", this._changes); |
| this._codeMirror.on("beforeChange", this._beforeChange); |
| this._codeMirror.on("blur", this._blur); |
| |
| this._additionalWordChars = WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars; |
| this._enabled = true; |
| |
| this._dictionary = dictionary; |
| this._addTextToCompletionDictionary(this._textEditor.text()); |
| } |
| |
| WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy = new WebInspector.CodeMirrorTextEditor.DummyAutocompleteController(); |
| WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars = {}; |
| WebInspector.CodeMirrorTextEditor._CSSAdditionalWordChars = { ".": true, "-": true }; |
| |
| WebInspector.CodeMirrorTextEditor.AutocompleteController.prototype = { |
| dispose: function() |
| { |
| this._codeMirror.off("scroll", this._onScroll); |
| this._codeMirror.off("cursorActivity", this._onCursorActivity); |
| this._codeMirror.off("changes", this._changes); |
| this._codeMirror.off("beforeChange", this._beforeChange); |
| this._codeMirror.off("blur", this._blur); |
| }, |
| |
| /** |
| * @param {boolean} enabled |
| */ |
| setEnabled: function(enabled) |
| { |
| if (enabled === this._enabled) |
| return; |
| this._enabled = enabled; |
| if (!enabled) |
| this._dictionary.reset(); |
| else |
| this._addTextToCompletionDictionary(this._textEditor.text()); |
| }, |
| |
| /** |
| * @param {string} mimeType |
| */ |
| setMimeType: function(mimeType) |
| { |
| var additionalWordChars = mimeType.indexOf("css") !== -1 ? WebInspector.CodeMirrorTextEditor._CSSAdditionalWordChars : WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars; |
| if (additionalWordChars !== this._additionalWordChars) { |
| this._additionalWordChars = additionalWordChars; |
| this._dictionary.reset(); |
| this._addTextToCompletionDictionary(this._textEditor.text()); |
| } |
| }, |
| |
| /** |
| * @param {string} char |
| * @return {boolean} |
| */ |
| _isWordChar: function(char) |
| { |
| return WebInspector.TextUtils.isWordChar(char) || !!this._additionalWordChars[char]; |
| }, |
| |
| /** |
| * @param {string} word |
| * @return {boolean} |
| */ |
| _shouldProcessWordForAutocompletion: function(word) |
| { |
| return !!word.length && (word[0] < '0' || word[0] > '9'); |
| }, |
| |
| /** |
| * @param {string} text |
| */ |
| _addTextToCompletionDictionary: function(text) |
| { |
| if (!this._enabled) |
| return; |
| var words = WebInspector.TextUtils.textToWords(text, this._isWordChar.bind(this)); |
| for (var i = 0; i < words.length; ++i) { |
| if (this._shouldProcessWordForAutocompletion(words[i])) |
| this._dictionary.addWord(words[i]); |
| } |
| }, |
| |
| /** |
| * @param {string} text |
| */ |
| _removeTextFromCompletionDictionary: function(text) |
| { |
| if (!this._enabled) |
| return; |
| var words = WebInspector.TextUtils.textToWords(text, this._isWordChar.bind(this)); |
| for (var i = 0; i < words.length; ++i) { |
| if (this._shouldProcessWordForAutocompletion(words[i])) |
| this._dictionary.removeWord(words[i]); |
| } |
| }, |
| |
| /** |
| * @param {!CodeMirror} codeMirror |
| * @param {!WebInspector.CodeMirrorTextEditor.BeforeChangeObject} changeObject |
| */ |
| _beforeChange: function(codeMirror, changeObject) |
| { |
| if (!this._enabled) |
| return; |
| this._updatedLines = this._updatedLines || {}; |
| for (var i = changeObject.from.line; i <= changeObject.to.line; ++i) |
| this._updatedLines[i] = this._textEditor.line(i); |
| }, |
| |
| /** |
| * @param {!CodeMirror} codeMirror |
| * @param {!Array.<!WebInspector.CodeMirrorTextEditor.ChangeObject>} changes |
| */ |
| _changes: function(codeMirror, changes) |
| { |
| if (!changes.length || !this._enabled) |
| return; |
| |
| if (this._updatedLines) { |
| for (var lineNumber in this._updatedLines) |
| this._removeTextFromCompletionDictionary(this._updatedLines[lineNumber]); |
| delete this._updatedLines; |
| } |
| |
| var linesToUpdate = {}; |
| var singleCharInput = false; |
| for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) { |
| var changeObject = changes[changeIndex]; |
| singleCharInput = (changeObject.origin === "+input" && changeObject.text.length === 1 && changeObject.text[0].length === 1) || |
| (this._suggestBox && changeObject.origin === "+delete" && changeObject.removed.length === 1 && changeObject.removed[0].length === 1); |
| |
| var editInfo = this._textEditor._changeObjectToEditOperation(changeObject); |
| for (var i = editInfo.newRange.startLine; i <= editInfo.newRange.endLine; ++i) |
| linesToUpdate[i] = this._textEditor.line(i); |
| } |
| for (var lineNumber in linesToUpdate) |
| this._addTextToCompletionDictionary(linesToUpdate[lineNumber]); |
| |
| if (singleCharInput) |
| this.autocomplete(); |
| }, |
| |
| _blur: function() |
| { |
| this.finishAutocomplete(); |
| }, |
| |
| /** |
| * @param {number} lineNumber |
| * @param {number} columnNumber |
| * @return {!WebInspector.TextRange} |
| */ |
| _autocompleteWordRange: function(lineNumber, columnNumber) |
| { |
| return this._textEditor._wordRangeForCursorPosition(lineNumber, columnNumber, this._isWordChar.bind(this)); |
| }, |
| |
| /** |
| * @param {!WebInspector.TextRange} mainSelection |
| * @param {!Array.<!{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>} selections |
| * @return {boolean} |
| */ |
| _validateSelectionsContexts: function(mainSelection, selections) |
| { |
| var mainSelectionContext = this._textEditor.copyRange(mainSelection); |
| for (var i = 0; i < selections.length; ++i) { |
| var wordRange = this._autocompleteWordRange(selections[i].head.line, selections[i].head.ch); |
| if (!wordRange) |
| return false; |
| var context = this._textEditor.copyRange(wordRange); |
| if (context !== mainSelectionContext) |
| return false; |
| } |
| return true; |
| }, |
| |
| autocomplete: function() |
| { |
| var dictionary = this._dictionary; |
| if (this._codeMirror.somethingSelected()) { |
| this.finishAutocomplete(); |
| return; |
| } |
| |
| var selections = this._codeMirror.listSelections().slice(); |
| var topSelection = selections.shift(); |
| var cursor = topSelection.head; |
| var substituteRange = this._autocompleteWordRange(cursor.line, cursor.ch); |
| if (!substituteRange || substituteRange.startColumn === cursor.ch || !this._validateSelectionsContexts(substituteRange, selections)) { |
| this.finishAutocomplete(); |
| return; |
| } |
| |
| var prefixRange = substituteRange.clone(); |
| prefixRange.endColumn = cursor.ch; |
| |
| var substituteWord = this._textEditor.copyRange(substituteRange); |
| var hasPrefixInDictionary = dictionary.hasWord(substituteWord); |
| if (hasPrefixInDictionary) |
| dictionary.removeWord(substituteWord); |
| var wordsWithPrefix = dictionary.wordsWithPrefix(this._textEditor.copyRange(prefixRange)); |
| if (hasPrefixInDictionary) |
| dictionary.addWord(substituteWord); |
| |
| function sortSuggestions(a, b) |
| { |
| return dictionary.wordCount(b) - dictionary.wordCount(a) || a.length - b.length; |
| } |
| |
| wordsWithPrefix.sort(sortSuggestions); |
| |
| if (!this._suggestBox) |
| this._suggestBox = new WebInspector.SuggestBox(this, 6); |
| var oldPrefixRange = this._prefixRange; |
| this._prefixRange = prefixRange; |
| if (!oldPrefixRange || prefixRange.startLine !== oldPrefixRange.startLine || prefixRange.startColumn !== oldPrefixRange.startColumn) |
| this._updateAnchorBox(); |
| this._suggestBox.updateSuggestions(this._anchorBox, wordsWithPrefix, 0, true, this._textEditor.copyRange(prefixRange)); |
| if (!this._suggestBox.visible()) |
| this.finishAutocomplete(); |
| }, |
| |
| finishAutocomplete: function() |
| { |
| if (!this._suggestBox) |
| return; |
| this._suggestBox.hide(); |
| this._suggestBox = null; |
| this._prefixRange = null; |
| this._anchorBox = null; |
| }, |
| |
| /** |
| * @param {!Event} e |
| * @return {boolean} |
| */ |
| keyDown: function(e) |
| { |
| if (!this._suggestBox) |
| return false; |
| if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) { |
| this.finishAutocomplete(); |
| return true; |
| } |
| if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Tab.code) { |
| this._suggestBox.acceptSuggestion(); |
| this.finishAutocomplete(); |
| return true; |
| } |
| return this._suggestBox.keyPressed(e); |
| }, |
| |
| /** |
| * @param {string} suggestion |
| * @param {boolean=} isIntermediateSuggestion |
| */ |
| applySuggestion: function(suggestion, isIntermediateSuggestion) |
| { |
| this._currentSuggestion = suggestion; |
| }, |
| |
| acceptSuggestion: function() |
| { |
| if (this._prefixRange.endColumn - this._prefixRange.startColumn === this._currentSuggestion.length) |
| return; |
| |
| var selections = this._codeMirror.listSelections().slice(); |
| var prefixLength = this._prefixRange.endColumn - this._prefixRange.startColumn; |
| for (var i = selections.length - 1; i >= 0; --i) { |
| var start = selections[i].head; |
| var end = new CodeMirror.Pos(start.line, start.ch - prefixLength); |
| this._codeMirror.replaceRange(this._currentSuggestion, start, end, "+autocomplete"); |
| } |
| }, |
| |
| _onScroll: function() |
| { |
| if (!this._suggestBox) |
| return; |
| var cursor = this._codeMirror.getCursor(); |
| var scrollInfo = this._codeMirror.getScrollInfo(); |
| var topmostLineNumber = this._codeMirror.lineAtHeight(scrollInfo.top, "local"); |
| var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local"); |
| if (cursor.line < topmostLineNumber || cursor.line > bottomLine) |
| this.finishAutocomplete(); |
| else { |
| this._updateAnchorBox(); |
| this._suggestBox.setPosition(this._anchorBox); |
| } |
| }, |
| |
| _onCursorActivity: function() |
| { |
| if (!this._suggestBox) |
| return; |
| var cursor = this._codeMirror.getCursor(); |
| if (cursor.line !== this._prefixRange.startLine || cursor.ch > this._prefixRange.endColumn || cursor.ch <= this._prefixRange.startColumn) |
| this.finishAutocomplete(); |
| }, |
| |
| _updateAnchorBox: function() |
| { |
| var line = this._prefixRange.startLine; |
| var column = this._prefixRange.startColumn; |
| var metrics = this._textEditor.cursorPositionToCoordinates(line, column); |
| this._anchorBox = metrics ? new AnchorBox(metrics.x, metrics.y, 0, metrics.height) : null; |
| }, |
| } |
| |
| /** |
| * @constructor |
| * @param {!WebInspector.CodeMirrorTextEditor} textEditor |
| * @param {!CodeMirror} codeMirror |
| */ |
| WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController = function(textEditor, codeMirror) |
| { |
| this._textEditor = textEditor; |
| this._codeMirror = codeMirror; |
| } |
| |
| WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController.prototype = { |
| selectionWillChange: function() |
| { |
| if (!this._muteSelectionListener) |
| delete this._fullWordSelection; |
| }, |
| |
| /** |
| * @param {!Array.<!WebInspector.TextRange>} selections |
| * @param {!WebInspector.TextRange} range |
| * @return {boolean} |
| */ |
| _findRange: function(selections, range) |
| { |
| for (var i = 0; i < selections.length; ++i) { |
| if (range.equal(selections[i])) |
| return true; |
| } |
| return false; |
| }, |
| |
| undoLastSelection: function() |
| { |
| this._muteSelectionListener = true; |
| this._codeMirror.execCommand("undoSelection"); |
| this._muteSelectionListener = false; |
| }, |
| |
| selectNextOccurrence: function() |
| { |
| var selections = this._textEditor.selections(); |
| var anyEmptySelection = false; |
| for (var i = 0; i < selections.length; ++i) { |
| var selection = selections[i]; |
| anyEmptySelection = anyEmptySelection || selection.isEmpty(); |
| if (selection.startLine !== selection.endLine) |
| return; |
| } |
| if (anyEmptySelection) { |
| this._expandSelectionsToWords(selections); |
| return; |
| } |
| |
| var last = selections[selections.length - 1]; |
| var next = last; |
| do { |
| next = this._findNextOccurrence(next, !!this._fullWordSelection); |
| } while (next && this._findRange(selections, next) && !next.equal(last)); |
| |
| if (!next) |
| return; |
| selections.push(next); |
| |
| this._muteSelectionListener = true; |
| this._textEditor.setSelections(selections, selections.length - 1); |
| delete this._muteSelectionListener; |
| |
| this._textEditor._revealLine(next.startLine); |
| }, |
| |
| /** |
| * @param {!Array.<!WebInspector.TextRange>} selections |
| */ |
| _expandSelectionsToWords: function(selections) |
| { |
| var newSelections = []; |
| for (var i = 0; i < selections.length; ++i) { |
| var selection = selections[i]; |
| var startRangeWord = this._textEditor._wordRangeForCursorPosition(selection.startLine, selection.startColumn, WebInspector.TextUtils.isWordChar) |
| || WebInspector.TextRange.createFromLocation(selection.startLine, selection.startColumn); |
| var endRangeWord = this._textEditor._wordRangeForCursorPosition(selection.endLine, selection.endColumn, WebInspector.TextUtils.isWordChar) |
| || WebInspector.TextRange.createFromLocation(selection.endLine, selection.endColumn); |
| var newSelection = new WebInspector.TextRange(startRangeWord.startLine, startRangeWord.startColumn, endRangeWord.endLine, endRangeWord.endColumn); |
| newSelections.push(newSelection); |
| } |
| this._textEditor.setSelections(newSelections, newSelections.length - 1); |
| this._fullWordSelection = true; |
| }, |
| |
| /** |
| * @param {!WebInspector.TextRange} range |
| * @param {boolean} fullWord |
| * @return {?WebInspector.TextRange} |
| */ |
| _findNextOccurrence: function(range, fullWord) |
| { |
| range = range.normalize(); |
| var matchedLineNumber; |
| var matchedColumnNumber; |
| var textToFind = this._textEditor.copyRange(range); |
| function findWordInLine(wordRegex, lineNumber, lineText, from, to) |
| { |
| if (typeof matchedLineNumber === "number") |
| return true; |
| wordRegex.lastIndex = from; |
| var result = wordRegex.exec(lineText); |
| if (!result || result.index + textToFind.length > to) |
| return false; |
| matchedLineNumber = lineNumber; |
| matchedColumnNumber = result.index; |
| return true; |
| } |
| |
| var iteratedLineNumber; |
| function lineIterator(regex, lineHandle) |
| { |
| if (findWordInLine(regex, iteratedLineNumber++, lineHandle.text, 0, lineHandle.text.length)) |
| return true; |
| } |
| |
| var regexSource = textToFind.escapeForRegExp(); |
| if (fullWord) |
| regexSource = "\\b" + regexSource + "\\b"; |
| var wordRegex = new RegExp(regexSource, "gi"); |
| var currentLineText = this._codeMirror.getLine(range.startLine); |
| |
| findWordInLine(wordRegex, range.startLine, currentLineText, range.endColumn, currentLineText.length); |
| iteratedLineNumber = range.startLine + 1; |
| this._codeMirror.eachLine(range.startLine + 1, this._codeMirror.lineCount(), lineIterator.bind(null, wordRegex)); |
| iteratedLineNumber = 0; |
| this._codeMirror.eachLine(0, range.startLine, lineIterator.bind(null, wordRegex)); |
| findWordInLine(wordRegex, range.startLine, currentLineText, 0, range.startColumn); |
| |
| if (typeof matchedLineNumber !== "number") |
| return null; |
| return new WebInspector.TextRange(matchedLineNumber, matchedColumnNumber, matchedLineNumber, matchedColumnNumber + textToFind.length); |
| } |
| } |
| |
| /** |
| * @param {string} modeName |
| * @param {string} tokenPrefix |
| */ |
| WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens = function(modeName, tokenPrefix) |
| { |
| var oldModeName = modeName + "-old"; |
| if (CodeMirror.modes[oldModeName]) |
| return; |
| |
| CodeMirror.defineMode(oldModeName, CodeMirror.modes[modeName]); |
| CodeMirror.defineMode(modeName, modeConstructor); |
| |
| function modeConstructor(config, parserConfig) |
| { |
| var innerConfig = {}; |
| for (var i in parserConfig) |
| innerConfig[i] = parserConfig[i]; |
| innerConfig.name = oldModeName; |
| var codeMirrorMode = CodeMirror.getMode(config, innerConfig); |
| codeMirrorMode.name = modeName; |
| codeMirrorMode.token = tokenOverride.bind(null, codeMirrorMode.token); |
| return codeMirrorMode; |
| } |
| |
| function tokenOverride(superToken, stream, state) |
| { |
| var token = superToken(stream, state); |
| return token ? tokenPrefix + token.split(/ +/).join(" " + tokenPrefix) : token; |
| } |
| } |
| |
| WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("css", "css-"); |
| WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("javascript", "js-"); |
| WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("xml", "xml-"); |
| |
| (function() { |
| var backgroundColor = InspectorFrontendHost.getSelectionBackgroundColor(); |
| var backgroundColorRule = backgroundColor ? ".CodeMirror .CodeMirror-selected { background-color: " + backgroundColor + ";}" : ""; |
| var foregroundColor = InspectorFrontendHost.getSelectionForegroundColor(); |
| var foregroundColorRule = foregroundColor ? ".CodeMirror .CodeMirror-selectedtext:not(.CodeMirror-persist-highlight) { color: " + foregroundColor + "!important;}" : ""; |
| if (!foregroundColorRule && !backgroundColorRule) |
| return; |
| |
| var style = document.createElement("style"); |
| style.textContent = backgroundColorRule + foregroundColorRule; |
| document.head.appendChild(style); |
| })(); |