summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRalph Amissah <ralph.amissah@gmail.com>2026-05-10 00:04:45 -0400
committerRalph Amissah <ralph.amissah@gmail.com>2026-05-10 00:04:45 -0400
commit04bac0f37f652e59401e78a7533f29472bd034dd (patch)
tree5c1534b255b4948328c9b8b098d67718719d1dca
parentgrammar: promote editor-note channel marker to named node (diff)
queries: add textobjects.scm and indents.scm
textobjects.scm exposes the structure that hosts (Neovim's nvim-treesitter-textobjects, Helix, Emacs treesit) can bind to keys like af / if (footnote / inside footnote), ah / ih (heading section), ab / ib (block body): @class.outer / @class.inner headings (part and segment) @function.outer / @function.inner block elements (code, poem, block, group, table, quote, pipe-table) @comment.outer / @comment.inner footnotes and editor notes @parameter.outer / @parameter.inner links and images @block.outer / @block.inner paragraphs @assignment.outer inline-formatting runs and header fields @attribute.outer book-index entries indents.scm anchors top-level structures at column 0 and marks block elements / header continuations as @indent.align. SiSU markup is largely flat-indented, so this file is mostly defensive: it stops auto-indent on <CR> from drifting away from column 0 inside paragraph text, and lets editors line up subsequent header continuation lines with the previous one. (assisted by Claude-Code)
-rw-r--r--queries/indents.scm48
-rw-r--r--queries/textobjects.scm140
2 files changed, 188 insertions, 0 deletions
diff --git a/queries/indents.scm b/queries/indents.scm
new file mode 100644
index 0000000..aa73af8
--- /dev/null
+++ b/queries/indents.scm
@@ -0,0 +1,48 @@
+; Indentation queries for SiSU Spine markup.
+;
+; SiSU markup is largely flat: paragraphs and headings live at column 0,
+; block bodies preserve their author-supplied indentation verbatim, and
+; nesting is by markers rather than by indent. So indents.scm is mostly a
+; no-op - the goal is to ensure that auto-indent on <CR> stays at column 0
+; for normal lines and respects existing indentation inside header
+; continuations and blocks.
+
+; Tree-sitter indent semantics (per nvim-treesitter and treesit):
+; @indent.begin - increases indent for the following line
+; @indent.end - matches the @indent.begin and decreases indent
+; @indent.zero - resets indent to column 0
+; @indent.align - aligns following lines with this node
+; @indent.branch - same level as the parent (for else/elif-style joins)
+
+; Top-level structures live at column 0 - reset to zero on the next line.
+(heading_part) @indent.zero
+(heading_segment) @indent.zero
+(paragraph) @indent.zero
+(book_index) @indent.zero
+(composite_include) @indent.zero
+(page_break) @indent.zero
+(horizontal_rule) @indent.zero
+(ocn_suppress_open) @indent.zero
+(ocn_suppress_close) @indent.zero
+(body_comment) @indent.zero
+
+; Block elements: opening line increases indent for the body, closing
+; line returns to zero. Editors that respect this will visually indent
+; raw content one step from the delimiter line, which is conventional.
+(code_block_curly) @indent.align
+(code_block_tic) @indent.align
+(poem_block_curly) @indent.align
+(poem_block_tic) @indent.align
+(block_block_curly) @indent.align
+(block_block_tic) @indent.align
+(group_block_curly) @indent.align
+(group_block_tic) @indent.align
+(table_block_curly) @indent.align
+(table_block_tic) @indent.align
+(quote_block_tic) @indent.align
+
+; Header continuation lines are indented by two spaces from column 0;
+; mark continuations as align so a host that chooses to auto-indent the
+; next continuation line matches the previous one.
+(header_field) @indent.align
+(header_continuation) @indent.align
diff --git a/queries/textobjects.scm b/queries/textobjects.scm
new file mode 100644
index 0000000..0a82481
--- /dev/null
+++ b/queries/textobjects.scm
@@ -0,0 +1,140 @@
+; Text-object queries for SiSU Spine markup.
+;
+; Capture conventions follow nvim-treesitter/textobjects:
+; @<thing>.outer -> select including delimiters / surrounding whitespace
+; @<thing>.inner -> select content only
+;
+; Hosts that consume these (Neovim's nvim-treesitter-textobjects, Helix,
+; Emacs treesit) bind keys such as `af` / `if` to .outer / .inner.
+
+; =================================================================
+; Headings (sectioning units)
+; =================================================================
+; A whole heading line is a "section header" object. Heading sections
+; (the heading plus its body content up to the next heading of equal or
+; higher level) are not directly expressible in tree-sitter without
+; additional grammar work; hosts can synthesise that from these captures.
+
+(heading_part) @class.outer
+(heading_part
+ content: (heading_content) @class.inner)
+
+(heading_segment) @class.outer
+(heading_segment
+ content: (heading_content) @class.inner)
+
+; =================================================================
+; Block elements (code / poem / block / group / table / quote)
+; =================================================================
+; Whole block including delimiters; raw_content is the inner.
+
+(code_block_curly) @function.outer
+(code_block_curly
+ content: (raw_content) @function.inner)
+
+(code_block_tic) @function.outer
+(code_block_tic
+ content: (raw_content) @function.inner)
+
+(poem_block_curly) @function.outer
+(poem_block_curly
+ content: (raw_content) @function.inner)
+
+(poem_block_tic) @function.outer
+(poem_block_tic
+ content: (raw_content) @function.inner)
+
+(block_block_curly) @function.outer
+(block_block_curly
+ content: (raw_content) @function.inner)
+
+(block_block_tic) @function.outer
+(block_block_tic
+ content: (raw_content) @function.inner)
+
+(group_block_curly) @function.outer
+(group_block_curly
+ content: (raw_content) @function.inner)
+
+(group_block_tic) @function.outer
+(group_block_tic
+ content: (raw_content) @function.inner)
+
+(table_block_curly) @function.outer
+(table_block_curly
+ content: (raw_content) @function.inner)
+
+(table_block_tic) @function.outer
+(table_block_tic
+ content: (raw_content) @function.inner)
+
+(quote_block_tic) @function.outer
+(quote_block_tic
+ content: (raw_content) @function.inner)
+
+(pipe_table) @function.outer
+
+; =================================================================
+; Footnotes and editor notes
+; =================================================================
+; Both share the same outer/inner shape; the inner skips the markers and
+; closing delimiters.
+
+(footnote) @comment.outer
+(footnote
+ (_)+ @comment.inner)
+
+(editor_note) @comment.outer
+(editor_note
+ (_)+ @comment.inner)
+
+; =================================================================
+; Links and images
+; =================================================================
+
+(link) @parameter.outer
+(link
+ text: (link_text) @parameter.inner)
+
+(image) @parameter.outer
+(image
+ spec: (image_spec) @parameter.inner)
+
+; =================================================================
+; Paragraph / inline-formatting runs
+; =================================================================
+
+(paragraph) @block.outer
+(paragraph
+ (_)+ @block.inner)
+
+; Inline formatting pairs - useful as fine-grained text objects.
+; The same delimiter character pattern (e.g. `*{` / `}*`) opens and
+; closes each, so .inner is everything between them.
+
+(emphasis) @assignment.outer
+(bold) @assignment.outer
+(italic) @assignment.outer
+(underline) @assignment.outer
+(citation_mark) @assignment.outer
+(superscript) @assignment.outer
+(subscript) @assignment.outer
+(inserted) @assignment.outer
+(strikethrough) @assignment.outer
+(monospace_inline) @assignment.outer
+
+; =================================================================
+; Book index entries
+; =================================================================
+
+(book_index) @attribute.outer
+(book_index
+ (index_content) @attribute.inner)
+
+; =================================================================
+; Header fields
+; =================================================================
+
+(header_field) @assignment.outer
+(header_field
+ value: (header_value) @assignment.inner)