Skip to content

Commit 728f82a

Browse files
committed
Allow to configure merlin config server
Before this commit it was only possible to configure ocamllsp either with .merlin files or with 'dune ocaml-merlin' command which starts a configuration server. This commit extends ocamllsp to allow to be configured with merlin config server, other than `dune ocaml-merin`. This is done by reading the following environment variables: - $OCAMLLSP_PROJECT_BUILD_SYSTEM specifies the project's build system, which should be an executable available on $PATH. The convention is there's a subcommand `$OCAMLLSP_PROJECT_BUILD_SYSTEM ocaml-merlin` which start merlin configuration server. - $OCAMLLSP_PROJECT_ROOT specifies project root In absence of these new environment variables ocamllsp falls back to the current behaviour.
1 parent f3814f6 commit 728f82a

File tree

3 files changed

+63
-24
lines changed

3 files changed

+63
-24
lines changed

CHANGES.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88

99
- Add `destruct` custom request (#1583)
1010

11+
- Allow to configure merlin using build systems other than dune.
12+
13+
If `$OCAMLLSP_PROJECT_BUILD_SYSTEM` environment variable is set then OCaml
14+
LSP will execute `$OCAMLLSP_PROJECT_BUILD_SYSTEM ocaml-merlin` command to get
15+
merlin configuration for a module. It is expected the command to communicate
16+
using `merlin-dot-protocol`.
17+
18+
If `$OCAMLLSP_PROJECT_ROOT` environment variable is set then OCaml LSP will
19+
use it as a project root directory.
20+
1121
# 1.25.0
1222

1323
## Features

ocaml-lsp-server/src/merlin_config.ml

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ let empty = Mconfig_dot.empty_config
3535
module Process = struct
3636
type nonrec t =
3737
{ pid : Pid.t
38+
; prog : string
3839
; initial_cwd : string
3940
; stdin : Lev_fiber.Io.output Lev_fiber.Io.t
4041
; stdout : Lev_fiber.Io.input Lev_fiber.Io.t
@@ -52,30 +53,42 @@ module Process = struct
5253
| Unix.WEXITED n ->
5354
(match n with
5455
| 0 -> ()
55-
| n -> Format.eprintf "dune finished with code = %d@.%!" n)
56-
| WSIGNALED s -> Format.eprintf "dune finished signal = %d@.%!" s
56+
| n -> Format.eprintf "%s finished with code = %d@.%!" t.prog n)
57+
| WSIGNALED s -> Format.eprintf "%s finished signal = %d@.%!" t.prog s
5758
| WSTOPPED _ -> ());
5859
Format.eprintf "closed merlin process@.%s@." (Dyn.to_string @@ to_dyn t);
5960
Lev_fiber.Io.close t.stdin;
6061
Lev_fiber.Io.close t.stdout
6162
;;
6263

6364
let start ~dir =
64-
match Bin.which "dune" with
65+
let bin, args =
66+
(* the convention is that $OCAMLLSP_PROJECT_BUILD_SYSTEM executable has
67+
`ocaml-merlin` subcommand to start a merlin configuration server *)
68+
match Sys.getenv_opt "OCAMLLSP_PROJECT_BUILD_SYSTEM" with
69+
| None -> "dune", [ "ocaml-merlin"; "--no-print-directory" ]
70+
| Some bin -> bin, [ "ocaml-merlin" ]
71+
in
72+
match Bin.which bin with
6573
| None ->
6674
Jsonrpc.Response.Error.raise
6775
(Jsonrpc.Response.Error.make
6876
~code:InternalError
69-
~message:"dune binary not found"
77+
~message:(Printf.sprintf "%s binary not found" bin)
7078
())
7179
| Some prog ->
7280
let stdin_r, stdin_w = Unix.pipe () in
7381
let stdout_r, stdout_w = Unix.pipe () in
7482
Unix.set_close_on_exec stdin_w;
7583
let pid =
76-
let argv = [ prog; "ocaml-merlin"; "--no-print-directory" ] in
7784
Pid.of_int
78-
(Spawn.spawn ~cwd:(Path dir) ~prog ~argv ~stdin:stdin_r ~stdout:stdout_w ())
85+
(Spawn.spawn
86+
~cwd:(Path dir)
87+
~prog
88+
~argv:(prog :: args)
89+
~stdin:stdin_r
90+
~stdout:stdout_w
91+
())
7992
in
8093
Unix.close stdin_r;
8194
Unix.close stdout_w;
@@ -94,7 +107,7 @@ module Process = struct
94107
let* stdin = make stdin_w Output in
95108
let+ stdout = make stdout_r Input in
96109
let session = Lev_fiber_csexp.Session.create ~socket:false stdout stdin in
97-
{ pid; initial_cwd = dir; stdin; stdout; session }
110+
{ prog; pid; initial_cwd = dir; stdin; stdout; session }
98111
;;
99112
end
100113

