aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/treesitter
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2024-05-24 19:18:11 +0000
committerJosh Rahm <joshuarahm@gmail.com>2024-05-24 19:18:11 +0000
commitff7ed8f586589d620a806c3758fac4a47a8e7e15 (patch)
tree729bbcb92231538fa61dab6c3d890b025484b7f5 /runtime/lua/vim/treesitter
parent376914f419eb08fdf4c1a63a77e1f035898a0f10 (diff)
parent28c04948a1c887a1cc0cb64de79fa32631700466 (diff)
downloadrneovim-ff7ed8f586589d620a806c3758fac4a47a8e7e15.tar.gz
rneovim-ff7ed8f586589d620a806c3758fac4a47a8e7e15.tar.bz2
rneovim-ff7ed8f586589d620a806c3758fac4a47a8e7e15.zip
Merge remote-tracking branch 'upstream/master' into mix_20240309
Diffstat (limited to 'runtime/lua/vim/treesitter')
-rw-r--r--runtime/lua/vim/treesitter/_fold.lua207
-rw-r--r--runtime/lua/vim/treesitter/_meta.lua53
-rw-r--r--runtime/lua/vim/treesitter/_query_linter.lua2
-rw-r--r--runtime/lua/vim/treesitter/dev.lua2
-rw-r--r--runtime/lua/vim/treesitter/health.lua2
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua77
-rw-r--r--runtime/lua/vim/treesitter/language.lua3
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua98
-rw-r--r--runtime/lua/vim/treesitter/query.lua225
9 files changed, 399 insertions, 270 deletions
diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua
index d96cc966de..eecf1ad6b1 100644
--- a/runtime/lua/vim/treesitter/_fold.lua
+++ b/runtime/lua/vim/treesitter/_fold.lua
@@ -4,10 +4,21 @@ local Range = require('vim.treesitter._range')
local api = vim.api
+---Treesitter folding is done in two steps:
+---(1) compute the fold levels with the syntax tree and cache the result (`compute_folds_levels`)
+---(2) evaluate foldexpr for each window, which reads from the cache (`foldupdate`)
---@class TS.FoldInfo
----@field levels string[] the foldexpr result for each line
----@field levels0 integer[] the raw fold levels
----@field edits? {[1]: integer, [2]: integer} line range edited since the last invocation of the callback scheduled in on_bytes. 0-indexed, end-exclusive.
+---
+---@field levels string[] the cached foldexpr result for each line
+---@field levels0 integer[] the cached raw fold levels
+---
+---The range edited since the last invocation of the callback scheduled in on_bytes.
+---Should compute fold levels in this range.
+---@field on_bytes_range? Range2
+---
+---The range on which to evaluate foldexpr.
+---When in insert mode, the evaluation is deferred to InsertLeave.
+---@field foldupdate_range? Range2
local FoldInfo = {}
FoldInfo.__index = FoldInfo
@@ -80,45 +91,16 @@ function FoldInfo:add_range(srow, erow)
list_insert(self.levels0, srow + 1, erow, -1)
end
----@package
+---@param range Range2
---@param srow integer
---@param erow_old integer
---@param erow_new integer 0-indexed, exclusive
-function FoldInfo:edit_range(srow, erow_old, erow_new)
- if self.edits then
- self.edits[1] = math.min(srow, self.edits[1])
- if erow_old <= self.edits[2] then
- self.edits[2] = self.edits[2] + (erow_new - erow_old)
- end
- self.edits[2] = math.max(self.edits[2], erow_new)
- else
- self.edits = { srow, erow_new }
+local function edit_range(range, srow, erow_old, erow_new)
+ range[1] = math.min(srow, range[1])
+ if erow_old <= range[2] then
+ range[2] = range[2] + (erow_new - erow_old)
end
-end
-
----@package
----@return integer? srow
----@return integer? erow 0-indexed, exclusive
-function FoldInfo:flush_edit()
- if self.edits then
- local srow, erow = self.edits[1], self.edits[2]
- self.edits = nil
- return srow, erow
- end
-end
-
---- If a parser doesn't have any ranges explicitly set, treesitter will
---- return a range with end_row and end_bytes with a value of UINT32_MAX,
---- so clip end_row to the max buffer line.
----
---- TODO(lewis6991): Handle this generally
----
---- @param bufnr integer
---- @param erow integer? 0-indexed, exclusive
---- @return integer
-local function normalise_erow(bufnr, erow)
- local max_erow = api.nvim_buf_line_count(bufnr)
- return math.min(erow or max_erow, max_erow)
+ range[2] = math.max(range[2], erow_new)
end
-- TODO(lewis6991): Setup a decor provider so injections folds can be parsed
@@ -128,9 +110,9 @@ end
---@param srow integer?
---@param erow integer? 0-indexed, exclusive
---@param parse_injections? boolean
-local function get_folds_levels(bufnr, info, srow, erow, parse_injections)
+local function compute_folds_levels(bufnr, info, srow, erow, parse_injections)
srow = srow or 0
- erow = normalise_erow(bufnr, erow)
+ erow = erow or api.nvim_buf_line_count(bufnr)
local parser = ts.get_parser(bufnr)
@@ -149,27 +131,43 @@ local function get_folds_levels(bufnr, info, srow, erow, parse_injections)
-- Collect folds starting from srow - 1, because we should first subtract the folds that end at
-- srow - 1 from the level of srow - 1 to get accurate level of srow.
- for id, node, metadata in query:iter_captures(tree:root(), bufnr, math.max(srow - 1, 0), erow) do
- if query.captures[id] == 'fold' then
- local range = ts.get_range(node, bufnr, metadata[id])
- local start, _, stop, stop_col = Range.unpack4(range)
-
- if stop_col == 0 then
- stop = stop - 1
- end
-
- local fold_length = stop - start + 1
-
- -- Fold only multiline nodes that are not exactly the same as previously met folds
- -- Checking against just the previously found fold is sufficient if nodes
- -- are returned in preorder or postorder when traversing tree
- if
- fold_length > vim.wo.foldminlines and not (start == prev_start and stop == prev_stop)
- then
- enter_counts[start + 1] = (enter_counts[start + 1] or 0) + 1
- leave_counts[stop + 1] = (leave_counts[stop + 1] or 0) + 1
- prev_start = start
- prev_stop = stop
+ for _, match, metadata in
+ query:iter_matches(tree:root(), bufnr, math.max(srow - 1, 0), erow, { all = true })
+ do
+ for id, nodes in pairs(match) do
+ if query.captures[id] == 'fold' then
+ local range = ts.get_range(nodes[1], bufnr, metadata[id])
+ local start, _, stop, stop_col = Range.unpack4(range)
+
+ for i = 2, #nodes, 1 do
+ local node_range = ts.get_range(nodes[i], bufnr, metadata[id])
+ local node_start, _, node_stop, node_stop_col = Range.unpack4(node_range)
+ if node_start < start then
+ start = node_start
+ end
+ if node_stop > stop then
+ stop = node_stop
+ stop_col = node_stop_col
+ end
+ end
+
+ if stop_col == 0 then
+ stop = stop - 1
+ end
+
+ local fold_length = stop - start + 1
+
+ -- Fold only multiline nodes that are not exactly the same as previously met folds
+ -- Checking against just the previously found fold is sufficient if nodes
+ -- are returned in preorder or postorder when traversing tree
+ if
+ fold_length > vim.wo.foldminlines and not (start == prev_start and stop == prev_stop)
+ then
+ enter_counts[start + 1] = (enter_counts[start + 1] or 0) + 1
+ leave_counts[stop + 1] = (leave_counts[stop + 1] or 0) + 1
+ prev_start = start
+ prev_stop = stop
+ end
end
end
end
@@ -215,7 +213,7 @@ local function get_folds_levels(bufnr, info, srow, erow, parse_injections)
clamped = nestmax
end
- -- Record the "real" level, so that it can be used as "base" of later get_folds_levels().
+ -- Record the "real" level, so that it can be used as "base" of later compute_folds_levels().
info.levels0[lnum] = adjusted
info.levels[lnum] = prefix .. tostring(clamped)
@@ -236,18 +234,17 @@ local group = api.nvim_create_augroup('treesitter/fold', {})
---
--- Nvim usually automatically updates folds when text changes, but it doesn't work here because
--- FoldInfo update is scheduled. So we do it manually.
-local function foldupdate(bufnr)
- local function do_update()
- for _, win in ipairs(vim.fn.win_findbuf(bufnr)) do
- api.nvim_win_call(win, function()
- if vim.wo.foldmethod == 'expr' then
- vim._foldupdate()
- end
- end)
- end
+---@package
+---@param srow integer
+---@param erow integer 0-indexed, exclusive
+function FoldInfo:foldupdate(bufnr, srow, erow)
+ if self.foldupdate_range then
+ edit_range(self.foldupdate_range, srow, erow, erow)
+ else
+ self.foldupdate_range = { srow, erow }
end
- if api.nvim_get_mode().mode == 'i' then
+ if api.nvim_get_mode().mode:match('^i') then
-- foldUpdate() is guarded in insert mode. So update folds on InsertLeave
if #(api.nvim_get_autocmds({
group = group,
@@ -259,12 +256,25 @@ local function foldupdate(bufnr)
group = group,
buffer = bufnr,
once = true,
- callback = do_update,
+ callback = function()
+ self:do_foldupdate(bufnr)
+ end,
})
return
end
- do_update()
+ self:do_foldupdate(bufnr)
+end
+
+---@package
+function FoldInfo:do_foldupdate(bufnr)
+ local srow, erow = self.foldupdate_range[1], self.foldupdate_range[2]
+ self.foldupdate_range = nil
+ for _, win in ipairs(vim.fn.win_findbuf(bufnr)) do
+ if vim.wo[win].foldmethod == 'expr' then
+ vim._foldupdate(win, srow, erow)
+ end
+ end
end
--- Schedule a function only if bufnr is loaded.
@@ -272,7 +282,7 @@ end
--- * queries seem to use the old buffer state in on_bytes for some unknown reason;
--- * to avoid textlock;
--- * to avoid infinite recursion:
---- get_folds_levels → parse → _do_callback → on_changedtree → get_folds_levels.
+--- compute_folds_levels → parse → _do_callback → on_changedtree → compute_folds_levels.
---@param bufnr integer
---@param fn function
local function schedule_if_loaded(bufnr, fn)
@@ -289,16 +299,27 @@ end
---@param tree_changes Range4[]
local function on_changedtree(bufnr, foldinfo, tree_changes)
schedule_if_loaded(bufnr, function()
+ local srow_upd, erow_upd ---@type integer?, integer?
+ local max_erow = api.nvim_buf_line_count(bufnr)
for _, change in ipairs(tree_changes) do
local srow, _, erow, ecol = Range.unpack4(change)
- if ecol > 0 then
+ -- If a parser doesn't have any ranges explicitly set, treesitter will
+ -- return a range with end_row and end_bytes with a value of UINT32_MAX,
+ -- so clip end_row to the max buffer line.
+ -- TODO(lewis6991): Handle this generally
+ if erow > max_erow then
+ erow = max_erow
+ elseif ecol > 0 then
erow = erow + 1
end
-- Start from `srow - foldminlines`, because this edit may have shrunken the fold below limit.
- get_folds_levels(bufnr, foldinfo, math.max(srow - vim.wo.foldminlines, 0), erow)
+ srow = math.max(srow - vim.wo.foldminlines, 0)
+ compute_folds_levels(bufnr, foldinfo, srow, erow)
+ srow_upd = srow_upd and math.min(srow_upd, srow) or srow
+ erow_upd = erow_upd and math.max(erow_upd, erow) or erow
end
if #tree_changes > 0 then
- foldupdate(bufnr)
+ foldinfo:foldupdate(bufnr, srow_upd, erow_upd)
end
end)
end
@@ -335,19 +356,29 @@ local function on_bytes(bufnr, foldinfo, start_row, start_col, old_row, old_col,
foldinfo:add_range(end_row_old, end_row_new)
end
end
- foldinfo:edit_range(start_row, end_row_old, end_row_new)
+
+ if foldinfo.on_bytes_range then
+ edit_range(foldinfo.on_bytes_range, start_row, end_row_old, end_row_new)
+ else
+ foldinfo.on_bytes_range = { start_row, end_row_new }
+ end
+ if foldinfo.foldupdate_range then
+ edit_range(foldinfo.foldupdate_range, start_row, end_row_old, end_row_new)
+ end
-- This callback must not use on_bytes arguments, because they can be outdated when the callback
-- is invoked. For example, `J` with non-zero count triggers multiple on_bytes before executing
- -- the scheduled callback. So we should collect the edits.
+ -- the scheduled callback. So we accumulate the edited ranges in `on_bytes_range`.
schedule_if_loaded(bufnr, function()
- local srow, erow = foldinfo:flush_edit()
- if not srow then
+ if not foldinfo.on_bytes_range then
return
end
+ local srow, erow = foldinfo.on_bytes_range[1], foldinfo.on_bytes_range[2]
+ foldinfo.on_bytes_range = nil
-- Start from `srow - foldminlines`, because this edit may have shrunken the fold below limit.
- get_folds_levels(bufnr, foldinfo, math.max(srow - vim.wo.foldminlines, 0), erow)
- foldupdate(bufnr)
+ srow = math.max(srow - vim.wo.foldminlines, 0)
+ compute_folds_levels(bufnr, foldinfo, srow, erow)
+ foldinfo:foldupdate(bufnr, srow, erow)
end)
end
end
@@ -366,7 +397,7 @@ function M.foldexpr(lnum)
if not foldinfos[bufnr] then
foldinfos[bufnr] = FoldInfo.new()
- get_folds_levels(bufnr, foldinfos[bufnr])
+ compute_folds_levels(bufnr, foldinfos[bufnr])
parser:register_cbs({
on_changedtree = function(tree_changes)
@@ -390,10 +421,10 @@ api.nvim_create_autocmd('OptionSet', {
pattern = { 'foldminlines', 'foldnestmax' },
desc = 'Refresh treesitter folds',
callback = function()
- for _, bufnr in ipairs(vim.tbl_keys(foldinfos)) do
+ for bufnr, _ in pairs(foldinfos) do
foldinfos[bufnr] = FoldInfo.new()
- get_folds_levels(bufnr, foldinfos[bufnr])
- foldupdate(bufnr)
+ compute_folds_levels(bufnr, foldinfos[bufnr])
+ foldinfos[bufnr]:foldupdate(bufnr, 0, api.nvim_buf_line_count(bufnr))
end
end,
})
diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua
index 19d97d2820..177699a207 100644
--- a/runtime/lua/vim/treesitter/_meta.lua
+++ b/runtime/lua/vim/treesitter/_meta.lua
@@ -20,6 +20,7 @@ error('Cannot require a meta file')
---@field descendant_for_range fun(self: TSNode, start_row: integer, start_col: integer, end_row: integer, end_col: integer): TSNode?
---@field named_descendant_for_range fun(self: TSNode, start_row: integer, start_col: integer, end_row: integer, end_col: integer): TSNode?
---@field parent fun(self: TSNode): TSNode?
+---@field child_containing_descendant fun(self: TSNode, descendant: TSNode): TSNode?
---@field next_sibling fun(self: TSNode): TSNode?
---@field prev_sibling fun(self: TSNode): TSNode?
---@field next_named_sibling fun(self: TSNode): TSNode?
@@ -34,22 +35,6 @@ error('Cannot require a meta file')
---@field byte_length fun(self: TSNode): integer
local TSNode = {}
----@param query TSQuery
----@param captures true
----@param start? integer
----@param end_? integer
----@param opts? table
----@return fun(): integer, TSNode, vim.treesitter.query.TSMatch
-function TSNode:_rawquery(query, captures, start, end_, opts) end
-
----@param query TSQuery
----@param captures false
----@param start? integer
----@param end_? integer
----@param opts? table
----@return fun(): integer, vim.treesitter.query.TSMatch
-function TSNode:_rawquery(query, captures, start, end_, opts) end
-
---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string)
---@class TSParser: userdata
@@ -76,9 +61,17 @@ function TSNode:_rawquery(query, captures, start, end_, opts) end
---@field captures string[]
---@field patterns table<integer, (integer|string)[][]>
+--- @param lang string
+vim._ts_inspect_language = function(lang) end
+
---@return integer
vim._ts_get_language_version = function() end
+--- @param path string
+--- @param lang string
+--- @param symbol_name? string
+vim._ts_add_language = function(path, lang, symbol_name) end
+
---@return integer
vim._ts_get_minimum_language_version = function() end
@@ -90,3 +83,31 @@ vim._ts_parse_query = function(lang, query) end
---@param lang string
---@return TSParser
vim._create_ts_parser = function(lang) end
+
+--- @class TSQueryMatch: userdata
+--- @field captures fun(self: TSQueryMatch): table<integer,TSNode[]>
+local TSQueryMatch = {}
+
+--- @return integer match_id
+--- @return integer pattern_index
+function TSQueryMatch:info() end
+
+--- @class TSQueryCursor: userdata
+--- @field remove_match fun(self: TSQueryCursor, id: integer)
+local TSQueryCursor = {}
+
+--- @return integer capture
+--- @return TSNode captured_node
+--- @return TSQueryMatch match
+function TSQueryCursor:next_capture() end
+
+--- @return TSQueryMatch match
+function TSQueryCursor:next_match() end
+
+--- @param node TSNode
+--- @param query TSQuery
+--- @param start integer?
+--- @param stop integer?
+--- @param opts? { max_start_depth?: integer, match_limit?: integer}
+--- @return TSQueryCursor
+function vim._create_ts_querycursor(node, query, start, stop, opts) end
diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua
index 6216d4e891..12b4cbc7b9 100644
--- a/runtime/lua/vim/treesitter/_query_linter.lua
+++ b/runtime/lua/vim/treesitter/_query_linter.lua
@@ -122,7 +122,7 @@ local parse = vim.func._memoize(hash_parse, function(node, buf, lang)
end)
--- @param buf integer
---- @param match vim.treesitter.query.TSMatch
+--- @param match table<integer,TSNode[]>
--- @param query vim.treesitter.Query
--- @param lang_context QueryLinterLanguageContext
--- @param diagnostics vim.Diagnostic[]
diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua
index dc2a14d238..5c91f101c0 100644
--- a/runtime/lua/vim/treesitter/dev.lua
+++ b/runtime/lua/vim/treesitter/dev.lua
@@ -226,7 +226,7 @@ function TSTreeView:draw(bufnr)
text = string.format('(%s', item.node:type())
end
else
- text = string.format('"%s"', item.node:type():gsub('\n', '\\n'):gsub('"', '\\"'))
+ text = string.format('%q', item.node:type()):gsub('\n', 'n')
end
local next = self:get(i + 1)
diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua
index a9b066d158..ed3616ef46 100644
--- a/runtime/lua/vim/treesitter/health.lua
+++ b/runtime/lua/vim/treesitter/health.lua
@@ -24,7 +24,7 @@ function M.check()
else
local lang = ts.language.inspect(parsername)
health.ok(
- string.format('Parser: %-10s ABI: %d, path: %s', parsername, lang._abi_version, parser)
+ string.format('Parser: %-20s ABI: %d, path: %s', parsername, lang._abi_version, parser)
)
end
end
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index 388680259a..d2f986b874 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -4,7 +4,7 @@ local Range = require('vim.treesitter._range')
local ns = api.nvim_create_namespace('treesitter/highlighter')
----@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata
+---@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch
---@class (private) vim.treesitter.highlighter.Query
---@field private _query vim.treesitter.Query?
@@ -215,7 +215,7 @@ end
---@param start_row integer
---@param new_end integer
function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end)
- api.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1)
+ api.nvim__redraw({ buf = self.bufnr, range = { start_row, start_row + new_end + 1 } })
end
---@package
@@ -227,7 +227,7 @@ end
---@param changes Range6[]
function TSHighlighter:on_changedtree(changes)
for _, ch in ipairs(changes) do
- api.nvim__buf_redraw_range(self.bufnr, ch[1], ch[4] + 1)
+ api.nvim__redraw({ buf = self.bufnr, range = { ch[1], ch[4] + 1 } })
end
end
@@ -243,6 +243,46 @@ function TSHighlighter:get_query(lang)
return self._queries[lang]
end
+--- @param match TSQueryMatch
+--- @param bufnr integer
+--- @param capture integer
+--- @param metadata vim.treesitter.query.TSMetadata
+--- @return string?
+local function get_url(match, bufnr, capture, metadata)
+ ---@type string|number|nil
+ local url = metadata[capture] and metadata[capture].url
+
+ if not url or type(url) == 'string' then
+ return url
+ end
+
+ local captures = match:captures()
+
+ if not captures[url] then
+ return
+ end
+
+ -- Assume there is only one matching node. If there is more than one, take the URL
+ -- from the first.
+ local other_node = captures[url][1]
+
+ return vim.treesitter.get_node_text(other_node, bufnr, {
+ metadata = metadata[url],
+ })
+end
+
+--- @param capture_name string
+--- @return boolean?, integer
+local function get_spell(capture_name)
+ if capture_name == 'spell' then
+ return true, 0
+ elseif capture_name == 'nospell' then
+ -- Give nospell a higher priority so it always overrides spell captures.
+ return false, 1
+ end
+ return nil, 0
+end
+
---@param self vim.treesitter.highlighter
---@param buf integer
---@param line integer
@@ -258,12 +298,16 @@ local function on_line_impl(self, buf, line, is_spell_nav)
end
if state.iter == nil or state.next_row < line then
+ -- Mainly used to skip over folds
+
+ -- TODO(lewis6991): Creating a new iterator loses the cached predicate results for query
+ -- matches. Move this logic inside iter_captures() so we can maintain the cache.
state.iter =
state.highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1)
end
while line >= state.next_row do
- local capture, node, metadata = state.iter(line)
+ local capture, node, metadata, match = state.iter(line)
local range = { root_end_row + 1, 0, root_end_row + 1, 0 }
if node then
@@ -275,27 +319,30 @@ local function on_line_impl(self, buf, line, is_spell_nav)
local hl = state.highlighter_query:get_hl_from_capture(capture)
local capture_name = state.highlighter_query:query().captures[capture]
- local spell = nil ---@type boolean?
- if capture_name == 'spell' then
- spell = true
- elseif capture_name == 'nospell' then
- spell = false
- end
- -- Give nospell a higher priority so it always overrides spell captures.
- local spell_pri_offset = capture_name == 'nospell' and 1 or 0
+ local spell, spell_pri_offset = get_spell(capture_name)
+
+ -- The "priority" attribute can be set at the pattern level or on a particular capture
+ local priority = (
+ tonumber(metadata.priority or metadata[capture] and metadata[capture].priority)
+ or vim.highlight.priorities.treesitter
+ ) + spell_pri_offset
+
+ -- The "conceal" attribute can be set at the pattern level or on a particular capture
+ local conceal = metadata.conceal or metadata[capture] and metadata[capture].conceal
+
+ local url = get_url(match, buf, capture, metadata)
if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then
- local priority = (tonumber(metadata.priority) or vim.highlight.priorities.treesitter)
- + spell_pri_offset
api.nvim_buf_set_extmark(buf, ns, start_row, start_col, {
end_line = end_row,
end_col = end_col,
hl_group = hl,
ephemeral = true,
priority = priority,
- conceal = metadata.conceal,
+ conceal = conceal,
spell = spell,
+ url = url,
})
end
end
diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua
index 47abf65332..d0a74daa6c 100644
--- a/runtime/lua/vim/treesitter/language.lua
+++ b/runtime/lua/vim/treesitter/language.lua
@@ -88,6 +88,9 @@ function M.add(lang, opts)
filetype = { filetype, { 'string', 'table' }, true },
})
+ -- parser names are assumed to be lowercase (consistent behavior on case-insensitive file systems)
+ lang = lang:lower()
+
if vim._ts_has_language(lang) then
M.register(lang, filetype)
return
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 62714d3f1b..b0812123b9 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -81,7 +81,7 @@ local TSCallbackNames = {
---List of regions this tree should manage and parse. If nil then regions are
---taken from _trees. This is mostly a short-lived cache for included_regions()
---@field private _lang string Language name
----@field private _parent_lang? string Parent language name
+---@field private _parent? vim.treesitter.LanguageTree Parent LanguageTree
---@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.
@@ -106,9 +106,8 @@ LanguageTree.__index = LanguageTree
---@param source (integer|string) Buffer or text string to parse
---@param lang string Root language of this tree
---@param opts vim.treesitter.LanguageTree.new.Opts?
----@param parent_lang? string Parent language name of this tree
---@return vim.treesitter.LanguageTree parser object
-function LanguageTree.new(source, lang, opts, parent_lang)
+function LanguageTree.new(source, lang, opts)
language.add(lang)
opts = opts or {}
@@ -122,7 +121,6 @@ function LanguageTree.new(source, lang, opts, parent_lang)
local self = {
_source = source,
_lang = lang,
- _parent_lang = parent_lang,
_children = {},
_trees = {},
_opts = opts,
@@ -158,8 +156,10 @@ function LanguageTree:_set_logger()
local lang = self:lang()
- vim.fn.mkdir(vim.fn.stdpath('log'), 'p')
- local logfilename = vim.fs.joinpath(vim.fn.stdpath('log'), 'treesitter.log')
+ local logdir = vim.fn.stdpath('log') --[[@as string]]
+
+ vim.fn.mkdir(logdir, 'p')
+ local logfilename = vim.fs.joinpath(logdir, 'treesitter.log')
local logfile, openerr = io.open(logfilename, 'a+')
@@ -225,7 +225,10 @@ function LanguageTree:_log(...)
self._logger('nvim', table.concat(msg, ' '))
end
---- Invalidates this parser and all its children
+--- Invalidates this parser and its children.
+---
+--- Should only be called when the tracked state of the LanguageTree is not valid against the parse
+--- tree in treesitter. Doesn't clear filesystem cache. Called often, so needs to be fast.
---@param reload boolean|nil
function LanguageTree:invalidate(reload)
self._valid = false
@@ -460,24 +463,6 @@ function LanguageTree:parse(range)
return self._trees
end
----@deprecated Misleading name. Use `LanguageTree:children()` (non-recursive) instead,
---- add recursion yourself if needed.
---- Invokes the callback for each |LanguageTree| and its children recursively
----
----@param fn fun(tree: vim.treesitter.LanguageTree, lang: string)
----@param include_self boolean|nil Whether to include the invoking tree in the results
-function LanguageTree:for_each_child(fn, include_self)
- vim.deprecate('LanguageTree:for_each_child()', 'LanguageTree:children()', '0.11')
- if include_self then
- fn(self, self._lang)
- end
-
- for _, child in pairs(self._children) do
- --- @diagnostic disable-next-line:deprecated
- child:for_each_child(fn, true)
- end
-end
-
--- Invokes the callback for each |LanguageTree| recursively.
---
--- Note: This includes the invoking tree's child trees as well.
@@ -505,19 +490,25 @@ function LanguageTree:add_child(lang)
self:remove_child(lang)
end
- local child = LanguageTree.new(self._source, lang, self._opts, self:lang())
+ local child = LanguageTree.new(self._source, lang, self._opts)
-- Inherit recursive callbacks
for nm, cb in pairs(self._callbacks_rec) do
vim.list_extend(child._callbacks_rec[nm], cb)
end
+ child._parent = self
self._children[lang] = child
self:_do_callback('child_added', self._children[lang])
return self._children[lang]
end
+--- @package
+function LanguageTree:parent()
+ return self._parent
+end
+
--- Removes a child language from this |LanguageTree|.
---
---@private
@@ -752,7 +743,6 @@ local has_parser = vim.func._memoize(1, function(lang)
end)
--- Return parser name for language (if exists) or filetype (if registered and exists).
---- Also attempts with the input lower-cased.
---
---@param alias string language or filetype name
---@return string? # resolved parser name
@@ -766,19 +756,10 @@ local function resolve_lang(alias)
return alias
end
- if has_parser(alias:lower()) then
- return alias:lower()
- end
-
local lang = vim.treesitter.language.get_lang(alias)
if lang and has_parser(lang) then
return lang
end
-
- lang = vim.treesitter.language.get_lang(alias:lower())
- if lang and has_parser(lang) then
- return lang
- end
end
---@private
@@ -792,7 +773,7 @@ function LanguageTree:_get_injection(match, metadata)
local combined = metadata['injection.combined'] ~= nil
local injection_lang = metadata['injection.language'] --[[@as string?]]
local lang = metadata['injection.self'] ~= nil and self:lang()
- or metadata['injection.parent'] ~= nil and self._parent_lang
+ or metadata['injection.parent'] ~= nil and self._parent:lang()
or (injection_lang and resolve_lang(injection_lang))
local include_children = metadata['injection.include-children'] ~= nil
@@ -802,7 +783,11 @@ function LanguageTree:_get_injection(match, metadata)
-- Lang should override any other language tag
if name == 'injection.language' then
local text = vim.treesitter.get_node_text(node, self._source, { metadata = metadata[id] })
- lang = resolve_lang(text)
+ lang = resolve_lang(text:lower()) -- language names are always lower case
+ elseif name == 'injection.filename' then
+ local text = vim.treesitter.get_node_text(node, self._source, { metadata = metadata[id] })
+ local ft = vim.filetype.match({ filename = text })
+ lang = ft and resolve_lang(ft)
elseif name == 'injection.content' then
ranges = get_node_ranges(node, self._source, metadata[id], include_children)
end
@@ -1054,20 +1039,19 @@ function LanguageTree:_on_detach(...)
end
end
---- Registers callbacks for the |LanguageTree|.
----@param cbs table An |nvim_buf_attach()|-like table argument with the following handlers:
---- - `on_bytes` : see |nvim_buf_attach()|, but this will be called _after_ the parsers callback.
+--- Registers callbacks for the [LanguageTree].
+---@param cbs table<TSCallbackNameOn,function> An [nvim_buf_attach()]-like table argument with the following handlers:
+--- - `on_bytes` : see [nvim_buf_attach()], but this will be called _after_ the parsers callback.
--- - `on_changedtree` : a callback that will be called every time the tree has syntactical changes.
--- It will be passed two arguments: a table of the ranges (as node ranges) that
--- changed and the changed tree.
--- - `on_child_added` : emitted when a child is added to the tree.
--- - `on_child_removed` : emitted when a child is removed from the tree.
---- - `on_detach` : emitted when the buffer is detached, see |nvim_buf_detach_event|.
+--- - `on_detach` : emitted when the buffer is detached, see [nvim_buf_detach_event].
--- Takes one argument, the number of the buffer.
--- @param recursive? boolean Apply callbacks recursively for all children. Any new children will
--- also inherit the callbacks.
function LanguageTree:register_cbs(cbs, recursive)
- ---@cast cbs table<TSCallbackNameOn,function>
if not cbs then
return
end
@@ -1091,7 +1075,14 @@ end
---@param range Range
---@return boolean
local function tree_contains(tree, range)
- return Range.contains({ tree:root():range() }, range)
+ local tree_ranges = tree:included_ranges(false)
+
+ return Range.contains({
+ tree_ranges[1][1],
+ tree_ranges[1][2],
+ tree_ranges[#tree_ranges][3],
+ tree_ranges[#tree_ranges][4],
+ }, range)
end
--- Determines whether {range} is contained in the |LanguageTree|.
@@ -1108,12 +1099,18 @@ function LanguageTree:contains(range)
return false
end
+--- @class vim.treesitter.LanguageTree.tree_for_range.Opts
+--- @inlinedoc
+---
+--- Ignore injected languages
+--- (default: `true`)
+--- @field ignore_injections? boolean
+
--- Gets the tree that contains {range}.
---
---@param range Range4 `{ start_line, start_col, end_line, end_col }`
----@param opts table|nil Optional keyword arguments:
---- - ignore_injections boolean Ignore injected languages (default true)
----@return TSTree|nil
+---@param opts? vim.treesitter.LanguageTree.tree_for_range.Opts
+---@return TSTree?
function LanguageTree:tree_for_range(range, opts)
opts = opts or {}
local ignore = vim.F.if_nil(opts.ignore_injections, true)
@@ -1139,9 +1136,8 @@ end
--- Gets the smallest named node that contains {range}.
---
---@param range Range4 `{ start_line, start_col, end_line, end_col }`
----@param opts table|nil Optional keyword arguments:
---- - ignore_injections boolean Ignore injected languages (default true)
----@return TSNode | nil Found node
+---@param opts? vim.treesitter.LanguageTree.tree_for_range.Opts
+---@return TSNode?
function LanguageTree:named_node_for_range(range, opts)
local tree = self:tree_for_range(range, opts)
if tree then
@@ -1152,7 +1148,7 @@ end
--- Gets the appropriate language that contains {range}.
---
---@param range Range4 `{ start_line, start_col, end_line, end_col }`
----@return vim.treesitter.LanguageTree Managing {range}
+---@return vim.treesitter.LanguageTree tree Managing {range}
function LanguageTree:language_for_range(range)
for _, child in pairs(self._children) do
if child:contains(range) then
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index a086f5e876..ef5c2143a7 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.l