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._codeMirror.on("keyHandled", this._onKeyHandled.bind(this));
151 this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false);
153 * @this {WebInspector.CodeMirrorTextEditor}
155 function updateAnticipateJumpFlag(value)
157 this._isHandlingMouseDownEvent = value;
159 this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, true), true);
160 this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, false), false);
162 this.element.style.overflow = "hidden";
163 this.element.firstChild.classList.add("source-code");
164 this.element.firstChild.classList.add("fill");
165 this._elementToWidget = new Map();
166 this._nestedUpdatesCounter = 0;
168 this.element.addEventListener("focus", this._handleElementFocus.bind(this), false);
169 this.element.addEventListener("keydown", this._handleKeyDown.bind(this), true);
170 this.element.addEventListener("keydown", this._handlePostKeyDown.bind(this), false);
171 this.element.tabIndex = 0;
173 this._setupWhitespaceHighlight();
176 /** @typedef {{canceled: boolean, from: !CodeMirror.Pos, to: !CodeMirror.Pos, text: string, origin: string, cancel: function()}} */
177 WebInspector.CodeMirrorTextEditor.BeforeChangeObject;
179 /** @typedef {{from: !CodeMirror.Pos, to: !CodeMirror.Pos, origin: string, text: !Array.<string>, removed: !Array.<string>}} */
180 WebInspector.CodeMirrorTextEditor.ChangeObject;
182 WebInspector.CodeMirrorTextEditor.maxHighlightLength = 1000;
185 * @param {!CodeMirror} codeMirror
187 WebInspector.CodeMirrorTextEditor.autocompleteCommand = function(codeMirror)
189 codeMirror._codeMirrorTextEditor._autocompleteController.autocomplete();
191 CodeMirror.commands.autocomplete = WebInspector.CodeMirrorTextEditor.autocompleteCommand;
194 * @param {!CodeMirror} codeMirror
196 WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand = function(codeMirror)
198 codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.undoLastSelection();
200 CodeMirror.commands.undoLastSelection = WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand;
203 * @param {!CodeMirror} codeMirror
205 WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand = function(codeMirror)
207 codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.selectNextOccurrence();
209 CodeMirror.commands.selectNextOccurrence = WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand;
212 * @param {boolean} shift
213 * @param {!CodeMirror} codeMirror
215 WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand = function(shift, codeMirror)
217 codeMirror._codeMirrorTextEditor._doCamelCaseMovement(-1, shift);
219 CodeMirror.commands.moveCamelLeft = WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand.bind(null, false);
220 CodeMirror.commands.selectCamelLeft = WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand.bind(null, true);
223 * @param {boolean} shift
224 * @param {!CodeMirror} codeMirror
226 WebInspector.CodeMirrorTextEditor.moveCamelRightCommand = function(shift, codeMirror)
228 codeMirror._codeMirrorTextEditor._doCamelCaseMovement(1, shift);
230 CodeMirror.commands.moveCamelRight = WebInspector.CodeMirrorTextEditor.moveCamelRightCommand.bind(null, false);
231 CodeMirror.commands.selectCamelRight = WebInspector.CodeMirrorTextEditor.moveCamelRightCommand.bind(null, true);
234 * @param {!CodeMirror} codeMirror
236 CodeMirror.commands.smartNewlineAndIndent = function(codeMirror)
238 codeMirror.operation(innerSmartNewlineAndIndent.bind(null, codeMirror));
240 function innerSmartNewlineAndIndent(codeMirror)
242 var selections = codeMirror.listSelections();
243 var replacements = [];
244 for (var i = 0; i < selections.length; ++i) {
245 var selection = selections[i];
246 var cur = CodeMirror.cmpPos(selection.head, selection.anchor) < 0 ? selection.head : selection.anchor;
247 var line = codeMirror.getLine(cur.line);
248 var indent = WebInspector.TextUtils.lineIndent(line);
249 replacements.push("\n" + indent.substring(0, Math.min(cur.ch, indent.length)));
251 codeMirror.replaceSelections(replacements);
252 codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
257 * @param {!CodeMirror} codeMirror
259 CodeMirror.commands.gotoMatchingBracket = function(codeMirror)
261 var updatedSelections = [];
262 var selections = codeMirror.listSelections();
263 for (var i = 0; i < selections.length; ++i) {
264 var selection = selections[i];
265 var cursor = selection.head;
266 var matchingBracket = codeMirror.findMatchingBracket(cursor, false, { maxScanLines: 10000 });
267 var updatedHead = cursor;
268 if (matchingBracket && matchingBracket.match) {
269 var columnCorrection = CodeMirror.cmpPos(matchingBracket.from, cursor) === 0 ? 1 : 0;
270 updatedHead = new CodeMirror.Pos(matchingBracket.to.line, matchingBracket.to.ch + columnCorrection);
272 updatedSelections.push({
277 codeMirror.setSelections(updatedSelections);
281 * @param {!CodeMirror} codemirror
283 CodeMirror.commands.undoAndReveal = function(codemirror)
285 var scrollInfo = codemirror.getScrollInfo();
286 codemirror.execCommand("undo");
287 var cursor = codemirror.getCursor("start");
288 codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo);
289 codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete();
293 * @param {!CodeMirror} codemirror
295 CodeMirror.commands.redoAndReveal = function(codemirror)
297 var scrollInfo = codemirror.getScrollInfo();
298 codemirror.execCommand("redo");
299 var cursor = codemirror.getCursor("start");
300 codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo);
301 codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete();
305 * @return {!Object|undefined}
307 CodeMirror.commands.dismissMultipleSelections = function(codemirror)
309 var selections = codemirror.listSelections();
310 var selection = selections[0];
311 if (selections.length === 1) {
312 if (codemirror._codeMirrorTextEditor._isSearchActive())
313 return CodeMirror.Pass;
314 if (WebInspector.CodeMirrorUtils.toRange(selection.anchor, selection.head).isEmpty())
315 return CodeMirror.Pass;
316 codemirror.setSelection(selection.anchor, selection.anchor, {scroll: false});
317 codemirror._codeMirrorTextEditor._revealLine(selection.anchor.line);
321 codemirror.setSelection(selection.anchor, selection.head, {scroll: false});
322 codemirror._codeMirrorTextEditor._revealLine(selection.anchor.line);
325 WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold = 2000;
326 WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan = 16;
327 WebInspector.CodeMirrorTextEditor.MaxEditableTextSize = 1024 * 1024 * 10;
329 WebInspector.CodeMirrorTextEditor.prototype = {
330 _onKeyHandled: function()
332 WebInspector.shortcutRegistry.dismissPendingShortcutAction();
335 _onAutoAppendedSpaces: function()
337 this._autoAppendedSpaces = this._autoAppendedSpaces || [];
338 for (var i = 0; i < this._autoAppendedSpaces.length; ++i) {
339 var position = this._autoAppendedSpaces[i].resolve();
342 var line = this.line(position.lineNumber);
343 if (line.length === position.columnNumber && WebInspector.TextUtils.lineIndent(line).length === line.length)
344 this._codeMirror.replaceRange("", new CodeMirror.Pos(position.lineNumber, 0), new CodeMirror.Pos(position.lineNumber, position.columnNumber));
346 this._autoAppendedSpaces = [];
347 var selections = this.selections();
348 for (var i = 0; i < selections.length; ++i) {
349 var selection = selections[i];
350 this._autoAppendedSpaces.push(this.textEditorPositionHandle(selection.startLine, selection.startColumn));
355 * @param {number} lineNumber
356 * @param {number} lineLength
357 * @param {number} charNumber
358 * @return {{lineNumber: number, columnNumber: number}}
360 _normalizePositionForOverlappingColumn: function(lineNumber, lineLength, charNumber)
362 var linesCount = this._codeMirror.lineCount();
363 var columnNumber = charNumber;
364 if (charNumber < 0 && lineNumber > 0) {
366 columnNumber = this.line(lineNumber).length;
367 } else if (charNumber >= lineLength && lineNumber < linesCount - 1) {
371 columnNumber = Number.constrain(charNumber, 0, lineLength);
374 lineNumber: lineNumber,
375 columnNumber: columnNumber
380 * @param {number} lineNumber
381 * @param {number} columnNumber
382 * @param {number} direction
383 * @return {{lineNumber: number, columnNumber: number}}
385 _camelCaseMoveFromPosition: function(lineNumber, columnNumber, direction)
388 * @param {number} charNumber
389 * @param {number} length
392 function valid(charNumber, length)
394 return charNumber >= 0 && charNumber < length;
398 * @param {string} text
399 * @param {number} charNumber
402 function isWordStart(text, charNumber)
404 var position = charNumber;
405 var nextPosition = charNumber + 1;
406 return valid(position, text.length) && valid(nextPosition, text.length)
407 && WebInspector.TextUtils.isWordChar(text[position]) && WebInspector.TextUtils.isWordChar(text[nextPosition])
408 && WebInspector.TextUtils.isUpperCase(text[position]) && WebInspector.TextUtils.isLowerCase(text[nextPosition]);
412 * @param {string} text
413 * @param {number} charNumber
416 function isWordEnd(text, charNumber)
418 var position = charNumber;
419 var prevPosition = charNumber - 1;
420 return valid(position, text.length) && valid(prevPosition, text.length)
421 && WebInspector.TextUtils.isWordChar(text[position]) && WebInspector.TextUtils.isWordChar(text[prevPosition])
422 && WebInspector.TextUtils.isUpperCase(text[position]) && WebInspector.TextUtils.isLowerCase(text[prevPosition]);
426 * @param {number} lineNumber
427 * @param {number} lineLength
428 * @param {number} columnNumber
429 * @return {{lineNumber: number, columnNumber: number}}
431 function constrainPosition(lineNumber, lineLength, columnNumber)
434 lineNumber: lineNumber,
435 columnNumber: Number.constrain(columnNumber, 0, lineLength)
439 var text = this.line(lineNumber);
440 var length = text.length;
442 if ((columnNumber === length && direction === 1)
443 || (columnNumber === 0 && direction === -1))
444 return this._normalizePositionForOverlappingColumn(lineNumber, length, columnNumber + direction);
446 var charNumber = direction === 1 ? columnNumber : columnNumber - 1;
448 // Move through initial spaces if any.
449 while (valid(charNumber, length) && WebInspector.TextUtils.isSpaceChar(text[charNumber]))
450 charNumber += direction;
451 if (!valid(charNumber, length))
452 return constrainPosition(lineNumber, length, charNumber);
454 if (WebInspector.TextUtils.isStopChar(text[charNumber])) {
455 while (valid(charNumber, length) && WebInspector.TextUtils.isStopChar(text[charNumber]))
456 charNumber += direction;
457 if (!valid(charNumber, length))
458 return constrainPosition(lineNumber, length, charNumber);
460 lineNumber: lineNumber,
461 columnNumber: direction === -1 ? charNumber + 1 : charNumber
465 charNumber += direction;
466 while (valid(charNumber, length) && !isWordStart(text, charNumber) && !isWordEnd(text, charNumber) && WebInspector.TextUtils.isWordChar(text[charNumber]))
467 charNumber += direction;
469 if (!valid(charNumber, length))
470 return constrainPosition(lineNumber, length, charNumber);
471 if (isWordStart(text, charNumber) || isWordEnd(text, charNumber)) {
473 lineNumber: lineNumber,
474 columnNumber: charNumber
479 lineNumber: lineNumber,
480 columnNumber: direction === -1 ? charNumber + 1 : charNumber
485 * @param {number} direction
486 * @param {boolean} shift
488 _doCamelCaseMovement: function(direction, shift)
490 var selections = this.selections();
491 for (var i = 0; i < selections.length; ++i) {
492 var selection = selections[i];
493 var move = this._camelCaseMoveFromPosition(selection.endLine, selection.endColumn, direction);
494 selection.endLine = move.lineNumber;
495 selection.endColumn = move.columnNumber;
497 selections[i] = selection.collapseToEnd();
499 this.setSelections(selections);
504 WebInspector.settings.textEditorIndent.removeChangeListener(this._updateEditorIndentation, this);
505 WebInspector.settings.textEditorAutoDetectIndent.removeChangeListener(this._updateEditorIndentation, this);
506 WebInspector.settings.showWhitespacesInEditor.removeChangeListener(this._updateCodeMirrorMode, this);
507 WebInspector.settings.textEditorBracketMatching.removeChangeListener(this._enableBracketMatchingIfNeeded, this);
510 _enableBracketMatchingIfNeeded: function()
512 this._codeMirror.setOption("autoCloseBrackets", WebInspector.settings.textEditorBracketMatching.get() ? { explode: false } : false);
517 if (this._wasOnceShown)
519 this._wasOnceShown = true;
520 this._codeMirror.refresh();
523 _guessIndentationLevel: function()
525 var tabRegex = /^\t+/;
528 function processLine(lineHandle)
530 var text = lineHandle.text;
531 if (text.length === 0 || !WebInspector.TextUtils.isSpaceChar(text[0]))
533 if (tabRegex.test(text)) {
538 while (i < text.length && WebInspector.TextUtils.isSpaceChar(text[i]))
542 indents[i] = 1 + (indents[i] || 0);
544 this._codeMirror.eachLine(0, 1000, processLine);
546 var onePercentFilterThreshold = this.linesCount / 100;
547 if (tabLines && tabLines > onePercentFilterThreshold)
549 var minimumIndent = Infinity;
550 for (var i in indents) {
551 if (indents[i] < onePercentFilterThreshold)
553 var indent = parseInt(i, 10);
554 if (minimumIndent > indent)
555 minimumIndent = indent;
557 if (minimumIndent === Infinity)
558 return WebInspector.settings.textEditorIndent.get();
559 return new Array(minimumIndent + 1).join(" ");
562 _updateEditorIndentation: function()
565 var indent = WebInspector.settings.textEditorIndent.get();
566 if (WebInspector.settings.textEditorAutoDetectIndent.get())
567 indent = this._guessIndentationLevel();
568 if (indent === WebInspector.TextUtils.Indent.TabCharacter) {
569 this._codeMirror.setOption("indentWithTabs", true);
570 this._codeMirror.setOption("indentUnit", 4);
572 this._codeMirror.setOption("indentWithTabs", false);
573 this._codeMirror.setOption("indentUnit", indent.length);
574 extraKeys.Tab = function(codeMirror)
576 if (codeMirror.somethingSelected())
577 return CodeMirror.Pass;
578 var pos = codeMirror.getCursor("head");
579 codeMirror.replaceRange(indent.substring(pos.ch % indent.length), codeMirror.getCursor());
582 this._codeMirror.setOption("extraKeys", extraKeys);
583 this._indentationLevel = indent;
591 return this._indentationLevel;
597 _isSearchActive: function()
599 return !!this._tokenHighlighter.highlightedRegex();
603 * @param {!RegExp} regex
604 * @param {?WebInspector.TextRange} range
606 highlightSearchResults: function(regex, range)
609 * @this {WebInspector.CodeMirrorTextEditor}
611 function innerHighlightRegex()
614 this._revealLine(range.startLine);
615 if (range.endColumn > WebInspector.CodeMirrorTextEditor.maxHighlightLength)
616 this.setSelection(range);
618 this.setSelection(WebInspector.TextRange.createFromLocation(range.startLine, range.startColumn));
620 // Collapse selection to end on search start so that we jump to next occurrence on the first enter press.
621 this.setSelection(this.selection().collapseToEnd());
623 this._tokenHighlighter.highlightSearchResults(regex, range);
625 if (!this._selectionBeforeSearch)
626 this._selectionBeforeSearch = this.selection();
627 this._codeMirror.operation(innerHighlightRegex.bind(this));
630 cancelSearchResultsHighlight: function()
632 this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));
633 if (this._selectionBeforeSearch) {
634 this._reportJump(this._selectionBeforeSearch, this.selection());
635 delete this._selectionBeforeSearch;
641 this._codeMirror.undo();
646 this._codeMirror.redo();
649 _setupWhitespaceHighlight: function()
651 if (WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected || !WebInspector.settings.showWhitespacesInEditor.get())
653 WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected = true;
654 const classBase = ".show-whitespaces .CodeMirror .cm-whitespace-";
655 const spaceChar = "·";
658 for (var i = 1; i <= WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan; ++i) {
659 spaceChars += spaceChar;
660 var rule = classBase + i + "::before { content: '" + spaceChars + "';}\n";
663 var style = document.createElement("style");
664 style.textContent = rules;
665 document.head.appendChild(style);
668 _handleKeyDown: function(e)
670 if (this._autocompleteController.keyDown(e))
674 _handlePostKeyDown: function(e)
676 if (e.defaultPrevented)
681 * @param {?WebInspector.CompletionDictionary} dictionary
683 setCompletionDictionary: function(dictionary)
685 this._autocompleteController.dispose();
687 this._autocompleteController = new WebInspector.CodeMirrorTextEditor.AutocompleteController(this, this._codeMirror, dictionary);
689 this._autocompleteController = WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy;
693 * @param {number} lineNumber
694 * @param {number} column
695 * @return {?{x: number, y: number, height: number}}
697 cursorPositionToCoordinates: function(lineNumber, column)
699 if (lineNumber >= this._codeMirror.lineCount() || lineNumber < 0 || column < 0 || column > this._codeMirror.getLine(lineNumber).length)
702 var metrics = this._codeMirror.cursorCoords(new CodeMirror.Pos(lineNumber, column));
707 height: metrics.bottom - metrics.top
714 * @return {?WebInspector.TextRange}
716 coordinatesToCursorPosition: function(x, y)
718 var element = document.elementFromPoint(x, y);
719 if (!element || !element.isSelfOrDescendant(this._codeMirror.getWrapperElement()))
721 var gutterBox = this._codeMirror.getGutterElement().boxInWindow();
722 if (x >= gutterBox.x && x <= gutterBox.x + gutterBox.width &&
723 y >= gutterBox.y && y <= gutterBox.y + gutterBox.height)
725 var coords = this._codeMirror.coordsChar({left: x, top: y});
726 return WebInspector.CodeMirrorUtils.toRange(coords, coords);
730 * @param {number} lineNumber
731 * @param {number} column
732 * @return {?{startColumn: number, endColumn: number, type: string}}
734 tokenAtTextPosition: function(lineNumber, column)
736 if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
738 var token = this._codeMirror.getTokenAt(new CodeMirror.Pos(lineNumber, (column || 0) + 1));
739 if (!token || !token.type)
742 startColumn: token.start,
743 endColumn: token.end,
749 * @param {!WebInspector.TextRange} textRange
752 copyRange: function(textRange)
754 var pos = WebInspector.CodeMirrorUtils.toPos(textRange.normalize());
755 return this._codeMirror.getRange(pos.start, pos.end);
763 return this._codeMirror.isClean();
766 markClean: function()
768 this._codeMirror.markClean();
771 _hasLongLines: function()
773 function lineIterator(lineHandle)
775 if (lineHandle.text.length > WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold)
779 var hasLongLines = false;
780 this._codeMirror.eachLine(lineIterator);
785 * @param {string} mimeType
788 _whitespaceOverlayMode: function(mimeType)
790 var modeName = CodeMirror.mimeModes[mimeType] ? (CodeMirror.mimeModes[mimeType].name || CodeMirror.mimeModes[mimeType]) : CodeMirror.mimeModes["text/plain"];
791 modeName += "+whitespaces";
792 if (CodeMirror.modes[modeName])
795 function modeConstructor(config, parserConfig)
797 function nextToken(stream)
799 if (stream.peek() === " ") {
801 while (spaces < WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan && stream.peek() === " ") {
805 return "whitespace whitespace-" + spaces;
807 while (!stream.eol() && stream.peek() !== " ")
811 var whitespaceMode = {
814 return CodeMirror.overlayMode(CodeMirror.getMode(config, mimeType), whitespaceMode, false);
816 CodeMirror.defineMode(modeName, modeConstructor);
820 _enableLongLinesMode: function()
822 this._codeMirror.setOption("styleSelectedText", false);
823 this._longLinesMode = true;
826 _disableLongLinesMode: function()
828 this._codeMirror.setOption("styleSelectedText", true);
829 this._longLinesMode = false;
832 _updateCodeMirrorMode: function()
834 this._setupWhitespaceHighlight();
835 var showWhitespaces = WebInspector.settings.showWhitespacesInEditor.get();
836 this.element.classList.toggle("show-whitespaces", showWhitespaces);
837 this._codeMirror.setOption("mode", showWhitespaces ? this._whitespaceOverlayMode(this._mimeType) : this._mimeType);
841 * @param {string} mimeType
843 setMimeType: function(mimeType)
845 this._mimeType = mimeType;
846 if (this._hasLongLines())
847 this._enableLongLinesMode();
849 this._disableLongLinesMode();
850 this._updateCodeMirrorMode();
851 this._autocompleteController.setMimeType(mimeType);
855 * @param {boolean} readOnly
857 setReadOnly: function(readOnly)
859 this.element.classList.toggle("CodeMirror-readonly", readOnly)
860 this._codeMirror.setOption("readOnly", readOnly);
868 return !!this._codeMirror.getOption("readOnly");
872 * @param {!Object} highlightDescriptor
874 removeHighlight: function(highlightDescriptor)
876 highlightDescriptor.clear();
880 * @param {!WebInspector.TextRange} range
881 * @param {string} cssClass
884 highlightRange: function(range, cssClass)
886 cssClass = "CodeMirror-persist-highlight " + cssClass;
887 var pos = WebInspector.CodeMirrorUtils.toPos(range);
889 return this._codeMirror.markText(pos.start, pos.end, {
891 startStyle: cssClass + "-start",
892 endStyle: cssClass + "-end"
899 defaultFocusedElement: function()
906 this._codeMirror.focus();
909 _handleElementFocus: function()
911 this._codeMirror.focus();
914 beginUpdates: function()
916 ++this._nestedUpdatesCounter;
919 endUpdates: function()
921 if (!--this._nestedUpdatesCounter)
922 this._codeMirror.refresh();
926 * @param {number} lineNumber
928 _revealLine: function(lineNumber)
930 this._innerRevealLine(lineNumber, this._codeMirror.getScrollInfo());
934 * @param {number} lineNumber
935 * @param {!{left: number, top: number, width: number, height: number, clientWidth: number, clientHeight: number}} scrollInfo
937 _innerRevealLine: function(lineNumber, scrollInfo)
939 var topLine = this._codeMirror.lineAtHeight(scrollInfo.top, "local");
940 var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
941 var linesPerScreen = bottomLine - topLine + 1;
942 if (lineNumber < topLine) {
943 var topLineToReveal = Math.max(lineNumber - (linesPerScreen / 2) + 1, 0) | 0;
944 this._codeMirror.scrollIntoView(new CodeMirror.Pos(topLineToReveal, 0));
945 } else if (lineNumber > bottomLine) {
946 var bottomLineToReveal = Math.min(lineNumber + (linesPerScreen / 2) - 1, this.linesCount - 1) | 0;
947 this._codeMirror.scrollIntoView(new CodeMirror.Pos(bottomLineToReveal, 0));
951 _gutterClick: function(instance, lineNumber, gutter, event)
953 this.dispatchEventToListeners(WebInspector.TextEditor.Events.GutterClick, { lineNumber: lineNumber, event: event });
956 _contextMenu: function(event)
958 var contextMenu = new WebInspector.ContextMenu(event);
959 var target = event.target.enclosingNodeOrSelfWithClass("CodeMirror-gutter-elt");
961 this._delegate.populateLineGutterContextMenu(contextMenu, parseInt(target.textContent, 10) - 1);
963 this._delegate.populateTextAreaContextMenu(contextMenu, 0);
964 contextMenu.appendApplicableItems(this);
969 * @param {number} lineNumber
970 * @param {boolean} disabled
971 * @param {boolean} conditional
973 addBreakpoint: function(lineNumber, disabled, conditional)
975 if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
977 var className = "cm-breakpoint" + (conditional ? " cm-breakpoint-conditional" : "") + (disabled ? " cm-breakpoint-disabled" : "");
978 this._codeMirror.addLineClass(lineNumber, "wrap", className);
982 * @param {number} lineNumber
984 removeBreakpoint: function(lineNumber)
986 if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
988 var wrapClasses = this._codeMirror.getLineHandle(lineNumber).wrapClass;
991 var classes = wrapClasses.split(" ");
992 for (var i = 0; i < classes.length; ++i) {
993 if (classes[i].startsWith("cm-breakpoint"))
994 this._codeMirror.removeLineClass(lineNumber, "wrap", classes[i]);
999 * @param {number} lineNumber
1001 setExecutionLine: function(lineNumber)
1003 this.clearPositionHighlight();
1004 this._executionLine = this._codeMirror.getLineHandle(lineNumber);
1005 if (!this._executionLine)
1007 this._codeMirror.addLineClass(this._executionLine, "wrap", "cm-execution-line");
1010 clearExecutionLine: function()
1012 this.clearPositionHighlight();
1013 if (this._executionLine)
1014 this._codeMirror.removeLineClass(this._executionLine, "wrap", "cm-execution-line");
1015 delete this._executionLine;
1019 * @param {number} lineNumber
1020 * @param {string} className
1021 * @param {boolean} toggled
1023 toggleLineClass: function(lineNumber, className, toggled)
1025 var lineHandle = this._codeMirror.getLineHandle(lineNumber);
1029 this._codeMirror.addLineClass(lineHandle, "wrap", className);
1031 this._codeMirror.removeLineClass(lineHandle, "wrap", className);
1035 * @param {number} lineNumber
1036 * @param {!Element} element
1038 addDecoration: function(lineNumber, element)
1040 var widget = this._codeMirror.addLineWidget(lineNumber, element);
1041 this._elementToWidget.put(element, widget);
1045 * @param {number} lineNumber
1046 * @param {!Element} element
1048 removeDecoration: function(lineNumber, element)
1050 var widget = this._elementToWidget.remove(element);
1052 this._codeMirror.removeLineWidget(widget);
1056 * @param {number} lineNumber
1057 * @param {number=} columnNumber
1058 * @param {boolean=} shouldHighlight
1060 revealPosition: function(lineNumber, columnNumber, shouldHighlight)
1062 lineNumber = Number.constrain(lineNumber, 0, this._codeMirror.lineCount() - 1);
1063 if (typeof columnNumber !== "number")
1065 columnNumber = Number.constrain(columnNumber, 0, this._codeMirror.getLine(lineNumber).length);
1067 this.clearPositionHighlight();
1068 this._highlightedLine = this._codeMirror.getLineHandle(lineNumber);
1069 if (!this._highlightedLine)
1071 this._revealLine(lineNumber);
1072 if (shouldHighlight) {
1073 this._codeMirror.addLineClass(this._highlightedLine, null, "cm-highlight");
1074 this._clearHighlightTimeout = setTimeout(this.clearPositionHighlight.bind(this), 2000);
1076 this.setSelection(WebInspector.TextRange.createFromLocation(lineNumber, columnNumber));
1079 clearPositionHighlight: function()
1081 if (this._clearHighlightTimeout)
1082 clearTimeout(this._clearHighlightTimeout);
1083 delete this._clearHighlightTimeout;
1085 if (this._highlightedLine)
1086 this._codeMirror.removeLineClass(this._highlightedLine, null, "cm-highlight");
1087 delete this._highlightedLine;
1091 * @return {!Array.<!Element>}
1093 elementsToRestoreScrollPositionsFor: function()
1099 * @param {!WebInspector.TextEditor} textEditor
1101 inheritScrollPositions: function(textEditor)
1106 * @param {number} width
1107 * @param {number} height
1109 _updatePaddingBottom: function(width, height)
1111 var scrollInfo = this._codeMirror.getScrollInfo();
1112 var newPaddingBottom;
1113 var linesElement = this.element.firstElementChild.querySelector(".CodeMirror-lines");
1114 var lineCount = this._codeMirror.lineCount();
1116 newPaddingBottom = 0;
1118 newPaddingBottom = Math.max(scrollInfo.clientHeight - this._codeMirror.getLineHandle(this._codeMirror.lastLine()).height, 0);
1119 newPaddingBottom += "px";
1120 linesElement.style.paddingBottom = newPaddingBottom;
1121 this._codeMirror.setSize(width, height);
1124 _resizeEditor: function()
1126 var parentElement = this.element.parentElement;
1127 if (!parentElement || !this.isShowing())
1129 var scrollLeft = this._codeMirror.doc.scrollLeft;
1130 var scrollTop = this._codeMirror.doc.scrollTop;
1131 var width = parentElement.offsetWidth;
1132 var height = parentElement.offsetHeight - this.element.offsetTop;
1133 this._codeMirror.setSize(width, height);
1134 this._updatePaddingBottom(width, height);
1135 this._codeMirror.scrollTo(scrollLeft, scrollTop);
1138 onResize: function()
1140 this._autocompleteController.finishAutocomplete();
1141 this._resizeEditor();
1145 * @param {!WebInspector.TextRange} range
1146 * @param {string} text
1147 * @return {!WebInspector.TextRange}
1149 editRange: function(range, text)
1151 var pos = WebInspector.CodeMirrorUtils.toPos(range);
1152 this._codeMirror.replaceRange(text, pos.start, pos.end);
1153 var newRange = WebInspector.CodeMirrorUtils.toRange(pos.start, this._codeMirror.posFromIndex(this._codeMirror.indexFromPos(pos.start) + text.length));
1154 this._delegate.onTextChanged(range, newRange);
1155 if (WebInspector.settings.textEditorAutoDetectIndent.get())
1156 this._updateEditorIndentation();
1161 * @param {number} lineNumber
1162 * @param {number} column
1163 * @param {function(string):boolean} isWordChar
1164 * @return {!WebInspector.TextRange}
1166 _wordRangeForCursorPosition: function(lineNumber, column, isWordChar)
1168 var line = this.line(lineNumber);
1169 var wordStart = column;
1170 if (column !== 0 && isWordChar(line.charAt(column - 1))) {
1171 wordStart = column - 1;
1172 while (wordStart > 0 && isWordChar(line.charAt(wordStart - 1)))
1175 var wordEnd = column;
1176 while (wordEnd < line.length && isWordChar(line.charAt(wordEnd)))
1178 return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, wordEnd);
1182 * @param {!WebInspector.CodeMirrorTextEditor.ChangeObject} changeObject
1183 * @return {{oldRange: !WebInspector.TextRange, newRange: !WebInspector.TextRange}}
1185 _changeObjectToEditOperation: function(changeObject)
1187 var oldRange = WebInspector.CodeMirrorUtils.toRange(changeObject.from, changeObject.to);
1188 var newRange = oldRange.clone();
1189 var linesAdded = changeObject.text.length;
1190 if (linesAdded === 0) {
1191 newRange.endLine = newRange.startLine;
1192 newRange.endColumn = newRange.startColumn;
1193 } else if (linesAdded === 1) {
1194 newRange.endLine = newRange.startLine;
1195 newRange.endColumn = newRange.startColumn + changeObject.text[0].length;
1197 newRange.endLine = newRange.startLine + linesAdded - 1;
1198 newRange.endColumn = changeObject.text[linesAdded - 1].length;
1207 * @param {!CodeMirror} codeMirror
1208 * @param {!Array.<!WebInspector.CodeMirrorTextEditor.ChangeObject>} changes
1210 _changes: function(codeMirror, changes)
1212 if (!changes.length)
1214 // 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.
1215 var hasOneLine = this._codeMirror.lineCount() === 1;
1216 if (hasOneLine !== this._hasOneLine)
1217 this._resizeEditor();
1218 this._hasOneLine = hasOneLine;
1219 var widgets = this._elementToWidget.values();
1220 for (var i = 0; i < widgets.length; ++i)
1221 this._codeMirror.removeLineWidget(widgets[i]);
1222 this._elementToWidget.clear();
1224 for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) {
1225 var changeObject = changes[changeIndex];
1227 var editInfo = this._changeObjectToEditOperation(changeObject);
1228 if (!this._muteTextChangedEvent)
1229 this._delegate.onTextChanged(editInfo.oldRange, editInfo.newRange);
1233 _cursorActivity: function()
1235 var start = this._codeMirror.getCursor("anchor");
1236 var end = this._codeMirror.getCursor("head");
1237 this._delegate.selectionChanged(WebInspector.CodeMirrorUtils.toRange(start, end));
1238 if (!this._isSearchActive())
1239 this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));
1243 * @param {!CodeMirror} codeMirror
1244 * @param {{ranges: !Array.<{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>}} selection
1246 _beforeSelectionChange: function(codeMirror, selection)
1248 this._selectNextOccurrenceController.selectionWillChange();
1249 if (!this._isHandlingMouseDownEvent)
1251 if (!selection.ranges.length)
1253 var primarySelection = selection.ranges[0];
1254 this._reportJump(this.selection(), WebInspector.CodeMirrorUtils.toRange(primarySelection.anchor, primarySelection.head));
1258 * @param {?WebInspector.TextRange} from
1259 * @param {?WebInspector.TextRange} to
1261 _reportJump: function(from, to)
1263 if (from && to && from.equal(to))
1265 this._delegate.onJumpToPosition(from, to);
1270 if (this._scrollTimer)
1271 clearTimeout(this._scrollTimer);
1272 var topmostLineNumber = this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local");
1273 this._scrollTimer = setTimeout(this._delegate.scrollChanged.bind(this._delegate, topmostLineNumber), 100);
1278 this._delegate.editorFocused();
1282 * @param {number} lineNumber
1284 scrollToLine: function(lineNumber)
1286 var pos = new CodeMirror.Pos(lineNumber, 0);
1287 var coords = this._codeMirror.charCoords(pos, "local");
1288 this._codeMirror.scrollTo(0, coords.top);
1294 firstVisibleLine: function()
1296 return this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local");
1302 lastVisibleLine: function()
1304 var scrollInfo = this._codeMirror.getScrollInfo();
1305 return this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
1309 * @return {!WebInspector.TextRange}
1311 selection: function()
1313 var start = this._codeMirror.getCursor("anchor");
1314 var end = this._codeMirror.getCursor("head");
1316 return WebInspector.CodeMirrorUtils.toRange(start, end);
1320 * @return {!Array.<!WebInspector.TextRange>}
1322 selections: function()
1324 var selectionList = this._codeMirror.listSelections();
1326 for (var i = 0; i < selectionList.length; ++i) {
1327 var selection = selectionList[i];
1328 result.push(WebInspector.CodeMirrorUtils.toRange(selection.anchor, selection.head));
1334 * @return {?WebInspector.TextRange}
1336 lastSelection: function()
1338 return this._lastSelection;
1342 * @param {!WebInspector.TextRange} textRange
1344 setSelection: function(textRange)
1346 this._lastSelection = textRange;
1347 var pos = WebInspector.CodeMirrorUtils.toPos(textRange);
1348 this._codeMirror.setSelection(pos.start, pos.end);
1352 * @param {!Array.<!WebInspector.TextRange>} ranges
1353 * @param {number=} primarySelectionIndex
1355 setSelections: function(ranges, primarySelectionIndex)
1357 var selections = [];
1358 for (var i = 0; i < ranges.length; ++i) {
1359 var selection = WebInspector.CodeMirrorUtils.toPos(ranges[i]);
1361 anchor: selection.start,
1365 primarySelectionIndex = primarySelectionIndex || 0;
1366 this._codeMirror.setSelections(selections, primarySelectionIndex, { scroll: false });
1370 * @param {string} text
1372 _detectLineSeparator: function(text)
1374 this._lineSeparator = text.indexOf("\r\n") >= 0 ? "\r\n" : "\n";
1378 * @param {string} text
1380 setText: function(text)
1382 this._muteTextChangedEvent = true;
1383 if (text.length > WebInspector.CodeMirrorTextEditor.MaxEditableTextSize) {
1384 this._autocompleteController.setEnabled(false);
1385 this.setReadOnly(true);
1387 this._codeMirror.setValue(text);
1388 this._updateEditorIndentation();
1389 if (this._shouldClearHistory) {
1390 this._codeMirror.clearHistory();
1391 this._shouldClearHistory = false;
1393 this._detectLineSeparator(text);
1394 delete this._muteTextChangedEvent;
1402 return this._codeMirror.getValue().replace(/\n/g, this._lineSeparator);
1406 * @return {!WebInspector.TextRange}
1410 var lineCount = this.linesCount;
1411 var lastLine = this._codeMirror.getLine(lineCount - 1);
1412 return WebInspector.CodeMirrorUtils.toRange(new CodeMirror.Pos(0, 0), new CodeMirror.Pos(lineCount - 1, lastLine.length));
1416 * @param {number} lineNumber
1419 line: function(lineNumber)
1421 return this._codeMirror.getLine(lineNumber);
1429 return this._codeMirror.lineCount();
1433 * @param {number} line
1434 * @param {string} name
1435 * @param {?Object} value
1437 setAttribute: function(line, name, value)
1439 if (line < 0 || line >= this._codeMirror.lineCount())
1441 var handle = this._codeMirror.getLineHandle(line);
1442 if (handle.attributes === undefined) handle.attributes = {};
1443 handle.attributes[name] = value;
1447 * @param {number} line
1448 * @param {string} name
1449 * @return {?Object} value
1451 getAttribute: function(line, name)
1453 if (line < 0 || line >= this._codeMirror.lineCount())
1455 var handle = this._codeMirror.getLineHandle(line);
1456 return handle.attributes && handle.attributes[name] !== undefined ? handle.attributes[name] : null;
1460 * @param {number} line
1461 * @param {string} name
1463 removeAttribute: function(line, name)
1465 if (line < 0 || line >= this._codeMirror.lineCount())
1467 var handle = this._codeMirror.getLineHandle(line);
1468 if (handle && handle.attributes)
1469 delete handle.attributes[name];
1473 * @param {number} lineNumber
1474 * @param {number} columnNumber
1475 * @return {!WebInspector.TextEditorPositionHandle}
1477 textEditorPositionHandle: function(lineNumber, columnNumber)
1479 return new WebInspector.CodeMirrorPositionHandle(this._codeMirror, new CodeMirror.Pos(lineNumber, columnNumber));
1482 __proto__: WebInspector.VBox.prototype
1487 * @implements {WebInspector.TextEditorPositionHandle}
1488 * @param {!CodeMirror} codeMirror
1489 * @param {!CodeMirror.Pos} pos
1491 WebInspector.CodeMirrorPositionHandle = function(codeMirror, pos)
1493 this._codeMirror = codeMirror;
1494 this._lineHandle = codeMirror.getLineHandle(pos.line);
1495 this._columnNumber = pos.ch;
1498 WebInspector.CodeMirrorPositionHandle.prototype = {
1500 * @return {?{lineNumber: number, columnNumber: number}}
1504 var lineNumber = this._codeMirror.getLineNumber(this._lineHandle);
1505 if (typeof lineNumber !== "number")
1508 lineNumber: lineNumber,
1509 columnNumber: this._columnNumber
1514 * @param {!WebInspector.TextEditorPositionHandle} positionHandle
1517 equal: function(positionHandle)
1519 return positionHandle._lineHandle === this._lineHandle && positionHandle._columnNumber == this._columnNumber && positionHandle._codeMirror === this._codeMirror;
1525 * @param {!WebInspector.CodeMirrorTextEditor} textEditor
1526 * @param {!CodeMirror} codeMirror
1528 WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(textEditor, codeMirror)
1530 this._textEditor = textEditor;
1531 this._codeMirror = codeMirror;
1534 WebInspector.CodeMirrorTextEditor.TokenHighlighter.prototype = {
1536 * @param {!RegExp} regex
1537 * @param {?WebInspector.TextRange} range
1539 highlightSearchResults: function(regex, range)
1541 var oldRegex = this._highlightRegex;
1542 this._highlightRegex = regex;
1543 this._highlightRange = range;
1544 if (this._searchResultMarker) {
1545 this._searchResultMarker.clear();
1546 delete this._searchResultMarker;
1548 if (this._highlightDescriptor && this._highlightDescriptor.selectionStart)
1549 this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection");
1550 var selectionStart = this._highlightRange ? new CodeMirror.Pos(this._highlightRange.startLine, this._highlightRange.startColumn) : null;
1552 this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection");
1553 if (this._highlightRegex === oldRegex) {
1554 // Do not re-add overlay mode if regex did not change for better performance.
1555 if (this._highlightDescriptor)
1556 this._highlightDescriptor.selectionStart = selectionStart;
1558 this._removeHighlight();
1559 this._setHighlighter(this._searchHighlighter.bind(this, this._highlightRegex), selectionStart);
1561 if (this._highlightRange) {
1562 var pos = WebInspector.CodeMirrorUtils.toPos(this._highlightRange);
1563 this._searchResultMarker = this._codeMirror.markText(pos.start, pos.end, {className: "cm-column-with-selection"});
1568 * @return {!RegExp|undefined}
1570 highlightedRegex: function()
1572 return this._highlightRegex;
1575 highlightSelectedTokens: function()
1577 delete this._highlightRegex;
1578 delete this._highlightRange;
1580 if (this._highlightDescriptor && this._highlightDescriptor.selectionStart)
1581 this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection");
1582 this._removeHighlight();
1583 var selectionStart = this._codeMirror.getCursor("start");
1584 var selectionEnd = this._codeMirror.getCursor("end");
1585 if (selectionStart.line !== selectionEnd.line)
1587 if (selectionStart.ch === selectionEnd.ch)
1590 var selections = this._codeMirror.getSelections();
1591 if (selections.length > 1)
1593 var selectedText = selections[0];
1594 if (this._isWord(selectedText, selectionStart.line, selectionStart.ch, selectionEnd.ch)) {
1596 this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection")
1597 this._setHighlighter(this._tokenHighlighter.bind(this, selectedText, selectionStart), selectionStart);
1602 * @param {string} selectedText
1603 * @param {number} lineNumber
1604 * @param {number} startColumn
1605 * @param {number} endColumn
1607 _isWord: function(selectedText, lineNumber, startColumn, endColumn)
1609 var line = this._codeMirror.getLine(lineNumber);
1610 var leftBound = startColumn === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(startColumn - 1));
1611 var rightBound = endColumn === line.length || !WebInspector.TextUtils.isWordChar(line.charAt(endColumn));
1612 return leftBound && rightBound && WebInspector.TextUtils.isWord(selectedText);
1615 _removeHighlight: function()
1617 if (this._highlightDescriptor) {
1618 this._codeMirror.removeOverlay(this._highlightDescriptor.overlay);
1619 delete this._highlightDescriptor;
1624 * @param {!RegExp} regex
1625 * @param {!CodeMirror.StringStream} stream
1627 _searchHighlighter: function(regex, stream)
1629 if (stream.column() === 0)
1630 delete this._searchMatchLength;
1631 if (this._searchMatchLength) {
1632 if (this._searchMatchLength > 2) {
1633 for (var i = 0; i < this._searchMatchLength - 2; ++i)
1635 this._searchMatchLength = 1;
1636 return "search-highlight";
1639 delete this._searchMatchLength;
1640 return "search-highlight search-highlight-end";
1643 var match = stream.match(regex, false);
1646 var matchLength = match[0].length;
1647 if (matchLength === 1)
1648 return "search-highlight search-highlight-full";
1649 this._searchMatchLength = matchLength;
1650 return "search-highlight search-highlight-start";
1653 while (!stream.match(regex, false) && stream.next()) {};
1657 * @param {string} token
1658 * @param {!CodeMirror.Pos} selectionStart
1659 * @param {!CodeMirror.StringStream} stream
1661 _tokenHighlighter: function(token, selectionStart, stream)
1663 var tokenFirstChar = token.charAt(0);
1664 if (stream.match(token) && (stream.eol() || !WebInspector.TextUtils.isWordChar(stream.peek())))
1665 return stream.column() === selectionStart.ch ? "token-highlight column-with-selection" : "token-highlight";
1669 eatenChar = stream.next();
1670 } while (eatenChar && (WebInspector.TextUtils.isWordChar(eatenChar) || stream.peek() !== tokenFirstChar));
1674 * @param {function(!CodeMirror.StringStream)} highlighter
1675 * @param {?CodeMirror.Pos} selectionStart
1677 _setHighlighter: function(highlighter, selectionStart)
1682 this._codeMirror.addOverlay(overlayMode);
1683 this._highlightDescriptor = {
1684 overlay: overlayMode,
1685 selectionStart: selectionStart
1692 * @param {!CodeMirror} codeMirror
1694 WebInspector.CodeMirrorTextEditor.BlockIndentController = function(codeMirror)
1696 codeMirror.addKeyMap(this);
1699 WebInspector.CodeMirrorTextEditor.BlockIndentController.prototype = {
1700 name: "blockIndentKeymap",
1705 Enter: function(codeMirror)
1707 var selections = codeMirror.listSelections();
1708 var replacements = [];
1709 var allSelectionsAreCollapsedBlocks = false;
1710 for (var i = 0; i < selections.length; ++i) {
1711 var selection = selections[i];
1712 var start = CodeMirror.cmpPos(selection.head, selection.anchor) < 0 ? selection.head : selection.anchor;
1713 var line = codeMirror.getLine(start.line);
1714 var indent = WebInspector.TextUtils.lineIndent(line);
1715 var indentToInsert = "\n" + indent + codeMirror._codeMirrorTextEditor.indent();
1716 var isCollapsedBlock = false;
1717 if (selection.head.ch === 0)
1718 return CodeMirror.Pass;
1719 if (line.substr(selection.head.ch - 1, 2) === "{}") {
1720 indentToInsert += "\n" + indent;
1721 isCollapsedBlock = true;
1722 } else if (line.substr(selection.head.ch - 1, 1) !== "{") {
1723 return CodeMirror.Pass;
1725 if (i > 0 && allSelectionsAreCollapsedBlocks !== isCollapsedBlock)
1726 return CodeMirror.Pass;
1727 replacements.push(indentToInsert);
1728 allSelectionsAreCollapsedBlocks = isCollapsedBlock;
1730 codeMirror.replaceSelections(replacements);
1731 if (!allSelectionsAreCollapsedBlocks) {
1732 codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
1735 selections = codeMirror.listSelections();
1736 var updatedSelections = [];
1737 for (var i = 0; i < selections.length; ++i) {
1738 var selection = selections[i];
1739 var line = codeMirror.getLine(selection.head.line - 1);
1740 var position = new CodeMirror.Pos(selection.head.line - 1, line.length);
1741 updatedSelections.push({
1746 codeMirror.setSelections(updatedSelections);
1747 codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
1753 "'}'": function(codeMirror)
1755 if (codeMirror.somethingSelected())
1756 return CodeMirror.Pass;
1758 var selections = codeMirror.listSelections();
1759 var replacements = [];
1760 for (var i = 0; i < selections.length; ++i) {
1761 var selection = selections[i];
1762 var line = codeMirror.getLine(selection.head.line);
1763 if (line !== WebInspector.TextUtils.lineIndent(line))
1764 return CodeMirror.Pass;
1765 replacements.push("}");
1768 codeMirror.replaceSelections(replacements);
1769 selections = codeMirror.listSelections();
1771 var updatedSelections = [];
1772 for (var i = 0; i < selections.length; ++i) {
1773 var selection = selections[i];
1774 var matchingBracket = codeMirror.findMatchingBracket(selection.head);
1775 if (!matchingBracket || !matchingBracket.match)
1777 updatedSelections.push({
1778 head: selection.head,
1779 anchor: new CodeMirror.Pos(selection.head.line, 0)
1781 var line = codeMirror.getLine(matchingBracket.to.line);
1782 var indent = WebInspector.TextUtils.lineIndent(line);
1783 replacements.push(indent + "}");
1785 codeMirror.setSelections(updatedSelections);
1786 codeMirror.replaceSelections(replacements);
1792 * @param {!CodeMirror} codeMirror
1794 WebInspector.CodeMirrorTextEditor.FixWordMovement = function(codeMirror)
1796 function moveLeft(shift, codeMirror)
1798 codeMirror.setExtending(shift);
1799 var cursor = codeMirror.getCursor("head");
1800 codeMirror.execCommand("goGroupLeft");
1801 var newCursor = codeMirror.getCursor("head");
1802 if (newCursor.ch === 0 && newCursor.line !== 0) {
1803 codeMirror.setExtending(false);
1807 var skippedText = codeMirror.getRange(newCursor, cursor, "#");
1808 if (/^\s+$/.test(skippedText))
1809 codeMirror.execCommand("goGroupLeft");
1810 codeMirror.setExtending(false);
1813 function moveRight(shift, codeMirror)
1815 codeMirror.setExtending(shift);
1816 var cursor = codeMirror.getCursor("head");
1817 codeMirror.execCommand("goGroupRight");
1818 var newCursor = codeMirror.getCursor("head");
1819 if (newCursor.ch === 0 && newCursor.line !== 0) {
1820 codeMirror.setExtending(false);
1824 var skippedText = codeMirror.getRange(cursor, newCursor, "#");
1825 if (/^\s+$/.test(skippedText))
1826 codeMirror.execCommand("goGroupRight");
1827 codeMirror.setExtending(false);
1830 var modifierKey = WebInspector.isMac() ? "Alt" : "Ctrl";
1831 var leftKey = modifierKey + "-Left";
1832 var rightKey = modifierKey + "-Right";
1834 keyMap[leftKey] = moveLeft.bind(null, false);
1835 keyMap[rightKey] = moveRight.bind(null, false);
1836 keyMap["Shift-" + leftKey] = moveLeft.bind(null, true);
1837 keyMap["Shift-" + rightKey] = moveRight.bind(null, true);
1838 codeMirror.addKeyMap(keyMap);
1844 WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI = function() {}
1846 WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI.prototype = {
1847 dispose: function() { },
1850 * @param {boolean} enabled
1852 setEnabled: function(enabled) { },
1855 * @param {string} mimeType
1857 setMimeType: function(mimeType) { },
1859 autocomplete: function() { },
1861 finishAutocomplete: function() { },
1867 keyDown: function(e) { }
1872 * @implements {WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI}
1874 WebInspector.CodeMirrorTextEditor.DummyAutocompleteController = function() { }
1876 WebInspector.CodeMirrorTextEditor.DummyAutocompleteController.prototype = {
1877 dispose: function() { },
1880 * @param {boolean} enabled
1882 setEnabled: function(enabled) { },
1885 * @param {string} mimeType
1887 setMimeType: function(mimeType) { },
1889 autocomplete: function() { },
1891 finishAutocomplete: function() { },
1897 keyDown: function(e)
1905 * @implements {WebInspector.SuggestBoxDelegate}
1906 * @implements {WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI}
1907 * @param {!WebInspector.CodeMirrorTextEditor} textEditor
1908 * @param {!CodeMirror} codeMirror
1909 * @param {?WebInspector.CompletionDictionary} dictionary
1911 WebInspector.CodeMirrorTextEditor.AutocompleteController = function(textEditor, codeMirror, dictionary)
1913 this._textEditor = textEditor;
1914 this._codeMirror = codeMirror;
1916 this._onScroll = this._onScroll.bind(this);
1917 this._onCursorActivity = this._onCursorActivity.bind(this);
1918 this._changes = this._changes.bind(this);
1919 this._beforeChange = this._beforeChange.bind(this);
1920 this._blur = this._blur.bind(this);
1921 this._codeMirror.on("scroll", this._onScroll);
1922 this._codeMirror.on("cursorActivity", this._onCursorActivity);
1923 this._codeMirror.on("changes", this._changes);
1924 this._codeMirror.on("beforeChange", this._beforeChange);
1925 this._codeMirror.on("blur", this._blur);
1927 this._additionalWordChars = WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars;
1928 this._enabled = true;
1930 this._dictionary = dictionary;
1931 this._addTextToCompletionDictionary(this._textEditor.text());
1934 WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy = new WebInspector.CodeMirrorTextEditor.DummyAutocompleteController();
1935 WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars = {};
1936 WebInspector.CodeMirrorTextEditor._CSSAdditionalWordChars = { ".": true, "-": true };
1938 WebInspector.CodeMirrorTextEditor.AutocompleteController.prototype = {
1941 this._codeMirror.off("scroll", this._onScroll);
1942 this._codeMirror.off("cursorActivity", this._onCursorActivity);
1943 this._codeMirror.off("changes", this._changes);
1944 this._codeMirror.off("beforeChange", this._beforeChange);
1945 this._codeMirror.off("blur", this._blur);
1949 * @param {boolean} enabled
1951 setEnabled: function(enabled)
1953 if (enabled === this._enabled)
1955 this._enabled = enabled;
1957 this._dictionary.reset();
1959 this._addTextToCompletionDictionary(this._textEditor.text());
1963 * @param {string} mimeType
1965 setMimeType: function(mimeType)
1967 var additionalWordChars = mimeType.indexOf("css") !== -1 ? WebInspector.CodeMirrorTextEditor._CSSAdditionalWordChars : WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars;
1968 if (additionalWordChars !== this._additionalWordChars) {
1969 this._additionalWordChars = additionalWordChars;
1970 this._dictionary.reset();
1971 this._addTextToCompletionDictionary(this._textEditor.text());
1976 * @param {string} char
1979 _isWordChar: function(char)
1981 return WebInspector.TextUtils.isWordChar(char) || !!this._additionalWordChars[char];
1985 * @param {string} word
1988 _shouldProcessWordForAutocompletion: function(word)
1990 return !!word.length && (word[0] < '0' || word[0] > '9');
1994 * @param {string} text
1996 _addTextToCompletionDictionary: function(text)
2000 var words = WebInspector.TextUtils.textToWords(text, this._isWordChar.bind(this));
2001 for (var i = 0; i < words.length; ++i) {
2002 if (this._shouldProcessWordForAutocompletion(words[i]))
2003 this._dictionary.addWord(words[i]);
2008 * @param {string} text
2010 _removeTextFromCompletionDictionary: function(text)
2014 var words = WebInspector.TextUtils.textToWords(text, this._isWordChar.bind(this));
2015 for (var i = 0; i < words.length; ++i) {
2016 if (this._shouldProcessWordForAutocompletion(words[i]))
2017 this._dictionary.removeWord(words[i]);
2022 * @param {!CodeMirror} codeMirror
2023 * @param {!WebInspector.CodeMirrorTextEditor.BeforeChangeObject} changeObject
2025 _beforeChange: function(codeMirror, changeObject)
2029 this._updatedLines = this._updatedLines || {};
2030 for (var i = changeObject.from.line; i <= changeObject.to.line; ++i)
2031 this._updatedLines[i] = this._textEditor.line(i);
2035 * @param {!CodeMirror} codeMirror
2036 * @param {!Array.<!WebInspector.CodeMirrorTextEditor.ChangeObject>} changes
2038 _changes: function(codeMirror, changes)
2040 if (!changes.length || !this._enabled)
2043 if (this._updatedLines) {
2044 for (var lineNumber in this._updatedLines)
2045 this._removeTextFromCompletionDictionary(this._updatedLines[lineNumber]);
2046 delete this._updatedLines;
2049 var linesToUpdate = {};
2050 var singleCharInput = false;
2051 for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) {
2052 var changeObject = changes[changeIndex];
2053 singleCharInput = (changeObject.origin === "+input" && changeObject.text.length === 1 && changeObject.text[0].length === 1) ||
2054 (this._suggestBox && changeObject.origin === "+delete" && changeObject.removed.length === 1 && changeObject.removed[0].length === 1);
2056 var editInfo = this._textEditor._changeObjectToEditOperation(changeObject);
2057 for (var i = editInfo.newRange.startLine; i <= editInfo.newRange.endLine; ++i)
2058 linesToUpdate[i] = this._textEditor.line(i);
2060 for (var lineNumber in linesToUpdate)
2061 this._addTextToCompletionDictionary(linesToUpdate[lineNumber]);
2063 if (singleCharInput)
2064 this.autocomplete();
2069 this.finishAutocomplete();
2073 * @param {number} lineNumber
2074 * @param {number} columnNumber
2075 * @return {!WebInspector.TextRange}
2077 _autocompleteWordRange: function(lineNumber, columnNumber)
2079 return this._textEditor._wordRangeForCursorPosition(lineNumber, columnNumber, this._isWordChar.bind(this));
2083 * @param {!WebInspector.TextRange} mainSelection
2084 * @param {!Array.<!{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>} selections
2087 _validateSelectionsContexts: function(mainSelection, selections)
2089 var mainSelectionContext = this._textEditor.copyRange(mainSelection);
2090 for (var i = 0; i < selections.length; ++i) {
2091 var wordRange = this._autocompleteWordRange(selections[i].head.line, selections[i].head.ch);
2094 var context = this._textEditor.copyRange(wordRange);
2095 if (context !== mainSelectionContext)
2101 autocomplete: function()
2103 var dictionary = this._dictionary;
2104 if (this._codeMirror.somethingSelected()) {
2105 this.finishAutocomplete();
2109 var selections = this._codeMirror.listSelections().slice();
2110 var topSelection = selections.shift();
2111 var cursor = topSelection.head;
2112 var substituteRange = this._autocompleteWordRange(cursor.line, cursor.ch);
2113 if (!substituteRange || substituteRange.startColumn === cursor.ch || !this._validateSelectionsContexts(substituteRange, selections)) {
2114 this.finishAutocomplete();
2118 var prefixRange = substituteRange.clone();
2119 prefixRange.endColumn = cursor.ch;
2121 var substituteWord = this._textEditor.copyRange(substituteRange);
2122 var hasPrefixInDictionary = dictionary.hasWord(substituteWord);
2123 if (hasPrefixInDictionary)
2124 dictionary.removeWord(substituteWord);
2125 var wordsWithPrefix = dictionary.wordsWithPrefix(this._textEditor.copyRange(prefixRange));
2126 if (hasPrefixInDictionary)
2127 dictionary.addWord(substituteWord);
2129 function sortSuggestions(a, b)
2131 return dictionary.wordCount(b) - dictionary.wordCount(a) || a.length - b.length;
2134 wordsWithPrefix.sort(sortSuggestions);
2136 if (!this._suggestBox)
2137 this._suggestBox = new WebInspector.SuggestBox(this, 6);
2138 var oldPrefixRange = this._prefixRange;
2139 this._prefixRange = prefixRange;
2140 if (!oldPrefixRange || prefixRange.startLine !== oldPrefixRange.startLine || prefixRange.startColumn !== oldPrefixRange.startColumn)
2141 this._updateAnchorBox();
2142 this._suggestBox.updateSuggestions(this._anchorBox, wordsWithPrefix, 0, true, this._textEditor.copyRange(prefixRange));
2143 if (!this._suggestBox.visible())
2144 this.finishAutocomplete();
2147 finishAutocomplete: function()
2149 if (!this._suggestBox)
2151 this._suggestBox.hide();
2152 this._suggestBox = null;
2153 this._prefixRange = null;
2154 this._anchorBox = null;
2161 keyDown: function(e)
2163 if (!this._suggestBox)
2165 if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
2166 this.finishAutocomplete();
2169 if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Tab.code) {
2170 this._suggestBox.acceptSuggestion();
2171 this.finishAutocomplete();
2174 return this._suggestBox.keyPressed(e);
2178 * @param {string} suggestion
2179 * @param {boolean=} isIntermediateSuggestion
2181 applySuggestion: function(suggestion, isIntermediateSuggestion)
2183 this._currentSuggestion = suggestion;
2186 acceptSuggestion: function()
2188 if (this._prefixRange.endColumn - this._prefixRange.startColumn === this._currentSuggestion.length)
2191 var selections = this._codeMirror.listSelections().slice();
2192 var prefixLength = this._prefixRange.endColumn - this._prefixRange.startColumn;
2193 for (var i = selections.length - 1; i >= 0; --i) {
2194 var start = selections[i].head;
2195 var end = new CodeMirror.Pos(start.line, start.ch - prefixLength);
2196 this._codeMirror.replaceRange(this._currentSuggestion, start, end, "+autocomplete");
2200 _onScroll: function()
2202 if (!this._suggestBox)
2204 var cursor = this._codeMirror.getCursor();
2205 var scrollInfo = this._codeMirror.getScrollInfo();
2206 var topmostLineNumber = this._codeMirror.lineAtHeight(scrollInfo.top, "local");
2207 var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
2208 if (cursor.line < topmostLineNumber || cursor.line > bottomLine)
2209 this.finishAutocomplete();
2211 this._updateAnchorBox();
2212 this._suggestBox.setPosition(this._anchorBox);
2216 _onCursorActivity: function()
2218 if (!this._suggestBox)
2220 var cursor = this._codeMirror.getCursor();
2221 if (cursor.line !== this._prefixRange.startLine || cursor.ch > this._prefixRange.endColumn || cursor.ch <= this._prefixRange.startColumn)
2222 this.finishAutocomplete();
2225 _updateAnchorBox: function()
2227 var line = this._prefixRange.startLine;
2228 var column = this._prefixRange.startColumn;
2229 var metrics = this._textEditor.cursorPositionToCoordinates(line, column);
2230 this._anchorBox = metrics ? new AnchorBox(metrics.x, metrics.y, 0, metrics.height) : null;
2236 * @param {!WebInspector.CodeMirrorTextEditor} textEditor
2237 * @param {!CodeMirror} codeMirror
2239 WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController = function(textEditor, codeMirror)
2241 this._textEditor = textEditor;
2242 this._codeMirror = codeMirror;
2245 WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController.prototype = {
2246 selectionWillChange: function()
2248 if (!this._muteSelectionListener)
2249 delete this._fullWordSelection;
2253 * @param {!Array.<!WebInspector.TextRange>} selections
2254 * @param {!WebInspector.TextRange} range
2257 _findRange: function(selections, range)
2259 for (var i = 0; i < selections.length; ++i) {
2260 if (range.equal(selections[i]))
2266 undoLastSelection: function()
2268 this._muteSelectionListener = true;
2269 this._codeMirror.execCommand("undoSelection");
2270 this._muteSelectionListener = false;
2273 selectNextOccurrence: function()
2275 var selections = this._textEditor.selections();
2276 var anyEmptySelection = false;
2277 for (var i = 0; i < selections.length; ++i) {
2278 var selection = selections[i];
2279 anyEmptySelection = anyEmptySelection || selection.isEmpty();
2280 if (selection.startLine !== selection.endLine)
2283 if (anyEmptySelection) {
2284 this._expandSelectionsToWords(selections);
2288 var last = selections[selections.length - 1];
2291 next = this._findNextOccurrence(next, !!this._fullWordSelection);
2292 } while (next && this._findRange(selections, next) && !next.equal(last));
2296 selections.push(next);
2298 this._muteSelectionListener = true;
2299 this._textEditor.setSelections(selections, selections.length - 1);
2300 delete this._muteSelectionListener;
2302 this._textEditor._revealLine(next.startLine);
2306 * @param {!Array.<!WebInspector.TextRange>} selections
2308 _expandSelectionsToWords: function(selections)
2310 var newSelections = [];
2311 for (var i = 0; i < selections.length; ++i) {
2312 var selection = selections[i];
2313 var startRangeWord = this._textEditor._wordRangeForCursorPosition(selection.startLine, selection.startColumn, WebInspector.TextUtils.isWordChar)
2314 || WebInspector.TextRange.createFromLocation(selection.startLine, selection.startColumn);
2315 var endRangeWord = this._textEditor._wordRangeForCursorPosition(selection.endLine, selection.endColumn, WebInspector.TextUtils.isWordChar)
2316 || WebInspector.TextRange.createFromLocation(selection.endLine, selection.endColumn);
2317 var newSelection = new WebInspector.TextRange(startRangeWord.startLine, startRangeWord.startColumn, endRangeWord.endLine, endRangeWord.endColumn);
2318 newSelections.push(newSelection);
2320 this._textEditor.setSelections(newSelections, newSelections.length - 1);
2321 this._fullWordSelection = true;
2325 * @param {!WebInspector.TextRange} range
2326 * @param {boolean} fullWord
2327 * @return {?WebInspector.TextRange}
2329 _findNextOccurrence: function(range, fullWord)
2331 range = range.normalize();
2332 var matchedLineNumber;
2333 var matchedColumnNumber;
2334 var textToFind = this._textEditor.copyRange(range);
2335 function findWordInLine(wordRegex, lineNumber, lineText, from, to)
2337 if (typeof matchedLineNumber === "number")
2339 wordRegex.lastIndex = from;
2340 var result = wordRegex.exec(lineText);
2341 if (!result || result.index + textToFind.length > to)
2343 matchedLineNumber = lineNumber;
2344 matchedColumnNumber = result.index;
2348 var iteratedLineNumber;
2349 function lineIterator(regex, lineHandle)
2351 if (findWordInLine(regex, iteratedLineNumber++, lineHandle.text, 0, lineHandle.text.length))
2355 var regexSource = textToFind.escapeForRegExp();
2357 regexSource = "\\b" + regexSource + "\\b";
2358 var wordRegex = new RegExp(regexSource, "gi");
2359 var currentLineText = this._codeMirror.getLine(range.startLine);
2361 findWordInLine(wordRegex, range.startLine, currentLineText, range.endColumn, currentLineText.length);
2362 iteratedLineNumber = range.startLine + 1;
2363 this._codeMirror.eachLine(range.startLine + 1, this._codeMirror.lineCount(), lineIterator.bind(null, wordRegex));
2364 iteratedLineNumber = 0;
2365 this._codeMirror.eachLine(0, range.startLine, lineIterator.bind(null, wordRegex));
2366 findWordInLine(wordRegex, range.startLine, currentLineText, 0, range.startColumn);
2368 if (typeof matchedLineNumber !== "number")
2370 return new WebInspector.TextRange(matchedLineNumber, matchedColumnNumber, matchedLineNumber, matchedColumnNumber + textToFind.length);
2375 * @param {string} modeName
2376 * @param {string} tokenPrefix
2378 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens = function(modeName, tokenPrefix)
2380 var oldModeName = modeName + "-old";
2381 if (CodeMirror.modes[oldModeName])
2384 CodeMirror.defineMode(oldModeName, CodeMirror.modes[modeName]);
2385 CodeMirror.defineMode(modeName, modeConstructor);
2387 function modeConstructor(config, parserConfig)
2389 var innerConfig = {};
2390 for (var i in parserConfig)
2391 innerConfig[i] = parserConfig[i];
2392 innerConfig.name = oldModeName;
2393 var codeMirrorMode = CodeMirror.getMode(config, innerConfig);
2394 codeMirrorMode.name = modeName;
2395 codeMirrorMode.token = tokenOverride.bind(null, codeMirrorMode.token);
2396 return codeMirrorMode;
2399 function tokenOverride(superToken, stream, state)
2401 var token = superToken(stream, state);
2402 return token ? tokenPrefix + token.split(/ +/).join(" " + tokenPrefix) : token;
2406 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("css", "css-");
2407 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("javascript", "js-");
2408 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("xml", "xml-");
2411 var backgroundColor = InspectorFrontendHost.getSelectionBackgroundColor();
2412 var backgroundColorRule = backgroundColor ? ".CodeMirror .CodeMirror-selected { background-color: " + backgroundColor + ";}" : "";
2413 var foregroundColor = InspectorFrontendHost.getSelectionForegroundColor();
2414 var foregroundColorRule = foregroundColor ? ".CodeMirror .CodeMirror-selectedtext:not(.CodeMirror-persist-highlight) { color: " + foregroundColor + "!important;}" : "";
2415 if (!foregroundColorRule && !backgroundColorRule)
2418 var style = document.createElement("style");
2419 style.textContent = backgroundColorRule + foregroundColorRule;
2420 document.head.appendChild(style);