fix(treesitter): fix most diagnostics

This commit is contained in:
Lewis Russell
2023-02-04 14:58:38 +00:00
committed by GitHub
parent 69bb145cea
commit 9a5678463c
10 changed files with 455 additions and 220 deletions

View File

@@ -41,4 +41,5 @@ globals = {
exclude_files = { exclude_files = {
'test/functional/fixtures/lua/syntax_error.lua', 'test/functional/fixtures/lua/syntax_error.lua',
'runtime/lua/vim/treesitter/_meta.lua'
} }

View File

@@ -1362,7 +1362,8 @@ defer_fn({fn}, {timeout}) *vim.defer_fn()*
Parameters: ~ Parameters: ~
• {fn} (function) Callback to call once `timeout` expires • {fn} (function) Callback to call once `timeout` expires
• {timeout} integer Number of milliseconds to wait before calling `fn` • {timeout} (integer) Number of milliseconds to wait before calling
`fn`
Return: ~ Return: ~
(table) timer luv timer object (table) timer luv timer object

View File

@@ -66,102 +66,102 @@ See |lua-treesitter-languagetree| for the list of available methods.
============================================================================== ==============================================================================
TREESITTER TREES *treesitter-tree* TREESITTER TREES *treesitter-tree*
*tstree* *TSTree*
A "treesitter tree" represents the parsed contents of a buffer, which can be A "treesitter tree" represents the parsed contents of a buffer, which can be
used to perform further analysis. It is a |luaref-userdata| reference to an used to perform further analysis. It is a |luaref-userdata| reference to an
object held by the tree-sitter library. object held by the tree-sitter library.
An instance `tstree` of a treesitter tree supports the following methods. An instance `TSTree` of a treesitter tree supports the following methods.
tstree:root() *tstree:root()* TSTree:root() *TSTree:root()*
Return the root node of this tree. Return the root node of this tree.
tstree:copy() *tstree:copy()* TSTree:copy() *TSTree:copy()*
Returns a copy of the `tstree`. Returns a copy of the `TSTree`.
============================================================================== ==============================================================================
TREESITTER NODES *treesitter-node* TREESITTER NODES *treesitter-node*
*tsnode* *TSNode*
A "treesitter node" represents one specific element of the parsed contents of 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 a buffer, which can be captured by a |Query| for, e.g., highlighting. It is a
|luaref-userdata| reference to an object held by the tree-sitter library. |luaref-userdata| reference to an object held by the tree-sitter library.
An instance `tsnode` of a treesitter node supports the following methods. An instance `TSNode` of a treesitter node supports the following methods.
tsnode:parent() *tsnode:parent()* TSNode:parent() *TSNode:parent()*
Get the node's immediate parent. Get the node's immediate parent.
tsnode:next_sibling() *tsnode:next_sibling()* TSNode:next_sibling() *TSNode:next_sibling()*
Get the node's next sibling. Get the node's next sibling.
tsnode:prev_sibling() *tsnode:prev_sibling()* TSNode:prev_sibling() *TSNode:prev_sibling()*
Get the node's previous sibling. Get the node's previous sibling.
tsnode:next_named_sibling() *tsnode:next_named_sibling()* TSNode:next_named_sibling() *TSNode:next_named_sibling()*
Get the node's next named sibling. Get the node's next named sibling.
tsnode:prev_named_sibling() *tsnode:prev_named_sibling()* TSNode:prev_named_sibling() *TSNode:prev_named_sibling()*
Get the node's previous named sibling. Get the node's previous named sibling.
tsnode:iter_children() *tsnode:iter_children()* TSNode:iter_children() *TSNode:iter_children()*
Iterates over all the direct children of {tsnode}, regardless of whether Iterates over all the direct children of {TSNode}, regardless of whether
they are named or not. they are named or not.
Returns the child node plus the eventual field name corresponding to this Returns the child node plus the eventual field name corresponding to this
child node. child node.
tsnode:field({name}) *tsnode:field()* TSNode:field({name}) *TSNode:field()*
Returns a table of the nodes corresponding to the {name} field. Returns a table of the nodes corresponding to the {name} field.
tsnode:child_count() *tsnode:child_count()* TSNode:child_count() *TSNode:child_count()*
Get the node's number of children. Get the node's number of children.
tsnode:child({index}) *tsnode:child()* TSNode:child({index}) *TSNode:child()*
Get the node's child at the given {index}, where zero represents the first Get the node's child at the given {index}, where zero represents the first
child. child.
tsnode:named_child_count() *tsnode:named_child_count()* TSNode:named_child_count() *TSNode:named_child_count()*
Get the node's number of named children. Get the node's number of named children.
tsnode:named_child({index}) *tsnode:named_child()* TSNode:named_child({index}) *TSNode:named_child()*
Get the node's named child at the given {index}, where zero represents the Get the node's named child at the given {index}, where zero represents the
first named child. first named child.
tsnode:start() *tsnode:start()* TSNode:start() *TSNode:start()*
Get the node's start position. Return three values: the row, column and Get the node's start position. Return three values: the row, column and
total byte count (all zero-based). total byte count (all zero-based).
tsnode:end_() *tsnode:end_()* TSNode:end_() *TSNode:end_()*
Get the node's end position. Return three values: the row, column and Get the node's end position. Return three values: the row, column and
total byte count (all zero-based). total byte count (all zero-based).
tsnode:range() *tsnode:range()* TSNode:range() *TSNode:range()*
Get the range of the node. Return four values: the row, column of the Get the range of the node. Return four values: the row, column of the
start position, then the row, column of the end position. start position, then the row, column of the end position.
tsnode:type() *tsnode:type()* TSNode:type() *TSNode:type()*
Get the node's type as a string. Get the node's type as a string.
tsnode:symbol() *tsnode:symbol()* TSNode:symbol() *TSNode:symbol()*
Get the node's type as a numerical id. Get the node's type as a numerical id.
tsnode:named() *tsnode:named()* TSNode:named() *TSNode:named()*
Check if the node is named. Named nodes correspond to named rules in the 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, whereas anonymous nodes correspond to string literals in the
grammar. grammar.
tsnode:missing() *tsnode:missing()* TSNode:missing() *TSNode:missing()*
Check if the node is missing. Missing nodes are inserted by the parser in Check if the node is missing. Missing nodes are inserted by the parser in
order to recover from certain kinds of syntax errors. order to recover from certain kinds of syntax errors.
tsnode:has_error() *tsnode:has_error()* TSNode:has_error() *TSNode:has_error()*
Check if the node is a syntax error or contains any syntax errors. Check if the node is a syntax error or contains any syntax errors.
tsnode:sexpr() *tsnode:sexpr()* TSNode:sexpr() *TSNode:sexpr()*
Get an S-expression representing the node as a string. Get an S-expression representing the node as a string.
tsnode:id() *tsnode:id()* TSNode:id() *TSNode:id()*
Get an unique identifier for the node inside its own tree. Get an unique identifier for the node inside its own tree.
No guarantees are made about this identifier's internal representation, No guarantees are made about this identifier's internal representation,
@@ -171,20 +171,20 @@ tsnode:id() *tsnode:id()*
Note: The `id` is not guaranteed to be unique for nodes from different Note: The `id` is not guaranteed to be unique for nodes from different
trees. trees.
*tsnode:descendant_for_range()* *TSNode:descendant_for_range()*
tsnode:descendant_for_range({start_row}, {start_col}, {end_row}, {end_col}) TSNode:descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
Get the smallest node within this node that spans the given range of (row, Get the smallest node within this node that spans the given range of (row,
column) positions column) positions
*tsnode:named_descendant_for_range()* *TSNode:named_descendant_for_range()*
tsnode:named_descendant_for_range({start_row}, {start_col}, {end_row}, {end_col}) TSNode:named_descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
Get the smallest named node within this node that spans the given range of Get the smallest named node within this node that spans the given range of
(row, column) positions (row, column) positions
============================================================================== ==============================================================================
TREESITTER QUERIES *treesitter-query* TREESITTER QUERIES *treesitter-query*
Treesitter queries are a way to extract information about a parsed |tstree|, Treesitter queries are a way to extract information about a parsed |TSTree|,
e.g., for the purpose of highlighting. Briefly, a `query` consists of one or e.g., for the purpose of highlighting. Briefly, a `query` consists of one or
more patterns. A `pattern` is defined over node types in the syntax tree. A more patterns. A `pattern` is defined over node types in the syntax tree. A
`match` corresponds to specific elements of the syntax tree which match a `match` corresponds to specific elements of the syntax tree which match a
@@ -336,7 +336,7 @@ repeated, for example, the following two modeline blocks are both valid: >
TREESITTER SYNTAX HIGHLIGHTING *treesitter-highlight* TREESITTER SYNTAX HIGHLIGHTING *treesitter-highlight*
Syntax highlighting is specified through queries named `highlights.scm`, Syntax highlighting is specified through queries named `highlights.scm`,
which match a |tsnode| in the parsed |tstree| to a `capture` that can be which match a |TSNode| in the parsed |TSTree| to a `capture` that can be
assigned a highlight group. For example, the query > assigned a highlight group. For example, the query >
(parameters (identifier) @parameter) (parameters (identifier) @parameter)
@@ -486,7 +486,7 @@ get_captures_at_cursor({winnr})
Returns a list of highlight capture names under the cursor Returns a list of highlight capture names under the cursor
Parameters: ~ Parameters: ~
• {winnr} (number|nil) Window handle or 0 for current window (default) • {winnr} (integer|nil) Window handle or 0 for current window (default)
Return: ~ Return: ~
string[] List of capture names string[] List of capture names
@@ -500,9 +500,9 @@ get_captures_at_pos({bufnr}, {row}, {col})
if none are defined). if none are defined).
Parameters: ~ Parameters: ~
• {bufnr} (number) Buffer number (0 for current buffer) • {bufnr} (integer) Buffer number (0 for current buffer)
• {row} (number) Position row • {row} (integer) Position row
• {col} (number) Position column • {col} (integer) Position column
Return: ~ Return: ~
table[] List of captures `{ capture = "capture name", metadata = { ... table[] List of captures `{ capture = "capture name", metadata = { ...
@@ -512,7 +512,7 @@ get_node_at_cursor({winnr}) *vim.treesitter.get_node_at_cursor()*
Returns the smallest named node under the cursor Returns the smallest named node under the cursor
Parameters: ~ Parameters: ~
• {winnr} (number|nil) Window handle or 0 for current window (default) • {winnr} (integer|nil) Window handle or 0 for current window (default)
Return: ~ Return: ~
(string) Name of node under the cursor (string) Name of node under the cursor
@@ -522,25 +522,28 @@ get_node_at_pos({bufnr}, {row}, {col}, {opts})
Returns the smallest named node at the given position Returns the smallest named node at the given position
Parameters: ~ Parameters: ~
• {bufnr} (number) Buffer number (0 for current buffer) • {bufnr} (integer) Buffer number (0 for current buffer)
• {row} (number) Position row • {row} (integer) Position row
• {col} (number) Position column • {col} (integer) Position column
• {opts} (table) Optional keyword arguments: • {opts} (table) Optional keyword arguments:
• lang string|nil Parser language • lang string|nil Parser language
• ignore_injections boolean Ignore injected languages • ignore_injections boolean Ignore injected languages
(default true) (default true)
Return: ~ Return: ~
userdata|nil |tsnode| under the cursor |TSNode||nil under the cursor
get_node_range({node_or_range}) *vim.treesitter.get_node_range()* get_node_range({node_or_range}) *vim.treesitter.get_node_range()*
Returns the node's range or an unpacked range table Returns the node's range or an unpacked range table
Parameters: ~ Parameters: ~
• {node_or_range} (userdata|table) |tsnode| or table of positions • {node_or_range} (|TSNode||table) Node or table of positions
Return: ~ Return: ~
(table) `{ start_row, start_col, end_row, end_col }` (integer) start_row
(integer) start_col
(integer) end_row
(integer) end_col
get_parser({bufnr}, {lang}, {opts}) *vim.treesitter.get_parser()* get_parser({bufnr}, {lang}, {opts}) *vim.treesitter.get_parser()*
Returns the parser for a specific buffer and filetype and attaches it to Returns the parser for a specific buffer and filetype and attaches it to
@@ -549,14 +552,14 @@ get_parser({bufnr}, {lang}, {opts}) *vim.treesitter.get_parser()*
If needed, this will create the parser. If needed, this will create the parser.
Parameters: ~ Parameters: ~
• {bufnr} (number|nil) Buffer the parser should be tied to (default: • {bufnr} (integer|nil) Buffer the parser should be tied to (default:
current buffer) current buffer)
• {lang} (string|nil) Filetype of this parser (default: buffer • {lang} (string|nil) Filetype of this parser (default: buffer
filetype) filetype)
• {opts} (table|nil) Options to pass to the created language tree • {opts} (table|nil) Options to pass to the created language tree
Return: ~ Return: ~
LanguageTree |LanguageTree| object to use for parsing |LanguageTree| object to use for parsing
*vim.treesitter.get_string_parser()* *vim.treesitter.get_string_parser()*
get_string_parser({str}, {lang}, {opts}) get_string_parser({str}, {lang}, {opts})
@@ -568,14 +571,14 @@ get_string_parser({str}, {lang}, {opts})
• {opts} (table|nil) Options to pass to the created language tree • {opts} (table|nil) Options to pass to the created language tree
Return: ~ Return: ~
LanguageTree |LanguageTree| object to use for parsing |LanguageTree| object to use for parsing
is_ancestor({dest}, {source}) *vim.treesitter.is_ancestor()* is_ancestor({dest}, {source}) *vim.treesitter.is_ancestor()*
Determines whether a node is the ancestor of another Determines whether a node is the ancestor of another
Parameters: ~ Parameters: ~
• {dest} userdata Possible ancestor |tsnode| • {dest} |TSNode| Possible ancestor
• {source} userdata Possible descendant |tsnode| • {source} |TSNode| Possible descendant
Return: ~ Return: ~
(boolean) True if {dest} is an ancestor of {source} (boolean) True if {dest} is an ancestor of {source}
@@ -585,9 +588,9 @@ is_in_node_range({node}, {line}, {col})
Determines whether (line, col) position is in node range Determines whether (line, col) position is in node range
Parameters: ~ Parameters: ~
• {node} userdata |tsnode| defining the range • {node} |TSNode| defining the range
• {line} (number) Line (0-based) • {line} (integer) Line (0-based)
• {col} (number) Column (0-based) • {col} (integer) Column (0-based)
Return: ~ Return: ~
(boolean) True if the position is in node range (boolean) True if the position is in node range
@@ -596,7 +599,7 @@ node_contains({node}, {range}) *vim.treesitter.node_contains()*
Determines if a node contains a range Determines if a node contains a range
Parameters: ~ Parameters: ~
• {node} userdata |tsnode| • {node} |TSNode|
• {range} (table) • {range} (table)
Return: ~ Return: ~
@@ -615,14 +618,14 @@ show_tree({opts}) *vim.treesitter.show_tree()*
keys: keys:
• lang (string|nil): The language of the source buffer. If • lang (string|nil): The language of the source buffer. If
omitted, the filetype of the source buffer is used. omitted, the filetype of the source buffer is used.
• bufnr (number|nil): Buffer to draw the tree into. If • bufnr (integer|nil): Buffer to draw the tree into. If
omitted, a new buffer is created. omitted, a new buffer is created.
• winid (number|nil): Window id to display the tree buffer in. • winid (integer|nil): Window id to display the tree buffer
If omitted, a new window is created with {command}. in. If omitted, a new window is created with {command}.
• command (string|nil): Vimscript command to create the • command (string|nil): Vimscript command to create the
window. Default value is "topleft 60vnew". Only used when window. Default value is "topleft 60vnew". Only used when
{winid} is nil. {winid} is nil.
• title (string|fun(bufnr:number):string|nil): Title of the • title (string|fun(bufnr:integer):string|nil): Title of the
window. If a function, it accepts the buffer number of the window. If a function, it accepts the buffer number of the
source buffer as its only argument and should return a source buffer as its only argument and should return a
string. string.
@@ -647,7 +650,7 @@ start({bufnr}, {lang}) *vim.treesitter.start()*
< <
Parameters: ~ Parameters: ~
• {bufnr} (number|nil) Buffer to be highlighted (default: current • {bufnr} (integer|nil) Buffer to be highlighted (default: current
buffer) buffer)
• {lang} (string|nil) Language of the parser (default: buffer • {lang} (string|nil) Language of the parser (default: buffer
filetype) filetype)
@@ -656,7 +659,7 @@ stop({bufnr}) *vim.treesitter.stop()*
Stops treesitter highlighting for a buffer Stops treesitter highlighting for a buffer
Parameters: ~ Parameters: ~
• {bufnr} (number|nil) Buffer to stop highlighting (default: current • {bufnr} (integer|nil) Buffer to stop highlighting (default: current
buffer) buffer)
@@ -709,8 +712,8 @@ add_directive({name}, {handler}, {force})
Parameters: ~ Parameters: ~
• {name} (string) Name of the directive, without leading # • {name} (string) Name of the directive, without leading #
• {handler} function(match:table, pattern:string, bufnr:number, • {handler} function(match:table<string,|TSNode|>, pattern:string,
predicate:string[], metadata:table) bufnr:number, predicate:string[], metadata:table)
• match: see |treesitter-query| • match: see |treesitter-query|
• node-level data are accessible via `match[capture_id]` • node-level data are accessible via `match[capture_id]`
@@ -718,6 +721,7 @@ add_directive({name}, {handler}, {force})
• predicate: list of strings containing the full directive • predicate: list of strings containing the full directive
being called, e.g. `(node (#set! conceal "-"))` would get being called, e.g. `(node (#set! conceal "-"))` would get
the predicate `{ "#set!", "conceal", "-" }` the predicate `{ "#set!", "conceal", "-" }`
• {force} (boolean)
*vim.treesitter.query.add_predicate()* *vim.treesitter.query.add_predicate()*
add_predicate({name}, {handler}, {force}) add_predicate({name}, {handler}, {force})
@@ -725,17 +729,18 @@ add_predicate({name}, {handler}, {force})
Parameters: ~ Parameters: ~
• {name} (string) Name of the predicate, without leading # • {name} (string) Name of the predicate, without leading #
• {handler} function(match:table, pattern:string, bufnr:number, • {handler} function(match:table<string,|TSNode|>, pattern:string,
predicate:string[]) bufnr:number, predicate:string[])
• see |vim.treesitter.query.add_directive()| for argument • see |vim.treesitter.query.add_directive()| for argument
meanings meanings
• {force} (boolean)
*vim.treesitter.query.get_node_text()* *vim.treesitter.query.get_node_text()*
get_node_text({node}, {source}, {opts}) get_node_text({node}, {source}, {opts})
Gets the text corresponding to a given node Gets the text corresponding to a given node
Parameters: ~ Parameters: ~
• {node} userdata |tsnode| • {node} |TSNode|
• {source} (number|string) Buffer or string from which the {node} is • {source} (number|string) Buffer or string from which the {node} is
extracted extracted
• {opts} (table|nil) Optional parameters. • {opts} (table|nil) Optional parameters.
@@ -743,7 +748,7 @@ get_node_text({node}, {source}, {opts})
true) true)
Return: ~ Return: ~
(string[]|string) (string[]|string|nil)
get_query({lang}, {query_name}) *vim.treesitter.query.get_query()* get_query({lang}, {query_name}) *vim.treesitter.query.get_query()*
Returns the runtime query {query_name} for {lang}. Returns the runtime query {query_name} for {lang}.
@@ -753,7 +758,7 @@ get_query({lang}, {query_name}) *vim.treesitter.query.get_query()*
• {query_name} (string) Name of the query (e.g. "highlights") • {query_name} (string) Name of the query (e.g. "highlights")
Return: ~ Return: ~
Query Parsed query Query|nil Parsed query
*vim.treesitter.query.get_query_files()* *vim.treesitter.query.get_query_files()*
get_query_files({lang}, {query_name}, {is_included}) get_query_files({lang}, {query_name}, {is_included})
@@ -826,16 +831,15 @@ Query:iter_captures({self}, {node}, {source}, {start}, {stop})
< <
Parameters: ~ Parameters: ~
• {node} userdata |tsnode| under which the search will occur • {node} |TSNode| under which the search will occur
• {source} (number|string) Source buffer or string to extract text from • {source} (integer|string) Source buffer or string to extract text
from
• {start} (number) Starting line for the search • {start} (number) Starting line for the search
• {stop} (number) Stopping line for the search (end-exclusive) • {stop} (number) Stopping line for the search (end-exclusive)
• {self} • {self}
Return: ~ Return: ~
(number) capture Matching capture id (fun(): integer, TSNode, TSMetadata ): capture id, capture node, metadata
(table) capture_node Capture for {node}
(table) metadata for the {capture}
*Query:iter_matches()* *Query:iter_matches()*
Query:iter_matches({self}, {node}, {source}, {start}, {stop}) Query:iter_matches({self}, {node}, {source}, {start}, {stop})
@@ -861,16 +865,15 @@ Query:iter_matches({self}, {node}, {source}, {start}, {stop})
< <
Parameters: ~ Parameters: ~
• {node} userdata |tsnode| under which the search will occur • {node} |TSNode| under which the search will occur
• {source} (number|string) Source buffer or string to search • {source} (integer|string) Source buffer or string to search
• {start} (number) Starting line for the search • {start} (integer) Starting line for the search
• {stop} (number) Stopping line for the search (end-exclusive) • {stop} (integer) Stopping line for the search (end-exclusive)
• {self} • {self}
Return: ~ Return: ~
(number) pattern id (fun(): integer, table<integer,TSNode>, table): pattern id, match,
(table) match metadata
(table) metadata
*vim.treesitter.query.set_query()* *vim.treesitter.query.set_query()*
set_query({lang}, {query_name}, {text}) set_query({lang}, {query_name}, {text})
@@ -892,7 +895,7 @@ new({tree}, {opts}) *vim.treesitter.highlighter.new()*
Creates a new highlighter using Creates a new highlighter using
Parameters: ~ Parameters: ~
• {tree} LanguageTree |LanguageTree| parser object to use for highlighting • {tree} |LanguageTree| parser object to use for highlighting
• {opts} (table|nil) Configuration of the highlighter: • {opts} (table|nil) Configuration of the highlighter:
• queries table overwrite queries used by the highlighter • queries table overwrite queries used by the highlighter
@@ -940,9 +943,9 @@ LanguageTree:for_each_child({self}, {fn}, {include_self})
Invokes the callback for each |LanguageTree| and its children recursively Invokes the callback for each |LanguageTree| and its children recursively
Parameters: ~ Parameters: ~
• {fn} function(tree: LanguageTree, lang: string) • {fn} fun(tree: LanguageTree, lang: string)
• {include_self} (boolean) Whether to include the invoking tree in the • {include_self} (boolean|nil) Whether to include the invoking tree in
results the results
• {self} • {self}
LanguageTree:for_each_tree({self}, {fn}) *LanguageTree:for_each_tree()* LanguageTree:for_each_tree({self}, {fn}) *LanguageTree:for_each_tree()*
@@ -951,7 +954,7 @@ LanguageTree:for_each_tree({self}, {fn}) *LanguageTree:for_each_tree()*
Note: This includes the invoking tree's child trees as well. Note: This includes the invoking tree's child trees as well.
Parameters: ~ Parameters: ~
• {fn} function(tree: TSTree, languageTree: LanguageTree) • {fn} fun(tree: TSTree, ltree: LanguageTree)
• {self} • {self}
LanguageTree:included_regions({self}) *LanguageTree:included_regions()* LanguageTree:included_regions({self}) *LanguageTree:included_regions()*
@@ -964,6 +967,7 @@ LanguageTree:invalidate({self}, {reload}) *LanguageTree:invalidate()*
Invalidates this parser and all its children Invalidates this parser and all its children
Parameters: ~ Parameters: ~
• {reload} (boolean|nil)
• {self} • {self}
LanguageTree:is_valid({self}) *LanguageTree:is_valid()* LanguageTree:is_valid({self}) *LanguageTree:is_valid()*
@@ -987,7 +991,7 @@ LanguageTree:language_for_range({self}, {range})
• {self} • {self}
Return: ~ Return: ~
LanguageTree Managing {range} |LanguageTree| Managing {range}
*LanguageTree:named_node_for_range()* *LanguageTree:named_node_for_range()*
LanguageTree:named_node_for_range({self}, {range}, {opts}) LanguageTree:named_node_for_range({self}, {range}, {opts})
@@ -1001,7 +1005,7 @@ LanguageTree:named_node_for_range({self}, {range}, {opts})
• {self} • {self}
Return: ~ Return: ~
userdata|nil Found |tsnode| |TSNode||nil Found node
LanguageTree:parse({self}) *LanguageTree:parse()* LanguageTree:parse({self}) *LanguageTree:parse()*
Parses all defined regions using a treesitter parser for the language this Parses all defined regions using a treesitter parser for the language this
@@ -1012,8 +1016,8 @@ LanguageTree:parse({self}) *LanguageTree:parse()*
• {self} • {self}
Return: ~ Return: ~
userdata[] Table of parsed |tstree| TSTree[]
(table) Change list (table|nil) Change list
LanguageTree:register_cbs({self}, {cbs}) *LanguageTree:register_cbs()* LanguageTree:register_cbs({self}, {cbs}) *LanguageTree:register_cbs()*
Registers callbacks for the |LanguageTree|. Registers callbacks for the |LanguageTree|.
@@ -1050,7 +1054,7 @@ LanguageTree:tree_for_range({self}, {range}, {opts})
• {self} • {self}
Return: ~ Return: ~
userdata|nil Contained |tstree| TSTree|nil
LanguageTree:trees({self}) *LanguageTree:trees()* LanguageTree:trees({self}) *LanguageTree:trees()*
Returns all trees this language tree contains. Does not include child Returns all trees this language tree contains. Does not include child
@@ -1065,7 +1069,7 @@ new({source}, {lang}, {opts}) *vim.treesitter.languagetree.new()*
may contain child languages themselves, hence the name). may contain child languages themselves, hence the name).
Parameters: ~ Parameters: ~
• {source} (number|string) Buffer or a string of text to parse • {source} (integer|string) Buffer or a string of text to parse
• {lang} (string) Root language this tree represents • {lang} (string) Root language this tree represents
• {opts} (table|nil) Optional keyword arguments: • {opts} (table|nil) Optional keyword arguments:
• injections table Mapping language to injection query • injections table Mapping language to injection query
@@ -1074,6 +1078,6 @@ new({source}, {lang}, {opts}) *vim.treesitter.languagetree.new()*
per language. per language.
Return: ~ Return: ~
LanguageTree |LanguageTree| parser object |LanguageTree| parser object
vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl:

