233 Commits

Author SHA1 Message Date
Nathan Zeng
a61c8f3580 refactor(test): use public enum in select_spec #39665
Problem: select_spec tests a public function, but uses private enums.

Solution: Replace private enums with public enums.
2026-05-08 03:38:35 -04:00
Szymon Wilczek
0c3e6e1b0e fix(treesitter): crash in ts_parser_delete after gc #39497
Problem:
parser_gc() calls ts_parser_delete() but leaves the userdata pointer
pointing to freed memory. If the GC finalizer runs at an unexpected time
(e.g. inside nvim_buf_get_lines #39411), a stale pointer could cause a crash.

Solution:
- NULL out `*ud` after ts_parser_delete() in parser_gc()
- Update parser_check() to handle NULL with a clear error message,
guarding all parser methods against UAF

Co-authored-by: Lewis Russell <lewis6991@gmail.com>
Signed-off-by: Szymon Wilczek <swilczek.lx@gmail.com>
2026-05-07 08:39:07 -04:00
Justin M. Keyes
bf8cdb340b fix(treesitter): drop select() kwargs #39646
Problem:
Unnecessary kwargs.

Solution:
Use param instead of kwargs.
2026-05-07 11:14:17 +00:00
Nathan Zeng
c286c9d686 feat(treesitter): provide select() #39069
Problem: No public method for treesitter incremental selection.

Solution: Add `vim.treesitter.select()`.
2026-05-06 06:16:58 -04:00
David Balatero
7ed5609439 fix(treesitter): get_node_text() inconsistent trailing newline #39409
Problem:
`get_node_text()` returned inconsistent results between buffer and
string sources when a node's range ends at `end_col == 0` (i.e. the node
ends with a newline). The buffer path dropped the trailing newline; the
string path included it correctly.

Solution:
Append `'\n'` in `buf_range_get_text()` when `end_col == 0` and
`start_row ~= end_row`. The `start_row ~= end_row` guard excludes
zero-width nodes at column 0, which should return `""`.

Remove the workaround in the `#trim!` directive that manually
compensated for the missing newline.

Strip whitespace in `resolve_lang()` so injection language nodes ending
at `end_col == 0` (e.g. `">lua\n"`) still resolve correctly.
2026-05-03 09:23:32 -04:00
Lewis Russell
33ea63011c perf(treesitter): reuse edited tree ranges for callbacks
After an edit, LanguageTree:_edit() updates the current trees. When the
LanguageTree manages explicit regions, _edit() also refreshes _regions
from tree:included_ranges(true), so those regions have the edited byte
offsets.

A later injection pass may call set_included_regions() with a different
number of child regions. That path discards the old trees and emits
changedtree callbacks for them. invalidate(true) does the same when a
buffer is reloaded. Before this change, both discard paths called
tree:included_ranges(true) for every old tree, even if _edit() had just
collected those exact ranges.

That duplicate range extraction is expensive with many injection trees.
Realistic shapes include generated C files with many macro bodies parsed
by the C preproc_arg injection, Markdown documents with many fenced blocks
of the same language, and template files with many embedded-language
islands. The stock highlighter registers recursive changedtree callbacks,
so this is on the normal highlighting edit path.

Track whether _regions currently came from tree:included_ranges(true)
with _regions_from_tree_ranges. _do_changedtree_callbacks() reuses
_regions only in that state; otherwise it falls back to calling
tree:included_ranges(true). Clear the marker when regions are replaced by
injection ranges, when a tree is reparsed, or when trees are discarded.

This avoids keeping a second copy of the ranges while preserving callback
precision: changedtree still receives tree:included_ranges(true) for the
old tree, not the broader managed region.

Benchmark on 100k C macro injections, one-line edit, recursive
changedtree callback:

- HEAD median: edited parse 84.2 ms, child region replacement 58.7 ms
- This change: edited parse 34.6 ms, child region replacement 8.5 ms

That is about 2.4x faster for the edit parse and 6.9x faster for child
region replacement in this workload.

Add a regression test that replacing injection regions still fires
changedtree and still reports the old tree's exact included ranges.

