mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 10:16:00 +00:00
Replace sleep-based indexing wait with $/progress tracking
Replace blind sleep() in VueLanguageServer with event-driven $/progress tracking from the companion TypeScript language server. This makes Vue file indexing both faster (no unnecessary waiting) and more reliable (no under-waiting on slow machines). Changes: - Add workDoneProgress capability to TS server initialize params - Track window/workDoneProgress/create and $/progress notifications to detect when tsserver finishes async project loading - Expose public wait_for_indexing()/expect_indexing() API on TypeScriptLanguageServer (avoids cross-class private access) - Wait for indexing completion in TS server's own _start_server(), matching the existing Kotlin LS pattern - Unify indexing timeout as a single class constant (15s Win / 10s Unix) - Remove experimental/serverStatus handler (not sent by ts-language-server)
This commit is contained in:
1 parent
3569a8145b
commit
1f02cc5f4f
2 files changed
+108
-16
No files matched your search
@@ -67,6 +67,10 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
- typescript_language_server_version: Version of typescript-language-server to install (default: "5.1.3")
|
||||
"""
|
||||
|
||||
# Safety timeout for $/progress-based indexing wait. Normally the event fires
|
||||
# well within this window; the timeout is only hit if the server never sends progress.
|
||||
INDEXING_PROGRESS_TIMEOUT = 15.0 if os.name == "nt" else 10.0
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
"""
|
||||
Creates a TypeScriptLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
@@ -81,6 +85,29 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
self.server_ready = threading.Event()
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
|
||||
# Progress tracking for $/progress notifications (project indexing, etc.)
|
||||
self._progress_lock = threading.Lock()
|
||||
self._active_progress_tokens: set[str] = set()
|
||||
self._indexing_complete = threading.Event()
|
||||
self._indexing_complete.set() # Initially set (no active work)
|
||||
|
||||
def wait_for_indexing(self, timeout: float) -> bool:
|
||||
"""Block until all $/progress tokens complete.
|
||||
|
||||
:param timeout: Maximum seconds to wait.
|
||||
:return: True if indexing completed, False on timeout.
|
||||
"""
|
||||
return self._indexing_complete.wait(timeout=timeout)
|
||||
|
||||
def expect_indexing(self) -> None:
|
||||
"""Signal that new files are about to be opened and async indexing should be awaited.
|
||||
|
||||
Clears the internal indexing-complete event so that a subsequent
|
||||
:meth:`wait_for_indexing` call blocks until all $/progress tokens
|
||||
complete (or the timeout expires).
|
||||
"""
|
||||
self._indexing_complete.clear()
|
||||
|
||||
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
|
||||
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
|
||||
|
||||
@@ -217,6 +244,9 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
"didChangeConfiguration": {"dynamicRegistration": True},
|
||||
"symbol": {"dynamicRegistration": True},
|
||||
},
|
||||
"window": {
|
||||
"workDoneProgress": True, # Enables $/progress notifications for project loading
|
||||
},
|
||||
},
|
||||
"processId": os.getpid(),
|
||||
"rootPath": repository_absolute_path,
|
||||
@@ -266,26 +296,62 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
def handle_typescript_version(params: dict) -> None:
|
||||
"""
|
||||
The $/typescriptVersion notification is sent by typescript-language-server
|
||||
once tsserver has loaded and reported its version. This is the most reliable
|
||||
readiness signal.
|
||||
once tsserver has loaded and reported its version. This is a reliable
|
||||
signal that tsserver is running and responsive.
|
||||
"""
|
||||
log.info(f"TypeScript server ready signal detected via $/typescriptVersion: {params}")
|
||||
log.info(f"TypeScript server version notification received: {params}")
|
||||
self.server_ready.set()
|
||||
|
||||
def check_experimental_status(params: dict) -> None:
|
||||
def work_done_progress_create(params: dict) -> dict:
|
||||
"""Handle window/workDoneProgress/create: the server is about to report async progress.
|
||||
|
||||
Clear the indexing-complete event so callers waiting on it will block until
|
||||
all progress tokens finish. This is sent by typescript-language-server when
|
||||
tsserver starts processing files (e.g. "Initializing JS/TS language features...").
|
||||
"""
|
||||
Also listen for experimental/serverStatus as a backup readiness signal.
|
||||
token = str(params.get("token", ""))
|
||||
log.debug(f"TypeScript LSP workDoneProgress/create: token={token!r}")
|
||||
with self._progress_lock:
|
||||
self._active_progress_tokens.add(token)
|
||||
self._indexing_complete.clear()
|
||||
return {}
|
||||
|
||||
def progress_handler(params: dict) -> None:
|
||||
"""Track $/progress begin/end to detect when all async work finishes.
|
||||
|
||||
typescript-language-server sends $/progress for project loading operations
|
||||
like "Initializing JS/TS language features...". When all progress tokens
|
||||
complete (kind='end'), _indexing_complete is set.
|
||||
"""
|
||||
if params.get("quiescent") == True:
|
||||
self.server_ready.set()
|
||||
token = str(params.get("token", ""))
|
||||
value = params.get("value", {})
|
||||
kind = value.get("kind")
|
||||
if kind == "begin":
|
||||
title = value.get("title", "")
|
||||
log.info(f"TypeScript LSP progress [{token}]: started - {title}")
|
||||
with self._progress_lock:
|
||||
self._active_progress_tokens.add(token)
|
||||
self._indexing_complete.clear()
|
||||
elif kind == "report":
|
||||
pct = value.get("percentage")
|
||||
msg = value.get("message", "")
|
||||
pct_str = f" ({pct}%)" if pct is not None else ""
|
||||
log.debug(f"TypeScript LSP progress [{token}]: {msg}{pct_str}")
|
||||
elif kind == "end":
|
||||
msg = value.get("message", "")
|
||||
log.info(f"TypeScript LSP progress [{token}]: ended - {msg}")
|
||||
with self._progress_lock:
|
||||
self._active_progress_tokens.discard(token)
|
||||
if not self._active_progress_tokens:
|
||||
self._indexing_complete.set()
|
||||
|
||||
self.server.on_request("client/registerCapability", register_capability_handler)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
||||
self.server.on_notification("$/progress", do_nothing)
|
||||
self.server.on_request("window/workDoneProgress/create", work_done_progress_create)
|
||||
self.server.on_notification("$/progress", progress_handler)
|
||||
self.server.on_notification("$/typescriptVersion", handle_typescript_version)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
self.server.on_notification("experimental/serverStatus", check_experimental_status)
|
||||
|
||||
log.info("Starting TypeScript server process")
|
||||
self.server.start()
|
||||
@@ -312,6 +378,19 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
# Fallback: assume server is ready after timeout
|
||||
self.server_ready.set()
|
||||
|
||||
# Wait for any async project loading to complete.
|
||||
# typescript-language-server may send $/progress for "Initializing JS/TS
|
||||
# language features…" after initialized. If no progress is sent,
|
||||
# _indexing_complete stays SET and wait() returns immediately.
|
||||
log.info("Waiting for TypeScript project indexing to complete (if async)...")
|
||||
if self.wait_for_indexing(timeout=self.INDEXING_PROGRESS_TIMEOUT):
|
||||
log.info("TypeScript project indexing complete")
|
||||
else:
|
||||
log.warning(
|
||||
"TypeScript project indexing did not complete within %.0fs; proceeding anyway",
|
||||
self.INDEXING_PROGRESS_TIMEOUT,
|
||||
)
|
||||
|
||||
@override
|
||||
def _get_wait_time_for_cross_file_referencing(self) -> float:
|
||||
return 2
|
||||
|
||||
@@ -134,8 +134,6 @@ class VueLanguageServer(SolidLanguageServer):
|
||||
|
||||
TS_SERVER_READY_TIMEOUT = 5.0
|
||||
VUE_SERVER_READY_TIMEOUT = 3.0
|
||||
# Windows requires more time due to slower I/O and process operations.
|
||||
VUE_INDEXING_WAIT_TIME = 4.0 if os.name == "nt" else 2.0
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
vue_lsp_executable_path, self.tsdk_path, self._ts_ls_cmd = self._setup_runtime_dependencies(config, solidlsp_settings)
|
||||
@@ -204,6 +202,11 @@ class VueLanguageServer(SolidLanguageServer):
|
||||
vue_files = self._find_all_vue_files()
|
||||
log.debug(f"Found {len(vue_files)} .vue files to index")
|
||||
|
||||
# Prepare the TS server to track new $/progress notifications triggered
|
||||
# by the didOpen calls below. Must happen BEFORE opening files to avoid
|
||||
# a race where progress begins and ends before we start waiting.
|
||||
self._ts_server.expect_indexing()
|
||||
|
||||
for vue_file in vue_files:
|
||||
try:
|
||||
with self._ts_server.open_file(vue_file) as file_buffer:
|
||||
@@ -213,13 +216,23 @@ class VueLanguageServer(SolidLanguageServer):
|
||||
log.debug(f"Failed to open {vue_file} on TS server: {e}")
|
||||
|
||||
self._vue_files_indexed = True
|
||||
log.info("Vue file indexing on TypeScript server complete")
|
||||
log.info("Vue file indexing on TypeScript server complete, waiting for TS server to finish processing")
|
||||
|
||||
sleep(self._get_vue_indexing_wait_time())
|
||||
log.debug("Wait period after Vue file indexing complete")
|
||||
self._wait_for_ts_indexing_complete()
|
||||
|
||||
def _get_vue_indexing_wait_time(self) -> float:
|
||||
return self.VUE_INDEXING_WAIT_TIME
|
||||
def _wait_for_ts_indexing_complete(self) -> None:
|
||||
"""Wait for the companion TypeScript server to finish processing opened Vue files.
|
||||
|
||||
Uses the $/progress tracking in TypeScriptLanguageServer: after Vue files are
|
||||
opened, tsserver sends "Initializing JS/TS language features…" progress.
|
||||
We wait for all progress tokens to complete, with a timeout fallback.
|
||||
"""
|
||||
assert self._ts_server is not None
|
||||
timeout = TypeScriptLanguageServer.INDEXING_PROGRESS_TIMEOUT
|
||||
if self._ts_server.wait_for_indexing(timeout=timeout):
|
||||
log.info("TypeScript server finished indexing Vue files (signaled via $/progress)")
|
||||
else:
|
||||
log.warning(f"Timeout ({timeout}s) waiting for TypeScript server to finish indexing Vue files, proceeding anyway")
|
||||
|
||||
def _send_references_request(self, relative_file_path: str, line: int, column: int) -> list[lsp_types.Location] | None:
|
||||
uri = PathUtils.path_to_uri(os.path.join(self.repository_root_path, relative_file_path))
|
||||
|
||||
Reference in new issue
Block a user