Problem: Cannot enforce a Vim script style.
Solution: Add the :scriptversion command. (closes #3857)
7. Commands |expression-commands|
8. Exception handling |exception-handling|
9. Examples |eval-examples|
-10. No +eval feature |no-eval-feature|
-11. The sandbox |eval-sandbox|
-12. Textlock |textlock|
-13. Testing |testing|
+10. Vim script version |vimscript-version|
+11. No +eval feature |no-eval-feature|
+12. The sandbox |eval-sandbox|
+13. Textlock |textlock|
+14. Testing |testing|
{Vi does not have any of these commands}
For String concatenation ".." is preferred, since "." is ambiguous, it is also
used for |Dict| member access and floating point numbers.
+When |vimscript-version| is 2 or higher, using "." is not allowed.
expr7 * expr7 Number multiplication *expr-star*
expr7 / expr7 Number division *expr-/*
vim_starting True while initial source'ing takes place. |startup|
*vim_starting*
viminfo Compiled with viminfo support.
+vimscript-1 Compiled Vim script version 1 support
+vimscript-2 Compiled Vim script version 2 support
virtualedit Compiled with 'virtualedit' option. (always true)
visual Compiled with Visual mode. (always true)
visualextra Compiled with extra Visual mode commands. (always
When the selected range of items is partly past the
end of the list, items will be added.
- *:let+=* *:let-=* *:letstar=*
- *:let/=* *:let%=* *:let.=* *E734*
+ *:let+=* *:let-=* *:letstar=*
+ *:let/=* *:let%=* *:let.=* *:let..=* *E734* *E985*
:let {var} += {expr1} Like ":let {var} = {var} + {expr1}".
:let {var} -= {expr1} Like ":let {var} = {var} - {expr1}".
:let {var} *= {expr1} Like ":let {var} = {var} * {expr1}".
:let {var} /= {expr1} Like ":let {var} = {var} / {expr1}".
:let {var} %= {expr1} Like ":let {var} = {var} % {expr1}".
:let {var} .= {expr1} Like ":let {var} = {var} . {expr1}".
+:let {var} ..= {expr1} Like ":let {var} = {var} .. {expr1}".
These fail if {var} was not set yet and when the type
of {var} and {expr1} don't fit the operator.
+ `.=` is not supported with Vim script version 2 and
+ later, see |vimscript-version|.
:let ${env-name} = {expr1} *:let-environment* *:let-$*
unlet scriptnames_output
==============================================================================
-10. No +eval feature *no-eval-feature*
+10. Vim script versions *vimscript-version* *vimscript-versions*
+
+Over time many features have been added to Vim script. This includes Ex
+commands, functions, variable types, etc. Each individual feature can be
+checked with the |has()| and |exists()| functions.
+
+Sometimes old syntax of functionality gets in the way of making Vim better.
+When support is taken away this will break older Vim scripts. To make this
+explicit the |:scriptversion| command can be used. When a Vim script is not
+compatible with older versions of Vim this will give an explicit error,
+instead of failing in mysterious ways. >
+
+ :scriptversion 1
+< This is the original Vim script, same as not using a |:scriptversion|
+ command. Can be used to go back to old syntax for a range of lines.
+ Test for support with: >
+ has('vimscript-1')
+
+ :scriptversion 2
+< String concatenation with "." is not supported, use ".." instead.
+ This avoids the ambiguity using "." for Dict member access and
+ floating point numbers. Now ".5" means the number 0.5.
+ Test for support with: >
+ has('vimscript-2')
+
+
+==============================================================================
+11. No +eval feature *no-eval-feature*
When the |+eval| feature was disabled at compile time, none of the expression
evaluation commands are available. To prevent this from causing Vim scripts
silently ignored, and the command is executed.
==============================================================================
-11. The sandbox *eval-sandbox* *sandbox* *E48*
+12. The sandbox *eval-sandbox* *sandbox* *E48*
The 'foldexpr', 'formatexpr', 'includeexpr', 'indentexpr', 'statusline' and
'foldtext' options may be evaluated in a sandbox. This means that you are
option will still be marked as it was set in the sandbox.
==============================================================================
-12. Textlock *textlock*
+13. Textlock *textlock*
In a few situations it is not allowed to change the text in the buffer, jump
to another window and some other things that might confuse or break what Vim
- etc.
==============================================================================
-13. Testing *testing*
+14. Testing *testing*
Vim can be tested after building it, usually with "make test".
The tests are located in the directory "src/testdir".
-*repeat.txt* For Vim version 8.1. Last change: 2018 Dec 18
+*repeat.txt* For Vim version 8.1. Last change: 2019 Apr 04
VIM REFERENCE MANUAL by Bram Moolenaar
<
{not in Vi}
+:scriptv[ersion] {version} *:scriptv* *:scriptversion*
+ *E999* *E984*
+ Specify the version of Vim for the lines that follow.
+ Does not apply to sourced scripts.
+
+ If {version} is higher than what the current Vim
+ version supports E999 will be given. You either need
+ to rewrite the script to make it work with an older
+ Vim version, or update Vim to a newer version. See
+ |vimscript-version| for what changed between versions.
+
*:scr* *:scriptnames*
:scr[iptnames] List all sourced script names, in the order they were
first sourced. The number is used for the script ID
current_sctx.sc_sid = SID_MODELINE;
current_sctx.sc_seq = 0;
current_sctx.sc_lnum = 0;
+ current_sctx.sc_version = 1;
#endif
// Make sure no risky things are executed as a side effect.
secure = 1;
char_u op[2];
char_u *argend;
int first = TRUE;
+ int concat;
argend = skip_var_list(arg, &var_count, &semicolon);
if (argend == NULL)
if (argend > arg && argend[-1] == '.') // for var.='str'
--argend;
expr = skipwhite(argend);
- if (*expr != '=' && !((vim_strchr((char_u *)"+-*/%.", *expr) != NULL
- && expr[1] == '=') || STRNCMP(expr, "..=", 3) == 0))
+ concat = expr[0] == '.'
+ && ((expr[1] == '=' && current_sctx.sc_version < 2)
+ || (expr[1] == '.' && expr[2] == '='));
+ if (*expr != '=' && !((vim_strchr((char_u *)"+-*/%", *expr) != NULL
+ && expr[1] == '=') || concat))
{
/*
* ":let" without "=": list variables
*/
if (*arg == '[')
emsg(_(e_invarg));
+ else if (expr[0] == '.')
+ emsg(_("E985: .= is not supported with script version 2"));
else if (!ends_excmd(*arg))
/* ":let var1 var2" */
arg = list_arg_vars(eap, arg, &first);
* Handle fourth level expression:
* + number addition
* - number subtraction
- * . string concatenation
+ * . string concatenation (if script version is 1)
* .. string concatenation
*
* "arg" must point to the first non-white of the expression.
char_u *s1, *s2;
char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN];
char_u *p;
+ int concat;
/*
* Get the first variable.
*/
for (;;)
{
+ // "." is only string concatenation when scriptversion is 1
op = **arg;
- if (op != '+' && op != '-' && op != '.')
+ concat = op == '.'
+ && (*(*arg + 1) == '.' || current_sctx.sc_version < 2);
+ if (op != '+' && op != '-' && !concat)
break;
if ((op != '+' || (rettv->v_type != VAR_LIST
*arg = skipwhite(*arg + 1);
end_leader = *arg;
+ if (**arg == '.' && (!isdigit(*(*arg + 1))
+#ifdef FEAT_FLOAT
+ || current_sctx.sc_version < 2
+#endif
+ ))
+ {
+ semsg(_(e_invexpr2), *arg);
+ ++*arg;
+ return FAIL;
+ }
+
switch (**arg)
{
/*
case '7':
case '8':
case '9':
+ case '.':
{
#ifdef FEAT_FLOAT
- char_u *p = skipdigits(*arg + 1);
+ char_u *p;
int get_float = FALSE;
/* We accept a float when the format matches
* "[0-9]\+\.[0-9]\+\([eE][+-]\?[0-9]\+\)\?". This is very
* strict to avoid backwards compatibility problems.
+ * With script version 2 and later the leading digit can be
+ * omitted.
* Don't look for a float after the "." operator, so that
* ":let vers = 1.2.3" doesn't fail. */
+ if (**arg == '.')
+ p = *arg;
+ else
+ p = skipdigits(*arg + 1);
if (!want_string && p[0] == '.' && vim_isdigit(p[1]))
{
get_float = TRUE;
#ifdef FEAT_VARTABS
"vartabs",
#endif
+ "vertsplit",
#ifdef FEAT_VIMINFO
"viminfo",
#endif
- "vertsplit",
+ "vimscript-1",
+ "vimscript-2",
"virtualedit",
"visual",
"visualextra",
/* q */ 348,
/* r */ 351,
/* s */ 371,
- /* t */ 438,
- /* u */ 481,
- /* v */ 492,
- /* w */ 510,
- /* x */ 524,
- /* y */ 533,
- /* z */ 534
+ /* t */ 439,
+ /* u */ 482,
+ /* v */ 493,
+ /* w */ 511,
+ /* x */ 525,
+ /* y */ 534,
+ /* z */ 535
};
/*
/* p */ { 1, 0, 3, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 0, 0, 16, 17, 26, 0, 27, 0, 28, 0 },
/* q */ { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
/* r */ { 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 19, 0, 0, 0, 0 },
- /* s */ { 2, 6, 15, 0, 18, 22, 0, 24, 25, 0, 0, 28, 30, 34, 38, 40, 0, 48, 0, 49, 0, 61, 62, 0, 63, 0 },
+ /* s */ { 2, 6, 15, 0, 19, 23, 0, 25, 26, 0, 0, 29, 31, 35, 39, 41, 0, 49, 0, 50, 0, 62, 63, 0, 64, 0 },
/* t */ { 2, 0, 19, 0, 22, 24, 0, 25, 0, 26, 0, 27, 31, 34, 36, 37, 0, 38, 40, 0, 41, 0, 0, 0, 0, 0 },
/* u */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
/* v */ { 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 9, 12, 0, 0, 0, 0, 15, 0, 16, 0, 0, 0, 0, 0 },
/* z */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
-static const int command_count = 547;
+static const int command_count = 548;
EX(CMD_scriptencoding, "scriptencoding", ex_scriptencoding,
WORD1|TRLBAR|CMDWIN,
ADDR_LINES),
+EX(CMD_scriptversion, "scriptversion", ex_scriptversion,
+ WORD1|TRLBAR|CMDWIN,
+ ADDR_LINES),
EX(CMD_scscope, "scscope", ex_scscope,
EXTRA|NOTRLCOM,
ADDR_LINES),
else
#endif
/* Try auto-writing the buffer. If this fails but the buffer no
- * longer exists it's not changed, that's OK. */
+ * longer exists it's not changed, that's OK. */
if (check_changed(buf, (p_awa ? CCGD_AW : 0)
| CCGD_MULTWIN
| CCGD_ALLBUF) && bufref_valid(&bufref))
* Also starts profiling timer for nested script. */
save_funccal(&funccalp_entry);
+ save_current_sctx = current_sctx;
+ current_sctx.sc_lnum = 0;
+ current_sctx.sc_version = 1;
+
// Check if this script was sourced before to finds its SID.
// If it's new, generate a new SID.
// Always use a new sequence number.
- save_current_sctx = current_sctx;
current_sctx.sc_seq = ++last_current_SID_seq;
- current_sctx.sc_lnum = 0;
# ifdef UNIX
stat_ok = (mch_stat((char *)fname_exp, &st) >= 0);
# endif
/*
* ":scriptencoding": Set encoding conversion for a sourced script.
- * Without the multi-byte feature it's simply ignored.
*/
void
-ex_scriptencoding(exarg_T *eap UNUSED)
+ex_scriptencoding(exarg_T *eap)
{
struct source_cookie *sp;
char_u *name;
vim_free(name);
}
+/*
+ * ":scriptversion": Set Vim script version for a sourced script.
+ */
+ void
+ex_scriptversion(exarg_T *eap UNUSED)
+{
+ int nr;
+
+ if (!getline_equal(eap->getline, eap->cookie, getsourceline))
+ {
+ emsg(_("E984: :scriptversion used outside of a sourced file"));
+ return;
+ }
+
+ nr = getdigits(&eap->arg);
+ if (nr == 0 || *eap->arg != NUL)
+ emsg(_(e_invarg));
+ else if (nr > 2)
+ semsg(_("E999: scriptversion not supported: %d"), nr);
+ else
+ current_sctx.sc_version = nr;
+}
+
#if defined(FEAT_EVAL) || defined(PROTO)
/*
* ":finish": Mark a sourced file as finished.
current_sctx.sc_sid = SID_ENV;
current_sctx.sc_seq = 0;
current_sctx.sc_lnum = 0;
+ current_sctx.sc_version = 1;
#endif
do_cmdline_cmd(initstr);
sourcing_name = save_sourcing_name;
char_u *def_val[2]; // default values for variable (vi and vim)
#ifdef FEAT_EVAL
sctx_T script_ctx; // script context where the option was last set
-# define SCTX_INIT , {0, 0, 0}
+# define SCTX_INIT , {0, 0, 0, 1}
#else
# define SCTX_INIT
#endif
script_ctx.sc_sid = set_sid;
script_ctx.sc_seq = 0;
script_ctx.sc_lnum = 0;
+ script_ctx.sc_version = 1;
}
set_option_sctx_idx(idx, opt_flags, script_ctx);
}
void script_line_exec(void);
void script_line_end(void);
void ex_scriptencoding(exarg_T *eap);
+void ex_scriptversion(exarg_T *eap);
void ex_finish(exarg_T *eap);
void do_finish(exarg_T *eap, int reanimate);
int source_finished(char_u *(*fgetline)(int, void *, int), void *cookie);
scid_T sc_sid; // script ID
int sc_seq; // sourcing sequence number
linenr_T sc_lnum; // line number
+ int sc_version; // :scriptversion
} sctx_T;
/*
let a..=b
call assert_equal('ab', a)
endfunc
+
+scriptversion 2
+func Test_string_concat_scriptversion2()
+ let a = 'a'
+ let b = 'b'
+
+ call assert_fails('echo a . b', 'E15:')
+ call assert_fails('let a .= b', 'E985:')
+ call assert_fails('let vers = 1.2.3', 'E15:')
+
+ if has('float')
+ let f = .5
+ call assert_equal(0.5, f)
+ endif
+endfunc
+
+scriptversion 1
+func Test_string_concat_scriptversion1()
+ let a = 'a'
+ let b = 'b'
+
+ echo a . b
+ let a .= b
+ let vers = 1.2.3
+ call assert_equal('123', vers)
+
+ if has('float')
+ call assert_fails('let f = .5', 'E15:')
+ endif
+endfunc
+
+func Test_scriptversion()
+ call writefile(['scriptversion 9'], 'Xversionscript')
+ call assert_fails('source Xversionscript', 'E999:')
+ call delete('Xversionscript')
+endfunc
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 1116,
/**/
1115,
/**/