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.
33 * @extends {WebInspector.VBox}
34 * @implements {WebInspector.TextEditor}
35 * @param {?string} url
36 * @param {!WebInspector.TextEditorDelegate} delegate
38 WebInspector.CodeMirrorTextEditor = function(url, delegate)
40 WebInspector.VBox.call(this);
41 this._delegate = delegate;
44 this.registerRequiredCSS("cm/codemirror.css");
45 this.registerRequiredCSS("cmdevtools.css");
47 this._codeMirror = new window.CodeMirror(this.element, {
49 gutters: ["CodeMirror-linenumbers"],
52 styleSelectedText: true,
55 this._codeMirror._codeMirrorTextEditor = this;
57 CodeMirror.keyMap["devtools-common"] = {
59 "Right": "goCharRight",
63 "Home": "goLineStartSmart",
65 "PageDown": "goPageDown",
66 "Delete": "delCharAfter",
67 "Backspace": "delCharBefore",
69 "Shift-Tab": "indentLess",
70 "Enter": "smartNewlineAndIndent",
71 "Ctrl-Space": "autocomplete",
72 "Esc": "dismissMultipleSelections",
73 "Ctrl-M": "gotoMatchingBracket"
76 CodeMirror.keyMap["devtools-pc"] = {
77 "Ctrl-A": "selectAll",
78 "Ctrl-Z": "undoAndReveal",
79 "Shift-Ctrl-Z": "redoAndReveal",
81 "Ctrl-Home": "goDocStart",
82 "Ctrl-Up": "goDocStart",
83 "Ctrl-End": "goDocEnd",
84 "Ctrl-Down": "goDocEnd",
85 "Ctrl-Left": "goGroupLeft",
86 "Ctrl-Right": "goGroupRight",
87 "Alt-Left": "moveCamelLeft",
88 "Alt-Right": "moveCamelRight",
89 "Shift-Alt-Left": "selectCamelLeft",
90 "Shift-Alt-Right": "selectCamelRight",
91 "Ctrl-Backspace": "delGroupBefore",
92 "Ctrl-Delete": "delGroupAfter",
93 "Ctrl-/": "toggleComment",
94 "Ctrl-D": "selectNextOccurrence",
95 "Ctrl-U": "undoLastSelection",
96 fallthrough: "devtools-common"
99 CodeMirror.keyMap["devtools-mac"] = {
100 "Cmd-A" : "selectAll",
101 "Cmd-Z" : "undoAndReveal",
102 "Shift-Cmd-Z": "redoAndReveal",
103 "Cmd-Up": "goDocStart",
104 "Cmd-Down": "goDocEnd",
105 "Alt-Left": "goGroupLeft",
106 "Alt-Right": "goGroupRight",
107 "Ctrl-Left": "moveCamelLeft",
108 "Ctrl-Right": "moveCamelRight",
109 "Shift-Ctrl-Left": "selectCamelLeft",
110 "Shift-Ctrl-Right": "selectCamelRight",
111 "Cmd-Left": "goLineStartSmart",
112 "Cmd-Right": "goLineEnd",
113 "Alt-Backspace": "delGroupBefore",
114 "Alt-Delete": "delGroupAfter",
115 "Cmd-/": "toggleComment",
116 "Cmd-D": "selectNextOccurrence",
117 "Cmd-U": "undoLastSelection",
118 fallthrough: "devtools-common"
121 WebInspector.settings.textEditorIndent.addChangeListener(this._updateEditorIndentation, this);
122 WebInspector.settings.textEditorAutoDetectIndent.addChangeListener(this._updateEditorIndentation, this);
123 this._updateEditorIndentation();
124 WebInspector.settings.showWhitespacesInEditor.addChangeListener(this._updateCodeMirrorMode, this);
125 WebInspector.settings.textEditorBracketMatching.addChangeListener(this._enableBracketMatchingIfNeeded, this);
126 this._enableBracketMatchingIfNeeded();
128 this._codeMirror.setOption("keyMap", WebInspector.isMac() ? "devtools-mac" : "devtools-pc");
129 this._codeMirror.setOption("flattenSpans", false);
131 this._codeMirror.setOption("maxHighlightLength", WebInspector.CodeMirrorTextEditor.maxHighlightLength);
132 this._codeMirror.setOption("mode", null);
133 this._codeMirror.setOption("crudeMeasuringFrom", 1000);
135 this._shouldClearHistory = true;
136 this._lineSeparator = "\n";
138 this._autocompleteController = WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy;
139 this._tokenHighlighter = new WebInspector.CodeMirrorTextEditor.TokenHighlighter(this, this._codeMirror);
140 this._blockIndentController = new WebInspector.CodeMirrorTextEditor.BlockIndentController(this._codeMirror);
141 this._fixWordMovement = new WebInspector.CodeMirrorTextEditor.FixWordMovement(this._codeMirror);
142 this._selectNextOccurrenceController = new WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController(this, this._codeMirror);
144 this._codeMirror.on("changes", this._changes.bind(this));
145 this._codeMirror.on("gutterClick", this._gutterClick.bind(this));
146 this._codeMirror.on("cursorActivity", this._cursorActivity.bind(this));
147 this._codeMirror.on("beforeSelectionChange", this._beforeSelectionChange.bind(this));
148 this._codeMirror.on("scroll", this._scroll.bind(this));
149 this._codeMirror.on("focus", this._focus.bind(this));
150 this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false);
152 * @this {WebInspector.CodeMirrorTextEditor}
154 function updateAnticipateJumpFlag(value)
156 this._isHandlingMouseDownEvent = value;
158 this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, true), true);
159 this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, false), false);
161 this.element.style.overflow = "hidden";
162 this.element.firstChild.classList.add("source-code");
163 this.element.firstChild.classList.add("fill");
164 this._elementToWidget = new Map();
165 this._nestedUpdatesCounter = 0;
167 this.element.addEventListener("focus", this._handleElementFocus.bind(this), false);
168 this.element.addEventListener("keydown", this._handleKeyDown.bind(this), true);
169 this.element.addEventListener("keydown", this._handlePostKeyDown.bind(this), false);
170 this.element.tabIndex = 0;
172 this._setupWhitespaceHighlight();
175 /** @typedef {{canceled: boolean, from: !CodeMirror.Pos, to: !CodeMirror.Pos, text: string, origin: string, cancel: function()}} */
176 WebInspector.CodeMirrorTextEditor.BeforeChangeObject;
178 /** @typedef {{from: !CodeMirror.Pos, to: !CodeMirror.Pos, origin: string, text: !Array.<string>, removed: !Array.<string>}} */
179 WebInspector.CodeMirrorTextEditor.ChangeObject;
181 WebInspector.CodeMirrorTextEditor.maxHighlightLength = 1000;
184 * @param {!CodeMirror} codeMirror
186 WebInspector.CodeMirrorTextEditor.autocompleteCommand = function(codeMirror)
188 codeMirror._codeMirrorTextEditor._autocompleteController.autocomplete();
190 CodeMirror.commands.autocomplete = WebInspector.CodeMirrorTextEditor.autocompleteCommand;
193 * @param {!CodeMirror} codeMirror
195 WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand = function(codeMirror)
197 codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.undoLastSelection();
199 CodeMirror.commands.undoLastSelection = WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand;
202 * @param {!CodeMirror} codeMirror
204 WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand = function(codeMirror)
206 codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.selectNextOccurrence();
208 CodeMirror.commands.selectNextOccurrence = WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand;
211 * @param {boolean} shift
212 * @param {!CodeMirror} codeMirror
214 WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand = function(shift, codeMirror)
216 codeMirror._codeMirrorTextEditor._doCamelCaseMovement(-1, shift);
218 CodeMirror.commands.moveCamelLeft = WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand.bind(null, false);
219 CodeMirror.commands.selectCamelLeft = WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand.bind(null, true);
222 * @param {boolean} shift
223 * @param {!CodeMirror} codeMirror
225 WebInspector.CodeMirrorTextEditor.moveCamelRightCommand = function(shift, codeMirror)
227 codeMirror._codeMirrorTextEditor._doCamelCaseMovement(1, shift);
229 CodeMirror.commands.moveCamelRight = WebInspector.CodeMirrorTextEditor.moveCamelRightCommand.bind(null, false);
230 CodeMirror.commands.selectCamelRight = WebInspector.CodeMirrorTextEditor.moveCamelRightCommand.bind(null, true);
233 * @param {!CodeMirror} codeMirror
235 CodeMirror.commands.smartNewlineAndIndent = function(codeMirror)
237 codeMirror.operation(innerSmartNewlineAndIndent.bind(null, codeMirror));
239 function innerSmartNewlineAndIndent(codeMirror)
241 var selections = codeMirror.listSelections();
242 var replacements = [];
243 for (var i = 0; i < selections.length; ++i) {
244 var selection = selections[i];
245 var cur = CodeMirror.cmpPos(selection.head, selection.anchor) < 0 ? selection.head : selection.anchor;
246 var line = codeMirror.getLine(cur.line);
247 var indent = WebInspector.TextUtils.lineIndent(line);
248 replacements.push("\n" + indent.substring(0, Math.min(cur.ch, indent.length)));
250 codeMirror.replaceSelections(replacements);
251 codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
256 * @param {!CodeMirror} codeMirror
258 CodeMirror.commands.gotoMatchingBracket = function(codeMirror)
260 var updatedSelections = [];
261 var selections = codeMirror.listSelections();
262 for (var i = 0; i < selections.length; ++i) {
263 var selection = selections[i];
264 var cursor = selection.head;
265 var matchingBracket = codeMirror.findMatchingBracket(cursor, false, { maxScanLines: 10000 });
266 var updatedHead = cursor;
267 if (matchingBracket && matchingBracket.match) {
268 var columnCorrection = CodeMirror.cmpPos(matchingBracket.from, cursor) === 0 ? 1 : 0;
269 updatedHead = new CodeMirror.Pos(matchingBracket.to.line, matchingBracket.to.ch + columnCorrection);
271 updatedSelections.push({
276 codeMirror.setSelections(updatedSelections);
280 * @param {!CodeMirror} codemirror
282 CodeMirror.commands.undoAndReveal = function(codemirror)
284 var scrollInfo = codemirror.getScrollInfo();
285 codemirror.execCommand("undo");
286 var cursor = codemirror.getCursor("start");
287 codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo);
288 codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete();
292 * @param {!CodeMirror} codemirror
294 CodeMirror.commands.redoAndReveal = function(codemirror)
296 var scrollInfo = codemirror.getScrollInfo();
297 codemirror.execCommand("redo");
298 var cursor = codemirror.getCursor("start");
299 codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo);
300 codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete();
304 * @return {!Object|undefined}
306 CodeMirror.commands.dismissMultipleSelections = function(codemirror)
308 var selections = codemirror.listSelections();
309 var selection = selections[0];
310 if (selections.length === 1) {
311 if (codemirror._codeMirrorTextEditor._isSearchActive())
312 return CodeMirror.Pass;
313 if (WebInspector.CodeMirrorUtils.toRange(selection.anchor, selection.head).isEmpty())
314 return CodeMirror.Pass;
315 codemirror.setSelection(selection.anchor, selection.anchor, {scroll: false});
316 codemirror._codeMirrorTextEditor._revealLine(selection.anchor.line);
320 codemirror.setSelection(selection.anchor, selection.head, {scroll: false});
321 codemirror._codeMirrorTextEditor._revealLine(selection.anchor.line);
324 WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold = 2000;
325 WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan = 16;
326 WebInspector.CodeMirrorTextEditor.MaxEditableTextSize = 1024 * 1024 * 10;
328 WebInspector.CodeMirrorTextEditor.prototype = {
329 _onAutoAppendedSpaces: function()
331 this._autoAppendedSpaces = this._autoAppendedSpaces || [];
332 for (var i = 0; i < this._autoAppendedSpaces.length; ++i) {
333 var position = this._autoAppendedSpaces[i].resolve();
336 var line = this.line(position.lineNumber);
337 if (line.length === position.columnNumber && WebInspector.TextUtils.lineIndent(line).length === line.length)
338 this._codeMirror.replaceRange("", new CodeMirror.Pos(position.lineNumber, 0), new CodeMirror.Pos(position.lineNumber, position.columnNumber));
340 this._autoAppendedSpaces = [];
341 var selections = this.selections();
342 for (var i = 0; i < selections.length; ++i) {
343 var selection = selections[i];
344 this._autoAppendedSpaces.push(this.textEditorPositionHandle(selection.startLine, selection.startColumn));
349 * @param {number} lineNumber
350 * @param {number} lineLength
351 * @param {number} charNumber
352 * @return {{lineNumber: number, columnNumber: number}}
354 _normalizePositionForOverlappingColumn: function(lineNumber, lineLength, charNumber)
356 var linesCount = this._codeMirror.lineCount();
357 var columnNumber = charNumber;
358 if (charNumber < 0 && lineNumber > 0) {
360 columnNumber = this.line(lineNumber).length;
361 } else if (charNumber >= lineLength && lineNumber < linesCount - 1) {
365 columnNumber = Number.constrain(charNumber, 0, lineLength);
368 lineNumber: lineNumber,
369 columnNumber: columnNumber
374 * @param {number} lineNumber
375 * @param {number} columnNumber
376 * @param {number} direction
377 * @return {{lineNumber: number, columnNumber: number}}
379 _camelCaseMoveFromPosition: function(lineNumber, columnNumber, direction)
382 * @param {number} charNumber
383 * @param {number} length
386 function valid(charNumber, length)
388 return charNumber >= 0 && charNumber < length;
392 * @param {string} text
393 * @param {number} charNumber
396 function isWordStart(text, charNumber)
398 var position = charNumber;
399 var nextPosition = charNumber + 1;
400 return valid(position, text.length) && valid(nextPosition, text.length)
401 && WebInspector.TextUtils.isWordChar(text[position]) && WebInspector.TextUtils.isWordChar(text[nextPosition])
402 && WebInspector.TextUtils.isUpperCase(text[position]) && WebInspector.TextUtils.isLowerCase(text[nextPosition]);
406 * @param {string} text
407 * @param {number} charNumber
410 function isWordEnd(text, charNumber)
412 var position = charNumber;
413 var prevPosition = charNumber - 1;
414 return valid(position, text.length) && valid(prevPosition, text.length)
415 && WebInspector.TextUtils.isWordChar(text[position]) && WebInspector.TextUtils.isWordChar(text[prevPosition])
416 && WebInspector.TextUtils.isUpperCase(text[position]) && WebInspector.TextUtils.isLowerCase(text[prevPosition]);
420 * @param {number} lineNumber
421 * @param {number} lineLength
422 * @param {number} columnNumber
423 * @return {{lineNumber: number, columnNumber: number}}
425 function constrainPosition(lineNumber, lineLength, columnNumber)
428 lineNumber: lineNumber,
429 columnNumber: Number.constrain(columnNumber, 0, lineLength)
433 var text = this.line(lineNumber);
434 var length = text.length;
436 if ((columnNumber === length && direction === 1)
437 || (columnNumber === 0 && direction === -1))
438 return this._normalizePositionForOverlappingColumn(lineNumber, length, columnNumber + direction);
440 var charNumber = direction === 1 ? columnNumber : columnNumber - 1;
442 // Move through initial spaces if any.
443 while (valid(charNumber, length) && WebInspector.TextUtils.isSpaceChar(text[charNumber]))
444 charNumber += direction;
445 if (!valid(charNumber, length))
446 return constrainPosition(lineNumber, length, charNumber);
448 if (WebInspector.TextUtils.isStopChar(text[charNumber])) {
449 while (valid(charNumber, length) && WebInspector.TextUtils.isStopChar(text[charNumber]))
450 charNumber += direction;
451 if (!valid(charNumber, length))
452 return constrainPosition(lineNumber, length, charNumber);
454 lineNumber: lineNumber,
455 columnNumber: direction === -1 ? charNumber + 1 : charNumber
459 charNumber += direction;
460 while (valid(charNumber, length) && !isWordStart(text, charNumber) && !isWordEnd(text, charNumber) && WebInspector.TextUtils.isWordChar(text[charNumber]))
461 charNumber += direction;
463 if (!valid(charNumber, length))
464 return constrainPosition(lineNumber, length, charNumber);
465 if (isWordStart(text, charNumber) || isWordEnd(text, charNumber)) {
467 lineNumber: lineNumber,
468 columnNumber: charNumber
473 lineNumber: lineNumber,
474 columnNumber: direction === -1 ? charNumber + 1 : charNumber
479 * @param {number} direction
480 * @param {boolean} shift
482 _doCamelCaseMovement: function(direction, shift)
484 var selections = this.selections();
485 for (var i = 0; i < selections.length; ++i) {
486 var selection = selections[i];
487 var move = this._camelCaseMoveFromPosition(selection.endLine, selection.endColumn, direction);
488 selection.endLine = move.lineNumber;
489 selection.endColumn = move.columnNumber;
491 selections[i] = selection.collapseToEnd();
493 this.setSelections(selections);
498 WebInspector.settings.textEditorIndent.removeChangeListener(this._updateEditorIndentation, this);
499 WebInspector.settings.textEditorAutoDetectIndent.removeChangeListener(this._updateEditorIndentation, this);
500 WebInspector.settings.showWhitespacesInEditor.removeChangeListener(this._updateCodeMirrorMode, this);
501 WebInspector.settings.textEditorBracketMatching.removeChangeListener(this._enableBracketMatchingIfNeeded, this);
504 _enableBracketMatchingIfNeeded: function()
506 this._codeMirror.setOption("autoCloseBrackets", WebInspector.settings.textEditorBracketMatching.get() ? { explode: false } : false);
511 if (this._wasOnceShown)
513 this._wasOnceShown = true;
514 this._codeMirror.refresh();
517 _guessIndentationLevel: function()
519 var tabRegex = /^\t+/;
522 function processLine(lineHandle)
524 var text = lineHandle.text;
525 if (text.length === 0 || !WebInspector.TextUtils.isSpaceChar(text[0]))
527 if (tabRegex.test(text)) {
532 while (i < text.length && WebInspector.TextUtils.isSpaceChar(text[i]))
536 indents[i] = 1 + (indents[i] || 0);
538 this._codeMirror.eachLine(0, 1000, processLine);
540 var onePercentFilterThreshold = this.linesCount / 100;
541 if (tabLines && tabLines > onePercentFilterThreshold)
543 var minimumIndent = Infinity;
544 for (var i in indents) {
545 if (indents[i] < onePercentFilterThreshold)
547 var indent = parseInt(i, 10);
548 if (minimumIndent > indent)
549 minimumIndent = indent;
551 if (minimumIndent === Infinity)
552 return WebInspector.settings.textEditorIndent.get();
553 return new Array(minimumIndent + 1).join(" ");
556 _updateEditorIndentation: function()
559 var indent = WebInspector.settings.textEditorIndent.get();
560 if (WebInspector.settings.textEditorAutoDetectIndent.get())
561 indent = this._guessIndentationLevel();
562 if (indent === WebInspector.TextUtils.Indent.TabCharacter) {
563 this._codeMirror.setOption("indentWithTabs", true);
564 this._codeMirror.setOption("indentUnit", 4);
566 this._codeMirror.setOption("indentWithTabs", false);
567 this._codeMirror.setOption("indentUnit", indent.length);
568 extraKeys.Tab = function(codeMirror)
570 if (codeMirror.somethingSelected())
571 return CodeMirror.Pass;
572 var pos = codeMirror.getCursor("head");
573 codeMirror.replaceRange(indent.substring(pos.ch % indent.length), codeMirror.getCursor());
576 this._codeMirror.setOption("extraKeys", extraKeys);
577 this._indentationLevel = indent;
585 return this._indentationLevel;
591 _isSearchActive: function()
593 return !!this._tokenHighlighter.highlightedRegex();
597 * @param {!RegExp} regex
598 * @param {?WebInspector.TextRange} range
600 highlightSearchResults: function(regex, range)
603 * @this {WebInspector.CodeMirrorTextEditor}
605 function innerHighlightRegex()
608 this._revealLine(range.startLine);
609 if (range.endColumn > WebInspector.CodeMirrorTextEditor.maxHighlightLength)
610 this.setSelection(range);
612 this.setSelection(WebInspector.TextRange.createFromLocation(range.startLine, range.startColumn));
614 // Collapse selection to end on search start so that we jump to next occurrence on the first enter press.
615 this.setSelection(this.selection().collapseToEnd());
617 this._tokenHighlighter.highlightSearchResults(regex, range);
619 if (!this._selectionBeforeSearch)
620 this._selectionBeforeSearch = this.selection();
621 this._codeMirror.operation(innerHighlightRegex.bind(this));
624 cancelSearchResultsHighlight: function()
626 this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));
627 if (this._selectionBeforeSearch) {
628 this._reportJump(this._selectionBeforeSearch, this.selection());
629 delete this._selectionBeforeSearch;
635 this._codeMirror.undo();
640 this._codeMirror.redo();
643 _setupWhitespaceHighlight: function()
645 if (WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected || !WebInspector.settings.showWhitespacesInEditor.get())
647 WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected = true;
648 const classBase = ".show-whitespaces .CodeMirror .cm-whitespace-";
649 const spaceChar = "·";
652 for (var i = 1; i <= WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan; ++i) {
653 spaceChars += spaceChar;
654 var rule = classBase + i + "::before { content: '" + spaceChars + "';}\n";
657 var style = document.createElement("style");
658 style.textContent = rules;
659 document.head.appendChild(style);
662 _handleKeyDown: function(e)
664 if (this._autocompleteController.keyDown(e))
668 _handlePostKeyDown: function(e)
670 if (e.defaultPrevented)
675 * @param {?WebInspector.CompletionDictionary} dictionary
677 setCompletionDictionary: function(dictionary)
679 this._autocompleteController.dispose();
681 this._autocompleteController = new WebInspector.CodeMirrorTextEditor.AutocompleteController(this, this._codeMirror, dictionary);
683 this._autocompleteController = WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy;
687 * @param {number} lineNumber
688 * @param {number} column
689 * @return {?{x: number, y: number, height: number}}
691 cursorPositionToCoordinates: function(lineNumber, column)
693 if (lineNumber >= this._codeMirror.lineCount() || lineNumber < 0 || column < 0 || column > this._codeMirror.getLine(lineNumber).length)
696 var metrics = this._codeMirror.cursorCoords(new CodeMirror.Pos(lineNumber, column));
701 height: metrics.bottom - metrics.top
708 * @return {?WebInspector.TextRange}
710 coordinatesToCursorPosition: function(x, y)
712 var element = document.elementFromPoint(x, y);
713 if (!element || !element.isSelfOrDescendant(this._codeMirror.getWrapperElement()))
715 var gutterBox = this._codeMirror.getGutterElement().boxInWindow();
716 if (x >= gutterBox.x && x <= gutterBox.x + gutterBox.width &&
717 y >= gutterBox.y && y <= gutterBox.y + gutterBox.height)
719 var coords = this._codeMirror.coordsChar({left: x, top: y});
720 return WebInspector.CodeMirrorUtils.toRange(coords, coords);
724 * @param {number} lineNumber
725 * @param {number} column
726 * @return {?{startColumn: number, endColumn: number, type: string}}
728 tokenAtTextPosition: function(lineNumber, column)
730 if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
732 var token = this._codeMirror.getTokenAt(new CodeMirror.Pos(lineNumber, (column || 0) + 1));
733 if (!token || !token.type)
736 startColumn: token.start,
737 endColumn: token.end,
743 * @param {!WebInspector.TextRange} textRange
746 copyRange: function(textRange)
748 var pos = WebInspector.CodeMirrorUtils.toPos(textRange.normalize());
749 return this._codeMirror.getRange(pos.start, pos.end);
757 return this._codeMirror.isClean();
760 markClean: function()
762 this._codeMirror.markClean();
765 _hasLongLines: function()
767 function lineIterator(lineHandle)
769 if (lineHandle.text.length > WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold)
773 var hasLongLines = false;
774 this._codeMirror.eachLine(lineIterator);
779 * @param {string} mimeType
782 _whitespaceOverlayMode: function(mimeType)
784 var modeName = CodeMirror.mimeModes[mimeType] ? (CodeMirror.mimeModes[mimeType].name || CodeMirror.mimeModes[mimeType]) : CodeMirror.mimeModes["text/plain"];
785 modeName += "+whitespaces";
786 if (CodeMirror.modes[modeName])
789 function modeConstructor(config, parserConfig)
791 function nextToken(stream)
793 if (stream.peek() === " ") {
795 while (spaces < WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan && stream.peek() === " ") {
799 return "whitespace whitespace-" + spaces;
801 while (!stream.eol() && stream.peek() !== " ")
805 var whitespaceMode = {
808 return CodeMirror.overlayMode(CodeMirror.getMode(config, mimeType), whitespaceMode, false);
810 CodeMirror.defineMode(modeName, modeConstructor);
814 _enableLongLinesMode: function()
816 this._codeMirror.setOption("styleSelectedText", false);
817 this._longLinesMode = true;
820 _disableLongLinesMode: function()
822 this._codeMirror.setOption("styleSelectedText", true);
823 this._longLinesMode = false;
826 _updateCodeMirrorMode: function()
828 this._setupWhitespaceHighlight();
829 var showWhitespaces = WebInspector.settings.showWhitespacesInEditor.get();
830 this.element.classList.toggle("show-whitespaces", showWhitespaces);
831 this._codeMirror.setOption("mode", showWhitespaces ? this._whitespaceOverlayMode(this._mimeType) : this._mimeType);
835 * @param {string} mimeType
837 setMimeType: function(mimeType)
839 this._mimeType = mimeType;
840 if (this._hasLongLines())
841 this._enableLongLinesMode();
843 this._disableLongLinesMode();
844 this._updateCodeMirrorMode();
845 this._autocompleteController.setMimeType(mimeType);
849 * @param {boolean} readOnly
851 setReadOnly: function(readOnly)
853 this.element.classList.toggle("CodeMirror-readonly", readOnly)
854 this._codeMirror.setOption("readOnly", readOnly);
862 return !!this._codeMirror.getOption("readOnly");
866 * @param {!Object} highlightDescriptor
868 removeHighlight: function(highlightDescriptor)
870 highlightDescriptor.clear();
874 * @param {!WebInspector.TextRange} range
875 * @param {string} cssClass
878 highlightRange: function(range, cssClass)
880 cssClass = "CodeMirror-persist-highlight " + cssClass;
881 var pos = WebInspector.CodeMirrorUtils.toPos(range);
883 return this._codeMirror.markText(pos.start, pos.end, {
885 startStyle: cssClass + "-start",
886 endStyle: cssClass + "-end"
893 defaultFocusedElement: function()
900 this._codeMirror.focus();
903 _handleElementFocus: function()
905 this._codeMirror.focus();
908 beginUpdates: function()
910 ++this._nestedUpdatesCounter;
913 endUpdates: function()
915 if (!--this._nestedUpdatesCounter)
916 this._codeMirror.refresh();
920 * @param {number} lineNumber
922 _revealLine: function(lineNumber)
924 this._innerRevealLine(lineNumber, this._codeMirror.getScrollInfo());
928 * @param {number} lineNumber
929 * @param {!{left: number, top: number, width: number, height: number, clientWidth: number, clientHeight: number}} scrollInfo
931 _innerRevealLine: function(lineNumber, scrollInfo)
933 var topLine = this._codeMirror.lineAtHeight(scrollInfo.top, "local");
934 var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
935 var linesPerScreen = bottomLine - topLine + 1;
936 if (lineNumber < topLine) {
937 var topLineToReveal = Math.max(lineNumber - (linesPerScreen / 2) + 1, 0) | 0;
938 this._codeMirror.scrollIntoView(new CodeMirror.Pos(topLineToReveal, 0));
939 } else if (lineNumber > bottomLine) {
940 var bottomLineToReveal = Math.min(lineNumber + (linesPerScreen / 2) - 1, this.linesCount - 1) | 0;
941 this._codeMirror.scrollIntoView(new CodeMirror.Pos(bottomLineToReveal, 0));
945 _gutterClick: function(instance, lineNumber, gutter, event)
947 this.dispatchEventToListeners(WebInspector.TextEditor.Events.GutterClick, { lineNumber: lineNumber, event: event });
950 _contextMenu: function(event)
952 var contextMenu = new WebInspector.ContextMenu(event);
953 var target = event.target.enclosingNodeOrSelfWithClass("CodeMirror-gutter-elt");
955 this._delegate.populateLineGutterContextMenu(contextMenu, parseInt(target.textContent, 10) - 1);
957 this._delegate.populateTextAreaContextMenu(contextMenu, 0);
958 contextMenu.appendApplicableItems(this);
963 * @param {number} lineNumber
964 * @param {boolean} disabled
965 * @param {boolean} conditional
967 addBreakpoint: function(lineNumber, disabled, conditional)
969 if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
971 var className = "cm-breakpoint" + (conditional ? " cm-breakpoint-conditional" : "") + (disabled ? " cm-breakpoint-disabled" : "");
972 this._codeMirror.addLineClass(lineNumber, "wrap", className);
976 * @param {number} lineNumber
978 removeBreakpoint: function(lineNumber)
980 if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
982 var wrapClasses = this._codeMirror.getLineHandle(lineNumber).wrapClass;
985 var classes = wrapClasses.split(" ");
986 for (var i = 0; i < classes.length; ++i) {
987 if (classes[i].startsWith("cm-breakpoint"))
988 this._codeMirror.removeLineClass(lineNumber, "wrap", classes[i]);
993 * @param {number} lineNumber
995 setExecutionLine: function(lineNumber)
997 this.clearPositionHighlight();
998 this._executionLine = this._codeMirror.getLineHandle(lineNumber);
999 if (!this._executionLine)
1001 this._codeMirror.addLineClass(this._executionLine, "wrap", "cm-execution-line");
1004 clearExecutionLine: function()
1006 this.clearPositionHighlight();
1007 if (this._executionLine)
1008 this._codeMirror.removeLineClass(this._executionLine, "wrap", "cm-execution-line");
1009 delete this._executionLine;
1013 * @param {number} lineNumber
1014 * @param {string} className
1015 * @param {boolean} toggled
1017 toggleLineClass: function(lineNumber, className, toggled)
1019 var lineHandle = this._codeMirror.getLineHandle(lineNumber);
1023 this._codeMirror.addLineClass(lineHandle, "wrap", className);
1025 this._codeMirror.removeLineClass(lineHandle, "wrap", className);
1029 * @param {number} lineNumber
1030 * @param {!Element} element
1032 addDecoration: function(lineNumber, element)
1034 var widget = this._codeMirror.addLineWidget(lineNumber, element);
1035 this._elementToWidget.put(element, widget);
1039 * @param {number} lineNumber
1040 * @param {!Element} element
1042 removeDecoration: function(lineNumber, element)
1044 var widget = this._elementToWidget.remove(element);
1046 this._codeMirror.removeLineWidget(widget);
1050 * @param {number} lineNumber
1051 * @param {number=} columnNumber
1052 * @param {boolean=} shouldHighlight
1054 revealPosition: function(lineNumber, columnNumber, shouldHighlight)
1056 lineNumber = Number.constrain(lineNumber, 0, this._codeMirror.lineCount() - 1);
1057 if (typeof columnNumber !== "number")
1059 columnNumber = Number.constrain(columnNumber, 0, this._codeMirror.getLine(lineNumber).length);
1061 this.clearPositionHighlight();
1062 this._highlightedLine = this._codeMirror.getLineHandle(lineNumber);
1063 if (!this._highlightedLine)
1065 this._revealLine(lineNumber);
1066 if (shouldHighlight) {
1067 this._codeMirror.addLineClass(this._highlightedLine, null, "cm-highlight");
1068 this._clearHighlightTimeout = setTimeout(this.clearPositionHighlight.bind(this), 2000);
1070 this.setSelection(WebInspector.TextRange.createFromLocation(lineNumber, columnNumber));
1073 clearPositionHighlight: function()
1075 if (this._clearHighlightTimeout)
1076 clearTimeout(this._clearHighlightTimeout);
1077 delete this._clearHighlightTimeout;
1079 if (this._highlightedLine)
1080 this._codeMirror.removeLineClass(this._highlightedLine, null, "cm-highlight");
1081 delete this._highlightedLine;
1085 * @return {!Array.<!Element>}
1087 elementsToRestoreScrollPositionsFor: function()
1093 * @param {!WebInspector.TextEditor} textEditor
1095 inheritScrollPositions: function(textEditor)
1100 * @param {number} width
1101 * @param {number} height
1103 _updatePaddingBottom: function(width, height)
1105 var scrollInfo = this._codeMirror.getScrollInfo();
1106 var newPaddingBottom;
1107 var linesElement = this.element.firstElementChild.querySelector(".CodeMirror-lines");
1108 var lineCount = this._codeMirror.lineCount();
1110 newPaddingBottom = 0;
1112 newPaddingBottom = Math.max(scrollInfo.clientHeight - this._codeMirror.getLineHandle(this._codeMirror.lastLine()).height, 0);
1113 newPaddingBottom += "px";
1114 linesElement.style.paddingBottom = newPaddingBottom;
1115 this._codeMirror.setSize(width, height);
1118 _resizeEditor: function()
1120 var parentElement = this.element.parentElement;
1121 if (!parentElement || !this.isShowing())
1123 var scrollLeft = this._codeMirror.doc.scrollLeft;
1124 var scrollTop = this._codeMirror.doc.scrollTop;
1125 var width = parentElement.offsetWidth;
1126 var height = parentElement.offsetHeight - this.element.offsetTop;
1127 this._codeMirror.setSize(width, height);
1128 this._updatePaddingBottom(width, height);
1129 this._codeMirror.scrollTo(scrollLeft, scrollTop);
1132 onResize: function()
1134 this._autocompleteController.finishAutocomplete();
1135 this._resizeEditor();
1139 * @param {!WebInspector.TextRange} range
1140 * @param {string} text
1141 * @return {!WebInspector.TextRange}
1143 editRange: function(range, text)
1145 var pos = WebInspector.CodeMirrorUtils.toPos(range);
1146 this._codeMirror.replaceRange(text, pos.start, pos.end);
1147 var newRange = WebInspector.CodeMirrorUtils.toRange(pos.start, this._codeMirror.posFromIndex(this._codeMirror.indexFromPos(pos.start) + text.length));
1148 this._delegate.onTextChanged(range, newRange);
1149 if (WebInspector.settings.textEditorAutoDetectIndent.get())
1150 this._updateEditorIndentation();
1155 * @param {number} lineNumber
1156 * @param {number} column
1157 * @param {function(string):boolean} isWordChar
1158 * @return {!WebInspector.TextRange}
1160 _wordRangeForCursorPosition: function(lineNumber, column, isWordChar)
1162 var line = this.line(lineNumber);
1163 var wordStart = column;
1164 if (column !== 0 && isWordChar(line.charAt(column - 1))) {
1165 wordStart = column - 1;
1166 while (wordStart > 0 && isWordChar(line.charAt(wordStart - 1)))
1169 var wordEnd = column;
1170 while (wordEnd < line.length && isWordChar(line.charAt(wordEnd)))
1172 return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, wordEnd);
1176 * @param {!WebInspector.CodeMirrorTextEditor.ChangeObject} changeObject
1177 * @return {{oldRange: !WebInspector.TextRange, newRange: !WebInspector.TextRange}}
1179 _changeObjectToEditOperation: function(changeObject)
1181 var oldRange = WebInspector.CodeMirrorUtils.toRange(changeObject.from, changeObject.to);
1182 var newRange = oldRange.clone();
1183 var linesAdded = changeObject.text.length;
1184 if (linesAdded === 0) {
1185 newRange.endLine = newRange.startLine;
1186 newRange.endColumn = newRange.startColumn;
1187 } else if (linesAdded === 1) {
1188 newRange.endLine = newRange.startLine;
1189 newRange.endColumn = newRange.startColumn + changeObject.text[0].length;
1191 newRange.endLine = newRange.startLine + linesAdded - 1;
1192 newRange.endColumn = changeObject.text[linesAdded - 1].length;
1201 * @param {!CodeMirror} codeMirror
1202 * @param {!Array.<!WebInspector.CodeMirrorTextEditor.ChangeObject>} changes
1204 _changes: function(codeMirror, changes)
1206 if (!changes.length)
1208 // 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.
1209 var hasOneLine = this._codeMirror.lineCount() === 1;
1210 if (hasOneLine !== this._hasOneLine)
1211 this._resizeEditor();
1212 this._hasOneLine = hasOneLine;
1213 var widgets = this._elementToWidget.values();
1214 for (var i = 0; i < widgets.length; ++i)
1215 this._codeMirror.removeLineWidget(widgets[i]);
1216 this._elementToWidget.clear();
1218 for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) {
1219 var changeObject = changes[changeIndex];
1221 var editInfo = this._changeObjectToEditOperation(changeObject);
1222 if (!this._muteTextChangedEvent)
1223 this._delegate.onTextChanged(editInfo.oldRange, editInfo.newRange);
1227 _cursorActivity: function()
1229 var start = this._codeMirror.getCursor("anchor");
1230 var end = this._codeMirror.getCursor("head");
1231 this._delegate.selectionChanged(WebInspector.CodeMirrorUtils.toRange(start, end));
1232 if (!this._isSearchActive())
1233 this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));
1237 * @param {!CodeMirror} codeMirror
1238 * @param {{ranges: !Array.<{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>}} selection
1240 _beforeSelectionChange: function(codeMirror, selection)
1242 this._selectNextOccurrenceController.selectionWillChange();
1243 if (!this._isHandlingMouseDownEvent)
1245 if (!selection.ranges.length)
1247 var primarySelection = selection.ranges[0];
1248 this._reportJump(this.selection(), WebInspector.CodeMirrorUtils.toRange(primarySelection.anchor, primarySelection.head));
1252 * @param {?WebInspector.TextRange} from
1253 * @param {?WebInspector.TextRange} to
1255 _reportJump: function(from, to)
1257 if (from && to && from.equal(to))
1259 this._delegate.onJumpToPosition(from, to);
1264 if (this._scrollTimer)
1265 clearTimeout(this._scrollTimer);
1266 var topmostLineNumber = this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local");
1267 this._scrollTimer = setTimeout(this._delegate.scrollChanged.bind(this._delegate, topmostLineNumber), 100);
1272 this._delegate.editorFocused();
1276 * @param {number} lineNumber
1278 scrollToLine: function(lineNumber)
1280 var pos = new CodeMirror.Pos(lineNumber, 0);
1281 var coords = this._codeMirror.charCoords(pos, "local");
1282 this._codeMirror.scrollTo(0, coords.top);
1288 firstVisibleLine: function()
1290 return this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local");
1296 lastVisibleLine: function()
1298 var scrollInfo = this._codeMirror.getScrollInfo();
1299 return this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
1303 * @return {!WebInspector.TextRange}
1305 selection: function()
1307 var start = this._codeMirror.getCursor("anchor");
1308 var end = this._codeMirror.getCursor("head");
1310 return WebInspector.CodeMirrorUtils.toRange(start, end);
1314 * @return {!Array.<!WebInspector.TextRange>}
1316 selections: function()
1318 var selectionList = this._codeMirror.listSelections();
1320 for (var i = 0; i < selectionList.length; ++i) {
1321 var selection = selectionList[i];
1322 result.push(WebInspector.CodeMirrorUtils.toRange(selection.anchor, selection.head));
1328 * @return {?WebInspector.TextRange}
1330 lastSelection: function()
1332 return this._lastSelection;
1336 * @param {!WebInspector.TextRange} textRange
1338 setSelection: function(textRange)
1340 this._lastSelection = textRange;
1341 var pos = WebInspector.CodeMirrorUtils.toPos(textRange);
1342 this._codeMirror.setSelection(pos.start, pos.end);
1346 * @param {!Array.<!WebInspector.TextRange>} ranges
1347 * @param {number=} primarySelectionIndex
1349 setSelections: function(ranges, primarySelectionIndex)
1351 var selections = [];
1352 for (var i = 0; i < ranges.length; ++i) {
1353 var selection = WebInspector.CodeMirrorUtils.toPos(ranges[i]);
1355 anchor: selection.start,
1359 primarySelectionIndex = primarySelectionIndex || 0;
1360 this._codeMirror.setSelections(selections, primarySelectionIndex, { scroll: false });
1364 * @param {string} text
1366 _detectLineSeparator: function(text)
1368 this._lineSeparator = text.indexOf("\r\n") >= 0 ? "\r\n" : "\n";
1372 * @param {string} text
1374 setText: function(text)
1376 this._muteTextChangedEvent = true;
1377 if (text.length > WebInspector.CodeMirrorTextEditor.MaxEditableTextSize) {
1378 this._autocompleteController.setEnabled(false);
1379 this.setReadOnly(true);
1381 this._codeMirror.setValue(text);
1382 this._updateEditorIndentation();
1383 if (this._shouldClearHistory) {
1384 this._codeMirror.clearHistory();
1385 this._shouldClearHistory = false;
1387 this._detectLineSeparator(text);
1388 delete this._muteTextChangedEvent;
1396 return this._codeMirror.getValue().replace(/\n/g, this._lineSeparator);
1400 * @return {!WebInspector.TextRange}
1404 var lineCount = this.linesCount;
1405 var lastLine = this._codeMirror.getLine(lineCount - 1);
1406 return WebInspector.CodeMirrorUtils.toRange(new CodeMirror.Pos(0, 0), new CodeMirror.Pos(lineCount - 1, lastLine.length));
1410 * @param {number} lineNumber
1413 line: function(lineNumber)
1415 return this._codeMirror.getLine(lineNumber);
1423 return this._codeMirror.lineCount();
1427 * @param {number} line
1428 * @param {string} name
1429 * @param {?Object} value
1431 setAttribute: function(line, name, value)
1433 if (line < 0 || line >= this._codeMirror.lineCount())
1435 var handle = this._codeMirror.getLineHandle(line);
1436 if (handle.attributes === undefined) handle.attributes = {};
1437 handle.attributes[name] = value;
1441 * @param {number} line
1442 * @param {string} name
1443 * @return {?Object} value
1445 getAttribute: function(line, name)
1447 if (line < 0 || line >= this._codeMirror.lineCount())
1449 var handle = this._codeMirror.getLineHandle(line);
1450 return handle.attributes && handle.attributes[name] !== undefined ? handle.attributes[name] : null;
1454 * @param {number} line
1455 * @param {string} name
1457 removeAttribute: function(line, name)
1459 if (line < 0 || line >= this._codeMirror.lineCount())
1461 var handle = this._codeMirror.getLineHandle(line);
1462 if (handle && handle.attributes)
1463 delete handle.attributes[name];
1467 * @param {number} lineNumber
1468 * @param {number} columnNumber
1469 * @return {!WebInspector.TextEditorPositionHandle}
1471 textEditorPositionHandle: function(lineNumber, columnNumber)
1473 return new WebInspector.CodeMirrorPositionHandle(this._codeMirror, new CodeMirror.Pos(lineNumber, columnNumber));
1476 __proto__: WebInspector.VBox.prototype
1481 * @implements {WebInspector.TextEditorPositionHandle}
1482 * @param {!CodeMirror} codeMirror
1483 * @param {!CodeMirror.Pos} pos
1485 WebInspector.CodeMirrorPositionHandle = function(codeMirror, pos)
1487 this._codeMirror = codeMirror;
1488 this._lineHandle = codeMirror.getLineHandle(pos.line);
1489 this._columnNumber = pos.ch;
1492 WebInspector.CodeMirrorPositionHandle.prototype = {
1494 * @return {?{lineNumber: number, columnNumber: number}}
1498 var lineNumber = this._codeMirror.getLineNumber(this._lineHandle);
1499 if (typeof lineNumber !== "number")
1502 lineNumber: lineNumber,
1503 columnNumber: this._columnNumber
1508 * @param {!WebInspector.TextEditorPositionHandle} positionHandle
1511 equal: function(positionHandle)
1513 return positionHandle._lineHandle === this._lineHandle && positionHandle._columnNumber == this._columnNumber && positionHandle._codeMirror === this._codeMirror;
1519 * @param {!WebInspector.CodeMirrorTextEditor} textEditor
1520 * @param {!CodeMirror} codeMirror
1522 WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(textEditor, codeMirror)
1524 this._textEditor = textEditor;
1525 this._codeMirror = codeMirror;
1528 WebInspector.CodeMirrorTextEditor.TokenHighlighter.prototype = {
1530 * @param {!RegExp} regex
1531 * @param {?WebInspector.TextRange} range
1533 highlightSearchResults: function(regex, range)
1535 var oldRegex = this._highlightRegex;
1536 this._highlightRegex = regex;
1537 this._highlightRange = range;
1538 if (this._searchResultMarker) {
1539 this._searchResultMarker.clear();
1540 delete this._searchResultMarker;
1542 if (this._highlightDescriptor && this._highlightDescriptor.selectionStart)
1543 this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection");
1544 var selectionStart = this._highlightRange ? new CodeMirror.Pos(this._highlightRange.startLine, this._highlightRange.startColumn) : null;
1546 this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection");
1547 if (this._highlightRegex === oldRegex) {
1548 // Do not re-add overlay mode if regex did not change for better performance.
1549 if (this._highlightDescriptor)
1550 this._highlightDescriptor.selectionStart = selectionStart;
1552 this._removeHighlight();
1553 this._setHighlighter(this._searchHighlighter.bind(this, this._highlightRegex), selectionStart);
1555 if (this._highlightRange) {
1556 var pos = WebInspector.CodeMirrorUtils.toPos(this._highlightRange);
1557 this._searchResultMarker = this._codeMirror.markText(pos.start, pos.end, {className: "cm-column-with-selection"});
1562 * @return {!RegExp|undefined}
1564 highlightedRegex: function()
1566 return this._highlightRegex;
1569 highlightSelectedTokens: function()
1571 delete this._highlightRegex;
1572 delete this._highlightRange;
1574 if (this._highlightDescriptor && this._highlightDescriptor.selectionStart)
1575 this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection");
1576 this._removeHighlight();
1577 var selectionStart = this._codeMirror.getCursor("start");
1578 var selectionEnd = this._codeMirror.getCursor("end");
1579 if (selectionStart.line !== selectionEnd.line)
1581 if (selectionStart.ch === selectionEnd.ch)
1584 var selections = this._codeMirror.getSelections();
1585 if (selections.length > 1)
1587 var selectedText = selections[0];
1588 if (this._isWord(selectedText, selectionStart.line, selectionStart.ch, selectionEnd.ch)) {
1590 this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection")
1591 this._setHighlighter(this._tokenHighlighter.bind(this, selectedText, selectionStart), selectionStart);
1596 * @param {string} selectedText
1597 * @param {number} lineNumber
1598 * @param {number} startColumn
1599 * @param {number} endColumn
1601 _isWord: function(selectedText, lineNumber, startColumn, endColumn)
1603 var line = this._codeMirror.getLine(lineNumber);
1604 var leftBound = startColumn === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(startColumn - 1));
1605 var rightBound = endColumn === line.length || !WebInspector.TextUtils.isWordChar(line.charAt(endColumn));
1606 return leftBound && rightBound && WebInspector.TextUtils.isWord(selectedText);
1609 _removeHighlight: function()
1611 if (this._highlightDescriptor) {
1612 this._codeMirror.removeOverlay(this._highlightDescriptor.overlay);
1613 delete this._highlightDescriptor;
1618 * @param {!RegExp} regex
1619 * @param {!CodeMirror.StringStream} stream
1621 _searchHighlighter: function(regex, stream)
1623 if (stream.column() === 0)
1624 delete this._searchMatchLength;
1625 if (this._searchMatchLength) {
1626 if (this._searchMatchLength > 2) {
1627 for (var i = 0; i < this._searchMatchLength - 2; ++i)
1629 this._searchMatchLength = 1;
1630 return "search-highlight";
1633 delete this._searchMatchLength;
1634 return "search-highlight search-highlight-end";
1637 var match = stream.match(regex, false);
1640 var matchLength = match[0].length;
1641 if (matchLength === 1)
1642 return "search-highlight search-highlight-full";
1643 this._searchMatchLength = matchLength;
1644 return "search-highlight search-highlight-start";
1647 while (!stream.match(regex, false) && stream.next()) {};
1651 * @param {string} token
1652 * @param {!CodeMirror.Pos} selectionStart
1653 * @param {!CodeMirror.StringStream} stream
1655 _tokenHighlighter: function(token, selectionStart, stream)
1657 var tokenFirstChar = token.charAt(0);
1658 if (stream.match(token) && (stream.eol() || !WebInspector.TextUtils.isWordChar(stream.peek())))
1659 return stream.column() === selectionStart.ch ? "token-highlight column-with-selection" : "token-highlight";
1663 eatenChar = stream.next();
1664 } while (eatenChar && (WebInspector.TextUtils.isWordChar(eatenChar) || stream.peek() !== tokenFirstChar));
1668 * @param {function(!CodeMirror.StringStream)} highlighter
1669 * @param {?CodeMirror.Pos} selectionStart
1671 _setHighlighter: function(highlighter, selectionStart)
1676 this._codeMirror.addOverlay(overlayMode);
1677 this._highlightDescriptor = {
1678 overlay: overlayMode,
1679 selectionStart: selectionStart
1686 * @param {!CodeMirror} codeMirror
1688 WebInspector.CodeMirrorTextEditor.BlockIndentController = function(codeMirror)
1690 codeMirror.addKeyMap(this);
1693 WebInspector.CodeMirrorTextEditor.BlockIndentController.prototype = {
1694 name: "blockIndentKeymap",
1699 Enter: function(codeMirror)
1701 var selections = codeMirror.listSelections();
1702 var replacements = [];
1703 var allSelectionsAreCollapsedBlocks = false;
1704 for (var i = 0; i < selections.length; ++i) {
1705 var selection = selections[i];
1706 var start = CodeMirror.cmpPos(selection.head, selection.anchor) < 0 ? selection.head : selection.anchor;
1707 var line = codeMirror.getLine(start.line);
1708 var indent = WebInspector.TextUtils.lineIndent(line);
1709 var indentToInsert = "\n" + indent + codeMirror._codeMirrorTextEditor.indent();
1710 var isCollapsedBlock = false;
1711 if (selection.head.ch === 0)
1712 return CodeMirror.Pass;
1713 if (line.substr(selection.head.ch - 1, 2) === "{}") {
1714 indentToInsert += "\n" + indent;
1715 isCollapsedBlock = true;
1716 } else if (line.substr(selection.head.ch - 1, 1) !== "{") {
1717 return CodeMirror.Pass;
1719 if (i > 0 && allSelectionsAreCollapsedBlocks !== isCollapsedBlock)
1720 return CodeMirror.Pass;
1721 replacements.push(indentToInsert);
1722 allSelectionsAreCollapsedBlocks = isCollapsedBlock;
1724 codeMirror.replaceSelections(replacements);
1725 if (!allSelectionsAreCollapsedBlocks) {
1726 codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
1729 selections = codeMirror.listSelections();
1730 var updatedSelections = [];
1731 for (var i = 0; i < selections.length; ++i) {
1732 var selection = selections[i];
1733 var line = codeMirror.getLine(selection.head.line - 1);
1734 var position = new CodeMirror.Pos(selection.head.line - 1, line.length);
1735 updatedSelections.push({
1740 codeMirror.setSelections(updatedSelections);
1741 codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
1747 "'}'": function(codeMirror)
1749 if (codeMirror.somethingSelected())
1750 return CodeMirror.Pass;
1752 var selections = codeMirror.listSelections();
1753 var replacements = [];
1754 for (var i = 0; i < selections.length; ++i) {
1755 var selection = selections[i];
1756 var line = codeMirror.getLine(selection.head.line);
1757 if (line !== WebInspector.TextUtils.lineIndent(line))
1758 return CodeMirror.Pass;
1759 replacements.push("}");
1762 codeMirror.replaceSelections(replacements);
1763 selections = codeMirror.listSelections();
1765 var updatedSelections = [];
1766 for (var i = 0; i < selections.length; ++i) {
1767 var selection = selections[i];
1768 var matchingBracket = codeMirror.findMatchingBracket(selection.head);
1769 if (!matchingBracket || !matchingBracket.match)
1771 updatedSelections.push({
1772 head: selection.head,
1773 anchor: new CodeMirror.Pos(selection.head.line, 0)
1775 var line = codeMirror.getLine(matchingBracket.to.line);
1776 var indent = WebInspector.TextUtils.lineIndent(line);
1777 replacements.push(indent + "}");
1779 codeMirror.setSelections(updatedSelections);
1780 codeMirror.replaceSelections(replacements);
1786 * @param {!CodeMirror} codeMirror
1788 WebInspector.CodeMirrorTextEditor.FixWordMovement = function(codeMirror)
1790 function moveLeft(shift, codeMirror)
1792 codeMirror.setExtending(shift);
1793 var cursor = codeMirror.getCursor("head");
1794 codeMirror.execCommand("goGroupLeft");
1795 var newCursor = codeMirror.getCursor("head");
1796 if (newCursor.ch === 0 && newCursor.line !== 0) {
1797 codeMirror.setExtending(false);
1801 var skippedText = codeMirror.getRange(newCursor, cursor, "#");
1802 if (/^\s+$/.test(skippedText))
1803 codeMirror.execCommand("goGroupLeft");
1804 codeMirror.setExtending(false);
1807 function moveRight(shift, codeMirror)
1809 codeMirror.setExtending(shift);
1810 var cursor = codeMirror.getCursor("head");
1811 codeMirror.execCommand("goGroupRight");
1812 var newCursor = codeMirror.getCursor("head");
1813 if (newCursor.ch === 0 && newCursor.line !== 0) {
1814 codeMirror.setExtending(false);
1818 var skippedText = codeMirror.getRange(cursor, newCursor, "#");
1819 if (/^\s+$/.test(skippedText))
1820 codeMirror.execCommand("goGroupRight");
1821 codeMirror.setExtending(false);
1824 var modifierKey = WebInspector.isMac() ? "Alt" : "Ctrl";
1825 var leftKey = modifierKey + "-Left";
1826 var rightKey = modifierKey + "-Right";
1828 keyMap[leftKey] = moveLeft.bind(null, false);
1829 keyMap[rightKey] = moveRight.bind(null, false);
1830 keyMap["Shift-" + leftKey] = moveLeft.bind(null, true);
1831 keyMap["Shift-" + rightKey] = moveRight.bind(null, true);
1832 codeMirror.addKeyMap(keyMap);
1838 WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI = function() {}
1840 WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI.prototype = {
1841 dispose: function() { },
1844 * @param {boolean} enabled
1846 setEnabled: function(enabled) { },
1849 * @param {string} mimeType
1851 setMimeType: function(mimeType) { },
1853 autocomplete: function() { },
1855 finishAutocomplete: function() { },
1861 keyDown: function(e) { }
1866 * @implements {WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI}
1868 WebInspector.CodeMirrorTextEditor.DummyAutocompleteController = function() { }
1870 WebInspector.CodeMirrorTextEditor.DummyAutocompleteController.prototype = {
1871 dispose: function() { },
1874 * @param {boolean} enabled
1876 setEnabled: function(enabled) { },
1879 * @param {string} mimeType
1881 setMimeType: function(mimeType) { },
1883 autocomplete: function() { },
1885 finishAutocomplete: function() { },
1891 keyDown: function(e)
1899 * @implements {WebInspector.SuggestBoxDelegate}
1900 * @implements {WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI}
1901 * @param {!WebInspector.CodeMirrorTextEditor} textEditor
1902 * @param {!CodeMirror} codeMirror
1903 * @param {?WebInspector.CompletionDictionary} dictionary
1905 WebInspector.CodeMirrorTextEditor.AutocompleteController = function(textEditor, codeMirror, dictionary)
1907 this._textEditor = textEditor;
1908 this._codeMirror = codeMirror;
1910 this._onScroll = this._onScroll.bind(this);
1911 this._onCursorActivity = this._onCursorActivity.bind(this);
1912 this._changes = this._changes.bind(this);
1913 this._beforeChange = this._beforeChange.bind(this);
1914 this._blur = this._blur.bind(this);
1915 this._codeMirror.on("scroll", this._onScroll);
1916 this._codeMirror.on("cursorActivity", this._onCursorActivity);
1917 this._codeMirror.on("changes", this._changes);
1918 this._codeMirror.on("beforeChange", this._beforeChange);
1919 this._codeMirror.on("blur", this._blur);
1921 this._additionalWordChars = WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars;
1922 this._enabled = true;
1924 this._dictionary = dictionary;
1925 this._addTextToCompletionDictionary(this._textEditor.text());
1928 WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy = new WebInspector.CodeMirrorTextEditor.DummyAutocompleteController();
1929 WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars = {};
1930 WebInspector.CodeMirrorTextEditor._CSSAdditionalWordChars = { ".": true, "-": true };
1932 WebInspector.CodeMirrorTextEditor.AutocompleteController.prototype = {
1935 this._codeMirror.off("scroll", this._onScroll);
1936 this._codeMirror.off("cursorActivity", this._onCursorActivity);
1937 this._codeMirror.off("changes", this._changes);
1938 this._codeMirror.off("beforeChange", this._beforeChange);
1939 this._codeMirror.off("blur", this._blur);
1943 * @param {boolean} enabled
1945 setEnabled: function(enabled)
1947 if (enabled === this._enabled)
1949 this._enabled = enabled;
1951 this._dictionary.reset();
1953 this._addTextToCompletionDictionary(this._textEditor.text());
1957 * @param {string} mimeType
1959 setMimeType: function(mimeType)
1961 var additionalWordChars = mimeType.indexOf("css") !== -1 ? WebInspector.CodeMirrorTextEditor._CSSAdditionalWordChars : WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars;
1962 if (additionalWordChars !== this._additionalWordChars) {
1963 this._additionalWordChars = additionalWordChars;
1964 this._dictionary.reset();
1965 this._addTextToCompletionDictionary(this._textEditor.text());
1970 * @param {string} char
1973 _isWordChar: function(char)
1975 return WebInspector.TextUtils.isWordChar(char) || !!this._additionalWordChars[char];
1979 * @param {string} word
1982 _shouldProcessWordForAutocompletion: function(word)
1984 return !!word.length && (word[0] < '0' || word[0] > '9');
1988 * @param {string} text
1990 _addTextToCompletionDictionary: function(text)
1994 var words = WebInspector.TextUtils.textToWords(text, this._isWordChar.bind(this));
1995 for (var i = 0; i < words.length; ++i) {
1996 if (this._shouldProcessWordForAutocompletion(words[i]))
1997 this._dictionary.addWord(words[i]);
2002 * @param {string} text
2004 _removeTextFromCompletionDictionary: function(text)
2008 var words = WebInspector.TextUtils.textToWords(text, this._isWordChar.bind(this));
2009 for (var i = 0; i < words.length; ++i) {
2010 if (this._shouldProcessWordForAutocompletion(words[i]))
2011 this._dictionary.removeWord(words[i]);
2016 * @param {!CodeMirror} codeMirror
2017 * @param {!WebInspector.CodeMirrorTextEditor.BeforeChangeObject} changeObject
2019 _beforeChange: function(codeMirror, changeObject)
2023 this._updatedLines = this._updatedLines || {};
2024 for (var i = changeObject.from.line; i <= changeObject.to.line; ++i)
2025 this._updatedLines[i] = this._textEditor.line(i);
2029 * @param {!CodeMirror} codeMirror
2030 * @param {!Array.<!WebInspector.CodeMirrorTextEditor.ChangeObject>} changes
2032 _changes: function(codeMirror, changes)
2034 if (!changes.length || !this._enabled)
2037 if (this._updatedLines) {
2038 for (var lineNumber in this._updatedLines)
2039 this._removeTextFromCompletionDictionary(this._updatedLines[lineNumber]);
2040 delete this._updatedLines;
2043 var linesToUpdate = {};
2044 var singleCharInput = false;
2045 for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) {
2046 var changeObject = changes[changeIndex];
2047 singleCharInput = (changeObject.origin === "+input" && changeObject.text.length === 1 && changeObject.text[0].length === 1) ||
2048 (this._suggestBox && changeObject.origin === "+delete" && changeObject.removed.length === 1 && changeObject.removed[0].length === 1);
2050 var editInfo = this._textEditor._changeObjectToEditOperation(changeObject);
2051 for (var i = editInfo.newRange.startLine; i <= editInfo.newRange.endLine; ++i)
2052 linesToUpdate[i] = this._textEditor.line(i);
2054 for (var lineNumber in linesToUpdate)
2055 this._addTextToCompletionDictionary(linesToUpdate[lineNumber]);
2057 if (singleCharInput)
2058 this.autocomplete();
2063 this.finishAutocomplete();
2067 * @param {number} lineNumber
2068 * @param {number} columnNumber
2069 * @return {!WebInspector.TextRange}
2071 _autocompleteWordRange: function(lineNumber, columnNumber)
2073 return this._textEditor._wordRangeForCursorPosition(lineNumber, columnNumber, this._isWordChar.bind(this));
2077 * @param {!WebInspector.TextRange} mainSelection
2078 * @param {!Array.<!{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>} selections
2081 _validateSelectionsContexts: function(mainSelection, selections)
2083 var mainSelectionContext = this._textEditor.copyRange(mainSelection);
2084 for (var i = 0; i < selections.length; ++i) {
2085 var wordRange = this._autocompleteWordRange(selections[i].head.line, selections[i].head.ch);
2088 var context = this._textEditor.copyRange(wordRange);
2089 if (context !== mainSelectionContext)
2095 autocomplete: function()
2097 var dictionary = this._dictionary;
2098 if (this._codeMirror.somethingSelected()) {
2099 this.finishAutocomplete();
2103 var selections = this._codeMirror.listSelections().slice();
2104 var topSelection = selections.shift();
2105 var cursor = topSelection.head;
2106 var substituteRange = this._autocompleteWordRange(cursor.line, cursor.ch);
2107 if (!substituteRange || substituteRange.startColumn === cursor.ch || !this._validateSelectionsContexts(substituteRange, selections)) {
2108 this.finishAutocomplete();
2112 var prefixRange = substituteRange.clone();
2113 prefixRange.endColumn = cursor.ch;
2115 var substituteWord = this._textEditor.copyRange(substituteRange);
2116 var hasPrefixInDictionary = dictionary.hasWord(substituteWord);
2117 if (hasPrefixInDictionary)
2118 dictionary.removeWord(substituteWord);
2119 var wordsWithPrefix = dictionary.wordsWithPrefix(this._textEditor.copyRange(prefixRange));
2120 if (hasPrefixInDictionary)
2121 dictionary.addWord(substituteWord);
2123 function sortSuggestions(a, b)
2125 return dictionary.wordCount(b) - dictionary.wordCount(a) || a.length - b.length;
2128 wordsWithPrefix.sort(sortSuggestions);
2130 if (!this._suggestBox)
2131 this._suggestBox = new WebInspector.SuggestBox(this, 6);
2132 var oldPrefixRange = this._prefixRange;
2133 this._prefixRange = prefixRange;
2134 if (!oldPrefixRange || prefixRange.startLine !== oldPrefixRange.startLine || prefixRange.startColumn !== oldPrefixRange.startColumn)
2135 this._updateAnchorBox();
2136 this._suggestBox.updateSuggestions(this._anchorBox, wordsWithPrefix, 0, true, this._textEditor.copyRange(prefixRange));
2137 if (!this._suggestBox.visible())
2138 this.finishAutocomplete();
2141 finishAutocomplete: function()
2143 if (!this._suggestBox)
2145 this._suggestBox.hide();
2146 this._suggestBox = null;
2147 this._prefixRange = null;
2148 this._anchorBox = null;
2155 keyDown: function(e)
2157 if (!this._suggestBox)
2159 if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
2160 this.finishAutocomplete();
2163 if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Tab.code) {
2164 this._suggestBox.acceptSuggestion();
2165 this.finishAutocomplete();
2168 return this._suggestBox.keyPressed(e);
2172 * @param {string} suggestion
2173 * @param {boolean=} isIntermediateSuggestion
2175 applySuggestion: function(suggestion, isIntermediateSuggestion)
2177 this._currentSuggestion = suggestion;
2180 acceptSuggestion: function()
2182 if (this._prefixRange.endColumn - this._prefixRange.startColumn === this._currentSuggestion.length)
2185 var selections = this._codeMirror.listSelections().slice();
2186 var prefixLength = this._prefixRange.endColumn - this._prefixRange.startColumn;
2187 for (var i = selections.length - 1; i >= 0; --i) {
2188 var start = selections[i].head;
2189 var end = new CodeMirror.Pos(start.line, start.ch - prefixLength);
2190 this._codeMirror.replaceRange(this._currentSuggestion, start, end, "+autocomplete");
2194 _onScroll: function()
2196 if (!this._suggestBox)
2198 var cursor = this._codeMirror.getCursor();
2199 var scrollInfo = this._codeMirror.getScrollInfo();
2200 var topmostLineNumber = this._codeMirror.lineAtHeight(scrollInfo.top, "local");
2201 var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
2202 if (cursor.line < topmostLineNumber || cursor.line > bottomLine)
2203 this.finishAutocomplete();
2205 this._updateAnchorBox();
2206 this._suggestBox.setPosition(this._anchorBox);
2210 _onCursorActivity: function()
2212 if (!this._suggestBox)
2214 var cursor = this._codeMirror.getCursor();
2215 if (cursor.line !== this._prefixRange.startLine || cursor.ch > this._prefixRange.endColumn || cursor.ch <= this._prefixRange.startColumn)
2216 this.finishAutocomplete();
2219 _updateAnchorBox: function()
2221 var line = this._prefixRange.startLine;
2222 var column = this._prefixRange.startColumn;
2223 var metrics = this._textEditor.cursorPositionToCoordinates(line, column);
2224 this._anchorBox = metrics ? new AnchorBox(metrics.x, metrics.y, 0, metrics.height) : null;
2230 * @param {!WebInspector.CodeMirrorTextEditor} textEditor
2231 * @param {!CodeMirror} codeMirror
2233 WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController = function(textEditor, codeMirror)
2235 this._textEditor = textEditor;
2236 this._codeMirror = codeMirror;
2239 WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController.prototype = {
2240 selectionWillChange: function()
2242 if (!this._muteSelectionListener)
2243 delete this._fullWordSelection;
2247 * @param {!Array.<!WebInspector.TextRange>} selections
2248 * @param {!WebInspector.TextRange} range
2251 _findRange: function(selections, range)
2253 for (var i = 0; i < selections.length; ++i) {
2254 if (range.equal(selections[i]))
2260 undoLastSelection: function()
2262 this._muteSelectionListener = true;
2263 this._codeMirror.execCommand("undoSelection");
2264 this._muteSelectionListener = false;
2267 selectNextOccurrence: function()
2269 var selections = this._textEditor.selections();
2270 var anyEmptySelection = false;
2271 for (var i = 0; i < selections.length; ++i) {
2272 var selection = selections[i];
2273 anyEmptySelection = anyEmptySelection || selection.isEmpty();
2274 if (selection.startLine !== selection.endLine)
2277 if (anyEmptySelection) {
2278 this._expandSelectionsToWords(selections);
2282 var last = selections[selections.length - 1];
2285 next = this._findNextOccurrence(next, !!this._fullWordSelection);
2286 } while (next && this._findRange(selections, next) && !next.equal(last));
2290 selections.push(next);
2292 this._muteSelectionListener = true;
2293 this._textEditor.setSelections(selections, selections.length - 1);
2294 delete this._muteSelectionListener;
2296 this._textEditor._revealLine(next.startLine);
2300 * @param {!Array.<!WebInspector.TextRange>} selections
2302 _expandSelectionsToWords: function(selections)
2304 var newSelections = [];
2305 for (var i = 0; i < selections.length; ++i) {
2306 var selection = selections[i];
2307 var startRangeWord = this._textEditor._wordRangeForCursorPosition(selection.startLine, selection.startColumn, WebInspector.TextUtils.isWordChar)
2308 || WebInspector.TextRange.createFromLocation(selection.startLine, selection.startColumn);
2309 var endRangeWord = this._textEditor._wordRangeForCursorPosition(selection.endLine, selection.endColumn, WebInspector.TextUtils.isWordChar)
2310 || WebInspector.TextRange.createFromLocation(selection.endLine, selection.endColumn);
2311 var newSelection = new WebInspector.TextRange(startRangeWord.startLine, startRangeWord.startColumn, endRangeWord.endLine, endRangeWord.endColumn);
2312 newSelections.push(newSelection);
2314 this._textEditor.setSelections(newSelections, newSelections.length - 1);
2315 this._fullWordSelection = true;
2319 * @param {!WebInspector.TextRange} range
2320 * @param {boolean} fullWord
2321 * @return {?WebInspector.TextRange}
2323 _findNextOccurrence: function(range, fullWord)
2325 range = range.normalize();
2326 var matchedLineNumber;
2327 var matchedColumnNumber;
2328 var textToFind = this._textEditor.copyRange(range);
2329 function findWordInLine(wordRegex, lineNumber, lineText, from, to)
2331 if (typeof matchedLineNumber === "number")
2333 wordRegex.lastIndex = from;
2334 var result = wordRegex.exec(lineText);
2335 if (!result || result.index + textToFind.length > to)
2337 matchedLineNumber = lineNumber;
2338 matchedColumnNumber = result.index;
2342 var iteratedLineNumber;
2343 function lineIterator(regex, lineHandle)
2345 if (findWordInLine(regex, iteratedLineNumber++, lineHandle.text, 0, lineHandle.text.length))
2349 var regexSource = textToFind.escapeForRegExp();
2351 regexSource = "\\b" + regexSource + "\\b";
2352 var wordRegex = new RegExp(regexSource, "gi");
2353 var currentLineText = this._codeMirror.getLine(range.startLine);
2355 findWordInLine(wordRegex, range.startLine, currentLineText, range.endColumn, currentLineText.length);
2356 iteratedLineNumber = range.startLine + 1;
2357 this._codeMirror.eachLine(range.startLine + 1, this._codeMirror.lineCount(), lineIterator.bind(null, wordRegex));
2358 iteratedLineNumber = 0;
2359 this._codeMirror.eachLine(0, range.startLine, lineIterator.bind(null, wordRegex));
2360 findWordInLine(wordRegex, range.startLine, currentLineText, 0, range.startColumn);
2362 if (typeof matchedLineNumber !== "number")
2364 return new WebInspector.TextRange(matchedLineNumber, matchedColumnNumber, matchedLineNumber, matchedColumnNumber + textToFind.length);
2369 * @param {string} modeName
2370 * @param {string} tokenPrefix
2372 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens = function(modeName, tokenPrefix)
2374 var oldModeName = modeName + "-old";
2375 if (CodeMirror.modes[oldModeName])
2378 CodeMirror.defineMode(oldModeName, CodeMirror.modes[modeName]);
2379 CodeMirror.defineMode(modeName, modeConstructor);
2381 function modeConstructor(config, parserConfig)
2383 var innerConfig = {};
2384 for (var i in parserConfig)
2385 innerConfig[i] = parserConfig[i];
2386 innerConfig.name = oldModeName;
2387 var codeMirrorMode = CodeMirror.getMode(config, innerConfig);
2388 codeMirrorMode.name = modeName;
2389 codeMirrorMode.token = tokenOverride.bind(null, codeMirrorMode.token);
2390 return codeMirrorMode;
2393 function tokenOverride(superToken, stream, state)
2395 var token = superToken(stream, state);
2396 return token ? tokenPrefix + token.split(/ +/).join(" " + tokenPrefix) : token;
2400 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("css", "css-");
2401 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("javascript", "js-");
2402 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("xml", "xml-");
2405 var backgroundColor = InspectorFrontendHost.getSelectionBackgroundColor();
2406 var backgroundColorRule = backgroundColor ? ".CodeMirror .CodeMirror-selected { background-color: " + backgroundColor + ";}" : "";
2407 var foregroundColor = InspectorFrontendHost.getSelectionForegroundColor();
2408 var foregroundColorRule = foregroundColor ? ".CodeMirror .CodeMirror-selectedtext:not(.CodeMirror-persist-highlight) { color: " + foregroundColor + "!important;}" : "";
2409 if (!foregroundColorRule && !backgroundColorRule)
2412 var style = document.createElement("style");
2413 style.textContent = backgroundColorRule + foregroundColorRule;
2414 document.head.appendChild(style);