Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to show diagnostics as annotations #1702

Merged
merged 101 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from 99 commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
603ece5
Show all diagnostics as annotations
rchl May 29, 2021
655454e
Merge remote-tracking branch 'origin/main' into feat/diag-annotations
rchl Jun 8, 2022
d4250a5
Merge branch 'main' into feat/diag-annotations
rchl Jun 22, 2022
4b15cb5
Merge remote-tracking branch 'origin/main' into feat/diag-annotations
rchl Jul 3, 2022
dc38b46
Merge branch 'main' into feat/diag-annotations
rchl Jul 7, 2022
2bbc951
Merge branch 'main' into feat/diag-annotations
rchl Jul 14, 2022
2563f4a
remove "all" option
rchl Jul 14, 2022
69a750d
handle multiple regions
rchl Jul 14, 2022
b117996
remove unused code
rchl Jul 14, 2022
846e21e
fixes
rchl Jul 14, 2022
cd7fbab
Merge remote-tracking branch 'origin/main' into feat/diag-annotations
rchl Jul 17, 2022
a0439b0
Merge branch 'main' into feat/diag-annotations
rchl Jul 25, 2022
1a45555
Merge branch 'main' into feat/diag-annotations
rchl Aug 2, 2022
adcabb2
Merge branch 'main' into feat/diag-annotations
rchl Aug 15, 2022
6fbdc32
Merge branch 'main' into feat/diag-annotations
rchl Aug 20, 2022
ab39c39
Merge branch 'main' into feat/diag-annotations
rchl Aug 21, 2022
c8f57a2
Merge branch 'main' into feat/diag-annotations
rchl Aug 24, 2022
72a0878
Merge branch 'main' into feat/diag-annotations
rchl Aug 29, 2022
4e67412
move handling to SessionView to get instant feedback on restarting se…
rchl Aug 29, 2022
ec3dfb9
lint
rchl Aug 30, 2022
cb95b89
Merge branch 'main' into feat/diag-annotations
rchl Aug 30, 2022
29bfefc
Merge branch 'main' into feat/diag-annotations
rchl Sep 11, 2022
33a0ef0
maybe I'll get it right eventually
rchl Sep 11, 2022
ea8186e
Merge branch 'main' into feat/diag-annotations
rchl Sep 12, 2022
e923ed5
Merge branch 'main' into feat/diag-annotations
rchl Sep 12, 2022
4a2c753
Merge branch 'main' into feat/diag-annotations
rchl Sep 13, 2022
2cd57c7
Merge branch 'main' into feat/diag-annotations
rchl Sep 26, 2022
b828dfa
Merge branch 'main' into feat/diag-annotations
rchl Sep 27, 2022
1bb4504
Merge branch 'main' into feat/diag-annotations
rchl Oct 3, 2022
b0ce92a
Merge branch 'main' into feat/diag-annotations
rchl Oct 3, 2022
70cad6a
Filter out non-quickfix actions in view
rchl Sep 24, 2022
c7c633d
expose function for tests
rchl Oct 5, 2022
3779d02
better split
rchl Oct 5, 2022
5b2ea06
fix bug and steal some changes from jwortmann
rchl Oct 5, 2022
2423dc0
better types
rchl Oct 5, 2022
7ef3334
careful with stupid lambdas
rchl Oct 5, 2022
4450573
address comments
rchl Oct 6, 2022
8ee7136
extra tests
rchl Oct 6, 2022
8501670
extra test
rchl Oct 7, 2022
e38ae24
fix filtering and clicking on code actions annotation
rchl Oct 7, 2022
092568b
simplify
rchl Oct 8, 2022
a476682
address comments
rchl Oct 8, 2022
b79a8ce
better fix
rchl Oct 8, 2022
235fc80
import
rchl Oct 8, 2022
db9b15f
better quick fix presentation in hover popup
rchl Oct 8, 2022
3540306
Add refactor to the context menu
rchl Oct 8, 2022
d10db20
Merge branch 'main' into fix/code-actions-filter
rchl Oct 9, 2022
0b0fa77
Merge branch 'main' into feat/diag-annotations
rchl Oct 10, 2022
2230be5
Merge branch 'fix/code-actions-filter' into feat/diag-annotations
rchl Oct 10, 2022
81870b8
Merge branch 'main' into feat/diag-annotations
rchl Oct 11, 2022
f9f1c50
Merge branch 'main' into feat/diag-annotations
rchl Oct 22, 2022
e0e41c8
Merge branch 'main' into feat/diag-annotations
rchl Oct 23, 2022
965fd6d
Merge branch 'main' into feat/diag-annotations
rchl Oct 27, 2022
fbae04a
Merge branch 'main' into feat/diag-annotations
rchl Oct 28, 2022
b1c7407
Merge branch 'main' into feat/diag-annotations
rchl Oct 31, 2022
083f692
Merge remote-tracking branch 'origin/main' into feat/diag-annotations
rchl Oct 31, 2022
9b9fc0b
Merge branch 'main' into feat/diag-annotations
rchl Nov 9, 2022
6cfa360
adjust
rchl Nov 11, 2022
a945978
Merge branch 'main' into feat/diag-annotations
rchl Nov 12, 2022
18803eb
Merge branch 'main' into feat/diag-annotations
rchl Jan 16, 2023
6d2d018
Merge branch 'main' into feat/diag-annotations
rchl Feb 18, 2023
6da62bf
Merge branch 'main' into feat/diag-annotations
rchl Feb 24, 2023
f5a8f3e
WIP
rchl Feb 26, 2023
2fd9616
Merge branch 'main' into feat/diag-annotations
rchl Mar 2, 2023
6e63396
fix crash
rchl Mar 3, 2023
8041444
Merge branch 'main' into feat/diag-annotations
rchl Mar 4, 2023
3db120b
POC of inline diagnostics below errors
rchl Mar 4, 2023
7dd0908
move
rchl Mar 4, 2023
2a44795
lint
rchl Mar 4, 2023
d2a9f84
no need to save as variables
rchl Mar 4, 2023
9d9d428
type-friendly code
rchl Mar 4, 2023
8540865
lint
rchl Mar 4, 2023
3ed247b
unused class
rchl Mar 4, 2023
cac80c4
(hack) try to keep selection at same viewport position
rchl Mar 6, 2023
611916a
better x
rchl Mar 6, 2023
f3b6d82
lines
rchl Mar 4, 2023
055835e
escape html in phantoms
rchl Mar 10, 2023
ea8d8fc
fix flicker?
rchl Mar 10, 2023
439c883
Merge branch 'main' into feat/diag-annotations
rchl Mar 10, 2023
47116fe
Merge remote-tracking branch 'origin/main' into feat/diag-annotations
rchl Mar 22, 2023
8657c88
Merge branch 'main' into feat/diag-annotations
rchl Apr 17, 2023
8fd634e
lint
rchl Apr 17, 2023
d7ea7d9
Merge branch 'diag-ann-2' into feat/diag-annotations
rchl Apr 17, 2023
6559797
changes
rchl Apr 17, 2023
4db1142
remove log
rchl Apr 22, 2023
efacaae
Merge branch 'main' into feat/diag-annotations
rchl May 17, 2023
2d759f4
Merge branch 'main' into feat/diag-annotations
rchl Jun 10, 2023
c2906ff
setting description update
rchl Jun 10, 2023
5155de2
unused
rchl Jun 10, 2023
e04de63
escape source
rchl Jun 10, 2023
cbad0c2
remove RegionProvider
rchl Jun 10, 2023
c73afdd
unused
rchl Jun 10, 2023
747e743
remove phantom support
rchl Jun 10, 2023
4dceb87
rename
rchl Jun 10, 2023
85ce5ae
typo
rchl Jun 10, 2023
96c9b44
Revert "remove RegionProvider"
rchl Jun 10, 2023
c998fc9
not needed for now
rchl Jun 10, 2023
8e6bd31
rename option
rchl Jun 12, 2023
e4cfdcb
rename class
rchl Jun 12, 2023
7bc11be
Merge remote-tracking branch 'origin/main' into feat/diag-annotations
rchl Jun 18, 2023
f446b66
switch to show_diagnostics_annotations_severity_level
rchl Jun 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions LSP.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@
// See also: "diagnostics_highlight_style".
"show_multiline_diagnostics_highlights": true,

