diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 22:39:54 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 22:39:54 +0000 |
commit | 21cb7d04c387e4198ca8098a884c78b56ffcf4c2 (patch) | |
tree | 84fe5690df1551f0bb2bdfe1a13aacd29ebc1de7 /src/nvim/api/private | |
parent | d9c904f85a23a496df4eb6be42aa43f007b22d50 (diff) | |
parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
download | rneovim-colorcolchar.tar.gz rneovim-colorcolchar.tar.bz2 rneovim-colorcolchar.zip |
Merge remote-tracking branch 'upstream/master' into colorcolcharcolorcolchar
Diffstat (limited to 'src/nvim/api/private')
-rw-r--r-- | src/nvim/api/private/converter.c | 30 | ||||
-rw-r--r-- | src/nvim/api/private/converter.h | 9 | ||||
-rw-r--r-- | src/nvim/api/private/defs.h | 25 | ||||
-rw-r--r-- | src/nvim/api/private/dispatch.c | 3 | ||||
-rw-r--r-- | src/nvim/api/private/dispatch.h | 12 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 205 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.h | 66 | ||||
-rw-r--r-- | src/nvim/api/private/validate.c | 76 | ||||
-rw-r--r-- | src/nvim/api/private/validate.h | 96 |
9 files changed, 418 insertions, 104 deletions
diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index 58ff552ab7..90023171e5 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -1,25 +1,21 @@ -// 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 <stdbool.h> #include <stddef.h> #include <stdint.h> -#include <stdlib.h> #include "klib/kvec.h" #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/assert.h" +#include "nvim/assert_defs.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" -#include "nvim/garray.h" +#include "nvim/func_attr.h" #include "nvim/lua/executor.h" #include "nvim/memory.h" -#include "nvim/types.h" -#include "nvim/vim.h" +#include "nvim/types_defs.h" +#include "nvim/vim_defs.h" /// Helper structure for vim_to_object typedef struct { @@ -49,9 +45,9 @@ typedef struct { #define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \ do { \ const size_t len_ = (size_t)(len); \ - const char *const str_ = (const char *)(str); \ + const char *const str_ = (str); \ assert(len_ == 0 || str_ != NULL); \ - kvi_push(edata->stack, STRING_OBJ(cbuf_to_string((len_?str_:""), len_))); \ + kvi_push(edata->stack, STRING_OBJ(cbuf_to_string((len_ ? str_ : ""), len_))); \ } while (0) #define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING @@ -204,6 +200,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata) #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 @@ -256,12 +253,14 @@ Object vim_to_object(typval_T *obj) return ret; } -/// Converts from type Object to a VimL value. +/// Converts from type Object to a Vimscript 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. +/// @param err Error object. +/// +/// @returns true if conversion is successful, otherwise false. bool object_to_vim(Object obj, typval_T *tv, Error *err) { tv->v_type = VAR_UNKNOWN; @@ -275,7 +274,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) case kObjectTypeBoolean: tv->v_type = VAR_BOOL; - tv->vval.v_bool = obj.data.boolean? kBoolVarTrue: kBoolVarFalse; + tv->vval.v_bool = obj.data.boolean ? kBoolVarTrue : kBoolVarFalse; break; case kObjectTypeBuffer: @@ -283,7 +282,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) case kObjectTypeTabpage: case kObjectTypeInteger: STATIC_ASSERT(sizeof(obj.data.integer) <= sizeof(varnumber_T), - "Integer size must be <= VimL number size"); + "Integer size must be <= Vimscript number size"); tv->v_type = VAR_NUMBER; tv->vval.v_number = (varnumber_T)obj.data.integer; break; @@ -363,9 +362,6 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) tv->vval.v_string = xstrdup(name); break; } - - default: - abort(); } return true; diff --git a/src/nvim/api/private/converter.h b/src/nvim/api/private/converter.h index 80ee640295..fc82abf332 100644 --- a/src/nvim/api/private/converter.h +++ b/src/nvim/api/private/converter.h @@ -1,11 +1,8 @@ -#ifndef NVIM_API_PRIVATE_CONVERTER_H -#define NVIM_API_PRIVATE_CONVERTER_H +#pragma once -#include "nvim/api/private/defs.h" -#include "nvim/eval/typval.h" +#include "nvim/api/private/defs.h" // IWYU pragma: keep +#include "nvim/eval/typval_defs.h" // IWYU pragma: keep #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/converter.h.generated.h" #endif - -#endif // NVIM_API_PRIVATE_CONVERTER_H diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 8acbf0d9de..25c8377518 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -1,5 +1,4 @@ -#ifndef NVIM_API_PRIVATE_DEFS_H -#define NVIM_API_PRIVATE_DEFS_H +#pragma once #include <stdbool.h> #include <stdint.h> @@ -7,7 +6,7 @@ #include "klib/kvec.h" #include "nvim/func_attr.h" -#include "nvim/types.h" +#include "nvim/types_defs.h" #define ARRAY_DICT_INIT KV_INITIAL_VALUE #define STRING_INIT { .data = NULL, .size = 0 } @@ -42,10 +41,10 @@ typedef enum { /// Mask for all internal calls #define INTERNAL_CALL_MASK (((uint64_t)1) << (sizeof(uint64_t) * 8 - 1)) -/// Internal call from VimL code +/// Internal call from Vimscript code #define VIML_INTERNAL_CALL INTERNAL_CALL_MASK -/// Internal call from lua code +/// Internal call from Lua code #define LUA_INTERNAL_CALL (VIML_INTERNAL_CALL + 1) static inline bool is_internal_call(uint64_t channel_id) @@ -124,14 +123,18 @@ struct key_value_pair { Object value; }; -typedef Object *(*field_hash)(void *retval, const char *str, size_t len); +typedef uint64_t OptionalKeys; + +// this is the prefix of all keysets with optional keys +typedef struct { + OptionalKeys is_set_; +} OptKeySet; + typedef struct { char *str; size_t ptr_off; + ObjectType type; // kObjectTypeNil == untyped + int opt_index; } KeySetLink; -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "keysets_defs.generated.h" -#endif - -#endif // NVIM_API_PRIVATE_DEFS_H +typedef KeySetLink *(*FieldHashfn)(const char *str, size_t len); diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c index f427bba00e..53fcd148bd 100644 --- a/src/nvim/api/private/dispatch.c +++ b/src/nvim/api/private/dispatch.c @@ -1,6 +1,3 @@ -// 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 <stddef.h> #include "nvim/api/private/defs.h" diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h index 4ae61b2bfb..6a2c9eaf54 100644 --- a/src/nvim/api/private/dispatch.h +++ b/src/nvim/api/private/dispatch.h @@ -1,12 +1,11 @@ -#ifndef NVIM_API_PRIVATE_DISPATCH_H -#define NVIM_API_PRIVATE_DISPATCH_H +#pragma once #include <stdbool.h> #include <stdint.h> #include "nvim/api/private/defs.h" -#include "nvim/memory.h" -#include "nvim/types.h" +#include "nvim/memory_defs.h" +#include "nvim/types_defs.h" typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, Array args, Arena *arena, Error *error); @@ -27,7 +26,6 @@ extern const MsgpackRpcRequestHandler method_handlers[]; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/dispatch.h.generated.h" -# include "api/private/dispatch_wrappers.h.generated.h" +# include "api/private/dispatch_wrappers.h.generated.h" // IWYU pragma: export +# include "keysets_defs.generated.h" #endif - -#endif // NVIM_API_PRIVATE_DISPATCH_H diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 519f2cc5bf..be39836a5b 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1,13 +1,10 @@ -// 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 <limits.h> #include <msgpack/unpack.h> #include <stdarg.h> #include <stdbool.h> #include <stddef.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -16,21 +13,26 @@ #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/ascii.h" +#include "nvim/api/private/validate.h" +#include "nvim/ascii_defs.h" #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" +#include "nvim/eval/vars.h" #include "nvim/ex_eval.h" +#include "nvim/func_attr.h" #include "nvim/garray.h" +#include "nvim/globals.h" #include "nvim/highlight_group.h" #include "nvim/lua/executor.h" -#include "nvim/map.h" +#include "nvim/map_defs.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/msgpack_rpc/helpers.h" -#include "nvim/pos.h" +#include "nvim/pos_defs.h" +#include "nvim/types_defs.h" #include "nvim/ui.h" #include "nvim/version.h" @@ -40,10 +42,10 @@ # include "api/private/ui_events_metadata.generated.h" #endif -/// Start block that may cause VimL exceptions while evaluating another code +/// Start block that may cause Vimscript 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. +/// Used when caller is supposed to be operating when other Vimscript code is being +/// processed and that “other Vimscript code” must not be affected. /// /// @param[out] tstate Location where try state should be saved. void try_enter(TryState *const tstate) @@ -234,8 +236,7 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva // Delete the key if (di == NULL) { // Doesn't exist, fail - api_set_error(err, kErrorTypeValidation, "Key not found: %s", - key.data); + api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data); } else { // Notify watchers if (watched) { @@ -264,13 +265,23 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva di = tv_dict_item_alloc_len(key.data, key.size); tv_dict_add(dict, di); } else { - if (watched) { - tv_copy(&di->di_tv, &oldtv); - } // Return the old value if (retval) { rv = vim_to_object(&di->di_tv); } + bool type_error = false; + if (dict == &vimvardict + && !before_set_vvar(key.data, di, &tv, true, watched, &type_error)) { + tv_clear(&tv); + if (type_error) { + api_set_error(err, kErrorTypeValidation, + "Setting v:%s to value with wrong type", key.data); + } + return rv; + } + if (watched) { + tv_copy(&di->di_tv, &oldtv); + } tv_clear(&di->di_tv); } @@ -478,6 +489,27 @@ Array string_to_array(const String input, bool crlf) return ret; } +/// Normalizes 0-based indexes to buffer line numbers. +int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob) +{ + assert(buf->b_ml.ml_line_count > 0); + int64_t max_index = buf->b_ml.ml_line_count + (int)end_exclusive - 1; + // A negative index counts from the bottom. + index = index < 0 ? max_index + index + 1 : index; + + // Check for oob and clamp. + if (index > max_index) { + *oob = true; + index = max_index; + } else if (index < 0) { + *oob = true; + index = 0; + } + // Convert the index to a 1-based line number. + index++; + return index; +} + /// Returns a substring of a buffer line /// /// @param buf Buffer handle @@ -495,7 +527,7 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col return rv; } - char *bufstr = ml_get_buf(buf, (linenr_T)lnum, false); + char *bufstr = ml_get_buf(buf, (linenr_T)lnum); size_t line_length = strlen(bufstr); start_col = start_col < 0 ? (int64_t)line_length + start_col + 1 : start_col; @@ -577,9 +609,6 @@ void api_free_object(Object value) case kObjectTypeLuaRef: api_free_luaref(value.data.luaref); break; - - default: - abort(); } } @@ -660,10 +689,10 @@ static void init_ui_event_metadata(Dictionary *metadata) 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"))); + ADD(ui_options, CSTR_TO_OBJ("rgb")); for (UIExtension i = 0; i < kUIExtCount; i++) { if (ui_ext_names[i][0] != '_') { - ADD(ui_options, STRING_OBJ(cstr_to_string(ui_ext_names[i]))); + ADD(ui_options, CSTR_TO_OBJ(ui_ext_names[i])); } } PUT(*metadata, "ui_options", ARRAY_OBJ(ui_options)); @@ -692,17 +721,17 @@ static void init_type_metadata(Dictionary *metadata) Dictionary buffer_metadata = ARRAY_DICT_INIT; PUT(buffer_metadata, "id", INTEGER_OBJ(kObjectTypeBuffer - EXT_OBJECT_TYPE_SHIFT)); - PUT(buffer_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_buf_"))); + PUT(buffer_metadata, "prefix", CSTR_TO_OBJ("nvim_buf_")); Dictionary window_metadata = ARRAY_DICT_INIT; PUT(window_metadata, "id", INTEGER_OBJ(kObjectTypeWindow - EXT_OBJECT_TYPE_SHIFT)); - PUT(window_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_win_"))); + PUT(window_metadata, "prefix", CSTR_TO_OBJ("nvim_win_")); Dictionary tabpage_metadata = ARRAY_DICT_INIT; PUT(tabpage_metadata, "id", INTEGER_OBJ(kObjectTypeTabpage - EXT_OBJECT_TYPE_SHIFT)); - PUT(tabpage_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_tabpage_"))); + PUT(tabpage_metadata, "prefix", CSTR_TO_OBJ("nvim_tabpage_")); PUT(types, "Buffer", DICTIONARY_OBJ(buffer_metadata)); PUT(types, "Window", DICTIONARY_OBJ(window_metadata)); @@ -767,10 +796,8 @@ Object copy_object(Object obj, Arena *arena) case kObjectTypeLuaRef: return LUAREF_OBJ(api_new_luaref(obj.data.luaref)); - - default: - abort(); } + UNREACHABLE; } void api_set_error(Error *err, ErrorType errType, const char *format, ...) @@ -806,7 +833,7 @@ bool api_object_to_bool(Object obj, const char *what, bool nil_value, Error *err } else if (obj.type == kObjectTypeInteger) { return obj.data.integer; // C semantics: non-zero int is true } else if (obj.type == kObjectTypeNil) { - return nil_value; // caller decides what NIL (missing retval in lua) means + return nil_value; // caller decides what NIL (missing retval in Lua) means } else { api_set_error(err, kErrorTypeValidation, "%s is not a boolean", what); return false; @@ -821,12 +848,40 @@ int object_to_hl_id(Object obj, const char *what, Error *err) } else if (obj.type == kObjectTypeInteger) { return MAX((int)obj.data.integer, 0); } else { - api_set_error(err, kErrorTypeValidation, - "%s is not a valid highlight", what); + api_set_error(err, kErrorTypeValidation, "Invalid highlight: %s", what); return 0; } } +char *api_typename(ObjectType t) +{ + switch (t) { + case kObjectTypeNil: + return "nil"; + case kObjectTypeBoolean: + return "Boolean"; + case kObjectTypeInteger: + return "Integer"; + case kObjectTypeFloat: + return "Float"; + case kObjectTypeString: + return "String"; + case kObjectTypeArray: + return "Array"; + case kObjectTypeDictionary: + return "Dict"; + case kObjectTypeLuaRef: + return "Function"; + case kObjectTypeBuffer: + return "Buffer"; + case kObjectTypeWindow: + return "Window"; + case kObjectTypeTabpage: + return "Tabpage"; + } + UNREACHABLE; +} + HlMessage parse_hl_msg(Array chunks, Error *err) { HlMessage hl_msg = KV_INITIAL_VALUE; @@ -865,17 +920,84 @@ free_exit: return (HlMessage)KV_INITIAL_VALUE; } -bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err) +// see also nlua_pop_keydict for the lua specific implementation +bool api_dict_to_keydict(void *retval, FieldHashfn hashy, Dictionary dict, Error *err) { for (size_t i = 0; i < dict.size; i++) { String k = dict.items[i].key; - Object *field = hashy(rv, k.data, k.size); + KeySetLink *field = hashy(k.data, k.size); if (!field) { api_set_error(err, kErrorTypeValidation, "Invalid key: '%.*s'", (int)k.size, k.data); return false; } - *field = dict.items[i].value; + if (field->opt_index >= 0) { + OptKeySet *ks = (OptKeySet *)retval; + ks->is_set_ |= (1ULL << field->opt_index); + } + + char *mem = ((char *)retval + field->ptr_off); + Object *value = &dict.items[i].value; + if (field->type == kObjectTypeNil) { + *(Object *)mem = *value; + } else if (field->type == kObjectTypeInteger) { + VALIDATE_T(field->str, kObjectTypeInteger, value->type, { + return false; + }); + *(Integer *)mem = value->data.integer; + } else if (field->type == kObjectTypeFloat) { + Float *val = (Float *)mem; + if (value->type == kObjectTypeInteger) { + *val = (Float)value->data.integer; + } else { + VALIDATE_T(field->str, kObjectTypeFloat, value->type, { + return false; + }); + *val = value->data.floating; + } + } else if (field->type == kObjectTypeBoolean) { + // caller should check HAS_KEY to override the nil behavior, or GET_BOOL_OR_TRUE + // to directly use true when nil + *(Boolean *)mem = api_object_to_bool(*value, field->str, false, err); + if (ERROR_SET(err)) { + return false; + } + } else if (field->type == kObjectTypeString) { + VALIDATE_T(field->str, kObjectTypeString, value->type, { + return false; + }); + *(String *)mem = value->data.string; + } else if (field->type == kObjectTypeArray) { + VALIDATE_T(field->str, kObjectTypeArray, value->type, { + return false; + }); + *(Array *)mem = value->data.array; + } else if (field->type == kObjectTypeDictionary) { + Dictionary *val = (Dictionary *)mem; + // allow empty array as empty dict for lua (directly or via lua-client RPC) + if (value->type == kObjectTypeArray && value->data.array.size == 0) { + *val = (Dictionary)ARRAY_DICT_INIT; + } else if (value->type == kObjectTypeDictionary) { + *val = value->data.dictionary; + } else { + api_err_exp(err, field->str, api_typename(field->type), api_typename(value->type)); + return false; + } + } else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow + || field->type == kObjectTypeTabpage) { + if (value->type == kObjectTypeInteger || value->type == field->type) { + *(handle_T *)mem = (handle_T)value->data.integer; + } else { + api_err_exp(err, field->str, api_typename(field->type), api_typename(value->type)); + return false; + } + } else if (field->type == kObjectTypeLuaRef) { + api_set_error(err, kErrorTypeValidation, "Invalid key: '%.*s' is only allowed from Lua", + (int)k.size, k.data); + return false; + } else { + abort(); + } } return true; @@ -884,7 +1006,18 @@ bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err void api_free_keydict(void *dict, KeySetLink *table) { for (size_t i = 0; table[i].str; i++) { - api_free_object(*(Object *)((char *)dict + table[i].ptr_off)); + char *mem = ((char *)dict + table[i].ptr_off); + if (table[i].type == kObjectTypeNil) { + api_free_object(*(Object *)mem); + } else if (table[i].type == kObjectTypeString) { + api_free_string(*(String *)mem); + } else if (table[i].type == kObjectTypeArray) { + api_free_array(*(Array *)mem); + } else if (table[i].type == kObjectTypeDictionary) { + api_free_dictionary(*(Dictionary *)mem); + } else if (table[i].type == kObjectTypeLuaRef) { + api_free_luaref(*(LuaRef *)mem); + } } } @@ -930,12 +1063,14 @@ bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err) } /// Get default statusline highlight for window -const char *get_default_stl_hl(win_T *wp, bool use_winbar) +const char *get_default_stl_hl(win_T *wp, bool use_winbar, int stc_hl_id) { if (wp == NULL) { return "TabLineFill"; } else if (use_winbar) { return (wp == curwin) ? "WinBar" : "WinBarNC"; + } else if (stc_hl_id > 0) { + return syn_id2name(stc_hl_id); } else { return (wp == curwin) ? "StatusLine" : "StatusLineNC"; } diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index ec97ba9ec6..e61dd5f992 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -1,26 +1,28 @@ -#ifndef NVIM_API_PRIVATE_HELPERS_H -#define NVIM_API_PRIVATE_HELPERS_H +#pragma once #include <stdbool.h> #include <stddef.h> +#include <stdint.h> #include "klib/kvec.h" #include "nvim/api/private/defs.h" +#include "nvim/api/private/dispatch.h" #include "nvim/decoration.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_eval_defs.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" -#include "nvim/macros.h" -#include "nvim/map.h" +#include "nvim/macros_defs.h" +#include "nvim/map_defs.h" #include "nvim/memory.h" -#include "nvim/vim.h" +#include "nvim/message.h" #define OBJECT_OBJ(o) o #define BOOLEAN_OBJ(b) ((Object) { \ .type = kObjectTypeBoolean, \ .data.boolean = b }) -#define BOOL(b) BOOLEAN_OBJ(b) #define INTEGER_OBJ(i) ((Object) { \ .type = kObjectTypeInteger, \ @@ -34,6 +36,7 @@ .type = kObjectTypeString, \ .data.string = s }) +#define CSTR_AS_OBJ(s) STRING_OBJ(cstr_as_string(s)) #define CSTR_TO_OBJ(s) STRING_OBJ(cstr_to_string(s)) #define BUFFER_OBJ(s) ((Object) { \ @@ -63,8 +66,9 @@ #define NIL ((Object)OBJECT_INIT) #define NULL_STRING ((String)STRING_INIT) -// currently treat key=vim.NIL as if the key was missing -#define HAS_KEY(o) ((o).type != kObjectTypeNil) +#define HAS_KEY(d, typ, key) (((d)->is_set__##typ##_ & (1 << KEYSET_OPTIDX_##typ##__##key)) != 0) + +#define GET_BOOL_OR_TRUE(d, typ, key) (HAS_KEY(d, typ, key) ? (d)->key : true) #define PUT(dict, k, v) \ kv_push(dict, ((KeyValuePair) { .key = cstr_to_string(k), .value = v })) @@ -72,8 +76,6 @@ #define PUT_C(dict, k, v) \ kv_push_c(dict, ((KeyValuePair) { .key = cstr_as_string(k), .value = v })) -#define PUT_BOOL(dict, name, condition) PUT(dict, name, BOOLEAN_OBJ(condition)); - #define ADD(array, item) \ kv_push(array, item) @@ -94,7 +96,7 @@ #define cbuf_as_string(d, s) ((String) { .data = d, .size = s }) -#define STATIC_CSTR_AS_STRING(s) ((String) { .data = s, .size = sizeof(s) - 1 }) +#define STATIC_CSTR_AS_STRING(s) ((String) { .data = s, .size = sizeof("" s) - 1 }) /// Create a new String instance, putting data in allocated memory /// @@ -103,6 +105,9 @@ .data = xmemdupz(s, sizeof(s) - 1), \ .size = sizeof(s) - 1 }) +#define STATIC_CSTR_AS_OBJ(s) STRING_OBJ(STATIC_CSTR_AS_STRING(s)) +#define STATIC_CSTR_TO_OBJ(s) STRING_OBJ(STATIC_CSTR_TO_STRING(s)) + // Helpers used by the generated msgpack-rpc api wrappers #define api_init_boolean #define api_init_integer @@ -122,18 +127,18 @@ #define api_free_window(value) #define api_free_tabpage(value) -EXTERN PMap(handle_T) buffer_handles INIT(= MAP_INIT); -EXTERN PMap(handle_T) window_handles INIT(= MAP_INIT); -EXTERN PMap(handle_T) tabpage_handles INIT(= MAP_INIT); +EXTERN PMap(int) buffer_handles INIT( = MAP_INIT); +EXTERN PMap(int) window_handles INIT( = MAP_INIT); +EXTERN PMap(int) tabpage_handles INIT( = MAP_INIT); -#define handle_get_buffer(h) pmap_get(handle_T)(&buffer_handles, (h)) -#define handle_get_window(h) pmap_get(handle_T)(&window_handles, (h)) -#define handle_get_tabpage(h) pmap_get(handle_T)(&tabpage_handles, (h)) +#define handle_get_buffer(h) pmap_get(int)(&buffer_handles, (h)) +#define handle_get_window(h) pmap_get(int)(&window_handles, (h)) +#define handle_get_tabpage(h) pmap_get(int)(&tabpage_handles, (h)) /// Structure used for saving state for :try /// -/// Used when caller is supposed to be operating when other VimL code is being -/// processed and that “other VimL code” must not be affected. +/// Used when caller is supposed to be operating when other Vimscript code is being +/// processed and that “other Vimscript code” must not be affected. typedef struct { except_T *current_exception; msglist_T *private_msg_list; @@ -149,14 +154,26 @@ typedef struct { // which would otherwise be ignored. This pattern is from do_cmdline(). // // TODO(bfredl): prepare error-handling at "top level" (nv_event). -#define TRY_WRAP(code) \ +#define TRY_WRAP(err, code) \ do { \ msglist_T **saved_msg_list = msg_list; \ msglist_T *private_msg_list; \ msg_list = &private_msg_list; \ private_msg_list = NULL; \ - code \ - msg_list = saved_msg_list; /* Restore the exception context. */ \ + try_start(); \ + code; \ + try_end(err); \ + msg_list = saved_msg_list; /* Restore the exception context. */ \ + } while (0) + +// Execute code with cursor position saved and restored and textlock active. +#define TEXTLOCK_WRAP(code) \ + do { \ + const pos_T save_cursor = curwin->w_cursor; \ + textlock++; \ + code; \ + textlock--; \ + curwin->w_cursor = save_cursor; \ } while (0) // Useful macro for executing some `code` for each item in an array. @@ -169,18 +186,17 @@ typedef struct { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/helpers.h.generated.h" -# include "keysets.h.generated.h" #endif #define WITH_SCRIPT_CONTEXT(channel_id, code) \ do { \ const sctx_T save_current_sctx = current_sctx; \ + const uint64_t save_channel_id = current_channel_id; \ current_sctx.sc_sid = \ (channel_id) == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; \ current_sctx.sc_lnum = 0; \ current_channel_id = channel_id; \ code; \ + current_channel_id = save_channel_id; \ current_sctx = save_current_sctx; \ } while (0); - -#endif // NVIM_API_PRIVATE_HELPERS_H diff --git a/src/nvim/api/private/validate.c b/src/nvim/api/private/validate.c new file mode 100644 index 0000000000..e198c671eb --- /dev/null +++ b/src/nvim/api/private/validate.c @@ -0,0 +1,76 @@ +#include <inttypes.h> +#include <stdio.h> +#include <string.h> + +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/api/private/validate.h" +#include "nvim/ascii_defs.h" +#include "nvim/globals.h" + +/// Creates "Invalid …" message and sets it on `err`. +void api_err_invalid(Error *err, const char *name, const char *val_s, int64_t val_n, bool quote_val) +{ + ErrorType errtype = kErrorTypeValidation; + // Treat `name` without whitespace as a parameter (surround in quotes). + // Treat `name` with whitespace as a description (no quotes). + char *has_space = strchr(name, ' '); + + // No value. + if (val_s && val_s[0] == '\0') { + api_set_error(err, errtype, has_space ? "Invalid %s" : "Invalid '%s'", name); + return; + } + + // Number value. + if (val_s == NULL) { + api_set_error(err, errtype, has_space ? "Invalid %s: %" PRId64 : "Invalid '%s': %" PRId64, + name, val_n); + return; + } + + // String value. + if (has_space) { + api_set_error(err, errtype, quote_val ? "Invalid %s: '%s'" : "Invalid %s: %s", name, val_s); + } else { + api_set_error(err, errtype, quote_val ? "Invalid '%s': '%s'" : "Invalid '%s': %s", name, val_s); + } +} + +/// Creates "Invalid …: expected …" message and sets it on `err`. +void api_err_exp(Error *err, const char *name, const char *expected, const char *actual) +{ + ErrorType errtype = kErrorTypeValidation; + // Treat `name` without whitespace as a parameter (surround in quotes). + // Treat `name` with whitespace as a description (no quotes). + char *has_space = strchr(name, ' '); + + if (!actual) { + api_set_error(err, errtype, + has_space ? "Invalid %s: expected %s" : "Invalid '%s': expected %s", + name, expected); + return; + } + + api_set_error(err, errtype, + has_space ? "Invalid %s: expected %s, got %s" : "Invalid '%s': expected %s, got %s", + name, expected, actual); +} + +bool check_string_array(Array arr, char *name, bool disallow_nl, Error *err) +{ + snprintf(IObuff, sizeof(IObuff), "'%s' item", name); + for (size_t i = 0; i < arr.size; i++) { + VALIDATE_T(IObuff, kObjectTypeString, arr.items[i].type, { + return false; + }); + // Disallow newlines in the middle of the line. + if (disallow_nl) { + const String l = arr.items[i].data.string; + VALIDATE(!memchr(l.data, NL, l.size), "'%s' item contains newlines", name, { + return false; + }); + } + } + return true; +} diff --git a/src/nvim/api/private/validate.h b/src/nvim/api/private/validate.h new file mode 100644 index 0000000000..d1c977cd6e --- /dev/null +++ b/src/nvim/api/private/validate.h @@ -0,0 +1,96 @@ +#pragma once + +#include <stdbool.h> +#include <stddef.h> + +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/assert_defs.h" +#include "nvim/macros_defs.h" + +#define VALIDATE(cond, fmt_, fmt_arg1, code) \ + do { \ + if (!(cond)) { \ + api_set_error(err, kErrorTypeValidation, fmt_, fmt_arg1); \ + code; \ + } \ + } while (0) + +#define VALIDATE_INT(cond, name, val_, code) \ + do { \ + if (!(cond)) { \ + api_err_invalid(err, name, NULL, val_, false); \ + code; \ + } \ + } while (0) + +#define VALIDATE_S(cond, name, val_, code) \ + do { \ + if (!(cond)) { \ + api_err_invalid(err, name, val_, 0, true); \ + code; \ + } \ + } while (0) + +#define VALIDATE_EXP(cond, name, expected, actual, code) \ + do { \ + if (!(cond)) { \ + api_err_exp(err, name, expected, actual); \ + code; \ + } \ + } while (0) + +#define VALIDATE_T(name, expected_t, actual_t, code) \ + do { \ + STATIC_ASSERT(expected_t != kObjectTypeDictionary, "use VALIDATE_T_DICT"); \ + if (expected_t != actual_t) { \ + api_err_exp(err, name, api_typename(expected_t), api_typename(actual_t)); \ + code; \ + } \ + } while (0) + +/// Checks that `obj_` has type `expected_t`. +#define VALIDATE_T2(obj_, expected_t, code) \ + do { \ + STATIC_ASSERT(expected_t != kObjectTypeDictionary, "use VALIDATE_T_DICT"); \ + if ((obj_).type != expected_t) { \ + api_err_exp(err, STR(obj_), api_typename(expected_t), api_typename((obj_).type)); \ + code; \ + } \ + } while (0) + +/// Checks that `obj_` has Dict type. Also allows empty Array in a Lua context. +#define VALIDATE_T_DICT(name, obj_, code) \ + do { \ + if ((obj_).type != kObjectTypeDictionary \ + && !(channel_id == LUA_INTERNAL_CALL \ + && (obj_).type == kObjectTypeArray \ + && (obj_).data.array.size == 0)) { \ + api_err_exp(err, name, api_typename(kObjectTypeDictionary), api_typename((obj_).type)); \ + code; \ + } \ + } while (0) + +/// Checks that actual_t is either the correct handle type or a type erased handle (integer) +#define VALIDATE_T_HANDLE(name, expected_t, actual_t, code) \ + do { \ + if (expected_t != actual_t && kObjectTypeInteger != actual_t) { \ + api_err_exp(err, name, api_typename(expected_t), api_typename(actual_t)); \ + code; \ + } \ + } while (0) + +#define VALIDATE_RANGE(cond, name, code) \ + do { \ + if (!(cond)) { \ + api_err_invalid(err, name, "out of range", 0, false); \ + code; \ + } \ + } while (0) + +#define VALIDATE_R(cond, name, code) \ + VALIDATE(cond, "Required: '%s'", name, code); + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/private/validate.h.generated.h" +#endif |