From 1558a4e3bcf09ac333a7ef0e9eff33a02a9d77fe Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Tue, 14 Apr 2026 11:19:15 +0200 Subject: [PATCH] Vue: fix, ensure LS is operational before accepting requests --- .../language_servers/vue_language_server.py | 88 ++++++++++++++----- 1 file changed, 65 insertions(+), 23 deletions(-) diff --git a/src/solidlsp/language_servers/vue_language_server.py b/src/solidlsp/language_servers/vue_language_server.py index 3ded6210..95d08732 100644 --- a/src/solidlsp/language_servers/vue_language_server.py +++ b/src/solidlsp/language_servers/vue_language_server.py @@ -156,6 +156,48 @@ class VueLanguageServer(SolidLanguageServer): self._ts_server_started = False self._vue_files_indexed = False self._indexed_vue_file_uris: list[str] = [] + self._ls_operational_ready_event = threading.Event() + self._ls_operational_lock = threading.Lock() + self._ls_operational_thread: threading.Thread | None = None + + def _warm_up_ls_operational_state(self) -> None: + """Warm up the Vue language server operational state asynchronously.""" + # execute the operational warm-up + try: + self._ensure_ls_operational() + except SolidLSPException: + if not self.server_started: + log.debug("Skipping Vue language server operational warm-up because the server is stopping") + return + log.exception("Error while warming up Vue language server operational state") + except Exception: + log.exception("Error while warming up Vue language server operational state") + + def _ensure_ls_operational(self) -> None: + # short-circuit completed warm-up + if self._ls_operational_ready_event.is_set(): + return + + # serialize the warm-up sequence + with self._ls_operational_lock: + # short-circuit repeated callers after waiting for the lock + if self._ls_operational_ready_event.is_set(): + return + + # validate server availability + if not self.server_started: + raise SolidLSPException("Language Server not started") + + # wait for cross-file reference readiness + if not self._has_waited_for_cross_file_references: + sleep(self._get_wait_time_for_cross_file_referencing()) + self._has_waited_for_cross_file_references = True + + # index Vue files on the companion TypeScript server + self._ensure_vue_files_indexed_on_ts_server() + + # publish operational readiness + self._ls_operational_ready_event.set() @override def is_ignored_dirname(self, dirname: str) -> bool: @@ -283,9 +325,7 @@ class VueLanguageServer(SolidLanguageServer): return result def request_file_references(self, relative_file_path: str) -> list: - if not self.server_started: - log.error("request_file_references called before Language Server started") - raise SolidLSPException("Language Server not started") + self._ensure_ls_operational() absolute_file_path = os.path.join(self.repository_root_path, relative_file_path) uri = PathUtils.path_to_uri(absolute_file_path) @@ -348,15 +388,7 @@ class VueLanguageServer(SolidLanguageServer): @override def request_references(self, relative_file_path: str, line: int, column: int) -> list[ls_types.Location]: - if not self.server_started: - log.error("request_references called before Language Server started") - raise SolidLSPException("Language Server not started") - - if not self._has_waited_for_cross_file_references: - sleep(self._get_wait_time_for_cross_file_referencing()) - self._has_waited_for_cross_file_references = True - - self._ensure_vue_files_indexed_on_ts_server() + self._ensure_ls_operational() symbol_refs = self._send_ts_references_request(relative_file_path, line=line, column=column) if relative_file_path.endswith(".vue"): @@ -381,20 +413,14 @@ class VueLanguageServer(SolidLanguageServer): @override def request_definition(self, relative_file_path: str, line: int, column: int) -> list[ls_types.Location]: - if not self.server_started: - log.error("request_definition called before Language Server started") - raise SolidLSPException("Language Server not started") - + self._ensure_ls_operational() assert self._ts_server is not None with self._ts_server.open_file(relative_file_path): return self._ts_server.request_definition(relative_file_path, line, column) @override def request_rename_symbol_edit(self, relative_file_path: str, line: int, column: int, new_name: str) -> ls_types.WorkspaceEdit | None: - if not self.server_started: - log.error("request_rename_symbol_edit called before Language Server started") - raise SolidLSPException("Language Server not started") - + self._ensure_ls_operational() assert self._ts_server is not None with self._ts_server.open_file(relative_file_path): return self._ts_server.request_rename_symbol_edit(relative_file_path, line, column, new_name) @@ -698,6 +724,15 @@ class VueLanguageServer(SolidLanguageServer): else: log.info("Vue server initialization complete") + # kick off asynchronous operational warm-up + self._ls_operational_ready_event.clear() + self._ls_operational_thread = threading.Thread( + target=self._warm_up_ls_operational_state, + name="vue-ls-operational-warmup", + daemon=True, + ) + self._ls_operational_thread.start() + def _find_tsconfig_for_file(self, file_path: str) -> str | None: if not file_path: tsconfig_path = os.path.join(self.repository_root_path, "tsconfig.json") @@ -724,8 +759,14 @@ class VueLanguageServer(SolidLanguageServer): @override def stop(self, shutdown_timeout: float = 5.0) -> None: - self._cleanup_indexed_vue_files() - self._stop_typescript_server() + # serialize shutdown with operational warm-up + with self._ls_operational_lock: + self.server_started = False + self._ls_operational_ready_event.clear() + self._cleanup_indexed_vue_files() + self._stop_typescript_server() + self._ls_operational_thread = None + super().stop(shutdown_timeout) @override @@ -760,8 +801,9 @@ class VueLanguageServer(SolidLanguageServer): return self._filter_shorthand_property_duplicates(symbols) + @staticmethod def _filter_shorthand_property_duplicates( - self, symbols: list[DocumentSymbol] | list[SymbolInformation] + symbols: list[DocumentSymbol] | list[SymbolInformation], ) -> list[DocumentSymbol] | list[SymbolInformation]: """ Filter out Property symbols that have a matching Variable symbol with the same name.