Skip to content

Commit

Permalink
Show all diagnostics as annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
rchl committed May 24, 2022
1 parent 7ad3911 commit 603ece5
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 6 deletions.
3 changes: 3 additions & 0 deletions LSP.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
// under the cursor in status bar if available.
"show_diagnostics_in_view_status": true,

// Show the diagnostics as inline annotations.
"show_diagnostics_inline": "off",

// Show highlights and gutter markers in the file views for diagnostics
// with level equal to or less than:
// none: 0 (never show)
Expand Down
16 changes: 16 additions & 0 deletions annotations.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.lsp_annotation {
margin: 0;
border-width: 0;
}
.lsp_annotation .errors {
color: color(var(--redish) alpha(0.85));
}
.lsp_annotation .warnings {
color: color(var(--yellowish) alpha(0.85));
}
.lsp_annotation .info {
color: color(var(--bluish) alpha(0.85));
}
.lsp_annotation .hints {
color: color(var(--bluish) alpha(0.85));
}
2 changes: 2 additions & 0 deletions plugin/core/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ def __init__(self) -> None:
self.notification_classname = "notification"
self.sheets = sublime.load_resource("Packages/LSP/sheets.css")
self.sheets_classname = "lsp_sheet"
self.annotations = sublime.load_resource("Packages/LSP/annotations.css")
self.annotations_classname = "lsp_annotation"


_css = None # type: Optional[CSS]
Expand Down
6 changes: 4 additions & 2 deletions plugin/core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from .file_watcher import FileWatcherEventType
from .logging import debug, set_debug_logging
from .protocol import TextDocumentSyncKindNone
from .typing import Any, Optional, List, Dict, Generator, Callable, Iterable, Union, Set, Tuple, TypedDict, TypeVar
from .typing import cast
from .typing import Any, Optional, List, Dict, Generator, Callable, Iterable, Literal, Union, Set, Tuple
from .typing import cast, TypedDict, TypeVar
from .url import filename_to_uri
from .url import parse_uri
from threading import RLock
Expand Down Expand Up @@ -209,6 +209,7 @@ class Settings:
show_diagnostics_count_in_view_status = None # type: bool
show_multiline_diagnostics_highlights = None # type: bool
show_diagnostics_in_view_status = None # type: bool
show_diagnostics_inline = None # type: Literal["all", "at-cursor", "off"]
show_diagnostics_panel_on_save = None # type: int
show_diagnostics_severity_level = None # type: int
show_references_in_quick_panel = None # type: bool
Expand Down Expand Up @@ -244,6 +245,7 @@ def r(name: str, default: Union[bool, int, str, list, dict]) -> None:
r("show_code_actions_in_hover", True)
r("show_diagnostics_count_in_view_status", False)
r("show_diagnostics_in_view_status", True)
r("show_diagnostics_inline", "none")
r("show_multiline_diagnostics_highlights", True)
r("show_diagnostics_panel_on_save", 2)
r("show_diagnostics_severity_level", 2)
Expand Down
20 changes: 20 additions & 0 deletions plugin/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,26 @@ def diagnostic_source(diagnostic: Diagnostic) -> str:
return diagnostic.get("source", "unknown-source")


def format_diagnostics_for_annotation(
diagnostics: List[Diagnostic], view: sublime.View
) -> Tuple[List[sublime.Region], List[str]]:
regions = []
annotations = []
for diagnostic in diagnostics:
lsp_range = diagnostic.get('range')
if not lsp_range:
continue
message = text2html(diagnostic.get('message') or '')
source = diagnostic.get('source')
css_class = DIAGNOSTIC_SEVERITY[diagnostic_severity(diagnostic) - 1][1]
line = "[{}] {}".format(source, message) if source else message
content = '<body id="annotation" class="{1}"><style>{0}</style><div class="{2}">{3}</div></body>'.format(
lsp_css().annotations, lsp_css().annotations_classname, css_class, line)
regions.append(range_to_region(Range.from_lsp(lsp_range), view))
annotations.append(content)
return (regions, annotations)


