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