aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/treesitter
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim/treesitter')
-rw-r--r--runtime/lua/vim/treesitter/_fold.lua36
-rw-r--r--runtime/lua/vim/treesitter/_meta.lua113
-rw-r--r--runtime/lua/vim/treesitter/_meta/misc.lua78
-rw-r--r--runtime/lua/vim/treesitter/_meta/tsnode.lua185
-rw-r--r--runtime/lua/vim/treesitter/_meta/tstree.lua44
-rw-r--r--runtime/lua/vim/treesitter/_query_linter.lua21
-rw-r--r--runtime/lua/vim/treesitter/_range.lua4
-rw-r--r--runtime/lua/vim/treesitter/dev.lua72
-rw-r--r--runtime/lua/vim/treesitter/health.lua3
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua15
-rw-r--r--runtime/lua/vim/treesitter/language.lua64
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua44
-rw-r--r--runtime/lua/vim/treesitter/query.lua43
13 files changed, 498 insertions, 224 deletions
diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua
index eecf1ad6b1..7237d2e7d4 100644
--- a/runtime/lua/vim/treesitter/_fold.lua
+++ b/runtime/lua/vim/treesitter/_fold.lua
@@ -87,7 +87,7 @@ end
---@param srow integer
---@param erow integer 0-indexed, exclusive
function FoldInfo:add_range(srow, erow)
- list_insert(self.levels, srow + 1, erow, '=')
+ list_insert(self.levels, srow + 1, erow, -1)
list_insert(self.levels0, srow + 1, erow, -1)
end
@@ -114,7 +114,7 @@ local function compute_folds_levels(bufnr, info, srow, erow, parse_injections)
srow = srow or 0
erow = erow or api.nvim_buf_line_count(bufnr)
- local parser = ts.get_parser(bufnr)
+ local parser = assert(ts.get_parser(bufnr, nil, { error = false }))
parser:parse(parse_injections and { srow, erow } or nil)
@@ -131,24 +131,18 @@ local function compute_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 _, match, metadata in
- query:iter_matches(tree:root(), bufnr, math.max(srow - 1, 0), erow, { all = true })
- do
+ for _, match, metadata in query:iter_matches(tree:root(), bufnr, math.max(srow - 1, 0), erow) 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
+ if #nodes > 1 then
+ -- assumes nodes are ordered by range
+ local end_range = ts.get_range(nodes[#nodes], bufnr, metadata[id])
+ local _, _, end_stop, end_stop_col = Range.unpack4(end_range)
+ stop = end_stop
+ stop_col = end_stop_col
end
if stop_col == 0 then
@@ -268,6 +262,15 @@ end
---@package
function FoldInfo:do_foldupdate(bufnr)
+ -- InsertLeave is not executed when <C-C> is used for exiting the insert mode, leaving
+ -- do_foldupdate untouched. If another execution of foldupdate consumes foldupdate_range, the
+ -- InsertLeave do_foldupdate gets nil foldupdate_range. In that case, skip the update. This is
+ -- correct because the update that consumed the range must have incorporated the range that
+ -- InsertLeave meant to update.
+ if not self.foldupdate_range then
+ return
+ end
+
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
@@ -383,14 +386,13 @@ local function on_bytes(bufnr, foldinfo, start_row, start_col, old_row, old_col,
end
end
----@package
---@param lnum integer|nil
---@return string
function M.foldexpr(lnum)
lnum = lnum or vim.v.lnum
local bufnr = api.nvim_get_current_buf()
- local parser = vim.F.npcall(ts.get_parser, bufnr)
+ local parser = ts.get_parser(bufnr, nil, { error = false })
if not parser then
return '0'
end
diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua
deleted file mode 100644
index 177699a207..0000000000
--- a/runtime/lua/vim/treesitter/_meta.lua
+++ /dev/null
@@ -1,113 +0,0 @@
----@meta
-error('Cannot require a meta file')
-
----@class TSNode: userdata
----@field id fun(self: TSNode): string
----@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: true): integer, integer, integer, integer, integer, integer
----@field start fun(self: TSNode): integer, integer, integer
----@field end_ fun(self: TSNode): integer, integer, integer
----@field type fun(self: TSNode): string
----@field symbol fun(self: TSNode): integer
----@field named fun(self: TSNode): boolean
----@field missing fun(self: TSNode): boolean
----@field extra fun(self: TSNode): boolean
----@field child_count fun(self: TSNode): integer
----@field named_child_count fun(self: TSNode): integer
----@field child fun(self: TSNode, index: integer): TSNode?
----@field named_child fun(self: TSNode, index: integer): TSNode?
----@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?
----@field prev_named_sibling fun(self: TSNode): TSNode?
----@field named_children fun(self: TSNode): TSNode[]
----@field has_changes fun(self: TSNode): boolean
----@field has_error fun(self: TSNode): boolean
----@field sexpr fun(self: TSNode): string
----@field equal fun(self: TSNode, other: TSNode): boolean
----@field iter_children fun(self: TSNode): fun(): TSNode, string
----@field field fun(self: TSNode, name: string): TSNode[]
----@field byte_length fun(self: TSNode): integer
-local TSNode = {}
-
----@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string)
-
----@class TSParser: userdata
----@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean): 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)[])
----@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 _logger fun(self: TSParser): TSLoggerCallback
-
----@class TSTree: userdata
----@field root fun(self: TSTree): TSNode
----@field edit fun(self: TSTree, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _:integer)
----@field copy fun(self: TSTree): TSTree
----@field included_ranges fun(self: TSTree, include_bytes: true): Range6[]
----@field included_ranges fun(self: TSTree, include_bytes: false): Range4[]
-
----@class TSQuery: userdata
----@field inspect fun(self: TSQuery): TSQueryInfo
-
----@class (exact) TSQueryInfo
----@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
-
----@param lang string Language to use for the query
----@param query string Query string in s-expr syntax
----@return TSQuery
-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/_meta/misc.lua b/runtime/lua/vim/treesitter/_meta/misc.lua
new file mode 100644
index 0000000000..33701ef254
--- /dev/null
+++ b/runtime/lua/vim/treesitter/_meta/misc.lua
@@ -0,0 +1,78 @@
+---@meta
+-- luacheck: no unused args
+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|string, include_bytes: boolean): 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)[])
+---@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 _logger fun(self: TSParser): TSLoggerCallback
+
+---@class TSQuery: userdata
+---@field inspect fun(self: TSQuery): TSQueryInfo
+
+---@class (exact) TSQueryInfo
+---@field captures string[]
+---@field patterns table<integer, (integer|string)[][]>
+
+--- @param lang string
+--- @return table
+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_from_object = function(path, lang, symbol_name) end
+
+--- @param path string
+--- @param lang string
+vim._ts_add_language_from_wasm = function(path, lang) end
+
+---@return integer
+vim._ts_get_minimum_language_version = function() end
+
+---@param lang string Language to use for the query
+---@param query string Query string in s-expr syntax
+---@return TSQuery
+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 = {} -- luacheck: no unused
+
+--- @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 = {} -- luacheck: no unused
+
+--- @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/_meta/tsnode.lua b/runtime/lua/vim/treesitter/_meta/tsnode.lua
new file mode 100644
index 0000000000..acc9f8d24e
--- /dev/null
+++ b/runtime/lua/vim/treesitter/_meta/tsnode.lua
@@ -0,0 +1,185 @@
+---@meta
+-- luacheck: no unused args
+error('Cannot require a meta file')
+
+--- @brief A "treesitter node" represents one specific element of the parsed contents of a buffer,
+--- which can be captured by a |Query| for, e.g., highlighting. It is a |userdata| reference to an
+--- object held by the treesitter library.
+---
+--- An instance `TSNode` of a treesitter node supports the following methods.
+
+---@nodoc
+---@class TSNode: userdata
+---@field named_children fun(self: TSNode): TSNode[]
+---@field __has_ancestor fun(self: TSNode, node_types: string[]): boolean
+local TSNode = {} -- luacheck: no unused
+
+--- Get the node's immediate parent.
+--- Prefer |TSNode:child_containing_descendant()|
+--- for iterating over the node's ancestors.
+--- @return TSNode?
+function TSNode:parent() end
+
+--- Get the node's next sibling.
+--- @return TSNode?
+function TSNode:next_sibling() end
+
+--- Get the node's previous sibling.
+--- @return TSNode?
+function TSNode:prev_sibling() end
+
+--- Get the node's next named sibling.
+--- @return TSNode?
+function TSNode:next_named_sibling() end
+
+--- Get the node's previous named sibling.
+--- @return TSNode?
+function TSNode:prev_named_sibling() end
+
+--- Iterates over all the direct children of {TSNode}, regardless of whether
+--- they are named or not.
+--- Returns the child node plus the eventual field name corresponding to this
+--- child node.
+--- @return fun(): TSNode, string
+function TSNode:iter_children() end
+
+--- Returns a table of the nodes corresponding to the {name} field.
+--- @param name string
+--- @return TSNode[]
+function TSNode:field(name) end
+
+--- Get the node's number of children.
+--- @return integer
+function TSNode:child_count() end
+
+--- Get the node's child at the given {index}, where zero represents the first
+--- child.
+--- @param index integer
+--- @return TSNode?
+function TSNode:child(index) end
+
+--- Get the node's number of named children.
+--- @return integer
+function TSNode:named_child_count() end
+
+--- Get the node's named child at the given {index}, where zero represents the
+--- first named child.
+--- @param index integer
+--- @return TSNode?
+function TSNode:named_child(index) end
+
+--- Get the node's child that contains {descendant}.
+--- @param descendant TSNode
+--- @return TSNode?
+function TSNode:child_containing_descendant(descendant) end
+
+--- Get the node's start position. Return three values: the row, column and
+--- total byte count (all zero-based).
+--- @return integer, integer, integer
+function TSNode:start() end
+
+--- Get the node's end position. Return three values: the row, column and
+--- total byte count (all zero-based).
+--- @return integer, integer, integer
+function TSNode:end_() end
+
+--- Get the range of the node.
+---
+--- Return four or six values:
+---
+--- - start row
+--- - start column
+--- - start byte (if {include_bytes} is `true`)
+--- - end row
+--- - end column
+--- - end byte (if {include_bytes} is `true`)
+--- @param include_bytes boolean?
+function TSNode:range(include_bytes) end
+
+--- @nodoc
+--- @param include_bytes false?
+--- @return integer, integer, integer, integer
+function TSNode:range(include_bytes) end
+
+--- @nodoc
+--- @param include_bytes true
+--- @return integer, integer, integer, integer, integer, integer
+function TSNode:range(include_bytes) end
+
+--- Get the node's type as a string.
+--- @return string
+function TSNode:type() end
+
+--- Get the node's type as a numerical id.
+--- @return integer
+function TSNode:symbol() end
+
+--- Check if the node is named. Named nodes correspond to named rules in the
+--- grammar, whereas anonymous nodes correspond to string literals in the
+--- grammar.
+--- @return boolean
+function TSNode:named() end
+
+--- Check if the node is missing. Missing nodes are inserted by the parser in
+--- order to recover from certain kinds of syntax errors.
+--- @return boolean
+function TSNode:missing() end
+
+--- Check if the node is extra. Extra nodes represent things like comments,
+--- which are not required by the grammar but can appear anywhere.
+--- @return boolean
+function TSNode:extra() end
+
+--- Check if a syntax node has been edited.
+--- @return boolean
+function TSNode:has_changes() end
+
+--- Check if the node is a syntax error or contains any syntax errors.
+--- @return boolean
+function TSNode:has_error() end
+
+--- Get an S-expression representing the node as a string.
+--- @return string
+function TSNode:sexpr() end
+
+--- Get a unique identifier for the node inside its own tree.
+---
+--- No guarantees are made about this identifier's internal representation,
+--- except for being a primitive Lua type with value equality (so not a
+--- table). Presently it is a (non-printable) string.
+---
+--- Note: The `id` is not guaranteed to be unique for nodes from different
+--- trees.
+--- @return string
+function TSNode:id() end
+
+--- Get the |TSTree| of the node.
+--- @return TSTree
+function TSNode:tree() end
+
+--- Get the smallest node within this node that spans the given range of (row,
+--- column) positions
+--- @param start_row integer
+--- @param start_col integer
+--- @param end_row integer
+--- @param end_col integer
+--- @return TSNode?
+function TSNode:descendant_for_range(start_row, start_col, end_row, end_col) end
+
+--- Get the smallest named node within this node that spans the given range of
+--- (row, column) positions
+--- @param start_row integer
+--- @param start_col integer
+--- @param end_row integer
+--- @param end_col integer
+--- @return TSNode?
+function TSNode:named_descendant_for_range(start_row, start_col, end_row, end_col) end
+
+--- Check if {node} refers to the same node within the same tree.
+--- @param node TSNode
+--- @return boolean
+function TSNode:equal(node) end
+
+--- Return the number of bytes spanned by this node.
+--- @return integer
+function TSNode:byte_length() end
diff --git a/runtime/lua/vim/treesitter/_meta/tstree.lua b/runtime/lua/vim/treesitter/_meta/tstree.lua
new file mode 100644
index 0000000000..24cb60040e
--- /dev/null
+++ b/runtime/lua/vim/treesitter/_meta/tstree.lua
@@ -0,0 +1,44 @@
+---@meta
+-- luacheck: no unused args
+error('Cannot require a meta file')
+
+--- @brief A "treesitter tree" represents the parsed contents of a buffer, which can be
+--- used to perform further analysis. It is a |userdata| reference to an object
+--- held by the treesitter library.
+---
+--- An instance `TSTree` of a treesitter tree supports the following methods.
+
+---@nodoc
+---@class TSTree: userdata
+local TSTree = {} -- luacheck: no unused
+
+--- Return the root node of this tree.
+---@return TSNode
+function TSTree:root() end
+
+-- stylua: ignore
+---@param start_byte integer
+---@param end_byte_old integer
+---@param end_byte_new integer
+---@param start_row integer
+---@param start_col integer
+---@param end_row_old integer
+---@param end_col_old integer
+---@param end_row_new integer
+---@param end_col_new integer
+---@nodoc
+function TSTree:edit(start_byte, end_byte_old, end_byte_new, start_row, start_col, end_row_old, end_col_old, end_row_new, end_col_new) end
+
+--- Returns a copy of the `TSTree`.
+---@return TSTree
+function TSTree:copy() end
+
+---@param include_bytes true
+---@return Range6[]
+---@nodoc
+function TSTree:included_ranges(include_bytes) end
+
+---@param include_bytes false
+---@return Range4[]
+---@nodoc
+function TSTree:included_ranges(include_bytes) end
diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua
index 12b4cbc7b9..c5e4b86e1e 100644
--- a/runtime/lua/vim/treesitter/_query_linter.lua
+++ b/runtime/lua/vim/treesitter/_query_linter.lua
@@ -40,7 +40,8 @@ end
local function guess_query_lang(buf)
local filename = api.nvim_buf_get_name(buf)
if filename ~= '' then
- return vim.F.npcall(vim.fn.fnamemodify, filename, ':p:h:t')
+ local resolved_filename = vim.F.npcall(vim.fn.fnamemodify, filename, ':p:h:t')
+ return resolved_filename and vim.treesitter.language.get_lang(resolved_filename)
end
end
@@ -64,7 +65,7 @@ local function normalize_opts(buf, opts)
end
local lint_query = [[;; query
- (program [(named_node) (list) (grouping)] @toplevel)
+ (program [(named_node) (anonymous_node) (list) (grouping)] @toplevel)
(named_node
name: _ @node.named)
(anonymous_node
@@ -170,17 +171,17 @@ function M.lint(buf, opts)
--- @type (table|nil)
local parser_info = vim.F.npcall(vim.treesitter.language.inspect, lang)
+ local lang_context = {
+ lang = lang,
+ parser_info = parser_info,
+ is_first_lang = i == 1,
+ }
- local parser = vim.treesitter.get_parser(buf)
+ local parser = assert(vim.treesitter.get_parser(buf, nil, { error = false }))
parser:parse()
parser:for_each_tree(function(tree, ltree)
if ltree:lang() == 'query' then
- for _, match, _ in query:iter_matches(tree:root(), buf, 0, -1, { all = true }) do
- local lang_context = {
- lang = lang,
- parser_info = parser_info,
- is_first_lang = i == 1,
- }
+ for _, match, _ in query:iter_matches(tree:root(), buf, 0, -1) do
lint_match(buf, match, query, lang_context, diagnostics)
end
end
@@ -240,7 +241,7 @@ function M.omnifunc(findstart, base)
end
end
for _, s in pairs(parser_info.symbols) do
- local text = s[2] and s[1] or '"' .. s[1]:gsub([[\]], [[\\]]) .. '"' ---@type string
+ local text = s[2] and s[1] or string.format('%q', s[1]):gsub('\n', 'n') ---@type string
if text:find(base, 1, true) then
table.insert(items, text)
end
diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua
index 8d727c3c52..82ab8517aa 100644
--- a/runtime/lua/vim/treesitter/_range.lua
+++ b/runtime/lua/vim/treesitter/_range.lua
@@ -3,16 +3,19 @@ local api = vim.api
local M = {}
---@class Range2
+---@inlinedoc
---@field [1] integer start row
---@field [2] integer end row
---@class Range4
+---@inlinedoc
---@field [1] integer start row
---@field [2] integer start column
---@field [3] integer end row
---@field [4] integer end column
---@class Range6
+---@inlinedoc
---@field [1] integer start row
---@field [2] integer start column
---@field [3] integer start bytes
@@ -150,6 +153,7 @@ function M.contains(r1, r2)
return true
end
+--- @private
--- @param source integer|string
--- @param index integer
--- @return integer
diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua
index 5c91f101c0..90c3720b80 100644
--- a/runtime/lua/vim/treesitter/dev.lua
+++ b/runtime/lua/vim/treesitter/dev.lua
@@ -76,10 +76,14 @@ end
---
---@package
function TSTreeView:new(bufnr, lang)
- local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang)
- if not ok then
- local err = parser --[[ @as string ]]
- return nil, 'No parser available for the given buffer:\n' .. err
+ local parser = vim.treesitter.get_parser(bufnr or 0, lang, { error = false })
+ if not parser then
+ return nil,
+ string.format(
+ 'Failed to create TSTreeView for buffer %s: no parser for lang "%s"',
+ bufnr,
+ lang
+ )
end
-- For each child tree (injected language), find the root of the tree and locate the node within
@@ -154,7 +158,8 @@ end
---@param w integer
---@param b integer
-local function set_dev_properties(w, b)
+---@param opts nil|{ indent?: integer }
+local function set_dev_options(w, b, opts)
vim.wo[w].scrolloff = 5
vim.wo[w].wrap = false
vim.wo[w].foldmethod = 'expr'
@@ -165,6 +170,12 @@ local function set_dev_properties(w, b)
vim.bo[b].buftype = 'nofile'
vim.bo[b].bufhidden = 'wipe'
vim.bo[b].filetype = 'query'
+ vim.bo[b].swapfile = false
+
+ opts = opts or {}
+ if opts.indent then
+ vim.bo[b].shiftwidth = opts.indent
+ end
end
--- Updates the cursor position in the inspector to match the node under the cursor.
@@ -174,7 +185,7 @@ end
--- @param source_buf integer
--- @param inspect_buf integer
--- @param inspect_win integer
---- @param pos? { [1]: integer, [2]: integer }
+--- @param pos? [integer, integer]
local function set_inspector_cursor(treeview, lang, source_buf, inspect_buf, inspect_win, pos)
api.nvim_buf_clear_namespace(inspect_buf, treeview.ns, 0, -1)
@@ -183,6 +194,7 @@ local function set_inspector_cursor(treeview, lang, source_buf, inspect_buf, ins
lang = lang,
pos = pos,
ignore_injections = false,
+ include_anonymous = treeview.opts.anon,
})
if not cursor_node then
return
@@ -220,14 +232,13 @@ function TSTreeView:draw(bufnr)
local text ---@type string
if item.node:named() then
- if item.field then
- text = string.format('%s: (%s', item.field, item.node:type())
- else
- text = string.format('(%s', item.node:type())
- end
+ text = string.format('(%s', item.node:type())
else
text = string.format('%q', item.node:type()):gsub('\n', 'n')
end
+ if item.field then
+ text = string.format('%s: %s', item.field, text)
+ end
local next = self:get(i + 1)
if not next or next.depth <= item.depth then
@@ -325,7 +336,10 @@ function M.inspect_tree(opts)
opts = opts or {}
+ -- source buffer
local buf = api.nvim_get_current_buf()
+
+ -- window id for source buffer
local win = api.nvim_get_current_win()
local treeview = assert(TSTreeView:new(buf, opts.lang))
@@ -334,12 +348,14 @@ function M.inspect_tree(opts)
close_win(vim.b[buf].dev_inspect)
end
+ -- window id for tree buffer
local w = opts.winid
if not w then
vim.cmd(opts.command or '60vnew')
w = api.nvim_get_current_win()
end
+ -- tree buffer
local b = opts.bufnr
if b then
api.nvim_win_set_buf(w, b)
@@ -350,7 +366,7 @@ function M.inspect_tree(opts)
vim.b[buf].dev_inspect = w
vim.b[b].dev_base = win -- base window handle
vim.b[b].disable_query_linter = true
- set_dev_properties(w, b)
+ set_dev_options(w, b, { indent = treeview.opts.indent })
local title --- @type string?
local opts_title = opts.title
@@ -375,6 +391,12 @@ function M.inspect_tree(opts)
callback = function()
local row = api.nvim_win_get_cursor(w)[1]
local lnum, col = treeview:get(row).node:start()
+
+ -- update source window if original was closed
+ if not api.nvim_win_is_valid(win) then
+ win = vim.fn.win_findbuf(buf)[1]
+ end
+
api.nvim_set_current_win(win)
api.nvim_win_set_cursor(win, { lnum + 1, col })
end,
@@ -432,6 +454,7 @@ function M.inspect_tree(opts)
return true
end
+ w = api.nvim_get_current_win()
api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1)
local row = api.nvim_win_get_cursor(w)[1]
local lnum, col, end_lnum, end_col = treeview:get(row).node:range()
@@ -441,6 +464,11 @@ function M.inspect_tree(opts)
hl_group = 'Visual',
})
+ -- update source window if original was closed
+ if not api.nvim_win_is_valid(win) then
+ win = vim.fn.win_findbuf(buf)[1]
+ end
+
local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win)
-- Move the cursor if highlighted range is completely out of view
@@ -506,7 +534,10 @@ function M.inspect_tree(opts)
buffer = buf,
once = true,
callback = function()
- close_win(w)
+ -- close all tree windows
+ for _, window in pairs(vim.fn.win_findbuf(b)) do
+ close_win(window)
+ end
end,
})
end
@@ -519,7 +550,7 @@ local edit_ns = api.nvim_create_namespace('treesitter/dev-edit')
local function update_editor_highlights(query_win, base_win, lang)
local base_buf = api.nvim_win_get_buf(base_win)
local query_buf = api.nvim_win_get_buf(query_win)
- local parser = vim.treesitter.get_parser(base_buf, lang)
+ local parser = assert(vim.treesitter.get_parser(base_buf, lang, { error = false }))
api.nvim_buf_clear_namespace(base_buf, edit_ns, 0, -1)
local query_content = table.concat(api.nvim_buf_get_lines(query_buf, 0, -1, false), '\n')
@@ -554,6 +585,8 @@ end
--- @private
--- @param lang? string language to open the query editor for.
+--- @return boolean? `true` on success, `nil` on failure
+--- @return string? error message, if applicable
function M.edit_query(lang)
local buf = api.nvim_get_current_buf()
local win = api.nvim_get_current_win()
@@ -576,9 +609,10 @@ function M.edit_query(lang)
end
vim.cmd(cmd)
- local ok, parser = pcall(vim.treesitter.get_parser, buf, lang)
- if not ok then
- return nil, 'No parser available for the given buffer'
+ local parser = vim.treesitter.get_parser(buf, lang, { error = false })
+ if not parser then
+ return nil,
+ string.format('Failed to show query editor for buffer %s: no parser for lang "%s"', buf, lang)
end
lang = parser:lang()
@@ -587,7 +621,7 @@ function M.edit_query(lang)
vim.b[buf].dev_edit = query_win
vim.bo[query_buf].omnifunc = 'v:lua.vim.treesitter.query.omnifunc'
- set_dev_properties(query_win, query_buf)
+ set_dev_options(query_win, query_buf)
-- Note that omnifunc guesses the language based on the containing folder,
-- so we add the parser's language to the buffer's name so that omnifunc
@@ -652,6 +686,8 @@ function M.edit_query(lang)
})
vim.cmd('normal! G')
vim.cmd.startinsert()
+
+ return true
end
return M
diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua
index ed3616ef46..637f9ea543 100644
--- a/runtime/lua/vim/treesitter/health.lua
+++ b/runtime/lua/vim/treesitter/health.lua
@@ -28,6 +28,9 @@ function M.check()
)
end
end
+
+ local can_wasm = vim._ts_add_language_from_wasm ~= nil
+ health.info(string.format('Can load WASM parsers: %s', tostring(can_wasm)))
end
return M
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index d2f986b874..a94c408f4e 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -47,7 +47,7 @@ function TSHighlighterQuery:get_hl_from_capture(capture)
return self.hl_cache[capture]
end
----@package
+---@nodoc
function TSHighlighterQuery:query()
return self._query
end
@@ -75,7 +75,7 @@ local TSHighlighter = {
TSHighlighter.__index = TSHighlighter
----@package
+---@nodoc
---
--- Creates a highlighter for `tree`.
---
@@ -139,11 +139,14 @@ function TSHighlighter.new(tree, opts)
-- but use synload.vim rather than syntax.vim to not enable
-- syntax FileType autocmds. Later on we should integrate with the
-- `:syntax` and `set syntax=...` machinery properly.
+ -- Still need to ensure that syntaxset augroup exists, so that calling :destroy()
+ -- immediately afterwards will not error.
if vim.g.syntax_on ~= 1 then
vim.cmd.runtime({ 'syntax/synload.vim', bang = true })
+ vim.api.nvim_create_augroup('syntaxset', { clear = false })
end
- api.nvim_buf_call(self.bufnr, function()
+ vim._with({ buf = self.bufnr }, function()
vim.opt_local.spelloptions:append('noplainbuffer')
end)
@@ -232,7 +235,7 @@ function TSHighlighter:on_changedtree(changes)
end
--- Gets the query used for @param lang
----@package
+---@nodoc
---@param lang string Language used by the highlighter.
---@return vim.treesitter.highlighter.Query
function TSHighlighter:get_query(lang)
@@ -377,11 +380,15 @@ function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _)
return
end
+ -- Do not affect potentially populated highlight state. Here we just want a temporary
+ -- empty state so the C code can detect whether the region should be spell checked.
+ local highlight_states = self._highlight_states
self:prepare_highlight_states(srow, erow)
for row = srow, erow do
on_line_impl(self, buf, row, true)
end
+ self._highlight_states = highlight_states
end
---@private
diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua
index d0a74daa6c..9f7807e036 100644
--- a/runtime/lua/vim/treesitter/language.lua
+++ b/runtime/lua/vim/treesitter/language.lua
@@ -7,11 +7,15 @@ local ft_to_lang = {
help = 'vimdoc',
}
---- Get the filetypes associated with the parser named {lang}.
+--- Returns the filetypes for which a parser named {lang} is used.
+---
+--- The list includes {lang} itself plus all filetypes registered via
+--- |vim.treesitter.language.register()|.
+---
--- @param lang string Name of parser
--- @return string[] filetypes
function M.get_filetypes(lang)
- local r = {} ---@type string[]
+ local r = { lang } ---@type string[]
for ft, p in pairs(ft_to_lang) do
if p == lang then
r[#r + 1] = ft
@@ -20,6 +24,12 @@ function M.get_filetypes(lang)
return r
end
+--- Returns the language name to be used when loading a parser for {filetype}.
+---
+--- If no language has been explicitly registered via |vim.treesitter.language.register()|,
+--- default to {filetype}. For composite filetypes like `html.glimmer`, only the main filetype is
+--- returned.
+---
--- @param filetype string
--- @return string|nil
function M.get_lang(filetype)
@@ -29,9 +39,9 @@ function M.get_lang(filetype)
if ft_to_lang[filetype] then
return ft_to_lang[filetype]
end
- -- support subfiletypes like html.glimmer
+ -- for subfiletypes like html.glimmer use only "main" filetype
filetype = vim.split(filetype, '.', { plain = true })[1]
- return ft_to_lang[filetype]
+ return ft_to_lang[filetype] or filetype
end
---@deprecated
@@ -52,17 +62,27 @@ function M.require_language(lang, path, silent, symbol_name)
return installed
end
- M.add(lang, opts)
- return true
+ return M.add(lang, opts)
+end
+
+--- Load wasm or native parser (wrapper)
+--- todo(clason): move to C
+---
+---@param path string Path of parser library
+---@param lang string Language name
+---@param symbol_name? string Internal symbol name for the language to load (default lang)
+---@return boolean? True if parser is loaded
+local function loadparser(path, lang, symbol_name)
+ if vim.endswith(path, '.wasm') then
+ return vim._ts_add_language_from_wasm and vim._ts_add_language_from_wasm(path, lang)
+ else
+ return vim._ts_add_language_from_object(path, lang, symbol_name)
+ end
end
---@class vim.treesitter.language.add.Opts
---@inlinedoc
---
----Default filetype the parser should be associated with.
----(Default: {lang})
----@field filetype? string|string[]
----
---Optional path the parser is located at
---@field path? string
---
@@ -71,46 +91,52 @@ end
--- Load parser with name {lang}
---
---- Parsers are searched in the `parser` runtime directory, or the provided {path}
+--- Parsers are searched in the `parser` runtime directory, or the provided {path}.
+--- Can be used to check for available parsers before enabling treesitter features, e.g.,
+--- ```lua
+--- if vim.treesitter.language.add('markdown') then
+--- vim.treesitter.start(bufnr, 'markdown')
+--- end
+--- ```
---
---@param lang string Name of the parser (alphanumerical and `_` only)
---@param opts? vim.treesitter.language.add.Opts Options:
+---@return boolean? True if parser is loaded
+---@return string? Error if parser cannot be loaded
function M.add(lang, opts)
opts = opts or {}
local path = opts.path
- local filetype = opts.filetype or lang
local symbol_name = opts.symbol_name
vim.validate({
lang = { lang, 'string' },
path = { path, 'string', true },
symbol_name = { symbol_name, 'string', true },
- 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
+ return true
end
if path == nil then
+ -- allow only safe language names when looking for libraries to load
if not (lang and lang:match('[%w_]+') == lang) then
- error("'" .. lang .. "' is not a valid language name")
+ return nil, string.format('Invalid language name "%s"', lang)
end
local fname = 'parser/' .. lang .. '.*'
local paths = api.nvim_get_runtime_file(fname, false)
if #paths == 0 then
- error("no parser for '" .. lang .. "' language, see :help treesitter-parsers")
+ return nil, string.format('No parser for language "%s"', lang)
end
path = paths[1]
end
- vim._ts_add_language(path, lang, symbol_name)
- M.register(lang, filetype)
+ return loadparser(path, lang, symbol_name) or nil,
+ string.format('Cannot load parser %s for language "%s"', path, lang)
end
--- @param x string|string[]
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index b0812123b9..fd68c2b910 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -98,9 +98,9 @@ local LanguageTree = {}
LanguageTree.__index = LanguageTree
---- @package
+--- @nodoc
---
---- |LanguageTree| contains a tree of parsers: the root treesitter parser for {lang} and any
+--- LanguageTree contains a tree of parsers: the root treesitter parser for {lang} and any
--- "injected" language parsers, which themselves may inject other languages, recursively.
---
---@param source (integer|string) Buffer or text string to parse
@@ -108,7 +108,7 @@ LanguageTree.__index = LanguageTree
---@param opts vim.treesitter.LanguageTree.new.Opts?
---@return vim.treesitter.LanguageTree parser object
function LanguageTree.new(source, lang, opts)
- language.add(lang)
+ assert(language.add(lang))
opts = opts or {}
if source == 0 then
@@ -638,6 +638,8 @@ end
---Gets the set of included regions managed by this LanguageTree. This can be different from the
---regions set by injection query, because a partial |LanguageTree:parse()| drops the regions
---outside the requested range.
+---Each list represents a range in the form of
+---{ {start_row}, {start_col}, {start_bytes}, {end_row}, {end_col}, {end_bytes} }.
---@return table<integer, Range6[]>
function LanguageTree:included_regions()
if self._regions then
@@ -732,7 +734,7 @@ local function add_injection(t, tree_index, pattern, lang, combined, ranges)
table.insert(t[tree_index][lang][pattern].regions, ranges)
end
--- TODO(clason): replace by refactored `ts.has_parser` API (without registering)
+-- TODO(clason): replace by refactored `ts.has_parser` API (without side effects)
--- The result of this function is cached to prevent nvim_get_runtime_file from being
--- called too often
--- @param lang string parser name
@@ -831,13 +833,7 @@ function LanguageTree:_get_injections()
local start_line, _, end_line, _ = root_node:range()
for pattern, match, metadata in
- self._injection_query:iter_matches(
- root_node,
- self._source,
- start_line,
- end_line + 1,
- { all = true }
- )
+ self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1)
do
local lang, combined, ranges = self:_get_injection(match, metadata)
if lang then
@@ -951,7 +947,7 @@ function LanguageTree:_edit(
end
end
----@package
+---@nodoc
---@param bufnr integer
---@param changed_tick integer
---@param start_row integer
@@ -1023,12 +1019,12 @@ function LanguageTree:_on_bytes(
)
end
----@package
+---@nodoc
function LanguageTree:_on_reload()
self:invalidate(true)
end
----@package
+---@nodoc
function LanguageTree:_on_detach(...)
self:invalidate(true)
self:_do_callback('detach', ...)
@@ -1087,7 +1083,7 @@ end
--- Determines whether {range} is contained in the |LanguageTree|.
---
----@param range Range4 `{ start_line, start_col, end_line, end_col }`
+---@param range Range4
---@return boolean
function LanguageTree:contains(range)
for _, tree in pairs(self._trees) do
@@ -1108,7 +1104,7 @@ end
--- Gets the tree that contains {range}.
---
----@param range Range4 `{ start_line, start_col, end_line, end_col }`
+---@param range Range4
---@param opts? vim.treesitter.LanguageTree.tree_for_range.Opts
---@return TSTree?
function LanguageTree:tree_for_range(range, opts)
@@ -1133,9 +1129,21 @@ function LanguageTree:tree_for_range(range, opts)
return nil
end
+--- Gets the smallest node that contains {range}.
+---
+---@param range Range4
+---@param opts? vim.treesitter.LanguageTree.tree_for_range.Opts
+---@return TSNode?
+function LanguageTree:node_for_range(range, opts)
+ local tree = self:tree_for_range(range, opts)
+ if tree then
+ return tree:root():descendant_for_range(unpack(range))
+ end
+end
+
--- Gets the smallest named node that contains {range}.
---
----@param range Range4 `{ start_line, start_col, end_line, end_col }`
+---@param range Range4
---@param opts? vim.treesitter.LanguageTree.tree_for_range.Opts
---@return TSNode?
function LanguageTree:named_node_for_range(range, opts)
@@ -1147,7 +1155,7 @@ end
--- Gets the appropriate language that contains {range}.
---
----@param range Range4 `{ start_line, start_col, end_line, end_col }`
+---@param range Range4
---@return vim.treesitter.LanguageTree tree Managing {range}
function LanguageTree:language_for_range(range)
for _, child in pairs(self._children) do
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index ef5c2143a7..4614967799 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -247,8 +247,7 @@ end)
---
---@see [vim.treesitter.query.get()]
M.parse = memoize('concat-2', function(lang, query)
- language.add(lang)
-
+ assert(language.add(lang))
local ts_query = vim._ts_parse_query(lang, query)
return Query.new(lang, ts_query)
end)
@@ -487,8 +486,8 @@ predicate_handlers['any-vim-match?'] = predicate_handlers['any-match?']
---@class vim.treesitter.query.TSMetadata
---@field range? Range
---@field conceal? string
----@field [integer] vim.treesitter.query.TSMetadata
----@field [string] integer|string
+---@field [integer]? vim.treesitter.query.TSMetadata
+---@field [string]? integer|string
---@alias TSDirective fun(match: table<integer,TSNode[]>, _, _, predicate: (string|integer)[], metadata: vim.treesitter.query.TSMetadata)
@@ -620,16 +619,16 @@ local directive_handlers = {
--- @field force? boolean
---
--- Use the correct implementation of the match table where capture IDs map to
---- a list of nodes instead of a single node. Defaults to false (for backward
---- compatibility). This option will eventually become the default and removed.
+--- a list of nodes instead of a single node. Defaults to true. This option will
+--- be removed in a future release.
--- @field all? boolean
--- Adds a new predicate to be used in queries
---
---@param name string Name of the predicate, without leading #
----@param handler fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: table)
+---@param handler fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: vim.treesitter.query.TSMetadata)
--- - see |vim.treesitter.query.add_directive()| for argument meanings
----@param opts vim.treesitter.query.add_predicate.Opts
+---@param opts? vim.treesitter.query.add_predicate.Opts
function M.add_predicate(name, handler, opts)
-- Backward compatibility: old signature had "force" as boolean argument
if type(opts) == 'boolean' then
@@ -642,7 +641,7 @@ function M.add_predicate(name, handler, opts)
error(string.format('Overriding existing predicate %s', name))
end
- if opts.all then
+ if opts.all ~= false then
predicate_handlers[name] = handler
else
--- @param match table<integer, TSNode[]>
@@ -667,7 +666,7 @@ end
--- metadata table `metadata[capture_id].key = value`
---
---@param name string Name of the directive, without leading #
----@param handler fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: table)
+---@param handler fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: vim.treesitter.query.TSMetadata)
--- - match: A table mapping capture IDs to a list of captured nodes
--- - pattern: the index of the matching pattern in the query file
--- - predicate: list of strings containing the full directive being called, e.g.
@@ -894,16 +893,10 @@ end
--- index of the pattern in the query, a table mapping capture indices to a list
--- of nodes, and metadata from any directives processing the match.
---
---- WARNING: Set `all=true` to ensure all matching nodes in a match are
---- returned, otherwise only the last node in a match is returned, breaking captures
---- involving quantifiers such as `(comment)+ @comment`. The default option
---- `all=false` is only provided for backward compatibility and will be removed
---- after Nvim 0.10.
----
--- Example:
---
--- ```lua
---- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, 0, -1, { all = true }) do
+--- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, 0, -1) do
--- for id, nodes in pairs(match) do
--- local name = query.captures[id]
--- for _, node in ipairs(nodes) do
@@ -925,11 +918,11 @@ end
--- - max_start_depth (integer) if non-zero, sets the maximum start depth
--- for each match. This is used to prevent traversing too deep into a tree.
--- - match_limit (integer) Set the maximum number of in-progress matches (Default: 256).
---- - all (boolean) When set, the returned match table maps capture IDs to a list of nodes.
---- Older versions of iter_matches incorrectly mapped capture IDs to a single node, which is
---- incorrect behavior. This option will eventually become the default and removed.
+--- - all (boolean) When `false` (default `true`), the returned table maps capture IDs to a single
+--- (last) node instead of the full list of matching nodes. This option is only for backward
+--- compatibility and will be removed in a future release.
---
----@return (fun(): integer, table<integer, TSNode[]>, table): pattern id, match, metadata
+---@return (fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata): pattern id, match, metadata
function Query:iter_matches(node, source, start, stop, opts)
opts = opts or {}
opts.match_limit = opts.match_limit or 256
@@ -960,10 +953,10 @@ function Query:iter_matches(node, source, start, stop, opts)
local captures = match:captures()
- if not opts.all then
+ if opts.all == false then
-- Convert the match table into the old buggy version for backward
- -- compatibility. This is slow. Plugin authors, if you're reading this, set the "all"
- -- option!
+ -- compatibility. This is slow, but we only do it when the caller explicitly opted into it by
+ -- setting `all` to `false`.
local old_match = {} ---@type table<integer, TSNode>
for k, v in pairs(captures or {}) do
old_match[k] = v[#v]
@@ -1034,7 +1027,7 @@ end
---
--- @param lang? string language to open the query editor for. If omitted, inferred from the current buffer's filetype.
function M.edit(lang)
- vim.treesitter.dev.edit_query(lang)
+ assert(vim.treesitter.dev.edit_query(lang))
end
return M