View File

@@ -3,8 +3,11 @@ local query = require('vim.treesitter.query')
local language = require('vim.treesitter.language') local language = require('vim.treesitter.language')
local LanguageTree = require('vim.treesitter.languagetree') local LanguageTree = require('vim.treesitter.languagetree')
---@type table<integer,LanguageTree>
local parsers = setmetatable({}, { __mode = 'v' }) local parsers = setmetatable({}, { __mode = 'v' })
---@class TreesitterModule
---@field highlighter TSHighlighter
local M = vim.tbl_extend('error', query, language) local M = vim.tbl_extend('error', query, language)
M.language_version = vim._ts_get_language_version() M.language_version = vim._ts_get_language_version()
@@ -12,6 +15,7 @@ M.minimum_language_version = vim._ts_get_minimum_language_version()
setmetatable(M, { setmetatable(M, {
__index = function(t, k) __index = function(t, k)
---@diagnostic disable:no-unknown
if k == 'highlighter' then if k == 'highlighter' then
t[k] = require('vim.treesitter.highlighter') t[k] = require('vim.treesitter.highlighter')
return t[k] return t[k]
@@ -29,21 +33,23 @@ setmetatable(M, {
--- ---
--- It is not recommended to use this; use |get_parser()| instead. --- It is not recommended to use this; use |get_parser()| instead.
--- ---
---@param bufnr string Buffer the parser will be tied to (0 for current buffer) ---@param bufnr integer Buffer the parser will be tied to (0 for current buffer)
---@param lang string Language of the parser ---@param lang string Language of the parser
---@param opts (table|nil) Options to pass to the created language tree ---@param opts (table|nil) Options to pass to the created language tree
--- ---
---@return LanguageTree |LanguageTree| object to use for parsing ---@return LanguageTree object to use for parsing
function M._create_parser(bufnr, lang, opts) function M._create_parser(bufnr, lang, opts)
language.require_language(lang) language.require_language(lang)
if bufnr == 0 then if bufnr == 0 then
bufnr = a.nvim_get_current_buf() bufnr = vim.api.nvim_get_current_buf()
end end
vim.fn.bufload(bufnr) vim.fn.bufload(bufnr)
local self = LanguageTree.new(bufnr, lang, opts) local self = LanguageTree.new(bufnr, lang, opts)
---@diagnostic disable:invisible
---@private ---@private
local function bytes_cb(_, ...) local function bytes_cb(_, ...)
self:_on_bytes(...) self:_on_bytes(...)
@@ -58,12 +64,14 @@ function M._create_parser(bufnr, lang, opts)
end end
---@private ---@private
local function reload_cb(_, ...) local function reload_cb(_)
self:_on_reload(...) self:_on_reload()
end end
local source = self:source() --[[@as integer]]
a.nvim_buf_attach( a.nvim_buf_attach(
self:source(), source,
false, false,
{ on_bytes = bytes_cb, on_detach = detach_cb, on_reload = reload_cb, preview = true } { on_bytes = bytes_cb, on_detach = detach_cb, on_reload = reload_cb, preview = true }
) )
@@ -77,11 +85,11 @@ end
--- ---
--- If needed, this will create the parser. --- If needed, this will create the parser.
--- ---
---@param bufnr (number|nil) Buffer the parser should be tied to (default: current buffer) ---@param bufnr (integer|nil) Buffer the parser should be tied to (default: current buffer)
---@param lang (string|nil) Filetype of this parser (default: buffer filetype) ---@param lang (string|nil) Filetype of this parser (default: buffer filetype)
---@param opts (table|nil) Options to pass to the created language tree ---@param opts (table|nil) Options to pass to the created language tree
--- ---
---@return LanguageTree |LanguageTree| object to use for parsing ---@return LanguageTree object to use for parsing
function M.get_parser(bufnr, lang, opts) function M.get_parser(bufnr, lang, opts)
opts = opts or {} opts = opts or {}
@@ -107,7 +115,7 @@ end
---@param lang string Language of this string ---@param lang string Language of this string
---@param opts (table|nil) Options to pass to the created language tree ---@param opts (table|nil) Options to pass to the created language tree
--- ---
---@return LanguageTree |LanguageTree| object to use for parsing ---@return LanguageTree object to use for parsing
function M.get_string_parser(str, lang, opts) function M.get_string_parser(str, lang, opts)
vim.validate({ vim.validate({
str = { str, 'string' }, str = { str, 'string' },
@@ -120,8 +128,8 @@ end
--- Determines whether a node is the ancestor of another --- Determines whether a node is the ancestor of another
--- ---
---@param dest userdata Possible ancestor |tsnode| ---@param dest TSNode Possible ancestor
---@param source userdata Possible descendant |tsnode| ---@param source TSNode Possible descendant
--- ---
---@return boolean True if {dest} is an ancestor of {source} ---@return boolean True if {dest} is an ancestor of {source}
function M.is_ancestor(dest, source) function M.is_ancestor(dest, source)
@@ -143,9 +151,12 @@ end
--- Returns the node's range or an unpacked range table --- Returns the node's range or an unpacked range table
--- ---
---@param node_or_range (userdata|table) |tsnode| or table of positions ---@param node_or_range (TSNode|table) Node or table of positions
--- ---
---@return table `{ start_row, start_col, end_row, end_col }` ---@return integer start_row
---@return integer start_col
---@return integer end_row
---@return integer end_col
function M.get_node_range(node_or_range) function M.get_node_range(node_or_range)
if type(node_or_range) == 'table' then if type(node_or_range) == 'table' then
return unpack(node_or_range) return unpack(node_or_range)
@@ -156,9 +167,9 @@ end
--- Determines whether (line, col) position is in node range --- Determines whether (line, col) position is in node range
--- ---
---@param node userdata |tsnode| defining the range ---@param node TSNode defining the range
---@param line number Line (0-based) ---@param line integer Line (0-based)
---@param col number Column (0-based) ---@param col integer Column (0-based)
--- ---
---@return boolean True if the position is in node range ---@return boolean True if the position is in node range
function M.is_in_node_range(node, line, col) function M.is_in_node_range(node, line, col)
@@ -180,7 +191,7 @@ end
--- Determines if a node contains a range --- Determines if a node contains a range
--- ---
---@param node userdata |tsnode| ---@param node TSNode
---@param range table ---@param range table
--- ---
---@return boolean True if the {node} contains the {range} ---@return boolean True if the {node} contains the {range}
@@ -197,9 +208,9 @@ end
--- Each capture is represented by a table containing the capture name as a string as --- Each capture is represented by a table containing the capture name as a string as
--- well as a table of metadata (`priority`, `conceal`, ...; empty if none are defined). --- well as a table of metadata (`priority`, `conceal`, ...; empty if none are defined).
--- ---
---@param bufnr number Buffer number (0 for current buffer) ---@param bufnr integer Buffer number (0 for current buffer)
---@param row number Position row ---@param row integer Position row
---@param col number Position column ---@param col integer Position column
--- ---
---@return table[] List of captures `{ capture = "capture name", metadata = { ... } }` ---@return table[] List of captures `{ capture = "capture name", metadata = { ... } }`
function M.get_captures_at_pos(bufnr, row, col) function M.get_captures_at_pos(bufnr, row, col)
@@ -250,7 +261,7 @@ end
--- Returns a list of highlight capture names under the cursor --- Returns a list of highlight capture names under the cursor
--- ---
---@param winnr (number|nil) Window handle or 0 for current window (default) ---@param winnr (integer|nil) Window handle or 0 for current window (default)
--- ---
---@return string[] List of capture names ---@return string[] List of capture names
function M.get_captures_at_cursor(winnr) function M.get_captures_at_cursor(winnr)
@@ -271,14 +282,14 @@ end
--- Returns the smallest named node at the given position --- Returns the smallest named node at the given position
--- ---
---@param bufnr number Buffer number (0 for current buffer) ---@param bufnr integer Buffer number (0 for current buffer)
---@param row number Position row ---@param row integer Position row
---@param col number Position column ---@param col integer Position column
---@param opts table Optional keyword arguments: ---@param opts table Optional keyword arguments:
--- - lang string|nil Parser language --- - lang string|nil Parser language
--- - ignore_injections boolean Ignore injected languages (default true) --- - ignore_injections boolean Ignore injected languages (default true)
--- ---
---@return userdata|nil |tsnode| under the cursor ---@return TSNode|nil under the cursor
function M.get_node_at_pos(bufnr, row, col, opts) function M.get_node_at_pos(bufnr, row, col, opts)
if bufnr == 0 then if bufnr == 0 then
bufnr = a.nvim_get_current_buf() bufnr = a.nvim_get_current_buf()
@@ -295,7 +306,7 @@ end
--- Returns the smallest named node under the cursor --- Returns the smallest named node under the cursor
--- ---
---@param winnr (number|nil) Window handle or 0 for current window (default) ---@param winnr (integer|nil) Window handle or 0 for current window (default)
--- ---
---@return string Name of node under the cursor ---@return string Name of node under the cursor
function M.get_node_at_cursor(winnr) function M.get_node_at_cursor(winnr)
@@ -323,7 +334,7 @@ end
--- }) --- })
--- </pre> --- </pre>
--- ---
---@param bufnr (number|nil) Buffer to be highlighted (default: current buffer) ---@param bufnr (integer|nil) Buffer to be highlighted (default: current buffer)
---@param lang (string|nil) Language of the parser (default: buffer filetype) ---@param lang (string|nil) Language of the parser (default: buffer filetype)
function M.start(bufnr, lang) function M.start(bufnr, lang)
bufnr = bufnr or a.nvim_get_current_buf() bufnr = bufnr or a.nvim_get_current_buf()
@@ -333,7 +344,7 @@ end
--- Stops treesitter highlighting for a buffer --- Stops treesitter highlighting for a buffer
--- ---
---@param bufnr (number|nil) Buffer to stop highlighting (default: current buffer) ---@param bufnr (integer|nil) Buffer to stop highlighting (default: current buffer)
function M.stop(bufnr) function M.stop(bufnr)
bufnr = bufnr or a.nvim_get_current_buf() bufnr = bufnr or a.nvim_get_current_buf()
@@ -351,13 +362,13 @@ end
---@param opts table|nil Optional options table with the following possible keys: ---@param opts table|nil Optional options table with the following possible keys:
--- - lang (string|nil): The language of the source buffer. If omitted, the --- - lang (string|nil): The language of the source buffer. If omitted, the
--- filetype of the source buffer is used. --- filetype of the source buffer is used.
--- - bufnr (number|nil): Buffer to draw the tree into. If omitted, a new --- - bufnr (integer|nil): Buffer to draw the tree into. If omitted, a new
--- buffer is created. --- buffer is created.
--- - winid (number|nil): Window id to display the tree buffer in. If omitted, --- - winid (integer|nil): Window id to display the tree buffer in. If omitted,
--- a new window is created with {command}. --- a new window is created with {command}.
--- - command (string|nil): Vimscript command to create the window. Default --- - command (string|nil): Vimscript command to create the window. Default
--- value is "topleft 60vnew". Only used when {winid} is nil. --- value is "topleft 60vnew". Only used when {winid} is nil.
--- - title (string|fun(bufnr:number):string|nil): Title of the window. If a --- - title (string|fun(bufnr:integer):string|nil): Title of the window. If a
--- function, it accepts the buffer number of the source buffer as its only --- function, it accepts the buffer number of the source buffer as its only
--- argument and should return a string. --- argument and should return a string.
function M.show_tree(opts) function M.show_tree(opts)

View File

@@ -0,0 +1,60 @@
---@meta
---@class TSNode
---@field id fun(self: TSNode): integer
---@field range fun(self: TSNode): 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 child_count fun(self: TSNode): integer
---@field named_child_count fun(self: TSNode): integer
---@field child fun(self: TSNode, integer): TSNode
---@field name_child fun(self: TSNode, integer): TSNode
---@field descendant_for_range fun(self: TSNode, integer, integer, integer, integer): TSNode
---@field named_descendant_for_range fun(self: TSNode, integer, integer, integer, integer): TSNode
---@field parent fun(self: 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_error fun(self: TSNode): boolean
---@field iter_children fun(self: TSNode): fun(): TSNode, string
local TSNode = {}
---@param query userdata
---@param captures true
---@param start integer
---@param end_ integer
---@return fun(): integer, TSNode, any
function TSNode:_rawquery(query, captures, start, end_) end
---@param query userdata
---@param captures false
---@param start integer
---@param end_ integer
---@return fun(): string, any
function TSNode:_rawquery(query, captures, start, end_) end
---@class TSParser
---@field parse fun(self: TSParser, tree, source: integer|string): TSTree, integer[]
---@field included_ranges fun(self: TSParser): integer[]
---@field set_included_ranges fun(self: TSParser, ranges: integer[][])
---@class TSTree
---@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
---@return integer
vim._ts_get_language_version = function() end
---@return integer
vim._ts_get_minimum_language_version = function() end
---@param lang string
---@return TSParser
vim._create_ts_parser = function(lang) end

View File

@@ -1,13 +1,27 @@
local a = vim.api local a = vim.api
local query = require('vim.treesitter.query') local query = require('vim.treesitter.query')
-- support reload for quick experimentation ---@alias TSHlIter fun(): integer, TSNode, TSMetadata
---@class TSHighlightState
---@field next_row integer
---@field iter TSHlIter|nil
---@class TSHighlighter ---@class TSHighlighter
---@field active table<integer,TSHighlighter>
---@field bufnr integer
---@field orig_spelloptions string
---@field _highlight_states table<TSTree,TSHighlightState>
---@field _queries table<string,TSHighlighterQuery>
---@field tree LanguageTree
local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {} local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {}
TSHighlighter.__index = TSHighlighter TSHighlighter.__index = TSHighlighter
TSHighlighter.active = TSHighlighter.active or {} TSHighlighter.active = TSHighlighter.active or {}
---@class TSHighlighterQuery
---@field _query Query|nil
---@field hl_cache table<integer,integer>
local TSHighlighterQuery = {} local TSHighlighterQuery = {}
TSHighlighterQuery.__index = TSHighlighterQuery TSHighlighterQuery.__index = TSHighlighterQuery
@@ -46,7 +60,7 @@ end
--- Creates a new highlighter using @param tree --- Creates a new highlighter using @param tree
--- ---
---@param tree LanguageTree |LanguageTree| parser object to use for highlighting ---@param tree LanguageTree parser object to use for highlighting
---@param opts (table|nil) Configuration of the highlighter: ---@param opts (table|nil) Configuration of the highlighter:
--- - queries table overwrite queries used by the highlighter --- - queries table overwrite queries used by the highlighter
---@return TSHighlighter Created highlighter object ---@return TSHighlighter Created highlighter object
@@ -57,9 +71,10 @@ function TSHighlighter.new(tree, opts)
error('TSHighlighter can not be used with a string parser source.') error('TSHighlighter can not be used with a string parser source.')
end end
opts = opts or {} opts = opts or {} ---@type { queries: table<string,string> }
self.tree = tree self.tree = tree
tree:register_cbs({ tree:register_cbs({
---@diagnostic disable:invisible
on_changedtree = function(...) on_changedtree = function(...)
self:on_changedtree(...) self:on_changedtree(...)
end, end,
@@ -67,17 +82,20 @@ function TSHighlighter.new(tree, opts)
self:on_bytes(...) self:on_bytes(...)
end, end,
on_detach = function(...) on_detach = function(...)
---@diagnostic disable-next-line:redundant-parameter
self:on_detach(...) self:on_detach(...)
end, end,
}) })
self.bufnr = tree:source() self.bufnr = tree:source() --[[@as integer]]
self.edit_count = 0 self.edit_count = 0
self.redraw_count = 0 self.redraw_count = 0
self.line_count = {} self.line_count = {}
-- A map of highlight states. -- A map of highlight states.
-- This state is kept during rendering across each line update. -- This state is kept during rendering across each line update.
self._highlight_states = {} self._highlight_states = {}
---@type table<string,TSHighlighterQuery>
self._queries = {} self._queries = {}
-- Queries for a specific language can be overridden by a custom -- Queries for a specific language can be overridden by a custom
@@ -128,6 +146,8 @@ function TSHighlighter:destroy()
end end
---@private ---@private
---@param tstree TSTree
---@return TSHighlightState
function TSHighlighter:get_highlight_state(tstree) function TSHighlighter:get_highlight_state(tstree)
if not self._highlight_states[tstree] then if not self._highlight_states[tstree] then
self._highlight_states[tstree] = { self._highlight_states[tstree] = {
@@ -145,6 +165,8 @@ function TSHighlighter:reset_highlight_state()
end end
---@private ---@private
---@param start_row integer
---@param new_end integer
function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end) function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end)
a.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1) a.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1)
end end
@@ -155,6 +177,7 @@ function TSHighlighter:on_detach()
end end
---@private ---@private
---@param changes integer[][]?
function TSHighlighter:on_changedtree(changes) function TSHighlighter:on_changedtree(changes)
for _, ch in ipairs(changes or {}) do for _, ch in ipairs(changes or {}) do
a.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3] + 1) a.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3] + 1)
@@ -165,7 +188,7 @@ end
-- --
---@private ---@private
---@param lang string Language used by the highlighter. ---@param lang string Language used by the highlighter.
---@return Query ---@return TSHighlighterQuery
function TSHighlighter:get_query(lang) function TSHighlighter:get_query(lang)
if not self._queries[lang] then if not self._queries[lang] then
self._queries[lang] = TSHighlighterQuery.new(lang) self._queries[lang] = TSHighlighterQuery.new(lang)
@@ -175,7 +198,12 @@ function TSHighlighter:get_query(lang)
end end
---@private ---@private
---@param self TSHighlighter
---@param buf integer
---@param line integer
---@param is_spell_nav boolean
local function on_line_impl(self, buf, line, is_spell_nav) local function on_line_impl(self, buf, line, is_spell_nav)
---@diagnostic disable:invisible
self.tree:for_each_tree(function(tstree, tree) self.tree:for_each_tree(function(tstree, tree)
if not tstree then if not tstree then
return return
@@ -213,7 +241,7 @@ local function on_line_impl(self, buf, line, is_spell_nav)
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]
local spell = nil local spell = nil ---@type boolean?
if capture_name == 'spell' then if capture_name == 'spell' then
spell = true spell = true
elseif capture_name == 'nospell' then elseif capture_name == 'nospell' then
@@ -242,6 +270,9 @@ local function on_line_impl(self, buf, line, is_spell_nav)
end end
---@private ---@private
---@param _win integer
---@param buf integer
---@param line integer
function TSHighlighter._on_line(_, _win, buf, line, _) function TSHighlighter._on_line(_, _win, buf, line, _)
local self = TSHighlighter.active[buf] local self = TSHighlighter.active[buf]
if not self then if not self then
@@ -252,6 +283,9 @@ function TSHighlighter._on_line(_, _win, buf, line, _)
end end
---@private ---@private
---@param buf integer
---@param srow integer
---@param erow integer
function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _) function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _)
local self = TSHighlighter.active[buf] local self = TSHighlighter.active[buf]
if not self then if not self then
@@ -266,6 +300,7 @@ function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _)
end end
---@private ---@private
---@param buf integer
function TSHighlighter._on_buf(_, buf) function TSHighlighter._on_buf(_, buf)
local self = TSHighlighter.active[buf] local self = TSHighlighter.active[buf]
if self then if self then
@@ -274,6 +309,9 @@ function TSHighlighter._on_buf(_, buf)
end end
---@private ---@private
---@param _win integer
---@param buf integer
---@param _topline integer
function TSHighlighter._on_win(_, _win, buf, _topline) function TSHighlighter._on_win(_, _win, buf, _topline)
local self = TSHighlighter.active[buf] local self = TSHighlighter.active[buf]
if not self then if not self then

