Skip to content

Add Gleam language server support#1357

Open
Koushik-Salammagari wants to merge 9 commits intooraios:mainfrom
Koushik-Salammagari:feat/gleam-language-server
Open

Add Gleam language server support#1357
Koushik-Salammagari wants to merge 9 commits intooraios:mainfrom
Koushik-Salammagari:feat/gleam-language-server

Conversation

@Koushik-Salammagari
Copy link
Copy Markdown
Contributor

Closes #1334

Summary

Adds support for the Gleam programming language by integrating its built-in language server (gleam lsp). The LSP is bundled with the Gleam compiler — no separate installation is required.

Files changed

File Change
src/solidlsp/language_servers/gleam_language_server.py New GleamLanguageServer class
src/solidlsp/ls_config.py Language.GLEAM enum value, *.gleam file matcher, get_ls_class registration
test/resources/repos/gleam/test_repo/ Minimal Gleam project: gleam.toml + two source modules
test/solidlsp/gleam/test_gleam_basic.py Symbol, within-file reference, cross-file reference, and bare-name tests
pyproject.toml gleam pytest marker
docs/01-about/020_programming-languages.md Gleam entry in language list
README.md Gleam added to language list

Implementation notes

  • GleamLanguageServer follows the same pattern as CrystalLanguageServer (requires user-installed binary, no auto-download).
  • Launch command: ["gleam", "lsp"] — the LSP is part of the compiler since Gleam v0.21.
  • Build artifacts are ignored via is_ignored_dirname (build/, _build/).
  • Tests are skipped automatically when gleam is not on PATH (shutil.which("gleam") is None).

Test repository

test/resources/repos/gleam/test_repo/
├── gleam.toml                 # depends on gleam_stdlib
└── src/
    ├── calculator.gleam       # Calculator type + add/subtract/multiply/format_result/describe
    └── utils.gleam            # format_output + is_non_empty (cross-file reference target)

CI note

A GitHub Actions step for installing the Gleam compiler will be needed to run these tests in CI (similar to how other language servers are set up in .github/workflows/pytest.yml). I'm happy to add that in a follow-up or in this PR if preferred — let me know.

Comment thread test/solidlsp/gleam/test_gleam_basic.py Outdated
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols


@pytest.mark.skipif(shutil.which("gleam") is None, reason="Gleam compiler is not available")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. There is another one open for gleam at the moment in #1334 but that one has no tests at all.

The condition here is insufficient, we need tests running in CI. Pls adjust the pytest workflow to set up gleam (maybe there is a suitable action) and adjust this condition to

Suggested change
@pytest.mark.skipif(shutil.which("gleam") is None, reason="Gleam compiler is not available")
from test.conftest import is_ci
@pytest.mark.skipif(shutil.which("gleam") is None and not is_ci, reason="Gleam compiler is not available")

@Koushik-Salammagari
Copy link
Copy Markdown
Contributor Author

@MischaPanch — addressed both points from your review:

  1. Added leopard-io/setup-gleam@v1 to the pytest workflow so Gleam is installed in CI.
  2. Updated the skipif condition to use is_ci from test.conftest as suggested.

Could you take another look when you get a chance? Happy to make any further adjustments.

@MischaPanch
Copy link
Copy Markdown
Contributor

Pls resolve the conflicts with main so CI can start

@Koushik-Salammagari Koushik-Salammagari force-pushed the feat/gleam-language-server branch from 7a9a429 to af6706e Compare April 16, 2026 16:29
@Koushik-Salammagari
Copy link
Copy Markdown
Contributor Author

@MischaPanch — rebased onto the latest main and resolved all conflicts (upstream had added mSL in the same spots; kept both). CI should now be able to run. Let me know if anything else needs attention!

@MischaPanch
Copy link
Copy Markdown
Contributor

@Koushik-Salammagari

Error: Unable to resolve action leopard-io/setup-gleam, repository not found

@MischaPanch
Copy link
Copy Markdown
Contributor

