From a2a32c96c3564105a3913cc32597767373c36a36 Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Tue, 13 Dec 2022 23:46:11 +0100 Subject: [PATCH 1/6] fix code lenses not updating after doing Undo --- plugin/documents.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugin/documents.py b/plugin/documents.py index 83ed04b3d..1152e25e7 100644 --- a/plugin/documents.py +++ b/plugin/documents.py @@ -27,6 +27,7 @@ from .core.signature_help import SigHelp from .core.types import basescope2languageid from .core.types import debounced +from .core.types import Debouncer from .core.types import FEATURES_TIMEOUT from .core.types import SettingsRegistration from .core.typing import Any, Callable, Optional, Dict, Generator, Iterable, List, Tuple, Union @@ -158,6 +159,7 @@ def on_change() -> None: self.set_uri(view_to_uri(view)) self._auto_complete_triggered_manually = False self._change_count_on_last_save = -1 + self._code_lenses_debouncer = Debouncer() self._registration = SettingsRegistration(view.settings(), on_change=on_change) self._setup() @@ -317,6 +319,8 @@ def on_text_changed_async(self, change_count: int, changes: Iterable[sublime.Tex if self.view.is_primary(): for sv in self.session_views_async(): sv.on_text_changed_async(change_count, changes) + self._code_lenses_debouncer.debounce( + self._do_code_lenses_async, timeout_ms=self.code_lenses_debounce_time, async_thread=True) if not different: return self._clear_highlight_regions() @@ -324,8 +328,6 @@ def on_text_changed_async(self, change_count: int, changes: Iterable[sublime.Tex self._when_selection_remains_stable_async(self._do_highlights_async, current_region, after_ms=self.highlights_debounce_time) self.do_signature_help_async(manual=False) - self._when_selection_remains_stable_async(self._do_code_lenses_async, current_region, - after_ms=self.code_lenses_debounce_time) def get_uri(self) -> str: return self._uri From c9995c173793135bc13781a7f4f9c1ad611c71c9 Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Wed, 14 Dec 2022 22:38:25 +0100 Subject: [PATCH 2/6] non-thread-safe --- plugin/core/types.py | 37 +++++++++++++++++++++---------------- plugin/documents.py | 8 ++++---- plugin/session_buffer.py | 13 ++++++------- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 56c6d2101..5630334e9 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -134,41 +134,46 @@ def __del__(self) -> None: self._settings.clear_on_change("LSP") -class Debouncer: +class DebouncerNonThreadSafe: + """ + Debouncer for delaying execution of a function until specified timeout time. + + When calling `debouce()` multiple times, with time span between calls being shorter than the specified `timeout_ms`, + the specified callback function will only be called once, after a period of `timeout_ms` since 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. + """ - def __init__(self) -> None: + def __init__(self, async_thread: bool) -> None: + self._async_thread = async_thread self._current_id = -1 self._next_id = 0 - self._current_id_lock = RLock() - def debounce(self, f: Callable[[], None], timeout_ms: int = 0, condition: Callable[[], bool] = lambda: True, - async_thread: bool = False) -> None: + def debounce( + self, f: Callable[[], None], timeout_ms: int = 0, condition: Callable[[], bool] = lambda: True + ) -> None: """ - Possibly run a function at a later point in time, either on the async thread or on the main thread. + Possibly run a function at a later point in time on the thread chosen during initialization. :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 async_thread: If true, run the function on the async worker thread, otherwise run - the function on the main thread """ def run(debounce_id: int) -> None: - with self._current_id_lock: - if debounce_id != self._current_id: - return + if debounce_id != self._current_id: + return if condition(): f() - runner = sublime.set_timeout_async if async_thread else sublime.set_timeout - with self._current_id_lock: - current_id = self._current_id = self._next_id + runner = sublime.set_timeout_async if self._async_thread else sublime.set_timeout + current_id = self._current_id = self._next_id self._next_id += 1 runner(lambda: run(current_id), timeout_ms) def cancel_pending(self) -> None: - with self._current_id_lock: - self._current_id = -1 + self._current_id = -1 def read_dict_setting(settings_obj: sublime.Settings, key: str, default: dict) -> dict: diff --git a/plugin/documents.py b/plugin/documents.py index 1152e25e7..cc3913c70 100644 --- a/plugin/documents.py +++ b/plugin/documents.py @@ -27,7 +27,7 @@ from .core.signature_help import SigHelp from .core.types import basescope2languageid from .core.types import debounced -from .core.types import Debouncer +from .core.types import DebouncerNonThreadSafe from .core.types import FEATURES_TIMEOUT from .core.types import SettingsRegistration from .core.typing import Any, Callable, Optional, Dict, Generator, Iterable, List, Tuple, Union @@ -159,7 +159,7 @@ def on_change() -> None: self.set_uri(view_to_uri(view)) self._auto_complete_triggered_manually = False self._change_count_on_last_save = -1 - self._code_lenses_debouncer = Debouncer() + self._code_lenses_debouncer_async = DebouncerNonThreadSafe(async_thread=True) self._registration = SettingsRegistration(view.settings(), on_change=on_change) self._setup() @@ -319,8 +319,8 @@ def on_text_changed_async(self, change_count: int, changes: Iterable[sublime.Tex if self.view.is_primary(): for sv in self.session_views_async(): sv.on_text_changed_async(change_count, changes) - self._code_lenses_debouncer.debounce( - self._do_code_lenses_async, timeout_ms=self.code_lenses_debounce_time, async_thread=True) + self._code_lenses_debouncer_async.debounce( + self._do_code_lenses_async, timeout_ms=self.code_lenses_debounce_time) if not different: return self._clear_highlight_regions() diff --git a/plugin/session_buffer.py b/plugin/session_buffer.py index 0cc7dd188..26bb80dde 100644 --- a/plugin/session_buffer.py +++ b/plugin/session_buffer.py @@ -12,7 +12,7 @@ from .core.settings import userprefs from .core.types import Capabilities from .core.types import debounced -from .core.types import Debouncer +from .core.types import DebouncerNonThreadSafe from .core.types import FEATURES_TIMEOUT from .core.typing import Any, Callable, Iterable, Optional, List, Set, Dict, Tuple, Union from .core.views import DIAGNOSTIC_SEVERITY @@ -110,7 +110,7 @@ def __init__(self, session_view: SessionViewProtocol, buffer_id: int, uri: Docum self.diagnostics_flags = 0 self.diagnostics_are_visible = False self.last_text_change_time = 0.0 - self.diagnostics_debouncer = Debouncer() + self.diagnostics_debouncer_async = DebouncerNonThreadSafe(async_thread=True) self.color_phantoms = sublime.PhantomSet(view, "lsp_color") self.document_links = [] # type: List[DocumentLink] self.semantic_tokens = SemanticTokensData() @@ -423,10 +423,10 @@ def on_diagnostics_async( else: data.regions.append(region) diagnostics.append((diagnostic, region)) - self._publish_diagnostics_to_session_views( + self._publish_diagnostics_to_session_views_async( diagnostics_version, diagnostics, data_per_severity, visible_session_views) - def _publish_diagnostics_to_session_views( + def _publish_diagnostics_to_session_views_async( self, diagnostics_version: int, diagnostics: List[Tuple[Diagnostic, sublime.Region]], @@ -442,7 +442,7 @@ def present() -> None: for sv in self.session_views: sv.present_diagnostics_async(sv in visible_session_views) - self.diagnostics_debouncer.cancel_pending() + self.diagnostics_debouncer_async.cancel_pending() if self.diagnostics_are_visible: # Old diagnostics are visible. Update immediately. @@ -458,11 +458,10 @@ def present() -> None: if delay_in_seconds <= 0.0: present() else: - self.diagnostics_debouncer.debounce( + self.diagnostics_debouncer_async.debounce( present, timeout_ms=int(1000.0 * delay_in_seconds), condition=lambda: bool(view and view.is_valid() and view.change_count() == diagnostics_version), - async_thread=True ) # --- textDocument/semanticTokens ---------------------------------------------------------------------------------- From 63c23cbe90a8b72212e8d75731be398d43a0863d Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Wed, 14 Dec 2022 22:40:06 +0100 Subject: [PATCH 3/6] unused import --- plugin/core/types.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 5630334e9..e0f09c027 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -6,7 +6,6 @@ from .typing import cast from .url import filename_to_uri from .url import parse_uri -from threading import RLock from wcmatch.glob import BRACE from wcmatch.glob import globmatch from wcmatch.glob import GLOBSTAR From 051c12e6c9b58fb004dfe8fce0c200269bf5e24d Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Wed, 14 Dec 2022 22:41:04 +0100 Subject: [PATCH 4/6] the --- plugin/core/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index e0f09c027..0006788de 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -138,7 +138,7 @@ class DebouncerNonThreadSafe: Debouncer for delaying execution of a function until specified timeout time. When calling `debouce()` multiple times, with time span between calls being shorter than the specified `timeout_ms`, - the specified callback function will only be called once, after a period of `timeout_ms` since last call. + the specified callback function will only be called once, after a period of `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. From ef009add2df554d45a1a9413afed09664c5857db Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Wed, 14 Dec 2022 23:10:54 +0100 Subject: [PATCH 5/6] typo --- plugin/core/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 0006788de..2cf5ea29f 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -137,7 +137,7 @@ class DebouncerNonThreadSafe: """ Debouncer for delaying execution of a function until specified timeout time. - When calling `debouce()` multiple times, with time span between calls being shorter than the specified `timeout_ms`, + When calling `debounce()` multiple times, with time span between calls being shorter than the specified `timeout_ms`, the specified callback function will only be called once, after a period of `timeout_ms` since the last call. This implementation is not thread safe. You must ensure that `debounce()` is called from the same thread as From da768b0e14daa18192d0224d5736414426c87ad5 Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Wed, 14 Dec 2022 23:24:19 +0100 Subject: [PATCH 6/6] shorten --- plugin/core/types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 2cf5ea29f..ef2ae9a56 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -137,8 +137,8 @@ class DebouncerNonThreadSafe: """ Debouncer for delaying execution of a function until specified timeout time. - When calling `debounce()` multiple times, with time span between calls being shorter than the specified `timeout_ms`, - the specified callback function will only be called once, after a period of `timeout_ms` since the last call. + When calling `debounce()` multiple times, if the time span between calls is shorter than the specified `timeout_ms`, + 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.