commit 976a47e81be085df9aac497be0f40a7f6a4d0345
parent 981ea41abbecf6bc7a32062986ebcad13959d895
Author: Riley Bruins <ribru17@hotmail.com>
Date: Sun, 14 Dec 2025 23:09:36 -0800
Revert "refactor(treesitter): use scratch buffer for string parser" #36964
This reverts commit 2a7cb32959b4c616bd2c76ae1933f8e068e391ad.
Diffstat:
5 files changed, 64 insertions(+), 68 deletions(-)
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
@@ -1471,10 +1471,10 @@ LanguageTree:register_cbs({cbs}, {recursive})
callbacks.
LanguageTree:source() *LanguageTree:source()*
- Returns the source bufnr of the language tree.
+ Returns the source content of the language tree (bufnr or string).
Return: ~
- (`integer`)
+ (`integer|string`)
*LanguageTree:tree_for_range()*
LanguageTree:tree_for_range({range}, {opts})
diff --git a/runtime/lua/vim/treesitter/_meta/misc.lua b/runtime/lua/vim/treesitter/_meta/misc.lua
@@ -5,7 +5,7 @@ error('Cannot require a meta file')
---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string)
---@class TSParser: userdata
----@field parse fun(self: TSParser, tree: TSTree?, source: integer, include_bytes: boolean, timeout_ns: integer?): 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 included_ranges fun(self: TSParser, include_bytes: boolean?): integer[]
---@field set_included_ranges fun(self: TSParser, ranges: (Range6|TSNode)[])
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
@@ -101,8 +101,7 @@ local TSCallbackNames = {
---taken from _trees. This is mostly a short-lived cache for included_regions()
---@field private _lang string Language name
---@field private _parent? vim.treesitter.LanguageTree Parent LanguageTree
----@field private _source integer Buffer to parse
----@field private _has_scratch_buf boolean Whether _source is a |scratch-buffer| for string parsing.
+---@field private _source (integer|string) Buffer or string to parse
---@field private _trees table<integer, TSTree> Reference to parsed tree (one for each language).
---Each key is the index of region, which is synced with _regions and _valid.
---@field private _valid_regions table<integer,true> Set of valid region IDs.
@@ -137,26 +136,11 @@ function LanguageTree.new(source, lang, opts)
source = vim.api.nvim_get_current_buf()
end
- local has_scratch_buf = false
-
- if type(source) == 'string' then
- local new_source = vim.api.nvim_create_buf(false, true)
- if new_source == 0 then
- error('Unable to create buffer for string parser')
- end
- vim.bo[new_source].fixeol = false
- vim.bo[new_source].eol = false
- vim.api.nvim_buf_set_lines(new_source, 0, -1, false, vim.split(source, '\n', { plain = true }))
- source = new_source
- has_scratch_buf = true
- end
-
local injections = opts.injections or {}
--- @class vim.treesitter.LanguageTree
local self = {
_source = source,
- _has_scratch_buf = has_scratch_buf,
_lang = lang,
_children = {},
_trees = {},
@@ -192,7 +176,8 @@ end
--- @private
function LanguageTree:_set_logger()
- local source = tostring(self:source())
+ local source = self:source()
+ source = type(source) == 'string' and 'text' or tostring(source)
local lang = self:lang()
@@ -416,8 +401,8 @@ function LanguageTree:children()
return self._children
end
---- Returns the source bufnr of the language tree.
---- @return integer
+--- Returns the source content of the language tree (bufnr or string).
+--- @return integer|string
function LanguageTree:source()
return self._source
end
@@ -580,8 +565,9 @@ function LanguageTree:_async_parse(range, on_parse)
end
local source = self._source
- local buf = vim.b[source]
- local ct = buf.changedtick
+ local is_buffer_parser = type(source) == 'number'
+ local buf = is_buffer_parser and vim.b[source] or nil
+ local ct = is_buffer_parser and buf.changedtick or nil
local total_parse_time = 0
local redrawtime = vim.o.redrawtime * 1000000
@@ -591,15 +577,19 @@ function LanguageTree:_async_parse(range, on_parse)
local parse = coroutine.wrap(self._parse)
local function step()
- if not vim.api.nvim_buf_is_valid(source) then
- return nil
- end
+ if is_buffer_parser then
+ if
+ not vim.api.nvim_buf_is_valid(source --[[@as number]])
+ then
+ return nil
+ end
- -- If buffer was changed in the middle of parsing, reset parse state
- if buf.changedtick ~= ct then
- ct = buf.changedtick
- total_parse_time = 0
- parse = coroutine.wrap(self._parse)
+ -- If buffer was changed in the middle of parsing, reset parse state
+ if buf.changedtick ~= ct then
+ ct = buf.changedtick
+ total_parse_time = 0
+ parse = coroutine.wrap(self._parse)
+ end
end
thread_state.timeout = not vim.g._ts_force_sync_parsing and default_parse_timeout_ns or nil
@@ -786,10 +776,6 @@ end
--- `remove_child` must be called on the parent to remove it.
function LanguageTree:destroy()
-- Cleanup here
- if self._has_scratch_buf then
- self._has_scratch_buf = false
- vim.api.nvim_buf_delete(self._source, {})
- end
for _, child in pairs(self._children) do
child:destroy()
end
@@ -907,7 +893,7 @@ function LanguageTree:included_regions()
end
---@param node TSNode
----@param source integer
+---@param source string|integer
---@param metadata vim.treesitter.query.TSMetadata
---@param include_children boolean
---@return Range6[]
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
@@ -513,34 +513,50 @@ static int parser_parse(lua_State *L)
old_tree = ud ? ud->tree : NULL;
}
- if (lua_type(L, 3) != LUA_TNUMBER) {
- return luaL_argerror(L, 3, "expected buffer handle");
- }
-
- handle_T bufnr = (handle_T)lua_tointeger(L, 3);
- buf_T *buf = handle_get_buffer(bufnr);
-
- if (!buf) {
+ TSTree *new_tree = NULL;
+ size_t len;
+ const char *str;
+ handle_T 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, 3)) {
+ case LUA_TSTRING:
+ str = lua_tolstring(L, 3, &len);
+ new_tree = ts_parser_parse_string(p, old_tree, str, (uint32_t)len);
+ break;
+
+ case LUA_TNUMBER:
+ bufnr = (handle_T)lua_tointeger(L, 3);
+ buf = handle_get_buffer(bufnr);
+
+ if (!buf) {
#define BUFSIZE 256
- char ebuf[BUFSIZE] = { 0 };
- vim_snprintf(ebuf, BUFSIZE, "invalid buffer handle: %d", bufnr);
- return luaL_argerror(L, 3, ebuf);
+ char ebuf[BUFSIZE] = { 0 };
+ vim_snprintf(ebuf, BUFSIZE, "invalid buffer handle: %d", bufnr);
+ return luaL_argerror(L, 3, ebuf);
#undef BUFSIZE
- }
+ }
- TSInput input = (TSInput){ (void *)buf, input_cb, TSInputEncodingUTF8, NULL };
- TSTree *new_tree = 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);
+ }
- 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);
+ break;
+
+ default:
+ return luaL_argerror(L, 3, "expected either string or buffer handle");
}
bool include_bytes = (lua_gettop(L) >= 4) && lua_toboolean(L, 4);
diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua
@@ -1264,12 +1264,6 @@ print()
parser:for_each_tree(function(tstree, tree)
ranges[tree:lang()] = { tstree:root():range(true) }
end)
-
- -- Scratch buffer should get cleaned up
- assert(vim.api.nvim_buf_is_loaded(parser:source()))
- parser:destroy()
- assert(not vim.api.nvim_buf_is_loaded(parser:source()))
-
return ranges
end)