// Show diagnostics as annotations anchored at the right side of the corresponding line.
// When enabled, it's recommended not to use the "annotation" value for the
// `show_code_actions` option as it's impossible to enforce which one gets shown first.
"show_diagnostics_annotations": false,

// --- Hover popup --------------------------------------------------------------------

// The maximum number of characters (approximately) before wrapping in the popup.
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 @@ -11,6 +11,8 @@ def __init__(self) -> None:
self.sheets = sublime.load_resource("Packages/LSP/sheets.css")
self.sheets_classname = "lsp_sheet"
self.inlay_hints = sublime.load_resource("Packages/LSP/inlay_hints.css")
self.annotations = sublime.load_resource("Packages/LSP/annotations.css")
self.annotations_classname = "lsp_annotation"


_css = None # type: Optional[CSS]
Expand Down
2 changes: 1 addition & 1 deletion plugin/core/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,7 @@ def additional_variables(cls) -> Optional[Dict[str, str]]:
def storage_path(cls) -> str:
"""
The storage path. Use this as your base directory to install server files. Its path is '$DATA/Package Storage'.
You should have an additional subdirectory preferrably the same name as your plugin. For instance:
You should have an additional subdirectory preferably the same name as your plugin. For instance:

```python
from LSP.plugin import AbstractPlugin
Expand Down
8 changes: 5 additions & 3 deletions plugin/core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def debounced(f: Callable[[], Any], timeout_ms: int = 0, condition: Callable[[],

:param f: The function to possibly run. Its return type is discarded.
:param timeout_ms: The time in milliseconds after which to possibly to run the function
:param condition: The condition that must evaluate to True in order to run the funtion
:param condition: The condition that must evaluate to True in order to run the function
:param async_thread: If true, run the function on the async worker thread, otherwise run the function on the
main thread
"""
Expand Down Expand Up @@ -142,7 +142,7 @@ class DebouncerNonThreadSafe:
the callback function will only be called once, after `timeout_ms` since the last call.

