diff options
Diffstat (limited to 'src/nvim/api/private/helpers.c')
-rw-r--r-- | src/nvim/api/private/helpers.c | 688 |
1 files changed, 507 insertions, 181 deletions
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index c88bf2127a..82c9a1da67 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1,3 +1,6 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + #include <assert.h> #include <inttypes.h> #include <stdbool.h> @@ -7,18 +10,24 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/handle.h" +#include "nvim/msgpack_rpc/helpers.h" #include "nvim/ascii.h" +#include "nvim/assert.h" #include "nvim/vim.h" #include "nvim/buffer.h" #include "nvim/window.h" +#include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" #include "nvim/map_defs.h" #include "nvim/map.h" #include "nvim/option.h" #include "nvim/option_defs.h" -#include "nvim/eval/typval_encode.h" +#include "nvim/version.h" #include "nvim/lib/kvec.h" +#include "nvim/getchar.h" +#include "nvim/ui.h" /// Helper structure for vim_to_object typedef struct { @@ -27,9 +36,75 @@ typedef struct { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/helpers.c.generated.h" +# include "api/private/funcs_metadata.generated.h" +# include "api/private/ui_events_metadata.generated.h" #endif +/// Start block that may cause VimL exceptions while evaluating another code +/// +/// Used when caller is supposed to be operating when other VimL code is being +/// processed and that “other VimL code” must not be affected. +/// +/// @param[out] tstate Location where try state should be saved. +void try_enter(TryState *const tstate) +{ + // TODO(ZyX-I): Check whether try_enter()/try_leave() may use + // enter_cleanup()/leave_cleanup(). Or + // save_dbg_stuff()/restore_dbg_stuff(). + *tstate = (TryState) { + .current_exception = current_exception, + .msg_list = (const struct msglist *const *)msg_list, + .private_msg_list = NULL, + .trylevel = trylevel, + .got_int = got_int, + .need_rethrow = need_rethrow, + .did_emsg = did_emsg, + }; + msg_list = &tstate->private_msg_list; + current_exception = NULL; + trylevel = 1; + got_int = false; + need_rethrow = false; + did_emsg = false; +} + +/// End try block, set the error message if any and restore previous state +/// +/// @warning Return is consistent with most functions (false on error), not with +/// try_end (true on error). +/// +/// @param[in] tstate Previous state to restore. +/// @param[out] err Location where error should be saved. +/// +/// @return false if error occurred, true otherwise. +bool try_leave(const TryState *const tstate, Error *const err) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + const bool ret = !try_end(err); + assert(trylevel == 0); + assert(!need_rethrow); + assert(!got_int); + assert(!did_emsg); + assert(msg_list == &tstate->private_msg_list); + assert(*msg_list == NULL); + assert(current_exception == NULL); + msg_list = (struct msglist **)tstate->msg_list; + current_exception = tstate->current_exception; + trylevel = tstate->trylevel; + got_int = tstate->got_int; + need_rethrow = tstate->need_rethrow; + did_emsg = tstate->did_emsg; + return ret; +} + /// Start block that may cause vimscript exceptions +/// +/// Each try_start() call should be mirrored by try_end() call. +/// +/// To be used as a replacement of `:try … catch … endtry` in C code, in cases +/// when error flag could not already be set. If there may be pending error +/// state at the time try_start() is executed which needs to be preserved, +/// try_enter()/try_leave() pair should be used instead. void try_start(void) { ++trylevel; @@ -42,20 +117,20 @@ void try_start(void) /// @return true if an error occurred bool try_end(Error *err) { - --trylevel; + // Note: all globals manipulated here should be saved/restored in + // try_enter/try_leave. + trylevel--; - // Without this it stops processing all subsequent VimL commands and - // generates strange error messages if I e.g. try calling Test() in a - // cycle + // Set by emsg(), affects aborting(). See also enter_cleanup(). did_emsg = false; if (got_int) { - if (did_throw) { + if (current_exception) { // If we got an interrupt, discard the current exception discard_current_exception(); } - api_set_error(err, Exception, _("Keyboard interrupt")); + api_set_error(err, kErrorTypeException, "Keyboard interrupt"); got_int = false; } else if (msg_list != NULL && *msg_list != NULL) { int should_free; @@ -63,19 +138,18 @@ bool try_end(Error *err) ET_ERROR, NULL, &should_free); - xstrlcpy(err->msg, msg, sizeof(err->msg)); - err->set = true; + api_set_error(err, kErrorTypeException, "%s", msg); free_global_msglist(); if (should_free) { xfree(msg); } - } else if (did_throw) { - api_set_error(err, Exception, "%s", current_exception->value); + } else if (current_exception) { + api_set_error(err, kErrorTypeException, "%s", current_exception->value); discard_current_exception(); } - return err->set; + return ERROR_SET(err); } /// Recursively expands a vimscript value in a dict @@ -85,18 +159,17 @@ bool try_end(Error *err) /// @param[out] err Details of an error that may have occurred Object dict_get_value(dict_T *dict, String key, Error *err) { - hashitem_T *hi = hash_find(&dict->dv_hashtab, (uint8_t *) key.data); + dictitem_T *const di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size); - if (HASHITEM_EMPTY(hi)) { - api_set_error(err, Validation, _("Key not found")); - return (Object) OBJECT_INIT; + if (di == NULL) { + api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data); + return (Object)OBJECT_INIT; } - dictitem_T *di = dict_lookup(hi); return vim_to_object(&di->di_tv); } -/// Set a value in a dict. Objects are recursively expanded into their +/// Set a value in a scope dict. Objects are recursively expanded into their /// vimscript equivalents. /// /// @param dict The vimscript dict @@ -104,42 +177,50 @@ Object dict_get_value(dict_T *dict, String key, Error *err) /// @param value The new value /// @param del Delete key in place of setting it. Argument `value` is ignored in /// this case. +/// @param retval If true the old value will be converted and returned. /// @param[out] err Details of an error that may have occurred -/// @return the old value, if any -Object dict_set_value(dict_T *dict, String key, Object value, bool del, - Error *err) +/// @return The old value if `retval` is true and the key was present, else NIL +Object dict_set_var(dict_T *dict, String key, Object value, bool del, + bool retval, Error *err) { Object rv = OBJECT_INIT; + dictitem_T *di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size); - if (dict->dv_lock) { - api_set_error(err, Exception, _("Dictionary is locked")); + if (di != NULL) { + if (di->di_flags & DI_FLAGS_RO) { + api_set_error(err, kErrorTypeException, "Key is read-only: %s", key.data); + return rv; + } else if (di->di_flags & DI_FLAGS_LOCK) { + api_set_error(err, kErrorTypeException, "Key is locked: %s", key.data); + return rv; + } else if (del && (di->di_flags & DI_FLAGS_FIX)) { + api_set_error(err, kErrorTypeException, "Key is fixed: %s", key.data); + return rv; + } + } else if (dict->dv_lock) { + api_set_error(err, kErrorTypeException, "Dictionary is locked"); return rv; - } - - if (key.size == 0) { - api_set_error(err, Validation, _("Empty dictionary keys aren't allowed")); + } else if (key.size == 0) { + api_set_error(err, kErrorTypeValidation, "Key name is empty"); return rv; - } - - if (key.size > INT_MAX) { - api_set_error(err, Validation, _("Key length is too high")); + } else if (key.size > INT_MAX) { + api_set_error(err, kErrorTypeValidation, "Key name is too long"); return rv; } - dictitem_T *di = dict_find(dict, (uint8_t *)key.data, (int)key.size); - if (del) { // Delete the key if (di == NULL) { // Doesn't exist, fail - api_set_error(err, Validation, _("Key \"%s\" doesn't exist"), key.data); + api_set_error(err, kErrorTypeValidation, "Key not found: %s", + key.data); } else { // Return the old value - rv = vim_to_object(&di->di_tv); + if (retval) { + rv = vim_to_object(&di->di_tv); + } // Delete the entry - hashitem_T *hi = hash_find(&dict->dv_hashtab, di->di_key); - hash_remove(&dict->dv_hashtab, hi); - dictitem_free(di); + tv_dict_item_remove(dict, di); } } else { // Update the key @@ -152,18 +233,20 @@ Object dict_set_value(dict_T *dict, String key, Object value, bool del, if (di == NULL) { // Need to create an entry - di = dictitem_alloc((uint8_t *) key.data); - dict_add(dict, di); + di = tv_dict_item_alloc_len(key.data, key.size); + tv_dict_add(dict, di); } else { // Return the old value - rv = vim_to_object(&di->di_tv); - clear_tv(&di->di_tv); + if (retval) { + rv = vim_to_object(&di->di_tv); + } + tv_clear(&di->di_tv); } // Update the value - copy_tv(&tv, &di->di_tv); + tv_copy(&tv, &di->di_tv); // Clear the temporary variable - clear_tv(&tv); + tv_clear(&tv); } return rv; @@ -182,7 +265,7 @@ Object get_option_from(void *from, int type, String name, Error *err) Object rv = OBJECT_INIT; if (name.size == 0) { - api_set_error(err, Validation, _("Empty option name")); + api_set_error(err, kErrorTypeValidation, "Empty option name"); return rv; } @@ -193,9 +276,7 @@ Object get_option_from(void *from, int type, String name, Error *err) type, from); if (!flags) { - api_set_error(err, - Validation, - _("Invalid option name \"%s\""), + api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'", name.data); return rv; } @@ -212,15 +293,14 @@ Object get_option_from(void *from, int type, String name, Error *err) rv.data.string.data = stringval; rv.data.string.size = strlen(stringval); } else { - api_set_error(err, - Exception, - _("Unable to get value for option \"%s\""), + api_set_error(err, kErrorTypeException, + "Failed to get value for option '%s'", name.data); } } else { api_set_error(err, - Exception, - _("Unknown type for option \"%s\""), + kErrorTypeException, + "Unknown type for option '%s'", name.data); } @@ -234,35 +314,32 @@ Object get_option_from(void *from, int type, String name, Error *err) /// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF` /// @param name The option name /// @param[out] err Details of an error that may have occurred -void set_option_to(void *to, int type, String name, Object value, Error *err) +void set_option_to(uint64_t channel_id, void *to, int type, + String name, Object value, Error *err) { if (name.size == 0) { - api_set_error(err, Validation, _("Empty option name")); + api_set_error(err, kErrorTypeValidation, "Empty option name"); return; } int flags = get_option_value_strict(name.data, NULL, NULL, type, to); if (flags == 0) { - api_set_error(err, - Validation, - _("Invalid option name \"%s\""), + api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'", name.data); return; } if (value.type == kObjectTypeNil) { if (type == SREQ_GLOBAL) { - api_set_error(err, - Exception, - _("Unable to unset option \"%s\""), + api_set_error(err, kErrorTypeException, "Cannot unset option '%s'", name.data); return; } else if (!(flags & SOPT_GLOBAL)) { api_set_error(err, - Exception, - _("Cannot unset option \"%s\" " - "because it doesn't have a global value"), + kErrorTypeException, + "Cannot unset option '%s' " + "because it doesn't have a global value", name.data); return; } else { @@ -271,69 +348,74 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) } } - int opt_flags = (type ? OPT_LOCAL : OPT_GLOBAL); + int numval = 0; + char *stringval = NULL; if (flags & SOPT_BOOL) { if (value.type != kObjectTypeBoolean) { api_set_error(err, - Validation, - _("Option \"%s\" requires a boolean value"), + kErrorTypeValidation, + "Option '%s' requires a Boolean value", name.data); return; } - bool val = value.data.boolean; - set_option_value_for(name.data, val, NULL, opt_flags, type, to, err); + numval = value.data.boolean; } else if (flags & SOPT_NUM) { if (value.type != kObjectTypeInteger) { - api_set_error(err, - Validation, - _("Option \"%s\" requires an integer value"), + api_set_error(err, kErrorTypeValidation, + "Option '%s' requires an integer value", name.data); return; } if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) { - api_set_error(err, - Validation, - _("Value for option \"%s\" is outside range"), + api_set_error(err, kErrorTypeValidation, + "Value for option '%s' is out of range", name.data); return; } - int val = (int) value.data.integer; - set_option_value_for(name.data, val, NULL, opt_flags, type, to, err); + numval = (int)value.data.integer; } else { if (value.type != kObjectTypeString) { - api_set_error(err, - Validation, - _("Option \"%s\" requires a string value"), + api_set_error(err, kErrorTypeValidation, + "Option '%s' requires a string value", name.data); return; } - set_option_value_for(name.data, 0, value.data.string.data, - opt_flags, type, to, err); + stringval = (char *)value.data.string.data; } + + const scid_T save_current_SID = current_SID; + current_SID = channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; + current_channel_id = channel_id; + + const int opt_flags = (type == SREQ_GLOBAL) ? OPT_GLOBAL : OPT_LOCAL; + set_option_value_for(name.data, numval, stringval, + opt_flags, type, to, err); + + current_SID = save_current_SID; } #define TYPVAL_ENCODE_ALLOW_SPECIALS false -#define TYPVAL_ENCODE_CONV_NIL() \ +#define TYPVAL_ENCODE_CONV_NIL(tv) \ kv_push(edata->stack, NIL) -#define TYPVAL_ENCODE_CONV_BOOL(num) \ +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ kv_push(edata->stack, BOOLEAN_OBJ((Boolean)(num))) -#define TYPVAL_ENCODE_CONV_NUMBER(num) \ +#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ kv_push(edata->stack, INTEGER_OBJ((Integer)(num))) #define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER -#define TYPVAL_ENCODE_CONV_FLOAT(flt) \ - kv_push(edata->stack, FLOATING_OBJ((Float)(flt))) +#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ + kv_push(edata->stack, FLOAT_OBJ((Float)(flt))) -#define TYPVAL_ENCODE_CONV_STRING(str, len) \ +#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \ do { \ const size_t len_ = (size_t)(len); \ const char *const str_ = (const char *)(str); \ @@ -346,16 +428,23 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) #define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING -#define TYPVAL_ENCODE_CONV_EXT_STRING(str, len, type) \ - TYPVAL_ENCODE_CONV_NIL() +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \ + TYPVAL_ENCODE_CONV_NIL(tv) + +#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ + do { \ + TYPVAL_ENCODE_CONV_NIL(tv); \ + goto typval_encode_stop_converting_one_item; \ + } while (0) -#define TYPVAL_ENCODE_CONV_FUNC(fun) \ - TYPVAL_ENCODE_CONV_NIL() +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_END(tv) -#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ +#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \ kv_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = 0, .size = 0 }))) -#define TYPVAL_ENCODE_CONV_EMPTY_DICT() \ +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ kv_push(edata->stack, \ DICTIONARY_OBJ(((Dictionary) { .capacity = 0, .size = 0 }))) @@ -363,17 +452,18 @@ static inline void typval_encode_list_start(EncodedData *const edata, const size_t len) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL { - const Object obj = OBJECT_INIT; kv_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = len, .size = 0, - .items = xmalloc(len * sizeof(*obj.data.array.items)), + .items = xmalloc(len * sizeof(*((Object)OBJECT_INIT).data.array.items)), }))); } -#define TYPVAL_ENCODE_CONV_LIST_START(len) \ +#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ typval_encode_list_start(edata, (size_t)(len)) +#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) + static inline void typval_encode_between_list_items(EncodedData *const edata) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL { @@ -384,7 +474,7 @@ static inline void typval_encode_between_list_items(EncodedData *const edata) list->data.array.items[list->data.array.size++] = item; } -#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS() \ +#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \ typval_encode_between_list_items(edata) static inline void typval_encode_list_end(EncodedData *const edata) @@ -397,25 +487,27 @@ static inline void typval_encode_list_end(EncodedData *const edata) #endif } -#define TYPVAL_ENCODE_CONV_LIST_END() \ +#define TYPVAL_ENCODE_CONV_LIST_END(tv) \ typval_encode_list_end(edata) static inline void typval_encode_dict_start(EncodedData *const edata, const size_t len) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL { - const Object obj = OBJECT_INIT; kv_push(edata->stack, DICTIONARY_OBJ(((Dictionary) { .capacity = len, .size = 0, - .items = xmalloc(len * sizeof(*obj.data.dictionary.items)), + .items = xmalloc(len * sizeof( + *((Object)OBJECT_INIT).data.dictionary.items)), }))); } -#define TYPVAL_ENCODE_CONV_DICT_START(len) \ +#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ typval_encode_dict_start(edata, (size_t)(len)) -#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(label, kv_pair) +#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) + +#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, kv_pair) static inline void typval_encode_after_key(EncodedData *const edata) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL @@ -434,7 +526,7 @@ static inline void typval_encode_after_key(EncodedData *const edata) } } -#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY() \ +#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) \ typval_encode_after_key(edata) static inline void typval_encode_between_dict_items(EncodedData *const edata) @@ -447,7 +539,7 @@ static inline void typval_encode_between_dict_items(EncodedData *const edata) dict->data.dictionary.items[dict->data.dictionary.size++].value = val; } -#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS() \ +#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \ typval_encode_between_dict_items(edata) static inline void typval_encode_dict_end(EncodedData *const edata) @@ -460,31 +552,44 @@ static inline void typval_encode_dict_end(EncodedData *const edata) #endif } -#define TYPVAL_ENCODE_CONV_DICT_END() \ +#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \ typval_encode_dict_end(edata) #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ - TYPVAL_ENCODE_CONV_NIL() - -TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, object, EncodedData *const, edata) + TYPVAL_ENCODE_CONV_NIL(val) + +#define TYPVAL_ENCODE_SCOPE static +#define TYPVAL_ENCODE_NAME object +#define TYPVAL_ENCODE_FIRST_ARG_TYPE EncodedData *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME edata +#include "nvim/eval/typval_encode.c.h" +#undef TYPVAL_ENCODE_SCOPE +#undef TYPVAL_ENCODE_NAME +#undef TYPVAL_ENCODE_FIRST_ARG_TYPE +#undef TYPVAL_ENCODE_FIRST_ARG_NAME #undef TYPVAL_ENCODE_CONV_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT -#undef TYPVAL_ENCODE_CONV_FUNC +#undef TYPVAL_ENCODE_CONV_FUNC_START +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF +#undef TYPVAL_ENCODE_CONV_FUNC_END #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_LIST_START +#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT #undef TYPVAL_ENCODE_CONV_NIL #undef TYPVAL_ENCODE_CONV_BOOL #undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER #undef TYPVAL_ENCODE_CONV_DICT_START +#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START #undef TYPVAL_ENCODE_CONV_DICT_END #undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY #undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS -#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK +#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK #undef TYPVAL_ENCODE_CONV_LIST_END #undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS #undef TYPVAL_ENCODE_CONV_RECURSE @@ -498,7 +603,10 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, object, EncodedData *const, edata) Object vim_to_object(typval_T *obj) { EncodedData edata = { .stack = KV_INITIAL_VALUE }; - encode_vim_to_object(&edata, obj, "vim_to_object argument"); + const int evo_ret = encode_vim_to_object(&edata, obj, + "vim_to_object argument"); + (void)evo_ret; + assert(evo_ret == OK); Object ret = kv_A(edata.stack, 0); assert(kv_size(edata.stack) == 1); kv_destroy(edata.stack); @@ -507,37 +615,65 @@ Object vim_to_object(typval_T *obj) buf_T *find_buffer_by_handle(Buffer buffer, Error *err) { + if (buffer == 0) { + return curbuf; + } + buf_T *rv = handle_get_buffer(buffer); if (!rv) { - api_set_error(err, Validation, _("Invalid buffer id")); + api_set_error(err, kErrorTypeValidation, "Invalid buffer id"); } return rv; } -win_T * find_window_by_handle(Window window, Error *err) +win_T *find_window_by_handle(Window window, Error *err) { + if (window == 0) { + return curwin; + } + win_T *rv = handle_get_window(window); if (!rv) { - api_set_error(err, Validation, _("Invalid window id")); + api_set_error(err, kErrorTypeValidation, "Invalid window id"); } return rv; } -tabpage_T * find_tab_by_handle(Tabpage tabpage, Error *err) +tabpage_T *find_tab_by_handle(Tabpage tabpage, Error *err) { + if (tabpage == 0) { + return curtab; + } + tabpage_T *rv = handle_get_tabpage(tabpage); if (!rv) { - api_set_error(err, Validation, _("Invalid tabpage id")); + api_set_error(err, kErrorTypeValidation, "Invalid tabpage id"); } return rv; } +/// Allocates a String consisting of a single char. Does not support multibyte +/// characters. The resulting string is also NUL-terminated, to facilitate +/// interoperating with code using C strings. +/// +/// @param char the char to convert +/// @return the resulting String, if the input char was NUL, an +/// empty String is returned +String cchar_to_string(char c) +{ + char buf[] = { c, NUL }; + return (String){ + .data = xmemdupz(buf, 1), + .size = (c != NUL) ? 1 : 0 + }; +} + /// Copies a C string into a String (binary safe string, characters + length). /// The resulting string is also NUL-terminated, to facilitate interoperating /// with code using C strings. @@ -548,16 +684,33 @@ tabpage_T * find_tab_by_handle(Tabpage tabpage, Error *err) String cstr_to_string(const char *str) { if (str == NULL) { - return (String) STRING_INIT; + return (String)STRING_INIT; } size_t len = strlen(str); - return (String) { - .data = xmemdupz(str, len), - .size = len + return (String){ + .data = xmemdupz(str, len), + .size = len, }; } +/// Copies buffer to an allocated String. +/// The resulting string is also NUL-terminated, to facilitate interoperating +/// with code using C strings. +/// +/// @param buf the buffer to copy +/// @param size length of the buffer +/// @return the resulting String, if the input string was NULL, an +/// empty String is returned +String cbuf_to_string(const char *buf, size_t size) + FUNC_ATTR_NONNULL_ALL +{ + return (String){ + .data = xmemdupz(buf, size), + .size = size + }; +} + /// Creates a String using the given C string. Unlike /// cstr_to_string this function DOES NOT copy the C string. /// @@ -567,15 +720,58 @@ String cstr_to_string(const char *str) String cstr_as_string(char *str) FUNC_ATTR_PURE { if (str == NULL) { - return (String) STRING_INIT; + return (String)STRING_INIT; } - return (String) {.data = str, .size = strlen(str)}; + return (String){ .data = str, .size = strlen(str) }; } +/// Collects `n` buffer lines into array `l`, optionally replacing newlines +/// with NUL. +/// +/// @param buf Buffer to get lines from +/// @param n Number of lines to collect +/// @param replace_nl Replace newlines ("\n") with NUL +/// @param start Line number to start from +/// @param[out] l Lines are copied here +/// @param err[out] Error, if any +/// @return true unless `err` was set +bool buf_collect_lines(buf_T *buf, size_t n, int64_t start, bool replace_nl, + Array *l, Error *err) +{ + for (size_t i = 0; i < n; i++) { + int64_t lnum = start + (int64_t)i; + + if (lnum >= MAXLNUM) { + if (err != NULL) { + api_set_error(err, kErrorTypeValidation, "Line index is too high"); + } + return false; + } + + const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false); + Object str = STRING_OBJ(cstr_to_string(bufstr)); + + if (replace_nl) { + // Vim represents NULs as NLs, but this may confuse clients. + strchrsub(str.data.string.data, '\n', '\0'); + } + + l->items[i] = str; + } + + return true; +} + +/// Converts from type Object to a VimL value. +/// +/// @param obj Object to convert from. +/// @param tv Conversion result is placed here. On failure member v_type is +/// set to VAR_UNKNOWN (no allocation was made for this variable). +/// returns true if conversion is successful, otherwise false. bool object_to_vim(Object obj, typval_T *tv, Error *err) { tv->v_type = VAR_UNKNOWN; - tv->v_lock = 0; + tv->v_lock = VAR_UNLOCKED; switch (obj.type) { case kObjectTypeNil: @@ -592,13 +788,10 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) case kObjectTypeWindow: case kObjectTypeTabpage: case kObjectTypeInteger: - if (obj.data.integer > INT_MAX || obj.data.integer < INT_MIN) { - api_set_error(err, Validation, _("Integer value outside range")); - return false; - } - + STATIC_ASSERT(sizeof(obj.data.integer) <= sizeof(varnumber_T), + "Integer size must be <= VimL number size"); tv->v_type = VAR_NUMBER; - tv->vval.v_number = (int)obj.data.integer; + tv->vval.v_number = (varnumber_T)obj.data.integer; break; case kObjectTypeFloat: @@ -616,56 +809,59 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) } break; - case kObjectTypeArray: - tv->v_type = VAR_LIST; - tv->vval.v_list = list_alloc(); + case kObjectTypeArray: { + list_T *const list = tv_list_alloc((ptrdiff_t)obj.data.array.size); for (uint32_t i = 0; i < obj.data.array.size; i++) { Object item = obj.data.array.items[i]; - listitem_T *li = listitem_alloc(); + typval_T li_tv; - if (!object_to_vim(item, &li->li_tv, err)) { - // cleanup - listitem_free(li); - list_free(tv->vval.v_list, true); + if (!object_to_vim(item, &li_tv, err)) { + tv_list_free(list); return false; } - list_append(tv->vval.v_list, li); + tv_list_append_owned_tv(list, li_tv); } - tv->vval.v_list->lv_refcount++; + tv_list_ref(list); + + tv->v_type = VAR_LIST; + tv->vval.v_list = list; break; + } - case kObjectTypeDictionary: - tv->v_type = VAR_DICT; - tv->vval.v_dict = dict_alloc(); + case kObjectTypeDictionary: { + dict_T *const dict = tv_dict_alloc(); for (uint32_t i = 0; i < obj.data.dictionary.size; i++) { KeyValuePair item = obj.data.dictionary.items[i]; String key = item.key; if (key.size == 0) { - api_set_error(err, - Validation, - _("Empty dictionary keys aren't allowed")); + api_set_error(err, kErrorTypeValidation, + "Empty dictionary keys aren't allowed"); // cleanup - dict_free(tv->vval.v_dict, true); + tv_dict_free(dict); return false; } - dictitem_T *di = dictitem_alloc((uint8_t *) key.data); + dictitem_T *const di = tv_dict_item_alloc(key.data); if (!object_to_vim(item.value, &di->di_tv, err)) { // cleanup - dictitem_free(di); - dict_free(tv->vval.v_dict, true); + tv_dict_item_free(di); + tv_dict_free(dict); return false; } - dict_add(tv->vval.v_dict, di); + tv_dict_add(dict, di); } - tv->vval.v_dict->dv_refcount++; + dict->dv_refcount++; + + tv->v_type = VAR_DICT; + tv->vval.v_dict = dict; break; + } default: abort(); } @@ -730,12 +926,25 @@ void api_free_dictionary(Dictionary value) xfree(value.items); } +void api_clear_error(Error *value) + FUNC_ATTR_NONNULL_ALL +{ + if (!ERROR_SET(value)) { + return; + } + xfree(value->msg); + value->msg = NULL; + value->type = kErrorTypeNone; +} + Dictionary api_metadata(void) { static Dictionary metadata = ARRAY_DICT_INIT; if (!metadata.size) { - msgpack_rpc_init_function_metadata(&metadata); + PUT(metadata, "version", DICTIONARY_OBJ(version_dict())); + init_function_metadata(&metadata); + init_ui_event_metadata(&metadata); init_error_type_metadata(&metadata); init_type_metadata(&metadata); } @@ -743,6 +952,44 @@ Dictionary api_metadata(void) return copy_object(DICTIONARY_OBJ(metadata)).data.dictionary; } +static void init_function_metadata(Dictionary *metadata) +{ + msgpack_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + if (msgpack_unpack_next(&unpacked, + (const char *)funcs_metadata, + sizeof(funcs_metadata), + NULL) != MSGPACK_UNPACK_SUCCESS) { + abort(); + } + Object functions; + msgpack_rpc_to_object(&unpacked.data, &functions); + msgpack_unpacked_destroy(&unpacked); + PUT(*metadata, "functions", functions); +} + +static void init_ui_event_metadata(Dictionary *metadata) +{ + msgpack_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + if (msgpack_unpack_next(&unpacked, + (const char *)ui_events_metadata, + sizeof(ui_events_metadata), + NULL) != MSGPACK_UNPACK_SUCCESS) { + abort(); + } + Object ui_events; + msgpack_rpc_to_object(&unpacked.data, &ui_events); + msgpack_unpacked_destroy(&unpacked); + PUT(*metadata, "ui_events", ui_events); + Array ui_options = ARRAY_DICT_INIT; + ADD(ui_options, STRING_OBJ(cstr_to_string("rgb"))); + for (UIExtension i = 0; i < kUIExtCount; i++) { + ADD(ui_options, STRING_OBJ(cstr_to_string(ui_ext_names[i]))); + } + PUT(*metadata, "ui_options", ARRAY_OBJ(ui_options)); +} + static void init_error_type_metadata(Dictionary *metadata) { Dictionary types = ARRAY_DICT_INIT; @@ -758,18 +1005,25 @@ static void init_error_type_metadata(Dictionary *metadata) PUT(*metadata, "error_types", DICTIONARY_OBJ(types)); } + static void init_type_metadata(Dictionary *metadata) { Dictionary types = ARRAY_DICT_INIT; Dictionary buffer_metadata = ARRAY_DICT_INIT; - PUT(buffer_metadata, "id", INTEGER_OBJ(kObjectTypeBuffer)); + PUT(buffer_metadata, "id", + INTEGER_OBJ(kObjectTypeBuffer - EXT_OBJECT_TYPE_SHIFT)); + PUT(buffer_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_buf_"))); Dictionary window_metadata = ARRAY_DICT_INIT; - PUT(window_metadata, "id", INTEGER_OBJ(kObjectTypeWindow)); + PUT(window_metadata, "id", + INTEGER_OBJ(kObjectTypeWindow - EXT_OBJECT_TYPE_SHIFT)); + PUT(window_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_win_"))); Dictionary tabpage_metadata = ARRAY_DICT_INIT; - PUT(tabpage_metadata, "id", INTEGER_OBJ(kObjectTypeTabpage)); + PUT(tabpage_metadata, "id", + INTEGER_OBJ(kObjectTypeTabpage - EXT_OBJECT_TYPE_SHIFT)); + PUT(tabpage_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_tabpage_"))); PUT(types, "Buffer", DICTIONARY_OBJ(buffer_metadata)); PUT(types, "Window", DICTIONARY_OBJ(window_metadata)); @@ -778,6 +1032,34 @@ static void init_type_metadata(Dictionary *metadata) PUT(*metadata, "types", DICTIONARY_OBJ(types)); } +String copy_string(String str) +{ + if (str.data != NULL) { + return (String){ .data = xmemdupz(str.data, str.size), .size = str.size }; + } else { + return (String)STRING_INIT; + } +} + +Array copy_array(Array array) +{ + Array rv = ARRAY_DICT_INIT; + for (size_t i = 0; i < array.size; i++) { + ADD(rv, copy_object(array.items[i])); + } + return rv; +} + +Dictionary copy_dictionary(Dictionary dict) +{ + Dictionary rv = ARRAY_DICT_INIT; + for (size_t i = 0; i < dict.size; i++) { + KeyValuePair item = dict.items[i]; + PUT(rv, item.key.data, copy_object(item.value)); + } + return rv; +} + /// Creates a deep clone of an object Object copy_object(Object obj) { @@ -789,23 +1071,13 @@ Object copy_object(Object obj) return obj; case kObjectTypeString: - return STRING_OBJ(cstr_to_string(obj.data.string.data)); + return STRING_OBJ(copy_string(obj.data.string)); - case kObjectTypeArray: { - Array rv = ARRAY_DICT_INIT; - for (size_t i = 0; i < obj.data.array.size; i++) { - ADD(rv, copy_object(obj.data.array.items[i])); - } - return ARRAY_OBJ(rv); - } + case kObjectTypeArray: + return ARRAY_OBJ(copy_array(obj.data.array)); case kObjectTypeDictionary: { - Dictionary rv = ARRAY_DICT_INIT; - for (size_t i = 0; i < obj.data.dictionary.size; i++) { - KeyValuePair item = obj.data.dictionary.items[i]; - PUT(rv, item.key.data, copy_object(item.value)); - } - return DICTIONARY_OBJ(rv); + return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary)); } default: abort(); @@ -822,7 +1094,7 @@ static void set_option_value_for(char *key, { win_T *save_curwin = NULL; tabpage_T *save_curtab = NULL; - buf_T *save_curbuf = NULL; + bufref_T save_curbuf = { NULL, 0, 0 }; try_start(); switch (opt_type) @@ -835,8 +1107,8 @@ static void set_option_value_for(char *key, return; } api_set_error(err, - Exception, - _("Problem while switching windows")); + kErrorTypeException, + "Problem while switching windows"); return; } set_option_value_err(key, numval, stringval, opt_flags, err); @@ -845,14 +1117,14 @@ static void set_option_value_for(char *key, case SREQ_BUF: switch_buffer(&save_curbuf, (buf_T *)from); set_option_value_err(key, numval, stringval, opt_flags, err); - restore_buffer(save_curbuf); + restore_buffer(&save_curbuf); break; case SREQ_GLOBAL: set_option_value_err(key, numval, stringval, opt_flags, err); break; } - if (err->set) { + if (ERROR_SET(err)) { return; } @@ -868,15 +1140,69 @@ static void set_option_value_err(char *key, { char *errmsg; - if ((errmsg = (char *)set_option_value((uint8_t *)key, - numval, - (uint8_t *)stringval, - opt_flags))) - { + if ((errmsg = set_option_value(key, numval, stringval, opt_flags))) { if (try_end(err)) { return; } - api_set_error(err, Exception, "%s", errmsg); + api_set_error(err, kErrorTypeException, "%s", errmsg); } } + +void api_set_error(Error *err, ErrorType errType, const char *format, ...) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PRINTF(3, 4) +{ + assert(kErrorTypeNone != errType); + va_list args1; + va_list args2; + va_start(args1, format); + va_copy(args2, args1); + int len = vsnprintf(NULL, 0, format, args1); + va_end(args1); + assert(len >= 0); + // Limit error message to 1 MB. + size_t bufsize = MIN((size_t)len + 1, 1024 * 1024); + err->msg = xmalloc(bufsize); + vsnprintf(err->msg, bufsize, format, args2); + va_end(args2); + + err->type = errType; +} + +/// Get an array containing dictionaries describing mappings +/// based on mode and buffer id +/// +/// @param mode The abbreviation for the mode +/// @param buf The buffer to get the mapping array. NULL for global +/// @returns Array of maparg()-like dictionaries describing mappings +ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) +{ + Array mappings = ARRAY_DICT_INIT; + dict_T *const dict = tv_dict_alloc(); + + // Convert the string mode to the integer mode + // that is stored within each mapblock + char_u *p = (char_u *)mode.data; + int int_mode = get_map_mode(&p, 0); + + // Determine the desired buffer value + long buffer_value = (buf == NULL) ? 0 : buf->handle; + + for (int i = 0; i < MAX_MAPHASH; i++) { + for (const mapblock_T *current_maphash = get_maphash(i, buf); + current_maphash; + current_maphash = current_maphash->m_next) { + // Check for correct mode + if (int_mode & current_maphash->m_mode) { + mapblock_fill_dict(dict, current_maphash, buffer_value, false); + ADD(mappings, vim_to_object( + (typval_T[]) { { .v_type = VAR_DICT, .vval.v_dict = dict } })); + + tv_dict_clear(dict); + } + } + } + tv_dict_free(dict); + + return mappings; +} |