tree-sitter-vimdoc
This grammar implements the vimdoc spec, :help help-spec.
This README describes how the grammar represents the spec (nodes), and its
limitations.
Predictable results are the primary goal, so that output formats (e.g. HTML) are well-formed; the input (vimdoc) is secondary. The first step should always be to try to fix the input rather than insist on a grammar that handles vimdoc's endless quirks.
Overview
blockis the main top-level node, delimited by blank line(s) or any line starting with<(codeblock terminator).- Contains
lineandline_linodes.
- Contains
line:- Contains atoms (words, tags, taglinks, …)
- Contains headings (
h1,h2,h3,column_heading).
line_li("listitem",:help help-list):- Lines starting with
-/•/[0-9].(not+/*) are listitems. - Use the
prefixnode to detect if the listitem is ordered (numbered) or unordered. - Consumes lines (incl. codeblocks) until blank line,
<line, or next listitem.- Except:
<followed by indented text (on the same or next line) is skipped, so the listitem continues after its codeblock:- foo: > code < still foo: > code < still foo - bar
- Except:
- Contains a codeblock not preceded by a blank line.
- Nesting is ignored: indented listitems are parsed as siblings. Consumers can check leading whitespace to decide nesting.
- Lines starting with
codeblock(:help help-codeblock):- Contained by
lineorline_li, because ">" can start a codeblock at the end of any line. - Contains
linenodes withoutwordnodes: it's just the full raw text line including whitespace. This is somewhat dictated by its "preformatted" nature; parsing the contents would require loading a "child" language (injection). See #2. - The terminating
<(and any following whitespace) is discarded (anonymous).
- Contained by
h1,h2,h3,column_heading(:help help-heading)
Tables
A table is a block whose first child is a
line containing column_heading with ≥2 cells, followed by line nodes
(rows, separators). A cell boundary is a <Tab> between two nodes.
Example (Lua):
--- Gets the rows (header first) of `block` if it is a table, else nil.
--- Each row is a list of cells, each cell a list of nodes.
local
-- This query finds potential "table" blocks; get_table() splits them into cells.
local query = vim...
local root = vim..::
for id, block in query:
Known issues
- Input must end with newline/EOL (
\n). Grammar does not support files without EOL. - Input must end with a blank line. Though this doesn't seem to matter in practice.
- Any line starting with
1.(or other number) is treated as a listitem, even if the first line of itsblockis not a listitem. Example:Foo was 0, not 1. Uh oh. - The spec requires a space before the codeblock
>, but the grammar also accepts a tab. Example::help lcs-tab. urlcannot contain a closing bracket]anywhere in the URL. (Workaround: URL-encode the bracket.) This is a tradeoff so that markdown hyperlinks work:[https://example.com](https://example.com)column_headingchildren should be plaintext, but currently are parsed as$._atom.
TODO
h4("tag heading") : a line containing only tags, or ending with a tag, is a "h4" heading.
Release
Steps to perform a release: https://tree-sitter.github.io/tree-sitter/creating-parsers/6-publishing.html
- Update tree-sitter CLI.
npm install tree-sitter-cli - Bump the version.
tree-sitter version x.y.z- Choose
patch/minor/majorto indicate query compatibility:patchfor bugfixes (no changes to queries needed)minorfor added nodes (queries may need changes to use new nodes but will not error)majorfor removed or renamed nodes (queries will error if not adapted), other breaking changes
- Choose
- Regenerate and test.
tree-sitter generate && tree-sitter test - Commit the generated files
git add . git commit -m 'release' - Push
git push - Tag and release: https://github.com/neovim/tree-sitter-vimdoc/releases/new
Publis to crates.io
Crate: https://crates.io/crates/tree-sitter-vimdoc
cargo publish --dry-run
cargo login
cargo publish