fix(parser): accept spaceless headings (==Heading==) like vimwiki (#7)
CI / cargo clippy (push) Successful in 34s
CI / cargo fmt --check (push) Successful in 39s
CI / cargo test (push) Successful in 1m4s
CI / editor keymaps (push) Successful in 1m12s

The heading lexer required a space after the opening `=`s, so `==Heading==`
fell through to a literal `<p>==Heading==</p>` instead of an `<h2>`. vimwiki
accepts the spaceless form, so a migrated page whose top heading was
`==TODO==` (or `====Progress====`) rendered with no heading at all.

Drop the space-after-marker requirement; the existing title trim already
handles one optional space per side, so both `== H ==` and `==H==` work.
Marker-only (`======`) and unbalanced (`==a==b`, `==> x`) lines still stay
paragraphs (title-present + matching trailing-`=` checks). Regression tests
added at the lexer and renderer levels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 01:09:32 +00:00
parent f1494dd2fc
commit 4a717bcb31
3 changed files with 50 additions and 4 deletions
+21
View File
@@ -77,6 +77,27 @@ fn centered_heading_gets_centered_class() {
assert!(out.contains("<h1 class=\"centered\" id=\"T\">T</h1>"));
}
#[test]
fn spaceless_heading_renders_as_heading() {
// `==Heading==` (no surrounding spaces) is a heading, like vimwiki — not a
// literal `<p>==Heading==</p>`.
let out = render("==Plain==\n");
assert!(
out.contains(
"<div id=\"Plain\"><h2 id=\"Plain\" class=\"header\">\
<a href=\"#Plain\">Plain</a></h2></div>"
),
"got: {out}"
);
// The spaceless + keyword combo from the report: a header, not a badge.
let out2 = render("==TODO==\n");
assert!(
out2.contains("<h2 id=\"TODO\" class=\"header\">"),
"got: {out2}"
);
assert!(!out2.contains("<p>"), "not a paragraph: {out2}");
}
#[test]
fn heading_keyword_renders_as_plain_text_not_badge() {
// `== TODO ==` is a section title, not an inline keyword: it must render as