Equation gallery: group multi-letter operands in two templates (#347)

A script or above/below operand takes one letter unless it is grouped, as in
Word, so `⟶┴yields` (Yields) put only "y" above the arrow and the n×n Matrix's
`a_1n`, `a_m1`, `a_mn` subscripted only "1" and "m". Grouped as `⟶┴(yields)`
and `a_(1n)`, `a_(m1)`, `a_(mn)`. A new test fails when any template or
built-in equation leaves letters right after a script or limit.


Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dmitrii Khlapov
2026-10-10 17:41:15 -04:00
committed by GitHub
co-authored by Dmitrii Khlapov Claude Opus 5.5
parent 1d7a54f883
commit e2d8551b7f
+24 -2
View File
@@ -435,7 +435,7 @@ pub const STRUCTURES: &[Gallery] = &[
t("op.eqAbove", "Text over Equal", "=┴⬚"),
],
),
("Common Operator Structures", &[t("op.yields", "Yields", "⟶┴yields"), t("op.deltaArrow", "Delta over arrow", "⟶┴∆")]),
("Common Operator Structures", &[t("op.yields", "Yields", "⟶┴(yields)"), t("op.deltaArrow", "Delta over arrow", "⟶┴∆")]),
],
},
Gallery {
@@ -486,7 +486,7 @@ pub const STRUCTURES: &[Gallery] = &[
(
"Sparse Matrices",
&[
t("mat.sparse", "n×n Matrix", "(■(a_11&⋯&a_1n@⋮&⋱&⋮@a_m1&⋯&a_mn))"),
t("mat.sparse", "n×n Matrix", "(■(a_11&⋯&a_(1n)@⋮&⋱&⋮@a_(m1)&⋯&a_(mn)))"),
t("mat.identityN", "n×n Identity Matrix", "[■(1&0&⋯&0@0&1&⋯&0@⋮&⋮&⋱&⋮@0&0&⋯&1)]"),
],
),
@@ -548,6 +548,28 @@ mod tests {
})
}
/// A script or above/below operand takes one letter unless it is grouped (`a_(mn)`), as in
/// Word; letters right after one mean a template split a word (`⟶┴yields` put only `y` above
/// the arrow).
fn split_operand(a: &[MNode]) -> bool {
a.windows(2).any(|w| {
matches!(w[0], MNode::Lim { .. } | MNode::Script { .. })
&& matches!(&w[1], MNode::Run(r) if r.text.starts_with(|c: char| c.is_alphabetic()))
}) || a
.iter()
.any(|n| (0..wordcraft_doc::math_edit::child_count(n)).any(|c| wordcraft_doc::math_edit::child(n, c).is_some_and(|x| split_operand(x))))
}
#[test]
fn multi_letter_operands_are_grouped() {
for tpl in templates() {
assert!(!split_operand(&tpl.nodes()), "{} splits an operand: {}", tpl.id, tpl.linear);
}
for (id, _, lin) in BUILT_INS {
assert!(!split_operand(&parse_linear(lin)), "{id} splits an operand: {lin}");
}
}
#[test]
fn every_template_builds() {
let mut ids = std::collections::HashSet::new();