mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 11:28:22 +00:00
vim-patch:9.0.0370: cleaning up afterwards can make a function messy
Problem: Cleaning up afterwards can make a function messy.
Solution: Add the :defer command.
1d84f7608f
Omit EX_EXPR_ARG: Vim9 script only.
Make :def throw E319 to avoid confusing behavior.
Co-authored-by: Bram Moolenaar <Bram@vim.org>
This commit is contained in:
@@ -350,10 +350,67 @@ A function can also be called as part of evaluating an expression or when it
|
|||||||
is used as a method: >
|
is used as a method: >
|
||||||
let x = GetList()
|
let x = GetList()
|
||||||
let y = GetList()->Filter()
|
let y = GetList()->Filter()
|
||||||
|
<
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
3. Cleaning up in a function ~
|
||||||
|
*:defer*
|
||||||
|
:defer {func}({args}) Call {func} when the current function is done.
|
||||||
|
{args} are evaluated here.
|
||||||
|
|
||||||
|
Quite often a command in a function has a global effect, which must be undone
|
||||||
|
when the function finishes. Handling this in all kinds of situations can be a
|
||||||
|
hassle. Especially when an unexpected error is encountered. This can be done
|
||||||
|
with `try` / `finally` blocks, but this gets complicated when there is more
|
||||||
|
than one.
|
||||||
|
|
||||||
|
A much simpler solution is using `defer`. It schedules a function call when
|
||||||
|
the function is returning, no matter if there is an error. Example: >
|
||||||
|
func Filter(text) abort
|
||||||
|
call writefile(a:text, 'Tempfile')
|
||||||
|
call system('filter < Tempfile > Outfile')
|
||||||
|
call Handle('Outfile')
|
||||||
|
call delete('Tempfile')
|
||||||
|
call delete('Outfile')
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
Here 'Tempfile' and 'Outfile' will not be deleted if something causes the
|
||||||
|
function to abort. `:defer` can be used to avoid that: >
|
||||||
|
func Filter(text) abort
|
||||||
|
call writefile(a:text, 'Tempfile')
|
||||||
|
defer delete('Tempfile')
|
||||||
|
defer delete('Outfile')
|
||||||
|
call system('filter < Tempfile > Outfile')
|
||||||
|
call Handle('Outfile')
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
Note that deleting "Outfile" is scheduled before calling `system()`, since it
|
||||||
|
can be created even when `system()` fails.
|
||||||
|
|
||||||
|
The deferred functions are called in reverse order, the last one added is
|
||||||
|
executed first. A useless example: >
|
||||||
|
func Useless() abort
|
||||||
|
for s in range(3)
|
||||||
|
defer execute('echomsg "number ' .. s .. '"')
|
||||||
|
endfor
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
Now `:messages` shows:
|
||||||
|
number 2
|
||||||
|
number 1
|
||||||
|
number 0
|
||||||
|
|
||||||
|
Any return value of the deferred function is discarded. The function cannot
|
||||||
|
be followed by anything, such as "->func" or ".member". Currently `:defer
|
||||||
|
GetArg()->TheFunc()` does not work, it may work in a later version.
|
||||||
|
|
||||||
|
Errors are reported but do not cause aborting execution of deferred functions.
|
||||||
|
|
||||||
|
No range is accepted.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
3. Automatically loading functions ~
|
|
||||||
|
4. Automatically loading functions ~
|
||||||
*autoload-functions*
|
*autoload-functions*
|
||||||
When using many or large functions, it's possible to automatically define them
|
When using many or large functions, it's possible to automatically define them
|
||||||
only when they are used. There are two methods: with an autocommand and with
|
only when they are used. There are two methods: with an autocommand and with
|
||||||
|
@@ -299,6 +299,7 @@ struct funccall_S {
|
|||||||
linenr_T breakpoint; ///< Next line with breakpoint or zero.
|
linenr_T breakpoint; ///< Next line with breakpoint or zero.
|
||||||
int dbg_tick; ///< debug_tick when breakpoint was set.
|
int dbg_tick; ///< debug_tick when breakpoint was set.
|
||||||
int level; ///< Top nesting level of executed function.
|
int level; ///< Top nesting level of executed function.
|
||||||
|
garray_T fc_defer; ///< Functions to be called on return.
|
||||||
proftime_T prof_child; ///< Time spent in a child.
|
proftime_T prof_child; ///< Time spent in a child.
|
||||||
funccall_T *caller; ///< Calling function or NULL; or next funccal in
|
funccall_T *caller; ///< Calling function or NULL; or next funccal in
|
||||||
///< list pointed to by previous_funccal.
|
///< list pointed to by previous_funccal.
|
||||||
|
@@ -53,6 +53,13 @@
|
|||||||
# include "eval/userfunc.c.generated.h"
|
# include "eval/userfunc.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// structure used as item in "fc_defer"
|
||||||
|
typedef struct {
|
||||||
|
char *dr_name; ///< function name, allocated
|
||||||
|
typval_T dr_argvars[MAX_FUNC_ARGS + 1];
|
||||||
|
int dr_argcount;
|
||||||
|
} defer_T;
|
||||||
|
|
||||||
static hashtab_T func_hashtab;
|
static hashtab_T func_hashtab;
|
||||||
|
|
||||||
// Used by get_func_tv()
|
// Used by get_func_tv()
|
||||||
@@ -469,7 +476,42 @@ void emsg_funcname(const char *errmsg, const char *name)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocate a variable for the result of a function.
|
/// Get function arguments at "*arg" and advance it.
|
||||||
|
/// Return them in "*argvars[MAX_FUNC_ARGS + 1]" and the count in "argcount".
|
||||||
|
static int get_func_arguments(char **arg, evalarg_T *const evalarg, int partial_argc,
|
||||||
|
typval_T *argvars, int *argcount)
|
||||||
|
{
|
||||||
|
char *argp = *arg;
|
||||||
|
int ret = OK;
|
||||||
|
|
||||||
|
// Get the arguments.
|
||||||
|
while (*argcount < MAX_FUNC_ARGS - partial_argc) {
|
||||||
|
argp = skipwhite(argp + 1); // skip the '(' or ','
|
||||||
|
|
||||||
|
if (*argp == ')' || *argp == ',' || *argp == NUL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (eval1(&argp, &argvars[*argcount], evalarg) == FAIL) {
|
||||||
|
ret = FAIL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
(*argcount)++;
|
||||||
|
if (*argp != ',') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
argp = skipwhite(argp);
|
||||||
|
if (*argp == ')') {
|
||||||
|
argp++;
|
||||||
|
} else {
|
||||||
|
ret = FAIL;
|
||||||
|
}
|
||||||
|
*arg = argp;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a function and put the result in "rettv".
|
||||||
///
|
///
|
||||||
/// @param name name of the function
|
/// @param name name of the function
|
||||||
/// @param len length of "name" or -1 to use strlen()
|
/// @param len length of "name" or -1 to use strlen()
|
||||||
@@ -480,34 +522,16 @@ void emsg_funcname(const char *errmsg, const char *name)
|
|||||||
int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, evalarg_T *const evalarg,
|
int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, evalarg_T *const evalarg,
|
||||||
funcexe_T *funcexe)
|
funcexe_T *funcexe)
|
||||||
{
|
{
|
||||||
char *argp;
|
|
||||||
int ret = OK;
|
|
||||||
typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
|
typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
|
||||||
int argcount = 0; // number of arguments found
|
int argcount = 0; // number of arguments found
|
||||||
const bool evaluate = evalarg == NULL ? false : (evalarg->eval_flags & EVAL_EVALUATE);
|
const bool evaluate = evalarg == NULL ? false : (evalarg->eval_flags & EVAL_EVALUATE);
|
||||||
|
|
||||||
// Get the arguments.
|
char *argp = *arg;
|
||||||
argp = *arg;
|
int ret = get_func_arguments(&argp, evalarg,
|
||||||
while (argcount < MAX_FUNC_ARGS
|
(funcexe->fe_partial == NULL
|
||||||
- (funcexe->fe_partial == NULL ? 0 : funcexe->fe_partial->pt_argc)) {
|
? 0
|
||||||
argp = skipwhite(argp + 1); // skip the '(' or ','
|
: funcexe->fe_partial->pt_argc),
|
||||||
if (*argp == ')' || *argp == ',' || *argp == NUL) {
|
argvars, &argcount);
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (eval1(&argp, &argvars[argcount], evalarg) == FAIL) {
|
|
||||||
ret = FAIL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
argcount++;
|
|
||||||
if (*argp != ',') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (*argp == ')') {
|
|
||||||
argp++;
|
|
||||||
} else {
|
|
||||||
ret = FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret == OK) {
|
if (ret == OK) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@@ -1148,6 +1172,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
|
|||||||
DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
|
DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invoke functions added with ":defer".
|
||||||
|
handle_defer();
|
||||||
|
|
||||||
RedrawingDisabled--;
|
RedrawingDisabled--;
|
||||||
|
|
||||||
// when the function was aborted because of an error, return -1
|
// when the function was aborted because of an error, return -1
|
||||||
@@ -1544,7 +1571,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t
|
|||||||
int argv_base = 0;
|
int argv_base = 0;
|
||||||
partial_T *partial = funcexe->fe_partial;
|
partial_T *partial = funcexe->fe_partial;
|
||||||
|
|
||||||
// Initialize rettv so that it is safe for caller to invoke clear_tv(rettv)
|
// Initialize rettv so that it is safe for caller to invoke tv_clear(rettv)
|
||||||
// even when call_func() returns FAIL.
|
// even when call_func() returns FAIL.
|
||||||
rettv->v_type = VAR_UNKNOWN;
|
rettv->v_type = VAR_UNKNOWN;
|
||||||
|
|
||||||
@@ -3033,7 +3060,115 @@ void ex_return(exarg_T *eap)
|
|||||||
clear_evalarg(&evalarg, eap);
|
clear_evalarg(&evalarg, eap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int ex_call_inner(exarg_T *eap, char *name, char **arg, char *startarg,
|
||||||
|
funcexe_T *funcexe_init, evalarg_T *const evalarg)
|
||||||
|
{
|
||||||
|
bool doesrange;
|
||||||
|
bool failed = false;
|
||||||
|
|
||||||
|
for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) {
|
||||||
|
if (eap->addr_count > 0) { // -V560
|
||||||
|
if (lnum > curbuf->b_ml.ml_line_count) {
|
||||||
|
// If the function deleted lines or switched to another buffer
|
||||||
|
// the line number may become invalid.
|
||||||
|
emsg(_(e_invrange));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
curwin->w_cursor.lnum = lnum;
|
||||||
|
curwin->w_cursor.col = 0;
|
||||||
|
curwin->w_cursor.coladd = 0;
|
||||||
|
}
|
||||||
|
*arg = startarg;
|
||||||
|
|
||||||
|
funcexe_T funcexe = *funcexe_init;
|
||||||
|
funcexe.fe_doesrange = &doesrange;
|
||||||
|
typval_T rettv;
|
||||||
|
rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
|
||||||
|
if (get_func_tv(name, -1, &rettv, arg, evalarg, &funcexe) == FAIL) {
|
||||||
|
failed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle a function returning a Funcref, Dictionary or List.
|
||||||
|
if (handle_subscript((const char **)arg, &rettv, &EVALARG_EVALUATE, true) == FAIL) {
|
||||||
|
failed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tv_clear(&rettv);
|
||||||
|
if (doesrange) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop when immediately aborting on error, or when an interrupt
|
||||||
|
// occurred or an exception was thrown but not caught.
|
||||||
|
// get_func_tv() returned OK, so that the check for trailing
|
||||||
|
// characters below is executed.
|
||||||
|
if (aborting()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Core part of ":defer func(arg)". "arg" points to the "(" and is advanced.
|
||||||
|
///
|
||||||
|
/// @return FAIL or OK.
|
||||||
|
static int ex_defer_inner(char *name, char **arg, evalarg_T *const evalarg)
|
||||||
|
{
|
||||||
|
typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
|
||||||
|
int argcount = 0; // number of arguments found
|
||||||
|
int ret = FAIL;
|
||||||
|
|
||||||
|
if (current_funccal == NULL) {
|
||||||
|
semsg(_(e_str_not_inside_function), "defer");
|
||||||
|
return FAIL;
|
||||||
|
}
|
||||||
|
if (get_func_arguments(arg, evalarg, false, argvars, &argcount) == FAIL) {
|
||||||
|
goto theend;
|
||||||
|
}
|
||||||
|
char *saved_name = xstrdup(name);
|
||||||
|
|
||||||
|
if (current_funccal->fc_defer.ga_itemsize == 0) {
|
||||||
|
ga_init(¤t_funccal->fc_defer, sizeof(defer_T), 10);
|
||||||
|
}
|
||||||
|
defer_T *dr = GA_APPEND_VIA_PTR(defer_T, ¤t_funccal->fc_defer);
|
||||||
|
dr->dr_name = saved_name;
|
||||||
|
dr->dr_argcount = argcount;
|
||||||
|
while (argcount > 0) {
|
||||||
|
argcount--;
|
||||||
|
dr->dr_argvars[argcount] = argvars[argcount];
|
||||||
|
}
|
||||||
|
ret = OK;
|
||||||
|
|
||||||
|
theend:
|
||||||
|
while (--argcount >= 0) {
|
||||||
|
tv_clear(&argvars[argcount]);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invoked after a function has finished: invoke ":defer" functions.
|
||||||
|
static void handle_defer(void)
|
||||||
|
{
|
||||||
|
for (int idx = current_funccal->fc_defer.ga_len - 1; idx >= 0; idx--) {
|
||||||
|
defer_T *dr = ((defer_T *)current_funccal->fc_defer.ga_data) + idx;
|
||||||
|
funcexe_T funcexe = { .fe_evaluate = true };
|
||||||
|
typval_T rettv;
|
||||||
|
rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
|
||||||
|
call_func(dr->dr_name, -1, &rettv, dr->dr_argcount, dr->dr_argvars, &funcexe);
|
||||||
|
tv_clear(&rettv);
|
||||||
|
xfree(dr->dr_name);
|
||||||
|
for (int i = dr->dr_argcount - 1; i >= 0; i--) {
|
||||||
|
tv_clear(&dr->dr_argvars[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ga_clear(¤t_funccal->fc_defer);
|
||||||
|
}
|
||||||
|
|
||||||
/// ":1,25call func(arg1, arg2)" function call.
|
/// ":1,25call func(arg1, arg2)" function call.
|
||||||
|
/// ":defer func(arg1, arg2)" deferred function call.
|
||||||
void ex_call(exarg_T *eap)
|
void ex_call(exarg_T *eap)
|
||||||
{
|
{
|
||||||
char *arg = eap->arg;
|
char *arg = eap->arg;
|
||||||
@@ -3041,9 +3176,6 @@ void ex_call(exarg_T *eap)
|
|||||||
char *name;
|
char *name;
|
||||||
char *tofree;
|
char *tofree;
|
||||||
int len;
|
int len;
|
||||||
typval_T rettv;
|
|
||||||
linenr_T lnum;
|
|
||||||
bool doesrange;
|
|
||||||
bool failed = false;
|
bool failed = false;
|
||||||
funcdict_T fudi;
|
funcdict_T fudi;
|
||||||
partial_T *partial = NULL;
|
partial_T *partial = NULL;
|
||||||
@@ -3051,6 +3183,7 @@ void ex_call(exarg_T *eap)
|
|||||||
|
|
||||||
fill_evalarg_from_eap(&evalarg, eap, eap->skip);
|
fill_evalarg_from_eap(&evalarg, eap, eap->skip);
|
||||||
if (eap->skip) {
|
if (eap->skip) {
|
||||||
|
typval_T rettv;
|
||||||
// trans_function_name() doesn't work well when skipping, use eval0()
|
// trans_function_name() doesn't work well when skipping, use eval0()
|
||||||
// instead to skip to any following command, e.g. for:
|
// instead to skip to any following command, e.g. for:
|
||||||
// :if 0 | call dict.foo().bar() | endif.
|
// :if 0 | call dict.foo().bar() | endif.
|
||||||
@@ -3089,59 +3222,24 @@ void ex_call(exarg_T *eap)
|
|||||||
// Skip white space to allow ":call func ()". Not good, but required for
|
// Skip white space to allow ":call func ()". Not good, but required for
|
||||||
// backward compatibility.
|
// backward compatibility.
|
||||||
startarg = skipwhite(arg);
|
startarg = skipwhite(arg);
|
||||||
rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this.
|
|
||||||
|
|
||||||
if (*startarg != '(') {
|
if (*startarg != '(') {
|
||||||
semsg(_(e_missingparen), eap->arg);
|
semsg(_(e_missingparen), eap->arg);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
lnum = eap->line1;
|
if (eap->cmdidx == CMD_defer) {
|
||||||
for (; lnum <= eap->line2; lnum++) {
|
|
||||||
if (eap->addr_count > 0) { // -V560
|
|
||||||
if (lnum > curbuf->b_ml.ml_line_count) {
|
|
||||||
// If the function deleted lines or switched to another buffer
|
|
||||||
// the line number may become invalid.
|
|
||||||
emsg(_(e_invrange));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
curwin->w_cursor.lnum = lnum;
|
|
||||||
curwin->w_cursor.col = 0;
|
|
||||||
curwin->w_cursor.coladd = 0;
|
|
||||||
}
|
|
||||||
arg = startarg;
|
arg = startarg;
|
||||||
|
failed = ex_defer_inner(name, &arg, &evalarg) == FAIL;
|
||||||
|
} else {
|
||||||
funcexe_T funcexe = FUNCEXE_INIT;
|
funcexe_T funcexe = FUNCEXE_INIT;
|
||||||
funcexe.fe_firstline = eap->line1;
|
|
||||||
funcexe.fe_lastline = eap->line2;
|
|
||||||
funcexe.fe_doesrange = &doesrange;
|
|
||||||
funcexe.fe_evaluate = true;
|
|
||||||
funcexe.fe_partial = partial;
|
funcexe.fe_partial = partial;
|
||||||
funcexe.fe_selfdict = fudi.fd_dict;
|
funcexe.fe_selfdict = fudi.fd_dict;
|
||||||
|
funcexe.fe_firstline = eap->line1;
|
||||||
|
funcexe.fe_lastline = eap->line2;
|
||||||
funcexe.fe_found_var = found_var;
|
funcexe.fe_found_var = found_var;
|
||||||
if (get_func_tv(name, -1, &rettv, &arg, &evalarg, &funcexe) == FAIL) {
|
funcexe.fe_evaluate = true;
|
||||||
failed = true;
|
failed = ex_call_inner(eap, name, &arg, startarg, &funcexe, &evalarg);
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle a function returning a Funcref, Dictionary or List.
|
|
||||||
if (handle_subscript((const char **)&arg, &rettv, &EVALARG_EVALUATE, true) == FAIL) {
|
|
||||||
failed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
tv_clear(&rettv);
|
|
||||||
if (doesrange) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop when immediately aborting on error, or when an interrupt
|
|
||||||
// occurred or an exception was thrown but not caught.
|
|
||||||
// get_func_tv() returned OK, so that the check for trailing
|
|
||||||
// characters below is executed.
|
|
||||||
if (aborting()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// When inside :try we need to check for following "| catch" or "| endtry".
|
// When inside :try we need to check for following "| catch" or "| endtry".
|
||||||
|
@@ -714,6 +714,18 @@ module.cmds = {
|
|||||||
addr_type='ADDR_OTHER',
|
addr_type='ADDR_OTHER',
|
||||||
func='ex_debuggreedy',
|
func='ex_debuggreedy',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
command='def',
|
||||||
|
flags=bit.bor(EXTRA, BANG, SBOXOK, CMDWIN, LOCK_OK),
|
||||||
|
addr_type='ADDR_NONE',
|
||||||
|
func='ex_ni',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command='defer',
|
||||||
|
flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, CMDWIN, LOCK_OK),
|
||||||
|
addr_type='ADDR_NONE',
|
||||||
|
func='ex_call',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
command='delcommand',
|
command='delcommand',
|
||||||
flags=bit.bor(BANG, NEEDARG, WORD1, TRLBAR, CMDWIN, LOCK_OK),
|
flags=bit.bor(BANG, NEEDARG, WORD1, TRLBAR, CMDWIN, LOCK_OK),
|
||||||
|
@@ -1966,7 +1966,7 @@ void rewind_conditionals(cstack_T *cstack, int idx, int cond_type, int *cond_lev
|
|||||||
/// Handle ":endfunction" when not after a ":function"
|
/// Handle ":endfunction" when not after a ":function"
|
||||||
void ex_endfunction(exarg_T *eap)
|
void ex_endfunction(exarg_T *eap)
|
||||||
{
|
{
|
||||||
emsg(_("E193: :endfunction not inside a function"));
|
semsg(_(e_str_not_inside_function), ":endfunction");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @return true if the string "p" looks like a ":while" or ":for" command.
|
/// @return true if the string "p" looks like a ":while" or ":for" command.
|
||||||
|
@@ -986,6 +986,8 @@ EXTERN const char e_maxmempat[] INIT(= N_("E363: pattern uses more memory than '
|
|||||||
EXTERN const char e_emptybuf[] INIT(= N_("E749: empty buffer"));
|
EXTERN const char e_emptybuf[] INIT(= N_("E749: empty buffer"));
|
||||||
EXTERN const char e_nobufnr[] INIT(= N_("E86: Buffer %" PRId64 " does not exist"));
|
EXTERN const char e_nobufnr[] INIT(= N_("E86: Buffer %" PRId64 " does not exist"));
|
||||||
|
|
||||||
|
EXTERN const char e_str_not_inside_function[] INIT(= N_("E193: %s not inside a function"));
|
||||||
|
|
||||||
EXTERN const char e_invalpat[] INIT(= N_("E682: Invalid search pattern or delimiter"));
|
EXTERN const char e_invalpat[] INIT(= N_("E682: Invalid search pattern or delimiter"));
|
||||||
EXTERN const char e_bufloaded[] INIT(= N_("E139: File is loaded in another buffer"));
|
EXTERN const char e_bufloaded[] INIT(= N_("E139: File is loaded in another buffer"));
|
||||||
EXTERN const char e_notset[] INIT(= N_("E764: Option '%s' is not set"));
|
EXTERN const char e_notset[] INIT(= N_("E764: Option '%s' is not set"));
|
||||||
|
@@ -532,4 +532,36 @@ func Test_funcdef_alloc_failure()
|
|||||||
bw!
|
bw!
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
func AddDefer(arg)
|
||||||
|
call extend(g:deferred, [a:arg])
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
func WithDeferTwo()
|
||||||
|
call extend(g:deferred, ['in Two'])
|
||||||
|
for nr in range(3)
|
||||||
|
defer AddDefer('Two' .. nr)
|
||||||
|
endfor
|
||||||
|
call extend(g:deferred, ['end Two'])
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
func WithDeferOne()
|
||||||
|
call extend(g:deferred, ['in One'])
|
||||||
|
call writefile(['text'], 'Xfuncdefer')
|
||||||
|
defer delete('Xfuncdefer')
|
||||||
|
defer AddDefer('One')
|
||||||
|
call WithDeferTwo()
|
||||||
|
call extend(g:deferred, ['end One'])
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
func Test_defer()
|
||||||
|
let g:deferred = []
|
||||||
|
call WithDeferOne()
|
||||||
|
|
||||||
|
call assert_equal(['in One', 'in Two', 'end Two', 'Two2', 'Two1', 'Two0', 'end One', 'One'], g:deferred)
|
||||||
|
unlet g:deferred
|
||||||
|
|
||||||
|
call assert_equal('', glob('Xfuncdefer'))
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
|
||||||
" vim: shiftwidth=2 sts=2 expandtab
|
" vim: shiftwidth=2 sts=2 expandtab
|
||||||
|
Reference in New Issue
Block a user