@@ -146,7 +159,8 @@ module Entry = struct
146159
else (
147160
Table.remove t.db.running t.process.initial_cwd;
148161
Format.eprintf
149-
"halting dune merlin process@.%s@."
162+
"halting %s merlin process@.%s@."
163+
t.process.prog
150164
(Dyn.to_string (Process.to_dyn t.process));
151165
Dot_protocol_io.Commands.halt t.process.session)
152166
;;
@@ -211,10 +225,13 @@ let get_config (p : Process.t) ~workdir path_abs =
211225
Mconfig_dot.postprocess_config cfg, failures
212226
| Error (Merlin_dot_protocol.Unexpected_output msg) -> empty, [ msg ]
213227
| Error (Csexp_parse_error _) ->
214-
( empty
215-
, [ "ocamllsp could not load its configuration from the external reader. Building \
216-
your project with `dune` might solve this issue."
217-
] )
228+
let suggest =
229+
Printf.sprintf
230+
"ocamllsp could not load its configuration from the external reader. Building \
231+
your project with `%s` might solve this issue."
232+
p.prog
233+
in
234+
empty, [ suggest ]
218235
;;
219236

220237
let file_exists fname =
@@ -223,7 +240,17 @@ let file_exists fname =
223240
| s -> s.st_kind <> S_DIR
224241
;;
225242

226-
let find_project_context start_dir =
243+
let check_project_root_markers ~workdir ~dir markers =
244+
List.find_map markers ~f:(fun f ->
245+
let fname = Filename.concat dir f in
246+
if file_exists fname
247+
then (
248+
let workdir = Misc.canonicalize_filename (Option.value ~default:dir workdir) in
249+
Some ({ workdir; process_dir = dir }, fname))
250+
else None)
251+
;;
252+
253+
let find_dune_project_context start_dir =
227254
(* The workdir is the first directory we find which contains a [dune] file. We
228255
need to keep track of this folder because [dune ocaml-merlin] might be
229256
started from a folder that is a parent of the [workdir]. Thus we cannot
@@ -235,15 +262,9 @@ let find_project_context start_dir =
235262
let fnames = List.map ~f:(Filename.concat dir) [ "dune"; "dune-file" ] in
236263
if List.exists ~f:file_exists fnames then Some dir else None
237264
in
238-
let rec loop workdir dir =
265+
let rec loop ~workdir ~dir =
239266
match
240-
List.find_map [ "dune-project"; "dune-workspace" ] ~f:(fun f ->
241-
let fname = Filename.concat dir f in
242-
if file_exists fname
243-
then (
244-
let workdir = Misc.canonicalize_filename (Option.value ~default:dir workdir) in
245-
Some ({ workdir; process_dir = dir }, fname))
246-
else None)
267+
check_project_root_markers [ "dune-project"; "dune-workspace" ] ~workdir ~dir
247268
with
248269
| Some s -> Some s
249270
| None ->
@@ -252,10 +273,18 @@ let find_project_context start_dir =
252273
then (
253274
(* Was this directory the workdir ? *)
254275
let workdir = map_workdir dir workdir in
255-
loop workdir parent)
276+
loop ~workdir ~dir:parent)
256277
else None
257278
in
258-
loop None start_dir
279+
loop ~workdir:None ~dir:start_dir
280+
;;
281+
282+
let find_project_context start_dir =
283+
match Sys.getenv_opt "OCAMLLSP_PROJECT_ROOT" with
284+
| Some dir ->
285+
let dir = Misc.canonicalize_filename dir in
286+
Some ({ workdir = dir; process_dir = dir }, "<merlin-config>")
287+
| None -> find_dune_project_context start_dir
259288
;;
260289

261290
type nonrec t =

ocaml-lsp-server/src/merlin_config.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
(** Fetch merlin configuration with dune subprocesses *)
1+
(** Fetch merlin configuration from a project build system (usually dune) *)
22

33
open Import
44

0 commit comments

Comments
 (0)