diff options
-rw-r--r-- | runtime/doc/builtin.txt | 8 | ||||
-rw-r--r-- | runtime/doc/develop.txt | 4 | ||||
-rw-r--r-- | runtime/doc/diagnostic.txt | 28 | ||||
-rw-r--r-- | runtime/doc/news.txt | 5 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/vimfn.lua | 8 | ||||
-rw-r--r-- | runtime/lua/vim/diagnostic.lua | 31 | ||||
-rw-r--r-- | src/nvim/debugger.c | 6 | ||||
-rw-r--r-- | src/nvim/eval.lua | 8 | ||||
-rw-r--r-- | src/nvim/eval/typval.c | 2 | ||||
-rw-r--r-- | src/nvim/eval/typval_defs.h | 7 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.c | 259 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 3 | ||||
-rw-r--r-- | src/nvim/getchar.c | 33 | ||||
-rw-r--r-- | src/nvim/keycodes.c | 2 | ||||
-rw-r--r-- | src/nvim/option.c | 4 | ||||
-rw-r--r-- | src/nvim/statusline.c | 27 | ||||
-rw-r--r-- | src/nvim/tui/tui.c | 4 | ||||
-rw-r--r-- | test/functional/lua/diagnostic_spec.lua | 26 | ||||
-rw-r--r-- | test/functional/ui/statusline_spec.lua | 505 | ||||
-rw-r--r-- | test/functional/vimscript/getchar_spec.lua | 92 | ||||
-rw-r--r-- | test/old/testdir/test_functions.vim | 63 | ||||
-rw-r--r-- | test/old/testdir/test_user_func.vim | 72 |
22 files changed, 786 insertions, 411 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 3d9010aa2c..96574e2899 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -3114,6 +3114,14 @@ getchar([{expr} [, {opts}]]) *getchar()* The optional argument {opts} is a Dict and supports the following items: + cursor A String specifying cursor behavior + when waiting for a character. + "hide": hide the cursor. + "keep": keep current cursor unchanged. + "msg": move cursor to message area. + (default: automagically decide + between "keep" and "msg") + number If |TRUE|, return a Number when getting a single character. If |FALSE|, the return value is always diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index 1e4889747d..d3170f114f 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -376,6 +376,10 @@ Where possible, these patterns apply to _both_ Lua and the API: - See |vim.lsp.inlay_hint.enable()| and |vim.lsp.inlay_hint.is_enabled()| for a reference implementation of these "best practices". - NOTE: open questions: https://github.com/neovim/neovim/issues/28603 +- Transformation functions should also have a filter functionality when + appropriate. That is, when the function returns a nil value it "filters" its + input, otherwise the transformed value is used. + - Example: |vim.diagnostic.config.format()| API DESIGN GUIDELINES *dev-api* diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index 80197670ee..b7bdf2b446 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -532,11 +532,13 @@ Lua module: vim.diagnostic *diagnostic-api* the buffer. Otherwise, any truthy value means to always show the diagnostic source. Overrides the setting from |vim.diagnostic.config()|. - • {format}? (`fun(diagnostic:vim.Diagnostic): string`) A + • {format}? (`fun(diagnostic:vim.Diagnostic): string?`) A function that takes a diagnostic as input and - returns a string. The return value is the text used - to display the diagnostic. Overrides the setting - from |vim.diagnostic.config()|. + returns a string or nil. If the return value is nil, + the diagnostic is not displayed by the handler. Else + the output text is used to display the diagnostic. + Overrides the setting from + |vim.diagnostic.config()|. • {prefix}? (`string|table|(fun(diagnostic:vim.Diagnostic,i:integer,total:integer): string, string)`) Prefix each diagnostic in the floating window: • If a `function`, {i} is the index of the @@ -607,10 +609,11 @@ Lua module: vim.diagnostic *diagnostic-api* Fields: ~ • {current_line}? (`boolean`, default: `false`) Only show diagnostics for the current line. - • {format}? (`fun(diagnostic:vim.Diagnostic): string`) A function - that takes a diagnostic as input and returns a - string. The return value is the text used to display - the diagnostic. + • {format}? (`fun(diagnostic:vim.Diagnostic): string?`) A + function that takes a diagnostic as input and returns + a string or nil. If the return value is nil, the + diagnostic is not displayed by the handler. Else the + output text is used to display the diagnostic. *vim.diagnostic.Opts.VirtualText* @@ -635,9 +638,9 @@ Lua module: vim.diagnostic *diagnostic-api* • {suffix}? (`string|(fun(diagnostic:vim.Diagnostic): string)`) Append diagnostic message with suffix. This can be used to render an LSP diagnostic error code. - • {format}? (`fun(diagnostic:vim.Diagnostic): string`) The - return value is the text used to display the - diagnostic. Example: >lua + • {format}? (`fun(diagnostic:vim.Diagnostic): string?`) If + not nil, the return value is the text used to + display the diagnostic. Example: >lua function(diagnostic) if diagnostic.severity == vim.diagnostic.severity.ERROR then return string.format("E: %s", diagnostic.message) @@ -645,6 +648,9 @@ Lua module: vim.diagnostic *diagnostic-api* return diagnostic.message end < + + If the return value is nil, the diagnostic is + not displayed by the handler. • {hl_mode}? (`'replace'|'combine'|'blend'`) See |nvim_buf_set_extmark()|. • {virt_text}? (`[string,any][]`) See |nvim_buf_set_extmark()|. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index e03d6ca512..4e573197b7 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -412,6 +412,11 @@ UI • |:checkhealth| can display in a floating window, controlled by the |g:health| variable. +VIMSCRIPT + +• |getchar()| and |getcharstr()| have optional {opts} |Dict| argument to control: + cursor behavior, return type, and whether to simplify the returned key. + ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index c0be6b089c..098c0e907a 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -2781,6 +2781,14 @@ function vim.fn.getchangelist(buf) end --- The optional argument {opts} is a Dict and supports the --- following items: --- +--- cursor A String specifying cursor behavior +--- when waiting for a character. +--- "hide": hide the cursor. +--- "keep": keep current cursor unchanged. +--- "msg": move cursor to message area. +--- (default: automagically decide +--- between "keep" and "msg") +--- --- number If |TRUE|, return a Number when getting --- a single character. --- If |FALSE|, the return value is always diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 8044767cb0..972a5d1fa6 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -150,10 +150,11 @@ end --- Overrides the setting from |vim.diagnostic.config()|. --- @field source? boolean|'if_many' --- ---- A function that takes a diagnostic as input and returns a string. ---- The return value is the text used to display the diagnostic. +--- A function that takes a diagnostic as input and returns a string or nil. +--- If the return value is nil, the diagnostic is not displayed by the handler. +--- Else the output text is used to display the diagnostic. --- Overrides the setting from |vim.diagnostic.config()|. ---- @field format? fun(diagnostic:vim.Diagnostic): string +--- @field format? fun(diagnostic:vim.Diagnostic): string? --- --- Prefix each diagnostic in the floating window: --- - If a `function`, {i} is the index of the diagnostic being evaluated and @@ -207,7 +208,7 @@ end --- This can be used to render an LSP diagnostic error code. --- @field suffix? string|(fun(diagnostic:vim.Diagnostic): string) --- ---- The return value is the text used to display the diagnostic. Example: +--- If not nil, the return value is the text used to display the diagnostic. Example: --- ```lua --- function(diagnostic) --- if diagnostic.severity == vim.diagnostic.severity.ERROR then @@ -216,7 +217,8 @@ end --- return diagnostic.message --- end --- ``` ---- @field format? fun(diagnostic:vim.Diagnostic): string +--- If the return value is nil, the diagnostic is not displayed by the handler. +--- @field format? fun(diagnostic:vim.Diagnostic): string? --- --- See |nvim_buf_set_extmark()|. --- @field hl_mode? 'replace'|'combine'|'blend' @@ -239,9 +241,10 @@ end --- (default: `false`) --- @field current_line? boolean --- ---- A function that takes a diagnostic as input and returns a string. ---- The return value is the text used to display the diagnostic. ---- @field format? fun(diagnostic:vim.Diagnostic): string +--- A function that takes a diagnostic as input and returns a string or nil. +--- If the return value is nil, the diagnostic is not displayed by the handler. +--- Else the output text is used to display the diagnostic. +--- @field format? fun(diagnostic:vim.Diagnostic): string? --- @class vim.diagnostic.Opts.Signs --- @@ -503,15 +506,21 @@ local function prefix_source(diagnostics) end, diagnostics) end +--- @param format fun(vim.Diagnostic): string? --- @param diagnostics vim.Diagnostic[] --- @return vim.Diagnostic[] local function reformat_diagnostics(format, diagnostics) vim.validate('format', format, 'function') vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics') - local formatted = vim.deepcopy(diagnostics, true) - for _, diagnostic in ipairs(formatted) do - diagnostic.message = format(diagnostic) + local formatted = {} + for _, diagnostic in ipairs(diagnostics) do + local message = format(diagnostic) + if message ~= nil then + local formatted_diagnostic = vim.deepcopy(diagnostic, true) + formatted_diagnostic.message = message + table.insert(formatted, formatted_diagnostic) + end end return formatted end diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index f3e4ef0698..e60d04fdfd 100644 --- a/src/nvim/debugger.c +++ b/src/nvim/debugger.c @@ -110,13 +110,11 @@ void do_debug(char *cmd) } if (debug_oldval != NULL) { smsg(0, _("Oldval = \"%s\""), debug_oldval); - xfree(debug_oldval); - debug_oldval = NULL; + XFREE_CLEAR(debug_oldval); } if (debug_newval != NULL) { smsg(0, _("Newval = \"%s\""), debug_newval); - xfree(debug_newval); - debug_newval = NULL; + XFREE_CLEAR(debug_newval); } char *sname = estack_sfile(ESTACK_NONE); if (sname != NULL) { diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 82e3992287..9d787c68ea 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -3507,6 +3507,14 @@ M.funcs = { The optional argument {opts} is a Dict and supports the following items: + cursor A String specifying cursor behavior + when waiting for a character. + "hide": hide the cursor. + "keep": keep current cursor unchanged. + "msg": move cursor to message area. + (default: automagically decide + between "keep" and "msg") + number If |TRUE|, return a Number when getting a single character. If |FALSE|, the return value is always diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 48b2e82c0a..f9cf245e50 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2660,7 +2660,7 @@ int tv_dict_add_func(dict_T *const d, const char *const key, const size_t key_le dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); item->di_tv.v_type = VAR_FUNC; - item->di_tv.vval.v_string = xstrdup(fp->uf_name); + item->di_tv.vval.v_string = xmemdupz(fp->uf_name, fp->uf_namelen); if (tv_dict_add(d, item) == FAIL) { tv_dict_item_free(item); return FAIL; diff --git a/src/nvim/eval/typval_defs.h b/src/nvim/eval/typval_defs.h index 1ec7631c4b..90b0d4e4a9 100644 --- a/src/nvim/eval/typval_defs.h +++ b/src/nvim/eval/typval_defs.h @@ -357,9 +357,10 @@ struct ufunc { funccall_T *uf_scoped; ///< l: local variables for closure char *uf_name_exp; ///< if "uf_name[]" starts with SNR the name with ///< "<SNR>" as a string, otherwise NULL - char uf_name[]; ///< Name of function (actual size equals name); - ///< can start with <SNR>123_ - ///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR) + size_t uf_namelen; ///< Length of uf_name (excluding the NUL) + char uf_name[]; ///< Name of function (actual size equals name); + ///< can start with <SNR>123_ + ///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR) }; struct partial_S { diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 2e549fcf37..68bbf76043 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -264,25 +264,47 @@ static void register_closure(ufunc_T *fp) [current_funccal->fc_ufuncs.ga_len++] = fp; } +static char lambda_name[8 + NUMBUFLEN]; +static size_t lambda_namelen = 0; + /// @return a name for a lambda. Returned in static memory. char *get_lambda_name(void) { - static char name[30]; static int lambda_no = 0; - snprintf(name, sizeof(name), "<lambda>%d", ++lambda_no); - return name; + int n = snprintf(lambda_name, sizeof(lambda_name), "<lambda>%d", ++lambda_no); + if (n < 1) { + lambda_namelen = 0; + } else if (n >= (int)sizeof(lambda_name)) { + lambda_namelen = sizeof(lambda_name) - 1; + } else { + lambda_namelen = (size_t)n; + } + + return lambda_name; } -static void set_ufunc_name(ufunc_T *fp, char *name) +/// Get the length of the last lambda name. +size_t get_lambda_name_len(void) { + return lambda_namelen; +} + +/// Allocate a "ufunc_T" for a function called "name". +static ufunc_T *alloc_ufunc(const char *name, size_t namelen) +{ + size_t len = offsetof(ufunc_T, uf_name) + namelen + 1; + ufunc_T *fp = xcalloc(1, len); STRCPY(fp->uf_name, name); + fp->uf_namelen = namelen; if ((uint8_t)name[0] == K_SPECIAL) { - fp->uf_name_exp = xmalloc(strlen(name) + 3); - STRCPY(fp->uf_name_exp, "<SNR>"); - strcat(fp->uf_name_exp, fp->uf_name + 3); + len = namelen + 3; + fp->uf_name_exp = xmalloc(len); + snprintf(fp->uf_name_exp, len, "<SNR>%s", fp->uf_name + 3); } + + return fp; } /// Parse a lambda expression and get a Funcref from "*arg". @@ -350,8 +372,9 @@ int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) garray_T newlines; char *name = get_lambda_name(); + size_t namelen = get_lambda_name_len(); - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); + fp = alloc_ufunc(name, namelen); pt = xcalloc(1, sizeof(partial_T)); ga_init(&newlines, (int)sizeof(char *), 1); @@ -369,7 +392,6 @@ int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) } fp->uf_refcount = 1; - set_ufunc_name(fp, name); hash_add(&func_hashtab, UF2HIKEY(fp)); fp->uf_args = newargs; ga_init(&fp->uf_def_args, (int)sizeof(char *), 1); @@ -409,7 +431,10 @@ int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) errret: ga_clear_strings(&newargs); - xfree(fp); + if (fp != NULL) { + xfree(fp->uf_name_exp); + xfree(fp); + } xfree(pt); if (evalarg != NULL && evalarg->eval_tofree == NULL) { evalarg->eval_tofree = tofree; @@ -627,33 +652,36 @@ static char *fname_trans_sid(const char *const name, char *const fname_buf, char int *const error) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - const int llen = eval_fname_script(name); - if (llen == 0) { + const char *script_name = name + eval_fname_script(name); + if (script_name == name) { return (char *)name; // no prefix } fname_buf[0] = (char)K_SPECIAL; fname_buf[1] = (char)KS_EXTRA; fname_buf[2] = KE_SNR; - int i = 3; - if (eval_fname_sid(name)) { // "<SID>" or "s:" + size_t fname_buflen = 3; + if (!eval_fname_sid(name)) { // "<SID>" or "s:" + fname_buf[fname_buflen] = NUL; + } else { if (current_sctx.sc_sid <= 0) { *error = FCERR_SCRIPT; } else { - snprintf(fname_buf + i, (size_t)(FLEN_FIXED + 1 - i), "%" PRId64 "_", - (int64_t)current_sctx.sc_sid); - i = (int)strlen(fname_buf); + fname_buflen += (size_t)snprintf(fname_buf + fname_buflen, + FLEN_FIXED + 1 - fname_buflen, + "%" PRIdSCID "_", + current_sctx.sc_sid); } } + size_t fnamelen = fname_buflen + strlen(script_name); char *fname; - if ((size_t)i + strlen(name + llen) < FLEN_FIXED) { - STRCPY(fname_buf + i, name + llen); + if (fnamelen < FLEN_FIXED) { + STRCPY(fname_buf + fname_buflen, script_name); fname = fname_buf; } else { - fname = xmalloc((size_t)i + strlen(name + llen) + 1); + fname = xmalloc(fnamelen + 1); *tofree = fname; - memmove(fname, fname_buf, (size_t)i); - STRCPY(fname + i, name + llen); + snprintf(fname, fnamelen + 1, "%s%s", fname_buf, script_name); } return fname; } @@ -711,20 +739,20 @@ ufunc_T *find_func(const char *name) /// Copy the function name of "fp" to buffer "buf". /// "buf" must be able to hold the function name plus three bytes. /// Takes care of script-local function names. -static void cat_func_name(char *buf, size_t buflen, ufunc_T *fp) +static int cat_func_name(char *buf, size_t bufsize, ufunc_T *fp) { int len = -1; - size_t uflen = strlen(fp->uf_name); + size_t uflen = fp->uf_namelen; assert(uflen > 0); if ((uint8_t)fp->uf_name[0] == K_SPECIAL && uflen > 3) { - len = snprintf(buf, buflen, "<SNR>%s", fp->uf_name + 3); + len = snprintf(buf, bufsize, "<SNR>%s", fp->uf_name + 3); } else { - len = snprintf(buf, buflen, "%s", fp->uf_name); + len = snprintf(buf, bufsize, "%s", fp->uf_name); } - (void)len; // Avoid unused warning on release builds assert(len > 0); + return (len >= (int)bufsize) ? (int)bufsize - 1 : len; } /// Add a number variable "name" to dict "dp" with value "nr". @@ -891,7 +919,6 @@ static void func_clear_items(ufunc_T *fp) ga_clear_strings(&(fp->uf_args)); ga_clear_strings(&(fp->uf_def_args)); ga_clear_strings(&(fp->uf_lines)); - XFREE_CLEAR(fp->uf_name_exp); if (fp->uf_flags & FC_LUAREF) { api_free_luaref(fp->uf_luaref); @@ -930,6 +957,8 @@ static void func_free(ufunc_T *fp) if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) { func_remove(fp); } + + XFREE_CLEAR(fp->uf_name_exp); xfree(fp); } @@ -983,6 +1012,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett bool islambda = false; char numbuf[NUMBUFLEN]; char *name; + size_t namelen; typval_T *tv_to_free[MAX_FUNC_ARGS]; int tv_to_free_len = 0; proftime_T wait_start; @@ -1104,23 +1134,25 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett break; } } + + namelen = strlen(name); } else { if ((fp->uf_flags & FC_NOARGS) != 0) { // Bail out if no a: arguments used (in lambda). break; } // "..." argument a:1, a:2, etc. - snprintf(numbuf, sizeof(numbuf), "%d", ai + 1); + namelen = (size_t)snprintf(numbuf, sizeof(numbuf), "%d", ai + 1); name = numbuf; } - if (fixvar_idx < FIXVAR_CNT && strlen(name) <= VAR_SHORT_LEN) { + if (fixvar_idx < FIXVAR_CNT && namelen <= VAR_SHORT_LEN) { v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++]; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + STRCPY(v->di_key, name); } else { - v = xmalloc(sizeof(dictitem_T) + strlen(name)); - v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; + v = tv_dict_item_alloc_len(name, namelen); + v->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX; } - STRCPY(v->di_key, name); // Note: the values are copied directly to avoid alloc/free. // "argvars" must have VAR_FIXED for v_lock. @@ -2095,7 +2127,7 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part len = (int)(end - lv.ll_name); } - size_t sid_buf_len = 0; + size_t sid_buflen = 0; char sid_buf[20]; // Copy the function name to allocated memory. @@ -2105,15 +2137,16 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part lead = 0; // do nothing } else if (lead > 0) { lead = 3; - if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) || eval_fname_sid(*pp)) { + if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) + || eval_fname_sid(*pp)) { // It's "s:" or "<SID>". if (current_sctx.sc_sid <= 0) { emsg(_(e_usingsid)); goto theend; } - sid_buf_len = - (size_t)snprintf(sid_buf, sizeof(sid_buf), "%" PRIdSCID "_", current_sctx.sc_sid); - lead += (int)sid_buf_len; + sid_buflen = (size_t)snprintf(sid_buf, sizeof(sid_buf), "%" PRIdSCID "_", + current_sctx.sc_sid); + lead += (int)sid_buflen; } } else if (!(flags & TFN_INT) && builtin_function(lv.ll_name, (int)lv.ll_name_len)) { semsg(_("E128: Function name must start with a capital or \"s:\": %s"), @@ -2135,8 +2168,8 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part name[0] = (char)K_SPECIAL; name[1] = (char)KS_EXTRA; name[2] = KE_SNR; - if (sid_buf_len > 0) { // If it's "<SID>" - memcpy(name + 3, sid_buf, sid_buf_len); + if (sid_buflen > 0) { // If it's "<SID>" + memcpy(name + 3, sid_buf, sid_buflen); } } memmove(name + lead, lv.ll_name, (size_t)len); @@ -2172,12 +2205,12 @@ char *get_scriptlocal_funcname(char *funcname) char sid_buf[25]; // Expand s: and <SID> prefix into <SNR>nr_<name> - snprintf(sid_buf, sizeof(sid_buf), "<SNR>%" PRId64 "_", - (int64_t)current_sctx.sc_sid); + size_t sid_buflen = (size_t)snprintf(sid_buf, sizeof(sid_buf), "<SNR>%" PRIdSCID "_", + current_sctx.sc_sid); const int off = *funcname == 's' ? 2 : 5; - char *newname = xmalloc(strlen(sid_buf) + strlen(funcname + off) + 1); - STRCPY(newname, sid_buf); - strcat(newname, funcname + off); + size_t newnamesize = sid_buflen + strlen(funcname + off) + 1; + char *newname = xmalloc(newnamesize); + snprintf(newname, newnamesize, "%s%s", sid_buf, funcname + off); return newname; } @@ -2249,6 +2282,7 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in int ret = FAIL; bool is_heredoc = false; char *heredoc_trimmed = NULL; + size_t heredoc_trimmedlen = 0; bool do_concat = true; while (true) { @@ -2312,19 +2346,18 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in // * ":let {var-name} =<< [trim] {marker}" and "{marker}" if (heredoc_trimmed == NULL || (is_heredoc && skipwhite(theline) == theline) - || strncmp(theline, heredoc_trimmed, - strlen(heredoc_trimmed)) == 0) { + || strncmp(theline, heredoc_trimmed, heredoc_trimmedlen) == 0) { if (heredoc_trimmed == NULL) { p = theline; } else if (is_heredoc) { - p = skipwhite(theline) == theline - ? theline : theline + strlen(heredoc_trimmed); + p = skipwhite(theline) == theline ? theline : theline + heredoc_trimmedlen; } else { - p = theline + strlen(heredoc_trimmed); + p = theline + heredoc_trimmedlen; } if (strcmp(p, skip_until) == 0) { XFREE_CLEAR(skip_until); XFREE_CLEAR(heredoc_trimmed); + heredoc_trimmedlen = 0; do_concat = true; is_heredoc = false; } @@ -2401,7 +2434,7 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n' && (!ASCII_ISALPHA(p[2]) || (p[2] == 's')))))) { - skip_until = xstrdup("."); + skip_until = xmemdupz(".", 1); } // heredoc: Check for ":python <<EOF", ":lua <<EOF", etc. @@ -2426,10 +2459,11 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in if (strncmp(p, "trim", 4) == 0) { // Ignore leading white space. p = skipwhite(p + 4); - heredoc_trimmed = xmemdupz(theline, (size_t)(skipwhite(theline) - theline)); + heredoc_trimmedlen = (size_t)(skipwhite(theline) - theline); + heredoc_trimmed = xmemdupz(theline, heredoc_trimmedlen); } if (*p == NUL) { - skip_until = xstrdup("."); + skip_until = xmemdupz(".", 1); } else { skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); } @@ -2437,35 +2471,38 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in is_heredoc = true; } - // Check for ":let v =<< [trim] EOF" - // and ":let [a, b] =<< [trim] EOF" - arg = p; - if (checkforcmd(&arg, "let", 2)) { - int var_count = 0; - int semicolon = 0; - arg = (char *)skip_var_list(arg, &var_count, &semicolon, true); - if (arg != NULL) { - arg = skipwhite(arg); - } - if (arg != NULL && strncmp(arg, "=<<", 3) == 0) { - p = skipwhite(arg + 3); - while (true) { - if (strncmp(p, "trim", 4) == 0) { - // Ignore leading white space. - p = skipwhite(p + 4); - heredoc_trimmed = xmemdupz(theline, (size_t)(skipwhite(theline) - theline)); - continue; - } - if (strncmp(p, "eval", 4) == 0) { - // Ignore leading white space. - p = skipwhite(p + 4); - continue; + if (!is_heredoc) { + // Check for ":let v =<< [trim] EOF" + // and ":let [a, b] =<< [trim] EOF" + arg = p; + if (checkforcmd(&arg, "let", 2)) { + int var_count = 0; + int semicolon = 0; + arg = (char *)skip_var_list(arg, &var_count, &semicolon, true); + if (arg != NULL) { + arg = skipwhite(arg); + } + if (arg != NULL && strncmp(arg, "=<<", 3) == 0) { + p = skipwhite(arg + 3); + while (true) { + if (strncmp(p, "trim", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + heredoc_trimmedlen = (size_t)(skipwhite(theline) - theline); + heredoc_trimmed = xmemdupz(theline, heredoc_trimmedlen); + continue; + } + if (strncmp(p, "eval", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + continue; + } + break; } - break; + skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); + do_concat = false; + is_heredoc = true; } - skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); - do_concat = false; - is_heredoc = true; } } } @@ -2514,7 +2551,8 @@ void ex_function(exarg_T *eap) garray_T newlines; int varargs = false; int flags = 0; - ufunc_T *fp; + ufunc_T *fp = NULL; + bool free_fp = false; bool overwrite = false; funcdict_T fudi; static int func_nr = 0; // number for nameless function @@ -2773,6 +2811,7 @@ void ex_function(exarg_T *eap) } // If there are no errors, add the function + size_t namelen = 0; if (fudi.fd_dict == NULL) { dictitem_T *v = find_var(name, strlen(name), &ht, false); if (v != NULL && v->di_tv.v_type == VAR_FUNC) { @@ -2788,11 +2827,11 @@ void ex_function(exarg_T *eap) && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) { emsg_funcname(e_funcexts, name); - goto erret; + goto errret_keep; } if (fp->uf_calls > 0) { emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), name); - goto erret; + goto errret_keep; } if (fp->uf_refcount > 1) { // This function is referenced somewhere, don't redefine it but @@ -2813,7 +2852,7 @@ void ex_function(exarg_T *eap) } } } else { - char numbuf[20]; + char numbuf[NUMBUFLEN]; fp = NULL; if (fudi.fd_newkey == NULL && !eap->forceit) { @@ -2833,8 +2872,8 @@ void ex_function(exarg_T *eap) // Give the function a sequential number. Can only be used with a // Funcref! xfree(name); - snprintf(numbuf, sizeof(numbuf), "%d", ++func_nr); - name = xstrdup(numbuf); + namelen = (size_t)snprintf(numbuf, sizeof(numbuf), "%d", ++func_nr); + name = xmemdupz(numbuf, namelen); } if (fp == NULL) { @@ -2858,7 +2897,10 @@ void ex_function(exarg_T *eap) } } - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); + if (namelen == 0) { + namelen = strlen(name); + } + fp = alloc_ufunc(name, namelen); if (fudi.fd_dict != NULL) { if (fudi.fd_di == NULL) { @@ -2866,7 +2908,7 @@ void ex_function(exarg_T *eap) fudi.fd_di = tv_dict_item_alloc(fudi.fd_newkey); if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) { xfree(fudi.fd_di); - xfree(fp); + XFREE_CLEAR(fp); goto erret; } } else { @@ -2874,19 +2916,18 @@ void ex_function(exarg_T *eap) tv_clear(&fudi.fd_di->di_tv); } fudi.fd_di->di_tv.v_type = VAR_FUNC; - fudi.fd_di->di_tv.vval.v_string = xstrdup(name); + fudi.fd_di->di_tv.vval.v_string = xmemdupz(name, namelen); // behave like "dict" was used flags |= FC_DICT; } // insert the new function in the function list - set_ufunc_name(fp, name); if (overwrite) { hashitem_T *hi = hash_find(&func_hashtab, name); hi->hi_key = UF2HIKEY(fp); } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { - xfree(fp); + free_fp = true; goto erret; } fp->uf_refcount = 1; @@ -2915,9 +2956,21 @@ void ex_function(exarg_T *eap) goto ret_free; erret: + if (fp != NULL) { + // these were set to "newargs" and "default_args", which are cleared below + ga_init(&fp->uf_args, (int)sizeof(char *), 1); + ga_init(&fp->uf_def_args, (int)sizeof(char *), 1); + } +errret_2: + if (fp != NULL) { + XFREE_CLEAR(fp->uf_name_exp); + } + if (free_fp) { + XFREE_CLEAR(fp); + } +errret_keep: ga_clear_strings(&newargs); ga_clear_strings(&default_args); -errret_2: ga_clear_strings(&newlines); ret_free: xfree(line_to_free); @@ -3010,15 +3063,16 @@ char *get_user_func_name(expand_T *xp, int idx) return ""; // don't show dict and lambda functions } - if (strlen(fp->uf_name) + 4 >= IOSIZE) { + if (fp->uf_namelen + 4 >= IOSIZE) { return fp->uf_name; // Prevent overflow. } - cat_func_name(IObuff, IOSIZE, fp); + int len = cat_func_name(IObuff, IOSIZE, fp); if (xp->xp_context != EXPAND_USER_FUNC) { - xstrlcat(IObuff, "(", IOSIZE); + xstrlcpy(IObuff + len, "(", IOSIZE - (size_t)len); if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args)) { - xstrlcat(IObuff, ")", IOSIZE); + len++; + xstrlcpy(IObuff + len, ")", IOSIZE - (size_t)len); } } return IObuff; @@ -3620,21 +3674,26 @@ char *get_return_cmd(void *rettv) { char *s = NULL; char *tofree = NULL; + size_t slen = 0; if (rettv != NULL) { tofree = s = encode_tv2echo((typval_T *)rettv, NULL); } if (s == NULL) { s = ""; + } else { + slen = strlen(s); } xstrlcpy(IObuff, ":return ", IOSIZE); xstrlcpy(IObuff + 8, s, IOSIZE - 8); - if (strlen(s) + 8 >= IOSIZE) { + size_t IObufflen = 8 + slen; + if (IObufflen >= IOSIZE) { STRCPY(IObuff + IOSIZE - 4, "..."); + IObufflen = IOSIZE - 1; } xfree(tofree); - return xstrdup(IObuff); + return xstrnsave(IObuff, IObufflen); } /// Get next function line. @@ -4077,7 +4136,8 @@ bool set_ref_in_func(char *name, ufunc_T *fp_in, int copyID) char *register_luafunc(LuaRef ref) { char *name = get_lambda_name(); - ufunc_T *fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); + size_t namelen = get_lambda_name_len(); + ufunc_T *fp = alloc_ufunc(name, namelen); fp->uf_refcount = 1; fp->uf_varargs = true; @@ -4086,7 +4146,6 @@ char *register_luafunc(LuaRef ref) fp->uf_script_ctx = current_sctx; fp->uf_luaref = ref; - STRCPY(fp->uf_name, name); hash_add(&func_hashtab, UF2HIKEY(fp)); // coverity[leaked_storage] diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 137226e2ad..ceb17a995d 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7400,8 +7400,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum *errormsg = _(e_usingsid); return NULL; } - snprintf(strbuf, sizeof(strbuf), "<SNR>%" PRIdSCID "_", - current_sctx.sc_sid); + snprintf(strbuf, sizeof(strbuf), "<SNR>%" PRIdSCID "_", current_sctx.sc_sid); result = strbuf; break; diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index c9d2165fb0..5bf89ee5a8 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1872,9 +1872,11 @@ static int no_reduce_keys = 0; ///< Do not apply modifiers to the key. static void getchar_common(typval_T *argvars, typval_T *rettv, bool allow_number) FUNC_ATTR_NONNULL_ALL { - varnumber_T n; + varnumber_T n = 0; + const int called_emsg_start = called_emsg; bool error = false; bool simplify = true; + char cursor_flag = NUL; if (argvars[0].v_type != VAR_UNKNOWN && tv_check_for_opt_dict_arg(argvars, 1) == FAIL) { @@ -1888,10 +1890,28 @@ static void getchar_common(typval_T *argvars, typval_T *rettv, bool allow_number allow_number = tv_dict_get_bool(d, "number", true); } else if (tv_dict_has_key(d, "number")) { semsg(_(e_invarg2), "number"); - error = true; } simplify = tv_dict_get_bool(d, "simplify", true); + + const char *cursor_str = tv_dict_get_string(d, "cursor", false); + if (cursor_str != NULL) { + if (strcmp(cursor_str, "hide") != 0 + && strcmp(cursor_str, "keep") != 0 + && strcmp(cursor_str, "msg") != 0) { + semsg(_(e_invargNval), "cursor", cursor_str); + } else { + cursor_flag = cursor_str[0]; + } + } + } + + if (called_emsg != called_emsg_start) { + return; + } + + if (cursor_flag == 'h') { + ui_busy_start(); } no_mapping++; @@ -1899,9 +1919,8 @@ static void getchar_common(typval_T *argvars, typval_T *rettv, bool allow_number if (!simplify) { no_reduce_keys++; } - while (!error) { - if (msg_col > 0) { - // Position the cursor. Needed after a message that ends in a space. + while (true) { + if (cursor_flag == 'm' || (cursor_flag == NUL && msg_col > 0)) { ui_cursor_goto(msg_row, msg_col); } @@ -1945,6 +1964,10 @@ static void getchar_common(typval_T *argvars, typval_T *rettv, bool allow_number no_reduce_keys--; } + if (cursor_flag == 'h') { + ui_busy_stop(); + } + set_vim_var_nr(VV_MOUSE_WIN, 0); set_vim_var_nr(VV_MOUSE_WINID, 0); set_vim_var_nr(VV_MOUSE_LNUM, 0); diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c index f7215d3d12..a9f8c9222a 100644 --- a/src/nvim/keycodes.c +++ b/src/nvim/keycodes.c @@ -917,7 +917,7 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co result[dlen++] = (char)K_SPECIAL; result[dlen++] = (char)KS_EXTRA; result[dlen++] = KE_SNR; - snprintf(result + dlen, buf_len - dlen, "%" PRId64, (int64_t)sid); + snprintf(result + dlen, buf_len - dlen, "%" PRIdSCID, sid); dlen += strlen(result + dlen); result[dlen++] = '_'; continue; diff --git a/src/nvim/option.c b/src/nvim/option.c index f9eb67ff83..796165453c 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -5226,7 +5226,7 @@ void buf_copy_options(buf_T *buf, int flags) // or to a help buffer. if (dont_do_help) { buf->b_p_isk = save_p_isk; - if (p_vts && p_vts != empty_string_option && !buf->b_p_vts_array) { + if (p_vts && *p_vts != NUL && !buf->b_p_vts_array) { tabstop_set(p_vts, &buf->b_p_vts_array); } else { buf->b_p_vts_array = NULL; @@ -5239,7 +5239,7 @@ void buf_copy_options(buf_T *buf, int flags) COPY_OPT_SCTX(buf, kBufOptTabstop); buf->b_p_vts = xstrdup(p_vts); COPY_OPT_SCTX(buf, kBufOptVartabstop); - if (p_vts && p_vts != empty_string_option && !buf->b_p_vts_array) { + if (p_vts && *p_vts != NUL && !buf->b_p_vts_array) { tabstop_set(p_vts, &buf->b_p_vts_array); } else { buf->b_p_vts_array = NULL; diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index 6947a14a2c..ddae023ad5 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -927,6 +927,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op static stl_hlrec_t *stl_hltab = NULL; static StlClickRecord *stl_tabtab = NULL; static int *stl_separator_locations = NULL; + static int curitem = 0; #define TMPLEN 70 char buf_tmp[TMPLEN]; @@ -1013,7 +1014,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op int groupdepth = 0; int evaldepth = 0; - int curitem = 0; + // nvim_eval_statusline() can be called from inside a {-expression item so + // this may be a recursive call. Keep track of the start index into "stl_items". + // During post-processing only treat items filled in a certain recursion level. + int evalstart = curitem; + bool prevchar_isflag = true; bool prevchar_isitem = false; @@ -1949,7 +1954,9 @@ stcsign: } *out_p = NUL; - int itemcnt = curitem; + // Subtract offset from `itemcnt` and restore `curitem` to previous recursion level. + int itemcnt = curitem - evalstart; + curitem = evalstart; // Free the format buffer if we allocated it internally if (usefmt != fmt) { @@ -1975,7 +1982,7 @@ stcsign: trunc_p = stl_items[0].start; item_idx = 0; - for (int i = 0; i < itemcnt; i++) { + for (int i = evalstart; i < itemcnt + evalstart; i++) { if (stl_items[i].type == Trunc) { // Truncate at %< stl_items. trunc_p = stl_items[i].start; @@ -2005,9 +2012,9 @@ stcsign: // Ignore any items in the statusline that occur after // the truncation point - for (int i = 0; i < itemcnt; i++) { + for (int i = evalstart; i < itemcnt + evalstart; i++) { if (stl_items[i].start > trunc_p) { - for (int j = i; j < itemcnt; j++) { + for (int j = i; j < itemcnt + evalstart; j++) { if (stl_items[j].type == ClickFunc) { XFREE_CLEAR(stl_items[j].cmd); } @@ -2046,7 +2053,7 @@ stcsign: // the truncation marker `<` is not counted. int item_offset = trunc_len - 1; - for (int i = item_idx; i < itemcnt; i++) { + for (int i = item_idx; i < itemcnt + evalstart; i++) { // Items starting at or after the end of the truncated section need // to be moved backwards. if (stl_items[i].start >= trunc_end_p) { @@ -2079,7 +2086,7 @@ stcsign: // Find how many separators there are, which we will use when // figuring out how many groups there are. int num_separators = 0; - for (int i = 0; i < itemcnt; i++) { + for (int i = evalstart; i < itemcnt + evalstart; i++) { if (stl_items[i].type == Separate) { // Create an array of the start location for each separator mark. stl_separator_locations[num_separators] = i; @@ -2104,7 +2111,7 @@ stcsign: } for (int item_idx = stl_separator_locations[l] + 1; - item_idx < itemcnt; + item_idx < itemcnt + evalstart; item_idx++) { stl_items[item_idx].start += dislocation; } @@ -2118,7 +2125,7 @@ stcsign: if (hltab != NULL) { *hltab = stl_hltab; stl_hlrec_t *sp = stl_hltab; - for (int l = 0; l < itemcnt; l++) { + for (int l = evalstart; l < itemcnt + evalstart; l++) { if (stl_items[l].type == Highlight || stl_items[l].type == HighlightFold || stl_items[l].type == HighlightSign) { sp->start = stl_items[l].start; @@ -2139,7 +2146,7 @@ stcsign: if (tabtab != NULL) { *tabtab = stl_tabtab; StlClickRecord *cur_tab_rec = stl_tabtab; - for (int l = 0; l < itemcnt; l++) { + for (int l = evalstart; l < itemcnt + evalstart; l++) { if (stl_items[l].type == TabPage) { cur_tab_rec->start = stl_items[l].start; if (stl_items[l].minwid == 0) { diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index f4337d5011..31f95a1006 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -2294,6 +2294,7 @@ static void augment_terminfo(TUIData *tui, const char *term, int vte_version, in bool putty = terminfo_is_term_family(term, "putty"); bool screen = terminfo_is_term_family(term, "screen"); bool tmux = terminfo_is_term_family(term, "tmux") || !!os_getenv("TMUX"); + bool st = terminfo_is_term_family(term, "st"); bool iterm = terminfo_is_term_family(term, "iterm") || terminfo_is_term_family(term, "iterm2") || terminfo_is_term_family(term, "iTerm.app") @@ -2378,9 +2379,10 @@ static void augment_terminfo(TUIData *tui, const char *term, int vte_version, in // would use a tmux control sequence and an extra if(screen) test. tui->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\")); - } else if ((xterm || hterm || rxvt || tmux || alacritty) + } else if ((xterm || hterm || rxvt || tmux || alacritty || st) && (vte_version == 0 || vte_version >= 3900)) { // Supported in urxvt, newer VTE. + // Supported in st, but currently missing in ncurses definitions. #32217 tui->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(ut, "ext.set_cursor_color", "\033]12;%p1%s\007"); } diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index 08c287735e..9a982a1c6d 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -2134,6 +2134,32 @@ describe('vim.diagnostic', function() end) ) end) + + it('can filter diagnostics by returning nil when formatting', function() + local result = exec_lua(function() + vim.diagnostic.config { + virtual_text = { + format = function(diagnostic) + if diagnostic.code == 'foo_err' then + return nil + end + return diagnostic.message + end, + }, + } + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('An error here!', 0, 0, 0, 0, 'foo_server', 'foo_err'), + _G.make_error('An error there!', 1, 1, 1, 1, 'bar_server', 'bar_err'), + }) + + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) + return extmarks + end) + + eq(1, #result) + eq(' An error there!', result[1][4].virt_text[3][1]) + end) end) describe('handlers.virtual_lines', function() diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua index 1d0f181244..50e31ac6a9 100644 --- a/test/functional/ui/statusline_spec.lua +++ b/test/functional/ui/statusline_spec.lua @@ -507,268 +507,263 @@ describe('global statusline', function() end) end) -it('statusline does not crash if it has Arabic characters #19447', function() - clear() - api.nvim_set_option_value('statusline', 'غً', {}) - api.nvim_set_option_value('laststatus', 2, {}) - command('redraw!') - assert_alive() -end) +describe('statusline', function() + local screen + before_each(function() + clear() + screen = Screen.new(40, 8) + screen:add_extra_attr_ids { + [100] = { bold = true, reverse = true, foreground = Screen.colors.Blue }, + [101] = { reverse = true, bold = true, foreground = Screen.colors.SlateBlue }, + } + end) -it('statusline is redrawn with :resize from <Cmd> mapping #19629', function() - clear() - local screen = Screen.new(40, 8) - exec([[ - set laststatus=2 - nnoremap <Up> <cmd>resize -1<CR> - nnoremap <Down> <cmd>resize +1<CR> - ]]) - feed('<Up>') - screen:expect([[ - ^ | - {1:~ }|*4 - {3:[No Name] }| - |*2 - ]]) - feed('<Down>') - screen:expect([[ - ^ | - {1:~ }|*5 - {3:[No Name] }| - | - ]]) -end) + it('does not crash if it has Arabic characters #19447', function() + api.nvim_set_option_value('statusline', 'غً', {}) + api.nvim_set_option_value('laststatus', 2, {}) + command('redraw!') + assert_alive() + end) -it('showcmdloc=statusline does not show if statusline is too narrow', function() - clear() - local screen = Screen.new(40, 8) - command('set showcmd') - command('set showcmdloc=statusline') - command('1vsplit') - screen:expect([[ - ^ │ | - {1:~}│{1:~ }|*5 - {3:< }{2:[No Name] }| - | - ]]) - feed('1234') - screen:expect_unchanged() -end) + it('is redrawn with :resize from <Cmd> mapping #19629', function() + exec([[ + set laststatus=2 + nnoremap <Up> <cmd>resize -1<CR> + nnoremap <Down> <cmd>resize +1<CR> + ]]) + feed('<Up>') + screen:expect([[ + ^ | + {1:~ }|*4 + {3:[No Name] }| + |*2 + ]]) + feed('<Down>') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:[No Name] }| + | + ]]) + end) -it('K_EVENT does not trigger a statusline redraw unnecessarily', function() - clear() - local _ = Screen.new(40, 8) - -- does not redraw on vim.schedule (#17937) - command([[ - set laststatus=2 - let g:counter = 0 - func Status() - let g:counter += 1 - lua vim.schedule(function() end) - return g:counter - endfunc - set statusline=%!Status() - ]]) - sleep(50) - eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) - -- also in insert mode - feed('i') - sleep(50) - eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) - -- does not redraw on timer call (#14303) - command([[ - let g:counter = 0 - func Timer(timer) - endfunc - call timer_start(1, 'Timer', {'repeat': 100}) - ]]) - sleep(50) - eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) -end) + it('does not contain showmcd with showcmdloc=statusline when too narrow', function() + command('set showcmd') + command('set showcmdloc=statusline') + command('1vsplit') + screen:expect([[ + ^ │ | + {1:~}│{1:~ }|*5 + {3:< }{2:[No Name] }| + | + ]]) + feed('1234') + screen:expect_unchanged() + end) -it('statusline is redrawn on various state changes', function() - clear() - local screen = Screen.new(40, 4) - - -- recording state change #22683 - command('set ls=2 stl=%{repeat(reg_recording(),5)}') - screen:expect([[ - ^ | - {1:~ }| - {3: }| - | - ]]) - feed('qQ') - screen:expect([[ - ^ | - {1:~ }| - {3:QQQQQ }| - {5:recording @Q} | - ]]) - feed('q') - screen:expect([[ - ^ | - {1:~ }| - {3: }| - | - ]]) - - -- Visual mode change #23932 - command('set ls=2 stl=%{mode(1)}') - screen:expect([[ - ^ | - {1:~ }| - {3:n }| - | - ]]) - feed('v') - screen:expect([[ - ^ | - {1:~ }| - {3:v }| - {5:-- VISUAL --} | - ]]) - feed('V') - screen:expect([[ - ^ | - {1:~ }| - {3:V }| - {5:-- VISUAL LINE --} | - ]]) - feed('<C-V>') - screen:expect([[ - ^ | - {1:~ }| - {3:^V }| - {5:-- VISUAL BLOCK --} | - ]]) - feed('<Esc>') - screen:expect([[ - ^ | - {1:~ }| - {3:n }| - | - ]]) -end) + it('does not redraw unnecessarily after K_EVENT', function() + -- does not redraw on vim.schedule (#17937) + command([[ + set laststatus=2 + let g:counter = 0 + func Status() + let g:counter += 1 + lua vim.schedule(function() end) + return g:counter + endfunc + set statusline=%!Status() + ]]) + sleep(50) + eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) + -- also in insert mode + feed('i') + sleep(50) + eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) + -- does not redraw on timer call (#14303) + command([[ + let g:counter = 0 + func Timer(timer) + endfunc + call timer_start(1, 'Timer', {'repeat': 100}) + ]]) + sleep(50) + eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) + end) -it('ruler is redrawn in cmdline with redrawstatus #22804', function() - clear() - local screen = Screen.new(40, 2) - command([[ - let g:n = 'initial value' - set ls=1 ru ruf=%{g:n} - redraw - let g:n = 'other value' - redrawstatus - ]]) - screen:expect([[ - ^ | - other value | - ]]) -end) + it('is redrawn on various state changes', function() + -- recording state change #22683 + command('set ls=2 stl=%{repeat(reg_recording(),5)}') + local s1 = [[ + ^ | + {1:~ }|*5 + {3: }| + | + ]] + screen:expect(s1) + feed('qQ') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:QQQQQ }| + {5:recording @Q} | + ]]) + feed('q') + screen:expect(s1) + + -- Visual mode change #23932 + command('set ls=2 stl=%{mode(1)}') + local s2 = [[ + ^ | + {1:~ }|*5 + {3:n }| + | + ]] + screen:expect(s2) + feed('v') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:v }| + {5:-- VISUAL --} | + ]]) + feed('V') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:V }| + {5:-- VISUAL LINE --} | + ]]) + feed('<C-V>') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:^V }| + {5:-- VISUAL BLOCK --} | + ]]) + feed('<Esc>') + screen:expect(s2) + end) -it('shows correct ruler in cmdline with no statusline', function() - clear() - local screen = Screen.new(30, 8) - -- Use long ruler to check 'ruler' with 'rulerformat' set has correct width. - command [[ - set ruler rulerformat=%{winnr()}longlonglong ls=0 winwidth=10 - split - wincmd b - vsplit - wincmd t - wincmd | - mode - ]] - -- Window 1 is current. It has a statusline, so cmdline should show the - -- last window's ruler, which has no statusline. - command '1wincmd w' - screen:expect [[ - ^ | - {1:~ }|*2 - {3:[No Name] 1longlonglong }| - │ | - {1:~ }│{1:~ }|*2 - 3longlonglong | - ]] - -- Window 2 is current. It has no statusline, so cmdline should show its - -- ruler instead. - command '2wincmd w' - screen:expect [[ - | - {1:~ }|*2 - {2:[No Name] 1longlonglong }| - ^ │ | - {1:~ }│{1:~ }|*2 - 2longlonglong | - ]] - -- Window 3 is current. Cmdline should again show its ruler. - command '3wincmd w' - screen:expect [[ - | - {1:~ }|*2 - {2:[No Name] 1longlonglong }| - │^ | - {1:~ }│{1:~ }|*2 - 3longlonglong | - ]] -end) + it('ruler is redrawn in cmdline with redrawstatus #22804', function() + command([[ + let g:n = 'initial value' + set ls=1 ru ruf=%{g:n} + redraw + let g:n = 'other value' + redrawstatus + ]]) + screen:expect([[ + ^ | + {1:~ }|*6 + other value | + ]]) + end) -it('uses "stl" and "stlnc" fillchars even if they are the same #19803', function() - clear() - local screen = Screen.new(53, 4) - command('hi clear StatusLine') - command('hi clear StatusLineNC') - command('vsplit') - screen:expect { - grid = [[ - ^ │ | - {1:~ }│{1:~ }| - [No Name] [No Name] | - | - ]], - } -end) + it('hidden moves ruler to cmdline', function() + -- Use long ruler to check 'ruler' with 'rulerformat' set has correct width. + command [[ + set ruler rulerformat=%{winnr()}longlonglong ls=0 winwidth=10 + split + wincmd b + vsplit + wincmd t + wincmd | + mode + ]] + -- Window 1 is current. It has a statusline, so cmdline should show the + -- last window's ruler, which has no statusline. + command '1wincmd w' + screen:expect([[ + ^ | + {1:~ }|*2 + {3:[No Name] 1longlonglong }| + │ | + {1:~ }│{1:~ }|*2 + 3longlonglong | + ]]) + -- Window 2 is current. It has no statusline, so cmdline should show its + -- ruler instead. + command '2wincmd w' + screen:expect([[ + | + {1:~ }|*2 + {2:[No Name] 1longlonglong }| + ^ │ | + {1:~ }│{1:~ }|*2 + 2longlonglong | + ]]) + -- Window 3 is current. Cmdline should again show its ruler. + command '3wincmd w' + screen:expect([[ + | + {1:~ }|*2 + {2:[No Name] 1longlonglong }| + │^ | + {1:~ }│{1:~ }|*2 + 3longlonglong | + ]]) + end) -it('showcmdloc=statusline works with vertical splits', function() - clear() - local screen = Screen.new(53, 4) - command('rightbelow vsplit') - command('set showcmd showcmdloc=statusline') - feed('1234') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {2:[No Name] }{3:[No Name] 1234 }| - | - ]]) - feed('<Esc>') - command('set laststatus=3') - feed('1234') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {3:[No Name] 1234 }| - | - ]]) -end) + it('uses "stl" and "stlnc" fillchars even if they are the same #19803', function() + command('hi clear StatusLine') + command('hi clear StatusLineNC') + command('vsplit') + screen:expect([[ + ^ │ | + {1:~ }│{1:~ }|*5 + [No Name] [No Name] | + | + ]]) + end) -it('keymap is shown with vertical splits #27269', function() - clear() - local screen = Screen.new(53, 4) - command('setlocal keymap=dvorak') - command('rightbelow vsplit') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {2:[No Name] <en-dv> }{3:[No Name] <en-dv> }| - | - ]]) - command('set laststatus=3') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {3:[No Name] <en-dv> }| - | - ]]) + it('showcmdloc=statusline works with vertical splits', function() + command('rightbelow vsplit') + command('set showcmd showcmdloc=statusline') + feed('1234') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {2:[No Name] }{3:[No Name] 1234 }| + | + ]]) + feed('<Esc>') + command('set laststatus=3') + feed('1234') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {3:[No Name] 1234 }| + | + ]]) + end) + + it('keymap is shown with vertical splits #27269', function() + command('setlocal keymap=dvorak') + command('rightbelow vsplit') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {2:[No Name] <en-dv> }{3:[No Name] <en-dv> }| + | + ]]) + + command('set laststatus=3') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {3:[No Name] <en-dv> }| + | + ]]) + end) + + it("nested call from nvim_eval_statusline() doesn't overwrite items #32259", function() + exec_lua('vim.o.laststatus = 2') + exec_lua([[vim.o.statusline = '%#Special#B:%{nvim_eval_statusline("%f", []).str}']]) + screen:expect([[ + ^ | + {1:~ }|*5 + {101:B:[No Name] }| + | + ]]) + end) end) diff --git a/test/functional/vimscript/getchar_spec.lua b/test/functional/vimscript/getchar_spec.lua new file mode 100644 index 0000000000..1327d741cf --- /dev/null +++ b/test/functional/vimscript/getchar_spec.lua @@ -0,0 +1,92 @@ +local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') + +local clear = n.clear +local exec = n.exec +local feed = n.feed +local async_command = n.async_meths.nvim_command + +describe('getchar()', function() + before_each(clear) + + -- oldtest: Test_getchar_cursor_position() + it('cursor positioning', function() + local screen = Screen.new(40, 6) + exec([[ + call setline(1, ['foobar', 'foobar', 'foobar']) + call cursor(3, 6) + ]]) + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + | + ]]) + + -- Default: behaves like "msg" when immediately after printing a message, + -- even if :sleep moved cursor elsewhere. + for _, cmd in ipairs({ + 'echo 1234 | call getchar()', + 'echo 1234 | call getchar(-1, {})', + "echo 1234 | call getchar(-1, #{cursor: 'msg'})", + 'echo 1234 | sleep 1m | call getchar()', + 'echo 1234 | sleep 1m | call getchar(-1, {})', + "echo 1234 | sleep 1m | call getchar(-1, #{cursor: 'msg'})", + }) do + async_command(cmd) + screen:expect([[ + foobar |*3 + {1:~ }|*2 + 1234^ | + ]]) + feed('a') + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + 1234 | + ]]) + end + + -- Default: behaves like "keep" when not immediately after printing a message. + for _, cmd in ipairs({ + 'call getchar()', + 'call getchar(-1, {})', + "call getchar(-1, #{cursor: 'keep'})", + "echo 1234 | sleep 1m | call getchar(-1, #{cursor: 'keep'})", + }) do + async_command(cmd) + screen:expect_unchanged() + feed('a') + screen:expect_unchanged() + end + + async_command("call getchar(-1, #{cursor: 'msg'})") + screen:expect([[ + foobar |*3 + {1:~ }|*2 + ^1234 | + ]]) + feed('a') + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + 1234 | + ]]) + + async_command("call getchar(-1, #{cursor: 'hide'})") + screen:expect([[ + foobar |*3 + {1:~ }|*2 + 1234 | + ]]) + feed('a') + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + 1234 | + ]]) + end) +end) diff --git a/test/old/testdir/test_functions.vim b/test/old/testdir/test_functions.vim index f57743900a..738a417b86 100644 --- a/test/old/testdir/test_functions.vim +++ b/test/old/testdir/test_functions.vim @@ -2458,6 +2458,14 @@ func Test_getchar() call assert_fails('call getchar(1, 1)', 'E1206:') call assert_fails('call getcharstr(1, 1)', 'E1206:') + call assert_fails('call getchar(1, #{cursor: "foo"})', 'E475:') + call assert_fails('call getcharstr(1, #{cursor: "foo"})', 'E475:') + call assert_fails('call getchar(1, #{cursor: 0z})', 'E976:') + call assert_fails('call getcharstr(1, #{cursor: 0z})', 'E976:') + call assert_fails('call getchar(1, #{simplify: 0z})', 'E974:') + call assert_fails('call getcharstr(1, #{simplify: 0z})', 'E974:') + call assert_fails('call getchar(1, #{number: []})', 'E745:') + call assert_fails('call getchar(1, #{number: {}})', 'E728:') call assert_fails('call getcharstr(1, #{number: v:true})', 'E475:') call assert_fails('call getcharstr(1, #{number: v:false})', 'E475:') @@ -2476,10 +2484,59 @@ func Test_getchar() enew! endfunc +func Test_getchar_cursor_position() + CheckRunVimInTerminal + + let lines =<< trim END + call setline(1, ['foobar', 'foobar', 'foobar']) + call cursor(3, 6) + nnoremap <F1> <Cmd>echo 1234<Bar>call getchar()<CR> + nnoremap <F2> <Cmd>call getchar()<CR> + nnoremap <F3> <Cmd>call getchar(-1, {})<CR> + nnoremap <F4> <Cmd>call getchar(-1, #{cursor: 'msg'})<CR> + nnoremap <F5> <Cmd>call getchar(-1, #{cursor: 'keep'})<CR> + nnoremap <F6> <Cmd>call getchar(-1, #{cursor: 'hide'})<CR> + END + call writefile(lines, 'XgetcharCursorPos', 'D') + let buf = RunVimInTerminal('-S XgetcharCursorPos', {'rows': 6}) + call WaitForAssert({-> assert_equal([3, 6], term_getcursor(buf)[0:1])}) + + call term_sendkeys(buf, "\<F1>") + call WaitForAssert({-> assert_equal([6, 5], term_getcursor(buf)[0:1])}) + call assert_true(term_getcursor(buf)[2].visible) + call term_sendkeys(buf, 'a') + call WaitForAssert({-> assert_equal([3, 6], term_getcursor(buf)[0:1])}) + call assert_true(term_getcursor(buf)[2].visible) + + for key in ["\<F2>", "\<F3>", "\<F4>"] + call term_sendkeys(buf, key) + call WaitForAssert({-> assert_equal([6, 1], term_getcursor(buf)[0:1])}) + call assert_true(term_getcursor(buf)[2].visible) + call term_sendkeys(buf, 'a') + call WaitForAssert({-> assert_equal([3, 6], term_getcursor(buf)[0:1])}) + call assert_true(term_getcursor(buf)[2].visible) + endfor + + call term_sendkeys(buf, "\<F5>") + call TermWait(buf, 50) + call assert_equal([3, 6], term_getcursor(buf)[0:1]) + call assert_true(term_getcursor(buf)[2].visible) + call term_sendkeys(buf, 'a') + call TermWait(buf, 50) + call assert_equal([3, 6], term_getcursor(buf)[0:1]) + call assert_true(term_getcursor(buf)[2].visible) + + call term_sendkeys(buf, "\<F6>") + call WaitForAssert({-> assert_false(term_getcursor(buf)[2].visible)}) + call term_sendkeys(buf, 'a') + call WaitForAssert({-> assert_true(term_getcursor(buf)[2].visible)}) + call assert_equal([3, 6], term_getcursor(buf)[0:1]) + + call StopVimInTerminal(buf) +endfunc + func Test_libcall_libcallnr() - if !has('libcall') - return - endif + CheckFeature libcall if has('win32') let libc = 'msvcrt.dll' diff --git a/test/old/testdir/test_user_func.vim b/test/old/testdir/test_user_func.vim index b509b03778..b1543c8f24 100644 --- a/test/old/testdir/test_user_func.vim +++ b/test/old/testdir/test_user_func.vim @@ -421,12 +421,48 @@ func Test_func_def_error() call assert_fails('exe l', 'E717:') " Define an autoload function with an incorrect file name - call writefile(['func foo#Bar()', 'return 1', 'endfunc'], 'Xscript') + call writefile(['func foo#Bar()', 'return 1', 'endfunc'], 'Xscript', 'D') call assert_fails('source Xscript', 'E746:') - call delete('Xscript') " Try to list functions using an invalid search pattern call assert_fails('function /\%(/', 'E53:') + + " Use a script-local function to cover uf_name_exp. + func s:TestRedefine(arg1 = 1, arg2 = 10) + let caught_E122 = 0 + try + func s:TestRedefine(arg1 = 1, arg2 = 10) + endfunc + catch /E122:/ + let caught_E122 = 1 + endtry + call assert_equal(1, caught_E122) + + let caught_E127 = 0 + try + func! s:TestRedefine(arg1 = 1, arg2 = 10) + endfunc + catch /E127:/ + let caught_E127 = 1 + endtry + call assert_equal(1, caught_E127) + + " The failures above shouldn't cause heap-use-after-free here. + return [a:arg1 + a:arg2, expand('<stack>')] + endfunc + + let stacks = [] + " Call the function twice. + " Failing to redefine a function shouldn't clear its argument list. + for i in range(2) + let [val, stack] = s:TestRedefine(1000) + call assert_equal(1010, val) + call assert_match(expand('<SID>') .. 'TestRedefine\[20\]$', stack) + call add(stacks, stack) + endfor + call assert_equal(stacks[0], stacks[1]) + + delfunc s:TestRedefine endfunc " Test for deleting a function @@ -910,4 +946,36 @@ func Test_func_curly_brace_invalid_name() delfunc Fail endfunc +func Test_func_return_in_try_verbose() + func TryReturnList() + try + return [1, 2, 3] + endtry + endfunc + func TryReturnNumber() + try + return 123 + endtry + endfunc + func TryReturnOverlongString() + try + return repeat('a', 9999) + endtry + endfunc + + " This should not cause heap-use-after-free + call assert_match('\n:return \[1, 2, 3\] made pending\n', + \ execute('14verbose call TryReturnList()')) + " This should not cause stack-use-after-scope + call assert_match('\n:return 123 made pending\n', + \ execute('14verbose call TryReturnNumber()')) + " An overlong string is truncated + call assert_match('\n:return a\{100,}\.\.\.', + \ execute('14verbose call TryReturnOverlongString()')) + + delfunc TryReturnList + delfunc TryReturnNumber + delfunc TryReturnOverlongString +endfunc + " vim: shiftwidth=2 sts=2 expandtab |