diff options
author | Yegappan Lakshmanan <yegappan@yahoo.com> | 2021-08-04 21:12:52 +0200 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2021-08-04 21:12:52 +0200 |
commit | 9dc4bef897a37a610a28d69cee6d30749db61296 (patch) | |
tree | 6a17977e373aa38cd1f30216a5f3739ceebaf183 | |
parent | 57942237c1d54d8a236b43c56dc2b002339eb394 (diff) |
patch 8.2.3288: cannot easily access namespace dictionaries from Luav8.2.3288
Problem: Cannot easily access namespace dictionaries from Lua.
Solution: Add vim.g, vim.b, etc. (Yegappan Lakshmanan, closes #8693,
from NeoVim)
-rw-r--r-- | runtime/doc/if_lua.txt | 32 | ||||
-rw-r--r-- | src/if_lua.c | 138 | ||||
-rw-r--r-- | src/testdir/test_lua.vim | 242 | ||||
-rw-r--r-- | src/version.c | 2 |
4 files changed, 414 insertions, 0 deletions
diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt index 48b42e65db..cb68eefe00 100644 --- a/runtime/doc/if_lua.txt +++ b/runtime/doc/if_lua.txt @@ -211,6 +211,38 @@ Vim evaluation and command execution, and others. vim.lua_version The Lua version Vim was compiled with, in the form {major}.{minor}.{patch}, e.g. "5.1.4". + *lua-vim-variables* +The Vim editor global dictionaries |g:| |w:| |b:| |t:| |v:| can be accessed +from Lua conveniently and idiomatically by referencing the `vim.*` Lua tables +described below. In this way you can easily read and modify global Vimscript +variables from Lua. + +Example: > + + vim.g.foo = 5 -- Set the g:foo Vimscript variable. + print(vim.g.foo) -- Get and print the g:foo Vimscript variable. + vim.g.foo = nil -- Delete (:unlet) the Vimscript variable. + +vim.g *vim.g* + Global (|g:|) editor variables. + Key with no value returns `nil`. + +vim.b *vim.b* + Buffer-scoped (|b:|) variables for the current buffer. + Invalid or unset key returns `nil`. + +vim.w *vim.w* + Window-scoped (|w:|) variables for the current window. + Invalid or unset key returns `nil`. + +vim.t *vim.t* + Tabpage-scoped (|t:|) variables for the current tabpage. + Invalid or unset key returns `nil`. + +vim.v *vim.v* + |v:| variables. + Invalid or unset key returns `nil`. + ============================================================================== 3. List userdata *lua-list* diff --git a/src/if_lua.c b/src/if_lua.c index a3bb0b96bd..bcd0b02f97 100644 --- a/src/if_lua.c +++ b/src/if_lua.c @@ -1777,6 +1777,122 @@ luaV_debug(lua_State *L) } } + static dict_T * +luaV_get_var_scope(lua_State *L) +{ + const char *scope = luaL_checkstring(L, 1); + dict_T *dict = NULL; + + if (STRICMP((char *)scope, "g") == 0) + dict = get_globvar_dict(); + else if (STRICMP((char *)scope, "v") == 0) + dict = get_vimvar_dict(); + else if (STRICMP((char *)scope, "b") == 0) + dict = curbuf->b_vars; + else if (STRICMP((char *)scope, "w") == 0) + dict = curwin->w_vars; + else if (STRICMP((char *)scope, "t") == 0) + dict = curtab->tp_vars; + else + { + luaL_error(L, "invalid scope %s", scope); + return NULL; + } + + return dict; +} + + static int +luaV_setvar(lua_State *L) +{ + dict_T *dict; + dictitem_T *di; + size_t len; + char *name; + int del; + char *error = NULL; + + name = (char *)luaL_checklstring(L, 3, &len); + del = (lua_gettop(L) < 4) || lua_isnil(L, 4); + + dict = luaV_get_var_scope(L); + if (dict == NULL) + return 0; + + di = dict_find(dict, (char_u *)name, len); + if (di != NULL) + { + if (di->di_flags & DI_FLAGS_RO) + error = "variable is read-only"; + else if (di->di_flags & DI_FLAGS_LOCK) + error = "variable is locked"; + else if (del && di->di_flags & DI_FLAGS_FIX) + error = "variable is fixed"; + if (error != NULL) + return luaL_error(L, error); + } + else if (dict->dv_lock) + return luaL_error(L, "Dictionary is locked"); + + if (del) + { + // Delete the key + if (di == NULL) + // Doesn't exist, nothing to do + return 0; + else + // Delete the entry + dictitem_remove(dict, di); + } + else + { + // Update the key + typval_T tv; + + // Convert the lua value to a vimscript type in the temporary variable + lua_pushvalue(L, 4); + if (luaV_totypval(L, -1, &tv) == FAIL) + return luaL_error(L, "Couldn't convert lua value"); + + if (di == NULL) + { + // Need to create an entry + di = dictitem_alloc((char_u *)name); + if (di == NULL) + return 0; + // Update the value + copy_tv(&tv, &di->di_tv); + dict_add(dict, di); + } else + { + // Clear the old value + clear_tv(&di->di_tv); + // Update the value + copy_tv(&tv, &di->di_tv); + } + + // Clear the temporary variable + clear_tv(&tv); + } + + return 0; +} + + static int +luaV_getvar(lua_State *L) +{ + dict_T *dict = luaV_get_var_scope(L); + size_t len; + const char *name = luaL_checklstring(L, 3, &len); + + dictitem_T *di = dict_find(dict, (char_u *)name, len); + if (di == NULL) + return 0; // nil + + luaV_pushtypval(L, &di->di_tv); + return 1; +} + static int luaV_command(lua_State *L) { @@ -2103,6 +2219,8 @@ static const luaL_Reg luaV_module[] = { {"open", luaV_open}, {"type", luaV_type}, {"call", luaV_call}, + {"_getvar", luaV_getvar}, + {"_setvar", luaV_setvar}, {"lua_version", NULL}, {NULL, NULL} }; @@ -2275,6 +2393,25 @@ luaV_pushversion(lua_State *L) " last_vim_paths = cur_vim_paths\n"\ "end" +#define LUA_VIM_SETUP_VARIABLE_DICTS \ + "do\n"\ + " local function make_dict_accessor(scope)\n"\ + " local mt = {}\n"\ + " function mt:__newindex(k, v)\n"\ + " return vim._setvar(scope, 0, k, v)\n"\ + " end\n"\ + " function mt:__index(k)\n"\ + " return vim._getvar(scope, 0, k)\n"\ + " end\n"\ + " return setmetatable({}, mt)\n"\ + " end\n"\ + " vim.g = make_dict_accessor('g')\n"\ + " vim.v = make_dict_accessor('v')\n"\ + " vim.b = make_dict_accessor('b')\n"\ + " vim.w = make_dict_accessor('w')\n"\ + " vim.t = make_dict_accessor('t')\n"\ + "end" + static int luaopen_vim(lua_State *L) { @@ -2335,6 +2472,7 @@ luaopen_vim(lua_State *L) // custom code (void)luaL_dostring(L, LUA_VIM_FN_CODE); (void)luaL_dostring(L, LUA_VIM_UPDATE_PACKAGE_PATHS); + (void)luaL_dostring(L, LUA_VIM_SETUP_VARIABLE_DICTS); lua_getglobal(L, "vim"); lua_getfield(L, -1, "_update_package_paths"); diff --git a/src/testdir/test_lua.vim b/src/testdir/test_lua.vim index c30f9597ea..953e8a9b67 100644 --- a/src/testdir/test_lua.vim +++ b/src/testdir/test_lua.vim @@ -916,4 +916,246 @@ vim.command('let s ..= "B"') call assert_equal('ABCDE', s) endfunc +" Test for adding, accessing and removing global variables using the vim.g +" Lua table +func Test_lua_global_var_table() + " Access global variables with different types of values + let g:Var1 = 10 + let g:Var2 = 'Hello' + let g:Var3 = ['a', 'b'] + let g:Var4 = #{x: 'edit', y: 'run'} + let g:Var5 = function('min') + call assert_equal(10, luaeval('vim.g.Var1')) + call assert_equal('Hello', luaeval('vim.g.Var2')) + call assert_equal(['a', 'b'], luaeval('vim.g.Var3')) + call assert_equal(#{x: 'edit', y: 'run'}, luaeval('vim.g.Var4')) + call assert_equal(2, luaeval('vim.g.Var5')([5, 9, 2])) + + " Access list of dictionaries and dictionary of lists + let g:Var1 = [#{a: 10}, #{b: 20}] + let g:Var2 = #{p: [5, 6], q: [1.1, 2.2]} + call assert_equal([#{a: 10}, #{b: 20}], luaeval('vim.g.Var1')) + call assert_equal(#{p: [5, 6], q: [1.1, 2.2]}, luaeval('vim.g.Var2')) + + " Create new global variables with different types of values + unlet g:Var1 g:Var2 g:Var3 g:Var4 g:Var5 + lua << trim END + vim.g.Var1 = 34 + vim.g.Var2 = 'World' + vim.g.Var3 = vim.list({'#', '$'}) + vim.g.Var4 = vim.dict({model='honda', year=2020}) + vim.g.Var5 = vim.funcref('max') + END + call assert_equal(34, g:Var1) + call assert_equal('World', g:Var2) + call assert_equal(['#', '$'], g:Var3) + call assert_equal(#{model: 'honda', year: 2020}, g:Var4) + call assert_equal(10, g:Var5([5, 10, 9])) + + " Create list of dictionaries and dictionary of lists + unlet g:Var1 g:Var2 + lua << trim END + vim.g.Var1 = vim.list({vim.dict({a=10}), vim.dict({b=20})}) + vim.g.Var2 = vim.dict({p=vim.list({5, 6}), q=vim.list({1.1, 2.2})}) + END + call assert_equal([#{a: 10}, #{b: 20}], luaeval('vim.g.Var1')) + call assert_equal(#{p: [5, 6], q: [1.1, 2.2]}, luaeval('vim.g.Var2')) + + " Modify a global variable with a list value or a dictionary value + let g:Var1 = [10, 20] + let g:Var2 = #{one: 'mercury', two: 'mars'} + lua << trim END + vim.g.Var1[2] = Nil + vim.g.Var1[3] = 15 + vim.g.Var2['two'] = Nil + vim.g.Var2['three'] = 'earth' + END + call assert_equal([10, 15], g:Var1) + call assert_equal(#{one: 'mercury', three: 'earth'}, g:Var2) + + " Remove global variables with different types of values + let g:Var1 = 10 + let g:Var2 = 'Hello' + let g:Var3 = ['a', 'b'] + 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 + END + call assert_false(exists('g:Var1')) + call assert_false(exists('g:Var2')) + call assert_false(exists('g:Var3')) + call assert_false(exists('g:Var4')) + call assert_false(exists('g:Var5')) + + " Try to modify and remove a locked global variable + 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') + unlockvar g:Var1 + let g:Var2 = [7, 14] + lockvar 0 g:Var2 + 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_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[3] = 21', 'list is locked') + unlockvar g:Var2 + + " Attempt to access a non-existing global variable + call assert_equal(v:null, luaeval('vim.g.NonExistingVar')) + lua vim.g.NonExisting = Nil + + unlet! g:Var1 g:Var2 g:Var3 g:Var4 g:Var5 +endfunc + +" Test for accessing and modifying predefined vim variables using the vim.v +" Lua table +func Test_lua_predefined_var_table() + call assert_equal(v:progpath, luaeval('vim.v.progpath')) + let v:errmsg = 'SomeError' + 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') + 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_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.newvar = 1', 'Dictionary is locked') +endfunc + +" Test for adding, accessing and modifying window-local variables using the +" vim.w Lua table +func Test_lua_window_var_table() + " Access window variables with different types of values + new + let w:wvar1 = 10 + let w:wvar2 = 'edit' + let w:wvar3 = 3.14 + let w:wvar4 = 0zdeadbeef + let w:wvar5 = ['a', 'b'] + let w:wvar6 = #{action: 'run'} + call assert_equal(10, luaeval('vim.w.wvar1')) + call assert_equal('edit', luaeval('vim.w.wvar2')) + call assert_equal(3.14, luaeval('vim.w.wvar3')) + call assert_equal(0zdeadbeef, luaeval('vim.w.wvar4')) + call assert_equal(['a', 'b'], luaeval('vim.w.wvar5')) + call assert_equal(#{action: 'run'}, luaeval('vim.w.wvar6')) + call assert_equal(v:null, luaeval('vim.w.NonExisting')) + + " modify a window variable + lua vim.w.wvar2 = 'paste' + call assert_equal('paste', w:wvar2) + + " change the type stored in a variable + let w:wvar2 = [1, 2] + lua vim.w.wvar2 = vim.dict({a=10, b=20}) + call assert_equal(#{a: 10, b: 20}, w:wvar2) + + " create a new window variable + lua vim.w.wvar7 = vim.dict({a=vim.list({1, 2}), b=20}) + call assert_equal(#{a: [1, 2], b: 20}, w:wvar7) + + " delete a window variable + lua vim.w.wvar2 = Nil + call assert_false(exists('w:wvar2')) + + new + call assert_equal(v:null, luaeval('vim.w.wvar1')) + call assert_equal(v:null, luaeval('vim.w.wvar2')) + %bw! +endfunc + +" Test for adding, accessing and modifying buffer-local variables using the +" vim.b Lua table +func Test_lua_buffer_var_table() + " Access buffer variables with different types of values + let b:bvar1 = 10 + let b:bvar2 = 'edit' + let b:bvar3 = 3.14 + let b:bvar4 = 0zdeadbeef + let b:bvar5 = ['a', 'b'] + let b:bvar6 = #{action: 'run'} + call assert_equal(10, luaeval('vim.b.bvar1')) + call assert_equal('edit', luaeval('vim.b.bvar2')) + call assert_equal(3.14, luaeval('vim.b.bvar3')) + call assert_equal(0zdeadbeef, luaeval('vim.b.bvar4')) + call assert_equal(['a', 'b'], luaeval('vim.b.bvar5')) + call assert_equal(#{action: 'run'}, luaeval('vim.b.bvar6')) + call assert_equal(v:null, luaeval('vim.b.NonExisting')) + + " modify a buffer variable + lua vim.b.bvar2 = 'paste' + call assert_equal('paste', b:bvar2) + + " change the type stored in a variable + let b:bvar2 = [1, 2] + lua vim.b.bvar2 = vim.dict({a=10, b=20}) + call assert_equal(#{a: 10, b: 20}, b:bvar2) + + " create a new buffer variable + lua vim.b.bvar7 = vim.dict({a=vim.list({1, 2}), b=20}) + call assert_equal(#{a: [1, 2], b: 20}, b:bvar7) + + " delete a buffer variable + lua vim.b.bvar2 = Nil + call assert_false(exists('b:bvar2')) + + new + call assert_equal(v:null, luaeval('vim.b.bvar1')) + call assert_equal(v:null, luaeval('vim.b.bvar2')) + %bw! +endfunc + +" Test for adding, accessing and modifying tabpage-local variables using the +" vim.t Lua table +func Test_lua_tabpage_var_table() + " Access tabpage variables with different types of values + let t:tvar1 = 10 + let t:tvar2 = 'edit' + let t:tvar3 = 3.14 + let t:tvar4 = 0zdeadbeef + let t:tvar5 = ['a', 'b'] + let t:tvar6 = #{action: 'run'} + call assert_equal(10, luaeval('vim.t.tvar1')) + call assert_equal('edit', luaeval('vim.t.tvar2')) + call assert_equal(3.14, luaeval('vim.t.tvar3')) + call assert_equal(0zdeadbeef, luaeval('vim.t.tvar4')) + call assert_equal(['a', 'b'], luaeval('vim.t.tvar5')) + call assert_equal(#{action: 'run'}, luaeval('vim.t.tvar6')) + call assert_equal(v:null, luaeval('vim.t.NonExisting')) + + " modify a tabpage variable + lua vim.t.tvar2 = 'paste' + call assert_equal('paste', t:tvar2) + + " change the type stored in a variable + let t:tvar2 = [1, 2] + lua vim.t.tvar2 = vim.dict({a=10, b=20}) + call assert_equal(#{a: 10, b: 20}, t:tvar2) + + " create a new tabpage variable + lua vim.t.tvar7 = vim.dict({a=vim.list({1, 2}), b=20}) + call assert_equal(#{a: [1, 2], b: 20}, t:tvar7) + + " delete a tabpage variable + lua vim.t.tvar2 = Nil + call assert_false(exists('t:tvar2')) + + tabnew + call assert_equal(v:null, luaeval('vim.t.tvar1')) + call assert_equal(v:null, luaeval('vim.t.tvar2')) + %bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c index ef05ea36d6..b878054039 100644 --- 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 */ /**/ + 3288, +/**/ 3287, /**/ 3286, |