AI-assisted: Codex
2026-04-28 17:38:09 +01:00
Peter Cardenas
eeee4bd4fc feat(treesitter/extmark): support removing a conceal highlight #35087
Problem:
Cannot remove a `@conceal` highlight when defined in highlights.scm.

Solution:
Support a `@noconceal` highlight that works similarly to `@nospell` where it
overrides the conceal set on the range to remove it. Additionally, can
set the conceal metadata field to false for the same behavior.
2026-04-25 11:42:44 -04:00
altermo
451811b1be feat(treesitter): expand selection to sibling node #38938
Problem:
Can't expand treesitter-incremental-selection to the next and previous
sibling nodes.

Solution:
Pressing `]N` in visual mode will expand the selection to the next
sibling node, and `[N` will do the same with the previous node.
2026-04-22 17:10:24 -04:00
Yi Ming
e84076c7c6 test(lsp): extract buf/util parts from lsp_spec.lua #39149
Problem:
`test/functional/plugin/lsp_spec.lua` had grown into a large catch-all file that mixed core LSP client lifecycle coverage, `vim.lsp.buf.*` behavior, and `vim.lsp.util.*` behavior in one place.

Solution:
Split the large tests into more focused test files without changing test coverage or intended behavior.

After this change, `lsp_spec.lua` is more focused on core LSP client/config/dynamic-registration behavior.
2026-04-17 15:27:50 -04:00
altermo
bb2284d75e fix(treesitter): TSNode:id() with NUL byte causes unreliable select() #39134
Problem:
`TSNode:id()` returns the underlying c pointer as a string, which may include
NUL bytes. In PUC Lua, `('%s'):format('\0a\0')` returns `''` and not `'\0a\0'`
(i.e. treats the string as a c-string (which terminates at the NUL byte)).

This resulted in two different nodes being able to have the same id.

Solution:
Use concatenation `..` instead of `string.format()`.
2026-04-16 13:52:20 -04:00
Barrett Ruth
3838a2579e fix(treesitter): restore highlighting on 32 bit systems #39091
Problem: Treesitter highlighting regressed on 32-bit builds because ranges that should cover the whole buffer were corrupted when passed into Lua.

