From 213f5337cb91f10e788de1ebf527e8f3e120389d Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Wed, 16 Oct 2024 18:44:06 +0200 Subject: [PATCH 1/4] Make DocumentSyncListener more efficient --- plugin/documents.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/plugin/documents.py b/plugin/documents.py index a08658d0a..f30dff33e 100644 --- a/plugin/documents.py +++ b/plugin/documents.py @@ -328,6 +328,8 @@ def session_views_async(self) -> list[SessionView]: return list(self._session_views.values()) def on_text_changed_async(self, change_count: int, changes: Iterable[sublime.TextChange]) -> None: + if not self.sessions_async(): + return if self.view.is_primary(): for sv in self.session_views_async(): sv.on_text_changed_async(change_count, changes) @@ -364,6 +366,8 @@ def on_activated_async(self) -> None: return if not self._registered: self._register_async() + if not self.sessions_async(): + return if userprefs().show_code_actions: self._do_code_actions_async() for sv in self.session_views_async(): @@ -382,6 +386,8 @@ def on_activated_async(self) -> None: sb.do_inlay_hints_async(self.view) def on_selection_modified_async(self) -> None: + if not self.sessions_async(): + return first_region, _ = self._update_stored_selection_async() if first_region is None: return @@ -483,6 +489,8 @@ def on_query_context(self, key: str, operator: int, operand: Any, match_all: boo return None def on_hover(self, point: int, hover_zone: int) -> None: + if not self.sessions_async(): + return if self.view.is_popup_visible(): return window = self.view.window() @@ -525,6 +533,8 @@ def _on_hover_gutter_async(self, point: int) -> None: on_navigate=lambda href: self._on_navigate(href, point)) def on_text_command(self, command_name: str, args: dict | None) -> tuple[str, dict] | None: + if not self.sessions_async(): + return None if command_name == "auto_complete": self._auto_complete_triggered_manually = True elif command_name == "show_scope_name" and userprefs().semantic_highlighting: @@ -540,6 +550,8 @@ def on_text_command(self, command_name: str, args: dict | None) -> tuple[str, di return None def on_post_text_command(self, command_name: str, args: dict[str, Any] | None) -> None: + if not self.sessions_async(): + return if command_name == 'paste': format_on_paste = self.view.settings().get('lsp_format_on_paste', userprefs().lsp_format_on_paste) if format_on_paste and self.session_async("documentRangeFormattingProvider"): @@ -553,6 +565,8 @@ def on_post_text_command(self, command_name: str, args: dict[str, Any] | None) - self.view.hide_popup() def on_query_completions(self, prefix: str, locations: list[int]) -> sublime.CompletionList | None: + if not self.sessions_async(): + return None completion_list = sublime.CompletionList() triggered_manually = self._auto_complete_triggered_manually self._auto_complete_triggered_manually = False # reset state for next completion popup From 7a8b4821c38f5a4de25e021963035d67429e684f Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Wed, 16 Oct 2024 21:44:31 +0200 Subject: [PATCH 2/4] Check for SessionViews instead --- plugin/documents.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/plugin/documents.py b/plugin/documents.py index f30dff33e..4ef6c8c87 100644 --- a/plugin/documents.py +++ b/plugin/documents.py @@ -328,10 +328,11 @@ def session_views_async(self) -> list[SessionView]: return list(self._session_views.values()) def on_text_changed_async(self, change_count: int, changes: Iterable[sublime.TextChange]) -> None: - if not self.sessions_async(): + session_views = self.session_views_async() + if not session_views: return if self.view.is_primary(): - for sv in self.session_views_async(): + for sv in session_views: sv.on_text_changed_async(change_count, changes) self._on_view_updated_async() @@ -366,11 +367,12 @@ def on_activated_async(self) -> None: return if not self._registered: self._register_async() - if not self.sessions_async(): + session_views = self.session_views_async() + if not session_views: return if userprefs().show_code_actions: self._do_code_actions_async() - for sv in self.session_views_async(): + for sv in session_views: if sv.code_lenses_needs_refresh: sv.set_code_lenses_pending_refresh(needs_refresh=False) sv.start_code_lenses_async() @@ -386,7 +388,7 @@ def on_activated_async(self) -> None: sb.do_inlay_hints_async(self.view) def on_selection_modified_async(self) -> None: - if not self.sessions_async(): + if not self.session_views_async(): return first_region, _ = self._update_stored_selection_async() if first_region is None: @@ -489,7 +491,7 @@ def on_query_context(self, key: str, operator: int, operand: Any, match_all: boo return None def on_hover(self, point: int, hover_zone: int) -> None: - if not self.sessions_async(): + if not self.session_views_async(): return if self.view.is_popup_visible(): return @@ -533,7 +535,7 @@ def _on_hover_gutter_async(self, point: int) -> None: on_navigate=lambda href: self._on_navigate(href, point)) def on_text_command(self, command_name: str, args: dict | None) -> tuple[str, dict] | None: - if not self.sessions_async(): + if not self.session_views_async(): return None if command_name == "auto_complete": self._auto_complete_triggered_manually = True @@ -550,7 +552,7 @@ def on_text_command(self, command_name: str, args: dict | None) -> tuple[str, di return None def on_post_text_command(self, command_name: str, args: dict[str, Any] | None) -> None: - if not self.sessions_async(): + if not self.session_views_async(): return if command_name == 'paste': format_on_paste = self.view.settings().get('lsp_format_on_paste', userprefs().lsp_format_on_paste) @@ -565,7 +567,7 @@ def on_post_text_command(self, command_name: str, args: dict[str, Any] | None) - self.view.hide_popup() def on_query_completions(self, prefix: str, locations: list[int]) -> sublime.CompletionList | None: - if not self.sessions_async(): + if not self.session_views_async(): return None completion_list = sublime.CompletionList() triggered_manually = self._auto_complete_triggered_manually From 4c37df76371c64f57b4abfbd87abf605107f3260 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Thu, 17 Oct 2024 00:28:22 +0200 Subject: [PATCH 3/4] Convert to decorator --- plugin/documents.py | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/plugin/documents.py b/plugin/documents.py index 4ef6c8c87..03c3b0cf7 100644 --- a/plugin/documents.py +++ b/plugin/documents.py @@ -55,8 +55,10 @@ from .session_buffer import SessionBuffer from .session_view import SessionView from functools import partial -from typing import Any, Callable, Generator, Iterable +from functools import wraps +from typing import Any, Callable, Generator, Iterable, TypeVar from typing import cast +from typing_extensions import ParamSpec from weakref import WeakSet from weakref import WeakValueDictionary import itertools @@ -68,6 +70,22 @@ SUBLIME_WORD_MASK = 515 +P = ParamSpec('P') +R = TypeVar('R') + + +def requires_session(func: Callable[P, R]) -> Callable[P, R | None]: + """ + A decorator for the `DocumentSyncListener` event handlers, which immediately returns `None` if there are no + `SessionView`s. + """ + @wraps(func) + def wrapper(self: DocumentSyncListener, *args: P.args, **kwargs: P.kwargs) -> R | None: + if not self.session_views_async(): + return None + return func(self, *args, **kwargs) # pyright: ignore[reportCallIssue] + return wrapper # pyright: ignore[reportReturnType] + def is_regular_view(v: sublime.View) -> bool: # Not from the quick panel (CTRL+P), and not a special view like a console, output panel or find-in-files panels. @@ -327,12 +345,10 @@ def session_buffers_async(self, capability: str | None = None) -> list[SessionBu def session_views_async(self) -> list[SessionView]: return list(self._session_views.values()) + @requires_session def on_text_changed_async(self, change_count: int, changes: Iterable[sublime.TextChange]) -> None: - session_views = self.session_views_async() - if not session_views: - return if self.view.is_primary(): - for sv in session_views: + for sv in self.session_views_async(): sv.on_text_changed_async(change_count, changes) self._on_view_updated_async() @@ -387,9 +403,8 @@ def on_activated_async(self) -> None: sb.set_inlay_hints_pending_refresh(needs_refresh=False) sb.do_inlay_hints_async(self.view) + @requires_session def on_selection_modified_async(self) -> None: - if not self.session_views_async(): - return first_region, _ = self._update_stored_selection_async() if first_region is None: return @@ -490,9 +505,8 @@ def on_query_context(self, key: str, operator: int, operand: Any, match_all: boo return operand == bool(session_view.session_buffer.get_document_link_at_point(self.view, position)) return None + @requires_session def on_hover(self, point: int, hover_zone: int) -> None: - if not self.session_views_async(): - return if self.view.is_popup_visible(): return window = self.view.window() @@ -534,9 +548,8 @@ def _on_hover_gutter_async(self, point: int) -> None: location=point, on_navigate=lambda href: self._on_navigate(href, point)) + @requires_session def on_text_command(self, command_name: str, args: dict | None) -> tuple[str, dict] | None: - if not self.session_views_async(): - return None if command_name == "auto_complete": self._auto_complete_triggered_manually = True elif command_name == "show_scope_name" and userprefs().semantic_highlighting: @@ -551,9 +564,8 @@ def on_text_command(self, command_name: str, args: dict | None) -> tuple[str, di return ('paste', {}) return None + @requires_session def on_post_text_command(self, command_name: str, args: dict[str, Any] | None) -> None: - if not self.session_views_async(): - return if command_name == 'paste': format_on_paste = self.view.settings().get('lsp_format_on_paste', userprefs().lsp_format_on_paste) if format_on_paste and self.session_async("documentRangeFormattingProvider"): @@ -566,9 +578,8 @@ def on_post_text_command(self, command_name: str, args: dict[str, Any] | None) - # hide the popup when `esc` or arrows are pressed pressed self.view.hide_popup() + @requires_session def on_query_completions(self, prefix: str, locations: list[int]) -> sublime.CompletionList | None: - if not self.session_views_async(): - return None completion_list = sublime.CompletionList() triggered_manually = self._auto_complete_triggered_manually self._auto_complete_triggered_manually = False # reset state for next completion popup From 1b5fb95c22153a42e020eb22a8e0d5e7f9ad37f5 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Thu, 17 Oct 2024 16:28:34 +0200 Subject: [PATCH 4/4] Fix type annotations --- plugin/documents.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugin/documents.py b/plugin/documents.py index 03c3b0cf7..3c1d133fd 100644 --- a/plugin/documents.py +++ b/plugin/documents.py @@ -58,7 +58,7 @@ from functools import wraps from typing import Any, Callable, Generator, Iterable, TypeVar from typing import cast -from typing_extensions import ParamSpec +from typing_extensions import Concatenate, ParamSpec from weakref import WeakSet from weakref import WeakValueDictionary import itertools @@ -74,7 +74,9 @@ R = TypeVar('R') -def requires_session(func: Callable[P, R]) -> Callable[P, R | None]: +def requires_session( + func: Callable[Concatenate[DocumentSyncListener, P], R] +) -> Callable[Concatenate[DocumentSyncListener, P], R | None]: """ A decorator for the `DocumentSyncListener` event handlers, which immediately returns `None` if there are no `SessionView`s. @@ -83,8 +85,8 @@ def requires_session(func: Callable[P, R]) -> Callable[P, R | None]: def wrapper(self: DocumentSyncListener, *args: P.args, **kwargs: P.kwargs) -> R | None: if not self.session_views_async(): return None - return func(self, *args, **kwargs) # pyright: ignore[reportCallIssue] - return wrapper # pyright: ignore[reportReturnType] + return func(self, *args, **kwargs) + return wrapper def is_regular_view(v: sublime.View) -> bool: