diff options
author | Björn Linse <bjorn.linse@gmail.com> | 2019-10-27 20:49:03 +0100 |
---|---|---|
committer | Björn Linse <bjorn.linse@gmail.com> | 2019-11-10 13:08:05 +0100 |
commit | 474d0bcbf724c7eed740f60391a0ed35d651e1d3 (patch) | |
tree | 2a609072157fd975cea1d5f0c4c99ebdd0eca2c3 | |
parent | 5689008060d29dd7b45157a8f82cb96018c284e8 (diff) | |
download | rneovim-474d0bcbf724c7eed740f60391a0ed35d651e1d3.tar.gz rneovim-474d0bcbf724c7eed740f60391a0ed35d651e1d3.tar.bz2 rneovim-474d0bcbf724c7eed740f60391a0ed35d651e1d3.zip |
lua: vim.rpcrequest, vim.rpcnotify, vim.NIL
-rw-r--r-- | runtime/doc/if_lua.txt | 23 | ||||
-rw-r--r-- | runtime/lua/vim/inspect.lua | 2 | ||||
-rw-r--r-- | src/nvim/lua/converter.c | 40 | ||||
-rw-r--r-- | src/nvim/lua/executor.c | 83 | ||||
-rw-r--r-- | src/nvim/lua/executor.h | 2 | ||||
-rw-r--r-- | src/nvim/main.c | 1 | ||||
-rw-r--r-- | test/functional/lua/utility_functions_spec.lua | 72 |
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) |