this repo has no description
at main 280 lines 11 kB view raw
1(** Bridge between x-ocaml's X_protocol and js_top_worker's message protocol. 2 3 This module translates X_protocol requests into js_top_worker message 4 protocol calls and converts the results back into X_protocol responses. *) 5 6module Jtw = Js_top_worker_client_msg 7module Msg = Js_top_worker_message.Message 8 9type t = { 10 mutable client : Jtw.t; 11 url : string; 12 mutable init_config : Msg.init_config option; 13 mutable on_message_cb : X_protocol.response -> unit; 14} 15 16let is_cross_origin url = 17 try 18 let page_origin = Jv.to_string (Jv.get (Brr.Window.to_jv Brr.G.window) "origin") in 19 let url_obj = Jv.new' (Jv.get Jv.global "URL") [| Jv.of_string url |] in 20 let url_origin = Jv.to_string (Jv.get url_obj "origin") in 21 page_origin <> url_origin 22 with _ -> false 23 24let make_effective_url url = 25 if is_cross_origin url then begin 26 let base_dir = 27 match String.rindex_opt url '/' with 28 | Some i -> String.sub url 0 i 29 | None -> url 30 in 31 let js_code = Printf.sprintf 32 {|globalThis.__global_rel_url="%s";importScripts("%s");|} 33 base_dir url in 34 let blob = Jv.new' (Jv.get Jv.global "Blob") 35 [| Jv.of_jv_array [| Jv.of_string js_code |]; 36 Jv.obj [| "type", Jv.of_string "application/javascript" |] |] in 37 Jv.to_string 38 (Jv.call (Jv.get Jv.global "URL") "createObjectURL" [| blob |]) 39 end else 40 url 41 42let make url = 43 let effective_url = make_effective_url url in 44 let client = Jtw.create effective_url in 45 let t = { client; url; init_config = None; on_message_cb = (fun _ -> ()) } in 46 (* Wire widget rendering: forward widget messages from the worker to the 47 DOM renderer, and send widget events back to the worker. *) 48 let send_fn ~widget_id ~handler_id ~event_type ~value = 49 Jtw.send_widget_event client ~widget_id ~handler_id ~event_type ~value 50 in 51 Jtw.set_on_widget_update client (fun widget_id view_any -> 52 (* view_any is Js.Unsafe.any — coerce to Jv.t (they are the same repr) *) 53 let view_jv : Jv.t = Obj.magic view_any in 54 Widget_render.update_widget ~send:send_fn widget_id view_jv); 55 Jtw.set_on_widget_clear client (fun widget_id -> 56 Widget_render.clear_widget widget_id); 57 Jtw.set_on_widget_config client (fun widget_id config -> 58 Widget_render.config_widget widget_id config); 59 Jtw.set_on_widget_command client (fun widget_id cmd data -> 60 Widget_render.command_widget widget_id cmd data); 61 Jtw.set_on_widget_register_adapter client (fun kind js_code -> 62 Widget_render.register_js_adapter ~send:send_fn kind js_code); 63 t 64 65let on_message t fn = t.on_message_cb <- fn 66 67(** Send a response back to x-ocaml via the stored callback. *) 68let respond t resp = t.on_message_cb resp 69 70(** Convert Msg.completions to Protocol.completions *) 71let convert_completions (c : Msg.completions) : Protocol.completions = 72 let convert_kind s : [ `Value | `Constructor | `Variant | `Label 73 | `Module | `Modtype | `Type | `MethodCall | `Keyword ] = 74 match s with 75 | "Constructor" -> `Constructor 76 | "Keyword" -> `Keyword 77 | "Label" -> `Label 78 | "MethodCall" -> `MethodCall 79 | "Modtype" -> `Modtype 80 | "Module" -> `Module 81 | "Type" -> `Type 82 | "Variant" -> `Variant 83 | _ -> `Value 84 in 85 { 86 Protocol.from = c.from; 87 to_ = c.to_; 88 entries = List.map (fun (e : Msg.compl_entry) -> 89 { Query_protocol.Compl.name = e.name; 90 kind = convert_kind e.kind; 91 desc = e.desc; 92 info = e.info; 93 deprecated = e.deprecated; 94#if defined OXCAML 95 ppx_template_generated = false; 96#endif 97 } 98 ) c.entries; 99 } 100 101(** Convert Msg.error to Protocol.error *) 102let convert_error (e : Msg.error) : Protocol.error = 103 let loc_of_msg_loc (l : Msg.location) : Ocaml_parsing.Location.t = 104 { loc_start = { pos_fname = ""; pos_lnum = l.loc_start.pos_lnum; 105 pos_bol = l.loc_start.pos_bol; pos_cnum = l.loc_start.pos_cnum }; 106 loc_end = { pos_fname = ""; pos_lnum = l.loc_end.pos_lnum; 107 pos_bol = l.loc_end.pos_bol; pos_cnum = l.loc_end.pos_cnum }; 108 loc_ghost = false } 109 in 110 let kind = match e.kind with 111 | "error" -> Ocaml_parsing.Location.Report_error 112 | s when String.length s > 8 && String.sub s 0 8 = "warning:" -> 113 Report_warning (String.sub s 8 (String.length s - 8)) 114 | s when String.length s > 17 && String.sub s 0 17 = "warning_as_error:" -> 115 Report_warning_as_error (String.sub s 17 (String.length s - 17)) 116 | s when String.length s > 6 && String.sub s 0 6 = "alert:" -> 117 Report_alert (String.sub s 6 (String.length s - 6)) 118 | s when String.length s > 15 && String.sub s 0 15 = "alert_as_error:" -> 119 Report_alert_as_error (String.sub s 15 (String.length s - 15)) 120 | _ -> Report_error 121 in 122 let source = match e.source with 123 | "lexer" -> Ocaml_parsing.Location.Lexer 124 | "parser" -> Parser 125 | "typer" -> Typer 126 | "warning" -> Warning 127 | "env" -> Env 128 | "config" -> Config 129 | "unknown" -> Unknown 130 | _ -> Unknown 131 in 132 { Protocol.kind; loc = loc_of_msg_loc e.loc; main = e.main; sub = e.sub; source } 133 134(** Convert Msg.type_info to typed_enclosings entry *) 135let convert_type_info (t : Msg.type_info) = 136 let loc : Ocaml_parsing.Location.t = 137 { loc_start = { pos_fname = ""; pos_lnum = t.loc.loc_start.pos_lnum; 138 pos_bol = t.loc.loc_start.pos_bol; pos_cnum = t.loc.loc_start.pos_cnum }; 139 loc_end = { pos_fname = ""; pos_lnum = t.loc.loc_end.pos_lnum; 140 pos_bol = t.loc.loc_end.pos_bol; pos_cnum = t.loc.loc_end.pos_cnum }; 141 loc_ghost = false } 142 in 143 let tail = match t.tail with 144 | "tail_position" -> `Tail_position 145 | "tail_call" -> `Tail_call 146 | _ -> `No 147 in 148 (loc, `String t.type_str, tail) 149 150let init ?(findlib_requires = []) ?findlib_index t = 151 (* Derive stdlib_dcs from findlib_index base URL so cross-origin universes 152 can find their dynamic_cmis.json at the correct absolute URL. *) 153 let stdlib_dcs = match findlib_index with 154 | Some fi -> 155 (match String.rindex_opt fi '/' with 156 | Some i -> Some (String.sub fi 0 (i + 1) ^ "lib/ocaml/dynamic_cmis.json") 157 | None -> None) 158 | None -> None 159 in 160 let config : Msg.init_config = { 161 findlib_requires; 162 stdlib_dcs; 163 findlib_index; 164 } in 165 t.init_config <- Some config; 166 Lwt.async (fun () -> 167 let open Lwt.Infix in 168 Jtw.init t.client config >>= fun () -> 169 Lwt.return_unit) 170 171let reset t = 172 Jtw.terminate t.client; 173 let effective_url = make_effective_url t.url in 174 t.client <- Jtw.create effective_url; 175 match t.init_config with 176 | Some config -> 177 Lwt.async (fun () -> 178 let open Lwt.Infix in 179 Jtw.init t.client config >>= fun () -> 180 Lwt.return_unit) 181 | None -> () 182 183let post t (req : X_protocol.request) = 184 match req with 185 | X_protocol.Eval (id, _line_number, code) -> 186 (* Tell widget_render which cell is executing so widgets are placed 187 right after this cell's <x-ocaml> element in the DOM. *) 188 let doc = Brr.Document.to_jv Brr.G.document in 189 let cells = Jv.call doc "querySelectorAll" [| Jv.of_string "x-ocaml" |] in 190 let cell_el = Jv.call cells "item" [| Jv.of_int id |] in 191 if not (Jv.is_null cell_el) then 192 Widget_render.set_active_cell cell_el; 193 let stream = Jtw.eval_stream t.client code in 194 Lwt.async (fun () -> 195 Lwt.catch (fun () -> 196 Lwt_stream.iter (function 197 | Jtw.Phrase { loc; caml_ppf; _ } -> 198 let outputs = 199 if caml_ppf <> "" then [X_protocol.Meta caml_ppf] else [] 200 in 201 if outputs <> [] then 202 respond t (X_protocol.Top_response_at (id, loc, outputs)) 203 | Jtw.Done { stdout; stderr; caml_ppf; _ } -> 204 let outputs = ref [] in 205 if caml_ppf <> "" then 206 outputs := X_protocol.Meta caml_ppf :: !outputs; 207 if stdout <> "" then 208 outputs := X_protocol.Stdout stdout :: !outputs; 209 if stderr <> "" then 210 outputs := X_protocol.Stderr stderr :: !outputs; 211 respond t (X_protocol.Top_response (id, List.rev !outputs)) 212 | Jtw.Error msg -> 213 respond t (X_protocol.Top_response 214 (id, [X_protocol.Stderr msg])) 215 ) stream) 216 (fun _exn -> 217 respond t (X_protocol.Top_response 218 (id, [X_protocol.Stderr "Internal error during evaluation"])); 219 Lwt.return_unit)) 220 221 | X_protocol.Merlin (id, Protocol.Complete_prefix (src, pos, filename)) -> 222 let position = match pos with 223 | `Offset n -> n 224 | _ -> 0 225 in 226 Lwt.async (fun () -> 227 let open Lwt.Infix in 228 Lwt.catch (fun () -> 229 Jtw.complete ?filename t.client src position >|= fun completions -> 230 respond t (X_protocol.Merlin_response 231 (id, Protocol.Completions (convert_completions completions)))) 232 (fun _exn -> 233 respond t (X_protocol.Merlin_response 234 (id, Protocol.Completions { Protocol.from = 0; to_ = 0; entries = [] })); 235 Lwt.return_unit)) 236 237 | X_protocol.Merlin (id, Protocol.Type_enclosing (src, pos, filename)) -> 238 let position = match pos with 239 | `Offset n -> n 240 | _ -> 0 241 in 242 Lwt.async (fun () -> 243 let open Lwt.Infix in 244 Lwt.catch (fun () -> 245 Jtw.type_at ?filename t.client src position >|= fun types -> 246 respond t (X_protocol.Merlin_response 247 (id, Protocol.Typed_enclosings (List.map convert_type_info types)))) 248 (fun _exn -> 249 respond t (X_protocol.Merlin_response 250 (id, Protocol.Typed_enclosings [])); 251 Lwt.return_unit)) 252 253 | X_protocol.Merlin (id, Protocol.All_errors (src, filename)) -> 254 Lwt.async (fun () -> 255 let open Lwt.Infix in 256 Lwt.catch (fun () -> 257 Jtw.errors ?filename t.client src >|= fun errors -> 258 respond t (X_protocol.Merlin_response 259 (id, Protocol.Errors (List.map convert_error errors)))) 260 (fun _exn -> 261 respond t (X_protocol.Merlin_response (id, Protocol.Errors [])); 262 Lwt.return_unit)) 263 264 | X_protocol.Merlin (id, Protocol.Add_cmis _) -> 265 respond t (X_protocol.Merlin_response (id, Protocol.Added_cmis)) 266 267 | X_protocol.Format (id, code) -> 268 respond t (X_protocol.Formatted_source (id, code)) 269 270 | X_protocol.Format_config _ -> () 271 272 | X_protocol.Setup -> () 273 274 | X_protocol.Add_cmis _ -> () (* js_top_worker manages its own CMIs *) 275 276let eval ~id ~line_number t code = 277 post t (X_protocol.Eval (id, line_number, code)) 278 279let fmt ~id t code = 280 post t (X_protocol.Format (id, code))