summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Milligan <tommilligan@users.noreply.github.com>2023-09-10 00:02:58 +0100
committerGitHub <noreply@github.com>2023-09-10 00:02:58 +0100
commit8e68cf919f92116c61053a1f51b2a9ea34c2ede2 (patch)
tree88e4fbfb350bca9c165efeffc2119b7a8cd5b757
parent771e9c9fd8e9cac941b9da8a96cbb1298437e933 (diff)
parent02640dab1f288d61db20268dad36e9b6e1787674 (diff)
Merge pull request #124 from tommilligan/bug-123
feat: support admonitions inside list items
-rw-r--r--integration/expected/chapter_1_main.html25
-rw-r--r--integration/src/chapter_1.md20
-rw-r--r--src/markdown.rs71
-rw-r--r--src/parse.rs23
-rw-r--r--src/render.rs31
5 files changed, 153 insertions, 17 deletions
diff --git a/integration/expected/chapter_1_main.html b/integration/expected/chapter_1_main.html
index 1425700..dcd49cb 100644
--- a/integration/expected/chapter_1_main.html
+++ b/integration/expected/chapter_1_main.html
@@ -90,4 +90,29 @@ let x = 20;
<span class="boring">}</span></code></pre></pre>
</div>
</div>
+<p>In a list:</p>
+<ol>
+<li>
+<p>Thing one</p>
+<pre><code class="language-sh">Thing one
+</code></pre>
+</li>
+<li>
+<p>Thing two</p>
+<div id="admonition-note-4" class="admonition note">
+<div class="admonition-title">
+<p>Note</p>
+<p><a class="admonition-anchor-link" href="#admonition-note-4"></a></p>
+</div>
+<div>
+<p>Thing two</p>
+</div>
+</div>
+</li>
+<li>
+<p>Thing three</p>
+<pre><code class="language-sh">Thing three
+</code></pre>
+</li>
+</ol>
diff --git a/integration/src/chapter_1.md b/integration/src/chapter_1.md
index aa0b3ce..93b7ccd 100644
--- a/integration/src/chapter_1.md
+++ b/integration/src/chapter_1.md
@@ -41,3 +41,23 @@ let x = 10;
let x = 20;
```
````
+
+In a list:
+
+1. Thing one
+
+ ```sh
+ Thing one
+ ```
+
+1. Thing two
+
+ ```admonish
+ Thing two
+ ```
+
+1. Thing three
+
+ ```sh
+ Thing three
+ ```
diff --git a/src/markdown.rs b/src/markdown.rs
index a685d8e..0ea2fa2 100644
--- a/src/markdown.rs
+++ b/src/markdown.rs
@@ -29,11 +29,24 @@ pub(crate) fn preprocess(
if let Event::Start(Tag::CodeBlock(Fenced(info_string))) = event.clone() {
let span_content = &content[span.start..span.end];
+ // Scan for a line start before this span.
+ // For safety, only scan up to a fixed limit of the text
+ const INDENT_SCAN_MAX: usize = 1024;
+ // If there's less text than that, just scan from the start
+ let line_scan_start = span.start.checked_sub(INDENT_SCAN_MAX).unwrap_or_default();
+ // If we can't find a newline, assume no indent
+ let indent = content[line_scan_start..span.start]
+ .chars()
+ .rev()
+ .position(|c| c == '\n')
+ .unwrap_or_default();
+
let admonition = match parse_admonition(
info_string.as_ref(),
admonition_defaults,
span_content,
on_failure,
+ indent,
) {
Some(admonition) => admonition,
None => continue,
@@ -732,4 +745,62 @@ Text
assert_eq!(expected, prep(content));
}
+
+ #[test]
+ fn list_embed() {
+ let content = r#"# Chapter
+
+1. Thing one
+
+ ```sh
+ Thing one
+ ```
+
+1. Thing two
+
+ ```admonish
+ Thing two
+ ```
+
+1. Thing three
+
+ ```sh
+ Thing three
+ ```
+"#;
+
+ let expected = r##"# Chapter
+
+1. Thing one
+
+ ```sh
+ Thing one
+ ```
+
+1. Thing two
+
+
+ <div id="admonition-note" class="admonition note">
+ <div class="admonition-title">
+
+ Note
+
+ <a class="admonition-anchor-link" href="#admonition-note"></a>
+ </div>
+ <div>
+
+ Thing two
+
+ </div>
+ </div>
+
+1. Thing three
+
+ ```sh
+ Thing three
+ ```
+"##;
+
+ assert_eq!(expected, prep(content));
+ }
}
diff --git a/src/parse.rs b/src/parse.rs
index 89953cb..e06b234 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -23,6 +23,7 @@ pub(crate) fn parse_admonition<'a>(
admonition_defaults: &'a AdmonitionDefaults,
content: &'a str,
on_failure: OnFailure,
+ indent: usize,
) -> Option<Result<Admonition<'a>>> {
// We need to know fence details anyway for error messages
let extracted = extract_admonish_body(content);
@@ -30,8 +31,6 @@ pub(crate) fn parse_admonition<'a>(
let info = AdmonitionMeta::from_info_string(info_string, admonition_defaults)?;
let info = match info {
Ok(info) => info,
- // FIXME return error messages to break build if configured
- // Err(message) => return Some(Err(content)),
Err(message) => {
// Construct a fence capable of enclosing whatever we wrote for the
// actual input block
@@ -63,6 +62,7 @@ Original markdown input:
{enclosing_fence}
"#
)),
+ indent,
})
}
OnFailure::Bail => Err(anyhow!("Error processing admonition, bailing:\n{content}")),
@@ -70,7 +70,24 @@ Original markdown input:
}
};
- Some(Ok(Admonition::new(info, extracted.body)))
+ Some(Ok(Admonition::new(
+ info,
+ extracted.body,
+ // Note that this is a bit hacky - the fence information comes from the start
+ // of the block, and includes the whole line.
+ //
+ // This is more likely to be what we want, as ending indentation is unrelated
+ // according to the commonmark spec (ref https://spec.commonmark.org/0.12/#example-85)
+ //
+ // The main case we're worried about here is indenting enough to be inside list items,
+ // and in this case the starting code fence must be indented enough to be considered
+ // part of the list item.
+ //
+ // The hacky thing is that we're considering line indent in the document as a whole,
+ // not relative to the context of some containing item. But I think that's what we
+ // want for now, anyway.
+ indent,
+ )))
}
/// We can't trust the info string length to find the start of the body
diff --git a/src/render.rs b/src/render.rs
index b1094b6..d6e9eff 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -31,10 +31,11 @@ pub(crate) struct Admonition<'a> {
pub(crate) content: Cow<'a, str>,
pub(crate) additional_classnames: Vec<String>,
pub(crate) collapsible: bool,
+ pub(crate) indent: usize,
}
impl<'a> Admonition<'a> {
- pub(crate) fn new(info: AdmonitionMeta, content: &'a str) -> Self {
+ pub(crate) fn new(info: AdmonitionMeta, content: &'a str, indent: usize) -> Self {
let AdmonitionMeta {
directive,
title,
@@ -47,6 +48,7 @@ impl<'a> Admonition<'a> {
content: Cow::Borrowed(content),
additional_classnames,
collapsible,
+ indent,
}
}
@@ -66,17 +68,18 @@ impl<'a> Admonition<'a> {
let mut additional_class = Cow::Borrowed(self.directive.classname());
let title = &self.title;
let content = &self.content;
+ let indent = " ".repeat(self.indent);
let title_block = if self.collapsible { "summary" } else { "div" };
let title_html = if !title.is_empty() {
Cow::Owned(format!(
- r##"<{title_block} class="admonition-title">
-
-{title}
-
-<a class="admonition-anchor-link" href="#{ANCHOR_ID_PREFIX}-{anchor_id}"></a>
-</{title_block}>
+ r##"{indent}<{title_block} class="admonition-title">
+{indent}
+{indent}{title}
+{indent}
+{indent}<a class="admonition-anchor-link" href="#{ANCHOR_ID_PREFIX}-{anchor_id}"></a>
+{indent}</{title_block}>
"##
))
} else {
@@ -100,13 +103,13 @@ impl<'a> Admonition<'a> {
// rendered as markdown paragraphs.
format!(
r#"
-<{admonition_block} id="{ANCHOR_ID_PREFIX}-{anchor_id}" class="admonition {additional_class}">
-{title_html}<div>
-
-{content}
-
-</div>
-</{admonition_block}>"#,
+{indent}<{admonition_block} id="{ANCHOR_ID_PREFIX}-{anchor_id}" class="admonition {additional_class}">
+{title_html}{indent}<div>
+{indent}
+{indent}{content}
+{indent}
+{indent}</div>
+{indent}</{admonition_block}>"#,
)
}