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