this repo has no description

feat(x-ocaml): wire jtw backend and enable type-on-hover in playground

- Fix client.ml absolute_url to handle root-relative paths (/ prefix)
using window.location.origin instead of page directory path
- Abstract merlin_ext.ml to use a post function closure instead of
concrete Client.t, decoupling from specific backend
- Update cell.ml/mli to accept eval/fmt/post function closures
instead of Client.t, and split init/start to avoid synchronous
response race condition with jtw backend
- Wire x_ocaml.ml to read backend attribute and dispatch through
Backend module (jtw or builtin)
- Add W.setup call after W.init in jtw_client to load stdlib
- Create rpc_worker.ml: full-featured JSON-RPC worker combining
the complete S module (findlibish, stdlib) with JSON-RPC protocol

Verified: code execution and Merlin type-on-hover both work in the
scrollycode playground overlay (hovering shows type tooltips like
"type bool = false | true").

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+103
+11
example/dune
··· 41 41 (libraries js_top_worker-web logs.browser mime_printer tyxml)) 42 42 43 43 (executable 44 + (name rpc_worker) 45 + (modes js) 46 + (modules rpc_worker) 47 + (link_flags (-linkall)) 48 + (preprocess (pps js_of_ocaml-ppx)) 49 + (js_of_ocaml 50 + (javascript_files ../lib/stubs.js) 51 + (flags --effects=disabled --toplevel --opt 3 +toplevel.js +dynlink.js)) 52 + (libraries js_top_worker js_top_worker-web js_top_worker-rpc logs.browser mime_printer tyxml)) 53 + 54 + (executable 44 55 (name unix_worker) 45 56 (public_name unix_worker) 46 57 (modes byte)
+92
example/rpc_worker.ml
··· 1 + (** Full-featured JSON-RPC worker for x-ocaml integration. 2 + 3 + Uses the same full S module as worker.ml (with Findlibish, sync/async 4 + get, etc.) but speaks JSON-RPC instead of the message protocol. *) 5 + 6 + open Js_top_worker_rpc 7 + open Js_top_worker 8 + module Server = Toplevel_api_gen.Make (Impl.IdlM.GenServer ()) 9 + 10 + let server process e = 11 + let _, id, call = Jsonrpc.version_id_and_call_of_string e in 12 + Lwt.bind (process call) (fun response -> 13 + let rtxt = Jsonrpc.string_of_response ~id response in 14 + Js_of_ocaml.Worker.post_message (Js_of_ocaml.Js.string rtxt); 15 + Lwt.return ()) 16 + 17 + module S : Impl.S = struct 18 + type findlib_t = Js_top_worker_web.Findlibish.t 19 + 20 + let capture : (unit -> 'a) -> unit -> Impl.captured * 'a = 21 + fun f () -> 22 + let stdout_buff = Buffer.create 1024 in 23 + let stderr_buff = Buffer.create 1024 in 24 + Js_of_ocaml.Sys_js.set_channel_flusher stdout 25 + (Buffer.add_string stdout_buff); 26 + Js_of_ocaml.Sys_js.set_channel_flusher stderr 27 + (Buffer.add_string stderr_buff); 28 + let x = f () in 29 + let captured = 30 + { 31 + Impl.stdout = Buffer.contents stdout_buff; 32 + stderr = Buffer.contents stderr_buff; 33 + } 34 + in 35 + (captured, x) 36 + 37 + let sync_get = Js_top_worker_web.Jslib.sync_get 38 + let async_get = Js_top_worker_web.Jslib.async_get 39 + 40 + let create_file ~name ~content = 41 + try Js_of_ocaml.Sys_js.create_file ~name ~content 42 + with Sys_error _ -> () 43 + 44 + let get_stdlib_dcs uri = 45 + Js_top_worker_web.Findlibish.fetch_dynamic_cmis sync_get uri 46 + |> Result.to_list 47 + 48 + let import_scripts urls = 49 + let absolute_urls = List.map Js_top_worker_web.Jslib.map_url urls in 50 + Js_of_ocaml.Worker.import_scripts absolute_urls 51 + 52 + let findlib_init = Js_top_worker_web.Findlibish.init async_get 53 + 54 + let require b v = function 55 + | [] -> [] 56 + | packages -> 57 + Js_top_worker_web.Findlibish.require ~import_scripts sync_get b v 58 + packages 59 + 60 + let init_function func_name = 61 + let open Js_of_ocaml in 62 + let func = Js.Unsafe.js_expr func_name in 63 + fun () -> Js.Unsafe.fun_call func [| Js.Unsafe.inject Dom_html.window |] 64 + 65 + let path = "/static/cmis" 66 + end 67 + 68 + module M = Impl.Make (S) 69 + 70 + let run () = 71 + let open Js_of_ocaml in 72 + let open M in 73 + Console.console##log (Js.string "RPC worker starting..."); 74 + Logs.set_reporter (Logs_browser.console_reporter ()); 75 + Logs.set_level (Some Logs.Debug); 76 + Server.init (Impl.IdlM.T.lift init); 77 + Server.create_env (Impl.IdlM.T.lift create_env); 78 + Server.destroy_env (Impl.IdlM.T.lift destroy_env); 79 + Server.list_envs (Impl.IdlM.T.lift list_envs); 80 + Server.setup (Impl.IdlM.T.lift setup); 81 + Server.exec execute; 82 + Server.complete_prefix complete_prefix; 83 + Server.query_errors query_errors; 84 + Server.type_enclosing type_enclosing; 85 + Server.exec_toplevel exec_toplevel; 86 + let rpc_fn = Impl.IdlM.server Server.implementation in 87 + Worker.set_onmessage (fun x -> 88 + let s = Js.to_string x in 89 + ignore (server rpc_fn s)); 90 + Console.console##log (Js.string "RPC worker ready") 91 + 92 + let () = run ()