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("cm/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     };
     74 
     75     CodeMirror.keyMap["devtools-pc"] = {
     76         "Ctrl-A": "selectAll",
     77         "Ctrl-Z": "undoAndReveal",
     78         "Shift-Ctrl-Z": "redoAndReveal",
     79         "Ctrl-Y": "redo",
     80         "Ctrl-Home": "goDocStart",
     81         "Ctrl-Up": "goDocStart",
     82         "Ctrl-End": "goDocEnd",
     83         "Ctrl-Down": "goDocEnd",
     84         "Ctrl-Left": "goGroupLeft",
     85         "Ctrl-Right": "goGroupRight",
     86         "Alt-Left": "goLineStart",
     87         "Alt-Right": "goLineEnd",
     88         "Ctrl-Backspace": "delGroupBefore",
     89         "Ctrl-Delete": "delGroupAfter",
     90         "Ctrl-/": "toggleComment",
     91         "Ctrl-D": "selectNextOccurrence",
     92         "Ctrl-U": "undoLastSelection",
     93         fallthrough: "devtools-common"
     94     };
     95 
     96     CodeMirror.keyMap["devtools-mac"] = {
     97         "Cmd-A" : "selectAll",
     98         "Cmd-Z" : "undoAndReveal",
     99         "Shift-Cmd-Z": "redoAndReveal",
    100         "Cmd-Up": "goDocStart",
    101         "Cmd-Down": "goDocEnd",
    102         "Alt-Left": "goGroupLeft",
    103         "Alt-Right": "goGroupRight",
    104         "Cmd-Left": "goLineStartSmart",
    105         "Cmd-Right": "goLineEnd",
    106         "Alt-Backspace": "delGroupBefore",
    107         "Alt-Delete": "delGroupAfter",
    108         "Cmd-/": "toggleComment",
    109         "Cmd-D": "selectNextOccurrence",
    110         "Cmd-U": "undoLastSelection",
    111         fallthrough: "devtools-common"
    112     };
    113 
    114     WebInspector.settings.textEditorIndent.addChangeListener(this._updateEditorIndentation, this);
    115     this._updateEditorIndentation();
    116     WebInspector.settings.showWhitespacesInEditor.addChangeListener(this._updateCodeMirrorMode, this);
    117     WebInspector.settings.textEditorBracketMatching.addChangeListener(this._enableBracketMatchingIfNeeded, this);
    118     this._enableBracketMatchingIfNeeded();
    119 
    120     this._codeMirror.setOption("keyMap", WebInspector.isMac() ? "devtools-mac" : "devtools-pc");
    121     this._codeMirror.setOption("flattenSpans", false);
    122 
    123     this._codeMirror.setOption("maxHighlightLength", WebInspector.CodeMirrorTextEditor.maxHighlightLength);
    124     this._codeMirror.setOption("mode", null);
    125     this._codeMirror.setOption("crudeMeasuringFrom", 1000);
    126 
    127     this._shouldClearHistory = true;
    128     this._lineSeparator = "\n";
    129 
    130     this._autocompleteController = WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy;
    131     this._tokenHighlighter = new WebInspector.CodeMirrorTextEditor.TokenHighlighter(this, this._codeMirror);
    132     this._blockIndentController = new WebInspector.CodeMirrorTextEditor.BlockIndentController(this._codeMirror);
    133     this._fixWordMovement = new WebInspector.CodeMirrorTextEditor.FixWordMovement(this._codeMirror);
    134     this._selectNextOccurrenceController = new WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController(this, this._codeMirror);
    135 
    136     this._codeMirror.on("changes", this._changes.bind(this));
    137     this._codeMirror.on("gutterClick", this._gutterClick.bind(this));
    138     this._codeMirror.on("cursorActivity", this._cursorActivity.bind(this));
    139     this._codeMirror.on("beforeSelectionChange", this._beforeSelectionChange.bind(this));
    140     this._codeMirror.on("scroll", this._scroll.bind(this));
    141     this._codeMirror.on("focus", this._focus.bind(this));
    142     this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false);
    143     /**
    144      * @this {WebInspector.CodeMirrorTextEditor}
    145      */
    146     function updateAnticipateJumpFlag(value)
    147     {
    148         this._isHandlingMouseDownEvent = value;
    149     }
    150     this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, true), true);
    151     this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, false), false);
    152 
    153     this.element.style.overflow = "hidden";
    154     this.element.firstChild.classList.add("source-code");
    155     this.element.firstChild.classList.add("fill");
    156     this._elementToWidget = new Map();
    157     this._nestedUpdatesCounter = 0;
    158 
    159     this.element.addEventListener("focus", this._handleElementFocus.bind(this), false);
    160     this.element.addEventListener("keydown", this._handleKeyDown.bind(this), true);
    161     this.element.addEventListener("keydown", this._handlePostKeyDown.bind(this), false);
    162     this.element.tabIndex = 0;
    163 
    164     this._setupWhitespaceHighlight();
    165 }
    166 
    167 /** @typedef {{canceled: boolean, from: !CodeMirror.Pos, to: !CodeMirror.Pos, text: string, origin: string, cancel: function()}} */
    168 WebInspector.CodeMirrorTextEditor.BeforeChangeObject;
    169 
    170 /** @typedef {{from: !CodeMirror.Pos, to: !CodeMirror.Pos, origin: string, text: !Array.<string>, removed: !Array.<string>}} */
    171 WebInspector.CodeMirrorTextEditor.ChangeObject;
    172 
    173 WebInspector.CodeMirrorTextEditor.maxHighlightLength = 1000;
    174 
    175 /**
    176  * @param {!CodeMirror} codeMirror
    177  */
    178 WebInspector.CodeMirrorTextEditor.autocompleteCommand = function(codeMirror)
    179 {
    180     codeMirror._codeMirrorTextEditor._autocompleteController.autocomplete();
    181 }
    182 CodeMirror.commands.autocomplete = WebInspector.CodeMirrorTextEditor.autocompleteCommand;
    183 
    184 /**
    185  * @param {!CodeMirror} codeMirror
    186  */
    187 WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand = function(codeMirror)
    188 {
    189     codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.undoLastSelection();
    190 }
    191 CodeMirror.commands.undoLastSelection = WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand;
    192 
    193 /**
    194  * @param {!CodeMirror} codeMirror
    195  */
    196 WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand = function(codeMirror)
    197 {
    198     codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.selectNextOccurrence();
    199 }
    200 CodeMirror.commands.selectNextOccurrence = WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand;
    201 
    202 /**
    203  * @param {!CodeMirror} codeMirror
    204  */
    205 CodeMirror.commands.smartNewlineAndIndent = function(codeMirror)
    206 {
    207     codeMirror.operation(innerSmartNewlineAndIndent.bind(null, codeMirror));
    208 
    209     function countIndent(line)
    210     {
    211         for (var i = 0; i < line.length; ++i) {
    212             if (!WebInspector.TextUtils.isSpaceChar(line[i]))
    213                 return i;
    214         }
    215         return line.length;
    216     }
    217 
    218     function innerSmartNewlineAndIndent(codeMirror)
    219     {
    220         var cur = codeMirror.getCursor("start");
    221         var line = codeMirror.getLine(cur.line);
    222         var indent = cur.line > 0 ? countIndent(line) : 0;
    223         if (cur.ch <= indent) {
    224             codeMirror.replaceSelection("\n" + line.substring(0, cur.ch), "end", "+input");
    225             codeMirror.setSelection(new CodeMirror.Pos(cur.line + 1, cur.ch));
    226         } else
    227             codeMirror.execCommand("newlineAndIndent");
    228     }
    229 }
    230 
    231 CodeMirror.commands.undoAndReveal = function(codemirror)
    232 {
    233     var scrollInfo = codemirror.getScrollInfo();
    234     codemirror.execCommand("undo");
    235     var cursor = codemirror.getCursor("start");
    236     codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo);
    237     codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete();
    238 }
    239 
    240 CodeMirror.commands.redoAndReveal = function(codemirror)
    241 {
    242     var scrollInfo = codemirror.getScrollInfo();
    243     codemirror.execCommand("redo");
    244     var cursor = codemirror.getCursor("start");
    245     codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo);
    246     codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete();
    247 }
    248 
    249 /**
    250  * @return {!Object|undefined}
    251  */
    252 CodeMirror.commands.dismissMultipleSelections = function(codemirror)
    253 {
    254     var selections = codemirror.listSelections();
    255     var selection = selections[0];
    256     if (selections.length === 1) {
    257         if (codemirror._codeMirrorTextEditor._isSearchActive())
    258             return CodeMirror.Pass;
    259         if (WebInspector.CodeMirrorUtils.toRange(selection.anchor, selection.head).isEmpty())
    260             return CodeMirror.Pass;
    261         codemirror.setSelection(selection.anchor, selection.anchor, {scroll: false});
    262         codemirror._codeMirrorTextEditor._revealLine(selection.anchor.line);
    263         return;
    264     }
    265 
    266     codemirror.setSelection(selection.anchor, selection.head, {scroll: false});
    267     codemirror._codeMirrorTextEditor._revealLine(selection.anchor.line);
    268 }
    269 
    270 WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold = 2000;
    271 WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan = 16;
    272 WebInspector.CodeMirrorTextEditor.MaxEditableTextSize = 1024 * 1024 * 10;
    273 
    274 WebInspector.CodeMirrorTextEditor.prototype = {
    275     dispose: function()
    276     {
    277         WebInspector.settings.textEditorIndent.removeChangeListener(this._updateEditorIndentation, this);
    278         WebInspector.settings.showWhitespacesInEditor.removeChangeListener(this._updateCodeMirrorMode, this);
    279         WebInspector.settings.textEditorBracketMatching.removeChangeListener(this._enableBracketMatchingIfNeeded, this);
    280     },
    281 
    282     _enableBracketMatchingIfNeeded: function()
    283     {
    284         this._codeMirror.setOption("autoCloseBrackets", WebInspector.settings.textEditorBracketMatching.get() ? { explode: false } : false);
    285     },
    286 
    287     wasShown: function()
    288     {
    289         if (this._wasOnceShown)
    290             return;
    291         this._wasOnceShown = true;
    292         this._codeMirror.refresh();
    293     },
    294 
    295     _guessIndentationLevel: function()
    296     {
    297         var tabRegex = /^\t+/;
    298         var tabLines = 0;
    299         var indents = {};
    300         function processLine(lineHandle)
    301         {
    302             var text = lineHandle.text;
    303             if (text.length === 0 || !WebInspector.TextUtils.isSpaceChar(text[0]))
    304                 return;
    305             if (tabRegex.test(text)) {
    306                 ++tabLines;
    307                 return;
    308             }
    309             var i = 0;
    310             while (i < text.length && WebInspector.TextUtils.isSpaceChar(text[i]))
    311                 ++i;
    312             if (i % 2 !== 0)
    313                 return;
    314             indents[i] = 1 + (indents[i] || 0);
    315         }
    316         this._codeMirror.eachLine(0, 1000, processLine);
    317 
    318         var onePercentFilterThreshold = this.linesCount / 100;
    319         if (tabLines && tabLines > onePercentFilterThreshold)
    320             return "\t";
    321         var minimumIndent = Infinity;
    322         for (var i in indents) {
    323             if (indents[i] < onePercentFilterThreshold)
    324                 continue;
    325             var indent = parseInt(i, 10);
    326             if (minimumIndent > indent)
    327                 minimumIndent = indent;
    328         }
    329         if (minimumIndent === Infinity)
    330             return WebInspector.TextUtils.Indent.FourSpaces;
    331         return new Array(minimumIndent + 1).join(" ");
    332     },
    333 
    334     _updateEditorIndentation: function()
    335     {
    336         var extraKeys = {};
    337         var indent = WebInspector.settings.textEditorIndent.get();
    338         if (WebInspector.settings.textEditorAutoDetectIndent.get())
    339             indent = this._guessIndentationLevel();
    340         if (indent === WebInspector.TextUtils.Indent.TabCharacter) {
    341             this._codeMirror.setOption("indentWithTabs", true);
    342             this._codeMirror.setOption("indentUnit", 4);
    343         } else {
    344             this._codeMirror.setOption("indentWithTabs", false);
    345             this._codeMirror.setOption("indentUnit", indent.length);
    346             extraKeys.Tab = function(codeMirror)
    347             {
    348                 if (codeMirror.somethingSelected())
    349                     return CodeMirror.Pass;
    350                 var pos = codeMirror.getCursor("head");
    351                 codeMirror.replaceRange(indent.substring(pos.ch % indent.length), codeMirror.getCursor());
    352             }
    353         }
    354         this._codeMirror.setOption("extraKeys", extraKeys);
    355         this._indentationLevel = indent;
    356     },
    357 
    358     /**
    359      * @return {string}
    360      */
    361     indent: function()
    362     {
    363         return this._indentationLevel;
    364     },
    365 
    366     /**
    367      * @return {boolean}
    368      */
    369     _isSearchActive: function()
    370     {
    371         return !!this._tokenHighlighter.highlightedRegex();
    372     },
    373 
    374     /**
    375      * @param {!RegExp} regex
    376      * @param {?WebInspector.TextRange} range
    377      */
    378     highlightSearchResults: function(regex, range)
    379     {
    380         /**
    381          * @this {WebInspector.CodeMirrorTextEditor}
    382          */
    383         function innerHighlightRegex()
    384         {
    385             if (range) {
    386                 this._revealLine(range.startLine);
    387                 if (range.endColumn > WebInspector.CodeMirrorTextEditor.maxHighlightLength)
    388                     this.setSelection(range);
    389                 else
    390                     this.setSelection(WebInspector.TextRange.createFromLocation(range.startLine, range.startColumn));
    391             } else {
    392                 // Collapse selection to end on search start so that we jump to next occurrence on the first enter press.
    393                 this.setSelection(this.selection().collapseToEnd());
    394             }
    395             this._tokenHighlighter.highlightSearchResults(regex, range);
    396         }
    397         if (!this._selectionBeforeSearch)
    398             this._selectionBeforeSearch = this.selection();
    399         this._codeMirror.operation(innerHighlightRegex.bind(this));
    400     },
    401 
    402     cancelSearchResultsHighlight: function()
    403     {
    404         this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));
    405         if (this._selectionBeforeSearch) {
    406             this._reportJump(this._selectionBeforeSearch, this.selection());
    407             delete this._selectionBeforeSearch;
    408         }
    409     },
    410 
    411     undo: function()
    412     {
    413         this._codeMirror.undo();
    414     },
    415 
    416     redo: function()
    417     {
    418         this._codeMirror.redo();
    419     },
    420 
    421     _setupWhitespaceHighlight: function()
    422     {
    423         if (WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected || !WebInspector.settings.showWhitespacesInEditor.get())
    424             return;
    425         WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected = true;
    426         const classBase = ".show-whitespaces .CodeMirror .cm-whitespace-";
    427         const spaceChar = "";
    428         var spaceChars = "";
    429         var rules = "";
    430         for (var i = 1; i <= WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan; ++i) {
    431             spaceChars += spaceChar;
    432             var rule = classBase + i + "::before { content: '" + spaceChars + "';}\n";
    433             rules += rule;
    434         }
    435         var style = document.createElement("style");
    436         style.textContent = rules;
    437         document.head.appendChild(style);
    438     },
    439 
    440     _handleKeyDown: function(e)
    441     {
    442         if (this._autocompleteController.keyDown(e))
    443             e.consume(true);
    444     },
    445 
    446     _handlePostKeyDown: function(e)
    447     {
    448         if (e.defaultPrevented)
    449             e.consume(true);
    450     },
    451 
    452     /**
    453      * @param {?WebInspector.CompletionDictionary} dictionary
    454      */
    455     setCompletionDictionary: function(dictionary)
    456     {
    457         this._autocompleteController.dispose();
    458         if (dictionary)
    459             this._autocompleteController = new WebInspector.CodeMirrorTextEditor.AutocompleteController(this, this._codeMirror, dictionary);
    460         else
    461             this._autocompleteController = WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy;
    462     },
    463 
    464     /**
    465      * @param {number} lineNumber
    466      * @param {number} column
    467      * @return {?{x: number, y: number, height: number}}
    468      */
    469     cursorPositionToCoordinates: function(lineNumber, column)
    470     {
    471         if (lineNumber >= this._codeMirror.lineCount() || lineNumber < 0 || column < 0 || column > this._codeMirror.getLine(lineNumber).length)
    472             return null;
    473 
    474         var metrics = this._codeMirror.cursorCoords(new CodeMirror.Pos(lineNumber, column));
    475 
    476         return {
    477             x: metrics.left,
    478             y: metrics.top,
    479             height: metrics.bottom - metrics.top
    480         };
    481     },
    482 
    483     /**
    484      * @param {number} x
    485      * @param {number} y
    486      * @return {?WebInspector.TextRange}
    487      */
    488     coordinatesToCursorPosition: function(x, y)
    489     {
    490         var element = document.elementFromPoint(x, y);
    491         if (!element || !element.isSelfOrDescendant(this._codeMirror.getWrapperElement()))
    492             return null;
    493         var gutterBox = this._codeMirror.getGutterElement().boxInWindow();
    494         if (x >= gutterBox.x && x <= gutterBox.x + gutterBox.width &&
    495             y >= gutterBox.y && y <= gutterBox.y + gutterBox.height)
    496             return null;
    497         var coords = this._codeMirror.coordsChar({left: x, top: y});
    498         return WebInspector.CodeMirrorUtils.toRange(coords, coords);
    499     },
    500 
    501     /**
    502      * @param {number} lineNumber
    503      * @param {number} column
    504      * @return {?{startColumn: number, endColumn: number, type: string}}
    505      */
    506     tokenAtTextPosition: function(lineNumber, column)
    507     {
    508         if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
    509             return null;
    510         var token = this._codeMirror.getTokenAt(new CodeMirror.Pos(lineNumber, (column || 0) + 1));
    511         if (!token || !token.type)
    512             return null;
    513         return {
    514             startColumn: token.start,
    515             endColumn: token.end - 1,
    516             type: token.type
    517         };
    518     },
    519 
    520     /**
    521      * @param {!WebInspector.TextRange} textRange
    522      * @return {string}
    523      */
    524     copyRange: function(textRange)
    525     {
    526         var pos = WebInspector.CodeMirrorUtils.toPos(textRange.normalize());
    527         return this._codeMirror.getRange(pos.start, pos.end);
    528     },
    529 
    530     /**
    531      * @return {boolean}
    532      */
    533     isClean: function()
    534     {
    535         return this._codeMirror.isClean();
    536     },
    537 
    538     markClean: function()
    539     {
    540         this._codeMirror.markClean();
    541     },
    542 
    543     _hasLongLines: function()
    544     {
    545         function lineIterator(lineHandle)
    546         {
    547             if (lineHandle.text.length > WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold)
    548                 hasLongLines = true;
    549             return hasLongLines;
    550         }
    551         var hasLongLines = false;
    552         this._codeMirror.eachLine(lineIterator);
    553         return hasLongLines;
    554     },
    555 
    556     /**
    557      * @param {string} mimeType
    558      * @return {string}
    559      */
    560     _whitespaceOverlayMode: function(mimeType)
    561     {
    562         var modeName = CodeMirror.mimeModes[mimeType] ? (CodeMirror.mimeModes[mimeType].name || CodeMirror.mimeModes[mimeType]) : CodeMirror.mimeModes["text/plain"];
    563         modeName += "+whitespaces";
    564         if (CodeMirror.modes[modeName])
    565             return modeName;
    566 
    567         function modeConstructor(config, parserConfig)
    568         {
    569             function nextToken(stream)
    570             {
    571                 if (stream.peek() === " ") {
    572                     var spaces = 0;
    573                     while (spaces < WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan && stream.peek() === " ") {
    574                         ++spaces;
    575                         stream.next();
    576                     }
    577                     return "whitespace whitespace-" + spaces;
    578                 }
    579                 while (!stream.eol() && stream.peek() !== " ")
    580                     stream.next();
    581                 return null;
    582             }
    583             var whitespaceMode = {
    584                 token: nextToken
    585             };
    586             return CodeMirror.overlayMode(CodeMirror.getMode(config, mimeType), whitespaceMode, false);
    587         }
    588         CodeMirror.defineMode(modeName, modeConstructor);
    589         return modeName;
    590     },
    591 
    592     _enableLongLinesMode: function()
    593     {
    594         this._codeMirror.setOption("styleSelectedText", false);
    595         this._longLinesMode = true;
    596     },
    597 
    598     _disableLongLinesMode: function()
    599     {
    600         this._codeMirror.setOption("styleSelectedText", true);
    601         this._longLinesMode = false;
    602     },
    603 
    604     _updateCodeMirrorMode: function()
    605     {
    606         var showWhitespaces = WebInspector.settings.showWhitespacesInEditor.get();
    607         this.element.classList.toggle("show-whitespaces", showWhitespaces);
    608         this._codeMirror.setOption("mode", showWhitespaces ? this._whitespaceOverlayMode(this._mimeType) : this._mimeType);
    609     },
    610 
    611     /**
    612      * @param {string} mimeType
    613      */
    614     setMimeType: function(mimeType)
    615     {
    616         this._mimeType = mimeType;
    617         if (this._hasLongLines())
    618             this._enableLongLinesMode();
    619         else
    620             this._disableLongLinesMode();
    621         this._updateCodeMirrorMode();
    622         this._autocompleteController.setMimeType(mimeType);
    623     },
    624 
    625     /**
    626      * @param {boolean} readOnly
    627      */
    628     setReadOnly: function(readOnly)
    629     {
    630         this.element.classList.toggle("CodeMirror-readonly", readOnly)
    631         this._codeMirror.setOption("readOnly", readOnly);
    632     },
    633 
    634     /**
    635      * @return {boolean}
    636      */
    637     readOnly: function()
    638     {
    639         return !!this._codeMirror.getOption("readOnly");
    640     },
    641 
    642     /**
    643      * @param {!Object} highlightDescriptor
    644      */
    645     removeHighlight: function(highlightDescriptor)
    646     {
    647         highlightDescriptor.clear();
    648     },
    649 
    650     /**
    651      * @param {!WebInspector.TextRange} range
    652      * @param {string} cssClass
    653      * @return {!Object}
    654      */
    655     highlightRange: function(range, cssClass)
    656     {
    657         cssClass = "CodeMirror-persist-highlight " + cssClass;
    658         var pos = WebInspector.CodeMirrorUtils.toPos(range);
    659         ++pos.end.ch;
    660         return this._codeMirror.markText(pos.start, pos.end, {
    661             className: cssClass,
    662             startStyle: cssClass + "-start",
    663             endStyle: cssClass + "-end"
    664         });
    665     },
    666 
    667     /**
    668      * @return {!Element}
    669      */
    670     defaultFocusedElement: function()
    671     {
    672         return this.element;
    673     },
    674 
    675     focus: function()
    676     {
    677         this._codeMirror.focus();
    678     },
    679 
    680     _handleElementFocus: function()
    681     {
    682         this._codeMirror.focus();
    683     },
    684 
    685     beginUpdates: function()
    686     {
    687         ++this._nestedUpdatesCounter;
    688     },
    689 
    690     endUpdates: function()
    691     {
    692         if (!--this._nestedUpdatesCounter)
    693             this._codeMirror.refresh();
    694     },
    695 
    696     /**
    697      * @param {number} lineNumber
    698      */
    699     _revealLine: function(lineNumber)
    700     {
    701         this._innerRevealLine(lineNumber, this._codeMirror.getScrollInfo());
    702     },
    703 
    704     /**
    705      * @param {number} lineNumber
    706      * @param {!{left: number, top: number, width: number, height: number, clientWidth: number, clientHeight: number}} scrollInfo
    707      */
    708     _innerRevealLine: function(lineNumber, scrollInfo)
    709     {
    710         var topLine = this._codeMirror.lineAtHeight(scrollInfo.top, "local");
    711         var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
    712         var linesPerScreen = bottomLine - topLine + 1;
    713         if (lineNumber < topLine) {
    714             var topLineToReveal = Math.max(lineNumber - (linesPerScreen / 2) + 1, 0) | 0;
    715             this._codeMirror.scrollIntoView(new CodeMirror.Pos(topLineToReveal, 0));
    716         } else if (lineNumber > bottomLine) {
    717             var bottomLineToReveal = Math.min(lineNumber + (linesPerScreen / 2) - 1, this.linesCount - 1) | 0;
    718             this._codeMirror.scrollIntoView(new CodeMirror.Pos(bottomLineToReveal, 0));
    719         }
    720     },
    721 
    722     _gutterClick: function(instance, lineNumber, gutter, event)
    723     {
    724         this.dispatchEventToListeners(WebInspector.TextEditor.Events.GutterClick, { lineNumber: lineNumber, event: event });
    725     },
    726 
    727     _contextMenu: function(event)
    728     {
    729         var contextMenu = new WebInspector.ContextMenu(event);
    730         var target = event.target.enclosingNodeOrSelfWithClass("CodeMirror-gutter-elt");
    731         if (target)
    732             this._delegate.populateLineGutterContextMenu(contextMenu, parseInt(target.textContent, 10) - 1);
    733         else
    734             this._delegate.populateTextAreaContextMenu(contextMenu, 0);
    735         contextMenu.show();
    736     },
    737 
    738     /**
    739      * @param {number} lineNumber
    740      * @param {boolean} disabled
    741      * @param {boolean} conditional
    742      */
    743     addBreakpoint: function(lineNumber, disabled, conditional)
    744     {
    745         if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
    746             return;
    747         var className = "cm-breakpoint" + (conditional ? " cm-breakpoint-conditional" : "") + (disabled ? " cm-breakpoint-disabled" : "");
    748         this._codeMirror.addLineClass(lineNumber, "wrap", className);
    749     },
    750 
    751     /**
    752      * @param {number} lineNumber
    753      */
    754     removeBreakpoint: function(lineNumber)
    755     {
    756         if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
    757             return;
    758         var wrapClasses = this._codeMirror.getLineHandle(lineNumber).wrapClass;
    759         if (!wrapClasses)
    760             return;
    761         var classes = wrapClasses.split(" ");
    762         for (var i = 0; i < classes.length; ++i) {
    763             if (classes[i].startsWith("cm-breakpoint"))
    764                 this._codeMirror.removeLineClass(lineNumber, "wrap", classes[i]);
    765         }
    766     },
    767 
    768     /**
    769      * @param {number} lineNumber
    770      */
    771     setExecutionLine: function(lineNumber)
    772     {
    773         this.clearPositionHighlight();
    774         this._executionLine = this._codeMirror.getLineHandle(lineNumber);
    775         if (!this._executionLine)
    776             return;
    777         this._codeMirror.addLineClass(this._executionLine, "wrap", "cm-execution-line");
    778     },
    779 
    780     clearExecutionLine: function()
    781     {
    782         this.clearPositionHighlight();
    783         if (this._executionLine)
    784             this._codeMirror.removeLineClass(this._executionLine, "wrap", "cm-execution-line");
    785         delete this._executionLine;
    786     },
    787 
    788     /**
    789      * @param {number} lineNumber
    790      * @param {!Element} element
    791      */
    792     addDecoration: function(lineNumber, element)
    793     {
    794         var widget = this._codeMirror.addLineWidget(lineNumber, element);
    795         this._elementToWidget.put(element, widget);
    796     },
    797 
    798     /**
    799      * @param {number} lineNumber
    800      * @param {!Element} element
    801      */
    802     removeDecoration: function(lineNumber, element)
    803     {
    804         var widget = this._elementToWidget.remove(element);
    805         if (widget)
    806             this._codeMirror.removeLineWidget(widget);
    807     },
    808 
    809     /**
    810      * @param {number} lineNumber
    811      * @param {number=} columnNumber
    812      * @param {boolean=} shouldHighlight
    813      */
    814     revealPosition: function(lineNumber, columnNumber, shouldHighlight)
    815     {
    816         lineNumber = Number.constrain(lineNumber, 0, this._codeMirror.lineCount() - 1);
    817         if (typeof columnNumber !== "number")
    818             columnNumber = 0;
    819         columnNumber = Number.constrain(columnNumber, 0, this._codeMirror.getLine(lineNumber).length);
    820 
    821         this.clearPositionHighlight();
    822         this._highlightedLine = this._codeMirror.getLineHandle(lineNumber);
    823         if (!this._highlightedLine)
    824           return;
    825         this._revealLine(lineNumber);
    826         if (shouldHighlight) {
    827             this._codeMirror.addLineClass(this._highlightedLine, null, "cm-highlight");
    828             this._clearHighlightTimeout = setTimeout(this.clearPositionHighlight.bind(this), 2000);
    829         }
    830         this.setSelection(WebInspector.TextRange.createFromLocation(lineNumber, columnNumber));
    831     },
    832 
    833     clearPositionHighlight: function()
    834     {
    835         if (this._clearHighlightTimeout)
    836             clearTimeout(this._clearHighlightTimeout);
    837         delete this._clearHighlightTimeout;
    838 
    839         if (this._highlightedLine)
    840             this._codeMirror.removeLineClass(this._highlightedLine, null, "cm-highlight");
    841         delete this._highlightedLine;
    842     },
    843 
    844     /**
    845      * @return {!Array.<!Element>}
    846      */
    847     elementsToRestoreScrollPositionsFor: function()
    848     {
    849         return [];
    850     },
    851 
    852     /**
    853      * @param {!WebInspector.TextEditor} textEditor
    854      */
    855     inheritScrollPositions: function(textEditor)
    856     {
    857     },
    858 
    859     /**
    860      * @param {number} width
    861      * @param {number} height
    862      */
    863     _updatePaddingBottom: function(width, height)
    864     {
    865         var scrollInfo = this._codeMirror.getScrollInfo();
    866         var newPaddingBottom;
    867         var linesElement = this.element.firstElementChild.querySelector(".CodeMirror-lines");
    868         var lineCount = this._codeMirror.lineCount();
    869         if (lineCount <= 1)
    870             newPaddingBottom = 0;
    871         else
    872             newPaddingBottom = Math.max(scrollInfo.clientHeight - this._codeMirror.getLineHandle(this._codeMirror.lastLine()).height, 0);
    873         newPaddingBottom += "px";
    874         linesElement.style.paddingBottom = newPaddingBottom;
    875         this._codeMirror.setSize(width, height);
    876     },
    877 
    878     _resizeEditor: function()
    879     {
    880         var parentElement = this.element.parentElement;
    881         if (!parentElement || !this.isShowing())
    882             return;
    883         var scrollLeft = this._codeMirror.doc.scrollLeft;
    884         var scrollTop = this._codeMirror.doc.scrollTop;
    885         var width = parentElement.offsetWidth;
    886         var height = parentElement.offsetHeight;
    887         this._codeMirror.setSize(width, height);
    888         this._updatePaddingBottom(width, height);
    889         this._codeMirror.scrollTo(scrollLeft, scrollTop);
    890     },
    891 
    892     onResize: function()
    893     {
    894         this._autocompleteController.finishAutocomplete();
    895         this._resizeEditor();
    896     },
    897 
    898     /**
    899      * @param {!WebInspector.TextRange} range
    900      * @param {string} text
    901      * @return {!WebInspector.TextRange}
    902      */
    903     editRange: function(range, text)
    904     {
    905         var pos = WebInspector.CodeMirrorUtils.toPos(range);
    906         this._codeMirror.replaceRange(text, pos.start, pos.end);
    907         var newRange = WebInspector.CodeMirrorUtils.toRange(pos.start, this._codeMirror.posFromIndex(this._codeMirror.indexFromPos(pos.start) + text.length));
    908         this._delegate.onTextChanged(range, newRange);
    909         if (WebInspector.settings.textEditorAutoDetectIndent.get())
    910             this._updateEditorIndentation();
    911         return newRange;
    912     },
    913 
    914     /**
    915      * @param {number} lineNumber
    916      * @param {number} column
    917      * @param {function(string):boolean} isWordChar
    918      * @return {!WebInspector.TextRange}
    919      */
    920     _wordRangeForCursorPosition: function(lineNumber, column, isWordChar)
    921     {
    922         var line = this.line(lineNumber);
    923         var wordStart = column;
    924         if (column !== 0 && isWordChar(line.charAt(column - 1))) {
    925             wordStart = column - 1;
    926             while (wordStart > 0 && isWordChar(line.charAt(wordStart - 1)))
    927                 --wordStart;
    928         }
    929         var wordEnd = column;
    930         while (wordEnd < line.length && isWordChar(line.charAt(wordEnd)))
    931             ++wordEnd;
    932         return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, wordEnd);
    933     },
    934 
    935     /**
    936      * @param {!WebInspector.CodeMirrorTextEditor.ChangeObject} changeObject
    937      * @return {{oldRange: !WebInspector.TextRange, newRange: !WebInspector.TextRange}}
    938      */
    939     _changeObjectToEditOperation: function(changeObject)
    940     {
    941         var oldRange = WebInspector.CodeMirrorUtils.toRange(changeObject.from, changeObject.to);
    942         var newRange = oldRange.clone();
    943         var linesAdded = changeObject.text.length;
    944         if (linesAdded === 0) {
    945             newRange.endLine = newRange.startLine;
    946             newRange.endColumn = newRange.startColumn;
    947         } else if (linesAdded === 1) {
    948             newRange.endLine = newRange.startLine;
    949             newRange.endColumn = newRange.startColumn + changeObject.text[0].length;
    950         } else {
    951             newRange.endLine = newRange.startLine + linesAdded - 1;
    952             newRange.endColumn = changeObject.text[linesAdded - 1].length;
    953         }
    954         return {
    955             oldRange: oldRange,
    956             newRange: newRange
    957         };
    958     },
    959 
    960     /**
    961      * @param {!CodeMirror} codeMirror
    962      * @param {!Array.<!WebInspector.CodeMirrorTextEditor.ChangeObject>} changes
    963      */
    964     _changes: function(codeMirror, changes)
    965     {
    966         if (!changes.length)
    967             return;
    968         // 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.
    969         var hasOneLine = this._codeMirror.lineCount() === 1;
    970         if (hasOneLine !== this._hasOneLine)
    971             this._resizeEditor();
    972         this._hasOneLine = hasOneLine;
    973         var widgets = this._elementToWidget.values();
    974         for (var i = 0; i < widgets.length; ++i)
    975             this._codeMirror.removeLineWidget(widgets[i]);
    976         this._elementToWidget.clear();
    977 
    978         for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) {
    979             var changeObject = changes[changeIndex];
    980 
    981             var editInfo = this._changeObjectToEditOperation(changeObject);
    982             if (!this._muteTextChangedEvent)
    983                 this._delegate.onTextChanged(editInfo.oldRange, editInfo.newRange);
    984         }
    985     },
    986 
    987     _cursorActivity: function()
    988     {
    989         var start = this._codeMirror.getCursor("anchor");
    990         var end = this._codeMirror.getCursor("head");
    991         this._delegate.selectionChanged(WebInspector.CodeMirrorUtils.toRange(start, end));
    992         if (!this._isSearchActive())
    993             this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));
    994     },
    995 
    996     /**
    997      * @param {!CodeMirror} codeMirror
    998      * @param {{ranges: !Array.<{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>}} selection
    999      */
   1000     _beforeSelectionChange: function(codeMirror, selection)
   1001     {
   1002         this._selectNextOccurrenceController.selectionWillChange();
   1003         if (!this._isHandlingMouseDownEvent)
   1004             return;
   1005         if (!selection.ranges.length)
   1006             return;
   1007         var primarySelection = selection.ranges[0];
   1008         this._reportJump(this.selection(), WebInspector.CodeMirrorUtils.toRange(primarySelection.anchor, primarySelection.head));
   1009     },
   1010 
   1011     /**
   1012      * @param {?WebInspector.TextRange} from
   1013      * @param {?WebInspector.TextRange} to
   1014      */
   1015     _reportJump: function(from, to)
   1016     {
   1017         if (from && to && from.equal(to))
   1018             return;
   1019         this._delegate.onJumpToPosition(from, to);
   1020     },
   1021 
   1022     _scroll: function()
   1023     {
   1024         if (this._scrollTimer)
   1025             clearTimeout(this._scrollTimer);
   1026         var topmostLineNumber = this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local");
   1027         this._scrollTimer = setTimeout(this._delegate.scrollChanged.bind(this._delegate, topmostLineNumber), 100);
   1028     },
   1029 
   1030     _focus: function()
   1031     {
   1032         this._delegate.editorFocused();
   1033     },
   1034 
   1035     /**
   1036      * @param {number} lineNumber
   1037      */
   1038     scrollToLine: function(lineNumber)
   1039     {
   1040         var pos = new CodeMirror.Pos(lineNumber, 0);
   1041         var coords = this._codeMirror.charCoords(pos, "local");
   1042         this._codeMirror.scrollTo(0, coords.top);
   1043     },
   1044 
   1045     /**
   1046      * @return {number}
   1047      */
   1048     firstVisibleLine: function()
   1049     {
   1050         return this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local");
   1051     },
   1052 
   1053     /**
   1054      * @return {number}
   1055      */
   1056     lastVisibleLine: function()
   1057     {
   1058         var scrollInfo = this._codeMirror.getScrollInfo();
   1059         return this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
   1060     },
   1061 
   1062     /**
   1063      * @return {!WebInspector.TextRange}
   1064      */
   1065     selection: function()
   1066     {
   1067         var start = this._codeMirror.getCursor("anchor");
   1068         var end = this._codeMirror.getCursor("head");
   1069 
   1070         return WebInspector.CodeMirrorUtils.toRange(start, end);
   1071     },
   1072 
   1073     /**
   1074      * @return {!Array.<!WebInspector.TextRange>}
   1075      */
   1076     selections: function()
   1077     {
   1078         var selectionList = this._codeMirror.listSelections();
   1079         var result = [];
   1080         for (var i = 0; i < selectionList.length; ++i) {
   1081             var selection = selectionList[i];
   1082             result.push(WebInspector.CodeMirrorUtils.toRange(selection.anchor, selection.head));
   1083         }
   1084         return result;
   1085     },
   1086 
   1087     /**
   1088      * @return {?WebInspector.TextRange}
   1089      */
   1090     lastSelection: function()
   1091     {
   1092         return this._lastSelection;
   1093     },
   1094 
   1095     /**
   1096      * @param {!WebInspector.TextRange} textRange
   1097      */
   1098     setSelection: function(textRange)
   1099     {
   1100         this._lastSelection = textRange;
   1101         var pos = WebInspector.CodeMirrorUtils.toPos(textRange);
   1102         this._codeMirror.setSelection(pos.start, pos.end);
   1103     },
   1104 
   1105     /**
   1106      * @param {!Array.<!WebInspector.TextRange>} ranges
   1107      * @param {number=} primarySelectionIndex
   1108      */
   1109     setSelections: function(ranges, primarySelectionIndex)
   1110     {
   1111         var selections = [];
   1112         for (var i = 0; i < ranges.length; ++i) {
   1113             var selection = WebInspector.CodeMirrorUtils.toPos(ranges[i]);
   1114             selections.push({
   1115                 anchor: selection.start,
   1116                 head: selection.end
   1117             });
   1118         }
   1119         primarySelectionIndex = primarySelectionIndex || 0;
   1120         this._codeMirror.setSelections(selections, primarySelectionIndex, { scroll: false });
   1121     },
   1122 
   1123     /**
   1124      * @param {string} text
   1125      */
   1126     _detectLineSeparator: function(text)
   1127     {
   1128         this._lineSeparator = text.indexOf("\r\n") >= 0 ? "\r\n" : "\n";
   1129     },
   1130 
   1131     /**
   1132      * @param {string} text
   1133      */
   1134     setText: function(text)
   1135     {
   1136         this._muteTextChangedEvent = true;
   1137         if (text.length > WebInspector.CodeMirrorTextEditor.MaxEditableTextSize) {
   1138             this._autocompleteController.setEnabled(false);
   1139             this.setReadOnly(true);
   1140         }
   1141         this._codeMirror.setValue(text);
   1142         this._updateEditorIndentation();
   1143         if (this._shouldClearHistory) {
   1144             this._codeMirror.clearHistory();
   1145             this._shouldClearHistory = false;
   1146         }
   1147         this._detectLineSeparator(text);
   1148         delete this._muteTextChangedEvent;
   1149     },
   1150 
   1151     /**
   1152      * @return {string}
   1153      */
   1154     text: function()
   1155     {
   1156         return this._codeMirror.getValue().replace(/\n/g, this._lineSeparator);
   1157     },
   1158 
   1159     /**
   1160      * @return {!WebInspector.TextRange}
   1161      */
   1162     range: function()
   1163     {
   1164         var lineCount = this.linesCount;
   1165         var lastLine = this._codeMirror.getLine(lineCount - 1);
   1166         return WebInspector.CodeMirrorUtils.toRange(new CodeMirror.Pos(0, 0), new CodeMirror.Pos(lineCount - 1, lastLine.length));
   1167     },
   1168 
   1169     /**
   1170      * @param {number} lineNumber
   1171      * @return {string}
   1172      */
   1173     line: function(lineNumber)
   1174     {
   1175         return this._codeMirror.getLine(lineNumber);
   1176     },
   1177 
   1178     /**
   1179      * @return {number}
   1180      */
   1181     get linesCount()
   1182     {
   1183         return this._codeMirror.lineCount();
   1184     },
   1185 
   1186     /**
   1187      * @param {number} line
   1188      * @param {string} name
   1189      * @param {?Object} value
   1190      */
   1191     setAttribute: function(line, name, value)
   1192     {
   1193         if (line < 0 || line >= this._codeMirror.lineCount())
   1194             return;
   1195         var handle = this._codeMirror.getLineHandle(line);
   1196         if (handle.attributes === undefined) handle.attributes = {};
   1197         handle.attributes[name] = value;
   1198     },
   1199 
   1200     /**
   1201      * @param {number} line
   1202      * @param {string} name
   1203      * @return {?Object} value
   1204      */
   1205     getAttribute: function(line, name)
   1206     {
   1207         if (line < 0 || line >= this._codeMirror.lineCount())
   1208             return null;
   1209         var handle = this._codeMirror.getLineHandle(line);
   1210         return handle.attributes && handle.attributes[name] !== undefined ? handle.attributes[name] : null;
   1211     },
   1212 
   1213     /**
   1214      * @param {number} line
   1215      * @param {string} name
   1216      */
   1217     removeAttribute: function(line, name)
   1218     {
   1219         if (line < 0 || line >= this._codeMirror.lineCount())
   1220             return;
   1221         var handle = this._codeMirror.getLineHandle(line);
   1222         if (handle && handle.attributes)
   1223             delete handle.attributes[name];
   1224     },
   1225 
   1226     /**
   1227      * @param {number} lineNumber
   1228      * @param {number} columnNumber
   1229      * @return {!WebInspector.TextEditorPositionHandle}
   1230      */
   1231     textEditorPositionHandle: function(lineNumber, columnNumber)
   1232     {
   1233         return new WebInspector.CodeMirrorPositionHandle(this._codeMirror, new CodeMirror.Pos(lineNumber, columnNumber));
   1234     },
   1235 
   1236     __proto__: WebInspector.VBox.prototype
   1237 }
   1238 
   1239 /**
   1240  * @constructor
   1241  * @implements {WebInspector.TextEditorPositionHandle}
   1242  * @param {!CodeMirror} codeMirror
   1243  * @param {!CodeMirror.Pos} pos
   1244  */
   1245 WebInspector.CodeMirrorPositionHandle = function(codeMirror, pos)
   1246 {
   1247     this._codeMirror = codeMirror;
   1248     this._lineHandle = codeMirror.getLineHandle(pos.line);
   1249     this._columnNumber = pos.ch;
   1250 }
   1251 
   1252 WebInspector.CodeMirrorPositionHandle.prototype = {
   1253     /**
   1254      * @return {?{lineNumber: number, columnNumber: number}}
   1255      */
   1256     resolve: function()
   1257     {
   1258         var lineNumber = this._codeMirror.getLineNumber(this._lineHandle);
   1259         if (typeof lineNumber !== "number")
   1260             return null;
   1261         return {
   1262             lineNumber: lineNumber,
   1263             columnNumber: this._columnNumber
   1264         };
   1265     },
   1266 
   1267     /**
   1268      * @param {!WebInspector.TextEditorPositionHandle} positionHandle
   1269      * @return {boolean}
   1270      */
   1271     equal: function(positionHandle)
   1272     {
   1273         return positionHandle._lineHandle === this._lineHandle && positionHandle._columnNumber == this._columnNumber && positionHandle._codeMirror === this._codeMirror;
   1274     }
   1275 }
   1276 
   1277 /**
   1278  * @constructor
   1279  * @param {!WebInspector.CodeMirrorTextEditor} textEditor
   1280  * @param {!CodeMirror} codeMirror
   1281  */
   1282 WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(textEditor, codeMirror)
   1283 {
   1284     this._textEditor = textEditor;
   1285     this._codeMirror = codeMirror;
   1286 }
   1287 
   1288 WebInspector.CodeMirrorTextEditor.TokenHighlighter.prototype = {
   1289     /**
   1290      * @param {!RegExp} regex
   1291      * @param {?WebInspector.TextRange} range
   1292      */
   1293     highlightSearchResults: function(regex, range)
   1294     {
   1295         var oldRegex = this._highlightRegex;
   1296         this._highlightRegex = regex;
   1297         this._highlightRange = range;
   1298         if (this._searchResultMarker) {
   1299             this._searchResultMarker.clear();
   1300             delete this._searchResultMarker;
   1301         }
   1302         if (this._highlightDescriptor && this._highlightDescriptor.selectionStart)
   1303             this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection");
   1304         var selectionStart = this._highlightRange ? new CodeMirror.Pos(this._highlightRange.startLine, this._highlightRange.startColumn) : null;
   1305         if (selectionStart)
   1306             this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection");
   1307         if (this._highlightRegex === oldRegex) {
   1308             // Do not re-add overlay mode if regex did not change for better performance.
   1309             if (this._highlightDescriptor)
   1310                 this._highlightDescriptor.selectionStart = selectionStart;
   1311         } else {
   1312             this._removeHighlight();
   1313             this._setHighlighter(this._searchHighlighter.bind(this, this._highlightRegex), selectionStart);
   1314         }
   1315         if (this._highlightRange) {
   1316             var pos = WebInspector.CodeMirrorUtils.toPos(this._highlightRange);
   1317             this._searchResultMarker = this._codeMirror.markText(pos.start, pos.end, {className: "cm-column-with-selection"});
   1318         }
   1319     },
   1320 
   1321     /**
   1322      * @return {!RegExp|undefined}
   1323      */
   1324     highlightedRegex: function()
   1325     {
   1326         return this._highlightRegex;
   1327     },
   1328 
   1329     highlightSelectedTokens: function()
   1330     {
   1331         delete this._highlightRegex;
   1332         delete this._highlightRange;
   1333 
   1334         if (this._highlightDescriptor && this._highlightDescriptor.selectionStart)
   1335             this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection");
   1336         this._removeHighlight();
   1337         var selectionStart = this._codeMirror.getCursor("start");
   1338         var selectionEnd = this._codeMirror.getCursor("end");
   1339         if (selectionStart.line !== selectionEnd.line)
   1340             return;
   1341         if (selectionStart.ch === selectionEnd.ch)
   1342             return;
   1343 
   1344         var selections = this._codeMirror.getSelections();
   1345         if (selections.length > 1)
   1346             return;
   1347         var selectedText = selections[0];
   1348         if (this._isWord(selectedText, selectionStart.line, selectionStart.ch, selectionEnd.ch)) {
   1349             if (selectionStart)
   1350                 this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection")
   1351             this._setHighlighter(this._tokenHighlighter.bind(this, selectedText, selectionStart), selectionStart);
   1352         }
   1353     },
   1354 
   1355     /**
   1356      * @param {string} selectedText
   1357      * @param {number} lineNumber
   1358      * @param {number} startColumn
   1359      * @param {number} endColumn
   1360      */
   1361     _isWord: function(selectedText, lineNumber, startColumn, endColumn)
   1362     {
   1363         var line = this._codeMirror.getLine(lineNumber);
   1364         var leftBound = startColumn === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(startColumn - 1));
   1365         var rightBound = endColumn === line.length || !WebInspector.TextUtils.isWordChar(line.charAt(endColumn));
   1366         return leftBound && rightBound && WebInspector.TextUtils.isWord(selectedText);
   1367     },
   1368 
   1369     _removeHighlight: function()
   1370     {
   1371         if (this._highlightDescriptor) {
   1372             this._codeMirror.removeOverlay(this._highlightDescriptor.overlay);
   1373             delete this._highlightDescriptor;
   1374         }
   1375     },
   1376 
   1377     /**
   1378      * @param {!RegExp} regex
   1379      * @param {!CodeMirror.StringStream} stream
   1380      */
   1381     _searchHighlighter: function(regex, stream)
   1382     {
   1383         if (stream.column() === 0)
   1384             delete this._searchMatchLength;
   1385         if (this._searchMatchLength) {
   1386             if (this._searchMatchLength > 2) {
   1387                 for (var i = 0; i < this._searchMatchLength - 2; ++i)
   1388                     stream.next();
   1389                 this._searchMatchLength = 1;
   1390                 return "search-highlight";
   1391             } else {
   1392                 stream.next();
   1393                 delete this._searchMatchLength;
   1394                 return "search-highlight search-highlight-end";
   1395             }
   1396         }
   1397         var match = stream.match(regex, false);
   1398         if (match) {
   1399             stream.next();
   1400             var matchLength = match[0].length;
   1401             if (matchLength === 1)
   1402                 return "search-highlight search-highlight-full";
   1403             this._searchMatchLength = matchLength;
   1404             return "search-highlight search-highlight-start";
   1405         }
   1406 
   1407         while (!stream.match(regex, false) && stream.next()) {};
   1408     },
   1409 
   1410     /**
   1411      * @param {string} token
   1412      * @param {!CodeMirror.Pos} selectionStart
   1413      * @param {!CodeMirror.StringStream} stream
   1414      */
   1415     _tokenHighlighter: function(token, selectionStart, stream)
   1416     {
   1417         var tokenFirstChar = token.charAt(0);
   1418         if (stream.match(token) && (stream.eol() || !WebInspector.TextUtils.isWordChar(stream.peek())))
   1419             return stream.column() === selectionStart.ch ? "token-highlight column-with-selection" : "token-highlight";
   1420 
   1421         var eatenChar;
   1422         do {
   1423             eatenChar = stream.next();
   1424         } while (eatenChar && (WebInspector.TextUtils.isWordChar(eatenChar) || stream.peek() !== tokenFirstChar));
   1425     },
   1426 
   1427     /**
   1428      * @param {function(!CodeMirror.StringStream)} highlighter
   1429      * @param {?CodeMirror.Pos} selectionStart
   1430      */
   1431     _setHighlighter: function(highlighter, selectionStart)
   1432     {
   1433         var overlayMode = {
   1434             token: highlighter
   1435         };
   1436         this._codeMirror.addOverlay(overlayMode);
   1437         this._highlightDescriptor = {
   1438             overlay: overlayMode,
   1439             selectionStart: selectionStart
   1440         };
   1441     }
   1442 }
   1443 
   1444 /**
   1445  * @constructor
   1446  * @param {!CodeMirror} codeMirror
   1447  */
   1448 WebInspector.CodeMirrorTextEditor.BlockIndentController = function(codeMirror)
   1449 {
   1450     codeMirror.addKeyMap(this);
   1451 }
   1452 
   1453 WebInspector.CodeMirrorTextEditor.BlockIndentController.prototype = {
   1454     name: "blockIndentKeymap",
   1455 
   1456     /**
   1457      * @return {*}
   1458      */
   1459     Enter: function(codeMirror)
   1460     {
   1461         if (codeMirror.somethingSelected())
   1462             return CodeMirror.Pass;
   1463         var cursor = codeMirror.getCursor();
   1464         if (cursor.ch === 0)
   1465             return CodeMirror.Pass;
   1466         var line = codeMirror.getLine(cursor.line);
   1467         if (line.substr(cursor.ch - 1, 2) === "{}") {
   1468             codeMirror.execCommand("newlineAndIndent");
   1469             codeMirror.setCursor(cursor);
   1470             codeMirror.execCommand("newlineAndIndent");
   1471             codeMirror.execCommand("indentMore");
   1472         } else if (line.substr(cursor.ch - 1, 1) === "{") {
   1473             codeMirror.execCommand("newlineAndIndent");
   1474             codeMirror.execCommand("indentMore");
   1475         } else
   1476             return CodeMirror.Pass;
   1477     },
   1478 
   1479     /**
   1480      * @return {*}
   1481      */
   1482     "'}'": function(codeMirror)
   1483     {
   1484         var cursor = codeMirror.getCursor();
   1485         var line = codeMirror.getLine(cursor.line);
   1486         for (var i = 0 ; i < line.length; ++i) {
   1487             if (!WebInspector.TextUtils.isSpaceChar(line.charAt(i)))
   1488                 return CodeMirror.Pass;
   1489         }
   1490 
   1491         codeMirror.replaceRange("}", cursor);
   1492         var matchingBracket = codeMirror.findMatchingBracket(cursor);
   1493         if (!matchingBracket || !matchingBracket.match)
   1494             return;
   1495 
   1496         line = codeMirror.getLine(matchingBracket.to.line);
   1497         var desiredIndentation = 0;
   1498         while (desiredIndentation < line.length && WebInspector.TextUtils.isSpaceChar(line.charAt(desiredIndentation)))
   1499             ++desiredIndentation;
   1500 
   1501         codeMirror.replaceRange(line.substr(0, desiredIndentation) + "}", new CodeMirror.Pos(cursor.line, 0), new CodeMirror.Pos(cursor.line, cursor.ch + 1));
   1502     }
   1503 }
   1504 
   1505 /**
   1506  * @constructor
   1507  * @param {!CodeMirror} codeMirror
   1508  */
   1509 WebInspector.CodeMirrorTextEditor.FixWordMovement = function(codeMirror)
   1510 {
   1511     function moveLeft(shift, codeMirror)
   1512     {
   1513         codeMirror.setExtending(shift);
   1514         var cursor = codeMirror.getCursor("head");
   1515         codeMirror.execCommand("goGroupLeft");
   1516         var newCursor = codeMirror.getCursor("head");
   1517         if (newCursor.ch === 0 && newCursor.line !== 0) {
   1518             codeMirror.setExtending(false);
   1519             return;
   1520         }
   1521 
   1522         var skippedText = codeMirror.getRange(newCursor, cursor, "#");
   1523         if (/^\s+$/.test(skippedText))
   1524             codeMirror.execCommand("goGroupLeft");
   1525         codeMirror.setExtending(false);
   1526     }
   1527 
   1528     function moveRight(shift, codeMirror)
   1529     {
   1530         codeMirror.setExtending(shift);
   1531         var cursor = codeMirror.getCursor("head");
   1532         codeMirror.execCommand("goGroupRight");
   1533         var newCursor = codeMirror.getCursor("head");
   1534         if (newCursor.ch === 0 && newCursor.line !== 0) {
   1535             codeMirror.setExtending(false);
   1536             return;
   1537         }
   1538 
   1539         var skippedText = codeMirror.getRange(cursor, newCursor, "#");
   1540         if (/^\s+$/.test(skippedText))
   1541             codeMirror.execCommand("goGroupRight");
   1542         codeMirror.setExtending(false);
   1543     }
   1544 
   1545     var modifierKey = WebInspector.isMac() ? "Alt" : "Ctrl";
   1546     var leftKey = modifierKey + "-Left";
   1547     var rightKey = modifierKey + "-Right";
   1548     var keyMap = {};
   1549     keyMap[leftKey] = moveLeft.bind(null, false);
   1550     keyMap[rightKey] = moveRight.bind(null, false);
   1551     keyMap["Shift-" + leftKey] = moveLeft.bind(null, true);
   1552     keyMap["Shift-" + rightKey] = moveRight.bind(null, true);
   1553     codeMirror.addKeyMap(keyMap);
   1554 }
   1555 
   1556 /**
   1557  * @interface
   1558  */
   1559 WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI = function() {}
   1560 
   1561 WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI.prototype = {
   1562     dispose: function() { },
   1563 
   1564     /**
   1565      * @param {boolean} enabled
   1566      */
   1567     setEnabled: function(enabled) { },
   1568 
   1569     /**
   1570      * @param {string} mimeType
   1571      */
   1572     setMimeType: function(mimeType) { },
   1573 
   1574     autocomplete: function() { },
   1575 
   1576     finishAutocomplete: function() { },
   1577 
   1578     /**
   1579      * @param {?Event} e
   1580      * @return {boolean}
   1581      */
   1582     keyDown: function(e) { }
   1583 }
   1584 
   1585 /**
   1586  * @constructor
   1587  * @implements {WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI}
   1588  */
   1589 WebInspector.CodeMirrorTextEditor.DummyAutocompleteController = function() { }
   1590 
   1591 WebInspector.CodeMirrorTextEditor.DummyAutocompleteController.prototype = {
   1592     dispose: function() { },
   1593 
   1594     /**
   1595      * @param {boolean} enabled
   1596      */
   1597     setEnabled: function(enabled) { },
   1598 
   1599     /**
   1600      * @param {string} mimeType
   1601      */
   1602     setMimeType: function(mimeType) { },
   1603 
   1604     autocomplete: function() { },
   1605 
   1606     finishAutocomplete: function() { },
   1607 
   1608     /**
   1609      * @param {?Event} e
   1610      * @return {boolean}
   1611      */
   1612     keyDown: function(e)
   1613     {
   1614         return false;
   1615     }
   1616 }
   1617 
   1618 /**
   1619  * @constructor
   1620  * @implements {WebInspector.SuggestBoxDelegate}
   1621  * @implements {WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI}
   1622  * @param {!WebInspector.CodeMirrorTextEditor} textEditor
   1623  * @param {!CodeMirror} codeMirror
   1624  * @param {?WebInspector.CompletionDictionary} dictionary
   1625  */
   1626 WebInspector.CodeMirrorTextEditor.AutocompleteController = function(textEditor, codeMirror, dictionary)
   1627 {
   1628     this._textEditor = textEditor;
   1629     this._codeMirror = codeMirror;
   1630 
   1631     this._onScroll = this._onScroll.bind(this);
   1632     this._onCursorActivity = this._onCursorActivity.bind(this);
   1633     this._changes = this._changes.bind(this);
   1634     this._beforeChange = this._beforeChange.bind(this);
   1635     this._blur = this._blur.bind(this);
   1636     this._codeMirror.on("scroll", this._onScroll);
   1637     this._codeMirror.on("cursorActivity", this._onCursorActivity);
   1638     this._codeMirror.on("changes", this._changes);
   1639     this._codeMirror.on("beforeChange", this._beforeChange);
   1640     this._codeMirror.on("blur", this._blur);
   1641 
   1642     this._additionalWordChars = WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars;
   1643     this._enabled = true;
   1644 
   1645     this._dictionary = dictionary;
   1646     this._addTextToCompletionDictionary(this._textEditor.text());
   1647 }
   1648 
   1649 WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy = new WebInspector.CodeMirrorTextEditor.DummyAutocompleteController();
   1650 WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars = {};
   1651 WebInspector.CodeMirrorTextEditor._CSSAdditionalWordChars = { ".": true, "-": true };
   1652 
   1653 WebInspector.CodeMirrorTextEditor.AutocompleteController.prototype = {
   1654     dispose: function()
   1655     {
   1656         this._codeMirror.off("scroll", this._onScroll);
   1657         this._codeMirror.off("cursorActivity", this._onCursorActivity);
   1658         this._codeMirror.off("changes", this._changes);
   1659         this._codeMirror.off("beforeChange", this._beforeChange);
   1660         this._codeMirror.off("blur", this._blur);
   1661     },
   1662 
   1663     /**
   1664      * @param {boolean} enabled
   1665      */
   1666     setEnabled: function(enabled)
   1667     {
   1668         if (enabled === this._enabled)
   1669             return;
   1670         this._enabled = enabled;
   1671         if (!enabled)
   1672             this._dictionary.reset();
   1673         else
   1674             this._addTextToCompletionDictionary(this._textEditor.text());
   1675     },
   1676 
   1677     /**
   1678      * @param {string} mimeType
   1679      */
   1680     setMimeType: function(mimeType)
   1681     {
   1682         var additionalWordChars = mimeType.indexOf("css") !== -1 ? WebInspector.CodeMirrorTextEditor._CSSAdditionalWordChars : WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars;
   1683         if (additionalWordChars !== this._additionalWordChars) {
   1684             this._additionalWordChars = additionalWordChars;
   1685             this._dictionary.reset();
   1686             this._addTextToCompletionDictionary(this._textEditor.text());
   1687         }
   1688     },
   1689 
   1690     /**
   1691      * @param {string} char
   1692      * @return {boolean}
   1693      */
   1694     _isWordChar: function(char)
   1695     {
   1696         return WebInspector.TextUtils.isWordChar(char) || !!this._additionalWordChars[char];
   1697     },
   1698 
   1699     /**
   1700      * @param {string} word
   1701      * @return {boolean}
   1702      */
   1703     _shouldProcessWordForAutocompletion: function(word)
   1704     {
   1705         return !!word.length && (word[0] < '0' || word[0] > '9');
   1706     },
   1707 
   1708     /**
   1709      * @param {string} text
   1710      */
   1711     _addTextToCompletionDictionary: function(text)
   1712     {
   1713         if (!this._enabled)
   1714             return;
   1715         var words = WebInspector.TextUtils.textToWords(text, this._isWordChar.bind(this));
   1716         for (var i = 0; i < words.length; ++i) {
   1717             if (this._shouldProcessWordForAutocompletion(words[i]))
   1718                 this._dictionary.addWord(words[i]);
   1719         }
   1720     },
   1721 
   1722     /**
   1723      * @param {string} text
   1724      */
   1725     _removeTextFromCompletionDictionary: function(text)
   1726     {
   1727         if (!this._enabled)
   1728             return;
   1729         var words = WebInspector.TextUtils.textToWords(text, this._isWordChar.bind(this));
   1730         for (var i = 0; i < words.length; ++i) {
   1731             if (this._shouldProcessWordForAutocompletion(words[i]))
   1732                 this._dictionary.removeWord(words[i]);
   1733         }
   1734     },
   1735 
   1736     /**
   1737      * @param {!CodeMirror} codeMirror
   1738      * @param {!WebInspector.CodeMirrorTextEditor.BeforeChangeObject} changeObject
   1739      */
   1740     _beforeChange: function(codeMirror, changeObject)
   1741     {
   1742         if (!this._enabled)
   1743             return;
   1744         this._updatedLines = this._updatedLines || {};
   1745         for (var i = changeObject.from.line; i <= changeObject.to.line; ++i)
   1746             this._updatedLines[i] = this._textEditor.line(i);
   1747     },
   1748 
   1749     /**
   1750      * @param {!CodeMirror} codeMirror
   1751      * @param {!Array.<!WebInspector.CodeMirrorTextEditor.ChangeObject>} changes
   1752      */
   1753     _changes: function(codeMirror, changes)
   1754     {
   1755         if (!changes.length || !this._enabled)
   1756             return;
   1757 
   1758         if (this._updatedLines) {
   1759             for (var lineNumber in this._updatedLines)
   1760                 this._removeTextFromCompletionDictionary(this._updatedLines[lineNumber]);
   1761             delete this._updatedLines;
   1762         }
   1763 
   1764         var linesToUpdate = {};
   1765         var singleCharInput = false;
   1766         for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) {
   1767             var changeObject = changes[changeIndex];
   1768             singleCharInput = (changeObject.origin === "+input" && changeObject.text.length === 1 && changeObject.text[0].length === 1) ||
   1769                 (changeObject.origin === "+delete" && changeObject.removed.length === 1 && changeObject.removed[0].length === 1);
   1770 
   1771             var editInfo = this._textEditor._changeObjectToEditOperation(changeObject);
   1772             for (var i = editInfo.newRange.startLine; i <= editInfo.newRange.endLine; ++i)
   1773                 linesToUpdate[i] = this._textEditor.line(i);
   1774         }
   1775         for (var lineNumber in linesToUpdate)
   1776             this._addTextToCompletionDictionary(linesToUpdate[lineNumber]);
   1777 
   1778         if (singleCharInput)
   1779             this.autocomplete();
   1780     },
   1781 
   1782     _blur: function()
   1783     {
   1784         this.finishAutocomplete();
   1785     },
   1786 
   1787     /**
   1788      * @param {number} lineNumber
   1789      * @param {number} columnNumber
   1790      * @return {!WebInspector.TextRange}
   1791      */
   1792     _autocompleteWordRange: function(lineNumber, columnNumber)
   1793     {
   1794         return this._textEditor._wordRangeForCursorPosition(lineNumber, columnNumber, this._isWordChar.bind(this));
   1795     },
   1796 
   1797     /**
   1798      * @param {!WebInspector.TextRange} mainSelection
   1799      * @param {!Array.<!{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>} selections
   1800      * @return {boolean}
   1801      */
   1802     _validateSelectionsContexts: function(mainSelection, selections)
   1803     {
   1804         var mainSelectionContext = this._textEditor.copyRange(mainSelection);
   1805         for (var i = 0; i < selections.length; ++i) {
   1806             var wordRange = this._autocompleteWordRange(selections[i].head.line, selections[i].head.ch);
   1807             if (!wordRange)
   1808                 return false;
   1809             var context = this._textEditor.copyRange(wordRange);
   1810             if (context !== mainSelectionContext)
   1811                 return false;
   1812         }
   1813         return true;
   1814     },
   1815 
   1816     autocomplete: function()
   1817     {
   1818         var dictionary = this._dictionary;
   1819         if (this._codeMirror.somethingSelected()) {
   1820             this.finishAutocomplete();
   1821             return;
   1822         }
   1823 
   1824         var selections = this._codeMirror.listSelections().slice();
   1825         var topSelection = selections.shift();
   1826         var cursor = topSelection.head;
   1827         var substituteRange = this._autocompleteWordRange(cursor.line, cursor.ch);
   1828         if (!substituteRange || substituteRange.startColumn === cursor.ch || !this._validateSelectionsContexts(substituteRange, selections)) {
   1829             this.finishAutocomplete();
   1830             return;
   1831         }
   1832 
   1833         var prefixRange = substituteRange.clone();
   1834         prefixRange.endColumn = cursor.ch;
   1835 
   1836         var substituteWord = this._textEditor.copyRange(substituteRange);
   1837         var hasPrefixInDictionary = dictionary.hasWord(substituteWord);
   1838         if (hasPrefixInDictionary)
   1839             dictionary.removeWord(substituteWord);
   1840         var wordsWithPrefix = dictionary.wordsWithPrefix(this._textEditor.copyRange(prefixRange));
   1841         if (hasPrefixInDictionary)
   1842             dictionary.addWord(substituteWord);
   1843 
   1844         function sortSuggestions(a, b)
   1845         {
   1846             return dictionary.wordCount(b) - dictionary.wordCount(a) || a.length - b.length;
   1847         }
   1848 
   1849         wordsWithPrefix.sort(sortSuggestions);
   1850 
   1851         if (!this._suggestBox)
   1852             this._suggestBox = new WebInspector.SuggestBox(this, 6);
   1853         var oldPrefixRange = this._prefixRange;
   1854         this._prefixRange = prefixRange;
   1855         if (!oldPrefixRange || prefixRange.startLine !== oldPrefixRange.startLine || prefixRange.startColumn !== oldPrefixRange.startColumn)
   1856             this._updateAnchorBox();
   1857         this._suggestBox.updateSuggestions(this._anchorBox, wordsWithPrefix, 0, true, this._textEditor.copyRange(prefixRange));
   1858         if (!this._suggestBox.visible())
   1859             this.finishAutocomplete();
   1860     },
   1861 
   1862     finishAutocomplete: function()
   1863     {
   1864         if (!this._suggestBox)
   1865             return;
   1866         this._suggestBox.hide();
   1867         this._suggestBox = null;
   1868         this._prefixRange = null;
   1869         this._anchorBox = null;
   1870     },
   1871 
   1872     /**
   1873      * @param {?Event} e
   1874      * @return {boolean}
   1875      */
   1876     keyDown: function(e)
   1877     {
   1878         if (!this._suggestBox)
   1879             return false;
   1880         if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
   1881             this.finishAutocomplete();
   1882             return true;
   1883         }
   1884         if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Tab.code) {
   1885             this._suggestBox.acceptSuggestion();
   1886             this.finishAutocomplete();
   1887             return true;
   1888         }
   1889         return this._suggestBox.keyPressed(e);
   1890     },
   1891 
   1892     /**
   1893      * @param {string} suggestion
   1894      * @param {boolean=} isIntermediateSuggestion
   1895      */
   1896     applySuggestion: function(suggestion, isIntermediateSuggestion)
   1897     {
   1898         this._currentSuggestion = suggestion;
   1899     },
   1900 
   1901     acceptSuggestion: function()
   1902     {
   1903         if (this._prefixRange.endColumn - this._prefixRange.startColumn === this._currentSuggestion.length)
   1904             return;
   1905 
   1906         var selections = this._codeMirror.listSelections().slice();
   1907         var prefixLength = this._prefixRange.endColumn - this._prefixRange.startColumn;
   1908         for (var i = selections.length - 1; i >= 0; --i) {
   1909             var start = selections[i].head;
   1910             var end = new CodeMirror.Pos(start.line, start.ch - prefixLength);
   1911             this._codeMirror.replaceRange(this._currentSuggestion, start, end, "+autocomplete");
   1912         }
   1913     },
   1914 
   1915     _onScroll: function()
   1916     {
   1917         if (!this._suggestBox)
   1918             return;
   1919         var cursor = this._codeMirror.getCursor();
   1920         var scrollInfo = this._codeMirror.getScrollInfo();
   1921         var topmostLineNumber = this._codeMirror.lineAtHeight(scrollInfo.top, "local");
   1922         var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
   1923         if (cursor.line < topmostLineNumber || cursor.line > bottomLine)
   1924             this.finishAutocomplete();
   1925         else {
   1926             this._updateAnchorBox();
   1927             this._suggestBox.setPosition(this._anchorBox);
   1928         }
   1929     },
   1930 
   1931     _onCursorActivity: function()
   1932     {
   1933         if (!this._suggestBox)
   1934             return;
   1935         var cursor = this._codeMirror.getCursor();
   1936         if (cursor.line !== this._prefixRange.startLine || cursor.ch > this._prefixRange.endColumn || cursor.ch <= this._prefixRange.startColumn)
   1937             this.finishAutocomplete();
   1938     },
   1939 
   1940     _updateAnchorBox: function()
   1941     {
   1942         var line = this._prefixRange.startLine;
   1943         var column = this._prefixRange.startColumn;
   1944         var metrics = this._textEditor.cursorPositionToCoordinates(line, column);
   1945         this._anchorBox = metrics ? new AnchorBox(metrics.x, metrics.y, 0, metrics.height) : null;
   1946     },
   1947 }
   1948 
   1949 /**
   1950  * @constructor
   1951  * @param {!WebInspector.CodeMirrorTextEditor} textEditor
   1952  * @param {!CodeMirror} codeMirror
   1953  */
   1954 WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController = function(textEditor, codeMirror)
   1955 {
   1956     this._textEditor = textEditor;
   1957     this._codeMirror = codeMirror;
   1958 }
   1959 
   1960 WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController.prototype = {
   1961     selectionWillChange: function()
   1962     {
   1963         if (!this._muteSelectionListener)
   1964             delete this._fullWordSelection;
   1965     },
   1966 
   1967     /**
   1968      * @param {!Array.<!WebInspector.TextRange>} selections
   1969      * @param {!WebInspector.TextRange} range
   1970      * @return {boolean}
   1971      */
   1972     _findRange: function(selections, range)
   1973     {
   1974         for (var i = 0; i < selections.length; ++i) {
   1975             if (range.equal(selections[i]))
   1976                 return true;
   1977         }
   1978         return false;
   1979     },
   1980 
   1981     undoLastSelection: function()
   1982     {
   1983         this._muteSelectionListener = true;
   1984         this._codeMirror.execCommand("undoSelection");
   1985         this._muteSelectionListener = false;
   1986     },
   1987 
   1988     selectNextOccurrence: function()
   1989     {
   1990         var selections = this._textEditor.selections();
   1991         var anyEmptySelection = false;
   1992         for (var i = 0; i < selections.length; ++i) {
   1993             var selection = selections[i];
   1994             anyEmptySelection = anyEmptySelection || selection.isEmpty();
   1995             if (selection.startLine !== selection.endLine)
   1996                 return;
   1997         }
   1998         if (anyEmptySelection) {
   1999             this._expandSelectionsToWords(selections);
   2000             return;
   2001         }
   2002 
   2003         var last = selections[selections.length - 1];
   2004         var next = last;
   2005         do {
   2006             next = this._findNextOccurrence(next, !!this._fullWordSelection);
   2007         } while (next && this._findRange(selections, next) && !next.equal(last));
   2008 
   2009         if (!next)
   2010             return;
   2011         selections.push(next);
   2012 
   2013         this._muteSelectionListener = true;
   2014         this._textEditor.setSelections(selections, selections.length - 1);
   2015         delete this._muteSelectionListener;
   2016 
   2017         this._textEditor._revealLine(next.startLine);
   2018     },
   2019 
   2020     /**
   2021      * @param {!Array.<!WebInspector.TextRange>} selections
   2022      */
   2023     _expandSelectionsToWords: function(selections)
   2024     {
   2025         var newSelections = [];
   2026         for (var i = 0; i < selections.length; ++i) {
   2027             var selection = selections[i];
   2028             var startRangeWord = this._textEditor._wordRangeForCursorPosition(selection.startLine, selection.startColumn, WebInspector.TextUtils.isWordChar)
   2029                 || WebInspector.TextRange.createFromLocation(selection.startLine, selection.startColumn);
   2030             var endRangeWord = this._textEditor._wordRangeForCursorPosition(selection.endLine, selection.endColumn, WebInspector.TextUtils.isWordChar)
   2031                 || WebInspector.TextRange.createFromLocation(selection.endLine, selection.endColumn);
   2032             var newSelection = new WebInspector.TextRange(startRangeWord.startLine, startRangeWord.startColumn, endRangeWord.endLine, endRangeWord.endColumn);
   2033             newSelections.push(newSelection);
   2034         }
   2035         this._textEditor.setSelections(newSelections, newSelections.length - 1);
   2036         this._fullWordSelection = true;
   2037     },
   2038 
   2039     /**
   2040      * @param {!WebInspector.TextRange} range
   2041      * @param {boolean} fullWord
   2042      * @return {?WebInspector.TextRange}
   2043      */
   2044     _findNextOccurrence: function(range, fullWord)
   2045     {
   2046         range = range.normalize();
   2047         var matchedLineNumber;
   2048         var matchedColumnNumber;
   2049         var textToFind = this._textEditor.copyRange(range);
   2050         function findWordInLine(wordRegex, lineNumber, lineText, from, to)
   2051         {
   2052             if (typeof matchedLineNumber === "number")
   2053                 return true;
   2054             wordRegex.lastIndex = from;
   2055             var result = wordRegex.exec(lineText);
   2056             if (!result || result.index + textToFind.length > to)
   2057                 return false;
   2058             matchedLineNumber = lineNumber;
   2059             matchedColumnNumber = result.index;
   2060             return true;
   2061         }
   2062 
   2063         var iteratedLineNumber;
   2064         function lineIterator(regex, lineHandle)
   2065         {
   2066             if (findWordInLine(regex, iteratedLineNumber++, lineHandle.text, 0, lineHandle.text.length))
   2067                 return true;
   2068         }
   2069 
   2070         var regexSource = textToFind.escapeForRegExp();
   2071         if (fullWord)
   2072             regexSource = "\\b" + regexSource + "\\b";
   2073         var wordRegex = new RegExp(regexSource, "gi");
   2074         var currentLineText = this._codeMirror.getLine(range.startLine);
   2075 
   2076         findWordInLine(wordRegex, range.startLine, currentLineText, range.endColumn, currentLineText.length);
   2077         iteratedLineNumber = range.startLine + 1;
   2078         this._codeMirror.eachLine(range.startLine + 1, this._codeMirror.lineCount(), lineIterator.bind(null, wordRegex));
   2079         iteratedLineNumber = 0;
   2080         this._codeMirror.eachLine(0, range.startLine, lineIterator.bind(null, wordRegex));
   2081         findWordInLine(wordRegex, range.startLine, currentLineText, 0, range.startColumn);
   2082 
   2083         if (typeof matchedLineNumber !== "number")
   2084             return null;
   2085         return new WebInspector.TextRange(matchedLineNumber, matchedColumnNumber, matchedLineNumber, matchedColumnNumber + textToFind.length);
   2086     }
   2087 }
   2088 
   2089 /**
   2090  * @param {string} modeName
   2091  * @param {string} tokenPrefix
   2092  */
   2093 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens = function(modeName, tokenPrefix)
   2094 {
   2095     var oldModeName = modeName + "-old";
   2096     if (CodeMirror.modes[oldModeName])
   2097         return;
   2098 
   2099     CodeMirror.defineMode(oldModeName, CodeMirror.modes[modeName]);
   2100     CodeMirror.defineMode(modeName, modeConstructor);
   2101 
   2102     function modeConstructor(config, parserConfig)
   2103     {
   2104         var innerConfig = {};
   2105         for (var i in parserConfig)
   2106             innerConfig[i] = parserConfig[i];
   2107         innerConfig.name = oldModeName;
   2108         var codeMirrorMode = CodeMirror.getMode(config, innerConfig);
   2109         codeMirrorMode.name = modeName;
   2110         codeMirrorMode.token = tokenOverride.bind(null, codeMirrorMode.token);
   2111         return codeMirrorMode;
   2112     }
   2113 
   2114     function tokenOverride(superToken, stream, state)
   2115     {
   2116         var token = superToken(stream, state);
   2117         return token ? tokenPrefix + token : token;
   2118     }
   2119 }
   2120 
   2121 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("css", "css-");
   2122 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("javascript", "js-");
   2123 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("xml", "xml-");
   2124 
   2125 (function() {
   2126     var backgroundColor = InspectorFrontendHost.getSelectionBackgroundColor();
   2127     var backgroundColorRule = backgroundColor ? ".CodeMirror .CodeMirror-selected { background-color: " + backgroundColor + ";}" : "";
   2128     var foregroundColor = InspectorFrontendHost.getSelectionForegroundColor();
   2129     var foregroundColorRule = foregroundColor ? ".CodeMirror .CodeMirror-selectedtext:not(.CodeMirror-persist-highlight) { color: " + foregroundColor + "!important;}" : "";
   2130     if (!foregroundColorRule && !backgroundColorRule)
   2131         return;
   2132 
   2133     var style = document.createElement("style");
   2134     style.textContent = backgroundColorRule + foregroundColorRule;
   2135     document.head.appendChild(style);
   2136 })();
   2137