mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 19:38:20 +00:00
refactor(treesitter): migrate to ts parser callback API #33141
Remove the `set_timeout` functions for `TSParser` and instead add a timeout parameter to the regular parse function. Remove these deprecated tree-sitter API functions and replace them with the preferred `TSParseOptions` style.
This commit is contained in:
@@ -5,12 +5,10 @@ error('Cannot require a meta file')
|
|||||||
---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string)
|
---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string)
|
||||||
|
|
||||||
---@class TSParser: userdata
|
---@class TSParser: userdata
|
||||||
---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean): TSTree, (Range4|Range6)[]
|
---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean, timeout_ns: integer?): TSTree?, (Range4|Range6)[]
|
||||||
---@field reset fun(self: TSParser)
|
---@field reset fun(self: TSParser)
|
||||||
---@field included_ranges fun(self: TSParser, include_bytes: boolean?): integer[]
|
---@field included_ranges fun(self: TSParser, include_bytes: boolean?): integer[]
|
||||||
---@field set_included_ranges fun(self: TSParser, ranges: (Range6|TSNode)[])
|
---@field set_included_ranges fun(self: TSParser, ranges: (Range6|TSNode)[])
|
||||||
---@field set_timeout fun(self: TSParser, timeout: integer)
|
|
||||||
---@field timeout fun(self: TSParser): integer
|
|
||||||
---@field _set_logger fun(self: TSParser, lex: boolean, parse: boolean, cb: TSLoggerCallback)
|
---@field _set_logger fun(self: TSParser, lex: boolean, parse: boolean, cb: TSLoggerCallback)
|
||||||
---@field _logger fun(self: TSParser): TSLoggerCallback
|
---@field _logger fun(self: TSParser): TSLoggerCallback
|
||||||
|
|
||||||
|
@@ -43,8 +43,10 @@
|
|||||||
local query = require('vim.treesitter.query')
|
local query = require('vim.treesitter.query')
|
||||||
local language = require('vim.treesitter.language')
|
local language = require('vim.treesitter.language')
|
||||||
local Range = require('vim.treesitter._range')
|
local Range = require('vim.treesitter._range')
|
||||||
|
local hrtime = vim.uv.hrtime
|
||||||
|
|
||||||
local default_parse_timeout_ms = 3
|
-- Parse in 3ms chunks.
|
||||||
|
local default_parse_timeout_ns = 3 * 1000000
|
||||||
|
|
||||||
---@type Range2
|
---@type Range2
|
||||||
local entire_document_range = { 0, math.huge }
|
local entire_document_range = { 0, math.huge }
|
||||||
@@ -198,16 +200,16 @@ function LanguageTree:_set_logger()
|
|||||||
self._parser:_set_logger(log_lex, log_parse, self._logger)
|
self._parser:_set_logger(log_lex, log_parse, self._logger)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Measure execution time of a function
|
---Measure execution time of a function, in nanoseconds.
|
||||||
---@generic R1, R2, R3
|
---@generic R1, R2, R3
|
||||||
---@param f fun(): R1, R2, R3
|
---@param f fun(): R1, R2, R3
|
||||||
---@return number, R1, R2, R3
|
---@return number, R1, R2, R3
|
||||||
local function tcall(f, ...)
|
local function tcall(f, ...)
|
||||||
local start = vim.uv.hrtime()
|
local start = hrtime()
|
||||||
---@diagnostic disable-next-line
|
---@diagnostic disable-next-line
|
||||||
local r = { f(...) }
|
local r = { f(...) }
|
||||||
--- @type number
|
--- @type number
|
||||||
local duration = (vim.uv.hrtime() - start) / 1000000
|
local duration = hrtime() - start
|
||||||
--- @diagnostic disable-next-line: redundant-return-value
|
--- @diagnostic disable-next-line: redundant-return-value
|
||||||
return duration, unpack(r)
|
return duration, unpack(r)
|
||||||
end
|
end
|
||||||
@@ -388,18 +390,29 @@ function LanguageTree:_parse_regions(range, thread_state)
|
|||||||
)
|
)
|
||||||
then
|
then
|
||||||
self._parser:set_included_ranges(ranges)
|
self._parser:set_included_ranges(ranges)
|
||||||
self._parser:set_timeout(thread_state.timeout and thread_state.timeout * 1000 or 0) -- ms -> micros
|
|
||||||
|
|
||||||
local parse_time, tree, tree_changes =
|
local parse_time, tree, tree_changes = tcall(
|
||||||
tcall(self._parser.parse, self._parser, self._trees[i], self._source, true)
|
self._parser.parse,
|
||||||
|
self._parser,
|
||||||
|
self._trees[i],
|
||||||
|
self._source,
|
||||||
|
true,
|
||||||
|
thread_state.timeout
|
||||||
|
)
|
||||||
while true do
|
while true do
|
||||||
if tree then
|
if tree then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
coroutine.yield(self._trees, false)
|
coroutine.yield(self._trees, false)
|
||||||
|
|
||||||
parse_time, tree, tree_changes =
|
parse_time, tree, tree_changes = tcall(
|
||||||
tcall(self._parser.parse, self._parser, self._trees[i], self._source, true)
|
self._parser.parse,
|
||||||
|
self._parser,
|
||||||
|
self._trees[i],
|
||||||
|
self._source,
|
||||||
|
true,
|
||||||
|
thread_state.timeout
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
self:_subtract_time(thread_state, parse_time)
|
self:_subtract_time(thread_state, parse_time)
|
||||||
@@ -503,7 +516,7 @@ function LanguageTree:_async_parse(range, on_parse)
|
|||||||
local buf = is_buffer_parser and vim.b[source] or nil
|
local buf = is_buffer_parser and vim.b[source] or nil
|
||||||
local ct = is_buffer_parser and buf.changedtick or nil
|
local ct = is_buffer_parser and buf.changedtick or nil
|
||||||
local total_parse_time = 0
|
local total_parse_time = 0
|
||||||
local redrawtime = vim.o.redrawtime
|
local redrawtime = vim.o.redrawtime * 1000000
|
||||||
|
|
||||||
local thread_state = {} ---@type ParserThreadState
|
local thread_state = {} ---@type ParserThreadState
|
||||||
|
|
||||||
@@ -526,7 +539,7 @@ function LanguageTree:_async_parse(range, on_parse)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
thread_state.timeout = not vim.g._ts_force_sync_parsing and default_parse_timeout_ms or nil
|
thread_state.timeout = not vim.g._ts_force_sync_parsing and default_parse_timeout_ns or nil
|
||||||
local parse_time, trees, finished = tcall(parse, self, range, thread_state)
|
local parse_time, trees, finished = tcall(parse, self, range, thread_state)
|
||||||
total_parse_time = total_parse_time + parse_time
|
total_parse_time = total_parse_time + parse_time
|
||||||
|
|
||||||
@@ -987,7 +1000,7 @@ function LanguageTree:_get_injections(range, thread_state)
|
|||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local start = vim.uv.hrtime()
|
local start = hrtime()
|
||||||
|
|
||||||
---@type table<string,Range6[][]>
|
---@type table<string,Range6[][]>
|
||||||
local result = {}
|
local result = {}
|
||||||
@@ -1016,9 +1029,9 @@ function LanguageTree:_get_injections(range, thread_state)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Check the current function duration against the timeout, if it exists.
|
-- Check the current function duration against the timeout, if it exists.
|
||||||
local current_time = vim.uv.hrtime()
|
local current_time = hrtime()
|
||||||
self:_subtract_time(thread_state, (current_time - start) / 1000000)
|
self:_subtract_time(thread_state, current_time - start)
|
||||||
start = current_time
|
start = hrtime()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -15,6 +15,8 @@
|
|||||||
#include <tree_sitter/api.h>
|
#include <tree_sitter/api.h>
|
||||||
#include <uv.h>
|
#include <uv.h>
|
||||||
|
|
||||||
|
#include "nvim/os/time.h"
|
||||||
|
|
||||||
#ifdef HAVE_WASMTIME
|
#ifdef HAVE_WASMTIME
|
||||||
# include <wasm.h>
|
# include <wasm.h>
|
||||||
|
|
||||||
@@ -52,6 +54,11 @@ typedef struct {
|
|||||||
TSTree *tree;
|
TSTree *tree;
|
||||||
} TSLuaTree;
|
} TSLuaTree;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t parse_start_time;
|
||||||
|
uint64_t timeout_threshold_ns;
|
||||||
|
} TSLuaParserCallbackPayload;
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "lua/treesitter.c.generated.h"
|
# include "lua/treesitter.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -362,8 +369,6 @@ static struct luaL_Reg parser_meta[] = {
|
|||||||
{ "reset", parser_reset },
|
{ "reset", parser_reset },
|
||||||
{ "set_included_ranges", parser_set_ranges },
|
{ "set_included_ranges", parser_set_ranges },
|
||||||
{ "included_ranges", parser_get_ranges },
|
{ "included_ranges", parser_get_ranges },
|
||||||
{ "set_timeout", parser_set_timeout },
|
|
||||||
{ "timeout", parser_get_timeout },
|
|
||||||
{ "_set_logger", parser_set_logger },
|
{ "_set_logger", parser_set_logger },
|
||||||
{ "_logger", parser_get_logger },
|
{ "_logger", parser_get_logger },
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
@@ -487,6 +492,13 @@ static void push_ranges(lua_State *L, const TSRange *ranges, const size_t length
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool on_parser_progress(TSParseState *state)
|
||||||
|
{
|
||||||
|
TSLuaParserCallbackPayload *payload = state->payload;
|
||||||
|
uint64_t parse_time = os_hrtime() - payload->parse_start_time;
|
||||||
|
return parse_time >= payload->timeout_threshold_ns;
|
||||||
|
}
|
||||||
|
|
||||||
static int parser_parse(lua_State *L)
|
static int parser_parse(lua_State *L)
|
||||||
{
|
{
|
||||||
TSParser *p = parser_check(L, 1);
|
TSParser *p = parser_check(L, 1);
|
||||||
@@ -524,7 +536,17 @@ static int parser_parse(lua_State *L)
|
|||||||
}
|
}
|
||||||
|
|
||||||
input = (TSInput){ (void *)buf, input_cb, TSInputEncodingUTF8, NULL };
|
input = (TSInput){ (void *)buf, input_cb, TSInputEncodingUTF8, NULL };
|
||||||
|
if (!lua_isnil(L, 5)) {
|
||||||
|
uint64_t timeout_ns = (uint64_t)lua_tointeger(L, 5);
|
||||||
|
TSLuaParserCallbackPayload payload =
|
||||||
|
(TSLuaParserCallbackPayload){ .parse_start_time = os_hrtime(),
|
||||||
|
.timeout_threshold_ns = timeout_ns };
|
||||||
|
TSParseOptions parse_options = { .payload = &payload,
|
||||||
|
.progress_callback = on_parser_progress };
|
||||||
|
new_tree = ts_parser_parse_with_options(p, old_tree, input, parse_options);
|
||||||
|
} else {
|
||||||
new_tree = ts_parser_parse(p, old_tree, input);
|
new_tree = ts_parser_parse(p, old_tree, input);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -534,12 +556,11 @@ static int parser_parse(lua_State *L)
|
|||||||
|
|
||||||
bool include_bytes = (lua_gettop(L) >= 4) && lua_toboolean(L, 4);
|
bool include_bytes = (lua_gettop(L) >= 4) && lua_toboolean(L, 4);
|
||||||
|
|
||||||
// Sometimes parsing fails (timeout, or wrong parser ABI)
|
|
||||||
// In those case, just return an error.
|
|
||||||
if (!new_tree) {
|
if (!new_tree) {
|
||||||
if (ts_parser_timeout_micros(p) == 0) {
|
// Sometimes parsing fails (no language was set, or it was set to one with an incompatible ABI)
|
||||||
// No timeout set, must have had an error
|
// In those cases, just return an error.
|
||||||
return luaL_error(L, "An error occurred when parsing.");
|
if (!ts_parser_language(p)) {
|
||||||
|
return luaL_error(L, "Language was unset, or has an incompatible ABI.");
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -670,26 +691,6 @@ static int parser_get_ranges(lua_State *L)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int parser_set_timeout(lua_State *L)
|
|
||||||
{
|
|
||||||
TSParser *p = parser_check(L, 1);
|
|
||||||
|
|
||||||
if (lua_gettop(L) < 2) {
|
|
||||||
luaL_error(L, "integer expected");
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t timeout = (uint32_t)luaL_checkinteger(L, 2);
|
|
||||||
ts_parser_set_timeout_micros(p, timeout);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int parser_get_timeout(lua_State *L)
|
|
||||||
{
|
|
||||||
TSParser *p = parser_check(L, 1);
|
|
||||||
lua_pushinteger(L, (lua_Integer)ts_parser_timeout_micros(p));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void logger_cb(void *payload, TSLogType logtype, const char *s)
|
static void logger_cb(void *payload, TSLogType logtype, const char *s)
|
||||||
{
|
{
|
||||||
TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)payload;
|
TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)payload;
|
||||||
|
Reference in New Issue
Block a user