This implementation is not thread safe. You must ensure that `debounce()` is called from the same thread as
was choosen during initialization through the `async_thread` argument.
was chosen during initialization through the `async_thread` argument.
"""

def __init__(self, async_thread: bool) -> None:
Expand All @@ -158,7 +158,7 @@ def debounce(

:param f: The function to possibly run
:param timeout_ms: The time in milliseconds after which to possibly to run the function
:param condition: The condition that must evaluate to True in order to run the funtion
:param condition: The condition that must evaluate to True in order to run the function
"""

def run(debounce_id: int) -> None:
Expand Down Expand Up @@ -212,6 +212,7 @@ class Settings:
semantic_highlighting = cast(bool, None)
show_code_actions = cast(str, None)
show_code_lens = cast(str, None)
show_diagnostics_annotations = cast(bool, None)
show_inlay_hints = cast(bool, None)
show_code_actions_in_hover = cast(bool, None)
show_diagnostics_count_in_view_status = cast(bool, None)
Expand Down Expand Up @@ -252,6 +253,7 @@ def r(name: str, default: Union[bool, int, str, list, dict]) -> None:
r("semantic_highlighting", False)
r("show_code_actions", "annotation")
r("show_code_lens", "annotation")
r("show_diagnostics_annotations", False)
r("show_inlay_hints", False)
r("show_code_actions_in_hover", True)
r("show_diagnostics_count_in_view_status", False)
Expand Down
19 changes: 18 additions & 1 deletion plugin/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -859,14 +859,31 @@ def diagnostic_severity(diagnostic: Diagnostic) -> DiagnosticSeverity:
return diagnostic.get("severity", DiagnosticSeverity.Error)


