Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
693422c
initial lsp
aspeddro Dec 1, 2024
34bcd22
Merge branch 'master' into lsp
aspeddro Dec 30, 2024
7f209ef
Merge branch 'master' into lsp
aspeddro Mar 17, 2025
e203450
Merge branch 'master' into lsp
aspeddro Jun 20, 2025
e1273d6
Merge branch 'master' into lsp
aspeddro Apr 28, 2026
129af21
update
aspeddro Apr 29, 2026
6b65a25
add state
aspeddro Apr 30, 2026
e2989d0
Extract server I/O into Server module and simplify handlers
aspeddro May 12, 2026
a99abc6
Add parse_implementation_from_source and refactor LSP server
aspeddro May 12, 2026
2529f30
Merge branch 'master' into lsp
aspeddro May 13, 2026
365836c
Split LSP into bin/src layout and add hover integration test
aspeddro May 14, 2026
1692629
Refactor analysis to decouple I/O from core logic
aspeddro May 16, 2026
743126f
Delegate hover logic to `Analysis.Commands.hover`
aspeddro May 16, 2026
0e522be
Rename `tokenModifiersString` to `tokenModifiers` with correct type
aspeddro May 16, 2026
9da83c8
Merge branch 'master' into lsp
aspeddro May 22, 2026
9df9079
Update dune-project
aspeddro May 22, 2026
1b65e21
Merge branch 'master' into lsp
aspeddro May 30, 2026
768a040
Merge branch 'master' into lsp
aspeddro May 31, 2026
29cd3d7
Refactor LSP test infrastructure
aspeddro May 31, 2026
a20da0d
ci: add linux-headers
aspeddro May 31, 2026
5a57bbc
ci: add linux-headers-generic
aspeddro May 31, 2026
9442c4b
ci: linux-libc-dev
aspeddro May 31, 2026
64dffd8
ci: add build-essential
aspeddro May 31, 2026
e0f6296
ci: add build-essential and linux-libc-dev
aspeddro May 31, 2026
8cebaf2
ci: update cache version to v5
aspeddro May 31, 2026
85138b1
ci: make Linux headers visible to musl-gcc
aspeddro Jun 1, 2026
ee6de86
Merge branch 'master' into lsp
aspeddro Jun 1, 2026
66b69fe
ci: support arm and disable test for build for ocaml 5.0
aspeddro Jun 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 57 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@ jobs:
exe-suffix: ".exe"
dune-profile: release

# Disable for now. eio and eio_main require ocaml >= 5.2.0
# Verify that the compiler still builds with the oldest OCaml version we support.
- os: ubuntu-24.04
ocaml_compiler: ocaml-variants.5.0.0+options,ocaml-option-static
node-target: linux-x64
rust-target: x86_64-unknown-linux-musl
dune-profile: static
# - os: ubuntu-24.04
# ocaml_compiler: ocaml-variants.5.0.0+options,ocaml-option-static
# node-target: linux-x64
# rust-target: x86_64-unknown-linux-musl
# dune-profile: static

runs-on: ${{matrix.os}}

Expand Down Expand Up @@ -107,7 +108,7 @@ jobs:
# https://github.com/ocaml/setup-ocaml/blob/2f57267f071bc8547dfcb9433ff21d44fffef190/packages/setup-ocaml/src/unix.ts#L48
# plus OPAM wants cmake
packages: bubblewrap darcs g++-multilib gcc-multilib mercurial musl-tools rsync cmake
version: v4
version: v5

- name: Restore rewatch build cache
id: rewatch-build-cache
Expand Down Expand Up @@ -176,6 +177,56 @@ jobs:
C:\.opam
key: ${{ env.opam_cache_key }}

# The static OCaml switch uses musl-gcc. linux-libc-dev installs Linux
# headers under /usr/include, but musl-gcc searches the musl include dir.
# Link the Linux headers into musl's include path so packages with C stubs
# such as uring can include <linux/...> headers.
- name: Make Linux headers visible to musl-gcc
if: runner.os == 'Linux'
run: |
set -eux

