aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/builtin.txt8
-rw-r--r--runtime/doc/develop.txt4
-rw-r--r--runtime/doc/diagnostic.txt28
-rw-r--r--runtime/doc/news.txt5
-rw-r--r--runtime/lua/vim/_meta/vimfn.lua8
-rw-r--r--runtime/lua/vim/diagnostic.lua31
-rw-r--r--src/nvim/debugger.c6
-rw-r--r--src/nvim/eval.lua8
-rw-r--r--src/nvim/eval/typval.c2
-rw-r--r--src/nvim/eval/typval_defs.h7
-rw-r--r--src/nvim/eval/userfunc.c259
-rw-r--r--src/nvim/ex_docmd.c3
-rw-r--r--src/nvim/getchar.c33
-rw-r--r--src/nvim/keycodes.c2
-rw-r--r--src/nvim/option.c4
-rw-r--r--src/nvim/statusline.c27
-rw-r--r--src/nvim/tui/tui.c4
-rw-r--r--test/functional/lua/diagnostic_spec.lua26
-rw-r--r--test/functional/ui/statusline_spec.lua505
-rw-r--r--test/functional/vimscript/getchar_spec.lua92
-rw-r--r--test/old/testdir/test_functions.vim63
-rw-r--r--test/old/testdir/test_user_func.vim72
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