use std::borrow::Cow;
use crate::ast::math_text::math_source;
use crate::ast::plain_text::{push_atom, TextAtom};
use pulldown_cmark::Event;
pub(crate) fn events_to_text(events: &[Event<'_>], start: usize, end: usize) -> String {
let mut out = String::new();
for event in &events[start..end] {
match event {
Event::Text(t) => push_atom(&mut out, TextAtom::Verbatim(t)),
Event::Code(c) => push_atom(&mut out, TextAtom::Verbatim(c)),
Event::InlineMath(t) => push_atom(&mut out, math_atom(t, false)),
Event::DisplayMath(t) => push_atom(&mut out, math_atom(t, true)),
_ => {}
}
}
out
}
fn math_atom(tex: &str, display: bool) -> TextAtom<'static> {
TextAtom::Math(Cow::Owned(math_source(tex, display)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::parser::ParseConfig;
use crate::ast::{parse_with_config, Block};
use crate::heading::anchor::obsidian_heading_anchor;
#[test]
fn event_walk_and_inline_walk_agree_on_every_heading() {
for md in [
"# Euler $e^{i\\pi}=-1$ identity\n",
"# Convolution $f*g$ and $h*k$ end\n",
"# Dual $V^*$ and $W^*$ end\n",
"# Case $$a+b$$ tail\n",
"# Mixed `code` and *em* and **strong** here\n",
"# A [link](/x) and  inline\n",
"# 中文 $\\alpha$ 标题\n",
"# Nested *em with `code` and $x^2$* tail\n",
] {
for math in [false, true] {
let cfg = ParseConfig { math, ..Default::default() };
let doc = parse_with_config(md, &cfg);
let Block::Heading { children, id, .. } = &doc.blocks[0] else {
panic!("expected a heading for {md:?}");
};
let label = crate::ast::plain_text::inlines_to_plain_text(children);
assert_eq!(
id.as_deref().expect("heading must have an id"),
obsidian_heading_anchor(&label),
"slug and label disagree for {md:?} (math={math})"
);
}
}
}
#[test]
fn setext_soft_break_divergence_is_pinned_not_fixed() {
let doc = parse_with_config("foo\nbar\n===\n", &ParseConfig::default());
let Block::Heading { children, id, .. } = &doc.blocks[0] else {
panic!("expected a setext heading, got {:?}", doc.blocks[0]);
};
let label = crate::ast::plain_text::inlines_to_plain_text(children);
assert_eq!(label, "foo\nbar", "AST keeps the SoftBreak as a newline");
assert_eq!(id.as_deref(), Some("foobar"), "the event walk drops it");
}
#[test]
fn policy_is_one_function() {
let mut out = String::new();
push_atom(&mut out, TextAtom::Verbatim("a"));
push_atom(&mut out, TextAtom::Break);
push_atom(&mut out, math_atom("x^2", false));
push_atom(&mut out, math_atom("y", true));
assert_eq!(out, "a $x^2$$$y$$");
}
}