From 0571145c40b0a2ae106acc43891c2680c76b8383 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 25 Sep 2019 09:15:33 +0200 Subject: paste: fix handling of "<" in cmdline (#11094) Fixes https://github.com/neovim/neovim/issues/11088. --- src/nvim/lua/vim.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index a03e97490d..b1a684b977 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -189,7 +189,7 @@ paste = (function() if mode == 'c' and not got_line1 then -- cmdline-mode: paste only 1 line. got_line1 = (#lines > 1) vim.api.nvim_set_option('paste', true) -- For nvim_input(). - local line1, _ = string.gsub(lines[1], '[\r\n\012\027]', ' ') -- Scrub. + local line1 = lines[1]:gsub('<', ''):gsub('[\r\n\012\027]', ' ') -- Scrub. vim.api.nvim_input(line1) vim.api.nvim_set_option('paste', false) elseif mode ~= 'c' then -- Else: discard remaining cmdline-mode chunks. -- cgit From cd100963866b2c33a286cbf6aac8e42cd16fd248 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Mon, 29 Oct 2018 19:11:41 +0100 Subject: tree-sitter: initial tree-sitter support --- src/nvim/lua/executor.c | 42 ++++ src/nvim/lua/tree_sitter.c | 472 +++++++++++++++++++++++++++++++++++++++++++++ src/nvim/lua/tree_sitter.h | 10 + 3 files changed, 524 insertions(+) create mode 100644 src/nvim/lua/tree_sitter.c create mode 100644 src/nvim/lua/tree_sitter.h (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index f51aa3c6d4..8b0140f794 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -31,6 +31,7 @@ #include "nvim/lua/executor.h" #include "nvim/lua/converter.h" +#include "nvim/lua/tree_sitter.h" #include "luv/luv.h" @@ -310,7 +311,11 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setfield(lstate, -2, "luv"); lua_pop(lstate, 3); + // internal vim._treesitter... API + nlua_add_treesitter(lstate); + lua_setglobal(lstate, "vim"); + return 0; } @@ -816,3 +821,40 @@ void ex_luafile(exarg_T *const eap) return; } } + +static int unsafe_ptr_to_ts_tree(lua_State *L) +{ + if (!lua_gettop(L)) { + return 0; + } + TSTree *const *ptr = lua_topointer(L,1); + tslua_push_tree(L, *ptr); + return 1; +} + +static int create_tslua_parser(lua_State *L) +{ + TSLanguage *tree_sitter_c(void), *tree_sitter_javascript(void); + + if (!lua_gettop(L)) { + return 0; + } + char *str = lua_tostring(L,1); + + TSLanguage *lang = tree_sitter_c(); + if (str && striequal(str, "javascript")) { + lang = tree_sitter_javascript(); + } + tslua_push_parser(L, lang); + return 1; +} + +static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +{ + tslua_init(lstate); + lua_pushcfunction(lstate, unsafe_ptr_to_ts_tree); + lua_setfield(lstate, -2, "unsafe_ts_tree"); + + lua_pushcfunction(lstate, create_tslua_parser); + lua_setfield(lstate, -2, "ts_parser"); +} diff --git a/src/nvim/lua/tree_sitter.c b/src/nvim/lua/tree_sitter.c new file mode 100644 index 0000000000..1ecb2bcac4 --- /dev/null +++ b/src/nvim/lua/tree_sitter.c @@ -0,0 +1,472 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// lua bindings for tree-siter. +// NB: this file should contain a generic lua interface for +// tree-sitter trees and nodes, and could be broken out as a reusable library + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "tree_sitter/api.h" + +// NOT state-safe, delete when GC is confimed working: +static int debug_n_trees = 0, debug_n_cursors = 0; + +#define REG_KEY "tree_sitter-private" + +#include "nvim/lua/tree_sitter.h" +#include "nvim/api/private/handle.h" +#include "nvim/memline.h" + +typedef struct { + TSParser *parser; + TSTree *tree; +} Tslua_parser; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/tree_sitter.c.generated.h" +#endif + +static struct luaL_Reg parser_meta[] = { + {"__gc", parser_gc}, + {"__tostring", parser_tostring}, + {"parse_buf", parser_parse_buf}, + {"edit", parser_edit}, + {"tree", parser_tree}, + {NULL, NULL} +}; + +static struct luaL_Reg tree_meta[] = { + {"__gc", tree_gc}, + {"__tostring", tree_tostring}, + {"root", tree_root}, + {NULL, NULL} +}; + +static struct luaL_Reg node_meta[] = { + {"__tostring", node_tostring}, + {"__len", node_child_count}, + {"range", node_range}, + {"start", node_start}, + {"type", node_type}, + {"symbol", node_symbol}, + {"child_count", node_child_count}, + {"child", node_child}, + {"descendant_for_point_range", node_descendant_for_point_range}, + {"parent", node_parent}, + {NULL, NULL} +}; + +void build_meta(lua_State *L, const luaL_Reg *meta) +{ + // [env, target] + for (size_t i = 0; meta[i].name != NULL; i++) { + lua_pushcfunction(L, meta[i].func); // [env, target, func] + lua_pushvalue(L, -3); // [env, target, func, env] + lua_setfenv(L, -2); // [env, target, func] + lua_setfield(L, -2, meta[i].name); // [env, target] + } + + lua_pushvalue(L, -1); // [env, target, target] + lua_setfield(L, -2, "__index"); // [env, target] +} + + + +/// init the tslua library +/// +/// all global state is stored in the regirstry of the lua_State +void tslua_init(lua_State *L) +{ + lua_createtable(L, 0, 0); + + // type metatables + lua_createtable(L, 0, 0); + build_meta(L, parser_meta); + lua_setfield(L, -2, "parser-meta"); + + lua_createtable(L, 0, 0); + build_meta(L, tree_meta); + lua_setfield(L, -2, "tree-meta"); + + lua_createtable(L, 0, 0); + build_meta(L, node_meta); + lua_setfield(L, -2, "node-meta"); + + lua_setfield(L, LUA_REGISTRYINDEX, REG_KEY); + + lua_pushcfunction(L, tslua_debug); + lua_setglobal(L, "_tslua_debug"); +} + +static int tslua_debug(lua_State *L) +{ + lua_pushinteger(L, debug_n_trees); + lua_pushinteger(L, debug_n_cursors); + return 2; +} + +void tslua_push_parser(lua_State *L, TSLanguage *lang) +{ + TSParser *parser = ts_parser_new(); + ts_parser_set_language(parser, lang); + Tslua_parser *p = lua_newuserdata(L, sizeof(Tslua_parser)); // [udata] + p->parser = parser; + p->tree = NULL; + + lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] + lua_getfield(L, -1, "parser-meta"); // [udata, env, meta] + lua_setmetatable(L, -3); // [udata, env] + lua_pop(L, 1); // [udata] +} + +static Tslua_parser *parser_check(lua_State *L) +{ + if (!lua_gettop(L)) { + return 0; + } + if (!lua_isuserdata(L, 1)) { + return 0; + } + // TODO: typecheck! + return lua_touserdata(L, 1); +} + +static int parser_gc(lua_State *L) +{ + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + ts_parser_delete(p->parser); + if (p->tree) { + ts_tree_delete(p->tree); + } + + return 0; +} + +static int parser_tostring(lua_State *L) +{ + lua_pushstring(L, ""); + return 1; +} + +static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position, uint32_t *bytes_read) +{ + buf_T *bp = payload; + static char buf[200]; + if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { + *bytes_read = 0; + return ""; + } + char_u *line = ml_get_buf(bp, position.row+1, false); + size_t len = STRLEN(line); + size_t tocopy = MIN(len-position.column,200); + + // TODO: translate embedded \n to \000 + memcpy(buf, line+position.column, tocopy); + *bytes_read = (uint32_t)tocopy; + if (tocopy < 200) { + buf[tocopy] = '\n'; + (*bytes_read)++; + } + return buf; +} + +static int parser_parse_buf(lua_State *L) +{ + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + long bufnr = lua_tointeger(L, 2); + void *payload = handle_get_buffer(bufnr); + TSInput input = {payload, input_cb, TSInputEncodingUTF8}; + TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); + if (p->tree) { + ts_tree_delete(p->tree); + } + p->tree = new_tree; + + tslua_push_tree(L, ts_tree_copy(p->tree)); + return 1; +} + +static int parser_tree(lua_State *L) +{ + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + if (p->tree) { + tslua_push_tree(L, ts_tree_copy(p->tree)); + } else { + lua_pushnil(L); + } + return 1; +} + +static int parser_edit(lua_State *L) +{ + if(lua_gettop(L) < 10) { + lua_pushstring(L, "not enough args to parser:edit()"); + lua_error(L); + return 0; // unreachable + } + + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + if (!p->tree) { + return 0; + } + + long start_byte = lua_tointeger(L, 2); + long old_end_byte = lua_tointeger(L, 3); + long new_end_byte = lua_tointeger(L, 4); + TSPoint start_point = { lua_tointeger(L, 5), lua_tointeger(L, 6) }; + TSPoint old_end_point = { lua_tointeger(L, 7), lua_tointeger(L, 8) }; + TSPoint new_end_point = { lua_tointeger(L, 9), lua_tointeger(L, 10) }; + + TSInputEdit edit = { start_byte, old_end_byte, new_end_byte, + start_point, old_end_point, new_end_point }; + + ts_tree_edit(p->tree, &edit); + + return 0; +} + + +// Tree methods + +/// push tree interface on lua stack. +/// +/// This takes "ownership" of the tree and will free it +/// when the wrapper object is garbage collected +void tslua_push_tree(lua_State *L, TSTree *tree) +{ + TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] + *ud = tree; + lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] + lua_getfield(L, -1, "tree-meta"); // [udata, env, meta] + lua_setmetatable(L, -3); // [udata, env] + lua_pop(L, 1); // [udata] + + // table used for node wrappers to keep a reference to tree wrapper + // NB: in lua 5.3 the uservalue for the node could just be the tree, but + // in lua 5.1 the uservalue (fenv) must be a table. + lua_createtable(L, 1, 0); // [udata, reftable] + lua_pushvalue(L, -2); // [udata, reftable, udata] + lua_rawseti(L, -2, 1); // [udata, reftable] + lua_setfenv(L, -2); // [udata] + debug_n_trees++; +} + +static TSTree *tree_check(lua_State *L) +{ + if (!lua_gettop(L)) { + return 0; + } + if (!lua_isuserdata(L, 1)) { + return 0; + } + // TODO: typecheck! + TSTree **ud = lua_touserdata(L, 1); + return *ud; +} + +static int tree_gc(lua_State *L) +{ + TSTree *tree = tree_check(L); + if (!tree) { + return 0; + } + + ts_tree_delete(tree); + debug_n_trees--; + return 0; +} + +static int tree_tostring(lua_State *L) +{ + lua_pushstring(L, ""); + return 1; +} + +static int tree_root(lua_State *L) +{ + TSTree *tree = tree_check(L); + if (!tree) { + return 0; + } + TSNode root = ts_tree_root_node(tree); + push_node(L, root); + return 1; +} + +// Node methods + +/// push node interface on lua stack +/// +/// top of stack must either be the tree this node belongs to or another node +/// of the same tree! This value is not popped. Can only be called inside a +/// cfunction with the tslua environment. +static void push_node(lua_State *L, TSNode node) +{ + if (ts_node_is_null(node)) { + lua_pushnil(L); // [src, nil] + return; + } + TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata] + *ud = node; + lua_getfield(L, LUA_ENVIRONINDEX, "node-meta"); // [src, udata, meta] + lua_setmetatable(L, -2); // [src, udata] + lua_getfenv(L, -2); // [src, udata, reftable] + lua_setfenv(L, -2); // [src, udata] +} + +static bool node_check(lua_State *L, TSNode *res) +{ + if (!lua_gettop(L)) { + return 0; + } + if (!lua_isuserdata(L, 1)) { + return 0; + } + // TODO: typecheck! + TSNode *ud = lua_touserdata(L, 1); + *res = *ud; + return true; +} + + +static int node_tostring(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushstring(L, ""); + lua_concat(L, 3); + return 1; +} + +static int node_range(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = ts_node_start_point(node); + TSPoint end = ts_node_end_point(node); + lua_pushnumber(L, start.row); + lua_pushnumber(L, start.column); + lua_pushnumber(L, end.row); + lua_pushnumber(L, end.column); + return 4; +} + +static int node_start(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = ts_node_start_point(node); + uint32_t start_byte = ts_node_start_byte(node); + lua_pushnumber(L, start.row); + lua_pushnumber(L, start.column); + lua_pushnumber(L, start_byte); + return 3; +} + +static int node_child_count(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + uint32_t count = ts_node_child_count(node); + lua_pushnumber(L, count); + return 1; +} + +static int node_type(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushstring(L, ts_node_type(node)); + return 1; +} + +static int node_symbol(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSSymbol symbol = ts_node_symbol(node); + lua_pushnumber(L, symbol); + return 1; +} + +static int node_child(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + long num = lua_tointeger(L, 2); + TSNode child = ts_node_child(node, (uint32_t)num); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + +static int node_descendant_for_point_range(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = {(uint32_t)lua_tointeger(L, 2), + (uint32_t)lua_tointeger(L, 3)}; + TSPoint end = {(uint32_t)lua_tointeger(L, 4), + (uint32_t)lua_tointeger(L, 5)}; + TSNode child = ts_node_descendant_for_point_range(node, start, end); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + +static int node_parent(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSNode parent = ts_node_parent(node); + push_node(L, parent); + return 1; +} + diff --git a/src/nvim/lua/tree_sitter.h b/src/nvim/lua/tree_sitter.h new file mode 100644 index 0000000000..2ae0ec8371 --- /dev/null +++ b/src/nvim/lua/tree_sitter.h @@ -0,0 +1,10 @@ +#ifndef NVIM_LUA_TREE_SITTER_H +#define NVIM_LUA_TREE_SITTER_H + +#include "tree_sitter/api.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/tree_sitter.h.generated.h" +#endif + +#endif // NVIM_LUA_TREE_SITTER_H -- cgit From 0e0beef85e4d3932e0d49528d8474794f7b69b01 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Thu, 6 Jun 2019 12:20:07 +0200 Subject: tree-sitter: load parsers as .so files --- src/nvim/lua/executor.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 8b0140f794..1794cee8d8 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -834,16 +834,31 @@ static int unsafe_ptr_to_ts_tree(lua_State *L) static int create_tslua_parser(lua_State *L) { - TSLanguage *tree_sitter_c(void), *tree_sitter_javascript(void); - - if (!lua_gettop(L)) { + if (lua_gettop(L) < 2) { return 0; } - char *str = lua_tostring(L,1); + const char *path = lua_tostring(L,1); + const char *lang_name = lua_tostring(L,2); + + // TODO: unsafe! + char symbol_buf[128] = "tree_sitter_"; + STRCAT(symbol_buf, lang_name); + + // TODO: we should maybe keep the uv_lib_t around, and close them + // at exit, to keep LeakSanitizer happy. + uv_lib_t lib; + if (uv_dlopen(path, &lib)) { + return luaL_error(L, "uv_dlopen: %s", uv_dlerror(&lib)); + } + + TSLanguage *(*lang_parser)(void); + if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { + return luaL_error(L, "uv_dlsym: %s", uv_dlerror(&lib)); + } - TSLanguage *lang = tree_sitter_c(); - if (str && striequal(str, "javascript")) { - lang = tree_sitter_javascript(); + TSLanguage *lang = lang_parser(); + if (lang == NULL) { + return luaL_error(L, "failed to load parser"); } tslua_push_parser(L, lang); return 1; -- cgit From 1e9e2451bef21ff705e677802d1b0980356f1f86 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Fri, 7 Jun 2019 18:19:59 +0200 Subject: tree-sitter: objectify API --- src/nvim/lua/executor.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 1794cee8d8..a6447ebb2b 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -822,16 +822,6 @@ void ex_luafile(exarg_T *const eap) } } -static int unsafe_ptr_to_ts_tree(lua_State *L) -{ - if (!lua_gettop(L)) { - return 0; - } - TSTree *const *ptr = lua_topointer(L,1); - tslua_push_tree(L, *ptr); - return 1; -} - static int create_tslua_parser(lua_State *L) { if (lua_gettop(L) < 2) { @@ -867,9 +857,7 @@ static int create_tslua_parser(lua_State *L) static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { tslua_init(lstate); - lua_pushcfunction(lstate, unsafe_ptr_to_ts_tree); - lua_setfield(lstate, -2, "unsafe_ts_tree"); lua_pushcfunction(lstate, create_tslua_parser); - lua_setfield(lstate, -2, "ts_parser"); + lua_setfield(lstate, -2, "_create_ts_parser"); } -- cgit From afba23099fccc929fd0319a9a965a7b727407c7a Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 8 Jun 2019 15:51:38 +0200 Subject: tree-sitter: support pre-registration of languages --- src/nvim/lua/executor.c | 32 ++++++--------------------- src/nvim/lua/tree_sitter.c | 54 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 26 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index a6447ebb2b..ae53bfce6a 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -824,34 +824,13 @@ void ex_luafile(exarg_T *const eap) static int create_tslua_parser(lua_State *L) { - if (lua_gettop(L) < 2) { - return 0; + if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) { + return luaL_error(L, "string expected"); } - const char *path = lua_tostring(L,1); - const char *lang_name = lua_tostring(L,2); - // TODO: unsafe! - char symbol_buf[128] = "tree_sitter_"; - STRCAT(symbol_buf, lang_name); + const char *lang_name = lua_tostring(L,1); - // TODO: we should maybe keep the uv_lib_t around, and close them - // at exit, to keep LeakSanitizer happy. - uv_lib_t lib; - if (uv_dlopen(path, &lib)) { - return luaL_error(L, "uv_dlopen: %s", uv_dlerror(&lib)); - } - - TSLanguage *(*lang_parser)(void); - if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { - return luaL_error(L, "uv_dlsym: %s", uv_dlerror(&lib)); - } - - TSLanguage *lang = lang_parser(); - if (lang == NULL) { - return luaL_error(L, "failed to load parser"); - } - tslua_push_parser(L, lang); - return 1; + return tslua_push_parser(L, lang_name); } static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL @@ -860,4 +839,7 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, create_tslua_parser); lua_setfield(lstate, -2, "_create_ts_parser"); + + lua_pushcfunction(lstate, ts_lua_register_lang); + lua_setfield(lstate, -2, "ts_add_language"); } diff --git a/src/nvim/lua/tree_sitter.c b/src/nvim/lua/tree_sitter.c index 1ecb2bcac4..f992639955 100644 --- a/src/nvim/lua/tree_sitter.c +++ b/src/nvim/lua/tree_sitter.c @@ -65,6 +65,8 @@ static struct luaL_Reg node_meta[] = { {NULL, NULL} }; +PMap(cstr_t) *langs; + void build_meta(lua_State *L, const luaL_Reg *meta) { // [env, target] @@ -86,6 +88,9 @@ void build_meta(lua_State *L, const luaL_Reg *meta) /// all global state is stored in the regirstry of the lua_State void tslua_init(lua_State *L) { + + langs = pmap_new(cstr_t)(); + lua_createtable(L, 0, 0); // type metatables @@ -114,9 +119,55 @@ static int tslua_debug(lua_State *L) return 2; } -void tslua_push_parser(lua_State *L, TSLanguage *lang) + +int ts_lua_register_lang(lua_State *L) +{ + if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { + return luaL_error(L, "string expected"); + } + + const char *path = lua_tostring(L,1); + const char *lang_name = lua_tostring(L,2); + + if (pmap_has(cstr_t)(langs, lang_name)) { + return 0; + } + + // TODO: unsafe! + char symbol_buf[128] = "tree_sitter_"; + STRCAT(symbol_buf, lang_name); + + // TODO: we should maybe keep the uv_lib_t around, and close them + // at exit, to keep LeakSanitizer happy. + uv_lib_t lib; + if (uv_dlopen(path, &lib)) { + return luaL_error(L, "Failed to load parser: uv_dlopen: %s", uv_dlerror(&lib)); + } + + TSLanguage *(*lang_parser)(void); + if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { + return luaL_error(L, "Failed to load parser: uv_dlsym: %s", uv_dlerror(&lib)); + } + + TSLanguage *lang = lang_parser(); + if (lang == NULL) { + return luaL_error(L, "Failed to load parser: internal error"); + } + + pmap_put(cstr_t)(langs, xstrdup(lang_name), lang); + + lua_pushboolean(L, true); + return 1; +} + +int tslua_push_parser(lua_State *L, const char *lang_name) { TSParser *parser = ts_parser_new(); + TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); + if (!lang) { + return luaL_error(L, "no such language: %s", lang_name); + } + ts_parser_set_language(parser, lang); Tslua_parser *p = lua_newuserdata(L, sizeof(Tslua_parser)); // [udata] p->parser = parser; @@ -126,6 +177,7 @@ void tslua_push_parser(lua_State *L, TSLanguage *lang) lua_getfield(L, -1, "parser-meta"); // [udata, env, meta] lua_setmetatable(L, -3); // [udata, env] lua_pop(L, 1); // [udata] + return 1; } static Tslua_parser *parser_check(lua_State *L) -- cgit From 4ea5e63aa8c866b4fcc9d10f1a26078d2517f96a Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 9 Jun 2019 13:26:48 +0200 Subject: tree-sitter: add basic testing on ci build tree-sitter c parser on ci for testing purposes --- src/nvim/lua/executor.c | 2 +- src/nvim/lua/vim.lua | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index ae53bfce6a..23c4fcabbc 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -841,5 +841,5 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setfield(lstate, -2, "_create_ts_parser"); lua_pushcfunction(lstate, ts_lua_register_lang); - lua_setfield(lstate, -2, "ts_add_language"); + lua_setfield(lstate, -2, "_ts_add_language"); } diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index b1a684b977..c38926fe24 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -232,6 +232,9 @@ local function __index(t, key) if key == 'inspect' then t.inspect = require('vim.inspect') return t.inspect + elseif key == 'tree_sitter' then + t.tree_sitter = require('vim.tree_sitter') + return t.tree_sitter elseif require('vim.shared')[key] ~= nil then -- Expose all `vim.shared` functions on the `vim` module. t[key] = require('vim.shared')[key] -- cgit From c8f861b739b4703b1198dc1f88b09edbeb0d9f2e Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 15 Jun 2019 12:10:12 +0200 Subject: tree-sitter: rename tree_sitter => treesitter for consistency --- src/nvim/lua/executor.c | 2 +- src/nvim/lua/tree_sitter.c | 524 --------------------------------------------- src/nvim/lua/tree_sitter.h | 10 - src/nvim/lua/treesitter.c | 524 +++++++++++++++++++++++++++++++++++++++++++++ src/nvim/lua/treesitter.h | 10 + src/nvim/lua/vim.lua | 6 +- 6 files changed, 538 insertions(+), 538 deletions(-) delete mode 100644 src/nvim/lua/tree_sitter.c delete mode 100644 src/nvim/lua/tree_sitter.h create mode 100644 src/nvim/lua/treesitter.c create mode 100644 src/nvim/lua/treesitter.h (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 23c4fcabbc..8a35f11c71 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -31,7 +31,7 @@ #include "nvim/lua/executor.h" #include "nvim/lua/converter.h" -#include "nvim/lua/tree_sitter.h" +#include "nvim/lua/treesitter.h" #include "luv/luv.h" diff --git a/src/nvim/lua/tree_sitter.c b/src/nvim/lua/tree_sitter.c deleted file mode 100644 index f992639955..0000000000 --- a/src/nvim/lua/tree_sitter.c +++ /dev/null @@ -1,524 +0,0 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - -// lua bindings for tree-siter. -// NB: this file should contain a generic lua interface for -// tree-sitter trees and nodes, and could be broken out as a reusable library - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "tree_sitter/api.h" - -// NOT state-safe, delete when GC is confimed working: -static int debug_n_trees = 0, debug_n_cursors = 0; - -#define REG_KEY "tree_sitter-private" - -#include "nvim/lua/tree_sitter.h" -#include "nvim/api/private/handle.h" -#include "nvim/memline.h" - -typedef struct { - TSParser *parser; - TSTree *tree; -} Tslua_parser; - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "lua/tree_sitter.c.generated.h" -#endif - -static struct luaL_Reg parser_meta[] = { - {"__gc", parser_gc}, - {"__tostring", parser_tostring}, - {"parse_buf", parser_parse_buf}, - {"edit", parser_edit}, - {"tree", parser_tree}, - {NULL, NULL} -}; - -static struct luaL_Reg tree_meta[] = { - {"__gc", tree_gc}, - {"__tostring", tree_tostring}, - {"root", tree_root}, - {NULL, NULL} -}; - -static struct luaL_Reg node_meta[] = { - {"__tostring", node_tostring}, - {"__len", node_child_count}, - {"range", node_range}, - {"start", node_start}, - {"type", node_type}, - {"symbol", node_symbol}, - {"child_count", node_child_count}, - {"child", node_child}, - {"descendant_for_point_range", node_descendant_for_point_range}, - {"parent", node_parent}, - {NULL, NULL} -}; - -PMap(cstr_t) *langs; - -void build_meta(lua_State *L, const luaL_Reg *meta) -{ - // [env, target] - for (size_t i = 0; meta[i].name != NULL; i++) { - lua_pushcfunction(L, meta[i].func); // [env, target, func] - lua_pushvalue(L, -3); // [env, target, func, env] - lua_setfenv(L, -2); // [env, target, func] - lua_setfield(L, -2, meta[i].name); // [env, target] - } - - lua_pushvalue(L, -1); // [env, target, target] - lua_setfield(L, -2, "__index"); // [env, target] -} - - - -/// init the tslua library -/// -/// all global state is stored in the regirstry of the lua_State -void tslua_init(lua_State *L) -{ - - langs = pmap_new(cstr_t)(); - - lua_createtable(L, 0, 0); - - // type metatables - lua_createtable(L, 0, 0); - build_meta(L, parser_meta); - lua_setfield(L, -2, "parser-meta"); - - lua_createtable(L, 0, 0); - build_meta(L, tree_meta); - lua_setfield(L, -2, "tree-meta"); - - lua_createtable(L, 0, 0); - build_meta(L, node_meta); - lua_setfield(L, -2, "node-meta"); - - lua_setfield(L, LUA_REGISTRYINDEX, REG_KEY); - - lua_pushcfunction(L, tslua_debug); - lua_setglobal(L, "_tslua_debug"); -} - -static int tslua_debug(lua_State *L) -{ - lua_pushinteger(L, debug_n_trees); - lua_pushinteger(L, debug_n_cursors); - return 2; -} - - -int ts_lua_register_lang(lua_State *L) -{ - if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { - return luaL_error(L, "string expected"); - } - - const char *path = lua_tostring(L,1); - const char *lang_name = lua_tostring(L,2); - - if (pmap_has(cstr_t)(langs, lang_name)) { - return 0; - } - - // TODO: unsafe! - char symbol_buf[128] = "tree_sitter_"; - STRCAT(symbol_buf, lang_name); - - // TODO: we should maybe keep the uv_lib_t around, and close them - // at exit, to keep LeakSanitizer happy. - uv_lib_t lib; - if (uv_dlopen(path, &lib)) { - return luaL_error(L, "Failed to load parser: uv_dlopen: %s", uv_dlerror(&lib)); - } - - TSLanguage *(*lang_parser)(void); - if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { - return luaL_error(L, "Failed to load parser: uv_dlsym: %s", uv_dlerror(&lib)); - } - - TSLanguage *lang = lang_parser(); - if (lang == NULL) { - return luaL_error(L, "Failed to load parser: internal error"); - } - - pmap_put(cstr_t)(langs, xstrdup(lang_name), lang); - - lua_pushboolean(L, true); - return 1; -} - -int tslua_push_parser(lua_State *L, const char *lang_name) -{ - TSParser *parser = ts_parser_new(); - TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); - if (!lang) { - return luaL_error(L, "no such language: %s", lang_name); - } - - ts_parser_set_language(parser, lang); - Tslua_parser *p = lua_newuserdata(L, sizeof(Tslua_parser)); // [udata] - p->parser = parser; - p->tree = NULL; - - lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] - lua_getfield(L, -1, "parser-meta"); // [udata, env, meta] - lua_setmetatable(L, -3); // [udata, env] - lua_pop(L, 1); // [udata] - return 1; -} - -static Tslua_parser *parser_check(lua_State *L) -{ - if (!lua_gettop(L)) { - return 0; - } - if (!lua_isuserdata(L, 1)) { - return 0; - } - // TODO: typecheck! - return lua_touserdata(L, 1); -} - -static int parser_gc(lua_State *L) -{ - Tslua_parser *p = parser_check(L); - if (!p) { - return 0; - } - - ts_parser_delete(p->parser); - if (p->tree) { - ts_tree_delete(p->tree); - } - - return 0; -} - -static int parser_tostring(lua_State *L) -{ - lua_pushstring(L, ""); - return 1; -} - -static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position, uint32_t *bytes_read) -{ - buf_T *bp = payload; - static char buf[200]; - if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { - *bytes_read = 0; - return ""; - } - char_u *line = ml_get_buf(bp, position.row+1, false); - size_t len = STRLEN(line); - size_t tocopy = MIN(len-position.column,200); - - // TODO: translate embedded \n to \000 - memcpy(buf, line+position.column, tocopy); - *bytes_read = (uint32_t)tocopy; - if (tocopy < 200) { - buf[tocopy] = '\n'; - (*bytes_read)++; - } - return buf; -} - -static int parser_parse_buf(lua_State *L) -{ - Tslua_parser *p = parser_check(L); - if (!p) { - return 0; - } - - long bufnr = lua_tointeger(L, 2); - void *payload = handle_get_buffer(bufnr); - TSInput input = {payload, input_cb, TSInputEncodingUTF8}; - TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); - if (p->tree) { - ts_tree_delete(p->tree); - } - p->tree = new_tree; - - tslua_push_tree(L, ts_tree_copy(p->tree)); - return 1; -} - -static int parser_tree(lua_State *L) -{ - Tslua_parser *p = parser_check(L); - if (!p) { - return 0; - } - - if (p->tree) { - tslua_push_tree(L, ts_tree_copy(p->tree)); - } else { - lua_pushnil(L); - } - return 1; -} - -static int parser_edit(lua_State *L) -{ - if(lua_gettop(L) < 10) { - lua_pushstring(L, "not enough args to parser:edit()"); - lua_error(L); - return 0; // unreachable - } - - Tslua_parser *p = parser_check(L); - if (!p) { - return 0; - } - - if (!p->tree) { - return 0; - } - - long start_byte = lua_tointeger(L, 2); - long old_end_byte = lua_tointeger(L, 3); - long new_end_byte = lua_tointeger(L, 4); - TSPoint start_point = { lua_tointeger(L, 5), lua_tointeger(L, 6) }; - TSPoint old_end_point = { lua_tointeger(L, 7), lua_tointeger(L, 8) }; - TSPoint new_end_point = { lua_tointeger(L, 9), lua_tointeger(L, 10) }; - - TSInputEdit edit = { start_byte, old_end_byte, new_end_byte, - start_point, old_end_point, new_end_point }; - - ts_tree_edit(p->tree, &edit); - - return 0; -} - - -// Tree methods - -/// push tree interface on lua stack. -/// -/// This takes "ownership" of the tree and will free it -/// when the wrapper object is garbage collected -void tslua_push_tree(lua_State *L, TSTree *tree) -{ - TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] - *ud = tree; - lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] - lua_getfield(L, -1, "tree-meta"); // [udata, env, meta] - lua_setmetatable(L, -3); // [udata, env] - lua_pop(L, 1); // [udata] - - // table used for node wrappers to keep a reference to tree wrapper - // NB: in lua 5.3 the uservalue for the node could just be the tree, but - // in lua 5.1 the uservalue (fenv) must be a table. - lua_createtable(L, 1, 0); // [udata, reftable] - lua_pushvalue(L, -2); // [udata, reftable, udata] - lua_rawseti(L, -2, 1); // [udata, reftable] - lua_setfenv(L, -2); // [udata] - debug_n_trees++; -} - -static TSTree *tree_check(lua_State *L) -{ - if (!lua_gettop(L)) { - return 0; - } - if (!lua_isuserdata(L, 1)) { - return 0; - } - // TODO: typecheck! - TSTree **ud = lua_touserdata(L, 1); - return *ud; -} - -static int tree_gc(lua_State *L) -{ - TSTree *tree = tree_check(L); - if (!tree) { - return 0; - } - - ts_tree_delete(tree); - debug_n_trees--; - return 0; -} - -static int tree_tostring(lua_State *L) -{ - lua_pushstring(L, ""); - return 1; -} - -static int tree_root(lua_State *L) -{ - TSTree *tree = tree_check(L); - if (!tree) { - return 0; - } - TSNode root = ts_tree_root_node(tree); - push_node(L, root); - return 1; -} - -// Node methods - -/// push node interface on lua stack -/// -/// top of stack must either be the tree this node belongs to or another node -/// of the same tree! This value is not popped. Can only be called inside a -/// cfunction with the tslua environment. -static void push_node(lua_State *L, TSNode node) -{ - if (ts_node_is_null(node)) { - lua_pushnil(L); // [src, nil] - return; - } - TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata] - *ud = node; - lua_getfield(L, LUA_ENVIRONINDEX, "node-meta"); // [src, udata, meta] - lua_setmetatable(L, -2); // [src, udata] - lua_getfenv(L, -2); // [src, udata, reftable] - lua_setfenv(L, -2); // [src, udata] -} - -static bool node_check(lua_State *L, TSNode *res) -{ - if (!lua_gettop(L)) { - return 0; - } - if (!lua_isuserdata(L, 1)) { - return 0; - } - // TODO: typecheck! - TSNode *ud = lua_touserdata(L, 1); - *res = *ud; - return true; -} - - -static int node_tostring(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - lua_pushstring(L, ""); - lua_concat(L, 3); - return 1; -} - -static int node_range(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - TSPoint start = ts_node_start_point(node); - TSPoint end = ts_node_end_point(node); - lua_pushnumber(L, start.row); - lua_pushnumber(L, start.column); - lua_pushnumber(L, end.row); - lua_pushnumber(L, end.column); - return 4; -} - -static int node_start(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - TSPoint start = ts_node_start_point(node); - uint32_t start_byte = ts_node_start_byte(node); - lua_pushnumber(L, start.row); - lua_pushnumber(L, start.column); - lua_pushnumber(L, start_byte); - return 3; -} - -static int node_child_count(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - uint32_t count = ts_node_child_count(node); - lua_pushnumber(L, count); - return 1; -} - -static int node_type(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - lua_pushstring(L, ts_node_type(node)); - return 1; -} - -static int node_symbol(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - TSSymbol symbol = ts_node_symbol(node); - lua_pushnumber(L, symbol); - return 1; -} - -static int node_child(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - long num = lua_tointeger(L, 2); - TSNode child = ts_node_child(node, (uint32_t)num); - - lua_pushvalue(L, 1); - push_node(L, child); - return 1; -} - -static int node_descendant_for_point_range(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - TSPoint start = {(uint32_t)lua_tointeger(L, 2), - (uint32_t)lua_tointeger(L, 3)}; - TSPoint end = {(uint32_t)lua_tointeger(L, 4), - (uint32_t)lua_tointeger(L, 5)}; - TSNode child = ts_node_descendant_for_point_range(node, start, end); - - lua_pushvalue(L, 1); - push_node(L, child); - return 1; -} - -static int node_parent(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - TSNode parent = ts_node_parent(node); - push_node(L, parent); - return 1; -} - diff --git a/src/nvim/lua/tree_sitter.h b/src/nvim/lua/tree_sitter.h deleted file mode 100644 index 2ae0ec8371..0000000000 --- a/src/nvim/lua/tree_sitter.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef NVIM_LUA_TREE_SITTER_H -#define NVIM_LUA_TREE_SITTER_H - -#include "tree_sitter/api.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "lua/tree_sitter.h.generated.h" -#endif - -#endif // NVIM_LUA_TREE_SITTER_H diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c new file mode 100644 index 0000000000..794bdc6749 --- /dev/null +++ b/src/nvim/lua/treesitter.c @@ -0,0 +1,524 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// lua bindings for tree-siter. +// NB: this file should contain a generic lua interface for +// tree-sitter trees and nodes, and could be broken out as a reusable library + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "tree_sitter/api.h" + +// NOT state-safe, delete when GC is confimed working: +static int debug_n_trees = 0, debug_n_cursors = 0; + +#define REG_KEY "treesitter-private" + +#include "nvim/lua/treesitter.h" +#include "nvim/api/private/handle.h" +#include "nvim/memline.h" + +typedef struct { + TSParser *parser; + TSTree *tree; +} Tslua_parser; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/treesitter.c.generated.h" +#endif + +static struct luaL_Reg parser_meta[] = { + {"__gc", parser_gc}, + {"__tostring", parser_tostring}, + {"parse_buf", parser_parse_buf}, + {"edit", parser_edit}, + {"tree", parser_tree}, + {NULL, NULL} +}; + +static struct luaL_Reg tree_meta[] = { + {"__gc", tree_gc}, + {"__tostring", tree_tostring}, + {"root", tree_root}, + {NULL, NULL} +}; + +static struct luaL_Reg node_meta[] = { + {"__tostring", node_tostring}, + {"__len", node_child_count}, + {"range", node_range}, + {"start", node_start}, + {"type", node_type}, + {"symbol", node_symbol}, + {"child_count", node_child_count}, + {"child", node_child}, + {"descendant_for_point_range", node_descendant_for_point_range}, + {"parent", node_parent}, + {NULL, NULL} +}; + +PMap(cstr_t) *langs; + +void build_meta(lua_State *L, const luaL_Reg *meta) +{ + // [env, target] + for (size_t i = 0; meta[i].name != NULL; i++) { + lua_pushcfunction(L, meta[i].func); // [env, target, func] + lua_pushvalue(L, -3); // [env, target, func, env] + lua_setfenv(L, -2); // [env, target, func] + lua_setfield(L, -2, meta[i].name); // [env, target] + } + + lua_pushvalue(L, -1); // [env, target, target] + lua_setfield(L, -2, "__index"); // [env, target] +} + + + +/// init the tslua library +/// +/// all global state is stored in the regirstry of the lua_State +void tslua_init(lua_State *L) +{ + + langs = pmap_new(cstr_t)(); + + lua_createtable(L, 0, 0); + + // type metatables + lua_createtable(L, 0, 0); + build_meta(L, parser_meta); + lua_setfield(L, -2, "parser-meta"); + + lua_createtable(L, 0, 0); + build_meta(L, tree_meta); + lua_setfield(L, -2, "tree-meta"); + + lua_createtable(L, 0, 0); + build_meta(L, node_meta); + lua_setfield(L, -2, "node-meta"); + + lua_setfield(L, LUA_REGISTRYINDEX, REG_KEY); + + lua_pushcfunction(L, tslua_debug); + lua_setglobal(L, "_tslua_debug"); +} + +static int tslua_debug(lua_State *L) +{ + lua_pushinteger(L, debug_n_trees); + lua_pushinteger(L, debug_n_cursors); + return 2; +} + + +int ts_lua_register_lang(lua_State *L) +{ + if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { + return luaL_error(L, "string expected"); + } + + const char *path = lua_tostring(L,1); + const char *lang_name = lua_tostring(L,2); + + if (pmap_has(cstr_t)(langs, lang_name)) { + return 0; + } + + // TODO: unsafe! + char symbol_buf[128] = "tree_sitter_"; + STRCAT(symbol_buf, lang_name); + + // TODO: we should maybe keep the uv_lib_t around, and close them + // at exit, to keep LeakSanitizer happy. + uv_lib_t lib; + if (uv_dlopen(path, &lib)) { + return luaL_error(L, "Failed to load parser: uv_dlopen: %s", uv_dlerror(&lib)); + } + + TSLanguage *(*lang_parser)(void); + if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { + return luaL_error(L, "Failed to load parser: uv_dlsym: %s", uv_dlerror(&lib)); + } + + TSLanguage *lang = lang_parser(); + if (lang == NULL) { + return luaL_error(L, "Failed to load parser: internal error"); + } + + pmap_put(cstr_t)(langs, xstrdup(lang_name), lang); + + lua_pushboolean(L, true); + return 1; +} + +int tslua_push_parser(lua_State *L, const char *lang_name) +{ + TSParser *parser = ts_parser_new(); + TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); + if (!lang) { + return luaL_error(L, "no such language: %s", lang_name); + } + + ts_parser_set_language(parser, lang); + Tslua_parser *p = lua_newuserdata(L, sizeof(Tslua_parser)); // [udata] + p->parser = parser; + p->tree = NULL; + + lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] + lua_getfield(L, -1, "parser-meta"); // [udata, env, meta] + lua_setmetatable(L, -3); // [udata, env] + lua_pop(L, 1); // [udata] + return 1; +} + +static Tslua_parser *parser_check(lua_State *L) +{ + if (!lua_gettop(L)) { + return 0; + } + if (!lua_isuserdata(L, 1)) { + return 0; + } + // TODO: typecheck! + return lua_touserdata(L, 1); +} + +static int parser_gc(lua_State *L) +{ + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + ts_parser_delete(p->parser); + if (p->tree) { + ts_tree_delete(p->tree); + } + + return 0; +} + +static int parser_tostring(lua_State *L) +{ + lua_pushstring(L, ""); + return 1; +} + +static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position, uint32_t *bytes_read) +{ + buf_T *bp = payload; + static char buf[200]; + if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { + *bytes_read = 0; + return ""; + } + char_u *line = ml_get_buf(bp, position.row+1, false); + size_t len = STRLEN(line); + size_t tocopy = MIN(len-position.column,200); + + // TODO: translate embedded \n to \000 + memcpy(buf, line+position.column, tocopy); + *bytes_read = (uint32_t)tocopy; + if (tocopy < 200) { + buf[tocopy] = '\n'; + (*bytes_read)++; + } + return buf; +} + +static int parser_parse_buf(lua_State *L) +{ + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + long bufnr = lua_tointeger(L, 2); + void *payload = handle_get_buffer(bufnr); + TSInput input = {payload, input_cb, TSInputEncodingUTF8}; + TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); + if (p->tree) { + ts_tree_delete(p->tree); + } + p->tree = new_tree; + + tslua_push_tree(L, ts_tree_copy(p->tree)); + return 1; +} + +static int parser_tree(lua_State *L) +{ + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + if (p->tree) { + tslua_push_tree(L, ts_tree_copy(p->tree)); + } else { + lua_pushnil(L); + } + return 1; +} + +static int parser_edit(lua_State *L) +{ + if(lua_gettop(L) < 10) { + lua_pushstring(L, "not enough args to parser:edit()"); + lua_error(L); + return 0; // unreachable + } + + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + if (!p->tree) { + return 0; + } + + long start_byte = lua_tointeger(L, 2); + long old_end_byte = lua_tointeger(L, 3); + long new_end_byte = lua_tointeger(L, 4); + TSPoint start_point = { lua_tointeger(L, 5), lua_tointeger(L, 6) }; + TSPoint old_end_point = { lua_tointeger(L, 7), lua_tointeger(L, 8) }; + TSPoint new_end_point = { lua_tointeger(L, 9), lua_tointeger(L, 10) }; + + TSInputEdit edit = { start_byte, old_end_byte, new_end_byte, + start_point, old_end_point, new_end_point }; + + ts_tree_edit(p->tree, &edit); + + return 0; +} + + +// Tree methods + +/// push tree interface on lua stack. +/// +/// This takes "ownership" of the tree and will free it +/// when the wrapper object is garbage collected +void tslua_push_tree(lua_State *L, TSTree *tree) +{ + TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] + *ud = tree; + lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] + lua_getfield(L, -1, "tree-meta"); // [udata, env, meta] + lua_setmetatable(L, -3); // [udata, env] + lua_pop(L, 1); // [udata] + + // table used for node wrappers to keep a reference to tree wrapper + // NB: in lua 5.3 the uservalue for the node could just be the tree, but + // in lua 5.1 the uservalue (fenv) must be a table. + lua_createtable(L, 1, 0); // [udata, reftable] + lua_pushvalue(L, -2); // [udata, reftable, udata] + lua_rawseti(L, -2, 1); // [udata, reftable] + lua_setfenv(L, -2); // [udata] + debug_n_trees++; +} + +static TSTree *tree_check(lua_State *L) +{ + if (!lua_gettop(L)) { + return 0; + } + if (!lua_isuserdata(L, 1)) { + return 0; + } + // TODO: typecheck! + TSTree **ud = lua_touserdata(L, 1); + return *ud; +} + +static int tree_gc(lua_State *L) +{ + TSTree *tree = tree_check(L); + if (!tree) { + return 0; + } + + ts_tree_delete(tree); + debug_n_trees--; + return 0; +} + +static int tree_tostring(lua_State *L) +{ + lua_pushstring(L, ""); + return 1; +} + +static int tree_root(lua_State *L) +{ + TSTree *tree = tree_check(L); + if (!tree) { + return 0; + } + TSNode root = ts_tree_root_node(tree); + push_node(L, root); + return 1; +} + +// Node methods + +/// push node interface on lua stack +/// +/// top of stack must either be the tree this node belongs to or another node +/// of the same tree! This value is not popped. Can only be called inside a +/// cfunction with the tslua environment. +static void push_node(lua_State *L, TSNode node) +{ + if (ts_node_is_null(node)) { + lua_pushnil(L); // [src, nil] + return; + } + TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata] + *ud = node; + lua_getfield(L, LUA_ENVIRONINDEX, "node-meta"); // [src, udata, meta] + lua_setmetatable(L, -2); // [src, udata] + lua_getfenv(L, -2); // [src, udata, reftable] + lua_setfenv(L, -2); // [src, udata] +} + +static bool node_check(lua_State *L, TSNode *res) +{ + if (!lua_gettop(L)) { + return 0; + } + if (!lua_isuserdata(L, 1)) { + return 0; + } + // TODO: typecheck! + TSNode *ud = lua_touserdata(L, 1); + *res = *ud; + return true; +} + + +static int node_tostring(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushstring(L, ""); + lua_concat(L, 3); + return 1; +} + +static int node_range(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = ts_node_start_point(node); + TSPoint end = ts_node_end_point(node); + lua_pushnumber(L, start.row); + lua_pushnumber(L, start.column); + lua_pushnumber(L, end.row); + lua_pushnumber(L, end.column); + return 4; +} + +static int node_start(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = ts_node_start_point(node); + uint32_t start_byte = ts_node_start_byte(node); + lua_pushnumber(L, start.row); + lua_pushnumber(L, start.column); + lua_pushnumber(L, start_byte); + return 3; +} + +static int node_child_count(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + uint32_t count = ts_node_child_count(node); + lua_pushnumber(L, count); + return 1; +} + +static int node_type(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushstring(L, ts_node_type(node)); + return 1; +} + +static int node_symbol(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSSymbol symbol = ts_node_symbol(node); + lua_pushnumber(L, symbol); + return 1; +} + +static int node_child(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + long num = lua_tointeger(L, 2); + TSNode child = ts_node_child(node, (uint32_t)num); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + +static int node_descendant_for_point_range(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = {(uint32_t)lua_tointeger(L, 2), + (uint32_t)lua_tointeger(L, 3)}; + TSPoint end = {(uint32_t)lua_tointeger(L, 4), + (uint32_t)lua_tointeger(L, 5)}; + TSNode child = ts_node_descendant_for_point_range(node, start, end); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + +static int node_parent(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSNode parent = ts_node_parent(node); + push_node(L, parent); + return 1; +} + diff --git a/src/nvim/lua/treesitter.h b/src/nvim/lua/treesitter.h new file mode 100644 index 0000000000..d59b5e47a0 --- /dev/null +++ b/src/nvim/lua/treesitter.h @@ -0,0 +1,10 @@ +#ifndef NVIM_LUA_TREESITTER_H +#define NVIM_LUA_TREESITTER_H + +#include "tree_sitter/api.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/treesitter.h.generated.h" +#endif + +#endif // NVIM_LUA_TREESITTER_H diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index c38926fe24..b67762e48e 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -232,9 +232,9 @@ local function __index(t, key) if key == 'inspect' then t.inspect = require('vim.inspect') return t.inspect - elseif key == 'tree_sitter' then - t.tree_sitter = require('vim.tree_sitter') - return t.tree_sitter + elseif key == 'treesitter' then + t.treesitter = require('vim.treesitter') + return t.treesitter elseif require('vim.shared')[key] ~= nil then -- Expose all `vim.shared` functions on the `vim` module. t[key] = require('vim.shared')[key] -- cgit From a361e09cc531caf9dfb41bf860ca2d540ac2789d Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 15 Jun 2019 12:50:41 +0200 Subject: tree-sitter: use standard luaL_newmetatable and luaL_checkudata pattern --- src/nvim/lua/treesitter.c | 94 +++++++++++++++-------------------------------- 1 file changed, 30 insertions(+), 64 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 794bdc6749..8d24d15ccd 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -20,15 +20,13 @@ // NOT state-safe, delete when GC is confimed working: static int debug_n_trees = 0, debug_n_cursors = 0; -#define REG_KEY "treesitter-private" - #include "nvim/lua/treesitter.h" #include "nvim/api/private/handle.h" #include "nvim/memline.h" typedef struct { TSParser *parser; - TSTree *tree; + TSTree *tree; // internal tree, used for editing/reparsing } Tslua_parser; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -67,18 +65,18 @@ static struct luaL_Reg node_meta[] = { PMap(cstr_t) *langs; -void build_meta(lua_State *L, const luaL_Reg *meta) +void build_meta(lua_State *L, const char* tname, const luaL_Reg *meta) { - // [env, target] - for (size_t i = 0; meta[i].name != NULL; i++) { - lua_pushcfunction(L, meta[i].func); // [env, target, func] - lua_pushvalue(L, -3); // [env, target, func, env] - lua_setfenv(L, -2); // [env, target, func] - lua_setfield(L, -2, meta[i].name); // [env, target] - } + if (luaL_newmetatable(L, tname)) { // [meta] + for (size_t i = 0; meta[i].name != NULL; i++) { + lua_pushcfunction(L, meta[i].func); // [meta, func] + lua_setfield(L, -2, meta[i].name); // [meta] + } - lua_pushvalue(L, -1); // [env, target, target] - lua_setfield(L, -2, "__index"); // [env, target] + lua_pushvalue(L, -1); // [meta, meta] + lua_setfield(L, -2, "__index"); // [meta] + } + lua_pop(L, 1); // [] (don't use it now) } @@ -91,22 +89,12 @@ void tslua_init(lua_State *L) langs = pmap_new(cstr_t)(); - lua_createtable(L, 0, 0); - // type metatables - lua_createtable(L, 0, 0); - build_meta(L, parser_meta); - lua_setfield(L, -2, "parser-meta"); + build_meta(L, "treesitter_parser", parser_meta); - lua_createtable(L, 0, 0); - build_meta(L, tree_meta); - lua_setfield(L, -2, "tree-meta"); + build_meta(L, "treesitter_tree", tree_meta); - lua_createtable(L, 0, 0); - build_meta(L, node_meta); - lua_setfield(L, -2, "node-meta"); - - lua_setfield(L, LUA_REGISTRYINDEX, REG_KEY); + build_meta(L, "treesitter_node", node_meta); lua_pushcfunction(L, tslua_debug); lua_setglobal(L, "_tslua_debug"); @@ -173,23 +161,14 @@ int tslua_push_parser(lua_State *L, const char *lang_name) p->parser = parser; p->tree = NULL; - lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] - lua_getfield(L, -1, "parser-meta"); // [udata, env, meta] - lua_setmetatable(L, -3); // [udata, env] - lua_pop(L, 1); // [udata] + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_parser"); // [udata, meta] + lua_setmetatable(L, -2); // [udata] return 1; } static Tslua_parser *parser_check(lua_State *L) { - if (!lua_gettop(L)) { - return 0; - } - if (!lua_isuserdata(L, 1)) { - return 0; - } - // TODO: typecheck! - return lua_touserdata(L, 1); + return luaL_checkudata(L, 1, "treesitter_parser"); } static int parser_gc(lua_State *L) @@ -313,31 +292,22 @@ void tslua_push_tree(lua_State *L, TSTree *tree) { TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] *ud = tree; - lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] - lua_getfield(L, -1, "tree-meta"); // [udata, env, meta] - lua_setmetatable(L, -3); // [udata, env] - lua_pop(L, 1); // [udata] + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_tree"); // [udata, meta] + lua_setmetatable(L, -2); // [udata] // table used for node wrappers to keep a reference to tree wrapper // NB: in lua 5.3 the uservalue for the node could just be the tree, but // in lua 5.1 the uservalue (fenv) must be a table. - lua_createtable(L, 1, 0); // [udata, reftable] - lua_pushvalue(L, -2); // [udata, reftable, udata] - lua_rawseti(L, -2, 1); // [udata, reftable] - lua_setfenv(L, -2); // [udata] + lua_createtable(L, 1, 0); // [udata, reftable] + lua_pushvalue(L, -2); // [udata, reftable, udata] + lua_rawseti(L, -2, 1); // [udata, reftable] + lua_setfenv(L, -2); // [udata] debug_n_trees++; } static TSTree *tree_check(lua_State *L) { - if (!lua_gettop(L)) { - return 0; - } - if (!lua_isuserdata(L, 1)) { - return 0; - } - // TODO: typecheck! - TSTree **ud = lua_touserdata(L, 1); + TSTree **ud = luaL_checkudata(L, 1, "treesitter_tree"); return *ud; } @@ -385,7 +355,7 @@ static void push_node(lua_State *L, TSNode node) } TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata] *ud = node; - lua_getfield(L, LUA_ENVIRONINDEX, "node-meta"); // [src, udata, meta] + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_node"); // [src, udata, meta] lua_setmetatable(L, -2); // [src, udata] lua_getfenv(L, -2); // [src, udata, reftable] lua_setfenv(L, -2); // [src, udata] @@ -393,16 +363,12 @@ static void push_node(lua_State *L, TSNode node) static bool node_check(lua_State *L, TSNode *res) { - if (!lua_gettop(L)) { - return 0; - } - if (!lua_isuserdata(L, 1)) { - return 0; + TSNode *ud = luaL_checkudata(L, 1, "treesitter_node"); + if (ud) { + *res = *ud; + return true; } - // TODO: typecheck! - TSNode *ud = lua_touserdata(L, 1); - *res = *ud; - return true; + return false; } -- cgit From c1dc1bedba4e0e3db2cd2e52d9241991567f8218 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 15 Jun 2019 13:12:59 +0200 Subject: tree-sitter: style --- src/nvim/lua/executor.c | 3 +- src/nvim/lua/treesitter.c | 113 ++++++++++++++++++++++++---------------------- 2 files changed, 59 insertions(+), 57 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 8a35f11c71..c208711985 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -828,8 +828,7 @@ static int create_tslua_parser(lua_State *L) return luaL_error(L, "string expected"); } - const char *lang_name = lua_tostring(L,1); - + const char *lang_name = lua_tostring(L, 1); return tslua_push_parser(L, lang_name); } diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 8d24d15ccd..2337d598b3 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -34,38 +34,38 @@ typedef struct { #endif static struct luaL_Reg parser_meta[] = { - {"__gc", parser_gc}, - {"__tostring", parser_tostring}, - {"parse_buf", parser_parse_buf}, - {"edit", parser_edit}, - {"tree", parser_tree}, - {NULL, NULL} + { "__gc", parser_gc }, + { "__tostring", parser_tostring }, + { "parse_buf", parser_parse_buf }, + { "edit", parser_edit }, + { "tree", parser_tree }, + { NULL, NULL } }; static struct luaL_Reg tree_meta[] = { - {"__gc", tree_gc}, - {"__tostring", tree_tostring}, - {"root", tree_root}, - {NULL, NULL} + { "__gc", tree_gc }, + { "__tostring", tree_tostring }, + { "root", tree_root }, + { NULL, NULL } }; static struct luaL_Reg node_meta[] = { - {"__tostring", node_tostring}, - {"__len", node_child_count}, - {"range", node_range}, - {"start", node_start}, - {"type", node_type}, - {"symbol", node_symbol}, - {"child_count", node_child_count}, - {"child", node_child}, - {"descendant_for_point_range", node_descendant_for_point_range}, - {"parent", node_parent}, - {NULL, NULL} + { "__tostring", node_tostring }, + { "__len", node_child_count }, + { "range", node_range }, + { "start", node_start }, + { "type", node_type }, + { "symbol", node_symbol }, + { "child_count", node_child_count }, + { "child", node_child }, + { "descendant_for_point_range", node_descendant_for_point_range }, + { "parent", node_parent }, + { NULL, NULL } }; PMap(cstr_t) *langs; -void build_meta(lua_State *L, const char* tname, const luaL_Reg *meta) +void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) { if (luaL_newmetatable(L, tname)) { // [meta] for (size_t i = 0; meta[i].name != NULL; i++) { @@ -86,7 +86,6 @@ void build_meta(lua_State *L, const char* tname, const luaL_Reg *meta) /// all global state is stored in the regirstry of the lua_State void tslua_init(lua_State *L) { - langs = pmap_new(cstr_t)(); // type metatables @@ -114,8 +113,8 @@ int ts_lua_register_lang(lua_State *L) return luaL_error(L, "string expected"); } - const char *path = lua_tostring(L,1); - const char *lang_name = lua_tostring(L,2); + const char *path = lua_tostring(L, 1); + const char *lang_name = lua_tostring(L, 2); if (pmap_has(cstr_t)(langs, lang_name)) { return 0; @@ -129,12 +128,14 @@ int ts_lua_register_lang(lua_State *L) // at exit, to keep LeakSanitizer happy. uv_lib_t lib; if (uv_dlopen(path, &lib)) { - return luaL_error(L, "Failed to load parser: uv_dlopen: %s", uv_dlerror(&lib)); + return luaL_error(L, "Failed to load parser: uv_dlopen: %s", + uv_dlerror(&lib)); } TSLanguage *(*lang_parser)(void); if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { - return luaL_error(L, "Failed to load parser: uv_dlsym: %s", uv_dlerror(&lib)); + return luaL_error(L, "Failed to load parser: uv_dlsym: %s", + uv_dlerror(&lib)); } TSLanguage *lang = lang_parser(); @@ -192,26 +193,29 @@ static int parser_tostring(lua_State *L) return 1; } -static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position, uint32_t *bytes_read) +static const char *input_cb(void *payload, uint32_t byte_index, + TSPoint position, uint32_t *bytes_read) { - buf_T *bp = payload; - static char buf[200]; - if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { - *bytes_read = 0; - return ""; - } - char_u *line = ml_get_buf(bp, position.row+1, false); - size_t len = STRLEN(line); - size_t tocopy = MIN(len-position.column,200); - - // TODO: translate embedded \n to \000 - memcpy(buf, line+position.column, tocopy); - *bytes_read = (uint32_t)tocopy; - if (tocopy < 200) { - buf[tocopy] = '\n'; - (*bytes_read)++; - } - return buf; + buf_T *bp = payload; +#define BUFSIZE 256 + static char buf[BUFSIZE]; + if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { + *bytes_read = 0; + return ""; + } + char_u *line = ml_get_buf(bp, position.row+1, false); + size_t len = STRLEN(line); + size_t tocopy = MIN(len-position.column, BUFSIZE); + + // TODO: translate embedded \n to \000 + memcpy(buf, line+position.column, tocopy); + *bytes_read = (uint32_t)tocopy; + if (tocopy < 200) { + buf[tocopy] = '\n'; + (*bytes_read)++; + } + return buf; +#undef BUFSIZE } static int parser_parse_buf(lua_State *L) @@ -223,7 +227,7 @@ static int parser_parse_buf(lua_State *L) long bufnr = lua_tointeger(L, 2); void *payload = handle_get_buffer(bufnr); - TSInput input = {payload, input_cb, TSInputEncodingUTF8}; + TSInput input = { payload, input_cb, TSInputEncodingUTF8 }; TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); if (p->tree) { ts_tree_delete(p->tree); @@ -251,10 +255,9 @@ static int parser_tree(lua_State *L) static int parser_edit(lua_State *L) { - if(lua_gettop(L) < 10) { + if (lua_gettop(L) < 10) { lua_pushstring(L, "not enough args to parser:edit()"); - lua_error(L); - return 0; // unreachable + return lua_error(L); } Tslua_parser *p = parser_check(L); @@ -350,7 +353,7 @@ static int tree_root(lua_State *L) static void push_node(lua_State *L, TSNode node) { if (ts_node_is_null(node)) { - lua_pushnil(L); // [src, nil] + lua_pushnil(L); // [src, nil] return; } TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata] @@ -466,10 +469,10 @@ static int node_descendant_for_point_range(lua_State *L) if (!node_check(L, &node)) { return 0; } - TSPoint start = {(uint32_t)lua_tointeger(L, 2), - (uint32_t)lua_tointeger(L, 3)}; - TSPoint end = {(uint32_t)lua_tointeger(L, 4), - (uint32_t)lua_tointeger(L, 5)}; + TSPoint start = { (uint32_t)lua_tointeger(L, 2), + (uint32_t)lua_tointeger(L, 3) }; + TSPoint end = { (uint32_t)lua_tointeger(L, 4), + (uint32_t)lua_tointeger(L, 5) }; TSNode child = ts_node_descendant_for_point_range(node, start, end); lua_pushvalue(L, 1); -- cgit From a88a9f128e29b27315a87d0119fbc649196999bc Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 15 Jun 2019 13:43:30 +0200 Subject: tree-sitter: add some more API --- src/nvim/lua/treesitter.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 2337d598b3..9d599da85f 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -54,11 +54,19 @@ static struct luaL_Reg node_meta[] = { { "__len", node_child_count }, { "range", node_range }, { "start", node_start }, + { "end_", node_end }, { "type", node_type }, { "symbol", node_symbol }, + { "named", node_named }, + { "missing", node_missing }, + { "has_error", node_has_error }, + { "sexpr", node_sexpr }, { "child_count", node_child_count }, + { "named_child_count", node_named_child_count }, { "child", node_child }, + { "named_child", node_named_child }, { "descendant_for_point_range", node_descendant_for_point_range }, + { "named_descendant_for_point_range", node_named_descendant_for_point_range }, { "parent", node_parent }, { NULL, NULL } }; @@ -417,6 +425,20 @@ static int node_start(lua_State *L) return 3; } +static int node_end(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint end = ts_node_end_point(node); + uint32_t end_byte = ts_node_end_byte(node); + lua_pushnumber(L, end.row); + lua_pushnumber(L, end.column); + lua_pushnumber(L, end_byte); + return 3; +} + static int node_child_count(lua_State *L) { TSNode node; @@ -428,6 +450,17 @@ static int node_child_count(lua_State *L) return 1; } +static int node_named_child_count(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + uint32_t count = ts_node_named_child_count(node); + lua_pushnumber(L, count); + return 1; +} + static int node_type(lua_State *L) { TSNode node; @@ -449,6 +482,48 @@ static int node_symbol(lua_State *L) return 1; } +static int node_named(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushboolean(L, ts_node_is_named(node)); + return 1; +} + +static int node_sexpr(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + char *allocated = ts_node_string(node); + lua_pushstring(L, allocated); + xfree(allocated); + return 1; +} + +static int node_missing(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushboolean(L, ts_node_is_missing(node)); + return 1; +} + +static int node_has_error(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushboolean(L, ts_node_has_error(node)); + return 1; +} + static int node_child(lua_State *L) { TSNode node; @@ -463,6 +538,20 @@ static int node_child(lua_State *L) return 1; } +static int node_named_child(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + long num = lua_tointeger(L, 2); + TSNode child = ts_node_named_child(node, (uint32_t)num); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + static int node_descendant_for_point_range(lua_State *L) { TSNode node; @@ -480,6 +569,23 @@ static int node_descendant_for_point_range(lua_State *L) return 1; } +static int node_named_descendant_for_point_range(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = { (uint32_t)lua_tointeger(L, 2), + (uint32_t)lua_tointeger(L, 3) }; + TSPoint end = { (uint32_t)lua_tointeger(L, 4), + (uint32_t)lua_tointeger(L, 5) }; + TSNode child = ts_node_named_descendant_for_point_range(node, start, end); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + static int node_parent(lua_State *L) { TSNode node; -- cgit From d24dec596c25690aba0aca658546ffdfcc6a952c Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 15 Jun 2019 14:05:35 +0200 Subject: tree-sitter: inspect language --- src/nvim/lua/executor.c | 3 +++ src/nvim/lua/treesitter.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index c208711985..aa83e3c1ba 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -841,4 +841,7 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, ts_lua_register_lang); lua_setfield(lstate, -2, "_ts_add_language"); + + lua_pushcfunction(lstate, ts_lua_inspect_lang); + lua_setfield(lstate, -2, "_ts_inspect_language"); } diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 9d599da85f..db337db533 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -157,6 +157,51 @@ int ts_lua_register_lang(lua_State *L) return 1; } +int ts_lua_inspect_lang(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); + + TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); + if (!lang) { + return luaL_error(L, "no such language: %s", lang_name); + } + + lua_createtable(L, 0, 2); // [retval] + + size_t nsymbols = (size_t)ts_language_symbol_count(lang); + + lua_createtable(L, nsymbols-1, 1); // [retval, symbols] + for (size_t i = 0; i < nsymbols; i++) { + TSSymbolType t = ts_language_symbol_type(lang, i); + if (t == TSSymbolTypeAuxiliary) { + // not used by the API + continue; + } + lua_createtable(L, 2, 0); // [retval, symbols, elem] + lua_pushstring(L, ts_language_symbol_name(lang, i)); + lua_rawseti(L, -2, 1); + lua_pushboolean(L, t == TSSymbolTypeRegular); + lua_rawseti(L, -2, 2); // [retval, symbols, elem] + lua_rawseti(L, -2, i); // [retval, symbols] + } + + lua_setfield(L, -2, "symbols"); // [retval] + + // TODO: this seems to be empty, what langs have fields? + size_t nfields = (size_t)ts_language_field_count(lang); + lua_createtable(L, nfields-1, 1); // [retval, fields] + for (size_t i = 0; i < nfields; i++) { + lua_pushstring(L, ts_language_field_name_for_id(lang, i)); + lua_rawseti(L, -2, i); // [retval, fields] + } + + lua_setfield(L, -2, "fields"); // [retval] + return 1; +} + int tslua_push_parser(lua_State *L, const char *lang_name) { TSParser *parser = ts_parser_new(); -- cgit From d697841a9d3030efaf10dbddaee9f3c0a8fe1b78 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 16 Jun 2019 14:09:18 +0200 Subject: tree-sitter: cleanup tree refcounting --- src/nvim/lua/treesitter.c | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index db337db533..016c175b59 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -17,9 +17,6 @@ #include "tree_sitter/api.h" -// NOT state-safe, delete when GC is confimed working: -static int debug_n_trees = 0, debug_n_cursors = 0; - #include "nvim/lua/treesitter.h" #include "nvim/api/private/handle.h" #include "nvim/memline.h" @@ -102,19 +99,8 @@ void tslua_init(lua_State *L) build_meta(L, "treesitter_tree", tree_meta); build_meta(L, "treesitter_node", node_meta); - - lua_pushcfunction(L, tslua_debug); - lua_setglobal(L, "_tslua_debug"); } -static int tslua_debug(lua_State *L) -{ - lua_pushinteger(L, debug_n_trees); - lua_pushinteger(L, debug_n_cursors); - return 2; -} - - int ts_lua_register_lang(lua_State *L) { if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { @@ -280,6 +266,9 @@ static int parser_parse_buf(lua_State *L) long bufnr = lua_tointeger(L, 2); void *payload = handle_get_buffer(bufnr); + if (!payload) { + return luaL_error(L, "invalid buffer handle: %d", bufnr); + } TSInput input = { payload, input_cb, TSInputEncodingUTF8 }; TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); if (p->tree) { @@ -287,7 +276,7 @@ static int parser_parse_buf(lua_State *L) } p->tree = new_tree; - tslua_push_tree(L, ts_tree_copy(p->tree)); + tslua_push_tree(L, p->tree); return 1; } @@ -298,11 +287,7 @@ static int parser_tree(lua_State *L) return 0; } - if (p->tree) { - tslua_push_tree(L, ts_tree_copy(p->tree)); - } else { - lua_pushnil(L); - } + tslua_push_tree(L, p->tree); return 1; } @@ -342,12 +327,15 @@ static int parser_edit(lua_State *L) /// push tree interface on lua stack. /// -/// This takes "ownership" of the tree and will free it -/// when the wrapper object is garbage collected +/// This makes a copy of the tree, so ownership of the argument is unaffected. void tslua_push_tree(lua_State *L, TSTree *tree) { + if (tree == NULL) { + lua_pushnil(L); + return; + } TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] - *ud = tree; + *ud = ts_tree_copy(tree); lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_tree"); // [udata, meta] lua_setmetatable(L, -2); // [udata] @@ -358,7 +346,6 @@ void tslua_push_tree(lua_State *L, TSTree *tree) lua_pushvalue(L, -2); // [udata, reftable, udata] lua_rawseti(L, -2, 1); // [udata, reftable] lua_setfenv(L, -2); // [udata] - debug_n_trees++; } static TSTree *tree_check(lua_State *L) @@ -375,7 +362,6 @@ static int tree_gc(lua_State *L) } ts_tree_delete(tree); - debug_n_trees--; return 0; } -- cgit From 06ee45b9b1c14c7ce6cb23403cdbe2852d495cad Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Fri, 21 Jun 2019 14:14:51 +0200 Subject: tree-sitter: fix lint, delete "demo" plugin (replaced by functional tests) --- src/nvim/lua/executor.c | 4 +-- src/nvim/lua/treesitter.c | 70 +++++++++++++++++++++++++---------------------- src/nvim/lua/treesitter.h | 4 +++ 3 files changed, 43 insertions(+), 35 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index aa83e3c1ba..127458fe39 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -839,9 +839,9 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, create_tslua_parser); lua_setfield(lstate, -2, "_create_ts_parser"); - lua_pushcfunction(lstate, ts_lua_register_lang); + lua_pushcfunction(lstate, tslua_register_lang); lua_setfield(lstate, -2, "_ts_add_language"); - lua_pushcfunction(lstate, ts_lua_inspect_lang); + lua_pushcfunction(lstate, tslua_inspect_lang); lua_setfield(lstate, -2, "_ts_inspect_language"); } diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 016c175b59..c27ae8c877 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -1,9 +1,9 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -// lua bindings for tree-siter. -// NB: this file should contain a generic lua interface for -// tree-sitter trees and nodes, and could be broken out as a reusable library +// lua bindings for tree-sitter. +// NB: this file mostly contains a generic lua interface for tree-sitter +// trees and nodes, and could be broken out as a reusable lua package #include #include @@ -22,9 +22,9 @@ #include "nvim/memline.h" typedef struct { - TSParser *parser; - TSTree *tree; // internal tree, used for editing/reparsing -} Tslua_parser; + TSParser *parser; + TSTree *tree; // internal tree, used for editing/reparsing +} TSLua_parser; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/treesitter.c.generated.h" @@ -68,9 +68,9 @@ static struct luaL_Reg node_meta[] = { { NULL, NULL } }; -PMap(cstr_t) *langs; +static PMap(cstr_t) *langs; -void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) +static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) { if (luaL_newmetatable(L, tname)) { // [meta] for (size_t i = 0; meta[i].name != NULL; i++) { @@ -84,8 +84,6 @@ void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) lua_pop(L, 1); // [] (don't use it now) } - - /// init the tslua library /// /// all global state is stored in the regirstry of the lua_State @@ -95,13 +93,11 @@ void tslua_init(lua_State *L) // type metatables build_meta(L, "treesitter_parser", parser_meta); - build_meta(L, "treesitter_tree", tree_meta); - build_meta(L, "treesitter_node", node_meta); } -int ts_lua_register_lang(lua_State *L) +int tslua_register_lang(lua_State *L) { if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { return luaL_error(L, "string expected"); @@ -114,22 +110,27 @@ int ts_lua_register_lang(lua_State *L) return 0; } - // TODO: unsafe! - char symbol_buf[128] = "tree_sitter_"; - STRCAT(symbol_buf, lang_name); +#define BUFSIZE 128 + char symbol_buf[BUFSIZE]; + snprintf(symbol_buf, BUFSIZE, "tree_sitter_%s", lang_name); +#undef BUFSIZE - // TODO: we should maybe keep the uv_lib_t around, and close them - // at exit, to keep LeakSanitizer happy. uv_lib_t lib; if (uv_dlopen(path, &lib)) { - return luaL_error(L, "Failed to load parser: uv_dlopen: %s", - uv_dlerror(&lib)); + snprintf((char *)IObuff, IOSIZE, "Failed to load parser: uv_dlopen: %s", + uv_dlerror(&lib)); + uv_dlclose(&lib); + lua_pushstring(L, (char *)IObuff); + return lua_error(L); } TSLanguage *(*lang_parser)(void); if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { - return luaL_error(L, "Failed to load parser: uv_dlsym: %s", - uv_dlerror(&lib)); + snprintf((char *)IObuff, IOSIZE, "Failed to load parser: uv_dlsym: %s", + uv_dlerror(&lib)); + uv_dlclose(&lib); + lua_pushstring(L, (char *)IObuff); + return lua_error(L); } TSLanguage *lang = lang_parser(); @@ -143,7 +144,7 @@ int ts_lua_register_lang(lua_State *L) return 1; } -int ts_lua_inspect_lang(lua_State *L) +int tslua_inspect_lang(lua_State *L) { if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) { return luaL_error(L, "string expected"); @@ -176,7 +177,6 @@ int ts_lua_inspect_lang(lua_State *L) lua_setfield(L, -2, "symbols"); // [retval] - // TODO: this seems to be empty, what langs have fields? size_t nfields = (size_t)ts_language_field_count(lang); lua_createtable(L, nfields-1, 1); // [retval, fields] for (size_t i = 0; i < nfields; i++) { @@ -190,14 +190,14 @@ int ts_lua_inspect_lang(lua_State *L) int tslua_push_parser(lua_State *L, const char *lang_name) { - TSParser *parser = ts_parser_new(); TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); if (!lang) { return luaL_error(L, "no such language: %s", lang_name); } + TSParser *parser = ts_parser_new(); ts_parser_set_language(parser, lang); - Tslua_parser *p = lua_newuserdata(L, sizeof(Tslua_parser)); // [udata] + TSLua_parser *p = lua_newuserdata(L, sizeof(TSLua_parser)); // [udata] p->parser = parser; p->tree = NULL; @@ -206,14 +206,14 @@ int tslua_push_parser(lua_State *L, const char *lang_name) return 1; } -static Tslua_parser *parser_check(lua_State *L) +static TSLua_parser *parser_check(lua_State *L) { return luaL_checkudata(L, 1, "treesitter_parser"); } static int parser_gc(lua_State *L) { - Tslua_parser *p = parser_check(L); + TSLua_parser *p = parser_check(L); if (!p) { return 0; } @@ -238,6 +238,7 @@ static const char *input_cb(void *payload, uint32_t byte_index, buf_T *bp = payload; #define BUFSIZE 256 static char buf[BUFSIZE]; + if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { *bytes_read = 0; return ""; @@ -246,10 +247,13 @@ static const char *input_cb(void *payload, uint32_t byte_index, size_t len = STRLEN(line); size_t tocopy = MIN(len-position.column, BUFSIZE); - // TODO: translate embedded \n to \000 memcpy(buf, line+position.column, tocopy); + // Translate embedded \n to NUL + memchrsub(buf, '\n', '\0', tocopy); *bytes_read = (uint32_t)tocopy; - if (tocopy < 200) { + if (tocopy < BUFSIZE) { + // now add the final \n. If it didn't fit, input_cb will be called again + // on the same line with advanced column. buf[tocopy] = '\n'; (*bytes_read)++; } @@ -259,7 +263,7 @@ static const char *input_cb(void *payload, uint32_t byte_index, static int parser_parse_buf(lua_State *L) { - Tslua_parser *p = parser_check(L); + TSLua_parser *p = parser_check(L); if (!p) { return 0; } @@ -282,7 +286,7 @@ static int parser_parse_buf(lua_State *L) static int parser_tree(lua_State *L) { - Tslua_parser *p = parser_check(L); + TSLua_parser *p = parser_check(L); if (!p) { return 0; } @@ -298,7 +302,7 @@ static int parser_edit(lua_State *L) return lua_error(L); } - Tslua_parser *p = parser_check(L); + TSLua_parser *p = parser_check(L); if (!p) { return 0; } diff --git a/src/nvim/lua/treesitter.h b/src/nvim/lua/treesitter.h index d59b5e47a0..812166f67b 100644 --- a/src/nvim/lua/treesitter.h +++ b/src/nvim/lua/treesitter.h @@ -1,6 +1,10 @@ #ifndef NVIM_LUA_TREESITTER_H #define NVIM_LUA_TREESITTER_H +#include +#include +#include + #include "tree_sitter/api.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -- cgit From e0d6228978dd1389f75a3e0351f6e6d5625643ae Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 21 Sep 2019 10:10:47 +0200 Subject: tree-sitter: use "range" instead of "point_range" consistently in lua API --- src/nvim/lua/treesitter.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index c27ae8c877..a71234d2c4 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -62,8 +62,8 @@ static struct luaL_Reg node_meta[] = { { "named_child_count", node_named_child_count }, { "child", node_child }, { "named_child", node_named_child }, - { "descendant_for_point_range", node_descendant_for_point_range }, - { "named_descendant_for_point_range", node_named_descendant_for_point_range }, + { "descendant_for_range", node_descendant_for_range }, + { "named_descendant_for_range", node_named_descendant_for_range }, { "parent", node_parent }, { NULL, NULL } }; @@ -587,7 +587,7 @@ static int node_named_child(lua_State *L) return 1; } -static int node_descendant_for_point_range(lua_State *L) +static int node_descendant_for_range(lua_State *L) { TSNode node; if (!node_check(L, &node)) { @@ -604,7 +604,7 @@ static int node_descendant_for_point_range(lua_State *L) return 1; } -static int node_named_descendant_for_point_range(lua_State *L) +static int node_named_descendant_for_range(lua_State *L) { TSNode node; if (!node_check(L, &node)) { -- cgit From d5a69eb07648a515d03aa5c9e268aef852015ea9 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 22 Sep 2019 11:33:55 +0200 Subject: tree-sitter: handle node equality --- src/nvim/lua/treesitter.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index a71234d2c4..d2072402bb 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -48,6 +48,7 @@ static struct luaL_Reg tree_meta[] = { static struct luaL_Reg node_meta[] = { { "__tostring", node_tostring }, + { "__eq", node_eq }, { "__len", node_child_count }, { "range", node_range }, { "start", node_start }, @@ -431,6 +432,23 @@ static int node_tostring(lua_State *L) return 1; } +static int node_eq(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + // This should only be called if both x and y in "x == y" has the + // treesitter_node metatable. So it is ok to error out otherwise. + TSNode *ud = luaL_checkudata(L, 2, "treesitter_node"); + if (!ud) { + return 0; + } + TSNode node2 = *ud; + lua_pushboolean(L, ts_node_eq(node, node2)); + return 1; +} + static int node_range(lua_State *L) { TSNode node; -- cgit From 8ee7c94a92598d46b488b7fe3b1a5cff6b1bf94a Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 25 Aug 2019 22:01:35 +0200 Subject: lua: add vim.fn.{func} for direct access to vimL function compared to vim.api.|nvim_call_function|, this fixes some typing issues due to the indirect conversion via the API. float values are preserved as such (fixes #9389) as well as empty dicts/arrays. Ref https://github.com/norcalli/nvim.lua for the call syntax --- src/nvim/lua/converter.c | 14 +++++++++-- src/nvim/lua/executor.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++- src/nvim/lua/vim.lua | 12 +++++++++ 3 files changed, 86 insertions(+), 3 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 9665655e74..844232c64a 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -401,6 +401,8 @@ nlua_pop_typval_table_processing_end: return ret; } +static bool typval_conv_special = false; + #define TYPVAL_ENCODE_ALLOW_SPECIALS true #define TYPVAL_ENCODE_CONV_NIL(tv) \ @@ -439,7 +441,13 @@ nlua_pop_typval_table_processing_end: lua_createtable(lstate, 0, 0) #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ - nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary) + do { \ + if (typval_conv_special) { \ + nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \ + } else { \ + lua_createtable(lstate, 0, 0); \ + } \ + } while (0) #define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ do { \ @@ -548,9 +556,11 @@ nlua_pop_typval_table_processing_end: /// @param[in] tv typval_T to convert. /// /// @return true in case of success, false otherwise. -bool nlua_push_typval(lua_State *lstate, typval_T *const tv) +bool nlua_push_typval(lua_State *lstate, typval_T *const tv, bool special) { + typval_conv_special = special; const int initial_size = lua_gettop(lstate); + if (!lua_checkstack(lstate, initial_size + 2)) { emsgf(_("E1502: Lua failed to grow stack to %i"), initial_size + 4); return false; diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 127458fe39..b7c1d57964 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -295,6 +295,9 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // in_fast_event lua_pushcfunction(lstate, &nlua_in_fast_event); lua_setfield(lstate, -2, "in_fast_event"); + // call + lua_pushcfunction(lstate, &nlua_call); + lua_setfield(lstate, -2, "call"); // vim.loop luv_set_loop(lstate, &main_loop.uv); @@ -539,6 +542,64 @@ int nlua_in_fast_event(lua_State *lstate) return 1; } +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); + int nargs = lua_gettop(lstate)-1; + if (nargs > MAX_FUNC_ARGS) { + return luaL_error(lstate, "Function called with too many arguments"); + } + + typval_T vim_args[MAX_FUNC_ARGS + 1]; + int i = 0; // also used for freeing the variables + for (; i < nargs; i++) { + lua_pushvalue(lstate, (int)i+2); + if (!nlua_pop_typval(lstate, &vim_args[i])) { + api_set_error(&err, kErrorTypeException, + "error converting argument %d", i+1); + goto free_vim_args; + } + } + + // TODO(bfredl): this should be simplified in error handling refactor + struct msglist **saved_msg_list = msg_list; + struct msglist *private_msg_list = NULL; + msg_list = &private_msg_list; + + 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` + // (see above) 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); + + msg_list = saved_msg_list; + +free_vim_args: + while (i > 0) { + tv_clear(&vim_args[--i]); + } + if (ERROR_SET(&err)) { + lua_pushstring(lstate, err.msg); + api_clear_error(&err); + return lua_error(lstate); + } + return 1; +} + #ifdef WIN32 /// os.getenv: override os.getenv to maintain coherency. #9681 /// @@ -623,7 +684,7 @@ void executor_eval_lua(const String str, typval_T *const arg, if (arg->v_type == VAR_UNKNOWN) { lua_pushnil(lstate); } else { - nlua_push_typval(lstate, arg); + nlua_push_typval(lstate, arg, true); } if (lua_pcall(lstate, 1, 1, 0)) { nlua_error(lstate, diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index b67762e48e..5514819a02 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -242,6 +242,17 @@ local function __index(t, key) end end + +-- vim.fn.{func}(...) +local function fn_index(t, key) + local function func(...) + return vim.call(key, ...) + end + t[key] = func + return func +end +local fn = setmetatable({}, {__index=fn_index}) + local module = { _update_package_paths = _update_package_paths, _os_proc_children = _os_proc_children, @@ -249,6 +260,7 @@ local module = { _system = _system, paste = paste, schedule_wrap = schedule_wrap, + fn=fn, } setmetatable(module, { -- cgit From efaf4732e26e2f0fbfab947296141376223b30d7 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 27 Oct 2019 15:26:32 -0700 Subject: lua/executor.c: use TRY_WRAP --- src/nvim/lua/executor.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index b7c1d57964..c7ff163f83 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -563,11 +563,8 @@ int nlua_call(lua_State *lstate) } } + TRY_WRAP({ // TODO(bfredl): this should be simplified in error handling refactor - struct msglist **saved_msg_list = msg_list; - struct msglist *private_msg_list = NULL; - msg_list = &private_msg_list; - force_abort = false; suppress_errthrow = false; current_exception = NULL; @@ -577,7 +574,7 @@ int nlua_call(lua_State *lstate) typval_T rettv; int dummy; // call_func() retval is deceptive, ignore it. Instead we set `msg_list` - // (see above) to capture abort-causing non-exception errors. + // (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); @@ -585,8 +582,7 @@ int nlua_call(lua_State *lstate) nlua_push_typval(lstate, &rettv, false); } tv_clear(&rettv); - - msg_list = saved_msg_list; + }); free_vim_args: while (i > 0) { -- cgit From 9ef16a1628722958b6e14fe9274006e50ed6682d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 27 Oct 2019 15:05:59 -0700 Subject: doc: vim.fn, vim.call(), vim.api [ci skip] --- src/nvim/lua/vim.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 5514819a02..adb90084db 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -165,6 +165,20 @@ end --- Paste handler, invoked by |nvim_paste()| when a conforming UI --- (such as the |TUI|) pastes text into the editor. --- +--- Example: To remove ANSI color codes when pasting: +---
+--- vim.paste = (function()
+---   local overridden = vim.paste
+---   return function(lines, phase)
+---     for i,line in ipairs(lines) do
+---       -- Scrub ANSI color codes from paste input.
+---       lines[i] = line:gsub('\27%[[0-9;mK]+', '')
+---     end
+---     overridden(lines, phase)
+---   end
+--- end)()
+--- 
+--- --@see |paste| --- --@param lines |readfile()|-style list of lines to paste. |channel-lines| -- cgit From 7a23b67d3594ffb8b6d8629fd9ca1ef8147596db Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 9 Nov 2019 21:18:51 -0800 Subject: paste: Select-mode, Visual-mode #11360 fix #11344 --- src/nvim/lua/vim.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index adb90084db..1ebdde99d5 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -206,8 +206,11 @@ paste = (function() local line1 = lines[1]:gsub('<', ''):gsub('[\r\n\012\027]', ' ') -- Scrub. vim.api.nvim_input(line1) vim.api.nvim_set_option('paste', false) - elseif mode ~= 'c' then -- Else: discard remaining cmdline-mode chunks. - if phase < 2 and mode ~= 'i' and mode ~= 'R' and mode ~= 't' then + elseif mode ~= 'c' then + if phase < 2 and mode:find('^[vV\22sS\19]') then + vim.api.nvim_command([[exe "normal! \"]]) + vim.api.nvim_put(lines, 'c', false, true) + elseif phase < 2 and not mode:find('^[iRt]') then vim.api.nvim_put(lines, 'c', true, true) -- XXX: Normal-mode: workaround bad cursor-placement after first chunk. vim.api.nvim_command('normal! a') -- cgit From 474d0bcbf724c7eed740f60391a0ed35d651e1d3 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 27 Oct 2019 20:49:03 +0100 Subject: lua: vim.rpcrequest, vim.rpcnotify, vim.NIL --- src/nvim/lua/converter.c | 40 +++++++++++++++++++++-- src/nvim/lua/executor.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/lua/executor.h | 2 ++ 3 files changed, 123 insertions(+), 2 deletions(-) (limited to 'src/nvim/lua') 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); \ -- cgit From f59e1f58a2ed24198fb1653edeffb6ab5ec2bbdb Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 10 Nov 2019 23:07:36 -0800 Subject: Lua: mark some functions as "private" Problem: scripts/gen_vimdoc.py gets confused and tries to generate docs for `fn_index` and `func`. Solution: Rename them to be private. --- src/nvim/lua/vim.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 1ebdde99d5..2b4c5486c7 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -261,14 +261,14 @@ end -- vim.fn.{func}(...) -local function fn_index(t, key) - local function func(...) +local function _fn_index(t, key) + local function _fn(...) return vim.call(key, ...) end - t[key] = func - return func + t[key] = _fn + return _fn end -local fn = setmetatable({}, {__index=fn_index}) +local fn = setmetatable({}, {__index=_fn_index}) local module = { _update_package_paths = _update_package_paths, -- cgit From 00dc12c5d8454a2d3c6806710f63bbb446076e96 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 13 Nov 2019 12:55:26 -0800 Subject: lua LSP client: initial implementation (#11336) Mainly configuration and RPC infrastructure can be considered "done". Specific requests and their callbacks will be improved later (and also served by plugins). There are also some TODO:s for the client itself, like incremental updates. Co-authored by at-tjdevries and at-h-michael, with many review/suggestion contributions. --- src/nvim/lua/vim.lua | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 2b4c5486c7..ce24d1716d 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -256,6 +256,13 @@ local function __index(t, key) -- Expose all `vim.shared` functions on the `vim` module. t[key] = require('vim.shared')[key] return t[key] + elseif require('vim.uri')[key] ~= nil then + -- Expose all `vim.uri` functions on the `vim` module. + t[key] = require('vim.uri')[key] + return t[key] + elseif key == 'lsp' then + t.lsp = require('vim.lsp') + return t.lsp end end -- cgit From dab40f43b18d35b283804ecb033310198cbf7548 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Wed, 30 Oct 2019 20:53:09 +0100 Subject: Add v:lua.func() vimL syntax for calling lua Also simplify error messages when calling lua from vimL. --- src/nvim/lua/executor.c | 114 ++++++++++++++++++++++++++---------------------- 1 file changed, 62 insertions(+), 52 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 5e5cb0cea5..093c130c5f 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -48,9 +48,6 @@ typedef struct { # include "lua/executor.c.generated.h" #endif -/// Name of the run code for use in messages -#define NLUA_EVAL_NAME "" - /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. @@ -397,29 +394,6 @@ static lua_State *nlua_enter(void) return lstate; } -/// Execute lua string -/// -/// @param[in] str String to execute. -/// @param[out] ret_tv Location where result will be saved. -/// -/// @return Result of the execution. -void executor_exec_lua(const String str, typval_T *const ret_tv) - FUNC_ATTR_NONNULL_ALL -{ - lua_State *const lstate = nlua_enter(); - - if (luaL_loadbuffer(lstate, str.data, str.size, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s")); - return; - } - if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s")); - return; - } - - nlua_pop_typval(lstate, ret_tv); -} - static void nlua_print_event(void **argv) { char *str = argv[0]; @@ -732,10 +706,6 @@ void executor_eval_lua(const String str, typval_T *const arg, typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { - lua_State *const lstate = nlua_enter(); - - garray_T str_ga; - ga_init(&str_ga, 1, 80); #define EVALHEADER "local _A=select(1,...) return (" const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str.size + 1; char *lcmd; @@ -748,30 +718,71 @@ void executor_eval_lua(const String str, typval_T *const arg, memcpy(lcmd + sizeof(EVALHEADER) - 1, str.data, str.size); lcmd[lcmd_len - 1] = ')'; #undef EVALHEADER - if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { - nlua_error(lstate, - _("E5107: Error while creating lua chunk for luaeval(): %.*s")); - if (lcmd != (char *)IObuff) { - xfree(lcmd); - } - return; - } + typval_exec_lua(lcmd, lcmd_len, "luaeval()", arg, 1, true, ret_tv); + if (lcmd != (char *)IObuff) { xfree(lcmd); } +} - if (arg->v_type == VAR_UNKNOWN) { - lua_pushnil(lstate); +void executor_call_lua(const char *str, size_t len, typval_T *const args, + int argcount, typval_T *ret_tv) + FUNC_ATTR_NONNULL_ALL +{ +#define CALLHEADER "return " +#define CALLSUFFIX "(...)" + const size_t lcmd_len = sizeof(CALLHEADER) - 1 + len + sizeof(CALLSUFFIX) - 1; + char *lcmd; + if (lcmd_len < IOSIZE) { + lcmd = (char *)IObuff; } else { - nlua_push_typval(lstate, arg, true); + lcmd = xmalloc(lcmd_len); + } + memcpy(lcmd, CALLHEADER, sizeof(CALLHEADER) - 1); + memcpy(lcmd + sizeof(CALLHEADER) - 1, str, len); + memcpy(lcmd + sizeof(CALLHEADER) - 1 + len, CALLSUFFIX, + sizeof(CALLSUFFIX) - 1); +#undef CALLHEADER +#undef CALLSUFFIX + + typval_exec_lua(lcmd, lcmd_len, "v:lua", args, argcount, false, ret_tv); + + if (lcmd != (char *)IObuff) { + xfree(lcmd); + } +} + +static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, + typval_T *const args, int argcount, bool special, + typval_T *ret_tv) +{ + if (check_restricted() || check_secure()) { + ret_tv->v_type = VAR_NUMBER; + ret_tv->vval.v_number = 0; + return; } - if (lua_pcall(lstate, 1, 1, 0)) { - nlua_error(lstate, - _("E5108: Error while calling lua chunk for luaeval(): %.*s")); + + lua_State *const lstate = nlua_enter(); + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, name)) { + nlua_error(lstate, _("E5107: Error loading lua %.*s")); return; } - nlua_pop_typval(lstate, ret_tv); + 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); + } + } + if (lua_pcall(lstate, argcount, ret_tv ? 1 : 0, 0)) { + nlua_error(lstate, _("E5108: Error executing lua %.*s")); + return; + } + + if (ret_tv) { + nlua_pop_typval(lstate, ret_tv); + } } /// Execute lua string @@ -857,9 +868,8 @@ void ex_lua(exarg_T *const eap) xfree(code); return; } - typval_T tv = { .v_type = VAR_UNKNOWN }; - executor_exec_lua((String) { .data = code, .size = len }, &tv); - tv_clear(&tv); + typval_exec_lua(code, len, ":lua", NULL, 0, false, NULL); + xfree(code); } @@ -897,8 +907,8 @@ void ex_luado(exarg_T *const eap) #undef DOSTART #undef DOEND - if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s")); + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, ":luado")) { + nlua_error(lstate, _("E5109: Error loading lua: %.*s")); if (lcmd_len >= IOSIZE) { xfree(lcmd); } @@ -908,7 +918,7 @@ void ex_luado(exarg_T *const eap) xfree(lcmd); } if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5110: Error while creating lua function: %.*s")); + nlua_error(lstate, _("E5110: Error executing lua: %.*s")); return; } for (linenr_T l = eap->line1; l <= eap->line2; l++) { @@ -919,7 +929,7 @@ void ex_luado(exarg_T *const eap) lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false)); lua_pushnumber(lstate, (lua_Number)l); if (lua_pcall(lstate, 2, 1, 0)) { - nlua_error(lstate, _("E5111: Error while calling lua function: %.*s")); + nlua_error(lstate, _("E5111: Error calling lua: %.*s")); break; } if (lua_isstring(lstate, -1)) { -- cgit From af53a0c0123338575dd59934449d7fe836835d1c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 17 Nov 2019 19:06:59 -0800 Subject: doc: Lua [ci skip] #11378 - Rework :help lua-commands - Rename if_lua.txt => lua.txt --- src/nvim/lua/vim.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index ce24d1716d..1665a55aff 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -167,8 +167,7 @@ end --- --- Example: To remove ANSI color codes when pasting: ---
---- vim.paste = (function()
----   local overridden = vim.paste
+--- vim.paste = (function(overridden)
 ---   return function(lines, phase)
 ---     for i,line in ipairs(lines) do
 ---       -- Scrub ANSI color codes from paste input.
@@ -176,7 +175,7 @@ end
 ---     end
 ---     overridden(lines, phase)
 ---   end
---- end)()
+--- end)(vim.paste)
 --- 
--- --@see |paste| -- cgit From d0d38fc36e0c1602186aa540417070fa6c1e2746 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Sun, 24 Nov 2019 02:28:48 -0800 Subject: Lua: vim.env, vim.{g,v,w,bo,wo} #11442 - Add vim variable meta accessors: vim.env, vim.{g,v,w,bo,wo} - Redo gen_char_blob to generate multiple blobs instead of just one so that multiple Lua modules can be inlined. - Reorder vim.lua inclusion so that it can use previously defined C functions and utility functions like vim.shared and vim.inspect things. - Inline shared.lua into nvim, but also keep it available in runtime. --- src/nvim/lua/executor.c | 25 ++++-- src/nvim/lua/vim.lua | 227 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 172 insertions(+), 80 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 093c130c5f..5450f62f54 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -268,12 +268,7 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL #endif // vim - const char *code = (char *)&vim_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua") - || lua_pcall(lstate, 0, LUA_MULTRET, 0)) { - nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); - return 1; - } + lua_newtable(lstate); // vim.api nlua_add_api_functions(lstate); // vim.types, vim.type_idx, vim.val_idx @@ -334,6 +329,24 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setglobal(lstate, "vim"); + { + const char *code = (char *)&shared_module[0]; + if (luaL_loadbuffer(lstate, code, strlen(code), "@shared.lua") + || lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("E5106: Error while creating shared module: %.*s")); + return 1; + } + } + + { + const char *code = (char *)&vim_module[0]; + if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua") + || lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); + return 1; + } + } + return 0; } diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 1665a55aff..8019511317 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -33,35 +33,35 @@ -- - https://github.com/bakpakin/Fennel (pretty print, repl) -- - https://github.com/howl-editor/howl/tree/master/lib/howl/util +local vim = vim +assert(vim) -- Internal-only until comments in #8107 are addressed. -- Returns: -- {errcode}, {output} -local function _system(cmd) - local out = vim.api.nvim_call_function('system', { cmd }) - local err = vim.api.nvim_get_vvar('shell_error') +function vim._system(cmd) + local out = vim.fn.system(cmd) + local err = vim.v.shell_error return err, out end -- Gets process info from the `ps` command. -- Used by nvim_get_proc() as a fallback. -local function _os_proc_info(pid) +function vim._os_proc_info(pid) if pid == nil or pid <= 0 or type(pid) ~= 'number' then error('invalid pid') end local cmd = { 'ps', '-p', pid, '-o', 'comm=', } - local err, name = _system(cmd) - if 1 == err and string.gsub(name, '%s*', '') == '' then + local err, name = vim._system(cmd) + if 1 == err and vim.trim(name) == '' then return {} -- Process not found. elseif 0 ~= err then - local args_str = vim.api.nvim_call_function('string', { cmd }) - error('command failed: '..args_str) + error('command failed: '..vim.fn.string(cmd)) end - local _, ppid = _system({ 'ps', '-p', pid, '-o', 'ppid=', }) + local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=', }) -- Remove trailing whitespace. - name = string.gsub(string.gsub(name, '%s+$', ''), '^.*/', '') - ppid = string.gsub(ppid, '%s+$', '') - ppid = tonumber(ppid) == nil and -1 or tonumber(ppid) + name = vim.trim(name):gsub('^.*/', '') + ppid = tonumber(ppid) or -1 return { name = name, pid = pid, @@ -71,20 +71,19 @@ end -- Gets process children from the `pgrep` command. -- Used by nvim_get_proc_children() as a fallback. -local function _os_proc_children(ppid) +function vim._os_proc_children(ppid) if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then error('invalid ppid') end local cmd = { 'pgrep', '-P', ppid, } - local err, rv = _system(cmd) - if 1 == err and string.gsub(rv, '%s*', '') == '' then + local err, rv = vim._system(cmd) + if 1 == err and vim.trim(rv) == '' then return {} -- Process not found. elseif 0 ~= err then - local args_str = vim.api.nvim_call_function('string', { cmd }) - error('command failed: '..args_str) + error('command failed: '..vim.fn.string(cmd)) end local children = {} - for s in string.gmatch(rv, '%S+') do + for s in rv:gmatch('%S+') do local i = tonumber(s) if i ~= nil then table.insert(children, i) @@ -98,7 +97,7 @@ end -- Last inserted paths. Used to clear out items from package.[c]path when they -- are no longer in &runtimepath. local last_nvim_paths = {} -local function _update_package_paths() +function vim._update_package_paths() local cur_nvim_paths = {} local rtps = vim.api.nvim_list_runtime_paths() local sep = package.config:sub(1, 1) @@ -162,35 +161,35 @@ local function inspect(object, options) -- luacheck: no unused error(object, options) -- Stub for gen_vimdoc.py end ---- Paste handler, invoked by |nvim_paste()| when a conforming UI ---- (such as the |TUI|) pastes text into the editor. ---- ---- Example: To remove ANSI color codes when pasting: ----
---- vim.paste = (function(overridden)
----   return function(lines, phase)
----     for i,line in ipairs(lines) do
----       -- Scrub ANSI color codes from paste input.
----       lines[i] = line:gsub('\27%[[0-9;mK]+', '')
----     end
----     overridden(lines, phase)
----   end
---- end)(vim.paste)
---- 
---- ---@see |paste| ---- ---@param lines |readfile()|-style list of lines to paste. |channel-lines| ---@param phase -1: "non-streaming" paste: the call contains all lines. ---- If paste is "streamed", `phase` indicates the stream state: ---- - 1: starts the paste (exactly once) ---- - 2: continues the paste (zero or more times) ---- - 3: ends the paste (exactly once) ---@returns false if client should cancel the paste. -local function paste(lines, phase) end -- luacheck: no unused -paste = (function() +do local tdots, tick, got_line1 = 0, 0, false - return function(lines, phase) + + --- Paste handler, invoked by |nvim_paste()| when a conforming UI + --- (such as the |TUI|) pastes text into the editor. + --- + --- Example: To remove ANSI color codes when pasting: + ---
+  --- vim.paste = (function(overridden)
+  ---   return function(lines, phase)
+  ---     for i,line in ipairs(lines) do
+  ---       -- Scrub ANSI color codes from paste input.
+  ---       lines[i] = line:gsub('\27%[[0-9;mK]+', '')
+  ---     end
+  ---     overridden(lines, phase)
+  ---   end
+  --- end)(vim.paste)
+  --- 
+ --- + --@see |paste| + --- + --@param lines |readfile()|-style list of lines to paste. |channel-lines| + --@param phase -1: "non-streaming" paste: the call contains all lines. + --- If paste is "streamed", `phase` indicates the stream state: + --- - 1: starts the paste (exactly once) + --- - 2: continues the paste (zero or more times) + --- - 3: ends the paste (exactly once) + --@returns false if client should cancel the paste. + function vim.paste(lines, phase) local call = vim.api.nvim_call_function local now = vim.loop.now() local mode = call('mode', {}):sub(1,1) @@ -230,20 +229,33 @@ paste = (function() end return true -- Paste will not continue if not returning `true`. end -end)() +end --- Defers callback `cb` until the Nvim API is safe to call. --- ---@see |lua-loop-callbacks| ---@see |vim.schedule()| ---@see |vim.in_fast_event()| -local function schedule_wrap(cb) +function vim.schedule_wrap(cb) return (function (...) local args = {...} vim.schedule(function() cb(unpack(args)) end) end) end +-- vim.fn.{func}(...) +vim.fn = setmetatable({}, { + __index = function(t, key) + local function _fn(...) + return vim.call(key, ...) + end + t[key] = _fn + return _fn + 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) if key == 'inspect' then t.inspect = require('vim.inspect') @@ -251,10 +263,6 @@ local function __index(t, key) elseif key == 'treesitter' then t.treesitter = require('vim.treesitter') return t.treesitter - elseif require('vim.shared')[key] ~= nil then - -- Expose all `vim.shared` functions on the `vim` module. - t[key] = require('vim.shared')[key] - return t[key] elseif require('vim.uri')[key] ~= nil then -- Expose all `vim.uri` functions on the `vim` module. t[key] = require('vim.uri')[key] @@ -265,29 +273,100 @@ local function __index(t, key) end end +setmetatable(vim, { + __index = __index +}) --- vim.fn.{func}(...) -local function _fn_index(t, key) - local function _fn(...) - return vim.call(key, ...) +do + local a = vim.api + local validate = vim.validate + local function make_meta_accessor(get, set, del) + validate { + get = {get, 'f'}; + set = {set, 'f'}; + del = {del, 'f', true}; + } + local mt = {} + if del then + function mt:__newindex(k, v) + if v == nil then + return del(k) + end + return set(k, v) + end + else + function mt:__newindex(k, v) + return set(k, v) + end + end + function mt:__index(k) + return get(k) + end + return setmetatable({}, mt) + end + local function pcall_ret(status, ...) + if status then return ... end + end + local function nil_wrap(fn) + return function(...) + return pcall_ret(pcall(fn, ...)) + end + end + vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var) + vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar) + vim.o = make_meta_accessor(nil_wrap(a.nvim_get_option), a.nvim_set_option) + vim.env = make_meta_accessor(vim.fn.getenv, vim.fn.setenv) + -- TODO(ashkan) if/when these are available from an API, generate them + -- instead of hardcoding. + local window_options = { + arab = true; arabic = true; breakindent = true; breakindentopt = true; + bri = true; briopt = true; cc = true; cocu = true; + cole = true; colorcolumn = true; concealcursor = true; conceallevel = true; + crb = true; cuc = true; cul = true; cursorbind = true; + cursorcolumn = true; cursorline = true; diff = true; fcs = true; + fdc = true; fde = true; fdi = true; fdl = true; + fdm = true; fdn = true; fdt = true; fen = true; + fillchars = true; fml = true; fmr = true; foldcolumn = true; + foldenable = true; foldexpr = true; foldignore = true; foldlevel = true; + foldmarker = true; foldmethod = true; foldminlines = true; foldnestmax = true; + foldtext = true; lbr = true; lcs = true; linebreak = true; + list = true; listchars = true; nu = true; number = true; + numberwidth = true; nuw = true; previewwindow = true; pvw = true; + relativenumber = true; rightleft = true; rightleftcmd = true; rl = true; + rlc = true; rnu = true; scb = true; scl = true; + scr = true; scroll = true; scrollbind = true; signcolumn = true; + spell = true; statusline = true; stl = true; wfh = true; + wfw = true; winbl = true; winblend = true; winfixheight = true; + winfixwidth = true; winhighlight = true; winhl = true; wrap = true; + } + local function new_buf_opt_accessor(bufnr) + local function get(k) + if window_options[k] then + return a.nvim_err_writeln(k.." is a window option, not a buffer option") + end + return a.nvim_buf_get_option(bufnr, k) + end + local function set(k, v) + if window_options[k] then + return a.nvim_err_writeln(k.." is a window option, not a buffer option") + end + return a.nvim_buf_set_option(bufnr, k, v) + end + return make_meta_accessor(nil_wrap(get), set) + end + vim.bo = new_buf_opt_accessor(0) + getmetatable(vim.bo).__call = function(_, bufnr) + return new_buf_opt_accessor(bufnr) + end + local function new_win_opt_accessor(winnr) + local function get(k) return a.nvim_win_get_option(winnr, k) end + local function set(k, v) return a.nvim_win_set_option(winnr, k, v) end + return make_meta_accessor(nil_wrap(get), set) + end + vim.wo = new_win_opt_accessor(0) + getmetatable(vim.wo).__call = function(_, winnr) + return new_win_opt_accessor(winnr) end - t[key] = _fn - return _fn end -local fn = setmetatable({}, {__index=_fn_index}) - -local module = { - _update_package_paths = _update_package_paths, - _os_proc_children = _os_proc_children, - _os_proc_info = _os_proc_info, - _system = _system, - paste = paste, - schedule_wrap = schedule_wrap, - fn=fn, -} - -setmetatable(module, { - __index = __index -}) return module -- cgit From a76a669ac24ec91144153b65e0a0dc5598802653 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Tue, 26 Nov 2019 17:57:53 +0100 Subject: lua: make vim.wo and vim.bo used nested indexing for specified handle Also missing option should be an error. Options are functionality, not arbitrary variable names (as for vim.g) --- src/nvim/lua/vim.lua | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 8019511317..e13b44a8ed 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -314,7 +314,7 @@ do end vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var) vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar) - vim.o = make_meta_accessor(nil_wrap(a.nvim_get_option), a.nvim_set_option) + vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option) vim.env = make_meta_accessor(vim.fn.getenv, vim.fn.setenv) -- TODO(ashkan) if/when these are available from an API, generate them -- instead of hardcoding. @@ -344,29 +344,31 @@ do if window_options[k] then return a.nvim_err_writeln(k.." is a window option, not a buffer option") end - return a.nvim_buf_get_option(bufnr, k) + if bufnr == nil and type(k) == "number" then + return new_buf_opt_accessor(k) + end + return a.nvim_buf_get_option(bufnr or 0, k) end local function set(k, v) if window_options[k] then return a.nvim_err_writeln(k.." is a window option, not a buffer option") end - return a.nvim_buf_set_option(bufnr, k, v) + return a.nvim_buf_set_option(bufnr or 0, k, v) end - return make_meta_accessor(nil_wrap(get), set) - end - vim.bo = new_buf_opt_accessor(0) - getmetatable(vim.bo).__call = function(_, bufnr) - return new_buf_opt_accessor(bufnr) + return make_meta_accessor(get, set) end + vim.bo = new_buf_opt_accessor(nil) local function new_win_opt_accessor(winnr) - local function get(k) return a.nvim_win_get_option(winnr, k) end - local function set(k, v) return a.nvim_win_set_option(winnr, k, v) end - return make_meta_accessor(nil_wrap(get), set) - end - vim.wo = new_win_opt_accessor(0) - getmetatable(vim.wo).__call = function(_, winnr) - return new_win_opt_accessor(winnr) + local function get(k) + if winnr == nil and type(k) == "number" then + return new_win_opt_accessor(k) + end + return a.nvim_win_get_option(winnr or nil, k) + end + local function set(k, v) return a.nvim_win_set_option(winnr or nil, k, v) end + return make_meta_accessor(get, set) end + vim.wo = new_win_opt_accessor(nil) end return module -- cgit From 001e69cd4602e84219fd7cfd8ade62f0cb24097c Mon Sep 17 00:00:00 2001 From: Brian Wignall Date: Tue, 26 Nov 2019 07:15:14 -0500 Subject: doc: fix typos close #11459 --- src/nvim/lua/converter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 44fe60e9c8..09d1a68898 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -1225,7 +1225,7 @@ GENERATE_INDEX_FUNCTION(Tabpage) #undef GENERATE_INDEX_FUNCTION -/// Record some auxilary values in vim module +/// Record some auxiliary values in vim module /// /// Assumes that module table is on top of the stack. /// -- cgit From edca84cfc91e637e97fcadd484a57911b20cfe75 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Sun, 1 Dec 2019 05:04:57 -0800 Subject: Return nil instead of NIL for vim.env (#11486) --- src/nvim/lua/vim.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index e13b44a8ed..00f7c8a48b 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -315,7 +315,14 @@ do vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var) vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar) vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option) - vim.env = make_meta_accessor(vim.fn.getenv, vim.fn.setenv) + local function getenv(k) + local v = vim.fn.getenv(k) + if v == vim.NIL then + return nil + end + return v + end + vim.env = make_meta_accessor(getenv, vim.fn.setenv) -- TODO(ashkan) if/when these are available from an API, generate them -- instead of hardcoding. local window_options = { -- cgit From e6da21d12895e2f34c6ce41bb16400d5eef3ea12 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Sun, 1 Dec 2019 05:28:53 -0800 Subject: Add vim.cmd as an alias for nvim_command (#11446) --- src/nvim/lua/vim.lua | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 00f7c8a48b..090869c68e 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -277,6 +277,10 @@ setmetatable(vim, { __index = __index }) +-- An easier alias for commands. +vim.cmd = vim.api.nvim_command + +-- These are the vim.env/v/g/o/bo/wo variable magic accessors. do local a = vim.api local validate = vim.validate -- cgit From a3b6c2a3dc5576db45fe4e893cfb8482af591c92 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 2 Dec 2019 00:46:46 -0800 Subject: API: rename nvim_execute_lua => nvim_exec_lua - We already find ourselves renaming nvim_execute_lua in tests and scripts, which suggests "exec" is the verb we actually want. - Add "exec" verb to `:help dev-api`. --- src/nvim/lua/executor.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 5450f62f54..25f4be1c4d 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -798,9 +798,9 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, } } -/// Execute lua string +/// Execute Lua string /// -/// Used for nvim_execute_lua(). +/// Used for nvim_exec_lua(). /// /// @param[in] str String to execute. /// @param[in] args array of ... args -- cgit From 0e6c6261e1b5a518995f53ca3898a68b9bb02012 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Sat, 7 Dec 2019 03:34:02 -0800 Subject: Fix access on vim.wo (#11517) * Add more tests for vim.wo --- src/nvim/lua/vim.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 090869c68e..e7c5458102 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -374,9 +374,9 @@ do if winnr == nil and type(k) == "number" then return new_win_opt_accessor(k) end - return a.nvim_win_get_option(winnr or nil, k) + return a.nvim_win_get_option(winnr or 0, k) end - local function set(k, v) return a.nvim_win_set_option(winnr or nil, k, v) end + local function set(k, v) return a.nvim_win_set_option(winnr or 0, k, v) end return make_meta_accessor(get, set) end vim.wo = new_win_opt_accessor(nil) -- cgit From 440695c29696f261337227e5c419aa1cf313c2dd Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 28 Sep 2019 14:27:20 +0200 Subject: tree-sitter: implement query functionality and highlighting prototype [skip.lint] --- src/nvim/lua/executor.c | 25 +++- src/nvim/lua/treesitter.c | 321 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 321 insertions(+), 25 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 25f4be1c4d..1d3d9929d3 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -835,7 +835,7 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err) } Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args, - bool retval) + bool retval, Error *err) { lua_State *const lstate = nlua_enter(); nlua_pushref(lstate, ref); @@ -845,16 +845,24 @@ Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args, } if (lua_pcall(lstate, (int)args.size+1, retval ? 1 : 0, 0)) { - // TODO(bfredl): callbacks:s might not always be msg-safe, for instance - // lua callbacks for redraw events. Later on let the caller deal with the - // error instead. - nlua_error(lstate, _("Error executing lua callback: %.*s")); + // if err is passed, the caller will deal with the error. + if (err) { + size_t len; + const char *errstr = lua_tolstring(lstate, -1, &len); + api_set_error(err, kErrorTypeException, + "Error executing lua: %.*s", (int)len, errstr); + } else { + nlua_error(lstate, _("Error executing lua callback: %.*s")); + } return NIL; } - Error err = ERROR_INIT; if (retval) { - return nlua_pop_Object(lstate, false, &err); + Error dummy = ERROR_INIT; + if (err == NULL) { + err = &dummy; + } + return nlua_pop_Object(lstate, false, err); } else { return NIL; } @@ -1007,4 +1015,7 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, tslua_inspect_lang); lua_setfield(lstate, -2, "_ts_inspect_language"); + + lua_pushcfunction(lstate, ts_lua_parse_query); + lua_setfield(lstate, -2, "_ts_parse_query"); } diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index d2072402bb..874fabd89f 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -26,6 +26,11 @@ typedef struct { TSTree *tree; // internal tree, used for editing/reparsing } TSLua_parser; +typedef struct { + TSQueryCursor *cursor; + int predicated_match; +} TSLua_cursor; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/treesitter.c.generated.h" #endif @@ -66,6 +71,20 @@ static struct luaL_Reg node_meta[] = { { "descendant_for_range", node_descendant_for_range }, { "named_descendant_for_range", node_named_descendant_for_range }, { "parent", node_parent }, + { "_rawquery", node_rawquery }, + { NULL, NULL } +}; + +static struct luaL_Reg query_meta[] = { + { "__gc", query_gc }, + { "__tostring", query_tostring }, + { "inspect", query_inspect }, + { NULL, NULL } +}; + +// cursor is not exposed, but still needs garbage collection +static struct luaL_Reg querycursor_meta[] = { + { "__gc", querycursor_gc }, { NULL, NULL } }; @@ -96,6 +115,8 @@ void tslua_init(lua_State *L) build_meta(L, "treesitter_parser", parser_meta); build_meta(L, "treesitter_tree", tree_meta); build_meta(L, "treesitter_node", node_meta); + build_meta(L, "treesitter_query", query_meta); + build_meta(L, "treesitter_querycursor", querycursor_meta); } int tslua_register_lang(lua_State *L) @@ -276,13 +297,33 @@ static int parser_parse_buf(lua_State *L) } TSInput input = { payload, input_cb, TSInputEncodingUTF8 }; TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); + + uint32_t n_ranges = 0; + TSRange *changed = p->tree ? ts_tree_get_changed_ranges(p->tree, new_tree, + &n_ranges) : NULL; if (p->tree) { ts_tree_delete(p->tree); } p->tree = new_tree; tslua_push_tree(L, p->tree); - return 1; + + lua_createtable(L, n_ranges, 0); + for (size_t i = 0; i < n_ranges; i++) { + lua_createtable(L, 4, 0); + lua_pushinteger(L, changed[i].start_point.row); + lua_rawseti(L, -2, 1); + lua_pushinteger(L, changed[i].start_point.column); + lua_rawseti(L, -2, 2); + lua_pushinteger(L, changed[i].end_point.row); + lua_rawseti(L, -2, 3); + lua_pushinteger(L, changed[i].end_point.column); + lua_rawseti(L, -2, 4); + + lua_rawseti(L, -2, i+1); + } + xfree(changed); + return 2; } static int parser_tree(lua_State *L) @@ -383,7 +424,7 @@ static int tree_root(lua_State *L) return 0; } TSNode root = ts_tree_root_node(tree); - push_node(L, root); + push_node(L, root, 1); return 1; } @@ -394,18 +435,19 @@ static int tree_root(lua_State *L) /// top of stack must either be the tree this node belongs to or another node /// of the same tree! This value is not popped. Can only be called inside a /// cfunction with the tslua environment. -static void push_node(lua_State *L, TSNode node) +static void push_node(lua_State *L, TSNode node, int uindex) { + assert(uindex > 0 || uindex < -LUA_MINSTACK); if (ts_node_is_null(node)) { - lua_pushnil(L); // [src, nil] + lua_pushnil(L); // [nil] return; } - TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata] + TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [udata] *ud = node; - lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_node"); // [src, udata, meta] - lua_setmetatable(L, -2); // [src, udata] - lua_getfenv(L, -2); // [src, udata, reftable] - lua_setfenv(L, -2); // [src, udata] + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_node"); // [udata, meta] + lua_setmetatable(L, -2); // [udata] + lua_getfenv(L, uindex); // [udata, reftable] + lua_setfenv(L, -2); // [udata] } static bool node_check(lua_State *L, TSNode *res) @@ -586,8 +628,7 @@ static int node_child(lua_State *L) long num = lua_tointeger(L, 2); TSNode child = ts_node_child(node, (uint32_t)num); - lua_pushvalue(L, 1); - push_node(L, child); + push_node(L, child, 1); return 1; } @@ -600,8 +641,7 @@ static int node_named_child(lua_State *L) long num = lua_tointeger(L, 2); TSNode child = ts_node_named_child(node, (uint32_t)num); - lua_pushvalue(L, 1); - push_node(L, child); + push_node(L, child, 1); return 1; } @@ -617,8 +657,7 @@ static int node_descendant_for_range(lua_State *L) (uint32_t)lua_tointeger(L, 5) }; TSNode child = ts_node_descendant_for_point_range(node, start, end); - lua_pushvalue(L, 1); - push_node(L, child); + push_node(L, child, 1); return 1; } @@ -634,8 +673,7 @@ static int node_named_descendant_for_range(lua_State *L) (uint32_t)lua_tointeger(L, 5) }; TSNode child = ts_node_named_descendant_for_point_range(node, start, end); - lua_pushvalue(L, 1); - push_node(L, child); + push_node(L, child, 1); return 1; } @@ -646,7 +684,254 @@ static int node_parent(lua_State *L) return 0; } TSNode parent = ts_node_parent(node); - push_node(L, parent); + push_node(L, parent, 1); + return 1; +} + +/// assumes the match table being on top of the stack +static void set_match(lua_State *L, TSQueryMatch *match, int nodeidx) +{ + for (int i = 0; i < match->capture_count; i++) { + push_node(L, match->captures[i].node, nodeidx); + lua_rawseti(L, -2, match->captures[i].index+1); + } +} + +static int query_next_match(lua_State *L) +{ + TSLua_cursor *ud = lua_touserdata(L, lua_upvalueindex(1)); + TSQueryCursor *cursor = ud->cursor; + + TSQuery *query = query_check(L, lua_upvalueindex(3)); + TSQueryMatch match; + if (ts_query_cursor_next_match(cursor, &match)) { + lua_pushinteger(L, match.pattern_index+1); // [index] + lua_createtable(L, ts_query_capture_count(query), 2); // [index, match] + set_match(L, &match, lua_upvalueindex(2)); + return 2; + } + return 0; +} + + +static int query_next_capture(lua_State *L) +{ + TSLua_cursor *ud = lua_touserdata(L, lua_upvalueindex(1)); + TSQueryCursor *cursor = ud->cursor; + + TSQuery *query = query_check(L, lua_upvalueindex(3)); + + if (ud->predicated_match > -1) { + lua_getfield(L, lua_upvalueindex(4), "active"); + bool active = lua_toboolean(L, -1); + lua_pop(L, 1); + if (!active) { + ts_query_cursor_remove_match(cursor, ud->predicated_match); + } + ud->predicated_match = -1; + } + + TSQueryMatch match; + uint32_t capture_index; + if (ts_query_cursor_next_capture(cursor, &match, &capture_index)) { + TSQueryCapture capture = match.captures[capture_index]; + + lua_pushinteger(L, capture.index+1); // [index] + push_node(L, capture.node, lua_upvalueindex(2)); // [index, node] + + uint32_t n_pred; + ts_query_predicates_for_pattern(query, match.pattern_index, &n_pred); + if (n_pred > 0 && capture_index == 0) { + lua_pushvalue(L, lua_upvalueindex(4)); // [index, node, match] + set_match(L, &match, lua_upvalueindex(2)); + lua_pushinteger(L, match.pattern_index+1); + lua_setfield(L, -2, "pattern"); + + if (match.capture_count > 1) { + ud->predicated_match = match.id; + lua_pushboolean(L, false); + lua_setfield(L, -2, "active"); + } + return 3; + } + return 2; + } + return 0; +} + +static int node_rawquery(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSQuery *query = query_check(L, 2); + // TODO(bfredl): these are expensive allegedly, + // use a reuse list later on? + TSQueryCursor *cursor = ts_query_cursor_new(); + ts_query_cursor_exec(cursor, query, node); + + bool captures = lua_toboolean(L, 3); + + if (lua_gettop(L) >= 4) { + int start = luaL_checkinteger(L, 4); + int end = lua_gettop(L) >= 5 ? luaL_checkinteger(L, 5) : MAXLNUM; + ts_query_cursor_set_point_range(cursor, + (TSPoint){ start, 0 }, (TSPoint){ end, 0 }); + } + + TSLua_cursor *ud = lua_newuserdata(L, sizeof(*ud)); // [udata] + ud->cursor = cursor; + ud->predicated_match = -1; + + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_querycursor"); + lua_setmetatable(L, -2); // [udata] + lua_pushvalue(L, 1); // [udata, node] + + // include query separately, as to keep a ref to it for gc + lua_pushvalue(L, 2); // [udata, node, query] + + if (captures) { + // placeholder for match state + lua_createtable(L, ts_query_capture_count(query), 2); // [u, n, q, match] + lua_pushcclosure(L, query_next_capture, 4); // [closure] + } else { + lua_pushcclosure(L, query_next_match, 3); // [closure] + } + return 1; } +static int querycursor_gc(lua_State *L) +{ + TSLua_cursor *ud = luaL_checkudata(L, 1, "treesitter_querycursor"); + ts_query_cursor_delete(ud->cursor); + return 0; +} + +// Query methods + +int ts_lua_parse_query(lua_State *L) +{ + if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { + 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); + } + + size_t len; + const char *src = lua_tolstring(L, 2, &len); + + uint32_t error_offset; + TSQueryError error_type; + TSQuery *query = ts_query_new(lang, src, len, &error_offset, &error_type); + + if (!query) { + return luaL_error(L, "query: %s at position %d", + query_err_string(error_type), (int)error_offset); + } + + TSQuery **ud = lua_newuserdata(L, sizeof(TSQuery *)); // [udata] + *ud = query; + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_query"); // [udata, meta] + lua_setmetatable(L, -2); // [udata] + return 1; +} + + +static const char *query_err_string(TSQueryError err) { + switch (err) { + case TSQueryErrorSyntax: return "invalid syntax"; + case TSQueryErrorNodeType: return "invalid node type"; + case TSQueryErrorField: return "invalid field"; + case TSQueryErrorCapture: return "invalid capture"; + default: return "error"; + } +} + +static TSQuery *query_check(lua_State *L, int index) +{ + TSQuery **ud = luaL_checkudata(L, index, "treesitter_query"); + return *ud; +} + +static int query_gc(lua_State *L) +{ + TSQuery *query = query_check(L, 1); + if (!query) { + return 0; + } + + ts_query_delete(query); + return 0; +} + +static int query_tostring(lua_State *L) +{ + lua_pushstring(L, ""); + return 1; +} + +static int query_inspect(lua_State *L) +{ + TSQuery *query = query_check(L, 1); + if (!query) { + return 0; + } + + uint32_t n_pat = ts_query_pattern_count(query); + lua_createtable(L, 0, 2); // [retval] + lua_createtable(L, n_pat, 1); // [retval, patterns] + for (size_t i = 0; i < n_pat; i++) { + uint32_t len; + const TSQueryPredicateStep *step = ts_query_predicates_for_pattern(query, + i, &len); + if (len == 0) { + continue; + } + lua_createtable(L, len/4, 1); // [retval, patterns, pat] + lua_createtable(L, 3, 0); // [retval, patterns, pat, pred] + int nextpred = 1; + int nextitem = 1; + for (size_t k = 0; k < len; k++) { + if (step[k].type == TSQueryPredicateStepTypeDone) { + lua_rawseti(L, -2, nextpred++); // [retval, patterns, pat] + lua_createtable(L, 3, 0); // [retval, patterns, pat, pred] + nextitem = 1; + continue; + } + + if (step[k].type == TSQueryPredicateStepTypeString) { + uint32_t strlen; + const char *str = ts_query_string_value_for_id(query, step[k].value_id, + &strlen); + lua_pushlstring(L, str, strlen); // [retval, patterns, pat, pred, item] + } else if (step[k].type == TSQueryPredicateStepTypeCapture) { + lua_pushnumber(L, step[k].value_id+1); // [..., pat, pred, item] + } else { + abort(); + } + lua_rawseti(L, -2, nextitem++); // [retval, patterns, pat, pred] + } + // last predicate should have ended with TypeDone + lua_pop(L, 1); // [retval, patters, pat] + lua_rawseti(L, -2, i+1); // [retval, patterns] + } + lua_setfield(L, -2, "patterns"); // [retval] + + uint32_t n_captures = ts_query_capture_count(query); + lua_createtable(L, n_captures, 0); // [retval, captures] + for (size_t i = 0; i < n_captures; i++) { + uint32_t strlen; + const char *str = ts_query_capture_name_for_id(query, i, &strlen); + lua_pushlstring(L, str, strlen); // [retval, captures, capture] + lua_rawseti(L, -2, i+1); + } + lua_setfield(L, -2, "captures"); // [retval] + + return 1; +} -- cgit From c740e3b4b5274e04b9a88a1467abb0fd5590301f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 25 Dec 2019 06:28:10 -0500 Subject: clang/'Logic error': set ret_tv if non-null --- src/nvim/lua/executor.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 1d3d9929d3..2cd6c0db66 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -770,8 +770,10 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, typval_T *ret_tv) { if (check_restricted() || check_secure()) { - ret_tv->v_type = VAR_NUMBER; - ret_tv->vval.v_number = 0; + if (ret_tv) { + ret_tv->v_type = VAR_NUMBER; + ret_tv->vval.v_number = 0; + } return; } -- cgit From ea4127e9a7a624484f51c21e17f37c766da15da0 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Wed, 27 Nov 2019 20:45:41 +0100 Subject: lua: metatable for empty dict value --- src/nvim/lua/converter.c | 13 +++++++++++++ src/nvim/lua/executor.c | 13 +++++++++++++ src/nvim/lua/executor.h | 1 + src/nvim/lua/vim.lua | 4 ++++ 4 files changed, 31 insertions(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 09d1a68898..fca74b5901 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -156,6 +156,13 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) && other_keys_num == 0 && ret.string_keys_num == 0)) { 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) { ret.type = kObjectTypeDictionary; } else { @@ -465,6 +472,8 @@ static bool typval_conv_special = false; nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \ } else { \ lua_createtable(lstate, 0, 0); \ + nlua_pushref(lstate, nlua_empty_dict_ref); \ + lua_setmetatable(lstate, -2); \ } \ } while (0) @@ -695,6 +704,10 @@ void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict, nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); } else { 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++) { nlua_push_String(lstate, dict.items[i].key, special); diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 2cd6c0db66..242d4e18d1 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -324,6 +324,13 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL nlua_nil_ref = nlua_ref(lstate, -1); 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 nlua_add_treesitter(lstate); @@ -665,6 +672,12 @@ static int nlua_nil_tostring(lua_State *lstate) return 1; } +static int nlua_empty_dict_tostring(lua_State *lstate) +{ + lua_pushstring(lstate, "vim.empty_dict()"); + 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 32f66b629c..3259fc0fa1 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -13,6 +13,7 @@ void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF); +EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF); #define set_api_error(s, err) \ do { \ diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index e7c5458102..8ba550ea31 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -243,6 +243,10 @@ function vim.schedule_wrap(cb) end) end +function vim.empty_dict() + return setmetatable({}, vim._empty_dict_mt) +end + -- vim.fn.{func}(...) vim.fn = setmetatable({}, { __index = function(t, key) -- cgit From 00c57c98dfb2df58875a3d9ad8fc557ec9a24cba Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 25 Jan 2020 13:43:41 +0100 Subject: treesitter: add standard &rtp/parser/ search path for parsers --- src/nvim/lua/executor.c | 5 ++++- src/nvim/lua/treesitter.c | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 242d4e18d1..9a8347cf19 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1025,9 +1025,12 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, create_tslua_parser); lua_setfield(lstate, -2, "_create_ts_parser"); - lua_pushcfunction(lstate, tslua_register_lang); + lua_pushcfunction(lstate, tslua_add_language); lua_setfield(lstate, -2, "_ts_add_language"); + lua_pushcfunction(lstate, tslua_has_language); + lua_setfield(lstate, -2, "_ts_has_language"); + lua_pushcfunction(lstate, tslua_inspect_lang); lua_setfield(lstate, -2, "_ts_inspect_language"); diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 874fabd89f..a420f79ffd 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -119,7 +119,14 @@ void tslua_init(lua_State *L) build_meta(L, "treesitter_querycursor", querycursor_meta); } -int tslua_register_lang(lua_State *L) +int tslua_has_language(lua_State *L) +{ + const char *lang_name = luaL_checkstring(L, 1); + lua_pushboolean(L, pmap_has(cstr_t)(langs, lang_name)); + return 1; +} + +int tslua_add_language(lua_State *L) { if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { return luaL_error(L, "string expected"); -- cgit From 6c5bbf07d988ef55e5e8ba8d70b62c1f0885261b Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Mon, 10 Feb 2020 00:33:26 -0800 Subject: eval.c: factor out eval/funcs.c #11828 close #11828 ref #5081 cf. vim patch 7.4.2063 --- src/nvim/lua/executor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 9a8347cf19..9e0063ebaa 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -528,7 +528,7 @@ int nlua_debug(lua_State *lstate) for (;;) { lua_settop(lstate, 0); typval_T input; - get_user_input(input_args, &input, false); + get_user_input(input_args, &input, false, false); msg_putchar('\n'); // Avoid outputting on input line. if (input.v_type != VAR_STRING || input.vval.v_string == NULL -- cgit From d50c1123d5616c9757bb5707416696cda1a43716 Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Mon, 10 Feb 2020 18:34:18 +0100 Subject: fix: includes --- src/nvim/lua/executor.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 9e0063ebaa..cadff961ce 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -25,6 +25,7 @@ #include "nvim/undo.h" #include "nvim/ascii.h" #include "nvim/change.h" +#include "nvim/eval/user_funcs.h" #ifdef WIN32 #include "nvim/os/os.h" -- cgit From 5e815edece308a91296720cd6cb8d988af6c90c8 Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Tue, 11 Feb 2020 16:19:14 +0100 Subject: rename: user_funcs -> userfunc Lets stick with vim for now --- src/nvim/lua/executor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index cadff961ce..632832416b 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -25,7 +25,7 @@ #include "nvim/undo.h" #include "nvim/ascii.h" #include "nvim/change.h" -#include "nvim/eval/user_funcs.h" +#include "nvim/eval/userfunc.h" #ifdef WIN32 #include "nvim/os/os.h" -- cgit From 9c00fea585ccab56a6044a174ce8d9a2c605c6cd Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Mon, 4 Nov 2019 20:40:30 +0100 Subject: lua: add regex support, and `@match` support in treesitter queries --- src/nvim/lua/executor.c | 152 ++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/lua/treesitter.c | 5 +- 2 files changed, 153 insertions(+), 4 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 9e0063ebaa..9167ec5e54 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -11,6 +11,7 @@ #include "nvim/func_attr.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/private/handle.h" #include "nvim/api/vim.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/vim.h" @@ -19,6 +20,7 @@ #include "nvim/message.h" #include "nvim/memline.h" #include "nvim/buffer_defs.h" +#include "nvim/regexp.h" #include "nvim/macros.h" #include "nvim/screen.h" #include "nvim/cursor.h" @@ -244,6 +246,14 @@ static int nlua_schedule(lua_State *const lstate) return 0; } +static struct luaL_Reg regex_meta[] = { + { "__gc", regex_gc }, + { "__tostring", regex_tostring }, + { "match_str", regex_match_str }, + { "match_line", regex_match_line }, + { NULL, NULL } +}; + /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. @@ -291,6 +301,15 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // call lua_pushcfunction(lstate, &nlua_call); lua_setfield(lstate, -2, "call"); + // regex + lua_pushcfunction(lstate, &nlua_regex); + lua_setfield(lstate, -2, "regex"); + + luaL_newmetatable(lstate, "nvim_regex"); + luaL_register(lstate, NULL, regex_meta); + lua_pushvalue(lstate, -1); // [meta, meta] + lua_setfield(lstate, -2, "__index"); // [meta] + lua_pop(lstate, 1); // don't use metatable now // rpcrequest lua_pushcfunction(lstate, &nlua_rpcrequest); @@ -1037,3 +1056,136 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, ts_lua_parse_query); lua_setfield(lstate, -2, "_ts_parse_query"); } + +static int nlua_regex(lua_State *lstate) +{ + Error err = ERROR_INIT; + const char *text = luaL_checkstring(lstate, 1); + regprog_T *prog = NULL; + + TRY_WRAP({ + try_start(); + prog = vim_regcomp((char_u *)text, RE_AUTO | RE_MAGIC | RE_STRICT); + try_end(&err); + }); + + if (ERROR_SET(&err)) { + return luaL_error(lstate, "couldn't parse regex: %s", err.msg); + } + assert(prog); + + regprog_T **p = lua_newuserdata(lstate, sizeof(regprog_T *)); + *p = prog; + + lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim_regex"); // [udata, meta] + lua_setmetatable(lstate, -2); // [udata] + return 1; +} + +static regprog_T **regex_check(lua_State *L) +{ + return luaL_checkudata(L, 1, "nvim_regex"); +} + + +static int regex_gc(lua_State *lstate) +{ + regprog_T **prog = regex_check(lstate); + vim_regfree(*prog); + return 0; +} + +static int regex_tostring(lua_State *lstate) +{ + lua_pushstring(lstate, ""); + return 1; +} + +static int regex_match(lua_State *lstate, regprog_T **prog, char_u *str) +{ + regmatch_T rm; + rm.regprog = *prog; + rm.rm_ic = false; + bool match = vim_regexec(&rm, str, 0); + *prog = rm.regprog; + + if (match) { + lua_pushinteger(lstate, (lua_Integer)(rm.startp[0]-str)); + lua_pushinteger(lstate, (lua_Integer)(rm.endp[0]-str)); + return 2; + } + return 0; +} + +static int regex_match_str(lua_State *lstate) +{ + regprog_T **prog = regex_check(lstate); + const char *str = luaL_checkstring(lstate, 2); + int nret = regex_match(lstate, prog, (char_u *)str); + + if (!*prog) { + return luaL_error(lstate, "regex: internal error"); + } + + return nret; +} + +static int regex_match_line(lua_State *lstate) +{ + regprog_T **prog = regex_check(lstate); + + int narg = lua_gettop(lstate); + if (narg < 3) { + return luaL_error(lstate, "not enough args"); + } + + long bufnr = luaL_checkinteger(lstate, 2); + long rownr = luaL_checkinteger(lstate, 3); + long start = 0, end = -1; + if (narg >= 4) { + start = luaL_checkinteger(lstate, 4); + } + if (narg >= 5) { + end = luaL_checkinteger(lstate, 5); + if (end < 0) { + return luaL_error(lstate, "invalid end"); + } + } + + buf_T *buf = bufnr ? handle_get_buffer((int)bufnr) : curbuf; + if (!buf || buf->b_ml.ml_mfp == NULL) { + return luaL_error(lstate, "invalid buffer"); + } + + if (rownr >= buf->b_ml.ml_line_count) { + return luaL_error(lstate, "invalid row"); + } + + char_u *line = ml_get_buf(buf, rownr+1, false); + size_t len = STRLEN(line); + + if (start < 0 || (size_t)start > len) { + return luaL_error(lstate, "invalid start"); + } + + char_u save = NUL; + if (end >= 0) { + if ((size_t)end > len || end < start) { + return luaL_error(lstate, "invalid end"); + } + save = line[end]; + line[end] = NUL; + } + + int nret = regex_match(lstate, prog, line+start); + + if (end >= 0) { + line[end] = save; + } + + if (!*prog) { + return luaL_error(lstate, "regex: internal error"); + } + + return nret; +} diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index a420f79ffd..4753df7b87 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -93,10 +93,7 @@ static PMap(cstr_t) *langs; static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) { if (luaL_newmetatable(L, tname)) { // [meta] - for (size_t i = 0; meta[i].name != NULL; i++) { - lua_pushcfunction(L, meta[i].func); // [meta, func] - lua_setfield(L, -2, meta[i].name); // [meta] - } + luaL_register(L, NULL, meta); lua_pushvalue(L, -1); // [meta, meta] lua_setfield(L, -2, "__index"); // [meta] -- cgit From 6c9a5743a0c296e74a48368a65783b9d49e6f702 Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux <39092278+vigoux@users.noreply.github.com> Date: Wed, 22 Apr 2020 18:54:56 +0200 Subject: treesitter: check for integer overflow (#12135) Sometimes treesitter calls for an invalid column within a line, checking that the column is actually valid and forcing the value avoids an integer overflow and an infinite sequence of invalid reads. Fixes #12131 --- src/nvim/lua/treesitter.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 4753df7b87..51d9549033 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -271,17 +271,22 @@ static const char *input_cb(void *payload, uint32_t byte_index, } char_u *line = ml_get_buf(bp, position.row+1, false); size_t len = STRLEN(line); - size_t tocopy = MIN(len-position.column, BUFSIZE); - - memcpy(buf, line+position.column, tocopy); - // Translate embedded \n to NUL - memchrsub(buf, '\n', '\0', tocopy); - *bytes_read = (uint32_t)tocopy; - if (tocopy < BUFSIZE) { - // now add the final \n. If it didn't fit, input_cb will be called again - // on the same line with advanced column. - buf[tocopy] = '\n'; - (*bytes_read)++; + + if (position.column > len) { + *bytes_read = 0; + } else { + size_t tocopy = MIN(len-position.column, BUFSIZE); + + memcpy(buf, line+position.column, tocopy); + // Translate embedded \n to NUL + memchrsub(buf, '\n', '\0', tocopy); + *bytes_read = (uint32_t)tocopy; + if (tocopy < BUFSIZE) { + // now add the final \n. If it didn't fit, input_cb will be called again + // on the same line with advanced column. + buf[tocopy] = '\n'; + (*bytes_read)++; + } } return buf; #undef BUFSIZE -- cgit From 48c219829786c14b6e511229a59e2fda32ffe352 Mon Sep 17 00:00:00 2001 From: Jesse Date: Tue, 5 May 2020 13:18:41 +0200 Subject: paste: support replace mode (#11945) * paste: support replace mode * Clean up Co-authored-by: Jesse Bakker --- src/nvim/lua/vim.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 8ba550ea31..fc337c5e7e 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -212,6 +212,18 @@ do vim.api.nvim_put(lines, 'c', true, true) -- XXX: Normal-mode: workaround bad cursor-placement after first chunk. vim.api.nvim_command('normal! a') + elseif phase < 2 and mode == 'R' then + local nchars = 0 + for _, line in ipairs(lines) do + nchars = nchars + line:len() + end + local row, col = unpack(vim.api.nvim_win_get_cursor(0)) + local bufline = vim.api.nvim_buf_get_lines(0, row-1, row, true)[1] + local firstline = lines[1] + firstline = bufline:sub(1, col)..firstline + lines[1] = firstline + lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len()) + vim.api.nvim_buf_set_lines(0, row-1, row, false, lines) else vim.api.nvim_put(lines, 'c', false, true) end -- cgit From 1407899c32018f1988936adfddc1dede73c559cb Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Thu, 7 May 2020 15:22:24 -0400 Subject: lua: Add buffer, window and tab accessors (#12268) * Add buffer, window and tab accessors * Fix deletion and add tests --- src/nvim/lua/vim.lua | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index fc337c5e7e..43a0a76b94 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -332,9 +332,26 @@ do return pcall_ret(pcall(fn, ...)) end end + + vim.b = make_meta_accessor( + nil_wrap(function(v) return a.nvim_buf_get_var(0, v) end), + function(v, k) return a.nvim_buf_set_var(0, v, k) end, + function(v) return a.nvim_buf_del_var(0, v) end + ) + vim.w = make_meta_accessor( + nil_wrap(function(v) return a.nvim_win_get_var(0, v) end), + function(v, k) return a.nvim_win_set_var(0, v, k) end, + function(v) return a.nvim_win_del_var(0, v) end + ) + vim.t = make_meta_accessor( + nil_wrap(function(v) return a.nvim_tabpage_get_var(0, v) end), + function(v, k) return a.nvim_tabpage_set_var(0, v, k) end, + function(v) return a.nvim_tabpage_del_var(0, v) end + ) vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var) vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar) vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option) + local function getenv(k) local v = vim.fn.getenv(k) if v == vim.NIL then -- cgit From f2894bffb024b712e69158d7914e9d9d3d495f72 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 18 May 2020 15:49:50 +0200 Subject: lua: Add highlight.on_yank (#12279) * add lua function to highlight yanked region * extract namespace, better naming, default values * add default for event argument * free timer * factor out mark to position calculation * d'oh * make sure timer stops before callback (cf. luv example) * factor out timer, more documentation * fixup * validate function argument for schedule * fix block selection past eol * correct handling of multibyte characters * move arguments around, some cleanup * move utility functions to vim.lua * use anonymous namespaces, avoid local api * rename function * add test for schedule_fn * fix indent * turn hl-yank into proper (hightlight) module * factor out position-to-region function mark extraction now part of highlight.on_yank * rename schedule_fn to defer_fn * add test for vim.region * todo: handle double-width characters * remove debug printout * do not shadow arguments * defer also callable table * whitespace change * move highlight to vim/highlight.lua * add documentation * add @return documentation * test: add check before vim.defer fires * doc: fixup --- src/nvim/lua/vim.lua | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 43a0a76b94..523d23ec12 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -415,4 +415,67 @@ do vim.wo = new_win_opt_accessor(nil) end +--- Get a table of lines with start, end columns for a region marked by two points +--- +--@param bufnr number of buffer +--@param pos1 (line, column) tuple marking beginning of region +--@param pos2 (line, column) tuple marking end of region +--@param regtype type of selection (:help setreg) +--@param inclusive boolean indicating whether the selection is end-inclusive +--@return region lua table of the form {linenr = {startcol,endcol}} +function vim.region(bufnr, pos1, pos2, regtype, inclusive) + if not vim.api.nvim_buf_is_loaded(bufnr) then + vim.fn.bufload(bufnr) + end + + -- in case of block selection, columns need to be adjusted for non-ASCII characters + -- TODO: handle double-width characters + local bufline + if regtype:byte() == 22 then + bufline = vim.api.nvim_buf_get_lines(bufnr, pos1[1], pos1[1] + 1, true)[1] + pos1[2] = vim.str_utfindex(bufline, pos1[2]) + end + + local region = {} + for l = pos1[1], pos2[1] do + local c1, c2 + if regtype:byte() == 22 then -- block selection: take width from regtype + c1 = pos1[2] + c2 = c1 + regtype:sub(2) + -- and adjust for non-ASCII characters + bufline = vim.api.nvim_buf_get_lines(bufnr, l, l + 1, true)[1] + if c1 < #bufline then + c1 = vim.str_byteindex(bufline, c1) + end + if c2 < #bufline then + c2 = vim.str_byteindex(bufline, c2) + end + else + c1 = (l == pos1[1]) and (pos1[2]) or 0 + c2 = (l == pos2[1]) and (pos2[2] + (inclusive and 1 or 0)) or -1 + end + table.insert(region, l, {c1, c2}) + end + return region +end + +--- Defers calling `fn` until `timeout` ms passes. +--- +--- Use to do a one-shot timer that calls `fn` +--@param fn Callback to call once `timeout` expires +--@param timeout Number of milliseconds to wait before calling `fn` +--@return timer luv timer object +function vim.defer_fn(fn, timeout) + vim.validate { fn = { fn, 'c', true}; } + local timer = vim.loop.new_timer() + timer:start(timeout, 0, vim.schedule_wrap(function() + timer:stop() + timer:close() + + fn() + end)) + + return timer +end + return module -- cgit From 504d6878da8fc128f68396f9ff42d2b1d2ff16b1 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Wed, 6 Nov 2019 19:23:24 +0100 Subject: lua: vim.wait initial outline --- src/nvim/lua/executor.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 144646fca2..f2075897b4 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -28,6 +28,8 @@ #include "nvim/ascii.h" #include "nvim/change.h" #include "nvim/eval/userfunc.h" +#include "nvim/event/time.h" +#include "nvim/event/loop.h" #ifdef WIN32 #include "nvim/os/os.h" @@ -255,6 +257,91 @@ static struct luaL_Reg regex_meta[] = { { NULL, NULL } }; +// Dummy timer callback. Used by f_wait(). +static void dummy_timer_due_cb(TimeWatcher *tw, void *data) +{ +} + +// Dummy timer close callback. Used by f_wait(). +static void dummy_timer_close_cb(TimeWatcher *tw, void *data) +{ + xfree(tw); +} + +static bool nlua_wait_condition(lua_State *lstate, int *status, + bool *callback_result) +{ + lua_pushvalue(lstate, 2); + *status = lua_pcall(lstate, 0, 1, 0); + if (*status) { + return true; // break on error, but keep error on stack + } + *callback_result = lua_toboolean(lstate, -1); + lua_pop(lstate, 1); + return *callback_result; // break if true +} + +/// "vim.wait(timeout, condition[, interval])" function +static int nlua_wait(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + intptr_t timeout = luaL_checkinteger(lstate, 1); + if (timeout < 0) { + return luaL_error(lstate, "timeout must be > 0"); + } + if (lua_type(lstate, 2) != LUA_TFUNCTION) { + lua_pushliteral(lstate, "vim.wait: condition must be a function"); + return lua_error(lstate); + } + + intptr_t interval = 200; + if (lua_gettop(lstate) >= 3) { + interval = luaL_checkinteger(lstate, 3); + if (interval < 0) { + return luaL_error(lstate, "interval must be > 0"); + } + } + + TimeWatcher *tw = xmalloc(sizeof(TimeWatcher)); + + // Start dummy timer. + time_watcher_init(&main_loop, tw, NULL); + tw->events = main_loop.events; + tw->blockable = true; + time_watcher_start(tw, dummy_timer_due_cb, + (uint64_t)interval, (uint64_t)interval); + + int pcall_status = 0; + bool callback_result = false; + + LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, (int)timeout, + nlua_wait_condition(lstate, &pcall_status, + &callback_result) + || got_int); + + if (pcall_status) { + // TODO: add prefix to error? + // handled after stopped time_watcher + } else if (got_int) { + got_int = false; + vgetc(); + lua_pushinteger(lstate, -2); + } else if (callback_result) { + lua_pushinteger(lstate, 0); + } else { + lua_pushinteger(lstate, -1); + } + + // Stop dummy timer + time_watcher_stop(tw); + time_watcher_close(tw, dummy_timer_close_cb); + + if (pcall_status) { + return lua_error(lstate); + } + return 1; +} + /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. @@ -305,7 +392,6 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // regex lua_pushcfunction(lstate, &nlua_regex); lua_setfield(lstate, -2, "regex"); - luaL_newmetatable(lstate, "nvim_regex"); luaL_register(lstate, NULL, regex_meta); lua_pushvalue(lstate, -1); // [meta, meta] @@ -320,6 +406,10 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, &nlua_rpcnotify); lua_setfield(lstate, -2, "rpcnotify"); + // wait + lua_pushcfunction(lstate, &nlua_wait); + lua_setfield(lstate, -2, "wait"); + // vim.loop luv_set_loop(lstate, &main_loop.uv); luv_set_callback(lstate, nlua_luv_cfpcall); -- cgit From be662fe5c7d06ee63377dd7defb72aea88134305 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Wed, 20 May 2020 11:08:19 -0400 Subject: lua: vim.wait implementation --- src/nvim/lua/executor.c | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index f2075897b4..327ed6d6b7 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -289,7 +289,17 @@ static int nlua_wait(lua_State *lstate) if (timeout < 0) { return luaL_error(lstate, "timeout must be > 0"); } - if (lua_type(lstate, 2) != LUA_TFUNCTION) { + + // Check if condition can be called. + bool is_function = (lua_type(lstate, 2) == LUA_TFUNCTION); + + // Check if condition is callable table + if (!is_function && luaL_getmetafield(lstate, 2, "__call") != 0) { + is_function = (lua_type(lstate, -1) == LUA_TFUNCTION); + lua_pop(lstate, 1); + } + + if (!is_function) { lua_pushliteral(lstate, "vim.wait: condition must be a function"); return lua_error(lstate); } @@ -314,32 +324,32 @@ static int nlua_wait(lua_State *lstate) int pcall_status = 0; bool callback_result = false; - LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, (int)timeout, - nlua_wait_condition(lstate, &pcall_status, - &callback_result) - || got_int); + LOOP_PROCESS_EVENTS_UNTIL( + &main_loop, + main_loop.events, + (int)timeout, + nlua_wait_condition(lstate, &pcall_status, &callback_result) || got_int); + + // Stop dummy timer + time_watcher_stop(tw); + time_watcher_close(tw, dummy_timer_close_cb); if (pcall_status) { - // TODO: add prefix to error? - // handled after stopped time_watcher + return lua_error(lstate); + } else if (callback_result) { + lua_pushboolean(lstate, 1); + lua_pushnil(lstate); } else if (got_int) { got_int = false; vgetc(); + lua_pushboolean(lstate, 0); lua_pushinteger(lstate, -2); - } else if (callback_result) { - lua_pushinteger(lstate, 0); } else { + lua_pushboolean(lstate, 0); lua_pushinteger(lstate, -1); } - // Stop dummy timer - time_watcher_stop(tw); - time_watcher_close(tw, dummy_timer_close_cb); - - if (pcall_status) { - return lua_error(lstate); - } - return 1; + return 2; } /// Initialize lua interpreter state -- cgit From 1805fb469a39d998f9bef0415999aa835d051044 Mon Sep 17 00:00:00 2001 From: Billy Su Date: Tue, 28 Apr 2020 23:21:50 +0800 Subject: vim-patch:8.2.0111: VAR_SPECIAL is also used for booleans Problem: VAR_SPECIAL is also used for booleans. Solution: Add VAR_BOOL for better type checking. https://github.com/vim/vim/commit/9b4a15d5dba354d2e1e02871470bad103f34769a --- src/nvim/lua/converter.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index fca74b5901..69114c967d 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -286,10 +286,10 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) break; } case LUA_TBOOLEAN: { - cur.tv->v_type = VAR_SPECIAL; - cur.tv->vval.v_special = (lua_toboolean(lstate, -1) - ? kSpecialVarTrue - : kSpecialVarFalse); + cur.tv->v_type = VAR_BOOL; + cur.tv->vval.v_bool = (lua_toboolean(lstate, -1) + ? kBoolVarTrue + : kBoolVarFalse); break; } case LUA_TSTRING: { -- cgit From 36d71e775a44526abd70b2720a589a1113c650d7 Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux Date: Sun, 7 Jun 2020 14:19:38 +0200 Subject: treesitter: simplify puhstree call process --- src/nvim/lua/executor.c | 12 +----------- src/nvim/lua/treesitter.c | 8 +++++++- 2 files changed, 8 insertions(+), 12 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 327ed6d6b7..4b47b34d8a 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1128,21 +1128,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); diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 51d9549033..3447af9114 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -214,8 +214,14 @@ 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); -- cgit From 333f3f19db612acc893791f04624da174efe04b5 Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux Date: Sun, 7 Jun 2020 16:37:11 +0200 Subject: treesitter: add set_included_ranges to the parser This is the first step towards language injection using treesitter. --- src/nvim/lua/treesitter.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 3447af9114..9e3bbb38fc 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 } }; @@ -383,6 +385,92 @@ static int parser_edit(lua_State *L) return 0; } +static int parser_set_ranges(lua_State *L) { + if (lua_gettop(L) < 3) { + lua_pushstring(L, "not enough args to parser:set_ranges()"); + return lua_error(L); + } + + TSLua_parser *p = parser_check(L); + if (!p || !p->tree) { + return 0; + } + + int bufnr = lua_tointeger(L, 2); + + if (! lua_istable(L, 3)) { + lua_pushstring(L, "argument for parser:set_ranges() should be a table."); + return lua_error(L); + } + + size_t tbl_len = lua_objlen(L, 3); + TSRange *ranges = xmalloc(sizeof(TSRange) * tbl_len); + + + // [ parser, ranges ] + for (size_t index = 0; index < tbl_len; index++) { + lua_rawgeti(L, 3, index + 1); // [ parser, ranges, range ] + + if (!lua_istable(L, -1)) { + xfree(ranges); + lua_pushstring(L, "argument for parser:set_ranges() should be a table of tables."); + return lua_error(L); + } + + if (lua_objlen(L, -1) < 4 ) { + xfree(ranges); + lua_pushstring(L, "argument for parser:set_ranges() should be a table of ranges of 4 elements."); + return lua_error(L); + } + + lua_rawgeti(L, -1, 1); // [ parser, ranges, range, num ] + unsigned int start_row = lua_tointeger(L, -1); + lua_pop(L, 1); // [ parser, ranges, range ] + + + lua_rawgeti(L, -1, 2); // [ parser, ranges, range, num ] + unsigned int start_col = lua_tointeger(L, -1); + lua_pop(L, 1); // [ parser, ranges, range ] + + lua_rawgeti(L, -1, 3); // [ parser, ranges, range, num ] + unsigned int stop_row = lua_tointeger(L, -1); + lua_pop(L, 1); // [ parser, ranges, range ] + + lua_rawgeti(L, -1, 4); // [ parser, ranges, range, num ] + unsigned int stop_col = lua_tointeger(L, -1); + lua_pop(L, 1); // [ parser, ranges, range ] + + buf_T * buf = buflist_findnr(bufnr); + + if (!buf) { + buf = curbuf; + } + + // TODO: For sure that's wrong, try to find a way to get the byte offset directly + uint32_t start_byte = ml_find_line_or_offset(buf, start_row, NULL, false) + start_col; + uint32_t stop_byte = ml_find_line_or_offset(buf, stop_row, NULL, false) + stop_col; + + ranges[index] = (TSRange) { + .start_point = (TSPoint) { + .row = start_row, + .column = start_col + }, + .end_point = (TSPoint) { + .row = stop_row, + .column = stop_col + }, + .start_byte = start_byte, + .end_byte = stop_byte + }; + } + + // This memcpies ranges, thus we can free it. + ts_parser_set_included_ranges(p->parser, ranges, tbl_len); + xfree(ranges); + + return 0; +} + // Tree methods -- cgit From ac18403d6e58a08956f9465998f2223df4e19108 Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux Date: Sun, 14 Jun 2020 18:50:22 +0200 Subject: treesitter: test newly added set_included_ranges --- src/nvim/lua/treesitter.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 9e3bbb38fc..6176a1b7c3 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -447,8 +447,9 @@ static int parser_set_ranges(lua_State *L) { } // TODO: For sure that's wrong, try to find a way to get the byte offset directly - uint32_t start_byte = ml_find_line_or_offset(buf, start_row, NULL, false) + start_col; - uint32_t stop_byte = ml_find_line_or_offset(buf, stop_row, NULL, false) + stop_col; + // Lines are 0 based for consistency + uint32_t start_byte = ml_find_line_or_offset(buf, start_row + 1, NULL, false) + start_col; + uint32_t stop_byte = ml_find_line_or_offset(buf, stop_row + 1, NULL, false) + stop_col; ranges[index] = (TSRange) { .start_point = (TSPoint) { -- cgit From 75a1239eb51a805837111ac60e5dbddfadc8cb6f Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux Date: Sun, 14 Jun 2020 19:03:36 +0200 Subject: treesitter: fix some clint errors Also fixes some mismatches on the name of the function --- src/nvim/lua/treesitter.c | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 6176a1b7c3..6e554d9b54 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -218,7 +218,6 @@ int tslua_inspect_lang(lua_State *L) int tslua_push_parser(lua_State *L) { - // Gather language if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) { return luaL_error(L, "string expected"); @@ -385,7 +384,8 @@ static int parser_edit(lua_State *L) return 0; } -static int parser_set_ranges(lua_State *L) { +static int parser_set_ranges(lua_State *L) +{ if (lua_gettop(L) < 3) { lua_pushstring(L, "not enough args to parser:set_ranges()"); return lua_error(L); @@ -398,8 +398,8 @@ static int parser_set_ranges(lua_State *L) { int bufnr = lua_tointeger(L, 2); - if (! lua_istable(L, 3)) { - lua_pushstring(L, "argument for parser:set_ranges() should be a table."); + if (!lua_istable(L, 3)) { + lua_pushstring(L, "argument for parser:set_included_ranges() should be a table."); return lua_error(L); } @@ -409,36 +409,40 @@ static int parser_set_ranges(lua_State *L) { // [ parser, ranges ] for (size_t index = 0; index < tbl_len; index++) { - lua_rawgeti(L, 3, index + 1); // [ parser, ranges, range ] + lua_rawgeti(L, 3, index + 1); // [ parser, ranges, range ] if (!lua_istable(L, -1)) { xfree(ranges); - lua_pushstring(L, "argument for parser:set_ranges() should be a table of tables."); + lua_pushstring( + L, + "argument for parser:set_included_ranges() should be a table of tables."); return lua_error(L); } - if (lua_objlen(L, -1) < 4 ) { + if (lua_objlen(L, -1) < 4) { xfree(ranges); - lua_pushstring(L, "argument for parser:set_ranges() should be a table of ranges of 4 elements."); + lua_pushstring( + L, + "argument for parser:set_included_ranges() should be a table of ranges of 4 elements."); return lua_error(L); } - lua_rawgeti(L, -1, 1); // [ parser, ranges, range, num ] + lua_rawgeti(L, -1, 1); // [ parser, ranges, range, num ] unsigned int start_row = lua_tointeger(L, -1); - lua_pop(L, 1); // [ parser, ranges, range ] + lua_pop(L, 1); // [ parser, ranges, range ] - lua_rawgeti(L, -1, 2); // [ parser, ranges, range, num ] + lua_rawgeti(L, -1, 2); // [ parser, ranges, range, num ] unsigned int start_col = lua_tointeger(L, -1); - lua_pop(L, 1); // [ parser, ranges, range ] + lua_pop(L, 1); // [ parser, ranges, range ] - lua_rawgeti(L, -1, 3); // [ parser, ranges, range, num ] + lua_rawgeti(L, -1, 3); // [ parser, ranges, range, num ] unsigned int stop_row = lua_tointeger(L, -1); - lua_pop(L, 1); // [ parser, ranges, range ] + lua_pop(L, 1); // [ parser, ranges, range ] - lua_rawgeti(L, -1, 4); // [ parser, ranges, range, num ] + lua_rawgeti(L, -1, 4); // [ parser, ranges, range, num ] unsigned int stop_col = lua_tointeger(L, -1); - lua_pop(L, 1); // [ parser, ranges, range ] + lua_pop(L, 1); // [ parser, ranges, range ] buf_T * buf = buflist_findnr(bufnr); @@ -446,10 +450,13 @@ static int parser_set_ranges(lua_State *L) { buf = curbuf; } - // TODO: For sure that's wrong, try to find a way to get the byte offset directly + // TODO(vigoux): For sure that's wrong, try to find a way to get the + // byte offset directly // Lines are 0 based for consistency - uint32_t start_byte = ml_find_line_or_offset(buf, start_row + 1, NULL, false) + start_col; - uint32_t stop_byte = ml_find_line_or_offset(buf, stop_row + 1, NULL, false) + stop_col; + uint32_t start_byte = + ml_find_line_or_offset(buf, start_row + 1, NULL, false) + start_col; + uint32_t stop_byte = + ml_find_line_or_offset(buf, stop_row + 1, NULL, false) + stop_col; ranges[index] = (TSRange) { .start_point = (TSPoint) { -- cgit From b652f74ca3a6722ad0d185a0f4093907a6af65d7 Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux Date: Tue, 16 Jun 2020 08:17:25 +0200 Subject: treesitter: use nodes to mark ranges --- src/nvim/lua/treesitter.c | 119 ++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 67 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 6e554d9b54..aa76ff33a4 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -386,8 +386,8 @@ static int parser_edit(lua_State *L) static int parser_set_ranges(lua_State *L) { - if (lua_gettop(L) < 3) { - lua_pushstring(L, "not enough args to parser:set_ranges()"); + if (lua_gettop(L) < 2) { + lua_pushstring(L, "not enough args to parser:set_included_ranges()"); return lua_error(L); } @@ -396,20 +396,18 @@ static int parser_set_ranges(lua_State *L) return 0; } - int bufnr = lua_tointeger(L, 2); - - if (!lua_istable(L, 3)) { + if (!lua_istable(L, 2)) { lua_pushstring(L, "argument for parser:set_included_ranges() should be a table."); return lua_error(L); } - size_t tbl_len = lua_objlen(L, 3); + 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, 3, index + 1); // [ parser, ranges, range ] + lua_rawgeti(L, 2, index + 1); // [ parser, ranges, range ] if (!lua_istable(L, -1)) { xfree(ranges); @@ -419,60 +417,47 @@ static int parser_set_ranges(lua_State *L) return lua_error(L); } - if (lua_objlen(L, -1) < 4) { + if (lua_objlen(L, -1) != 2) { xfree(ranges); lua_pushstring( L, - "argument for parser:set_included_ranges() should be a table of ranges of 4 elements."); + "argument for parser:set_included_ranges() should be a table of ranges of 2 elements."); return lua_error(L); } - lua_rawgeti(L, -1, 1); // [ parser, ranges, range, num ] - unsigned int start_row = lua_tointeger(L, -1); - lua_pop(L, 1); // [ parser, ranges, range ] - - - lua_rawgeti(L, -1, 2); // [ parser, ranges, range, num ] - unsigned int start_col = lua_tointeger(L, -1); - lua_pop(L, 1); // [ parser, ranges, range ] - - lua_rawgeti(L, -1, 3); // [ parser, ranges, range, num ] - unsigned int stop_row = lua_tointeger(L, -1); - lua_pop(L, 1); // [ parser, ranges, range ] - lua_rawgeti(L, -1, 4); // [ parser, ranges, range, num ] - unsigned int stop_col = lua_tointeger(L, -1); + lua_rawgeti(L, -1, 1); // [ parser, ranges, range, start_node ] + TSNode start_node; + if (!node_check(L, -1, &start_node)) { + xfree(ranges); + lua_pushstring( + L, + "ranges should be tables of nodes."); + return lua_error(L); + } lua_pop(L, 1); // [ parser, ranges, range ] - buf_T * buf = buflist_findnr(bufnr); - - if (!buf) { - buf = curbuf; + lua_rawgeti(L, -1, 1); // [ parser, ranges, range, stop_node ] + TSNode stop_node; + if (!node_check(L, -1, &stop_node)) { + xfree(ranges); + lua_pushstring( + L, + "ranges should be tables of nodes."); + return lua_error(L); } - - // TODO(vigoux): For sure that's wrong, try to find a way to get the - // byte offset directly - // Lines are 0 based for consistency - uint32_t start_byte = - ml_find_line_or_offset(buf, start_row + 1, NULL, false) + start_col; - uint32_t stop_byte = - ml_find_line_or_offset(buf, stop_row + 1, NULL, false) + stop_col; + lua_pop(L, 1); // [ parser, ranges, range ] ranges[index] = (TSRange) { - .start_point = (TSPoint) { - .row = start_row, - .column = start_col - }, - .end_point = (TSPoint) { - .row = stop_row, - .column = stop_col - }, - .start_byte = start_byte, - .end_byte = stop_byte + .start_point = ts_node_start_point(start_node), + .end_point = ts_node_end_point(stop_node), + .start_byte = ts_node_start_byte(start_node), + .end_byte = ts_node_end_byte(stop_node) }; + lua_pop(L, 1); // [ parser, ranges ] } - // This memcpies ranges, thus we can free it. + // This memcpies ranges, thus we can free it afterwards ts_parser_set_included_ranges(p->parser, ranges, tbl_len); xfree(ranges); @@ -561,9 +546,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; @@ -575,7 +560,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, " Date: Tue, 23 Jun 2020 09:08:56 +0200 Subject: treesitter: fix lint --- src/nvim/lua/treesitter.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index aa76ff33a4..913be5afe8 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -387,8 +387,9 @@ static int parser_edit(lua_State *L) static int parser_set_ranges(lua_State *L) { if (lua_gettop(L) < 2) { - lua_pushstring(L, "not enough args to parser:set_included_ranges()"); - return lua_error(L); + return luaL_error( + L, + "not enough args to parser:set_included_ranges()"); } TSLua_parser *p = parser_check(L); @@ -397,8 +398,9 @@ static int parser_set_ranges(lua_State *L) } if (!lua_istable(L, 2)) { - lua_pushstring(L, "argument for parser:set_included_ranges() should be a table."); - return lua_error(L); + return luaL_error( + L, + "argument for parser:set_included_ranges() should be a table."); } size_t tbl_len = lua_objlen(L, 2); @@ -411,18 +413,16 @@ static int parser_set_ranges(lua_State *L) if (!lua_istable(L, -1)) { xfree(ranges); - lua_pushstring( + return luaL_error( L, "argument for parser:set_included_ranges() should be a table of tables."); - return lua_error(L); } if (lua_objlen(L, -1) != 2) { xfree(ranges); - lua_pushstring( + return luaL_error( L, "argument for parser:set_included_ranges() should be a table of ranges of 2 elements."); - return lua_error(L); } @@ -560,7 +560,7 @@ static bool node_check(lua_State *L, int index, TSNode *res) static int node_tostring(lua_State *L) { TSNode node; - if (!node_check(L,1, &node)) { + if (!node_check(L, 1, &node)) { return 0; } lua_pushstring(L, " Date: Mon, 29 Jun 2020 23:02:30 +0200 Subject: treesitter: use single nodes in set_ranges fixup! treesitter: fix lint --- src/nvim/lua/treesitter.c | 43 +++++++------------------------------------ 1 file changed, 7 insertions(+), 36 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 913be5afe8..ddf54720a7 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -411,50 +411,21 @@ static int parser_set_ranges(lua_State *L) for (size_t index = 0; index < tbl_len; index++) { lua_rawgeti(L, 2, index + 1); // [ parser, ranges, range ] - if (!lua_istable(L, -1)) { + TSNode node; + if (!node_check(L, -1, &node)) { xfree(ranges); return luaL_error( - L, - "argument for parser:set_included_ranges() should be a table of tables."); - } - - if (lua_objlen(L, -1) != 2) { - xfree(ranges); - return luaL_error( - L, - "argument for parser:set_included_ranges() should be a table of ranges of 2 elements."); - } - - - lua_rawgeti(L, -1, 1); // [ parser, ranges, range, start_node ] - TSNode start_node; - if (!node_check(L, -1, &start_node)) { - xfree(ranges); - lua_pushstring( - L, - "ranges should be tables of nodes."); - return lua_error(L); - } - lua_pop(L, 1); // [ parser, ranges, range ] - - lua_rawgeti(L, -1, 1); // [ parser, ranges, range, stop_node ] - TSNode stop_node; - if (!node_check(L, -1, &stop_node)) { - xfree(ranges); - lua_pushstring( L, "ranges should be tables of nodes."); - return lua_error(L); } - lua_pop(L, 1); // [ parser, ranges, range ] + lua_pop(L, 1); // [ parser, ranges ] ranges[index] = (TSRange) { - .start_point = ts_node_start_point(start_node), - .end_point = ts_node_end_point(stop_node), - .start_byte = ts_node_start_byte(start_node), - .end_byte = ts_node_end_byte(stop_node) + .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) }; - lua_pop(L, 1); // [ parser, ranges ] } // This memcpies ranges, thus we can free it afterwards -- cgit From 7b529e7912517af078e005dd7b06b3d042be9cb7 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Thu, 2 Jul 2020 07:09:17 -0400 Subject: doc: fix scripts and regenerate (#12506) * Fix some small doc issues * doc: fixup * doc: fixup * Fix lint and rebase * Remove bad advice * Ugh, stupid mpack files... * Don't let people include these for now until they specifically want to * Prevent duplicate tag --- src/nvim/lua/vim.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 523d23ec12..047ce1ad43 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -255,6 +255,8 @@ function vim.schedule_wrap(cb) end) end +--- +--@private function vim.empty_dict() return setmetatable({}, vim._empty_dict_mt) end -- cgit From 4ab7bbf3eaeacc32e8970b76a19c8682f98cc183 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 6 Jul 2020 03:30:12 +0200 Subject: lua: add options to highlight.on_yank (#12549) NOTE: Configuration options have changed for highlight.on_yank. Check help for |:help highlight.on_yank()| --- src/nvim/lua/vim.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 047ce1ad43..771ec971b7 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -288,6 +288,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 -- cgit From 91572ddad185c2c4f6d266543d66919543c5bb2a Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 7 Jul 2020 10:55:40 +0200 Subject: doc: mention that defer_fn applies schedule_wrap (#12601) --- src/nvim/lua/vim.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 771ec971b7..18256242e8 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -467,6 +467,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 -- cgit From 529251d5e49ed869aa9b4b3a168e97c8e30211d6 Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux <39092278+vigoux@users.noreply.github.com> Date: Fri, 10 Jul 2020 15:33:27 +0200 Subject: treesitter: call bufload before parsing (#12603) --- src/nvim/lua/treesitter.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index ddf54720a7..138031237e 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -307,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; -- cgit From 971a191c4d772493535d55524b994fe385fae546 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Fri, 19 Jun 2020 00:23:30 -0400 Subject: lua: Add ability to pass lua functions directly to vimL --- src/nvim/lua/converter.c | 16 +++++++++++++ src/nvim/lua/converter.h | 8 +++++++ src/nvim/lua/executor.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++-- src/nvim/lua/executor.h | 1 + src/nvim/lua/vim.lua | 4 ++++ 5 files changed, 86 insertions(+), 2 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 69114c967d..9fc4ca7e7e 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 { @@ -384,6 +386,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); + + 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..e74e3fb084 100644 --- a/src/nvim/lua/converter.h +++ b/src/nvim/lua/converter.h @@ -9,6 +9,14 @@ #include "nvim/func_attr.h" #include "nvim/eval.h" +typedef struct { + LuaRef func_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 4b47b34d8a..5e924a9f90 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" @@ -833,7 +833,7 @@ 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); @@ -933,6 +933,33 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, } } +/// Call a LuaCallable given some typvals +int typval_exec_lua_callable( + lua_State *lstate, + LuaCallable lua_cb, + int argcount, + typval_T *argvars, + typval_T *rettv +) +{ + LuaRef cb = lua_cb.func_ref; + + nlua_pushref(lstate, cb); + + for (int i = 0; i < argcount; i++) { + nlua_push_typval(lstate, &argvars[i], false); + } + + if (lua_pcall(lstate, argcount, 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(). @@ -1280,3 +1307,31 @@ 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); + xfree(funcstate); +} + + 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/vim.lua b/src/nvim/lua/vim.lua index 18256242e8..820b237c4f 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -272,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) -- cgit From 6360cf7ce87407bd8a519b9a17f45b2148291904 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Tue, 30 Jun 2020 02:05:06 -0400 Subject: lua: Add ability to pass tables with __call vim-patch:8.2.1054: not so easy to pass a lua function to Vim vim-patch:8.2.1084: Lua: registering function has useless code I think I have also opened up the possibility for people to use these callbacks elsewhere, since I've added a new struct that we should be able to use. Also, this should allow us to determine what the state of a list is in Lua or a dictionary in Lua, since we now can track the luaref as we go. --- src/nvim/lua/converter.c | 13 +++- src/nvim/lua/converter.h | 5 +- src/nvim/lua/executor.c | 192 ++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 171 insertions(+), 39 deletions(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 9fc4ca7e7e..32e804d213 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -316,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++) { @@ -331,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; @@ -344,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) { @@ -354,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); @@ -388,8 +399,8 @@ nlua_pop_typval_table_processing_end: } 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, diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h index e74e3fb084..8601a32418 100644 --- a/src/nvim/lua/converter.h +++ b/src/nvim/lua/converter.h @@ -10,11 +10,12 @@ #include "nvim/eval.h" typedef struct { - LuaRef func_ref; + LuaRef func_ref; + LuaRef table_ref; } LuaCallable; typedef struct { - LuaCallable lua_callable; + LuaCallable lua_callable; } LuaCFunctionState; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 5e924a9f90..9f30609d66 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -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: @@ -839,6 +849,19 @@ 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; @@ -934,6 +952,14 @@ 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, @@ -942,22 +968,32 @@ int typval_exec_lua_callable( typval_T *rettv ) { - LuaRef cb = lua_cb.func_ref; + int offset = 0; + LuaRef cb = lua_cb.func_ref; - nlua_pushref(lstate, cb); + if (cb == LUA_NOREF) { + // This shouldn't happen. + luaL_error(lstate, "Invalid function passed to VimL"); + return ERROR_OTHER; + } - for (int i = 0; i < argcount; i++) { - nlua_push_typval(lstate, &argvars[i], false); - } + nlua_pushref(lstate, cb); - if (lua_pcall(lstate, argcount, 1, 0)) { - luaL_error(lstate, "nlua_CFunction_func_call failed."); - return ERROR_OTHER; - } + 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); + nlua_pop_typval(lstate, rettv); - return ERROR_NONE; + return ERROR_NONE; } /// Execute Lua string @@ -1331,7 +1367,91 @@ void nlua_CFunction_func_free(void *state) 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; + } +} -- cgit From 398201cfabaef47aa93c8a485336a00ef075ad3e Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Sun, 19 Jul 2020 17:16:48 -0400 Subject: lua: Fix crash on unprotected lua errors (#12658) Can be reproduced with a script like this: -- in some lua file vim.fn.timer_start(10, function() error("uh....") end) -- will cause neovim to crash with the following error. PANIC: unprotected error in call to Lua API (nlua_CFunction_func_call failed.) After this, it will instead print the error message from the top of the stack, like so. tmp/error_nvim.lua:10: uh... Also added an example test. Previously this test caused the embedded nvim to panic. --- src/nvim/lua/executor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 9f30609d66..ea53a8ebc2 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -987,7 +987,7 @@ int typval_exec_lua_callable( PUSH_ALL_TYPVALS(lstate, argvars, argcount, false); if (lua_pcall(lstate, argcount + offset, 1, 0)) { - luaL_error(lstate, "nlua_CFunction_func_call failed."); + nlua_print(lstate); return ERROR_OTHER; } -- cgit From bfda389c5a47e5842844471aa357728e49a97916 Mon Sep 17 00:00:00 2001 From: erw7 Date: Fri, 17 Jul 2020 14:04:06 +0900 Subject: build: Fix build failure with CI in FreeBSD Co-authored-by: James McCoy --- src/nvim/lua/executor.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/nvim/lua') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 9f30609d66..a39642acb9 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1405,7 +1405,9 @@ char_u *nlua_register_table_as_callable(typval_T *const arg) lua_State *const lstate = nlua_enter(); +#ifndef NDEBUG int top = lua_gettop(lstate); +#endif nlua_pushref(lstate, table_ref); if (!lua_getmetatable(lstate, -1)) { -- cgit