Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ silently.
|---------------------|---------------------------------------------|------------------------------------------------------|
| `parser_path` | `nil` | Register a pre-compiled parser directly. |
| `install_url` | tree-sitter-carve repo | URL for `:TSInstall carve`. |
| `install_revision` | `b6877de1af5f6d1dde55395ebeee32a0483b1946` | Revision to install (post-0.1.2 main; pinned to the bundled queries). |
| `install_revision` | `17362de88d2c3177e7c6b4d5f83841f38a42ae4d` | Revision to install (post-0.1.2 main, at composite figures; pinned to the bundled queries). |
| `register_filetype` | `true` | Map `carve` filetype to `carve` lang. |

## License
Expand Down
2 changes: 1 addition & 1 deletion lua/carve/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ local DEFAULTS = {
-- (see README, "Bundled queries"), so an unpinned branch could compile a
-- grammar newer or older than the queries and silently miss captures.
-- Bump this alongside every query re-copy so the two stay paired.
install_revision = 'b6877de1af5f6d1dde55395ebeee32a0483b1946',
install_revision = '17362de88d2c3177e7c6b4d5f83841f38a42ae4d',
-- Map the `carve` filetype to the `carve` language.
register_filetype = true,
}
Expand Down
81 changes: 81 additions & 0 deletions queries/carve/highlights.scm
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,87 @@
(class_name)
] @type

; Composite figures (PART 9 4c, markup-carve/carve#1215). The kind word `figure`
; is RESERVED among the `:::` types: a BARE opener - the fence, its separator,
; the word, and nothing else - is ONE figure of ordered panels, not an
; admonition. `!title !label` IS the distinction, and it is the whole reason this
; belongs in a query rather than in the grammar: the parse tree already tells the
; two apart by which fields the opener carries, so a reserved kind word needs a
; reserved capture rather than a new node. An opener carrying a quoted title or a
; `[label]` matches nothing here and keeps `@type` above, which is the generic
; Tier-2 container the clause says it stays.
;
; The group caption needs no rule: it is an ordinary `^ ` line one line below the
; closing fence, and the parser already places it as a sibling of the container
; rather than inside it, where the existing `(caption)` patterns claim it.
((div
class: (class_name) @type.builtin
!title
!label)
(#eq? @type.builtin "figure")
(#set! priority 105))

; GROUPS DO NOT NEST: a bare `::: figure` inside an open group is a generic
; container, not an inner group, at ANY depth. A query has no transitive
; closure - there is no "any descendant" - so the reach is spelled as wildcard
; chains rooted at the group, one per intervening level, each restoring `@type`
; on the inner opener at a higher priority than the pattern above gives it.
;
; Three levels covers every shape the language actually produces: a direct child
; of the group's content; one intervening container (`div` > `content` > `div`,
; and `block_quote` > `content` > `div`); and a list item
; (`list` > `list_item` > `list_item_content` > `div`). The wildcards are
; deliberate - naming the container types would have to be revisited every time
; a new block gains a content field, and the chain LENGTH is the real constraint.
;
; RESIDUAL, written down rather than left to be rediscovered: a bare opener
; reached through MORE than three levels - a quote inside a list item inside the
; group, say - keeps the group capture. The parse tree is right either way; only
; the colour is not.
((div
class: (class_name) @_group.class
!title
!label
content: (content
(div
class: (class_name) @type
!title
!label)))
(#eq? @_group.class "figure")
(#eq? @type "figure")
(#set! priority 110))

((div
class: (class_name) @_group.class
!title
!label
content: (content
(_
(_
(div
class: (class_name) @type
!title
!label)))))
(#eq? @_group.class "figure")
(#eq? @type "figure")
(#set! priority 110))

((div
class: (class_name) @_group.class
!title
!label
content: (content
(_
(_
(_
(div
class: (class_name) @type
!title
!label))))))
(#eq? @_group.class "figure")
(#eq? @type "figure")
(#set! priority 110))

(identifier) @tag

(key_value
Expand Down
35 changes: 35 additions & 0 deletions syntax/carve.vim
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,39 @@ syntax keyword carveAdmonition contained note tip warning danger info success ex
syntax match carveDivTitle /"[^"]*"/ contained
syntax match carveDivLabel /\[[^]]*\]/ contained

" A BARE `::: figure` opener - the fence, its separator, the kind word, and
" NOTHING else - is a composite figure (PART 9 4c, markup-carve/carve#1215): one
" figure of ordered panels, not an admonition. `\s*$` is the whole distinction;
" `::: figure "T"` and `::: figure [g]` match nothing here and stay the generic
" container the clause says they stay.
"
" A REGION rather than a match, and the region is what carries the rule that
" GROUPS DO NOT NEST. Its `contains` omits carveFigureGroup, so a bare
" `::: figure` anywhere inside an open group - at any depth, through a quote or
" a list item as readily as directly - falls through to carveDivFence, which is
" the generic reading the clause degrades it to. A plain `syntax match` cannot
" say that: it has no notion of being inside anything, and it coloured every
" nested opener as another group.
"
" `\z(` / `\z1` is the colon-fence depth rule (PART 9 12): the region closes on
" a fence of the SAME length as the one that opened it, which is what lets
" `::::` nest inside `:::`. `keepend` stops an inner item from carrying the
" region past its own closer.
"
" The separator is a SPACE run, never a tab (grammar PART 7, MARKER SEPARATORS):
" `:::` + TAB + `figure` opens nothing, so it is left to carveDivFence, which
" over-colours it exactly as it does today.
"
" The group caption needs no rule: it is an ordinary `^ ` line below the closing
" fence, and the region ends AT that fence, so carveCaption claims it at the top
" level exactly as it claims every other caption.
syntax region carveFigureGroup
\ matchgroup=carveFigureGroupFence
\ start=/^\s*\z(:\{3,}\) \+figure\s*$/
\ end=/^\s*\z1\s*$/
\ keepend
\ contains=ALLBUT,carveFigureGroup

" ---------------------------------------------------------------------------
" Tables: | cell | with |= headers, alignment and span markers.
" ---------------------------------------------------------------------------
Expand Down Expand Up @@ -298,6 +331,8 @@ highlight default link carveMathDelim Delimiter
highlight default link carveLiteralInline String
highlight default link carveLiteralDelim Delimiter

highlight default link carveFigureGroup NONE
highlight default link carveFigureGroupFence Type
highlight default link carveDivFence Delimiter
highlight default link carveAdmonition Keyword
highlight default link carveDivTitle String
Expand Down
22 changes: 22 additions & 0 deletions tests/highlight.crv
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,25 @@ An over-long tag [u]{:toolongtag} is prose.
A colon-leading value [v]{key=:fr} is a value.
%% ^^^ carveAttrKey
%% ^^^ carveSpan

::: figure
%%^^^^^^^^^^ carveFigureGroupFence
![one](a.png)
^ (a) One
:::
%%^^^ carveFigureGroupFence
^ Figure #: Group caption
%%^^^^^^^^^^^^^^^^^^^^^^^^^ carveCaption

::: figure "A titled figure div"
%%^^^^ carveDivFence
%% ^^^^^^^^^^^^^^^^^^^^^ carveDivTitle
Body.
:::

::: figure
:::: figure
%%^^^^ carveDivFence
Nested is a generic container.
::::
:::