Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / source_frame / SourceFrame.js
blob41f0c36a1617b36ac4c40ebb088ad0a310b3b9f5
1 /*
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
6 * met:
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.
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.
31 /**
32 * @extends {WebInspector.VBox}
33 * @constructor
34 * @implements {WebInspector.Replaceable}
35 * @param {!WebInspector.ContentProvider} contentProvider
37 WebInspector.SourceFrame = function(contentProvider)
39 WebInspector.VBox.call(this);
41 this._url = contentProvider.contentURL();
42 this._contentProvider = contentProvider;
44 var textEditorDelegate = new WebInspector.TextEditorDelegateForSourceFrame(this);
46 this._textEditor = new WebInspector.CodeMirrorTextEditor(this._url, textEditorDelegate);
48 this._currentSearchResultIndex = -1;
49 this._searchResults = [];
51 this._rowMessageBuckets = {};
53 this._textEditor.setReadOnly(!this.canEditSource());
55 this._shortcuts = {};
56 this.element.addEventListener("keydown", this._handleKeyDown.bind(this), false);
58 this._sourcePosition = new WebInspector.ToolbarText("", "source-frame-cursor-position");
60 this._errorPopoverHelper = new WebInspector.PopoverHelper(this.element, this._getErrorAnchor.bind(this), this._showErrorPopover.bind(this));
61 this._errorPopoverHelper.setTimeout(100, 100);
64 WebInspector.SourceFrame.Events = {
65 ScrollChanged: "ScrollChanged",
66 SelectionChanged: "SelectionChanged",
67 JumpHappened: "JumpHappened"
70 WebInspector.SourceFrame.prototype = {
71 /**
72 * @param {!Element} target
73 * @param {!Event} event
74 * @return {(!Element|undefined)}
76 _getErrorAnchor: function(target, event)
78 var element = target.enclosingNodeOrSelfWithClass("text-editor-line-decoration-icon")
79 || target.enclosingNodeOrSelfWithClass("text-editor-line-decoration-wave");
80 if (!element)
81 return;
82 this._errorWavePopoverAnchor = new AnchorBox(event.clientX, event.clientY, 1, 1);
83 return element;
86 /**
87 * @param {!Element} anchor
88 * @param {!WebInspector.Popover} popover
90 _showErrorPopover: function(anchor, popover)
92 var messageBucket = anchor.enclosingNodeOrSelfWithClass("text-editor-line-decoration")._messageBucket;
93 var messagesOutline = messageBucket.messagesDescription();
94 var popoverAnchor = anchor.enclosingNodeOrSelfWithClass("text-editor-line-decoration-icon") ? anchor : this._errorWavePopoverAnchor;
95 popover.showForAnchor(messagesOutline, popoverAnchor);
98 /**
99 * @param {number} key
100 * @param {function():boolean} handler
102 addShortcut: function(key, handler)
104 this._shortcuts[key] = handler;
107 wasShown: function()
109 this._ensureContentLoaded();
110 this._textEditor.show(this.element);
111 this._editorAttached = true;
112 // We need CodeMirrorTextEditor to be initialized prior to this call as it calls |cursorPositionToCoordinates| internally. @see crbug.com/506566
113 setImmediate(this._updateBucketDecorations.bind(this));
114 this._wasShownOrLoaded();
117 _updateBucketDecorations: function()
119 for (var line in this._rowMessageBuckets) {
120 var bucket = this._rowMessageBuckets[line];
121 bucket._updateDecoration();
126 * @return {boolean}
128 _isEditorShowing: function()
130 return this.isShowing() && this._editorAttached;
133 willHide: function()
135 WebInspector.Widget.prototype.willHide.call(this);
137 this._clearPositionToReveal();
141 * @return {!WebInspector.ToolbarText}
143 toolbarText: function()
145 return this._sourcePosition;
149 * @override
150 * @return {!Element}
152 defaultFocusedElement: function()
154 return this._textEditor.defaultFocusedElement();
157 get loaded()
159 return this._loaded;
162 get textEditor()
164 return this._textEditor;
167 _ensureContentLoaded: function()
169 if (!this._contentRequested) {
170 this._contentRequested = true;
171 this._contentProvider.requestContent(this.setContent.bind(this));
175 clearMessages: function()
177 for (var line in this._rowMessageBuckets) {
178 var bubble = this._rowMessageBuckets[line];
179 bubble.detachFromEditor();
182 this._rowMessageBuckets = {};
183 this._errorPopoverHelper.hidePopover();
187 * @param {number} line 0-based
188 * @param {number=} column
189 * @param {boolean=} shouldHighlight
191 revealPosition: function(line, column, shouldHighlight)
193 this._clearLineToScrollTo();
194 this._clearSelectionToSet();
195 this._positionToReveal = { line: line, column: column, shouldHighlight: shouldHighlight };
196 this._innerRevealPositionIfNeeded();
199 _innerRevealPositionIfNeeded: function()
201 if (!this._positionToReveal)
202 return;
204 if (!this.loaded || !this._isEditorShowing())
205 return;
207 this._textEditor.revealPosition(this._positionToReveal.line, this._positionToReveal.column, this._positionToReveal.shouldHighlight);
208 delete this._positionToReveal;
211 _clearPositionToReveal: function()
213 this._textEditor.clearPositionHighlight();
214 delete this._positionToReveal;
218 * @param {number} line
220 scrollToLine: function(line)
222 this._clearPositionToReveal();
223 this._lineToScrollTo = line;
224 this._innerScrollToLineIfNeeded();
227 _innerScrollToLineIfNeeded: function()
229 if (typeof this._lineToScrollTo === "number") {
230 if (this.loaded && this._isEditorShowing()) {
231 this._textEditor.scrollToLine(this._lineToScrollTo);
232 delete this._lineToScrollTo;
237 _clearLineToScrollTo: function()
239 delete this._lineToScrollTo;
243 * @return {!WebInspector.TextRange}
245 selection: function()
247 return this.textEditor.selection();
251 * @param {!WebInspector.TextRange} textRange
253 setSelection: function(textRange)
255 this._selectionToSet = textRange;
256 this._innerSetSelectionIfNeeded();
259 _innerSetSelectionIfNeeded: function()
261 if (this._selectionToSet && this.loaded && this._isEditorShowing()) {
262 this._textEditor.setSelection(this._selectionToSet);
263 delete this._selectionToSet;
267 _clearSelectionToSet: function()
269 delete this._selectionToSet;
272 _wasShownOrLoaded: function()
274 this._innerRevealPositionIfNeeded();
275 this._innerSetSelectionIfNeeded();
276 this._innerScrollToLineIfNeeded();
279 onTextChanged: function(oldRange, newRange)
281 if (this._searchResultsChangedCallback)
282 this._searchResultsChangedCallback();
283 this.clearMessages();
287 * @param {string} content
288 * @param {string} mimeType
289 * @return {string}
291 _simplifyMimeType: function(content, mimeType)
293 if (!mimeType)
294 return "";
295 if (mimeType.indexOf("javascript") >= 0 ||
296 mimeType.indexOf("jscript") >= 0 ||
297 mimeType.indexOf("ecmascript") >= 0)
298 return "text/javascript";
299 // A hack around the fact that files with "php" extension might be either standalone or html embedded php scripts.
300 if (mimeType === "text/x-php" && content.match(/\<\?.*\?\>/g))
301 return "application/x-httpd-php";
302 return mimeType;
306 * @param {string} highlighterType
308 setHighlighterType: function(highlighterType)
310 this._highlighterType = highlighterType;
311 this._updateHighlighterType("");
315 * @param {string} content
317 _updateHighlighterType: function(content)
319 this._textEditor.setMimeType(this._simplifyMimeType(content, this._highlighterType));
323 * @param {?string} content
325 setContent: function(content)
327 if (!this._loaded) {
328 this._loaded = true;
329 this._textEditor.setText(content || "");
330 this._textEditor.markClean();
331 } else {
332 var firstLine = this._textEditor.firstVisibleLine();
333 var selection = this._textEditor.selection();
334 this._textEditor.setText(content || "");
335 this._textEditor.scrollToLine(firstLine);
336 this._textEditor.setSelection(selection);
339 this._updateHighlighterType(content || "");
340 this.clearMessages();
341 this._wasShownOrLoaded();
343 if (this._delayedFindSearchMatches) {
344 this._delayedFindSearchMatches();
345 delete this._delayedFindSearchMatches;
347 this.onTextEditorContentLoaded();
350 onTextEditorContentLoaded: function() {},
353 * @param {!WebInspector.SearchableView.SearchConfig} searchConfig
354 * @param {boolean} shouldJump
355 * @param {boolean} jumpBackwards
356 * @param {function(!WebInspector.Widget, number)} searchFinishedCallback
358 _doFindSearchMatches: function(searchConfig, shouldJump, jumpBackwards, searchFinishedCallback)
360 this._currentSearchResultIndex = -1;
361 this._searchResults = [];
363 var regex = searchConfig.toSearchRegex();
364 this._searchRegex = regex;
365 this._searchResults = this._collectRegexMatches(regex);
366 searchFinishedCallback(this, this._searchResults.length);
367 if (!this._searchResults.length)
368 this._textEditor.cancelSearchResultsHighlight();
369 else if (shouldJump && jumpBackwards)
370 this.jumpToPreviousSearchResult();
371 else if (shouldJump)
372 this.jumpToNextSearchResult();
373 else
374 this._textEditor.highlightSearchResults(regex, null);
378 * @param {!WebInspector.SearchableView.SearchConfig} searchConfig
379 * @param {boolean} shouldJump
380 * @param {boolean} jumpBackwards
381 * @param {function(!WebInspector.Widget, number)} searchFinishedCallback
382 * @param {function(number)} currentMatchChangedCallback
383 * @param {function()} searchResultsChangedCallback
385 performSearch: function(searchConfig, shouldJump, jumpBackwards, searchFinishedCallback, currentMatchChangedCallback, searchResultsChangedCallback)
387 this._resetSearch();
388 this._currentSearchMatchChangedCallback = currentMatchChangedCallback;
389 this._searchResultsChangedCallback = searchResultsChangedCallback;
390 var searchFunction = this._doFindSearchMatches.bind(this, searchConfig, shouldJump, jumpBackwards, searchFinishedCallback);
391 if (this.loaded)
392 searchFunction.call(this);
393 else
394 this._delayedFindSearchMatches = searchFunction;
396 this._ensureContentLoaded();
399 _editorFocused: function()
401 this._resetCurrentSearchResultIndex();
404 _resetCurrentSearchResultIndex: function()
406 if (!this._searchResults.length)
407 return;
408 this._currentSearchResultIndex = -1;
409 if (this._currentSearchMatchChangedCallback)
410 this._currentSearchMatchChangedCallback(this._currentSearchResultIndex);
411 this._textEditor.highlightSearchResults(this._searchRegex, null);
414 _resetSearch: function()
416 delete this._delayedFindSearchMatches;
417 delete this._currentSearchMatchChangedCallback;
418 delete this._searchResultsChangedCallback;
419 this._currentSearchResultIndex = -1;
420 this._searchResults = [];
421 delete this._searchRegex;
424 searchCanceled: function()
426 var range = this._currentSearchResultIndex !== -1 ? this._searchResults[this._currentSearchResultIndex] : null;
427 this._resetSearch();
428 if (!this.loaded)
429 return;
430 this._textEditor.cancelSearchResultsHighlight();
431 if (range)
432 this.setSelection(range);
436 * @return {boolean}
438 hasSearchResults: function()
440 return this._searchResults.length > 0;
443 jumpToFirstSearchResult: function()
445 this.jumpToSearchResult(0);
448 jumpToLastSearchResult: function()
450 this.jumpToSearchResult(this._searchResults.length - 1);
454 * @return {number}
456 _searchResultIndexForCurrentSelection: function()
458 return insertionIndexForObjectInListSortedByFunction(this._textEditor.selection().collapseToEnd(), this._searchResults, WebInspector.TextRange.comparator);
461 jumpToNextSearchResult: function()
463 var currentIndex = this._searchResultIndexForCurrentSelection();
464 var nextIndex = this._currentSearchResultIndex === -1 ? currentIndex : currentIndex + 1;
465 this.jumpToSearchResult(nextIndex);
468 jumpToPreviousSearchResult: function()
470 var currentIndex = this._searchResultIndexForCurrentSelection();
471 this.jumpToSearchResult(currentIndex - 1);
474 get currentSearchResultIndex()
476 return this._currentSearchResultIndex;
479 jumpToSearchResult: function(index)
481 if (!this.loaded || !this._searchResults.length)
482 return;
483 this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length;
484 if (this._currentSearchMatchChangedCallback)
485 this._currentSearchMatchChangedCallback(this._currentSearchResultIndex);
486 this._textEditor.highlightSearchResults(this._searchRegex, this._searchResults[this._currentSearchResultIndex]);
490 * @override
491 * @param {!WebInspector.SearchableView.SearchConfig} searchConfig
492 * @param {string} replacement
494 replaceSelectionWith: function(searchConfig, replacement)
496 var range = this._searchResults[this._currentSearchResultIndex];
497 if (!range)
498 return;
499 this._textEditor.highlightSearchResults(this._searchRegex, null);
501 var oldText = this._textEditor.copyRange(range);
502 var regex = searchConfig.toSearchRegex();
503 var text;
504 if (regex.__fromRegExpQuery)
505 text = oldText.replace(regex, replacement);
506 else
507 text = oldText.replace(regex, function() { return replacement; });
509 var newRange = this._textEditor.editRange(range, text);
510 this._textEditor.setSelection(newRange.collapseToEnd());
514 * @override
515 * @param {!WebInspector.SearchableView.SearchConfig} searchConfig
516 * @param {string} replacement
518 replaceAllWith: function(searchConfig, replacement)
520 this._resetCurrentSearchResultIndex();
522 var text = this._textEditor.text();
523 var range = this._textEditor.range();
525 var regex = searchConfig.toSearchRegex(true);
526 if (regex.__fromRegExpQuery)
527 text = text.replace(regex, replacement);
528 else
529 text = text.replace(regex, function() { return replacement; });
531 var ranges = this._collectRegexMatches(regex);
532 if (!ranges.length)
533 return;
535 // Calculate the position of the end of the last range to be edited.
536 var currentRangeIndex = insertionIndexForObjectInListSortedByFunction(this._textEditor.selection(), ranges, WebInspector.TextRange.comparator);
537 var lastRangeIndex = mod(currentRangeIndex - 1, ranges.length);
538 var lastRange = ranges[lastRangeIndex];
539 var replacementLineEndings = replacement.lineEndings();
540 var replacementLineCount = replacementLineEndings.length;
541 var lastLineNumber = lastRange.startLine + replacementLineEndings.length - 1;
542 var lastColumnNumber = lastRange.startColumn;
543 if (replacementLineEndings.length > 1)
544 lastColumnNumber = replacementLineEndings[replacementLineCount - 1] - replacementLineEndings[replacementLineCount - 2] - 1;
546 this._textEditor.editRange(range, text);
547 this._textEditor.revealPosition(lastLineNumber, lastColumnNumber);
548 this._textEditor.setSelection(WebInspector.TextRange.createFromLocation(lastLineNumber, lastColumnNumber));
551 _collectRegexMatches: function(regexObject)
553 var ranges = [];
554 for (var i = 0; i < this._textEditor.linesCount; ++i) {
555 var line = this._textEditor.line(i);
556 var offset = 0;
557 do {
558 var match = regexObject.exec(line);
559 if (match) {
560 var matchEndIndex = match.index + Math.max(match[0].length, 1);
561 if (match[0].length)
562 ranges.push(new WebInspector.TextRange(i, offset + match.index, i, offset + matchEndIndex));
563 offset += matchEndIndex;
564 line = line.substring(matchEndIndex);
566 } while (match && line);
568 return ranges;
572 * @param {!WebInspector.SourceFrameMessage} message
574 addMessageToSource: function(message)
576 var lineNumber = message.lineNumber();
577 if (lineNumber >= this._textEditor.linesCount)
578 lineNumber = this._textEditor.linesCount - 1;
579 if (lineNumber < 0)
580 lineNumber = 0;
582 if (!this._rowMessageBuckets[lineNumber])
583 this._rowMessageBuckets[lineNumber] = new WebInspector.SourceFrame.RowMessageBucket(this, this._textEditor, lineNumber);
584 var messageBucket = this._rowMessageBuckets[lineNumber];
585 messageBucket.addMessage(message);
589 * @param {!WebInspector.SourceFrameMessage} message
591 removeMessageFromSource: function(message)
593 var lineNumber = message.lineNumber();
594 if (lineNumber >= this._textEditor.linesCount)
595 lineNumber = this._textEditor.linesCount - 1;
596 if (lineNumber < 0)
597 lineNumber = 0;
599 var messageBucket = this._rowMessageBuckets[lineNumber];
600 if (!messageBucket)
601 return;
602 messageBucket.removeMessage(message);
603 if (!messageBucket.uniqueMessagesCount()) {
604 messageBucket.detachFromEditor();
605 delete this._rowMessageBuckets[lineNumber];
609 populateLineGutterContextMenu: function(contextMenu, lineNumber)
613 populateTextAreaContextMenu: function(contextMenu, lineNumber, columnNumber)
618 * @param {?WebInspector.TextRange} from
619 * @param {?WebInspector.TextRange} to
621 onJumpToPosition: function(from, to)
623 this.dispatchEventToListeners(WebInspector.SourceFrame.Events.JumpHappened, {
624 from: from,
625 to: to
630 * @return {boolean}
632 canEditSource: function()
634 return false;
638 * @param {!WebInspector.TextRange} textRange
640 selectionChanged: function(textRange)
642 this._updateSourcePosition();
643 this.dispatchEventToListeners(WebInspector.SourceFrame.Events.SelectionChanged, textRange);
644 WebInspector.notifications.dispatchEventToListeners(WebInspector.SourceFrame.Events.SelectionChanged, textRange);
647 _updateSourcePosition: function()
649 var selections = this._textEditor.selections();
650 if (!selections.length)
651 return;
652 if (selections.length > 1) {
653 this._sourcePosition.setText(WebInspector.UIString("%d selection regions", selections.length));
654 return;
656 var textRange = selections[0];
657 if (textRange.isEmpty()) {
658 this._sourcePosition.setText(WebInspector.UIString("Line %d, Column %d", textRange.endLine + 1, textRange.endColumn + 1));
659 return;
661 textRange = textRange.normalize();
663 var selectedText = this._textEditor.copyRange(textRange);
664 if (textRange.startLine === textRange.endLine)
665 this._sourcePosition.setText(WebInspector.UIString("%d characters selected", selectedText.length));
666 else
667 this._sourcePosition.setText(WebInspector.UIString("%d lines, %d characters selected", textRange.endLine - textRange.startLine + 1, selectedText.length));
671 * @param {number} lineNumber
673 scrollChanged: function(lineNumber)
675 this.dispatchEventToListeners(WebInspector.SourceFrame.Events.ScrollChanged, lineNumber);
678 _handleKeyDown: function(e)
680 var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(e);
681 var handler = this._shortcuts[shortcutKey];
682 if (handler && handler())
683 e.consume(true);
686 __proto__: WebInspector.VBox.prototype
690 * @constructor
691 * @param {string} messageText
692 * @param {!WebInspector.SourceFrameMessage.Level} level
693 * @param {number} lineNumber
694 * @param {number=} columnNumber
696 WebInspector.SourceFrameMessage = function(messageText, level, lineNumber, columnNumber)
698 this._messageText = messageText;
699 this._level = level;
700 this._lineNumber = lineNumber;
701 this._columnNumber = columnNumber;
705 * @enum {string}
707 WebInspector.SourceFrameMessage.Level = {
708 Error: "Error",
709 Warning: "Warning"
713 * @param {!WebInspector.ConsoleMessage} consoleMessage
714 * @param {number} lineNumber
715 * @param {number} columnNumber
716 * @return {!WebInspector.SourceFrameMessage}
718 WebInspector.SourceFrameMessage.fromConsoleMessage = function(consoleMessage, lineNumber, columnNumber)
720 console.assert(consoleMessage.level === WebInspector.ConsoleMessage.MessageLevel.Error || consoleMessage.level === WebInspector.ConsoleMessage.MessageLevel.Warning);
721 var level = consoleMessage.level === WebInspector.ConsoleMessage.MessageLevel.Error ? WebInspector.SourceFrameMessage.Level.Error : WebInspector.SourceFrameMessage.Level.Warning;
722 return new WebInspector.SourceFrameMessage(consoleMessage.messageText, level, lineNumber, columnNumber);
725 WebInspector.SourceFrameMessage.prototype = {
727 * @return {string}
729 messageText: function()
731 return this._messageText;
735 * @return {!WebInspector.SourceFrameMessage.Level}
737 level: function()
739 return this._level;
743 * @return {number}
745 lineNumber: function()
747 return this._lineNumber;
751 * @return {(number|undefined)}
753 columnNumber: function()
755 return this._columnNumber;
759 * @param {!WebInspector.SourceFrameMessage} another
760 * @return {boolean}
762 isEqual: function(another)
764 return this.messageText() === another.messageText() && this.level() === another.level() && this.lineNumber() === another.lineNumber() && this.columnNumber() === another.columnNumber();
768 WebInspector.SourceFrame._iconClassPerLevel = {};
769 WebInspector.SourceFrame._iconClassPerLevel[WebInspector.SourceFrameMessage.Level.Error] = "error-icon";
770 WebInspector.SourceFrame._iconClassPerLevel[WebInspector.SourceFrameMessage.Level.Warning] = "warning-icon";
772 WebInspector.SourceFrame._lineClassPerLevel = {};
773 WebInspector.SourceFrame._lineClassPerLevel[WebInspector.SourceFrameMessage.Level.Error] = "text-editor-line-with-error";
774 WebInspector.SourceFrame._lineClassPerLevel[WebInspector.SourceFrameMessage.Level.Warning] = "text-editor-line-with-warning";
777 * @constructor
778 * @param {!WebInspector.SourceFrameMessage} message
780 WebInspector.SourceFrame.RowMessage = function(message)
782 this._message = message;
783 this._repeatCount = 1;
784 this.element = createElementWithClass("div", "text-editor-row-message");
785 this._icon = this.element.createChild("label", "", "dt-icon-label");
786 this._icon.type = WebInspector.SourceFrame._iconClassPerLevel[message.level()];
787 this._repeatCountElement = this.element.createChild("span", "bubble-repeat-count hidden error");
788 var linesContainer = this.element.createChild("div", "text-editor-row-message-lines");
789 var lines = this._message.messageText().split("\n");
790 for (var i = 0; i < lines.length; ++i) {
791 var messageLine = linesContainer.createChild("div");
792 messageLine.textContent = lines[i];
796 WebInspector.SourceFrame.RowMessage.prototype = {
798 * @return {!WebInspector.SourceFrameMessage}
800 message: function()
802 return this._message;
806 * @return {number}
808 repeatCount: function()
810 return this._repeatCount;
813 setRepeatCount: function(repeatCount)
815 if (this._repeatCount === repeatCount)
816 return;
817 this._repeatCount = repeatCount;
818 this._updateMessageRepeatCount();
821 _updateMessageRepeatCount: function()
823 this._repeatCountElement.textContent = this._repeatCount;
824 var showRepeatCount = this._repeatCount > 1;
825 this._repeatCountElement.classList.toggle("hidden", !showRepeatCount);
826 this._icon.classList.toggle("hidden", showRepeatCount);
831 * @constructor
832 * @param {!WebInspector.SourceFrame} sourceFrame
833 * @param {!WebInspector.CodeMirrorTextEditor} textEditor
834 * @param {number} lineNumber
836 WebInspector.SourceFrame.RowMessageBucket = function(sourceFrame, textEditor, lineNumber)
838 this._sourceFrame = sourceFrame;
839 this._textEditor = textEditor;
840 this._lineHandle = textEditor.textEditorPositionHandle(lineNumber, 0);
841 this._decoration = createElementWithClass("div", "text-editor-line-decoration");
842 this._decoration._messageBucket = this;
843 this._wave = this._decoration.createChild("div", "text-editor-line-decoration-wave");
844 this._icon = this._wave.createChild("label", "text-editor-line-decoration-icon", "dt-icon-label");
846 this._textEditor.addDecoration(lineNumber, this._decoration);
848 this._messagesDescriptionElement = createElementWithClass("div", "text-editor-messages-description-container");
849 /** @type {!Array.<!WebInspector.SourceFrame.RowMessage>} */
850 this._messages = [];
852 this._level = null;
855 WebInspector.SourceFrame.RowMessageBucket.prototype = {
857 * @param {number} lineNumber
858 * @param {number} columnNumber
860 _updateWavePosition: function(lineNumber, columnNumber)
862 lineNumber = Math.min(lineNumber, this._textEditor.linesCount - 1);
863 var lineText = this._textEditor.line(lineNumber);
864 columnNumber = Math.min(columnNumber, lineText.length);
865 var lineIndent = WebInspector.TextUtils.lineIndent(lineText).length;
866 var base = this._textEditor.cursorPositionToCoordinates(lineNumber, 0);
868 var start = this._textEditor.cursorPositionToCoordinates(lineNumber, Math.max(columnNumber - 1, lineIndent));
869 var end = this._textEditor.cursorPositionToCoordinates(lineNumber, lineText.length);
870 /** @const */
871 var codeMirrorLinesLeftPadding = 4;
872 this._wave.style.left = (start.x - base.x + codeMirrorLinesLeftPadding) + "px";
873 this._wave.style.width = (end.x - start.x) + "px";
877 * @return {!Element}
879 messagesDescription: function()
881 this._messagesDescriptionElement.removeChildren();
882 for (var i = 0; i < this._messages.length; ++i) {
883 this._messagesDescriptionElement.appendChild(this._messages[i].element);
885 return this._messagesDescriptionElement;
888 detachFromEditor: function()
890 var position = this._lineHandle.resolve();
891 if (!position)
892 return;
893 var lineNumber = position.lineNumber;
894 if (this._level)
895 this._textEditor.toggleLineClass(lineNumber, WebInspector.SourceFrame._lineClassPerLevel[this._level], false);
896 this._textEditor.removeDecoration(lineNumber, this._decoration);
900 * @return {number}
902 uniqueMessagesCount: function()
904 return this._messages.length;
908 * @param {!WebInspector.SourceFrameMessage} message
910 addMessage: function(message)
912 for (var i = 0; i < this._messages.length; ++i) {
913 var rowMessage = this._messages[i];
914 if (rowMessage.message().isEqual(message)) {
915 rowMessage.setRepeatCount(rowMessage.repeatCount() + 1);
916 return;
920 var rowMessage = new WebInspector.SourceFrame.RowMessage(message);
921 this._messages.push(rowMessage);
922 this._updateDecoration();
926 * @param {!WebInspector.SourceFrameMessage} message
928 removeMessage: function(message)
930 for (var i = 0; i < this._messages.length; ++i) {
931 var rowMessage = this._messages[i];
932 if (!rowMessage.message().isEqual(message))
933 continue;
934 rowMessage.setRepeatCount(rowMessage.repeatCount() - 1);
935 if (!rowMessage.repeatCount())
936 this._messages.splice(i, 1);
937 this._updateDecoration();
938 return;
942 _updateDecoration: function()
944 if (!this._sourceFrame._isEditorShowing())
945 return;
946 if (!this._messages.length)
947 return;
948 var position = this._lineHandle.resolve();
949 if (!position)
950 return;
952 var lineNumber = position.lineNumber;
953 var columnNumber = Number.MAX_VALUE;
954 var maxMessage = null;
955 for (var i = 0; i < this._messages.length; ++i) {
956 var message = this._messages[i].message();
957 columnNumber = Math.min(columnNumber, message.columnNumber());
958 if (!maxMessage || WebInspector.SourceFrameMessage.messageLevelComparator(maxMessage, message) < 0)
959 maxMessage = message;
961 this._updateWavePosition(lineNumber, columnNumber);
963 if (this._level) {
964 this._textEditor.toggleLineClass(lineNumber, WebInspector.SourceFrame._lineClassPerLevel[this._level], false);
965 this._icon.type = "";
967 this._level = maxMessage.level();
968 if (!this._level)
969 return;
970 this._textEditor.toggleLineClass(lineNumber, WebInspector.SourceFrame._lineClassPerLevel[this._level], true);
971 this._icon.type = WebInspector.SourceFrame._iconClassPerLevel[this._level];
976 * @implements {WebInspector.TextEditorDelegate}
977 * @constructor
979 WebInspector.TextEditorDelegateForSourceFrame = function(sourceFrame)
981 this._sourceFrame = sourceFrame;
984 WebInspector.TextEditorDelegateForSourceFrame.prototype = {
986 * @override
988 onTextChanged: function(oldRange, newRange)
990 this._sourceFrame.onTextChanged(oldRange, newRange);
994 * @override
995 * @param {!WebInspector.TextRange} textRange
997 selectionChanged: function(textRange)
999 this._sourceFrame.selectionChanged(textRange);
1003 * @override
1004 * @param {number} lineNumber
1006 scrollChanged: function(lineNumber)
1008 this._sourceFrame.scrollChanged(lineNumber);
1012 * @override
1014 editorFocused: function()
1016 this._sourceFrame._editorFocused();
1020 * @override
1022 populateLineGutterContextMenu: function(contextMenu, lineNumber)
1024 this._sourceFrame.populateLineGutterContextMenu(contextMenu, lineNumber);
1028 * @override
1030 populateTextAreaContextMenu: function(contextMenu, lineNumber, columnNumber)
1032 this._sourceFrame.populateTextAreaContextMenu(contextMenu, lineNumber, columnNumber);
1036 * @override
1037 * @param {?WebInspector.TextRange} from
1038 * @param {?WebInspector.TextRange} to
1040 onJumpToPosition: function(from, to)
1042 this._sourceFrame.onJumpToPosition(from, to);
1046 WebInspector.SourceFrameMessage._messageLevelPriority = {
1047 "Warning": 3,
1048 "Error": 4
1052 * @param {!WebInspector.SourceFrameMessage} a
1053 * @param {!WebInspector.SourceFrameMessage} b
1054 * @return {number}
1056 WebInspector.SourceFrameMessage.messageLevelComparator = function(a, b)
1058 return WebInspector.SourceFrameMessage._messageLevelPriority[a.level()] - WebInspector.SourceFrameMessage._messageLevelPriority[b.level()];