mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +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
	 zeertzjq
					zeertzjq