From 06347a64cac5e33574713a59ace9d1d0ea4b6f82 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 25 May 2024 05:19:46 +0800 Subject: vim-patch:9.1.0443: Can't use blockwise selection with width for getregion() (#28985) Problem: Can't use a blockwise selection with a width for getregion(). Solution: Add support for blockwise selection with width like the return value of getregtype() or the "regtype" value of TextYankPost (zeertzjq). closes: vim/vim#14842 https://github.com/vim/vim/commit/afc2295c2201ae87bfbb42d5f5315ad0583ccabf --- src/nvim/eval/funcs.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index e4cb63eb8e..3cb6ef41c0 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2824,7 +2824,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 +2858,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 +2932,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); } } -- cgit From 22fe04452e12944b409c8ee92cf4069ac8026987 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 27 May 2024 05:50:49 +0800 Subject: vim-patch:9.1.0446: getregionpos() inconsistent for partly-selected multibyte char (#29032) Problem: getregionpos() behaves inconsistently for a partly-selected multibyte char. Solution: Always use column of the first byte for a partly-selected multibyte char (zeertzjq). closes: vim/vim#14851 https://github.com/vim/vim/commit/ef73374dc3e4bf8104ba31d5b22517f8028b467a --- src/nvim/eval/funcs.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 3cb6ef41c0..8b22c7a797 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3042,6 +3042,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) { @@ -3060,7 +3061,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; @@ -3072,7 +3073,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; -- cgit From bb6190bec5f18c1f9e2c1d29ef1f7cf7912ea625 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 1 Jun 2024 08:19:41 -0700 Subject: refactor: move shared messages to errors.h #26214 --- src/nvim/eval/executor.c | 1 + src/nvim/eval/funcs.c | 1 + src/nvim/eval/typval.c | 1 + src/nvim/eval/userfunc.c | 1 + src/nvim/eval/vars.c | 1 + src/nvim/eval/window.c | 1 + 6 files changed, 6 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index 1b8c057d7c..3255e78d09 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -1,6 +1,7 @@ #include #include +#include "nvim/errors.h" #include "nvim/eval.h" #include "nvim/eval/executor.h" #include "nvim/eval/typval.h" diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 8b22c7a797..0165e4af60 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -38,6 +38,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" diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index eb8c89c36e..1ea1fe46d2 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -10,6 +10,7 @@ #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" diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 39bd63462c..00fb896797 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" diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 1c15274acc..7b93a291c4 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" diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index 68de40f983..d7485c12a3 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" -- cgit From 0e49c3ad1a599376d0a5c229f304a06d48c56163 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 2 Jun 2024 05:47:47 +0800 Subject: vim-patch:9.1.0456: Left shift is incorrect with vartabstop and shiftwidth=0 Problem: Left shift is incorrect with vartabstop and shiftwidth=0 Solution: make tabstop_at() function aware of shift direction (Gary Johnson) The problem was that with 'vartabstop' set and 'shiftwidth' equal 0, left shifts using << were shifting the line to the wrong column. The tabstop to the right of the first character in the line was being used as the shift amount instead of the tabstop to the left of that first character. The reason was that the tabstop_at() function always returned the value of the tabstop to the right of the given column and was not accounting for the direction of the shift. The solution was to make tabstop_at() aware of the direction of the shift and to choose the tabtop accordingly. A test was added to check this behavior and make sure it doesn't regress. While at it, also fix a few indentation/alignment issues. fixes: vim/vim#14864 closes: vim/vim#14887 https://github.com/vim/vim/commit/88d4f255b7b7a19bb4f6489e0ad0956e47d51fed Co-authored-by: Gary Johnson --- src/nvim/eval/funcs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 0165e4af60..e704135366 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -8302,7 +8302,7 @@ 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); -- cgit From 200e7ad1578619e78c664bd0c6be024168433412 Mon Sep 17 00:00:00 2001 From: James Tirta Halim Date: Mon, 3 Jun 2024 11:10:30 +0700 Subject: fixup: apply the change on more files --- src/nvim/eval/encode.c | 6 +++--- src/nvim/eval/funcs.c | 6 +++--- src/nvim/eval/typval.c | 2 +- src/nvim/eval/window.c | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index d35ac4eb7b..e216dbdaa6 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -855,7 +855,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 +883,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,7 +908,7 @@ 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; } diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index e704135366..666d46cdad 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -4318,7 +4318,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); @@ -9174,13 +9174,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" diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 1ea1fe46d2..13b31ab30f 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1824,7 +1824,7 @@ char *callback_to_string(Callback *cb, Arena *arena) snprintf(msg, msglen, "", cb->data.partial->pt_name); break; default: - *msg = '\0'; + *msg = NUL; break; } return msg; diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index d7485c12a3..86495f1cb6 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -272,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")) { -- cgit From bbd2f340a2895ed59785f952b2585e6590602cad Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 11 Jun 2024 10:25:32 +0200 Subject: refactor(memory): use builtin strcat() instead of STRCAT() The latter was mostly relevant with the past char_u madness. NOTE: STRCAT also functioned as a counterfeit "NOLINT" for clint apparently. But NOLINT-ing every usecase is just the same as disabling the check entirely. --- src/nvim/eval/userfunc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 00fb896797..122a1ac8ab 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -265,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, ""); - STRCAT(fp->uf_name_exp, fp->uf_name + 3); + strcat(fp->uf_name_exp, fp->uf_name + 3); } } @@ -2062,7 +2062,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; } -- cgit From c37695a5d5f2e8914fff86f3581bed70b4c85d3c Mon Sep 17 00:00:00 2001 From: James <89495599+IAKOBVS@users.noreply.github.com> Date: Tue, 11 Jun 2024 22:40:24 +0700 Subject: refactor: use S_LEN(s) instead of s, n (#29219) --- src/nvim/eval/funcs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 666d46cdad..9a1ed7dea9 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -5095,7 +5095,7 @@ static void get_matches_in_str(const char *str, regmatch_T *rmp, list_T *mlist, // return a list with the submatches for (int i = 1; i < NSUBEXP; i++) { if (rmp->endp[i] == NULL) { - tv_list_append_string(sml, "", 0); + tv_list_append_string(sml, S_LEN("")); } else { tv_list_append_string(sml, rmp->startp[i], rmp->endp[i] - rmp->startp[i]); } -- cgit From 43d8435cf84468e776e0a6a98d05ae7bd62583b7 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 13 Jun 2024 22:20:06 +0100 Subject: revert: "refactor: use S_LEN macro" (#29319) revert: "refactor: use S_LEN(s) instead of s, n (#29219)" This reverts commit c37695a5d5f2e8914fff86f3581bed70b4c85d3c. --- src/nvim/eval/funcs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 9a1ed7dea9..666d46cdad 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -5095,7 +5095,7 @@ static void get_matches_in_str(const char *str, regmatch_T *rmp, list_T *mlist, // return a list with the submatches for (int i = 1; i < NSUBEXP; i++) { if (rmp->endp[i] == NULL) { - tv_list_append_string(sml, S_LEN("")); + tv_list_append_string(sml, "", 0); } else { tv_list_append_string(sml, rmp->startp[i], rmp->endp[i] - rmp->startp[i]); } -- cgit From 2bb1a18631c4035e4a582b7d995968acbac874bf Mon Sep 17 00:00:00 2001 From: bfredl Date: Sat, 15 Jun 2024 19:50:44 +0200 Subject: refactor(typval): don't use msgpack_packer for msgpackdump() Step towords completely eliminating msgpack_packer. --- src/nvim/eval/encode.c | 76 ++++++++++++++--------------------------- src/nvim/eval/encode.h | 3 +- src/nvim/eval/funcs.c | 24 +++++++------ src/nvim/eval/typval.c | 8 +++-- src/nvim/eval/typval_encode.c.h | 7 ++++ 5 files changed, 52 insertions(+), 66 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index e216dbdaa6..eb70b0534d 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -13,7 +13,7 @@ #include #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 _() @@ -412,6 +413,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 +539,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") @@ -821,6 +826,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 @@ -913,52 +919,22 @@ char *encode_tv2json(typval_T *tv, size_t *len) } #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(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((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 +946,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 +994,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 +1016,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..efccfcb5a6 100644 --- a/src/nvim/eval/encode.h +++ b/src/nvim/eval/encode.h @@ -5,6 +5,7 @@ #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 +14,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/funcs.c b/src/nvim/eval/funcs.c index 666d46cdad..9cb504c074 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -94,6 +94,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" @@ -5501,15 +5502,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]; @@ -5517,11 +5510,20 @@ 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, diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 13b31ab30f..f9eb5c33c0 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -3064,8 +3064,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; @@ -3252,11 +3251,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. @@ -3284,6 +3284,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 { \ @@ -3500,6 +3501,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 diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index c0cd0ce557..afee6dab18 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 /// @@ -323,6 +328,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 +426,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; } -- cgit From bda63d5b97dfb333de6f4bd757dbb978906062a2 Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 25 Jun 2024 15:33:47 +0200 Subject: refactor(typval)!: remove distinction of binary and nonbinary strings This is a breaking change which will make refactor of typval and shada code a lot easier. In particular, code that would use or check for v:msgpack_types.binary in the wild would be broken. This appears to be rarely used in existing plugins. Also some cases where v:msgpack_type.string would be used to represent a binary string of "string" type, we use a BLOB instead, which is vimscripts native type for binary blobs, and already was used for BIN formats when necessary. msgpackdump(msgpackparse(data)) no longer preserves the distinction of BIN and STR strings. This is very common behavior for language-specific msgpack bindings. Nvim uses msgpack as a tool to serialize its data. Nvim is not a tool to bit-perfectly manipulate arbitrary msgpack data out in the wild. The changed tests should indicate how behavior changes in various edge cases. --- src/nvim/eval/decode.c | 64 +++++++++++------------------------------ src/nvim/eval/encode.c | 3 +- src/nvim/eval/typval_encode.c.h | 10 ++----- 3 files changed, 19 insertions(+), 58 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index d7df7bb150..13cd3274dd 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -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: @@ -982,18 +959,8 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) }; 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; - } - 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; - } + *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, false, false); break; case MSGPACK_OBJECT_ARRAY: { list_T *const list = tv_list_alloc((ptrdiff_t)mobj.via.array.size); @@ -1016,7 +983,8 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) } 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 + if ((mobj.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR + && mobj.via.map.ptr[i].key.type != MSGPACK_OBJECT_BIN) || 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) { diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index eb70b0534d..383cf9f1bd 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -776,8 +776,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; diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index afee6dab18..3a78fc5afd 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -501,9 +501,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; } @@ -513,11 +511,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; } -- cgit From 782b3024ef0e3d6615e9dc90509a29e96ab58152 Mon Sep 17 00:00:00 2001 From: bfredl Date: Mon, 24 Jun 2024 11:27:56 +0200 Subject: refactor(shada): don't use msgpack_packer for shada Now msgpack-c is never used for packing. The real fun part will be replacing it for unpacking. --- src/nvim/eval/encode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 383cf9f1bd..92c5aaeffd 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -918,7 +918,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) } #define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \ - mpack_bin(buf, (len), packer); \ + mpack_bin(cbuf_as_string(buf, (len)), packer); \ #define TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len) \ mpack_str(cbuf_as_string(buf, (len)), packer); \ @@ -927,7 +927,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) mpack_ext(buf, (len), (int8_t)(type), packer); \ #define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \ - mpack_bin((blob) ? (blob)->bv_ga.ga_data : NULL, (size_t)(len), packer); \ + mpack_bin(cbuf_as_string((blob) ? (blob)->bv_ga.ga_data : NULL, (size_t)(len)), packer); #define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ mpack_integer(&packer->ptr, (Integer)(num)) -- cgit From 2a883d9c597e70d25ffc53373731d05d18a89b91 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 5 Jul 2024 15:20:02 +0800 Subject: vim-patch:9.1.0524: the recursive parameter in the *_equal functions can be removed (#29572) Problem: the recursive parameter in the *_equal functions can be removed Solution: Remove the recursive parameter in dict_equal(), list_equal() object_equal and tv_equal(). Use a comparison of the static var recursive_cnt == 0 to determine whether or not tv_equal() has been called recursively (Yinzuo Jiang). closes: vim/vim#15070 https://github.com/vim/vim/commit/7ccd1a2e858dbb2ac7fb09971dfcbfad62baa677 Co-authored-by: Yinzuo Jiang --- src/nvim/eval/funcs.c | 8 ++++---- src/nvim/eval/typval.c | 24 ++++++++++-------------- 2 files changed, 14 insertions(+), 18 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 9cb504c074..e28e46d057 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -953,7 +953,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++; } } @@ -973,7 +973,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++; } }); @@ -3770,7 +3770,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; } @@ -3807,7 +3807,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; } diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index f9eb5c33c0..0d6de3c3e5 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1460,10 +1460,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) { @@ -1485,8 +1484,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; } } @@ -2679,8 +2677,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) { @@ -2702,7 +2701,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; } }); @@ -3838,10 +3837,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 @@ -3857,7 +3855,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) { @@ -3868,15 +3866,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; } -- cgit From 3c53e8f78511d6db9a6c804e5a479ba38c33102d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 5 Jul 2024 18:39:06 +0800 Subject: refactor(eval): use uv_random() for init_srand() (#29575) N/A patches for version.c: vim-patch:9.1.0518: initialize the random buffer can be improved vim-patch:9.1.0531: resource leak in mch_get_random() --- src/nvim/eval/funcs.c | 48 +++++++++++++----------------------------------- 1 file changed, 13 insertions(+), 35 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index e28e46d057..c319bd8214 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -5883,42 +5883,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) -- cgit From 545aafbeb80eb52c182ce139800489b392a12d0d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 10 Jul 2024 08:07:16 +0800 Subject: vim-patch:9.1.0547: No way to get the arity of a Vim function (#29638) Problem: No way to get the arity of a Vim function (Austin Ziegler) Solution: Enhance get() Vim script function to return the function argument info using get(func, "arity") (LemonBoy) fixes: vim/vim#15097 closes: vim/vim#15109 https://github.com/vim/vim/commit/48b7d05a4f88c4326bd5d7a73a523f2d953b3e51 Co-authored-by: LemonBoy --- src/nvim/eval/funcs.c | 27 +++++++++++++++++++++++++++ src/nvim/eval/userfunc.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index c319bd8214..22e7f383a5 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2372,6 +2372,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); } diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 122a1ac8ab..42391ecec7 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -642,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 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. -- cgit From 7dffc36e61c46e6adc92cff5944e876446f3c40e Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 13 Jun 2024 12:00:58 +0200 Subject: refactor(declarations): also generate prototypes for functions in headers Before this change, "static inline" functions in headers needed to have their function attributes specified in a completely different way. The prototype had to be duplicated, and REAL_FATTR_ had to be used instead of the public FUNC_ATTR_ names. TODO: need a check that a "header.h.inline.generated.h" file is not forgotten when the first "static inline" function with attributes is added to a header (they would just be silently missing). --- src/nvim/eval/typval.h | 91 +++++++++++++------------------------------ src/nvim/eval/typval_encode.h | 11 +++--- 2 files changed, 32 insertions(+), 70 deletions(-) (limited to 'src/nvim/eval') 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 #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_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)); -- cgit From 185b22720de9156393ddc22c2c59dc3eb46b8d97 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 18 Jul 2024 07:47:20 +0800 Subject: vim-patch:9.0.0003: functions are global while they could be local (#29777) Problem: Functions are global while they could be local. Solution: Add "static". Add a few tests. (Yegappan Lakshmanan, closes vim/vim#10612) https://github.com/vim/vim/commit/ee47eaceaa148e07b566ff420f9a3c2edde2fa34 Omit script_name_after_autoload(), untrans_function_name(): Vim9 script only. Co-authored-by: Yegappan Lakshmanan --- src/nvim/eval/vars.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 7b93a291c4..2dca942bb5 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -105,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); -- cgit From 96b358e9f138e60a24c3f3c9b45e9b82ffb92c1c Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 30 Jul 2024 11:08:16 +0800 Subject: vim-patch:partial:9.0.0327: items() does not work on a list Problem: items() does not work on a list. (Sergey Vlasov) Solution: Make items() work on a list. (closes vim/vim#11013) https://github.com/vim/vim/commit/976f859763b215050a03248dbc2bb62fa5d0d059 Skip CHECK_LIST_MATERIALIZE. Co-authored-by: Bram Moolenaar --- src/nvim/eval/typval.c | 75 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 13 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 0d6de3c3e5..8fc8dcc479 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -16,6 +16,7 @@ #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" @@ -59,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 @@ -87,6 +95,8 @@ static const char e_string_or_list_required_for_argument_nr[] = N_("E1222: String or List 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_list_or_dict_required_for_argument_nr[] + = N_("E1227: List or Dictionary required for argument %d"); static const char e_blob_required_for_argument_nr[] = N_("E1238: Blob required for argument %d"); static const char e_invalid_value_for_blob_nr[] @@ -782,6 +792,27 @@ 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++; + }); +} + /// Extend first list with the second /// /// @param[out] l1 List to extend. @@ -3134,35 +3165,38 @@ 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_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; @@ -3188,19 +3222,23 @@ 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_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 @@ -4398,6 +4436,17 @@ 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 list or dict +int tv_check_for_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_LIST && args[idx].v_type != VAR_DICT) { + semsg(_(e_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) -- cgit From 8ca3c1515c3fd5f70e870e535649a4c2afc5bbaf Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 30 Jul 2024 11:34:38 +0800 Subject: vim-patch:9.0.0331: cannot use items() on a string Problem: Cannot use items() on a string. Solution: Make items() work on a string. (closes vim/vim#11016) https://github.com/vim/vim/commit/3e518a8ec74065aedd67d352c93d6ae6be550316 Co-authored-by: Bram Moolenaar --- src/nvim/eval/typval.c | 53 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 16 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 8fc8dcc479..4458dba27d 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -93,10 +93,10 @@ 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_list_or_dict_required_for_argument_nr[] - = N_("E1227: List or Dictionary required for argument %d"); static const char e_blob_required_for_argument_nr[] = N_("E1238: Blob required for argument %d"); static const char e_invalid_value_for_blob_nr[] @@ -813,6 +813,30 @@ static void tv_list2items(typval_T *argvars, typval_T *rettv) }); } +/// "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. @@ -3172,7 +3196,7 @@ void tv_dict_alloc_ret(typval_T *const ret_tv) static void tv_dict2list(typval_T *const argvars, typval_T *const rettv, const DictListType what) { if ((what == kDict2ListItems - ? tv_check_for_list_or_dict_arg(argvars, 0) + ? 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; @@ -3202,15 +3226,8 @@ static void tv_dict2list(typval_T *const argvars, typval_T *const rettv, const D 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; } } @@ -3222,7 +3239,9 @@ static void tv_dict2list(typval_T *const argvars, typval_T *const rettv, const D /// "items(dict)" function void f_items(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (argvars[0].v_type == VAR_LIST) { + 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); @@ -4436,12 +4455,14 @@ 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 list or dict -int tv_check_for_list_or_dict_arg(const typval_T *const args, const int idx) +/// 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_LIST && args[idx].v_type != VAR_DICT) { - semsg(_(e_list_or_dict_required_for_argument_nr), idx + 1); + 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; -- cgit From 43c137c3d8473ab87b5dad5c4c2414dd1016f663 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 31 Jul 2024 09:53:13 +0800 Subject: vim-patch:9.2.1731: Vim9: cannot use += to append to empty NULL list Problem: Vim9: cannot use += to append to empty NULL list. Solution: Copy the list instead of extending it. (closes vim/vim#6998) https://github.com/vim/vim/commit/81ed4960482f8baabdd7f95b4d5e39744be88ae7 Co-authored-by: Bram Moolenaar --- src/nvim/eval/executor.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index 3255e78d09..4f83eaa173 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -59,8 +59,13 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, const char *cons 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); + if (tv2->vval.v_list != NULL) { + 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; case VAR_NUMBER: -- cgit From 98d05a2dee7b90d7c23a47bc4d96e257c3a3d31e Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 30 Jul 2024 13:31:18 +0800 Subject: vim-patch:8.2.3264: Vim9: assign test fails Problem: Vim9: assign test fails. Solution: Add missing change. https://github.com/vim/vim/commit/f24f51d03035379cf3e5b2dccf489a40bc4ca92a Co-authored-by: Bram Moolenaar --- src/nvim/eval/executor.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index 4f83eaa173..bdca0338ed 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -31,9 +31,10 @@ char *e_list_index_out_of_range_nr 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 { - // Can't do anything with a Funcref, a Dict or special value on the right. + // 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) { + && ((tv2->v_type != VAR_BOOL && tv2->v_type != VAR_SPECIAL) || *op == '.')) { switch (tv1->v_type) { case VAR_DICT: case VAR_FUNC: -- cgit From a1561cbbea55cc74d4dc880585f942f4a114c4b8 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 30 Jul 2024 12:48:15 +0800 Subject: vim-patch:partial:9.1.0411: too long functions in eval.c Problem: too long functions in eval.c Solution: refactor functions (Yegappan Lakshmanan) closes: vim/vim#14755 https://github.com/vim/vim/commit/4ceb4dc825854032eed423ec1fc372317d3420bf Skip the eval_expr_typval() changes. Co-authored-by: Yegappan Lakshmanan --- src/nvim/eval/executor.c | 320 +++++++++++++++++++++++++++++------------------ 1 file changed, 198 insertions(+), 122 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index bdca0338ed..26b7a627a6 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -21,6 +21,168 @@ 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 (tv1->vval.v_blob == NULL || tv2->vval.v_blob == NULL) { + 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. @@ -29,131 +191,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 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 == '.')) { - 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 (tv2->vval.v_list != NULL) { - 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; - 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(); - } + 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; } -- cgit From 619cb143f93fbf75adde9710415a74d36c8eb63d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 30 Jul 2024 13:38:13 +0800 Subject: vim-patch:9.1.0415: Some functions are not tested Problem: Some functions are not tested Solution: Add a few more tests, fix a few minor problems (Yegappan Lakshmanan) closes: vim/vim#14789 https://github.com/vim/vim/commit/fe424d13ef6e5486923f23f15bb6951e3079412e Co-authored-by: Yegappan Lakshmanan --- src/nvim/eval/executor.c | 8 +++++++- src/nvim/eval/funcs.c | 13 ++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index 26b7a627a6..5b92f217d1 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -31,7 +31,13 @@ static int tv_op_blob(typval_T *tv1, const typval_T *tv2, const char *op) } // Blob += Blob - if (tv1->vval.v_blob == NULL || tv2->vval.v_blob == NULL) { + 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; } diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 22e7f383a5..bd977523c6 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3880,6 +3880,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)); @@ -3887,6 +3888,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; @@ -3916,6 +3921,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)); @@ -3926,6 +3932,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; @@ -3942,7 +3952,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; } -- cgit From 48e4589eaded3213956aa9ddbcc0aa6971a974e5 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 2 Aug 2024 10:58:10 +0800 Subject: vim-patch:8.2.4416: Vim9: using a script-local function requires using "s:" (#29950) Problem: Vim9: using a script-local function requires using "s:" when setting 'completefunc'. Solution: Do not require "s:" in Vim9 script. (closes vim/vim#9796) https://github.com/vim/vim/commit/1fca5f3e86f08e696058fc7e86dfe41b415a78e6 vim-patch:8.2.4417: using NULL pointer Problem: Using NULL pointer. Solution: Set offset after checking for NULL pointer. https://github.com/vim/vim/commit/e89bfd212b21c227f026e467f882c62cdd6e642d Co-authored-by: Bram Moolenaar --- src/nvim/eval/userfunc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 42391ecec7..ab8e67016f 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2084,7 +2084,7 @@ char *get_scriptlocal_funcname(char *funcname) if (strncmp(funcname, "s:", 2) != 0 && strncmp(funcname, "", 5) != 0) { - // The function name is not a script-local function name + // The function name does not have a script-local prefix. return NULL; } -- cgit From f7fde0173af95925e7324b7d3c09776173dab8a7 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 1 Aug 2024 10:13:45 +0800 Subject: vim-patch:9.0.0632: calling a function from an "expr" option has overhead Problem: Calling a function from an "expr" option has too much overhead. Solution: Add call_simple_func() and use it for 'foldexpr' https://github.com/vim/vim/commit/87b4e5c5db9d1cfd6f2e79656e1a6cff3c69d15f Cherry-pick a call_func() change from patch 8.2.1343. Add expr-option-function docs to options.txt. Co-authored-by: Bram Moolenaar --- src/nvim/eval/userfunc.c | 69 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 10 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index ab8e67016f..f7d1e7e0f8 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -1561,12 +1561,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); @@ -1686,12 +1686,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; @@ -1765,7 +1762,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 @@ -1779,6 +1776,58 @@ theend: return ret; } +/// 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" or -1 to use strlen() +/// @param rettv return value goes here +int call_simple_func(const char *funcname, int 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, (size_t)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; @@ -3248,7 +3297,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; } } -- cgit From 582bf4f1e15988565da53a91395e2d0131628fbb Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 1 Aug 2024 10:41:08 +0800 Subject: vim-patch:9.0.0634: evaluating "expr" options has more overhead than needed Problem: Evaluating "expr" options has more overhead than needed. Solution: Use call_simple_func() for 'foldtext', 'includeexpr', 'printexpr', "expr" of 'spellsuggest', 'diffexpr', 'patchexpr', 'balloonexpr', 'formatexpr', 'indentexpr' and 'charconvert'. https://github.com/vim/vim/commit/a4e0b9785e409e9e660171cea76dfcc5fdafad9b vim-patch:9.0.0635: build error and compiler warnings Problem: Build error and compiler warnings. Solution: Add missing change. Add type casts. https://github.com/vim/vim/commit/3292a229402c9892f5ab90645fbfe2b1db342f5b Co-authored-by: Bram Moolenaar --- src/nvim/eval/vars.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 2dca942bb5..3a5d056a9a 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -89,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; -- cgit From 6d722f33098da447ac29496b71dd58f2ae337996 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 2 Aug 2024 11:49:00 +0800 Subject: vim-patch:9.1.0649: Wrong comment for "len" argument of call_simple_func() Problem: Wrong comment for "len" argument of call_simple_func(). Solution: Remove the "or -1 to use strlen()". Also change its type to size_t to remove one cast. (zeertzjq) closes: vim/vim#15410 https://github.com/vim/vim/commit/c1ed788c1b41db9b5f1ef548dc877f771f535bbe --- src/nvim/eval/userfunc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index f7d1e7e0f8..3690ab5d7b 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -1782,9 +1782,9 @@ theend: /// Returns NOTDONE when the function could not be found. /// /// @param funcname name of the function -/// @param len length of "name" or -1 to use strlen() +/// @param len length of "name" /// @param rettv return value goes here -int call_simple_func(const char *funcname, int len, typval_T *rettv) +int call_simple_func(const char *funcname, size_t len, typval_T *rettv) FUNC_ATTR_NONNULL_ALL { int ret = FAIL; @@ -1793,7 +1793,7 @@ int call_simple_func(const char *funcname, int len, typval_T *rettv) rettv->vval.v_number = 0; // Make a copy of the name, an option can be changed in the function. - char *name = xstrnsave(funcname, (size_t)len); + char *name = xstrnsave(funcname, len); int error = FCERR_NONE; char *tofree = NULL; -- cgit From 99bb0a10d3400ee8b9b2773d51a957dacbf52b33 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 2 Aug 2024 12:16:04 +0800 Subject: refactor(eval): treat v:lua call as simple function --- src/nvim/eval/userfunc.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 3690ab5d7b..f7ce5334f0 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -1776,6 +1776,15 @@ theend: return ret; } +int call_simple_luafunc(const char *funcname, size_t len, typval_T *rettv) + FUNC_ATTR_NONNULL_ALL +{ + 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. -- cgit From 2a3561819e0e80150986779cee87659b7c92d0c1 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 2 Aug 2024 16:00:27 +0800 Subject: fix(eval): handle wrong v:lua in expr option properly (#29953) --- src/nvim/eval/userfunc.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index f7ce5334f0..d2bae72531 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -1779,6 +1779,9 @@ theend: 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); -- cgit From 383f6934720a203d093c762cbd5362092110f35f Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 3 Aug 2024 07:42:52 +0800 Subject: refactor: move some functions out of eval.c (#29964) - common_function() has always been in evalfunc.c in Vim - return_register() has always been in evalfunc.c in Vim - get_user_input() was moved to ex_getln.c in Vim 8.1.1957 - tv_get_lnum_buf() was moved to typval.c in Vim 8.2.0847 --- src/nvim/eval/funcs.c | 209 +++++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/eval/typval.c | 23 ++++++ 2 files changed, 232 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index bd977523c6..d2f6af4d9e 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -134,6 +134,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" @@ -2272,6 +2273,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, "", 5) == 0) { + // Expand s: and into 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); @@ -6414,6 +6573,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) { @@ -7439,6 +7606,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) { @@ -8625,6 +8807,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(); + 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) { diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 4458dba27d..c00abe452c 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -4208,6 +4208,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. -- cgit From f926cc32c9262b6254e2843276b951cef9da1afe Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 2 Jul 2024 13:45:50 +0200 Subject: refactor(shada): rework msgpack decoding without msgpack-c This also makes shada reading slightly faster due to avoiding some copying and allocation. Use keysets to drive decoding of msgpack maps for shada entries. --- src/nvim/eval/decode.c | 307 +++++++++++++++++++++++++++++++------------------ src/nvim/eval/decode.h | 2 +- src/nvim/eval/encode.c | 5 +- src/nvim/eval/encode.h | 1 - src/nvim/eval/funcs.c | 128 ++++++++++----------- src/nvim/eval/typval.c | 5 +- 6 files changed, 260 insertions(+), 188 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 13cd3274dd..1ff8716763 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -7,6 +6,7 @@ #include #include "klib/kvec.h" +#include "mpack/object.h" #include "nvim/ascii_defs.h" #include "nvim/charset.h" #include "nvim/eval.h" @@ -885,173 +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: - case MSGPACK_OBJECT_BIN: - *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, false, false); + + 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_ARRAY: { - list_T *const list = tv_list_alloc((ptrdiff_t)mobj.via.array.size); + case MPACK_TOKEN_CHUNK: { + char *data = parent->data[1].p; + memcpy(data + parent->pos, + node->tok.data.chunk_ptr, node->tok.length); + break; + } + + 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.type != MSGPACK_OBJECT_BIN) - || 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 // IWYU pragma: keep #include // 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 92c5aaeffd..79f334601d 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -55,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; @@ -97,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. diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h index efccfcb5a6..2bacc82b0d 100644 --- a/src/nvim/eval/encode.h +++ b/src/nvim/eval/encode.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include "nvim/eval/typval_defs.h" diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index d2f6af4d9e..2f51532ec4 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -4,9 +4,6 @@ #include #include #include -#include -#include -#include #include #include #include @@ -18,6 +15,7 @@ #include #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" @@ -5723,36 +5721,24 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -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) @@ -5766,48 +5752,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) @@ -5817,18 +5812,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 diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index c00abe452c..e7b6a0feee 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -485,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 -- cgit From 1247684ae14e83c5b742be390de8dee00fd4e241 Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 2 Jul 2024 13:47:04 +0200 Subject: build(deps): remove msgpack-c dependency --- src/nvim/eval/decode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 1ff8716763..afc2efddf6 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -1010,7 +1010,7 @@ static void typval_parse_enter(mpack_parser_t *parser, mpack_node_t *node) } } -/// free node which was entered but never exited, due to a nested error +/// 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) @@ -1109,7 +1109,7 @@ msgpack_to_vim_generic_map: {} } default: - // other kinds are handled completely in typval_parse_enter, + // other kinds are handled completely in typval_parse_enter break; } } -- cgit From b5f92c4e5c7428fe1c1f91620f4b545b30b49855 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 6 Aug 2024 20:13:07 +0800 Subject: refactor: extract eval/fs.c from eval/funcs.c (#29985) In Vim a lot of filesystem functions have been moved to filepath.c. However, some of these functions actually deal with file contents, and Nvim's filesystem-related functions are spread out in a different way. Therefore, it's better to use a different file for these functions. --- src/nvim/eval/fs.c | 1465 +++++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/eval/fs.h | 8 + src/nvim/eval/funcs.c | 1300 +------------------------------------------ 3 files changed, 1483 insertions(+), 1290 deletions(-) create mode 100644 src/nvim/eval/fs.c create mode 100644 src/nvim/eval/fs.h (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/fs.c b/src/nvim/eval/fs.c new file mode 100644 index 0000000000..9719caa52e --- /dev/null +++ b/src/nvim/eval/fs.c @@ -0,0 +1,1465 @@ +// eval/fs.c: Filesystem related builtin functions + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} + +/// "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(); + } +} + +/// "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 ? _("") : 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 2f51532ec4..de1d784577 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -10,7 +9,6 @@ #include #include #include -#include #include #include @@ -58,8 +56,6 @@ #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" @@ -102,10 +98,7 @@ #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" @@ -773,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) { @@ -1251,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) { @@ -1569,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; @@ -1683,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) { @@ -2105,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) { @@ -2702,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) { @@ -2830,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) { @@ -3464,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) { @@ -3777,117 +3253,17 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } static bool has_wsl(void) -{ - static TriState has_wsl = kNone; - if (has_wsl == kNone) { - Error err = ERROR_INIT; - Object o = NLUA_EXEC_STATIC("return vim.uv.os_uname()['release']:lower()" - ":match('microsoft')", - (Array)ARRAY_DICT_INIT, kRetNilBool, NULL, &err); - assert(!ERROR_SET(&err)); - has_wsl = LUARET_TRUTHY(o) ? kTrue : kFalse; - } - 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(); +{ + static TriState has_wsl = kNone; + if (has_wsl == kNone) { + Error err = ERROR_INIT; + Object o = NLUA_EXEC_STATIC("return vim.uv.os_uname()['release']:lower()" + ":match('microsoft')", + (Array)ARRAY_DICT_INIT, kRetNilBool, NULL, &err); + assert(!ERROR_SET(&err)); + has_wsl = LUARET_TRUTHY(o) ? kTrue : kFalse; } + return has_wsl == kTrue; } /// "highlightID(name)" function @@ -4306,12 +3682,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) { @@ -5555,78 +4925,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) { @@ -5898,28 +5196,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) { @@ -6258,267 +5534,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 ? _("") : 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) { @@ -6701,18 +5716,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) { @@ -6781,175 +5784,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) { @@ -8504,15 +7338,6 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 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) { @@ -9298,13 +8123,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) { @@ -9650,104 +8468,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) { -- cgit From 28fbba2092adb9659253434605cb94252241f5e0 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 6 Aug 2024 20:49:59 +0800 Subject: vim-patch:9.1.0465: missing filecopy() function (#29989) Problem: missing filecopy() function Solution: implement filecopy() Vim script function (Shougo Matsushita) closes: vim/vim#12346 https://github.com/vim/vim/commit/60c8743ab6c90e402e6ed49d27455ef7e5698abe Co-authored-by: Shougo Matsushita --- src/nvim/eval/fs.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/fs.c b/src/nvim/eval/fs.c index 9719caa52e..381fee1a3f 100644 --- a/src/nvim/eval/fs.c +++ b/src/nvim/eval/fs.c @@ -154,6 +154,27 @@ void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 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) { -- cgit From 37952bf7b442cac794c4663f2e0123e7d72bc443 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 6 Aug 2024 21:19:12 +0800 Subject: vim-patch:8.2.4838: checking for absolute path is not trivial (#29990) Problem: Checking for absolute path is not trivial. Solution: Add isabsolutepath(). (closes vim/vim#10303) https://github.com/vim/vim/commit/dca1d40cd0f2af0755519e7028378bd3c8fefd31 vim-patch:8a3b805c6c9c Co-authored-by: LemonBoy --- src/nvim/eval/fs.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/fs.c b/src/nvim/eval/fs.c index 381fee1a3f..f5b33c804e 100644 --- a/src/nvim/eval/fs.c +++ b/src/nvim/eval/fs.c @@ -688,6 +688,12 @@ void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } +/// "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) { -- cgit From 8a2aec99748229ad9d1e12c1cbc0768d063e8eed Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 8 Sep 2024 12:48:32 -0700 Subject: fix(startup): server fails if $NVIM_APPNAME is relative dir #30310 Problem: If $NVIM_APPNAME is a relative dir path, Nvim fails to start its primary/default server, and `v:servername` is empty. Root cause is d34c64e342dfba9248d1055e702d02620a1b31a8, but this wasn't noticed until 96128a5076b7 started reporting the error more loudly. Solution: - `server_address_new`: replace slashes "/" in the appname before using it as a servername. - `vim_mktempdir`: always prefer the system-wide top-level "nvim.user/" directory. That isn't intended to be specific to NVIM_APPNAME; rather, each *subdirectory* ("nvim.user/xxx") is owned by each Nvim instance. Nvim "apps" can be identified by the server socket(s) stored in those per-Nvim subdirs. fix #30256 --- src/nvim/eval/funcs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index de1d784577..aed6fdae14 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -7641,7 +7641,7 @@ static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) return; } const void *iter = NULL; - const char *appname = get_appname(); + const char *appname = get_appname(false); do { size_t dir_len; const char *dir; -- cgit From 057d27a9d6ef0bb2ee5130704c45b9e9197e7c36 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 15 Sep 2024 12:20:58 -0700 Subject: refactor: rename "process" => "proc" #30387 Problem: - "process" is often used as a verb (`multiqueue_process_events`), which is ambiguous for cases where it's used as a topic. - The documented naming convention for processes is "proc". - `:help dev-name-common` - Shorter is better, when it doesn't harm readability or discoverability. Solution: Rename "process" => "proc" in all C symbols and module names. --- src/nvim/eval/funcs.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index aed6fdae14..ef638802ad 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -49,7 +49,7 @@ #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" @@ -101,7 +101,7 @@ #include "nvim/os/fs.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" @@ -3770,7 +3770,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; } @@ -3796,13 +3796,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; } @@ -4077,7 +4077,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); @@ -4113,10 +4113,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; @@ -4144,8 +4144,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. } @@ -8207,7 +8207,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); -- cgit From 737f58e23230ea14f1648ac1fc7f442ea0f8563c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 20 Sep 2024 07:34:50 +0200 Subject: refactor(api)!: rename Dictionary => Dict In the api_info() output: :new|put =map(filter(api_info().functions, '!has_key(v:val,''deprecated_since'')'), 'v:val') ... {'return_type': 'ArrayOf(Integer, 2)', 'name': 'nvim_win_get_position', 'method': v:true, 'parameters': [['Window', 'window']], 'since': 1} The `ArrayOf(Integer, 2)` return type didn't break clients when we added it, which is evidence that clients don't use the `return_type` field, thus renaming Dictionary => Dict in api_info() is not (in practice) a breaking change. --- src/nvim/eval/funcs.c | 6 +++--- src/nvim/eval/typval_defs.h | 6 +++--- src/nvim/eval/typval_encode.c.h | 3 +-- src/nvim/eval/userfunc.c | 6 +++--- src/nvim/eval/userfunc.h | 8 ++++---- src/nvim/eval/vars.c | 4 ++-- 6 files changed, 16 insertions(+), 17 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index ef638802ad..e8b9288717 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -995,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); } @@ -1067,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); 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 3a78fc5afd..c0e994562c 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -173,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 diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index d2bae72531..4f1098632c 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -955,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) @@ -1922,7 +1922,7 @@ static int list_func_head(ufunc_T *fp, bool indent, bool force) } /// Get a function name, translating "" and "". -/// 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 @@ -3242,7 +3242,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; 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 3a5d056a9a..a33636667e 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -1108,7 +1108,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; @@ -1283,7 +1283,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); } -- cgit