aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api/private/helpers.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/api/private/helpers.c')
-rw-r--r--src/nvim/api/private/helpers.c688
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;
+}