From 962fb486b69fafaf5b69ca92fdbd0d2c5922a1ab Mon Sep 17 00:00:00 2001 From: Jordan Munch O'Hare Date: Sun, 4 Jan 2026 12:24:04 +0100 Subject: [PATCH] fix(elm): resolve ELM_HOME path and wait for workspace initialization (#885) - Convert relative ELM_HOME to absolute path based on repository root, fixing package discovery when devbox uses relative paths like '.elm' - Wait for textDocument/publishDiagnostics before marking server ready, ensuring workspace is fully scanned before accepting LSP requests - Use shutil.which() for elm tool paths in initialization options Fixes 'No Elm workspace contains' errors when elm-language-server starts before workspace indexing completes. Co-authored-by: Jordan Munch O'Hare --- .../language_servers/elm_language_server.py | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/solidlsp/language_servers/elm_language_server.py b/src/solidlsp/language_servers/elm_language_server.py index 0a4c25bd..e8b86938 100644 --- a/src/solidlsp/language_servers/elm_language_server.py +++ b/src/solidlsp/language_servers/elm_language_server.py @@ -32,8 +32,23 @@ class ElmLanguageServer(SolidLanguageServer): Creates an ElmLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ elm_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings) + + # Resolve ELM_HOME to absolute path if it's set to a relative path + env = {} + elm_home = os.environ.get("ELM_HOME") + if elm_home: + if not os.path.isabs(elm_home): + # Convert relative ELM_HOME to absolute based on repository root + elm_home = os.path.abspath(os.path.join(repository_root_path, elm_home)) + env["ELM_HOME"] = elm_home + log.info(f"Using ELM_HOME: {elm_home}") + super().__init__( - config, repository_root_path, ProcessLaunchInfo(cmd=elm_lsp_executable_path, cwd=repository_root_path), "elm", solidlsp_settings + config, + repository_root_path, + ProcessLaunchInfo(cmd=elm_lsp_executable_path, cwd=repository_root_path, env=env), + "elm", + solidlsp_settings, ) self.server_ready = threading.Event() @@ -119,9 +134,9 @@ class ElmLanguageServer(SolidLanguageServer): }, }, "initializationOptions": { - "elmPath": "elm", - "elmFormatPath": "elm-format", - "elmTestPath": "elm-test", + "elmPath": shutil.which("elm") or "elm", + "elmFormatPath": shutil.which("elm-format") or "elm-format", + "elmTestPath": shutil.which("elm-test") or "elm-test", "skipInstallPackageConfirmation": True, "onlyUpdateDiagnosticsOnSave": False, }, @@ -141,6 +156,7 @@ class ElmLanguageServer(SolidLanguageServer): """ Starts the Elm Language Server, waits for the server to be ready and yields the LanguageServer instance. """ + workspace_ready = threading.Event() def do_nothing(params: dict) -> None: return @@ -148,9 +164,14 @@ class ElmLanguageServer(SolidLanguageServer): def window_log_message(msg: dict) -> None: log.info(f"LSP: window/logMessage: {msg}") + def on_diagnostics(params: dict) -> None: + # Receiving diagnostics indicates the workspace has been scanned + log.info("LSP: Received diagnostics notification, workspace is ready") + workspace_ready.set() + self.server.on_notification("window/logMessage", window_log_message) self.server.on_notification("$/progress", do_nothing) - self.server.on_notification("textDocument/publishDiagnostics", do_nothing) + self.server.on_notification("textDocument/publishDiagnostics", on_diagnostics) log.info("Starting Elm server process") self.server.start() @@ -167,7 +188,13 @@ class ElmLanguageServer(SolidLanguageServer): assert "documentSymbolProvider" in init_response["capabilities"] self.server.notify.initialized({}) - log.info("Elm server initialized successfully, waiting for workspace scan...") + log.info("Elm server initialized, waiting for workspace scan...") + + # Wait for workspace to be scanned (indicated by receiving diagnostics) + if workspace_ready.wait(timeout=30.0): + log.info("Elm server workspace scan completed") + else: + log.warning("Timeout waiting for Elm workspace scan, proceeding anyway") self.server_ready.set() self.completions_available.set()