package vcaml
OCaml bindings for the Neovim API
Install
Dune Dependency
Authors
Maintainers
Sources
vcaml-v0.16.0.tar.gz
sha256=dd123302c46af7ca6eda8a7806c78236fd217a8c73a2e1cd7da85f1d69ed1ae4
doc/src/vcaml.semantics_test/test_nvim_semantics.ml.html
Source file test_nvim_semantics.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
open Core open Async open Import open Vcaml open Vcaml_test_helpers (* The tests in this file demonstrate properties of Neovim's own semantics. VCaml makes assumptions about how Neovim operates, so if these fail that most likely indicates that a change to VCaml is necessary. *) let hundred_ms = Time_ns.Span.create ~ms:100 () let%expect_test "[rpcrequest] blocks other channels" = Expect_test_helpers_async.with_temp_dir (fun tmp_dir -> let socket = tmp_dir ^/ "socket" in let%bind nvim = (* There is some undocumented internal limit for the socket length (it doesn't appear in `:h limits`) so to ensure we create a socket we set the working dir to [tmp_dir] and create the socket with a relative path. *) Process.create_exn () ~working_dir:tmp_dir ~prog:neovim_path ~args:[ "--headless"; "-n"; "--clean"; "--listen"; "./socket" ] ~env:(`Extend [ "NVIM_RPLUGIN_MANIFEST", "rplugin.vim" ]) in let%bind () = with_process_cleanup ~name:"nvim" (Process.pid nvim) ~f:(fun () -> match%bind spin_until_nvim_creates_socket_file (Process.pid nvim) ~socket with | `Nvim_crashed exit_or_signal -> return (`Already_reaped exit_or_signal) | `Socket_missing -> raise_s [%message "Socket was not created"] | `Socket_created -> let block_nvim ~client = let blocking = Ivar.create () in let result = Ivar.create () in let function_name = "rpc" in let call_rpc = (wrap_viml_function ~type_:Defun.Vim.(Integer @-> String @-> Nil @-> return Nil) ~function_name:"rpcrequest") (Client.channel client) function_name () in register_request_blocking client ~name:function_name ~type_:Defun.Ocaml.Sync.(Nil @-> return Nil) ~f:(fun ~keyboard_interrupted:_ ~client:_ () -> Ivar.fill blocking (); Ivar.read result |> Deferred.ok); let result_deferred = run_join [%here] client call_rpc in let%map () = Ivar.read blocking in fun response -> Ivar.fill result response; result_deferred in let%bind client1 = socket_client socket >>| ok_exn in let%bind client2 = socket_client socket >>| ok_exn in let print_when_client2_is_unblocked () = don't_wait_for (let%map result = run_join [%here] client2 (Nvim.eval "'Client 2 is unblocked'" ~result_type:String) in print_s [%sexp (result : string Or_error.t)]) in print_when_client2_is_unblocked (); let%bind () = Clock_ns.after hundred_ms in let%bind () = Scheduler.yield_until_no_jobs_remain () in print_s [%message "Blocking nvim (client1)"]; let%bind respond_to_rpc = block_nvim ~client:client1 in print_when_client2_is_unblocked (); let%bind () = Clock_ns.after hundred_ms in let%bind () = Scheduler.yield_until_no_jobs_remain () in print_s [%message "Unblocking nvim (client1)"]; let%bind () = respond_to_rpc () >>| ok_exn in let%bind () = Clock_ns.after hundred_ms in let%bind () = Scheduler.yield_until_no_jobs_remain () in let%bind () = attempt_to_quit ~tmp_dir ~client:client2 in let%bind () = Client.close client1 in let%bind () = Client.close client2 in return (`Need_to_reap `Patient)) in [%expect {| (Ok "Client 2 is unblocked") "Blocking nvim (client1)" "Unblocking nvim (client1)" (Ok "Client 2 is unblocked") ("nvim exited" (exit_or_signal (Ok ()))) |}]; return ()) ;; let%expect_test "Plugin dying during [rpcrequest] does not bring down Neovim" = Expect_test_helpers_async.with_temp_dir (fun tmp_dir -> let socket = tmp_dir ^/ "socket" in let%bind nvim = (* There is some undocumented internal limit for the socket length (it doesn't appear in `:h limits`) so to ensure we create a socket we set the working dir to [tmp_dir] and create the socket with a relative path. *) Process.create_exn () ~working_dir:tmp_dir ~prog:neovim_path ~args:[ "--headless"; "-n"; "--clean"; "--listen"; "./socket" ] ~env:(`Extend [ "NVIM_RPLUGIN_MANIFEST", "rplugin.vim" ]) in let%bind () = with_process_cleanup ~name:"nvim" (Process.pid nvim) ~f:(fun () -> match%bind spin_until_nvim_creates_socket_file (Process.pid nvim) ~socket with | `Nvim_crashed exit_or_signal -> return (`Already_reaped exit_or_signal) | `Socket_missing -> raise_s [%message "Socket was not created"] | `Socket_created -> let block_nvim ~client = let blocking = Ivar.create () in let function_name = "rpc" in let call_rpc = (wrap_viml_function ~type_:Defun.Vim.(Integer @-> String @-> Nil @-> return Nil) ~function_name:"rpcrequest") (Client.channel client) function_name () in register_request_blocking client ~name:function_name ~type_:Defun.Ocaml.Sync.(Nil @-> return Nil) ~f:(fun ~keyboard_interrupted:_ ~client:_ () -> Ivar.fill blocking (); Deferred.never ()); don't_wait_for (run_join [%here] client call_rpc >>| ok_exn); Ivar.read blocking in (match Core_unix.fork () with | `In_the_child -> Scheduler.reset_in_forked_process (); don't_wait_for (let%bind client = socket_client socket >>| ok_exn in let%bind () = block_nvim ~client in (* We don't want to allow the [at_exit] handler that expect test collector registers to run for this child process. *) Core_unix.exit_immediately 0); never_returns (Scheduler.go ()) | `In_the_parent child -> let%bind exit_or_signal = Unix.waitpid child in print_s [%message "child exited" (exit_or_signal : Unix.Exit_or_signal.t)]; let%bind client = socket_client socket >>| ok_exn in let%bind result = run_join [%here] client (Nvim.eval "'nvim is still running'" ~result_type:String) in print_s [%sexp (result : string Or_error.t)]; let%bind () = attempt_to_quit ~tmp_dir ~client in let%bind () = Client.close client in return (`Need_to_reap `Patient))) in [%expect {| ("child exited" (exit_or_signal (Ok ()))) (Ok "nvim is still running") ("nvim exited" (exit_or_signal (Ok ()))) |}]; return ()) ;; let%expect_test "Nested [rpcrequest]s are supported" = let result = with_client (fun client -> let open Deferred.Or_error.Let_syntax in let factorial = (wrap_viml_function ~type_:Defun.Vim.(Integer @-> String @-> Integer @-> return Integer) ~function_name:"rpcrequest") (Client.channel client) "factorial" in register_request_blocking client ~name:"factorial" ~type_:Defun.Ocaml.Sync.(Type.Integer @-> return Integer) ~f:(fun ~keyboard_interrupted:_ ~client n -> match n with | 0 -> return 1 | _ -> let%map result = run_join [%here] client (factorial (n - 1)) in n * result); run_join [%here] client (factorial 5)) in let%bind result = with_timeout (Time_float.Span.of_int_sec 3) result in print_s [%sexp (result : [ `Result of int | `Timeout ])]; [%expect {| (Result 120) |}]; return () ;;
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>