diff options
Diffstat (limited to 'src/nvim/lua')
-rw-r--r-- | src/nvim/lua/converter.c | 27 | ||||
-rw-r--r-- | src/nvim/lua/converter.h | 9 | ||||
-rw-r--r-- | src/nvim/lua/executor.c | 241 | ||||
-rw-r--r-- | src/nvim/lua/executor.h | 1 | ||||
-rw-r--r-- | src/nvim/lua/treesitter.c | 110 | ||||
-rw-r--r-- | src/nvim/lua/vim.lua | 11 |
6 files changed, 336 insertions, 63 deletions
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 69114c967d..32e804d213 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -19,6 +19,7 @@ #include "nvim/globals.h" #include "nvim/message.h" #include "nvim/eval/typval.h" +#include "nvim/eval/userfunc.h" #include "nvim/ascii.h" #include "nvim/macros.h" @@ -50,6 +51,7 @@ typedef struct { #define LUA_PUSH_STATIC_STRING(lstate, s) \ lua_pushlstring(lstate, s, sizeof(s) - 1) + static LuaTableProps nlua_traverse_table(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { @@ -314,6 +316,13 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) break; } case LUA_TTABLE: { + // Only need to track table refs if we have a metatable associated. + LuaRef table_ref = LUA_NOREF; + if (lua_getmetatable(lstate, -1)) { + lua_pop(lstate, 1); + table_ref = nlua_ref(lstate, -1); + } + const LuaTableProps table_props = nlua_traverse_table(lstate); for (size_t i = 0; i < kv_size(stack); i++) { @@ -329,6 +338,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) case kObjectTypeArray: { cur.tv->v_type = VAR_LIST; cur.tv->vval.v_list = tv_list_alloc((ptrdiff_t)table_props.maxidx); + cur.tv->vval.v_list->lua_table_ref = table_ref; tv_list_ref(cur.tv->vval.v_list); if (table_props.maxidx != 0) { cur.container = true; @@ -342,6 +352,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) cur.tv->v_type = VAR_DICT; cur.tv->vval.v_dict = tv_dict_alloc(); cur.tv->vval.v_dict->dv_refcount++; + cur.tv->vval.v_dict->lua_table_ref = table_ref; } else { cur.special = table_props.has_string_with_nul; if (table_props.has_string_with_nul) { @@ -352,11 +363,13 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) S_LEN("_VAL")); assert(val_di != NULL); cur.tv = &val_di->di_tv; + cur.tv->vval.v_list->lua_table_ref = table_ref; assert(cur.tv->v_type == VAR_LIST); } else { cur.tv->v_type = VAR_DICT; cur.tv->vval.v_dict = tv_dict_alloc(); cur.tv->vval.v_dict->dv_refcount++; + cur.tv->vval.v_dict->lua_table_ref = table_ref; } cur.container = true; cur.idx = lua_gettop(lstate); @@ -384,6 +397,20 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) nlua_pop_typval_table_processing_end: break; } + case LUA_TFUNCTION: { + LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); + state->lua_callable.func_ref = nlua_ref(lstate, -1); + state->lua_callable.table_ref = LUA_NOREF; + + char_u *name = register_cfunc( + &nlua_CFunction_func_call, + &nlua_CFunction_func_free, + state); + + cur.tv->v_type = VAR_FUNC; + cur.tv->vval.v_string = vim_strsave(name); + break; + } case LUA_TUSERDATA: { nlua_pushref(lstate, nlua_nil_ref); bool is_nil = lua_rawequal(lstate, -2, -1); diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h index 542c56ea3e..8601a32418 100644 --- a/src/nvim/lua/converter.h +++ b/src/nvim/lua/converter.h @@ -9,6 +9,15 @@ #include "nvim/func_attr.h" #include "nvim/eval.h" +typedef struct { + LuaRef func_ref; + LuaRef table_ref; +} LuaCallable; + +typedef struct { + LuaCallable lua_callable; +} LuaCFunctionState; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/converter.h.generated.h" #endif diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 327ed6d6b7..9f30609d66 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -35,8 +35,8 @@ #include "nvim/os/os.h" #endif -#include "nvim/lua/executor.h" #include "nvim/lua/converter.h" +#include "nvim/lua/executor.h" #include "nvim/lua/treesitter.h" #include "luv/luv.h" @@ -53,6 +53,15 @@ typedef struct { # include "lua/executor.c.generated.h" #endif +#define PUSH_ALL_TYPVALS(lstate, args, argcount, special) \ + for (int i = 0; i < argcount; i++) { \ + if (args[i].v_type == VAR_UNKNOWN) { \ + lua_pushnil(lstate); \ + } else { \ + nlua_push_typval(lstate, &args[i], special); \ + } \ + } + /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. @@ -700,24 +709,25 @@ int nlua_call(lua_State *lstate) } TRY_WRAP({ - // TODO(bfredl): this should be simplified in error handling refactor - force_abort = false; - suppress_errthrow = false; - current_exception = NULL; - did_emsg = false; - - try_start(); - typval_T rettv; - int dummy; - // call_func() retval is deceptive, ignore it. Instead we set `msg_list` - // (TRY_WRAP) to capture abort-causing non-exception errors. - (void)call_func(name, (int)name_len, &rettv, nargs, - vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, true, NULL, NULL); - if (!try_end(&err)) { - nlua_push_typval(lstate, &rettv, false); - } - tv_clear(&rettv); + // TODO(bfredl): this should be simplified in error handling refactor + force_abort = false; + suppress_errthrow = false; + current_exception = NULL; + did_emsg = false; + + try_start(); + typval_T rettv; + int dummy; + // call_func() retval is deceptive, ignore it. Instead we set `msg_list` + // (TRY_WRAP) to capture abort-causing non-exception errors. + (void)call_func(name, (int)name_len, &rettv, nargs, + vim_args, NULL, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, NULL, NULL); + if (!try_end(&err)) { + nlua_push_typval(lstate, &rettv, false); + } + tv_clear(&rettv); }); free_vim_args: @@ -833,12 +843,25 @@ void executor_free_luaref(LuaRef ref) nlua_unref(lstate, ref); } -/// push a value referenced in the regirstry +/// push a value referenced in the registry void nlua_pushref(lua_State *lstate, LuaRef ref) { lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref); } +/// Gets a new reference to an object stored at original_ref +/// +/// NOTE: It does not copy the value, it creates a new ref to the lua object. +/// Leaves the stack unchanged. +LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref) +{ + nlua_pushref(lstate, original_ref); + LuaRef new_ref = nlua_ref(lstate, -1); + lua_pop(lstate, 1); + + return new_ref; +} + /// Evaluate lua string /// /// Used for luaeval(). @@ -916,13 +939,8 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, return; } - for (int i = 0; i < argcount; i++) { - if (args[i].v_type == VAR_UNKNOWN) { - lua_pushnil(lstate); - } else { - nlua_push_typval(lstate, &args[i], special); - } - } + PUSH_ALL_TYPVALS(lstate, args, argcount, special); + if (lua_pcall(lstate, argcount, ret_tv ? 1 : 0, 0)) { nlua_error(lstate, _("E5108: Error executing lua %.*s")); return; @@ -933,6 +951,51 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, } } +/// Call a LuaCallable given some typvals +/// +/// Used to call any lua callable passed from Lua into VimL +/// +/// @param[in] lstate Lua State +/// @param[in] lua_cb Lua Callable +/// @param[in] argcount Count of typval arguments +/// @param[in] argvars Typval Arguments +/// @param[out] rettv The return value from the called function. +int typval_exec_lua_callable( + lua_State *lstate, + LuaCallable lua_cb, + int argcount, + typval_T *argvars, + typval_T *rettv +) +{ + int offset = 0; + LuaRef cb = lua_cb.func_ref; + + if (cb == LUA_NOREF) { + // This shouldn't happen. + luaL_error(lstate, "Invalid function passed to VimL"); + return ERROR_OTHER; + } + + nlua_pushref(lstate, cb); + + if (lua_cb.table_ref != LUA_NOREF) { + offset += 1; + nlua_pushref(lstate, lua_cb.table_ref); + } + + PUSH_ALL_TYPVALS(lstate, argvars, argcount, false); + + if (lua_pcall(lstate, argcount + offset, 1, 0)) { + luaL_error(lstate, "nlua_CFunction_func_call failed."); + return ERROR_OTHER; + } + + nlua_pop_typval(lstate, rettv); + + return ERROR_NONE; +} + /// Execute Lua string /// /// Used for nvim_exec_lua(). @@ -1128,21 +1191,11 @@ void ex_luafile(exarg_T *const eap) } } -static int create_tslua_parser(lua_State *L) -{ - if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) { - return luaL_error(L, "string expected"); - } - - const char *lang_name = lua_tostring(L, 1); - return tslua_push_parser(L, lang_name); -} - static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { tslua_init(lstate); - lua_pushcfunction(lstate, create_tslua_parser); + lua_pushcfunction(lstate, tslua_push_parser); lua_setfield(lstate, -2, "_create_ts_parser"); lua_pushcfunction(lstate, tslua_add_language); @@ -1290,3 +1343,115 @@ static int regex_match_line(lua_State *lstate) return nret; } + +int nlua_CFunction_func_call( + int argcount, + typval_T *argvars, + typval_T *rettv, + void *state) +{ + lua_State *const lstate = nlua_enter(); + LuaCFunctionState *funcstate = (LuaCFunctionState *)state; + + return typval_exec_lua_callable( + lstate, + funcstate->lua_callable, + argcount, + argvars, + rettv); +} +/// Required functions for lua c functions as VimL callbacks +void nlua_CFunction_func_free(void *state) +{ + lua_State *const lstate = nlua_enter(); + LuaCFunctionState *funcstate = (LuaCFunctionState *)state; + + nlua_unref(lstate, funcstate->lua_callable.func_ref); + nlua_unref(lstate, funcstate->lua_callable.table_ref); + xfree(funcstate); +} + +bool nlua_is_table_from_lua(typval_T *const arg) +{ + if (arg->v_type != VAR_DICT && arg->v_type != VAR_LIST) { + return false; + } + + if (arg->v_type == VAR_DICT) { + return arg->vval.v_dict->lua_table_ref > 0 + && arg->vval.v_dict->lua_table_ref != LUA_NOREF; + } else if (arg->v_type == VAR_LIST) { + return arg->vval.v_list->lua_table_ref > 0 + && arg->vval.v_list->lua_table_ref != LUA_NOREF; + } + + return false; +} + +char_u *nlua_register_table_as_callable(typval_T *const arg) +{ + if (!nlua_is_table_from_lua(arg)) { + return NULL; + } + + LuaRef table_ref; + if (arg->v_type == VAR_DICT) { + table_ref = arg->vval.v_dict->lua_table_ref; + } else if (arg->v_type == VAR_LIST) { + table_ref = arg->vval.v_list->lua_table_ref; + } else { + return NULL; + } + + lua_State *const lstate = nlua_enter(); + + int top = lua_gettop(lstate); + + nlua_pushref(lstate, table_ref); + if (!lua_getmetatable(lstate, -1)) { + return NULL; + } + + lua_getfield(lstate, -1, "__call"); + if (!lua_isfunction(lstate, -1)) { + return NULL; + } + + LuaRef new_table_ref = nlua_newref(lstate, table_ref); + + LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); + state->lua_callable.func_ref = nlua_ref(lstate, -1); + state->lua_callable.table_ref = new_table_ref; + + char_u *name = register_cfunc( + &nlua_CFunction_func_call, + &nlua_CFunction_func_free, + state); + + + lua_pop(lstate, 3); + assert(top == lua_gettop(lstate)); + + return name; +} + +/// Helper function to free a list_T +void nlua_free_typval_list(list_T *const l) +{ + if (l->lua_table_ref != LUA_NOREF && l->lua_table_ref > 0) { + lua_State *const lstate = nlua_enter(); + nlua_unref(lstate, l->lua_table_ref); + l->lua_table_ref = LUA_NOREF; + } +} + + +/// Helper function to free a dict_T +void nlua_free_typval_dict(dict_T *const d) +{ + if (d->lua_table_ref != LUA_NOREF && d->lua_table_ref > 0) { + lua_State *const lstate = nlua_enter(); + nlua_unref(lstate, d->lua_table_ref); + d->lua_table_ref = LUA_NOREF; + } +} diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 3259fc0fa1..6599b44584 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -8,6 +8,7 @@ #include "nvim/func_attr.h" #include "nvim/eval/typval.h" #include "nvim/ex_cmds_defs.h" +#include "nvim/lua/converter.h" // Generated by msgpack-gen.lua void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 51d9549033..138031237e 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -20,6 +20,7 @@ #include "nvim/lua/treesitter.h" #include "nvim/api/private/handle.h" #include "nvim/memline.h" +#include "nvim/buffer.h" typedef struct { TSParser *parser; @@ -41,6 +42,7 @@ static struct luaL_Reg parser_meta[] = { { "parse_buf", parser_parse_buf }, { "edit", parser_edit }, { "tree", parser_tree }, + { "set_included_ranges", parser_set_ranges }, { NULL, NULL } }; @@ -214,8 +216,13 @@ int tslua_inspect_lang(lua_State *L) return 1; } -int tslua_push_parser(lua_State *L, const char *lang_name) +int tslua_push_parser(lua_State *L) { + // Gather language + if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) { + return luaL_error(L, "string expected"); + } + const char *lang_name = lua_tostring(L, 1); TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); if (!lang) { return luaL_error(L, "no such language: %s", lang_name); @@ -300,11 +307,13 @@ static int parser_parse_buf(lua_State *L) } long bufnr = lua_tointeger(L, 2); - void *payload = handle_get_buffer(bufnr); - if (!payload) { + buf_T *buf = handle_get_buffer(bufnr); + + if (!buf) { return luaL_error(L, "invalid buffer handle: %d", bufnr); } - TSInput input = { payload, input_cb, TSInputEncodingUTF8 }; + + TSInput input = { (void *)buf, input_cb, TSInputEncodingUTF8 }; TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); uint32_t n_ranges = 0; @@ -377,6 +386,57 @@ static int parser_edit(lua_State *L) return 0; } +static int parser_set_ranges(lua_State *L) +{ + if (lua_gettop(L) < 2) { + return luaL_error( + L, + "not enough args to parser:set_included_ranges()"); + } + + TSLua_parser *p = parser_check(L); + if (!p || !p->tree) { + return 0; + } + + if (!lua_istable(L, 2)) { + return luaL_error( + L, + "argument for parser:set_included_ranges() should be a table."); + } + + size_t tbl_len = lua_objlen(L, 2); + TSRange *ranges = xmalloc(sizeof(TSRange) * tbl_len); + + + // [ parser, ranges ] + for (size_t index = 0; index < tbl_len; index++) { + lua_rawgeti(L, 2, index + 1); // [ parser, ranges, range ] + + TSNode node; + if (!node_check(L, -1, &node)) { + xfree(ranges); + return luaL_error( + L, + "ranges should be tables of nodes."); + } + lua_pop(L, 1); // [ parser, ranges ] + + ranges[index] = (TSRange) { + .start_point = ts_node_start_point(node), + .end_point = ts_node_end_point(node), + .start_byte = ts_node_start_byte(node), + .end_byte = ts_node_end_byte(node) + }; + } + + // This memcpies ranges, thus we can free it afterwards + ts_parser_set_included_ranges(p->parser, ranges, tbl_len); + xfree(ranges); + + return 0; +} + // Tree methods @@ -459,9 +519,9 @@ static void push_node(lua_State *L, TSNode node, int uindex) lua_setfenv(L, -2); // [udata] } -static bool node_check(lua_State *L, TSNode *res) +static bool node_check(lua_State *L, int index, TSNode *res) { - TSNode *ud = luaL_checkudata(L, 1, "treesitter_node"); + TSNode *ud = luaL_checkudata(L, index, "treesitter_node"); if (ud) { *res = *ud; return true; @@ -473,7 +533,7 @@ static bool node_check(lua_State *L, TSNode *res) static int node_tostring(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } lua_pushstring(L, "<node "); @@ -486,7 +546,7 @@ static int node_tostring(lua_State *L) static int node_eq(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } // This should only be called if both x and y in "x == y" has the @@ -503,7 +563,7 @@ static int node_eq(lua_State *L) static int node_range(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSPoint start = ts_node_start_point(node); @@ -518,7 +578,7 @@ static int node_range(lua_State *L) static int node_start(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSPoint start = ts_node_start_point(node); @@ -532,7 +592,7 @@ static int node_start(lua_State *L) static int node_end(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSPoint end = ts_node_end_point(node); @@ -546,7 +606,7 @@ static int node_end(lua_State *L) static int node_child_count(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } uint32_t count = ts_node_child_count(node); @@ -557,7 +617,7 @@ static int node_child_count(lua_State *L) static int node_named_child_count(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } uint32_t count = ts_node_named_child_count(node); @@ -568,7 +628,7 @@ static int node_named_child_count(lua_State *L) static int node_type(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } lua_pushstring(L, ts_node_type(node)); @@ -578,7 +638,7 @@ static int node_type(lua_State *L) static int node_symbol(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSSymbol symbol = ts_node_symbol(node); @@ -589,7 +649,7 @@ static int node_symbol(lua_State *L) static int node_named(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } lua_pushboolean(L, ts_node_is_named(node)); @@ -599,7 +659,7 @@ static int node_named(lua_State *L) static int node_sexpr(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } char *allocated = ts_node_string(node); @@ -611,7 +671,7 @@ static int node_sexpr(lua_State *L) static int node_missing(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } lua_pushboolean(L, ts_node_is_missing(node)); @@ -621,7 +681,7 @@ static int node_missing(lua_State *L) static int node_has_error(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } lua_pushboolean(L, ts_node_has_error(node)); @@ -631,7 +691,7 @@ static int node_has_error(lua_State *L) static int node_child(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } long num = lua_tointeger(L, 2); @@ -644,7 +704,7 @@ static int node_child(lua_State *L) static int node_named_child(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } long num = lua_tointeger(L, 2); @@ -657,7 +717,7 @@ static int node_named_child(lua_State *L) static int node_descendant_for_range(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSPoint start = { (uint32_t)lua_tointeger(L, 2), @@ -673,7 +733,7 @@ static int node_descendant_for_range(lua_State *L) static int node_named_descendant_for_range(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSPoint start = { (uint32_t)lua_tointeger(L, 2), @@ -689,7 +749,7 @@ static int node_named_descendant_for_range(lua_State *L) static int node_parent(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSNode parent = ts_node_parent(node); @@ -771,7 +831,7 @@ static int query_next_capture(lua_State *L) static int node_rawquery(lua_State *L) { TSNode node; - if (!node_check(L, &node)) { + if (!node_check(L, 1, &node)) { return 0; } TSQuery *query = query_check(L, 2); diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 523d23ec12..820b237c4f 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -255,6 +255,8 @@ function vim.schedule_wrap(cb) end) end +--- <Docs described in |vim.empty_dict()| > +--@private function vim.empty_dict() return setmetatable({}, vim._empty_dict_mt) end @@ -270,6 +272,10 @@ vim.fn = setmetatable({}, { end }) +vim.funcref = function(viml_func_name) + return vim.fn[viml_func_name] +end + -- These are for loading runtime modules lazily since they aren't available in -- the nvim binary as specified in executor.c local function __index(t, key) @@ -286,6 +292,9 @@ local function __index(t, key) elseif key == 'lsp' then t.lsp = require('vim.lsp') return t.lsp + elseif key == 'highlight' then + t.highlight = require('vim.highlight') + return t.highlight end end @@ -462,6 +471,8 @@ end --- Defers calling `fn` until `timeout` ms passes. --- --- Use to do a one-shot timer that calls `fn` +--- Note: The {fn} is |schedule_wrap|ped automatically, so API functions are +--- safe to call. --@param fn Callback to call once `timeout` expires --@param timeout Number of milliseconds to wait before calling `fn` --@return timer luv timer object |