mirror of
https://github.com/neovim/neovim.git
synced 2024-12-24 13:15:09 -07:00
lua: metatable for empty dict value
This commit is contained in:
parent
a251b588ac
commit
ea4127e9a7
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user