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 <stdbool.h>
7 #include <stddef.h>
8 #include <stdlib.h>
9 #include <string.h>
10 
11 #include "nvim/api/private/converter.h"
12 #include "nvim/api/private/defs.h"
13 #include "nvim/api/private/helpers.h"
14 #include "nvim/api/vim.h"
15 #include "nvim/ascii.h"
16 #include "nvim/assert.h"
17 #include "nvim/buffer.h"
18 #include "nvim/charset.h"
19 #include "nvim/decoration.h"
20 #include "nvim/eval.h"
21 #include "nvim/eval/typval.h"
22 #include "nvim/extmark.h"
23 #include "nvim/fileio.h"
24 #include "nvim/getchar.h"
25 #include "nvim/lib/kvec.h"
26 #include "nvim/lua/executor.h"
27 #include "nvim/map.h"
28 #include "nvim/map_defs.h"
29 #include "nvim/mark.h"
30 #include "nvim/memline.h"
31 #include "nvim/memory.h"
32 #include "nvim/msgpack_rpc/helpers.h"
33 #include "nvim/option.h"
34 #include "nvim/option_defs.h"
35 #include "nvim/syntax.h"
36 #include "nvim/ui.h"
37 #include "nvim/version.h"
38 #include "nvim/vim.h"
39 #include "nvim/window.h"
40 
41 #ifdef INCLUDE_GENERATED_DECLARATIONS
42 # include "api/private/funcs_metadata.generated.h"
43 # include "api/private/helpers.c.generated.h"
44 # include "api/private/ui_events_metadata.generated.h"
45 #endif
46 
47 /// Start block that may cause VimL exceptions while evaluating another code
48 ///
49 /// Used when caller is supposed to be operating when other VimL code is being
50 /// processed and that “other VimL code” must not be affected.
51 ///
52 /// @param[out]  tstate  Location where try state should be saved.
try_enter(TryState * const tstate)53 void try_enter(TryState *const tstate)
54 {
55   // TODO(ZyX-I): Check whether try_enter()/try_leave() may use
56   //              enter_cleanup()/leave_cleanup(). Or
57   //              save_dbg_stuff()/restore_dbg_stuff().
58   *tstate = (TryState) {
59     .current_exception = current_exception,
60     .msg_list = (const struct msglist *const *)msg_list,
61     .private_msg_list = NULL,
62     .trylevel = trylevel,
63     .got_int = got_int,
64     .need_rethrow = need_rethrow,
65     .did_emsg = did_emsg,
66   };
67   msg_list = &tstate->private_msg_list;
68   current_exception = NULL;
69   trylevel = 1;
70   got_int = false;
71   need_rethrow = false;
72   did_emsg = false;
73 }
74 
75 /// End try block, set the error message if any and restore previous state
76 ///
77 /// @warning Return is consistent with most functions (false on error), not with
78 ///          try_end (true on error).
79 ///
80 /// @param[in]  tstate  Previous state to restore.
81 /// @param[out]  err  Location where error should be saved.
82 ///
83 /// @return false if error occurred, true otherwise.
try_leave(const TryState * const tstate,Error * const err)84 bool try_leave(const TryState *const tstate, Error *const err)
85   FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
86 {
87   const bool ret = !try_end(err);
88   assert(trylevel == 0);
89   assert(!need_rethrow);
90   assert(!got_int);
91   assert(!did_emsg);
92   assert(msg_list == &tstate->private_msg_list);
93   assert(*msg_list == NULL);
94   assert(current_exception == NULL);
95   msg_list = (struct msglist **)tstate->msg_list;
96   current_exception = tstate->current_exception;
97   trylevel = tstate->trylevel;
98   got_int = tstate->got_int;
99   need_rethrow = tstate->need_rethrow;
100   did_emsg = tstate->did_emsg;
101   return ret;
102 }
103 
104 /// Start block that may cause vimscript exceptions
105 ///
106 /// Each try_start() call should be mirrored by try_end() call.
107 ///
108 /// To be used as a replacement of `:try … catch … endtry` in C code, in cases
109 /// when error flag could not already be set. If there may be pending error
110 /// state at the time try_start() is executed which needs to be preserved,
111 /// try_enter()/try_leave() pair should be used instead.
try_start(void)112 void try_start(void)
113 {
114   ++trylevel;
115 }
116 
117 /// End try block, set the error message if any and return true if an error
118 /// occurred.
119 ///
120 /// @param err Pointer to the stack-allocated error object
121 /// @return true if an error occurred
try_end(Error * err)122 bool try_end(Error *err)
123 {
124   // Note: all globals manipulated here should be saved/restored in
125   // try_enter/try_leave.
126   trylevel--;
127 
128   // Set by emsg(), affects aborting().  See also enter_cleanup().
129   did_emsg = false;
130   force_abort = false;
131 
132   if (got_int) {
133     if (current_exception) {
134       // If we got an interrupt, discard the current exception
135       discard_current_exception();
136     }
137 
138     api_set_error(err, kErrorTypeException, "Keyboard interrupt");
139     got_int = false;
140   } else if (msg_list != NULL && *msg_list != NULL) {
141     int should_free;
142     char *msg = (char *)get_exception_string(*msg_list,
143                                              ET_ERROR,
144                                              NULL,
145                                              &should_free);
146     api_set_error(err, kErrorTypeException, "%s", msg);
147     free_global_msglist();
148 
149     if (should_free) {
150       xfree(msg);
151     }
152   } else if (current_exception) {
153     api_set_error(err, kErrorTypeException, "%s", current_exception->value);
154     discard_current_exception();
155   }
156 
157   return ERROR_SET(err);
158 }
159 
160 /// Recursively expands a vimscript value in a dict
161 ///
162 /// @param dict The vimscript dict
163 /// @param key The key
164 /// @param[out] err Details of an error that may have occurred
dict_get_value(dict_T * dict,String key,Error * err)165 Object dict_get_value(dict_T *dict, String key, Error *err)
166 {
167   dictitem_T *const di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size);
168 
169   if (di == NULL) {
170     api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data);
171     return (Object)OBJECT_INIT;
172   }
173 
174   return vim_to_object(&di->di_tv);
175 }
176 
dict_check_writable(dict_T * dict,String key,bool del,Error * err)177 dictitem_T *dict_check_writable(dict_T *dict, String key, bool del, Error *err)
178 {
179   dictitem_T *di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size);
180 
181   if (di != NULL) {
182     if (di->di_flags & DI_FLAGS_RO) {
183       api_set_error(err, kErrorTypeException, "Key is read-only: %s", key.data);
184     } else if (di->di_flags & DI_FLAGS_LOCK) {
185       api_set_error(err, kErrorTypeException, "Key is locked: %s", key.data);
186     } else if (del && (di->di_flags & DI_FLAGS_FIX)) {
187       api_set_error(err, kErrorTypeException, "Key is fixed: %s", key.data);
188     }
189   } else if (dict->dv_lock) {
190     api_set_error(err, kErrorTypeException, "Dictionary is locked");
191   } else if (key.size == 0) {
192     api_set_error(err, kErrorTypeValidation, "Key name is empty");
193   } else if (key.size > INT_MAX) {
194     api_set_error(err, kErrorTypeValidation, "Key name is too long");
195   }
196 
197   return di;
198 }
199 
200 /// Set a value in a scope dict. Objects are recursively expanded into their
201 /// vimscript equivalents.
202 ///
203 /// @param dict The vimscript dict
204 /// @param key The key
205 /// @param value The new value
206 /// @param del Delete key in place of setting it. Argument `value` is ignored in
207 ///            this case.
208 /// @param retval If true the old value will be converted and returned.
209 /// @param[out] err Details of an error that may have occurred
210 /// @return The old value if `retval` is true and the key was present, else NIL
dict_set_var(dict_T * dict,String key,Object value,bool del,bool retval,Error * err)211 Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retval, Error *err)
212 {
213   Object rv = OBJECT_INIT;
214   dictitem_T *di = dict_check_writable(dict, key, del, err);
215 
216   if (ERROR_SET(err)) {
217     return rv;
218   }
219 
220   if (del) {
221     // Delete the key
222     if (di == NULL) {
223       // Doesn't exist, fail
224       api_set_error(err, kErrorTypeValidation, "Key not found: %s",
225                     key.data);
226     } else {
227       // Return the old value
228       if (retval) {
229         rv = vim_to_object(&di->di_tv);
230       }
231       // Delete the entry
232       tv_dict_item_remove(dict, di);
233     }
234   } else {
235     // Update the key
236     typval_T tv;
237 
238     // Convert the object to a vimscript type in the temporary variable
239     if (!object_to_vim(value, &tv, err)) {
240       return rv;
241     }
242 
243     if (di == NULL) {
244       // Need to create an entry
245       di = tv_dict_item_alloc_len(key.data, key.size);
246       tv_dict_add(dict, di);
247     } else {
248       // Return the old value
249       if (retval) {
250         rv = vim_to_object(&di->di_tv);
251       }
252       tv_clear(&di->di_tv);
253     }
254 
255     // Update the value
256     tv_copy(&tv, &di->di_tv);
257     // Clear the temporary variable
258     tv_clear(&tv);
259   }
260 
261   return rv;
262 }
263 
264 /// Gets the value of a global or local(buffer, window) option.
265 ///
266 /// @param from If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
267 ///        to the window or buffer.
268 /// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
269 /// @param name The option name
270 /// @param[out] err Details of an error that may have occurred
271 /// @return the option value
get_option_from(void * from,int type,String name,Error * err)272 Object get_option_from(void *from, int type, String name, Error *err)
273 {
274   Object rv = OBJECT_INIT;
275 
276   if (name.size == 0) {
277     api_set_error(err, kErrorTypeValidation, "Empty option name");
278     return rv;
279   }
280 
281   // Return values
282   int64_t numval;
283   char *stringval = NULL;
284   int flags = get_option_value_strict(name.data, &numval, &stringval,
285                                       type, from);
286 
287   if (!flags) {
288     api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'",
289                   name.data);
290     return rv;
291   }
292 
293   if (flags & SOPT_BOOL) {
294     rv.type = kObjectTypeBoolean;
295     rv.data.boolean = numval ? true : false;
296   } else if (flags & SOPT_NUM) {
297     rv.type = kObjectTypeInteger;
298     rv.data.integer = numval;
299   } else if (flags & SOPT_STRING) {
300     if (stringval) {
301       rv.type = kObjectTypeString;
302       rv.data.string.data = stringval;
303       rv.data.string.size = strlen(stringval);
304     } else {
305       api_set_error(err, kErrorTypeException,
306                     "Failed to get value for option '%s'",
307                     name.data);
308     }
309   } else {
310     api_set_error(err,
311                   kErrorTypeException,
312                   "Unknown type for option '%s'",
313                   name.data);
314   }
315 
316   return rv;
317 }
318 
319 /// Sets the value of a global or local(buffer, window) option.
320 ///
321 /// @param to If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
322 ///        to the window or buffer.
323 /// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
324 /// @param name The option name
325 /// @param[out] err Details of an error that may have occurred
set_option_to(uint64_t channel_id,void * to,int type,String name,Object value,Error * err)326 void set_option_to(uint64_t channel_id, void *to, int type, String name, Object value, Error *err)
327 {
328   if (name.size == 0) {
329     api_set_error(err, kErrorTypeValidation, "Empty option name");
330     return;
331   }
332 
333   int flags = get_option_value_strict(name.data, NULL, NULL, type, to);
334 
335   if (flags == 0) {
336     api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'",
337                   name.data);
338     return;
339   }
340 
341   if (value.type == kObjectTypeNil) {
342     if (type == SREQ_GLOBAL) {
343       api_set_error(err, kErrorTypeException, "Cannot unset option '%s'",
344                     name.data);
345       return;
346     } else if (!(flags & SOPT_GLOBAL)) {
347       api_set_error(err,
348                     kErrorTypeException,
349                     "Cannot unset option '%s' "
350                     "because it doesn't have a global value",
351                     name.data);
352       return;
353     } else {
354       unset_global_local_option(name.data, to);
355       return;
356     }
357   }
358 
359   int numval = 0;
360   char *stringval = NULL;
361 
362   if (flags & SOPT_BOOL) {
363     if (value.type != kObjectTypeBoolean) {
364       api_set_error(err,
365                     kErrorTypeValidation,
366                     "Option '%s' requires a Boolean value",
367                     name.data);
368       return;
369     }
370 
371     numval = value.data.boolean;
372   } else if (flags & SOPT_NUM) {
373     if (value.type != kObjectTypeInteger) {
374       api_set_error(err, kErrorTypeValidation,
375                     "Option '%s' requires an integer value",
376                     name.data);
377       return;
378     }
379 
380     if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) {
381       api_set_error(err, kErrorTypeValidation,
382                     "Value for option '%s' is out of range",
383                     name.data);
384       return;
385     }
386 
387     numval = (int)value.data.integer;
388   } else {
389     if (value.type != kObjectTypeString) {
390       api_set_error(err, kErrorTypeValidation,
391                     "Option '%s' requires a string value",
392                     name.data);
393       return;
394     }
395 
396     stringval = value.data.string.data;
397   }
398 
399   const sctx_T save_current_sctx = current_sctx;
400   current_sctx.sc_sid =
401     channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT;
402   current_sctx.sc_lnum = 0;
403   current_channel_id = channel_id;
404 
405   const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL))
406                         ? 0 : (type == SREQ_GLOBAL)
407                               ? OPT_GLOBAL : OPT_LOCAL;
408   set_option_value_for(name.data, numval, stringval,
409                        opt_flags, type, to, err);
410 
411   current_sctx = save_current_sctx;
412 }
413 
414 
find_buffer_by_handle(Buffer buffer,Error * err)415 buf_T *find_buffer_by_handle(Buffer buffer, Error *err)
416 {
417   if (buffer == 0) {
418     return curbuf;
419   }
420 
421   buf_T *rv = handle_get_buffer(buffer);
422 
423   if (!rv) {
424     api_set_error(err, kErrorTypeValidation, "Invalid buffer id: %d", buffer);
425   }
426 
427   return rv;
428 }
429 
find_window_by_handle(Window window,Error * err)430 win_T *find_window_by_handle(Window window, Error *err)
431 {
432   if (window == 0) {
433     return curwin;
434   }
435 
436   win_T *rv = handle_get_window(window);
437 
438   if (!rv) {
439     api_set_error(err, kErrorTypeValidation, "Invalid window id: %d", window);
440   }
441 
442   return rv;
443 }
444 
find_tab_by_handle(Tabpage tabpage,Error * err)445 tabpage_T *find_tab_by_handle(Tabpage tabpage, Error *err)
446 {
447   if (tabpage == 0) {
448     return curtab;
449   }
450 
451   tabpage_T *rv = handle_get_tabpage(tabpage);
452 
453   if (!rv) {
454     api_set_error(err, kErrorTypeValidation, "Invalid tabpage id: %d", tabpage);
455   }
456 
457   return rv;
458 }
459 
460 /// Allocates a String consisting of a single char. Does not support multibyte
461 /// characters. The resulting string is also NUL-terminated, to facilitate
462 /// interoperating with code using C strings.
463 ///
464 /// @param char the char to convert
465 /// @return the resulting String, if the input char was NUL, an
466 ///         empty String is returned
cchar_to_string(char c)467 String cchar_to_string(char c)
468 {
469   char buf[] = { c, NUL };
470   return (String){
471     .data = xmemdupz(buf, 1),
472     .size = (c != NUL) ? 1 : 0
473   };
474 }
475 
476 /// Copies a C string into a String (binary safe string, characters + length).
477 /// The resulting string is also NUL-terminated, to facilitate interoperating
478 /// with code using C strings.
479 ///
480 /// @param str the C string to copy
481 /// @return the resulting String, if the input string was NULL, an
482 ///         empty String is returned
cstr_to_string(const char * str)483 String cstr_to_string(const char *str)
484 {
485   if (str == NULL) {
486     return (String)STRING_INIT;
487   }
488 
489   size_t len = strlen(str);
490   return (String){
491     .data = xmemdupz(str, len),
492     .size = len,
493   };
494 }
495 
496 /// Copies buffer to an allocated String.
497 /// The resulting string is also NUL-terminated, to facilitate interoperating
498 /// with code using C strings.
499 ///
500 /// @param buf the buffer to copy
501 /// @param size length of the buffer
502 /// @return the resulting String, if the input string was NULL, an
503 ///         empty String is returned
cbuf_to_string(const char * buf,size_t size)504 String cbuf_to_string(const char *buf, size_t size)
505   FUNC_ATTR_NONNULL_ALL
506 {
507   return (String){
508     .data = xmemdupz(buf, size),
509     .size = size
510   };
511 }
512 
cstrn_to_string(const char * str,size_t maxsize)513 String cstrn_to_string(const char *str, size_t maxsize)
514   FUNC_ATTR_NONNULL_ALL
515 {
516   return cbuf_to_string(str, strnlen(str, maxsize));
517 }
518 
519 /// Creates a String using the given C string. Unlike
520 /// cstr_to_string this function DOES NOT copy the C string.
521 ///
522 /// @param str the C string to use
523 /// @return The resulting String, or an empty String if
524 ///           str was NULL
cstr_as_string(char * str)525 String cstr_as_string(char *str) FUNC_ATTR_PURE
526 {
527   if (str == NULL) {
528     return (String)STRING_INIT;
529   }
530   return (String){ .data = str, .size = strlen(str) };
531 }
532 
533 /// Return the owned memory of a ga as a String
534 ///
535 /// Reinitializes the ga to a valid empty state.
ga_take_string(garray_T * ga)536 String ga_take_string(garray_T *ga)
537 {
538   String str = { .data = (char *)ga->ga_data, .size = (size_t)ga->ga_len };
539   ga->ga_data = NULL;
540   ga->ga_len = 0;
541   ga->ga_maxlen = 0;
542   return str;
543 }
544 
545 /// Creates "readfile()-style" ArrayOf(String) from a binary string.
546 ///
547 /// - Lines break at \n (NL/LF/line-feed).
548 /// - NUL bytes are replaced with NL.
549 /// - If the last byte is a linebreak an extra empty list item is added.
550 ///
551 /// @param input  Binary string
552 /// @param crlf  Also break lines at CR and CRLF.
553 /// @return [allocated] String array
string_to_array(const String input,bool crlf)554 Array string_to_array(const String input, bool crlf)
555 {
556   Array ret = ARRAY_DICT_INIT;
557   for (size_t i = 0; i < input.size; i++) {
558     const char *start = input.data + i;
559     const char *end = start;
560     size_t line_len = 0;
561     for (; line_len < input.size - i; line_len++) {
562       end = start + line_len;
563       if (*end == NL || (crlf && *end == CAR)) {
564         break;
565       }
566     }
567     i += line_len;
568     if (crlf && *end == CAR && i + 1 < input.size && *(end + 1) == NL) {
569       i += 1;  // Advance past CRLF.
570     }
571     String s = {
572       .size = line_len,
573       .data = xmemdupz(start, line_len),
574     };
575     memchrsub(s.data, NUL, NL, line_len);
576     ADD(ret, STRING_OBJ(s));
577     // If line ends at end-of-buffer, add empty final item.
578     // This is "readfile()-style", see also ":help channel-lines".
579     if (i + 1 == input.size && (*end == NL || (crlf && *end == CAR))) {
580       ADD(ret, STRING_OBJ(STRING_INIT));
581     }
582   }
583 
584   return ret;
585 }
586 
587 /// Set, tweak, or remove a mapping in a mode. Acts as the implementation for
588 /// functions like @ref nvim_buf_set_keymap.
589 ///
590 /// Arguments are handled like @ref nvim_set_keymap unless noted.
591 /// @param  buffer    Buffer handle for a specific buffer, or 0 for the current
592 ///                   buffer, or -1 to signify global behavior ("all buffers")
593 /// @param  is_unmap  When true, removes the mapping that matches {lhs}.
modify_keymap(Buffer buffer,bool is_unmap,String mode,String lhs,String rhs,Dict (keymap)* opts,Error * err)594 void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String rhs,
595                    Dict(keymap) *opts, Error *err)
596 {
597   bool global = (buffer == -1);
598   if (global) {
599     buffer = 0;
600   }
601   buf_T *target_buf = find_buffer_by_handle(buffer, err);
602 
603   if (!target_buf) {
604     return;
605   }
606 
607   MapArguments parsed_args = MAP_ARGUMENTS_INIT;
608   if (opts) {
609 #define KEY_TO_BOOL(name) \
610   parsed_args.name = api_object_to_bool(opts->name, #name, false, err); \
611   if (ERROR_SET(err)) { \
612     goto fail_and_free; \
613   }
614 
615     KEY_TO_BOOL(nowait);
616     KEY_TO_BOOL(noremap);
617     KEY_TO_BOOL(silent);
618     KEY_TO_BOOL(script);
619     KEY_TO_BOOL(expr);
620     KEY_TO_BOOL(unique);
621 #undef KEY_TO_BOOL
622   }
623   parsed_args.buffer = !global;
624 
625   set_maparg_lhs_rhs((char_u *)lhs.data, lhs.size,
626                      (char_u *)rhs.data, rhs.size,
627                      CPO_TO_CPO_FLAGS, &parsed_args);
628 
629   if (parsed_args.lhs_len > MAXMAPLEN) {
630     api_set_error(err, kErrorTypeValidation,  "LHS exceeds maximum map length: %s", lhs.data);
631     goto fail_and_free;
632   }
633 
634   if (mode.size > 1) {
635     api_set_error(err, kErrorTypeValidation, "Shortname is too long: %s", mode.data);
636     goto fail_and_free;
637   }
638   int mode_val;  // integer value of the mapping mode, to be passed to do_map()
639   char_u *p = (char_u *)((mode.size) ? mode.data : "m");
640   if (STRNCMP(p, "!", 2) == 0) {
641     mode_val = get_map_mode(&p, true);  // mapmode-ic
642   } else {
643     mode_val = get_map_mode(&p, false);
644     if ((mode_val == VISUAL + SELECTMODE + NORMAL + OP_PENDING)
645         && mode.size > 0) {
646       // get_map_mode() treats unrecognized mode shortnames as ":map".
647       // This is an error unless the given shortname was empty string "".
648       api_set_error(err, kErrorTypeValidation, "Invalid mode shortname: \"%s\"", (char *)p);
649       goto fail_and_free;
650     }
651   }
652 
653   if (parsed_args.lhs_len == 0) {
654     api_set_error(err, kErrorTypeValidation, "Invalid (empty) LHS");
655     goto fail_and_free;
656   }
657 
658   bool is_noremap = parsed_args.noremap;
659   assert(!(is_unmap && is_noremap));
660 
661   if (!is_unmap && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) {
662     if (rhs.size == 0) {  // assume that the user wants RHS to be a <Nop>
663       parsed_args.rhs_is_noop = true;
664     } else {
665       // the given RHS was nonempty and not a <Nop>, but was parsed as if it
666       // were empty?
667       assert(false && "Failed to parse nonempty RHS!");
668       api_set_error(err, kErrorTypeValidation, "Parsing of nonempty RHS failed: %s", rhs.data);
669       goto fail_and_free;
670     }
671   } else if (is_unmap && parsed_args.rhs_len) {
672     api_set_error(err, kErrorTypeValidation,
673                   "Gave nonempty RHS in unmap command: %s", parsed_args.rhs);
674     goto fail_and_free;
675   }
676 
677   // buf_do_map() reads noremap/unmap as its own argument.
678   int maptype_val = 0;
679   if (is_unmap) {
680     maptype_val = 1;
681   } else if (is_noremap) {
682     maptype_val = 2;
683   }
684 
685   switch (buf_do_map(maptype_val, &parsed_args, mode_val, 0, target_buf)) {
686   case 0:
687     break;
688   case 1:
689     api_set_error(err, kErrorTypeException, (char *)e_invarg, 0);
690     goto fail_and_free;
691   case 2:
692     api_set_error(err, kErrorTypeException, (char *)e_nomap, 0);
693     goto fail_and_free;
694   case 5:
695     api_set_error(err, kErrorTypeException,
696                   "E227: mapping already exists for %s", parsed_args.lhs);
697     goto fail_and_free;
698   default:
699     assert(false && "Unrecognized return code!");
700     goto fail_and_free;
701   }  // switch
702 
703 fail_and_free:
704   xfree(parsed_args.rhs);
705   xfree(parsed_args.orig_rhs);
706   return;
707 }
708 
709 /// Collects `n` buffer lines into array `l`, optionally replacing newlines
710 /// with NUL.
711 ///
712 /// @param buf Buffer to get lines from
713 /// @param n Number of lines to collect
714 /// @param replace_nl Replace newlines ("\n") with NUL
715 /// @param start Line number to start from
716 /// @param[out] l Lines are copied here
717 /// @param err[out] Error, if any
718 /// @return true unless `err` was set
buf_collect_lines(buf_T * buf,size_t n,int64_t start,bool replace_nl,Array * l,Error * err)719 bool buf_collect_lines(buf_T *buf, size_t n, int64_t start, bool replace_nl, Array *l, Error *err)
720 {
721   for (size_t i = 0; i < n; i++) {
722     int64_t lnum = start + (int64_t)i;
723 
724     if (lnum >= MAXLNUM) {
725       if (err != NULL) {
726         api_set_error(err, kErrorTypeValidation, "Line index is too high");
727       }
728       return false;
729     }
730 
731     const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false);
732     Object str = STRING_OBJ(cstr_to_string(bufstr));
733 
734     if (replace_nl) {
735       // Vim represents NULs as NLs, but this may confuse clients.
736       strchrsub(str.data.string.data, '\n', '\0');
737     }
738 
739     l->items[i] = str;
740   }
741 
742   return true;
743 }
744 
745 
api_free_string(String value)746 void api_free_string(String value)
747 {
748   if (!value.data) {
749     return;
750   }
751 
752   xfree(value.data);
753 }
754 
api_free_object(Object value)755 void api_free_object(Object value)
756 {
757   switch (value.type) {
758   case kObjectTypeNil:
759   case kObjectTypeBoolean:
760   case kObjectTypeInteger:
761   case kObjectTypeFloat:
762   case kObjectTypeBuffer:
763   case kObjectTypeWindow:
764   case kObjectTypeTabpage:
765     break;
766 
767   case kObjectTypeString:
768     api_free_string(value.data.string);
769     break;
770 
771   case kObjectTypeArray:
772     api_free_array(value.data.array);
773     break;
774 
775   case kObjectTypeDictionary:
776     api_free_dictionary(value.data.dictionary);
777     break;
778 
779   case kObjectTypeLuaRef:
780     api_free_luaref(value.data.luaref);
781     break;
782 
783   default:
784     abort();
785   }
786 }
787 
api_free_array(Array value)788 void api_free_array(Array value)
789 {
790   for (size_t i = 0; i < value.size; i++) {
791     api_free_object(value.items[i]);
792   }
793 
794   xfree(value.items);
795 }
796 
api_free_dictionary(Dictionary value)797 void api_free_dictionary(Dictionary value)
798 {
799   for (size_t i = 0; i < value.size; i++) {
800     api_free_string(value.items[i].key);
801     api_free_object(value.items[i].value);
802   }
803 
804   xfree(value.items);
805 }
806 
api_clear_error(Error * value)807 void api_clear_error(Error *value)
808   FUNC_ATTR_NONNULL_ALL
809 {
810   if (!ERROR_SET(value)) {
811     return;
812   }
813   xfree(value->msg);
814   value->msg = NULL;
815   value->type = kErrorTypeNone;
816 }
817 
api_metadata(void)818 Dictionary api_metadata(void)
819 {
820   static Dictionary metadata = ARRAY_DICT_INIT;
821 
822   if (!metadata.size) {
823     PUT(metadata, "version", DICTIONARY_OBJ(version_dict()));
824     init_function_metadata(&metadata);
825     init_ui_event_metadata(&metadata);
826     init_error_type_metadata(&metadata);
827     init_type_metadata(&metadata);
828   }
829 
830   return copy_object(DICTIONARY_OBJ(metadata)).data.dictionary;
831 }
832 
init_function_metadata(Dictionary * metadata)833 static void init_function_metadata(Dictionary *metadata)
834 {
835   msgpack_unpacked unpacked;
836   msgpack_unpacked_init(&unpacked);
837   if (msgpack_unpack_next(&unpacked,
838                           (const char *)funcs_metadata,
839                           sizeof(funcs_metadata),
840                           NULL) != MSGPACK_UNPACK_SUCCESS) {
841     abort();
842   }
843   Object functions;
844   msgpack_rpc_to_object(&unpacked.data, &functions);
845   msgpack_unpacked_destroy(&unpacked);
846   PUT(*metadata, "functions", functions);
847 }
848 
init_ui_event_metadata(Dictionary * metadata)849 static void init_ui_event_metadata(Dictionary *metadata)
850 {
851   msgpack_unpacked unpacked;
852   msgpack_unpacked_init(&unpacked);
853   if (msgpack_unpack_next(&unpacked,
854                           (const char *)ui_events_metadata,
855                           sizeof(ui_events_metadata),
856                           NULL) != MSGPACK_UNPACK_SUCCESS) {
857     abort();
858   }
859   Object ui_events;
860   msgpack_rpc_to_object(&unpacked.data, &ui_events);
861   msgpack_unpacked_destroy(&unpacked);
862   PUT(*metadata, "ui_events", ui_events);
863   Array ui_options = ARRAY_DICT_INIT;
864   ADD(ui_options, STRING_OBJ(cstr_to_string("rgb")));
865   for (UIExtension i = 0; i < kUIExtCount; i++) {
866     if (ui_ext_names[i][0] != '_') {
867       ADD(ui_options, STRING_OBJ(cstr_to_string(ui_ext_names[i])));
868     }
869   }
870   PUT(*metadata, "ui_options", ARRAY_OBJ(ui_options));
871 }
872 
init_error_type_metadata(Dictionary * metadata)873 static void init_error_type_metadata(Dictionary *metadata)
874 {
875   Dictionary types = ARRAY_DICT_INIT;
876 
877   Dictionary exception_metadata = ARRAY_DICT_INIT;
878   PUT(exception_metadata, "id", INTEGER_OBJ(kErrorTypeException));
879 
880   Dictionary validation_metadata = ARRAY_DICT_INIT;
881   PUT(validation_metadata, "id", INTEGER_OBJ(kErrorTypeValidation));
882 
883   PUT(types, "Exception", DICTIONARY_OBJ(exception_metadata));
884   PUT(types, "Validation", DICTIONARY_OBJ(validation_metadata));
885 
886   PUT(*metadata, "error_types", DICTIONARY_OBJ(types));
887 }
888 
init_type_metadata(Dictionary * metadata)889 static void init_type_metadata(Dictionary *metadata)
890 {
891   Dictionary types = ARRAY_DICT_INIT;
892 
893   Dictionary buffer_metadata = ARRAY_DICT_INIT;
894   PUT(buffer_metadata, "id",
895       INTEGER_OBJ(kObjectTypeBuffer - EXT_OBJECT_TYPE_SHIFT));
896   PUT(buffer_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_buf_")));
897 
898   Dictionary window_metadata = ARRAY_DICT_INIT;
899   PUT(window_metadata, "id",
900       INTEGER_OBJ(kObjectTypeWindow - EXT_OBJECT_TYPE_SHIFT));
901   PUT(window_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_win_")));
902 
903   Dictionary tabpage_metadata = ARRAY_DICT_INIT;
904   PUT(tabpage_metadata, "id",
905       INTEGER_OBJ(kObjectTypeTabpage - EXT_OBJECT_TYPE_SHIFT));
906   PUT(tabpage_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_tabpage_")));
907 
908   PUT(types, "Buffer", DICTIONARY_OBJ(buffer_metadata));
909   PUT(types, "Window", DICTIONARY_OBJ(window_metadata));
910   PUT(types, "Tabpage", DICTIONARY_OBJ(tabpage_metadata));
911 
912   PUT(*metadata, "types", DICTIONARY_OBJ(types));
913 }
914 
copy_string(String str)915 String copy_string(String str)
916 {
917   if (str.data != NULL) {
918     return (String){ .data = xmemdupz(str.data, str.size), .size = str.size };
919   } else {
920     return (String)STRING_INIT;
921   }
922 }
923 
copy_array(Array array)924 Array copy_array(Array array)
925 {
926   Array rv = ARRAY_DICT_INIT;
927   for (size_t i = 0; i < array.size; i++) {
928     ADD(rv, copy_object(array.items[i]));
929   }
930   return rv;
931 }
932 
copy_dictionary(Dictionary dict)933 Dictionary copy_dictionary(Dictionary dict)
934 {
935   Dictionary rv = ARRAY_DICT_INIT;
936   for (size_t i = 0; i < dict.size; i++) {
937     KeyValuePair item = dict.items[i];
938     PUT(rv, item.key.data, copy_object(item.value));
939   }
940   return rv;
941 }
942 
943 /// Creates a deep clone of an object
copy_object(Object obj)944 Object copy_object(Object obj)
945 {
946   switch (obj.type) {
947   case kObjectTypeBuffer:
948   case kObjectTypeTabpage:
949   case kObjectTypeWindow:
950   case kObjectTypeNil:
951   case kObjectTypeBoolean:
952   case kObjectTypeInteger:
953   case kObjectTypeFloat:
954     return obj;
955 
956   case kObjectTypeString:
957     return STRING_OBJ(copy_string(obj.data.string));
958 
959   case kObjectTypeArray:
960     return ARRAY_OBJ(copy_array(obj.data.array));
961 
962   case kObjectTypeDictionary:
963     return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary));
964   default:
965     abort();
966   }
967 }
968 
set_option_value_for(char * key,int numval,char * stringval,int opt_flags,int opt_type,void * from,Error * err)969 static void set_option_value_for(char *key, int numval, char *stringval, int opt_flags,
970                                  int opt_type, void *from, Error *err)
971 {
972   win_T *save_curwin = NULL;
973   tabpage_T *save_curtab = NULL;
974   aco_save_T aco;
975 
976   try_start();
977   switch (opt_type)
978   {
979   case SREQ_WIN:
980     if (switch_win_noblock(&save_curwin, &save_curtab, (win_T *)from,
981                            win_find_tabpage((win_T *)from), true)
982         == FAIL) {
983       restore_win_noblock(save_curwin, save_curtab, true);
984       if (try_end(err)) {
985         return;
986       }
987       api_set_error(err,
988                     kErrorTypeException,
989                     "Problem while switching windows");
990       return;
991     }
992     set_option_value_err(key, numval, stringval, opt_flags, err);
993     restore_win_noblock(save_curwin, save_curtab, true);
994     break;
995   case SREQ_BUF:
996     aucmd_prepbuf(&aco, (buf_T *)from);
997     set_option_value_err(key, numval, stringval, opt_flags, err);
998     aucmd_restbuf(&aco);
999     break;
1000   case SREQ_GLOBAL:
1001     set_option_value_err(key, numval, stringval, opt_flags, err);
1002     break;
1003   }
1004 
1005   if (ERROR_SET(err)) {
1006     return;
1007   }
1008 
1009   try_end(err);
1010 }
1011 
1012 
set_option_value_err(char * key,int numval,char * stringval,int opt_flags,Error * err)1013 static void set_option_value_err(char *key, int numval, char *stringval, int opt_flags, Error *err)
1014 {
1015   char *errmsg;
1016 
1017   if ((errmsg = set_option_value(key, numval, stringval, opt_flags))) {
1018     if (try_end(err)) {
1019       return;
1020     }
1021 
1022     api_set_error(err, kErrorTypeException, "%s", errmsg);
1023   }
1024 }
1025 
api_set_error(Error * err,ErrorType errType,const char * format,...)1026 void api_set_error(Error *err, ErrorType errType, const char *format, ...)
1027   FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PRINTF(3, 4)
1028 {
1029   assert(kErrorTypeNone != errType);
1030   va_list args1;
1031   va_list args2;
1032   va_start(args1, format);
1033   va_copy(args2, args1);
1034   int len = vsnprintf(NULL, 0, format, args1);
1035   va_end(args1);
1036   assert(len >= 0);
1037   // Limit error message to 1 MB.
1038   size_t bufsize = MIN((size_t)len + 1, 1024 * 1024);
1039   err->msg = xmalloc(bufsize);
1040   vsnprintf(err->msg, bufsize, format, args2);
1041   va_end(args2);
1042 
1043   err->type = errType;
1044 }
1045 
1046 /// Get an array containing dictionaries describing mappings
1047 /// based on mode and buffer id
1048 ///
1049 /// @param  mode  The abbreviation for the mode
1050 /// @param  buf  The buffer to get the mapping array. NULL for global
1051 /// @returns Array of maparg()-like dictionaries describing mappings
keymap_array(String mode,buf_T * buf)1052 ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
1053 {
1054   Array mappings = ARRAY_DICT_INIT;
1055   dict_T *const dict = tv_dict_alloc();
1056 
1057   // Convert the string mode to the integer mode
1058   // that is stored within each mapblock
1059   char_u *p = (char_u *)mode.data;
1060   int int_mode = get_map_mode(&p, 0);
1061 
1062   // Determine the desired buffer value
1063   long buffer_value = (buf == NULL) ? 0 : buf->handle;
1064 
1065   for (int i = 0; i < MAX_MAPHASH; i++) {
1066     for (const mapblock_T *current_maphash = get_maphash(i, buf);
1067          current_maphash;
1068          current_maphash = current_maphash->m_next) {
1069       // Check for correct mode
1070       if (int_mode & current_maphash->m_mode) {
1071         mapblock_fill_dict(dict, current_maphash, buffer_value, false);
1072         ADD(mappings, vim_to_object((typval_T[]) { { .v_type = VAR_DICT, .vval.v_dict = dict } }));
1073 
1074         tv_dict_clear(dict);
1075       }
1076     }
1077   }
1078   tv_dict_free(dict);
1079 
1080   return mappings;
1081 }
1082 
1083 /// Gets the line and column of an extmark.
1084 ///
1085 /// Extmarks may be queried by position, name or even special names
1086 /// in the future such as "cursor".
1087 ///
1088 /// @param[out] lnum extmark line
1089 /// @param[out] colnr extmark column
1090 ///
1091 /// @return true if the extmark was found, else false
extmark_get_index_from_obj(buf_T * buf,Integer ns_id,Object obj,int * row,colnr_T * col,Error * err)1092 bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
1093                                 *row, colnr_T *col, Error *err)
1094 {
1095   // Check if it is mark id
1096   if (obj.type == kObjectTypeInteger) {
1097     Integer id = obj.data.integer;
1098     if (id == 0) {
1099       *row = 0;
1100       *col = 0;
1101       return true;
1102     } else if (id == -1) {
1103       *row = MAXLNUM;
1104       *col = MAXCOL;
1105       return true;
1106     } else if (id < 0) {
1107       api_set_error(err, kErrorTypeValidation, "Mark id must be positive");
1108       return false;
1109     }
1110 
1111     ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
1112     if (extmark.row >= 0) {
1113       *row = extmark.row;
1114       *col = extmark.col;
1115       return true;
1116     } else {
1117       api_set_error(err, kErrorTypeValidation, "No mark with requested id");
1118       return false;
1119     }
1120 
1121     // Check if it is a position
1122   } else if (obj.type == kObjectTypeArray) {
1123     Array pos = obj.data.array;
1124     if (pos.size != 2
1125         || pos.items[0].type != kObjectTypeInteger
1126         || pos.items[1].type != kObjectTypeInteger) {
1127       api_set_error(err, kErrorTypeValidation,
1128                     "Position must have 2 integer elements");
1129       return false;
1130     }
1131     Integer pos_row = pos.items[0].data.integer;
1132     Integer pos_col = pos.items[1].data.integer;
1133     *row = (int)(pos_row >= 0 ? pos_row  : MAXLNUM);
1134     *col = (colnr_T)(pos_col >= 0 ? pos_col : MAXCOL);
1135     return true;
1136   } else {
1137     api_set_error(err, kErrorTypeValidation,
1138                   "Position must be a mark id Integer or position Array");
1139     return false;
1140   }
1141 }
1142 
parse_virt_text(Array chunks,Error * err,int * width)1143 VirtText parse_virt_text(Array chunks, Error *err, int *width)
1144 {
1145   VirtText virt_text = KV_INITIAL_VALUE;
1146   int w = 0;
1147   for (size_t i = 0; i < chunks.size; i++) {
1148     if (chunks.items[i].type != kObjectTypeArray) {
1149       api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
1150       goto free_exit;
1151     }
1152     Array chunk = chunks.items[i].data.array;
1153     if (chunk.size == 0 || chunk.size > 2
1154         || chunk.items[0].type != kObjectTypeString) {
1155       api_set_error(err, kErrorTypeValidation,
1156                     "Chunk is not an array with one or two strings");
1157       goto free_exit;
1158     }
1159 
1160     String str = chunk.items[0].data.string;
1161 
1162     int hl_id = 0;
1163     if (chunk.size == 2) {
1164       Object hl = chunk.items[1];
1165       if (hl.type == kObjectTypeArray) {
1166         Array arr = hl.data.array;
1167         for (size_t j = 0; j < arr.size; j++) {
1168           hl_id = object_to_hl_id(arr.items[j], "virt_text highlight", err);
1169           if (ERROR_SET(err)) {
1170             goto free_exit;
1171           }
1172           if (j < arr.size-1) {
1173             kv_push(virt_text, ((VirtTextChunk){ .text = NULL,
1174                                                  .hl_id = hl_id }));
1175           }
1176         }
1177       } else {
1178         hl_id = object_to_hl_id(hl, "virt_text highlight", err);
1179         if (ERROR_SET(err)) {
1180           goto free_exit;
1181         }
1182       }
1183     }
1184 
1185     char *text = transstr(str.size > 0 ? str.data : "", false);  // allocates
1186     w += (int)mb_string2cells((char_u *)text);
1187 
1188     kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
1189   }
1190 
1191   *width = w;
1192   return virt_text;
1193 
1194 free_exit:
1195   clear_virttext(&virt_text);
1196   return virt_text;
1197 }
1198 
1199 /// Force obj to bool.
1200 /// If it fails, returns false and sets err
1201 /// @param obj          The object to coerce to a boolean
1202 /// @param what         The name of the object, used for error message
1203 /// @param nil_value    What to return if the type is nil.
1204 /// @param err          Set if there was an error in converting to a bool
api_object_to_bool(Object obj,const char * what,bool nil_value,Error * err)1205 bool api_object_to_bool(Object obj, const char *what, bool nil_value, Error *err)
1206 {
1207   if (obj.type == kObjectTypeBoolean) {
1208     return obj.data.boolean;
1209   } else if (obj.type == kObjectTypeInteger) {
1210     return obj.data.integer;  // C semantics: non-zero int is true
1211   } else if (obj.type == kObjectTypeNil) {
1212     return nil_value;  // caller decides what NIL (missing retval in lua) means
1213   } else {
1214     api_set_error(err, kErrorTypeValidation, "%s is not a boolean", what);
1215     return false;
1216   }
1217 }
1218 
object_to_hl_id(Object obj,const char * what,Error * err)1219 int object_to_hl_id(Object obj, const char *what, Error *err)
1220 {
1221   if (obj.type == kObjectTypeString) {
1222     String str = obj.data.string;
1223     return str.size ? syn_check_group(str.data, (int)str.size) : 0;
1224   } else if (obj.type == kObjectTypeInteger) {
1225     return MAX((int)obj.data.integer, 0);
1226   } else {
1227     api_set_error(err, kErrorTypeValidation,
1228                   "%s is not a valid highlight", what);
1229     return 0;
1230   }
1231 }
1232 
parse_hl_msg(Array chunks,Error * err)1233 HlMessage parse_hl_msg(Array chunks, Error *err)
1234 {
1235   HlMessage hl_msg = KV_INITIAL_VALUE;
1236   for (size_t i = 0; i < chunks.size; i++) {
1237     if (chunks.items[i].type != kObjectTypeArray) {
1238       api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
1239       goto free_exit;
1240     }
1241     Array chunk = chunks.items[i].data.array;
1242     if (chunk.size == 0 || chunk.size > 2
1243         || chunk.items[0].type != kObjectTypeString
1244         || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) {
1245       api_set_error(err, kErrorTypeValidation,
1246                     "Chunk is not an array with one or two strings");
1247       goto free_exit;
1248     }
1249 
1250     String str = copy_string(chunk.items[0].data.string);
1251 
1252     int attr = 0;
1253     if (chunk.size == 2) {
1254       String hl = chunk.items[1].data.string;
1255       if (hl.size > 0) {
1256         // TODO(bfredl): use object_to_hl_id and allow integer
1257         int hl_id = syn_check_group(hl.data, (int)hl.size);
1258         attr = hl_id > 0 ? syn_id2attr(hl_id) : 0;
1259       }
1260     }
1261     kv_push(hl_msg, ((HlMessageChunk){ .text = str, .attr = attr }));
1262   }
1263 
1264   return hl_msg;
1265 
1266 free_exit:
1267   clear_hl_msg(&hl_msg);
1268   return hl_msg;
1269 }
1270 
api_dict_to_keydict(void * rv,field_hash hashy,Dictionary dict,Error * err)1271 bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err)
1272 {
1273   for (size_t i = 0; i < dict.size; i++) {
1274     String k = dict.items[i].key;
1275     Object *field = hashy(rv, k.data, k.size);
1276     if (!field) {
1277       api_set_error(err, kErrorTypeValidation, "Invalid key: '%.*s'", (int)k.size, k.data);
1278       return false;
1279     }
1280 
1281     *field = dict.items[i].value;
1282   }
1283 
1284   return true;
1285 }
1286 
api_free_keydict(void * dict,KeySetLink * table)1287 void api_free_keydict(void *dict, KeySetLink *table)
1288 {
1289   for (size_t i = 0; table[i].str; i++) {
1290     api_free_object(*(Object *)((char *)dict + table[i].ptr_off));
1291   }
1292 }
1293 
1294 /// Set a named mark
1295 /// buffer and mark name must be validated already
1296 /// @param buffer     Buffer to set the mark on
1297 /// @param name       Mark name
1298 /// @param line       Line number
1299 /// @param col        Column/row number
1300 /// @return true if the mark was set, else false
set_mark(buf_T * buf,String name,Integer line,Integer col,Error * err)1301 bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err)
1302 {
1303   buf = buf == NULL ? curbuf : buf;
1304   // If line == 0 the marks is being deleted
1305   bool res = false;
1306   bool deleting = false;
1307   if (line == 0) {
1308     col = 0;
1309     deleting = true;
1310   } else {
1311     if (col > MAXCOL) {
1312       api_set_error(err, kErrorTypeValidation, "Column value outside range");
1313       return res;
1314     }
1315     if (line < 1 || line > buf->b_ml.ml_line_count) {
1316       api_set_error(err, kErrorTypeValidation, "Line value outside range");
1317       return res;
1318     }
1319   }
1320   pos_T pos = { line, (int)col, (int)col };
1321   res = setmark_pos(*name.data, &pos, buf->handle);
1322   if (!res) {
1323     if (deleting) {
1324       api_set_error(err, kErrorTypeException,
1325                     "Failed to delete named mark: %c", *name.data);
1326     } else {
1327       api_set_error(err, kErrorTypeException,
1328                     "Failed to set named mark: %c", *name.data);
1329     }
1330   }
1331   return res;
1332 }
1333 
1334 /// Get default statusline highlight for window
get_default_stl_hl(win_T * wp)1335 const char *get_default_stl_hl(win_T *wp)
1336 {
1337   if (wp == NULL) {
1338     return "TabLineFill";
1339   } else if (wp == curwin) {
1340     return "StatusLine";
1341   } else {
1342     return "StatusLineNC";
1343   }
1344 }
1345