diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 22:40:31 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 22:40:31 +0000 |
commit | 339e2d15cc26fe86988ea06468d912a46c8d6f29 (patch) | |
tree | a6167fc8fcfc6ae2dc102f57b2473858eac34063 /src/nvim/eval/funcs.c | |
parent | 067dc73729267c0262438a6fdd66e586f8496946 (diff) | |
parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
download | rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.tar.gz rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.tar.bz2 rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.zip |
Merge remote-tracking branch 'upstream/master' into fix_repeatcmdline
Diffstat (limited to 'src/nvim/eval/funcs.c')
-rw-r--r-- | src/nvim/eval/funcs.c | 2255 |
1 files changed, 1056 insertions, 1199 deletions
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 5a31288ecd..13425b21d1 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1,6 +1,3 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - #include <assert.h> #include <fcntl.h> #include <float.h> @@ -11,11 +8,13 @@ #include <msgpack/pack.h> #include <msgpack/unpack.h> #include <signal.h> +#include <stdarg.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> +#include <sys/types.h> #include <time.h> #include <uv.h> @@ -25,14 +24,15 @@ #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" -#include "nvim/ascii.h" -#include "nvim/assert.h" +#include "nvim/ascii_defs.h" +#include "nvim/assert_defs.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" +#include "nvim/cmdexpand_defs.h" #include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/diff.h" @@ -44,6 +44,7 @@ #include "nvim/eval/executor.h" #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" #include "nvim/eval/window.h" @@ -57,41 +58,40 @@ #include "nvim/ex_getln.h" #include "nvim/file_search.h" #include "nvim/fileio.h" +#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/gettext.h" #include "nvim/globals.h" -#include "nvim/grid_defs.h" -#include "nvim/hashtab.h" +#include "nvim/grid.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/input.h" +#include "nvim/insexpand.h" #include "nvim/keycodes.h" #include "nvim/lua/executor.h" -#include "nvim/macros.h" +#include "nvim/macros_defs.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/math.h" #include "nvim/mbyte.h" -#include "nvim/memfile_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/msgpack_rpc/channel.h" -#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/option_vars.h" #include "nvim/optionstr.h" #include "nvim/os/dl.h" #include "nvim/os/fileio.h" -#include "nvim/os/fs_defs.h" +#include "nvim/os/fs.h" #include "nvim/os/os.h" #include "nvim/os/pty_process.h" #include "nvim/os/shell.h" @@ -100,7 +100,7 @@ #include "nvim/path.h" #include "nvim/plines.h" #include "nvim/popupmenu.h" -#include "nvim/pos.h" +#include "nvim/pos_defs.h" #include "nvim/profile.h" #include "nvim/regexp.h" #include "nvim/runtime.h" @@ -113,9 +113,8 @@ #include "nvim/syntax.h" #include "nvim/tag.h" #include "nvim/ui.h" -#include "nvim/undo.h" #include "nvim/version.h" -#include "nvim/vim.h" +#include "nvim/vim_defs.h" #include "nvim/window.h" /// Describe data to return from find_some_match() @@ -145,11 +144,17 @@ PRAGMA_DIAG_POP PRAGMA_DIAG_POP #endif -static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob"); -static char *e_invalwindow = N_("E957: Invalid window number"); -static char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value"); -static char e_using_number_as_bool_nr[] - = N_("E1023: Using a Number as a Bool: %d"); +static const char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob"); +static const char *e_invalwindow = N_("E957: Invalid window number"); +static const char e_argument_of_str_must_be_list_string_or_dictionary[] + = N_("E706: Argument of %s must be a List, String or Dictionary"); +static const char e_invalid_submatch_number_nr[] + = N_("E935: Invalid submatch number: %d"); +static const char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value"); +static const char e_string_list_or_blob_required[] + = N_("E1098: String, List or Blob required"); +static const char e_missing_function_argument[] + = N_("E1132: Missing function argument"); /// Dummy va_list for passing to vim_snprintf /// @@ -226,6 +231,31 @@ const EvalFuncDef *find_internal_func(const char *const name) return index >= 0 ? &functions[index] : NULL; } +/// Check the argument count to use for internal function "fdef". +/// @return -1 for failure, 0 if no method base accepted, 1 if method base is +/// first argument, 2 if method base is second argument, etc. +int check_internal_func(const EvalFuncDef *const fdef, const int argcount) + FUNC_ATTR_NONNULL_ALL +{ + int res; + + if (argcount < fdef->min_argc) { + res = FCERR_TOOFEW; + } else if (argcount > fdef->max_argc) { + res = FCERR_TOOMANY; + } else { + return fdef->base_arg; + } + + const char *const name = fdef->name; + if (res == FCERR_TOOMANY) { + semsg(_(e_toomanyarg), name); + } else { + semsg(_(e_toofewarg), name); + } + return -1; +} + int call_internal_func(const char *const fname, const int argcount, typval_T *const argvars, typval_T *const rettv) FUNC_ATTR_NONNULL_ALL @@ -261,6 +291,9 @@ int call_internal_method(const char *const fname, const int argcount, typval_T * typval_T argv[MAX_FUNC_ARGS + 1]; const ptrdiff_t base_index = fdef->base_arg == BASE_LAST ? argcount : fdef->base_arg - 1; + if (argcount < base_index) { + return FCERR_TOOFEW; + } memcpy(argv, argvars, (size_t)base_index * sizeof(typval_T)); argv[base_index] = *basetv; memcpy(argv + base_index + 1, argvars + base_index, @@ -319,11 +352,12 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) Object result = handler.fn(VIML_INTERNAL_CALL, args, &res_arena, &err); if (ERROR_SET(&err)) { - semsg_multiline((const char *)e_api_error, err.msg); + semsg_multiline(e_api_error, err.msg); goto end; } if (!object_to_vim(result, rettv, &err)) { + assert(ERROR_SET(&err)); semsg(_("Error converting the call result: %s"), err.msg); } @@ -448,7 +482,7 @@ buf_T *tv_get_buf(typval_T *tv, int curtab_only) int save_magic = p_magic; p_magic = true; char *save_cpo = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; buf_T *buf = buflist_findnr(buflist_findpat(name, name + strlen(name), true, false, curtab_only)); @@ -492,7 +526,7 @@ buf_T *get_buf_arg(typval_T *arg) /// "byte2line(byte)" function static void f_byte2line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - long boff = tv_get_number(&argvars[0]) - 1; + int boff = (int)tv_get_number(&argvars[0]) - 1; if (boff < 0) { rettv->vval.v_number = -1; } else { @@ -501,41 +535,6 @@ static void f_byte2line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -static void byteidx(typval_T *argvars, typval_T *rettv, int comp) -{ - const char *const str = tv_get_string_chk(&argvars[0]); - varnumber_T idx = tv_get_number_chk(&argvars[1], NULL); - rettv->vval.v_number = -1; - if (str == NULL || idx < 0) { - return; - } - - const char *t = str; - for (; idx > 0; idx--) { - if (*t == NUL) { // EOL reached. - return; - } - if (comp) { - t += utf_ptr2len(t); - } else { - t += utfc_ptr2len(t); - } - } - rettv->vval.v_number = (varnumber_T)(t - str); -} - -/// "byteidx()" function -static void f_byteidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - byteidx(argvars, rettv, false); -} - -/// "byteidxcomp()" function -static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - byteidx(argvars, rettv, true); -} - /// "call(func, arglist [, dict])" function static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -563,14 +562,13 @@ static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) func = (char *)tv_get_string(&argvars[0]); } - if (*func == NUL) { - return; // type error or empty name + if (func == NULL || *func == NUL) { + return; // type error, empty name or null function } dict_T *selfdict = NULL; if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_DICT) { - emsg(_(e_dictreq)); + if (tv_check_for_dict_arg(argvars, 2) == FAIL) { if (owned) { func_unref(func); } @@ -653,7 +651,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) bool crlf = false; #else Channel *chan = find_channel(id); - bool crlf = (chan != NULL && chan->term) ? true: false; + bool crlf = (chan != NULL && chan->term) ? true : false; #endif if (argvars[1].v_type == VAR_BLOB) { @@ -761,53 +759,6 @@ static void f_charcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) get_col(argvars, rettv, true); } -/// "charidx()" function -static void f_charidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = -1; - - if (argvars[0].v_type != VAR_STRING - || argvars[1].v_type != VAR_NUMBER - || (argvars[2].v_type != VAR_UNKNOWN - && argvars[2].v_type != VAR_NUMBER - && argvars[2].v_type != VAR_BOOL)) { - emsg(_(e_invarg)); - return; - } - - const char *str = tv_get_string_chk(&argvars[0]); - varnumber_T idx = tv_get_number_chk(&argvars[1], NULL); - if (str == NULL || idx < 0) { - return; - } - int countcc = 0; - if (argvars[2].v_type != VAR_UNKNOWN) { - countcc = (int)tv_get_number(&argvars[2]); - } - if (countcc < 0 || countcc > 1) { - semsg(_(e_using_number_as_bool_nr), countcc); - return; - } - - int (*ptr2len)(const char *); - if (countcc) { - ptr2len = utf_ptr2len; - } else { - ptr2len = utfc_ptr2len; - } - - const char *p; - int len; - for (p = str, len = 0; p <= str + idx; len++) { - if (*p == NUL) { - return; - } - p += ptr2len(p); - } - - rettv->vval.v_number = len > 0 ? len - 1 : 0; -} - /// "chdir(dir)" function static void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -925,8 +876,7 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (!error) { - rettv->vval.v_number = do_dialog(type, NULL, (char *)message, (char *)buttons, def, NULL, - false); + rettv->vval.v_number = do_dialog(type, NULL, message, buttons, def, NULL, false); } } @@ -936,10 +886,90 @@ static void f_copy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) var_item_copy(NULL, &argvars[0], rettv, false, 0); } +/// Count the number of times "needle" occurs in string "haystack". +/// +/// @param ic ignore case +static varnumber_T count_string(const char *haystack, const char *needle, bool ic) +{ + varnumber_T n = 0; + const char *p = haystack; + + if (p == NULL || needle == NULL || *needle == NUL) { + return 0; + } + + if (ic) { + const size_t len = strlen(needle); + + while (*p != NUL) { + if (mb_strnicmp(p, needle, len) == 0) { + n++; + p += len; + } else { + MB_PTR_ADV(p); + } + } + } else { + const char *next; + while ((next = strstr(p, needle)) != NULL) { + n++; + p = next + strlen(needle); + } + } + + return n; +} + +/// Count the number of times item "needle" occurs in List "l" starting at index "idx". +/// +/// @param ic ignore case +static varnumber_T count_list(list_T *l, typval_T *needle, int64_t idx, bool ic) +{ + if (tv_list_len(l) == 0) { + return 0; + } + + listitem_T *li = tv_list_find(l, (int)idx); + if (li == NULL) { + semsg(_(e_list_index_out_of_range_nr), idx); + return 0; + } + + 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)) { + n++; + } + } + + return n; +} + +/// Count the number of times item "needle" occurs in Dict "d". +/// +/// @param ic ignore case +static varnumber_T count_dict(dict_T *d, typval_T *needle, bool ic) +{ + if (d == NULL) { + return 0; + } + + varnumber_T n = 0; + + TV_DICT_ITER(d, di, { + if (tv_equal(&di->di_tv, needle, ic, false)) { + n++; + } + }); + + return n; +} + /// "count()" function static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - long n = 0; + varnumber_T n = 0; int ic = 0; bool error = false; @@ -947,78 +977,30 @@ static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ic = (int)tv_get_number_chk(&argvars[2], &error); } - if (argvars[0].v_type == VAR_STRING) { - const char *expr = tv_get_string_chk(&argvars[1]); - const char *p = argvars[0].vval.v_string; - - if (!error && expr != NULL && *expr != NUL && p != NULL) { - if (ic) { - const size_t len = strlen(expr); - - while (*p != NUL) { - if (mb_strnicmp((char *)p, (char *)expr, len) == 0) { - n++; - p += len; - } else { - MB_PTR_ADV(p); - } - } - } else { - char *next; - while ((next = strstr((char *)p, (char *)expr)) != NULL) { - n++; - p = next + strlen(expr); - } - } + if (!error && argvars[0].v_type == VAR_STRING) { + n = count_string(argvars[0].vval.v_string, tv_get_string_chk(&argvars[1]), ic); + } else if (!error && argvars[0].v_type == VAR_LIST) { + int64_t idx = 0; + if (argvars[2].v_type != VAR_UNKNOWN + && argvars[3].v_type != VAR_UNKNOWN) { + idx = (int64_t)tv_get_number_chk(&argvars[3], &error); } - } else if (argvars[0].v_type == VAR_LIST) { - list_T *l = argvars[0].vval.v_list; - - if (l != NULL) { - listitem_T *li = tv_list_first(l); - if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[3].v_type != VAR_UNKNOWN) { - long idx = tv_get_number_chk(&argvars[3], &error); - if (!error) { - li = tv_list_find(l, (int)idx); - if (li == NULL) { - semsg(_(e_listidx), (int64_t)idx); - } - } - } - if (error) { - li = NULL; - } - } - - for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { - if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) { - n++; - } - } + if (!error) { + n = count_list(argvars[0].vval.v_list, &argvars[1], idx, ic); } - } else if (argvars[0].v_type == VAR_DICT) { + } else if (!error && argvars[0].v_type == VAR_DICT) { dict_T *d = argvars[0].vval.v_dict; if (d != NULL) { - if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[3].v_type != VAR_UNKNOWN) { - emsg(_(e_invarg)); - } - } - - int todo = error ? 0 : (int)d->dv_hashtab.ht_used; - for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) { - n++; - } - } + if (argvars[2].v_type != VAR_UNKNOWN + && argvars[3].v_type != VAR_UNKNOWN) { + emsg(_(e_invarg)); + } else { + n = count_dict(argvars[0].vval.v_dict, &argvars[1], ic); } } - } else { - semsg(_(e_listdictarg), "count()"); + } else if (!error) { + semsg(_(e_argument_of_str_must_be_list_string_or_dictionary), "count()"); } rettv->vval.v_number = n; } @@ -1042,7 +1024,7 @@ static void f_ctxget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) Dictionary ctx_dict = ctx_to_dict(ctx); Error err = ERROR_INIT; - object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err); + (void)object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err); api_free_dictionary(ctx_dict); api_clear_error(&err); } @@ -1064,17 +1046,17 @@ static void f_ctxpush(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) TV_LIST_ITER(argvars[0].vval.v_list, li, { typval_T *tv_li = TV_LIST_ITEM_TV(li); if (tv_li->v_type == VAR_STRING) { - if (strequal((char *)tv_li->vval.v_string, "regs")) { + if (strequal(tv_li->vval.v_string, "regs")) { types |= kCtxRegs; - } else if (strequal((char *)tv_li->vval.v_string, "jumps")) { + } else if (strequal(tv_li->vval.v_string, "jumps")) { types |= kCtxJumps; - } else if (strequal((char *)tv_li->vval.v_string, "bufs")) { + } else if (strequal(tv_li->vval.v_string, "bufs")) { types |= kCtxBufs; - } else if (strequal((char *)tv_li->vval.v_string, "gvars")) { + } else if (strequal(tv_li->vval.v_string, "gvars")) { types |= kCtxGVars; - } else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) { + } else if (strequal(tv_li->vval.v_string, "sfuncs")) { types |= kCtxSFuncs; - } else if (strequal((char *)tv_li->vval.v_string, "funcs")) { + } else if (strequal(tv_li->vval.v_string, "funcs")) { types |= kCtxFuncs; } } @@ -1108,14 +1090,16 @@ static void f_ctxset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - int save_did_emsg = did_emsg; + const int save_did_emsg = did_emsg; did_emsg = false; Dictionary dict = vim_to_object(&argvars[0]).data.dictionary; Context tmp = CONTEXT_INIT; - ctx_from_dict(dict, &tmp); + Error err = ERROR_INIT; + ctx_from_dict(dict, &tmp, &err); - if (did_emsg) { + if (ERROR_SET(&err)) { + semsg("%s", err.msg); ctx_free(&tmp); } else { ctx_free(ctx); @@ -1123,6 +1107,7 @@ static void f_ctxset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } api_free_dictionary(dict); + api_clear_error(&err); did_emsg = save_did_emsg; } @@ -1134,12 +1119,13 @@ static void f_ctxsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } /// Set the cursor position. -/// If 'charcol' is true, then use the column number as a character offset. +/// If "charcol" is true, then use the column number as a character offset. /// Otherwise use the column number as a byte offset. static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) { - long lnum, col; - long coladd = 0; + linenr_T lnum; + colnr_T col; + colnr_T coladd = 0; bool set_curswant = true; rettv->vval.v_number = -1; @@ -1167,12 +1153,12 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) } else if (lnum == 0) { lnum = curwin->w_cursor.lnum; } - col = (long)tv_get_number_chk(&argvars[1], NULL); + col = (colnr_T)tv_get_number_chk(&argvars[1], NULL); if (charcol) { - col = buf_charidx_to_byteidx(curbuf, (linenr_T)lnum, (int)col) + 1; + col = buf_charidx_to_byteidx(curbuf, lnum, (int)col) + 1; } if (argvars[2].v_type != VAR_UNKNOWN) { - coladd = (long)tv_get_number_chk(&argvars[2], NULL); + coladd = (colnr_T)tv_get_number_chk(&argvars[2], NULL); } } else { emsg(_(e_invarg)); @@ -1182,12 +1168,12 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) return; // type error; errmsg already given } if (lnum > 0) { - curwin->w_cursor.lnum = (linenr_T)lnum; + curwin->w_cursor.lnum = lnum; } if (col > 0) { - curwin->w_cursor.col = (colnr_T)col - 1; + curwin->w_cursor.col = col - 1; } - curwin->w_cursor.coladd = (colnr_T)coladd; + curwin->w_cursor.coladd = coladd; // Make sure the cursor is in a valid position. check_cursor(); @@ -1236,18 +1222,16 @@ static void f_debugbreak(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "deepcopy()" function static void f_deepcopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - int noref = 0; + if (tv_check_for_opt_bool_arg(argvars, 1) == FAIL) { + return; + } + varnumber_T noref = 0; if (argvars[1].v_type != VAR_UNKNOWN) { - noref = (int)tv_get_bool_chk(&argvars[1], NULL); - } - if (noref < 0 || noref > 1) { - semsg(_(e_using_number_as_bool_nr), noref); - } else { - var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 - ? get_copyID() - : 0)); + noref = tv_get_bool_chk(&argvars[1], NULL); } + + var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 ? get_copyID() : 0)); } /// "delete()" function @@ -1546,11 +1530,11 @@ static void f_eval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *s = tv_get_string_chk(&argvars[0]); if (s != NULL) { - s = (const char *)skipwhite(s); + s = skipwhite(s); } const char *const expr_start = s; - if (s == NULL || eval1((char **)&s, rettv, true) == FAIL) { + if (s == NULL || eval1((char **)&s, rettv, &EVALARG_EVALUATE) == FAIL) { if (expr_start != NULL && !aborting()) { semsg(_(e_invexpr2), expr_start); } @@ -1722,7 +1706,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) xfree(exp); } } else if (*p == '&' || *p == '+') { // Option. - n = (get_option_tv(&p, NULL, true) == OK); + n = (eval_option(&p, NULL, true) == OK); if (*skipwhite(p) != NUL) { n = false; // Trailing garbage. } @@ -1752,7 +1736,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) char *p_csl_save = p_csl; // avoid using 'completeslash' here - p_csl = empty_option; + p_csl = empty_string_option; #endif rettv->v_type = VAR_STRING; @@ -1769,7 +1753,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) emsg_off++; } size_t len; - char *errormsg = NULL; + const char *errormsg = NULL; char *result = eval_vars((char *)s, s, &len, NULL, &errormsg, NULL, false); if (p_verbose == 0) { emsg_off--; @@ -1779,7 +1763,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (rettv->v_type == VAR_LIST) { tv_list_alloc_ret(rettv, (result != NULL)); if (result != NULL) { - tv_list_append_string(rettv->vval.v_list, (const char *)result, -1); + tv_list_append_string(rettv->vval.v_list, result, -1); } XFREE_CLEAR(result); } else { @@ -1805,8 +1789,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ExpandOne(&xpc, (char *)s, 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, - (const char *)xpc.xp_files[i], -1); + tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); } ExpandCleanup(&xpc); } @@ -1835,7 +1818,7 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// Expand all the special characters in a command string. static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char *errormsg = NULL; + const char *errormsg = NULL; bool emsgoff = true; if (argvars[1].v_type == VAR_DICT @@ -1870,8 +1853,8 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_string = cmdstr; } -/// "flatten(list[, {maxdepth}])" function -static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +/// "flatten()" and "flattennew()" functions +static void flatten_common(typval_T *argvars, typval_T *rettv, bool make_copy) { bool error = false; @@ -1880,11 +1863,11 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - long maxdepth; + int maxdepth; if (argvars[1].v_type == VAR_UNKNOWN) { maxdepth = 999999; } else { - maxdepth = (long)tv_get_number_chk(&argvars[1], &error); + maxdepth = (int)tv_get_number_chk(&argvars[1], &error); if (error) { return; } @@ -1895,92 +1878,179 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } list_T *list = argvars[0].vval.v_list; - if (list != NULL - && !value_check_lock(tv_list_locked(list), - N_("flatten() argument"), - TV_TRANSLATE) - && tv_list_flatten(list, maxdepth) == OK) { - tv_copy(&argvars[0], rettv); + rettv->v_type = VAR_LIST; + rettv->vval.v_list = list; + if (list == NULL) { + return; } + + if (make_copy) { + list = tv_list_copy(NULL, list, false, get_copyID()); + rettv->vval.v_list = list; + if (list == NULL) { + return; + } + } else { + if (value_check_lock(tv_list_locked(list), N_("flatten() argument"), TV_TRANSLATE)) { + return; + } + tv_list_ref(list); + } + + tv_list_flatten(list, NULL, tv_list_len(list), maxdepth); } -/// "extend(list, list [, idx])" function -/// "extend(dict, dict [, action])" function -static void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +/// "flatten(list[, {maxdepth}])" function +static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - const char *const arg_errmsg = N_("extend() argument"); + flatten_common(argvars, rettv, false); +} - if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { - bool error = false; +/// "flattennew(list[, {maxdepth}])" function +static void f_flattennew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + flatten_common(argvars, rettv, true); +} - list_T *const l1 = argvars[0].vval.v_list; - list_T *const l2 = argvars[1].vval.v_list; - if (!value_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { - listitem_T *item; - if (argvars[2].v_type != VAR_UNKNOWN) { - long before = (long)tv_get_number_chk(&argvars[2], &error); - if (error) { - return; // Type error; errmsg already given. - } +/// extend() a List. Append List argvars[1] to List argvars[0] before index +/// argvars[3] and return the resulting list in "rettv". +/// +/// @param is_new true for extendnew() +static void extend_list(typval_T *argvars, const char *arg_errmsg, bool is_new, typval_T *rettv) +{ + bool error = false; - if (before == tv_list_len(l1)) { - item = NULL; - } else { - item = tv_list_find(l1, (int)before); - if (item == NULL) { - semsg(_(e_listidx), (int64_t)before); - return; - } - } - } else { + list_T *l1 = argvars[0].vval.v_list; + list_T *const l2 = argvars[1].vval.v_list; + if (is_new || !value_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { + if (is_new) { + l1 = tv_list_copy(NULL, l1, false, get_copyID()); + if (l1 == NULL) { + return; + } + } + + listitem_T *item; + if (argvars[2].v_type != VAR_UNKNOWN) { + int before = (int)tv_get_number_chk(&argvars[2], &error); + if (error) { + return; // Type error; errmsg already given. + } + + if (before == tv_list_len(l1)) { item = NULL; + } else { + item = tv_list_find(l1, before); + if (item == NULL) { + semsg(_(e_list_index_out_of_range_nr), (int64_t)before); + return; + } } - tv_list_extend(l1, l2, item); + } else { + item = NULL; + } + tv_list_extend(l1, l2, item); + if (is_new) { + *rettv = (typval_T){ + .v_type = VAR_LIST, + .v_lock = VAR_UNLOCKED, + .vval.v_list = l1, + }; + } else { tv_copy(&argvars[0], rettv); } - } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == - VAR_DICT) { - dict_T *const d1 = argvars[0].vval.v_dict; - dict_T *const d2 = argvars[1].vval.v_dict; - if (d1 == NULL) { - const bool locked = value_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); - (void)locked; - assert(locked == true); - } else if (d2 == NULL) { - // Do nothing - tv_copy(&argvars[0], rettv); - } else if (!value_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) { - const char *action = "force"; - // Check the third argument. - if (argvars[2].v_type != VAR_UNKNOWN) { - const char *const av[] = { "keep", "force", "error" }; + } +} - action = tv_get_string_chk(&argvars[2]); - if (action == NULL) { - return; // Type error; error message already given. - } - size_t i; - for (i = 0; i < ARRAY_SIZE(av); i++) { - if (strcmp(action, av[i]) == 0) { - break; - } - } - if (i == 3) { - semsg(_(e_invarg2), action); - return; +/// extend() a Dict. Append Dict argvars[1] to Dict argvars[0] and return the +/// resulting Dict in "rettv". +/// +/// @param is_new true for extendnew() +static void extend_dict(typval_T *argvars, const char *arg_errmsg, bool is_new, typval_T *rettv) +{ + dict_T *d1 = argvars[0].vval.v_dict; + dict_T *const d2 = argvars[1].vval.v_dict; + if (d1 == NULL) { + const bool locked = value_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); + (void)locked; + assert(locked == true); + } else if (d2 == NULL) { + // Do nothing + tv_copy(&argvars[0], rettv); + } else if (is_new || !value_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) { + if (is_new) { + d1 = tv_dict_copy(NULL, d1, false, get_copyID()); + if (d1 == NULL) { + return; + } + } + + const char *action = "force"; + // Check the third argument. + if (argvars[2].v_type != VAR_UNKNOWN) { + const char *const av[] = { "keep", "force", "error" }; + + action = tv_get_string_chk(&argvars[2]); + if (action == NULL) { + return; // Type error; error message already given. + } + size_t i; + for (i = 0; i < ARRAY_SIZE(av); i++) { + if (strcmp(action, av[i]) == 0) { + break; } } + if (i == 3) { + semsg(_(e_invarg2), action); + return; + } + } - tv_dict_extend(d1, d2, action); + tv_dict_extend(d1, d2, action); + if (is_new) { + *rettv = (typval_T){ + .v_type = VAR_DICT, + .v_lock = VAR_UNLOCKED, + .vval.v_dict = d1, + }; + } else { tv_copy(&argvars[0], rettv); } + } +} + +/// "extend()" or "extendnew()" function. +/// +/// @param is_new true for extendnew() +static void extend(typval_T *argvars, typval_T *rettv, char *arg_errmsg, bool is_new) +{ + if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { + extend_list(argvars, arg_errmsg, is_new, rettv); + } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT) { + extend_dict(argvars, arg_errmsg, is_new, rettv); } else { - semsg(_(e_listdictarg), "extend()"); + semsg(_(e_listdictarg), is_new ? "extendnew()" : "extend()"); } } +/// "extend(list, list [, idx])" function +/// "extend(dict, dict [, action])" function +static void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + char *errmsg = N_("extend() argument"); + extend(argvars, rettv, errmsg, false); +} + +/// "extendnew(list, list [, idx])" function +/// "extendnew(dict, dict [, action])" function +static void f_extendnew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + char *errmsg = N_("extendnew() argument"); + extend(argvars, rettv, errmsg, true); +} + /// "feedkeys()" function static void f_feedkeys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2053,6 +2123,9 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) } 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); @@ -2063,13 +2136,17 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) find_what, curbuf->b_ffname, (find_what == FINDFILE_DIR ? "" - : curbuf->b_p_sua)); + : 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, (const char *)fresult, -1); + 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) { @@ -2077,12 +2154,6 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) } } -/// "filter()" function -static void f_filter(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - filter_map(argvars, rettv, false); -} - /// "finddir({fname}[, {path}[, {count}]])" function static void f_finddir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2164,7 +2235,8 @@ static void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "foreground()" function static void f_foreground(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{} +{ +} static void f_funcref(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2433,7 +2505,7 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) [kCdScopeTabpage] = 0, // Number of tab to look at. }; - char *cwd = NULL; // Current working directory to print + 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. @@ -2681,47 +2753,6 @@ static void f_getmarklist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) get_buf_local_marks(buf, rettv->vval.v_list); } -/// "getmousepos()" function -static void f_getmousepos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int row = mouse_row; - int col = mouse_col; - int grid = mouse_grid; - varnumber_T winid = 0; - varnumber_T winrow = 0; - varnumber_T wincol = 0; - linenr_T lnum = 0; - varnumber_T column = 0; - - tv_dict_alloc_ret(rettv); - dict_T *d = rettv->vval.v_dict; - - tv_dict_add_nr(d, S_LEN("screenrow"), (varnumber_T)mouse_row + 1); - tv_dict_add_nr(d, S_LEN("screencol"), (varnumber_T)mouse_col + 1); - - win_T *wp = mouse_find_win(&grid, &row, &col); - if (wp != NULL) { - int height = wp->w_height + wp->w_hsep_height + wp->w_status_height; - // The height is adjusted by 1 when there is a bottom border. This is not - // necessary for a top border since `row` starts at -1 in that case. - if (row < height + wp->w_border_adj[2]) { - winid = wp->handle; - winrow = row + 1 + wp->w_winrow_off; // Adjust by 1 for top border - wincol = col + 1 + wp->w_wincol_off; // Adjust by 1 for left border - if (row >= 0 && row < wp->w_height && col >= 0 && col < wp->w_width) { - (void)mouse_comp_pos(wp, &row, &col, &lnum); - col = vcol2col(wp, lnum, col); - column = col + 1; - } - } - } - tv_dict_add_nr(d, S_LEN("winid"), winid); - tv_dict_add_nr(d, S_LEN("winrow"), winrow); - tv_dict_add_nr(d, S_LEN("wincol"), wincol); - tv_dict_add_nr(d, S_LEN("line"), (varnumber_T)lnum); - tv_dict_add_nr(d, S_LEN("column"), column); -} - /// "getpid()" function static void f_getpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2840,7 +2871,8 @@ static void f_gettagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// Dummy timer callback. Used by f_wait(). static void dummy_timer_due_cb(TimeWatcher *tw, void *data) -{} +{ +} /// Dummy timer close callback. Used by f_wait(). static void dummy_timer_close_cb(TimeWatcher *tw, void *data) @@ -2867,8 +2899,8 @@ static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) int timeout = (int)argvars[0].vval.v_number; typval_T expr = argvars[1]; int interval = argvars[2].v_type == VAR_NUMBER - ? (int)argvars[2].vval.v_number - : 200; // Default. + ? (int)argvars[2].vval.v_number + : 200; // Default. TimeWatcher *tw = xmalloc(sizeof(TimeWatcher)); // Start dummy timer. @@ -2882,8 +2914,11 @@ static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) bool error = false; const int called_emsg_before = called_emsg; + // Flush screen updates before blocking. + ui_flush(); + LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout, - eval_expr_typval(&expr, &argv, 0, &exprval) != OK + eval_expr_typval(&expr, false, &argv, 0, &exprval) != OK || tv_get_number_chk(&exprval, &error) || called_emsg > called_emsg_before || error || got_int); @@ -2941,8 +2976,7 @@ static void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 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, (const char *)xpc.xp_files[i], - -1); + tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); } ExpandCleanup(&xpc); } @@ -2983,7 +3017,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 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); + 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"); @@ -3013,14 +3047,12 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "gettext()" function static void f_gettext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (argvars[0].v_type != VAR_STRING - || argvars[0].vval.v_string == NULL - || *argvars[0].vval.v_string == NUL) { - semsg(_(e_invarg2), tv_get_string(&argvars[0])); - } else { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xstrdup(_(argvars[0].vval.v_string)); + if (tv_check_for_nonempty_string_arg(argvars, 0) == FAIL) { + return; } + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xstrdup(_(argvars[0].vval.v_string)); } /// "has()" function @@ -3064,9 +3096,6 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) "conceal", "cursorbind", "cursorshape", -#ifdef DEBUG - "debug", -#endif "dialog_con", "diff", "digraphs", @@ -3149,6 +3178,9 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) "windows", "winaltkeys", "writebackup", +#ifdef HAVE_XATTR + "xattr", +#endif "nvim", }; @@ -3164,7 +3196,9 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (!n) { - if (STRNICMP(name, "patch", 5) == 0) { + if (STRNICMP(name, "gui_running", 11) == 0) { + n = ui_gui_attached(); + } else if (STRNICMP(name, "patch", 5) == 0) { if (name[5] == '-' && strlen(name) >= 11 && ascii_isdigit(name[6]) @@ -3219,7 +3253,7 @@ static bool has_wsl(void) static TriState has_wsl = kNone; if (has_wsl == kNone) { Error err = ERROR_INIT; - Object o = nlua_exec(STATIC_CSTR_AS_STRING("return vim.loop.os_uname()['release']:lower()" + Object o = nlua_exec(STATIC_CSTR_AS_STRING("return vim.uv.os_uname()['release']:lower()" ":match('microsoft') and true or false"), (Array)ARRAY_DICT_INIT, &err); assert(!ERROR_SET(&err)); @@ -3253,7 +3287,7 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) [kCdScopeTabpage] = 0, // Number of tab to look at. }; - tabpage_T *tp = curtab; // The tabpage 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; @@ -3352,34 +3386,6 @@ static void f_hostname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_string = xstrdup(hostname); } -/// iconv() function -static void f_iconv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - vimconv_T vimconv; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - const char *const str = tv_get_string(&argvars[0]); - char buf1[NUMBUFLEN]; - char *const from = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[1], buf1))); - char buf2[NUMBUFLEN]; - char *const to = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[2], buf2))); - vimconv.vc_type = CONV_NONE; - convert_setup(&vimconv, from, to); - - // If the encodings are equal, no conversion needed. - if (vimconv.vc_type == CONV_NONE) { - rettv->vval.v_string = xstrdup(str); - } else { - rettv->vval.v_string = string_convert(&vimconv, (char *)str, NULL); - } - - convert_setup(&vimconv, NULL, NULL); - xfree(from); - xfree(to); -} - /// "indent()" function static void f_indent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -3394,7 +3400,7 @@ static void f_indent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "index()" function static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - long idx = 0; + int idx = 0; bool ic = false; rettv->vval.v_number = -1; @@ -3421,7 +3427,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) for (idx = start; idx < tv_blob_len(b); idx++) { typval_T tv; tv.v_type = VAR_NUMBER; - tv.vval.v_number = tv_blob_get(b, (int)idx); + tv.vval.v_number = tv_blob_get(b, idx); if (tv_equal(&tv, &argvars[1], ic, false)) { rettv->vval.v_number = idx; return; @@ -3447,7 +3453,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (error || idx == -1) { item = NULL; } else { - item = tv_list_find(l, (int)idx); + item = tv_list_find(l, idx); assert(item != NULL); } if (argvars[3].v_type != VAR_UNKNOWN) { @@ -3466,6 +3472,138 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } +/// Evaluate "expr" with the v:key and v:val arguments and return the result. +/// The expression is expected to return a boolean value. The caller should set +/// the VV_KEY and VV_VAL vim variables before calling this function. +static varnumber_T indexof_eval_expr(typval_T *expr) +{ + typval_T argv[3]; + argv[0] = *get_vim_var_tv(VV_KEY); + argv[1] = *get_vim_var_tv(VV_VAL); + typval_T newtv; + newtv.v_type = VAR_UNKNOWN; + + if (eval_expr_typval(expr, false, argv, 2, &newtv) == FAIL) { + return false; + } + + bool error = false; + varnumber_T found = tv_get_bool_chk(&newtv, &error); + tv_clear(&newtv); + + return error ? false : found; +} + +/// Evaluate "expr" for each byte in the Blob "b" starting with the byte at +/// "startidx" and return the index of the byte where "expr" is TRUE. Returns +/// -1 if "expr" doesn't evaluate to TRUE for any of the bytes. +static varnumber_T indexof_blob(blob_T *b, varnumber_T startidx, typval_T *expr) +{ + if (b == NULL) { + return -1; + } + + if (startidx < 0) { + // negative index: index from the last byte + startidx = tv_blob_len(b) + startidx; + if (startidx < 0) { + startidx = 0; + } + } + + 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)); + + if (indexof_eval_expr(expr)) { + return idx; + } + } + + return -1; +} + +/// Evaluate "expr" for each item in the List "l" starting with the item at +/// "startidx" and return the index of the item where "expr" is TRUE. Returns +/// -1 if "expr" doesn't evaluate to TRUE for any of the items. +static varnumber_T indexof_list(list_T *l, varnumber_T startidx, typval_T *expr) +{ + if (l == NULL) { + return -1; + } + + listitem_T *item; + varnumber_T idx = 0; + if (startidx == 0) { + item = tv_list_first(l); + } else { + // Start at specified item. + idx = tv_list_uidx(l, (int)startidx); + if (idx == -1) { + item = NULL; + } else { + item = tv_list_find(l, (int)idx); + assert(item != NULL); + } + } + + 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)); + + bool found = indexof_eval_expr(expr); + tv_clear(get_vim_var_tv(VV_VAL)); + + if (found) { + return idx; + } + } + + return -1; +} + +/// "indexof()" function +static void f_indexof(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = -1; + + if (tv_check_for_list_or_blob_arg(argvars, 0) == FAIL + || tv_check_for_string_or_func_arg(argvars, 1) == FAIL + || tv_check_for_opt_dict_arg(argvars, 2) == FAIL) { + return; + } + + if ((argvars[1].v_type == VAR_STRING && argvars[1].vval.v_string == NULL) + || (argvars[1].v_type == VAR_FUNC && argvars[1].vval.v_partial == NULL)) { + return; + } + + varnumber_T startidx = 0; + if (argvars[2].v_type == VAR_DICT) { + startidx = tv_dict_get_number_def(argvars[2].vval.v_dict, "startidx", 0); + } + + typval_T save_val; + typval_T save_key; + prepare_vimvar(VV_VAL, &save_val); + prepare_vimvar(VV_KEY, &save_key); + + // We reset "did_emsg" to be able to detect whether an error occurred + // during evaluation of the expression. + const int save_did_emsg = did_emsg; + did_emsg = false; + + if (argvars[0].v_type == VAR_BLOB) { + rettv->vval.v_number = indexof_blob(argvars[0].vval.v_blob, startidx, &argvars[1]); + } else { + rettv->vval.v_number = indexof_list(argvars[0].vval.v_list, startidx, &argvars[1]); + } + + restore_vimvar(VV_KEY, &save_key); + restore_vimvar(VV_VAL, &save_val); + did_emsg |= save_did_emsg; +} + static bool inputsecret_flag = false; /// "input()" function @@ -3547,7 +3685,6 @@ static void f_inputsecret(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "insert()" function static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - list_T *l; bool error = false; if (argvars[0].v_type == VAR_BLOB) { @@ -3559,11 +3696,11 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - long before = 0; + int before = 0; const int len = tv_blob_len(b); if (argvars[2].v_type != VAR_UNKNOWN) { - before = (long)tv_get_number_chk(&argvars[2], &error); + before = (int)tv_get_number_chk(&argvars[2], &error); if (error) { return; // type error; errmsg already given } @@ -3590,9 +3727,13 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) tv_copy(&argvars[0], rettv); } else if (argvars[0].v_type != VAR_LIST) { semsg(_(e_listblobarg), "insert()"); - } else if (!value_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - N_("insert() argument"), TV_TRANSLATE)) { - long before = 0; + } else { + list_T *l = argvars[0].vval.v_list; + if (value_check_lock(tv_list_locked(l), N_("insert() argument"), TV_TRANSLATE)) { + return; + } + + int64_t before = 0; if (argvars[2].v_type != VAR_UNKNOWN) { before = tv_get_number_chk(&argvars[2], &error); } @@ -3605,7 +3746,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (before != tv_list_len(l)) { item = tv_list_find(l, (int)before); if (item == NULL) { - semsg(_(e_listidx), (int64_t)before); + semsg(_(e_list_index_out_of_range_nr), before); l = NULL; } } @@ -3617,8 +3758,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } /// "interrupt()" function -static void f_interrupt(typval_T *argvars FUNC_ATTR_UNUSED, typval_T *rettv FUNC_ATTR_UNUSED, - EvalFuncData fptr FUNC_ATTR_UNUSED) +static void f_interrupt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { got_int = true; } @@ -3795,7 +3935,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en if (!clear_env) { typval_T temp_env = TV_INITIAL_VALUE; - f_environ(NULL, &temp_env, (EvalFuncData){ .nullptr = NULL }); + f_environ(NULL, &temp_env, (EvalFuncData){ .null = NULL }); tv_dict_extend(env, temp_env.vval.v_dict, "force"); tv_dict_free(temp_env.vval.v_dict); @@ -3812,12 +3952,13 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en } } #ifndef MSWIN - // Set COLORTERM to "truecolor" if termguicolors is set and 256 - // otherwise, but only if it was set in the parent terminal at all - dictitem_T *dv = tv_dict_find(env, S_LEN("COLORTERM")); - if (dv) { - tv_dict_item_remove(env, dv); - tv_dict_add_str(env, S_LEN("COLORTERM"), p_tgc ? "truecolor" : "256"); + // Set COLORTERM to "truecolor" if termguicolors is set + if (p_tgc) { + dictitem_T *dv = tv_dict_find(env, S_LEN("COLORTERM")); + if (dv) { + tv_dict_item_remove(env, dv); + } + tv_dict_add_str(env, S_LEN("COLORTERM"), "truecolor"); } #endif } @@ -3849,7 +3990,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en #ifdef MSWIN TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, { // Always use upper-case keys for Windows so we detect duplicate keys - char *const key = strcase_save((const char *)var->di_key, true); + char *const key = strcase_save(var->di_key, true); size_t len = strlen(key); dictitem_T *dv = tv_dict_find(env, key, len); if (dv) { @@ -3995,7 +4136,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) env = create_environment(job_env, clear_env, pty, term_name); - Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, + Channel *chan = channel_job_start(argv, NULL, on_stdout, on_stderr, on_exit, pty, rpc, overlapped, detach, stdin_mode, cwd, width, height, env, &rettv->vval.v_number); if (chan) { @@ -4052,6 +4193,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } ui_busy_start(); + ui_flush(); list_T *args = argvars[0].vval.v_list; Channel **jobs = xcalloc((size_t)tv_list_len(args), sizeof(*jobs)); MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop); @@ -4364,30 +4506,24 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) nlua_typval_eval(cstr_as_string((char *)str), &argvars[1], rettv); } -/// "map()" function -static void f_map(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - filter_map(argvars, rettv, true); -} - static void find_some_match(typval_T *const argvars, typval_T *const rettv, const SomeMatchType type) { char *str = NULL; - long len = 0; + int64_t len = 0; char *expr = NULL; regmatch_T regmatch; - long start = 0; - long nth = 1; + int64_t start = 0; + int64_t nth = 1; colnr_T startcol = 0; bool match = false; list_T *l = NULL; - long idx = 0; + int idx = 0; char *tofree = NULL; // Make 'cpoptions' empty, the 'l' flag should not be used here. char *save_cpo = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; rettv->vval.v_number = -1; switch (type) { @@ -4421,7 +4557,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, li = tv_list_first(l); } else { expr = str = (char *)tv_get_string(&argvars[0]); - len = (long)strlen(str); + len = (int64_t)strlen(str); } char patbuf[NUMBUFLEN]; @@ -4442,7 +4578,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, if (idx == -1) { goto theend; } - li = tv_list_find(l, (int)idx); + li = tv_list_find(l, idx); } else { if (start < 0) { start = 0; @@ -4469,11 +4605,11 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, } } - regmatch.regprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING); + regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { regmatch.rm_ic = p_ic; - for (;;) { + while (true) { if (l != NULL) { if (li == NULL) { match = false; @@ -4534,8 +4670,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, if (regmatch.endp[i] == NULL) { tv_list_append_string(rettv->vval.v_list, NULL, 0); } else { - tv_list_append_string(rettv->vval.v_list, - (const char *)regmatch.startp[i], + tv_list_append_string(rettv->vval.v_list, regmatch.startp[i], (regmatch.endp[i] - regmatch.startp[i])); } } @@ -4545,7 +4680,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, if (l != NULL) { tv_copy(TV_LIST_ITEM_TV(li), rettv); } else { - rettv->vval.v_string = xmemdupz((const char *)regmatch.startp[0], + rettv->vval.v_string = xmemdupz(regmatch.startp[0], (size_t)(regmatch.endp[0] - regmatch.startp[0])); } @@ -4674,7 +4809,7 @@ static void f_min(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "mkdir()" function static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - int prot = 0755; // -V536 + int prot = 0755; rettv->vval.v_number = FAIL; if (check_secure()) { @@ -4692,6 +4827,9 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) *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); @@ -4699,9 +4837,17 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } } - if (strcmp(tv_get_string(&argvars[1]), "p") == 0) { + 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); + 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); @@ -4709,10 +4855,27 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } rettv->vval.v_number = OK; - return; } } - rettv->vval.v_number = vim_mkdir_emsg(dir, prot); + 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 @@ -4732,6 +4895,50 @@ static void f_mode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->v_type = VAR_STRING; } +static void may_add_state_char(garray_T *gap, const char *include, uint8_t c) +{ + if (include == NULL || vim_strchr(include, c) != NULL) { + ga_append(gap, c); + } +} + +/// "state()" function +static void f_state(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + garray_T ga; + ga_init(&ga, 1, 20); + const char *include = NULL; + + if (argvars[0].v_type != VAR_UNKNOWN) { + include = tv_get_string(&argvars[0]); + } + + if (!(stuff_empty() && typebuf.tb_len == 0 && !using_script())) { + may_add_state_char(&ga, include, 'm'); + } + if (op_pending()) { + may_add_state_char(&ga, include, 'o'); + } + if (autocmd_busy) { + may_add_state_char(&ga, include, 'x'); + } + if (ins_compl_active()) { + may_add_state_char(&ga, include, 'a'); + } + if (!get_was_safe_state()) { + may_add_state_char(&ga, include, 'S'); + } + for (int i = 0; i < get_callback_depth() && i < 3; i++) { + may_add_state_char(&ga, include, 'c'); + } + if (msg_scrolled > 0) { + may_add_state_char(&ga, include, 's'); + } + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = ga.ga_data; +} + /// "msgpackdump()" function static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL @@ -4755,7 +4962,7 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN]; int idx = 0; TV_LIST_ITER(list, li, { - vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx); + vim_snprintf(msgbuf, sizeof(msgbuf), msg, idx); idx++; if (encode_vim_to_msgpack(packer, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) { break; @@ -4790,9 +4997,10 @@ static int msgpackparse_convert_item(const msgpack_object data, const msgpack_un tv_list_append_owned_tv(ret_list, tv); return OK; } - default: + case MSGPACK_UNPACK_EXTRA_BYTES: abort(); } + UNREACHABLE; } static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret_list) @@ -4813,7 +5021,7 @@ static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret } msgpack_unpacked unpacked; msgpack_unpacked_init(&unpacked); - do { + while (true) { if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) { emsg(_(e_outofmem)); goto end; @@ -4843,7 +5051,7 @@ static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret if (rlret == OK) { break; } - } while (true); + } end: msgpack_unpacker_free(unpacker); @@ -4928,7 +5136,7 @@ static void f_nr2char(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - char buf[MB_MAXBYTES]; + char buf[MB_MAXCHAR]; const int len = utf_char2bytes((int)num, buf); rettv->v_type = VAR_STRING; @@ -5057,7 +5265,7 @@ static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, EvalFuncDa } callback_free(&buf->b_prompt_interrupt); - buf->b_prompt_interrupt= interrupt_callback; + buf->b_prompt_interrupt = interrupt_callback; } /// "prompt_getprompt({buffer})" function @@ -5213,10 +5421,10 @@ static void f_rand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) goto theend; } - typval_T *const tvx = TV_LIST_ITEM_TV(tv_list_find(l, 0L)); - typval_T *const tvy = TV_LIST_ITEM_TV(tv_list_find(l, 1L)); - typval_T *const tvz = TV_LIST_ITEM_TV(tv_list_find(l, 2L)); - typval_T *const tvw = TV_LIST_ITEM_TV(tv_list_find(l, 3L)); + typval_T *const tvx = TV_LIST_ITEM_TV(tv_list_find(l, 0)); + typval_T *const tvy = TV_LIST_ITEM_TV(tv_list_find(l, 1)); + typval_T *const tvz = TV_LIST_ITEM_TV(tv_list_find(l, 2)); + typval_T *const tvw = TV_LIST_ITEM_TV(tv_list_find(l, 3)); if (tvx->v_type != VAR_NUMBER) { goto theend; } @@ -5344,7 +5552,7 @@ static varnumber_T readdir_checkitem(void *context, const char *name) argv[0].vval.v_string = (char *)name; typval_T rettv; - if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) { + if (eval_expr_typval(expr, false, argv, 1, &rettv) == FAIL) { goto theend; } @@ -5388,18 +5596,27 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl 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 prevlen = 0; // length of data in prev ptrdiff_t prevsize = 0; // size of prev buffer - long maxline = MAXLNUM; + int64_t maxline = MAXLNUM; + off_T offset = 0; + off_T size = -1; if (argvars[1].v_type != VAR_UNKNOWN) { - 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 (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]); + } } } @@ -5418,11 +5635,8 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl if (blob) { tv_blob_alloc_ret(rettv); - if (!read_blob(fd, rettv->vval.v_blob)) { + if (read_blob(fd, rettv, offset, size) == FAIL) { semsg(_(e_notread), fname); - // An empty blob is returned on error. - tv_blob_free(rettv->vval.v_blob); - rettv->vval.v_blob = NULL; } fclose(fd); return; @@ -5444,7 +5658,7 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); p++) { if (readlen <= 0 || *p == '\n') { - char *s = NULL; + char *s = NULL; size_t len = (size_t)(p - start); // Finished a line. Remove CRs before NL. @@ -5461,7 +5675,7 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl } if (prevlen == 0) { assert(len < INT_MAX); - s = xstrnsave(start, len); + 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 @@ -5500,11 +5714,11 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl // 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; + : 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; + : (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; @@ -5517,9 +5731,9 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl // have to shuffle buf to close gap int adjust_prevlen = 0; - if (dest < buf) { // -V782 + if (dest < buf) { // adjust_prevlen must be 1 or 2. - adjust_prevlen = (int)(buf - dest); // -V782 + adjust_prevlen = (int)(buf - dest); dest = buf; } if (readlen > p - buf + 1) { @@ -5547,7 +5761,7 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl prevsize = p - start; } else { ptrdiff_t grow50pc = (prevsize * 3) / 2; - ptrdiff_t growmin = (p - start) * 2 + prevlen; + ptrdiff_t growmin = (p - start) * 2 + prevlen; prevsize = grow50pc > growmin ? grow50pc : growmin; } prev = xrealloc(prev, (size_t)prevsize); @@ -5654,8 +5868,8 @@ static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL } bool error = false; - varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0L, &error); - varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1L, &error); + varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0, &error); + varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1, &error); if (error) { return FAIL; } @@ -5769,6 +5983,37 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) while (n-- > 0) { tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL); } + } else if (argvars[0].v_type == VAR_BLOB) { + tv_blob_alloc_ret(rettv); + if (argvars[0].vval.v_blob == NULL || n <= 0) { + return; + } + + const int slen = argvars[0].vval.v_blob->bv_ga.ga_len; + const int len = (int)(slen * n); + if (len <= 0) { + return; + } + + ga_grow(&rettv->vval.v_blob->bv_ga, len); + + rettv->vval.v_blob->bv_ga.ga_len = len; + + int i; + for (i = 0; i < slen; i++) { + if (tv_blob_get(argvars[0].vval.v_blob, i) != 0) { + break; + } + } + + if (i == slen) { + // No need to copy since all bytes are already zero + return; + } + + for (i = 0; i < n; i++) { + tv_blob_set_range(rettv->vval.v_blob, i * slen, (i + 1) * slen - 1, argvars); + } } else { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -5842,8 +6087,8 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) char *const buf = xmallocz(MAXPATHL); char *cpy; - for (;;) { - for (;;) { + while (true) { + while (true) { len = readlink(p, buf, MAXPATHL); if (len <= 0) { break; @@ -5860,13 +6105,13 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } // Ensure that the result will have a trailing path separator - // if the argument has one. */ + // 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. */ + // concatenate the remainders. q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf); if (*q != NUL) { cpy = remain; @@ -5880,7 +6125,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) q = path_tail(p); if (q > p && *q == NUL) { // Ignore trailing path separator. - q[-1] = NUL; + p[q - p - 1] = NUL; q = path_tail(p); } if (q > p && !path_is_absolute(buf)) { @@ -5959,7 +6204,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } # else char *v = os_realpath(fname, NULL); - rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v); + rettv->vval.v_string = v == NULL ? xstrdup(fname) : v; # endif #endif @@ -5969,6 +6214,10 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "reverse({list})" function static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { + if (tv_check_for_string_or_list_or_blob_arg(argvars, 0) == FAIL) { + return; + } + if (argvars[0].v_type == VAR_BLOB) { blob_T *const b = argvars[0].vval.v_blob; const int len = tv_blob_len(b); @@ -5979,9 +6228,14 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) tv_blob_set(b, len - i - 1, tmp); } tv_blob_set_ret(rettv, b); - } else if (argvars[0].v_type != VAR_LIST) { - semsg(_(e_listblobarg), "reverse()"); - } else { + } else if (argvars[0].v_type == VAR_STRING) { + rettv->v_type = VAR_STRING; + if (argvars[0].vval.v_string != NULL) { + rettv->vval.v_string = reverse_text(argvars[0].vval.v_string); + } else { + rettv->vval.v_string = NULL; + } + } else if (argvars[0].v_type == VAR_LIST) { list_T *const l = argvars[0].vval.v_list; if (!value_check_lock(tv_list_locked(l), N_("reverse() argument"), TV_TRANSLATE)) { @@ -5991,102 +6245,181 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// "reduce(list, { accumulator, element -> value } [, initial])" function -static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +/// Implementation of reduce() for list "argvars[0]", using the function "expr" +/// starting with the optional initial value argvars[2] and return the result in +/// "rettv". +static void reduce_list(typval_T *argvars, typval_T *expr, typval_T *rettv) { - if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) { - emsg(_(e_listblobreq)); - return; - } + list_T *const l = argvars[0].vval.v_list; + const int called_emsg_start = called_emsg; - const char *func_name; - partial_T *partial = NULL; - if (argvars[1].v_type == VAR_FUNC) { - func_name = argvars[1].vval.v_string; - } else if (argvars[1].v_type == VAR_PARTIAL) { - partial = argvars[1].vval.v_partial; - func_name = partial_name(partial); + typval_T initial; + const listitem_T *li = NULL; + if (argvars[2].v_type == VAR_UNKNOWN) { + if (tv_list_len(l) == 0) { + semsg(_(e_reduceempty), "List"); + return; + } + const listitem_T *const first = tv_list_first(l); + initial = *TV_LIST_ITEM_TV(first); + li = TV_LIST_ITEM_NEXT(l, first); } else { - func_name = tv_get_string(&argvars[1]); + initial = argvars[2]; + li = tv_list_first(l); } - if (*func_name == NUL) { - return; // type error or empty name + + tv_copy(&initial, rettv); + + if (l == NULL) { + return; } - funcexe_T funcexe = FUNCEXE_INIT; - funcexe.fe_evaluate = true; - funcexe.fe_partial = partial; + const VarLockStatus prev_locked = tv_list_locked(l); - typval_T initial; - typval_T argv[3]; - if (argvars[0].v_type == VAR_LIST) { - list_T *const l = argvars[0].vval.v_list; - const listitem_T *li; + tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here + for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + typval_T argv[3]; + argv[0] = *rettv; + argv[1] = *TV_LIST_ITEM_TV(li); + rettv->v_type = VAR_UNKNOWN; - if (argvars[2].v_type == VAR_UNKNOWN) { - if (tv_list_len(l) == 0) { - semsg(_(e_reduceempty), "List"); - return; - } - const listitem_T *const first = tv_list_first(l); - initial = *TV_LIST_ITEM_TV(first); - li = TV_LIST_ITEM_NEXT(l, first); - } else { - initial = argvars[2]; - li = tv_list_first(l); + const int r = eval_expr_typval(expr, true, argv, 2, rettv); + + tv_clear(&argv[0]); + if (r == FAIL || called_emsg != called_emsg_start) { + break; } + } + tv_list_set_lock(l, prev_locked); +} - tv_copy(&initial, rettv); +/// Implementation of reduce() for String "argvars[0]" using the function "expr" +/// starting with the optional initial value "argvars[2]" and return the result +/// in "rettv". +static void reduce_string(typval_T *argvars, typval_T *expr, typval_T *rettv) +{ + const char *p = tv_get_string(&argvars[0]); + int len; + const int called_emsg_start = called_emsg; - if (l != NULL) { - const VarLockStatus prev_locked = tv_list_locked(l); - const int called_emsg_start = called_emsg; - - tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here - for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { - argv[0] = *rettv; - argv[1] = *TV_LIST_ITEM_TV(li); - rettv->v_type = VAR_UNKNOWN; - const int r = call_func((char *)func_name, -1, rettv, 2, argv, &funcexe); - tv_clear(&argv[0]); - if (r == FAIL || called_emsg != called_emsg_start) { - break; - } - } - tv_list_set_lock(l, prev_locked); + if (argvars[2].v_type == VAR_UNKNOWN) { + if (*p == NUL) { + semsg(_(e_reduceempty), "String"); + return; } + len = utfc_ptr2len(p); + *rettv = (typval_T){ + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = xmemdupz(p, (size_t)len), + }; + p += len; + } else if (tv_check_for_string_arg(argvars, 2) == FAIL) { + return; } else { - const blob_T *const b = argvars[0].vval.v_blob; - int i; + tv_copy(&argvars[2], rettv); + } - if (argvars[2].v_type == VAR_UNKNOWN) { - if (tv_blob_len(b) == 0) { - semsg(_(e_reduceempty), "Blob"); - return; - } - initial.v_type = VAR_NUMBER; - initial.vval.v_number = tv_blob_get(b, 0); - i = 1; - } else if (argvars[2].v_type != VAR_NUMBER) { - emsg(_(e_number_exp)); + for (; *p != NUL; p += len) { + typval_T argv[3]; + argv[0] = *rettv; + len = utfc_ptr2len(p); + argv[1] = (typval_T){ + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = xmemdupz(p, (size_t)len), + }; + + const int r = eval_expr_typval(expr, true, argv, 2, rettv); + + tv_clear(&argv[0]); + tv_clear(&argv[1]); + if (r == FAIL || called_emsg != called_emsg_start) { + break; + } + } +} + +/// Implementation of reduce() for Blob "argvars[0]" using the function "expr" +/// starting with the optional initial value "argvars[2]" and return the result +/// in "rettv". +static void reduce_blob(typval_T *argvars, typval_T *expr, typval_T *rettv) +{ + const blob_T *const b = argvars[0].vval.v_blob; + const int called_emsg_start = called_emsg; + + typval_T initial; + int i; + if (argvars[2].v_type == VAR_UNKNOWN) { + if (tv_blob_len(b) == 0) { + semsg(_(e_reduceempty), "Blob"); return; - } else { - initial = argvars[2]; - i = 0; } + initial = (typval_T){ + .v_type = VAR_NUMBER, + .v_lock = VAR_UNLOCKED, + .vval.v_number = tv_blob_get(b, 0), + }; + i = 1; + } else if (tv_check_for_number_arg(argvars, 2) == FAIL) { + return; + } else { + initial = argvars[2]; + i = 0; + } + + tv_copy(&initial, rettv); + for (; i < tv_blob_len(b); i++) { + typval_T argv[3]; + argv[0] = *rettv; + argv[1] = (typval_T){ + .v_type = VAR_NUMBER, + .v_lock = VAR_UNLOCKED, + .vval.v_number = tv_blob_get(b, i), + }; - tv_copy(&initial, rettv); - for (; i < tv_blob_len(b); i++) { - argv[0] = *rettv; - argv[1].v_type = VAR_NUMBER; - argv[1].vval.v_number = tv_blob_get(b, i); - if (call_func((char *)func_name, -1, rettv, 2, argv, &funcexe) == FAIL) { - return; - } + const int r = eval_expr_typval(expr, true, argv, 2, rettv); + + if (r == FAIL || called_emsg != called_emsg_start) { + return; } } } +/// "reduce(list, { accumulator, element -> value } [, initial])" function +/// "reduce(blob, { accumulator, element -> value } [, initial])" function +/// "reduce(string, { accumulator, element -> value } [, initial])" function +static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (argvars[0].v_type != VAR_STRING + && argvars[0].v_type != VAR_LIST + && argvars[0].v_type != VAR_BLOB) { + emsg(_(e_string_list_or_blob_required)); + return; + } + + const char *func_name; + if (argvars[1].v_type == VAR_FUNC) { + func_name = argvars[1].vval.v_string; + } else if (argvars[1].v_type == VAR_PARTIAL) { + func_name = partial_name(argvars[1].vval.v_partial); + } else { + func_name = tv_get_string(&argvars[1]); + } + if (func_name == NULL || *func_name == NUL) { + emsg(_(e_missing_function_argument)); + return; + } + + if (argvars[0].v_type == VAR_LIST) { + reduce_list(argvars, &argvars[1], rettv); + } else if (argvars[0].v_type == VAR_STRING) { + reduce_string(argvars, &argvars[1], rettv); + } else { + reduce_blob(argvars, &argvars[1], rettv); + } +} + #define SP_NOMOVE 0x01 ///< don't move cursor #define SP_REPEAT 0x02 ///< repeat to find outer pair #define SP_RETCOUNT 0x04 ///< return matchcount @@ -6164,8 +6497,8 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) { bool save_p_ws = p_ws; int retval = 0; // default: FAIL - long lnum_stop = 0; - long time_limit = 0; + linenr_T lnum_stop = 0; + int64_t time_limit = 0; int options = SEARCH_KEEP; bool use_skip = false; @@ -6187,7 +6520,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) // Optional arguments: line number to stop searching, timeout and skip. if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { - lnum_stop = tv_get_number_chk(&argvars[2], NULL); + lnum_stop = (linenr_T)tv_get_number_chk(&argvars[2], NULL); if (lnum_stop < 0) { goto theend; } @@ -6217,14 +6550,14 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) pos_T pos = save_cursor = curwin->w_cursor; pos_T firstpos = { 0 }; searchit_arg_T sia = { - .sa_stop_lnum = (linenr_T)lnum_stop, + .sa_stop_lnum = lnum_stop, .sa_tm = &tm, }; int subpatnum; // Repeat until {skip} returns false. - for (;;) { + while (true) { subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char *)pat, 1, options, RE_SEARCH, &sia); // finding the first match again means there is no match where {skip} @@ -6362,6 +6695,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) sctx_T save_current_sctx; char *save_autocmd_fname, *save_autocmd_match; + bool save_autocmd_fname_full; int save_autocmd_bufnr; funccal_entry_T funccal_entry; @@ -6371,6 +6705,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) save_current_sctx = current_sctx; save_autocmd_fname = autocmd_fname; save_autocmd_match = autocmd_match; + save_autocmd_fname_full = autocmd_fname_full; save_autocmd_bufnr = autocmd_bufnr; save_funccal(&funccal_entry); @@ -6379,6 +6714,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ((estack_T *)exestack.ga_data)[exestack.ga_len++] = provider_caller_scope.es_entry; autocmd_fname = provider_caller_scope.autocmd_fname; autocmd_match = provider_caller_scope.autocmd_match; + autocmd_fname_full = provider_caller_scope.autocmd_fname_full; autocmd_bufnr = provider_caller_scope.autocmd_bufnr; set_current_funccal((funccall_T *)(provider_caller_scope.funccalp)); } @@ -6396,6 +6732,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) exestack.ga_len--; autocmd_fname = save_autocmd_fname; autocmd_match = save_autocmd_match; + autocmd_fname_full = save_autocmd_fname_full; autocmd_bufnr = save_autocmd_bufnr; restore_funccal(); } @@ -6404,7 +6741,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) const char *name = NULL; Channel *chan = find_channel(chan_id); if (chan) { - name = rpc_client_name(chan); + name = get_client_info(chan, "name"); } msg_ext_set_kind("rpc_error"); if (name) { @@ -6419,6 +6756,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (!object_to_vim(result, rettv, &err)) { + assert(ERROR_SET(&err)); semsg(_("Error converting the call result: %s"), err.msg); } @@ -6484,7 +6822,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // The last item of argv must be NULL argv[i] = NULL; - Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT, + Channel *chan = channel_job_start(argv, NULL, CALLBACK_READER_INIT, CALLBACK_READER_INIT, CALLBACK_NONE, false, true, false, false, kChannelStdinPipe, NULL, 0, 0, NULL, @@ -6555,7 +6893,9 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) { c = -1; } else { - c = utf_ptr2char((char *)grid->chars[grid->line_offset[row] + (size_t)col]); + char buf[MAX_SCHAR_SIZE + 1]; + schar_get(buf, grid_getchar(grid, row, col, NULL)); + c = utf_ptr2char(buf); } rettv->vval.v_number = c; } @@ -6569,21 +6909,22 @@ static void f_screenchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ScreenGrid *grid; screenchar_adjust(&grid, &row, &col); + tv_list_alloc_ret(rettv, kListLenMayKnow); if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) { - tv_list_alloc_ret(rettv, 0); return; } - int pcc[MAX_MCO]; - int c = utfc_ptr2char((char *)grid->chars[grid->line_offset[row] + (size_t)col], pcc); - int composing_len = 0; - while (pcc[composing_len] != 0) { - composing_len++; - } - tv_list_alloc_ret(rettv, composing_len + 1); - tv_list_append_number(rettv->vval.v_list, c); - for (int i = 0; i < composing_len; i++) { - tv_list_append_number(rettv->vval.v_list, pcc[i]); - } + + char buf[MAX_SCHAR_SIZE + 1]; + schar_get(buf, grid_getchar(grid, row, col, NULL)); + + // schar values are already processed chars which are always NUL-terminated. + // A single [0] is expected when char is NUL. + size_t i = 0; + do { + int c = utf_ptr2char(buf + i); + tv_list_append_number(rettv->vval.v_list, c); + i += (size_t)utf_ptr2len(buf + i); + } while (buf[i] != NUL); } /// "screencol()" function @@ -6616,7 +6957,9 @@ static void f_screenstring(typval_T *argvars, typval_T *rettv, EvalFuncData fptr return; } - rettv->vval.v_string = xstrdup((char *)grid->chars[grid->line_offset[row] + (size_t)col]); + char buf[MAX_SCHAR_SIZE + 1]; + schar_get(buf, grid_getchar(grid, row, col, NULL)); + rettv->vval.v_string = xstrdup(buf); } /// "search()" function @@ -6655,8 +6998,8 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) bool save_p_ws = p_ws; int flags = 0; int retval = 0; // default: FAIL - long lnum_stop = 0; - long time_limit = 0; + linenr_T lnum_stop = 0; + int64_t time_limit = 0; // Get the three pattern arguments: start, middle, end. Will result in an // error if not a valid argument. @@ -6698,7 +7041,7 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) skip = &argvars[4]; if (argvars[5].v_type != VAR_UNKNOWN) { - lnum_stop = tv_get_number_chk(&argvars[5], NULL); + lnum_stop = (linenr_T)tv_get_number_chk(&argvars[5], NULL); if (lnum_stop < 0) { semsg(_(e_invarg2), tv_get_string(&argvars[5])); goto theend; @@ -6713,8 +7056,8 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) } } - retval = (int)do_searchpair(spat, mpat, epat, dir, skip, - flags, match_pos, (linenr_T)lnum_stop, time_limit); + retval = do_searchpair(spat, mpat, epat, dir, skip, + flags, match_pos, lnum_stop, time_limit); theend: p_ws = save_p_ws; @@ -6759,19 +7102,19 @@ static void f_searchpairpos(typval_T *argvars, typval_T *rettv, EvalFuncData fpt /// @param time_limit stop after this many msec /// /// @returns 0 or -1 for no match, -long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir, - const typval_T *skip, int flags, pos_T *match_pos, linenr_T lnum_stop, - long time_limit) +int do_searchpair(const char *spat, const char *mpat, const char *epat, int dir, + const typval_T *skip, int flags, pos_T *match_pos, linenr_T lnum_stop, + int64_t time_limit) FUNC_ATTR_NONNULL_ARG(1, 2, 3) { - long retval = 0; + int retval = 0; int nest = 1; bool use_skip = false; int options = SEARCH_KEEP; // Make 'cpoptions' empty, the 'l' flag should not be used here. char *save_cpo = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; // Set the time limit, if there is one. proftime_T tm = profile_setlimit(time_limit); @@ -6804,13 +7147,13 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir pos_T foundpos; clearpos(&foundpos); char *pat = pat3; - for (;;) { + while (true) { searchit_arg_T sia = { .sa_stop_lnum = lnum_stop, .sa_tm = &tm, }; - int n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, + int n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1, options, RE_SEARCH, &sia); if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) { // didn't find it or found the first match again: FAIL @@ -6897,14 +7240,14 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir xfree(pat2); xfree(pat3); - if (p_cpo == empty_option) { + if (p_cpo == empty_string_option) { p_cpo = save_cpo; } else { // Darn, evaluating the {skip} expression changed the value. // If it's still empty it was changed and restored, need to restore in // the complicated way. if (*p_cpo == NUL) { - set_option_value_give_err("cpo", 0L, save_cpo, 0); + set_option_value_give_err("cpo", CSTR_AS_OPTVAL(save_cpo), 0); } free_string_option(save_cpo); } @@ -7011,7 +7354,7 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } /// Set the cursor or mark position. -/// If 'charpos' is true, then use the column number as a character offset. +/// If "charpos" is true, then use the column number as a character offset. /// Otherwise use the column number as a byte offset. static void set_position(typval_T *argvars, typval_T *rettv, bool charpos) { @@ -7059,8 +7402,7 @@ static void f_setcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) static void f_setcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (argvars[0].v_type != VAR_DICT) { - emsg(_(e_dictreq)); + if (tv_check_for_dict_arg(argvars, 0) == FAIL) { return; } @@ -7071,8 +7413,7 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fpt char *const csearch = tv_dict_get_string(d, "char", false); if (csearch != NULL) { - int pcc[MAX_MCO]; - const int c = utfc_ptr2char(csearch, pcc); + int c = utf_ptr2char(csearch); set_last_csearch(c, csearch, utfc_ptr2len(csearch)); } @@ -7100,6 +7441,13 @@ static void f_setenv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) char valbuf[NUMBUFLEN]; const char *name = tv_get_string_buf(&argvars[0], namebuf); + // setting an environment variable may be dangerous, e.g. you could + // setenv GCONV_PATH=/tmp and then have iconv() unexpectedly call + // a shell command using some shared library: + if (check_secure()) { + return; + } + if (argvars[1].v_type == VAR_SPECIAL && argvars[1].vval.v_special == kSpecialVarNull) { vim_unsetenv_ext(name); @@ -7146,7 +7494,7 @@ static void f_setpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } /// Translate a register type string to the yank type and block length -static int get_yank_type(char **const pp, MotionType *const yank_type, long *const block_len) +static int get_yank_type(char **const pp, MotionType *const yank_type, int *const block_len) FUNC_ATTR_NONNULL_ALL { char *stropt = *pp; @@ -7164,7 +7512,7 @@ static int get_yank_type(char **const pp, MotionType *const yank_type, long *con *yank_type = kMTBlockWise; if (ascii_isdigit(stropt[1])) { stropt++; - *block_len = getdigits_long(&stropt, false, 0) - 1; + *block_len = getdigits_int(&stropt, false, 0) - 1; stropt--; } break; @@ -7180,7 +7528,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool append = false; - long block_len = -1; + int block_len = -1; MotionType yank_type = kMTUnknown; rettv->vval.v_number = 1; // FAIL is default. @@ -7319,7 +7667,7 @@ free_lstval: /// "settagstack()" function static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - static char *e_invact2 = N_("E962: Invalid action: '%s'"); + static const char *e_invact2 = N_("E962: Invalid action: '%s'"); char action = 'r'; rettv->vval.v_number = -1; @@ -7331,8 +7679,7 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } // second argument: dict with items to set in the tag stack - if (argvars[1].v_type != VAR_DICT) { - emsg(_(e_dictreq)); + if (tv_check_for_dict_arg(argvars, 1) == FAIL) { return; } dict_T *d = argvars[1].vval.v_dict; @@ -7343,8 +7690,10 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // third argument: action - 'a' for append and 'r' for replace. // default is to replace the stack. if (argvars[2].v_type == VAR_UNKNOWN) { - action = 'r'; - } else if (argvars[2].v_type == VAR_STRING) { + // action = 'r'; + } else if (tv_check_for_string_arg(argvars, 2) == FAIL) { + return; + } else { const char *actstr; actstr = tv_get_string_chk(&argvars[2]); if (actstr == NULL) { @@ -7357,9 +7706,6 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) semsg(_(e_invact2), actstr); return; } - } else { - emsg(_(e_stringreq)); - return; } if (set_tagstack(wp, d, action) == OK) { @@ -7394,11 +7740,11 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = 0; if (argvars[0].v_type != VAR_UNKNOWN) { - long col = (long)tv_get_number_chk(argvars, NULL); + colnr_T col = (colnr_T)tv_get_number_chk(argvars, NULL); if (col < 0) { return; // type error; errmsg already given } - rettv->vval.v_number = get_sw_value_col(curbuf, (colnr_T)col); + rettv->vval.v_number = get_sw_value_col(curbuf, col); return; } rettv->vval.v_number = get_sw_value(curbuf); @@ -7526,7 +7872,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, EvalFuncData fptr const int wo_spell_save = curwin->w_p_spell; if (!curwin->w_p_spell) { - did_set_spelllang(curwin); + parse_spelllang(curwin); curwin->w_p_spell = true; } @@ -7570,10 +7916,11 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, EvalFuncData fptr tv_list_alloc_ret(rettv, 2); tv_list_append_string(rettv->vval.v_list, word, (ssize_t)len); tv_list_append_string(rettv->vval.v_list, - (attr == HLF_SPB ? "bad" : - attr == HLF_SPR ? "rare" : - attr == HLF_SPL ? "local" : - attr == HLF_SPC ? "caps" : NULL), -1); + (attr == HLF_SPB + ? "bad" : (attr == HLF_SPR + ? "rare" : (attr == HLF_SPL + ? "local" : (attr == HLF_SPC + ? "caps" : NULL)))), -1); } /// "spellsuggest()" function @@ -7583,7 +7930,7 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr const int wo_spell_save = curwin->w_p_spell; if (!curwin->w_p_spell) { - did_set_spelllang(curwin); + parse_spelllang(curwin); curwin->w_p_spell = true; } @@ -7632,7 +7979,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // Make 'cpoptions' empty, the 'l' flag should not be used here. char *save_cpo = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; const char *str = tv_get_string(&argvars[0]); const char *pat = NULL; @@ -7657,7 +8004,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } regmatch_T regmatch = { - .regprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING), + .regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING), .startp = { NULL }, .endp = { NULL }, .rm_ic = false, @@ -7668,18 +8015,18 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (*str == NUL) { match = false; // Empty item at the end. } else { - match = vim_regexec_nl(®match, (char *)str, col); + match = vim_regexec_nl(®match, str, col); } const char *end; if (match) { - end = (const char *)regmatch.startp[0]; + end = regmatch.startp[0]; } else { end = str + strlen(str); } if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0 && *str != NUL && match - && end < (const char *)regmatch.endp[0])) { + && end < regmatch.endp[0])) { tv_list_append_string(rettv->vval.v_list, str, end - str); } if (!match) { @@ -7692,7 +8039,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // Don't get stuck at the same match. col = utfc_ptr2len(regmatch.endp[0]); } - str = (const char *)regmatch.endp[0]; + str = regmatch.endp[0]; } vim_regfree(regmatch.regprog); @@ -7750,60 +8097,6 @@ static void f_str2float(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->v_type = VAR_FLOAT; } -/// "str2list()" function -static void f_str2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - tv_list_alloc_ret(rettv, kListLenUnknown); - const char *p = tv_get_string(&argvars[0]); - - for (; *p != NUL; p += utf_ptr2len(p)) { - tv_list_append_number(rettv->vval.v_list, utf_ptr2char(p)); - } -} - -/// "str2nr()" function -static void f_str2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int base = 10; - int what = 0; - - if (argvars[1].v_type != VAR_UNKNOWN) { - base = (int)tv_get_number(&argvars[1]); - if (base != 2 && base != 8 && base != 10 && base != 16) { - emsg(_(e_invarg)); - return; - } - if (argvars[2].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[2])) { - what |= STR2NR_QUOTE; - } - } - - char *p = skipwhite(tv_get_string(&argvars[0])); - bool isneg = (*p == '-'); - if (*p == '+' || *p == '-') { - p = skipwhite(p + 1); - } - switch (base) { - case 2: - what |= STR2NR_BIN | STR2NR_FORCE; - break; - case 8: - what |= STR2NR_OCT | STR2NR_OOCT | STR2NR_FORCE; - break; - case 16: - what |= STR2NR_HEX | STR2NR_FORCE; - break; - } - varnumber_T n; - vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, false); - // Text after the number is silently ignored. - if (isneg) { - rettv->vval.v_number = -n; - } else { - rettv->vval.v_number = n; - } -} - /// "strftime({format}[, {time}])" function static void f_strftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -7823,263 +8116,35 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // MSVC returns NULL for an invalid value of seconds. if (curtime_ptr == NULL) { rettv->vval.v_string = xstrdup(_("(Invalid)")); - } else { - vimconv_T conv; - - conv.vc_type = CONV_NONE; - char *enc = enc_locale(); - convert_setup(&conv, p_enc, enc); - if (conv.vc_type != CONV_NONE) { - p = string_convert(&conv, p, NULL); - } - char result_buf[256]; - if (p == NULL || strftime(result_buf, sizeof(result_buf), p, curtime_ptr) == 0) { - result_buf[0] = NUL; - } - - if (conv.vc_type != CONV_NONE) { - xfree(p); - } - convert_setup(&conv, enc, p_enc); - if (conv.vc_type != CONV_NONE) { - rettv->vval.v_string = string_convert(&conv, result_buf, NULL); - } else { - rettv->vval.v_string = xstrdup(result_buf); - } - - // Release conversion descriptors. - convert_setup(&conv, NULL, NULL); - xfree(enc); - } -} - -/// "strgetchar()" function -static void f_strgetchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = -1; - - const char *const str = tv_get_string_chk(&argvars[0]); - if (str == NULL) { return; } - bool error = false; - varnumber_T charidx = tv_get_number_chk(&argvars[1], &error); - if (error) { - return; - } - - const size_t len = strlen(str); - size_t byteidx = 0; - - while (charidx >= 0 && byteidx < len) { - if (charidx == 0) { - rettv->vval.v_number = utf_ptr2char(str + byteidx); - break; - } - charidx--; - byteidx += (size_t)utf_ptr2len(str + byteidx); - } -} - -/// "stridx()" function -static void f_stridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = -1; - - char buf[NUMBUFLEN]; - const char *const needle = tv_get_string_chk(&argvars[1]); - const char *haystack = tv_get_string_buf_chk(&argvars[0], buf); - const char *const haystack_start = haystack; - if (needle == NULL || haystack == NULL) { - return; // Type error; errmsg already given. - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - bool error = false; - - const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], - &error); - if (error || start_idx >= (ptrdiff_t)strlen(haystack)) { - return; - } - if (start_idx >= 0) { - haystack += start_idx; - } - } - - const char *pos = strstr(haystack, needle); - if (pos != NULL) { - rettv->vval.v_number = (varnumber_T)(pos - haystack_start); - } -} - -/// "string()" function -static void f_string(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = encode_tv2string(&argvars[0], NULL); -} - -/// "strlen()" function -static void f_strlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0])); -} - -static void strchar_common(typval_T *argvars, typval_T *rettv, bool skipcc) -{ - const char *s = tv_get_string(&argvars[0]); - varnumber_T len = 0; - int (*func_mb_ptr2char_adv)(const char **pp); - - func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv; - while (*s != NUL) { - func_mb_ptr2char_adv(&s); - len++; - } - rettv->vval.v_number = len; -} - -/// "strcharlen()" function -static void f_strcharlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - strchar_common(argvars, rettv, true); -} - -/// "strchars()" function -static void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int skipcc = false; - - if (argvars[1].v_type != VAR_UNKNOWN) { - skipcc = (int)tv_get_bool(&argvars[1]); - } - if (skipcc < 0 || skipcc > 1) { - semsg(_(e_using_number_as_bool_nr), skipcc); - } else { - strchar_common(argvars, rettv, skipcc); - } -} - -/// "strdisplaywidth()" function -static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *const s = tv_get_string(&argvars[0]); - int col = 0; - - if (argvars[1].v_type != VAR_UNKNOWN) { - col = (int)tv_get_number(&argvars[1]); - } - - rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char *)s) - col); -} - -/// "strwidth()" function -static void f_strwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *const s = tv_get_string(&argvars[0]); - - rettv->vval.v_number = (varnumber_T)mb_string2cells(s); -} -/// "strcharpart()" function -static void f_strcharpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *const p = tv_get_string(&argvars[0]); - const size_t slen = strlen(p); + vimconv_T conv; - int nbyte = 0; - bool error = false; - varnumber_T nchar = tv_get_number_chk(&argvars[1], &error); - if (!error) { - if (nchar > 0) { - while (nchar > 0 && (size_t)nbyte < slen) { - nbyte += utf_ptr2len(p + nbyte); - nchar--; - } - } else { - nbyte = (int)nchar; - } + conv.vc_type = CONV_NONE; + char *enc = enc_locale(); + convert_setup(&conv, p_enc, enc); + if (conv.vc_type != CONV_NONE) { + p = string_convert(&conv, p, NULL); } - int len = 0; - if (argvars[2].v_type != VAR_UNKNOWN) { - int charlen = (int)tv_get_number(&argvars[2]); - while (charlen > 0 && nbyte + len < (int)slen) { - int off = nbyte + len; - - if (off < 0) { - len += 1; - } else { - len += utf_ptr2len(p + off); - } - charlen--; - } - } else { - len = (int)slen - nbyte; // default: all bytes that are available. + char result_buf[256]; + if (p == NULL || strftime(result_buf, sizeof(result_buf), p, curtime_ptr) == 0) { + result_buf[0] = NUL; } - // Only return the overlap between the specified part and the actual - // string. - if (nbyte < 0) { - len += nbyte; - nbyte = 0; - } else if ((size_t)nbyte > slen) { - nbyte = (int)slen; - } - if (len < 0) { - len = 0; - } else if (nbyte + len > (int)slen) { - len = (int)slen - nbyte; + if (conv.vc_type != CONV_NONE) { + xfree(p); } - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xstrndup(p + nbyte, (size_t)len); -} - -/// "strpart()" function -static void f_strpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - bool error = false; - - const char *const p = tv_get_string(&argvars[0]); - const size_t slen = strlen(p); - - varnumber_T n = tv_get_number_chk(&argvars[1], &error); - varnumber_T len; - if (error) { - len = 0; - } else if (argvars[2].v_type != VAR_UNKNOWN) { - len = tv_get_number(&argvars[2]); + convert_setup(&conv, enc, p_enc); + if (conv.vc_type != CONV_NONE) { + rettv->vval.v_string = string_convert(&conv, result_buf, NULL); } else { - len = (varnumber_T)slen - n; // Default len: all bytes that are available. - } - - // Only return the overlap between the specified part and the actual - // string. - if (n < 0) { - len += n; - n = 0; - } else if (n > (varnumber_T)slen) { - n = (varnumber_T)slen; - } - if (len < 0) { - len = 0; - } else if (n + len > (varnumber_T)slen) { - len = (varnumber_T)slen - n; - } - - if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) { - int off; - - // length in characters - for (off = (int)n; off < (int)slen && len > 0; len--) { - off += utfc_ptr2len(p + off); - } - len = off - n; + rettv->vval.v_string = xstrdup(result_buf); } - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xmemdupz(p + n, (size_t)len); + // Release conversion descriptors. + convert_setup(&conv, NULL, NULL); + xfree(enc); } /// "strptime({format}, {timestring})" function @@ -8114,56 +8179,6 @@ static void f_strptime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) xfree(enc); } -/// "strridx()" function -static void f_strridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - char buf[NUMBUFLEN]; - const char *const needle = tv_get_string_chk(&argvars[1]); - const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf); - - rettv->vval.v_number = -1; - if (needle == NULL || haystack == NULL) { - return; // Type error; errmsg already given. - } - - const size_t haystack_len = strlen(haystack); - ptrdiff_t end_idx; - if (argvars[2].v_type != VAR_UNKNOWN) { - // Third argument: upper limit for index. - end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL); - if (end_idx < 0) { - return; // Can never find a match. - } - } else { - end_idx = (ptrdiff_t)haystack_len; - } - - const char *lastmatch = NULL; - if (*needle == NUL) { - // Empty string matches past the end. - lastmatch = haystack + end_idx; - } else { - for (const char *rest = haystack; *rest != NUL; rest++) { - rest = strstr(rest, needle); - if (rest == NULL || rest > haystack + end_idx) { - break; - } - lastmatch = rest; - } - } - - if (lastmatch != NULL) { - rettv->vval.v_number = (varnumber_T)(lastmatch - haystack); - } -} - -/// "strtrans()" function -static void f_strtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = transstr(tv_get_string(&argvars[0]), true); -} - /// "submatch()" function static void f_submatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -8174,7 +8189,7 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (no < 0 || no >= NSUBEXP) { - semsg(_("E935: invalid submatch number: %d"), no); + semsg(_(e_invalid_submatch_number_nr), no); return; } int retList = 0; @@ -8224,11 +8239,18 @@ static void f_substitute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } +/// "swapfilelist()" function +static void f_swapfilelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_list_alloc_ret(rettv, kListLenUnknown); + recover_names(NULL, false, rettv->vval.v_list, 0, NULL); +} + /// "swapinfo(swap_filename)" function static void f_swapinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); - get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict); + swapfile_dict(tv_get_string(argvars), rettv->vval.v_dict); } /// "swapname(expr)" function @@ -8348,7 +8370,7 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char *)(p == NULL ? p : xstrdup(p)); + rettv->vval.v_string = p == NULL ? NULL : xstrdup(p); } /// "synIDtrans(id)" function @@ -8391,8 +8413,8 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, EvalFuncData fptr cchar = syn_get_sub_char(); if (cchar == NUL && curwin->w_p_cole == 1) { cchar = (curwin->w_p_lcs_chars.conceal == NUL) - ? ' ' - : curwin->w_p_lcs_chars.conceal; + ? ' ' + : curwin->w_p_lcs_chars.conceal; } if (cchar != NUL) { utf_char2bytes(cchar, str); @@ -8431,7 +8453,7 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// f_system - the VimL system() function +/// f_system - the Vimscript system() function static void f_system(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_system_output_as_rettv(argvars, rettv, false); @@ -8512,7 +8534,10 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (check_secure()) { return; } - + if (text_locked()) { + text_locked_msg(); + return; + } if (curbuf->b_changed) { emsg(_("Can only call this function in an unmodified buffer")); return; @@ -8579,7 +8604,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) const bool detach = false; ChannelStdinMode stdin_mode = kChannelStdinPipe; uint16_t term_width = (uint16_t)MAX(0, curwin->w_width_inner - win_col_off(curwin)); - Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, + Channel *chan = channel_job_start(argv, NULL, on_stdout, on_stderr, on_exit, pty, rpc, overlapped, detach, stdin_mode, cwd, term_width, (uint16_t)curwin->w_height_inner, env, &rettv->vval.v_number); @@ -8625,21 +8650,24 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) INTEGER_OBJ(pid), false, false, &err); api_clear_error(&err); + channel_incref(chan); channel_terminal_open(curbuf, chan); channel_create_event(chan, NULL); + channel_decref(chan); } /// "timer_info([timer])" function static void f_timer_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { + tv_list_alloc_ret(rettv, kListLenUnknown); + + if (tv_check_for_opt_number_arg(argvars, 0) == FAIL) { + return; + } + if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[0].v_type != VAR_NUMBER) { - emsg(_(e_number_exp)); - return; - } - tv_list_alloc_ret(rettv, 1); timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0])); - if (timer != NULL && !timer->stopped) { + if (timer != NULL && (!timer->stopped || timer->refcount > 1)) { add_timer_info(rettv, timer); } } else { @@ -8654,6 +8682,7 @@ static void f_timer_pause(typval_T *argvars, typval_T *unused, EvalFuncData fptr emsg(_(e_number_exp)); return; } + int paused = (bool)tv_get_number(&argvars[1]); timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0])); if (timer != NULL) { @@ -8678,11 +8707,10 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (argvars[2].v_type != VAR_UNKNOWN) { - dict_T *dict = argvars[2].vval.v_dict; - if (argvars[2].v_type != VAR_DICT || dict == NULL) { - semsg(_(e_invarg2), tv_get_string(&argvars[2])); + if (tv_check_for_nonnull_dict_arg(argvars, 2) == FAIL) { return; } + dict_T *dict = argvars[2].vval.v_dict; dictitem_T *const di = tv_dict_find(dict, S_LEN("repeat")); if (di != NULL) { repeat = (int)tv_get_number(&di->di_tv); @@ -8702,8 +8730,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "timer_stop(timerid)" function static void f_timer_stop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (argvars[0].v_type != VAR_NUMBER) { - emsg(_(e_number_exp)); + if (tv_check_for_number_arg(argvars, 0) == FAIL) { return; } @@ -8720,186 +8747,6 @@ static void f_timer_stopall(typval_T *argvars, typval_T *unused, EvalFuncData fp timer_stop_all(); } -/// "tolower(string)" function -static void f_tolower(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), false); -} - -/// "toupper(string)" function -static void f_toupper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), true); -} - -/// "tr(string, fromstr, tostr)" function -static void f_tr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - char buf[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - - const char *in_str = tv_get_string(&argvars[0]); - const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf); - const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2); - - // Default return value: empty string. - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (fromstr == NULL || tostr == NULL) { - return; // Type error; errmsg already given. - } - garray_T ga; - ga_init(&ga, (int)sizeof(char), 80); - - // fromstr and tostr have to contain the same number of chars. - bool first = true; - while (*in_str != NUL) { - const char *cpstr = in_str; - const int inlen = utfc_ptr2len(in_str); - int cplen = inlen; - int idx = 0; - int fromlen; - for (const char *p = fromstr; *p != NUL; p += fromlen) { - fromlen = utfc_ptr2len(p); - if (fromlen == inlen && strncmp(in_str, p, (size_t)inlen) == 0) { - int tolen; - for (p = tostr; *p != NUL; p += tolen) { - tolen = utfc_ptr2len(p); - if (idx-- == 0) { - cplen = tolen; - cpstr = (char *)p; - break; - } - } - if (*p == NUL) { // tostr is shorter than fromstr. - goto error; - } - break; - } - idx++; - } - - if (first && cpstr == in_str) { - // Check that fromstr and tostr have the same number of - // (multi-byte) characters. Done only once when a character - // of in_str doesn't appear in fromstr. - first = false; - int tolen; - for (const char *p = tostr; *p != NUL; p += tolen) { - tolen = utfc_ptr2len(p); - idx--; - } - if (idx != 0) { - goto error; - } - } - - ga_grow(&ga, cplen); - memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen); - ga.ga_len += cplen; - - in_str += inlen; - } - - // add a terminating NUL - ga_append(&ga, NUL); - - rettv->vval.v_string = ga.ga_data; - return; -error: - semsg(_(e_invarg2), fromstr); - ga_clear(&ga); -} - -/// "trim({expr})" function -static void f_trim(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - char buf1[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - const char *head = tv_get_string_buf_chk(&argvars[0], buf1); - const char *mask = NULL; - const char *prev; - const char *p; - int dir = 0; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (head == NULL) { - return; - } - - if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_STRING) { - semsg(_(e_invarg2), tv_get_string(&argvars[1])); - return; - } - - if (argvars[1].v_type == VAR_STRING) { - mask = tv_get_string_buf_chk(&argvars[1], buf2); - if (argvars[2].v_type != VAR_UNKNOWN) { - bool error = false; - // leading or trailing characters to trim - dir = (int)tv_get_number_chk(&argvars[2], &error); - if (error) { - return; - } - if (dir < 0 || dir > 2) { - semsg(_(e_invarg2), tv_get_string(&argvars[2])); - return; - } - } - } - - int c1; - if (dir == 0 || dir == 1) { - // Trim leading characters - while (*head != NUL) { - c1 = utf_ptr2char((char *)head); - if (mask == NULL) { - if (c1 > ' ' && c1 != 0xa0) { - break; - } - } else { - for (p = mask; *p != NUL; MB_PTR_ADV(p)) { - if (c1 == utf_ptr2char((char *)p)) { - break; - } - } - if (*p == NUL) { - break; - } - } - MB_PTR_ADV(head); - } - } - - const char *tail = head + strlen(head); - if (dir == 0 || dir == 2) { - // Trim trailing characters - for (; tail > head; tail = prev) { - prev = tail; - MB_PTR_BACK(head, prev); - c1 = utf_ptr2char((char *)prev); - if (mask == NULL) { - if (c1 > ' ' && c1 != 0xa0) { - break; - } - } else { - for (p = mask; *p != NUL; MB_PTR_ADV(p)) { - if (c1 == utf_ptr2char((char *)p)) { - break; - } - } - if (*p == NUL) { - break; - } - } - } - } - rettv->vval.v_string = xstrnsave(head, (size_t)(tail - head)); -} - /// "type(expr)" function static void f_type(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -8932,49 +8779,31 @@ static void f_type(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = n; } -/// "undofile(name)" function -static void f_undofile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +/// "virtcol({expr}, [, {list} [, {winid}]])" function +static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - rettv->v_type = VAR_STRING; - const char *const fname = tv_get_string(&argvars[0]); - - if (*fname == NUL) { - // If there is no file name there will be no undo file. - rettv->vval.v_string = NULL; - } else { - char *ffname = FullName_save(fname, true); + colnr_T vcol_start = 0; + colnr_T vcol_end = 0; + switchwin_T switchwin; + bool winchanged = false; - if (ffname != NULL) { - rettv->vval.v_string = u_get_undo_file_name(ffname, false); + if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { + // use the window specified in the third argument + tabpage_T *tp; + win_T *wp = win_id2wp_tp((int)tv_get_number(&argvars[2]), &tp); + if (wp == NULL || tp == NULL) { + goto theend; } - xfree(ffname); - } -} -/// "undotree()" function -static void f_undotree(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - tv_dict_alloc_ret(rettv); - - dict_T *dict = rettv->vval.v_dict; - - tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced); - tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last); - tv_dict_add_nr(dict, S_LEN("save_last"), - (varnumber_T)curbuf->b_u_save_nr_last); - tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)curbuf->b_u_seq_cur); - tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur); - tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur); + if (switch_win_noblock(&switchwin, wp, tp, true) != OK) { + goto theend; + } - tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); -} + check_cursor(); + winchanged = true; + } -/// "virtcol(string)" function -static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - colnr_T vcol = 0; int fnum = curbuf->b_fnum; - pos_T *fp = var2fpos(&argvars[0], false, &fnum, false); if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count && fnum == curbuf->b_fnum) { @@ -8987,11 +8816,23 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) fp->col = (colnr_T)len; } } - getvvcol(curwin, fp, NULL, NULL, &vcol); - vcol++; + getvvcol(curwin, fp, &vcol_start, NULL, &vcol_end); + vcol_start++; + vcol_end++; } - rettv->vval.v_number = vcol; +theend: + if (argvars[1].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[1])) { + tv_list_alloc_ret(rettv, 2); + tv_list_append_number(rettv->vval.v_list, vcol_start); + tv_list_append_number(rettv->vval.v_list, vcol_end); + } else { + rettv->vval.v_number = vcol_end; + } + + if (winchanged) { + restore_win_noblock(&switchwin, true); + } } /// "visualmode()" function @@ -9055,6 +8896,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 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) { @@ -9068,6 +8910,8 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) binary = true; break; case 'a': append = true; break; + case 'D': + defer = true; break; case 's': do_fsync = true; break; case 'S': @@ -9087,6 +8931,11 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (fname == NULL) { return; } + + if (defer && !can_add_defer()) { + return; + } + FileDescriptor fp; int error; if (*fname == NUL) { @@ -9095,9 +8944,17 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ((append ? kFileAppend : kFileTruncate) | (mkdir_p ? kFileMkDir : kFileCreate) | kFileCreate), 0666)) != 0) { - semsg(_("E482: Can't open file %s for writing: %s"), - fname, os_strerror(error)); + 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); |