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