# Get the GNU multiarch triplet for the current machine.
# Examples:
# x86_64-linux-gnu
# aarch64-linux-gnu
GNU_MULTIARCH="$(gcc -print-multiarch)"

# Convert the GNU triplet into the musl include directory name.
# Examples:
# x86_64-linux-gnu -> x86_64-linux-musl
# aarch64-linux-gnu -> aarch64-linux-musl
MUSL_MULTIARCH="${GNU_MULTIARCH%-gnu}-musl"

# musl-gcc searches this include directory.
MUSL_INCLUDE="/usr/include/${MUSL_MULTIARCH}"

# Linux arch-specific asm headers are installed here by linux-libc-dev.
GNU_ASM="/usr/include/${GNU_MULTIARCH}/asm"

# Ensure the musl include directory exists.
sudo mkdir -p "$MUSL_INCLUDE"

# Remove old paths first.
# This avoids silently keeping broken/stale symlinks from previous runs.
sudo rm -rf "$MUSL_INCLUDE/linux"
sudo rm -rf "$MUSL_INCLUDE/asm"
sudo rm -rf "$MUSL_INCLUDE/asm-generic"

# Expose Linux UAPI headers to musl-gcc.
# This fixes packages that include headers like <linux/swab.h>.
sudo ln -s /usr/include/linux "$MUSL_INCLUDE/linux"

# Expose architecture-specific asm headers to musl-gcc.
sudo ln -s "$GNU_ASM" "$MUSL_INCLUDE/asm"

# Expose generic asm headers used by many Linux headers.
sudo ln -s /usr/include/asm-generic "$MUSL_INCLUDE/asm-generic"

# Smoke test: fail early if musl-gcc still cannot find Linux headers.
echo '#include <linux/swab.h>' > /tmp/test.c
musl-gcc -c /tmp/test.c -o /tmp/test.o

- name: Use OCaml ${{matrix.ocaml_compiler}}
uses: ocaml/setup-ocaml@v3.6.0
if: steps.cache-opam-env.outputs.cache-hit != 'true'
Expand Down
2 changes: 1 addition & 1 deletion dune
Original file line number Diff line number Diff line change
@@ -1 +1 @@
(dirs compiler tests analysis tools)
(dirs compiler tests analysis tools lsp)
15 changes: 15 additions & 0 deletions dune-project
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,18 @@
(yojson
(= 3.0.0))
(odoc :with-doc)))

(package
(name rescript-language-server)
(synopsis "ReScript LSP")
(depends
(ocaml
(>= 4.10))
(lsp
(>= 1.22.0))
(eio
(>= 1.3))
(eio_main
(>= 1.3))
analysis
dune))
5 changes: 5 additions & 0 deletions lsp/bin/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
(executable
(name main)
(package rescript-language-server)
(public_name rescript-language-server)
(libraries rescript_language_server))
1 change: 1 addition & 0 deletions lsp/bin/main.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let () = Rescript_language_server.main ()
Empty file added lsp/bin/main.mli
Empty file.
Empty file added lsp/src/configuration.ml
Empty file.
5 changes: 5 additions & 0 deletions lsp/src/diagnostics.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module UriMap = Map.Make (Lsp.Uri)

type t = Lsp.Types.Diagnostic.t list UriMap.t

let create () = UriMap.empty
30 changes: 30 additions & 0 deletions lsp/src/document_store.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
(* module UriMap = Map.Make (Lsp.Uri) *)

type document = {text: string; version: int}

type t = {documents: (Lsp.Uri.t, document) Hashtbl.t}

let create () = {documents = Hashtbl.create 25}

let open_document t ~uri ~text ~version =
Hashtbl.add t.documents uri {text; version};
t

let update_document t ~uri ~text ~version =
(match Hashtbl.find_opt t.documents uri with
| None ->
raise
(Failure (Printf.sprintf "Document not found: %s" (Lsp.Uri.to_string uri)))
| Some _ -> Hashtbl.replace t.documents uri {text; version});
t

let remove_document t ~uri =
Hashtbl.remove t.documents uri;
t

