Skip to main content

marco_core/intelligence/markdown/
inlines.rs

1//! Inline-level markdown grammar/parsing namespace for intelligence.
2//!
3//! Current implementation reuses parser/grammar inlines from `crate::parser`.
4
5use super::ast::{is_inline_kind, Node, NodeKind};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8/// Inline semantic categories for AST nodes.
9pub enum InlineCategory {
10    /// Plain text inline node.
11    Text,
12    /// Inline task checkbox marker node.
13    TaskCheckboxInline,
14    /// Emphasis inline node.
15    Emphasis,
16    /// Strong inline node.
17    Strong,
18    /// Strong+emphasis inline node.
19    StrongEmphasis,
20    /// Strikethrough inline node.
21    Strikethrough,
22    /// Mark/highlight inline node.
23    Mark,
24    /// Superscript inline node.
25    Superscript,
26    /// Subscript inline node.
27    Subscript,
28    /// Link inline node.
29    Link,
30    /// Reference-style link inline node.
31    LinkReference,
32    /// Footnote reference inline node.
33    FootnoteReference,
34    /// Image inline node.
35    Image,
36    /// Code span inline node.
37    CodeSpan,
38    /// Inline HTML node.
39    InlineHtml,
40    /// Hard line break inline node.
41    HardBreak,
42    /// Soft line break inline node.
43    SoftBreak,
44    /// Platform mention inline node.
45    PlatformMention,
46    /// Inline math node.
47    InlineMath,
48    /// Display math node.
49    DisplayMath,
50}
51
52/// Classify a node kind as an inline category when applicable.
53pub fn classify_inline_kind(kind: &NodeKind) -> Option<InlineCategory> {
54    match kind {
55        NodeKind::Text(_) => Some(InlineCategory::Text),
56        NodeKind::TaskCheckboxInline { .. } => Some(InlineCategory::TaskCheckboxInline),
57        NodeKind::Emphasis => Some(InlineCategory::Emphasis),
58        NodeKind::Strong => Some(InlineCategory::Strong),
59        NodeKind::StrongEmphasis => Some(InlineCategory::StrongEmphasis),
60        NodeKind::Strikethrough => Some(InlineCategory::Strikethrough),
61        NodeKind::Mark => Some(InlineCategory::Mark),
62        NodeKind::Superscript => Some(InlineCategory::Superscript),
63        NodeKind::Subscript => Some(InlineCategory::Subscript),
64        NodeKind::Link { .. } => Some(InlineCategory::Link),
65        NodeKind::LinkReference { .. } => Some(InlineCategory::LinkReference),
66        NodeKind::FootnoteReference { .. } => Some(InlineCategory::FootnoteReference),
67        NodeKind::Image { .. } => Some(InlineCategory::Image),
68        NodeKind::CodeSpan(_) => Some(InlineCategory::CodeSpan),
69        NodeKind::InlineHtml(_) => Some(InlineCategory::InlineHtml),
70        NodeKind::HardBreak => Some(InlineCategory::HardBreak),
71        NodeKind::SoftBreak => Some(InlineCategory::SoftBreak),
72        NodeKind::PlatformMention { .. } => Some(InlineCategory::PlatformMention),
73        NodeKind::InlineMath { .. } => Some(InlineCategory::InlineMath),
74        NodeKind::DisplayMath { .. } => Some(InlineCategory::DisplayMath),
75        _ => None,
76    }
77}
78
79/// Returns `true` when the node is considered inline-level.
80pub fn is_inline_node(node: &Node) -> bool {
81    is_inline_kind(&node.kind)
82}
83
84/// Collect inline nodes recursively in pre-order.
85pub fn collect_inline_nodes<'a>(nodes: &'a [Node], out: &mut Vec<&'a Node>) {
86    for node in nodes {
87        if is_inline_node(node) {
88            out.push(node);
89        }
90        if !node.children.is_empty() {
91            collect_inline_nodes(&node.children, out);
92        }
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn smoke_test_classify_inline_kind_basic() {
102        assert_eq!(
103            classify_inline_kind(&NodeKind::Text("x".to_string())),
104            Some(InlineCategory::Text)
105        );
106        assert_eq!(classify_inline_kind(&NodeKind::Paragraph), None);
107    }
108
109    #[test]
110    fn smoke_test_collect_inline_nodes_recursive() {
111        let nodes = vec![Node {
112            kind: NodeKind::Paragraph,
113            span: None,
114            children: vec![Node {
115                kind: NodeKind::Link {
116                    url: "https://example.com".to_string(),
117                    title: None,
118                },
119                span: None,
120                children: vec![Node {
121                    kind: NodeKind::Text("example".to_string()),
122                    span: None,
123                    children: vec![],
124                }],
125            }],
126        }];
127
128        let mut inlines = Vec::new();
129        collect_inline_nodes(&nodes, &mut inlines);
130
131        assert_eq!(inlines.len(), 2);
132        assert!(matches!(inlines[0].kind, NodeKind::Link { .. }));
133        assert!(matches!(inlines[1].kind, NodeKind::Text(_)));
134    }
135}