Web Inspector: Inline Error / Warning message for Issues
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 10 Mar 2015 03:01:01 +0000 (03:01 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 10 Mar 2015 03:01:01 +0000 (03:01 +0000)
https://bugs.webkit.org/show_bug.cgi?id=142520

Reviewed by Timothy Hatcher.

* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Main.html:
New strings and new files.

* UserInterface/Controllers/IssueManager.js:
(WebInspector.IssueManager.prototype.issueWasAdded):
* UserInterface/Models/IssueMessage.js:
(WebInspector.IssueMessage):
(WebInspector.IssueMessage.prototype.get columnNumber):
* UserInterface/Protocol/ConsoleObserver.js:
(WebInspector.ConsoleObserver.prototype.messageAdded):
Correctly pass the column number into IssueMessage.

* UserInterface/Models/LineWidget.js:
(WebInspector.LineWidget):
(WebInspector.LineWidget.prototype.get codeMirrorLineWidget):
(WebInspector.LineWidget.prototype.get widgetElement):
Create a new Model class for a LineWidget. The root element
should never change, but its children can be updated.

* UserInterface/Views/SourceCodeTextEditor.css:
(.source-code.text-editor .CodeMirror-linewidget):
Override styles so that widgets can overlap line content.

(.source-code.text-editor > .CodeMirror .issue-widget):
(.source-code.text-editor > .CodeMirror .issue-widget.inline):
Float issue widgets to the right side of the editor.

(.source-code.text-editor > .CodeMirror .issue-widget > .arrow):
(.source-code.text-editor > .CodeMirror .issue-widget.inline > .arrow):
Pure CSS arrow for widgets on the same line as their issue.

(.source-code.text-editor > .CodeMirror .issue-widget > .icon):
(.source-code.text-editor > .CodeMirror .issue-widget > .icon.icon-warning):
(.source-code.text-editor > .CodeMirror .issue-widget > .icon.icon-error):
Issue icon styles for the different issue types.

(.source-code.text-editor > .CodeMirror .issue-widget.warning):
(.source-code.text-editor > .CodeMirror .issue-widget.inline.warning):
(.source-code.text-editor > .CodeMirror .issue-widget.inline.warning > .arrow):
(.source-code.text-editor > .CodeMirror .issue-widget.error):
(.source-code.text-editor > .CodeMirror .issue-widget.inline.error):
(.source-code.text-editor > .CodeMirror .issue-widget.inline.error > .arrow):
Different colors for the different issue types.

(.source-code.text-editor > .CodeMirror .issue-widget > .text):
(.source-code.text-editor > .CodeMirror .issue-widget.inline > .text):
Text positioning when on the same line or when expanded.

* UserInterface/Views/SourceCodeTextEditor.js:
(WebInspector.SourceCodeTextEditor.prototype._addIssue):
(WebInspector.SourceCodeTextEditor.prototype._iconClassNameForIssueLevel):
(WebInspector.SourceCodeTextEditor.prototype._updateIssueWidgetForIssues):
(WebInspector.SourceCodeTextEditor.prototype._isWidgetToggleable):
(WebInspector.SourceCodeTextEditor.prototype._handleWidgetClick):
Create widgets for issues.

(WebInspector.SourceCodeTextEditor.prototype._contentDidPopulate):
(WebInspector.SourceCodeTextEditor.prototype.textEditorUpdatedFormatting):
(WebInspector.SourceCodeTextEditor.prototype._clearWidgets):
(WebInspector.SourceCodeTextEditor.prototype._reinsertAllIssues):
Update all widgets in certain cases.

* UserInterface/Views/TextEditor.js:
(WebInspector.TextEditor.prototype.addStyleClassToLine):
This assertion is known to happen for issues added to an editor
before the content has loaded.

(WebInspector.TextEditor.prototype.createWidgetForLine):
Create a widget for a line.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@181306 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Controllers/IssueManager.js
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/IssueMessage.js
Source/WebInspectorUI/UserInterface/Models/LineWidget.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Protocol/ConsoleObserver.js
Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.css
Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js
Source/WebInspectorUI/UserInterface/Views/TextEditor.js

index fc60698..abf9244 100644 (file)
@@ -1,3 +1,81 @@
+2015-03-09  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Inline Error / Warning message for Issues
+        https://bugs.webkit.org/show_bug.cgi?id=142520
+
+        Reviewed by Timothy Hatcher.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Main.html:
+        New strings and new files.
+
+        * UserInterface/Controllers/IssueManager.js:
+        (WebInspector.IssueManager.prototype.issueWasAdded):
+        * UserInterface/Models/IssueMessage.js:
+        (WebInspector.IssueMessage):
+        (WebInspector.IssueMessage.prototype.get columnNumber):
+        * UserInterface/Protocol/ConsoleObserver.js:
+        (WebInspector.ConsoleObserver.prototype.messageAdded):
+        Correctly pass the column number into IssueMessage.
+
+        * UserInterface/Models/LineWidget.js:
+        (WebInspector.LineWidget):
+        (WebInspector.LineWidget.prototype.get codeMirrorLineWidget):
+        (WebInspector.LineWidget.prototype.get widgetElement):
+        Create a new Model class for a LineWidget. The root element
+        should never change, but its children can be updated.
+
+        * UserInterface/Views/SourceCodeTextEditor.css:
+        (.source-code.text-editor .CodeMirror-linewidget):
+        Override styles so that widgets can overlap line content.
+
+        (.source-code.text-editor > .CodeMirror .issue-widget):
+        (.source-code.text-editor > .CodeMirror .issue-widget.inline):
+        Float issue widgets to the right side of the editor.
+
+        (.source-code.text-editor > .CodeMirror .issue-widget > .arrow):
+        (.source-code.text-editor > .CodeMirror .issue-widget.inline > .arrow):
+        Pure CSS arrow for widgets on the same line as their issue.
+
+        (.source-code.text-editor > .CodeMirror .issue-widget > .icon):
+        (.source-code.text-editor > .CodeMirror .issue-widget > .icon.icon-warning):
+        (.source-code.text-editor > .CodeMirror .issue-widget > .icon.icon-error):
+        Issue icon styles for the different issue types.
+
+        (.source-code.text-editor > .CodeMirror .issue-widget.warning):
+        (.source-code.text-editor > .CodeMirror .issue-widget.inline.warning):
+        (.source-code.text-editor > .CodeMirror .issue-widget.inline.warning > .arrow):
+        (.source-code.text-editor > .CodeMirror .issue-widget.error):
+        (.source-code.text-editor > .CodeMirror .issue-widget.inline.error):
+        (.source-code.text-editor > .CodeMirror .issue-widget.inline.error > .arrow):
+        Different colors for the different issue types.
+
+        (.source-code.text-editor > .CodeMirror .issue-widget > .text):
+        (.source-code.text-editor > .CodeMirror .issue-widget.inline > .text):
+        Text positioning when on the same line or when expanded.
+
+        * UserInterface/Views/SourceCodeTextEditor.js:
+        (WebInspector.SourceCodeTextEditor.prototype._addIssue):
+        (WebInspector.SourceCodeTextEditor.prototype._iconClassNameForIssueLevel):
+        (WebInspector.SourceCodeTextEditor.prototype._updateIssueWidgetForIssues):
+        (WebInspector.SourceCodeTextEditor.prototype._isWidgetToggleable):
+        (WebInspector.SourceCodeTextEditor.prototype._handleWidgetClick):
+        Create widgets for issues.
+
+        (WebInspector.SourceCodeTextEditor.prototype._contentDidPopulate):
+        (WebInspector.SourceCodeTextEditor.prototype.textEditorUpdatedFormatting):
+        (WebInspector.SourceCodeTextEditor.prototype._clearWidgets):
+        (WebInspector.SourceCodeTextEditor.prototype._reinsertAllIssues):
+        Update all widgets in certain cases.
+
+        * UserInterface/Views/TextEditor.js:
+        (WebInspector.TextEditor.prototype.addStyleClassToLine):
+        This assertion is known to happen for issues added to an editor
+        before the content has loaded.
+
+        (WebInspector.TextEditor.prototype.createWidgetForLine):
+        Create a widget for a line.
+
 2015-03-06  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: JS Pretty Printing: "case" or "default" outside of switch causes unbalanced indentation
index f01dfaa..ef8f790 100644 (file)
Binary files a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js and b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js differ
index 590cf83..6322042 100644 (file)
@@ -50,7 +50,7 @@ WebInspector.IssueManager.prototype = {
         this.dispatchEventToListeners(WebInspector.IssueManager.Event.Cleared);
     },
 
-    issueWasAdded: function(source, level, text, url, lineNumber, parameters)
+    issueWasAdded: function(source, level, text, url, lineNumber, columnNumber, parameters)
     {
         var modifiedLineNumber;
         if (lineNumber) {
@@ -58,7 +58,7 @@ WebInspector.IssueManager.prototype = {
             modifiedLineNumber = lineNumber - 1;
         }
 
-        var issue = new WebInspector.IssueMessage(source, level, text, url, modifiedLineNumber, parameters);
+        var issue = new WebInspector.IssueMessage(source, level, text, url, modifiedLineNumber, columnNumber, parameters);
         this._issues.push(issue);
 
         this.dispatchEventToListeners(WebInspector.IssueManager.Event.IssueWasAdded, {issue});
index c321579..09a5994 100644 (file)
     <script src="Models/KeyboardShortcut.js"></script>
     <script src="Models/LayoutTimelineRecord.js"></script>
     <script src="Models/LazySourceCodeLocation.js"></script>
+    <script src="Models/LineWidget.js"></script>
     <script src="Models/LogObject.js"></script>
     <script src="Models/NativeFunctionParameters.js"></script>
     <script src="Models/NetworkTimeline.js"></script>
index 27ae821..003d7e4 100644 (file)
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.IssueMessage = function(source, level, text, url, lineNumber, parameters)
+WebInspector.IssueMessage = function(source, level, text, url, lineNumber, columnNumber, parameters)
 {
     WebInspector.Object.call(this);
 
     this._level = level;
     this._text = text;
 
+    // FIXME: Move to a SourceCodeLocation.
+
     // FIXME <http://webkit.org/b/76404>: Remove the string equality checks for undefined
     // once we don't get that value anymore from WebCore.
 
@@ -40,6 +42,9 @@ WebInspector.IssueMessage = function(source, level, text, url, lineNumber, param
     if (typeof lineNumber === "number" && lineNumber >= 0)
         this._lineNumber = lineNumber;
 
+    if (typeof columnNumber === "number" && columnNumber >= 0)
+        this._columnNumber = columnNumber;
+
     if (parameters && parameters !== "undefined") {
         this._parameters = [];
         for (var i = 0; i < parameters.length; ++i) {
@@ -175,6 +180,7 @@ WebInspector.IssueMessage.Type.displayName = function(type)
 
 WebInspector.IssueMessage.prototype = {
     constructor: WebInspector.IssueMessage,
+    __proto__: WebInspector.Object.prototype,
 
     get type()
     {
@@ -201,9 +207,14 @@ WebInspector.IssueMessage.prototype = {
         return this._lineNumber;
     },
 
+    get columnNumber()
+    {
+        return this._columnNumber;
+    },
+
     // Private
 
-    _formatTextIfNecessary: function()
+    _formatTextIfNecessary()
     {
         if (!this._parameters)
             return;
@@ -238,5 +249,3 @@ WebInspector.IssueMessage.prototype = {
         this._text = resultText;
     }
 };
-
-WebInspector.IssueMessage.prototype.__proto__ = WebInspector.Object.prototype;
diff --git a/Source/WebInspectorUI/UserInterface/Models/LineWidget.js b/Source/WebInspectorUI/UserInterface/Models/LineWidget.js
new file mode 100644 (file)
index 0000000..8afdd40
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.LineWidget = function(codeMirrorLineWidget, widgetElement)
+{
+    WebInspector.Object.call(this);
+
+    console.assert(widgetElement instanceof Element);
+
+    this._codeMirrorLineWidget = codeMirrorLineWidget;
+    this._widgetElement = widgetElement;
+};
+
+WebInspector.LineWidget.prototype = {
+    constructor: WebInspector.LineWidget,
+    __proto__: WebInspector.Object.prototype,
+
+    // Public
+
+    get codeMirrorLineWidget()
+    {
+        return this._codeMirrorLineWidget;
+    },
+
+    get widgetElement()
+    {
+        return this._widgetElement;
+    },
+
+    clear()
+    {
+        this._codeMirrorLineWidget.clear();
+    },
+
+    update()
+    {
+        // FIXME: Later version of CodeMirror has update.
+        if (this._codeMirrorLineWidget.update)
+            this._codeMirrorLineWidget.update();
+    }
+};
index 898334b..7d4953e 100644 (file)
@@ -35,6 +35,9 @@ WebInspector.ConsoleObserver.prototype = {
 
     messageAdded: function(message)
     {
+        if (message.type === "assert" && !message.text)
+            message.text = WebInspector.UIString("Assertion");
+
         if (message.level === "warning" || message.level === "error")
             WebInspector.issueManager.issueWasAdded(message.source, message.level, message.text, message.url, message.line, message.column || 0, message.parameters);
 
index ae31f07..472afc2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+.source-code.text-editor .CodeMirror-linewidget {
+    overflow: initial;
+    z-index: 3; /* overlap line content */
+}
+
+.source-code.text-editor > .CodeMirror .warning {
+    background-color: rgb(253, 249, 226);
+}
+
 .source-code.text-editor > .CodeMirror .error {
     background-color: rgb(255, 220, 208);
 }
 
-.source-code.text-editor > .CodeMirror .warning {
+.source-code.text-editor > .CodeMirror .issue-widget {
+    float: right;
+    padding: 0 5px 1px 5px;
+    border-bottom-left-radius: 5px;
+    cursor: default;
+}
+
+.source-code.text-editor > .CodeMirror .issue-widget.inline {
+    position: relative;
+    top: -13px;
+
+    padding: 0px 5px 0 3px;
+    border-bottom-left-radius: 0;
+}
+
+.source-code.text-editor > .CodeMirror .issue-widget > .arrow {
+    display: none;
+}
+
+.source-code.text-editor > .CodeMirror .issue-widget.inline > .arrow {
+    position: absolute;
+    left: -5.5px;
+    display: block;
+
+    top: 0px;
+    width: 0px;
+    height: 0px;
+
+    border-top: 6.5px solid transparent;
+    border-bottom: 6.5px solid transparent; 
+    border-right: 5.5px solid transparent;
+}
+
+.source-code.text-editor > .CodeMirror .issue-widget > .icon {
+    height: 9px;
+    width: 9px;
+    padding-right: 12px;
+
+    background-size: 9px 9px;
+    background-repeat: no-repeat;
+    background-position-y: 1px;
+}
+
+.source-code.text-editor > .CodeMirror .issue-widget > .icon.icon-warning {
+    background-image: url(../Images/Warning.svg);
+}
+
+.source-code.text-editor > .CodeMirror .issue-widget > .icon.icon-error {
+    background-image: url(../Images/Error.svg);
+}
+
+.source-code.text-editor > .CodeMirror .issue-widget.warning {
     background-color: rgb(253, 249, 226);
 }
 
+.source-code.text-editor > .CodeMirror .issue-widget.inline.warning {
+    background-color: rgb(254, 233, 177);
+}
+
+.source-code.text-editor > .CodeMirror .issue-widget.inline.warning > .arrow {
+    border-right-color: rgb(254, 233, 177);
+}
+
+.source-code.text-editor > .CodeMirror .issue-widget.error {
+    background-color: rgb(255, 220, 208);
+}
+
+.source-code.text-editor > .CodeMirror .issue-widget.inline.error {
+    background-color: rgb(255, 173, 153);
+}
+
+.source-code.text-editor > .CodeMirror .issue-widget.inline.error > .arrow {
+    border-right-color: rgb(255, 173, 153);
+}
+
+.source-code.text-editor > .CodeMirror .issue-widget > .text {
+    font-family: -webkit-system-font, sans-serif;
+}
+
+.source-code.text-editor > .CodeMirror .issue-widget.inline > .text {
+    display: inline-block;
+
+    max-width: 300px;
+    height: 12px;
+    padding-top: 1px;
+
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    overflow: hidden;
+}
+
 .popover .debugger-popover-content {
     font-family: Menlo, monospace;
     font-size: 11px;
index 0920e96..c0a4c3d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -29,7 +29,8 @@ WebInspector.SourceCodeTextEditor = function(sourceCode)
 
     this._sourceCode = sourceCode;
     this._breakpointMap = {};
-    this._issuesLineNumberMap = {};
+    this._issuesLineNumberMap = new Map;
+    this._widgetMap = new Map;
     this._contentPopulated = false;
     this._invalidLineNumbers = {0: true};
     this._ignoreContentDidChange = 0;
@@ -90,8 +91,8 @@ WebInspector.SourceCodeTextEditor.HoveredExpressionHighlightStyleClassName = "ho
 WebInspector.SourceCodeTextEditor.DurationToMouseOverTokenToMakeHoveredToken = 500;
 WebInspector.SourceCodeTextEditor.DurationToMouseOutOfHoveredTokenToRelease = 1000;
 WebInspector.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling = 100;
-
 WebInspector.SourceCodeTextEditor.AutoFormatMinimumLineLength = 500;
+WebInspector.SourceCodeTextEditor.WidgetContainsMultipleIssuesSymbol = Symbol("source-code-widget-contains-multiple-issues");
 
 WebInspector.SourceCodeTextEditor.Event = {
     ContentWillPopulate: "source-code-text-editor-content-will-populate",
@@ -438,17 +439,10 @@ WebInspector.SourceCodeTextEditor.prototype = {
         this.dispatchEventToListeners(WebInspector.SourceCodeTextEditor.Event.ContentDidPopulate);
 
         // We add the issues each time content is populated. This is needed because lines might not exist
-        // if we tried added them before when the full content wasn't avaiable. (When populating with
+        // if we tried added them before when the full content wasn't available. (When populating with
         // partial script content this can be called multiple times.)
 
-        this._issuesLineNumberMap = {};
-
-        var issues = WebInspector.issueManager.issuesForSourceCode(this._sourceCode);
-        for (var i = 0; i < issues.length; ++i) {
-            var issue = issues[i];
-            console.assert(this._matchesIssue(issue));
-            this._addIssue(issue);
-        }
+        this._reinsertAllIssues();
 
         this._updateEditableMarkers();
     },
@@ -814,20 +808,153 @@ WebInspector.SourceCodeTextEditor.prototype = {
 
     _addIssue: function(issue)
     {
-        var lineNumberIssues = this._issuesLineNumberMap[issue.lineNumber];
-        if (!lineNumberIssues)
-            lineNumberIssues = this._issuesLineNumberMap[issue.lineNumber] = [];
+        // FIXME: Issue should have a SourceCodeLocation.
+        var sourceCodeLocation = this._sourceCode.createSourceCodeLocation(issue.lineNumber, issue.columnNumber);
+        var lineNumber = sourceCodeLocation.formattedLineNumber;
+
+        var lineNumberIssues = this._issuesLineNumberMap.get(lineNumber);
+        if (!lineNumberIssues) {
+            lineNumberIssues = [];
+            this._issuesLineNumberMap.set(lineNumber, lineNumberIssues);
+        }
+
+        // Avoid displaying duplicate issues on the same line.
+        for (var existingIssue of lineNumberIssues) {
+            if (existingIssue.columnNumber === issue.columnNumber && existingIssue.text === issue.text)
+                return;
+        }
 
         lineNumberIssues.push(issue);
 
         if (issue.level === WebInspector.IssueMessage.Level.Error)
-            this.addStyleClassToLine(issue.lineNumber, WebInspector.SourceCodeTextEditor.LineErrorStyleClassName);
+            this.addStyleClassToLine(lineNumber, WebInspector.SourceCodeTextEditor.LineErrorStyleClassName);
         else if (issue.level === WebInspector.IssueMessage.Level.Warning)
-            this.addStyleClassToLine(issue.lineNumber, WebInspector.SourceCodeTextEditor.LineWarningStyleClassName);
+            this.addStyleClassToLine(lineNumber, WebInspector.SourceCodeTextEditor.LineWarningStyleClassName);
         else
             console.error("Unknown issue level");
 
-        // FIXME <rdar://problem/10854857>: Show the issue message on the line as a bubble.
+        var widget = this._issueWidgetForLine(lineNumber);
+        if (widget) {
+            if (issue.level === WebInspector.IssueMessage.Level.Error)
+                widget.widgetElement.classList.add(WebInspector.SourceCodeTextEditor.LineErrorStyleClassName);
+            else if (issue.level === WebInspector.IssueMessage.Level.Warning)
+                widget.widgetElement.classList.add(WebInspector.SourceCodeTextEditor.LineWarningStyleClassName);
+
+            this._updateIssueWidgetForIssues(widget, lineNumberIssues);
+        }
+    },
+
+    _issueWidgetForLine: function(lineNumber)
+    {
+        var widget = this._widgetMap.get(lineNumber);
+        if (widget)
+            return widget;
+
+        widget = this.createWidgetForLine(lineNumber);
+        if (!widget)
+            return null;
+
+        var widgetElement = widget.widgetElement;
+        widgetElement.classList.add("issue-widget");
+        widgetElement.classList.add("inline");
+        widgetElement.addEventListener("click", this._handleWidgetClick.bind(this, widget, lineNumber));
+
+        this._widgetMap.set(lineNumber, widget);
+
+        return widget;
+    },
+
+    _iconClassNameForIssueLevel: function(level)
+    {
+        if (level === WebInspector.IssueMessage.Level.Warning)
+            return "icon-warning";
+
+        console.assert(level === WebInspector.IssueMessage.Level.Error);
+        return "icon-error";
+    },
+
+    _updateIssueWidgetForIssues: function(widget, issues)
+    {
+        var widgetElement = widget.widgetElement;
+        widgetElement.removeChildren();
+
+        if (widgetElement.classList.contains("inline") || issues.length === 1) {
+            var arrowElement = widgetElement.appendChild(document.createElement("span"));
+            arrowElement.className = "arrow";
+
+            var iconElement = widgetElement.appendChild(document.createElement("span"));
+            iconElement.className = "icon";            
+
+            var textElement = widgetElement.appendChild(document.createElement("span"));
+            textElement.className = "text";
+
+            if (issues.length === 1) {
+                iconElement.classList.add(this._iconClassNameForIssueLevel(issues[0].level));
+                textElement.textContent = issues[0].text;
+            } else {
+                var errorsCount = 0;
+                var warningsCount = 0;
+                for (var issue of issues) {
+                    if (issue.level === WebInspector.IssueMessage.Level.Error)
+                        ++errorsCount;
+                    else if (issue.level === WebInspector.IssueMessage.Level.Warning)
+                        ++warningsCount;
+                }
+
+                if (warningsCount && errorsCount) {
+                    iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
+                    textElement.textContent = WebInspector.UIString("%d Errors, %d Warnings").format(errorsCount, warningsCount);
+                } else if (errorsCount) {
+                    iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
+                    textElement.textContent = WebInspector.UIString("%d Errors").format(errorsCount);
+                } else if (warningsCount) {
+                    iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
+                    textElement.textContent = WebInspector.UIString("%d Warnings").format(warningsCount);
+                }
+
+                widget[WebInspector.SourceCodeTextEditor.WidgetContainsMultipleIssuesSymbol] = true;
+            }
+        } else {
+            for (var issue of issues) {
+                var iconElement = widgetElement.appendChild(document.createElement("span"));
+                iconElement.className = "icon";
+                iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
+
+                var textElement = widgetElement.appendChild(document.createElement("span"));
+                textElement.className = "text";
+                textElement.textContent = issue.text;
+
+                widgetElement.appendChild(document.createElement("br"));
+            }
+        }
+
+        widget.update();
+    },
+
+    _isWidgetToggleable: function(widget)
+    {
+        if (widget[WebInspector.SourceCodeTextEditor.WidgetContainsMultipleIssuesSymbol])
+            return true;
+
+        if (!widget.widgetElement.classList.contains("inline"))
+            return true;
+
+        var textElement = widget.widgetElement.lastChild;
+        if (textElement.offsetWidth !== textElement.scrollWidth)
+            return true;
+        
+        return false;
+    },
+
+    _handleWidgetClick: function(widget, lineNumber, event)
+    {
+        if (!this._isWidgetToggleable(widget))
+            return;
+
+        widget.widgetElement.classList.toggle("inline");
+
+        var lineNumberIssues = this._issuesLineNumberMap.get(lineNumber);
+        this._updateIssueWidgetForIssues(widget, lineNumberIssues);
     },
 
     _breakpointInfoForBreakpoint: function(breakpoint)
@@ -1066,8 +1193,8 @@ WebInspector.SourceCodeTextEditor.prototype = {
                 this._sourceCode.resource.formatterSourceMap = this.formatterSourceMap;
         }
 
-        // Some breakpoints may have moved, some might not have. Just go through
-        // and remove and reinsert all the breakpoints.
+        // Some breakpoints / issues may have moved, some might not have. Just go through
+        // and remove and reinsert all the breakpoints / issues.
 
         var oldBreakpointMap = this._breakpointMap;
         this._breakpointMap = {};
@@ -1081,6 +1208,28 @@ WebInspector.SourceCodeTextEditor.prototype = {
                 this.setBreakpointInfoForLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
             }
         }
+
+        this._reinsertAllIssues();
+    },
+
+    _clearWidgets: function()
+    {
+        for (var widget of this._widgetMap.values())
+            widget.clear();
+
+        this._widgetMap.clear();
+    },
+
+    _reinsertAllIssues: function()
+    {
+        this._issuesLineNumberMap.clear();
+        this._clearWidgets();
+
+        var issues = WebInspector.issueManager.issuesForSourceCode(this._sourceCode);
+        for (var issue of issues) {
+            console.assert(this._matchesIssue(issue));
+            this._addIssue(issue);
+        }
     },
 
     _debuggerDidPause: function(event)
index 92f3e9e..b7b7878 100644 (file)
@@ -576,7 +576,6 @@ WebInspector.TextEditor.prototype = {
     addStyleClassToLine: function(lineNumber, styleClassName)
     {
         var lineHandle = this._codeMirror.getLineHandle(lineNumber);
-        console.assert(lineHandle);
         if (!lineHandle)
             return null;
 
@@ -603,6 +602,17 @@ WebInspector.TextEditor.prototype = {
         return this._codeMirror.toggleLineClass(lineHandle, "wrap", styleClassName);
     },
 
+    createWidgetForLine: function(lineNumber)
+    {
+        var lineHandle = this._codeMirror.getLineHandle(lineNumber);
+        if (!lineHandle)
+            return null;
+
+        var widgetElement = document.createElement("div");
+        var lineWidget = this._codeMirror.addLineWidget(lineHandle, widgetElement, {coverGutter: false, noHScroll: true, handleMouseEvents: true});
+        return new WebInspector.LineWidget(lineWidget, widgetElement);
+    },
+
     get lineCount()
     {
         return this._codeMirror.lineCount();