aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/lua
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/lua')
-rw-r--r--src/nvim/lua/converter.c7
-rw-r--r--src/nvim/lua/executor.c125
-rw-r--r--src/nvim/lua/executor.h9
-rw-r--r--src/nvim/lua/treesitter.c231
-rw-r--r--src/nvim/lua/vim.lua56
5 files changed, 364 insertions, 64 deletions
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 32e804d213..030df69caa 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -1249,6 +1249,13 @@ type_error:
return ret;
}
+LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err)
+{
+ LuaRef rv = nlua_ref(lstate, -1);
+ lua_pop(lstate, 1);
+ return rv;
+}
+
#define GENERATE_INDEX_FUNCTION(type) \
type nlua_pop_##type(lua_State *lstate, Error *err) \
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 86da517685..5c665920b5 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -299,45 +299,66 @@ static int nlua_wait(lua_State *lstate)
return luaL_error(lstate, "timeout must be > 0");
}
- // Check if condition can be called.
- bool is_function = (lua_type(lstate, 2) == LUA_TFUNCTION);
+ int lua_top = lua_gettop(lstate);
- // 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);
- }
+ // Check if condition can be called.
+ bool is_function = false;
+ if (lua_top >= 2 && !lua_isnil(lstate, 2)) {
+ 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);
+ if (!is_function) {
+ lua_pushliteral(
+ lstate,
+ "vim.wait: if passed, condition must be a function");
+ return lua_error(lstate);
+ }
}
intptr_t interval = 200;
- if (lua_gettop(lstate) >= 3) {
+ if (lua_top >= 3 && !lua_isnil(lstate, 3)) {
interval = luaL_checkinteger(lstate, 3);
if (interval < 0) {
return luaL_error(lstate, "interval must be > 0");
}
}
+ bool fast_only = false;
+ if (lua_top >= 4) {
+ fast_only = lua_toboolean(lstate, 4);
+ }
+
+ MultiQueue *loop_events = fast_only || in_fast_callback > 0
+ ? main_loop.fast_events : main_loop.events;
+
TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
// Start dummy timer.
time_watcher_init(&main_loop, tw, NULL);
- tw->events = main_loop.events;
+ tw->events = loop_events;
tw->blockable = true;
- time_watcher_start(tw, dummy_timer_due_cb,
- (uint64_t)interval, (uint64_t)interval);
+ 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,
+ loop_events,
(int)timeout,
- nlua_wait_condition(lstate, &pcall_status, &callback_result) || got_int);
+ is_function ? nlua_wait_condition(
+ lstate,
+ &pcall_status,
+ &callback_result) : false || got_int);
// Stop dummy timer
time_watcher_stop(tw);
@@ -845,7 +866,7 @@ void nlua_unref(lua_State *lstate, LuaRef ref)
}
}
-void executor_free_luaref(LuaRef ref)
+void api_free_luaref(LuaRef ref)
{
lua_State *const lstate = nlua_enter();
nlua_unref(lstate, ref);
@@ -879,8 +900,8 @@ LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref)
/// @param[out] ret_tv Location where result will be saved.
///
/// @return Result of the execution.
-void executor_eval_lua(const String str, typval_T *const arg,
- typval_T *const ret_tv)
+void nlua_typval_eval(const String str, typval_T *const arg,
+ typval_T *const ret_tv)
FUNC_ATTR_NONNULL_ALL
{
#define EVALHEADER "local _A=select(1,...) return ("
@@ -902,8 +923,8 @@ void executor_eval_lua(const String str, typval_T *const arg,
}
}
-void executor_call_lua(const char *str, size_t len, typval_T *const args,
- int argcount, typval_T *ret_tv)
+void nlua_typval_call(const char *str, size_t len, typval_T *const args,
+ int argcount, typval_T *ret_tv)
FUNC_ATTR_NONNULL_ALL
{
#define CALLHEADER "return "
@@ -1006,14 +1027,14 @@ int typval_exec_lua_callable(
/// Execute Lua string
///
-/// Used for nvim_exec_lua().
+/// Used for nvim_exec_lua() and internally to execute a lua string.
///
/// @param[in] str String to execute.
/// @param[in] args array of ... args
/// @param[out] err Location where error will be saved.
///
/// @return Return value of the execution.
-Object executor_exec_lua_api(const String str, const Array args, Error *err)
+Object nlua_exec(const String str, const Array args, Error *err)
{
lua_State *const lstate = nlua_enter();
@@ -1040,17 +1061,30 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err)
return nlua_pop_Object(lstate, false, err);
}
-Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args,
- bool retval, Error *err)
+/// call a LuaRef as a function (or table with __call metamethod)
+///
+/// @param ref the reference to call (not consumed)
+/// @param name if non-NULL, sent to callback as first arg
+/// if NULL, only args are used
+/// @param retval if true, convert return value to Object
+/// if false, discard return value
+/// @param err Error details, if any (if NULL, errors are echoed)
+/// @return Return value of function, if retval was set. Otherwise NIL.
+Object nlua_call_ref(LuaRef ref, const char *name, Array args,
+ bool retval, Error *err)
{
lua_State *const lstate = nlua_enter();
nlua_pushref(lstate, ref);
- lua_pushstring(lstate, name);
+ int nargs = (int)args.size;
+ if (name != NULL) {
+ lua_pushstring(lstate, name);
+ nargs++;
+ }
for (size_t i = 0; i < args.size; i++) {
nlua_push_Object(lstate, args.items[i], false);
}
- if (lua_pcall(lstate, (int)args.size+1, retval ? 1 : 0, 0)) {
+ if (lua_pcall(lstate, nargs, retval ? 1 : 0, 0)) {
// if err is passed, the caller will deal with the error.
if (err) {
size_t len;
@@ -1465,3 +1499,40 @@ void nlua_free_typval_dict(dict_T *const d)
d->lua_table_ref = LUA_NOREF;
}
}
+
+void nlua_execute_log_keystroke(int c)
+{
+ char_u buf[NUMBUFLEN];
+ size_t buf_len = special_to_buf(c, mod_mask, false, buf);
+
+ lua_State *const lstate = nlua_enter();
+
+#ifndef NDEBUG
+ int top = lua_gettop(lstate);
+#endif
+
+ // [ vim ]
+ lua_getglobal(lstate, "vim");
+
+ // [ vim, vim._log_keystroke ]
+ lua_getfield(lstate, -1, "_log_keystroke");
+ luaL_checktype(lstate, -1, LUA_TFUNCTION);
+
+ // [ vim, vim._log_keystroke, buf ]
+ lua_pushlstring(lstate, (const char *)buf, buf_len);
+
+ if (lua_pcall(lstate, 1, 0, 0)) {
+ nlua_error(
+ lstate,
+ _("Error executing vim.log_keystroke lua callback: %.*s"));
+ }
+
+ // [ vim ]
+ lua_pop(lstate, 1);
+
+#ifndef NDEBUG
+ // [ ]
+ assert(top == lua_gettop(lstate));
+#endif
+}
+
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index 6599b44584..1d7a15d9aa 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -24,6 +24,15 @@ EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF);
memcpy(&err_->msg[0], s, sizeof(s)); \
} while (0)
+#define NLUA_CLEAR_REF(x) \
+ do { \
+ /* Take the address to avoid double evaluation. #1375 */ \
+ if ((x) != LUA_NOREF) { \
+ api_free_luaref(x); \
+ (x) = LUA_NOREF; \
+ } \
+ } while (0)
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/executor.h.generated.h"
#endif
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index 138031237e..5258352e72 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -39,10 +39,11 @@ typedef struct {
static struct luaL_Reg parser_meta[] = {
{ "__gc", parser_gc },
{ "__tostring", parser_tostring },
- { "parse_buf", parser_parse_buf },
+ { "parse", parser_parse },
{ "edit", parser_edit },
{ "tree", parser_tree },
{ "set_included_ranges", parser_set_ranges },
+ { "included_ranges", parser_get_ranges },
{ NULL, NULL }
};
@@ -62,6 +63,7 @@ static struct luaL_Reg node_meta[] = {
{ "end_", node_end },
{ "type", node_type },
{ "symbol", node_symbol },
+ { "field", node_field },
{ "named", node_named },
{ "missing", node_missing },
{ "has_error", node_has_error },
@@ -73,6 +75,7 @@ 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 },
+ { "iter_children", node_iter_children },
{ "_rawquery", node_rawquery },
{ NULL, NULL }
};
@@ -84,12 +87,17 @@ static struct luaL_Reg query_meta[] = {
{ NULL, NULL }
};
-// cursor is not exposed, but still needs garbage collection
+// cursors are not exposed, but still needs garbage collection
static struct luaL_Reg querycursor_meta[] = {
{ "__gc", querycursor_gc },
{ NULL, NULL }
};
+static struct luaL_Reg treecursor_meta[] = {
+ { "__gc", treecursor_gc },
+ { NULL, NULL }
+};
+
static PMap(cstr_t) *langs;
static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta)
@@ -116,6 +124,7 @@ void tslua_init(lua_State *L)
build_meta(L, "treesitter_node", node_meta);
build_meta(L, "treesitter_query", query_meta);
build_meta(L, "treesitter_querycursor", querycursor_meta);
+ build_meta(L, "treesitter_treecursor", treecursor_meta);
}
int tslua_has_language(lua_State *L)
@@ -166,6 +175,14 @@ int tslua_add_language(lua_State *L)
return luaL_error(L, "Failed to load parser: internal error");
}
+ uint32_t lang_version = ts_language_version(lang);
+ if (lang_version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION) {
+ return luaL_error(
+ L,
+ "ABI version mismatch : expected %" PRIu32 ", found %" PRIu32,
+ TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION, lang_version);
+ }
+
pmap_put(cstr_t)(langs, xstrdup(lang_name), lang);
lua_pushboolean(L, true);
@@ -278,43 +295,84 @@ 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);
-
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 "";
+ }
+ 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
}
-static int parser_parse_buf(lua_State *L)
+static void push_ranges(lua_State *L,
+ const TSRange *ranges,
+ const unsigned int length)
+{
+ lua_createtable(L, length, 0);
+ for (size_t i = 0; i < length; i++) {
+ lua_createtable(L, 4, 0);
+ lua_pushinteger(L, ranges[i].start_point.row);
+ lua_rawseti(L, -2, 1);
+ lua_pushinteger(L, ranges[i].start_point.column);
+ lua_rawseti(L, -2, 2);
+ lua_pushinteger(L, ranges[i].end_point.row);
+ lua_rawseti(L, -2, 3);
+ lua_pushinteger(L, ranges[i].end_point.column);
+ lua_rawseti(L, -2, 4);
+
+ lua_rawseti(L, -2, i+1);
+ }
+}
+
+static int parser_parse(lua_State *L)
{
TSLua_parser *p = parser_check(L);
if (!p) {
return 0;
}
- long bufnr = lua_tointeger(L, 2);
- buf_T *buf = handle_get_buffer(bufnr);
+ TSTree *new_tree;
+ size_t len;
+ const char *str;
+ long bufnr;
+ buf_T *buf;
+ TSInput input;
+
+ // This switch is necessary because of the behavior of lua_isstring, that
+ // consider numbers as strings...
+ switch (lua_type(L, 2)) {
+ case LUA_TSTRING:
+ str = lua_tolstring(L, 2, &len);
+ new_tree = ts_parser_parse_string(p->parser, p->tree, str, len);
+ break;
+
+ case LUA_TNUMBER:
+ bufnr = lua_tointeger(L, 2);
+ buf = handle_get_buffer(bufnr);
+
+ if (!buf) {
+ return luaL_error(L, "invalid buffer handle: %d", bufnr);
+ }
- if (!buf) {
- return luaL_error(L, "invalid buffer handle: %d", bufnr);
- }
+ input = (TSInput){ (void *)buf, input_cb, TSInputEncodingUTF8 };
+ new_tree = ts_parser_parse(p->parser, p->tree, input);
- TSInput input = { (void *)buf, input_cb, TSInputEncodingUTF8 };
- TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input);
+ break;
+
+ default:
+ return luaL_error(L, "invalid argument to parser:parse()");
+ }
uint32_t n_ranges = 0;
TSRange *changed = p->tree ? ts_tree_get_changed_ranges(p->tree, new_tree,
@@ -326,20 +384,8 @@ static int parser_parse_buf(lua_State *L)
tslua_push_tree(L, p->tree);
- 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);
+ push_ranges(L, changed, n_ranges);
- lua_rawseti(L, -2, i+1);
- }
xfree(changed);
return 2;
}
@@ -437,6 +483,21 @@ static int parser_set_ranges(lua_State *L)
return 0;
}
+static int parser_get_ranges(lua_State *L)
+{
+ TSLua_parser *p = parser_check(L);
+ if (!p || !p->parser) {
+ return 0;
+ }
+
+ unsigned int len;
+ const TSRange *ranges = ts_parser_included_ranges(p->parser, &len);
+
+ push_ranges(L, ranges, len);
+
+ return 1;
+}
+
// Tree methods
@@ -646,6 +707,34 @@ static int node_symbol(lua_State *L)
return 1;
}
+static int node_field(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+
+ size_t name_len;
+ const char *field_name = luaL_checklstring(L, 2, &name_len);
+
+ TSTreeCursor cursor = ts_tree_cursor_new(node);
+
+ lua_newtable(L); // [table]
+ unsigned int curr_index = 0;
+
+ if (ts_tree_cursor_goto_first_child(&cursor)) {
+ do {
+ if (!STRCMP(field_name, ts_tree_cursor_current_field_name(&cursor))) {
+ push_node(L, ts_tree_cursor_current_node(&cursor), 1); // [table, node]
+ lua_rawseti(L, -2, ++curr_index);
+ }
+ } while (ts_tree_cursor_goto_next_sibling(&cursor));
+ }
+
+ ts_tree_cursor_delete(&cursor);
+ return 1;
+}
+
static int node_named(lua_State *L)
{
TSNode node;
@@ -746,6 +835,74 @@ static int node_named_descendant_for_range(lua_State *L)
return 1;
}
+static int node_next_child(lua_State *L)
+{
+ TSTreeCursor *ud = luaL_checkudata(
+ L, lua_upvalueindex(1), "treesitter_treecursor");
+ if (!ud) {
+ return 0;
+ }
+
+ TSNode source;
+ if (!node_check(L, lua_upvalueindex(2), &source)) {
+ return 0;
+ }
+
+ // First call should return first child
+ if (ts_node_eq(source, ts_tree_cursor_current_node(ud))) {
+ if (ts_tree_cursor_goto_first_child(ud)) {
+ goto push;
+ } else {
+ goto end;
+ }
+ }
+
+ if (ts_tree_cursor_goto_next_sibling(ud)) {
+push:
+ push_node(
+ L,
+ ts_tree_cursor_current_node(ud),
+ lua_upvalueindex(2)); // [node]
+
+ const char * field = ts_tree_cursor_current_field_name(ud);
+
+ if (field != NULL) {
+ lua_pushstring(L, ts_tree_cursor_current_field_name(ud));
+ } else {
+ lua_pushnil(L);
+ } // [node, field_name_or_nil]
+ return 2;
+ }
+
+end:
+ return 0;
+}
+
+static int node_iter_children(lua_State *L)
+{
+ TSNode source;
+ if (!node_check(L, 1, &source)) {
+ return 0;
+ }
+
+ TSTreeCursor *ud = lua_newuserdata(L, sizeof(TSTreeCursor)); // [udata]
+ *ud = ts_tree_cursor_new(source);
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_treecursor"); // [udata, mt]
+ lua_setmetatable(L, -2); // [udata]
+ lua_pushvalue(L, 1); // [udata, source_node]
+ lua_pushcclosure(L, node_next_child, 2);
+
+ return 1;
+}
+
+static int treecursor_gc(lua_State *L)
+{
+ TSTreeCursor *ud = luaL_checkudata(L, 1, "treesitter_treecursor");
+ ts_tree_cursor_delete(ud);
+ return 0;
+}
+
static int node_parent(lua_State *L)
{
TSNode node;
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index 820b237c4f..bfa8b91208 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -489,4 +489,60 @@ function vim.defer_fn(fn, timeout)
return timer
end
+local on_keystroke_callbacks = {}
+
+--- Register a lua {fn} with an {id} to be run after every keystroke.
+---
+--@param fn function: Function to call. It should take one argument, which is a string.
+--- The string will contain the literal keys typed.
+--- See |i_CTRL-V|
+---
+--- If {fn} is nil, it removes the callback for the associated {ns_id}
+--@param ns_id number? Namespace ID. If not passed or 0, will generate and return a new
+--- namespace ID from |nvim_create_namesapce()|
+---
+--@return number Namespace ID associated with {fn}
+---
+--@note {fn} will be automatically removed if an error occurs while calling.
+--- This is to prevent the annoying situation of every keystroke erroring
+--- while trying to remove a broken callback.
+--@note {fn} will not be cleared from |nvim_buf_clear_namespace()|
+--@note {fn} will receive the keystrokes after mappings have been evaluated
+function vim.register_keystroke_callback(fn, ns_id)
+ vim.validate {
+ fn = { fn, 'c', true},
+ ns_id = { ns_id, 'n', true }
+ }
+
+ if ns_id == nil or ns_id == 0 then
+ ns_id = vim.api.nvim_create_namespace('')
+ end
+
+ on_keystroke_callbacks[ns_id] = fn
+ return ns_id
+end
+
+--- Function that executes the keystroke callbacks.
+--@private
+function vim._log_keystroke(char)
+ local failed_ns_ids = {}
+ local failed_messages = {}
+ for k, v in pairs(on_keystroke_callbacks) do
+ local ok, err_msg = pcall(v, char)
+ if not ok then
+ vim.register_keystroke_callback(nil, k)
+
+ table.insert(failed_ns_ids, k)
+ table.insert(failed_messages, err_msg)
+ end
+ end
+
+ if failed_ns_ids[1] then
+ error(string.format(
+ "Error executing 'on_keystroke' with ns_ids of '%s'\n With messages: %s",
+ table.concat(failed_ns_ids, ", "),
+ table.concat(failed_messages, "\n")))
+ end
+end
+
return module