Added
vim.treesitterAPI — full treesitter integration backed byweb-tree-sitter(WASM), running as a parallel parser alongside CM6's Lezer. Markdown and HTML grammars are bundled; the subsystem activates on-demand when Lua code callsget_parser(). Provides the complete Neovimvim.treesitterAPI surface:- Core:
get_parser,get_string_parser,get_node,get_node_text,get_range,get_node_range,is_in_node_range,is_ancestor,node_contains,get_captures_at_pos,get_captures_at_cursor - TSNode: 31 methods (
type,parent,child,named_child,field,start,end_,range,sexpr,equal,iter_children,named_children,descendant_for_range, and more) - TSTree:
root,copy,included_ranges - LanguageTree: 18 methods (
parse,trees,lang,children,parent,root,for_each_tree,register_cbs,node_for_range,tree_for_range,language_for_range,invalidate,destroy, and more) - Query engine:
query.parse,query.get,query.set,Query:iter_captures,Query:iter_matches,Query:disable_capture,Query:disable_pattern - 8 built-in predicates:
#eq?,#match?,#vim-match?,#lua-match?,#contains?,#any-of?,#has-ancestor?,#has-parent?with generic#not-*/#any-*prefix handling - 4 built-in directives:
#set!,#offset!,#gsub!,#trim! - Custom predicates/directives:
query.add_predicate,query.add_directive,query.list_predicates,query.list_directives - Language management:
language.register,language.get_lang,language.get_filetypes,language.add(async via coroutine bridge),language.inspect(ABI version, fields, symbols, supertypes) - Stubs (present, won't error):
start,stop,foldexpr,select,inspect_tree - Plugin:
src/treesitter/runtime.ts,src/treesitter/bridge.ts,src/treesitter/query.ts,src/treesitter/predicates.ts,src/treesitter/directives.ts,src/treesitter/language-tree.ts,src/treesitter/injection.ts,src/treesitter/types.ts,src/treesitter/wasm.d.ts - Plugin:
src/lua/treesitter/api.ts,src/lua/treesitter/node.ts,src/lua/treesitter/tree.ts,src/lua/treesitter/language.ts,src/lua/treesitter/query-api.ts,src/lua/treesitter/language-tree-api.ts,src/lua/treesitter/range.ts - Grammars:
src/treesitter/grammars/tree-sitter-markdown.wasm,src/treesitter/grammars/tree-sitter-html.wasm
- Core:
- Treesitter-enhanced Markdown features — core plugin features now use treesitter for structural parsing when available, with regex fallback:
- Fold provider: treesitter
sectionnode hierarchy for heading fold boundaries (replaces heading-level regex) - Fold placeholder: treesitter-based heading text and code language extraction
- Heading navigation (
]h/[h):getAllNodesOfType(view, 'atx_heading')replaces line-by-line regex scan; automatically skips headings inside fenced code blocks - Code block text objects (
iC/aC):findContainingNodeOfType(view, row, col, 'fenced_code_block')replaces full-document fence scan - Blockquote text objects (
iB/aB):findContainingNodeOfType(view, row, col, 'block_quote')with correct nesting depth (fixes nesteddiBscoping) - Delimiter text objects (
i*/a*,i_/a_,i`/a`,i$/a$,i~/a~): inline grammar nodes (emphasis,strong_emphasis,code_span,latex_block,strikethrough) for correct nested delimiter boundaries - Snippet context detection: treesitter-based code/prose/frontmatter detection (O(1) vs O(n) document scan)
- Plugin:
src/text-objects/code-block.ts,src/text-objects/blockquote.ts,src/text-objects/delimiter.ts,src/motions/headings.ts,src/snippets/context.ts,src/fold/provider.ts,src/fold/placeholder.ts
- Fold provider: treesitter
- Markdown inline grammar — bundled
tree-sitter-markdown-inline.wasm(416KB) for inline content parsing. Provides structural nodes for emphasis, strong emphasis, code spans, inline links, strikethrough, LaTeX, HTML tags, and backslash escapes.- Plugin:
src/treesitter/grammars/tree-sitter-markdown-inline.wasm - Plugin:
src/treesitter/runtime.ts(parseInlineContent,getInlineNodeAtPosition)
- Plugin:
- JS-side treesitter API — TypeScript helper functions for querying treesitter trees from plugin code (not Lua):
- Position lookup:
getNodeAtPosition,getNamedNodeAtPosition,getInlineNodeAtPosition - Ancestor queries:
hasAncestorOfType,findAncestorOfType,isInsideNodeType,findContainingNodeOfType - Inline nodes:
findContainingInlineNodeOfType,isInsideInlineNodeType - Navigation:
findNextNodeOfType,getAllNodesOfType - Full queries:
queryCaptures - Plugin:
src/treesitter/js-api.ts,src/treesitter/tree-state.ts
- Position lookup:
- Fork:
setTokenClassifierhook — codemirror-vim fork now exposesVim.setTokenClassifier(fn)for host-provided token classification. The%bracket matcher uses the classifier to skip brackets inside inline code spans, improving Markdown bracket matching accuracy.- Fork:
~/Repos/codemirror-vim/src/vim.js(setTokenClassifier,moveToMatchedSymbol, surround match path) - Plugin:
src/main.ts(installTokenClassifier)
- Fork:
Changed
- Fold golden tests re-recorded — fold-motions golden data re-recorded with
vim.treesitter.foldexpr()active andluaSetupforcing synchronous treesitter parse + fold recomputation before each test case. Corrected2zkgolden value that was affected bynvim_feedkeysasync timing (verified againstnormal!behavior). All 12 fold motion golden cases now have correct Neovim reference values.
Fixed
- Note Composer extract fails in visual-line mode —
editor.replaceSelection()now works when called after visual-line mode has been exited betweengetSelection()andreplaceSelection(). Root cause: commands that open a modal (Note Composer, Note Refactor) return immediately fromcheckCallback(false), causing thewithExpandedSelectionwrapper'sfinallyblock to restore cursor-only CM6 selection. The modal's async completion callback then callsgetSelection()(works — patched) followed byreplaceSelection(), but by that time the modal interaction has exited visual-line mode, sogetVisualLineSel()returnsnulland the patchedreplaceSelectionfalls through to the unpatched original which sees no CM6 selection. Fixed by snapshotting the visual-line selection range whengetSelection()is called;replaceSelection()uses the snapshot as fallback when vim visual-line state is no longer active, clearing it after use. (#157, regression of #137/#138)- Plugin:
src/vim/visual-line-command-fix.ts(lastVisualLineSelsnapshot ingetSelection(), fallback inreplaceSelection())
- Plugin:
zj/zkskip nested heading folds —zjnow visits all foldable lines including nested headings (e.g.,## Headinginside a# Headingsection), matching Neovim behavior. PreviouslyfindNextFoldable/findPrevFoldablehad aparentRangefilter that excluded child folds contained within a parent fold's range. Closes 6 fold motion Neovim deviations (zj,2zj,zk,2zk,[z,]z).- Plugin:
src/fold/motions.ts(removedparentRangefilter fromfoldNext/foldPrev, fixedfoldPrevcount search to use fold start instead of fold end)
- Plugin:
Tests
- 1 regression test in
test/specs/visual-line-command.e2e.ts(#157): readsgetSelection()in V-LINE, exits visual mode via<Esc>, callsreplaceSelection()— verifies snapshotted range is used for replacement - 97 unit tests for treesitter subsystem:
test/unit/treesitter/runtime.test.ts(12),test/unit/treesitter/api.test.ts(36),test/unit/treesitter/query.test.ts(19),test/unit/treesitter/language-tree.test.ts(16),test/unit/treesitter/js-api.test.ts(22 — including 8 inline node detection tests) - 12 e2e tests for treesitter Lua API:
test/specs/treesitter.e2e.ts - Re-recorded golden data:
test/neovim/golden-data/fold-motions.json(withluaSetupforcing treesitter parse before fold motions) - 6 fold motion Neovim deviations removed from
test/neovim/deviations.ts
Documentation
CHANGELOG.mdKNOWN_LIMITATIONS.md: updated visual-line selection section with snapshot behavior for modal-based commands (#157)AGENTS.md: treesitter architecture, new file descriptions, Lua API surface update, forksetTokenClassifierhookCONTRIBUTING.md: treesitter file tree insrc/structure,tree-state.tsaddedKNOWN_LIMITATIONS.md: treesitter integration section with 7 known limitationsREADME.md: treesitter feature in Lua configuration descriptiondocs/configuration/lua-config.md: updated API availability note
Full Changelog: 0.136.0...0.137.0