mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 09:44:31 +00:00 
			
		
		
		
	lua: metatable for empty dict value
This commit is contained in:
		@@ -717,6 +717,16 @@ vim.NIL								    *vim.NIL*
 | 
				
			|||||||
	is equivalent to a missing value: `{"foo", nil}` is the same as 
 | 
						is equivalent to a missing value: `{"foo", nil}` is the same as 
 | 
				
			||||||
	`{"foo"}`
 | 
						`{"foo"}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					vim.empty_dict()					    *vim.empty_dict()*
 | 
				
			||||||
 | 
						Creates a special table which will be converted to an empty
 | 
				
			||||||
 | 
						dictionary when converting lua values to vimL or API types. The
 | 
				
			||||||
 | 
						table is empty, and this property is marked using a metatable. An
 | 
				
			||||||
 | 
						empty table `{}` without this metatable will default to convert to
 | 
				
			||||||
 | 
						an array/list.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Note: if numeric keys are added to the table, the metatable will be
 | 
				
			||||||
 | 
						ignored and the dict converted to a list/array anyway.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
vim.rpcnotify({channel}, {method}[, {args}...])		    *vim.rpcnotify()*
 | 
					vim.rpcnotify({channel}, {method}[, {args}...])		    *vim.rpcnotify()*
 | 
				
			||||||
	Sends {event} to {channel} via |RPC| and returns immediately.
 | 
						Sends {event} to {channel} via |RPC| and returns immediately.
 | 
				
			||||||
	If {channel} is 0, the event is broadcast to all channels.
 | 
						If {channel} is 0, the event is broadcast to all channels.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -244,6 +244,11 @@ function Inspector:putTable(t)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    local nonSequentialKeys, nonSequentialKeysLength, sequenceLength = getNonSequentialKeys(t)
 | 
					    local nonSequentialKeys, nonSequentialKeysLength, sequenceLength = getNonSequentialKeys(t)
 | 
				
			||||||
    local mt                = getmetatable(t)
 | 
					    local mt                = getmetatable(t)
 | 
				
			||||||
 | 
					    if (vim and sequenceLength == 0 and nonSequentialKeysLength == 0
 | 
				
			||||||
 | 
					        and mt == vim._empty_dict_mt) then
 | 
				
			||||||
 | 
					      self:puts(tostring(t))
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    self:puts('{')
 | 
					    self:puts('{')
 | 
				
			||||||
    self:down(function()
 | 
					    self:down(function()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,9 +275,15 @@ function vim.tbl_flatten(t)
 | 
				
			|||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- Determine whether a Lua table can be treated as an array.
 | 
					-- Determine whether a Lua table can be treated as an array.
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- An empty table `{}` will default to being treated as an array.
 | 
				
			||||||
 | 
					-- Use `vim.emtpy_dict()` to create a table treated as an
 | 
				
			||||||
 | 
					-- empty dict. Empty tables returned by `rpcrequest()` and
 | 
				
			||||||
 | 
					-- `vim.fn` functions can be checked using this function
 | 
				
			||||||
 | 
					-- whether they represent empty API arrays and vimL lists.
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
--@params Table
 | 
					--@params Table
 | 
				
			||||||
--@returns true: A non-empty array, false: A non-empty table, nil: An empty table
 | 
					--@returns true: An array-like table, false: A dict-like or mixed table
 | 
				
			||||||
function vim.tbl_islist(t)
 | 
					function vim.tbl_islist(t)
 | 
				
			||||||
  if type(t) ~= 'table' then
 | 
					  if type(t) ~= 'table' then
 | 
				
			||||||
    return false
 | 
					    return false
 | 
				
			||||||
@@ -296,7 +302,12 @@ function vim.tbl_islist(t)
 | 
				
			|||||||
  if count > 0 then
 | 
					  if count > 0 then
 | 
				
			||||||
    return true
 | 
					    return true
 | 
				
			||||||
  else
 | 
					  else
 | 
				
			||||||
    return nil
 | 
					    -- TODO(bfredl): in the future, we will always be inside nvim
 | 
				
			||||||
 | 
					    -- then this check can be deleted.
 | 
				
			||||||
 | 
					    if vim._empty_dict_mt == nil then
 | 
				
			||||||
 | 
					      return nil
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    return getmetatable(t) ~= vim._empty_dict_mt
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -156,6 +156,13 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate)
 | 
				
			|||||||
            && other_keys_num == 0
 | 
					            && other_keys_num == 0
 | 
				
			||||||
            && ret.string_keys_num == 0)) {
 | 
					            && ret.string_keys_num == 0)) {
 | 
				
			||||||
      ret.type = kObjectTypeArray;
 | 
					      ret.type = kObjectTypeArray;
 | 
				
			||||||
 | 
					      if (tsize == 0 && lua_getmetatable(lstate, -1)) {
 | 
				
			||||||
 | 
					        nlua_pushref(lstate, nlua_empty_dict_ref);
 | 
				
			||||||
 | 
					        if (lua_rawequal(lstate, -2, -1)) {
 | 
				
			||||||
 | 
					          ret.type = kObjectTypeDictionary;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        lua_pop(lstate, 2);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    } else if (ret.string_keys_num == tsize) {
 | 
					    } else if (ret.string_keys_num == tsize) {
 | 
				
			||||||
      ret.type = kObjectTypeDictionary;
 | 
					      ret.type = kObjectTypeDictionary;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
@@ -465,6 +472,8 @@ static bool typval_conv_special = false;
 | 
				
			|||||||
        nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \
 | 
					        nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \
 | 
				
			||||||
      } else { \
 | 
					      } else { \
 | 
				
			||||||
        lua_createtable(lstate, 0, 0); \
 | 
					        lua_createtable(lstate, 0, 0); \
 | 
				
			||||||
 | 
					        nlua_pushref(lstate, nlua_empty_dict_ref); \
 | 
				
			||||||
 | 
					        lua_setmetatable(lstate, -2); \
 | 
				
			||||||
      } \
 | 
					      } \
 | 
				
			||||||
    } while (0)
 | 
					    } while (0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -695,6 +704,10 @@ void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict,
 | 
				
			|||||||
    nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary);
 | 
					    nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary);
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    lua_createtable(lstate, 0, (int)dict.size);
 | 
					    lua_createtable(lstate, 0, (int)dict.size);
 | 
				
			||||||
 | 
					    if (dict.size == 0 && !special) {
 | 
				
			||||||
 | 
					      nlua_pushref(lstate, nlua_empty_dict_ref);
 | 
				
			||||||
 | 
					      lua_setmetatable(lstate, -2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  for (size_t i = 0; i < dict.size; i++) {
 | 
					  for (size_t i = 0; i < dict.size; i++) {
 | 
				
			||||||
    nlua_push_String(lstate, dict.items[i].key, special);
 | 
					    nlua_push_String(lstate, dict.items[i].key, special);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -324,6 +324,13 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
 | 
				
			|||||||
  nlua_nil_ref = nlua_ref(lstate, -1);
 | 
					  nlua_nil_ref = nlua_ref(lstate, -1);
 | 
				
			||||||
  lua_setfield(lstate, -2, "NIL");
 | 
					  lua_setfield(lstate, -2, "NIL");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // vim._empty_dict_mt
 | 
				
			||||||
 | 
					  lua_createtable(lstate, 0, 0);
 | 
				
			||||||
 | 
					  lua_pushcfunction(lstate, &nlua_empty_dict_tostring);
 | 
				
			||||||
 | 
					  lua_setfield(lstate, -2, "__tostring");
 | 
				
			||||||
 | 
					  nlua_empty_dict_ref = nlua_ref(lstate, -1);
 | 
				
			||||||
 | 
					  lua_setfield(lstate, -2, "_empty_dict_mt");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // internal vim._treesitter... API
 | 
					  // internal vim._treesitter... API
 | 
				
			||||||
  nlua_add_treesitter(lstate);
 | 
					  nlua_add_treesitter(lstate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -665,6 +672,12 @@ static int nlua_nil_tostring(lua_State *lstate)
 | 
				
			|||||||
  return 1;
 | 
					  return 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int nlua_empty_dict_tostring(lua_State *lstate)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  lua_pushstring(lstate, "vim.empty_dict()");
 | 
				
			||||||
 | 
					  return 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef WIN32
 | 
					#ifdef WIN32
 | 
				
			||||||
/// os.getenv: override os.getenv to maintain coherency. #9681
 | 
					/// os.getenv: override os.getenv to maintain coherency. #9681
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,7 @@
 | 
				
			|||||||
void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
 | 
					void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF);
 | 
					EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF);
 | 
				
			||||||
 | 
					EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define set_api_error(s, err) \
 | 
					#define set_api_error(s, err) \
 | 
				
			||||||
    do { \
 | 
					    do { \
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -243,6 +243,10 @@ function vim.schedule_wrap(cb)
 | 
				
			|||||||
  end)
 | 
					  end)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function vim.empty_dict()
 | 
				
			||||||
 | 
					  return setmetatable({}, vim._empty_dict_mt)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- vim.fn.{func}(...)
 | 
					-- vim.fn.{func}(...)
 | 
				
			||||||
vim.fn = setmetatable({}, {
 | 
					vim.fn = setmetatable({}, {
 | 
				
			||||||
  __index = function(t, key)
 | 
					  __index = function(t, key)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -354,7 +354,8 @@ describe('lua stdlib', function()
 | 
				
			|||||||
  end)
 | 
					  end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('vim.tbl_islist', function()
 | 
					  it('vim.tbl_islist', function()
 | 
				
			||||||
    eq(NIL, exec_lua("return vim.tbl_islist({})"))
 | 
					    eq(true, exec_lua("return vim.tbl_islist({})"))
 | 
				
			||||||
 | 
					    eq(false, exec_lua("return vim.tbl_islist(vim.empty_dict())"))
 | 
				
			||||||
    eq(true, exec_lua("return vim.tbl_islist({'a', 'b', 'c'})"))
 | 
					    eq(true, exec_lua("return vim.tbl_islist({'a', 'b', 'c'})"))
 | 
				
			||||||
    eq(false, exec_lua("return vim.tbl_islist({'a', '32', a='hello', b='baz'})"))
 | 
					    eq(false, exec_lua("return vim.tbl_islist({'a', '32', a='hello', b='baz'})"))
 | 
				
			||||||
    eq(false, exec_lua("return vim.tbl_islist({1, a='hello', b='baz'})"))
 | 
					    eq(false, exec_lua("return vim.tbl_islist({1, a='hello', b='baz'})"))
 | 
				
			||||||
@@ -458,6 +459,19 @@ describe('lua stdlib', function()
 | 
				
			|||||||
    ]]))
 | 
					    ]]))
 | 
				
			||||||
    eq({3, 'aa', true, NIL}, exec_lua([[return ret]]))
 | 
					    eq({3, 'aa', true, NIL}, exec_lua([[return ret]]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    eq({{}, {}, false, true}, exec_lua([[
 | 
				
			||||||
 | 
					      vim.rpcrequest(chan, 'nvim_exec', 'let xx = {}\nlet yy = []', false)
 | 
				
			||||||
 | 
					      local dict = vim.rpcrequest(chan, 'nvim_eval', 'xx')
 | 
				
			||||||
 | 
					      local list = vim.rpcrequest(chan, 'nvim_eval', 'yy')
 | 
				
			||||||
 | 
					      return {dict, list, vim.tbl_islist(dict), vim.tbl_islist(list)}
 | 
				
			||||||
 | 
					     ]]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     exec_lua([[
 | 
				
			||||||
 | 
					       vim.rpcrequest(chan, 'nvim_set_var', 'aa', {})
 | 
				
			||||||
 | 
					       vim.rpcrequest(chan, 'nvim_set_var', 'bb', vim.empty_dict())
 | 
				
			||||||
 | 
					     ]])
 | 
				
			||||||
 | 
					     eq({1, 1}, eval('[type(g:aa) == type([]), type(g:bb) == type({})]'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    -- error handling
 | 
					    -- error handling
 | 
				
			||||||
    eq({false, 'Invalid channel: 23'},
 | 
					    eq({false, 'Invalid channel: 23'},
 | 
				
			||||||
       exec_lua([[return {pcall(vim.rpcrequest, 23, 'foo')}]]))
 | 
					       exec_lua([[return {pcall(vim.rpcrequest, 23, 'foo')}]]))
 | 
				
			||||||
@@ -486,7 +500,7 @@ describe('lua stdlib', function()
 | 
				
			|||||||
    })
 | 
					    })
 | 
				
			||||||
    screen:attach()
 | 
					    screen:attach()
 | 
				
			||||||
    exec_lua([[
 | 
					    exec_lua([[
 | 
				
			||||||
      local timer = vim.loop.new_timer()
 | 
					      timer = vim.loop.new_timer()
 | 
				
			||||||
      timer:start(20, 0, function ()
 | 
					      timer:start(20, 0, function ()
 | 
				
			||||||
        -- notify ok (executed later when safe)
 | 
					        -- notify ok (executed later when safe)
 | 
				
			||||||
        vim.rpcnotify(chan, 'nvim_set_var', 'yy', {3, vim.NIL})
 | 
					        vim.rpcnotify(chan, 'nvim_set_var', 'yy', {3, vim.NIL})
 | 
				
			||||||
@@ -505,6 +519,32 @@ describe('lua stdlib', function()
 | 
				
			|||||||
    ]]}
 | 
					    ]]}
 | 
				
			||||||
    feed('<cr>')
 | 
					    feed('<cr>')
 | 
				
			||||||
    eq({3, NIL}, meths.get_var('yy'))
 | 
					    eq({3, NIL}, meths.get_var('yy'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    exec_lua([[timer:close()]])
 | 
				
			||||||
 | 
					  end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('vim.empty_dict()', function()
 | 
				
			||||||
 | 
					    eq({true, false, true, true}, exec_lua([[
 | 
				
			||||||
 | 
					      vim.api.nvim_set_var('listy', {})
 | 
				
			||||||
 | 
					      vim.api.nvim_set_var('dicty', vim.empty_dict())
 | 
				
			||||||
 | 
					      local listy = vim.fn.eval("listy")
 | 
				
			||||||
 | 
					      local dicty = vim.fn.eval("dicty")
 | 
				
			||||||
 | 
					      return {vim.tbl_islist(listy), vim.tbl_islist(dicty), next(listy) == nil, next(dicty) == nil}
 | 
				
			||||||
 | 
					    ]]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    -- vim.empty_dict() gives new value each time
 | 
				
			||||||
 | 
					    -- equality is not overriden (still by ref)
 | 
				
			||||||
 | 
					    -- non-empty table uses the usual heuristics (ignores the tag)
 | 
				
			||||||
 | 
					    eq({false, {"foo"}, {namey="bar"}}, exec_lua([[
 | 
				
			||||||
 | 
					      local aa = vim.empty_dict()
 | 
				
			||||||
 | 
					      local bb = vim.empty_dict()
 | 
				
			||||||
 | 
					      local equally = (aa == bb)
 | 
				
			||||||
 | 
					      aa[1] = "foo"
 | 
				
			||||||
 | 
					      bb["namey"] = "bar"
 | 
				
			||||||
 | 
					      return {equally, aa, bb}
 | 
				
			||||||
 | 
					    ]]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    eq("{ {}, vim.empty_dict() }", exec_lua("return vim.inspect({{}, vim.empty_dict()})"))
 | 
				
			||||||
  end)
 | 
					  end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('vim.validate', function()
 | 
					  it('vim.validate', function()
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user