Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / SourceFrame.js
1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 /**
32  * @extends {WebInspector.View}
33  * @constructor
34  * @implements {WebInspector.Replaceable}
35  * @param {!WebInspector.ContentProvider} contentProvider
36  */
37 WebInspector.SourceFrame = function(contentProvider)
38 {
39     WebInspector.View.call(this);
40     this.element.classList.add("script-view");
41
42     this._url = contentProvider.contentURL();
43     this._contentProvider = contentProvider;
44
45     var textEditorDelegate = new WebInspector.TextEditorDelegateForSourceFrame(this);
46
47     WebInspector.moduleManager.loadModule("codemirror");
48     this._textEditor = new WebInspector.CodeMirrorTextEditor(this._url, textEditorDelegate);
49
50     this._currentSearchResultIndex = -1;
51     this._searchResults = [];
52
53     this._messages = [];
54     this._rowMessages = {};
55     this._messageBubbles = {};
56
57     this._textEditor.setReadOnly(!this.canEditSource());
58
59     this._shortcuts = {};
60     this.element.addEventListener("keydown", this._handleKeyDown.bind(this), false);
61
62     this._sourcePosition = new WebInspector.StatusBarText("", "source-frame-cursor-position");
63 }
64
65 /**
66  * @param {string} query
67  * @param {string=} modifiers
68  * @return {!RegExp}
69  */
70 WebInspector.SourceFrame.createSearchRegex = function(query, modifiers)
71 {
72     var regex;
73     modifiers = modifiers || "";
74
75     // First try creating regex if user knows the / / hint.
76     try {
77         if (/^\/.+\/$/.test(query)) {
78             regex = new RegExp(query.substring(1, query.length - 1), modifiers);
79             regex.__fromRegExpQuery = true;
80         }
81     } catch (e) {
82         // Silent catch.
83     }
84
85     // Otherwise just do case-insensitive search.
86     if (!regex)
87         regex = createPlainTextSearchRegex(query, "i" + modifiers);
88
89     return regex;
90 }
91
92 WebInspector.SourceFrame.Events = {
93     ScrollChanged: "ScrollChanged",
94     SelectionChanged: "SelectionChanged",
95     JumpHappened: "JumpHappened"
96 }
97
98 WebInspector.SourceFrame.prototype = {
99     /**
100      * @param {number} key
101      * @param {function()} handler
102      */
103     addShortcut: function(key, handler)
104     {
105         this._shortcuts[key] = handler;
106     },
107
108     wasShown: function()
109     {
110         this._ensureContentLoaded();
111         this._textEditor.show(this.element);
112         this._editorAttached = true;
113         this._wasShownOrLoaded();
114     },
115
116     /**
117      * @return {boolean}
118      */
119     _isEditorShowing: function()
120     {
121         return this.isShowing() && this._editorAttached;
122     },
123
124     willHide: function()
125     {
126         WebInspector.View.prototype.willHide.call(this);
127
128         this._clearPositionHighlight();
129         this._clearLineToReveal();
130     },
131
132     /**
133      * @return {?Element}
134      */
135     statusBarText: function()
136     {
137         return this._sourcePosition.element;
138     },
139
140     /**
141      * @return {!Array.<!Element>}
142      */
143     statusBarItems: function()
144     {
145         return [];
146     },
147
148     /**
149      * @return {!Element}
150      */
151     defaultFocusedElement: function()
152     {
153         return this._textEditor.defaultFocusedElement();
154     },
155
156     get loaded()
157     {
158         return this._loaded;
159     },
160
161     /**
162      * @return {boolean}
163      */
164     hasContent: function()
165     {
166         return true;
167     },
168
169     get textEditor()
170     {
171         return this._textEditor;
172     },
173
174     _ensureContentLoaded: function()
175     {
176         if (!this._contentRequested) {
177             this._contentRequested = true;
178             this._contentProvider.requestContent(this.setContent.bind(this));
179         }
180     },
181
182     addMessage: function(msg)
183     {
184         this._messages.push(msg);
185         if (this.loaded)
186             this.addMessageToSource(msg.line - 1, msg);
187     },
188
189     clearMessages: function()
190     {
191         for (var line in this._messageBubbles) {
192             var bubble = this._messageBubbles[line];
193             var lineNumber = parseInt(line, 10);
194             this._textEditor.removeDecoration(lineNumber, bubble);
195         }
196
197         this._messages = [];
198         this._rowMessages = {};
199         this._messageBubbles = {};
200     },
201
202     /**
203      * @override
204      * @return {boolean}
205      */
206     canHighlightPosition: function()
207     {
208         return true;
209     },
210
211     /**
212      * @override
213      */
214     highlightPosition: function(line, column)
215     {
216         this._clearLineToReveal();
217         this._clearLineToScrollTo();
218         this._clearSelectionToSet();
219         this._positionToHighlight = { line: line, column: column };
220         this._innerHighlightPositionIfNeeded();
221     },
222
223     _innerHighlightPositionIfNeeded: function()
224     {
225         if (!this._positionToHighlight)
226             return;
227
228         if (!this.loaded || !this._isEditorShowing())
229             return;
230
231         this._textEditor.highlightPosition(this._positionToHighlight.line, this._positionToHighlight.column);
232         delete this._positionToHighlight;
233     },
234
235     _clearPositionHighlight: function()
236     {
237         this._textEditor.clearPositionHighlight();
238         delete this._positionToHighlight;
239     },
240
241     /**
242      * @param {number} line
243      */
244     revealLine: function(line)
245     {
246         this._clearPositionHighlight();
247         this._clearLineToScrollTo();
248         this._clearSelectionToSet();
249         this._lineToReveal = line;
250         this._innerRevealLineIfNeeded();
251     },
252
253     _innerRevealLineIfNeeded: function()
254     {
255         if (typeof this._lineToReveal === "number") {
256             if (this.loaded && this._isEditorShowing()) {
257                 this._textEditor.revealLine(this._lineToReveal);
258                 delete this._lineToReveal;
259             }
260         }
261     },
262
263     _clearLineToReveal: function()
264     {
265         delete this._lineToReveal;
266     },
267
268     /**
269      * @param {number} line
270      */
271     scrollToLine: function(line)
272     {
273         this._clearPositionHighlight();
274         this._clearLineToReveal();
275         this._lineToScrollTo = line;
276         this._innerScrollToLineIfNeeded();
277     },
278
279     _innerScrollToLineIfNeeded: function()
280     {
281         if (typeof this._lineToScrollTo === "number") {
282             if (this.loaded && this._isEditorShowing()) {
283                 this._textEditor.scrollToLine(this._lineToScrollTo);
284                 delete this._lineToScrollTo;
285             }
286         }
287     },
288
289     _clearLineToScrollTo: function()
290     {
291         delete this._lineToScrollTo;
292     },
293
294     /**
295      * @param {!WebInspector.TextRange} textRange
296      */
297     setSelection: function(textRange)
298     {
299         this._selectionToSet = textRange;
300         this._innerSetSelectionIfNeeded();
301     },
302
303     _innerSetSelectionIfNeeded: function()
304     {
305         if (this._selectionToSet && this.loaded && this._isEditorShowing()) {
306             this._textEditor.setSelection(this._selectionToSet);
307             delete this._selectionToSet;
308         }
309     },
310
311     _clearSelectionToSet: function()
312     {
313         delete this._selectionToSet;
314     },
315
316     _wasShownOrLoaded: function()
317     {
318         this._innerHighlightPositionIfNeeded();
319         this._innerRevealLineIfNeeded();
320         this._innerSetSelectionIfNeeded();
321         this._innerScrollToLineIfNeeded();
322     },
323
324     onTextChanged: function(oldRange, newRange)
325     {
326         if (this._searchResultsChangedCallback && !this._isReplacing)
327             this._searchResultsChangedCallback();
328         this.clearMessages();
329     },
330
331     _simplifyMimeType: function(content, mimeType)
332     {
333         if (!mimeType)
334             return "";
335         if (mimeType.indexOf("javascript") >= 0 ||
336             mimeType.indexOf("jscript") >= 0 ||
337             mimeType.indexOf("ecmascript") >= 0)
338             return "text/javascript";
339         // A hack around the fact that files with "php" extension might be either standalone or html embedded php scripts.
340         if (mimeType === "text/x-php" && content.match(/\<\?.*\?\>/g))
341             return "application/x-httpd-php";
342         return mimeType;
343     },
344
345     /**
346      * @param {string} highlighterType
347      */
348     setHighlighterType: function(highlighterType)
349     {
350         this._highlighterType = highlighterType;
351         this._updateHighlighterType("");
352     },
353
354     /**
355      * @param {string} content
356      */
357     _updateHighlighterType: function(content)
358     {
359         this._textEditor.setMimeType(this._simplifyMimeType(content, this._highlighterType));
360     },
361
362     /**
363      * @param {?string} content
364      */
365     setContent: function(content)
366     {
367         if (!this._loaded) {
368             this._loaded = true;
369             this._textEditor.setText(content || "");
370             this._textEditor.markClean();
371         } else {
372             var firstLine = this._textEditor.firstVisibleLine();
373             var selection = this._textEditor.selection();
374             this._textEditor.setText(content || "");
375             this._textEditor.scrollToLine(firstLine);
376             this._textEditor.setSelection(selection);
377         }
378
379         this._updateHighlighterType(content || "");
380
381         this._textEditor.beginUpdates();
382
383         this._setTextEditorDecorations();
384
385         this._wasShownOrLoaded();
386
387         if (this._delayedFindSearchMatches) {
388             this._delayedFindSearchMatches();
389             delete this._delayedFindSearchMatches;
390         }
391
392         this.onTextEditorContentLoaded();
393
394         this._textEditor.endUpdates();
395     },
396
397     onTextEditorContentLoaded: function() {},
398
399     _setTextEditorDecorations: function()
400     {
401         this._rowMessages = {};
402         this._messageBubbles = {};
403
404         this._textEditor.beginUpdates();
405
406         this._addExistingMessagesToSource();
407
408         this._textEditor.endUpdates();
409     },
410
411     /**
412      * @param {string} query
413      * @param {boolean} shouldJump
414      * @param {function(!WebInspector.View, number)} callback
415      * @param {function(number)} currentMatchChangedCallback
416      * @param {function()} searchResultsChangedCallback
417      */
418     performSearch: function(query, shouldJump, callback, currentMatchChangedCallback, searchResultsChangedCallback)
419     {
420         /**
421          * @param {string} query
422          * @this {WebInspector.SourceFrame}
423          */
424         function doFindSearchMatches(query)
425         {
426             this._currentSearchResultIndex = -1;
427             this._searchResults = [];
428
429             var regex = WebInspector.SourceFrame.createSearchRegex(query);
430             this._searchRegex = regex;
431             this._searchResults = this._collectRegexMatches(regex);
432             if (!this._searchResults.length)
433                 this._textEditor.cancelSearchResultsHighlight();
434             else if (shouldJump)
435                 this.jumpToNextSearchResult();
436             else
437                 this._textEditor.highlightSearchResults(regex, null);
438             callback(this, this._searchResults.length);
439         }
440
441         this._resetSearch();
442         this._currentSearchMatchChangedCallback = currentMatchChangedCallback;
443         this._searchResultsChangedCallback = searchResultsChangedCallback;
444         if (this.loaded)
445             doFindSearchMatches.call(this, query);
446         else
447             this._delayedFindSearchMatches = doFindSearchMatches.bind(this, query);
448
449         this._ensureContentLoaded();
450     },
451
452     _editorFocused: function()
453     {
454         if (!this._searchResults.length)
455             return;
456         this._currentSearchResultIndex = -1;
457         if (this._currentSearchMatchChangedCallback)
458             this._currentSearchMatchChangedCallback(this._currentSearchResultIndex);
459         this._textEditor.highlightSearchResults(this._searchRegex, null);
460     },
461
462     _searchResultAfterSelectionIndex: function(selection)
463     {
464         if (!selection)
465             return 0;
466         for (var i = 0; i < this._searchResults.length; ++i) {
467             if (this._searchResults[i].compareTo(selection) >= 0)
468                 return i;
469         }
470         return 0;
471     },
472
473     _resetSearch: function()
474     {
475         delete this._delayedFindSearchMatches;
476         delete this._currentSearchMatchChangedCallback;
477         delete this._searchResultsChangedCallback;
478         this._currentSearchResultIndex = -1;
479         this._searchResults = [];
480         delete this._searchRegex;
481     },
482
483     searchCanceled: function()
484     {
485         var range = this._currentSearchResultIndex !== -1 ? this._searchResults[this._currentSearchResultIndex] : null;
486         this._resetSearch();
487         if (!this.loaded)
488             return;
489         this._textEditor.cancelSearchResultsHighlight();
490         if (range)
491             this._textEditor.setSelection(range);
492     },
493
494     /**
495      * @return {boolean}
496      */
497     hasSearchResults: function()
498     {
499         return this._searchResults.length > 0;
500     },
501
502     jumpToFirstSearchResult: function()
503     {
504         this.jumpToSearchResult(0);
505     },
506
507     jumpToLastSearchResult: function()
508     {
509         this.jumpToSearchResult(this._searchResults.length - 1);
510     },
511
512     jumpToNextSearchResult: function()
513     {
514         var currentIndex = this._searchResultAfterSelectionIndex(this._textEditor.selection());
515         var nextIndex = this._currentSearchResultIndex === -1 ? currentIndex : currentIndex + 1;
516         this.jumpToSearchResult(nextIndex);
517     },
518
519     jumpToPreviousSearchResult: function()
520     {
521         var currentIndex = this._searchResultAfterSelectionIndex(this._textEditor.selection());
522         this.jumpToSearchResult(currentIndex - 1);
523     },
524
525     /**
526      * @return {boolean}
527      */
528     showingFirstSearchResult: function()
529     {
530         return this._searchResults.length &&  this._currentSearchResultIndex === 0;
531     },
532
533     /**
534      * @return {boolean}
535      */
536     showingLastSearchResult: function()
537     {
538         return this._searchResults.length && this._currentSearchResultIndex === (this._searchResults.length - 1);
539     },
540
541     get currentSearchResultIndex()
542     {
543         return this._currentSearchResultIndex;
544     },
545
546     jumpToSearchResult: function(index)
547     {
548         if (!this.loaded || !this._searchResults.length)
549             return;
550         this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length;
551         if (this._currentSearchMatchChangedCallback)
552             this._currentSearchMatchChangedCallback(this._currentSearchResultIndex);
553         this._textEditor.highlightSearchResults(this._searchRegex, this._searchResults[this._currentSearchResultIndex]);
554     },
555
556     /**
557      * @param {string} text
558      */
559     replaceSelectionWith: function(text)
560     {
561         var range = this._searchResults[this._currentSearchResultIndex];
562         if (!range)
563             return;
564         this._textEditor.highlightSearchResults(this._searchRegex, null);
565
566         this._isReplacing = true;
567         var newRange = this._textEditor.editRange(range, text);
568         delete this._isReplacing;
569
570         this._textEditor.setSelection(newRange.collapseToEnd());
571     },
572
573     /**
574      * @param {string} query
575      * @param {string} replacement
576      */
577     replaceAllWith: function(query, replacement)
578     {
579         this._textEditor.highlightSearchResults(this._searchRegex, null);
580
581         var text = this._textEditor.text();
582         var range = this._textEditor.range();
583         var regex = WebInspector.SourceFrame.createSearchRegex(query, "g");
584         if (regex.__fromRegExpQuery)
585             text = text.replace(regex, replacement);
586         else
587             text = text.replace(regex, function() { return replacement; });
588
589         this._isReplacing = true;
590         this._textEditor.editRange(range, text);
591         delete this._isReplacing;
592     },
593
594     _collectRegexMatches: function(regexObject)
595     {
596         var ranges = [];
597         for (var i = 0; i < this._textEditor.linesCount; ++i) {
598             var line = this._textEditor.line(i);
599             var offset = 0;
600             do {
601                 var match = regexObject.exec(line);
602                 if (match) {
603                     if (match[0].length)
604                         ranges.push(new WebInspector.TextRange(i, offset + match.index, i, offset + match.index + match[0].length));
605                     offset += match.index + 1;
606                     line = line.substring(match.index + 1);
607                 }
608             } while (match && line);
609         }
610         return ranges;
611     },
612
613     _addExistingMessagesToSource: function()
614     {
615         var length = this._messages.length;
616         for (var i = 0; i < length; ++i)
617             this.addMessageToSource(this._messages[i].line - 1, this._messages[i]);
618     },
619
620     /**
621      * @param {number} lineNumber
622      * @param {!WebInspector.ConsoleMessage} msg
623      */
624     addMessageToSource: function(lineNumber, msg)
625     {
626         if (lineNumber >= this._textEditor.linesCount)
627             lineNumber = this._textEditor.linesCount - 1;
628         if (lineNumber < 0)
629             lineNumber = 0;
630
631         var rowMessages = this._rowMessages[lineNumber];
632         if (!rowMessages) {
633             rowMessages = [];
634             this._rowMessages[lineNumber] = rowMessages;
635         }
636
637         for (var i = 0; i < rowMessages.length; ++i) {
638             if (rowMessages[i].consoleMessage.isEqual(msg)) {
639                 rowMessages[i].repeatCount = msg.totalRepeatCount;
640                 this._updateMessageRepeatCount(rowMessages[i]);
641                 return;
642             }
643         }
644
645         var rowMessage = { consoleMessage: msg };
646         rowMessages.push(rowMessage);
647
648         this._textEditor.beginUpdates();
649         var messageBubbleElement = this._messageBubbles[lineNumber];
650         if (!messageBubbleElement) {
651             messageBubbleElement = document.createElement("div");
652             messageBubbleElement.className = "webkit-html-message-bubble";
653             this._messageBubbles[lineNumber] = messageBubbleElement;
654             this._textEditor.addDecoration(lineNumber, messageBubbleElement);
655         }
656
657         var imageElement = document.createElement("div");
658         switch (msg.level) {
659             case WebInspector.ConsoleMessage.MessageLevel.Error:
660                 messageBubbleElement.classList.add("webkit-html-error-message");
661                 imageElement.className = "error-icon-small";
662                 break;
663             case WebInspector.ConsoleMessage.MessageLevel.Warning:
664                 messageBubbleElement.classList.add("webkit-html-warning-message");
665                 imageElement.className = "warning-icon-small";
666                 break;
667         }
668
669         var messageLineElement = document.createElement("div");
670         messageLineElement.className = "webkit-html-message-line";
671         messageBubbleElement.appendChild(messageLineElement);
672
673         // Create the image element in the Inspector's document so we can use relative image URLs.
674         messageLineElement.appendChild(imageElement);
675         messageLineElement.appendChild(document.createTextNode(msg.message));
676
677         rowMessage.element = messageLineElement;
678         rowMessage.repeatCount = msg.totalRepeatCount;
679         this._updateMessageRepeatCount(rowMessage);
680         this._textEditor.endUpdates();
681     },
682
683     _updateMessageRepeatCount: function(rowMessage)
684     {
685         if (rowMessage.repeatCount < 2)
686             return;
687
688         if (!rowMessage.repeatCountElement) {
689             var repeatCountElement = document.createElement("span");
690             rowMessage.element.appendChild(repeatCountElement);
691             rowMessage.repeatCountElement = repeatCountElement;
692         }
693
694         rowMessage.repeatCountElement.textContent = WebInspector.UIString(" (repeated %d times)", rowMessage.repeatCount);
695     },
696
697     /**
698      * @param {number} lineNumber
699      * @param {!WebInspector.ConsoleMessage} msg
700      */
701     removeMessageFromSource: function(lineNumber, msg)
702     {
703         if (lineNumber >= this._textEditor.linesCount)
704             lineNumber = this._textEditor.linesCount - 1;
705         if (lineNumber < 0)
706             lineNumber = 0;
707
708         var rowMessages = this._rowMessages[lineNumber];
709         for (var i = 0; rowMessages && i < rowMessages.length; ++i) {
710             var rowMessage = rowMessages[i];
711             if (rowMessage.consoleMessage !== msg)
712                 continue;
713
714             var messageLineElement = rowMessage.element;
715             var messageBubbleElement = messageLineElement.parentElement;
716             messageBubbleElement.removeChild(messageLineElement);
717             rowMessages.remove(rowMessage);
718             if (!rowMessages.length)
719                 delete this._rowMessages[lineNumber];
720             if (!messageBubbleElement.childElementCount) {
721                 this._textEditor.removeDecoration(lineNumber, messageBubbleElement);
722                 delete this._messageBubbles[lineNumber];
723             }
724             break;
725         }
726     },
727
728     populateLineGutterContextMenu: function(contextMenu, lineNumber)
729     {
730     },
731
732     populateTextAreaContextMenu: function(contextMenu, lineNumber)
733     {
734     },
735
736     /**
737      * @param {?WebInspector.TextRange} from
738      * @param {?WebInspector.TextRange} to
739      */
740     onJumpToPosition: function(from, to)
741     {
742         this.dispatchEventToListeners(WebInspector.SourceFrame.Events.JumpHappened, {
743             from: from,
744             to: to
745         });
746     },
747
748     inheritScrollPositions: function(sourceFrame)
749     {
750         this._textEditor.inheritScrollPositions(sourceFrame._textEditor);
751     },
752
753     /**
754      * @return {boolean}
755      */
756     canEditSource: function()
757     {
758         return false;
759     },
760
761     /**
762      * @param {!WebInspector.TextRange} textRange
763      */
764     selectionChanged: function(textRange)
765     {
766         this._updateSourcePosition(textRange);
767         this.dispatchEventToListeners(WebInspector.SourceFrame.Events.SelectionChanged, textRange);
768         WebInspector.notifications.dispatchEventToListeners(WebInspector.SourceFrame.Events.SelectionChanged, textRange);
769     },
770
771     /**
772      * @param {!WebInspector.TextRange} textRange
773      */
774     _updateSourcePosition: function(textRange)
775     {
776         if (!textRange)
777             return;
778
779         if (textRange.isEmpty()) {
780             this._sourcePosition.setText(WebInspector.UIString("Line %d, Column %d", textRange.endLine + 1, textRange.endColumn + 1));
781             return;
782         }
783         textRange = textRange.normalize();
784
785         var selectedText = this._textEditor.copyRange(textRange);
786         if (textRange.startLine === textRange.endLine)
787             this._sourcePosition.setText(WebInspector.UIString("%d characters selected", selectedText.length));
788         else
789             this._sourcePosition.setText(WebInspector.UIString("%d lines, %d characters selected", textRange.endLine - textRange.startLine + 1, selectedText.length));
790     },
791
792     /**
793      * @param {number} lineNumber
794      */
795     scrollChanged: function(lineNumber)
796     {
797         this.dispatchEventToListeners(WebInspector.SourceFrame.Events.ScrollChanged, lineNumber);
798     },
799
800     _handleKeyDown: function(e)
801     {
802         var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(e);
803         var handler = this._shortcuts[shortcutKey];
804         if (handler && handler())
805             e.consume(true);
806     },
807
808     __proto__: WebInspector.View.prototype
809 }
810
811
812 /**
813  * @implements {WebInspector.TextEditorDelegate}
814  * @constructor
815  */
816 WebInspector.TextEditorDelegateForSourceFrame = function(sourceFrame)
817 {
818     this._sourceFrame = sourceFrame;
819 }
820
821 WebInspector.TextEditorDelegateForSourceFrame.prototype = {
822     onTextChanged: function(oldRange, newRange)
823     {
824         this._sourceFrame.onTextChanged(oldRange, newRange);
825     },
826
827     /**
828      * @param {!WebInspector.TextRange} textRange
829      */
830     selectionChanged: function(textRange)
831     {
832         this._sourceFrame.selectionChanged(textRange);
833     },
834
835     /**
836      * @param {number} lineNumber
837      */
838     scrollChanged: function(lineNumber)
839     {
840         this._sourceFrame.scrollChanged(lineNumber);
841     },
842
843     editorFocused: function()
844     {
845         this._sourceFrame._editorFocused();
846     },
847
848     populateLineGutterContextMenu: function(contextMenu, lineNumber)
849     {
850         this._sourceFrame.populateLineGutterContextMenu(contextMenu, lineNumber);
851     },
852
853     populateTextAreaContextMenu: function(contextMenu, lineNumber)
854     {
855         this._sourceFrame.populateTextAreaContextMenu(contextMenu, lineNumber);
856     },
857
858     /**
859      * @param {string} hrefValue
860      * @param {boolean} isExternal
861      * @return {!Element}
862      */
863     createLink: function(hrefValue, isExternal)
864     {
865         var targetLocation = WebInspector.ParsedURL.completeURL(this._sourceFrame._url, hrefValue);
866         return WebInspector.linkifyURLAsNode(targetLocation || hrefValue, hrefValue, undefined, isExternal);
867     },
868
869     /**
870      * @param {?WebInspector.TextRange} from
871      * @param {?WebInspector.TextRange} to
872      */
873     onJumpToPosition: function(from, to)
874     {
875         this._sourceFrame.onJumpToPosition(from, to);
876     }
877 }