this repo has no description
at main 188 lines 6.9 kB view raw
1open Brr 2 3type t = { 4 backend : Backend.t; 5 extra_style : Jstr.t option; 6 inline_style : Jstr.t option; 7 default_run_on : string; 8 mutable cells : Cell.t list; 9 (* test_links: maps exercise cell id -> list of test cells linked to it *) 10 mutable test_links : (int * Cell.t list) list; 11 (* Messages to replay after a backend reset *) 12 init_messages : X_protocol.request list ref; 13} 14 15let find_by_id t id = List.find (fun c -> Cell.id c = id) t.cells 16 17let find_by_cell_id t name = 18 List.find_opt (fun c -> Cell.cell_id c = Some name) t.cells 19 20let cells t = List.rev t.cells 21 22(* Find the nearest preceding exercise cell in document order. 23 t.cells is stored in reverse document order (most recent first). *) 24let find_preceding_exercise t = 25 List.find_opt (fun c -> Cell.mode c = Cell.Exercise) t.cells 26 27(* Register a test cell as linked to an exercise cell. 28 When the exercise completes, all linked tests will be triggered. *) 29let link_test t ~exercise ~test = 30 let ex_id = Cell.id exercise in 31 let existing = 32 match List.assoc_opt ex_id t.test_links with 33 | Some tests -> tests 34 | None -> [] 35 in 36 t.test_links <- (ex_id, test :: existing) 37 :: List.filter (fun (id, _) -> id <> ex_id) t.test_links 38 39(* Set up the on_completed callback for an exercise cell. 40 When it completes, run all linked test cells. *) 41let setup_exercise_callback t exercise = 42 Cell.set_on_completed exercise (fun _ex -> 43 let ex_id = Cell.id exercise in 44 match List.assoc_opt ex_id t.test_links with 45 | None -> () 46 | Some tests -> List.iter Cell.run tests) 47 48let read_meta_content name = 49 let doc = Document.to_jv G.document in 50 let selector = Jstr.of_string ("meta[name=\"" ^ name ^ "\"]") in 51 let result = Jv.call doc "querySelector" [| Jv.of_jstr selector |] in 52 if Jv.is_none result then None 53 else 54 let content = Jv.call result "getAttribute" [| Jv.of_string "content" |] in 55 if Jv.is_none content then None 56 else Some (Jv.to_string content) 57 58let reset_all t = 59 Backend.reset t.backend; 60 (* Re-register the message handler on the fresh worker *) 61 Backend.on_message t.backend (function 62 | X_protocol.Formatted_source (id, code_fmt) -> 63 Cell.set_source (find_by_id t id) code_fmt 64 | Top_response_at (id, loc, msg) -> 65 Cell.add_message (find_by_id t id) loc msg 66 | Top_response (id, msg) -> 67 Cell.completed_run (find_by_id t id) msg 68 | Merlin_response (id, msg) -> 69 Cell.receive_merlin (find_by_id t id) msg); 70 (* Replay init messages *) 71 List.iter (Backend.post t.backend) !(t.init_messages); 72 (* Reset all cells to Not_run *) 73 List.iter Cell.reset_status t.cells 74 75let create ~backend ?extra_style ?inline_style ?(default_run_on = "load") 76 ?format_config () = 77 let init_messages = ref [] in 78 let t = 79 { backend; extra_style; inline_style; default_run_on; 80 cells = []; test_links = []; init_messages } 81 in 82 (* Route backend responses to the appropriate cell *) 83 Backend.on_message backend (function 84 | X_protocol.Formatted_source (id, code_fmt) -> 85 Cell.set_source (find_by_id t id) code_fmt 86 | Top_response_at (id, loc, msg) -> 87 Cell.add_message (find_by_id t id) loc msg 88 | Top_response (id, msg) -> 89 Cell.completed_run (find_by_id t id) msg 90 | Merlin_response (id, msg) -> 91 Cell.receive_merlin (find_by_id t id) msg); 92 (* Initialise backend and record messages for replay after reset *) 93 let send msg = 94 init_messages := !(init_messages) @ [msg]; 95 Backend.post backend msg 96 in 97 send Setup; 98 (match format_config with 99 | Some conf -> send (Format_config conf) 100 | None -> ()); 101 (* Universe discovery: register external package CMIs with merlin. 102 <meta name="x-ocaml-packages" content="pkg1,pkg2,..."> 103 <meta name="x-ocaml-cmis-url" content="./cmis/"> (optional, default ./cmis/) 104 For each package, we send Add_cmis with dynamic_cmis pointing to 105 {base_url}/{pkg}/ with toplevel module {Pkg} and prefix {pkg}__. *) 106 (match read_meta_content "x-ocaml-packages" with 107 | None -> () 108 | Some packages_str -> 109 let base_url = 110 match read_meta_content "x-ocaml-cmis-url" with 111 | Some url -> url 112 | None -> "./cmis/" 113 in 114 let packages = 115 List.filter (fun s -> s <> "") 116 (List.map String.trim (String.split_on_char ',' packages_str)) 117 in 118 List.iter (fun pkg -> 119 let capitalised = String.capitalize_ascii pkg in 120 let dcs = Protocol.{ 121 dcs_url = base_url ^ pkg ^ "/"; 122 dcs_toplevel_modules = [ capitalised ]; 123 dcs_file_prefixes = [ pkg ^ "__" ]; 124 } in 125 send 126 (Add_cmis { Protocol.static_cmis = []; dynamic_cmis = Some dcs })) 127 packages); 128 t 129 130let run_on_of_string = function "click" -> `Click | "load" | _ -> `Load 131 132let register t this = 133 let id = List.length t.cells in 134 let prev = match t.cells with [] -> None | c :: _ -> Some c in 135 let mode = 136 Cell.mode_of_string 137 (Option.value ~default:"interactive" 138 (Webcomponent.get_attribute this "mode")) 139 in 140 let run_on = 141 run_on_of_string 142 (Option.value ~default:t.default_run_on 143 (Webcomponent.get_attribute this "run-on")) 144 in 145 let cell_id = Webcomponent.get_attribute this "data-id" in 146 let cell_for = Webcomponent.get_attribute this "data-for" in 147 let cell_env = Webcomponent.get_attribute this "data-env" in 148 let filename = Webcomponent.get_attribute this "data-filename" in 149 let merlin = 150 match Webcomponent.get_attribute this "data-merlin" with 151 | Some "false" -> false 152 | _ -> true 153 in 154 let eval_fn ~id ~line_number code = 155 Backend.eval ~id ~line_number t.backend code 156 in 157 let fmt_fn ~id code = Backend.fmt ~id t.backend code in 158 let post_fn msg = Backend.post t.backend msg in 159 let cell = 160 Cell.init ~id ~mode ~run_on ?cell_id ?cell_for ?cell_env ?filename ~merlin 161 ?extra_style:t.extra_style ?inline_style:t.inline_style ~eval_fn ~fmt_fn 162 ~post_fn this 163 in 164 t.cells <- cell :: t.cells; 165 Cell.set_prev ~prev cell; 166 Cell.set_stop_fn cell (fun () -> reset_all t); 167 (* Test linking: connect Test cells to their target Exercise cell *) 168 (match mode with 169 | Cell.Test -> 170 let target = 171 match cell_for with 172 | Some target_id -> find_by_cell_id t target_id 173 | None -> find_preceding_exercise t 174 in 175 (match target with 176 | Some exercise -> 177 link_test t ~exercise ~test:cell; 178 setup_exercise_callback t exercise; 179 (* Late registration: if exercise already completed, trigger now *) 180 if Cell.has_completed exercise then Cell.run cell 181 | None -> ()) 182 | Cell.Exercise -> 183 (* Set up the callback so tests linked later will be triggered *) 184 setup_exercise_callback t cell 185 | _ -> ()); 186 Cell.start cell this; 187 if List.for_all Cell.loadable t.cells then Cell.run cell; 188 cell