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
module Unshadow = struct
module Buffer = Buffer
end
open Core
open Async
open Import
open Unshadow
(** Note that (1) this is not the full set of modes that [nvim_get_mode] can return, and
(2) the string representations of these modes and the modes returned by
[nvim_get_mode] are different. Thus we should not try to unify the types. *)
module Mode = struct
type t =
| Normal
| Operator_pending
| Insert
| Cmd_line
| Select
| Visual
| Terminal
| Visual_and_select
| Normal_and_visual_and_operator_pending
| Insert_and_command_line
| Language
[@@deriving compare, enumerate, equal, sexp_of]
let of_string = function
| " " -> Normal_and_visual_and_operator_pending
| "!" -> Insert_and_command_line
| "n" -> Normal
| "v" -> Visual_and_select
| "o" -> Operator_pending
| "i" -> Insert
| "c" -> Cmd_line
| "s" -> Select
| "x" -> Visual
| "l" -> Language
| "t" -> Terminal
| mode -> raise_s [%message "Unrecognized mode" (mode : string)] [@nontail]
;;
let to_string = function
| Normal -> "n"
| Operator_pending -> "o"
| Insert -> "i"
| Cmd_line -> "c"
| Select -> "s"
| Visual -> "x"
| Terminal -> "t"
| Visual_and_select -> "v"
| Normal_and_visual_and_operator_pending -> ""
| Insert_and_command_line -> "!"
| Language -> "l"
;;
end
let scope_of_msgpack msgpack =
match (msgpack : Msgpack.t) with
| Int 0 -> Ok `Global
| _ ->
let open Or_error.Let_syntax in
let%map buffer = Type.of_msgpack Buffer msgpack in
`Buffer_local buffer
;;
type t =
{ lhs : string
; mode : Mode.t
; scope : [ `Global | `Buffer_local of Nvim_internal.Buffer.t ]
; description : string option [@sexp.option]
; rhs : string
; expr : [ `Replace_keycodes of bool ] option [@sexp.option]
; nowait : bool
; silent : bool
; recursive : bool
; script_id : int
}
[@@deriving compare, sexp_of]
let of_msgpack_map map =
let open Or_error.Let_syntax in
let find_key = find_or_error_and_convert in
let%bind modes =
let%bind modes = find_key map "mode" (Type.of_msgpack String) in
List.map (String.to_list modes) ~f:(fun mode ->
Or_error.try_with (fun () -> Mode.of_string (Char.to_string mode)))
|> Or_error.combine_errors
in
let%bind silent = find_key map "silent" (Type.of_msgpack Bool) in
let%bind noremap = find_key map "noremap" (Type.of_msgpack Bool) in
let%bind nowait = find_key map "nowait" (Type.of_msgpack Bool) in
let%bind expr =
match%bind find_key map "expr" (Type.of_msgpack Bool) with
| false -> return None
| true ->
let%map replace_keycodes =
find_and_convert map "replace_keycodes" (Type.of_msgpack Bool)
>>| Option.value ~default:false
in
Some (`Replace_keycodes replace_keycodes)
in
let%bind script_id = find_key map "sid" (Type.of_msgpack Int) in
let%bind lhs = find_key map "lhs" (Type.of_msgpack String) in
let%bind rhs = find_key map "rhs" (Type.of_msgpack String) in
let%bind description = find_and_convert map "desc" (Type.of_msgpack String) in
let%bind scope = find_key map "buffer" scope_of_msgpack in
let recursive = not noremap in
let keymaps =
List.map modes ~f:(fun mode ->
{ description; lhs; rhs; mode; scope; expr; nowait; silent; recursive; script_id })
in
return keymaps
;;
let get ~(here : [%call_pos]) client ~scope ~mode =
let query =
match scope with
| `Global -> Nvim_internal.nvim_get_keymap
| `Buffer_local buffer -> Nvim_internal.nvim_buf_get_keymap ~buffer
in
let modes =
match (mode : Mode.t) with
| Normal
| Operator_pending
| Select
| Visual
| Terminal
| Visual_and_select
| Normal_and_visual_and_operator_pending
| Language -> [ mode ]
| Insert -> [ Insert; Language ]
| Cmd_line -> [ Cmd_line; Language ]
| Insert_and_command_line -> [ Insert; Cmd_line; Language ]
in
modes
|> Deferred.Or_error.List.concat_map ~how:`Sequential ~f:(fun mode ->
query ~mode:(Mode.to_string mode)
|> map_witness ~f:(fun keymaps ->
keymaps
|> List.map ~f:of_msgpack_map
|> Or_error.combine_errors
|> Or_error.map ~f:List.concat)
|> run ~here client)
>>|? List.dedup_and_sort ~compare
;;
let set_internal
(type a)
~(return_type : a Type.t)
~expr
~here
client
?(replace_keycodes = true)
?(recursive = false)
?(unique = false)
?(nowait = false)
?(silent = false)
?(description = "")
~mode
~scope
~lhs
~(rhs : a Ocaml_from_nvim.Callback.t)
()
=
let%bind.Deferred.Or_error scope =
match scope with
| `Global | `Buffer_local (Buffer.Or_current.Id _) -> Deferred.Or_error.return scope
| `Buffer_local Current ->
let%map.Deferred.Or_error buffer = Nvim.get_current_buf client in
`Buffer_local (Buffer.Or_current.Id buffer)
in
let%bind rhs =
match (rhs : a Ocaml_from_nvim.Callback.t) with
| Viml rhs -> return rhs
| Rpc rpc ->
let name =
Ocaml_from_nvim.Private.register_callback ~here client ~return_type rpc
in
let channel = Client.channel client in
let%map () =
match scope with
| `Global -> return ()
| `Buffer_local buffer ->
Autocmd.create
~here
client
~description:[%string "Unregister %{name}"]
~once:true
~patterns_or_buffer:(Buffer buffer)
~group:(Autocmd.Private.vcaml_internal_group client)
~events:[ BufDelete; BufWipeout ]
(Viml
[%string
{|
if !empty(nvim_get_chan_info(%{channel#Int}))
call rpcnotify(%{channel#Int}, "%{Client.Private.unregister_blocking_rpc}", "%{name}")
endif |}])
|> Deferred.ignore_m
in
(match expr with
| true -> [%string {|rpcrequest(%{channel#Int}, "%{name}")|}]
| false -> [%string {|<Cmd>call rpcrequest(%{channel#Int}, "%{name}")<CR>|}])
in
let query =
match scope with
| `Global -> Nvim_internal.nvim_set_keymap
| `Buffer_local buffer -> Nvim_internal.nvim_buf_set_keymap ~buffer
in
let mode = Mode.to_string mode in
let opts =
let boolean_opts =
let opts =
[ "expr", expr
; "noremap", not recursive
; "nowait", nowait
; "silent", silent
; "unique", unique
]
in
let opts =
match expr with
| false -> opts
| true -> ("replace_keycodes", replace_keycodes) :: opts
in
List.map opts ~f:(fun (key, value) -> key, Msgpack.Bool value)
in
match description with
| "" -> boolean_opts
| _ -> ("desc", String description) :: boolean_opts
in
query ~mode ~lhs ~rhs ~opts:(String.Map.of_alist_exn opts) |> run ~here client
;;
let set ~(here : [%call_pos]) client =
set_internal ~return_type:Nil ~expr:false ~here client ?replace_keycodes:None
;;
let set_expr ~(here : [%call_pos]) client =
set_internal ~return_type:String ~expr:true ~here client
;;
let unset ~(here : [%call_pos]) client ~scope ~lhs ~mode =
let query =
match scope with
| `Global -> Nvim_internal.nvim_del_keymap
| `Buffer_local buffer -> Nvim_internal.nvim_buf_del_keymap ~buffer
in
let mode = Mode.to_string mode in
query ~mode ~lhs |> run ~here client
;;