# HG changeset patch # User Bram Moolenaar # Date 1634134504 -7200 # Node ID 377a7686a52f8b82c4c18e4900e1fb40922955bc # Parent 26aa27d37199e8581758ab3204ce14e5fea66451 patch 8.2.3503: Vim9: using g:pat:cmd is confusing Commit: https://github.com/vim/vim/commit/7b829268921e8fc1c63c34d245063c1c4e7d21af Author: Bram Moolenaar Date: Wed Oct 13 15:04:34 2021 +0100 patch 8.2.3503: Vim9: using g:pat:cmd is confusing Problem: Vim9: using g:pat:cmd is confusing. Solution: Do not recognize g: as the :global command. Also for s:pat:repl. (closes #8982) diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt --- a/runtime/doc/vim9.txt +++ b/runtime/doc/vim9.txt @@ -942,8 +942,21 @@ Ex command ranges need to be prefixed wi Some Ex commands can be confused with assignments in Vim9 script: > g:name = value # assignment + :g:pattern:cmd # :global command + +To avoid confusion between a `:global` or `:substitute` command and an +expression or assignment, a few separators cannot be used when these commands +are abbreviated to a single character: ':', '-' and '.'. > g:pattern:cmd # invalid command - ERROR - :g:pattern:cmd # :global command + s:pattern:repl # invalid command - ERROR + g-pattern-cmd # invalid command - ERROR + s-pattern-repl # invalid command - ERROR + g.pattern.cmd # invalid command - ERROR + s.pattern.repl # invalid command - ERROR + +Also, there cannot be a space between the command and the separator: > + g /pattern/cmd # invalid command - ERROR + s /pattern/repl # invalid command - ERROR Functions defined with `:def` compile the whole function. Legacy functions can bail out, and the following lines are not parsed: > diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -666,3 +666,7 @@ EXTERN char e_invalid_value_for_blob_nr[ INIT(= N_("E1239: Invalid value for blob: %d")); EXTERN char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long")); +EXTERN char e_separator_not_supported_str[] + INIT(= N_("E1241: Separator not supported: %s")); +EXTERN char e_no_white_space_allowed_before_separator_str[] + INIT(= N_("E1242: No white space allowed before separator: %s")); diff --git a/src/ex_cmds.c b/src/ex_cmds.c --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -3724,6 +3724,9 @@ ex_substitute(exarg_T *eap) // don't accept alphanumeric for separator if (check_regexp_delim(*cmd) == FAIL) return; + if (in_vim9script() && check_global_and_subst(eap->cmd, eap->arg) + == FAIL) + return; /* * undocumented vi feature: @@ -4899,6 +4902,9 @@ ex_global(exarg_T *eap) cmd = eap->arg; which_pat = RE_LAST; // default: use last used regexp + if (in_vim9script() && check_global_and_subst(eap->cmd, eap->arg) == FAIL) + return; + /* * undocumented vi feature: * "\/" and "\?": use previous search pattern. diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -3600,6 +3600,15 @@ find_ex_command( } } + // "g:", "s:" and "l:" are always assumed to be a variable, thus start + // an expression. A global/substitute/list command needs to use a + // longer name. + if (vim_strchr((char_u *)"gsl", *p) != NULL && p[1] == ':') + { + eap->cmdidx = CMD_eval; + return eap->cmd; + } + // If it is an ID it might be a variable with an operator on the next // line, if the variable exists it can't be an Ex command. if (p > eap->cmd && ends_excmd(*skipwhite(p)) diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro --- a/src/proto/vim9compile.pro +++ b/src/proto/vim9compile.pro @@ -17,6 +17,7 @@ void fill_exarg_from_cctx(exarg_T *eap, int assignment_len(char_u *p, int *heredoc); void vim9_declare_error(char_u *name); int check_vim9_unlet(char_u *name); +int check_global_and_subst(char_u *cmd, char_u *arg); int compile_def_function(ufunc_T *ufunc, int check_return_type, compiletype_T compile_type, cctx_T *outer_cctx); void set_function_type(ufunc_T *ufunc); void delete_instr(isn_T *isn); diff --git a/src/testdir/test_vim9_cmd.vim b/src/testdir/test_vim9_cmd.vim --- a/src/testdir/test_vim9_cmd.vim +++ b/src/testdir/test_vim9_cmd.vim @@ -1489,5 +1489,54 @@ def Test_cmdwin_block() au! justTesting enddef +def Test_var_not_cmd() + var lines =<< trim END + g:notexist:cmd + END + CheckDefAndScriptFailure2(lines, 'E488: Trailing characters: :cmd', 'E121: Undefined variable: g:notexist', 1) + + lines =<< trim END + g-pat-cmd + END + CheckDefAndScriptFailure(lines, 'E1241:', 1) + + lines =<< trim END + s:notexist:repl + END + CheckDefAndScriptFailure2(lines, 'E488: Trailing characters: :repl', 'E121: Undefined variable: s:notexist', 1) + + lines =<< trim END + s-pat-repl + END + CheckDefAndScriptFailure(lines, 'E1241:', 1) + + lines =<< trim END + w:notexist->len() + END + CheckDefExecAndScriptFailure(lines, 'E121: Undefined variable: w:notexist', 1) + + lines =<< trim END + b:notexist->len() + END + CheckDefExecAndScriptFailure(lines, 'E121: Undefined variable: b:notexist', 1) + + lines =<< trim END + t:notexist->len() + END + CheckDefExecAndScriptFailure(lines, 'E121: Undefined variable: t:notexist', 1) +enddef + +def Test_no_space_after_command() + var lines =<< trim END + g /pat/cmd + END + CheckDefAndScriptFailure(lines, 'E1242:', 1) + + lines =<< trim END + s /pat/repl + END + CheckDefAndScriptFailure(lines, 'E1242:', 1) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -758,6 +758,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3503, +/**/ 3502, /**/ 3501, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -9473,6 +9473,26 @@ compile_cexpr(char_u *line, exarg_T *eap #endif /* + * Check if the separator for a :global or :substitute command is OK. + */ + int +check_global_and_subst(char_u *cmd, char_u *arg) +{ + if (arg == cmd + 1 && vim_strchr(":-.", *arg) != NULL) + { + semsg(_(e_separator_not_supported_str), arg); + return FAIL; + } + if (VIM_ISWHITE(cmd[1])) + { + semsg(_(e_no_white_space_allowed_before_separator_str), cmd); + return FAIL; + } + return OK; +} + + +/* * Add a function to the list of :def functions. * This sets "ufunc->uf_dfunc_idx" but the function isn't compiled yet. */ @@ -10066,6 +10086,8 @@ compile_def_function( break; case CMD_substitute: + if (check_global_and_subst(ea.cmd, p) == FAIL) + goto erret; if (cctx.ctx_skip == SKIP_YES) line = (char_u *)""; else @@ -10132,6 +10154,10 @@ compile_def_function( line = compile_script(line, &cctx); break; + case CMD_global: + if (check_global_and_subst(ea.cmd, p) == FAIL) + goto erret; + // FALLTHROUGH default: // Not recognized, execute with do_cmdline_cmd(). ea.arg = p;