diff options
-rw-r--r-- | Makefile | 9 | ||||
-rw-r--r-- | cmake/RunTests.cmake | 2 | ||||
-rw-r--r-- | runtime/autoload/health/nvim.vim | 8 | ||||
-rw-r--r-- | runtime/doc/eval.txt | 50 | ||||
-rw-r--r-- | runtime/doc/options.txt | 75 | ||||
-rw-r--r-- | src/nvim/eval.c | 317 | ||||
-rw-r--r-- | src/nvim/eval.lua | 1 | ||||
-rw-r--r-- | src/nvim/ex_cmds.lua | 6 | ||||
-rw-r--r-- | src/nvim/generators/gen_options.lua | 1 | ||||
-rw-r--r-- | src/nvim/getchar.c | 12 | ||||
-rw-r--r-- | src/nvim/option.c | 6 | ||||
-rw-r--r-- | src/nvim/option_defs.h | 1 | ||||
-rw-r--r-- | src/nvim/options.lua | 21 | ||||
-rw-r--r-- | src/nvim/os/dl.c | 1 | ||||
-rw-r--r-- | src/nvim/testdir/shared.vim | 14 | ||||
-rw-r--r-- | src/nvim/testdir/test49.vim | 3 | ||||
-rw-r--r-- | src/nvim/testdir/test_bufline.vim | 67 | ||||
-rw-r--r-- | src/nvim/testdir/test_const.vim | 237 | ||||
-rw-r--r-- | src/nvim/testdir/test_functions.vim | 48 | ||||
-rw-r--r-- | src/nvim/testdir/test_modeline.vim | 95 | ||||
-rw-r--r-- | src/nvim/testdir/test_source.vim | 10 | ||||
-rw-r--r-- | src/nvim/testdir/test_vimscript.vim | 1 | ||||
-rw-r--r-- | src/nvim/testdir/test_writefile.vim | 2 | ||||
-rw-r--r-- | third-party/CMakeLists.txt | 6 | ||||
-rw-r--r-- | third-party/cmake/BuildLuajit.cmake | 2 | ||||
-rw-r--r-- | third-party/cmake/BuildLuarocks.cmake | 18 |
26 files changed, 866 insertions, 147 deletions
@@ -88,15 +88,18 @@ ifeq ($(call filter-true,$(USE_BUNDLED)),) +$(BUILD_CMD) -C $(DEPS_BUILD_DIR) endif -build/.ran-third-party-cmake: +build/.ran-third-party-cmake:: + mkdir -p build + touch $@ + ifeq ($(call filter-true,$(USE_BUNDLED)),) +build/.ran-third-party-cmake:: $(DEPS_BUILD_DIR) +$(DEPS_BUILD_DIR): mkdir -p $(DEPS_BUILD_DIR) cd $(DEPS_BUILD_DIR) && \ $(CMAKE_PRG) -G '$(BUILD_TYPE)' $(BUNDLED_CMAKE_FLAG) $(BUNDLED_LUA_CMAKE_FLAG) \ $(DEPS_CMAKE_FLAGS) $(THIS_DIR)/third-party endif - mkdir -p build - touch $@ # TODO: cmake 3.2+ add_custom_target() has a USES_TERMINAL flag. oldtest: | nvim helptags diff --git a/cmake/RunTests.cmake b/cmake/RunTests.cmake index 0282d1c738..db67b14917 100644 --- a/cmake/RunTests.cmake +++ b/cmake/RunTests.cmake @@ -44,7 +44,7 @@ execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory $ENV{TMPDIR}) set(ENV{SYSTEM_NAME} ${SYSTEM_NAME}) execute_process( COMMAND ${BUSTED_PRG} -v -o ${BUSTED_OUTPUT_TYPE} - --lua=${LUA_PRG} --lazy --helper=${TEST_DIR}/${TEST_TYPE}/preload.lua + --lazy --helper=${TEST_DIR}/${TEST_TYPE}/preload.lua --lpath=${BUILD_DIR}/?.lua --lpath=${WORKING_DIR}/runtime/lua/?.lua --lpath=?.lua diff --git a/runtime/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim index 8fcea2e941..a2227e3b29 100644 --- a/runtime/autoload/health/nvim.vim +++ b/runtime/autoload/health/nvim.vim @@ -8,7 +8,7 @@ function! s:check_config() abort if !filereadable(vimrc) let ok = v:false let has_vim = filereadable(expand('~/.vimrc')) - call health#report_warn('Missing user config file: '.vimrc, + call health#report_warn((-1 == getfsize(vimrc) ? 'Missing' : 'Unreadable').' user config file: '.vimrc, \[ has_vim ? ':help nvim-from-vim' : ':help init.vim' ]) endif @@ -41,6 +41,12 @@ function! s:check_config() abort \ 'Check `:verbose set paste?` to see if a plugin or script set the option.', ]) endif + let shadafile = (empty(&shadafile) || &shadafile ==# 'NONE') ? stdpath('data').'/shada/main.shada' : &shadafile + if !empty(shadafile) && (!filereadable(shadafile) || !filewritable(shadafile)) + let ok = v:false + call health#report_error('shada file is not '.(filereadable(shadafile) ? 'writeable' : 'readable').":\n".shadafile) + endif + if ok call health#report_ok('no issues found') endif diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 4eaa72ee68..afbe4f2661 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2286,6 +2286,9 @@ searchpos({pattern} [, {flags} [, {stopline} [, {timeout}]]]) server2client({clientid}, {string}) Number send reply string serverlist() String get a list of available servers +setbufline( {expr}, {lnum}, {line}) + Number set line {lnum} to {line} in buffer + {expr} setbufvar({expr}, {varname}, {val}) set {varname} in buffer {expr} to {val} setcharsearch({dict}) Dict set character search from {dict} setcmdpos({pos}) Number set cursor position in command-line @@ -6997,6 +7000,19 @@ serverstop({address}) *serverstop()* If |v:servername| is stopped it is set to the next available address returned by |serverlist()|. +setbufline({expr}, {lnum}, {text}) *setbufline()* + Set line {lnum} to {text} in buffer {expr}. To insert + lines use |append()|. + + For the use of {expr}, see |bufname()| above. + + {lnum} is used like with |setline()|. + This works like |setline()| for the specified buffer. + On success 0 is returned, on failure 1 is returned. + + If {expr} is not a valid buffer or {lnum} is not valid, an + error message is given. + setbufvar({expr}, {varname}, {val}) *setbufvar()* Set option or local variable {varname} in buffer {expr} to {val}. @@ -7064,13 +7080,19 @@ setfperm({fname}, {mode}) *setfperm()* *chmod* setline({lnum}, {text}) *setline()* Set line {lnum} of the current buffer to {text}. To insert - lines use |append()|. + lines use |append()|. To set lines in another buffer use + |setbufline()|. + {lnum} is used like with |getline()|. When {lnum} is just below the last line the {text} will be added as a new line. + If this succeeds, 0 is returned. If this fails (most likely - because {lnum} is invalid) 1 is returned. Example: > + because {lnum} is invalid) 1 is returned. + + Example: > :call setline(5, strftime("%c")) + < When {text} is a |List| then line {lnum} and following lines will be set to the items in the list. Example: > :call setline(5, ['aaa', 'bbb', 'ccc']) @@ -9629,7 +9651,29 @@ This does NOT work: > No error message is given for a non-existing variable, also without !. If the system does not support deleting an environment - variable, it is made emtpy. + variable, it is made empty. + + *:cons* *:const* +:cons[t] {var-name} = {expr1} +:cons[t] [{name1}, {name2}, ...] = {expr1} +:cons[t] [{name}, ..., ; {lastname}] = {expr1} + Similar to |:let|, but additionally lock the variable + after setting the value. This is the same as locking + the variable with |:lockvar| just after |:let|, thus: > + :const x = 1 +< is equivalent to: > + :let x = 1 + :lockvar 1 x +< This is useful if you want to make sure the variable + is not modified. + *E995* + |:const| does not allow to for changing a variable. > + :let x = 1 + :const x = 2 " Error! +< *E996* + Note that environment variables, option values and + register values cannot be used here, since they cannot + be locked. :lockv[ar][!] [depth] {name} ... *:lockvar* *:lockv* Lock the internal variable {name}. Locking means that diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index b2efd9287d..212e686f96 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -478,14 +478,17 @@ backslash in front of the ':' will be removed. Example: /* vi:set dir=c\:\tmp: */ ~ This sets the 'dir' option to "c:\tmp". Only a single backslash before the ':' is removed. Thus to include "\:" you have to specify "\\:". - + *E992* No other commands than "set" are supported, for security reasons (somebody might create a Trojan horse text file with modelines). And not all options -can be set. For some options a flag is set, so that when it's used the -|sandbox| is effective. Still, there is always a small risk that a modeline -causes trouble. E.g., when some joker sets 'textwidth' to 5 all your lines -are wrapped unexpectedly. So disable modelines before editing untrusted text. -The mail ftplugin does this, for example. +can be set. For some options a flag is set, so that when the value is used +the |sandbox| is effective. Some options can only be set from the modeline +when 'modelineexpr' is set (the default is off). + +Still, there is always a small risk that a modeline causes trouble. E.g., +when some joker sets 'textwidth' to 5 all your lines are wrapped unexpectedly. +So disable modelines before editing untrusted text. The mail ftplugin does +this, for example. Hint: If you would like to do something else than setting an option, you could define an autocommand that checks the file for a specific string. For @@ -2427,7 +2430,7 @@ A jump table for the options with a short description can be found at |Q_op|. The expression will be evaluated in the |sandbox| if set from a modeline, see |sandbox-option|. This option can't be set from a |modeline| when the 'diff' option is - on. + on or the 'modelineexpr' option is off. It is not allowed to change text or jump to another window while evaluating 'foldexpr' |textlock|. @@ -2542,6 +2545,7 @@ A jump table for the options with a short description can be found at |Q_op|. The expression will be evaluated in the |sandbox| if set from a modeline, see |sandbox-option|. + This option cannot be set in a modeline when 'modelineexpr' is off. It is not allowed to change text or jump to another window while evaluating 'foldtext' |textlock|. @@ -2577,16 +2581,8 @@ A jump table for the options with a short description can be found at |Q_op|. The expression will be evaluated in the |sandbox| when set from a modeline, see |sandbox-option|. That stops the option from working, since changing the buffer text is not allowed. - - *'formatoptions'* *'fo'* -'formatoptions' 'fo' string (default: "tcqj", Vi default: "vt") - local to buffer - This is a sequence of letters which describes how automatic - formatting is to be done. See |fo-table|. When the 'paste' option is - on, no formatting is done (like 'formatoptions' is empty). Commas can - be inserted for readability. - To avoid problems with flags that are added in the future, use the - "+=" and "-=" feature of ":set" |add-option-flags|. + This option cannot be set in a modeline when 'modelineexpr' is off. + NOTE: This option is set to "" when 'compatible' is set. *'formatlistpat'* *'flp'* 'formatlistpat' 'flp' string (default: "^\s*\d\+[\]:.)}\t ]\s*") @@ -2601,6 +2597,16 @@ A jump table for the options with a short description can be found at |Q_op|. The default recognizes a number, followed by an optional punctuation character and white space. + *'formatoptions'* *'fo'* +'formatoptions' 'fo' string (default: "tcqj", Vi default: "vt") + local to buffer + This is a sequence of letters which describes how automatic + formatting is to be done. See |fo-table|. When the 'paste' option is + on, no formatting is done (like 'formatoptions' is empty). Commas can + be inserted for readability. + To avoid problems with flags that are added in the future, use the + "+=" and "-=" feature of ":set" |add-option-flags|. + *'formatprg'* *'fp'* 'formatprg' 'fp' string (default "") global or local to buffer |global-local| @@ -2631,6 +2637,9 @@ A jump table for the options with a short description can be found at |Q_op|. - system signals low battery life - Nvim exits abnormally + This option cannot be set from a |modeline| or in the |sandbox|, for + security reasons. + *'gdefault'* *'gd'* *'nogdefault'* *'nogd'* 'gdefault' 'gd' boolean (default off) global @@ -2972,6 +2981,7 @@ A jump table for the options with a short description can be found at |Q_op|. 'guitabtooltip' is used for the tooltip, see below. The expression will be evaluated in the |sandbox| when set from a modeline, see |sandbox-option|. + This option cannot be set in a modeline when 'modelineexpr' is off. Only used when the GUI tab pages line is displayed. 'e' must be present in 'guioptions'. For the non-GUI tab pages line 'tabline' is @@ -3100,6 +3110,7 @@ A jump table for the options with a short description can be found at |Q_op|. When this option contains printf-style '%' items, they will be expanded according to the rules used for 'statusline'. See 'titlestring' for example settings. + This option cannot be set in a modeline when 'modelineexpr' is off. *'ignorecase'* *'ic'* *'noignorecase'* *'noic'* 'ignorecase' 'ic' boolean (default off) @@ -3203,6 +3214,7 @@ A jump table for the options with a short description can be found at |Q_op|. The expression will be evaluated in the |sandbox| when set from a modeline, see |sandbox-option|. + This option cannot be set in a modeline when 'modelineexpr' is off. It is not allowed to change text or jump to another window while evaluating 'includeexpr' |textlock|. @@ -3271,6 +3283,7 @@ A jump table for the options with a short description can be found at |Q_op|. The expression will be evaluated in the |sandbox| when set from a modeline, see |sandbox-option|. + This option cannot be set in a modeline when 'modelineexpr' is off. It is not allowed to change text or jump to another window while evaluating 'indentexpr' |textlock|. @@ -3888,10 +3901,23 @@ A jump table for the options with a short description can be found at |Q_op|. < If you have less than 512 Mbyte |:mkspell| may fail for some languages, no matter what you set 'mkspellmem' to. + This option cannot be set from a |modeline| or in the |sandbox|. + *'modeline'* *'ml'* *'nomodeline'* *'noml'* 'modeline' 'ml' boolean (Vim default: on (off for root), Vi default: off) local to buffer + If 'modeline' is on 'modelines' gives the number of lines that is + checked for set commands. If 'modeline' is off or 'modelines' is zero + no lines are checked. See |modeline|. + + *'modelineexpr'* *'mle'* *'nomodelineexpr'* *'nomle'* +'modelineexpr' 'mle' boolean (default: off) + global + When on allow some options that are an expression to be set in the + modeline. Check the option for whether it is affected by + 'modelineexpr'. Also see |modeline|. + *'modelines'* *'mls'* 'modelines' 'mls' number (default 5) global @@ -4668,6 +4694,8 @@ A jump table for the options with a short description can be found at |Q_op|. When this option is not empty, it determines the content of the ruler string, as displayed for the 'ruler' option. The format of this option is like that of 'statusline'. + This option cannot be set in a modeline when 'modelineexpr' is off. + The default ruler width is 17 characters. To make the ruler 15 characters wide, put "%15(" at the start and "%)" at the end. Example: > @@ -5065,6 +5093,8 @@ A jump table for the options with a short description can be found at |Q_op|. When equal to "NONE" no shada file will be read or written. This option can be set with the |-i| command line flag. The |--clean| command line flag sets it to "NONE". + This option cannot be set from a |modeline| or in the |sandbox|, for + security reasons. *'shell'* *'sh'* *E91* 'shell' 'sh' string (default $SHELL or "sh", @@ -5304,7 +5334,8 @@ A jump table for the options with a short description can be found at |Q_op|. "Pattern not found", "Back at original", etc. q use "recording" instead of "recording @a" F don't give the file info when editing a file, like `:silent` - was used for the command + was used for the command; note that this also affects messages + from autocommands S do not show search count message when searching, e.g. "[1/5]" @@ -5831,6 +5862,7 @@ A jump table for the options with a short description can be found at |Q_op|. The 'statusline' option will be evaluated in the |sandbox| if set from a modeline, see |sandbox-option|. + This option cannot be set in a modeline when 'modelineexpr' is off. It is not allowed to change text or jump to another window while evaluating 'statusline' |textlock|. @@ -5985,6 +6017,8 @@ A jump table for the options with a short description can be found at |Q_op|. the text to be displayed. Use "%1T" for the first label, "%2T" for the second one, etc. Use "%X" items for closing labels. + This option cannot be set in a modeline when 'modelineexpr' is off. + Keep in mind that only one of the tab pages is the current one, others are invisible and you can't jump to their windows. @@ -6264,8 +6298,11 @@ A jump table for the options with a short description can be found at |Q_op|. global When this option is not empty, it will be used for the title of the window. This happens only when the 'title' option is on. + When this option contains printf-style '%' items, they will be expanded according to the rules used for 'statusline'. + This option cannot be set in a modeline when 'modelineexpr' is off. + Example: > :auto BufEnter * let &titlestring = hostname() . "/" . expand("%:p") :set title titlestring=%<%F%=%l/%L-%P titlelen=70 @@ -6299,6 +6336,8 @@ A jump table for the options with a short description can be found at |Q_op|. undo file that exists is used. When it cannot be read an error is given, no further entry is used. See |undo-persistence|. + This option cannot be set from a |modeline| or in the |sandbox|, for + security reasons. *'undofile'* *'noundofile'* *'udf'* *'noudf'* 'undofile' 'udf' boolean (default off) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index f19fbe112f..cd2888883b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -176,6 +176,7 @@ static char *e_funcref = N_("E718: Funcref required"); static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary"); static char *e_nofunc = N_("E130: Unknown function: %s"); static char *e_illvar = N_("E461: Illegal variable name: %s"); +static char *e_cannot_mod = N_("E995: Cannot modify existing variable"); static const char *e_readonlyvar = N_( "E46: Cannot change read-only variable \"%.*s\""); @@ -776,10 +777,11 @@ var_redir_start( did_emsg = FALSE; tv.v_type = VAR_STRING; tv.vval.v_string = (char_u *)""; - if (append) - set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"."); - else - set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"="); + if (append) { + set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"."); + } else { + set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"="); + } clear_lval(redir_lval); err = did_emsg; did_emsg |= save_emsg; @@ -837,7 +839,7 @@ void var_redir_stop(void) redir_endp = (char_u *)get_lval(redir_varname, NULL, redir_lval, false, false, 0, FNE_CHECK_START); if (redir_endp != NULL && redir_lval->ll_name != NULL) { - set_var_lval(redir_lval, redir_endp, &tv, false, (char_u *)"."); + set_var_lval(redir_lval, redir_endp, &tv, false, false, (char_u *)"."); } clear_lval(redir_lval); } @@ -1518,21 +1520,33 @@ int eval_foldexpr(char_u *arg, int *cp) return (int)retval; } -/* - * ":let" list all variable values - * ":let var1 var2" list variable values - * ":let var = expr" assignment command. - * ":let var += expr" assignment command. - * ":let var -= expr" assignment command. - * ":let var *= expr" assignment command. - * ":let var /= expr" assignment command. - * ":let var %= expr" assignment command. - * ":let var .= expr" assignment command. - * ":let var ..= expr" assignment command. - * ":let [var1, var2] = expr" unpack list. - */ +// ":cons[t] var = expr1" define constant +// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list +// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list +void ex_const(exarg_T *eap) +{ + ex_let_const(eap, true); +} + +// ":let" list all variable values +// ":let var1 var2" list variable values +// ":let var = expr" assignment command. +// ":let var += expr" assignment command. +// ":let var -= expr" assignment command. +// ":let var *= expr" assignment command. +// ":let var /= expr" assignment command. +// ":let var %= expr" assignment command. +// ":let var .= expr" assignment command. +// ":let var ..= expr" assignment command. +// ":let [var1, var2] = expr" unpack list. +// ":let [name, ..., ; lastname] = expr" unpack list. void ex_let(exarg_T *eap) { + ex_let_const(eap, false); +} + +static void ex_let_const(exarg_T *eap, const bool is_const) +{ char_u *arg = eap->arg; char_u *expr = NULL; typval_T rettv; @@ -1594,7 +1608,8 @@ void ex_let(exarg_T *eap) } emsg_skip--; } else if (i != FAIL) { - (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, op); + (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, + is_const, op); tv_clear(&rettv); } } @@ -1612,9 +1627,10 @@ static int ex_let_vars( char_u *arg_start, typval_T *tv, - int copy, /* copy values from "tv", don't move */ - int semicolon, /* from skip_var_list() */ - int var_count, /* from skip_var_list() */ + int copy, // copy values from "tv", don't move + int semicolon, // from skip_var_list() + int var_count, // from skip_var_list() + int is_const, // lock variables for :const char_u *nextchars ) { @@ -1625,8 +1641,9 @@ ex_let_vars( /* * ":let var = expr" or ":for var in list" */ - if (ex_let_one(arg, tv, copy, nextchars, nextchars) == NULL) + if (ex_let_one(arg, tv, copy, is_const, nextchars, nextchars) == NULL) { return FAIL; + } return OK; } @@ -1654,8 +1671,8 @@ ex_let_vars( size_t rest_len = tv_list_len(l); while (*arg != ']') { arg = skipwhite(arg + 1); - arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", - nextchars); + arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, is_const, + (const char_u *)",;]", nextchars); if (arg == NULL) { return FAIL; } @@ -1677,7 +1694,7 @@ ex_let_vars( ltv.vval.v_list = rest_list; tv_list_ref(rest_list); - arg = ex_let_one(skipwhite(arg + 1), <v, false, + arg = ex_let_one(skipwhite(arg + 1), <v, false, is_const, (char_u *)"]", nextchars); tv_clear(<v); if (arg == NULL) { @@ -1933,8 +1950,8 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) /// @return a pointer to the char just after the var name or NULL in case of /// error. static char_u *ex_let_one(char_u *arg, typval_T *const tv, - const bool copy, const char_u *const endchars, - const char_u *const op) + const bool copy, const bool is_const, + const char_u *const endchars, const char_u *const op) FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT { char_u *arg_end = NULL; @@ -1946,6 +1963,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, * ":let $VAR = expr": Set environment variable. */ if (*arg == '$') { + if (is_const) { + EMSG(_("E996: Cannot lock an environment variable")); + return NULL; + } // Find the end of the name. arg++; char *name = (char *)arg; @@ -1991,6 +2012,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, // ":let &l:option = expr": Set local option value. // ":let &g:option = expr": Set global option value. } else if (*arg == '&') { + if (is_const) { + EMSG(_("E996: Cannot lock an option")); + return NULL; + } // Find the end of the name. char *const p = (char *)find_option_end((const char **)&arg, &opt_flags); if (p == NULL @@ -2041,6 +2066,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, } // ":let @r = expr": Set register contents. } else if (*arg == '@') { + if (is_const) { + EMSG(_("E996: Cannot lock a register")); + return NULL; + } arg++; if (op != NULL && vim_strchr((char_u *)"+-*/%", *op) != NULL) { emsgf(_(e_letwrong), op); @@ -2080,7 +2109,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) { EMSG(_(e_letunexp)); } else { - set_var_lval(&lv, p, tv, copy, op); + set_var_lval(&lv, p, tv, copy, is_const, op); arg_end = p; } } @@ -2445,7 +2474,7 @@ static void clear_lval(lval_T *lp) * "%" for "%=", "." for ".=" or "=" for "=". */ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, - int copy, const char_u *op) + int copy, const bool is_const, const char_u *op) { int cc; listitem_T *ri; @@ -2457,6 +2486,12 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, if (op != NULL && *op != '=') { typval_T tv; + if (is_const) { + EMSG(_(e_cannot_mod)); + *endp = cc; + return; + } + // handle +=, -=, *=, /=, %= and .= di = NULL; if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name), @@ -2472,7 +2507,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, tv_clear(&tv); } } else { - set_var(lp->ll_name, lp->ll_name_len, rettv, copy); + set_var_const(lp->ll_name, lp->ll_name_len, rettv, copy, is_const); } *endp = cc; } else if (tv_check_lock(lp->ll_newkey == NULL @@ -2483,6 +2518,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, listitem_T *ll_li = lp->ll_li; int ll_n1 = lp->ll_n1; + if (is_const) { + EMSG(_("E996: Cannot lock a range")); + return; + } + // Check whether any of the list items is locked for (ri = tv_list_first(rettv->vval.v_list); ri != NULL && ll_li != NULL; ) { @@ -2538,6 +2578,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, dict_T *dict = lp->ll_dict; bool watched = tv_dict_is_watched(dict); + if (is_const) { + EMSG(_("E996: Cannot lock a list or dict")); + return; + } + // Assign to a List or Dictionary item. if (lp->ll_newkey != NULL) { if (op != NULL && *op != '=') { @@ -2661,7 +2706,7 @@ bool next_for_item(void *fi_void, char_u *arg) } else { fi->fi_lw.lw_item = TV_LIST_ITEM_NEXT(fi->fi_list, item); return (ex_let_vars(arg, TV_LIST_ITEM_TV(item), true, - fi->fi_semicolon, fi->fi_varcount, NULL) == OK); + fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK); } } @@ -14912,6 +14957,123 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +/// Set line or list of lines in buffer "buf". +static void set_buffer_lines(buf_T *buf, linenr_T lnum, typval_T *lines, + typval_T *rettv) +{ + list_T *l = NULL; + listitem_T *li = NULL; + long added = 0; + linenr_T lcount; + buf_T *curbuf_save = NULL; + win_T *curwin_save = NULL; + int is_curbuf = buf == curbuf; + + // When using the current buffer ml_mfp will be set if needed. Useful when + // setline() is used on startup. For other buffers the buffer must be + // loaded. + if (buf == NULL || (!is_curbuf && buf->b_ml.ml_mfp == NULL) || lnum < 1) { + rettv->vval.v_number = 1; // FAIL + return; + } + + if (!is_curbuf) { + wininfo_T *wip; + + curbuf_save = curbuf; + curwin_save = curwin; + curbuf = buf; + for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { + if (wip->wi_win != NULL) { + curwin = wip->wi_win; + break; + } + } + } + + lcount = curbuf->b_ml.ml_line_count; + + const char *line = NULL; + + if (lines->v_type == VAR_LIST) { + l = lines->vval.v_list; + li = tv_list_first(l); + } else { + line = tv_get_string_chk(lines); + } + + // Default result is zero == OK. + for (;; ) { + if (lines->v_type == VAR_LIST) { + // List argument, get next string. + if (li == NULL) { + break; + } + line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); + li = TV_LIST_ITEM_NEXT(l, li); + } + + rettv->vval.v_number = 1; // FAIL + if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { + break; + } + + /* When coming here from Insert mode, sync undo, so that this can be + * undone separately from what was previously inserted. */ + if (u_sync_once == 2) { + u_sync_once = 1; /* notify that u_sync() was called */ + u_sync(TRUE); + } + + if (lnum <= curbuf->b_ml.ml_line_count) { + // Existing line, replace it. + if (u_savesub(lnum) == OK + && ml_replace(lnum, (char_u *)line, true) == OK) { + changed_bytes(lnum, 0); + if (is_curbuf && lnum == curwin->w_cursor.lnum) { + check_cursor_col(); + } + rettv->vval.v_number = 0; // OK + } + } else if (added > 0 || u_save(lnum - 1, lnum) == OK) { + // lnum is one past the last line, append the line. + added++; + if (ml_append(lnum - 1, (char_u *)line, 0, false) == OK) { + rettv->vval.v_number = 0; // OK + } + } + + if (l == NULL) /* only one string argument */ + break; + ++lnum; + } + + if (added > 0) { + appended_lines_mark(lcount, added); + } + + if (!is_curbuf) { + curbuf = curbuf_save; + curwin = curwin_save; + } +} + +/// "setbufline()" function +static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + linenr_T lnum; + buf_T *buf; + + buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + rettv->vval.v_number = 1; // FAIL + } else { + lnum = tv_get_lnum_buf(&argvars[1], buf); + + set_buffer_lines(buf, lnum, &argvars[2], rettv); + } +} + /* * "setbufvar()" function */ @@ -15044,67 +15206,8 @@ static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l = NULL; - listitem_T *li = NULL; - long added = 0; - linenr_T lcount = curbuf->b_ml.ml_line_count; - linenr_T lnum = tv_get_lnum(&argvars[0]); - const char *line = NULL; - if (argvars[1].v_type == VAR_LIST) { - l = argvars[1].vval.v_list; - li = tv_list_first(l); - } else { - line = tv_get_string_chk(&argvars[1]); - } - - // Default result is zero == OK. - for (;; ) { - if (argvars[1].v_type == VAR_LIST) { - // List argument, get next string. - if (li == NULL) { - break; - } - line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); - li = TV_LIST_ITEM_NEXT(l, li); - } - - rettv->vval.v_number = 1; // FAIL - if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { - break; - } - - /* When coming here from Insert mode, sync undo, so that this can be - * undone separately from what was previously inserted. */ - if (u_sync_once == 2) { - u_sync_once = 1; /* notify that u_sync() was called */ - u_sync(TRUE); - } - - if (lnum <= curbuf->b_ml.ml_line_count) { - // Existing line, replace it. - if (u_savesub(lnum) == OK - && ml_replace(lnum, (char_u *)line, true) == OK) { - changed_bytes(lnum, 0); - if (lnum == curwin->w_cursor.lnum) - check_cursor_col(); - rettv->vval.v_number = 0; /* OK */ - } - } else if (added > 0 || u_save(lnum - 1, lnum) == OK) { - // lnum is one past the last line, append the line. - added++; - if (ml_append(lnum - 1, (char_u *)line, 0, false) == OK) { - rettv->vval.v_number = 0; // OK - } - } - - if (l == NULL) /* only one string argument */ - break; - ++lnum; - } - - if (added > 0) - appended_lines_mark(lcount, added); + set_buffer_lines(curbuf, lnum, &argvars[1], rettv); } /// Create quickfix/location list from VimL values @@ -20112,6 +20215,24 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, const bool copy) FUNC_ATTR_NONNULL_ALL { + set_var_const(name, name_len, tv, copy, false); +} + +/// Set variable to the given value +/// +/// If the variable already exists, the value is updated. Otherwise the variable +/// is created. +/// +/// @param[in] name Variable name to set. +/// @param[in] name_len Length of the variable name. +/// @param tv Variable value. +/// @param[in] copy True if value in tv is to be copied. +/// @param[in] is_const True if value in tv is to be locked. +static void set_var_const(const char *name, const size_t name_len, + typval_T *const tv, const bool copy, + const bool is_const) + FUNC_ATTR_NONNULL_ALL +{ dictitem_T *v; hashtab_T *ht; dict_T *dict; @@ -20137,6 +20258,11 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, typval_T oldtv = TV_INITIAL_VALUE; if (v != NULL) { + if (is_const) { + EMSG(_(e_cannot_mod)); + return; + } + // existing variable, need to clear the value if (var_check_ro(v->di_flags, name, name_len) || tv_check_lock(v->di_tv.v_lock, name, name_len)) { @@ -20203,6 +20329,9 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, return; } v->di_flags = DI_FLAGS_ALLOC; + if (is_const) { + v->di_flags |= DI_FLAGS_LOCK; + } } if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) { @@ -20221,6 +20350,10 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, tv_clear(&oldtv); } } + + if (is_const) { + v->di_tv.v_lock |= VAR_LOCKED; + } } /// Check whether variable is read-only (DI_FLAGS_RO, DI_FLAGS_RO_SBX) diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 6159103767..9deee69f32 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -263,6 +263,7 @@ return { serverlist={}, serverstart={args={0, 1}}, serverstop={args=1}, + setbufline={args=3}, setbufvar={args=3}, setcharsearch={args=1}, setcmdpos={args=1}, diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 0f69d476f9..58dc62e953 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -601,6 +601,12 @@ return { func='ex_wrongmodifier', }, { + command='const', + flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, CMDWIN), + addr_type=ADDR_LINES, + func='ex_const', + }, + { command='copen', flags=bit.bor(RANGE, NOTADR, COUNT, TRLBAR), addr_type=ADDR_LINES, diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index fdc00d5dc0..d9c65e17c5 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -79,6 +79,7 @@ local get_flags = function(o) {'pri_mkrc'}, {'deny_in_modelines', 'P_NO_ML'}, {'deny_duplicates', 'P_NODUP'}, + {'modelineexpr', 'P_MLE'}, }) do local key_name = flag_desc[1] local def_name = flag_desc[2] or ('P_' .. key_name:upper()) diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 27ad6bb86c..7efae1e637 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1253,9 +1253,17 @@ openscript ( EMSG(_(e_nesting)); return; } - if (ignore_script) - /* Not reading from script, also don't open one. Warning message? */ + + // Disallow sourcing a file in the sandbox, the commands would be executed + // later, possibly outside of the sandbox. + if (check_secure()) { return; + } + + if (ignore_script) { + // Not reading from script, also don't open one. Warning message? + return; + } if (scriptin[curscript] != NULL) /* already reading script */ ++curscript; diff --git a/src/nvim/option.c b/src/nvim/option.c index a39be0fe96..8dadf926b9 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -253,6 +253,7 @@ typedef struct vimoption { #define P_RWINONLY 0x10000000U ///< only redraw current window #define P_NDNAME 0x20000000U ///< only normal dir name chars allowed #define P_UI_OPTION 0x40000000U ///< send option to remote ui +#define P_MLE 0x80000000U ///< under control of 'modelineexpr' #define HIGHLIGHT_INIT \ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText," \ @@ -1327,6 +1328,11 @@ int do_set( errmsg = (char_u *)_("E520: Not allowed in a modeline"); goto skip; } + if ((flags & P_MLE) && !p_mle) { + errmsg = (char_u *)_( + "E992: Not allowed in a modeline when 'modelineexpr' is off"); + goto skip; + } // In diff mode some options are overruled. This avoids that // 'foldmethod' becomes "marker" instead of "diff" and that // "wrap" gets set. diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index f9f2a7d5dc..8df5039037 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -496,6 +496,7 @@ EXTERN long p_mmd; // 'maxmapdepth' EXTERN long p_mmp; // 'maxmempattern' EXTERN long p_mis; // 'menuitems' EXTERN char_u *p_msm; // 'mkspellmem' +EXTERN long p_mle; // 'modelineexpr' EXTERN long p_mls; // 'modelines' EXTERN char_u *p_mouse; // 'mouse' EXTERN char_u *p_mousem; // 'mousemodel' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 96e098778c..e892e59ba6 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -8,6 +8,7 @@ -- defaults={condition=nil, if_true={vi=224, vim=0}, if_false=nil}, -- secure=nil, gettext=nil, noglob=nil, normal_fname_chars=nil, -- pri_mkrc=nil, deny_in_modelines=nil, normal_dname_chars=nil, +-- modelineexpr=nil, -- expand=nil, nodefault=nil, no_mkrc=nil, vi_def=true, vim=true, -- alloced=nil, -- save_pv_indir=nil, @@ -283,6 +284,7 @@ return { deny_duplicates=true, vi_def=true, expand=true, + secure=true, varname='p_cdpath', defaults={if_true={vi=",,"}} }, @@ -847,6 +849,7 @@ return { type='string', scope={'window'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, redraw={'current_window'}, defaults={if_true={vi="0"}} @@ -922,6 +925,7 @@ return { type='string', scope={'window'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, redraw={'current_window'}, defaults={if_true={vi="foldtext()"}} @@ -931,6 +935,7 @@ return { type='string', scope={'buffer'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, varname='p_fex', defaults={if_true={vi=""}} @@ -1045,6 +1050,7 @@ return { full_name='guitablabel', abbreviation='gtl', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, redraw={'current_window'}, enable_if=false, }, @@ -1136,6 +1142,7 @@ return { full_name='iconstring', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, varname='p_iconstring', defaults={if_true={vi=""}} }, @@ -1198,6 +1205,7 @@ return { full_name='includeexpr', abbreviation='inex', type='string', scope={'buffer'}, vi_def=true, + modelineexpr=true, alloced=true, varname='p_inex', defaults={if_true={vi=""}} @@ -1214,6 +1222,7 @@ return { type='string', scope={'buffer'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, varname='p_inde', defaults={if_true={vi=""}} @@ -1528,6 +1537,14 @@ return { defaults={if_true={vi=false, vim=true}} }, { + full_name='modelineexpr', abbreviation='mle', + type='bool', scope={'global'}, + vi_def=true, + secure=true, + varname='p_mle', + defaults={if_true={vi=false}} + }, + { full_name='modelines', abbreviation='mls', type='number', scope={'global'}, vi_def=true, @@ -1903,6 +1920,7 @@ return { type='string', scope={'global'}, vi_def=true, alloced=true, + modelineexpr=true, redraw={'statuslines'}, varname='p_ruf', defaults={if_true={vi=""}} @@ -2310,6 +2328,7 @@ return { type='string', scope={'global', 'window'}, vi_def=true, alloced=true, + modelineexpr=true, redraw={'statuslines'}, varname='p_stl', defaults={if_true={vi=""}} @@ -2369,6 +2388,7 @@ return { full_name='tabline', abbreviation='tal', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, redraw={'all_windows'}, varname='p_tal', defaults={if_true={vi=""}} @@ -2528,6 +2548,7 @@ return { full_name='titlestring', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, varname='p_titlestring', defaults={if_true={vi=""}} }, diff --git a/src/nvim/os/dl.c b/src/nvim/os/dl.c index bbd0424a82..f0fadb16f2 100644 --- a/src/nvim/os/dl.c +++ b/src/nvim/os/dl.c @@ -54,6 +54,7 @@ bool os_libcall(const char *libname, // open the dynamic loadable library if (uv_dlopen(libname, &lib)) { EMSG2(_("dlerror = \"%s\""), uv_dlerror(&lib)); + uv_dlclose(&lib); return false; } diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index ac34dec569..a5cf4def4f 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -190,12 +190,18 @@ func s:feedkeys(timer) endfunc " Get the command to run Vim, with -u NONE and --headless arguments. +" If there is an argument use it instead of "NONE". " Returns an empty string on error. -func GetVimCommand() +func GetVimCommand(...) + if a:0 == 0 + let name = 'NONE' + else + let name = a:1 + endif let cmd = v:progpath - let cmd = substitute(cmd, '-u \f\+', '-u NONE', '') - if cmd !~ '-u NONE' - let cmd = cmd . ' -u NONE' + let cmd = substitute(cmd, '-u \f\+', '-u ' . name, '') + if cmd !~ '-u '. name + let cmd = cmd . ' -u ' . name endif let cmd .= ' --headless -i NONE' let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '') diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim index 467abcd9b9..837e55ebca 100644 --- a/src/nvim/testdir/test49.vim +++ b/src/nvim/testdir/test49.vim @@ -1,6 +1,6 @@ " Vim script language tests " Author: Servatius Brandt <Servatius.Brandt@fujitsu-siemens.com> -" Last Change: 2016 Feb 07 +" Last Change: 2019 May 24 "------------------------------------------------------------------------------- " Test environment {{{1 @@ -9005,5 +9005,4 @@ Xcheck 50443995 "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=4 tw=80 fdm=marker -" vim: fdt=substitute(substitute(foldtext(),\ '\\%(^+--\\)\\@<=\\(\\s*\\)\\(.\\{-}\\)\:\ \\%(\"\ \\)\\=\\(Test\ \\d*\\)\:\\s*',\ '\\3\ (\\2)\:\ \\1',\ \"\"),\ '\\(Test\\s*\\)\\(\\d\\)\\D\\@=',\ '\\1\ \\2',\ "") "------------------------------------------------------------------------------- diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim new file mode 100644 index 0000000000..b886e99506 --- /dev/null +++ b/src/nvim/testdir/test_bufline.vim @@ -0,0 +1,67 @@ +" Tests for setbufline() and getbufline() + +source shared.vim + +func Test_setbufline_getbufline() + new + let b = bufnr('%') + hide + call assert_equal(0, setbufline(b, 1, ['foo', 'bar'])) + call assert_equal(['foo'], getbufline(b, 1)) + call assert_equal(['bar'], getbufline(b, 2)) + call assert_equal(['foo', 'bar'], getbufline(b, 1, 2)) + exe "bd!" b + call assert_equal([], getbufline(b, 1, 2)) + + split Xtest + call setline(1, ['a', 'b', 'c']) + let b = bufnr('%') + wincmd w + call assert_equal(1, setbufline(b, 5, ['x'])) + call assert_equal(1, setbufline(1234, 1, ['x'])) + call assert_equal(0, setbufline(b, 4, ['d', 'e'])) + call assert_equal(['c'], getbufline(b, 3)) + call assert_equal(['d'], getbufline(b, 4)) + call assert_equal(['e'], getbufline(b, 5)) + call assert_equal([], getbufline(b, 6)) + exe "bwipe! " . b +endfunc + +func Test_setbufline_getbufline_fold() + split Xtest + setlocal foldmethod=expr foldexpr=0 + let b = bufnr('%') + new + call assert_equal(0, setbufline(b, 1, ['foo', 'bar'])) + call assert_equal(['foo'], getbufline(b, 1)) + call assert_equal(['bar'], getbufline(b, 2)) + call assert_equal(['foo', 'bar'], getbufline(b, 1, 2)) + exe "bwipe!" b + bwipe! +endfunc + +func Test_setbufline_getbufline_fold_tab() + split Xtest + setlocal foldmethod=expr foldexpr=0 + let b = bufnr('%') + tab new + call assert_equal(0, setbufline(b, 1, ['foo', 'bar'])) + call assert_equal(['foo'], getbufline(b, 1)) + call assert_equal(['bar'], getbufline(b, 2)) + call assert_equal(['foo', 'bar'], getbufline(b, 1, 2)) + exe "bwipe!" b + bwipe! +endfunc + +func Test_setline_startup() + let cmd = GetVimCommand('Xscript') + if cmd == '' + return + endif + call writefile(['call setline(1, "Hello")', 'silent w Xtest', 'q!'], 'Xscript') + call system(cmd) + call assert_equal(['Hello'], readfile('Xtest')) + + call delete('Xscript') + call delete('Xtest') +endfunc diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim new file mode 100644 index 0000000000..06062c5e58 --- /dev/null +++ b/src/nvim/testdir/test_const.vim @@ -0,0 +1,237 @@ + +" Test for :const + +func s:noop() +endfunc + +func Test_define_var_with_lock() + const i = 1 + const f = 1.1 + const s = 'vim' + const F = funcref('s:noop') + const l = [1, 2, 3] + const d = {'foo': 10} + if has('channel') + const j = test_null_job() + const c = test_null_channel() + endif + const b = v:true + const n = v:null + + call assert_true(exists('i')) + call assert_true(exists('f')) + call assert_true(exists('s')) + call assert_true(exists('F')) + call assert_true(exists('l')) + call assert_true(exists('d')) + if has('channel') + call assert_true(exists('j')) + call assert_true(exists('c')) + endif + call assert_true(exists('b')) + call assert_true(exists('n')) + + call assert_fails('let i = 1', 'E741:') + call assert_fails('let f = 1.1', 'E741:') + call assert_fails('let s = "vim"', 'E741:') + call assert_fails('let F = funcref("s:noop")', 'E741:') + call assert_fails('let l = [1, 2, 3]', 'E741:') + call assert_fails('let d = {"foo": 10}', 'E741:') + if has('channel') + call assert_fails('let j = test_null_job()', 'E741:') + call assert_fails('let c = test_null_channel()', 'E741:') + endif + call assert_fails('let b = v:true', 'E741:') + call assert_fails('let n = v:null', 'E741:') + + " Unlet + unlet i + unlet f + unlet s + unlet F + unlet l + unlet d + if has('channel') + unlet j + unlet c + endif + unlet b + unlet n +endfunc + +func Test_define_l_var_with_lock() + " With l: prefix + const l:i = 1 + const l:f = 1.1 + const l:s = 'vim' + const l:F = funcref('s:noop') + const l:l = [1, 2, 3] + const l:d = {'foo': 10} + if has('channel') + const l:j = test_null_job() + const l:c = test_null_channel() + endif + const l:b = v:true + const l:n = v:null + + call assert_fails('let l:i = 1', 'E741:') + call assert_fails('let l:f = 1.1', 'E741:') + call assert_fails('let l:s = "vim"', 'E741:') + call assert_fails('let l:F = funcref("s:noop")', 'E741:') + call assert_fails('let l:l = [1, 2, 3]', 'E741:') + call assert_fails('let l:d = {"foo": 10}', 'E741:') + if has('channel') + call assert_fails('let l:j = test_null_job()', 'E741:') + call assert_fails('let l:c = test_null_channel()', 'E741:') + endif + call assert_fails('let l:b = v:true', 'E741:') + call assert_fails('let l:n = v:null', 'E741:') + + " Unlet + unlet l:i + unlet l:f + unlet l:s + unlet l:F + unlet l:l + unlet l:d + if has('channel') + unlet l:j + unlet l:c + endif + unlet l:b + unlet l:n +endfunc + +func Test_define_script_var_with_lock() + const s:x = 0 + call assert_fails('let s:x = 1', 'E741:') + unlet s:x +endfunc + +func Test_descructuring_with_lock() + const [a, b, c] = [1, 1.1, 'vim'] + + call assert_fails('let a = 1', 'E741:') + call assert_fails('let b = 1.1', 'E741:') + call assert_fails('let c = "vim"', 'E741:') + + const [d; e] = [1, 1.1, 'vim'] + call assert_fails('let d = 1', 'E741:') + call assert_fails('let e = [2.2, "a"]', 'E741:') +endfunc + +func Test_cannot_modify_existing_variable() + let i = 1 + let f = 1.1 + let s = 'vim' + let F = funcref('s:noop') + let l = [1, 2, 3] + let d = {'foo': 10} + if has('channel') + let j = test_null_job() + let c = test_null_channel() + endif + let b = v:true + let n = v:null + + call assert_fails('const i = 1', 'E995:') + call assert_fails('const f = 1.1', 'E995:') + call assert_fails('const s = "vim"', 'E995:') + call assert_fails('const F = funcref("s:noop")', 'E995:') + call assert_fails('const l = [1, 2, 3]', 'E995:') + call assert_fails('const d = {"foo": 10}', 'E995:') + if has('channel') + call assert_fails('const j = test_null_job()', 'E995:') + call assert_fails('const c = test_null_channel()', 'E995:') + endif + call assert_fails('const b = v:true', 'E995:') + call assert_fails('const n = v:null', 'E995:') + call assert_fails('const [i, f, s] = [1, 1.1, "vim"]', 'E995:') + + const i2 = 1 + const f2 = 1.1 + const s2 = 'vim' + const F2 = funcref('s:noop') + const l2 = [1, 2, 3] + const d2 = {'foo': 10} + if has('channel') + const j2 = test_null_job() + const c2 = test_null_channel() + endif + const b2 = v:true + const n2 = v:null + + call assert_fails('const i2 = 1', 'E995:') + call assert_fails('const f2 = 1.1', 'E995:') + call assert_fails('const s2 = "vim"', 'E995:') + call assert_fails('const F2 = funcref("s:noop")', 'E995:') + call assert_fails('const l2 = [1, 2, 3]', 'E995:') + call assert_fails('const d2 = {"foo": 10}', 'E995:') + if has('channel') + call assert_fails('const j2 = test_null_job()', 'E995:') + call assert_fails('const c2 = test_null_channel()', 'E995:') + endif + call assert_fails('const b2 = v:true', 'E995:') + call assert_fails('const n2 = v:null', 'E995:') + call assert_fails('const [i2, f2, s2] = [1, 1.1, "vim"]', 'E995:') +endfunc + +func Test_const_with_index_access() + let l = [1, 2, 3] + call assert_fails('const l[0] = 4', 'E996:') + call assert_fails('const l[0:1] = [1, 2]', 'E996:') + + let d = {'aaa': 0} + call assert_fails("const d['aaa'] = 4", 'E996:') + call assert_fails("const d.aaa = 4", 'E996:') +endfunc + +func Test_const_with_compound_assign() + let i = 0 + call assert_fails('const i += 4', 'E995:') + call assert_fails('const i -= 4', 'E995:') + call assert_fails('const i *= 4', 'E995:') + call assert_fails('const i /= 4', 'E995:') + call assert_fails('const i %= 4', 'E995:') + + let s = 'a' + call assert_fails('const s .= "b"', 'E995:') + + let [a, b, c] = [1, 2, 3] + call assert_fails('const [a, b, c] += [4, 5, 6]', 'E995:') + + let [d; e] = [1, 2, 3] + call assert_fails('const [d; e] += [4, 5, 6]', 'E995:') +endfunc + +func Test_const_with_special_variables() + call assert_fails('const $FOO = "hello"', 'E996:') + call assert_fails('const @a = "hello"', 'E996:') + call assert_fails('const &filetype = "vim"', 'E996:') + call assert_fails('const &l:filetype = "vim"', 'E996:') + call assert_fails('const &g:encoding = "utf-8"', 'E996:') +endfunc + +func Test_const_with_eval_name() + let s = 'foo' + + " eval name with :const should work + const abc_{s} = 1 + const {s}{s} = 1 + + let s2 = 'abc_foo' + call assert_fails('const {s2} = "bar"', 'E995:') +endfunc + +func Test_lock_depth_is_1() + const l = [1, 2, 3] + const d = {'foo': 10} + + " Modify list + call add(l, 4) + let l[0] = 42 + + " Modify dict + let d['bar'] = 'hello' + let d.foo = 44 +endfunc diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index ed9c70403e..0c3c356622 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1,4 +1,5 @@ " Tests for various functions. +source shared.vim " Must be done first, since the alternate buffer must be unset. func Test_00_bufexists() @@ -1140,3 +1141,50 @@ func Test_reg_executing_and_recording() delfunc s:save_reg_stat unlet s:reg_stat endfunc + +func Test_libcall_libcallnr() + if !has('libcall') + return + endif + + if has('win32') + let libc = 'msvcrt.dll' + elseif has('mac') + let libc = 'libSystem.B.dylib' + elseif system('uname -s') =~ 'SunOS' + " Set the path to libc.so according to the architecture. + let test_bits = system('file ' . GetVimProg()) + let test_arch = system('uname -p') + if test_bits =~ '64-bit' && test_arch =~ 'sparc' + let libc = '/usr/lib/sparcv9/libc.so' + elseif test_bits =~ '64-bit' && test_arch =~ 'i386' + let libc = '/usr/lib/amd64/libc.so' + else + let libc = '/usr/lib/libc.so' + endif + else + " On Unix, libc.so can be in various places. + " Interestingly, using an empty string for the 1st argument of libcall + " allows to call functions from libc which is not documented. + let libc = '' + endif + + if has('win32') + call assert_equal($USERPROFILE, libcall(libc, 'getenv', 'USERPROFILE')) + else + call assert_equal($HOME, libcall(libc, 'getenv', 'HOME')) + endif + + " If function returns NULL, libcall() should return an empty string. + call assert_equal('', libcall(libc, 'getenv', 'X_ENV_DOES_NOT_EXIT')) + + " Test libcallnr() with string and integer argument. + call assert_equal(4, libcallnr(libc, 'strlen', 'abcd')) + call assert_equal(char2nr('A'), libcallnr(libc, 'toupper', char2nr('a'))) + + call assert_fails("call libcall(libc, 'Xdoesnotexist_', '')", 'E364:') + call assert_fails("call libcallnr(libc, 'Xdoesnotexist_', '')", 'E364:') + + call assert_fails("call libcall('Xdoesnotexist_', 'getenv', 'HOME')", 'E364:') + call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", 'E364:') +endfunc diff --git a/src/nvim/testdir/test_modeline.vim b/src/nvim/testdir/test_modeline.vim index 091a833774..1e196e07f0 100644 --- a/src/nvim/testdir/test_modeline.vim +++ b/src/nvim/testdir/test_modeline.vim @@ -60,14 +60,17 @@ func Test_modeline_keymap() set keymap= iminsert=0 imsearch=-1 endfunc -func s:modeline_fails(what, text) +func s:modeline_fails(what, text, error) + if !exists('+' . a:what) + return + endif let fname = "Xmodeline_fails_" . a:what call writefile(['vim: set ' . a:text . ' :', 'nothing'], fname) let modeline = &modeline set modeline filetype plugin on syntax enable - call assert_fails('split ' . fname, 'E474:') + call assert_fails('split ' . fname, a:error) call assert_equal("", &filetype) call assert_equal("", &syntax) @@ -79,16 +82,92 @@ func s:modeline_fails(what, text) endfunc func Test_modeline_filetype_fails() - call s:modeline_fails('filetype', 'ft=evil$CMD') + call s:modeline_fails('filetype', 'ft=evil$CMD', 'E474:') endfunc func Test_modeline_syntax_fails() - call s:modeline_fails('syntax', 'syn=evil$CMD') + call s:modeline_fails('syntax', 'syn=evil$CMD', 'E474:') endfunc func Test_modeline_keymap_fails() - if !has('keymap') - return - endif - call s:modeline_fails('keymap', 'keymap=evil$CMD') + call s:modeline_fails('keymap', 'keymap=evil$CMD', 'E474:') +endfunc + +func Test_modeline_fails_always() + call s:modeline_fails('backupdir', 'backupdir=Something()', 'E520:') + call s:modeline_fails('cdpath', 'cdpath=Something()', 'E520:') + call s:modeline_fails('charconvert', 'charconvert=Something()', 'E520:') + call s:modeline_fails('completefunc', 'completefunc=Something()', 'E520:') + call s:modeline_fails('cscopeprg', 'cscopeprg=Something()', 'E520:') + call s:modeline_fails('diffexpr', 'diffexpr=Something()', 'E520:') + call s:modeline_fails('directory', 'directory=Something()', 'E520:') + call s:modeline_fails('equalprg', 'equalprg=Something()', 'E520:') + call s:modeline_fails('errorfile', 'errorfile=Something()', 'E520:') + call s:modeline_fails('exrc', 'exrc=Something()', 'E520:') + call s:modeline_fails('formatprg', 'formatprg=Something()', 'E520:') + call s:modeline_fails('fsync', 'fsync=Something()', 'E520:') + call s:modeline_fails('grepprg', 'grepprg=Something()', 'E520:') + call s:modeline_fails('helpfile', 'helpfile=Something()', 'E520:') + call s:modeline_fails('imactivatefunc', 'imactivatefunc=Something()', 'E520:') + call s:modeline_fails('imstatusfunc', 'imstatusfunc=Something()', 'E520:') + call s:modeline_fails('imstyle', 'imstyle=Something()', 'E520:') + call s:modeline_fails('keywordprg', 'keywordprg=Something()', 'E520:') + call s:modeline_fails('langmap', 'langmap=Something()', 'E520:') + call s:modeline_fails('luadll', 'luadll=Something()', 'E520:') + call s:modeline_fails('makeef', 'makeef=Something()', 'E520:') + call s:modeline_fails('makeprg', 'makeprg=Something()', 'E520:') + call s:modeline_fails('mkspellmem', 'mkspellmem=Something()', 'E520:') + call s:modeline_fails('mzschemedll', 'mzschemedll=Something()', 'E520:') + call s:modeline_fails('mzschemegcdll', 'mzschemegcdll=Something()', 'E520:') + call s:modeline_fails('modelineexpr', 'modelineexpr=Something()', 'E520:') + call s:modeline_fails('omnifunc', 'omnifunc=Something()', 'E520:') + call s:modeline_fails('operatorfunc', 'operatorfunc=Something()', 'E520:') + call s:modeline_fails('perldll', 'perldll=Something()', 'E520:') + call s:modeline_fails('printdevice', 'printdevice=Something()', 'E520:') + call s:modeline_fails('patchexpr', 'patchexpr=Something()', 'E520:') + call s:modeline_fails('printexpr', 'printexpr=Something()', 'E520:') + call s:modeline_fails('pythondll', 'pythondll=Something()', 'E520:') + call s:modeline_fails('pythonhome', 'pythonhome=Something()', 'E520:') + call s:modeline_fails('pythonthreedll', 'pythonthreedll=Something()', 'E520:') + call s:modeline_fails('pythonthreehome', 'pythonthreehome=Something()', 'E520:') + call s:modeline_fails('pyxversion', 'pyxversion=Something()', 'E520:') + call s:modeline_fails('rubydll', 'rubydll=Something()', 'E520:') + call s:modeline_fails('runtimepath', 'runtimepath=Something()', 'E520:') + call s:modeline_fails('secure', 'secure=Something()', 'E520:') + call s:modeline_fails('shell', 'shell=Something()', 'E520:') + call s:modeline_fails('shellcmdflag', 'shellcmdflag=Something()', 'E520:') + call s:modeline_fails('shellpipe', 'shellpipe=Something()', 'E520:') + call s:modeline_fails('shellquote', 'shellquote=Something()', 'E520:') + call s:modeline_fails('shellredir', 'shellredir=Something()', 'E520:') + call s:modeline_fails('shellxquote', 'shellxquote=Something()', 'E520:') + call s:modeline_fails('spellfile', 'spellfile=Something()', 'E520:') + call s:modeline_fails('spellsuggest', 'spellsuggest=Something()', 'E520:') + call s:modeline_fails('tcldll', 'tcldll=Something()', 'E520:') + call s:modeline_fails('titleold', 'titleold=Something()', 'E520:') + call s:modeline_fails('viewdir', 'viewdir=Something()', 'E520:') + call s:modeline_fails('viminfo', 'viminfo=Something()', 'E520:') + call s:modeline_fails('viminfofile', 'viminfofile=Something()', 'E520:') + call s:modeline_fails('winptydll', 'winptydll=Something()', 'E520:') + call s:modeline_fails('undodir', 'undodir=Something()', 'E520:') + " only check a few terminal options + " Skip these since nvim doesn't support termcodes as options + "call s:modeline_fails('t_AB', 't_AB=Something()', 'E520:') + "call s:modeline_fails('t_ce', 't_ce=Something()', 'E520:') + "call s:modeline_fails('t_sr', 't_sr=Something()', 'E520:') + "call s:modeline_fails('t_8b', 't_8b=Something()', 'E520:') +endfunc + +func Test_modeline_fails_modelineexpr() + call s:modeline_fails('balloonexpr', 'balloonexpr=Something()', 'E992:') + call s:modeline_fails('foldexpr', 'foldexpr=Something()', 'E992:') + call s:modeline_fails('foldtext', 'foldtext=Something()', 'E992:') + call s:modeline_fails('formatexpr', 'formatexpr=Something()', 'E992:') + call s:modeline_fails('guitablabel', 'guitablabel=Something()', 'E992:') + call s:modeline_fails('iconstring', 'iconstring=Something()', 'E992:') + call s:modeline_fails('includeexpr', 'includeexpr=Something()', 'E992:') + call s:modeline_fails('indentexpr', 'indentexpr=Something()', 'E992:') + call s:modeline_fails('rulerformat', 'rulerformat=Something()', 'E992:') + call s:modeline_fails('statusline', 'statusline=Something()', 'E992:') + call s:modeline_fails('tabline', 'tabline=Something()', 'E992:') + call s:modeline_fails('titlestring', 'titlestring=Something()', 'E992:') endfunc diff --git a/src/nvim/testdir/test_source.vim b/src/nvim/testdir/test_source.vim new file mode 100644 index 0000000000..42ac0c4d0f --- /dev/null +++ b/src/nvim/testdir/test_source.vim @@ -0,0 +1,10 @@ +" Tests for the :source command. + +func Test_source_sandbox() + new + call writefile(["Ohello\<Esc>"], 'Xsourcehello') + source! Xsourcehello | echo + call assert_equal('hello', getline(1)) + call assert_fails('sandbox source! Xsourcehello', 'E48:') + bwipe! +endfunc diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index d0ecbef7e1..f3e40e1210 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1375,5 +1375,4 @@ endfunc "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=4 tw=80 fdm=marker -" vim: fdt=substitute(substitute(foldtext(),\ '\\%(^+--\\)\\@<=\\(\\s*\\)\\(.\\{-}\\)\:\ \\%(\"\ \\)\\=\\(Test\ \\d*\\)\:\\s*',\ '\\3\ (\\2)\:\ \\1',\ \"\"),\ '\\(Test\\s*\\)\\(\\d\\)\\D\\@=',\ '\\1\ \\2',\ "") "------------------------------------------------------------------------------- diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index 9da9df2150..6d88c0d8cd 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -33,7 +33,7 @@ func Test_writefile_fails_gently() endfunc func Test_writefile_fails_conversion() - if !has('multi_byte') || !has('iconv') + if !has('multi_byte') || !has('iconv') || system('uname -s') =~ 'SunOS' return endif " Without a backup file the write won't happen if there is a conversion diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index c9e8537272..90581462d7 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -135,13 +135,13 @@ endif() set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/cpp-3.0.0/msgpack-3.0.0.tar.gz) set(MSGPACK_SHA256 bfbb71b7c02f806393bc3cbc491b40523b89e64f83860c58e3e54af47de176e4) -set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/7dbf0b05f1228c1c719866db5e5f3d58f87f74c8.tar.gz) -set(LUAJIT_SHA256 cbae019b5e396164eb5f0d07777b55cc03931bb944f83c61a010c053c9f5fd5b) +# https://github.com/LuaJIT/LuaJIT/tree/v2.0 +set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/61464b0a5b685489bee7b6680c0e9663f2143a84.tar.gz) +set(LUAJIT_SHA256 033fa4ef19f559ef18a9b9fb017d0cb8be58befe05ab604e92814234910f1c68) set(LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz) set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333) -# NOTE: Version must match LUAROCKS_VERSION in third-party/cmake/BuildLuarocks.cmake set(LUAROCKS_URL https://github.com/luarocks/luarocks/archive/v2.4.4.tar.gz) set(LUAROCKS_SHA256 9eb3d0738fd02ad8bf39bcedccac4e83e9b5fff2bcca247c3584b925b2075d9c) diff --git a/third-party/cmake/BuildLuajit.cmake b/third-party/cmake/BuildLuajit.cmake index e965927f4e..791da4b7df 100644 --- a/third-party/cmake/BuildLuajit.cmake +++ b/third-party/cmake/BuildLuajit.cmake @@ -41,7 +41,6 @@ else() set(AMD64_ABI "") endif() set(INSTALLCMD_UNIX ${MAKE_PRG} CFLAGS=-fPIC - CFLAGS+=-DLUAJIT_DISABLE_JIT CFLAGS+=-DLUA_USE_APICHECK CFLAGS+=-DLUA_USE_ASSERT ${AMD64_ABI} @@ -94,7 +93,6 @@ elseif(MINGW) endif() BuildLuaJit(BUILD_COMMAND ${LUAJIT_MAKE_PRG} CC=${DEPS_C_COMPILER} PREFIX=${DEPS_INSTALL_DIR} - CFLAGS+=-DLUAJIT_DISABLE_JIT CFLAGS+=-DLUA_USE_APICHECK CFLAGS+=-DLUA_USE_ASSERT CCDEBUG+=-g diff --git a/third-party/cmake/BuildLuarocks.cmake b/third-party/cmake/BuildLuarocks.cmake index 140c50ba0d..1f707ddd8a 100644 --- a/third-party/cmake/BuildLuarocks.cmake +++ b/third-party/cmake/BuildLuarocks.cmake @@ -41,9 +41,6 @@ endfunction() # The luarocks binary location set(LUAROCKS_BINARY ${HOSTDEPS_BIN_DIR}/luarocks) -# NOTE: Version must match version of LuaRocks in third-party/CMakeLists.txt -set(LUAROCKS_VERSION 2.4) - # Arguments for calls to 'luarocks build' if(NOT MSVC) # In MSVC don't pass the compiler/linker to luarocks, the bundled @@ -61,6 +58,15 @@ if(UNIX OR (MINGW AND CMAKE_CROSSCOMPILING)) elseif(USE_BUNDLED_LUA) list(APPEND LUAROCKS_OPTS --with-lua=${HOSTDEPS_INSTALL_DIR}) + else() + list(APPEND CMAKE_MODULE_PATH "../cmake") + find_package(LuaJit) + if(LUAJIT_FOUND) + list(APPEND LUAROCKS_OPTS + --lua-version=5.1 + --with-lua-include=${LUAJIT_INCLUDE_DIRS} + --lua-suffix=jit) + endif() endif() BuildLuarocks( @@ -80,14 +86,14 @@ elseif(MSVC OR MINGW) /LUA ${DEPS_INSTALL_DIR} /LIB ${DEPS_LIB_DIR} /BIN ${DEPS_BIN_DIR} - /INC ${DEPS_INSTALL_DIR}/include/luajit-2.0/ - /P ${DEPS_INSTALL_DIR}/${LUAROCKS_VERSION} /TREE ${DEPS_INSTALL_DIR} + /INC ${DEPS_INSTALL_DIR}/include/luajit-2.0 + /P ${DEPS_INSTALL_DIR}/luarocks /TREE ${DEPS_INSTALL_DIR} /SCRIPTS ${DEPS_BIN_DIR} /CMOD ${DEPS_BIN_DIR} ${COMPILER_FLAG} /LUAMOD ${DEPS_BIN_DIR}/lua) - set(LUAROCKS_BINARY ${DEPS_INSTALL_DIR}/${LUAROCKS_VERSION}/luarocks.bat) + set(LUAROCKS_BINARY ${DEPS_INSTALL_DIR}/luarocks/luarocks.bat) else() message(FATAL_ERROR "Trying to build luarocks in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}") endif() |