diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2022-08-03 00:08:17 -0600 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2022-08-03 00:08:17 -0600 |
commit | 9449e1b8d273ff78eb894c588110ffa0c17d6ee3 (patch) | |
tree | 9e4470c33bd4187d9f42f0b2c4aaa995310c5be8 /src/nvim/eval | |
parent | 308e1940dcd64aa6c344c403d4f9e0dda58d9c5c (diff) | |
parent | b8dcbcc732baf84fc48d6b272c3ade0bcb129b3b (diff) | |
download | rneovim-9449e1b8d273ff78eb894c588110ffa0c17d6ee3.tar.gz rneovim-9449e1b8d273ff78eb894c588110ffa0c17d6ee3.tar.bz2 rneovim-9449e1b8d273ff78eb894c588110ffa0c17d6ee3.zip |
Merge remote-tracking branch 'upstream/master' into rahm
Diffstat (limited to 'src/nvim/eval')
-rw-r--r-- | src/nvim/eval/executor.c | 2 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 1186 | ||||
-rw-r--r-- | src/nvim/eval/funcs.h | 2 | ||||
-rw-r--r-- | src/nvim/eval/typval.c | 621 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.c | 5 | ||||
-rw-r--r-- | src/nvim/eval/vars.c | 1822 | ||||
-rw-r--r-- | src/nvim/eval/vars.h | 9 |
7 files changed, 2657 insertions, 990 deletions
diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index 3e66150180..b461456a3a 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -12,8 +12,6 @@ # include "eval/executor.c.generated.h" #endif -static char *e_letwrong = N_("E734: Wrong variable type for %s="); - char *e_listidx = N_("E684: list index out of range: %" PRId64); /// Handle tv1 += tv2, -=, *=, /=, %=, .= diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 7bed21e99b..691ccfe535 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -25,6 +25,7 @@ #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" +#include "nvim/eval/vars.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/file_search.h" @@ -36,6 +37,7 @@ #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/input.h" +#include "nvim/insexpand.h" #include "nvim/lua/executor.h" #include "nvim/macros.h" #include "nvim/mapping.h" @@ -213,12 +215,11 @@ int call_internal_method(const char_u *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; - memcpy(argv, argvars, base_index * sizeof(typval_T)); + const ptrdiff_t base_index = fdef->base_arg == BASE_LAST ? argcount : fdef->base_arg - 1; + memcpy(argv, argvars, (size_t)base_index * sizeof(typval_T)); argv[base_index] = *basetv; memcpy(argv + base_index + 1, argvars + base_index, - (argcount - base_index) * sizeof(typval_T)); + (size_t)(argcount - base_index) * sizeof(typval_T)); argv[argcount + 1].v_type = VAR_UNKNOWN; fdef->func(argv, rettv, fdef->data); @@ -326,7 +327,7 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) const varnumber_T n = tv_get_number_chk(&argvars[1], &error); if (!error) { - ga_append(&b->bv_ga, (int)n); + ga_append(&b->bv_ga, (char)n); tv_copy(&argvars[0], rettv); } } @@ -430,7 +431,7 @@ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; - int idx = tv_get_number_chk(&argvars[0], NULL); + int idx = (int)tv_get_number_chk(&argvars[0], NULL); if (arglist != NULL && idx >= 0 && idx < argcount) { rettv->vval.v_string = xstrdup((const char *)alist_name(&arglist[idx])); } else if (idx == -1) { @@ -831,7 +832,7 @@ static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } const char *error; - rettv->vval.v_number = channel_close(argvars[0].vval.v_number, part, &error); + rettv->vval.v_number = channel_close((uint64_t)argvars[0].vval.v_number, part, &error); if (!rettv->vval.v_number) { emsg(error); } @@ -859,7 +860,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) const blob_T *const b = argvars[1].vval.v_blob; input_len = tv_blob_len(b); if (input_len > 0) { - input = xmemdup(b->bv_ga.ga_data, input_len); + input = xmemdup(b->bv_ga.ga_data, (size_t)input_len); } } else { input = save_tv_as_string(&argvars[1], &input_len, false); @@ -870,9 +871,9 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) // or there is no input to send. return; } - uint64_t id = argvars[0].vval.v_number; + uint64_t id = (uint64_t)argvars[0].vval.v_number; const char *error = NULL; - rettv->vval.v_number = channel_send(id, input, input_len, true, &error); + rettv->vval.v_number = (varnumber_T)channel_send(id, input, (size_t)input_len, true, &error); if (error) { emsg(error); } @@ -1054,64 +1055,6 @@ static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_col(argvars, rettv, false); } -/// "complete()" function -static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if ((State & MODE_INSERT) == 0) { - emsg(_("E785: complete() can only be used in Insert mode")); - return; - } - - // Check for undo allowed here, because if something was already inserted - // the line was already saved for undo and this check isn't done. - if (!undo_allowed(curbuf)) { - return; - } - - if (argvars[1].v_type != VAR_LIST) { - emsg(_(e_invarg)); - } else { - const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL); - if (startcol > 0) { - set_completion(startcol - 1, argvars[1].vval.v_list); - } - } -} - -/// "complete_add()" function -static void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0, false); -} - -/// "complete_check()" function -static void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int saved = RedrawingDisabled; - - RedrawingDisabled = 0; - ins_compl_check_keys(0, true); - rettv->vval.v_number = compl_interrupted; - RedrawingDisabled = saved; -} - -/// "complete_info()" function -static void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - - list_T *what_list = NULL; - - if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[0].v_type != VAR_LIST) { - emsg(_(e_listreq)); - return; - } - what_list = argvars[0].vval.v_list; - } - get_complete_info(what_list, rettv->vval.v_dict); -} - /// "confirm(message, buttons[, default [, type]])" function static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -1134,7 +1077,7 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) error = true; } if (argvars[2].v_type != VAR_UNKNOWN) { - def = tv_get_number_chk(&argvars[2], &error); + def = (int)tv_get_number_chk(&argvars[2], &error); if (argvars[3].v_type != VAR_UNKNOWN) { typestr = tv_get_string_buf_chk(&argvars[3], buf2); if (typestr == NULL) { @@ -1181,7 +1124,7 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool error = false; if (argvars[2].v_type != VAR_UNKNOWN) { - ic = tv_get_number_chk(&argvars[2], &error); + ic = (int)tv_get_number_chk(&argvars[2], &error); } if (argvars[0].v_type == VAR_STRING) { @@ -1219,7 +1162,7 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[3].v_type != VAR_UNKNOWN) { idx = tv_get_number_chk(&argvars[3], &error); if (!error) { - li = tv_list_find(l, idx); + li = tv_list_find(l, (int)idx); if (li == NULL) { semsg(_(e_listidx), (int64_t)idx); } @@ -1292,7 +1235,7 @@ static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr) { size_t index = 0; if (argvars[0].v_type == VAR_NUMBER) { - index = argvars[0].vval.v_number; + index = (size_t)argvars[0].vval.v_number; } else if (argvars[0].v_type != VAR_UNKNOWN) { semsg(_(e_invarg2), "expected nothing or a Number as an argument"); return; @@ -1360,7 +1303,7 @@ static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr) size_t index = 0; if (argvars[1].v_type == VAR_NUMBER) { - index = argvars[1].vval.v_number; + index = (size_t)argvars[1].vval.v_number; } else if (argvars[1].v_type != VAR_UNKNOWN) { semsg(_(e_invarg2), "expected nothing or a Number as second argument"); return; @@ -1394,7 +1337,7 @@ static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = ctx_size(); + rettv->vval.v_number = (varnumber_T)ctx_size(); } /// Set the cursor position. @@ -1428,7 +1371,7 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) line = tv_get_lnum(argvars); col = (long)tv_get_number_chk(&argvars[1], NULL); if (charcol) { - col = buf_charidx_to_byteidx(curbuf, line, col) + 1; + col = buf_charidx_to_byteidx(curbuf, (linenr_T)line, (int)col) + 1; } if (argvars[2].v_type != VAR_UNKNOWN) { coladd = (long)tv_get_number_chk(&argvars[2], NULL); @@ -1441,12 +1384,12 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) return; // type error; errmsg already given } if (line > 0) { - curwin->w_cursor.lnum = line; + curwin->w_cursor.lnum = (linenr_T)line; } if (col > 0) { - curwin->w_cursor.col = col - 1; + curwin->w_cursor.col = (colnr_T)col - 1; } - curwin->w_cursor.coladd = coladd; + curwin->w_cursor.coladd = (colnr_T)coladd; // Make sure the cursor is in a valid position. check_cursor(); @@ -1498,7 +1441,7 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) int noref = 0; if (argvars[1].v_type != VAR_UNKNOWN) { - noref = tv_get_number_chk(&argvars[1], NULL); + noref = (int)tv_get_number_chk(&argvars[1], NULL); } if (noref < 0 || noref > 1) { emsg(_(e_invarg)); @@ -1675,7 +1618,7 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_buffer == buf) { if (wp->w_cursor.lnum > last) { - wp->w_cursor.lnum -= count; + wp->w_cursor.lnum -= (linenr_T)count; } else if (wp->w_cursor.lnum > first) { wp->w_cursor.lnum = first; } @@ -1712,7 +1655,7 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T lnum = tv_get_lnum(argvars); static linenr_T prev_lnum = 0; - static int changedtick = 0; + static varnumber_T changedtick = 0; static int fnum = 0; static int change_start = 0; static int change_end = 0; @@ -1749,7 +1692,7 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (hlID == HLF_CHD || hlID == HLF_TXD) { - col = tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}. + col = (int)tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}. if (col >= change_start && col <= change_end) { hlID = HLF_TXD; // Changed text. } else { @@ -1820,7 +1763,7 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) os_copy_fullenv(env, env_size); - for (ssize_t i = env_size - 1; i >= 0; i--) { + for (ssize_t i = (ssize_t)env_size - 1; i >= 0; i--) { const char *str = env[i]; const char * const end = strchr(str + (str[0] == '=' ? 1 : 0), '='); @@ -1848,9 +1791,7 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) xfree(key); continue; } - tv_dict_add_str(rettv->vval.v_dict, - key, len, - value); + tv_dict_add_str(rettv->vval.v_dict, key, (size_t)len, value); xfree(key); } os_free_fullenv(env); @@ -2029,7 +1970,7 @@ static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; - int id = tv_get_number(argvars); + int id = (int)tv_get_number(argvars); tabpage_T *tp; win_T *wp = win_id2wp_tp(id, &tp); if (wp != NULL && tp != NULL) { @@ -2189,11 +2130,11 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) char *errormsg = NULL; rettv->v_type = VAR_STRING; - char_u *cmdstr = (char_u *)xstrdup(tv_get_string(&argvars[0])); + char *cmdstr = xstrdup(tv_get_string(&argvars[0])); exarg_T eap = { - .cmd = (char *)cmdstr, - .arg = (char *)cmdstr, + .cmd = cmdstr, + .arg = cmdstr, .usefilter = false, .nextcmd = NULL, .cmdidx = CMD_USER, @@ -2204,7 +2145,7 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (errormsg != NULL && *errormsg != NUL) { emsg(errormsg); } - rettv->vval.v_string = (char *)cmdstr; + rettv->vval.v_string = cmdstr; } /// "flatten(list[, {maxdepth}])" function @@ -2265,7 +2206,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (before == tv_list_len(l1)) { item = NULL; } else { - item = tv_list_find(l1, before); + item = tv_list_find(l1, (int)before); if (item == NULL) { semsg(_(e_listidx), (int64_t)before); return; @@ -2382,7 +2323,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) } if (argvars[2].v_type != VAR_UNKNOWN) { - count = tv_get_number_chk(&argvars[2], &error); + count = (int)tv_get_number_chk(&argvars[2], &error); } } } @@ -2577,7 +2518,7 @@ static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } - unsigned long count = (unsigned long)foldend - foldstart + 1; + unsigned long count = (unsigned long)foldend - (unsigned long)foldstart + 1; txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count); r = xmalloc(STRLEN(txt) + STRLEN(dashes) // for %s @@ -2613,7 +2554,7 @@ static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) foldinfo_T info = fold_info(curwin, lnum); if (info.fi_lines > 0) { - text = get_foldtext(curwin, lnum, lnum + info.fi_lines - 1, info, buf); + text = get_foldtext(curwin, lnum, lnum + (linenr_T)info.fi_lines - 1, info, buf); if (text == buf) { text = vim_strsave(text); } @@ -2661,7 +2602,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type == VAR_BLOB) { bool error = false; - int idx = tv_get_number_chk(&argvars[1], &error); + int idx = (int)tv_get_number_chk(&argvars[1], &error); if (!error) { rettv->v_type = VAR_NUMBER; @@ -2679,7 +2620,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) if ((l = argvars[0].vval.v_list) != NULL) { bool error = false; - li = tv_list_find(l, tv_get_number_chk(&argvars[1], &error)); + li = tv_list_find(l, (int)tv_get_number_chk(&argvars[1], &error)); if (!error && li != NULL) { tv = TV_LIST_ITEM_TV(li); } @@ -2860,66 +2801,6 @@ static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_buffer_lines(buf, lnum, end, true, rettv); } -/// "getbufvar()" function -static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool done = false; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - if (!tv_check_str_or_nr(&argvars[0])) { - goto f_getbufvar_end; - } - - const char *varname = tv_get_string_chk(&argvars[1]); - emsg_off++; - buf_T *const buf = tv_get_buf(&argvars[0], false); - - if (buf != NULL && varname != NULL) { - if (*varname == '&') { // buffer-local-option - buf_T *const save_curbuf = curbuf; - - // set curbuf to be our buf, temporarily - curbuf = buf; - - if (varname[1] == NUL) { - // get all buffer-local options in a dict - dict_T *opts = get_winbuf_options(true); - - if (opts != NULL) { - tv_dict_set_ret(rettv, opts); - done = true; - } - } else if (get_option_tv(&varname, rettv, true) == OK) { - // buffer-local-option - done = true; - } - - // restore previous notion of curbuf - curbuf = save_curbuf; - } else { - // Look up the variable. - // Let getbufvar({nr}, "") return the "b:" dictionary. - dictitem_T *const v = *varname == NUL - ? (dictitem_T *)&buf->b_bufvar - : find_var_in_ht(&buf->b_vars->dv_hashtab, 'b', - varname, strlen(varname), false); - if (v != NULL) { - tv_copy(&v->di_tv, rettv); - done = true; - } - } - } - emsg_off--; - -f_getbufvar_end: - if (!done && argvars[2].v_type != VAR_UNKNOWN) { - // use the default value - tv_copy(&argvars[2], rettv); - } -} - /// "getchangelist()" function static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -2929,7 +2810,7 @@ static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type == VAR_UNKNOWN) { buf = curbuf; } else { - vim_ignored = tv_get_number(&argvars[0]); // issue errmsg if type error + vim_ignored = (int)tv_get_number(&argvars[0]); // issue errmsg if type error emsg_off++; buf = tv_get_buf(&argvars[0], false); emsg_off--; @@ -2940,13 +2821,23 @@ static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *const l = tv_list_alloc(buf->b_changelistlen); tv_list_append_list(rettv->vval.v_list, l); - // The current window change list index tracks only the position in the - // current buffer change list. For other buffers, use the change list - // length as the current index. - tv_list_append_number(rettv->vval.v_list, - (buf == curwin->w_buffer) - ? curwin->w_changelistidx - : buf->b_changelistlen); + // The current window change list index tracks only the position for the + // current buffer. For other buffers use the stored index for the current + // window, or, if that's not available, the change list length. + int changelistindex; + if (buf == curwin->w_buffer) { + changelistindex = curwin->w_changelistidx; + } else { + wininfo_T *wip; + + FOR_ALL_BUF_WININFO(buf, wip) { + if (wip->wi_win == curwin) { + break; + } + } + changelistindex = wip != NULL ? wip->wi_changelistidx : buf->b_changelistlen; + } + tv_list_append_number(rettv->vval.v_list, (varnumber_T)changelistindex); for (int i = 0; i < buf->b_changelistlen; i++) { if (buf->b_changelist[i].mark.lnum == 0) { @@ -3008,6 +2899,11 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) no_mapping--; allow_keys--; + if (!ui_has_messages()) { + // redraw the screen after getchar() + update_screen(CLEAR); + } + set_vim_var_nr(VV_MOUSE_WIN, 0); set_vim_var_nr(VV_MOUSE_WINID, 0); set_vim_var_nr(VV_MOUSE_LNUM, 0); @@ -3022,21 +2918,21 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) if (mod_mask != 0) { temp[i++] = K_SPECIAL; temp[i++] = KS_MODIFIER; - temp[i++] = mod_mask; + temp[i++] = (char_u)mod_mask; } if (IS_SPECIAL(n)) { temp[i++] = K_SPECIAL; - temp[i++] = K_SECOND(n); + temp[i++] = (char_u)K_SECOND(n); temp[i++] = K_THIRD(n); } else { - i += utf_char2bytes(n, (char *)temp + i); + i += utf_char2bytes((int)n, (char *)temp + i); } assert(i < 10); temp[i++] = NUL; rettv->v_type = VAR_STRING; rettv->vval.v_string = (char *)vim_strsave(temp); - if (is_mouse_key(n)) { + if (is_mouse_key((int)n)) { int row = mouse_row; int col = mouse_col; int grid = mouse_grid; @@ -3081,7 +2977,7 @@ static void f_getcharstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) int i = 0; if (n != 0) { - i += utf_char2bytes(n, (char *)temp); + i += utf_char2bytes((int)n, (char *)temp); } assert(i < 7); temp[i++] = NUL; @@ -3200,7 +3096,7 @@ static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = xmallocz(1); - rettv->vval.v_string[0] = get_cmdline_type(); + rettv->vval.v_string[0] = (char)get_cmdline_type(); } /// "getcmdwintype()" function @@ -3209,7 +3105,7 @@ static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; rettv->vval.v_string = xmallocz(1); - rettv->vval.v_string[0] = cmdwin_type; + rettv->vval.v_string[0] = (char)cmdwin_type; } /// "getcompletion()" function @@ -3249,7 +3145,7 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (strcmp(type, "cmdline") == 0) { set_one_cmd_context(&xpc, pattern); xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - xpc.xp_col = STRLEN(pattern); + xpc.xp_col = (int)STRLEN(pattern); goto theend; } @@ -3330,7 +3226,7 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg(_(e_invarg)); return; } - scope_number[i] = argvars[i].vval.v_number; + scope_number[i] = (int)argvars[i].vval.v_number; // It is an error for the scope number to be less than `-1`. if (scope_number[i] < -1) { emsg(_(e_invarg)); @@ -3430,7 +3326,7 @@ static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) perm = xstrdup("---------"); for (int i = 0; i < 9; i++) { if (file_perm & (1 << (8 - i))) { - perm[i] = flags[i % 3]; + perm[i] = (char)flags[i % 3]; } } } @@ -3764,50 +3660,6 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "gettabvar()" function -static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool done = false; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - const char *const varname = tv_get_string_chk(&argvars[1]); - tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); - if (tp != NULL && varname != NULL) { - // Set tp to be our tabpage, temporarily. Also set the window to the - // first window in the tabpage, otherwise the window is not valid. - win_T *const window = tp == curtab || tp->tp_firstwin == NULL - ? firstwin - : tp->tp_firstwin; - switchwin_T switchwin; - if (switch_win(&switchwin, window, tp, true) == OK) { - // look up the variable - // Let gettabvar({nr}, "") return the "t:" dictionary. - const dictitem_T *const v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't', - varname, strlen(varname), - false); - if (v != NULL) { - tv_copy(&v->di_tv, rettv); - done = true; - } - } - - // restore previous notion of curwin - restore_win(&switchwin, true); - } - - if (!done && argvars[2].v_type != VAR_UNKNOWN) { - tv_copy(&argvars[2], rettv); - } -} - -/// "gettabwinvar()" function -static void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getwinvar(argvars, rettv, 1); -} - /// "gettagstack()" function static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -3833,7 +3685,7 @@ static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_alloc_ret(rettv, kListLenMayKnow); if (argvars[0].v_type != VAR_UNKNOWN) { - wparg = win_id2wp(tv_get_number(&argvars[0])); + wparg = win_id2wp((int)tv_get_number(&argvars[0])); if (wparg == NULL) { return; } @@ -3886,10 +3738,10 @@ static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - int timeout = argvars[0].vval.v_number; + int timeout = (int)argvars[0].vval.v_number; typval_T expr = argvars[1]; int interval = argvars[2].v_type == VAR_NUMBER - ? argvars[2].vval.v_number + ? (int)argvars[2].vval.v_number : 200; // Default. TimeWatcher *tw = xmalloc(sizeof(TimeWatcher)); @@ -3897,7 +3749,7 @@ static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) time_watcher_init(&main_loop, tw, NULL); tw->events = main_loop.events; tw->blockable = true; - time_watcher_start(tw, dummy_timer_due_cb, interval, interval); + time_watcher_start(tw, dummy_timer_due_cb, (uint64_t)interval, (uint64_t)interval); typval_T argv = TV_INITIAL_VALUE; typval_T exprval = TV_INITIAL_VALUE; @@ -4005,7 +3857,7 @@ static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr) if ((di = tv_dict_find(d, "rightbelow", -1)) != NULL) { flags |= tv_get_number(&di->di_tv) ? WSP_BELOW : WSP_ABOVE; } - size = tv_dict_get_number(d, "size"); + size = (int)tv_dict_get_number(d, "size"); } win_move_into_split(wp, targetwin, size, flags); @@ -4031,12 +3883,6 @@ static void f_getwinposy(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = -1; } -/// "getwinvar()" function -static void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getwinvar(argvars, rettv, 0); -} - /// "glob()" function static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -4181,6 +4027,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "cindent", "cmdline_compl", "cmdline_hist", + "cmdwin", "comments", "conceal", "cscope", @@ -4282,7 +4129,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) }; // XXX: eval_has_provider() may shell out :( - const int save_shell_error = get_vim_var_nr(VV_SHELL_ERROR); + const int save_shell_error = (int)get_vim_var_nr(VV_SHELL_ERROR); bool n = false; const char *const name = tv_get_string(&argvars[0]); for (size_t i = 0; i < ARRAY_SIZE(has_list); i++) { @@ -4359,22 +4206,6 @@ static bool has_wsl(void) return has_wsl == kTrue; } -/// "has_key()" function -static void f_has_key(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_DICT) { - emsg(_(e_dictreq)); - return; - } - if (argvars[0].vval.v_dict == NULL) { - return; - } - - rettv->vval.v_number = tv_dict_find(argvars[0].vval.v_dict, - tv_get_string(&argvars[1]), - -1) != NULL; -} - /// `haslocaldir([{win}[, {tab}]])` function /// /// Returns `1` if the scope object has a local directory, `0` otherwise. If a @@ -4413,7 +4244,7 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg(_(e_invarg)); return; } - scope_number[i] = argvars[i].vval.v_number; + scope_number[i] = (int)argvars[i].vval.v_number; if (scope_number[i] < -1) { emsg(_(e_invarg)); return; @@ -4629,7 +4460,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) int start = 0; if (argvars[2].v_type != VAR_UNKNOWN) { - start = tv_get_number_chk(&argvars[2], &error); + start = (int)tv_get_number_chk(&argvars[2], &error); if (error) { return; } @@ -4647,7 +4478,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr 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, idx); + tv.vval.v_number = tv_blob_get(b, (int)idx); if (tv_equal(&tv, &argvars[1], ic, false)) { rettv->vval.v_number = idx; return; @@ -4665,11 +4496,11 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool error = false; // Start at specified item. - idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error)); + idx = tv_list_uidx(l, (int)tv_get_number_chk(&argvars[2], &error)); if (error || idx == -1) { item = NULL; } else { - item = tv_list_find(l, idx); + item = tv_list_find(l, (int)idx); assert(item != NULL); } if (argvars[3].v_type != VAR_UNKNOWN) { @@ -4797,7 +4628,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } } - const int val = tv_get_number_chk(&argvars[1], &error); + const int val = (int)tv_get_number_chk(&argvars[1], &error); if (error) { return; } @@ -4808,8 +4639,8 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) ga_grow(&b->bv_ga, 1); char_u *const p = (char_u *)b->bv_ga.ga_data; - memmove(p + before + 1, p + before, (size_t)len - before); - *(p + before) = val; + memmove(p + before + 1, p + before, (size_t)(len - before)); + *(p + before) = (char_u)val; b->bv_ga.ga_len++; tv_copy(&argvars[0], rettv); @@ -4828,7 +4659,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) listitem_T *item = NULL; if (before != tv_list_len(l)) { - item = tv_list_find(l, before); + item = tv_list_find(l, (int)before); if (item == NULL) { semsg(_(e_listidx), (int64_t)before); l = NULL; @@ -4925,14 +4756,8 @@ static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const int len = vim_vsnprintf_typval(NULL, 0, "%p", dummy_ap, argvars); rettv->v_type = VAR_STRING; - rettv->vval.v_string = xmalloc(len + 1); - vim_vsnprintf_typval(rettv->vval.v_string, len + 1, "%p", dummy_ap, argvars); -} - -/// "items(dict)" function -static void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_list(argvars, rettv, 2); + rettv->vval.v_string = xmalloc((size_t)len + 1); + vim_vsnprintf_typval(rettv->vval.v_string, (size_t)len + 1, "%p", dummy_ap, argvars); } /// "jobpid(id)" function @@ -4950,7 +4775,7 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - Channel *data = find_job(argvars[0].vval.v_number, true); + Channel *data = find_job((uint64_t)argvars[0].vval.v_number, true); if (!data) { return; } @@ -4976,7 +4801,7 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - Channel *data = find_job(argvars[0].vval.v_number, true); + Channel *data = find_job((uint64_t)argvars[0].vval.v_number, true); if (!data) { return; } @@ -4986,8 +4811,8 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - pty_process_resize(&data->stream.pty, argvars[1].vval.v_number, - argvars[2].vval.v_number); + pty_process_resize(&data->stream.pty, (uint16_t)argvars[1].vval.v_number, + (uint16_t)argvars[2].vval.v_number); rettv->vval.v_number = 1; } @@ -5102,7 +4927,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en i < ARRAY_SIZE(required_env_vars) && required_env_vars[i]; i++) { size_t len = strlen(required_env_vars[i]); - dictitem_T *dv = tv_dict_find(env, required_env_vars[i], len); + dictitem_T *dv = tv_dict_find(env, required_env_vars[i], (ptrdiff_t)len); if (!dv) { const char *env_var = os_getenv(required_env_vars[i]); if (env_var) { @@ -5251,7 +5076,7 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - Channel *data = find_job(argvars[0].vval.v_number, false); + Channel *data = find_job((uint64_t)argvars[0].vval.v_number, false); if (!data) { return; } @@ -5285,7 +5110,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) ui_busy_start(); list_T *args = argvars[0].vval.v_list; - Channel **jobs = xcalloc(tv_list_len(args), sizeof(*jobs)); + Channel **jobs = xcalloc((size_t)tv_list_len(args), sizeof(*jobs)); MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop); // Validate, prepare jobs for waiting. @@ -5293,7 +5118,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) TV_LIST_ITER_CONST(args, arg, { Channel *chan = NULL; if (TV_LIST_ITEM_TV(arg)->v_type != VAR_NUMBER - || !(chan = find_channel(TV_LIST_ITEM_TV(arg)->vval.v_number)) + || !(chan = find_channel((uint64_t)TV_LIST_ITEM_TV(arg)->vval.v_number)) || chan->streamtype != kChannelStreamProc) { jobs[i] = NULL; // Invalid job. } else if (process_is_stopped(&chan->stream.proc)) { @@ -5316,7 +5141,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) int remaining = -1; uint64_t before = 0; if (argvars[1].v_type == VAR_NUMBER && argvars[1].vval.v_number >= 0) { - remaining = argvars[1].vval.v_number; + remaining = (int)argvars[1].vval.v_number; before = os_hrtime(); } @@ -5367,30 +5192,6 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_list = rv; } -/// "join()" function -static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_LIST) { - emsg(_(e_listreq)); - return; - } - const char *const sep = (argvars[1].v_type == VAR_UNKNOWN - ? " " - : tv_get_string_chk(&argvars[1])); - - rettv->v_type = VAR_STRING; - - if (sep != NULL) { - garray_T ga; - ga_init(&ga, (int)sizeof(char), 80); - tv_list_join(&ga, argvars[0].vval.v_list, sep); - ga_append(&ga, NUL); - rettv->vval.v_string = ga.ga_data; - } else { - rettv->vval.v_string = NULL; - } -} - /// json_decode() function static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -5432,12 +5233,6 @@ static void f_json_encode(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = encode_tv2json(&argvars[0], NULL); } -/// "keys()" function -static void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_list(argvars, rettv, 0); -} - /// "last_buffer_nr()" function. static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -5482,7 +5277,7 @@ static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type) { - rettv->v_type = out_type; + rettv->v_type = (VarType)out_type; if (out_type != VAR_NUMBER) { rettv->vval.v_string = NULL; } @@ -5503,7 +5298,7 @@ static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type) // input variables char *str_in = (in_type == VAR_STRING) ? argvars[2].vval.v_string : NULL; - int int_in = argvars[2].vval.v_number; + int int_in = (int)argvars[2].vval.v_number; // output variables char **str_out = (out_type == VAR_STRING) ? &rettv->vval.v_string : NULL; @@ -5594,35 +5389,6 @@ static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "list2str()" function -static void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - garray_T ga; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (argvars[0].v_type != VAR_LIST) { - emsg(_(e_invarg)); - return; - } - - list_T *const l = argvars[0].vval.v_list; - if (l == NULL) { - return; // empty list results in empty string - } - - ga_init(&ga, 1, 80); - char buf[MB_MAXBYTES + 1]; - - TV_LIST_ITER_CONST(l, li, { - buf[utf_char2bytes(tv_get_number(TV_LIST_ITEM_TV(li)), (char *)buf)] = NUL; - ga_concat(&ga, (char *)buf); - }); - ga_append(&ga, NUL); - - rettv->vval.v_string = ga.ga_data; -} - /// "localtime()" function static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -5716,11 +5482,11 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, goto theend; } if (l != NULL) { - idx = tv_list_uidx(l, start); + idx = tv_list_uidx(l, (int)start); if (idx == -1) { goto theend; } - li = tv_list_find(l, idx); + li = tv_list_find(l, (int)idx); } else { if (start < 0) { start = 0; @@ -5732,7 +5498,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, // otherwise skip part of the string. Differs when pattern is "^" // or "\<". if (argvars[3].v_type != VAR_UNKNOWN) { - startcol = start; + startcol = (colnr_T)start; } else { str += start; len -= start; @@ -5976,7 +5742,7 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_UNKNOWN) { - prot = tv_get_number_chk(&argvars[2], NULL); + prot = (int)tv_get_number_chk(&argvars[2], NULL); if (prot == -1) { return; } @@ -6144,7 +5910,7 @@ static void msgpackparse_unpack_blob(const blob_T *const blob, list_T *const ret msgpack_unpacked_init(&unpacked); for (size_t offset = 0; offset < (size_t)len;) { const msgpack_unpack_return result - = msgpack_unpack_next(&unpacked, blob->bv_ga.ga_data, len, &offset); + = msgpack_unpack_next(&unpacked, blob->bv_ga.ga_data, (size_t)len, &offset); if (msgpackparse_convert_item(unpacked.data, result, ret_list, true) != OK) { break; @@ -6290,9 +6056,9 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *fmt = tv_get_string_buf(&argvars[0], buf); len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1); if (!did_emsg) { - char *s = xmalloc(len + 1); + char *s = xmalloc((size_t)len + 1); rettv->vval.v_string = s; - (void)vim_vsnprintf_typval(s, len + 1, fmt, dummy_ap, argvars + 1); + (void)vim_vsnprintf_typval(s, (size_t)len + 1, fmt, dummy_ap, argvars + 1); } did_emsg |= saved_did_emsg; } @@ -6436,7 +6202,7 @@ static void init_srand(uint32_t *const x) // Reading /dev/urandom doesn't work, fall back to time(). #endif // uncrustify:off - *x = time(NULL); + *x = (uint32_t)time(NULL); #ifndef MSWIN } #endif @@ -6514,10 +6280,10 @@ static void f_rand(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (tvw->v_type != VAR_NUMBER) { goto theend; } - uint32_t x = tvx->vval.v_number; - uint32_t y = tvy->vval.v_number; - uint32_t z = tvz->vval.v_number; - uint32_t w = tvw->vval.v_number; + uint32_t x = (uint32_t)tvx->vval.v_number; + uint32_t y = (uint32_t)tvy->vval.v_number; + uint32_t z = (uint32_t)tvz->vval.v_number; + uint32_t w = (uint32_t)tvw->vval.v_number; result = shuffle_xoshiro128starstar(&x, &y, &z, &w); @@ -6549,7 +6315,7 @@ static void f_srand(typval_T *argvars, typval_T *rettv, FunPtr fptr) init_srand(&x); } else { bool error = false; - x = tv_get_number_chk(&argvars[0], &error); + x = (uint32_t)tv_get_number_chk(&argvars[0], &error); if (error) { return; } @@ -6716,7 +6482,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown); while (maxline < 0 || tv_list_len(l) < maxline) { - readlen = (int)fread(buf, 1, io_size, fd); + readlen = (int)fread(buf, 1, (size_t)io_size, fd); // This for loop processes what was read, but is also entered at end // of file so that either: @@ -6730,7 +6496,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) p++) { if (*p == '\n' || readlen <= 0) { char_u *s = NULL; - size_t len = p - start; + size_t len = (size_t)(p - start); // Finished a line. Remove CRs before NL. if (readlen > 0 && !binary) { @@ -6751,9 +6517,9 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) /* Change "prev" buffer to be the right size. This way * the bytes are only copied once, and very long lines are * allocated only once. */ - s = xrealloc(prev, prevlen + len + 1); + s = xrealloc(prev, (size_t)prevlen + len + 1); memcpy(s + prevlen, start, len); - s[prevlen + len] = NUL; + s[(size_t)prevlen + len] = NUL; prev = NULL; // the list will own the string prevlen = prevsize = 0; } @@ -6808,7 +6574,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) dest = buf; } if (readlen > p - buf + 1) { - memmove(dest, p + 1, readlen - (p - buf) - 1); + memmove(dest, p + 1, (size_t)readlen - (size_t)(p - buf) - 1); } readlen -= 3 - adjust_prevlen; prevlen -= adjust_prevlen; @@ -6835,10 +6601,10 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) long growmin = (long)((p - start) * 2 + prevlen); prevsize = grow50pc > growmin ? grow50pc : growmin; } - prev = xrealloc(prev, prevsize); + prev = xrealloc(prev, (size_t)prevsize); } // Add the line part to end of "prev". - memmove(prev + prevlen, start, p - start); + memmove(prev + prevlen, start, (size_t)(p - start)); prevlen += (long)(p - start); } } // while @@ -6887,7 +6653,7 @@ static void f_getreginfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } (void)tv_dict_add_str(dict, S_LEN("regtype"), buf); - buf[0] = get_register_name(get_unname_register()); + buf[0] = (char)get_register_name(get_unname_register()); buf[1] = NUL; if (regname == '"') { (void)tv_dict_add_str(dict, S_LEN("points_to"), buf); @@ -6938,7 +6704,7 @@ static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL union { struct { int32_t low, high; } split; proftime_T prof; - } u = { .split.high = n1, .split.low = n2 }; + } u = { .split.high = (int32_t)n1, .split.low = (int32_t)n2 }; *tm = u.prof; @@ -7008,134 +6774,16 @@ static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "remove()" function static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - listitem_T *item, *item2; - listitem_T *li; - long idx; - long end; - dict_T *d; - dictitem_T *di; const char *const arg_errmsg = N_("remove() argument"); if (argvars[0].v_type == VAR_DICT) { - if (argvars[2].v_type != VAR_UNKNOWN) { - semsg(_(e_toomanyarg), "remove()"); - } else if ((d = argvars[0].vval.v_dict) != NULL - && !var_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) { - const char *key = tv_get_string_chk(&argvars[1]); - if (key != NULL) { - di = tv_dict_find(d, key, -1); - if (di == NULL) { - semsg(_(e_dictkey), key); - } else if (!var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE) - && !var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) { - *rettv = di->di_tv; - di->di_tv = TV_INITIAL_VALUE; - tv_dict_item_remove(d, di); - if (tv_dict_is_watched(d)) { - tv_dict_watcher_notify(d, key, NULL, rettv); - } - } - } - } + tv_dict_remove(argvars, rettv, arg_errmsg); } else if (argvars[0].v_type == VAR_BLOB) { - blob_T *const b = argvars[0].vval.v_blob; - - if (b != NULL && var_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE)) { - return; - } - - bool error = false; - idx = (long)tv_get_number_chk(&argvars[1], &error); - - if (!error) { - const int len = tv_blob_len(b); - - if (idx < 0) { - // count from the end - idx = len + idx; - } - if (idx < 0 || idx >= len) { - semsg(_(e_blobidx), (int64_t)idx); - return; - } - if (argvars[2].v_type == VAR_UNKNOWN) { - // Remove one item, return its value. - char_u *const p = (char_u *)b->bv_ga.ga_data; - rettv->vval.v_number = (varnumber_T)(*(p + idx)); - memmove(p + idx, p + idx + 1, (size_t)len - idx - 1); - b->bv_ga.ga_len--; - } else { - // Remove range of items, return blob with values. - end = (long)tv_get_number_chk(&argvars[2], &error); - if (error) { - return; - } - if (end < 0) { - // count from the end - end = len + end; - } - if (end >= len || idx > end) { - semsg(_(e_blobidx), (int64_t)end); - return; - } - blob_T *const blob = tv_blob_alloc(); - blob->bv_ga.ga_len = end - idx + 1; - ga_grow(&blob->bv_ga, end - idx + 1); - - char_u *const p = (char_u *)b->bv_ga.ga_data; - memmove((char_u *)blob->bv_ga.ga_data, p + idx, - (size_t)(end - idx + 1)); - tv_blob_set_ret(rettv, blob); - - if (len - end - 1 > 0) { - memmove(p + idx, p + end + 1, (size_t)(len - end - 1)); - } - b->bv_ga.ga_len -= end - idx + 1; - } - } - } else if (argvars[0].v_type != VAR_LIST) { + tv_blob_remove(argvars, rettv, arg_errmsg); + } else if (argvars[0].v_type == VAR_LIST) { + tv_list_remove(argvars, rettv, arg_errmsg); + } else { semsg(_(e_listdictblobarg), "remove()"); - } else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - arg_errmsg, TV_TRANSLATE)) { - bool error = false; - - idx = tv_get_number_chk(&argvars[1], &error); - if (error) { - // Type error: do nothing, errmsg already given. - } else if ((item = tv_list_find(l, idx)) == NULL) { - semsg(_(e_listidx), (int64_t)idx); - } else { - if (argvars[2].v_type == VAR_UNKNOWN) { - // Remove one item, return its value. - tv_list_drop_items(l, item, item); - *rettv = *TV_LIST_ITEM_TV(item); - xfree(item); - } else { - // Remove range of items, return list with values. - end = tv_get_number_chk(&argvars[2], &error); - if (error) { - // Type error: do nothing. - } else if ((item2 = tv_list_find(l, end)) == NULL) { - semsg(_(e_listidx), (int64_t)end); - } else { - int cnt = 0; - - for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { - cnt++; - if (li == item2) { - break; - } - } - if (li == NULL) { // Didn't find "item2" after "item". - emsg(_(e_invrange)); - } else { - tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt), - cnt); - } - } - } - } } } @@ -7173,15 +6821,15 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (slen == 0) { return; } - const size_t len = slen * n; + const size_t len = slen * (size_t)n; // Detect overflow. - if (len / n != slen) { + if (len / (size_t)n != slen) { return; } char *const r = xmallocz(len); for (varnumber_T i = 0; i < n; i++) { - memmove(r + i * slen, p, slen); + memmove(r + (size_t)i * slen, p, slen); } rettv->vval.v_string = r; @@ -7295,9 +6943,9 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) q = (char *)path_next_component(remain + 1); len = q - remain - (*q != NUL); const size_t p_len = strlen(p); - cpy = xmallocz(p_len + len); + cpy = xmallocz(p_len + (size_t)len); memcpy(cpy, p, p_len + 1); - xstrlcat(cpy + p_len, remain, len + 1); + xstrlcat(cpy + p_len, remain, (size_t)len + 1); xfree(p); p = cpy; @@ -7861,7 +7509,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Allocate extra memory for the argument vector and the NULL pointer int argvl = argsl + 2; - char **argv = xmalloc(sizeof(char_u *) * argvl); + char **argv = xmalloc(sizeof(char_u *) * (size_t)argvl); // Copy program name argv[0] = xstrdup(argvars[0].vval.v_string); @@ -7904,13 +7552,13 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // if called with a job, stop it, else closes the channel - uint64_t id = argvars[0].vval.v_number; + uint64_t id = (uint64_t)argvars[0].vval.v_number; if (find_job(id, false)) { f_jobstop(argvars, rettv, NULL); } else { const char *error; - rettv->vval.v_number = channel_close(argvars[0].vval.v_number, - kChannelPartRpc, &error); + rettv->vval.v_number = + channel_close((uint64_t)argvars[0].vval.v_number, kChannelPartRpc, &error); if (!rettv->vval.v_number) { emsg(error); } @@ -7931,7 +7579,7 @@ static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) { c = -1; } else { - c = grid->attrs[grid->line_offset[row] + col]; + c = grid->attrs[grid->line_offset[row] + (size_t)col]; } rettv->vval.v_number = c; } @@ -7942,15 +7590,15 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) int c; ScreenGrid *grid; - int row = tv_get_number_chk(&argvars[0], NULL) - 1; - int col = tv_get_number_chk(&argvars[1], NULL) - 1; + int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; + int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; screenchar_adjust(&grid, &row, &col); if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) { c = -1; } else { - c = utf_ptr2char((char *)grid->chars[grid->line_offset[row] + col]); + c = utf_ptr2char((char *)grid->chars[grid->line_offset[row] + (size_t)col]); } rettv->vval.v_number = c; } @@ -7959,8 +7607,8 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) { ScreenGrid *grid; - int row = tv_get_number_chk(&argvars[0], NULL) - 1; - int col = tv_get_number_chk(&argvars[1], NULL) - 1; + int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; + int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; screenchar_adjust(&grid, &row, &col); @@ -7969,7 +7617,7 @@ static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } int pcc[MAX_MCO]; - int c = utfc_ptr2char(grid->chars[grid->line_offset[row] + col], pcc); + int c = utfc_ptr2char(grid->chars[grid->line_offset[row] + (size_t)col], pcc); int composing_len = 0; while (pcc[composing_len] != 0) { composing_len++; @@ -8004,8 +7652,8 @@ static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - pos.lnum = tv_get_number(&argvars[1]); - pos.col = tv_get_number(&argvars[2]) - 1; + pos.lnum = (linenr_T)tv_get_number(&argvars[1]); + pos.col = (colnr_T)tv_get_number(&argvars[2]) - 1; pos.coladd = 0; textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false); @@ -8028,8 +7676,8 @@ static void f_screenstring(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; ScreenGrid *grid; - int row = tv_get_number_chk(&argvars[0], NULL) - 1; - int col = tv_get_number_chk(&argvars[1], NULL) - 1; + int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; + int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; screenchar_adjust(&grid, &row, &col); @@ -8037,7 +7685,7 @@ static void f_screenstring(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - rettv->vval.v_string = (char *)vim_strsave(grid->chars[grid->line_offset[row] + col]); + rettv->vval.v_string = (char *)vim_strsave(grid->chars[grid->line_offset[row] + (size_t)col]); } /// "search()" function @@ -8135,8 +7783,8 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) } } - retval = do_searchpair(spat, mpat, epat, dir, skip, - flags, match_pos, lnum_stop, time_limit); + retval = (int)do_searchpair(spat, mpat, epat, dir, skip, + flags, match_pos, (linenr_T)lnum_stop, time_limit); theend: p_ws = save_p_ws; @@ -8363,7 +8011,7 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) char **addrs = server_address_list(&n); // Copy addrs into a linked list. - list_T *const l = tv_list_alloc_ret(rettv, n); + list_T *const l = tv_list_alloc_ret(rettv, (ptrdiff_t)n); for (size_t i = 0; i < n; i++) { tv_list_append_allocated_string(l, addrs[i]); } @@ -8450,50 +8098,6 @@ static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "setbufvar()" function -static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_secure() - || !tv_check_str_or_nr(&argvars[0])) { - return; - } - const char *varname = tv_get_string_chk(&argvars[1]); - buf_T *const buf = tv_get_buf(&argvars[0], false); - typval_T *varp = &argvars[2]; - - if (buf != NULL && varname != NULL) { - if (*varname == '&') { - long numval; - bool error = false; - aco_save_T aco; - - // set curbuf to be our buf, temporarily - aucmd_prepbuf(&aco, buf); - - varname++; - numval = tv_get_number_chk(varp, &error); - char nbuf[NUMBUFLEN]; - const char *const strval = tv_get_string_buf_chk(varp, nbuf); - if (!error && strval != NULL) { - set_option_value(varname, numval, strval, OPT_LOCAL); - } - - // reset notion of buffer - aucmd_restbuf(&aco); - } else { - const size_t varname_len = STRLEN(varname); - char *const bufvarname = xmalloc(varname_len + 3); - buf_T *const save_curbuf = curbuf; - curbuf = buf; - memcpy(bufvarname, "b:", 2); - memcpy(bufvarname + 2, varname, varname_len + 1); - set_var(bufvarname, varname_len + 2, varp, true); - xfree(bufvarname); - curbuf = save_curbuf; - } - } -} - /// Set the cursor or mark position. /// If 'charpos' is TRUE, then use the column number as a character offset. /// Otherwise use the column number as a byte offset. @@ -8746,7 +8350,7 @@ static void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) static int get_yank_type(char_u **const pp, MotionType *const yank_type, long *const block_len) FUNC_ATTR_NONNULL_ALL { - char_u *stropt = *pp; + char *stropt = (char *)(*pp); switch (*stropt) { case 'v': case 'c': // character-wise selection @@ -8768,7 +8372,7 @@ static int get_yank_type(char_u **const pp, MotionType *const yank_type, long *c default: return FAIL; } - *pp = stropt; + *pp = (char_u *)stropt; return OK; } @@ -8788,7 +8392,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (strregname == NULL) { return; // Type error; errmsg already given. } - char regname = (uint8_t)(*strregname); + char regname = *strregname; if (regname == 0 || regname == '@') { regname = '"'; } @@ -8867,7 +8471,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) // First half: use for pointers to result lines; second half: use for // pointers to allocated copies. - char **lstval = xmalloc(sizeof(char *) * ((len + 1) * 2)); + char **lstval = xmalloc(sizeof(char *) * (((size_t)len + 1) * 2)); const char **curval = (const char **)lstval; char **allocval = lstval + len + 2; char **curallocval = allocval; @@ -8889,8 +8493,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) }); *curval++ = NULL; - write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type, - block_len); + write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type, (colnr_T)block_len); free_lstval: while (curallocval > allocval) { @@ -8902,8 +8505,8 @@ free_lstval: if (strval == NULL) { return; } - write_reg_contents_ex(regname, (const char_u *)strval, STRLEN(strval), - append, yank_type, block_len); + write_reg_contents_ex(regname, (const char_u *)strval, (ssize_t)STRLEN(strval), + append, yank_type, (colnr_T)block_len); } if (pointreg != 0) { get_yank_register(pointreg, YREG_YANK); @@ -8916,43 +8519,6 @@ free_lstval: } } -/// "settabvar()" function -static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = 0; - - if (check_secure()) { - return; - } - - tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); - const char *const varname = tv_get_string_chk(&argvars[1]); - typval_T *const varp = &argvars[2]; - - if (varname != NULL && tp != NULL) { - tabpage_T *const save_curtab = curtab; - goto_tabpage_tp(tp, false, false); - - const size_t varname_len = strlen(varname); - char *const tabvarname = xmalloc(varname_len + 3); - memcpy(tabvarname, "t:", 2); - memcpy(tabvarname + 2, varname, varname_len + 1); - set_var(tabvarname, varname_len + 2, varp, true); - xfree(tabvarname); - - // Restore current tabpage. - if (valid_tabpage(save_curtab)) { - goto_tabpage_tp(save_curtab, false, false); - } - } -} - -/// "settabwinvar()" function -static void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - setwinvar(argvars, rettv, 1); -} - /// "settagstack()" function static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -9006,12 +8572,6 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "setwinvar()" function -static void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - setwinvar(argvars, rettv, 0); -} - /// f_sha256 - sha256({string}) function static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -9046,7 +8606,7 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (col < 0) { return; // type error; errmsg already given } - rettv->vval.v_number = get_sw_value_col(curbuf, col); + rettv->vval.v_number = get_sw_value_col(curbuf, (colnr_T)col); return; } rettv->vval.v_number = get_sw_value(curbuf); @@ -9113,341 +8673,6 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; } -/// struct storing information about current sort -typedef struct { - int item_compare_ic; - bool item_compare_lc; - bool item_compare_numeric; - bool item_compare_numbers; - bool item_compare_float; - const char *item_compare_func; - partial_T *item_compare_partial; - dict_T *item_compare_selfdict; - bool item_compare_func_err; -} sortinfo_T; -static sortinfo_T *sortinfo = NULL; - -#define ITEM_COMPARE_FAIL 999 - -/// Compare functions for f_sort() and f_uniq() below. -static int item_compare(const void *s1, const void *s2, bool keep_zero) -{ - ListSortItem *const si1 = (ListSortItem *)s1; - ListSortItem *const si2 = (ListSortItem *)s2; - - typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item); - typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item); - - int res; - - if (sortinfo->item_compare_numbers) { - const varnumber_T v1 = tv_get_number(tv1); - const varnumber_T v2 = tv_get_number(tv2); - - res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; - goto item_compare_end; - } - - if (sortinfo->item_compare_float) { - const float_T v1 = tv_get_float(tv1); - const float_T v2 = tv_get_float(tv2); - - res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; - goto item_compare_end; - } - - char *tofree1 = NULL; - char *tofree2 = NULL; - char *p1; - char *p2; - - // encode_tv2string() puts quotes around a string and allocates memory. Don't - // do that for string variables. Use a single quote when comparing with - // a non-string to do what the docs promise. - if (tv1->v_type == VAR_STRING) { - if (tv2->v_type != VAR_STRING || sortinfo->item_compare_numeric) { - p1 = "'"; - } else { - p1 = tv1->vval.v_string; - } - } else { - tofree1 = p1 = encode_tv2string(tv1, NULL); - } - if (tv2->v_type == VAR_STRING) { - if (tv1->v_type != VAR_STRING || sortinfo->item_compare_numeric) { - p2 = "'"; - } else { - p2 = tv2->vval.v_string; - } - } else { - tofree2 = p2 = encode_tv2string(tv2, NULL); - } - if (p1 == NULL) { - p1 = ""; - } - if (p2 == NULL) { - p2 = ""; - } - if (!sortinfo->item_compare_numeric) { - if (sortinfo->item_compare_lc) { - res = strcoll(p1, p2); - } else { - res = sortinfo->item_compare_ic ? STRICMP(p1, p2): STRCMP(p1, p2); - } - } else { - double n1, n2; - n1 = strtod(p1, &p1); - n2 = strtod(p2, &p2); - res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1; - } - - xfree(tofree1); - xfree(tofree2); - -item_compare_end: - // When the result would be zero, compare the item indexes. Makes the - // sort stable. - if (res == 0 && !keep_zero) { - // WARNING: When using uniq si1 and si2 are actually listitem_T **, no - // indexes are there. - res = si1->idx > si2->idx ? 1 : -1; - } - return res; -} - -static int item_compare_keeping_zero(const void *s1, const void *s2) -{ - return item_compare(s1, s2, true); -} - -static int item_compare_not_keeping_zero(const void *s1, const void *s2) -{ - return item_compare(s1, s2, false); -} - -static int item_compare2(const void *s1, const void *s2, bool keep_zero) -{ - ListSortItem *si1, *si2; - int res; - typval_T rettv; - typval_T argv[3]; - const char *func_name; - partial_T *partial = sortinfo->item_compare_partial; - - // shortcut after failure in previous call; compare all items equal - if (sortinfo->item_compare_func_err) { - return 0; - } - - si1 = (ListSortItem *)s1; - si2 = (ListSortItem *)s2; - - if (partial == NULL) { - func_name = sortinfo->item_compare_func; - } else { - func_name = (const char *)partial_name(partial); - } - - // Copy the values. This is needed to be able to set v_lock to VAR_FIXED - // in the copy without changing the original list items. - tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]); - tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]); - - rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this - funcexe_T funcexe = FUNCEXE_INIT; - funcexe.evaluate = true; - funcexe.partial = partial; - funcexe.selfdict = sortinfo->item_compare_selfdict; - res = call_func(func_name, -1, &rettv, 2, argv, &funcexe); - tv_clear(&argv[0]); - tv_clear(&argv[1]); - - if (res == FAIL) { - res = ITEM_COMPARE_FAIL; - } else { - res = tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); - if (res > 0) { - res = 1; - } else if (res < 0) { - res = -1; - } - } - if (sortinfo->item_compare_func_err) { - res = ITEM_COMPARE_FAIL; // return value has wrong type - } - tv_clear(&rettv); - - // When the result would be zero, compare the pointers themselves. Makes - // the sort stable. - if (res == 0 && !keep_zero) { - // WARNING: When using uniq si1 and si2 are actually listitem_T **, no - // indexes are there. - res = si1->idx > si2->idx ? 1 : -1; - } - - return res; -} - -static int item_compare2_keeping_zero(const void *s1, const void *s2) -{ - return item_compare2(s1, s2, true); -} - -static int item_compare2_not_keeping_zero(const void *s1, const void *s2) -{ - return item_compare2(s1, s2, false); -} - -/// "sort({list})" function -static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) -{ - ListSortItem *ptrs; - long len; - long i; - - // Pointer to current info struct used in compare function. Save and restore - // the current one for nested calls. - sortinfo_T info; - sortinfo_T *old_sortinfo = sortinfo; - sortinfo = &info; - - const char *const arg_errmsg = (sort - ? N_("sort() argument") - : N_("uniq() argument")); - - if (argvars[0].v_type != VAR_LIST) { - semsg(_(e_listarg), sort ? "sort()" : "uniq()"); - } else { - list_T *const l = argvars[0].vval.v_list; - if (var_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { - goto theend; - } - tv_list_set_ret(rettv, l); - - len = tv_list_len(l); - if (len <= 1) { - goto theend; // short list sorts pretty quickly - } - - info.item_compare_ic = false; - info.item_compare_lc = false; - info.item_compare_numeric = false; - info.item_compare_numbers = false; - info.item_compare_float = false; - info.item_compare_func = NULL; - info.item_compare_partial = NULL; - info.item_compare_selfdict = NULL; - - if (argvars[1].v_type != VAR_UNKNOWN) { - // optional second argument: {func} - if (argvars[1].v_type == VAR_FUNC) { - info.item_compare_func = (const char *)argvars[1].vval.v_string; - } else if (argvars[1].v_type == VAR_PARTIAL) { - info.item_compare_partial = argvars[1].vval.v_partial; - } else { - bool error = false; - - i = tv_get_number_chk(&argvars[1], &error); - if (error) { - goto theend; // type error; errmsg already given - } - if (i == 1) { - info.item_compare_ic = true; - } else if (argvars[1].v_type != VAR_NUMBER) { - info.item_compare_func = tv_get_string(&argvars[1]); - } else if (i != 0) { - emsg(_(e_invarg)); - goto theend; - } - if (info.item_compare_func != NULL) { - if (*info.item_compare_func == NUL) { - // empty string means default sort - info.item_compare_func = NULL; - } else if (strcmp(info.item_compare_func, "n") == 0) { - info.item_compare_func = NULL; - info.item_compare_numeric = true; - } else if (strcmp(info.item_compare_func, "N") == 0) { - info.item_compare_func = NULL; - info.item_compare_numbers = true; - } else if (strcmp(info.item_compare_func, "f") == 0) { - info.item_compare_func = NULL; - info.item_compare_float = true; - } else if (strcmp(info.item_compare_func, "i") == 0) { - info.item_compare_func = NULL; - info.item_compare_ic = true; - } else if (strcmp(info.item_compare_func, "l") == 0) { - info.item_compare_func = NULL; - info.item_compare_lc = true; - } - } - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - // optional third argument: {dict} - if (argvars[2].v_type != VAR_DICT) { - emsg(_(e_dictreq)); - goto theend; - } - info.item_compare_selfdict = argvars[2].vval.v_dict; - } - } - - // Make an array with each entry pointing to an item in the List. - ptrs = xmalloc((size_t)(len * sizeof(ListSortItem))); - - if (sort) { - info.item_compare_func_err = false; - tv_list_item_sort(l, ptrs, - ((info.item_compare_func == NULL - && info.item_compare_partial == NULL) - ? item_compare_not_keeping_zero - : item_compare2_not_keeping_zero), - &info.item_compare_func_err); - if (info.item_compare_func_err) { - emsg(_("E702: Sort compare function failed")); - } - } else { - ListSorter item_compare_func_ptr; - - // f_uniq(): ptrs will be a stack of items to remove. - info.item_compare_func_err = false; - if (info.item_compare_func != NULL - || info.item_compare_partial != NULL) { - item_compare_func_ptr = item_compare2_keeping_zero; - } else { - item_compare_func_ptr = item_compare_keeping_zero; - } - - int idx = 0; - for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) - ; li != NULL;) { - listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); - if (item_compare_func_ptr(&prev_li, &li) == 0) { - if (info.item_compare_func_err) { // -V547 - emsg(_("E882: Uniq compare function failed")); - break; - } - li = tv_list_item_remove(l, li); - } else { - idx++; - li = TV_LIST_ITEM_NEXT(l, li); - } - } - } - - xfree(ptrs); - } - -theend: - sortinfo = old_sortinfo; -} - -/// "sort"({list})" function -static void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - do_sort_uniq(argvars, rettv, true); -} - /// "stdioopen()" function static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -9483,12 +8708,6 @@ static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; } -/// "uniq({list})" function -static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - do_sort_uniq(argvars, rettv, false); -} - /// "reltimefloat()" function static void f_reltimefloat(typval_T *argvars, typval_T *rettv, FunPtr fptr) FUNC_ATTR_NONNULL_ALL @@ -9549,7 +8768,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; } str += len; - capcol -= len; + capcol -= (int)len; len = 0; } } @@ -9558,7 +8777,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) assert(len <= INT_MAX); tv_list_alloc_ret(rettv, 2); - tv_list_append_string(rettv->vval.v_list, word, len); + 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" @@ -9591,7 +8810,7 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *const str = tv_get_string(&argvars[0]); if (argvars[1].v_type != VAR_UNKNOWN) { - maxcount = tv_get_number_chk(&argvars[1], &typeerr); + maxcount = (int)tv_get_number_chk(&argvars[1], &typeerr); if (maxcount <= 0) { goto f_spellsuggest_return; } @@ -9763,7 +8982,7 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) int what = 0; if (argvars[1].v_type != VAR_UNKNOWN) { - base = tv_get_number(&argvars[1]); + base = (int)tv_get_number(&argvars[1]); if (base != 2 && base != 8 && base != 10 && base != 16) { emsg(_(e_invarg)); return; @@ -9874,7 +9093,7 @@ static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; } charidx--; - byteidx += utf_ptr2len(str + byteidx); + byteidx += (size_t)utf_ptr2len(str + byteidx); } } @@ -9932,7 +9151,7 @@ static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) int (*func_mb_ptr2char_adv)(const char_u **pp); if (argvars[1].v_type != VAR_UNKNOWN) { - skipcc = tv_get_number_chk(&argvars[1], NULL); + skipcc = (int)tv_get_number_chk(&argvars[1], NULL); } if (skipcc < 0 || skipcc > 1) { emsg(_(e_invarg)); @@ -9953,7 +9172,7 @@ static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) int col = 0; if (argvars[1].v_type != VAR_UNKNOWN) { - col = tv_get_number(&argvars[1]); + col = (int)tv_get_number(&argvars[1]); } rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char_u *)s) - col); @@ -9983,12 +9202,12 @@ static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) nchar--; } } else { - nbyte = nchar; + nbyte = (int)nchar; } } int len = 0; if (argvars[2].v_type != VAR_UNKNOWN) { - int charlen = tv_get_number(&argvars[2]); + int charlen = (int)tv_get_number(&argvars[2]); while (charlen > 0 && nbyte + len < (int)slen) { int off = nbyte + len; @@ -10000,7 +9219,7 @@ static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) charlen--; } } else { - len = slen - nbyte; // default: all bytes that are available. + len = (int)slen - nbyte; // default: all bytes that are available. } // Only return the overlap between the specified part and the actual @@ -10009,12 +9228,12 @@ static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) len += nbyte; nbyte = 0; } else if ((size_t)nbyte > slen) { - nbyte = slen; + nbyte = (int)slen; } if (len < 0) { len = 0; } else if (nbyte + len > (int)slen) { - len = slen - nbyte; + len = (int)slen - nbyte; } rettv->v_type = VAR_STRING; @@ -10036,7 +9255,7 @@ static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else if (argvars[2].v_type != VAR_UNKNOWN) { len = tv_get_number(&argvars[2]); } else { - len = slen - n; // Default len: all bytes that are available. + len = (varnumber_T)slen - n; // Default len: all bytes that are available. } // Only return the overlap between the specified part and the actual @@ -10045,19 +9264,19 @@ static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) len += n; n = 0; } else if (n > (varnumber_T)slen) { - n = slen; + n = (varnumber_T)slen; } if (len < 0) { len = 0; } else if (n + len > (varnumber_T)slen) { - len = slen - n; + 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 = n; off < (int)slen && len > 0; len--) { + for (off = (int)n; off < (int)slen && len > 0; len--) { off += utfc_ptr2len(p + off); } len = off - n; @@ -10165,7 +9384,7 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) int retList = 0; if (argvars[1].v_type != VAR_UNKNOWN) { - retList = tv_get_number_chk(&argvars[1], &error); + retList = (int)tv_get_number_chk(&argvars[1], &error); if (error) { return; } @@ -10238,7 +9457,7 @@ static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; bool transerr = false; - const int trans = tv_get_number_chk(&argvars[2], &transerr); + const int trans = (int)tv_get_number_chk(&argvars[2], &transerr); int id = 0; if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count @@ -10287,8 +9506,12 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) p = highlight_has_attr(id, HL_ITALIC, modec); } break; - case 'n': // name - p = get_highlight_name_ext(NULL, id - 1, false); + case 'n': + if (TOLOWER_ASC(what[1]) == 'o') { // nocombine + p = highlight_has_attr(id, HL_NOCOMBINE, modec); + } else { // name + p = get_highlight_name_ext(NULL, id - 1, false); + } break; case 'r': // reverse p = highlight_has_attr(id, HL_INVERSE, modec); @@ -10335,7 +9558,7 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "synIDtrans(id)" function static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int id = tv_get_number(&argvars[0]); + int id = (int)tv_get_number(&argvars[0]); if (id > 0) { id = syn_get_final_id(id); @@ -10662,10 +9885,10 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) const bool overlapped = false; const bool detach = false; ChannelStdinMode stdin_mode = kChannelStdinPipe; - uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin)); + 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, pty, rpc, overlapped, detach, stdin_mode, - cwd, term_width, curwin->w_height_inner, + cwd, term_width, (uint16_t)curwin->w_height_inner, env, &rettv->vval.v_number); if (rettv->vval.v_number <= 0) { return; @@ -10699,7 +9922,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) Error err = ERROR_INIT; // deprecated: use 'channel' buffer option dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"), - INTEGER_OBJ(chan->id), false, false, &err); + INTEGER_OBJ((Integer)chan->id), false, false, &err); api_clear_error(&err); dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"), INTEGER_OBJ(pid), false, false, &err); @@ -10740,8 +9963,8 @@ static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) if (!timer->paused && paused) { time_watcher_stop(&timer->tw); } else if (timer->paused && !paused) { - time_watcher_start(&timer->tw, timer_due_cb, timer->timeout, - timer->timeout); + time_watcher_start(&timer->tw, timer_due_cb, (uint64_t)timer->timeout, + (uint64_t)timer->timeout); } timer->paused = paused; } @@ -10766,7 +9989,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) } dictitem_T *const di = tv_dict_find(dict, S_LEN("repeat")); if (di != NULL) { - repeat = tv_get_number(&di->di_tv); + repeat = (int)tv_get_number(&di->di_tv); if (repeat == 0) { repeat = 1; } @@ -10777,8 +10000,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (!callback_from_typval(&callback, &argvars[1])) { return; } - rettv->vval.v_number = - timer_start(tv_get_number(&argvars[0]), repeat, &callback); + rettv->vval.v_number = (varnumber_T)timer_start(tv_get_number(&argvars[0]), repeat, &callback); } /// "timer_stop(timerid)" function @@ -10975,7 +10197,7 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } - rettv->vval.v_string = (char *)vim_strnsave(head, tail - head); + rettv->vval.v_string = (char *)vim_strnsave(head, (size_t)(tail - head)); } /// "type(expr)" function @@ -11047,12 +10269,6 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); } -/// "values(dict)" function -static void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_list(argvars, rettv, 1); -} - /// "virtcol(string)" function static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -11085,7 +10301,7 @@ static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) char_u str[2]; rettv->v_type = VAR_STRING; - str[0] = curbuf->b_visual_mode_eval; + str[0] = (char_u)curbuf->b_visual_mode_eval; str[1] = NUL; rettv->vval.v_string = (char *)vim_strsave(str); @@ -11299,29 +10515,29 @@ static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { dictitem_T *di; if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) { - curwin->w_cursor.lnum = tv_get_number(&di->di_tv); + curwin->w_cursor.lnum = (linenr_T)tv_get_number(&di->di_tv); } if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) { - curwin->w_cursor.col = tv_get_number(&di->di_tv); + curwin->w_cursor.col = (colnr_T)tv_get_number(&di->di_tv); } if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) { - curwin->w_cursor.coladd = tv_get_number(&di->di_tv); + curwin->w_cursor.coladd = (colnr_T)tv_get_number(&di->di_tv); } if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) { - curwin->w_curswant = tv_get_number(&di->di_tv); + curwin->w_curswant = (colnr_T)tv_get_number(&di->di_tv); curwin->w_set_curswant = false; } if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) { - set_topline(curwin, tv_get_number(&di->di_tv)); + set_topline(curwin, (linenr_T)tv_get_number(&di->di_tv)); } if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) { - curwin->w_topfill = tv_get_number(&di->di_tv); + curwin->w_topfill = (int)tv_get_number(&di->di_tv); } if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) { - curwin->w_leftcol = tv_get_number(&di->di_tv); + curwin->w_leftcol = (colnr_T)tv_get_number(&di->di_tv); } if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) { - curwin->w_skipcol = tv_get_number(&di->di_tv); + curwin->w_skipcol = (colnr_T)tv_get_number(&di->di_tv); } check_cursor(); diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h index 5f8d81c989..583ee0e75e 100644 --- a/src/nvim/eval/funcs.h +++ b/src/nvim/eval/funcs.h @@ -4,8 +4,6 @@ #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" -typedef void (*FunPtr)(void); - /// Prototype of C function that implements VimL function typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data); diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index e19cf411c0..fd57b45e86 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -19,6 +19,7 @@ #include "nvim/eval/typval.h" #include "nvim/eval/typval_encode.h" #include "nvim/eval/userfunc.h" +#include "nvim/eval/vars.h" #include "nvim/garray.h" #include "nvim/gettext.h" #include "nvim/globals.h" @@ -829,6 +830,454 @@ int tv_list_join(garray_T *const gap, list_T *const l, const char *const sep) return retval; } +/// "join()" function +void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_LIST) { + emsg(_(e_listreq)); + return; + } + const char *const sep = (argvars[1].v_type == VAR_UNKNOWN + ? " " + : tv_get_string_chk(&argvars[1])); + + rettv->v_type = VAR_STRING; + + if (sep != NULL) { + garray_T ga; + ga_init(&ga, (int)sizeof(char), 80); + tv_list_join(&ga, argvars[0].vval.v_list, sep); + ga_append(&ga, NUL); + rettv->vval.v_string = ga.ga_data; + } else { + rettv->vval.v_string = NULL; + } +} + +/// "list2str()" function +void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + garray_T ga; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (argvars[0].v_type != VAR_LIST) { + emsg(_(e_invarg)); + return; + } + + list_T *const l = argvars[0].vval.v_list; + if (l == NULL) { + return; // empty list results in empty string + } + + ga_init(&ga, 1, 80); + char buf[MB_MAXBYTES + 1]; + + TV_LIST_ITER_CONST(l, li, { + buf[utf_char2bytes((int)tv_get_number(TV_LIST_ITEM_TV(li)), (char *)buf)] = NUL; + ga_concat(&ga, (char *)buf); + }); + ga_append(&ga, NUL); + + rettv->vval.v_string = ga.ga_data; +} + +/// "remove({list})" function +void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) +{ + list_T *l; + bool error = false; + + if (var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + arg_errmsg, TV_TRANSLATE)) { + return; + } + + long idx = tv_get_number_chk(&argvars[1], &error); + + listitem_T *item; + + if (error) { + // Type error: do nothing, errmsg already given. + } else if ((item = tv_list_find(l, (int)idx)) == NULL) { + semsg(_(e_listidx), (int64_t)idx); + } else { + if (argvars[2].v_type == VAR_UNKNOWN) { + // Remove one item, return its value. + tv_list_drop_items(l, item, item); + *rettv = *TV_LIST_ITEM_TV(item); + xfree(item); + } else { + listitem_T *item2; + // Remove range of items, return list with values. + long end = tv_get_number_chk(&argvars[2], &error); + if (error) { + // Type error: do nothing. + } else if ((item2 = tv_list_find(l, (int)end)) == NULL) { + semsg(_(e_listidx), (int64_t)end); + } else { + int cnt = 0; + + listitem_T *li; + for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + cnt++; + if (li == item2) { + break; + } + } + if (li == NULL) { // Didn't find "item2" after "item". + emsg(_(e_invrange)); + } else { + tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt), + cnt); + } + } + } + } +} + +/// struct storing information about current sort +typedef struct { + int item_compare_ic; + bool item_compare_lc; + bool item_compare_numeric; + bool item_compare_numbers; + bool item_compare_float; + const char *item_compare_func; + partial_T *item_compare_partial; + dict_T *item_compare_selfdict; + bool item_compare_func_err; +} sortinfo_T; +static sortinfo_T *sortinfo = NULL; + +#define ITEM_COMPARE_FAIL 999 + +/// Compare functions for f_sort() and f_uniq() below. +static int item_compare(const void *s1, const void *s2, bool keep_zero) +{ + ListSortItem *const si1 = (ListSortItem *)s1; + ListSortItem *const si2 = (ListSortItem *)s2; + + typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item); + typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item); + + int res; + + if (sortinfo->item_compare_numbers) { + const varnumber_T v1 = tv_get_number(tv1); + const varnumber_T v2 = tv_get_number(tv2); + + res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; + goto item_compare_end; + } + + if (sortinfo->item_compare_float) { + const float_T v1 = tv_get_float(tv1); + const float_T v2 = tv_get_float(tv2); + + res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; + goto item_compare_end; + } + + char *tofree1 = NULL; + char *tofree2 = NULL; + char *p1; + char *p2; + + // encode_tv2string() puts quotes around a string and allocates memory. Don't + // do that for string variables. Use a single quote when comparing with + // a non-string to do what the docs promise. + if (tv1->v_type == VAR_STRING) { + if (tv2->v_type != VAR_STRING || sortinfo->item_compare_numeric) { + p1 = "'"; + } else { + p1 = tv1->vval.v_string; + } + } else { + tofree1 = p1 = encode_tv2string(tv1, NULL); + } + if (tv2->v_type == VAR_STRING) { + if (tv1->v_type != VAR_STRING || sortinfo->item_compare_numeric) { + p2 = "'"; + } else { + p2 = tv2->vval.v_string; + } + } else { + tofree2 = p2 = encode_tv2string(tv2, NULL); + } + if (p1 == NULL) { + p1 = ""; + } + if (p2 == NULL) { + p2 = ""; + } + if (!sortinfo->item_compare_numeric) { + if (sortinfo->item_compare_lc) { + res = strcoll(p1, p2); + } else { + res = sortinfo->item_compare_ic ? STRICMP(p1, p2): STRCMP(p1, p2); + } + } else { + double n1, n2; + n1 = strtod(p1, &p1); + n2 = strtod(p2, &p2); + res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1; + } + + xfree(tofree1); + xfree(tofree2); + +item_compare_end: + // When the result would be zero, compare the item indexes. Makes the + // sort stable. + if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. + res = si1->idx > si2->idx ? 1 : -1; + } + return res; +} + +static int item_compare_keeping_zero(const void *s1, const void *s2) +{ + return item_compare(s1, s2, true); +} + +static int item_compare_not_keeping_zero(const void *s1, const void *s2) +{ + return item_compare(s1, s2, false); +} + +static int item_compare2(const void *s1, const void *s2, bool keep_zero) +{ + ListSortItem *si1, *si2; + int res; + typval_T rettv; + typval_T argv[3]; + const char *func_name; + partial_T *partial = sortinfo->item_compare_partial; + + // shortcut after failure in previous call; compare all items equal + if (sortinfo->item_compare_func_err) { + return 0; + } + + si1 = (ListSortItem *)s1; + si2 = (ListSortItem *)s2; + + if (partial == NULL) { + func_name = sortinfo->item_compare_func; + } else { + func_name = (const char *)partial_name(partial); + } + + // Copy the values. This is needed to be able to set v_lock to VAR_FIXED + // in the copy without changing the original list items. + tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]); + tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]); + + rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.evaluate = true; + funcexe.partial = partial; + funcexe.selfdict = sortinfo->item_compare_selfdict; + res = call_func(func_name, -1, &rettv, 2, argv, &funcexe); + tv_clear(&argv[0]); + tv_clear(&argv[1]); + + if (res == FAIL) { + res = ITEM_COMPARE_FAIL; + } else { + res = (int)tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); + if (res > 0) { + res = 1; + } else if (res < 0) { + res = -1; + } + } + if (sortinfo->item_compare_func_err) { + res = ITEM_COMPARE_FAIL; // return value has wrong type + } + tv_clear(&rettv); + + // When the result would be zero, compare the pointers themselves. Makes + // the sort stable. + if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. + res = si1->idx > si2->idx ? 1 : -1; + } + + return res; +} + +static int item_compare2_keeping_zero(const void *s1, const void *s2) +{ + return item_compare2(s1, s2, true); +} + +static int item_compare2_not_keeping_zero(const void *s1, const void *s2) +{ + return item_compare2(s1, s2, false); +} + +/// "sort({list})" function +static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) +{ + ListSortItem *ptrs; + long len; + long i; + + // Pointer to current info struct used in compare function. Save and restore + // the current one for nested calls. + sortinfo_T info; + sortinfo_T *old_sortinfo = sortinfo; + sortinfo = &info; + + const char *const arg_errmsg = (sort + ? N_("sort() argument") + : N_("uniq() argument")); + + if (argvars[0].v_type != VAR_LIST) { + semsg(_(e_listarg), sort ? "sort()" : "uniq()"); + } else { + list_T *const l = argvars[0].vval.v_list; + if (var_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { + goto theend; + } + tv_list_set_ret(rettv, l); + + len = tv_list_len(l); + if (len <= 1) { + goto theend; // short list sorts pretty quickly + } + + info.item_compare_ic = false; + info.item_compare_lc = false; + info.item_compare_numeric = false; + info.item_compare_numbers = false; + info.item_compare_float = false; + info.item_compare_func = NULL; + info.item_compare_partial = NULL; + info.item_compare_selfdict = NULL; + + if (argvars[1].v_type != VAR_UNKNOWN) { + // optional second argument: {func} + if (argvars[1].v_type == VAR_FUNC) { + info.item_compare_func = (const char *)argvars[1].vval.v_string; + } else if (argvars[1].v_type == VAR_PARTIAL) { + info.item_compare_partial = argvars[1].vval.v_partial; + } else { + bool error = false; + + i = tv_get_number_chk(&argvars[1], &error); + if (error) { + goto theend; // type error; errmsg already given + } + if (i == 1) { + info.item_compare_ic = true; + } else if (argvars[1].v_type != VAR_NUMBER) { + info.item_compare_func = tv_get_string(&argvars[1]); + } else if (i != 0) { + emsg(_(e_invarg)); + goto theend; + } + if (info.item_compare_func != NULL) { + if (*info.item_compare_func == NUL) { + // empty string means default sort + info.item_compare_func = NULL; + } else if (strcmp(info.item_compare_func, "n") == 0) { + info.item_compare_func = NULL; + info.item_compare_numeric = true; + } else if (strcmp(info.item_compare_func, "N") == 0) { + info.item_compare_func = NULL; + info.item_compare_numbers = true; + } else if (strcmp(info.item_compare_func, "f") == 0) { + info.item_compare_func = NULL; + info.item_compare_float = true; + } else if (strcmp(info.item_compare_func, "i") == 0) { + info.item_compare_func = NULL; + info.item_compare_ic = true; + } else if (strcmp(info.item_compare_func, "l") == 0) { + info.item_compare_func = NULL; + info.item_compare_lc = true; + } + } + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + // optional third argument: {dict} + if (argvars[2].v_type != VAR_DICT) { + emsg(_(e_dictreq)); + goto theend; + } + info.item_compare_selfdict = argvars[2].vval.v_dict; + } + } + + // Make an array with each entry pointing to an item in the List. + ptrs = xmalloc((size_t)((unsigned)len * sizeof(ListSortItem))); + + if (sort) { + info.item_compare_func_err = false; + tv_list_item_sort(l, ptrs, + ((info.item_compare_func == NULL + && info.item_compare_partial == NULL) + ? item_compare_not_keeping_zero + : item_compare2_not_keeping_zero), + &info.item_compare_func_err); + if (info.item_compare_func_err) { + emsg(_("E702: Sort compare function failed")); + } + } else { + ListSorter item_compare_func_ptr; + + // f_uniq(): ptrs will be a stack of items to remove. + info.item_compare_func_err = false; + if (info.item_compare_func != NULL + || info.item_compare_partial != NULL) { + item_compare_func_ptr = item_compare2_keeping_zero; + } else { + item_compare_func_ptr = item_compare_keeping_zero; + } + + int idx = 0; + for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) + ; li != NULL;) { + listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); + if (item_compare_func_ptr(&prev_li, &li) == 0) { + if (info.item_compare_func_err) { // -V547 + emsg(_("E882: Uniq compare function failed")); + break; + } + li = tv_list_item_remove(l, li); + } else { + idx++; + li = TV_LIST_ITEM_NEXT(l, li); + } + } + } + + xfree(ptrs); + } + +theend: + sortinfo = old_sortinfo; +} + +/// "sort"({list})" function +void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + do_sort_uniq(argvars, rettv, true); +} + +/// "uniq({list})" function +void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + do_sort_uniq(argvars, rettv, false); +} + /// Check whether two lists are equal /// /// @param[in] l1 First list to compare. @@ -2199,6 +2648,66 @@ bool tv_blob_equal(const blob_T *const b1, const blob_T *const b2) return true; } +/// "remove({blob})" function +void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) +{ + blob_T *const b = argvars[0].vval.v_blob; + + if (b != NULL && var_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE)) { + return; + } + + bool error = false; + long idx = tv_get_number_chk(&argvars[1], &error); + + if (!error) { + const int len = tv_blob_len(b); + + if (idx < 0) { + // count from the end + idx = len + idx; + } + if (idx < 0 || idx >= len) { + semsg(_(e_blobidx), (int64_t)idx); + return; + } + if (argvars[2].v_type == VAR_UNKNOWN) { + // Remove one item, return its value. + char_u *const p = (char_u *)b->bv_ga.ga_data; + rettv->vval.v_number = (varnumber_T)(*(p + idx)); + memmove(p + idx, p + idx + 1, (size_t)(len - idx - 1)); + b->bv_ga.ga_len--; + } else { + // Remove range of items, return blob with values. + long end = tv_get_number_chk(&argvars[2], &error); + if (error) { + return; + } + if (end < 0) { + // count from the end + end = len + end; + } + if (end >= len || idx > end) { + semsg(_(e_blobidx), (int64_t)end); + return; + } + blob_T *const blob = tv_blob_alloc(); + blob->bv_ga.ga_len = (int)(end - idx + 1); + ga_grow(&blob->bv_ga, (int)(end - idx + 1)); + + char_u *const p = (char_u *)b->bv_ga.ga_data; + memmove((char_u *)blob->bv_ga.ga_data, p + idx, + (size_t)(end - idx + 1)); + tv_blob_set_ret(rettv, blob); + + if (len - end - 1 > 0) { + memmove(p + idx, p + end + 1, (size_t)(len - end - 1)); + } + b->bv_ga.ga_len -= (int)(end - idx + 1); + } + } +} + //{{{1 Generic typval operations //{{{2 Init/alloc/clear //{{{3 Alloc @@ -2243,6 +2752,118 @@ void tv_dict_alloc_ret(typval_T *const ret_tv) tv_dict_set_ret(ret_tv, d); } +/// Turn a dictionary into a list +/// +/// @param[in] tv Dictionary to convert. Is checked for actually being +/// a dictionary, will give an error if not. +/// @param[out] rettv Location where result will be saved. +/// @param[in] what What to save in rettv. +static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictListType what) +{ + if (tv->v_type != VAR_DICT) { + emsg(_(e_dictreq)); + return; + } + if (tv->vval.v_dict == NULL) { + return; + } + + tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); + + TV_DICT_ITER(tv->vval.v_dict, di, { + typval_T tv_item = { .v_lock = VAR_UNLOCKED }; + + switch (what) { + case kDictListKeys: + tv_item.v_type = VAR_STRING; + tv_item.vval.v_string = (char *)vim_strsave(di->di_key); + break; + case kDictListValues: + tv_copy(&di->di_tv, &tv_item); + break; + case kDictListItems: { + // items() + list_T *const sub_l = tv_list_alloc(2); + tv_item.v_type = VAR_LIST; + tv_item.vval.v_list = sub_l; + tv_list_ref(sub_l); + + tv_list_append_owned_tv(sub_l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = xstrdup((const char *)di->di_key), + }); + + tv_list_append_tv(sub_l, &di->di_tv); + + break; + } + } + + tv_list_append_owned_tv(rettv->vval.v_list, tv_item); + }); +} + +/// "items(dict)" function +void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_list(argvars, rettv, 2); +} + +/// "keys()" function +void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_list(argvars, rettv, 0); +} + +/// "values(dict)" function +void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_list(argvars, rettv, 1); +} + +/// "has_key()" function +void f_has_key(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_DICT) { + emsg(_(e_dictreq)); + return; + } + if (argvars[0].vval.v_dict == NULL) { + return; + } + + rettv->vval.v_number = tv_dict_find(argvars[0].vval.v_dict, + tv_get_string(&argvars[1]), + -1) != NULL; +} + +/// "remove({dict})" function +void tv_dict_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) +{ + dict_T *d; + if (argvars[2].v_type != VAR_UNKNOWN) { + semsg(_(e_toomanyarg), "remove()"); + } else if ((d = argvars[0].vval.v_dict) != NULL + && !var_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) { + const char *key = tv_get_string_chk(&argvars[1]); + if (key != NULL) { + dictitem_T *di = tv_dict_find(d, key, -1); + if (di == NULL) { + semsg(_(e_dictkey), key); + } else if (!var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE) + && !var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) { + *rettv = di->di_tv; + di->di_tv = TV_INITIAL_VALUE; + tv_dict_item_remove(d, di); + if (tv_dict_is_watched(d)) { + tv_dict_watcher_notify(d, key, NULL, rettv); + } + } + } + } +} + /// Allocate an empty blob for a return value. /// /// Also sets reference count. diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index c2579944e4..a90148bf23 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -9,13 +9,16 @@ #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" +#include "nvim/eval/funcs.h" #include "nvim/eval/userfunc.h" +#include "nvim/eval/vars.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/globals.h" +#include "nvim/insexpand.h" #include "nvim/lua/executor.h" #include "nvim/os/input.h" #include "nvim/regexp.h" @@ -1265,7 +1268,7 @@ void free_all_functions(void) // Clean up the current_funccal chain and the funccal stack. while (current_funccal != NULL) { tv_clear(current_funccal->rettv); - cleanup_function_call(current_funccal); + cleanup_function_call(current_funccal); // -V595 if (current_funccal == NULL && funccal_stack != NULL) { restore_funccal(); } diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c new file mode 100644 index 0000000000..d01fff6b94 --- /dev/null +++ b/src/nvim/eval/vars.c @@ -0,0 +1,1822 @@ +// 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 + +// eval/vars.c: functions for dealing with variables + +#include "nvim/ascii.h" +#include "nvim/autocmd.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/eval.h" +#include "nvim/eval/encode.h" +#include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/userfunc.h" +#include "nvim/eval/vars.h" +#include "nvim/ex_cmds.h" +#include "nvim/ex_docmd.h" +#include "nvim/ops.h" +#include "nvim/option.h" +#include "nvim/search.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/vars.c.generated.h" +#endif + +// TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead + +#define DICT_MAXNEST 100 // maximum nesting of lists and dicts + +static char *e_letunexp = N_("E18: Unexpected characters in :let"); +static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s"); + +/// Get a list of lines from a HERE document. The here document is a list of +/// lines surrounded by a marker. +/// cmd << {marker} +/// {line1} +/// {line2} +/// .... +/// {marker} +/// +/// The {marker} is a string. If the optional 'trim' word is supplied before the +/// marker, then the leading indentation before the lines (matching the +/// indentation in the 'cmd' line) is stripped. +/// +/// @return a List with {lines} or NULL. +static list_T *heredoc_get(exarg_T *eap, char *cmd) +{ + char *marker; + char *p; + int marker_indent_len = 0; + int text_indent_len = 0; + char *text_indent = NULL; + + if (eap->getline == NULL) { + emsg(_("E991: cannot use =<< here")); + return NULL; + } + + // Check for the optional 'trim' word before the marker + cmd = skipwhite(cmd); + if (STRNCMP(cmd, "trim", 4) == 0 + && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) { + cmd = skipwhite(cmd + 4); + + // Trim the indentation from all the lines in the here document. + // The amount of indentation trimmed is the same as the indentation of + // the first line after the :let command line. To find the end marker + // the indent of the :let command line is trimmed. + p = *eap->cmdlinep; + while (ascii_iswhite(*p)) { + p++; + marker_indent_len++; + } + text_indent_len = -1; + } + + // The marker is the next word. + if (*cmd != NUL && *cmd != '"') { + marker = skipwhite(cmd); + p = (char *)skiptowhite((char_u *)marker); + if (*skipwhite(p) != NUL && *skipwhite(p) != '"') { + emsg(_(e_trailing)); + return NULL; + } + *p = NUL; + if (islower(*marker)) { + emsg(_("E221: Marker cannot start with lower case letter")); + return NULL; + } + } else { + emsg(_("E172: Missing marker")); + return NULL; + } + + list_T *l = tv_list_alloc(0); + for (;;) { + int mi = 0; + int ti = 0; + + char *theline = eap->getline(NUL, eap->cookie, 0, false); + if (theline == NULL) { + semsg(_("E990: Missing end marker '%s'"), marker); + break; + } + + // with "trim": skip the indent matching the :let line to find the + // marker + if (marker_indent_len > 0 + && STRNCMP(theline, *eap->cmdlinep, marker_indent_len) == 0) { + mi = marker_indent_len; + } + if (STRCMP(marker, theline + mi) == 0) { + xfree(theline); + break; + } + if (text_indent_len == -1 && *theline != NUL) { + // set the text indent from the first line. + p = theline; + text_indent_len = 0; + while (ascii_iswhite(*p)) { + p++; + text_indent_len++; + } + text_indent = xstrnsave(theline, (size_t)text_indent_len); + } + // with "trim": skip the indent matching the first line + if (text_indent != NULL) { + for (ti = 0; ti < text_indent_len; ti++) { + if (theline[ti] != text_indent[ti]) { + break; + } + } + } + + tv_list_append_string(l, theline + ti, -1); + xfree(theline); + } + xfree(text_indent); + + return l; +} + +/// ":let" list all variable values +/// ":let var1 var2" list variable values +/// ":let var = expr" assignment command. +/// ":let var += expr" assignment command. +/// ":let var -= expr" assignment command. +/// ":let var *= expr" assignment command. +/// ":let var /= expr" assignment command. +/// ":let var %= expr" assignment command. +/// ":let var .= expr" assignment command. +/// ":let var ..= expr" assignment command. +/// ":let [var1, var2] = expr" unpack list. +/// ":let [name, ..., ; lastname] = expr" unpack list. +void ex_let(exarg_T *eap) +{ + ex_let_const(eap, false); +} + +/// ":cons[t] var = expr1" define constant +/// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list +/// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list +void ex_const(exarg_T *eap) +{ + ex_let_const(eap, true); +} + +static void ex_let_const(exarg_T *eap, const bool is_const) +{ + char *arg = eap->arg; + char *expr = NULL; + typval_T rettv; + int i; + int var_count = 0; + int semicolon = 0; + char op[2]; + char *argend; + int first = true; + + argend = (char *)skip_var_list(arg, &var_count, &semicolon); + if (argend == NULL) { + return; + } + if (argend > arg && argend[-1] == '.') { // For var.='str'. + argend--; + } + expr = skipwhite(argend); + if (*expr != '=' && !((vim_strchr("+-*/%.", *expr) != NULL + && expr[1] == '=') || STRNCMP(expr, "..=", 3) == 0)) { + // ":let" without "=": list variables + if (*arg == '[') { + emsg(_(e_invarg)); + } else if (!ends_excmd(*arg)) { + // ":let var1 var2" + arg = (char *)list_arg_vars(eap, (const char *)arg, &first); + } else if (!eap->skip) { + // ":let" + list_glob_vars(&first); + list_buf_vars(&first); + list_win_vars(&first); + list_tab_vars(&first); + list_script_vars(&first); + list_func_vars(&first); + list_vim_vars(&first); + } + eap->nextcmd = (char *)check_nextcmd((char_u *)arg); + } else if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') { + // HERE document + list_T *l = heredoc_get(eap, expr + 3); + if (l != NULL) { + tv_list_set_ret(&rettv, l); + if (!eap->skip) { + op[0] = '='; + op[1] = NUL; + (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, + is_const, (char *)op); + } + tv_clear(&rettv); + } + } else { + op[0] = '='; + op[1] = NUL; + if (*expr != '=') { + if (vim_strchr("+-*/%.", *expr) != NULL) { + op[0] = *expr; // +=, -=, *=, /=, %= or .= + if (expr[0] == '.' && expr[1] == '.') { // ..= + expr++; + } + } + expr = skipwhite(expr + 2); + } else { + expr = skipwhite(expr + 1); + } + + if (eap->skip) { + emsg_skip++; + } + i = eval0(expr, &rettv, &eap->nextcmd, !eap->skip); + if (eap->skip) { + if (i != FAIL) { + tv_clear(&rettv); + } + emsg_skip--; + } else if (i != FAIL) { + (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, + is_const, (char *)op); + tv_clear(&rettv); + } + } +} + +/// Assign the typevalue "tv" to the variable or variables at "arg_start". +/// Handles both "var" with any type and "[var, var; var]" with a list type. +/// When "op" is not NULL it points to a string with characters that +/// must appear after the variable(s). Use "+", "-" or "." for add, subtract +/// or concatenate. +/// +/// @param copy copy values from "tv", don't move +/// @param semicolon from skip_var_list() +/// @param var_count from skip_var_list() +/// @param is_const lock variables for :const +/// +/// @return OK or FAIL; +int ex_let_vars(char *arg_start, typval_T *tv, int copy, int semicolon, int var_count, int is_const, + char *op) +{ + char *arg = arg_start; + typval_T ltv; + + if (*arg != '[') { + // ":let var = expr" or ":for var in list" + if (ex_let_one(arg, tv, copy, is_const, op, op) == NULL) { + return FAIL; + } + return OK; + } + + // ":let [v1, v2] = list" or ":for [v1, v2] in listlist" + if (tv->v_type != VAR_LIST) { + emsg(_(e_listreq)); + return FAIL; + } + list_T *const l = tv->vval.v_list; + + const int len = tv_list_len(l); + if (semicolon == 0 && var_count < len) { + emsg(_("E687: Less targets than List items")); + return FAIL; + } + if (var_count - semicolon > len) { + emsg(_("E688: More targets than List items")); + return FAIL; + } + // List l may actually be NULL, but it should fail with E688 or even earlier + // if you try to do ":let [] = v:_null_list". + assert(l != NULL); + + listitem_T *item = tv_list_first(l); + size_t rest_len = (size_t)tv_list_len(l); + while (*arg != ']') { + arg = skipwhite(arg + 1); + arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, is_const, ",;]", op); + if (arg == NULL) { + return FAIL; + } + rest_len--; + + item = TV_LIST_ITEM_NEXT(l, item); + arg = skipwhite(arg); + if (*arg == ';') { + // Put the rest of the list (may be empty) in the var after ';'. + // Create a new list for this. + list_T *const rest_list = tv_list_alloc((ptrdiff_t)rest_len); + while (item != NULL) { + tv_list_append_tv(rest_list, TV_LIST_ITEM_TV(item)); + item = TV_LIST_ITEM_NEXT(l, item); + } + + ltv.v_type = VAR_LIST; + ltv.v_lock = VAR_UNLOCKED; + ltv.vval.v_list = rest_list; + tv_list_ref(rest_list); + + arg = ex_let_one(skipwhite(arg + 1), <v, false, is_const, "]", op); + tv_clear(<v); + if (arg == NULL) { + return FAIL; + } + break; + } else if (*arg != ',' && *arg != ']') { + internal_error("ex_let_vars()"); + return FAIL; + } + } + + return OK; +} + +/// Skip over assignable variable "var" or list of variables "[var, var]". +/// Used for ":let varvar = expr" and ":for varvar in expr". +/// For "[var, var]" increment "*var_count" for each variable. +/// for "[var, var; var]" set "semicolon". +/// +/// @return NULL for an error. +const char *skip_var_list(const char *arg, int *var_count, int *semicolon) +{ + const char *p; + const char *s; + + if (*arg == '[') { + // "[var, var]": find the matching ']'. + p = arg; + for (;;) { + p = skipwhite(p + 1); // skip whites after '[', ';' or ',' + s = skip_var_one((char *)p); + if (s == p) { + semsg(_(e_invarg2), p); + return NULL; + } + (*var_count)++; + + p = skipwhite(s); + if (*p == ']') { + break; + } else if (*p == ';') { + if (*semicolon == 1) { + emsg(_("E452: Double ; in list of variables")); + return NULL; + } + *semicolon = 1; + } else if (*p != ',') { + semsg(_(e_invarg2), p); + return NULL; + } + } + return p + 1; + } else { + return skip_var_one((char *)arg); + } +} + +/// Skip one (assignable) variable name, including @r, $VAR, &option, d.key, +/// l[idx]. +static const char *skip_var_one(const char *arg) +{ + if (*arg == '@' && arg[1] != NUL) { + return arg + 1 + utfc_ptr2len(arg + 1); + } + return (char *)find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg, + NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); +} + +/// List variables for hashtab "ht" with prefix "prefix". +/// +/// @param empty if true also list NULL strings as empty strings. +void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, int *first) +{ + hashitem_T *hi; + dictitem_T *di; + int todo; + + todo = (int)ht->ht_used; + for (hi = ht->ht_array; todo > 0 && !got_int; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + di = TV_DICT_HI2DI(hi); + char buf[IOSIZE]; + + // apply :filter /pat/ to variable name + xstrlcpy(buf, prefix, IOSIZE); + xstrlcat(buf, (char *)di->di_key, IOSIZE); + if (message_filtered((char_u *)buf)) { + continue; + } + + if (empty || di->di_tv.v_type != VAR_STRING + || di->di_tv.vval.v_string != NULL) { + list_one_var(di, prefix, first); + } + } + } +} + +/// List global variables. +static void list_glob_vars(int *first) +{ + list_hashtable_vars(&globvarht, "", true, first); +} + +/// List buffer variables. +static void list_buf_vars(int *first) +{ + list_hashtable_vars(&curbuf->b_vars->dv_hashtab, "b:", true, first); +} + +/// List window variables. +static void list_win_vars(int *first) +{ + list_hashtable_vars(&curwin->w_vars->dv_hashtab, "w:", true, first); +} + +/// List tab page variables. +static void list_tab_vars(int *first) +{ + list_hashtable_vars(&curtab->tp_vars->dv_hashtab, "t:", true, first); +} + +/// List variables in "arg". +static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) +{ + int error = false; + int len; + const char *name; + const char *name_start; + typval_T tv; + + while (!ends_excmd(*arg) && !got_int) { + if (error || eap->skip) { + arg = find_name_end(arg, NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); + if (!ascii_iswhite(*arg) && !ends_excmd(*arg)) { + emsg_severe = true; + emsg(_(e_trailing)); + break; + } + } else { + // get_name_len() takes care of expanding curly braces + name_start = name = arg; + char *tofree; + len = get_name_len(&arg, &tofree, true, true); + if (len <= 0) { + // This is mainly to keep test 49 working: when expanding + // curly braces fails overrule the exception error message. + if (len < 0 && !aborting()) { + emsg_severe = true; + semsg(_(e_invarg2), arg); + break; + } + error = true; + } else { + if (tofree != NULL) { + name = tofree; + } + if (get_var_tv(name, len, &tv, NULL, true, false) + == FAIL) { + error = true; + } else { + // handle d.key, l[idx], f(expr) + const char *const arg_subsc = arg; + if (handle_subscript(&arg, &tv, true, true, name, &name) == FAIL) { + error = true; + } else { + if (arg == arg_subsc && len == 2 && name[1] == ':') { + switch (*name) { + case 'g': + list_glob_vars(first); break; + case 'b': + list_buf_vars(first); break; + case 'w': + list_win_vars(first); break; + case 't': + list_tab_vars(first); break; + case 'v': + list_vim_vars(first); break; + case 's': + list_script_vars(first); break; + case 'l': + list_func_vars(first); break; + default: + semsg(_("E738: Can't list variables for %s"), name); + } + } else { + char *const s = encode_tv2echo(&tv, NULL); + const char *const used_name = (arg == arg_subsc + ? name + : name_start); + assert(used_name != NULL); + const ptrdiff_t name_size = (used_name == tofree + ? (ptrdiff_t)strlen(used_name) + : (arg - used_name)); + list_one_var_a("", used_name, name_size, + tv.v_type, s == NULL ? "" : s, first); + xfree(s); + } + tv_clear(&tv); + } + } + } + + xfree(tofree); + } + + arg = (const char *)skipwhite(arg); + } + + return arg; +} + +// TODO(ZyX-I): move to eval/ex_cmds + +/// Set one item of `:let var = expr` or `:let [v1, v2] = list` to its value +/// +/// @param[in] arg Start of the variable name. +/// @param[in] tv Value to assign to the variable. +/// @param[in] copy If true, copy value from `tv`. +/// @param[in] endchars Valid characters after variable name or NULL. +/// @param[in] op Operation performed: *op is `+`, `-`, `.` for `+=`, etc. +/// NULL for `=`. +/// +/// @return a pointer to the char just after the var name or NULL in case of +/// error. +static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bool is_const, + const char *const endchars, const char *const op) + FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT +{ + char *arg_end = NULL; + int len; + int opt_flags; + char *tofree = NULL; + + // ":let $VAR = expr": Set environment variable. + if (*arg == '$') { + if (is_const) { + emsg(_("E996: Cannot lock an environment variable")); + return NULL; + } + // Find the end of the name. + arg++; + char *name = arg; + len = get_env_len((const char **)&arg); + if (len == 0) { + semsg(_(e_invarg2), name - 1); + } else { + if (op != NULL && vim_strchr("+-*/%", *op) != NULL) { + semsg(_(e_letwrong), op); + } else if (endchars != NULL + && vim_strchr(endchars, *skipwhite(arg)) == NULL) { + emsg(_(e_letunexp)); + } else if (!check_secure()) { + const char c1 = name[len]; + name[len] = NUL; + const char *p = tv_get_string_chk(tv); + if (p != NULL && op != NULL && *op == '.') { + char *s = vim_getenv(name); + + if (s != NULL) { + tofree = (char *)concat_str((const char_u *)s, (const char_u *)p); + p = (const char *)tofree; + xfree(s); + } + } + if (p != NULL) { + os_setenv(name, p, 1); + if (STRICMP(name, "HOME") == 0) { + init_homedir(); + } else if (didset_vim && STRICMP(name, "VIM") == 0) { + didset_vim = false; + } else if (didset_vimruntime + && STRICMP(name, "VIMRUNTIME") == 0) { + didset_vimruntime = false; + } + arg_end = arg; + } + name[len] = c1; + xfree(tofree); + } + } + // ":let &option = expr": Set option value. + // ":let &l:option = expr": Set local option value. + // ":let &g:option = expr": Set global option value. + } else if (*arg == '&') { + if (is_const) { + emsg(_("E996: Cannot lock an option")); + return NULL; + } + // Find the end of the name. + char *const p = (char *)find_option_end((const char **)&arg, &opt_flags); + if (p == NULL + || (endchars != NULL + && vim_strchr(endchars, *skipwhite(p)) == NULL)) { + emsg(_(e_letunexp)); + } else { + varnumber_T n = 0; + getoption_T opt_type; + long numval; + char *stringval = NULL; + const char *s = NULL; + bool failed = false; + + const char c1 = *p; + *p = NUL; + + opt_type = get_option_value(arg, &numval, &stringval, opt_flags); + if (opt_type == gov_bool + || opt_type == gov_number + || opt_type == gov_hidden_bool + || opt_type == gov_hidden_number) { + // number, possibly hidden + n = (long)tv_get_number(tv); + } + + // Avoid setting a string option to the text "v:false" or similar. + if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) { + s = tv_get_string_chk(tv); + } + + if (op != NULL && *op != '=') { + if (((opt_type == gov_bool || opt_type == gov_number) && *op == '.') + || (opt_type == gov_string && *op != '.')) { + semsg(_(e_letwrong), op); + failed = true; // don't set the value + } else { + // number or bool + if (opt_type == gov_number || opt_type == gov_bool) { + switch (*op) { + case '+': + n = numval + n; break; + case '-': + n = numval - n; break; + case '*': + n = numval * n; break; + case '/': + n = num_divide(numval, n); break; + case '%': + n = num_modulus(numval, n); break; + } + s = NULL; + } else if (opt_type == gov_string && stringval != NULL && s != NULL) { + // string + char *const oldstringval = stringval; + stringval = (char *)concat_str((const char_u *)stringval, + (const char_u *)s); + xfree(oldstringval); + s = stringval; + } + } + } + + if (!failed) { + if (opt_type != gov_string || s != NULL) { + set_option_value(arg, n, s, opt_flags); + arg_end = p; + } else { + emsg(_(e_stringreq)); + } + } + *p = c1; + xfree(stringval); + } + // ":let @r = expr": Set register contents. + } else if (*arg == '@') { + if (is_const) { + emsg(_("E996: Cannot lock a register")); + return NULL; + } + arg++; + + int regname = utf_ptr2char(arg); + int mblen = utf_ptr2len(arg); + + if (op != NULL && vim_strchr("+-*/%", *op) != NULL) { + semsg(_(e_letwrong), op); + } else if (endchars != NULL + && vim_strchr(endchars, *skipwhite(arg + mblen)) == NULL) { + emsg(_(e_letunexp)); + } else { + char *s; + + char *ptofree = NULL; + const char *p = tv_get_string_chk(tv); + if (p != NULL && op != NULL && *op == '.') { + s = get_reg_contents(*arg == '@' ? '"' : regname, kGRegExprSrc); + if (s != NULL) { + ptofree = (char *)concat_str((char_u *)s, (const char_u *)p); + p = (const char *)ptofree; + xfree(s); + } + } + if (p != NULL) { + write_reg_contents(*arg == '@' ? '"' : regname, + (const char_u *)p, (ssize_t)STRLEN(p), false); + arg_end = arg + mblen; + } + xfree(ptofree); + } + // ":let var = expr": Set internal variable. + // ":let {expr} = expr": Idem, name made with curly braces + } else if (eval_isnamec1(*arg) || *arg == '{') { + lval_T lv; + + char *const p = get_lval(arg, tv, &lv, false, false, 0, FNE_CHECK_START); + if (p != NULL && lv.ll_name != NULL) { + if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) { + emsg(_(e_letunexp)); + } else { + set_var_lval(&lv, p, tv, copy, is_const, op); + arg_end = p; + } + } + clear_lval(&lv); + } else { + semsg(_(e_invarg2), arg); + } + + return arg_end; +} + +/// ":unlet[!] var1 ... " command. +void ex_unlet(exarg_T *eap) +{ + ex_unletlock(eap, eap->arg, 0, do_unlet_var); +} + +// TODO(ZyX-I): move to eval/ex_cmds + +/// ":lockvar" and ":unlockvar" commands +void ex_lockvar(exarg_T *eap) +{ + char *arg = eap->arg; + int deep = 2; + + if (eap->forceit) { + deep = -1; + } else if (ascii_isdigit(*arg)) { + deep = getdigits_int(&arg, false, -1); + arg = skipwhite(arg); + } + + ex_unletlock(eap, arg, deep, do_lock_var); +} + +// TODO(ZyX-I): move to eval/ex_cmds + +/// Common parsing logic for :unlet, :lockvar and :unlockvar. +/// +/// Invokes `callback` afterwards if successful and `eap->skip == false`. +/// +/// @param[in] eap Ex command arguments for the command. +/// @param[in] argstart Start of the string argument for the command. +/// @param[in] deep Levels to (un)lock for :(un)lockvar, -1 to (un)lock +/// everything. +/// @param[in] callback Appropriate handler for the command. +static void ex_unletlock(exarg_T *eap, char *argstart, int deep, ex_unletlock_callback callback) + FUNC_ATTR_NONNULL_ALL +{ + char *arg = argstart; + char *name_end; + bool error = false; + lval_T lv; + + do { + if (*arg == '$') { + lv.ll_name = (const char *)arg; + lv.ll_tv = NULL; + arg++; + if (get_env_len((const char **)&arg) == 0) { + semsg(_(e_invarg2), arg - 1); + return; + } + if (!error && !eap->skip && callback(&lv, arg, eap, deep) == FAIL) { + error = true; + } + name_end = arg; + } else { + // Parse the name and find the end. + name_end = get_lval(arg, NULL, &lv, true, eap->skip || error, + 0, FNE_CHECK_START); + if (lv.ll_name == NULL) { + error = true; // error, but continue parsing. + } + if (name_end == NULL + || (!ascii_iswhite(*name_end) && !ends_excmd(*name_end))) { + if (name_end != NULL) { + emsg_severe = true; + emsg(_(e_trailing)); + } + if (!(eap->skip || error)) { + clear_lval(&lv); + } + break; + } + + if (!error && !eap->skip && callback(&lv, name_end, eap, deep) == FAIL) { + error = true; + } + + if (!eap->skip) { + clear_lval(&lv); + } + } + arg = skipwhite(name_end); + } while (!ends_excmd(*arg)); + + eap->nextcmd = (char *)check_nextcmd((char_u *)arg); +} + +// TODO(ZyX-I): move to eval/ex_cmds + +/// Unlet a variable indicated by `lp`. +/// +/// @param[in] lp The lvalue. +/// @param[in] name_end End of the string argument for the command. +/// @param[in] eap Ex command arguments for :unlet. +/// @param[in] deep Unused. +/// +/// @return OK on success, or FAIL on failure. +static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_ATTR_UNUSED) + FUNC_ATTR_NONNULL_ALL +{ + int forceit = eap->forceit; + int ret = OK; + int cc; + + if (lp->ll_tv == NULL) { + cc = (char_u)(*name_end); + *name_end = NUL; + + // Environment variable, normal name or expanded name. + if (*lp->ll_name == '$') { + os_unsetenv(lp->ll_name + 1); + } else if (do_unlet(lp->ll_name, lp->ll_name_len, forceit) == FAIL) { + ret = FAIL; + } + *name_end = (char)cc; + } else if ((lp->ll_list != NULL + // ll_list is not NULL when lvalue is not in a list, NULL lists + // yield E689. + && var_check_lock(tv_list_locked(lp->ll_list), + lp->ll_name, + lp->ll_name_len)) + || (lp->ll_dict != NULL + && var_check_lock(lp->ll_dict->dv_lock, + lp->ll_name, + lp->ll_name_len))) { + return FAIL; + } else if (lp->ll_range) { + assert(lp->ll_list != NULL); + // Delete a range of List items. + listitem_T *const first_li = lp->ll_li; + listitem_T *last_li = first_li; + for (;;) { + listitem_T *const li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); + if (var_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock, + lp->ll_name, + lp->ll_name_len)) { + return false; + } + lp->ll_li = li; + lp->ll_n1++; + if (lp->ll_li == NULL || (!lp->ll_empty2 && lp->ll_n2 < lp->ll_n1)) { + break; + } else { + last_li = lp->ll_li; + } + } + tv_list_remove_items(lp->ll_list, first_li, last_li); + } else { + if (lp->ll_list != NULL) { + // unlet a List item. + tv_list_item_remove(lp->ll_list, lp->ll_li); + } else { + // unlet a Dictionary item. + dict_T *d = lp->ll_dict; + assert(d != NULL); + dictitem_T *di = lp->ll_di; + bool watched = tv_dict_is_watched(d); + char *key = NULL; + typval_T oldtv; + + if (watched) { + tv_copy(&di->di_tv, &oldtv); + // need to save key because dictitem_remove will free it + key = xstrdup((char *)di->di_key); + } + + tv_dict_item_remove(d, di); + + if (watched) { + tv_dict_watcher_notify(d, key, NULL, &oldtv); + tv_clear(&oldtv); + xfree(key); + } + } + } + + return ret; +} + +// TODO(ZyX-I): move to eval/ex_cmds + +/// unlet a variable +/// +/// @param[in] name Variable name to unlet. +/// @param[in] name_len Variable name length. +/// @param[in] forceit If true, do not complain if variable doesn’t exist. +/// +/// @return OK if it existed, FAIL otherwise. +int do_unlet(const char *const name, const size_t name_len, const bool forceit) + FUNC_ATTR_NONNULL_ALL +{ + const char *varname; + dict_T *dict; + hashtab_T *ht = find_var_ht_dict(name, name_len, &varname, &dict); + + if (ht != NULL && *varname != NUL) { + dict_T *d = get_current_funccal_dict(ht); + if (d == NULL) { + if (ht == &globvarht) { + d = &globvardict; + } else if (is_compatht(ht)) { + d = &vimvardict; + } else { + dictitem_T *const di = find_var_in_ht(ht, *name, "", 0, false); + d = di->di_tv.vval.v_dict; + } + if (d == NULL) { + internal_error("do_unlet()"); + return FAIL; + } + } + + hashitem_T *hi = hash_find(ht, varname); + if (HASHITEM_EMPTY(hi)) { + hi = find_hi_in_scoped_ht(name, &ht); + } + if (hi != NULL && !HASHITEM_EMPTY(hi)) { + dictitem_T *const di = TV_DICT_HI2DI(hi); + if (var_check_fixed(di->di_flags, name, TV_CSTRING) + || var_check_ro(di->di_flags, name, TV_CSTRING) + || var_check_lock(d->dv_lock, name, TV_CSTRING)) { + return FAIL; + } + + if (var_check_lock(d->dv_lock, name, TV_CSTRING)) { + return FAIL; + } + + typval_T oldtv; + bool watched = tv_dict_is_watched(dict); + + if (watched) { + tv_copy(&di->di_tv, &oldtv); + } + + delete_var(ht, hi); + + if (watched) { + tv_dict_watcher_notify(dict, varname, NULL, &oldtv); + tv_clear(&oldtv); + } + return OK; + } + } + if (forceit) { + return OK; + } + semsg(_("E108: No such variable: \"%s\""), name); + return FAIL; +} + +// TODO(ZyX-I): move to eval/ex_cmds + +/// Lock or unlock variable indicated by `lp`. +/// +/// Locks if `eap->cmdidx == CMD_lockvar`, unlocks otherwise. +/// +/// @param[in] lp The lvalue. +/// @param[in] name_end Unused. +/// @param[in] eap Ex command arguments for :(un)lockvar. +/// @param[in] deep Levels to (un)lock, -1 to (un)lock everything. +/// +/// @return OK on success, or FAIL on failure. +static int do_lock_var(lval_T *lp, char *name_end FUNC_ATTR_UNUSED, exarg_T *eap, int deep) + FUNC_ATTR_NONNULL_ARG(1, 3) +{ + bool lock = eap->cmdidx == CMD_lockvar; + int ret = OK; + + if (deep == 0) { // Nothing to do. + return OK; + } + + if (lp->ll_tv == NULL) { + if (*lp->ll_name == '$') { + semsg(_(e_lock_unlock), lp->ll_name); + ret = FAIL; + } else { + // Normal name or expanded name. + dictitem_T *const di = find_var(lp->ll_name, lp->ll_name_len, NULL, + true); + if (di == NULL) { + ret = FAIL; + } else if ((di->di_flags & DI_FLAGS_FIX) + && di->di_tv.v_type != VAR_DICT + && di->di_tv.v_type != VAR_LIST) { + // For historical reasons this error is not given for Lists and + // Dictionaries. E.g. b: dictionary may be locked/unlocked. + semsg(_(e_lock_unlock), lp->ll_name); + ret = FAIL; + } else { + if (lock) { + di->di_flags |= DI_FLAGS_LOCK; + } else { + di->di_flags &= (uint8_t)(~DI_FLAGS_LOCK); + } + tv_item_lock(&di->di_tv, deep, lock, false); + } + } + } else if (lp->ll_range) { + listitem_T *li = lp->ll_li; + + // (un)lock a range of List items. + while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { + tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock, false); + li = TV_LIST_ITEM_NEXT(lp->ll_list, li); + lp->ll_n1++; + } + } else if (lp->ll_list != NULL) { + // (un)lock a List item. + tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock, false); + } else { + // (un)lock a Dictionary item. + tv_item_lock(&lp->ll_di->di_tv, deep, lock, false); + } + + return ret; +} + +/// Get the value of internal variable "name". +/// Return OK or FAIL. If OK is returned "rettv" must be cleared. +/// +/// @param len length of "name" +/// @param rettv NULL when only checking existence +/// @param dip non-NULL when typval's dict item is needed +/// @param verbose may give error message +/// @param no_autoload do not use script autoloading +int get_var_tv(const char *name, int len, typval_T *rettv, dictitem_T **dip, bool verbose, + bool no_autoload) +{ + int ret = OK; + typval_T *tv = NULL; + dictitem_T *v; + + v = find_var(name, (size_t)len, NULL, no_autoload); + if (v != NULL) { + tv = &v->di_tv; + if (dip != NULL) { + *dip = v; + } + } + + if (tv == NULL) { + if (rettv != NULL && verbose) { + semsg(_("E121: Undefined variable: %.*s"), len, name); + } + ret = FAIL; + } else if (rettv != NULL) { + tv_copy(tv, rettv); + } + + return ret; +} + +/// @return the string value of a (global/local) variable or +/// NULL when it doesn't exist. +/// +/// @see tv_get_string() for how long the pointer remains valid. +char_u *get_var_value(const char *const name) +{ + dictitem_T *v; + + v = find_var(name, strlen(name), NULL, false); + if (v == NULL) { + return NULL; + } + return (char_u *)tv_get_string(&v->di_tv); +} + +/// Clean up a list of internal variables. +/// Frees all allocated variables and the value they contain. +/// Clears hashtab "ht", does not free it. +void vars_clear(hashtab_T *ht) +{ + vars_clear_ext(ht, true); +} + +/// Like vars_clear(), but only free the value if "free_val" is TRUE. +void vars_clear_ext(hashtab_T *ht, int free_val) +{ + int todo; + hashitem_T *hi; + dictitem_T *v; + + hash_lock(ht); + todo = (int)ht->ht_used; + for (hi = ht->ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + + // Free the variable. Don't remove it from the hashtab, + // ht_array might change then. hash_clear() takes care of it + // later. + v = TV_DICT_HI2DI(hi); + if (free_val) { + tv_clear(&v->di_tv); + } + if (v->di_flags & DI_FLAGS_ALLOC) { + xfree(v); + } + } + } + hash_clear(ht); + ht->ht_used = 0; +} + +/// Delete a variable from hashtab "ht" at item "hi". +/// Clear the variable value and free the dictitem. +void delete_var(hashtab_T *ht, hashitem_T *hi) +{ + dictitem_T *di = TV_DICT_HI2DI(hi); + + hash_remove(ht, hi); + tv_clear(&di->di_tv); + xfree(di); +} + +/// List the value of one internal variable. +static void list_one_var(dictitem_T *v, const char *prefix, int *first) +{ + char *const s = encode_tv2echo(&v->di_tv, NULL); + list_one_var_a(prefix, (const char *)v->di_key, (ptrdiff_t)STRLEN(v->di_key), + v->di_tv.v_type, (s == NULL ? "" : s), first); + xfree(s); +} + +/// @param[in] name_len Length of the name. May be -1, in this case strlen() +/// will be used. +/// @param[in,out] first When true clear rest of screen and set to false. +static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t name_len, + const VarType type, const char *string, int *first) +{ + // don't use msg() or msg_attr() to avoid overwriting "v:statusmsg" + msg_start(); + msg_puts(prefix); + if (name != NULL) { // "a:" vars don't have a name stored + msg_puts_attr_len(name, name_len, 0); + } + msg_putchar(' '); + msg_advance(22); + if (type == VAR_NUMBER) { + msg_putchar('#'); + } else if (type == VAR_FUNC || type == VAR_PARTIAL) { + msg_putchar('*'); + } else if (type == VAR_LIST) { + msg_putchar('['); + if (*string == '[') { + string++; + } + } else if (type == VAR_DICT) { + msg_putchar('{'); + if (*string == '{') { + string++; + } + } else { + msg_putchar(' '); + } + + msg_outtrans((char *)string); + + if (type == VAR_FUNC || type == VAR_PARTIAL) { + msg_puts("()"); + } + if (*first) { + msg_clr_eos(); + *first = false; + } +} + +/// Set variable to the given value +/// +/// If the variable already exists, the value is updated. Otherwise the variable +/// is created. +/// +/// @param[in] name Variable name to set. +/// @param[in] name_len Length of the variable name. +/// @param tv Variable value. +/// @param[in] copy True if value in tv is to be copied. +void set_var(const char *name, const size_t name_len, typval_T *const tv, const bool copy) + FUNC_ATTR_NONNULL_ALL +{ + set_var_const(name, name_len, tv, copy, false); +} + +/// Set variable to the given value +/// +/// If the variable already exists, the value is updated. Otherwise the variable +/// is created. +/// +/// @param[in] name Variable name to set. +/// @param[in] name_len Length of the variable name. +/// @param tv Variable value. +/// @param[in] copy True if value in tv is to be copied. +/// @param[in] is_const True if value in tv is to be locked. +void set_var_const(const char *name, const size_t name_len, typval_T *const tv, const bool copy, + const bool is_const) + FUNC_ATTR_NONNULL_ALL +{ + dictitem_T *v; + hashtab_T *ht; + dict_T *dict; + + const char *varname; + ht = find_var_ht_dict(name, name_len, &varname, &dict); + const bool watched = tv_dict_is_watched(dict); + + if (ht == NULL || *varname == NUL) { + semsg(_(e_illvar), name); + return; + } + v = find_var_in_ht(ht, 0, varname, name_len - (size_t)(varname - name), true); + + // Search in parent scope which is possible to reference from lambda + if (v == NULL) { + v = find_var_in_scoped_ht(name, name_len, true); + } + + if (tv_is_func(*tv) && !var_check_func_name(name, v == NULL)) { + return; + } + + typval_T oldtv = TV_INITIAL_VALUE; + if (v != NULL) { + if (is_const) { + emsg(_(e_cannot_mod)); + return; + } + + // existing variable, need to clear the value + if (var_check_ro(v->di_flags, name, name_len) + || var_check_lock(v->di_tv.v_lock, name, name_len)) { + return; + } + + // Handle setting internal v: variables separately where needed to + // prevent changing the type. + if (is_vimvarht(ht)) { + if (v->di_tv.v_type == VAR_STRING) { + XFREE_CLEAR(v->di_tv.vval.v_string); + if (copy || tv->v_type != VAR_STRING) { + const char *const val = tv_get_string(tv); + + // Careful: when assigning to v:errmsg and tv_get_string() + // causes an error message the variable will already be set. + if (v->di_tv.vval.v_string == NULL) { + v->di_tv.vval.v_string = xstrdup(val); + } + } else { + // Take over the string to avoid an extra alloc/free. + v->di_tv.vval.v_string = tv->vval.v_string; + tv->vval.v_string = NULL; + } + return; + } else if (v->di_tv.v_type == VAR_NUMBER) { + v->di_tv.vval.v_number = tv_get_number(tv); + if (strcmp(varname, "searchforward") == 0) { + set_search_direction(v->di_tv.vval.v_number ? '/' : '?'); + } else if (strcmp(varname, "hlsearch") == 0) { + no_hlsearch = !v->di_tv.vval.v_number; + redraw_all_later(SOME_VALID); + } + return; + } else if (v->di_tv.v_type != tv->v_type) { + semsg(_("E963: setting %s to value with wrong type"), name); + return; + } + } + + if (watched) { + tv_copy(&v->di_tv, &oldtv); + } + tv_clear(&v->di_tv); + } else { // Add a new variable. + // Can't add "v:" or "a:" variable. + if (is_vimvarht(ht) || ht == get_funccal_args_ht()) { + semsg(_(e_illvar), name); + return; + } + + // Make sure the variable name is valid. + if (!valid_varname(varname)) { + return; + } + + // Make sure dict is valid + assert(dict != NULL); + + v = xmalloc(sizeof(dictitem_T) + strlen(varname)); + STRCPY(v->di_key, varname); + if (tv_dict_add(dict, v) == FAIL) { + xfree(v); + return; + } + v->di_flags = DI_FLAGS_ALLOC; + if (is_const) { + v->di_flags |= DI_FLAGS_LOCK; + } + } + + if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) { + tv_copy(tv, &v->di_tv); + } else { + v->di_tv = *tv; + v->di_tv.v_lock = VAR_UNLOCKED; + tv_init(tv); + } + + if (watched) { + if (oldtv.v_type == VAR_UNKNOWN) { + tv_dict_watcher_notify(dict, (char *)v->di_key, &v->di_tv, NULL); + } else { + tv_dict_watcher_notify(dict, (char *)v->di_key, &v->di_tv, &oldtv); + tv_clear(&oldtv); + } + } + + if (is_const) { + // Like :lockvar! name: lock the value and what it contains, but only + // if the reference count is up to one. That locks only literal + // values. + tv_item_lock(&v->di_tv, DICT_MAXNEST, true, true); + } +} + +/// Check whether variable is read-only (DI_FLAGS_RO, DI_FLAGS_RO_SBX) +/// +/// Also gives an error message. +/// +/// @param[in] flags di_flags attribute value. +/// @param[in] name Variable name, for use in error message. +/// @param[in] name_len Variable name length. Use #TV_TRANSLATE to translate +/// variable name and compute the length. Use #TV_CSTRING +/// to compute the length with strlen() without +/// translating. +/// +/// Both #TV_… values are used for optimization purposes: +/// variable name with its length is needed only in case +/// of error, when no error occurs computing them is +/// a waste of CPU resources. This especially applies to +/// gettext. +/// +/// @return True if variable is read-only: either always or in sandbox when +/// sandbox is enabled, false otherwise. +bool var_check_ro(const int flags, const char *name, size_t name_len) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + const char *error_message = NULL; + if (flags & DI_FLAGS_RO) { + error_message = _(e_readonlyvar); + } else if ((flags & DI_FLAGS_RO_SBX) && sandbox) { + error_message = N_("E794: Cannot set variable in the sandbox: \"%.*s\""); + } + + if (error_message == NULL) { + return false; + } + if (name_len == TV_TRANSLATE) { + name = _(name); + name_len = strlen(name); + } else if (name_len == TV_CSTRING) { + name_len = strlen(name); + } + + semsg(_(error_message), (int)name_len, name); + + return true; +} + +/// Check whether variable is fixed (DI_FLAGS_FIX) +/// +/// Also gives an error message. +/// +/// @param[in] flags di_flags attribute value. +/// @param[in] name Variable name, for use in error message. +/// @param[in] name_len Variable name length. Use #TV_TRANSLATE to translate +/// variable name and compute the length. Use #TV_CSTRING +/// to compute the length with strlen() without +/// translating. +/// +/// Both #TV_… values are used for optimization purposes: +/// variable name with its length is needed only in case +/// of error, when no error occurs computing them is +/// a waste of CPU resources. This especially applies to +/// gettext. +/// +/// @return True if variable is fixed, false otherwise. +bool var_check_fixed(const int flags, const char *name, size_t name_len) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + if (flags & DI_FLAGS_FIX) { + if (name_len == TV_TRANSLATE) { + name = _(name); + name_len = strlen(name); + } else if (name_len == TV_CSTRING) { + name_len = strlen(name); + } + semsg(_("E795: Cannot delete variable %.*s"), (int)name_len, name); + return true; + } + return false; +} + +// TODO(ZyX-I): move to eval/expressions + +/// Check if name is a valid name to assign funcref to +/// +/// @param[in] name Possible function/funcref name. +/// @param[in] new_var True if it is a name for a variable. +/// +/// @return false in case of error, true in case of success. Also gives an +/// error message if appropriate. +bool var_check_func_name(const char *const name, const bool new_var) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + // Allow for w: b: s: and t:. + if (!(vim_strchr("wbst", name[0]) != NULL && name[1] == ':') + && !ASCII_ISUPPER((name[0] != NUL && name[1] == ':') + ? name[2] : name[0])) { + semsg(_("E704: Funcref variable name must start with a capital: %s"), name); + return false; + } + // Don't allow hiding a function. When "v" is not NULL we might be + // assigning another function to the same var, the type is checked + // below. + if (new_var && function_exists(name, false)) { + semsg(_("E705: Variable name conflicts with existing function: %s"), name); + return false; + } + return true; +} + +// TODO(ZyX-I): move to eval/expressions + +/// Check if a variable name is valid +/// +/// @param[in] varname Variable name to check. +/// +/// @return false when variable name is not valid, true when it is. Also gives +/// an error message if appropriate. +bool valid_varname(const char *varname) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + for (const char *p = varname; *p != NUL; p++) { + if (!eval_isnamec1((int)(uint8_t)(*p)) + && (p == varname || !ascii_isdigit(*p)) + && *p != AUTOLOAD_CHAR) { + semsg(_(e_illvar), varname); + return false; + } + } + return true; +} + +/// Implements the logic to retrieve local variable and option values. +/// Used by "getwinvar()" "gettabvar()" "gettabwinvar()" "getbufvar()". +/// +/// @param deftv default value if not found +/// @param htname 't'ab, 'w'indow or 'b'uffer local +/// @param tp can be NULL +/// @param buf ignored if htname is not 'b' +static void get_var_from(const char *varname, typval_T *rettv, typval_T *deftv, int htname, + tabpage_T *tp, win_T *win, buf_T *buf) +{ + bool done = false; + const bool do_change_curbuf = buf != NULL && htname == 'b'; + + emsg_off++; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + if (varname != NULL && tp != NULL && win != NULL && (htname != 'b' || buf != NULL)) { + // Set curwin to be our win, temporarily. Also set the tabpage, + // otherwise the window is not valid. Only do this when needed, + // autocommands get blocked. + // If we have a buffer reference avoid the switching, we're saving and + // restoring curbuf directly. + const bool need_switch_win = !(tp == curtab && win == curwin) && !do_change_curbuf; + switchwin_T switchwin; + if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) { + if (*varname == '&' && htname != 't') { + buf_T *const save_curbuf = curbuf; + + // Change curbuf so the option is read from the correct buffer. + if (do_change_curbuf) { + curbuf = buf; + } + + if (varname[1] == NUL) { + // get all window-local or buffer-local options in a dict + dict_T *opts = get_winbuf_options(htname == 'b'); + + if (opts != NULL) { + tv_dict_set_ret(rettv, opts); + done = true; + } + } else if (get_option_tv(&varname, rettv, true) == OK) { + // Local option + done = true; + } + + curbuf = save_curbuf; + } else if (*varname == NUL) { + const ScopeDictDictItem *v; + // Empty string: return a dict with all the local variables. + if (htname == 'b') { + v = &buf->b_bufvar; + } else if (htname == 'w') { + v = &win->w_winvar; + } else { + v = &tp->tp_winvar; + } + tv_copy(&v->di_tv, rettv); + done = true; + } else { + hashtab_T *ht; + + if (htname == 'b') { + ht = &buf->b_vars->dv_hashtab; + } else if (htname == 'w') { + ht = &win->w_vars->dv_hashtab; + } else { + ht = &tp->tp_vars->dv_hashtab; + } + + // Look up the variable. + const dictitem_T *const v = find_var_in_ht(ht, htname, varname, strlen(varname), false); + if (v != NULL) { + tv_copy(&v->di_tv, rettv); + done = true; + } + } + } + + if (need_switch_win) { + // restore previous notion of curwin + restore_win(&switchwin, true); + } + } + + if (!done && deftv->v_type != VAR_UNKNOWN) { + // use the default value + tv_copy(deftv, rettv); + } + + emsg_off--; +} + +/// getwinvar() and gettabwinvar() +/// +/// @param off 1 for gettabwinvar() +static void getwinvar(typval_T *argvars, typval_T *rettv, int off) +{ + tabpage_T *tp; + + if (off == 1) { + tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); + } else { + tp = curtab; + } + win_T *const win = find_win_by_nr(&argvars[off], tp); + const char *const varname = tv_get_string_chk(&argvars[off + 1]); + + get_var_from(varname, rettv, &argvars[off + 2], 'w', tp, win, NULL); +} + +/// Set option "varname" to the value of "varp" for the current buffer/window. +static void set_option_from_tv(const char *varname, typval_T *varp) +{ + long numval = 0; + const char *strval; + bool error = false; + char nbuf[NUMBUFLEN]; + + if (varp->v_type == VAR_BOOL) { + if (is_string_option(varname)) { + emsg(_(e_stringreq)); + return; + } + numval = (long)varp->vval.v_number; + strval = "0"; // avoid using "false" + } else { + numval = (long)tv_get_number_chk(varp, &error); + strval = tv_get_string_buf_chk(varp, nbuf); + } + if (!error && strval != NULL) { + set_option_value(varname, numval, strval, OPT_LOCAL); + } +} + +/// "setwinvar()" and "settabwinvar()" functions +static void setwinvar(typval_T *argvars, typval_T *rettv, int off) +{ + if (check_secure()) { + return; + } + + tabpage_T *tp = NULL; + if (off == 1) { + tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); + } else { + tp = curtab; + } + win_T *const win = find_win_by_nr(&argvars[off], tp); + const char *varname = tv_get_string_chk(&argvars[off + 1]); + typval_T *varp = &argvars[off + 2]; + + if (win != NULL && varname != NULL && varp != NULL) { + bool need_switch_win = !(tp == curtab && win == curwin); + switchwin_T switchwin; + if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) { + if (*varname == '&') { + set_option_from_tv(varname + 1, varp); + } else { + const size_t varname_len = strlen(varname); + char *const winvarname = xmalloc(varname_len + 3); + memcpy(winvarname, "w:", 2); + memcpy(winvarname + 2, varname, varname_len + 1); + set_var(winvarname, varname_len + 2, varp, true); + xfree(winvarname); + } + } + if (need_switch_win) { + restore_win(&switchwin, true); + } + } +} + +bool var_exists(const char *var) + FUNC_ATTR_NONNULL_ALL +{ + char *tofree; + bool n = false; + + // get_name_len() takes care of expanding curly braces + const char *name = var; + const int len = get_name_len(&var, &tofree, true, false); + if (len > 0) { + typval_T tv; + + if (tofree != NULL) { + name = tofree; + } + n = get_var_tv(name, len, &tv, NULL, false, true) == OK; + if (n) { + // Handle d.key, l[idx], f(expr). + n = handle_subscript(&var, &tv, true, false, name, &name) == OK; + if (n) { + tv_clear(&tv); + } + } + } + if (*var != NUL) { + n = false; + } + + xfree(tofree); + return n; +} + +/// "gettabvar()" function +void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const varname = tv_get_string_chk(&argvars[1]); + tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); + win_T *win = NULL; + + if (tp != NULL) { + win = tp == curtab || tp->tp_firstwin == NULL ? firstwin : tp->tp_firstwin; + } + + get_var_from(varname, rettv, &argvars[2], 't', tp, win, NULL); +} + +/// "gettabwinvar()" function +void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + getwinvar(argvars, rettv, 1); +} + +/// "getwinvar()" function +void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + getwinvar(argvars, rettv, 0); +} + +/// "getbufvar()" function +void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const varname = tv_get_string_chk(&argvars[1]); + buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); + + get_var_from(varname, rettv, &argvars[2], 'b', curtab, curwin, buf); +} + +/// "settabvar()" function +void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = 0; + + if (check_secure()) { + return; + } + + tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); + const char *const varname = tv_get_string_chk(&argvars[1]); + typval_T *const varp = &argvars[2]; + + if (varname != NULL && tp != NULL) { + tabpage_T *const save_curtab = curtab; + goto_tabpage_tp(tp, false, false); + + const size_t varname_len = strlen(varname); + char *const tabvarname = xmalloc(varname_len + 3); + memcpy(tabvarname, "t:", 2); + memcpy(tabvarname + 2, varname, varname_len + 1); + set_var(tabvarname, varname_len + 2, varp, true); + xfree(tabvarname); + + // Restore current tabpage. + if (valid_tabpage(save_curtab)) { + goto_tabpage_tp(save_curtab, false, false); + } + } +} + +/// "settabwinvar()" function +void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + setwinvar(argvars, rettv, 1); +} + +/// "setwinvar()" function +void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + setwinvar(argvars, rettv, 0); +} + +/// "setbufvar()" function +void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (check_secure() + || !tv_check_str_or_nr(&argvars[0])) { + return; + } + const char *varname = tv_get_string_chk(&argvars[1]); + buf_T *const buf = tv_get_buf(&argvars[0], false); + typval_T *varp = &argvars[2]; + + if (buf != NULL && varname != NULL) { + if (*varname == '&') { + aco_save_T aco; + + // set curbuf to be our buf, temporarily + aucmd_prepbuf(&aco, buf); + + set_option_from_tv(varname + 1, varp); + + // reset notion of buffer + aucmd_restbuf(&aco); + } else { + const size_t varname_len = STRLEN(varname); + char *const bufvarname = xmalloc(varname_len + 3); + buf_T *const save_curbuf = curbuf; + curbuf = buf; + memcpy(bufvarname, "b:", 2); + memcpy(bufvarname + 2, varname, varname_len + 1); + set_var(bufvarname, varname_len + 2, varp, true); + xfree(bufvarname); + curbuf = save_curbuf; + } + } +} diff --git a/src/nvim/eval/vars.h b/src/nvim/eval/vars.h new file mode 100644 index 0000000000..73efc4938a --- /dev/null +++ b/src/nvim/eval/vars.h @@ -0,0 +1,9 @@ +#ifndef NVIM_EVAL_VARS_H +#define NVIM_EVAL_VARS_H + +#include "nvim/ex_cmds_defs.h" // For exarg_T + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/vars.h.generated.h" +#endif +#endif // NVIM_EVAL_VARS_H |