docs+test(web): name and pin the @attribute retro-paint (review round 3)

Review found that adding ("attribute", fg(3)) for HTML/CSS also colours the
@attribute capture three already-bundled grammars emit — rust (attribute_item),
lua (<const>), yaml (directives) — which were previously unpainted. Verified on
a Rust buffer: #[derive(Debug)] now paints uniformly yellow (fg 3), an
improvement over unpainted and the distinct-attribute convention most editors
follow.

Name this retro-paint as intended in the framing (Q#WEB4, rev 4) and pin it with
rust_attribute_repaints_via_shared_attribute_capture so it is a chosen effect,
not incidental. @tag is unaffected (HTML/CSS only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Levi Neuwirth 2026-07-23 16:11:47 -04:00
parent 394d39942c
commit 515c76f5ab
2 changed files with 85 additions and 6 deletions

View File

@ -1,11 +1,11 @@
# Web grammars (HTML + CSS) + HTML injections — framing # Web grammars (HTML + CSS) + HTML injections — framing
**Revision 3 — pre-implementation. Ground truth: canonical `main` @ **Revision 4 — implemented on branch `web-grammars` (PR #146). Ground truth:
`4daa1b8` (after LaTeX #144 and inline-math-docs #145), 2026-07-23. Status: canonical `main` @ `4daa1b8` (after LaTeX #144 and inline-math-docs #145),
framing only; no implementation.** Rev 2 settled the capture set, injection 2026-07-23.** Rev 2 settled the capture set, injection scope, and LSP claim
scope, and LSP claim (round 1); rev 3 corrects the `#match?` predicate analysis (round 1); rev 3 corrected the `#match?` predicate analysis (round 2); rev 4
(tree-sitter evaluates it natively) and refreshes the folding footprint to the names the intended `@attribute` retro-paint on the already-bundled rust/lua/yaml
current branch (round 2). See §0.1. grammars — verified on a Rust buffer and pinned by a test (round 3). See §0.1.
## 0.1 Revision history ## 0.1 Revision history
@ -44,6 +44,17 @@ current branch (round 2). See §0.1.
`overlay.rs`/`daemon.rs`/`syntax.rs`. §0 now lists the real set; the `overlay.rs`/`daemon.rs`/`syntax.rs`. §0 now lists the real set; the
zero-overlap conclusion is unchanged (stronger, if anything). zero-overlap conclusion is unchanged (stronger, if anything).
### Round 3 (rev 3 → rev 4)
- **R3-1 (medium).** Q#WEB4 verified which captures pmacs *lacks* but not the
reverse: which already-bundled grammars *use* `@attribute`. Three do — rust
(`attribute_item`), lua (`<const>`), yaml (directives) — so the new
`("attribute", fg(3))` entry retro-paints their previously-unpainted spans
yellow. Named in Q#WEB4 as intended (verified on a Rust buffer: uniform
yellow, an improvement over unpainted), and pinned by
`rust_attribute_repaints_via_shared_attribute_capture`. `tag` is unaffected
(HTML/CSS only).
Add tree-sitter **HTML** and **CSS** grammars so `.html`/`.htm` and `.css` Add tree-sitter **HTML** and **CSS** grammars so `.html`/`.htm` and `.css`
buffers get lexical highlighting, and — the north-star payoff — light up buffers get lexical highlighting, and — the north-star payoff — light up
HTML's embedded-language **injections**: `<script>` → JavaScript (already HTML's embedded-language **injections**: `<script>` → JavaScript (already
@ -197,6 +208,19 @@ There is **no** `tag.delimiter` (rev-1 speculation, removed); HTML's `<`/`>`/
- **general**`tag`/`attribute` are standard tree-sitter web captures, so it - **general**`tag`/`attribute` are standard tree-sitter web captures, so it
also serves future html-ish grammars (vue/svelte/astro). also serves future html-ish grammars (vue/svelte/astro).
**Retro-paint on already-bundled languages (intended, added rev 4).** The
capture table is global, so adding `attribute` also colours the `@attribute`
capture that three bundled grammars already emit but which was previously
unrecognized (and so unpainted): **rust** (`attribute_item`/`inner_attribute_item`
— every `#[derive(…)]` / `#![…]`), **lua** (the `<const>`/`<close>` variable
attribute), and **yaml** (`%YAML`/`%TAG` directives). On merge these begin
painting `attribute` yellow (`fg 3`). Verified on a Rust buffer: `#[derive(Debug)]`
paints uniformly yellow (the `attribute_item` span carries no narrower overriding
captures) — a distinct-attribute convention most editors follow, and an
improvement over unpainted. This is **chosen, not incidental**: it is pinned by
`rust_attribute_repaints_via_shared_attribute_capture` (`src/highlight.rs`).
`tag` is clean — only HTML/CSS use it, so it has no retro-effect.
**On CSS custom properties (corrected in rev 3):** pmacs passes the buffer text **On CSS custom properties (corrected in rev 3):** pmacs passes the buffer text
to `QueryCursor::captures` (`src/syntax.rs:1701`), so tree-sitter evaluates the to `QueryCursor::captures` (`src/syntax.rs:1701`), so tree-sitter evaluates the
standard `#match?`/`#eq?`/`#any-of?` predicates natively — pmacs special-cases standard `#match?`/`#eq?`/`#any-of?` predicates natively — pmacs special-cases

View File

@ -1416,6 +1416,61 @@ mod tests {
); );
} }
#[test]
fn rust_attribute_repaints_via_shared_attribute_capture() {
// Intended side effect, named in the framing (Q#WEB4): the
// `("attribute", fg(3))` entry added for HTML/CSS also colours the
// `@attribute` capture that tree-sitter-rust (attribute_item), -lua
// (`<const>`), and -yaml (directives) already emit. A Rust
// `#[derive(Debug)]` — previously unpainted, since `@attribute` was
// unrecognized — now paints the attribute style throughout. Pinned so
// the retro-paint on this repo's primary language is a chosen effect,
// not an incidental one.
use crate::buffer::{Buffer, BufferId, EditOp};
use crate::cell::{Cell, CellSize};
use crate::syntax::{ParseView, SyntaxRegistry};
let reg = SyntaxRegistry::new();
let language = reg.language("rust").expect("rust grammar");
let src = b"#[derive(Debug)]\nstruct S;\n";
let mut buf = Buffer::new(BufferId::next(), "a.rs");
buf.apply_edit(EditOp::Insert { pos: 0, bytes: src })
.unwrap();
let view = ParseView::new(&buf, language, "rust".to_owned());
let handle = view.handle();
let _vid = buf.attach_view(Box::new(view));
let mut req = handle.make_request();
req.injection_aliases = reg.injection_alias_snapshot();
let bundle = crate::syntax::run_parse(req).expect("rust parse");
handle.install(reg.resolve_layer_queries(&bundle));
let mut hv = SyntaxHighlightView::new(handle, reg.theme());
let (rows, cols) = (1usize, 20usize);
let mut backing: Vec<Cell> = vec![Cell::default(); rows * cols];
let mut grid = CellGrid {
cells: &mut backing,
stride: cols as u32,
size: CellSize::new(rows as u32, cols as u32),
};
let viewport = Viewport {
buffer_start: 0,
buffer_end: u64::MAX,
cell_origin: CellCoord::new(0, 0),
cell_size: CellSize::new(rows as u32, cols as u32),
gutter_w: 0,
};
let registry = buf;
hv.render(&registry, viewport, &mut grid);
// `derive` (col 2) sits inside the `attribute_item` and paints the
// shared @attribute style (fg 3) — the intended retro-paint.
assert_eq!(
grid.get(CellCoord::new(0, 2)).style.fg,
pmacs_protocol::cell::Color::Indexed(3),
"a Rust #[derive] attribute paints the shared @attribute style (fg 3)"
);
}
#[test] #[test]
fn web_grid_paints_html_tag_and_attribute() { fn web_grid_paints_html_tag_and_attribute() {
// Q#WEB4 acceptance: the two capture entries this lane adds (`tag`, // Q#WEB4 acceptance: the two capture entries this lane adds (`tag`,