aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjörn Linse <bjorn.linse@gmail.com>2019-10-27 20:49:03 +0100
committerBjörn Linse <bjorn.linse@gmail.com>2019-11-10 13:08:05 +0100
commit474d0bcbf724c7eed740f60391a0ed35d651e1d3 (patch)
tree2a609072157fd975cea1d5f0c4c99ebdd0eca2c3
parent5689008060d29dd7b45157a8f82cb96018c284e8 (diff)
downloadrneovim-474d0bcbf724c7eed740f60391a0ed35d651e1d3.tar.gz
rneovim-474d0bcbf724c7eed740f60391a0ed35d651e1d3.tar.bz2
rneovim-474d0bcbf724c7eed740f60391a0ed35d651e1d3.zip
lua: vim.rpcrequest, vim.rpcnotify, vim.NIL
-rw-r--r--runtime/doc/if_lua.txt23
-rw-r--r--runtime/lua/vim/inspect.lua2
-rw-r--r--src/nvim/lua/converter.c40
-rw-r--r--src/nvim/lua/executor.c83
-rw-r--r--src/nvim/lua/executor.h2
-rw-r--r--src/nvim/main.c1
-rw-r--r--test/functional/lua/utility_functions_spec.lua72
7 files changed, 220 insertions, 3 deletions
diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt
index 97d851a20f..bba43ea32c 100644
--- a/runtime/doc/if_lua.txt
+++ b/runtime/doc/if_lua.txt
@@ -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.
diff --git a/runtime/lua/vim/inspect.lua b/runtime/lua/vim/inspect.lua
index 7cb40ca64d..0f3b908dc1 100644
--- a/runtime/lua/vim/inspect.lua
+++ b/runtime/lua/vim/inspect.lua
@@ -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)
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 844232c64a..44fe60e9c8 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -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,
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index c7ff163f83..5e5cb0cea5 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -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
///
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index 8d356a5600..32f66b629c 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -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); \
diff --git a/src/nvim/main.c b/src/nvim/main.c
index e0a1e60fc0..e39eec4038 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -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
diff --git a/test/functional/lua/utility_functions_spec.lua b/test/functional/lua/utility_functions_spec.lua
index 6aeea5fc4f..6631aa8e4c 100644
--- a/test/functional/lua/utility_functions_spec.lua
+++ b/test/functional/lua/utility_functions_spec.lua
@@ -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)