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

Fix issues with diagnostics panel toggling on save #2063

Merged
merged 7 commits into from
Sep 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
T = TypeVar('T')


class DiagnosticsManager(OrderedDict):
# NOTE: OrderedDict can only be properly typed in Python >=3.8.
class DiagnosticsStorage(OrderedDict):
# From the specs:
#
# When a file changes it is the server’s responsibility to re-compute
Expand All @@ -34,8 +35,9 @@ def add_diagnostics_async(self, document_uri: DocumentUri, diagnostics: List[Dia
self[uri] = diagnostics
self.move_to_end(uri) # maintain incoming order

def filter_map_diagnostics_async(self, pred: Callable[[Diagnostic], bool],
f: Callable[[ParsedUri, Diagnostic], T]) -> Iterator[Tuple[ParsedUri, List[T]]]:
def filter_map_diagnostics_async(
self, pred: Callable[[Diagnostic], bool], f: Callable[[ParsedUri, Diagnostic], T]
) -> Iterator[Tuple[ParsedUri, List[T]]]:
"""
Yields `(uri, results)` items with `results` being a list of `f(diagnostic)` for each
diagnostic for this `uri` with `pred(diagnostic) == True`, filtered by `bool(f(diagnostic))`.
Expand Down
2 changes: 1 addition & 1 deletion plugin/core/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def navigate_diagnostics(view: sublime.View, point: Optional[int], forward: bool
return
diagnostics = [] # type: List[Diagnostic]
for session in windows.lookup(window).get_sessions():
diagnostics.extend(session.diagnostics_manager.diagnostics_by_document_uri(uri))
diagnostics.extend(session.diagnostics.diagnostics_by_document_uri(uri))
if not diagnostics:
return
# Sort diagnostics by location
Expand Down
27 changes: 10 additions & 17 deletions plugin/core/sessions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .collections import DottedDict
from .diagnostics_manager import DiagnosticsManager
from .diagnostics_storage import DiagnosticsStorage
from .edit import apply_edits
from .edit import parse_workspace_edit
from .edit import TextEditTuple
Expand All @@ -14,8 +14,6 @@
from .open import center_selection
from .open import open_externally
from .open import open_file
from .panels import is_panel_open
from .panels import PanelName
from .progress import WindowProgressReporter
from .promise import PackagedTask
from .promise import Promise
Expand Down Expand Up @@ -43,6 +41,7 @@
from .protocol import LSPObject
from .protocol import MarkupKind
from .protocol import Notification
from .protocol import PublishDiagnosticsParams
from .protocol import Range
from .protocol import Request
from .protocol import Response
Expand Down Expand Up @@ -186,7 +185,7 @@ def start_async(self, configuration: ClientConfig, initiating_view: sublime.View
pass

@abstractmethod
def update_diagnostics_panel_async(self) -> None:
def on_diagnostics_updated(self) -> None:
pass

@abstractmethod
Expand Down Expand Up @@ -613,12 +612,6 @@ def on_session_initialized_async(self, session: 'Session') -> None:
def on_session_shutdown_async(self, session: 'Session') -> None:
raise NotImplementedError()

@abstractmethod
def diagnostics_async(
self
) -> Iterable[Tuple['SessionBufferProtocol', List[Tuple[Diagnostic, sublime.Region]]]]:
raise NotImplementedError()

@abstractmethod
def diagnostics_intersecting_region_async(
self,
Expand Down Expand Up @@ -1140,6 +1133,7 @@ def __init__(self, manager: Manager, logger: Logger, workspace_folders: List[Wor
self.window = manager.window()
self.state = ClientStates.STARTING
self.capabilities = Capabilities()
self.diagnostics = DiagnosticsStorage()
self.exiting = False
self._registrations = {} # type: Dict[str, _RegistrationData]
self._init_callback = None # type: Optional[InitCallback]
Expand All @@ -1155,7 +1149,6 @@ 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()
self._semantic_tokens_map = get_semantic_tokens_map(config.semantic_tokens)

def __getattr__(self, name: str) -> Any:
Expand Down Expand Up @@ -1692,21 +1685,21 @@ def m_workspace_inlayHint_refresh(self, params: None, request_id: Any) -> None:
for sv in not_visible_session_views:
sv.session_buffer.set_inlay_hints_pending_refresh()

def m_textDocument_publishDiagnostics(self, params: Any) -> None:
def m_textDocument_publishDiagnostics(self, params: PublishDiagnosticsParams) -> None:
"""handles the textDocument/publishDiagnostics notification"""
uri = params["uri"]
mgr = self.manager()
if not mgr:
return
uri = params["uri"]
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"])
if is_panel_open(self.window, PanelName.Diagnostics):
mgr.update_diagnostics_panel_async()
diagnostics = params["diagnostics"]
self.diagnostics.add_diagnostics_async(uri, diagnostics)
mgr.on_diagnostics_updated()
sb = self.get_session_buffer_for_uri_async(uri)
if sb:
sb.on_diagnostics_async(params["diagnostics"], params.get("version"))
sb.on_diagnostics_async(diagnostics, params.get("version"))

def m_client_registerCapability(self, params: Any, request_id: Any) -> None:
"""handles the client/registerCapability request"""
Expand Down
25 changes: 14 additions & 11 deletions plugin/core/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from .configurations import ConfigManager
from .configurations import WindowConfigManager
from .diagnostics import ensure_diagnostics_panel
from .diagnostics_manager import is_severity_included
from .diagnostics_storage import is_severity_included
from .logging import debug
from .logging import exception_log
from .message_request_handler import MessageRequestHandler
Expand Down Expand Up @@ -80,7 +80,6 @@ def __init__(
self._listeners = WeakSet() # type: WeakSet[AbstractViewListener]
self._new_listener = None # type: Optional[AbstractViewListener]
self._new_session = None # type: Optional[Session]
self._diagnostic_phantom_set = None # type: Optional[sublime.PhantomSet]
self._panel_code_phantoms = None # type: Optional[sublime.PhantomSet]
self.total_error_count = 0
self.total_warning_count = 0
Expand Down Expand Up @@ -413,21 +412,27 @@ def handle_stderr_log(self, session: Session, message: str) -> None:
def handle_show_message(self, session: Session, params: Any) -> None:
sublime.status_message("{}: {}".format(session.config.name, extract_message(params)))

def update_diagnostics_panel_async(self) -> None:
to_render = [] # type: List[str]
def on_diagnostics_updated(self) -> None:
self.total_error_count = 0
self.total_warning_count = 0
listeners = list(self._listeners)
for session in self._sessions:
local_errors, local_warnings = session.diagnostics.sum_total_errors_and_warnings_async()
self.total_error_count += local_errors
self.total_warning_count += local_warnings
for listener in list(self._listeners):
set_diagnostics_count(listener.view, self.total_error_count, self.total_warning_count)
if is_panel_open(self._window, PanelName.Diagnostics):
self.update_diagnostics_panel_async()

def update_diagnostics_panel_async(self) -> None:
to_render = [] # type: List[str]
prephantoms = [] # type: List[Tuple[int, int, str, str]]
row = 0
max_severity = userprefs().diagnostics_panel_include_severity_level
contributions = OrderedDict(
) # type: OrderedDict[str, List[Tuple[str, Optional[int], Optional[str], Optional[str]]]]
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
for (_, path), contribution in session.diagnostics_manager.filter_map_diagnostics_async(
for (_, path), contribution in session.diagnostics.filter_map_diagnostics_async(
is_severity_included(max_severity), lambda _, diagnostic: format_diagnostic_for_panel(diagnostic)):
seen = path in contributions
contributions.setdefault(path, []).extend(contribution)
Expand All @@ -443,8 +448,6 @@ def update_diagnostics_panel_async(self) -> None:
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
Expand Down
40 changes: 25 additions & 15 deletions plugin/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ def on_change() -> None:
else:
self.set_uri(view_to_uri(view))
self._auto_complete_triggered_manually = False
self._change_count_on_last_save = -1
self._registration = SettingsRegistration(view.settings(), on_change=on_change)
self._setup()

Expand Down Expand Up @@ -234,13 +235,12 @@ 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_async(
self
def _diagnostics_async(
self, allow_stale: bool = False
predragnikolic marked this conversation as resolved.
Show resolved Hide resolved
) -> Generator[Tuple[SessionBufferProtocol, List[Tuple[Diagnostic, sublime.Region]]], None, None]:
change_count = self.view.change_count()
for sb in self.session_buffers_async():
# do not provide stale diagnostics
if sb.diagnostics_version == change_count:
if sb.diagnostics_version == change_count or allow_stale:
yield sb, sb.diagnostics

def diagnostics_intersecting_region_async(
Expand All @@ -249,7 +249,7 @@ def diagnostics_intersecting_region_async(
) -> Tuple[List[Tuple[SessionBufferProtocol, List[Diagnostic]]], sublime.Region]:
covering = sublime.Region(region.begin(), region.end())
result = [] # type: List[Tuple[SessionBufferProtocol, List[Diagnostic]]]
for sb, diagnostics in self.diagnostics_async():
for sb, diagnostics in self._diagnostics_async():
intersections = [] # type: List[Diagnostic]
for diagnostic, candidate in diagnostics:
# Checking against points is inclusive unlike checking whether region intersects another
Expand All @@ -268,7 +268,7 @@ def diagnostics_touching_point_async(
) -> Tuple[List[Tuple[SessionBufferProtocol, List[Diagnostic]]], sublime.Region]:
covering = sublime.Region(pt, pt)
result = [] # type: List[Tuple[SessionBufferProtocol, List[Diagnostic]]]
for sb, diagnostics in self.diagnostics_async():
for sb, diagnostics in self._diagnostics_async():
intersections = [] # type: List[Diagnostic]
for diagnostic, candidate in diagnostics:
severity = diagnostic_severity(diagnostic)
Expand All @@ -286,6 +286,8 @@ def on_diagnostics_updated_async(self) -> None:
if userprefs().show_code_actions:
self._do_code_actions()
self._update_diagnostic_in_status_bar_async()
if self.view.change_count() == self._change_count_on_last_save:
self._toggle_diagnostics_panel_if_needed_async()

def _update_diagnostic_in_status_bar_async(self) -> None:
if userprefs().show_diagnostics_in_view_status:
Expand Down Expand Up @@ -387,19 +389,27 @@ def on_post_save_async(self) -> None:
# The URI scheme has changed. This means we need to re-determine whether any language servers should
# be attached to the view.
sublime.set_timeout(self._reset)
window = self.view.window()
if window and userprefs().show_diagnostics_panel_on_save > 0 and is_panel_open(window, PanelName.Diagnostics):
self._hide_diagnostics_panel_if_empty()
self._change_count_on_last_save = self.view.change_count()
self._toggle_diagnostics_panel_if_needed_async()

def _hide_diagnostics_panel_if_empty(self) -> None:
def _toggle_diagnostics_panel_if_needed_async(self) -> None:
severity_threshold = userprefs().show_diagnostics_panel_on_save
hide_panel = True
for _, diagnostics in self.diagnostics_async():
if severity_threshold == 0:
return
window = self.view.window()
if not window or not self._manager:
return
has_relevant_diagnostcs = False
for _, diagnostics in self._diagnostics_async(allow_stale=True):
if any(diagnostic_severity(diagnostic) <= severity_threshold for diagnostic, _ in diagnostics):
hide_panel = False
has_relevant_diagnostcs = True
break
if hide_panel and self._manager:
self._manager.hide_diagnostics_panel_async()
if is_panel_open(window, PanelName.Diagnostics):
if not has_relevant_diagnostcs:
self._manager.hide_diagnostics_panel_async()
else:
if has_relevant_diagnostcs:
self._manager.show_diagnostics_panel_async()

def on_close(self) -> None:
if self._registered and self._manager:
Expand Down
12 changes: 6 additions & 6 deletions plugin/goto_diagnostic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .core.diagnostics_manager import ParsedUri, is_severity_included
from .core.diagnostics_storage import ParsedUri, is_severity_included
from .core.protocol import Diagnostic, DocumentUri, DiagnosticSeverity, Location
from .core.registry import windows
from .core.sessions import Session
Expand Down Expand Up @@ -48,8 +48,8 @@ def is_enabled(self, uri: Optional[DocumentUri] = None, diagnostic: Optional[dic
return False
if uri:
parsed_uri = parse_uri(uri)
return any(parsed_uri in session.diagnostics_manager for session in get_sessions(self.window))
return any(bool(session.diagnostics_manager) for session in get_sessions(self.window))
return any(parsed_uri in session.diagnostics for session in get_sessions(self.window))
return any(bool(session.diagnostics) for session in get_sessions(self.window))

def input(self, args: dict) -> Optional[sublime_plugin.CommandInputHandler]:
uri, diagnostic = args.get("uri"), args.get("diagnostic")
Expand Down Expand Up @@ -88,12 +88,12 @@ def list_items(self) -> Tuple[List[sublime.ListInputItem], int]:
severities_per_path = OrderedDict() # type: OrderedDict[ParsedUri, List[DiagnosticSeverity]]
self.first_locations = dict() # type: Dict[ParsedUri, Tuple[Session, Location]]
for session in get_sessions(self.window):
for parsed_uri, severity in session.diagnostics_manager.filter_map_diagnostics_flat_async(
for parsed_uri, severity in session.diagnostics.filter_map_diagnostics_flat_async(
is_severity_included(max_severity), lambda _, diagnostic: diagnostic_severity(diagnostic)):
severities_per_path.setdefault(parsed_uri, []).append(severity)
if parsed_uri not in self.first_locations:
severities_per_path.move_to_end(parsed_uri)
diagnostics = session.diagnostics_manager.diagnostics_by_parsed_uri(parsed_uri)
diagnostics = session.diagnostics.diagnostics_by_parsed_uri(parsed_uri)
if diagnostics:
self.first_locations[parsed_uri] = session, diagnostic_location(parsed_uri, diagnostics[0])
# build items
Expand Down Expand Up @@ -174,7 +174,7 @@ def list_items(self) -> 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),
session.diagnostics_manager.diagnostics_by_parsed_uri(self.parsed_uri)):
session.diagnostics.diagnostics_by_parsed_uri(self.parsed_uri)):
lines = diagnostic["message"].splitlines()
first_line = lines[0] if lines else ""
if len(lines) > 1:
Expand Down
Loading