# HG changeset patch # User Bram Moolenaar # Date 1627846203 -7200 # Node ID dcd45fe7fe2ef1ea1253abf8a9c8e3f539b77fb2 # Parent 0290badbbf7b484ada1b71c0a055d61fb8a6e60d patch 8.2.3271: Vim9: cannot use :command or :au with block in :def function Commit: https://github.com/vim/vim/commit/e4db17fb6e2d029aa2dddfca703ace9bcf0d85fd Author: Bram Moolenaar Date: Sun Aug 1 21:19:43 2021 +0200 patch 8.2.3271: Vim9: cannot use :command or :au with block in :def function Problem: Vim9: cannot use :command or :au with a block in a :def function. Solution: Recognize the start of the block. diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -2741,7 +2741,7 @@ checkforcmd( * Check for an Ex command with optional tail, not followed by "(". * If there is a match advance "pp" to the argument and return TRUE. */ - static int + int checkforcmd_noparen( char_u **pp, // start of command char *cmd, // name of command diff --git a/src/proto/ex_docmd.pro b/src/proto/ex_docmd.pro --- a/src/proto/ex_docmd.pro +++ b/src/proto/ex_docmd.pro @@ -9,6 +9,7 @@ void *getline_cookie(char_u *(*fgetline) char_u *getline_peek(char_u *(*fgetline)(int, void *, int, getline_opt_T), void *cookie); char *ex_errmsg(char *msg, char_u *arg); int checkforcmd(char_u **pp, char *cmd, int len); +int checkforcmd_noparen(char_u **pp, char *cmd, int len); int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, int skip_only); int has_cmdmod(cmdmod_T *cmod); int cmdmod_error(void); diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -334,6 +334,34 @@ def Test_block_local_vars_with_func() CheckScriptSuccess(lines) enddef +" legacy func for command that's defined later +func InvokeSomeCommand() + SomeCommand +endfunc + +def Test_autocommand_block() + com SomeCommand { + g:someVar = 'some' + } + InvokeSomeCommand() + assert_equal('some', g:someVar) + + delcommand SomeCommand + unlet g:someVar +enddef + +def Test_command_block() + au BufNew *.xml { + g:otherVar = 'other' + } + split other.xml + assert_equal('other', g:otherVar) + + bwipe! + au! BufNew *.xml + unlet g:otherVar +enddef + func g:NoSuchFunc() echo 'none' endfunc diff --git a/src/usercmd.c b/src/usercmd.c --- a/src/usercmd.c +++ b/src/usercmd.c @@ -983,29 +983,32 @@ may_get_cmd_block(exarg_T *eap, char_u * if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1)) && eap->getline != NULL) { - garray_T ga; - char_u *line = NULL; + garray_T ga; + char_u *line = NULL; ga_init2(&ga, sizeof(char_u *), 10); if (ga_add_string(&ga, p) == FAIL) return retp; - // Read lines between '{' and '}'. Does not support nesting or - // here-doc constructs. - for (;;) - { - vim_free(line); - if ((line = eap->getline(':', eap->cookie, - 0, GETLINE_CONCAT_CONTBAR)) == NULL) + // If the argument ends in "}" it must have been concatenated already + // for ISN_EXEC. + if (p[STRLEN(p) - 1] != '}') + // Read lines between '{' and '}'. Does not support nesting or + // here-doc constructs. + for (;;) { - emsg(_(e_missing_rcurly)); - break; + vim_free(line); + if ((line = eap->getline(':', eap->cookie, + 0, GETLINE_CONCAT_CONTBAR)) == NULL) + { + emsg(_(e_missing_rcurly)); + break; + } + if (ga_add_string(&ga, line) == FAIL) + break; + if (*skipwhite(line) == '}') + break; } - if (ga_add_string(&ga, line) == FAIL) - break; - if (*skipwhite(line) == '}') - break; - } vim_free(line); retp = *tofree = ga_concat_strings(&ga, "\n"); ga_clear_strings(&ga); diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -903,12 +903,25 @@ get_function_body( --end; if (end > p && *end == '{') { + int is_block; + + // check for trailing "=> {": start of an inline function --end; while (end > p && VIM_ISWHITE(*end)) --end; - if (end > p + 2 && end[-1] == '=' && end[0] == '>') + is_block = end > p + 2 && end[-1] == '=' && end[0] == '>'; + if (!is_block) { - // found trailing "=> {", start of an inline function + char_u *s = p; + + // check for line starting with "au" for :autocmd or + // "com" for :command, these can use a {} block + is_block = checkforcmd_noparen(&s, "autocmd", 2) + || checkforcmd_noparen(&s, "command", 3); + } + + if (is_block) + { if (nesting == MAX_FUNC_NESTING - 1) emsg(_(e_function_nesting_too_deep)); else diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -756,6 +756,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3271, +/**/ 3270, /**/ 3269, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -8861,11 +8861,13 @@ compile_put(char_u *arg, exarg_T *eap, c * A command that is not compiled, execute with legacy code. */ static char_u * -compile_exec(char_u *line, exarg_T *eap, cctx_T *cctx) -{ +compile_exec(char_u *line_arg, exarg_T *eap, cctx_T *cctx) +{ + char_u *line = line_arg; char_u *p; int has_expr = FALSE; char_u *nextcmd = (char_u *)""; + char_u *tofree = NULL; if (cctx->ctx_skip == SKIP_YES) goto theend; @@ -8922,6 +8924,34 @@ compile_exec(char_u *line, exarg_T *eap, nextcmd = p + 1; } } + else if (eap->cmdidx == CMD_command || eap->cmdidx == CMD_autocmd) + { + // If there is a trailing '{' read lines until the '}' + p = eap->arg + STRLEN(eap->arg) - 1; + while (p > eap->arg && VIM_ISWHITE(*p)) + --p; + if (*p == '{') + { + exarg_T ea; + int flags; // unused + int start_lnum = SOURCING_LNUM; + + CLEAR_FIELD(ea); + ea.arg = eap->arg; + fill_exarg_from_cctx(&ea, cctx); + (void)may_get_cmd_block(&ea, p, &tofree, &flags); + if (tofree != NULL) + { + *p = NUL; + line = concat_str(line, tofree); + if (line == NULL) + goto theend; + vim_free(tofree); + tofree = line; + SOURCING_LNUM = start_lnum; + } + } + } } if (eap->cmdidx == CMD_syntax && STRNCMP(eap->arg, "include ", 8) == 0) @@ -9008,6 +9038,7 @@ theend: --nextcmd; *nextcmd = '|'; } + vim_free(tofree); return nextcmd; }