mirror of
https://github.com/c-cube/linol.git
synced 2025-12-05 19:00:34 -05:00
Compare commits
26 commits
e43f2e588a
...
1b4c56b134
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b4c56b134 | ||
|
|
fc691e0abd | ||
|
|
9be3237051 | ||
|
|
7fbc187548 | ||
|
|
075361a3b3 | ||
|
|
f89022e9d0 | ||
|
|
691eac4863 | ||
|
|
d7dd8ecec0 | ||
|
|
43839963e1 | ||
|
|
f83580c8c1 | ||
|
|
5b264f9f67 | ||
|
|
5ba6f40a3c | ||
|
|
60dc752c77 | ||
|
|
b3e7de8bbe | ||
|
|
9b5d77990a | ||
|
|
aae7605aff | ||
|
|
213f7164a7 | ||
|
|
b188de9c7d | ||
|
|
60a573a202 | ||
|
|
09d9ccce04 | ||
|
|
68314089ee | ||
|
|
7f1c20700a | ||
|
|
ca4546f1b5 | ||
|
|
a63ac9b5cb | ||
|
|
50cc7a9527 | ||
|
|
fa8ec8ee77 |
541 changed files with 167176 additions and 257 deletions
3
.github/workflows/gh-pages.yml
vendored
3
.github/workflows/gh-pages.yml
vendored
|
|
@ -10,7 +10,8 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- uses: ocaml/setup-ocaml@v3
|
||||
with:
|
||||
ocaml-compiler: '5.2'
|
||||
|
|
|
|||
10
.github/workflows/main.yml
vendored
10
.github/workflows/main.yml
vendored
|
|
@ -15,6 +15,8 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- uses: ocaml/setup-ocaml@v3
|
||||
with:
|
||||
ocaml-compiler: ${{ matrix.ocaml-compiler }}
|
||||
|
|
@ -37,21 +39,17 @@ jobs:
|
|||
- 5.1.x
|
||||
- 5.2.x
|
||||
- 5.3.x
|
||||
lsp-version:
|
||||
- 1.19.0
|
||||
- 1.20.1
|
||||
- 1.22.0
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- uses: ocaml/setup-ocaml@v3
|
||||
with:
|
||||
ocaml-compiler: ${{ matrix.ocaml-compiler }}
|
||||
dune-cache: true
|
||||
allow-prerelease-opam: true
|
||||
- run: opam pin -n .
|
||||
- run: opam pin add jsonrpc ${{ matrix.lsp-version }}
|
||||
- run: opam pin add lsp ${{ matrix.lsp-version }}
|
||||
- run: opam depext -yt linol linol-lwt linol-eio
|
||||
- run: opam install -t . --deps-only
|
||||
- run: opam exec -- dune build
|
||||
|
|
|
|||
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
|
|
@ -1,4 +1,4 @@
|
|||
version = 0.24.1
|
||||
version = 0.27.0
|
||||
profile=conventional
|
||||
margin=80
|
||||
if-then-else=k-r
|
||||
|
|
|
|||
12
CHANGES.md
12
CHANGES.md
|
|
@ -1,4 +1,16 @@
|
|||
|
||||
# 0.10
|
||||
|
||||
- use `git subtree` to vendor lsp+jsonrpc, so that they
|
||||
are not dependencies anymore and do not conflict with
|
||||
other users
|
||||
- Add `filter_text_document` to ignore some documents
|
||||
|
||||
# 0.9
|
||||
|
||||
- Drop redundant dependency on atomic
|
||||
- Add support for lsp 1.22
|
||||
|
||||
# 0.8
|
||||
|
||||
- move to LSP 1.19 and 1.20
|
||||
|
|
|
|||
6
Makefile
6
Makefile
|
|
@ -13,6 +13,12 @@ clean:
|
|||
doc:
|
||||
@dune build @doc
|
||||
|
||||
fmt:
|
||||
@dune build @fmt --auto-promote
|
||||
|
||||
update-submodules:
|
||||
@git submodule update --init
|
||||
|
||||
VERSION=$(shell awk '/^version:/ {print $$2}' linol.opam)
|
||||
|
||||
update_next_tag:
|
||||
|
|
|
|||
1
dune
Normal file
1
dune
Normal file
|
|
@ -0,0 +1 @@
|
|||
(data_only_dirs thirdparty)
|
||||
33
dune-project
33
dune-project
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
(name linol)
|
||||
|
||||
(version 0.8)
|
||||
(version 0.10)
|
||||
|
||||
(license MIT)
|
||||
|
||||
|
|
@ -28,17 +28,14 @@
|
|||
"logs"
|
||||
("trace"
|
||||
(>= "0.4"))
|
||||
("lsp"
|
||||
(and
|
||||
(>= "1.19")
|
||||
(< "1.23")))
|
||||
("jsonrpc"
|
||||
(and
|
||||
(>= "1.19")
|
||||
(< "1.23")))
|
||||
("ocaml"
|
||||
(>= "4.14"))
|
||||
("odoc" :with-doc)))
|
||||
("odoc" :with-doc)
|
||||
; The following dependencies are needed for lsp, which we vendor
|
||||
(uutf
|
||||
(>= 1.0.2))
|
||||
(ppx_yojson_conv_lib
|
||||
(>= "v0.14"))))
|
||||
|
||||
(package
|
||||
(name linol-lwt)
|
||||
|
|
@ -53,14 +50,6 @@
|
|||
(and
|
||||
(>= "5.1")
|
||||
(< "6.0")))
|
||||
("lsp"
|
||||
(and
|
||||
(>= "1.19")
|
||||
(< "1.23")))
|
||||
("jsonrpc"
|
||||
(and
|
||||
(>= "1.19")
|
||||
(< "1.23")))
|
||||
("odoc" :with-doc)))
|
||||
|
||||
(package
|
||||
|
|
@ -78,12 +67,4 @@
|
|||
(>= "1.0")
|
||||
(< "2.0")))
|
||||
(eio_main :with-test)
|
||||
("lsp"
|
||||
(and
|
||||
(>= "1.19")
|
||||
(< "1.23")))
|
||||
("jsonrpc"
|
||||
(and
|
||||
(>= "1.19")
|
||||
(< "1.23")))
|
||||
("odoc" :with-doc)))
|
||||
|
|
|
|||
|
|
@ -4,11 +4,4 @@
|
|||
; Deps on linol + Eio backend
|
||||
linol
|
||||
linol-eio
|
||||
eio_main
|
||||
; Types from the lsp library are exposed by the linol libs,
|
||||
; and thus almost guaranteed to be used by code using linol;
|
||||
; it is thus better to explicitly list lsp as a dep rather
|
||||
; than rely on its inclusion as a transitive dep of linol
|
||||
; since it would for instance generate errors if the
|
||||
; implicit-transitive-deps option of dune is set to false
|
||||
lsp))
|
||||
eio_main))
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
of a document are expected to be able to return.
|
||||
*)
|
||||
|
||||
module Lsp = Linol.Lsp
|
||||
|
||||
type state_after_processing = unit
|
||||
|
||||
let process_some_input_file (_file_contents : string) : state_after_processing =
|
||||
|
|
|
|||
|
|
@ -3,11 +3,4 @@
|
|||
(libraries
|
||||
; Deps on linol + LWT backend
|
||||
linol
|
||||
linol-lwt
|
||||
; Types from the lsp library are exposed by the linol libs,
|
||||
; and thus almost guaranteed to be used by code using linol;
|
||||
; it is thus better to explicitly list lsp as a dep rather
|
||||
; than rely on its inclusion as a transitive dep of linol
|
||||
; since it would for instance generate errors if the
|
||||
; implicit-transitive-deps option of dune is set to false
|
||||
lsp))
|
||||
linol-lwt))
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
of a document are expected to be able to return.
|
||||
*)
|
||||
|
||||
module Lsp = Linol.Lsp
|
||||
|
||||
type state_after_processing = unit
|
||||
|
||||
let process_some_input_file (_file_contents : string) : state_after_processing =
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
version: "0.8"
|
||||
version: "0.10"
|
||||
synopsis: "LSP server library (with Eio for concurrency)"
|
||||
maintainer: ["Simon Cruanes"]
|
||||
authors: ["Nick Hu"]
|
||||
|
|
@ -14,8 +14,6 @@ depends: [
|
|||
"base-unix"
|
||||
"eio" {>= "1.0" & < "2.0"}
|
||||
"eio_main" {with-test}
|
||||
"lsp" {>= "1.19" & < "1.23"}
|
||||
"jsonrpc" {>= "1.19" & < "1.23"}
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
build: [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
version: "0.8"
|
||||
version: "0.10"
|
||||
synopsis: "LSP server library (with Lwt for concurrency)"
|
||||
maintainer: ["Simon Cruanes"]
|
||||
authors: ["Simon Cruanes"]
|
||||
|
|
@ -13,8 +13,6 @@ depends: [
|
|||
"linol" {= version}
|
||||
"base-unix"
|
||||
"lwt" {>= "5.1" & < "6.0"}
|
||||
"lsp" {>= "1.19" & < "1.23"}
|
||||
"jsonrpc" {>= "1.19" & < "1.23"}
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
build: [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
version: "0.8"
|
||||
version: "0.10"
|
||||
synopsis: "LSP server library"
|
||||
maintainer: ["Simon Cruanes"]
|
||||
authors: ["Simon Cruanes"]
|
||||
|
|
@ -12,10 +12,10 @@ depends: [
|
|||
"yojson" {>= "1.6"}
|
||||
"logs"
|
||||
"trace" {>= "0.4"}
|
||||
"lsp" {>= "1.19" & < "1.23"}
|
||||
"jsonrpc" {>= "1.19" & < "1.23"}
|
||||
"ocaml" {>= "4.14"}
|
||||
"odoc" {with-doc}
|
||||
"uutf" {>= "1.0.2"}
|
||||
"ppx_yojson_conv_lib" {>= "v0.14"}
|
||||
]
|
||||
build: [
|
||||
["dune" "subst"] {pinned}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,9 @@ let ( let* ) x f = f x
|
|||
let ( and+ ) a b = a, b
|
||||
let return x = x
|
||||
let failwith = failwith
|
||||
|
||||
let fail = Printexc.raise_with_backtrace
|
||||
|
||||
let stdin = fun () -> stdin
|
||||
let stdout = fun () -> stdout
|
||||
let stdin () = stdin
|
||||
let stdout () = stdout
|
||||
|
||||
let default_spawn f =
|
||||
let run () =
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
module Lsp = Linol_lsp.Lsp
|
||||
module Jsonrpc = Linol_jsonrpc.Jsonrpc
|
||||
module Trace = Trace_core
|
||||
|
||||
let ( let@ ) = ( @@ )
|
||||
|
|
|
|||
8
src/dune
8
src/dune
|
|
@ -3,4 +3,10 @@
|
|||
(public_name linol)
|
||||
(private_modules log)
|
||||
(flags :standard -warn-error -a+8)
|
||||
(libraries yojson lsp logs threads trace.core))
|
||||
(libraries
|
||||
yojson
|
||||
logs
|
||||
threads
|
||||
trace.core
|
||||
(re_export linol.lsp)
|
||||
(re_export linol.jsonrpc)))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
(library
|
||||
(name linol_eio)
|
||||
(public_name linol-eio)
|
||||
(libraries eio eio.unix linol)
|
||||
(libraries eio eio.unix linol linol.lsp linol.jsonrpc)
|
||||
(flags :standard -warn-error -a))
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
open struct
|
||||
module Lsp = Linol_lsp.Lsp
|
||||
end
|
||||
|
||||
module type IO = Linol.IO
|
||||
|
||||
module IO_eio :
|
||||
|
|
@ -15,11 +19,15 @@ module IO_eio :
|
|||
let failwith = failwith
|
||||
let fail = raise
|
||||
|
||||
let catch f handler = try f () with exn ->
|
||||
let bt = Printexc.get_raw_backtrace () in
|
||||
handler exn bt
|
||||
let catch f handler =
|
||||
try f ()
|
||||
with exn ->
|
||||
let bt = Printexc.get_raw_backtrace () in
|
||||
handler exn bt
|
||||
|
||||
let stdin env =
|
||||
Eio.Buf_read.of_flow ~max_size:1_000_000 (Eio.Stdenv.stdin env)
|
||||
|
||||
let stdin env = Eio.Buf_read.of_flow ~max_size:1_000_000 (Eio.Stdenv.stdin env)
|
||||
let stdout = Eio.Stdenv.stdout
|
||||
|
||||
type env = Eio_unix.Stdenv.base
|
||||
|
|
@ -27,29 +35,28 @@ module IO_eio :
|
|||
type out_channel = Eio_unix.sink_ty Eio.Std.r
|
||||
|
||||
let write_string out_ch str = Eio.Flow.copy_string str out_ch
|
||||
|
||||
let write out_ch bytes off len =
|
||||
Eio.Buf_write.with_flow out_ch @@ fun w ->
|
||||
Eio.Buf_write.bytes w ~off ~len bytes
|
||||
|
||||
let read in_ch bytes off len =
|
||||
let str = Eio.Buf_read.take len in_ch in
|
||||
Bytes.blit_string str off bytes 0 len
|
||||
let read_line in_ch =
|
||||
Eio.Buf_read.line in_ch
|
||||
|
||||
let read_line in_ch = Eio.Buf_read.line in_ch
|
||||
end
|
||||
|
||||
(** Spawn function. *)
|
||||
let spawn f =
|
||||
let promise, resolver = Eio.Promise.create () in
|
||||
begin
|
||||
try
|
||||
f ();
|
||||
Eio.Promise.resolve_ok resolver ()
|
||||
with
|
||||
exn ->
|
||||
(Printf.eprintf "uncaught exception in `spawn`:\n%s\n%!"
|
||||
(Printexc.to_string exn));
|
||||
Eio.Promise.resolve_error resolver exn
|
||||
end;
|
||||
(try
|
||||
f ();
|
||||
Eio.Promise.resolve_ok resolver ()
|
||||
with exn ->
|
||||
Printf.eprintf "uncaught exception in `spawn`:\n%s\n%!"
|
||||
(Printexc.to_string exn);
|
||||
Eio.Promise.resolve_error resolver exn);
|
||||
|
||||
Eio.Promise.await_exn promise
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,11 @@ module type S = sig
|
|||
t
|
||||
|
||||
val create_stdio :
|
||||
?on_received:(json -> unit) -> ?on_sent:(json -> unit) -> env:IO.env -> server -> t
|
||||
?on_received:(json -> unit) ->
|
||||
?on_sent:(json -> unit) ->
|
||||
env:IO.env ->
|
||||
server ->
|
||||
t
|
||||
|
||||
val send_server_notification : t -> Lsp.Server_notification.t -> unit IO.t
|
||||
|
||||
|
|
@ -120,7 +124,8 @@ module Make (IO : IO) : S with module IO = IO = struct
|
|||
`Int id
|
||||
|
||||
(** Registers a new handler for a request response. The return indicates
|
||||
whether a value was inserted or not (in which case it's already present). *)
|
||||
whether a value was inserted or not (in which case it's already present).
|
||||
*)
|
||||
let register_server_request_response_handler (self : t) (id : Req_id.t)
|
||||
(handler : server_request_handler_pair) : bool =
|
||||
if Hashtbl.mem self.pending_responses id then
|
||||
|
|
@ -359,10 +364,10 @@ module Make (IO : IO) : S with module IO = IO = struct
|
|||
Req_id.t IO.t =
|
||||
server_request self (Request_and_handler (req, cb))
|
||||
|
||||
(** [shutdown ()] is called after processing each request to check if the server
|
||||
could wait for new messages.
|
||||
When launching an LSP server using [Server.Make.server], the
|
||||
natural choice for it is [s#get_status = `ReceivedExit] *)
|
||||
(** [shutdown ()] is called after processing each request to check if the
|
||||
server could wait for new messages. When launching an LSP server using
|
||||
[Server.Make.server], the natural choice for it is
|
||||
[s#get_status = `ReceivedExit] *)
|
||||
let run ?(shutdown = fun _ -> false) (self : t) : unit IO.t =
|
||||
let async f =
|
||||
self.s#spawn_query_handler f;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
(** Simple JSON-RPC2 implementation.
|
||||
|
||||
See {{: https://www.jsonrpc.org/specification} the spec} *)
|
||||
See {{:https://www.jsonrpc.org/specification} the spec} *)
|
||||
|
||||
open Common_
|
||||
|
||||
type json = Yojson.Safe.t
|
||||
|
||||
|
|
@ -24,7 +26,11 @@ module type S = sig
|
|||
(** Create a connection from the pair of channels *)
|
||||
|
||||
val create_stdio :
|
||||
?on_received:(json -> unit) -> ?on_sent:(json -> unit) -> env:IO.env -> server -> t
|
||||
?on_received:(json -> unit) ->
|
||||
?on_sent:(json -> unit) ->
|
||||
env:IO.env ->
|
||||
server ->
|
||||
t
|
||||
(** Create a connection using stdin/stdout *)
|
||||
|
||||
val send_server_notification : t -> Lsp.Server_notification.t -> unit IO.t
|
||||
|
|
@ -36,13 +42,13 @@ module type S = sig
|
|||
'from_server Lsp.Server_request.t ->
|
||||
(('from_server, Jsonrpc.Response.Error.t) result -> unit IO.t) ->
|
||||
Req_id.t IO.t
|
||||
(** Send a request from the server, and pass a callback that will be
|
||||
called with the result in the future.
|
||||
(** Send a request from the server, and pass a callback that will be called
|
||||
with the result in the future.
|
||||
@since 0.5 *)
|
||||
|
||||
val run : ?shutdown:(unit -> bool) -> t -> unit IO.t
|
||||
(** Listen for incoming messages and responses.
|
||||
@param shutdown if true, tells the server to shut down *)
|
||||
@param shutdown if true, tells the server to shut down *)
|
||||
end
|
||||
|
||||
module Make (IO : IO) : S with module IO = IO
|
||||
|
|
|
|||
11
src/linol.ml
11
src/linol.ml
|
|
@ -1,10 +1,17 @@
|
|||
(** Linol.
|
||||
|
||||
Abstraction over The "Lsp" library, to make it easier to develop
|
||||
LSP servers in OCaml (but not necessarily {b for} OCaml). *)
|
||||
Abstraction over The "Lsp" library, to make it easier to develop LSP servers
|
||||
in OCaml (but not necessarily {b for} OCaml). *)
|
||||
|
||||
module type IO = Sigs.IO
|
||||
|
||||
(** {2 Re-export from vendored lsp} *)
|
||||
|
||||
module Lsp = Linol_lsp.Lsp
|
||||
module Jsonrpc = Linol_jsonrpc.Jsonrpc
|
||||
|
||||
(** {2 Main modules} *)
|
||||
|
||||
module Jsonrpc2 = Jsonrpc2
|
||||
module Server = Server
|
||||
module Blocking_IO = Blocking_IO
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
(library
|
||||
(name linol_lwt)
|
||||
(public_name linol-lwt)
|
||||
(libraries yojson lwt lwt.unix linol lsp jsonrpc)
|
||||
(libraries yojson lwt lwt.unix linol linol.lsp linol.jsonrpc)
|
||||
(flags :standard -warn-error -a))
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
open struct
|
||||
module Lsp = Linol_lsp.Lsp
|
||||
end
|
||||
|
||||
module type IO = Linol.IO
|
||||
|
||||
module IO_lwt :
|
||||
|
|
@ -18,8 +22,8 @@ module IO_lwt :
|
|||
|
||||
let return = Lwt.return
|
||||
let failwith = Lwt.fail_with
|
||||
let stdin = fun () -> Lwt_io.stdin
|
||||
let stdout = fun () -> Lwt_io.stdout
|
||||
let stdin () = Lwt_io.stdin
|
||||
let stdout () = Lwt_io.stdout
|
||||
|
||||
type env = unit
|
||||
type in_channel = Lwt_io.input Lwt_io.channel
|
||||
|
|
|
|||
343
src/server.ml
343
src/server.ml
|
|
@ -13,7 +13,8 @@ type nonrec doc_state = {
|
|||
|
||||
(** Request ID.
|
||||
|
||||
The unique ID of a request, used by JSONRPC to map each request to its reply. *)
|
||||
The unique ID of a request, used by JSONRPC to map each request to its
|
||||
reply. *)
|
||||
module Req_id = struct
|
||||
type t = Jsonrpc.Id.t
|
||||
|
||||
|
|
@ -62,16 +63,18 @@ module Make (IO : IO) = struct
|
|||
'a Lsp.Client_request.t ->
|
||||
('a, string) result IO.t
|
||||
(** Method called to handle client requests.
|
||||
@param notify_back an object used to reply to the client, send progress
|
||||
messages, diagnostics, etc.
|
||||
@param id the query RPC ID, can be used for tracing, cancellation, etc. *)
|
||||
@param notify_back
|
||||
an object used to reply to the client, send progress messages,
|
||||
diagnostics, etc.
|
||||
@param id
|
||||
the query RPC ID, can be used for tracing, cancellation, etc. *)
|
||||
|
||||
method must_quit = false
|
||||
(** Set to true if the client requested to exit *)
|
||||
|
||||
method virtual spawn_query_handler : (unit -> unit IO.t) -> unit
|
||||
(** How to start a new future/task/thread concurrently. This is used
|
||||
to process incoming user queries.
|
||||
(** How to start a new future/task/thread concurrently. This is used to
|
||||
process incoming user queries.
|
||||
@since 0.5 *)
|
||||
end
|
||||
|
||||
|
|
@ -116,8 +119,8 @@ module Make (IO : IO) = struct
|
|||
method cancel_request (id : Jsonrpc.Id.t) : unit IO.t =
|
||||
notify_back @@ CancelRequest id
|
||||
|
||||
method work_done_progress_begin (p : Lsp.Types.WorkDoneProgressBegin.t)
|
||||
: unit IO.t =
|
||||
method work_done_progress_begin (p : Lsp.Types.WorkDoneProgressBegin.t) :
|
||||
unit IO.t =
|
||||
match workDoneToken with
|
||||
| Some token ->
|
||||
notify_back @@ WorkDoneProgress { token; value = Begin p }
|
||||
|
|
@ -130,23 +133,25 @@ module Make (IO : IO) = struct
|
|||
notify_back @@ WorkDoneProgress { value = Report p; token }
|
||||
| None -> IO.return ()
|
||||
|
||||
method work_done_progress_end (p : Lsp.Types.WorkDoneProgressEnd.t)
|
||||
: unit IO.t =
|
||||
method work_done_progress_end (p : Lsp.Types.WorkDoneProgressEnd.t) :
|
||||
unit IO.t =
|
||||
match workDoneToken with
|
||||
| Some token -> notify_back @@ WorkDoneProgress { value = End p; token }
|
||||
| None -> IO.return ()
|
||||
|
||||
method send_notification (n : Lsp.Server_notification.t) : unit IO.t =
|
||||
notify_back n
|
||||
(** Send a notification from the server to the client (general purpose method) *)
|
||||
(** Send a notification from the server to the client (general purpose
|
||||
method) *)
|
||||
|
||||
method send_request
|
||||
: 'from_server.
|
||||
'from_server Lsp.Server_request.t ->
|
||||
(('from_server, Jsonrpc.Response.Error.t) result -> unit IO.t) ->
|
||||
Req_id.t IO.t =
|
||||
method send_request :
|
||||
'from_server.
|
||||
'from_server Lsp.Server_request.t ->
|
||||
(('from_server, Jsonrpc.Response.Error.t) result -> unit IO.t) ->
|
||||
Req_id.t IO.t =
|
||||
fun r h -> server_request @@ Request_and_handler (r, h)
|
||||
(** Send a request from the server to the client (general purpose method) *)
|
||||
(** Send a request from the server to the client (general purpose method)
|
||||
*)
|
||||
end
|
||||
|
||||
type nonrec doc_state = doc_state = {
|
||||
|
|
@ -162,10 +167,10 @@ module Make (IO : IO) = struct
|
|||
let+ x = x in
|
||||
Ok x
|
||||
|
||||
(** An easily overloadable class. Pick the methods you want to support.
|
||||
The user must provide at least the callbacks for document lifecycle:
|
||||
open, close, update. The most basic LSP server should check documents
|
||||
when they're updated and report diagnostics back to the editor. *)
|
||||
(** An easily overloadable class. Pick the methods you want to support. The
|
||||
user must provide at least the callbacks for document lifecycle: open,
|
||||
close, update. The most basic LSP server should check documents when
|
||||
they're updated and report diagnostics back to the editor. *)
|
||||
class virtual server =
|
||||
object (self)
|
||||
inherit base_server
|
||||
|
|
@ -178,7 +183,7 @@ module Make (IO : IO) = struct
|
|||
|
||||
method get_status = status
|
||||
(** Check if exit or shutdown request was made by the client.
|
||||
@since 0.5 *)
|
||||
@since 0.5 *)
|
||||
|
||||
method find_doc (uri : DocumentUri.t) : doc_state option =
|
||||
try Some (Hashtbl.find docs uri) with Not_found -> None
|
||||
|
|
@ -223,50 +228,47 @@ module Make (IO : IO) = struct
|
|||
|
||||
method config_completion : CompletionOptions.t option = None
|
||||
(** Configuration for the completion API.
|
||||
@since 0.4 *)
|
||||
@since 0.4 *)
|
||||
|
||||
method config_code_lens_options : CodeLensOptions.t option = None
|
||||
(** @since 0.3 *)
|
||||
|
||||
method config_definition
|
||||
: [ `Bool of bool | `DefinitionOptions of DefinitionOptions.t ] option
|
||||
=
|
||||
method config_definition :
|
||||
[ `Bool of bool | `DefinitionOptions of DefinitionOptions.t ] option =
|
||||
None
|
||||
(** @since 0.3 *)
|
||||
|
||||
method config_hover
|
||||
: [ `Bool of bool | `HoverOptions of HoverOptions.t ] option =
|
||||
method config_hover :
|
||||
[ `Bool of bool | `HoverOptions of HoverOptions.t ] option =
|
||||
None
|
||||
(** @since 0.3 *)
|
||||
|
||||
method config_inlay_hints
|
||||
: [ `Bool of bool
|
||||
| `InlayHintOptions of InlayHintOptions.t
|
||||
| `InlayHintRegistrationOptions of InlayHintRegistrationOptions.t
|
||||
]
|
||||
option =
|
||||
method config_inlay_hints :
|
||||
[ `Bool of bool
|
||||
| `InlayHintOptions of InlayHintOptions.t
|
||||
| `InlayHintRegistrationOptions of InlayHintRegistrationOptions.t
|
||||
]
|
||||
option =
|
||||
None
|
||||
(** Configuration for the inlay hints API. *)
|
||||
|
||||
method config_symbol
|
||||
: [ `Bool of bool
|
||||
| `DocumentSymbolOptions of DocumentSymbolOptions.t
|
||||
]
|
||||
option =
|
||||
method config_symbol :
|
||||
[ `Bool of bool | `DocumentSymbolOptions of DocumentSymbolOptions.t ]
|
||||
option =
|
||||
None
|
||||
(** @since 0.3 *)
|
||||
|
||||
method config_code_action_provider
|
||||
: [ `CodeActionOptions of CodeActionOptions.t | `Bool of bool ] =
|
||||
method config_code_action_provider :
|
||||
[ `CodeActionOptions of CodeActionOptions.t | `Bool of bool ] =
|
||||
`Bool false
|
||||
(** @since 0.3 *)
|
||||
|
||||
method config_modify_capabilities (c : ServerCapabilities.t)
|
||||
: ServerCapabilities.t =
|
||||
method config_modify_capabilities (c : ServerCapabilities.t) :
|
||||
ServerCapabilities.t =
|
||||
c
|
||||
(** Modify capabilities before sending them back to the client.
|
||||
By default we just return them unmodified.
|
||||
@since 0.3 *)
|
||||
(** Modify capabilities before sending them back to the client. By default
|
||||
we just return them unmodified.
|
||||
@since 0.3 *)
|
||||
|
||||
method config_list_commands : string list = []
|
||||
(** List of commands available *)
|
||||
|
|
@ -304,73 +306,73 @@ module Make (IO : IO) = struct
|
|||
(** Called when the user hovers on some identifier in the document *)
|
||||
|
||||
method on_req_completion ~notify_back:(_ : notify_back) ~id:_ ~uri:_
|
||||
~pos:_ ~ctx:_ ~workDoneToken:_ ~partialResultToken:_ (_ : doc_state)
|
||||
: [ `CompletionList of CompletionList.t
|
||||
| `List of CompletionItem.t list
|
||||
]
|
||||
option
|
||||
IO.t =
|
||||
~pos:_ ~ctx:_ ~workDoneToken:_ ~partialResultToken:_ (_ : doc_state) :
|
||||
[ `CompletionList of CompletionList.t
|
||||
| `List of CompletionItem.t list
|
||||
]
|
||||
option
|
||||
IO.t =
|
||||
IO.return None
|
||||
(** Called when the user requests completion in the document *)
|
||||
|
||||
method on_req_definition ~notify_back:(_ : notify_back) ~id:_ ~uri:_
|
||||
~pos:_ ~workDoneToken:_ ~partialResultToken:_ (_ : doc_state)
|
||||
: Locations.t option IO.t =
|
||||
~pos:_ ~workDoneToken:_ ~partialResultToken:_ (_ : doc_state) :
|
||||
Locations.t option IO.t =
|
||||
IO.return None
|
||||
(** Called when the user wants to jump-to-definition *)
|
||||
(** Called when the user wants to jump-to-definition *)
|
||||
|
||||
method on_req_code_lens ~notify_back:(_ : notify_back) ~id:_ ~uri:_
|
||||
~workDoneToken:_ ~partialResultToken:_ (_ : doc_state)
|
||||
: CodeLens.t list IO.t =
|
||||
~workDoneToken:_ ~partialResultToken:_ (_ : doc_state) :
|
||||
CodeLens.t list IO.t =
|
||||
IO.return []
|
||||
(** List code lenses for the given document
|
||||
@since 0.3 *)
|
||||
@since 0.3 *)
|
||||
|
||||
method on_req_code_lens_resolve ~notify_back:(_ : notify_back) ~id:_
|
||||
(cl : CodeLens.t) : CodeLens.t IO.t =
|
||||
IO.return cl
|
||||
(** Code lens resolution, must return a code lens with non null "command"
|
||||
@since 0.3 *)
|
||||
@since 0.3 *)
|
||||
|
||||
method on_req_code_action ~notify_back:(_ : notify_back) ~id:_
|
||||
(_c : CodeActionParams.t) : CodeActionResult.t IO.t =
|
||||
IO.return None
|
||||
(** Code action.
|
||||
@since 0.3 *)
|
||||
@since 0.3 *)
|
||||
|
||||
method on_req_execute_command ~notify_back:(_ : notify_back) ~id:_
|
||||
~workDoneToken:_ (_c : string) (_args : Yojson.Safe.t list option)
|
||||
: Yojson.Safe.t IO.t =
|
||||
~workDoneToken:_ (_c : string) (_args : Yojson.Safe.t list option) :
|
||||
Yojson.Safe.t IO.t =
|
||||
IO.return `Null
|
||||
(** Execute a command with given arguments.
|
||||
@since 0.3 *)
|
||||
@since 0.3 *)
|
||||
|
||||
method on_req_symbol ~notify_back:(_ : notify_back) ~id:_ ~uri:_
|
||||
~workDoneToken:_ ~partialResultToken:_ ()
|
||||
: [ `DocumentSymbol of DocumentSymbol.t list
|
||||
| `SymbolInformation of SymbolInformation.t list
|
||||
]
|
||||
option
|
||||
IO.t =
|
||||
~workDoneToken:_ ~partialResultToken:_ () :
|
||||
[ `DocumentSymbol of DocumentSymbol.t list
|
||||
| `SymbolInformation of SymbolInformation.t list
|
||||
]
|
||||
option
|
||||
IO.t =
|
||||
IO.return None
|
||||
(** List symbols in this document.
|
||||
@since 0.3 *)
|
||||
@since 0.3 *)
|
||||
|
||||
method on_unknown_request ~notify_back:(_ : notify_back) ~server_request:_
|
||||
~id:_ _meth _params : Yojson.Safe.t IO.t =
|
||||
IO.failwith "unhandled request"
|
||||
|
||||
method on_req_inlay_hint ~notify_back:(_ : notify_back) ~id:_ ~uri:_
|
||||
~range:(_ : Lsp.Types.Range.t) ()
|
||||
: Lsp.Types.InlayHint.t list option IO.t =
|
||||
~range:(_ : Lsp.Types.Range.t) () :
|
||||
Lsp.Types.InlayHint.t list option IO.t =
|
||||
IO.return None
|
||||
(** Provide inlay hints for this document.
|
||||
@since 0.5 *)
|
||||
@since 0.5 *)
|
||||
|
||||
method on_req_shutdown ~notify_back:(_ : notify_back) ~id:_ : unit IO.t =
|
||||
IO.return ()
|
||||
(** Process a shutdown request.
|
||||
@since 0.7 *)
|
||||
@since 0.7 *)
|
||||
|
||||
method on_request : type r.
|
||||
notify_back:_ ->
|
||||
|
|
@ -627,6 +629,11 @@ module Make (IO : IO) = struct
|
|||
IO.return ()
|
||||
(** Override to handle unprocessed notifications *)
|
||||
|
||||
method filter_text_document (_doc_uri : Lsp.Types.DocumentUri.t) : bool =
|
||||
true
|
||||
(** Filter the document URI to check if we want to process it or not. By
|
||||
default we accept all documents. *)
|
||||
|
||||
method on_notification ~notify_back ~server_request
|
||||
(n : Lsp.Client_notification.t) : unit IO.t =
|
||||
let@ _sp =
|
||||
|
|
@ -651,105 +658,121 @@ module Make (IO : IO) = struct
|
|||
match n with
|
||||
| Lsp.Client_notification.TextDocumentDidOpen
|
||||
{ DidOpenTextDocumentParams.textDocument = doc } ->
|
||||
Log.debug (fun k ->
|
||||
k "notif: did open '%s'" (DocumentUri.to_path doc.uri));
|
||||
let notify_back =
|
||||
new notify_back
|
||||
~uri:doc.uri ~workDoneToken:None ~partialResultToken:None
|
||||
~version:doc.version ~notify_back ~server_request ()
|
||||
in
|
||||
let st =
|
||||
{
|
||||
uri = doc.uri;
|
||||
version = doc.version;
|
||||
content = doc.text;
|
||||
languageId = doc.languageId;
|
||||
}
|
||||
in
|
||||
Hashtbl.replace docs doc.uri st;
|
||||
if not (self#filter_text_document doc.uri) then
|
||||
IO.return ()
|
||||
else (
|
||||
Log.debug (fun k ->
|
||||
k "notif: did open '%s'" (DocumentUri.to_path doc.uri));
|
||||
let notify_back =
|
||||
new notify_back
|
||||
~uri:doc.uri ~workDoneToken:None ~partialResultToken:None
|
||||
~version:doc.version ~notify_back ~server_request ()
|
||||
in
|
||||
let st =
|
||||
{
|
||||
uri = doc.uri;
|
||||
version = doc.version;
|
||||
content = doc.text;
|
||||
languageId = doc.languageId;
|
||||
}
|
||||
in
|
||||
Hashtbl.replace docs doc.uri st;
|
||||
|
||||
async self (fun () ->
|
||||
self#on_notif_doc_did_open
|
||||
~notify_back:(notify_back : notify_back)
|
||||
doc ~content:st.content)
|
||||
async self (fun () ->
|
||||
self#on_notif_doc_did_open
|
||||
~notify_back:(notify_back : notify_back)
|
||||
doc ~content:st.content)
|
||||
)
|
||||
| Lsp.Client_notification.TextDocumentDidClose { textDocument = doc } ->
|
||||
Log.debug (fun k ->
|
||||
k "notif: did close '%s'" (DocumentUri.to_path doc.uri));
|
||||
let notify_back =
|
||||
new notify_back
|
||||
~workDoneToken:None ~partialResultToken:None ~uri:doc.uri
|
||||
~notify_back ~server_request ()
|
||||
in
|
||||
if not (self#filter_text_document doc.uri) then
|
||||
IO.return ()
|
||||
else (
|
||||
Log.debug (fun k ->
|
||||
k "notif: did close '%s'" (DocumentUri.to_path doc.uri));
|
||||
let notify_back =
|
||||
new notify_back
|
||||
~workDoneToken:None ~partialResultToken:None ~uri:doc.uri
|
||||
~notify_back ~server_request ()
|
||||
in
|
||||
|
||||
async self (fun () ->
|
||||
self#on_notif_doc_did_close
|
||||
~notify_back:(notify_back : notify_back)
|
||||
doc)
|
||||
async self (fun () ->
|
||||
self#on_notif_doc_did_close
|
||||
~notify_back:(notify_back : notify_back)
|
||||
doc)
|
||||
)
|
||||
| Lsp.Client_notification.TextDocumentDidChange
|
||||
{ textDocument = doc; contentChanges = c } ->
|
||||
Log.debug (fun k ->
|
||||
k "notif: did change '%s'" (DocumentUri.to_path doc.uri));
|
||||
let notify_back =
|
||||
new notify_back
|
||||
~workDoneToken:None ~partialResultToken:None ~uri:doc.uri
|
||||
~notify_back ~server_request ()
|
||||
in
|
||||
if not (self#filter_text_document doc.uri) then
|
||||
IO.return ()
|
||||
else (
|
||||
Log.debug (fun k ->
|
||||
k "notif: did change '%s'" (DocumentUri.to_path doc.uri));
|
||||
let notify_back =
|
||||
new notify_back
|
||||
~workDoneToken:None ~partialResultToken:None ~uri:doc.uri
|
||||
~notify_back ~server_request ()
|
||||
in
|
||||
|
||||
let old_doc =
|
||||
match Hashtbl.find_opt docs doc.uri with
|
||||
| None ->
|
||||
(* WTF vscode. Well let's try and deal with it. *)
|
||||
Log.err (fun k ->
|
||||
k "unknown document: '%s'" (DocumentUri.to_path doc.uri));
|
||||
let version = doc.version in
|
||||
let old_doc =
|
||||
match Hashtbl.find_opt docs doc.uri with
|
||||
| None ->
|
||||
(* WTF vscode. Well let's try and deal with it. *)
|
||||
Log.err (fun k ->
|
||||
k "unknown document: '%s'" (DocumentUri.to_path doc.uri));
|
||||
let version = doc.version in
|
||||
|
||||
let languageId = "" in
|
||||
(* FIXME*)
|
||||
Lsp.Text_document.make ~position_encoding:positionEncoding
|
||||
(DidOpenTextDocumentParams.create
|
||||
~textDocument:
|
||||
(TextDocumentItem.create ~languageId ~uri:doc.uri ~version
|
||||
~text:""))
|
||||
| Some st ->
|
||||
Lsp.Text_document.make ~position_encoding:positionEncoding
|
||||
(DidOpenTextDocumentParams.create
|
||||
~textDocument:
|
||||
(TextDocumentItem.create ~languageId:st.languageId
|
||||
~uri:doc.uri ~version:st.version ~text:st.content))
|
||||
in
|
||||
let languageId = "" in
|
||||
(* FIXME*)
|
||||
Lsp.Text_document.make ~position_encoding:positionEncoding
|
||||
(DidOpenTextDocumentParams.create
|
||||
~textDocument:
|
||||
(TextDocumentItem.create ~languageId ~uri:doc.uri
|
||||
~version ~text:""))
|
||||
| Some st ->
|
||||
Lsp.Text_document.make ~position_encoding:positionEncoding
|
||||
(DidOpenTextDocumentParams.create
|
||||
~textDocument:
|
||||
(TextDocumentItem.create ~languageId:st.languageId
|
||||
~uri:doc.uri ~version:st.version ~text:st.content))
|
||||
in
|
||||
|
||||
let new_doc : Lsp.Text_document.t =
|
||||
Lsp.Text_document.apply_content_changes old_doc c
|
||||
in
|
||||
let new_doc : Lsp.Text_document.t =
|
||||
Lsp.Text_document.apply_content_changes old_doc c
|
||||
in
|
||||
|
||||
let new_st : doc_state =
|
||||
{
|
||||
uri = doc.uri;
|
||||
languageId = Lsp.Text_document.languageId new_doc;
|
||||
content = Lsp.Text_document.text new_doc;
|
||||
version = Lsp.Text_document.version new_doc;
|
||||
}
|
||||
in
|
||||
let new_st : doc_state =
|
||||
{
|
||||
uri = doc.uri;
|
||||
languageId = Lsp.Text_document.languageId new_doc;
|
||||
content = Lsp.Text_document.text new_doc;
|
||||
version = Lsp.Text_document.version new_doc;
|
||||
}
|
||||
in
|
||||
|
||||
Hashtbl.replace docs doc.uri new_st;
|
||||
Hashtbl.replace docs doc.uri new_st;
|
||||
|
||||
async self (fun () ->
|
||||
self#on_notif_doc_did_change
|
||||
~notify_back:(notify_back : notify_back)
|
||||
doc c
|
||||
~old_content:(Lsp.Text_document.text old_doc)
|
||||
~new_content:new_st.content)
|
||||
async self (fun () ->
|
||||
self#on_notif_doc_did_change
|
||||
~notify_back:(notify_back : notify_back)
|
||||
doc c
|
||||
~old_content:(Lsp.Text_document.text old_doc)
|
||||
~new_content:new_st.content)
|
||||
)
|
||||
| Lsp.Client_notification.DidSaveTextDocument params ->
|
||||
let notify_back =
|
||||
new notify_back
|
||||
~workDoneToken:None ~partialResultToken:None
|
||||
~uri:params.textDocument.uri ~notify_back ~server_request ()
|
||||
in
|
||||
if not (self#filter_text_document params.textDocument.uri) then
|
||||
IO.return ()
|
||||
else (
|
||||
let notify_back =
|
||||
new notify_back
|
||||
~workDoneToken:None ~partialResultToken:None
|
||||
~uri:params.textDocument.uri ~notify_back ~server_request ()
|
||||
in
|
||||
|
||||
async self (fun () ->
|
||||
self#on_notif_doc_did_save
|
||||
~notify_back:(notify_back : notify_back)
|
||||
params)
|
||||
async self (fun () ->
|
||||
self#on_notif_doc_did_save
|
||||
~notify_back:(notify_back : notify_back)
|
||||
params)
|
||||
)
|
||||
| Lsp.Client_notification.Exit ->
|
||||
status <- `ReceivedExit;
|
||||
IO.return ()
|
||||
|
|
|
|||
11
thirdparty/lsp/.editorconfig
vendored
Normal file
11
thirdparty/lsp/.editorconfig
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
11
thirdparty/lsp/.git-blame-ignore-revs
vendored
Normal file
11
thirdparty/lsp/.git-blame-ignore-revs
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# To understand why we need this file, see https://www.moxio.com/blog/43/ignoring-bulk-change-commits-with-git-blame
|
||||
|
||||
# add ocamlformat config `wrap-fun-args=false`
|
||||
|
||||
75504946eaa6f817550b649df508d61dde12bbda
|
||||
# Upgrade to OCamlformat 0.26.0
|
||||
ab49baa5873e7f0b9181dbed3ad89681f1e4bcee
|
||||
# Upgrade to OCamlformat 0.26.1
|
||||
1a6419bac3ce012deb9c6891e6b25e2486c33388
|
||||
# Upgrade to OCamlformat 0.27.0
|
||||
2ccbee5dd691690228307d3636e2f82c8cdb3902
|
||||
16
thirdparty/lsp/.github/dependabot.yml
vendored
Normal file
16
thirdparty/lsp/.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
labels:
|
||||
- dependencies
|
||||
- no changelog
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
labels:
|
||||
- dependencies
|
||||
- no changelog
|
||||
103
thirdparty/lsp/.github/workflows/build-and-test.yml
vendored
Normal file
103
thirdparty/lsp/.github/workflows/build-and-test.yml
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
name: Build and Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
# Prime the caches every Monday
|
||||
- cron: 0 1 * * MON
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
name: Build and Test
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Set git to use LF
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
|
||||
- name: Checkout tree
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set-up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Install npm packages
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Set-up OCaml
|
||||
uses: ocaml/setup-ocaml@v3
|
||||
with:
|
||||
ocaml-compiler: "ocaml-base-compiler.5.3.0"
|
||||
|
||||
# Remove this pin once a compatible version of Merlin has been released
|
||||
- name: Pin dev Merlin
|
||||
run: opam --cli=2.1 pin --with-version=5.4-503 https://github.com/ocaml/merlin.git#main
|
||||
|
||||
- name: Build and install dependencies
|
||||
run: opam install .
|
||||
|
||||
# the makefile explains why we don't use --with-test
|
||||
# ppx expect is not yet compatible with 5.1 and test output vary from one
|
||||
# compiler to another. We only test on 4.14.
|
||||
- name: Install test dependencies
|
||||
run: opam exec -- make install-test-deps
|
||||
|
||||
- name: Run build @all
|
||||
run: opam exec -- make all
|
||||
|
||||
- name: Run the unit tests
|
||||
run: opam exec -- make test-ocaml
|
||||
|
||||
- name: Run the template integration tests
|
||||
run: opam exec -- make test-e2e
|
||||
|
||||
coverage:
|
||||
name: Coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout tree
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set-up OCaml
|
||||
uses: ocaml/setup-ocaml@v3
|
||||
with:
|
||||
ocaml-compiler: "ocaml-base-compiler.5.3.0"
|
||||
|
||||
- name: Set git user
|
||||
run: |
|
||||
git config --global user.name github-actions[bot]
|
||||
git config --global user.email github-actions[bot]@users.noreply.github.com
|
||||
|
||||
# Remove this pin once a compatible version of Merlin has been released
|
||||
- name: Pin dev Merlin
|
||||
run: opam --cli=2.1 pin --with-version=5.4-503 https://github.com/ocaml/merlin.git#main
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
opam install . --deps-only
|
||||
opam exec -- make coverage-deps install-test-deps
|
||||
|
||||
- run: opam exec -- make test-coverage
|
||||
env:
|
||||
COVERALLS_REPO_TOKEN: ${{ github.token }}
|
||||
PULL_REQUEST_NUMBER: ${{ github.event.number }}
|
||||
13
thirdparty/lsp/.github/workflows/changelog.yml
vendored
Normal file
13
thirdparty/lsp/.github/workflows/changelog.yml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
name: Changelog check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
|
||||
jobs:
|
||||
Changelog-Entry-Check:
|
||||
name: Check Changelog Action
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: tarides/changelog-check-action@v3
|
||||
31
thirdparty/lsp/.github/workflows/nix.yml
vendored
Normal file
31
thirdparty/lsp/.github/workflows/nix.yml
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
name: "Nix"
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
# tests:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Checkout tree
|
||||
# uses: actions/checkout@v4
|
||||
# with:
|
||||
# submodules: true
|
||||
# - name: nix
|
||||
# uses: cachix/install-nix-action@v30
|
||||
# with:
|
||||
# nix_path: nixpkgs=channel:nixos-unstable
|
||||
# - run: nix develop .#check -c make nix-tests
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout tree
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: nix
|
||||
uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- run: nix develop .#fmt -c make nix-fmt
|
||||
21
thirdparty/lsp/.github/workflows/update-flake-lock.yml
vendored
Normal file
21
thirdparty/lsp/.github/workflows/update-flake-lock.yml
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
name: Update Nix Flake Lock
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: 0 0 * * 0
|
||||
|
||||
jobs:
|
||||
lockfile:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: DeterminateSystems/update-flake-lock@v24
|
||||
with:
|
||||
pr-labels: |
|
||||
dependencies
|
||||
no changelog
|
||||
12
thirdparty/lsp/.gitignore
vendored
Normal file
12
thirdparty/lsp/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
_build/
|
||||
_opam/
|
||||
.idea/
|
||||
.log/
|
||||
.vscode/
|
||||
node_modules/
|
||||
tmp/
|
||||
.DS_Store
|
||||
.merlin
|
||||
*.install
|
||||
*.log
|
||||
result
|
||||
3
thirdparty/lsp/.ocamlformat
vendored
Normal file
3
thirdparty/lsp/.ocamlformat
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
version=0.27.0
|
||||
profile=janestreet
|
||||
ocaml-version=4.14.0
|
||||
3
thirdparty/lsp/.ocamlformat-ignore
vendored
Normal file
3
thirdparty/lsp/.ocamlformat-ignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
vendor
|
||||
_opam
|
||||
_esy
|
||||
769
thirdparty/lsp/CHANGES.md
vendored
Normal file
769
thirdparty/lsp/CHANGES.md
vendored
Normal file
|
|
@ -0,0 +1,769 @@
|
|||
# 1.22.0
|
||||
|
||||
## Features
|
||||
|
||||
- Enable experimental project-wide renaming of identifiers (#1431)
|
||||
|
||||
# 1.21.0
|
||||
|
||||
## Features
|
||||
|
||||
- Add a new server option `standardHover`, that can be used by clients to
|
||||
disable the default hover provider. When `standardHover = false`
|
||||
`textDocument/hover` requests always returns with empty result. (#1416)
|
||||
|
||||
# 1.20.1
|
||||
|
||||
## Fixes
|
||||
|
||||
- Deactivate the `jump` code actions by default. Clients can enable them with
|
||||
the `merlinJumpCodeActions` configuration option. Alternatively a custom
|
||||
request is provided for ad hoc use of the feature. (#1411)
|
||||
|
||||
# 1.20.0
|
||||
|
||||
## Features
|
||||
|
||||
- Add custom
|
||||
[`ocamllsp/typeSearch`](/ocaml-lsp-server/docs/ocamllsp/typeSearch-spec.md) request (#1369)
|
||||
|
||||
- Make MerlinJump code action configurable (#1376)
|
||||
- Add support for OCaml 5.3 (#1386)
|
||||
|
||||
- Add custom [`ocamllsp/jump`](/ocaml-lsp-server/docs/ocamllsp/merlinJump-spec.md) request (#1374)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix fd leak in running external processes for preprocessing (#1349)
|
||||
|
||||
- Fix prefix parsing for completion of object methods (#1363, fixes #1358)
|
||||
|
||||
- Remove some duplicates in the `selectionRange` answers (#1368)
|
||||
|
||||
# 1.19.0
|
||||
|
||||
## Features
|
||||
|
||||
- Add custom [`ocamllsp/getDocumentation`](/ocaml-lsp-server/docs/ocamllsp/getDocumentation-spec.md) request (#1336)
|
||||
|
||||
- Add support for OCaml 5.2 (#1233)
|
||||
|
||||
- Add a code-action for syntactic and semantic movement shortcuts based on Merlin's Jump command (#1364)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Kill unnecessary ocamlformat processes with sigterm rather than sigint or
|
||||
sigkill (#1343)
|
||||
|
||||
## Features
|
||||
|
||||
- Add custom [`ocamllsp/construct`](https://github.com/ocaml/ocaml-lsp/blob/ocaml-lsp-server/docs/ocamllsp/construct-spec.md) request (#1348)
|
||||
|
||||
# 1.18.0
|
||||
|
||||
## Features
|
||||
|
||||
- Introduce a configuration option to control dune diagnostics. The option is
|
||||
called `duneDiganostics` and it may be set to `{ enable: false }` to disable
|
||||
diagnostics. (#1221)
|
||||
|
||||
- Support folding of `ifthenelse` expressions (#1031)
|
||||
|
||||
- Improve hover behavior (#1245)
|
||||
|
||||
Hovers are no longer displaye on useless parsetree nodes such as keywords,
|
||||
comments, etc.
|
||||
|
||||
Multiline hovers are now filtered away.
|
||||
|
||||
Display expanded ppx's in the hover window.
|
||||
|
||||
- Improve document symbols (#1247)
|
||||
|
||||
Use the parse tree instead of the typed tree. This means that document
|
||||
symbols will work even if the source code doesn't type check.
|
||||
|
||||
Include symbols at arbitrary depth.
|
||||
|
||||
Differentiate functions / types / variants / etc.
|
||||
|
||||
This now includes PPXs like `let%expect_test` or `let%bench` in the outline.
|
||||
|
||||
- Introduce a `destruct-line` code action. This is an improved version of the
|
||||
old `destruct` code action. (#1283)
|
||||
|
||||
- Improve signature inference to only include types for elements that were
|
||||
absent from the signature. Previously, all signature items would always be
|
||||
inserted. (#1289)
|
||||
|
||||
- Add an `update-signature` code action to update the types of elements that
|
||||
were already present in the signature (#1289)
|
||||
|
||||
- Add custom
|
||||
[`ocamllsp/merlinCallCompatible`](https://github.com/ocaml/ocaml-lsp/blob/e165f6a3962c356adc7364b9ca71788e93489dd0/ocaml-lsp-server/docs/ocamllsp/merlinCallCompatible-spec.md)
|
||||
request (#1265)
|
||||
|
||||
- Add custom [`ocamllsp/typeEnclosing`](https://github.com/ocaml/ocaml-lsp/blob/109801e56f2060caf4487427bede28b824f4f1fe/ocaml-lsp-server/docs/ocamllsp/typeEnclosing-spec.md) request (#1304)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Detect document kind by looking at merlin's `suffixes` config.
|
||||
|
||||
This enables more lsp features for non-.ml/.mli files. Though it still
|
||||
depends on merlin's support. (#1237)
|
||||
|
||||
- Correctly accept the `--clientProcessId` flag. (#1242)
|
||||
|
||||
- Disable automatic completion and signature help inside comments (#1246)
|
||||
|
||||
- Includes a new optional/configurable option to toggle syntax documentation. If
|
||||
toggled on, allows display of syntax documentation on hover tooltips. Can be
|
||||
controlled via environment variables and by GUI for VS code. (#1218)
|
||||
|
||||
- For completions on labels that the LSP gets from merlin, take into account
|
||||
whether the prefix being completed starts with `~` or `?`. Change the label
|
||||
completions that start with `?` to start with `~` when the prefix being
|
||||
completed starts with `~`. (#1277)
|
||||
|
||||
- Fix document syncing (#1278, #1280, fixes #1207)
|
||||
|
||||
- Stop generating inlay hints on generated code (#1290)
|
||||
|
||||
- Fix parenthesizing of function types in `SignatureHelp` (#1296)
|
||||
|
||||
- Fix syntax documentation rendering (#1318)
|
||||
|
||||
# 1.17.0
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix missing super & subscripts in markdown documentation. (#1170)
|
||||
|
||||
- Do not invoke dune at all if `--fallback-read-dot-merlin` flag is on. (#1173)
|
||||
|
||||
- Fix semantic highlighting of infix operators that contain '.'. (#1186)
|
||||
|
||||
- Disable highlighting unit as an enum member to fix comment highlighting bug. (#1185)
|
||||
|
||||
- Improve type-on-hover and type-annotate efficiency by only formatting the type
|
||||
of the first enclosing. (#1191, #1196)
|
||||
|
||||
- Fix the encoding of URI's to match how vscode does it (#1197)
|
||||
|
||||
- Fix parsing of completion prefixes (#1181)
|
||||
|
||||
## Features
|
||||
|
||||
- Compatibility with Odoc 2.3.0, with support for the introduced syntax: tables,
|
||||
and "codeblock output" (#1184)
|
||||
|
||||
- Display text of references in doc strings (#1166)
|
||||
|
||||
- Add mark/remove unused actions for open, types, for loop indexes, modules,
|
||||
match cases, rec, and constructors (#1141)
|
||||
- Add inlay hints for types on let bindings (#1159)
|
||||
|
||||
- Offer auto-completion for the keyword `in` (#1217)
|
||||
|
||||
# 1.16.2
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix file permissions used when specifying output files of pp and ppx. (#1153)
|
||||
|
||||
# 1.16.1
|
||||
|
||||
## Fixes
|
||||
|
||||
- Support building with OCaml 5.0 and 5.1 (#1150)
|
||||
|
||||
# 1.16.0
|
||||
|
||||
## Fixes
|
||||
|
||||
- Disable code lens by default. The support can be re-enabled by explicitly
|
||||
setting it in the configuration. (#1134)
|
||||
|
||||
- Fix initilization of `ocamlformat-rpc` in some edge cases when ocamlformat is
|
||||
initialized concurrently (#1132)
|
||||
|
||||
- Kill unnecessary `$ dune ocaml-merlin` with SIGTERM rather than SIGKILL
|
||||
(#1124)
|
||||
|
||||
- Refactor comment parsing to use `odoc-parser` and `cmarkit` instead of
|
||||
`octavius` and `omd` (#1088)
|
||||
|
||||
This allows users who migrated to omd 2.X to install ocaml-lsp-server in the
|
||||
same opam switch.
|
||||
|
||||
We also slightly improved markdown generation support and fixed a couple in
|
||||
the generation of inline heading and module types.
|
||||
|
||||
- Allow opening documents that were already open. This is a workaround for
|
||||
neovim's lsp client (#1067)
|
||||
|
||||
- Disable type annotation for functions (#1054)
|
||||
|
||||
- Respect codeActionLiteralSupport capability (#1046)
|
||||
|
||||
- Fix a document syncing issue when utf-16 is the position encoding (#1004)
|
||||
|
||||
- Disable "Type-annotate" action for code that is already annotated.
|
||||
([#1037](https://github.com/ocaml/ocaml-lsp/pull/1037)), fixes
|
||||
[#1036](https://github.com/ocaml/ocaml-lsp/issues/1036)
|
||||
|
||||
- Fix semantic highlighting of long identifiers when using preprocessors
|
||||
([#1049](https://github.com/ocaml/ocaml-lsp/pull/1049), fixes
|
||||
[#1034](https://github.com/ocaml/ocaml-lsp/issues/1034))
|
||||
|
||||
- Fix the type of DocumentSelector in cram document registration (#1068)
|
||||
|
||||
- Accept the `--clientProcessId` command line argument. (#1074)
|
||||
|
||||
- Accept `--port` as a synonym for `--socket`. (#1075)
|
||||
|
||||
- Fix connecting to dune rpc on Windows. (#1080)
|
||||
|
||||
## Features
|
||||
|
||||
- Add "Remove type annotation" code action. (#1039)
|
||||
|
||||
- Support settings through `didChangeConfiguration` notification (#1103)
|
||||
|
||||
- Add "Extract local" and "Extract function" code actions. (#870)
|
||||
|
||||
- Depend directly on `merlin-lib` 4.9 (#1070)
|
||||
|
||||
# 1.15.1
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix race condition when a document was being edited and dune in watch mode was
|
||||
running ([#1005](https://github.com/ocaml/ocaml-lsp/pull/1005), fixes
|
||||
[#941](https://github.com/ocaml/ocaml-lsp/issues/941),
|
||||
[#1003](https://github.com/ocaml/ocaml-lsp/issues/1003))
|
||||
|
||||
# 1.15.0
|
||||
|
||||
## Features
|
||||
|
||||
- Enable [semantic highlighting](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens)
|
||||
support by default (#933)
|
||||
|
||||
- Support connecting over pipes and socket. Pipes on Windows aren't yet
|
||||
supported (#946)
|
||||
|
||||
[More](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#implementationConsiderations)
|
||||
about communication channels in LSP specification.
|
||||
|
||||
- Re-enable `ocamlformat-rpc` for formatting code snippets (but not files and
|
||||
not on Windows) (#920, #939)
|
||||
|
||||
One needs to have installed either `ocamlformat` package version > 0.21.0 or,
|
||||
otherwise, `ocamlformat-rpc` package. Note that previously `ocamlformat-rpc`
|
||||
came in a standalone OPAM package, but since `ocamlformat` version > 0.21.0,
|
||||
it comes within `ocamlformat` package.
|
||||
|
||||
- Add custom
|
||||
[`ocamllsp/hoverExtended`](https://github.com/ocaml/ocaml-lsp/blob/e165f6a3962c356adc7364b9ca71788e93489dd0/ocaml-lsp-server/docs/ocamllsp/hoverExtended-spec.md#L1)
|
||||
request (#561)
|
||||
|
||||
- Support utf-8 position encoding clients (#919)
|
||||
|
||||
[More](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position) about position encoding in LSP specification.
|
||||
|
||||
- Show unwrapped module alias types on hovering over module names. This is due
|
||||
to upgrading to merlin 4.7 and using merlin's `verbosity=smart` by default
|
||||
(#942)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Respect the client's completion item resolve and preSelect capabilities
|
||||
(#925, #936)
|
||||
|
||||
- Disable polling for dune's watch mode on Windows and OCaml 4.14.0 (#935)
|
||||
|
||||
- Fix semantic highlighting of "long identifiers," e.g., `Foo.Bar.x` (#932)
|
||||
|
||||
- Fix syncing of document contents:
|
||||
|
||||
- For ranges that span an entire line (#927)
|
||||
- Previously, whole line edits would incorrectly eat the newline characters (#971)
|
||||
|
||||
# 1.14.2
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix random requests failing after switching documents (#904, fixes #898)
|
||||
|
||||
- Do not offer related diagnostic information unless the user enables in client
|
||||
capabilities (#905)
|
||||
|
||||
- Do not offer diagnostic tags unless the client supports them (#909)
|
||||
|
||||
- Do not attach extra data to diagnostics unless the client supports this
|
||||
(#910)
|
||||
|
||||
- Use /bin/sh instead of /bin/bash. This fixes ocamllsp on NixOS
|
||||
|
||||
# 1.14.1
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix various server crashes when opening non OCaml/Reason files. Files such as
|
||||
dune, cram, etc. would cause the server to crash. (#884, fixes #871)
|
||||
|
||||
- Ignore unknown tags in merlin configuration to improve forward compatibility
|
||||
with Dune. (#883)
|
||||
|
||||
# 1.14.0
|
||||
|
||||
## Features
|
||||
|
||||
- Code action for inlining let bindings within a module or expression. (#847)
|
||||
|
||||
- Tag "unused code" and "deprecated" warnings, allowing clients to better
|
||||
display them. (#848)
|
||||
|
||||
- Refresh merlin configuration after every dune build in watch mode (#853)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Respect `showDocument` capabilities. Do not offer commands or code actions
|
||||
that rely on this request without client support. (#836)
|
||||
|
||||
- Fix signatureHelp on .mll files: avoid "Document.dune" exceptions
|
||||
|
||||
# 1.13.1
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix cwd when executing ppx (#805)
|
||||
|
||||
# 1.13.0
|
||||
|
||||
## Features
|
||||
|
||||
- Code actions for jumping to related files (`.ml`, `.mli`, etc.) (#795)
|
||||
|
||||
# 1.12.4
|
||||
|
||||
- Allow cancellation of workspace symbols requests (#777)
|
||||
|
||||
- Fix unintentionally interleaved jsonrpc IO that would corrupt the session
|
||||
(#786)
|
||||
|
||||
- Ignore `SIGPIPE` . (#788)
|
||||
|
||||
# 1.12.3
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix a bad interaction between inferred interfaces and promotion code actions
|
||||
in watch mode (#753)
|
||||
|
||||
- Fix URI parsing (#739 fixes #471 and #459)
|
||||
|
||||
# 1.12.2
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix shutting down an already closed socket (#740)
|
||||
|
||||
# 1.12.1
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix preprocessing, ppx, and reason support (#735 fixes #696, #706)
|
||||
|
||||
- Support `include` in folding ranges (#730)
|
||||
|
||||
# 1.12.0
|
||||
|
||||
## Features
|
||||
|
||||
- Fix cancellation mechanism for all requests (#707)
|
||||
|
||||
- Allow cancellation of formatting requests (#707)
|
||||
|
||||
- Add `--fallback-read-dot-merlin` to the LSP Server (#705). If `ocamllsp` is
|
||||
started with this new flag, it will fall back to looking for Merlin
|
||||
configuration in `.merlin` files rather than calling `dune ocaml-merlin`.
|
||||
(#705)
|
||||
|
||||
- Support folding more ranges (#692)
|
||||
|
||||
# 1.11.6
|
||||
|
||||
## Fixes
|
||||
|
||||
- Stop leaking file descriptors like a sieve (#701)
|
||||
|
||||
# 1.11.5
|
||||
|
||||
- Fix process termination. Once the lsp server is stepped, the process will
|
||||
gracefully terminate (#697, fixes #694)
|
||||
|
||||
- Forward stderr from dune's merlin configuration to the lsp server's stderr
|
||||
(#697)
|
||||
|
||||
# 1.11.4
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix bug with large buffers being resized incorrectly in Lev
|
||||
|
||||
- Add folding ranges for more AST types (#680)
|
||||
|
||||
# 1.11.3
|
||||
|
||||
## Fixes
|
||||
|
||||
- Enable dune rpc integration by default (#691, fixes #690)
|
||||
|
||||
# 1.11.2
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix running external processes on Windows
|
||||
|
||||
# 1.11.1
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix Uri handling on Windows
|
||||
|
||||
- Fix build on MSVC 2015
|
||||
|
||||
# 1.11.0
|
||||
|
||||
## Features
|
||||
|
||||
- Add support for dune in watch mode. The lsp server will now display build
|
||||
errors in the diagnostics and offer promotion code actions.
|
||||
|
||||
- Re-introduce ocamlformat-rpc (#599, fixes #495)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix workspace symbols that could have a wrong path in some cases
|
||||
([#675](https://github.com/ocaml/ocaml-lsp/pull/671))
|
||||
|
||||
# 1.10.6
|
||||
|
||||
## Fixes
|
||||
|
||||
- Compatiblity with OCaml 4.14.0
|
||||
|
||||
# 1.10.5
|
||||
|
||||
## Fixes
|
||||
|
||||
- Patch merlin to remove the result module
|
||||
|
||||
# 1.10.4
|
||||
|
||||
## Fixes
|
||||
|
||||
- Use newer versions of ocamlformat-rpc-lib (fixes #697)
|
||||
|
||||
# 1.10.3
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix more debouncing bugs (#629)
|
||||
|
||||
# 1.10.2
|
||||
|
||||
## Fixes
|
||||
|
||||
- Catch merlin desturct exceptions (#626)
|
||||
|
||||
- Fix broken debouncing (#627)
|
||||
|
||||
# 1.10.1
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix executing ppx executables
|
||||
|
||||
# 1.10.0
|
||||
|
||||
## Features
|
||||
|
||||
- Add better support for code folding: more folds and more precise folds
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix infer interface code action crash when implementation source does not
|
||||
exist (#597)
|
||||
|
||||
- Improve error message when the reason plugin for merlin is absent (#608)
|
||||
|
||||
- Fix `chdir` races when running ppx (#550)
|
||||
|
||||
- More accurate completion kinds.
|
||||
New completion kinds for variants and fields. Removed inaccurate completion
|
||||
kinds for constructors and types. (#510)
|
||||
|
||||
- Fix handling request cancellation (#616)
|
||||
|
||||
# 1.9.1
|
||||
|
||||
## Fixes
|
||||
|
||||
- Disable functionality reliant on ocamlformat-rpc for now (#555)
|
||||
|
||||
- 4.13 compatiblity
|
||||
|
||||
# 1.9.0 (11/21/2021)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Ppx processes are now executed correctly (#513)
|
||||
|
||||
## Breaking Change
|
||||
|
||||
- ocamllsp drops support for `.merlin` files, and as a consequence no longer
|
||||
depends on dot-merlin-reader. (#523)
|
||||
|
||||
## Features
|
||||
|
||||
- New code action to automatically remove values, types, opens (#502)
|
||||
|
||||
# 1.8.3 (09/26/2021)
|
||||
|
||||
- Fix debouncing of document updates. It was essentially completely broken in
|
||||
all but the most trivial cases. (#509 fixes #504)
|
||||
|
||||
- Fix completion when passing named and functional arguments (#512)
|
||||
|
||||
# 1.8.2 (09/14/2021)
|
||||
|
||||
- Disable experimental dune support. It was accidentally left enabled.
|
||||
|
||||
# 1.8.1 (09/12/2021)
|
||||
|
||||
- Update to latest merlin.
|
||||
|
||||
# 1.8.0 (08/19/2021)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Handle workspace change notifications. Previously, the server would only use
|
||||
the set of workspaces given at startup to search for workspace symbols. After
|
||||
this change, workspace folders that are added later will also be considered.
|
||||
(#498)
|
||||
|
||||
## Features
|
||||
|
||||
- Add a new code action `Add missing rec keyword`, which is available when
|
||||
adding a `rec` keyword can fix `Unbound value ...` error, e.g.,
|
||||
|
||||
```ocaml
|
||||
let fact n = if n = 0 then 1 else n * fact (n - 1)
|
||||
(* ^^^^ Unbound value fact *)
|
||||
```
|
||||
|
||||
Adding `rec` to the definition of `fact` will fix the problem. The new code
|
||||
action offers adding `rec`.
|
||||
|
||||
- Use ocamlformat to properly format type snippets. This feature requires the
|
||||
`ocamlformat-rpc` opam package to be installed. (#386)
|
||||
|
||||
- Add completion support for polymorphic variants, when it is possible to pin
|
||||
down the precise type. Examples (`<|>` stands for the cursor) when completion
|
||||
will work (#473)
|
||||
|
||||
Function application:
|
||||
|
||||
```
|
||||
let foo (a: [`Alpha | `Beta]) = ()
|
||||
|
||||
foo `A<|>
|
||||
```
|
||||
|
||||
Type explicitly shown:
|
||||
|
||||
```
|
||||
let a : [`Alpha | `Beta] = `B<|>
|
||||
```
|
||||
|
||||
Note: this is actually a bug fix, since we were ignoring the backtick when
|
||||
constructing the prefix for completion.
|
||||
|
||||
- Parse merlin errors (best effort) into a more structured form. This allows
|
||||
reporting all locations as "related information" (#475)
|
||||
|
||||
- Add support for Merlin `Construct` command as completion suggestions, i.e.,
|
||||
show complex expressions that could complete the typed hole. (#472)
|
||||
|
||||
- Add a code action `Construct an expression` that is shown when the cursor is
|
||||
at the end of the typed hole, i.e., `_|`, where `|` is the cursor. The code
|
||||
action simply triggers the client (currently only VS Code is supported) to
|
||||
show completion suggestions. (#472)
|
||||
|
||||
- Change the formatting-on-save error notification to a warning notification
|
||||
(#472)
|
||||
|
||||
- Code action to qualify ("put module name in identifiers") and unqualify
|
||||
("remove module name from identifiers") module names in identifiers (#399)
|
||||
|
||||
Starting from:
|
||||
|
||||
```ocaml
|
||||
open Unix
|
||||
|
||||
let times = Unix.times ()
|
||||
let f x = x.Unix.tms_stime, x.Unix.tms_utime
|
||||
```
|
||||
|
||||
Calling "remove module name from identifiers" with the cursor on the open
|
||||
statement will produce:
|
||||
|
||||
```ocaml
|
||||
open Unix
|
||||
|
||||
let times = times ()
|
||||
let f x = x.tms_stime, x.tms_utime
|
||||
```
|
||||
|
||||
Calling "put module name in identifiers" will restore:
|
||||
|
||||
```ocaml
|
||||
open Unix
|
||||
|
||||
let times = Unix.times ()
|
||||
let f x = x.Unix.tms_stime, x.Unix.tms_utime
|
||||
```
|
||||
|
||||
## Fixes
|
||||
|
||||
- Do not show "random" documentation on hover
|
||||
|
||||
- fixed by [merlin#1364](https://github.com/ocaml/merlin/pull/1364)
|
||||
- fixes duplicate:
|
||||
- [ocaml-lsp#344](https://github.com/ocaml/ocaml-lsp/issues/344)
|
||||
- [vscode-ocaml-platform#111](https://github.com/ocamllabs/vscode-ocaml-platform/issues/111)
|
||||
|
||||
- Correctly rename a variable used as a named/optional argument (#478)
|
||||
|
||||
- When reporting an error at the beginning of the file, use the first line not
|
||||
the second (#489)
|
||||
|
||||
# 1.7.0 (07/28/2021)
|
||||
|
||||
## Features
|
||||
|
||||
- Add sub-errors as "related" information in diagnostics (#457)
|
||||
|
||||
- Add support for navigating to a symbol inside a workspace (#398)
|
||||
|
||||
- Show typed holes as errors
|
||||
|
||||
Merlin has a concept of "typed holes" that are syntactically represented as `_`. Files
|
||||
that incorporate typed holes are not considered valid OCaml, but Merlin and OCaml-LSP
|
||||
support them. One example when such typed holes can occur is when on "destructs" a value,
|
||||
e.g., destructing `(Some 1)` will generate code `match Some 1 with Some _ -> _ | None -> _`. While the first underscore is a valid "match-all"/wildcard pattern, the rest of
|
||||
underscores are typed holes.
|
||||
|
||||
# 1.6.1 (05/17/2020)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Switch `verbosity` from 1 to 0. This is the same default that merlin uses.
|
||||
The old value for verbosity (#433)
|
||||
|
||||
- Get fresh diagnostics (warning and error messages) on a file save (#438)
|
||||
|
||||
Note: If you want the fresh diagnostics to take into account changes in other
|
||||
files, you likely need to rebuild your project. An easy way to get automatic
|
||||
rebuilds is to run `dune` in a watching mode, e.g.,[dune build --watch].
|
||||
|
||||
# 1.6.0 (04/30/2020)
|
||||
|
||||
## Features
|
||||
|
||||
- Code action to annotate a value with its type (#397)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix interface/implementation switching on Windows (#427)
|
||||
|
||||
- Correctly parse project paths with spaces and other special characters that
|
||||
must be escaped.
|
||||
|
||||
- Print types with `-short-paths` even if the project wasn't built yet
|
||||
|
||||
# 1.5.0 (03/18/2020)
|
||||
|
||||
- Support 4.12 and drop support for all earlier versions
|
||||
|
||||
- Update to the latest version of merlin
|
||||
|
||||
# 1.4.1 (03/16/2020)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Backport fixes from merlin (#382, #383)
|
||||
|
||||
- Encode request & notification `params` in a list. This is required by the
|
||||
spec. (#351)
|
||||
|
||||
# 1.4.0 (12/17/2020)
|
||||
|
||||
## Features
|
||||
|
||||
- Support cancellation notifications when possible. (#323)
|
||||
|
||||
- Implement signature help request for functions (#324)
|
||||
|
||||
- Server LSP requests & notifications concurrently. Requests that require merlin
|
||||
are still serialized. (#330)
|
||||
|
||||
# 1.3.0 (11/23/2020)
|
||||
|
||||
## Features
|
||||
|
||||
- Code action to insert inferred module interface (#308)
|
||||
|
||||
- Filter keywords by context (#307)
|
||||
|
||||
# 1.2.0 (11/16/2020)
|
||||
|
||||
## Features
|
||||
|
||||
- Add keyword completion
|
||||
|
||||
- Add go to declaration functionality to jump to a value's specification in a
|
||||
.mli file (#294)
|
||||
|
||||
## Fixes
|
||||
|
||||
- #245: correctly use mutexes on OpenBSD (#264)
|
||||
|
||||
- #268: Do not use vendored libraries when building the lsp package (#260)
|
||||
|
||||
- #271: Clear diagnostics when files are closed
|
||||
|
||||
- Disable non-prefix completion. There's no reliably way to trigger it and it
|
||||
can be slow.
|
||||
|
||||
# 1.1.0 (10/14/2020)
|
||||
|
||||
## Features
|
||||
|
||||
- Implement a command to switch between module interfaces and implementations
|
||||
(#254)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Do not crash on invalid positions (#248)
|
||||
|
||||
- add missing record fields to list of completions (#253)
|
||||
|
||||
- do not offer `destruct` as a code action in interface files (#255)
|
||||
|
||||
# 1.0.0 (08/28/2020)
|
||||
|
||||
- Initial Release
|
||||
13
thirdparty/lsp/CODE_OF_CONDUCT.md
vendored
Normal file
13
thirdparty/lsp/CODE_OF_CONDUCT.md
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Code of Conduct
|
||||
|
||||
This project has adopted the [OCaml Code of Conduct](https://github.com/ocaml/code-of-conduct/blob/main/CODE_OF_CONDUCT.md).
|
||||
|
||||
# Enforcement
|
||||
|
||||
This project follows the OCaml Code of Conduct
|
||||
[enforcement policy](https://github.com/ocaml/code-of-conduct/blob/main/CODE_OF_CONDUCT.md#enforcement).
|
||||
To report any violations, please contact:
|
||||
|
||||
- Sonja Heinze <sonja [at] tarides [dot] com>
|
||||
- Ulysse Gérard <ulysse [at] tarides [dot] com>
|
||||
- Xavier Van de Woestyne <xavier [at] tarides [dot] com>
|
||||
124
thirdparty/lsp/CONTRIBUTING.md
vendored
Normal file
124
thirdparty/lsp/CONTRIBUTING.md
vendored
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
# Contributing to ocaml-lsp
|
||||
|
||||
OCaml-lsp is a community oriented open-source project and we encourage and value
|
||||
any kind of contribution. Thanks for taking the time to contribute 🐫 !
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
OCaml-lsp adheres to the OCaml Code of Conduct as stated in the [Code of Conduct
|
||||
document](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this
|
||||
code. Please report unacceptable behavior either to local contacts (listed in
|
||||
[here](CODE_OF_CONDUCT.md)) or to someone listed in the upstream [OCaml Code of
|
||||
Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
## Documentation
|
||||
|
||||
Much of the information relating to the repository, such as installation
|
||||
guidelines, how to set up a development environment and how to run unit tests,
|
||||
can be found in the project [README](README.md). And custom requests are
|
||||
documented in the
|
||||
[ocaml-lsp-server/docs/ocamllsp](ocaml-lsp-server/docs/ocamllsp) directory.
|
||||
|
||||
Contributions to the documentation are welcome!
|
||||
|
||||
|
||||
## Question, bug reports and feature requests
|
||||
|
||||
We rely on [Github's issue tracker](https://github.com/ocaml/ocaml-lsp/issues) for
|
||||
support questions, feature requests and bug reports.
|
||||
|
||||
When reporting an issue, please include a precise reproduction in the bug report
|
||||
when that's possible, as it is a very useful tool to investigate. You should
|
||||
also check that you are using the latest version of OCaml-lsp and that a similar
|
||||
issue has not already been submitted.
|
||||
|
||||
## Code contributions
|
||||
|
||||
### Styleguides
|
||||
|
||||
- **OCaml**: a large part of the code base is written in OCaml and the project
|
||||
is configured to work with
|
||||
[ocamlformat](https://ocaml.org/p/ocamlformat/latest) (version defined in the
|
||||
[`.ocamlformat` file](.ocamlformat)).
|
||||
|
||||
- **TypeScript**: TypeScript is used to describe certain end-to-end tests
|
||||
(abbreviated as `e2e`) and the project uses the
|
||||
[prettier](https://prettier.io/) formatter. But the TypeScript testsuite is
|
||||
deprecated (we do not allow extending them anymore. Gradually we'll rewrite
|
||||
them all to OCaml).
|
||||
|
||||
Apart from that, the project tries to apply implicit conventions, at the
|
||||
decision of the maintainers. At the same time, it tries to follow certain naming
|
||||
conventions:
|
||||
|
||||
- use of `t` manifest types in modules, when it makes sense;
|
||||
- conversion functions respecting the naming scheme: `to_xxx` or `of_xxx`;
|
||||
- When you want to provide a conversion function for JSON, use the following
|
||||
convention: `t_of_yojson` and `yojson_of_t` to fit properly with
|
||||
`ppx_yojson_conv`.
|
||||
|
||||
Changes unrelated to the issue addressed by a PR should be made in a separate
|
||||
PR. Additionally, formatting changes in parts of the code not concerned by a
|
||||
specific PR should be proposed in another PR.
|
||||
|
||||
Ideally, any opened issue should be accompanied by a test with a reproduction.
|
||||
When working on a fix for an issue, the first commit should contain the test
|
||||
showing the issue. Following commits should fix the issue and update the test
|
||||
result accordingly.
|
||||
|
||||
### Repository organization
|
||||
|
||||
The repository exposes a number of separate libraries (some of which are
|
||||
internal) and vendor libraries (to reduce the dependencies required by the
|
||||
project). Here is a list of the libraries exposed by the project.
|
||||
|
||||
#### ocaml-lsp-server
|
||||
|
||||
Contains the concrete implementation of a protocol server language for OCaml. A
|
||||
frontend used in particular by [Visual Studio
|
||||
Code](https://github.com/ocamllabs/vscode-ocaml-platform), but also by code
|
||||
editors supporting LSP. The code lives mainly in the following directories:
|
||||
[ocaml-lsp-server/](ocaml-lsp-server/).
|
||||
|
||||
In addition, the project exposes two sub-directories dedicated to [code
|
||||
actions](ocaml-lsp-server/src/code_actions) and [custom
|
||||
requests](https://github.com/ocaml/ocaml-lsp/tree/master/ocaml-lsp-server/src/custom_requests).
|
||||
|
||||
In most cases, it is likely that the contributions will focus solely on this
|
||||
project.
|
||||
|
||||
##### Warning
|
||||
|
||||
For historical reasons, but also for development convenience, `ocaml-lsp-server`
|
||||
should not build logic based on `Typedtree` (which changes from version to
|
||||
version and migration logic is not provided by `lsp`, nor `ocaml-lsp-server` but
|
||||
by [Merlin](https://github.com/ocaml/merlin)). If a command, or a constant,
|
||||
relies on the `Typedtree`, it can be marked as _unstable_. Another approach,
|
||||
more robust, is to build a command in Merlin that handles the logic to ensure
|
||||
the migration is localized to a single project.
|
||||
|
||||
#### lsp
|
||||
|
||||
Implementation of the LSP protocol in OCaml. It is designed to be as portable as
|
||||
possible and does not make any assumptions about IO. This is the implementation
|
||||
of the plumbing required to describe the LSP protocol and is used by a concrete
|
||||
server (for example the OCaml server) to describe the exposition of the
|
||||
protocol. The code lives mainly in the following directories: [lsp/](lsp/) and
|
||||
[lsp-fiber/](lsp-fiber/).
|
||||
|
||||
##### Warning
|
||||
|
||||
The set of types forming the LSP protocol API is generated automatically by a
|
||||
[preprocessor](lsp/bin) based on the [protocol
|
||||
specification](https://microsoft.github.io/language-server-protocol/overviews/lsp/overview/).
|
||||
The pair of [types.ml](lsp/src/types.ml) and [types.mli](lsp/src/types.mli)
|
||||
files must be consciously modified manually (never modifying the parts marked as
|
||||
being generated by CINAPS, the preprocessor).
|
||||
|
||||
|
||||
#### jsonrpc
|
||||
|
||||
Describes an implementation of the [JSON-RPC
|
||||
2.0](https://www.jsonrpc.org/specification) protocol, which is mainly used as a
|
||||
communication protocol for LSP. The code lives mainly in the following
|
||||
directories: [jsonrpc](jsonrpc/) and [jsonrpc-fiber/](jsonrpc-fiber/).
|
||||
16
thirdparty/lsp/LICENSE.md
vendored
Normal file
16
thirdparty/lsp/LICENSE.md
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
ISC License
|
||||
|
||||
Copyright (X) 2018-2019, the [ocaml-lsp
|
||||
contributors](https://github.com/ocaml/ocaml-lsp/graphs/contributors)
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
104
thirdparty/lsp/Makefile
vendored
Normal file
104
thirdparty/lsp/Makefile
vendored
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
.DEFAULT_GOAL := all
|
||||
|
||||
TEST_E2E_DIR = ocaml-lsp-server/test/e2e
|
||||
|
||||
.PHONY: yarn-install
|
||||
yarn-install:
|
||||
yarn install --frozen-lockfile
|
||||
|
||||
-include Makefile.dev
|
||||
|
||||
.PHONY: all
|
||||
all:
|
||||
dune build @all
|
||||
|
||||
# we don't use --with-test because it pulls test dependencies transitively in
|
||||
# practice this ends up pulling a lot of extra packages which invariably
|
||||
# results in a conflict
|
||||
.PHONY: install-test-deps
|
||||
install-test-deps:
|
||||
opam install --yes cinaps 'ppx_expect>=v0.17.0' \
|
||||
ocamlformat.$$(awk -F = '$$1 == "version" {print $$2}' .ocamlformat)
|
||||
|
||||
.PHONY: dev
|
||||
dev: ## Setup a development environment
|
||||
opam switch create --no-install . ocaml-base-compiler.4.14.0
|
||||
opam install -y dune-release merlin ocamlformat utop ocaml-lsp-server
|
||||
opam install --locked --deps-only --with-doc -y .
|
||||
$(MAKE) install-test-deps
|
||||
|
||||
.PHONY: install
|
||||
install: ## Install the packages on the system
|
||||
dune build @install && dune install
|
||||
|
||||
.PHONY: lock
|
||||
lock: ## Generate the lock files
|
||||
opam lock -y .
|
||||
|
||||
.PHONY: bench
|
||||
bench: ##
|
||||
dune exec ocaml-lsp-server/bench/ocaml_lsp_bench.exe --profile bench
|
||||
|
||||
.PHONY: test-ocaml
|
||||
test-ocaml: ## Run the unit tests
|
||||
dune build @lsp/test/runtest @lsp-fiber/runtest @jsonrpc-fiber/runtest @ocaml-lsp-server/runtest
|
||||
|
||||
.PHONY: promote
|
||||
promote:
|
||||
dune promote
|
||||
|
||||
.PHONY: check
|
||||
check:
|
||||
dune build @check
|
||||
|
||||
.PHONY: test-e2e
|
||||
test-e2e: yarn-install
|
||||
dune build @install && dune exec -- ocaml-lsp-server/test/run_test_e2e.exe
|
||||
|
||||
.PHONY: promote-e2e
|
||||
promote-e2e:
|
||||
dune build @install && cd $(TEST_E2E_DIR) && dune exec -- yarn run promote
|
||||
|
||||
.PHONY: test
|
||||
test: test-ocaml test-e2e
|
||||
|
||||
.PHONY: clean
|
||||
clean: ## Clean build artifacts and other generated files
|
||||
dune clean
|
||||
|
||||
.PHONY: fmt
|
||||
fmt: ## Format the codebase with ocamlformat
|
||||
dune build @fmt --auto-promote
|
||||
|
||||
.PHONY: watch
|
||||
watch: ## Watch for the filesystem and rebuild on every change
|
||||
dune build --watch
|
||||
|
||||
.PHONY: utop
|
||||
utop: ## Run a REPL and link with the project's libraries
|
||||
dune utop . -- -implicit-bindings
|
||||
|
||||
.PHONY: release
|
||||
release: ## Release on Opam
|
||||
dune-release distrib --skip-build --skip-lint --skip-tests --include-submodules
|
||||
# See https://github.com/ocamllabs/dune-release/issues/206
|
||||
DUNE_RELEASE_DELEGATE=github-dune-release-delegate dune-release publish distrib --verbose
|
||||
dune-release opam pkg
|
||||
dune-release opam submit
|
||||
|
||||
.PHONY: nix-tests
|
||||
nix-tests: yarn-install
|
||||
make test
|
||||
|
||||
.PHONY: nix-fmt
|
||||
nix-fmt: yarn-install
|
||||
dune build @fmt --auto-promote
|
||||
|
||||
.PHONY: coverage-deps
|
||||
coverage-deps:
|
||||
opam install -y bisect_ppx
|
||||
|
||||
.PHONY: test-coverage
|
||||
test-coverage:
|
||||
dune build --instrument-with bisect_ppx --force @lsp/test/runtest @lsp-fiber/runtest @jsonrpc-fiber/runtest @ocaml-lsp-server/runtest
|
||||
bisect-ppx-report send-to Coveralls
|
||||
423
thirdparty/lsp/README.md
vendored
Normal file
423
thirdparty/lsp/README.md
vendored
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
# OCaml-LSP <!-- omit from toc -->
|
||||
<!-- TOC is updated automatically by "Markdown All in One" vscode extension -->
|
||||
|
||||
[![Build][build-badge]][build]
|
||||
[![Coverage Status][coverall-badge]][coverall]
|
||||
|
||||
[build-badge]: https://github.com/ocaml/ocaml-lsp/actions/workflows/build-and-test.yml/badge.svg
|
||||
[build]: https://github.com/ocaml/ocaml-lsp/actions
|
||||
[coverall-badge]: https://coveralls.io/repos/github/ocaml/ocaml-lsp/badge.svg?branch=master
|
||||
[coverall]: https://coveralls.io/github/ocaml/ocaml-lsp?branch=master
|
||||
|
||||
OCaml-LSP is a language server for OCaml that implements [Language Server
|
||||
Protocol](https://microsoft.github.io/language-server-protocol/) (LSP).
|
||||
|
||||
> If you use Visual Studio Code, see OCaml Platform extension
|
||||
> [page](https://github.com/ocamllabs/vscode-ocaml-platform) for detailed
|
||||
> instructions on setting up your editor for OCaml development with OCaml-LSP:
|
||||
> what packages need to be installed, how to configure your project and get
|
||||
> most out of the OCaml editor support, and how to report and debug problems.
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Installing with package managers](#installing-with-package-managers)
|
||||
- [Opam](#opam)
|
||||
- [Esy](#esy)
|
||||
- [Installing from sources](#installing-from-sources)
|
||||
- [Additional package installations](#additional-package-installations)
|
||||
- [Usage](#usage)
|
||||
- [Integration with Dune RPC](#integration-with-dune-rpc)
|
||||
- [Merlin configuration (advanced)](#merlin-configuration-advanced)
|
||||
- [Features](#features)
|
||||
- [Semantic highlighting](#semantic-highlighting)
|
||||
- [LSP Extensions](#lsp-extensions)
|
||||
- [Unusual features](#unusual-features)
|
||||
- [Debugging](#debugging)
|
||||
- [Contributing to project](#contributing-to-project)
|
||||
- [Changelog](#changelog)
|
||||
- [Tests](#tests)
|
||||
- [Relationship to Other Tools](#relationship-to-other-tools)
|
||||
- [History](#history)
|
||||
- [Comparison to other LSP Servers for OCaml](#comparison-to-other-lsp-servers-for-ocaml)
|
||||
|
||||
## Installation
|
||||
|
||||
Below we show how to install OCaml-LSP using opam, esy, and from sources. OCaml-LSP comes in a package called `ocaml-lsp-server` but the installed program (i.e., binary) is called `ocamllsp`.
|
||||
|
||||
### Installing with package managers
|
||||
|
||||
#### Opam
|
||||
|
||||
To install the language server in the currently used opam [switch](https://opam.ocaml.org/doc/Manual.html#Switches):
|
||||
|
||||
```sh
|
||||
$ opam install ocaml-lsp-server
|
||||
```
|
||||
|
||||
_Note:_ you will need to install `ocaml-lsp-server` in every switch where you
|
||||
would like to use it.
|
||||
|
||||
#### Esy
|
||||
|
||||
To add the language server to an esy project, run in terminal:
|
||||
|
||||
```sh
|
||||
$ esy add @opam/ocaml-lsp-server
|
||||
```
|
||||
|
||||
### Installing from sources
|
||||
|
||||
This project uses submodules to handle dependencies. This is done so that users
|
||||
who install `ocaml-lsp-server` into their sandbox will not share dependency
|
||||
constraints on the same packages that `ocaml-lsp-server` is using.
|
||||
|
||||
```sh
|
||||
$ git clone --recurse-submodules http://github.com/ocaml/ocaml-lsp.git
|
||||
$ cd ocaml-lsp
|
||||
$ make install
|
||||
```
|
||||
|
||||
### Additional package installations
|
||||
|
||||
- Install [ocamlformat](https://github.com/ocaml-ppx/ocamlformat#installation)
|
||||
package if you want source file formatting support.
|
||||
|
||||
Note: To have source file formatting support in your project, there needs to
|
||||
be an `.ocamlformat` file present in your project's root directory.
|
||||
|
||||
- OCaml-LSP also uses a program called `ocamlformat-rpc` to format code that is
|
||||
either generated or displayed by OCaml-LSP, e.g., when you hover over a module
|
||||
identifier, you can see its typed nicely formatted. This program comes with
|
||||
`ocamlformat` (version > 0.21.0). Previously, it was a standalone package.
|
||||
|
||||
## Usage
|
||||
|
||||
Usually, your code editor, or some extension/plugin that you install on it, is
|
||||
responsible for launching `ocamllsp`.
|
||||
|
||||
Important: OCaml Language Server has its information about the files from the
|
||||
last time your built your project. We recommend using the Dune build system and
|
||||
running it in "watch" mode to always have correctly functioning OCaml-LSP, e.g.,
|
||||
`dune build --watch`.
|
||||
|
||||
### Integration with Dune RPC
|
||||
|
||||
> since OCaml-LSP 1.11.0
|
||||
|
||||
OCaml-LSP can communicate with Dune's RPC system to offer some interesting
|
||||
features. User can launch Dune's RPC system by running Dune in watch mode.
|
||||
OCaml-LSP will *not* launch Dune's RPC for you. But OCaml-LSP will see if there
|
||||
is an RPC running and will communicate with it automatically.
|
||||
|
||||
There are various interesting features and caveats:
|
||||
|
||||
1. Dune's RPC enables new kinds of diagnostics (i.e., warnings and errors) to be
|
||||
shown in the editor, e.g., mismatching interface and implementation files.
|
||||
You need to save the file to refresh such diagnostics because Dune doesn't
|
||||
see unsaved files; otherwise, you may see stale (no longer correct) warnings
|
||||
or errors. OCaml-LSP updates diagnostics after each build is complete in
|
||||
watch mode.
|
||||
|
||||
2. Dune file promotion support. If you, for example, use `ppx_expect` and have
|
||||
failing tests, you will get a diagnostic when Dune reports that your file can
|
||||
be promoted. You can promote your file using the code action `Promote`.
|
||||
|
||||
### Merlin configuration (advanced)
|
||||
|
||||
If you would like OCaml-LSP to respect your `.merlin` files, OCaml-LSP needs to
|
||||
be invoked with `--fallback-read-dot-merlin` argument passed to it and you must
|
||||
have the `dot-merlin-reader` package installed.
|
||||
|
||||
## Features
|
||||
|
||||
<!-- TODO:
|
||||
this is quite a large list (which becomes even larger since it's missing some requests), which is not necessarily of big interest to users.
|
||||
|
||||
We should consider:
|
||||
1. Moving it to the bottom
|
||||
2. Converting it into a table
|
||||
|
||||
| Description | Method | OCaml | Reason | Dune | Menhir | .ocamlformat | ...
|
||||
| Auto-completion | textDocument/completion | x | x | o | o | o | ...
|
||||
|
||||
3. (not sure how) Generate the table automatically because, otherwise, it's outdated frequently.
|
||||
-->
|
||||
|
||||
The server supports the following LSP requests (inexhaustive list):
|
||||
|
||||
- [x] `textDocument/completion`
|
||||
- [x] `completionItem/resolve`
|
||||
- [x] `textdocument/hover`
|
||||
- [ ] `textDocument/signatureHelp`
|
||||
- [x] `textDocument/declaration`
|
||||
- [x] `textDocument/definition`
|
||||
- [x] `textDocument/typeDefinition`
|
||||
- [ ] `textDocument/implementation`
|
||||
- [x] `textDocument/codeLens`
|
||||
- [x] `textDocument/documentHighlight`
|
||||
- [x] `textDocument/documentSymbol`
|
||||
- [x] `textDocument/references`
|
||||
- [ ] `textDocument/documentColor`
|
||||
- [ ] `textDocument/colorPresentation`
|
||||
- [x] `textDocument/formatting`
|
||||
- [ ] `textDocument/rangeFormatting`
|
||||
- [ ] `textDocument/onTypeFormatting`
|
||||
- [x] `textDocument/prepareRename`
|
||||
- [x] `textDocument/foldingRange`
|
||||
- [x] `textDocument/selectionRange`
|
||||
- [x] `workspace/didChangeConfiguration`
|
||||
- [x] `workspace/symbol`
|
||||
|
||||
Note that degrees of support for each LSP request are varying.
|
||||
|
||||
## Configuration
|
||||
|
||||
[Read more about configurations supported by ocamllsp](./ocaml-lsp-server/docs/ocamllsp/config.md)
|
||||
|
||||
### Semantic highlighting
|
||||
|
||||
> since OCaml-LSP 1.15.0 (since version `1.15.0-4.14` for OCaml 4, `1.15.0-5.0` for OCaml 5)
|
||||
|
||||
Semantic highlighting support is enabled by default.
|
||||
|
||||
> since OCaml-LSP 1.14.0
|
||||
|
||||
OCaml-LSP implements experimental semantic highlighting support (also known as
|
||||
semantic tokens support). The support can be activated by passing an environment
|
||||
variable to OCaml-LSP:
|
||||
|
||||
- To enable non-incremental (expectedly slower but more stable) version, pass
|
||||
`OCAMLLSP_SEMANTIC_HIGHLIGHTING=full` environment variable to OCaml-LSP.
|
||||
|
||||
- To enable incremental (potentially faster but more error-prone, at least on VS
|
||||
Code) version, pass `OCAMLLSP_SEMANTIC_HIGHLIGHTING=full/delta` to OCaml-LSP.
|
||||
|
||||
Tip (for VS Code OCaml Platform users): You can use `ocaml.server.extraEnv`
|
||||
setting in VS Code to pass various environment variables to OCaml-LSP.
|
||||
|
||||
```json
|
||||
{
|
||||
"ocaml.server.extraEnv": {
|
||||
"OCAMLLSP_SEMANTIC_HIGHLIGHTING": "full"
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### LSP Extensions
|
||||
|
||||
The server also supports a number of OCaml specific extensions to the protocol:
|
||||
- [Switch to implementation/interface](ocaml-lsp-server/docs/ocamllsp/switchImplIntf-spec.md)
|
||||
- [Infer interface](ocaml-lsp-server/docs/ocamllsp/inferIntf-spec.md)
|
||||
- [Locate typed holes](ocaml-lsp-server/docs/ocamllsp/typedHoles-spec.md)
|
||||
- [Find wrapping AST node](ocaml-lsp-server/docs/ocamllsp/wrappingAstNode-spec.md)
|
||||
|
||||
Note that editor support for these extensions varies. In general, the OCaml Platform extension for Visual Studio Code will have the best support.
|
||||
|
||||
### Unusual features
|
||||
|
||||
#### Destructing a value <!-- omit in toc -->
|
||||
|
||||
> since OCaml-LSP 1.0.0
|
||||
|
||||
OCaml-LSP has a code action that allows to generate an exhaustive pattern
|
||||
matching for values. For example, placing a cursor near a value `(Some 10)|`
|
||||
where `|` is your cursor, OCaml-LSP will offer a code action "Destruct", which
|
||||
replaces `(Some 10)` with `(match Some with | None -> _ | Some _ -> _)`.
|
||||
Importantly, one can only destruct a value if OCaml-LSP can infer the value's
|
||||
precise type. The value can be type-annotated, e.g., if it's a function argument
|
||||
with polymorphic (or yet unknown) type in this context. In the code snippet
|
||||
below, we type-annotate the function parameter `v` because when we type `let f v
|
||||
= v|`, the type of `v` is polymorphic, so we can't destruct it.
|
||||
|
||||
You can also usually destruct the value by placing the cursor on the wildcard
|
||||
(`_`) pattern in a pattern-match. For example,
|
||||
|
||||
```ocaml
|
||||
type t = A | B of string option
|
||||
|
||||
let f (v : t) = match v with | A -> _ | B _| -> _
|
||||
```
|
||||
|
||||
invoking destruct near the cursor (`|`) in the snippet above, you get
|
||||
|
||||
```ocaml
|
||||
type t = A | B of string option
|
||||
|
||||
let f (v : t) = match v with | A -> _ | B (None) | B (Some _) -> _
|
||||
```
|
||||
|
||||
Importantly, note the underscores in place of expressions in each branch of the
|
||||
pattern match above. The underscores that occur in place of expressions are
|
||||
called "typed holes" - a concept explained below.
|
||||
|
||||
Tip (formatting): generated code may not be greatly formatted. If your project
|
||||
uses a formatter such as OCamlFormat, you can run formatting and get a
|
||||
well-formatted document (OCamlFormat supports typed holes formatting).
|
||||
|
||||
Tip (for VS Code OCaml Platform users): You can destruct a value using a keybinding
|
||||
<kbd>Alt</kbd>+<kbd>D</kbd> or on MacOS <kbd>Option</kbd>+<kbd>D</kbd>
|
||||
|
||||
#### Typed holes <!-- omit in toc -->
|
||||
|
||||
> since OCaml-LSP 1.8.0
|
||||
|
||||
OCaml-LSP has a concept of a "typed hole" syntactically represented as `_`
|
||||
(underscore). A typed hole represents a well-typed "substitute" for an
|
||||
expression. OCaml-LSP considers these underscores that occur in place of
|
||||
expressions as a valid well-typed OCaml program: `let foo : int = _` (the typed
|
||||
hole has type `int` here) or `let bar = _ 10` (the hole has type `int -> 'a`).
|
||||
One can use such holes during development as temporary substitutes for
|
||||
expressions and "plug" the holes later with appropriate expressions.
|
||||
|
||||
Note, files that incorporate typed holes are *not* considered valid OCaml by the
|
||||
OCaml compiler and, hence, cannot be compiled.
|
||||
|
||||
Also, an underscore occurring in place of a pattern (for example `let _ = 10`)
|
||||
should not be confused with a typed hole that occurs in place of an expression,
|
||||
e.g., `let a = _`.
|
||||
|
||||
#### Constructing values by type (experimental) <!-- omit in toc -->
|
||||
|
||||
> since OCaml-LSP 1.8.0
|
||||
|
||||
OCaml-LSP can "construct" expressions based on the type required and offer them
|
||||
during auto-completion. For example, typing `_` (typed hole) in the snippet
|
||||
below will trigger auto-completion (`|` is your cursor):
|
||||
|
||||
```ocaml
|
||||
(* file foo.ml *)
|
||||
type t = A | B of string option
|
||||
|
||||
(* file bar.ml *)
|
||||
let v : Foo.t = _|
|
||||
```
|
||||
|
||||
The auto-completion offers completions `Foo.A` and `Foo.B _`. You can further
|
||||
construct values by placing the cursor as such: `Foo.B _|` and triggering code
|
||||
action "Construct an expression" which offers completions `None` and `Some _`.
|
||||
Trigger the same code action in `Some _|` will offer `""` - one of the possible
|
||||
expressions to replace the typed hole with.
|
||||
|
||||
Constructing a value is thus triggered either by typing `_` in place of an
|
||||
expression or trigger the code action "Construct an Expression". Also, the type
|
||||
of the value needs to be non-polymorphic to construct a meaningful value.
|
||||
|
||||
Tip (for VS Code OCaml Platform users): You can construct a value using a keybinding
|
||||
<kbd>Alt</kbd>+<kbd>C</kbd> or on MacOS <kbd>Option</kbd>+<kbd>C</kbd>
|
||||
|
||||
#### Syntax Documentation
|
||||
|
||||
> since OCaml-LSP 1.18.0
|
||||
|
||||
OCaml-LSP can display documentation about the node under the cursor when
|
||||
the user hovers over some OCaml code. For example, hovering over the code
|
||||
snippet below will display some information about what the syntax
|
||||
is:
|
||||
|
||||
```ocaml
|
||||
type point = {x: int; y: int}
|
||||
```
|
||||
Hovering over the above will
|
||||
display:
|
||||
```
|
||||
ocaml type point = { x : int; y : int }
|
||||
syntax Record type:
|
||||
Allows you to define variants with a fixed set of fields, and all of the
|
||||
constructors for a record variant type must have the same fields. See
|
||||
Manual
|
||||
```
|
||||
The documentation is gotten from the Merlin engine which receives
|
||||
the nodes under the cursor and infers what the syntax may be about, and
|
||||
displays the required information along with links to the manual for further
|
||||
reading.
|
||||
|
||||
Syntax Documentation is an optional feature and can be activated by
|
||||
using the LSP config system with the key called `syntaxDocumentation` and can
|
||||
be enabled via setting it to `{ enable: true }`.
|
||||
|
||||
## Debugging
|
||||
|
||||
If you use Visual Studio Code, please see OCaml Platform extension
|
||||
[page](https://github.com/ocamllabs/vscode-ocaml-platform) for a detailed guide
|
||||
on how to report and debug problems.
|
||||
|
||||
If you use another code editor and use OCaml-LSP, you should be able to set the
|
||||
server trace to `verbose` using your editor's LSP client and watch the trace
|
||||
for errors such as logged exceptions.
|
||||
|
||||
## Contributing to project
|
||||
|
||||
```bash
|
||||
# clone repo with submodules
|
||||
git clone --recursive git@github.com:ocaml/ocaml-lsp.git
|
||||
|
||||
cd ocaml-lsp
|
||||
|
||||
# if you already cloned, pull submodules
|
||||
git submodule update --init --recursive
|
||||
|
||||
# create local switch (or use global one)
|
||||
opam switch --yes create .
|
||||
|
||||
# don't forget to set your environment to use the local switch
|
||||
eval $(opam env)
|
||||
|
||||
# install dependencies
|
||||
make install-test-deps
|
||||
|
||||
# build
|
||||
make all
|
||||
|
||||
# the ocamllsp executable can be found at _build/default/ocaml-lsp-server/bin/main.exe
|
||||
```
|
||||
|
||||
### Changelog
|
||||
|
||||
User-visible changes should come with an entry in the changelog under the appropriate part of
|
||||
the **unreleased** section. PR that doesn't provide an entry will fail CI check. This behavior
|
||||
can be overridden by using the "no changelog" label, which is used for changes that are not user-visible.
|
||||
|
||||
## Tests
|
||||
|
||||
To run tests execute:
|
||||
|
||||
```sh
|
||||
$ make test
|
||||
```
|
||||
|
||||
Note that tests require [Node.js](https://nodejs.org/en/) and
|
||||
[Yarn](https://yarnpkg.com/lang/en/) installed.
|
||||
|
||||
## Relationship to Other Tools
|
||||
|
||||
The lsp server uses merlin under the hood, but users are not required to have
|
||||
merlin installed. We vendor merlin because we currently heavily depend on some
|
||||
implementation details of merlin that make it infeasible to upgrade the lsp
|
||||
server and merlin independently.
|
||||
|
||||
## History
|
||||
|
||||
The implementation of the lsp protocol itself was taken from
|
||||
[facebook's hack](https://github.com/facebook/hhvm/blob/master/hphp/hack/src/utils/lsp/lsp.mli)
|
||||
|
||||
Previously, this lsp server was a part of merlin, until it was realized that
|
||||
the lsp protocol covers a wider scope than merlin.
|
||||
|
||||
## Comparison to other LSP Servers for OCaml
|
||||
|
||||
Note that the comparisons below make no claims of being objective and may be
|
||||
entirely out of
|
||||
date. Also, both servers seem deprecated.
|
||||
|
||||
- [reason-language-server](https://github.com/jaredly/reason-language-server)
|
||||
This server supports
|
||||
[bucklescript](https://github.com/BuckleScript/bucklescript) &
|
||||
[reason](https://github.com/facebook/reason). However, this project does not
|
||||
use merlin which means that it supports fewer versions of OCaml and offers less
|
||||
"smart" functionality - especially in the face of sources that do not yet
|
||||
compile.
|
||||
|
||||
- [ocaml-language-server](https://github.com/ocaml-lsp/ocaml-language-server)
|
||||
This project is extremely similar in the functionality it provides because it
|
||||
also reuses merlin on the backend. The essential difference is that this
|
||||
project is written in typescript, while our server is in OCaml. We feel that
|
||||
it's best to use OCaml to maximize the contributor pool.
|
||||
23
thirdparty/lsp/biome.json
vendored
Normal file
23
thirdparty/lsp/biome.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"$schema": "node_modules/@biomejs/biome/configuration_schema.json",
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"ignore": ["lsp/bin/metamodel/metaModel.json", "package.json"],
|
||||
"useEditorconfig": true
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"ignore": ["ocaml-lsp-server/test/e2e/**"],
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"vcs": {
|
||||
"clientKind": "git",
|
||||
"enabled": true,
|
||||
"useIgnoreFile": true
|
||||
}
|
||||
}
|
||||
13
thirdparty/lsp/dune
vendored
Normal file
13
thirdparty/lsp/dune
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
(vendored_dirs vendor)
|
||||
|
||||
(data_only_dirs submodules)
|
||||
|
||||
(rule
|
||||
(copy lsp.opam.template jsonrpc.opam.template))
|
||||
|
||||
(rule
|
||||
(copy lsp.opam.template ocaml-lsp-server.opam.template))
|
||||
|
||||
(env
|
||||
(_
|
||||
(flags :standard -alert -unstable -w -58)))
|
||||
82
thirdparty/lsp/dune-project
vendored
Normal file
82
thirdparty/lsp/dune-project
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
(lang dune 3.0)
|
||||
(using cinaps 1.0)
|
||||
(name lsp)
|
||||
|
||||
(implicit_transitive_deps false)
|
||||
|
||||
(license ISC)
|
||||
(maintainers "Rudi Grinberg <me@rgrinberg.com>")
|
||||
(authors
|
||||
"Andrey Popp <8mayday@gmail.com>"
|
||||
"Rusty Key <iam@stfoo.ru>"
|
||||
"Louis Roché <louis@louisroche.net>"
|
||||
"Oleksiy Golovko <alexei.golovko@gmail.com>"
|
||||
"Rudi Grinberg <me@rgrinberg.com>"
|
||||
"Sacha Ayoun <sachaayoun@gmail.com>"
|
||||
"cannorin <cannorin@gmail.com>"
|
||||
"Ulugbek Abdullaev <ulugbekna@gmail.com>"
|
||||
"Thibaut Mattio <thibaut.mattio@gmail.com>"
|
||||
"Max Lantas <mnxndev@outlook.com>")
|
||||
|
||||
(source (github ocaml/ocaml-lsp))
|
||||
|
||||
(generate_opam_files true)
|
||||
|
||||
(package
|
||||
(name lsp)
|
||||
(synopsis "LSP protocol implementation in OCaml")
|
||||
(description "
|
||||
Implementation of the LSP protocol in OCaml. It is designed to be as portable as
|
||||
possible and does not make any assumptions about IO.
|
||||
")
|
||||
(depends
|
||||
(jsonrpc (= :version))
|
||||
yojson
|
||||
(ppx_yojson_conv_lib (>= "v0.14"))
|
||||
(cinaps :with-test)
|
||||
(ppx_expect (and (>= v0.17.0) :with-test))
|
||||
(uutf (>= 1.0.2))
|
||||
(odoc :with-doc)
|
||||
(ocaml (>= 4.14))
|
||||
(ppx_yojson_conv :with-dev-setup)))
|
||||
|
||||
(package
|
||||
(name ocaml-lsp-server)
|
||||
(synopsis "LSP Server for OCaml")
|
||||
(description "An LSP server for OCaml.")
|
||||
(depends
|
||||
yojson
|
||||
(base (>= v0.16.0))
|
||||
(lsp (= :version))
|
||||
(jsonrpc (= :version))
|
||||
(re (>= 1.5.0))
|
||||
(ppx_yojson_conv_lib (>= "v0.14"))
|
||||
(dune-rpc (>= 3.4.0))
|
||||
(chrome-trace (>= 3.3.0))
|
||||
dyn
|
||||
stdune
|
||||
(fiber (and (>= 3.1.1) (< 4.0.0)))
|
||||
(ocaml (and (>= 5.3) (< 5.4)))
|
||||
xdg
|
||||
ordering
|
||||
dune-build-info
|
||||
spawn
|
||||
astring
|
||||
camlp-streams
|
||||
(ppx_expect (and (>= v0.17.0) :with-test))
|
||||
(ocamlformat (and :with-test (= 0.27.0)))
|
||||
(ocamlc-loc (>= 3.7.0))
|
||||
(pp (>= 1.1.2))
|
||||
(csexp (>= 1.5))
|
||||
(ocamlformat-rpc-lib (>= 0.21.0))
|
||||
(odoc :with-doc)
|
||||
(merlin-lib (and (>= 5.4) (< 6.0)))
|
||||
(ppx_yojson_conv :with-dev-setup)))
|
||||
|
||||
(package
|
||||
(name jsonrpc)
|
||||
(synopsis "Jsonrpc protocol implemenation")
|
||||
(description "See https://www.jsonrpc.org/specification")
|
||||
(depends
|
||||
(ocaml (>= 4.08))
|
||||
(odoc :with-doc)))
|
||||
3
thirdparty/lsp/fiber-test/dune
vendored
Normal file
3
thirdparty/lsp/fiber-test/dune
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
(library
|
||||
(name fiber_test)
|
||||
(libraries fiber stdune dyn pp))
|
||||
52
thirdparty/lsp/fiber-test/fiber_test.ml
vendored
Normal file
52
thirdparty/lsp/fiber-test/fiber_test.ml
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
open Stdune
|
||||
|
||||
let print pp = Format.printf "%a@." Pp.to_fmt pp
|
||||
let print_dyn dyn = print (Dyn.pp dyn)
|
||||
|
||||
module Scheduler : sig
|
||||
type t
|
||||
|
||||
exception Never
|
||||
|
||||
val create : unit -> t
|
||||
val run : t -> 'a Fiber.t -> 'a
|
||||
end = struct
|
||||
type t = unit Fiber.Ivar.t Queue.t
|
||||
|
||||
let t_var = Fiber.Var.create ()
|
||||
let create () = Queue.create ()
|
||||
|
||||
exception Never
|
||||
|
||||
let run t fiber =
|
||||
let fiber = Fiber.Var.set t_var t (fun () -> fiber) in
|
||||
Fiber.run fiber ~iter:(fun () ->
|
||||
let next =
|
||||
match Queue.pop t with
|
||||
| None -> raise Never
|
||||
| Some e -> Fiber.Fill (e, ())
|
||||
in
|
||||
Nonempty_list.[ next ])
|
||||
;;
|
||||
end
|
||||
|
||||
let test ?(expect_never = false) to_dyn f =
|
||||
let never_raised = ref false in
|
||||
let f =
|
||||
let on_error exn =
|
||||
Format.eprintf "%a@." Exn_with_backtrace.pp_uncaught exn;
|
||||
Exn_with_backtrace.reraise exn
|
||||
in
|
||||
Fiber.with_error_handler f ~on_error
|
||||
in
|
||||
(try Scheduler.run (Scheduler.create ()) f |> to_dyn |> print_dyn with
|
||||
| Scheduler.Never -> never_raised := true);
|
||||
match !never_raised, expect_never with
|
||||
| false, false ->
|
||||
(* We don't raise in this case b/c we assume something else is being
|
||||
tested *)
|
||||
()
|
||||
| true, true -> print_endline "[PASS] Never raised as expected"
|
||||
| false, true -> print_endline "[FAIL] expected Never to be raised but it wasn't"
|
||||
| true, false -> print_endline "[FAIL] unexpected Never raised"
|
||||
;;
|
||||
1
thirdparty/lsp/fiber-test/fiber_test.mli
vendored
Normal file
1
thirdparty/lsp/fiber-test/fiber_test.mli
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
val test : ?expect_never:bool -> ('a -> Dyn.t) -> (unit -> 'a Fiber.t) -> unit
|
||||
115
thirdparty/lsp/flake.lock
generated
vendored
Normal file
115
thirdparty/lsp/flake.lock
generated
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"merlin5_1": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1727427299,
|
||||
"narHash": "sha256-P9+3BPBWrulS/1r03CqMdicFcgEcLK1Gy7pCAcYt3n4=",
|
||||
"owner": "ocaml",
|
||||
"repo": "merlin",
|
||||
"rev": "650a7865bc37a646250f7c52fa6644d9d4a5218b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ocaml",
|
||||
"ref": "501",
|
||||
"repo": "merlin",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"merlin5_2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1736508467,
|
||||
"narHash": "sha256-ZJFtPreWenLlXDokh3dOR+b3LRuZJgs9+6r+tEx9/Vo=",
|
||||
"owner": "ocaml",
|
||||
"repo": "merlin",
|
||||
"rev": "9dcffb9e998703f5f5d6e7c575c30cd822cea210",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ocaml",
|
||||
"ref": "main",
|
||||
"repo": "merlin",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1736449896,
|
||||
"narHash": "sha256-Ct6RqUtqIfazkg1X4o2FXWuYpw0A+OJsd3cFGtmXaqk=",
|
||||
"owner": "nix-ocaml",
|
||||
"repo": "nix-overlays",
|
||||
"rev": "be7cfa6043ed31b17e4b86769c05825c62e55829",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-ocaml",
|
||||
"repo": "nix-overlays",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1736384491,
|
||||
"narHash": "sha256-h0hPzFp7iVhCqzBx+kJGdO/KmG8AkYRJ0jKxQ6+diug=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8e97141d59b87e2bf254cd0920be29955d45a698",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8e97141d59b87e2bf254cd0920be29955d45a698",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"merlin5_1": "merlin5_1",
|
||||
"merlin5_2": "merlin5_2",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
184
thirdparty/lsp/flake.nix
vendored
Normal file
184
thirdparty/lsp/flake.nix
vendored
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
{
|
||||
inputs = {
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
nixpkgs.url = "github:nix-ocaml/nix-overlays";
|
||||
merlin5_2 = {
|
||||
url = "github:ocaml/merlin/main";
|
||||
flake = false;
|
||||
};
|
||||
merlin5_1 = {
|
||||
url = "github:ocaml/merlin/501";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, flake-utils, nixpkgs, ... }@inputs:
|
||||
let
|
||||
package = "ocaml-lsp-server";
|
||||
ocamlformat = pkgs: pkgs.ocamlformat_0_26_2;
|
||||
basePackage = {
|
||||
duneVersion = "3";
|
||||
version = "n/a";
|
||||
src = ./.;
|
||||
doCheck = true;
|
||||
};
|
||||
overlay = merlin: final: prev: {
|
||||
ocaml-lsp = prev.ocaml-lsp.overrideAttrs (_: {
|
||||
# Do not add share/nix-support, so that dependencies from
|
||||
# the scope don't leak into dependent derivations
|
||||
doNixSupport = false;
|
||||
});
|
||||
dune-release =
|
||||
prev.dune-release.overrideAttrs (_: { doCheck = false; });
|
||||
ocamlPackages = prev.ocamlPackages.overrideScope' (oself: osuper:
|
||||
let
|
||||
fixPreBuild = o: {
|
||||
propagatedBuildInputs = o.propagatedBuildInputs ++ [ oself.pp ];
|
||||
preBuild = ''
|
||||
rm -r vendor/csexp vendor/pp
|
||||
'';
|
||||
};
|
||||
in {
|
||||
# TODO remove these hacks eventually
|
||||
dyn = osuper.dyn.overrideAttrs fixPreBuild;
|
||||
dune-private-libs =
|
||||
osuper.dune-private-libs.overrideAttrs fixPreBuild;
|
||||
dune-glob = osuper.dune-glob.overrideAttrs fixPreBuild;
|
||||
dune-action-plugin =
|
||||
osuper.dune-action-plugin.overrideAttrs fixPreBuild;
|
||||
dune-rpc = osuper.dune-rpc.overrideAttrs fixPreBuild;
|
||||
stdune = osuper.stdune.overrideAttrs fixPreBuild;
|
||||
merlin-lib = osuper.merlin-lib.overrideAttrs (o: { src = merlin; });
|
||||
});
|
||||
};
|
||||
ocamlVersionOverlay =
|
||||
(ocaml: self: super: { ocamlPackages = ocaml super.ocaml-ng; });
|
||||
makeLocalPackages = pkgs:
|
||||
let buildDunePackage = pkgs.ocamlPackages.buildDunePackage;
|
||||
in rec {
|
||||
jsonrpc = buildDunePackage (basePackage // {
|
||||
pname = "jsonrpc";
|
||||
doCheck = false;
|
||||
propagatedBuildInputs = with pkgs.ocamlPackages; [ ];
|
||||
});
|
||||
|
||||
lsp = buildDunePackage (basePackage // {
|
||||
pname = "lsp";
|
||||
doCheck = false;
|
||||
propagatedBuildInputs = with pkgs.ocamlPackages; [
|
||||
jsonrpc
|
||||
yojson
|
||||
ppx_yojson_conv_lib
|
||||
uutf
|
||||
];
|
||||
checkInputs = let p = pkgs.ocamlPackages;
|
||||
in [
|
||||
p.stdune
|
||||
p.cinaps
|
||||
p.ppx_expect
|
||||
p.ppx_yojson_conv
|
||||
(ocamlformat pkgs)
|
||||
];
|
||||
});
|
||||
|
||||
ocaml-lsp = with pkgs.ocamlPackages;
|
||||
buildDunePackage (basePackage // {
|
||||
pname = package;
|
||||
doCheck = false;
|
||||
checkInputs = let p = pkgs.ocamlPackages;
|
||||
in [
|
||||
p.ppx_expect
|
||||
p.ppx_yojson_conv
|
||||
(ocamlformat pkgs)
|
||||
pkgs.yarn
|
||||
];
|
||||
buildInputs = [
|
||||
jsonrpc
|
||||
lsp
|
||||
ocamlc-loc
|
||||
astring
|
||||
camlp-streams
|
||||
dune-build-info
|
||||
re
|
||||
dune-rpc
|
||||
chrome-trace
|
||||
dyn
|
||||
fiber
|
||||
xdg
|
||||
ordering
|
||||
spawn
|
||||
csexp
|
||||
ocamlformat-rpc-lib
|
||||
stdune
|
||||
yojson
|
||||
ppx_yojson_conv_lib
|
||||
merlin-lib
|
||||
base
|
||||
];
|
||||
propagatedBuildInputs = [ ];
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
dune build ${package}.install --release ''${enableParallelBuilding:+-j $NIX_BUILD_CORES}
|
||||
runHook postBuild
|
||||
'';
|
||||
meta = { mainProgram = "ocamllsp"; };
|
||||
});
|
||||
};
|
||||
in {
|
||||
overlays.default = (final: prev: {
|
||||
ocamlPackages = prev.ocamlPackages.overrideScope
|
||||
(oself: osuper: with oself; makeLocalPackages final);
|
||||
});
|
||||
} // (flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgsWithoutOverlays = (import nixpkgs { inherit system; });
|
||||
makeNixpkgs = ocaml: merlin:
|
||||
import nixpkgs {
|
||||
overlays = [ (ocamlVersionOverlay ocaml) (overlay merlin) ];
|
||||
inherit system;
|
||||
};
|
||||
pkgs_5_1 =
|
||||
makeNixpkgs (ocaml: ocaml.ocamlPackages_5_1) inputs.merlin5_1;
|
||||
pkgs_5_2 =
|
||||
makeNixpkgs (ocaml: ocaml.ocamlPackages_5_2) inputs.merlin5_2;
|
||||
localPackages_5_1 = makeLocalPackages pkgs_5_1;
|
||||
localPackages_5_2 = makeLocalPackages pkgs_5_2;
|
||||
devShell = localPackages: nixpkgs:
|
||||
nixpkgs.mkShell {
|
||||
buildInputs = [ nixpkgs.ocamlPackages.utop ];
|
||||
inputsFrom =
|
||||
builtins.map (x: x.overrideAttrs (p: n: { doCheck = true; }))
|
||||
(builtins.attrValues localPackages);
|
||||
};
|
||||
in {
|
||||
packages = (localPackages_5_2 // {
|
||||
default = localPackages_5_2.ocaml-lsp;
|
||||
ocaml_5_1 = localPackages_5_1;
|
||||
});
|
||||
|
||||
devShells = {
|
||||
default = devShell localPackages_5_2 pkgs_5_2;
|
||||
|
||||
ocaml5_1 = devShell localPackages_5_1 pkgs_5_1;
|
||||
|
||||
release = pkgsWithoutOverlays.mkShell {
|
||||
buildInputs = [ pkgsWithoutOverlays.dune-release ];
|
||||
};
|
||||
|
||||
fmt = pkgsWithoutOverlays.mkShell {
|
||||
buildInputs = [
|
||||
# TODO: get rid of ocaml once dune get format without ocaml being
|
||||
# present
|
||||
pkgsWithoutOverlays.ocaml
|
||||
(ocamlformat pkgsWithoutOverlays)
|
||||
pkgsWithoutOverlays.yarn
|
||||
pkgsWithoutOverlays.dune_3
|
||||
];
|
||||
};
|
||||
|
||||
check = pkgs_5_2.mkShell {
|
||||
inputsFrom = builtins.attrValues localPackages_5_2;
|
||||
};
|
||||
};
|
||||
}));
|
||||
}
|
||||
5
thirdparty/lsp/jsonrpc-fiber/src/dune
vendored
Normal file
5
thirdparty/lsp/jsonrpc-fiber/src/dune
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
(library
|
||||
(name jsonrpc_fiber)
|
||||
(libraries fiber dyn jsonrpc ppx_yojson_conv_lib stdune yojson)
|
||||
(instrumentation
|
||||
(backend bisect_ppx)))
|
||||
85
thirdparty/lsp/jsonrpc-fiber/src/import.ml
vendored
Normal file
85
thirdparty/lsp/jsonrpc-fiber/src/import.ml
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
module List = ListLabels
|
||||
|
||||
include struct
|
||||
(* TODO remove stdune dependence *)
|
||||
|
||||
open Stdune
|
||||
module Code_error = Code_error
|
||||
module Exn_with_backtrace = Exn_with_backtrace
|
||||
end
|
||||
|
||||
include struct
|
||||
open Jsonrpc
|
||||
module Id = Id
|
||||
module Response = Response
|
||||
module Request = Request
|
||||
module Notification = Notification
|
||||
module Packet = Packet
|
||||
end
|
||||
|
||||
module Json = struct
|
||||
type t = Ppx_yojson_conv_lib.Yojson.Safe.t
|
||||
|
||||
let to_pretty_string (t : t) = Yojson.Safe.pretty_to_string ~std:false t
|
||||
let error = Ppx_yojson_conv_lib.Yojson_conv.of_yojson_error
|
||||
let pp ppf (t : t) = Yojson.Safe.pretty_print ppf t
|
||||
|
||||
let rec of_dyn (t : Dyn.t) : t =
|
||||
match t with
|
||||
| Opaque -> `String "<opaque>"
|
||||
| Unit -> `String "()"
|
||||
| Int i -> `Int i
|
||||
| Int32 i -> `Int (Int32.to_int i)
|
||||
| Nativeint i -> `Int (Nativeint.to_int i)
|
||||
| Int64 i -> `Int (Int64.to_int i)
|
||||
| Bool b -> `Bool b
|
||||
| String s -> `String s
|
||||
| Bytes s -> `String (Bytes.to_string s)
|
||||
| Char c -> `String (String.make 1 c)
|
||||
| Float f -> `Float f
|
||||
| Option None -> `String "<none>"
|
||||
| Option (Some s) -> of_dyn s
|
||||
| List xs -> `List (List.map ~f:of_dyn xs)
|
||||
| Array xs -> `List (List.map ~f:of_dyn (Array.to_list xs))
|
||||
| Tuple xs -> `List (List.map ~f:of_dyn xs)
|
||||
| Record r -> `Assoc (List.map r ~f:(fun (k, v) -> k, of_dyn v))
|
||||
| Variant (name, args) -> `Assoc [ name, of_dyn (List args) ]
|
||||
| Set xs -> `List (List.map ~f:of_dyn xs)
|
||||
| Map map -> `List (List.map map ~f:(fun (k, v) -> `List [ of_dyn k; of_dyn v ]))
|
||||
;;
|
||||
end
|
||||
|
||||
module Log = struct
|
||||
let level : (string option -> bool) ref = ref (fun _ -> false)
|
||||
let out = ref Format.err_formatter
|
||||
|
||||
type message =
|
||||
{ message : string
|
||||
; payload : (string * Json.t) list
|
||||
}
|
||||
|
||||
let msg message payload = { message; payload }
|
||||
|
||||
let log ?section k =
|
||||
if !level section
|
||||
then (
|
||||
let message = k () in
|
||||
(match section with
|
||||
| None -> Format.fprintf !out "%s@." message.message
|
||||
| Some section -> Format.fprintf !out "[%s] %s@." section message.message);
|
||||
(match message.payload with
|
||||
| [] -> ()
|
||||
| fields -> Format.fprintf !out "%a@." Json.pp (`Assoc fields));
|
||||
Format.pp_print_flush !out ())
|
||||
;;
|
||||
end
|
||||
|
||||
let sprintf = Printf.sprintf
|
||||
|
||||
let () =
|
||||
Printexc.register_printer (function
|
||||
| Jsonrpc.Response.Error.E t ->
|
||||
let json = Jsonrpc.Response.Error.yojson_of_t t in
|
||||
Some ("jsonrpc response error " ^ Json.to_pretty_string (json :> Json.t))
|
||||
| _ -> None)
|
||||
;;
|
||||
369
thirdparty/lsp/jsonrpc-fiber/src/jsonrpc_fiber.ml
vendored
Normal file
369
thirdparty/lsp/jsonrpc-fiber/src/jsonrpc_fiber.ml
vendored
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
open Import
|
||||
open Fiber.O
|
||||
|
||||
module Id = struct
|
||||
include Id
|
||||
module Table = Stdlib.MoreLabels.Hashtbl.Make (Id)
|
||||
end
|
||||
|
||||
module Notify = struct
|
||||
type t =
|
||||
| Stop
|
||||
| Continue
|
||||
end
|
||||
|
||||
module Sender = struct
|
||||
type t =
|
||||
{ mutable called : bool
|
||||
; for_ : Id.t
|
||||
; send : Response.t -> unit Fiber.t
|
||||
}
|
||||
|
||||
let make id send = { for_ = id; called = false; send }
|
||||
|
||||
let send t (r : Response.t) : unit Fiber.t =
|
||||
Fiber.of_thunk (fun () ->
|
||||
if t.called
|
||||
then Code_error.raise "cannot send response twice" []
|
||||
else if not (Id.equal t.for_ r.id)
|
||||
then Code_error.raise "invalid id" []
|
||||
else t.called <- true;
|
||||
t.send r)
|
||||
;;
|
||||
end
|
||||
|
||||
exception Stopped of Request.t
|
||||
|
||||
let () =
|
||||
Printexc.register_printer (function
|
||||
| Stopped req ->
|
||||
let json = Request.yojson_of_t req in
|
||||
Some ("Session closed. Request will not be answered. " ^ Json.to_pretty_string json)
|
||||
| _ -> None)
|
||||
;;
|
||||
|
||||
module Reply = struct
|
||||
type t =
|
||||
| Now of Response.t
|
||||
| Later of ((Response.t -> unit Fiber.t) -> unit Fiber.t)
|
||||
|
||||
let now (r : Response.t) = Now r
|
||||
let later f = Later f
|
||||
|
||||
let send (t : t) sender =
|
||||
match t with
|
||||
| Now r -> Sender.send sender r
|
||||
| Later f -> f (fun (r : Response.t) -> Sender.send sender r)
|
||||
;;
|
||||
end
|
||||
|
||||
module Make (Chan : sig
|
||||
type t
|
||||
|
||||
val send : t -> Packet.t list -> unit Fiber.t
|
||||
val recv : t -> Packet.t option Fiber.t
|
||||
val close : t -> [ `Read | `Write ] -> unit Fiber.t
|
||||
end) =
|
||||
struct
|
||||
type 'state t =
|
||||
{ chan : Chan.t
|
||||
; on_request : ('state, Request.t) context -> (Reply.t * 'state) Fiber.t
|
||||
; on_notification : ('state, Notification.t) context -> (Notify.t * 'state) Fiber.t
|
||||
; pending : (Response.t, [ `Stopped | `Cancelled ]) result Fiber.Ivar.t Id.Table.t
|
||||
; stopped : unit Fiber.Ivar.t
|
||||
; name : string
|
||||
; mutable running : bool
|
||||
; mutable tick : int
|
||||
; mutable state : 'state
|
||||
; mutable pending_requests_stopped : bool
|
||||
}
|
||||
|
||||
and ('a, 'message) context = 'a t * 'message
|
||||
|
||||
type cancel = unit Fiber.t
|
||||
|
||||
let fire cancel = cancel
|
||||
|
||||
module Context = struct
|
||||
type nonrec ('a, 'id) t = ('a, 'id) context
|
||||
|
||||
let message = snd
|
||||
let session = fst
|
||||
let state t = (session t).state
|
||||
end
|
||||
|
||||
let log t = Log.log ~section:t.name
|
||||
|
||||
let response_of_exn id (exn : Exn_with_backtrace.t) =
|
||||
let error =
|
||||
match exn.exn with
|
||||
| Jsonrpc.Response.Error.E resp -> resp
|
||||
| _ ->
|
||||
let data = exn |> Exn_with_backtrace.to_dyn |> Json.of_dyn in
|
||||
Response.Error.make ~code:InternalError ~data ~message:"uncaught exception" ()
|
||||
in
|
||||
Response.error id error
|
||||
;;
|
||||
|
||||
let on_request_fail ctx : (Reply.t * _) Fiber.t =
|
||||
let req : Request.t = Context.message ctx in
|
||||
let state = Context.state ctx in
|
||||
let error = Response.Error.make ~code:InternalError ~message:"not implemented" () in
|
||||
Fiber.return (Reply.now (Response.error req.id error), state)
|
||||
;;
|
||||
|
||||
let state t = t.state
|
||||
|
||||
let on_notification_fail ctx =
|
||||
let state = Context.state ctx in
|
||||
Fiber.return (Notify.Continue, state)
|
||||
;;
|
||||
|
||||
let stop_pending_requests t =
|
||||
Fiber.of_thunk (fun () ->
|
||||
if t.pending_requests_stopped
|
||||
then Fiber.return ()
|
||||
else (
|
||||
t.pending_requests_stopped <- true;
|
||||
let to_cancel =
|
||||
Id.Table.fold t.pending ~init:[] ~f:(fun ~key:_ ~data:x acc -> x :: acc)
|
||||
in
|
||||
Id.Table.clear t.pending;
|
||||
Fiber.parallel_iter to_cancel ~f:(fun ivar ->
|
||||
let* res = Fiber.Ivar.peek ivar in
|
||||
match res with
|
||||
| Some _ -> Fiber.return ()
|
||||
| None -> Fiber.Ivar.fill ivar (Error `Stopped))))
|
||||
;;
|
||||
|
||||
let create
|
||||
?(on_request = on_request_fail)
|
||||
?(on_notification = on_notification_fail)
|
||||
~name
|
||||
chan
|
||||
state
|
||||
=
|
||||
let pending = Id.Table.create 10 in
|
||||
{ chan
|
||||
; on_request
|
||||
; on_notification
|
||||
; pending
|
||||
; stopped = Fiber.Ivar.create ()
|
||||
; name
|
||||
; running = false
|
||||
; tick = 0
|
||||
; state
|
||||
; pending_requests_stopped = false
|
||||
}
|
||||
;;
|
||||
|
||||
let stopped t = Fiber.Ivar.read t.stopped
|
||||
|
||||
let stop t =
|
||||
Fiber.fork_and_join_unit
|
||||
(fun () -> Chan.close t.chan `Read)
|
||||
(fun () -> stop_pending_requests t)
|
||||
;;
|
||||
|
||||
let close t =
|
||||
Fiber.all_concurrently_unit
|
||||
[ Chan.close t.chan `Read
|
||||
; Chan.close t.chan `Write
|
||||
; Fiber.Ivar.fill t.stopped ()
|
||||
; stop_pending_requests t
|
||||
]
|
||||
;;
|
||||
|
||||
let run t =
|
||||
let send_response resp =
|
||||
log t (fun () ->
|
||||
Log.msg "sending response" [ "response", Response.yojson_of_t resp ]);
|
||||
Chan.send t.chan [ Response resp ]
|
||||
in
|
||||
let later = Fiber.Pool.create () in
|
||||
let rec loop () =
|
||||
t.tick <- t.tick + 1;
|
||||
log t (fun () -> Log.msg "new tick" [ "tick", `Int t.tick ]);
|
||||
let* res = Chan.recv t.chan in
|
||||
log t (fun () -> Log.msg "waited for something" []);
|
||||
match res with
|
||||
| None -> Fiber.return ()
|
||||
| Some packet ->
|
||||
(match packet with
|
||||
| Notification r -> on_notification r
|
||||
| Request r -> on_request r
|
||||
| Response r ->
|
||||
let* () = Fiber.Pool.task later ~f:(fun () -> on_response r) in
|
||||
loop ()
|
||||
| Batch_call _ -> Code_error.raise "batch requests aren't supported" []
|
||||
| Batch_response _ -> assert false)
|
||||
and on_response r =
|
||||
let log (what : string) =
|
||||
log t (fun () -> Log.msg ("response " ^ what) [ "r", Response.yojson_of_t r ])
|
||||
in
|
||||
match Id.Table.find_opt t.pending r.id with
|
||||
| None ->
|
||||
log "dropped";
|
||||
Fiber.return ()
|
||||
| Some ivar ->
|
||||
log "acknowledged";
|
||||
Id.Table.remove t.pending r.id;
|
||||
let* resp = Fiber.Ivar.peek ivar in
|
||||
(match resp with
|
||||
| Some _ -> Fiber.return ()
|
||||
| None -> Fiber.Ivar.fill ivar (Ok r))
|
||||
and on_request (r : Request.t) =
|
||||
log t (fun () -> Log.msg "handling request" []);
|
||||
let* result =
|
||||
let sent = ref false in
|
||||
Fiber.map_reduce_errors
|
||||
(module Stdune.Monoid.Unit)
|
||||
~on_error:(fun exn_bt ->
|
||||
if !sent
|
||||
then (* TODO log *)
|
||||
Fiber.return ()
|
||||
else (
|
||||
let response = response_of_exn r.id exn_bt in
|
||||
sent := true;
|
||||
Fiber.Pool.task later ~f:(fun () -> send_response response)))
|
||||
(fun () -> t.on_request (t, r))
|
||||
in
|
||||
log t (fun () -> Log.msg "received result" []);
|
||||
match result with
|
||||
| Error () -> loop ()
|
||||
| Ok (reply, state) ->
|
||||
t.state <- state;
|
||||
let sender = Sender.make r.id send_response in
|
||||
let* () =
|
||||
Fiber.Pool.task later ~f:(fun () ->
|
||||
let+ res =
|
||||
Fiber.map_reduce_errors
|
||||
(module Stdune.Monoid.Unit)
|
||||
(fun () -> Reply.send reply sender)
|
||||
~on_error:(fun exn_bt ->
|
||||
if sender.called
|
||||
then (* TODO we should log *)
|
||||
Fiber.return ()
|
||||
else (
|
||||
let resp = response_of_exn r.id exn_bt in
|
||||
Sender.send sender resp))
|
||||
in
|
||||
match res with
|
||||
| Ok () -> ()
|
||||
| Error () -> ())
|
||||
in
|
||||
loop ()
|
||||
and on_notification (r : Notification.t) : unit Fiber.t =
|
||||
let* res = Fiber.collect_errors (fun () -> t.on_notification (t, r)) in
|
||||
match res with
|
||||
| Ok (next, state) ->
|
||||
t.state <- state;
|
||||
(match next with
|
||||
| Stop -> Fiber.return ()
|
||||
| Continue -> loop ())
|
||||
| Error errors ->
|
||||
Format.eprintf
|
||||
"Uncaught error when handling notification:@.%a@.Error:@.%s@."
|
||||
Json.pp
|
||||
(Notification.yojson_of_t r)
|
||||
(Dyn.to_string (Dyn.list Exn_with_backtrace.to_dyn errors));
|
||||
loop ()
|
||||
in
|
||||
Fiber.of_thunk (fun () ->
|
||||
t.running <- true;
|
||||
let* () =
|
||||
Fiber.fork_and_join_unit
|
||||
(fun () ->
|
||||
let* () = loop () in
|
||||
Fiber.Pool.stop later)
|
||||
(fun () -> Fiber.Pool.run later)
|
||||
in
|
||||
close t)
|
||||
;;
|
||||
|
||||
let check_running t =
|
||||
if not t.running then Code_error.raise "jsonrpc must be running" []
|
||||
;;
|
||||
|
||||
let notification t (n : Notification.t) =
|
||||
Fiber.of_thunk (fun () ->
|
||||
check_running t;
|
||||
Chan.send t.chan [ Notification n ])
|
||||
;;
|
||||
|
||||
let register_request_ivar t id ivar =
|
||||
match Id.Table.find_opt t.pending id with
|
||||
| Some _ -> Code_error.raise "duplicate request id" []
|
||||
| None -> Id.Table.add t.pending ~key:id ~data:ivar
|
||||
;;
|
||||
|
||||
let read_request_ivar req ivar =
|
||||
let+ res = Fiber.Ivar.read ivar in
|
||||
match res with
|
||||
| Ok s -> s
|
||||
| Error `Cancelled -> assert false
|
||||
| Error `Stopped -> raise (Stopped req)
|
||||
;;
|
||||
|
||||
let request t (req : Request.t) =
|
||||
Fiber.of_thunk (fun () ->
|
||||
check_running t;
|
||||
let* () = Chan.send t.chan [ Request req ] in
|
||||
let ivar = Fiber.Ivar.create () in
|
||||
register_request_ivar t req.id ivar;
|
||||
read_request_ivar req ivar)
|
||||
;;
|
||||
|
||||
let request_with_cancel t (req : Request.t) =
|
||||
let ivar = Fiber.Ivar.create () in
|
||||
let cancel = Fiber.Ivar.fill ivar (Error `Cancelled) in
|
||||
let resp =
|
||||
Fiber.of_thunk (fun () ->
|
||||
check_running t;
|
||||
let* () =
|
||||
let+ () = Chan.send t.chan [ Request req ] in
|
||||
register_request_ivar t req.id ivar
|
||||
in
|
||||
let+ res = Fiber.Ivar.read ivar in
|
||||
match res with
|
||||
| Ok s -> `Ok s
|
||||
| Error `Cancelled -> `Cancelled
|
||||
| Error `Stopped -> raise (Stopped req))
|
||||
in
|
||||
cancel, resp
|
||||
;;
|
||||
|
||||
module Batch = struct
|
||||
type response =
|
||||
Jsonrpc.Request.t
|
||||
* (Jsonrpc.Response.t, [ `Stopped | `Cancelled ]) result Fiber.Ivar.t
|
||||
|
||||
type t = [ `Notification of Notification.t | `Request of response ] list ref
|
||||
|
||||
let await (req, resp) = read_request_ivar req resp
|
||||
let create () = ref []
|
||||
let notification t n = t := `Notification n :: !t
|
||||
|
||||
let request (t : t) r : response =
|
||||
let ivar = Fiber.Ivar.create () in
|
||||
let resp = r, ivar in
|
||||
t := `Request resp :: !t;
|
||||
resp
|
||||
;;
|
||||
end
|
||||
|
||||
let submit (t : _ t) (batch : Batch.t) =
|
||||
Fiber.of_thunk (fun () ->
|
||||
check_running t;
|
||||
let pending = !batch in
|
||||
batch := [];
|
||||
let pending, ivars =
|
||||
List.fold_left pending ~init:([], []) ~f:(fun (pending, ivars) -> function
|
||||
| `Notification n -> Jsonrpc.Packet.Notification n :: pending, ivars
|
||||
| `Request ((r : Request.t), ivar) ->
|
||||
Jsonrpc.Packet.Request r :: pending, (r.id, ivar) :: ivars)
|
||||
in
|
||||
List.iter ivars ~f:(fun (id, ivar) -> register_request_ivar t id ivar);
|
||||
Chan.send t.chan pending)
|
||||
;;
|
||||
end
|
||||
75
thirdparty/lsp/jsonrpc-fiber/src/jsonrpc_fiber.mli
vendored
Normal file
75
thirdparty/lsp/jsonrpc-fiber/src/jsonrpc_fiber.mli
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
module Notify : sig
|
||||
type t =
|
||||
| Stop
|
||||
| Continue
|
||||
end
|
||||
|
||||
module Reply : sig
|
||||
type t
|
||||
|
||||
val now : Jsonrpc.Response.t -> t
|
||||
val later : ((Jsonrpc.Response.t -> unit Fiber.t) -> unit Fiber.t) -> t
|
||||
end
|
||||
|
||||
(** Raised when the server is shutdown and a pending request will not complete. *)
|
||||
exception Stopped of Jsonrpc.Request.t
|
||||
|
||||
(** IO free implementation of the jsonrpc protocol. We stay completely agnostic
|
||||
of transport by only dealing with abstract jsonrpc packets *)
|
||||
module Make (Chan : sig
|
||||
type t
|
||||
|
||||
val send : t -> Jsonrpc.Packet.t list -> unit Fiber.t
|
||||
val recv : t -> Jsonrpc.Packet.t option Fiber.t
|
||||
val close : t -> [ `Read | `Write ] -> unit Fiber.t
|
||||
end) : sig
|
||||
type 'state t
|
||||
|
||||
module Context : sig
|
||||
type 'a session := 'a t
|
||||
type ('state, 'message) t
|
||||
|
||||
val message : (_, 'message) t -> 'message
|
||||
val state : ('a, _) t -> 'a
|
||||
val session : ('a, _) t -> 'a session
|
||||
end
|
||||
|
||||
val create
|
||||
: ?on_request:(('state, Jsonrpc.Request.t) Context.t -> (Reply.t * 'state) Fiber.t)
|
||||
-> ?on_notification:
|
||||
(('state, Jsonrpc.Notification.t) Context.t -> (Notify.t * 'state) Fiber.t)
|
||||
-> name:string
|
||||
-> Chan.t
|
||||
-> 'state
|
||||
-> 'state t
|
||||
|
||||
val state : 'a t -> 'a
|
||||
val stop : _ t -> unit Fiber.t
|
||||
val stopped : _ t -> unit Fiber.t
|
||||
val run : _ t -> unit Fiber.t
|
||||
val notification : _ t -> Jsonrpc.Notification.t -> unit Fiber.t
|
||||
val request : _ t -> Jsonrpc.Request.t -> Jsonrpc.Response.t Fiber.t
|
||||
|
||||
type cancel
|
||||
|
||||
val fire : cancel -> unit Fiber.t
|
||||
|
||||
val request_with_cancel
|
||||
: _ t
|
||||
-> Jsonrpc.Request.t
|
||||
-> cancel * [ `Ok of Jsonrpc.Response.t | `Cancelled ] Fiber.t
|
||||
|
||||
module Batch : sig
|
||||
type t
|
||||
|
||||
val create : unit -> t
|
||||
val notification : t -> Jsonrpc.Notification.t -> unit
|
||||
|
||||
type response
|
||||
|
||||
val await : response -> Jsonrpc.Response.t Fiber.t
|
||||
val request : t -> Jsonrpc.Request.t -> response
|
||||
end
|
||||
|
||||
val submit : _ t -> Batch.t -> unit Fiber.t
|
||||
end
|
||||
22
thirdparty/lsp/jsonrpc-fiber/test/dune
vendored
Normal file
22
thirdparty/lsp/jsonrpc-fiber/test/dune
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
(library
|
||||
(name jsonrpc_fiber_tests)
|
||||
(enabled_if
|
||||
(>= %{ocaml_version} 4.08))
|
||||
(inline_tests)
|
||||
(libraries
|
||||
base
|
||||
dyn
|
||||
fiber
|
||||
fiber_test
|
||||
jsonrpc
|
||||
jsonrpc_fiber
|
||||
;; This is because of the (implicit_transitive_deps false)
|
||||
;; in dune-project
|
||||
ppx_expect
|
||||
ppx_expect.config
|
||||
ppx_expect.config_types
|
||||
ppx_inline_test.config
|
||||
stdune
|
||||
yojson)
|
||||
(preprocess
|
||||
(pps ppx_expect)))
|
||||
342
thirdparty/lsp/jsonrpc-fiber/test/jsonrpc_fiber_tests.ml
vendored
Normal file
342
thirdparty/lsp/jsonrpc-fiber/test/jsonrpc_fiber_tests.ml
vendored
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
open Stdune
|
||||
open Jsonrpc
|
||||
open Jsonrpc_fiber
|
||||
open Fiber.O
|
||||
open Fiber.Stream
|
||||
|
||||
module Stream_chan = struct
|
||||
type t = Jsonrpc.Packet.t In.t * Jsonrpc.Packet.t Out.t
|
||||
|
||||
let close (_, o) what =
|
||||
match what with
|
||||
| `Read -> Fiber.return ()
|
||||
| `Write -> Out.write o None
|
||||
;;
|
||||
|
||||
let send (_, o) p = Fiber.sequential_iter p ~f:(fun x -> Out.write o (Some x))
|
||||
let recv (i, _) = In.read i
|
||||
end
|
||||
|
||||
module Jrpc = Jsonrpc_fiber.Make (Stream_chan)
|
||||
module Context = Jrpc.Context
|
||||
|
||||
let print_json json = print_endline (Yojson.Safe.pretty_to_string ~std:false json)
|
||||
|
||||
let no_output () =
|
||||
let received_none = ref false in
|
||||
Out.create (function
|
||||
| None ->
|
||||
if !received_none
|
||||
then failwith "received None more than once"
|
||||
else received_none := true;
|
||||
Fiber.return ()
|
||||
| Some _ -> failwith "unexpected element")
|
||||
;;
|
||||
|
||||
let%expect_test "start and stop server" =
|
||||
let run () =
|
||||
let in_ = In.of_list [] in
|
||||
let jrpc = Jrpc.create ~name:"test" (in_, no_output ()) () in
|
||||
let run = Jrpc.run jrpc in
|
||||
Fiber.fork_and_join_unit (fun () -> run) (fun () -> Jrpc.stop jrpc)
|
||||
in
|
||||
let () = Fiber_test.test Dyn.opaque run in
|
||||
[%expect
|
||||
{|
|
||||
<opaque> |}]
|
||||
;;
|
||||
|
||||
let%expect_test "server accepts notifications" =
|
||||
let notif =
|
||||
{ Jsonrpc.Notification.method_ = "method"; params = Some (`List [ `String "bar" ]) }
|
||||
in
|
||||
let run () =
|
||||
let in_ = In.of_list [ Jsonrpc.Packet.Notification notif ] in
|
||||
let on_notification c =
|
||||
let n = Context.message c in
|
||||
let state = Context.state c in
|
||||
assert (notif = n);
|
||||
print_endline "received notification";
|
||||
Fiber.return (Notify.Stop, state)
|
||||
in
|
||||
let jrpc = Jrpc.create ~name:"test" ~on_notification (in_, no_output ()) () in
|
||||
Jrpc.run jrpc
|
||||
in
|
||||
Fiber_test.test Dyn.opaque run;
|
||||
[%expect
|
||||
{|
|
||||
received notification
|
||||
<opaque> |}]
|
||||
;;
|
||||
|
||||
let of_ref ref =
|
||||
Fiber.Stream.Out.create (function
|
||||
| None -> Fiber.return ()
|
||||
| Some x ->
|
||||
ref := x :: !ref;
|
||||
Fiber.return ())
|
||||
;;
|
||||
|
||||
let%expect_test "serving requests" =
|
||||
let id = `Int 1 in
|
||||
let request =
|
||||
{ Jsonrpc.Request.id; method_ = "bla"; params = Some (`List [ `Int 100 ]) }
|
||||
in
|
||||
let response_data = `String "response" in
|
||||
let run () =
|
||||
let responses = ref [] in
|
||||
let in_ = In.of_list [ Jsonrpc.Packet.Request request ] in
|
||||
let on_request c =
|
||||
let r = Context.message c in
|
||||
let state = Context.state c in
|
||||
assert (r = request);
|
||||
let response = Jsonrpc.Response.ok r.id response_data in
|
||||
Fiber.return (Reply.now response, state)
|
||||
in
|
||||
let out = of_ref responses in
|
||||
let jrpc = Jrpc.create ~name:"test" ~on_request (in_, out) () in
|
||||
let+ () = Jrpc.run jrpc in
|
||||
List.iter !responses ~f:(fun resp ->
|
||||
let json = Jsonrpc.Packet.yojson_of_t resp in
|
||||
print_endline (Yojson.Safe.pretty_to_string ~std:false json))
|
||||
in
|
||||
Fiber_test.test Dyn.opaque run;
|
||||
[%expect
|
||||
{|
|
||||
{ "id": 1, "jsonrpc": "2.0", "result": "response" }
|
||||
<opaque> |}]
|
||||
;;
|
||||
|
||||
(* The current client/server implement has no concurrent handling of requests.
|
||||
We can show this when we try to send a request when handling a response. *)
|
||||
let%expect_test "concurrent requests" =
|
||||
let print packet =
|
||||
print_endline
|
||||
(Yojson.Safe.pretty_to_string ~std:false (Jsonrpc.Packet.yojson_of_t packet))
|
||||
in
|
||||
let waiter chan =
|
||||
let on_request c =
|
||||
let self = Context.session c in
|
||||
let request = Context.message c in
|
||||
print_endline "waiter: received request";
|
||||
print (Request request);
|
||||
let response =
|
||||
Reply.later (fun send ->
|
||||
print_endline "waiter: sending response";
|
||||
let* () = send (Jsonrpc.Response.ok request.id `Null) in
|
||||
print_endline "waiter: making request";
|
||||
let* response =
|
||||
let request = Jsonrpc.Request.create ~id:(`Int 100) ~method_:"shutdown" () in
|
||||
Jrpc.request self request
|
||||
in
|
||||
print_endline "waiter: received response:";
|
||||
print (Response response);
|
||||
let* () = send (Jsonrpc.Response.ok request.id `Null) in
|
||||
print_endline "waiter: stopping";
|
||||
let+ () = Jrpc.stop self in
|
||||
print_endline "waiter: stopped")
|
||||
in
|
||||
Fiber.return (response, ())
|
||||
in
|
||||
Jrpc.create ~name:"waiter" ~on_request chan ()
|
||||
in
|
||||
let waitee chan =
|
||||
let on_request c =
|
||||
print_endline "waitee: received request";
|
||||
let request = Context.message c in
|
||||
print (Request request);
|
||||
let response =
|
||||
Reply.later (fun send ->
|
||||
let* () = send (Jsonrpc.Response.ok request.id (`Int 42)) in
|
||||
if request.method_ = "shutdown"
|
||||
then (
|
||||
let self = Context.session c in
|
||||
print_endline "waitee: stopping";
|
||||
let+ () = Jrpc.stop self in
|
||||
print_endline "waitee: stopped")
|
||||
else Fiber.return ())
|
||||
in
|
||||
let state = Context.state c in
|
||||
Fiber.return (response, state)
|
||||
in
|
||||
Jrpc.create ~on_request ~name:"waitee" chan ()
|
||||
in
|
||||
let waitee_in, waiter_out = pipe () in
|
||||
let waiter_in, waitee_out = pipe () in
|
||||
let waitee = waitee (waitee_in, waitee_out) in
|
||||
let waiter = waiter (waiter_in, waiter_out) in
|
||||
let run () =
|
||||
let initial_request () =
|
||||
let request = Jsonrpc.Request.create ~id:(`String "initial") ~method_:"init" () in
|
||||
print_endline "initial: waitee requests from waiter";
|
||||
let+ resp = Jrpc.request waitee request in
|
||||
print_endline "initial request response:";
|
||||
print (Response resp)
|
||||
in
|
||||
Fiber.all_concurrently_unit [ Jrpc.run waitee; initial_request (); Jrpc.run waiter ]
|
||||
in
|
||||
Fiber_test.test Dyn.opaque run;
|
||||
[%expect
|
||||
{|
|
||||
initial: waitee requests from waiter
|
||||
waiter: received request
|
||||
{ "id": "initial", "method": "init", "jsonrpc": "2.0" }
|
||||
waiter: sending response
|
||||
waiter: making request
|
||||
waitee: received request
|
||||
{ "id": 100, "method": "shutdown", "jsonrpc": "2.0" }
|
||||
waitee: stopping
|
||||
waitee: stopped
|
||||
initial request response:
|
||||
{ "id": "initial", "jsonrpc": "2.0", "result": null }
|
||||
waiter: received response:
|
||||
{ "id": 100, "jsonrpc": "2.0", "result": 42 }
|
||||
[FAIL] unexpected Never raised |}]
|
||||
;;
|
||||
|
||||
let%expect_test "test from jsonrpc_test.ml" =
|
||||
Printexc.record_backtrace false;
|
||||
let response =
|
||||
let i = ref 0 in
|
||||
fun () ->
|
||||
incr i;
|
||||
`Int !i
|
||||
in
|
||||
let on_request ctx =
|
||||
let req : Jsonrpc.Request.t = Context.message ctx in
|
||||
let state = Context.state ctx in
|
||||
Fiber.return (Reply.now (Jsonrpc.Response.ok req.id (response ())), state)
|
||||
in
|
||||
let on_notification ctx =
|
||||
let n : Jsonrpc.Notification.t = Context.message ctx in
|
||||
if n.method_ = "raise" then failwith "special failure";
|
||||
let json = Notification.yojson_of_t n in
|
||||
print_endline ">> received notification";
|
||||
print_json json;
|
||||
Fiber.return (Jsonrpc_fiber.Notify.Continue, ())
|
||||
in
|
||||
let responses = ref [] in
|
||||
let initial_requests =
|
||||
let request ?params id method_ : Jsonrpc.Packet.t =
|
||||
Request (Jsonrpc.Request.create ?params ~id ~method_ ())
|
||||
in
|
||||
let notification ?params method_ : Jsonrpc.Packet.t =
|
||||
Notification (Jsonrpc.Notification.create ?params ~method_ ())
|
||||
in
|
||||
[ request (`Int 10) "foo"
|
||||
; request (`String "testing") "bar"
|
||||
; notification "notif1"
|
||||
; notification "notif2"
|
||||
; notification "raise"
|
||||
]
|
||||
in
|
||||
let reqs_in, reqs_out = pipe () in
|
||||
let chan =
|
||||
let out = of_ref responses in
|
||||
reqs_in, out
|
||||
in
|
||||
let session = Jrpc.create ~on_notification ~on_request ~name:"test" chan () in
|
||||
let write_reqs () =
|
||||
let* () =
|
||||
Fiber.sequential_iter initial_requests ~f:(fun req -> Out.write reqs_out (Some req))
|
||||
in
|
||||
Out.write reqs_out None
|
||||
in
|
||||
Fiber_test.test Dyn.opaque (fun () ->
|
||||
Fiber.fork_and_join_unit write_reqs (fun () -> Jrpc.run session));
|
||||
List.rev !responses
|
||||
|> List.iter ~f:(fun packet ->
|
||||
let json = Jsonrpc.Packet.yojson_of_t packet in
|
||||
print_json json);
|
||||
[%expect
|
||||
{|
|
||||
>> received notification
|
||||
{ "method": "notif1", "jsonrpc": "2.0" }
|
||||
>> received notification
|
||||
{ "method": "notif2", "jsonrpc": "2.0" }
|
||||
Uncaught error when handling notification:
|
||||
{ "method": "raise", "jsonrpc": "2.0" }
|
||||
Error:
|
||||
[ { exn = "Failure(\"special failure\")"; backtrace = "" } ]
|
||||
<opaque>
|
||||
{ "id": 10, "jsonrpc": "2.0", "result": 1 }
|
||||
{ "id": "testing", "jsonrpc": "2.0", "result": 2 } |}]
|
||||
;;
|
||||
|
||||
let%expect_test "cancellation" =
|
||||
let () = Printexc.record_backtrace true in
|
||||
let print packet =
|
||||
print_endline
|
||||
(Yojson.Safe.pretty_to_string ~std:false (Jsonrpc.Packet.yojson_of_t packet))
|
||||
in
|
||||
let server_req_ack = Fiber.Ivar.create () in
|
||||
let client_req_ack = Fiber.Ivar.create () in
|
||||
let server chan =
|
||||
let on_request c =
|
||||
let request = Context.message c in
|
||||
let state = Context.state c in
|
||||
print_endline "server: received request";
|
||||
print (Request request);
|
||||
let* () = Fiber.Ivar.fill server_req_ack () in
|
||||
let response =
|
||||
Reply.later (fun send ->
|
||||
print_endline "server: waiting for client ack before sending response";
|
||||
let* () = Fiber.Ivar.read client_req_ack in
|
||||
print_endline "server: got client ack, sending response";
|
||||
send (Jsonrpc.Response.ok request.id (`String "Ok")))
|
||||
in
|
||||
Fiber.return (response, state)
|
||||
in
|
||||
Jrpc.create ~name:"server" ~on_request chan ()
|
||||
in
|
||||
let client chan = Jrpc.create ~name:"client" chan () in
|
||||
let run () =
|
||||
let client_in, client_out = pipe () in
|
||||
let server_in, server_out = pipe () in
|
||||
let client = client (client_in, server_out) in
|
||||
let server = server (server_in, client_out) in
|
||||
let request = Jsonrpc.Request.create ~id:(`String "initial") ~method_:"init" () in
|
||||
let cancel, req = Jrpc.request_with_cancel client request in
|
||||
let fire_cancellation =
|
||||
let* () = Fiber.return () in
|
||||
print_endline "client: waiting for server ack before cancelling request";
|
||||
let* () = Fiber.Ivar.read server_req_ack in
|
||||
print_endline "client: got server ack, cancelling request";
|
||||
let* () = Jrpc.fire cancel in
|
||||
Fiber.Ivar.fill client_req_ack ()
|
||||
in
|
||||
let initial_request =
|
||||
let* () = Fiber.return () in
|
||||
print_endline "client: sending request";
|
||||
let+ resp = req in
|
||||
match resp with
|
||||
| `Cancelled -> print_endline "request has been cancelled"
|
||||
| `Ok resp ->
|
||||
print_endline "request response:";
|
||||
print (Response resp)
|
||||
in
|
||||
Fiber.all_concurrently
|
||||
[ fire_cancellation
|
||||
; Jrpc.run client
|
||||
; initial_request
|
||||
>>> Fiber.fork_and_join_unit
|
||||
(fun () -> Out.write server_out None >>> Jrpc.stop client)
|
||||
(fun () -> Jrpc.stop server)
|
||||
; Jrpc.run server
|
||||
; Jrpc.stopped client
|
||||
; Jrpc.stopped server
|
||||
]
|
||||
in
|
||||
Fiber_test.test Dyn.opaque run;
|
||||
[%expect
|
||||
{|
|
||||
client: waiting for server ack before cancelling request
|
||||
client: sending request
|
||||
server: received request
|
||||
{ "id": "initial", "method": "init", "jsonrpc": "2.0" }
|
||||
server: waiting for client ack before sending response
|
||||
client: got server ack, cancelling request
|
||||
request has been cancelled
|
||||
server: got client ack, sending response
|
||||
<opaque> |}]
|
||||
;;
|
||||
0
thirdparty/lsp/jsonrpc-fiber/test/jsonrpc_fiber_tests.mli
vendored
Normal file
0
thirdparty/lsp/jsonrpc-fiber/test/jsonrpc_fiber_tests.mli
vendored
Normal file
41
thirdparty/lsp/jsonrpc.opam
vendored
Normal file
41
thirdparty/lsp/jsonrpc.opam
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
synopsis: "Jsonrpc protocol implemenation"
|
||||
description: "See https://www.jsonrpc.org/specification"
|
||||
maintainer: ["Rudi Grinberg <me@rgrinberg.com>"]
|
||||
authors: [
|
||||
"Andrey Popp <8mayday@gmail.com>"
|
||||
"Rusty Key <iam@stfoo.ru>"
|
||||
"Louis Roché <louis@louisroche.net>"
|
||||
"Oleksiy Golovko <alexei.golovko@gmail.com>"
|
||||
"Rudi Grinberg <me@rgrinberg.com>"
|
||||
"Sacha Ayoun <sachaayoun@gmail.com>"
|
||||
"cannorin <cannorin@gmail.com>"
|
||||
"Ulugbek Abdullaev <ulugbekna@gmail.com>"
|
||||
"Thibaut Mattio <thibaut.mattio@gmail.com>"
|
||||
"Max Lantas <mnxndev@outlook.com>"
|
||||
]
|
||||
license: "ISC"
|
||||
homepage: "https://github.com/ocaml/ocaml-lsp"
|
||||
bug-reports: "https://github.com/ocaml/ocaml-lsp/issues"
|
||||
depends: [
|
||||
"dune" {>= "3.0"}
|
||||
"ocaml" {>= "4.08"}
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
dev-repo: "git+https://github.com/ocaml/ocaml-lsp.git"
|
||||
build: [
|
||||
["dune" "subst"] {dev}
|
||||
[
|
||||
"dune"
|
||||
"build"
|
||||
"-p"
|
||||
name
|
||||
"-j"
|
||||
jobs
|
||||
"@install"
|
||||
"@doc" {with-doc}
|
||||
]
|
||||
]
|
||||
|
||||
x-maintenance-intent: [ "(latest)" "(latest)-414" ]
|
||||
4
thirdparty/lsp/jsonrpc/src/dune
vendored
Normal file
4
thirdparty/lsp/jsonrpc/src/dune
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
(library
|
||||
(public_name jsonrpc)
|
||||
(instrumentation
|
||||
(backend bisect_ppx)))
|
||||
60
thirdparty/lsp/jsonrpc/src/import.ml
vendored
Normal file
60
thirdparty/lsp/jsonrpc/src/import.ml
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
module List = ListLabels
|
||||
|
||||
module Option = struct
|
||||
let map t ~f =
|
||||
match t with
|
||||
| None -> None
|
||||
| Some x -> Some (f x)
|
||||
;;
|
||||
end
|
||||
|
||||
module Json = struct
|
||||
type t =
|
||||
[ `Assoc of (string * t) list
|
||||
| `Bool of bool
|
||||
| `Float of float
|
||||
| `Int of int
|
||||
| `Intlit of string
|
||||
| `List of t list
|
||||
| `Null
|
||||
| `String of string
|
||||
| `Tuple of t list
|
||||
| `Variant of string * t option
|
||||
]
|
||||
|
||||
exception Of_json of (string * t)
|
||||
|
||||
let () =
|
||||
Printexc.register_printer (function
|
||||
| Of_json (msg, _) -> Some ("Jsonrpc: json conversion failed: " ^ msg)
|
||||
| _ -> None)
|
||||
;;
|
||||
|
||||
let error msg json = raise (Of_json (msg, json))
|
||||
|
||||
module Jsonable = struct
|
||||
module type S = sig
|
||||
type json
|
||||
type t
|
||||
|
||||
val yojson_of_t : t -> json
|
||||
val t_of_yojson : json -> t
|
||||
end
|
||||
with type json := t
|
||||
end
|
||||
|
||||
let field fields name conv = List.assoc_opt name fields |> Option.map ~f:conv
|
||||
|
||||
let field_exn fields name conv =
|
||||
match field fields name conv with
|
||||
| Some f -> f
|
||||
| None -> error ("missing field " ^ name) (`Assoc fields)
|
||||
;;
|
||||
|
||||
module Conv = struct
|
||||
let string_of_yojson = function
|
||||
| `String s -> s
|
||||
| json -> error "expected string" json
|
||||
;;
|
||||
end
|
||||
end
|
||||
319
thirdparty/lsp/jsonrpc/src/jsonrpc.ml
vendored
Normal file
319
thirdparty/lsp/jsonrpc/src/jsonrpc.ml
vendored
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
open Import
|
||||
open Json.Conv
|
||||
module Json = Json
|
||||
|
||||
module Id = struct
|
||||
type t =
|
||||
[ `String of string
|
||||
| `Int of int
|
||||
]
|
||||
|
||||
let yojson_of_t = function
|
||||
| `String s -> `String s
|
||||
| `Int i -> `Int i
|
||||
;;
|
||||
|
||||
let t_of_yojson = function
|
||||
| `String s -> `String s
|
||||
| `Int i -> `Int i
|
||||
| json -> Json.error "Id.t" json
|
||||
;;
|
||||
|
||||
let hash x = Hashtbl.hash x
|
||||
let equal = ( = )
|
||||
end
|
||||
|
||||
module Constant = struct
|
||||
let jsonrpc = "jsonrpc"
|
||||
let jsonrpcv = "2.0"
|
||||
let id = "id"
|
||||
let method_ = "method"
|
||||
let params = "params"
|
||||
let result = "result"
|
||||
let error = "error"
|
||||
end
|
||||
|
||||
let assert_jsonrpc_version fields =
|
||||
let jsonrpc = Json.field_exn fields Constant.jsonrpc Json.Conv.string_of_yojson in
|
||||
if not (String.equal jsonrpc Constant.jsonrpcv)
|
||||
then
|
||||
Json.error
|
||||
("invalid packet: jsonrpc version doesn't match " ^ jsonrpc)
|
||||
(`Assoc fields)
|
||||
;;
|
||||
|
||||
module Structured = struct
|
||||
type t =
|
||||
[ `Assoc of (string * Json.t) list
|
||||
| `List of Json.t list
|
||||
]
|
||||
|
||||
let t_of_yojson = function
|
||||
| `Assoc xs -> `Assoc xs
|
||||
| `List xs -> `List xs
|
||||
| json -> Json.error "invalid structured value" json
|
||||
;;
|
||||
|
||||
let yojson_of_t t = (t :> Json.t)
|
||||
end
|
||||
|
||||
module Notification = struct
|
||||
type t =
|
||||
{ method_ : string
|
||||
; params : Structured.t option
|
||||
}
|
||||
|
||||
let fields ~method_ ~params =
|
||||
let json =
|
||||
[ Constant.method_, `String method_; Constant.jsonrpc, `String Constant.jsonrpcv ]
|
||||
in
|
||||
match params with
|
||||
| None -> json
|
||||
| Some params -> (Constant.params, (params :> Json.t)) :: json
|
||||
;;
|
||||
|
||||
let yojson_of_t { method_; params } = `Assoc (fields ~method_ ~params)
|
||||
let create ?params ~method_ () = { params; method_ }
|
||||
end
|
||||
|
||||
module Request = struct
|
||||
type t =
|
||||
{ id : Id.t
|
||||
; method_ : string
|
||||
; params : Structured.t option
|
||||
}
|
||||
|
||||
let yojson_of_t { id; method_; params } =
|
||||
let fields = Notification.fields ~method_ ~params in
|
||||
`Assoc ((Constant.id, Id.yojson_of_t id) :: fields)
|
||||
;;
|
||||
|
||||
let create ?params ~id ~method_ () = { params; id; method_ }
|
||||
end
|
||||
|
||||
module Response = struct
|
||||
module Error = struct
|
||||
module Code = struct
|
||||
type t =
|
||||
| ParseError
|
||||
| InvalidRequest
|
||||
| MethodNotFound
|
||||
| InvalidParams
|
||||
| InternalError
|
||||
(* the codes below are LSP specific *)
|
||||
| ServerErrorStart
|
||||
| ServerErrorEnd
|
||||
| ServerNotInitialized
|
||||
| UnknownErrorCode
|
||||
| RequestFailed
|
||||
| ServerCancelled
|
||||
| ContentModified
|
||||
| RequestCancelled
|
||||
(* all other codes are custom *)
|
||||
| Other of int
|
||||
|
||||
let of_int = function
|
||||
| -32700 -> ParseError
|
||||
| -32600 -> InvalidRequest
|
||||
| -32601 -> MethodNotFound
|
||||
| -32602 -> InvalidParams
|
||||
| -32603 -> InternalError
|
||||
| -32099 -> ServerErrorStart
|
||||
| -32000 -> ServerErrorEnd
|
||||
| -32002 -> ServerNotInitialized
|
||||
| -32001 -> UnknownErrorCode
|
||||
| -32800 -> RequestCancelled
|
||||
| -32801 -> ContentModified
|
||||
| -32802 -> ServerCancelled
|
||||
| -32803 -> RequestFailed
|
||||
| code -> Other code
|
||||
;;
|
||||
|
||||
let to_int = function
|
||||
| ParseError -> -32700
|
||||
| InvalidRequest -> -32600
|
||||
| MethodNotFound -> -32601
|
||||
| InvalidParams -> -32602
|
||||
| InternalError -> -32603
|
||||
| ServerErrorStart -> -32099
|
||||
| ServerErrorEnd -> -32000
|
||||
| ServerNotInitialized -> -32002
|
||||
| UnknownErrorCode -> -32001
|
||||
| RequestCancelled -> -32800
|
||||
| ContentModified -> -32801
|
||||
| ServerCancelled -> -32802
|
||||
| RequestFailed -> -32803
|
||||
| Other code -> code
|
||||
;;
|
||||
|
||||
let t_of_yojson json =
|
||||
match json with
|
||||
| `Int i -> of_int i
|
||||
| _ -> Json.error "invalid code" json
|
||||
;;
|
||||
|
||||
let yojson_of_t t = `Int (to_int t)
|
||||
end
|
||||
|
||||
type t =
|
||||
{ code : Code.t
|
||||
; message : string
|
||||
; data : Json.t option
|
||||
}
|
||||
|
||||
let yojson_of_t { code; message; data } =
|
||||
let assoc = [ "code", Code.yojson_of_t code; "message", `String message ] in
|
||||
let assoc =
|
||||
match data with
|
||||
| None -> assoc
|
||||
| Some data -> ("data", data) :: assoc
|
||||
in
|
||||
`Assoc assoc
|
||||
;;
|
||||
|
||||
let t_of_yojson json =
|
||||
match json with
|
||||
| `Assoc fields ->
|
||||
let code = Json.field_exn fields "code" Code.t_of_yojson in
|
||||
let message = Json.field_exn fields "message" string_of_yojson in
|
||||
let data = Json.field fields "data" (fun x -> x) in
|
||||
{ code; message; data }
|
||||
| _ -> Json.error "Jsonrpc.Response.t" json
|
||||
;;
|
||||
|
||||
exception E of t
|
||||
|
||||
let raise t = raise (E t)
|
||||
let make ?data ~code ~message () = { data; code; message }
|
||||
|
||||
let of_exn exn =
|
||||
let message = Printexc.to_string exn in
|
||||
make ~code:InternalError ~message ()
|
||||
;;
|
||||
end
|
||||
|
||||
type t =
|
||||
{ id : Id.t
|
||||
; result : (Json.t, Error.t) Result.t
|
||||
}
|
||||
|
||||
let yojson_of_t { id; result } =
|
||||
let result =
|
||||
match result with
|
||||
| Ok json -> Constant.result, json
|
||||
| Error e -> Constant.error, Error.yojson_of_t e
|
||||
in
|
||||
`Assoc
|
||||
[ Constant.id, Id.yojson_of_t id
|
||||
; Constant.jsonrpc, `String Constant.jsonrpcv
|
||||
; result
|
||||
]
|
||||
;;
|
||||
|
||||
let t_of_yojson json =
|
||||
match json with
|
||||
| `Assoc fields ->
|
||||
let id = Json.field_exn fields Constant.id Id.t_of_yojson in
|
||||
let jsonrpc = Json.field_exn fields Constant.jsonrpc Json.Conv.string_of_yojson in
|
||||
if jsonrpc <> Constant.jsonrpcv
|
||||
then Json.error "Invalid response" json
|
||||
else (
|
||||
match Json.field fields Constant.result (fun x -> x) with
|
||||
| Some res -> { id; result = Ok res }
|
||||
| None ->
|
||||
let result = Error (Json.field_exn fields Constant.error Error.t_of_yojson) in
|
||||
{ id; result })
|
||||
| _ -> Json.error "Jsonrpc.Result.t" json
|
||||
;;
|
||||
|
||||
let make ~id ~result = { id; result }
|
||||
let ok id result = make ~id ~result:(Ok result)
|
||||
let error id error = make ~id ~result:(Error error)
|
||||
end
|
||||
|
||||
module Packet = struct
|
||||
type t =
|
||||
| Notification of Notification.t
|
||||
| Request of Request.t
|
||||
| Response of Response.t
|
||||
| Batch_response of Response.t list
|
||||
| Batch_call of [ `Request of Request.t | `Notification of Notification.t ] list
|
||||
|
||||
let yojson_of_t = function
|
||||
| Notification r -> Notification.yojson_of_t r
|
||||
| Request r -> Request.yojson_of_t r
|
||||
| Response r -> Response.yojson_of_t r
|
||||
| Batch_response r -> `List (List.map r ~f:Response.yojson_of_t)
|
||||
| Batch_call r ->
|
||||
`List
|
||||
(List.map r ~f:(function
|
||||
| `Request r -> Request.yojson_of_t r
|
||||
| `Notification r -> Notification.yojson_of_t r))
|
||||
;;
|
||||
|
||||
let t_of_fields (fields : (string * Json.t) list) =
|
||||
assert_jsonrpc_version fields;
|
||||
match Json.field fields Constant.id Id.t_of_yojson with
|
||||
| None ->
|
||||
let method_ = Json.field_exn fields Constant.method_ Json.Conv.string_of_yojson in
|
||||
let params = Json.field fields Constant.params Structured.t_of_yojson in
|
||||
Notification { Notification.params; method_ }
|
||||
| Some id ->
|
||||
(match Json.field fields Constant.method_ Json.Conv.string_of_yojson with
|
||||
| Some method_ ->
|
||||
let params = Json.field fields Constant.params Structured.t_of_yojson in
|
||||
Request { Request.method_; params; id }
|
||||
| None ->
|
||||
Response
|
||||
(match Json.field fields Constant.result (fun x -> x) with
|
||||
| Some result -> { Response.id; result = Ok result }
|
||||
| None ->
|
||||
let error =
|
||||
Json.field_exn fields Constant.error Response.Error.t_of_yojson
|
||||
in
|
||||
{ id; result = Error error }))
|
||||
;;
|
||||
|
||||
let t_of_yojson_single json =
|
||||
match json with
|
||||
| `Assoc fields -> t_of_fields fields
|
||||
| _ -> Json.error "invalid packet" json
|
||||
;;
|
||||
|
||||
let t_of_yojson (json : Json.t) =
|
||||
match json with
|
||||
| `List [] -> Json.error "invalid packet" json
|
||||
| `List (x :: xs) ->
|
||||
(* we inspect the first element to see what we're dealing with *)
|
||||
let x =
|
||||
match x with
|
||||
| `Assoc fields -> t_of_fields fields
|
||||
| _ -> Json.error "invalid packet" json
|
||||
in
|
||||
(match
|
||||
match x with
|
||||
| Notification x -> `Call (`Notification x)
|
||||
| Request x -> `Call (`Request x)
|
||||
| Response r -> `Response r
|
||||
| _ -> Json.error "invalid packet" json
|
||||
with
|
||||
| `Call x ->
|
||||
Batch_call
|
||||
(x
|
||||
:: List.map xs ~f:(fun call ->
|
||||
let x = t_of_yojson_single call in
|
||||
match x with
|
||||
| Notification n -> `Notification n
|
||||
| Request n -> `Request n
|
||||
| _ -> Json.error "invalid packet" json))
|
||||
| `Response x ->
|
||||
Batch_response
|
||||
(x
|
||||
:: List.map xs ~f:(fun resp ->
|
||||
let resp = t_of_yojson_single resp in
|
||||
match resp with
|
||||
| Response n -> n
|
||||
| _ -> Json.error "invalid packet" json)))
|
||||
| _ -> t_of_yojson_single json
|
||||
;;
|
||||
end
|
||||
128
thirdparty/lsp/jsonrpc/src/jsonrpc.mli
vendored
Normal file
128
thirdparty/lsp/jsonrpc/src/jsonrpc.mli
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
(** Jsonrpc implementation *)
|
||||
|
||||
module Json : sig
|
||||
type t =
|
||||
[ `Assoc of (string * t) list
|
||||
| `Bool of bool
|
||||
| `Float of float
|
||||
| `Int of int
|
||||
| `Intlit of string
|
||||
| `List of t list
|
||||
| `Null
|
||||
| `String of string
|
||||
| `Tuple of t list
|
||||
| `Variant of string * t option
|
||||
]
|
||||
|
||||
(** Raised when conversions from json fail *)
|
||||
exception Of_json of (string * t)
|
||||
|
||||
module Jsonable : sig
|
||||
module type S = sig
|
||||
type json
|
||||
type t
|
||||
|
||||
val yojson_of_t : t -> json
|
||||
val t_of_yojson : json -> t
|
||||
end
|
||||
with type json := t
|
||||
end
|
||||
end
|
||||
|
||||
module Id : sig
|
||||
type t =
|
||||
[ `String of string
|
||||
| `Int of int
|
||||
]
|
||||
|
||||
include Json.Jsonable.S with type t := t
|
||||
|
||||
val hash : t -> int
|
||||
val equal : t -> t -> bool
|
||||
end
|
||||
|
||||
module Structured : sig
|
||||
type t =
|
||||
[ `Assoc of (string * Json.t) list
|
||||
| `List of Json.t list
|
||||
]
|
||||
|
||||
include Json.Jsonable.S with type t := t
|
||||
end
|
||||
|
||||
module Notification : sig
|
||||
type t =
|
||||
{ method_ : string
|
||||
; params : Structured.t option
|
||||
}
|
||||
|
||||
val create : ?params:Structured.t -> method_:string -> unit -> t
|
||||
val yojson_of_t : t -> Json.t
|
||||
end
|
||||
|
||||
module Request : sig
|
||||
type t =
|
||||
{ id : Id.t
|
||||
; method_ : string
|
||||
; params : Structured.t option
|
||||
}
|
||||
|
||||
val create : ?params:Structured.t -> id:Id.t -> method_:string -> unit -> t
|
||||
val yojson_of_t : t -> Json.t
|
||||
end
|
||||
|
||||
module Response : sig
|
||||
module Error : sig
|
||||
module Code : sig
|
||||
type t =
|
||||
| ParseError
|
||||
| InvalidRequest
|
||||
| MethodNotFound
|
||||
| InvalidParams
|
||||
| InternalError
|
||||
| ServerErrorStart
|
||||
| ServerErrorEnd
|
||||
| ServerNotInitialized
|
||||
| UnknownErrorCode
|
||||
| RequestFailed
|
||||
| ServerCancelled
|
||||
| ContentModified
|
||||
| RequestCancelled
|
||||
| Other of int
|
||||
end
|
||||
|
||||
type t =
|
||||
{ code : Code.t
|
||||
; message : string
|
||||
; data : Json.t option
|
||||
}
|
||||
|
||||
exception E of t
|
||||
|
||||
val make : ?data:Json.t -> code:Code.t -> message:string -> unit -> t
|
||||
val raise : t -> 'a
|
||||
val of_exn : exn -> t
|
||||
val yojson_of_t : t -> Json.t
|
||||
end
|
||||
|
||||
type t =
|
||||
{ id : Id.t
|
||||
; result : (Json.t, Error.t) Result.t
|
||||
}
|
||||
|
||||
val ok : Id.t -> Json.t -> t
|
||||
val error : Id.t -> Error.t -> t
|
||||
|
||||
include Json.Jsonable.S with type t := t
|
||||
end
|
||||
|
||||
module Packet : sig
|
||||
type t =
|
||||
| Notification of Notification.t
|
||||
| Request of Request.t
|
||||
| Response of Response.t
|
||||
| Batch_response of Response.t list
|
||||
| Batch_call of [ `Request of Request.t | `Notification of Notification.t ] list
|
||||
|
||||
include Json.Jsonable.S with type t := t
|
||||
end
|
||||
1
thirdparty/lsp/lsp-fiber/src/client.ml
vendored
Normal file
1
thirdparty/lsp/lsp-fiber/src/client.ml
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
include Rpc.Client
|
||||
1
thirdparty/lsp/lsp-fiber/src/client.mli
vendored
Normal file
1
thirdparty/lsp/lsp-fiber/src/client.mli
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
include module type of Rpc.Client
|
||||
14
thirdparty/lsp/lsp-fiber/src/dune
vendored
Normal file
14
thirdparty/lsp/lsp-fiber/src/dune
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
(library
|
||||
(name lsp_fiber)
|
||||
(libraries
|
||||
dyn
|
||||
fiber
|
||||
lev_fiber
|
||||
jsonrpc
|
||||
jsonrpc_fiber
|
||||
lsp
|
||||
ppx_yojson_conv_lib
|
||||
stdune
|
||||
yojson)
|
||||
(instrumentation
|
||||
(backend bisect_ppx)))
|
||||
55
thirdparty/lsp/lsp-fiber/src/fiber_io.ml
vendored
Normal file
55
thirdparty/lsp/lsp-fiber/src/fiber_io.ml
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
open Import
|
||||
open Fiber.O
|
||||
module Lio = Lev_fiber.Io
|
||||
|
||||
type t = Lio.input Lio.t * Lio.output Lio.t * Fiber.Mutex.t
|
||||
|
||||
module Io =
|
||||
Io.Make
|
||||
(struct
|
||||
include Fiber
|
||||
|
||||
let raise exn = raise exn
|
||||
end)
|
||||
(struct
|
||||
type input = Lio.Reader.t
|
||||
type output = Lio.Writer.t
|
||||
|
||||
let read_line ic =
|
||||
let+ res = Lio.Reader.read_line ic in
|
||||
match res with
|
||||
| Ok s -> Some s
|
||||
| Error (`Partial_eof _) -> None
|
||||
;;
|
||||
|
||||
let read_exactly ic len =
|
||||
let+ res = Lio.Reader.read_exactly ic len in
|
||||
match res with
|
||||
| Ok s -> Some s
|
||||
| Error (`Partial_eof _) -> None
|
||||
;;
|
||||
|
||||
let write oc strings =
|
||||
Fiber.of_thunk (fun () ->
|
||||
List.iter strings ~f:(Lio.Writer.add_string oc);
|
||||
Fiber.return ())
|
||||
;;
|
||||
end)
|
||||
|
||||
let send (_, oc, m) packets =
|
||||
Fiber.Mutex.with_lock m ~f:(fun () ->
|
||||
Lio.with_write oc ~f:(fun writer ->
|
||||
let* () = Fiber.sequential_iter packets ~f:(Io.write writer) in
|
||||
Lio.Writer.flush writer))
|
||||
;;
|
||||
|
||||
let recv (ic, _, _) = Lio.with_read ic ~f:Io.read
|
||||
let make ic oc = ic, oc, Fiber.Mutex.create ()
|
||||
|
||||
let close (ic, oc, _) what =
|
||||
Fiber.of_thunk (fun () ->
|
||||
(match what with
|
||||
| `Write -> Lio.close oc
|
||||
| `Read -> Lio.close ic);
|
||||
Fiber.return ())
|
||||
;;
|
||||
11
thirdparty/lsp/lsp-fiber/src/fiber_io.mli
vendored
Normal file
11
thirdparty/lsp/lsp-fiber/src/fiber_io.mli
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
(** Reprsents a bi-directional jsonrpc packet stream read in dedicated threads.
|
||||
|
||||
TODO Nothing here is specific to jsonrpc *)
|
||||
open! Import
|
||||
|
||||
type t
|
||||
|
||||
val close : t -> [ `Read | `Write ] -> unit Fiber.t
|
||||
val send : t -> Jsonrpc.Packet.t list -> unit Fiber.t
|
||||
val recv : t -> Jsonrpc.Packet.t option Fiber.t
|
||||
val make : Lev_fiber.Io.input Lev_fiber.Io.t -> Lev_fiber.Io.output Lev_fiber.Io.t -> t
|
||||
115
thirdparty/lsp/lsp-fiber/src/import.ml
vendored
Normal file
115
thirdparty/lsp/lsp-fiber/src/import.ml
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
module List = Stdlib.ListLabels
|
||||
module Code_error = Stdune.Code_error
|
||||
module Header = Lsp.Header
|
||||
module Io = Lsp.Io
|
||||
|
||||
module Fdecl : sig
|
||||
type 'a t
|
||||
|
||||
val get : 'a t -> 'a
|
||||
val set : 'a t -> 'a -> unit
|
||||
val create : unit -> 'a t
|
||||
end = struct
|
||||
type 'a t = 'a option ref
|
||||
|
||||
let create () = ref None
|
||||
|
||||
let set t x =
|
||||
match !t with
|
||||
| Some _ -> invalid_arg "Fdecl.create: already set"
|
||||
| None -> t := Some x
|
||||
;;
|
||||
|
||||
let get t =
|
||||
match !t with
|
||||
| None -> invalid_arg "Fdecl.get: not set"
|
||||
| Some t -> t
|
||||
;;
|
||||
end
|
||||
|
||||
module Json = struct
|
||||
include Lsp.Import.Json
|
||||
|
||||
let pp ppf (t : t) = Yojson.Safe.pretty_print ppf t
|
||||
|
||||
let rec of_dyn (t : Dyn.t) : t =
|
||||
match t with
|
||||
| Opaque -> `String "<opaque>"
|
||||
| Unit -> `String "()"
|
||||
| Int i -> `Int i
|
||||
| Int32 i -> `Int (Int32.to_int i)
|
||||
| Nativeint i -> `Int (Nativeint.to_int i)
|
||||
| Int64 i -> `Int (Int64.to_int i)
|
||||
| Bool b -> `Bool b
|
||||
| String s -> `String s
|
||||
| Bytes s -> `String (Bytes.to_string s)
|
||||
| Char c -> `String (String.make 1 c)
|
||||
| Float f -> `Float f
|
||||
| Option None -> `String "<none>"
|
||||
| Option (Some s) -> of_dyn s
|
||||
| List xs -> `List (List.map ~f:of_dyn xs)
|
||||
| Array xs -> `List (List.map ~f:of_dyn (Array.to_list xs))
|
||||
| Tuple xs -> `List (List.map ~f:of_dyn xs)
|
||||
| Record r -> `Assoc (List.map r ~f:(fun (k, v) -> k, of_dyn v))
|
||||
| Variant (name, args) -> `Assoc [ name, of_dyn (List args) ]
|
||||
| Set xs -> `List (List.map ~f:of_dyn xs)
|
||||
| Map map -> `List (List.map map ~f:(fun (k, v) -> `List [ of_dyn k; of_dyn v ]))
|
||||
;;
|
||||
|
||||
let rec to_dyn (t : t) : Dyn.t =
|
||||
match t with
|
||||
| `String s -> String s
|
||||
| `Int i -> Int i
|
||||
| `Float f -> Float f
|
||||
| `Bool f -> Bool f
|
||||
| `Assoc o -> Record (List.map o ~f:(fun (k, v) -> k, to_dyn v))
|
||||
| `List l -> List (List.map l ~f:to_dyn)
|
||||
| `Tuple args -> Tuple (List.map args ~f:to_dyn)
|
||||
| `Null -> Dyn.Variant ("Null", [])
|
||||
| `Variant (name, Some arg) -> Variant (name, [ to_dyn arg ])
|
||||
| `Variant (name, None) -> Variant (name, [])
|
||||
| `Intlit s -> String s
|
||||
;;
|
||||
end
|
||||
|
||||
module Log = struct
|
||||
let level : (string option -> bool) ref = ref (fun _ -> false)
|
||||
let out = ref Format.err_formatter
|
||||
|
||||
type message =
|
||||
{ message : string
|
||||
; payload : (string * Json.t) list
|
||||
}
|
||||
|
||||
let msg message payload = { message; payload }
|
||||
|
||||
let log ?section k =
|
||||
if !level section
|
||||
then (
|
||||
let message = k () in
|
||||
(match section with
|
||||
| None -> Format.fprintf !out "%s@." message.message
|
||||
| Some section -> Format.fprintf !out "[%s] %s@." section message.message);
|
||||
(match message.payload with
|
||||
| [] -> ()
|
||||
| fields -> Format.fprintf !out "%a@." Json.pp (`Assoc fields));
|
||||
Format.pp_print_flush !out ())
|
||||
;;
|
||||
end
|
||||
|
||||
let sprintf = Printf.sprintf
|
||||
|
||||
module Types = Lsp.Types
|
||||
module Client_request = Lsp.Client_request
|
||||
module Server_request = Lsp.Server_request
|
||||
module Server_notification = Lsp.Server_notification
|
||||
module Client_notification = Lsp.Client_notification
|
||||
|
||||
module Jrpc_id = struct
|
||||
include Jsonrpc.Id
|
||||
|
||||
let to_dyn = function
|
||||
| `String s -> Dyn.String s
|
||||
| `Int i -> Dyn.Int i
|
||||
;;
|
||||
end
|
||||
18
thirdparty/lsp/lsp-fiber/src/lazy_fiber.ml
vendored
Normal file
18
thirdparty/lsp/lsp-fiber/src/lazy_fiber.ml
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
type 'a t =
|
||||
{ value : 'a Fiber.Ivar.t
|
||||
; mutable f : (unit -> 'a Fiber.t) option
|
||||
}
|
||||
|
||||
let create f = { f = Some f; value = Fiber.Ivar.create () }
|
||||
|
||||
let force t =
|
||||
let open Fiber.O in
|
||||
match t.f with
|
||||
| None -> Fiber.Ivar.read t.value
|
||||
| Some f ->
|
||||
Fiber.of_thunk (fun () ->
|
||||
t.f <- None;
|
||||
let* v = f () in
|
||||
let+ () = Fiber.Ivar.fill t.value v in
|
||||
v)
|
||||
;;
|
||||
4
thirdparty/lsp/lsp-fiber/src/lazy_fiber.mli
vendored
Normal file
4
thirdparty/lsp/lsp-fiber/src/lazy_fiber.mli
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
type 'a t
|
||||
|
||||
val create : (unit -> 'a Fiber.t) -> 'a t
|
||||
val force : 'a t -> 'a Fiber.t
|
||||
11
thirdparty/lsp/lsp-fiber/src/lsp_fiber.ml
vendored
Normal file
11
thirdparty/lsp/lsp-fiber/src/lsp_fiber.ml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
module Fiber_io = Fiber_io
|
||||
module Lazy_fiber = Lazy_fiber
|
||||
module Rpc = Rpc
|
||||
module Client = Client
|
||||
module Server = Server
|
||||
module Json = Import.Json
|
||||
|
||||
module Private = struct
|
||||
module Log = Import.Log
|
||||
module Fdecl = Import.Fdecl
|
||||
end
|
||||
450
thirdparty/lsp/lsp-fiber/src/rpc.ml
vendored
Normal file
450
thirdparty/lsp/lsp-fiber/src/rpc.ml
vendored
Normal file
|
|
@ -0,0 +1,450 @@
|
|||
open Import
|
||||
open Fiber.O
|
||||
module Id = Jsonrpc.Id
|
||||
module Response = Jsonrpc.Response
|
||||
module Session = Jsonrpc_fiber.Make (Fiber_io)
|
||||
|
||||
module Reply = struct
|
||||
type 'r t =
|
||||
| Now of 'r
|
||||
| Later of (('r -> unit Fiber.t) -> unit Fiber.t)
|
||||
|
||||
let now r = Now r
|
||||
let later f = Later f
|
||||
end
|
||||
|
||||
let cancel_token = Fiber.Var.create ()
|
||||
|
||||
module State = struct
|
||||
type t =
|
||||
| Waiting_for_init
|
||||
| Running
|
||||
| Closed
|
||||
end
|
||||
|
||||
module type S = sig
|
||||
type 'a out_request
|
||||
type out_notification
|
||||
type 'a in_request
|
||||
type in_notification
|
||||
type 'state t
|
||||
|
||||
module Handler : sig
|
||||
type 'a session := 'a t
|
||||
|
||||
type 'state on_request =
|
||||
{ on_request : 'a. 'state session -> 'a in_request -> ('a Reply.t * 'state) Fiber.t
|
||||
}
|
||||
|
||||
type 'state t
|
||||
|
||||
val make
|
||||
: ?on_request:'state on_request
|
||||
-> ?on_notification:('state session -> in_notification -> 'state Fiber.t)
|
||||
-> unit
|
||||
-> 'state t
|
||||
end
|
||||
|
||||
val state : 'a t -> 'a
|
||||
val make : 'state Handler.t -> Fiber_io.t -> 'state -> 'state t
|
||||
val stop : _ t -> unit Fiber.t
|
||||
val request : _ t -> 'resp out_request -> 'resp Fiber.t
|
||||
val notification : _ t -> out_notification -> unit Fiber.t
|
||||
val cancel_token : unit -> Fiber.Cancel.t option Fiber.t
|
||||
|
||||
module Batch : sig
|
||||
type 'a session := 'a t
|
||||
type t
|
||||
|
||||
val create : _ session -> t
|
||||
val notification : t -> out_notification -> unit
|
||||
|
||||
type 'a response
|
||||
|
||||
val await : 'a response -> 'a Fiber.t
|
||||
val request : t -> 'resp out_request -> 'resp response
|
||||
val submit : t -> unit Fiber.t
|
||||
end
|
||||
end
|
||||
|
||||
module type Request_intf = sig
|
||||
type 'a t
|
||||
type packed = E : 'r t -> packed
|
||||
|
||||
val of_jsonrpc : Jsonrpc.Request.t -> (packed, string) result
|
||||
val yojson_of_result : 'a t -> 'a -> Json.t
|
||||
val to_jsonrpc_request : 'a t -> id:Id.t -> Jsonrpc.Request.t
|
||||
val response_of_json : 'a t -> Json.t -> 'a
|
||||
end
|
||||
|
||||
module type Notification_intf = sig
|
||||
type t
|
||||
|
||||
val of_jsonrpc : Jsonrpc.Notification.t -> (t, string) result
|
||||
val to_jsonrpc : t -> Jsonrpc.Notification.t
|
||||
end
|
||||
|
||||
module Table = Stdlib.Hashtbl.Make (Jsonrpc.Id)
|
||||
|
||||
module Make
|
||||
(Initialize : sig
|
||||
type t
|
||||
end)
|
||||
(Out_request : Request_intf)
|
||||
(Out_notification : Notification_intf)
|
||||
(In_request : Request_intf)
|
||||
(In_notification : Notification_intf) =
|
||||
struct
|
||||
type 'a out_request = 'a Out_request.t
|
||||
type 'a in_request = 'a In_request.t
|
||||
type out_notification = Out_notification.t
|
||||
type in_notification = In_notification.t
|
||||
|
||||
type 'state t =
|
||||
{ io : Fiber_io.t
|
||||
; (* mutable only to initialiaze this record *)
|
||||
mutable session : 'state Session.t Fdecl.t
|
||||
; (* Internal state of the session *)
|
||||
mutable state : State.t
|
||||
; (* Filled when the server is initialied *)
|
||||
initialized : Initialize.t Fiber.Ivar.t
|
||||
; mutable req_id : int
|
||||
; pending : Fiber.Cancel.t Table.t
|
||||
; detached : Fiber.Pool.t
|
||||
}
|
||||
|
||||
and 'state on_request =
|
||||
{ on_request : 'a. 'state t -> 'a in_request -> ('a Reply.t * 'state) Fiber.t }
|
||||
|
||||
and 'state handler =
|
||||
{ h_on_request : 'state on_request
|
||||
; h_on_notification : 'state t -> In_notification.t -> 'state Fiber.t
|
||||
}
|
||||
|
||||
module Handler = struct
|
||||
type nonrec 'state on_request = 'state on_request =
|
||||
{ on_request : 'a. 'state t -> 'a in_request -> ('a Reply.t * 'state) Fiber.t }
|
||||
|
||||
type nonrec 'state t = 'state handler =
|
||||
{ h_on_request : 'state on_request
|
||||
; h_on_notification : 'state t -> In_notification.t -> 'state Fiber.t
|
||||
}
|
||||
|
||||
let on_notification_default _ notification =
|
||||
Format.eprintf "dropped notification@.%!";
|
||||
let notification = In_notification.to_jsonrpc notification in
|
||||
Code_error.raise
|
||||
"unexpected notification"
|
||||
[ "notification", Json.to_dyn (Jsonrpc.Notification.yojson_of_t notification) ]
|
||||
;;
|
||||
|
||||
let on_request_default =
|
||||
{ on_request =
|
||||
(fun _ _ ->
|
||||
Jsonrpc.Response.Error.make ~code:InternalError ~message:"Not supported" ()
|
||||
|> Jsonrpc.Response.Error.raise)
|
||||
}
|
||||
;;
|
||||
|
||||
let make
|
||||
?(on_request = on_request_default)
|
||||
?(on_notification = on_notification_default)
|
||||
()
|
||||
=
|
||||
{ h_on_request = on_request; h_on_notification = on_notification }
|
||||
;;
|
||||
end
|
||||
|
||||
let state t = Session.state (Fdecl.get t.session)
|
||||
|
||||
let to_jsonrpc (type state) (t : state t) h_on_request h_on_notification =
|
||||
let on_request (ctx : (state, Jsonrpc.Request.t) Session.Context.t) =
|
||||
let req = Session.Context.message ctx in
|
||||
let state = Session.Context.state ctx in
|
||||
match In_request.of_jsonrpc req with
|
||||
| Error message ->
|
||||
let code = Jsonrpc.Response.Error.Code.InvalidParams in
|
||||
let error = Jsonrpc.Response.Error.make ~code ~message () in
|
||||
Fiber.return (Jsonrpc_fiber.Reply.now (Jsonrpc.Response.error req.id error), state)
|
||||
| Ok (In_request.E r) ->
|
||||
let cancel = Fiber.Cancel.create () in
|
||||
let remove = lazy (Table.remove t.pending req.id) in
|
||||
let+ response, state =
|
||||
Fiber.with_error_handler
|
||||
~on_error:
|
||||
(Stdune.Exn_with_backtrace.map_and_reraise ~f:(fun exn ->
|
||||
Lazy.force remove;
|
||||
exn))
|
||||
(fun () ->
|
||||
Fiber.Var.set cancel_token cancel (fun () ->
|
||||
Table.replace t.pending req.id cancel;
|
||||
h_on_request.on_request t r))
|
||||
in
|
||||
let to_response x =
|
||||
Jsonrpc.Response.ok req.id (In_request.yojson_of_result r x)
|
||||
in
|
||||
let reply =
|
||||
match response with
|
||||
| Reply.Now r ->
|
||||
Lazy.force remove;
|
||||
Jsonrpc_fiber.Reply.now (to_response r)
|
||||
| Reply.Later k ->
|
||||
let f send =
|
||||
Fiber.finalize
|
||||
(fun () ->
|
||||
Fiber.Var.set cancel_token cancel (fun () ->
|
||||
k (fun r -> send (to_response r))))
|
||||
~finally:(fun () ->
|
||||
Lazy.force remove;
|
||||
Fiber.return ())
|
||||
in
|
||||
Jsonrpc_fiber.Reply.later f
|
||||
in
|
||||
reply, state
|
||||
in
|
||||
let on_notification ctx =
|
||||
let r = Session.Context.message ctx in
|
||||
match In_notification.of_jsonrpc r with
|
||||
| Ok r -> h_on_notification t r
|
||||
| Error error ->
|
||||
Log.log ~section:"lsp" (fun () ->
|
||||
Log.msg "Invalid notification" [ "error", `String error ]);
|
||||
let state = Session.Context.state ctx in
|
||||
Fiber.return (Jsonrpc_fiber.Notify.Continue, state)
|
||||
in
|
||||
on_request, on_notification
|
||||
;;
|
||||
|
||||
let make ~name h_on_request h_on_notification io state =
|
||||
let t =
|
||||
{ io
|
||||
; state = Waiting_for_init
|
||||
; session = Fdecl.create ()
|
||||
; initialized = Fiber.Ivar.create ()
|
||||
; req_id = 1
|
||||
; pending = Table.create 32
|
||||
; detached = Fiber.Pool.create ()
|
||||
}
|
||||
in
|
||||
let session =
|
||||
let on_request, on_notification = to_jsonrpc t h_on_request h_on_notification in
|
||||
Session.create ~on_request ~on_notification ~name io state
|
||||
in
|
||||
Fdecl.set t.session session;
|
||||
t
|
||||
;;
|
||||
|
||||
let create_request t req =
|
||||
let id = `Int t.req_id in
|
||||
t.req_id <- t.req_id + 1;
|
||||
Out_request.to_jsonrpc_request req ~id
|
||||
;;
|
||||
|
||||
let receive_response req (resp : Jsonrpc.Response.t) =
|
||||
match resp.result |> Result.map (Out_request.response_of_json req) with
|
||||
| Ok s -> s
|
||||
| Error e -> raise (Jsonrpc.Response.Error.E e)
|
||||
;;
|
||||
|
||||
let request (type r) (t : _ t) (req : r Out_request.t) : r Fiber.t =
|
||||
Fiber.of_thunk (fun () ->
|
||||
let+ resp =
|
||||
let req = create_request t req in
|
||||
Session.request (Fdecl.get t.session) req
|
||||
in
|
||||
receive_response req resp)
|
||||
;;
|
||||
|
||||
let request_with_cancel (type r) (t : _ t) cancel ~on_cancel (req : r Out_request.t)
|
||||
: [ `Ok of r | `Cancelled ] Fiber.t
|
||||
=
|
||||
let* () = Fiber.return () in
|
||||
let jsonrpc_req = create_request t req in
|
||||
let+ resp, cancel_status =
|
||||
Fiber.Cancel.with_handler
|
||||
cancel
|
||||
~on_cancel:(fun () -> on_cancel jsonrpc_req.id)
|
||||
(fun () ->
|
||||
let+ resp = Session.request (Fdecl.get t.session) jsonrpc_req in
|
||||
match resp.result with
|
||||
| Error { code = RequestCancelled; _ } -> `Cancelled
|
||||
| Ok _ when Fiber.Cancel.fired cancel -> `Cancelled
|
||||
| Ok s -> `Ok (Out_request.response_of_json req s)
|
||||
| Error e -> raise (Jsonrpc.Response.Error.E e))
|
||||
in
|
||||
match cancel_status with
|
||||
| Cancelled () -> `Cancelled
|
||||
| Not_cancelled ->
|
||||
(match resp with
|
||||
| `Ok resp -> `Ok resp
|
||||
| `Cancelled -> assert false)
|
||||
;;
|
||||
|
||||
let notification (t : _ t) (n : Out_notification.t) : unit Fiber.t =
|
||||
let jsonrpc_request = Out_notification.to_jsonrpc n in
|
||||
Session.notification (Fdecl.get t.session) jsonrpc_request
|
||||
;;
|
||||
|
||||
module Batch = struct
|
||||
type session = E : 'a t -> session
|
||||
|
||||
type t =
|
||||
{ batch : Session.Batch.t
|
||||
; session : session
|
||||
}
|
||||
|
||||
let create session = { batch = Session.Batch.create (); session = E session }
|
||||
|
||||
let notification t n =
|
||||
let n = Out_notification.to_jsonrpc n in
|
||||
Session.Batch.notification t.batch n
|
||||
;;
|
||||
|
||||
type 'a response = 'a Lazy_fiber.t
|
||||
|
||||
let await req = Lazy_fiber.force req
|
||||
|
||||
let request (type r) (t : t) (req : r Out_request.t) : r response =
|
||||
let (E session) = t.session in
|
||||
let response =
|
||||
let req = create_request session req in
|
||||
Session.Batch.request t.batch req
|
||||
in
|
||||
Lazy_fiber.create (fun () ->
|
||||
let+ response = Session.Batch.await response in
|
||||
receive_response req response)
|
||||
;;
|
||||
|
||||
let submit { session = E session; batch } =
|
||||
let t = Fdecl.get session.session in
|
||||
Session.submit t batch
|
||||
;;
|
||||
end
|
||||
|
||||
let initialized t = Fiber.Ivar.read t.initialized
|
||||
|
||||
let stop t =
|
||||
let+ () = Session.stop (Fdecl.get t.session) in
|
||||
t.state <- Closed
|
||||
;;
|
||||
|
||||
let start_loop t =
|
||||
Fiber.fork_and_join_unit
|
||||
(fun () ->
|
||||
let* () = Session.run (Fdecl.get t.session) in
|
||||
Fiber.Pool.stop t.detached)
|
||||
(fun () -> Fiber.Pool.run t.detached)
|
||||
;;
|
||||
|
||||
let handle_cancel_req t id =
|
||||
let+ () =
|
||||
match Table.find_opt t.pending id with
|
||||
| None -> Fiber.return ()
|
||||
| Some token -> Fiber.Pool.task t.detached ~f:(fun () -> Fiber.Cancel.fire token)
|
||||
in
|
||||
Jsonrpc_fiber.Notify.Continue, state t
|
||||
;;
|
||||
|
||||
let cancel_token () = Fiber.Var.get cancel_token
|
||||
end
|
||||
|
||||
module Client = struct
|
||||
open Types
|
||||
|
||||
include
|
||||
Make (InitializeResult) (Client_request) (Client_notification) (Server_request)
|
||||
(Server_notification)
|
||||
|
||||
let h_on_notification handler t n =
|
||||
match n with
|
||||
| Server_notification.CancelRequest id -> handle_cancel_req t id
|
||||
| _ ->
|
||||
let+ res = handler.h_on_notification t n in
|
||||
Jsonrpc_fiber.Notify.Continue, res
|
||||
;;
|
||||
|
||||
let make handler io =
|
||||
let h_on_notification = h_on_notification handler in
|
||||
make ~name:"client" handler.h_on_request h_on_notification io
|
||||
;;
|
||||
|
||||
let request_with_cancel t cancel r =
|
||||
request_with_cancel t cancel r ~on_cancel:(fun id ->
|
||||
notification t (Client_notification.CancelRequest id))
|
||||
;;
|
||||
|
||||
let start (t : _ t) (p : InitializeParams.t) =
|
||||
Fiber.of_thunk (fun () ->
|
||||
assert (t.state = Waiting_for_init);
|
||||
let loop () = start_loop t in
|
||||
let init () =
|
||||
let* resp = request t (Client_request.Initialize p) in
|
||||
Log.log ~section:"client" (fun () ->
|
||||
let resp = InitializeResult.yojson_of_t resp in
|
||||
Log.msg "initialized" [ "resp", resp ]);
|
||||
t.state <- Running;
|
||||
Fiber.Ivar.fill t.initialized resp
|
||||
in
|
||||
Fiber.fork_and_join_unit loop init)
|
||||
;;
|
||||
end
|
||||
|
||||
module Server = struct
|
||||
open Types
|
||||
|
||||
include
|
||||
Make (InitializeParams) (Server_request) (Server_notification) (Client_request)
|
||||
(Client_notification)
|
||||
|
||||
let h_on_notification handler t n =
|
||||
Fiber.of_thunk (fun () ->
|
||||
match n with
|
||||
| Client_notification.Exit ->
|
||||
Log.log ~section:"server" (fun () -> Log.msg "received exit notification" []);
|
||||
let* () = stop t in
|
||||
Fiber.return (Jsonrpc_fiber.Notify.Stop, state t)
|
||||
| Client_notification.CancelRequest id -> handle_cancel_req t id
|
||||
| _ ->
|
||||
if t.state = Waiting_for_init
|
||||
then (
|
||||
let state = state t in
|
||||
Fiber.return (Jsonrpc_fiber.Notify.Continue, state))
|
||||
else
|
||||
let+ state = handler.h_on_notification t n in
|
||||
Jsonrpc_fiber.Notify.Continue, state)
|
||||
;;
|
||||
|
||||
let on_request handler t in_r =
|
||||
Fiber.of_thunk (fun () ->
|
||||
match Client_request.E in_r with
|
||||
| Client_request.E (Client_request.Initialize i) ->
|
||||
if t.state = Waiting_for_init
|
||||
then (
|
||||
let* result = handler.h_on_request.on_request t in_r in
|
||||
t.state <- Running;
|
||||
(* XXX Should we wait for the waiter of initialized to finish? *)
|
||||
let* () = Fiber.Ivar.fill t.initialized i in
|
||||
Fiber.return result)
|
||||
else (
|
||||
let code = Response.Error.Code.InvalidRequest in
|
||||
let message = "already initialized" in
|
||||
raise (Jsonrpc.Response.Error.E (Jsonrpc.Response.Error.make ~code ~message ())))
|
||||
| Client_request.E _ ->
|
||||
if t.state = Waiting_for_init
|
||||
then (
|
||||
let code = Response.Error.Code.ServerNotInitialized in
|
||||
let message = "not initialized" in
|
||||
raise (Jsonrpc.Response.Error.E (Jsonrpc.Response.Error.make ~code ~message ())))
|
||||
else handler.h_on_request.on_request t in_r)
|
||||
;;
|
||||
|
||||
let make (type s) (handler : s Handler.t) io (initial_state : s) =
|
||||
let h_on_request : _ Handler.on_request =
|
||||
{ Handler.on_request = (fun t x -> on_request handler t x) }
|
||||
in
|
||||
let h_on_notification = h_on_notification handler in
|
||||
make ~name:"server" h_on_request h_on_notification io initial_state
|
||||
;;
|
||||
|
||||
let start t = start_loop t
|
||||
end
|
||||
91
thirdparty/lsp/lsp-fiber/src/rpc.mli
vendored
Normal file
91
thirdparty/lsp/lsp-fiber/src/rpc.mli
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
(** * This encodes LSP RPC state machine. *)
|
||||
|
||||
open! Import
|
||||
|
||||
module Reply : sig
|
||||
type 'resp t
|
||||
|
||||
val now : 'r -> 'r t
|
||||
val later : (('r -> unit Fiber.t) -> unit Fiber.t) -> 'r t
|
||||
end
|
||||
|
||||
module type S = sig
|
||||
type 'a out_request
|
||||
type out_notification
|
||||
type 'a in_request
|
||||
type in_notification
|
||||
type 'state t
|
||||
|
||||
module Handler : sig
|
||||
type 'a session := 'a t
|
||||
|
||||
type 'state on_request =
|
||||
{ on_request : 'a. 'state session -> 'a in_request -> ('a Reply.t * 'state) Fiber.t
|
||||
}
|
||||
|
||||
type 'state t
|
||||
|
||||
val make
|
||||
: ?on_request:'state on_request
|
||||
-> ?on_notification:('state session -> in_notification -> 'state Fiber.t)
|
||||
-> unit
|
||||
-> 'state t
|
||||
end
|
||||
|
||||
val state : 'a t -> 'a
|
||||
val make : 'state Handler.t -> Fiber_io.t -> 'state -> 'state t
|
||||
val stop : 'state t -> unit Fiber.t
|
||||
val request : _ t -> 'resp out_request -> 'resp Fiber.t
|
||||
val notification : _ t -> out_notification -> unit Fiber.t
|
||||
|
||||
(** only available inside requests *)
|
||||
val cancel_token : unit -> Fiber.Cancel.t option Fiber.t
|
||||
|
||||
module Batch : sig
|
||||
type 'a session := 'a t
|
||||
type t
|
||||
|
||||
val create : _ session -> t
|
||||
val notification : t -> out_notification -> unit
|
||||
|
||||
type 'a response
|
||||
|
||||
val await : 'a response -> 'a Fiber.t
|
||||
val request : t -> 'resp out_request -> 'resp response
|
||||
val submit : t -> unit Fiber.t
|
||||
end
|
||||
end
|
||||
|
||||
module Client : sig
|
||||
open Types
|
||||
|
||||
include
|
||||
S
|
||||
with type 'a out_request = 'a Client_request.t
|
||||
and type out_notification = Client_notification.t
|
||||
and type 'a in_request = 'a Server_request.t
|
||||
and type in_notification = Server_notification.t
|
||||
|
||||
val request_with_cancel
|
||||
: _ t
|
||||
-> Fiber.Cancel.t
|
||||
-> 'resp out_request
|
||||
-> [ `Ok of 'resp | `Cancelled ] Fiber.t
|
||||
|
||||
val initialized : _ t -> InitializeResult.t Fiber.t
|
||||
val start : _ t -> InitializeParams.t -> unit Fiber.t
|
||||
end
|
||||
|
||||
module Server : sig
|
||||
open Types
|
||||
|
||||
include
|
||||
S
|
||||
with type 'a out_request = 'a Server_request.t
|
||||
and type out_notification = Server_notification.t
|
||||
and type 'a in_request = 'a Client_request.t
|
||||
and type in_notification = Client_notification.t
|
||||
|
||||
val initialized : _ t -> InitializeParams.t Fiber.t
|
||||
val start : _ t -> unit Fiber.t
|
||||
end
|
||||
1
thirdparty/lsp/lsp-fiber/src/server.ml
vendored
Normal file
1
thirdparty/lsp/lsp-fiber/src/server.ml
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
include Rpc.Server
|
||||
2
thirdparty/lsp/lsp-fiber/src/server.mli
vendored
Normal file
2
thirdparty/lsp/lsp-fiber/src/server.mli
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
open! Import
|
||||
include module type of Rpc.Server
|
||||
29
thirdparty/lsp/lsp-fiber/test/dune
vendored
Normal file
29
thirdparty/lsp/lsp-fiber/test/dune
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
; we cannot use the normal test alias because cinaps overtakes it
|
||||
|
||||
(library
|
||||
(name lsp_fiber_tests)
|
||||
(inline_tests)
|
||||
(preprocess
|
||||
(pps ppx_expect))
|
||||
(enabled_if
|
||||
(>= %{ocaml_version} 4.08))
|
||||
(libraries
|
||||
base
|
||||
fiber
|
||||
fiber_test
|
||||
lev
|
||||
lev_fiber
|
||||
jsonrpc
|
||||
jsonrpc_fiber
|
||||
lsp
|
||||
lsp_fiber
|
||||
;; This is because of the (implicit_transitive_deps false)
|
||||
;; in dune-project
|
||||
ppx_expect
|
||||
ppx_expect.config
|
||||
ppx_expect.config_types
|
||||
ppx_inline_test.config
|
||||
ppx_yojson_conv_lib
|
||||
stdune
|
||||
threads.posix
|
||||
yojson))
|
||||
223
thirdparty/lsp/lsp-fiber/test/lsp_fiber_test.ml
vendored
Normal file
223
thirdparty/lsp/lsp-fiber/test/lsp_fiber_test.ml
vendored
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
open Fiber.O
|
||||
open Lsp
|
||||
open Lsp.Types
|
||||
open Lsp_fiber
|
||||
|
||||
module Test = struct
|
||||
module Client = struct
|
||||
let run
|
||||
?(capabilities = ClientCapabilities.create ())
|
||||
?on_request
|
||||
?on_notification
|
||||
state
|
||||
(in_, out)
|
||||
=
|
||||
let initialize = InitializeParams.create ~capabilities () in
|
||||
let client =
|
||||
let stream_io = Lsp_fiber.Fiber_io.make in_ out in
|
||||
let handler = Client.Handler.make ?on_request ?on_notification () in
|
||||
Client.make handler stream_io state
|
||||
in
|
||||
client, Client.start client initialize
|
||||
;;
|
||||
end
|
||||
|
||||
module Server = struct
|
||||
let run ?on_request ?on_notification state (in_, out) =
|
||||
let server =
|
||||
let stream_io = Fiber_io.make in_ out in
|
||||
let handler = Server.Handler.make ?on_request ?on_notification () in
|
||||
Server.make handler stream_io state
|
||||
in
|
||||
server, Server.start server
|
||||
;;
|
||||
end
|
||||
end
|
||||
|
||||
let pipe () = Lev_fiber.Io.pipe ~cloexec:true ()
|
||||
|
||||
let test make_client make_server =
|
||||
Printexc.record_backtrace false;
|
||||
let run () =
|
||||
let* client_in, server_out = pipe () in
|
||||
let* server_in, client_out = pipe () in
|
||||
let server () = make_server (server_in, server_out) in
|
||||
let client () = make_client (client_in, client_out) in
|
||||
let+ () = Fiber.fork_and_join_unit server client in
|
||||
print_endline "Successful termination of test"
|
||||
in
|
||||
Lev_fiber.run run |> Lev_fiber.Error.ok_exn;
|
||||
print_endline "[TEST] finished"
|
||||
;;
|
||||
|
||||
let json_pp = Yojson.Safe.pretty_print ~std:false
|
||||
|
||||
module End_to_end_client = struct
|
||||
let on_request (type a) _ (_ : a Server_request.t) =
|
||||
Jsonrpc.Response.Error.raise
|
||||
(Jsonrpc.Response.Error.make ~message:"not implemented" ~code:InternalError ())
|
||||
;;
|
||||
|
||||
let on_notification (client : _ Client.t) n =
|
||||
let state = Client.state client in
|
||||
let received_notification = state in
|
||||
let req = Server_notification.to_jsonrpc n in
|
||||
Format.eprintf
|
||||
"client: received notification@.%a@.%!"
|
||||
json_pp
|
||||
(Jsonrpc.Notification.yojson_of_t req);
|
||||
let+ () = Fiber.Ivar.fill received_notification () in
|
||||
Format.eprintf "client: filled received_notification@.%!";
|
||||
state
|
||||
;;
|
||||
|
||||
let run io =
|
||||
let detached = Fiber.Pool.create () in
|
||||
let received_notification = Fiber.Ivar.create () in
|
||||
let client, running =
|
||||
let on_request = { Client.Handler.on_request } in
|
||||
Test.Client.run ~on_request ~on_notification received_notification io
|
||||
in
|
||||
let init () : unit Fiber.t =
|
||||
Format.eprintf "client: waiting for initialization@.%!";
|
||||
let* (_ : InitializeResult.t) = Client.initialized client in
|
||||
Format.eprintf "client: server initialized. sending request@.%!";
|
||||
let cancel = Fiber.Cancel.create () in
|
||||
let* () =
|
||||
Fiber.Pool.task detached ~f:(fun () ->
|
||||
Format.eprintf
|
||||
"client: waiting to receive notification before cancelling the request@.%!";
|
||||
let* () = Fiber.Ivar.read received_notification in
|
||||
Format.eprintf "client: received notification, cancelling the request@.%!";
|
||||
Fiber.Cancel.fire cancel)
|
||||
in
|
||||
let* res_cancel =
|
||||
let req_cancel =
|
||||
Client_request.ExecuteCommand
|
||||
(ExecuteCommandParams.create ~command:"cmd_cancel" ())
|
||||
in
|
||||
Format.eprintf "client: sending request cmd_cancel@.%!";
|
||||
Client.request_with_cancel client cancel req_cancel
|
||||
and* res_reply =
|
||||
let req_reply =
|
||||
Client_request.ExecuteCommand
|
||||
(ExecuteCommandParams.create ~command:"cmd_reply" ())
|
||||
in
|
||||
Format.eprintf "client: sending request cmd_reply@.%!";
|
||||
Client.request client req_reply
|
||||
in
|
||||
(match res_cancel with
|
||||
| `Cancelled -> Format.eprintf "client: req_cancel got cancelled@.%!"
|
||||
| `Ok _ -> assert false);
|
||||
Format.eprintf
|
||||
"client: Successfully executed req_reply with result:@.%a@."
|
||||
json_pp
|
||||
res_reply;
|
||||
Format.eprintf "client: sending request to shutdown@.%!";
|
||||
let* () = Fiber.Pool.stop detached in
|
||||
Client.notification client Exit
|
||||
in
|
||||
Fiber.fork_and_join_unit init (fun () ->
|
||||
Fiber.fork_and_join_unit (fun () -> running) (fun () -> Fiber.Pool.run detached))
|
||||
;;
|
||||
end
|
||||
|
||||
module End_to_end_server = struct
|
||||
type status =
|
||||
| Started
|
||||
| Initialized
|
||||
|
||||
let on_request =
|
||||
let on_request (type a) self (req : a Client_request.t) : (a Rpc.Reply.t * _) Fiber.t =
|
||||
let state = Server.state self in
|
||||
let _status, detached = state in
|
||||
match req with
|
||||
| Client_request.Initialize _ ->
|
||||
let capabilities = ServerCapabilities.create () in
|
||||
let result = InitializeResult.create ~capabilities () in
|
||||
Format.eprintf "server: initializing server@.";
|
||||
Format.eprintf "server: returning initialization result@.%!";
|
||||
Fiber.return (Rpc.Reply.now result, (Initialized, detached))
|
||||
| Client_request.ExecuteCommand { command; _ } ->
|
||||
Format.eprintf "server: received command %s@.%!" command;
|
||||
let* () =
|
||||
match command with
|
||||
| "cmd_cancel" ->
|
||||
Fiber.Pool.task detached ~f:(fun () ->
|
||||
Format.eprintf "server: sending message notification to client@.%!";
|
||||
let msg =
|
||||
ShowMessageParams.create
|
||||
~type_:MessageType.Info
|
||||
~message:"notifying client"
|
||||
in
|
||||
Server.notification self (Server_notification.ShowMessage msg))
|
||||
| _ -> Fiber.return ()
|
||||
in
|
||||
let* () = Fiber.Pool.stop detached in
|
||||
let result = `String "successful execution" in
|
||||
let* cancel = Rpc.Server.cancel_token () in
|
||||
(match command with
|
||||
| "cmd_cancel" ->
|
||||
let+ () = Lev_fiber.Timer.sleepf 0.2 in
|
||||
( Rpc.Reply.later (fun k ->
|
||||
let* cancel = Rpc.Server.cancel_token () in
|
||||
(* Make sure that we can access the cancel token in a Reply
|
||||
response *)
|
||||
assert (Option.is_some cancel);
|
||||
k result)
|
||||
, state )
|
||||
| _ ->
|
||||
(* Make sure that we can access the cancel token in a Now response *)
|
||||
assert (Option.is_some cancel);
|
||||
Fiber.return (Rpc.Reply.now result, state))
|
||||
| _ ->
|
||||
Jsonrpc.Response.Error.raise
|
||||
(Jsonrpc.Response.Error.make ~code:InternalError ~message:"not supported" ())
|
||||
in
|
||||
{ Server.Handler.on_request }
|
||||
;;
|
||||
|
||||
let on_notification self _ =
|
||||
let state = Server.state self in
|
||||
Format.eprintf "server: Received notification@.%!";
|
||||
Fiber.return state
|
||||
;;
|
||||
|
||||
let run io =
|
||||
let detached = Fiber.Pool.create () in
|
||||
let _server, running =
|
||||
Test.Server.run ~on_request ~on_notification (Started, detached) io
|
||||
in
|
||||
Fiber.fork_and_join_unit (fun () -> running) (fun () -> Fiber.Pool.run detached)
|
||||
;;
|
||||
end
|
||||
|
||||
let%expect_test "end to end run of lsp tests" =
|
||||
test End_to_end_client.run End_to_end_server.run;
|
||||
[%expect
|
||||
{|
|
||||
client: waiting for initialization
|
||||
server: initializing server
|
||||
server: returning initialization result
|
||||
client: server initialized. sending request
|
||||
client: sending request cmd_cancel
|
||||
client: sending request cmd_reply
|
||||
client: waiting to receive notification before cancelling the request
|
||||
server: received command cmd_cancel
|
||||
server: sending message notification to client
|
||||
client: received notification
|
||||
{
|
||||
"params": { "message": "notifying client", "type": 3 },
|
||||
"method": "window/showMessage",
|
||||
"jsonrpc": "2.0"
|
||||
}
|
||||
client: filled received_notification
|
||||
client: received notification, cancelling the request
|
||||
server: received command cmd_reply
|
||||
client: req_cancel got cancelled
|
||||
client: Successfully executed req_reply with result:
|
||||
"successful execution"
|
||||
client: sending request to shutdown
|
||||
Successful termination of test
|
||||
[TEST] finished |}]
|
||||
;;
|
||||
0
thirdparty/lsp/lsp-fiber/test/lsp_fiber_test.mli
vendored
Normal file
0
thirdparty/lsp/lsp-fiber/test/lsp_fiber_test.mli
vendored
Normal file
52
thirdparty/lsp/lsp.opam
vendored
Normal file
52
thirdparty/lsp/lsp.opam
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
synopsis: "LSP protocol implementation in OCaml"
|
||||
description: """
|
||||
|
||||
Implementation of the LSP protocol in OCaml. It is designed to be as portable as
|
||||
possible and does not make any assumptions about IO.
|
||||
"""
|
||||
maintainer: ["Rudi Grinberg <me@rgrinberg.com>"]
|
||||
authors: [
|
||||
"Andrey Popp <8mayday@gmail.com>"
|
||||
"Rusty Key <iam@stfoo.ru>"
|
||||
"Louis Roché <louis@louisroche.net>"
|
||||
"Oleksiy Golovko <alexei.golovko@gmail.com>"
|
||||
"Rudi Grinberg <me@rgrinberg.com>"
|
||||
"Sacha Ayoun <sachaayoun@gmail.com>"
|
||||
"cannorin <cannorin@gmail.com>"
|
||||
"Ulugbek Abdullaev <ulugbekna@gmail.com>"
|
||||
"Thibaut Mattio <thibaut.mattio@gmail.com>"
|
||||
"Max Lantas <mnxndev@outlook.com>"
|
||||
]
|
||||
license: "ISC"
|
||||
homepage: "https://github.com/ocaml/ocaml-lsp"
|
||||
bug-reports: "https://github.com/ocaml/ocaml-lsp/issues"
|
||||
depends: [
|
||||
"dune" {>= "3.0"}
|
||||
"jsonrpc" {= version}
|
||||
"yojson"
|
||||
"ppx_yojson_conv_lib" {>= "v0.14"}
|
||||
"cinaps" {with-test}
|
||||
"ppx_expect" {>= "v0.17.0" & with-test}
|
||||
"uutf" {>= "1.0.2"}
|
||||
"odoc" {with-doc}
|
||||
"ocaml" {>= "4.14"}
|
||||
"ppx_yojson_conv" {with-dev-setup}
|
||||
]
|
||||
dev-repo: "git+https://github.com/ocaml/ocaml-lsp.git"
|
||||
build: [
|
||||
["dune" "subst"] {dev}
|
||||
[
|
||||
"dune"
|
||||
"build"
|
||||
"-p"
|
||||
name
|
||||
"-j"
|
||||
jobs
|
||||
"@install"
|
||||
"@doc" {with-doc}
|
||||
]
|
||||
]
|
||||
|
||||
x-maintenance-intent: [ "(latest)" "(latest)-414" ]
|
||||
15
thirdparty/lsp/lsp.opam.template
vendored
Normal file
15
thirdparty/lsp/lsp.opam.template
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
build: [
|
||||
["dune" "subst"] {dev}
|
||||
[
|
||||
"dune"
|
||||
"build"
|
||||
"-p"
|
||||
name
|
||||
"-j"
|
||||
jobs
|
||||
"@install"
|
||||
"@doc" {with-doc}
|
||||
]
|
||||
]
|
||||
|
||||
x-maintenance-intent: [ "(latest)" "(latest)-414" ]
|
||||
178
thirdparty/lsp/lsp/bin/cinaps.ml
vendored
Normal file
178
thirdparty/lsp/lsp/bin/cinaps.ml
vendored
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
open Import
|
||||
|
||||
let preprocess_metamodel =
|
||||
object (self)
|
||||
inherit Metamodel.map as super
|
||||
|
||||
method! or_ path (types : Metamodel.type_ list) =
|
||||
match
|
||||
List.filter_map types ~f:(function
|
||||
| Literal (Record []) -> None
|
||||
| _ as t -> Some (self#type_ path t))
|
||||
with
|
||||
| [] -> assert false
|
||||
| [ t ] -> t
|
||||
| [ Metamodel.Literal (Record f1); Literal (Record f2) ] as ts ->
|
||||
(match path with
|
||||
| Top (Alias s) when s.name = "TextDocumentContentChangeEvent" ->
|
||||
let t =
|
||||
let union_fields l1 l2 ~f =
|
||||
let of_map =
|
||||
String.Map.of_list_map_exn ~f:(fun (x : Metamodel.property) -> x.name, x)
|
||||
in
|
||||
String.Map.merge (of_map l1) (of_map l2) ~f |> String.Map.values
|
||||
in
|
||||
union_fields f1 f2 ~f:(fun k t1 t2 ->
|
||||
if k = "text"
|
||||
then t1
|
||||
else if k = "range"
|
||||
then (
|
||||
match t1, t2 with
|
||||
| None, Some s | Some s, None ->
|
||||
assert (not s.optional);
|
||||
Some { s with optional = true }
|
||||
| None, None | Some _, Some _ -> assert false)
|
||||
else (
|
||||
match t1, t2 with
|
||||
| None, None -> assert false
|
||||
| Some s, None | None, Some s -> Some s
|
||||
| Some _, Some _ -> assert false))
|
||||
in
|
||||
self#type_ path (Metamodel.Literal (Record t))
|
||||
| _ -> super#or_ path ts)
|
||||
| ts -> super#or_ path ts
|
||||
|
||||
method! property path (p : Metamodel.property) =
|
||||
let update_type type_ =
|
||||
let type_ = self#type_ path type_ in
|
||||
super#property path { p with type_ }
|
||||
in
|
||||
let open Metamodel.Path in
|
||||
match path with
|
||||
| Top (Structure s)
|
||||
when p.name = "trace"
|
||||
&& (s.name = "_InitializeParams" || s.name = "InitializeParams") ->
|
||||
update_type (Reference "TraceValues")
|
||||
| Top (Structure s) when p.name = "location" && s.name = "WorkspaceSymbol" ->
|
||||
(match p.type_ with
|
||||
| Or [ type_; _ ] -> update_type type_
|
||||
| _ -> assert false)
|
||||
| _ -> super#property path p
|
||||
|
||||
method! enumeration m =
|
||||
match m.name = "TraceValues" with
|
||||
| false -> super#enumeration m
|
||||
| true ->
|
||||
super#enumeration
|
||||
(let values =
|
||||
let compact : Metamodel.enumerationEntry =
|
||||
{ name = "Compact"
|
||||
; value = `String "compact"
|
||||
; doc = { since = None; documentation = None }
|
||||
}
|
||||
in
|
||||
compact :: m.values
|
||||
in
|
||||
{ m with values })
|
||||
end
|
||||
;;
|
||||
|
||||
let expand_superclasses db (m : Metamodel.t) =
|
||||
let structures =
|
||||
let uniquify_fields fields =
|
||||
List.fold_left fields ~init:String.Map.empty ~f:(fun acc (f : Metamodel.property) ->
|
||||
String.Map.set acc f.name f)
|
||||
|> String.Map.values
|
||||
in
|
||||
let rec fields_of_type (t : Metamodel.type_) =
|
||||
match t with
|
||||
| Reference s ->
|
||||
(match Metamodel.Entity.DB.find db s with
|
||||
| Structure s -> fields_of_structure s
|
||||
| Enumeration _ -> assert false
|
||||
| Alias a -> fields_of_type a.type_)
|
||||
| _ -> assert false
|
||||
and fields_of_structure (s : Metamodel.structure) =
|
||||
let fields = List.map (s.extends @ s.mixins) ~f:fields_of_type @ [ s.properties ] in
|
||||
List.concat fields
|
||||
in
|
||||
List.map m.structures ~f:(fun s ->
|
||||
let properties = fields_of_structure s |> uniquify_fields in
|
||||
{ s with properties })
|
||||
in
|
||||
{ m with structures }
|
||||
;;
|
||||
|
||||
let ocaml =
|
||||
lazy
|
||||
(Metamodel_lsp.t ()
|
||||
|> preprocess_metamodel#t
|
||||
|> (fun metamodel ->
|
||||
let db = Metamodel.Entity.DB.create metamodel in
|
||||
expand_superclasses db metamodel)
|
||||
|> Typescript.of_metamodel
|
||||
|> Ocaml.of_typescript)
|
||||
;;
|
||||
|
||||
module Output = struct
|
||||
open Ocaml
|
||||
|
||||
type t =
|
||||
{ mutable modules : Module.t list
|
||||
; kind : Ml.Kind.t
|
||||
; out : out_channel
|
||||
}
|
||||
|
||||
let create modules kind out_channel = { modules; out = out_channel; kind }
|
||||
|
||||
let module_name (t : t) (m : Module.t) =
|
||||
match t.kind with
|
||||
| Intf -> (m.intf.name :> string)
|
||||
| Impl -> (m.impl.name :> string)
|
||||
;;
|
||||
|
||||
let _skip (t : t) name =
|
||||
match t.modules with
|
||||
| [] -> failwith "non left to skip"
|
||||
| m :: modules ->
|
||||
let name' = module_name t m in
|
||||
assert (String.equal name name');
|
||||
t.modules <- modules
|
||||
;;
|
||||
|
||||
let pp_file pp ch =
|
||||
let fmt = Format.formatter_of_out_channel ch in
|
||||
Pp.to_fmt fmt pp;
|
||||
Format.pp_print_flush fmt ()
|
||||
;;
|
||||
|
||||
let write t cmd =
|
||||
let to_write, modules =
|
||||
match cmd with
|
||||
| `Finish -> t.modules, []
|
||||
| `Until m ->
|
||||
let rec loop xs acc =
|
||||
match xs with
|
||||
| [] -> List.rev acc, []
|
||||
| x :: xs ->
|
||||
if module_name t x = m then List.rev acc, x :: xs else loop xs (x :: acc)
|
||||
in
|
||||
loop t.modules []
|
||||
in
|
||||
t.modules <- modules;
|
||||
List.iter to_write ~f:(fun m ->
|
||||
let pp = Module.pp m in
|
||||
let pp = Ml.Kind.Map.get pp t.kind in
|
||||
pp_file pp t.out)
|
||||
;;
|
||||
end
|
||||
|
||||
let print_ml () =
|
||||
let output = Output.create (Lazy.force ocaml) Ml.Kind.Impl stdout in
|
||||
Output.write output `Finish
|
||||
;;
|
||||
|
||||
let print_mli () =
|
||||
let output = Output.create (Lazy.force ocaml) Ml.Kind.Intf stdout in
|
||||
Output.write output `Finish
|
||||
;;
|
||||
2
thirdparty/lsp/lsp/bin/cinaps.mli
vendored
Normal file
2
thirdparty/lsp/lsp/bin/cinaps.mli
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
val print_ml : unit -> unit
|
||||
val print_mli : unit -> unit
|
||||
16
thirdparty/lsp/lsp/bin/dune
vendored
Normal file
16
thirdparty/lsp/lsp/bin/dune
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
(include_subdirs unqualified)
|
||||
|
||||
(test
|
||||
(name test_metamodel)
|
||||
(modules test_metamodel)
|
||||
(libraries stdune yojson lsp_gen)
|
||||
(deps metamodel/metaModel.json)
|
||||
(action
|
||||
(run ./test_metamodel.exe %{deps})))
|
||||
|
||||
(library
|
||||
(name lsp_gen)
|
||||
(instrumentation
|
||||
(backend bisect_ppx))
|
||||
(modules :standard \ test_metamodel)
|
||||
(libraries stdune dyn pp yojson))
|
||||
13
thirdparty/lsp/lsp/bin/import.ml
vendored
Normal file
13
thirdparty/lsp/lsp/bin/import.ml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
include struct
|
||||
open Stdune
|
||||
module List = List
|
||||
module Id = Id
|
||||
module String = String
|
||||
module Code_error = Code_error
|
||||
module Comparable = Comparable
|
||||
module Top_closure = Top_closure
|
||||
module Poly = Poly
|
||||
module Option = Option
|
||||
|
||||
let sprintf = sprintf
|
||||
end
|
||||
7
thirdparty/lsp/lsp/bin/lsp_gen.ml
vendored
Normal file
7
thirdparty/lsp/lsp/bin/lsp_gen.ml
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
module Typescript = Typescript
|
||||
module Ocaml = Ocaml
|
||||
module Cinaps = Cinaps
|
||||
module Metamodel = Metamodel
|
||||
|
||||
let print_ml = Cinaps.print_ml
|
||||
let print_mli = Cinaps.print_mli
|
||||
9
thirdparty/lsp/lsp/bin/metamodel/dune
vendored
Normal file
9
thirdparty/lsp/lsp/bin/metamodel/dune
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
; get rid of this gross hack once dune has proper crunch support
|
||||
|
||||
(rule
|
||||
(with-stdout-to
|
||||
metamodel_lsp.ml
|
||||
(progn
|
||||
(echo "let t () = Metamodel.t @@ Yojson.Safe.from_string {json|")
|
||||
(echo "%{read:metaModel.json}")
|
||||
(echo "|json}"))))
|
||||
14920
thirdparty/lsp/lsp/bin/metamodel/metaModel.json
vendored
Normal file
14920
thirdparty/lsp/lsp/bin/metamodel/metaModel.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
458
thirdparty/lsp/lsp/bin/metamodel/metamodel.ml
vendored
Normal file
458
thirdparty/lsp/lsp/bin/metamodel/metamodel.ml
vendored
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
open Stdune
|
||||
|
||||
type doc =
|
||||
{ since : string option
|
||||
; documentation : string option
|
||||
}
|
||||
|
||||
type baseType =
|
||||
| Uri
|
||||
| DocumentUri
|
||||
| Integer
|
||||
| Uinteger
|
||||
| Decimal
|
||||
| RegExp
|
||||
| String
|
||||
| Boolean
|
||||
| Null
|
||||
|
||||
type mapKeyType =
|
||||
| Uri
|
||||
| DocumentUri
|
||||
| String
|
||||
| Integer
|
||||
| Reference of string
|
||||
|
||||
type literalType =
|
||||
| String of string
|
||||
| Boolean of bool
|
||||
| Integer of int
|
||||
| Record of property list
|
||||
|
||||
and property =
|
||||
{ doc : doc
|
||||
; name : string
|
||||
; optional : bool
|
||||
; type_ : type_
|
||||
}
|
||||
|
||||
and mapType =
|
||||
{ key : mapKeyType
|
||||
; value : type_
|
||||
}
|
||||
|
||||
and type_ =
|
||||
| Base of baseType
|
||||
| Reference of string
|
||||
| Array of type_
|
||||
| Or of type_ list
|
||||
| And of type_ list
|
||||
| Tuple of type_ list
|
||||
| Literal of literalType
|
||||
| Map of mapType
|
||||
|
||||
type typeAlias =
|
||||
{ name : string
|
||||
; type_ : type_
|
||||
; doc : doc
|
||||
}
|
||||
|
||||
type enumerationEntry =
|
||||
{ name : string
|
||||
; value : [ `String of string | `Int of int ]
|
||||
; doc : doc
|
||||
}
|
||||
|
||||
type enumerationType = { name : [ `String | `Integer | `Uinteger ] }
|
||||
|
||||
type enumeration =
|
||||
{ doc : doc
|
||||
; name : string
|
||||
; supportsCustomValues : bool
|
||||
; type_ : enumerationType
|
||||
; values : enumerationEntry list
|
||||
}
|
||||
|
||||
type structure =
|
||||
{ doc : doc
|
||||
; extends : type_ list
|
||||
; mixins : type_ list
|
||||
; name : string
|
||||
; properties : property list
|
||||
}
|
||||
|
||||
type call =
|
||||
{ method_ : string
|
||||
; params : [ `Param of type_ | `Params of type_ list ] option
|
||||
; registrationOptions : type_ option
|
||||
; doc : doc
|
||||
}
|
||||
|
||||
type request =
|
||||
{ call : call
|
||||
; errorData : type_ option
|
||||
; partialResult : type_ option
|
||||
; result : type_
|
||||
}
|
||||
|
||||
type notification = { call : call }
|
||||
|
||||
type t =
|
||||
{ requests : request list
|
||||
; notifications : notification list
|
||||
; structures : structure list
|
||||
; enumerations : enumeration list
|
||||
; typeAliases : typeAlias list
|
||||
}
|
||||
|
||||
let error msg json = failwith (msg ^ "\n" ^ Yojson.Safe.pretty_to_string ~std:false json)
|
||||
|
||||
let fields = function
|
||||
| `Assoc xs -> xs
|
||||
| xs -> error "expected fields" xs
|
||||
;;
|
||||
|
||||
let field ?default (name : string) p fields =
|
||||
match List.assoc fields name with
|
||||
| Some f -> p f
|
||||
| None ->
|
||||
(match default with
|
||||
| None -> error ("field not found " ^ name) (`Assoc fields)
|
||||
| Some x -> x)
|
||||
;;
|
||||
|
||||
let field_o name p fields =
|
||||
match List.assoc fields name with
|
||||
| None -> None
|
||||
| Some f -> Some (p f)
|
||||
;;
|
||||
|
||||
let bool = function
|
||||
| `Bool b -> b
|
||||
| json -> error "boolean expected" json
|
||||
;;
|
||||
|
||||
let literal lit json = if not (Poly.equal json lit) then error "unexpected literal" json
|
||||
|
||||
let enum variants json =
|
||||
match json with
|
||||
| `String s ->
|
||||
(match List.assoc variants s with
|
||||
| None -> error "not a valid enum value" json
|
||||
| Some v -> v)
|
||||
| _ -> error "not a valid enum value" json
|
||||
;;
|
||||
|
||||
let string = function
|
||||
| `String s -> s
|
||||
| json -> error "expected string" json
|
||||
;;
|
||||
|
||||
let string_or_number = function
|
||||
| `String s -> `String s
|
||||
| `Int i -> `Int i
|
||||
| json -> error "expected string or number" json
|
||||
;;
|
||||
|
||||
let name fields = field "name" string fields
|
||||
|
||||
let list conv = function
|
||||
| `List xs -> List.map xs ~f:conv
|
||||
| json -> error "expected list" json
|
||||
;;
|
||||
|
||||
let baseType json : baseType =
|
||||
match json with
|
||||
| `String s ->
|
||||
(match s with
|
||||
| "URI" | "Uri" -> Uri
|
||||
| "DocumentUri" -> DocumentUri
|
||||
| "integer" -> Integer
|
||||
| "uinteger" -> Uinteger
|
||||
| "decimal" -> Decimal
|
||||
| "RegExp" -> RegExp
|
||||
| "string" -> String
|
||||
| "boolean" -> Boolean
|
||||
| "null" -> Null
|
||||
| _ -> error "unknown base type" json)
|
||||
| _ -> error "unknown base type" json
|
||||
;;
|
||||
|
||||
let mapKeyType json : mapKeyType =
|
||||
let fields = fields json in
|
||||
let kind = field "kind" string fields in
|
||||
match kind with
|
||||
| "reference" -> Reference (name fields)
|
||||
| "base" ->
|
||||
field
|
||||
"name"
|
||||
(enum
|
||||
[ "Uri", Uri
|
||||
; "URI", Uri
|
||||
; "DocumentUri", DocumentUri
|
||||
; "string", String
|
||||
; "integer", Integer
|
||||
])
|
||||
fields
|
||||
| kind -> error ("invalid kind for map key type: " ^ kind) json
|
||||
;;
|
||||
|
||||
let doc fields =
|
||||
let since = field_o "since" string fields in
|
||||
let documentation = field_o "documentation" string fields in
|
||||
{ since; documentation }
|
||||
;;
|
||||
|
||||
let rec type_ json =
|
||||
let fields_conv = fields in
|
||||
let fields = fields json in
|
||||
let kind = field "kind" string fields in
|
||||
match kind with
|
||||
| "reference" -> Reference (name fields)
|
||||
| "array" ->
|
||||
let element = field "element" type_ fields in
|
||||
Array element
|
||||
| "base" ->
|
||||
let b = field "name" baseType fields in
|
||||
Base b
|
||||
| "or" ->
|
||||
let items = field "items" (list type_) fields in
|
||||
Or items
|
||||
| "and" ->
|
||||
let items = field "items" (list type_) fields in
|
||||
And items
|
||||
| "tuple" ->
|
||||
let items = field "items" (list type_) fields in
|
||||
Tuple items
|
||||
| "stringLiteral" ->
|
||||
let value = field "value" string fields in
|
||||
Literal (String value)
|
||||
| "map" ->
|
||||
let key = field "key" mapKeyType fields in
|
||||
let value = field "value" type_ fields in
|
||||
Map { key; value }
|
||||
| "literal" ->
|
||||
let fields =
|
||||
field
|
||||
"value"
|
||||
(fun json ->
|
||||
let fields = fields_conv json in
|
||||
properties fields)
|
||||
fields
|
||||
in
|
||||
Literal (Record fields)
|
||||
| kind -> error "unrecognized kind" (`String kind)
|
||||
|
||||
and properties fields : property list = field "properties" (list property) fields
|
||||
|
||||
and property json : property =
|
||||
let fields = fields json in
|
||||
let name = name fields in
|
||||
let doc = doc fields in
|
||||
let type_ = type_field fields in
|
||||
let optional = field ~default:false "optional" bool fields in
|
||||
{ name; type_; optional; doc }
|
||||
|
||||
and type_field fields = field "type" type_ fields
|
||||
|
||||
let params = function
|
||||
| `List l -> `Params (List.map l ~f:type_)
|
||||
| `Assoc _ as json -> `Param (type_ json)
|
||||
| json -> error "list or object expected" json
|
||||
;;
|
||||
|
||||
let call fields =
|
||||
let method_ = field "method" string fields in
|
||||
let params = field_o "params" params fields in
|
||||
let doc = doc fields in
|
||||
let registrationOptions = field_o "registrationOptions" type_ fields in
|
||||
{ registrationOptions; doc; method_; params }
|
||||
;;
|
||||
|
||||
let notification json =
|
||||
let fields = fields json in
|
||||
let call = call fields in
|
||||
{ call }
|
||||
;;
|
||||
|
||||
let request json =
|
||||
let fields = fields json in
|
||||
let call = call fields in
|
||||
let errorData = field_o "errorData" type_ fields in
|
||||
let partialResult = field_o "partialResult" type_ fields in
|
||||
let result = field "result" type_ fields in
|
||||
{ call; errorData; partialResult; result }
|
||||
;;
|
||||
|
||||
let enumerationEntry json : enumerationEntry =
|
||||
let fields = fields json in
|
||||
let name = name fields in
|
||||
let doc = doc fields in
|
||||
let value = field "value" string_or_number fields in
|
||||
{ name; value; doc }
|
||||
;;
|
||||
|
||||
let enumerationType json =
|
||||
let fields = fields json in
|
||||
let () = field "kind" (literal (`String "base")) fields in
|
||||
let name =
|
||||
field
|
||||
"name"
|
||||
(enum [ "integer", `Integer; "string", `String; "uinteger", `Uinteger ])
|
||||
fields
|
||||
in
|
||||
{ name }
|
||||
;;
|
||||
|
||||
let enumeration json =
|
||||
let fields = fields json in
|
||||
let name = name fields in
|
||||
let doc = doc fields in
|
||||
let values = field "values" (list enumerationEntry) fields in
|
||||
let type_ = field "type" enumerationType fields in
|
||||
let supportsCustomValues = field ~default:false "supportsCustomValues" bool fields in
|
||||
{ supportsCustomValues; type_; values; name; doc }
|
||||
;;
|
||||
|
||||
let structure json =
|
||||
let fields = fields json in
|
||||
let doc = doc fields in
|
||||
let name = name fields in
|
||||
let extends = field ~default:[] "extends" (list type_) fields in
|
||||
let mixins = field ~default:[] "mixins" (list type_) fields in
|
||||
let properties = properties fields in
|
||||
{ doc; name; extends; mixins; properties }
|
||||
;;
|
||||
|
||||
let typeAlias json : typeAlias =
|
||||
let fields = fields json in
|
||||
let name = name fields in
|
||||
let type_ = type_field fields in
|
||||
let doc = doc fields in
|
||||
{ doc; name; type_ }
|
||||
;;
|
||||
|
||||
let t json =
|
||||
let fields = fields json in
|
||||
let requests = field "requests" (list request) fields in
|
||||
let notifications = field "notifications" (list notification) fields in
|
||||
let structures = field "structures" (list structure) fields in
|
||||
let enumerations = field "enumerations" (list enumeration) fields in
|
||||
let typeAliases = field "typeAliases" (list typeAlias) fields in
|
||||
{ requests; notifications; structures; enumerations; typeAliases }
|
||||
;;
|
||||
|
||||
type metamodel = t
|
||||
|
||||
module Entity = struct
|
||||
type t =
|
||||
| Structure of structure
|
||||
| Enumeration of enumeration
|
||||
| Alias of typeAlias
|
||||
|
||||
module DB = struct
|
||||
type nonrec t = t String.Map.t
|
||||
|
||||
let create
|
||||
({ structures; requests = _; notifications = _; enumerations; typeAliases } :
|
||||
metamodel)
|
||||
: t
|
||||
=
|
||||
let structures =
|
||||
String.Map.of_list_map_exn structures ~f:(fun s -> s.name, Structure s)
|
||||
in
|
||||
let enumerations =
|
||||
String.Map.of_list_map_exn enumerations ~f:(fun s -> s.name, Enumeration s)
|
||||
in
|
||||
let typeAliases =
|
||||
String.Map.of_list_map_exn typeAliases ~f:(fun a -> a.name, Alias a)
|
||||
in
|
||||
String.Map.union_exn structures enumerations |> String.Map.union_exn typeAliases
|
||||
;;
|
||||
|
||||
let find t x = String.Map.find_exn t x
|
||||
end
|
||||
end
|
||||
|
||||
module Path = struct
|
||||
type top =
|
||||
| Request of request
|
||||
| Notification of notification
|
||||
| Structure of structure
|
||||
| Enumeration of enumeration
|
||||
| Alias of typeAlias
|
||||
|
||||
type t =
|
||||
| Top of top
|
||||
| Property of property * t
|
||||
end
|
||||
|
||||
class map =
|
||||
let open Path in
|
||||
object (self)
|
||||
method property path (p : property) =
|
||||
let path = Property (p, path) in
|
||||
{ p with type_ = self#type_ path p.type_ }
|
||||
|
||||
method literal path t =
|
||||
match (t : literalType) with
|
||||
| Record ps -> Record (List.map ps ~f:(self#property path))
|
||||
| _ -> t
|
||||
|
||||
method or_ path types = Or (List.map types ~f:(self#type_ path))
|
||||
|
||||
method type_ path t : type_ =
|
||||
match t with
|
||||
| Base _ as t -> t
|
||||
| Reference _ -> t
|
||||
| Array t -> Array (self#type_ path t)
|
||||
| Or types -> self#or_ path types
|
||||
| And ts -> And (List.map ts ~f:(self#type_ path))
|
||||
| Tuple ts -> Tuple (List.map ts ~f:(self#type_ path))
|
||||
| Literal lt -> Literal (self#literal path lt)
|
||||
| Map mt -> Map { mt with value = self#type_ path mt.value }
|
||||
|
||||
method private call path (c : call) =
|
||||
let params =
|
||||
let params = function
|
||||
| `Param t -> `Param (self#type_ path t)
|
||||
| `Params ts -> `Params (List.map ts ~f:(self#type_ path))
|
||||
in
|
||||
Option.map ~f:params c.params
|
||||
in
|
||||
let registrationOptions = Option.map ~f:(self#type_ path) c.registrationOptions in
|
||||
{ c with params; registrationOptions }
|
||||
|
||||
method request (r : request) =
|
||||
let path = Top (Request r) in
|
||||
let call = self#call path r.call in
|
||||
let errorData = Option.map ~f:(self#type_ path) r.errorData in
|
||||
let partialResult = Option.map ~f:(self#type_ path) r.partialResult in
|
||||
let result = self#type_ path r.result in
|
||||
{ call; errorData; partialResult; result }
|
||||
|
||||
method notification { call } =
|
||||
let path = Top (Notification { call }) in
|
||||
{ call = self#call path call }
|
||||
|
||||
method structure s =
|
||||
let path = Top (Structure s) in
|
||||
let extends = List.map s.extends ~f:(self#type_ path) in
|
||||
let mixins = List.map s.mixins ~f:(self#type_ path) in
|
||||
let properties = List.map s.properties ~f:(self#property path) in
|
||||
{ s with extends; mixins; properties }
|
||||
|
||||
method typeAlias (a : typeAlias) =
|
||||
let path = Top (Alias a) in
|
||||
{ a with type_ = self#type_ path a.type_ }
|
||||
|
||||
method enumeration (e : enumeration) : enumeration = e
|
||||
|
||||
method t { requests; notifications; structures; enumerations; typeAliases } =
|
||||
let requests = List.map requests ~f:self#request in
|
||||
let notifications = List.map notifications ~f:self#notification in
|
||||
let structures = List.map structures ~f:self#structure in
|
||||
let typeAliases = List.map typeAliases ~f:self#typeAlias in
|
||||
let enumerations = List.map enumerations ~f:self#enumeration in
|
||||
{ enumerations; requests; notifications; structures; typeAliases }
|
||||
end
|
||||
149
thirdparty/lsp/lsp/bin/metamodel/metamodel.mli
vendored
Normal file
149
thirdparty/lsp/lsp/bin/metamodel/metamodel.mli
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
type doc =
|
||||
{ since : string option
|
||||
; documentation : string option
|
||||
}
|
||||
|
||||
type baseType =
|
||||
| Uri
|
||||
| DocumentUri
|
||||
| Integer
|
||||
| Uinteger
|
||||
| Decimal
|
||||
| RegExp
|
||||
| String
|
||||
| Boolean
|
||||
| Null
|
||||
|
||||
type mapKeyType =
|
||||
| Uri
|
||||
| DocumentUri
|
||||
| String
|
||||
| Integer
|
||||
| Reference of string
|
||||
|
||||
type literalType =
|
||||
| String of string
|
||||
| Boolean of bool
|
||||
| Integer of int
|
||||
| Record of property list
|
||||
|
||||
and property =
|
||||
{ doc : doc
|
||||
; name : string
|
||||
; optional : bool
|
||||
; type_ : type_
|
||||
}
|
||||
|
||||
and mapType =
|
||||
{ key : mapKeyType
|
||||
; value : type_
|
||||
}
|
||||
|
||||
and type_ =
|
||||
| Base of baseType
|
||||
| Reference of string
|
||||
| Array of type_
|
||||
| Or of type_ list
|
||||
| And of type_ list
|
||||
| Tuple of type_ list
|
||||
| Literal of literalType
|
||||
| Map of mapType
|
||||
|
||||
type typeAlias =
|
||||
{ name : string
|
||||
; type_ : type_
|
||||
; doc : doc
|
||||
}
|
||||
|
||||
type enumerationEntry =
|
||||
{ name : string
|
||||
; value : [ `Int of int | `String of string ]
|
||||
; doc : doc
|
||||
}
|
||||
|
||||
type enumerationType = { name : [ `Integer | `String | `Uinteger ] }
|
||||
|
||||
type enumeration =
|
||||
{ doc : doc
|
||||
; name : string
|
||||
; supportsCustomValues : bool
|
||||
; type_ : enumerationType
|
||||
; values : enumerationEntry list
|
||||
}
|
||||
|
||||
type structure =
|
||||
{ doc : doc
|
||||
; extends : type_ list
|
||||
; mixins : type_ list
|
||||
; name : string
|
||||
; properties : property list
|
||||
}
|
||||
|
||||
type call =
|
||||
{ method_ : string
|
||||
; params : [ `Param of type_ | `Params of type_ list ] option
|
||||
; registrationOptions : type_ option
|
||||
; doc : doc
|
||||
}
|
||||
|
||||
type request =
|
||||
{ call : call
|
||||
; errorData : type_ option
|
||||
; partialResult : type_ option
|
||||
; result : type_
|
||||
}
|
||||
|
||||
type notification = { call : call }
|
||||
|
||||
type t =
|
||||
{ requests : request list
|
||||
; notifications : notification list
|
||||
; structures : structure list
|
||||
; enumerations : enumeration list
|
||||
; typeAliases : typeAlias list
|
||||
}
|
||||
|
||||
val t : Yojson.Safe.t -> t
|
||||
|
||||
module Entity : sig
|
||||
type metamodel := t
|
||||
|
||||
type t =
|
||||
| Structure of structure
|
||||
| Enumeration of enumeration
|
||||
| Alias of typeAlias
|
||||
|
||||
module DB : sig
|
||||
type entity := t
|
||||
type t
|
||||
|
||||
val create : metamodel -> t
|
||||
val find : t -> string -> entity
|
||||
end
|
||||
end
|
||||
|
||||
module Path : sig
|
||||
type top =
|
||||
| Request of request
|
||||
| Notification of notification
|
||||
| Structure of structure
|
||||
| Enumeration of enumeration
|
||||
| Alias of typeAlias
|
||||
|
||||
type t =
|
||||
| Top of top
|
||||
| Property of property * t
|
||||
end
|
||||
|
||||
class map : object
|
||||
method literal : Path.t -> literalType -> literalType
|
||||
method property : Path.t -> property -> property
|
||||
method or_ : Path.t -> type_ list -> type_
|
||||
method type_ : Path.t -> type_ -> type_
|
||||
method t : t -> t
|
||||
method request : request -> request
|
||||
method structure : structure -> structure
|
||||
method notification : notification -> notification
|
||||
method typeAlias : typeAlias -> typeAlias
|
||||
method enumeration : enumeration -> enumeration
|
||||
end
|
||||
15
thirdparty/lsp/lsp/bin/named.ml
vendored
Normal file
15
thirdparty/lsp/lsp/bin/named.ml
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
type 'a t =
|
||||
{ name : string
|
||||
; data : 'a
|
||||
}
|
||||
|
||||
let make ~name data = { name; data }
|
||||
let data t = t.data
|
||||
let name t = t.name
|
||||
let map t ~f = { t with data = f t.data }
|
||||
let set_data t data = { t with data }
|
||||
|
||||
let to_dyn f { name; data } =
|
||||
let open Dyn in
|
||||
record [ "name", String name; "data", f data ]
|
||||
;;
|
||||
273
thirdparty/lsp/lsp/bin/ocaml/json_gen.ml
vendored
Normal file
273
thirdparty/lsp/lsp/bin/ocaml/json_gen.ml
vendored
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
open! Import
|
||||
open Ml
|
||||
|
||||
let json_t = Type.Path (Dot (Ident "Json", "t"))
|
||||
|
||||
let pat_of_literal (t : Ts_types.Literal.t) : Expr.pat =
|
||||
let open Expr in
|
||||
let tag, args =
|
||||
match t with
|
||||
| Ts_types.Literal.String s -> "String", Pat (Expr.String s)
|
||||
| Int i -> "Int", Pat (Expr.Int i)
|
||||
| Float _ -> assert false
|
||||
in
|
||||
Pat (Constr { poly = true; tag; args = [ args ] })
|
||||
;;
|
||||
|
||||
let constr_of_literal (t : Ts_types.Literal.t) : Expr.t =
|
||||
let open Expr in
|
||||
let tag, args =
|
||||
match t with
|
||||
| Ts_types.Literal.String s -> "String", Create (Expr.String s)
|
||||
| Int i -> "Int", Create (Expr.Int i)
|
||||
| Float _ -> assert false
|
||||
in
|
||||
Create (Constr { poly = true; tag; args = [ args ] })
|
||||
;;
|
||||
|
||||
let json_error_pat msg =
|
||||
let open Expr in
|
||||
( Wildcard
|
||||
, App
|
||||
( Create (Ident "Json.error")
|
||||
, [ Unnamed (Create (String msg)); Unnamed (Create (Ident "json")) ] ) )
|
||||
;;
|
||||
|
||||
let is_json_constr (constr : Type.constr) =
|
||||
List.mem [ "String"; "Int"; "Bool" ] constr.name ~equal:String.equal
|
||||
;;
|
||||
|
||||
module Name = struct
|
||||
let of_ = sprintf "%s_of_yojson"
|
||||
let to_ = sprintf "yojson_of_%s"
|
||||
|
||||
let conv = function
|
||||
| `To -> to_
|
||||
| `Of -> of_
|
||||
;;
|
||||
end
|
||||
|
||||
open Arg
|
||||
|
||||
let of_json ~name expr =
|
||||
let pat = [ Unnamed "json", Type.json ] in
|
||||
let data = { Expr.pat; type_ = Type.name name; body = expr } in
|
||||
let name = Name.of_ name in
|
||||
{ Named.name; data }
|
||||
;;
|
||||
|
||||
let to_json ~name expr =
|
||||
let pat = [ Unnamed name, Type.name name ] in
|
||||
let data = { Expr.pat; type_ = Type.json; body = expr } in
|
||||
let name = Name.to_ name in
|
||||
{ Named.name; data }
|
||||
;;
|
||||
|
||||
let add_json_conv_for_t (sig_ : Module.sig_ Module.t) =
|
||||
let conv_t =
|
||||
{ Named.name = "t"
|
||||
; data =
|
||||
(let t = Type.Path (Path.Ident "t") in
|
||||
Module.Include (Module.Name.of_string "Json.Jsonable.S", [ t, t ]))
|
||||
}
|
||||
in
|
||||
{ sig_ with bindings = sig_.bindings @ [ conv_t ] }
|
||||
;;
|
||||
|
||||
module Enum = struct
|
||||
let of_json ~allow_other ~poly { Named.name; data = constrs } =
|
||||
let open Ml.Expr in
|
||||
let body =
|
||||
let clauses =
|
||||
List.map constrs ~f:(fun (constr, literal) ->
|
||||
let pat = pat_of_literal literal in
|
||||
let tag = constr in
|
||||
pat, Create (Constr { tag; poly; args = [] }))
|
||||
in
|
||||
let clauses =
|
||||
if allow_other
|
||||
then (
|
||||
let s = Ident "s" in
|
||||
let pat = Pat (Constr { tag = "String"; poly = true; args = [ Pat s ] }) in
|
||||
let make = Create (Constr { tag = "Other"; poly; args = [ Create s ] }) in
|
||||
clauses @ [ pat, make ])
|
||||
else clauses
|
||||
in
|
||||
let msg =
|
||||
sprintf
|
||||
"Invalid value. Expected one of: %s"
|
||||
(List.map constrs ~f:(fun (_, literal) ->
|
||||
Ts_types.Literal.to_maybe_quoted_string literal)
|
||||
|> String.concat ~sep:", ")
|
||||
in
|
||||
Match (Create (Ident "json"), clauses @ [ json_error_pat msg ])
|
||||
in
|
||||
of_json ~name body
|
||||
;;
|
||||
|
||||
let to_json ~allow_other ~poly { Named.name; data = constrs } =
|
||||
let open Ml.Expr in
|
||||
let body =
|
||||
let clauses =
|
||||
List.map constrs ~f:(fun (constr, literal) ->
|
||||
let pat = Pat (Constr { tag = constr; poly; args = [] }) in
|
||||
pat, constr_of_literal literal)
|
||||
in
|
||||
let clauses =
|
||||
if allow_other
|
||||
then (
|
||||
let s = Ident "s" in
|
||||
let pat = Pat (Constr { tag = "Other"; poly; args = [ Pat s ] }) in
|
||||
let make =
|
||||
Create (Constr { tag = "String"; poly = true; args = [ Create s ] })
|
||||
in
|
||||
clauses @ [ pat, make ])
|
||||
else clauses
|
||||
in
|
||||
Match (Create (Ident name), clauses)
|
||||
in
|
||||
to_json ~name body
|
||||
;;
|
||||
|
||||
let conv ~allow_other ~poly t =
|
||||
let to_json = to_json ~allow_other ~poly t in
|
||||
let of_json = of_json ~allow_other ~poly t in
|
||||
[ to_json; of_json ]
|
||||
;;
|
||||
end
|
||||
|
||||
module Poly_variant = struct
|
||||
type constrs =
|
||||
{ json_constrs : Ml.Type.constr list
|
||||
; untagged_constrs : Ml.Type.constr list
|
||||
}
|
||||
|
||||
let split_clauses constrs =
|
||||
let json_constrs, untagged_constrs =
|
||||
List.partition_map constrs ~f:(fun x ->
|
||||
if is_json_constr x then Left x else Right x)
|
||||
in
|
||||
{ json_constrs; untagged_constrs }
|
||||
;;
|
||||
|
||||
let conv_of_constr target (utc : Ml.Type.constr) =
|
||||
let rec conv (p : Ml.Path.t) : Ml.Path.t =
|
||||
match p with
|
||||
| Ident name -> Ident (Name.conv target name)
|
||||
| Dot (s, name) -> Dot (s, Name.conv target name)
|
||||
| Apply (s, y) -> Apply (s, conv y)
|
||||
in
|
||||
let conv p = Ml.Path.to_string (conv p) in
|
||||
let open Ml.Expr in
|
||||
let json_mod n =
|
||||
match target with
|
||||
| `To -> Ident ("Json.To." ^ n)
|
||||
| `Of -> Ident ("Json.Of." ^ n)
|
||||
in
|
||||
let conv t = Create (Ident (conv t)) in
|
||||
match (utc.args : Ml.Type.t list) with
|
||||
| [ Path p ] -> conv p
|
||||
| [ List (Prim p) ] ->
|
||||
let ident =
|
||||
match p with
|
||||
| String -> "string"
|
||||
| _ -> assert false
|
||||
in
|
||||
App (Create (json_mod "list"), [ Unnamed (conv (Ident ident)) ])
|
||||
| [ List (Path p) ] -> App (Create (json_mod "list"), [ Unnamed (conv p) ])
|
||||
| [ Tuple [ Prim Int; Prim Int ] ] -> Create (json_mod "int_pair")
|
||||
| [] -> assert false
|
||||
| _ -> Code_error.raise "untagged" [ "utc", Ml.Type.dyn_of_constr utc ]
|
||||
;;
|
||||
|
||||
let json_clauses json_constrs =
|
||||
List.map json_constrs ~f:(fun (c : Ml.Type.constr) ->
|
||||
let open Ml.Expr in
|
||||
let constr arg = Constr { tag = c.name; poly = true; args = [ arg ] } in
|
||||
let pat = Pat (constr (Pat (Ident "j"))) in
|
||||
let expr : t = Create (constr (Create (Ident "j"))) in
|
||||
pat, expr)
|
||||
;;
|
||||
|
||||
let to_json { Named.name; data = constrs } =
|
||||
let { json_constrs; untagged_constrs } = split_clauses constrs in
|
||||
let open Ml.Expr in
|
||||
let json_clauses = json_clauses json_constrs in
|
||||
let untagged_clauses =
|
||||
List.map untagged_constrs ~f:(fun (utc : Ml.Type.constr) ->
|
||||
let constr arg = Constr { tag = utc.name; poly = true; args = [ arg ] } in
|
||||
let pat = Pat (constr (Pat (Ident "s"))) in
|
||||
let expr = App (conv_of_constr `To utc, [ Unnamed (Create (Ident "s")) ]) in
|
||||
pat, expr)
|
||||
in
|
||||
let expr = Match (Create (Ident name), json_clauses @ untagged_clauses) in
|
||||
to_json ~name expr
|
||||
;;
|
||||
|
||||
let of_json { Named.name; data = constrs } =
|
||||
let { json_constrs; untagged_constrs } = split_clauses constrs in
|
||||
let open Ml.Expr in
|
||||
let clauses = json_clauses json_constrs in
|
||||
let untagged =
|
||||
let args =
|
||||
let constrs =
|
||||
List.map untagged_constrs ~f:(fun (utc : Ml.Type.constr) ->
|
||||
let create =
|
||||
let of_json =
|
||||
App (conv_of_constr `Of utc, [ Unnamed (Create (Ident "json")) ])
|
||||
in
|
||||
Create (Constr { tag = utc.name; poly = true; args = [ of_json ] })
|
||||
in
|
||||
Fun ([ Unnamed (Pat (Ident "json")) ], create))
|
||||
in
|
||||
Create (List constrs)
|
||||
in
|
||||
App
|
||||
( Create (Ident "Json.Of.untagged_union")
|
||||
, [ Unnamed (Create (String name))
|
||||
; Unnamed args
|
||||
; Unnamed (Create (Ident "json"))
|
||||
] )
|
||||
in
|
||||
let expr =
|
||||
match json_constrs, untagged_constrs with
|
||||
| [], [] -> assert false
|
||||
| [], _ -> untagged
|
||||
| _, [] -> Match (Create (Ident "json"), clauses @ [ json_error_pat name ])
|
||||
| _ :: _, _ :: _ -> Match (Create (Ident "json"), clauses @ [ Wildcard, untagged ])
|
||||
in
|
||||
of_json ~name expr
|
||||
;;
|
||||
end
|
||||
|
||||
(* [name] is used as the pattern in the "to" converter. In the "of" converter,
|
||||
it's used to generate better error messages. *)
|
||||
let make_literal_wrapper_conv ~field_name ~literal_value ~type_name =
|
||||
(* Some json representations require an extra "kind" field set to some string
|
||||
constant *)
|
||||
let open Ml.Expr in
|
||||
let args = List.map ~f:(fun x -> Ml.Arg.Unnamed (Create x)) in
|
||||
let to_ =
|
||||
let a =
|
||||
[ String field_name
|
||||
; String literal_value
|
||||
; Ident (Name.conv `To type_name)
|
||||
; Ident type_name
|
||||
]
|
||||
in
|
||||
App (Create (Ident "Json.To.literal_field"), args a)
|
||||
in
|
||||
let of_ =
|
||||
let a =
|
||||
[ String type_name
|
||||
; String field_name
|
||||
; String literal_value
|
||||
; Ident (Name.conv `Of type_name)
|
||||
; Ident "json"
|
||||
]
|
||||
in
|
||||
App (Create (Ident "Json.Of.literal_field"), args a)
|
||||
in
|
||||
[ to_json ~name:type_name to_; of_json ~name:type_name of_ ]
|
||||
|> List.map ~f:(Named.map ~f:(fun v -> Ml.Module.Value v))
|
||||
;;
|
||||
21
thirdparty/lsp/lsp/bin/ocaml/json_gen.mli
vendored
Normal file
21
thirdparty/lsp/lsp/bin/ocaml/json_gen.mli
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
val json_t : Ml.Type.t
|
||||
val add_json_conv_for_t : Ml.Module.sig_ Ml.Module.t -> Ml.Module.sig_ Ml.Module.t
|
||||
|
||||
module Enum : sig
|
||||
val conv
|
||||
: allow_other:bool
|
||||
-> poly:bool
|
||||
-> (string * Ts_types.Literal.t) list Named.t
|
||||
-> Ml.Expr.toplevel Named.t list
|
||||
end
|
||||
|
||||
module Poly_variant : sig
|
||||
val of_json : Ml.Type.constr list Named.t -> Ml.Expr.toplevel Named.t
|
||||
val to_json : Ml.Type.constr list Named.t -> Ml.Expr.toplevel Named.t
|
||||
end
|
||||
|
||||
val make_literal_wrapper_conv
|
||||
: field_name:string
|
||||
-> literal_value:string
|
||||
-> type_name:string
|
||||
-> Ml.Module.impl Named.t list
|
||||
598
thirdparty/lsp/lsp/bin/ocaml/ml.ml
vendored
Normal file
598
thirdparty/lsp/lsp/bin/ocaml/ml.ml
vendored
Normal file
|
|
@ -0,0 +1,598 @@
|
|||
open Import
|
||||
|
||||
module Kind = struct
|
||||
type t =
|
||||
| Intf
|
||||
| Impl
|
||||
|
||||
type ('intf, 'impl) pair =
|
||||
{ intf : 'intf
|
||||
; impl : 'impl
|
||||
}
|
||||
|
||||
module Map = struct
|
||||
type 'a t = ('a, 'a) pair
|
||||
|
||||
let get { intf; impl } = function
|
||||
| Impl -> impl
|
||||
| Intf -> intf
|
||||
;;
|
||||
|
||||
let make_both a = { intf = a; impl = a }
|
||||
|
||||
let iter { intf; impl } ~f =
|
||||
f intf;
|
||||
f impl
|
||||
;;
|
||||
|
||||
let map { intf; impl } ~f = { intf = f intf; impl = f impl }
|
||||
|
||||
let both (type a b) (x : a t) (y : b t) : (a * b) t =
|
||||
{ intf = x.intf, y.intf; impl = x.impl, y.impl }
|
||||
;;
|
||||
end
|
||||
end
|
||||
|
||||
let is_kw = function
|
||||
| "type" | "method" | "end" | "to" | "external" -> true
|
||||
| _ -> false
|
||||
;;
|
||||
|
||||
module Arg = struct
|
||||
type 'e t =
|
||||
| Unnamed of 'e
|
||||
| Labeled of string * 'e
|
||||
| Optional of string * 'e
|
||||
|
||||
let to_dyn f =
|
||||
let open Dyn in
|
||||
function
|
||||
| Unnamed a -> Dyn.variant "Unnamed" [ f a ]
|
||||
| Labeled (s, a) -> Dyn.variant "Labeled" [ string s; f a ]
|
||||
| Optional (s, a) -> Dyn.variant "Optional" [ string s; f a ]
|
||||
;;
|
||||
end
|
||||
|
||||
module Path = struct
|
||||
type t =
|
||||
| Ident of string
|
||||
| Dot of t * string
|
||||
| Apply of t * t
|
||||
|
||||
let rec to_string = function
|
||||
| Ident s -> s
|
||||
| Dot (t, s) -> to_string t ^ "." ^ s
|
||||
| Apply (f, x) -> to_string f ^ "(" ^ to_string x ^ ")"
|
||||
;;
|
||||
|
||||
let rec pp = function
|
||||
| Ident s -> Pp.verbatim s
|
||||
| Dot (s, p) -> Pp.concat [ pp s; Pp.verbatim "."; Pp.verbatim p ]
|
||||
| Apply (s, p) -> Pp.concat [ pp s; W.surround `Paren (pp p) ]
|
||||
;;
|
||||
end
|
||||
|
||||
module Type = struct
|
||||
[@@@warning "-30"]
|
||||
|
||||
type prim =
|
||||
| Unit
|
||||
| String
|
||||
| Int
|
||||
| Bool
|
||||
|
||||
let dyn_of_prim : prim -> Dyn.t =
|
||||
let open Dyn in
|
||||
function
|
||||
| Unit -> variant "Unit" []
|
||||
| String -> variant "String" []
|
||||
| Int -> variant "Int" []
|
||||
| Bool -> variant "Bool" []
|
||||
;;
|
||||
|
||||
type t =
|
||||
| Path of Path.t
|
||||
| Var of string
|
||||
| Prim of prim
|
||||
| Tuple of t list
|
||||
| Optional of t
|
||||
| List of t
|
||||
| Poly_variant of constr list
|
||||
| Assoc of t * t
|
||||
| App of t * t list
|
||||
| Fun of t Arg.t * t
|
||||
|
||||
and constr =
|
||||
{ name : string
|
||||
; args : t list
|
||||
}
|
||||
|
||||
and field =
|
||||
{ name : string
|
||||
; typ : t
|
||||
; attrs : (string * string list) list
|
||||
}
|
||||
|
||||
let rec to_dyn =
|
||||
let open Dyn in
|
||||
function
|
||||
| Var v -> variant "Var" [ string v ]
|
||||
| List v -> variant "List" [ to_dyn v ]
|
||||
| Assoc (x, y) -> variant "Assoc" [ to_dyn x; to_dyn y ]
|
||||
| Tuple xs -> variant "Tuple" (List.map ~f:to_dyn xs)
|
||||
| Optional t -> variant "Optional" [ to_dyn t ]
|
||||
| Path p -> variant "Path" [ string @@ Path.to_string p ]
|
||||
| Poly_variant xs -> variant "Poly_variant" (List.map ~f:dyn_of_constr xs)
|
||||
| App (x, y) -> variant "App" (to_dyn x :: List.map y ~f:to_dyn)
|
||||
| Prim p -> variant "Prim" [ dyn_of_prim p ]
|
||||
| Fun (arg, t) -> variant "Fun" [ Arg.to_dyn to_dyn arg; to_dyn t ]
|
||||
|
||||
and dyn_of_constr { name; args } =
|
||||
Dyn.(record [ "name", string name; "args", (list to_dyn) args ])
|
||||
|
||||
and dyn_of_field { name; typ; attrs } =
|
||||
let open Dyn in
|
||||
record
|
||||
[ "name", string name
|
||||
; "typ", to_dyn typ
|
||||
; "attrs", list (pair string (list string)) attrs
|
||||
]
|
||||
;;
|
||||
|
||||
type decl =
|
||||
| Alias of t
|
||||
| Record of field list
|
||||
| Variant of constr list
|
||||
|
||||
let dyn_of_decl =
|
||||
let open Dyn in
|
||||
function
|
||||
| Alias a -> variant "Alias" [ to_dyn a ]
|
||||
| Record fs -> variant "Record" (List.map ~f:dyn_of_field fs)
|
||||
| Variant cs -> variant "Variant" (List.map ~f:dyn_of_constr cs)
|
||||
;;
|
||||
|
||||
class virtual ['env, 'm] mapreduce =
|
||||
object (self : 'self)
|
||||
method virtual empty : 'm
|
||||
method virtual plus : 'm -> 'm -> 'm
|
||||
|
||||
method poly_variant env constrs =
|
||||
let r, s = self#fold_left_map constrs ~f:(fun c -> self#constr env c) in
|
||||
Poly_variant r, s
|
||||
|
||||
method tuple (env : 'env) t =
|
||||
let (r : t list), s = self#fold_left_map t ~f:(fun (t : t) -> self#t env t) in
|
||||
Tuple r, s
|
||||
|
||||
method path _ p = Path p, self#empty
|
||||
method var _ n = Var n, self#empty
|
||||
method prim _ p = Prim p, self#empty
|
||||
|
||||
method optional env p =
|
||||
let t, s = self#t env p in
|
||||
Optional t, s
|
||||
|
||||
method list env t =
|
||||
let t, s = self#t env t in
|
||||
List t, s
|
||||
|
||||
method assoc env k v =
|
||||
let k, s1 = self#t env k in
|
||||
let v, s2 = self#t env v in
|
||||
Assoc (k, v), self#plus s1 s2
|
||||
|
||||
method app env f xs =
|
||||
let f, s1 = self#t env f in
|
||||
let xs, s2 = self#fold_left_map xs ~f:(fun x -> self#t env x) in
|
||||
App (f, xs), self#plus s1 s2
|
||||
|
||||
method t env this =
|
||||
match (this : t) with
|
||||
| Path p -> self#path env p
|
||||
| Var v -> self#var env v
|
||||
| Prim p -> self#prim env p
|
||||
| Tuple t -> self#tuple env t
|
||||
| Optional t -> self#optional env t
|
||||
| List t -> self#list env t
|
||||
| Poly_variant t -> self#poly_variant env t
|
||||
| Assoc (k, v) -> self#assoc env k v
|
||||
| App (f, xs) -> self#app env f xs
|
||||
| Fun (_, _) -> assert false
|
||||
|
||||
method alias env t =
|
||||
let r0, s0 = self#t env t in
|
||||
Alias r0, s0
|
||||
|
||||
method constr env (constr : constr) =
|
||||
let args, s = self#fold_left_map constr.args ~f:(fun t -> self#t env t) in
|
||||
{ constr with args }, s
|
||||
|
||||
method private fold_left_map : 'a. f:('a -> 'a * 'm) -> 'a list -> 'a list * 'm =
|
||||
fun ~f xs ->
|
||||
let accf, accm =
|
||||
List.fold_left xs ~init:([], self#empty) ~f:(fun (accf, accm) x ->
|
||||
let r, s = f x in
|
||||
r :: accf, self#plus accm s)
|
||||
in
|
||||
List.rev accf, accm
|
||||
|
||||
method field env f =
|
||||
let typ, s = self#t env f.typ in
|
||||
{ f with typ }, s
|
||||
|
||||
method record env fields =
|
||||
let r, s = self#fold_left_map fields ~f:(fun f -> self#field env f) in
|
||||
Record r, s
|
||||
|
||||
method variant env constrs =
|
||||
let v, s = self#fold_left_map constrs ~f:(fun f -> self#constr env f) in
|
||||
Variant v, s
|
||||
|
||||
method decl env decl =
|
||||
match decl with
|
||||
| Alias a -> self#alias env a
|
||||
| Record fs -> self#record env fs
|
||||
| Variant v -> self#variant env v
|
||||
end
|
||||
|
||||
let field typ ~name = { name; typ; attrs = [] }
|
||||
let fun_ args t = List.fold_right args ~init:t ~f:(fun arg acc -> Fun (arg, acc))
|
||||
let constr args ~name = { name; args }
|
||||
let list t = List t
|
||||
let assoc_list ~key ~data = Assoc (key, data)
|
||||
let t = Path (Ident "t")
|
||||
let module_t m = Path (Dot (Ident (String.capitalize_ascii m), "t"))
|
||||
let string = Prim String
|
||||
let name s = Path (Ident s)
|
||||
let int = Prim Int
|
||||
let bool = Prim Bool
|
||||
let alpha = Var "a"
|
||||
|
||||
let enum constrs =
|
||||
Variant (List.map constrs ~f:(fun constr -> { name = constr; args = [] }))
|
||||
;;
|
||||
|
||||
let poly_enum constrs =
|
||||
Poly_variant (List.map constrs ~f:(fun constr -> { name = constr; args = [] }))
|
||||
;;
|
||||
|
||||
let json = Path (Dot (Ident "Json", "t"))
|
||||
let unit = Prim Unit
|
||||
let array t = App (Path (Ident "array"), [ t ])
|
||||
|
||||
let void =
|
||||
let void = Path.Dot (Ident "Json", "Void") in
|
||||
Path (Dot (void, "t"))
|
||||
;;
|
||||
|
||||
let json_object =
|
||||
let obj = Path.Dot (Ident "Json", "Object") in
|
||||
Path (Dot (obj, "t"))
|
||||
;;
|
||||
|
||||
module Type = W.Type
|
||||
|
||||
let pp_prim (p : prim) : W.t =
|
||||
match p with
|
||||
| String -> Pp.verbatim "string"
|
||||
| Int -> Pp.verbatim "int"
|
||||
| Bool -> Pp.verbatim "bool"
|
||||
| Unit -> Pp.verbatim "unit"
|
||||
;;
|
||||
|
||||
let rec pp (a : t) ~(kind : Kind.t) : W.t =
|
||||
match a with
|
||||
| Prim p -> pp_prim p
|
||||
| Var v -> Type.var v
|
||||
| Path p -> Path.pp p
|
||||
| App (f, xs) -> Type.app (pp ~kind f) (List.map ~f:(pp ~kind) xs)
|
||||
| Tuple t -> Type.tuple (List.map ~f:(pp ~kind) t)
|
||||
| Optional t -> pp ~kind (App (Path (Ident "option"), [ t ]))
|
||||
| List t -> pp ~kind (App (Path (Ident "list"), [ t ]))
|
||||
| Poly_variant constrs ->
|
||||
List.map constrs ~f:(fun { name; args } -> name, List.map args ~f:(pp ~kind))
|
||||
|> Type.poly
|
||||
| Assoc (k, v) ->
|
||||
let t = List (Tuple [ k; v ]) in
|
||||
pp t ~kind
|
||||
| Fun (a, r) ->
|
||||
(match a with
|
||||
| Arg.Unnamed t ->
|
||||
Pp.concat [ pp t ~kind; Pp.space; Pp.verbatim "->"; Pp.space; pp ~kind r ]
|
||||
| Arg.Labeled (l, t) ->
|
||||
Pp.concat
|
||||
[ Pp.textf "%s:" l
|
||||
; pp t ~kind
|
||||
; Pp.space
|
||||
; Pp.verbatim "->"
|
||||
; Pp.space
|
||||
; pp ~kind r
|
||||
]
|
||||
| Arg.Optional (l, t) ->
|
||||
Pp.concat
|
||||
[ Pp.textf "?%s:" l
|
||||
; pp t ~kind
|
||||
; Pp.space
|
||||
; Pp.verbatim "->"
|
||||
; Pp.space
|
||||
; pp ~kind r
|
||||
])
|
||||
;;
|
||||
|
||||
let pp_decl' ~(kind : Kind.t) (a : decl) =
|
||||
match a with
|
||||
| Alias a ->
|
||||
let pp = pp ~kind a in
|
||||
(match a, kind with
|
||||
| (List _ | Path _ | Prim _), Impl -> W.Type.deriving ~record:false pp
|
||||
| _, _ -> pp)
|
||||
| Variant v ->
|
||||
List.map v ~f:(fun { name; args } -> name, List.map ~f:(pp ~kind) args)
|
||||
|> Type.variant
|
||||
| Record r ->
|
||||
let r =
|
||||
List.map r ~f:(fun { name; typ; attrs } ->
|
||||
let def =
|
||||
let field = pp ~kind typ in
|
||||
let attrs =
|
||||
let attrs =
|
||||
match kind with
|
||||
| Intf -> []
|
||||
| Impl -> attrs
|
||||
in
|
||||
List.concat_map attrs ~f:(fun (a, r) ->
|
||||
[ W.Attr.make a (List.map ~f:Pp.verbatim r) ])
|
||||
in
|
||||
Type.field_attrs ~field ~attrs
|
||||
in
|
||||
name, def)
|
||||
|> Type.record
|
||||
in
|
||||
(match kind with
|
||||
| Intf -> r
|
||||
| Impl -> W.Type.deriving r ~record:true)
|
||||
;;
|
||||
|
||||
let pp_decl ~name ~kind (a : decl) : W.t =
|
||||
let body = pp_decl' ~kind a in
|
||||
Type.decl name body
|
||||
;;
|
||||
end
|
||||
|
||||
module Expr = struct
|
||||
[@@@ocaml.warning "-30-32-37"]
|
||||
|
||||
type expr =
|
||||
| Let of pat * expr * expr (** let pat = e1 in e2 *)
|
||||
| Match of expr * (pat * expr) list (** match e1 with [p -> e]* *)
|
||||
| Fun of pat Arg.t list * expr (** fun p2 p2 .. -> e *)
|
||||
| App of expr * expr Arg.t list (** f e1 e2 .. *)
|
||||
| Create of expr prim (** Literal/Primitive *)
|
||||
| Assert_false (** assert false *)
|
||||
|
||||
and 'e prim =
|
||||
| Unit
|
||||
| Bool of bool
|
||||
| Int of int
|
||||
| String of string
|
||||
| Ident of string
|
||||
| Cons of 'e * 'e prim
|
||||
| List of 'e list
|
||||
| Tuple of 'e list
|
||||
| Record of 'e record_
|
||||
| Constr of 'e constr
|
||||
|
||||
and pat =
|
||||
| Wildcard
|
||||
| Pat of pat prim
|
||||
|
||||
and 'e record_ = (string * 'e) list
|
||||
|
||||
and 'e constr =
|
||||
{ tag : string
|
||||
; poly : bool
|
||||
; args : 'e list
|
||||
}
|
||||
|
||||
type t = expr
|
||||
|
||||
let assert_false_clause = Wildcard, Assert_false
|
||||
|
||||
type toplevel =
|
||||
{ pat : (string Arg.t * Type.t) list
|
||||
; type_ : Type.t
|
||||
; body : t
|
||||
}
|
||||
|
||||
let constr ?(poly = false) ?(args = []) tag = { poly; args; tag }
|
||||
|
||||
let pp_constr f { tag; poly; args } =
|
||||
let tag =
|
||||
let tag = String.capitalize tag in
|
||||
Pp.verbatim (if poly then "`" ^ tag else tag)
|
||||
in
|
||||
match args with
|
||||
| [] -> tag
|
||||
| args ->
|
||||
let sep = Pp.verbatim "," in
|
||||
let args = W.surround `Paren (Pp.concat_map ~sep ~f args) in
|
||||
Pp.concat [ tag; Pp.space; args ]
|
||||
;;
|
||||
|
||||
let rec pp_pat = function
|
||||
| Wildcard -> Pp.verbatim "_"
|
||||
| Pat pat ->
|
||||
(match pat with
|
||||
| Unit -> Pp.verbatim "()"
|
||||
| Bool b -> Pp.textf "%b" b
|
||||
| Int i -> Pp.textf "%i" i
|
||||
| String s -> Pp.textf "%S" s
|
||||
| Ident s -> Pp.verbatim s
|
||||
| Cons _ -> assert false
|
||||
| List _ -> assert false
|
||||
| Tuple _ -> assert false
|
||||
| Record _ -> assert false
|
||||
| Constr c -> pp_constr pp_pat c)
|
||||
;;
|
||||
|
||||
let rec pp_create : expr prim -> _ Pp.t = function
|
||||
| Unit -> Pp.verbatim "()"
|
||||
| Bool b -> Pp.textf "%b" b
|
||||
| Int i ->
|
||||
let pp = Pp.textf "%i" i in
|
||||
if i < 0 then W.surround `Paren pp else pp
|
||||
| String s -> Pp.textf "%S" s
|
||||
| Ident s -> Pp.verbatim s
|
||||
| Cons _ -> assert false
|
||||
| List xs ->
|
||||
let xs = Pp.concat_map xs ~sep:(Pp.verbatim ";") ~f:pp in
|
||||
W.surround `Square xs
|
||||
| Tuple _ -> assert false
|
||||
| Record fields ->
|
||||
let record =
|
||||
let open Pp.O in
|
||||
Pp.concat_map
|
||||
fields
|
||||
~sep:(Pp.verbatim ";" ++ Pp.space)
|
||||
~f:(fun (name, expr) ->
|
||||
if expr = Create (Ident name)
|
||||
then pp expr
|
||||
else Pp.verbatim name ++ Pp.space ++ Pp.verbatim "=" ++ pp expr)
|
||||
in
|
||||
W.surround `Curly record
|
||||
| Constr c -> pp_constr pp c
|
||||
|
||||
and pp = function
|
||||
| Assert_false -> Pp.verbatim "assert false"
|
||||
| Match (expr, patterns) ->
|
||||
let with_ =
|
||||
Pp.concat [ Pp.verbatim "match"; Pp.space; pp expr; Pp.space; Pp.verbatim "with" ]
|
||||
in
|
||||
let clauses =
|
||||
Pp.concat_map patterns ~f:(fun (pat, expr) ->
|
||||
Pp.concat
|
||||
[ Pp.verbatim "| "
|
||||
; pp_pat pat
|
||||
; Pp.space
|
||||
; Pp.verbatim "->"
|
||||
; Pp.space
|
||||
; Pp.verbatim "("
|
||||
; pp expr
|
||||
; Pp.verbatim ")"
|
||||
])
|
||||
in
|
||||
Pp.concat [ with_; Pp.newline; clauses ]
|
||||
| Create c -> pp_create c
|
||||
| App (x, args) ->
|
||||
let args =
|
||||
Pp.concat_map args ~sep:Pp.space ~f:(fun arg ->
|
||||
match arg with
|
||||
| Unnamed e -> pp e
|
||||
| _ -> assert false)
|
||||
in
|
||||
Pp.concat [ pp x; Pp.space; args ]
|
||||
| Fun (pats, expr) ->
|
||||
W.surround
|
||||
`Paren
|
||||
(Pp.concat
|
||||
[ Pp.verbatim "fun"
|
||||
; Pp.space
|
||||
; Pp.concat_map pats ~sep:Pp.space ~f:(fun arg ->
|
||||
match arg with
|
||||
| Unnamed e -> pp_pat e
|
||||
| _ -> assert false)
|
||||
; Pp.space
|
||||
; Pp.verbatim "->"
|
||||
; pp expr
|
||||
])
|
||||
| _ -> assert false
|
||||
;;
|
||||
|
||||
let pp_toplevel ~kind name { pat; type_; body } =
|
||||
let pat =
|
||||
Pp.concat_map pat ~f:(fun (pat, typ) ->
|
||||
let typ = Type.pp ~kind typ in
|
||||
match pat with
|
||||
| Unnamed s ->
|
||||
Pp.concat
|
||||
[ Pp.verbatim "("; Pp.verbatim s; Pp.verbatim " : "; typ; Pp.verbatim ")" ]
|
||||
| Labeled (l, r) ->
|
||||
if l = r
|
||||
then Pp.concat [ Pp.textf "~(%s :" l; typ; Pp.verbatim ")" ]
|
||||
else assert false
|
||||
| Optional (l, r) ->
|
||||
if l = r
|
||||
then Pp.concat [ Pp.textf "?(%s :" l; typ; Pp.space; Pp.verbatim "option)" ]
|
||||
else assert false)
|
||||
in
|
||||
let body = pp body in
|
||||
let type_ = Type.pp type_ ~kind in
|
||||
Pp.concat
|
||||
[ Pp.textf "let %s" name
|
||||
; pat
|
||||
; Pp.textf " : "
|
||||
; type_
|
||||
; Pp.textf "="
|
||||
; Pp.newline
|
||||
; body
|
||||
]
|
||||
;;
|
||||
end
|
||||
|
||||
module Module = struct
|
||||
module Name : sig
|
||||
type t = private string
|
||||
|
||||
val of_string : string -> t
|
||||
end = struct
|
||||
type t = string
|
||||
|
||||
let of_string s =
|
||||
match s.[0] with
|
||||
| 'a' .. 'z' -> Code_error.raise "invalid module name" [ "s", Dyn.string s ]
|
||||
| _ -> s
|
||||
;;
|
||||
end
|
||||
|
||||
type 'a t =
|
||||
{ name : Name.t
|
||||
; bindings : 'a Named.t list
|
||||
}
|
||||
|
||||
let empty name = { name; bindings = [] }
|
||||
|
||||
type sig_ =
|
||||
| Value of Type.t
|
||||
| Type_decl of Type.decl
|
||||
| Include of Name.t * (Type.t * Type.t) list
|
||||
|
||||
type impl =
|
||||
| Type_decl of Type.decl
|
||||
| Value of Expr.toplevel
|
||||
|
||||
let pp_sig { name; bindings } =
|
||||
let bindings =
|
||||
Pp.concat_map bindings ~sep:Pp.newline ~f:(fun { name; data } ->
|
||||
match (data : sig_) with
|
||||
| Value t -> W.Sig.val_ name [ Type.pp ~kind:Intf t ]
|
||||
| Type_decl t -> W.Type.decl name (Type.pp_decl' ~kind:Intf t)
|
||||
| Include (mod_, destructive_subs) ->
|
||||
List.map destructive_subs ~f:(fun (l, r) ->
|
||||
let f = Type.pp ~kind:Intf in
|
||||
f l, f r)
|
||||
|> W.Sig.include_ (mod_ :> string))
|
||||
in
|
||||
W.Sig.module_ (name :> string) bindings
|
||||
;;
|
||||
|
||||
let pp_impl { name; bindings } =
|
||||
let bindings =
|
||||
Pp.concat_map bindings ~sep:Pp.newline ~f:(fun { name; data = v } ->
|
||||
match v with
|
||||
| Value decl -> Expr.pp_toplevel ~kind:Impl name decl
|
||||
| Type_decl t -> W.Type.decl name (Type.pp_decl' ~kind:Impl t))
|
||||
in
|
||||
W.module_ (name :> string) bindings
|
||||
;;
|
||||
end
|
||||
227
thirdparty/lsp/lsp/bin/ocaml/ml.mli
vendored
Normal file
227
thirdparty/lsp/lsp/bin/ocaml/ml.mli
vendored
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
(** Representation of OCaml code used for generation *)
|
||||
|
||||
val is_kw : string -> bool
|
||||
|
||||
module Kind : sig
|
||||
type t =
|
||||
| Intf
|
||||
| Impl
|
||||
|
||||
type ('intf, 'impl) pair =
|
||||
{ intf : 'intf
|
||||
; impl : 'impl
|
||||
}
|
||||
|
||||
module Map : sig
|
||||
type 'a t = ('a, 'a) pair
|
||||
type kind
|
||||
|
||||
val get : 'a t -> kind -> 'a
|
||||
val iter : 'a t -> f:('a -> unit) -> unit
|
||||
val map : 'a t -> f:('a -> 'b) -> 'b t
|
||||
val both : 'a t -> 'b t -> ('a * 'b) t
|
||||
val make_both : 'a -> 'a t
|
||||
end
|
||||
with type kind := t
|
||||
end
|
||||
|
||||
module Arg : sig
|
||||
(** Represent arrow types and argument patterns *)
|
||||
|
||||
type 'e t =
|
||||
| Unnamed of 'e
|
||||
| Labeled of string * 'e
|
||||
| Optional of string * 'e
|
||||
end
|
||||
|
||||
module Path : sig
|
||||
type t =
|
||||
| Ident of string
|
||||
| Dot of t * string
|
||||
| Apply of t * t
|
||||
|
||||
val to_string : t -> string
|
||||
end
|
||||
|
||||
module Type : sig
|
||||
[@@@warning "-30"]
|
||||
|
||||
type prim =
|
||||
| Unit
|
||||
| String
|
||||
| Int
|
||||
| Bool
|
||||
|
||||
type t =
|
||||
| Path of Path.t
|
||||
| Var of string
|
||||
| Prim of prim
|
||||
| Tuple of t list
|
||||
| Optional of t
|
||||
| List of t
|
||||
| Poly_variant of constr list
|
||||
| Assoc of t * t
|
||||
| App of t * t list
|
||||
| Fun of t Arg.t * t
|
||||
|
||||
and field =
|
||||
{ name : string
|
||||
; typ : t
|
||||
; attrs : (string * string list) list
|
||||
}
|
||||
|
||||
and constr =
|
||||
{ name : string
|
||||
; args : t list
|
||||
}
|
||||
|
||||
val to_dyn : t -> Dyn.t
|
||||
val dyn_of_constr : constr -> Dyn.t
|
||||
|
||||
type decl =
|
||||
| Alias of t
|
||||
| Record of field list
|
||||
| Variant of constr list
|
||||
|
||||
val dyn_of_decl : decl -> Dyn.t
|
||||
val fun_ : t Arg.t list -> t -> t
|
||||
|
||||
(* This is for lists where the keys are equal to strings *)
|
||||
val assoc_list : key:t -> data:t -> t
|
||||
val pp_decl : name:string -> kind:Kind.t -> decl -> unit Pp.t
|
||||
val pp : t -> kind:Kind.t -> unit Pp.t
|
||||
val field : t -> name:string -> field
|
||||
val constr : t list -> name:string -> constr
|
||||
|
||||
(** Simplified sum types*)
|
||||
val enum : string list -> decl
|
||||
|
||||
(** Polymorphic variant form *)
|
||||
val poly_enum : string list -> t
|
||||
|
||||
val list : t -> t
|
||||
val module_t : string -> t
|
||||
val t : t
|
||||
val string : t
|
||||
val name : string -> t
|
||||
val int : t
|
||||
val bool : t
|
||||
val alpha : t
|
||||
val json : t
|
||||
val json_object : t
|
||||
val unit : t
|
||||
val void : t
|
||||
val array : t -> t
|
||||
|
||||
(** Fold and map over a type expression.
|
||||
|
||||
['m] is the type of monoid summarized.
|
||||
|
||||
['env] is a custom value threaded through the path. Parent nodes can use
|
||||
this to give child nodes context *)
|
||||
class virtual ['env, 'm] mapreduce : object ('self)
|
||||
method virtual empty : 'm
|
||||
method virtual plus : 'm -> 'm -> 'm
|
||||
|
||||
(** doesn't really to be here, but putting it here avoids passing [empty]
|
||||
and [plus] to a general purpose [fold_left_map]*)
|
||||
method private fold_left_map : 'a. f:('a -> 'a * 'm) -> 'a list -> 'a list * 'm
|
||||
|
||||
method alias : 'env -> t -> decl * 'm
|
||||
method app : 'env -> t -> t list -> t * 'm
|
||||
method assoc : 'env -> t -> t -> t * 'm
|
||||
method constr : 'env -> constr -> constr * 'm
|
||||
method field : 'env -> field -> field * 'm
|
||||
method list : 'env -> t -> t * 'm
|
||||
method path : 'env -> Path.t -> t * 'm
|
||||
method optional : 'env -> t -> t * 'm
|
||||
method poly_variant : 'env -> constr list -> t * 'm
|
||||
method prim : 'env -> prim -> t * 'm
|
||||
method record : 'env -> field list -> decl * 'm
|
||||
method t : 'env -> t -> t * 'm
|
||||
method decl : 'env -> decl -> decl * 'm
|
||||
method tuple : 'env -> t list -> t * 'm
|
||||
method var : 'env -> string -> t * 'm
|
||||
method variant : 'env -> constr list -> decl * 'm
|
||||
end
|
||||
end
|
||||
|
||||
module Expr : sig
|
||||
(** An (untyped) ocaml expression. It is the responsibility of the generator
|
||||
to create well typed expressions *)
|
||||
type expr =
|
||||
| Let of pat * expr * expr
|
||||
| Match of expr * (pat * expr) list
|
||||
| Fun of pat Arg.t list * expr
|
||||
| App of expr * expr Arg.t list
|
||||
| Create of expr prim
|
||||
| Assert_false
|
||||
|
||||
(* patterns or constructors, depending on ['e] *)
|
||||
and 'e prim =
|
||||
| Unit
|
||||
| Bool of bool
|
||||
| Int of int
|
||||
| String of string
|
||||
(* This should be Path.t as well *)
|
||||
| Ident of string
|
||||
| Cons of 'e * 'e prim
|
||||
| List of 'e list
|
||||
| Tuple of 'e list
|
||||
| Record of 'e record_
|
||||
| Constr of 'e constr
|
||||
|
||||
and pat =
|
||||
| Wildcard (** [_ -> ] *)
|
||||
| Pat of pat prim
|
||||
|
||||
and 'e record_ = (string * 'e) list
|
||||
|
||||
and 'e constr =
|
||||
{ tag : string (** the tag in a tagged union *)
|
||||
; poly : bool (** polymorphic variant? *)
|
||||
; args : 'e list
|
||||
}
|
||||
|
||||
type t = expr
|
||||
|
||||
(** [ _ -> assert false ] *)
|
||||
val assert_false_clause : pat * expr
|
||||
|
||||
(** toplevel declartion (without the name) *)
|
||||
type toplevel =
|
||||
{ pat : (string Arg.t * Type.t) list
|
||||
(** paterns and their types. types should be optional but they really
|
||||
help the error messages if the generated code is incorrect *)
|
||||
; type_ : Type.t (** useful to annotate the return types *)
|
||||
; body : t
|
||||
}
|
||||
end
|
||||
|
||||
module Module : sig
|
||||
(** Generate OCaml modules with JS converters *)
|
||||
module Name : sig
|
||||
type t = private string
|
||||
|
||||
val of_string : string -> t
|
||||
end
|
||||
|
||||
type 'a t =
|
||||
{ name : Name.t
|
||||
; bindings : 'a Named.t list
|
||||
}
|
||||
|
||||
val empty : Name.t -> 'a t
|
||||
|
||||
type sig_ =
|
||||
| Value of Type.t
|
||||
| Type_decl of Type.decl
|
||||
| Include of Name.t * (Type.t * Type.t) list
|
||||
|
||||
type impl =
|
||||
| Type_decl of Type.decl
|
||||
| Value of Expr.toplevel
|
||||
|
||||
val pp_sig : sig_ t -> unit Pp.t
|
||||
val pp_impl : impl t -> unit Pp.t
|
||||
end
|
||||
78
thirdparty/lsp/lsp/bin/ocaml/ml_create.ml
vendored
Normal file
78
thirdparty/lsp/lsp/bin/ocaml/ml_create.ml
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
open Import
|
||||
|
||||
let f_name name = if name = "t" then "create" else sprintf "create_%s" name
|
||||
|
||||
let need_unit =
|
||||
List.exists ~f:(fun (f : Ml.Type.field) ->
|
||||
match f.typ with
|
||||
| Optional _ -> true
|
||||
| _ -> false)
|
||||
;;
|
||||
|
||||
let intf { Named.name; data = fields } =
|
||||
let type_ =
|
||||
let need_unit = need_unit fields in
|
||||
let fields : Ml.Type.t Ml.Arg.t list =
|
||||
List.map fields ~f:(fun (field : Ml.Type.field) ->
|
||||
match field.typ with
|
||||
| Optional t -> Ml.Arg.Optional (field.name, t)
|
||||
| t -> Labeled (field.name, t))
|
||||
in
|
||||
let args : Ml.Type.t Ml.Arg.t list =
|
||||
if need_unit
|
||||
then
|
||||
(* Gross hack because I was too lazy to allow patterns in toplevel
|
||||
exprs *)
|
||||
fields @ [ Ml.Arg.Unnamed Ml.Type.unit ]
|
||||
else fields
|
||||
in
|
||||
Ml.Type.fun_ args (Ml.Type.name name)
|
||||
in
|
||||
let f_name = f_name name in
|
||||
{ Named.name = f_name; data = type_ }
|
||||
;;
|
||||
|
||||
let impl { Named.name; data = fields } =
|
||||
let make =
|
||||
let fields =
|
||||
List.map fields ~f:(fun (field : Ml.Type.field) ->
|
||||
let open Ml.Expr in
|
||||
field.name, Create (Ident field.name))
|
||||
in
|
||||
Ml.Expr.Create (Record fields)
|
||||
in
|
||||
let pat =
|
||||
let need_unit = need_unit fields in
|
||||
let fields =
|
||||
List.map fields ~f:(fun (field : Ml.Type.field) ->
|
||||
match field.typ with
|
||||
| Optional t -> Ml.Arg.Optional (field.name, field.name), t
|
||||
| t -> Ml.Arg.Labeled (field.name, field.name), t)
|
||||
in
|
||||
if need_unit
|
||||
then
|
||||
(* Gross hack because I was too lazy to allow patterns in toplevel
|
||||
exprs *)
|
||||
fields @ [ Unnamed "()", Ml.Type.unit ]
|
||||
else fields
|
||||
in
|
||||
let body = { Ml.Expr.pat; type_ = Ml.Type.name name; body = make } in
|
||||
let f_name = f_name name in
|
||||
{ Named.name = f_name; data = body }
|
||||
;;
|
||||
|
||||
let impl_of_type (t : Ml.Type.decl Named.t) =
|
||||
match (t.data : Ml.Type.decl) with
|
||||
| Record fields ->
|
||||
let create = impl { t with data = fields } in
|
||||
[ { create with data = Ml.Module.Value create.data } ]
|
||||
| _ -> []
|
||||
;;
|
||||
|
||||
let intf_of_type (t : Ml.Type.decl Named.t) : Ml.Module.sig_ Named.t list =
|
||||
match (t.data : Ml.Type.decl) with
|
||||
| Record fields ->
|
||||
let create = intf { t with data = fields } in
|
||||
[ { create with data = Ml.Module.Value create.data } ]
|
||||
| _ -> []
|
||||
;;
|
||||
3
thirdparty/lsp/lsp/bin/ocaml/ml_create.mli
vendored
Normal file
3
thirdparty/lsp/lsp/bin/ocaml/ml_create.mli
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
(* Generate create functions with optional/labeled arguments *)
|
||||
val intf_of_type : Ml.Type.decl Named.t -> Ml.Module.sig_ Named.t list
|
||||
val impl_of_type : Ml.Type.decl Named.t -> Ml.Module.impl Named.t list
|
||||
28
thirdparty/lsp/lsp/bin/ocaml/ml_kind.ml
vendored
Normal file
28
thirdparty/lsp/lsp/bin/ocaml/ml_kind.ml
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
open! Import
|
||||
|
||||
type 'a t =
|
||||
{ intf : 'a
|
||||
; impl : 'a
|
||||
}
|
||||
|
||||
type kind =
|
||||
| Impl
|
||||
| Intf
|
||||
|
||||
let get { intf; impl } = function
|
||||
| Impl -> impl
|
||||
| Intf -> intf
|
||||
;;
|
||||
|
||||
let make_both a = { intf = a; impl = a }
|
||||
|
||||
let iter { intf; impl } ~f =
|
||||
f intf;
|
||||
f impl
|
||||
;;
|
||||
|
||||
let map { intf; impl } ~f = { intf = f intf; impl = f impl }
|
||||
|
||||
let both (type a b) (x : a t) (y : b t) : (a * b) t =
|
||||
{ intf = x.intf, y.intf; impl = x.impl, y.impl }
|
||||
;;
|
||||
681
thirdparty/lsp/lsp/bin/ocaml/ocaml.ml
vendored
Normal file
681
thirdparty/lsp/lsp/bin/ocaml/ocaml.ml
vendored
Normal file
|
|
@ -0,0 +1,681 @@
|
|||
open! Import
|
||||
open! Ts_types
|
||||
|
||||
(* TypeScript to OCaml conversion pipeline. The goal of this pipeline is to do
|
||||
the conversion in logical stages. Unfortunately, this doesn't quite work *)
|
||||
|
||||
(* These declarations are all excluded because we don't support them or their
|
||||
definitions are hand written *)
|
||||
let skipped_ts_decls =
|
||||
[ "InitializedParams"
|
||||
; "NotificationMessage"
|
||||
; "RequestMessage"
|
||||
; "ResponseError"
|
||||
; "DocumentUri"
|
||||
; "ResponseMessage"
|
||||
; "Message"
|
||||
; "ErrorCodes"
|
||||
; "MarkedString"
|
||||
; "ProgressToken"
|
||||
; "ProgressParams"
|
||||
; "TextDocumentFilter"
|
||||
; "PrepareRenameResult"
|
||||
; "LSPAny"
|
||||
; "LSPObject"
|
||||
; "LSPArray"
|
||||
; "LSPErrorCodes"
|
||||
; "NotebookDocumentSyncOptions"
|
||||
; "NotebookDocumentFilter"
|
||||
; "NotebookDocumentSyncRegistrationOptions"
|
||||
; "URI"
|
||||
]
|
||||
;;
|
||||
|
||||
(* XXX this is temporary until we support the [supportsCustomValues] field *)
|
||||
let with_custom_values =
|
||||
[ "FoldingRangeKind"; "CodeActionKind"; "PositionEncodingKind"; "WatchKind" ]
|
||||
;;
|
||||
|
||||
module Expanded = struct
|
||||
(** The expanded form is still working with typescript types. However, all
|
||||
"anonymous" records and sums have been hoisted to the toplevel. So there
|
||||
is a 1-1 correspondence to the OCaml typse we are going to generate *)
|
||||
|
||||
[@@@ocaml.warning "-37"]
|
||||
|
||||
type binding =
|
||||
| Record of Resolved.field list
|
||||
| Interface of Resolved.interface
|
||||
| Poly_enum of Resolved.typ list
|
||||
| Alias of Resolved.typ
|
||||
|
||||
type t = binding Ml.Module.t
|
||||
|
||||
(** Every anonymous record *)
|
||||
let new_binding_of_typ (x : Resolved.typ) : binding option =
|
||||
let record = function
|
||||
| [ { Named.name = _; data = Resolved.Pattern _ } ] -> None
|
||||
| f -> Some (Record f)
|
||||
in
|
||||
match x with
|
||||
| List (Record d) | Record d -> record d
|
||||
| Sum [ _; Record d ] -> record d
|
||||
| _ -> None
|
||||
;;
|
||||
|
||||
class discovered_types =
|
||||
object
|
||||
inherit [binding Named.t list] Resolved.fold as super
|
||||
|
||||
(** Every record valued field introduces a new type
|
||||
|
||||
TODO handle the case where two fields share a type *)
|
||||
method! field f ~init =
|
||||
let init =
|
||||
match f.data with
|
||||
| Pattern _ -> init
|
||||
| Single { optional = _; typ } ->
|
||||
(match new_binding_of_typ typ with
|
||||
| None -> init
|
||||
| Some data ->
|
||||
let new_record = { f with data } in
|
||||
if List.mem ~equal:Poly.equal init new_record
|
||||
then init
|
||||
else new_record :: init)
|
||||
in
|
||||
super#field f ~init
|
||||
end
|
||||
|
||||
let bindings (r : Resolved.t) =
|
||||
let t : binding Named.t =
|
||||
let data =
|
||||
match r.data with
|
||||
| Enum_anon _ -> assert false
|
||||
| Interface i -> Interface i
|
||||
| Type typ ->
|
||||
(match new_binding_of_typ typ with
|
||||
| Some data -> data
|
||||
| None -> Alias typ)
|
||||
in
|
||||
{ data; name = "t" }
|
||||
in
|
||||
let init = [ t ] in
|
||||
match r.data with
|
||||
| Enum_anon _ -> assert false
|
||||
| Type typ -> (new discovered_types)#typ typ ~init
|
||||
| Interface intf -> (new discovered_types)#typ (Record intf.fields) ~init
|
||||
;;
|
||||
|
||||
let of_ts (r : Resolved.t) : t =
|
||||
let name = Ml.Module.Name.of_string (String.capitalize_ascii r.name) in
|
||||
{ Ml.Module.name; bindings = bindings r }
|
||||
;;
|
||||
end
|
||||
|
||||
module Json = Json_gen
|
||||
|
||||
module Module : sig
|
||||
open Ml
|
||||
|
||||
type t = (Module.sig_ Module.t, Module.impl Module.t) Kind.pair
|
||||
|
||||
val add_private_values : t -> Expr.toplevel Named.t list -> t
|
||||
val type_decls : Module.Name.t -> Type.decl Named.t list Kind.Map.t -> t
|
||||
|
||||
(** Use Json.Nullable_option or Json.Assoc.t where appropriate *)
|
||||
val use_json_conv_types : t -> t
|
||||
|
||||
(** Rename fields that are also OCaml keywords *)
|
||||
val rename_invalid_fields : Ml.Kind.t -> Type.decl -> Type.decl
|
||||
|
||||
val pp : t -> unit Pp.t Kind.Map.t
|
||||
end = struct
|
||||
module Module = Ml.Module
|
||||
|
||||
type t = (Module.sig_ Module.t, Module.impl Module.t) Ml.Kind.pair
|
||||
|
||||
let type_decls name (type_decls : Ml.Type.decl Named.t list Ml.Kind.Map.t) : t =
|
||||
let module_ bindings = { Ml.Module.name; bindings } in
|
||||
let intf : Module.sig_ Module.t =
|
||||
List.map type_decls.intf ~f:(fun (td : Ml.Type.decl Named.t) ->
|
||||
{ td with Named.data = (Ml.Module.Type_decl td.data : Ml.Module.sig_) })
|
||||
|> module_
|
||||
in
|
||||
let impl =
|
||||
List.map type_decls.impl ~f:(fun (td : Ml.Type.decl Named.t) ->
|
||||
{ td with Named.data = Ml.Module.Type_decl td.data })
|
||||
|> module_
|
||||
in
|
||||
{ Ml.Kind.intf; impl }
|
||||
;;
|
||||
|
||||
let add_private_values (t : t) bindings : t =
|
||||
let bindings =
|
||||
List.map bindings ~f:(fun (v : _ Named.t) ->
|
||||
{ v with Named.data = Ml.Module.Value v.data })
|
||||
in
|
||||
let impl = { t.impl with bindings = t.impl.bindings @ bindings } in
|
||||
{ t with impl }
|
||||
;;
|
||||
|
||||
let json_assoc_t = Ml.Path.Dot (Dot (Ident "Json", "Assoc"), "t")
|
||||
|
||||
let rename_invalid_fields =
|
||||
let map (kind : Ml.Kind.t) =
|
||||
let open Ml.Type in
|
||||
object (self)
|
||||
inherit [unit, unit] Ml.Type.mapreduce as super
|
||||
method empty = ()
|
||||
method plus () () = ()
|
||||
|
||||
method! field x f =
|
||||
let f =
|
||||
if Ml.is_kw f.name
|
||||
then (
|
||||
let attrs =
|
||||
match kind with
|
||||
| Impl -> ("key", [ sprintf "%S" f.name ]) :: f.attrs
|
||||
| Intf -> f.attrs
|
||||
in
|
||||
{ f with name = f.name ^ "_"; attrs })
|
||||
else f
|
||||
in
|
||||
super#field x f
|
||||
|
||||
method! assoc x k v = self#t x (App (Path json_assoc_t, [ k; v ]))
|
||||
end
|
||||
in
|
||||
fun kind t -> (map kind)#decl () t |> fst
|
||||
;;
|
||||
|
||||
let use_json_conv_types =
|
||||
let map =
|
||||
let open Ml.Type in
|
||||
object (self)
|
||||
inherit [unit, unit] Ml.Type.mapreduce as super
|
||||
method empty = ()
|
||||
method plus () () = ()
|
||||
|
||||
method! optional x t =
|
||||
if t = Json_gen.json_t
|
||||
then super#optional x t
|
||||
else (
|
||||
let opt = Ml.Path.Dot (Dot (Ident "Json", "Nullable_option"), "t") in
|
||||
self#t x (App (Path opt, [ t ])))
|
||||
|
||||
method! field x f =
|
||||
let f =
|
||||
match f.typ with
|
||||
| Optional t ->
|
||||
if t = Json_gen.json_t
|
||||
then { f with attrs = ("yojson.option", []) :: f.attrs }
|
||||
else
|
||||
{ f with
|
||||
attrs =
|
||||
("default", [ "None" ])
|
||||
:: ("yojson_drop_default", [ "( = )" ])
|
||||
:: f.attrs
|
||||
}
|
||||
| _ -> f
|
||||
in
|
||||
super#field x f
|
||||
|
||||
method! assoc x k v = self#t x (App (Path json_assoc_t, [ k; v ]))
|
||||
end
|
||||
in
|
||||
fun (t : t) ->
|
||||
let impl =
|
||||
let bindings =
|
||||
List.map t.impl.bindings ~f:(fun (x : _ Named.t) ->
|
||||
let data =
|
||||
match (x.data : Module.impl) with
|
||||
| Type_decl decl -> Ml.Module.Type_decl (map#decl () decl |> fst)
|
||||
| x -> x
|
||||
in
|
||||
{ x with data })
|
||||
in
|
||||
{ t.impl with bindings }
|
||||
in
|
||||
{ t with impl }
|
||||
;;
|
||||
|
||||
let pp (t : t) ~kind =
|
||||
match (kind : Ml.Kind.t) with
|
||||
| Intf -> Ml.Module.pp_sig t.intf
|
||||
| Impl -> Ml.Module.pp_impl t.impl
|
||||
;;
|
||||
|
||||
let pp t = { Ml.Kind.intf = pp t ~kind:Intf; impl = pp t ~kind:Impl }
|
||||
end
|
||||
|
||||
let enum_module ~allow_other ({ Named.name; data = constrs } as t) =
|
||||
let json_bindings = Json_gen.Enum.conv ~allow_other ~poly:false { t with name = "t" } in
|
||||
let t =
|
||||
let data =
|
||||
let constrs = List.map constrs ~f:(fun (name, _) -> Ml.Type.constr ~name []) in
|
||||
let constrs =
|
||||
if allow_other
|
||||
then
|
||||
(* [String] is a hack. It could be a differnt type, but it isn't in
|
||||
practice *)
|
||||
constrs @ [ Ml.Type.constr ~name:"Other" [ Ml.Type.Prim String ] ]
|
||||
else constrs
|
||||
in
|
||||
Ml.Type.Variant constrs
|
||||
in
|
||||
{ Named.name = "t"; data }
|
||||
in
|
||||
let type_decls = Ml.Kind.Map.make_both [ t ] in
|
||||
let module_ = Module.type_decls (Ml.Module.Name.of_string name) type_decls in
|
||||
Module.add_private_values module_ json_bindings
|
||||
;;
|
||||
|
||||
module Entities = struct
|
||||
type t = (Ident.t * Resolved.t) list
|
||||
|
||||
let find db e : _ Named.t =
|
||||
match List.assoc db e with
|
||||
| Some s -> s
|
||||
| None -> Code_error.raise "Entities.find: unable to find" [ "e", Ident.to_dyn e ]
|
||||
;;
|
||||
|
||||
let of_map map ts =
|
||||
List.map ts ~f:(fun (r : Resolved.t) -> String.Map.find_exn map r.name, r)
|
||||
;;
|
||||
|
||||
let rev_find (db : t) (resolved : Resolved.t) : Ident.t =
|
||||
match
|
||||
List.filter_map db ~f:(fun (id, r) ->
|
||||
if r.name = resolved.name then Some id else None)
|
||||
with
|
||||
| [] -> Code_error.raise "rev_find: resolved not found" []
|
||||
| [ x ] -> x
|
||||
| _ :: _ -> Code_error.raise "re_vind: duplicate entries" []
|
||||
;;
|
||||
end
|
||||
|
||||
module Mapper : sig
|
||||
(* Convert typescript types to OCaml types *)
|
||||
|
||||
val make_typ : Entities.t -> Resolved.typ Named.t -> Ml.Type.t
|
||||
|
||||
type literal_field =
|
||||
{ field_name : string
|
||||
; literal_value : string
|
||||
}
|
||||
|
||||
(** Map a TS record into an OCaml record. Literal valued fields such as kind:
|
||||
'foo' are extracted into a separate list *)
|
||||
val record_
|
||||
: Entities.t
|
||||
-> Resolved.field list Named.t
|
||||
-> Ml.Type.decl Named.t * literal_field list
|
||||
|
||||
(** Extract all untagged unions in field position. These will be turned into
|
||||
polymorphic variants using a naming scheme for the tags. *)
|
||||
val extract_poly_vars : Ml.Type.decl -> Ml.Type.decl * Ml.Type.constr list Named.t list
|
||||
end = struct
|
||||
type literal_field =
|
||||
{ field_name : string
|
||||
; literal_value : string
|
||||
}
|
||||
|
||||
module Type = Ml.Type
|
||||
|
||||
let is_same_as_json =
|
||||
let constrs =
|
||||
[ Prim.Null; String; Bool; Number; Object; List ]
|
||||
|> List.map ~f:(fun s -> Resolved.Ident s)
|
||||
in
|
||||
fun set -> List.for_all constrs ~f:(fun e -> List.mem set e ~equal:Poly.equal)
|
||||
;;
|
||||
|
||||
let id = Type.name "Jsonrpc.Id.t"
|
||||
|
||||
let is_same_as_id =
|
||||
let sort = List.sort ~compare:Poly.compare in
|
||||
let constrs =
|
||||
[ Prim.String; Number ] |> List.map ~f:(fun s -> Resolved.Ident s) |> sort
|
||||
in
|
||||
fun cs -> List.equal ( = ) constrs (sort cs)
|
||||
;;
|
||||
|
||||
(* Any type that includes null needs to be extracted to be converted to an
|
||||
option *)
|
||||
let remove_null cs =
|
||||
let is_null x =
|
||||
match x with
|
||||
| Resolved.Ident Prim.Null -> Either.Left x
|
||||
| _ -> Right x
|
||||
in
|
||||
let nulls, non_nulls = List.partition_map ~f:is_null cs in
|
||||
match nulls with
|
||||
| [] -> `No_null_present
|
||||
| _ :: _ :: _ -> assert false
|
||||
| [ _ ] -> `Null_removed non_nulls
|
||||
;;
|
||||
|
||||
let make_typ db { Named.name; data = t } =
|
||||
let rec type_ topmost_field_name (t : Resolved.typ) =
|
||||
match t with
|
||||
| Ident Uinteger -> Type.int (* XXX shall we use a dedicated uinteger eventually? *)
|
||||
| Ident Number -> Type.int
|
||||
| Ident String -> Type.string
|
||||
| Ident Bool -> Type.bool
|
||||
| Ident Object -> Type.json_object
|
||||
| Ident Self -> Type.t (* XXX wrong *)
|
||||
| Ident Any -> Type.json
|
||||
| Ident Null -> assert false
|
||||
| Ident List -> Type.list Type.json
|
||||
| Ident Uri | Ident (Resolved { id = _; name = "URI" }) ->
|
||||
Type.module_t "DocumentUri"
|
||||
| Ident (Resolved { id = _; name = "LSPAny" }) -> Type.json
|
||||
| Ident (Resolved { id = _; name = "LSPObject" }) -> Type.json_object
|
||||
| Ident (Resolved r) ->
|
||||
let entity = Entities.find db r in
|
||||
Type.module_t entity.name
|
||||
| List (Ident (Uinteger | Number)) when topmost_field_name = Some "data" ->
|
||||
Type.array Type.int
|
||||
| List t -> Type.list (type_ topmost_field_name t)
|
||||
| Tuple ts -> Type.Tuple (List.map ~f:(type_ topmost_field_name) ts)
|
||||
| Sum s -> sum topmost_field_name s
|
||||
| App _ | Literal _ -> Type.void
|
||||
| Record r -> record r
|
||||
and sum topmost_field_name s =
|
||||
if is_same_as_json s
|
||||
then Type.json
|
||||
else (
|
||||
match remove_null s with
|
||||
| `No_null_present -> if is_same_as_id s then id else poly topmost_field_name s
|
||||
| `Null_removed [ s ] -> Type.Optional (type_ topmost_field_name s)
|
||||
| `Null_removed [] -> assert false
|
||||
| `Null_removed cs -> Type.Optional (sum topmost_field_name cs))
|
||||
and simplify_record (fields : Resolved.field list) =
|
||||
(* A record with only a pattern field is simplified to an association
|
||||
list *)
|
||||
match fields with
|
||||
| [ { Named.name; data = Pattern { pat; typ } } ] ->
|
||||
let topmost_field_name = Some name in
|
||||
let key = type_ topmost_field_name pat in
|
||||
let data = type_ topmost_field_name typ in
|
||||
Some (Type.assoc_list ~key ~data)
|
||||
| [] -> Some Type.json_object
|
||||
| _ -> None
|
||||
and record fields =
|
||||
match simplify_record fields with
|
||||
| None -> Type.name name
|
||||
| Some a -> a
|
||||
and poly topmost_field_name s : Ml.Type.t =
|
||||
let type_ = type_ topmost_field_name in
|
||||
try
|
||||
Poly_variant
|
||||
(List.map s ~f:(fun t ->
|
||||
let name, constrs =
|
||||
match (t : Resolved.typ) with
|
||||
| Ident Self | Ident Null -> assert false
|
||||
| Ident String -> "String", [ type_ t ]
|
||||
| Ident Number -> "Int", [ type_ t ]
|
||||
| Ident Object -> "Assoc", [ type_ t ]
|
||||
| Ident Bool -> "Bool", [ type_ t ]
|
||||
| List _ | Ident List -> "List", [ type_ t ]
|
||||
| Ident (Resolved r) -> (Entities.find db r).name, [ type_ t ]
|
||||
| Tuple [ Ident Uinteger; Ident Uinteger ] -> "Offset", [ type_ t ]
|
||||
| Literal (String x) -> x, []
|
||||
| Record _ ->
|
||||
let topmost_field_name = Option.value_exn topmost_field_name in
|
||||
topmost_field_name, [ type_ t ]
|
||||
| _ -> raise Exit
|
||||
in
|
||||
Type.constr ~name constrs))
|
||||
with
|
||||
| Exit -> Type.unit
|
||||
in
|
||||
type_ (Some name) t
|
||||
;;
|
||||
|
||||
let make_field db (field : Resolved.field) =
|
||||
match field.data with
|
||||
| Pattern { pat; typ } ->
|
||||
let key = make_typ db { Named.name = field.name; data = pat } in
|
||||
let data = make_typ db { Named.name = field.name; data = typ } in
|
||||
let typ = Type.assoc_list ~key ~data in
|
||||
Either.Left (Ml.Type.field typ ~name:field.name)
|
||||
| Single { typ = Literal s; optional = false } ->
|
||||
let literal_value =
|
||||
match s with
|
||||
| String s -> s
|
||||
| _ -> assert false
|
||||
in
|
||||
Right { literal_value; field_name = field.name }
|
||||
| Single { typ; optional } ->
|
||||
let typ = make_typ db { Named.name = field.name; data = typ } in
|
||||
let typ = if optional then Type.Optional typ else typ in
|
||||
Left (Ml.Type.field typ ~name:field.name)
|
||||
;;
|
||||
|
||||
let record_ db { Named.name; data = (fields : Resolved.field list) } =
|
||||
let data, literals =
|
||||
match fields with
|
||||
| [ { Named.name; data = Pattern { pat; typ } } ] ->
|
||||
let key = make_typ db { Named.name; data = pat } in
|
||||
let data = make_typ db { Named.name; data = typ } in
|
||||
Type.Alias (Type.assoc_list ~key ~data), []
|
||||
| [] -> Type.Alias Type.json_object, []
|
||||
| _ ->
|
||||
let fields, literals = List.partition_map fields ~f:(make_field db) in
|
||||
Type.Record fields, literals
|
||||
in
|
||||
{ Named.name; data }, literals
|
||||
;;
|
||||
|
||||
let extract_poly_vars s =
|
||||
let extract =
|
||||
object (self)
|
||||
inherit
|
||||
[string option, Ml.Type.constr list Named.t list] Ml.Type.mapreduce as super
|
||||
|
||||
method empty = []
|
||||
|
||||
(* TODO grossly slow *)
|
||||
method plus x y = x @ y
|
||||
|
||||
method! field _ (f : Ml.Type.field) =
|
||||
let env = Some f.name in
|
||||
super#field env f
|
||||
|
||||
method! poly_variant env constrs =
|
||||
match env with
|
||||
| None -> super#poly_variant env constrs
|
||||
| Some name ->
|
||||
(* This hack is needed to avoid collision with user visible types
|
||||
that we might introduce *)
|
||||
let name = name ^ "_pvar" in
|
||||
let replacement = Ml.Type.name name in
|
||||
let constrs, m = self#fold_left_map ~f:(self#constr env) constrs in
|
||||
replacement, self#plus m [ { Named.name; data = constrs } ]
|
||||
end
|
||||
in
|
||||
extract#decl None s
|
||||
;;
|
||||
end
|
||||
|
||||
module Gen : sig
|
||||
val module_ : Entities.t -> Expanded.binding Ml.Module.t -> Module.t
|
||||
end = struct
|
||||
module Type = Ml.Type
|
||||
|
||||
let type_ db ({ Named.name; data = _ } as t) =
|
||||
let main_type =
|
||||
let typ = Mapper.make_typ db t in
|
||||
{ Named.name; data = Type.Alias typ }
|
||||
in
|
||||
[ main_type ]
|
||||
;;
|
||||
|
||||
let record db ({ Named.name = _; data = _ } as t) =
|
||||
let main_type, literals = Mapper.record_ db t in
|
||||
Some (main_type, literals)
|
||||
;;
|
||||
|
||||
let poly_enum { Named.name; data = _ } : Type.decl Named.t list =
|
||||
[ { Named.name; data = Type.Alias Type.unit } ]
|
||||
;;
|
||||
|
||||
let poly_enum_conv (t : _ Named.t) =
|
||||
if List.for_all t.data ~f:(fun (c : Ml.Type.constr) -> List.is_empty c.args)
|
||||
then
|
||||
(* This is equivalent to an enum *)
|
||||
List.map t.data ~f:(fun (c : Ml.Type.constr) -> c.name, Literal.String c.name)
|
||||
|> Named.set_data t
|
||||
|> Json_gen.Enum.conv ~allow_other:false ~poly:true
|
||||
else [ Json_gen.Poly_variant.of_json t; Json_gen.Poly_variant.to_json t ]
|
||||
;;
|
||||
|
||||
(* This is the more complex case *)
|
||||
|
||||
let module_ db { Ml.Module.name; bindings } : Module.t =
|
||||
let type_decls =
|
||||
let add_record = function
|
||||
| None -> []
|
||||
| Some (decl, literals) -> [ `Record (decl, literals) ]
|
||||
in
|
||||
let add_else = List.map ~f:(fun x -> `Type x) in
|
||||
List.concat_map bindings ~f:(fun (r : Expanded.binding Named.t) ->
|
||||
match r.data with
|
||||
| Record data -> record db { r with data } |> add_record
|
||||
| Interface data -> record db { r with data = data.fields } |> add_record
|
||||
| Poly_enum data -> poly_enum { r with data } |> add_else
|
||||
| Alias data -> type_ db { r with data } |> add_else)
|
||||
in
|
||||
let intf : Ml.Module.sig_ Named.t list =
|
||||
List.map type_decls ~f:(function
|
||||
| `Record (t, _) -> t
|
||||
| `Type t -> t)
|
||||
|> List.concat_map ~f:(fun (td : Ml.Type.decl Named.t) ->
|
||||
let td = { td with data = Module.rename_invalid_fields Intf td.data } in
|
||||
[ { td with Named.data = (Ml.Module.Type_decl td.data : Ml.Module.sig_) } ]
|
||||
@ Ml_create.intf_of_type td)
|
||||
in
|
||||
let impl : Ml.Module.impl Named.t list =
|
||||
(* TODO we should make sure to handle duplicate variants extracted *)
|
||||
List.concat_map type_decls ~f:(fun d ->
|
||||
let d, literal_wrapper =
|
||||
match d with
|
||||
| `Record (l, [ lw ]) -> l, Some lw
|
||||
| `Record (l, []) -> l, None
|
||||
| `Record (_, _ :: _) ->
|
||||
assert false
|
||||
(* we don't support multiple literals in a single record for
|
||||
now *)
|
||||
| `Type l -> l, None
|
||||
in
|
||||
let typ_, poly_vars = Mapper.extract_poly_vars (Named.data d) in
|
||||
let poly_vars_and_convs =
|
||||
List.concat_map poly_vars ~f:(fun pv ->
|
||||
let decl =
|
||||
Named.map pv ~f:(fun decl ->
|
||||
Ml.Module.Type_decl (Alias (Poly_variant decl)))
|
||||
in
|
||||
let json_conv =
|
||||
poly_enum_conv pv |> List.map ~f:(Named.map ~f:(fun v -> Ml.Module.Value v))
|
||||
in
|
||||
decl :: json_conv)
|
||||
in
|
||||
let typ_ = { d with data = typ_ } in
|
||||
let literal_wrapper =
|
||||
match literal_wrapper with
|
||||
| None -> []
|
||||
| Some { field_name; literal_value } ->
|
||||
Json_gen.make_literal_wrapper_conv
|
||||
~field_name
|
||||
~literal_value
|
||||
~type_name:typ_.name
|
||||
in
|
||||
let typ_ = { typ_ with data = Module.rename_invalid_fields Impl typ_.data } in
|
||||
let json_convs_for_t =
|
||||
match d.data with
|
||||
| Alias (Poly_variant data) ->
|
||||
poly_enum_conv { d with Named.data }
|
||||
|> List.map ~f:(Named.map ~f:(fun v -> Ml.Module.Value v))
|
||||
| _ -> []
|
||||
in
|
||||
poly_vars_and_convs
|
||||
@ [ { typ_ with data = Ml.Module.Type_decl typ_.data } ]
|
||||
@ json_convs_for_t
|
||||
@ Ml_create.impl_of_type typ_
|
||||
@ literal_wrapper)
|
||||
in
|
||||
let module_ bindings = { Ml.Module.name; bindings } in
|
||||
{ Ml.Kind.intf = module_ intf; impl = module_ impl }
|
||||
;;
|
||||
end
|
||||
|
||||
(* extract all resovled identifiers *)
|
||||
class name_idents =
|
||||
object
|
||||
inherit [Ident.t list] Resolved.fold
|
||||
|
||||
method! ident i ~init =
|
||||
match i with
|
||||
| Resolved r -> r :: init
|
||||
| _ -> init
|
||||
end
|
||||
|
||||
let resolve_typescript (ts : Unresolved.t list) =
|
||||
let ts, db = Typescript.resolve_all ts in
|
||||
let db = Entities.of_map db ts in
|
||||
match
|
||||
let idents = new name_idents in
|
||||
Ident.Top_closure.top_closure
|
||||
ts
|
||||
~key:(fun x -> Entities.rev_find db x)
|
||||
~deps:(fun x -> idents#t x ~init:[] |> List.map ~f:(Entities.find db))
|
||||
with
|
||||
| Error cycle ->
|
||||
let cycle = List.map cycle ~f:(fun (x : Resolved.t) -> x.name) in
|
||||
Code_error.raise "Unexpected cycle" [ "cycle", Dyn.(list string) cycle ]
|
||||
| Ok ts -> db, ts
|
||||
;;
|
||||
|
||||
let of_resolved_typescript db (ts : Resolved.t list) =
|
||||
let simple_enums, everything_else =
|
||||
List.filter_partition_map ts ~f:(fun (t : Resolved.t) ->
|
||||
if List.mem skipped_ts_decls t.name ~equal:String.equal
|
||||
then Skip
|
||||
else (
|
||||
match t.data with
|
||||
| Enum_anon data -> Left { t with data }
|
||||
| Interface _ | Type _ -> Right t))
|
||||
in
|
||||
let simple_enums =
|
||||
List.map simple_enums ~f:(fun (t : _ Named.t) ->
|
||||
(* "open" enums need an `Other constructor *)
|
||||
let allow_other = List.mem ~equal:String.equal with_custom_values t.name in
|
||||
let data =
|
||||
List.filter_map t.data ~f:(fun (constr, v) ->
|
||||
match (v : Ts_types.Enum.case) with
|
||||
| Literal l -> Some (constr, l)
|
||||
| Alias _ ->
|
||||
(* TODO we don't handle these for now *)
|
||||
None)
|
||||
in
|
||||
enum_module ~allow_other { t with data })
|
||||
in
|
||||
let everything_else =
|
||||
List.map everything_else ~f:(fun (t : _ Named.t) ->
|
||||
let mod_ = Expanded.of_ts t in
|
||||
Gen.module_ db mod_)
|
||||
in
|
||||
simple_enums @ everything_else
|
||||
|> List.map ~f:(fun (decl : _ Ml.Kind.pair) ->
|
||||
let decl =
|
||||
let intf = Json_gen.add_json_conv_for_t decl.intf in
|
||||
{ decl with intf }
|
||||
in
|
||||
Module.use_json_conv_types decl)
|
||||
;;
|
||||
|
||||
let of_typescript ts =
|
||||
let db, ts = resolve_typescript ts in
|
||||
of_resolved_typescript db ts
|
||||
;;
|
||||
7
thirdparty/lsp/lsp/bin/ocaml/ocaml.mli
vendored
Normal file
7
thirdparty/lsp/lsp/bin/ocaml/ocaml.mli
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
module Module : sig
|
||||
type t = (Ml.Module.sig_ Ml.Module.t, Ml.Module.impl Ml.Module.t) Ml.Kind.pair
|
||||
|
||||
val pp : t -> unit Pp.t Ml.Kind.Map.t
|
||||
end
|
||||
|
||||
val of_typescript : Ts_types.Unresolved.t list -> Module.t list
|
||||
199
thirdparty/lsp/lsp/bin/ocaml/w.ml
vendored
Normal file
199
thirdparty/lsp/lsp/bin/ocaml/w.ml
vendored
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
open Import
|
||||
open Pp.O
|
||||
open Pp
|
||||
|
||||
type t = unit Pp.t
|
||||
type w = t
|
||||
|
||||
(* This module contains all the writing primitives *)
|
||||
|
||||
let ident = verbatim
|
||||
let i = verbatim
|
||||
let quoted s = i (sprintf "%S" s)
|
||||
|
||||
let surround delim a =
|
||||
let start, finish =
|
||||
match delim with
|
||||
| `Paren -> i "(", i ")"
|
||||
| `Curly -> i "{", i "}"
|
||||
| `Square -> i "[", i "]"
|
||||
in
|
||||
Pp.concat [ start; a; finish ]
|
||||
;;
|
||||
|
||||
module Json = struct
|
||||
let invalid_pat name = ident "json", Pp.textf "Json.error \"invalid %s\" json" name
|
||||
let typ = "Json.t"
|
||||
|
||||
module Literal = struct
|
||||
let str n = sprintf "`String %S" n
|
||||
let int i = sprintf "`Int (%d)" i
|
||||
let null = "`Null"
|
||||
let bool b = sprintf "`Bool %b" b
|
||||
end
|
||||
|
||||
let str = sprintf "`String %s"
|
||||
let int = sprintf "`Int %s"
|
||||
let bool = sprintf "`Bool %s"
|
||||
end
|
||||
|
||||
module Gen = struct
|
||||
let record ~delim fields =
|
||||
let sep = Pp.concat [ Pp.verbatim ";"; Pp.newline ] in
|
||||
Pp.text "{"
|
||||
++ Pp.concat_map ~sep fields ~f:(fun (name, f) ->
|
||||
Pp.concat [ Pp.textf "%s %s " name delim; f ])
|
||||
++ Pp.verbatim "}"
|
||||
;;
|
||||
|
||||
let clause ~delim l r = Pp.concat [ l; Pp.verbatim (sprintf " %s " delim); r ]
|
||||
end
|
||||
|
||||
module Attr = struct
|
||||
type t =
|
||||
{ name : string
|
||||
; payload : w list
|
||||
}
|
||||
|
||||
let make name payload = { name; payload }
|
||||
|
||||
let pp kind { name; payload } =
|
||||
let kind =
|
||||
match kind with
|
||||
| `Field -> "@"
|
||||
| `Type -> "@@"
|
||||
in
|
||||
Pp.concat [ i kind; i name; Pp.space; Pp.concat ~sep:Pp.space payload ]
|
||||
|> surround `Square
|
||||
;;
|
||||
end
|
||||
|
||||
module Type = struct
|
||||
let string = i "string"
|
||||
let int = i "int"
|
||||
let name = i
|
||||
let bool = i "bool"
|
||||
let gen_decl kw name body = Pp.concat [ Pp.textf "%s %s =" kw name; Pp.newline; body ]
|
||||
let and_ name body = gen_decl "and" name body
|
||||
let decl name body = gen_decl "type" name body
|
||||
let record fields = Gen.record ~delim:":" fields
|
||||
|
||||
let field_attrs ~field ~attrs =
|
||||
match attrs with
|
||||
| [] -> field
|
||||
| attrs ->
|
||||
let attrs = Pp.concat_map attrs ~sep:Pp.space ~f:(Attr.pp `Field) in
|
||||
Pp.concat [ field; Pp.space; attrs ]
|
||||
;;
|
||||
|
||||
let var typ = Pp.textf "'%s" typ
|
||||
|
||||
let app typ = function
|
||||
| [] -> assert false
|
||||
| [ x ] -> Pp.concat [ x; Pp.space; typ ]
|
||||
| xs ->
|
||||
let args =
|
||||
let sep = Pp.verbatim "," in
|
||||
Pp.concat [ Pp.verbatim "("; Pp.concat ~sep xs; Pp.verbatim ")" ]
|
||||
in
|
||||
Pp.concat [ args; Pp.space; typ ]
|
||||
;;
|
||||
|
||||
let tuple fields =
|
||||
let sep = i "*" in
|
||||
i "(" ++ Pp.concat ~sep fields ++ i ")"
|
||||
;;
|
||||
|
||||
let rec_decls xs =
|
||||
match xs with
|
||||
| [] -> Pp.concat []
|
||||
| (name, body) :: xs ->
|
||||
decl name body
|
||||
++ newline
|
||||
++ Pp.concat_map xs ~sep:Pp.newline ~f:(fun (name, body) -> and_ name body)
|
||||
;;
|
||||
|
||||
let deriving td ~record =
|
||||
let fields = if record then space ++ i "[@@yojson.allow_extra_fields]" else space in
|
||||
Pp.concat
|
||||
[ td
|
||||
; Pp.newline
|
||||
; Pp.text "[@@deriving_inline yojson]"
|
||||
; fields
|
||||
; space
|
||||
; Pp.text "[@@@end]"
|
||||
]
|
||||
;;
|
||||
|
||||
let opt_attr = ident "option [@yojson.option]"
|
||||
let opt_field f = Pp.seq f opt_attr
|
||||
let default f def = Pp.concat [ f; ident "[@default "; ident def; ident "]" ]
|
||||
let key name = concat [ ident "[@key "; quoted name; ident "]" ]
|
||||
|
||||
let gen_variant ~poly constrs =
|
||||
let sep = Pp.concat [ Pp.newline; i "| " ] in
|
||||
Pp.concat_map constrs ~sep ~f:(fun (name, arg) ->
|
||||
let name =
|
||||
let name = String.capitalize_ascii name in
|
||||
if poly then "`" ^ name else name
|
||||
in
|
||||
match arg with
|
||||
| [] -> i name
|
||||
| xs ->
|
||||
let xs =
|
||||
match xs with
|
||||
| [ x ] -> x
|
||||
| xs -> tuple xs
|
||||
in
|
||||
Gen.clause ~delim:"of" (ident name) xs)
|
||||
;;
|
||||
|
||||
let poly constrs = concat [ i "["; gen_variant ~poly:true constrs; i "]" ]
|
||||
let variant constrs = gen_variant ~poly:false constrs
|
||||
end
|
||||
|
||||
let gen_module kw name body =
|
||||
Pp.concat
|
||||
[ Pp.textf "module %s %s" name kw
|
||||
; Pp.newline
|
||||
; body
|
||||
; newline
|
||||
; verbatim "end"
|
||||
; newline
|
||||
]
|
||||
;;
|
||||
|
||||
module Sig = struct
|
||||
let module_ name body = gen_module ": sig" name body
|
||||
|
||||
let include_ name destructive_subs =
|
||||
let inc_ = Pp.textf "include %s" name in
|
||||
match destructive_subs with
|
||||
| [] -> inc_
|
||||
| substs ->
|
||||
let substs =
|
||||
let sep = Pp.text " and " in
|
||||
Pp.concat_map ~sep substs ~f:(fun (l, r) ->
|
||||
Pp.concat
|
||||
[ Pp.text "type"; Pp.space; l; Pp.space; Pp.verbatim ":="; Pp.space; r ])
|
||||
in
|
||||
Pp.concat [ inc_; Pp.space; Pp.text "with"; Pp.space; substs ]
|
||||
;;
|
||||
|
||||
let val_ name b =
|
||||
let sep = Pp.concat [ space; i "->"; space ] in
|
||||
let b = Pp.concat ~sep b in
|
||||
Pp.concat [ textf "val %s : " name; b; Pp.newline ]
|
||||
;;
|
||||
|
||||
let assoc k v = Pp.concat [ Type.tuple [ k; v ]; Pp.space; i "list" ]
|
||||
end
|
||||
|
||||
let warnings codes = seq (textf "[@@@warning %S]" codes) newline
|
||||
|
||||
let opens names =
|
||||
Pp.concat_map names ~f:(fun name -> Pp.concat [ textf "open! %s" name; newline ])
|
||||
;;
|
||||
|
||||
let module_ name body = gen_module "= struct" name body
|
||||
let record fields = Gen.record ~delim:"=" fields
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue