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

Moved diagnostic panel contributions from SessionBuffer to Session (#1868) #1881

Merged
merged 8 commits into from
Oct 25, 2021
60 changes: 60 additions & 0 deletions plugin/core/diagnostics_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from .protocol import Diagnostic, DiagnosticSeverity
from .settings import userprefs
from .typing import Callable, Iterator, List, Optional, Tuple
from .url import parse_uri
from .views import diagnostic_severity, format_diagnostic_for_panel
from collections import OrderedDict


class DiagnosticsManager(OrderedDict):
# From the specs:
#
# When a file changes it is the server’s responsibility to re-compute
# diagnostics and push them to the client. If the computed set is empty
# it has to push the empty array to clear former diagnostics. Newly
# pushed diagnostics always replace previously pushed diagnostics. There
# is no merging that happens on the client side.
#
# https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics

def add_diagnostics_async(self, uri: str, diagnostics: List[Diagnostic]) -> None:
_, path = parse_uri(uri)
if not diagnostics:
# received "clear diagnostics" message for this path
self.pop(path, None)
return
max_severity = userprefs().diagnostics_panel_include_severity_level
self[path] = (
list(
filter(
None,
(
format_diagnostic_for_panel(diagnostic)
for diagnostic in diagnostics
if diagnostic_severity(diagnostic) <= max_severity
),
)
),
len(list(filter(has_severity(DiagnosticSeverity.Error), diagnostics))),
len(list(filter(has_severity(DiagnosticSeverity.Warning), diagnostics))),
)
self.move_to_end(path) # maintain incoming order

def diagnostics_panel_contributions_async(
self,
) -> Iterator[Tuple[str, List[Tuple[str, Optional[int], Optional[str], Optional[str]]]]]:
for path, (contribution, _, _) in self.items():
if contribution:
yield path, contribution

def sum_total_errors_and_warnings_async(self) -> Tuple[int, int]:
return (
sum(errors for _, errors, _ in self.values()),
sum(warnings for _, _, warnings in self.values()),
)


def has_severity(severity: int) -> Callable[[Diagnostic], bool]:
def has_severity(diagnostic: Diagnostic) -> bool:
return diagnostic_severity(diagnostic) == severity
return has_severity
4 changes: 4 additions & 0 deletions plugin/core/sessions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .collections import DottedDict
from .diagnostics_manager import DiagnosticsManager
from .edit import apply_workspace_edit
from .edit import parse_workspace_edit
from .file_watcher import DEFAULT_KIND
Expand Down Expand Up @@ -886,6 +887,7 @@ def __init__(self, manager: Manager, logger: Logger, workspace_folders: List[Wor
self._plugin_class = plugin_class
self._plugin = None # type: Optional[AbstractPlugin]
self._status_messages = {} # type: Dict[str, str]
self.diagnostics_manager = DiagnosticsManager()

def __getattr__(self, name: str) -> Any:
"""
Expand Down Expand Up @@ -1342,6 +1344,8 @@ def m_textDocument_publishDiagnostics(self, params: Any) -> None:
reason = mgr.should_present_diagnostics(uri)
if isinstance(reason, str):
return debug("ignoring unsuitable diagnostics for", uri, "reason:", reason)
self.diagnostics_manager.add_diagnostics_async(uri, params["diagnostics"])
mgr.update_diagnostics_panel_async()
sb = self.get_session_buffer_for_uri_async(uri)
if sb:
sb.on_diagnostics_async(params["diagnostics"], params.get("version"))
Expand Down
50 changes: 15 additions & 35 deletions plugin/core/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
from weakref import WeakSet
import functools
import json
import os
import sublime
import threading
import urllib.parse
Expand Down Expand Up @@ -102,14 +101,6 @@ def diagnostics_intersecting_async(
else:
return self.diagnostics_intersecting_region_async(region_or_point)

@abstractmethod
def diagnostics_panel_contribution_async(self) -> Sequence[Tuple[str, Optional[int], Optional[str], Optional[str]]]:
raise NotImplementedError()

@abstractmethod
def sum_total_errors_and_warnings_async(self) -> Tuple[int, int]:
raise NotImplementedError()

@abstractmethod
def on_diagnostics_updated_async(self) -> None:
raise NotImplementedError()
Expand Down Expand Up @@ -176,7 +167,7 @@ def __init__(
self._panel_code_phantoms = None # type: Optional[sublime.PhantomSet]
self.total_error_count = 0
self.total_warning_count = 0
sublime.set_timeout(functools.partial(self._update_panel_main_thread, None, _NO_DIAGNOSTICS_PLACEHOLDER, []))
sublime.set_timeout(functools.partial(self._update_panel_main_thread, _NO_DIAGNOSTICS_PLACEHOLDER, []))

def get_config_manager(self) -> WindowConfigManager:
return self._configs
Expand Down Expand Up @@ -508,47 +499,36 @@ def handle_show_message(self, session: Session, params: Any) -> None:

def update_diagnostics_panel_async(self) -> None:
to_render = [] # type: List[str]
base_dir = None
self.total_error_count = 0
self.total_warning_count = 0
listeners = list(self._listeners)
rchl marked this conversation as resolved.
Show resolved Hide resolved
prephantoms = [] # type: List[Tuple[int, int, str, str]]
row = 0
for listener in listeners:
local_errors, local_warnings = listener.sum_total_errors_and_warnings_async()
for session in self._sessions:
local_errors, local_warnings = session.diagnostics_manager.sum_total_errors_and_warnings_async()
self.total_error_count += local_errors
self.total_warning_count += local_warnings
contribution = listener.diagnostics_panel_contribution_async()
if not contribution:
continue
file_path = listener.view.file_name() or ""
base_dir = self.get_project_path(file_path) # What about different base dirs for multiple folders?
file_path = os.path.relpath(file_path, base_dir) if base_dir else file_path
to_render.append("{}:".format(file_path))
row += 1
for content, offset, code, href in contribution:
to_render.append(content)
if offset is not None and code is not None and href is not None:
prephantoms.append((row, offset, code, href))
row += content.count("\n") + 1
to_render.append("") # add spacing between filenames
row += 1
for path, contribution in session.diagnostics_manager.diagnostics_panel_contributions_async():
to_render.append("{}:".format(path))
row += 1
for content, offset, code, href in contribution:
to_render.append(content)
if offset is not None and code is not None and href is not None:
prephantoms.append((row, offset, code, href))
row += content.count("\n") + 1
to_render.append("") # add spacing between filenames
row += 1
for listener in listeners:
set_diagnostics_count(listener.view, self.total_error_count, self.total_warning_count)
characters = "\n".join(to_render)
if not characters:
characters = _NO_DIAGNOSTICS_PLACEHOLDER
sublime.set_timeout(functools.partial(self._update_panel_main_thread, base_dir, characters, prephantoms))
sublime.set_timeout(functools.partial(self._update_panel_main_thread, characters, prephantoms))

def _update_panel_main_thread(self, base_dir: Optional[str], characters: str,
prephantoms: List[Tuple[int, int, str, str]]) -> None:
def _update_panel_main_thread(self, characters: str, prephantoms: List[Tuple[int, int, str, str]]) -> None:
panel = ensure_diagnostics_panel(self._window)
if not panel or not panel.is_valid():
return
if isinstance(base_dir, str):
panel.settings().set("result_base_dir", base_dir)
else:
panel.settings().erase("result_base_dir")
panel.run_command("lsp_update_panel", {"characters": characters})
if self._panel_code_phantoms is None:
self._panel_code_phantoms = sublime.PhantomSet(panel, "hrefs")
Expand Down
23 changes: 0 additions & 23 deletions plugin/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
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 document_color_params
from .core.views import first_selection_region
Expand Down Expand Up @@ -238,20 +237,6 @@ def on_session_shutdown_async(self, session: Session) -> None:
# SessionView was likely not created for this config so remove status here.
session.config.erase_view_status(self.view)

def diagnostics_panel_contribution_async(self) -> List[Tuple[str, Optional[int], Optional[str], Optional[str]]]:
result = [] # type: List[Tuple[str, Optional[int], Optional[str], Optional[str]]]
# Sort by severity
for severity in range(1, len(DIAGNOSTIC_SEVERITY) + 1):
for sb in self.session_buffers_async():
data = sb.data_per_severity.get((severity, False))
if data:
result.extend(data.panel_contribution)
data = sb.data_per_severity.get((severity, True))
if data:
result.extend(data.panel_contribution)
# sort the result by asc line number
return sorted(result)

def diagnostics_async(
self
) -> Generator[Tuple[SessionBuffer, List[Tuple[Diagnostic, sublime.Region]]], None, None]:
Expand Down Expand Up @@ -739,14 +724,6 @@ def trigger_on_pre_save_async(self) -> None:
for sv in self.session_views_async():
sv.on_pre_save_async()

def sum_total_errors_and_warnings_async(self) -> Tuple[int, int]:
errors = 0
warnings = 0
for sb in self.session_buffers_async():
errors += sb.total_errors
warnings += sb.total_warnings
return errors, warnings

def revert_async(self) -> None:
if self.view.is_primary():
for sv in self.session_views_async():
Expand Down
9 changes: 1 addition & 8 deletions plugin/session_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from .core.views import did_close
from .core.views import did_open
from .core.views import did_save
from .core.views import format_diagnostic_for_panel
from .core.views import MissingUriError
from .core.views import range_to_region
from .core.views import will_save
Expand All @@ -41,13 +40,12 @@ def update(self, version: int, changes: Iterable[sublime.TextChange]) -> None:

class DiagnosticSeverityData:

__slots__ = ('regions', 'regions_with_tag', 'annotations', 'panel_contribution', 'scope', 'icon')
__slots__ = ('regions', 'regions_with_tag', 'annotations', 'scope', 'icon')

def __init__(self, severity: int) -> None:
self.regions = [] # type: List[sublime.Region]
self.regions_with_tag = {} # type: Dict[int, List[sublime.Region]]
self.annotations = [] # type: List[str]
self.panel_contribution = [] # type: List[Tuple[str, Optional[int], Optional[str], Optional[str]]]
_, _, self.scope, self.icon, _, _ = DIAGNOSTIC_SEVERITY[severity - 1]
if userprefs().diagnostics_gutter_marker != "sign":
self.icon = userprefs().diagnostics_gutter_marker
Expand Down Expand Up @@ -300,8 +298,6 @@ def on_diagnostics_async(self, raw_diagnostics: List[Diagnostic], version: Optio
total_errors += 1
elif severity == DiagnosticSeverity.Warning:
total_warnings += 1
if severity <= userprefs().diagnostics_panel_include_severity_level:
data.panel_contribution.append(format_diagnostic_for_panel(diagnostic))
rchl marked this conversation as resolved.
Show resolved Hide resolved
if severity <= userprefs().show_diagnostics_panel_on_save:
should_show_diagnostics_panel = True
self._publish_diagnostics_to_session_views(
Expand Down Expand Up @@ -374,9 +370,6 @@ def _present_diagnostics_async(
self.should_show_diagnostics_panel = should_show_diagnostics_panel
for sv in self.session_views:
sv.present_diagnostics_async()
mgr = self.session.manager()
if mgr:
mgr.update_diagnostics_panel_async()

def __str__(self) -> str:
return '{}:{}:{}'.format(self.session.config.name, self.id, self.get_uri())