mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-17 18:14:37 +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")
|
- 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):
|
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.
|
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.server_ready = threading.Event()
|
||||||
self.initialize_searcher_command_available = 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:
|
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
|
||||||
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
|
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
|
||||||
|
|
||||||
@@ -217,6 +244,9 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
|||||||
"didChangeConfiguration": {"dynamicRegistration": True},
|
"didChangeConfiguration": {"dynamicRegistration": True},
|
||||||
"symbol": {"dynamicRegistration": True},
|
"symbol": {"dynamicRegistration": True},
|
||||||
},
|
},
|
||||||
|
"window": {
|
||||||
|
"workDoneProgress": True, # Enables $/progress notifications for project loading
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"processId": os.getpid(),
|
"processId": os.getpid(),
|
||||||
"rootPath": repository_absolute_path,
|
"rootPath": repository_absolute_path,
|
||||||
@@ -266,26 +296,62 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
|||||||
def handle_typescript_version(params: dict) -> None:
|
def handle_typescript_version(params: dict) -> None:
|
||||||
"""
|
"""
|
||||||
The $/typescriptVersion notification is sent by typescript-language-server
|
The $/typescriptVersion notification is sent by typescript-language-server
|
||||||
once tsserver has loaded and reported its version. This is the most reliable
|
once tsserver has loaded and reported its version. This is a reliable
|
||||||
readiness signal.
|
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()
|
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:
|
token = str(params.get("token", ""))
|
||||||
self.server_ready.set()
|
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_request("client/registerCapability", register_capability_handler)
|
||||||
self.server.on_notification("window/logMessage", window_log_message)
|
self.server.on_notification("window/logMessage", window_log_message)
|
||||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
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("$/typescriptVersion", handle_typescript_version)
|
||||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||||
self.server.on_notification("experimental/serverStatus", check_experimental_status)
|
|
||||||
|
|
||||||
log.info("Starting TypeScript server process")
|
log.info("Starting TypeScript server process")
|
||||||
self.server.start()
|
self.server.start()
|
||||||
@@ -312,6 +378,19 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
|||||||
# Fallback: assume server is ready after timeout
|
# Fallback: assume server is ready after timeout
|
||||||
self.server_ready.set()
|
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
|
@override
|
||||||
def _get_wait_time_for_cross_file_referencing(self) -> float:
|
def _get_wait_time_for_cross_file_referencing(self) -> float:
|
||||||
return 2
|
return 2
|
||||||
|
|||||||
@@ -134,8 +134,6 @@ class VueLanguageServer(SolidLanguageServer):
|
|||||||
|
|
||||||
TS_SERVER_READY_TIMEOUT = 5.0
|
TS_SERVER_READY_TIMEOUT = 5.0
|
||||||
VUE_SERVER_READY_TIMEOUT = 3.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):
|
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)
|
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()
|
vue_files = self._find_all_vue_files()
|
||||||
log.debug(f"Found {len(vue_files)} .vue files to index")
|
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:
|
for vue_file in vue_files:
|
||||||
try:
|
try:
|
||||||
with self._ts_server.open_file(vue_file) as file_buffer:
|
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}")
|
log.debug(f"Failed to open {vue_file} on TS server: {e}")
|
||||||
|
|
||||||
self._vue_files_indexed = True
|
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())
|
self._wait_for_ts_indexing_complete()
|
||||||
log.debug("Wait period after Vue file indexing complete")
|
|
||||||
|
|
||||||
def _get_vue_indexing_wait_time(self) -> float:
|
def _wait_for_ts_indexing_complete(self) -> None:
|
||||||
return self.VUE_INDEXING_WAIT_TIME
|
"""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:
|
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))
|
uri = PathUtils.path_to_uri(os.path.join(self.repository_root_path, relative_file_path))
|
||||||
|
|||||||
Reference in new issue
Block a user