2 * Copyright (C) 2011 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
32 * @extends {WebInspector.VBox}
34 * @implements {WebInspector.Replaceable}
35 * @param {!WebInspector.ContentProvider} contentProvider
37 WebInspector.SourceFrame = function(contentProvider)
39 WebInspector.VBox.call(this);
40 this.element.classList.add("script-view");
42 this._url = contentProvider.contentURL();
43 this._contentProvider = contentProvider;
45 var textEditorDelegate = new WebInspector.TextEditorDelegateForSourceFrame(this);
47 this._textEditor = new WebInspector.CodeMirrorTextEditor(this._url, textEditorDelegate);
49 this._currentSearchResultIndex = -1;
50 this._searchResults = [];
53 this._rowMessageBuckets = {};
55 this._textEditor.setReadOnly(!this.canEditSource());
58 this.element.addEventListener("keydown", this._handleKeyDown.bind(this), false);
60 this._sourcePosition = new WebInspector.StatusBarText("", "source-frame-cursor-position");
62 this._errorPopoverHelper = new WebInspector.PopoverHelper(this.element, this._getErrorAnchor.bind(this), this._showErrorPopover.bind(this));
63 this._errorPopoverHelper.setTimeout(100, 100);
67 * @param {string} query
68 * @param {string=} modifiers
71 WebInspector.SourceFrame.createSearchRegex = function(query, modifiers)
74 modifiers = modifiers || "";
76 // First try creating regex if user knows the / / hint.
78 if (/^\/.+\/$/.test(query)) {
79 regex = new RegExp(query.substring(1, query.length - 1), modifiers);
80 regex.__fromRegExpQuery = true;
86 // Otherwise just do case-insensitive search.
88 regex = createPlainTextSearchRegex(query, "i" + modifiers);
93 WebInspector.SourceFrame.Events = {
94 ScrollChanged: "ScrollChanged",
95 SelectionChanged: "SelectionChanged",
96 JumpHappened: "JumpHappened"
99 WebInspector.SourceFrame.prototype = {
101 * @param {!Element} target
102 * @param {!Event} event
103 * @return {(!Element|undefined)}
105 _getErrorAnchor: function(target, event)
107 var element = target.enclosingNodeOrSelfWithClass("text-editor-line-decoration-icon")
108 || target.enclosingNodeOrSelfWithClass("text-editor-line-decoration-wave");
111 this._errorWavePopoverAnchor = new AnchorBox(event.clientX, event.clientY, 1, 1);
116 * @param {!Element} anchor
117 * @param {!WebInspector.Popover} popover
119 _showErrorPopover: function(anchor, popover)
121 var messageBucket = anchor.enclosingNodeOrSelfWithClass("text-editor-line-decoration")._messageBucket;
122 var messagesOutline = messageBucket.messagesDescription();
123 var popoverAnchor = anchor.enclosingNodeOrSelfWithClass("text-editor-line-decoration-icon") ? anchor : this._errorWavePopoverAnchor;
124 popover.show(messagesOutline, popoverAnchor);
128 * @param {number} key
129 * @param {function():boolean} handler
131 addShortcut: function(key, handler)
133 this._shortcuts[key] = handler;
138 this._ensureContentLoaded();
139 this._textEditor.show(this.element);
140 this._editorAttached = true;
141 for (var line in this._rowMessageBuckets) {
142 var bucket = this._rowMessageBuckets[line];
143 bucket._updateDecorationPosition();
145 this._wasShownOrLoaded();
151 _isEditorShowing: function()
153 return this.isShowing() && this._editorAttached;
158 WebInspector.View.prototype.willHide.call(this);
160 this._clearPositionToReveal();
166 statusBarText: function()
168 return this._sourcePosition.element;
172 * @return {!Array.<!Element>}
174 statusBarItems: function()
182 defaultFocusedElement: function()
184 return this._textEditor.defaultFocusedElement();
195 hasContent: function()
202 return this._textEditor;
205 _ensureContentLoaded: function()
207 if (!this._contentRequested) {
208 this._contentRequested = true;
209 this._contentProvider.requestContent(this.setContent.bind(this));
213 addMessage: function(msg)
215 this._messages.push(msg);
217 this.addMessageToSource(msg.line - 1, msg);
220 clearMessages: function()
222 for (var line in this._rowMessageBuckets) {
223 var bubble = this._rowMessageBuckets[line];
224 bubble.detachFromEditor();
228 this._rowMessageBuckets = {};
232 * @param {number} line
233 * @param {number=} column
234 * @param {boolean=} shouldHighlight
236 revealPosition: function(line, column, shouldHighlight)
238 this._clearLineToScrollTo();
239 this._clearSelectionToSet();
240 this._positionToReveal = { line: line, column: column, shouldHighlight: shouldHighlight };
241 this._innerRevealPositionIfNeeded();
244 _innerRevealPositionIfNeeded: function()
246 if (!this._positionToReveal)
249 if (!this.loaded || !this._isEditorShowing())
252 this._textEditor.revealPosition(this._positionToReveal.line, this._positionToReveal.column, this._positionToReveal.shouldHighlight);
253 delete this._positionToReveal;
256 _clearPositionToReveal: function()
258 this._textEditor.clearPositionHighlight();
259 delete this._positionToReveal;
263 * @param {number} line
265 scrollToLine: function(line)
267 this._clearPositionToReveal();
268 this._lineToScrollTo = line;
269 this._innerScrollToLineIfNeeded();
272 _innerScrollToLineIfNeeded: function()
274 if (typeof this._lineToScrollTo === "number") {
275 if (this.loaded && this._isEditorShowing()) {
276 this._textEditor.scrollToLine(this._lineToScrollTo);
277 delete this._lineToScrollTo;
282 _clearLineToScrollTo: function()
284 delete this._lineToScrollTo;
288 * @return {!WebInspector.TextRange}
290 selection: function()
292 return this.textEditor.selection();
296 * @param {!WebInspector.TextRange} textRange
298 setSelection: function(textRange)
300 this._selectionToSet = textRange;
301 this._innerSetSelectionIfNeeded();
304 _innerSetSelectionIfNeeded: function()
306 if (this._selectionToSet && this.loaded && this._isEditorShowing()) {
307 this._textEditor.setSelection(this._selectionToSet);
308 delete this._selectionToSet;
312 _clearSelectionToSet: function()
314 delete this._selectionToSet;
317 _wasShownOrLoaded: function()
319 this._innerRevealPositionIfNeeded();
320 this._innerSetSelectionIfNeeded();
321 this._innerScrollToLineIfNeeded();
324 onTextChanged: function(oldRange, newRange)
326 if (this._searchResultsChangedCallback && !this._isReplacing)
327 this._searchResultsChangedCallback();
328 this.clearMessages();
331 _simplifyMimeType: function(content, mimeType)
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";
346 * @param {string} highlighterType
348 setHighlighterType: function(highlighterType)
350 this._highlighterType = highlighterType;
351 this._updateHighlighterType("");
355 * @param {string} content
357 _updateHighlighterType: function(content)
359 this._textEditor.setMimeType(this._simplifyMimeType(content, this._highlighterType));
363 * @param {?string} content
365 setContent: function(content)
369 this._textEditor.setText(content || "");
370 this._textEditor.markClean();
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);
379 this._updateHighlighterType(content || "");
381 this._textEditor.beginUpdates();
383 this._setTextEditorDecorations();
385 this._wasShownOrLoaded();
387 if (this._delayedFindSearchMatches) {
388 this._delayedFindSearchMatches();
389 delete this._delayedFindSearchMatches;
392 this.onTextEditorContentLoaded();
394 this._textEditor.endUpdates();
397 onTextEditorContentLoaded: function() {},
399 _setTextEditorDecorations: function()
401 this._rowMessageBuckets = {};
403 this._textEditor.beginUpdates();
404 this._addExistingMessagesToSource();
405 this._textEditor.endUpdates();
409 * @param {string} query
410 * @param {boolean} shouldJump
411 * @param {boolean} jumpBackwards
412 * @param {function(!WebInspector.View, number)} callback
413 * @param {function(number)} currentMatchChangedCallback
414 * @param {function()} searchResultsChangedCallback
416 performSearch: function(query, shouldJump, jumpBackwards, callback, currentMatchChangedCallback, searchResultsChangedCallback)
419 * @param {string} query
420 * @this {WebInspector.SourceFrame}
422 function doFindSearchMatches(query)
424 this._currentSearchResultIndex = -1;
425 this._searchResults = [];
427 var regex = WebInspector.SourceFrame.createSearchRegex(query);
428 this._searchRegex = regex;
429 this._searchResults = this._collectRegexMatches(regex);
430 if (!this._searchResults.length)
431 this._textEditor.cancelSearchResultsHighlight();
432 else if (shouldJump && jumpBackwards)
433 this.jumpToPreviousSearchResult();
435 this.jumpToNextSearchResult();
437 this._textEditor.highlightSearchResults(regex, null);
438 callback(this, this._searchResults.length);
442 this._currentSearchMatchChangedCallback = currentMatchChangedCallback;
443 this._searchResultsChangedCallback = searchResultsChangedCallback;
445 doFindSearchMatches.call(this, query);
447 this._delayedFindSearchMatches = doFindSearchMatches.bind(this, query);
449 this._ensureContentLoaded();
452 _editorFocused: function()
454 this._resetCurrentSearchResultIndex();
457 _resetCurrentSearchResultIndex: function()
459 if (!this._searchResults.length)
461 this._currentSearchResultIndex = -1;
462 if (this._currentSearchMatchChangedCallback)
463 this._currentSearchMatchChangedCallback(this._currentSearchResultIndex);
464 this._textEditor.highlightSearchResults(this._searchRegex, null);
467 _resetSearch: function()
469 delete this._delayedFindSearchMatches;
470 delete this._currentSearchMatchChangedCallback;
471 delete this._searchResultsChangedCallback;
472 this._currentSearchResultIndex = -1;
473 this._searchResults = [];
474 delete this._searchRegex;
477 searchCanceled: function()
479 var range = this._currentSearchResultIndex !== -1 ? this._searchResults[this._currentSearchResultIndex] : null;
483 this._textEditor.cancelSearchResultsHighlight();
485 this._textEditor.setSelection(range);
491 hasSearchResults: function()
493 return this._searchResults.length > 0;
496 jumpToFirstSearchResult: function()
498 this.jumpToSearchResult(0);
501 jumpToLastSearchResult: function()
503 this.jumpToSearchResult(this._searchResults.length - 1);
509 _searchResultIndexForCurrentSelection: function()
511 return insertionIndexForObjectInListSortedByFunction(this._textEditor.selection(), this._searchResults, WebInspector.TextRange.comparator);
514 jumpToNextSearchResult: function()
516 var currentIndex = this._searchResultIndexForCurrentSelection();
517 var nextIndex = this._currentSearchResultIndex === -1 ? currentIndex : currentIndex + 1;
518 this.jumpToSearchResult(nextIndex);
521 jumpToPreviousSearchResult: function()
523 var currentIndex = this._searchResultIndexForCurrentSelection();
524 this.jumpToSearchResult(currentIndex - 1);
530 showingFirstSearchResult: function()
532 return this._searchResults.length && this._currentSearchResultIndex === 0;
538 showingLastSearchResult: function()
540 return this._searchResults.length && this._currentSearchResultIndex === (this._searchResults.length - 1);
543 get currentSearchResultIndex()
545 return this._currentSearchResultIndex;
548 jumpToSearchResult: function(index)
550 if (!this.loaded || !this._searchResults.length)
552 this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length;
553 if (this._currentSearchMatchChangedCallback)
554 this._currentSearchMatchChangedCallback(this._currentSearchResultIndex);
555 this._textEditor.highlightSearchResults(this._searchRegex, this._searchResults[this._currentSearchResultIndex]);
559 * @param {string} text
561 replaceSelectionWith: function(text)
563 var range = this._searchResults[this._currentSearchResultIndex];
566 this._textEditor.highlightSearchResults(this._searchRegex, null);
568 this._isReplacing = true;
569 var newRange = this._textEditor.editRange(range, text);
570 delete this._isReplacing;
572 this._textEditor.setSelection(newRange.collapseToEnd());
576 * @param {string} query
577 * @param {string} replacement
579 replaceAllWith: function(query, replacement)
581 this._resetCurrentSearchResultIndex();
583 var text = this._textEditor.text();
584 var range = this._textEditor.range();
585 var regex = WebInspector.SourceFrame.createSearchRegex(query, "g");
586 if (regex.__fromRegExpQuery)
587 text = text.replace(regex, replacement);
589 text = text.replace(regex, function() { return replacement; });
591 var ranges = this._collectRegexMatches(regex);
595 // Calculate the position of the end of the last range to be edited.
596 var currentRangeIndex = insertionIndexForObjectInListSortedByFunction(this._textEditor.selection(), ranges, WebInspector.TextRange.comparator);
597 var lastRangeIndex = mod(currentRangeIndex - 1, ranges.length);
598 var lastRange = ranges[lastRangeIndex];
599 var replacementLineEndings = replacement.lineEndings();
600 var replacementLineCount = replacementLineEndings.length;
601 var lastLineNumber = lastRange.startLine + replacementLineEndings.length - 1;
602 var lastColumnNumber = lastRange.startColumn;
603 if (replacementLineEndings.length > 1)
604 lastColumnNumber = replacementLineEndings[replacementLineCount - 1] - replacementLineEndings[replacementLineCount - 2] - 1;
606 this._isReplacing = true;
607 this._textEditor.editRange(range, text);
608 this._textEditor.revealPosition(lastLineNumber, lastColumnNumber);
609 this._textEditor.setSelection(WebInspector.TextRange.createFromLocation(lastLineNumber, lastColumnNumber));
610 delete this._isReplacing;
613 _collectRegexMatches: function(regexObject)
616 for (var i = 0; i < this._textEditor.linesCount; ++i) {
617 var line = this._textEditor.line(i);
620 var match = regexObject.exec(line);
623 ranges.push(new WebInspector.TextRange(i, offset + match.index, i, offset + match.index + match[0].length));
624 offset += match.index + 1;
625 line = line.substring(match.index + 1);
627 } while (match && line);
632 _addExistingMessagesToSource: function()
634 var length = this._messages.length;
635 for (var i = 0; i < length; ++i)
636 this.addMessageToSource(this._messages[i].line - 1, this._messages[i]);
640 * @param {number} lineNumber
641 * @param {!WebInspector.ConsoleMessage} consoleMessage
643 addMessageToSource: function(lineNumber, consoleMessage)
645 if (lineNumber >= this._textEditor.linesCount)
646 lineNumber = this._textEditor.linesCount - 1;
650 if (!this._rowMessageBuckets[lineNumber])
651 this._rowMessageBuckets[lineNumber] = new WebInspector.SourceFrame.RowMessageBucket(this, this._textEditor, lineNumber);
652 var messageBucket = this._rowMessageBuckets[lineNumber];
653 messageBucket.addMessage(consoleMessage);
657 * @param {number} lineNumber
658 * @param {!WebInspector.ConsoleMessage} msg
660 removeMessageFromSource: function(lineNumber, msg)
662 if (lineNumber >= this._textEditor.linesCount)
663 lineNumber = this._textEditor.linesCount - 1;
667 var messageBucket = this._rowMessageBuckets[lineNumber];
670 messageBucket.removeMessage(msg);
671 if (!messageBucket.uniqueMessagesCount()) {
672 messageBucket.detachFromEditor();
673 delete this._rowMessageBuckets[lineNumber];
677 populateLineGutterContextMenu: function(contextMenu, lineNumber)
681 populateTextAreaContextMenu: function(contextMenu, lineNumber)
686 * @param {?WebInspector.TextRange} from
687 * @param {?WebInspector.TextRange} to
689 onJumpToPosition: function(from, to)
691 this.dispatchEventToListeners(WebInspector.SourceFrame.Events.JumpHappened, {
697 inheritScrollPositions: function(sourceFrame)
699 this._textEditor.inheritScrollPositions(sourceFrame._textEditor);
705 canEditSource: function()
711 * @param {!WebInspector.TextRange} textRange
713 selectionChanged: function(textRange)
715 this._updateSourcePosition();
716 this.dispatchEventToListeners(WebInspector.SourceFrame.Events.SelectionChanged, textRange);
717 WebInspector.notifications.dispatchEventToListeners(WebInspector.SourceFrame.Events.SelectionChanged, textRange);
720 _updateSourcePosition: function()
722 var selections = this._textEditor.selections();
723 if (!selections.length)
725 if (selections.length > 1) {
726 this._sourcePosition.setText(WebInspector.UIString("%d selection regions", selections.length));
729 var textRange = selections[0];
730 if (textRange.isEmpty()) {
731 this._sourcePosition.setText(WebInspector.UIString("Line %d, Column %d", textRange.endLine + 1, textRange.endColumn + 1));
734 textRange = textRange.normalize();
736 var selectedText = this._textEditor.copyRange(textRange);
737 if (textRange.startLine === textRange.endLine)
738 this._sourcePosition.setText(WebInspector.UIString("%d characters selected", selectedText.length));
740 this._sourcePosition.setText(WebInspector.UIString("%d lines, %d characters selected", textRange.endLine - textRange.startLine + 1, selectedText.length));
744 * @param {number} lineNumber
746 scrollChanged: function(lineNumber)
748 this.dispatchEventToListeners(WebInspector.SourceFrame.Events.ScrollChanged, lineNumber);
751 _handleKeyDown: function(e)
753 var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(e);
754 var handler = this._shortcuts[shortcutKey];
755 if (handler && handler())
759 __proto__: WebInspector.VBox.prototype
762 WebInspector.SourceFrame._iconClassPerLevel = {};
763 WebInspector.SourceFrame._iconClassPerLevel[WebInspector.ConsoleMessage.MessageLevel.Error] = "error-icon-small";
764 WebInspector.SourceFrame._iconClassPerLevel[WebInspector.ConsoleMessage.MessageLevel.Warning] = "warning-icon-small";
766 WebInspector.SourceFrame._lineClassPerLevel = {};
767 WebInspector.SourceFrame._lineClassPerLevel[WebInspector.ConsoleMessage.MessageLevel.Error] = "text-editor-line-with-error";
768 WebInspector.SourceFrame._lineClassPerLevel[WebInspector.ConsoleMessage.MessageLevel.Warning] = "text-editor-line-with-warning";
772 * @param {!WebInspector.ConsoleMessage} consoleMessage
774 WebInspector.SourceFrame.RowMessage = function(consoleMessage)
776 this._consoleMessage = consoleMessage;
777 this._repeatCount = 1;
778 this.element = document.createElementWithClass("div", "text-editor-row-message");
779 this._icon = this.element.createChild("span", "text-editor-row-message-icon");
780 this._icon.classList.add(WebInspector.SourceFrame._iconClassPerLevel[consoleMessage.level]);
781 this._repeatCountElement = this.element.createChild("span", "bubble-repeat-count hidden error");
782 var linesContainer = this.element.createChild("div", "text-editor-row-message-lines");
783 var lines = this._consoleMessage.messageText.split("\n");
784 for (var i = 0; i < lines.length; ++i) {
785 var messageLine = linesContainer.createChild("div");
786 messageLine.textContent = lines[i];
790 WebInspector.SourceFrame.RowMessage.prototype = {
792 * @return {!WebInspector.ConsoleMessage}
794 consoleMessage: function()
796 return this._consoleMessage;
802 repeatCount: function()
804 return this._repeatCount;
807 setRepeatCount: function(repeatCount)
809 if (this._repeatCount === repeatCount)
811 this._repeatCount = repeatCount;
812 this._updateMessageRepeatCount();
815 _updateMessageRepeatCount: function()
817 this._repeatCountElement.textContent = this._repeatCount;
818 var showRepeatCount = this._repeatCount > 1;
819 this._repeatCountElement.classList.toggle("hidden", !showRepeatCount);
820 this._icon.classList.toggle("hidden", showRepeatCount);
826 * @param {!WebInspector.SourceFrame} sourceFrame
827 * @param {!WebInspector.TextEditor} textEditor
828 * @param {number} lineNumber
830 WebInspector.SourceFrame.RowMessageBucket = function(sourceFrame, textEditor, lineNumber)
832 this._sourceFrame = sourceFrame;
833 this._textEditor = textEditor;
834 this._lineHandle = textEditor.textEditorPositionHandle(lineNumber, 0);
835 this._decoration = document.createElementWithClass("div", "text-editor-line-decoration");
836 this._decoration._messageBucket = this;
837 this._wave = this._decoration.createChild("div", "text-editor-line-decoration-wave");
838 this._icon = this._wave.createChild("div", "text-editor-line-decoration-icon");
840 this._textEditor.addDecoration(lineNumber, this._decoration);
842 this._messagesDescriptionElement = document.createElementWithClass("div", "text-editor-messages-description-container");
843 /** @type {!Array.<!WebInspector.SourceFrame.RowMessage>} */
846 this._updateDecorationPosition();
851 WebInspector.SourceFrame.RowMessageBucket.prototype = {
852 _updateDecorationPosition: function()
854 if (!this._sourceFrame._isEditorShowing())
856 var position = this._lineHandle.resolve();
859 var lineNumber = position.lineNumber;
860 var lineText = this._textEditor.line(lineNumber);
861 var lineIndent = WebInspector.TextUtils.lineIndent(lineText).length;
862 var base = this._textEditor.cursorPositionToCoordinates(lineNumber, 0);
863 var start = this._textEditor.cursorPositionToCoordinates(lineNumber, lineIndent);
864 var end = this._textEditor.cursorPositionToCoordinates(lineNumber, lineText.length);
866 var codeMirrorLinesLeftPadding = 4;
867 this._wave.style.left = (start.x - base.x + codeMirrorLinesLeftPadding) + "px";
868 this._wave.style.width = (end.x - start.x) + "px";
874 messagesDescription: function()
876 this._messagesDescriptionElement.removeChildren();
877 for (var i = 0; i < this._messages.length; ++i) {
878 this._messagesDescriptionElement.appendChild(this._messages[i].element);
880 return this._messagesDescriptionElement;
883 detachFromEditor: function()
885 var position = this._lineHandle.resolve();
888 var lineNumber = position.lineNumber;
890 this._textEditor.toggleLineClass(lineNumber, WebInspector.SourceFrame._lineClassPerLevel[this._level], false);
891 this._textEditor.removeDecoration(lineNumber, this._decoration);
897 uniqueMessagesCount: function()
899 return this._messages.length;
903 * @param {!WebInspector.ConsoleMessage} consoleMessage
905 addMessage: function(consoleMessage)
907 for (var i = 0; i < this._messages.length; ++i) {
908 var message = this._messages[i];
909 if (message.consoleMessage().isEqual(consoleMessage)) {
910 message.setRepeatCount(message.repeatCount() + 1);
915 var rowMessage = new WebInspector.SourceFrame.RowMessage(consoleMessage);
916 this._messages.push(rowMessage);
917 this._updateBucketLevel();
921 * @param {!WebInspector.ConsoleMessage} consoleMessage
923 removeMessage: function(consoleMessage)
925 for (var i = 0; i < this._messages.length; ++i) {
926 var rowMessage = this._messages[i];
927 if (!rowMessage.consoleMessage().isEqual(consoleMessage))
929 rowMessage.setRepeatCount(rowMessage.repeatCount() - 1);
930 if (!rowMessage.repeatCount())
931 this._messages.splice(i, 1);
932 this._updateBucketLevel();
937 _updateBucketLevel: function()
939 if (!this._messages.length)
941 var position = this._lineHandle.resolve();
945 var lineNumber = position.lineNumber;
946 var maxMessage = null;
947 for (var i = 0; i < this._messages.length; ++i) {
948 var message = this._messages[i].consoleMessage();;
949 if (!maxMessage || WebInspector.ConsoleMessage.messageLevelComparator(maxMessage, message) < 0)
950 maxMessage = message;
954 this._textEditor.toggleLineClass(lineNumber, WebInspector.SourceFrame._lineClassPerLevel[this._level], false);
955 this._icon.classList.toggle(WebInspector.SourceFrame._iconClassPerLevel[this._level], false);
957 this._level = maxMessage.level;
960 this._textEditor.toggleLineClass(lineNumber, WebInspector.SourceFrame._lineClassPerLevel[this._level], true);
961 this._icon.classList.toggle(WebInspector.SourceFrame._iconClassPerLevel[this._level], true);
966 * @implements {WebInspector.TextEditorDelegate}
969 WebInspector.TextEditorDelegateForSourceFrame = function(sourceFrame)
971 this._sourceFrame = sourceFrame;
974 WebInspector.TextEditorDelegateForSourceFrame.prototype = {
975 onTextChanged: function(oldRange, newRange)
977 this._sourceFrame.onTextChanged(oldRange, newRange);
981 * @param {!WebInspector.TextRange} textRange
983 selectionChanged: function(textRange)
985 this._sourceFrame.selectionChanged(textRange);
989 * @param {number} lineNumber
991 scrollChanged: function(lineNumber)
993 this._sourceFrame.scrollChanged(lineNumber);
996 editorFocused: function()
998 this._sourceFrame._editorFocused();
1001 populateLineGutterContextMenu: function(contextMenu, lineNumber)
1003 this._sourceFrame.populateLineGutterContextMenu(contextMenu, lineNumber);
1006 populateTextAreaContextMenu: function(contextMenu, lineNumber)
1008 this._sourceFrame.populateTextAreaContextMenu(contextMenu, lineNumber);
1012 * @param {string} hrefValue
1013 * @param {boolean} isExternal
1014 * @return {!Element}
1016 createLink: function(hrefValue, isExternal)
1018 var targetLocation = WebInspector.ParsedURL.completeURL(this._sourceFrame._url, hrefValue);
1019 return WebInspector.linkifyURLAsNode(targetLocation || hrefValue, hrefValue, undefined, isExternal);
1023 * @param {?WebInspector.TextRange} from
1024 * @param {?WebInspector.TextRange} to
1026 onJumpToPosition: function(from, to)
1028 this._sourceFrame.onJumpToPosition(from, to);