panache_parser/syntax/
inlines.rs1use 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}