Home | History | Annotate | Download | only in source_frame
      1 /*
      2  * Copyright (C) 2012 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      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
     13  * distribution.
     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.
     17  *
     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.
     29  */
     30 
     31 /**
     32  * @constructor
     33  * @extends {WebInspector.VBox}
     34  * @implements {WebInspector.TextEditor}
     35  * @param {?string} url
     36  * @param {!WebInspector.TextEditorDelegate} delegate
     37  */
     38 WebInspector.CodeMirrorTextEditor = function(url, delegate)
     39 {
     40     WebInspector.VBox.call(this);
     41     this._delegate = delegate;
     42     this._url = url;
     43 
     44     this.registerRequiredCSS("cm/codemirror.css");
     45     this.registerRequiredCSS("cmdevtools.css");
     46 
     47     this._codeMirror = new window.CodeMirror(this.element, {
     48         lineNumbers: true,
     49         gutters: ["CodeMirror-linenumbers"],
     50         matchBrackets: true,
     51         smartIndent: false,
     52         styleSelectedText: true,
     53         electricChars: false
     54     });
     55     this._codeMirror._codeMirrorTextEditor = this;
     56 
     57     CodeMirror.keyMap["devtools-common"] = {
     58         "Left": "goCharLeft",
     59         "Right": "goCharRight",
     60         "Up": "goLineUp",
     61         "Down": "goLineDown",
     62         "End": "goLineEnd",
     63         "Home": "goLineStartSmart",
     64         "PageUp": "goPageUp",
     65         "PageDown": "goPageDown",
     66         "Delete": "delCharAfter",
     67         "Backspace": "delCharBefore",
     68         "Tab": "defaultTab",
     69         "Shift-Tab": "indentLess",
     70         "Enter": "smartNewlineAndIndent",
     71         "Ctrl-Space": "autocomplete",
     72         "Esc": "dismissMultipleSelections",
     73         "Ctrl-M": "gotoMatchingBracket"
     74     };
     75 
     76     CodeMirror.keyMap["devtools-pc"] = {
     77         "Ctrl-A": "selectAll",
     78         "Ctrl-Z": "undoAndReveal",
     79         "Shift-Ctrl-Z": "redoAndReveal",
     80         "Ctrl-Y": "redo",
     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"
     97     };
     98 
     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"
    119     };
    120 
    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();
    127 
    128     this._codeMirror.setOption("keyMap", WebInspector.isMac() ? "devtools-mac" : "devtools-pc");
    129 
    130     CodeMirror.commands.maybeAvoidSmartSingleQuotes = this._maybeAvoidSmartQuotes.bind(this, "'");
    131     CodeMirror.commands.maybeAvoidSmartDoubleQuotes = this._maybeAvoidSmartQuotes.bind(this, "\"");
    132     this._codeMirror.addKeyMap({
    133         "'": "maybeAvoidSmartSingleQuotes",
    134         "'\"'": "maybeAvoidSmartDoubleQuotes"
    135     });
    136 
    137     this._codeMirror.setOption("flattenSpans", false);
    138 
    139     this._codeMirror.setOption("maxHighlightLength", WebInspector.CodeMirrorTextEditor.maxHighlightLength);
    140     this._codeMirror.setOption("mode", null);
    141     this._codeMirror.setOption("crudeMeasuringFrom", 1000);
    142 
    143     this._shouldClearHistory = true;
    144     this._lineSeparator = "\n";
    145 
    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);
    151 
    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);
    160     /**
    161      * @this {WebInspector.CodeMirrorTextEditor}
    162      */
    163     function updateAnticipateJumpFlag(value)
    164     {
    165         this._isHandlingMouseDownEvent = value;
    166     }
    167     this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, true), true);
    168     this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, false), false);
    169 
    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;
    175 
    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;
    180 
    181     this._setupWhitespaceHighlight();
    182 }
    183 
    184 /** @typedef {{canceled: boolean, from: !CodeMirror.Pos, to: !CodeMirror.Pos, text: string, origin: string, cancel: function()}} */
    185 WebInspector.CodeMirrorTextEditor.BeforeChangeObject;
    186 
    187 /** @typedef {{from: !CodeMirror.Pos, to: !CodeMirror.Pos, origin: string, text: !Array.<string>, removed: !Array.<string>}} */
    188 WebInspector.CodeMirrorTextEditor.ChangeObject;
    189 
    190 WebInspector.CodeMirrorTextEditor.maxHighlightLength = 1000;
    191 
    192 /**
    193  * @param {!CodeMirror} codeMirror
    194  */
    195 WebInspector.CodeMirrorTextEditor.autocompleteCommand = function(codeMirror)
    196 {
    197     codeMirror._codeMirrorTextEditor._autocompleteController.autocomplete();
    198 }
    199 CodeMirror.commands.autocomplete = WebInspector.CodeMirrorTextEditor.autocompleteCommand;
    200 
    201 /**
    202  * @param {!CodeMirror} codeMirror
    203  */
    204 WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand = function(codeMirror)
    205 {
    206     codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.undoLastSelection();
    207 }
    208 CodeMirror.commands.undoLastSelection = WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand;
    209 
    210 /**
    211  * @param {!CodeMirror} codeMirror
    212  */
    213 WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand = function(codeMirror)
    214 {
    215     codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.selectNextOccurrence();
    216 }
    217 CodeMirror.commands.selectNextOccurrence = WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand;
    218 
    219 /**
    220  * @param {boolean} shift
    221  * @param {!CodeMirror} codeMirror
    222  */
    223 WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand = function(shift, codeMirror)
    224 {
    225     codeMirror._codeMirrorTextEditor._doCamelCaseMovement(-1, shift);
    226 }
    227 CodeMirror.commands.moveCamelLeft = WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand.bind(null, false);
    228 CodeMirror.commands.selectCamelLeft = WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand.bind(null, true);
    229 
    230 /**
    231  * @param {boolean} shift
    232  * @param {!CodeMirror} codeMirror
    233  */
    234 WebInspector.CodeMirrorTextEditor.moveCamelRightCommand = function(shift, codeMirror)
    235 {
    236     codeMirror._codeMirrorTextEditor._doCamelCaseMovement(1, shift);
    237 }
    238 CodeMirror.commands.moveCamelRight = WebInspector.CodeMirrorTextEditor.moveCamelRightCommand.bind(null, false);
    239 CodeMirror.commands.selectCamelRight = WebInspector.CodeMirrorTextEditor.moveCamelRightCommand.bind(null, true);
    240 
    241 /**
    242  * @param {!CodeMirror} codeMirror
    243  */
    244 CodeMirror.commands.smartNewlineAndIndent = function(codeMirror)
    245 {
    246     codeMirror.operation(innerSmartNewlineAndIndent.bind(null, codeMirror));
    247 
    248     function innerSmartNewlineAndIndent(codeMirror)
    249     {
    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)));
    258         }
    259         codeMirror.replaceSelections(replacements);
    260         codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
    261     }
    262 }
    263 
    264 /**
    265  * @param {!CodeMirror} codeMirror
    266  */
    267 CodeMirror.commands.gotoMatchingBracket = function(codeMirror)
    268 {
    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);
    279         }
    280         updatedSelections.push({
    281             anchor: updatedHead,
    282             head: updatedHead
    283         });
    284     }
    285     codeMirror.setSelections(updatedSelections);
    286 }
    287 
    288 /**
    289  * @param {!CodeMirror} codemirror
    290  */
    291 CodeMirror.commands.undoAndReveal = function(codemirror)
    292 {
    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();
    298 }
    299 
    300 /**
    301  * @param {!CodeMirror} codemirror
    302  */
    303 CodeMirror.commands.redoAndReveal = function(codemirror)
    304 {
    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();
    310 }
    311 
    312 /**
    313  * @return {!Object|undefined}
    314  */
    315 CodeMirror.commands.dismissMultipleSelections = function(codemirror)
    316 {
    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);
    326         return;
    327     }
    328 
    329     codemirror.setSelection(selection.anchor, selection.head, {scroll: false});
    330     codemirror._codeMirrorTextEditor._revealLine(selection.anchor.line);
    331 }
    332 
    333 WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold = 2000;
    334 WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan = 16;
    335 WebInspector.CodeMirrorTextEditor.MaxEditableTextSize = 1024 * 1024 * 10;
    336 
    337 WebInspector.CodeMirrorTextEditor.prototype = {
    338     /**
    339      * @param {string} quoteCharacter
    340      * @return {*}
    341      */
    342     _maybeAvoidSmartQuotes: function(quoteCharacter)
    343     {
    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;
    349 
    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);
    356         if (tokenValue[0] === tokenValue[tokenValue.length - 1] && (tokenValue[0] === "'" || tokenValue[0] === "\""))
    357             return CodeMirror.Pass;
    358         this._codeMirror.replaceSelection(quoteCharacter);
    359     },
    360 
    361     _onKeyHandled: function()
    362     {
    363         WebInspector.shortcutRegistry.dismissPendingShortcutAction();
    364     },
    365 
    366     _onAutoAppendedSpaces: function()
    367     {
    368         this._autoAppendedSpaces = this._autoAppendedSpaces || [];
    369         for (var i = 0; i < this._autoAppendedSpaces.length; ++i) {
    370             var position = this._autoAppendedSpaces[i].resolve();
    371             if (!position)
    372                 continue;
    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));
    376         }
    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));
    382         }
    383     },
    384 
    385     /**
    386      * @param {number} lineNumber
    387      * @param {number} lineLength
    388      * @param {number} charNumber
    389      * @return {{lineNumber: number, columnNumber: number}}
    390      */
    391     _normalizePositionForOverlappingColumn: function(lineNumber, lineLength, charNumber)
    392     {
    393         var linesCount = this._codeMirror.lineCount();
    394         var columnNumber = charNumber;
    395         if (charNumber < 0 && lineNumber > 0) {
    396             --lineNumber;
    397             columnNumber = this.line(lineNumber).length;
    398         } else if (charNumber >= lineLength && lineNumber < linesCount - 1) {
    399             ++lineNumber;
    400             columnNumber = 0;
    401         } else {
    402             columnNumber = Number.constrain(charNumber, 0, lineLength);
    403         }
    404         return {
    405             lineNumber: lineNumber,
    406             columnNumber: columnNumber
    407         };
    408     },
    409 
    410     /**
    411      * @param {number} lineNumber
    412      * @param {number} columnNumber
    413      * @param {number} direction
    414      * @return {{lineNumber: number, columnNumber: number}}
    415      */
    416     _camelCaseMoveFromPosition: function(lineNumber, columnNumber, direction)
    417     {
    418         /**
    419          * @param {number} charNumber
    420          * @param {number} length
    421          * @return {boolean}
    422          */
    423         function valid(charNumber, length)
    424         {
    425             return charNumber >= 0 && charNumber < length;
    426         }
    427 
    428         /**
    429          * @param {string} text
    430          * @param {number} charNumber
    431          * @return {boolean}
    432          */
    433         function isWordStart(text, charNumber)
    434         {
    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]);
    440         }
    441 
    442         /**
    443          * @param {string} text
    444          * @param {number} charNumber
    445          * @return {boolean}
    446          */
    447         function isWordEnd(text, charNumber)
    448         {
    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]);
    454         }
    455 
    456         /**
    457          * @param {number} lineNumber
    458          * @param {number} lineLength
    459          * @param {number} columnNumber
    460          * @return {{lineNumber: number, columnNumber: number}}
    461          */
    462         function constrainPosition(lineNumber, lineLength, columnNumber)
    463         {
    464             return {
    465                 lineNumber: lineNumber,
    466                 columnNumber: Number.constrain(columnNumber, 0, lineLength)
    467             };
    468         }
    469 
    470         var text = this.line(lineNumber);
    471         var length = text.length;
    472 
    473         if ((columnNumber === length && direction === 1)
    474             || (columnNumber === 0 && direction === -1))
    475             return this._normalizePositionForOverlappingColumn(lineNumber, length, columnNumber + direction);
    476 
    477         var charNumber = direction === 1 ? columnNumber : columnNumber - 1;
    478 
    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);
    484 
    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);
    490             return {
    491                 lineNumber: lineNumber,
    492                 columnNumber: direction === -1 ? charNumber + 1 : charNumber
    493             };
    494         }
    495 
    496         charNumber += direction;
    497         while (valid(charNumber, length) && !isWordStart(text, charNumber) && !isWordEnd(text, charNumber) && WebInspector.TextUtils.isWordChar(text[charNumber]))
    498             charNumber += direction;
    499 
    500         if (!valid(charNumber, length))
    501             return constrainPosition(lineNumber, length, charNumber);
    502         if (isWordStart(text, charNumber) || isWordEnd(text, charNumber)) {
    503             return {
    504                 lineNumber: lineNumber,
    505                 columnNumber: charNumber
    506             };
    507         }
    508 
    509         return {
    510             lineNumber: lineNumber,
    511             columnNumber: direction === -1 ? charNumber + 1 : charNumber
    512         };
    513     },
    514 
    515     /**
    516      * @param {number} direction
    517      * @param {boolean} shift
    518      */
    519     _doCamelCaseMovement: function(direction, shift)
    520     {
    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;
    527             if (!shift)
    528                 selections[i] = selection.collapseToEnd();
    529         }
    530         this.setSelections(selections);
    531     },
    532 
    533     dispose: function()
    534     {
    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);
    539     },
    540 
    541     _enableBracketMatchingIfNeeded: function()
    542     {
    543         this._codeMirror.setOption("autoCloseBrackets", WebInspector.settings.textEditorBracketMatching.get() ? { explode: false } : false);
    544     },
    545 
    546     wasShown: function()
    547     {
    548         if (this._wasOnceShown)
    549             return;
    550         this._wasOnceShown = true;
    551         this._codeMirror.refresh();
    552     },
    553 
    554     _guessIndentationLevel: function()
    555     {
    556         var tabRegex = /^\t+/;
    557         var tabLines = 0;
    558         var indents = {};
    559         function processLine(lineHandle)
    560         {
    561             var text = lineHandle.text;
    562             if (text.length === 0 || !WebInspector.TextUtils.isSpaceChar(text[0]))
    563                 return;
    564             if (tabRegex.test(text)) {
    565                 ++tabLines;
    566                 return;
    567             }
    568             var i = 0;
    569             while (i < text.length && WebInspector.TextUtils.isSpaceChar(text[i]))
    570                 ++i;
    571             if (i % 2 !== 0)
    572                 return;
    573             indents[i] = 1 + (indents[i] || 0);
    574         }
    575         this._codeMirror.eachLine(0, 1000, processLine);
    576 
    577         var onePercentFilterThreshold = this.linesCount / 100;
    578         if (tabLines && tabLines > onePercentFilterThreshold)
    579             return "\t";
    580         var minimumIndent = Infinity;
    581         for (var i in indents) {
    582             if (indents[i] < onePercentFilterThreshold)
    583                 continue;
    584             var indent = parseInt(i, 10);
    585             if (minimumIndent > indent)
    586                 minimumIndent = indent;
    587         }
    588         if (minimumIndent === Infinity)
    589             return WebInspector.settings.textEditorIndent.get();
    590         return new Array(minimumIndent + 1).join(" ");
    591     },
    592 
    593     _updateEditorIndentation: function()
    594     {
    595         var extraKeys = {};
    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);
    602         } else {
    603             this._codeMirror.setOption("indentWithTabs", false);
    604             this._codeMirror.setOption("indentUnit", indent.length);
    605             extraKeys.Tab = function(codeMirror)
    606             {
    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());
    611             }
    612         }
    613         this._codeMirror.setOption("extraKeys", extraKeys);
    614         this._indentationLevel = indent;
    615     },
    616 
    617     /**
    618      * @return {string}
    619      */
    620     indent: function()
    621     {
    622         return this._indentationLevel;
    623     },
    624 
    625     /**
    626      * @return {boolean}
    627      */
    628     _isSearchActive: function()
    629     {
    630         return !!this._tokenHighlighter.highlightedRegex();
    631     },
    632 
    633     /**
    634      * @param {!RegExp} regex
    635      * @param {?WebInspector.TextRange} range
    636      */
    637     highlightSearchResults: function(regex, range)
    638     {
    639         /**
    640          * @this {WebInspector.CodeMirrorTextEditor}
    641          */
    642         function innerHighlightRegex()
    643         {
    644             if (range) {
    645                 this._revealLine(range.startLine);
    646                 if (range.endColumn > WebInspector.CodeMirrorTextEditor.maxHighlightLength)
    647                     this.setSelection(range);
    648                 else
    649                     this.setSelection(WebInspector.TextRange.createFromLocation(range.startLine, range.startColumn));
    650             } else {
    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());
    653             }
    654             this._tokenHighlighter.highlightSearchResults(regex, range);
    655         }
    656         if (!this._selectionBeforeSearch)
    657             this._selectionBeforeSearch = this.selection();
    658         this._codeMirror.operation(innerHighlightRegex.bind(this));
    659     },
    660 
    661     cancelSearchResultsHighlight: function()
    662     {
    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;
    667         }
    668     },
    669 
    670     undo: function()
    671     {
    672         this._codeMirror.undo();
    673     },
    674 
    675     redo: function()
    676     {
    677         this._codeMirror.redo();
    678     },
    679 
    680     _setupWhitespaceHighlight: function()
    681     {
    682         if (WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected || !WebInspector.settings.showWhitespacesInEditor.get())
    683             return;
    684         WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected = true;
    685         const classBase = ".show-whitespaces .CodeMirror .cm-whitespace-";
    686         const spaceChar = "";
    687         var spaceChars = "";
    688         var rules = "";
    689         for (var i = 1; i <= WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan; ++i) {
    690             spaceChars += spaceChar;
    691             var rule = classBase + i + "::before { content: '" + spaceChars + "';}\n";
    692             rules += rule;
    693         }
    694         var style = document.createElement("style");
    695         style.textContent = rules;
    696         document.head.appendChild(style);
    697     },
    698 
    699     _handleKeyDown: function(e)
    700     {
    701         if (this._autocompleteController.keyDown(e))
    702             e.consume(true);
    703     },
    704 
    705     _handlePostKeyDown: function(e)
    706     {
    707         if (e.defaultPrevented)
    708             e.consume(true);
    709     },
    710 
    711     /**
    712      * @param {?WebInspector.CompletionDictionary} dictionary
    713      */
    714     setCompletionDictionary: function(dictionary)
    715     {
    716         this._autocompleteController.dispose();
    717         if (dictionary)
    718             this._autocompleteController = new WebInspector.CodeMirrorTextEditor.AutocompleteController(this, this._codeMirror, dictionary);
    719         else
    720             this._autocompleteController = WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy;
    721     },
    722 
    723     /**
    724      * @param {number} lineNumber
    725      * @param {number} column
    726      * @return {?{x: number, y: number, height: number}}
    727      */
    728     cursorPositionToCoordinates: function(lineNumber, column)
    729     {
    730         if (lineNumber >= this._codeMirror.lineCount() || lineNumber < 0 || column < 0 || column > this._codeMirror.getLine(lineNumber).length)
    731             return null;
    732 
    733         var metrics = this._codeMirror.cursorCoords(new CodeMirror.Pos(lineNumber, column));
    734 
    735         return {
    736             x: metrics.left,
    737             y: metrics.top,
    738             height: metrics.bottom - metrics.top
    739         };
    740     },
    741 
    742     /**
    743      * @param {number} x
    744      * @param {number} y
    745      * @return {?WebInspector.TextRange}
    746      */
    747     coordinatesToCursorPosition: function(x, y)
    748     {
    749         var element = document.elementFromPoint(x, y);
    750         if (!element || !element.isSelfOrDescendant(this._codeMirror.getWrapperElement()))
    751             return null;
    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)
    755             return null;
    756         var coords = this._codeMirror.coordsChar({left: x, top: y});
    757         return WebInspector.CodeMirrorUtils.toRange(coords, coords);
    758     },
    759 
    760     /**
    761      * @param {number} lineNumber
    762      * @param {number} column
    763      * @return {?{startColumn: number, endColumn: number, type: string}}
    764      */
    765     tokenAtTextPosition: function(lineNumber, column)
    766     {
    767         if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
    768             return null;
    769         var token = this._codeMirror.getTokenAt(new CodeMirror.Pos(lineNumber, (column || 0) + 1));
    770         if (!token || !token.type)
    771             return null;
    772         return {
    773             startColumn: token.start,
    774             endColumn: token.end,
    775             type: token.type
    776         };
    777     },
    778 
    779     /**
    780      * @param {!WebInspector.TextRange} textRange
    781      * @return {string}
    782      */
    783     copyRange: function(textRange)
    784     {
    785         var pos = WebInspector.CodeMirrorUtils.toPos(textRange.normalize());
    786         return this._codeMirror.getRange(pos.start, pos.end);
    787     },
    788 
    789     /**
    790      * @return {boolean}
    791      */
    792     isClean: function()
    793     {
    794         return this._codeMirror.isClean();
    795     },
    796 
    797     markClean: function()
    798     {
    799         this._codeMirror.markClean();
    800     },
    801 
    802     _hasLongLines: function()
    803     {
    804         function lineIterator(lineHandle)
    805         {
    806             if (lineHandle.text.length > WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold)
    807                 hasLongLines = true;
    808             return hasLongLines;
    809         }
    810         var hasLongLines = false;
    811         this._codeMirror.eachLine(lineIterator);
    812         return hasLongLines;
    813     },
    814 
    815     /**
    816      * @param {string} mimeType
    817      * @return {string}
    818      */
    819     _whitespaceOverlayMode: function(mimeType)
    820     {
    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])
    824             return modeName;
    825 
    826         function modeConstructor(config, parserConfig)
    827         {
    828             function nextToken(stream)
    829             {
    830                 if (stream.peek() === " ") {
    831                     var spaces = 0;
    832                     while (spaces < WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan && stream.peek() === " ") {
    833                         ++spaces;
    834                         stream.next();
    835                     }
    836                     return "whitespace whitespace-" + spaces;
    837                 }
    838                 while (!stream.eol() && stream.peek() !== " ")
    839                     stream.next();
    840                 return null;
    841             }
    842             var whitespaceMode = {
    843                 token: nextToken
    844             };
    845             return CodeMirror.overlayMode(CodeMirror.getMode(config, mimeType), whitespaceMode, false);
    846         }
    847         CodeMirror.defineMode(modeName, modeConstructor);
    848         return modeName;
    849     },
    850 
    851     _enableLongLinesMode: function()
    852     {
    853         this._codeMirror.setOption("styleSelectedText", false);
    854         this._longLinesMode = true;
    855     },
    856 
    857     _disableLongLinesMode: function()
    858     {
    859         this._codeMirror.setOption("styleSelectedText", true);
    860         this._longLinesMode = false;
    861     },
    862 
    863     _updateCodeMirrorMode: function()
    864     {
    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);
    869     },
    870 
    871     /**
    872      * @param {string} mimeType
    873      */
    874     setMimeType: function(mimeType)
    875     {
    876         this._mimeType = mimeType;
    877         if (this._hasLongLines())
    878             this._enableLongLinesMode();
    879         else
    880             this._disableLongLinesMode();
    881         this._updateCodeMirrorMode();
    882         this._autocompleteController.setMimeType(mimeType);
    883     },
    884 
    885     /**
    886      * @param {boolean} readOnly
    887      */
    888     setReadOnly: function(readOnly)
    889     {
    890         this.element.classList.toggle("CodeMirror-readonly", readOnly)
    891         this._codeMirror.setOption("readOnly", readOnly);
    892     },
    893 
    894     /**
    895      * @return {boolean}
    896      */
    897     readOnly: function()
    898     {
    899         return !!this._codeMirror.getOption("readOnly");
    900     },
    901 
    902     /**
    903      * @param {!Object} highlightDescriptor
    904      */
    905     removeHighlight: function(highlightDescriptor)
    906     {
    907         highlightDescriptor.clear();
    908     },
    909 
    910     /**
    911      * @param {!WebInspector.TextRange} range
    912      * @param {string} cssClass
    913      * @return {!Object}
    914      */
    915     highlightRange: function(range, cssClass)
    916     {
    917         cssClass = "CodeMirror-persist-highlight " + cssClass;
    918         var pos = WebInspector.CodeMirrorUtils.toPos(range);
    919         ++pos.end.ch;
    920         return this._codeMirror.markText(pos.start, pos.end, {
    921             className: cssClass,
    922             startStyle: cssClass + "-start",
    923             endStyle: cssClass + "-end"
    924         });
    925     },
    926 
    927     /**
    928      * @return {!Element}
    929      */
    930     defaultFocusedElement: function()
    931     {
    932         return this.element;
    933     },
    934 
    935     focus: function()
    936     {
    937         this._codeMirror.focus();
    938     },
    939 
    940     _handleElementFocus: function()
    941     {
    942         this._codeMirror.focus();
    943     },
    944 
    945     beginUpdates: function()
    946     {
    947         ++this._nestedUpdatesCounter;
    948     },
    949 
    950     endUpdates: function()
    951     {
    952         if (!--this._nestedUpdatesCounter)
    953             this._codeMirror.refresh();
    954     },
    955 
    956     /**
    957      * @param {number} lineNumber
    958      */
    959     _revealLine: function(lineNumber)
    960     {
    961         this._innerRevealLine(lineNumber, this._codeMirror.getScrollInfo());
    962     },
    963 
    964     /**
    965      * @param {number} lineNumber
    966      * @param {!{left: number, top: number, width: number, height: number, clientWidth: number, clientHeight: number}} scrollInfo
    967      */
    968     _innerRevealLine: function(lineNumber, scrollInfo)
    969     {
    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));
    979         }
    980     },
    981 
    982     _gutterClick: function(instance, lineNumber, gutter, event)
    983     {
    984         this.dispatchEventToListeners(WebInspector.TextEditor.Events.GutterClick, { lineNumber: lineNumber, event: event });
    985     },
    986 
    987     _contextMenu: function(event)
    988     {
    989         var contextMenu = new WebInspector.ContextMenu(event);
    990         var target = event.target.enclosingNodeOrSelfWithClass("CodeMirror-gutter-elt");
    991         if (target)
    992             this._delegate.populateLineGutterContextMenu(contextMenu, parseInt(target.textContent, 10) - 1);
    993         else
    994             this._delegate.populateTextAreaContextMenu(contextMenu, 0);
    995         contextMenu.appendApplicableItems(this);
    996         contextMenu.show();
    997     },
    998 
    999     /**
   1000      * @param {number} lineNumber
   1001      * @param {boolean} disabled
   1002      * @param {boolean} conditional
   1003      */
   1004     addBreakpoint: function(lineNumber, disabled, conditional)
   1005     {
   1006         if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
   1007             return;
   1008         var className = "cm-breakpoint" + (conditional ? " cm-breakpoint-conditional" : "") + (disabled ? " cm-breakpoint-disabled" : "");
   1009         this._codeMirror.addLineClass(lineNumber, "wrap", className);
   1010     },
   1011 
   1012     /**
   1013      * @param {number} lineNumber
   1014      */
   1015     removeBreakpoint: function(lineNumber)
   1016     {
   1017         if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
   1018             return;
   1019         var wrapClasses = this._codeMirror.getLineHandle(lineNumber).wrapClass;
   1020         if (!wrapClasses)
   1021             return;
   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]);
   1026         }
   1027     },
   1028 
   1029     /**
   1030      * @param {number} lineNumber
   1031      */
   1032     setExecutionLine: function(lineNumber)
   1033     {
   1034         this.clearPositionHighlight();
   1035         this._executionLine = this._codeMirror.getLineHandle(lineNumber);
   1036         if (!this._executionLine)
   1037             return;
   1038         this._codeMirror.addLineClass(this._executionLine, "wrap", "cm-execution-line");
   1039     },
   1040 
   1041     clearExecutionLine: function()
   1042     {
   1043         this.clearPositionHighlight();
   1044         if (this._executionLine)
   1045             this._codeMirror.removeLineClass(this._executionLine, "wrap", "cm-execution-line");
   1046         delete this._executionLine;
   1047     },
   1048 
   1049     /**
   1050      * @param {number} lineNumber
   1051      * @param {string} className
   1052      * @param {boolean} toggled
   1053      */
   1054     toggleLineClass: function(lineNumber, className, toggled)
   1055     {
   1056         var lineHandle = this._codeMirror.getLineHandle(lineNumber);
   1057         if (!lineHandle)
   1058             return;
   1059         if (toggled)
   1060             this._codeMirror.addLineClass(lineHandle, "wrap", className);
   1061         else
   1062             this._codeMirror.removeLineClass(lineHandle, "wrap", className);
   1063     },
   1064 
   1065     /**
   1066      * @param {number} lineNumber
   1067      * @param {!Element} element
   1068      */
   1069     addDecoration: function(lineNumber, element)
   1070     {
   1071         var widget = this._codeMirror.addLineWidget(lineNumber, element);
   1072         this._elementToWidget.set(element, widget);
   1073     },
   1074 
   1075     /**
   1076      * @param {number} lineNumber
   1077      * @param {!Element} element
   1078      */
   1079     removeDecoration: function(lineNumber, element)
   1080     {
   1081         var widget = this._elementToWidget.remove(element);
   1082         if (widget)
   1083             this._codeMirror.removeLineWidget(widget);
   1084     },
   1085 
   1086     /**
   1087      * @param {number} lineNumber 0-based
   1088      * @param {number=} columnNumber
   1089      * @param {boolean=} shouldHighlight
   1090      */
   1091     revealPosition: function(lineNumber, columnNumber, shouldHighlight)
   1092     {
   1093         lineNumber = Number.constrain(lineNumber, 0, this._codeMirror.lineCount() - 1);
   1094         if (typeof columnNumber !== "number")
   1095             columnNumber = 0;
   1096         columnNumber = Number.constrain(columnNumber, 0, this._codeMirror.getLine(lineNumber).length);
   1097 
   1098         this.clearPositionHighlight();
   1099         this._highlightedLine = this._codeMirror.getLineHandle(lineNumber);
   1100         if (!this._highlightedLine)
   1101           return;
   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);
   1106         }
   1107         this.setSelection(WebInspector.TextRange.createFromLocation(lineNumber, columnNumber));
   1108     },
   1109 
   1110     clearPositionHighlight: function()
   1111     {
   1112         if (this._clearHighlightTimeout)
   1113             clearTimeout(this._clearHighlightTimeout);
   1114         delete this._clearHighlightTimeout;
   1115 
   1116         if (this._highlightedLine)
   1117             this._codeMirror.removeLineClass(this._highlightedLine, null, "cm-highlight");
   1118         delete this._highlightedLine;
   1119     },
   1120 
   1121     /**
   1122      * @return {!Array.<!Element>}
   1123      */
   1124     elementsToRestoreScrollPositionsFor: function()
   1125     {
   1126         return [];
   1127     },
   1128 
   1129     /**
   1130      * @param {!WebInspector.TextEditor} textEditor
   1131      */
   1132     inheritScrollPositions: function(textEditor)
   1133     {
   1134     },
   1135 
   1136     /**
   1137      * @param {number} width
   1138      * @param {number} height
   1139      */
   1140     _updatePaddingBottom: function(width, height)
   1141     {
   1142         var scrollInfo = this._codeMirror.getScrollInfo();
   1143         var newPaddingBottom;
   1144         var linesElement = this.element.firstElementChild.querySelector(".CodeMirror-lines");
   1145         var lineCount = this._codeMirror.lineCount();
   1146         if (lineCount <= 1)
   1147             newPaddingBottom = 0;
   1148         else
   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);
   1153     },
   1154 
   1155     _resizeEditor: function()
   1156     {
   1157         var parentElement = this.element.parentElement;
   1158         if (!parentElement || !this.isShowing())
   1159             return;
   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);
   1167     },
   1168 
   1169     onResize: function()
   1170     {
   1171         this._autocompleteController.finishAutocomplete();
   1172         this._resizeEditor();
   1173     },
   1174 
   1175     /**
   1176      * @param {!WebInspector.TextRange} range
   1177      * @param {string} text
   1178      * @return {!WebInspector.TextRange}
   1179      */
   1180     editRange: function(range, text)
   1181     {
   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();
   1188         return newRange;
   1189     },
   1190 
   1191     /**
   1192      * @param {number} lineNumber
   1193      * @param {number} column
   1194      * @param {function(string):boolean} isWordChar
   1195      * @return {!WebInspector.TextRange}
   1196      */
   1197     _wordRangeForCursorPosition: function(lineNumber, column, isWordChar)
   1198     {
   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)))
   1204                 --wordStart;
   1205         }
   1206         var wordEnd = column;
   1207         while (wordEnd < line.length && isWordChar(line.charAt(wordEnd)))
   1208             ++wordEnd;
   1209         return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, wordEnd);
   1210     },
   1211 
   1212     /**
   1213      * @param {!WebInspector.CodeMirrorTextEditor.ChangeObject} changeObject
   1214      * @return {{oldRange: !WebInspector.TextRange, newRange: !WebInspector.TextRange}}
   1215      */
   1216     _changeObjectToEditOperation: function(changeObject)
   1217     {
   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;
   1227         } else {
   1228             newRange.endLine = newRange.startLine + linesAdded - 1;
   1229             newRange.endColumn = changeObject.text[linesAdded - 1].length;
   1230         }
   1231         return {
   1232             oldRange: oldRange,
   1233             newRange: newRange
   1234         };
   1235     },
   1236 
   1237     /**
   1238      * @param {!CodeMirror} codeMirror
   1239      * @param {!Array.<!WebInspector.CodeMirrorTextEditor.ChangeObject>} changes
   1240      */
   1241     _changes: function(codeMirror, changes)
   1242     {
   1243         if (!changes.length)
   1244             return;
   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();
   1254 
   1255         for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) {
   1256             var changeObject = changes[changeIndex];
   1257 
   1258             var editInfo = this._changeObjectToEditOperation(changeObject);
   1259             if (!this._muteTextChangedEvent)
   1260                 this._delegate.onTextChanged(editInfo.oldRange, editInfo.newRange);
   1261         }
   1262     },
   1263 
   1264     _cursorActivity: function()
   1265     {
   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));
   1271     },
   1272 
   1273     /**
   1274      * @param {!CodeMirror} codeMirror
   1275      * @param {{ranges: !Array.<{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>}} selection
   1276      */
   1277     _beforeSelectionChange: function(codeMirror, selection)
   1278     {
   1279         this._selectNextOccurrenceController.selectionWillChange();
   1280         if (!this._isHandlingMouseDownEvent)
   1281             return;
   1282         if (!selection.ranges.length)
   1283             return;
   1284         var primarySelection = selection.ranges[0];
   1285         this._reportJump(this.selection(), WebInspector.CodeMirrorUtils.toRange(primarySelection.anchor, primarySelection.head));
   1286     },
   1287 
   1288     /**
   1289      * @param {?WebInspector.TextRange} from
   1290      * @param {?WebInspector.TextRange} to
   1291      */
   1292     _reportJump: function(from, to)
   1293     {
   1294         if (from && to && from.equal(to))
   1295             return;
   1296         this._delegate.onJumpToPosition(from, to);
   1297     },
   1298 
   1299     _scroll: function()
   1300     {
   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);
   1305     },
   1306 
   1307     _focus: function()
   1308     {
   1309         this._delegate.editorFocused();
   1310     },
   1311 
   1312     /**
   1313      * @param {number} lineNumber
   1314      */
   1315     scrollToLine: function(lineNumber)
   1316     {
   1317         var pos = new CodeMirror.Pos(lineNumber, 0);
   1318         var coords = this._codeMirror.charCoords(pos, "local");
   1319         this._codeMirror.scrollTo(0, coords.top);
   1320     },
   1321 
   1322     /**
   1323      * @return {number}
   1324      */
   1325     firstVisibleLine: function()
   1326     {
   1327         return this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local");
   1328     },
   1329 
   1330     /**
   1331      * @return {number}
   1332      */
   1333     lastVisibleLine: function()
   1334     {
   1335         var scrollInfo = this._codeMirror.getScrollInfo();
   1336         return this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
   1337     },
   1338 
   1339     /**
   1340      * @return {!WebInspector.TextRange}
   1341      */
   1342     selection: function()
   1343     {
   1344         var start = this._codeMirror.getCursor("anchor");
   1345         var end = this._codeMirror.getCursor("head");
   1346 
   1347         return WebInspector.CodeMirrorUtils.toRange(start, end);
   1348     },
   1349 
   1350     /**
   1351      * @return {!Array.<!WebInspector.TextRange>}
   1352      */
   1353     selections: function()
   1354     {
   1355         var selectionList = this._codeMirror.listSelections();
   1356         var result = [];
   1357         for (var i = 0; i < selectionList.length; ++i) {
   1358             var selection = selectionList[i];
   1359             result.push(WebInspector.CodeMirrorUtils.toRange(selection.anchor, selection.head));
   1360         }
   1361         return result;
   1362     },
   1363 
   1364     /**
   1365      * @return {?WebInspector.TextRange}
   1366      */
   1367     lastSelection: function()
   1368     {
   1369         return this._lastSelection;
   1370     },
   1371 
   1372     /**
   1373      * @param {!WebInspector.TextRange} textRange
   1374      */
   1375     setSelection: function(textRange)
   1376     {
   1377         this._lastSelection = textRange;
   1378         var pos = WebInspector.CodeMirrorUtils.toPos(textRange);
   1379         this._codeMirror.setSelection(pos.start, pos.end);
   1380     },
   1381 
   1382     /**
   1383      * @param {!Array.<!WebInspector.TextRange>} ranges
   1384      * @param {number=} primarySelectionIndex
   1385      */
   1386     setSelections: function(ranges, primarySelectionIndex)
   1387     {
   1388         var selections = [];
   1389         for (var i = 0; i < ranges.length; ++i) {
   1390             var selection = WebInspector.CodeMirrorUtils.toPos(ranges[i]);
   1391             selections.push({
   1392                 anchor: selection.start,
   1393                 head: selection.end
   1394             });
   1395         }
   1396         primarySelectionIndex = primarySelectionIndex || 0;
   1397         this._codeMirror.setSelections(selections, primarySelectionIndex, { scroll: false });
   1398     },
   1399 
   1400     /**
   1401      * @param {string} text
   1402      */
   1403     _detectLineSeparator: function(text)
   1404     {
   1405         this._lineSeparator = text.indexOf("\r\n") >= 0 ? "\r\n" : "\n";
   1406     },
   1407 
   1408     /**
   1409      * @param {string} text
   1410      */
   1411     setText: function(text)
   1412     {
   1413         this._muteTextChangedEvent = true;
   1414         if (text.length > WebInspector.CodeMirrorTextEditor.MaxEditableTextSize) {
   1415             this._autocompleteController.setEnabled(false);
   1416             this.setReadOnly(true);
   1417         }
   1418         this._codeMirror.setValue(text);
   1419         this._updateEditorIndentation();
   1420         if (this._shouldClearHistory) {
   1421             this._codeMirror.clearHistory();
   1422             this._shouldClearHistory = false;
   1423         }
   1424         this._detectLineSeparator(text);
   1425         delete this._muteTextChangedEvent;
   1426     },
   1427 
   1428     /**
   1429      * @return {string}
   1430      */
   1431     text: function()
   1432     {
   1433         return this._codeMirror.getValue().replace(/\n/g, this._lineSeparator);
   1434     },
   1435 
   1436     /**
   1437      * @return {!WebInspector.TextRange}
   1438      */
   1439     range: function()
   1440     {
   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));
   1444     },
   1445 
   1446     /**
   1447      * @param {number} lineNumber
   1448      * @return {string}
   1449      */
   1450     line: function(lineNumber)
   1451     {
   1452         return this._codeMirror.getLine(lineNumber);
   1453     },
   1454 
   1455     /**
   1456      * @return {number}
   1457      */
   1458     get linesCount()
   1459     {
   1460         return this._codeMirror.lineCount();
   1461     },
   1462 
   1463     /**
   1464      * @param {number} line
   1465      * @param {string} name
   1466      * @param {?Object} value
   1467      */
   1468     setAttribute: function(line, name, value)
   1469     {
   1470         if (line < 0 || line >= this._codeMirror.lineCount())
   1471             return;
   1472         var handle = this._codeMirror.getLineHandle(line);
   1473         if (handle.attributes === undefined) handle.attributes = {};
   1474         handle.attributes[name] = value;
   1475     },
   1476 
   1477     /**
   1478      * @param {number} line
   1479      * @param {string} name
   1480      * @return {?Object} value
   1481      */
   1482     getAttribute: function(line, name)
   1483     {
   1484         if (line < 0 || line >= this._codeMirror.lineCount())
   1485             return null;
   1486         var handle = this._codeMirror.getLineHandle(line);
   1487         return handle.attributes && handle.attributes[name] !== undefined ? handle.attributes[name] : null;
   1488     },
   1489 
   1490     /**
   1491      * @param {number} line
   1492      * @param {string} name
   1493      */
   1494     removeAttribute: function(line, name)
   1495     {
   1496         if (line < 0 || line >= this._codeMirror.lineCount())
   1497             return;
   1498         var handle = this._codeMirror.getLineHandle(line);
   1499         if (handle && handle.attributes)
   1500             delete handle.attributes[name];
   1501     },
   1502 
   1503     /**
   1504      * @param {number} lineNumber
   1505      * @param {number} columnNumber
   1506      * @return {!WebInspector.TextEditorPositionHandle}
   1507      */
   1508     textEditorPositionHandle: function(lineNumber, columnNumber)
   1509     {
   1510         return new WebInspector.CodeMirrorPositionHandle(this._codeMirror, new CodeMirror.Pos(lineNumber, columnNumber));
   1511     },
   1512 
   1513     __proto__: WebInspector.VBox.prototype
   1514 }
   1515 
   1516 /**
   1517  * @constructor
   1518  * @implements {WebInspector.TextEditorPositionHandle}
   1519  * @param {!CodeMirror} codeMirror
   1520  * @param {!CodeMirror.Pos} pos
   1521  */
   1522 WebInspector.CodeMirrorPositionHandle = function(codeMirror, pos)
   1523 {
   1524     this._codeMirror = codeMirror;
   1525     this._lineHandle = codeMirror.getLineHandle(pos.line);
   1526     this._columnNumber = pos.ch;
   1527 }
   1528 
   1529 WebInspector.CodeMirrorPositionHandle.prototype = {
   1530     /**
   1531      * @return {?{lineNumber: number, columnNumber: number}}
   1532      */
   1533     resolve: function()
   1534     {
   1535         var lineNumber = this._codeMirror.getLineNumber(this._lineHandle);
   1536         if (typeof lineNumber !== "number")
   1537             return null;
   1538         return {
   1539             lineNumber: lineNumber,
   1540             columnNumber: this._columnNumber
   1541         };
   1542     },
   1543 
   1544     /**
   1545      * @param {!WebInspector.TextEditorPositionHandle} positionHandle
   1546      * @return {boolean}
   1547      */
   1548     equal: function(positionHandle)
   1549     {
   1550         return positionHandle._lineHandle === this._lineHandle && positionHandle._columnNumber == this._columnNumber && positionHandle._codeMirror === this._codeMirror;
   1551     }
   1552 }
   1553 
   1554 /**
   1555  * @constructor
   1556  * @param {!WebInspector.CodeMirrorTextEditor} textEditor
   1557  * @param {!CodeMirror} codeMirror
   1558  */
   1559 WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(textEditor, codeMirror)
   1560 {
   1561     this._textEditor = textEditor;
   1562     this._codeMirror = codeMirror;
   1563 }
   1564 
   1565 WebInspector.CodeMirrorTextEditor.TokenHighlighter.prototype = {
   1566     /**
   1567      * @param {!RegExp} regex
   1568      * @param {?WebInspector.TextRange} range
   1569      */
   1570     highlightSearchResults: function(regex, range)
   1571     {
   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;
   1578         }
   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;
   1582         if (selectionStart)
   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;
   1588         } else {
   1589             this._removeHighlight();
   1590             this._setHighlighter(this._searchHighlighter.bind(this, this._highlightRegex), selectionStart);
   1591         }
   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"});
   1595         }
   1596     },
   1597 
   1598     /**
   1599      * @return {!RegExp|undefined}
   1600      */
   1601     highlightedRegex: function()
   1602     {
   1603         return this._highlightRegex;
   1604     },
   1605 
   1606     highlightSelectedTokens: function()
   1607     {
   1608         delete this._highlightRegex;
   1609         delete this._highlightRange;
   1610 
   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)
   1617             return;
   1618         if (selectionStart.ch === selectionEnd.ch)
   1619             return;
   1620 
   1621         var selections = this._codeMirror.getSelections();
   1622         if (selections.length > 1)
   1623             return;
   1624         var selectedText = selections[0];
   1625         if (this._isWord(selectedText, selectionStart.line, selectionStart.ch, selectionEnd.ch)) {
   1626             if (selectionStart)
   1627                 this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection")
   1628             this._setHighlighter(this._tokenHighlighter.bind(this, selectedText, selectionStart), selectionStart);
   1629         }
   1630     },
   1631 
   1632     /**
   1633      * @param {string} selectedText
   1634      * @param {number} lineNumber
   1635      * @param {number} startColumn
   1636      * @param {number} endColumn
   1637      */
   1638     _isWord: function(selectedText, lineNumber, startColumn, endColumn)
   1639     {
   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);
   1644     },
   1645 
   1646     _removeHighlight: function()
   1647     {
   1648         if (this._highlightDescriptor) {
   1649             this._codeMirror.removeOverlay(this._highlightDescriptor.overlay);
   1650             delete this._highlightDescriptor;
   1651         }
   1652     },
   1653 
   1654     /**
   1655      * @param {!RegExp} regex
   1656      * @param {!CodeMirror.StringStream} stream
   1657      */
   1658     _searchHighlighter: function(regex, stream)
   1659     {
   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)
   1665                     stream.next();
   1666                 this._searchMatchLength = 1;
   1667                 return "search-highlight";
   1668             } else {
   1669                 stream.next();
   1670                 delete this._searchMatchLength;
   1671                 return "search-highlight search-highlight-end";
   1672             }
   1673         }
   1674         var match = stream.match(regex, false);
   1675         if (match) {
   1676             stream.next();
   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";
   1682         }
   1683 
   1684         while (!stream.match(regex, false) && stream.next()) {};
   1685     },
   1686 
   1687     /**
   1688      * @param {string} token
   1689      * @param {!CodeMirror.Pos} selectionStart
   1690      * @param {!CodeMirror.StringStream} stream
   1691      */
   1692     _tokenHighlighter: function(token, selectionStart, stream)
   1693     {
   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";
   1697 
   1698         var eatenChar;
   1699         do {
   1700             eatenChar = stream.next();
   1701         } while (eatenChar && (WebInspector.TextUtils.isWordChar(eatenChar) || stream.peek() !== tokenFirstChar));
   1702     },
   1703 
   1704     /**
   1705      * @param {function(!CodeMirror.StringStream)} highlighter
   1706      * @param {?CodeMirror.Pos} selectionStart
   1707      */
   1708     _setHighlighter: function(highlighter, selectionStart)
   1709     {
   1710         var overlayMode = {
   1711             token: highlighter
   1712         };
   1713         this._codeMirror.addOverlay(overlayMode);
   1714         this._highlightDescriptor = {
   1715             overlay: overlayMode,
   1716             selectionStart: selectionStart
   1717         };
   1718     }
   1719 }
   1720 
   1721 /**
   1722  * @constructor
   1723  * @param {!CodeMirror} codeMirror
   1724  */
   1725 WebInspector.CodeMirrorTextEditor.BlockIndentController = function(codeMirror)
   1726 {
   1727     codeMirror.addKeyMap(this);
   1728 }
   1729 
   1730 WebInspector.CodeMirrorTextEditor.BlockIndentController.prototype = {
   1731     name: "blockIndentKeymap",
   1732 
   1733     /**
   1734      * @return {*}
   1735      */
   1736     Enter: function(codeMirror)
   1737     {
   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;
   1755             }
   1756             if (i > 0 && allSelectionsAreCollapsedBlocks !== isCollapsedBlock)
   1757                 return CodeMirror.Pass;
   1758             replacements.push(indentToInsert);
   1759             allSelectionsAreCollapsedBlocks = isCollapsedBlock;
   1760         }
   1761         codeMirror.replaceSelections(replacements);
   1762         if (!allSelectionsAreCollapsedBlocks) {
   1763             codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
   1764             return;
   1765         }
   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({
   1773                 head: position,
   1774                 anchor: position
   1775             });
   1776         }
   1777         codeMirror.setSelections(updatedSelections);
   1778         codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
   1779     },
   1780 
   1781     /**
   1782      * @return {*}
   1783      */
   1784     "'}'": function(codeMirror)
   1785     {
   1786         if (codeMirror.somethingSelected())
   1787             return CodeMirror.Pass;
   1788 
   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("}");
   1797         }
   1798 
   1799         codeMirror.replaceSelections(replacements);
   1800         selections = codeMirror.listSelections();
   1801         replacements = [];
   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)
   1807                 return;
   1808             updatedSelections.push({
   1809                 head: selection.head,
   1810                 anchor: new CodeMirror.Pos(selection.head.line, 0)
   1811             });
   1812             var line = codeMirror.getLine(matchingBracket.to.line);
   1813             var indent = WebInspector.TextUtils.lineIndent(line);
   1814             replacements.push(indent + "}");
   1815         }
   1816         codeMirror.setSelections(updatedSelections);
   1817         codeMirror.replaceSelections(replacements);
   1818     }
   1819 }
   1820 
   1821 /**
   1822  * @constructor
   1823  * @param {!CodeMirror} codeMirror
   1824  */
   1825 WebInspector.CodeMirrorTextEditor.FixWordMovement = function(codeMirror)
   1826 {
   1827     function moveLeft(shift, codeMirror)
   1828     {
   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);
   1835             return;
   1836         }
   1837 
   1838         var skippedText = codeMirror.getRange(newCursor, cursor, "#");
   1839         if (/^\s+$/.test(skippedText))
   1840             codeMirror.execCommand("goGroupLeft");
   1841         codeMirror.setExtending(false);
   1842     }
   1843 
   1844     function moveRight(shift, codeMirror)
   1845     {
   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);
   1852             return;
   1853         }
   1854 
   1855         var skippedText = codeMirror.getRange(cursor, newCursor, "#");
   1856         if (/^\s+$/.test(skippedText))
   1857             codeMirror.execCommand("goGroupRight");
   1858         codeMirror.setExtending(false);
   1859     }
   1860 
   1861     var modifierKey = WebInspector.isMac() ? "Alt" : "Ctrl";
   1862     var leftKey = modifierKey + "-Left";
   1863     var rightKey = modifierKey + "-Right";
   1864     var keyMap = {};
   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);
   1870 }
   1871 
   1872 /**
   1873  * @interface
   1874  */
   1875 WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI = function() {}
   1876 
   1877 WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI.prototype = {
   1878     dispose: function() { },
   1879 
   1880     /**
   1881      * @param {boolean} enabled
   1882      */
   1883     setEnabled: function(enabled) { },
   1884 
   1885     /**
   1886      * @param {string} mimeType
   1887      */
   1888     setMimeType: function(mimeType) { },
   1889 
   1890     autocomplete: function() { },
   1891 
   1892     finishAutocomplete: function() { },
   1893 
   1894     /**
   1895      * @param {!Event} e
   1896      * @return {boolean}
   1897      */
   1898     keyDown: function(e) { }
   1899 }
   1900 
   1901 /**
   1902  * @constructor
   1903  * @implements {WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI}
   1904  */
   1905 WebInspector.CodeMirrorTextEditor.DummyAutocompleteController = function() { }
   1906 
   1907 WebInspector.CodeMirrorTextEditor.DummyAutocompleteController.prototype = {
   1908     dispose: function() { },
   1909 
   1910     /**
   1911      * @param {boolean} enabled
   1912      */
   1913     setEnabled: function(enabled) { },
   1914 
   1915     /**
   1916      * @param {string} mimeType
   1917      */
   1918     setMimeType: function(mimeType) { },
   1919 
   1920     autocomplete: function() { },
   1921 
   1922     finishAutocomplete: function() { },
   1923 
   1924     /**
   1925      * @param {!Event} e
   1926      * @return {boolean}
   1927      */
   1928     keyDown: function(e)
   1929     {
   1930         return false;
   1931     }
   1932 }
   1933 
   1934 /**
   1935  * @constructor
   1936  * @implements {WebInspector.SuggestBoxDelegate}
   1937  * @implements {WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI}
   1938  * @param {!WebInspector.CodeMirrorTextEditor} textEditor
   1939  * @param {!CodeMirror} codeMirror
   1940  * @param {?WebInspector.CompletionDictionary} dictionary
   1941  */
   1942 WebInspector.CodeMirrorTextEditor.AutocompleteController = function(textEditor, codeMirror, dictionary)
   1943 {
   1944     this._textEditor = textEditor;
   1945     this._codeMirror = codeMirror;
   1946 
   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);
   1957 
   1958     this._additionalWordChars = WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars;
   1959     this._enabled = true;
   1960 
   1961     this._dictionary = dictionary;
   1962     this._addTextToCompletionDictionary(this._textEditor.text());
   1963 }
   1964 
   1965 WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy = new WebInspector.CodeMirrorTextEditor.DummyAutocompleteController();
   1966 WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars = {};
   1967 WebInspector.CodeMirrorTextEditor._CSSAdditionalWordChars = { ".": true, "-": true };
   1968 
   1969 WebInspector.CodeMirrorTextEditor.AutocompleteController.prototype = {
   1970     dispose: function()
   1971     {
   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);
   1977     },
   1978 
   1979     /**
   1980      * @param {boolean} enabled
   1981      */
   1982     setEnabled: function(enabled)
   1983     {
   1984         if (enabled === this._enabled)
   1985             return;
   1986         this._enabled = enabled;
   1987         if (!enabled)
   1988             this._dictionary.reset();
   1989         else
   1990             this._addTextToCompletionDictionary(this._textEditor.text());
   1991     },
   1992 
   1993     /**
   1994      * @param {string} mimeType
   1995      */
   1996     setMimeType: function(mimeType)
   1997     {
   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());
   2003         }
   2004     },
   2005 
   2006     /**
   2007      * @param {string} char
   2008      * @return {boolean}
   2009      */
   2010     _isWordChar: function(char)
   2011     {
   2012         return WebInspector.TextUtils.isWordChar(char) || !!this._additionalWordChars[char];
   2013     },
   2014 
   2015     /**
   2016      * @param {string} word
   2017      * @return {boolean}
   2018      */
   2019     _shouldProcessWordForAutocompletion: function(word)
   2020     {
   2021         return !!word.length && (word[0] < '0' || word[0] > '9');
   2022     },
   2023 
   2024     /**
   2025      * @param {string} text
   2026      */
   2027     _addTextToCompletionDictionary: function(text)
   2028     {
   2029         if (!this._enabled)
   2030             return;
   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]);
   2035         }
   2036     },
   2037 
   2038     /**
   2039      * @param {string} text
   2040      */
   2041     _removeTextFromCompletionDictionary: function(text)
   2042     {
   2043         if (!this._enabled)
   2044             return;
   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]);
   2049         }
   2050     },
   2051 
   2052     /**
   2053      * @param {!CodeMirror} codeMirror
   2054      * @param {!WebInspector.CodeMirrorTextEditor.BeforeChangeObject} changeObject
   2055      */
   2056     _beforeChange: function(codeMirror, changeObject)
   2057     {
   2058         if (!this._enabled)
   2059             return;
   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);
   2063     },
   2064 
   2065     /**
   2066      * @param {!CodeMirror} codeMirror
   2067      * @param {!Array.<!WebInspector.CodeMirrorTextEditor.ChangeObject>} changes
   2068      */
   2069     _changes: function(codeMirror, changes)
   2070     {
   2071         if (!changes.length || !this._enabled)
   2072             return;
   2073 
   2074         if (this._updatedLines) {
   2075             for (var lineNumber in this._updatedLines)
   2076                 this._removeTextFromCompletionDictionary(this._updatedLines[lineNumber]);
   2077             delete this._updatedLines;
   2078         }
   2079 
   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);
   2086 
   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);
   2090         }
   2091         for (var lineNumber in linesToUpdate)
   2092             this._addTextToCompletionDictionary(linesToUpdate[lineNumber]);
   2093 
   2094         if (singleCharInput)
   2095             this.autocomplete();
   2096     },
   2097 
   2098     _blur: function()
   2099     {
   2100         this.finishAutocomplete();
   2101     },
   2102 
   2103     /**
   2104      * @param {number} lineNumber
   2105      * @param {number} columnNumber
   2106      * @return {!WebInspector.TextRange}
   2107      */
   2108     _autocompleteWordRange: function(lineNumber, columnNumber)
   2109     {
   2110         return this._textEditor._wordRangeForCursorPosition(lineNumber, columnNumber, this._isWordChar.bind(this));
   2111     },
   2112 
   2113     /**
   2114      * @param {!WebInspector.TextRange} mainSelection
   2115      * @param {!Array.<!{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>} selections
   2116      * @return {boolean}
   2117      */
   2118     _validateSelectionsContexts: function(mainSelection, selections)
   2119     {
   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);
   2123             if (!wordRange)
   2124                 return false;
   2125             var context = this._textEditor.copyRange(wordRange);
   2126             if (context !== mainSelectionContext)
   2127                 return false;
   2128         }
   2129         return true;
   2130     },
   2131 
   2132     autocomplete: function()
   2133     {
   2134         var dictionary = this._dictionary;
   2135         if (this._codeMirror.somethingSelected()) {
   2136             this.finishAutocomplete();
   2137             return;
   2138         }
   2139 
   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();
   2146             return;
   2147         }
   2148 
   2149         var prefixRange = substituteRange.clone();
   2150         prefixRange.endColumn = cursor.ch;
   2151 
   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);
   2159 
   2160         function sortSuggestions(a, b)
   2161         {
   2162             return dictionary.wordCount(b) - dictionary.wordCount(a) || a.length - b.length;
   2163         }
   2164 
   2165         wordsWithPrefix.sort(sortSuggestions);
   2166 
   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();
   2176     },
   2177 
   2178     finishAutocomplete: function()
   2179     {
   2180         if (!this._suggestBox)
   2181             return;
   2182         this._suggestBox.hide();
   2183         this._suggestBox = null;
   2184         this._prefixRange = null;
   2185         this._anchorBox = null;
   2186     },
   2187 
   2188     /**
   2189      * @param {!Event} e
   2190      * @return {boolean}
   2191      */
   2192     keyDown: function(e)
   2193     {
   2194         if (!this._suggestBox)
   2195             return false;
   2196         if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
   2197             this.finishAutocomplete();
   2198             return true;
   2199         }
   2200         if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Tab.code) {
   2201             this._suggestBox.acceptSuggestion();
   2202             this.finishAutocomplete();
   2203             return true;
   2204         }
   2205         return this._suggestBox.keyPressed(e);
   2206     },
   2207 
   2208     /**
   2209      * @param {string} suggestion
   2210      * @param {boolean=} isIntermediateSuggestion
   2211      */
   2212     applySuggestion: function(suggestion, isIntermediateSuggestion)
   2213     {
   2214         this._currentSuggestion = suggestion;
   2215     },
   2216 
   2217     acceptSuggestion: function()
   2218     {
   2219         if (this._prefixRange.endColumn - this._prefixRange.startColumn === this._currentSuggestion.length)
   2220             return;
   2221 
   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");
   2228         }
   2229     },
   2230 
   2231     _onScroll: function()
   2232     {
   2233         if (!this._suggestBox)
   2234             return;
   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();
   2241         else {
   2242             this._updateAnchorBox();
   2243             this._suggestBox.setPosition(this._anchorBox);
   2244         }
   2245     },
   2246 
   2247     _onCursorActivity: function()
   2248     {
   2249         if (!this._suggestBox)
   2250             return;
   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();
   2254     },
   2255 
   2256     _updateAnchorBox: function()
   2257     {
   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;
   2262     },
   2263 }
   2264 
   2265 /**
   2266  * @constructor
   2267  * @param {!WebInspector.CodeMirrorTextEditor} textEditor
   2268  * @param {!CodeMirror} codeMirror
   2269  */
   2270 WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController = function(textEditor, codeMirror)
   2271 {
   2272     this._textEditor = textEditor;
   2273     this._codeMirror = codeMirror;
   2274 }
   2275 
   2276 WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController.prototype = {
   2277     selectionWillChange: function()
   2278     {
   2279         if (!this._muteSelectionListener)
   2280             delete this._fullWordSelection;
   2281     },
   2282 
   2283     /**
   2284      * @param {!Array.<!WebInspector.TextRange>} selections
   2285      * @param {!WebInspector.TextRange} range
   2286      * @return {boolean}
   2287      */
   2288     _findRange: function(selections, range)
   2289     {
   2290         for (var i = 0; i < selections.length; ++i) {
   2291             if (range.equal(selections[i]))
   2292                 return true;
   2293         }
   2294         return false;
   2295     },
   2296 
   2297     undoLastSelection: function()
   2298     {
   2299         this._muteSelectionListener = true;
   2300         this._codeMirror.execCommand("undoSelection");
   2301         this._muteSelectionListener = false;
   2302     },
   2303 
   2304     selectNextOccurrence: function()
   2305     {
   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)
   2312                 return;
   2313         }
   2314         if (anyEmptySelection) {
   2315             this._expandSelectionsToWords(selections);
   2316             return;
   2317         }
   2318 
   2319         var last = selections[selections.length - 1];
   2320         var next = last;
   2321         do {
   2322             next = this._findNextOccurrence(next, !!this._fullWordSelection);
   2323         } while (next && this._findRange(selections, next) && !next.equal(last));
   2324 
   2325         if (!next)
   2326             return;
   2327         selections.push(next);
   2328 
   2329         this._muteSelectionListener = true;
   2330         this._textEditor.setSelections(selections, selections.length - 1);
   2331         delete this._muteSelectionListener;
   2332 
   2333         this._textEditor._revealLine(next.startLine);
   2334     },
   2335 
   2336     /**
   2337      * @param {!Array.<!WebInspector.TextRange>} selections
   2338      */
   2339     _expandSelectionsToWords: function(selections)
   2340     {
   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);
   2350         }
   2351         this._textEditor.setSelections(newSelections, newSelections.length - 1);
   2352         this._fullWordSelection = true;
   2353     },
   2354 
   2355     /**
   2356      * @param {!WebInspector.TextRange} range
   2357      * @param {boolean} fullWord
   2358      * @return {?WebInspector.TextRange}
   2359      */
   2360     _findNextOccurrence: function(range, fullWord)
   2361     {
   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)
   2367         {
   2368             if (typeof matchedLineNumber === "number")
   2369                 return true;
   2370             wordRegex.lastIndex = from;
   2371             var result = wordRegex.exec(lineText);
   2372             if (!result || result.index + textToFind.length > to)
   2373                 return false;
   2374             matchedLineNumber = lineNumber;
   2375             matchedColumnNumber = result.index;
   2376             return true;
   2377         }
   2378 
   2379         var iteratedLineNumber;
   2380         function lineIterator(regex, lineHandle)
   2381         {
   2382             if (findWordInLine(regex, iteratedLineNumber++, lineHandle.text, 0, lineHandle.text.length))
   2383                 return true;
   2384         }
   2385 
   2386         var regexSource = textToFind.escapeForRegExp();
   2387         if (fullWord)
   2388             regexSource = "\\b" + regexSource + "\\b";
   2389         var wordRegex = new RegExp(regexSource, "g");
   2390         var currentLineText = this._codeMirror.getLine(range.startLine);
   2391 
   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);
   2398 
   2399         if (typeof matchedLineNumber !== "number")
   2400             return null;
   2401         return new WebInspector.TextRange(matchedLineNumber, matchedColumnNumber, matchedLineNumber, matchedColumnNumber + textToFind.length);
   2402     }
   2403 }
   2404 
   2405 /**
   2406  * @param {string} modeName
   2407  * @param {string} tokenPrefix
   2408  */
   2409 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens = function(modeName, tokenPrefix)
   2410 {
   2411     var oldModeName = modeName + "-old";
   2412     if (CodeMirror.modes[oldModeName])
   2413         return;
   2414 
   2415     CodeMirror.defineMode(oldModeName, CodeMirror.modes[modeName]);
   2416     CodeMirror.defineMode(modeName, modeConstructor);
   2417 
   2418     function modeConstructor(config, parserConfig)
   2419     {
   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;
   2428     }
   2429 
   2430     function tokenOverride(superToken, stream, state)
   2431     {
   2432         var token = superToken(stream, state);
   2433         return token ? tokenPrefix + token.split(/ +/).join(" " + tokenPrefix) : token;
   2434     }
   2435 }
   2436 
   2437 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("css", "css-");
   2438 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("javascript", "js-");
   2439 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("xml", "xml-");
   2440 
   2441 (function() {
   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)
   2447         return;
   2448 
   2449     var style = document.createElement("style");
   2450     style.textContent = backgroundColorRule + foregroundColorRule;
   2451     document.head.appendChild(style);
   2452 })();
   2453