aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/eval
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2024-11-19 22:57:13 +0000
committerJosh Rahm <joshuarahm@gmail.com>2024-11-19 22:57:13 +0000
commit9be89f131f87608f224f0ee06d199fcd09d32176 (patch)
tree11022dcfa9e08cb4ac5581b16734196128688d48 /src/nvim/eval
parentff7ed8f586589d620a806c3758fac4a47a8e7e15 (diff)
parent88085c2e80a7e3ac29aabb6b5420377eed99b8b6 (diff)
downloadrneovim-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.c363
-rw-r--r--src/nvim/eval/decode.h2
-rw-r--r--src/nvim/eval/encode.c90
-rw-r--r--src/nvim/eval/encode.h4
-rw-r--r--src/nvim/eval/executor.c325
-rw-r--r--src/nvim/eval/fs.c1492
-rw-r--r--src/nvim/eval/fs.h8
-rw-r--r--src/nvim/eval/funcs.c1811
-rw-r--r--src/nvim/eval/typval.c175
-rw-r--r--src/nvim/eval/typval.h91
-rw-r--r--src/nvim/eval/typval_defs.h6
-rw-r--r--src/nvim/eval/typval_encode.c.h20
-rw-r--r--src/nvim/eval/typval_encode.h11
-rw-r--r--src/nvim/eval/userfunc.c132
-rw-r--r--src/nvim/eval/userfunc.h8
-rw-r--r--src/nvim/eval/vars.c9
-rw-r--r--src/nvim/eval/window.c3
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")) {