diff --git a/src/documents/markdown/tex.rs b/src/documents/markdown/tex.rs index f4b61f4..985e21f 100644 --- a/src/documents/markdown/tex.rs +++ b/src/documents/markdown/tex.rs @@ -72,7 +72,7 @@ pub(super) fn swapped<'a>(text: &'a str, options: &ParseOptions) -> Swapped<'a> fn keep(container: &mdast::Node, text: &str, kept: &mut [bool]) { let mut nodes = Vec::new(); walk(container, &mut nodes); - // Open code tags, and where the outermost one's text starts. + // Open styled tags, and where code text last started. let (mut open, mut from) = (Vec::new(), 0); for node in &nodes[1..] { use mdast::Node::*; @@ -97,30 +97,53 @@ fn keep(container: &mdast::Node, text: &str, kept: &mut [bool]) { let Some(tag) = html::tag(&text[whole.clone()]) else { continue; }; - if html::inline_style(&tag.name) != Some(Style::Code) { + if html::inline_style(&tag.name).is_none() { continue; } - if !tag.closing { - if open.is_empty() { - from = whole.end; - } - open.push(tag.name); - } else if let Some(ix) = open.iter().rposition(|name| *name == tag.name) { - open.remove(ix); - if open.is_empty() { - kept[from..whole.start].fill(true); + let was = coded(&open); + match tag.closing { + true => end(&mut open, &tag.name), + false => { + if tag.name == "a" { + end(&mut open, "a"); + } + open.push(tag.name); } } + match (was, coded(&open)) { + (false, true) => from = whole.end, + (true, false) => kept[from..whole.start].fill(true), + _ => {} + } } _ => {} } } // An unclosed `` runs to its block's end. - if !open.is_empty() { + if coded(&open) { kept[from..span(container).end].fill(true); } } +/// Whether an open tag shows its text as code. +fn coded(open: &[String]) -> bool { + (open.iter()).any(|name| html::inline_style(name) == Some(Style::Code)) +} + +/// Ends the innermost `name` as the reader does: inner tags close. +fn end(open: &mut Vec, name: &str) { + let Some(at) = open.iter().rposition(|opened| opened == name) else { + return; + }; + let closed = open.split_off(at); + open.extend( + closed + .into_iter() + .skip(1) + .filter(|name| html::reopens(name)), + ); +} + /// Pairs and bare dollars in `within`; pairs cross nothing kept. fn paired( text: &str, diff --git a/src/documents/markdown/tex/tests.rs b/src/documents/markdown/tex/tests.rs index ecc02e6..c8158fa 100644 --- a/src/documents/markdown/tex/tests.rs +++ b/src/documents/markdown/tex/tests.rs @@ -86,6 +86,27 @@ fn code_and_tags_stay_literal() { untouched(r"a \(x\) \(y\) b"); untouched(r"a \(x\) \(y\) b"); assert_eq!(tex(r"a \(x\)"), ["x"], "after code, prose"); + for crossed in [ + r"a x \(y\) b", + r"a x \(y\) b", + r"a x \(y\) b", + ] { + assert_eq!( + tex(crossed), + ["y"], + "a crossed close ends code: {crossed:?}" + ); + } + untouched(r"a x \(y\) b"); + let reopened = crate::documents::tree::parse(r"a x \(y\) b"); + let crate::documents::tree::Node::Paragraph(inline) = &reopened[0] else { + panic!("a paragraph: {reopened:?}"); + }; + let code = crate::documents::tree::Style::Code; + assert!( + (inline.styles.iter()).any(|(range, style)| *style == code && range.contains(&6)), + "the reader reopens code over (y): {inline:?}" + ); untouched(r"`\(x\)`"); untouched(r"\(a `b` c\)"); assert_eq!(tex(r"\(x\)"), ["x"], "other tags hold prose");