Solution: Round-trip those range values through Lua and validate them so treesitter sees the same ranges on 32 and 64-bit builds.
2026-04-16 03:59:20 -04:00
altermo
1bcf2d7f90 fix(treesitter): select with node ending with unicode char (#38557)
Co-authored-by: zeertzjq <zeertzjq@outlook.com>
2026-03-31 13:03:18 +08:00
Justin M. Keyes
a3b48b1054 docs: api, plugins, ui2 2026-03-24 00:14:55 +01:00
vanaigr
3449487a88 test: issue #35575 2026-03-23 19:36:48 +01:00
Gregory Anders
a728eb7af1 refactor(treesitter)!: remove "all" option of Query:iter_matches #33070
This option was introduced to help with transitioning to the new
behavior during the 0.11 release cycle with the intention of removing in
0.12.
2026-03-23 14:34:14 -04:00
altermo
bc00aec21c test(treesitter): skip unreliable select test #38391
The test sometimes(around 1/30) fails on PUC-lua.
2026-03-20 17:42:27 -04:00
nikolightsaber
fd1e019e90 refactor(treesitter)!: get_parser return nil on error #37276 2026-03-13 15:57:36 -04:00
Justin M. Keyes
b8a976afda docs: api, messages, lsp, trust
gen_vimdoc.lua: In prepare for the upcoming release, comment-out the
"Experimental" warning for prerelease features.
2026-03-11 18:00:18 +01:00
altermo
72d3a57f27 feat(treesitter): incremental selection
Co-authored-by: György Andorka <gyorgy.andorka@protonmail.com>
2026-03-08 11:07:49 +01:00
Stefan VanBuren
01817eb6f3 fix(treesitter): normalize language aliases
Hyphenated language names are silently dropped when used as injections
(see #38132).

This combines the normalization of language aliases into `resolve_lang`,
and also adds the normalization of hyphens to underscores, which allows
for handling of injected language tags with hyphens in their names.

Fixes #38132.
2026-03-04 17:15:59 +01:00
zeertzjq
34b3bd1ac5 test(treesitter/fold_spec): fix flaky test (#38075) 2026-02-26 21:28:52 +08:00
phanium
16aab4cb48 fix(treesitter): InspectTree only show the largest injection #37906
Problem:
:InspectTree don't show luadoc injection lua file. Since luadoc share
the same "root" with comment in their common primary (lua) tree.
Current logic simply show the largest (comment injection) and ignore all
smaller one (luadoc injection).

Solution:
Handle different lang injections separately. Then sort them by
byte_length to ensure the draw tree consistent.
2026-02-24 16:22:30 -05:00
zeertzjq
e3d46a6337 test: start test runners in Xtest_xdg dir (#37964)
This is a better way to prevent parallel tests from interfering with
each other, as there are many ways files can be created and deleted in
tests, so enforcing different file names is hard.

Using $TMPDIR can also work in most cases, but 'backipskip' etc. have
special defaults for $TMPDIR.

Symlink runtime/, src/, test/ and README.md to Xtest_xdg dir to make
tests more convenient (and symlinking test/ is required for busted).

Also, use README.md instead of test/README.md in the Ex mode inccommand
test, as test/README.md no longer contains 'N' char.
2026-02-20 06:53:33 +08:00
zeertzjq
08f4811061 fix(treesitter): :InspectTree wrong title for non-relative path #37965
Problem:  :InspectTree sets wrong title for file with non-relative path.
Solution: Use full path if relpath() returns nil.
2026-02-19 12:05:48 -05:00
ashab-k
86c939ba91 fix(treesitter): fix spell navigation on first line (#37361)
Problem:  Spell navigation skips words on the first line because
          _on_spell_nav passes an empty range (0,0) to the highlighter.

Solution: Use math.max(erow, srow + 1) to ensure a valid search window.

Signed-off-by: ashab-k <ashabkhan2000@gmail.com>
2026-01-15 10:20:24 +08:00
Riley Bruins
976a47e81b Revert "refactor(treesitter): use scratch buffer for string parser" #36964
This reverts commit 2a7cb32959.
2025-12-15 02:09:36 -05:00
Riley Bruins
ac15b384a6 refactor(treesitter): use scratch buffer for string parser #35988
This commit changes `languagetree.lua` so that it creates a scratch
buffer under the hood when dealing with string parsers. This will make
it much easier to just use extmarks whenever we need to track injection
trees in `languagetree.lua`. This also allows us to remove the
`treesitter.c` code for parsing a string directly.

Note that the string parser's scratch buffer has `set noeol nofixeol` so
that the parsed source exactly matches the passed in string.
2025-10-02 15:33:18 -07:00
Riley Bruins
b4016f4525 fix(treesitter): don't add final newline if not present #35970
**Problem(?):** Buffers that (for whatever reason) aren't meant to have
a final newline are still parsed with a final newline in `treesitter.c`.

**Solution:** Don't add the newline to the last buffer line if it
shouldn't be there. (This more closely matches the approach of
`read_buffer_into()`.)

This allows us to, say, use a scratch buffer with `noeol` and `nofixeol`
behind the scenes in `get_string_parser()`.

...which would allow us to track injection trees with extmarks in that
case.

...which would allow us to not drop previous trees after reparsing a
different range with `get_parser():parse()`.

...which would prevent flickering when editing a buffer that has 2+
windows to it in view at a time.

...which would allow us to keep our sanity!!!

(one step at a time...)
2025-10-01 17:31:52 -07:00
zeertzjq
eb7c12d3e3 test: remove a few more redundant clear() calls (#35903) 2025-09-24 14:25:54 +08:00
vanaigr
5edbabdbec perf: add on_range in treesitter highlighting 2025-08-28 08:22:38 -05:00
tao
d73cfefed5 fix(folds): error when deleting lines at end of buffer #35396
Problem:
with `foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr()
foldminlines=0`, deleting lines at the end of the buffer always
reports an invalid top error, because the top value (i.e. the
start line number of the deletion) is always 1 greater than
the total line number of the modified buffer.

Solution:
remove the ml_line_count validation
2025-08-21 19:55:45 -07:00
Sean Dewar
3ec63cdab8 fix(treesitter): run FileType autocmds in the context of <abuf>
Problem: many FileType autocommands assume curbuf is the same as the target
buffer; this can cause &syntax to be restored for the wrong buffer in some cases
when TSHighlighter:destroy is called.

Solution: run nvim_exec_autocmds in the context of the target buffer via
nvim_buf_call.
2025-08-19 20:03:05 +01:00
Justin M. Keyes
7bf04bc01f test(fold): flaky "doesn't open folds that are not touched" #34911 2025-07-13 17:05:01 -07:00
Artem
18e0301e1f fix(treesitter): inconsistent highlight of multiline combined injection #32619
Problem:
Combined injections not entirely highlighted.

Solution:
Reapply layer highlights on each line.
2025-07-06 11:05:41 -07:00
Rodrigodd
94f44d58fd test(treesitter): test tree:root() is idempotent
Test for regression #34605
2025-07-02 17:05:17 +01:00
Justin M. Keyes
8001276bd0 docs: vim.fs., diagnostics, lsp #34402 2025-06-13 07:49:21 -07:00
Riley Bruins
1d5b3b5b4c feat(treesitter)!: apply offset! directive to all captures #34383
This commit changes the `offset!` directive so that instead of setting a
`metadata.range` value for the entire pattern, it will set a
`metadata.offset` value. This offset will be applied to the range only
in `vim.treesitter.get_range()`, rather than at directive application
time. This allows the offset to be applied to any and all nodes captured
by the given pattern, and removes the requirement that `#offset!` be
applied to only a single node.

The downside of this change is that plugins which read from
`metadata.range` may be thrown off course, but such plugins should
prefer `vim.treesitter.get_range()` when retrieving ranges anyway.

Note that `#trim!` still sets `metadata.range`, and
`vim.treesitter.get_range()` still reads from `metadata.range`, if it
exists.
2025-06-13 06:42:10 -07:00
Justin M. Keyes
ca9689858d ci: skip flaky fold test on freebsd/cirrus
FAILED
    test/functional/treesitter/fold_spec.lua
     @
    720
    :
    treesitter foldexpr doesn't open folds that are not touched
    test/functional/treesitter/fold_spec.lua:767: Row 1 did not match.
    Expected:
      |*{1:-}^t1                                     |
      |*{1:-}# h2                                   |
      |*{1:│}t2                                     |
      |{3:~                                       }|
      |{3:~                                       }|
      |{3:~                                       }|
      |{3:~                                       }|
      |1 line less; before #2  {MATCH:.*}|
    Actual:
      |*{1: }^t1                                     |
      |*{1:+}{2:+--  2 lines: # h2·····················}|
      |*{3:~                                       }|
      |{3:~                                       }|
      |{3:~                                       }|
      |{3:~                                       }|
      |{3:~                                       }|
      |1 line less; before #2  0 seconds ago   |
    To print the expect() call that would assert the current screen state, use
    screen:snapshot_util(). In case of non-deterministic failures, use
    screen:redraw_debug() to show all intermediate screen states.
    Snapshot:
    screen:expect([[
      {1: }^t1                                     |
      {1:+}{2:+--  2 lines: # h2·····················}|
      {3:~                                       }|*5
      1 line less; before #2  0 seconds ago   |
    ]])
2025-06-09 15:46:58 +02:00
zeertzjq
d2dad30898 vim-patch:9.1.1439: Last diff folds not merged (#34380)
Problem:  Last diff folds not merged (after v8.1.1922)
Solution: loop over all windows in the current tabpage and update all
          folds (Gary Johnson)

This commit fixes a bug where the last two folds of a diff are not
merged when the last difference between the two diff'd buffers is
resolved.

Normally, when two buffers are diff'd, folding is used to show only the
text that differs and to hide the text that is the same between the two
buffers.  When a difference is resolved by making a block of text the
same in both buffers, the folds are updated to merge that block with the
folds above and below it into one closed fold.

That updating of the folds did not occur when the block of text was the
last diff block in the buffers.

The bug was introduced by this patch on August 24, 2019:

    patch 8.1.1922: in diff mode global operations can be very slow

    Problem:    In diff mode global operations can be very slow.
    Solution:   Do not call diff_redraw() many times, call it once when
		redrawing.  And also don't update folds multiple times.

Unfortunately, folds were then not updated often enough.

The problem was fixed by adding a short loop to the ex_diffgetput()
function in diff.c to update all the folds in the current tab when the
last difference is removed.

A test for this was added to test_diffmode.vim.  Two of the reference
screen dumps for another test in that file,
Test_diffget_diffput_linematch(), had to be changed to have all the
folds closed rather than to have the last diff block remain open.

closes: vim/vim#17457

3fa0d3514b

Co-authored-by: Gary Johnson <garyjohn@spocom.com>
2025-06-09 06:57:54 +08:00
Rodrigodd
4c333fdbb7 test(treesitter): test node access after tree edit 2025-06-06 15:35:52 +01:00
Christian Clason
8a207b3e19 build(deps): bump tree-sitter-c to v0.24.1 2025-05-24 20:02:29 +02:00
Riley Bruins
baabc35987 fix(treesitter): properly clip nested injections 2025-05-24 11:51:11 +01:00
Natanael Copa
c2d218d0c6 test(treesitter): add flaky test to TEST_SKIP_FRAGILE #33912
ref: https://github.com/neovim/neovim/issues/33910#issuecomment-2863276239
2025-05-09 12:26:17 -07:00
luukvbaal
2aa7948266 fix(treesitter): invalidate conceal_lines cache earlier #33875
Problem:  conceal_lines cache is invalidated in `on_buf`
          which is too late for code calculating text height after a
          buffer change but before a redraw (like `lsp/util.lua`).

Solution: Replace `on_buf` with `on_bytes` handler that invalidates
          the cache and clears the marks.
2025-05-06 18:03:54 -07:00
luukvbaal
276860b538 fix(messages): clear 'showmode' message after insert_expand #33607
Problem:  'showmode' ext_messages state is not cleared after insert_expand mode.
Solution: Replace empty message with more idiomatic way to clear the showmode.
2025-04-24 12:11:49 -07:00
Riley Bruins
0977f70f4d fix(treesitter): injected lang ranges may cross capture boundaries #32549
Problem:
treesitter injected language ranges sometimes cross over the capture
boundaries when `@combined`.

Solution:
Clip child regions to not spill out of parent regions within
languagetree.lua, and only apply highlights within those regions in
highlighter.lua.


Co-authored-by: Cormac Relf <web@cormacrelf.net>
2025-04-13 14:22:17 -07:00
Riley Bruins
75cbd9a8ae refactor(treesitter): simplify injection retrieval #33104
Simplify the logic for retrieving the injection ranges for the language
tree. The trees are now also sorted by starting position, regardless of
whether they are part of a combined injection or not. This would be
helpful if ranges are ever to be stored in an interval tree or other
kind of sorted tree structure.
2025-03-28 04:38:47 -07:00
Justin M. Keyes
2e68e9c051 fix: temporarily disable 0.12 deprecation tests
Re-enable these after release.
2025-03-26 14:49:23 +01:00
Ian Chamberlain
8b5a0a00c8 feat(treesitter): allow disabling captures and patterns on TSQuery (#32790)
Problem: Cannot disable individual captures and patterns in treesitter queries.

Solution: 
* Expose the corresponding tree-sitter API functions for `TSQuery` object. 
* Add documentation for `TSQuery`.
* Return the pattern ID from `get_captures_at_pos()` (and hence `:Inspect!`).
2025-03-11 14:45:01 +01:00
Lewis Russell
ec8922978e feat(treesitter): add more metadata to language.inspect() (#32657)
Problem: No way to check the version of a treesitter parser.

Solution: Add version metadata (ABI 15 parsers only) as well as parser state count and supertype information (ABI 15) in `vim.treesitter.language.inspect()`. Also graduate the `abi_version` field, as this is now the official upstream name.

---------

Co-authored-by: Christian Clason <c.clason@uni-graz.at>
2025-03-01 15:51:09 +00:00