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)]
8pub enum InlineCategory {
9    Text,
10    TaskCheckboxInline,
11    Emphasis,
12    Strong,
13    StrongEmphasis,
14    Strikethrough,
15    Mark,
16    Superscript,
17    Subscript,
18    Link,
19    LinkReference,
20    FootnoteReference,
21    Image,
22    CodeSpan,
23    InlineHtml,
24    HardBreak,
25    SoftBreak,
26    PlatformMention,
27    InlineMath,
28    DisplayMath,
29}
30
31pub fn classify_inline_kind(kind: &NodeKind) -> Option<InlineCategory> {
32    match kind {
33        NodeKind::Text(_) => Some(InlineCategory::Text),
34        NodeKind::TaskCheckboxInline { .. } => Some(InlineCategory::TaskCheckboxInline),
35        NodeKind::Emphasis => Some(InlineCategory::Emphasis),
36        NodeKind::Strong => Some(InlineCategory::Strong),
37        NodeKind::StrongEmphasis => Some(InlineCategory::StrongEmphasis),
38        NodeKind::Strikethrough => Some(InlineCategory::Strikethrough),
39        NodeKind::Mark => Some(InlineCategory::Mark),
40        NodeKind::Superscript => Some(InlineCategory::Superscript),
41        NodeKind::Subscript => Some(InlineCategory::Subscript),
42        NodeKind::Link { .. } => Some(InlineCategory::Link),
43        NodeKind::LinkReference { .. } => Some(InlineCategory::LinkReference),
44        NodeKind::FootnoteReference { .. } => Some(InlineCategory::FootnoteReference),
45        NodeKind::Image { .. } => Some(InlineCategory::Image),
46        NodeKind::CodeSpan(_) => Some(InlineCategory::CodeSpan),
47        NodeKind::InlineHtml(_) => Some(InlineCategory::InlineHtml),
48        NodeKind::HardBreak => Some(InlineCategory::HardBreak),
49        NodeKind::SoftBreak => Some(InlineCategory::SoftBreak),
50        NodeKind::PlatformMention { .. } => Some(InlineCategory::PlatformMention),
51        NodeKind::InlineMath { .. } => Some(InlineCategory::InlineMath),
52        NodeKind::DisplayMath { .. } => Some(InlineCategory::DisplayMath),
53        _ => None,
54    }
55}
56
57pub fn is_inline_node(node: &Node) -> bool {
58    is_inline_kind(&node.kind)
59}
60
61/// Collect inline nodes recursively in pre-order.
62pub fn collect_inline_nodes<'a>(nodes: &'a [Node], out: &mut Vec<&'a Node>) {
63    for node in nodes {
64        if is_inline_node(node) {
65            out.push(node);
66        }
67        if !node.children.is_empty() {
68            collect_inline_nodes(&node.children, out);
69        }
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn smoke_test_classify_inline_kind_basic() {
79        assert_eq!(
80            classify_inline_kind(&NodeKind::Text("x".to_string())),
81            Some(InlineCategory::Text)
82        );
83        assert_eq!(classify_inline_kind(&NodeKind::Paragraph), None);
84    }
85
86    #[test]
87    fn smoke_test_collect_inline_nodes_recursive() {
88        let nodes = vec![Node {
89            kind: NodeKind::Paragraph,
90            span: None,
91            children: vec![Node {
92                kind: NodeKind::Link {
93                    url: "https://example.com".to_string(),
94                    title: None,
95                },
96                span: None,
97                children: vec![Node {
98                    kind: NodeKind::Text("example".to_string()),
99                    span: None,
100                    children: vec![],
101                }],
102            }],
103        }];
104
105        let mut inlines = Vec::new();
106        collect_inline_nodes(&nodes, &mut inlines);
107
108        assert_eq!(inlines.len(), 2);
109        assert!(matches!(inlines[0].kind, NodeKind::Link { .. }));
110        assert!(matches!(inlines[1].kind, NodeKind::Text(_)));
111    }
112}