1/*
2 * Copyright (C) 2013 Apple 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
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26WebInspector.TextEditor = function(element, mimeType, delegate)
27{
28    WebInspector.Object.call(this);
29
30    var text = (element ? element.textContent : "");
31    this._element = element || document.createElement("div");
32    this._element.classList.add(WebInspector.TextEditor.StyleClassName);
33    this._element.classList.add(WebInspector.SyntaxHighlightedStyleClassName);
34
35    this._codeMirror = CodeMirror(this.element, {
36        readOnly: true,
37        indentWithTabs: true,
38        indentUnit: 4,
39        lineNumbers: true,
40        lineWrapping: true,
41        matchBrackets: true,
42        autoCloseBrackets: true
43    });
44
45    this._codeMirror.on("change", this._contentChanged.bind(this));
46    this._codeMirror.on("gutterClick", this._gutterMouseDown.bind(this));
47    this._codeMirror.on("gutterContextMenu", this._gutterContextMenu.bind(this));
48    this._codeMirror.getScrollerElement().addEventListener("click", this._openClickedLinks.bind(this), true);
49
50    this._completionController = new WebInspector.CodeMirrorCompletionController(this._codeMirror, this);
51    this._tokenTrackingController = new WebInspector.CodeMirrorTokenTrackingController(this._codeMirror, this);
52
53    this._initialStringNotSet = true;
54
55    this.mimeType = mimeType;
56
57    this._breakpoints = {};
58    this._executionLineNumber = NaN;
59    this._executionColumnNumber = NaN;
60
61    this._searchQuery = null;
62    this._searchResults = [];
63    this._currentSearchResultIndex = -1;
64    this._ignoreCodeMirrorContentDidChangeEvent = 0;
65
66    this._formatted = false;
67    this._formatterSourceMap = null;
68
69    this._delegate = delegate || null;
70};
71
72WebInspector.Object.addConstructorFunctions(WebInspector.TextEditor);
73
74WebInspector.TextEditor.StyleClassName = "text-editor";
75WebInspector.TextEditor.HighlightedStyleClassName = "highlighted";
76WebInspector.TextEditor.SearchResultStyleClassName = "search-result";
77WebInspector.TextEditor.HasBreakpointStyleClassName = "has-breakpoint";
78WebInspector.TextEditor.BreakpointResolvedStyleClassName = "breakpoint-resolved";
79WebInspector.TextEditor.BreakpointAutoContinueStyleClassName = "breakpoint-auto-continue";
80WebInspector.TextEditor.BreakpointDisabledStyleClassName = "breakpoint-disabled";
81WebInspector.TextEditor.MultipleBreakpointsStyleClassName = "multiple-breakpoints";
82WebInspector.TextEditor.ExecutionLineStyleClassName = "execution-line";
83WebInspector.TextEditor.BouncyHighlightStyleClassName = "bouncy-highlight";
84WebInspector.TextEditor.NumberOfFindsPerSearchBatch = 10;
85WebInspector.TextEditor.HighlightAnimationDuration = 2000;
86
87WebInspector.TextEditor.Event = {
88    ExecutionLineNumberDidChange: "text-editor-execution-line-number-did-change",
89    NumberOfSearchResultsDidChange: "text-editor-number-of-search-results-did-change",
90    ContentDidChange: "text-editor-content-did-change",
91    FormattingDidChange: "text-editor-formatting-did-change"
92};
93
94WebInspector.TextEditor.prototype = {
95    constructor: WebInspector.TextEditor,
96
97    // Public
98
99    get element()
100    {
101        return this._element;
102    },
103
104    get string()
105    {
106        return this._codeMirror.getValue();
107    },
108
109    set string(newString)
110    {
111        function update()
112        {
113            this._codeMirror.setValue(newString);
114
115            if (this._initialStringNotSet) {
116                this._codeMirror.clearHistory();
117                this._codeMirror.markClean();
118                delete this._initialStringNotSet;
119            }
120
121            // Automatically format the content.
122            if (this._autoFormat) {
123                console.assert(!this.formatted);
124                this.formatted = true;
125                delete this._autoFormat;
126            }
127
128            // Update the execution line now that we might have content for that line.
129            this._updateExecutionLine();
130
131            // Set the breakpoint styles now that we might have content for those lines.
132            for (var lineNumber in this._breakpoints)
133                this._setBreakpointStylesOnLine(lineNumber);
134
135            // Try revealing the pending line now that we might have content with enough lines.
136            this._revealPendingPositionIfPossible();
137        }
138
139        this._ignoreCodeMirrorContentDidChangeEvent++;
140        this._codeMirror.operation(update.bind(this));
141        this._ignoreCodeMirrorContentDidChangeEvent--;
142        console.assert(this._ignoreCodeMirrorContentDidChangeEvent >= 0);
143    },
144
145    get readOnly()
146    {
147        return this._codeMirror.getOption("readOnly") || false;
148    },
149
150    set readOnly(readOnly)
151    {
152        this._codeMirror.setOption("readOnly", readOnly);
153    },
154
155    get formatted()
156    {
157        return this._formatted;
158    },
159
160    set formatted(formatted)
161    {
162        if (this._formatted === formatted)
163            return;
164
165        console.assert(!formatted || this.canBeFormatted());
166        if (formatted && !this.canBeFormatted())
167            return;
168
169        this._ignoreCodeMirrorContentDidChangeEvent++;
170        this._prettyPrint(formatted);
171        this._ignoreCodeMirrorContentDidChangeEvent--;
172        console.assert(this._ignoreCodeMirrorContentDidChangeEvent >= 0);
173
174        this._formatted = formatted;
175
176        this.dispatchEventToListeners(WebInspector.TextEditor.Event.FormattingDidChange);
177    },
178
179    set autoFormat(auto)
180    {
181        this._autoFormat = auto;
182    },
183
184    hasFormatter: function()
185    {
186        const supportedModes = {
187            "javascript": true,
188            "css": true,
189        };
190
191        var mode = this._codeMirror.getMode();
192        return mode.name in supportedModes;
193    },
194
195    canBeFormatted: function()
196    {
197        // Can be overriden by subclasses.
198        return this.hasFormatter();
199    },
200
201    get selectedTextRange()
202    {
203        var start = this._codeMirror.getCursor(true);
204        var end = this._codeMirror.getCursor(false);
205        return this._textRangeFromCodeMirrorPosition(start, end);
206    },
207
208    set selectedTextRange(textRange)
209    {
210        var position = this._codeMirrorPositionFromTextRange(textRange);
211        this._codeMirror.setSelection(position.start, position.end);
212    },
213
214    get mimeType()
215    {
216        return this._mimeType;
217    },
218
219    set mimeType(newMIMEType)
220    {
221        newMIMEType = parseMIMEType(newMIMEType).type;
222
223        this._mimeType = newMIMEType;
224        this._codeMirror.setOption("mode", newMIMEType);
225    },
226
227    get executionLineNumber()
228    {
229        return this._executionLineNumber;
230    },
231
232    set executionLineNumber(lineNumber)
233    {
234        // Only return early if there isn't a line handle and that isn't changing.
235        if (!this._executionLineHandle && isNaN(lineNumber))
236            return;
237
238        this._executionLineNumber = lineNumber;
239        this._updateExecutionLine();
240
241        // Still dispatch the event even if the number didn't change. The execution state still
242        // could have changed (e.g. continuing in a loop with a breakpoint inside).
243        this.dispatchEventToListeners(WebInspector.TextEditor.Event.ExecutionLineNumberDidChange);
244    },
245
246    get executionColumnNumber()
247    {
248        return this._executionColumnNumber;
249    },
250
251    set executionColumnNumber(columnNumber)
252    {
253        this._executionColumnNumber = columnNumber;
254    },
255
256    get formatterSourceMap()
257    {
258        return this._formatterSourceMap;
259    },
260
261    get tokenTrackingController()
262    {
263        return this._tokenTrackingController;
264    },
265
266    get delegate()
267    {
268        return this._delegate;
269    },
270
271    set delegate(newDelegate)
272    {
273        this._delegate = newDelegate || null;
274    },
275
276    get numberOfSearchResults()
277    {
278        return this._searchResults.length;
279    },
280
281    get currentSearchQuery()
282    {
283        return this._searchQuery;
284    },
285
286    set automaticallyRevealFirstSearchResult(reveal)
287    {
288        this._automaticallyRevealFirstSearchResult = reveal;
289
290        // If we haven't shown a search result yet, reveal one now.
291        if (this._automaticallyRevealFirstSearchResult && this._searchResults.length > 0) {
292            if (this._currentSearchResultIndex === -1)
293                this._revealFirstSearchResultAfterCursor();
294        }
295    },
296
297    performSearch: function(query)
298    {
299        if (this._searchQuery === query)
300            return;
301
302        this.searchCleared();
303
304        this._searchQuery = query;
305
306        // Allow subclasses to handle the searching if they have a better way.
307        // If we are formatted, just use CodeMirror's search.
308        if (typeof this.customPerformSearch === "function" && !this.formatted) {
309            if (this.customPerformSearch(query))
310                return;
311        }
312
313        // Go down the slow patch for all other text content.
314        var queryRegex = new RegExp(query.escapeForRegExp(), "gi");
315        var searchCursor = this._codeMirror.getSearchCursor(queryRegex, {line: 0, ch: 0}, false);
316        var boundBatchSearch = batchSearch.bind(this);
317        var numberOfSearchResultsDidChangeTimeout = null;
318
319        function reportNumberOfSearchResultsDidChange()
320        {
321            if (numberOfSearchResultsDidChangeTimeout) {
322                clearTimeout(numberOfSearchResultsDidChangeTimeout);
323                numberOfSearchResultsDidChangeTimeout = null;
324            }
325
326            this.dispatchEventToListeners(WebInspector.TextEditor.Event.NumberOfSearchResultsDidChange);
327        }
328
329        function batchSearch()
330        {
331            // Bail if the query changed since we started.
332            if (this._searchQuery !== query)
333                return;
334
335            var newSearchResults = [];
336            var foundResult = false;
337            for (var i = 0; i < WebInspector.TextEditor.NumberOfFindsPerSearchBatch && (foundResult = searchCursor.findNext()); ++i) {
338                var textRange = this._textRangeFromCodeMirrorPosition(searchCursor.from(), searchCursor.to());
339                newSearchResults.push(textRange);
340            }
341
342            this.addSearchResults(newSearchResults);
343
344            // Don't report immediately, coalesce updates so they come in no faster than half a second.
345            if (!numberOfSearchResultsDidChangeTimeout)
346                numberOfSearchResultsDidChangeTimeout = setTimeout(reportNumberOfSearchResultsDidChange.bind(this), 500);
347
348            if (foundResult) {
349                // More lines to search, set a timeout so we don't block the UI long.
350                setTimeout(boundBatchSearch, 50);
351            } else {
352                // Report immediately now that we are finished, canceling any pending update.
353                reportNumberOfSearchResultsDidChange.call(this);
354            }
355        }
356
357        // Start the search.
358        boundBatchSearch();
359    },
360
361    addSearchResults: function(textRanges)
362    {
363        console.assert(textRanges);
364        if (!textRanges || !textRanges.length)
365            return;
366
367        function markRanges()
368        {
369            for (var i = 0; i < textRanges.length; ++i) {
370                var position = this._codeMirrorPositionFromTextRange(textRanges[i]);
371                var mark = this._codeMirror.markText(position.start, position.end, {className: WebInspector.TextEditor.SearchResultStyleClassName});
372                this._searchResults.push(mark);
373            }
374
375            // If we haven't shown a search result yet, reveal one now.
376            if (this._automaticallyRevealFirstSearchResult) {
377                if (this._currentSearchResultIndex === -1)
378                    this._revealFirstSearchResultAfterCursor();
379            }
380        }
381
382        this._codeMirror.operation(markRanges.bind(this));
383    },
384
385    searchCleared: function()
386    {
387        function clearResults() {
388            for (var i = 0; i < this._searchResults.length; ++i)
389                this._searchResults[i].clear();
390        }
391
392        this._codeMirror.operation(clearResults.bind(this));
393
394        this._searchQuery = null;
395        this._searchResults = [];
396        this._currentSearchResultIndex = -1;
397    },
398
399    searchQueryWithSelection: function()
400    {
401        if (!this._codeMirror.somethingSelected())
402            return null;
403
404        return this._codeMirror.getSelection();
405    },
406
407    revealPreviousSearchResult: function(changeFocus)
408    {
409        if (!this._searchResults.length)
410            return;
411
412        if (this._currentSearchResultIndex === -1 || this._cursorDoesNotMatchLastRevealedSearchResult()) {
413            this._revealFirstSearchResultBeforeCursor(changeFocus);
414            return;
415        }
416
417        if (this._currentSearchResultIndex > 0)
418            --this._currentSearchResultIndex;
419        else
420            this._currentSearchResultIndex = this._searchResults.length - 1;
421
422        this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus, -1);
423    },
424
425    revealNextSearchResult: function(changeFocus)
426    {
427        if (!this._searchResults.length)
428            return;
429
430        if (this._currentSearchResultIndex === -1 || this._cursorDoesNotMatchLastRevealedSearchResult()) {
431            this._revealFirstSearchResultAfterCursor(changeFocus);
432            return;
433        }
434
435        if (this._currentSearchResultIndex + 1 < this._searchResults.length)
436            ++this._currentSearchResultIndex;
437        else
438            this._currentSearchResultIndex = 0;
439
440        this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus, 1);
441    },
442
443    line: function(lineNumber)
444    {
445        return this._codeMirror.getLine(lineNumber);
446    },
447
448    revealPosition: function(position, textRangeToSelect, forceUnformatted, noHighlight)
449    {
450        console.assert(position === undefined || position instanceof WebInspector.SourceCodePosition, "revealPosition called without a SourceCodePosition");
451        if (!(position instanceof WebInspector.SourceCodePosition))
452            return;
453
454        var lineHandle = this._codeMirror.getLineHandle(position.lineNumber);
455        if (!lineHandle || !this._visible || this._initialStringNotSet) {
456            // If we can't get a line handle or are not visible then we wait to do the reveal.
457            this._positionToReveal = position;
458            this._textRangeToSelect = textRangeToSelect;
459            this._forceUnformatted = forceUnformatted;
460            return;
461        }
462
463        // Delete now that the reveal is happening.
464        delete this._positionToReveal;
465        delete this._textRangeToSelect;
466        delete this._forceUnformatted;
467
468        // If we need to unformat, reveal the line after a wait.
469        // Otherwise the line highlight doesn't work properly.
470        if (this._formatted && forceUnformatted) {
471            this.formatted = false;
472            setTimeout(this.revealPosition.bind(this), 0, position, textRangeToSelect);
473            return;
474        }
475
476        if (!textRangeToSelect)
477            textRangeToSelect = new WebInspector.TextRange(position.lineNumber, position.columnNumber, position.lineNumber, position.columnNumber);
478
479        function removeStyleClass()
480        {
481            this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.HighlightedStyleClassName);
482        }
483
484        function revealAndHighlightLine()
485        {
486            // If the line is not visible, reveal it as the center line in the editor.
487            var position = this._codeMirrorPositionFromTextRange(textRangeToSelect);
488            if (!this._isPositionVisible(position.start))
489                this._scrollIntoViewCentered(position.start);
490
491            this.selectedTextRange = textRangeToSelect;
492
493            if (noHighlight)
494                return;
495
496            this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.HighlightedStyleClassName);
497
498            // Use a timeout instead of a webkitAnimationEnd event listener because the line element might
499            // be removed if the user scrolls during the animation. In that case webkitAnimationEnd isn't
500            // fired, and the line would highlight again the next time it scrolls into view.
501            setTimeout(removeStyleClass.bind(this), WebInspector.TextEditor.HighlightAnimationDuration);
502        }
503
504        this._codeMirror.operation(revealAndHighlightLine.bind(this));
505    },
506
507    updateLayout: function(force)
508    {
509        this._codeMirror.refresh();
510    },
511
512    shown: function()
513    {
514        this._visible = true;
515
516        // Refresh since our size might have changed.
517        this._codeMirror.refresh();
518
519        // Try revealing the pending line now that we are visible.
520        // This needs to be done as a separate operation from the refresh
521        // so that the scrollInfo coordinates are correct.
522        this._revealPendingPositionIfPossible();
523    },
524
525    hidden: function()
526    {
527        this._visible = false;
528    },
529
530    setBreakpointInfoForLineAndColumn: function(lineNumber, columnNumber, breakpointInfo)
531    {
532        if (this._ignoreSetBreakpointInfoCalls)
533            return;
534
535        if (breakpointInfo)
536            this._addBreakpointToLineAndColumnWithInfo(lineNumber, columnNumber, breakpointInfo);
537        else
538            this._removeBreakpointFromLineAndColumn(lineNumber, columnNumber);
539    },
540
541    updateBreakpointLineAndColumn: function(oldLineNumber, oldColumnNumber, newLineNumber, newColumnNumber)
542    {
543        console.assert(this._breakpoints[oldLineNumber]);
544        if (!this._breakpoints[oldLineNumber])
545            return;
546
547        console.assert(this._breakpoints[oldLineNumber][oldColumnNumber]);
548        if (!this._breakpoints[oldLineNumber][oldColumnNumber])
549            return;
550
551        var breakpointInfo = this._breakpoints[oldLineNumber][oldColumnNumber];
552        this._removeBreakpointFromLineAndColumn(oldLineNumber, oldColumnNumber);
553        this._addBreakpointToLineAndColumnWithInfo(newLineNumber, newColumnNumber, breakpointInfo);
554    },
555
556    addStyleClassToLine: function(lineNumber, styleClassName)
557    {
558        var lineHandle = this._codeMirror.getLineHandle(lineNumber);
559        console.assert(lineHandle);
560        if (!lineHandle)
561            return;
562
563        return this._codeMirror.addLineClass(lineHandle, "wrap", styleClassName);
564    },
565
566    removeStyleClassFromLine: function(lineNumber, styleClassName)
567    {
568        var lineHandle = this._codeMirror.getLineHandle(lineNumber);
569        console.assert(lineHandle);
570        if (!lineHandle)
571            return;
572
573        return this._codeMirror.removeLineClass(lineHandle, "wrap", styleClassName);
574    },
575
576    toggleStyleClassForLine: function(lineNumber, styleClassName)
577    {
578        var lineHandle = this._codeMirror.getLineHandle(lineNumber);
579        console.assert(lineHandle);
580        if (!lineHandle)
581            return;
582
583        return this._codeMirror.toggleLineClass(lineHandle, "wrap", styleClassName);
584    },
585
586    get lineCount()
587    {
588        return this._codeMirror.lineCount();
589    },
590
591    focus: function()
592    {
593        this._codeMirror.focus();
594    },
595
596    contentDidChange: function(replacedRanges, newRanges)
597    {
598        // Implemented by subclasses.
599    },
600
601    rectsForRange: function(range)
602    {
603        return this._codeMirror.rectsForRange(range);
604    },
605
606    get markers()
607    {
608        return this._codeMirror.getAllMarks().map(function(codeMirrorTextMarker) {
609            return WebInspector.TextMarker.textMarkerForCodeMirrorTextMarker(codeMirrorTextMarker);
610        });
611    },
612
613    markersAtPosition: function(position)
614    {
615        return this._codeMirror.findMarksAt(position).map(function(codeMirrorTextMarker) {
616            return WebInspector.TextMarker.textMarkerForCodeMirrorTextMarker(codeMirrorTextMarker);
617        });
618    },
619
620    createColorMarkers: function(range)
621    {
622        return this._codeMirror.createColorMarkers(range);
623    },
624
625    createGradientMarkers: function(range)
626    {
627        return this._codeMirror.createGradientMarkers(range);
628    },
629
630    editingControllerForMarker: function(editableMarker)
631    {
632        switch (editableMarker.type) {
633        case WebInspector.TextMarker.Type.Color:
634            return new WebInspector.CodeMirrorColorEditingController(this._codeMirror, editableMarker);
635        case WebInspector.TextMarker.Type.Gradient:
636            return new WebInspector.CodeMirrorGradientEditingController(this._codeMirror, editableMarker);
637        default:
638            return new WebInspector.CodeMirrorEditingController(this._codeMirror, editableMarker);
639        }
640    },
641
642    // Private
643
644    _contentChanged: function(codeMirror, change)
645    {
646        if (this._ignoreCodeMirrorContentDidChangeEvent > 0)
647            return;
648
649        var replacedRanges = [];
650        var newRanges = [];
651        while (change) {
652            replacedRanges.push(new WebInspector.TextRange(
653                change.from.line,
654                change.from.ch,
655                change.to.line,
656                change.to.ch
657            ));
658            newRanges.push(new WebInspector.TextRange(
659                change.from.line,
660                change.from.ch,
661                change.from.line + change.text.length - 1,
662                change.text.length === 1 ? change.from.ch + change.text[0].length : change.text.lastValue.length
663            ));
664            change = change.next;
665        }
666        this.contentDidChange(replacedRanges, newRanges);
667
668        if (this._formatted) {
669            this._formatterSourceMap = null;
670            this._formatted = false;
671
672            if (this._delegate && typeof this._delegate.textEditorUpdatedFormatting === "function")
673                this._delegate.textEditorUpdatedFormatting(this);
674
675            this.dispatchEventToListeners(WebInspector.TextEditor.Event.FormattingDidChange);
676        }
677
678        this.dispatchEventToListeners(WebInspector.TextEditor.Event.ContentDidChange);
679    },
680
681    _textRangeFromCodeMirrorPosition: function(start, end)
682    {
683        console.assert(start);
684        console.assert(end);
685
686        return new WebInspector.TextRange(start.line, start.ch, end.line, end.ch);
687    },
688
689    _codeMirrorPositionFromTextRange: function(textRange)
690    {
691        console.assert(textRange);
692
693        var start = {line: textRange.startLine, ch: textRange.startColumn};
694        var end = {line: textRange.endLine, ch: textRange.endColumn};
695        return {start: start, end: end};
696    },
697
698    _revealPendingPositionIfPossible: function()
699    {
700        // Nothing to do if we don't have a pending position.
701        if (!this._positionToReveal)
702            return;
703
704        // Don't try to reveal unless we are visible.
705        if (!this._visible)
706            return;
707
708        this.revealPosition(this._positionToReveal, this._textRangeToSelect, this._forceUnformatted);
709    },
710
711    _revealSearchResult: function(result, changeFocus, directionInCaseOfRevalidation)
712    {
713        var position = result.find();
714
715        // Check for a valid position, it might have been removed from editing by the user.
716        // If the position is invalide, revalidate all positions reveal as needed.
717        if (!position) {
718            this._revalidateSearchResults(directionInCaseOfRevalidation);
719            return;
720        }
721
722        // If the line is not visible, reveal it as the center line in the editor.
723        if (!this._isPositionVisible(position.from))
724            this._scrollIntoViewCentered(position.from);
725
726        // Update the text selection to select the search result.
727        this.selectedTextRange = this._textRangeFromCodeMirrorPosition(position.from, position.to);
728
729        // Remove the automatically reveal state now that we have revealed a search result.
730        this._automaticallyRevealFirstSearchResult = false;
731
732        // Focus the editor if requested.
733        if (changeFocus)
734            this._codeMirror.focus();
735
736        // Remove the bouncy highlight if it is still around. The animation will not
737        // start unless we remove it and add it back to the document.
738        if (this._bouncyHighlightElement)
739            this._bouncyHighlightElement.remove();
740
741        // Create the bouncy highlight.
742        this._bouncyHighlightElement = document.createElement("div");
743        this._bouncyHighlightElement.className = WebInspector.TextEditor.BouncyHighlightStyleClassName;
744
745        // Collect info for the bouncy highlight.
746        var textContent = this._codeMirror.getSelection();
747        var coordinates = this._codeMirror.cursorCoords(true, "page");
748
749        // Adjust the coordinates to be based in the text editor's space.
750        var textEditorRect = this._element.getBoundingClientRect();
751        coordinates.top -= textEditorRect.top;
752        coordinates.left -= textEditorRect.left;
753
754        // Position and show the bouncy highlight.
755        this._bouncyHighlightElement.textContent = textContent;
756        this._bouncyHighlightElement.style.top = coordinates.top + "px";
757        this._bouncyHighlightElement.style.left = coordinates.left + "px";
758        this._element.appendChild(this._bouncyHighlightElement);
759
760        function animationEnded()
761        {
762            if (!this._bouncyHighlightElement)
763                return;
764
765            this._bouncyHighlightElement.remove();
766            delete this._bouncyHighlightElement;
767        }
768
769        // Listen for the end of the animation so we can remove the element.
770        this._bouncyHighlightElement.addEventListener("webkitAnimationEnd", animationEnded.bind(this));
771    },
772
773    _binarySearchInsertionIndexInSearchResults: function(object, comparator)
774    {
775        // It is possible that markers in the search results array may have been deleted.
776        // In those cases the comparator will return "null" and we immediately stop
777        // the binary search and return null. The search results list needs to be updated.
778        var array = this._searchResults;
779
780        var first = 0;
781        var last = array.length - 1;
782
783        while (first <= last) {
784            var mid = (first + last) >> 1;
785            var c = comparator(object, array[mid]);
786            if (c === null)
787                return null;
788            if (c > 0)
789                first = mid + 1;
790            else if (c < 0)
791                last = mid - 1;
792            else
793                return mid;
794        }
795
796        return first - 1;
797    },
798
799    _revealFirstSearchResultBeforeCursor: function(changeFocus)
800    {
801        console.assert(this._searchResults.length);
802
803        var currentCursorPosition = this._codeMirror.getCursor("start");
804        if (currentCursorPosition.line === 0 && currentCursorPosition.ch === 0) {
805            this._currentSearchResultIndex = this._searchResults.length - 1;
806            this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus, -1);
807            return;
808        }
809
810        var index = this._binarySearchInsertionIndexInSearchResults(currentCursorPosition, function(current, searchResult) {
811            var searchResultMarker = searchResult.find();
812            if (!searchResultMarker)
813                return null;
814            return WebInspector.compareCodeMirrorPositions(current, searchResultMarker.from);
815        });
816
817        if (index === null) {
818            this._revalidateSearchResults(-1);
819            return;
820        }
821
822        this._currentSearchResultIndex = index;
823        this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus);
824    },
825
826    _revealFirstSearchResultAfterCursor: function(changeFocus)
827    {
828        console.assert(this._searchResults.length);
829
830        var currentCursorPosition = this._codeMirror.getCursor("start");
831        if (currentCursorPosition.line === 0 && currentCursorPosition.ch === 0) {
832            this._currentSearchResultIndex = 0;
833            this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus, 1);
834            return;
835        }
836
837        var index = this._binarySearchInsertionIndexInSearchResults(currentCursorPosition, function(current, searchResult) {
838            var searchResultMarker = searchResult.find();
839            if (!searchResultMarker)
840                return null;
841            return WebInspector.compareCodeMirrorPositions(current, searchResultMarker.from);
842        });
843
844        if (index === null) {
845            this._revalidateSearchResults(1);
846            return;
847        }
848
849        if (index + 1 < this._searchResults.length)
850            ++index;
851        else
852            index = 0;
853
854        this._currentSearchResultIndex = index;
855        this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus);
856    },
857
858    _cursorDoesNotMatchLastRevealedSearchResult: function()
859    {
860        console.assert(this._currentSearchResultIndex !== -1);
861        console.assert(this._searchResults.length);
862
863        var lastRevealedSearchResultMarker = this._searchResults[this._currentSearchResultIndex].find();
864        if (!lastRevealedSearchResultMarker)
865            return true;
866
867        var currentCursorPosition = this._codeMirror.getCursor("start");
868        var lastRevealedSearchResultPosition = lastRevealedSearchResultMarker.from;
869
870        return WebInspector.compareCodeMirrorPositions(currentCursorPosition, lastRevealedSearchResultPosition) !== 0;
871    },
872
873    _revalidateSearchResults: function(direction)
874    {
875        console.assert(direction !== undefined);
876
877        this._currentSearchResultIndex = -1;
878
879        var updatedSearchResults = [];
880        for (var i = 0; i < this._searchResults.length; ++i) {
881            if (this._searchResults[i].find())
882                updatedSearchResults.push(this._searchResults[i]);
883        }
884
885        console.assert(updatedSearchResults.length !== this._searchResults.length);
886
887        this._searchResults = updatedSearchResults;
888        this.dispatchEventToListeners(WebInspector.TextEditor.Event.NumberOfSearchResultsDidChange);
889
890        if (this._searchResults.length) {
891            if (direction > 0)
892                this._revealFirstSearchResultAfterCursor();
893            else
894                this._revealFirstSearchResultBeforeCursor();
895        }
896    },
897
898    _updateExecutionLine: function()
899    {
900        function update()
901        {
902            if (this._executionLineHandle)
903                this._codeMirror.removeLineClass(this._executionLineHandle, "wrap", WebInspector.TextEditor.ExecutionLineStyleClassName);
904
905            this._executionLineHandle = !isNaN(this._executionLineNumber) ? this._codeMirror.getLineHandle(this._executionLineNumber) : null;
906
907            if (this._executionLineHandle)
908                this._codeMirror.addLineClass(this._executionLineHandle, "wrap", WebInspector.TextEditor.ExecutionLineStyleClassName);
909        }
910
911        this._codeMirror.operation(update.bind(this));
912    },
913
914    _setBreakpointStylesOnLine: function(lineNumber)
915    {
916        var columnBreakpoints = this._breakpoints[lineNumber];
917        console.assert(columnBreakpoints);
918        if (!columnBreakpoints)
919            return;
920
921        var allDisabled = true;
922        var allResolved = true;
923        var allAutoContinue = true;
924        var multiple = Object.keys(columnBreakpoints).length > 1;
925        for (var columnNumber in columnBreakpoints) {
926            var breakpointInfo = columnBreakpoints[columnNumber];
927            if (!breakpointInfo.disabled)
928                allDisabled = false;
929            if (!breakpointInfo.resolved)
930                allResolved = false;
931            if (!breakpointInfo.autoContinue)
932                allAutoContinue = false;
933        }
934
935        function updateStyles()
936        {
937            // We might not have a line if the content isn't fully populated yet.
938            // This will be called again when the content is available.
939            var lineHandle = this._codeMirror.getLineHandle(lineNumber);
940            if (!lineHandle)
941                return;
942
943            this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.HasBreakpointStyleClassName);
944
945            if (allResolved)
946                this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointResolvedStyleClassName);
947            else
948                this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointResolvedStyleClassName);
949
950            if (allDisabled)
951                this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointDisabledStyleClassName);
952            else
953                this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointDisabledStyleClassName);
954
955            if (allAutoContinue)
956                this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointAutoContinueStyleClassName);
957            else
958                this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointAutoContinueStyleClassName);
959
960            if (multiple)
961                this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.MultipleBreakpointsStyleClassName);
962            else
963                this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.MultipleBreakpointsStyleClassName);
964        }
965
966        this._codeMirror.operation(updateStyles.bind(this));
967    },
968
969    _addBreakpointToLineAndColumnWithInfo: function(lineNumber, columnNumber, breakpointInfo)
970    {
971        if (!this._breakpoints[lineNumber])
972            this._breakpoints[lineNumber] = {};
973        this._breakpoints[lineNumber][columnNumber] = breakpointInfo;
974
975        this._setBreakpointStylesOnLine(lineNumber);
976    },
977
978    _removeBreakpointFromLineAndColumn: function(lineNumber, columnNumber)
979    {
980        console.assert(columnNumber in this._breakpoints[lineNumber]);
981        delete this._breakpoints[lineNumber][columnNumber];
982
983        // There are still breakpoints on the line. Update the breakpoint style.
984        if (!isEmptyObject(this._breakpoints[lineNumber])) {
985            this._setBreakpointStylesOnLine(lineNumber);
986            return;
987        }
988
989        delete this._breakpoints[lineNumber];
990
991        function updateStyles()
992        {
993            var lineHandle = this._codeMirror.getLineHandle(lineNumber);
994            if (!lineHandle)
995                return;
996
997            this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.HasBreakpointStyleClassName);
998            this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointResolvedStyleClassName);
999            this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointDisabledStyleClassName);
1000            this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointAutoContinueStyleClassName);
1001            this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.MultipleBreakpointsStyleClassName);
1002        }
1003
1004        this._codeMirror.operation(updateStyles.bind(this));
1005    },
1006
1007    _allColumnBreakpointInfoForLine: function(lineNumber)
1008    {
1009        return this._breakpoints[lineNumber];
1010    },
1011
1012    _setColumnBreakpointInfoForLine: function(lineNumber, columnBreakpointInfo)
1013    {
1014        console.assert(columnBreakpointInfo);
1015        this._breakpoints[lineNumber] = columnBreakpointInfo;
1016        this._setBreakpointStylesOnLine(lineNumber);
1017    },
1018
1019    _gutterMouseDown: function(codeMirror, lineNumber, gutterElement, event)
1020    {
1021        if (event.button !== 0 || event.ctrlKey)
1022            return;
1023
1024        if (!this._codeMirror.hasLineClass(lineNumber, "wrap", WebInspector.TextEditor.HasBreakpointStyleClassName)) {
1025            console.assert(!(lineNumber in this._breakpoints));
1026
1027            // No breakpoint, add a new one.
1028            if (this._delegate && typeof this._delegate.textEditorBreakpointAdded === "function") {
1029                var data = this._delegate.textEditorBreakpointAdded(this, lineNumber, 0);
1030                if (data) {
1031                    var breakpointInfo = data.breakpointInfo;
1032                    if (breakpointInfo)
1033                        this._addBreakpointToLineAndColumnWithInfo(data.lineNumber, data.columnNumber, breakpointInfo);
1034                }
1035            }
1036
1037            return;
1038        }
1039
1040        console.assert(lineNumber in this._breakpoints);
1041
1042        if (this._codeMirror.hasLineClass(lineNumber, "wrap", WebInspector.TextEditor.MultipleBreakpointsStyleClassName)) {
1043            console.assert(!isEmptyObject(this._breakpoints[lineNumber]));
1044            return;
1045        }
1046
1047        // Single existing breakpoint, start tracking it for dragging.
1048        console.assert(Object.keys(this._breakpoints[lineNumber]).length === 1);
1049        var columnNumber = Object.keys(this._breakpoints[lineNumber])[0];
1050        this._draggingBreakpointInfo = this._breakpoints[lineNumber][columnNumber];
1051        this._lineNumberWithMousedDownBreakpoint = lineNumber;
1052        this._lineNumberWithDraggedBreakpoint = lineNumber;
1053        this._columnNumberWithMousedDownBreakpoint = columnNumber;
1054        this._columnNumberWithDraggedBreakpoint = columnNumber;
1055
1056        this._documentMouseMovedEventListener = this._documentMouseMoved.bind(this);
1057        this._documentMouseUpEventListener = this._documentMouseUp.bind(this);
1058
1059        // Register these listeners on the document so we can track the mouse if it leaves the gutter.
1060        document.addEventListener("mousemove", this._documentMouseMovedEventListener, true);
1061        document.addEventListener("mouseup", this._documentMouseUpEventListener, true);
1062    },
1063
1064    _gutterContextMenu: function(codeMirror, lineNumber, gutterElement, event)
1065    {
1066        if (this._delegate && typeof this._delegate.textEditorGutterContextMenu === "function") {
1067            var breakpoints = [];
1068            for (var columnNumber in this._breakpoints[lineNumber])
1069                breakpoints.push({lineNumber:lineNumber, columnNumber:columnNumber});
1070
1071            this._delegate.textEditorGutterContextMenu(this, lineNumber, 0, breakpoints, event);
1072        }
1073    },
1074
1075    _documentMouseMoved: function(event)
1076    {
1077        console.assert("_lineNumberWithMousedDownBreakpoint" in this);
1078        if (!("_lineNumberWithMousedDownBreakpoint" in this))
1079            return;
1080
1081        event.preventDefault();
1082
1083        var lineNumber;
1084        var position = this._codeMirror.coordsChar({left: event.pageX, top: event.pageY});
1085
1086        // CodeMirror's coordsChar returns a position even if it is outside the bounds. Nullify the position
1087        // if the event is outside the bounds of the gutter so we will remove the breakpoint.
1088        var gutterBounds = this._codeMirror.getGutterElement().getBoundingClientRect();
1089        if (event.pageX < gutterBounds.left || event.pageX > gutterBounds.right || event.pageY < gutterBounds.top || event.pageY > gutterBounds.bottom)
1090            position = null;
1091
1092        // If we have a position and it has a line then use it.
1093        if (position && "line" in position)
1094            lineNumber = position.line;
1095
1096        // The _lineNumberWithDraggedBreakpoint property can be undefined if the user drags
1097        // outside of the gutter. The lineNumber variable can be undefined for the same reason.
1098
1099        if (lineNumber === this._lineNumberWithDraggedBreakpoint)
1100            return;
1101
1102        // Record that the mouse dragged some so when mouse up fires we know to do the
1103        // work of removing and moving the breakpoint.
1104        this._mouseDragged = true;
1105
1106        if ("_lineNumberWithDraggedBreakpoint" in this) {
1107            // We have a line that is currently showing the dragged breakpoint. Remove that breakpoint
1108            // and restore the previous one (if any.)
1109            if (this._previousColumnBreakpointInfo)
1110                this._setColumnBreakpointInfoForLine(this._lineNumberWithDraggedBreakpoint, this._previousColumnBreakpointInfo);
1111            else
1112                this._removeBreakpointFromLineAndColumn(this._lineNumberWithDraggedBreakpoint, this._columnNumberWithDraggedBreakpoint);
1113
1114            delete this._previousColumnBreakpointInfo;
1115            delete this._lineNumberWithDraggedBreakpoint;
1116            delete this._columnNumberWithDraggedBreakpoint;
1117        }
1118
1119        if (lineNumber !== undefined) {
1120            // We have a new line that will now show the dragged breakpoint.
1121            var newColumnBreakpoints = {};
1122            var columnNumber = (lineNumber === this._lineNumberWithMousedDownBreakpoint ? this._columnNumberWithDraggedBreakpoint : 0);
1123            newColumnBreakpoints[columnNumber] = this._draggingBreakpointInfo;
1124            this._previousColumnBreakpointInfo = this._allColumnBreakpointInfoForLine(lineNumber);
1125            this._setColumnBreakpointInfoForLine(lineNumber, newColumnBreakpoints);
1126            this._lineNumberWithDraggedBreakpoint = lineNumber;
1127            this._columnNumberWithDraggedBreakpoint = columnNumber;
1128        }
1129    },
1130
1131    _documentMouseUp: function(event)
1132    {
1133        console.assert("_lineNumberWithMousedDownBreakpoint" in this);
1134        if (!("_lineNumberWithMousedDownBreakpoint" in this))
1135            return;
1136
1137        event.preventDefault();
1138
1139        document.removeEventListener("mousemove", this._documentMouseMovedEventListener, true);
1140        document.removeEventListener("mouseup", this._documentMouseUpEventListener, true);
1141
1142        const delegateImplementsBreakpointClicked = this._delegate && typeof this._delegate.textEditorBreakpointClicked === "function";
1143        const delegateImplementsBreakpointRemoved = this._delegate && typeof this._delegate.textEditorBreakpointRemoved === "function";
1144        const delegateImplementsBreakpointMoved = this._delegate && typeof this._delegate.textEditorBreakpointMoved === "function";
1145
1146        if (this._mouseDragged) {
1147            if (!("_lineNumberWithDraggedBreakpoint" in this)) {
1148                // The breakpoint was dragged off the gutter, remove it.
1149                if (delegateImplementsBreakpointRemoved) {
1150                    this._ignoreSetBreakpointInfoCalls = true;
1151                    this._delegate.textEditorBreakpointRemoved(this, this._lineNumberWithMousedDownBreakpoint, this._columnNumberWithMousedDownBreakpoint);
1152                    delete this._ignoreSetBreakpointInfoCalls;
1153                }
1154            } else if (this._lineNumberWithMousedDownBreakpoint !== this._lineNumberWithDraggedBreakpoint) {
1155                // The dragged breakpoint was moved to a new line.
1156
1157                // If there is are breakpoints already at the drop line, tell the delegate to remove them.
1158                // We have already updated the breakpoint info internally, so when the delegate removes the breakpoints
1159                // and tells us to clear the breakpoint info, we can ignore those calls.
1160                if (this._previousColumnBreakpointInfo && delegateImplementsBreakpointRemoved) {
1161                    this._ignoreSetBreakpointInfoCalls = true;
1162                    for (var columnNumber in this._previousColumnBreakpointInfo)
1163                        this._delegate.textEditorBreakpointRemoved(this, this._lineNumberWithDraggedBreakpoint, columnNumber);
1164                    delete this._ignoreSetBreakpointInfoCalls;
1165                }
1166
1167                // Tell the delegate to move the breakpoint from one line to another.
1168                if (delegateImplementsBreakpointMoved) {
1169                    this._ignoreSetBreakpointInfoCalls = true;
1170                    this._delegate.textEditorBreakpointMoved(this, this._lineNumberWithMousedDownBreakpoint, this._columnNumberWithMousedDownBreakpoint, this._lineNumberWithDraggedBreakpoint, this._columnNumberWithDraggedBreakpoint);
1171                    delete this._ignoreSetBreakpointInfoCalls;
1172                }
1173            }
1174        } else {
1175            // Toggle the disabled state of the breakpoint.
1176            console.assert(this._lineNumberWithMousedDownBreakpoint in this._breakpoints);
1177            console.assert(this._columnNumberWithMousedDownBreakpoint in this._breakpoints[this._lineNumberWithMousedDownBreakpoint]);
1178            if (this._lineNumberWithMousedDownBreakpoint in this._breakpoints && this._columnNumberWithMousedDownBreakpoint in this._breakpoints[this._lineNumberWithMousedDownBreakpoint] && delegateImplementsBreakpointClicked)
1179                this._delegate.textEditorBreakpointClicked(this, this._lineNumberWithMousedDownBreakpoint, this._columnNumberWithMousedDownBreakpoint);
1180        }
1181
1182        delete this._documentMouseMovedEventListener;
1183        delete this._documentMouseUpEventListener;
1184        delete this._lineNumberWithMousedDownBreakpoint;
1185        delete this._lineNumberWithDraggedBreakpoint;
1186        delete this._columnNumberWithMousedDownBreakpoint;
1187        delete this._columnNumberWithDraggedBreakpoint;
1188        delete this._previousColumnBreakpointInfo;
1189        delete this._mouseDragged;
1190    },
1191
1192    _openClickedLinks: function(event)
1193    {
1194        // Get the position in the text and the token at that position.
1195        var position = this._codeMirror.coordsChar({left: event.pageX, top: event.pageY});
1196        var tokenInfo = this._codeMirror.getTokenAt(position);
1197        if (!tokenInfo || !tokenInfo.type || !tokenInfo.string)
1198            return;
1199
1200        // If the token is not a link, then ignore it.
1201        if (!/\blink\b/.test(tokenInfo.type))
1202            return;
1203
1204        // The token string is the URL we should open. It might be a relative URL.
1205        var url = tokenInfo.string;
1206
1207        // Get the base URL.
1208        var baseURL = "";
1209        if (this._delegate && typeof this._delegate.textEditorBaseURL === "function")
1210            baseURL = this._delegate.textEditorBaseURL(this);
1211
1212        // Open the link after resolving the absolute URL from the base URL.
1213        WebInspector.openURL(absoluteURL(url, baseURL));
1214
1215        // Stop processing the event.
1216        event.preventDefault();
1217        event.stopPropagation();
1218    },
1219
1220    _isPositionVisible: function(position)
1221    {
1222        var scrollInfo = this._codeMirror.getScrollInfo();
1223        var visibleRangeStart = scrollInfo.top;
1224        var visibleRangeEnd = visibleRangeStart + scrollInfo.clientHeight;
1225        var coords = this._codeMirror.charCoords(position, "local");
1226
1227        return coords.top >= visibleRangeStart && coords.bottom <= visibleRangeEnd;
1228    },
1229
1230    _scrollIntoViewCentered: function(position)
1231    {
1232        var scrollInfo = this._codeMirror.getScrollInfo();
1233        var lineHeight = Math.ceil(this._codeMirror.defaultTextHeight());
1234        var margin = Math.floor((scrollInfo.clientHeight - lineHeight) / 2);
1235        this._codeMirror.scrollIntoView(position, margin);
1236    },
1237
1238    _prettyPrint: function(pretty)
1239    {
1240        function prettyPrintAndUpdateEditor()
1241        {
1242            const start = {line: 0, ch: 0};
1243            const end = {line: this._codeMirror.lineCount() - 1};
1244
1245            var oldSelectionAnchor = this._codeMirror.getCursor("anchor");
1246            var oldSelectionHead = this._codeMirror.getCursor("head");
1247            var newSelectionAnchor, newSelectionHead;
1248            var newExecutionLocation = null;
1249
1250            if (pretty) {
1251                // <rdar://problem/10593948> Provide a way to change the tab width in the Web Inspector
1252                const indentString = "    ";
1253                var originalLineEndings = [];
1254                var formattedLineEndings = [];
1255                var mapping = {original: [0], formatted: [0]};
1256                var builder = new FormatterContentBuilder(mapping, originalLineEndings, formattedLineEndings, 0, 0, indentString);
1257                var formatter = new Formatter(this._codeMirror, builder);
1258                formatter.format(start, end);
1259
1260                this._formatterSourceMap = WebInspector.FormatterSourceMap.fromBuilder(builder);
1261
1262                this._codeMirror.setValue(builder.formattedContent);
1263
1264                if (this._positionToReveal) {
1265                    var newRevealPosition = this._formatterSourceMap.originalToFormatted(this._positionToReveal.lineNumber, this._positionToReveal.columnNumber);
1266                    this._positionToReveal = new WebInspector.SourceCodePosition(newRevealPosition.lineNumber, newRevealPosition.columnNumber);
1267                }
1268
1269                if (this._textRangeToSelect) {
1270                    var mappedRevealSelectionStart = this._formatterSourceMap.originalToFormatted(this._textRangeToSelect.startLine, this._textRangeToSelect.startColumn);
1271                    var mappedRevealSelectionEnd = this._formatterSourceMap.originalToFormatted(this._textRangeToSelect.endLine, this._textRangeToSelect.endColumn);
1272                    this._textRangeToSelect = new WebInspector.TextRange(mappedRevealSelectionStart.lineNumber, mappedRevealSelectionStart.columnNumber, mappedRevealSelectionEnd.lineNumber, mappedRevealSelectionEnd.columnNumber);
1273                }
1274
1275                if (!isNaN(this._executionLineNumber)) {
1276                    console.assert(!isNaN(this._executionColumnNumber));
1277                    newExecutionLocation = this._formatterSourceMap.originalToFormatted(this._executionLineNumber, this._executionColumnNumber);
1278                }
1279
1280                var mappedAnchorLocation = this._formatterSourceMap.originalToFormatted(oldSelectionAnchor.line, oldSelectionAnchor.ch);
1281                var mappedHeadLocation = this._formatterSourceMap.originalToFormatted(oldSelectionHead.line, oldSelectionHead.ch);
1282                newSelectionAnchor = {line:mappedAnchorLocation.lineNumber, ch:mappedAnchorLocation.columnNumber};
1283                newSelectionHead = {line:mappedHeadLocation.lineNumber, ch:mappedHeadLocation.columnNumber};
1284            } else {
1285                this._codeMirror.undo();
1286
1287                if (this._positionToReveal) {
1288                    var newRevealPosition = this._formatterSourceMap.formattedToOriginal(this._positionToReveal.lineNumber, this._positionToReveal.columnNumber);
1289                    this._positionToReveal = new WebInspector.SourceCodePosition(newRevealPosition.lineNumber, newRevealPosition.columnNumber);
1290                }
1291
1292                if (this._textRangeToSelect) {
1293                    var mappedRevealSelectionStart = this._formatterSourceMap.formattedToOriginal(this._textRangeToSelect.startLine, this._textRangeToSelect.startColumn);
1294                    var mappedRevealSelectionEnd = this._formatterSourceMap.formattedToOriginal(this._textRangeToSelect.endLine, this._textRangeToSelect.endColumn);
1295                    this._textRangeToSelect = new WebInspector.TextRange(mappedRevealSelectionStart.lineNumber, mappedRevealSelectionStart.columnNumber, mappedRevealSelectionEnd.lineNumber, mappedRevealSelectionEnd.columnNumber);
1296                }
1297
1298                if (!isNaN(this._executionLineNumber)) {
1299                    console.assert(!isNaN(this._executionColumnNumber));
1300                    newExecutionLocation = this._formatterSourceMap.formattedToOriginal(this._executionLineNumber, this._executionColumnNumber);
1301                }
1302
1303                var mappedAnchorLocation = this._formatterSourceMap.formattedToOriginal(oldSelectionAnchor.line, oldSelectionAnchor.ch);
1304                var mappedHeadLocation = this._formatterSourceMap.formattedToOriginal(oldSelectionHead.line, oldSelectionHead.ch);
1305                newSelectionAnchor = {line:mappedAnchorLocation.lineNumber, ch:mappedAnchorLocation.columnNumber};
1306                newSelectionHead = {line:mappedHeadLocation.lineNumber, ch:mappedHeadLocation.columnNumber};
1307
1308                this._formatterSourceMap = null;
1309            }
1310
1311            this._scrollIntoViewCentered(newSelectionAnchor);
1312            this._codeMirror.setSelection(newSelectionAnchor, newSelectionHead);
1313
1314            if (newExecutionLocation) {
1315                delete this._executionLineHandle;
1316                this.executionColumnNumber = newExecutionLocation.columnNumber;
1317                this.executionLineNumber = newExecutionLocation.lineNumber;
1318            }
1319
1320            // FIXME: <rdar://problem/13129955> FindBanner: New searches should not lose search position (start from current selection/caret)
1321            if (this.currentSearchQuery) {
1322                var searchQuery = this.currentSearchQuery;
1323                this.searchCleared();
1324                // Set timeout so that this happens after the current CodeMirror operation.
1325                // The editor has to update for the value and selection changes.
1326                setTimeout(function(query) {
1327                    this.performSearch(searchQuery);
1328                }.bind(this), 0);
1329            }
1330
1331            if (this._delegate && typeof this._delegate.textEditorUpdatedFormatting === "function")
1332                this._delegate.textEditorUpdatedFormatting(this);
1333        }
1334
1335        this._codeMirror.operation(prettyPrintAndUpdateEditor.bind(this));
1336    }
1337};
1338
1339WebInspector.TextEditor.prototype.__proto__ = WebInspector.Object.prototype;
1340