View File

@@ -2,20 +2,39 @@ local a = vim.api
local query = require('vim.treesitter.query') local query = require('vim.treesitter.query')
local language = require('vim.treesitter.language') local language = require('vim.treesitter.language')
---@class LanguageTree ---@alias Range {[1]: integer, [2]: integer, [3]: integer, [4]: integer}
---@field _callbacks function[] Callback handlers --
---@field _children LanguageTree[] Injected languages ---@alias TSCallbackName
---@field _injection_query table Queries defining injected languages ---| 'changedtree'
---@field _opts table Options ---| 'bytes'
---@field _parser userdata Parser for language ---| 'detach'
---@field _regions table List of regions this tree should manage and parse ---| 'child_added'
---@field _lang string Language name ---| 'child_removed'
---@field _regions table
---@field _source (number|string) Buffer or string to parse
---@field _trees userdata[] Reference to parsed |tstree| (one for each language)
---@field _valid boolean If the parsed tree is valid
---@alias TSCallbackNameOn
---| 'on_changedtree'
---| 'on_bytes'
---| 'on_detach'
---| 'on_child_added'
---| 'on_child_removed'
---@class LanguageTree
---@field private _callbacks table<TSCallbackName,function[]> Callback handlers
---@field private _children table<string,LanguageTree> Injected languages
---@field private _injection_query Query Queries defining injected languages
---@field private _opts table Options
---@field private _parser TSParser Parser for language
---@field private _regions Range[][] List of regions this tree should manage and parse
---@field private _lang string Language name
---@field private _source (integer|string) Buffer or string to parse
---@field private _trees TSTree[] Reference to parsed tree (one for each language)
---@field private _valid boolean If the parsed tree is valid
local LanguageTree = {} local LanguageTree = {}
---@class LanguageTreeOpts
---@field queries table<string,string> -- Deprecated
---@field injections table<string,string>
LanguageTree.__index = LanguageTree LanguageTree.__index = LanguageTree
--- A |LanguageTree| holds the treesitter parser for a given language {lang} used --- A |LanguageTree| holds the treesitter parser for a given language {lang} used
@@ -23,16 +42,17 @@ LanguageTree.__index = LanguageTree
--- needs to store parsers for these child languages as well (which in turn may contain --- needs to store parsers for these child languages as well (which in turn may contain
--- child languages themselves, hence the name). --- child languages themselves, hence the name).
--- ---
---@param source (number|string) Buffer or a string of text to parse ---@param source (integer|string) Buffer or a string of text to parse
---@param lang string Root language this tree represents ---@param lang string Root language this tree represents
---@param opts (table|nil) Optional keyword arguments: ---@param opts (table|nil) Optional keyword arguments:
--- - injections table Mapping language to injection query strings. --- - injections table Mapping language to injection query strings.
--- This is useful for overriding the built-in --- This is useful for overriding the built-in
--- runtime file searching for the injection language --- runtime file searching for the injection language
--- query per language. --- query per language.
---@return LanguageTree |LanguageTree| parser object ---@return LanguageTree parser object
function LanguageTree.new(source, lang, opts) function LanguageTree.new(source, lang, opts)
language.require_language(lang) language.require_language(lang)
---@type LanguageTreeOpts
opts = opts or {} opts = opts or {}
if opts.queries then if opts.queries then
@@ -65,6 +85,7 @@ function LanguageTree.new(source, lang, opts)
end end
--- Invalidates this parser and all its children --- Invalidates this parser and all its children
---@param reload boolean|nil
function LanguageTree:invalidate(reload) function LanguageTree:invalidate(reload)
self._valid = false self._valid = false
@@ -73,7 +94,7 @@ function LanguageTree:invalidate(reload)
self._trees = {} self._trees = {}
end end
for _, child in ipairs(self._children) do for _, child in pairs(self._children) do
child:invalidate(reload) child:invalidate(reload)
end end
end end
@@ -111,8 +132,8 @@ end
--- This will run the injection query for this language to --- This will run the injection query for this language to
--- determine if any child languages should be created. --- determine if any child languages should be created.
--- ---
---@return userdata[] Table of parsed |tstree| ---@return TSTree[]
---@return table Change list ---@return table|nil Change list
function LanguageTree:parse() function LanguageTree:parse()
if self._valid then if self._valid then
return self._trees return self._trees
@@ -146,7 +167,7 @@ function LanguageTree:parse()
end end
local injections_by_lang = self:_get_injections() local injections_by_lang = self:_get_injections()
local seen_langs = {} local seen_langs = {} ---@type table<string,boolean>
for lang, injection_ranges in pairs(injections_by_lang) do for lang, injection_ranges in pairs(injections_by_lang) do
local has_lang = language.require_language(lang, nil, true) local has_lang = language.require_language(lang, nil, true)
@@ -188,8 +209,8 @@ end
--- Invokes the callback for each |LanguageTree| and its children recursively --- Invokes the callback for each |LanguageTree| and its children recursively
--- ---
---@param fn function(tree: LanguageTree, lang: string) ---@param fn fun(tree: LanguageTree, lang: string)
---@param include_self boolean Whether to include the invoking tree in the results ---@param include_self boolean|nil Whether to include the invoking tree in the results
function LanguageTree:for_each_child(fn, include_self) function LanguageTree:for_each_child(fn, include_self)
if include_self then if include_self then
fn(self, self._lang) fn(self, self._lang)
@@ -204,7 +225,7 @@ end
--- ---
--- Note: This includes the invoking tree's child trees as well. --- Note: This includes the invoking tree's child trees as well.
--- ---
---@param fn function(tree: TSTree, languageTree: 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 ipairs(self._trees) do
fn(tree, self) fn(tree, self)
@@ -221,7 +242,7 @@ end
--- ---
---@private ---@private
---@param lang string Language to add. ---@param lang string Language to add.
---@return LanguageTree Injected |LanguageTree| ---@return LanguageTree injected
function LanguageTree:add_child(lang) function LanguageTree:add_child(lang)
if self._children[lang] then if self._children[lang] then
self:remove_child(lang) self:remove_child(lang)
@@ -258,7 +279,7 @@ end
--- `remove_child` must be called on the parent to remove it. --- `remove_child` must be called on the parent to remove it.
function LanguageTree:destroy() function LanguageTree:destroy()
-- Cleanup here -- Cleanup here
for _, child in ipairs(self._children) do for _, child in pairs(self._children) do
child:destroy() child:destroy()
end end
end end
@@ -280,20 +301,22 @@ end
--- Note: This call invalidates the tree and requires it to be parsed again. --- Note: This call invalidates the tree and requires it to be parsed again.
--- ---
---@private ---@private
---@param regions table List of regions this tree should manage and parse. ---@param regions integer[][][] List of regions this tree should manage and parse.
function LanguageTree:set_included_regions(regions) function LanguageTree:set_included_regions(regions)
-- Transform the tables from 4 element long to 6 element long (with byte offset) -- Transform the tables from 4 element long to 6 element long (with byte offset)
for _, region in ipairs(regions) do for _, region in ipairs(regions) do
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
---@diagnostic disable-next-line:no-unknown
local start_row, start_col, end_row, end_col = unpack(range) local start_row, start_col, end_row, end_col = unpack(range)
local start_byte = 0 local start_byte = 0
local end_byte = 0 local end_byte = 0
local source = self._source
-- TODO(vigoux): proper byte computation here, and account for EOL ? -- TODO(vigoux): proper byte computation here, and account for EOL ?
if type(self._source) == 'number' then if type(source) == 'number' then
-- Easy case, this is a buffer parser -- Easy case, this is a buffer parser
start_byte = a.nvim_buf_get_offset(self._source, start_row) + start_col start_byte = a.nvim_buf_get_offset(source, start_row) + start_col
end_byte = a.nvim_buf_get_offset(self._source, end_row) + end_col end_byte = a.nvim_buf_get_offset(source, end_row) + end_col
elseif type(self._source) == 'string' then elseif type(self._source) == 'string' then
-- string parser, single `\n` delimited string -- string parser, single `\n` delimited string
start_byte = vim.fn.byteidx(self._source, start_col) start_byte = vim.fn.byteidx(self._source, start_col)
@@ -320,9 +343,13 @@ function LanguageTree:included_regions()
end end
---@private ---@private
---@param node TSNode
---@param id integer
---@param metadata TSMetadata
---@return Range
local function get_range_from_metadata(node, id, metadata) local function get_range_from_metadata(node, id, metadata)
if metadata[id] and metadata[id].range then if metadata[id] and metadata[id].range then
return metadata[id].range return metadata[id].range --[[@as Range]]
end end
return { node:range() } return { node:range() }
end end
@@ -334,11 +361,13 @@ 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, integer[][]>
function LanguageTree:_get_injections() function LanguageTree:_get_injections()
if not self._injection_query then if not self._injection_query then
return {} return {}
end end
---@type table<integer,table<string,table<integer,table>>>
local injections = {} local injections = {}
for tree_index, tree in ipairs(self._trees) do for tree_index, tree in ipairs(self._trees) do
@@ -348,14 +377,14 @@ function LanguageTree:_get_injections()
for pattern, match, metadata in for pattern, match, metadata in
self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1) self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1)
do do
local lang = nil local lang = nil ---@type string
local ranges = {} local ranges = {} ---@type Range[]
local combined = metadata.combined local combined = metadata.combined ---@type boolean
-- Directives can configure how injections are captured as well as actual node captures. -- Directives can configure how injections are captured as well as actual node captures.
-- This allows more advanced processing for determining ranges and language resolution. -- This allows more advanced processing for determining ranges and language resolution.
if metadata.content then if metadata.content then
local content = metadata.content local content = metadata.content ---@type any
-- Allow for captured nodes to be used -- Allow for captured nodes to be used
if type(content) == 'number' then if type(content) == 'number' then
@@ -368,7 +397,7 @@ function LanguageTree:_get_injections()
end end
if metadata.language then if metadata.language then
lang = metadata.language lang = metadata.language ---@type string
end end
-- You can specify the content and language together -- You can specify the content and language together
@@ -379,7 +408,7 @@ function LanguageTree:_get_injections()
-- Lang should override any other language tag -- Lang should override any other language tag
if name == 'language' and not lang then if name == 'language' and not lang then
lang = query.get_node_text(node, self._source) lang = query.get_node_text(node, self._source) --[[@as string]]
elseif name == 'combined' then elseif name == 'combined' then
combined = true combined = true
elseif name == 'content' and #ranges == 0 then elseif name == 'content' and #ranges == 0 then
@@ -417,6 +446,7 @@ function LanguageTree:_get_injections()
end end
end end
---@type table<string,Range[][]>
local result = {} local result = {}
-- Generate a map by lang of node lists. -- Generate a map by lang of node lists.
@@ -429,11 +459,13 @@ function LanguageTree:_get_injections()
for _, entry in pairs(patterns) do for _, entry in pairs(patterns) do
if entry.combined then if entry.combined then
---@diagnostic disable-next-line:no-unknown
local regions = vim.tbl_map(function(e) local regions = vim.tbl_map(function(e)
return vim.tbl_flatten(e) return vim.tbl_flatten(e)
end, entry.regions) end, entry.regions)
table.insert(result[lang], regions) table.insert(result[lang], regions)
else else
---@diagnostic disable-next-line:no-unknown
for _, ranges in ipairs(entry.regions) do for _, ranges in ipairs(entry.regions) do
table.insert(result[lang], ranges) table.insert(result[lang], ranges)
end end
@@ -446,6 +478,7 @@ function LanguageTree:_get_injections()
end end
---@private ---@private
---@param cb_name TSCallbackName
function LanguageTree:_do_callback(cb_name, ...) function LanguageTree:_do_callback(cb_name, ...)
for _, cb in ipairs(self._callbacks[cb_name]) do for _, cb in ipairs(self._callbacks[cb_name]) do
cb(...) cb(...)
@@ -453,6 +486,17 @@ function LanguageTree:_do_callback(cb_name, ...)
end end
---@private ---@private
---@param bufnr integer
---@param changed_tick integer
---@param start_row integer
---@param start_col integer
---@param start_byte integer
---@param old_row integer
---@param old_col integer
---@param old_byte integer
---@param new_row integer
---@param new_col integer
---@param new_byte integer
function LanguageTree:_on_bytes( function LanguageTree:_on_bytes(
bufnr, bufnr,
changed_tick, changed_tick,
@@ -523,6 +567,7 @@ end
--- - `on_child_added` : emitted when a child is added to the 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_child_removed` : emitted when a child is removed from the tree.
function LanguageTree:register_cbs(cbs) function LanguageTree:register_cbs(cbs)
---@cast cbs table<TSCallbackNameOn,function>
if not cbs then if not cbs then
return return
end end
@@ -549,6 +594,9 @@ function LanguageTree:register_cbs(cbs)
end end
---@private ---@private
---@param tree TSTree
---@param range Range
---@return boolean
local function tree_contains(tree, range) local function tree_contains(tree, range)
local start_row, start_col, end_row, end_col = tree:root():range() local start_row, start_col, end_row, end_col = tree:root():range()
local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2]) local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2])
@@ -559,7 +607,7 @@ end
--- Determines whether {range} is contained in the |LanguageTree|. --- Determines whether {range} is contained in the |LanguageTree|.
--- ---
---@param range table `{ start_line, start_col, end_line, end_col }` ---@param range Range `{ start_line, start_col, end_line, end_col }`
---@return boolean ---@return boolean
function LanguageTree:contains(range) function LanguageTree:contains(range)
for _, tree in pairs(self._trees) do for _, tree in pairs(self._trees) do
@@ -573,10 +621,10 @@ end
--- Gets the tree that contains {range}. --- Gets the tree that contains {range}.
--- ---
---@param range table `{ start_line, start_col, end_line, end_col }` ---@param range Range `{ start_line, start_col, end_line, end_col }`
---@param opts table|nil Optional keyword arguments: ---@param opts table|nil Optional keyword arguments:
--- - ignore_injections boolean Ignore injected languages (default true) --- - ignore_injections boolean Ignore injected languages (default true)
---@return userdata|nil Contained |tstree| ---@return TSTree|nil
function LanguageTree:tree_for_range(range, opts) function LanguageTree:tree_for_range(range, opts)
opts = opts or {} opts = opts or {}
local ignore = vim.F.if_nil(opts.ignore_injections, true) local ignore = vim.F.if_nil(opts.ignore_injections, true)
@@ -602,10 +650,10 @@ end
--- Gets the smallest named node that contains {range}. --- Gets the smallest named node that contains {range}.
--- ---
---@param range table `{ start_line, start_col, end_line, end_col }` ---@param range Range `{ start_line, start_col, end_line, end_col }`
---@param opts table|nil Optional keyword arguments: ---@param opts table|nil Optional keyword arguments:
--- - ignore_injections boolean Ignore injected languages (default true) --- - ignore_injections boolean Ignore injected languages (default true)
---@return userdata|nil Found |tsnode| ---@return TSNode|nil Found node
function LanguageTree:named_node_for_range(range, opts) function LanguageTree:named_node_for_range(range, opts)
local tree = self:tree_for_range(range, opts) local tree = self:tree_for_range(range, opts)
if tree then if tree then
@@ -615,7 +663,7 @@ end
--- Gets the appropriate language that contains {range}. --- Gets the appropriate language that contains {range}.
--- ---
---@param range table `{ start_line, start_col, end_line, end_col }` ---@param range Range `{ start_line, start_col, end_line, end_col }`
---@return LanguageTree Managing {range} ---@return LanguageTree Managing {range}
function LanguageTree:language_for_range(range) function LanguageTree:language_for_range(range)
for _, child in pairs(self._children) do for _, child in pairs(self._children) do

View File

@@ -1,12 +1,13 @@
local api = vim.api local api = vim.api
local M = {} ---@class TSPlayground
---@class Playground
---@field ns number API namespace ---@field ns number API namespace
---@field opts table Options table with the following keys: ---@field opts table Options table with the following keys:
--- - anon (boolean): If true, display anonymous nodes --- - anon (boolean): If true, display anonymous nodes
--- - lang (boolean): If true, display the language alongside each node --- - lang (boolean): If true, display the language alongside each node
---@field nodes Node[]
---@field named Node[]
local TSPlayground = {}
--- ---
---@class Node ---@class Node
---@field id number Node id ---@field id number Node id
@@ -18,6 +19,7 @@ local M = {}
---@field end_lnum number Final line number of this node in the source buffer ---@field end_lnum number Final line number of this node in the source buffer
---@field end_col number Final column number of this node in the source buffer ---@field end_col number Final column number of this node in the source buffer
---@field lang string Source language of this node ---@field lang string Source language of this node
---@field root TSNode
--- Traverse all child nodes starting at {node}. --- Traverse all child nodes starting at {node}.
--- ---
@@ -31,10 +33,10 @@ local M = {}
--- node of each of these trees is contained within a node in the primary tree. The {injections} --- node of each of these trees is contained within a node in the primary tree. The {injections}
--- table maps nodes in the primary tree to root nodes of injected trees. --- table maps nodes in the primary tree to root nodes of injected trees.
--- ---
---@param node userdata Starting node to begin traversal |tsnode| ---@param node TSNode Starting node to begin traversal |tsnode|
---@param depth number Current recursion depth ---@param depth number Current recursion depth
---@param lang string Language of the tree currently being traversed ---@param lang string Language of the tree currently being traversed
---@param injections table Mapping of node ids to root nodes of injected language trees (see ---@param injections table<integer,Node> Mapping of node ids to root nodes of injected language trees (see
--- explanation above) --- explanation above)
---@param tree Node[] Output table containing a list of tables each representing a node in the tree ---@param tree Node[] Output table containing a list of tables each representing a node in the tree
---@private ---@private
@@ -48,7 +50,7 @@ local function traverse(node, depth, lang, injections, tree)
local type = child:type() local type = child:type()
local lnum, col, end_lnum, end_col = child:range() local lnum, col, end_lnum, end_col = child:range()
local named = child:named() local named = child:named()
local text local text ---@type string
if named then if named then
if field then if field then
text = string.format('%s: (%s)', field, type) text = string.format('%s: (%s)', field, type)
@@ -79,14 +81,14 @@ end
--- Create a new Playground object. --- Create a new Playground object.
--- ---
---@param bufnr number Source buffer number ---@param bufnr integer Source buffer number
---@param lang string|nil Language of source buffer ---@param lang string|nil Language of source buffer
--- ---
---@return Playground|nil ---@return TSPlayground|nil
---@return string|nil Error message, if any ---@return string|nil Error message, if any
--- ---
---@private ---@private
function M.new(self, bufnr, lang) function TSPlayground:new(bufnr, lang)
local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang) local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang)
if not ok then if not ok then
return nil, 'No parser available for the given buffer' return nil, 'No parser available for the given buffer'
@@ -96,7 +98,7 @@ function M.new(self, bufnr, lang)
-- 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()[1]:root()
local injections = {} 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)
local r = tree:root() local r = tree:root()
@@ -112,7 +114,7 @@ function M.new(self, bufnr, lang)
local nodes = traverse(root, 0, parser:lang(), injections, {}) local nodes = traverse(root, 0, parser:lang(), injections, {})
local named = {} local named = {} ---@type Node[]
for _, v in ipairs(nodes) do for _, v in ipairs(nodes) do
if v.named then if v.named then
named[#named + 1] = v named[#named + 1] = v
@@ -138,9 +140,9 @@ end
--- ---
---@param bufnr number Buffer number to write into. ---@param bufnr number Buffer number to write into.
---@private ---@private
function M.draw(self, bufnr) function TSPlayground:draw(bufnr)
vim.bo[bufnr].modifiable = true vim.bo[bufnr].modifiable = true
local lines = {} local lines = {} ---@type string[]
for _, item in self:iter() do for _, item in self:iter() do
lines[#lines + 1] = table.concat({ lines[#lines + 1] = table.concat({
string.rep(' ', item.depth), string.rep(' ', item.depth),
@@ -168,19 +170,19 @@ end
---@param i number Node number to get ---@param i number Node number to get
---@return Node ---@return Node
---@private ---@private
function M.get(self, i) function TSPlayground:get(i)
local t = self.opts.anon and self.nodes or self.named local t = self.opts.anon and self.nodes or self.named
return t[i] return t[i]
end end
--- Iterate over all of the nodes in this Playground object. --- Iterate over all of the nodes in this Playground object.
--- ---
---@return function Iterator over all nodes in this Playground ---@return (fun(): integer, Node) Iterator over all nodes in this Playground
---@return table ---@return table
---@return number ---@return number
---@private ---@private
function M.iter(self) function TSPlayground:iter()
return ipairs(self.opts.anon and self.nodes or self.named) return ipairs(self.opts.anon and self.nodes or self.named)
end end
return M return TSPlayground

View File

@@ -1,21 +1,25 @@
local a = vim.api local a = vim.api
local language = require('vim.treesitter.language') local language = require('vim.treesitter.language')
-- query: pattern matching on trees
-- predicate matching is implemented in lua
--
---@class Query ---@class Query
---@field captures string[] List of captures used in query ---@field captures string[] List of captures used in query
---@field info table Contains used queries, predicates, directives ---@field info TSQueryInfo Contains used queries, predicates, directives
---@field query userdata Parsed query ---@field query userdata Parsed query
local Query = {} local Query = {}
Query.__index = Query Query.__index = Query
---@class TSQueryInfo
---@field captures table
---@field patterns table<string,any[][]>
local M = {} local M = {}
---@private ---@private
---@param files string[]
---@return string[]
local function dedupe_files(files) local function dedupe_files(files)
local result = {} local result = {}
---@type table<string,boolean>
local seen = {} local seen = {}
for _, path in ipairs(files) do for _, path in ipairs(files) do
@@ -65,10 +69,10 @@ function M.get_query_files(lang, query_name, is_included)
return {} return {}
end end
local base_query = nil local base_query = nil ---@type string?
local extensions = {} local extensions = {}
local base_langs = {} local base_langs = {} ---@type string[]
-- Now get the base languages by looking at the first line of every file -- Now get the base languages by looking at the first line of every file
-- The syntax is the following : -- The syntax is the following :
@@ -87,6 +91,7 @@ function M.get_query_files(lang, query_name, is_included)
local extension = false local extension = false
for modeline in for modeline in
---@return string
function() function()
return file:read('*l') return file:read('*l')
end end
@@ -97,6 +102,7 @@ function M.get_query_files(lang, query_name, is_included)
local langlist = modeline:match(MODELINE_FORMAT) local langlist = modeline:match(MODELINE_FORMAT)
if langlist then if langlist then
---@diagnostic disable-next-line:param-type-mismatch
for _, incllang in ipairs(vim.split(langlist, ',', true)) do for _, incllang in ipairs(vim.split(langlist, ',', true)) do
local is_optional = incllang:match('%(.*%)') local is_optional = incllang:match('%(.*%)')
@@ -137,6 +143,8 @@ function M.get_query_files(lang, query_name, is_included)
end end
---@private ---@private
---@param filenames string[]
---@return string
local function read_query_files(filenames) local function read_query_files(filenames)
local contents = {} local contents = {}
@@ -147,7 +155,8 @@ local function read_query_files(filenames)
return table.concat(contents, '') return table.concat(contents, '')
end end
--- The explicitly set queries from |vim.treesitter.query.set_query()| -- The explicitly set queries from |vim.treesitter.query.set_query()|
---@type table<string,table<string,Query>>
local explicit_queries = setmetatable({}, { local explicit_queries = setmetatable({}, {
__index = function(t, k) __index = function(t, k)
local lang_queries = {} local lang_queries = {}
@@ -174,7 +183,7 @@ end
---@param lang string Language to use for the query ---@param lang string Language to use for the query
---@param query_name string Name of the query (e.g. "highlights") ---@param query_name string Name of the query (e.g. "highlights")
--- ---
---@return Query Parsed query ---@return Query|nil Parsed query
function M.get_query(lang, query_name) function M.get_query(lang, query_name)
if explicit_queries[lang][query_name] then if explicit_queries[lang][query_name] then
return explicit_queries[lang][query_name] return explicit_queries[lang][query_name]
@@ -188,6 +197,7 @@ function M.get_query(lang, query_name)
end end
end end
---@type {[string]: {[string]: Query}}
local query_cache = vim.defaulttable(function() local query_cache = vim.defaulttable(function()
return setmetatable({}, { __mode = 'v' }) return setmetatable({}, { __mode = 'v' })
end) end)
@@ -226,11 +236,11 @@ end
--- Gets the text corresponding to a given node --- Gets the text corresponding to a given node
--- ---
---@param node userdata |tsnode| ---@param node TSNode
---@param source (number|string) Buffer or string from which the {node} is extracted ---@param source (number|string) Buffer or string from which the {node} is extracted
---@param opts (table|nil) Optional parameters. ---@param opts (table|nil) Optional parameters.
--- - concat: (boolean) Concatenate result in a string (default true) --- - concat: (boolean) Concatenate result in a string (default true)
---@return (string[]|string) ---@return (string[]|string|nil)
function M.get_node_text(node, source, opts) function M.get_node_text(node, source, opts)
opts = opts or {} opts = opts or {}
local concat = vim.F.if_nil(opts.concat, true) local concat = vim.F.if_nil(opts.concat, true)
@@ -239,12 +249,12 @@ function M.get_node_text(node, source, opts)
local end_row, end_col, end_byte = node:end_() local end_row, end_col, end_byte = node:end_()
if type(source) == 'number' then if type(source) == 'number' then
local lines
local eof_row = a.nvim_buf_line_count(source) local eof_row = a.nvim_buf_line_count(source)
if start_row >= eof_row then if start_row >= eof_row then
return nil return nil
end end
local lines ---@type string[]
if end_col == 0 then if end_col == 0 then
lines = a.nvim_buf_get_lines(source, start_row, end_row, true) lines = a.nvim_buf_get_lines(source, start_row, end_row, true)
end_col = -1 end_col = -1
@@ -267,8 +277,13 @@ function M.get_node_text(node, source, opts)
end end
end end
---@alias TSMatch table<integer,TSNode>
---@alias TSPredicate fun(match: TSMatch, _, _, predicate: any[]): boolean
-- Predicate handler receive the following arguments -- Predicate handler receive the following arguments
-- (match, pattern, bufnr, predicate) -- (match, pattern, bufnr, predicate)
---@type table<string,TSPredicate>
local predicate_handlers = { local predicate_handlers = {
['eq?'] = function(match, _, source, predicate) ['eq?'] = function(match, _, source, predicate)
local node = match[predicate[2]] local node = match[predicate[2]]
@@ -277,13 +292,13 @@ local predicate_handlers = {
end end
local node_text = M.get_node_text(node, source) local node_text = M.get_node_text(node, source)
local str local str ---@type string
if type(predicate[3]) == 'string' then if type(predicate[3]) == 'string' then
-- (#eq? @aa "foo") -- (#eq? @aa "foo")
str = predicate[3] str = predicate[3]
else else
-- (#eq? @aa @bb) -- (#eq? @aa @bb)
str = M.get_node_text(match[predicate[3]], source) str = M.get_node_text(match[predicate[3]], source) --[[@as string]]
end end
if node_text ~= str or str == nil then if node_text ~= str or str == nil then
@@ -299,7 +314,7 @@ local predicate_handlers = {
return true return true
end end
local regex = predicate[3] local regex = predicate[3]
return string.find(M.get_node_text(node, source), regex) return string.find(M.get_node_text(node, source) --[[@as string]], regex) ~= nil
end, end,
['match?'] = (function() ['match?'] = (function()
@@ -321,10 +336,12 @@ local predicate_handlers = {
}) })
return function(match, _, source, pred) return function(match, _, source, pred)
---@cast match TSMatch
local node = match[pred[2]] local node = match[pred[2]]
if not node then if not node then
return true return true
end end
---@diagnostic disable-next-line no-unknown
local regex = compiled_vim_regexes[pred[3]] local regex = compiled_vim_regexes[pred[3]]
return regex:match_str(M.get_node_text(node, source)) return regex:match_str(M.get_node_text(node, source))
end end
@@ -335,7 +352,7 @@ local predicate_handlers = {
if not node then if not node then
return true return true
end end
local node_text = M.get_node_text(node, source) local node_text = M.get_node_text(node, source) --[[@as string]]
for i = 3, #predicate do for i = 3, #predicate do
if string.find(node_text, predicate[i], 1, true) then if string.find(node_text, predicate[i], 1, true) then
@@ -359,6 +376,7 @@ local predicate_handlers = {
if not string_set then if not string_set then
string_set = {} string_set = {}
for i = 3, #predicate do for i = 3, #predicate do
---@diagnostic disable-next-line:no-unknown
string_set[predicate[i]] = true string_set[predicate[i]] = true
end end
predicate['string_set'] = string_set predicate['string_set'] = string_set
@@ -371,21 +389,39 @@ local predicate_handlers = {
-- As we provide lua-match? also expose vim-match? -- As we provide lua-match? also expose vim-match?
predicate_handlers['vim-match?'] = predicate_handlers['match?'] predicate_handlers['vim-match?'] = predicate_handlers['match?']
---@class TSMetadata
---@field [integer] TSMetadata
---@field [string] integer|string
---@field range Range
---@alias TSDirective fun(match: TSMatch, _, _, predicate: any[], metadata: TSMetadata)
-- Predicate handler receive the following arguments
-- (match, pattern, bufnr, predicate)
-- Directives store metadata or perform side effects against a match. -- Directives store metadata or perform side effects against a match.
-- Directives should always end with a `!`. -- Directives should always end with a `!`.
-- Directive handler receive the following arguments -- Directive handler receive the following arguments
-- (match, pattern, bufnr, predicate, metadata) -- (match, pattern, bufnr, predicate, metadata)
---@type table<string,TSDirective>
local directive_handlers = { local directive_handlers = {
['set!'] = function(_, _, _, pred, metadata) ['set!'] = function(_, _, _, pred, metadata)
if #pred == 4 then if #pred == 4 then
-- (#set! @capture "key" "value") -- (#set! @capture "key" "value")
---@diagnostic disable-next-line:no-unknown
local _, capture_id, key, value = unpack(pred) local _, capture_id, key, value = unpack(pred)
---@cast value integer|string
---@cast capture_id integer
---@cast key string
if not metadata[capture_id] then if not metadata[capture_id] then
metadata[capture_id] = {} metadata[capture_id] = {}
end end
metadata[capture_id][key] = value metadata[capture_id][key] = value
else else
---@diagnostic disable-next-line:no-unknown
local _, key, value = unpack(pred) local _, key, value = unpack(pred)
---@cast value integer|string
---@cast key string
-- (#set! "key" "value") -- (#set! "key" "value")
metadata[key] = value metadata[key] = value
end end
@@ -393,9 +429,11 @@ local directive_handlers = {
-- Shifts the range of a node. -- Shifts the range of a node.
-- Example: (#offset! @_node 0 1 0 -1) -- Example: (#offset! @_node 0 1 0 -1)
['offset!'] = function(match, _, _, pred, metadata) ['offset!'] = function(match, _, _, pred, metadata)
---@cast pred integer[]
local capture_id = pred[2] local capture_id = pred[2]
local offset_node = match[capture_id] local offset_node = match[capture_id]
local range = { offset_node:range() } local range = { offset_node:range() }
---@cast range integer[] bug in sumneko
local start_row_offset = pred[3] or 0 local start_row_offset = pred[3] or 0
local start_col_offset = pred[4] or 0 local start_col_offset = pred[4] or 0
local end_row_offset = pred[5] or 0 local end_row_offset = pred[5] or 0
@@ -419,8 +457,9 @@ local directive_handlers = {
--- Adds a new predicate to be used in queries --- Adds a new predicate to be used in queries
--- ---
---@param name string Name of the predicate, without leading # ---@param name string Name of the predicate, without leading #
---@param handler function(match:table, pattern:string, bufnr:number, predicate:string[]) ---@param handler function(match:table<string,TSNode>, pattern:string, bufnr:number, predicate:string[])
--- - see |vim.treesitter.query.add_directive()| for argument meanings --- - see |vim.treesitter.query.add_directive()| for argument meanings
---@param force boolean
function M.add_predicate(name, handler, force) function M.add_predicate(name, handler, force)
if predicate_handlers[name] and not force then if predicate_handlers[name] and not force then
error(string.format('Overriding %s', name)) error(string.format('Overriding %s', name))
@@ -437,12 +476,13 @@ end
--- metadata table `metadata[capture_id].key = value` --- metadata table `metadata[capture_id].key = value`
--- ---
---@param name string Name of the directive, without leading # ---@param name string Name of the directive, without leading #
---@param handler function(match:table, pattern:string, bufnr:number, predicate:string[], metadata:table) ---@param handler function(match:table<string,TSNode>, pattern:string, bufnr:number, predicate:string[], metadata:table)
--- - match: see |treesitter-query| --- - match: see |treesitter-query|
--- - node-level data are accessible via `match[capture_id]` --- - node-level data are accessible via `match[capture_id]`
--- - pattern: see |treesitter-query| --- - pattern: see |treesitter-query|
--- - predicate: list of strings containing the full directive being called, e.g. --- - predicate: list of strings containing the full directive being called, e.g.
--- `(node (#set! conceal "-"))` would get the predicate `{ "#set!", "conceal", "-" }` --- `(node (#set! conceal "-"))` would get the predicate `{ "#set!", "conceal", "-" }`
---@param force boolean
function M.add_directive(name, handler, force) function M.add_directive(name, handler, force)
if directive_handlers[name] and not force then if directive_handlers[name] and not force then
error(string.format('Overriding %s', name)) error(string.format('Overriding %s', name))
@@ -474,6 +514,9 @@ local function is_directive(name)
end end
---@private ---@private
---@param match TSMatch
---@param pattern string
---@param source integer|string
function Query:match_preds(match, pattern, source) function Query:match_preds(match, pattern, source)
local preds = self.info.patterns[pattern] local preds = self.info.patterns[pattern]
@@ -482,8 +525,9 @@ function Query:match_preds(match, pattern, source)
-- continue on the other case. This way unknown predicates will not be considered, -- continue on the other case. This way unknown predicates will not be considered,
-- which allows some testing and easier user extensibility (#12173). -- which allows some testing and easier user extensibility (#12173).
-- Also, tree-sitter strips the leading # from predicates for us. -- Also, tree-sitter strips the leading # from predicates for us.
local pred_name local pred_name ---@type string
local is_not
local is_not ---@type boolean
-- Skip over directives... they will get processed after all the predicates. -- Skip over directives... they will get processed after all the predicates.
if not is_directive(pred[1]) then if not is_directive(pred[1]) then
@@ -513,6 +557,8 @@ function Query:match_preds(match, pattern, source)
end end
---@private ---@private
---@param match TSMatch
---@param metadata TSMetadata
function Query:apply_directives(match, pattern, source, metadata) function Query:apply_directives(match, pattern, source, metadata)
local preds = self.info.patterns[pattern] local preds = self.info.patterns[pattern]
@@ -534,6 +580,10 @@ end
-- When the node's range is used, the stop is incremented by 1 -- When the node's range is used, the stop is incremented by 1
-- to make the search inclusive. -- to make the search inclusive.
---@private ---@private
---@param start integer
---@param stop integer
---@param node TSNode
---@return integer, integer
local function value_or_node_range(start, stop, node) local function value_or_node_range(start, stop, node)
if start == nil and stop == nil then if start == nil and stop == nil then
local node_start, _, node_stop, _ = node:range() local node_start, _, node_stop, _ = node:range()
@@ -565,14 +615,12 @@ end
--- end --- end
--- </pre> --- </pre>
--- ---
---@param node userdata |tsnode| under which the search will occur ---@param node TSNode under which the search will occur
---@param source (number|string) Source buffer or string to extract text from ---@param source (integer|string) Source buffer or string to extract text from
---@param start number Starting line for the search ---@param start number Starting line for the search
---@param stop number Stopping line for the search (end-exclusive) ---@param stop number Stopping line for the search (end-exclusive)
--- ---
---@return number capture Matching capture id ---@return (fun(): integer, TSNode, TSMetadata): capture id, capture node, metadata
---@return table capture_node Capture for {node}
---@return table metadata for the {capture}
function Query:iter_captures(node, source, start, stop) function Query:iter_captures(node, source, start, stop)
if type(source) == 'number' and source == 0 then if type(source) == 'number' and source == 0 then
source = vim.api.nvim_get_current_buf() source = vim.api.nvim_get_current_buf()
@@ -622,14 +670,12 @@ end
--- end --- end
--- </pre> --- </pre>
--- ---
---@param node userdata |tsnode| under which the search will occur ---@param node TSNode under which the search will occur
---@param source (number|string) Source buffer or string to search ---@param source (integer|string) Source buffer or string to search
---@param start number Starting line for the search ---@param start integer Starting line for the search
---@param stop number Stopping line for the search (end-exclusive) ---@param stop integer Stopping line for the search (end-exclusive)
--- ---
---@return number pattern id ---@return (fun(): integer, table<integer,TSNode>, table): pattern id, match, metadata
---@return table match
---@return table metadata
function Query:iter_matches(node, source, start, stop) function Query:iter_matches(node, source, start, stop)
if type(source) == 'number' and source == 0 then if type(source) == 'number' and source == 0 then
source = vim.api.nvim_get_current_buf() source = vim.api.nvim_get_current_buf()
@@ -638,6 +684,7 @@ function Query:iter_matches(node, source, start, stop)
start, stop = value_or_node_range(start, stop, node) start, stop = value_or_node_range(start, stop, node)
local raw_iter = node:_rawquery(self.query, false, start, stop) local raw_iter = node:_rawquery(self.query, false, start, stop)
---@cast raw_iter fun(): string, any
local function iter() local function iter()
local pattern, match = raw_iter() local pattern, match = raw_iter()
local metadata = {} local metadata = {}

View File

@@ -286,7 +286,12 @@ local function checkComment4fn(Fn_magic, MagicLines)
return fn_magic return fn_magic
end end
local types = { 'number', 'string', 'table', 'list', 'boolean', 'function' } local types = { 'integer', 'number', 'string', 'table', 'list', 'boolean', 'function' }
local tagged_types = { 'TSNode', 'LanguageTree' }
-- Document these as 'table'
local alias_types = { 'Range' }
--! \brief run the filter --! \brief run the filter
function TLua2DoX_filter.readfile(this, AppStamp, Filename) function TLua2DoX_filter.readfile(this, AppStamp, Filename)
@@ -320,7 +325,12 @@ function TLua2DoX_filter.readfile(this, AppStamp, Filename)
offset = 1 offset = 1
end end
if string.sub(line, 3, 3) == '@' or string.sub(line, 1, 4) == '---@' then -- it's a magic comment if vim.startswith(line, '---@cast')
or vim.startswith(line, '---@diagnostic')
or vim.startswith(line, '---@type') then
-- Ignore LSP directives
outStream:writeln('// gg:"' .. line .. '"')
elseif string.sub(line, 3, 3) == '@' or string.sub(line, 1, 4) == '---@' then -- it's a magic comment
state = 'in_magic_comment' state = 'in_magic_comment'
local magic = string.sub(line, 4 + offset) local magic = string.sub(line, 4 + offset)
@@ -366,6 +376,17 @@ function TLua2DoX_filter.readfile(this, AppStamp, Filename)
magic_split[type_index] = magic_split[type_index]:gsub(k, v) magic_split[type_index] = magic_split[type_index]:gsub(k, v)
end end
end end
for _, type in ipairs(tagged_types) do
magic_split[type_index] =
magic_split[type_index]:gsub(type, '|%1|')
end
for _, type in ipairs(alias_types) do
magic_split[type_index] =
magic_split[type_index]:gsub('^'..type..'$', 'table')
end
-- surround some types by () -- surround some types by ()
for _, type in ipairs(types) do for _, type in ipairs(types) do
magic_split[type_index] = magic_split[type_index] =
@@ -373,6 +394,8 @@ function TLua2DoX_filter.readfile(this, AppStamp, Filename)
magic_split[type_index] = magic_split[type_index] =
magic_split[type_index]:gsub('^(' .. type .. '):?$', '(%1)') magic_split[type_index]:gsub('^(' .. type .. '):?$', '(%1)')
end end
end end
magic = table.concat(magic_split, ' ') magic = table.concat(magic_split, ' ')