feat: a bare figure fence highlights as a composite figure - #197
Merged
Conversation
PART 9 section 4c reserves the kind word `figure` among the `:::` types: a BARE opener - the fence, its separator, the word, and nothing else - is ONE figure of ordered panels, not an admonition. THE GRAMMAR NEEDED NOTHING, which is the finding rather than the shortcut. Measured before writing any rule: the parser already distinguishes the two openers by which FIELDS they carry - a bare one has neither `title:` nor `label:` - and it already places the `^ ` line after a closing fence as a SIBLING of the container rather than inside it, which is exactly where the clause puts the group caption. So a reserved kind word wants a reserved CAPTURE, not a new node, and the three parse shapes are pinned in test/corpus/carve.txt so a future grammar change cannot quietly take that distinction away. queries/highlights.scm gets it. `!title !label` is the whole distinction and is why this is expressible as a query at all; a titled or labelled opener matches nothing new and keeps `@type`. GROUPS DO NOT NEST is a second pattern at a higher priority, restoring `@type` on a bare opener that is a direct child of a group's content - a query cannot say "whose ancestor is not a group", so it says the specific shape instead. The residual is written at the rule: a bare opener nested deeper keeps the group capture, where the clause degrades any depth. And a check the repo did not have. The queries are the other half of what this package ships - vim-carve, helix-carve and zed-carve pin this repo and load these files - and nothing read them, so a capture naming a node that no longer exists would have stayed green. scripts/highlight-captures.mjs resolves what an editor actually paints: highest `(#set! priority N)` wins, later patterns break a tie, 100 by default. Reporting every match instead - which is what `tree-sitter query` prints - would call the nested case green while the editor painted the generic colour over it. Two things the resolver has to get right, both found by watching it fail: `spell`, `nospell`, `conceal` and `none` are not colours and land on the same nodes the colour patterns do, and `#offset!` is a directive the node binding refuses to build a query with, so it is stripped rather than checking a hand-copied subset of the file.
The first pass covered a bare `::: figure` that is a DIRECT child of an open group's content, and named the rest as a residual. That left the shapes people actually write over-coloured: a group holding a `::: note` holding a figure, a group holding a quote holding one, and a group holding a list item holding one - all three are one intervening block, not an exotic depth. A tree-sitter query has no transitive closure, so "any descendant" cannot be written. What can is a wildcard chain rooted at the group, one pattern per intervening level, and three levels is enough for every shape the language produces: the direct child; `div`/`block_quote` > `content` > `div`; and `list` > `list_item` > `list_item_content` > `div`. The wildcards are deliberate rather than lazy - naming the container types would need revisiting every time a block gains a content field, while the chain LENGTH is the real constraint and is now what the comment states. The residual that remains is a bare opener more than three levels down, and it is named at the rule. Each of the three new shapes is checked, and each was watched to fail: removing the two wildcard patterns fails exactly those three cases and nothing else.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Carries the composite-figures vocabulary (PART 9 §4c, landed as markup-carve/carve#1215, design markup-carve/carve#1122) into the queries this repo ships.
The grammar needed nothing, and that is the finding
Measured before writing any rule. The parser already tells the two openers apart by which FIELDS each carries:
Input:
Tree:
Input:
Tree:
A bare opener carries neither
title:norlabel:; a titled one does. And the^line after the closing fence already parses as a SIBLING of the container, not inside it, which is exactly where §4c puts the group caption - §4's sixth host. So a reserved kind word wants a reserved CAPTURE, not a new node. Three parse shapes are pinned intest/corpus/carve.txtso a later grammar change cannot quietly take the distinction away.What changed
queries/highlights.scm::: figureopener'sclass_namecaptures@type.builtin; four patterns in total, one for the group and three restoring@typeinside ittest/corpus/carve.txtscripts/highlight-captures.mjs.github/workflows/ci.yml,package.json!title !labelis the whole distinction and is why this is expressible as a query at all. A titled or labelled opener matches nothing new and keeps@type.GROUPS DO NOT NEST is three more patterns at a higher priority, restoring
@typeon a bare opener inside a group. A tree-sitter query has no transitive closure, so "any descendant" cannot be written; what can is a wildcard chain rooted at the group, one per intervening level. Three levels covers every shape the language produces - the direct child,div/block_quote>content>div, andlist>list_item>list_item_content>div. The residual, a bare opener more than three levels down, is stated at the rule.A check this repo did not have
The queries are the other half of what this package ships -
vim-carve,helix-carveandzed-carvepin this repo and load these files - and nothing here read them. A capture naming a node that no longer exists, or two patterns claiming one node with nobody deciding which wins, left every check green.scripts/highlight-captures.mjsresolves what a consumer resolves: highest(#set! priority N)wins, later patterns break a tie, 100 by default. Reporting every match instead - which is whattree-sitter queryprints - would call the nested cases green while the editor painted the generic colour over them.Two things it has to get right, both found by watching it fail first:
spell,nospell,concealandnoneare not colours and land on the same nodes the colour patterns do; and#offset!is a directive the node binding refuses to build a query with, so it is stripped rather than checking a hand-copied subset of the file, which would have passed while the real file was broken.Downstream
vim-carveandhelix-carvecarry their own copies of these queries and pin this repo, so both need a pin bump plus the same two-file query change once this lands.zed-carvelikewise. Those are separate PRs.