Commit Graph

176 Commits

Author SHA1 Message Date
Riley Bruins
3abfaafad2 fix(treesitter): detect trees with outdated regions in is_valid() 2025-02-11 09:17:08 +00:00
Riley Bruins
09f9f0a946 feat(treesitter): show which nodes are missing in InspectTree
Now `:InspectTree` will show missing nodes as e.g. `(MISSING identifier)`
or `(MISSING ";")` rather than just `(identifier)` or `";"`. This is
doable because the `MISSING` keyword is now valid query syntax.

Co-authored-by: Christian Clason <c.clason@uni-graz.at>
2025-02-05 09:29:31 +01:00
Riley Bruins
8543aa406c feat(treesitter): allow LanguageTree:is_valid() to accept a range
When given, only that range will be checked for validity rather than the
entire tree. This is used in the highlighter to save CPU cycles since we
only need to parse a certain region at a time anyway.
2025-02-02 12:13:25 -08:00
Riley Bruins
096ae3bfd7 fix(treesitter): nil access when running string parser async 2025-02-01 17:02:52 +01:00
Maria José Solano
da0ae95349 feat(treesitter): support modelines in query.set() (#30257) 2025-01-29 08:59:28 +01:00
Christian Clason
eb60cd74fb build(deps)!: bump tree-sitter to HEAD, wasmtime to v29.0.1 (#32200)
Breaking change: `ts_node_child_containing_descendant()` was removed

Breaking change: tree-sitter 0.25 (HEAD) required
2025-01-27 16:16:06 +01:00
Guilherme Soares
a3ef29d570 test: use temp file #31907 2025-01-13 01:41:49 -08:00
Riley Bruins
bd4ca22d03 feat(treesitter)!: don't parse tree in get_parser() or start()
**Problem:** `vim.treesitter.get_parser()` and `vim.treesitter.start()`
both parse the tree before returning it. This is problematic because if
this is a sync parse, it will stall the editor on large files. If it is
an async parse, the functions return stale trees.

**Solution:** Remove this parsing side effect and leave it to the user
to parse the returned trees, either synchronously or asynchronously.
2025-01-12 08:10:49 -08:00
Riley Bruins
45e606b1fd feat(treesitter): async parsing
**Problem:** Parsing can be slow for large files, and it is a blocking
operation which can be disruptive and annoying.

**Solution:** Provide a function for asynchronous parsing, which accepts
a callback to be run after parsing completes.

Co-authored-by: Lewis Russell <lewis6991@gmail.com>
Co-authored-by: Luuk van Baal <luukvbaal@gmail.com>
Co-authored-by: VanaIgr <vanaigranov@gmail.com>
2025-01-12 08:10:47 -08:00
Riley Bruins
3fdc430241 perf(treesitter): cache queries strongly
**Problem:** Query parsing uses a weak cache which is invalidated
frequently

**Solution:** Make the cache strong, and invalidate it manually when
necessary (that is, when `rtp` is changed or `query.set()` is called)

Co-authored-by: Christian Clason <c.clason@uni-graz.at>
2025-01-12 16:44:24 +01:00
Guilherme Soares
17b46d01e2 test(treesitter): inspect_tree #31182
To prevent #30986 and #31198 regression update inspect_tree tests
2025-01-08 05:06:09 -08:00
Riley Bruins
d9ee0d2984 perf(treesitter): don't fetch parser for each fold line
**Problem:** The treesitter `foldexpr` calls `get_parser()` for each
line in the buffer when calculating folds. This can be incredibly slow
for buffers where a parser cannot be found (because the result is not
cached), and exponentially more so when the user has many
`runtimepath`s.

**Solution:** Only fetch the parser when it is needed; that is, only
when initializing fold data for a buffer.

Co-authored-by: Jongwook Choi <wookayin@gmail.com>
Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
2025-01-07 16:43:45 +01:00
vanaigr
dd234135ad refactor: split predicates and directives 2025-01-06 00:35:19 -06:00
Riley Bruins
3dfb9e6f60 feat(treesitter): include capture id in return value of get_captures_at_pos() #30559
**Problem:** Currently, it is difficult to get node(s)-level metadata
for a capture returned by `get_captures_at_pos()`. This is because it is
stored in `metadata[id]` and we do not have access to the value of `id`,
so to get this value we have to iterate over the keys of `metadata`. See
[this commit](d636229300 (diff-8bd4742121c2f359d0345f3c6c253a58220f1a28670cc4e1c957992232059a6cR16)).

Things would be much simpler if we were given the `id` of the capture so
we could use it to just index `metadata` directly.

**Solution:** Include `id` in the data returned by
`get_captures_at_pos()`
2024-12-11 04:34:24 -08:00
Riley Bruins
c63e49cce2 fix(treesitter): #trim! range for nodes ending at col 0 #31488
Problem:
char-wise folding for `#trim!` ranges are improperly calculated for nodes that
end at column 0, due to the way `get_node_text` works.

Solution:
Add the blank line that `get_node_text` removes for for nodes ending at column
0. Also properly set column positions when performing linewise trims.
2024-12-07 03:01:59 -08:00
Riley Bruins
f0ea38a4bc test(treesitter): add a simple testutil file
The util file, for now, just abstracts the common `run_query` function.
2024-12-06 08:36:28 -08:00
Riley Bruins
b8c75a31e6 feat(treesitter): #trim! can trim all whitespace
This commit also implements more generic trimming, acting on all
whitespace (charwise) rather than just empty lines.

It will unblock
https://github.com/nvim-treesitter/nvim-treesitter/pull/3442 and allow
for properly concealing markdown bullet markers regardless of indent
width, e.g.
2024-12-06 08:36:08 -08:00
luukvbaal
e2a91876ac test(screen): adjust screen state per stylua #31441
Before:
screen:expect({        | screen:expect({
  grid = [[            |   grid = [[
    {10:>!}a        |  |     line ^1                   |
    {7:  }b        |   |     {1:~                        }|*4
    {10:>>}c        |  |   ]], messages={ {
    {7:  }^         |  |     content = { { "\ntest\n[O]k: ", 6, 11 } },
    {1:~          }|*9 |     kind = "confirm"
               |       |   } }
  ]]                   | })
})

After:
screen:expect([[         | screen:expect({
  {10:>!}a            |  |   grid = [[
  {7:  }b            |   |     line ^1                   |
  {10:>>}c            |  |     {1:~                        }|*4
  {7:  }^             |  |   ]],
  {1:~              }|*9 |   messages = { {
                 |       |     content = { { "\ntest\n[O]k: ", 6, 11 } },
]])                      |     kind = "confirm"
                         |   } },
                         | })
2024-12-04 07:31:08 -08:00
luukvbaal
9d0117fd30 test(treesitter): global highlight definitions and fold test #31407
Add test for foldtext= highlighting. Change file to global highlight
definitions while at it.
2024-12-02 06:08:26 -08:00
Christian Clason
6e44a6a289 fix(treesitter): update queries 2024-11-22 09:39:45 +01:00
bfredl
e61228a214 fix(tests): needing two calls to setup a screen is cringe
Before calling "attach" a screen object is just a dummy container for
(row, col) values whose purpose is to be sent as part of the "attach"
function call anyway.

Just create the screen in an attached state directly. Keep the complete
(row, col, options) config together. It is still completely valid to
later detach and re-attach as needed, including to another session.
2024-11-14 12:40:57 +01:00
Riley Bruins
36990f324d fix(treesitter): show proper node name error messages
**Problem:** Currently node names with non-alphanumeric, non
underscore/hyphen characters (only possible with anonymous nodes) are
not given a proper error message. See tree-sitter issue 3892 for more
details.

**Solution:** Apply a different scanning logic to anonymous nodes to
correctly identify the entire node name (i.e., up until the final double
quote)
2024-11-13 13:32:58 +01:00
Riley Bruins
4b90952851 fix(treesitter): mark supertype nodes as named
**Problem:** Tree-sitter 0.24.0 introduced a new symbol type to denote
supertype nodes (`TSSymbolTypeSupertype`). Now, `language.inspect()`
(and the query `omnifunc`) return supertype symbols, but with double
quotes around them.

**Solution:** Mark a symbol as "named" based on it *not* being an
anonymous node, rather than checking that it is a regular node (which a
supertype also is not).
2024-10-12 09:59:44 +02:00
Riley Bruins
d3193afc25 fix(treesitter): remove duplicate symbol names in language.inspect()
**Problems:**

- `vim.treesitter.language.inspect()` returns duplicate
  symbol names, sometimes up to 6 of one kind in the case of `markdown`
- The list-like `symbols` table can have holes and is thus not even a
  valid msgpack table anyway, mentioned in a test

**Solution:** Return symbols as a map, rather than a list, where field
names are the names of the symbol. The boolean value associated with the
field encodes whether or not the symbol is named.

Note that anonymous nodes are surrounded with double quotes (`"`) to
prevent potential collisions with named counterparts that have the same
identifier.
2024-10-11 18:15:07 +02:00
Riley Bruins
267c7525f7 feat(treesitter): introduce child_with_descendant()
This commit also marks `child_containing_descendant()` as deprecated
(per upstream's documentation), and uses `child_with_descendant()` in
its place. Minimum required tree-sitter version will now be `0.24`.
2024-10-11 17:29:45 +02:00
Christian Clason
99e0facf3a feat(treesitter)!: use return values in language.add()
Problem: No clear way to check whether parsers are available for a given
language.

Solution: Make `language.add()` return `true` if a parser was
successfully added and `nil` otherwise. Use explicit `assert` instead of
relying on thrown errors.
2024-09-29 15:27:16 +02:00
Riley Bruins
0f067cd34d fix(treesitter): suppress get_parser warnings via opts.error 2024-09-28 00:31:45 +02:00
Justin M. Keyes
09d76afe84 feat(defaults): pretty :help headings #30544
Problem:
Headings in :help do not stand out visually.

Solution:
Define a non-standard `@markup.heading.1.delimiter` group and
special-case it in `highlight_group.c`.

FUTURE:
This is a cheap workaround until we have #25718 which will enable:
- fully driven by `vimdoc/highlights.scm` instead of using highlight
  tricks (`guibg=bg guifg=bg guisp=fg`)
- better support of "cterm" ('notermguicolors')
2024-09-27 08:53:30 -07:00
Riley Bruins
64847fbdc9 perf(treesitter): use child_containing_descendant() in is_ancestor()
**Problem:** `is_ancestor()` uses a slow, bottom-up parent lookup which
has performance pitfalls detailed in #28512.

**Solution:** Take `is_ancestor()` from $O(n^2)$ to $O(n)$ by
incorporating the use of the `child_containing_descendant()` function
2024-09-25 23:01:08 +02:00
Lewis Russell
e5c174421d test: support upvalues in exec_lua 2024-09-21 16:04:09 +01:00
Riley Bruins
b9b408a56c feat(treesitter): start moving get_parser to return nil #30313
**Problem:** `vim.treesitter.get_parser` will throw an error if no parser
can be found.

- This means the caller is responsible for wrapping it in a `pcall`,
  which is easy to forget
- It also makes it slightly harder to potentially memoize `get_parser`
  in the future
- It's a bit unintuitive since many other `get_*` style functions
  conventionally return `nil` if no object is found (e.g. `get_node`,
  `get_lang`, `query.get`, etc.)

**Solution:** Return `nil` if no parser can be found or created

- This requires a function signature change, and some new assertions in
  places where the parser will always (or should always) be found.
- This commit starts by making this change internally, since it is
  breaking. Eventually it will be rolled out to the public API.
2024-09-13 05:09:11 -07:00
Gregory Anders
6913c5e1d9 feat(treesitter)!: default to correct behavior for quantified captures (#30193)
For context, see https://github.com/neovim/neovim/pull/24738. Before
that PR, Nvim did not correctly handle captures with quantifiers. That
PR made the correct behavior opt-in to minimize breaking changes, with
the intention that the correct behavior would eventually become the
default. Users can still opt-in to the old (incorrect) behavior for now,
but this option will eventually be removed completely.

BREAKING CHANGE: Any plugin which uses `Query:iter_matches()` must
update their call sites to expect an array of nodes in the `match`
table, rather than a single node.
2024-09-01 18:01:53 +00:00
Lewis Russell
688b961d13 feat(treesitter): add support for wasm parsers
Problem: Installing treesitter parser is hard (harder than
climbing to heaven).

Solution: Add optional support for wasm parsers with `wasmtime`.

Notes:

* Needs to be enabled by setting `ENABLE_WASMTIME` for tree-sitter and
  Neovim. Build with
  `make CMAKE_EXTRA_FLAGS=-DENABLE_WASMTIME=ON
  DEPS_CMAKE_FLAGS=-DENABLE_WASMTIME=ON`
* Adds optional Rust (obviously) and C11 dependencies.
* Wasmtime comes with a lot of features that can negatively affect
  Neovim performance due to library and symbol table size. Make sure to
  build with minimal features and full LTO.
* To reduce re-compilation times, install `sccache` and build with
  `RUSTC_WRAPPER=<path/to/sccache> make ...`
2024-08-26 16:44:03 +02:00
Lewis Russell
7d24c4d6b0 test: allow exec_lua to handle functions
Problem:

Tests have lots of exec_lua calls which input blocks of code
provided as unformatted strings.

Solution:

Teach exec_lua how to handle functions.
2024-08-02 19:04:37 +01:00
Riley Bruins
1af55bfcf2 feat(treesitter): allow get_node to return anonymous nodes
Adds a new field `include_anonymous` to the `get_node` options to allow
anonymous nodes to be returned.
2024-07-29 17:15:46 +02:00
Riley Bruins
bd3b6ec836 feat(treesitter): add node_for_range function
This is identical to `named_node_for_range` except that it includes
anonymous nodes. This maintains consistency in the API because we
already have `descendant_for_range` and `named_descendant_for_range`.
2024-07-29 17:15:46 +02:00
Christian Clason
862338255d fix(runtime): sync bundled treesitter queries 2024-07-24 16:02:53 +02:00
Christian Clason
51d85f7ea5 build(deps): drop unused bundled bash, python parsers and queries
Problem: Neovim bundles treesitter parsers for bash and python but does
not use them by default. This dilutes the messaging about the bundled
parsers being required for functionality or reasonable out-of-the-box
experience. It also increases the risk of query incompatibilities for no
gain.

Solution: Stop bundling bash and python parser and queries.
2024-07-09 15:26:48 +02:00
Riley Bruins
9217e0d671 fix(treesitter): display fields for anonymous nodes in :InspectTree 2024-07-05 10:11:48 +02:00
zeertzjq
599fc7cee4 test: starting and stopping treesitter highlight (#29546) 2024-07-03 08:40:55 +08:00
zeertzjq
d413038b4f fix(treesitter): ensure syntaxset augroup exists (#29542)
Problem:
Error when calling vim.treesitter.start() and vim.treesitter.stop() in
init.lua.

Solution:
Ensure syntaxset augroup exists after loading synload.vim.
2024-07-03 07:40:42 +08:00
Riley Bruins
c57a85e0ed perf(treesitter): remove unnecessary foldexpr loop
Instead of looping over all captured nodes, just take the end range from
the last node in the list. This uses the fact that nodes returned by
iter_matches are ordered by their range (earlier to later).
2024-06-24 14:10:25 +02:00
Luuk van Baal
da4e8dc5b0 fix(treesitter): do not modify highlight state for _on_spell_nav
Problem:  Treesitter highlighter clears the already populated highlight
          state when performing spell checking while drawing a
          smoothscrolled topline.
Solution: Save and restore the highlight state in the highlighter's
          _on_spell_nav callback.
2024-06-24 09:22:27 +01:00
Jaehwang Jung
0e3e1e6b6d fix(treesitter): don't open fold when o/O adds a line below #28709
Problem:
`o`-ing on a folded line opens the fold, because the new line gets the
fold level from the above line (level '='), which extends the fold to
the new line. `O` has a similar problem when run on the line below a
fold.

Solution:
Use -1 for the added line to get the lower level from the above/below
line.
2024-06-20 06:37:09 -07:00
Guilherme Soares
c4eb0b64bd fix(treesitter): find buffer in multiple windows #28922
Problem:
1. When interacting with multiple :InspectTree and the source buffer
   windows there is a high chance of errors due to the window ids not
   being updated and validated.
2. Not all InspectTree windows were closed when the source buffer was
   closed.

Solution:
1. Update InspectTree window id on `CursorMoved` event and validate
   source buffer window id before trying to navigate to it.
2. Close all InspectTree windows
2024-05-27 04:20:03 -07:00
vanaigr
4b02916334 perf(treesitter): use child_containing_descendant() in has-ancestor? (#28512)
Problem: `has-ancestor?` is O(n²) for the depth of the tree since it iterates over each of the node's ancestors (bottom-up), and each ancestor takes O(n) time.
This happens because tree-sitter's nodes don't store their parent nodes, and the tree is searched (top-down) each time a new parent is requested.

Solution: Make use of new `ts_node_child_containing_descendant()` in tree-sitter v0.22.6 (which is now the minimum required version) to rewrite the `has-ancestor?` predicate in C to become O(n).

For a sample file, decreases the time taken by `has-ancestor?` from 360ms to 6ms.
2024-05-16 16:57:58 +02:00
Riley Bruins
6a264e0897 fix(treesitter): allow optional directive captures (#28664) 2024-05-14 09:14:43 -05:00
bfredl
0df681a91d fix(treesitter): make tests for memoize more robust
Instead of painfully messing with timing to determine if queries were
reparsed, we can simply keep a counter next to the call to ts_query_new

Also memoization had a hidden dependency on the garbage collection of
the the key, a hash value which never is kept around in memory. this was
done intentionally as the hash does not capture all relevant state for the
query (external included files) even if actual query objects still
would be reachable in memory. To make the test fully deterministic in
CI, we explicitly control GC.
2024-04-29 16:20:46 +02:00
Christian Clason
26b5405d18 fix(treesitter): enforce lowercase language names (#28546)
* fix(treesitter): enforce lowercase language names

Problem: On case-insensitive file systems (e.g., macOS), `has_parser`
will return `true` for uppercase aliases, which will then try to inject
the uppercase language unsuccessfully.

Solution: Enforce and assume parser names to be lowercase when
resolving language names.
2024-04-28 16:27:47 +02:00
dundargoc
052498ed42 test: improve test conventions
Specifically, functions that are run in the context of the test runner
are put in module `test/testutil.lua` while the functions that are run
in the context of the test session are put in
`test/functional/testnvim.lua`.

Closes https://github.com/neovim/neovim/issues/27004.
2024-04-23 18:17:04 +02:00