diff options
| author | Josh Rahm <joshuarahm@gmail.com> | 2024-11-19 22:57:13 +0000 |
|---|---|---|
| committer | Josh Rahm <joshuarahm@gmail.com> | 2024-11-19 22:57:13 +0000 |
| commit | 9be89f131f87608f224f0ee06d199fcd09d32176 (patch) | |
| tree | 11022dcfa9e08cb4ac5581b16734196128688d48 /src/nvim/eval | |
| parent | ff7ed8f586589d620a806c3758fac4a47a8e7e15 (diff) | |
| parent | 88085c2e80a7e3ac29aabb6b5420377eed99b8b6 (diff) | |
| download | rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.tar.gz rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.tar.bz2 rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.zip | |
Merge remote-tracking branch 'upstream/master' into mix_20240309
Diffstat (limited to 'src/nvim/eval')
| -rw-r--r-- | src/nvim/eval/decode.c | 363 | ||||
| -rw-r--r-- | src/nvim/eval/decode.h | 2 | ||||
| -rw-r--r-- | src/nvim/eval/encode.c | 90 | ||||
| -rw-r--r-- | src/nvim/eval/encode.h | 4 | ||||
| -rw-r--r-- | src/nvim/eval/executor.c | 325 | ||||
| -rw-r--r-- | src/nvim/eval/fs.c | 1492 | ||||
| -rw-r--r-- | src/nvim/eval/fs.h | 8 | ||||
| -rw-r--r-- | src/nvim/eval/funcs.c | 1811 | ||||
| -rw-r--r-- | src/nvim/eval/typval.c | 175 | ||||
| -rw-r--r-- | src/nvim/eval/typval.h | 91 | ||||
| -rw-r--r-- | src/nvim/eval/typval_defs.h | 6 | ||||
| -rw-r--r-- | src/nvim/eval/typval_encode.c.h | 20 | ||||
| -rw-r--r-- | src/nvim/eval/typval_encode.h | 11 | ||||
| -rw-r--r-- | src/nvim/eval/userfunc.c | 132 | ||||
| -rw-r--r-- | src/nvim/eval/userfunc.h | 8 | ||||
| -rw-r--r-- | src/nvim/eval/vars.c | 9 | ||||
| -rw-r--r-- | src/nvim/eval/window.c | 3 |
17 files changed, 2633 insertions, 1917 deletions
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index d7df7bb150..afc2efddf6 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -1,5 +1,4 @@ #include <assert.h> -#include <msgpack/object.h> #include <stdbool.h> #include <stddef.h> #include <stdint.h> @@ -7,6 +6,7 @@ #include <string.h> #include "klib/kvec.h" +#include "mpack/object.h" #include "nvim/ascii_defs.h" #include "nvim/charset.h" #include "nvim/eval.h" @@ -247,45 +247,29 @@ list_T *decode_create_map_special_dict(typval_T *const ret_tv, const ptrdiff_t l /// /// @param[in] s String to decode. /// @param[in] len String length. -/// @param[in] hasnul Whether string has NUL byte, not or it was not yet -/// determined. -/// @param[in] binary Determines decode type if string has NUL bytes. -/// If true convert string to VAR_BLOB, otherwise to the -/// kMPString special type. +/// @param[in] force_blob whether string always should be decoded as a blob, +/// or only when embedded NUL bytes were present /// @param[in] s_allocated If true, then `s` was allocated and can be saved in /// a returned structure. If it is not saved there, it /// will be freed. /// /// @return Decoded string. -typval_T decode_string(const char *const s, const size_t len, const TriState hasnul, - const bool binary, const bool s_allocated) +typval_T decode_string(const char *const s, const size_t len, bool force_blob, + const bool s_allocated) FUNC_ATTR_WARN_UNUSED_RESULT { assert(s != NULL || len == 0); - const bool really_hasnul = (hasnul == kNone - ? ((s != NULL) && (memchr(s, NUL, len) != NULL)) - : (bool)hasnul); - if (really_hasnul) { + const bool use_blob = force_blob || ((s != NULL) && (memchr(s, NUL, len) != NULL)); + if (use_blob) { typval_T tv; tv.v_lock = VAR_UNLOCKED; - if (binary) { - tv_blob_alloc_ret(&tv); - ga_concat_len(&tv.vval.v_blob->bv_ga, s, len); + blob_T *b = tv_blob_alloc_ret(&tv); + if (s_allocated) { + b->bv_ga.ga_data = (void *)s; + b->bv_ga.ga_len = (int)len; + b->bv_ga.ga_maxlen = (int)len; } else { - list_T *const list = tv_list_alloc(kListLenMayKnow); - tv_list_ref(list); - create_special_dict(&tv, kMPString, - (typval_T){ .v_type = VAR_LIST, - .v_lock = VAR_UNLOCKED, - .vval = { .v_list = list } }); - const int elw_ret = encode_list_write((void *)list, s, len); - if (s_allocated) { - xfree((void *)s); - } - if (elw_ret == -1) { - tv_clear(&tv); - return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; - } + ga_concat_len(&b->bv_ga, s, len); } return tv; } @@ -405,7 +389,6 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len, char *str = xmalloc(len + 1); int fst_in_pair = 0; char *str_end = str; - bool hasnul = false; #define PUT_FST_IN_PAIR(fst_in_pair, str_end) \ do { \ if ((fst_in_pair) != 0) { \ @@ -426,9 +409,6 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len, uvarnumber_T ch; vim_str2nr(ubuf, NULL, NULL, STR2NR_HEX | STR2NR_FORCE, NULL, &ch, 4, true, NULL); - if (ch == 0) { - hasnul = true; - } if (SURROGATE_HI_START <= ch && ch <= SURROGATE_HI_END) { PUT_FST_IN_PAIR(fst_in_pair, str_end); fst_in_pair = (int)ch; @@ -476,10 +456,7 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len, PUT_FST_IN_PAIR(fst_in_pair, str_end); #undef PUT_FST_IN_PAIR *str_end = NUL; - typval_T obj = decode_string(str, (size_t)(str_end - str), hasnul ? kTrue : kFalse, false, true); - if (obj.v_type == VAR_UNKNOWN) { - goto parse_json_string_fail; - } + typval_T obj = decode_string(str, (size_t)(str_end - str), false, true); POP(obj, obj.v_type != VAR_STRING); goto parse_json_string_ret; parse_json_string_fail: @@ -908,182 +885,250 @@ json_decode_string_ret: #undef DICT_LEN -/// Convert msgpack object to a Vimscript one -int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +static void positive_integer_to_special_typval(typval_T *rettv, uint64_t val) { - switch (mobj.type) { - case MSGPACK_OBJECT_NIL: + if (val <= VARNUMBER_MAX) { *rettv = (typval_T) { + .v_type = VAR_NUMBER, + .v_lock = VAR_UNLOCKED, + .vval = { .v_number = (varnumber_T)val }, + }; + } else { + list_T *const list = tv_list_alloc(4); + tv_list_ref(list); + create_special_dict(rettv, kMPInteger, ((typval_T) { + .v_type = VAR_LIST, + .v_lock = VAR_UNLOCKED, + .vval = { .v_list = list }, + })); + tv_list_append_number(list, 1); + tv_list_append_number(list, (varnumber_T)((val >> 62) & 0x3)); + tv_list_append_number(list, (varnumber_T)((val >> 31) & 0x7FFFFFFF)); + tv_list_append_number(list, (varnumber_T)(val & 0x7FFFFFFF)); + } +} + +static void typval_parse_enter(mpack_parser_t *parser, mpack_node_t *node) +{ + typval_T *result = NULL; + + mpack_node_t *parent = MPACK_PARENT_NODE(node); + if (parent) { + switch (parent->tok.type) { + case MPACK_TOKEN_ARRAY: { + list_T *list = parent->data[1].p; + result = tv_list_append_owned_tv(list, (typval_T) { .v_type = VAR_UNKNOWN }); + break; + } + case MPACK_TOKEN_MAP: { + typval_T(*items)[2] = parent->data[1].p; + result = &items[parent->pos][parent->key_visited]; + break; + } + + case MPACK_TOKEN_STR: + case MPACK_TOKEN_BIN: + case MPACK_TOKEN_EXT: + assert(node->tok.type == MPACK_TOKEN_CHUNK); + break; + + default: + abort(); + } + } else { + result = parser->data.p; + } + + // for types that are completed in typval_parse_exit + node->data[0].p = result; + node->data[1].p = NULL; // free on error if non-NULL + + switch (node->tok.type) { + case MPACK_TOKEN_NIL: + *result = (typval_T) { .v_type = VAR_SPECIAL, .v_lock = VAR_UNLOCKED, .vval = { .v_special = kSpecialVarNull }, }; break; - case MSGPACK_OBJECT_BOOLEAN: - *rettv = (typval_T) { + case MPACK_TOKEN_BOOLEAN: + *result = (typval_T) { .v_type = VAR_BOOL, .v_lock = VAR_UNLOCKED, .vval = { - .v_bool = mobj.via.boolean ? kBoolVarTrue : kBoolVarFalse + .v_bool = mpack_unpack_boolean(node->tok) ? kBoolVarTrue : kBoolVarFalse }, }; break; - case MSGPACK_OBJECT_POSITIVE_INTEGER: - if (mobj.via.u64 <= VARNUMBER_MAX) { - *rettv = (typval_T) { - .v_type = VAR_NUMBER, - .v_lock = VAR_UNLOCKED, - .vval = { .v_number = (varnumber_T)mobj.via.u64 }, - }; - } else { - list_T *const list = tv_list_alloc(4); - tv_list_ref(list); - create_special_dict(rettv, kMPInteger, ((typval_T) { - .v_type = VAR_LIST, - .v_lock = VAR_UNLOCKED, - .vval = { .v_list = list }, - })); - uint64_t n = mobj.via.u64; - tv_list_append_number(list, 1); - tv_list_append_number(list, (varnumber_T)((n >> 62) & 0x3)); - tv_list_append_number(list, (varnumber_T)((n >> 31) & 0x7FFFFFFF)); - tv_list_append_number(list, (varnumber_T)(n & 0x7FFFFFFF)); - } + case MPACK_TOKEN_SINT: { + *result = (typval_T) { + .v_type = VAR_NUMBER, + .v_lock = VAR_UNLOCKED, + .vval = { .v_number = mpack_unpack_sint(node->tok) }, + }; break; - case MSGPACK_OBJECT_NEGATIVE_INTEGER: - if (mobj.via.i64 >= VARNUMBER_MIN) { - *rettv = (typval_T) { - .v_type = VAR_NUMBER, - .v_lock = VAR_UNLOCKED, - .vval = { .v_number = (varnumber_T)mobj.via.i64 }, - }; - } else { - list_T *const list = tv_list_alloc(4); - tv_list_ref(list); - create_special_dict(rettv, kMPInteger, ((typval_T) { - .v_type = VAR_LIST, - .v_lock = VAR_UNLOCKED, - .vval = { .v_list = list }, - })); - uint64_t n = -((uint64_t)mobj.via.i64); - tv_list_append_number(list, -1); - tv_list_append_number(list, (varnumber_T)((n >> 62) & 0x3)); - tv_list_append_number(list, (varnumber_T)((n >> 31) & 0x7FFFFFFF)); - tv_list_append_number(list, (varnumber_T)(n & 0x7FFFFFFF)); - } + } + case MPACK_TOKEN_UINT: + positive_integer_to_special_typval(result, mpack_unpack_uint(node->tok)); break; - case MSGPACK_OBJECT_FLOAT32: - case MSGPACK_OBJECT_FLOAT64: - *rettv = (typval_T) { + case MPACK_TOKEN_FLOAT: + *result = (typval_T) { .v_type = VAR_FLOAT, .v_lock = VAR_UNLOCKED, - .vval = { .v_float = mobj.via.f64 }, + .vval = { .v_float = mpack_unpack_float(node->tok) }, }; break; - case MSGPACK_OBJECT_STR: - *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kTrue, false, - false); - if (rettv->v_type == VAR_UNKNOWN) { - return FAIL; - } + + case MPACK_TOKEN_BIN: + case MPACK_TOKEN_STR: + case MPACK_TOKEN_EXT: + // actually converted in typval_parse_exit after the data chunks + node->data[1].p = xmallocz(node->tok.length); break; - case MSGPACK_OBJECT_BIN: - *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kNone, true, - false); - if (rettv->v_type == VAR_UNKNOWN) { - return FAIL; - } + case MPACK_TOKEN_CHUNK: { + char *data = parent->data[1].p; + memcpy(data + parent->pos, + node->tok.data.chunk_ptr, node->tok.length); break; - case MSGPACK_OBJECT_ARRAY: { - list_T *const list = tv_list_alloc((ptrdiff_t)mobj.via.array.size); + } + + case MPACK_TOKEN_ARRAY: { + list_T *const list = tv_list_alloc((ptrdiff_t)node->tok.length); tv_list_ref(list); - *rettv = (typval_T) { + *result = (typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, .vval = { .v_list = list }, }; - for (size_t i = 0; i < mobj.via.array.size; i++) { - // Not populated yet, need to create list item to push. - tv_list_append_owned_tv(list, (typval_T) { .v_type = VAR_UNKNOWN }); - if (msgpack_to_vim(mobj.via.array.ptr[i], - TV_LIST_ITEM_TV(tv_list_last(list))) - == FAIL) { - return FAIL; - } + node->data[1].p = list; + break; + } + case MPACK_TOKEN_MAP: + // we don't know if this will be safe to convert to a typval dict yet + node->data[1].p = xmallocz(node->tok.length * 2 * sizeof(typval_T)); + break; + } +} + +/// Free node which was entered but never exited, due to a nested error +/// +/// Don't bother with typvals as these will be GC:d eventually +void typval_parser_error_free(mpack_parser_t *parser) +{ + for (uint32_t i = 0; i < parser->size; i++) { + mpack_node_t *node = &parser->items[i]; + switch (node->tok.type) { + case MPACK_TOKEN_BIN: + case MPACK_TOKEN_STR: + case MPACK_TOKEN_EXT: + case MPACK_TOKEN_MAP: + XFREE_CLEAR(node->data[1].p); + break; + default: + break; } + } +} + +static void typval_parse_exit(mpack_parser_t *parser, mpack_node_t *node) +{ + typval_T *result = node->data[0].p; + switch (node->tok.type) { + case MPACK_TOKEN_BIN: + case MPACK_TOKEN_STR: + *result = decode_string(node->data[1].p, node->tok.length, false, true); + node->data[1].p = NULL; break; + + case MPACK_TOKEN_EXT: { + list_T *const list = tv_list_alloc(2); + tv_list_ref(list); + tv_list_append_number(list, node->tok.data.ext_type); + list_T *const ext_val_list = tv_list_alloc(kListLenMayKnow); + tv_list_append_list(list, ext_val_list); + create_special_dict(result, kMPExt, ((typval_T) { .v_type = VAR_LIST, + .v_lock = VAR_UNLOCKED, + .vval = { .v_list = list } })); + // TODO(bfredl): why not use BLOB? + encode_list_write((void *)ext_val_list, node->data[1].p, node->tok.length); + XFREE_CLEAR(node->data[1].p); } - case MSGPACK_OBJECT_MAP: { - for (size_t i = 0; i < mobj.via.map.size; i++) { - if (mobj.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR - || mobj.via.map.ptr[i].key.via.str.size == 0 - || memchr(mobj.via.map.ptr[i].key.via.str.ptr, NUL, - mobj.via.map.ptr[i].key.via.str.size) != NULL) { + break; + + case MPACK_TOKEN_MAP: { + typval_T(*items)[2] = node->data[1].p; + for (size_t i = 0; i < node->tok.length; i++) { + typval_T *key = &items[i][0]; + if (key->v_type != VAR_STRING + || key->vval.v_string == NULL + || key->vval.v_string[0] == NUL) { goto msgpack_to_vim_generic_map; } } dict_T *const dict = tv_dict_alloc(); dict->dv_refcount++; - *rettv = (typval_T) { + *result = (typval_T) { .v_type = VAR_DICT, .v_lock = VAR_UNLOCKED, .vval = { .v_dict = dict }, }; - for (size_t i = 0; i < mobj.via.map.size; i++) { - dictitem_T *const di = xmallocz(offsetof(dictitem_T, di_key) - + mobj.via.map.ptr[i].key.via.str.size); - memcpy(&di->di_key[0], mobj.via.map.ptr[i].key.via.str.ptr, - mobj.via.map.ptr[i].key.via.str.size); + for (size_t i = 0; i < node->tok.length; i++) { + char *key = items[i][0].vval.v_string; + size_t keylen = strlen(key); + dictitem_T *const di = xmallocz(offsetof(dictitem_T, di_key) + keylen); + memcpy(&di->di_key[0], key, keylen); di->di_tv.v_type = VAR_UNKNOWN; if (tv_dict_add(dict, di) == FAIL) { // Duplicate key: fallback to generic map - tv_clear(rettv); + TV_DICT_ITER(dict, d, { + d->di_tv.v_type = VAR_SPECIAL; // don't free values in tv_clear(), they will be reused + d->di_tv.vval.v_special = kSpecialVarNull; + }); + tv_clear(result); xfree(di); goto msgpack_to_vim_generic_map; } - if (msgpack_to_vim(mobj.via.map.ptr[i].val, &di->di_tv) == FAIL) { - return FAIL; - } + di->di_tv = items[i][1]; + } + for (size_t i = 0; i < node->tok.length; i++) { + xfree(items[i][0].vval.v_string); } + XFREE_CLEAR(node->data[1].p); break; msgpack_to_vim_generic_map: {} - list_T *const list = decode_create_map_special_dict(rettv, (ptrdiff_t)mobj.via.map.size); - for (size_t i = 0; i < mobj.via.map.size; i++) { + list_T *const list = decode_create_map_special_dict(result, node->tok.length); + for (size_t i = 0; i < node->tok.length; i++) { list_T *const kv_pair = tv_list_alloc(2); tv_list_append_list(list, kv_pair); - typval_T key_tv = { .v_type = VAR_UNKNOWN }; - if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_tv) == FAIL) { - tv_clear(&key_tv); - return FAIL; - } - tv_list_append_owned_tv(kv_pair, key_tv); - - typval_T val_tv = { .v_type = VAR_UNKNOWN }; - if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_tv) == FAIL) { - tv_clear(&val_tv); - return FAIL; - } - tv_list_append_owned_tv(kv_pair, val_tv); + tv_list_append_owned_tv(kv_pair, items[i][0]); + tv_list_append_owned_tv(kv_pair, items[i][1]); } + XFREE_CLEAR(node->data[1].p); break; } - case MSGPACK_OBJECT_EXT: { - list_T *const list = tv_list_alloc(2); - tv_list_ref(list); - tv_list_append_number(list, mobj.via.ext.type); - list_T *const ext_val_list = tv_list_alloc(kListLenMayKnow); - tv_list_append_list(list, ext_val_list); - create_special_dict(rettv, kMPExt, ((typval_T) { .v_type = VAR_LIST, - .v_lock = VAR_UNLOCKED, - .vval = { .v_list = list } })); - if (encode_list_write((void *)ext_val_list, mobj.via.ext.ptr, - mobj.via.ext.size) == -1) { - return FAIL; - } + + default: + // other kinds are handled completely in typval_parse_enter break; } +} + +int mpack_parse_typval(mpack_parser_t *parser, const char **data, size_t *size) +{ + return mpack_parse(parser, data, size, typval_parse_enter, typval_parse_exit); +} + +int unpack_typval(const char **data, size_t *size, typval_T *ret) +{ + ret->v_type = VAR_UNKNOWN; + mpack_parser_t parser; + mpack_parser_init(&parser, 0); + parser.data.p = ret; + int status = mpack_parse_typval(&parser, data, size); + if (status != MPACK_OK) { + typval_parser_error_free(&parser); + tv_clear(ret); } - return OK; + return status; } diff --git a/src/nvim/eval/decode.h b/src/nvim/eval/decode.h index c0d10a469a..485cc65561 100644 --- a/src/nvim/eval/decode.h +++ b/src/nvim/eval/decode.h @@ -1,8 +1,8 @@ #pragma once -#include <msgpack.h> // IWYU pragma: keep #include <stddef.h> // IWYU pragma: keep +#include "mpack/object.h" #include "nvim/eval/typval_defs.h" // IWYU pragma: keep #include "nvim/types_defs.h" // IWYU pragma: keep diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index d35ac4eb7b..79f334601d 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -13,7 +13,7 @@ #include <string.h> #include "klib/kvec.h" -#include "msgpack/pack.h" +#include "nvim/api/private/helpers.h" #include "nvim/ascii_defs.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" @@ -28,6 +28,7 @@ #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/msgpack_rpc/packer.h" #include "nvim/strings.h" #include "nvim/types_defs.h" #include "nvim/vim_defs.h" // For _() @@ -54,11 +55,11 @@ int encode_blob_write(void *const data, const char *const buf, const size_t len) } /// Msgpack callback for writing to readfile()-style list -int encode_list_write(void *const data, const char *const buf, const size_t len) +void encode_list_write(void *const data, const char *const buf, const size_t len) FUNC_ATTR_NONNULL_ARG(1) { if (len == 0) { - return 0; + return; } list_T *const list = (list_T *)data; const char *const end = buf + len; @@ -96,7 +97,6 @@ int encode_list_write(void *const data, const char *const buf, const size_t len) if (line_end == end) { tv_list_append_allocated_string(list, NULL); } - return 0; } /// Abort conversion to string after a recursion error. @@ -412,6 +412,8 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ ga_concat(gap, "{}") +#define TYPVAL_ENCODE_CHECK_BEFORE + #define TYPVAL_ENCODE_CONV_NIL(tv) \ ga_concat(gap, "v:null") @@ -536,6 +538,8 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s #undef TYPVAL_ENCODE_ALLOW_SPECIALS #define TYPVAL_ENCODE_ALLOW_SPECIALS true +#define TYPVAL_ENCODE_CHECK_BEFORE + #undef TYPVAL_ENCODE_CONV_NIL #define TYPVAL_ENCODE_CONV_NIL(tv) \ ga_concat(gap, "null") @@ -771,8 +775,7 @@ bool encode_check_json_key(const typval_T *const tv) const dictitem_T *val_di; if ((type_di = tv_dict_find(spdict, S_LEN("_TYPE"))) == NULL || type_di->di_tv.v_type != VAR_LIST - || (type_di->di_tv.vval.v_list != eval_msgpack_type_lists[kMPString] - && type_di->di_tv.vval.v_list != eval_msgpack_type_lists[kMPBinary]) + || type_di->di_tv.vval.v_list != eval_msgpack_type_lists[kMPString] || (val_di = tv_dict_find(spdict, S_LEN("_VAL"))) == NULL || val_di->di_tv.v_type != VAR_LIST) { return false; @@ -821,6 +824,7 @@ bool encode_check_json_key(const typval_T *const tv) #undef TYPVAL_ENCODE_CONV_LIST_START #undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT +#undef TYPVAL_ENCODE_CHECK_BEFORE #undef TYPVAL_ENCODE_CONV_NIL #undef TYPVAL_ENCODE_CONV_BOOL #undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER @@ -855,7 +859,7 @@ char *encode_tv2string(typval_T *tv, size_t *len) if (len != NULL) { *len = (size_t)ga.ga_len; } - ga_append(&ga, '\0'); + ga_append(&ga, NUL); return (char *)ga.ga_data; } @@ -883,7 +887,7 @@ char *encode_tv2echo(typval_T *tv, size_t *len) if (len != NULL) { *len = (size_t)ga.ga_len; } - ga_append(&ga, '\0'); + ga_append(&ga, NUL); return (char *)ga.ga_data; } @@ -908,57 +912,27 @@ char *encode_tv2json(typval_T *tv, size_t *len) if (len != NULL) { *len = (size_t)ga.ga_len; } - ga_append(&ga, '\0'); + ga_append(&ga, NUL); return (char *)ga.ga_data; } #define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \ - do { \ - if ((buf) == NULL) { \ - msgpack_pack_bin(packer, 0); \ - } else { \ - const size_t len_ = (len); \ - msgpack_pack_bin(packer, len_); \ - msgpack_pack_bin_body(packer, buf, len_); \ - } \ - } while (0) + mpack_bin(cbuf_as_string(buf, (len)), packer); \ #define TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len) \ - do { \ - if ((buf) == NULL) { \ - msgpack_pack_str(packer, 0); \ - } else { \ - const size_t len_ = (len); \ - msgpack_pack_str(packer, len_); \ - msgpack_pack_str_body(packer, buf, len_); \ - } \ - } while (0) + mpack_str(cbuf_as_string(buf, (len)), packer); \ #define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type) \ - do { \ - if ((buf) == NULL) { \ - msgpack_pack_ext(packer, 0, (int8_t)(type)); \ - } else { \ - const size_t len_ = (len); \ - msgpack_pack_ext(packer, len_, (int8_t)(type)); \ - msgpack_pack_ext_body(packer, buf, len_); \ - } \ - } while (0) + mpack_ext(buf, (len), (int8_t)(type), packer); \ #define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \ - do { \ - const size_t len_ = (size_t)(len); \ - msgpack_pack_bin(packer, len_); \ - if (len_ > 0) { \ - msgpack_pack_bin_body(packer, (blob)->bv_ga.ga_data, len_); \ - } \ - } while (0) + mpack_bin(cbuf_as_string((blob) ? (blob)->bv_ga.ga_data : NULL, (size_t)(len)), packer); #define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ - msgpack_pack_int64(packer, (int64_t)(num)) + mpack_integer(&packer->ptr, (Integer)(num)) #define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ - msgpack_pack_double(packer, (double)(flt)) + mpack_float8(&packer->ptr, (double)(flt)) #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ return conv_error(_("E5004: Error while dumping %s, %s: " \ @@ -970,33 +944,30 @@ char *encode_tv2json(typval_T *tv, size_t *len) #define TYPVAL_ENCODE_CONV_FUNC_END(tv) #define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \ - msgpack_pack_array(packer, 0) + mpack_array(&packer->ptr, 0) #define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ - msgpack_pack_array(packer, (size_t)(len)) + mpack_array(&packer->ptr, (uint32_t)(len)) #define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ - msgpack_pack_map(packer, 0) + mpack_map(&packer->ptr, 0) + +#define TYPVAL_ENCODE_CHECK_BEFORE \ + mpack_check_buffer(packer) #define TYPVAL_ENCODE_CONV_NIL(tv) \ - msgpack_pack_nil(packer) + mpack_nil(&packer->ptr) #define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ - do { \ - if (num) { \ - msgpack_pack_true(packer); \ - } else { \ - msgpack_pack_false(packer); \ - } \ - } while (0) + mpack_bool(&packer->ptr, (bool)num); \ #define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) \ - msgpack_pack_uint64(packer, (num)) + mpack_uint64(&packer->ptr, (num)) #define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ - msgpack_pack_map(packer, (size_t)(len)) + mpack_map(&packer->ptr, (uint32_t)(len)) #define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) @@ -1021,7 +992,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) #define TYPVAL_ENCODE_SCOPE #define TYPVAL_ENCODE_NAME msgpack -#define TYPVAL_ENCODE_FIRST_ARG_TYPE msgpack_packer *const +#define TYPVAL_ENCODE_FIRST_ARG_TYPE PackerBuffer *const #define TYPVAL_ENCODE_FIRST_ARG_NAME packer #include "nvim/eval/typval_encode.c.h" #undef TYPVAL_ENCODE_SCOPE @@ -1043,6 +1014,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) #undef TYPVAL_ENCODE_CONV_LIST_START #undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT +#undef TYPVAL_ENCODE_CHECK_BEFORE #undef TYPVAL_ENCODE_CONV_NIL #undef TYPVAL_ENCODE_CONV_BOOL #undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h index 6d1c0b61c5..2bacc82b0d 100644 --- a/src/nvim/eval/encode.h +++ b/src/nvim/eval/encode.h @@ -1,10 +1,10 @@ #pragma once -#include <msgpack/pack.h> #include <string.h> #include "nvim/eval/typval_defs.h" #include "nvim/garray_defs.h" +#include "nvim/msgpack_rpc/packer_defs.h" /// Convert Vimscript value to msgpack string /// @@ -13,7 +13,7 @@ /// @param[in] objname Object name, used for error message. /// /// @return OK in case of success, FAIL otherwise. -int encode_vim_to_msgpack(msgpack_packer *packer, typval_T *tv, const char *objname); +int encode_vim_to_msgpack(PackerBuffer *packer, typval_T *tv, const char *objname); /// Convert Vimscript value to :echo output /// diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index 1b8c057d7c..5b92f217d1 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -1,6 +1,7 @@ #include <inttypes.h> #include <stdlib.h> +#include "nvim/errors.h" #include "nvim/eval.h" #include "nvim/eval/executor.h" #include "nvim/eval/typval.h" @@ -20,6 +21,174 @@ char *e_list_index_out_of_range_nr = N_("E684: List index out of range: %" PRId64); +/// Handle "blob1 += blob2". +/// Returns OK or FAIL. +static int tv_op_blob(typval_T *tv1, const typval_T *tv2, const char *op) + FUNC_ATTR_NONNULL_ALL +{ + if (*op != '+' || tv2->v_type != VAR_BLOB) { + return FAIL; + } + + // Blob += Blob + if (tv2->vval.v_blob == NULL) { + return OK; + } + + if (tv1->vval.v_blob == NULL) { + tv1->vval.v_blob = tv2->vval.v_blob; + tv1->vval.v_blob->bv_refcount++; + return OK; + } + + blob_T *const b1 = tv1->vval.v_blob; + blob_T *const b2 = tv2->vval.v_blob; + const int len = tv_blob_len(b2); + + for (int i = 0; i < len; i++) { + ga_append(&b1->bv_ga, tv_blob_get(b2, i)); + } + + return OK; +} + +/// Handle "list1 += list2". +/// Returns OK or FAIL. +static int tv_op_list(typval_T *tv1, const typval_T *tv2, const char *op) + FUNC_ATTR_NONNULL_ALL +{ + if (*op != '+' || tv2->v_type != VAR_LIST) { + return FAIL; + } + + // List += List + if (tv2->vval.v_list == NULL) { + return OK; + } + + if (tv1->vval.v_list == NULL) { + tv1->vval.v_list = tv2->vval.v_list; + tv_list_ref(tv1->vval.v_list); + } else { + tv_list_extend(tv1->vval.v_list, tv2->vval.v_list, NULL); + } + + return OK; +} + +/// Handle number operations: +/// nr += nr , nr -= nr , nr *=nr , nr /= nr , nr %= nr +/// +/// Returns OK or FAIL. +static int tv_op_number(typval_T *tv1, const typval_T *tv2, const char *op) + FUNC_ATTR_NONNULL_ALL +{ + varnumber_T n = tv_get_number(tv1); + if (tv2->v_type == VAR_FLOAT) { + float_T f = (float_T)n; + if (*op == '%') { + return FAIL; + } + switch (*op) { + case '+': + f += tv2->vval.v_float; break; + case '-': + f -= tv2->vval.v_float; break; + case '*': + f *= tv2->vval.v_float; break; + case '/': + f /= tv2->vval.v_float; break; + } + tv_clear(tv1); + tv1->v_type = VAR_FLOAT; + tv1->vval.v_float = f; + } else { + switch (*op) { + case '+': + n += tv_get_number(tv2); break; + case '-': + n -= tv_get_number(tv2); break; + case '*': + n *= tv_get_number(tv2); break; + case '/': + n = num_divide(n, tv_get_number(tv2)); break; + case '%': + n = num_modulus(n, tv_get_number(tv2)); break; + } + tv_clear(tv1); + tv1->v_type = VAR_NUMBER; + tv1->vval.v_number = n; + } + + return OK; +} + +/// Handle "str1 .= str2" +/// Returns OK or FAIL. +static int tv_op_string(typval_T *tv1, const typval_T *tv2, const char *op) + FUNC_ATTR_NONNULL_ALL +{ + if (tv2->v_type == VAR_FLOAT) { + return FAIL; + } + + // str .= str + const char *tvs = tv_get_string(tv1); + char numbuf[NUMBUFLEN]; + char *const s = concat_str(tvs, tv_get_string_buf(tv2, numbuf)); + tv_clear(tv1); + tv1->v_type = VAR_STRING; + tv1->vval.v_string = s; + + return OK; +} + +/// Handle "tv1 += tv2", "tv1 -= tv2", "tv1 *= tv2", "tv1 /= tv2", "tv1 %= tv2" +/// and "tv1 .= tv2" +/// Returns OK or FAIL. +static int tv_op_nr_or_string(typval_T *tv1, const typval_T *tv2, const char *op) + FUNC_ATTR_NONNULL_ALL +{ + if (tv2->v_type == VAR_LIST) { + return FAIL; + } + + if (vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) { + return tv_op_number(tv1, tv2, op); + } + + return tv_op_string(tv1, tv2, op); +} + +/// Handle "f1 += f2", "f1 -= f2", "f1 *= f2", "f1 /= f2". +/// Returns OK or FAIL. +static int tv_op_float(typval_T *tv1, const typval_T *tv2, const char *op) + FUNC_ATTR_NONNULL_ALL +{ + if (*op == '%' || *op == '.' + || (tv2->v_type != VAR_FLOAT + && tv2->v_type != VAR_NUMBER + && tv2->v_type != VAR_STRING)) { + return FAIL; + } + + const float_T f = (tv2->v_type == VAR_FLOAT + ? tv2->vval.v_float + : (float_T)tv_get_number(tv2)); + switch (*op) { + case '+': + tv1->vval.v_float += f; break; + case '-': + tv1->vval.v_float -= f; break; + case '*': + tv1->vval.v_float *= f; break; + case '/': + tv1->vval.v_float /= f; break; + } + + return OK; +} + /// Handle tv1 += tv2, -=, *=, /=, %=, .= /// /// @param[in,out] tv1 First operand, modified typval. @@ -28,125 +197,45 @@ char *e_list_index_out_of_range_nr /// /// @return OK or FAIL. int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, const char *const op) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NO_SANITIZE_UNDEFINED + FUNC_ATTR_NONNULL_ALL { - // Can't do anything with a Funcref, a Dict or special value on the right. - if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT - && tv2->v_type != VAR_BOOL && tv2->v_type != VAR_SPECIAL) { - switch (tv1->v_type) { - case VAR_DICT: - case VAR_FUNC: - case VAR_PARTIAL: - case VAR_BOOL: - case VAR_SPECIAL: - break; - case VAR_BLOB: - if (*op != '+' || tv2->v_type != VAR_BLOB) { - break; - } - // Blob += Blob - if (tv1->vval.v_blob != NULL && tv2->vval.v_blob != NULL) { - blob_T *const b1 = tv1->vval.v_blob; - blob_T *const b2 = tv2->vval.v_blob; - for (int i = 0; i < tv_blob_len(b2); i++) { - ga_append(&b1->bv_ga, tv_blob_get(b2, i)); - } - } - return OK; - case VAR_LIST: - if (*op != '+' || tv2->v_type != VAR_LIST) { - break; - } - // List += List - if (tv1->vval.v_list != NULL && tv2->vval.v_list != NULL) { - tv_list_extend(tv1->vval.v_list, tv2->vval.v_list, NULL); - } - return OK; - case VAR_NUMBER: - case VAR_STRING: - if (tv2->v_type == VAR_LIST) { - break; - } - if (vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) { - // nr += nr or nr -= nr, nr *= nr, nr /= nr, nr %= nr - varnumber_T n = tv_get_number(tv1); - if (tv2->v_type == VAR_FLOAT) { - float_T f = (float_T)n; - - if (*op == '%') { - break; - } - switch (*op) { - case '+': - f += tv2->vval.v_float; break; - case '-': - f -= tv2->vval.v_float; break; - case '*': - f *= tv2->vval.v_float; break; - case '/': - f /= tv2->vval.v_float; break; - } - tv_clear(tv1); - tv1->v_type = VAR_FLOAT; - tv1->vval.v_float = f; - } else { - switch (*op) { - case '+': - n += tv_get_number(tv2); break; - case '-': - n -= tv_get_number(tv2); break; - case '*': - n *= tv_get_number(tv2); break; - case '/': - n = num_divide(n, tv_get_number(tv2)); break; - case '%': - n = num_modulus(n, tv_get_number(tv2)); break; - } - tv_clear(tv1); - tv1->v_type = VAR_NUMBER; - tv1->vval.v_number = n; - } - } else { - // str .= str - if (tv2->v_type == VAR_FLOAT) { - break; - } - const char *tvs = tv_get_string(tv1); - char numbuf[NUMBUFLEN]; - char *const s = - concat_str(tvs, tv_get_string_buf(tv2, numbuf)); - tv_clear(tv1); - tv1->v_type = VAR_STRING; - tv1->vval.v_string = s; - } - return OK; - case VAR_FLOAT: { - if (*op == '%' || *op == '.' - || (tv2->v_type != VAR_FLOAT - && tv2->v_type != VAR_NUMBER - && tv2->v_type != VAR_STRING)) { - break; - } - const float_T f = (tv2->v_type == VAR_FLOAT - ? tv2->vval.v_float - : (float_T)tv_get_number(tv2)); - switch (*op) { - case '+': - tv1->vval.v_float += f; break; - case '-': - tv1->vval.v_float -= f; break; - case '*': - tv1->vval.v_float *= f; break; - case '/': - tv1->vval.v_float /= f; break; - } - return OK; - } - case VAR_UNKNOWN: - abort(); - } + // Can't do anything with a Funcref or Dict on the right. + // v:true and friends only work with "..=". + if (tv2->v_type == VAR_FUNC || tv2->v_type == VAR_DICT + || ((tv2->v_type == VAR_BOOL || tv2->v_type == VAR_SPECIAL) && *op == '.')) { + semsg(_(e_letwrong), op); + return FAIL; + } + + int retval = FAIL; + + switch (tv1->v_type) { + case VAR_DICT: + case VAR_FUNC: + case VAR_PARTIAL: + case VAR_BOOL: + case VAR_SPECIAL: + break; + case VAR_BLOB: + retval = tv_op_blob(tv1, tv2, op); + break; + case VAR_LIST: + retval = tv_op_list(tv1, tv2, op); + break; + case VAR_NUMBER: + case VAR_STRING: + retval = tv_op_nr_or_string(tv1, tv2, op); + break; + case VAR_FLOAT: + retval = tv_op_float(tv1, tv2, op); + break; + case VAR_UNKNOWN: + abort(); + } + + if (retval != OK) { + semsg(_(e_letwrong), op); } - semsg(_(e_letwrong), op); - return FAIL; + return retval; } diff --git a/src/nvim/eval/fs.c b/src/nvim/eval/fs.c new file mode 100644 index 0000000000..f5b33c804e --- /dev/null +++ b/src/nvim/eval/fs.c @@ -0,0 +1,1492 @@ +// eval/fs.c: Filesystem related builtin functions + +#include <assert.h> +#include <limits.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "auto/config.h" +#include "nvim/ascii_defs.h" +#include "nvim/buffer_defs.h" +#include "nvim/cmdexpand.h" +#include "nvim/cmdexpand_defs.h" +#include "nvim/errors.h" +#include "nvim/eval.h" +#include "nvim/eval/fs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/userfunc.h" +#include "nvim/eval/window.h" +#include "nvim/ex_cmds.h" +#include "nvim/ex_docmd.h" +#include "nvim/file_search.h" +#include "nvim/fileio.h" +#include "nvim/garray.h" +#include "nvim/garray_defs.h" +#include "nvim/gettext_defs.h" +#include "nvim/globals.h" +#include "nvim/macros_defs.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option_vars.h" +#include "nvim/os/fileio.h" +#include "nvim/os/fileio_defs.h" +#include "nvim/os/fs.h" +#include "nvim/os/fs_defs.h" +#include "nvim/os/os_defs.h" +#include "nvim/path.h" +#include "nvim/pos_defs.h" +#include "nvim/strings.h" +#include "nvim/types_defs.h" +#include "nvim/vim_defs.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/fs.c.generated.h" +#endif + +static const char e_error_while_writing_str[] = N_("E80: Error while writing: %s"); + +/// "chdir(dir)" function +void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + if (argvars[0].v_type != VAR_STRING) { + // Returning an empty string means it failed. + // No error message, for historic reasons. + return; + } + + // Return the current directory + char *cwd = xmalloc(MAXPATHL); + if (os_dirname(cwd, MAXPATHL) != FAIL) { +#ifdef BACKSLASH_IN_FILENAME + slash_adjust(cwd); +#endif + rettv->vval.v_string = xstrdup(cwd); + } + xfree(cwd); + + CdScope scope = kCdScopeGlobal; + if (curwin->w_localdir != NULL) { + scope = kCdScopeWindow; + } else if (curtab->tp_localdir != NULL) { + scope = kCdScopeTabpage; + } + + if (!changedir_func(argvars[0].vval.v_string, scope)) { + // Directory change failed + XFREE_CLEAR(rettv->vval.v_string); + } +} + +/// "delete()" function +void f_delete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = -1; + if (check_secure()) { + return; + } + + const char *const name = tv_get_string(&argvars[0]); + if (*name == NUL) { + emsg(_(e_invarg)); + return; + } + + char nbuf[NUMBUFLEN]; + const char *flags; + if (argvars[1].v_type != VAR_UNKNOWN) { + flags = tv_get_string_buf(&argvars[1], nbuf); + } else { + flags = ""; + } + + if (*flags == NUL) { + // delete a file + rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1; + } else if (strcmp(flags, "d") == 0) { + // delete an empty directory + rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1; + } else if (strcmp(flags, "rf") == 0) { + // delete a directory recursively + rettv->vval.v_number = delete_recursive(name); + } else { + semsg(_(e_invexpr2), flags); + } +} + +/// "executable()" function +void f_executable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (tv_check_for_string_arg(argvars, 0) == FAIL) { + return; + } + + // Check in $PATH and also check directly if there is a directory name + rettv->vval.v_number = os_can_exe(tv_get_string(&argvars[0]), NULL, true); +} + +/// "exepath()" function +void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (tv_check_for_nonempty_string_arg(argvars, 0) == FAIL) { + return; + } + + char *path = NULL; + + os_can_exe(tv_get_string(&argvars[0]), &path, true); + +#ifdef BACKSLASH_IN_FILENAME + if (path != NULL) { + slash_adjust(path); + } +#endif + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = path; +} + +/// "filecopy()" function +void f_filecopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = false; + + if (check_secure() + || tv_check_for_string_arg(argvars, 0) == FAIL + || tv_check_for_string_arg(argvars, 1) == FAIL) { + return; + } + + const char *from = tv_get_string(&argvars[0]); + + FileInfo from_info; + if (os_fileinfo_link(from, &from_info) + && (S_ISREG(from_info.stat.st_mode) || S_ISLNK(from_info.stat.st_mode))) { + rettv->vval.v_number + = vim_copyfile(tv_get_string(&argvars[0]), tv_get_string(&argvars[1])) == OK; + } +} + +/// "filereadable()" function +void f_filereadable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const char *const p = tv_get_string(&argvars[0]); + rettv->vval.v_number = (*p && !os_isdir(p) && os_file_is_readable(p)); +} + +/// @return 0 for not writable +/// 1 for writable file +/// 2 for a dir which we have rights to write into. +void f_filewritable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const char *filename = tv_get_string(&argvars[0]); + rettv->vval.v_number = os_file_is_writable(filename); +} + +static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) +{ + char *fresult = NULL; + char *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; + int count = 1; + bool first = true; + bool error = false; + + rettv->vval.v_string = NULL; + rettv->v_type = VAR_STRING; + + const char *fname = tv_get_string(&argvars[0]); + + char pathbuf[NUMBUFLEN]; + if (argvars[1].v_type != VAR_UNKNOWN) { + const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf); + if (p == NULL) { + error = true; + } else { + if (*p != NUL) { + path = (char *)p; + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + count = (int)tv_get_number_chk(&argvars[2], &error); + } + } + } + + if (count < 0) { + tv_list_alloc_ret(rettv, kListLenUnknown); + } + + if (*fname != NUL && !error) { + char *file_to_find = NULL; + char *search_ctx = NULL; + + do { + if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) { + xfree(fresult); + } + fresult = find_file_in_path_option(first ? (char *)fname : NULL, + first ? strlen(fname) : 0, + 0, first, path, + find_what, curbuf->b_ffname, + (find_what == FINDFILE_DIR + ? "" + : curbuf->b_p_sua), + &file_to_find, &search_ctx); + first = false; + + if (fresult != NULL && rettv->v_type == VAR_LIST) { + tv_list_append_string(rettv->vval.v_list, fresult, -1); + } + } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL); + + xfree(file_to_find); + vim_findfile_cleanup(search_ctx); + } + + if (rettv->v_type == VAR_STRING) { + rettv->vval.v_string = fresult; + } +} + +/// "finddir({fname}[, {path}[, {count}]])" function +void f_finddir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + findfilendir(argvars, rettv, FINDFILE_DIR); +} + +/// "findfile({fname}[, {path}[, {count}]])" function +void f_findfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + findfilendir(argvars, rettv, FINDFILE_FILE); +} + +/// `getcwd([{win}[, {tab}]])` function +/// +/// Every scope not specified implies the currently selected scope object. +/// +/// @pre The arguments must be of type number. +/// @pre There may not be more than two arguments. +/// @pre An argument may not be -1 if preceding arguments are not all -1. +/// +/// @post The return value will be a string. +void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + // Possible scope of working directory to return. + CdScope scope = kCdScopeInvalid; + + // Numbers of the scope objects (window, tab) we want the working directory + // of. A `-1` means to skip this scope, a `0` means the current object. + int scope_number[] = { + [kCdScopeWindow] = 0, // Number of window to look at. + [kCdScopeTabpage] = 0, // Number of tab to look at. + }; + + char *cwd = NULL; // Current working directory to print + char *from = NULL; // The original string to copy + + tabpage_T *tp = curtab; // The tabpage to look at. + win_T *win = curwin; // The window to look at. + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + // Pre-conditions and scope extraction together + for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { + // If there is no argument there are no more scopes after it, break out. + if (argvars[i].v_type == VAR_UNKNOWN) { + break; + } + if (argvars[i].v_type != VAR_NUMBER) { + emsg(_(e_invarg)); + return; + } + scope_number[i] = (int)argvars[i].vval.v_number; + // It is an error for the scope number to be less than `-1`. + if (scope_number[i] < -1) { + emsg(_(e_invarg)); + return; + } + // Use the narrowest scope the user requested + if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { + // The scope is the current iteration step. + scope = i; + } else if (scope_number[i] < 0) { + scope = i + 1; + } + } + + // Find the tabpage by number + if (scope_number[kCdScopeTabpage] > 0) { + tp = find_tabpage(scope_number[kCdScopeTabpage]); + if (!tp) { + emsg(_("E5000: Cannot find tab number.")); + return; + } + } + + // Find the window in `tp` by number, `NULL` if none. + if (scope_number[kCdScopeWindow] >= 0) { + if (scope_number[kCdScopeTabpage] < 0) { + emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); + return; + } + + if (scope_number[kCdScopeWindow] > 0) { + win = find_win_by_nr(&argvars[0], tp); + if (!win) { + emsg(_("E5002: Cannot find window number.")); + return; + } + } + } + + cwd = xmalloc(MAXPATHL); + + switch (scope) { + case kCdScopeWindow: + assert(win); + from = win->w_localdir; + if (from) { + break; + } + FALLTHROUGH; + case kCdScopeTabpage: + assert(tp); + from = tp->tp_localdir; + if (from) { + break; + } + FALLTHROUGH; + case kCdScopeGlobal: + if (globaldir) { // `globaldir` is not always set. + from = globaldir; + break; + } + FALLTHROUGH; // In global directory, just need to get OS CWD. + case kCdScopeInvalid: // If called without any arguments, get OS CWD. + if (os_dirname(cwd, MAXPATHL) == FAIL) { + from = ""; // Return empty string on failure. + } + } + + if (from) { + xstrlcpy(cwd, from, MAXPATHL); + } + + rettv->vval.v_string = xstrdup(cwd); +#ifdef BACKSLASH_IN_FILENAME + slash_adjust(rettv->vval.v_string); +#endif + + xfree(cwd); +} + +/// "getfperm({fname})" function +void f_getfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + char *perm = NULL; + char flags[] = "rwx"; + + const char *filename = tv_get_string(&argvars[0]); + int32_t file_perm = os_getperm(filename); + if (file_perm >= 0) { + perm = xstrdup("---------"); + for (int i = 0; i < 9; i++) { + if (file_perm & (1 << (8 - i))) { + perm[i] = flags[i % 3]; + } + } + } + rettv->v_type = VAR_STRING; + rettv->vval.v_string = perm; +} + +/// "getfsize({fname})" function +void f_getfsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const char *fname = tv_get_string(&argvars[0]); + + rettv->v_type = VAR_NUMBER; + + FileInfo file_info; + if (os_fileinfo(fname, &file_info)) { + uint64_t filesize = os_fileinfo_size(&file_info); + if (os_isdir(fname)) { + rettv->vval.v_number = 0; + } else { + rettv->vval.v_number = (varnumber_T)filesize; + + // non-perfect check for overflow + if ((uint64_t)rettv->vval.v_number != filesize) { + rettv->vval.v_number = -2; + } + } + } else { + rettv->vval.v_number = -1; + } +} + +/// "getftime({fname})" function +void f_getftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const char *fname = tv_get_string(&argvars[0]); + + FileInfo file_info; + if (os_fileinfo(fname, &file_info)) { + rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec; + } else { + rettv->vval.v_number = -1; + } +} + +/// "getftype({fname})" function +void f_getftype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + char *type = NULL; + char *t; + + const char *fname = tv_get_string(&argvars[0]); + + rettv->v_type = VAR_STRING; + FileInfo file_info; + if (os_fileinfo_link(fname, &file_info)) { + uint64_t mode = file_info.stat.st_mode; + if (S_ISREG(mode)) { + t = "file"; + } else if (S_ISDIR(mode)) { + t = "dir"; + } else if (S_ISLNK(mode)) { + t = "link"; + } else if (S_ISBLK(mode)) { + t = "bdev"; + } else if (S_ISCHR(mode)) { + t = "cdev"; + } else if (S_ISFIFO(mode)) { + t = "fifo"; + } else if (S_ISSOCK(mode)) { + t = "socket"; + } else { + t = "other"; + } + type = xstrdup(t); + } + rettv->vval.v_string = type; +} + +/// "glob()" function +void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int options = WILD_SILENT|WILD_USE_NL; + expand_T xpc; + bool error = false; + + // When the optional second argument is non-zero, don't remove matches + // for 'wildignore' and don't put matches for 'suffixes' at the end. + rettv->v_type = VAR_STRING; + if (argvars[1].v_type != VAR_UNKNOWN) { + if (tv_get_number_chk(&argvars[1], &error)) { + options |= WILD_KEEP_ALL; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + if (tv_get_number_chk(&argvars[2], &error)) { + tv_list_set_ret(rettv, NULL); + } + if (argvars[3].v_type != VAR_UNKNOWN + && tv_get_number_chk(&argvars[3], &error)) { + options |= WILD_ALLLINKS; + } + } + } + if (!error) { + ExpandInit(&xpc); + xpc.xp_context = EXPAND_FILES; + if (p_wic) { + options += WILD_ICASE; + } + if (rettv->v_type == VAR_STRING) { + rettv->vval.v_string = ExpandOne(&xpc, (char *) + tv_get_string(&argvars[0]), NULL, options, + WILD_ALL); + } else { + ExpandOne(&xpc, (char *)tv_get_string(&argvars[0]), NULL, options, + WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); + for (int i = 0; i < xpc.xp_numfiles; i++) { + tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); + } + ExpandCleanup(&xpc); + } + } else { + rettv->vval.v_string = NULL; + } +} + +/// "globpath()" function +void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int flags = WILD_IGNORE_COMPLETESLASH; // Flags for globpath. + bool error = false; + + // Return a string, or a list if the optional third argument is non-zero. + rettv->v_type = VAR_STRING; + + if (argvars[2].v_type != VAR_UNKNOWN) { + // When the optional second argument is non-zero, don't remove matches + // for 'wildignore' and don't put matches for 'suffixes' at the end. + if (tv_get_number_chk(&argvars[2], &error)) { + flags |= WILD_KEEP_ALL; + } + + if (argvars[3].v_type != VAR_UNKNOWN) { + if (tv_get_number_chk(&argvars[3], &error)) { + tv_list_set_ret(rettv, NULL); + } + if (argvars[4].v_type != VAR_UNKNOWN + && tv_get_number_chk(&argvars[4], &error)) { + flags |= WILD_ALLLINKS; + } + } + } + + char buf1[NUMBUFLEN]; + const char *const file = tv_get_string_buf_chk(&argvars[1], buf1); + if (file != NULL && !error) { + garray_T ga; + ga_init(&ga, (int)sizeof(char *), 10); + globpath((char *)tv_get_string(&argvars[0]), (char *)file, &ga, flags, false); + + if (rettv->v_type == VAR_STRING) { + rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); + } else { + tv_list_alloc_ret(rettv, ga.ga_len); + for (int i = 0; i < ga.ga_len; i++) { + tv_list_append_string(rettv->vval.v_list, + ((const char **)(ga.ga_data))[i], -1); + } + } + + ga_clear_strings(&ga); + } else { + rettv->vval.v_string = NULL; + } +} + +/// "glob2regpat()" function +void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = pat == NULL ? NULL : file_pat_to_reg_pat(pat, NULL, NULL, false); +} + +/// `haslocaldir([{win}[, {tab}]])` function +/// +/// Returns `1` if the scope object has a local directory, `0` otherwise. If a +/// scope object is not specified the current one is implied. This function +/// share a lot of code with `f_getcwd`. +/// +/// @pre The arguments must be of type number. +/// @pre There may not be more than two arguments. +/// @pre An argument may not be -1 if preceding arguments are not all -1. +/// +/// @post The return value will be either the number `1` or `0`. +void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + // Possible scope of working directory to return. + CdScope scope = kCdScopeInvalid; + + // Numbers of the scope objects (window, tab) we want the working directory + // of. A `-1` means to skip this scope, a `0` means the current object. + int scope_number[] = { + [kCdScopeWindow] = 0, // Number of window to look at. + [kCdScopeTabpage] = 0, // Number of tab to look at. + }; + + tabpage_T *tp = curtab; // The tabpage to look at. + win_T *win = curwin; // The window to look at. + + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + // Pre-conditions and scope extraction together + for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { + if (argvars[i].v_type == VAR_UNKNOWN) { + break; + } + if (argvars[i].v_type != VAR_NUMBER) { + emsg(_(e_invarg)); + return; + } + scope_number[i] = (int)argvars[i].vval.v_number; + if (scope_number[i] < -1) { + emsg(_(e_invarg)); + return; + } + // Use the narrowest scope the user requested + if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { + // The scope is the current iteration step. + scope = i; + } else if (scope_number[i] < 0) { + scope = i + 1; + } + } + + // If the user didn't specify anything, default to window scope + if (scope == kCdScopeInvalid) { + scope = MIN_CD_SCOPE; + } + + // Find the tabpage by number + if (scope_number[kCdScopeTabpage] > 0) { + tp = find_tabpage(scope_number[kCdScopeTabpage]); + if (!tp) { + emsg(_("E5000: Cannot find tab number.")); + return; + } + } + + // Find the window in `tp` by number, `NULL` if none. + if (scope_number[kCdScopeWindow] >= 0) { + if (scope_number[kCdScopeTabpage] < 0) { + emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); + return; + } + + if (scope_number[kCdScopeWindow] > 0) { + win = find_win_by_nr(&argvars[0], tp); + if (!win) { + emsg(_("E5002: Cannot find window number.")); + return; + } + } + } + + switch (scope) { + case kCdScopeWindow: + assert(win); + rettv->vval.v_number = win->w_localdir ? 1 : 0; + break; + case kCdScopeTabpage: + assert(tp); + rettv->vval.v_number = tp->tp_localdir ? 1 : 0; + break; + case kCdScopeGlobal: + // The global scope never has a local directory + break; + case kCdScopeInvalid: + // We should never get here + abort(); + } +} + +/// "isabsolutepath()" function +void f_isabsolutepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = path_is_absolute(tv_get_string(&argvars[0])); +} + +/// "isdirectory()" function +void f_isdirectory(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = os_isdir(tv_get_string(&argvars[0])); +} + +/// "mkdir()" function +void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int prot = 0755; + + rettv->vval.v_number = FAIL; + if (check_secure()) { + return; + } + + char buf[NUMBUFLEN]; + const char *const dir = tv_get_string_buf(&argvars[0], buf); + if (*dir == NUL) { + return; + } + + if (*path_tail(dir) == NUL) { + // Remove trailing slashes. + *path_tail_with_sep((char *)dir) = NUL; + } + + bool defer = false; + bool defer_recurse = false; + char *created = NULL; + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[2].v_type != VAR_UNKNOWN) { + prot = (int)tv_get_number_chk(&argvars[2], NULL); + if (prot == -1) { + return; + } + } + const char *arg2 = tv_get_string(&argvars[1]); + defer = vim_strchr(arg2, 'D') != NULL; + defer_recurse = vim_strchr(arg2, 'R') != NULL; + if ((defer || defer_recurse) && !can_add_defer()) { + return; + } + + if (vim_strchr(arg2, 'p') != NULL) { + char *failed_dir; + int ret = os_mkdir_recurse(dir, prot, &failed_dir, + defer || defer_recurse ? &created : NULL); + if (ret != 0) { + semsg(_(e_mkdir), failed_dir, os_strerror(ret)); + xfree(failed_dir); + rettv->vval.v_number = FAIL; + return; + } + rettv->vval.v_number = OK; + } + } + if (rettv->vval.v_number == FAIL) { + rettv->vval.v_number = vim_mkdir_emsg(dir, prot); + } + + // Handle "D" and "R": deferred deletion of the created directory. + if (rettv->vval.v_number == OK + && created == NULL && (defer || defer_recurse)) { + created = FullName_save(dir, false); + } + if (created != NULL) { + typval_T tv[2]; + tv[0].v_type = VAR_STRING; + tv[0].v_lock = VAR_UNLOCKED; + tv[0].vval.v_string = created; + tv[1].v_type = VAR_STRING; + tv[1].v_lock = VAR_UNLOCKED; + tv[1].vval.v_string = xstrdup(defer_recurse ? "rf" : "d"); + add_defer("delete", 2, tv); + } +} + +/// "pathshorten()" function +void f_pathshorten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int trim_len = 1; + + if (argvars[1].v_type != VAR_UNKNOWN) { + trim_len = (int)tv_get_number(&argvars[1]); + if (trim_len < 1) { + trim_len = 1; + } + } + + rettv->v_type = VAR_STRING; + const char *p = tv_get_string_chk(&argvars[0]); + if (p == NULL) { + rettv->vval.v_string = NULL; + } else { + rettv->vval.v_string = xstrdup(p); + shorten_dir_len(rettv->vval.v_string, trim_len); + } +} + +/// Evaluate "expr" (= "context") for readdir(). +static varnumber_T readdir_checkitem(void *context, const char *name) + FUNC_ATTR_NONNULL_ALL +{ + typval_T *expr = (typval_T *)context; + typval_T argv[2]; + varnumber_T retval = 0; + bool error = false; + + if (expr->v_type == VAR_UNKNOWN) { + return 1; + } + + typval_T save_val; + prepare_vimvar(VV_VAL, &save_val); + set_vim_var_string(VV_VAL, name, -1); + argv[0].v_type = VAR_STRING; + argv[0].vval.v_string = (char *)name; + + typval_T rettv; + if (eval_expr_typval(expr, false, argv, 1, &rettv) == FAIL) { + goto theend; + } + + retval = tv_get_number_chk(&rettv, &error); + if (error) { + retval = -1; + } + + tv_clear(&rettv); + +theend: + set_vim_var_string(VV_VAL, NULL, 0); + restore_vimvar(VV_VAL, &save_val); + return retval; +} + +/// "readdir()" function +void f_readdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_list_alloc_ret(rettv, kListLenUnknown); + + const char *path = tv_get_string(&argvars[0]); + typval_T *expr = &argvars[1]; + garray_T ga; + int ret = readdir_core(&ga, path, (void *)expr, readdir_checkitem); + if (ret == OK && ga.ga_len > 0) { + for (int i = 0; i < ga.ga_len; i++) { + const char *p = ((const char **)ga.ga_data)[i]; + tv_list_append_string(rettv->vval.v_list, p, -1); + } + } + ga_clear_strings(&ga); +} + +/// Read blob from file "fd". +/// Caller has allocated a blob in "rettv". +/// +/// @param[in] fd File to read from. +/// @param[in,out] rettv Blob to write to. +/// @param[in] offset Read the file from the specified offset. +/// @param[in] size Read the specified size, or -1 if no limit. +/// +/// @return OK on success, or FAIL on failure. +static int read_blob(FILE *const fd, typval_T *rettv, off_T offset, off_T size_arg) + FUNC_ATTR_NONNULL_ALL +{ + blob_T *const blob = rettv->vval.v_blob; + FileInfo file_info; + if (!os_fileinfo_fd(fileno(fd), &file_info)) { + return FAIL; // can't read the file, error + } + + int whence; + off_T size = size_arg; + const off_T file_size = (off_T)os_fileinfo_size(&file_info); + if (offset >= 0) { + // The size defaults to the whole file. If a size is given it is + // limited to not go past the end of the file. + if (size == -1 || (size > file_size - offset && !S_ISCHR(file_info.stat.st_mode))) { + // size may become negative, checked below + size = (off_T)os_fileinfo_size(&file_info) - offset; + } + whence = SEEK_SET; + } else { + // limit the offset to not go before the start of the file + if (-offset > file_size && !S_ISCHR(file_info.stat.st_mode)) { + offset = -file_size; + } + // Size defaults to reading until the end of the file. + if (size == -1 || size > -offset) { + size = -offset; + } + whence = SEEK_END; + } + if (size <= 0) { + return OK; + } + if (offset != 0 && vim_fseek(fd, offset, whence) != 0) { + return OK; + } + + ga_grow(&blob->bv_ga, (int)size); + blob->bv_ga.ga_len = (int)size; + if (fread(blob->bv_ga.ga_data, 1, (size_t)blob->bv_ga.ga_len, fd) + < (size_t)blob->bv_ga.ga_len) { + // An empty blob is returned on error. + tv_blob_free(rettv->vval.v_blob); + rettv->vval.v_blob = NULL; + return FAIL; + } + return OK; +} + +/// "readfile()" or "readblob()" function +static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_blob) +{ + bool binary = false; + bool blob = always_blob; + FILE *fd; + char buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 + int io_size = sizeof(buf); + char *prev = NULL; // previously read bytes, if any + ptrdiff_t prevlen = 0; // length of data in prev + ptrdiff_t prevsize = 0; // size of prev buffer + int64_t maxline = MAXLNUM; + off_T offset = 0; + off_T size = -1; + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (always_blob) { + offset = (off_T)tv_get_number(&argvars[1]); + if (argvars[2].v_type != VAR_UNKNOWN) { + size = (off_T)tv_get_number(&argvars[2]); + } + } else { + if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { + binary = true; + } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) { + blob = true; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + maxline = tv_get_number(&argvars[2]); + } + } + } + + if (blob) { + tv_blob_alloc_ret(rettv); + } else { + tv_list_alloc_ret(rettv, kListLenUnknown); + } + + // Always open the file in binary mode, library functions have a mind of + // their own about CR-LF conversion. + const char *const fname = tv_get_string(&argvars[0]); + + if (os_isdir(fname)) { + semsg(_(e_isadir2), fname); + return; + } + if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) { + semsg(_(e_notopen), *fname == NUL ? _("<empty>") : fname); + return; + } + + if (blob) { + if (read_blob(fd, rettv, offset, size) == FAIL) { + semsg(_(e_notread), fname); + } + fclose(fd); + return; + } + + list_T *const l = rettv->vval.v_list; + + while (maxline < 0 || tv_list_len(l) < maxline) { + int readlen = (int)fread(buf, 1, (size_t)io_size, fd); + + // This for loop processes what was read, but is also entered at end + // of file so that either: + // - an incomplete line gets written + // - a "binary" file gets an empty line at the end if it ends in a + // newline. + char *p; // Position in buf. + char *start; // Start of current line. + for (p = buf, start = buf; + p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); + p++) { + if (readlen <= 0 || *p == '\n') { + char *s = NULL; + size_t len = (size_t)(p - start); + + // Finished a line. Remove CRs before NL. + if (readlen > 0 && !binary) { + while (len > 0 && start[len - 1] == '\r') { + len--; + } + // removal may cross back to the "prev" string + if (len == 0) { + while (prevlen > 0 && prev[prevlen - 1] == '\r') { + prevlen--; + } + } + } + if (prevlen == 0) { + assert(len < INT_MAX); + s = xmemdupz(start, len); + } else { + // Change "prev" buffer to be the right size. This way + // the bytes are only copied once, and very long lines are + // allocated only once. + s = xrealloc(prev, (size_t)prevlen + len + 1); + memcpy(s + prevlen, start, len); + s[(size_t)prevlen + len] = NUL; + prev = NULL; // the list will own the string + prevlen = prevsize = 0; + } + + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = s, + }); + + start = p + 1; // Step over newline. + if (maxline < 0) { + if (tv_list_len(l) > -maxline) { + assert(tv_list_len(l) == 1 + (-maxline)); + tv_list_item_remove(l, tv_list_first(l)); + } + } else if (tv_list_len(l) >= maxline) { + assert(tv_list_len(l) == maxline); + break; + } + if (readlen <= 0) { + break; + } + } else if (*p == NUL) { + *p = '\n'; + // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this + // when finding the BF and check the previous two bytes. + } else if ((uint8_t)(*p) == 0xbf && !binary) { + // Find the two bytes before the 0xbf. If p is at buf, or buf + 1, + // these may be in the "prev" string. + char back1 = p >= buf + 1 ? p[-1] + : prevlen >= 1 ? prev[prevlen - 1] : NUL; + char back2 = p >= buf + 2 ? p[-2] + : (p == buf + 1 && prevlen >= 1 + ? prev[prevlen - 1] + : prevlen >= 2 ? prev[prevlen - 2] : NUL); + + if ((uint8_t)back2 == 0xef && (uint8_t)back1 == 0xbb) { + char *dest = p - 2; + + // Usually a BOM is at the beginning of a file, and so at + // the beginning of a line; then we can just step over it. + if (start == dest) { + start = p + 1; + } else { + // have to shuffle buf to close gap + int adjust_prevlen = 0; + + if (dest < buf) { + // adjust_prevlen must be 1 or 2. + adjust_prevlen = (int)(buf - dest); + dest = buf; + } + if (readlen > p - buf + 1) { + memmove(dest, p + 1, (size_t)readlen - (size_t)(p - buf) - 1); + } + readlen -= 3 - adjust_prevlen; + prevlen -= adjust_prevlen; + p = dest - 1; + } + } + } + } // for + + if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) { + break; + } + if (start < p) { + // There's part of a line in buf, store it in "prev". + if (p - start + prevlen >= prevsize) { + // A common use case is ordinary text files and "prev" gets a + // fragment of a line, so the first allocation is made + // small, to avoid repeatedly 'allocing' large and + // 'reallocing' small. + if (prevsize == 0) { + prevsize = p - start; + } else { + ptrdiff_t grow50pc = (prevsize * 3) / 2; + ptrdiff_t growmin = (p - start) * 2 + prevlen; + prevsize = grow50pc > growmin ? grow50pc : growmin; + } + prev = xrealloc(prev, (size_t)prevsize); + } + // Add the line part to end of "prev". + memmove(prev + prevlen, start, (size_t)(p - start)); + prevlen += p - start; + } + } // while + + xfree(prev); + fclose(fd); +} + +/// "readblob()" function +void f_readblob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + read_file_or_blob(argvars, rettv, true); +} + +/// "readfile()" function +void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + read_file_or_blob(argvars, rettv, false); +} + +/// "rename({from}, {to})" function +void f_rename(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (check_secure()) { + rettv->vval.v_number = -1; + } else { + char buf[NUMBUFLEN]; + rettv->vval.v_number = vim_rename(tv_get_string(&argvars[0]), + tv_get_string_buf(&argvars[1], buf)); + } +} + +/// "resolve()" function +void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_STRING; + const char *fname = tv_get_string(&argvars[0]); +#ifdef MSWIN + char *v = os_resolve_shortcut(fname); + if (v == NULL) { + if (os_is_reparse_point_include(fname)) { + v = os_realpath(fname, NULL, MAXPATHL + 1); + } + } + rettv->vval.v_string = (v == NULL ? xstrdup(fname) : v); +#else +# ifdef HAVE_READLINK + { + bool is_relative_to_current = false; + bool has_trailing_pathsep = false; + int limit = 100; + + char *p = xstrdup(fname); + + if (p[0] == '.' && (vim_ispathsep(p[1]) + || (p[1] == '.' && (vim_ispathsep(p[2]))))) { + is_relative_to_current = true; + } + + ptrdiff_t len = (ptrdiff_t)strlen(p); + if (len > 1 && after_pathsep(p, p + len)) { + has_trailing_pathsep = true; + p[len - 1] = NUL; // The trailing slash breaks readlink(). + } + + char *q = (char *)path_next_component(p); + char *remain = NULL; + if (*q != NUL) { + // Separate the first path component in "p", and keep the + // remainder (beginning with the path separator). + remain = xstrdup(q - 1); + q[-1] = NUL; + } + + char *const buf = xmallocz(MAXPATHL); + + char *cpy; + while (true) { + while (true) { + len = readlink(p, buf, MAXPATHL); + if (len <= 0) { + break; + } + buf[len] = NUL; + + if (limit-- == 0) { + xfree(p); + xfree(remain); + emsg(_("E655: Too many symbolic links (cycle?)")); + rettv->vval.v_string = NULL; + xfree(buf); + return; + } + + // Ensure that the result will have a trailing path separator + // if the argument has one. + if (remain == NULL && has_trailing_pathsep) { + add_pathsep(buf); + } + + // Separate the first path component in the link value and + // concatenate the remainders. + q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf); + if (*q != NUL) { + cpy = remain; + remain = remain != NULL ? concat_str(q - 1, remain) : xstrdup(q - 1); + xfree(cpy); + q[-1] = NUL; + } + + q = path_tail(p); + if (q > p && *q == NUL) { + // Ignore trailing path separator. + p[q - p - 1] = NUL; + q = path_tail(p); + } + if (q > p && !path_is_absolute(buf)) { + // Symlink is relative to directory of argument. Replace the + // symlink with the resolved name in the same directory. + const size_t p_len = strlen(p); + const size_t buf_len = strlen(buf); + p = xrealloc(p, p_len + buf_len + 1); + memcpy(path_tail(p), buf, buf_len + 1); + } else { + xfree(p); + p = xstrdup(buf); + } + } + + if (remain == NULL) { + break; + } + + // Append the first path component of "remain" to "p". + q = (char *)path_next_component(remain + 1); + len = q - remain - (*q != NUL); + const size_t p_len = strlen(p); + cpy = xmallocz(p_len + (size_t)len); + memcpy(cpy, p, p_len + 1); + xstrlcat(cpy + p_len, remain, (size_t)len + 1); + xfree(p); + p = cpy; + + // Shorten "remain". + if (*q != NUL) { + STRMOVE(remain, q - 1); + } else { + XFREE_CLEAR(remain); + } + } + + // If the result is a relative path name, make it explicitly relative to + // the current directory if and only if the argument had this form. + if (!vim_ispathsep(*p)) { + if (is_relative_to_current + && *p != NUL + && !(p[0] == '.' + && (p[1] == NUL + || vim_ispathsep(p[1]) + || (p[1] == '.' + && (p[2] == NUL + || vim_ispathsep(p[2])))))) { + // Prepend "./". + cpy = concat_str("./", p); + xfree(p); + p = cpy; + } else if (!is_relative_to_current) { + // Strip leading "./". + q = p; + while (q[0] == '.' && vim_ispathsep(q[1])) { + q += 2; + } + if (q > p) { + STRMOVE(p, p + 2); + } + } + } + + // Ensure that the result will have no trailing path separator + // if the argument had none. But keep "/" or "//". + if (!has_trailing_pathsep) { + q = p + strlen(p); + if (after_pathsep(p, q)) { + *path_tail_with_sep(p) = NUL; + } + } + + rettv->vval.v_string = p; + xfree(buf); + } +# else + char *v = os_realpath(fname, NULL, MAXPATHL + 1); + rettv->vval.v_string = v == NULL ? xstrdup(fname) : v; +# endif +#endif + + simplify_filename(rettv->vval.v_string); +} + +/// "simplify()" function +void f_simplify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const char *const p = tv_get_string(&argvars[0]); + rettv->vval.v_string = xstrdup(p); + simplify_filename(rettv->vval.v_string); // Simplify in place. + rettv->v_type = VAR_STRING; +} + +/// "tempname()" function +void f_tempname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = vim_tempname(); +} + +/// Write "list" of strings to file "fd". +/// +/// @param fp File to write to. +/// @param[in] list List to write. +/// @param[in] binary Whether to write in binary mode. +/// +/// @return true in case of success, false otherwise. +static bool write_list(FileDescriptor *const fp, const list_T *const list, const bool binary) + FUNC_ATTR_NONNULL_ARG(1) +{ + int error = 0; + TV_LIST_ITER_CONST(list, li, { + const char *const s = tv_get_string_chk(TV_LIST_ITEM_TV(li)); + if (s == NULL) { + return false; + } + const char *hunk_start = s; + for (const char *p = hunk_start;; p++) { + if (*p == NUL || *p == NL) { + if (p != hunk_start) { + const ptrdiff_t written = file_write(fp, hunk_start, + (size_t)(p - hunk_start)); + if (written < 0) { + error = (int)written; + goto write_list_error; + } + } + if (*p == NUL) { + break; + } else { + hunk_start = p + 1; + const ptrdiff_t written = file_write(fp, (char[]){ NUL }, 1); + if (written < 0) { + error = (int)written; + break; + } + } + } + } + if (!binary || TV_LIST_ITEM_NEXT(list, li) != NULL) { + const ptrdiff_t written = file_write(fp, "\n", 1); + if (written < 0) { + error = (int)written; + goto write_list_error; + } + } + }); + if ((error = file_flush(fp)) != 0) { + goto write_list_error; + } + return true; +write_list_error: + semsg(_(e_error_while_writing_str), os_strerror(error)); + return false; +} + +/// Write a blob to file with descriptor `fp`. +/// +/// @param[in] fp File to write to. +/// @param[in] blob Blob to write. +/// +/// @return true on success, or false on failure. +static bool write_blob(FileDescriptor *const fp, const blob_T *const blob) + FUNC_ATTR_NONNULL_ARG(1) +{ + int error = 0; + const int len = tv_blob_len(blob); + if (len > 0) { + const ptrdiff_t written = file_write(fp, blob->bv_ga.ga_data, (size_t)len); + if (written < (ptrdiff_t)len) { + error = (int)written; + goto write_blob_error; + } + } + error = file_flush(fp); + if (error != 0) { + goto write_blob_error; + } + return true; +write_blob_error: + semsg(_(e_error_while_writing_str), os_strerror(error)); + return false; +} + +/// "writefile()" function +void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = -1; + + if (check_secure()) { + return; + } + + if (argvars[0].v_type == VAR_LIST) { + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) { + return; + } + }); + } else if (argvars[0].v_type != VAR_BLOB) { + semsg(_(e_invarg2), + _("writefile() first argument must be a List or a Blob")); + return; + } + + bool binary = false; + bool append = false; + bool defer = false; + bool do_fsync = !!p_fs; + bool mkdir_p = false; + if (argvars[2].v_type != VAR_UNKNOWN) { + const char *const flags = tv_get_string_chk(&argvars[2]); + if (flags == NULL) { + return; + } + for (const char *p = flags; *p; p++) { + switch (*p) { + case 'b': + binary = true; break; + case 'a': + append = true; break; + case 'D': + defer = true; break; + case 's': + do_fsync = true; break; + case 'S': + do_fsync = false; break; + case 'p': + mkdir_p = true; break; + default: + // Using %s, p and not %c, *p to preserve multibyte characters + semsg(_("E5060: Unknown flag: %s"), p); + return; + } + } + } + + char buf[NUMBUFLEN]; + const char *const fname = tv_get_string_buf_chk(&argvars[1], buf); + if (fname == NULL) { + return; + } + + if (defer && !can_add_defer()) { + return; + } + + FileDescriptor fp; + int error; + if (*fname == NUL) { + emsg(_("E482: Can't open file with an empty name")); + } else if ((error = file_open(&fp, fname, + ((append ? kFileAppend : kFileTruncate) + | (mkdir_p ? kFileMkDir : kFileCreate) + | kFileCreate), 0666)) != 0) { + semsg(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error)); + } else { + if (defer) { + typval_T tv = { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = FullName_save(fname, false), + }; + add_defer("delete", 1, &tv); + } + + bool write_ok; + if (argvars[0].v_type == VAR_BLOB) { + write_ok = write_blob(&fp, argvars[0].vval.v_blob); + } else { + write_ok = write_list(&fp, argvars[0].vval.v_list, binary); + } + if (write_ok) { + rettv->vval.v_number = 0; + } + if ((error = file_close(&fp, do_fsync)) != 0) { + semsg(_("E80: Error when closing file %s: %s"), + fname, os_strerror(error)); + } + } +} diff --git a/src/nvim/eval/fs.h b/src/nvim/eval/fs.h new file mode 100644 index 0000000000..ae6a93d0dc --- /dev/null +++ b/src/nvim/eval/fs.h @@ -0,0 +1,8 @@ +#pragma once + +#include "nvim/eval/typval_defs.h" // IWYU pragma: keep +#include "nvim/types_defs.h" // IWYU pragma: keep + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/fs.h.generated.h" +#endif diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index e3afc1cf54..6d1cb4b2c3 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1,23 +1,19 @@ #include <assert.h> -#include <fcntl.h> #include <float.h> #include <inttypes.h> #include <limits.h> #include <math.h> -#include <msgpack/object.h> -#include <msgpack/pack.h> -#include <msgpack/unpack.h> #include <signal.h> #include <stdarg.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sys/stat.h> #include <time.h> #include <uv.h> #include "auto/config.h" +#include "mpack/object.h" #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" @@ -38,6 +34,7 @@ #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/edit.h" +#include "nvim/errors.h" #include "nvim/eval.h" #include "nvim/eval/buffer.h" #include "nvim/eval/decode.h" @@ -52,15 +49,13 @@ #include "nvim/event/defs.h" #include "nvim/event/loop.h" #include "nvim/event/multiqueue.h" -#include "nvim/event/process.h" +#include "nvim/event/proc.h" #include "nvim/event/time.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" -#include "nvim/file_search.h" -#include "nvim/fileio.h" #include "nvim/garray.h" #include "nvim/garray_defs.h" #include "nvim/getchar.h" @@ -93,6 +88,7 @@ #include "nvim/move.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/channel_defs.h" +#include "nvim/msgpack_rpc/packer.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/normal.h" #include "nvim/normal_defs.h" @@ -102,13 +98,10 @@ #include "nvim/option_vars.h" #include "nvim/optionstr.h" #include "nvim/os/dl.h" -#include "nvim/os/fileio.h" -#include "nvim/os/fileio_defs.h" #include "nvim/os/fs.h" -#include "nvim/os/fs_defs.h" #include "nvim/os/os.h" #include "nvim/os/os_defs.h" -#include "nvim/os/pty_process.h" +#include "nvim/os/pty_proc.h" #include "nvim/os/shell.h" #include "nvim/os/stdpaths_defs.h" #include "nvim/os/time.h" @@ -132,6 +125,7 @@ #include "nvim/tag.h" #include "nvim/types_defs.h" #include "nvim/ui.h" +#include "nvim/ui_compositor.h" #include "nvim/version.h" #include "nvim/vim_defs.h" #include "nvim/window.h" @@ -772,41 +766,6 @@ static void f_charcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) get_col(argvars, rettv, true); } -/// "chdir(dir)" function -static void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - if (argvars[0].v_type != VAR_STRING) { - // Returning an empty string means it failed. - // No error message, for historic reasons. - return; - } - - // Return the current directory - char *cwd = xmalloc(MAXPATHL); - if (os_dirname(cwd, MAXPATHL) != FAIL) { -#ifdef BACKSLASH_IN_FILENAME - slash_adjust(cwd); -#endif - rettv->vval.v_string = xstrdup(cwd); - } - xfree(cwd); - - CdScope scope = kCdScopeGlobal; - if (curwin->w_localdir != NULL) { - scope = kCdScopeWindow; - } else if (curtab->tp_localdir != NULL) { - scope = kCdScopeTabpage; - } - - if (!changedir_func(argvars[0].vval.v_string, scope)) { - // Directory change failed - XFREE_CLEAR(rettv->vval.v_string); - } -} - /// "cindent(lnum)" function static void f_cindent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -951,7 +910,7 @@ static varnumber_T count_list(list_T *l, typval_T *needle, int64_t idx, bool ic) varnumber_T n = 0; for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { - if (tv_equal(TV_LIST_ITEM_TV(li), needle, ic, false)) { + if (tv_equal(TV_LIST_ITEM_TV(li), needle, ic)) { n++; } } @@ -971,7 +930,7 @@ static varnumber_T count_dict(dict_T *d, typval_T *needle, bool ic) varnumber_T n = 0; TV_DICT_ITER(d, di, { - if (tv_equal(&di->di_tv, needle, ic, false)) { + if (tv_equal(&di->di_tv, needle, ic)) { n++; } }); @@ -1036,9 +995,9 @@ static void f_ctxget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } Arena arena = ARENA_EMPTY; - Dictionary ctx_dict = ctx_to_dict(ctx, &arena); + Dict ctx_dict = ctx_to_dict(ctx, &arena); Error err = ERROR_INIT; - object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err); + object_to_vim(DICT_OBJ(ctx_dict), rettv, &err); arena_mem_free(arena_finish(&arena)); api_clear_error(&err); } @@ -1108,7 +1067,7 @@ static void f_ctxset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) did_emsg = false; Arena arena = ARENA_EMPTY; - Dictionary dict = vim_to_object(&argvars[0], &arena, true).data.dictionary; + Dict dict = vim_to_object(&argvars[0], &arena, true).data.dict; Context tmp = CONTEXT_INIT; Error err = ERROR_INIT; ctx_from_dict(dict, &tmp, &err); @@ -1250,42 +1209,6 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 ? get_copyID() : 0)); } -/// "delete()" function -static void f_delete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = -1; - if (check_secure()) { - return; - } - - const char *const name = tv_get_string(&argvars[0]); - if (*name == NUL) { - emsg(_(e_invarg)); - return; - } - - char nbuf[NUMBUFLEN]; - const char *flags; - if (argvars[1].v_type != VAR_UNKNOWN) { - flags = tv_get_string_buf(&argvars[1], nbuf); - } else { - flags = ""; - } - - if (*flags == NUL) { - // delete a file - rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1; - } else if (strcmp(flags, "d") == 0) { - // delete an empty directory - rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1; - } else if (strcmp(flags, "rf") == 0) { - // delete a directory recursively - rettv->vval.v_number = delete_recursive(name); - } else { - semsg(_(e_invexpr2), flags); - } -} - /// dictwatcheradd(dict, key, funcref) function static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -1568,17 +1491,6 @@ static void f_eventhandler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr rettv->vval.v_number = vgetc_busy; } -/// "executable()" function -static void f_executable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - if (tv_check_for_string_arg(argvars, 0) == FAIL) { - return; - } - - // Check in $PATH and also check directly if there is a directory name - rettv->vval.v_number = os_can_exe(tv_get_string(&argvars[0]), NULL, true); -} - typedef struct { const list_T *const l; const listitem_T *li; @@ -1682,27 +1594,6 @@ static void f_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) execute_common(argvars, rettv, 0); } -/// "exepath()" function -static void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - if (tv_check_for_nonempty_string_arg(argvars, 0) == FAIL) { - return; - } - - char *path = NULL; - - os_can_exe(tv_get_string(&argvars[0]), &path, true); - -#ifdef BACKSLASH_IN_FILENAME - if (path != NULL) { - slash_adjust(path); - } -#endif - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = path; -} - /// "exists()" function static void f_exists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2104,100 +1995,6 @@ static void f_feedkeys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) cstr_as_string(flags), true); } -/// "filereadable()" function -static void f_filereadable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *const p = tv_get_string(&argvars[0]); - rettv->vval.v_number = - (*p && !os_isdir(p) && os_file_is_readable(p)); -} - -/// @return 0 for not writable -/// 1 for writable file -/// 2 for a dir which we have rights to write into. -static void f_filewritable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *filename = tv_get_string(&argvars[0]); - rettv->vval.v_number = os_file_is_writable(filename); -} - -static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) -{ - char *fresult = NULL; - char *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; - int count = 1; - bool first = true; - bool error = false; - - rettv->vval.v_string = NULL; - rettv->v_type = VAR_STRING; - - const char *fname = tv_get_string(&argvars[0]); - - char pathbuf[NUMBUFLEN]; - if (argvars[1].v_type != VAR_UNKNOWN) { - const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf); - if (p == NULL) { - error = true; - } else { - if (*p != NUL) { - path = (char *)p; - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - count = (int)tv_get_number_chk(&argvars[2], &error); - } - } - } - - if (count < 0) { - tv_list_alloc_ret(rettv, kListLenUnknown); - } - - if (*fname != NUL && !error) { - char *file_to_find = NULL; - char *search_ctx = NULL; - - do { - if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) { - xfree(fresult); - } - fresult = find_file_in_path_option(first ? (char *)fname : NULL, - first ? strlen(fname) : 0, - 0, first, path, - find_what, curbuf->b_ffname, - (find_what == FINDFILE_DIR - ? "" - : curbuf->b_p_sua), - &file_to_find, &search_ctx); - first = false; - - if (fresult != NULL && rettv->v_type == VAR_LIST) { - tv_list_append_string(rettv->vval.v_list, fresult, -1); - } - } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL); - - xfree(file_to_find); - vim_findfile_cleanup(search_ctx); - } - - if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = fresult; - } -} - -/// "finddir({fname}[, {path}[, {count}]])" function -static void f_finddir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - findfilendir(argvars, rettv, FINDFILE_DIR); -} - -/// "findfile({fname}[, {path}[, {count}]])" function -static void f_findfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - findfilendir(argvars, rettv, FINDFILE_FILE); -} - /// "float2nr({float})" function static void f_float2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2270,6 +2067,164 @@ static void f_foreground(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { } +/// "function()" function +/// "funcref()" function +static void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref) +{ + char *s; + char *name; + bool use_string = false; + partial_T *arg_pt = NULL; + char *trans_name = NULL; + + if (argvars[0].v_type == VAR_FUNC) { + // function(MyFunc, [arg], dict) + s = argvars[0].vval.v_string; + } else if (argvars[0].v_type == VAR_PARTIAL + && argvars[0].vval.v_partial != NULL) { + // function(dict.MyFunc, [arg]) + arg_pt = argvars[0].vval.v_partial; + s = partial_name(arg_pt); + // TODO(bfredl): do the entire nlua_is_table_from_lua dance + } else { + // function('MyFunc', [arg], dict) + s = (char *)tv_get_string(&argvars[0]); + use_string = true; + } + + if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) { + name = s; + trans_name = save_function_name(&name, false, + TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL); + if (*name != NUL) { + s = NULL; + } + } + if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s)) + || (is_funcref && trans_name == NULL)) { + semsg(_(e_invarg2), (use_string ? tv_get_string(&argvars[0]) : s)); + // Don't check an autoload name for existence here. + } else if (trans_name != NULL + && (is_funcref + ? find_func(trans_name) == NULL + : !translated_function_exists(trans_name))) { + semsg(_("E700: Unknown function: %s"), s); + } else { + int dict_idx = 0; + int arg_idx = 0; + list_T *list = NULL; + if (strncmp(s, "s:", 2) == 0 || strncmp(s, "<SID>", 5) == 0) { + // Expand s: and <SID> into <SNR>nr_, so that the function can + // also be called from another script. Using trans_function_name() + // would also work, but some plugins depend on the name being + // printable text. + name = get_scriptlocal_funcname(s); + } else { + name = xstrdup(s); + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[2].v_type != VAR_UNKNOWN) { + // function(name, [args], dict) + arg_idx = 1; + dict_idx = 2; + } else if (argvars[1].v_type == VAR_DICT) { + // function(name, dict) + dict_idx = 1; + } else { + // function(name, [args]) + arg_idx = 1; + } + if (dict_idx > 0) { + if (tv_check_for_dict_arg(argvars, dict_idx) == FAIL) { + xfree(name); + goto theend; + } + if (argvars[dict_idx].vval.v_dict == NULL) { + dict_idx = 0; + } + } + if (arg_idx > 0) { + if (argvars[arg_idx].v_type != VAR_LIST) { + emsg(_("E923: Second argument of function() must be " + "a list or a dict")); + xfree(name); + goto theend; + } + list = argvars[arg_idx].vval.v_list; + if (tv_list_len(list) == 0) { + arg_idx = 0; + } else if (tv_list_len(list) > MAX_FUNC_ARGS) { + emsg_funcname(e_toomanyarg, s); + xfree(name); + goto theend; + } + } + } + if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL || is_funcref) { + partial_T *const pt = xcalloc(1, sizeof(*pt)); + + // result is a VAR_PARTIAL + if (arg_idx > 0 || (arg_pt != NULL && arg_pt->pt_argc > 0)) { + const int arg_len = (arg_pt == NULL ? 0 : arg_pt->pt_argc); + const int lv_len = tv_list_len(list); + + pt->pt_argc = arg_len + lv_len; + pt->pt_argv = xmalloc(sizeof(pt->pt_argv[0]) * (size_t)pt->pt_argc); + int i = 0; + for (; i < arg_len; i++) { + tv_copy(&arg_pt->pt_argv[i], &pt->pt_argv[i]); + } + if (lv_len > 0) { + TV_LIST_ITER(list, li, { + tv_copy(TV_LIST_ITEM_TV(li), &pt->pt_argv[i++]); + }); + } + } + + // For "function(dict.func, [], dict)" and "func" is a partial + // use "dict". That is backwards compatible. + if (dict_idx > 0) { + // The dict is bound explicitly, pt_auto is false + pt->pt_dict = argvars[dict_idx].vval.v_dict; + (pt->pt_dict->dv_refcount)++; + } else if (arg_pt != NULL) { + // If the dict was bound automatically the result is also + // bound automatically. + pt->pt_dict = arg_pt->pt_dict; + pt->pt_auto = arg_pt->pt_auto; + if (pt->pt_dict != NULL) { + (pt->pt_dict->dv_refcount)++; + } + } + + pt->pt_refcount = 1; + if (arg_pt != NULL && arg_pt->pt_func != NULL) { + pt->pt_func = arg_pt->pt_func; + func_ptr_ref(pt->pt_func); + xfree(name); + } else if (is_funcref) { + pt->pt_func = find_func(trans_name); + func_ptr_ref(pt->pt_func); + xfree(name); + } else { + pt->pt_name = name; + func_ref(name); + } + + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = pt; + } else { + // result is a VAR_FUNC + rettv->v_type = VAR_FUNC; + rettv->vval.v_string = name; + func_ref(name); + } + } +theend: + xfree(trans_name); +} + static void f_funcref(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { common_function(argvars, rettv, true); @@ -2370,6 +2325,33 @@ static void f_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) for (int i = 0; i < pt->pt_argc; i++) { tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]); } + } else if (strcmp(what, "arity") == 0) { + int required = 0; + int optional = 0; + bool varargs = false; + const char *name = partial_name(pt); + + get_func_arity(name, &required, &optional, &varargs); + + rettv->v_type = VAR_DICT; + tv_dict_alloc_ret(rettv); + dict_T *dict = rettv->vval.v_dict; + + // Take into account the arguments of the partial, if any. + // Note that it is possible to supply more arguments than the function + // accepts. + if (pt->pt_argc >= required + optional) { + required = optional = 0; + } else if (pt->pt_argc > required) { + optional -= pt->pt_argc - required; + required = 0; + } else { + required -= pt->pt_argc; + } + + tv_dict_add_nr(dict, S_LEN("required"), required); + tv_dict_add_nr(dict, S_LEN("optional"), optional); + tv_dict_add_bool(dict, S_LEN("varargs"), varargs); } else { semsg(_(e_invarg2), what); } @@ -2516,127 +2498,6 @@ static void f_getcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fpt tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until()); } -/// `getcwd([{win}[, {tab}]])` function -/// -/// Every scope not specified implies the currently selected scope object. -/// -/// @pre The arguments must be of type number. -/// @pre There may not be more than two arguments. -/// @pre An argument may not be -1 if preceding arguments are not all -1. -/// -/// @post The return value will be a string. -static void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - // Possible scope of working directory to return. - CdScope scope = kCdScopeInvalid; - - // Numbers of the scope objects (window, tab) we want the working directory - // of. A `-1` means to skip this scope, a `0` means the current object. - int scope_number[] = { - [kCdScopeWindow] = 0, // Number of window to look at. - [kCdScopeTabpage] = 0, // Number of tab to look at. - }; - - char *cwd = NULL; // Current working directory to print - char *from = NULL; // The original string to copy - - tabpage_T *tp = curtab; // The tabpage to look at. - win_T *win = curwin; // The window to look at. - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - // Pre-conditions and scope extraction together - for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { - // If there is no argument there are no more scopes after it, break out. - if (argvars[i].v_type == VAR_UNKNOWN) { - break; - } - if (argvars[i].v_type != VAR_NUMBER) { - emsg(_(e_invarg)); - return; - } - scope_number[i] = (int)argvars[i].vval.v_number; - // It is an error for the scope number to be less than `-1`. - if (scope_number[i] < -1) { - emsg(_(e_invarg)); - return; - } - // Use the narrowest scope the user requested - if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { - // The scope is the current iteration step. - scope = i; - } else if (scope_number[i] < 0) { - scope = i + 1; - } - } - - // Find the tabpage by number - if (scope_number[kCdScopeTabpage] > 0) { - tp = find_tabpage(scope_number[kCdScopeTabpage]); - if (!tp) { - emsg(_("E5000: Cannot find tab number.")); - return; - } - } - - // Find the window in `tp` by number, `NULL` if none. - if (scope_number[kCdScopeWindow] >= 0) { - if (scope_number[kCdScopeTabpage] < 0) { - emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); - return; - } - - if (scope_number[kCdScopeWindow] > 0) { - win = find_win_by_nr(&argvars[0], tp); - if (!win) { - emsg(_("E5002: Cannot find window number.")); - return; - } - } - } - - cwd = xmalloc(MAXPATHL); - - switch (scope) { - case kCdScopeWindow: - assert(win); - from = win->w_localdir; - if (from) { - break; - } - FALLTHROUGH; - case kCdScopeTabpage: - assert(tp); - from = tp->tp_localdir; - if (from) { - break; - } - FALLTHROUGH; - case kCdScopeGlobal: - if (globaldir) { // `globaldir` is not always set. - from = globaldir; - break; - } - FALLTHROUGH; // In global directory, just need to get OS CWD. - case kCdScopeInvalid: // If called without any arguments, get OS CWD. - if (os_dirname(cwd, MAXPATHL) == FAIL) { - from = ""; // Return empty string on failure. - } - } - - if (from) { - xstrlcpy(cwd, from, MAXPATHL); - } - - rettv->vval.v_string = xstrdup(cwd); -#ifdef BACKSLASH_IN_FILENAME - slash_adjust(rettv->vval.v_string); -#endif - - xfree(cwd); -} - /// "getfontname()" function static void f_getfontname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2644,98 +2505,6 @@ static void f_getfontname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_string = NULL; } -/// "getfperm({fname})" function -static void f_getfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - char *perm = NULL; - char flags[] = "rwx"; - - const char *filename = tv_get_string(&argvars[0]); - int32_t file_perm = os_getperm(filename); - if (file_perm >= 0) { - perm = xstrdup("---------"); - for (int i = 0; i < 9; i++) { - if (file_perm & (1 << (8 - i))) { - perm[i] = flags[i % 3]; - } - } - } - rettv->v_type = VAR_STRING; - rettv->vval.v_string = perm; -} - -/// "getfsize({fname})" function -static void f_getfsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *fname = tv_get_string(&argvars[0]); - - rettv->v_type = VAR_NUMBER; - - FileInfo file_info; - if (os_fileinfo(fname, &file_info)) { - uint64_t filesize = os_fileinfo_size(&file_info); - if (os_isdir(fname)) { - rettv->vval.v_number = 0; - } else { - rettv->vval.v_number = (varnumber_T)filesize; - - // non-perfect check for overflow - if ((uint64_t)rettv->vval.v_number != filesize) { - rettv->vval.v_number = -2; - } - } - } else { - rettv->vval.v_number = -1; - } -} - -/// "getftime({fname})" function -static void f_getftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *fname = tv_get_string(&argvars[0]); - - FileInfo file_info; - if (os_fileinfo(fname, &file_info)) { - rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec; - } else { - rettv->vval.v_number = -1; - } -} - -/// "getftype({fname})" function -static void f_getftype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - char *type = NULL; - char *t; - - const char *fname = tv_get_string(&argvars[0]); - - rettv->v_type = VAR_STRING; - FileInfo file_info; - if (os_fileinfo_link(fname, &file_info)) { - uint64_t mode = file_info.stat.st_mode; - if (S_ISREG(mode)) { - t = "file"; - } else if (S_ISDIR(mode)) { - t = "dir"; - } else if (S_ISLNK(mode)) { - t = "link"; - } else if (S_ISBLK(mode)) { - t = "bdev"; - } else if (S_ISCHR(mode)) { - t = "cdev"; - } else if (S_ISFIFO(mode)) { - t = "fifo"; - } else if (S_ISSOCK(mode)) { - t = "socket"; - } else { - t = "other"; - } - type = xstrdup(t); - } - rettv->vval.v_string = type; -} - /// "getjumplist()" function static void f_getjumplist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2824,7 +2593,7 @@ static char *block_def2str(struct block_def *bd) } static int getregionpos(typval_T *argvars, typval_T *rettv, pos_T *p1, pos_T *p2, - bool *const inclusive, MotionType *region_type, oparg_T *oa) + bool *const inclusive, MotionType *region_type, oparg_T *oap) FUNC_ATTR_NONNULL_ALL { tv_list_alloc_ret(rettv, kListLenMayKnow); @@ -2858,11 +2627,17 @@ static int getregionpos(typval_T *argvars, typval_T *rettv, pos_T *p1, pos_T *p2 type = default_type; } + int block_width = 0; if (type[0] == 'v' && type[1] == NUL) { *region_type = kMTCharWise; } else if (type[0] == 'V' && type[1] == NUL) { *region_type = kMTLineWise; - } else if (type[0] == Ctrl_V && type[1] == NUL) { + } else if (type[0] == Ctrl_V) { + char *p = type + 1; + if (*p != NUL && ((block_width = getdigits_int(&p, false, 0)) <= 0 || *p != NUL)) { + semsg(_(e_invargNval), "type", type); + return FAIL; + } *region_type = kMTBlockWise; } else { semsg(_(e_invargNval), "type", type); @@ -2926,16 +2701,18 @@ static int getregionpos(typval_T *argvars, typval_T *rettv, pos_T *p1, pos_T *p2 colnr_T sc1, ec1, sc2, ec2; getvvcol(curwin, p1, &sc1, NULL, &ec1); getvvcol(curwin, p2, &sc2, NULL, &ec2); - oa->motion_type = kMTBlockWise; - oa->inclusive = true; - oa->op_type = OP_NOP; - oa->start = *p1; - oa->end = *p2; - oa->start_vcol = MIN(sc1, sc2); - if (is_select_exclusive && ec1 < sc2 && 0 < sc2 && ec2 > ec1) { - oa->end_vcol = sc2 - 1; + oap->motion_type = kMTBlockWise; + oap->inclusive = true; + oap->op_type = OP_NOP; + oap->start = *p1; + oap->end = *p2; + oap->start_vcol = MIN(sc1, sc2); + if (block_width > 0) { + oap->end_vcol = oap->start_vcol + block_width - 1; + } else if (is_select_exclusive && ec1 < sc2 && 0 < sc2 && ec2 > ec1) { + oap->end_vcol = sc2 - 1; } else { - oa->end_vcol = MAX(ec1, ec2); + oap->end_vcol = MAX(ec1, ec2); } } @@ -3034,6 +2811,7 @@ static void f_getregionpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr for (linenr_T lnum = p1.lnum; lnum <= p2.lnum; lnum++) { pos_T ret_p1, ret_p2; + char *line = ml_get(lnum); colnr_T line_len = ml_get_len(lnum); if (region_type == kMTLineWise) { @@ -3052,7 +2830,7 @@ static void f_getregionpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr if (bd.is_oneChar) { // selection entirely inside one char if (region_type == kMTBlockWise) { - ret_p1.col = bd.textcol; + ret_p1.col = (colnr_T)(mb_prevptr(line, bd.textstart) - line) + 1; ret_p1.coladd = bd.start_char_vcols - (bd.start_vcol - oa.start_vcol); } else { ret_p1.col = p1.col + 1; @@ -3064,7 +2842,7 @@ static void f_getregionpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr ret_p1.coladd = oa.start_vcol - bd.start_vcol; bd.is_oneChar = true; } else if (bd.startspaces > 0) { - ret_p1.col = bd.textcol; + ret_p1.col = (colnr_T)(mb_prevptr(line, bd.textstart) - line) + 1; ret_p1.coladd = bd.start_char_vcols - bd.startspaces; } else { ret_p1.col = bd.textcol + 1; @@ -3269,113 +3047,6 @@ static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) time_watcher_close(tw, dummy_timer_close_cb); } -/// "glob()" function -static void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int options = WILD_SILENT|WILD_USE_NL; - expand_T xpc; - bool error = false; - - // When the optional second argument is non-zero, don't remove matches - // for 'wildignore' and don't put matches for 'suffixes' at the end. - rettv->v_type = VAR_STRING; - if (argvars[1].v_type != VAR_UNKNOWN) { - if (tv_get_number_chk(&argvars[1], &error)) { - options |= WILD_KEEP_ALL; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - if (tv_get_number_chk(&argvars[2], &error)) { - tv_list_set_ret(rettv, NULL); - } - if (argvars[3].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[3], &error)) { - options |= WILD_ALLLINKS; - } - } - } - if (!error) { - ExpandInit(&xpc); - xpc.xp_context = EXPAND_FILES; - if (p_wic) { - options += WILD_ICASE; - } - if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = ExpandOne(&xpc, (char *) - tv_get_string(&argvars[0]), NULL, options, - WILD_ALL); - } else { - ExpandOne(&xpc, (char *)tv_get_string(&argvars[0]), NULL, options, - WILD_ALL_KEEP); - tv_list_alloc_ret(rettv, xpc.xp_numfiles); - for (int i = 0; i < xpc.xp_numfiles; i++) { - tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); - } - ExpandCleanup(&xpc); - } - } else { - rettv->vval.v_string = NULL; - } -} - -/// "globpath()" function -static void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int flags = WILD_IGNORE_COMPLETESLASH; // Flags for globpath. - bool error = false; - - // Return a string, or a list if the optional third argument is non-zero. - rettv->v_type = VAR_STRING; - - if (argvars[2].v_type != VAR_UNKNOWN) { - // When the optional second argument is non-zero, don't remove matches - // for 'wildignore' and don't put matches for 'suffixes' at the end. - if (tv_get_number_chk(&argvars[2], &error)) { - flags |= WILD_KEEP_ALL; - } - - if (argvars[3].v_type != VAR_UNKNOWN) { - if (tv_get_number_chk(&argvars[3], &error)) { - tv_list_set_ret(rettv, NULL); - } - if (argvars[4].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[4], &error)) { - flags |= WILD_ALLLINKS; - } - } - } - - char buf1[NUMBUFLEN]; - const char *const file = tv_get_string_buf_chk(&argvars[1], buf1); - if (file != NULL && !error) { - garray_T ga; - ga_init(&ga, (int)sizeof(char *), 10); - globpath((char *)tv_get_string(&argvars[0]), (char *)file, &ga, flags, false); - - if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); - } else { - tv_list_alloc_ret(rettv, ga.ga_len); - for (int i = 0; i < ga.ga_len; i++) { - tv_list_append_string(rettv->vval.v_list, - ((const char **)(ga.ga_data))[i], -1); - } - } - - ga_clear_strings(&ga); - } else { - rettv->vval.v_string = NULL; - } -} - -/// "glob2regpat()" function -static void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (pat == NULL) ? NULL : file_pat_to_reg_pat(pat, NULL, NULL, false); -} - /// "gettext()" function static void f_gettext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -3596,106 +3267,6 @@ static bool has_wsl(void) return has_wsl == kTrue; } -/// `haslocaldir([{win}[, {tab}]])` function -/// -/// Returns `1` if the scope object has a local directory, `0` otherwise. If a -/// scope object is not specified the current one is implied. This function -/// share a lot of code with `f_getcwd`. -/// -/// @pre The arguments must be of type number. -/// @pre There may not be more than two arguments. -/// @pre An argument may not be -1 if preceding arguments are not all -1. -/// -/// @post The return value will be either the number `1` or `0`. -static void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - // Possible scope of working directory to return. - CdScope scope = kCdScopeInvalid; - - // Numbers of the scope objects (window, tab) we want the working directory - // of. A `-1` means to skip this scope, a `0` means the current object. - int scope_number[] = { - [kCdScopeWindow] = 0, // Number of window to look at. - [kCdScopeTabpage] = 0, // Number of tab to look at. - }; - - tabpage_T *tp = curtab; // The tabpage to look at. - win_T *win = curwin; // The window to look at. - - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - // Pre-conditions and scope extraction together - for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { - if (argvars[i].v_type == VAR_UNKNOWN) { - break; - } - if (argvars[i].v_type != VAR_NUMBER) { - emsg(_(e_invarg)); - return; - } - scope_number[i] = (int)argvars[i].vval.v_number; - if (scope_number[i] < -1) { - emsg(_(e_invarg)); - return; - } - // Use the narrowest scope the user requested - if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { - // The scope is the current iteration step. - scope = i; - } else if (scope_number[i] < 0) { - scope = i + 1; - } - } - - // If the user didn't specify anything, default to window scope - if (scope == kCdScopeInvalid) { - scope = MIN_CD_SCOPE; - } - - // Find the tabpage by number - if (scope_number[kCdScopeTabpage] > 0) { - tp = find_tabpage(scope_number[kCdScopeTabpage]); - if (!tp) { - emsg(_("E5000: Cannot find tab number.")); - return; - } - } - - // Find the window in `tp` by number, `NULL` if none. - if (scope_number[kCdScopeWindow] >= 0) { - if (scope_number[kCdScopeTabpage] < 0) { - emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); - return; - } - - if (scope_number[kCdScopeWindow] > 0) { - win = find_win_by_nr(&argvars[0], tp); - if (!win) { - emsg(_("E5002: Cannot find window number.")); - return; - } - } - } - - switch (scope) { - case kCdScopeWindow: - assert(win); - rettv->vval.v_number = win->w_localdir ? 1 : 0; - break; - case kCdScopeTabpage: - assert(tp); - rettv->vval.v_number = tp->tp_localdir ? 1 : 0; - break; - case kCdScopeGlobal: - // The global scope never has a local directory - break; - case kCdScopeInvalid: - // We should never get here - abort(); - } -} - /// "highlightID(name)" function static void f_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -3760,7 +3331,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) typval_T tv; tv.v_type = VAR_NUMBER; tv.vval.v_number = tv_blob_get(b, idx); - if (tv_equal(&tv, &argvars[1], ic, false)) { + if (tv_equal(&tv, &argvars[1], ic)) { rettv->vval.v_number = idx; return; } @@ -3797,7 +3368,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { - if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) { + if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic)) { rettv->vval.v_number = idx; break; } @@ -3843,6 +3414,7 @@ static varnumber_T indexof_blob(blob_T *b, varnumber_T startidx, typval_T *expr) } } + const int called_emsg_start = called_emsg; for (varnumber_T idx = startidx; idx < tv_blob_len(b); idx++) { set_vim_var_nr(VV_KEY, idx); set_vim_var_nr(VV_VAL, tv_blob_get(b, (int)idx)); @@ -3850,6 +3422,10 @@ static varnumber_T indexof_blob(blob_T *b, varnumber_T startidx, typval_T *expr) if (indexof_eval_expr(expr)) { return idx; } + + if (called_emsg != called_emsg_start) { + return -1; + } } return -1; @@ -3879,6 +3455,7 @@ static varnumber_T indexof_list(list_T *l, varnumber_T startidx, typval_T *expr) } } + const int called_emsg_start = called_emsg; for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { set_vim_var_nr(VV_KEY, idx); tv_copy(TV_LIST_ITEM_TV(item), get_vim_var_tv(VV_VAL)); @@ -3889,6 +3466,10 @@ static varnumber_T indexof_list(list_T *l, varnumber_T startidx, typval_T *expr) if (found) { return idx; } + + if (called_emsg != called_emsg_start) { + return -1; + } } return -1; @@ -3905,7 +3486,8 @@ static void f_indexof(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - if ((argvars[1].v_type == VAR_STRING && argvars[1].vval.v_string == NULL) + if ((argvars[1].v_type == VAR_STRING + && (argvars[1].vval.v_string == NULL || *argvars[1].vval.v_string == NUL)) || (argvars[1].v_type == VAR_FUNC && argvars[1].vval.v_partial == NULL)) { return; } @@ -4101,12 +3683,6 @@ static void f_invert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL); } -/// "isdirectory()" function -static void f_isdirectory(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = os_isdir(tv_get_string(&argvars[0])); -} - /// "islocked()" function static void f_islocked(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -4195,7 +3771,7 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - Process *proc = &data->stream.proc; + Proc *proc = &data->stream.proc; rettv->vval.v_number = proc->pid; } @@ -4221,13 +3797,13 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - if (data->stream.proc.type != kProcessTypePty) { + if (data->stream.proc.type != kProcTypePty) { emsg(_(e_channotpty)); return; } - pty_process_resize(&data->stream.pty, (uint16_t)argvars[1].vval.v_number, - (uint16_t)argvars[2].vval.v_number); + pty_proc_resize(&data->stream.pty, (uint16_t)argvars[1].vval.v_number, + (uint16_t)argvars[2].vval.v_number); rettv->vval.v_number = 1; } @@ -4309,7 +3885,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en // Set $NVIM (in the child process) to v:servername. #3118 char *nvim_addr = get_vim_var_str(VV_SEND_SERVER); - if (nvim_addr[0] != '\0') { + if (nvim_addr[0] != NUL) { dictitem_T *dv = tv_dict_find(env, S_LEN("NVIM")); if (dv) { tv_dict_item_remove(env, dv); @@ -4502,7 +4078,7 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // Ignore return code, but show error later. channel_close(data->id, kChannelPartRpc, &error); } - process_stop(&data->stream.proc); + proc_stop(&data->stream.proc); rettv->vval.v_number = 1; if (error) { emsg(error); @@ -4538,10 +4114,10 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) || !(chan = find_channel((uint64_t)TV_LIST_ITEM_TV(arg)->vval.v_number)) || chan->streamtype != kChannelStreamProc) { jobs[i] = NULL; // Invalid job. - } else if (process_is_stopped(&chan->stream.proc)) { + } else if (proc_is_stopped(&chan->stream.proc)) { // Job is stopped but not fully destroyed. // Ensure all callbacks on its event queue are executed. #15402 - process_wait(&chan->stream.proc, -1, NULL); + proc_wait(&chan->stream.proc, -1, NULL); jobs[i] = NULL; // Invalid job. } else { jobs[i] = chan; @@ -4569,8 +4145,8 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (jobs[i] == NULL) { continue; // Invalid job, will assign status=-3 below. } - int status = process_wait(&jobs[i]->stream.proc, remaining, - waiting_jobs); + int status = proc_wait(&jobs[i]->stream.proc, remaining, + waiting_jobs); if (status < 0) { break; // Interrupted (CTRL-C) or timeout, skip remaining jobs. } @@ -5350,78 +4926,6 @@ static void f_min(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) max_min(argvars, rettv, false); } -/// "mkdir()" function -static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int prot = 0755; - - rettv->vval.v_number = FAIL; - if (check_secure()) { - return; - } - - char buf[NUMBUFLEN]; - const char *const dir = tv_get_string_buf(&argvars[0], buf); - if (*dir == NUL) { - return; - } - - if (*path_tail(dir) == NUL) { - // Remove trailing slashes. - *path_tail_with_sep((char *)dir) = NUL; - } - - bool defer = false; - bool defer_recurse = false; - char *created = NULL; - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_UNKNOWN) { - prot = (int)tv_get_number_chk(&argvars[2], NULL); - if (prot == -1) { - return; - } - } - const char *arg2 = tv_get_string(&argvars[1]); - defer = vim_strchr(arg2, 'D') != NULL; - defer_recurse = vim_strchr(arg2, 'R') != NULL; - if ((defer || defer_recurse) && !can_add_defer()) { - return; - } - - if (vim_strchr(arg2, 'p') != NULL) { - char *failed_dir; - int ret = os_mkdir_recurse(dir, prot, &failed_dir, - defer || defer_recurse ? &created : NULL); - if (ret != 0) { - semsg(_(e_mkdir), failed_dir, os_strerror(ret)); - xfree(failed_dir); - rettv->vval.v_number = FAIL; - return; - } - rettv->vval.v_number = OK; - } - } - if (rettv->vval.v_number == FAIL) { - rettv->vval.v_number = vim_mkdir_emsg(dir, prot); - } - - // Handle "D" and "R": deferred deletion of the created directory. - if (rettv->vval.v_number == OK - && created == NULL && (defer || defer_recurse)) { - created = FullName_save(dir, false); - } - if (created != NULL) { - typval_T tv[2]; - tv[0].v_type = VAR_STRING; - tv[0].v_lock = VAR_UNLOCKED; - tv[0].vval.v_string = created; - tv[1].v_type = VAR_STRING; - tv[1].v_lock = VAR_UNLOCKED; - tv[1].vval.v_string = xstrdup(defer_recurse ? "rf" : "d"); - add_defer("delete", 2, tv); - } -} - /// "mode()" function static void f_mode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -5492,15 +4996,7 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } list_T *const list = argvars[0].vval.v_list; - msgpack_packer *packer; - if (argvars[1].v_type != VAR_UNKNOWN - && strequal(tv_get_string(&argvars[1]), "B")) { - tv_blob_alloc_ret(rettv); - packer = msgpack_packer_new(rettv->vval.v_blob, &encode_blob_write); - } else { - packer = msgpack_packer_new(tv_list_alloc_ret(rettv, kListLenMayKnow), - &encode_list_write); - } + PackerBuffer packer = packer_string_buffer(); const char *const msg = _("msgpackdump() argument, index %i"); // Assume that translation will not take more then 4 times more space char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN]; @@ -5508,43 +5004,40 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) TV_LIST_ITER(list, li, { vim_snprintf(msgbuf, sizeof(msgbuf), msg, idx); idx++; - if (encode_vim_to_msgpack(packer, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) { + if (encode_vim_to_msgpack(&packer, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) { break; } }); - msgpack_packer_free(packer); + String data = packer_take_string(&packer); + if (argvars[1].v_type != VAR_UNKNOWN && strequal(tv_get_string(&argvars[1]), "B")) { + blob_T *b = tv_blob_alloc_ret(rettv); + b->bv_ga.ga_data = data.data; + b->bv_ga.ga_len = (int)data.size; + b->bv_ga.ga_maxlen = (int)(packer.endptr - packer.startptr); + } else { + encode_list_write(tv_list_alloc_ret(rettv, kListLenMayKnow), data.data, data.size); + api_free_string(data); + } } -static int msgpackparse_convert_item(const msgpack_object data, const msgpack_unpack_return result, - list_T *const ret_list, const bool fail_if_incomplete) - FUNC_ATTR_NONNULL_ALL +static void emsg_mpack_error(int status) { - switch (result) { - case MSGPACK_UNPACK_PARSE_ERROR: + switch (status) { + case MPACK_ERROR: semsg(_(e_invarg2), "Failed to parse msgpack string"); - return FAIL; - case MSGPACK_UNPACK_NOMEM_ERROR: - emsg(_(e_outofmem)); - return FAIL; - case MSGPACK_UNPACK_CONTINUE: - if (fail_if_incomplete) { - semsg(_(e_invarg2), "Incomplete msgpack string"); - return FAIL; - } - return NOTDONE; - case MSGPACK_UNPACK_SUCCESS: { - typval_T tv = { .v_type = VAR_UNKNOWN }; - if (msgpack_to_vim(data, &tv) == FAIL) { - semsg(_(e_invarg2), "Failed to convert msgpack string"); - return FAIL; - } - tv_list_append_owned_tv(ret_list, tv); - return OK; - } - case MSGPACK_UNPACK_EXTRA_BYTES: - abort(); + break; + + case MPACK_EOF: + semsg(_(e_invarg2), "Incomplete msgpack string"); + break; + + case MPACK_NOMEM: + semsg(_(e_invarg2), "object was too deep to unpack"); + break; + + default: + break; } - UNREACHABLE; } static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret_list) @@ -5558,48 +5051,57 @@ static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret return; } ListReaderState lrstate = encode_init_lrstate(list); - msgpack_unpacker *const unpacker = msgpack_unpacker_new(IOSIZE); - if (unpacker == NULL) { - emsg(_(e_outofmem)); - return; - } - msgpack_unpacked unpacked; - msgpack_unpacked_init(&unpacked); + char *buf = alloc_block(); + size_t buf_size = 0; + + typval_T cur_item = { .v_type = VAR_UNKNOWN }; + mpack_parser_t parser; + mpack_parser_init(&parser, 0); + parser.data.p = &cur_item; + + int status = MPACK_OK; while (true) { - if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) { - emsg(_(e_outofmem)); - goto end; - } size_t read_bytes; - const int rlret = encode_read_from_list(&lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, + const int rlret = encode_read_from_list(&lrstate, buf + buf_size, ARENA_BLOCK_SIZE - buf_size, &read_bytes); if (rlret == FAIL) { semsg(_(e_invarg2), "List item is not a string"); goto end; } - msgpack_unpacker_buffer_consumed(unpacker, read_bytes); - if (read_bytes == 0) { - break; - } - while (unpacker->off < unpacker->used) { - const msgpack_unpack_return result - = msgpack_unpacker_next(unpacker, &unpacked); - const int conv_result = msgpackparse_convert_item(unpacked.data, result, - ret_list, rlret == OK); - if (conv_result == NOTDONE) { + buf_size += read_bytes; + + const char *ptr = buf; + while (buf_size) { + status = mpack_parse_typval(&parser, &ptr, &buf_size); + if (status == MPACK_OK) { + tv_list_append_owned_tv(ret_list, cur_item); + cur_item.v_type = VAR_UNKNOWN; + } else { break; - } else if (conv_result == FAIL) { - goto end; } } + if (rlret == OK) { break; } + + if (status == MPACK_EOF) { + // move remaining data to front of buffer + if (buf_size && ptr > buf) { + memmove(buf, ptr, buf_size); + } + } else if (status != MPACK_OK) { + break; + } + } + + if (status != MPACK_OK) { + typval_parser_error_free(&parser); + emsg_mpack_error(status); } end: - msgpack_unpacker_free(unpacker); - msgpack_unpacked_destroy(&unpacked); + free_block(buf); } static void msgpackparse_unpack_blob(const blob_T *const blob, list_T *const ret_list) @@ -5609,18 +5111,19 @@ static void msgpackparse_unpack_blob(const blob_T *const blob, list_T *const ret if (len == 0) { return; } - msgpack_unpacked unpacked; - msgpack_unpacked_init(&unpacked); - for (size_t offset = 0; offset < (size_t)len;) { - const msgpack_unpack_return result - = msgpack_unpack_next(&unpacked, blob->bv_ga.ga_data, (size_t)len, &offset); - if (msgpackparse_convert_item(unpacked.data, result, ret_list, true) - != OK) { - break; + + const char *data = blob->bv_ga.ga_data; + size_t remaining = (size_t)len; + while (remaining) { + typval_T tv; + int status = unpack_typval(&data, &remaining, &tv); + if (status != MPACK_OK) { + emsg_mpack_error(status); + return; } - } - msgpack_unpacked_destroy(&unpacked); + tv_list_append_owned_tv(ret_list, tv); + } } /// "msgpackparse" function @@ -5694,28 +5197,6 @@ static void f_or(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) | tv_get_number_chk(&argvars[1], NULL); } -/// "pathshorten()" function -static void f_pathshorten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int trim_len = 1; - - if (argvars[1].v_type != VAR_UNKNOWN) { - trim_len = (int)tv_get_number(&argvars[1]); - if (trim_len < 1) { - trim_len = 1; - } - } - - rettv->v_type = VAR_STRING; - const char *p = tv_get_string_chk(&argvars[0]); - if (p == NULL) { - rettv->vval.v_string = NULL; - } else { - rettv->vval.v_string = xstrdup(p); - shorten_dir_len(rettv->vval.v_string, trim_len); - } -} - /// "pow()" function static void f_pow(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -5872,42 +5353,20 @@ static void f_py3eval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) static void init_srand(uint32_t *const x) FUNC_ATTR_NONNULL_ALL { -#ifndef MSWIN - static int dev_urandom_state = NOTDONE; // FAIL or OK once tried - - if (dev_urandom_state != FAIL) { - const int fd = os_open("/dev/urandom", O_RDONLY, 0); - struct { - union { - uint32_t number; - char bytes[sizeof(uint32_t)]; - } contents; - } buf; - - // Attempt reading /dev/urandom. - if (fd == -1) { - dev_urandom_state = FAIL; - } else { - buf.contents.number = 0; - if (read(fd, buf.contents.bytes, sizeof(uint32_t)) != sizeof(uint32_t)) { - dev_urandom_state = FAIL; - } else { - dev_urandom_state = OK; - *x = buf.contents.number; - } - os_close(fd); - } - } - if (dev_urandom_state != OK) { - // Reading /dev/urandom doesn't work, fall back to os_hrtime() XOR with process ID -#endif - // uncrustify:off - *x = (uint32_t)os_hrtime(); - *x ^= (uint32_t)os_get_pid(); -#ifndef MSWIN + union { + uint32_t number; + uint8_t bytes[sizeof(uint32_t)]; + } buf; + + if (uv_random(NULL, NULL, buf.bytes, sizeof(buf.bytes), 0, NULL) == 0) { + *x = buf.number; + return; } -#endif - // uncrustify:on + + // The system's random number generator doesn't work, + // fall back to os_hrtime() XOR with process ID + *x = (uint32_t)os_hrtime(); + *x ^= (uint32_t)os_get_pid(); } static inline uint32_t splitmix32(uint32_t *const x) @@ -6076,267 +5535,6 @@ static void f_range(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// Evaluate "expr" (= "context") for readdir(). -static varnumber_T readdir_checkitem(void *context, const char *name) - FUNC_ATTR_NONNULL_ALL -{ - typval_T *expr = (typval_T *)context; - typval_T argv[2]; - varnumber_T retval = 0; - bool error = false; - - if (expr->v_type == VAR_UNKNOWN) { - return 1; - } - - typval_T save_val; - prepare_vimvar(VV_VAL, &save_val); - set_vim_var_string(VV_VAL, name, -1); - argv[0].v_type = VAR_STRING; - argv[0].vval.v_string = (char *)name; - - typval_T rettv; - if (eval_expr_typval(expr, false, argv, 1, &rettv) == FAIL) { - goto theend; - } - - retval = tv_get_number_chk(&rettv, &error); - if (error) { - retval = -1; - } - - tv_clear(&rettv); - -theend: - set_vim_var_string(VV_VAL, NULL, 0); - restore_vimvar(VV_VAL, &save_val); - return retval; -} - -/// "readdir()" function -static void f_readdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - tv_list_alloc_ret(rettv, kListLenUnknown); - - const char *path = tv_get_string(&argvars[0]); - typval_T *expr = &argvars[1]; - garray_T ga; - int ret = readdir_core(&ga, path, (void *)expr, readdir_checkitem); - if (ret == OK && ga.ga_len > 0) { - for (int i = 0; i < ga.ga_len; i++) { - const char *p = ((const char **)ga.ga_data)[i]; - tv_list_append_string(rettv->vval.v_list, p, -1); - } - } - ga_clear_strings(&ga); -} - -/// "readfile()" or "readblob()" function -static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_blob) -{ - bool binary = false; - bool blob = always_blob; - FILE *fd; - char buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 - int io_size = sizeof(buf); - char *prev = NULL; // previously read bytes, if any - ptrdiff_t prevlen = 0; // length of data in prev - ptrdiff_t prevsize = 0; // size of prev buffer - int64_t maxline = MAXLNUM; - off_T offset = 0; - off_T size = -1; - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (always_blob) { - offset = (off_T)tv_get_number(&argvars[1]); - if (argvars[2].v_type != VAR_UNKNOWN) { - size = (off_T)tv_get_number(&argvars[2]); - } - } else { - if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { - binary = true; - } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) { - blob = true; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - maxline = tv_get_number(&argvars[2]); - } - } - } - - if (blob) { - tv_blob_alloc_ret(rettv); - } else { - tv_list_alloc_ret(rettv, kListLenUnknown); - } - - // Always open the file in binary mode, library functions have a mind of - // their own about CR-LF conversion. - const char *const fname = tv_get_string(&argvars[0]); - - if (os_isdir(fname)) { - semsg(_(e_isadir2), fname); - return; - } - if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) { - semsg(_(e_notopen), *fname == NUL ? _("<empty>") : fname); - return; - } - - if (blob) { - if (read_blob(fd, rettv, offset, size) == FAIL) { - semsg(_(e_notread), fname); - } - fclose(fd); - return; - } - - list_T *const l = rettv->vval.v_list; - - while (maxline < 0 || tv_list_len(l) < maxline) { - int readlen = (int)fread(buf, 1, (size_t)io_size, fd); - - // This for loop processes what was read, but is also entered at end - // of file so that either: - // - an incomplete line gets written - // - a "binary" file gets an empty line at the end if it ends in a - // newline. - char *p; // Position in buf. - char *start; // Start of current line. - for (p = buf, start = buf; - p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); - p++) { - if (readlen <= 0 || *p == '\n') { - char *s = NULL; - size_t len = (size_t)(p - start); - - // Finished a line. Remove CRs before NL. - if (readlen > 0 && !binary) { - while (len > 0 && start[len - 1] == '\r') { - len--; - } - // removal may cross back to the "prev" string - if (len == 0) { - while (prevlen > 0 && prev[prevlen - 1] == '\r') { - prevlen--; - } - } - } - if (prevlen == 0) { - assert(len < INT_MAX); - s = xmemdupz(start, len); - } else { - // Change "prev" buffer to be the right size. This way - // the bytes are only copied once, and very long lines are - // allocated only once. - s = xrealloc(prev, (size_t)prevlen + len + 1); - memcpy(s + prevlen, start, len); - s[(size_t)prevlen + len] = NUL; - prev = NULL; // the list will own the string - prevlen = prevsize = 0; - } - - tv_list_append_owned_tv(l, (typval_T) { - .v_type = VAR_STRING, - .v_lock = VAR_UNLOCKED, - .vval.v_string = s, - }); - - start = p + 1; // Step over newline. - if (maxline < 0) { - if (tv_list_len(l) > -maxline) { - assert(tv_list_len(l) == 1 + (-maxline)); - tv_list_item_remove(l, tv_list_first(l)); - } - } else if (tv_list_len(l) >= maxline) { - assert(tv_list_len(l) == maxline); - break; - } - if (readlen <= 0) { - break; - } - } else if (*p == NUL) { - *p = '\n'; - // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this - // when finding the BF and check the previous two bytes. - } else if ((uint8_t)(*p) == 0xbf && !binary) { - // Find the two bytes before the 0xbf. If p is at buf, or buf + 1, - // these may be in the "prev" string. - char back1 = p >= buf + 1 ? p[-1] - : prevlen >= 1 ? prev[prevlen - 1] : NUL; - char back2 = p >= buf + 2 ? p[-2] - : (p == buf + 1 && prevlen >= 1 - ? prev[prevlen - 1] - : prevlen >= 2 ? prev[prevlen - 2] : NUL); - - if ((uint8_t)back2 == 0xef && (uint8_t)back1 == 0xbb) { - char *dest = p - 2; - - // Usually a BOM is at the beginning of a file, and so at - // the beginning of a line; then we can just step over it. - if (start == dest) { - start = p + 1; - } else { - // have to shuffle buf to close gap - int adjust_prevlen = 0; - - if (dest < buf) { - // adjust_prevlen must be 1 or 2. - adjust_prevlen = (int)(buf - dest); - dest = buf; - } - if (readlen > p - buf + 1) { - memmove(dest, p + 1, (size_t)readlen - (size_t)(p - buf) - 1); - } - readlen -= 3 - adjust_prevlen; - prevlen -= adjust_prevlen; - p = dest - 1; - } - } - } - } // for - - if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) { - break; - } - if (start < p) { - // There's part of a line in buf, store it in "prev". - if (p - start + prevlen >= prevsize) { - // A common use case is ordinary text files and "prev" gets a - // fragment of a line, so the first allocation is made - // small, to avoid repeatedly 'allocing' large and - // 'reallocing' small. - if (prevsize == 0) { - prevsize = p - start; - } else { - ptrdiff_t grow50pc = (prevsize * 3) / 2; - ptrdiff_t growmin = (p - start) * 2 + prevlen; - prevsize = grow50pc > growmin ? grow50pc : growmin; - } - prev = xrealloc(prev, (size_t)prevsize); - } - // Add the line part to end of "prev". - memmove(prev + prevlen, start, (size_t)(p - start)); - prevlen += p - start; - } - } // while - - xfree(prev); - fclose(fd); -} - -/// "readblob()" function -static void f_readblob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - read_file_or_blob(argvars, rettv, true); -} - -/// "readfile()" function -static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - read_file_or_blob(argvars, rettv, false); -} - /// "getreginfo()" function static void f_getreginfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -6387,6 +5585,14 @@ static void f_getreginfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } +static void return_register(int regname, typval_T *rettv) +{ + char buf[2] = { (char)regname, 0 }; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xstrdup(buf); +} + /// "reg_executing()" function static void f_reg_executing(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -6511,18 +5717,6 @@ static void f_remove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// "rename({from}, {to})" function -static void f_rename(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - if (check_secure()) { - rettv->vval.v_number = -1; - } else { - char buf[NUMBUFLEN]; - rettv->vval.v_number = vim_rename(tv_get_string(&argvars[0]), - tv_get_string_buf(&argvars[1], buf)); - } -} - /// "repeat()" function static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -6591,175 +5785,6 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// "resolve()" function -static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_STRING; - const char *fname = tv_get_string(&argvars[0]); -#ifdef MSWIN - char *v = os_resolve_shortcut(fname); - if (v == NULL) { - if (os_is_reparse_point_include(fname)) { - v = os_realpath(fname, NULL, MAXPATHL + 1); - } - } - rettv->vval.v_string = (v == NULL ? xstrdup(fname) : v); -#else -# ifdef HAVE_READLINK - { - bool is_relative_to_current = false; - bool has_trailing_pathsep = false; - int limit = 100; - - char *p = xstrdup(fname); - - if (p[0] == '.' && (vim_ispathsep(p[1]) - || (p[1] == '.' && (vim_ispathsep(p[2]))))) { - is_relative_to_current = true; - } - - ptrdiff_t len = (ptrdiff_t)strlen(p); - if (len > 1 && after_pathsep(p, p + len)) { - has_trailing_pathsep = true; - p[len - 1] = NUL; // The trailing slash breaks readlink(). - } - - char *q = (char *)path_next_component(p); - char *remain = NULL; - if (*q != NUL) { - // Separate the first path component in "p", and keep the - // remainder (beginning with the path separator). - remain = xstrdup(q - 1); - q[-1] = NUL; - } - - char *const buf = xmallocz(MAXPATHL); - - char *cpy; - while (true) { - while (true) { - len = readlink(p, buf, MAXPATHL); - if (len <= 0) { - break; - } - buf[len] = NUL; - - if (limit-- == 0) { - xfree(p); - xfree(remain); - emsg(_("E655: Too many symbolic links (cycle?)")); - rettv->vval.v_string = NULL; - xfree(buf); - return; - } - - // Ensure that the result will have a trailing path separator - // if the argument has one. - if (remain == NULL && has_trailing_pathsep) { - add_pathsep(buf); - } - - // Separate the first path component in the link value and - // concatenate the remainders. - q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf); - if (*q != NUL) { - cpy = remain; - remain = (remain - ? concat_str(q - 1, remain) - : xstrdup(q - 1)); - xfree(cpy); - q[-1] = NUL; - } - - q = path_tail(p); - if (q > p && *q == NUL) { - // Ignore trailing path separator. - p[q - p - 1] = NUL; - q = path_tail(p); - } - if (q > p && !path_is_absolute(buf)) { - // Symlink is relative to directory of argument. Replace the - // symlink with the resolved name in the same directory. - const size_t p_len = strlen(p); - const size_t buf_len = strlen(buf); - p = xrealloc(p, p_len + buf_len + 1); - memcpy(path_tail(p), buf, buf_len + 1); - } else { - xfree(p); - p = xstrdup(buf); - } - } - - if (remain == NULL) { - break; - } - - // Append the first path component of "remain" to "p". - q = (char *)path_next_component(remain + 1); - len = q - remain - (*q != NUL); - const size_t p_len = strlen(p); - cpy = xmallocz(p_len + (size_t)len); - memcpy(cpy, p, p_len + 1); - xstrlcat(cpy + p_len, remain, (size_t)len + 1); - xfree(p); - p = cpy; - - // Shorten "remain". - if (*q != NUL) { - STRMOVE(remain, q - 1); - } else { - XFREE_CLEAR(remain); - } - } - - // If the result is a relative path name, make it explicitly relative to - // the current directory if and only if the argument had this form. - if (!vim_ispathsep(*p)) { - if (is_relative_to_current - && *p != NUL - && !(p[0] == '.' - && (p[1] == NUL - || vim_ispathsep(p[1]) - || (p[1] == '.' - && (p[2] == NUL - || vim_ispathsep(p[2])))))) { - // Prepend "./". - cpy = concat_str("./", p); - xfree(p); - p = cpy; - } else if (!is_relative_to_current) { - // Strip leading "./". - q = p; - while (q[0] == '.' && vim_ispathsep(q[1])) { - q += 2; - } - if (q > p) { - STRMOVE(p, p + 2); - } - } - } - - // Ensure that the result will have no trailing path separator - // if the argument had none. But keep "/" or "//". - if (!has_trailing_pathsep) { - q = p + strlen(p); - if (after_pathsep(p, q)) { - *path_tail_with_sep(p) = NUL; - } - } - - rettv->vval.v_string = p; - xfree(buf); - } -# else - char *v = os_realpath(fname, NULL, MAXPATHL + 1); - rettv->vval.v_string = v == NULL ? xstrdup(fname) : v; -# endif -#endif - - simplify_filename(rettv->vval.v_string); -} - /// "reverse({list})" function static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -7412,6 +6437,21 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } +static void screenchar_adjust(ScreenGrid **grid, int *row, int *col) +{ + // TODO(bfredl): this is a hack for legacy tests which use screenchar() + // to check printed messages on the screen (but not floats etc + // as these are not legacy features). If the compositor is refactored to + // have its own buffer, this should just read from it instead. + msg_scroll_flush(); + + *grid = ui_comp_get_grid_at_coord(*row, *col); + + // Make `row` and `col` relative to the grid + *row -= (*grid)->comp_row; + *col -= (*grid)->comp_col; +} + /// "screenattr()" function static void f_screenattr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -8293,21 +7333,12 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (col < 0) { return; // type error; errmsg already given } - rettv->vval.v_number = get_sw_value_col(curbuf, col); + rettv->vval.v_number = get_sw_value_col(curbuf, col, false); return; } rettv->vval.v_number = get_sw_value(curbuf); } -/// "simplify()" function -static void f_simplify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *const p = tv_get_string(&argvars[0]); - rettv->vval.v_string = xstrdup(p); - simplify_filename(rettv->vval.v_string); // Simplify in place. - rettv->v_type = VAR_STRING; -} - /// "sockconnect()" function static void f_sockconnect(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -8598,6 +7629,33 @@ theend: p_cpo = save_cpo; } +/// "stdpath()" helper for list results +static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) + FUNC_ATTR_NONNULL_ALL +{ + list_T *const list = tv_list_alloc(kListLenShouldKnow); + rettv->v_type = VAR_LIST; + rettv->vval.v_list = list; + tv_list_ref(list); + char *const dirs = stdpaths_get_xdg_var(xdg); + if (dirs == NULL) { + return; + } + const void *iter = NULL; + const char *appname = get_appname(false); + do { + size_t dir_len; + const char *dir; + iter = vim_env_iter(ENV_SEPCHAR, dirs, iter, &dir, &dir_len); + if (dir != NULL && dir_len > 0) { + char *dir_with_nvim = xmemdupz(dir, dir_len); + dir_with_nvim = concat_fnames_realloc(dir_with_nvim, appname, true); + tv_list_append_allocated_string(list, dir_with_nvim); + } + } while (iter != NULL); + xfree(dirs); +} + /// "stdpath(type)" function static void f_stdpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -9066,13 +8124,6 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) (char *)tag_pattern, (char *)fname); } -/// "tempname()" function -static void f_tempname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_tempname(); -} - /// "termopen(cmd[, cwd])" function static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -9157,7 +8208,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - int pid = chan->stream.pty.process.pid; + int pid = chan->stream.pty.proc.pid; // "./…" => "/home/foo/…" vim_FullName(cwd, NameBuff, sizeof(NameBuff), false); @@ -9165,13 +8216,13 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true); // Trim slash. if (len != 1 && (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/')) { - IObuff[len - 1] = '\0'; + IObuff[len - 1] = NUL; } if (len == 1 && IObuff[0] == '/') { // Avoid ambiguity in the URI when CWD is root directory. IObuff[1] = '.'; - IObuff[2] = '\0'; + IObuff[2] = NUL; } // Terminal URI: "term://$CWD//$PID:$CMD" @@ -9418,104 +8469,6 @@ static void f_wordcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) cursor_pos_info(rettv->vval.v_dict); } -/// "writefile()" function -static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = -1; - - if (check_secure()) { - return; - } - - if (argvars[0].v_type == VAR_LIST) { - TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { - if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) { - return; - } - }); - } else if (argvars[0].v_type != VAR_BLOB) { - semsg(_(e_invarg2), - _("writefile() first argument must be a List or a Blob")); - return; - } - - bool binary = false; - bool append = false; - bool defer = false; - bool do_fsync = !!p_fs; - bool mkdir_p = false; - if (argvars[2].v_type != VAR_UNKNOWN) { - const char *const flags = tv_get_string_chk(&argvars[2]); - if (flags == NULL) { - return; - } - for (const char *p = flags; *p; p++) { - switch (*p) { - case 'b': - binary = true; break; - case 'a': - append = true; break; - case 'D': - defer = true; break; - case 's': - do_fsync = true; break; - case 'S': - do_fsync = false; break; - case 'p': - mkdir_p = true; break; - default: - // Using %s, p and not %c, *p to preserve multibyte characters - semsg(_("E5060: Unknown flag: %s"), p); - return; - } - } - } - - char buf[NUMBUFLEN]; - const char *const fname = tv_get_string_buf_chk(&argvars[1], buf); - if (fname == NULL) { - return; - } - - if (defer && !can_add_defer()) { - return; - } - - FileDescriptor fp; - int error; - if (*fname == NUL) { - emsg(_("E482: Can't open file with an empty name")); - } else if ((error = file_open(&fp, fname, - ((append ? kFileAppend : kFileTruncate) - | (mkdir_p ? kFileMkDir : kFileCreate) - | kFileCreate), 0666)) != 0) { - semsg(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error)); - } else { - if (defer) { - typval_T tv = { - .v_type = VAR_STRING, - .v_lock = VAR_UNLOCKED, - .vval.v_string = FullName_save(fname, false), - }; - add_defer("delete", 1, &tv); - } - - bool write_ok; - if (argvars[0].v_type == VAR_BLOB) { - write_ok = write_blob(&fp, argvars[0].vval.v_blob); - } else { - write_ok = write_list(&fp, argvars[0].vval.v_list, binary); - } - if (write_ok) { - rettv->vval.v_number = 0; - } - if ((error = file_close(&fp, do_fsync)) != 0) { - semsg(_("E80: Error when closing file %s: %s"), - fname, os_strerror(error)); - } - } -} - /// "xor(expr, expr)" function static void f_xor(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index eb8c89c36e..e7b6a0feee 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -10,11 +10,13 @@ #include "nvim/ascii_defs.h" #include "nvim/assert_defs.h" #include "nvim/charset.h" +#include "nvim/errors.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" #include "nvim/eval/executor.h" #include "nvim/eval/gc.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/typval_encode.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" @@ -58,6 +60,13 @@ typedef struct { typedef int (*ListSorter)(const void *, const void *); +/// Type for tv_dict2list() function +typedef enum { + kDict2ListKeys, ///< List dictionary keys. + kDict2ListValues, ///< List dictionary values. + kDict2ListItems, ///< List dictionary contents: [keys, values]. +} DictListType; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/typval.c.generated.h" #endif @@ -84,6 +93,8 @@ static const char e_string_or_number_required_for_argument_nr[] = N_("E1220: String or Number required for argument %d"); static const char e_string_or_list_required_for_argument_nr[] = N_("E1222: String or List required for argument %d"); +static const char e_string_list_or_dict_required_for_argument_nr[] + = N_("E1225: String, List or Dictionary required for argument %d"); static const char e_list_or_blob_required_for_argument_nr[] = N_("E1226: List or Blob required for argument %d"); static const char e_blob_required_for_argument_nr[] @@ -474,13 +485,14 @@ void tv_list_append_tv(list_T *const l, typval_T *const tv) /// Like tv_list_append_tv(), but tv is moved to a list /// /// This means that it is no longer valid to use contents of the typval_T after -/// function exits. -void tv_list_append_owned_tv(list_T *const l, typval_T tv) +/// function exits. A pointer is returned to the allocated typval which can be used +typval_T *tv_list_append_owned_tv(list_T *const l, typval_T tv) FUNC_ATTR_NONNULL_ALL { listitem_T *const li = tv_list_item_alloc(); *TV_LIST_ITEM_TV(li) = tv; tv_list_append(l, li); + return TV_LIST_ITEM_TV(li); } /// Append a list to a list as one item @@ -781,6 +793,51 @@ void tv_list_flatten(list_T *list, listitem_T *first, int64_t maxitems, int64_t } } +/// "items(list)" function +/// Caller must have already checked that argvars[0] is a List. +static void tv_list2items(typval_T *argvars, typval_T *rettv) +{ + list_T *l = argvars[0].vval.v_list; + + tv_list_alloc_ret(rettv, tv_list_len(l)); + if (l == NULL) { + return; // null list behaves like an empty list + } + + varnumber_T idx = 0; + TV_LIST_ITER(l, li, { + list_T *l2 = tv_list_alloc(2); + tv_list_append_list(rettv->vval.v_list, l2); + tv_list_append_number(l2, idx); + tv_list_append_tv(l2, TV_LIST_ITEM_TV(li)); + idx++; + }); +} + +/// "items(string)" function +/// Caller must have already checked that argvars[0] is a String. +static void tv_string2items(typval_T *argvars, typval_T *rettv) +{ + const char *p = argvars[0].vval.v_string; + + tv_list_alloc_ret(rettv, kListLenMayKnow); + if (p == NULL) { + return; // null string behaves like an empty string + } + + for (varnumber_T idx = 0; *p != NUL; idx++) { + int len = utfc_ptr2len(p); + if (len == 0) { + break; + } + list_T *l2 = tv_list_alloc(2); + tv_list_append_list(rettv->vval.v_list, l2); + tv_list_append_number(l2, idx); + tv_list_append_string(l2, p, len); + p += len; + } +} + /// Extend first list with the second /// /// @param[out] l1 List to extend. @@ -1459,10 +1516,9 @@ void f_uniq(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// @param[in] l1 First list to compare. /// @param[in] l2 Second list to compare. /// @param[in] ic True if case is to be ignored. -/// @param[in] recursive True when used recursively. /// /// @return True if lists are equal, false otherwise. -bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, const bool recursive) +bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic) FUNC_ATTR_WARN_UNUSED_RESULT { if (l1 == l2) { @@ -1484,8 +1540,7 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, const bool for (; item1 != NULL && item2 != NULL ; (item1 = TV_LIST_ITEM_NEXT(l1, item1), item2 = TV_LIST_ITEM_NEXT(l2, item2))) { - if (!tv_equal(TV_LIST_ITEM_TV(item1), TV_LIST_ITEM_TV(item2), ic, - recursive)) { + if (!tv_equal(TV_LIST_ITEM_TV(item1), TV_LIST_ITEM_TV(item2), ic)) { return false; } } @@ -1823,7 +1878,7 @@ char *callback_to_string(Callback *cb, Arena *arena) snprintf(msg, msglen, "<vim partial: %s>", cb->data.partial->pt_name); break; default: - *msg = '\0'; + *msg = NUL; break; } return msg; @@ -2678,8 +2733,9 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action /// @param[in] d1 First dictionary. /// @param[in] d2 Second dictionary. /// @param[in] ic True if case is to be ignored. -/// @param[in] recursive True when used recursively. -bool tv_dict_equal(dict_T *const d1, dict_T *const d2, const bool ic, const bool recursive) +/// +/// @return True if dictionaries are equal, false otherwise. +bool tv_dict_equal(dict_T *const d1, dict_T *const d2, const bool ic) FUNC_ATTR_WARN_UNUSED_RESULT { if (d1 == d2) { @@ -2701,7 +2757,7 @@ bool tv_dict_equal(dict_T *const d1, dict_T *const d2, const bool ic, const bool if (di2 == NULL) { return false; } - if (!tv_equal(&di1->di_tv, &di2->di_tv, ic, recursive)) { + if (!tv_equal(&di1->di_tv, &di2->di_tv, ic)) { return false; } }); @@ -3063,8 +3119,7 @@ void f_blob2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// list2blob() function void f_list2blob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - tv_blob_alloc_ret(rettv); - blob_T *const blob = rettv->vval.v_blob; + blob_T *blob = tv_blob_alloc_ret(rettv); if (tv_check_for_list_arg(argvars, 0) == FAIL) { return; @@ -3135,49 +3190,45 @@ void tv_dict_alloc_ret(typval_T *const ret_tv) /// Turn a dictionary into a list /// -/// @param[in] tv Dictionary to convert. Is checked for actually being +/// @param[in] argvars Arguments to items(). The first argument is check for being /// a dictionary, will give an error if not. /// @param[out] rettv Location where result will be saved. /// @param[in] what What to save in rettv. -static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictListType what) +static void tv_dict2list(typval_T *const argvars, typval_T *const rettv, const DictListType what) { - if (tv->v_type != VAR_DICT) { - emsg(_(e_dictreq)); + if ((what == kDict2ListItems + ? tv_check_for_string_or_list_or_dict_arg(argvars, 0) + : tv_check_for_dict_arg(argvars, 0)) == FAIL) { + tv_list_alloc_ret(rettv, 0); return; } - tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); - if (tv->vval.v_dict == NULL) { + dict_T *d = argvars[0].vval.v_dict; + tv_list_alloc_ret(rettv, tv_dict_len(d)); + if (d == NULL) { // NULL dict behaves like an empty dict return; } - TV_DICT_ITER(tv->vval.v_dict, di, { + TV_DICT_ITER(d, di, { typval_T tv_item = { .v_lock = VAR_UNLOCKED }; switch (what) { - case kDictListKeys: + case kDict2ListKeys: tv_item.v_type = VAR_STRING; tv_item.vval.v_string = xstrdup(di->di_key); break; - case kDictListValues: + case kDict2ListValues: tv_copy(&di->di_tv, &tv_item); break; - case kDictListItems: { + case kDict2ListItems: { // items() list_T *const sub_l = tv_list_alloc(2); tv_item.v_type = VAR_LIST; tv_item.vval.v_list = sub_l; tv_list_ref(sub_l); - - tv_list_append_owned_tv(sub_l, (typval_T) { - .v_type = VAR_STRING, - .v_lock = VAR_UNLOCKED, - .vval.v_string = xstrdup(di->di_key), - }); - + tv_list_append_string(sub_l, di->di_key, -1); tv_list_append_tv(sub_l, &di->di_tv); - break; } } @@ -3189,19 +3240,25 @@ static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictLi /// "items(dict)" function void f_items(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - tv_dict_list(argvars, rettv, 2); + if (argvars[0].v_type == VAR_STRING) { + tv_string2items(argvars, rettv); + } else if (argvars[0].v_type == VAR_LIST) { + tv_list2items(argvars, rettv); + } else { + tv_dict2list(argvars, rettv, kDict2ListItems); + } } /// "keys()" function void f_keys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - tv_dict_list(argvars, rettv, 0); + tv_dict2list(argvars, rettv, kDict2ListKeys); } /// "values(dict)" function void f_values(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - tv_dict_list(argvars, rettv, 1); + tv_dict2list(argvars, rettv, kDict2ListValues); } /// "has_key()" function @@ -3251,11 +3308,12 @@ void tv_dict_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) /// Also sets reference count. /// /// @param[out] ret_tv Structure where blob is saved. -void tv_blob_alloc_ret(typval_T *const ret_tv) +blob_T *tv_blob_alloc_ret(typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { blob_T *const b = tv_blob_alloc(); tv_blob_set_ret(ret_tv, b); + return b; } /// Copy a blob typval to a different typval. @@ -3283,6 +3341,7 @@ void tv_blob_copy(blob_T *const from, typval_T *const to) //{{{3 Clear #define TYPVAL_ENCODE_ALLOW_SPECIALS false +#define TYPVAL_ENCODE_CHECK_BEFORE #define TYPVAL_ENCODE_CONV_NIL(tv) \ do { \ @@ -3499,6 +3558,7 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, dict_T **const dic #undef TYPVAL_ENCODE_FIRST_ARG_NAME #undef TYPVAL_ENCODE_ALLOW_SPECIALS +#undef TYPVAL_ENCODE_CHECK_BEFORE #undef TYPVAL_ENCODE_CONV_NIL #undef TYPVAL_ENCODE_CONV_BOOL #undef TYPVAL_ENCODE_CONV_NUMBER @@ -3835,10 +3895,9 @@ static int tv_equal_recurse_limit; /// @param[in] tv1 First value to compare. /// @param[in] tv2 Second value to compare. /// @param[in] ic True if case is to be ignored. -/// @param[in] recursive True when used recursively. /// /// @return true if values are equal. -bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, const bool recursive) +bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { // TODO(ZyX-I): Make this not recursive @@ -3854,7 +3913,7 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, const boo // Reduce the limit every time running into it. That should work fine for // deeply linked structures that are not recursively linked and catch // recursiveness quickly. - if (!recursive) { + if (recursive_cnt == 0) { tv_equal_recurse_limit = 1000; } if (recursive_cnt >= tv_equal_recurse_limit) { @@ -3865,15 +3924,13 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, const boo switch (tv1->v_type) { case VAR_LIST: { recursive_cnt++; - const bool r = tv_list_equal(tv1->vval.v_list, tv2->vval.v_list, ic, - true); + const bool r = tv_list_equal(tv1->vval.v_list, tv2->vval.v_list, ic); recursive_cnt--; return r; } case VAR_DICT: { recursive_cnt++; - const bool r = tv_dict_equal(tv1->vval.v_dict, tv2->vval.v_dict, ic, - true); + const bool r = tv_dict_equal(tv1->vval.v_dict, tv2->vval.v_dict, ic); recursive_cnt--; return r; } @@ -4152,6 +4209,29 @@ linenr_T tv_get_lnum(const typval_T *const tv) return lnum; } +/// Get the line number from Vimscript object +/// +/// @note Unlike tv_get_lnum(), this one supports only "$" special string. +/// +/// @param[in] tv Object to get value from. Is expected to be a number or +/// a special string "$". +/// @param[in] buf Buffer to take last line number from in case tv is "$". May +/// be NULL, in this case "$" results in zero return. +/// +/// @return Line number or 0 in case of error. +linenr_T tv_get_lnum_buf(const typval_T *const tv, const buf_T *const buf) + FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (tv->v_type == VAR_STRING + && tv->vval.v_string != NULL + && tv->vval.v_string[0] == '$' + && tv->vval.v_string[1] == NUL + && buf != NULL) { + return buf->b_ml.ml_line_count; + } + return (linenr_T)tv_get_number_chk(tv, NULL); +} + /// Get the floating-point value of a Vimscript object /// /// Raises an error if object is not number or floating-point. @@ -4399,6 +4479,19 @@ int tv_check_for_opt_string_or_list_arg(const typval_T *const args, const int id || tv_check_for_string_or_list_arg(args, idx) != FAIL) ? OK : FAIL; } +/// Give an error and return FAIL unless "args[idx]" is a string or a list or a dict +int tv_check_for_string_or_list_or_dict_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_STRING + && args[idx].v_type != VAR_LIST + && args[idx].v_type != VAR_DICT) { + semsg(_(e_string_list_or_dict_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + /// Give an error and return FAIL unless "args[idx]" is a string /// or a function reference. int tv_check_for_string_or_func_arg(const typval_T *const args, const int idx) diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index f9ebd2f778..ff70eadaaa 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -7,7 +7,6 @@ #include <string.h> #include "nvim/eval/typval_defs.h" // IWYU pragma: keep -#include "nvim/func_attr.h" #include "nvim/gettext_defs.h" #include "nvim/hashtab.h" #include "nvim/lib/queue_defs.h" @@ -16,6 +15,10 @@ #include "nvim/message.h" #include "nvim/types_defs.h" +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/typval.h.inline.generated.h" +#endif + // In a hashtab item "hi_key" points to "di_key" in a dictitem. // This avoids adding a pointer to the hashtab item. @@ -23,15 +26,13 @@ #define TV_DICT_HI2DI(hi) \ ((dictitem_T *)((hi)->hi_key - offsetof(dictitem_T, di_key))) -static inline void tv_list_ref(list_T *l) - REAL_FATTR_ALWAYS_INLINE; - /// Increase reference count for a given list /// /// Does nothing for NULL lists. /// /// @param[in,out] l List to modify. static inline void tv_list_ref(list_T *const l) + FUNC_ATTR_ALWAYS_INLINE { if (l == NULL) { return; @@ -39,29 +40,25 @@ static inline void tv_list_ref(list_T *const l) l->lv_refcount++; } -static inline void tv_list_set_ret(typval_T *tv, list_T *l) - REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1); - /// Set a list as the return value. Increments the reference count. /// /// @param[out] tv Object to receive the list /// @param[in,out] l List to pass to the object static inline void tv_list_set_ret(typval_T *const tv, list_T *const l) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1) { tv->v_type = VAR_LIST; tv->vval.v_list = l; tv_list_ref(l); } -static inline VarLockStatus tv_list_locked(const list_T *l) - REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; - /// Get list lock status /// /// Returns VAR_FIXED for NULL lists. /// /// @param[in] l List to check. static inline VarLockStatus tv_list_locked(const list_T *const l) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { if (l == NULL) { return VAR_FIXED; @@ -84,9 +81,6 @@ static inline void tv_list_set_lock(list_T *const l, const VarLockStatus lock) l->lv_lock = lock; } -static inline void tv_list_set_copyid(list_T *l, int copyid) - REAL_FATTR_NONNULL_ALL; - /// Set list copyID /// /// Does not expect NULL list, be careful. @@ -94,17 +88,16 @@ static inline void tv_list_set_copyid(list_T *l, int copyid) /// @param[out] l List to modify. /// @param[in] copyid New copyID. static inline void tv_list_set_copyid(list_T *const l, const int copyid) + FUNC_ATTR_NONNULL_ALL { l->lv_copyID = copyid; } -static inline int tv_list_len(const list_T *l) - REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; - /// Get the number of items in a list /// /// @param[in] l List to check. static inline int tv_list_len(const list_T *const l) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { if (l == NULL) { return 0; @@ -112,22 +105,17 @@ static inline int tv_list_len(const list_T *const l) return l->lv_len; } -static inline int tv_list_copyid(const list_T *l) - REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; - /// Get list copyID /// /// Does not expect NULL list, be careful. /// /// @param[in] l List to check. static inline int tv_list_copyid(const list_T *const l) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { return l->lv_copyID; } -static inline list_T *tv_list_latest_copy(const list_T *l) - REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; - /// Get latest list copy /// /// Gets lv_copylist field assigned by tv_list_copy() earlier. @@ -136,13 +124,11 @@ static inline list_T *tv_list_latest_copy(const list_T *l) /// /// @param[in] l List to check. static inline list_T *tv_list_latest_copy(const list_T *const l) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { return l->lv_copylist; } -static inline int tv_list_uidx(const list_T *l, int n) - REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; - /// Normalize index: that is, return either -1 or non-negative index /// /// @param[in] l List to index. Used to get length. @@ -150,6 +136,7 @@ static inline int tv_list_uidx(const list_T *l, int n) /// /// @return -1 or list index in range [0, tv_list_len(l)). static inline int tv_list_uidx(const list_T *const l, int n) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { // Negative index is relative to the end. if (n < 0) { @@ -163,9 +150,6 @@ static inline int tv_list_uidx(const list_T *const l, int n) return n; } -static inline bool tv_list_has_watchers(const list_T *l) - REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; - /// Check whether list has watchers /// /// E.g. is referenced by a :for loop. @@ -174,19 +158,18 @@ static inline bool tv_list_has_watchers(const list_T *l) /// /// @return true if there are watchers, false otherwise. static inline bool tv_list_has_watchers(const list_T *const l) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return l && l->lv_watch; } -static inline listitem_T *tv_list_first(const list_T *l) - REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; - /// Get first list item /// /// @param[in] l List to get item from. /// /// @return List item or NULL in case of an empty list. static inline listitem_T *tv_list_first(const list_T *const l) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { if (l == NULL) { return NULL; @@ -194,15 +177,13 @@ static inline listitem_T *tv_list_first(const list_T *const l) return l->lv_first; } -static inline listitem_T *tv_list_last(const list_T *l) - REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; - /// Get last list item /// /// @param[in] l List to get item from. /// /// @return List item or NULL in case of an empty list. static inline listitem_T *tv_list_last(const list_T *const l) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { if (l == NULL) { return NULL; @@ -210,14 +191,12 @@ static inline listitem_T *tv_list_last(const list_T *const l) return l->lv_last; } -static inline void tv_dict_set_ret(typval_T *tv, dict_T *d) - REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1); - /// Set a dictionary as the return value /// /// @param[out] tv Object to receive the dictionary /// @param[in,out] d Dictionary to pass to the object static inline void tv_dict_set_ret(typval_T *const tv, dict_T *const d) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1) { tv->v_type = VAR_DICT; tv->vval.v_dict = d; @@ -226,13 +205,11 @@ static inline void tv_dict_set_ret(typval_T *const tv, dict_T *const d) } } -static inline long tv_dict_len(const dict_T *d) - REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; - /// Get the number of items in a Dictionary /// /// @param[in] d Dictionary to check. static inline long tv_dict_len(const dict_T *const d) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { if (d == NULL) { return 0; @@ -240,22 +217,17 @@ static inline long tv_dict_len(const dict_T *const d) return (long)d->dv_hashtab.ht_used; } -static inline bool tv_dict_is_watched(const dict_T *d) - REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; - /// Check if dictionary is watched /// /// @param[in] d Dictionary to check. /// /// @return true if there is at least one watcher. static inline bool tv_dict_is_watched(const dict_T *const d) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return d && !QUEUE_EMPTY(&d->watchers); } -static inline void tv_blob_set_ret(typval_T *tv, blob_T *b) - REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1); - /// Set a blob as the return value. /// /// Increments the reference count. @@ -263,6 +235,7 @@ static inline void tv_blob_set_ret(typval_T *tv, blob_T *b) /// @param[out] tv Object to receive the blob. /// @param[in,out] b Blob to pass to the object. static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1) { tv->v_type = VAR_BLOB; tv->vval.v_blob = b; @@ -271,13 +244,11 @@ static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b) } } -static inline int tv_blob_len(const blob_T *b) - REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; - /// Get the length of the data in the blob, in bytes. /// /// @param[in] b Blob to check. static inline int tv_blob_len(const blob_T *const b) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { if (b == NULL) { return 0; @@ -285,9 +256,6 @@ static inline int tv_blob_len(const blob_T *const b) return b->bv_ga.ga_len; } -static inline uint8_t tv_blob_get(const blob_T *b, int idx) - REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT; - /// Get the byte at index `idx` in the blob. /// /// @param[in] b Blob to index. Cannot be NULL. @@ -295,19 +263,18 @@ static inline uint8_t tv_blob_get(const blob_T *b, int idx) /// /// @return Byte value at the given index. static inline uint8_t tv_blob_get(const blob_T *const b, int idx) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { return ((uint8_t *)b->bv_ga.ga_data)[idx]; } -static inline void tv_blob_set(blob_T *blob, int idx, uint8_t c) - REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; - /// Store the byte `c` at index `idx` in the blob. /// /// @param[in] b Blob to index. Cannot be NULL. /// @param[in] idx Index in a blob. Must be valid. /// @param[in] c Value to store. static inline void tv_blob_set(blob_T *const blob, int idx, uint8_t c) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL { ((uint8_t *)blob->bv_ga.ga_data)[idx] = c; } @@ -417,9 +384,6 @@ extern bool tv_in_free_unref_items; } \ }) -static inline bool tv_get_float_chk(const typval_T *tv, float_T *ret_f) - REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT; - /// Get the float value /// /// Raises an error if object is not number or floating-point. @@ -429,6 +393,7 @@ static inline bool tv_get_float_chk(const typval_T *tv, float_T *ret_f) /// /// @return true in case of success, false if tv is not a number or float. static inline bool tv_get_float_chk(const typval_T *const tv, float_T *const ret_f) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { if (tv->v_type == VAR_FLOAT) { *ret_f = tv->vval.v_float; @@ -442,22 +407,17 @@ static inline bool tv_get_float_chk(const typval_T *const tv, float_T *const ret return false; } -static inline DictWatcher *tv_dict_watcher_node_data(QUEUE *q) - REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL REAL_FATTR_NONNULL_RET - REAL_FATTR_NO_SANITIZE_ADDRESS REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; - /// Compute the `DictWatcher` address from a QUEUE node. /// /// This only exists for .asan-blacklist (ASAN doesn't handle QUEUE_DATA pointer /// arithmetic). static inline DictWatcher *tv_dict_watcher_node_data(QUEUE *q) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET + FUNC_ATTR_NO_SANITIZE_ADDRESS FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return QUEUE_DATA(q, DictWatcher, node); } -static inline bool tv_is_func(typval_T tv) - REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE REAL_FATTR_CONST; - /// Check whether given typval_T contains a function /// /// That is, whether it contains VAR_FUNC or VAR_PARTIAL. @@ -466,6 +426,7 @@ static inline bool tv_is_func(typval_T tv) /// /// @return True if it is a function or a partial, false otherwise. static inline bool tv_is_func(const typval_T tv) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST { return tv.v_type == VAR_FUNC || tv.v_type == VAR_PARTIAL; } diff --git a/src/nvim/eval/typval_defs.h b/src/nvim/eval/typval_defs.h index e88e6a222a..1ec7631c4b 100644 --- a/src/nvim/eval/typval_defs.h +++ b/src/nvim/eval/typval_defs.h @@ -109,7 +109,7 @@ typedef enum { VAR_STRING, ///< String, .v_string is used. VAR_FUNC, ///< Function reference, .v_string is used as function name. VAR_LIST, ///< List, .v_list is used. - VAR_DICT, ///< Dictionary, .v_dict is used. + VAR_DICT, ///< Dict, .v_dict is used. VAR_FLOAT, ///< Floating-point value, .v_float is used. VAR_BOOL, ///< true, false VAR_SPECIAL, ///< Special value (null), .v_special is used. @@ -141,7 +141,7 @@ typedef struct { float_T v_float; ///< Floating-point number, for VAR_FLOAT. char *v_string; ///< String, for VAR_STRING and VAR_FUNC, can be NULL. list_T *v_list; ///< List for VAR_LIST, can be NULL. - dict_T *v_dict; ///< Dictionary for VAR_DICT, can be NULL. + dict_T *v_dict; ///< Dict for VAR_DICT, can be NULL. partial_T *v_partial; ///< Closure: function with args. blob_T *v_blob; ///< Blob for VAR_BLOB, can be NULL. } vval; ///< Actual value. @@ -259,7 +259,7 @@ struct dictvar_S { dict_T *dv_copydict; ///< Copied dict used by deepcopy(). dict_T *dv_used_next; ///< Next dictionary in used dictionaries list. dict_T *dv_used_prev; ///< Previous dictionary in used dictionaries list. - QUEUE watchers; ///< Dictionary key watchers set by user code. + QUEUE watchers; ///< Dict key watchers set by user code. LuaRef lua_table_ref; }; diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index c0cd0ce557..c0e994562c 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -6,6 +6,11 @@ /// something else. For these macros to work the following macros must be /// defined: +/// @def TYPVAL_ENCODE_CHECK_BEFORE +/// @brief Macro used before any specific CONV function +/// +/// can be used for a common check, like flushing a buffer if necessary + /// @def TYPVAL_ENCODE_CONV_NIL /// @brief Macros used to convert NIL value /// @@ -168,8 +173,7 @@ /// point to a special dictionary. /// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR /// (for dictionaries represented as special lists). -/// @param len Dictionary length. Is an expression which evaluates to an -/// integer. +/// @param len Dict length. Is an expression which evaluates to an integer. /// @def TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START /// @brief Macros used after pushing dictionary onto the stack @@ -323,6 +327,7 @@ static int TYPVAL_ENCODE_CONVERT_ONE_VALUE( TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, MPConvStack *const mpstack, MPConvStackVal *const cur_mpsv, typval_T *const tv, const int copyID, const char *const objname) { + TYPVAL_ENCODE_CHECK_BEFORE; switch (tv->v_type) { case VAR_STRING: TYPVAL_ENCODE_CONV_STRING(tv, tv->vval.v_string, tv_strlen(tv)); @@ -420,6 +425,7 @@ static int TYPVAL_ENCODE_CONVERT_ONE_VALUE( break; } } + TYPVAL_ENCODE_CHECK_BEFORE; if (i == ARRAY_SIZE(eval_msgpack_type_lists)) { goto _convert_one_value_regular_dict; } @@ -494,9 +500,7 @@ static int TYPVAL_ENCODE_CONVERT_ONE_VALUE( } TYPVAL_ENCODE_CONV_FLOAT(tv, val_di->di_tv.vval.v_float); break; - case kMPString: - case kMPBinary: { - const bool is_string = ((MessagePackType)i == kMPString); + case kMPString: { if (val_di->di_tv.v_type != VAR_LIST) { goto _convert_one_value_regular_dict; } @@ -506,11 +510,7 @@ static int TYPVAL_ENCODE_CONVERT_ONE_VALUE( &buf)) { goto _convert_one_value_regular_dict; } - if (is_string) { - TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len); - } else { - TYPVAL_ENCODE_CONV_STRING(tv, buf, len); - } + TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len); xfree(buf); break; } diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index a6e0bd4b2b..8547783213 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -11,7 +11,10 @@ #include "klib/kvec.h" #include "nvim/eval/typval_defs.h" -#include "nvim/func_attr.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/typval_encode.h.inline.generated.h" +#endif /// Type of the stack entry typedef enum { @@ -62,10 +65,6 @@ typedef struct { /// Stack used to convert Vimscript values to messagepack. typedef kvec_withinit_t(MPConvStackVal, 8) MPConvStack; -static inline size_t tv_strlen(const typval_T *tv) - REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT - REAL_FATTR_NONNULL_ALL; - /// Length of the string stored in typval_T /// /// @param[in] tv String for which to compute length for. Must be typval_T @@ -74,6 +73,8 @@ static inline size_t tv_strlen(const typval_T *tv) /// @return Length of the string stored in typval_T, including 0 for NULL /// string. static inline size_t tv_strlen(const typval_T *const tv) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL { assert(tv->v_type == VAR_STRING); return (tv->vval.v_string == NULL ? 0 : strlen(tv->vval.v_string)); diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 0ec07399b4..0050a77fe3 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -15,6 +15,7 @@ #include "nvim/charset.h" #include "nvim/cmdexpand_defs.h" #include "nvim/debugger.h" +#include "nvim/errors.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" #include "nvim/eval/funcs.h" @@ -264,7 +265,7 @@ static void set_ufunc_name(ufunc_T *fp, char *name) if ((uint8_t)name[0] == K_SPECIAL) { fp->uf_name_exp = xmalloc(strlen(name) + 3); STRCPY(fp->uf_name_exp, "<SNR>"); - STRCAT(fp->uf_name_exp, fp->uf_name + 3); + strcat(fp->uf_name_exp, fp->uf_name + 3); } } @@ -641,6 +642,44 @@ static char *fname_trans_sid(const char *const name, char *const fname_buf, char return fname; } +int get_func_arity(const char *name, int *required, int *optional, bool *varargs) +{ + int argcount = 0; + int min_argcount = 0; + + const EvalFuncDef *fdef = find_internal_func(name); + if (fdef != NULL) { + argcount = fdef->max_argc; + min_argcount = fdef->min_argc; + *varargs = false; + } else { + char fname_buf[FLEN_FIXED + 1]; + char *tofree = NULL; + int error = FCERR_NONE; + + // May need to translate <SNR>123_ to K_SNR. + char *fname = fname_trans_sid(name, fname_buf, &tofree, &error); + ufunc_T *ufunc = NULL; + if (error == FCERR_NONE) { + ufunc = find_func(fname); + } + xfree(tofree); + + if (ufunc == NULL) { + return FAIL; + } + + argcount = ufunc->uf_args.ga_len; + min_argcount = ufunc->uf_args.ga_len - ufunc->uf_def_args.ga_len; + *varargs = ufunc->uf_varargs; + } + + *required = min_argcount; + *optional = argcount - min_argcount; + + return OK; +} + /// Find a function by name, return pointer to it in ufuncs. /// /// @return NULL for unknown function. @@ -916,7 +955,7 @@ void remove_funccal(void) /// @param[out] rettv Return value. /// @param[in] firstline First line of range. /// @param[in] lastline Last line of range. -/// @param selfdict Dictionary for "self" for dictionary functions. +/// @param selfdict Dict for "self" for dictionary functions. void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv, linenr_T firstline, linenr_T lastline, dict_T *selfdict) FUNC_ATTR_NONNULL_ARG(1, 3, 4) @@ -1528,12 +1567,12 @@ varnumber_T callback_call_retnr(Callback *callback, int argcount, typval_T *argv /// Give an error message for the result of a function. /// Nothing if "error" is FCERR_NONE. -static void user_func_error(int error, const char *name, funcexe_T *funcexe) +static void user_func_error(int error, const char *name, bool found_var) FUNC_ATTR_NONNULL_ARG(2) { switch (error) { case FCERR_UNKNOWN: - if (funcexe->fe_found_var) { + if (found_var) { semsg(_(e_not_callable_type_str), name); } else { emsg_funcname(e_unknown_function_str, name); @@ -1653,12 +1692,9 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t } if (error == FCERR_NONE && funcexe->fe_evaluate) { - char *rfname = fname; - - // Ignore "g:" before a function name. - if (fp == NULL && fname[0] == 'g' && fname[1] == ':') { - rfname = fname + 2; - } + // Skip "g:" before a function name. + bool is_global = fp == NULL && fname[0] == 'g' && fname[1] == ':'; + char *rfname = is_global ? fname + 2 : fname; rettv->v_type = VAR_NUMBER; // default rettv is number zero rettv->vval.v_number = 0; @@ -1732,7 +1768,7 @@ theend: // Report an error unless the argument evaluation or function call has been // cancelled due to an aborting error, an interrupt, or an exception. if (!aborting()) { - user_func_error(error, (name != NULL) ? name : funcname, funcexe); + user_func_error(error, (name != NULL) ? name : funcname, funcexe->fe_found_var); } // clear the copies made from the partial @@ -1746,6 +1782,70 @@ theend: return ret; } +int call_simple_luafunc(const char *funcname, size_t len, typval_T *rettv) + FUNC_ATTR_NONNULL_ALL +{ + rettv->v_type = VAR_NUMBER; // default rettv is number zero + rettv->vval.v_number = 0; + + typval_T argvars[1]; + argvars[0].v_type = VAR_UNKNOWN; + nlua_typval_call(funcname, len, argvars, 0, rettv); + return OK; +} + +/// Call a function without arguments, partial or dict. +/// This is like call_func() when the call is only "FuncName()". +/// To be used by "expr" options. +/// Returns NOTDONE when the function could not be found. +/// +/// @param funcname name of the function +/// @param len length of "name" +/// @param rettv return value goes here +int call_simple_func(const char *funcname, size_t len, typval_T *rettv) + FUNC_ATTR_NONNULL_ALL +{ + int ret = FAIL; + + rettv->v_type = VAR_NUMBER; // default rettv is number zero + rettv->vval.v_number = 0; + + // Make a copy of the name, an option can be changed in the function. + char *name = xstrnsave(funcname, len); + + int error = FCERR_NONE; + char *tofree = NULL; + char fname_buf[FLEN_FIXED + 1]; + char *fname = fname_trans_sid(name, fname_buf, &tofree, &error); + + // Skip "g:" before a function name. + bool is_global = fname[0] == 'g' && fname[1] == ':'; + char *rfname = is_global ? fname + 2 : fname; + + ufunc_T *fp = find_func(rfname); + if (fp == NULL) { + ret = NOTDONE; + } else if (fp != NULL && (fp->uf_flags & FC_DELETED)) { + error = FCERR_DELETED; + } else if (fp != NULL) { + typval_T argvars[1]; + argvars[0].v_type = VAR_UNKNOWN; + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.fe_evaluate = true; + + error = call_user_func_check(fp, 0, argvars, rettv, &funcexe, NULL); + if (error == FCERR_NONE) { + ret = OK; + } + } + + user_func_error(error, name, false); + xfree(tofree); + xfree(name); + + return ret; +} + char *printable_func_name(ufunc_T *fp) { return fp->uf_name_exp != NULL ? fp->uf_name_exp : fp->uf_name; @@ -1828,7 +1928,7 @@ static int list_func_head(ufunc_T *fp, bool indent, bool force) } /// Get a function name, translating "<SID>" and "<SNR>". -/// Also handles a Funcref in a List or Dictionary. +/// Also handles a Funcref in a List or Dict. /// flags: /// TFN_INT: internal function name OK /// TFN_QUIET: be quiet @@ -2051,7 +2151,7 @@ char *get_scriptlocal_funcname(char *funcname) if (strncmp(funcname, "s:", 2) != 0 && strncmp(funcname, "<SID>", 5) != 0) { - // The function name is not a script-local function name + // The function name does not have a script-local prefix. return NULL; } @@ -2067,7 +2167,7 @@ char *get_scriptlocal_funcname(char *funcname) const int off = *funcname == 's' ? 2 : 5; char *newname = xmalloc(strlen(sid_buf) + strlen(funcname + off) + 1); STRCPY(newname, sid_buf); - STRCAT(newname, funcname + off); + strcat(newname, funcname + off); return newname; } @@ -3148,7 +3248,7 @@ static int ex_call_inner(exarg_T *eap, char *name, char **arg, char *startarg, break; } - // Handle a function returning a Funcref, Dictionary or List. + // Handle a function returning a Funcref, Dict or List. if (handle_subscript((const char **)arg, &rettv, &EVALARG_EVALUATE, true) == FAIL) { failed = true; break; @@ -3215,7 +3315,7 @@ static int ex_defer_inner(char *name, char **arg, const partial_T *const partial if (ufunc != NULL) { int error = check_user_func_argcount(ufunc, argcount); if (error != FCERR_UNKNOWN) { - user_func_error(error, name, NULL); + user_func_error(error, name, false); r = FAIL; } } diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h index b3488b15a7..0a27403a4b 100644 --- a/src/nvim/eval/userfunc.h +++ b/src/nvim/eval/userfunc.h @@ -32,9 +32,9 @@ /// Structure used by trans_function_name() typedef struct { - dict_T *fd_dict; ///< Dictionary used. - char *fd_newkey; ///< New key in "dict" in allocated memory. - dictitem_T *fd_di; ///< Dictionary item used. + dict_T *fd_dict; ///< Dict used. + char *fd_newkey; ///< New key in "dict" in allocated memory. + dictitem_T *fd_di; ///< Dict item used. } funcdict_T; typedef struct funccal_entry funccal_entry_T; @@ -69,7 +69,7 @@ typedef struct { bool *fe_doesrange; ///< [out] if not NULL: function handled range bool fe_evaluate; ///< actually evaluate expressions partial_T *fe_partial; ///< for extra arguments - dict_T *fe_selfdict; ///< Dictionary for "self" + dict_T *fe_selfdict; ///< Dict for "self" typval_T *fe_basetv; ///< base for base->method() bool fe_found_var; ///< if the function is not found then give an ///< error that a variable is not callable. diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 2eca209ea3..0e0088bfad 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -15,6 +15,7 @@ #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" +#include "nvim/errors.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" #include "nvim/eval/funcs.h" @@ -88,7 +89,7 @@ char *eval_one_expr_in_str(char *p, garray_T *gap, bool evaluate) } if (evaluate) { *block_end = NUL; - char *expr_val = eval_to_string(block_start, false); + char *expr_val = eval_to_string(block_start, false, false); *block_end = '}'; if (expr_val == NULL) { return NULL; @@ -104,7 +105,7 @@ char *eval_one_expr_in_str(char *p, garray_T *gap, bool evaluate) /// string in allocated memory. "{{" is reduced to "{" and "}}" to "}". /// Used for a heredoc assignment. /// Returns NULL for an error. -char *eval_all_expr_in_str(char *str) +static char *eval_all_expr_in_str(char *str) { garray_T ga; ga_init(&ga, 1, 80); @@ -1112,7 +1113,7 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_ // unlet a List item. tv_list_item_remove(lp->ll_list, lp->ll_li); } else { - // unlet a Dictionary item. + // unlet a Dict item. dict_T *d = lp->ll_dict; assert(d != NULL); dictitem_T *di = lp->ll_di; @@ -1287,7 +1288,7 @@ static int do_lock_var(lval_T *lp, char *name_end FUNC_ATTR_UNUSED, exarg_T *eap // (un)lock a List item. tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock, false); } else { - // (un)lock a Dictionary item. + // (un)lock a Dict item. tv_item_lock(&lp->ll_di->di_tv, deep, lock, false); } diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index 68de40f983..86495f1cb6 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -10,6 +10,7 @@ #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/cursor.h" +#include "nvim/errors.h" #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" @@ -271,7 +272,7 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar) // if count is not specified, default to 1 count = 1; } - if (endp != NULL && *endp != '\0') { + if (endp != NULL && *endp != NUL) { if (strequal(endp, "j")) { twin = win_vert_neighbor(tp, twin, false, count); } else if (strequal(endp, "k")) { |