diff options
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/doc/lua.txt | 3 | ||||
-rw-r--r-- | runtime/doc/luvref.txt | 118 | ||||
-rw-r--r-- | runtime/doc/news.txt | 11 | ||||
-rw-r--r-- | runtime/doc/options.txt | 20 | ||||
-rw-r--r-- | runtime/doc/spell.txt | 6 | ||||
-rw-r--r-- | runtime/doc/treesitter.txt | 191 | ||||
-rw-r--r-- | runtime/doc/vim_diff.txt | 2 | ||||
-rw-r--r-- | runtime/lua/nvim/health.lua | 12 | ||||
-rw-r--r-- | runtime/lua/vim/filetype.lua | 104 | ||||
-rw-r--r-- | runtime/lua/vim/filetype/detect.lua | 11 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter.lua | 96 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/_meta.lua | 60 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/highlighter.lua | 50 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/languagetree.lua | 128 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/playground.lua | 72 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/query.lua | 201 |
16 files changed, 733 insertions, 352 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 47249a484b..0472c11a15 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1362,7 +1362,8 @@ defer_fn({fn}, {timeout}) *vim.defer_fn()* Parameters: ~ • {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: ~ (table) timer luv timer object diff --git a/runtime/doc/luvref.txt b/runtime/doc/luvref.txt index 859e75e4af..66cee0649c 100644 --- a/runtime/doc/luvref.txt +++ b/runtime/doc/luvref.txt @@ -24,7 +24,7 @@ be used in other Lua environments. More information about the core libuv library can be found at the original libuv documentation page (https://docs.libuv.org/). -TCP Echo Server Example~ +TCP Echo Server Example ~ Here is a small example showing a TCP echo server: @@ -51,15 +51,15 @@ Here is a small example showing a TCP echo server: uv.run() -- an explicit run call is necessary outside of luvit < -Module Layout~ +Module Layout ~ The luv library contains a single Lua module referred to hereafter as `uv` for simplicity. This module consists mostly of functions with names corresponding to their original libuv versions. For example, the libuv function -`uv_tcp_bind` has a luv version at |uv.tcp_bind()|. Currently, only one -non-function field exists: `uv.constants`, which is a table. +`uv_tcp_bind` has a luv version at |uv.tcp_bind()|. Currently, only two +non-function fields exists: `uv.constants` and `uv.errno`, which are tables. -Functions vs Methods~ +Functions vs Methods ~ In addition to having simple functions, luv provides an optional method-style API. For example, `uv.tcp_bind(server, host, port)` can alternatively be @@ -67,7 +67,7 @@ called as `server:bind(host, port)` . Note that the first argument `server` becomes the object and `tcp_` is removed from the function name. Method forms are documented below where they exist. -Synchronous vs Asynchronous Functions~ +Synchronous vs Asynchronous Functions ~ Functions that accept a callback are asynchronous. These functions may immediately return results to the caller to indicate their initial status, but @@ -82,7 +82,7 @@ Some (generally FS and DNS) functions can behave either synchronously or asynchronously. If a callback is provided to these functions, they behave asynchronously; if no callback is provided, they behave synchronously. -Pseudo-Types~ +Pseudo-Types ~ Some unique types are defined. These are not actual types in Lua, but they are used here to facilitate documenting consistent behavior: @@ -133,10 +133,10 @@ module. ============================================================================== ERROR HANDLING *luv-error-handling* -In libuv, errors are negative numbered constants; however, these errors and -the functions used to handle them are not exposed to luv users. Instead, if an -internal error is encountered, the luv function will return to the caller an -assertable `nil, err, name` tuple. +In libuv, errors are negative numbered constants; however, while those errors +are exposed through `uv.errno`, the functions used to handle them are not +exposed to luv users. Instead, if an internal error is encountered, the luv +function will return to the caller an assertable `nil, err, name` tuple. - `nil` idiomatically indicates failure - `err` is a string with the format `{name}: {message}` @@ -151,6 +151,94 @@ When a function is called successfully, it will return either a value that is relevant to the operation of the function, or the integer `0` to indicate success, or sometimes nothing at all. These cases are documented below. +`uv.errno` *uv.errno* + +A table value which exposes error constants as a map, where the key is the +error name (without the `UV_` prefix) and its value is a negative number. +See Libuv's "Error constants" page for further details. +(https://docs.libuv.org/en/v1.x/errors.html#error-constants) + +- `E2BIG`: argument list too long. +- `EACCES`: permission denied. +- `EADDRINUSE`: address already in use. +- `EADDRNOTAVAIL`: address not available. +- `EAFNOSUPPORT`: address family not supported. +- `EAGAIN`: resource temporarily unavailable. +- `EAI_ADDRFAMILY`: address family not supported. +- `EAI_AGAIN`: temporary failure. +- `EAI_BADFLAGS`: bad ai_flags value. +- `EAI_BADHINTS`: invalid value for hints. +- `EAI_CANCELED`: request canceled. +- `EAI_FAIL`: permanent failure. +- `EAI_FAMILY`: ai_family not supported. +- `EAI_MEMORY`: out of memory. +- `EAI_NODATA`: no address. +- `EAI_NONAME`: unknown node or service. +- `EAI_OVERFLOW`: argument buffer overflow. +- `EAI_PROTOCOL`: resolved protocol is unknown. +- `EAI_SERVICE`: service not available for socket type. +- `EAI_SOCKTYPE`: socket type not supported. +- `EALREADY`: connection already in progress. +- `EBADF`: bad file descriptor. +- `EBUSY`: resource busy or locked. +- `ECANCELED`: operation canceled. +- `ECHARSET`: invalid Unicode character. +- `ECONNABORTED`: software caused connection abort. +- `ECONNREFUSED`: connection refused. +- `ECONNRESET`: connection reset by peer. +- `EDESTADDRREQ`: destination address required. +- `EEXIST`: file already exists. +- `EFAULT`: bad address in system call argument. +- `EFBIG`: file too large. +- `EHOSTUNREACH`: host is unreachable. +- `EINTR`: interrupted system call. +- `EINVAL`: invalid argument. +- `EIO`: i/o error. +- `EISCONN`: socket is already connected. +- `EISDIR`: illegal operation on a directory. +- `ELOOP`: too many symbolic links encountered. +- `EMFILE`: too many open files. +- `EMSGSIZE`: message too long. +- `ENAMETOOLONG`: name too long. +- `ENETDOWN`: network is down. +- `ENETUNREACH`: network is unreachable. +- `ENFILE`: file table overflow. +- `ENOBUFS`: no buffer space available. +- `ENODEV`: no such device. +- `ENOENT`: no such file or directory. +- `ENOMEM`: not enough memory. +- `ENONET`: machine is not on the network. +- `ENOPROTOOPT`: protocol not available. +- `ENOSPC`: no space left on device. +- `ENOSYS`: function not implemented. +- `ENOTCONN`: socket is not connected. +- `ENOTDIR`: not a directory. +- `ENOTEMPTY`: directory not empty. +- `ENOTSOCK`: socket operation on non-socket. +- `ENOTSUP`: operation not supported on socket. +- `EOVERFLOW`: value too large for defined data type. +- `EPERM`: operation not permitted. +- `EPIPE`: broken pipe. +- `EPROTO`: protocol error. +- `EPROTONOSUPPORT`: protocol not supported. +- `EPROTOTYPE`: protocol wrong type for socket. +- `ERANGE`: result too large. +- `EROFS`: read-only file system. +- `ESHUTDOWN`: cannot send after transport endpoint shutdown. +- `ESPIPE`: invalid seek. +- `ESRCH`: no such process. +- `ETIMEDOUT`: connection timed out. +- `ETXTBSY`: text file is busy. +- `EXDEV`: cross-device link not permitted. +- `UNKNOWN`: unknown error. +- `EOF`: end of file. +- `ENXIO`: no such device or address. +- `EMLINK`: too many links. +- `ENOTTY`: inappropriate ioctl for device. +- `EFTYPE`: inappropriate file type or format. +- `EILSEQ`: illegal byte sequence. +- `ESOCKTNOSUPPORT`: socket type not supported. + ============================================================================== VERSION CHECKING *luv-version-checking* @@ -3888,11 +3976,11 @@ uv.metrics_idle_time() *uv.metrics_idle_time()* ============================================================================== CREDITS *luv-credits* -This document is a reformatted version of the LUV documentation, based on -commit c51e705 (5 May 2022) of the luv repository -https://github.com/luvit/luv/commit/c51e7052ec4f0a25058f70c1b4ee99dd36180e59. +This document is a reformatted version of the LUV documentation, up-to-date +with commit e8e7b7e (3 Feb 2023) of the luv repository +https://github.com/luvit/luv/commit/e8e7b7e13225348a8806118a3ea9e021383a9536. -Included from https://github.com/nanotee/luv-vimdocs with kind permission. +Based on https://github.com/nanotee/luv-vimdocs with kind permission. vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 5c234677ef..2afb22bb43 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -47,6 +47,9 @@ The following changes may require adaptations in user config or plugins. • libiconv is now a required build dependency. +• Unsaved changes are now preserved rather than discarded when |channel-stdio| + is closed. + ============================================================================== NEW FEATURES *news-features* @@ -63,7 +66,7 @@ The following new APIs or features were added. < (or the Vimscript equivalent) to their |config| file. -• Run Lua scripts from your shell using |-l|. > +• Added support for running Lua scripts from shell using |-l|. > nvim -l foo.lua --arg1 --arg2 < Also works with stdin: > echo "print(42)" | nvim -l - @@ -151,6 +154,12 @@ The following new APIs or features were added. • |:highlight| now supports an additional attribute "altfont". +• Treesitter captures can now be transformed by directives. This will allow + more complicated dynamic language injections. + +• |vim.treesitter.query.get_node_text()| now accepts a `metadata` option for + writing custom directives using |vim.treesitter.query.add_directive()|. + ============================================================================== CHANGED FEATURES *news-changes* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 1f91b89322..e4106358f1 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -4765,7 +4765,7 @@ A jump table for the options with a short description can be found at |Q_op|. Flags to change the way redrawing works, for debugging purposes. Most useful with 'writedelay' set to some reasonable value. Supports the following flags: - compositor Indicate what redraws come from the compositor + compositor Indicate each redraw event handled by the compositor by briefly flashing the redrawn regions in colors indicating the redraw type. These are the highlight groups used (and their default colors): @@ -4777,6 +4777,11 @@ A jump table for the options with a short description can be found at |Q_op|. RedrawDebugRecompose guibg=Red redraw generated by the compositor itself, due to a grid being moved or deleted. + line introduce a delay after each line drawn on the screen. + When using the TUI or another single-grid UI, "compositor" + gives more information and should be preferred (every + line is processed as a separate event by the compositor) + flush introduce a delay after each "flush" event. nothrottle Turn off throttling of the message grid. This is an optimization that joins many small scrolls to one larger scroll when drawing the message area (with @@ -4786,7 +4791,7 @@ A jump table for the options with a short description can be found at |Q_op|. useful when running nvim inside a debugger (and the test suite). nodelta Send all internally redrawn cells to the UI, even if - they are unchanged from the already displayed state. + they are unchanged from the already displayed state. *'redrawtime'* *'rdt'* 'redrawtime' 'rdt' number (default 2000) @@ -6196,8 +6201,10 @@ A jump table for the options with a short description can be found at |Q_op|. this label. < - Where to truncate line if too long. Default is at the start. No width fields allowed. - = - Separation point between alignment sections. Each section will - be separated by an equal number of spaces. + = - Separation point between alignment sections. Each section will + be separated by an equal number of spaces. With one %= what + comes after it will be right-aligned. With two %= there is a + middle part, with white space left and right of it. No width fields allowed. # - Set highlight group. The name must follow and then a # again. Thus use %#HLname# for highlight group HLname. The same @@ -7372,8 +7379,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'writedelay'* *'wd'* 'writedelay' 'wd' number (default 0) global - The number of milliseconds to wait for each character sent to the - screen. When positive, characters are sent to the UI one by one. - See 'redrawdebug' for more options. For debugging purposes. + Only takes effect toghether with 'redrawdebug'. + The number of milliseconds to wait after each line or each flush vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/spell.txt b/runtime/doc/spell.txt index 98a6af1b8b..c6c4da2dea 100644 --- a/runtime/doc/spell.txt +++ b/runtime/doc/spell.txt @@ -440,9 +440,9 @@ find these functions useful: SETTING 'spellcapcheck' AUTOMATICALLY *set-spc-auto* After the 'spelllang' option has been set successfully, Vim will source the -files "spell/LANG.vim" in 'runtimepath'. "LANG" is the value of 'spelllang' -up to the first comma, dot or underscore. This can be used to set options -specifically for the language, especially 'spellcapcheck'. +files "spell/LANG.vim" and "spell/LANG.lua" in 'runtimepath'. "LANG" is the +value of 'spelllang' up to the first comma, dot or underscore. This can be +used to set options specifically for the language, especially 'spellcapcheck'. The distribution includes a few of these files. Use this command to see what they do: > diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 9bfdc0b94e..693804497d 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -66,102 +66,102 @@ See |lua-treesitter-languagetree| for the list of available methods. ============================================================================== TREESITTER TREES *treesitter-tree* - *tstree* + *TSTree* 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 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. -tstree:copy() *tstree:copy()* - Returns a copy of the `tstree`. +TSTree:copy() *TSTree:copy()* + Returns a copy of the `TSTree`. ============================================================================== TREESITTER NODES *treesitter-node* - *tsnode* + *TSNode* 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 |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. -tsnode:next_sibling() *tsnode:next_sibling()* +TSNode:next_sibling() *TSNode: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. -tsnode:next_named_sibling() *tsnode:next_named_sibling()* +TSNode:next_named_sibling() *TSNode: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. -tsnode:iter_children() *tsnode:iter_children()* - Iterates over all the direct children of {tsnode}, regardless of whether +TSNode:iter_children() *TSNode:iter_children()* + Iterates over all the direct children of {TSNode}, regardless of whether they are named or not. Returns the child node plus the eventual field name corresponding to this child node. -tsnode:field({name}) *tsnode:field()* +TSNode:field({name}) *TSNode: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. -tsnode:child({index}) *tsnode:child()* +TSNode:child({index}) *TSNode:child()* Get the node's child at the given {index}, where zero represents the first 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. -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 first named child. -tsnode:start() *tsnode:start()* +TSNode:start() *TSNode:start()* Get the node's start position. Return three values: the row, column and 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 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 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. -tsnode:symbol() *tsnode:symbol()* +TSNode:symbol() *TSNode:symbol()* 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 grammar, whereas anonymous nodes correspond to string literals in the grammar. -tsnode:missing() *tsnode:missing()* +TSNode:missing() *TSNode:missing()* Check if the node is missing. Missing nodes are inserted by the parser in 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. -tsnode:sexpr() *tsnode:sexpr()* +TSNode:sexpr() *TSNode:sexpr()* 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. 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 trees. - *tsnode:descendant_for_range()* -tsnode:descendant_for_range({start_row}, {start_col}, {end_row}, {end_col}) + *TSNode:descendant_for_range()* +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, column) positions - *tsnode:named_descendant_for_range()* -tsnode:named_descendant_for_range({start_row}, {start_col}, {end_row}, {end_col}) + *TSNode:named_descendant_for_range()* +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 (row, column) positions ============================================================================== 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 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 @@ -336,7 +336,7 @@ repeated, for example, the following two modeline blocks are both valid: > TREESITTER SYNTAX HIGHLIGHTING *treesitter-highlight* 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 > (parameters (identifier) @parameter) @@ -486,7 +486,7 @@ get_captures_at_cursor({winnr}) Returns a list of highlight capture names under the cursor Parameters: ~ - • {winnr} (number|nil) Window handle or 0 for current window (default) + • {winnr} (integer|nil) Window handle or 0 for current window (default) Return: ~ string[] List of capture names @@ -500,9 +500,9 @@ get_captures_at_pos({bufnr}, {row}, {col}) if none are defined). Parameters: ~ - • {bufnr} (number) Buffer number (0 for current buffer) - • {row} (number) Position row - • {col} (number) Position column + • {bufnr} (integer) Buffer number (0 for current buffer) + • {row} (integer) Position row + • {col} (integer) Position column Return: ~ 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 Parameters: ~ - • {winnr} (number|nil) Window handle or 0 for current window (default) + • {winnr} (integer|nil) Window handle or 0 for current window (default) Return: ~ (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 Parameters: ~ - • {bufnr} (number) Buffer number (0 for current buffer) - • {row} (number) Position row - • {col} (number) Position column + • {bufnr} (integer) Buffer number (0 for current buffer) + • {row} (integer) Position row + • {col} (integer) Position column • {opts} (table) Optional keyword arguments: • lang string|nil Parser language • ignore_injections boolean Ignore injected languages (default true) Return: ~ - userdata|nil |tsnode| under the cursor + |TSNode||nil under the cursor get_node_range({node_or_range}) *vim.treesitter.get_node_range()* Returns the node's range or an unpacked range table Parameters: ~ - • {node_or_range} (userdata|table) |tsnode| or table of positions + • {node_or_range} (|TSNode||table) Node or table of positions 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()* 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. 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) • {lang} (string|nil) Filetype of this parser (default: buffer filetype) • {opts} (table|nil) Options to pass to the created language tree Return: ~ - LanguageTree |LanguageTree| object to use for parsing + |LanguageTree| object to use for parsing *vim.treesitter.get_string_parser()* 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 Return: ~ - LanguageTree |LanguageTree| object to use for parsing + |LanguageTree| object to use for parsing is_ancestor({dest}, {source}) *vim.treesitter.is_ancestor()* Determines whether a node is the ancestor of another Parameters: ~ - • {dest} userdata Possible ancestor |tsnode| - • {source} userdata Possible descendant |tsnode| + • {dest} |TSNode| Possible ancestor + • {source} |TSNode| Possible descendant Return: ~ (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 Parameters: ~ - • {node} userdata |tsnode| defining the range - • {line} (number) Line (0-based) - • {col} (number) Column (0-based) + • {node} |TSNode| defining the range + • {line} (integer) Line (0-based) + • {col} (integer) Column (0-based) Return: ~ (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 Parameters: ~ - • {node} userdata |tsnode| + • {node} |TSNode| • {range} (table) Return: ~ @@ -615,14 +618,14 @@ show_tree({opts}) *vim.treesitter.show_tree()* keys: • lang (string|nil): The language of the source buffer. If 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. - • winid (number|nil): Window id to display the tree buffer in. - If omitted, a new window is created with {command}. + • winid (integer|nil): Window id to display the tree buffer + in. If omitted, a new window is created with {command}. • command (string|nil): Vimscript command to create the window. Default value is "topleft 60vnew". Only used when {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 source buffer as its only argument and should return a string. @@ -647,7 +650,7 @@ start({bufnr}, {lang}) *vim.treesitter.start()* < Parameters: ~ - • {bufnr} (number|nil) Buffer to be highlighted (default: current + • {bufnr} (integer|nil) Buffer to be highlighted (default: current buffer) • {lang} (string|nil) Language of the parser (default: buffer filetype) @@ -656,7 +659,7 @@ stop({bufnr}) *vim.treesitter.stop()* Stops treesitter highlighting for a buffer Parameters: ~ - • {bufnr} (number|nil) Buffer to stop highlighting (default: current + • {bufnr} (integer|nil) Buffer to stop highlighting (default: current buffer) @@ -709,8 +712,8 @@ add_directive({name}, {handler}, {force}) Parameters: ~ • {name} (string) Name of the directive, without leading # - • {handler} function(match:table, pattern:string, bufnr:number, - predicate:string[], metadata:table) + • {handler} function(match:table<string,|TSNode|>, pattern:string, + bufnr:number, predicate:string[], metadata:table) • match: see |treesitter-query| • 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 being called, e.g. `(node (#set! conceal "-"))` would get the predicate `{ "#set!", "conceal", "-" }` + • {force} (boolean) *vim.treesitter.query.add_predicate()* add_predicate({name}, {handler}, {force}) @@ -725,25 +729,29 @@ add_predicate({name}, {handler}, {force}) Parameters: ~ • {name} (string) Name of the predicate, without leading # - • {handler} function(match:table, pattern:string, bufnr:number, - predicate:string[]) + • {handler} function(match:table<string,|TSNode|>, pattern:string, + bufnr:number, predicate:string[]) • see |vim.treesitter.query.add_directive()| for argument meanings + • {force} (boolean) *vim.treesitter.query.get_node_text()* get_node_text({node}, {source}, {opts}) Gets the text corresponding to a given node Parameters: ~ - • {node} userdata |tsnode| + • {node} |TSNode| • {source} (number|string) Buffer or string from which the {node} is extracted • {opts} (table|nil) Optional parameters. • concat: (boolean) Concatenate result in a string (default true) + • metadata (table) Metadata of a specific capture. This + would be set to `metadata[capture_id]` when using + |vim.treesitter.query.add_directive()|. Return: ~ - (string[]|string) + (string[]|string|nil) get_query({lang}, {query_name}) *vim.treesitter.query.get_query()* Returns the runtime query {query_name} for {lang}. @@ -753,7 +761,7 @@ get_query({lang}, {query_name}) *vim.treesitter.query.get_query()* • {query_name} (string) Name of the query (e.g. "highlights") Return: ~ - Query Parsed query + Query|nil Parsed query *vim.treesitter.query.get_query_files()* get_query_files({lang}, {query_name}, {is_included}) @@ -826,16 +834,15 @@ Query:iter_captures({self}, {node}, {source}, {start}, {stop}) < Parameters: ~ - • {node} userdata |tsnode| under which the search will occur - • {source} (number|string) Source buffer or string to extract text from + • {node} |TSNode| under which the search will occur + • {source} (integer|string) Source buffer or string to extract text + from • {start} (number) Starting line for the search • {stop} (number) Stopping line for the search (end-exclusive) • {self} Return: ~ - (number) capture Matching capture id - (table) capture_node Capture for {node} - (table) metadata for the {capture} + (fun(): integer, TSNode, TSMetadata ): capture id, capture node, metadata *Query:iter_matches()* Query:iter_matches({self}, {node}, {source}, {start}, {stop}) @@ -861,16 +868,15 @@ Query:iter_matches({self}, {node}, {source}, {start}, {stop}) < Parameters: ~ - • {node} userdata |tsnode| under which the search will occur - • {source} (number|string) Source buffer or string to search - • {start} (number) Starting line for the search - • {stop} (number) Stopping line for the search (end-exclusive) + • {node} |TSNode| under which the search will occur + • {source} (integer|string) Source buffer or string to search + • {start} (integer) Starting line for the search + • {stop} (integer) Stopping line for the search (end-exclusive) • {self} Return: ~ - (number) pattern id - (table) match - (table) metadata + (fun(): integer, table<integer,TSNode>, table): pattern id, match, + metadata *vim.treesitter.query.set_query()* set_query({lang}, {query_name}, {text}) @@ -892,7 +898,7 @@ new({tree}, {opts}) *vim.treesitter.highlighter.new()* Creates a new highlighter using 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: • queries table overwrite queries used by the highlighter @@ -940,9 +946,9 @@ LanguageTree:for_each_child({self}, {fn}, {include_self}) Invokes the callback for each |LanguageTree| and its children recursively Parameters: ~ - • {fn} function(tree: LanguageTree, lang: string) - • {include_self} (boolean) Whether to include the invoking tree in the - results + • {fn} fun(tree: LanguageTree, lang: string) + • {include_self} (boolean|nil) Whether to include the invoking tree in + the results • {self} LanguageTree:for_each_tree({self}, {fn}) *LanguageTree:for_each_tree()* @@ -951,7 +957,7 @@ LanguageTree:for_each_tree({self}, {fn}) *LanguageTree:for_each_tree()* Note: This includes the invoking tree's child trees as well. Parameters: ~ - • {fn} function(tree: TSTree, languageTree: LanguageTree) + • {fn} fun(tree: TSTree, ltree: LanguageTree) • {self} LanguageTree:included_regions({self}) *LanguageTree:included_regions()* @@ -964,6 +970,7 @@ LanguageTree:invalidate({self}, {reload}) *LanguageTree:invalidate()* Invalidates this parser and all its children Parameters: ~ + • {reload} (boolean|nil) • {self} LanguageTree:is_valid({self}) *LanguageTree:is_valid()* @@ -987,7 +994,7 @@ LanguageTree:language_for_range({self}, {range}) • {self} Return: ~ - LanguageTree Managing {range} + |LanguageTree| Managing {range} *LanguageTree:named_node_for_range()* LanguageTree:named_node_for_range({self}, {range}, {opts}) @@ -1001,7 +1008,7 @@ LanguageTree:named_node_for_range({self}, {range}, {opts}) • {self} Return: ~ - userdata|nil Found |tsnode| + |TSNode||nil Found node LanguageTree:parse({self}) *LanguageTree:parse()* Parses all defined regions using a treesitter parser for the language this @@ -1012,8 +1019,8 @@ LanguageTree:parse({self}) *LanguageTree:parse()* • {self} Return: ~ - userdata[] Table of parsed |tstree| - (table) Change list + TSTree[] + (table|nil) Change list LanguageTree:register_cbs({self}, {cbs}) *LanguageTree:register_cbs()* Registers callbacks for the |LanguageTree|. @@ -1050,7 +1057,7 @@ LanguageTree:tree_for_range({self}, {range}, {opts}) • {self} Return: ~ - userdata|nil Contained |tstree| + TSTree|nil LanguageTree:trees({self}) *LanguageTree:trees()* Returns all trees this language tree contains. Does not include child @@ -1065,7 +1072,7 @@ new({source}, {lang}, {opts}) *vim.treesitter.languagetree.new()* may contain child languages themselves, hence the name). 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 • {opts} (table|nil) Optional keyword arguments: • injections table Mapping language to injection query @@ -1074,6 +1081,6 @@ new({source}, {lang}, {opts}) *vim.treesitter.languagetree.new()* per language. Return: ~ - LanguageTree |LanguageTree| parser object + |LanguageTree| parser object vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index bb3b670b24..97fc211c36 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -267,7 +267,6 @@ Options: 'pumblend' pseudo-transparent popupmenu 'scrollback' 'signcolumn' supports up to 9 dynamic/fixed columns - 'statusline' supports unlimited alignment sections 'tabline' %@Func@foo%X can call any function on mouse-click 'winblend' pseudo-transparency in floating windows |api-floatwin| 'winhighlight' window-local highlights @@ -292,6 +291,7 @@ These Nvim features were later integrated into Vim. - |WinScrolled| - |:sign-define| "numhl" argument - |:source| works with anonymous (no file) scripts +- 'statusline' supports unlimited alignment sections ============================================================================== 5. Changed features *nvim-features-changed* diff --git a/runtime/lua/nvim/health.lua b/runtime/lua/nvim/health.lua index f11899434e..b6d84404ec 100644 --- a/runtime/lua/nvim/health.lua +++ b/runtime/lua/nvim/health.lua @@ -183,7 +183,7 @@ local function check_rplugin_manifest() existing_rplugins[item.path] = 'python' end - for item in ipairs(vim.fn['remote#host#PluginsForHost']('python3')) do + for _, item in ipairs(vim.fn['remote#host#PluginsForHost']('python3')) do existing_rplugins[item.path] = 'python3' end @@ -200,7 +200,7 @@ local function check_rplugin_manifest() local scripts = vim.fn.glob(python_dir .. '/*.py', true, true) vim.list_extend(scripts, vim.fn.glob(python_dir .. '/*/__init__.py', true, true)) - for script in ipairs(scripts) do + for _, script in ipairs(scripts) do local contents = vim.fn.join(vim.fn.readfile(script)) if vim.regex([[\<\%(from\|import\)\s\+neovim\>]]):match_str(contents) then if vim.regex([[[\/]__init__\.py$]]):match_str(script) then @@ -384,7 +384,13 @@ local function check_terminal() ) end - for env_var in ipairs({ 'XTERM_VERSION', 'VTE_VERSION', 'TERM_PROGRAM', 'COLORTERM', 'SSH_TTY' }) do + for _, env_var in ipairs({ + 'XTERM_VERSION', + 'VTE_VERSION', + 'TERM_PROGRAM', + 'COLORTERM', + 'SSH_TTY', + }) do if vim.env[env_var] then health.report_info(vim.fn.printf('$%s="%s"', env_var, vim.env[env_var])) end diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 8144731b09..ca91f3f402 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -191,6 +191,7 @@ local extension = { qc = 'c', cabal = 'cabal', capnp = 'capnp', + cdc = 'cdc', cdl = 'cdl', toc = 'cdrtoc', cfc = 'cf', @@ -422,6 +423,7 @@ local extension = { fsh = 'fsh', fsi = 'fsharp', fsx = 'fsharp', + fc = 'func', fusion = 'fusion', gdb = 'gdb', gdmo = 'gdmo', @@ -690,6 +692,7 @@ local extension = { isc = 'monk', moo = 'moo', moon = 'moonscript', + move = 'move', mp = 'mp', mpiv = function(path, bufnr) return 'mp', function(b) @@ -870,6 +873,7 @@ local extension = { Snw = 'rnoweb', robot = 'robot', resource = 'robot', + ron = 'ron', rsc = 'routeros', x = 'rpcgen', rpl = 'rpl', @@ -1433,6 +1437,7 @@ local filename = { gnashrc = 'gnash', ['.gnuplot'] = 'gnuplot', ['go.sum'] = 'gosum', + ['go.work.sum'] = 'gosum', ['go.work'] = 'gowork', ['.gprc'] = 'gp', ['/.gnupg/gpg.conf'] = 'gpg', @@ -2444,6 +2449,7 @@ local function match_pattern(name, path, tail, pat) return false end end + -- If the pattern contains a / match against the full path, otherwise just the tail local fullpat = '^' .. pat .. '$' local matches @@ -2521,64 +2527,64 @@ function M.match(args) name = api.nvim_buf_get_name(bufnr) end - if name then - name = normalize_path(name) - end - local ft, on_detect - -- First check for the simple case where the full path exists as a key - local path = vim.fn.fnamemodify(name, ':p') - ft, on_detect = dispatch(filename[path], path, bufnr) - if ft then - return ft, on_detect - end + if name then + name = normalize_path(name) - -- Next check against just the file name - local tail = vim.fn.fnamemodify(name, ':t') - ft, on_detect = dispatch(filename[tail], path, bufnr) - if ft then - return ft, on_detect - end + -- First check for the simple case where the full path exists as a key + local path = vim.fn.fnamemodify(name, ':p') + ft, on_detect = dispatch(filename[path], path, bufnr) + if ft then + return ft, on_detect + end - -- Next, check the file path against available patterns with non-negative priority - local j = 1 - for i, v in ipairs(pattern_sorted) do - local k = next(v) - local opts = v[k][2] - if opts.priority < 0 then - j = i - break + -- Next check against just the file name + local tail = vim.fn.fnamemodify(name, ':t') + ft, on_detect = dispatch(filename[tail], path, bufnr) + if ft then + return ft, on_detect end - local filetype = v[k][1] - local matches = match_pattern(name, path, tail, k) - if matches then - ft, on_detect = dispatch(filetype, path, bufnr, matches) - if ft then - return ft, on_detect + -- Next, check the file path against available patterns with non-negative priority + local j = 1 + for i, v in ipairs(pattern_sorted) do + local k = next(v) + local opts = v[k][2] + if opts.priority < 0 then + j = i + break + end + + local filetype = v[k][1] + local matches = match_pattern(name, path, tail, k) + if matches then + ft, on_detect = dispatch(filetype, path, bufnr, matches) + if ft then + return ft, on_detect + end end end - end - -- Next, check file extension - local ext = vim.fn.fnamemodify(name, ':e') - ft, on_detect = dispatch(extension[ext], path, bufnr) - if ft then - return ft, on_detect - end + -- Next, check file extension + local ext = vim.fn.fnamemodify(name, ':e') + ft, on_detect = dispatch(extension[ext], path, bufnr) + if ft then + return ft, on_detect + end - -- Next, check patterns with negative priority - for i = j, #pattern_sorted do - local v = pattern_sorted[i] - local k = next(v) + -- Next, check patterns with negative priority + for i = j, #pattern_sorted do + local v = pattern_sorted[i] + local k = next(v) - local filetype = v[k][1] - local matches = match_pattern(name, path, tail, k) - if matches then - ft, on_detect = dispatch(filetype, path, bufnr, matches) - if ft then - return ft, on_detect + local filetype = v[k][1] + local matches = match_pattern(name, path, tail, k) + if matches then + ft, on_detect = dispatch(filetype, path, bufnr, matches) + if ft then + return ft, on_detect + end end end end @@ -2598,7 +2604,9 @@ function M.match(args) -- If the function tries to use the filename that is nil then it will fail, -- but this enables checks which do not need a filename to still work. local ok - ok, ft = pcall(require('vim.filetype.detect').match_contents, contents, name) + ok, ft = pcall(require('vim.filetype.detect').match_contents, contents, name, function(ext) + return dispatch(extension[ext], name, bufnr) + end) if ok and ft then return ft end diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index edffdde9c7..b3d9fedeae 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -1420,7 +1420,7 @@ local patterns_hashbang = { ---@private -- File starts with "#!". -local function match_from_hashbang(contents, path) +local function match_from_hashbang(contents, path, dispatch_extension) local first_line = contents[1] -- Check for a line like "#!/usr/bin/env {options} bash". Turn it into -- "#!/usr/bin/bash" to make matching easier. @@ -1473,6 +1473,11 @@ local function match_from_hashbang(contents, path) return ft end end + + -- If nothing matched, check the extension table. For a hashbang like + -- '#!/bin/env foo', this will set the filetype to 'fooscript' assuming + -- the filetype for the 'foo' extension is 'fooscript' in the extension table. + return dispatch_extension(name) end local patterns_text = { @@ -1652,10 +1657,10 @@ local function match_from_text(contents, path) return cvs_diff(path, contents) end -M.match_contents = function(contents, path) +function M.match_contents(contents, path, dispatch_extension) local first_line = contents[1] if first_line:find('^#!') then - return match_from_hashbang(contents, path) + return match_from_hashbang(contents, path, dispatch_extension) else return match_from_text(contents, path) end diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 582922ecb6..4127198576 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -3,8 +3,11 @@ local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') local LanguageTree = require('vim.treesitter.languagetree') +---@type table<integer,LanguageTree> local parsers = setmetatable({}, { __mode = 'v' }) +---@class TreesitterModule +---@field highlighter TSHighlighter local M = vim.tbl_extend('error', query, language) M.language_version = vim._ts_get_language_version() @@ -12,6 +15,7 @@ M.minimum_language_version = vim._ts_get_minimum_language_version() setmetatable(M, { __index = function(t, k) + ---@diagnostic disable:no-unknown if k == 'highlighter' then t[k] = require('vim.treesitter.highlighter') return t[k] @@ -29,21 +33,23 @@ setmetatable(M, { --- --- 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 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) language.require_language(lang) if bufnr == 0 then - bufnr = a.nvim_get_current_buf() + bufnr = vim.api.nvim_get_current_buf() end vim.fn.bufload(bufnr) local self = LanguageTree.new(bufnr, lang, opts) + ---@diagnostic disable:invisible + ---@private local function bytes_cb(_, ...) self:_on_bytes(...) @@ -58,12 +64,14 @@ function M._create_parser(bufnr, lang, opts) end ---@private - local function reload_cb(_, ...) - self:_on_reload(...) + local function reload_cb(_) + self:_on_reload() end + local source = self:source() --[[@as integer]] + a.nvim_buf_attach( - self:source(), + source, false, { on_bytes = bytes_cb, on_detach = detach_cb, on_reload = reload_cb, preview = true } ) @@ -77,22 +85,24 @@ end --- --- 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 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) opts = opts or {} if bufnr == nil or bufnr == 0 then bufnr = a.nvim_get_current_buf() end - if lang == nil then - lang = a.nvim_buf_get_option(bufnr, 'filetype') - end - if parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then + if parsers[bufnr] == nil then + lang = lang or a.nvim_buf_get_option(bufnr, 'filetype') + parsers[bufnr] = M._create_parser(bufnr, lang, opts) + elseif lang and parsers[bufnr]:lang() ~= lang then + -- Only try to create a new parser if lang is provided + -- and it doesn't match the stored parser parsers[bufnr] = M._create_parser(bufnr, lang, opts) end @@ -107,7 +117,7 @@ end ---@param lang string Language of this string ---@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) vim.validate({ str = { str, 'string' }, @@ -120,8 +130,8 @@ end --- Determines whether a node is the ancestor of another --- ----@param dest userdata Possible ancestor |tsnode| ----@param source userdata Possible descendant |tsnode| +---@param dest TSNode Possible ancestor +---@param source TSNode Possible descendant --- ---@return boolean True if {dest} is an ancestor of {source} function M.is_ancestor(dest, source) @@ -143,9 +153,12 @@ end --- 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) if type(node_or_range) == 'table' then return unpack(node_or_range) @@ -156,9 +169,9 @@ end --- Determines whether (line, col) position is in node range --- ----@param node userdata |tsnode| defining the range ----@param line number Line (0-based) ----@param col number Column (0-based) +---@param node TSNode defining the range +---@param line integer Line (0-based) +---@param col integer Column (0-based) --- ---@return boolean True if the position is in node range function M.is_in_node_range(node, line, col) @@ -180,7 +193,7 @@ end --- Determines if a node contains a range --- ----@param node userdata |tsnode| +---@param node TSNode ---@param range table --- ---@return boolean True if the {node} contains the {range} @@ -197,9 +210,9 @@ end --- 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). --- ----@param bufnr number Buffer number (0 for current buffer) ----@param row number Position row ----@param col number Position column +---@param bufnr integer Buffer number (0 for current buffer) +---@param row integer Position row +---@param col integer Position column --- ---@return table[] List of captures `{ capture = "capture name", metadata = { ... } }` function M.get_captures_at_pos(bufnr, row, col) @@ -250,7 +263,7 @@ end --- 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 function M.get_captures_at_cursor(winnr) @@ -271,14 +284,14 @@ end --- Returns the smallest named node at the given position --- ----@param bufnr number Buffer number (0 for current buffer) ----@param row number Position row ----@param col number Position column +---@param bufnr integer Buffer number (0 for current buffer) +---@param row integer Position row +---@param col integer Position column ---@param opts table Optional keyword arguments: --- - lang string|nil Parser language --- - 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) if bufnr == 0 then bufnr = a.nvim_get_current_buf() @@ -295,7 +308,7 @@ end --- 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 function M.get_node_at_cursor(winnr) @@ -323,7 +336,7 @@ end --- }) --- </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) function M.start(bufnr, lang) bufnr = bufnr or a.nvim_get_current_buf() @@ -333,7 +346,7 @@ end --- 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) bufnr = bufnr or a.nvim_get_current_buf() @@ -351,13 +364,13 @@ end ---@param opts table|nil Optional options table with the following possible keys: --- - lang (string|nil): The language of the source buffer. If omitted, the --- 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. ---- - 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}. --- - command (string|nil): Vimscript command to create the window. Default --- 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 --- argument and should return a string. function M.show_tree(opts) @@ -400,6 +413,7 @@ function M.show_tree(opts) vim.bo[b].buflisted = false vim.bo[b].buftype = 'nofile' vim.bo[b].bufhidden = 'wipe' + vim.bo[b].filetype = 'query' local title = opts.title if not title then @@ -414,9 +428,6 @@ function M.show_tree(opts) pg:draw(b) - vim.fn.matchadd('Comment', '\\[[0-9:-]\\+\\]') - vim.fn.matchadd('String', '".*"') - a.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) a.nvim_buf_set_keymap(b, 'n', '<CR>', '', { desc = 'Jump to the node under the cursor in the source buffer', @@ -456,6 +467,15 @@ function M.show_tree(opts) end_col = math.max(0, pos.end_col), hl_group = 'Visual', }) + + local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win) + + -- Move the cursor if highlighted range is completely out of view + if pos.lnum < topline and pos.end_lnum < topline then + a.nvim_win_set_cursor(win, { pos.end_lnum + 1, 0 }) + elseif pos.lnum > botline and pos.end_lnum > botline then + a.nvim_win_set_cursor(win, { pos.lnum + 1, 0 }) + end end, }) diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua new file mode 100644 index 0000000000..87b4560798 --- /dev/null +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -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 diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index d77a0d0d03..8adaa4ef2f 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -1,13 +1,27 @@ local a = vim.api 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 +---@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 {} TSHighlighter.__index = TSHighlighter TSHighlighter.active = TSHighlighter.active or {} +---@class TSHighlighterQuery +---@field _query Query|nil +---@field hl_cache table<integer,integer> local TSHighlighterQuery = {} TSHighlighterQuery.__index = TSHighlighterQuery @@ -46,7 +60,7 @@ end --- 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: --- - queries table overwrite queries used by the highlighter ---@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.') end - opts = opts or {} + opts = opts or {} ---@type { queries: table<string,string> } self.tree = tree tree:register_cbs({ + ---@diagnostic disable:invisible on_changedtree = function(...) self:on_changedtree(...) end, @@ -67,17 +82,20 @@ function TSHighlighter.new(tree, opts) self:on_bytes(...) end, on_detach = function(...) + ---@diagnostic disable-next-line:redundant-parameter self:on_detach(...) end, }) - self.bufnr = tree:source() + self.bufnr = tree:source() --[[@as integer]] self.edit_count = 0 self.redraw_count = 0 self.line_count = {} -- A map of highlight states. -- This state is kept during rendering across each line update. self._highlight_states = {} + + ---@type table<string,TSHighlighterQuery> self._queries = {} -- Queries for a specific language can be overridden by a custom @@ -128,6 +146,8 @@ function TSHighlighter:destroy() end ---@private +---@param tstree TSTree +---@return TSHighlightState function TSHighlighter:get_highlight_state(tstree) if not self._highlight_states[tstree] then self._highlight_states[tstree] = { @@ -145,6 +165,8 @@ function TSHighlighter:reset_highlight_state() end ---@private +---@param start_row integer +---@param new_end integer function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end) a.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1) end @@ -155,6 +177,7 @@ function TSHighlighter:on_detach() end ---@private +---@param changes integer[][]? function TSHighlighter:on_changedtree(changes) for _, ch in ipairs(changes or {}) do a.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3] + 1) @@ -165,7 +188,7 @@ end -- ---@private ---@param lang string Language used by the highlighter. ----@return Query +---@return TSHighlighterQuery function TSHighlighter:get_query(lang) if not self._queries[lang] then self._queries[lang] = TSHighlighterQuery.new(lang) @@ -175,7 +198,12 @@ function TSHighlighter:get_query(lang) end ---@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) + ---@diagnostic disable:invisible self.tree:for_each_tree(function(tstree, tree) if not tstree then return @@ -213,7 +241,7 @@ local function on_line_impl(self, buf, line, is_spell_nav) local hl = highlighter_query.hl_cache[capture] local capture_name = highlighter_query:query().captures[capture] - local spell = nil + local spell = nil ---@type boolean? if capture_name == 'spell' then spell = true elseif capture_name == 'nospell' then @@ -242,6 +270,9 @@ local function on_line_impl(self, buf, line, is_spell_nav) end ---@private +---@param _win integer +---@param buf integer +---@param line integer function TSHighlighter._on_line(_, _win, buf, line, _) local self = TSHighlighter.active[buf] if not self then @@ -252,6 +283,9 @@ function TSHighlighter._on_line(_, _win, buf, line, _) end ---@private +---@param buf integer +---@param srow integer +---@param erow integer function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _) local self = TSHighlighter.active[buf] if not self then @@ -266,6 +300,7 @@ function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _) end ---@private +---@param buf integer function TSHighlighter._on_buf(_, buf) local self = TSHighlighter.active[buf] if self then @@ -274,6 +309,9 @@ function TSHighlighter._on_buf(_, buf) end ---@private +---@param _win integer +---@param buf integer +---@param _topline integer function TSHighlighter._on_win(_, _win, buf, _topline) local self = TSHighlighter.active[buf] if not self then diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index a1e96f8ef2..8255c6f4fe 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -2,20 +2,39 @@ local a = vim.api local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') ----@class LanguageTree ----@field _callbacks function[] Callback handlers ----@field _children LanguageTree[] Injected languages ----@field _injection_query table Queries defining injected languages ----@field _opts table Options ----@field _parser userdata Parser for language ----@field _regions table List of regions this tree should manage and parse ----@field _lang string Language name ----@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 Range {[1]: integer, [2]: integer, [3]: integer, [4]: integer} +-- +---@alias TSCallbackName +---| 'changedtree' +---| 'bytes' +---| 'detach' +---| 'child_added' +---| 'child_removed' + +---@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 = {} + +---@class LanguageTreeOpts +---@field queries table<string,string> -- Deprecated +---@field injections table<string,string> + LanguageTree.__index = LanguageTree --- 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 --- 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 opts (table|nil) Optional keyword arguments: --- - injections table Mapping language to injection query strings. --- This is useful for overriding the built-in --- runtime file searching for the injection language --- query per language. ----@return LanguageTree |LanguageTree| parser object +---@return LanguageTree parser object function LanguageTree.new(source, lang, opts) language.require_language(lang) + ---@type LanguageTreeOpts opts = opts or {} if opts.queries then @@ -65,6 +85,7 @@ function LanguageTree.new(source, lang, opts) end --- Invalidates this parser and all its children +---@param reload boolean|nil function LanguageTree:invalidate(reload) self._valid = false @@ -73,7 +94,7 @@ function LanguageTree:invalidate(reload) self._trees = {} end - for _, child in ipairs(self._children) do + for _, child in pairs(self._children) do child:invalidate(reload) end end @@ -111,8 +132,8 @@ end --- This will run the injection query for this language to --- determine if any child languages should be created. --- ----@return userdata[] Table of parsed |tstree| ----@return table Change list +---@return TSTree[] +---@return table|nil Change list function LanguageTree:parse() if self._valid then return self._trees @@ -146,7 +167,7 @@ function LanguageTree:parse() end 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 local has_lang = language.require_language(lang, nil, true) @@ -188,8 +209,8 @@ end --- Invokes the callback for each |LanguageTree| and its children recursively --- ----@param fn function(tree: LanguageTree, lang: string) ----@param include_self boolean Whether to include the invoking tree in the results +---@param fn fun(tree: LanguageTree, lang: string) +---@param include_self boolean|nil Whether to include the invoking tree in the results function LanguageTree:for_each_child(fn, include_self) if include_self then fn(self, self._lang) @@ -204,7 +225,7 @@ end --- --- 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) for _, tree in ipairs(self._trees) do fn(tree, self) @@ -221,7 +242,7 @@ end --- ---@private ---@param lang string Language to add. ----@return LanguageTree Injected |LanguageTree| +---@return LanguageTree injected function LanguageTree:add_child(lang) if self._children[lang] then self:remove_child(lang) @@ -258,7 +279,7 @@ end --- `remove_child` must be called on the parent to remove it. function LanguageTree:destroy() -- Cleanup here - for _, child in ipairs(self._children) do + for _, child in pairs(self._children) do child:destroy() end end @@ -280,20 +301,22 @@ end --- Note: This call invalidates the tree and requires it to be parsed again. --- ---@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) -- Transform the tables from 4 element long to 6 element long (with byte offset) for _, region in ipairs(regions) do for i, range in ipairs(region) do 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_byte = 0 local end_byte = 0 + local source = self._source -- 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 - start_byte = a.nvim_buf_get_offset(self._source, start_row) + start_col - end_byte = a.nvim_buf_get_offset(self._source, end_row) + end_col + start_byte = a.nvim_buf_get_offset(source, start_row) + start_col + end_byte = a.nvim_buf_get_offset(source, end_row) + end_col elseif type(self._source) == 'string' then -- string parser, single `\n` delimited string start_byte = vim.fn.byteidx(self._source, start_col) @@ -320,9 +343,13 @@ function LanguageTree:included_regions() end ---@private +---@param node TSNode +---@param id integer +---@param metadata TSMetadata +---@return Range local function get_range_from_metadata(node, id, metadata) if metadata[id] and metadata[id].range then - return metadata[id].range + return metadata[id].range --[[@as Range]] end return { node:range() } end @@ -334,11 +361,13 @@ end --- TODO: Allow for an offset predicate to tailor the injection range --- instead of using the entire nodes range. ---@private +---@return table<string, integer[][]> function LanguageTree:_get_injections() if not self._injection_query then return {} end + ---@type table<integer,table<string,table<integer,table>>> local injections = {} for tree_index, tree in ipairs(self._trees) do @@ -348,14 +377,14 @@ function LanguageTree:_get_injections() for pattern, match, metadata in self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1) do - local lang = nil - local ranges = {} - local combined = metadata.combined + local lang = nil ---@type string + local ranges = {} ---@type Range[] + local combined = metadata.combined ---@type boolean -- Directives can configure how injections are captured as well as actual node captures. -- This allows more advanced processing for determining ranges and language resolution. if metadata.content then - local content = metadata.content + local content = metadata.content ---@type any -- Allow for captured nodes to be used if type(content) == 'number' then @@ -368,7 +397,7 @@ function LanguageTree:_get_injections() end if metadata.language then - lang = metadata.language + lang = metadata.language ---@type string end -- You can specify the content and language together @@ -379,7 +408,7 @@ function LanguageTree:_get_injections() -- Lang should override any other language tag if name == 'language' and not lang then - lang = query.get_node_text(node, self._source) + lang = query.get_node_text(node, self._source, { metadata = metadata[id] }) elseif name == 'combined' then combined = true elseif name == 'content' and #ranges == 0 then @@ -417,6 +446,7 @@ function LanguageTree:_get_injections() end end + ---@type table<string,Range[][]> local result = {} -- Generate a map by lang of node lists. @@ -429,11 +459,13 @@ function LanguageTree:_get_injections() for _, entry in pairs(patterns) do if entry.combined then + ---@diagnostic disable-next-line:no-unknown local regions = vim.tbl_map(function(e) return vim.tbl_flatten(e) end, entry.regions) table.insert(result[lang], regions) else + ---@diagnostic disable-next-line:no-unknown for _, ranges in ipairs(entry.regions) do table.insert(result[lang], ranges) end @@ -446,6 +478,7 @@ function LanguageTree:_get_injections() end ---@private +---@param cb_name TSCallbackName function LanguageTree:_do_callback(cb_name, ...) for _, cb in ipairs(self._callbacks[cb_name]) do cb(...) @@ -453,6 +486,17 @@ function LanguageTree:_do_callback(cb_name, ...) end ---@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( bufnr, changed_tick, @@ -523,6 +567,7 @@ end --- - `on_child_added` : emitted when a child is added to the tree. --- - `on_child_removed` : emitted when a child is removed from the tree. function LanguageTree:register_cbs(cbs) + ---@cast cbs table<TSCallbackNameOn,function> if not cbs then return end @@ -549,6 +594,9 @@ function LanguageTree:register_cbs(cbs) end ---@private +---@param tree TSTree +---@param range Range +---@return boolean local function tree_contains(tree, 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]) @@ -559,7 +607,7 @@ end --- 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 function LanguageTree:contains(range) for _, tree in pairs(self._trees) do @@ -573,10 +621,10 @@ end --- 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: --- - ignore_injections boolean Ignore injected languages (default true) ----@return userdata|nil Contained |tstree| +---@return TSTree|nil function LanguageTree:tree_for_range(range, opts) opts = opts or {} local ignore = vim.F.if_nil(opts.ignore_injections, true) @@ -602,10 +650,10 @@ end --- 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: --- - 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) local tree = self:tree_for_range(range, opts) if tree then @@ -615,7 +663,7 @@ end --- 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} function LanguageTree:language_for_range(range) for _, child in pairs(self._children) do diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua index bb073290c6..001bc2d5bf 100644 --- a/runtime/lua/vim/treesitter/playground.lua +++ b/runtime/lua/vim/treesitter/playground.lua @@ -1,12 +1,13 @@ local api = vim.api -local M = {} - ----@class Playground +---@class TSPlayground ---@field ns number API namespace ---@field opts table Options table with the following keys: --- - anon (boolean): If true, display anonymous nodes --- - lang (boolean): If true, display the language alongside each node +---@field nodes Node[] +---@field named Node[] +local TSPlayground = {} --- ---@class Node ---@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_col number Final column number of this node in the source buffer ---@field lang string Source language of this node +---@field root TSNode --- 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} --- 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 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) ---@param tree Node[] Output table containing a list of tables each representing a node in the tree ---@private @@ -48,7 +50,7 @@ local function traverse(node, depth, lang, injections, tree) local type = child:type() local lnum, col, end_lnum, end_col = child:range() local named = child:named() - local text + local text ---@type string if named then if field then text = string.format('%s: (%s)', field, type) @@ -79,14 +81,14 @@ end --- 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 --- ----@return Playground|nil +---@return TSPlayground|nil ---@return string|nil Error message, if any --- ---@private -function M.new(self, bufnr, lang) +function TSPlayground:new(bufnr, lang) local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang) if not ok then 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 root in the child tree to the {injections} table. local root = parser:parse()[1]:root() - local injections = {} + local injections = {} ---@type table<integer,table> parser:for_each_child(function(child, lang_) child:for_each_tree(function(tree) local r = tree:root() @@ -112,7 +114,7 @@ function M.new(self, bufnr, lang) local nodes = traverse(root, 0, parser:lang(), injections, {}) - local named = {} + local named = {} ---@type Node[] for _, v in ipairs(nodes) do if v.named then named[#named + 1] = v @@ -134,30 +136,38 @@ function M.new(self, bufnr, lang) return t end +local decor_ns = api.nvim_create_namespace('ts.playground') + --- Write the contents of this Playground into {bufnr}. --- ---@param bufnr number Buffer number to write into. ---@private -function M.draw(self, bufnr) +function TSPlayground:draw(bufnr) vim.bo[bufnr].modifiable = true - local lines = {} + local lines = {} ---@type string[] for _, item in self:iter() do - lines[#lines + 1] = table.concat({ - string.rep(' ', item.depth), - item.text, - item.lnum == item.end_lnum - and string.format(' [%d:%d-%d]', item.lnum + 1, item.col + 1, item.end_col) - or string.format( - ' [%d:%d-%d:%d]', - item.lnum + 1, - item.col + 1, - item.end_lnum + 1, - item.end_col - ), - self.opts.lang and string.format(' %s', item.lang) or '', - }) + lines[#lines + 1] = string.rep(' ', item.depth) .. item.text end api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + + api.nvim_buf_clear_namespace(bufnr, decor_ns, 0, -1) + + for i, item in self:iter() do + local range_str + if item.lnum == item.end_lnum then + range_str = string.format('[%d:%d-%d]', item.lnum + 1, item.col + 1, item.end_col) + else + range_str = + string.format('[%d:%d-%d:%d]', item.lnum + 1, item.col + 1, item.end_lnum + 1, item.end_col) + end + + local lang_str = self.opts.lang and string.format(' %s', item.lang) or '' + + api.nvim_buf_set_extmark(bufnr, decor_ns, i - 1, 0, { + virt_text = { { range_str, 'Comment' }, { lang_str, 'Title' } }, + }) + end + vim.bo[bufnr].modifiable = false end @@ -168,19 +178,19 @@ end ---@param i number Node number to get ---@return Node ---@private -function M.get(self, i) +function TSPlayground:get(i) local t = self.opts.anon and self.nodes or self.named return t[i] end --- 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 number ---@private -function M.iter(self) +function TSPlayground:iter() return ipairs(self.opts.anon and self.nodes or self.named) end -return M +return TSPlayground diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index dbf134573d..a0522d7cda 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -1,21 +1,25 @@ local a = vim.api local language = require('vim.treesitter.language') --- query: pattern matching on trees --- predicate matching is implemented in lua --- ---@class 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 local Query = {} Query.__index = Query +---@class TSQueryInfo +---@field captures table +---@field patterns table<string,any[][]> + local M = {} ---@private +---@param files string[] +---@return string[] local function dedupe_files(files) local result = {} + ---@type table<string,boolean> local seen = {} for _, path in ipairs(files) do @@ -51,6 +55,38 @@ local function add_included_lang(base_langs, lang, ilang) return false end +---@private +---@param buf (number) +---@param range (table) +---@param concat (boolean) +---@returns (string[]|string|nil) +local function buf_range_get_text(buf, range, concat) + local lines + local start_row, start_col, end_row, end_col = unpack(range) + local eof_row = a.nvim_buf_line_count(buf) + if start_row >= eof_row then + return nil + end + + if end_col == 0 then + lines = a.nvim_buf_get_lines(buf, start_row, end_row, true) + end_col = -1 + else + lines = a.nvim_buf_get_lines(buf, start_row, end_row + 1, true) + end + + if #lines > 0 then + if #lines == 1 then + lines[1] = string.sub(lines[1], start_col + 1, end_col) + else + lines[1] = string.sub(lines[1], start_col + 1) + lines[#lines] = string.sub(lines[#lines], 1, end_col) + end + end + + return concat and table.concat(lines, '\n') or lines +end + --- Gets the list of files used to make up a query --- ---@param lang string Language to get query for @@ -65,10 +101,10 @@ function M.get_query_files(lang, query_name, is_included) return {} end - local base_query = nil + local base_query = nil ---@type string? local extensions = {} - local base_langs = {} + local base_langs = {} ---@type string[] -- Now get the base languages by looking at the first line of every file -- The syntax is the following : @@ -87,6 +123,7 @@ function M.get_query_files(lang, query_name, is_included) local extension = false for modeline in + ---@return string function() return file:read('*l') end @@ -97,6 +134,7 @@ function M.get_query_files(lang, query_name, is_included) local langlist = modeline:match(MODELINE_FORMAT) if langlist then + ---@diagnostic disable-next-line:param-type-mismatch for _, incllang in ipairs(vim.split(langlist, ',', true)) do local is_optional = incllang:match('%(.*%)') @@ -137,6 +175,8 @@ function M.get_query_files(lang, query_name, is_included) end ---@private +---@param filenames string[] +---@return string local function read_query_files(filenames) local contents = {} @@ -147,7 +187,8 @@ local function read_query_files(filenames) return table.concat(contents, '') 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({}, { __index = function(t, k) local lang_queries = {} @@ -174,7 +215,7 @@ end ---@param lang string Language to use for the query ---@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) if explicit_queries[lang][query_name] then return explicit_queries[lang][query_name] @@ -188,6 +229,7 @@ function M.get_query(lang, query_name) end end +---@type {[string]: {[string]: Query}} local query_cache = vim.defaulttable(function() return setmetatable({}, { __mode = 'v' }) end) @@ -226,49 +268,36 @@ end --- 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 opts (table|nil) Optional parameters. --- - concat: (boolean) Concatenate result in a string (default true) ----@return (string[]|string) +--- - metadata (table) Metadata of a specific capture. This would be +--- set to `metadata[capture_id]` when using +--- |vim.treesitter.query.add_directive()|. +---@return (string[]|string|nil) function M.get_node_text(node, source, opts) opts = opts or {} local concat = vim.F.if_nil(opts.concat, true) + local metadata = opts.metadata or {} - local start_row, start_col, start_byte = node:start() - local end_row, end_col, end_byte = node:end_() - - if type(source) == 'number' then - local lines - local eof_row = a.nvim_buf_line_count(source) - if start_row >= eof_row then - return nil - end - - if end_col == 0 then - lines = a.nvim_buf_get_lines(source, start_row, end_row, true) - end_col = -1 - else - lines = a.nvim_buf_get_lines(source, start_row, end_row + 1, true) - end - - if #lines > 0 then - if #lines == 1 then - lines[1] = string.sub(lines[1], start_col + 1, end_col) - else - lines[1] = string.sub(lines[1], start_col + 1) - lines[#lines] = string.sub(lines[#lines], 1, end_col) - end - end - - return concat and table.concat(lines, '\n') or lines + if metadata.text then + return metadata.text + elseif type(source) == 'number' then + return metadata.range and buf_range_get_text(source, metadata.range, concat) + or buf_range_get_text(source, { node:range() }, concat) elseif type(source) == 'string' then - return source:sub(start_byte + 1, end_byte) + return source:sub(select(3, node:start()) + 1, select(3, node:end_())) end end +---@alias TSMatch table<integer,TSNode> + +---@alias TSPredicate fun(match: TSMatch, _, _, predicate: any[]): boolean + -- Predicate handler receive the following arguments -- (match, pattern, bufnr, predicate) +---@type table<string,TSPredicate> local predicate_handlers = { ['eq?'] = function(match, _, source, predicate) local node = match[predicate[2]] @@ -277,13 +306,13 @@ local predicate_handlers = { end local node_text = M.get_node_text(node, source) - local str + local str ---@type string if type(predicate[3]) == 'string' then -- (#eq? @aa "foo") str = predicate[3] else -- (#eq? @aa @bb) - str = M.get_node_text(match[predicate[3]], source) + str = M.get_node_text(match[predicate[3]], source) --[[@as string]] end if node_text ~= str or str == nil then @@ -299,7 +328,7 @@ local predicate_handlers = { return true end 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, ['match?'] = (function() @@ -321,10 +350,12 @@ local predicate_handlers = { }) return function(match, _, source, pred) + ---@cast match TSMatch local node = match[pred[2]] if not node then return true end + ---@diagnostic disable-next-line no-unknown local regex = compiled_vim_regexes[pred[3]] return regex:match_str(M.get_node_text(node, source)) end @@ -335,7 +366,7 @@ local predicate_handlers = { if not node then return true 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 if string.find(node_text, predicate[i], 1, true) then @@ -359,6 +390,7 @@ local predicate_handlers = { if not string_set then string_set = {} for i = 3, #predicate do + ---@diagnostic disable-next-line:no-unknown string_set[predicate[i]] = true end predicate['string_set'] = string_set @@ -371,21 +403,39 @@ local predicate_handlers = { -- As we provide lua-match? also expose vim-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 should always end with a `!`. -- Directive handler receive the following arguments -- (match, pattern, bufnr, predicate, metadata) +---@type table<string,TSDirective> local directive_handlers = { ['set!'] = function(_, _, _, pred, metadata) if #pred == 4 then -- (#set! @capture "key" "value") + ---@diagnostic disable-next-line:no-unknown local _, capture_id, key, value = unpack(pred) + ---@cast value integer|string + ---@cast capture_id integer + ---@cast key string if not metadata[capture_id] then metadata[capture_id] = {} end metadata[capture_id][key] = value else + ---@diagnostic disable-next-line:no-unknown local _, key, value = unpack(pred) + ---@cast value integer|string + ---@cast key string -- (#set! "key" "value") metadata[key] = value end @@ -393,9 +443,13 @@ local directive_handlers = { -- Shifts the range of a node. -- Example: (#offset! @_node 0 1 0 -1) ['offset!'] = function(match, _, _, pred, metadata) + ---@cast pred integer[] local capture_id = pred[2] - local offset_node = match[capture_id] - local range = { offset_node:range() } + if not metadata[capture_id] then + metadata[capture_id] = {} + end + + local range = metadata[capture_id].range or { match[capture_id]:range() } local start_row_offset = pred[3] or 0 local start_col_offset = pred[4] or 0 local end_row_offset = pred[5] or 0 @@ -408,19 +462,32 @@ local directive_handlers = { -- If this produces an invalid range, we just skip it. if range[1] < range[3] or (range[1] == range[3] and range[2] <= range[4]) then - if not metadata[capture_id] then - metadata[capture_id] = {} - end metadata[capture_id].range = range end end, + + -- Transform the content of the node + -- Example: (#gsub! @_node ".*%.(.*)" "%1") + ['gsub!'] = function(match, _, bufnr, pred, metadata) + assert(#pred == 4) + + local id = pred[2] + local node = match[id] + local text = M.get_node_text(node, bufnr, { metadata = metadata[id] }) or '' + + if not metadata[id] then + metadata[id] = {} + end + metadata[id].text = text:gsub(pred[3], pred[4]) + end, } --- Adds a new predicate to be used in queries --- ---@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 +---@param force boolean function M.add_predicate(name, handler, force) if predicate_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -437,12 +504,13 @@ end --- metadata table `metadata[capture_id].key = value` --- ---@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| --- - node-level data are accessible via `match[capture_id]` --- - pattern: see |treesitter-query| --- - predicate: list of strings containing the full directive being called, e.g. --- `(node (#set! conceal "-"))` would get the predicate `{ "#set!", "conceal", "-" }` +---@param force boolean function M.add_directive(name, handler, force) if directive_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -474,6 +542,9 @@ local function is_directive(name) end ---@private +---@param match TSMatch +---@param pattern string +---@param source integer|string function Query:match_preds(match, pattern, source) local preds = self.info.patterns[pattern] @@ -482,8 +553,9 @@ function Query:match_preds(match, pattern, source) -- continue on the other case. This way unknown predicates will not be considered, -- which allows some testing and easier user extensibility (#12173). -- Also, tree-sitter strips the leading # from predicates for us. - local pred_name - local is_not + local pred_name ---@type string + + local is_not ---@type boolean -- Skip over directives... they will get processed after all the predicates. if not is_directive(pred[1]) then @@ -513,6 +585,8 @@ function Query:match_preds(match, pattern, source) end ---@private +---@param match TSMatch +---@param metadata TSMetadata function Query:apply_directives(match, pattern, source, metadata) local preds = self.info.patterns[pattern] @@ -534,6 +608,10 @@ end -- When the node's range is used, the stop is incremented by 1 -- to make the search inclusive. ---@private +---@param start integer +---@param stop integer +---@param node TSNode +---@return integer, integer local function value_or_node_range(start, stop, node) if start == nil and stop == nil then local node_start, _, node_stop, _ = node:range() @@ -565,14 +643,12 @@ end --- end --- </pre> --- ----@param node userdata |tsnode| under which the search will occur ----@param source (number|string) Source buffer or string to extract text from +---@param node TSNode under which the search will occur +---@param source (integer|string) Source buffer or string to extract text from ---@param start number Starting line for the search ---@param stop number Stopping line for the search (end-exclusive) --- ----@return number capture Matching capture id ----@return table capture_node Capture for {node} ----@return table metadata for the {capture} +---@return (fun(): integer, TSNode, TSMetadata): capture id, capture node, metadata function Query:iter_captures(node, source, start, stop) if type(source) == 'number' and source == 0 then source = vim.api.nvim_get_current_buf() @@ -622,14 +698,12 @@ end --- end --- </pre> --- ----@param node userdata |tsnode| under which the search will occur ----@param source (number|string) Source buffer or string to search ----@param start number Starting line for the search ----@param stop number Stopping line for the search (end-exclusive) +---@param node TSNode under which the search will occur +---@param source (integer|string) Source buffer or string to search +---@param start integer Starting line for the search +---@param stop integer Stopping line for the search (end-exclusive) --- ----@return number pattern id ----@return table match ----@return table metadata +---@return (fun(): integer, table<integer,TSNode>, table): pattern id, match, metadata function Query:iter_matches(node, source, start, stop) if type(source) == 'number' and source == 0 then source = vim.api.nvim_get_current_buf() @@ -638,6 +712,7 @@ function Query:iter_matches(node, source, start, stop) start, stop = value_or_node_range(start, stop, node) local raw_iter = node:_rawquery(self.query, false, start, stop) + ---@cast raw_iter fun(): string, any local function iter() local pattern, match = raw_iter() local metadata = {} |