| /* |
| * 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. |
| */ |
| |
| importScript("cm/codemirror.js"); |
| importScript("cm/css.js"); |
| importScript("cm/javascript.js"); |
| importScript("cm/xml.js"); |
| importScript("cm/htmlmixed.js"); |
| |
| importScript("cm/matchbrackets.js"); |
| importScript("cm/closebrackets.js"); |
| importScript("cm/markselection.js"); |
| importScript("cm/comment.js"); |
| importScript("cm/overlay.js"); |
| |
| importScript("cm/htmlembedded.js"); |
| importScript("cm/clike.js"); |
| importScript("cm/coffeescript.js"); |
| importScript("cm/php.js"); |
| importScript("cm/python.js"); |
| importScript("cm/shell.js"); |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.View} |
| * @implements {WebInspector.TextEditor} |
| * @param {?string} url |
| * @param {WebInspector.TextEditorDelegate} delegate |
| */ |
| WebInspector.CodeMirrorTextEditor = function(url, delegate) |
| { |
| WebInspector.View.call(this); |
| this._delegate = delegate; |
| this._url = url; |
| |
| this.registerRequiredCSS("cm/codemirror.css"); |
| this.registerRequiredCSS("cm/cmdevtools.css"); |
| |
| this._codeMirror = window.CodeMirror(this.element, { |
| lineNumbers: true, |
| gutters: ["CodeMirror-linenumbers"], |
| matchBrackets: true, |
| smartIndent: false, |
| styleSelectedText: true, |
| electricChars: false, |
| autoCloseBrackets: { explode: 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" |
| }; |
| |
| 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": "goLineStart", |
| "Alt-Right": "goLineEnd", |
| "Ctrl-Backspace": "delGroupBefore", |
| "Ctrl-Delete": "delGroupAfter", |
| "Ctrl-/": "toggleComment", |
| 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", |
| "Cmd-Left": "goLineStartSmart", |
| "Cmd-Right": "goLineEnd", |
| "Alt-Backspace": "delGroupBefore", |
| "Alt-Delete": "delGroupAfter", |
| "Cmd-/": "toggleComment", |
| fallthrough: "devtools-common" |
| }; |
| |
| WebInspector.settings.textEditorIndent.addChangeListener(this._updateEditorIndentation, this); |
| this._updateEditorIndentation(); |
| WebInspector.settings.showWhitespacesInEditor.addChangeListener(this._updateCodeMirrorMode, this); |
| |
| this._codeMirror.setOption("keyMap", WebInspector.isMac() ? "devtools-mac" : "devtools-pc"); |
| this._codeMirror.setOption("flattenSpans", false); |
| this._codeMirror.setOption("maxHighlightLength", 1000); |
| this._codeMirror.setOption("mode", null); |
| |
| this._shouldClearHistory = true; |
| this._lineSeparator = "\n"; |
| |
| this._tokenHighlighter = new WebInspector.CodeMirrorTextEditor.TokenHighlighter(this._codeMirror); |
| this._blockIndentController = new WebInspector.CodeMirrorTextEditor.BlockIndentController(this._codeMirror); |
| this._fixWordMovement = new WebInspector.CodeMirrorTextEditor.FixWordMovement(this._codeMirror); |
| this._autocompleteController = new WebInspector.CodeMirrorTextEditor.AutocompleteController(this, this._codeMirror); |
| |
| this._codeMirror.on("change", this._change.bind(this)); |
| this._codeMirror.on("beforeChange", this._beforeChange.bind(this)); |
| this._codeMirror.on("gutterClick", this._gutterClick.bind(this)); |
| this._codeMirror.on("cursorActivity", this._cursorActivity.bind(this)); |
| this._codeMirror.on("scroll", this._scroll.bind(this)); |
| this._codeMirror.on("focus", this._focus.bind(this)); |
| this._codeMirror.on("blur", this._blur.bind(this)); |
| this.element.addEventListener("contextmenu", this._contextMenu.bind(this)); |
| |
| this.element.addStyleClass("fill"); |
| this.element.style.overflow = "hidden"; |
| this.element.firstChild.addStyleClass("source-code"); |
| this.element.firstChild.addStyleClass("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.tabIndex = 0; |
| |
| this._overrideModeWithPrefixedTokens("css-base", "css-"); |
| this._overrideModeWithPrefixedTokens("javascript", "js-"); |
| this._overrideModeWithPrefixedTokens("xml", "xml-"); |
| |
| this._setupSelectionColor(); |
| this._setupWhitespaceHighlight(); |
| } |
| |
| WebInspector.CodeMirrorTextEditor.autocompleteCommand = function(codeMirror) |
| { |
| codeMirror._codeMirrorTextEditor._autocompleteController.autocomplete(); |
| } |
| CodeMirror.commands.autocomplete = WebInspector.CodeMirrorTextEditor.autocompleteCommand; |
| |
| CodeMirror.commands.smartNewlineAndIndent = function(codeMirror) |
| { |
| codeMirror.operation(innerSmartNewlineAndIndent.bind(this, codeMirror)); |
| |
| function countIndent(line) |
| { |
| for(var i = 0; i < line.length; ++i) { |
| if (!WebInspector.TextUtils.isSpaceChar(line[i])) |
| return i; |
| } |
| return line.length; |
| } |
| |
| function innerSmartNewlineAndIndent(codeMirror) |
| { |
| var cur = codeMirror.getCursor("start"); |
| var line = codeMirror.getLine(cur.line); |
| var indent = cur.line > 0 ? countIndent(line) : 0; |
| if (cur.ch <= indent) { |
| codeMirror.replaceSelection("\n" + line.substring(0, cur.ch), "end", "+input"); |
| codeMirror.setSelection(new CodeMirror.Pos(cur.line + 1, cur.ch)); |
| } else |
| codeMirror.execCommand("newlineAndIndent"); |
| } |
| } |
| |
| CodeMirror.commands.undoAndReveal = function(codemirror) |
| { |
| var scrollInfo = codemirror.getScrollInfo(); |
| codemirror.execCommand("undo"); |
| var cursor = codemirror.getCursor("start"); |
| codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo); |
| } |
| |
| CodeMirror.commands.redoAndReveal = function(codemirror) |
| { |
| var scrollInfo = codemirror.getScrollInfo(); |
| codemirror.execCommand("redo"); |
| var cursor = codemirror.getCursor("start"); |
| codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo); |
| } |
| |
| WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold = 2000; |
| WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan = 16; |
| |
| WebInspector.CodeMirrorTextEditor.prototype = { |
| wasShown: function() |
| { |
| 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(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.TextUtils.Indent.FourSpaces; |
| 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; |
| }, |
| |
| /** |
| * @param {!RegExp} regex |
| * @param {WebInspector.TextRange} range |
| */ |
| highlightSearchResults: function(regex, range) |
| { |
| function innerHighlightRegex() |
| { |
| if (range) { |
| this.revealLine(range.startLine); |
| this.setSelection(WebInspector.TextRange.createFromLocation(range.startLine, range.startColumn)); |
| } else { |
| // Collapse selection to end on search start so that we jump to next occurence on the first enter press. |
| this.setSelection(this.selection().collapseToEnd()); |
| } |
| this._tokenHighlighter.highlightSearchResults(regex, range); |
| } |
| |
| this._codeMirror.operation(innerHighlightRegex.bind(this)); |
| }, |
| |
| cancelSearchResultsHighlight: function() |
| { |
| this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter)); |
| }, |
| |
| undo: function() |
| { |
| this._codeMirror.undo(); |
| }, |
| |
| redo: function() |
| { |
| this._codeMirror.redo(); |
| }, |
| |
| _setupSelectionColor: function() |
| { |
| if (WebInspector.CodeMirrorTextEditor._selectionStyleInjected) |
| return; |
| WebInspector.CodeMirrorTextEditor._selectionStyleInjected = true; |
| var backgroundColor = WebInspector.getSelectionBackgroundColor(); |
| var backgroundColorRule = backgroundColor ? ".CodeMirror .CodeMirror-selected { background-color: " + backgroundColor + ";}" : ""; |
| var foregroundColor = WebInspector.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); |
| }, |
| |
| _setupWhitespaceHighlight: function() |
| { |
| if (WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected || !WebInspector.settings.showWhitespacesInEditor.get()) |
| return; |
| WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected = true; |
| const classBase = ".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; |
| } |
| rules += ".cm-tab:before { display: block !important; }\n"; |
| var style = document.createElement("style"); |
| style.textContent = rules; |
| document.head.appendChild(style); |
| }, |
| |
| _handleKeyDown: function(e) |
| { |
| if (this._autocompleteController.keyDown(e)) |
| e.consume(true); |
| }, |
| |
| _shouldProcessWordForAutocompletion: function(word) |
| { |
| return word.length && (word[0] < '0' || word[0] > '9'); |
| }, |
| |
| /** |
| * @param {string} text |
| */ |
| _addTextToCompletionDictionary: function(text) |
| { |
| var words = WebInspector.TextUtils.textToWords(text); |
| for(var i = 0; i < words.length; ++i) { |
| if (this._shouldProcessWordForAutocompletion(words[i])) |
| this._dictionary.addWord(words[i]); |
| } |
| }, |
| |
| /** |
| * @param {string} text |
| */ |
| _removeTextFromCompletionDictionary: function(text) |
| { |
| var words = WebInspector.TextUtils.textToWords(text); |
| for(var i = 0; i < words.length; ++i) { |
| if (this._shouldProcessWordForAutocompletion(words[i])) |
| this._dictionary.removeWord(words[i]); |
| } |
| }, |
| |
| /** |
| * @param {WebInspector.CompletionDictionary} dictionary |
| */ |
| setCompletionDictionary: function(dictionary) |
| { |
| this._dictionary = dictionary; |
| this._addTextToCompletionDictionary(this.text()); |
| }, |
| |
| /** |
| * @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 this._toRange(coords, coords); |
| }, |
| |
| _convertTokenType: function(tokenType) |
| { |
| if (tokenType.startsWith("js-variable") || tokenType.startsWith("js-property") || tokenType === "js-def") |
| return "javascript-ident"; |
| if (tokenType === "js-string-2") |
| return "javascript-regexp"; |
| if (tokenType === "js-number" || tokenType === "js-comment" || tokenType === "js-string" || tokenType === "js-keyword") |
| return "javascript-" + tokenType.substring("js-".length); |
| return null; |
| }, |
| |
| /** |
| * @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; |
| var convertedType = this._convertTokenType(token.type); |
| if (!convertedType) |
| return null; |
| return { |
| startColumn: token.start, |
| endColumn: token.end - 1, |
| type: convertedType |
| }; |
| }, |
| |
| /** |
| * @param {WebInspector.TextRange} textRange |
| * @return {string} |
| */ |
| copyRange: function(textRange) |
| { |
| var pos = this._toPos(textRange); |
| 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] + "+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; |
| }, |
| |
| /** |
| * @param {string} modeName |
| * @param {string} tokenPrefix |
| */ |
| _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(this, codeMirrorMode.token); |
| return codeMirrorMode; |
| } |
| |
| function tokenOverride(superToken, stream, state) |
| { |
| var token = superToken(stream, state); |
| return token ? tokenPrefix + token : token; |
| } |
| }, |
| |
| _enableLongLinesMode: function() |
| { |
| this._codeMirror.setOption("styleSelectedText", false); |
| this._longLinesMode = true; |
| }, |
| |
| _disableLongLinesMode: function() |
| { |
| this._codeMirror.setOption("styleSelectedText", true); |
| this._longLinesMode = false; |
| }, |
| |
| _updateCodeMirrorMode: function() |
| { |
| var showWhitespaces = WebInspector.settings.showWhitespacesInEditor.get(); |
| 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.enableStyleClass("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 = this._toPos(range); |
| ++pos.end.ch; |
| return this._codeMirror.markText(pos.start, pos.end, { |
| className: cssClass, |
| startStyle: cssClass + "-start", |
| endStyle: cssClass + "-end" |
| }); |
| }, |
| |
| /** |
| * @param {string} regex |
| * @param {string} cssClass |
| * @return {Object} |
| */ |
| highlightRegex: function(regex, cssClass) { }, |
| |
| /** |
| * @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.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._executionLine = this._codeMirror.getLineHandle(lineNumber); |
| this._codeMirror.addLineClass(this._executionLine, "wrap", "cm-execution-line"); |
| }, |
| |
| clearExecutionLine: function() |
| { |
| if (this._executionLine) |
| this._codeMirror.removeLineClass(this._executionLine, "wrap", "cm-execution-line"); |
| delete this._executionLine; |
| }, |
| |
| /** |
| * @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 |
| */ |
| highlightPosition: function(lineNumber, columnNumber) |
| { |
| if (lineNumber < 0) |
| return; |
| lineNumber = Math.min(lineNumber, this._codeMirror.lineCount() - 1); |
| if (typeof columnNumber !== "number" || columnNumber < 0 || columnNumber > this._codeMirror.getLine(lineNumber).length) |
| columnNumber = 0; |
| |
| this.clearPositionHighlight(); |
| this._highlightedLine = this._codeMirror.getLineHandle(lineNumber); |
| if (!this._highlightedLine) |
| return; |
| this.revealLine(lineNumber); |
| this._codeMirror.addLineClass(this._highlightedLine, null, "cm-highlight"); |
| this._clearHighlightTimeout = setTimeout(this.clearPositionHighlight.bind(this), 2000); |
| if (!this.readOnly()) |
| this._codeMirror.setSelection(new CodeMirror.Pos(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) |
| { |
| }, |
| |
| onResize: function() |
| { |
| var width = this.element.parentElement.offsetWidth; |
| var height = this.element.parentElement.offsetHeight; |
| this._codeMirror.setSize(width, height); |
| }, |
| |
| /** |
| * @param {WebInspector.TextRange} range |
| * @param {string} text |
| * @return {WebInspector.TextRange} |
| */ |
| editRange: function(range, text) |
| { |
| var pos = this._toPos(range); |
| this._codeMirror.replaceRange(text, pos.start, pos.end); |
| var newRange = this._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 {boolean=} prefixOnly |
| * @return {?WebInspector.TextRange} |
| */ |
| _wordRangeForCursorPosition: function(lineNumber, column, prefixOnly) |
| { |
| var line = this.line(lineNumber); |
| if (column === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(column - 1))) |
| return null; |
| var wordStart = column - 1; |
| while(wordStart > 0 && WebInspector.TextUtils.isWordChar(line.charAt(wordStart - 1))) |
| --wordStart; |
| if (prefixOnly) |
| return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, column); |
| var wordEnd = column; |
| while(wordEnd < line.length && WebInspector.TextUtils.isWordChar(line.charAt(wordEnd))) |
| ++wordEnd; |
| return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, wordEnd); |
| }, |
| |
| _beforeChange: function(codeMirror, changeObject) |
| { |
| if (!this._dictionary) |
| return; |
| this._updatedLines = this._updatedLines || {}; |
| for(var i = changeObject.from.line; i <= changeObject.to.line; ++i) |
| this._updatedLines[i] = this.line(i); |
| }, |
| |
| /** |
| * @param {CodeMirror} codeMirror |
| * @param {{origin: string, text: Array.<string>, removed: Array.<string>}} changeObject |
| */ |
| _change: function(codeMirror, changeObject) |
| { |
| var widgets = this._elementToWidget.values(); |
| for (var i = 0; i < widgets.length; ++i) |
| this._codeMirror.removeLineWidget(widgets[i]); |
| this._elementToWidget.clear(); |
| |
| if (this._updatedLines) { |
| for(var lineNumber in this._updatedLines) |
| this._removeTextFromCompletionDictionary(this._updatedLines[lineNumber]); |
| delete this._updatedLines; |
| } |
| |
| var linesToUpdate = {}; |
| var singleCharInput = false; |
| do { |
| var oldRange = this._toRange(changeObject.from, changeObject.to); |
| var newRange = oldRange.clone(); |
| var linesAdded = changeObject.text.length; |
| singleCharInput = (changeObject.origin === "+input" && changeObject.text.length === 1 && changeObject.text[0].length === 1) || |
| (changeObject.origin === "+delete" && changeObject.removed.length === 1 && changeObject.removed[0].length === 1); |
| 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; |
| } |
| |
| if (!this._muteTextChangedEvent) |
| this._delegate.onTextChanged(oldRange, newRange); |
| |
| for(var i = newRange.startLine; i <= newRange.endLine; ++i) { |
| linesToUpdate[i] = true; |
| } |
| if (this._dictionary) { |
| for(var i = newRange.startLine; i <= newRange.endLine; ++i) |
| linesToUpdate[i] = this.line(i); |
| } |
| } while (changeObject = changeObject.next); |
| if (this._dictionary) { |
| for(var lineNumber in linesToUpdate) |
| this._addTextToCompletionDictionary(linesToUpdate[lineNumber]); |
| } |
| if (singleCharInput) |
| this._autocompleteController.autocomplete(); |
| }, |
| |
| _cursorActivity: function() |
| { |
| var start = this._codeMirror.getCursor("anchor"); |
| var end = this._codeMirror.getCursor("head"); |
| this._delegate.selectionChanged(this._toRange(start, end)); |
| if (!this._tokenHighlighter.highlightedRegex()) |
| this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter)); |
| }, |
| |
| _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(); |
| }, |
| |
| _blur: function() |
| { |
| this._autocompleteController.finishAutocomplete(); |
| }, |
| |
| /** |
| * @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 this._toRange(start, end); |
| }, |
| |
| /** |
| * @return {WebInspector.TextRange?} |
| */ |
| lastSelection: function() |
| { |
| return this._lastSelection; |
| }, |
| |
| /** |
| * @param {WebInspector.TextRange} textRange |
| */ |
| setSelection: function(textRange) |
| { |
| this._lastSelection = textRange; |
| var pos = this._toPos(textRange); |
| this._codeMirror.setSelection(pos.start, pos.end); |
| }, |
| |
| /** |
| * @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; |
| 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 this._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 {WebInspector.TextRange} range |
| * @return {{start: CodeMirror.Pos, end: CodeMirror.Pos}} |
| */ |
| _toPos: function(range) |
| { |
| return { |
| start: new CodeMirror.Pos(range.startLine, range.startColumn), |
| end: new CodeMirror.Pos(range.endLine, range.endColumn) |
| } |
| }, |
| |
| _toRange: function(start, end) |
| { |
| return new WebInspector.TextRange(start.line, start.ch, end.line, end.ch); |
| }, |
| |
| __proto__: WebInspector.View.prototype |
| } |
| |
| /** |
| * @constructor |
| * @param {CodeMirror} codeMirror |
| */ |
| WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(codeMirror) |
| { |
| 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. |
| this._highlightDescriptor.selectionStart = selectionStart; |
| } else { |
| this._removeHighlight(); |
| this._setHighlighter(this._searchHighlighter.bind(this, this._highlightRegex, this._highlightRange), selectionStart); |
| } |
| if (selectionStart) { |
| var pos = WebInspector.CodeMirrorTextEditor.prototype._toPos(this._highlightRange); |
| this._searchResultMarker = this._codeMirror.markText(pos.start, pos.end, {className: "cm-column-with-selection"}); |
| } |
| }, |
| |
| 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 selectedText = this._codeMirror.getSelection(); |
| 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 {WebInspector.TextRange} range |
| * @param {CodeMirror.StringStream} stream |
| */ |
| _searchHighlighter: function(regex, range, stream) |
| { |
| if (stream.column() === 0) |
| delete this._searchMatchLength; |
| if (this._searchMatchLength) { |
| if (this._searchMatchLength > 1) { |
| 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 |
| */ |
| _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", |
| |
| Enter: function(codeMirror) |
| { |
| if (codeMirror.somethingSelected()) |
| return CodeMirror.Pass; |
| var cursor = codeMirror.getCursor(); |
| if (cursor.ch === 0) |
| return CodeMirror.Pass; |
| var line = codeMirror.getLine(cursor.line); |
| if (line.substr(cursor.ch - 1, 2) === "{}") { |
| codeMirror.execCommand("newlineAndIndent"); |
| codeMirror.setCursor(cursor); |
| codeMirror.execCommand("newlineAndIndent"); |
| codeMirror.execCommand("indentMore"); |
| } else if (line.substr(cursor.ch - 1, 1) === "{") { |
| codeMirror.execCommand("newlineAndIndent"); |
| codeMirror.execCommand("indentMore"); |
| } else |
| return CodeMirror.Pass; |
| }, |
| |
| "'}'": function(codeMirror) |
| { |
| var cursor = codeMirror.getCursor(); |
| var line = codeMirror.getLine(cursor.line); |
| for(var i = 0 ; i < line.length; ++i) |
| if (!WebInspector.TextUtils.isSpaceChar(line.charAt(i))) |
| return CodeMirror.Pass; |
| |
| codeMirror.replaceRange("}", cursor); |
| var matchingBracket = codeMirror.findMatchingBracket(); |
| if (!matchingBracket || !matchingBracket.match) |
| return; |
| |
| line = codeMirror.getLine(matchingBracket.to.line); |
| var desiredIndentation = 0; |
| while (desiredIndentation < line.length && WebInspector.TextUtils.isSpaceChar(line.charAt(desiredIndentation))) |
| ++desiredIndentation; |
| |
| codeMirror.replaceRange(line.substr(0, desiredIndentation) + "}", new CodeMirror.Pos(cursor.line, 0), new CodeMirror.Pos(cursor.line, cursor.ch + 1)); |
| } |
| } |
| |
| /** |
| * @constructor |
| * @param {CodeMirror} codeMirror |
| */ |
| WebInspector.CodeMirrorTextEditor.FixWordMovement = function(codeMirror) |
| { |
| function moveLeft(shift, codeMirror) |
| { |
| var cursor = codeMirror.getCursor("head"); |
| if (cursor.ch !== 0 || cursor.line === 0) |
| return CodeMirror.Pass; |
| codeMirror.setExtending(shift); |
| codeMirror.execCommand("goLineUp"); |
| codeMirror.execCommand("goLineEnd") |
| codeMirror.setExtending(false); |
| } |
| function moveRight(shift, codeMirror) |
| { |
| var cursor = codeMirror.getCursor("head"); |
| var line = codeMirror.getLine(cursor.line); |
| if (cursor.ch !== line.length || cursor.line + 1 === codeMirror.lineCount()) |
| return CodeMirror.Pass; |
| codeMirror.setExtending(shift); |
| codeMirror.execCommand("goLineDown"); |
| codeMirror.execCommand("goLineStart"); |
| codeMirror.setExtending(false); |
| } |
| function delWordBack(codeMirror) |
| { |
| if (codeMirror.somethingSelected()) |
| return CodeMirror.Pass; |
| var cursor = codeMirror.getCursor("head"); |
| if (cursor.ch === 0) |
| codeMirror.execCommand("delCharBefore"); |
| else |
| return CodeMirror.Pass; |
| } |
| |
| var modifierKey = WebInspector.isMac() ? "Alt" : "Ctrl"; |
| var leftKey = modifierKey + "-Left"; |
| var rightKey = modifierKey + "-Right"; |
| var keyMap = {}; |
| keyMap[leftKey] = moveLeft.bind(this, false); |
| keyMap[rightKey] = moveRight.bind(this, false); |
| keyMap["Shift-" + leftKey] = moveLeft.bind(this, true); |
| keyMap["Shift-" + rightKey] = moveRight.bind(this, true); |
| keyMap[modifierKey + "-Backspace"] = delWordBack.bind(this); |
| codeMirror.addKeyMap(keyMap); |
| } |
| |
| /** |
| * @constructor |
| * @implements {WebInspector.SuggestBoxDelegate} |
| * @param {WebInspector.CodeMirrorTextEditor} textEditor |
| * @param {CodeMirror} codeMirror |
| */ |
| WebInspector.CodeMirrorTextEditor.AutocompleteController = function(textEditor, codeMirror) |
| { |
| this._textEditor = textEditor; |
| this._codeMirror = codeMirror; |
| this._codeMirror.on("scroll", this._onScroll.bind(this)); |
| this._codeMirror.on("cursorActivity", this._onCursorActivity.bind(this)); |
| } |
| |
| WebInspector.CodeMirrorTextEditor.AutocompleteController.prototype = { |
| autocomplete: function() |
| { |
| var dictionary = this._textEditor._dictionary; |
| if (!dictionary || this._codeMirror.somethingSelected()) { |
| this.finishAutocomplete(); |
| return; |
| } |
| |
| var cursor = this._codeMirror.getCursor(); |
| var substituteRange = this._textEditor._wordRangeForCursorPosition(cursor.line, cursor.ch, false); |
| if (!substituteRange || substituteRange.startColumn === cursor.ch) { |
| 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, this._textEditor.element, "generic-suggest", 6); |
| this._anchorBox = this._anchorBoxForPosition(cursor.line, cursor.ch); |
| } |
| this._suggestBox.updateSuggestions(this._anchorBox, wordsWithPrefix, 0, true, this._textEditor.copyRange(prefixRange)); |
| this._prefixRange = 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 |
| */ |
| 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) { |
| var pos = this._textEditor._toPos(this._prefixRange); |
| this._codeMirror.replaceRange(this._currentSuggestion, pos.start, pos.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._anchorBox = this._anchorBoxForPosition(cursor.line, cursor.ch); |
| 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(); |
| }, |
| |
| /** |
| * @param {number} line |
| * @param {number} column |
| * @return {AnchorBox} |
| */ |
| _anchorBoxForPosition: function(line, column) |
| { |
| var metrics = this._textEditor.cursorPositionToCoordinates(line, column); |
| return metrics ? new AnchorBox(metrics.x, metrics.y, 0, metrics.height) : null; |
| }, |
| } |