From d4dedd5d84dc545629adb68419a87fc064b8a9a5 Mon Sep 17 00:00:00 2001 From: MischaPanch Date: Fri, 15 Aug 2025 13:10:46 +0200 Subject: [PATCH 1/2] Improve waiting logic for cross-file references to be ready Now waiting only once instead of on each request, hopefully that's enough --- .../language_servers/csharp_language_server.py | 14 +++++++------- .../typescript_language_server.py | 12 ++---------- .../language_servers/vts_language_server.py | 7 ++----- src/solidlsp/ls.py | 17 +++++++++++++++++ test/solidlsp/csharp/test_csharp_basic.py | 4 ++++ 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/solidlsp/language_servers/csharp_language_server.py b/src/solidlsp/language_servers/csharp_language_server.py index 9a19bf3a..42c00f60 100644 --- a/src/solidlsp/language_servers/csharp_language_server.py +++ b/src/solidlsp/language_servers/csharp_language_server.py @@ -13,7 +13,6 @@ import threading import urllib.request import zipfile from pathlib import Path -from time import sleep from typing import cast from overrides import override @@ -22,7 +21,6 @@ from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.ls_exceptions import SolidLSPException from solidlsp.ls_logger import LanguageServerLogger -from solidlsp.ls_types import Location from solidlsp.ls_utils import PathUtils from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo @@ -739,8 +737,10 @@ class CSharpLanguageServer(SolidLanguageServer): self.logger.log(f"Opened project files: {project_files}", logging.DEBUG) @override - def request_references(self, relative_file_path: str, line: int, column: int) -> list[Location]: - # Like in the typescript LS, we need to wait here for the language server to - # get correct results that include cross-file references. - sleep(2) - return super().request_references(relative_file_path, line, column) + def _get_wait_time_for_cross_file_referencing(self) -> float: + is_windows = platform.system() == "Windows" + # seems like on windows initialization may take longer + if is_windows: + return 3 + else: + return 1 diff --git a/src/solidlsp/language_servers/typescript_language_server.py b/src/solidlsp/language_servers/typescript_language_server.py index ccb5e8c4..361d7284 100644 --- a/src/solidlsp/language_servers/typescript_language_server.py +++ b/src/solidlsp/language_servers/typescript_language_server.py @@ -7,7 +7,6 @@ import os import pathlib import shutil import threading -from time import sleep from overrides import override from sensai.util.logging import LogTime @@ -250,12 +249,5 @@ class TypeScriptLanguageServer(SolidLanguageServer): self.completions_available.set() @override - # For some reason, the LS may need longer to process this, so we just retry - def _send_references_request(self, relative_file_path: str, line: int, column: int): - # TODO: The LS doesn't return references contained in other files if it doesn't sleep. This is - # despite the LS having processed requests already. I don't know what causes this, but sleeping - # one second helps. It may be that sleeping only once is enough but that's hard to reliably test. - # It may be that even this 1sec is not enough in larger TS projects, at some point we should find what - # causes this and solve it. - sleep(1) - return super()._send_references_request(relative_file_path, line, column) + def _get_wait_time_for_cross_file_referencing(self) -> float: + return 1 diff --git a/src/solidlsp/language_servers/vts_language_server.py b/src/solidlsp/language_servers/vts_language_server.py index c8596b89..b07e4c29 100644 --- a/src/solidlsp/language_servers/vts_language_server.py +++ b/src/solidlsp/language_servers/vts_language_server.py @@ -9,7 +9,6 @@ import os import pathlib import shutil import threading -from time import sleep from overrides import override @@ -231,8 +230,6 @@ class VtsLanguageServer(SolidLanguageServer): self.server_ready.set() self.completions_available.set() - # VTS may need longer to process references, so we include a sleep similar to TypeScript LS @override - def _send_references_request(self, relative_file_path: str, line: int, column: int): - sleep(1) - return super()._send_references_request(relative_file_path, line, column) + def _get_wait_time_for_cross_file_referencing(self) -> float: + return 1 diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index f4ff544f..9b888029 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -14,6 +14,7 @@ from collections.abc import Iterator from contextlib import contextmanager from copy import copy from pathlib import Path, PurePath +from time import sleep from typing import Self, Union, cast import pathspec @@ -323,6 +324,16 @@ class SolidLanguageServer(ABC): self._server_context = None self._request_timeout: float | None = None + self._has_waited_for_cross_file_references = False + + def _get_wait_time_for_cross_file_referencing(self) -> float: + """Meant to be overridden by subclasses for LS that don't have a reliable "finished initializing" signal. + + LS may return incomplete results on calls to `request_references` (only references found in the same file), + if the LS is not fully initialized yet. + """ + return 0 + def set_request_timeout(self, timeout: float | None) -> None: """ :param timeout: the timeout, in seconds, for requests to the language server. @@ -699,6 +710,12 @@ class SolidLanguageServer(ABC): ) raise SolidLSPException("Language Server not started") + if not self._has_waited_for_cross_file_references: + # Some LS require waiting for a while before they can return cross-file references. + # This is a workaround for such LS that don't have a reliable "finished initializing" signal. + sleep(self._get_wait_time_for_cross_file_referencing()) + self._has_waited_for_cross_file_references = True + with self.open_file(relative_file_path): try: response = self._send_references_request(relative_file_path, line=line, column=column) diff --git a/test/solidlsp/csharp/test_csharp_basic.py b/test/solidlsp/csharp/test_csharp_basic.py index a0c83a13..faacc8cc 100644 --- a/test/solidlsp/csharp/test_csharp_basic.py +++ b/test/solidlsp/csharp/test_csharp_basic.py @@ -120,6 +120,10 @@ class TestCSharpLanguageServer: "Models/Person.cs" in ref_file for ref_file in ref_files ), "Should find reference in Models/Person.cs where Calculator.Subtract is called" + # check for a second time, since the first call may trigger initialization and change the state of the LS + refs_second_call = language_server.request_references(file_path, sel_start["line"], sel_start["character"] + 1) + assert refs_second_call == refs, "Second call to request_references should return the same results" + @pytest.mark.csharp class TestCSharpSolutionProjectOpening: From e6ae39ab1b503c36507a3ba790886343acbe9366 Mon Sep 17 00:00:00 2001 From: MischaPanch Date: Fri, 15 Aug 2025 13:28:32 +0200 Subject: [PATCH 2/2] Fixed path in test --- src/solidlsp/language_servers/csharp_language_server.py | 7 +------ test/solidlsp/csharp/test_csharp_basic.py | 5 +++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/solidlsp/language_servers/csharp_language_server.py b/src/solidlsp/language_servers/csharp_language_server.py index 42c00f60..a0d5408d 100644 --- a/src/solidlsp/language_servers/csharp_language_server.py +++ b/src/solidlsp/language_servers/csharp_language_server.py @@ -738,9 +738,4 @@ class CSharpLanguageServer(SolidLanguageServer): @override def _get_wait_time_for_cross_file_referencing(self) -> float: - is_windows = platform.system() == "Windows" - # seems like on windows initialization may take longer - if is_windows: - return 3 - else: - return 1 + return 1 diff --git a/test/solidlsp/csharp/test_csharp_basic.py b/test/solidlsp/csharp/test_csharp_basic.py index faacc8cc..d4bbd8f1 100644 --- a/test/solidlsp/csharp/test_csharp_basic.py +++ b/test/solidlsp/csharp/test_csharp_basic.py @@ -1,6 +1,7 @@ import os import tempfile from pathlib import Path +from typing import cast from unittest.mock import Mock, patch import pytest @@ -110,14 +111,14 @@ class TestCSharpLanguageServer: refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"] + 1) # Should find references in both Program.cs and Models/Person.cs - ref_files = [ref.get("relativePath", "") for ref in refs] + ref_files = cast(list[str], [ref.get("relativePath", "") for ref in refs]) print(f"Found references: {refs}") print(f"Reference files: {ref_files}") # Check that we have references from both files assert any("Program.cs" in ref_file for ref_file in ref_files), "Should find reference in Program.cs" assert any( - "Models/Person.cs" in ref_file for ref_file in ref_files + os.path.join("Models", "Person.cs") in ref_file for ref_file in ref_files ), "Should find reference in Models/Person.cs where Calculator.Subtract is called" # check for a second time, since the first call may trigger initialization and change the state of the LS