2 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 importScript("cm/codemirror.js");
32 importScript("cm/css.js");
33 importScript("cm/javascript.js");
34 importScript("cm/xml.js");
35 importScript("cm/htmlmixed.js");
37 importScript("cm/matchbrackets.js");
38 importScript("cm/closebrackets.js");
39 importScript("cm/markselection.js");
40 importScript("cm/comment.js");
41 importScript("cm/overlay.js");
43 importScript("cm/htmlembedded.js");
44 importScript("cm/clike.js");
45 importScript("cm/coffeescript.js");
46 importScript("cm/php.js");
47 importScript("cm/python.js");
48 importScript("cm/shell.js");
49 importScript("CodeMirrorUtils.js");
53 * @extends {WebInspector.VBox}
54 * @implements {WebInspector.TextEditor}
55 * @param {?string} url
56 * @param {!WebInspector.TextEditorDelegate} delegate
58 WebInspector.CodeMirrorTextEditor = function(url, delegate)
60 WebInspector.VBox.call(this);
61 this._delegate = delegate;
64 this.registerRequiredCSS("cm/codemirror.css");
65 this.registerRequiredCSS("cm/cmdevtools.css");
67 this._codeMirror = window.CodeMirror(this.element, {
69 gutters: ["CodeMirror-linenumbers"],
72 styleSelectedText: true,
75 this._codeMirror._codeMirrorTextEditor = this;
77 CodeMirror.keyMap["devtools-common"] = {
79 "Right": "goCharRight",
83 "Home": "goLineStartSmart",
85 "PageDown": "goPageDown",
86 "Delete": "delCharAfter",
87 "Backspace": "delCharBefore",
89 "Shift-Tab": "indentLess",
90 "Enter": "smartNewlineAndIndent",
91 "Ctrl-Space": "autocomplete"
94 CodeMirror.keyMap["devtools-pc"] = {
95 "Ctrl-A": "selectAll",
96 "Ctrl-Z": "undoAndReveal",
97 "Shift-Ctrl-Z": "redoAndReveal",
99 "Ctrl-Home": "goDocStart",
100 "Ctrl-Up": "goDocStart",
101 "Ctrl-End": "goDocEnd",
102 "Ctrl-Down": "goDocEnd",
103 "Ctrl-Left": "goGroupLeft",
104 "Ctrl-Right": "goGroupRight",
105 "Alt-Left": "goLineStart",
106 "Alt-Right": "goLineEnd",
107 "Ctrl-Backspace": "delGroupBefore",
108 "Ctrl-Delete": "delGroupAfter",
109 "Ctrl-/": "toggleComment",
110 fallthrough: "devtools-common"
113 CodeMirror.keyMap["devtools-mac"] = {
114 "Cmd-A" : "selectAll",
115 "Cmd-Z" : "undoAndReveal",
116 "Shift-Cmd-Z": "redoAndReveal",
117 "Cmd-Up": "goDocStart",
118 "Cmd-Down": "goDocEnd",
119 "Alt-Left": "goGroupLeft",
120 "Alt-Right": "goGroupRight",
121 "Cmd-Left": "goLineStartSmart",
122 "Cmd-Right": "goLineEnd",
123 "Alt-Backspace": "delGroupBefore",
124 "Alt-Delete": "delGroupAfter",
125 "Cmd-/": "toggleComment",
126 fallthrough: "devtools-common"
129 WebInspector.settings.textEditorIndent.addChangeListener(this._updateEditorIndentation, this);
130 this._updateEditorIndentation();
131 WebInspector.settings.showWhitespacesInEditor.addChangeListener(this._updateCodeMirrorMode, this);
132 WebInspector.settings.textEditorBracketMatching.addChangeListener(this._enableBracketMatchingIfNeeded, this);
133 this._enableBracketMatchingIfNeeded();
135 this._codeMirror.setOption("keyMap", WebInspector.isMac() ? "devtools-mac" : "devtools-pc");
136 this._codeMirror.setOption("flattenSpans", false);
138 this._codeMirror.setOption("maxHighlightLength", WebInspector.CodeMirrorTextEditor.maxHighlightLength);
139 this._codeMirror.setOption("mode", null);
140 this._codeMirror.setOption("crudeMeasuringFrom", 1000);
142 this._shouldClearHistory = true;
143 this._lineSeparator = "\n";
145 this._tokenHighlighter = new WebInspector.CodeMirrorTextEditor.TokenHighlighter(this._codeMirror);
146 this._blockIndentController = new WebInspector.CodeMirrorTextEditor.BlockIndentController(this._codeMirror);
147 this._fixWordMovement = new WebInspector.CodeMirrorTextEditor.FixWordMovement(this._codeMirror);
148 this._autocompleteController = new WebInspector.CodeMirrorTextEditor.AutocompleteController(this, this._codeMirror);
150 this._codeMirror.on("change", this._change.bind(this));
151 this._codeMirror.on("beforeChange", this._beforeChange.bind(this));
152 this._codeMirror.on("gutterClick", this._gutterClick.bind(this));
153 this._codeMirror.on("cursorActivity", this._cursorActivity.bind(this));
154 this._codeMirror.on("beforeSelectionChange", this._beforeSelectionChange.bind(this));
155 this._codeMirror.on("scroll", this._scroll.bind(this));
156 this._codeMirror.on("focus", this._focus.bind(this));
157 this._codeMirror.on("blur", this._blur.bind(this));
158 this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false);
160 * @this {WebInspector.CodeMirrorTextEditor}
162 function updateAnticipateJumpFlag(value)
164 this._isHandlingMouseDownEvent = value;
166 this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, true), true);
167 this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, false), false);
169 this.element.style.overflow = "hidden";
170 this.element.firstChild.classList.add("source-code");
171 this.element.firstChild.classList.add("fill");
172 this._elementToWidget = new Map();
173 this._nestedUpdatesCounter = 0;
175 this.element.addEventListener("focus", this._handleElementFocus.bind(this), false);
176 this.element.addEventListener("keydown", this._handleKeyDown.bind(this), true);
177 this.element.addEventListener("keydown", this._handlePostKeyDown.bind(this), false);
178 this.element.tabIndex = 0;
180 this._setupWhitespaceHighlight();
183 /** @typedef {{canceled: boolean, from: CodeMirror.Pos, to: CodeMirror.Pos, text: string, origin: string, cancel: function()}} */
184 WebInspector.CodeMirrorTextEditor.BeforeChangeObject;
186 /** @typedef {{from: CodeMirror.Pos, to: CodeMirror.Pos, origin: string, text: !Array.<string>, removed: !Array.<string>}} */
187 WebInspector.CodeMirrorTextEditor.ChangeObject;
189 WebInspector.CodeMirrorTextEditor.maxHighlightLength = 1000;
191 WebInspector.CodeMirrorTextEditor.autocompleteCommand = function(codeMirror)
193 codeMirror._codeMirrorTextEditor._autocompleteController.autocomplete();
195 CodeMirror.commands.autocomplete = WebInspector.CodeMirrorTextEditor.autocompleteCommand;
197 CodeMirror.commands.smartNewlineAndIndent = function(codeMirror)
199 codeMirror.operation(innerSmartNewlineAndIndent.bind(null, codeMirror));
201 function countIndent(line)
203 for (var i = 0; i < line.length; ++i) {
204 if (!WebInspector.TextUtils.isSpaceChar(line[i]))
210 function innerSmartNewlineAndIndent(codeMirror)
212 var cur = codeMirror.getCursor("start");
213 var line = codeMirror.getLine(cur.line);
214 var indent = cur.line > 0 ? countIndent(line) : 0;
215 if (cur.ch <= indent) {
216 codeMirror.replaceSelection("\n" + line.substring(0, cur.ch), "end", "+input");
217 codeMirror.setSelection(new CodeMirror.Pos(cur.line + 1, cur.ch));
219 codeMirror.execCommand("newlineAndIndent");
223 CodeMirror.commands.undoAndReveal = function(codemirror)
225 var scrollInfo = codemirror.getScrollInfo();
226 codemirror.execCommand("undo");
227 var cursor = codemirror.getCursor("start");
228 codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo);
229 codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete();
232 CodeMirror.commands.redoAndReveal = function(codemirror)
234 var scrollInfo = codemirror.getScrollInfo();
235 codemirror.execCommand("redo");
236 var cursor = codemirror.getCursor("start");
237 codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo);
238 codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete();
241 WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold = 2000;
242 WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan = 16;
243 WebInspector.CodeMirrorTextEditor.MaxEditableTextSize = 1024 * 1024 * 10;
245 WebInspector.CodeMirrorTextEditor.prototype = {
246 _enableBracketMatchingIfNeeded: function()
248 this._codeMirror.setOption("autoCloseBrackets", WebInspector.settings.textEditorBracketMatching.get() ? { explode: false } : false);
253 if (this._wasOnceShown)
255 this._wasOnceShown = true;
256 this._codeMirror.refresh();
259 _guessIndentationLevel: function()
261 var tabRegex = /^\t+/;
264 function processLine(lineHandle)
266 var text = lineHandle.text;
267 if (text.length === 0 || !WebInspector.TextUtils.isSpaceChar(text[0]))
269 if (tabRegex.test(text)) {
274 while (i < text.length && WebInspector.TextUtils.isSpaceChar(text[i]))
278 indents[i] = 1 + (indents[i] || 0);
280 this._codeMirror.eachLine(0, 1000, processLine);
282 var onePercentFilterThreshold = this.linesCount / 100;
283 if (tabLines && tabLines > onePercentFilterThreshold)
285 var minimumIndent = Infinity;
286 for (var i in indents) {
287 if (indents[i] < onePercentFilterThreshold)
289 var indent = parseInt(i, 10);
290 if (minimumIndent > indent)
291 minimumIndent = indent;
293 if (minimumIndent === Infinity)
294 return WebInspector.TextUtils.Indent.FourSpaces;
295 return new Array(minimumIndent + 1).join(" ");
298 _updateEditorIndentation: function()
301 var indent = WebInspector.settings.textEditorIndent.get();
302 if (WebInspector.settings.textEditorAutoDetectIndent.get())
303 indent = this._guessIndentationLevel();
304 if (indent === WebInspector.TextUtils.Indent.TabCharacter) {
305 this._codeMirror.setOption("indentWithTabs", true);
306 this._codeMirror.setOption("indentUnit", 4);
308 this._codeMirror.setOption("indentWithTabs", false);
309 this._codeMirror.setOption("indentUnit", indent.length);
310 extraKeys.Tab = function(codeMirror)
312 if (codeMirror.somethingSelected())
313 return CodeMirror.Pass;
314 var pos = codeMirror.getCursor("head");
315 codeMirror.replaceRange(indent.substring(pos.ch % indent.length), codeMirror.getCursor());
318 this._codeMirror.setOption("extraKeys", extraKeys);
319 this._indentationLevel = indent;
327 return this._indentationLevel;
331 * @param {!RegExp} regex
332 * @param {?WebInspector.TextRange} range
334 highlightSearchResults: function(regex, range)
337 * @this {WebInspector.CodeMirrorTextEditor}
339 function innerHighlightRegex()
342 this._revealLine(range.startLine);
343 if (range.endColumn > WebInspector.CodeMirrorTextEditor.maxHighlightLength)
344 this.setSelection(range);
346 this.setSelection(WebInspector.TextRange.createFromLocation(range.startLine, range.startColumn));
348 // Collapse selection to end on search start so that we jump to next occurence on the first enter press.
349 this.setSelection(this.selection().collapseToEnd());
351 this._tokenHighlighter.highlightSearchResults(regex, range);
353 if (!this._selectionBeforeSearch)
354 this._selectionBeforeSearch = this.selection();
355 this._codeMirror.operation(innerHighlightRegex.bind(this));
358 cancelSearchResultsHighlight: function()
360 this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));
361 if (this._selectionBeforeSearch) {
362 this._reportJump(this._selectionBeforeSearch, this.selection());
363 delete this._selectionBeforeSearch;
369 this._codeMirror.undo();
374 this._codeMirror.redo();
377 _setupWhitespaceHighlight: function()
379 if (WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected || !WebInspector.settings.showWhitespacesInEditor.get())
381 WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected = true;
382 const classBase = ".show-whitespaces .CodeMirror .cm-whitespace-";
383 const spaceChar = "·";
386 for (var i = 1; i <= WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan; ++i) {
387 spaceChars += spaceChar;
388 var rule = classBase + i + "::before { content: '" + spaceChars + "';}\n";
391 var style = document.createElement("style");
392 style.textContent = rules;
393 document.head.appendChild(style);
396 _handleKeyDown: function(e)
398 if (this._autocompleteController.keyDown(e))
402 _handlePostKeyDown: function(e)
404 if (e.defaultPrevented)
408 _shouldProcessWordForAutocompletion: function(word)
410 return word.length && (word[0] < '0' || word[0] > '9');
414 * @param {string} text
416 _addTextToCompletionDictionary: function(text)
420 var words = WebInspector.TextUtils.textToWords(text);
421 for (var i = 0; i < words.length; ++i) {
422 if (this._shouldProcessWordForAutocompletion(words[i]))
423 this._dictionary.addWord(words[i]);
428 * @param {string} text
430 _removeTextFromCompletionDictionary: function(text)
434 var words = WebInspector.TextUtils.textToWords(text);
435 for (var i = 0; i < words.length; ++i) {
436 if (this._shouldProcessWordForAutocompletion(words[i]))
437 this._dictionary.removeWord(words[i]);
442 * @param {?WebInspector.CompletionDictionary} dictionary
444 setCompletionDictionary: function(dictionary)
447 delete this._dictionary;
450 this._dictionary = dictionary;
451 this._addTextToCompletionDictionary(this.text());
455 * @param {number} lineNumber
456 * @param {number} column
457 * @return {?{x: number, y: number, height: number}}
459 cursorPositionToCoordinates: function(lineNumber, column)
461 if (lineNumber >= this._codeMirror.lineCount() || lineNumber < 0 || column < 0 || column > this._codeMirror.getLine(lineNumber).length)
464 var metrics = this._codeMirror.cursorCoords(new CodeMirror.Pos(lineNumber, column));
469 height: metrics.bottom - metrics.top
476 * @return {?WebInspector.TextRange}
478 coordinatesToCursorPosition: function(x, y)
480 var element = document.elementFromPoint(x, y);
481 if (!element || !element.isSelfOrDescendant(this._codeMirror.getWrapperElement()))
483 var gutterBox = this._codeMirror.getGutterElement().boxInWindow();
484 if (x >= gutterBox.x && x <= gutterBox.x + gutterBox.width &&
485 y >= gutterBox.y && y <= gutterBox.y + gutterBox.height)
487 var coords = this._codeMirror.coordsChar({left: x, top: y});
488 return this._toRange(coords, coords);
492 * @param {number} lineNumber
493 * @param {number} column
494 * @return {?{startColumn: number, endColumn: number, type: string}}
496 tokenAtTextPosition: function(lineNumber, column)
498 if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
500 var token = this._codeMirror.getTokenAt(new CodeMirror.Pos(lineNumber, (column || 0) + 1));
501 if (!token || !token.type)
504 startColumn: token.start,
505 endColumn: token.end - 1,
511 * @param {!WebInspector.TextRange} textRange
514 copyRange: function(textRange)
516 var pos = this._toPos(textRange.normalize());
517 return this._codeMirror.getRange(pos.start, pos.end);
525 return this._codeMirror.isClean();
528 markClean: function()
530 this._codeMirror.markClean();
533 _hasLongLines: function()
535 function lineIterator(lineHandle)
537 if (lineHandle.text.length > WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold)
541 var hasLongLines = false;
542 this._codeMirror.eachLine(lineIterator);
547 * @param {string} mimeType
550 _whitespaceOverlayMode: function(mimeType)
552 var modeName = CodeMirror.mimeModes[mimeType] ? (CodeMirror.mimeModes[mimeType].name || CodeMirror.mimeModes[mimeType]) : CodeMirror.mimeModes["text/plain"];
553 modeName += "+whitespaces";
554 if (CodeMirror.modes[modeName])
557 function modeConstructor(config, parserConfig)
559 function nextToken(stream)
561 if (stream.peek() === " ") {
563 while (spaces < WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan && stream.peek() === " ") {
567 return "whitespace whitespace-" + spaces;
569 while (!stream.eol() && stream.peek() !== " ")
573 var whitespaceMode = {
576 return CodeMirror.overlayMode(CodeMirror.getMode(config, mimeType), whitespaceMode, false);
578 CodeMirror.defineMode(modeName, modeConstructor);
582 _enableLongLinesMode: function()
584 this._codeMirror.setOption("styleSelectedText", false);
585 this._longLinesMode = true;
588 _disableLongLinesMode: function()
590 this._codeMirror.setOption("styleSelectedText", true);
591 this._longLinesMode = false;
594 _updateCodeMirrorMode: function()
596 var showWhitespaces = WebInspector.settings.showWhitespacesInEditor.get();
597 this.element.classList.toggle("show-whitespaces", showWhitespaces);
598 this._codeMirror.setOption("mode", showWhitespaces ? this._whitespaceOverlayMode(this._mimeType) : this._mimeType);
602 * @param {string} mimeType
604 setMimeType: function(mimeType)
606 this._mimeType = mimeType;
607 if (this._hasLongLines())
608 this._enableLongLinesMode();
610 this._disableLongLinesMode();
611 this._updateCodeMirrorMode();
615 * @param {boolean} readOnly
617 setReadOnly: function(readOnly)
619 this.element.classList.toggle("CodeMirror-readonly", readOnly)
620 this._codeMirror.setOption("readOnly", readOnly);
628 return !!this._codeMirror.getOption("readOnly");
632 * @param {!Object} highlightDescriptor
634 removeHighlight: function(highlightDescriptor)
636 highlightDescriptor.clear();
640 * @param {!WebInspector.TextRange} range
641 * @param {string} cssClass
644 highlightRange: function(range, cssClass)
646 cssClass = "CodeMirror-persist-highlight " + cssClass;
647 var pos = this._toPos(range);
649 return this._codeMirror.markText(pos.start, pos.end, {
651 startStyle: cssClass + "-start",
652 endStyle: cssClass + "-end"
659 defaultFocusedElement: function()
666 this._codeMirror.focus();
669 _handleElementFocus: function()
671 this._codeMirror.focus();
674 beginUpdates: function()
676 ++this._nestedUpdatesCounter;
679 endUpdates: function()
681 if (!--this._nestedUpdatesCounter)
682 this._codeMirror.refresh();
686 * @param {number} lineNumber
688 _revealLine: function(lineNumber)
690 this._innerRevealLine(lineNumber, this._codeMirror.getScrollInfo());
694 * @param {number} lineNumber
695 * @param {!{left: number, top: number, width: number, height: number, clientWidth: number, clientHeight: number}} scrollInfo
697 _innerRevealLine: function(lineNumber, scrollInfo)
699 var topLine = this._codeMirror.lineAtHeight(scrollInfo.top, "local");
700 var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
701 var linesPerScreen = bottomLine - topLine + 1;
702 if (lineNumber < topLine) {
703 var topLineToReveal = Math.max(lineNumber - (linesPerScreen / 2) + 1, 0) | 0;
704 this._codeMirror.scrollIntoView(new CodeMirror.Pos(topLineToReveal, 0));
705 } else if (lineNumber > bottomLine) {
706 var bottomLineToReveal = Math.min(lineNumber + (linesPerScreen / 2) - 1, this.linesCount - 1) | 0;
707 this._codeMirror.scrollIntoView(new CodeMirror.Pos(bottomLineToReveal, 0));
711 _gutterClick: function(instance, lineNumber, gutter, event)
713 this.dispatchEventToListeners(WebInspector.TextEditor.Events.GutterClick, { lineNumber: lineNumber, event: event });
716 _contextMenu: function(event)
718 var contextMenu = new WebInspector.ContextMenu(event);
719 var target = event.target.enclosingNodeOrSelfWithClass("CodeMirror-gutter-elt");
721 this._delegate.populateLineGutterContextMenu(contextMenu, parseInt(target.textContent, 10) - 1);
723 this._delegate.populateTextAreaContextMenu(contextMenu, 0);
728 * @param {number} lineNumber
729 * @param {boolean} disabled
730 * @param {boolean} conditional
732 addBreakpoint: function(lineNumber, disabled, conditional)
734 if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
736 var className = "cm-breakpoint" + (conditional ? " cm-breakpoint-conditional" : "") + (disabled ? " cm-breakpoint-disabled" : "");
737 this._codeMirror.addLineClass(lineNumber, "wrap", className);
741 * @param {number} lineNumber
743 removeBreakpoint: function(lineNumber)
745 if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
747 var wrapClasses = this._codeMirror.getLineHandle(lineNumber).wrapClass;
750 var classes = wrapClasses.split(" ");
751 for (var i = 0; i < classes.length; ++i) {
752 if (classes[i].startsWith("cm-breakpoint"))
753 this._codeMirror.removeLineClass(lineNumber, "wrap", classes[i]);
758 * @param {number} lineNumber
760 setExecutionLine: function(lineNumber)
762 this.clearPositionHighlight();
763 this._executionLine = this._codeMirror.getLineHandle(lineNumber);
764 if (!this._executionLine)
766 this._codeMirror.addLineClass(this._executionLine, "wrap", "cm-execution-line");
769 clearExecutionLine: function()
771 this.clearPositionHighlight();
772 if (this._executionLine)
773 this._codeMirror.removeLineClass(this._executionLine, "wrap", "cm-execution-line");
774 delete this._executionLine;
778 * @param {number} lineNumber
779 * @param {!Element} element
781 addDecoration: function(lineNumber, element)
783 var widget = this._codeMirror.addLineWidget(lineNumber, element);
784 this._elementToWidget.put(element, widget);
788 * @param {number} lineNumber
789 * @param {!Element} element
791 removeDecoration: function(lineNumber, element)
793 var widget = this._elementToWidget.remove(element);
795 this._codeMirror.removeLineWidget(widget);
799 * @param {number} lineNumber
800 * @param {number=} columnNumber
801 * @param {boolean=} shouldHighlight
803 revealPosition: function(lineNumber, columnNumber, shouldHighlight)
805 lineNumber = Number.constrain(lineNumber, 0, this._codeMirror.lineCount() - 1);
806 if (typeof columnNumber !== "number")
808 columnNumber = Number.constrain(columnNumber, 0, this._codeMirror.getLine(lineNumber).length);
810 this.clearPositionHighlight();
811 this._highlightedLine = this._codeMirror.getLineHandle(lineNumber);
812 if (!this._highlightedLine)
814 this._revealLine(lineNumber);
815 if (shouldHighlight) {
816 this._codeMirror.addLineClass(this._highlightedLine, null, "cm-highlight");
817 this._clearHighlightTimeout = setTimeout(this.clearPositionHighlight.bind(this), 2000);
819 this.setSelection(WebInspector.TextRange.createFromLocation(lineNumber, columnNumber));
822 clearPositionHighlight: function()
824 if (this._clearHighlightTimeout)
825 clearTimeout(this._clearHighlightTimeout);
826 delete this._clearHighlightTimeout;
828 if (this._highlightedLine)
829 this._codeMirror.removeLineClass(this._highlightedLine, null, "cm-highlight");
830 delete this._highlightedLine;
834 * @return {!Array.<!Element>}
836 elementsToRestoreScrollPositionsFor: function()
842 * @param {!WebInspector.TextEditor} textEditor
844 inheritScrollPositions: function(textEditor)
849 * @param {number} width
850 * @param {number} height
852 _updatePaddingBottom: function(width, height)
854 var scrollInfo = this._codeMirror.getScrollInfo();
855 var newPaddingBottom;
856 var linesElement = this.element.firstElementChild.querySelector(".CodeMirror-lines");
857 var lineCount = this._codeMirror.lineCount();
859 newPaddingBottom = 0;
861 newPaddingBottom = Math.max(scrollInfo.clientHeight - this._codeMirror.getLineHandle(this._codeMirror.lastLine()).height, 0);
862 newPaddingBottom += "px";
863 linesElement.style.paddingBottom = newPaddingBottom;
864 this._codeMirror.setSize(width, height);
867 _resizeEditor: function()
869 var parentElement = this.element.parentElement;
870 if (!parentElement || !this.isShowing())
872 var scrollLeft = this._codeMirror.doc.scrollLeft;
873 var scrollTop = this._codeMirror.doc.scrollTop;
874 var width = parentElement.offsetWidth;
875 var height = parentElement.offsetHeight;
876 this._codeMirror.setSize(width, height);
877 this._updatePaddingBottom(width, height);
878 this._codeMirror.scrollTo(scrollLeft, scrollTop);
883 this._autocompleteController.finishAutocomplete();
884 this._resizeEditor();
888 * @param {!WebInspector.TextRange} range
889 * @param {string} text
890 * @return {!WebInspector.TextRange}
892 editRange: function(range, text)
894 var pos = this._toPos(range);
895 this._codeMirror.replaceRange(text, pos.start, pos.end);
896 var newRange = this._toRange(pos.start, this._codeMirror.posFromIndex(this._codeMirror.indexFromPos(pos.start) + text.length));
897 this._delegate.onTextChanged(range, newRange);
898 if (WebInspector.settings.textEditorAutoDetectIndent.get())
899 this._updateEditorIndentation();
904 * @param {number} lineNumber
905 * @param {number} column
906 * @param {boolean=} prefixOnly
907 * @return {?WebInspector.TextRange}
909 _wordRangeForCursorPosition: function(lineNumber, column, prefixOnly)
911 var line = this.line(lineNumber);
912 if (column === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(column - 1)))
914 var wordStart = column - 1;
915 while (wordStart > 0 && WebInspector.TextUtils.isWordChar(line.charAt(wordStart - 1)))
918 return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, column);
919 var wordEnd = column;
920 while (wordEnd < line.length && WebInspector.TextUtils.isWordChar(line.charAt(wordEnd)))
922 return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, wordEnd);
926 * @param {!CodeMirror} codeMirror
927 * @param {!WebInspector.CodeMirrorTextEditor.BeforeChangeObject} changeObject
929 _beforeChange: function(codeMirror, changeObject)
931 if (!this._dictionary)
933 this._updatedLines = this._updatedLines || {};
934 for (var i = changeObject.from.line; i <= changeObject.to.line; ++i)
935 this._updatedLines[i] = this.line(i);
939 * @param {!CodeMirror} codeMirror
940 * @param {!WebInspector.CodeMirrorTextEditor.ChangeObject} changeObject
942 _change: function(codeMirror, changeObject)
944 // 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.
945 var hasOneLine = this._codeMirror.lineCount() === 1;
946 if (hasOneLine !== this._hasOneLine)
947 this._resizeEditor();
948 this._hasOneLine = hasOneLine;
949 var widgets = this._elementToWidget.values();
950 for (var i = 0; i < widgets.length; ++i)
951 this._codeMirror.removeLineWidget(widgets[i]);
952 this._elementToWidget.clear();
954 if (this._updatedLines) {
955 for (var lineNumber in this._updatedLines)
956 this._removeTextFromCompletionDictionary(this._updatedLines[lineNumber]);
957 delete this._updatedLines;
960 var linesToUpdate = {};
961 var singleCharInput = false;
963 var oldRange = this._toRange(changeObject.from, changeObject.to);
964 var newRange = oldRange.clone();
965 var linesAdded = changeObject.text.length;
966 singleCharInput = (changeObject.origin === "+input" && changeObject.text.length === 1 && changeObject.text[0].length === 1) ||
967 (changeObject.origin === "+delete" && changeObject.removed.length === 1 && changeObject.removed[0].length === 1);
968 if (linesAdded === 0) {
969 newRange.endLine = newRange.startLine;
970 newRange.endColumn = newRange.startColumn;
971 } else if (linesAdded === 1) {
972 newRange.endLine = newRange.startLine;
973 newRange.endColumn = newRange.startColumn + changeObject.text[0].length;
975 newRange.endLine = newRange.startLine + linesAdded - 1;
976 newRange.endColumn = changeObject.text[linesAdded - 1].length;
979 if (!this._muteTextChangedEvent)
980 this._delegate.onTextChanged(oldRange, newRange);
982 if (this._dictionary) {
983 for (var i = newRange.startLine; i <= newRange.endLine; ++i)
984 linesToUpdate[i] = this.line(i);
986 } while (changeObject = changeObject.next);
987 if (this._dictionary) {
988 for (var lineNumber in linesToUpdate)
989 this._addTextToCompletionDictionary(linesToUpdate[lineNumber]);
992 this._autocompleteController.autocomplete();
995 _cursorActivity: function()
997 var start = this._codeMirror.getCursor("anchor");
998 var end = this._codeMirror.getCursor("head");
999 this._delegate.selectionChanged(this._toRange(start, end));
1000 if (!this._tokenHighlighter.highlightedRegex())
1001 this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));
1005 * @param {!CodeMirror} codeMirror
1006 * @param {!{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}} selection
1008 _beforeSelectionChange: function(codeMirror, selection)
1010 if (!this._isHandlingMouseDownEvent)
1012 this._reportJump(this.selection(), this._toRange(selection.anchor, selection.head));
1016 * @param {?WebInspector.TextRange} from
1017 * @param {?WebInspector.TextRange} to
1019 _reportJump: function(from, to)
1021 if (from && to && from.equal(to))
1023 this._delegate.onJumpToPosition(from, to);
1028 if (this._scrollTimer)
1029 clearTimeout(this._scrollTimer);
1030 var topmostLineNumber = this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local");
1031 this._scrollTimer = setTimeout(this._delegate.scrollChanged.bind(this._delegate, topmostLineNumber), 100);
1036 this._delegate.editorFocused();
1041 this._autocompleteController.finishAutocomplete();
1045 * @param {number} lineNumber
1047 scrollToLine: function(lineNumber)
1049 var pos = new CodeMirror.Pos(lineNumber, 0);
1050 var coords = this._codeMirror.charCoords(pos, "local");
1051 this._codeMirror.scrollTo(0, coords.top);
1057 firstVisibleLine: function()
1059 return this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local");
1065 lastVisibleLine: function()
1067 var scrollInfo = this._codeMirror.getScrollInfo();
1068 return this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
1072 * @return {!WebInspector.TextRange}
1074 selection: function()
1076 var start = this._codeMirror.getCursor("anchor");
1077 var end = this._codeMirror.getCursor("head");
1079 return this._toRange(start, end);
1083 * @return {?WebInspector.TextRange}
1085 lastSelection: function()
1087 return this._lastSelection;
1091 * @param {!WebInspector.TextRange} textRange
1093 setSelection: function(textRange)
1095 this._lastSelection = textRange;
1096 var pos = this._toPos(textRange);
1097 this._codeMirror.setSelection(pos.start, pos.end);
1101 * @param {string} text
1103 _detectLineSeparator: function(text)
1105 this._lineSeparator = text.indexOf("\r\n") >= 0 ? "\r\n" : "\n";
1109 * @param {string} text
1111 setText: function(text)
1113 this._muteTextChangedEvent = true;
1114 if (text.length > WebInspector.CodeMirrorTextEditor.MaxEditableTextSize) {
1115 if (this._dictionary)
1116 this._dictionary.reset();
1117 this.setReadOnly(true);
1119 this._codeMirror.setValue(text);
1120 this._updateEditorIndentation();
1121 if (this._shouldClearHistory) {
1122 this._codeMirror.clearHistory();
1123 this._shouldClearHistory = false;
1125 this._detectLineSeparator(text);
1126 delete this._muteTextChangedEvent;
1134 return this._codeMirror.getValue().replace(/\n/g, this._lineSeparator);
1138 * @return {!WebInspector.TextRange}
1142 var lineCount = this.linesCount;
1143 var lastLine = this._codeMirror.getLine(lineCount - 1);
1144 return this._toRange(new CodeMirror.Pos(0, 0), new CodeMirror.Pos(lineCount - 1, lastLine.length));
1148 * @param {number} lineNumber
1151 line: function(lineNumber)
1153 return this._codeMirror.getLine(lineNumber);
1161 return this._codeMirror.lineCount();
1165 * @param {number} line
1166 * @param {string} name
1167 * @param {?Object} value
1169 setAttribute: function(line, name, value)
1171 if (line < 0 || line >= this._codeMirror.lineCount())
1173 var handle = this._codeMirror.getLineHandle(line);
1174 if (handle.attributes === undefined) handle.attributes = {};
1175 handle.attributes[name] = value;
1179 * @param {number} line
1180 * @param {string} name
1181 * @return {?Object} value
1183 getAttribute: function(line, name)
1185 if (line < 0 || line >= this._codeMirror.lineCount())
1187 var handle = this._codeMirror.getLineHandle(line);
1188 return handle.attributes && handle.attributes[name] !== undefined ? handle.attributes[name] : null;
1192 * @param {number} line
1193 * @param {string} name
1195 removeAttribute: function(line, name)
1197 if (line < 0 || line >= this._codeMirror.lineCount())
1199 var handle = this._codeMirror.getLineHandle(line);
1200 if (handle && handle.attributes)
1201 delete handle.attributes[name];
1205 * @param {!WebInspector.TextRange} range
1206 * @return {!{start: !CodeMirror.Pos, end: !CodeMirror.Pos}}
1208 _toPos: function(range)
1211 start: new CodeMirror.Pos(range.startLine, range.startColumn),
1212 end: new CodeMirror.Pos(range.endLine, range.endColumn)
1216 _toRange: function(start, end)
1218 return new WebInspector.TextRange(start.line, start.ch, end.line, end.ch);
1222 * @param {number} lineNumber
1223 * @param {number} columnNumber
1224 * @return {!WebInspector.TextEditorPositionHandle}
1226 textEditorPositionHandle: function(lineNumber, columnNumber)
1228 return new WebInspector.CodeMirrorPositionHandle(this._codeMirror, new CodeMirror.Pos(lineNumber, columnNumber));
1231 __proto__: WebInspector.VBox.prototype
1236 * @implements {WebInspector.TextEditorPositionHandle}
1237 * @param {!CodeMirror} codeMirror
1238 * @param {!CodeMirror.Pos} pos
1240 WebInspector.CodeMirrorPositionHandle = function(codeMirror, pos)
1242 this._codeMirror = codeMirror;
1243 this._lineHandle = codeMirror.getLineHandle(pos.line);
1244 this._columnNumber = pos.ch;
1247 WebInspector.CodeMirrorPositionHandle.prototype = {
1249 * @return {?{lineNumber: number, columnNumber: number}}
1253 var lineNumber = this._codeMirror.getLineNumber(this._lineHandle);
1254 if (typeof lineNumber !== "number")
1257 lineNumber: lineNumber,
1258 columnNumber: this._columnNumber
1263 * @param {!WebInspector.TextEditorPositionHandle} positionHandle
1266 equal: function(positionHandle)
1268 return positionHandle._lineHandle === this._lineHandle && positionHandle._columnNumber == this._columnNumber && positionHandle._codeMirror === this._codeMirror;
1274 * @param {!CodeMirror} codeMirror
1276 WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(codeMirror)
1278 this._codeMirror = codeMirror;
1281 WebInspector.CodeMirrorTextEditor.TokenHighlighter.prototype = {
1283 * @param {!RegExp} regex
1284 * @param {?WebInspector.TextRange} range
1286 highlightSearchResults: function(regex, range)
1288 var oldRegex = this._highlightRegex;
1289 this._highlightRegex = regex;
1290 this._highlightRange = range;
1291 if (this._searchResultMarker) {
1292 this._searchResultMarker.clear();
1293 delete this._searchResultMarker;
1295 if (this._highlightDescriptor && this._highlightDescriptor.selectionStart)
1296 this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection");
1297 var selectionStart = this._highlightRange ? new CodeMirror.Pos(this._highlightRange.startLine, this._highlightRange.startColumn) : null;
1299 this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection");
1300 if (this._highlightRegex === oldRegex) {
1301 // Do not re-add overlay mode if regex did not change for better performance.
1302 if (this._highlightDescriptor)
1303 this._highlightDescriptor.selectionStart = selectionStart;
1305 this._removeHighlight();
1306 this._setHighlighter(this._searchHighlighter.bind(this, this._highlightRegex), selectionStart);
1308 if (this._highlightRange) {
1309 var pos = WebInspector.CodeMirrorTextEditor.prototype._toPos(this._highlightRange);
1310 this._searchResultMarker = this._codeMirror.markText(pos.start, pos.end, {className: "cm-column-with-selection"});
1315 * @return {!RegExp|undefined}
1317 highlightedRegex: function()
1319 return this._highlightRegex;
1322 highlightSelectedTokens: function()
1324 delete this._highlightRegex;
1325 delete this._highlightRange;
1327 if (this._highlightDescriptor && this._highlightDescriptor.selectionStart)
1328 this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection");
1329 this._removeHighlight();
1330 var selectionStart = this._codeMirror.getCursor("start");
1331 var selectionEnd = this._codeMirror.getCursor("end");
1332 if (selectionStart.line !== selectionEnd.line)
1334 if (selectionStart.ch === selectionEnd.ch)
1337 var selectedText = this._codeMirror.getSelection();
1338 if (this._isWord(selectedText, selectionStart.line, selectionStart.ch, selectionEnd.ch)) {
1340 this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection")
1341 this._setHighlighter(this._tokenHighlighter.bind(this, selectedText, selectionStart), selectionStart);
1346 * @param {string} selectedText
1347 * @param {number} lineNumber
1348 * @param {number} startColumn
1349 * @param {number} endColumn
1351 _isWord: function(selectedText, lineNumber, startColumn, endColumn)
1353 var line = this._codeMirror.getLine(lineNumber);
1354 var leftBound = startColumn === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(startColumn - 1));
1355 var rightBound = endColumn === line.length || !WebInspector.TextUtils.isWordChar(line.charAt(endColumn));
1356 return leftBound && rightBound && WebInspector.TextUtils.isWord(selectedText);
1359 _removeHighlight: function()
1361 if (this._highlightDescriptor) {
1362 this._codeMirror.removeOverlay(this._highlightDescriptor.overlay);
1363 delete this._highlightDescriptor;
1368 * @param {!RegExp} regex
1369 * @param {!CodeMirror.StringStream} stream
1371 _searchHighlighter: function(regex, stream)
1373 if (stream.column() === 0)
1374 delete this._searchMatchLength;
1375 if (this._searchMatchLength) {
1376 if (this._searchMatchLength > 1) {
1377 for (var i = 0; i < this._searchMatchLength - 2; ++i)
1379 this._searchMatchLength = 1;
1380 return "search-highlight";
1383 delete this._searchMatchLength;
1384 return "search-highlight search-highlight-end";
1387 var match = stream.match(regex, false);
1390 var matchLength = match[0].length;
1391 if (matchLength === 1)
1392 return "search-highlight search-highlight-full";
1393 this._searchMatchLength = matchLength;
1394 return "search-highlight search-highlight-start";
1397 while (!stream.match(regex, false) && stream.next()) {};
1401 * @param {string} token
1402 * @param {!CodeMirror.Pos} selectionStart
1403 * @param {!CodeMirror.StringStream} stream
1405 _tokenHighlighter: function(token, selectionStart, stream)
1407 var tokenFirstChar = token.charAt(0);
1408 if (stream.match(token) && (stream.eol() || !WebInspector.TextUtils.isWordChar(stream.peek())))
1409 return stream.column() === selectionStart.ch ? "token-highlight column-with-selection" : "token-highlight";
1413 eatenChar = stream.next();
1414 } while (eatenChar && (WebInspector.TextUtils.isWordChar(eatenChar) || stream.peek() !== tokenFirstChar));
1418 * @param {function(!CodeMirror.StringStream)} highlighter
1420 _setHighlighter: function(highlighter, selectionStart)
1425 this._codeMirror.addOverlay(overlayMode);
1426 this._highlightDescriptor = {
1427 overlay: overlayMode,
1428 selectionStart: selectionStart
1435 * @param {!CodeMirror} codeMirror
1437 WebInspector.CodeMirrorTextEditor.BlockIndentController = function(codeMirror)
1439 codeMirror.addKeyMap(this);
1442 WebInspector.CodeMirrorTextEditor.BlockIndentController.prototype = {
1443 name: "blockIndentKeymap",
1448 Enter: function(codeMirror)
1450 if (codeMirror.somethingSelected())
1451 return CodeMirror.Pass;
1452 var cursor = codeMirror.getCursor();
1453 if (cursor.ch === 0)
1454 return CodeMirror.Pass;
1455 var line = codeMirror.getLine(cursor.line);
1456 if (line.substr(cursor.ch - 1, 2) === "{}") {
1457 codeMirror.execCommand("newlineAndIndent");
1458 codeMirror.setCursor(cursor);
1459 codeMirror.execCommand("newlineAndIndent");
1460 codeMirror.execCommand("indentMore");
1461 } else if (line.substr(cursor.ch - 1, 1) === "{") {
1462 codeMirror.execCommand("newlineAndIndent");
1463 codeMirror.execCommand("indentMore");
1465 return CodeMirror.Pass;
1471 "'}'": function(codeMirror)
1473 var cursor = codeMirror.getCursor();
1474 var line = codeMirror.getLine(cursor.line);
1475 for (var i = 0 ; i < line.length; ++i) {
1476 if (!WebInspector.TextUtils.isSpaceChar(line.charAt(i)))
1477 return CodeMirror.Pass;
1480 codeMirror.replaceRange("}", cursor);
1481 var matchingBracket = codeMirror.findMatchingBracket();
1482 if (!matchingBracket || !matchingBracket.match)
1485 line = codeMirror.getLine(matchingBracket.to.line);
1486 var desiredIndentation = 0;
1487 while (desiredIndentation < line.length && WebInspector.TextUtils.isSpaceChar(line.charAt(desiredIndentation)))
1488 ++desiredIndentation;
1490 codeMirror.replaceRange(line.substr(0, desiredIndentation) + "}", new CodeMirror.Pos(cursor.line, 0), new CodeMirror.Pos(cursor.line, cursor.ch + 1));
1496 * @param {!CodeMirror} codeMirror
1498 WebInspector.CodeMirrorTextEditor.FixWordMovement = function(codeMirror)
1500 function moveLeft(shift, codeMirror)
1502 var cursor = codeMirror.getCursor("head");
1503 if (cursor.ch !== 0 || cursor.line === 0)
1504 return CodeMirror.Pass;
1505 codeMirror.setExtending(shift);
1506 codeMirror.execCommand("goLineUp");
1507 codeMirror.execCommand("goLineEnd")
1508 codeMirror.setExtending(false);
1510 function moveRight(shift, codeMirror)
1512 var cursor = codeMirror.getCursor("head");
1513 var line = codeMirror.getLine(cursor.line);
1514 if (cursor.ch !== line.length || cursor.line + 1 === codeMirror.lineCount())
1515 return CodeMirror.Pass;
1516 codeMirror.setExtending(shift);
1517 codeMirror.execCommand("goLineDown");
1518 codeMirror.execCommand("goLineStart");
1519 codeMirror.setExtending(false);
1521 function delWordBack(codeMirror)
1523 if (codeMirror.somethingSelected())
1524 return CodeMirror.Pass;
1525 var cursor = codeMirror.getCursor("head");
1526 if (cursor.ch === 0)
1527 codeMirror.execCommand("delCharBefore");
1529 return CodeMirror.Pass;
1532 var modifierKey = WebInspector.isMac() ? "Alt" : "Ctrl";
1533 var leftKey = modifierKey + "-Left";
1534 var rightKey = modifierKey + "-Right";
1536 keyMap[leftKey] = moveLeft.bind(null, false);
1537 keyMap[rightKey] = moveRight.bind(null, false);
1538 keyMap["Shift-" + leftKey] = moveLeft.bind(null, true);
1539 keyMap["Shift-" + rightKey] = moveRight.bind(null, true);
1540 keyMap[modifierKey + "-Backspace"] = delWordBack;
1541 codeMirror.addKeyMap(keyMap);
1546 * @implements {WebInspector.SuggestBoxDelegate}
1547 * @param {!WebInspector.CodeMirrorTextEditor} textEditor
1548 * @param {!CodeMirror} codeMirror
1550 WebInspector.CodeMirrorTextEditor.AutocompleteController = function(textEditor, codeMirror)
1552 this._textEditor = textEditor;
1553 this._codeMirror = codeMirror;
1554 this._codeMirror.on("scroll", this._onScroll.bind(this));
1555 this._codeMirror.on("cursorActivity", this._onCursorActivity.bind(this));
1558 WebInspector.CodeMirrorTextEditor.AutocompleteController.prototype = {
1559 autocomplete: function()
1561 var dictionary = this._textEditor._dictionary;
1562 if (!dictionary || this._codeMirror.somethingSelected()) {
1563 this.finishAutocomplete();
1567 var cursor = this._codeMirror.getCursor();
1568 var substituteRange = this._textEditor._wordRangeForCursorPosition(cursor.line, cursor.ch, false);
1569 if (!substituteRange || substituteRange.startColumn === cursor.ch) {
1570 this.finishAutocomplete();
1573 var prefixRange = substituteRange.clone();
1574 prefixRange.endColumn = cursor.ch;
1576 var substituteWord = this._textEditor.copyRange(substituteRange);
1577 var hasPrefixInDictionary = dictionary.hasWord(substituteWord);
1578 if (hasPrefixInDictionary)
1579 dictionary.removeWord(substituteWord);
1580 var wordsWithPrefix = dictionary.wordsWithPrefix(this._textEditor.copyRange(prefixRange));
1581 if (hasPrefixInDictionary)
1582 dictionary.addWord(substituteWord);
1584 function sortSuggestions(a, b)
1586 return dictionary.wordCount(b) - dictionary.wordCount(a) || a.length - b.length;
1589 wordsWithPrefix.sort(sortSuggestions);
1591 if (!this._suggestBox) {
1592 this._suggestBox = new WebInspector.SuggestBox(this, this._textEditor.element, "generic-suggest", 6);
1593 this._anchorBox = this._anchorBoxForPosition(cursor.line, cursor.ch);
1595 this._suggestBox.updateSuggestions(this._anchorBox, wordsWithPrefix, 0, true, this._textEditor.copyRange(prefixRange));
1596 this._prefixRange = prefixRange;
1597 if (!this._suggestBox.visible())
1598 this.finishAutocomplete();
1601 finishAutocomplete: function()
1603 if (!this._suggestBox)
1605 this._suggestBox.hide();
1606 this._suggestBox = null;
1607 this._prefixRange = null;
1608 this._anchorBox = null;
1615 keyDown: function(e)
1617 if (!this._suggestBox)
1619 if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
1620 this.finishAutocomplete();
1623 if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Tab.code) {
1624 this._suggestBox.acceptSuggestion();
1625 this.finishAutocomplete();
1628 return this._suggestBox.keyPressed(e);
1632 * @param {string} suggestion
1633 * @param {boolean=} isIntermediateSuggestion
1635 applySuggestion: function(suggestion, isIntermediateSuggestion)
1637 this._currentSuggestion = suggestion;
1640 acceptSuggestion: function()
1642 if (this._prefixRange.endColumn - this._prefixRange.startColumn !== this._currentSuggestion.length) {
1643 var pos = this._textEditor._toPos(this._prefixRange);
1644 this._codeMirror.replaceRange(this._currentSuggestion, pos.start, pos.end, "+autocomplete");
1648 _onScroll: function()
1650 if (!this._suggestBox)
1652 var cursor = this._codeMirror.getCursor();
1653 var scrollInfo = this._codeMirror.getScrollInfo();
1654 var topmostLineNumber = this._codeMirror.lineAtHeight(scrollInfo.top, "local");
1655 var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
1656 if (cursor.line < topmostLineNumber || cursor.line > bottomLine)
1657 this.finishAutocomplete();
1659 this._anchorBox = this._anchorBoxForPosition(cursor.line, cursor.ch);
1660 this._suggestBox.setPosition(this._anchorBox);
1664 _onCursorActivity: function()
1666 if (!this._suggestBox)
1668 var cursor = this._codeMirror.getCursor();
1669 if (cursor.line !== this._prefixRange.startLine || cursor.ch > this._prefixRange.endColumn || cursor.ch < this._prefixRange.startColumn)
1670 this.finishAutocomplete();
1674 * @param {number} line
1675 * @param {number} column
1676 * @return {?AnchorBox}
1678 _anchorBoxForPosition: function(line, column)
1680 var metrics = this._textEditor.cursorPositionToCoordinates(line, column);
1681 return metrics ? new AnchorBox(metrics.x, metrics.y, 0, metrics.height) : null;
1686 * @param {string} modeName
1687 * @param {string} tokenPrefix
1689 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens = function(modeName, tokenPrefix)
1691 var oldModeName = modeName + "-old";
1692 if (CodeMirror.modes[oldModeName])
1695 CodeMirror.defineMode(oldModeName, CodeMirror.modes[modeName]);
1696 CodeMirror.defineMode(modeName, modeConstructor);
1698 function modeConstructor(config, parserConfig)
1700 var innerConfig = {};
1701 for (var i in parserConfig)
1702 innerConfig[i] = parserConfig[i];
1703 innerConfig.name = oldModeName;
1704 var codeMirrorMode = CodeMirror.getMode(config, innerConfig);
1705 codeMirrorMode.name = modeName;
1706 codeMirrorMode.token = tokenOverride.bind(null, codeMirrorMode.token);
1707 return codeMirrorMode;
1710 function tokenOverride(superToken, stream, state)
1712 var token = superToken(stream, state);
1713 return token ? tokenPrefix + token : token;
1717 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("css", "css-");
1718 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("javascript", "js-");
1719 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("xml", "xml-");
1722 var backgroundColor = InspectorFrontendHost.getSelectionBackgroundColor();
1723 var backgroundColorRule = backgroundColor ? ".CodeMirror .CodeMirror-selected { background-color: " + backgroundColor + ";}" : "";
1724 var foregroundColor = InspectorFrontendHost.getSelectionForegroundColor();
1725 var foregroundColorRule = foregroundColor ? ".CodeMirror .CodeMirror-selectedtext:not(.CodeMirror-persist-highlight) { color: " + foregroundColor + "!important;}" : "";
1726 if (!foregroundColorRule && !backgroundColorRule)
1729 var style = document.createElement("style");
1730 style.textContent = backgroundColorRule + foregroundColorRule;
1731 document.head.appendChild(style);