From 18db9d08dc5f60875b6a7c176d593f8f949b5732 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Sun, 14 Apr 2024 18:27:03 +0200 Subject: [PATCH 01/10] Don't restart servers when userprefs changes --- plugin/core/sessions.py | 14 +++++++++++++- plugin/core/settings.py | 30 ++++++++++++++++++++++++------ plugin/core/types.py | 2 ++ plugin/core/windows.py | 17 ++++++++++++++++- plugin/session_buffer.py | 22 ++++++++++++++++------ plugin/session_view.py | 20 +++++++++++--------- 6 files changed, 82 insertions(+), 23 deletions(-) diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index 0f660af8c..2aa190246 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -568,6 +568,9 @@ def present_diagnostics_async( ) -> None: ... + def redraw_diagnostics_async(self) -> None: + ... + def on_request_started_async(self, request_id: int, request: Request) -> None: ... @@ -650,6 +653,9 @@ def get_document_link_at_point(self, view: sublime.View, point: int) -> Optional def update_document_link(self, new_link: DocumentLink) -> None: ... + def redraw_document_links_async(self) -> None: + ... + def do_semantic_tokens_async(self, view: sublime.View) -> None: ... @@ -659,6 +665,9 @@ def set_semantic_tokens_pending_refresh(self, needs_refresh: bool = True) -> Non def get_semantic_tokens(self) -> List[Any]: ... + def clear_semantic_tokens_async(self) -> None: + ... + def do_inlay_hints_async(self, view: sublime.View) -> None: ... @@ -1322,8 +1331,11 @@ def set_config_status_async(self, message: str) -> None: :param message: The message """ self.config_status_message = message.strip() + self.redraw_config_status_async() + + def redraw_config_status_async(self) -> None: for sv in self.session_views_async(): - self.config.set_view_status(sv.view, message) + self.config.set_view_status(sv.view, self.config_status_message) def set_window_status_async(self, key: str, message: str) -> None: self._status_messages[key] = message diff --git a/plugin/core/settings.py b/plugin/core/settings.py index 319de6e55..45f6cd268 100644 --- a/plugin/core/settings.py +++ b/plugin/core/settings.py @@ -5,6 +5,7 @@ from .types import Settings from .types import SettingsRegistration from .typing import Any, Optional, Dict, Callable +import json import os import sublime @@ -14,11 +15,17 @@ class ClientConfigs: def __init__(self) -> None: self.all = {} # type: Dict[str, ClientConfig] self.external = {} # type: Dict[str, ClientConfig] - self._listener = None # type: Optional[Callable[[Optional[str]], None]] + self._clients_listener = None # type: Optional[Callable[[Optional[str]], None]] + self._userprefs_listener = None # type: Optional[Callable[[], None]] + self._clients_hash = None # type: Optional[int] def _notify_listener(self, config_name: Optional[str] = None) -> None: - if callable(self._listener): - self._listener(config_name) + if callable(self._clients_listener): + self._clients_listener(config_name) + + def _notify_userprefs_listener(self) -> None: + if callable(self._userprefs_listener): + self._userprefs_listener() def add_for_testing(self, config: ClientConfig) -> None: assert config.name not in self.all @@ -71,8 +78,14 @@ def update_configs(self) -> None: global _settings_obj if _settings_obj is None: return + clients_dict = read_dict_setting(_settings_obj, "clients", {}) + _clients_hash = hash(json.dumps(clients_dict, sort_keys=True)) + if _clients_hash == self._clients_hash: + self._notify_userprefs_listener() + return + self._clients_hash = _clients_hash clients = DottedDict(read_dict_setting(_settings_obj, "default_clients", {})) - clients.update(read_dict_setting(_settings_obj, "clients", {})) + clients.update(clients_dict) self.all.clear() self.all.update({name: ClientConfig.from_dict(name, d) for name, d in clients.get().items()}) self.all.update(self.external) @@ -103,8 +116,13 @@ def enable(self, config_name: str) -> None: def disable(self, config_name: str) -> None: self._set_enabled(config_name, False) - def set_listener(self, recipient: Callable[[Optional[str]], None]) -> None: - self._listener = recipient + def set_listeners( + self, + clients_listener: Callable[[Optional[str]], None], + userprefs_listener: Callable[[], None] + ) -> None: + self._clients_listener = clients_listener + self._userprefs_listener = userprefs_listener _settings_obj = None # type: Optional[sublime.Settings] diff --git a/plugin/core/types.py b/plugin/core/types.py index 06710a094..0e1574fe9 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -832,6 +832,8 @@ def set_view_status(self, view: sublime.View, message: str) -> None: if sublime.load_settings("LSP.sublime-settings").get("show_view_status"): status = "{} ({})".format(self.name, message) if message else self.name view.set_status(self.status_key, status) + else: + self.erase_view_status(view) def erase_view_status(self, view: sublime.View) -> None: view.erase_status(self.status_key) diff --git a/plugin/core/windows.py b/plugin/core/windows.py index 662572e78..d3ef0f736 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -522,12 +522,27 @@ class WindowRegistry: def __init__(self) -> None: self._enabled = False self._windows = {} # type: Dict[int, WindowManager] - client_configs.set_listener(self._on_client_config_updated) + client_configs.set_listeners(self._on_client_config_updated, self._on_userprefs_updated) def _on_client_config_updated(self, config_name: Optional[str] = None) -> None: for wm in self._windows.values(): wm.get_config_manager().update(config_name) + def _on_userprefs_updated(self) -> None: + sublime.set_timeout_async(self._on_userprefs_updated_async) + + def _on_userprefs_updated_async(self) -> None: + for wm in self._windows.values(): + wm.on_diagnostics_updated() + for session in wm.get_sessions(): + session.redraw_config_status_async() + for sb in session.session_buffers_async(): + sb.redraw_document_links_async() + if not userprefs().semantic_highlighting: + sb.clear_semantic_tokens_async() + for sv in session.session_views_async(): + sv.redraw_diagnostics_async() + def enable(self) -> None: self._enabled = True # Initialize manually at plugin_loaded as we'll miss out on "on_new_window_async" events. diff --git a/plugin/session_buffer.py b/plugin/session_buffer.py index 71febb56b..1b9d6f8bf 100644 --- a/plugin/session_buffer.py +++ b/plugin/session_buffer.py @@ -418,14 +418,20 @@ def _do_document_link_async(self, view: sublime.View, version: int) -> None: def _on_document_link_async(self, view: sublime.View, response: Optional[List[DocumentLink]]) -> None: self._document_links = response or [] + self.redraw_document_links_async() + + def redraw_document_links_async(self) -> None: if self._document_links and userprefs().link_highlight_style == "underline": - view.add_regions( - "lsp_document_link", - [range_to_region(link["range"], view) for link in self._document_links], - scope="markup.underline.link.lsp", - flags=DOCUMENT_LINK_FLAGS) + view = self.some_view() + if not view: + return + regions = [range_to_region(link["range"], view) for link in self._document_links] + for sv in self.session_views: + sv.view.add_regions( + "lsp_document_link", regions, scope="markup.underline.link.lsp", flags=DOCUMENT_LINK_FLAGS) else: - view.erase_regions("lsp_document_link") + for sv in self.session_views: + sv.view.erase_regions("lsp_document_link") def get_document_link_at_point(self, view: sublime.View, point: int) -> Optional[DocumentLink]: for link in self._document_links: @@ -692,6 +698,10 @@ def set_semantic_tokens_pending_refresh(self, needs_refresh: bool = True) -> Non def get_semantic_tokens(self) -> List[SemanticToken]: return self.semantic_tokens.tokens + def clear_semantic_tokens_async(self) -> None: + for sv in self.session_views: + self._clear_semantic_token_regions(sv.view) + # --- textDocument/inlayHint ---------------------------------------------------------------------------------- def do_inlay_hints_async(self, view: sublime.View) -> None: diff --git a/plugin/session_view.py b/plugin/session_view.py index 3736fe9a4..5007e5e4e 100644 --- a/plugin/session_view.py +++ b/plugin/session_view.py @@ -55,6 +55,7 @@ class SessionView: def __init__(self, listener: AbstractViewListener, session: Session, uri: DocumentUri) -> None: self._view = listener.view self._session = session + self._diagnostics_data_per_severity = {} # type: Dict[Tuple[int, bool], DiagnosticSeverityData] self._diagnostic_annotations = DiagnosticsAnnotationsView(self._view, session.config.name) self._initialize_region_keys() self._active_requests = {} # type: Dict[int, ActiveRequest] @@ -300,22 +301,23 @@ def diagnostics_tag_scope(self, tag: int) -> Optional[str]: def present_diagnostics_async( self, is_view_visible: bool, data_per_severity: Dict[Tuple[int, bool], DiagnosticSeverityData] ) -> None: + self._diagnostics_data_per_severity = data_per_severity + self.redraw_diagnostics_async() + listener = self.listener() + if listener: + listener.on_diagnostics_updated_async(is_view_visible) + + def redraw_diagnostics_async(self) -> None: flags = userprefs().diagnostics_highlight_style_flags() # for single lines multiline_flags = None if userprefs().show_multiline_diagnostics_highlights else sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE | sublime.NO_UNDO # noqa: E501 level = userprefs().show_diagnostics_severity_level for sev in reversed(range(1, len(DIAGNOSTIC_SEVERITY) + 1)): - self._draw_diagnostics( - data_per_severity, sev, level, flags[sev - 1] or DIAGNOSTIC_SEVERITY[sev - 1][4], multiline=False) - self._draw_diagnostics( - data_per_severity, sev, level, multiline_flags or DIAGNOSTIC_SEVERITY[sev - 1][5], multiline=True) + self._draw_diagnostics(sev, level, flags[sev - 1] or DIAGNOSTIC_SEVERITY[sev - 1][4], multiline=False) + self._draw_diagnostics(sev, level, multiline_flags or DIAGNOSTIC_SEVERITY[sev - 1][5], multiline=True) self._diagnostic_annotations.draw(self.session_buffer.diagnostics) - listener = self.listener() - if listener: - listener.on_diagnostics_updated_async(is_view_visible) def _draw_diagnostics( self, - data_per_severity: Dict[Tuple[int, bool], DiagnosticSeverityData], severity: int, max_severity_level: int, flags: int, @@ -324,7 +326,7 @@ def _draw_diagnostics( ICON_FLAGS = sublime.HIDE_ON_MINIMAP | sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE | sublime.NO_UNDO key = self.diagnostics_key(severity, multiline) tags = {tag: TagData('{}_tags_{}'.format(key, tag)) for tag in DIAGNOSTIC_TAG_VALUES} - data = data_per_severity.get((severity, multiline)) + data = self._diagnostics_data_per_severity.get((severity, multiline)) if data and severity <= max_severity_level: non_tag_regions = data.regions for tag, regions in data.regions_with_tag.items(): From 4f573035e7f2b8b941a36145f5e305c0a2a9ed13 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Thu, 25 Apr 2024 11:13:40 +0200 Subject: [PATCH 02/10] Resolve more merge conflicts --- plugin/core/settings.py | 2 +- plugin/session_view.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/core/settings.py b/plugin/core/settings.py index 692ef310e..4a68a518d 100644 --- a/plugin/core/settings.py +++ b/plugin/core/settings.py @@ -119,7 +119,7 @@ def disable(self, config_name: str) -> None: def set_listeners( self, - clients_listener: Callable[[str | None], None] + clients_listener: Callable[[str | None], None], userprefs_listener: Callable[[], None] ) -> None: self._clients_listener = clients_listener diff --git a/plugin/session_view.py b/plugin/session_view.py index c07f856f9..4c03c604b 100644 --- a/plugin/session_view.py +++ b/plugin/session_view.py @@ -56,7 +56,7 @@ class SessionView: def __init__(self, listener: AbstractViewListener, session: Session, uri: DocumentUri) -> None: self._view = listener.view self._session = session - self._diagnostics_data_per_severity = {} # type: Dict[Tuple[int, bool], DiagnosticSeverityData] + self._diagnostics_data_per_severity: dict[tuple[int, bool], DiagnosticSeverityData] = {} self._diagnostic_annotations = DiagnosticsAnnotationsView(self._view, session.config.name) self._initialize_region_keys() self._active_requests: dict[int, ActiveRequest] = {} From 3dd85ec876ab1ccf2288cff8a22f5b951a88fc0e Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Mon, 23 Sep 2024 14:23:46 +0200 Subject: [PATCH 03/10] Rename method --- plugin/core/settings.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugin/core/settings.py b/plugin/core/settings.py index 4a68a518d..24660506c 100644 --- a/plugin/core/settings.py +++ b/plugin/core/settings.py @@ -20,7 +20,7 @@ def __init__(self) -> None: self._userprefs_listener: Callable[[], None] | None = None self._clients_hash: int | None = None - def _notify_listener(self, config_name: str | None = None) -> None: + def _notify_clients_listener(self, config_name: str | None = None) -> None: if callable(self._clients_listener): self._clients_listener(config_name) @@ -31,11 +31,11 @@ def _notify_userprefs_listener(self) -> None: def add_for_testing(self, config: ClientConfig) -> None: assert config.name not in self.all self.all[config.name] = config - self._notify_listener() + self._notify_clients_listener() def remove_for_testing(self, config: ClientConfig) -> None: self.all.pop(config.name) - self._notify_listener() + self._notify_clients_listener() def add_external_config(self, name: str, s: sublime.Settings, file: str, notify_listener: bool) -> bool: if name in self.external: @@ -56,13 +56,13 @@ def add_external_config(self, name: str, s: sublime.Settings, file: str, notify_ # That causes many calls to WindowConfigManager.match_view, which is relatively speaking an expensive # operation. To ensure that this dance is done only once, we delay notifying the WindowConfigManager until # all plugins have done their `register_plugin` call. - debounced(lambda: self._notify_listener(name), 200, lambda: len(self.external) == size) + debounced(lambda: self._notify_clients_listener(name), 200, lambda: len(self.external) == size) return True def remove_external_config(self, name: str) -> None: self.external.pop(name, None) if self.all.pop(name, None): - self._notify_listener() + self._notify_clients_listener() def update_external_config(self, name: str, s: sublime.Settings, file: str) -> None: try: @@ -73,7 +73,7 @@ def update_external_config(self, name: str, s: sublime.Settings, file: str) -> N return self.external[name] = config self.all[name] = config - self._notify_listener(name) + self._notify_clients_listener(name) def update_configs(self) -> None: global _settings_obj @@ -92,7 +92,7 @@ def update_configs(self) -> None: self.all.update(self.external) debug("enabled configs:", ", ".join(sorted(c.name for c in self.all.values() if c.enabled))) debug("disabled configs:", ", ".join(sorted(c.name for c in self.all.values() if not c.enabled))) - self._notify_listener() + self._notify_clients_listener() def _set_enabled(self, config_name: str, is_enabled: bool) -> None: from .sessions import get_plugin From b2bab2f146c06da56f19a80ac574c4c3ac223005 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Mon, 23 Sep 2024 14:44:05 +0200 Subject: [PATCH 04/10] Store diagnostics data in SessionBuffer --- plugin/core/sessions.py | 4 +--- plugin/session_buffer.py | 4 +++- plugin/session_view.py | 8 ++------ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index fe3e7d44a..0ed01e315 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -577,9 +577,7 @@ def has_capability_async(self, capability_path: str) -> bool: def shutdown_async(self) -> None: ... - def present_diagnostics_async( - self, is_view_visible: bool, data_per_severity: dict[tuple[int, bool], DiagnosticSeverityData] - ) -> None: + def present_diagnostics_async(self, is_view_visible: bool) -> None: ... def redraw_diagnostics_async(self) -> None: diff --git a/plugin/session_buffer.py b/plugin/session_buffer.py index 38123be04..5bdb7213d 100644 --- a/plugin/session_buffer.py +++ b/plugin/session_buffer.py @@ -118,6 +118,7 @@ def __init__(self, session_view: SessionViewProtocol, buffer_id: int, uri: Docum self._id = buffer_id self._pending_changes: PendingChanges | None = None self.diagnostics: list[tuple[Diagnostic, sublime.Region]] = [] + self.diagnostics_data_per_severity: dict[tuple[int, bool], DiagnosticSeverityData] = {} self.diagnostics_version = -1 self.diagnostics_flags = 0 self._diagnostics_are_visible = False @@ -545,13 +546,14 @@ def on_diagnostics_async( else: data.regions.append(region) diagnostics.append((diagnostic, region)) + self.diagnostics_data_per_severity = data_per_severity def present() -> None: self.diagnostics_version = diagnostics_version self.diagnostics = diagnostics self._diagnostics_are_visible = bool(diagnostics) for sv in self.session_views: - sv.present_diagnostics_async(sv in visible_session_views, data_per_severity) + sv.present_diagnostics_async(sv in visible_session_views) self._diagnostics_debouncer_async.cancel_pending() if self._diagnostics_are_visible: diff --git a/plugin/session_view.py b/plugin/session_view.py index 113ff258e..7345ab5cc 100644 --- a/plugin/session_view.py +++ b/plugin/session_view.py @@ -56,7 +56,6 @@ class SessionView: def __init__(self, listener: AbstractViewListener, session: Session, uri: DocumentUri) -> None: self._view = listener.view self._session = session - self._diagnostics_data_per_severity: dict[tuple[int, bool], DiagnosticSeverityData] = {} self._diagnostic_annotations = DiagnosticsAnnotationsView(self._view, session.config.name) self._initialize_region_keys() self._active_requests: dict[int, ActiveRequest] = {} @@ -300,10 +299,7 @@ def diagnostics_tag_scope(self, tag: int) -> str | None: return f'markup.{k.lower()}.lsp' return None - def present_diagnostics_async( - self, is_view_visible: bool, data_per_severity: dict[tuple[int, bool], DiagnosticSeverityData] - ) -> None: - self._diagnostics_data_per_severity = data_per_severity + def present_diagnostics_async(self, is_view_visible: bool) -> None: self.redraw_diagnostics_async() listener = self.listener() if listener: @@ -328,7 +324,7 @@ def _draw_diagnostics( ICON_FLAGS = sublime.HIDE_ON_MINIMAP | sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE | sublime.NO_UNDO key = self.diagnostics_key(severity, multiline) tags = {tag: TagData(f'{key}_tags_{tag}') for tag in DIAGNOSTIC_TAG_VALUES} - data = self._diagnostics_data_per_severity.get((severity, multiline)) + data = self._session_buffer.diagnostics_data_per_severity.get((severity, multiline)) if data and severity <= max_severity_level: non_tag_regions = data.regions for tag, regions in data.regions_with_tag.items(): From 32d3bef74016697caeeb5d5ff6a905814425203b Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Mon, 23 Sep 2024 15:28:30 +0200 Subject: [PATCH 05/10] Delegate userprefs changes to Session -> SessionBuffer -> SessionView --- plugin/core/sessions.py | 24 +++++++++++++----------- plugin/core/windows.py | 8 +------- plugin/session_buffer.py | 13 ++++++++++--- plugin/session_view.py | 7 +++++-- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index 0ed01e315..f8932f8f5 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -580,9 +580,6 @@ def shutdown_async(self) -> None: def present_diagnostics_async(self, is_view_visible: bool) -> None: ... - def redraw_diagnostics_async(self) -> None: - ... - def on_request_started_async(self, request_id: int, request: Request) -> None: ... @@ -607,6 +604,9 @@ def set_code_lenses_pending_refresh(self, needs_refresh: bool = True) -> None: def reset_show_definitions(self) -> None: ... + def on_userprefs_changed_async(self) -> None: + ... + class SessionBufferProtocol(Protocol): @@ -654,6 +654,9 @@ def get_capability(self, capability_path: str) -> Any | None: def has_capability(self, capability_path: str) -> bool: ... + def on_userprefs_changed_async(self) -> None: + ... + def on_diagnostics_async( self, raw_diagnostics: list[Diagnostic], version: int | None, visible_session_views: set[SessionViewProtocol] ) -> None: @@ -665,9 +668,6 @@ def get_document_link_at_point(self, view: sublime.View, point: int) -> Document def update_document_link(self, new_link: DocumentLink) -> None: ... - def redraw_document_links_async(self) -> None: - ... - def do_semantic_tokens_async(self, view: sublime.View) -> None: ... @@ -677,9 +677,6 @@ def set_semantic_tokens_pending_refresh(self, needs_refresh: bool = True) -> Non def get_semantic_tokens(self) -> list[Any]: ... - def clear_semantic_tokens_async(self) -> None: - ... - def do_inlay_hints_async(self, view: sublime.View) -> None: ... @@ -1351,9 +1348,9 @@ def set_config_status_async(self, message: str) -> None: :param message: The message """ self.config_status_message = message.strip() - self.redraw_config_status_async() + self._redraw_config_status_async() - def redraw_config_status_async(self) -> None: + def _redraw_config_status_async(self) -> None: for sv in self.session_views_async(): self.config.set_view_status(sv.view, self.config_status_message) @@ -1490,6 +1487,11 @@ def on_file_event_async(self, events: list[FileWatcherEvent]) -> None: # --- misc methods ------------------------------------------------------------------------------------------------- + def on_userprefs_changed_async(self) -> None: + self._redraw_config_status_async() + for sb in self.session_buffers_async(): + sb.on_userprefs_changed_async() + def markdown_language_id_to_st_syntax_map(self) -> MarkdownLangMap | None: return self._plugin.markdown_language_id_to_st_syntax_map() if self._plugin is not None else None diff --git a/plugin/core/windows.py b/plugin/core/windows.py index e5dc1a92e..f063b630d 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -535,13 +535,7 @@ def _on_userprefs_updated_async(self) -> None: for wm in self._windows.values(): wm.on_diagnostics_updated() for session in wm.get_sessions(): - session.redraw_config_status_async() - for sb in session.session_buffers_async(): - sb.redraw_document_links_async() - if not userprefs().semantic_highlighting: - sb.clear_semantic_tokens_async() - for sv in session.session_views_async(): - sv.redraw_diagnostics_async() + session.on_userprefs_changed_async() def enable(self) -> None: self._enabled = True diff --git a/plugin/session_buffer.py b/plugin/session_buffer.py index 5bdb7213d..09ee54a2e 100644 --- a/plugin/session_buffer.py +++ b/plugin/session_buffer.py @@ -380,6 +380,13 @@ def on_post_save_async(self, view: sublime.View, new_uri: DocumentUri) -> None: self._has_changed_during_save = False self._on_after_change_async(view, view.change_count()) + def on_userprefs_changed_async(self) -> None: + self._redraw_document_links_async() + if not userprefs().semantic_highlighting: + self._clear_semantic_tokens_async() + for sv in self.session_views: + sv.on_userprefs_changed_async() + def some_view(self) -> sublime.View | None: if not self.session_views: return None @@ -429,9 +436,9 @@ def _do_document_link_async(self, view: sublime.View, version: int) -> None: def _on_document_link_async(self, view: sublime.View, response: list[DocumentLink] | None) -> None: self._document_links = response or [] - self.redraw_document_links_async() + self._redraw_document_links_async() - def redraw_document_links_async(self) -> None: + def _redraw_document_links_async(self) -> None: if self._document_links and userprefs().link_highlight_style == "underline": view = self.some_view() if not view: @@ -715,7 +722,7 @@ def set_semantic_tokens_pending_refresh(self, needs_refresh: bool = True) -> Non def get_semantic_tokens(self) -> list[SemanticToken]: return self.semantic_tokens.tokens - def clear_semantic_tokens_async(self) -> None: + def _clear_semantic_tokens_async(self) -> None: for sv in self.session_views: self._clear_semantic_token_regions(sv.view) diff --git a/plugin/session_view.py b/plugin/session_view.py index 7345ab5cc..febbe6fef 100644 --- a/plugin/session_view.py +++ b/plugin/session_view.py @@ -300,12 +300,12 @@ def diagnostics_tag_scope(self, tag: int) -> str | None: return None def present_diagnostics_async(self, is_view_visible: bool) -> None: - self.redraw_diagnostics_async() + self._redraw_diagnostics_async() listener = self.listener() if listener: listener.on_diagnostics_updated_async(is_view_visible) - def redraw_diagnostics_async(self) -> None: + def _redraw_diagnostics_async(self) -> None: flags = userprefs().diagnostics_highlight_style_flags() # for single lines multiline_flags = None if userprefs().show_multiline_diagnostics_highlights else sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE | sublime.NO_UNDO # noqa: E501 level = userprefs().show_diagnostics_severity_level @@ -376,6 +376,9 @@ def on_pre_save_async(self) -> None: def on_post_save_async(self, new_uri: DocumentUri) -> None: self.session_buffer.on_post_save_async(self.view, new_uri) + def on_userprefs_changed_async(self) -> None: + self._redraw_diagnostics_async() + # --- textDocument/codeLens ---------------------------------------------------------------------------------------- def start_code_lenses_async(self) -> None: From d0e89cd1bcbbae52afe94ef9105fa6064af9df4a Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Mon, 23 Sep 2024 15:43:59 +0200 Subject: [PATCH 06/10] Remove unused imports --- plugin/core/sessions.py | 1 - plugin/session_view.py | 1 - 2 files changed, 2 deletions(-) diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index f8932f8f5..d84d0f923 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -103,7 +103,6 @@ from .url import parse_uri from .url import unparse_uri from .version import __version__ -from .views import DiagnosticSeverityData from .views import extract_variables from .views import get_storage_path from .views import get_uri_and_range_from_location diff --git a/plugin/session_view.py b/plugin/session_view.py index febbe6fef..64e5a7e05 100644 --- a/plugin/session_view.py +++ b/plugin/session_view.py @@ -18,7 +18,6 @@ from .core.sessions import Session from .core.settings import userprefs from .core.views import DIAGNOSTIC_SEVERITY -from .core.views import DiagnosticSeverityData from .core.views import text_document_identifier from .diagnostics import DiagnosticsAnnotationsView from .session_buffer import SessionBuffer From bc61186f5f89a681e3b169d4b9d2309aa33120e6 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Mon, 30 Sep 2024 06:01:16 +0200 Subject: [PATCH 07/10] Add interface for settings change listener --- plugin/core/settings.py | 35 +++++++++++++++++++++-------------- plugin/core/windows.py | 9 +++++---- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/plugin/core/settings.py b/plugin/core/settings.py index 24660506c..0b90dfe76 100644 --- a/plugin/core/settings.py +++ b/plugin/core/settings.py @@ -5,28 +5,40 @@ from .types import read_dict_setting from .types import Settings from .types import SettingsRegistration -from typing import Any, Callable +from abc import ABCMeta +from abc import abstractmethod +from typing import Any import json import os import sublime +class LspSettingsChangeListener(metaclass=ABCMeta): + + @abstractmethod + def on_client_config_updated(self, config_name: str | None = None) -> None: + raise NotImplementedError() + + @abstractmethod + def on_userprefs_updated(self) -> None: + raise NotImplementedError() + + class ClientConfigs: def __init__(self) -> None: self.all: dict[str, ClientConfig] = {} self.external: dict[str, ClientConfig] = {} - self._clients_listener: Callable[[str | None], None] | None = None - self._userprefs_listener: Callable[[], None] | None = None + self._listener: LspSettingsChangeListener | None = None self._clients_hash: int | None = None def _notify_clients_listener(self, config_name: str | None = None) -> None: - if callable(self._clients_listener): - self._clients_listener(config_name) + if self._listener: + self._listener.on_client_config_updated(config_name) def _notify_userprefs_listener(self) -> None: - if callable(self._userprefs_listener): - self._userprefs_listener() + if self._listener: + self._listener.on_userprefs_updated() def add_for_testing(self, config: ClientConfig) -> None: assert config.name not in self.all @@ -117,13 +129,8 @@ def enable(self, config_name: str) -> None: def disable(self, config_name: str) -> None: self._set_enabled(config_name, False) - def set_listeners( - self, - clients_listener: Callable[[str | None], None], - userprefs_listener: Callable[[], None] - ) -> None: - self._clients_listener = clients_listener - self._userprefs_listener = userprefs_listener + def set_listener(self, listener: LspSettingsChangeListener) -> None: + self._listener = listener _settings_obj: sublime.Settings | None = None diff --git a/plugin/core/windows.py b/plugin/core/windows.py index f063b630d..b4e1f815b 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -23,6 +23,7 @@ from .sessions import Manager from .sessions import Session from .settings import client_configs +from .settings import LspSettingsChangeListener from .settings import userprefs from .transports import create_transport from .types import ClientConfig @@ -518,17 +519,17 @@ def on_configs_changed(self, config_name: str | None = None) -> None: sublime.set_timeout_async(lambda: self.restart_sessions_async(config_name)) -class WindowRegistry: +class WindowRegistry(LspSettingsChangeListener): def __init__(self) -> None: self._enabled = False self._windows: dict[int, WindowManager] = {} - client_configs.set_listeners(self._on_client_config_updated, self._on_userprefs_updated) + client_configs.set_listener(self) - def _on_client_config_updated(self, config_name: str | None = None) -> None: + def on_client_config_updated(self, config_name: str | None = None) -> None: for wm in self._windows.values(): wm.get_config_manager().update(config_name) - def _on_userprefs_updated(self) -> None: + def on_userprefs_updated(self) -> None: sublime.set_timeout_async(self._on_userprefs_updated_async) def _on_userprefs_updated_async(self) -> None: From 99fe8e2ef7bcc1724bb1e3b4e13399bf394018e8 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Tue, 1 Oct 2024 14:28:54 +0200 Subject: [PATCH 08/10] Fix inlay hint state being volatile until toggled --- plugin/inlay_hint.py | 17 ++++++++--------- plugin/session_buffer.py | 5 ++++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/plugin/inlay_hint.py b/plugin/inlay_hint.py index 819e7a2b8..3b633da69 100644 --- a/plugin/inlay_hint.py +++ b/plugin/inlay_hint.py @@ -19,10 +19,15 @@ class LspToggleInlayHintsCommand(LspWindowCommand): capability = 'inlayHintProvider' + def __init__(self, window: sublime.Window) -> None: + super().__init__(window) + window.settings().set('lsp_show_inlay_hints', userprefs().show_inlay_hints) + def run(self, enable: bool | None = None) -> None: + window_settings = self.window.settings() if not isinstance(enable, bool): - enable = not self.are_enabled(self.window) - self.window.settings().set('lsp_show_inlay_hints', enable) + enable = not bool(window_settings.get('lsp_show_inlay_hints')) + window_settings.set('lsp_show_inlay_hints', enable) status = 'on' if enable else 'off' sublime.status_message(f'Inlay Hints are {status}') for session in self.sessions(): @@ -30,13 +35,7 @@ def run(self, enable: bool | None = None) -> None: sv.session_buffer.do_inlay_hints_async(sv.view) def is_checked(self) -> bool: - return self.are_enabled(self.window) - - @classmethod - def are_enabled(cls, window: sublime.Window | None) -> bool: - if not window: - return userprefs().show_inlay_hints - return bool(window.settings().get('lsp_show_inlay_hints', userprefs().show_inlay_hints)) + return bool(self.window.settings().get('lsp_show_inlay_hints')) class LspInlayHintClickCommand(LspTextCommand): diff --git a/plugin/session_buffer.py b/plugin/session_buffer.py index 09ee54a2e..06ef4e28b 100644 --- a/plugin/session_buffer.py +++ b/plugin/session_buffer.py @@ -731,7 +731,10 @@ def _clear_semantic_tokens_async(self) -> None: def do_inlay_hints_async(self, view: sublime.View) -> None: if not self.has_capability("inlayHintProvider"): return - if not LspToggleInlayHintsCommand.are_enabled(view.window()): + window = view.window() + if not window: + return + if not window.settings().get('lsp_show_inlay_hints'): self.remove_all_inlay_hints() return params: InlayHintParams = { From df2357878e5091ef2a7c62349d386f455950a523 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Tue, 1 Oct 2024 14:33:48 +0200 Subject: [PATCH 09/10] Unused import --- plugin/session_buffer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/session_buffer.py b/plugin/session_buffer.py index 06ef4e28b..cc3ed3e25 100644 --- a/plugin/session_buffer.py +++ b/plugin/session_buffer.py @@ -43,7 +43,6 @@ from .core.views import text_document_identifier from .core.views import will_save from .inlay_hint import inlay_hint_to_phantom -from .inlay_hint import LspToggleInlayHintsCommand from .semantic_highlighting import SemanticToken from functools import partial from typing import Any, Callable, Iterable, List, Protocol From 19a6986dbc14aff6767d6e9e2bc28826b35ea666 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Wed, 2 Oct 2024 18:07:15 +0200 Subject: [PATCH 10/10] Set semantic tokens refresh flag on userprefs change --- plugin/session_buffer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugin/session_buffer.py b/plugin/session_buffer.py index cc3ed3e25..959bfad39 100644 --- a/plugin/session_buffer.py +++ b/plugin/session_buffer.py @@ -381,7 +381,9 @@ def on_post_save_async(self, view: sublime.View, new_uri: DocumentUri) -> None: def on_userprefs_changed_async(self) -> None: self._redraw_document_links_async() - if not userprefs().semantic_highlighting: + if userprefs().semantic_highlighting: + self.semantic_tokens.needs_refresh = True + else: self._clear_semantic_tokens_async() for sv in self.session_views: sv.on_userprefs_changed_async()