• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
38WebInspector.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()}} */
168WebInspector.CodeMirrorTextEditor.BeforeChangeObject;
169
170/** @typedef {{from: !CodeMirror.Pos, to: !CodeMirror.Pos, origin: string, text: !Array.<string>, removed: !Array.<string>}} */
171WebInspector.CodeMirrorTextEditor.ChangeObject;
172
173WebInspector.CodeMirrorTextEditor.maxHighlightLength = 1000;
174
175/**
176 * @param {!CodeMirror} codeMirror
177 */
178WebInspector.CodeMirrorTextEditor.autocompleteCommand = function(codeMirror)
179{
180    codeMirror._codeMirrorTextEditor._autocompleteController.autocomplete();
181}
182CodeMirror.commands.autocomplete = WebInspector.CodeMirrorTextEditor.autocompleteCommand;
183
184/**
185 * @param {!CodeMirror} codeMirror
186 */
187WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand = function(codeMirror)
188{
189    codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.undoLastSelection();
190}
191CodeMirror.commands.undoLastSelection = WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand;
192
193/**
194 * @param {!CodeMirror} codeMirror
195 */
196WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand = function(codeMirror)
197{
198    codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.selectNextOccurrence();
199}
200CodeMirror.commands.selectNextOccurrence = WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand;
201
202/**
203 * @param {!CodeMirror} codeMirror
204 */
205CodeMirror.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
231CodeMirror.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
240CodeMirror.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 */
252CodeMirror.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
270WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold = 2000;
271WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan = 16;
272WebInspector.CodeMirrorTextEditor.MaxEditableTextSize = 1024 * 1024 * 10;
273
274WebInspector.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 */
1245WebInspector.CodeMirrorPositionHandle = function(codeMirror, pos)
1246{
1247    this._codeMirror = codeMirror;
1248    this._lineHandle = codeMirror.getLineHandle(pos.line);
1249    this._columnNumber = pos.ch;
1250}
1251
1252WebInspector.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 */
1282WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(textEditor, codeMirror)
1283{
1284    this._textEditor = textEditor;
1285    this._codeMirror = codeMirror;
1286}
1287
1288WebInspector.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 */
1448WebInspector.CodeMirrorTextEditor.BlockIndentController = function(codeMirror)
1449{
1450    codeMirror.addKeyMap(this);
1451}
1452
1453WebInspector.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 */
1509WebInspector.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 */
1559WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI = function() {}
1560
1561WebInspector.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 */
1589WebInspector.CodeMirrorTextEditor.DummyAutocompleteController = function() { }
1590
1591WebInspector.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 */
1626WebInspector.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
1649WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy = new WebInspector.CodeMirrorTextEditor.DummyAutocompleteController();
1650WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars = {};
1651WebInspector.CodeMirrorTextEditor._CSSAdditionalWordChars = { ".": true, "-": true };
1652
1653WebInspector.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 */
1954WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController = function(textEditor, codeMirror)
1955{
1956    this._textEditor = textEditor;
1957    this._codeMirror = codeMirror;
1958}
1959
1960WebInspector.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 */
2093WebInspector.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
2121WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("css", "css-");
2122WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("javascript", "js-");
2123WebInspector.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