def format_diagnostics_for_annotation(
diagnostics: List[Diagnostic], severity: DiagnosticSeverity, view: sublime.View
) -> Tuple[List[str], str]:
css_class = DIAGNOSTIC_SEVERITY[severity - 1][1]
scope = DIAGNOSTIC_SEVERITY[severity - 1][2]
color = view.style_for_scope(scope).get('foreground') or 'red'
annotations = []
for diagnostic in diagnostics:
message = text2html(diagnostic.get('message') or '')
source = diagnostic.get('source')
line = "[{}] {}".format(text2html(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)
annotations.append(content)
return (annotations, color)


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.

:param diagnostic: The diagnostic
:returns: Tuple of (content, optional offset, optional code, optional href)
When the last three elements are optional, don't show an inline phantom
When the last three elemenst are not optional, show an inline phantom
When the last three elements are not optional, show an inline phantom
using the information given.
"""
formatted, code, href = diagnostic_source_and_code(diagnostic)
Expand Down
49 changes: 49 additions & 0 deletions plugin/diagnostics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from .core.protocol import Diagnostic
from .core.protocol import DiagnosticSeverity
from .core.typing import Dict, List, Tuple
from .core.views import DIAGNOSTIC_KINDS
from .core.views import diagnostic_severity
from .core.views import format_diagnostics_for_annotation
import sublime


class DiagnosticsAnnotationsView():
ANNOTATIONS_REGION_KEY = "lsp_d-annotations"

@classmethod
def initialize_region_keys(cls, view: sublime.View) -> None:
r = [sublime.Region(0, 0)]
for severity in DIAGNOSTIC_KINDS.keys():
view.add_regions(cls._annotation_key(severity), r)

@classmethod
def _annotation_key(cls, severity: DiagnosticSeverity) -> str:
return '{}-{}'.format(cls.ANNOTATIONS_REGION_KEY, severity)

def __init__(self, view: sublime.View) -> None:
self._view = view

def clear_annotations(self) -> None:
for severity in DIAGNOSTIC_KINDS.keys():
self._view.erase_regions(self._annotation_key(severity))

def update_diagnostic_annotations_async(self, diagnostics: List[Tuple[Diagnostic, sublime.Region]]) -> None:
# To achieve the correct order of annotations (most severe shown first) and have the color of annotation
# match the diagnostic severity, we have to separately add regions for each severity, from most to least severe.
diagnostics_per_severity = {} # type: Dict[DiagnosticSeverity, List[Tuple[Diagnostic, sublime.Region]]]
for severity in DIAGNOSTIC_KINDS.keys():
diagnostics_per_severity[severity] = []
for diagnostic, region in diagnostics:
diagnostics_per_severity[diagnostic_severity(diagnostic)].append((diagnostic, region))
flags = sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE
for severity, diagnostics in diagnostics_per_severity.items():
if not diagnostics:
continue
all_diagnostics = []
regions = []
for diagnostic, region in diagnostics:
all_diagnostics.append(diagnostic)
regions.append(region)
predragnikolic marked this conversation as resolved.
Show resolved Hide resolved
annotations, color = format_diagnostics_for_annotation(all_diagnostics, severity, self._view)
self._view.add_regions(
self._annotation_key(severity), regions, flags=flags, annotations=annotations, annotation_color=color)
14 changes: 11 additions & 3 deletions plugin/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from .core.views import text_document_position_params
from .core.views import update_lsp_popup
from .core.windows import WindowManager
from .diagnostics import DiagnosticsAnnotationsView
from .hover import code_actions_content
from .session_buffer import SessionBuffer
from .session_view import SessionView
Expand Down Expand Up @@ -154,6 +155,7 @@ def on_change() -> None:
self._registration = SettingsRegistration(view.settings(), on_change=on_change)
self._completions_task = None # type: Optional[QueryCompletionsTask]
self._stored_selection = [] # type: List[sublime.Region]
self._diagnostics_view = DiagnosticsAnnotationsView(self.view)
self._setup()

def __del__(self) -> None:
Expand Down Expand Up @@ -249,7 +251,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 @@ -285,6 +287,12 @@ def on_diagnostics_updated_async(self, is_view_visible: bool) -> None:
is_active_view = window and window.active_view() == self.view
if is_active_view and self.view.change_count() == self._change_count_on_last_save:
self._toggle_diagnostics_panel_if_needed_async()
self._diagnostics_view.clear_annotations()
if userprefs().show_diagnostics_annotations:
all_diagnostics = [] # type: List[Tuple[Diagnostic, sublime.Region]]
for _, diagnostics in self._diagnostics_async(allow_stale=True):
all_diagnostics.extend(diagnostics)
self._diagnostics_view.update_diagnostic_annotations_async(all_diagnostics)
rchl marked this conversation as resolved.
Show resolved Hide resolved

def _update_diagnostic_in_status_bar_async(self) -> None:
if userprefs().show_diagnostics_in_view_status:
Expand Down Expand Up @@ -354,7 +362,7 @@ def on_activated_async(self) -> None:
sb.do_inlay_hints_async(self.view)

def on_selection_modified_async(self) -> None:
first_region, any_different = self._update_stored_selection_async()
first_region, _ = self._update_stored_selection_async()
if first_region is None:
return
if not self._is_in_higlighted_region(first_region.b):
Expand Down Expand Up @@ -862,7 +870,7 @@ def _register_async(self) -> None:
def _on_view_updated_async(self) -> None:
self._code_lenses_debouncer_async.debounce(
self._do_code_lenses_async, timeout_ms=self.code_lenses_debounce_time)
first_region, any_different = self._update_stored_selection_async()
first_region, _ = self._update_stored_selection_async()
if first_region is None:
return
self._clear_highlight_regions()
Expand Down
2 changes: 1 addition & 1 deletion plugin/goto_diagnostic.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ def name(self) -> str:
return "diagnostic"

def list_items(self) -> List[sublime.ListInputItem]:
list_items = []
list_items = [] # type: List[sublime.ListInputItem]
max_severity = userprefs().diagnostics_panel_include_severity_level
for i, session in enumerate(self.sessions):
for diagnostic in filter(is_severity_included(max_severity),
Expand Down
5 changes: 5 additions & 0 deletions plugin/session_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .core.typing import Any, Iterable, List, Tuple, Optional, Dict, Generator
from .core.views import DIAGNOSTIC_SEVERITY
from .core.views import text_document_identifier
from .diagnostics import DiagnosticsAnnotationsView
from .session_buffer import SessionBuffer
from weakref import ref
from weakref import WeakValueDictionary
Expand Down Expand Up @@ -95,6 +96,9 @@ def on_before_remove(self) -> None:
self.view.erase_regions("{}_underline".format(self.diagnostics_key(severity, True)))
self.view.erase_regions("lsp_document_link")
self.session_buffer.remove_session_view(self)
listener = self.listener()
if listener:
listener.on_diagnostics_updated_async(False)

@property
def session(self) -> Session:
Expand Down Expand Up @@ -155,6 +159,7 @@ def _initialize_region_keys(self) -> None:
self.view.add_regions("lsp_highlight_{}{}".format(kind, mode), r)
if hover_highlight_style in ("underline", "stippled"):
self.view.add_regions(HOVER_HIGHLIGHT_KEY, r)
DiagnosticsAnnotationsView.initialize_region_keys(self.view)

def _clear_auto_complete_triggers(self, settings: sublime.Settings) -> None:
'''Remove all of our modifications to the view's "auto_complete_triggers"'''
Expand Down
5 changes: 5 additions & 0 deletions sublime-package.json
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,11 @@
"default": true,
"markdownDescription": "Show the diagnostics description of the code under the cursor in status bar if available."
},
"show_diagnostics_annotations": {
"default": false,
"type": "boolean",
"markdownDescription": "Show diagnostics as annotations anchored at the right side of the corresponding line. When enabled, it's recommended not to use the `\"annotation\"` value for the `show_code_actions` option as it's impossible to enforce which one gets shown first."
},
"show_diagnostics_severity_level": {
"type": "integer",
"default": 4,
Expand Down