neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

helpers.c (30865B)


      1 #include <assert.h>
      2 #include <limits.h>
      3 #include <stdarg.h>
      4 #include <stdbool.h>
      5 #include <stddef.h>
      6 #include <stdint.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <string.h>
     10 
     11 #include "klib/kvec.h"
     12 #include "nvim/api/private/converter.h"
     13 #include "nvim/api/private/defs.h"
     14 #include "nvim/api/private/helpers.h"
     15 #include "nvim/api/private/validate.h"
     16 #include "nvim/ascii_defs.h"
     17 #include "nvim/buffer_defs.h"
     18 #include "nvim/eval/typval.h"
     19 #include "nvim/eval/vars.h"
     20 #include "nvim/ex_eval.h"
     21 #include "nvim/garray_defs.h"
     22 #include "nvim/globals.h"
     23 #include "nvim/highlight_group.h"
     24 #include "nvim/lua/executor.h"
     25 #include "nvim/map_defs.h"
     26 #include "nvim/mark.h"
     27 #include "nvim/memline.h"
     28 #include "nvim/memory.h"
     29 #include "nvim/memory_defs.h"
     30 #include "nvim/message.h"
     31 #include "nvim/msgpack_rpc/unpacker.h"
     32 #include "nvim/pos_defs.h"
     33 #include "nvim/runtime.h"
     34 #include "nvim/types_defs.h"
     35 
     36 #include "api/private/api_metadata.generated.h"
     37 #include "api/private/helpers.c.generated.h"  // IWYU pragma: keep
     38 
     39 /// Start block that may cause Vimscript exceptions while evaluating another code
     40 ///
     41 /// Used just in case caller is supposed to be operating when other Vimscript code
     42 /// is being processed and that “other Vimscript code” must not be affected.
     43 ///
     44 /// @warning Avoid calling directly; use TRY_WRAP instead.
     45 ///
     46 /// @param[out]  tstate  Location where try state should be saved.
     47 void try_enter(TryState *const tstate)
     48 {
     49  // TODO(ZyX-I): Check whether try_enter()/try_leave() may use
     50  //              enter_cleanup()/leave_cleanup(). Or
     51  //              save_dbg_stuff()/restore_dbg_stuff().
     52  *tstate = (TryState) {
     53    .current_exception = current_exception,
     54    .msg_list = (const msglist_T *const *)msg_list,
     55    .private_msg_list = NULL,
     56    .got_int = got_int,
     57    .did_throw = did_throw,
     58    .need_rethrow = need_rethrow,
     59    .did_emsg = did_emsg,
     60  };
     61  // `msg_list` controls the collection of abort-causing non-exception errors,
     62  // which would otherwise be ignored.  This pattern is from do_cmdline().
     63  msg_list = &tstate->private_msg_list;
     64  current_exception = NULL;
     65  got_int = false;
     66  did_throw = false;
     67  need_rethrow = false;
     68  did_emsg = false;
     69  trylevel++;
     70 }
     71 
     72 /// Ends a `try_enter` block; sets error message if any.
     73 ///
     74 /// @warning Avoid calling directly; use TRY_WRAP instead.
     75 ///
     76 /// @param[out] err Pointer to the stack-allocated error object
     77 void try_leave(const TryState *const tstate, Error *const err)
     78  FUNC_ATTR_NONNULL_ALL
     79 {
     80  // Note: all globals manipulated here should be saved/restored in
     81  // try_enter/try_leave.
     82  assert(trylevel > 0);
     83  trylevel--;
     84 
     85  // Set by emsg(), affects aborting().  See also enter_cleanup().
     86  did_emsg = false;
     87  force_abort = false;
     88 
     89  if (got_int) {
     90    if (did_throw) {
     91      // If we got an interrupt, discard the current exception
     92      discard_current_exception();
     93    }
     94 
     95    api_set_error(err, kErrorTypeException, "Keyboard interrupt");
     96    got_int = false;
     97  } else if (msg_list != NULL && *msg_list != NULL) {
     98    bool should_free;
     99    char *msg = get_exception_string(*msg_list,
    100                                     ET_ERROR,
    101                                     NULL,
    102                                     &should_free);
    103    api_set_error(err, kErrorTypeException, "%s", msg);
    104    free_global_msglist();
    105 
    106    if (should_free) {
    107      xfree(msg);
    108    }
    109  } else if (did_throw || need_rethrow) {
    110    if (*current_exception->throw_name != NUL) {
    111      if (current_exception->throw_lnum != 0) {
    112        api_set_error(err, kErrorTypeException, "%s, line %" PRIdLINENR ": %s",
    113                      current_exception->throw_name, current_exception->throw_lnum,
    114                      current_exception->value);
    115      } else {
    116        api_set_error(err, kErrorTypeException, "%s: %s",
    117                      current_exception->throw_name, current_exception->value);
    118      }
    119    } else {
    120      api_set_error(err, kErrorTypeException, "%s", current_exception->value);
    121    }
    122    discard_current_exception();
    123  }
    124 
    125  // Restore the exception context.
    126  msg_list = (msglist_T **)tstate->msg_list;
    127  current_exception = tstate->current_exception;
    128  got_int = tstate->got_int;
    129  did_throw = tstate->did_throw;
    130  need_rethrow = tstate->need_rethrow;
    131  did_emsg = tstate->did_emsg;
    132 }
    133 
    134 /// Recursively expands a vimscript value in a dict
    135 ///
    136 /// @param dict The vimscript dict
    137 /// @param key The key
    138 /// @param[out] err Details of an error that may have occurred
    139 Object dict_get_value(dict_T *dict, String key, Arena *arena, Error *err)
    140 {
    141  dictitem_T *const di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size);
    142 
    143  if (di == NULL) {
    144    api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data);
    145    return (Object)OBJECT_INIT;
    146  }
    147 
    148  return vim_to_object(&di->di_tv, arena, true);
    149 }
    150 
    151 dictitem_T *dict_check_writable(dict_T *dict, String key, bool del, Error *err)
    152 {
    153  dictitem_T *di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size);
    154 
    155  if (di != NULL) {
    156    if (di->di_flags & DI_FLAGS_RO) {
    157      api_set_error(err, kErrorTypeException, "Key is read-only: %s", key.data);
    158    } else if (di->di_flags & DI_FLAGS_LOCK) {
    159      api_set_error(err, kErrorTypeException, "Key is locked: %s", key.data);
    160    } else if (del && (di->di_flags & DI_FLAGS_FIX)) {
    161      api_set_error(err, kErrorTypeException, "Key is fixed: %s", key.data);
    162    }
    163  } else if (dict->dv_lock) {
    164    api_set_error(err, kErrorTypeException, "Dict is locked");
    165  } else if (key.size == 0) {
    166    api_set_error(err, kErrorTypeValidation, "Key name is empty");
    167  } else if (key.size > INT_MAX) {
    168    api_set_error(err, kErrorTypeValidation, "Key name is too long");
    169  }
    170 
    171  return di;
    172 }
    173 
    174 /// Set a value in a scope dict. Objects are recursively expanded into their
    175 /// vimscript equivalents.
    176 ///
    177 /// @param dict The vimscript dict
    178 /// @param key The key
    179 /// @param value The new value
    180 /// @param del Delete key in place of setting it. Argument `value` is ignored in
    181 ///            this case.
    182 /// @param retval If true the old value will be converted and returned.
    183 /// @param[out] err Details of an error that may have occurred
    184 /// @return The old value if `retval` is true and the key was present, else NIL
    185 Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retval, Arena *arena,
    186                    Error *err)
    187 {
    188  Object rv = OBJECT_INIT;
    189  dictitem_T *di = dict_check_writable(dict, key, del, err);
    190 
    191  if (ERROR_SET(err)) {
    192    return rv;
    193  }
    194 
    195  bool watched = tv_dict_is_watched(dict);
    196 
    197  if (del) {
    198    // Delete the key
    199    if (di == NULL) {
    200      // Doesn't exist, fail
    201      api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data);
    202    } else {
    203      // Notify watchers
    204      if (watched) {
    205        tv_dict_watcher_notify(dict, key.data, NULL, &di->di_tv);
    206      }
    207      // Return the old value
    208      if (retval) {
    209        rv = vim_to_object(&di->di_tv, arena, false);
    210      }
    211      // Delete the entry
    212      tv_dict_item_remove(dict, di);
    213    }
    214  } else {
    215    // Update the key
    216    typval_T tv;
    217 
    218    // Convert the object to a vimscript type in the temporary variable
    219    object_to_vim(value, &tv, err);
    220 
    221    typval_T oldtv = TV_INITIAL_VALUE;
    222 
    223    if (di == NULL) {
    224      // Need to create an entry
    225      di = tv_dict_item_alloc_len(key.data, key.size);
    226      tv_dict_add(dict, di);
    227    } else {
    228      // Return the old value
    229      if (retval) {
    230        rv = vim_to_object(&di->di_tv, arena, false);
    231      }
    232      bool type_error = false;
    233      if (dict == get_vimvar_dict()
    234          && !before_set_vvar(key.data, di, &tv, true, watched, &type_error)) {
    235        tv_clear(&tv);
    236        if (type_error) {
    237          api_set_error(err, kErrorTypeValidation,
    238                        "Setting v:%s to value with wrong type", key.data);
    239        }
    240        return rv;
    241      }
    242      if (watched) {
    243        tv_copy(&di->di_tv, &oldtv);
    244      }
    245      tv_clear(&di->di_tv);
    246    }
    247 
    248    // Update the value
    249    tv_copy(&tv, &di->di_tv);
    250 
    251    // Notify watchers
    252    if (watched) {
    253      tv_dict_watcher_notify(dict, key.data, &tv, &oldtv);
    254      tv_clear(&oldtv);
    255    }
    256 
    257    // Clear the temporary variable
    258    tv_clear(&tv);
    259  }
    260 
    261  return rv;
    262 }
    263 
    264 buf_T *find_buffer_by_handle(Buffer buffer, Error *err)
    265 {
    266  if (buffer == 0) {
    267    return curbuf;
    268  }
    269 
    270  buf_T *rv = handle_get_buffer(buffer);
    271 
    272  if (!rv) {
    273    api_set_error(err, kErrorTypeValidation, "Invalid buffer id: %d", buffer);
    274  }
    275 
    276  return rv;
    277 }
    278 
    279 win_T *find_window_by_handle(Window window, Error *err)
    280 {
    281  if (window == 0) {
    282    return curwin;
    283  }
    284 
    285  win_T *rv = handle_get_window(window);
    286 
    287  if (!rv) {
    288    api_set_error(err, kErrorTypeValidation, "Invalid window id: %d", window);
    289  }
    290 
    291  return rv;
    292 }
    293 
    294 tabpage_T *find_tab_by_handle(Tabpage tabpage, Error *err)
    295 {
    296  if (tabpage == 0) {
    297    return curtab;
    298  }
    299 
    300  tabpage_T *rv = handle_get_tabpage(tabpage);
    301 
    302  if (!rv) {
    303    api_set_error(err, kErrorTypeValidation, "Invalid tabpage id: %d", tabpage);
    304  }
    305 
    306  return rv;
    307 }
    308 
    309 /// Allocates a String consisting of a single char. Does not support multibyte
    310 /// characters. The resulting string is also NUL-terminated, to facilitate
    311 /// interoperating with code using C strings.
    312 ///
    313 /// @param char the char to convert
    314 /// @return the resulting String, if the input char was NUL, an
    315 ///         empty String is returned
    316 String cchar_to_string(char c)
    317 {
    318  char buf[] = { c, NUL };
    319  return (String){
    320    .data = xmemdupz(buf, 1),
    321    .size = (c != NUL) ? 1 : 0
    322  };
    323 }
    324 
    325 /// Copies a C string into a String (binary safe string, characters + length).
    326 /// The resulting string is also NUL-terminated, to facilitate interoperating
    327 /// with code using C strings.
    328 ///
    329 /// @param str the C string to copy
    330 /// @return the resulting String, if the input string was NULL, an
    331 ///         empty String is returned
    332 String cstr_to_string(const char *str)
    333 {
    334  if (str == NULL) {
    335    return (String)STRING_INIT;
    336  }
    337 
    338  size_t len = strlen(str);
    339  return (String){
    340    .data = xmemdupz(str, len),
    341    .size = len,
    342  };
    343 }
    344 
    345 /// Copies a String to an allocated, NUL-terminated C string.
    346 ///
    347 /// @param str the String to copy
    348 /// @return the resulting C string
    349 char *string_to_cstr(String str)
    350  FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
    351 {
    352  return xstrndup(str.data, str.size);
    353 }
    354 
    355 /// Copies buffer to an allocated String.
    356 /// The resulting string is also NUL-terminated, to facilitate interoperating
    357 /// with code using C strings.
    358 ///
    359 /// @param buf the buffer to copy
    360 /// @param size length of the buffer
    361 /// @return the resulting String, if the input string was NULL, an
    362 ///         empty String is returned
    363 String cbuf_to_string(const char *buf, size_t size)
    364  FUNC_ATTR_NONNULL_ALL
    365 {
    366  return (String){
    367    .data = xmemdupz(buf, size),
    368    .size = size
    369  };
    370 }
    371 
    372 String cstrn_to_string(const char *str, size_t maxsize)
    373  FUNC_ATTR_NONNULL_ALL
    374 {
    375  return cbuf_to_string(str, strnlen(str, maxsize));
    376 }
    377 
    378 String cstrn_as_string(char *str, size_t maxsize)
    379  FUNC_ATTR_NONNULL_ALL
    380 {
    381  return cbuf_as_string(str, strnlen(str, maxsize));
    382 }
    383 
    384 /// Creates a String using the given C string. Unlike
    385 /// cstr_to_string this function DOES NOT copy the C string.
    386 ///
    387 /// @param str the C string to use
    388 /// @return The resulting String, or an empty String if
    389 ///           str was NULL
    390 String cstr_as_string(const char *str) FUNC_ATTR_PURE
    391 {
    392  if (str == NULL) {
    393    return (String)STRING_INIT;
    394  }
    395  return (String){ .data = (char *)str, .size = strlen(str) };
    396 }
    397 
    398 /// Return the owned memory of a ga as a String
    399 ///
    400 /// Reinitializes the ga to a valid empty state.
    401 String ga_take_string(garray_T *ga)
    402 {
    403  String str = { .data = (char *)ga->ga_data, .size = (size_t)ga->ga_len };
    404  ga->ga_data = NULL;
    405  ga->ga_len = 0;
    406  ga->ga_maxlen = 0;
    407  return str;
    408 }
    409 
    410 /// Creates "readfile()-style" ArrayOf(String) from a binary string.
    411 ///
    412 /// - Lines break at \n (NL/LF/line-feed).
    413 /// - NUL bytes are replaced with NL.
    414 /// - If the last byte is a linebreak an extra empty list item is added.
    415 ///
    416 /// @param input  Binary string
    417 /// @param crlf  Also break lines at CR and CRLF.
    418 /// @return [allocated] String array
    419 Array string_to_array(const String input, bool crlf, Arena *arena)
    420 {
    421  ArrayBuilder ret = ARRAY_DICT_INIT;
    422  kvi_init(ret);
    423  for (size_t i = 0; i < input.size; i++) {
    424    const char *start = input.data + i;
    425    const char *end = start;
    426    size_t line_len = 0;
    427    for (; line_len < input.size - i; line_len++) {
    428      end = start + line_len;
    429      if (*end == NL || (crlf && *end == CAR)) {
    430        break;
    431      }
    432    }
    433    i += line_len;
    434    if (crlf && *end == CAR && i + 1 < input.size && *(end + 1) == NL) {
    435      i += 1;  // Advance past CRLF.
    436    }
    437    String s = CBUF_TO_ARENA_STR(arena, start, line_len);
    438    memchrsub(s.data, NUL, NL, line_len);
    439    kvi_push(ret, STRING_OBJ(s));
    440    // If line ends at end-of-buffer, add empty final item.
    441    // This is "readfile()-style", see also ":help channel-lines".
    442    if (i + 1 == input.size && (*end == NL || (crlf && *end == CAR))) {
    443      kvi_push(ret, STRING_OBJ(STRING_INIT));
    444    }
    445  }
    446 
    447  return arena_take_arraybuilder(arena, &ret);
    448 }
    449 
    450 /// Normalizes 0-based indexes to buffer line numbers.
    451 int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob)
    452 {
    453  assert(buf->b_ml.ml_line_count > 0);
    454  int64_t max_index = buf->b_ml.ml_line_count + (int)end_exclusive - 1;
    455  // A negative index counts from the bottom.
    456  index = index < 0 ? max_index + index + 1 : index;
    457 
    458  // Check for oob and clamp.
    459  if (index > max_index) {
    460    *oob = true;
    461    index = max_index;
    462  } else if (index < 0) {
    463    *oob = true;
    464    index = 0;
    465  }
    466  // Convert the index to a 1-based line number.
    467  index++;
    468  return index;
    469 }
    470 
    471 /// Returns a substring of a buffer line
    472 ///
    473 /// @param buf          Buffer id
    474 /// @param lnum         Line number (1-based)
    475 /// @param start_col    Starting byte offset into line (0-based)
    476 /// @param end_col      Ending byte offset into line (0-based, exclusive)
    477 /// @param err          Error object
    478 /// @return The text between start_col and end_col on line lnum of buffer buf
    479 String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col, Error *err)
    480 {
    481  String rv = STRING_INIT;
    482 
    483  if (lnum >= MAXLNUM) {
    484    api_set_error(err, kErrorTypeValidation, "Line index is too high");
    485    return rv;
    486  }
    487 
    488  char *bufstr = ml_get_buf(buf, (linenr_T)lnum);
    489  colnr_T line_length = ml_get_buf_len(buf, (linenr_T)lnum);
    490 
    491  start_col = start_col < 0 ? line_length + start_col + 1 : start_col;
    492  end_col = end_col < 0 ? line_length + end_col + 1 : end_col;
    493 
    494  start_col = MIN(MAX(0, start_col), line_length);
    495  end_col = MIN(MAX(0, end_col), line_length);
    496 
    497  if (start_col > end_col) {
    498    api_set_error(err, kErrorTypeValidation, "start_col must be less than or equal to end_col");
    499    return rv;
    500  }
    501 
    502  return cbuf_as_string(bufstr + start_col, (size_t)(end_col - start_col));
    503 }
    504 
    505 void api_free_string(String value)
    506 {
    507  xfree(value.data);
    508 }
    509 
    510 Array arena_array(Arena *arena, size_t max_size)
    511 {
    512  Array arr = ARRAY_DICT_INIT;
    513  kv_fixsize_arena(arena, arr, max_size);
    514  return arr;
    515 }
    516 
    517 Dict arena_dict(Arena *arena, size_t max_size)
    518 {
    519  Dict dict = ARRAY_DICT_INIT;
    520  kv_fixsize_arena(arena, dict, max_size);
    521  return dict;
    522 }
    523 
    524 String arena_string(Arena *arena, String str)
    525 {
    526  if (str.size) {
    527    return cbuf_as_string(arena_memdupz(arena, str.data, str.size), str.size);
    528  } else {
    529    return (String){ .data = arena ? "" : xstrdup(""), .size = 0 };
    530  }
    531 }
    532 
    533 Array arena_take_arraybuilder(Arena *arena, ArrayBuilder *arr)
    534 {
    535  Array ret = arena_array(arena, kv_size(*arr));
    536  ret.size = kv_size(*arr);
    537  memcpy(ret.items, arr->items, sizeof(ret.items[0]) * ret.size);
    538  kvi_destroy(*arr);
    539  return ret;
    540 }
    541 
    542 void api_free_object(Object value)
    543 {
    544  switch (value.type) {
    545  case kObjectTypeNil:
    546  case kObjectTypeBoolean:
    547  case kObjectTypeInteger:
    548  case kObjectTypeFloat:
    549  case kObjectTypeBuffer:
    550  case kObjectTypeWindow:
    551  case kObjectTypeTabpage:
    552    break;
    553 
    554  case kObjectTypeString:
    555    api_free_string(value.data.string);
    556    break;
    557 
    558  case kObjectTypeArray:
    559    api_free_array(value.data.array);
    560    break;
    561 
    562  case kObjectTypeDict:
    563    api_free_dict(value.data.dict);
    564    break;
    565 
    566  case kObjectTypeLuaRef:
    567    api_free_luaref(value.data.luaref);
    568    break;
    569  }
    570 }
    571 
    572 void api_free_array(Array value)
    573 {
    574  for (size_t i = 0; i < value.size; i++) {
    575    api_free_object(value.items[i]);
    576  }
    577 
    578  xfree(value.items);
    579 }
    580 
    581 void api_free_dict(Dict value)
    582 {
    583  for (size_t i = 0; i < value.size; i++) {
    584    api_free_string(value.items[i].key);
    585    api_free_object(value.items[i].value);
    586  }
    587 
    588  xfree(value.items);
    589 }
    590 
    591 void api_clear_error(Error *value)
    592  FUNC_ATTR_NONNULL_ALL
    593 {
    594  if (!ERROR_SET(value)) {
    595    return;
    596  }
    597  xfree(value->msg);
    598  value->msg = NULL;
    599  value->type = kErrorTypeNone;
    600 }
    601 
    602 // initialized once, never freed
    603 static ArenaMem mem_for_metadata = NULL;
    604 
    605 /// @returns a shared value. caller must not modify it!
    606 Object api_metadata(void)
    607 {
    608  static Object metadata = OBJECT_INIT;
    609 
    610  if (metadata.type == kObjectTypeNil) {
    611    Arena arena = ARENA_EMPTY;
    612    Error err = ERROR_INIT;
    613    metadata = unpack((char *)packed_api_metadata, sizeof(packed_api_metadata), &arena, &err);
    614    if (ERROR_SET(&err) || metadata.type != kObjectTypeDict) {
    615      abort();
    616    }
    617    mem_for_metadata = arena_finish(&arena);
    618  }
    619 
    620  return metadata;
    621 }
    622 
    623 String api_metadata_raw(void)
    624 {
    625  return cbuf_as_string((char *)packed_api_metadata, sizeof(packed_api_metadata));
    626 }
    627 
    628 // all the copy_[object] functions allow arena=NULL,
    629 // then global allocations are used, and the resulting object
    630 // should be freed with an api_free_[object] function
    631 
    632 String copy_string(String str, Arena *arena)
    633 {
    634  if (str.data != NULL) {
    635    return (String){ .data = arena_memdupz(arena, str.data, str.size), .size = str.size };
    636  } else {
    637    return (String)STRING_INIT;
    638  }
    639 }
    640 
    641 Array copy_array(Array array, Arena *arena)
    642 {
    643  Array rv = arena_array(arena, array.size);
    644  for (size_t i = 0; i < array.size; i++) {
    645    ADD(rv, copy_object(array.items[i], arena));
    646  }
    647  return rv;
    648 }
    649 
    650 Dict copy_dict(Dict dict, Arena *arena)
    651 {
    652  Dict rv = arena_dict(arena, dict.size);
    653  for (size_t i = 0; i < dict.size; i++) {
    654    KeyValuePair item = dict.items[i];
    655    PUT_C(rv, copy_string(item.key, arena).data, copy_object(item.value, arena));
    656  }
    657  return rv;
    658 }
    659 
    660 /// Creates a deep clone of an object
    661 Object copy_object(Object obj, Arena *arena)
    662 {
    663  switch (obj.type) {
    664  case kObjectTypeBuffer:
    665  case kObjectTypeTabpage:
    666  case kObjectTypeWindow:
    667  case kObjectTypeNil:
    668  case kObjectTypeBoolean:
    669  case kObjectTypeInteger:
    670  case kObjectTypeFloat:
    671    return obj;
    672 
    673  case kObjectTypeString:
    674    return STRING_OBJ(copy_string(obj.data.string, arena));
    675 
    676  case kObjectTypeArray:
    677    return ARRAY_OBJ(copy_array(obj.data.array, arena));
    678 
    679  case kObjectTypeDict:
    680    return DICT_OBJ(copy_dict(obj.data.dict, arena));
    681 
    682  case kObjectTypeLuaRef:
    683    return LUAREF_OBJ(api_new_luaref(obj.data.luaref));
    684  }
    685  UNREACHABLE;
    686 }
    687 
    688 void api_set_error(Error *err, ErrorType errType, const char *format, ...)
    689  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PRINTF(3, 4)
    690 {
    691  assert(kErrorTypeNone != errType);
    692  va_list args1;
    693  va_list args2;
    694  va_start(args1, format);
    695  va_copy(args2, args1);
    696  int len = vsnprintf(NULL, 0, format, args1);
    697  va_end(args1);
    698  assert(len >= 0);
    699  // Limit error message to 1 MB.
    700  size_t bufsize = MIN((size_t)len + 1, 1024 * 1024);
    701  err->msg = xmalloc(bufsize);
    702  vsnprintf(err->msg, bufsize, format, args2);
    703  va_end(args2);
    704 
    705  err->type = errType;
    706 }
    707 
    708 /// Force obj to bool.
    709 /// If it fails, returns false and sets err
    710 /// @param obj          The object to coerce to a boolean
    711 /// @param what         The name of the object, used for error message
    712 /// @param nil_value    What to return if the type is nil.
    713 /// @param err          Set if there was an error in converting to a bool
    714 bool api_object_to_bool(Object obj, const char *what, bool nil_value, Error *err)
    715 {
    716  if (obj.type == kObjectTypeBoolean) {
    717    return obj.data.boolean;
    718  } else if (obj.type == kObjectTypeInteger) {
    719    return obj.data.integer;  // C semantics: non-zero int is true
    720  } else if (obj.type == kObjectTypeNil) {
    721    return nil_value;  // caller decides what NIL (missing retval in Lua) means
    722  } else {
    723    api_set_error(err, kErrorTypeValidation, "%s is not a boolean", what);
    724    return false;
    725  }
    726 }
    727 
    728 int object_to_hl_id(Object obj, const char *what, Error *err)
    729 {
    730  if (obj.type == kObjectTypeString) {
    731    String str = obj.data.string;
    732    return str.size ? syn_check_group(str.data, str.size) : 0;
    733  } else if (obj.type == kObjectTypeInteger) {
    734    int id = (int)obj.data.integer;
    735    return (1 <= id && id <= highlight_num_groups()) ? id : 0;
    736  } else {
    737    api_set_error(err, kErrorTypeValidation, "Invalid hl_group: %s", what);
    738    return 0;
    739  }
    740 }
    741 
    742 char *api_typename(ObjectType t)
    743 {
    744  switch (t) {
    745  case kObjectTypeNil:
    746    return "nil";
    747  case kObjectTypeBoolean:
    748    return "Boolean";
    749  case kObjectTypeInteger:
    750    return "Integer";
    751  case kObjectTypeFloat:
    752    return "Float";
    753  case kObjectTypeString:
    754    return "String";
    755  case kObjectTypeArray:
    756    return "Array";
    757  case kObjectTypeDict:
    758    return "Dict";
    759  case kObjectTypeLuaRef:
    760    return "Function";
    761  case kObjectTypeBuffer:
    762    return "Buffer";
    763  case kObjectTypeWindow:
    764    return "Window";
    765  case kObjectTypeTabpage:
    766    return "Tabpage";
    767  }
    768  UNREACHABLE;
    769 }
    770 
    771 HlMessage parse_hl_msg(ArrayOf(Tuple(String, *HLGroupID)) chunks, bool is_err, Error *err)
    772 {
    773  HlMessage hl_msg = KV_INITIAL_VALUE;
    774  for (size_t i = 0; i < chunks.size; i++) {
    775    VALIDATE_T("chunk", kObjectTypeArray, chunks.items[i].type, {
    776      goto free_exit;
    777    });
    778    Tuple(String, *HLGroupID) chunk = chunks.items[i].data.array;
    779    VALIDATE((chunk.size > 0 && chunk.size <= 2 && chunk.items[0].type == kObjectTypeString),
    780             "%s", "Invalid chunk: expected Array with 1 or 2 Strings", {
    781      goto free_exit;
    782    });
    783 
    784    String str = copy_string(chunk.items[0].data.string, NULL);
    785 
    786    int hl_id =
    787      chunk.size == 2 ? object_to_hl_id(chunk.items[1], "text highlight", err)
    788                      : is_err ? HLF_E
    789                               : 0;
    790    kv_push(hl_msg, ((HlMessageChunk){ .text = str, .hl_id = hl_id }));
    791  }
    792 
    793  return hl_msg;
    794 
    795 free_exit:
    796  hl_msg_free(hl_msg);
    797  return (HlMessage)KV_INITIAL_VALUE;
    798 }
    799 
    800 // see also nlua_pop_keydict for the lua specific implementation
    801 bool api_dict_to_keydict(void *retval, FieldHashfn hashy, Dict dict, Error *err)
    802 {
    803  for (size_t i = 0; i < dict.size; i++) {
    804    String k = dict.items[i].key;
    805    KeySetLink *field = hashy(k.data, k.size);
    806    if (!field) {
    807      api_set_error(err, kErrorTypeValidation, "Invalid key: '%.*s'", (int)k.size, k.data);
    808      return false;
    809    }
    810 
    811    if (field->opt_index >= 0) {
    812      OptKeySet *ks = (OptKeySet *)retval;
    813      ks->is_set_ |= (1ULL << field->opt_index);
    814    }
    815 
    816    char *mem = ((char *)retval + field->ptr_off);
    817    Object *value = &dict.items[i].value;
    818 
    819    if (field->type == kObjectTypeNil) {
    820      *(Object *)mem = *value;
    821    } else if (field->type == kObjectTypeInteger) {
    822      if (field->is_hlgroup) {
    823        int hl_id = 0;
    824        if (value->type != kObjectTypeNil) {
    825          hl_id = object_to_hl_id(*value, k.data, err);
    826          if (ERROR_SET(err)) {
    827            return false;
    828          }
    829        }
    830        *(Integer *)mem = hl_id;
    831      } else {
    832        VALIDATE_T(field->str, kObjectTypeInteger, value->type, {
    833          return false;
    834        });
    835 
    836        *(Integer *)mem = value->data.integer;
    837      }
    838    } else if (field->type == kObjectTypeFloat) {
    839      Float *val = (Float *)mem;
    840      if (value->type == kObjectTypeInteger) {
    841        *val = (Float)value->data.integer;
    842      } else {
    843        VALIDATE_T(field->str, kObjectTypeFloat, value->type, {
    844          return false;
    845        });
    846        *val = value->data.floating;
    847      }
    848    } else if (field->type == kObjectTypeBoolean) {
    849      // caller should check HAS_KEY to override the nil behavior, or GET_BOOL_OR_TRUE
    850      // to directly use true when nil
    851      *(Boolean *)mem = api_object_to_bool(*value, field->str, false, err);
    852      if (ERROR_SET(err)) {
    853        return false;
    854      }
    855    } else if (field->type == kObjectTypeString) {
    856      VALIDATE_T(field->str, kObjectTypeString, value->type, {
    857        return false;
    858      });
    859      *(String *)mem = value->data.string;
    860    } else if (field->type == kObjectTypeArray) {
    861      VALIDATE_T(field->str, kObjectTypeArray, value->type, {
    862        return false;
    863      });
    864      *(Array *)mem = value->data.array;
    865    } else if (field->type == kObjectTypeDict) {
    866      Dict *val = (Dict *)mem;
    867      // allow empty array as empty dict for lua (directly or via lua-client RPC)
    868      if (value->type == kObjectTypeArray && value->data.array.size == 0) {
    869        *val = (Dict)ARRAY_DICT_INIT;
    870      } else if (value->type == kObjectTypeDict) {
    871        *val = value->data.dict;
    872      } else {
    873        api_err_exp(err, field->str, api_typename((ObjectType)field->type),
    874                    api_typename(value->type));
    875        return false;
    876      }
    877    } else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow
    878               || field->type == kObjectTypeTabpage) {
    879      if (value->type == kObjectTypeInteger || value->type == (ObjectType)field->type) {
    880        *(handle_T *)mem = (handle_T)value->data.integer;
    881      } else {
    882        api_err_exp(err, field->str, api_typename((ObjectType)field->type),
    883                    api_typename(value->type));
    884        return false;
    885      }
    886    } else if (field->type == kObjectTypeLuaRef) {
    887      api_set_error(err, kErrorTypeValidation, "Invalid key: '%.*s' is only allowed from Lua",
    888                    (int)k.size, k.data);
    889      return false;
    890    } else {
    891      abort();
    892    }
    893  }
    894 
    895  return true;
    896 }
    897 
    898 Dict api_keydict_to_dict(void *value, KeySetLink *table, size_t max_size, Arena *arena)
    899 {
    900  Dict rv = arena_dict(arena, max_size);
    901  for (size_t i = 0; table[i].str; i++) {
    902    KeySetLink *field = &table[i];
    903    bool is_set = true;
    904    if (field->opt_index >= 0) {
    905      OptKeySet *ks = (OptKeySet *)value;
    906      is_set = ks->is_set_ & (1ULL << field->opt_index);
    907    }
    908 
    909    if (!is_set) {
    910      continue;
    911    }
    912 
    913    char *mem = ((char *)value + field->ptr_off);
    914    Object val = NIL;
    915 
    916    if (field->type == kObjectTypeNil) {
    917      val = *(Object *)mem;
    918    } else if (field->type == kObjectTypeInteger) {
    919      val = INTEGER_OBJ(*(Integer *)mem);
    920    } else if (field->type == kObjectTypeFloat) {
    921      val = FLOAT_OBJ(*(Float *)mem);
    922    } else if (field->type == kObjectTypeBoolean) {
    923      val = BOOLEAN_OBJ(*(Boolean *)mem);
    924    } else if (field->type == kObjectTypeString) {
    925      val = STRING_OBJ(*(String *)mem);
    926    } else if (field->type == kObjectTypeArray) {
    927      val = ARRAY_OBJ(*(Array *)mem);
    928    } else if (field->type == kObjectTypeDict) {
    929      val = DICT_OBJ(*(Dict *)mem);
    930    } else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow
    931               || field->type == kObjectTypeTabpage) {
    932      val.data.integer = *(handle_T *)mem;
    933      val.type = (ObjectType)field->type;
    934    } else if (field->type == kObjectTypeLuaRef) {
    935      // do nothing
    936    } else {
    937      abort();
    938    }
    939 
    940    PUT_C(rv, field->str, val);
    941  }
    942 
    943  return rv;
    944 }
    945 
    946 void api_luarefs_free_object(Object value)
    947 {
    948  // TODO(bfredl): this is more complicated than it needs to be.
    949  // we should be able to lock down more specifically where luarefs can be
    950  switch (value.type) {
    951  case kObjectTypeLuaRef:
    952    api_free_luaref(value.data.luaref);
    953    break;
    954 
    955  case kObjectTypeArray:
    956    api_luarefs_free_array(value.data.array);
    957    break;
    958 
    959  case kObjectTypeDict:
    960    api_luarefs_free_dict(value.data.dict);
    961    break;
    962 
    963  default:
    964    break;
    965  }
    966 }
    967 
    968 void api_luarefs_free_keydict(void *dict, KeySetLink *table)
    969 {
    970  for (size_t i = 0; table[i].str; i++) {
    971    char *mem = ((char *)dict + table[i].ptr_off);
    972    if (table[i].type == kObjectTypeNil) {
    973      api_luarefs_free_object(*(Object *)mem);
    974    } else if (table[i].type == kObjectTypeLuaRef) {
    975      api_free_luaref(*(LuaRef *)mem);
    976    } else if (table[i].type == kObjectTypeDict) {
    977      api_luarefs_free_dict(*(Dict *)mem);
    978    }
    979  }
    980 }
    981 
    982 void api_luarefs_free_array(Array value)
    983 {
    984  for (size_t i = 0; i < value.size; i++) {
    985    api_luarefs_free_object(value.items[i]);
    986  }
    987 }
    988 
    989 void api_luarefs_free_dict(Dict value)
    990 {
    991  for (size_t i = 0; i < value.size; i++) {
    992    api_luarefs_free_object(value.items[i].value);
    993  }
    994 }
    995 
    996 /// Set a named mark
    997 /// buffer and mark name must be validated already
    998 /// @param buffer     Buffer to set the mark on
    999 /// @param name       Mark name
   1000 /// @param line       Line number
   1001 /// @param col        Column/row number
   1002 /// @return true if the mark was set, else false
   1003 bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err)
   1004 {
   1005  buf = buf == NULL ? curbuf : buf;
   1006  // If line == 0 the marks is being deleted
   1007  bool res = false;
   1008  bool deleting = false;
   1009  if (line == 0) {
   1010    col = 0;
   1011    deleting = true;
   1012  } else {
   1013    if (col > MAXCOL) {
   1014      api_set_error(err, kErrorTypeValidation, "Column value outside range");
   1015      return res;
   1016    }
   1017    if (line < 1 || line > buf->b_ml.ml_line_count) {
   1018      api_set_error(err, kErrorTypeValidation, "Line value outside range");
   1019      return res;
   1020    }
   1021  }
   1022  assert(INT32_MIN <= line && line <= INT32_MAX);
   1023  pos_T pos = { (linenr_T)line, (int)col, 0 };
   1024  res = setmark_pos(*name.data, &pos, buf->handle, NULL);
   1025  if (!res) {
   1026    if (deleting) {
   1027      api_set_error(err, kErrorTypeException,
   1028                    "Failed to delete named mark: %c", *name.data);
   1029    } else {
   1030      api_set_error(err, kErrorTypeException,
   1031                    "Failed to set named mark: %c", *name.data);
   1032    }
   1033  }
   1034  return res;
   1035 }
   1036 
   1037 /// Get default statusline highlight for window
   1038 const char *get_default_stl_hl(win_T *wp, bool use_winbar, int stc_hl_id)
   1039 {
   1040  if (wp == NULL) {
   1041    return "TabLineFill";
   1042  } else if (use_winbar) {
   1043    return (wp == curwin) ? "WinBar" : "WinBarNC";
   1044  } else if (stc_hl_id > 0) {
   1045    return syn_id2name(stc_hl_id);
   1046  } else {
   1047    return (wp == curwin) ? "StatusLine" : "StatusLineNC";
   1048  }
   1049 }
   1050 
   1051 /// Sets sctx for API calls.
   1052 ///
   1053 /// @param channel_id  api client id to determine if it's a internal or RPC call.
   1054 ///
   1055 /// @return  previous value of current_sctx. To be used later for restoring sctx.
   1056 sctx_T api_set_sctx(uint64_t channel_id)
   1057 {
   1058  sctx_T old_current_sctx = current_sctx;
   1059  // The script context is already properly set when calling an API from Vimscript.
   1060  if (channel_id != VIML_INTERNAL_CALL) {
   1061    current_sctx.sc_lnum = 0;
   1062    if (channel_id == LUA_INTERNAL_CALL) {
   1063      // When the current script is a Lua script, don't override sc_sid.
   1064      if (!script_is_lua(current_sctx.sc_sid)) {
   1065        current_sctx.sc_sid = SID_LUA;
   1066      }
   1067    } else {
   1068      current_sctx.sc_sid = SID_API_CLIENT;
   1069      current_sctx.sc_chan = channel_id;
   1070    }
   1071  }
   1072  return old_current_sctx;
   1073 }