aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/treesitter/languagetree.lua
Commit message (Collapse)AuthorAge
* fix(lua): enable param-type-mismatch diagnosticLewis Russell2026-09-28
| | | | | | | | | Problem: Argument type mismatches are excluded from EmmyLua checks. Solution: Correct runtime annotations and calls, document local checker workarounds, and enable the diagnostic. AI-assisted
* fix(lua): enable assignment type checking in runtime codeLewis Russell2026-09-25
| | | | | | | | | | | Enable EmmyLua's assign-type-mismatch diagnostic after correcting the runtime annotations and assignments it flags. Correct option, tuple, and return types, and use separate locals when a value changes representation. Add guards and narrow casts where needed so the runtime passes the stricter check. Regenerate the affected docs. AI-assisted
* fix(lua): enable nil-safety diagnostics in runtime codeLewis Russell2026-09-25
| | | | | | | | | | | | Enable EmmyLua's need-check-nil diagnostic and resolve nullable values through more precise types, explicit invariants, and guards for missing runtime state. Keep analyzer-specific workarounds local and update the affected API documentation. Exclude the bundled re.lua from analysis so its nil-check warnings do not fail CI. AI-assisted
* fix(treesitter): type synchronous parse results as non-nilLewis Russell2026-09-25
| | | | | | | | LanguageTree:parse() always returns trees when no callback is supplied, but its shared return type also permits nil for asynchronous parsing. Add the synchronous overload and document that distinction. AI-assisted
* fix(lua): add missing param annotations (#41911)Lewis Russell2026-09-15
|
* build: enable more EmmyLua checksLewis Russell2026-09-09
| | | | | | | | | | Enable checks for unused code, deprecated calls, return types, and annotations. Fix the warnings and replace the deprecated highlight function. Keep exceptions where old APIs are still needed or the checker gets it wrong. AI-assisted
* build: enable more EmmyLua checksLewis Russell2026-09-08
| | | | | | | | | | | | | | | | - call-non-callable - missing-fields - redefined-label - redefined-local - undefined-field - unreachable-code Promote redefined-local and unreachable-code to warnings. Fix DOS line-ending handling for multiline semantic tokens, and support intersection types in the help parser. AI-assisted
* build: replace LuaLS with EmmyLuaLewis Russell2026-09-07
| | | | | | | | | | | | | | | Problem: LuaLS struggles with the generics used in Nvim's runtime, requiring broad diagnostic suppressions. Indexing is also slow. Solution: Use EmmyLua for type checks in the build and CI. It offers more sophisticated type checking, substantially better support for generics, and much better flow analysis. Correct the affected annotations. Use `@internal`, supported directly by EmmyLua, instead of `@nodoc` for shared internal declarations, and support it in the help parser. AI-assisted
* feat(lua)!: vim.isnil, vim.nonnil, deprecate vim.F #39495Olivia Kinnear2026-05-06
|
* fix(treesitter): get_node_text() inconsistent trailing newline #39409David Balatero2026-05-03
| | | | | | | | | | | | | | | | | | 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.
* build(lint): allow "bufnr" as positional param #39515Justin M. Keyes2026-04-30
| | | | | Allow `bufnr` as a positional param name because it is very common. However as a field name, or part of a function name, it is usually a mistake.
* perf(treesitter): reuse edited tree ranges for callbacksLewis Russell2026-04-28
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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
* fix(treesitter): escape hyphen in lua patternStefan VanBuren2026-03-07
| | | | Ref: https://github.com/neovim/neovim/pull/38140#discussion_r2897235978
* fix(treesitter): normalize language aliasesStefan VanBuren2026-03-04
| | | | | | | | | | | 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.
* Revert "refactor(treesitter): use scratch buffer for string parser" #36964Riley Bruins2025-12-15
| | | This reverts commit 2a7cb32959b4c616bd2c76ae1933f8e068e391ad.
* perf(treesitter): parse multiple ranges in languagetree, eliminate ↵Riley Bruins2025-11-18
| | | | | | | | | | | flickering #36503 **Problem:** Whenever `LanguageTree:parse()` is called, injection trees from previously parsed ranges are dropped. **Solution:** Allow the function to accept a list of ranges, so it can return injection trees for all the given ranges. Co-authored-by: Jaehwang Jung <tomtomjhj@gmail.com>
* refactor(treesitter): use scratch buffer for string parser #35988Riley Bruins2025-10-02
| | | | | | | | | | 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.
* docs(treesitter): fix language-injection url #35592Yochem van Rosmalen2025-09-01
|
* fix(treesitter): ensure TSLuaTree is always immutableRodrigodd2025-07-02
| | | | | | | | | | | | | | | | Problem: The previous fix in #34314 relies on copying the tree in `tree_root` to ensure the `TSNode`'s tree cannot be mutated. But that causes the problem where two calls to `tree_root` return nodes from different copies of a tree, which do not compare as equal. This has broken at least one plugin. Solution: Make all `TSTree`s on the Lua side always immutable, avoiding the need to copy the tree in `tree_root`, and make the only mutation point, `tree_edit`, copy the tree instead.
* fix(treesitter): support multiple `@injection.content` capturesRiley Bruins2025-06-08
| | | | | | | Before, only the last capture's range would be counted for injection. Now all captured ranges will be counted in the ranges array. This is more intuitive, and also provides a nice solution/alternative to the "scoped injections" issue.
* fix: type fixesLewis Russell2025-06-06
| | | | Type fixes caught by emmylua
* fix(treesitter): properly clip nested injectionsRiley Bruins2025-05-24
|
* fix(treesitter): proper tree `contains()` logic with combined injectionsRiley Bruins2025-05-11
| | | | | | | | | | | | **Problem:** `LanguageTree:contains()` considers any range within the start of the first tree and end of the last tree as "within" the language tree. In the case of combined injections, this is problematic because we only want to consider ranges within any of the combined trees as "contained" (as opposed to any range within the entire range spanned by all combined trees). **Solution:** Use a more discriminative check in `LanguageTree:contains()`.
* refactor(docs): remove unnecessary `@private`/`@nodoc` annotations (#33951)Maria José Solano2025-05-10
| | | | | * refactor(docs): remove `@private` annotations from local functions * refactor(docs): remove unnecessary `@nodoc` annotations
* fix(treesitter): injected lang ranges may cross capture boundaries #32549Riley Bruins2025-04-13
| | | | | | | | | | | | | 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>
* refactor(treesitter): migrate to ts parser callback API #33141Riley Bruins2025-03-29
| | | | | Remove the `set_timeout` functions for `TSParser` and instead add a timeout parameter to the regular parse function. Remove these deprecated tree-sitter API functions and replace them with the preferred `TSParseOptions` style.
* refactor(treesitter): simplify injection retrieval #33104Riley Bruins2025-03-28
| | | | | | | 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.
* refactor(treesitter): simplify parsing coroutine logicRiley Bruins2025-02-23
| | | | | | | | | Lua coroutines can yield across non-coroutine function boundaries, meaning that we don't need to wrap each helper function in a coroutine and resume it within `_parse()`. If we just have them yield when appropriate, this will be caught by the top level `_parse()` coroutine, and resuming the `_parse()` will resume from the position in the helper function where we yielded last.
* perf(treesitter): don't block when finding injection rangesRiley Bruins2025-02-21
| | | | | | | | | | | **Problem:** Currently, parsing is asynchronous, but it involves a (sometimes lengthy) step which finds all injection ranges for a tree by iterating over that language's injection queries. This causes edits in large files to be extremely slow, and also causes a long stutter during the initial parse of a large file. **Solution:** Break up the injection query iteration over multiple event loop iterations.
* perf(treesitter): only search for injections within the parse rangeRiley Bruins2025-02-21
| | | | Co-authored-by: Jaehwang Jung <tomtomjhj@gmail.com>
* fix(treesitter): separately track the number of valid regionsRiley Bruins2025-02-17
| | | | | | We need to add a separate variable to keep track of this information, since we cannot read the length of the valid regions table itself, since it has holes.
* fix(treesitter): detect trees with outdated regions in `is_valid()`Riley Bruins2025-02-11
|
* feat(treesitter): allow LanguageTree:is_valid() to accept a rangeRiley Bruins2025-02-02
| | | | | | 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.
* refactor(treesitter): use coroutines for resuming _parse() logicRiley Bruins2025-02-02
| | | | | | This means that all work previously done by a `_parse()` iteration will be kept in future iterations. This prevents it from running indefinitely in some cases where the file is very large and there are 2+ injections.
* refactor(treesitter): always return valid range from parse() #32273Riley Bruins2025-02-02
| | | | | | | | | | | Problem: When running an initial parse, parse() returns an empty table rather than an actual range. In `languagetree.lua`, we manually check if a parse was incremental to determine the changed parse region. Solution: - Always return a range (in the C side) from parse(). - Simplify the language tree code a bit. - Logger no longer shows empty ranges on the initial parse.
* refactor(treesitter): drop `LanguageTree._has_regions` #32274Riley Bruins2025-02-02
| | | | | | | | | | | | | | | | This simplifies some logic in `languagetree.lua`, removing the need for `_has_regions`, and removing side effects in `:included_regions()`. Before: - Edit is made which sets `_regions = nil` - Upon the next call to `included_regions()` (usually right after we marked `_regions` as `nil` due to an `_iter_regions()` call), if `_regions` is nil, we repopulate the table (as long as the tree actually has regions) After: - Edit is made which resets `_regions` if it exists - `included_regions()` no longer needs to perform this logic itself, and also no longer needs to read a `_has_regions` variable
* fix(treesitter): nil access when running string parser asyncRiley Bruins2025-02-01
|
* docs: miscdundargoc2025-01-30
| | | | | | | | | Co-authored-by: Dustin S. <dstackmasta27@gmail.com> Co-authored-by: Ferenc Fejes <fejes@inf.elte.hu> Co-authored-by: Maria José Solano <majosolano99@gmail.com> Co-authored-by: Yochem van Rosmalen <git@yochem.nl> Co-authored-by: brianhuster <phambinhanctb2004@gmail.com> Co-authored-by: zeertzjq <zeertzjq@outlook.com>
* fix(treesitter): stop async parsing if buffer is invalidnotomo2025-01-29
| | | | | Problem: Error occurs if delete buffer in the middle of parsing. Solution: Check if buffer is valid in parsing.
* fix(treesitter): empty queries can disable injections (#31748)Riley Bruins2025-01-28
| | | | | | | | | | | | **Problem:** Currently, if users want to efficiently disable injections, they have to delete the injection query files at their runtime path. This is because we only check for existence of the files before running the query over the entire buffer. **Solution:** Check for existence of query files, *and* that those files actually have captures. This will allow users to just comment out existing queries (or better yet, just add their own injection query to `~/.config/nvim` which contains only comments) to disable running the query over the entire buffer (a potentially slow operation)
* fix: resolve all remaining LuaLS diagnosticsLewis Russell2025-01-27
|
* docs(treesitter): expose LanguageTree:parent() #32108Jaehwang Jung2025-01-20
| | | | | Plugins may want to climb up the LanguageTree. Also add missing type annotations for other methods.
* fix(treesitter): clean up parsing queueJaehwang Jung2025-01-19
|
* feat(treesitter): async parsingRiley Bruins2025-01-12
| | | | | | | | | | | | **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>
* refactor(treesitter): simplify condition #31889Riley Bruins2025-01-06
|
* fix(treesitter): remove redundant on_bytes callback #31041luukvbaal2024-11-16
| | | | | | | Problem: Treesitter highlighter implements an on_bytes callback that just re-marks a buffer range for redraw. The edit that prompted the callback will already have done that. Solution: Remove redundant on_bytes callback from the treesitter highlighter module.
* feat(treesitter)!: use return values in `language.add()`Christian Clason2024-09-29
| | | | | | | | | 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.
* feat(treesitter)!: default to correct behavior for quantified captures (#30193)Gregory Anders2024-09-01
| | | | | | | | | | | | 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.
* docs(treesitter): generate inline docs for `Range`sYi Ming2024-08-06
| | | | | | | | docs(treesitter): in-place parameter description docs(treesitter): remove internal type names docs(treesitter): add missing private annotation
* feat(treesitter): add node_for_range functionRiley Bruins2024-07-29
| | | | | | 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`.