# HG changeset patch # User Christian Brabandt # Date 1695591003 -7200 # Node ID 441ceb1c45c142e7a0fd444db370f7da982311b3 # Parent 9e4fde36162a758540546375e6dfc322991d28d1 patch 9.0.1933: Can change the type of a v: variable using if_lua Commit: https://github.com/vim/vim/commit/edcba96c0088210927558b0e2583f3b689f457c4 Author: zeertzjq Date: Sun Sep 24 23:13:51 2023 +0200 patch 9.0.1933: Can change the type of a v: variable using if_lua Problem: Can change the type of a v: variable using if_lua. Solution: Add additional handling of v: variables like :let. closes: #13161 Signed-off-by: Christian Brabandt Co-authored-by: zeertzjq diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -2520,8 +2520,8 @@ EXTERN char e_no_line_number_to_use_for_ INIT(= N_("E961: No line number to use for \"\"")); EXTERN char e_invalid_action_str_2[] INIT(= N_("E962: Invalid action: '%s'")); -EXTERN char e_setting_str_to_value_with_wrong_type[] - INIT(= N_("E963: Setting %s to value with wrong type")); +EXTERN char e_setting_v_str_to_value_with_wrong_type[] + INIT(= N_("E963: Setting v:%s to value with wrong type")); #endif #ifdef FEAT_PROP_POPUP EXTERN char_u e_invalid_column_number_nr[] diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -3673,6 +3673,62 @@ list_one_var_a( } /* + * Addition handling for setting a v: variable. + * Return TRUE if the variable should be set normally, + * FALSE if nothing else needs to be done. + */ + int +before_set_vvar( + char_u *varname, + dictitem_T *di, + typval_T *tv, + int copy, + int *type_error) +{ + if (di->di_tv.v_type == VAR_STRING) + { + VIM_CLEAR(di->di_tv.vval.v_string); + if (copy || tv->v_type != VAR_STRING) + { + char_u *val = tv_get_string(tv); + + // Careful: when assigning to v:errmsg and + // tv_get_string() causes an error message the variable + // will already be set. + if (di->di_tv.vval.v_string == NULL) + di->di_tv.vval.v_string = vim_strsave(val); + } + else + { + // Take over the string to avoid an extra alloc/free. + di->di_tv.vval.v_string = tv->vval.v_string; + tv->vval.v_string = NULL; + } + return FALSE; + } + else if (di->di_tv.v_type == VAR_NUMBER) + { + di->di_tv.vval.v_number = tv_get_number(tv); + if (STRCMP(varname, "searchforward") == 0) + set_search_direction(di->di_tv.vval.v_number ? '/' : '?'); +#ifdef FEAT_SEARCH_EXTRA + else if (STRCMP(varname, "hlsearch") == 0) + { + no_hlsearch = !di->di_tv.vval.v_number; + redraw_all_later(UPD_SOME_VALID); + } +#endif + return FALSE; + } + else if (di->di_tv.v_type != tv->v_type) + { + *type_error = TRUE; + return FALSE; + } + return TRUE; +} + +/* * Set variable "name" to value in "tv". * If the variable already exists, the value is updated. * Otherwise the variable is created. @@ -3877,51 +3933,15 @@ set_var_const( // existing variable, need to clear the value - // Handle setting internal di: variables separately where needed to + // Handle setting internal v: variables separately where needed to // prevent changing the type. - if (ht == &vimvarht) + int type_error = FALSE; + if (ht == &vimvarht + && !before_set_vvar(varname, di, tv, copy, &type_error)) { - if (di->di_tv.v_type == VAR_STRING) - { - VIM_CLEAR(di->di_tv.vval.v_string); - if (copy || tv->v_type != VAR_STRING) - { - char_u *val = tv_get_string(tv); - - // Careful: when assigning to v:errmsg and - // tv_get_string() causes an error message the variable - // will already be set. - if (di->di_tv.vval.v_string == NULL) - di->di_tv.vval.v_string = vim_strsave(val); - } - else - { - // Take over the string to avoid an extra alloc/free. - di->di_tv.vval.v_string = tv->vval.v_string; - tv->vval.v_string = NULL; - } - goto failed; - } - else if (di->di_tv.v_type == VAR_NUMBER) - { - di->di_tv.vval.v_number = tv_get_number(tv); - if (STRCMP(varname, "searchforward") == 0) - set_search_direction(di->di_tv.vval.v_number - ? '/' : '?'); -#ifdef FEAT_SEARCH_EXTRA - else if (STRCMP(varname, "hlsearch") == 0) - { - no_hlsearch = !di->di_tv.vval.v_number; - redraw_all_later(UPD_SOME_VALID); - } -#endif - goto failed; - } - else if (di->di_tv.v_type != tv->v_type) - { - semsg(_(e_setting_str_to_value_with_wrong_type), name); - goto failed; - } + if (type_error) + semsg(_(e_setting_v_str_to_value_with_wrong_type), varname); + goto failed; } clear_tv(&di->di_tv); diff --git a/src/if_lua.c b/src/if_lua.c --- a/src/if_lua.c +++ b/src/if_lua.c @@ -1900,6 +1900,16 @@ luaV_setvar(lua_State *L) } else { + int type_error = FALSE; + if (dict == get_vimvar_dict() + && !before_set_vvar((char_u *)name, di, &tv, TRUE, &type_error)) + { + clear_tv(&tv); + if (type_error) + return luaL_error(L, + "Setting v:%s to value with wrong type", name); + return 0; + } // Clear the old value clear_tv(&di->di_tv); // Update the value diff --git a/src/proto/evalvars.pro b/src/proto/evalvars.pro --- a/src/proto/evalvars.pro +++ b/src/proto/evalvars.pro @@ -75,6 +75,7 @@ void unref_var_dict(dict_T *dict); void vars_clear(hashtab_T *ht); void vars_clear_ext(hashtab_T *ht, int free_val); void delete_var(hashtab_T *ht, hashitem_T *hi); +int before_set_vvar(char_u *varname, dictitem_T *di, typval_T *tv, int copy, int *type_error); void set_var(char_u *name, typval_T *tv, int copy); void set_var_const(char_u *name, scid_T sid, type_T *type_arg, typval_T *tv_arg, int copy, int flags_arg, int var_idx); int var_check_permission(dictitem_T *di, char_u *name); diff --git a/src/testdir/test_lua.vim b/src/testdir/test_lua.vim --- a/src/testdir/test_lua.vim +++ b/src/testdir/test_lua.vim @@ -970,9 +970,9 @@ func Test_lua_global_var_table() let g:Var1 = [10, 20] let g:Var2 = #{one: 'mercury', two: 'mars'} lua << trim END - vim.g.Var1[2] = Nil + vim.g.Var1[2] = nil vim.g.Var1[3] = 15 - vim.g.Var2['two'] = Nil + vim.g.Var2['two'] = nil vim.g.Var2['three'] = 'earth' END call assert_equal([10, 15], g:Var1) @@ -985,11 +985,11 @@ func Test_lua_global_var_table() let g:Var4 = #{x: 'edit', y: 'run'} let g:Var5 = function('min') lua << trim END - vim.g.Var1 = Nil - vim.g.Var2 = Nil - vim.g.Var3 = Nil - vim.g.Var4 = Nil - vim.g.Var5 = Nil + vim.g.Var1 = nil + vim.g.Var2 = nil + vim.g.Var3 = nil + vim.g.Var4 = nil + vim.g.Var5 = nil END call assert_false(exists('g:Var1')) call assert_false(exists('g:Var2')) @@ -1001,16 +1001,16 @@ func Test_lua_global_var_table() let g:Var1 = 10 lockvar g:Var1 call assert_fails('lua vim.g.Var1 = 20', 'variable is locked') - call assert_fails('lua vim.g.Var1 = Nil', 'variable is locked') + call assert_fails('lua vim.g.Var1 = nil', 'variable is locked') unlockvar g:Var1 let g:Var2 = [7, 14] lockvar 0 g:Var2 - lua vim.g.Var2[2] = Nil + lua vim.g.Var2[2] = nil lua vim.g.Var2[3] = 21 - call assert_fails('lua vim.g.Var2 = Nil', 'variable is locked') + call assert_fails('lua vim.g.Var2 = nil', 'variable is locked') call assert_equal([7, 21], g:Var2) lockvar 1 g:Var2 - call assert_fails('lua vim.g.Var2[2] = Nil', 'list is locked') + call assert_fails('lua vim.g.Var2[2] = nil', 'list is locked') call assert_fails('lua vim.g.Var2[3] = 21', 'list is locked') unlockvar g:Var2 @@ -1020,7 +1020,7 @@ func Test_lua_global_var_table() " Attempt to access a non-existing global variable call assert_equal(v:null, luaeval('vim.g.NonExistingVar')) - lua vim.g.NonExisting = Nil + lua vim.g.NonExisting = nil unlet! g:Var1 g:Var2 g:Var3 g:Var4 g:Var5 endfunc @@ -1033,14 +1033,36 @@ func Test_lua_predefined_var_table() call assert_equal('SomeError', luaeval('vim.v.errmsg')) lua vim.v.errmsg = 'OtherError' call assert_equal('OtherError', v:errmsg) - call assert_fails('lua vim.v.errmsg = Nil', 'variable is fixed') + lua vim.v.errmsg = 42 + call assert_equal('42', v:errmsg) + call assert_fails('lua vim.v.errmsg = nil', 'variable is fixed') let v:oldfiles = ['one', 'two'] call assert_equal(['one', 'two'], luaeval('vim.v.oldfiles')) lua vim.v.oldfiles = vim.list({}) call assert_equal([], v:oldfiles) + call assert_fails('lua vim.v.oldfiles = "a"', + \ 'Setting v:oldfiles to value with wrong type') + call assert_equal([], v:oldfiles) call assert_equal(v:null, luaeval('vim.v.null')) - call assert_fails('lua vim.v.argv[1] = Nil', 'list is locked') + call assert_fails('lua vim.v.argv[1] = nil', 'list is locked') call assert_fails('lua vim.v.newvar = 1', 'Dictionary is locked') + + new + call setline(1, ' foo foo foo') + /foo + call assert_equal([0, 1, 2, 0, 2], getcurpos()) + call assert_equal(1, v:searchforward) + normal! n + call assert_equal([0, 1, 6, 0, 6], getcurpos()) + lua vim.v.searchforward = 0 + call assert_equal(0, v:searchforward) + normal! n + call assert_equal([0, 1, 2, 0, 2], getcurpos()) + lua vim.v.searchforward = 1 + call assert_equal(1, v:searchforward) + normal! n + call assert_equal([0, 1, 6, 0, 6], getcurpos()) + bwipe! endfunc " Test for adding, accessing and modifying window-local variables using the @@ -1076,7 +1098,7 @@ func Test_lua_window_var_table() call assert_equal(#{a: [1, 2], b: 20}, w:wvar7) " delete a window variable - lua vim.w.wvar2 = Nil + lua vim.w.wvar2 = nil call assert_false(exists('w:wvar2')) new @@ -1117,7 +1139,7 @@ func Test_lua_buffer_var_table() call assert_equal(#{a: [1, 2], b: 20}, b:bvar7) " delete a buffer variable - lua vim.b.bvar2 = Nil + lua vim.b.bvar2 = nil call assert_false(exists('b:bvar2')) new @@ -1158,7 +1180,7 @@ func Test_lua_tabpage_var_table() call assert_equal(#{a: [1, 2], b: 20}, t:tvar7) " delete a tabpage variable - lua vim.t.tvar2 = Nil + lua vim.t.tvar2 = nil call assert_false(exists('t:tvar2')) tabnew diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -700,6 +700,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1933, +/**/ 1932, /**/ 1931,