Patch 8.2.3288
Problem:    Cannot easily access namespace dictionaries from Lua.
Solution:   Add vim.g, vim.b, etc. (Yegappan Lakshmanan, closes #8693,
            from NeoVim)
Files:      runtime/doc/if_lua.txt, src/if_lua.c, src/testdir/test_lua.vim


*** ../vim-8.2.3287/runtime/doc/if_lua.txt      2021-04-07 20:11:07.987846226 
+0200
--- runtime/doc/if_lua.txt      2021-08-03 18:54:54.060171470 +0200
***************
*** 211,216 ****
--- 211,248 ----
        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*
  
*** ../vim-8.2.3287/src/if_lua.c        2021-07-29 20:22:10.734009550 +0200
--- src/if_lua.c        2021-08-04 21:10:47.131364762 +0200
***************
*** 1777,1782 ****
--- 1777,1898 ----
      }
  }
  
+     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,2108 ****
--- 2219,2226 ----
      {"open", luaV_open},
      {"type", luaV_type},
      {"call", luaV_call},
+     {"_getvar", luaV_getvar},
+     {"_setvar", luaV_setvar},
      {"lua_version", NULL},
      {NULL, NULL}
  };
***************
*** 2275,2280 ****
--- 2393,2417 ----
      "  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,2340 ****
--- 2472,2478 ----
      // 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");
*** ../vim-8.2.3287/src/testdir/test_lua.vim    2021-07-28 21:48:55.841029431 
+0200
--- src/testdir/test_lua.vim    2021-08-04 21:10:47.131364762 +0200
***************
*** 916,919 ****
--- 916,1161 ----
    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
*** ../vim-8.2.3287/src/version.c       2021-08-04 20:54:52.297882068 +0200
--- src/version.c       2021-08-04 21:11:26.055267099 +0200
***************
*** 757,758 ****
--- 757,760 ----
  {   /* Add new patch number below this line */
+ /**/
+     3288,
  /**/

-- 
Futility Factor: No experiment is ever a complete failure - it can always
serve as a negative example.

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/202108041913.174JDfoG1114679%40masaka.moolenaar.net.

Raspunde prin e-mail lui