Source file vcaml.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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
open Core
open Async
module Internal = Nvim_internal
module Client_info = Client_info
module Channel_info = Channel_info
module Nvim_command = Nvim_command
module Keymap = Keymap
module Type = Types.Phantom
module Buf = Buf
module Window = Window
module Tabpage = Tabpage

module Client = struct
  include Client

  type t = Types.client =
    { events : (Msgpack_rpc.event -> unit) Bus.Read_only.t
    ; call_nvim_api_fn : 'a. 'a Internal.Types.api_result -> 'a Deferred.Or_error.t
    ; register_request :
        name:string -> f:(Msgpack.t list -> Msgpack.t Or_error.t) -> unit Or_error.t
    ; buffers_attached : int Buf.Table.t
    ; attach_sequencer : unit Sequencer.t
    }

  module Connection_type = struct
    type t =
      | Unix of string
      | Embed of
          { prog : string
          ; args : string list
          ; working_dir : string
          ; env : (string * string) list
          }
      | Child
  end

  open Connection_type

  module Child = struct
    module T = struct
      type t =
        { reader : Reader.t
        ; writer : Writer.t
        }
      [@@deriving fields]
    end

    include Msgpack_rpc.Make (T) ()

    let self () =
      connect { T.reader = Lazy.force Reader.stdin; writer = Lazy.force Writer.stdout }
    ;;
  end

  module Embedded = struct
    open Async

    module T = struct
      type t =
        { reader : Reader.t
        ; writer : Writer.t
        }
      [@@deriving fields]
    end

    include Msgpack_rpc.Make (T) ()

    let spawn ~prog ~args ~working_dir ~env =
      let open Deferred.Or_error.Let_syntax in
      let%bind underlying =
        Process.create ~prog ~args ~working_dir ~env:(`Replace env) ()
      in
      let conn =
        { T.reader = Process.stdout underlying; writer = Process.stdin underlying }
      in
      return (connect conn, underlying)
    ;;
  end

  let attach = function
    | Unix sock_name ->
      let module T = Transport.Make (Msgpack_unix) in
      let%bind s = Msgpack_unix.Unix_socket.open_from_filename sock_name in
      let cli = Msgpack_unix.connect s in
      let%bind.Deferred.Or_error client = T.attach cli in
      Deferred.Or_error.return (client, None)
    | Embed { prog; args; working_dir; env } ->
      let module T = Transport.Make (Embedded) in
      let%bind.Deferred.Or_error client, process =
        Embedded.spawn ~prog ~args ~working_dir ~env
      in
      let%bind.Deferred.Or_error client = T.attach client in
      Deferred.Or_error.return (client, Some process)
    | Child ->
      let module T = Transport.Make (Child) in
      let%bind.Deferred.Or_error client = T.attach (Child.self ()) in
      Deferred.Or_error.return (client, None)
  ;;

  let embed ~prog ~args ~working_dir ~env =
    attach (Embed { prog; args; working_dir; env })
  ;;
end

type 'a api_call = 'a Api_call.t

module Property = struct
  type 'a t =
    { get : 'a Or_error.t api_call
    ; set : 'a -> unit Or_error.t api_call
    }
end

let run = Api_call.run
let run_join = Api_call.run_join

module Defun = struct
  module Vim = struct
    type ('f, 'leftmost_input, 'out) t =
      | Nullary : 'output Type.t -> ('output Or_error.t Api_call.t, unit, 'output) t
      | Cons : 'a Type.t * ('b, _, 'output) t -> ('a -> 'b, 'a, 'output) t

    let return t = Nullary t
    let unary t_in t_out = Cons (t_in, return t_out)
    let ( @-> ) a t = Cons (a, t)

    let rec make_fn
      : type fn i out.
        string -> (fn, i, out) t -> (Msgpack.t list -> Msgpack.t list) -> fn
      =
      fun function_name arity f ->
      (* Due to the fact that OCaml does not (easily) support higher-ranked polymorphism,
         we need to construct the function [to_msgpack] *after* we unpack this GADT, so it
         can have the type [i -> Msgpack.t] (which is fixed by [arity] in this function).
         Otherwise, it needs the type [forall 'a . 'a witness -> 'a -> Msgpack.t], which is
         not that easily expressible.
      *)
      match arity with
      | Nullary return_type ->
        let args = f [] in
        let open Api_call.Let_syntax in
        let%map result = Client.Untested.call_function ~fn:function_name ~args in
        let open Or_error.Let_syntax in
        let%bind result = result in
        Extract.value
          ~err_msg:"return type given to [wrap_viml_function] is incorrect"
          return_type
          result
      | Cons (t, rest) ->
        fun i ->
          let to_msgpack = Extract.inject t in
          make_fn function_name rest (fun args -> f (to_msgpack i :: args))
    ;;
  end

  module Ocaml = struct
    module Sync = struct
      type ('f, 'leftmost_input) t =
        | Nullary : 'output Type.t -> ('output Or_error.t, unit) t
        | Cons : 'a Type.t * ('b, _) t -> ('a -> 'b, 'a) t

      let return t = Nullary t

      let rec make_fn
        : type fn i.
          Types.client -> (fn, i) t -> fn -> Msgpack.t list -> Msgpack.t Or_error.t
        =
        fun client arity f l ->
        match arity, l with
        | Nullary return_type, [] -> f |> Or_error.map ~f:(Extract.inject return_type)
        | Nullary return_type, [ Msgpack.Nil ] ->
          f |> Or_error.map ~f:(Extract.inject return_type)
        | Cons (leftmost, rest), x :: xs ->
          Extract.value leftmost x
          |> Or_error.bind ~f:(fun v ->
            let f' = f v in
            make_fn client rest f' xs)
        | _, _ -> Or_error.error_s [%message "Wrong number of arguments"]
      ;;

      let ( @-> ) a b = Cons (a, b)
    end

    module Async = struct
      type 'f t =
        | Unit : unit Deferred.t t
        | Cons : 'a Type.t * 'b t -> ('a -> 'b) t

      let rec make_fn
        : type fn.
          Types.client -> string -> fn t -> fn -> Msgpack.t list -> unit Deferred.t
        =
        fun client name arity f l ->
        match arity, l with
        | Unit, [] -> return ()
        | Unit, [ Msgpack.Nil ] -> return ()
        | Cons (leftmost, rest), x :: xs ->
          (match%bind Extract.value leftmost x |> Deferred.return with
           | Ok v ->
             let f' = f v in
             make_fn client name rest f' xs
           | Error e ->
             Log.Global.error !"got wrong argument type for %s: %{sexp: Error.t}" name e;
             return ())
        | _ ->
          Log.Global.error "got wrong number of args for async request %s" name;
          return ()
      ;;

      let unit = Unit
      let ( @-> ) a b = Cons (a, b)
    end
  end
end

let wrap_viml_function ~type_ ~function_name =
  Defun.Vim.make_fn function_name type_ Fn.id
;;

let construct_getset ~name ~type_ ~remote_get ~remote_set =
  let get =
    let open Api_call.Let_syntax in
    let%map result = remote_get ~name in
    let open Or_error.Let_syntax in
    let%bind result = result in
    Extract.value
      ~err_msg:(sprintf "return type given to wrapper for %s is incorrect" name)
      type_
      result
  in
  let set v =
    let value = Extract.inject type_ v in
    remote_set ~name ~value
  in
  { Property.get; set }
;;

let wrap_var =
  construct_getset
    ~remote_get:Client.Untested.get_var
    ~remote_set:Client.Untested.set_var
;;

let wrap_get_vvar ~name ~type_ =
  let open Api_call.Let_syntax in
  let%map result = Client.Untested.get_vvar ~name in
  let open Or_error.Let_syntax in
  let%bind result = result in
  Extract.value ~err_msg:"return type given to [wrap_get_vvar] is incorrect" type_ result
;;

let wrap_option =
  construct_getset
    ~remote_get:Client.Untested.get_option
    ~remote_set:Client.Untested.set_option
;;

let register_request_blocking ({ Client.register_request; _ } as client) ~name ~type_ ~f =
  register_request ~name ~f:(Defun.Ocaml.Sync.make_fn client type_ f)
;;

let register_request_async ({ Client.events; _ } as client) ~name ~type_ ~f =
  Bus.iter_exn events [%here] ~f:(fun { Msgpack_rpc.method_name; params } ->
    if not (String.equal method_name name)
    then ()
    else Defun.Ocaml.Async.make_fn client name type_ f params |> don't_wait_for)
;;

let convert_msgpack_response type_ call =
  Api_call.map_bind
    call
    ~f:
      (Extract.value
         ~err_msg:"return type given to [convert_msgpack_response] is incorrect"
         type_)
;;

module Api_call = struct
  module Let_syntax = Api_call.Let_syntax
end