Seems like the action is broken :(

Run gleam-lang/setup-gleam@v1.0.2
/Users/runner/work/_actions/gleam-lang/setup-gleam/v1.0.2/main.sh latest
Installing sandboxed asdf version manager
Cloning into '/var/folders/tb/y368xp_x10s3ty1b_mtl5mxr0000gn/T/tmp.yOxWle0Ayt'...
warning: refs/tags/v0.7.5 d9aed0a2f6d958596a790a5f138ec710bb2cdc80 is not a commit!
Installing asdf Gleam plugin
Downloading Gleam latest
Could not find archive at URL https://github.com/gleam-lang/gleam/releases/download/vlatest/gleam-vlatest-aarch64-apple-darwin.tar.gz
Installing Gleam to /usr/local/bin/gleam
cp: /var/folders/tb/y368xp_x10s3ty1b_mtl5mxr0000gn/T/tmp.yOxWle0Ayt/installs/gleam/latest/bin/gleam: No such file or directory

@Koushik-Salammagari
Copy link
Copy Markdown
Contributor Author

@MischaPanch — the gleam-lang/setup-gleam action is broken upstream: it resolves latest as the literal string vlatest in the download URL, causing the No such file or directory error.

Replaced it with a direct binary install that queries the GitHub API for the real latest tag and downloads the appropriate platform binary (linux-musl, aarch64-apple-darwin / x86_64-apple-darwin, windows-msvc). Same pattern used by other language installs in this workflow (e.g. ZLS, Verible).

Should unblock CI now — let me know if anything else needs attention!

@Koushik-Salammagari Koushik-Salammagari force-pushed the feat/gleam-language-server branch from c475b38 to 4a9df49 Compare April 16, 2026 18:24
@MischaPanch
Copy link
Copy Markdown
Contributor

I fixed the failing lean-caching test on main (by removing it). Gleam tests fail here. Pls merge main, I will review once tests pass in CI

Integrates the Gleam language server (bundled with the Gleam compiler
as `gleam lsp`) into Serena. No separate LSP installation is required
beyond the `gleam` compiler binary being on PATH.

Changes:
- src/solidlsp/language_servers/gleam_language_server.py: new server class
  with gleam deps download before LSP start for stdlib availability
- src/solidlsp/ls_config.py: GLEAM enum, FilenameMatcher(*.gleam), get_ls_class
- test/resources/repos/gleam/test_repo/: minimal Gleam project with two modules
- test/solidlsp/gleam/test_gleam_basic.py: symbol, within-file reference,
  cross-file reference, and bare-name tests
- pyproject.toml: gleam pytest marker
- docs/01-about/020_programming-languages.md: Gleam entry
- README.md: Gleam added to language list
- CHANGELOG.md: Gleam entry

Closes oraios#1334
Downloads the latest Gleam release binary directly from GitHub Releases
(linux-musl, aarch64/x86_64 macOS, windows-msvc). The gleam-lang/setup-gleam
action is broken upstream so a direct download is used instead, matching
the pattern already used for ZLS and Verible in this workflow.
Gleam LSP compiles the project in the background after receiving
initialized{}. Requesting textDocument/documentSymbol before this
finishes returns empty arrays. Wait for the first $$/progress end
notification (which Gleam emits when the workspace compile is done)
before returning from _start_server, with a 30 s fallback timeout.
…lysis

The previous implementation set _compile_ready on the first $/progress end
notification. Gleam LSP emits one begin/end pair per compilation phase and
may open multiple phases sequentially; waking up after the first end left
subsequent files (e.g. utils.gleam) unanalysed, causing empty document-symbol
responses and failing test_find_symbol / test_find_references_across_files.

Now all active tokens are tracked in a set. _compile_ready is cleared whenever
a new begin arrives and set only when every token has ended. A short 5-second
bootstrap wait handles cached builds where no progress notifications are sent.
Timeout raised to 60 s to accommodate slower CI runners.
@Koushik-Salammagari Koushik-Salammagari force-pushed the feat/gleam-language-server branch from b5ba243 to d48ee9d Compare April 17, 2026 17:29
@Koushik-Salammagari
Copy link
Copy Markdown
Contributor Author

@MischaPanch — rebased onto latest main and fixed the Gleam test failures.

Root cause: The previous implementation called threading.Event.set() on the first $/progress end notification. Gleam LSP emits one begin/end pair per compilation phase and may run several phases sequentially. Waking up after the first end left subsequent files (e.g. utils.gleam) unanalysed at the point documentSymbol was requested — returning empty results and failing test_find_symbol and test_find_references_across_files.

Fix (commit d48ee9df):

  • All active progress tokens are now tracked in a set.
  • _compile_ready is cleared whenever a new begin arrives and set only when every token has ended.
  • A 5-second bootstrap wait handles cached builds where no progress notifications are sent.
  • Timeout raised to 60 s for slower CI runners.

CI should pass now — let me know if anything else needs attention!

@MischaPanch
Copy link
Copy Markdown
Contributor

@Koushik-Salammagari you have access to the falling GH actions, right?

…t symbols

When textDocument/didOpen is sent for a Gleam file the LSP may start a
recompilation ($/progress begin/end). The documentSymbol request was
sent immediately, racing with the compile and returning an empty list.
The empty list was then cached in _document_symbols_cache, permanently
hiding symbols for subsequent requests within the same session.

Two fixes:
1. GleamLanguageServer._request_document_symbols: open the file with
   didOpen, sleep 200ms to let any $/progress begin arrive, then wait
   for _compile_ready before forwarding to the base implementation.
2. ls.py request_document_symbols: skip caching empty DocumentSymbols
   in _document_symbols_cache (mirrors the existing guard in
   _request_document_symbols for _raw_document_symbols_cache).
@Koushik-Salammagari
Copy link
Copy Markdown
Contributor Author

@MischaPanch — yes I can see the failures. The OCaml test failures are HTTP 504s from GitHub's package servers while fetching dune/merlin/fiber dependencies — not related to my changes. Could you re-run the failed jobs? I don't have rights to trigger a re-run on the upstream repo

overrides library enforces return type compatibility at runtime.
list | None was rejected; must match base class signature exactly:
list[SymbolInformation] | list[DocumentSymbol] | None.
@MischaPanch
Copy link
Copy Markdown
Contributor

@Koushik-Salammagari as per CI, gleam tests still fail, and gleam installation on macos fails

FAILED test/solidlsp/gleam/test_gleam_basic.py::TestGleamLanguageServer::test_find_symbol[gleam] - AssertionError: 'format_output' function not found in symbol tree
assert False
 +  where False = <function SymbolUtils.symbol_tree_contains_name at 0x7fc67fd2d940>([{'children': [{'children': [{'children': [], 'kind': <SymbolKind.File: 1>, 'location': {...}, 'name': 'utils', ...}, {'children': [...], 'kind': <SymbolKind.File: 1>, 'location': {...}, 'name': 'calculator', ...}], 'kind': <SymbolKind.Package: 4>, 'location': {'absolutePath': '/home/runner/work/serena/serena/test/resources/repos/gleam/test_repo/src', 'range': {'end': {...}, 'start': {...}}, 'relativePath': 'src', 'uri': 'file:///home/runner/work/serena/serena/test/resources/repos/gleam/test_repo/src'}, 'name': 'src', ...}], 'kind': <SymbolKind.Package: 4>, 'location': {'absolutePath': '/home/runner/work/serena/serena/test/resources/repos/gleam/test_repo', 'range': {'end': {'character': 0, 'line': 0}, 'start': {'character': 0, 'line': 0}}, 'relativePath': '.', 'uri': 'file:///home/runner/work/serena/serena/test/resources/repos/gleam/test_repo'}, 'name': 'test_repo'}], 'format_output')
 +    where <function SymbolUtils.symbol_tree_contains_name at 0x7fc67fd2d940> = SymbolUtils.symbol_tree_contains_name
FAILED test/solidlsp/gleam/test_gleam_basic.py::TestGleamLanguageServer::test_find_references_across_files[gleam] - AssertionError: Could not find 'format_output' symbol in utils.gleam

- macOS CI install replaced with `brew install gleam` (binary download
  was unreliable on GitHub-hosted runners)
- Added `_get_symbols_with_retry` helper: retries documentSymbol up to 5
  times with 0.5s*attempt backoff when the LSP returns empty results.
  Gleam LSP does not always emit $/progress for per-file analysis after
  textDocument/didOpen, so the previous single-shot request could race
  and return empty children for files not yet analysed (e.g. utils.gleam).
@MischaPanch
Copy link
Copy Markdown
Contributor

@Koushik-Salammagari you are still on this, right? reference-finding tests fail, maybe more waiting would help (we have general mechanisms for that in the base class), or maybe the LS needs to be initialized in a particular way

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for Gleam

2 participants