Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

btv.treesitter

btv.treesitter.foldexpr(_lnum)

No documentation comment in the prelude.

Defined in btv.lua.

btv.treesitter.highlight(lang, text)

btv.treesitter.highlight(lang, text) -> promise of the tree-sitter highlight spans for the off-buffer snippet text in language lang — the same stateless highlighter (injections included) the picker preview uses, exposed so a plugin can token-colour an arbitrary snippet without opening a buffer (the help window’s >lua code blocks are the motivating case). Resolves with an array of { line = <0-based row>, col_start = <byte>, col_end = <byte>, group = <capture> } (col_end exclusive); the columns are byte offsets within each snippet line, which a caller maps to extmark columns. Resolve-only: a language with no installed grammar (and the wasm serverless build, whose highlighter is JS-side) settles with an empty array, so the caller simply paints nothing.

Defined in btv.lua.

btv.treesitter.reload(lang)

btv.treesitter.reload(lang): look for lang’s grammar again, and repaint what’s open in it. An editor remembers that a language has no grammar, and :TSInstall only tells the editor that ran it (TSInstalled), so this is for another editor that installed one: an app embedding several hears of it from the one, and tells the rest. Native only: the browser has one highlighter, outside the editor, and reports that this isn’t there.

Defined in btv.lua.

btv.treesitter.installed()

btv.treesitter.installed() -> promise of the list of languages with a grammar that can highlight: on native, a parser in bemtvi’s data dir or a borrowed neovim site/ with a highlights query (beside the parser, on the runtimepath, or from btv.treesitter.set_query); in the browser, what its highlighter has. :TSInstall adds to it. So a plugin can tell what’s missing before asking for it:

btv.treesitter.installed():next(function(langs)
  if not btv.tbl.contains(langs, "rust") then
    btv.cmd("TSInstall rust")
  end
end)

Defined in btv.lua.

btv.treesitter.set_query(lang, name, text)

btv.treesitter.set_query(lang, name, text): use text as lang’s name query, in place of its query files, in every buffer. name is one of highlights, indents, injections, folds or textobjects. nil for text goes back to the files. A query that won’t compile says so on the message line and changes nothing.

Not in the browser, whose highlighter is JS-side: it reports that on the message line.

Defined in btv.lua.

btv.treesitter.fragment_context(lang, templates)

btv.treesitter.fragment_context(lang, templates) — teach the fragment highlighter how to make sense of a code block that is not a whole program.

The code blocks inside LSP documentation (hover, completion docs) are usually fragments: a struct field, a bare statement, a body-less signature. Parsed as a whole file they land in tree-sitter’s error recovery, which doesn’t merely under-highlight them — it names constructs that aren’t in the text (Vec in field: Vec<String> comes out @constructor). bemtvi’s fragment highlighter drops those guesses; a framing is how it gets the real structure back instead.

templates is an ordered list of framings, each a string with one %s marking where the snippet goes. A snippet that doesn’t parse on its own is tried inside each in turn, and the first framing that parses cleanly wins — its highlight spans are mapped back onto the snippet’s own lines and columns:

btv.treesitter.fragment_context("rust", {
  "struct __btv {\n%s\n}",   -- a field hover: `field: Vec<String>`
  "fn __btv() {\n%s\n}",     -- a statement or expression
})

Only a clean parse is accepted, so a framing that doesn’t fit costs one throwaway parse and nothing else — the snippet falls through to the conservative repaint (keywords, strings, numbers, comments, punctuation; no guessed constructs), which is also where an annotation dialect ends up: lua_ls writes function f(t: table) into a ```lua fence, and that is not a fragment of any Lua program.

Indentation-sensitive languages. When a template’s %s follows only whitespace on its line, that whitespace is the block level the fragment sits at, not just an opener the first line continues — so every line of the fragment is indented to match:

btv.treesitter.fragment_context("python", { "class __btv:\n    %s" })

Without that a multi-line python fragment would be framed as a header, one indented line, and then a dedent — a syntax error rather than a block. The spans come back with the indent taken off every line, so the caller still sees the fragment’s own columns.

A same-line framing works too ("fn __btv() { return %s }") — there the prefix’s width comes off the first line only. The wrapped text always ends in a newline, because some grammars (go) treat a missing final terminator as a parse defect.

Two shapes the ladder is run for you on. A hover often carries the server’s own display label in front of the code — pyright sends (method) def join(self, x: str) -> str, tsserver (property) Foo.bar: number — and that label is what stops an otherwise framable signature from framing. It is peeled off, the ladder runs on the rest, and the label itself is painted as a comment. And a block that is a list rather than one fragment — ty sends every overload of a function as its own signature line — is resolved line by line, each through its own ladder. Both are all-or-nothing: a peel or a split that doesn’t end in a clean parse leaves no trace, and the block falls to the repaint whole.

Calling this replaces the language’s list; passing {} turns the ladder off for it. bemtvi ships defaults for rust, python, go, lua, javascript, typescript, tsx, c, cpp, java, c_sharp, kotlin, swift, scala, dart, zig, ruby, php, elixir, css, scss and json.

Defined in btv.lua.