Experimental ReScript Language Server in OCaml#8425
Draft
aspeddro wants to merge 116 commits into
Draft
Conversation
- Move Io, Chan, and listen infrastructure to server.ml - Simplify on_request to take a packed request directly - Add basic hover response with markdown content - Rename public executable to rescript-language-server
- Add `parse_implementation_from_source` to parsing/print engine types and all engine implementations, enabling parsing from a string source rather than a filename - Use `parse_implementation_from_source` in CompletionFrontEnd - Rename package from `rescript-lsp` to `rescript-language-server` - Refactor LSP server with typed state, document store, and diagnostics - Add hover support via completion backend integration
Move LSP modules under lsp/src/ with a thin lsp/bin/ entry point, add a configuration module, and introduce a tests/lsp_tests workspace exercising hover end-to-end.
Splits `Commands.ml` into a pure layer that returns OCaml values (option, list, typed records like Protocol.hover, Protocol.signatureHelp, Protocol.completionItem) and a new `analysis/src/Cli.ml` that does the stringify-and-print step. `analysis/bin/main.ml` now dispatches to `Cli.*`, while the LSP server consumes `Commands.*` directly. Makes the parsers accept source strings: `res_driver` gains `parse_interface_from_source` alongside the existing `parse_implementation_from_source`
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. Thanks for integrating Codecov - We've got you covered ☂️ |
8 tasks
- Rename Helper module to Client - Move expected output files into workspace directories - Improve request log format to show file path:line:col - Simplify hover.ml by removing intermediate result binding - Disable in-source compilation for basic-workspace
Do not push open-buffer syntax diagnostics through textDocument/publishDiagnostics on didChange. They are already available through textDocument/diagnostic. Keep the changed URI force-published so any stale pushed syntax diagnostics are cleared when the filtered diagnostic set is empty.
1 task
fhammerschmidt
added a commit
that referenced
this pull request
Jun 22, 2026
* Omit empty document symbol children DocumentSymbol.children is optional, so only set it when a symbol has nested entries. This avoids serializing leaf symbols with an empty children array. Signed-off-by: Pedro Castro <aspeddro@gmail.com> * add transform_opt for server-side use * analysis: add state_to_yojson * packages: add dependencies * Update CHANGELOG.md * Apply codex review suggestions * Refactor cmt_viewer.dump for serve-side * Analysis cmt dump return a string instead a option --------- Signed-off-by: Pedro Castro <aspeddro@gmail.com> Co-authored-by: Florian Hammerschmidt <florianh89@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This branch introduces a standalone ReScript LSP server
(
rescript-language-server) built on top of the existinganalysislibrary.It's a separate, OCaml-side exploration alongside the Rust/rewatch-based
experiment in #8243, the two share the same goal (a LSP server for ReScript) but
approach it from different ends of the toolchain.
Why rewriting the server in OCaml is a good fit
The editor features are already implemented in the OCaml analysis library.
Hover, completion, references, rename, document symbols, code actions, and
diagnostics all depend on compiler data structures such as
.cmt/.cmti,typed trees, locations, package metadata, and compiler diagnostics. Running the
LSP server in OCaml lets the server call those APIs directly instead of shelling
out to
rescript-editor-analysis.exeor passing JSON through stdin. Thatremoves a large amount of process orchestration and serialization glue, and
keeps the LSP closer to the compiler types it needs to understand. This also
makes the server easier to evolve with the compiler. When typedtree, diagnostic
formats, package discovery, or
.cmthandling changes, the LSP can be updatedin the same language and build system as the analysis code. That reduces version
skew and makes bugs easier to reproduce with Dune/expect tests.
What's here
lsp/package split intolsp/bin(entrypoint) andlsp/src(library),with its own opam file (
rescript-language-server.opam). Depends onlsp(>=1.22.0),
eio/eio_main,ppx_deriving_yojson,ppx_expectand the in-treeanalysislibrary.Status / what's not here yet
The main objective is to first maintain feature parity with the current server
(server.ts) as much as possible. Below is a list of some requests and
notifications. Some are out of scope because they don't make sense.
initialize- client requestinitialized- client notificationshutdown- client requestexit- client notificationtextDocument/didOpen- client notificationtextDocument/didChange- client notificationtextDocument/didClose- client notificationtextDocument/didSave- client notification - It will not beimplemented for now.
textDocument/hover- client requesttextDocument/publishDiagnostics- server notificationdependency errors be published?
does not point at a precise source range. When the document is open, we
expand the diagnostic range to cover the whole document so the editor can
display a file-level diagnostic. If the document is not open, we keep the
range parsed from the compiler log, i.e.,
{start: {line: 0, character: 0}, end: {line: 0, character: 6}}let a=1.Protocol positions are zero-based, so the position is
length - 1.Analysisfor syntax errors and ignore them in the compiler logparser.
TextDocumentDidChangeandprovide instant feedback.
.compiler.log- server featurecompiler_log.ml. Seetests/build_testsfor more examples.
.sourcedirs.json- server feature.sourcedirs.jsonis alwaysgenerated with
build_rootfield for each subpackage.workspace/didChangeWatchedFiles- client notification.compiler.logfiles, which usually means a ReScript build has finished.When a watched log changes, the server re-reads every known compiler log and
republishes diagnostics so stale errors are cleared and monorepo diagnostics
stay in sync.
GlobPatternwithbaseUri(workspace root)?rescript.jsonfiles in the workspace?rescript.json. For example,suffixandpackage-specsare needed to create the code actionOpen compiled JS file. Changes independenciesimpact variousfunctionalities because they modify the state in
Analysis.Shared_types.state.client/registerCapability- server requestworkspace/didChangeWatchedFilesis commonly registereddynamically. The server does not know all file-watch patterns during the
static
initializeresponse. In this LSP, the watcher list depends onproject state, especially
.sourcedirs.json, which tells us where eachReScript build root lives. In monorepos, that means the server needs to
register watchers for compiler logs after initialization, once it has
workspace context. That means watching generated
.compiler.logfiles. Whenthe client sees one change, it sends
workspace/didChangeWatchedFiles, andthe server refreshes diagnostics.
textDocument/diagnostic- client requesttextDocument/completion- client requestcompletionItem/resolve- client requesttextDocument/signatureHelp- client requesttextDocument/definition- client requesttextDocument/declaration- client request - It will not be implementedfor now.
textDocument/typeDefinition- client requesttextDocument/implementation- client request - It will not beimplemented for now.
textDocument/references- client requestSupport v12 CMT files in analysis #8477
textDocument/documentHighlight- client request - It will not beimplemented for now.
textDocument/documentSymbol- client request"document_symbols": "on"configuration. When enabled,tree-sitter is not used for document symbols.
workspace/symbol- client request - It will not be implemented fornow.
textDocument/codeAction- client requestrescript/openCompiled)window/showDocument.rescript/createInterface)rescript/switchImplementationInterface)window/showDocumentrequest.codeAction/resolve- client request - It will not be implemented fornow.
textDocument/codeLens- client requestworkspace/codeLens/refresh- server requestcodeLens/resolve- client request - It will not be implemented fornow.
textDocument/inlayHint- client requestinlayHint/resolve- client request - It will not be implemented fornow.
textDocument/semanticTokens/full- client requesttextDocument/semanticTokens/full/delta- client request - It will notbe implemented for now.
textDocument/semanticTokens/range- client request - It will not beimplemented for now.
textDocument/rename- client requesttextDocument/prepareRename- client requesttextDocument/formatting- client requesttextDocument/rangeFormatting- client request - It will not beimplemented for now.
textDocument/onTypeFormatting- client request - It will not beimplemented for now.
textDocument/foldingRange- client request - It will not beimplemented for now.
textDocument/selectionRange- client request - It will not beimplemented for now.
textDocument/documentLink- client request - It will not beimplemented for now.
documentLink/resolve- client request - It will not be implemented fornow.
textDocument/documentColor- client request - It will not beimplemented for now.
textDocument/linkedEditingRange- client request - It will not beimplemented for now.
workspace/didChangeConfiguration- client notificationworkspace/configuration- server requestworkspace/executeCommand- client requestrescript/dumpServerStateState.t(diagnostics, document store, status, configuration,analysis state (
Analysis.Shared_types.state) and compiler config).rescript/openCompiled: Triggered by a code actionwindow/showDocument.rescript/createInterface: Triggered by a code actioninterface file if the client supports the
window/showDocumentrequest.rescript/switchImplementationInterface: Triggered by a code actionwindow/showDocumentrequest.rescript/dumpCmt: Dump cmt file contentrescript/dumpParseTreeIt will not be implemented for now.rescript/dumpTypedTreeIt will not be implemented for now.window/showDocumentrequest.LSP: support windows/showDocument zed-industries/zed#58099.
textDocument/openCompiled- client requesttextDocument/createInterface- client requesttextDocument/switchImplementationInterface: Currently it's done on theclient side, but it could be a command triggered by a code action.
rescript/startBuild- client request - How the build integrationwill be done. See last point
initialization and drop this custom request?
rescript/stopBuild?rescript/compilationFinished- server notification - Sent from theserver to the client when compilation is finished
server.ts) sends this notification, and the VSCode clientuses it to run Code Analysis. The server sends it when
.compiler.logchanges. Only VSCode uses this notification.
reanalyze may be integrated into the server instead of running on the
client side.
$/progressserver notification?rescript/compilationStatus- server notification - Sends thecompilation status from the server to the client.
bar. Only VSCode uses this notification.
$/progressserver notification?server.ts) supports stdio and node-ipc. The VSCodeclient uses node-ipc. This implementation supports only stdio.
not be implemented for now.
produced by an existing
rescript build, rather than the LSP starting ormanaging builds itself.
for now. Idea to explore.
rescript watchprocess, it's much harder to compilein memory?
Tests
tests/lsp_tests/adds a dune-driven integration test (test.ml) that bootsthe server against a real ReScript workspace (
basic-workspace/), initializesthe LSP session, sends the
initializednotification, opens source documents,and snapshots responses for representative fixtures.
The LSP test target now also runs
dune runtest, covering inline tests such as.sourcedirs.jsonbuild-root,.compiler.logparsing used by diagnostics andfile-watcher setup.
Breaking Changes
initializationOptions. This server ignoressettings coming from
initializationOptions. SeeSpec should discourage abuse of initializationOptions and didChangeConfiguration microsoft/language-server-protocol#567 (comment).
We use
workspace/configuration.Server settings
Proposed interface. Some notes:
supportMarkdownLinksis not a setting. It's a great feature, butsome clients don't have good support; Neovim and Zed are examples. Therefore,
I'm promoting it to a setting so VSCode users can enable or disable it.
signatureHelp.enable. It's a basic feature on manyservers.
Release and transition plan
Some considerations
@rescript/language-serveror another) sothe user can install it as a development dependency or globally.
such as setting the binary path.
version. Switching between extension versions is annoying.
Release Proposal
lspbranch@rescript/language-server2.0.0-dev-SHA-.0with tagdev. The current version is1.72.0(
${{ startsWith(github.event.head_commit.message, 'publish language-server') && (github.ref == 'refs/heads/lsp') }})lsp/src/version.mlandpackage.jsonversion.conflicts.
installations.
mason.nvimcan install the server usingMasonInstall rescript-language-server@dev --forceNeovim setup
lspuntil we get a stable language server.When we have a stable version we merge
lspintomaster.Some points I have questions about.
@rescript/language-serverwith the tagnextin thevscode-repo. I think it can be confusing? No?
of the compiler. Sending it along with the compiler imposes many
restrictions.
Neovim setup
This section describes how Neovim users can configure the experimental server.
The new server requires some changes to the LSP setup. Use the
root_dirhandler function instead of
root_markers.Other related topics
Refactor analysis for use on the server side
Refactor analysis CLI helpers to use source input #8466 Analysis refactor for #8425 #8478
yojsonandlsplibraries in the analysis library Use Yojson and lsp types for analysis JSON output #8436Shared_types.stateanalysis refactor: remove global stateShared_types.state#8465Relationship to #8243
#8243 collapses the build watcher and LSP into a single Rust process in rewatch,
shelling out to
rescript-editor-analysis.exeover stdin. This PR keeps the LSPon the OCaml side and uses the
analysislibrary directly. Useful as acomparison point for the architecture discussion.