From 017b2657d0576ffb5c3c5136219e425eb51ddf6c Mon Sep 17 00:00:00 2001 From: Jakob Michael Werner Date: Wed, 15 Apr 2026 03:42:37 -0500 Subject: [PATCH 1/2] feat: add Gleam language support Add support for the Gleam programming language using the built-in language server invoked via `gleam lsp`. Requires the Gleam compiler to be installed and available on PATH. - Add GLEAM enum variant to Language with *.gleam file matcher - Create GleamLanguageServer with DependencyProvider pattern - Update README and programming languages docs - Add pytest marker for gleam tests Fixes #1334 --- README.md | 2 +- docs/01-about/020_programming-languages.md | 2 + pyproject.toml | 1 + .../language_servers/gleam_language_server.py | 145 ++++++++++++++++++ src/solidlsp/ls_config.py | 7 + 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 src/solidlsp/language_servers/gleam_language_server.py diff --git a/README.md b/README.md index 0473535a7..56e39fcb4 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ Serena incorporates a powerful abstraction layer for the integration of language The underlying language servers are typically open-source projects or at least freely available for use. When using Serena's language server backend, we provide **support for over 40 programming languages**, including -AL, Ansible, Bash, C#, C/C++, Clojure, Crystal, Dart, Elixir, Elm, Erlang, Fortran, F#, GLSL, Go, Groovy, Haskell, Haxe, HLSL, Java, JavaScript, Julia, Kotlin, Lean 4, Lua, Luau, Markdown, MATLAB, mSL, Nix, OCaml, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, Solidity, Swift, TOML, TypeScript, WGSL, YAML, and Zig. +AL, Ansible, Bash, C#, C/C++, Clojure, Crystal, Dart, Elixir, Elm, Erlang, Fortran, F#, Gleam, GLSL, Go, Groovy, Haskell, Haxe, HLSL, Java, JavaScript, Julia, Kotlin, Lean 4, Lua, Luau, Markdown, MATLAB, mSL, Nix, OCaml, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, Solidity, Swift, TOML, TypeScript, WGSL, YAML, and Zig. ### The Serena JetBrains Plugin diff --git a/docs/01-about/020_programming-languages.md b/docs/01-about/020_programming-languages.md index 999782ee1..7ec03cd43 100644 --- a/docs/01-about/020_programming-languages.md +++ b/docs/01-about/020_programming-languages.md @@ -52,6 +52,8 @@ Some languages require additional installations or setup steps, as noted. (requires Elixir installation; Expert language server is downloaded automatically) * **Elm** (requires Elm compiler) +* **Gleam** + (requires [Gleam](https://gleam.run/getting-started/installing/) compiler; uses the built-in language server via `gleam lsp`) * **Erlang** (requires installation of beam and [erlang_ls](https://github.com/erlang-ls/erlang_ls); experimental, might be slow or hang) * **F#** diff --git a/pyproject.toml b/pyproject.toml index 004e130be..cd8fd5cb4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -326,6 +326,7 @@ markers = [ "luau: language server running for Luau", "nix: language server running for Nix", "dart: language server running for Dart", + "gleam: language server running for Gleam", "erlang: language server running for Erlang", "ocaml: language server running for OCaml and Reason", "scala: language server running for Scala", diff --git a/src/solidlsp/language_servers/gleam_language_server.py b/src/solidlsp/language_servers/gleam_language_server.py new file mode 100644 index 000000000..02570f5e1 --- /dev/null +++ b/src/solidlsp/language_servers/gleam_language_server.py @@ -0,0 +1,145 @@ +""" +Provides Gleam specific instantiation of the LanguageServer class. +Uses the language server built into the Gleam compiler (gleam lsp). +""" + +import logging +import os +import pathlib +import shutil +import threading + +from overrides import override + +from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.settings import SolidLSPSettings + +log = logging.getLogger(__name__) + + +class GleamLanguageServer(SolidLanguageServer): + """ + Provides Gleam specific instantiation of the LanguageServer class. + + Uses the language server built into the Gleam compiler, invoked via ``gleam lsp``. + Requires the Gleam compiler to be installed and available on PATH. + See https://gleam.run/getting-started/installing/ for installation instructions. + """ + + def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings): + super().__init__( + config, + repository_root_path, + None, + "gleam", + solidlsp_settings, + ) + self.server_ready = threading.Event() + + def _create_dependency_provider(self) -> LanguageServerDependencyProvider: + return self.DependencyProvider(self._custom_settings, self._ls_resources_dir) + + class DependencyProvider(LanguageServerDependencyProviderSinglePath): + def _get_or_install_core_dependency(self) -> str: + """Find the Gleam compiler on PATH.""" + path = shutil.which("gleam") + if path is None: + raise RuntimeError( + "Gleam is not installed or not in PATH.\n" + "Please install Gleam from https://gleam.run/getting-started/installing/\n" + "and make sure the 'gleam' binary is available on your PATH." + ) + return path + + def _create_launch_command(self, core_path: str) -> list[str]: + return [core_path, "lsp"] + + @override + def is_ignored_dirname(self, dirname: str) -> bool: + return super().is_ignored_dirname(dirname) or dirname in ["build", "_gleam_artefacts"] + + @staticmethod + def _get_initialize_params(repository_absolute_path: str) -> InitializeParams: + root_uri = pathlib.Path(repository_absolute_path).as_uri() + initialize_params = { + "locale": "en", + "capabilities": { + "textDocument": { + "synchronization": {"didSave": True, "dynamicRegistration": True}, + "definition": {"dynamicRegistration": True, "linkSupport": True}, + "references": {"dynamicRegistration": True}, + "documentSymbol": { + "dynamicRegistration": True, + "hierarchicalDocumentSymbolSupport": True, + "symbolKind": {"valueSet": list(range(1, 27))}, + }, + "completion": { + "dynamicRegistration": True, + "completionItem": { + "snippetSupport": True, + "documentationFormat": ["markdown", "plaintext"], + }, + }, + "hover": { + "dynamicRegistration": True, + "contentFormat": ["markdown", "plaintext"], + }, + "codeAction": { + "dynamicRegistration": True, + "codeActionLiteralSupport": { + "codeActionKind": { + "valueSet": ["quickfix", "refactor", "source"], + } + }, + }, + }, + "workspace": { + "workspaceFolders": True, + "didChangeConfiguration": {"dynamicRegistration": True}, + "configuration": True, + }, + }, + "processId": os.getpid(), + "rootPath": repository_absolute_path, + "rootUri": root_uri, + "workspaceFolders": [ + { + "uri": root_uri, + "name": os.path.basename(repository_absolute_path), + } + ], + } + return initialize_params # type: ignore[return-value] + + def _start_server(self) -> None: + """Start the Gleam language server process.""" + + def register_capability_handler(_params: dict) -> None: + return + + def window_log_message(msg: dict) -> None: + log.info(f"LSP: window/logMessage: {msg}") + + def do_nothing(_params: dict) -> None: + return + + self.server.on_request("client/registerCapability", register_capability_handler) + self.server.on_notification("window/logMessage", window_log_message) + self.server.on_notification("$/progress", do_nothing) + self.server.on_notification("textDocument/publishDiagnostics", do_nothing) + + log.info("Starting Gleam language server process") + self.server.start() + initialize_params = self._get_initialize_params(self.repository_root_path) + + log.info("Sending initialize request from LSP client to LSP server and awaiting response") + init_response = self.server.send.initialize(initialize_params) + + capabilities = init_response["capabilities"] + log.info(f"Gleam language server capabilities: {list(capabilities.keys())}") + assert "textDocumentSync" in capabilities, "textDocumentSync capability missing" + + self.server.notify.initialized({}) + self.server_ready.set() diff --git a/src/solidlsp/ls_config.py b/src/solidlsp/ls_config.py index ff1378944..c5e71f7ea 100644 --- a/src/solidlsp/ls_config.py +++ b/src/solidlsp/ls_config.py @@ -60,6 +60,7 @@ class Language(str, Enum): Supports .luau files. Configure via .luaurc in the project root. """ NIX = "nix" + GLEAM = "gleam" ERLANG = "erlang" OCAML = "ocaml" AL = "al" @@ -262,6 +263,8 @@ def get_source_fn_matcher(self) -> FilenameMatcher: return FilenameMatcher("*.luau") case self.NIX: return FilenameMatcher("*.nix") + case self.GLEAM: + return FilenameMatcher("*.gleam") case self.ERLANG: return FilenameMatcher("*.erl", "*.hrl", "*.escript", "*.config", "*.app", "*.app.src") case self.OCAML: @@ -468,6 +471,10 @@ def get_ls_class(self) -> type["SolidLanguageServer"]: return LuauLanguageServer + case self.GLEAM: + from solidlsp.language_servers.gleam_language_server import GleamLanguageServer + + return GleamLanguageServer case self.ERLANG: from solidlsp.language_servers.erlang_language_server import ErlangLanguageServer From 13ce1967db95e936460147834b78776fbc7d7982 Mon Sep 17 00:00:00 2001 From: Jakob Michael Werner Date: Sun, 26 Apr 2026 22:03:07 -0500 Subject: [PATCH 2/2] refactor: remove unnecessary dependency provider for Gleam Gleam LSP uses the system-installed compiler; there is nothing to download. Pass ProcessLaunchInfo directly to __init__ instead of going through LanguageServerDependencyProvider. --- .../language_servers/gleam_language_server.py | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/solidlsp/language_servers/gleam_language_server.py b/src/solidlsp/language_servers/gleam_language_server.py index 02570f5e1..a3e31e08d 100644 --- a/src/solidlsp/language_servers/gleam_language_server.py +++ b/src/solidlsp/language_servers/gleam_language_server.py @@ -11,9 +11,10 @@ from overrides import override -from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer +from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings log = logging.getLogger(__name__) @@ -29,33 +30,22 @@ class GleamLanguageServer(SolidLanguageServer): """ def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings): + gleam_path = shutil.which("gleam") + if gleam_path is None: + raise RuntimeError( + "Gleam is not installed or not in PATH.\n" + "Please install Gleam from https://gleam.run/getting-started/installing/\n" + "and make sure the 'gleam' binary is available on your PATH." + ) super().__init__( config, repository_root_path, - None, + ProcessLaunchInfo(cmd=[gleam_path, "lsp"], cwd=repository_root_path), "gleam", solidlsp_settings, ) self.server_ready = threading.Event() - def _create_dependency_provider(self) -> LanguageServerDependencyProvider: - return self.DependencyProvider(self._custom_settings, self._ls_resources_dir) - - class DependencyProvider(LanguageServerDependencyProviderSinglePath): - def _get_or_install_core_dependency(self) -> str: - """Find the Gleam compiler on PATH.""" - path = shutil.which("gleam") - if path is None: - raise RuntimeError( - "Gleam is not installed or not in PATH.\n" - "Please install Gleam from https://gleam.run/getting-started/installing/\n" - "and make sure the 'gleam' binary is available on your PATH." - ) - return path - - def _create_launch_command(self, core_path: str) -> list[str]: - return [core_path, "lsp"] - @override def is_ignored_dirname(self, dirname: str) -> bool: return super().is_ignored_dirname(dirname) or dirname in ["build", "_gleam_artefacts"]