Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / 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 importScript("cm/codemirror.js");
32 importScript("cm/css.js");
33 importScript("cm/javascript.js");
34 importScript("cm/xml.js");
35 importScript("cm/htmlmixed.js");
36
37 importScript("cm/matchbrackets.js");
38 importScript("cm/closebrackets.js");
39 importScript("cm/markselection.js");
40 importScript("cm/comment.js");
41 importScript("cm/overlay.js");
42
43 importScript("cm/htmlembedded.js");
44 importScript("cm/clike.js");
45 importScript("cm/coffeescript.js");
46 importScript("cm/php.js");
47 importScript("cm/python.js");
48 importScript("cm/shell.js");
49 importScript("CodeMirrorUtils.js");
50
51 /**
52  * @constructor
53  * @extends {WebInspector.VBox}
54  * @implements {WebInspector.TextEditor}
55  * @param {?string} url
56  * @param {!WebInspector.TextEditorDelegate} delegate
57  */
58 WebInspector.CodeMirrorTextEditor = function(url, delegate)
59 {
60     WebInspector.VBox.call(this);
61     this._delegate = delegate;
62     this._url = url;
63
64     this.registerRequiredCSS("cm/codemirror.css");
65     this.registerRequiredCSS("cm/cmdevtools.css");
66
67     this._codeMirror = window.CodeMirror(this.element, {
68         lineNumbers: true,
69         gutters: ["CodeMirror-linenumbers"],
70         matchBrackets: true,
71         smartIndent: false,
72         styleSelectedText: true,
73         electricChars: false,
74     });
75     this._codeMirror._codeMirrorTextEditor = this;
76
77     CodeMirror.keyMap["devtools-common"] = {
78         "Left": "goCharLeft",
79         "Right": "goCharRight",
80         "Up": "goLineUp",
81         "Down": "goLineDown",
82         "End": "goLineEnd",
83         "Home": "goLineStartSmart",
84         "PageUp": "goPageUp",
85         "PageDown": "goPageDown",
86         "Delete": "delCharAfter",
87         "Backspace": "delCharBefore",
88         "Tab": "defaultTab",
89         "Shift-Tab": "indentLess",
90         "Enter": "smartNewlineAndIndent",
91         "Ctrl-Space": "autocomplete"
92     };
93
94     CodeMirror.keyMap["devtools-pc"] = {
95         "Ctrl-A": "selectAll",
96         "Ctrl-Z": "undoAndReveal",
97         "Shift-Ctrl-Z": "redoAndReveal",
98         "Ctrl-Y": "redo",
99         "Ctrl-Home": "goDocStart",
100         "Ctrl-Up": "goDocStart",
101         "Ctrl-End": "goDocEnd",
102         "Ctrl-Down": "goDocEnd",
103         "Ctrl-Left": "goGroupLeft",
104         "Ctrl-Right": "goGroupRight",
105         "Alt-Left": "goLineStart",
106         "Alt-Right": "goLineEnd",
107         "Ctrl-Backspace": "delGroupBefore",
108         "Ctrl-Delete": "delGroupAfter",
109         "Ctrl-/": "toggleComment",
110         fallthrough: "devtools-common"
111     };
112
113     CodeMirror.keyMap["devtools-mac"] = {
114         "Cmd-A" : "selectAll",
115         "Cmd-Z" : "undoAndReveal",
116         "Shift-Cmd-Z": "redoAndReveal",
117         "Cmd-Up": "goDocStart",
118         "Cmd-Down": "goDocEnd",
119         "Alt-Left": "goGroupLeft",
120         "Alt-Right": "goGroupRight",
121         "Cmd-Left": "goLineStartSmart",
122         "Cmd-Right": "goLineEnd",
123         "Alt-Backspace": "delGroupBefore",
124         "Alt-Delete": "delGroupAfter",
125         "Cmd-/": "toggleComment",
126         fallthrough: "devtools-common"
127     };
128
129     WebInspector.settings.textEditorIndent.addChangeListener(this._updateEditorIndentation, this);
130     this._updateEditorIndentation();
131     WebInspector.settings.showWhitespacesInEditor.addChangeListener(this._updateCodeMirrorMode, this);
132     WebInspector.settings.textEditorBracketMatching.addChangeListener(this._enableBracketMatchingIfNeeded, this);
133     this._enableBracketMatchingIfNeeded();
134
135     this._codeMirror.setOption("keyMap", WebInspector.isMac() ? "devtools-mac" : "devtools-pc");
136     this._codeMirror.setOption("flattenSpans", false);
137
138     this._codeMirror.setOption("maxHighlightLength", WebInspector.CodeMirrorTextEditor.maxHighlightLength);
139     this._codeMirror.setOption("mode", null);
140     this._codeMirror.setOption("crudeMeasuringFrom", 1000);
141
142     this._shouldClearHistory = true;
143     this._lineSeparator = "\n";
144
145     this._tokenHighlighter = new WebInspector.CodeMirrorTextEditor.TokenHighlighter(this._codeMirror);
146     this._blockIndentController = new WebInspector.CodeMirrorTextEditor.BlockIndentController(this._codeMirror);
147     this._fixWordMovement = new WebInspector.CodeMirrorTextEditor.FixWordMovement(this._codeMirror);
148     this._autocompleteController = new WebInspector.CodeMirrorTextEditor.AutocompleteController(this, this._codeMirror);
149
150     this._codeMirror.on("change", this._change.bind(this));
151     this._codeMirror.on("beforeChange", this._beforeChange.bind(this));
152     this._codeMirror.on("gutterClick", this._gutterClick.bind(this));
153     this._codeMirror.on("cursorActivity", this._cursorActivity.bind(this));
154     this._codeMirror.on("beforeSelectionChange", this._beforeSelectionChange.bind(this));
155     this._codeMirror.on("scroll", this._scroll.bind(this));
156     this._codeMirror.on("focus", this._focus.bind(this));
157     this._codeMirror.on("blur", this._blur.bind(this));
158     this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false);
159     /**
160      * @this {WebInspector.CodeMirrorTextEditor}
161      */
162     function updateAnticipateJumpFlag(value)
163     {
164         this._isHandlingMouseDownEvent = value;
165     }
166     this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, true), true);
167     this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, false), false);
168
169     this.element.style.overflow = "hidden";
170     this.element.firstChild.classList.add("source-code");
171     this.element.firstChild.classList.add("fill");
172     this._elementToWidget = new Map();
173     this._nestedUpdatesCounter = 0;
174
175     this.element.addEventListener("focus", this._handleElementFocus.bind(this), false);
176     this.element.addEventListener("keydown", this._handleKeyDown.bind(this), true);
177     this.element.addEventListener("keydown", this._handlePostKeyDown.bind(this), false);
178     this.element.tabIndex = 0;
179
180     this._setupWhitespaceHighlight();
181 }
182
183 /** @typedef {{canceled: boolean, from: CodeMirror.Pos, to: CodeMirror.Pos, text: string, origin: string, cancel: function()}} */
184 WebInspector.CodeMirrorTextEditor.BeforeChangeObject;
185
186 /** @typedef {{from: CodeMirror.Pos, to: CodeMirror.Pos, origin: string, text: !Array.<string>, removed: !Array.<string>}} */
187 WebInspector.CodeMirrorTextEditor.ChangeObject;
188
189 WebInspector.CodeMirrorTextEditor.maxHighlightLength = 1000;
190
191 WebInspector.CodeMirrorTextEditor.autocompleteCommand = function(codeMirror)
192 {
193     codeMirror._codeMirrorTextEditor._autocompleteController.autocomplete();
194 }
195 CodeMirror.commands.autocomplete = WebInspector.CodeMirrorTextEditor.autocompleteCommand;
196
197 CodeMirror.commands.smartNewlineAndIndent = function(codeMirror)
198 {
199     codeMirror.operation(innerSmartNewlineAndIndent.bind(null, codeMirror));
200
201     function countIndent(line)
202     {
203         for (var i = 0; i < line.length; ++i) {
204             if (!WebInspector.TextUtils.isSpaceChar(line[i]))
205                 return i;
206         }
207         return line.length;
208     }
209
210     function innerSmartNewlineAndIndent(codeMirror)
211     {
212         var cur = codeMirror.getCursor("start");
213         var line = codeMirror.getLine(cur.line);
214         var indent = cur.line > 0 ? countIndent(line) : 0;
215         if (cur.ch <= indent) {
216             codeMirror.replaceSelection("\n" + line.substring(0, cur.ch), "end", "+input");
217             codeMirror.setSelection(new CodeMirror.Pos(cur.line + 1, cur.ch));
218         } else
219             codeMirror.execCommand("newlineAndIndent");
220     }
221 }
222
223 CodeMirror.commands.undoAndReveal = function(codemirror)
224 {
225     var scrollInfo = codemirror.getScrollInfo();
226     codemirror.execCommand("undo");
227     var cursor = codemirror.getCursor("start");
228     codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo);
229     codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete();
230 }
231
232 CodeMirror.commands.redoAndReveal = function(codemirror)
233 {
234     var scrollInfo = codemirror.getScrollInfo();
235     codemirror.execCommand("redo");
236     var cursor = codemirror.getCursor("start");
237     codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo);
238     codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete();
239 }
240
241 WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold = 2000;
242 WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan = 16;
243 WebInspector.CodeMirrorTextEditor.MaxEditableTextSize = 1024 * 1024 * 10;
244
245 WebInspector.CodeMirrorTextEditor.prototype = {
246     _enableBracketMatchingIfNeeded: function()
247     {
248         this._codeMirror.setOption("autoCloseBrackets", WebInspector.settings.textEditorBracketMatching.get() ? { explode: false } : false);
249     },
250
251     wasShown: function()
252     {
253         if (this._wasOnceShown)
254             return;
255         this._wasOnceShown = true;
256         this._codeMirror.refresh();
257     },
258
259     _guessIndentationLevel: function()
260     {
261         var tabRegex = /^\t+/;
262         var tabLines = 0;
263         var indents = {};
264         function processLine(lineHandle)
265         {
266             var text = lineHandle.text;
267             if (text.length === 0 || !WebInspector.TextUtils.isSpaceChar(text[0]))
268                 return;
269             if (tabRegex.test(text)) {
270                 ++tabLines;
271                 return;
272             }
273             var i = 0;
274             while (i < text.length && WebInspector.TextUtils.isSpaceChar(text[i]))
275                 ++i;
276             if (i % 2 !== 0)
277                 return;
278             indents[i] = 1 + (indents[i] || 0);
279         }
280         this._codeMirror.eachLine(0, 1000, processLine);
281
282         var onePercentFilterThreshold = this.linesCount / 100;
283         if (tabLines && tabLines > onePercentFilterThreshold)
284             return "\t";
285         var minimumIndent = Infinity;
286         for (var i in indents) {
287             if (indents[i] < onePercentFilterThreshold)
288                 continue;
289             var indent = parseInt(i, 10);
290             if (minimumIndent > indent)
291                 minimumIndent = indent;
292         }
293         if (minimumIndent === Infinity)
294             return WebInspector.TextUtils.Indent.FourSpaces;
295         return new Array(minimumIndent + 1).join(" ");
296     },
297
298     _updateEditorIndentation: function()
299     {
300         var extraKeys = {};
301         var indent = WebInspector.settings.textEditorIndent.get();
302         if (WebInspector.settings.textEditorAutoDetectIndent.get())
303             indent = this._guessIndentationLevel();
304         if (indent === WebInspector.TextUtils.Indent.TabCharacter) {
305             this._codeMirror.setOption("indentWithTabs", true);
306             this._codeMirror.setOption("indentUnit", 4);
307         } else {
308             this._codeMirror.setOption("indentWithTabs", false);
309             this._codeMirror.setOption("indentUnit", indent.length);
310             extraKeys.Tab = function(codeMirror)
311             {
312                 if (codeMirror.somethingSelected())
313                     return CodeMirror.Pass;
314                 var pos = codeMirror.getCursor("head");
315                 codeMirror.replaceRange(indent.substring(pos.ch % indent.length), codeMirror.getCursor());
316             }
317         }
318         this._codeMirror.setOption("extraKeys", extraKeys);
319         this._indentationLevel = indent;
320     },
321
322     /**
323      * @return {string}
324      */
325     indent: function()
326     {
327         return this._indentationLevel;
328     },
329
330     /**
331      * @param {!RegExp} regex
332      * @param {?WebInspector.TextRange} range
333      */
334     highlightSearchResults: function(regex, range)
335     {
336         /**
337          * @this {WebInspector.CodeMirrorTextEditor}
338          */
339         function innerHighlightRegex()
340         {
341             if (range) {
342                 this._revealLine(range.startLine);
343                 if (range.endColumn > WebInspector.CodeMirrorTextEditor.maxHighlightLength)
344                     this.setSelection(range);
345                 else
346                     this.setSelection(WebInspector.TextRange.createFromLocation(range.startLine, range.startColumn));
347             } else {
348                 // Collapse selection to end on search start so that we jump to next occurence on the first enter press.
349                 this.setSelection(this.selection().collapseToEnd());
350             }
351             this._tokenHighlighter.highlightSearchResults(regex, range);
352         }
353         if (!this._selectionBeforeSearch)
354             this._selectionBeforeSearch = this.selection();
355         this._codeMirror.operation(innerHighlightRegex.bind(this));
356     },
357
358     cancelSearchResultsHighlight: function()
359     {
360         this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));
361         if (this._selectionBeforeSearch) {
362             this._reportJump(this._selectionBeforeSearch, this.selection());
363             delete this._selectionBeforeSearch;
364         }
365     },
366
367     undo: function()
368     {
369         this._codeMirror.undo();
370     },
371
372     redo: function()
373     {
374         this._codeMirror.redo();
375     },
376
377     _setupWhitespaceHighlight: function()
378     {
379         if (WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected || !WebInspector.settings.showWhitespacesInEditor.get())
380             return;
381         WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected = true;
382         const classBase = ".show-whitespaces .CodeMirror .cm-whitespace-";
383         const spaceChar = "·";
384         var spaceChars = "";
385         var rules = "";
386         for (var i = 1; i <= WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan; ++i) {
387             spaceChars += spaceChar;
388             var rule = classBase + i + "::before { content: '" + spaceChars + "';}\n";
389             rules += rule;
390         }
391         var style = document.createElement("style");
392         style.textContent = rules;
393         document.head.appendChild(style);
394     },
395
396     _handleKeyDown: function(e)
397     {
398         if (this._autocompleteController.keyDown(e))
399             e.consume(true);
400     },
401
402     _handlePostKeyDown: function(e)
403     {
404         if (e.defaultPrevented)
405             e.consume(true);
406     },
407
408     _shouldProcessWordForAutocompletion: function(word)
409     {
410         return word.length && (word[0] < '0' || word[0] > '9');
411     },
412
413     /**
414      * @param {string} text
415      */
416     _addTextToCompletionDictionary: function(text)
417     {
418         if (this.readOnly())
419             return;
420         var words = WebInspector.TextUtils.textToWords(text);
421         for (var i = 0; i < words.length; ++i) {
422             if (this._shouldProcessWordForAutocompletion(words[i]))
423                 this._dictionary.addWord(words[i]);
424         }
425     },
426
427     /**
428      * @param {string} text
429      */
430     _removeTextFromCompletionDictionary: function(text)
431     {
432         if (this.readOnly())
433             return;
434         var words = WebInspector.TextUtils.textToWords(text);
435         for (var i = 0; i < words.length; ++i) {
436             if (this._shouldProcessWordForAutocompletion(words[i]))
437                 this._dictionary.removeWord(words[i]);
438         }
439     },
440
441     /**
442      * @param {?WebInspector.CompletionDictionary} dictionary
443      */
444     setCompletionDictionary: function(dictionary)
445     {
446         if (!dictionary) {
447             delete this._dictionary;
448             return;
449         }
450         this._dictionary = dictionary;
451         this._addTextToCompletionDictionary(this.text());
452     },
453
454     /**
455      * @param {number} lineNumber
456      * @param {number} column
457      * @return {?{x: number, y: number, height: number}}
458      */
459     cursorPositionToCoordinates: function(lineNumber, column)
460     {
461         if (lineNumber >= this._codeMirror.lineCount() || lineNumber < 0 || column < 0 || column > this._codeMirror.getLine(lineNumber).length)
462             return null;
463
464         var metrics = this._codeMirror.cursorCoords(new CodeMirror.Pos(lineNumber, column));
465
466         return {
467             x: metrics.left,
468             y: metrics.top,
469             height: metrics.bottom - metrics.top
470         };
471     },
472
473     /**
474      * @param {number} x
475      * @param {number} y
476      * @return {?WebInspector.TextRange}
477      */
478     coordinatesToCursorPosition: function(x, y)
479     {
480         var element = document.elementFromPoint(x, y);
481         if (!element || !element.isSelfOrDescendant(this._codeMirror.getWrapperElement()))
482             return null;
483         var gutterBox = this._codeMirror.getGutterElement().boxInWindow();
484         if (x >= gutterBox.x && x <= gutterBox.x + gutterBox.width &&
485             y >= gutterBox.y && y <= gutterBox.y + gutterBox.height)
486             return null;
487         var coords = this._codeMirror.coordsChar({left: x, top: y});
488         return this._toRange(coords, coords);
489     },
490
491     /**
492      * @param {number} lineNumber
493      * @param {number} column
494      * @return {?{startColumn: number, endColumn: number, type: string}}
495      */
496     tokenAtTextPosition: function(lineNumber, column)
497     {
498         if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
499             return null;
500         var token = this._codeMirror.getTokenAt(new CodeMirror.Pos(lineNumber, (column || 0) + 1));
501         if (!token || !token.type)
502             return null;
503         return {
504             startColumn: token.start,
505             endColumn: token.end - 1,
506             type: token.type
507         };
508     },
509
510     /**
511      * @param {!WebInspector.TextRange} textRange
512      * @return {string}
513      */
514     copyRange: function(textRange)
515     {
516         var pos = this._toPos(textRange.normalize());
517         return this._codeMirror.getRange(pos.start, pos.end);
518     },
519
520     /**
521      * @return {boolean}
522      */
523     isClean: function()
524     {
525         return this._codeMirror.isClean();
526     },
527
528     markClean: function()
529     {
530         this._codeMirror.markClean();
531     },
532
533     _hasLongLines: function()
534     {
535         function lineIterator(lineHandle)
536         {
537             if (lineHandle.text.length > WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold)
538                 hasLongLines = true;
539             return hasLongLines;
540         }
541         var hasLongLines = false;
542         this._codeMirror.eachLine(lineIterator);
543         return hasLongLines;
544     },
545
546     /**
547      * @param {string} mimeType
548      * @return {string}
549      */
550     _whitespaceOverlayMode: function(mimeType)
551     {
552         var modeName = CodeMirror.mimeModes[mimeType] ? (CodeMirror.mimeModes[mimeType].name || CodeMirror.mimeModes[mimeType]) : CodeMirror.mimeModes["text/plain"];
553         modeName += "+whitespaces";
554         if (CodeMirror.modes[modeName])
555             return modeName;
556
557         function modeConstructor(config, parserConfig)
558         {
559             function nextToken(stream)
560             {
561                 if (stream.peek() === " ") {
562                     var spaces = 0;
563                     while (spaces < WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan && stream.peek() === " ") {
564                         ++spaces;
565                         stream.next();
566                     }
567                     return "whitespace whitespace-" + spaces;
568                 }
569                 while (!stream.eol() && stream.peek() !== " ")
570                     stream.next();
571                 return null;
572             }
573             var whitespaceMode = {
574                 token: nextToken
575             };
576             return CodeMirror.overlayMode(CodeMirror.getMode(config, mimeType), whitespaceMode, false);
577         }
578         CodeMirror.defineMode(modeName, modeConstructor);
579         return modeName;
580     },
581
582     _enableLongLinesMode: function()
583     {
584         this._codeMirror.setOption("styleSelectedText", false);
585         this._longLinesMode = true;
586     },
587
588     _disableLongLinesMode: function()
589     {
590         this._codeMirror.setOption("styleSelectedText", true);
591         this._longLinesMode = false;
592     },
593
594     _updateCodeMirrorMode: function()
595     {
596         var showWhitespaces = WebInspector.settings.showWhitespacesInEditor.get();
597         this.element.classList.toggle("show-whitespaces", showWhitespaces);
598         this._codeMirror.setOption("mode", showWhitespaces ? this._whitespaceOverlayMode(this._mimeType) : this._mimeType);
599     },
600
601     /**
602      * @param {string} mimeType
603      */
604     setMimeType: function(mimeType)
605     {
606         this._mimeType = mimeType;
607         if (this._hasLongLines())
608             this._enableLongLinesMode();
609         else
610             this._disableLongLinesMode();
611         this._updateCodeMirrorMode();
612     },
613
614     /**
615      * @param {boolean} readOnly
616      */
617     setReadOnly: function(readOnly)
618     {
619         this.element.classList.toggle("CodeMirror-readonly", readOnly)
620         this._codeMirror.setOption("readOnly", readOnly);
621     },
622
623     /**
624      * @return {boolean}
625      */
626     readOnly: function()
627     {
628         return !!this._codeMirror.getOption("readOnly");
629     },
630
631     /**
632      * @param {!Object} highlightDescriptor
633      */
634     removeHighlight: function(highlightDescriptor)
635     {
636         highlightDescriptor.clear();
637     },
638
639     /**
640      * @param {!WebInspector.TextRange} range
641      * @param {string} cssClass
642      * @return {!Object}
643      */
644     highlightRange: function(range, cssClass)
645     {
646         cssClass = "CodeMirror-persist-highlight " + cssClass;
647         var pos = this._toPos(range);
648         ++pos.end.ch;
649         return this._codeMirror.markText(pos.start, pos.end, {
650             className: cssClass,
651             startStyle: cssClass + "-start",
652             endStyle: cssClass + "-end"
653         });
654     },
655
656     /**
657      * @return {!Element}
658      */
659     defaultFocusedElement: function()
660     {
661         return this.element;
662     },
663
664     focus: function()
665     {
666         this._codeMirror.focus();
667     },
668
669     _handleElementFocus: function()
670     {
671         this._codeMirror.focus();
672     },
673
674     beginUpdates: function()
675     {
676         ++this._nestedUpdatesCounter;
677     },
678
679     endUpdates: function()
680     {
681         if (!--this._nestedUpdatesCounter)
682             this._codeMirror.refresh();
683     },
684
685     /**
686      * @param {number} lineNumber
687      */
688     _revealLine: function(lineNumber)
689     {
690         this._innerRevealLine(lineNumber, this._codeMirror.getScrollInfo());
691     },
692
693     /**
694      * @param {number} lineNumber
695      * @param {!{left: number, top: number, width: number, height: number, clientWidth: number, clientHeight: number}} scrollInfo
696      */
697     _innerRevealLine: function(lineNumber, scrollInfo)
698     {
699         var topLine = this._codeMirror.lineAtHeight(scrollInfo.top, "local");
700         var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
701         var linesPerScreen = bottomLine - topLine + 1;
702         if (lineNumber < topLine) {
703             var topLineToReveal = Math.max(lineNumber - (linesPerScreen / 2) + 1, 0) | 0;
704             this._codeMirror.scrollIntoView(new CodeMirror.Pos(topLineToReveal, 0));
705         } else if (lineNumber > bottomLine) {
706             var bottomLineToReveal = Math.min(lineNumber + (linesPerScreen / 2) - 1, this.linesCount - 1) | 0;
707             this._codeMirror.scrollIntoView(new CodeMirror.Pos(bottomLineToReveal, 0));
708         }
709     },
710
711     _gutterClick: function(instance, lineNumber, gutter, event)
712     {
713         this.dispatchEventToListeners(WebInspector.TextEditor.Events.GutterClick, { lineNumber: lineNumber, event: event });
714     },
715
716     _contextMenu: function(event)
717     {
718         var contextMenu = new WebInspector.ContextMenu(event);
719         var target = event.target.enclosingNodeOrSelfWithClass("CodeMirror-gutter-elt");
720         if (target)
721             this._delegate.populateLineGutterContextMenu(contextMenu, parseInt(target.textContent, 10) - 1);
722         else
723             this._delegate.populateTextAreaContextMenu(contextMenu, 0);
724         contextMenu.show();
725     },
726
727     /**
728      * @param {number} lineNumber
729      * @param {boolean} disabled
730      * @param {boolean} conditional
731      */
732     addBreakpoint: function(lineNumber, disabled, conditional)
733     {
734         if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
735             return;
736         var className = "cm-breakpoint" + (conditional ? " cm-breakpoint-conditional" : "") + (disabled ? " cm-breakpoint-disabled" : "");
737         this._codeMirror.addLineClass(lineNumber, "wrap", className);
738     },
739
740     /**
741      * @param {number} lineNumber
742      */
743     removeBreakpoint: function(lineNumber)
744     {
745         if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
746             return;
747         var wrapClasses = this._codeMirror.getLineHandle(lineNumber).wrapClass;
748         if (!wrapClasses)
749             return;
750         var classes = wrapClasses.split(" ");
751         for (var i = 0; i < classes.length; ++i) {
752             if (classes[i].startsWith("cm-breakpoint"))
753                 this._codeMirror.removeLineClass(lineNumber, "wrap", classes[i]);
754         }
755     },
756
757     /**
758      * @param {number} lineNumber
759      */
760     setExecutionLine: function(lineNumber)
761     {
762         this.clearPositionHighlight();
763         this._executionLine = this._codeMirror.getLineHandle(lineNumber);
764         if (!this._executionLine)
765             return;
766         this._codeMirror.addLineClass(this._executionLine, "wrap", "cm-execution-line");
767     },
768
769     clearExecutionLine: function()
770     {
771         this.clearPositionHighlight();
772         if (this._executionLine)
773             this._codeMirror.removeLineClass(this._executionLine, "wrap", "cm-execution-line");
774         delete this._executionLine;
775     },
776
777     /**
778      * @param {number} lineNumber
779      * @param {!Element} element
780      */
781     addDecoration: function(lineNumber, element)
782     {
783         var widget = this._codeMirror.addLineWidget(lineNumber, element);
784         this._elementToWidget.put(element, widget);
785     },
786
787     /**
788      * @param {number} lineNumber
789      * @param {!Element} element
790      */
791     removeDecoration: function(lineNumber, element)
792     {
793         var widget = this._elementToWidget.remove(element);
794         if (widget)
795             this._codeMirror.removeLineWidget(widget);
796     },
797
798     /**
799      * @param {number} lineNumber
800      * @param {number=} columnNumber
801      * @param {boolean=} shouldHighlight
802      */
803     revealPosition: function(lineNumber, columnNumber, shouldHighlight)
804     {
805         lineNumber = Number.constrain(lineNumber, 0, this._codeMirror.lineCount() - 1);
806         if (typeof columnNumber !== "number")
807             columnNumber = 0;
808         columnNumber = Number.constrain(columnNumber, 0, this._codeMirror.getLine(lineNumber).length);
809
810         this.clearPositionHighlight();
811         this._highlightedLine = this._codeMirror.getLineHandle(lineNumber);
812         if (!this._highlightedLine)
813           return;
814         this._revealLine(lineNumber);
815         if (shouldHighlight) {
816             this._codeMirror.addLineClass(this._highlightedLine, null, "cm-highlight");
817             this._clearHighlightTimeout = setTimeout(this.clearPositionHighlight.bind(this), 2000);
818         }
819         this.setSelection(WebInspector.TextRange.createFromLocation(lineNumber, columnNumber));
820     },
821
822     clearPositionHighlight: function()
823     {
824         if (this._clearHighlightTimeout)
825             clearTimeout(this._clearHighlightTimeout);
826         delete this._clearHighlightTimeout;
827
828         if (this._highlightedLine)
829             this._codeMirror.removeLineClass(this._highlightedLine, null, "cm-highlight");
830         delete this._highlightedLine;
831     },
832
833     /**
834      * @return {!Array.<!Element>}
835      */
836     elementsToRestoreScrollPositionsFor: function()
837     {
838         return [];
839     },
840
841     /**
842      * @param {!WebInspector.TextEditor} textEditor
843      */
844     inheritScrollPositions: function(textEditor)
845     {
846     },
847
848     /**
849      * @param {number} width
850      * @param {number} height
851      */
852     _updatePaddingBottom: function(width, height)
853     {
854         var scrollInfo = this._codeMirror.getScrollInfo();
855         var newPaddingBottom;
856         var linesElement = this.element.firstElementChild.querySelector(".CodeMirror-lines");
857         var lineCount = this._codeMirror.lineCount();
858         if (lineCount <= 1)
859             newPaddingBottom = 0;
860         else
861             newPaddingBottom = Math.max(scrollInfo.clientHeight - this._codeMirror.getLineHandle(this._codeMirror.lastLine()).height, 0);
862         newPaddingBottom += "px";
863         linesElement.style.paddingBottom = newPaddingBottom;
864         this._codeMirror.setSize(width, height);
865     },
866
867     _resizeEditor: function()
868     {
869         var parentElement = this.element.parentElement;
870         if (!parentElement || !this.isShowing())
871             return;
872         var scrollLeft = this._codeMirror.doc.scrollLeft;
873         var scrollTop = this._codeMirror.doc.scrollTop;
874         var width = parentElement.offsetWidth;
875         var height = parentElement.offsetHeight;
876         this._codeMirror.setSize(width, height);
877         this._updatePaddingBottom(width, height);
878         this._codeMirror.scrollTo(scrollLeft, scrollTop);
879     },
880
881     onResize: function()
882     {
883         this._autocompleteController.finishAutocomplete();
884         this._resizeEditor();
885     },
886
887     /**
888      * @param {!WebInspector.TextRange} range
889      * @param {string} text
890      * @return {!WebInspector.TextRange}
891      */
892     editRange: function(range, text)
893     {
894         var pos = this._toPos(range);
895         this._codeMirror.replaceRange(text, pos.start, pos.end);
896         var newRange = this._toRange(pos.start, this._codeMirror.posFromIndex(this._codeMirror.indexFromPos(pos.start) + text.length));
897         this._delegate.onTextChanged(range, newRange);
898         if (WebInspector.settings.textEditorAutoDetectIndent.get())
899             this._updateEditorIndentation();
900         return newRange;
901     },
902
903     /**
904      * @param {number} lineNumber
905      * @param {number} column
906      * @param {boolean=} prefixOnly
907      * @return {?WebInspector.TextRange}
908      */
909     _wordRangeForCursorPosition: function(lineNumber, column, prefixOnly)
910     {
911         var line = this.line(lineNumber);
912         if (column === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(column - 1)))
913             return null;
914         var wordStart = column - 1;
915         while (wordStart > 0 && WebInspector.TextUtils.isWordChar(line.charAt(wordStart - 1)))
916             --wordStart;
917         if (prefixOnly)
918             return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, column);
919         var wordEnd = column;
920         while (wordEnd < line.length && WebInspector.TextUtils.isWordChar(line.charAt(wordEnd)))
921             ++wordEnd;
922         return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, wordEnd);
923     },
924
925     /**
926      * @param {!CodeMirror} codeMirror
927      * @param {!WebInspector.CodeMirrorTextEditor.BeforeChangeObject} changeObject
928      */
929     _beforeChange: function(codeMirror, changeObject)
930     {
931         if (!this._dictionary)
932             return;
933         this._updatedLines = this._updatedLines || {};
934         for (var i = changeObject.from.line; i <= changeObject.to.line; ++i)
935             this._updatedLines[i] = this.line(i);
936     },
937
938     /**
939      * @param {!CodeMirror} codeMirror
940      * @param {!WebInspector.CodeMirrorTextEditor.ChangeObject} changeObject
941      */
942     _change: function(codeMirror, changeObject)
943     {
944         // 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.
945         var hasOneLine = this._codeMirror.lineCount() === 1;
946         if (hasOneLine !== this._hasOneLine)
947             this._resizeEditor();
948         this._hasOneLine = hasOneLine;
949         var widgets = this._elementToWidget.values();
950         for (var i = 0; i < widgets.length; ++i)
951             this._codeMirror.removeLineWidget(widgets[i]);
952         this._elementToWidget.clear();
953
954         if (this._updatedLines) {
955             for (var lineNumber in this._updatedLines)
956                 this._removeTextFromCompletionDictionary(this._updatedLines[lineNumber]);
957             delete this._updatedLines;
958         }
959
960         var linesToUpdate = {};
961         var singleCharInput = false;
962         do {
963             var oldRange = this._toRange(changeObject.from, changeObject.to);
964             var newRange = oldRange.clone();
965             var linesAdded = changeObject.text.length;
966             singleCharInput = (changeObject.origin === "+input" && changeObject.text.length === 1 && changeObject.text[0].length === 1) ||
967                 (changeObject.origin === "+delete" && changeObject.removed.length === 1 && changeObject.removed[0].length === 1);
968             if (linesAdded === 0) {
969                 newRange.endLine = newRange.startLine;
970                 newRange.endColumn = newRange.startColumn;
971             } else if (linesAdded === 1) {
972                 newRange.endLine = newRange.startLine;
973                 newRange.endColumn = newRange.startColumn + changeObject.text[0].length;
974             } else {
975                 newRange.endLine = newRange.startLine + linesAdded - 1;
976                 newRange.endColumn = changeObject.text[linesAdded - 1].length;
977             }
978
979             if (!this._muteTextChangedEvent)
980                 this._delegate.onTextChanged(oldRange, newRange);
981
982             if (this._dictionary) {
983                 for (var i = newRange.startLine; i <= newRange.endLine; ++i)
984                     linesToUpdate[i] = this.line(i);
985             }
986         } while (changeObject = changeObject.next);
987         if (this._dictionary) {
988             for (var lineNumber in linesToUpdate)
989                 this._addTextToCompletionDictionary(linesToUpdate[lineNumber]);
990         }
991         if (singleCharInput)
992             this._autocompleteController.autocomplete();
993     },
994
995     _cursorActivity: function()
996     {
997         var start = this._codeMirror.getCursor("anchor");
998         var end = this._codeMirror.getCursor("head");
999         this._delegate.selectionChanged(this._toRange(start, end));
1000         if (!this._tokenHighlighter.highlightedRegex())
1001             this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));
1002     },
1003
1004     /**
1005      * @param {!CodeMirror} codeMirror
1006      * @param {!{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}} selection
1007      */
1008     _beforeSelectionChange: function(codeMirror, selection)
1009     {
1010         if (!this._isHandlingMouseDownEvent)
1011             return;
1012         this._reportJump(this.selection(), this._toRange(selection.anchor, selection.head));
1013     },
1014
1015     /**
1016      * @param {?WebInspector.TextRange} from
1017      * @param {?WebInspector.TextRange} to
1018      */
1019     _reportJump: function(from, to)
1020     {
1021         if (from && to && from.equal(to))
1022             return;
1023         this._delegate.onJumpToPosition(from, to);
1024     },
1025
1026     _scroll: function()
1027     {
1028         if (this._scrollTimer)
1029             clearTimeout(this._scrollTimer);
1030         var topmostLineNumber = this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local");
1031         this._scrollTimer = setTimeout(this._delegate.scrollChanged.bind(this._delegate, topmostLineNumber), 100);
1032     },
1033
1034     _focus: function()
1035     {
1036         this._delegate.editorFocused();
1037     },
1038
1039     _blur: function()
1040     {
1041         this._autocompleteController.finishAutocomplete();
1042     },
1043
1044     /**
1045      * @param {number} lineNumber
1046      */
1047     scrollToLine: function(lineNumber)
1048     {
1049         var pos = new CodeMirror.Pos(lineNumber, 0);
1050         var coords = this._codeMirror.charCoords(pos, "local");
1051         this._codeMirror.scrollTo(0, coords.top);
1052     },
1053
1054     /**
1055      * @return {number}
1056      */
1057     firstVisibleLine: function()
1058     {
1059         return this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local");
1060     },
1061
1062     /**
1063      * @return {number}
1064      */
1065     lastVisibleLine: function()
1066     {
1067         var scrollInfo = this._codeMirror.getScrollInfo();
1068         return this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
1069     },
1070
1071     /**
1072      * @return {!WebInspector.TextRange}
1073      */
1074     selection: function()
1075     {
1076         var start = this._codeMirror.getCursor("anchor");
1077         var end = this._codeMirror.getCursor("head");
1078
1079         return this._toRange(start, end);
1080     },
1081
1082     /**
1083      * @return {?WebInspector.TextRange}
1084      */
1085     lastSelection: function()
1086     {
1087         return this._lastSelection;
1088     },
1089
1090     /**
1091      * @param {!WebInspector.TextRange} textRange
1092      */
1093     setSelection: function(textRange)
1094     {
1095         this._lastSelection = textRange;
1096         var pos = this._toPos(textRange);
1097         this._codeMirror.setSelection(pos.start, pos.end);
1098     },
1099
1100     /**
1101      * @param {string} text
1102      */
1103     _detectLineSeparator: function(text)
1104     {
1105         this._lineSeparator = text.indexOf("\r\n") >= 0 ? "\r\n" : "\n";
1106     },
1107
1108     /**
1109      * @param {string} text
1110      */
1111     setText: function(text)
1112     {
1113         this._muteTextChangedEvent = true;
1114         if (text.length > WebInspector.CodeMirrorTextEditor.MaxEditableTextSize) {
1115             if (this._dictionary)
1116                 this._dictionary.reset();
1117             this.setReadOnly(true);
1118         }
1119         this._codeMirror.setValue(text);
1120         this._updateEditorIndentation();
1121         if (this._shouldClearHistory) {
1122             this._codeMirror.clearHistory();
1123             this._shouldClearHistory = false;
1124         }
1125         this._detectLineSeparator(text);
1126         delete this._muteTextChangedEvent;
1127     },
1128
1129     /**
1130      * @return {string}
1131      */
1132     text: function()
1133     {
1134         return this._codeMirror.getValue().replace(/\n/g, this._lineSeparator);
1135     },
1136
1137     /**
1138      * @return {!WebInspector.TextRange}
1139      */
1140     range: function()
1141     {
1142         var lineCount = this.linesCount;
1143         var lastLine = this._codeMirror.getLine(lineCount - 1);
1144         return this._toRange(new CodeMirror.Pos(0, 0), new CodeMirror.Pos(lineCount - 1, lastLine.length));
1145     },
1146
1147     /**
1148      * @param {number} lineNumber
1149      * @return {string}
1150      */
1151     line: function(lineNumber)
1152     {
1153         return this._codeMirror.getLine(lineNumber);
1154     },
1155
1156     /**
1157      * @return {number}
1158      */
1159     get linesCount()
1160     {
1161         return this._codeMirror.lineCount();
1162     },
1163
1164     /**
1165      * @param {number} line
1166      * @param {string} name
1167      * @param {?Object} value
1168      */
1169     setAttribute: function(line, name, value)
1170     {
1171         if (line < 0 || line >= this._codeMirror.lineCount())
1172             return;
1173         var handle = this._codeMirror.getLineHandle(line);
1174         if (handle.attributes === undefined) handle.attributes = {};
1175         handle.attributes[name] = value;
1176     },
1177
1178     /**
1179      * @param {number} line
1180      * @param {string} name
1181      * @return {?Object} value
1182      */
1183     getAttribute: function(line, name)
1184     {
1185         if (line < 0 || line >= this._codeMirror.lineCount())
1186             return null;
1187         var handle = this._codeMirror.getLineHandle(line);
1188         return handle.attributes && handle.attributes[name] !== undefined ? handle.attributes[name] : null;
1189     },
1190
1191     /**
1192      * @param {number} line
1193      * @param {string} name
1194      */
1195     removeAttribute: function(line, name)
1196     {
1197         if (line < 0 || line >= this._codeMirror.lineCount())
1198             return;
1199         var handle = this._codeMirror.getLineHandle(line);
1200         if (handle && handle.attributes)
1201             delete handle.attributes[name];
1202     },
1203
1204     /**
1205      * @param {!WebInspector.TextRange} range
1206      * @return {!{start: !CodeMirror.Pos, end: !CodeMirror.Pos}}
1207      */
1208     _toPos: function(range)
1209     {
1210         return {
1211             start: new CodeMirror.Pos(range.startLine, range.startColumn),
1212             end: new CodeMirror.Pos(range.endLine, range.endColumn)
1213         }
1214     },
1215
1216     _toRange: function(start, end)
1217     {
1218         return new WebInspector.TextRange(start.line, start.ch, end.line, end.ch);
1219     },
1220
1221     /**
1222      * @param {number} lineNumber
1223      * @param {number} columnNumber
1224      * @return {!WebInspector.TextEditorPositionHandle}
1225      */
1226     textEditorPositionHandle: function(lineNumber, columnNumber)
1227     {
1228         return new WebInspector.CodeMirrorPositionHandle(this._codeMirror, new CodeMirror.Pos(lineNumber, columnNumber));
1229     },
1230
1231     __proto__: WebInspector.VBox.prototype
1232 }
1233
1234 /**
1235  * @constructor
1236  * @implements {WebInspector.TextEditorPositionHandle}
1237  * @param {!CodeMirror} codeMirror
1238  * @param {!CodeMirror.Pos} pos
1239  */
1240 WebInspector.CodeMirrorPositionHandle = function(codeMirror, pos)
1241 {
1242     this._codeMirror = codeMirror;
1243     this._lineHandle = codeMirror.getLineHandle(pos.line);
1244     this._columnNumber = pos.ch;
1245 }
1246
1247 WebInspector.CodeMirrorPositionHandle.prototype = {
1248     /**
1249      * @return {?{lineNumber: number, columnNumber: number}}
1250      */
1251     resolve: function()
1252     {
1253         var lineNumber = this._codeMirror.getLineNumber(this._lineHandle);
1254         if (typeof lineNumber !== "number")
1255             return null;
1256         return {
1257             lineNumber: lineNumber,
1258             columnNumber: this._columnNumber
1259         };
1260     },
1261
1262     /**
1263      * @param {!WebInspector.TextEditorPositionHandle} positionHandle
1264      * @return {boolean}
1265      */
1266     equal: function(positionHandle)
1267     {
1268         return positionHandle._lineHandle === this._lineHandle && positionHandle._columnNumber == this._columnNumber && positionHandle._codeMirror === this._codeMirror;
1269     }
1270 }
1271
1272 /**
1273  * @constructor
1274  * @param {!CodeMirror} codeMirror
1275  */
1276 WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(codeMirror)
1277 {
1278     this._codeMirror = codeMirror;
1279 }
1280
1281 WebInspector.CodeMirrorTextEditor.TokenHighlighter.prototype = {
1282     /**
1283      * @param {!RegExp} regex
1284      * @param {?WebInspector.TextRange} range
1285      */
1286     highlightSearchResults: function(regex, range)
1287     {
1288         var oldRegex = this._highlightRegex;
1289         this._highlightRegex = regex;
1290         this._highlightRange = range;
1291         if (this._searchResultMarker) {
1292             this._searchResultMarker.clear();
1293             delete this._searchResultMarker;
1294         }
1295         if (this._highlightDescriptor && this._highlightDescriptor.selectionStart)
1296             this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection");
1297         var selectionStart = this._highlightRange ? new CodeMirror.Pos(this._highlightRange.startLine, this._highlightRange.startColumn) : null;
1298         if (selectionStart)
1299             this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection");
1300         if (this._highlightRegex === oldRegex) {
1301             // Do not re-add overlay mode if regex did not change for better performance.
1302             if (this._highlightDescriptor)
1303                 this._highlightDescriptor.selectionStart = selectionStart;
1304         } else {
1305             this._removeHighlight();
1306             this._setHighlighter(this._searchHighlighter.bind(this, this._highlightRegex), selectionStart);
1307         }
1308         if (this._highlightRange) {
1309             var pos = WebInspector.CodeMirrorTextEditor.prototype._toPos(this._highlightRange);
1310             this._searchResultMarker = this._codeMirror.markText(pos.start, pos.end, {className: "cm-column-with-selection"});
1311         }
1312     },
1313
1314     /**
1315      * @return {!RegExp|undefined}
1316      */
1317     highlightedRegex: function()
1318     {
1319         return this._highlightRegex;
1320     },
1321
1322     highlightSelectedTokens: function()
1323     {
1324         delete this._highlightRegex;
1325         delete this._highlightRange;
1326
1327         if (this._highlightDescriptor && this._highlightDescriptor.selectionStart)
1328             this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection");
1329         this._removeHighlight();
1330         var selectionStart = this._codeMirror.getCursor("start");
1331         var selectionEnd = this._codeMirror.getCursor("end");
1332         if (selectionStart.line !== selectionEnd.line)
1333             return;
1334         if (selectionStart.ch === selectionEnd.ch)
1335             return;
1336
1337         var selectedText = this._codeMirror.getSelection();
1338         if (this._isWord(selectedText, selectionStart.line, selectionStart.ch, selectionEnd.ch)) {
1339             if (selectionStart)
1340                 this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection")
1341             this._setHighlighter(this._tokenHighlighter.bind(this, selectedText, selectionStart), selectionStart);
1342         }
1343     },
1344
1345     /**
1346      * @param {string} selectedText
1347      * @param {number} lineNumber
1348      * @param {number} startColumn
1349      * @param {number} endColumn
1350      */
1351     _isWord: function(selectedText, lineNumber, startColumn, endColumn)
1352     {
1353         var line = this._codeMirror.getLine(lineNumber);
1354         var leftBound = startColumn === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(startColumn - 1));
1355         var rightBound = endColumn === line.length || !WebInspector.TextUtils.isWordChar(line.charAt(endColumn));
1356         return leftBound && rightBound && WebInspector.TextUtils.isWord(selectedText);
1357     },
1358
1359     _removeHighlight: function()
1360     {
1361         if (this._highlightDescriptor) {
1362             this._codeMirror.removeOverlay(this._highlightDescriptor.overlay);
1363             delete this._highlightDescriptor;
1364         }
1365     },
1366
1367     /**
1368      * @param {!RegExp} regex
1369      * @param {!CodeMirror.StringStream} stream
1370      */
1371     _searchHighlighter: function(regex, stream)
1372     {
1373         if (stream.column() === 0)
1374             delete this._searchMatchLength;
1375         if (this._searchMatchLength) {
1376             if (this._searchMatchLength > 1) {
1377                 for (var i = 0; i < this._searchMatchLength - 2; ++i)
1378                     stream.next();
1379                 this._searchMatchLength = 1;
1380                 return "search-highlight";
1381             } else {
1382                 stream.next();
1383                 delete this._searchMatchLength;
1384                 return "search-highlight search-highlight-end";
1385             }
1386         }
1387         var match = stream.match(regex, false);
1388         if (match) {
1389             stream.next();
1390             var matchLength = match[0].length;
1391             if (matchLength === 1)
1392                 return "search-highlight search-highlight-full";
1393             this._searchMatchLength = matchLength;
1394             return "search-highlight search-highlight-start";
1395         }
1396
1397         while (!stream.match(regex, false) && stream.next()) {};
1398     },
1399
1400     /**
1401      * @param {string} token
1402      * @param {!CodeMirror.Pos} selectionStart
1403      * @param {!CodeMirror.StringStream} stream
1404      */
1405     _tokenHighlighter: function(token, selectionStart, stream)
1406     {
1407         var tokenFirstChar = token.charAt(0);
1408         if (stream.match(token) && (stream.eol() || !WebInspector.TextUtils.isWordChar(stream.peek())))
1409             return stream.column() === selectionStart.ch ? "token-highlight column-with-selection" : "token-highlight";
1410
1411         var eatenChar;
1412         do {
1413             eatenChar = stream.next();
1414         } while (eatenChar && (WebInspector.TextUtils.isWordChar(eatenChar) || stream.peek() !== tokenFirstChar));
1415     },
1416
1417     /**
1418      * @param {function(!CodeMirror.StringStream)} highlighter
1419      */
1420     _setHighlighter: function(highlighter, selectionStart)
1421     {
1422         var overlayMode = {
1423             token: highlighter
1424         };
1425         this._codeMirror.addOverlay(overlayMode);
1426         this._highlightDescriptor = {
1427             overlay: overlayMode,
1428             selectionStart: selectionStart
1429         };
1430     }
1431 }
1432
1433 /**
1434  * @constructor
1435  * @param {!CodeMirror} codeMirror
1436  */
1437 WebInspector.CodeMirrorTextEditor.BlockIndentController = function(codeMirror)
1438 {
1439     codeMirror.addKeyMap(this);
1440 }
1441
1442 WebInspector.CodeMirrorTextEditor.BlockIndentController.prototype = {
1443     name: "blockIndentKeymap",
1444
1445     /**
1446      * @return {*}
1447      */
1448     Enter: function(codeMirror)
1449     {
1450         if (codeMirror.somethingSelected())
1451             return CodeMirror.Pass;
1452         var cursor = codeMirror.getCursor();
1453         if (cursor.ch === 0)
1454             return CodeMirror.Pass;
1455         var line = codeMirror.getLine(cursor.line);
1456         if (line.substr(cursor.ch - 1, 2) === "{}") {
1457             codeMirror.execCommand("newlineAndIndent");
1458             codeMirror.setCursor(cursor);
1459             codeMirror.execCommand("newlineAndIndent");
1460             codeMirror.execCommand("indentMore");
1461         } else if (line.substr(cursor.ch - 1, 1) === "{") {
1462             codeMirror.execCommand("newlineAndIndent");
1463             codeMirror.execCommand("indentMore");
1464         } else
1465             return CodeMirror.Pass;
1466     },
1467
1468     /**
1469      * @return {*}
1470      */
1471     "'}'": function(codeMirror)
1472     {
1473         var cursor = codeMirror.getCursor();
1474         var line = codeMirror.getLine(cursor.line);
1475         for (var i = 0 ; i < line.length; ++i) {
1476             if (!WebInspector.TextUtils.isSpaceChar(line.charAt(i)))
1477                 return CodeMirror.Pass;
1478         }
1479
1480         codeMirror.replaceRange("}", cursor);
1481         var matchingBracket = codeMirror.findMatchingBracket();
1482         if (!matchingBracket || !matchingBracket.match)
1483             return;
1484
1485         line = codeMirror.getLine(matchingBracket.to.line);
1486         var desiredIndentation = 0;
1487         while (desiredIndentation < line.length && WebInspector.TextUtils.isSpaceChar(line.charAt(desiredIndentation)))
1488             ++desiredIndentation;
1489
1490         codeMirror.replaceRange(line.substr(0, desiredIndentation) + "}", new CodeMirror.Pos(cursor.line, 0), new CodeMirror.Pos(cursor.line, cursor.ch + 1));
1491     }
1492 }
1493
1494 /**
1495  * @constructor
1496  * @param {!CodeMirror} codeMirror
1497  */
1498 WebInspector.CodeMirrorTextEditor.FixWordMovement = function(codeMirror)
1499 {
1500     function moveLeft(shift, codeMirror)
1501     {
1502         var cursor = codeMirror.getCursor("head");
1503         if (cursor.ch !== 0 || cursor.line === 0)
1504             return CodeMirror.Pass;
1505         codeMirror.setExtending(shift);
1506         codeMirror.execCommand("goLineUp");
1507         codeMirror.execCommand("goLineEnd")
1508         codeMirror.setExtending(false);
1509     }
1510     function moveRight(shift, codeMirror)
1511     {
1512         var cursor = codeMirror.getCursor("head");
1513         var line = codeMirror.getLine(cursor.line);
1514         if (cursor.ch !== line.length || cursor.line + 1 === codeMirror.lineCount())
1515             return CodeMirror.Pass;
1516         codeMirror.setExtending(shift);
1517         codeMirror.execCommand("goLineDown");
1518         codeMirror.execCommand("goLineStart");
1519         codeMirror.setExtending(false);
1520     }
1521     function delWordBack(codeMirror)
1522     {
1523         if (codeMirror.somethingSelected())
1524             return CodeMirror.Pass;
1525         var cursor = codeMirror.getCursor("head");
1526         if (cursor.ch === 0)
1527             codeMirror.execCommand("delCharBefore");
1528         else
1529             return CodeMirror.Pass;
1530     }
1531
1532     var modifierKey = WebInspector.isMac() ? "Alt" : "Ctrl";
1533     var leftKey = modifierKey + "-Left";
1534     var rightKey = modifierKey + "-Right";
1535     var keyMap = {};
1536     keyMap[leftKey] = moveLeft.bind(null, false);
1537     keyMap[rightKey] = moveRight.bind(null, false);
1538     keyMap["Shift-" + leftKey] = moveLeft.bind(null, true);
1539     keyMap["Shift-" + rightKey] = moveRight.bind(null, true);
1540     keyMap[modifierKey + "-Backspace"] = delWordBack;
1541     codeMirror.addKeyMap(keyMap);
1542 }
1543
1544 /**
1545  * @constructor
1546  * @implements {WebInspector.SuggestBoxDelegate}
1547  * @param {!WebInspector.CodeMirrorTextEditor} textEditor
1548  * @param {!CodeMirror} codeMirror
1549  */
1550 WebInspector.CodeMirrorTextEditor.AutocompleteController = function(textEditor, codeMirror)
1551 {
1552     this._textEditor = textEditor;
1553     this._codeMirror = codeMirror;
1554     this._codeMirror.on("scroll", this._onScroll.bind(this));
1555     this._codeMirror.on("cursorActivity", this._onCursorActivity.bind(this));
1556 }
1557
1558 WebInspector.CodeMirrorTextEditor.AutocompleteController.prototype = {
1559     autocomplete: function()
1560     {
1561         var dictionary = this._textEditor._dictionary;
1562         if (!dictionary || this._codeMirror.somethingSelected()) {
1563             this.finishAutocomplete();
1564             return;
1565         }
1566
1567         var cursor = this._codeMirror.getCursor();
1568         var substituteRange = this._textEditor._wordRangeForCursorPosition(cursor.line, cursor.ch, false);
1569         if (!substituteRange || substituteRange.startColumn === cursor.ch) {
1570             this.finishAutocomplete();
1571             return;
1572         }
1573         var prefixRange = substituteRange.clone();
1574         prefixRange.endColumn = cursor.ch;
1575
1576         var substituteWord = this._textEditor.copyRange(substituteRange);
1577         var hasPrefixInDictionary = dictionary.hasWord(substituteWord);
1578         if (hasPrefixInDictionary)
1579             dictionary.removeWord(substituteWord);
1580         var wordsWithPrefix = dictionary.wordsWithPrefix(this._textEditor.copyRange(prefixRange));
1581         if (hasPrefixInDictionary)
1582             dictionary.addWord(substituteWord);
1583
1584         function sortSuggestions(a, b)
1585         {
1586             return dictionary.wordCount(b) - dictionary.wordCount(a) || a.length - b.length;
1587         }
1588
1589         wordsWithPrefix.sort(sortSuggestions);
1590
1591         if (!this._suggestBox) {
1592             this._suggestBox = new WebInspector.SuggestBox(this, this._textEditor.element, "generic-suggest", 6);
1593             this._anchorBox = this._anchorBoxForPosition(cursor.line, cursor.ch);
1594         }
1595         this._suggestBox.updateSuggestions(this._anchorBox, wordsWithPrefix, 0, true, this._textEditor.copyRange(prefixRange));
1596         this._prefixRange = prefixRange;
1597         if (!this._suggestBox.visible())
1598             this.finishAutocomplete();
1599     },
1600
1601     finishAutocomplete: function()
1602     {
1603         if (!this._suggestBox)
1604             return;
1605         this._suggestBox.hide();
1606         this._suggestBox = null;
1607         this._prefixRange = null;
1608         this._anchorBox = null;
1609     },
1610
1611     /**
1612      * @param {?Event} e
1613      * @return {boolean}
1614      */
1615     keyDown: function(e)
1616     {
1617         if (!this._suggestBox)
1618             return false;
1619         if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
1620             this.finishAutocomplete();
1621             return true;
1622         }
1623         if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Tab.code) {
1624             this._suggestBox.acceptSuggestion();
1625             this.finishAutocomplete();
1626             return true;
1627         }
1628         return this._suggestBox.keyPressed(e);
1629     },
1630
1631     /**
1632      * @param {string} suggestion
1633      * @param {boolean=} isIntermediateSuggestion
1634      */
1635     applySuggestion: function(suggestion, isIntermediateSuggestion)
1636     {
1637         this._currentSuggestion = suggestion;
1638     },
1639
1640     acceptSuggestion: function()
1641     {
1642         if (this._prefixRange.endColumn - this._prefixRange.startColumn !== this._currentSuggestion.length) {
1643             var pos = this._textEditor._toPos(this._prefixRange);
1644             this._codeMirror.replaceRange(this._currentSuggestion, pos.start, pos.end, "+autocomplete");
1645         }
1646     },
1647
1648     _onScroll: function()
1649     {
1650         if (!this._suggestBox)
1651             return;
1652         var cursor = this._codeMirror.getCursor();
1653         var scrollInfo = this._codeMirror.getScrollInfo();
1654         var topmostLineNumber = this._codeMirror.lineAtHeight(scrollInfo.top, "local");
1655         var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
1656         if (cursor.line < topmostLineNumber || cursor.line > bottomLine)
1657             this.finishAutocomplete();
1658         else {
1659             this._anchorBox = this._anchorBoxForPosition(cursor.line, cursor.ch);
1660             this._suggestBox.setPosition(this._anchorBox);
1661         }
1662     },
1663
1664     _onCursorActivity: function()
1665     {
1666         if (!this._suggestBox)
1667             return;
1668         var cursor = this._codeMirror.getCursor();
1669         if (cursor.line !== this._prefixRange.startLine || cursor.ch > this._prefixRange.endColumn || cursor.ch < this._prefixRange.startColumn)
1670             this.finishAutocomplete();
1671     },
1672
1673     /**
1674      * @param {number} line
1675      * @param {number} column
1676      * @return {?AnchorBox}
1677      */
1678     _anchorBoxForPosition: function(line, column)
1679     {
1680         var metrics = this._textEditor.cursorPositionToCoordinates(line, column);
1681         return metrics ? new AnchorBox(metrics.x, metrics.y, 0, metrics.height) : null;
1682     },
1683 }
1684
1685 /**
1686  * @param {string} modeName
1687  * @param {string} tokenPrefix
1688  */
1689 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens = function(modeName, tokenPrefix)
1690 {
1691     var oldModeName = modeName + "-old";
1692     if (CodeMirror.modes[oldModeName])
1693         return;
1694
1695     CodeMirror.defineMode(oldModeName, CodeMirror.modes[modeName]);
1696     CodeMirror.defineMode(modeName, modeConstructor);
1697
1698     function modeConstructor(config, parserConfig)
1699     {
1700         var innerConfig = {};
1701         for (var i in parserConfig)
1702             innerConfig[i] = parserConfig[i];
1703         innerConfig.name = oldModeName;
1704         var codeMirrorMode = CodeMirror.getMode(config, innerConfig);
1705         codeMirrorMode.name = modeName;
1706         codeMirrorMode.token = tokenOverride.bind(null, codeMirrorMode.token);
1707         return codeMirrorMode;
1708     }
1709
1710     function tokenOverride(superToken, stream, state)
1711     {
1712         var token = superToken(stream, state);
1713         return token ? tokenPrefix + token : token;
1714     }
1715 }
1716
1717 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("css", "css-");
1718 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("javascript", "js-");
1719 WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("xml", "xml-");
1720
1721 (function() {
1722     var backgroundColor = InspectorFrontendHost.getSelectionBackgroundColor();
1723     var backgroundColorRule = backgroundColor ? ".CodeMirror .CodeMirror-selected { background-color: " + backgroundColor + ";}" : "";
1724     var foregroundColor = InspectorFrontendHost.getSelectionForegroundColor();
1725     var foregroundColorRule = foregroundColor ? ".CodeMirror .CodeMirror-selectedtext:not(.CodeMirror-persist-highlight) { color: " + foregroundColor + "!important;}" : "";
1726     if (!foregroundColorRule && !backgroundColorRule)
1727         return;
1728
1729     var style = document.createElement("style");
1730     style.textContent = backgroundColorRule + foregroundColorRule;
1731     document.head.appendChild(style);
1732 })();