Preserve preformatted boundaries and empty code separation

This commit is contained in:
kevin
2026-09-18 16:59:49 +08:00
parent 30c4f85b4e
commit a631561899
4 changed files with 67 additions and 8 deletions
+21 -8
View File
@@ -384,10 +384,10 @@ impl<'a, D: Dom + ?Sized> Machine<'a, D> {
NodeKind::Text(text) => self.raw.push_str(text),
// Inline code normalizes line endings to spaces. Keep explicit
// breaks without converting descendant formatting into Markdown.
NodeKind::Element("br") if inline => self.raw.push('\n'),
NodeKind::Element("br") => self.raw.push('\n'),
NodeKind::Document | NodeKind::Element(_) if depth + 1 < self.options.max_depth => {
if !inline
&& matches!(self.dom.node_kind(node), NodeKind::Element(tag) if is_block(tag))
&& matches!(self.dom.node_kind(node), NodeKind::Element(tag) if is_structural_block(tag))
{
if !self.raw.is_empty() && !self.raw.ends_with('\n') {
self.raw.push('\n');
@@ -449,17 +449,20 @@ fn class_language(class: Option<&str>) -> Option<&str> {
}
fn is_block(tag: &str) -> bool {
is_structural_block(tag)
|| matches!(
tag,
"audio" | "canvas" | "frameset" | "isindex" | "noframes" | "output"
)
}
fn is_structural_block(tag: &str) -> bool {
matches!(
tag,
"address"
| "audio"
| "canvas"
| "frameset"
| "isindex"
| "noframes"
| "output"
| "article"
| "aside"
| "blockquote"
| "body"
| "caption"
| "center"
@@ -475,14 +478,23 @@ fn is_block(tag: &str) -> bool {
| "figure"
| "footer"
| "form"
| "h1"
| "h2"
| "h3"
| "h4"
| "h5"
| "h6"
| "header"
| "hgroup"
| "html"
| "legend"
| "li"
| "main"
| "menu"
| "nav"
| "ol"
| "p"
| "pre"
| "section"
| "summary"
| "table"
@@ -492,5 +504,6 @@ fn is_block(tag: &str) -> bool {
| "th"
| "thead"
| "tr"
| "ul"
)
}
+5
View File
@@ -171,6 +171,11 @@ impl<'a> Writer<'a> {
}
pub(crate) fn code_with_edges(&mut self, text: &str, preformatted: bool) {
// An empty element has no visible edge. Keep the pending code until
// the next visible text or element decides whether it needs a gap.
if text.is_empty() {
return;
}
// Each call represents a separate HTML code element. Keep adjacent
// elements distinct even when the source has no whitespace between them.
if self.code.is_some() {
+26
View File
@@ -106,6 +106,32 @@ fn pre_keeps_raw_text_and_line_endings() {
);
}
#[test]
fn preformatted_examples_keep_inline_values_and_separate_display_lines() {
for (source, expected) in [
(
"<pre>const answer = <output>42</output>;</pre>",
"const answer = 42;",
),
(
"<pre><h3>TypeScript</h3><code>function identity() {}</code></pre>",
"TypeScript\nfunction identity() {}",
),
(
"<pre><ol><li>first()</li><li>second()</li></ol></pre>",
"first()\nsecond()",
),
("<pre>first<br>second</pre>", "first\nsecond"),
] {
let result = markdown(source);
assert_eq!(
rendered_html(&result),
format!("<pre><code>{expected}\n</code></pre>\n"),
"{source}: {result}"
);
}
}
#[test]
fn nested_emphasis_closes_with_markdown_before_a_word() {
for (html, expected, expected_html) in [
+15
View File
@@ -158,6 +158,21 @@ fn adjacent_code_elements_keep_distinct_values() {
);
}
#[test]
fn empty_code_between_values_does_not_join_or_invent_code_text() {
for empty in ["<code></code>", "<code><!-- source note --></code>"] {
let source = format!("<code>name</code>{empty}<code>string</code>");
for preformatted in [false, true] {
let result = markdown(&source, preformatted);
assert_eq!(
rendered_html(&result),
"<p><code>name</code> <code>string</code></p>\n",
"{source}, preformatted={preformatted}: {result}"
);
}
}
}
#[test]
fn preformatted_block_children_keep_text_boundaries() {
let html = "<pre><div>ts</div><div><code>function identity() {}</code></div></pre>";