plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-tooling/tree-sitter-plum/queries/plum/format.scm
; ============================================================
; Top-level items — always their own line, blank line allowed
; between them (preserved from the source if it was already there)
; ============================================================
(source
(_) @append_hardline)
(source
(fn) @allow_blank_line_before)
(source
(enum) @allow_blank_line_before)
(source
(trait) @allow_blank_line_before)
(source
(const) @allow_blank_line_before)
(source
(import) @allow_blank_line_before)
(source
(test) @allow_blank_line_before)
; ============================================================
; Binary operators — space before and after. Scoped to the specific
; operator-expression node types they appear in (NOT bare tokens):
; "/" is also the bare separator inside a `url` (`std/os`), which
; must stay glued with no spaces — matching it unscoped would insert
; spaces there too and make the import unparseable on a second pass.
; ============================================================
(binary_operator
[
"+"
"-"
"*"
"/"
"%"
"^"
"&"
"|"
"<<"
">>"
] @prepend_space @append_space)
(comparison_operator
[
"=="
"!="
"<"
"<="
">"
">="
"<>"
] @prepend_space @append_space)
(boolean_operator
[
"&&"
"||"
] @prepend_space @append_space)
; ============================================================
; Assignment and arrows
; ============================================================
[
"="
":="
] @prepend_space @append_space
"->" @prepend_space @append_space
"=>" @prepend_space @append_space
(ternary_expression
[
"?"
":"
] @prepend_space @append_space)
(elvis_expression "?:" @prepend_space @append_space)
; ============================================================
; Keywords — space after (Topiary inserts NO whitespace at all
; between adjacent leaves unless a query says to; every keyword
; below needs its own rule or it glues to the next token, e.g.
; "module" + "test" -> "moduletest").
; ============================================================
(module "module" @append_space)
(import "import" @append_space)
(trait "trait" @append_space)
(enum "enum" @append_space)
(fn "extern" @append_space)
(fn "fun" @append_space)
(if "if" @append_space)
(else_if "else if" @append_space)
(else "else" @append_space)
; `else_if`/`else` are siblings of `if`'s own body (not wrapped in it), so
; without this they glue right onto the body's last line: `"neg"else if ...`.
(if
(else_if) @prepend_hardline)
(if
(else) @prepend_hardline)
(while "while" @append_space)
(for "for" @append_space)
(for "range" @prepend_space @append_space)
(match "match" @append_space)
(test "test" @append_space)
; "assert"/"return" take an OPTIONAL trailing expression — attach the space to
; that expression instead of the keyword, so a bare `return` (no value) never
; gets a dangling trailing space before its line break.
(assert (expression) @prepend_space)
(return (expression) @prepend_space)
; ============================================================
; Comments — preserve whether the ORIGINAL source had a line break
; before it: a standalone comment on its own line stays on its own
; line; a trailing comment (`x = 5 # note`) stays trailing. Using a
; plain @prepend_hardline here would wrongly force even a trailing
; comment onto its own line — and would keep moving on every
; formatting pass, since the next pass's "original" no longer
; matches, which is exactly the idempotence failure this fixes.
; ============================================================
(comment) @prepend_input_softline
; ============================================================
; Separators — no space before, one space after
; ============================================================
"," @append_space
":" @append_space
; ============================================================
; trait/enum bodies — unlike a `fn`'s body, these have no
; wrapping `(body ...)` node of their own (the grammar puts their
; `field`/`fn` children directly under `trait`/`enum`), so
; they need their own hardline + indent rules instead of reusing
; the `(body)` rule below.
; ============================================================
(trait
(field) @prepend_hardline)
(trait
"=" @append_indent_start)
(trait
(_) @append_indent_end
.)
(enum
(field) @prepend_hardline)
(enum
(fn) @prepend_hardline)
; A blank line between two fields/methods (or a field and a method) inside an
; `enum` body is preserved if the source already had one — same as top-level
; items — rather than always being collapsed to a single hardline.
(enum
(field) @allow_blank_line_before)
(enum
(fn) @allow_blank_line_before)
(enum
"=" @append_indent_start)
(enum
(_) @append_indent_end
.)
; `enum_field` (a variant like `| Some[T]`) is ALIASED to the same visible
; node kind ("field") as trait_field, but only it has a literal
; "|" — safe to target unscoped by parent.
(field
"|" @append_space)
; ============================================================
; match — same situation as trait/enum: `case`/`guard_case`
; children sit directly under `match`, no wrapping `(body ...)`.
; ============================================================
(match
(case) @prepend_hardline)
(match
(guard_case) @prepend_hardline)
(guard_case
"|" @append_space)
(match
"match" @append_indent_start)
(match
(_) @append_indent_end
.)
; ============================================================
; Closures — a space between the closing "|" and an inline expression
; body (`|v| v + 1`), same as `assert`/`return`'s optional-expression
; rule above. Targeting the `body` FIELD (not a bare "|" token, which
; appears twice — once opening, once closing) picks out only the
; closing one, regardless of whether the body is a single expression
; or an indented block.
; ============================================================
(closure
body: (_) @prepend_space)
; ============================================================
; Function body — each statement on its own line
; ============================================================
(body
(_) @prepend_hardline)
; ============================================================
; Indentation — body blocks
; ============================================================
; Start indent before body content, end indent after the body node
(body) @prepend_indent_start @append_indent_end