Merge pull request #11310 from bfredl/luarpc
lua: add vim.rpcrequest, vim.rpcnotify and vim.NIL
This commit is contained in:
commit
3a075ce3dc
|
@ -587,6 +587,26 @@ vim.in_fast_event() *vim.in_fast_event()*
|
|||
for input. When this is `false` most API functions are callable (but
|
||||
may be subject to other restrictions such as |textlock|).
|
||||
|
||||
vim.NIL *vim.NIL*
|
||||
Special value used to represent NIL in msgpack-rpc and |v:null| in
|
||||
vimL interaction, and similar cases. Lua `nil` cannot be used as
|
||||
part of a lua table representing a Dictionary or Array, as it
|
||||
is equivalent to a missing value: `{"foo", nil}` is the same as
|
||||
`{"foo"}`
|
||||
|
||||
vim.rpcnotify({channel}, {method}[, {args}...]) *vim.rpcnotify()*
|
||||
Sends {event} to {channel} via |RPC| and returns immediately.
|
||||
If {channel} is 0, the event is broadcast to all channels.
|
||||
|
||||
This function also works in a fast callback |lua-loop-callbacks|.
|
||||
|
||||
vim.rpcrequest({channel}, {method}[, {args}...]) *vim.rpcrequest()*
|
||||
Sends a request to {channel} to invoke {method} via
|
||||
|RPC| and blocks until a response is received.
|
||||
|
||||
Note: NIL values as part of the return value is represented as
|
||||
|vim.NIL| special value
|
||||
|
||||
vim.stricmp({a}, {b}) *vim.stricmp()*
|
||||
Compares strings case-insensitively. Returns 0, 1 or -1 if strings
|
||||
are equal, {a} is greater than {b} or {a} is lesser than {b},
|
||||
|
@ -624,6 +644,9 @@ vim.fn.{func}({...}) *vim.fn*
|
|||
be represented directly as a Lua number. Empty lists and dictionaries
|
||||
both are represented by an empty table.
|
||||
|
||||
Note: |v:null| values as part of the return value is represented as
|
||||
|vim.NIL| special value
|
||||
|
||||
Note: vim.fn keys are generated lazily, thus `pairs(vim.fn)` only
|
||||
enumerates functions that were called at least once.
|
||||
|
||||
|
|
|
@ -289,7 +289,7 @@ function Inspector:putValue(v)
|
|||
if tv == 'string' then
|
||||
self:puts(smartQuote(escape(v)))
|
||||
elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or
|
||||
tv == 'cdata' or tv == 'ctype' then
|
||||
tv == 'cdata' or tv == 'ctype' or (vim and v == vim.NIL) then
|
||||
self:puts(tostring(v))
|
||||
elseif tv == 'table' then
|
||||
self:putTable(v)
|
||||
|
|
|
@ -377,6 +377,19 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
|
|||
nlua_pop_typval_table_processing_end:
|
||||
break;
|
||||
}
|
||||
case LUA_TUSERDATA: {
|
||||
nlua_pushref(lstate, nlua_nil_ref);
|
||||
bool is_nil = lua_rawequal(lstate, -2, -1);
|
||||
lua_pop(lstate, 1);
|
||||
if (is_nil) {
|
||||
cur.tv->v_type = VAR_SPECIAL;
|
||||
cur.tv->vval.v_special = kSpecialVarNull;
|
||||
} else {
|
||||
EMSG(_("E5101: Cannot convert given lua type"));
|
||||
ret = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
EMSG(_("E5101: Cannot convert given lua type"));
|
||||
ret = false;
|
||||
|
@ -406,7 +419,13 @@ static bool typval_conv_special = false;
|
|||
#define TYPVAL_ENCODE_ALLOW_SPECIALS true
|
||||
|
||||
#define TYPVAL_ENCODE_CONV_NIL(tv) \
|
||||
lua_pushnil(lstate)
|
||||
do { \
|
||||
if (typval_conv_special) { \
|
||||
lua_pushnil(lstate); \
|
||||
} else { \
|
||||
nlua_pushref(lstate, nlua_nil_ref); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
|
||||
lua_pushboolean(lstate, (bool)(num))
|
||||
|
@ -718,7 +737,11 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special)
|
|||
{
|
||||
switch (obj.type) {
|
||||
case kObjectTypeNil: {
|
||||
lua_pushnil(lstate);
|
||||
if (special) {
|
||||
lua_pushnil(lstate);
|
||||
} else {
|
||||
nlua_pushref(lstate, nlua_nil_ref);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kObjectTypeLuaRef: {
|
||||
|
@ -1152,6 +1175,19 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
|
|||
break;
|
||||
}
|
||||
|
||||
case LUA_TUSERDATA: {
|
||||
nlua_pushref(lstate, nlua_nil_ref);
|
||||
bool is_nil = lua_rawequal(lstate, -2, -1);
|
||||
lua_pop(lstate, 1);
|
||||
if (is_nil) {
|
||||
*cur.obj = NIL;
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"Cannot convert userdata");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
type_error:
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/api/vim.h"
|
||||
#include "nvim/msgpack_rpc/channel.h"
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/ex_getln.h"
|
||||
#include "nvim/ex_cmds2.h"
|
||||
|
@ -299,6 +300,14 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
|
|||
lua_pushcfunction(lstate, &nlua_call);
|
||||
lua_setfield(lstate, -2, "call");
|
||||
|
||||
// rpcrequest
|
||||
lua_pushcfunction(lstate, &nlua_rpcrequest);
|
||||
lua_setfield(lstate, -2, "rpcrequest");
|
||||
|
||||
// rpcnotify
|
||||
lua_pushcfunction(lstate, &nlua_rpcnotify);
|
||||
lua_setfield(lstate, -2, "rpcnotify");
|
||||
|
||||
// vim.loop
|
||||
luv_set_loop(lstate, &main_loop.uv);
|
||||
luv_set_callback(lstate, nlua_luv_cfpcall);
|
||||
|
@ -314,6 +323,15 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
|
|||
lua_setfield(lstate, -2, "luv");
|
||||
lua_pop(lstate, 3);
|
||||
|
||||
// vim.NIL
|
||||
lua_newuserdata(lstate, 0);
|
||||
lua_createtable(lstate, 0, 0);
|
||||
lua_pushcfunction(lstate, &nlua_nil_tostring);
|
||||
lua_setfield(lstate, -2, "__tostring");
|
||||
lua_setmetatable(lstate, -2);
|
||||
nlua_nil_ref = nlua_ref(lstate, -1);
|
||||
lua_setfield(lstate, -2, "NIL");
|
||||
|
||||
// internal vim._treesitter... API
|
||||
nlua_add_treesitter(lstate);
|
||||
|
||||
|
@ -547,6 +565,10 @@ int nlua_call(lua_State *lstate)
|
|||
Error err = ERROR_INIT;
|
||||
size_t name_len;
|
||||
const char_u *name = (const char_u *)luaL_checklstring(lstate, 1, &name_len);
|
||||
if (!nlua_is_deferred_safe(lstate)) {
|
||||
return luaL_error(lstate, e_luv_api_disabled, "vimL function");
|
||||
}
|
||||
|
||||
int nargs = lua_gettop(lstate)-1;
|
||||
if (nargs > MAX_FUNC_ARGS) {
|
||||
return luaL_error(lstate, "Function called with too many arguments");
|
||||
|
@ -596,6 +618,67 @@ free_vim_args:
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int nlua_rpcrequest(lua_State *lstate)
|
||||
{
|
||||
if (!nlua_is_deferred_safe(lstate)) {
|
||||
return luaL_error(lstate, e_luv_api_disabled, "rpcrequest");
|
||||
}
|
||||
return nlua_rpc(lstate, true);
|
||||
}
|
||||
|
||||
static int nlua_rpcnotify(lua_State *lstate)
|
||||
{
|
||||
return nlua_rpc(lstate, false);
|
||||
}
|
||||
|
||||
static int nlua_rpc(lua_State *lstate, bool request)
|
||||
{
|
||||
size_t name_len;
|
||||
uint64_t chan_id = (uint64_t)luaL_checkinteger(lstate, 1);
|
||||
const char *name = luaL_checklstring(lstate, 2, &name_len);
|
||||
int nargs = lua_gettop(lstate)-2;
|
||||
Error err = ERROR_INIT;
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
|
||||
for (int i = 0; i < nargs; i++) {
|
||||
lua_pushvalue(lstate, (int)i+3);
|
||||
ADD(args, nlua_pop_Object(lstate, false, &err));
|
||||
if (ERROR_SET(&err)) {
|
||||
api_free_array(args);
|
||||
goto check_err;
|
||||
}
|
||||
}
|
||||
|
||||
if (request) {
|
||||
Object result = rpc_send_call(chan_id, name, args, &err);
|
||||
if (!ERROR_SET(&err)) {
|
||||
nlua_push_Object(lstate, result, false);
|
||||
api_free_object(result);
|
||||
}
|
||||
} else {
|
||||
if (!rpc_send_event(chan_id, name, args)) {
|
||||
api_set_error(&err, kErrorTypeValidation,
|
||||
"Invalid channel: %"PRIu64, chan_id);
|
||||
}
|
||||
}
|
||||
|
||||
check_err:
|
||||
if (ERROR_SET(&err)) {
|
||||
lua_pushstring(lstate, err.msg);
|
||||
api_clear_error(&err);
|
||||
return lua_error(lstate);
|
||||
}
|
||||
|
||||
return request ? 1 : 0;
|
||||
}
|
||||
|
||||
static int nlua_nil_tostring(lua_State *lstate)
|
||||
{
|
||||
lua_pushstring(lstate, "vim.NIL");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
#ifdef WIN32
|
||||
/// os.getenv: override os.getenv to maintain coherency. #9681
|
||||
///
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
// Generated by msgpack-gen.lua
|
||||
void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
|
||||
|
||||
EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF);
|
||||
|
||||
#define set_api_error(s, err) \
|
||||
do { \
|
||||
Error *err_ = (err); \
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "nvim/highlight.h"
|
||||
#include "nvim/iconv.h"
|
||||
#include "nvim/if_cscope.h"
|
||||
#include "nvim/lua/executor.h"
|
||||
#ifdef HAVE_LOCALE_H
|
||||
# include <locale.h>
|
||||
#endif
|
||||
|
|
|
@ -3,6 +3,8 @@ local helpers = require('test.functional.helpers')(after_each)
|
|||
local Screen = require('test.functional.ui.screen')
|
||||
|
||||
local funcs = helpers.funcs
|
||||
local meths = helpers.meths
|
||||
local command = helpers.command
|
||||
local clear = helpers.clear
|
||||
local eq = helpers.eq
|
||||
local eval = helpers.eval
|
||||
|
@ -11,6 +13,7 @@ local pcall_err = helpers.pcall_err
|
|||
local exec_lua = helpers.exec_lua
|
||||
local matches = helpers.matches
|
||||
local source = helpers.source
|
||||
local NIL = helpers.NIL
|
||||
|
||||
before_each(clear)
|
||||
|
||||
|
@ -315,6 +318,9 @@ describe('lua stdlib', function()
|
|||
func! VarArg(...)
|
||||
return a:000
|
||||
endfunc
|
||||
func! Nilly()
|
||||
return [v:null, v:null]
|
||||
endfunc
|
||||
]])
|
||||
eq(true, exec_lua([[return next(vim.fn.FooFunc(3)) == nil ]]))
|
||||
eq(3, eval("g:test"))
|
||||
|
@ -324,7 +330,73 @@ describe('lua stdlib', function()
|
|||
|
||||
eq({2, "foo", true}, exec_lua([[return vim.fn.VarArg(2, "foo", true)]]))
|
||||
|
||||
eq(true, exec_lua([[
|
||||
local x = vim.fn.Nilly()
|
||||
return #x == 2 and x[1] == vim.NIL and x[2] == vim.NIL
|
||||
]]))
|
||||
eq({NIL, NIL}, exec_lua([[return vim.fn.Nilly()]]))
|
||||
|
||||
-- error handling
|
||||
eq({false, 'Vim:E714: List required'}, exec_lua([[return {pcall(vim.fn.add, "aa", "bb")}]]))
|
||||
end)
|
||||
|
||||
it('vim.rpcrequest and vim.rpcnotify', function()
|
||||
exec_lua([[
|
||||
chan = vim.fn.jobstart({'cat'}, {rpc=true})
|
||||
vim.rpcrequest(chan, 'nvim_set_current_line', 'meow')
|
||||
]])
|
||||
eq('meow', meths.get_current_line())
|
||||
command("let x = [3, 'aa', v:true, v:null]")
|
||||
eq(true, exec_lua([[
|
||||
ret = vim.rpcrequest(chan, 'nvim_get_var', 'x')
|
||||
return #ret == 4 and ret[1] == 3 and ret[2] == 'aa' and ret[3] == true and ret[4] == vim.NIL
|
||||
]]))
|
||||
eq({3, 'aa', true, NIL}, exec_lua([[return ret]]))
|
||||
|
||||
-- error handling
|
||||
eq({false, 'Invalid channel: 23'},
|
||||
exec_lua([[return {pcall(vim.rpcrequest, 23, 'foo')}]]))
|
||||
eq({false, 'Invalid channel: 23'},
|
||||
exec_lua([[return {pcall(vim.rpcnotify, 23, 'foo')}]]))
|
||||
|
||||
eq({false, 'Vim:E121: Undefined variable: foobar'},
|
||||
exec_lua([[return {pcall(vim.rpcrequest, chan, 'nvim_eval', "foobar")}]]))
|
||||
|
||||
|
||||
-- rpcnotify doesn't wait on request
|
||||
eq('meow', exec_lua([[
|
||||
vim.rpcnotify(chan, 'nvim_set_current_line', 'foo')
|
||||
return vim.api.nvim_get_current_line()
|
||||
]]))
|
||||
eq('foo', meths.get_current_line())
|
||||
|
||||
local screen = Screen.new(50,7)
|
||||
screen:set_default_attr_ids({
|
||||
[1] = {bold = true, foreground = Screen.colors.Blue1},
|
||||
[2] = {bold = true, reverse = true},
|
||||
[3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
|
||||
[4] = {bold = true, foreground = Screen.colors.SeaGreen4},
|
||||
})
|
||||
screen:attach()
|
||||
exec_lua([[
|
||||
local timer = vim.loop.new_timer()
|
||||
timer:start(20, 0, function ()
|
||||
-- notify ok (executed later when safe)
|
||||
vim.rpcnotify(chan, 'nvim_set_var', 'yy', {3, vim.NIL})
|
||||
-- rpcrequest an error
|
||||
vim.rpcrequest(chan, 'nvim_set_current_line', 'bork')
|
||||
end)
|
||||
]])
|
||||
screen:expect{grid=[[
|
||||
foo |
|
||||
{1:~ }|
|
||||
{2: }|
|
||||
{3:Error executing luv callback:} |
|
||||
{3:[string "<nvim>"]:6: E5560: rpcrequest must not be}|
|
||||
{3: called in a lua loop callback} |
|
||||
{4:Press ENTER or type command to continue}^ |
|
||||
]]}
|
||||
feed('<cr>')
|
||||
eq({3, NIL}, meths.get_var('yy'))
|
||||
end)
|
||||
end)
|
||||
|
|
Loading…
Reference in New Issue