diff options
author | Tom Milligan <tommilligan@users.noreply.github.com> | 2023-09-10 00:02:58 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-10 00:02:58 +0100 |
commit | 8e68cf919f92116c61053a1f51b2a9ea34c2ede2 (patch) | |
tree | 88e4fbfb350bca9c165efeffc2119b7a8cd5b757 | |
parent | 771e9c9fd8e9cac941b9da8a96cbb1298437e933 (diff) | |
parent | 02640dab1f288d61db20268dad36e9b6e1787674 (diff) |
Merge pull request #124 from tommilligan/bug-123
feat: support admonitions inside list items
-rw-r--r-- | integration/expected/chapter_1_main.html | 25 | ||||
-rw-r--r-- | integration/src/chapter_1.md | 20 | ||||
-rw-r--r-- | src/markdown.rs | 71 | ||||
-rw-r--r-- | src/parse.rs | 23 | ||||
-rw-r--r-- | src/render.rs | 31 |
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}>"#, ) } |