Documents: code protection closes and reopens tags as the reader does
CI / platforms (ubuntu-24.04) (push) Waiting to run
CI / site (push) Successful in 33s
CI / platforms (windows-latest) (push) Successful in 5m1s
CI / crate (push) Failing after 11m3s

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude committed 2026-10-19 00:24:40 -04:00
1 parent 5fba7b3dfb
commit de961eb164
2 files changed
+56 -12

No files matched your search

+35 -12
View File
@@ -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 `<code>` 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<String>, 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,
+21
View File
@@ -86,6 +86,27 @@ fn code_and_tags_stay_literal() {
untouched(r"a <code><code>\(x\)</code> \(y\)</code> b");
untouched(r"a <kbd><code>\(x\)</code> \(y\)</kbd> b");
assert_eq!(tex(r"<code>a</code> \(x\)"), ["x"], "after code, prose");
for crossed in [
r"a <code><kbd>x</code> \(y\) b",
r"a <b><kbd>x</b> \(y\) b",
r"a <code><b>x</code> \(y\)</b> b",
] {
assert_eq!(
tex(crossed),
["y"],
"a crossed close ends code: {crossed:?}"
);
}
untouched(r"a <b><code>x</b> \(y\) b");
let reopened = crate::documents::tree::parse(r"a <b><code>x</b> \(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"<b>\(x\)</b>"), ["x"], "other tags hold prose");