let get_document t ~uri =
match Hashtbl.find_opt t.documents uri with
| Some doc -> doc
| None ->
raise
(Failure (Printf.sprintf "Document not found: %s" (Lsp.Uri.to_string uri)))
5 changes: 5 additions & 0 deletions lsp/src/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
(library
(name rescript_language_server)
(libraries lsp eio eio_main analysis)
(flags
(-w "-9")))
16 changes: 16 additions & 0 deletions lsp/src/hover.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
open Lsp.Types

let create ~(position : Position.t) ~(uri : DocumentUri.t)
(server : State.t Server.t) =
let path = DocumentUri.to_path uri in
let pos = (position.line, position.character) in

(* NOTE: Should be a config *)
let supportsMarkdownLinks = true in
let debug = false in
let open Analysis in
let source = (Document_store.get_document ~uri server.state.store).text in
let kindFile = Files.classifySourceFile path in
let full = Cmt.loadFullCmtFromPath ~path in

Commands.hover ~source ~kindFile ~pos ~debug ~supportsMarkdownLinks ~full
79 changes: 79 additions & 0 deletions lsp/src/rescript_language_server.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
let initialization (client_capabilities : Lsp.Types.ClientCapabilities.t) =
let open Lsp.Types in
let textDocumentSync =
`TextDocumentSyncOptions
(TextDocumentSyncOptions.create ~openClose:true
~change:TextDocumentSyncKind.Full ~willSave:false
~save:(`SaveOptions (SaveOptions.create ~includeText:false ()))
~willSaveWaitUntil:false ())
in
let capabilities =
ServerCapabilities.create ~textDocumentSync ~hoverProvider:(`Bool true) ()
in
let serverInfo =
let version = "2.0.0-aplha.1" in
InitializeResult.create_serverInfo ~name:"rescript-language-server" ~version
()
in
InitializeResult.create ~capabilities ~serverInfo ()

let on_initialize (params : Lsp.Types.InitializeParams.t) (state : State.t) =
(* TODO:
* Find root project (rescript.json, package.json) using InitializeParams.workspaceFolders and save in State.t
* See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initializeParams
* If not found rescript.json kill the server?
* Save initializationOptions in State.t
* This options are: askToStartBuild, codeLens.enable, inlayHints.enable, etc..
* Collect compiler diagnostics (syntax and type)?
*)
let diagnostics = Diagnostics.create () in
let initialization_info = initialization params.capabilities in
let state = State.initialize state ~params ~diagnostics in
(initialization_info, state)

let on_request (Lsp.Client_request.E request) (server : State.t Server.t) =
let state = Server.state server in
let ok value = Ok (Lsp.Client_request.yojson_of_result request value) in
match request with
| Lsp.Client_request.Initialize params ->
let initialization_info, state = on_initialize params state in
(ok initialization_info, state)
| Shutdown -> (ok (), state)
| TextDocumentHover {position; textDocument = {uri}} ->
(ok (Hover.create ~position ~uri server), state)
| _ ->
let err =
Jsonrpc.Response.Error.make
~code:Jsonrpc.Response.Error.Code.MethodNotFound
~message:"Request method not supported" ()
in
(Error err, state)

let on_notification notification (server : State.t Server.t) =
let state = Server.state server in

match notification with
| Lsp.Client_notification.TextDocumentDidOpen
{textDocument = {uri; text; version; _}} ->
let store = Document_store.open_document ~uri ~text ~version state.store in
{state with store}
(* | TextDocumentDidChange {textDocument = {uri; version; _}; contentChanges}
-> (
match List.rev contentChanges with
| {text; _} :: _ -> state
| [] -> state) *)
| TextDocumentDidClose {textDocument = {uri; _}} ->
(* TODO:
* remove state diagnostics
* send updated diagnostics?
*)
let store = Document_store.remove_document ~uri state.store in
{state with store}
| Exit -> state
| _ -> state

let main () =
Eio_main.run (fun env ->
let state = State.create ~store:(Document_store.create ()) in
Server.listen ~input:env#stdin ~output:env#stdout ~on_request
~on_notification ~state ~env)
Loading
Loading