Skip to main content

panache_parser/syntax/
inlines.rs

1//! Inline AST node wrappers.
2
3use super::{AstNode, PanacheLanguage, SyntaxKind, SyntaxNode};
4
5pub struct InlineMath(SyntaxNode);
6
7impl AstNode for InlineMath {
8    type Language = PanacheLanguage;
9
10    fn can_cast(kind: SyntaxKind) -> bool {
11        kind == SyntaxKind::INLINE_MATH
12    }
13
14    fn cast(syntax: SyntaxNode) -> Option<Self> {
15        if Self::can_cast(syntax.kind()) {
16            Some(Self(syntax))
17        } else {
18            None
19        }
20    }
21
22    fn syntax(&self) -> &SyntaxNode {
23        &self.0
24    }
25}
26
27impl InlineMath {
28    pub fn opening_marker(&self) -> Option<String> {
29        self.0.children_with_tokens().find_map(|child| {
30            child.into_token().and_then(|token| {
31                (token.kind() == SyntaxKind::INLINE_MATH_MARKER).then(|| token.text().to_string())
32            })
33        })
34    }
35
36    pub fn closing_marker(&self) -> Option<String> {
37        self.0
38            .children_with_tokens()
39            .filter_map(|child| child.into_token())
40            .filter(|token| token.kind() == SyntaxKind::INLINE_MATH_MARKER)
41            .nth(1)
42            .map(|token| token.text().to_string())
43    }
44
45    pub fn content(&self) -> String {
46        self.0
47            .children_with_tokens()
48            .filter_map(|child| child.into_token())
49            .filter(|token| token.kind() != SyntaxKind::INLINE_MATH_MARKER)
50            .map(|token| token.text().to_string())
51            .collect::<Vec<_>>()
52            .join("")
53    }
54
55    pub fn content_range(&self) -> Option<rowan::TextRange> {
56        let mut markers = self
57            .0
58            .children_with_tokens()
59            .filter_map(|child| child.into_token())
60            .filter(|token| token.kind() == SyntaxKind::INLINE_MATH_MARKER);
61
62        let start = markers.next()?.text_range().end();
63        let end = markers.next()?.text_range().start();
64        (start <= end).then(|| rowan::TextRange::new(start, end))
65    }
66}
67
68pub struct CodeSpan(SyntaxNode);
69
70impl AstNode for CodeSpan {
71    type Language = PanacheLanguage;
72
73    fn can_cast(kind: SyntaxKind) -> bool {
74        kind == SyntaxKind::INLINE_CODE
75    }
76
77    fn cast(syntax: SyntaxNode) -> Option<Self> {
78        if Self::can_cast(syntax.kind()) {
79            Some(Self(syntax))
80        } else {
81            None
82        }
83    }
84
85    fn syntax(&self) -> &SyntaxNode {
86        &self.0
87    }
88}
89
90impl CodeSpan {
91    pub fn marker(&self) -> Option<String> {
92        self.0.children_with_tokens().find_map(|child| {
93            child.into_token().and_then(|token| {
94                (token.kind() == SyntaxKind::INLINE_CODE_MARKER).then(|| token.text().to_string())
95            })
96        })
97    }
98
99    pub fn content(&self) -> String {
100        self.0
101            .children_with_tokens()
102            .filter_map(|child| child.into_token())
103            .filter(|token| token.kind() == SyntaxKind::INLINE_CODE_CONTENT)
104            .map(|token| token.text().to_string())
105            .collect::<Vec<_>>()
106            .join("")
107    }
108
109    pub fn content_range(&self) -> Option<rowan::TextRange> {
110        let mut markers = self
111            .0
112            .children_with_tokens()
113            .filter_map(|child| child.into_token())
114            .filter(|token| token.kind() == SyntaxKind::INLINE_CODE_MARKER);
115
116        let start = markers.next()?.text_range().end();
117        let end = markers.next()?.text_range().start();
118        (start <= end).then(|| rowan::TextRange::new(start, end))
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn inline_math_extracts_markers_and_content() {
128        let input = "Before $x^2 + y^2$ after\n";
129        let tree = crate::parse(input, None);
130        let math = tree
131            .descendants()
132            .find_map(InlineMath::cast)
133            .expect("inline math");
134
135        assert_eq!(math.opening_marker().as_deref(), Some("$"));
136        assert_eq!(math.closing_marker().as_deref(), Some("$"));
137        assert_eq!(math.content(), "x^2 + y^2");
138        let range = math.content_range().expect("content range");
139        let start: usize = range.start().into();
140        let end: usize = range.end().into();
141        assert_eq!(&input[start..end], "x^2 + y^2");
142    }
143
144    #[test]
145    fn code_span_extracts_marker_and_content() {
146        let input = "Use `code` here\n";
147        let tree = crate::parse(input, None);
148        let code = tree
149            .descendants()
150            .find_map(CodeSpan::cast)
151            .expect("code span");
152
153        assert_eq!(code.marker().as_deref(), Some("`"));
154        assert_eq!(code.content(), "code");
155        let range = code.content_range().expect("content range");
156        let start: usize = range.start().into();
157        let end: usize = range.end().into();
158        assert_eq!(&input[start..end], "code");
159    }
160}