Skip to main content

panache_parser/parser/utils/
inline_emission.rs

1//! Inline element emission during block parsing.
2//!
3//! This module provides utilities for emitting inline structure directly during
4//! block parsing, using Pandoc's single-pass architecture.
5//!
6//! **Key invariant**: "Detect first, emit once"
7//! Because GreenNodeBuilder cannot backtrack, we must determine what to emit
8//! before calling builder methods. The inline parser already follows this pattern
9//! (it detects delimiters/patterns before emitting nodes).
10
11use crate::config::Config;
12use crate::parser::inlines::core;
13use rowan::GreenNodeBuilder;
14
15/// Emit inline elements from text content directly into the builder.
16///
17/// This helper calls the recursive inline parser, allowing block-level
18/// parsers to emit inline structure during parsing.
19///
20/// # Arguments
21/// * `builder` - The GreenNodeBuilder to emit nodes into
22/// * `text` - The text content to parse for inline elements
23/// * `config` - Configuration controlling which extensions are enabled
24///
25/// # Example
26/// ```ignore
27/// // In a block parser (e.g., headings):
28/// builder.start_node(SyntaxKind::HEADING_CONTENT.into());
29/// emit_inlines(builder, heading_text, config);
30/// builder.finish_node();
31/// ```
32pub fn emit_inlines(builder: &mut GreenNodeBuilder, text: &str, config: &Config) {
33    log::trace!(
34        "emit_inlines: {:?} ({} bytes)",
35        &text[..text.len().min(40)],
36        text.len()
37    );
38
39    // Call the recursive inline parser
40    core::parse_inline_text_recursive(builder, text, config);
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use crate::config::Config;
47    use crate::syntax::{SyntaxKind, SyntaxNode};
48    use rowan::GreenNodeBuilder;
49
50    /// Test that emit_inlines produces correct inline structure.
51    #[test]
52    fn test_emit_inlines_basic() {
53        let config = Config::default();
54        let test_cases = vec![
55            "plain text",
56            "text with *emphasis*",
57            "text with **strong**",
58            "text with `code`",
59            "text with [link](url)",
60            "mixed *emph* and **strong** and `code`",
61            "nested *emphasis with `code` inside*",
62            "multiple *a* and *b* emphasis",
63        ];
64
65        for text in test_cases {
66            // Build using emit_inlines
67            let mut builder_new = GreenNodeBuilder::new();
68            builder_new.start_node(SyntaxKind::HEADING_CONTENT.into());
69            emit_inlines(&mut builder_new, text, &config);
70            builder_new.finish_node();
71            let green_new = builder_new.finish();
72            let tree_new = SyntaxNode::new_root(green_new);
73
74            // Verify losslessness
75            assert_eq!(
76                tree_new.text().to_string(),
77                text,
78                "Losslessness check failed for: {:?}",
79                text
80            );
81        }
82    }
83
84    /// Test that emit_inlines handles empty text correctly.
85    #[test]
86    fn test_emit_inlines_empty() {
87        let config = Config::default();
88        let mut builder = GreenNodeBuilder::new();
89        builder.start_node(SyntaxKind::HEADING_CONTENT.into());
90        emit_inlines(&mut builder, "", &config);
91        builder.finish_node();
92        let green = builder.finish();
93        let tree = SyntaxNode::new_root(green);
94
95        // Should produce a container with no inline content
96        assert_eq!(tree.kind(), SyntaxKind::HEADING_CONTENT);
97        assert_eq!(tree.children_with_tokens().count(), 0);
98    }
99
100    /// Test that emit_inlines preserves whitespace.
101    #[test]
102    fn test_emit_inlines_preserves_whitespace() {
103        let config = Config::default();
104        let text = "  leading and trailing  ";
105
106        let mut builder = GreenNodeBuilder::new();
107        builder.start_node(SyntaxKind::HEADING_CONTENT.into());
108        emit_inlines(&mut builder, text, &config);
109        builder.finish_node();
110        let green = builder.finish();
111        let tree = SyntaxNode::new_root(green);
112
113        // Should preserve all whitespace
114        assert_eq!(tree.text().to_string(), text);
115    }
116}