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 code lenses not updating after Undo #2139

Merged
merged 6 commits into from
Dec 18, 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
38 changes: 21 additions & 17 deletions plugin/core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -134,41 +133,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 `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.
"""

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:
Expand Down
6 changes: 4 additions & 2 deletions plugin/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .core.signature_help import SigHelp
from .core.types import basescope2languageid
from .core.types import debounced
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
Expand Down Expand Up @@ -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_async = DebouncerNonThreadSafe(async_thread=True)
self._registration = SettingsRegistration(view.settings(), on_change=on_change)
self._setup()

Expand Down Expand Up @@ -317,15 +319,15 @@ 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_async.debounce(
self._do_code_lenses_async, timeout_ms=self.code_lenses_debounce_time)
if not different:
return
self._clear_highlight_regions()
if userprefs().document_highlight_style:
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
Expand Down
13 changes: 6 additions & 7 deletions plugin/session_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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]],
Expand All @@ -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.
Expand All @@ -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 ----------------------------------------------------------------------------------
Expand Down