···11# merlin-js
2233Try-it: [https://voodoos.github.io/merlin-js](https://voodoos.github.io/merlin-js)
44+55+To run locally, run 'make' then launch an http server from the 'examples' directory. For example:
66+77+```sh
88+make
99+cd examples
1010+python3 -m http.server
1111+```
1212+1313+
···6666 match data with
6767 | Protocol.Typed_enclosings l -> l
6868 | _ -> assert false
6969+7070+let add_cmis worker cmis =
7171+ let open Fut.Syntax in
7272+ let action = Protocol.Add_cmis cmis in
7373+ let+ data : Protocol.answer = query ~action worker in
7474+ Console.(log ["Received response from adding cmis:"; data]);
7575+ match data with
7676+ | Protocol.Added_cmis -> ()
7777+ | _ -> assert false
+11-2
src/extension/merlin_codemirror.ml
···7979let ocaml = Jv.get Jv.global "__CM__mllike" |> Stream.Language.of_jv
8080let ocaml = Stream.Language.define ocaml
81818282-module Make (Config : sig val worker_url : string end) = struct
8383- let worker = Merlin_client.make_worker Config.worker_url
8282+module type Config = sig
8383+ val worker_url : string
8484+ val cmis : Protocol.cmis
8585+end
8686+8787+module Make (Config : Config) = struct
8888+ let worker =
8989+ let worker = Merlin_client.make_worker Config.worker_url in
9090+ let _ = Merlin_client.add_cmis worker Config.cmis in
9191+ worker
9292+8493 let autocomplete = autocomplete worker
8594 let tooltip_on_hover = tooltip_on_hover worker
8695 let linter = Lint.create (linter worker)
+12-1
src/extension/merlin_codemirror.mli
···66val ocaml : Code_mirror.Extension.t
77(** An extension providing OCaml syntax highlighting *)
8899-module Make : functor (Config : sig val worker_url : string end) -> sig
99+module type Config = sig
1010+ val worker_url : string
1111+ (** The url of the worker javascript file *)
1212+1313+ val cmis : Protocol.cmis
1414+ (** CMIs are required for merlin to work correctly. These can either be
1515+ provided statically or provided as a list of URLs from which the
1616+ CMIs can be downloaded. If using URLs, these will only be
1717+ downloaded on demand. *)
1818+end
1919+2020+module Make : functor (Config : Config) -> sig
1021 val autocomplete : Code_mirror.Extension.t
1122 (** An extension providing completions when typing *)
1223
+39
src/protocol/protocol.ml
···3344type source = string
5566+(** CMIs are provided either statically or as URLs to be downloaded on demand *)
77+88+(** Dynamic cmis are loaded from beneath the given url. In addition the
99+ top-level modules are specified, and prefixes for other modules. For
1010+ example, for the OCaml standard library, a user might pass:
1111+1212+ {[
1313+ { dcs_url="/static/stdlib";
1414+ dcs_toplevel_modules=["Stdlib"];
1515+ dcs_file_prefixes=["stdlib__"]; }
1616+ ]}
1717+1818+ In which case, merlin will expect to be able to download a valid file
1919+ from the url ["/static/stdlib/stdlib.cmi"] corresponding to the
2020+ specified toplevel module, and it will also attempt to download any
2121+ module with the prefix ["Stdlib__"] from the same base url, so for
2222+ example if an attempt is made to look up the module ["Stdlib__Foo"]
2323+ then merlin-js will attempt to download a file from the url
2424+ ["/static/stdlib/stdlib__Foo.cmi"].
2525+ *)
2626+2727+type dynamic_cmis = {
2828+ dcs_url : string;
2929+ dcs_toplevel_modules : string list;
3030+ dcs_file_prefixes : string list;
3131+}
3232+3333+type static_cmi = {
3434+ sc_name : string; (* capitalised, e.g. 'Stdlib' *)
3535+ sc_content : string;
3636+}
3737+3838+type cmis = {
3939+ static_cmis : static_cmi list;
4040+ dynamic_cmis : dynamic_cmis option;
4141+}
4242+643type action =
744 | Complete_prefix of source * Msource.position
845 | Type_enclosing of source * Msource.position
946 | All_errors of source
4747+ | Add_cmis of cmis
10481149type error = {
1250 kind : Location.report_kind;
···3169 | Completions of completions
3270 | Typed_enclosings of
3371 (Location.t * [ `Index of int | `String of string ] * is_tail_position) list
7272+ | Added_cmis
34733574let report_source_to_string = function
3675 | Location.Lexer -> "lexer"
···1515 let stdlib = Filename.concat cwd "stdlib" in
1616 let out = open_out "static_files.ml" in
17171818- Printf.fprintf out "let stdlib_cmis = [";
1818+ Printf.fprintf out "open Protocol\nlet stdlib_cmis = [";
1919 let dir = Unix.opendir stdlib in
2020 iter_cmi ~f:(fun file ->
2121 let fullpath = Filename.concat stdlib file in
2222- Printf.fprintf out "(%S,[%%blob %S]);" fullpath fullpath) dir;
2222+ let module_name = Filename.basename file |> String.capitalize_ascii in
2323+ Printf.fprintf out "{sc_name=%S; sc_content=[%%blob %S]};" module_name fullpath) dir;
2324 Printf.fprintf out "]\n";
24252526 close_out out
+81-7
src/worker/worker.ml
···33open Merlin_kernel
44module Location = Ocaml_parsing.Location
5566+let sync_get url =
77+ let open Js_of_ocaml in
88+ let x = XmlHttpRequest.create () in
99+ x##.responseType := Js.string "arraybuffer";
1010+ x##_open (Js.string "GET") (Js.string url) Js._false;
1111+ x##send Js.null;
1212+ match x##.status with
1313+ | 200 ->
1414+ Js.Opt.case
1515+ (File.CoerceTo.arrayBuffer x##.response)
1616+ (fun () ->
1717+ Firebug.console##log (Js.string "Failed to receive file");
1818+ None)
1919+ (fun b -> Some (Typed_array.String.of_arrayBuffer b))
2020+ | _ -> None
2121+2222+let filename_of_module unit_name =
2323+ Printf.sprintf "%s.cmi" (String.uncapitalize_ascii unit_name)
2424+2525+let reset_dirs () =
2626+ Ocaml_utils.Directory_content_cache.clear ();
2727+ let open Ocaml_utils.Load_path in
2828+ let dirs = get_paths () in
2929+ reset ();
3030+ List.iter ~f:(fun p ->
3131+ prepend_dir (Dir.create p)) dirs
3232+3333+let add_dynamic_cmis dcs =
3434+ let open Ocaml_typing.Persistent_env.Persistent_signature in
3535+ let old_loader = !load in
3636+3737+ let fetch =
3838+ (fun filename ->
3939+ let open Option.Infix in
4040+ let url = Filename.concat dcs.Protocol.dcs_url filename in
4141+ sync_get url)
4242+ in
4343+4444+ List.iter ~f:(fun name ->
4545+ let filename = filename_of_module name in
4646+ match fetch (filename_of_module name) with
4747+ | Some content ->
4848+ let name = Filename.(concat "/static/stdlib" filename) in
4949+ Js_of_ocaml.Sys_js.create_file ~name ~content
5050+ | None -> ()) dcs.dcs_toplevel_modules;
5151+5252+ let new_load ~unit_name =
5353+ let filename = filename_of_module unit_name in
5454+ let fs_name = Filename.(concat "/static/stdlib" filename) in
5555+ (* Check if it's already been downloaded. This will be the
5656+ case for all toplevel cmis. Also check whether we're supposed
5757+ to handle this cmi *)
5858+ if
5959+ not (Sys.file_exists fs_name) &&
6060+ List.exists ~f:(fun prefix ->
6161+ String.starts_with ~prefix filename) dcs.dcs_file_prefixes
6262+ then begin
6363+ match fetch filename with
6464+ | Some x ->
6565+ Js_of_ocaml.Sys_js.create_file ~name:fs_name ~content:x;
6666+ (* At this point we need to tell merlin that the dir contents
6767+ have changed *)
6868+ reset_dirs ()
6969+ | None ->
7070+ Printf.eprintf "Warning: Expected to find cmi at: %s\n%!"
7171+ (Filename.concat dcs.Protocol.dcs_url filename)
7272+ end;
7373+ old_loader ~unit_name
7474+ in
7575+ load := new_load
7676+7777+ let add_cmis { Protocol.static_cmis; dynamic_cmis } =
7878+ List.iter static_cmis ~f:(fun { Protocol.sc_name; sc_content } ->
7979+ let filename = Printf.sprintf "%s.cmi" (String.uncapitalize_ascii sc_name) in
8080+ let name = Filename.(concat "/static/stdlib" filename) in
8181+ Js_of_ocaml.Sys_js.create_file ~name ~content:sc_content);
8282+ Option.iter ~f:add_dynamic_cmis dynamic_cmis;
8383+ Protocol.Added_cmis
8484+685let config =
786 let initial = Mconfig.initial in
887 { initial with
···174253 })
175254 in
176255 Protocol.Errors errors
256256+ | Add_cmis cmis ->
257257+ add_cmis cmis
177258 in
178259 let res = Marshal.to_bytes res [] in
179260 Js_of_ocaml.Worker.post_message res
180261181181-182262let run () =
183183- (* Load the CMIs into the pseudo file-system *)
184184- (* This add roughly 3mo to the final script. These could be loaded dynamically
185185- after the worker *)
186186- List.iter Static_files.stdlib_cmis ~f:(fun ( path, content) ->
187187- let name = Filename.(concat "/static/stdlib" (basename path)) in
188188- Js_of_ocaml.Sys_js.create_file ~name ~content);
189263 Js_of_ocaml.Worker.set_onmessage on_message