aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.md7
-rw-r--r--runtime/doc/lua.txt10
-rw-r--r--runtime/doc/treesitter.txt563
-rw-r--r--runtime/lua/vim/lsp.lua49
-rw-r--r--runtime/lua/vim/lsp/buf.lua111
-rw-r--r--runtime/lua/vim/lsp/log.lua2
-rw-r--r--runtime/lua/vim/lsp/rpc.lua2
-rw-r--r--runtime/lua/vim/lsp/util.lua3
-rw-r--r--runtime/lua/vim/treesitter.lua38
-rw-r--r--runtime/lua/vim/treesitter/health.lua4
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua22
-rw-r--r--runtime/lua/vim/treesitter/language.lua20
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua160
-rw-r--r--runtime/lua/vim/treesitter/query.lua159
-rwxr-xr-xscripts/gen_vimdoc.py76
-rw-r--r--scripts/lua2dox.lua21
-rwxr-xr-xscripts/pvscheck.sh2
-rw-r--r--src/nvim/api/buffer.c23
-rw-r--r--src/nvim/api/window.c2
-rw-r--r--src/nvim/decoration.c26
-rw-r--r--src/nvim/decoration.h7
-rw-r--r--src/nvim/ex_cmds2.c12
-rw-r--r--src/nvim/lua/vim.lua16
-rw-r--r--src/nvim/main.c2
-rw-r--r--src/nvim/message.c12
-rw-r--r--src/nvim/screen.c106
-rw-r--r--src/nvim/sign.c6
-rw-r--r--src/nvim/syntax.c4
-rw-r--r--test/functional/treesitter/parser_spec.lua50
-rw-r--r--test/functional/ui/decorations_spec.lua119
30 files changed, 1234 insertions, 400 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index fc8bc230fd..ae30f00f31 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -9,11 +9,16 @@ labels: bug
<!-- Before reporting: search existing issues and check the FAQ. -->
- `nvim --version`:
-- `vim -u DEFAULTS` (version: ) behaves differently?
- Operating system/version:
- Terminal name/version:
- `$TERM`:
+<!--
+If this report is about different behaviour between Nvim and Vim, make sure to
+read `:h vim-differences` first. Otherwise remove the next line.
+-->
+[ ] `vim -u DEFAULTS` (version: ) behaves differently
+
### Steps to reproduce using `nvim -u NORC`
```
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 6d007c0e44..be01966d42 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -841,10 +841,18 @@ vim.call({func}, {...}) *vim.call()*
vim.fn[func]({...})
vim.cmd({cmd}) *vim.cmd()*
- Invokes an Ex command (the ":" commands, Vimscript statements).
+ Executes multiple lines of Vimscript at once. It is an alias to
+ |nvim_exec()|, where `output` is set to false. Thus it works identical
+ to |:source|.
See also |ex-cmd-index|.
Example: >
vim.cmd('echo 42')
+ vim.cmd([[
+ augroup My_group
+ autocmd!
+ autocmd FileType c setlocal cindent
+ augroup END
+ ]])
vim.fn.{func}({...}) *vim.fn*
Invokes |vim-function| or |user-function| {func} with arguments {...}.
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index 510585d0dd..1f4b5d3097 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -102,14 +102,14 @@ tsnode:field({name}) *tsnode:field()*
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.
@@ -146,7 +146,7 @@ tsnode:has_error() *tsnode:has_error()*
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 identier for the node inside its own tree.
No guarantees are made about this identifer's internal representation,
@@ -156,16 +156,16 @@ tsnode:id() *tsnode:id()*
NB: the id is not guaranteed to be unique for nodes from different trees.
tsnode:descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
- *tsnode:descendant_for_range()*
+ *tsnode:descendant_for_range()*
Get the smallest node within this node that spans the given range of
(row, column) positions
tsnode:named_descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
- *tsnode:named_descendant_for_range()*
+ *tsnode:named_descendant_for_range()*
Get the smallest named node within this node that spans the given
range of (row, column) positions
-Query methods *lua-treesitter-query*
+Query *lua-treesitter-query*
Tree-sitter queries are supported, with some limitations. Currently, the only
supported match predicate is `eq?` (both comparing a capture against a string
@@ -178,65 +178,6 @@ and predicates. A `capture` allows you to associate names with a specific
node in a pattern. A `predicate` adds arbitrary metadata and conditional data
to a match.
-vim.treesitter.parse_query({lang}, {query})
- *vim.treesitter.parse_query()*
- Parse {query} as a string. (If the query is in a file, the caller
- should read the contents into a string before calling).
-
- Returns a `Query` (see |lua-treesitter-query|) object which can be used to
- search nodes in the syntax tree for the patterns defined in {query}
- using `iter_*` methods below. Exposes `info` and `captures` with
- additional information about the {query}.
- - `captures` contains the list of unique capture names defined in
- {query}.
- -` info.captures` also points to `captures`.
- - `info.patterns` contains information about predicates.
-
-
-query:iter_captures({node}, {bufnr}, {start_row}, {end_row})
- *query:iter_captures()*
- Iterate over all captures from all matches inside {node}.
- {bufnr} is needed if the query contains predicates, then the caller
- must ensure to use a freshly parsed tree consistent with the current
- text of the buffer. {start_row} and {end_row} can be used to limit
- matches inside a row range (this is typically used with root node
- as the node, i e to get syntax highlight matches in the current
- viewport). When omitted the start and end row values are used from
- the given node.
-
- The iterator returns three values, a numeric id identifying the capture,
- the captured node, and metadata from any directives processing the match.
- The following example shows how to get captures by name:
->
- for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do
- local name = query.captures[id] -- name of the capture in the query
- -- typically useful info about the node:
- local type = node:type() -- type of the captured node
- local row1, col1, row2, col2 = node:range() -- range of the capture
- ... use the info here ...
- end
-<
-query:iter_matches({node}, {bufnr}, {start_row}, {end_row})
- *query:iter_matches()*
- Iterate over all matches within a node. The arguments are the same as
- for |query:iter_captures()| but the iterated values are different:
- an (1-based) index of the pattern in the query, a table mapping
- capture indices to nodes, and metadata from any directives processing the match.
- If the query has more than one pattern the capture table might be sparse,
- and e.g. `pairs()` method should be used over `ipairs`.
- Here an example iterating over all captures in every match:
->
- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
- for id, node in pairs(match) do
- local name = query.captures[id]
- -- `node` was captured by the `name` capture in the match
-
- local node_data = metadata[id] -- Node level metadata
-
- ... use the info here ...
- end
- end
-
Treesitter Query Predicates *lua-treesitter-predicates*
When writing queries for treesitter, one might use `predicates`, that is,
@@ -298,28 +239,6 @@ Here is a list of built-in directives:
`({capture_id}, {start_row}, {start_col}, {end_row}, {end_col}, {key?})`
The default key is "offset".
- *vim.treesitter.query.add_predicate()*
-vim.treesitter.query.add_predicate({name}, {handler})
-
-This adds a predicate with the name {name} to be used in queries.
-{handler} should be a function whose signature will be : >
- handler(match, pattern, bufnr, predicate)
-<
- *vim.treesitter.query.list_predicates()*
-vim.treesitter.query.list_predicates()
-
-This lists the currently available predicates to use in queries.
-
- *vim.treesitter.query.add_directive()*
-vim.treesitter.query.add_directive({name}, {handler})
-
-This adds a directive with the name {name} to be used in queries.
-{handler} should be a function whose signature will be : >
- handler(match, pattern, bufnr, predicate, metadata)
-Handlers can set match level data by setting directly on the metadata object `metadata.key = value`
-Handlers can set node level data by using the capture id on the metadata table
-`metadata[capture_id].key = value`
-
Treesitter syntax highlighting (WIP) *lua-treesitter-highlight*
NOTE: This is a partially implemented feature, and not usable as a default
@@ -364,92 +283,434 @@ identical identifiers, highlighting both as |hl-WarningMsg|: >
((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right)
(eq? @WarningMsg.left @WarningMsg.right))
+<
-Treesitter language injection (WIP) *lua-treesitter-language-injection*
+==============================================================================
+Lua module: vim.treesitter *lua-treesitter-core*
-NOTE: This is a partially implemented feature, and not usable as a default
-solution yet. What is documented here is a temporary interface intended
-for those who want to experiment with this feature and contribute to
-its development.
+get_parser({bufnr}, {lang}, {opts}) *get_parser()*
+ Gets the parser for this bufnr / ft combination.
-Languages can have nested languages within them, for example javascript inside
-HTML. We can "inject" a treesitter parser for a child language by configuring
-injection queries. Here is an example of Javascript and CSS injected into
-HTML. >
+ If needed this will create the parser. Unconditionnally attach
+ the provided callback
- local query = [[
- (script_element (raw_text) @javascript)
- (style_element (raw_text) @css)
- ]];
+ Parameters: ~
+ {bufnr} The buffer the parser should be tied to
+ {lang} The filetype of this parser
+ {opts} Options object to pass to the created language
+ tree
- local parser = vim.treesitter.get_parser(nil, nil, {
- injections = {html = query}
- })
+ Return: ~
+ The parser
- parser:parse()
+get_string_parser({str}, {lang}, {opts}) *get_string_parser()*
+ Gets a string parser
-Any capture will be treated as the node treesitter will use for the injected
-language. The capture name will be used as the language. There are a couple
-reserved captures that do not have this behavior
+ Parameters: ~
+ {str} The string to parse
+ {lang} The language of this string
+ {opts} Options to pass to the created language tree
-`@language`
-This will use a nodes text content as the language to be injected.
-`@content`
-This will use the captured nodes content as the injected content.
+==============================================================================
+Lua module: vim.treesitter.language *treesitter-language*
-`@combined`
-This will combine all matches of a pattern as one single block of content.
-By default, each match of a pattern is treated as it's own block of content
-and parsed independent of each other.
+inspect_language({lang}) *inspect_language()*
+ Inspects the provided language.
-`@<language>`
-Any other capture name will be treated as both the language and the content.
+ Inspecting provides some useful informations on the language
+ like node names, ...
-`@_<name>`
-Any capture with a leading "_" will not be treated as a language and will have
-no special processing and is useful for capturing nodes for directives.
+ Parameters: ~
+ {lang} The language.
-Injections can be configured using `directives` instead of using capture
-names. Here is an example of a directive that resolves the language based on a
-buffer variable instead of statically in the query. >
+require_language({lang}, {path}, {silent}) *require_language()*
+ Asserts that the provided language is installed, and
+ optionally provide a path for the parser
- local query = require("vim.treesitter.query")
+ Parsers are searched in the `parser` runtime directory.
- query.add_directive("inject-preprocessor!", function(_, bufnr, _, _, data)
- local success, lang = pcall(vim.api.nvim_buf_get_var, bufnr, "css_preprocessor")
+ Parameters: ~
+ {lang} The language the parser should parse
+ {path} Optional path the parser is located at
+ {silent} Don't throw an error if language not found
- data.language = success and lang or "css"
- end)
-Here is the same HTML query using this directive. >
+==============================================================================
+Lua module: vim.treesitter.query *treesitter-query*
- local query = [[
- (script_element (raw_text) @javascript)
- (style_element
- ((raw_text) @content
- (#inject-preprocessor!)))
- ]];
+add_directive({name}, {handler}, {force}) *add_directive()*
+ Adds a new directive to be used in queries
+
+ Parameters: ~
+ {name} the name of the directive, without leading #
+ {handler} the handler function to be used signature will
+ be (match, pattern, bufnr, predicate)
+
+add_predicate({name}, {handler}, {force}) *add_predicate()*
+ Adds a new predicate to be used in queries
+
+ Parameters: ~
+ {name} the name of the predicate, without leading #
+ {handler} the handler function to be used signature will
+ be (match, pattern, bufnr, predicate)
+
+get_node_text({node}, {source}) *get_node_text()*
+ Gets the text corresponding to a given node
+
+ Parameters: ~
+ {node} the node
+ {bsource} The buffer or string from which the node is
+ extracted
+
+get_query({lang}, {query_name}) *get_query()*
+ Returns the runtime query {query_name} for {lang}.
+
+ Parameters: ~
+ {lang} The language to use for the query
+ {query_name} The name of the query (i.e. "highlights")
+
+ Return: ~
+ The corresponding query, parsed.
+
+ *get_query_files()*
+get_query_files({lang}, {query_name}, {is_included})
+ Gets the list of files used to make up a query
+
+ Parameters: ~
+ {lang} The language
+ {query_name} The name of the query to load
+ {is_included} Internal parameter, most of the time left
+ as `nil`
+
+list_predicates() *list_predicates()*
+ TODO: Documentation
+
+parse_query({lang}, {query}) *parse_query()*
+ Parse {query} as a string. (If the query is in a file, the
+ caller should read the contents into a string before calling).
+
+ Returns a `Query` (see |lua-treesitter-query|) object which
+ can be used to search nodes in the syntax tree for the
+ patterns defined in {query} using `iter_*` methods below.
+
+ Exposes `info` and `captures` with additional information about the {query}.
+ • `captures` contains the list of unique capture names defined
+ in {query}. - `info.captures` also points to `captures` .
+ • `info.patterns` contains information about predicates.
+
+ Parameters: ~
+ {lang} The language
+ {query} A string containing the query (s-expr syntax)
+
+ Return: ~
+ The query
+
+ *Query:iter_captures()*
+Query:iter_captures({self}, {node}, {source}, {start}, {stop})
+ Iterate over all captures from all matches inside {node}
+
+ {source} is needed if the query contains predicates, then the
+ caller must ensure to use a freshly parsed tree consistent
+ with the current text of the buffer (if relevent). {start_row}
+ and {end_row} can be used to limit matches inside a row range
+ (this is typically used with root node as the node, i e to get
+ syntax highlight matches in the current viewport). When
+ omitted the start and end row values are used from the given
+ node.
+
+ The iterator returns three values, a numeric id identifying
+ the capture, the captured node, and metadata from any
+ directives processing the match. The following example shows
+ how to get captures by name:
+>
+
+ for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do
+ local name = query.captures[id] -- name of the capture in the query
+ -- typically useful info about the node:
+ local type = node:type() -- type of the captured node
+ local row1, col1, row2, col2 = node:range() -- range of the capture
+ ... use the info here ...
+ end
+<
+
+ Parameters: ~
+ {node} The node under which the search will occur
+ {source} The source buffer or string to exctract text
+ from
+ {start} The starting line of the search
+ {stop} The stopping line of the search (end-exclusive)
+ {self}
+
+ Return: ~
+ The matching capture id
+ The captured node
+
+ *Query:iter_matches()*
+Query:iter_matches({self}, {node}, {source}, {start}, {stop})
+ Iterates the matches of self on a given range.
+
+ Iterate over all matches within a node. The arguments are the
+ same as for |query:iter_captures()| but the iterated values
+ are different: an (1-based) index of the pattern in the query,
+ a table mapping capture indices to nodes, and metadata from
+ any directives processing the match. If the query has more
+ than one pattern the capture table might be sparse, and e.g.
+ `pairs()` method should be used over `ipairs` . Here an
+ example iterating over all captures in every match:
+>
+
+ for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
+ for id, node in pairs(match) do
+ local name = query.captures[id]
+ -- `node` was captured by the `name` capture in the match
+
+ local node_data = metadata[id] -- Node level metadata
+
+ ... use the info here ...
+ end
+ end
+<
+
+ Parameters: ~
+ {node} The node under which the search will occur
+ {source} The source buffer or string to search
+ {start} The starting line of the search
+ {stop} The stopping line of the search (end-exclusive)
+ {self}
+
+ Return: ~
+ The matching pattern id
+ The matching match
+
+set_query({lang}, {query_name}, {text}) *set_query()*
+ Sets the runtime query {query_name} for {lang}
+
+ This allows users to override any runtime files and/or
+ configuration set by plugins.
+
+ Parameters: ~
+ {lang} string: The language to use for the query
+ {query_name} string: The name of the query (i.e.
+ "highlights")
+ {text} string: The query text (unparsed).
+
+
+==============================================================================
+Lua module: vim.treesitter.highlighter *treesitter-highlighter*
+
+new({tree}, {opts}) *highlighter.new()*
+ Creates a new highlighter using
+
+ Parameters: ~
+ {tree} The language tree to use for highlighting
+ {opts} Table used to configure the highlighter
+ • queries: Table to overwrite queries used by the
+ highlighter
+
+TSHighlighter:destroy({self}) *TSHighlighter:destroy()*
+ Removes all internal references to the highlighter
+
+ Parameters: ~
+ {self}
+
+TSHighlighter:get_query({self}, {lang}) *TSHighlighter:get_query()*
+ Gets the query used for
+
+ Parameters: ~
+ {lang} A language used by the highlighter.
+ {self}
+
+
+==============================================================================
+Lua module: vim.treesitter.languagetree *treesitter-languagetree*
+
+LanguageTree:add_child({self}, {lang}) *LanguageTree:add_child()*
+ Adds a child language to this tree.
+
+ If the language already exists as a child, it will first be
+ removed.
+
+ Parameters: ~
+ {lang} The language to add.
+ {self}
+
+LanguageTree:children({self}) *LanguageTree:children()*
+ Returns a map of language to child tree.
+
+ Parameters: ~
+ {self}
+
+LanguageTree:contains({self}, {range}) *LanguageTree:contains()*
+ Determines wether This goes down the tree to recursively check childs.
+
+ Parameters: ~
+ {range} is contained in this language tree
+
+ Parameters: ~
+ {range} A range, that is a `{ start_line, start_col,
+ end_line, end_col }` table.
+ {self}
+
+LanguageTree:destroy({self}) *LanguageTree:destroy()*
+ Destroys this language tree and all its children.
+
+ Any cleanup logic should be performed here. Note, this DOES
+ NOT remove this tree from a parent. `remove_child` must be called on the parent to remove it.
+
+ Parameters: ~
+ {self}
+
+ *LanguageTree:for_each_child()*
+LanguageTree:for_each_child({self}, {fn}, {include_self})
+ Invokes the callback for each LanguageTree and it's children
+ recursively
+
+ Parameters: ~
+ {fn} The function to invoke. This is invoked
+ with arguments (tree: LanguageTree, lang:
+ string)
+ {include_self} Whether to include the invoking tree in
+ the results.
+ {self}
+
+LanguageTree:for_each_tree({self}, {fn}) *LanguageTree:for_each_tree()*
+ Invokes the callback for each treesitter trees recursively.
+
+ Note, this includes the invoking language tree's trees as
+ well.
+
+ Parameters: ~
+ {fn} The callback to invoke. The callback is invoked
+ with arguments (tree: TSTree, languageTree:
+ LanguageTree)
+ {self}
+
+LanguageTree:included_regions({self}) *LanguageTree:included_regions()*
+ Gets the set of included regions
+
+ Parameters: ~
+ {self}
+
+LanguageTree:invalidate({self}, {reload}) *LanguageTree:invalidate()*
+ Invalidates this parser and all its children
+
+ Parameters: ~
+ {self}
+
+LanguageTree:is_valid({self}) *LanguageTree:is_valid()*
+ Determines whether this tree is valid. If the tree is invalid, `parse()` must be called to get the an updated tree.
+
+ Parameters: ~
+ {self}
+
+LanguageTree:lang({self}) *LanguageTree:lang()*
+ Gets the language of this tree node.
+
+ Parameters: ~
+ {self}
+
+ *LanguageTree:language_for_range()*
+LanguageTree:language_for_range({self}, {range})
+ Gets the appropriate language that contains
+
+ Parameters: ~
+ {range} A text range, see |LanguageTree:contains|
+ {self}
+
+LanguageTree:parse({self}) *LanguageTree:parse()*
+ Parses all defined regions using a treesitter parser for the
+ language this tree represents. This will run the injection
+ query for this language to determine if any child languages
+ should be created.
+
+ Parameters: ~
+ {self}
+
+LanguageTree:register_cbs({self}, {cbs}) *LanguageTree:register_cbs()*
+ Registers callbacks for the parser
+
+ Parameters: ~
+ {cbs} An `nvim_buf_attach` -like table argument with the following keys : `on_bytes` : see `nvim_buf_attach` , but this will be called after the parsers callback. `on_changedtree` : a callback that will be called every time the
+ tree has syntactical changes. it will only be
+ passed one argument, that is a table of the ranges
+ (as node ranges) that changed. `on_child_added` : emitted when a child is added to the tree. `on_child_removed` : emitted when a child is removed from the tree.
+ {self}
+
+LanguageTree:remove_child({self}, {lang}) *LanguageTree:remove_child()*
+ Removes a child language from this tree.
+
+ Parameters: ~
+ {lang} The language to remove.
+ {self}
+
+ *LanguageTree:set_included_regions()*
+LanguageTree:set_included_regions({self}, {regions})
+ Sets the included regions that should be parsed by this
+ parser. A region is a set of nodes and/or ranges that will be
+ parsed in the same context.
+
+ For example, `{ { node1 }, { node2} }` is two separate
+ regions. This will be parsed by the parser in two different
+ contexts... thus resulting in two separate trees.
+
+ `{ { node1, node2 } }` is a single region consisting of two
+ nodes. This will be parsed by the parser in a single
+ context... thus resulting in a single tree.
+
+ This allows for embedded languages to be parsed together
+ across different nodes, which is useful for templating
+ languages like ERB and EJS.
+
+ Note, this call invalidates the tree and requires it to be
+ parsed again.
+
+ Parameters: ~
+ {regions} A list of regions this tree should manage and
+ parse.
+ {self}
+
+LanguageTree:source({self}) *LanguageTree:source()*
+ Returns the source content of the language tree (bufnr or
+ string).
+
+ Parameters: ~
+ {self}
+
+LanguageTree:trees({self}) *LanguageTree:trees()*
+ Returns all trees this language tree contains. Does not
+ include child languages.
+
+ Parameters: ~
+ {self}
+
+new({source}, {lang}, {opts}) *languagetree.new()*
+ Represents a single treesitter parser for a language. The
+ language can contain child languages with in its range, hence
+ the tree.
- local parser = vim.treesitter.get_parser(nil, nil, {
- injections = {html = query}
- })
+ Parameters: ~
+ {source} Can be a bufnr or a string of text to
+ parse
+ {lang} The language this tree represents
+ {opts} Options table
+ {opts.injections} A table of language to injection query
+ strings. This is useful for overriding
+ the built-in runtime file searching for
+ the injection language query per
+ language.
- parser:parse()
-The following properties can be attached to the metadata object provided to
-the directive.
+==============================================================================
+Lua module: vim.treesitter.health *treesitter-health*
-`language`
-Same as the language capture.
+check_health() *check_health()*
+ TODO: Documentation
-`content`
-A list of ranges or nodes to inject as content. These ranges and/or nodes will
-be treated as combined source and will be parsed within the same context. This
-differs from the `@content` capture which only captures a single node as
-content. This can also be a single number that references a captured node.
+list_parsers() *list_parsers()*
+ Lists the parsers currently installed
-`combined`
-Same as the combined capture.
+ Return: ~
+ A list of parsers
vim:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 26700288af..93ec9ed624 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -483,6 +483,13 @@ end
--- result. You can use this with `client.cancel_request(request_id)`
--- to cancel the request.
---
+--- - request_sync(method, params, timeout_ms, bufnr)
+--- Sends a request to the server and synchronously waits for the response.
+--- This is a wrapper around {client.request}
+--- Returns: { err=err, result=result }, a dictionary, where `err` and `result` come from
+--- the |lsp-handler|. On timeout, cancel or error, returns `(nil, err)` where `err` is a
+--- string describing the failure reason. If the request was unsuccessful returns `nil`.
+---
--- - notify(method, params)
--- Sends a notification to an LSP server.
--- Returns: a boolean to indicate if the notification was successful. If
@@ -891,6 +898,42 @@ function lsp.start_client(config)
end
--@private
+ --- Sends a request to the server and synchronously waits for the response.
+ ---
+ --- This is a wrapper around {client.request}
+ ---
+ --@param method (string) LSP method name.
+ --@param params (table) LSP request params.
+ --@param timeout_ms (number, optional, default=1000) Maximum time in
+ ---milliseconds to wait for a result.
+ --@param bufnr (number) Buffer handle (0 for current).
+ --@returns { err=err, result=result }, a dictionary, where `err` and `result` come from the |lsp-handler|.
+ ---On timeout, cancel or error, returns `(nil, err)` where `err` is a
+ ---string describing the failure reason. If the request was unsuccessful
+ ---returns `nil`.
+ --@see |vim.lsp.buf_request_sync()|
+ function client.request_sync(method, params, timeout_ms, bufnr)
+ local request_result = nil
+ local function _sync_handler(err, _, result)
+ request_result = { err = err, result = result }
+ end
+
+ local success, request_id = client.request(method, params, _sync_handler,
+ bufnr)
+ if not success then return nil end
+
+ local wait_result, reason = vim.wait(timeout_ms or 1000, function()
+ return request_result ~= nil
+ end, 10)
+
+ if not wait_result then
+ client.cancel_request(request_id)
+ return nil, wait_result_reason[reason]
+ end
+ return request_result
+ end
+
+ --@private
--- Sends a notification to an LSP server.
---
--@param method (string) LSP method name.
@@ -1289,12 +1332,12 @@ end
---
--- Calls |vim.lsp.buf_request_all()| but blocks Nvim while awaiting the result.
--- Parameters are the same as |vim.lsp.buf_request()| but the return result is
---- different. Wait maximum of {timeout_ms} (default 100) ms.
+--- different. Wait maximum of {timeout_ms} (default 1000) ms.
---
--@param bufnr (number) Buffer handle, or 0 for current.
--@param method (string) LSP method name
--@param params (optional, table) Parameters to send to the server
---@param timeout_ms (optional, number, default=100) Maximum time in
+--@param timeout_ms (optional, number, default=1000) Maximum time in
--- milliseconds to wait for a result.
---
--@returns Map of client_id:request_result. On timeout, cancel or error,
@@ -1307,7 +1350,7 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
request_results = it
end)
- local wait_result, reason = vim.wait(timeout_ms or 100, function()
+ local wait_result, reason = vim.wait(timeout_ms or 1000, function()
return request_results ~= nil
end, 10)
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 31116985e2..341a3e82fc 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -111,6 +111,39 @@ function M.completion(context)
return request('textDocument/completion', params)
end
+--@private
+--- If there is more than one client that supports the given method,
+--- asks the user to select one.
+--
+--@returns The client that the user selected or nil
+local function select_client(method)
+ local clients = vim.tbl_values(vim.lsp.buf_get_clients());
+ clients = vim.tbl_filter(function (client)
+ return client.supports_method(method)
+ end, clients)
+ -- better UX when choices are always in the same order (between restarts)
+ table.sort(clients, function (a, b) return a.name < b.name end)
+
+ if #clients > 1 then
+ local choices = {}
+ for k,v in ipairs(clients) do
+ table.insert(choices, string.format("%d %s", k, v.name))
+ end
+ local user_choice = vim.fn.confirm(
+ "Select a language server:",
+ table.concat(choices, "\n"),
+ 0,
+ "Question"
+ )
+ if user_choice == 0 then return nil end
+ return clients[user_choice]
+ elseif #clients < 1 then
+ return nil
+ else
+ return clients[1]
+ end
+end
+
--- Formats the current buffer.
---
--@param options (optional, table) Can be used to specify FormattingOptions.
@@ -119,8 +152,11 @@ end
--
--@see https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting
function M.formatting(options)
+ local client = select_client("textDocument/formatting")
+ if client == nil then return end
+
local params = util.make_formatting_params(options)
- return request('textDocument/formatting', params)
+ return client.request("textDocument/formatting", params)
end
--- Performs |vim.lsp.buf.formatting()| synchronously.
@@ -134,14 +170,62 @@ end
---
--@param options Table with valid `FormattingOptions` entries
--@param timeout_ms (number) Request timeout
+--@see |vim.lsp.buf.formatting_seq_sync|
function M.formatting_sync(options, timeout_ms)
+ local client = select_client("textDocument/formatting")
+ if client == nil then return end
+
local params = util.make_formatting_params(options)
- local result = vim.lsp.buf_request_sync(0, "textDocument/formatting", params, timeout_ms)
- if not result or vim.tbl_isempty(result) then return end
- local _, formatting_result = next(result)
- result = formatting_result.result
- if not result then return end
- vim.lsp.util.apply_text_edits(result)
+ local result, err = client.request_sync("textDocument/formatting", params, timeout_ms)
+ if result and result.result then
+ util.apply_text_edits(result.result)
+ elseif err then
+ vim.notify("vim.lsp.buf.formatting_sync: " .. err, vim.log.levels.WARN)
+ end
+end
+
+--- Formats the current buffer by sequentially requesting formatting from attached clients.
+---
+--- Useful when multiple clients with formatting capability are attached.
+---
+--- Since it's synchronous, can be used for running on save, to make sure buffer is formatted
+--- prior to being saved. {timeout_ms} is passed on to the |vim.lsp.client| `request_sync` method.
+--- Example:
+--- <pre>
+--- vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_seq_sync()]]
+--- </pre>
+---
+--@param options (optional, table) `FormattingOptions` entries
+--@param timeout_ms (optional, number) Request timeout
+--@param order (optional, table) List of client names. Formatting is requested from clients
+---in the following order: first all clients that are not in the `order` list, then
+---the remaining clients in the order as they occur in the `order` list.
+function M.formatting_seq_sync(options, timeout_ms, order)
+ local clients = vim.tbl_values(vim.lsp.buf_get_clients());
+
+ -- sort the clients according to `order`
+ for _, client_name in ipairs(order or {}) do
+ -- if the client exists, move to the end of the list
+ for i, client in ipairs(clients) do
+ if client.name == client_name then
+ table.insert(clients, table.remove(clients, i))
+ break
+ end
+ end
+ end
+
+ -- loop through the clients and make synchronous formatting requests
+ for _, client in ipairs(clients) do
+ if client.resolved_capabilities.document_formatting then
+ local params = util.make_formatting_params(options)
+ local result, err = client.request_sync("textDocument/formatting", params, timeout_ms)
+ if result and result.result then
+ util.apply_text_edits(result.result)
+ elseif err then
+ vim.notify(string.format("vim.lsp.buf.formatting_seq_sync: (%s) %s", client.name, err), vim.log.levels.WARN)
+ end
+ end
+ end
end
--- Formats a given range.
@@ -152,15 +236,12 @@ end
--@param end_pos ({number, number}, optional) mark-indexed position.
---Defaults to the end of the last visual selection.
function M.range_formatting(options, start_pos, end_pos)
- validate { options = {options, 't', true} }
- local sts = vim.bo.softtabstop;
- options = vim.tbl_extend('keep', options or {}, {
- tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop;
- insertSpaces = vim.bo.expandtab;
- })
+ local client = select_client("textDocument/rangeFormatting")
+ if client == nil then return end
+
local params = util.make_given_range_params(start_pos, end_pos)
- params.options = options
- return request('textDocument/rangeFormatting', params)
+ params.options = util.make_formatting_params(options).options
+ return client.request("textDocument/rangeFormatting", params)
end
--- Renames all references to the symbol under the cursor.
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
index 331e980e67..471a311c16 100644
--- a/runtime/lua/vim/lsp/log.lua
+++ b/runtime/lua/vim/lsp/log.lua
@@ -10,7 +10,7 @@ local log = {}
-- Can be used to lookup the number from the name or the name from the number.
-- Levels by name: 'trace', 'debug', 'info', 'warn', 'error'
-- Level numbers begin with 'trace' at 0
-log.levels = vim.log.levels
+log.levels = vim.deepcopy(vim.log.levels)
-- Default log level is warn.
local current_log_level = log.levels.WARN
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index 1aa8326514..0cabd1a0d4 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -518,7 +518,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
send_response(decoded.id, err, result)
end)
-- This works because we are expecting vim.NIL here
- elseif decoded.id and (decoded.result or decoded.error) then
+ elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then
-- Server Result
decoded.error = convert_NIL(decoded.error)
decoded.result = convert_NIL(decoded.result)
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 92ec447b55..ce8468aa8a 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -944,6 +944,9 @@ function M.jump_to_location(location)
local from = {from_bufnr, vim.fn.line('.'), vim.fn.col('.'), 0}
local item = {tagname=vim.fn.expand('<cword>'), from=from}
+ -- Save position in jumplist
+ vim.cmd("mark '")
+
--- Jump to new location (adjusting for UTF-16 encoding of characters)
local bufnr = vim.uri_to_bufnr(uri)
api.nvim_set_current_buf(bufnr)
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index f223c7b8c8..de997b2d86 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -25,12 +25,12 @@ setmetatable(M, {
})
--- Creates a new parser.
---
--- It is not recommended to use this, use vim.treesitter.get_parser() instead.
---
--- @param bufnr The buffer the parser will be tied to
--- @param lang The language of the parser
--- @param opts Options to pass to the language tree
+---
+--- It is not recommended to use this, use vim.treesitter.get_parser() instead.
+---
+--- @param bufnr The buffer the parser will be tied to
+--- @param lang The language of the parser
+--- @param opts Options to pass to the created language tree
function M._create_parser(bufnr, lang, opts)
language.require_language(lang)
if bufnr == 0 then
@@ -41,10 +41,12 @@ function M._create_parser(bufnr, lang, opts)
local self = LanguageTree.new(bufnr, lang, opts)
+ ---@private
local function bytes_cb(_, ...)
self:_on_bytes(...)
end
+ ---@private
local function detach_cb(_, ...)
if parsers[bufnr] == self then
parsers[bufnr] = nil
@@ -52,6 +54,7 @@ function M._create_parser(bufnr, lang, opts)
self:_on_detach(...)
end
+ ---@private
local function reload_cb(_, ...)
self:_on_reload(...)
end
@@ -64,15 +67,15 @@ function M._create_parser(bufnr, lang, opts)
end
--- Gets the parser for this bufnr / ft combination.
---
--- If needed this will create the parser.
--- Unconditionnally attach the provided callback
---
--- @param bufnr The buffer the parser should be tied to
--- @param ft The filetype of this parser
--- @param opts Options object to pass to the parser
---
--- @returns The parser
+---
+--- If needed this will create the parser.
+--- Unconditionnally attach the provided callback
+---
+--- @param bufnr The buffer the parser should be tied to
+--- @param lang The filetype of this parser
+--- @param opts Options object to pass to the created language tree
+---
+--- @returns The parser
function M.get_parser(bufnr, lang, opts)
opts = opts or {}
@@ -92,6 +95,11 @@ function M.get_parser(bufnr, lang, opts)
return parsers[bufnr]
end
+--- Gets a string parser
+---
+--- @param str The string to parse
+--- @param lang The language of this string
+--- @param opts Options to pass to the created language tree
function M.get_string_parser(str, lang, opts)
vim.validate {
str = { str, 'string' },
diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua
index dd0b11a6c7..e031ba1bd6 100644
--- a/runtime/lua/vim/treesitter/health.lua
+++ b/runtime/lua/vim/treesitter/health.lua
@@ -1,10 +1,14 @@
local M = {}
local ts = vim.treesitter
+--- Lists the parsers currently installed
+---
+---@return A list of parsers
function M.list_parsers()
return vim.api.nvim_get_runtime_file('parser/*', true)
end
+--- Performs a healthcheck for treesitter integration
function M.check_health()
local report_info = vim.fn['health#report_info']
local report_ok = vim.fn['health#report_ok']
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index fe7e1052c9..84b6a5f135 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -70,11 +70,13 @@ TSHighlighter.hl_map = {
["include"] = "Include",
}
+---@private
local function is_highlight_name(capture_name)
local firstc = string.sub(capture_name, 1, 1)
return firstc ~= string.lower(firstc)
end
+---@private
function TSHighlighterQuery.new(lang, query_string)
local self = setmetatable({}, { __index = TSHighlighterQuery })
@@ -99,10 +101,12 @@ function TSHighlighterQuery.new(lang, query_string)
return self
end
+---@private
function TSHighlighterQuery:query()
return self._query
end
+---@private
--- Get the hl from capture.
--- Returns a tuple { highlight_name: string, is_builtin: bool }
function TSHighlighterQuery:_get_hl_from_capture(capture)
@@ -116,6 +120,11 @@ function TSHighlighterQuery:_get_hl_from_capture(capture)
end
end
+--- Creates a new highlighter using @param tree
+---
+--- @param tree The language tree to use for highlighting
+--- @param opts Table used to configure the highlighter
+--- - queries: Table to overwrite queries used by the highlighter
function TSHighlighter.new(tree, opts)
local self = setmetatable({}, TSHighlighter)
@@ -165,12 +174,14 @@ function TSHighlighter.new(tree, opts)
return self
end
+--- Removes all internal references to the highlighter
function TSHighlighter:destroy()
if TSHighlighter.active[self.bufnr] then
TSHighlighter.active[self.bufnr] = nil
end
end
+---@private
function TSHighlighter:get_highlight_state(tstree)
if not self._highlight_states[tstree] then
self._highlight_states[tstree] = {
@@ -182,24 +193,31 @@ function TSHighlighter:get_highlight_state(tstree)
return self._highlight_states[tstree]
end
+---@private
function TSHighlighter:reset_highlight_state()
self._highlight_states = {}
end
+---@private
function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end)
a.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1)
end
+---@private
function TSHighlighter:on_detach()
self:destroy()
end
+---@private
function TSHighlighter:on_changedtree(changes)
for _, ch in ipairs(changes or {}) do
a.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3]+1)
end
end
+--- Gets the query used for @param lang
+---
+--- @param lang A language used by the highlighter.
function TSHighlighter:get_query(lang)
if not self._queries[lang] then
self._queries[lang] = TSHighlighterQuery.new(lang)
@@ -208,6 +226,7 @@ function TSHighlighter:get_query(lang)
return self._queries[lang]
end
+---@private
local function on_line_impl(self, buf, line)
self.tree:for_each_tree(function(tstree, tree)
if not tstree then return end
@@ -251,6 +270,7 @@ local function on_line_impl(self, buf, line)
end, true)
end
+---@private
function TSHighlighter._on_line(_, _win, buf, line, _)
local self = TSHighlighter.active[buf]
if not self then return end
@@ -258,6 +278,7 @@ function TSHighlighter._on_line(_, _win, buf, line, _)
on_line_impl(self, buf, line)
end
+---@private
function TSHighlighter._on_buf(_, buf)
local self = TSHighlighter.active[buf]
if self then
@@ -265,6 +286,7 @@ function TSHighlighter._on_buf(_, buf)
end
end
+---@private
function TSHighlighter._on_win(_, _win, buf, _topline)
local self = TSHighlighter.active[buf]
if not self then
diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua
index eed28e0e41..6dc37c7848 100644
--- a/runtime/lua/vim/treesitter/language.lua
+++ b/runtime/lua/vim/treesitter/language.lua
@@ -3,12 +3,12 @@ local a = vim.api
local M = {}
--- Asserts that the provided language is installed, and optionally provide a path for the parser
---
--- Parsers are searched in the `parser` runtime directory.
---
--- @param lang The language the parser should parse
--- @param path Optional path the parser is located at
--- @param silent Don't throw an error if language not found
+---
+--- Parsers are searched in the `parser` runtime directory.
+---
+--- @param lang The language the parser should parse
+--- @param path Optional path the parser is located at
+--- @param silent Don't throw an error if language not found
function M.require_language(lang, path, silent)
if vim._ts_has_language(lang) then
return true
@@ -37,10 +37,10 @@ function M.require_language(lang, path, silent)
end
--- Inspects the provided language.
---
--- Inspecting provides some useful informations on the language like node names, ...
---
--- @param lang The language.
+---
+--- Inspecting provides some useful informations on the language like node names, ...
+---
+--- @param lang The language.
function M.inspect_language(lang)
M.require_language(lang)
return vim._ts_inspect_language(lang)
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 2f5aeb0710..899d90e464 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -5,16 +5,16 @@ local language = require'vim.treesitter.language'
local LanguageTree = {}
LanguageTree.__index = LanguageTree
--- Represents a single treesitter parser for a language.
--- The language can contain child languages with in its range,
--- hence the tree.
---
--- @param source Can be a bufnr or a string of text to parse
--- @param lang The language this tree represents
--- @param opts Options table
--- @param opts.injections A table of language to injection query strings.
--- This is useful for overriding the built-in runtime file
--- searching for the injection language query per language.
+--- Represents a single treesitter parser for a language.
+--- The language can contain child languages with in its range,
+--- hence the tree.
+---
+--- @param source Can be a bufnr or a string of text to parse
+--- @param lang The language this tree represents
+--- @param opts Options table
+--- @param opts.injections A table of language to injection query strings.
+--- This is useful for overriding the built-in runtime file
+--- searching for the injection language query per language.
function LanguageTree.new(source, lang, opts)
language.require_language(lang)
opts = opts or {}
@@ -50,7 +50,7 @@ function LanguageTree.new(source, lang, opts)
return self
end
--- Invalidates this parser and all its children
+--- Invalidates this parser and all its children
function LanguageTree:invalidate(reload)
self._valid = false
@@ -64,38 +64,38 @@ function LanguageTree:invalidate(reload)
end
end
--- Returns all trees this language tree contains.
--- Does not include child languages.
+--- Returns all trees this language tree contains.
+--- Does not include child languages.
function LanguageTree:trees()
return self._trees
end
--- Gets the language of this tree layer.
+--- Gets the language of this tree node.
function LanguageTree:lang()
return self._lang
end
--- Determines whether this tree is valid.
--- If the tree is invalid, `parse()` must be called
--- to get the an updated tree.
+--- Determines whether this tree is valid.
+--- If the tree is invalid, `parse()` must be called
+--- to get the an updated tree.
function LanguageTree:is_valid()
return self._valid
end
--- Returns a map of language to child tree.
+--- Returns a map of language to child tree.
function LanguageTree:children()
return self._children
end
--- Returns the source content of the language tree (bufnr or string).
+--- Returns the source content of the language tree (bufnr or string).
function LanguageTree:source()
return self._source
end
--- Parses all defined regions using a treesitter parser
--- for the language this tree represents.
--- This will run the injection query for this language to
--- determine if any child languages should be created.
+--- Parses all defined regions using a treesitter parser
+--- for the language this tree represents.
+--- This will run the injection query for this language to
+--- determine if any child languages should be created.
function LanguageTree:parse()
if self._valid then
return self._trees
@@ -169,9 +169,10 @@ function LanguageTree:parse()
return self._trees, changes
end
--- Invokes the callback for each LanguageTree and it's children recursively
--- @param fn The function to invoke. This is invoked with arguments (tree: LanguageTree, lang: string)
--- @param include_self Whether to include the invoking tree in the results.
+--- Invokes the callback for each LanguageTree and it's children recursively
+---
+--- @param fn The function to invoke. This is invoked with arguments (tree: LanguageTree, lang: string)
+--- @param include_self 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)
@@ -182,10 +183,12 @@ function LanguageTree:for_each_child(fn, include_self)
end
end
--- Invokes the callback for each treesitter trees recursively.
--- Note, this includes the invoking language tree's trees as well.
--- @param fn The callback to invoke. The callback is invoked with arguments
--- (tree: TSTree, languageTree: LanguageTree)
+--- Invokes the callback for each treesitter trees recursively.
+---
+--- Note, this includes the invoking language tree's trees as well.
+---
+--- @param fn The callback to invoke. The callback is invoked with arguments
+--- (tree: TSTree, languageTree: LanguageTree)
function LanguageTree:for_each_tree(fn)
for _, tree in ipairs(self._trees) do
fn(tree, self)
@@ -196,9 +199,11 @@ function LanguageTree:for_each_tree(fn)
end
end
--- Adds a child language to this tree.
--- If the language already exists as a child, it will first be removed.
--- @param lang The language to add.
+--- Adds a child language to this tree.
+---
+--- If the language already exists as a child, it will first be removed.
+---
+--- @param lang The language to add.
function LanguageTree:add_child(lang)
if self._children[lang] then
self:remove_child(lang)
@@ -212,8 +217,9 @@ function LanguageTree:add_child(lang)
return self._children[lang]
end
--- Removes a child language from this tree.
--- @param lang The language to remove.
+--- Removes a child language from this tree.
+---
+--- @param lang The language to remove.
function LanguageTree:remove_child(lang)
local child = self._children[lang]
@@ -225,10 +231,11 @@ function LanguageTree:remove_child(lang)
end
end
--- Destroys this language tree and all its children.
--- Any cleanup logic should be performed here.
--- Note, this DOES NOT remove this tree from a parent.
--- `remove_child` must be called on the parent to remove it.
+--- Destroys this language tree and all its children.
+---
+--- Any cleanup logic should be performed here.
+--- Note, this DOES NOT remove this tree from a parent.
+--- `remove_child` must be called on the parent to remove it.
function LanguageTree:destroy()
-- Cleanup here
for _, child in ipairs(self._children) do
@@ -236,23 +243,23 @@ function LanguageTree:destroy()
end
end
--- Sets the included regions that should be parsed by this parser.
--- A region is a set of nodes and/or ranges that will be parsed in the same context.
---
--- For example, `{ { node1 }, { node2} }` is two separate regions.
--- This will be parsed by the parser in two different contexts... thus resulting
--- in two separate trees.
---
--- `{ { node1, node2 } }` is a single region consisting of two nodes.
--- This will be parsed by the parser in a single context... thus resulting
--- in a single tree.
---
--- This allows for embedded languages to be parsed together across different
--- nodes, which is useful for templating languages like ERB and EJS.
---
--- Note, this call invalidates the tree and requires it to be parsed again.
---
--- @param regions A list of regions this tree should manage and parse.
+--- Sets the included regions that should be parsed by this parser.
+--- A region is a set of nodes and/or ranges that will be parsed in the same context.
+---
+--- For example, `{ { node1 }, { node2} }` is two separate regions.
+--- This will be parsed by the parser in two different contexts... thus resulting
+--- in two separate trees.
+---
+--- `{ { node1, node2 } }` is a single region consisting of two nodes.
+--- This will be parsed by the parser in a single context... thus resulting
+--- in a single tree.
+---
+--- This allows for embedded languages to be parsed together across different
+--- nodes, which is useful for templating languages like ERB and EJS.
+---
+--- Note, this call invalidates the tree and requires it to be parsed again.
+---
+--- @param regions A list of regions this tree should manage and parse.
function LanguageTree:set_included_regions(regions)
-- TODO(vigoux): I don't think string parsers are useful for now
if type(self._source) == "number" then
@@ -281,16 +288,18 @@ function LanguageTree:set_included_regions(regions)
self:invalidate()
end
--- Gets the set of included regions
+--- Gets the set of included regions
function LanguageTree:included_regions()
return self._regions
end
--- Gets language injection points by language.
--- This is where most of the injection processing occurs.
--- TODO: Allow for an offset predicate to tailor the injection range
--- instead of using the entire nodes range.
--- @private
+--- Gets language injection points by language.
+---
+--- This is where most of the injection processing occurs.
+---
+--- TODO: Allow for an offset predicate to tailor the injection range
+--- instead of using the entire nodes range.
+--- @private
function LanguageTree:_get_injections()
if not self._injection_query then return {} end
@@ -395,12 +404,14 @@ function LanguageTree:_get_injections()
return result
end
+---@private
function LanguageTree:_do_callback(cb_name, ...)
for _, cb in ipairs(self._callbacks[cb_name]) do
cb(...)
end
end
+---@private
function LanguageTree:_on_bytes(bufnr, changed_tick,
start_row, start_col, start_byte,
old_row, old_col, old_byte,
@@ -425,24 +436,26 @@ function LanguageTree:_on_bytes(bufnr, changed_tick,
new_row, new_col, new_byte)
end
+---@private
function LanguageTree:_on_reload()
self:invalidate(true)
end
+---@private
function LanguageTree:_on_detach(...)
self:invalidate(true)
self:_do_callback('detach', ...)
end
--- Registers callbacks for the parser
--- @param cbs An `nvim_buf_attach`-like table argument with the following keys :
--- `on_bytes` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback.
--- `on_changedtree` : a callback that will be called every time the tree has syntactical changes.
--- it will only be passed one argument, that is a table of the ranges (as node ranges) that
--- changed.
--- `on_child_added` : emitted when a child is added to the tree.
--- `on_child_removed` : emitted when a child is removed from the tree.
+--- @param cbs An `nvim_buf_attach`-like table argument with the following keys :
+--- `on_bytes` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback.
+--- `on_changedtree` : a callback that will be called every time the tree has syntactical changes.
+--- it will only be passed one argument, that is a table of the ranges (as node ranges) that
+--- changed.
+--- `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)
if not cbs then return end
@@ -467,6 +480,7 @@ function LanguageTree:register_cbs(cbs)
end
end
+---@private
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])
@@ -479,6 +493,11 @@ local function tree_contains(tree, range)
return false
end
+--- Determines wether @param range is contained in this language tree
+---
+--- This goes down the tree to recursively check childs.
+---
+--- @param range A range, that is a `{ start_line, start_col, end_line, end_col }` table.
function LanguageTree:contains(range)
for _, tree in pairs(self._trees) do
if tree_contains(tree, range) then
@@ -489,6 +508,9 @@ function LanguageTree:contains(range)
return false
end
+--- Gets the appropriate language that contains @param range
+---
+--- @param range A text range, see |LanguageTree:contains|
function LanguageTree:language_for_range(range)
for _, child in pairs(self._children) do
if child:contains(range) then
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index ed5146be44..9b4d28e09a 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -8,6 +8,7 @@ Query.__index = Query
local M = {}
+---@private
local function dedupe_files(files)
local result = {}
local seen = {}
@@ -22,6 +23,7 @@ local function dedupe_files(files)
return result
end
+---@private
local function safe_read(filename, read_quantifier)
local file, err = io.open(filename, 'r')
if not file then
@@ -32,6 +34,11 @@ local function safe_read(filename, read_quantifier)
return content
end
+--- Gets the list of files used to make up a query
+---
+--- @param lang The language
+--- @param query_name The name of the query to load
+--- @param is_included Internal parameter, most of the time left as `nil`
function M.get_query_files(lang, query_name, is_included)
local query_path = string.format('queries/%s/%s.scm', lang, query_name)
local lang_files = dedupe_files(a.nvim_get_runtime_file(query_path, true))
@@ -79,6 +86,7 @@ function M.get_query_files(lang, query_name, is_included)
return query_files
end
+---@private
local function read_query_files(filenames)
local contents = {}
@@ -103,19 +111,20 @@ local explicit_queries = setmetatable({}, {
---
--- This allows users to override any runtime files and/or configuration
--- set by plugins.
----@param lang string: The language to use for the query
----@param query_name string: The name of the query (i.e. "highlights")
----@param text string: The query text (unparsed).
+---
+--- @param lang string: The language to use for the query
+--- @param query_name string: The name of the query (i.e. "highlights")
+--- @param text string: The query text (unparsed).
function M.set_query(lang, query_name, text)
explicit_queries[lang][query_name] = M.parse_query(lang, text)
end
--- Returns the runtime query {query_name} for {lang}.
---
--- @param lang The language to use for the query
--- @param query_name The name of the query (i.e. "highlights")
---
--- @return The corresponding query, parsed.
+---
+--- @param lang The language to use for the query
+--- @param query_name The name of the query (i.e. "highlights")
+---
+--- @return The corresponding query, parsed.
function M.get_query(lang, query_name)
if explicit_queries[lang][query_name] then
return explicit_queries[lang][query_name]
@@ -129,12 +138,23 @@ function M.get_query(lang, query_name)
end
end
---- Parses a query.
---
--- @param language The language
--- @param query A string containing the query (s-expr syntax)
---
--- @returns The query
+--- Parse {query} as a string. (If the query is in a file, the caller
+--- should read the contents into a string before calling).
+---
+--- Returns a `Query` (see |lua-treesitter-query|) object which can be used to
+--- search nodes in the syntax tree for the patterns defined in {query}
+--- using `iter_*` methods below.
+---
+--- Exposes `info` and `captures` with additional information about the {query}.
+--- - `captures` contains the list of unique capture names defined in
+--- {query}.
+--- -` info.captures` also points to `captures`.
+--- - `info.patterns` contains information about predicates.
+---
+--- @param lang The language
+--- @param query A string containing the query (s-expr syntax)
+---
+--- @returns The query
function M.parse_query(lang, query)
language.require_language(lang)
local self = setmetatable({}, Query)
@@ -147,8 +167,9 @@ end
-- TODO(vigoux): support multiline nodes too
--- Gets the text corresponding to a given node
--- @param node the node
--- @param bufnr the buffer from which the node is extracted.
+---
+--- @param node the node
+--- @param bsource The buffer or string from which the node is extracted
function M.get_node_text(node, source)
local start_row, start_col, start_byte = node:start()
local end_row, end_col, end_byte = node:end_()
@@ -200,6 +221,7 @@ local predicate_handlers = {
["match?"] = (function()
local magic_prefixes = {['\\v']=true, ['\\m']=true, ['\\M']=true, ['\\V']=true}
+ ---@private
local function check_magic(str)
if string.len(str) < 2 or magic_prefixes[string.sub(str,1,2)] then
return str
@@ -253,7 +275,11 @@ local directive_handlers = {
["set!"] = function(_, _, _, pred, metadata)
if #pred == 4 then
-- (#set! @capture "key" "value")
- metadata[pred[2]][pred[3]] = pred[4]
+ local capture = pred[2]
+ if not metadata[capture] then
+ metadata[capture] = {}
+ end
+ metadata[capture][pred[3]] = pred[4]
else
-- (#set! "key" "value")
metadata[pred[2]] = pred[3]
@@ -282,10 +308,10 @@ local directive_handlers = {
}
--- Adds a new predicate to be used in queries
---
--- @param name the name of the predicate, without leading #
--- @param handler the handler function to be used
--- signature will be (match, pattern, bufnr, predicate)
+---
+--- @param name the name of the predicate, without leading #
+--- @param handler the handler function to be used
+--- signature will be (match, pattern, bufnr, predicate)
function M.add_predicate(name, handler, force)
if predicate_handlers[name] and not force then
error(string.format("Overriding %s", name))
@@ -295,10 +321,10 @@ function M.add_predicate(name, handler, force)
end
--- Adds a new directive to be used in queries
---
--- @param name the name of the directive, without leading #
--- @param handler the handler function to be used
--- signature will be (match, pattern, bufnr, predicate)
+---
+--- @param name the name of the directive, without leading #
+--- @param handler the handler function to be used
+--- signature will be (match, pattern, bufnr, predicate)
function M.add_directive(name, handler, force)
if directive_handlers[name] and not force then
error(string.format("Overriding %s", name))
@@ -312,14 +338,17 @@ function M.list_predicates()
return vim.tbl_keys(predicate_handlers)
end
+---@private
local function xor(x, y)
return (x or y) and not (x and y)
end
+---@private
local function is_directive(name)
return string.sub(name, -1) == "!"
end
+---@private
function Query:match_preds(match, pattern, source)
local preds = self.info.patterns[pattern]
@@ -358,7 +387,7 @@ function Query:match_preds(match, pattern, source)
return true
end
---- Applies directives against a match and pattern.
+---@private
function Query:apply_directives(match, pattern, source, metadata)
local preds = self.info.patterns[pattern]
@@ -380,6 +409,7 @@ end
--- Returns the start and stop value if set else the node's range.
-- When the node's range is used, the stop is incremented by 1
-- to make the search inclusive.
+---@private
local function value_or_node_range(start, stop, node)
if start == nil and stop == nil then
local node_start, _, node_stop, _ = node:range()
@@ -389,15 +419,36 @@ local function value_or_node_range(start, stop, node)
return start, stop
end
---- Iterates of the captures of self on a given range.
---
--- @param node The node under which the search will occur
--- @param buffer The source buffer to search
--- @param start The starting line of the search
--- @param stop The stopping line of the search (end-exclusive)
---
--- @returns The matching capture id
--- @returns The captured node
+--- Iterate over all captures from all matches inside {node}
+---
+--- {source} is needed if the query contains predicates, then the caller
+--- must ensure to use a freshly parsed tree consistent with the current
+--- text of the buffer (if relevent). {start_row} and {end_row} can be used to limit
+--- matches inside a row range (this is typically used with root node
+--- as the node, i e to get syntax highlight matches in the current
+--- viewport). When omitted the start and end row values are used from the given node.
+---
+--- The iterator returns three values, a numeric id identifying the capture,
+--- the captured node, and metadata from any directives processing the match.
+--- The following example shows how to get captures by name:
+---
+--- <pre>
+--- for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do
+--- local name = query.captures[id] -- name of the capture in the query
+--- -- typically useful info about the node:
+--- local type = node:type() -- type of the captured node
+--- local row1, col1, row2, col2 = node:range() -- range of the capture
+--- ... use the info here ...
+--- end
+--- </pre>
+---
+--- @param node The node under which the search will occur
+--- @param source The source buffer or string to exctract text from
+--- @param start The starting line of the search
+--- @param stop The stopping line of the search (end-exclusive)
+---
+--- @returns The matching capture id
+--- @returns The captured node
function Query:iter_captures(node, source, start, stop)
if type(source) == "number" and source == 0 then
source = vim.api.nvim_get_current_buf()
@@ -406,6 +457,7 @@ function Query:iter_captures(node, source, start, stop)
start, stop = value_or_node_range(start, stop, node)
local raw_iter = node:_rawquery(self.query, true, start, stop)
+ ---@private
local function iter()
local capture, captured_node, match = raw_iter()
local metadata = {}
@@ -425,14 +477,35 @@ function Query:iter_captures(node, source, start, stop)
end
--- Iterates the matches of self on a given range.
---
--- @param node The node under which the search will occur
--- @param buffer The source buffer to search
--- @param start The starting line of the search
--- @param stop The stopping line of the search (end-exclusive)
---
--- @returns The matching pattern id
--- @returns The matching match
+---
+--- Iterate over all matches within a node. The arguments are the same as
+--- for |query:iter_captures()| but the iterated values are different:
+--- an (1-based) index of the pattern in the query, a table mapping
+--- capture indices to nodes, and metadata from any directives processing the match.
+--- If the query has more than one pattern the capture table might be sparse,
+--- and e.g. `pairs()` method should be used over `ipairs`.
+--- Here an example iterating over all captures in every match:
+---
+--- <pre>
+--- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
+--- for id, node in pairs(match) do
+--- local name = query.captures[id]
+--- -- `node` was captured by the `name` capture in the match
+---
+--- local node_data = metadata[id] -- Node level metadata
+---
+--- ... use the info here ...
+--- end
+--- end
+--- </pre>
+---
+--- @param node The node under which the search will occur
+--- @param source The source buffer or string to search
+--- @param start The starting line of the search
+--- @param stop The stopping line of the search (end-exclusive)
+---
+--- @returns The matching pattern id
+--- @returns The matching match
function Query:iter_matches(node, source, start, stop)
if type(source) == "number" and source == 0 then
source = vim.api.nvim_get_current_buf()
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index b4d896fecc..d46306d41a 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -186,6 +186,48 @@ CONFIG = {
'module_override': {},
'append_only': [],
},
+ 'treesitter': {
+ 'mode': 'lua',
+ 'filename': 'treesitter.txt',
+ 'section_start_token': '*lua-treesitter-core*',
+ 'section_order': [
+ 'treesitter.lua',
+ 'language.lua',
+ 'query.lua',
+ 'highlighter.lua',
+ 'languagetree.lua',
+ 'health.lua',
+ ],
+ 'files': ' '.join([
+ os.path.join(base_dir, 'runtime/lua/vim/treesitter.lua'),
+ os.path.join(base_dir, 'runtime/lua/vim/treesitter/'),
+ ]),
+ 'file_patterns': '*.lua',
+ 'fn_name_prefix': '',
+ 'section_name': {},
+ 'section_fmt': lambda name: (
+ 'Lua module: vim.treesitter'
+ if name.lower() == 'treesitter'
+ else f'Lua module: vim.treesitter.{name.lower()}'),
+ 'helptag_fmt': lambda name: (
+ '*lua-treesitter-core*'
+ if name.lower() == 'treesitter'
+ else f'*treesitter-{name.lower()}*'),
+ 'fn_helptag_fmt': lambda fstem, name: (
+ f'*{name}()*'
+ if name != 'new'
+ else f'*{fstem}.{name}()*'),
+ # 'fn_helptag_fmt': lambda fstem, name: (
+ # f'*vim.treesitter.{name}()*'
+ # if fstem == 'treesitter'
+ # else (
+ # '*vim.lsp.client*'
+ # # HACK. TODO(justinmk): class/structure support in lua2dox
+ # if 'lsp.client' == f'{fstem}.{name}'
+ # else f'*vim.lsp.{fstem}.{name}()*')),
+ 'module_override': {},
+ 'append_only': [],
+ }
}
param_exclude = (
@@ -666,15 +708,6 @@ def extract_from_xml(filename, target, width):
annotations = filter(None, map(lambda x: annotation_map.get(x),
annotations.split()))
- if not fmt_vimhelp:
- pass
- else:
- fstem = '?'
- if '.' in compoundname:
- fstem = compoundname.split('.')[0]
- fstem = CONFIG[target]['module_override'].get(fstem, fstem)
- vimtag = CONFIG[target]['fn_helptag_fmt'](fstem, name)
-
params = []
type_length = 0
@@ -695,17 +728,37 @@ def extract_from_xml(filename, target, width):
if fmt_vimhelp and param_type.endswith('*'):
param_type = param_type.strip('* ')
param_name = '*' + param_name
+
type_length = max(type_length, len(param_type))
params.append((param_type, param_name))
+ # Handle Object Oriented style functions here.
+ # We make sure they have "self" in the parameters,
+ # and a parent function
+ if return_type.startswith('function') \
+ and len(return_type.split(' ')) >= 2 \
+ and any(x[1] == 'self' for x in params):
+ split_return = return_type.split(' ')
+ name = f'{split_return[1]}:{name}'
+
c_args = []
for param_type, param_name in params:
c_args.append((' ' if fmt_vimhelp else '') + (
'%s %s' % (param_type.ljust(type_length), param_name)).strip())
+ if not fmt_vimhelp:
+ pass
+ else:
+ fstem = '?'
+ if '.' in compoundname:
+ fstem = compoundname.split('.')[0]
+ fstem = CONFIG[target]['module_override'].get(fstem, fstem)
+ vimtag = CONFIG[target]['fn_helptag_fmt'](fstem, name)
+
prefix = '%s(' % name
suffix = '%s)' % ', '.join('{%s}' % a[1] for a in params
if a[0] not in ('void', 'Error'))
+
if not fmt_vimhelp:
c_decl = '%s %s(%s);' % (return_type, name, ', '.join(c_args))
signature = prefix + suffix
@@ -774,7 +827,9 @@ def extract_from_xml(filename, target, width):
xrefs.clear()
- fns = collections.OrderedDict(sorted(fns.items()))
+ fns = collections.OrderedDict(sorted(
+ fns.items(),
+ key=lambda key_item_tuple: key_item_tuple[0].lower()))
deprecated_fns = collections.OrderedDict(sorted(deprecated_fns.items()))
return (fns, deprecated_fns)
@@ -1002,6 +1057,7 @@ def main(config, args):
title, helptag, section_doc = sections.pop(filename)
except KeyError:
msg(f'warning: empty docs, skipping (target={target}): {filename}')
+ msg(f' existing docs: {sections.keys()}')
continue
i += 1
if filename not in CONFIG[target]['append_only']:
diff --git a/scripts/lua2dox.lua b/scripts/lua2dox.lua
index 1dc4c0a5a0..0b36a1e061 100644
--- a/scripts/lua2dox.lua
+++ b/scripts/lua2dox.lua
@@ -491,6 +491,27 @@ function TLua2DoX_filter.readfile(this,AppStamp,Filename)
end
end
+ -- Big hax
+ if string.find(fn, ":") then
+ -- TODO: We need to add a first parameter of "SELF" here
+ -- local colon_place = string.find(fn, ":")
+ -- local name = string.sub(fn, 1, colon_place)
+ fn = fn:gsub(":", ".", 1)
+ outStream:writeln("/// @param self")
+
+ local paren_start = string.find(fn, "(", 1, true)
+ local paren_finish = string.find(fn, ")", 1, true)
+
+ -- Nothing in between the parens
+ local comma
+ if paren_finish == paren_start + 1 then
+ comma = ""
+ else
+ comma = ", "
+ end
+ fn = string.sub(fn, 1, paren_start) .. "self" .. comma .. string.sub(fn, paren_start + 1)
+ end
+
-- add vanilla function
outStream:writeln(fn_type .. 'function ' .. fn .. '{}')
end
diff --git a/scripts/pvscheck.sh b/scripts/pvscheck.sh
index f054f6e6fe..f3371b485e 100755
--- a/scripts/pvscheck.sh
+++ b/scripts/pvscheck.sh
@@ -389,7 +389,7 @@ run_analysis() {(
detect_url() {
local url="${1:-detect}"
if test "$url" = detect ; then
- curl --silent -L 'https://www.viva64.com/en/pvs-studio-download/' \
+ curl --silent -L 'https://pvs-studio.com/en/pvs-studio/download-all/' \
| grep -o 'https\{0,1\}://[^"<>]\{1,\}/pvs-studio[^/"<>]*-x86_64\.tgz' \
|| echo FAILED
else
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index e79a7a2de2..cc5a62a170 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -1426,6 +1426,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
/// - "eol": right after eol character (default)
/// - "overlay": display over the specified column, without
/// shifting the underlying text.
+/// - "right_align": display right aligned in the window.
+/// - virt_text_win_col : position the virtual text at a fixed
+/// window column (starting from the first
+/// text column)
/// - virt_text_hide : hide the virtual text when the background
/// text is selected or hidden due to
/// horizontal scroll 'nowrap'
@@ -1574,11 +1578,22 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
decor.virt_text_pos = kVTEndOfLine;
} else if (strequal("overlay", str.data)) {
decor.virt_text_pos = kVTOverlay;
+ } else if (strequal("right_align", str.data)) {
+ decor.virt_text_pos = kVTRightAlign;
} else {
api_set_error(err, kErrorTypeValidation,
"virt_text_pos: invalid value");
goto error;
}
+ } else if (strequal("virt_text_win_col", k.data)) {
+ if (v->type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation,
+ "virt_text_win_col is not a Number of the correct size");
+ goto error;
+ }
+
+ decor.col = (int)v->data.integer;
+ decor.virt_text_pos = kVTWinCol;
} else if (strequal("virt_text_hide", k.data)) {
decor.virt_text_hide = api_object_to_bool(*v,
"virt_text_hide", false, err);
@@ -1673,6 +1688,14 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
col2 = 0;
}
+ if (decor.virt_text_pos == kVTRightAlign) {
+ decor.col = 0;
+ for (size_t i = 0; i < kv_size(decor.virt_text); i++) {
+ decor.col += mb_string2cells((char_u *)kv_A(decor.virt_text, i).text);
+ }
+ }
+
+
Decoration *d = NULL;
if (ephemeral) {
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 89fa2f86fb..f942d6b19f 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -381,7 +381,7 @@ Integer nvim_win_get_number(Window window, Error *err)
}
int tabnr;
- win_get_tabwin(window, &tabnr, &rv);
+ win_get_tabwin(win->handle, &tabnr, &rv);
return rv;
}
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index e39d2328f5..ca1d141dd8 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -230,6 +230,10 @@ static void decor_add(DecorState *state, int start_row, int start_col,
*decor, attr_id,
kv_size(decor->virt_text) && owned, -1 };
+ if (decor->virt_text_pos == kVTEndOfLine) {
+ range.win_col = -2; // handled separately
+ }
+
kv_pushp(state->active);
size_t index;
for (index = kv_size(state->active)-1; index > 0; index--) {
@@ -242,7 +246,7 @@ static void decor_add(DecorState *state, int start_row, int start_col,
kv_A(state->active, index) = range;
}
-int decor_redraw_col(buf_T *buf, int col, int virt_col, bool hidden,
+int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden,
DecorState *state)
{
if (col <= state->col_until) {
@@ -321,8 +325,9 @@ next_mark:
attr = hl_combine_attr(attr, item.attr_id);
}
if ((item.start_row == state->row && item.start_col <= col)
- && kv_size(item.decor.virt_text) && item.virt_col == -1) {
- item.virt_col = (item.decor.virt_text_hide && hidden) ? -2 : virt_col;
+ && kv_size(item.decor.virt_text)
+ && item.decor.virt_text_pos == kVTOverlay && item.win_col == -1) {
+ item.win_col = (item.decor.virt_text_hide && hidden) ? -2 : win_col;
}
if (keep) {
kv_A(state->active, j++) = item;
@@ -340,18 +345,23 @@ void decor_redraw_end(DecorState *state)
state->buf = NULL;
}
-VirtText decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr)
+VirtText decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr,
+ bool *aligned)
{
decor_redraw_col(buf, MAXCOL, MAXCOL, false, state);
VirtText text = VIRTTEXT_EMPTY;
for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange item = kv_A(state->active, i);
- if (!kv_size(text)
- && item.start_row == state->row && kv_size(item.decor.virt_text)
- && item.decor.virt_text_pos == kVTEndOfLine) {
- text = item.decor.virt_text;
+ if (item.start_row == state->row && kv_size(item.decor.virt_text)) {
+ if (!kv_size(text) && item.decor.virt_text_pos == kVTEndOfLine) {
+ text = item.decor.virt_text;
+ } else if (item.decor.virt_text_pos == kVTRightAlign
+ || item.decor.virt_text_pos == kVTWinCol) {
+ *aligned = true;
+ }
}
+
if (item.decor.hl_eol && item.start_row <= state->row) {
*eol_attr = hl_combine_attr(*eol_attr, item.attr_id);
}
diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h
index 08d69060f0..4cebc0b731 100644
--- a/src/nvim/decoration.h
+++ b/src/nvim/decoration.h
@@ -21,6 +21,8 @@ typedef uint16_t DecorPriority;
typedef enum {
kVTEndOfLine,
kVTOverlay,
+ kVTWinCol,
+ kVTRightAlign,
} VirtTextPos;
typedef enum {
@@ -41,9 +43,10 @@ struct Decoration
// TODO(bfredl): style, signs, etc
DecorPriority priority;
bool shared; // shared decoration, don't free
+ int col; // fixed col value, like win_col
};
#define DECORATION_INIT { 0, KV_INITIAL_VALUE, kVTEndOfLine, false, \
- kHlModeUnknown, false, DECOR_PRIORITY_BASE, false }
+ kHlModeUnknown, false, DECOR_PRIORITY_BASE, false, 0 }
typedef struct {
int start_row;
@@ -53,7 +56,7 @@ typedef struct {
Decoration decor;
int attr_id; // cached lookup of decor.hl_id
bool virt_text_owned;
- int virt_col;
+ int win_col;
} DecorRange;
typedef struct {
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 950a1a436f..7f28c001f9 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -2719,16 +2719,10 @@ static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat)
while (!(p->buf[i] == '\n' || p->buf[i] == '\0')) {
i++;
}
- char buf[2046];
- char *dst;
- dst = xstpncpy(buf, (char *)p->buf + p->offset, i - p->offset);
- if ((uint32_t)(dst - buf) != i - p->offset) {
- smsg(_(":source error parsing command %s"), p->buf);
- return NULL;
- }
- buf[i - p->offset] = '\0';
+ size_t line_length = i - p->offset;
+ char_u *buf = xmemdupz(p->buf + p->offset, line_length);
p->offset = i + 1;
- return (char_u *)xstrdup(buf);
+ return buf;
}
static int source_using_linegetter(void *cookie,
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index 75e759094f..3994c5bc5b 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -400,7 +400,10 @@ do
wfw = true; winbl = true; winblend = true; winfixheight = true;
winfixwidth = true; winhighlight = true; winhl = true; wrap = true;
}
+
+ --@private
local function new_buf_opt_accessor(bufnr)
+ --@private
local function get(k)
if window_options[k] then
return a.nvim_err_writeln(k.." is a window option, not a buffer option")
@@ -410,23 +413,34 @@ do
end
return a.nvim_buf_get_option(bufnr or 0, k)
end
+
+ --@private
local function set(k, v)
if window_options[k] then
return a.nvim_err_writeln(k.." is a window option, not a buffer option")
end
return a.nvim_buf_set_option(bufnr or 0, k, v)
end
+
return make_meta_accessor(get, set)
end
vim.bo = new_buf_opt_accessor(nil)
+
+ --@private
local function new_win_opt_accessor(winnr)
+
+ --@private
local function get(k)
if winnr == nil and type(k) == "number" then
return new_win_opt_accessor(k)
end
return a.nvim_win_get_option(winnr or 0, k)
end
- local function set(k, v) return a.nvim_win_set_option(winnr or 0, k, v) end
+
+ --@private
+ local function set(k, v)
+ return a.nvim_win_set_option(winnr or 0, k, v)
+ end
return make_meta_accessor(get, set)
end
vim.wo = new_win_opt_accessor(nil)
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 7064f2a068..56cd97f133 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -375,7 +375,7 @@ int main(int argc, char **argv)
// Does ":filetype plugin indent on".
filetype_maybe_enable();
// Sources syntax/syntax.vim, which calls `:filetype on`.
- syn_maybe_on();
+ syn_maybe_enable();
}
// Read all the plugin files.
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 7c98d3c6b5..1783f62247 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -2265,12 +2265,14 @@ void msg_scroll_up(bool may_throttle)
/// per screen update.
///
/// NB: The bookkeeping is quite messy, and rests on a bunch of poorly
-/// documented assumtions. For instance that the message area always grows while
-/// being throttled, messages are only being output on the last line etc.
+/// documented assumptions. For instance that the message area always grows
+/// while being throttled, messages are only being output on the last line
+/// etc.
///
-/// Probably message scrollback storage should reimplented as a file_buffer, and
-/// message scrolling in TUI be reimplemented as a modal floating window. Then
-/// we get throttling "for free" using standard redraw_later code paths.
+/// Probably message scrollback storage should be reimplemented as a
+/// file_buffer, and message scrolling in TUI be reimplemented as a modal
+/// floating window. Then we get throttling "for free" using standard
+/// redraw_later code paths.
void msg_scroll_flush(void)
{
if (msg_grid.throttled) {
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 6be3b6fb60..5151d82c1b 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -2101,6 +2101,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
bool search_attr_from_match = false; // if search_attr is from :match
bool has_decor = false; // this buffer has decoration
bool do_virttext = false; // draw virtual text for this line
+ int win_col_offset; // offsett for window columns
char_u buf_fold[FOLD_TEXT_LEN + 1]; // Hold value returned by get_foldtext
@@ -2790,6 +2791,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
}
}
+ if (draw_state == WL_NR && n_extra == 0) {
+ win_col_offset = off;
+ }
+
if (wp->w_briopt_sbr && draw_state == WL_BRI - 1
&& n_extra == 0 && *p_sbr != NUL) {
// draw indent after showbreak value
@@ -2904,7 +2909,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
&& vcol >= (long)wp->w_virtcol)
|| (number_only && draw_state > WL_NR))
&& filler_todo <= 0) {
- draw_virt_text(buf, &col, grid->Columns);
+ draw_virt_text(buf, win_col_offset, &col, grid->Columns);
grid_put_linebuf(grid, row, 0, col, -grid->Columns, wp->w_p_rl, wp,
wp->w_hl_attr_normal, false);
// Pretend we have finished updating the window. Except when
@@ -3945,13 +3950,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
VirtText virt_text = KV_INITIAL_VALUE;
+ bool has_aligned = false;
if (err_text) {
int hl_err = syn_check_group((char_u *)S_LEN("ErrorMsg"));
kv_push(virt_text, ((VirtTextChunk){ .text = err_text,
.hl_id = hl_err }));
do_virttext = true;
} else if (has_decor) {
- virt_text = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr);
+ virt_text = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr,
+ &has_aligned);
if (kv_size(virt_text)) {
do_virttext = true;
}
@@ -3963,7 +3970,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
grid->Columns * (row - startrow + 1) + v
&& lnum != wp->w_cursor.lnum)
|| draw_color_col || line_attr_lowprio || line_attr
- || diff_hlf != (hlf_T)0 || do_virttext)) {
+ || diff_hlf != (hlf_T)0 || do_virttext
+ || has_aligned)) {
int rightmost_vcol = 0;
int i;
@@ -4001,7 +4009,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
}
int base_attr = hl_combine_attr(line_attr_lowprio, diff_attr);
- if (base_attr || line_attr) {
+ if (base_attr || line_attr || has_aligned) {
rightmost_vcol = INT_MAX;
}
@@ -4079,7 +4087,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
}
}
- draw_virt_text(buf, &col, grid->Columns);
+ draw_virt_text(buf, win_col_offset, &col, grid->Columns);
grid_put_linebuf(grid, row, 0, col, grid->Columns, wp->w_p_rl, wp,
wp->w_hl_attr_normal, false);
row++;
@@ -4300,7 +4308,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
&& !wp->w_p_rl; // Not right-to-left.
int draw_col = col - boguscols;
- draw_virt_text(buf, &draw_col, grid->Columns);
+ draw_virt_text(buf, win_col_offset, &draw_col, grid->Columns);
grid_put_linebuf(grid, row, 0, draw_col, grid->Columns, wp->w_p_rl,
wp, wp->w_hl_attr_normal, wrap);
if (wrap) {
@@ -4377,52 +4385,62 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
return row;
}
-void draw_virt_text(buf_T *buf, int *end_col, int max_col)
+void draw_virt_text(buf_T *buf, int col_off, int *end_col, int max_col)
{
DecorState *state = &decor_state;
+ int right_pos = max_col;
for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange *item = &kv_A(state->active, i);
- if (item->start_row == state->row && kv_size(item->decor.virt_text)
- && item->decor.virt_text_pos == kVTOverlay
- && item->virt_col >= 0) {
- VirtText vt = item->decor.virt_text;
- HlMode hl_mode = item->decor.hl_mode;
- LineState s = LINE_STATE("");
- int virt_attr = 0;
- int col = item->virt_col;
- size_t virt_pos = 0;
- item->virt_col = -2; // deactivate
+ if (item->start_row == state->row && kv_size(item->decor.virt_text)) {
+ if (item->win_col == -1) {
+ if (item->decor.virt_text_pos == kVTRightAlign) {
+ right_pos -= item->decor.col;
+ item->win_col = right_pos;
+ } else if (item->decor.virt_text_pos == kVTWinCol) {
+ item->win_col = MAX(item->decor.col+col_off, 0);
+ }
+ }
+ if (item->win_col < 0) {
+ continue;
+ }
+ VirtText vt = item->decor.virt_text;
+ HlMode hl_mode = item->decor.hl_mode;
+ LineState s = LINE_STATE("");
+ int virt_attr = 0;
+ int col = item->win_col;
+ size_t virt_pos = 0;
+ item->win_col = -2; // deactivate
- while (col < max_col) {
- if (!*s.p) {
- if (virt_pos == kv_size(vt)) {
- break;
- }
- s.p = kv_A(vt, virt_pos).text;
- int hl_id = kv_A(vt, virt_pos).hl_id;
- virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0;
- virt_pos++;
- continue;
- }
- int attr;
- bool through = false;
- if (hl_mode == kHlModeCombine) {
- attr = hl_combine_attr(linebuf_attr[col], virt_attr);
- } else if (hl_mode == kHlModeBlend) {
- through = (*s.p == ' ');
- attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through);
- } else {
- attr = virt_attr;
+ while (col < max_col) {
+ if (!*s.p) {
+ if (virt_pos == kv_size(vt)) {
+ break;
}
- schar_T dummy[2];
- int cells = line_putchar(&s, through ? dummy : &linebuf_char[col],
- max_col-col, false);
+ s.p = kv_A(vt, virt_pos).text;
+ int hl_id = kv_A(vt, virt_pos).hl_id;
+ virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0;
+ virt_pos++;
+ continue;
+ }
+ int attr;
+ bool through = false;
+ if (hl_mode == kHlModeCombine) {
+ attr = hl_combine_attr(linebuf_attr[col], virt_attr);
+ } else if (hl_mode == kHlModeBlend) {
+ through = (*s.p == ' ');
+ attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through);
+ } else {
+ attr = virt_attr;
+ }
+ schar_T dummy[2];
+ int cells = line_putchar(&s, through ? dummy : &linebuf_char[col],
+ max_col-col, false);
+ linebuf_attr[col++] = attr;
+ if (cells > 1) {
linebuf_attr[col++] = attr;
- if (cells > 1) {
- linebuf_attr[col++] = attr;
- }
}
- *end_col = MAX(*end_col, col);
+ }
+ *end_col = MAX(*end_col, col);
}
}
}
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index 5c7b497a19..97e64c6c4c 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -742,15 +742,15 @@ void sign_mark_adjust(
next = sign->se_next;
new_lnum = sign->se_lnum;
if (sign->se_lnum >= line1 && sign->se_lnum <= line2) {
- if (amount == MAXLNUM && (!is_fixed || signcol >= 2)) {
+ if (amount != MAXLNUM) {
+ new_lnum += amount;
+ } else if (!is_fixed || signcol >= 2) {
*lastp = next;
if (next) {
next->se_prev = last;
}
xfree(sign);
continue;
- } else {
- new_lnum += amount;
}
} else if (sign->se_lnum > line2) {
new_lnum += amount_after;
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 77a751e5ad..ed886ab7f9 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -3469,13 +3469,13 @@ static void syn_cmd_onoff(exarg_T *eap, char *name)
}
}
-void syn_maybe_on(void)
+void syn_maybe_enable(void)
{
if (!did_syntax_onoff) {
exarg_T ea;
ea.arg = (char_u *)"";
ea.skip = false;
- syn_cmd_onoff(&ea, "syntax");
+ syn_cmd_enable(&ea, false);
}
}
diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua
index 72ff6f2fb6..f267f9fb5d 100644
--- a/test/functional/treesitter/parser_spec.lua
+++ b/test/functional/treesitter/parser_spec.lua
@@ -599,6 +599,56 @@ int x = INT_MAX;
eq(result, "value")
end)
+
+ describe("when setting a key on a capture", function()
+ it("it should create the nested table", function()
+ insert([[
+ int x = 3;
+ ]])
+
+ local result = exec_lua([[
+ local query = require("vim.treesitter.query")
+ local value
+
+ query = vim.treesitter.parse_query("c", '((number_literal) @number (#set! @number "key" "value"))')
+ parser = vim.treesitter.get_parser(0, "c")
+
+ for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0) do
+ for _, nested_tbl in pairs(metadata) do
+ return nested_tbl.key
+ end
+ end
+ ]])
+
+ eq(result, "value")
+ end)
+
+ it("it should not overwrite the nested table", function()
+ insert([[
+ int x = 3;
+ ]])
+
+ local result = exec_lua([[
+ local query = require("vim.treesitter.query")
+ local result
+
+ query = vim.treesitter.parse_query("c", '((number_literal) @number (#set! @number "key" "value") (#set! @number "key2" "value2"))')
+ parser = vim.treesitter.get_parser(0, "c")
+
+ for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0) do
+ for _, nested_tbl in pairs(metadata) do
+ return nested_tbl
+ end
+ end
+ ]])
+ local expected = {
+ ["key"] = "value",
+ ["key2"] = "value2",
+ }
+
+ eq(expected, result)
+ end)
+ end)
end)
end)
end)
diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua
index 82d3075be2..09638df6c5 100644
--- a/test/functional/ui/decorations_spec.lua
+++ b/test/functional/ui/decorations_spec.lua
@@ -333,6 +333,35 @@ describe('decorations providers', function()
]]}
end)
+ it('can have virtual text of the style: right_align', function()
+ insert(mulholland)
+ setup_provider [[
+ local hl = a.nvim_get_hl_id_by_name "ErrorMsg"
+ local test_ns = a.nvim_create_namespace "mulholland"
+ function on_do(event, ...)
+ if event == "line" then
+ local win, buf, line = ...
+ a.nvim_buf_set_extmark(buf, test_ns, line, 0, {
+ virt_text = {{'+'}, {string.rep(' ', line+1), 'ErrorMsg'}};
+ virt_text_pos='right_align';
+ ephemeral = true;
+ })
+ end
+ end
+ ]]
+
+ screen:expect{grid=[[
+ // just to see if there was an acciden+{2: }|
+ // on Mulholland Drive +{2: }|
+ try_start(); +{2: }|
+ bufref_T save_buf; +{2: }|
+ switch_buffer(&save_buf, buf); +{2: }|
+ posp = getmark(mark, false); +{2: }|
+ restore_buffer(&save_buf);^ +{2: }|
+ |
+ ]]}
+ end)
+
it('can highlight beyond EOL', function()
insert(mulholland)
setup_provider [[
@@ -366,7 +395,7 @@ describe('decorations providers', function()
end)
describe('extmark decorations', function()
- local screen
+ local screen, ns
before_each( function()
clear()
screen = Screen.new(50, 15)
@@ -397,6 +426,8 @@ describe('extmark decorations', function()
[23] = {foreground = Screen.colors.Magenta1, background = Screen.colors.LightGrey};
[24] = {bold = true};
}
+
+ ns = meths.create_namespace 'test'
end)
local example_text = [[
@@ -417,7 +448,6 @@ end]]
insert(example_text)
feed 'gg'
- local ns = meths.create_namespace 'test'
for i = 1,9 do
meths.buf_set_extmark(0, ns, i, 0, { virt_text={{'|', 'LineNr'}}, virt_text_pos='overlay'})
if i == 3 or (i >= 6 and i <= 9) then
@@ -484,7 +514,6 @@ end]]
it('can have virtual text of overlay position and styling', function()
insert(example_text)
feed 'gg'
- local ns = meths.create_namespace 'test'
command 'set ft=lua'
command 'syntax on'
@@ -572,4 +601,88 @@ end]]
{24:-- VISUAL LINE --} |
]]}
end)
+
+ it('can have virtual text of fixed win_col position', function()
+ insert(example_text)
+ feed 'gg'
+ meths.buf_set_extmark(0, ns, 1, 0, { virt_text={{'Very', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'})
+ meths.buf_set_extmark(0, ns, 2, 10, { virt_text={{'Much', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'})
+ meths.buf_set_extmark(0, ns, 3, 15, { virt_text={{'Error', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'})
+ meths.buf_set_extmark(0, ns, 7, 21, { virt_text={{'-', 'NonText'}}, virt_text_win_col=4, hl_mode='blend'})
+
+ screen:expect{grid=[[
+ ^for _,item in ipairs(items) do |
+ local text, hl_id_cell, cou{4:Very} unpack(item) |
+ if hl_id_cell ~= nil then {4:Much} |
+ hl_id = hl_id_cell {4:Error} |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ {1:-} cell.text = text |
+ cell.hl_id = hl_id |
+ colpos = colpos+1 |
+ end |
+ end |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed '3G12|i<cr><esc>'
+ screen:expect{grid=[[
+ for _,item in ipairs(items) do |
+ local text, hl_id_cell, cou{4:Very} unpack(item) |
+ if hl_i {4:Much} |
+ ^d_cell ~= nil then |
+ hl_id = hl_id_cell {4:Error} |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ {1:-} cell.text = text |
+ cell.hl_id = hl_id |
+ colpos = colpos+1 |
+ end |
+ end |
+ {1:~ }|
+ |
+ ]]}
+
+ feed 'u:<cr>'
+ screen:expect{grid=[[
+ for _,item in ipairs(items) do |
+ local text, hl_id_cell, cou{4:Very} unpack(item) |
+ if hl_i^d_cell ~= nil then {4:Much} |
+ hl_id = hl_id_cell {4:Error} |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ {1:-} cell.text = text |
+ cell.hl_id = hl_id |
+ colpos = colpos+1 |
+ end |
+ end |
+ {1:~ }|
+ {1:~ }|
+ : |
+ ]]}
+
+ feed '8|i<cr><esc>'
+ screen:expect{grid=[[
+ for _,item in ipairs(items) do |
+ local text, hl_id_cell, cou{4:Very} unpack(item) |
+ if |
+ ^hl_id_cell ~= nil then {4:Much} |
+ hl_id = hl_id_cell {4:Error} |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ {1:-} cell.text = text |
+ cell.hl_id = hl_id |
+ colpos = colpos+1 |
+ end |
+ end |
+ {1:~ }|
+ |
+ ]]}
+ end)
end)