1 // This is an open source non-commercial project. Dear PVS-Studio, please check
2 // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3 
4 #include <assert.h>
5 #include <inttypes.h>
6 #include <limits.h>
7 #include <stdbool.h>
8 #include <stdlib.h>
9 #include <string.h>
10 
11 #include "nvim/api/buffer.h"
12 #include "nvim/api/deprecated.h"
13 #include "nvim/api/private/converter.h"
14 #include "nvim/api/private/defs.h"
15 #include "nvim/api/private/dispatch.h"
16 #include "nvim/api/private/helpers.h"
17 #include "nvim/api/vim.h"
18 #include "nvim/api/window.h"
19 #include "nvim/ascii.h"
20 #include "nvim/buffer.h"
21 #include "nvim/buffer_defs.h"
22 #include "nvim/context.h"
23 #include "nvim/decoration.h"
24 #include "nvim/edit.h"
25 #include "nvim/eval.h"
26 #include "nvim/eval/typval.h"
27 #include "nvim/eval/userfunc.h"
28 #include "nvim/ex_cmds2.h"
29 #include "nvim/ex_docmd.h"
30 #include "nvim/file_search.h"
31 #include "nvim/fileio.h"
32 #include "nvim/getchar.h"
33 #include "nvim/highlight.h"
34 #include "nvim/lua/executor.h"
35 #include "nvim/mark.h"
36 #include "nvim/memline.h"
37 #include "nvim/memory.h"
38 #include "nvim/message.h"
39 #include "nvim/move.h"
40 #include "nvim/msgpack_rpc/channel.h"
41 #include "nvim/msgpack_rpc/helpers.h"
42 #include "nvim/ops.h"
43 #include "nvim/option.h"
44 #include "nvim/os/input.h"
45 #include "nvim/os/process.h"
46 #include "nvim/popupmnu.h"
47 #include "nvim/screen.h"
48 #include "nvim/state.h"
49 #include "nvim/syntax.h"
50 #include "nvim/types.h"
51 #include "nvim/ui.h"
52 #include "nvim/vim.h"
53 #include "nvim/viml/parser/expressions.h"
54 #include "nvim/viml/parser/parser.h"
55 #include "nvim/window.h"
56 
57 #define LINE_BUFFER_SIZE 4096
58 
59 #ifdef INCLUDE_GENERATED_DECLARATIONS
60 # include "api/vim.c.generated.h"
61 #endif
62 
63 /// Gets a highlight definition by name.
64 ///
65 /// @param name Highlight group name
66 /// @param rgb Export RGB colors
67 /// @param[out] err Error details, if any
68 /// @return Highlight definition map
69 /// @see nvim_get_hl_by_id
nvim_get_hl_by_name(String name,Boolean rgb,Error * err)70 Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Error *err)
71   FUNC_API_SINCE(3)
72 {
73   Dictionary result = ARRAY_DICT_INIT;
74   int id = syn_name2id(name.data);
75 
76   if (id == 0) {
77     api_set_error(err, kErrorTypeException, "Invalid highlight name: %s",
78                   name.data);
79     return result;
80   }
81   result = nvim_get_hl_by_id(id, rgb, err);
82   return result;
83 }
84 
85 /// Gets a highlight definition by id. |hlID()|
86 ///
87 /// @param hl_id Highlight id as returned by |hlID()|
88 /// @param rgb Export RGB colors
89 /// @param[out] err Error details, if any
90 /// @return Highlight definition map
91 /// @see nvim_get_hl_by_name
nvim_get_hl_by_id(Integer hl_id,Boolean rgb,Error * err)92 Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err)
93   FUNC_API_SINCE(3)
94 {
95   Dictionary dic = ARRAY_DICT_INIT;
96   if (syn_get_final_id((int)hl_id) == 0) {
97     api_set_error(err, kErrorTypeException,
98                   "Invalid highlight id: %" PRId64, hl_id);
99     return dic;
100   }
101   int attrcode = syn_id2attr((int)hl_id);
102   return hl_get_attr_by_id(attrcode, rgb, err);
103 }
104 
105 /// Gets a highlight group by name
106 ///
107 /// similar to |hlID()|, but allocates a new ID if not present.
nvim_get_hl_id_by_name(String name)108 Integer nvim_get_hl_id_by_name(String name)
109   FUNC_API_SINCE(7)
110 {
111   return syn_check_group(name.data, (int)name.size);
112 }
113 
nvim__get_hl_defs(Integer ns_id,Error * err)114 Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
115 {
116   if (ns_id == 0) {
117     return get_global_hl_defs();
118   }
119   abort();
120 }
121 
122 /// Set a highlight group.
123 ///
124 /// @param ns_id number of namespace for this highlight
125 /// @param name highlight group name, like ErrorMsg
126 /// @param val highlight definition map, like |nvim_get_hl_by_name|.
127 ///            in addition the following keys are also recognized:
128 ///              `default`: don't override existing definition,
129 ///                         like `hi default`
130 ///              `ctermfg`: sets foreground of cterm color
131 ///              `ctermbg`: sets background of cterm color
132 ///              `cterm`  : cterm attribute map. sets attributed for
133 ///                         cterm colors. similer to `hi cterm`
134 ///                         Note: by default cterm attributes are
135 ///                               same as attributes of gui color
136 /// @param[out] err Error details, if any
137 ///
138 /// TODO: ns_id = 0, should modify :highlight namespace
139 /// TODO val should take update vs reset flag
nvim_set_hl(Integer ns_id,String name,Dictionary val,Error * err)140 void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err)
141   FUNC_API_SINCE(7)
142 {
143   int hl_id = syn_check_group(name.data, (int)name.size);
144   int link_id = -1;
145 
146   HlAttrs attrs = dict2hlattrs(val, true, &link_id, err);
147   if (!ERROR_SET(err)) {
148     ns_hl_def((NS)ns_id, hl_id, attrs, link_id);
149   }
150 }
151 
152 /// Set active namespace for highlights.
153 ///
154 /// NB: this function can be called from async contexts, but the
155 /// semantics are not yet well-defined. To start with
156 /// |nvim_set_decoration_provider| on_win and on_line callbacks
157 /// are explicitly allowed to change the namespace during a redraw cycle.
158 ///
159 /// @param ns_id the namespace to activate
160 /// @param[out] err Error details, if any
nvim__set_hl_ns(Integer ns_id,Error * err)161 void nvim__set_hl_ns(Integer ns_id, Error *err)
162   FUNC_API_FAST
163 {
164   if (ns_id >= 0) {
165     ns_hl_active = (NS)ns_id;
166   }
167 
168   // TODO(bfredl): this is a little bit hackish.  Eventually we want a standard
169   // event path for redraws caused by "fast" events. This could tie in with
170   // better throttling of async events causing redraws, such as non-batched
171   // nvim_buf_set_extmark calls from async contexts.
172   if (!provider_active && !ns_hl_changed) {
173     multiqueue_put(main_loop.events, on_redraw_event, 0);
174   }
175   ns_hl_changed = true;
176 }
177 
on_redraw_event(void ** argv)178 static void on_redraw_event(void **argv)
179 {
180   redraw_all_later(NOT_VALID);
181 }
182 
183 
184 /// Sends input-keys to Nvim, subject to various quirks controlled by `mode`
185 /// flags. This is a blocking call, unlike |nvim_input()|.
186 ///
187 /// On execution error: does not fail, but updates v:errmsg.
188 ///
189 /// To input sequences like <C-o> use |nvim_replace_termcodes()| (typically
190 /// with escape_csi=true) to replace |keycodes|, then pass the result to
191 /// nvim_feedkeys().
192 ///
193 /// Example:
194 /// <pre>
195 ///     :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
196 ///     :call nvim_feedkeys(key, 'n', v:true)
197 /// </pre>
198 ///
199 /// @param keys         to be typed
200 /// @param mode         behavior flags, see |feedkeys()|
201 /// @param escape_csi   If true, escape K_SPECIAL/CSI bytes in `keys`
202 /// @see feedkeys()
203 /// @see vim_strsave_escape_csi
nvim_feedkeys(String keys,String mode,Boolean escape_csi)204 void nvim_feedkeys(String keys, String mode, Boolean escape_csi)
205   FUNC_API_SINCE(1)
206 {
207   bool remap = true;
208   bool insert = false;
209   bool typed = false;
210   bool execute = false;
211   bool dangerous = false;
212 
213   for (size_t i = 0; i < mode.size; ++i) {
214     switch (mode.data[i]) {
215     case 'n':
216       remap = false; break;
217     case 'm':
218       remap = true; break;
219     case 't':
220       typed = true; break;
221     case 'i':
222       insert = true; break;
223     case 'x':
224       execute = true; break;
225     case '!':
226       dangerous = true; break;
227     }
228   }
229 
230   if (keys.size == 0 && !execute) {
231     return;
232   }
233 
234   char *keys_esc;
235   if (escape_csi) {
236     // Need to escape K_SPECIAL and CSI before putting the string in the
237     // typeahead buffer.
238     keys_esc = (char *)vim_strsave_escape_csi((char_u *)keys.data);
239   } else {
240     keys_esc = keys.data;
241   }
242   ins_typebuf((char_u *)keys_esc, (remap ? REMAP_YES : REMAP_NONE),
243               insert ? 0 : typebuf.tb_len, !typed, false);
244   if (vgetc_busy) {
245     typebuf_was_filled = true;
246   }
247 
248   if (escape_csi) {
249     xfree(keys_esc);
250   }
251 
252   if (execute) {
253     int save_msg_scroll = msg_scroll;
254 
255     // Avoid a 1 second delay when the keys start Insert mode.
256     msg_scroll = false;
257     if (!dangerous) {
258       ex_normal_busy++;
259     }
260     exec_normal(true);
261     if (!dangerous) {
262       ex_normal_busy--;
263     }
264     msg_scroll |= save_msg_scroll;
265   }
266 }
267 
268 /// Queues raw user-input. Unlike |nvim_feedkeys()|, this uses a low-level
269 /// input buffer and the call is non-blocking (input is processed
270 /// asynchronously by the eventloop).
271 ///
272 /// On execution error: does not fail, but updates v:errmsg.
273 ///
274 /// @note |keycodes| like <CR> are translated, so "<" is special.
275 ///       To input a literal "<", send <LT>.
276 ///
277 /// @note For mouse events use |nvim_input_mouse()|. The pseudokey form
278 ///       "<LeftMouse><col,row>" is deprecated since |api-level| 6.
279 ///
280 /// @param keys to be typed
281 /// @return Number of bytes actually written (can be fewer than
282 ///         requested if the buffer becomes full).
nvim_input(String keys)283 Integer nvim_input(String keys)
284   FUNC_API_SINCE(1) FUNC_API_FAST
285 {
286   return (Integer)input_enqueue(keys);
287 }
288 
289 /// Send mouse event from GUI.
290 ///
291 /// Non-blocking: does not wait on any result, but queues the event to be
292 /// processed soon by the event loop.
293 ///
294 /// @note Currently this doesn't support "scripting" multiple mouse events
295 ///       by calling it multiple times in a loop: the intermediate mouse
296 ///       positions will be ignored. It should be used to implement real-time
297 ///       mouse input in a GUI. The deprecated pseudokey form
298 ///       ("<LeftMouse><col,row>") of |nvim_input()| has the same limitation.
299 ///
300 /// @param button Mouse button: one of "left", "right", "middle", "wheel".
301 /// @param action For ordinary buttons, one of "press", "drag", "release".
302 ///               For the wheel, one of "up", "down", "left", "right".
303 /// @param modifier String of modifiers each represented by a single char.
304 ///                 The same specifiers are used as for a key press, except
305 ///                 that the "-" separator is optional, so "C-A-", "c-a"
306 ///                 and "CA" can all be used to specify Ctrl+Alt+click.
307 /// @param grid Grid number if the client uses |ui-multigrid|, else 0.
308 /// @param row Mouse row-position (zero-based, like redraw events)
309 /// @param col Mouse column-position (zero-based, like redraw events)
310 /// @param[out] err Error details, if any
nvim_input_mouse(String button,String action,String modifier,Integer grid,Integer row,Integer col,Error * err)311 void nvim_input_mouse(String button, String action, String modifier, Integer grid, Integer row,
312                       Integer col, Error *err)
313   FUNC_API_SINCE(6) FUNC_API_FAST
314 {
315   if (button.data == NULL || action.data == NULL) {
316     goto error;
317   }
318 
319   int code = 0;
320 
321   if (strequal(button.data, "left")) {
322     code = KE_LEFTMOUSE;
323   } else if (strequal(button.data, "middle")) {
324     code = KE_MIDDLEMOUSE;
325   } else if (strequal(button.data, "right")) {
326     code = KE_RIGHTMOUSE;
327   } else if (strequal(button.data, "wheel")) {
328     code = KE_MOUSEDOWN;
329   } else {
330     goto error;
331   }
332 
333   if (code == KE_MOUSEDOWN) {
334     if (strequal(action.data, "down")) {
335       code = KE_MOUSEUP;
336     } else if (strequal(action.data, "up")) {
337       // code = KE_MOUSEDOWN
338     } else if (strequal(action.data, "left")) {
339       code = KE_MOUSERIGHT;
340     } else if (strequal(action.data, "right")) {
341       code = KE_MOUSELEFT;
342     } else {
343       goto error;
344     }
345   } else {
346     if (strequal(action.data, "press")) {
347       // pass
348     } else if (strequal(action.data, "drag")) {
349       code += KE_LEFTDRAG - KE_LEFTMOUSE;
350     } else if (strequal(action.data, "release")) {
351       code += KE_LEFTRELEASE - KE_LEFTMOUSE;
352     } else {
353       goto error;
354     }
355   }
356 
357   int modmask = 0;
358   for (size_t i = 0; i < modifier.size; i++) {
359     char byte = modifier.data[i];
360     if (byte == '-') {
361       continue;
362     }
363     int mod = name_to_mod_mask(byte);
364     if (mod == 0) {
365       api_set_error(err, kErrorTypeValidation,
366                     "invalid modifier %c", byte);
367       return;
368     }
369     modmask |= mod;
370   }
371 
372   input_enqueue_mouse(code, (uint8_t)modmask, (int)grid, (int)row, (int)col);
373   return;
374 
375 error:
376   api_set_error(err, kErrorTypeValidation,
377                 "invalid button or action");
378 }
379 
380 /// Replaces terminal codes and |keycodes| (<CR>, <Esc>, ...) in a string with
381 /// the internal representation.
382 ///
383 /// @param str        String to be converted.
384 /// @param from_part  Legacy Vim parameter. Usually true.
385 /// @param do_lt      Also translate <lt>. Ignored if `special` is false.
386 /// @param special    Replace |keycodes|, e.g. <CR> becomes a "\n" char.
387 /// @see replace_termcodes
388 /// @see cpoptions
nvim_replace_termcodes(String str,Boolean from_part,Boolean do_lt,Boolean special)389 String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Boolean special)
390   FUNC_API_SINCE(1)
391 {
392   if (str.size == 0) {
393     // Empty string
394     return (String) { .data = NULL, .size = 0 };
395   }
396 
397   char *ptr = NULL;
398   replace_termcodes((char_u *)str.data, str.size, (char_u **)&ptr,
399                     from_part, do_lt, special, CPO_TO_CPO_FLAGS);
400   return cstr_as_string(ptr);
401 }
402 
403 
404 /// Execute Lua code. Parameters (if any) are available as `...` inside the
405 /// chunk. The chunk can return a value.
406 ///
407 /// Only statements are executed. To evaluate an expression, prefix it
408 /// with `return`: return my_function(...)
409 ///
410 /// @param code       Lua code to execute
411 /// @param args       Arguments to the code
412 /// @param[out] err   Details of an error encountered while parsing
413 ///                   or executing the Lua code.
414 ///
415 /// @return           Return value of Lua code if present or NIL.
nvim_exec_lua(String code,Array args,Error * err)416 Object nvim_exec_lua(String code, Array args, Error *err)
417   FUNC_API_SINCE(7)
418   FUNC_API_REMOTE_ONLY
419 {
420   return nlua_exec(code, args, err);
421 }
422 
423 /// Notify the user with a message
424 ///
425 /// Relays the call to vim.notify . By default forwards your message in the
426 /// echo area but can be overridden to trigger desktop notifications.
427 ///
428 /// @param msg        Message to display to the user
429 /// @param log_level  The log level
430 /// @param opts       Reserved for future use.
431 /// @param[out] err   Error details, if any
nvim_notify(String msg,Integer log_level,Dictionary opts,Error * err)432 Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err)
433   FUNC_API_SINCE(7)
434 {
435   FIXED_TEMP_ARRAY(args, 3);
436   args.items[0] = STRING_OBJ(msg);
437   args.items[1] = INTEGER_OBJ(log_level);
438   args.items[2] = DICTIONARY_OBJ(opts);
439 
440   return nlua_exec(STATIC_CSTR_AS_STRING("return vim.notify(...)"), args, err);
441 }
442 
443 /// Calculates the number of display cells occupied by `text`.
444 /// <Tab> counts as one cell.
445 ///
446 /// @param text       Some text
447 /// @param[out] err   Error details, if any
448 /// @return Number of cells
nvim_strwidth(String text,Error * err)449 Integer nvim_strwidth(String text, Error *err)
450   FUNC_API_SINCE(1)
451 {
452   if (text.size > INT_MAX) {
453     api_set_error(err, kErrorTypeValidation, "String is too long");
454     return 0;
455   }
456 
457   return (Integer)mb_string2cells((char_u *)text.data);
458 }
459 
460 /// Gets the paths contained in 'runtimepath'.
461 ///
462 /// @return List of paths
nvim_list_runtime_paths(Error * err)463 ArrayOf(String) nvim_list_runtime_paths(Error *err)
464   FUNC_API_SINCE(1)
465 {
466   return nvim_get_runtime_file(NULL_STRING, true, err);
467 }
468 
nvim__runtime_inspect(void)469 Array nvim__runtime_inspect(void)
470 {
471   return runtime_inspect();
472 }
473 
474 /// Find files in runtime directories
475 ///
476 /// 'name' can contain wildcards. For example
477 /// nvim_get_runtime_file("colors/*.vim", true) will return all color
478 /// scheme files. Always use forward slashes (/) in the search pattern for
479 /// subdirectories regardless of platform.
480 ///
481 /// It is not an error to not find any files. An empty array is returned then.
482 ///
483 /// @param name pattern of files to search for
484 /// @param all whether to return all matches or only the first
485 /// @return list of absolute paths to the found files
nvim_get_runtime_file(String name,Boolean all,Error * err)486 ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err)
487   FUNC_API_SINCE(7)
488   FUNC_API_FAST
489 {
490   Array rv = ARRAY_DICT_INIT;
491 
492   int flags = DIP_DIRFILE | (all ? DIP_ALL : 0);
493 
494   do_in_runtimepath((char_u *)(name.size ? name.data : ""),
495                     flags, find_runtime_cb, &rv);
496   return rv;
497 }
498 
find_runtime_cb(char_u * fname,void * cookie)499 static void find_runtime_cb(char_u *fname, void *cookie)
500 {
501   Array *rv = (Array *)cookie;
502   if (fname != NULL) {
503     ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname)));
504   }
505 }
506 
nvim__get_lib_dir(void)507 String nvim__get_lib_dir(void)
508 {
509   return cstr_as_string(get_lib_dir());
510 }
511 
512 /// Find files in runtime directories
513 ///
514 /// @param pat pattern of files to search for
515 /// @param all whether to return all matches or only the first
516 /// @param options
517 ///          is_lua: only search lua subdirs
518 /// @return list of absolute paths to the found files
nvim__get_runtime(Array pat,Boolean all,Dict (runtime)* opts,Error * err)519 ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, Error *err)
520   FUNC_API_SINCE(8)
521   FUNC_API_FAST
522 {
523   bool is_lua = api_object_to_bool(opts->is_lua, "is_lua", false, err);
524   if (ERROR_SET(err)) {
525     return (Array)ARRAY_DICT_INIT;
526   }
527   return runtime_get_named(is_lua, pat, all);
528 }
529 
530 
531 /// Changes the global working directory.
532 ///
533 /// @param dir      Directory path
534 /// @param[out] err Error details, if any
nvim_set_current_dir(String dir,Error * err)535 void nvim_set_current_dir(String dir, Error *err)
536   FUNC_API_SINCE(1)
537 {
538   if (dir.size >= MAXPATHL) {
539     api_set_error(err, kErrorTypeValidation, "Directory name is too long");
540     return;
541   }
542 
543   char string[MAXPATHL];
544   memcpy(string, dir.data, dir.size);
545   string[dir.size] = NUL;
546 
547   try_start();
548 
549   if (vim_chdir((char_u *)string)) {
550     if (!try_end(err)) {
551       api_set_error(err, kErrorTypeException, "Failed to change directory");
552     }
553     return;
554   }
555 
556   post_chdir(kCdScopeGlobal, true);
557   try_end(err);
558 }
559 
560 /// Gets the current line.
561 ///
562 /// @param[out] err Error details, if any
563 /// @return Current line string
nvim_get_current_line(Error * err)564 String nvim_get_current_line(Error *err)
565   FUNC_API_SINCE(1)
566 {
567   return buffer_get_line(curbuf->handle, curwin->w_cursor.lnum - 1, err);
568 }
569 
570 /// Sets the current line.
571 ///
572 /// @param line     Line contents
573 /// @param[out] err Error details, if any
nvim_set_current_line(String line,Error * err)574 void nvim_set_current_line(String line, Error *err)
575   FUNC_API_SINCE(1)
576   FUNC_API_CHECK_TEXTLOCK
577 {
578   buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err);
579 }
580 
581 /// Deletes the current line.
582 ///
583 /// @param[out] err Error details, if any
nvim_del_current_line(Error * err)584 void nvim_del_current_line(Error *err)
585   FUNC_API_SINCE(1)
586   FUNC_API_CHECK_TEXTLOCK
587 {
588   buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err);
589 }
590 
591 /// Gets a global (g:) variable.
592 ///
593 /// @param name     Variable name
594 /// @param[out] err Error details, if any
595 /// @return Variable value
nvim_get_var(String name,Error * err)596 Object nvim_get_var(String name, Error *err)
597   FUNC_API_SINCE(1)
598 {
599   return dict_get_value(&globvardict, name, err);
600 }
601 
602 /// Sets a global (g:) variable.
603 ///
604 /// @param name     Variable name
605 /// @param value    Variable value
606 /// @param[out] err Error details, if any
nvim_set_var(String name,Object value,Error * err)607 void nvim_set_var(String name, Object value, Error *err)
608   FUNC_API_SINCE(1)
609 {
610   dict_set_var(&globvardict, name, value, false, false, err);
611 }
612 
613 /// Removes a global (g:) variable.
614 ///
615 /// @param name     Variable name
616 /// @param[out] err Error details, if any
nvim_del_var(String name,Error * err)617 void nvim_del_var(String name, Error *err)
618   FUNC_API_SINCE(1)
619 {
620   dict_set_var(&globvardict, name, NIL, true, false, err);
621 }
622 
623 /// Gets a v: variable.
624 ///
625 /// @param name     Variable name
626 /// @param[out] err Error details, if any
627 /// @return         Variable value
nvim_get_vvar(String name,Error * err)628 Object nvim_get_vvar(String name, Error *err)
629   FUNC_API_SINCE(1)
630 {
631   return dict_get_value(&vimvardict, name, err);
632 }
633 
634 /// Sets a v: variable, if it is not readonly.
635 ///
636 /// @param name     Variable name
637 /// @param value    Variable value
638 /// @param[out] err Error details, if any
nvim_set_vvar(String name,Object value,Error * err)639 void nvim_set_vvar(String name, Object value, Error *err)
640   FUNC_API_SINCE(6)
641 {
642   dict_set_var(&vimvardict, name, value, false, false, err);
643 }
644 
645 /// Gets an option value string.
646 ///
647 /// @param name     Option name
648 /// @param[out] err Error details, if any
649 /// @return         Option value (global)
nvim_get_option(String name,Error * err)650 Object nvim_get_option(String name, Error *err)
651   FUNC_API_SINCE(1)
652 {
653   return get_option_from(NULL, SREQ_GLOBAL, name, err);
654 }
655 
656 /// Gets the option information for all options.
657 ///
658 /// The dictionary has the full option names as keys and option metadata
659 /// dictionaries as detailed at |nvim_get_option_info|.
660 ///
661 /// @return dictionary of all options
nvim_get_all_options_info(Error * err)662 Dictionary nvim_get_all_options_info(Error *err)
663   FUNC_API_SINCE(7)
664 {
665   return get_all_vimoptions();
666 }
667 
668 /// Gets the option information for one option
669 ///
670 /// Resulting dictionary has keys:
671 ///     - name: Name of the option (like 'filetype')
672 ///     - shortname: Shortened name of the option (like 'ft')
673 ///     - type: type of option ("string", "number" or "boolean")
674 ///     - default: The default value for the option
675 ///     - was_set: Whether the option was set.
676 ///
677 ///     - last_set_sid: Last set script id (if any)
678 ///     - last_set_linenr: line number where option was set
679 ///     - last_set_chan: Channel where option was set (0 for local)
680 ///
681 ///     - scope: one of "global", "win", or "buf"
682 ///     - global_local: whether win or buf option has a global value
683 ///
684 ///     - commalist: List of comma separated values
685 ///     - flaglist: List of single char flags
686 ///
687 ///
688 /// @param          name Option name
689 /// @param[out] err Error details, if any
690 /// @return         Option Information
nvim_get_option_info(String name,Error * err)691 Dictionary nvim_get_option_info(String name, Error *err)
692   FUNC_API_SINCE(7)
693 {
694   return get_vimoption(name, err);
695 }
696 
697 /// Sets an option value.
698 ///
699 /// @param channel_id
700 /// @param name     Option name
701 /// @param value    New option value
702 /// @param[out] err Error details, if any
nvim_set_option(uint64_t channel_id,String name,Object value,Error * err)703 void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err)
704   FUNC_API_SINCE(1)
705 {
706   set_option_to(channel_id, NULL, SREQ_GLOBAL, name, value, err);
707 }
708 
709 /// Echo a message.
710 ///
711 /// @param chunks  A list of [text, hl_group] arrays, each representing a
712 ///                text chunk with specified highlight. `hl_group` element
713 ///                can be omitted for no highlight.
714 /// @param history  if true, add to |message-history|.
715 /// @param opts  Optional parameters. Reserved for future use.
nvim_echo(Array chunks,Boolean history,Dictionary opts,Error * err)716 void nvim_echo(Array chunks, Boolean history, Dictionary opts, Error *err)
717   FUNC_API_SINCE(7)
718 {
719   HlMessage hl_msg = parse_hl_msg(chunks, err);
720   if (ERROR_SET(err)) {
721     goto error;
722   }
723 
724   if (opts.size > 0) {
725     api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
726     goto error;
727   }
728 
729   no_wait_return++;
730   msg_start();
731   msg_clr_eos();
732   bool need_clear = false;
733   for (uint32_t i = 0; i < kv_size(hl_msg); i++) {
734     HlMessageChunk chunk = kv_A(hl_msg, i);
735     msg_multiline_attr((const char *)chunk.text.data, chunk.attr,
736                        false, &need_clear);
737   }
738   if (history) {
739     msg_ext_set_kind("echomsg");
740     add_hl_msg_hist(hl_msg);
741   } else {
742     msg_ext_set_kind("echo");
743   }
744   no_wait_return--;
745   msg_end();
746 
747 error:
748   clear_hl_msg(&hl_msg);
749 }
750 
751 /// Writes a message to the Vim output buffer. Does not append "\n", the
752 /// message is buffered (won't display) until a linefeed is written.
753 ///
754 /// @param str Message
nvim_out_write(String str)755 void nvim_out_write(String str)
756   FUNC_API_SINCE(1)
757 {
758   write_msg(str, false);
759 }
760 
761 /// Writes a message to the Vim error buffer. Does not append "\n", the
762 /// message is buffered (won't display) until a linefeed is written.
763 ///
764 /// @param str Message
nvim_err_write(String str)765 void nvim_err_write(String str)
766   FUNC_API_SINCE(1)
767 {
768   write_msg(str, true);
769 }
770 
771 /// Writes a message to the Vim error buffer. Appends "\n", so the buffer is
772 /// flushed (and displayed).
773 ///
774 /// @param str Message
775 /// @see nvim_err_write()
nvim_err_writeln(String str)776 void nvim_err_writeln(String str)
777   FUNC_API_SINCE(1)
778 {
779   nvim_err_write(str);
780   nvim_err_write((String) { .data = "\n", .size = 1 });
781 }
782 
783 /// Gets the current list of buffer handles
784 ///
785 /// Includes unlisted (unloaded/deleted) buffers, like `:ls!`.
786 /// Use |nvim_buf_is_loaded()| to check if a buffer is loaded.
787 ///
788 /// @return List of buffer handles
nvim_list_bufs(void)789 ArrayOf(Buffer) nvim_list_bufs(void)
790   FUNC_API_SINCE(1)
791 {
792   Array rv = ARRAY_DICT_INIT;
793 
794   FOR_ALL_BUFFERS(b) {
795     rv.size++;
796   }
797 
798   rv.items = xmalloc(sizeof(Object) * rv.size);
799   size_t i = 0;
800 
801   FOR_ALL_BUFFERS(b) {
802     rv.items[i++] = BUFFER_OBJ(b->handle);
803   }
804 
805   return rv;
806 }
807 
808 /// Gets the current buffer.
809 ///
810 /// @return Buffer handle
nvim_get_current_buf(void)811 Buffer nvim_get_current_buf(void)
812   FUNC_API_SINCE(1)
813 {
814   return curbuf->handle;
815 }
816 
817 /// Sets the current buffer.
818 ///
819 /// @param buffer   Buffer handle
820 /// @param[out] err Error details, if any
nvim_set_current_buf(Buffer buffer,Error * err)821 void nvim_set_current_buf(Buffer buffer, Error *err)
822   FUNC_API_SINCE(1)
823   FUNC_API_CHECK_TEXTLOCK
824 {
825   buf_T *buf = find_buffer_by_handle(buffer, err);
826 
827   if (!buf) {
828     return;
829   }
830 
831   try_start();
832   int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0);
833   if (!try_end(err) && result == FAIL) {
834     api_set_error(err,
835                   kErrorTypeException,
836                   "Failed to switch to buffer %d",
837                   buffer);
838   }
839 }
840 
841 /// Gets the current list of window handles.
842 ///
843 /// @return List of window handles
nvim_list_wins(void)844 ArrayOf(Window) nvim_list_wins(void)
845   FUNC_API_SINCE(1)
846 {
847   Array rv = ARRAY_DICT_INIT;
848 
849   FOR_ALL_TAB_WINDOWS(tp, wp) {
850     rv.size++;
851   }
852 
853   rv.items = xmalloc(sizeof(Object) * rv.size);
854   size_t i = 0;
855 
856   FOR_ALL_TAB_WINDOWS(tp, wp) {
857     rv.items[i++] = WINDOW_OBJ(wp->handle);
858   }
859 
860   return rv;
861 }
862 
863 /// Gets the current window.
864 ///
865 /// @return Window handle
nvim_get_current_win(void)866 Window nvim_get_current_win(void)
867   FUNC_API_SINCE(1)
868 {
869   return curwin->handle;
870 }
871 
872 /// Sets the current window.
873 ///
874 /// @param window Window handle
875 /// @param[out] err Error details, if any
nvim_set_current_win(Window window,Error * err)876 void nvim_set_current_win(Window window, Error *err)
877   FUNC_API_SINCE(1)
878   FUNC_API_CHECK_TEXTLOCK
879 {
880   win_T *win = find_window_by_handle(window, err);
881 
882   if (!win) {
883     return;
884   }
885 
886   try_start();
887   goto_tabpage_win(win_find_tabpage(win), win);
888   if (!try_end(err) && win != curwin) {
889     api_set_error(err,
890                   kErrorTypeException,
891                   "Failed to switch to window %d",
892                   window);
893   }
894 }
895 
896 /// Creates a new, empty, unnamed buffer.
897 ///
898 /// @param listed Sets 'buflisted'
899 /// @param scratch Creates a "throwaway" |scratch-buffer| for temporary work
900 ///                (always 'nomodified'). Also sets 'nomodeline' on the buffer.
901 /// @param[out] err Error details, if any
902 /// @return Buffer handle, or 0 on error
903 ///
904 /// @see buf_open_scratch
nvim_create_buf(Boolean listed,Boolean scratch,Error * err)905 Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
906   FUNC_API_SINCE(6)
907 {
908   try_start();
909   buf_T *buf = buflist_new(NULL, NULL, (linenr_T)0,
910                            BLN_NOOPT | BLN_NEW | (listed ? BLN_LISTED : 0));
911   try_end(err);
912   if (buf == NULL) {
913     goto fail;
914   }
915 
916   // Open the memline for the buffer. This will avoid spurious autocmds when
917   // a later nvim_buf_set_lines call would have needed to "open" the buffer.
918   try_start();
919   block_autocmds();
920   int status = ml_open(buf);
921   unblock_autocmds();
922   try_end(err);
923   if (status == FAIL) {
924     goto fail;
925   }
926 
927   if (scratch) {
928     aco_save_T aco;
929     aucmd_prepbuf(&aco, buf);
930     set_option_value("bufhidden", 0L, "hide", OPT_LOCAL);
931     set_option_value("buftype", 0L, "nofile", OPT_LOCAL);
932     set_option_value("swapfile", 0L, NULL, OPT_LOCAL);
933     set_option_value("modeline", 0L, NULL, OPT_LOCAL);  // 'nomodeline'
934     aucmd_restbuf(&aco);
935   }
936   return buf->b_fnum;
937 
938 fail:
939   if (!ERROR_SET(err)) {
940     api_set_error(err, kErrorTypeException, "Failed to create buffer");
941   }
942   return 0;
943 }
944 
945 /// Open a terminal instance in a buffer
946 ///
947 /// By default (and currently the only option) the terminal will not be
948 /// connected to an external process. Instead, input send on the channel
949 /// will be echoed directly by the terminal. This is useful to display
950 /// ANSI terminal sequences returned as part of a rpc message, or similar.
951 ///
952 /// Note: to directly initiate the terminal using the right size, display the
953 /// buffer in a configured window before calling this. For instance, for a
954 /// floating display, first create an empty buffer using |nvim_create_buf()|,
955 /// then display it using |nvim_open_win()|, and then  call this function.
956 /// Then |nvim_chan_send()| can be called immediately to process sequences
957 /// in a virtual terminal having the intended size.
958 ///
959 /// @param buffer the buffer to use (expected to be empty)
960 /// @param opts   Optional parameters.
961 ///          - on_input: lua callback for input sent, i e keypresses in terminal
962 ///            mode. Note: keypresses are sent raw as they would be to the pty
963 ///            master end. For instance, a carriage return is sent
964 ///            as a "\r", not as a "\n". |textlock| applies. It is possible
965 ///            to call |nvim_chan_send| directly in the callback however.
966 ///                 ["input", term, bufnr, data]
967 /// @param[out] err Error details, if any
968 /// @return Channel id, or 0 on error
nvim_open_term(Buffer buffer,DictionaryOf (LuaRef)opts,Error * err)969 Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err)
970   FUNC_API_SINCE(7)
971 {
972   buf_T *buf = find_buffer_by_handle(buffer, err);
973   if (!buf) {
974     return 0;
975   }
976 
977   LuaRef cb = LUA_NOREF;
978   for (size_t i = 0; i < opts.size; i++) {
979     String k = opts.items[i].key;
980     Object *v = &opts.items[i].value;
981     if (strequal("on_input", k.data)) {
982       if (v->type != kObjectTypeLuaRef) {
983         api_set_error(err, kErrorTypeValidation,
984                       "%s is not a function", "on_input");
985         return 0;
986       }
987       cb = v->data.luaref;
988       v->data.luaref = LUA_NOREF;
989       break;
990     } else {
991       api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
992     }
993   }
994 
995   TerminalOptions topts;
996   Channel *chan = channel_alloc(kChannelStreamInternal);
997   chan->stream.internal.cb = cb;
998   topts.data = chan;
999   // NB: overridden in terminal_check_size if a window is already
1000   // displaying the buffer
1001   topts.width = (uint16_t)MAX(curwin->w_width_inner - win_col_off(curwin), 0);
1002   topts.height = (uint16_t)curwin->w_height_inner;
1003   topts.write_cb = term_write;
1004   topts.resize_cb = term_resize;
1005   topts.close_cb = term_close;
1006   Terminal *term = terminal_open(buf, topts);
1007   terminal_check_size(term);
1008   chan->term = term;
1009   return (Integer)chan->id;
1010 }
1011 
term_write(char * buf,size_t size,void * data)1012 static void term_write(char *buf, size_t size, void *data)
1013 {
1014   Channel *chan = data;
1015   LuaRef cb = chan->stream.internal.cb;
1016   if (cb == LUA_NOREF) {
1017     return;
1018   }
1019   FIXED_TEMP_ARRAY(args, 3);
1020   args.items[0] = INTEGER_OBJ((Integer)chan->id);
1021   args.items[1] = BUFFER_OBJ(terminal_buf(chan->term));
1022   args.items[2] = STRING_OBJ(((String){ .data = buf, .size = size }));
1023   textlock++;
1024   nlua_call_ref(cb, "input", args, false, NULL);
1025   textlock--;
1026 }
1027 
term_resize(uint16_t width,uint16_t height,void * data)1028 static void term_resize(uint16_t width, uint16_t height, void *data)
1029 {
1030   // TODO(bfredl): lua callback
1031 }
1032 
term_close(void * data)1033 static void term_close(void *data)
1034 {
1035   Channel *chan = data;
1036   terminal_destroy(chan->term);
1037   chan->term = NULL;
1038   api_free_luaref(chan->stream.internal.cb);
1039   chan->stream.internal.cb = LUA_NOREF;
1040   channel_decref(chan);
1041 }
1042 
1043 
1044 /// Send data to channel `id`. For a job, it writes it to the
1045 /// stdin of the process. For the stdio channel |channel-stdio|,
1046 /// it writes to Nvim's stdout.  For an internal terminal instance
1047 /// (|nvim_open_term()|) it writes directly to terimal output.
1048 /// See |channel-bytes| for more information.
1049 ///
1050 /// This function writes raw data, not RPC messages.  If the channel
1051 /// was created with `rpc=true` then the channel expects RPC
1052 /// messages, use |vim.rpcnotify()| and |vim.rpcrequest()| instead.
1053 ///
1054 /// @param chan id of the channel
1055 /// @param data data to write. 8-bit clean: can contain NUL bytes.
1056 /// @param[out] err Error details, if any
nvim_chan_send(Integer chan,String data,Error * err)1057 void nvim_chan_send(Integer chan, String data, Error *err)
1058   FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY FUNC_API_LUA_ONLY
1059 {
1060   const char *error = NULL;
1061   if (!data.size) {
1062     return;
1063   }
1064 
1065   channel_send((uint64_t)chan, data.data, data.size,
1066                false, &error);
1067   if (error) {
1068     api_set_error(err, kErrorTypeValidation, "%s", error);
1069   }
1070 }
1071 
1072 /// Gets the current list of tabpage handles.
1073 ///
1074 /// @return List of tabpage handles
nvim_list_tabpages(void)1075 ArrayOf(Tabpage) nvim_list_tabpages(void)
1076   FUNC_API_SINCE(1)
1077 {
1078   Array rv = ARRAY_DICT_INIT;
1079 
1080   FOR_ALL_TABS(tp) {
1081     rv.size++;
1082   }
1083 
1084   rv.items = xmalloc(sizeof(Object) * rv.size);
1085   size_t i = 0;
1086 
1087   FOR_ALL_TABS(tp) {
1088     rv.items[i++] = TABPAGE_OBJ(tp->handle);
1089   }
1090 
1091   return rv;
1092 }
1093 
1094 /// Gets the current tabpage.
1095 ///
1096 /// @return Tabpage handle
nvim_get_current_tabpage(void)1097 Tabpage nvim_get_current_tabpage(void)
1098   FUNC_API_SINCE(1)
1099 {
1100   return curtab->handle;
1101 }
1102 
1103 /// Sets the current tabpage.
1104 ///
1105 /// @param tabpage  Tabpage handle
1106 /// @param[out] err Error details, if any
nvim_set_current_tabpage(Tabpage tabpage,Error * err)1107 void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
1108   FUNC_API_SINCE(1)
1109   FUNC_API_CHECK_TEXTLOCK
1110 {
1111   tabpage_T *tp = find_tab_by_handle(tabpage, err);
1112 
1113   if (!tp) {
1114     return;
1115   }
1116 
1117   try_start();
1118   goto_tabpage_tp(tp, true, true);
1119   if (!try_end(err) && tp != curtab) {
1120     api_set_error(err,
1121                   kErrorTypeException,
1122                   "Failed to switch to tabpage %d",
1123                   tabpage);
1124   }
1125 }
1126 
1127 /// Pastes at cursor, in any mode.
1128 ///
1129 /// Invokes the `vim.paste` handler, which handles each mode appropriately.
1130 /// Sets redo/undo. Faster than |nvim_input()|. Lines break at LF ("\n").
1131 ///
1132 /// Errors ('nomodifiable', `vim.paste()` failure, …) are reflected in `err`
1133 /// but do not affect the return value (which is strictly decided by
1134 /// `vim.paste()`).  On error, subsequent calls are ignored ("drained") until
1135 /// the next paste is initiated (phase 1 or -1).
1136 ///
1137 /// @param data  Multiline input. May be binary (containing NUL bytes).
1138 /// @param crlf  Also break lines at CR and CRLF.
1139 /// @param phase  -1: paste in a single call (i.e. without streaming).
1140 ///               To "stream" a paste, call `nvim_paste` sequentially with
1141 ///               these `phase` values:
1142 ///                 - 1: starts the paste (exactly once)
1143 ///                 - 2: continues the paste (zero or more times)
1144 ///                 - 3: ends the paste (exactly once)
1145 /// @param[out] err Error details, if any
1146 /// @return
1147 ///     - true: Client may continue pasting.
1148 ///     - false: Client must cancel the paste.
nvim_paste(String data,Boolean crlf,Integer phase,Error * err)1149 Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
1150   FUNC_API_SINCE(6)
1151   FUNC_API_CHECK_TEXTLOCK
1152 {
1153   static bool draining = false;
1154   bool cancel = false;
1155 
1156   if (phase < -1 || phase > 3) {
1157     api_set_error(err, kErrorTypeValidation, "Invalid phase: %" PRId64, phase);
1158     return false;
1159   }
1160   Array args = ARRAY_DICT_INIT;
1161   Object rv = OBJECT_INIT;
1162   if (phase == -1 || phase == 1) {  // Start of paste-stream.
1163     draining = false;
1164   } else if (draining) {
1165     // Skip remaining chunks.  Report error only once per "stream".
1166     goto theend;
1167   }
1168   Array lines = string_to_array(data, crlf);
1169   ADD(args, ARRAY_OBJ(lines));
1170   ADD(args, INTEGER_OBJ(phase));
1171   rv = nvim_exec_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args,
1172                      err);
1173   if (ERROR_SET(err)) {
1174     draining = true;
1175     goto theend;
1176   }
1177   if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 1)) {
1178     ResetRedobuff();
1179     AppendCharToRedobuff('a');  // Dot-repeat.
1180   }
1181   // vim.paste() decides if client should cancel.  Errors do NOT cancel: we
1182   // want to drain remaining chunks (rather than divert them to main input).
1183   cancel = (rv.type == kObjectTypeBoolean && !rv.data.boolean);
1184   if (!cancel && !(State & CMDLINE)) {  // Dot-repeat.
1185     for (size_t i = 0; i < lines.size; i++) {
1186       String s = lines.items[i].data.string;
1187       assert(data.size <= INT_MAX);
1188       AppendToRedobuffLit((char_u *)s.data, (int)s.size);
1189       // readfile()-style: "\n" is indicated by presence of N+1 item.
1190       if (i + 1 < lines.size) {
1191         AppendCharToRedobuff(NL);
1192       }
1193     }
1194   }
1195   if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 3)) {
1196     AppendCharToRedobuff(ESC);  // Dot-repeat.
1197   }
1198 theend:
1199   api_free_object(rv);
1200   api_free_array(args);
1201   if (cancel || phase == -1 || phase == 3) {  // End of paste-stream.
1202     draining = false;
1203   }
1204 
1205   return !cancel;
1206 }
1207 
1208 /// Puts text at cursor, in any mode.
1209 ///
1210 /// Compare |:put| and |p| which are always linewise.
1211 ///
1212 /// @param lines  |readfile()|-style list of lines. |channel-lines|
1213 /// @param type  Edit behavior: any |getregtype()| result, or:
1214 ///              - "b" |blockwise-visual| mode (may include width, e.g. "b3")
1215 ///              - "c" |charwise| mode
1216 ///              - "l" |linewise| mode
1217 ///              - ""  guess by contents, see |setreg()|
1218 /// @param after  If true insert after cursor (like |p|), or before (like |P|).
1219 /// @param follow  If true place cursor at end of inserted text.
1220 /// @param[out] err Error details, if any
nvim_put(ArrayOf (String)lines,String type,Boolean after,Boolean follow,Error * err)1221 void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow, Error *err)
1222   FUNC_API_SINCE(6)
1223   FUNC_API_CHECK_TEXTLOCK
1224 {
1225   yankreg_T *reg = xcalloc(1, sizeof(yankreg_T));
1226   if (!prepare_yankreg_from_object(reg, type, lines.size)) {
1227     api_set_error(err, kErrorTypeValidation, "Invalid type: '%s'", type.data);
1228     goto cleanup;
1229   }
1230   if (lines.size == 0) {
1231     goto cleanup;  // Nothing to do.
1232   }
1233 
1234   for (size_t i = 0; i < lines.size; i++) {
1235     if (lines.items[i].type != kObjectTypeString) {
1236       api_set_error(err, kErrorTypeValidation,
1237                     "Invalid lines (expected array of strings)");
1238       goto cleanup;
1239     }
1240     String line = lines.items[i].data.string;
1241     reg->y_array[i] = (char_u *)xmemdupz(line.data, line.size);
1242     memchrsub(reg->y_array[i], NUL, NL, line.size);
1243   }
1244 
1245   finish_yankreg_from_object(reg, false);
1246 
1247   TRY_WRAP({
1248     try_start();
1249     bool VIsual_was_active = VIsual_active;
1250     msg_silent++;  // Avoid "N more lines" message.
1251     do_put(0, reg, after ? FORWARD : BACKWARD, 1, follow ? PUT_CURSEND : 0);
1252     msg_silent--;
1253     VIsual_active = VIsual_was_active;
1254     try_end(err);
1255   });
1256 
1257 cleanup:
1258   free_register(reg);
1259   xfree(reg);
1260 }
1261 
1262 /// Subscribes to event broadcasts.
1263 ///
1264 /// @param channel_id Channel id (passed automatically by the dispatcher)
1265 /// @param event      Event type string
nvim_subscribe(uint64_t channel_id,String event)1266 void nvim_subscribe(uint64_t channel_id, String event)
1267   FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
1268 {
1269   size_t length = (event.size < METHOD_MAXLEN ? event.size : METHOD_MAXLEN);
1270   char e[METHOD_MAXLEN + 1];
1271   memcpy(e, event.data, length);
1272   e[length] = NUL;
1273   rpc_subscribe(channel_id, e);
1274 }
1275 
1276 /// Unsubscribes to event broadcasts.
1277 ///
1278 /// @param channel_id Channel id (passed automatically by the dispatcher)
1279 /// @param event      Event type string
nvim_unsubscribe(uint64_t channel_id,String event)1280 void nvim_unsubscribe(uint64_t channel_id, String event)
1281   FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
1282 {
1283   size_t length = (event.size < METHOD_MAXLEN ?
1284                    event.size :
1285                    METHOD_MAXLEN);
1286   char e[METHOD_MAXLEN + 1];
1287   memcpy(e, event.data, length);
1288   e[length] = NUL;
1289   rpc_unsubscribe(channel_id, e);
1290 }
1291 
1292 /// Returns the 24-bit RGB value of a |nvim_get_color_map()| color name or
1293 /// "#rrggbb" hexadecimal string.
1294 ///
1295 /// Example:
1296 /// <pre>
1297 ///     :echo nvim_get_color_by_name("Pink")
1298 ///     :echo nvim_get_color_by_name("#cbcbcb")
1299 /// </pre>
1300 ///
1301 /// @param name Color name or "#rrggbb" string
1302 /// @return 24-bit RGB value, or -1 for invalid argument.
nvim_get_color_by_name(String name)1303 Integer nvim_get_color_by_name(String name)
1304   FUNC_API_SINCE(1)
1305 {
1306   return name_to_color(name.data);
1307 }
1308 
1309 /// Returns a map of color names and RGB values.
1310 ///
1311 /// Keys are color names (e.g. "Aqua") and values are 24-bit RGB color values
1312 /// (e.g. 65535).
1313 ///
1314 /// @return Map of color names and RGB values.
nvim_get_color_map(void)1315 Dictionary nvim_get_color_map(void)
1316   FUNC_API_SINCE(1)
1317 {
1318   Dictionary colors = ARRAY_DICT_INIT;
1319 
1320   for (int i = 0; color_name_table[i].name != NULL; i++) {
1321     PUT(colors, color_name_table[i].name,
1322         INTEGER_OBJ(color_name_table[i].color));
1323   }
1324   return colors;
1325 }
1326 
1327 /// Gets a map of the current editor state.
1328 ///
1329 /// @param opts  Optional parameters.
1330 ///               - types:  List of |context-types| ("regs", "jumps", "bufs",
1331 ///                 "gvars", …) to gather, or empty for "all".
1332 /// @param[out]  err  Error details, if any
1333 ///
1334 /// @return map of global |context|.
nvim_get_context(Dict (context)* opts,Error * err)1335 Dictionary nvim_get_context(Dict(context) *opts, Error *err)
1336   FUNC_API_SINCE(6)
1337 {
1338   Array types = ARRAY_DICT_INIT;
1339   if (opts->types.type == kObjectTypeArray) {
1340     types = opts->types.data.array;
1341   } else if (opts->types.type != kObjectTypeNil) {
1342     api_set_error(err, kErrorTypeValidation, "invalid value for key: types");
1343     return (Dictionary)ARRAY_DICT_INIT;
1344   }
1345 
1346   int int_types = types.size > 0 ? 0 : kCtxAll;
1347   if (types.size > 0) {
1348     for (size_t i = 0; i < types.size; i++) {
1349       if (types.items[i].type == kObjectTypeString) {
1350         const char *const s = types.items[i].data.string.data;
1351         if (strequal(s, "regs")) {
1352           int_types |= kCtxRegs;
1353         } else if (strequal(s, "jumps")) {
1354           int_types |= kCtxJumps;
1355         } else if (strequal(s, "bufs")) {
1356           int_types |= kCtxBufs;
1357         } else if (strequal(s, "gvars")) {
1358           int_types |= kCtxGVars;
1359         } else if (strequal(s, "sfuncs")) {
1360           int_types |= kCtxSFuncs;
1361         } else if (strequal(s, "funcs")) {
1362           int_types |= kCtxFuncs;
1363         } else {
1364           api_set_error(err, kErrorTypeValidation, "unexpected type: %s", s);
1365           return (Dictionary)ARRAY_DICT_INIT;
1366         }
1367       }
1368     }
1369   }
1370 
1371   Context ctx = CONTEXT_INIT;
1372   ctx_save(&ctx, int_types);
1373   Dictionary dict = ctx_to_dict(&ctx);
1374   ctx_free(&ctx);
1375   return dict;
1376 }
1377 
1378 /// Sets the current editor state from the given |context| map.
1379 ///
1380 /// @param  dict  |Context| map.
nvim_load_context(Dictionary dict)1381 Object nvim_load_context(Dictionary dict)
1382   FUNC_API_SINCE(6)
1383 {
1384   Context ctx = CONTEXT_INIT;
1385 
1386   int save_did_emsg = did_emsg;
1387   did_emsg = false;
1388 
1389   ctx_from_dict(dict, &ctx);
1390   if (!did_emsg) {
1391     ctx_restore(&ctx, kCtxAll);
1392   }
1393 
1394   ctx_free(&ctx);
1395 
1396   did_emsg = save_did_emsg;
1397   return (Object)OBJECT_INIT;
1398 }
1399 
1400 /// Gets the current mode. |mode()|
1401 /// "blocking" is true if Nvim is waiting for input.
1402 ///
1403 /// @returns Dictionary { "mode": String, "blocking": Boolean }
nvim_get_mode(void)1404 Dictionary nvim_get_mode(void)
1405   FUNC_API_SINCE(2) FUNC_API_FAST
1406 {
1407   Dictionary rv = ARRAY_DICT_INIT;
1408   char *modestr = get_mode();
1409   bool blocked = input_blocking();
1410 
1411   PUT(rv, "mode", STRING_OBJ(cstr_as_string(modestr)));
1412   PUT(rv, "blocking", BOOLEAN_OBJ(blocked));
1413 
1414   return rv;
1415 }
1416 
1417 /// Gets a list of global (non-buffer-local) |mapping| definitions.
1418 ///
1419 /// @param  mode       Mode short-name ("n", "i", "v", ...)
1420 /// @returns Array of maparg()-like dictionaries describing mappings.
1421 ///          The "buffer" key is always zero.
nvim_get_keymap(String mode)1422 ArrayOf(Dictionary) nvim_get_keymap(String mode)
1423   FUNC_API_SINCE(3)
1424 {
1425   return keymap_array(mode, NULL);
1426 }
1427 
1428 /// Sets a global |mapping| for the given mode.
1429 ///
1430 /// To set a buffer-local mapping, use |nvim_buf_set_keymap()|.
1431 ///
1432 /// Unlike |:map|, leading/trailing whitespace is accepted as part of the {lhs}
1433 /// or {rhs}. Empty {rhs} is |<Nop>|. |keycodes| are replaced as usual.
1434 ///
1435 /// Example:
1436 /// <pre>
1437 ///     call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})
1438 /// </pre>
1439 ///
1440 /// is equivalent to:
1441 /// <pre>
1442 ///     nmap <nowait> <Space><NL> <Nop>
1443 /// </pre>
1444 ///
1445 /// @param  mode  Mode short-name (map command prefix: "n", "i", "v", "x", …)
1446 ///               or "!" for |:map!|, or empty string for |:map|.
1447 /// @param  lhs   Left-hand-side |{lhs}| of the mapping.
1448 /// @param  rhs   Right-hand-side |{rhs}| of the mapping.
1449 /// @param  opts  Optional parameters map. Accepts all |:map-arguments|
1450 ///               as keys excluding |<buffer>| but including |noremap|.
1451 ///               Values are Booleans. Unknown key is an error.
1452 /// @param[out]   err   Error details, if any.
nvim_set_keymap(String mode,String lhs,String rhs,Dict (keymap)* opts,Error * err)1453 void nvim_set_keymap(String mode, String lhs, String rhs, Dict(keymap) *opts, Error *err)
1454   FUNC_API_SINCE(6)
1455 {
1456   modify_keymap(-1, false, mode, lhs, rhs, opts, err);
1457 }
1458 
1459 /// Unmaps a global |mapping| for the given mode.
1460 ///
1461 /// To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|.
1462 ///
1463 /// @see |nvim_set_keymap()|
nvim_del_keymap(String mode,String lhs,Error * err)1464 void nvim_del_keymap(String mode, String lhs, Error *err)
1465   FUNC_API_SINCE(6)
1466 {
1467   nvim_buf_del_keymap(-1, mode, lhs, err);
1468 }
1469 
1470 /// Gets a map of global (non-buffer-local) Ex commands.
1471 ///
1472 /// Currently only |user-commands| are supported, not builtin Ex commands.
1473 ///
1474 /// @param  opts  Optional parameters. Currently only supports
1475 ///               {"builtin":false}
1476 /// @param[out]  err   Error details, if any.
1477 ///
1478 /// @returns Map of maps describing commands.
nvim_get_commands(Dict (get_commands)* opts,Error * err)1479 Dictionary nvim_get_commands(Dict(get_commands) *opts, Error *err)
1480   FUNC_API_SINCE(4)
1481 {
1482   return nvim_buf_get_commands(-1, opts, err);
1483 }
1484 
1485 /// Returns a 2-tuple (Array), where item 0 is the current channel id and item
1486 /// 1 is the |api-metadata| map (Dictionary).
1487 ///
1488 /// @returns 2-tuple [{channel-id}, {api-metadata}]
nvim_get_api_info(uint64_t channel_id)1489 Array nvim_get_api_info(uint64_t channel_id)
1490   FUNC_API_SINCE(1) FUNC_API_FAST FUNC_API_REMOTE_ONLY
1491 {
1492   Array rv = ARRAY_DICT_INIT;
1493 
1494   assert(channel_id <= INT64_MAX);
1495   ADD(rv, INTEGER_OBJ((int64_t)channel_id));
1496   ADD(rv, DICTIONARY_OBJ(api_metadata()));
1497 
1498   return rv;
1499 }
1500 
1501 /// Self-identifies the client.
1502 ///
1503 /// The client/plugin/application should call this after connecting, to provide
1504 /// hints about its identity and purpose, for debugging and orchestration.
1505 ///
1506 /// Can be called more than once; the caller should merge old info if
1507 /// appropriate. Example: library first identifies the channel, then a plugin
1508 /// using that library later identifies itself.
1509 ///
1510 /// @note "Something is better than nothing". You don't need to include all the
1511 ///       fields.
1512 ///
1513 /// @param channel_id
1514 /// @param name Short name for the connected client
1515 /// @param version  Dictionary describing the version, with these
1516 ///                 (optional) keys:
1517 ///     - "major" major version (defaults to 0 if not set, for no release yet)
1518 ///     - "minor" minor version
1519 ///     - "patch" patch number
1520 ///     - "prerelease" string describing a prerelease, like "dev" or "beta1"
1521 ///     - "commit" hash or similar identifier of commit
1522 /// @param type Must be one of the following values. Client libraries should
1523 ///             default to "remote" unless overridden by the user.
1524 ///     - "remote" remote client connected to Nvim.
1525 ///     - "ui" gui frontend
1526 ///     - "embedder" application using Nvim as a component (for example,
1527 ///                  IDE/editor implementing a vim mode).
1528 ///     - "host" plugin host, typically started by nvim
1529 ///     - "plugin" single plugin, started by nvim
1530 /// @param methods Builtin methods in the client. For a host, this does not
1531 ///                include plugin methods which will be discovered later.
1532 ///                The key should be the method name, the values are dicts with
1533 ///                these (optional) keys (more keys may be added in future
1534 ///                versions of Nvim, thus unknown keys are ignored. Clients
1535 ///                must only use keys defined in this or later versions of
1536 ///                Nvim):
1537 ///     - "async"  if true, send as a notification. If false or unspecified,
1538 ///                use a blocking request
1539 ///     - "nargs" Number of arguments. Could be a single integer or an array
1540 ///                of two integers, minimum and maximum inclusive.
1541 ///
1542 /// @param attributes Arbitrary string:string map of informal client properties.
1543 ///     Suggested keys:
1544 ///     - "website": Client homepage URL (e.g. GitHub repository)
1545 ///     - "license": License description ("Apache 2", "GPLv3", "MIT", …)
1546 ///     - "logo":    URI or path to image, preferably small logo or icon.
1547 ///                  .png or .svg format is preferred.
1548 ///
1549 /// @param[out] err Error details, if any
nvim_set_client_info(uint64_t channel_id,String name,Dictionary version,String type,Dictionary methods,Dictionary attributes,Error * err)1550 void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version, String type,
1551                           Dictionary methods, Dictionary attributes, Error *err)
1552   FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY
1553 {
1554   Dictionary info = ARRAY_DICT_INIT;
1555   PUT(info, "name", copy_object(STRING_OBJ(name)));
1556 
1557   version = copy_dictionary(version);
1558   bool has_major = false;
1559   for (size_t i = 0; i < version.size; i++) {
1560     if (strequal(version.items[i].key.data, "major")) {
1561       has_major = true;
1562       break;
1563     }
1564   }
1565   if (!has_major) {
1566     PUT(version, "major", INTEGER_OBJ(0));
1567   }
1568   PUT(info, "version", DICTIONARY_OBJ(version));
1569 
1570   PUT(info, "type", copy_object(STRING_OBJ(type)));
1571   PUT(info, "methods", DICTIONARY_OBJ(copy_dictionary(methods)));
1572   PUT(info, "attributes", DICTIONARY_OBJ(copy_dictionary(attributes)));
1573 
1574   rpc_set_client_info(channel_id, info);
1575 }
1576 
1577 /// Gets information about a channel.
1578 ///
1579 /// @returns Dictionary describing a channel, with these keys:
1580 ///    - "id"       Channel id.
1581 ///    - "argv"     (optional) Job arguments list.
1582 ///    - "stream"   Stream underlying the channel.
1583 ///         - "stdio"      stdin and stdout of this Nvim instance
1584 ///         - "stderr"     stderr of this Nvim instance
1585 ///         - "socket"     TCP/IP socket or named pipe
1586 ///         - "job"        Job with communication over its stdio.
1587 ///    -  "mode"    How data received on the channel is interpreted.
1588 ///         - "bytes"      Send and receive raw bytes.
1589 ///         - "terminal"   |terminal| instance interprets ASCII sequences.
1590 ///         - "rpc"        |RPC| communication on the channel is active.
1591 ///    -  "pty"     (optional) Name of pseudoterminal. On a POSIX system this
1592 ///                 is a device path like "/dev/pts/1". If the name is unknown,
1593 ///                 the key will still be present if a pty is used (e.g. for
1594 ///                 winpty on Windows).
1595 ///    -  "buffer"  (optional) Buffer with connected |terminal| instance.
1596 ///    -  "client"  (optional) Info about the peer (client on the other end of
1597 ///                 the RPC channel), if provided by it via
1598 ///                 |nvim_set_client_info()|.
1599 ///
nvim_get_chan_info(Integer chan,Error * err)1600 Dictionary nvim_get_chan_info(Integer chan, Error *err)
1601   FUNC_API_SINCE(4)
1602 {
1603   if (chan < 0) {
1604     return (Dictionary)ARRAY_DICT_INIT;
1605   }
1606   return channel_info((uint64_t)chan);
1607 }
1608 
1609 /// Get information about all open channels.
1610 ///
1611 /// @returns Array of Dictionaries, each describing a channel with
1612 ///          the format specified at |nvim_get_chan_info()|.
nvim_list_chans(void)1613 Array nvim_list_chans(void)
1614   FUNC_API_SINCE(4)
1615 {
1616   return channel_all_info();
1617 }
1618 
1619 /// Calls many API methods atomically.
1620 ///
1621 /// This has two main usages:
1622 /// 1. To perform several requests from an async context atomically, i.e.
1623 ///    without interleaving redraws, RPC requests from other clients, or user
1624 ///    interactions (however API methods may trigger autocommands or event
1625 ///    processing which have such side-effects, e.g. |:sleep| may wake timers).
1626 /// 2. To minimize RPC overhead (roundtrips) of a sequence of many requests.
1627 ///
1628 /// @param channel_id
1629 /// @param calls an array of calls, where each call is described by an array
1630 ///              with two elements: the request name, and an array of arguments.
1631 /// @param[out] err Validation error details (malformed `calls` parameter),
1632 ///             if any. Errors from batched calls are given in the return value.
1633 ///
1634 /// @return Array of two elements. The first is an array of return
1635 /// values. The second is NIL if all calls succeeded. If a call resulted in
1636 /// an error, it is a three-element array with the zero-based index of the call
1637 /// which resulted in an error, the error type and the error message. If an
1638 /// error occurred, the values from all preceding calls will still be returned.
nvim_call_atomic(uint64_t channel_id,Array calls,Error * err)1639 Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err)
1640   FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
1641 {
1642   Array rv = ARRAY_DICT_INIT;
1643   Array results = ARRAY_DICT_INIT;
1644   Error nested_error = ERROR_INIT;
1645 
1646   size_t i;  // also used for freeing the variables
1647   for (i = 0; i < calls.size; i++) {
1648     if (calls.items[i].type != kObjectTypeArray) {
1649       api_set_error(err,
1650                     kErrorTypeValidation,
1651                     "Items in calls array must be arrays");
1652       goto validation_error;
1653     }
1654     Array call = calls.items[i].data.array;
1655     if (call.size != 2) {
1656       api_set_error(err,
1657                     kErrorTypeValidation,
1658                     "Items in calls array must be arrays of size 2");
1659       goto validation_error;
1660     }
1661 
1662     if (call.items[0].type != kObjectTypeString) {
1663       api_set_error(err,
1664                     kErrorTypeValidation,
1665                     "Name must be String");
1666       goto validation_error;
1667     }
1668     String name = call.items[0].data.string;
1669 
1670     if (call.items[1].type != kObjectTypeArray) {
1671       api_set_error(err,
1672                     kErrorTypeValidation,
1673                     "Args must be Array");
1674       goto validation_error;
1675     }
1676     Array args = call.items[1].data.array;
1677 
1678     MsgpackRpcRequestHandler handler =
1679       msgpack_rpc_get_handler_for(name.data,
1680                                   name.size,
1681                                   &nested_error);
1682 
1683     if (ERROR_SET(&nested_error)) {
1684       break;
1685     }
1686     Object result = handler.fn(channel_id, args, &nested_error);
1687     if (ERROR_SET(&nested_error)) {
1688       // error handled after loop
1689       break;
1690     }
1691 
1692     ADD(results, result);
1693   }
1694 
1695   ADD(rv, ARRAY_OBJ(results));
1696   if (ERROR_SET(&nested_error)) {
1697     Array errval = ARRAY_DICT_INIT;
1698     ADD(errval, INTEGER_OBJ((Integer)i));
1699     ADD(errval, INTEGER_OBJ(nested_error.type));
1700     ADD(errval, STRING_OBJ(cstr_to_string(nested_error.msg)));
1701     ADD(rv, ARRAY_OBJ(errval));
1702   } else {
1703     ADD(rv, NIL);
1704   }
1705   goto theend;
1706 
1707 validation_error:
1708   api_free_array(results);
1709 theend:
1710   api_clear_error(&nested_error);
1711   return rv;
1712 }
1713 
1714 /// Writes a message to vim output or error buffer. The string is split
1715 /// and flushed after each newline. Incomplete lines are kept for writing
1716 /// later.
1717 ///
1718 /// @param message  Message to write
1719 /// @param to_err   true: message is an error (uses `emsg` instead of `msg`)
write_msg(String message,bool to_err)1720 static void write_msg(String message, bool to_err)
1721 {
1722   static size_t out_pos = 0, err_pos = 0;
1723   static char out_line_buf[LINE_BUFFER_SIZE], err_line_buf[LINE_BUFFER_SIZE];
1724 
1725 #define PUSH_CHAR(i, pos, line_buf, msg) \
1726   if (message.data[i] == NL || pos == LINE_BUFFER_SIZE - 1) { \
1727     line_buf[pos] = NUL; \
1728     msg(line_buf); \
1729     pos = 0; \
1730     continue; \
1731   } \
1732   line_buf[pos++] = message.data[i];
1733 
1734   ++no_wait_return;
1735   for (uint32_t i = 0; i < message.size; i++) {
1736     if (to_err) {
1737       PUSH_CHAR(i, err_pos, err_line_buf, emsg);
1738     } else {
1739       PUSH_CHAR(i, out_pos, out_line_buf, msg);
1740     }
1741   }
1742   --no_wait_return;
1743   msg_end();
1744 }
1745 
1746 // Functions used for testing purposes
1747 
1748 /// Returns object given as argument.
1749 ///
1750 /// This API function is used for testing. One should not rely on its presence
1751 /// in plugins.
1752 ///
1753 /// @param[in]  obj  Object to return.
1754 ///
1755 /// @return its argument.
nvim__id(Object obj)1756 Object nvim__id(Object obj)
1757 {
1758   return copy_object(obj);
1759 }
1760 
1761 /// Returns array given as argument.
1762 ///
1763 /// This API function is used for testing. One should not rely on its presence
1764 /// in plugins.
1765 ///
1766 /// @param[in]  arr  Array to return.
1767 ///
1768 /// @return its argument.
nvim__id_array(Array arr)1769 Array nvim__id_array(Array arr)
1770 {
1771   return copy_object(ARRAY_OBJ(arr)).data.array;
1772 }
1773 
1774 /// Returns dictionary given as argument.
1775 ///
1776 /// This API function is used for testing. One should not rely on its presence
1777 /// in plugins.
1778 ///
1779 /// @param[in]  dct  Dictionary to return.
1780 ///
1781 /// @return its argument.
nvim__id_dictionary(Dictionary dct)1782 Dictionary nvim__id_dictionary(Dictionary dct)
1783 {
1784   return copy_object(DICTIONARY_OBJ(dct)).data.dictionary;
1785 }
1786 
1787 /// Returns floating-point value given as argument.
1788 ///
1789 /// This API function is used for testing. One should not rely on its presence
1790 /// in plugins.
1791 ///
1792 /// @param[in]  flt  Value to return.
1793 ///
1794 /// @return its argument.
nvim__id_float(Float flt)1795 Float nvim__id_float(Float flt)
1796 {
1797   return flt;
1798 }
1799 
1800 /// Gets internal stats.
1801 ///
1802 /// @return Map of various internal stats.
nvim__stats(void)1803 Dictionary nvim__stats(void)
1804 {
1805   Dictionary rv = ARRAY_DICT_INIT;
1806   PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync));
1807   PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw));
1808   PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_refcount));
1809   return rv;
1810 }
1811 
1812 /// Gets a list of dictionaries representing attached UIs.
1813 ///
1814 /// @return Array of UI dictionaries, each with these keys:
1815 ///   - "height"  Requested height of the UI
1816 ///   - "width"   Requested width of the UI
1817 ///   - "rgb"     true if the UI uses RGB colors (false implies |cterm-colors|)
1818 ///   - "ext_..." Requested UI extensions, see |ui-option|
1819 ///   - "chan"    Channel id of remote UI (not present for TUI)
nvim_list_uis(void)1820 Array nvim_list_uis(void)
1821   FUNC_API_SINCE(4)
1822 {
1823   return ui_array();
1824 }
1825 
1826 /// Gets the immediate children of process `pid`.
1827 ///
1828 /// @return Array of child process ids, empty if process not found.
nvim_get_proc_children(Integer pid,Error * err)1829 Array nvim_get_proc_children(Integer pid, Error *err)
1830   FUNC_API_SINCE(4)
1831 {
1832   Array rvobj = ARRAY_DICT_INIT;
1833   int *proc_list = NULL;
1834 
1835   if (pid <= 0 || pid > INT_MAX) {
1836     api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid);
1837     goto end;
1838   }
1839 
1840   size_t proc_count;
1841   int rv = os_proc_children((int)pid, &proc_list, &proc_count);
1842   if (rv != 0) {
1843     // syscall failed (possibly because of kernel options), try shelling out.
1844     DLOG("fallback to vim._os_proc_children()");
1845     Array a = ARRAY_DICT_INIT;
1846     ADD(a, INTEGER_OBJ(pid));
1847     String s = cstr_to_string("return vim._os_proc_children(select(1, ...))");
1848     Object o = nlua_exec(s, a, err);
1849     api_free_string(s);
1850     api_free_array(a);
1851     if (o.type == kObjectTypeArray) {
1852       rvobj = o.data.array;
1853     } else if (!ERROR_SET(err)) {
1854       api_set_error(err, kErrorTypeException,
1855                     "Failed to get process children. pid=%" PRId64 " error=%d",
1856                     pid, rv);
1857     }
1858     goto end;
1859   }
1860 
1861   for (size_t i = 0; i < proc_count; i++) {
1862     ADD(rvobj, INTEGER_OBJ(proc_list[i]));
1863   }
1864 
1865 end:
1866   xfree(proc_list);
1867   return rvobj;
1868 }
1869 
1870 /// Gets info describing process `pid`.
1871 ///
1872 /// @return Map of process properties, or NIL if process not found.
nvim_get_proc(Integer pid,Error * err)1873 Object nvim_get_proc(Integer pid, Error *err)
1874   FUNC_API_SINCE(4)
1875 {
1876   Object rvobj = OBJECT_INIT;
1877   rvobj.data.dictionary = (Dictionary)ARRAY_DICT_INIT;
1878   rvobj.type = kObjectTypeDictionary;
1879 
1880   if (pid <= 0 || pid > INT_MAX) {
1881     api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid);
1882     return NIL;
1883   }
1884 #ifdef WIN32
1885   rvobj.data.dictionary = os_proc_info((int)pid);
1886   if (rvobj.data.dictionary.size == 0) {  // Process not found.
1887     return NIL;
1888   }
1889 #else
1890   // Cross-platform process info APIs are miserable, so use `ps` instead.
1891   Array a = ARRAY_DICT_INIT;
1892   ADD(a, INTEGER_OBJ(pid));
1893   String s = cstr_to_string("return vim._os_proc_info(select(1, ...))");
1894   Object o = nlua_exec(s, a, err);
1895   api_free_string(s);
1896   api_free_array(a);
1897   if (o.type == kObjectTypeArray && o.data.array.size == 0) {
1898     return NIL;  // Process not found.
1899   } else if (o.type == kObjectTypeDictionary) {
1900     rvobj.data.dictionary = o.data.dictionary;
1901   } else if (!ERROR_SET(err)) {
1902     api_set_error(err, kErrorTypeException,
1903                   "Failed to get process info. pid=%" PRId64, pid);
1904   }
1905 #endif
1906   return rvobj;
1907 }
1908 
1909 /// Selects an item in the completion popupmenu.
1910 ///
1911 /// If |ins-completion| is not active this API call is silently ignored.
1912 /// Useful for an external UI using |ui-popupmenu| to control the popupmenu
1913 /// with the mouse. Can also be used in a mapping; use <cmd> |:map-cmd| to
1914 /// ensure the mapping doesn't end completion mode.
1915 ///
1916 /// @param item   Index (zero-based) of the item to select. Value of -1 selects
1917 ///               nothing and restores the original text.
1918 /// @param insert Whether the selection should be inserted in the buffer.
1919 /// @param finish Finish the completion and dismiss the popupmenu. Implies
1920 ///               `insert`.
1921 /// @param  opts  Optional parameters. Reserved for future use.
1922 /// @param[out] err Error details, if any
nvim_select_popupmenu_item(Integer item,Boolean insert,Boolean finish,Dictionary opts,Error * err)1923 void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Dictionary opts,
1924                                 Error *err)
1925   FUNC_API_SINCE(6)
1926 {
1927   if (opts.size > 0) {
1928     api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
1929     return;
1930   }
1931 
1932   if (finish) {
1933     insert = true;
1934   }
1935 
1936   pum_ext_select_item((int)item, insert, finish);
1937 }
1938 
1939 /// NB: if your UI doesn't use hlstate, this will not return hlstate first time
nvim__inspect_cell(Integer grid,Integer row,Integer col,Error * err)1940 Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err)
1941 {
1942   Array ret = ARRAY_DICT_INIT;
1943 
1944   // TODO(bfredl): if grid == 0 we should read from the compositor's buffer.
1945   // The only problem is that it does not yet exist.
1946   ScreenGrid *g = &default_grid;
1947   if (grid == pum_grid.handle) {
1948     g = &pum_grid;
1949   } else if (grid > 1) {
1950     win_T *wp = get_win_by_grid_handle((handle_T)grid);
1951     if (wp != NULL && wp->w_grid_alloc.chars != NULL) {
1952       g = &wp->w_grid_alloc;
1953     } else {
1954       api_set_error(err, kErrorTypeValidation,
1955                     "No grid with the given handle");
1956       return ret;
1957     }
1958   }
1959 
1960   if (row < 0 || row >= g->Rows
1961       || col < 0 || col >= g->Columns) {
1962     return ret;
1963   }
1964   size_t off = g->line_offset[(size_t)row] + (size_t)col;
1965   ADD(ret, STRING_OBJ(cstr_to_string((char *)g->chars[off])));
1966   int attr = g->attrs[off];
1967   ADD(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, err)));
1968   // will not work first time
1969   if (!highlight_use_hlstate()) {
1970     ADD(ret, ARRAY_OBJ(hl_inspect(attr)));
1971   }
1972   return ret;
1973 }
1974 
nvim__screenshot(String path)1975 void nvim__screenshot(String path)
1976   FUNC_API_FAST
1977 {
1978   ui_call_screenshot(path);
1979 }
1980 
1981 
1982 /// Deletes a uppercase/file named mark. See |mark-motions|.
1983 ///
1984 /// @note fails with error if a lowercase or buffer local named mark is used.
1985 /// @param name       Mark name
1986 /// @return true if the mark was deleted, else false.
1987 /// @see |nvim_buf_del_mark()|
1988 /// @see |nvim_get_mark()|
nvim_del_mark(String name,Error * err)1989 Boolean nvim_del_mark(String name, Error *err)
1990   FUNC_API_SINCE(8)
1991 {
1992   bool res = false;
1993   if (name.size != 1) {
1994     api_set_error(err, kErrorTypeValidation,
1995                   "Mark name must be a single character");
1996     return res;
1997   }
1998   // Only allow file/uppercase marks
1999   // TODO(muniter): Refactor this ASCII_ISUPPER macro to a proper function
2000   if (ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)) {
2001     res = set_mark(NULL, name, 0, 0, err);
2002   } else {
2003     api_set_error(err, kErrorTypeValidation,
2004                   "Only file/uppercase marks allowed, invalid mark name: '%c'",
2005                   *name.data);
2006   }
2007   return res;
2008 }
2009 
2010 /// Return a tuple (row, col, buffer, buffername) representing the position of
2011 /// the uppercase/file named mark. See |mark-motions|.
2012 ///
2013 /// Marks are (1,0)-indexed. |api-indexing|
2014 ///
2015 /// @note fails with error if a lowercase or buffer local named mark is used.
2016 /// @param name       Mark name
2017 /// @param opts       Optional parameters. Reserved for future use.
2018 /// @return 4-tuple (row, col, buffer, buffername), (0, 0, 0, '') if the mark is
2019 /// not set.
2020 /// @see |nvim_buf_set_mark()|
2021 /// @see |nvim_del_mark()|
nvim_get_mark(String name,Dictionary opts,Error * err)2022 Array nvim_get_mark(String name, Dictionary opts, Error *err)
2023   FUNC_API_SINCE(8)
2024 {
2025   Array rv = ARRAY_DICT_INIT;
2026 
2027   if (name.size != 1) {
2028     api_set_error(err, kErrorTypeValidation,
2029                   "Mark name must be a single character");
2030     return rv;
2031   } else if (!(ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data))) {
2032     api_set_error(err, kErrorTypeValidation,
2033                   "Only file/uppercase marks allowed, invalid mark name: '%c'",
2034                   *name.data);
2035     return rv;
2036   }
2037 
2038   xfmark_T mark = get_global_mark(*name.data);
2039   pos_T pos = mark.fmark.mark;
2040   bool allocated = false;
2041   int bufnr;
2042   char *filename;
2043 
2044   // Marks are from an open buffer it fnum is non zero
2045   if (mark.fmark.fnum != 0) {
2046     bufnr = mark.fmark.fnum;
2047     filename = (char *)buflist_nr2name(bufnr, true, true);
2048     allocated = true;
2049     // Marks comes from shada
2050   } else {
2051     filename = (char *)mark.fname;
2052     bufnr = 0;
2053   }
2054 
2055   bool exists = filename != NULL;
2056   Integer row;
2057   Integer col;
2058 
2059   if (!exists || pos.lnum <= 0) {
2060     if (allocated) {
2061       xfree(filename);
2062       allocated = false;
2063     }
2064     filename = "";
2065     bufnr = 0;
2066     row = 0;
2067     col = 0;
2068   } else {
2069     row = pos.lnum;
2070     col = pos.col;
2071   }
2072 
2073   ADD(rv, INTEGER_OBJ(row));
2074   ADD(rv, INTEGER_OBJ(col));
2075   ADD(rv, INTEGER_OBJ(bufnr));
2076   ADD(rv, STRING_OBJ(cstr_to_string(filename)));
2077 
2078   if (allocated) {
2079     xfree(filename);
2080   }
2081 
2082   return rv;
2083 }
2084 
2085 /// Evaluates statusline string.
2086 ///
2087 /// @param str Statusline string (see 'statusline').
2088 /// @param opts Optional parameters.
2089 ///           - winid: (number) |window-ID| of the window to use as context for statusline.
2090 ///           - maxwidth: (number) Maximum width of statusline.
2091 ///           - fillchar: (string) Character to fill blank spaces in the statusline (see
2092 ///                                'fillchars').
2093 ///           - highlights: (boolean) Return highlight information.
2094 ///           - use_tabline: (boolean) Evaluate tabline instead of statusline. When |TRUE|, {winid}
2095 ///                                    is ignored.
2096 ///
2097 /// @param[out] err Error details, if any.
2098 /// @return Dictionary containing statusline information, with these keys:
2099 ///       - str: (string) Characters that will be displayed on the statusline.
2100 ///       - width: (number) Display width of the statusline.
2101 ///       - highlights: Array containing highlight information of the statusline. Only included when
2102 ///                     the "highlights" key in {opts} is |TRUE|. Each element of the array is a
2103 ///                     |Dictionary| with these keys:
2104 ///           - start: (number) Byte index (0-based) of first character that uses the highlight.
2105 ///           - group: (string) Name of highlight group.
nvim_eval_statusline(String str,Dict (eval_statusline)* opts,Error * err)2106 Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *err)
2107   FUNC_API_SINCE(8) FUNC_API_FAST
2108 {
2109   Dictionary result = ARRAY_DICT_INIT;
2110 
2111   int maxwidth;
2112   char fillchar = 0;
2113   Window window = 0;
2114   bool use_tabline = false;
2115   bool highlights = false;
2116 
2117   if (HAS_KEY(opts->winid)) {
2118     if (opts->winid.type != kObjectTypeInteger) {
2119       api_set_error(err, kErrorTypeValidation, "winid must be an integer");
2120       return result;
2121     }
2122 
2123     window = (Window)opts->winid.data.integer;
2124   }
2125 
2126   if (HAS_KEY(opts->fillchar)) {
2127     if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size > 1) {
2128       api_set_error(err, kErrorTypeValidation, "fillchar must be an ASCII character");
2129       return result;
2130     }
2131 
2132     fillchar = opts->fillchar.data.string.data[0];
2133   }
2134 
2135   if (HAS_KEY(opts->highlights)) {
2136     highlights = api_object_to_bool(opts->highlights, "highlights", false, err);
2137 
2138     if (ERROR_SET(err)) {
2139       return result;
2140     }
2141   }
2142 
2143   if (HAS_KEY(opts->use_tabline)) {
2144     use_tabline = api_object_to_bool(opts->use_tabline, "use_tabline", false, err);
2145 
2146     if (ERROR_SET(err)) {
2147       return result;
2148     }
2149   }
2150 
2151   win_T *wp, *ewp;
2152 
2153   if (use_tabline) {
2154     wp = NULL;
2155     ewp = curwin;
2156     fillchar = ' ';
2157   } else {
2158     wp = find_window_by_handle(window, err);
2159     ewp = wp;
2160 
2161     if (fillchar == 0) {
2162       int attr;
2163       fillchar = (char)fillchar_status(&attr, wp);
2164     }
2165   }
2166 
2167   if (HAS_KEY(opts->maxwidth)) {
2168     if (opts->maxwidth.type != kObjectTypeInteger) {
2169       api_set_error(err, kErrorTypeValidation, "maxwidth must be an integer");
2170       return result;
2171     }
2172 
2173     maxwidth = (int)opts->maxwidth.data.integer;
2174   } else {
2175     maxwidth = use_tabline ? Columns : wp->w_width;
2176   }
2177 
2178   char buf[MAXPATHL];
2179   stl_hlrec_t *hltab;
2180   stl_hlrec_t **hltab_ptr = highlights ? &hltab : NULL;
2181 
2182   // Temporarily reset 'cursorbind' to prevent side effects from moving the cursor away and back.
2183   int p_crb_save = ewp->w_p_crb;
2184   ewp->w_p_crb = false;
2185 
2186   int width = build_stl_str_hl(ewp,
2187                                (char_u *)buf,
2188                                sizeof(buf),
2189                                (char_u *)str.data,
2190                                false,
2191                                (char_u)fillchar,
2192                                maxwidth,
2193                                hltab_ptr,
2194                                NULL);
2195 
2196   PUT(result, "width", INTEGER_OBJ(width));
2197 
2198   // Restore original value of 'cursorbind'
2199   ewp->w_p_crb = p_crb_save;
2200 
2201   if (highlights) {
2202     Array hl_values = ARRAY_DICT_INIT;
2203     const char *grpname;
2204     char user_group[6];
2205 
2206     // If first character doesn't have a defined highlight,
2207     // add the default highlight at the beginning of the highlight list
2208     if (hltab->start == NULL || ((char *)hltab->start - buf) != 0) {
2209       Dictionary hl_info = ARRAY_DICT_INIT;
2210       grpname = get_default_stl_hl(wp);
2211 
2212       PUT(hl_info, "start", INTEGER_OBJ(0));
2213       PUT(hl_info, "group", CSTR_TO_OBJ(grpname));
2214 
2215       ADD(hl_values, DICTIONARY_OBJ(hl_info));
2216     }
2217 
2218     for (stl_hlrec_t *sp = hltab; sp->start != NULL; sp++) {
2219       Dictionary hl_info = ARRAY_DICT_INIT;
2220 
2221       PUT(hl_info, "start", INTEGER_OBJ((char *)sp->start - buf));
2222 
2223       if (sp->userhl == 0) {
2224         grpname = get_default_stl_hl(wp);
2225       } else if (sp->userhl < 0) {
2226         grpname = (char *)syn_id2name(-sp->userhl);
2227       } else {
2228         snprintf(user_group, sizeof(user_group), "User%d", sp->userhl);
2229         grpname = user_group;
2230       }
2231 
2232       PUT(hl_info, "group", CSTR_TO_OBJ(grpname));
2233 
2234       ADD(hl_values, DICTIONARY_OBJ(hl_info));
2235     }
2236 
2237     PUT(result, "highlights", ARRAY_OBJ(hl_values));
2238   }
2239 
2240   PUT(result, "str", CSTR_TO_OBJ((char *)buf));
2241 
2242   return result;
2243 }
2244