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");
130 CodeMirror.commands.maybeAvoidSmartSingleQuotes = this._maybeAvoidSmartQuotes.bind(this, "'");
131 CodeMirror.commands.maybeAvoidSmartDoubleQuotes = this._maybeAvoidSmartQuotes.bind(this, "\"");
132 this._codeMirror.addKeyMap({
133 "'": "maybeAvoidSmartSingleQuotes",
134 "'\"'": "maybeAvoidSmartDoubleQuotes"
137 this._codeMirror.setOption("flattenSpans", false);
139 this._codeMirror.setOption("maxHighlightLength", WebInspector.CodeMirrorTextEditor.maxHighlightLength);
140 this._codeMirror.setOption("mode", null);
141 this._codeMirror.setOption("crudeMeasuringFrom", 1000);
143 this._shouldClearHistory = true;
144 this._lineSeparator = "\n";
146 this._autocompleteController = WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy;
147 this._tokenHighlighter = new WebInspector.CodeMirrorTextEditor.TokenHighlighter(this, this._codeMirror);
148 this._blockIndentController = new WebInspector.CodeMirrorTextEditor.BlockIndentController(this._codeMirror);
149 this._fixWordMovement = new WebInspector.CodeMirrorTextEditor.FixWordMovement(this._codeMirror);
150 this._selectNextOccurrenceController = new WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController(this, this._codeMirror);
152 this._codeMirror.on("changes", this._changes.bind(this));
153 this._codeMirror.on("gutterClick", this._gutterClick.bind(this));
154 this._codeMirror.on("cursorActivity", this._cursorActivity.bind(this));
155 this._codeMirror.on("beforeSelectionChange", this._beforeSelectionChange.bind(this));
156 this._codeMirror.on("scroll", this._scroll.bind(this));
157 this._codeMirror.on("focus", this._focus.bind(this));
158 this._codeMirror.on("keyHandled", this._onKeyHandled.bind(this));
159 this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false);
161 * @this {WebInspector.CodeMirrorTextEditor}
163 function updateAnticipateJumpFlag(value)
165 this._isHandlingMouseDownEvent = value;
167 this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, true), true);
168 this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, false), false);
170 this.element.style.overflow = "hidden";
171 this.element.firstChild.classList.add("source-code");
172 this.element.firstChild.classList.add("fill");
173 this._elementToWidget = new Map();
174 this._nestedUpdatesCounter = 0;
176 this.element.addEventListener("focus", this._handleElementFocus.bind(this), false);
177 this.element.addEventListener("keydown", this._handleKeyDown.bind(this), true);
178 this.element.addEventListener("keydown", this._handlePostKeyDown.bind(this), false);
179 this.element.tabIndex = 0;
181 this._setupWhitespaceHighlight();
184 /** @typedef {{canceled: boolean, from: !CodeMirror.Pos, to: !CodeMirror.Pos, text: string, origin: string, cancel: function()}} */
185 WebInspector.CodeMirrorTextEditor.BeforeChangeObject;
187 /** @typedef {{from: !CodeMirror.Pos, to: !CodeMirror.Pos, origin: string, text: !Array.<string>, removed: !Array.<string>}} */
188 WebInspector.CodeMirrorTextEditor.ChangeObject;
190 WebInspector.CodeMirrorTextEditor.maxHighlightLength = 1000;
193 * @param {!CodeMirror} codeMirror
195 WebInspector.CodeMirrorTextEditor.autocompleteCommand = function(codeMirror)
197 codeMirror._codeMirrorTextEditor._autocompleteController.autocomplete();
199 CodeMirror.commands.autocomplete = WebInspector.CodeMirrorTextEditor.autocompleteCommand;
202 * @param {!CodeMirror} codeMirror
204 WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand = function(codeMirror)
206 codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.undoLastSelection();
208 CodeMirror.commands.undoLastSelection = WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand;
211 * @param {!CodeMirror} codeMirror
213 WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand = function(codeMirror)
215 codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.selectNextOccurrence();
217 CodeMirror.commands.selectNextOccurrence = WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand;
220 * @param {boolean} shift
221 * @param {!CodeMirror} codeMirror
223 WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand = function(shift, codeMirror)
225 codeMirror._codeMirrorTextEditor._doCamelCaseMovement(-1, shift);
227 CodeMirror.commands.moveCamelLeft = WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand.bind(null, false);
228 CodeMirror.commands.selectCamelLeft = WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand.bind(null, true);
231 * @param {boolean} shift
232 * @param {!CodeMirror} codeMirror
234 WebInspector.CodeMirrorTextEditor.moveCamelRightCommand = function(shift, codeMirror)
236 codeMirror._codeMirrorTextEditor._doCamelCaseMovement(1, shift);
238 CodeMirror.commands.moveCamelRight = WebInspector.CodeMirrorTextEditor.moveCamelRightCommand.bind(null, false);
239 CodeMirror.commands.selectCamelRight = WebInspector.CodeMirrorTextEditor.moveCamelRightCommand.bind(null, true);
242 * @param {!CodeMirror} codeMirror
244 CodeMirror.commands.smartNewlineAndIndent = function(codeMirror)
246 codeMirror.operation(innerSmartNewlineAndIndent.bind(null, codeMirror));
248 function innerSmartNewlineAndIndent(codeMirror)
250 var selections = codeMirror.listSelections();
251 var replacements = [];
252 for (var i = 0; i < selections.length; ++i) {
253 var selection = selections[i];
254 var cur = CodeMirror.cmpPos(selection.head, selection.anchor) < 0 ? selection.head : selection.anchor;
255 var line = codeMirror.getLine(cur.line);
256 var indent = WebInspector.TextUtils.lineIndent(line);
257 replacements.push("\n" + indent.substring(0, Math.min(cur.ch, indent.length)));
259 codeMirror.replaceSelections(replacements);
260 codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
265 * @param {!CodeMirror} codeMirror
267 CodeMirror.commands.gotoMatchingBracket = function(codeMirror)
269 var updatedSelections = [];
270 var selections = codeMirror.listSelections();
271 for (var i = 0; i < selections.length; ++i) {
272 var selection = selections[i];
273 var cursor = selection.head;
274 var matchingBracket = codeMirror.findMatchingBracket(cursor, false, { maxScanLines: 10000 });
275 var updatedHead = cursor;
276 if (matchingBracket && matchingBracket.match) {
277 var columnCorrection = CodeMirror.cmpPos(matchingBracket.from, cursor) === 0 ? 1 : 0;
278 updatedHead = new CodeMirror.Pos(matchingBracket.to.line, matchingBracket.to.ch + columnCorrection);
280 updatedSelections.push({
285 codeMirror.setSelections(updatedSelections);
289 * @param {!CodeMirror} codemirror
291 CodeMirror.commands.undoAndReveal = function(codemirror)
293 var scrollInfo = codemirror.getScrollInfo();
294 codemirror.execCommand("undo");
295 var cursor = codemirror.getCursor("start");
296 codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo);
297 codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete();
301 * @param {!CodeMirror} codemirror
303 CodeMirror.commands.redoAndReveal = function(codemirror)
305 var scrollInfo = codemirror.getScrollInfo();
306 codemirror.execCommand("redo");
307 var cursor = codemirror.getCursor("start");
308 codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo);
309 codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete();
313 * @return {!Object|undefined}
315 CodeMirror.commands.dismissMultipleSelections = function(codemirror)
317 var selections = codemirror.listSelections();
318 var selection = selections[0];
319 if (selections.length === 1) {
320 if (codemirror._codeMirrorTextEditor._isSearchActive())
321 return CodeMirror.Pass;
322 if (WebInspector.CodeMirrorUtils.toRange(selection.anchor, selection.head).isEmpty())
323 return CodeMirror.Pass;
324 codemirror.setSelection(selection.anchor, selection.anchor, {scroll: false});
325 codemirror._codeMirrorTextEditor._revealLine(selection.anchor.line);
329 codemirror.setSelection(selection.anchor, selection.head, {scroll: false});
330 codemirror._codeMirrorTextEditor._revealLine(selection.anchor.line);
333 WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold = 2000;
334 WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan = 16;
335 WebInspector.CodeMirrorTextEditor.MaxEditableTextSize = 1024 * 1024 * 10;
337 WebInspector.CodeMirrorTextEditor.prototype = {
339 * @param {string} quoteCharacter
342 _maybeAvoidSmartQuotes: function(quoteCharacter)
344 if (!WebInspector.settings.textEditorBracketMatching.get())
345 return CodeMirror.Pass;
346 var selections = this.selections();
347 if (selections.length !== 1 || !selections[0].isEmpty())
348 return CodeMirror.Pass;
350 var selection = selections[0];
351 var token = this.tokenAtTextPosition(selection.startLine, selection.startColumn);
352 if (!token || token.type.indexOf("string") === -1)
353 return CodeMirror.Pass;
354 var line = this.line(selection.startLine);
355 var tokenValue = line.substring(token.startColumn, token.endColumn + 1);
356 if (tokenValue[0] === tokenValue[tokenValue.length - 1] && (tokenValue[0] === "'" || tokenValue[0] === "\""))
357 return CodeMirror.Pass;
358 this._codeMirror.replaceSelection(quoteCharacter);
361 _onKeyHandled: function()
363 WebInspector.shortcutRegistry.dismissPendingShortcutAction();
366 _onAutoAppendedSpaces: function()
368 this._autoAppendedSpaces = this._autoAppendedSpaces || [];
369 for (var i = 0; i < this._autoAppendedSpaces.length; ++i) {
370 var position = this._autoAppendedSpaces[i].resolve();
373 var line = this.line(position.lineNumber);
374 if (line.length === position.columnNumber && WebInspector.TextUtils.lineIndent(line).length === line.length)
375 this._codeMirror.replaceRange("", new CodeMirror.Pos(position.lineNumber, 0), new CodeMirror.Pos(position.lineNumber, position.columnNumber));
377 this._autoAppendedSpaces = [];
378 var selections = this.selections();
379 for (var i = 0; i < selections.length; ++i) {
380 var selection = selections[i];
381 this._autoAppendedSpaces.push(this.textEditorPositionHandle(selection.startLine, selection.startColumn));
386 * @param {number} lineNumber
387 * @param {number} lineLength
388 * @param {number} charNumber
389 * @return {{lineNumber: number, columnNumber: number}}
391 _normalizePositionForOverlappingColumn: function(lineNumber, lineLength, charNumber)
393 var linesCount = this._codeMirror.lineCount();
394 var columnNumber = charNumber;
395 if (charNumber < 0 && lineNumber > 0) {
397 columnNumber = this.line(lineNumber).length;
398 } else if (charNumber >= lineLength && lineNumber < linesCount - 1) {
402 columnNumber = Number.constrain(charNumber, 0, lineLength);
405 lineNumber: lineNumber,
406 columnNumber: columnNumber
411 * @param {number} lineNumber
412 * @param {number} columnNumber
413 * @param {number} direction
414 * @return {{lineNumber: number, columnNumber: number}}
416 _camelCaseMoveFromPosition: function(lineNumber, columnNumber, direction)
419 * @param {number} charNumber
420 * @param {number} length
423 function valid(charNumber, length)
425 return charNumber >= 0 && charNumber < length;
429 * @param {string} text
430 * @param {number} charNumber
433 function isWordStart(text, charNumber)
435 var position = charNumber;
436 var nextPosition = charNumber + 1;
437 return valid(position, text.length) && valid(nextPosition, text.length)
438 && WebInspector.TextUtils.isWordChar(text[position]) && WebInspector.TextUtils.isWordChar(text[nextPosition])
439 && WebInspector.TextUtils.isUpperCase(text[position]) && WebInspector.TextUtils.isLowerCase(text[nextPosition]);
443 * @param {string} text
444 * @param {number} charNumber
447 function isWordEnd(text, charNumber)
449 var position = charNumber;
450 var prevPosition = charNumber - 1;
451 return valid(position, text.length) && valid(prevPosition, text.length)
452 && WebInspector.TextUtils.isWordChar(text[position]) && WebInspector.TextUtils.isWordChar(text[prevPosition])
453 && WebInspector.TextUtils.isUpperCase(text[position]) && WebInspector.TextUtils.isLowerCase(text[prevPosition]);
457 * @param {number} lineNumber
458 * @param {number} lineLength
459 * @param {number} columnNumber
460 * @return {{lineNumber: number, columnNumber: number}}
462 function constrainPosition(lineNumber, lineLength, columnNumber)
465 lineNumber: lineNumber,
466 columnNumber: Number.constrain(columnNumber, 0, lineLength)
470 var text = this.line(lineNumber);
471 var length = text.length;
473 if ((columnNumber === length && direction === 1)
474 || (columnNumber === 0 && direction === -1))
475 return this._normalizePositionForOverlappingColumn(lineNumber, length, columnNumber + direction);
477 var charNumber = direction === 1 ? columnNumber : columnNumber - 1;
479 // Move through initial spaces if any.
480 while (valid(charNumber, length) && WebInspector.TextUtils.isSpaceChar(text[charNumber]))
481 charNumber += direction;
482 if (!valid(charNumber, length))
483 return constrainPosition(lineNumber, length, charNumber);
485 if (WebInspector.TextUtils.isStopChar(text[charNumber])) {
486 while (valid(charNumber, length) && WebInspector.TextUtils.isStopChar(text[charNumber]))
487 charNumber += direction;
488 if (!valid(charNumber, length))
489 return constrainPosition(lineNumber, length, charNumber);
491 lineNumber: lineNumber,
492 columnNumber: direction === -1 ? charNumber + 1 : charNumber
496 charNumber += direction;
497 while (valid(charNumber, length) && !isWordStart(text, charNumber) && !isWordEnd(text, charNumber) && WebInspector.TextUtils.isWordChar(text[charNumber]))
498 charNumber += direction;
500 if (!valid(charNumber, length))
501 return constrainPosition(lineNumber, length, charNumber);
502 if (isWordStart(text, charNumber) || isWordEnd(text, charNumber)) {
504 lineNumber: lineNumber,
505 columnNumber: charNumber
510 lineNumber: lineNumber,
511 columnNumber: direction === -1 ? charNumber + 1 : charNumber
516 * @param {number} direction
517 * @param {boolean} shift
519 _doCamelCaseMovement: function(direction, shift)
521 var selections = this.selections();
522 for (var i = 0; i < selections.length; ++i) {
523 var selection = selections[i];
524 var move = this._camelCaseMoveFromPosition(selection.endLine, selection.endColumn, direction);
525 selection.endLine = move.lineNumber;
526 selection.endColumn = move.columnNumber;
528 selections[i] = selection.collapseToEnd();
530 this.setSelections(selections);
535 WebInspector.settings.textEditorIndent.removeChangeListener(this._updateEditorIndentation, this);
536 WebInspector.settings.textEditorAutoDetectIndent.removeChangeListener(this._updateEditorIndentation, this);
537 WebInspector.settings.showWhitespacesInEditor.removeChangeListener(this._updateCodeMirrorMode, this);
538 WebInspector.settings.textEditorBracketMatching.removeChangeListener(this._enableBracketMatchingIfNeeded, this);
541 _enableBracketMatchingIfNeeded: function()
543 this._codeMirror.setOption("autoCloseBrackets", WebInspector.settings.textEditorBracketMatching.get() ? { explode: false } : false);
548 if (this._wasOnceShown)
550 this._wasOnceShown = true;
551 this._codeMirror.refresh();
554 _guessIndentationLevel: function()
556 var tabRegex = /^\t+/;
559 function processLine(lineHandle)
561 var text = lineHandle.text;
562 if (text.length === 0 || !WebInspector.TextUtils.isSpaceChar(text[0]))
564 if (tabRegex.test(text)) {
569 while (i < text.length && WebInspector.TextUtils.isSpaceChar(text[i]))
573 indents[i] = 1 + (indents[i] || 0);
575 this._codeMirror.eachLine(0, 1000, processLine);
577 var onePercentFilterThreshold = this.linesCount / 100;
578 if (tabLines && tabLines > onePercentFilterThreshold)
580 var minimumIndent = Infinity;
581 for (var i in indents) {
582 if (indents[i] < onePercentFilterThreshold)
584 var indent = parseInt(i, 10);
585 if (minimumIndent > indent)
586 minimumIndent = indent;
588 if (minimumIndent === Infinity)
589 return WebInspector.settings.textEditorIndent.get();
590 return new Array(minimumIndent + 1).join(" ");
593 _updateEditorIndentation: function()
596 var indent = WebInspector.settings.textEditorIndent.get();
597 if (WebInspector.settings.textEditorAutoDetectIndent.get())
598 indent = this._guessIndentationLevel();
599 if (indent === WebInspector.TextUtils.Indent.TabCharacter) {
600 this._codeMirror.setOption("indentWithTabs", true);
601 this._codeMirror.setOption("indentUnit", 4);
603 this._codeMirror.setOption("indentWithTabs", false);
604 this._codeMirror.setOption("indentUnit", indent.length);
605 extraKeys.Tab = function(codeMirror)
607 if (codeMirror.somethingSelected())
608 return CodeMirror.Pass;
609 var pos = codeMirror.getCursor("head");
610 codeMirror.replaceRange(indent.substring(pos.ch % indent.length), codeMirror.getCursor());
613 this._codeMirror.setOption("extraKeys", extraKeys);
614 this._indentationLevel = indent;
622 return this._indentationLevel;
628 _isSearchActive: function()
630 return !!this._tokenHighlighter.highlightedRegex();
634 * @param {!RegExp} regex
635 * @param {?WebInspector.TextRange} range
637 highlightSearchResults: function(regex, range)
640 * @this {WebInspector.CodeMirrorTextEditor}
642 function innerHighlightRegex()
645 this._revealLine(range.startLine);
646 if (range.endColumn > WebInspector.CodeMirrorTextEditor.maxHighlightLength)
647 this.setSelection(range);
649 this.setSelection(WebInspector.TextRange.createFromLocation(range.startLine, range.startColumn));
651 // Collapse selection to end on search start so that we jump to next occurrence on the first enter press.
652 this.setSelection(this.selection().collapseToEnd());
654 this._tokenHighlighter.highlightSearchResults(regex, range);
656 if (!this._selectionBeforeSearch)
657 this._selectionBeforeSearch = this.selection();
658 this._codeMirror.operation(innerHighlightRegex.bind(this));
661 cancelSearchResultsHighlight: function()
663 this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));
664 if (this._selectionBeforeSearch) {
665 this._reportJump(this._selectionBeforeSearch, this.selection());
666 delete this._selectionBeforeSearch;
672 this._codeMirror.undo();
677 this._codeMirror.redo();
680 _setupWhitespaceHighlight: function()
682 if (WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected || !WebInspector.settings.showWhitespacesInEditor.get())
684 WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected = true;
685 const classBase = ".show-whitespaces .CodeMirror .cm-whitespace-";
686 const spaceChar = "·";
689 for (var i = 1; i <= WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan; ++i) {
690 spaceChars += spaceChar;
691 var rule = classBase + i + "::before { content: '" + spaceChars + "';}\n";
694 var style = document.createElement("style");
695 style.textContent = rules;
696 document.head.appendChild(style);
699 _handleKeyDown: function(e)
701 if (this._autocompleteController.keyDown(e))
705 _handlePostKeyDown: function(e)
707 if (e.defaultPrevented)
712 * @param {?WebInspector.CompletionDictionary} dictionary
714 setCompletionDictionary: function(dictionary)
716 this._autocompleteController.dispose();
718 this._autocompleteController = new WebInspector.CodeMirrorTextEditor.AutocompleteController(this, this._codeMirror, dictionary);
720 this._autocompleteController = WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy;
724 * @param {number} lineNumber
725 * @param {number} column
726 * @return {?{x: number, y: number, height: number}}
728 cursorPositionToCoordinates: function(lineNumber, column)
730 if (lineNumber >= this._codeMirror.lineCount() || lineNumber < 0 || column < 0 || column > this._codeMirror.getLine(lineNumber).length)
733 var metrics = this._codeMirror.cursorCoords(new CodeMirror.Pos(lineNumber, column));
738 height: metrics.bottom - metrics.top
745 * @return {?WebInspector.TextRange}
747 coordinatesToCursorPosition: function(x, y)
749 var element = document.elementFromPoint(x, y);
750 if (!element || !element.isSelfOrDescendant(this._codeMirror.getWrapperElement()))
752 var gutterBox = this._codeMirror.getGutterElement().boxInWindow();
753 if (x >= gutterBox.x && x <= gutterBox.x + gutterBox.width &&
754 y >= gutterBox.y && y <= gutterBox.y + gutterBox.height)
756 var coords = this._codeMirror.coordsChar({left: x, top: y});
757 return WebInspector.CodeMirrorUtils.toRange(coords, coords);
761 * @param {number} lineNumber
762 * @param {number} column
763 * @return {?{startColumn: number, endColumn: number, type: string}}
765 tokenAtTextPosition: function(lineNumber, column)
767 if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
769 var token = this._codeMirror.getTokenAt(new CodeMirror.Pos(lineNumber, (column || 0) + 1));
770 if (!token || !token.type)
773 startColumn: token.start,
774 endColumn: token.end,
780 * @param {!WebInspector.TextRange} textRange
783 copyRange: function(textRange)
785 var pos = WebInspector.CodeMirrorUtils.toPos(textRange.normalize());
786 return this._codeMirror.getRange(pos.start, pos.end);
794 return this._codeMirror.isClean();
797 markClean: function()
799 this._codeMirror.markClean();
802 _hasLongLines: function()
804 function lineIterator(lineHandle)
806 if (lineHandle.text.length > WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold)
810 var hasLongLines = false;
811 this._codeMirror.eachLine(lineIterator);
816 * @param {string} mimeType
819 _whitespaceOverlayMode: function(mimeType)
821 var modeName = CodeMirror.mimeModes[mimeType] ? (CodeMirror.mimeModes[mimeType].name || CodeMirror.mimeModes[mimeType]) : CodeMirror.mimeModes["text/plain"];
822 modeName += "+whitespaces";
823 if (CodeMirror.modes[modeName])
826 function modeConstructor(config, parserConfig)
828 function nextToken(stream)
830 if (stream.peek() === " ") {
832 while (spaces < WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan && stream.peek() === " ") {
836 return "whitespace whitespace-" + spaces;
838 while (!stream.eol() && stream.peek() !== " ")
842 var whitespaceMode = {
845 return CodeMirror.overlayMode(CodeMirror.getMode(config, mimeType), whitespaceMode, false);
847 CodeMirror.defineMode(modeName, modeConstructor);
851 _enableLongLinesMode: function()
853 this._codeMirror.setOption("styleSelectedText", false);
854 this._longLinesMode = true;
857 _disableLongLinesMode: function()
859 this._codeMirror.setOption("styleSelectedText", true);
860 this._longLinesMode = false;
863 _updateCodeMirrorMode: function()
865 this._setupWhitespaceHighlight();
866 var showWhitespaces = WebInspector.settings.showWhitespacesInEditor.get();
867 this.element.classList.toggle("show-whitespaces", showWhitespaces);
868 this._codeMirror.setOption("mode", showWhitespaces ? this._whitespaceOverlayMode(this._mimeType) : this._mimeType);
872 * @param {string} mimeType
874 setMimeType: function(mimeType)
876 this._mimeType = mimeType;
877 if (this._hasLongLines())
878 this._enableLongLinesMode();
880 this._disableLongLinesMode();
881 this._updateCodeMirrorMode();
882 this._autocompleteController.setMimeType(mimeType);
886 * @param {boolean} readOnly
888 setReadOnly: function(readOnly)
890 this.element.classList.toggle("CodeMirror-readonly", readOnly)
891 this._codeMirror.setOption("readOnly", readOnly);
899 return !!this._codeMirror.getOption("readOnly");
903 * @param {!Object} highlightDescriptor
905 removeHighlight: function(highlightDescriptor)
907 highlightDescriptor.clear();
911 * @param {!WebInspector.TextRange} range
912 * @param {string} cssClass
915 highlightRange: function(range, cssClass)
917 cssClass = "CodeMirror-persist-highlight " + cssClass;
918 var pos = WebInspector.CodeMirrorUtils.toPos(range);
920 return this._codeMirror.markText(pos.start, pos.end, {
922 startStyle: cssClass + "-start",
923 endStyle: cssClass + "-end"
930 defaultFocusedElement: function()
937 this._codeMirror.focus();
940 _handleElementFocus: function()
942 this._codeMirror.focus();
945 beginUpdates: function()
947 ++this._nestedUpdatesCounter;
950 endUpdates: function()
952 if (!--this._nestedUpdatesCounter)
953 this._codeMirror.refresh();
957 * @param {number} lineNumber
959 _revealLine: function(lineNumber)
961 this._innerRevealLine(lineNumber, this._codeMirror.getScrollInfo());
965 * @param {number} lineNumber
966 * @param {!{left: number, top: number, width: number, height: number, clientWidth: number, clientHeight: number}} scrollInfo
968 _innerRevealLine: function(lineNumber, scrollInfo)
970 var topLine = this._codeMirror.lineAtHeight(scrollInfo.top, "local");
971 var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
972 var linesPerScreen = bottomLine - topLine + 1;
973 if (lineNumber < topLine) {
974 var topLineToReveal = Math.max(lineNumber - (linesPerScreen / 2) + 1, 0) | 0;
975 this._codeMirror.scrollIntoView(new CodeMirror.Pos(topLineToReveal, 0));
976 } else if (lineNumber > bottomLine) {
977 var bottomLineToReveal = Math.min(lineNumber + (linesPerScreen / 2) - 1, this.linesCount - 1) | 0;
978 this._codeMirror.scrollIntoView(new CodeMirror.Pos(bottomLineToReveal, 0));
982 _gutterClick: function(instance, lineNumber, gutter, event)
984 this.dispatchEventToListeners(WebInspector.TextEditor.Events.GutterClick, { lineNumber: lineNumber, event: event });
987 _contextMenu: function(event)
989 var contextMenu = new WebInspector.ContextMenu(event);
990 var target = event.target.enclosingNodeOrSelfWithClass("CodeMirror-gutter-elt");
992 this._delegate.populateLineGutterContextMenu(contextMenu, parseInt(target.textContent, 10) - 1);
994 this._delegate.populateTextAreaContextMenu(contextMenu, 0);
995 contextMenu.appendApplicableItems(this);
1000 * @param {number} lineNumber
1001 * @param {boolean} disabled
1002 * @param {boolean} conditional
1004 addBreakpoint: function(lineNumber, disabled, conditional)
1006 if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
1008 var className = "cm-breakpoint" + (conditional ? " cm-breakpoint-conditional" : "") + (disabled ? " cm-breakpoint-disabled" : "");
1009 this._codeMirror.addLineClass(lineNumber, "wrap", className);
1013 * @param {number} lineNumber
1015 removeBreakpoint: function(lineNumber)
1017 if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
1019 var wrapClasses = this._codeMirror.getLineHandle(lineNumber).wrapClass;
1022 var classes = wrapClasses.split(" ");
1023 for (var i = 0; i < classes.length; ++i) {
1024 if (classes[i].startsWith("cm-breakpoint"))
1025 this._codeMirror.removeLineClass(lineNumber, "wrap", classes[i]);
1030 * @param {number} lineNumber
1032 setExecutionLine: function(lineNumber)
1034 this.clearPositionHighlight();
1035 this._executionLine = this._codeMirror.getLineHandle(lineNumber);
1036 if (!this._executionLine)
1038 this._codeMirror.addLineClass(this._executionLine, "wrap", "cm-execution-line");
1041 clearExecutionLine: function()
1043 this.clearPositionHighlight();
1044 if (this._executionLine)
1045 this._codeMirror.removeLineClass(this._executionLine, "wrap", "cm-execution-line");
1046 delete this._executionLine;
1050 * @param {number} lineNumber
1051 * @param {string} className
1052 * @param {boolean} toggled
1054 toggleLineClass: function(lineNumber, className, toggled)
1056 var lineHandle = this._codeMirror.getLineHandle(lineNumber);
1060 this._codeMirror.addLineClass(lineHandle, "wrap", className);
1062 this._codeMirror.removeLineClass(lineHandle, "wrap", className);
1066 * @param {number} lineNumber
1067 * @param {!Element} element
1069 addDecoration: function(lineNumber, element)
1071 var widget = this._codeMirror.addLineWidget(lineNumber, element);
1072 this._elementToWidget.set(element, widget);
1076 * @param {number} lineNumber
1077 * @param {!Element} element
1079 removeDecoration: function(lineNumber, element)
1081 var widget = this._elementToWidget.remove(element);
1083 this._codeMirror.removeLineWidget(widget);
1087 * @param {number} lineNumber 0-based
1088 * @param {number=} columnNumber
1089 * @param {boolean=} shouldHighlight
1091 revealPosition: function(lineNumber, columnNumber, shouldHighlight)
1093 lineNumber = Number.constrain(lineNumber, 0, this._codeMirror.lineCount() - 1);
1094 if (typeof columnNumber !== "number")
1096 columnNumber = Number.constrain(columnNumber, 0, this._codeMirror.getLine(lineNumber).length);
1098 this.clearPositionHighlight();
1099 this._highlightedLine = this._codeMirror.getLineHandle(lineNumber);
1100 if (!this._highlightedLine)
1102 this._revealLine(lineNumber);
1103 if (shouldHighlight) {
1104 this._codeMirror.addLineClass(this._highlightedLine, null, "cm-highlight");
1105 this._clearHighlightTimeout = setTimeout(this.clearPositionHighlight.bind(this), 2000);
1107 this.setSelection(WebInspector.TextRange.createFromLocation(lineNumber, columnNumber));
1110 clearPositionHighlight: function()
1112 if (this._clearHighlightTimeout)
1113 clearTimeout(this._clearHighlightTimeout);
1114 delete this._clearHighlightTimeout;
1116 if (this._highlightedLine)
1117 this._codeMirror.removeLineClass(this._highlightedLine, null, "cm-highlight");
1118 delete this._highlightedLine;
1122 * @return {!Array.<!Element>}
1124 elementsToRestoreScrollPositionsFor: function()
1130 * @param {!WebInspector.TextEditor} textEditor
1132 inheritScrollPositions: function(textEditor)
1137 * @param {number} width
1138 * @param {number} height
1140 _updatePaddingBottom: function(width, height)
1142 var scrollInfo = this._codeMirror.getScrollInfo();
1143 var newPaddingBottom;
1144 var linesElement = this.element.firstElementChild.querySelector(".CodeMirror-lines");
1145 var lineCount = this._codeMirror.lineCount();
1147 newPaddingBottom = 0;
1149 newPaddingBottom = Math.max(scrollInfo.clientHeight - this._codeMirror.getLineHandle(this._codeMirror.lastLine()).height, 0);
1150 newPaddingBottom += "px";
1151 linesElement.style.paddingBottom = newPaddingBottom;
1152 this._codeMirror.setSize(width, height);
1155 _resizeEditor: function()
1157 var parentElement = this.element.parentElement;
1158 if (!parentElement || !this.isShowing())
1160 var scrollLeft = this._codeMirror.doc.scrollLeft;
1161 var scrollTop = this._codeMirror.doc.scrollTop;
1162 var width = parentElement.offsetWidth;
1163 var height = parentElement.offsetHeight - this.element.offsetTop;
1164 this._codeMirror.setSize(width, height);
1165 this._updatePaddingBottom(width, height);
1166 this._codeMirror.scrollTo(scrollLeft, scrollTop);
1169 onResize: function()
1171 this._autocompleteController.finishAutocomplete();
1172 this._resizeEditor();
1176 * @param {!WebInspector.TextRange} range
1177 * @param {string} text
1178 * @return {!WebInspector.TextRange}
1180 editRange: function(range, text)
1182 var pos = WebInspector.CodeMirrorUtils.toPos(range);
1183 this._codeMirror.replaceRange(text, pos.start, pos.end);
1184 var newRange = WebInspector.CodeMirrorUtils.toRange(pos.start, this._codeMirror.posFromIndex(this._codeMirror.indexFromPos(pos.start) + text.length));
1185 this._delegate.onTextChanged(range, newRange);
1186 if (WebInspector.settings.textEditorAutoDetectIndent.get())
1187 this._updateEditorIndentation();
1192 * @param {number} lineNumber
1193 * @param {number} column
1194 * @param {function(string):boolean} isWordChar
1195 * @return {!WebInspector.TextRange}
1197 _wordRangeForCursorPosition: function(lineNumber, column, isWordChar)
1199 var line = this.line(lineNumber);
1200 var wordStart = column;
1201 if (column !== 0 && isWordChar(line.charAt(column - 1))) {
1202 wordStart = column - 1;
1203 while (wordStart > 0 && isWordChar(line.charAt(wordStart - 1)))
1206 var wordEnd = column;
1207 while (wordEnd < line.length && isWordChar(line.charAt(wordEnd)))
1209 return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, wordEnd);
1213 * @param {!WebInspector.CodeMirrorTextEditor.ChangeObject} changeObject
1214 * @return {{oldRange: !WebInspector.TextRange, newRange: !WebInspector.TextRange}}
1216 _changeObjectToEditOperation: function(changeObject)
1218 var oldRange = WebInspector.CodeMirrorUtils.toRange(changeObject.from, changeObject.to);
1219 var newRange = oldRange.clone();
1220 var linesAdded = changeObject.text.length;
1221 if (linesAdded === 0) {
1222 newRange.endLine = newRange.startLine;
1223 newRange.endColumn = newRange.startColumn;
1224 } else if (linesAdded === 1) {
1225 newRange.endLine = newRange.startLine;
1226 newRange.endColumn = newRange.startColumn + changeObject.text[0].length;
1228 newRange.endLine = newRange.startLine + linesAdded - 1;
1229 newRange.endColumn = changeObject.text[linesAdded - 1].length;
1238 * @param {!CodeMirror} codeMirror
1239 * @param {!Array.<!WebInspector.CodeMirrorTextEditor.ChangeObject>} changes
1241 _changes: function(codeMirror, changes)
1243 if (!changes.length)
1245 // 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.
1246 var hasOneLine = this._codeMirror.lineCount() === 1;
1247 if (hasOneLine !== this._hasOneLine)
1248 this._resizeEditor();
1249 this._hasOneLine = hasOneLine;
1250 var widgets = this._elementToWidget.values();
1251 for (var i = 0; i < widgets.length; ++i)
1252 this._codeMirror.removeLineWidget(widgets[i]);
1253 this._elementToWidget.clear();
1255 for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) {
1256 var changeObject = changes[changeIndex];
1258 var editInfo = this._changeObjectToEditOperation(changeObject);
1259 if (!this._muteTextChangedEvent)
1260 this._delegate.onTextChanged(editInfo.oldRange, editInfo.newRange);
1264 _cursorActivity: function()
1266 var start = this._codeMirror.getCursor("anchor");
1267 var end = this._codeMirror.getCursor("head");
1268 this._delegate.selectionChanged(WebInspector.CodeMirrorUtils.toRange(start, end));
1269 if (!this._isSearchActive())
1270 this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));
1274 * @param {!CodeMirror} codeMirror
1275 * @param {{ranges: !Array.<{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>}} selection
1277 _beforeSelectionChange: function(codeMirror, selection)
1279 this._selectNextOccurrenceController.selectionWillChange();
1280 if (!this._isHandlingMouseDownEvent)
1282 if (!selection.ranges.length)
1284 var primarySelection = selection.ranges[0];
1285 this._reportJump(this.selection(), WebInspector.CodeMirrorUtils.toRange(primarySelection.anchor, primarySelection.head));
1289 * @param {?WebInspector.TextRange} from
1290 * @param {?WebInspector.TextRange} to
1292 _reportJump: function(from, to)
1294 if (from && to && from.equal(to))
1296 this._delegate.onJumpToPosition(from, to);
1301 if (this._scrollTimer)
1302 clearTimeout(this._scrollTimer);
1303 var topmostLineNumber = this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local");
1304 this._scrollTimer = setTimeout(this._delegate.scrollChanged.bind(this._delegate, topmostLineNumber), 100);
1309 this._delegate.editorFocused();
1313 * @param {number} lineNumber
1315 scrollToLine: function(lineNumber)
1317 var pos = new CodeMirror.Pos(lineNumber, 0);
1318 var coords = this._codeMirror.charCoords(pos, "local");
1319 this._codeMirror.scrollTo(0, coords.top);
1325 firstVisibleLine: function()
1327 return this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local");
1333 lastVisibleLine: function()
1335 var scrollInfo = this._codeMirror.getScrollInfo();
1336 return this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
1340 * @return {!WebInspector.TextRange}
1342 selection: function()
1344 var start = this._codeMirror.getCursor("anchor");
1345 var end = this._codeMirror.getCursor("head");
1347 return WebInspector.CodeMirrorUtils.toRange(start, end);
1351 * @return {!Array.<!WebInspector.TextRange>}
1353 selections: function()
1355 var selectionList = this._codeMirror.listSelections();
1357 for (var i = 0; i < selectionList.length; ++i) {
1358 var selection = selectionList[i];
1359 result.push(WebInspector.CodeMirrorUtils.toRange(selection.anchor, selection.head));
1365 * @return {?WebInspector.TextRange}
1367 lastSelection: function()
1369 return this._lastSelection;
1373 * @param {!WebInspector.TextRange} textRange
1375 setSelection: function(textRange)
1377 this._lastSelection = textRange;
1378 var pos = WebInspector.CodeMirrorUtils.toPos(textRange);
1379 this._codeMirror.setSelection(pos.start, pos.end);
1383 * @param {!Array.<!WebInspector.TextRange>} ranges
1384 * @param {number=} primarySelectionIndex
1386 setSelections: function(ranges, primarySelectionIndex)
1388 var selections = [];
1389 for (var i = 0; i < ranges.length; ++i) {
1390 var selection = WebInspector.CodeMirrorUtils.toPos(ranges[i]);
1392 anchor: selection.start,
1396 primarySelectionIndex = primarySelectionIndex || 0;
1397 this._codeMirror.setSelections(selections, primarySelectionIndex, { scroll: false });
1401 * @param {string} text
1403 _detectLineSeparator: function(text)
1405 this._lineSeparator = text.indexOf("\r\n") >= 0 ? "\r\n" : "\n";
1409 * @param {string} text
1411 setText: function(text)
1413 this._muteTextChangedEvent = true;
1414 if (text.length > WebInspector.CodeMirrorTextEditor.MaxEditableTextSize) {
1415 this._autocompleteController.setEnabled(false);
1416 this.setReadOnly(true);
1418 this._codeMirror.setValue(text);
1419 this._updateEditorIndentation();
1420 if (this._shouldClearHistory) {
1421 this._codeMirror.clearHistory();
1422 this._shouldClearHistory = false;
1424 this._detectLineSeparator(text);
1425 delete this._muteTextChangedEvent;
1433 return this._codeMirror.getValue().replace(/\n/g, this._lineSeparator);
1437 * @return {!WebInspector.TextRange}
1441 var lineCount = this.linesCount;
1442 var lastLine = this._codeMirror.getLine(lineCount - 1);
1443 return WebInspector.CodeMirrorUtils.toRange(new CodeMirror.Pos(0, 0), new CodeMirror.Pos(lineCount - 1, lastLine.length));
1447 * @param {number} lineNumber
1450 line: function(lineNumber)
1452 return this._codeMirror.getLine(lineNumber);
1460 return this._codeMirror.lineCount();
1464 * @param {number} line
1465 * @param {string} name
1466 * @param {?Object} value
1468 setAttribute: function(line, name, value)
1470 if (line < 0 || line >= this._codeMirror.lineCount())
1472 var handle = this._codeMirror.getLineHandle(line);
1473 if (handle.attributes === undefined) handle.attributes = {};
1474 handle.attributes[name] = value;
1478 * @param {number} line
1479 * @param {string} name
1480 * @return {?Object} value
1482 getAttribute: function(line, name)
1484 if (line < 0 || line >= this._codeMirror.lineCount())
1486 var handle = this._codeMirror.getLineHandle(line);
1487 return handle.attributes && handle.attributes[name] !== undefined ? handle.attributes[name] : null;
1491 * @param {number} line
1492 * @param {string} name
1494 removeAttribute: function(line, name)
1496 if (line < 0 || line >= this._codeMirror.lineCount())
1498 var handle = this._codeMirror.getLineHandle(line);
1499 if (handle && handle.attributes)
1500 delete handle.attributes[name];
1504 * @param {number} lineNumber
1505 * @param {number} columnNumber
1506 * @return {!WebInspector.TextEditorPositionHandle}
1508 textEditorPositionHandle: function(lineNumber, columnNumber)
1510 return new WebInspector.CodeMirrorPositionHandle(this._codeMirror, new CodeMirror.Pos(lineNumber, columnNumber));
1513 __proto__: WebInspector.VBox.prototype
1518 * @implements {WebInspector.TextEditorPositionHandle}
1519 * @param {!CodeMirror} codeMirror
1520 * @param {!CodeMirror.Pos} pos
1522 WebInspector.CodeMirrorPositionHandle = function(codeMirror, pos)
1524 this._codeMirror = codeMirror;
1525 this._lineHandle = codeMirror.getLineHandle(pos.line);
1526 this._columnNumber = pos.ch;
1529 WebInspector.CodeMirrorPositionHandle.prototype = {
1531 * @return {?{lineNumber: number, columnNumber: number}}
1535 var lineNumber = this._codeMirror.getLineNumber(this._lineHandle);
1536 if (typeof lineNumber !== "number")
1539 lineNumber: lineNumber,
1540 columnNumber: this._columnNumber
1545 * @param {!WebInspector.TextEditorPositionHandle} positionHandle
1548 equal: function(positionHandle)
1550 return positionHandle._lineHandle === this._lineHandle && positionHandle._columnNumber == this._columnNumber && positionHandle._codeMirror === this._codeMirror;
1556 * @param {!WebInspector.CodeMirrorTextEditor} textEditor
1557 * @param {!CodeMirror} codeMirror
1559 WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(textEditor, codeMirror)
1561 this._textEditor = textEditor;
1562 this._codeMirror = codeMirror;
1565 WebInspector.CodeMirrorTextEditor.TokenHighlighter.prototype = {
1567 * @param {!RegExp} regex
1568 * @param {?WebInspector.TextRange} range
1570 highlightSearchResults: function(regex, range)
1572 var oldRegex = this._highlightRegex;
1573 this._highlightRegex = regex;
1574 this._highlightRange = range;
1575 if (this._searchResultMarker) {
1576 this._searchResultMarker.clear();
1577 delete this._searchResultMarker;
1579 if (this._highlightDescriptor && this._highlightDescriptor.selectionStart)
1580 this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection");
1581 var selectionStart = this._highlightRange ? new CodeMirror.Pos(this._highlightRange.startLine, this._highlightRange.startColumn) : null;
1583 this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection");
1584 if (this._highlightRegex === oldRegex) {
1585 // Do not re-add overlay mode if regex did not change for better performance.
1586 if (this._highlightDescriptor)
1587 this._highlightDescriptor.selectionStart = selectionStart;
1589 this._removeHighlight();
1590 this._setHighlighter(this._searchHighlighter.bind(this, this._highlightRegex), selectionStart);
1592 if (this._highlightRange) {
1593 var pos = WebInspector.CodeMirrorUtils.toPos(this._highlightRange);
1594 this._searchResultMarker = this._codeMirror.markText(pos.start, pos.end, {className: "cm-column-with-selection"});
1599 * @return {!RegExp|undefined}
1601 highlightedRegex: function()
1603 return this._highlightRegex;
1606 highlightSelectedTokens: function()
1608 delete this._highlightRegex;
1609 delete this._highlightRange;
1611 if (this._highlightDescriptor && this._highlightDescriptor.selectionStart)
1612 this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection");
1613 this._removeHighlight();
1614 var selectionStart = this._codeMirror.getCursor("start");
1615 var selectionEnd = this._codeMirror.getCursor("end");
1616 if (selectionStart.line !== selectionEnd.line)
1618 if (selectionStart.ch === selectionEnd.ch)
1621 var selections = this._codeMirror.getSelections();
1622 if (selections.length > 1)
1624 var selectedText = selections[0];
1625 if (this._isWord(selectedText, selectionStart.line, selectionStart.ch, selectionEnd.ch)) {
1627 this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection")
1628 this._setHighlighter(this._tokenHighlighter.bind(this, selectedText, selectionStart), selectionStart);
1633 * @param {string} selectedText
1634 * @param {number} lineNumber
1635 * @param {number} startColumn
1636 * @param {number} endColumn
1638 _isWord: function(selectedText, lineNumber, startColumn, endColumn)
1640 var line = this._codeMirror.getLine(lineNumber);
1641 var leftBound = startColumn === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(startColumn - 1));
1642 var rightBound = endColumn === line.length || !WebInspector.TextUtils.isWordChar(line.charAt(endColumn));
1643 return leftBound && rightBound && WebInspector.TextUtils.isWord(selectedText);
1646 _removeHighlight: function()
1648 if (this._highlightDescriptor) {
1649 this._codeMirror.removeOverlay(this._highlightDescriptor.overlay);
1650 delete this._highlightDescriptor;
1655 * @param {!RegExp} regex
1656 * @param {!CodeMirror.StringStream} stream
1658 _searchHighlighter: function(regex, stream)
1660 if (stream.column() === 0)
1661 delete this._searchMatchLength;
1662 if (this._searchMatchLength) {
1663 if (this._searchMatchLength > 2) {
1664 for (var i = 0; i < this._searchMatchLength - 2; ++i)
1666 this._searchMatchLength = 1;
1667 return "search-highlight";
1670 delete this._searchMatchLength;
1671 return "search-highlight search-highlight-end";
1674 var match = stream.match(regex, false);
1677 var matchLength = match[0].length;
1678 if (matchLength === 1)
1679 return "search-highlight search-highlight-full";
1680 this._searchMatchLength = matchLength;
1681 return "search-highlight search-highlight-start";
1684 while (!stream.match(regex, false) && stream.next()) {};
1688 * @param {string} token
1689 * @param {!CodeMirror.Pos} selectionStart
1690 * @param {!CodeMirror.StringStream} stream
1692 _tokenHighlighter: function(token, selectionStart, stream)
1694 var tokenFirstChar = token.charAt(0);
1695 if (stream.match(token) && (stream.eol() || !WebInspector.TextUtils.isWordChar(stream.peek())))
1696 return stream.column() === selectionStart.ch ? "token-highlight column-with-selection" : "token-highlight";
1700 eatenChar = stream.next();
1701 } while (eatenChar && (WebInspector.TextUtils.isWordChar(eatenChar) || stream.peek() !== tokenFirstChar));
1705 * @param {function(!CodeMirror.StringStream)} highlighter
1706 * @param {?CodeMirror.Pos} selectionStart
1708 _setHighlighter: function(highlighter, selectionStart)
1713 this._codeMirror.addOverlay(overlayMode);
1714 this._highlightDescriptor = {
1715 overlay: overlayMode,
1716 selectionStart: selectionStart
1723 * @param {!CodeMirror} codeMirror
1725 WebInspector.CodeMirrorTextEditor.BlockIndentController = function(codeMirror)
1727 codeMirror.addKeyMap(this);
1730 WebInspector.CodeMirrorTextEditor.BlockIndentController.prototype = {
1731 name: "blockIndentKeymap",
1736 Enter: function(codeMirror)
1738 var selections = codeMirror.listSelections();
1739 var replacements = [];
1740 var allSelectionsAreCollapsedBlocks = false;
1741 for (var i = 0; i < selections.length; ++i) {
1742 var selection = selections[i];
1743 var start = CodeMirror.cmpPos(selection.head, selection.anchor) < 0 ? selection.head : selection.anchor;
1744 var line = codeMirror.getLine(start.line);
1745 var indent = WebInspector.TextUtils.lineIndent(line);
1746 var indentToInsert = "\n" + indent + codeMirror._codeMirrorTextEditor.indent();
1747 var isCollapsedBlock = false;
1748 if (selection.head.ch === 0)
1749 return CodeMirror.Pass;
1750 if (line.substr(selection.head.ch - 1, 2) === "{}") {
1751 indentToInsert += "\n" + indent;
1752 isCollapsedBlock = true;
1753 } else if (line.substr(selection.head.ch - 1, 1) !== "{") {
1754 return CodeMirror.Pass;
1756 if (i > 0 && allSelectionsAreCollapsedBlocks !== isCollapsedBlock)
1757 return CodeMirror.Pass;
1758 replacements.push(indentToInsert);
1759 allSelectionsAreCollapsedBlocks = isCollapsedBlock;
1761 codeMirror.replaceSelections(replacements);
1762 if (!allSelectionsAreCollapsedBlocks) {
1763 codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
1766 selections = codeMirror.listSelections();
1767 var updatedSelections = [];
1768 for (var i = 0; i < selections.length; ++i) {
1769 var selection = selections[i];
1770 var line = codeMirror.getLine(selection.head.line - 1);
1771 var position = new CodeMirror.Pos(selection.head.line - 1, line.length);
1772 updatedSelections.push({
1777 codeMirror.setSelections(updatedSelections);
1778 codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
1784 "'}'": function(codeMirror)
1786 if (codeMirror.somethingSelected())
1787 return CodeMirror.Pass;
1789 var selections = codeMirror.listSelections();
1790 var replacements = [];
1791 for (var i = 0; i < selections.length; ++i) {
1792 var selection = selections[i];
1793 var line = codeMirror.getLine(selection.head.line);
1794 if (line !== WebInspector.TextUtils.lineIndent(line))
1795 return CodeMirror.Pass;
1796 replacements.push("}");
1799 codeMirror.replaceSelections(replacements);
1800 selections = codeMirror.listSelections();
1802 var updatedSelections = [];
1803 for (var i = 0; i < selections.length; ++i) {
1804 var selection = selections[i];
1805 var matchingBracket = codeMirror.findMatchingBracket(selection.head);
1806 if (!matchingBracket || !matchingBracket.match)
1808 updatedSelections.push({
1809 head: selection.head,
1810 anchor: new CodeMirror.Pos(selection.head.line, 0)
1812 var line = codeMirror.getLine(matchingBracket.to.line);
1813 var indent = WebInspector.TextUtils.lineIndent(line);
1814 replacements.push(indent + "}");
1816 codeMirror.setSelections(updatedSelections);
1817 codeMirror.replaceSelections(replacements);
1823 * @param {!CodeMirror} codeMirror
1825 WebInspector.CodeMirrorTextEditor.FixWordMovement = function(codeMirror)
1827 function moveLeft(shift, codeMirror)
1829 codeMirror.setExtending(shift);
1830 var cursor = codeMirror.getCursor("head");
1831 codeMirror.execCommand("goGroupLeft");
1832 var newCursor = codeMirror.getCursor("head");
1833 if (newCursor.ch === 0 && newCursor.line !== 0) {
1834 codeMirror.setExtending(false);
1838 var skippedText = codeMirror.getRange(newCursor, cursor, "#");
1839 if (/^\s+$/.test(skippedText))
1840 codeMirror.execCommand("goGroupLeft");
1841 codeMirror.setExtending(false);
1844 function moveRight(shift, codeMirror)
1846 codeMirror.setExtending(shift);
1847 var cursor = codeMirror.getCursor("head");
1848 codeMirror.execCommand("goGroupRight");
1849 var newCursor = codeMirror.getCursor("head");
1850 if (newCursor.ch === 0 && newCursor.line !== 0) {
1851 codeMirror.setExtending(false);
1855 var skippedText = codeMirror.getRange(cursor, newCursor, "#");
1856 if (/^\s+$/.test(skippedText))
1857 codeMirror.execCommand("goGroupRight");
1858 codeMirror.setExtending(false);
1861 var modifierKey = WebInspector.isMac() ? "Alt" : "Ctrl";
1862 var leftKey = modifierKey + "-Left";
1863 var rightKey = modifierKey + "-Right";
1865 keyMap[leftKey] = moveLeft.bind(null, false);
1866 keyMap[rightKey] = moveRight.bind(null, false);
1867 keyMap["Shift-" + leftKey] = moveLeft.bind(null, true);
1868 keyMap["Shift-" + rightKey] = moveRight.bind(null, true);
1869 codeMirror.addKeyMap(keyMap);
1875 WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI = function() {}
1877 WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI.prototype = {
1878 dispose: function() { },
1881 * @param {boolean} enabled
1883 setEnabled: function(enabled) { },
1886 * @param {string} mimeType
1888 setMimeType: function(mimeType) { },
1890 autocomplete: function() { },
1892 finishAutocomplete: function() { },
1898 keyDown: function(e) { }
1903 * @implements {WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI}
1905 WebInspector.CodeMirrorTextEditor.DummyAutocompleteController = function() { }
1907 WebInspector.CodeMirrorTextEditor.DummyAutocompleteController.prototype = {
1908 dispose: function() { },
1911 * @param {boolean} enabled
1913 setEnabled: function(enabled) { },
1916 * @param {string} mimeType
1918 setMimeType: function(mimeType) { },
1920 autocomplete: function() { },
1922 finishAutocomplete: function() { },
1928 keyDown: function(e)
1936 * @implements {WebInspector.SuggestBoxDelegate}
1937 * @implements {WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI}
1938 * @param {!WebInspector.CodeMirrorTextEditor} textEditor
1939 * @param {!CodeMirror} codeMirror
1940 * @param {?WebInspector.CompletionDictionary} dictionary
1942 WebInspector.CodeMirrorTextEditor.AutocompleteController = function(textEditor, codeMirror, dictionary)
1944 this._textEditor = textEditor;
1945 this._codeMirror = codeMirror;
1947 this._onScroll = this._onScroll.bind(this);
1948 this._onCursorActivity = this._onCursorActivity.bind(this);
1949 this._changes = this._changes.bind(this);
1950 this._beforeChange = this._beforeChange.bind(this);
1951 this._blur = this._blur.bind(this);
1952 this._codeMirror.on("scroll", this._onScroll);
1953 this._codeMirror.on("cursorActivity", this._onCursorActivity);
1954 this._codeMirror.on("changes", this._changes);
1955 this._codeMirror.on("beforeChange", this._beforeChange);
1956 this._codeMirror.on("blur", this._blur);
1958 this._additionalWordChars = WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars;
1959 this._enabled = true;
1961 this._dictionary = dictionary;
1962 this._addTextToCompletionDictionary(this._textEditor.text());
1965 WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy = new WebInspector.CodeMirrorTextEditor.DummyAutocompleteController();
1966 WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars = {};
1967 WebInspector.CodeMirrorTextEditor._CSSAdditionalWordChars = { ".": true, "-": true };
1969 WebInspector.CodeMirrorTextEditor.AutocompleteController.prototype = {
1972 this._codeMirror.off("scroll", this._onScroll);
1973 this._codeMirror.off("cursorActivity", this._onCursorActivity);
1974 this._codeMirror.off("changes", this._changes);
1975 this._codeMirror.off("beforeChange", this._beforeChange);
1976 this._codeMirror.off("blur", this._blur);
1980 * @param {boolean} enabled
1982 setEnabled: function(enabled)
1984 if (enabled === this._enabled)
1986 this._enabled = enabled;
1988 this._dictionary.reset();
1990 this._addTextToCompletionDictionary(this._textEditor.text());
1994 * @param {string} mimeType
1996 setMimeType: function(mimeType)
1998 var additionalWordChars = mimeType.indexOf("css") !== -1 ? WebInspector.CodeMirrorTextEditor._CSSAdditionalWordChars : WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars;
1999 if (additionalWordChars !== this._additionalWordChars) {
2000 this._additionalWordChars = additionalWordChars;
2001 this._dictionary.reset();
2002 this._addTextToCompletionDictionary(this._textEditor.text());
2007 * @param {string} char
2010 _isWordChar: function(char)
2012 return WebInspector.TextUtils.isWordChar(char) || !!this._additionalWordChars[char];
2016 * @param {string} word
2019 _shouldProcessWordForAutocompletion: function(word)
2021 return !!word.length && (word[0] < '0' || word[0] > '9');
2025 * @param {string} text
2027 _addTextToCompletionDictionary: function(text)
2031 var words = WebInspector.TextUtils.textToWords(text, this._isWordChar.bind(this));
2032 for (var i = 0; i < words.length; ++i) {
2033 if (this._shouldProcessWordForAutocompletion(words[i]))
2034 this._dictionary.addWord(words[i]);
2039 * @param {string} text
2041 _removeTextFromCompletionDictionary: function(text)
2045 var words = WebInspector.TextUtils.textToWords(text, this._isWordChar.bind(this));
2046 for (var i = 0; i < words.length; ++i) {
2047 if (this._shouldProcessWordForAutocompletion(words[i]))
2048 this._dictionary.removeWord(words[i]);
2053 * @param {!CodeMirror} codeMirror
2054 * @param {!WebInspector.CodeMirrorTextEditor.BeforeChangeObject} changeObject
2056 _beforeChange: function(codeMirror, changeObject)
2060 this._updatedLines = this._updatedLines || {};
2061 for (var i = changeObject.from.line; i <= changeObject.to.line; ++i)
2062 this._updatedLines[i] = this._textEditor.line(i);
2066 * @param {!CodeMirror} codeMirror
2067 * @param {!Array.<!WebInspector.CodeMirrorTextEditor.ChangeObject>} changes
2069 _changes: function(codeMirror, changes)
2071 if (!changes.length || !this._enabled)
2074 if (this._updatedLines) {
2075 for (var lineNumber in this._updatedLines)
2076 this._removeTextFromCompletionDictionary(this._updatedLines[lineNumber]);
2077 delete this._updatedLines;
2080 var linesToUpdate = {};
2081 var singleCharInput = false;
2082 for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) {
2083 var changeObject = changes[changeIndex];
2084 singleCharInput = (changeObject.origin === "+input" && changeObject.text.length === 1 && changeObject.text[0].length === 1) ||
2085 (this._suggestBox && changeObject.origin === "+delete" && changeObject.removed.length === 1 && changeObject.removed[0].length === 1);
2087 var editInfo = this._textEditor._changeObjectToEditOperation(changeObject);
2088 for (var i = editInfo.newRange.startLine; i <= editInfo.newRange.endLine; ++i)
2089 linesToUpdate[i] = this._textEditor.line(i);
2091 for (var lineNumber in linesToUpdate)
2092 this._addTextToCompletionDictionary(linesToUpdate[lineNumber]);
2094 if (singleCharInput)
2095 this.autocomplete();
2100 this.finishAutocomplete();
2104 * @param {number} lineNumber
2105 * @param {number} columnNumber
2106 * @return {!WebInspector.TextRange}
2108 _autocompleteWordRange: function(lineNumber, columnNumber)
2110 return this._textEditor._wordRangeForCursorPosition(lineNumber, columnNumber, this._isWordChar.bind(this));
2114 * @param {!WebInspector.TextRange} mainSelection
2115 * @param {!Array.<!{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>} selections
2118 _validateSelectionsContexts: function(mainSelection, selections)
2120 var mainSelectionContext = this._textEditor.copyRange(mainSelection);
2121 for (var i = 0; i < selections.length; ++i) {
2122 var wordRange = this._autocompleteWordRange(selections[i].head.line, selections[i].head.ch);
2125 var context = this._textEditor.copyRange(wordRange);
2126 if (context !== mainSelectionContext)
2132 autocomplete: function()
2134 var dictionary = this._dictionary;
2135 if (this._codeMirror.somethingSelected()) {
2136 this.finishAutocomplete();
2140 var selections = this._codeMirror.listSelections().slice();
2141 var topSelection = selections.shift();
2142 var cursor = topSelection.head;
2143 var substituteRange = this._autocompleteWordRange(cursor.line, cursor.ch);
2144 if (!substituteRange || substituteRange.startColumn === cursor.ch || !this._validateSelectionsContexts(substituteRange, selections)) {
2145 this.finishAutocomplete();
2149 var prefixRange = substituteRange.clone();
2150 prefixRange.endColumn = cursor.ch;
2152 var substituteWord = this._textEditor.copyRange(substituteRange);
2153 var hasPrefixInDictionary = dictionary.hasWord(substituteWord);
2154 if (hasPrefixInDictionary)
2155 dictionary.removeWord(substituteWord);
2156 var wordsWithPrefix = dictionary.wordsWithPrefix(this._textEditor.copyRange(prefixRange));
2157 if (hasPrefixInDictionary)
2158 dictionary.addWord(substituteWord);
2160 function sortSuggestions(a, b)
2162 return dictionary.wordCount(b) - dictionary.wordCount(a) || a.length - b.length;
2165 wordsWithPrefix.sort(sortSuggestions);
2167 if (!this._suggestBox)
2168 this._suggestBox = new WebInspector.SuggestBox(this, 6);
2169 var oldPrefixRange = this._prefixRange;
2170 this._prefixRange = prefixRange;
2171 if (!oldPrefixRange || prefixRange.startLine !== oldPrefixRange.startLine || prefixRange.startColumn !== oldPrefixRange.startColumn)
2172 this._updateAnchorBox();
2173 this._suggestBox.updateSuggestions(this._anchorBox, wordsWithPrefix, 0, true, this._textEditor.copyRange(prefixRange));
2174 if (!this._suggestBox.visible())
2175 this.finishAutocomplete();
2178 finishAutocomplete: function()
2180 if (!this._suggestBox)
2182 this._suggestBox.hide();
2183 this._suggestBox = null;
2184 this._prefixRange = null;
2185 this._anchorBox = null;
2192 keyDown: function(e)
2194 if (!this._suggestBox)
2196 if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
2197 this.finishAutocomplete();
2200 if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Tab.code) {
2201 this._suggestBox.acceptSuggestion();
2202 this.finishAutocomplete();
2205 return this._suggestBox.keyPressed(e);
2209 * @param {string} suggestion
2210 * @param {boolean=} isIntermediateSuggestion
2212 applySuggestion: function(suggestion, isIntermediateSuggestion)
2214 this._currentSuggestion = suggestion;
2217 acceptSuggestion: function()
2219 if (this._prefixRange.endColumn - this._prefixRange.startColumn === this._currentSuggestion.length)
2222 var selections = this._codeMirror.listSelections().slice();
2223 var prefixLength = this._prefixRange.endColumn - this._prefixRange.startColumn;
2224 for (var i = selections.length - 1; i >= 0; --i) {
2225 var start = selections[i].head;
2226 var end = new CodeMirror.Pos(start.line, start.ch - prefixLength);
2227 this._codeMirror.replaceRange(this._currentSuggestion, start, end, "+autocomplete");
2231 _onScroll: function()
2233 if (!this._suggestBox)
2235 var cursor = this._codeMirror.getCursor();
2236 var scrollInfo = this._codeMirror.getScrollInfo();
2237 var topmostLineNumber = this._codeMirror.lineAtHeight(scrollInfo.top, "local");
2238 var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
2239 if (cursor.line < topmostLineNumber || cursor.line > bottomLine)
2240 this.finishAutocomplete();
2242 this._updateAnchorBox();
2243 this._suggestBox.setPosition(this._anchorBox);
2247 _onCursorActivity: function()
2249 if (!this._suggestBox)
2251 var cursor = this._codeMirror.getCursor();
2252 if (cursor.line !== this._prefixRange.startLine || cursor.ch > this._prefixRange.endColumn || cursor.ch <= this._prefixRange.startColumn)
2253 this.finishAutocomplete();
2256 _updateAnchorBox: function()
2258 var line = this._prefixRange.startLine;
2259 var column = this._prefixRange.startColumn;
2260 var metrics = this._textEditor.cursorPositionToCoordinates(line, column);
2261 this._anchorBox = metrics ? new AnchorBox(metrics.x, metrics.y, 0, metrics.height) : null;
2267 * @param {!WebInspector.CodeMirrorTextEditor} textEditor
2268 * @param {!CodeMirror} codeMirror
2270 WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController = function(textEditor, codeMirror)
2272 this._textEditor = textEditor;
2273 this._codeMirror = codeMirror;
2276 WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController.prototype = {
2277 selectionWillChange: function()
2279 if (!this._muteSelectionListener)
2280 delete this._fullWordSelection;
2284 * @param {!Array.<!WebInspector.TextRange>} selections
2285 * @param {!WebInspector.TextRange} range
2288 _findRange: function(selections, range)
2290 for (var i = 0; i < selections.length; ++i) {
2291 if (range.equal(selections[i]))
2297 undoLastSelection: function()
2299 this._muteSelectionListener = true;
2300 this._codeMirror.execCommand("undoSelection");
2301 this._muteSelectionListener = false;
2304 selectNextOccurrence: function()
2306 var selections = this._textEditor.selections();
2307 var anyEmptySelection = false;
2308 for (var i = 0; i < selections.length; ++i) {
2309 var selection = selections[i];
2310 anyEmptySelection = anyEmptySelection || selection.isEmpty();
2311 if (selection.startLine !== selection.endLine)
2314 if (anyEmptySelection) {
2315 this._expandSelectionsToWords(selections);
2319 var last = selections[selections.length - 1];
2322 next = this._findNextOccurrence(next, !!this._fullWordSelection);
2323 } while (next && this._findRange(selections, next) && !next.equal(last));
2327 selections.push(next);
2329 this._muteSelectionListener = true;
2330 this._textEditor.setSelections(selections, selections.length - 1);
2331 delete this._muteSelectionListener;
2333 this._textEditor._revealLine(next.startLine);
2337 * @param {!Array.<!WebInspector.TextRange>} selections
2339 _expandSelectionsToWords: function(selections)
2341 var newSelections = [];
2342 for (var i = 0; i < selections.length; ++i) {
2343 var selection = selections[i];
2344 var startRangeWord = this._textEditor._wordRangeForCursorPosition(selection.startLine, selection.startColumn, WebInspector.TextUtils.isWordChar)
2345 || WebInspector.TextRange.createFromLocation(selection.startLine, selection.startColumn);
2346 var endRangeWord = this._textEditor._wordRangeForCursorPosition(selection.endLine, selection.endColumn, WebInspector.TextUtils.isWordChar)
2347 || WebInspector.TextRange.createFromLocation(selection.endLine, selection.endColumn);
2348 var newSelection = new WebInspector.TextRange(startRangeWord.startLine, startRangeWord.startColumn, endRangeWord.endLine, endRangeWord.endColumn);
2349 newSelections.push(newSelection);
2351 this._textEditor.setSelections(newSelections, newSelections.length - 1);
2352 this._fullWordSelection = true;
2356 * @param {!WebInspector.TextRange} range
2357 * @param {boolean} fullWord
2358 * @return {?WebInspector.TextRange}
2360 _findNextOccurrence: function(range, fullWord)
2362 range = range.normalize();
2363 var matchedLineNumber;
2364 var matchedColumnNumber;
2365 var textToFind = this._textEditor.copyRange(range);
2366 function findWordInLine(wordRegex, lineNumber, lineText, from, to)
2368 if (typeof matchedLineNumber === "number")
2370 wordRegex.lastIndex = from;
2371 var result = wordRegex.exec(lineText);
2372 if (!result || result.index + textToFind.length > to)
2374 matchedLineNumber = lineNumber;
2375 matchedColumnNumber = result.index;
2379 var iteratedLineNumber;
2380 function lineIterator(regex, lineHandle)
2382 if (findWordInLine(regex, iteratedLineNumber++, lineHandle.text, 0, lineHandle.text.length))
2386 var regexSource = textToFind.escapeForRegExp();
2388 regexSource = "\\b" + regexSource + "\\b";
2389 var wordRegex = new RegExp(regexSource, "g");
2390 var currentLineText = this._codeMirror.getLine(range.startLine);
2392 findWordInLine(wordRegex, range.startLine, currentLineText, range.endColumn, currentLineText.length);
2393 iteratedLineNumber = range.startLine + 1;
2394 this._codeMirror.eachLine(range.startLine + 1, this._codeMirror.lineCount(), lineIterator.bind(null, wordRegex));
2395 iteratedLineNumber = 0;
2396 this._codeMirror.eachLine(0, range.startLine, lineIterator.bind(null, wordRegex));
2397 findWordInLine(wordRegex, range.startLine, currentLineText, 0, range.startColumn);
2399 if (typeof matchedLineNumber !== "number")
2401 return new WebInspector.TextRange(matchedLineNumber, matchedColumnNumber, matchedLineNumber, matchedColumnNumber + textToFind.length);
2406 * @param {string} modeName
2407 * @param {string} tokenPrefix
2409 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens = function(modeName, tokenPrefix)
2411 var oldModeName = modeName + "-old";
2412 if (CodeMirror.modes[oldModeName])
2415 CodeMirror.defineMode(oldModeName, CodeMirror.modes[modeName]);
2416 CodeMirror.defineMode(modeName, modeConstructor);
2418 function modeConstructor(config, parserConfig)
2420 var innerConfig = {};
2421 for (var i in parserConfig)
2422 innerConfig[i] = parserConfig[i];
2423 innerConfig.name = oldModeName;
2424 var codeMirrorMode = CodeMirror.getMode(config, innerConfig);
2425 codeMirrorMode.name = modeName;
2426 codeMirrorMode.token = tokenOverride.bind(null, codeMirrorMode.token);
2427 return codeMirrorMode;
2430 function tokenOverride(superToken, stream, state)
2432 var token = superToken(stream, state);
2433 return token ? tokenPrefix + token.split(/ +/).join(" " + tokenPrefix) : token;
2437 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("css", "css-");
2438 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("javascript", "js-");
2439 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("xml", "xml-");
2442 var backgroundColor = InspectorFrontendHost.getSelectionBackgroundColor();
2443 var backgroundColorRule = backgroundColor ? ".CodeMirror .CodeMirror-selected { background-color: " + backgroundColor + ";}" : "";
2444 var foregroundColor = InspectorFrontendHost.getSelectionForegroundColor();
2445 var foregroundColorRule = foregroundColor ? ".CodeMirror .CodeMirror-selectedtext:not(.CodeMirror-persist-highlight) { color: " + foregroundColor + "!important;}" : "";
2446 if (!foregroundColorRule && !backgroundColorRule)
2449 var style = document.createElement("style");
2450 style.textContent = backgroundColorRule + foregroundColorRule;
2451 document.head.appendChild(style);