mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 11:28:22 +00:00
feat(treesitter)!: incremental injection parsing
Problem: Treesitter highlighting is slow for large files with lots of injections. Solution: Only parse injections we are going to render during a redraw cycle. --- - `LanguageTree:parse()` will no longer parse injections by default and now requires an explicit range argument to be passed. - `TSHighlighter` now parses injections incrementally during on_win callbacks for the line range being rendered. - Plugins which require certain injections to be parsed must run `parser:parse({ start_row, end_row })` before using the tree.
This commit is contained in:

committed by
Lewis Russell

parent
5a25dcc5a4
commit
2ca076e45f
@@ -61,6 +61,10 @@ The following changes may require adaptations in user config or plugins.
|
|||||||
spaces (but paths themselves may contain spaces now).
|
spaces (but paths themselves may contain spaces now).
|
||||||
• |'directory'| will no longer remove a `>` at the start of the option.
|
• |'directory'| will no longer remove a `>` at the start of the option.
|
||||||
|
|
||||||
|
• |LanguageTree:parse()| will no longer parse injections by default and
|
||||||
|
now requires an explicit range argument to be passed. If injections are
|
||||||
|
required, provide an explicit range via `parser:parse({ start_row, end_row })`.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
NEW FEATURES *news-features*
|
NEW FEATURES *news-features*
|
||||||
|
|
||||||
@@ -69,6 +73,9 @@ The following new APIs and features were added.
|
|||||||
• Performance:
|
• Performance:
|
||||||
• 'diffopt' "linematch" scoring algorithm now favours larger and less groups
|
• 'diffopt' "linematch" scoring algorithm now favours larger and less groups
|
||||||
https://github.com/neovim/neovim/pull/23611
|
https://github.com/neovim/neovim/pull/23611
|
||||||
|
• Treesitter highlighting now parses injections incrementally during
|
||||||
|
screen redraws only for the line range being rendered. This significantly
|
||||||
|
improves performance in large files with many injections.
|
||||||
|
|
||||||
• |vim.iter()| provides a generic iterator interface for tables and Lua
|
• |vim.iter()| provides a generic iterator interface for tables and Lua
|
||||||
iterators |for-in|.
|
iterators |for-in|.
|
||||||
|
@@ -1019,13 +1019,6 @@ set({lang}, {query_name}, {text}) *vim.treesitter.query.set()*
|
|||||||
• {text} (string) Query text (unparsed).
|
• {text} (string) Query text (unparsed).
|
||||||
|
|
||||||
|
|
||||||
==============================================================================
|
|
||||||
Lua module: vim.treesitter.highlighter *lua-treesitter-highlighter*
|
|
||||||
|
|
||||||
TSHighlighter:destroy() *TSHighlighter:destroy()*
|
|
||||||
Removes all internal references to the highlighter.
|
|
||||||
|
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
Lua module: vim.treesitter.languagetree *lua-treesitter-languagetree*
|
Lua module: vim.treesitter.languagetree *lua-treesitter-languagetree*
|
||||||
|
|
||||||
@@ -1053,7 +1046,7 @@ Whenever you need to access the current syntax tree, parse the buffer:
|
|||||||
|
|
||||||
>lua
|
>lua
|
||||||
|
|
||||||
local tree = parser:parse()
|
local tree = parser:parse({ start_row, end_row })
|
||||||
|
|
||||||
<
|
<
|
||||||
|
|
||||||
@@ -1112,7 +1105,7 @@ LanguageTree:included_regions() *LanguageTree:included_regions()*
|
|||||||
Gets the set of included regions
|
Gets the set of included regions
|
||||||
|
|
||||||
Return: ~
|
Return: ~
|
||||||
integer[][]
|
Range6[][]
|
||||||
|
|
||||||
LanguageTree:invalidate({reload}) *LanguageTree:invalidate()*
|
LanguageTree:invalidate({reload}) *LanguageTree:invalidate()*
|
||||||
Invalidates this parser and all its children
|
Invalidates this parser and all its children
|
||||||
@@ -1155,10 +1148,22 @@ LanguageTree:named_node_for_range({range}, {opts})
|
|||||||
Return: ~
|
Return: ~
|
||||||
|TSNode| | nil Found node
|
|TSNode| | nil Found node
|
||||||
|
|
||||||
LanguageTree:parse() *LanguageTree:parse()*
|
LanguageTree:parse({range}) *LanguageTree:parse()*
|
||||||
Parses all defined regions using a treesitter parser for the language this
|
Recursively parse all regions in the language tree using
|
||||||
tree represents. This will run the injection query for this language to
|
|treesitter-parsers| for the corresponding languages and run injection
|
||||||
determine if any child languages should be created.
|
queries on the parsed trees to determine whether child trees should be
|
||||||
|
created and parsed.
|
||||||
|
|
||||||
|
Any region with empty range (`{}`, typically only the root tree) is always
|
||||||
|
parsed; otherwise (typically injections) only if it intersects {range} (or
|
||||||
|
if {range} is `true`).
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
• {range} boolean|Range|nil: Parse this range in the parser's source.
|
||||||
|
Set to `true` to run a complete parse of the source (Note:
|
||||||
|
Can be slow!) Set to `false|nil` to only parse regions with
|
||||||
|
empty ranges (typically only the root tree without
|
||||||
|
injections).
|
||||||
|
|
||||||
Return: ~
|
Return: ~
|
||||||
TSTree[]
|
TSTree[]
|
||||||
|
@@ -147,11 +147,14 @@ local function normalise_erow(bufnr, erow)
|
|||||||
return math.min(erow or max_erow, max_erow)
|
return math.min(erow or max_erow, max_erow)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- TODO(lewis6991): Setup a decor provider so injections folds can be parsed
|
||||||
|
-- as the window is redrawn
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@param info TS.FoldInfo
|
---@param info TS.FoldInfo
|
||||||
---@param srow integer?
|
---@param srow integer?
|
||||||
---@param erow integer?
|
---@param erow integer?
|
||||||
local function get_folds_levels(bufnr, info, srow, erow)
|
---@param parse_injections? boolean
|
||||||
|
local function get_folds_levels(bufnr, info, srow, erow, parse_injections)
|
||||||
srow = srow or 0
|
srow = srow or 0
|
||||||
erow = normalise_erow(bufnr, erow)
|
erow = normalise_erow(bufnr, erow)
|
||||||
|
|
||||||
@@ -162,7 +165,7 @@ local function get_folds_levels(bufnr, info, srow, erow)
|
|||||||
|
|
||||||
local parser = ts.get_parser(bufnr)
|
local parser = ts.get_parser(bufnr)
|
||||||
|
|
||||||
parser:parse()
|
parser:parse(parse_injections and { srow, erow } or nil)
|
||||||
|
|
||||||
parser:for_each_tree(function(tree, ltree)
|
parser:for_each_tree(function(tree, ltree)
|
||||||
local query = ts.query.get(ltree:lang(), 'folds')
|
local query = ts.query.get(ltree:lang(), 'folds')
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
---@meta
|
---@meta
|
||||||
|
|
||||||
---@class TSNode
|
---@class TSNode: userdata
|
||||||
---@field id fun(self: TSNode): integer
|
---@field id fun(self: TSNode): integer
|
||||||
---@field tree fun(self: TSNode): TSTree
|
---@field tree fun(self: TSNode): TSTree
|
||||||
---@field range fun(self: TSNode, include_bytes: false?): integer, integer, integer, integer
|
---@field range fun(self: TSNode, include_bytes: false?): integer, integer, integer, integer
|
||||||
@@ -51,7 +51,7 @@ function TSNode:_rawquery(query, captures, start, end_, opts) end
|
|||||||
---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean?): TSTree, integer[]
|
---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean?): TSTree, integer[]
|
||||||
---@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[])
|
---@field set_included_ranges fun(self: TSParser, ranges: (Range6|TSNode)[])
|
||||||
---@field set_timeout fun(self: TSParser, timeout: integer)
|
---@field set_timeout fun(self: TSParser, timeout: integer)
|
||||||
---@field timeout fun(self: TSParser): 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)
|
||||||
@@ -61,7 +61,8 @@ function TSNode:_rawquery(query, captures, start, end_, opts) end
|
|||||||
---@field root fun(self: TSTree): TSNode
|
---@field root fun(self: TSTree): TSNode
|
||||||
---@field edit fun(self: TSTree, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _:integer)
|
---@field edit fun(self: TSTree, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _:integer)
|
||||||
---@field copy fun(self: TSTree): TSTree
|
---@field copy fun(self: TSTree): TSTree
|
||||||
---@field included_ranges fun(self: TSTree, include_bytes: boolean?): integer[]
|
---@field included_ranges fun(self: TSTree, include_bytes: true): Range6[]
|
||||||
|
---@field included_ranges fun(self: TSTree, include_bytes: false): Range4[]
|
||||||
|
|
||||||
---@return integer
|
---@return integer
|
||||||
vim._ts_get_language_version = function() end
|
vim._ts_get_language_version = function() end
|
||||||
|
@@ -2,6 +2,10 @@ local api = vim.api
|
|||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
---@class Range2
|
||||||
|
---@field [1] integer start row
|
||||||
|
---@field [2] integer end row
|
||||||
|
|
||||||
---@class Range4
|
---@class Range4
|
||||||
---@field [1] integer start row
|
---@field [1] integer start row
|
||||||
---@field [2] integer start column
|
---@field [2] integer start column
|
||||||
@@ -16,7 +20,7 @@ local M = {}
|
|||||||
---@field [5] integer end column
|
---@field [5] integer end column
|
||||||
---@field [6] integer end bytes
|
---@field [6] integer end bytes
|
||||||
|
|
||||||
---@alias Range Range4|Range6
|
---@alias Range Range2|Range4|Range6
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
---@param a_row integer
|
---@param a_row integer
|
||||||
@@ -111,6 +115,9 @@ end
|
|||||||
---@param r Range
|
---@param r Range
|
||||||
---@return integer, integer, integer, integer
|
---@return integer, integer, integer, integer
|
||||||
function M.unpack4(r)
|
function M.unpack4(r)
|
||||||
|
if #r == 2 then
|
||||||
|
return r[1], 0, r[2], 0
|
||||||
|
end
|
||||||
local off_1 = #r == 6 and 1 or 0
|
local off_1 = #r == 6 and 1 or 0
|
||||||
return r[1], r[2], r[3 + off_1], r[4 + off_1]
|
return r[1], r[2], r[3 + off_1], r[4 + off_1]
|
||||||
end
|
end
|
||||||
|
@@ -99,7 +99,7 @@ function TSTreeView:new(bufnr, lang)
|
|||||||
-- For each child tree (injected language), find the root of the tree and locate the node within
|
-- For each child tree (injected language), find the root of the tree and locate the node within
|
||||||
-- the primary tree that contains that root. Add a mapping from the node in the primary tree to
|
-- the primary tree that contains that root. Add a mapping from the node in the primary tree to
|
||||||
-- the root in the child tree to the {injections} table.
|
-- the root in the child tree to the {injections} table.
|
||||||
local root = parser:parse()[1]:root()
|
local root = parser:parse(true)[1]:root()
|
||||||
local injections = {} ---@type table<integer,table>
|
local injections = {} ---@type table<integer,table>
|
||||||
parser:for_each_child(function(child, lang_)
|
parser:for_each_child(function(child, lang_)
|
||||||
child:for_each_tree(function(tree)
|
child:for_each_tree(function(tree)
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
local api = vim.api
|
local api = vim.api
|
||||||
local query = vim.treesitter.query
|
local query = vim.treesitter.query
|
||||||
|
local Range = require('vim.treesitter._range')
|
||||||
|
|
||||||
---@alias TSHlIter fun(): integer, TSNode, TSMetadata
|
---@alias TSHlIter fun(): integer, TSNode, TSMetadata
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ local query = vim.treesitter.query
|
|||||||
---@field _highlight_states table<TSTree,TSHighlightState>
|
---@field _highlight_states table<TSTree,TSHighlightState>
|
||||||
---@field _queries table<string,TSHighlighterQuery>
|
---@field _queries table<string,TSHighlighterQuery>
|
||||||
---@field tree LanguageTree
|
---@field tree LanguageTree
|
||||||
|
---@field redraw_count integer
|
||||||
local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {}
|
local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {}
|
||||||
TSHighlighter.__index = TSHighlighter
|
TSHighlighter.__index = TSHighlighter
|
||||||
|
|
||||||
@@ -139,6 +141,7 @@ function TSHighlighter.new(tree, opts)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- @nodoc
|
||||||
--- Removes all internal references to the highlighter
|
--- Removes all internal references to the highlighter
|
||||||
function TSHighlighter:destroy()
|
function TSHighlighter:destroy()
|
||||||
if TSHighlighter.active[self.bufnr] then
|
if TSHighlighter.active[self.bufnr] then
|
||||||
@@ -186,7 +189,7 @@ function TSHighlighter:on_detach()
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@package
|
---@package
|
||||||
---@param changes Range6[][]
|
---@param changes Range6[]
|
||||||
function TSHighlighter:on_changedtree(changes)
|
function TSHighlighter:on_changedtree(changes)
|
||||||
for _, ch in ipairs(changes) do
|
for _, ch in ipairs(changes) do
|
||||||
api.nvim__buf_redraw_range(self.bufnr, ch[1], ch[4] + 1)
|
api.nvim__buf_redraw_range(self.bufnr, ch[1], ch[4] + 1)
|
||||||
@@ -245,7 +248,7 @@ local function on_line_impl(self, buf, line, is_spell_nav)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local range = vim.treesitter.get_range(node, buf, metadata[capture])
|
local range = vim.treesitter.get_range(node, buf, metadata[capture])
|
||||||
local start_row, start_col, _, end_row, end_col, _ = unpack(range)
|
local start_row, start_col, end_row, end_col = Range.unpack4(range)
|
||||||
local hl = highlighter_query.hl_cache[capture]
|
local hl = highlighter_query.hl_cache[capture]
|
||||||
|
|
||||||
local capture_name = highlighter_query:query().captures[capture]
|
local capture_name = highlighter_query:query().captures[capture]
|
||||||
@@ -309,32 +312,23 @@ function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
|
||||||
---@param buf integer
|
|
||||||
function TSHighlighter._on_buf(_, buf)
|
|
||||||
local self = TSHighlighter.active[buf]
|
|
||||||
if self then
|
|
||||||
self.tree:parse()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
---@param _win integer
|
---@param _win integer
|
||||||
---@param buf integer
|
---@param buf integer
|
||||||
---@param _topline integer
|
---@param topline integer
|
||||||
function TSHighlighter._on_win(_, _win, buf, _topline)
|
---@param botline integer
|
||||||
|
function TSHighlighter._on_win(_, _win, buf, topline, botline)
|
||||||
local self = TSHighlighter.active[buf]
|
local self = TSHighlighter.active[buf]
|
||||||
if not self then
|
if not self then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
self.tree:parse({ topline, botline })
|
||||||
self:reset_highlight_state()
|
self:reset_highlight_state()
|
||||||
self.redraw_count = self.redraw_count + 1
|
self.redraw_count = self.redraw_count + 1
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
api.nvim_set_decoration_provider(ns, {
|
api.nvim_set_decoration_provider(ns, {
|
||||||
on_buf = TSHighlighter._on_buf,
|
|
||||||
on_win = TSHighlighter._on_win,
|
on_win = TSHighlighter._on_win,
|
||||||
on_line = TSHighlighter._on_line,
|
on_line = TSHighlighter._on_line,
|
||||||
_on_spell_nav = TSHighlighter._on_spell_nav,
|
_on_spell_nav = TSHighlighter._on_spell_nav,
|
||||||
|
@@ -18,7 +18,7 @@
|
|||||||
--- Whenever you need to access the current syntax tree, parse the buffer:
|
--- Whenever you need to access the current syntax tree, parse the buffer:
|
||||||
---
|
---
|
||||||
--- <pre>lua
|
--- <pre>lua
|
||||||
--- local tree = parser:parse()
|
--- local tree = parser:parse({ start_row, end_row })
|
||||||
--- </pre>
|
--- </pre>
|
||||||
---
|
---
|
||||||
--- This returns a table of immutable |treesitter-tree| objects representing the current state of
|
--- This returns a table of immutable |treesitter-tree| objects representing the current state of
|
||||||
@@ -74,6 +74,7 @@ local TSCallbackNames = {
|
|||||||
---@field package _callbacks_rec table<TSCallbackName,function[]> Callback handlers (recursive)
|
---@field package _callbacks_rec table<TSCallbackName,function[]> Callback handlers (recursive)
|
||||||
---@field private _children table<string,LanguageTree> Injected languages
|
---@field private _children table<string,LanguageTree> Injected languages
|
||||||
---@field private _injection_query Query Queries defining injected languages
|
---@field private _injection_query Query Queries defining injected languages
|
||||||
|
---@field private _injections_processed boolean
|
||||||
---@field private _opts table Options
|
---@field private _opts table Options
|
||||||
---@field private _parser TSParser Parser for language
|
---@field private _parser TSParser Parser for language
|
||||||
---@field private _has_regions boolean
|
---@field private _has_regions boolean
|
||||||
@@ -115,7 +116,9 @@ function LanguageTree.new(source, lang, opts)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local injections = opts.injections or {}
|
local injections = opts.injections or {}
|
||||||
local self = setmetatable({
|
|
||||||
|
--- @type LanguageTree
|
||||||
|
local self = {
|
||||||
_source = source,
|
_source = source,
|
||||||
_lang = lang,
|
_lang = lang,
|
||||||
_children = {},
|
_children = {},
|
||||||
@@ -123,14 +126,19 @@ function LanguageTree.new(source, lang, opts)
|
|||||||
_opts = opts,
|
_opts = opts,
|
||||||
_injection_query = injections[lang] and query.parse(lang, injections[lang])
|
_injection_query = injections[lang] and query.parse(lang, injections[lang])
|
||||||
or query.get(lang, 'injections'),
|
or query.get(lang, 'injections'),
|
||||||
|
_has_regions = false,
|
||||||
|
_injections_processed = false,
|
||||||
_valid = false,
|
_valid = false,
|
||||||
_parser = vim._create_ts_parser(lang),
|
_parser = vim._create_ts_parser(lang),
|
||||||
_callbacks = {},
|
_callbacks = {},
|
||||||
_callbacks_rec = {},
|
_callbacks_rec = {},
|
||||||
}, LanguageTree)
|
}
|
||||||
|
|
||||||
|
setmetatable(self, LanguageTree)
|
||||||
|
|
||||||
if vim.g.__ts_debug and type(vim.g.__ts_debug) == 'number' then
|
if vim.g.__ts_debug and type(vim.g.__ts_debug) == 'number' then
|
||||||
self:_set_logger()
|
self:_set_logger()
|
||||||
|
self:_log('START')
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, name in pairs(TSCallbackNames) do
|
for _, name in pairs(TSCallbackNames) do
|
||||||
@@ -141,6 +149,7 @@ function LanguageTree.new(source, lang, opts)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
function LanguageTree:_set_logger()
|
function LanguageTree:_set_logger()
|
||||||
local source = self:source()
|
local source = self:source()
|
||||||
source = type(source) == 'string' and 'text' or tostring(source)
|
source = type(source) == 'string' and 'text' or tostring(source)
|
||||||
@@ -171,7 +180,7 @@ end
|
|||||||
---Measure execution time of a function
|
---Measure execution time of a function
|
||||||
---@generic R1, R2, R3
|
---@generic R1, R2, R3
|
||||||
---@param f fun(): R1, R2, R2
|
---@param f fun(): R1, R2, R2
|
||||||
---@return integer, R1, R2, R3
|
---@return number, R1, R2, R3
|
||||||
local function tcall(f, ...)
|
local function tcall(f, ...)
|
||||||
local start = vim.uv.hrtime()
|
local start = vim.uv.hrtime()
|
||||||
---@diagnostic disable-next-line
|
---@diagnostic disable-next-line
|
||||||
@@ -219,7 +228,7 @@ function LanguageTree:invalidate(reload)
|
|||||||
|
|
||||||
-- buffer was reloaded, reparse all trees
|
-- buffer was reloaded, reparse all trees
|
||||||
if reload then
|
if reload then
|
||||||
for _, t in ipairs(self._trees) do
|
for _, t in pairs(self._trees) do
|
||||||
self:_do_callback('changedtree', t:included_ranges(true), t)
|
self:_do_callback('changedtree', t:included_ranges(true), t)
|
||||||
end
|
end
|
||||||
self._trees = {}
|
self._trees = {}
|
||||||
@@ -250,14 +259,18 @@ function LanguageTree:is_valid(exclude_children)
|
|||||||
local valid = self._valid
|
local valid = self._valid
|
||||||
|
|
||||||
if type(valid) == 'table' then
|
if type(valid) == 'table' then
|
||||||
for _, v in ipairs(valid) do
|
for i = 1, #self:included_regions() do
|
||||||
if not v then
|
if not valid[i] then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not exclude_children then
|
if not exclude_children then
|
||||||
|
if not self._injections_processed then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
for _, child in pairs(self._children) do
|
for _, child in pairs(self._children) do
|
||||||
if not child:is_valid(exclude_children) then
|
if not child:is_valid(exclude_children) then
|
||||||
return false
|
return false
|
||||||
@@ -265,9 +278,12 @@ function LanguageTree:is_valid(exclude_children)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
assert(type(valid) == 'boolean')
|
if type(valid) == 'boolean' then
|
||||||
|
return valid
|
||||||
|
end
|
||||||
|
|
||||||
return valid
|
self._valid = true
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns a map of language to child tree.
|
--- Returns a map of language to child tree.
|
||||||
@@ -280,47 +296,72 @@ function LanguageTree:source()
|
|||||||
return self._source
|
return self._source
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Parses all defined regions using a treesitter parser
|
--- @param region Range6[]
|
||||||
--- for the language this tree represents.
|
--- @param range? boolean|Range
|
||||||
--- This will run the injection query for this language to
|
--- @return boolean
|
||||||
--- determine if any child languages should be created.
|
local function intercepts_region(region, range)
|
||||||
---
|
if #region == 0 then
|
||||||
---@return TSTree[]
|
return true
|
||||||
function LanguageTree:parse()
|
|
||||||
if self:is_valid() then
|
|
||||||
self:_log('valid')
|
|
||||||
return self._trees
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local changes = {}
|
if range == nil then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
-- Collect some stats
|
if type(range) == 'boolean' then
|
||||||
local regions_parsed = 0
|
return range
|
||||||
local total_parse_time = 0
|
end
|
||||||
|
|
||||||
--- At least 1 region is invalid
|
for _, r in ipairs(region) do
|
||||||
if not self:is_valid(true) then
|
if Range.intercepts(r, range) then
|
||||||
-- If there are no ranges, set to an empty list
|
return true
|
||||||
-- so the included ranges in the parser are cleared.
|
|
||||||
for i, ranges in ipairs(self:included_regions()) do
|
|
||||||
if not self._valid or not self._valid[i] then
|
|
||||||
self._parser:set_included_ranges(ranges)
|
|
||||||
local parse_time, tree, tree_changes =
|
|
||||||
tcall(self._parser.parse, self._parser, self._trees[i], self._source, true)
|
|
||||||
|
|
||||||
-- Pass ranges if this is an initial parse
|
|
||||||
local cb_changes = self._trees[i] and tree_changes or tree:included_ranges(true)
|
|
||||||
|
|
||||||
self:_do_callback('changedtree', cb_changes, tree)
|
|
||||||
self._trees[i] = tree
|
|
||||||
vim.list_extend(changes, tree_changes)
|
|
||||||
|
|
||||||
total_parse_time = total_parse_time + parse_time
|
|
||||||
regions_parsed = regions_parsed + 1
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param range boolean|Range?
|
||||||
|
--- @return integer[] changes
|
||||||
|
--- @return integer no_regions_parsed
|
||||||
|
--- @return number total_parse_time
|
||||||
|
function LanguageTree:_parse_regions(range)
|
||||||
|
local changes = {}
|
||||||
|
local no_regions_parsed = 0
|
||||||
|
local total_parse_time = 0
|
||||||
|
|
||||||
|
if type(self._valid) ~= 'table' then
|
||||||
|
self._valid = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If there are no ranges, set to an empty list
|
||||||
|
-- so the included ranges in the parser are cleared.
|
||||||
|
for i, ranges in pairs(self:included_regions()) do
|
||||||
|
if not self._valid[i] and intercepts_region(ranges, range) then
|
||||||
|
self._parser:set_included_ranges(ranges)
|
||||||
|
local parse_time, tree, tree_changes =
|
||||||
|
tcall(self._parser.parse, self._parser, self._trees[i], self._source, true)
|
||||||
|
|
||||||
|
-- Pass ranges if this is an initial parse
|
||||||
|
local cb_changes = self._trees[i] and tree_changes or tree:included_ranges(true)
|
||||||
|
|
||||||
|
self:_do_callback('changedtree', cb_changes, tree)
|
||||||
|
self._trees[i] = tree
|
||||||
|
vim.list_extend(changes, tree_changes)
|
||||||
|
|
||||||
|
total_parse_time = total_parse_time + parse_time
|
||||||
|
no_regions_parsed = no_regions_parsed + 1
|
||||||
|
self._valid[i] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return changes, no_regions_parsed, total_parse_time
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @return number
|
||||||
|
function LanguageTree:_add_injections()
|
||||||
local seen_langs = {} ---@type table<string,boolean>
|
local seen_langs = {} ---@type table<string,boolean>
|
||||||
|
|
||||||
local query_time, injections_by_lang = tcall(self._get_injections, self)
|
local query_time, injections_by_lang = tcall(self._get_injections, self)
|
||||||
@@ -348,19 +389,60 @@ function LanguageTree:parse()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return query_time
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Recursively parse all regions in the language tree using |treesitter-parsers|
|
||||||
|
--- for the corresponding languages and run injection queries on the parsed trees
|
||||||
|
--- to determine whether child trees should be created and parsed.
|
||||||
|
---
|
||||||
|
--- Any region with empty range (`{}`, typically only the root tree) is always parsed;
|
||||||
|
--- otherwise (typically injections) only if it intersects {range} (or if {range} is `true`).
|
||||||
|
---
|
||||||
|
--- @param range boolean|Range|nil: Parse this range in the parser's source.
|
||||||
|
--- Set to `true` to run a complete parse of the source (Note: Can be slow!)
|
||||||
|
--- Set to `false|nil` to only parse regions with empty ranges (typically
|
||||||
|
--- only the root tree without injections).
|
||||||
|
--- @return TSTree[]
|
||||||
|
function LanguageTree:parse(range)
|
||||||
|
if self:is_valid() then
|
||||||
|
self:_log('valid')
|
||||||
|
return self._trees
|
||||||
|
end
|
||||||
|
|
||||||
|
local changes --- @type Range6?
|
||||||
|
|
||||||
|
-- Collect some stats
|
||||||
|
local no_regions_parsed = 0
|
||||||
|
local query_time = 0
|
||||||
|
local total_parse_time = 0
|
||||||
|
|
||||||
|
--- At least 1 region is invalid
|
||||||
|
if not self:is_valid(true) then
|
||||||
|
changes, no_regions_parsed, total_parse_time = self:_parse_regions(range)
|
||||||
|
-- Need to run injections when we parsed something
|
||||||
|
if no_regions_parsed > 0 then
|
||||||
|
self._injections_processed = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not self._injections_processed and range ~= false and range ~= nil then
|
||||||
|
query_time = self:_add_injections()
|
||||||
|
self._injections_processed = true
|
||||||
|
end
|
||||||
|
|
||||||
self:_log({
|
self:_log({
|
||||||
changes = changes,
|
changes = changes and #changes > 0 and changes or nil,
|
||||||
regions_parsed = regions_parsed,
|
regions_parsed = no_regions_parsed,
|
||||||
parse_time = total_parse_time,
|
parse_time = total_parse_time,
|
||||||
query_time = query_time,
|
query_time = query_time,
|
||||||
|
range = range,
|
||||||
})
|
})
|
||||||
|
|
||||||
self:for_each_child(function(child)
|
self:for_each_child(function(child)
|
||||||
child:parse()
|
child:parse(range)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
self._valid = true
|
|
||||||
|
|
||||||
return self._trees
|
return self._trees
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -384,7 +466,7 @@ end
|
|||||||
---
|
---
|
||||||
---@param fn fun(tree: TSTree, ltree: LanguageTree)
|
---@param fn fun(tree: TSTree, ltree: LanguageTree)
|
||||||
function LanguageTree:for_each_tree(fn)
|
function LanguageTree:for_each_tree(fn)
|
||||||
for _, tree in ipairs(self._trees) do
|
for _, tree in pairs(self._trees) do
|
||||||
fn(tree, self)
|
fn(tree, self)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -466,18 +548,17 @@ function LanguageTree:_iter_regions(fn)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(self._valid) ~= 'table' then
|
local was_valid = type(self._valid) ~= 'table'
|
||||||
|
|
||||||
|
if was_valid then
|
||||||
|
self:_log('was valid', self._valid)
|
||||||
self._valid = {}
|
self._valid = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local all_valid = true
|
local all_valid = true
|
||||||
|
|
||||||
for i, region in ipairs(self:included_regions()) do
|
for i, region in ipairs(self:included_regions()) do
|
||||||
if self._valid[i] == nil then
|
if was_valid or self._valid[i] then
|
||||||
self._valid[i] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if self._valid[i] then
|
|
||||||
self._valid[i] = fn(i, region)
|
self._valid[i] = fn(i, region)
|
||||||
if not self._valid[i] then
|
if not self._valid[i] then
|
||||||
self:_log(function()
|
self:_log(function()
|
||||||
@@ -521,6 +602,8 @@ function LanguageTree:set_included_regions(new_regions)
|
|||||||
for i, range in ipairs(region) do
|
for i, range in ipairs(region) do
|
||||||
if type(range) == 'table' and #range == 4 then
|
if type(range) == 'table' and #range == 4 then
|
||||||
region[i] = Range.add_bytes(self._source, range)
|
region[i] = Range.add_bytes(self._source, range)
|
||||||
|
elseif type(range) == 'userdata' then
|
||||||
|
region[i] = { range:range(true) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -542,7 +625,7 @@ function LanguageTree:set_included_regions(new_regions)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---Gets the set of included regions
|
---Gets the set of included regions
|
||||||
---@return integer[][]
|
---@return Range6[][]
|
||||||
function LanguageTree:included_regions()
|
function LanguageTree:included_regions()
|
||||||
if self._regions then
|
if self._regions then
|
||||||
return self._regions
|
return self._regions
|
||||||
@@ -581,7 +664,7 @@ local function get_node_ranges(node, source, metadata, include_children)
|
|||||||
|
|
||||||
-- We are excluding children so we need to mask out their ranges
|
-- We are excluding children so we need to mask out their ranges
|
||||||
for i = 0, child_count - 1 do
|
for i = 0, child_count - 1 do
|
||||||
local child = node:named_child(i)
|
local child = assert(node:named_child(i))
|
||||||
local c_srow, c_scol, c_sbyte, c_erow, c_ecol, c_ebyte = child:range(true)
|
local c_srow, c_scol, c_sbyte, c_erow, c_ecol, c_ebyte = child:range(true)
|
||||||
if c_srow > srow or c_scol > scol then
|
if c_srow > srow or c_scol > scol then
|
||||||
ranges[#ranges + 1] = { srow, scol, sbyte, c_srow, c_scol, c_sbyte }
|
ranges[#ranges + 1] = { srow, scol, sbyte, c_srow, c_scol, c_sbyte }
|
||||||
@@ -749,8 +832,8 @@ end
|
|||||||
---
|
---
|
||||||
--- TODO: Allow for an offset predicate to tailor the injection range
|
--- TODO: Allow for an offset predicate to tailor the injection range
|
||||||
--- instead of using the entire nodes range.
|
--- instead of using the entire nodes range.
|
||||||
---@private
|
--- @private
|
||||||
---@return table<string, Range6[][]>
|
--- @return table<string, Range6[][]>
|
||||||
function LanguageTree:_get_injections()
|
function LanguageTree:_get_injections()
|
||||||
if not self._injection_query then
|
if not self._injection_query then
|
||||||
return {}
|
return {}
|
||||||
@@ -759,7 +842,7 @@ function LanguageTree:_get_injections()
|
|||||||
---@type table<integer,TSInjection>
|
---@type table<integer,TSInjection>
|
||||||
local injections = {}
|
local injections = {}
|
||||||
|
|
||||||
for tree_index, tree in ipairs(self._trees) do
|
for index, tree in pairs(self._trees) do
|
||||||
local root_node = tree:root()
|
local root_node = tree:root()
|
||||||
local start_line, _, end_line, _ = root_node:range()
|
local start_line, _, end_line, _ = root_node:range()
|
||||||
|
|
||||||
@@ -771,7 +854,7 @@ function LanguageTree:_get_injections()
|
|||||||
-- TODO(lewis6991): remove after 0.9 (#20434)
|
-- TODO(lewis6991): remove after 0.9 (#20434)
|
||||||
lang, combined, ranges = self:_get_injection_deprecated(match, metadata)
|
lang, combined, ranges = self:_get_injection_deprecated(match, metadata)
|
||||||
end
|
end
|
||||||
add_injection(injections, tree_index, pattern, lang, combined, ranges)
|
add_injection(injections, index, pattern, lang, combined, ranges)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -794,7 +877,7 @@ function LanguageTree:_get_injections()
|
|||||||
end, entry.regions)
|
end, entry.regions)
|
||||||
table.insert(result[lang], regions)
|
table.insert(result[lang], regions)
|
||||||
else
|
else
|
||||||
for _, ranges in ipairs(entry.regions) do
|
for _, ranges in pairs(entry.regions) do
|
||||||
table.insert(result[lang], ranges)
|
table.insert(result[lang], ranges)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -828,7 +911,7 @@ function LanguageTree:_edit(
|
|||||||
end_row_new,
|
end_row_new,
|
||||||
end_col_new
|
end_col_new
|
||||||
)
|
)
|
||||||
for _, tree in ipairs(self._trees) do
|
for _, tree in pairs(self._trees) do
|
||||||
tree:edit(
|
tree:edit(
|
||||||
start_byte,
|
start_byte,
|
||||||
end_byte_old,
|
end_byte_old,
|
||||||
|
@@ -435,6 +435,7 @@ predicate_handlers['vim-match?'] = predicate_handlers['match?']
|
|||||||
|
|
||||||
---@class TSMetadata
|
---@class TSMetadata
|
||||||
---@field range? Range
|
---@field range? Range
|
||||||
|
---@field conceal? string
|
||||||
---@field [integer] TSMetadata
|
---@field [integer] TSMetadata
|
||||||
---@field [string] integer|string
|
---@field [string] integer|string
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
local helpers = require('test.functional.helpers')(after_each)
|
local helpers = require('test.functional.helpers')(after_each)
|
||||||
|
|
||||||
local clear = helpers.clear
|
local clear = helpers.clear
|
||||||
|
local dedent = helpers.dedent
|
||||||
local eq = helpers.eq
|
local eq = helpers.eq
|
||||||
local insert = helpers.insert
|
local insert = helpers.insert
|
||||||
local exec_lua = helpers.exec_lua
|
local exec_lua = helpers.exec_lua
|
||||||
@@ -502,22 +503,12 @@ end]]
|
|||||||
local root = parser:parse()[1]:root()
|
local root = parser:parse()[1]:root()
|
||||||
parser:set_included_regions({{root:child(0)}})
|
parser:set_included_regions({{root:child(0)}})
|
||||||
parser:invalidate()
|
parser:invalidate()
|
||||||
return { parser:parse()[1]:root():range() }
|
return { parser:parse(true)[1]:root():range() }
|
||||||
]]
|
]]
|
||||||
|
|
||||||
eq({0, 0, 18, 1}, res2)
|
eq({0, 0, 18, 1}, res2)
|
||||||
|
|
||||||
local range = exec_lua [[
|
eq({ { { 0, 0, 0, 18, 1, 512 } } }, exec_lua [[ return parser:included_regions() ]])
|
||||||
local res = {}
|
|
||||||
for _, region in ipairs(parser:included_regions()) do
|
|
||||||
for _, node in ipairs(region) do
|
|
||||||
table.insert(res, {node:range()})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return res
|
|
||||||
]]
|
|
||||||
|
|
||||||
eq(range, { { 0, 0, 18, 1 } })
|
|
||||||
|
|
||||||
local range_tbl = exec_lua [[
|
local range_tbl = exec_lua [[
|
||||||
parser:set_included_regions { { { 0, 0, 17, 1 } } }
|
parser:set_included_regions { { { 0, 0, 17, 1 } } }
|
||||||
@@ -542,7 +533,7 @@ end]]
|
|||||||
|
|
||||||
parser:set_included_regions({nodes})
|
parser:set_included_regions({nodes})
|
||||||
|
|
||||||
local root = parser:parse()[1]:root()
|
local root = parser:parse(true)[1]:root()
|
||||||
|
|
||||||
local res = {}
|
local res = {}
|
||||||
for i=0,(root:named_child_count() - 1) do
|
for i=0,(root:named_child_count() - 1) do
|
||||||
@@ -638,9 +629,11 @@ int x = INT_MAX;
|
|||||||
describe("when parsing regions independently", function()
|
describe("when parsing regions independently", function()
|
||||||
it("should inject a language", function()
|
it("should inject a language", function()
|
||||||
exec_lua([[
|
exec_lua([[
|
||||||
|
vim.g.__ts_debug = 1
|
||||||
parser = vim.treesitter.get_parser(0, "c", {
|
parser = vim.treesitter.get_parser(0, "c", {
|
||||||
injections = {
|
injections = {
|
||||||
c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}})
|
c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}})
|
||||||
|
parser:parse(true)
|
||||||
]])
|
]])
|
||||||
|
|
||||||
eq("table", exec_lua("return type(parser:children().c)"))
|
eq("table", exec_lua("return type(parser:children().c)"))
|
||||||
@@ -673,6 +666,7 @@ int x = INT_MAX;
|
|||||||
parser = vim.treesitter.get_parser(0, "c", {
|
parser = vim.treesitter.get_parser(0, "c", {
|
||||||
injections = {
|
injections = {
|
||||||
c = "(preproc_def (preproc_arg) @c @combined) (preproc_function_def value: (preproc_arg) @c @combined)"}})
|
c = "(preproc_def (preproc_arg) @c @combined) (preproc_function_def value: (preproc_arg) @c @combined)"}})
|
||||||
|
parser:parse(true)
|
||||||
]])
|
]])
|
||||||
|
|
||||||
eq("table", exec_lua("return type(parser:children().c)"))
|
eq("table", exec_lua("return type(parser:children().c)"))
|
||||||
@@ -713,6 +707,7 @@ int x = INT_MAX;
|
|||||||
injections = {
|
injections = {
|
||||||
c = "(preproc_def ((preproc_arg) @_c (#inject-clang! @_c)))" ..
|
c = "(preproc_def ((preproc_arg) @_c (#inject-clang! @_c)))" ..
|
||||||
"(preproc_function_def value: ((preproc_arg) @_a (#inject-clang! @_a)))"}})
|
"(preproc_function_def value: ((preproc_arg) @_a (#inject-clang! @_a)))"}})
|
||||||
|
parser:parse(true)
|
||||||
]=])
|
]=])
|
||||||
|
|
||||||
eq("table", exec_lua("return type(parser:children().c)"))
|
eq("table", exec_lua("return type(parser:children().c)"))
|
||||||
@@ -760,6 +755,7 @@ int x = INT_MAX;
|
|||||||
parser = vim.treesitter.get_parser(0, "c", {
|
parser = vim.treesitter.get_parser(0, "c", {
|
||||||
injections = {
|
injections = {
|
||||||
c = "(preproc_def ((preproc_arg) @c (#offset! @c 0 2 0 -1))) (preproc_function_def value: (preproc_arg) @c)"}})
|
c = "(preproc_def ((preproc_arg) @c (#offset! @c 0 2 0 -1))) (preproc_function_def value: (preproc_arg) @c)"}})
|
||||||
|
parser:parse(true)
|
||||||
]])
|
]])
|
||||||
|
|
||||||
eq("table", exec_lua("return type(parser:children().c)"))
|
eq("table", exec_lua("return type(parser:children().c)"))
|
||||||
@@ -800,6 +796,7 @@ int x = INT_MAX;
|
|||||||
local result = exec_lua([[
|
local result = exec_lua([[
|
||||||
parser = vim.treesitter.get_parser(0, "c", {
|
parser = vim.treesitter.get_parser(0, "c", {
|
||||||
injections = { c = "(preproc_def (preproc_arg) @c)"}})
|
injections = { c = "(preproc_def (preproc_arg) @c)"}})
|
||||||
|
parser:parse(true)
|
||||||
|
|
||||||
local sub_tree = parser:language_for_range({1, 18, 1, 19})
|
local sub_tree = parser:language_for_range({1, 18, 1, 19})
|
||||||
|
|
||||||
@@ -951,7 +948,7 @@ int x = INT_MAX;
|
|||||||
|
|
||||||
local r = exec_lua([[
|
local r = exec_lua([[
|
||||||
local parser = vim.treesitter.get_string_parser(..., 'lua')
|
local parser = vim.treesitter.get_string_parser(..., 'lua')
|
||||||
parser:parse()
|
parser:parse(true)
|
||||||
local ranges = {}
|
local ranges = {}
|
||||||
parser:for_each_tree(function(tstree, tree)
|
parser:for_each_tree(function(tstree, tree)
|
||||||
ranges[tree:lang()] = { tstree:root():range(true) }
|
ranges[tree:lang()] = { tstree:root():range(true) }
|
||||||
@@ -997,7 +994,7 @@ int x = INT_MAX;
|
|||||||
vimdoc = "((codeblock (language) @injection.language (code) @injection.content))"
|
vimdoc = "((codeblock (language) @injection.language (code) @injection.content))"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
parser1:parse()
|
parser1:parse(true)
|
||||||
]]
|
]]
|
||||||
|
|
||||||
eq(0, exec_lua("return #vim.tbl_keys(parser1:children())"))
|
eq(0, exec_lua("return #vim.tbl_keys(parser1:children())"))
|
||||||
@@ -1008,7 +1005,7 @@ int x = INT_MAX;
|
|||||||
vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))"
|
vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
parser2:parse()
|
parser2:parse(true)
|
||||||
]]
|
]]
|
||||||
|
|
||||||
eq(1, exec_lua("return #vim.tbl_keys(parser2:children())"))
|
eq(1, exec_lua("return #vim.tbl_keys(parser2:children())"))
|
||||||
@@ -1016,4 +1013,66 @@ int x = INT_MAX;
|
|||||||
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("parsers injections incrementally", function()
|
||||||
|
insert(dedent[[
|
||||||
|
>lua
|
||||||
|
local a = {}
|
||||||
|
<
|
||||||
|
|
||||||
|
>lua
|
||||||
|
local b = {}
|
||||||
|
<
|
||||||
|
|
||||||
|
>lua
|
||||||
|
local c = {}
|
||||||
|
<
|
||||||
|
|
||||||
|
>lua
|
||||||
|
local d = {}
|
||||||
|
<
|
||||||
|
|
||||||
|
>lua
|
||||||
|
local e = {}
|
||||||
|
<
|
||||||
|
|
||||||
|
>lua
|
||||||
|
local f = {}
|
||||||
|
<
|
||||||
|
|
||||||
|
>lua
|
||||||
|
local g = {}
|
||||||
|
<
|
||||||
|
]])
|
||||||
|
|
||||||
|
exec_lua [[
|
||||||
|
parser = require('vim.treesitter.languagetree').new(0, "vimdoc", {
|
||||||
|
injections = {
|
||||||
|
vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]]
|
||||||
|
|
||||||
|
--- Do not parse injections by default
|
||||||
|
eq(0, exec_lua [[
|
||||||
|
parser:parse()
|
||||||
|
return #vim.tbl_keys(parser:children())
|
||||||
|
]])
|
||||||
|
|
||||||
|
--- Only parse injections between lines 0, 2
|
||||||
|
eq(1, exec_lua [[
|
||||||
|
parser:parse({0, 2})
|
||||||
|
return #parser:children().lua:trees()
|
||||||
|
]])
|
||||||
|
|
||||||
|
eq(2, exec_lua [[
|
||||||
|
parser:parse({2, 6})
|
||||||
|
return #parser:children().lua:trees()
|
||||||
|
]])
|
||||||
|
|
||||||
|
eq(7, exec_lua [[
|
||||||
|
parser:parse(true)
|
||||||
|
return #parser:children().lua:trees()
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
end)
|
end)
|
||||||
|
@@ -570,21 +570,23 @@ function module.concat_tables(...)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- @param str string
|
--- @param str string
|
||||||
--- @param leave_indent? boolean
|
--- @param leave_indent? integer
|
||||||
--- @return string
|
--- @return string
|
||||||
function module.dedent(str, leave_indent)
|
function module.dedent(str, leave_indent)
|
||||||
-- find minimum common indent across lines
|
-- find minimum common indent across lines
|
||||||
local indent = nil
|
local indent --- @type string?
|
||||||
for line in str:gmatch('[^\n]+') do
|
for line in str:gmatch('[^\n]+') do
|
||||||
local line_indent = line:match('^%s+') or ''
|
local line_indent = line:match('^%s+') or ''
|
||||||
if indent == nil or #line_indent < #indent then
|
if indent == nil or #line_indent < #indent then
|
||||||
indent = line_indent
|
indent = line_indent
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if indent == nil or #indent == 0 then
|
|
||||||
|
if not indent or #indent == 0 then
|
||||||
-- no minimum common indent
|
-- no minimum common indent
|
||||||
return str
|
return str
|
||||||
end
|
end
|
||||||
|
|
||||||
local left_indent = (' '):rep(leave_indent or 0)
|
local left_indent = (' '):rep(leave_indent or 0)
|
||||||
-- create a pattern for the indent
|
-- create a pattern for the indent
|
||||||
indent = indent:gsub('%s', '[ \t]')
|
indent = indent:gsub('%s', '[ \t]')
|
||||||
|
Reference in New Issue
Block a user