def format_diagnostic_for_panel(diagnostic: Diagnostic) -> Tuple[str, Optional[int], Optional[str], Optional[str]]:
"""
Turn an LSP diagnostic into a string suitable for an output panel.
Expand Down
34 changes: 32 additions & 2 deletions plugin/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@
from .core.typing import Any, Callable, Optional, Dict, Generator, Iterable, List, Tuple, Union
from .core.url import parse_uri
from .core.url import view_to_uri
from .core.views import DIAGNOSTIC_SEVERITY
from .core.views import diagnostic_severity
from .core.views import first_selection_region
from .core.views import format_completion
from .core.views import format_diagnostics_for_annotation
from .core.views import make_command_link
from .core.views import MarkdownLangMap
from .core.views import range_to_region
Expand Down Expand Up @@ -257,7 +259,7 @@ def diagnostics_intersecting_region_async(
for diagnostic, candidate in diagnostics:
# Checking against points is inclusive unlike checking whether region intersects another
# region which is exclusive (at region end) and we want an inclusive behavior in this case.
if region.contains(candidate.a) or region.contains(candidate.b):
if region.intersects(candidate) or region.contains(candidate.a) or region.contains(candidate.b):
covering = covering.cover(candidate)
intersections.append(diagnostic)
if intersections:
Expand Down Expand Up @@ -289,6 +291,7 @@ def on_diagnostics_updated_async(self) -> None:
if userprefs().show_code_actions:
self._do_code_actions()
self._update_diagnostic_in_status_bar_async()
self._update_inline_diagnostic_async()

def _update_diagnostic_in_status_bar_async(self) -> None:
if userprefs().show_diagnostics_in_view_status:
Expand All @@ -304,6 +307,30 @@ def _update_diagnostic_in_status_bar_async(self) -> None:
return
self.view.erase_status(self.ACTIVE_DIAGNOSTIC)

def _update_inline_diagnostic_async(self) -> None:
region_key = "lsp_d-a"
self.view.erase_regions(region_key)
if userprefs().show_diagnostics_inline != 'at-cursor':
return
r = first_selection_region(self.view)
if r is None:
return
sorted_diagnostics = [] # type: List[Diagnostic]
session_buffer_diagnostics, _ = self.diagnostics_intersecting_region_async(r)
for _, diagnostics in session_buffer_diagnostics:
sorted_diagnostics.extend(diagnostics)
if sorted_diagnostics:
sorted_diagnostics = sorted(sorted_diagnostics, key=lambda d: d.get('severity', 1))
first_diagnostic = sorted_diagnostics[0]
lsp_range = first_diagnostic.get('range')
if lsp_range:
scope = DIAGNOSTIC_SEVERITY[first_diagnostic.get('severity', 1) - 1][2]
icon = ""
flags = sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE
annotation_color = self.view.style_for_scope(scope).get('foreground') or 'red'
regions, annotations = format_diagnostics_for_annotation(sorted_diagnostics, self.view)
self.view.add_regions(region_key, regions, scope, icon, flags, annotations, annotation_color)

def session_views_async(self) -> Generator[SessionView, None, None]:
yield from self._session_views.values()

Expand Down Expand Up @@ -364,6 +391,7 @@ def on_selection_modified_async(self) -> None:
self._when_selection_remains_stable_async(self._do_code_actions, current_region,
after_ms=self.code_actions_debounce_time)
self._update_diagnostic_in_status_bar_async()
self._update_inline_diagnostic_async()
self._resolve_visible_code_lenses_async()

def on_post_save_async(self) -> None:
Expand Down Expand Up @@ -555,7 +583,9 @@ def _on_code_actions(self, responses: CodeActionsByConfigName) -> None:
flags = sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE
annotations = []
annotation_color = ""
if userprefs().show_code_actions == 'bulb':
if userprefs().show_code_actions == 'bulb' or (
userprefs().show_code_actions == 'annotation' and userprefs().show_diagnostics_inline == 'at-cursor'
):
scope = 'region.yellowish lightbulb.lsp'
icon = 'Packages/LSP/icons/lightbulb.png'
self._lightbulb_line = self.view.rowcol(regions[0].begin())[0]
Expand Down
6 changes: 5 additions & 1 deletion plugin/session_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ def update(self, version: int, changes: Iterable[sublime.TextChange]) -> None:

class DiagnosticSeverityData:

__slots__ = ('regions', 'regions_with_tag', 'annotations', 'scope', 'icon')
__slots__ = ('region_diagnostics', 'tag_diagnostics', 'regions', 'regions_with_tag', 'annotations', 'scope', 'icon')

def __init__(self, severity: int) -> None:
self.region_diagnostics = [] # type: List[Diagnostic]
self.tag_diagnostics = [] # type: List[Diagnostic]
self.regions = [] # type: List[sublime.Region]
self.regions_with_tag = {} # type: Dict[int, List[sublime.Region]]
self.annotations = [] # type: List[str]
Expand Down Expand Up @@ -361,8 +363,10 @@ def on_diagnostics_async(self, raw_diagnostics: List[Diagnostic], version: Optio
if tags:
for tag in tags:
data.regions_with_tag.setdefault(tag, []).append(region)
data.tag_diagnostics.append(diagnostic)
else:
data.regions.append(region)
data.region_diagnostics.append(diagnostic)
diagnostics.append((diagnostic, region))
if severity == DiagnosticSeverity.Error:
total_errors += 1
Expand Down
10 changes: 9 additions & 1 deletion plugin/session_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,10 +278,18 @@ def _draw_diagnostics(self, severity: int, max_severity_level: int, flags: int,
tag_scope = self.diagnostics_tag_scope(tag)
# Trick to only add tag regions if there is a corresponding color scheme scope defined.
if tag_scope and 'background' in self.view.style_for_scope(tag_scope):
# annotations = [format_diagnostic_for_annotation(diag) for diag in data.tag_diagnostics]
# annotation_color = self.view.style_for_scope(tag_scope)['foreground']
# self.view.add_regions(key_tags[tag], regions, tag_scope, flags=sublime.DRAW_NO_OUTLINE,
# annotations=annotations, annotation_color=annotation_color)
self.view.add_regions(key_tags[tag], regions, tag_scope, flags=sublime.DRAW_NO_OUTLINE)
else:
non_tag_regions.extend(regions)
self.view.add_regions(key, non_tag_regions, data.scope, data.icon, flags)
# annotations = [format_diagnostic_for_annotation(diag) for diag in data.region_diagnostics]
# annotation_color = self.view.style_for_scope(data.scope)['foreground']
# self.view.add_regions(key, non_tag_regions, data.scope, data.icon, flags | sublime.DRAW_EMPTY,
# annotations, annotation_color)
self.view.add_regions(key, non_tag_regions, data.scope, data.icon, flags | sublime.DRAW_EMPTY)
else:
self.view.erase_regions(key)

Expand Down
14 changes: 14 additions & 0 deletions sublime-package.json
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,20 @@
"default": true,
"markdownDescription": "Show the diagnostics description of the code under the cursor in status bar if available."
},
"show_diagnostics_inline": {
"default": "off",
"enum": [
"all",
"at-cursor",
"off"
],
"markdownEnumDescriptions": [
"Show all existing diagnostics as inline annotations.",
"Only show those diagnostics inline that intersect with current selection / cursor. Enabling this option forces the `show_code_actions` option to `\"bulb\"` if set to `\"annotation\"`.",
"Don't show diagnostics inline."
],
"markdownDescription": "Show the diagnostics as inline annotations."
},
"show_diagnostics_severity_level": {
"type": "integer",
"default": 4,
Expand Down

0 comments on commit 603ece5

Please sign in to comment.