panache_parser/syntax/
links.rs1use super::ast::support;
4use super::{AstNode, PanacheLanguage, SyntaxKind, SyntaxNode};
5
6pub struct Link(SyntaxNode);
7
8impl AstNode for Link {
9 type Language = PanacheLanguage;
10
11 fn can_cast(kind: SyntaxKind) -> bool {
12 kind == SyntaxKind::LINK
13 }
14
15 fn cast(syntax: SyntaxNode) -> Option<Self> {
16 if Self::can_cast(syntax.kind()) {
17 Some(Self(syntax))
18 } else {
19 None
20 }
21 }
22
23 fn syntax(&self) -> &SyntaxNode {
24 &self.0
25 }
26}
27
28impl Link {
29 pub fn text(&self) -> Option<LinkText> {
31 support::child(&self.0)
32 }
33
34 pub fn dest(&self) -> Option<LinkDest> {
36 support::child(&self.0)
37 }
38
39 pub fn reference(&self) -> Option<LinkRef> {
41 support::child(&self.0)
42 }
43}
44
45pub struct AutoLink(SyntaxNode);
46
47impl AstNode for AutoLink {
48 type Language = PanacheLanguage;
49
50 fn can_cast(kind: SyntaxKind) -> bool {
51 kind == SyntaxKind::AUTO_LINK
52 }
53
54 fn cast(syntax: SyntaxNode) -> Option<Self> {
55 if Self::can_cast(syntax.kind()) {
56 Some(Self(syntax))
57 } else {
58 None
59 }
60 }
61
62 fn syntax(&self) -> &SyntaxNode {
63 &self.0
64 }
65}
66
67impl AutoLink {
68 pub fn target(&self) -> String {
70 self.0
71 .children_with_tokens()
72 .filter_map(|it| it.into_token())
73 .filter(|token| token.kind() == SyntaxKind::TEXT)
74 .map(|token| token.text().to_string())
75 .collect()
76 }
77}
78
79pub struct LinkText(SyntaxNode);
80
81impl AstNode for LinkText {
82 type Language = PanacheLanguage;
83
84 fn can_cast(kind: SyntaxKind) -> bool {
85 kind == SyntaxKind::LINK_TEXT
86 }
87
88 fn cast(syntax: SyntaxNode) -> Option<Self> {
89 if Self::can_cast(syntax.kind()) {
90 Some(Self(syntax))
91 } else {
92 None
93 }
94 }
95
96 fn syntax(&self) -> &SyntaxNode {
97 &self.0
98 }
99}
100
101impl LinkText {
102 pub fn text_content(&self) -> String {
104 self.0
105 .descendants_with_tokens()
106 .filter_map(|it| it.into_token())
107 .filter(|token| token.kind() == SyntaxKind::TEXT)
108 .map(|token| token.text().to_string())
109 .collect()
110 }
111}
112
113pub struct LinkDest(SyntaxNode);
114
115impl AstNode for LinkDest {
116 type Language = PanacheLanguage;
117
118 fn can_cast(kind: SyntaxKind) -> bool {
119 kind == SyntaxKind::LINK_DEST
120 }
121
122 fn cast(syntax: SyntaxNode) -> Option<Self> {
123 if Self::can_cast(syntax.kind()) {
124 Some(Self(syntax))
125 } else {
126 None
127 }
128 }
129
130 fn syntax(&self) -> &SyntaxNode {
131 &self.0
132 }
133}
134
135impl LinkDest {
136 pub fn url(&self) -> String {
138 self.0.text().to_string()
139 }
140
141 pub fn url_content(&self) -> String {
143 let text = self.0.text().to_string();
144 text.trim_start_matches('(')
145 .trim_end_matches(')')
146 .to_string()
147 }
148
149 pub fn hash_anchor_id_range(&self) -> Option<rowan::TextRange> {
151 let text = self.0.text().to_string();
152 let hash_idx = text.find('#')?;
153 let after_hash = &text[hash_idx + 1..];
154 let id_len = after_hash
155 .chars()
156 .take_while(|ch| !ch.is_whitespace() && *ch != ')')
157 .map(char::len_utf8)
158 .sum::<usize>();
159 if id_len == 0 {
160 return None;
161 }
162 let node_start: usize = self.0.text_range().start().into();
163 let start = rowan::TextSize::from((node_start + hash_idx + 1) as u32);
164 let end = rowan::TextSize::from((node_start + hash_idx + 1 + id_len) as u32);
165 Some(rowan::TextRange::new(start, end))
166 }
167
168 pub fn hash_anchor_id(&self) -> Option<String> {
170 let text = self.0.text().to_string();
171 let hash_idx = text.find('#')?;
172 let after_hash = &text[hash_idx + 1..];
173 let id_len = after_hash
174 .chars()
175 .take_while(|ch| !ch.is_whitespace() && *ch != ')')
176 .map(char::len_utf8)
177 .sum::<usize>();
178 if id_len == 0 {
179 return None;
180 }
181 Some(after_hash[..id_len].to_string())
182 }
183}
184
185pub struct LinkRef(SyntaxNode);
186
187impl AstNode for LinkRef {
188 type Language = PanacheLanguage;
189
190 fn can_cast(kind: SyntaxKind) -> bool {
191 kind == SyntaxKind::LINK_REF
192 }
193
194 fn cast(syntax: SyntaxNode) -> Option<Self> {
195 if Self::can_cast(syntax.kind()) {
196 Some(Self(syntax))
197 } else {
198 None
199 }
200 }
201
202 fn syntax(&self) -> &SyntaxNode {
203 &self.0
204 }
205}
206
207impl LinkRef {
208 pub fn label(&self) -> String {
210 self.0
211 .children_with_tokens()
212 .filter_map(|it| it.into_token())
213 .filter(|token| token.kind() == SyntaxKind::TEXT)
214 .map(|token| token.text().to_string())
215 .collect()
216 }
217
218 pub fn label_range(&self) -> Option<rowan::TextRange> {
220 self.0
221 .children_with_tokens()
222 .filter_map(|it| it.into_token())
223 .find(|token| token.kind() == SyntaxKind::TEXT)
224 .map(|token| token.text_range())
225 }
226
227 pub fn label_value_range(&self) -> Option<rowan::TextRange> {
229 self.label_range()
230 }
231}
232
233pub struct ImageLink(SyntaxNode);
234
235impl AstNode for ImageLink {
236 type Language = PanacheLanguage;
237
238 fn can_cast(kind: SyntaxKind) -> bool {
239 kind == SyntaxKind::IMAGE_LINK
240 }
241
242 fn cast(syntax: SyntaxNode) -> Option<Self> {
243 if Self::can_cast(syntax.kind()) {
244 Some(Self(syntax))
245 } else {
246 None
247 }
248 }
249
250 fn syntax(&self) -> &SyntaxNode {
251 &self.0
252 }
253}
254
255impl ImageLink {
256 pub fn alt(&self) -> Option<ImageAlt> {
258 support::child(&self.0)
259 }
260
261 pub fn dest(&self) -> Option<LinkDest> {
263 support::child(&self.0)
264 }
265
266 pub fn reference(&self) -> Option<LinkRef> {
268 support::child(&self.0)
269 }
270
271 pub fn reference_label(&self) -> Option<String> {
273 self.reference().map(|link_ref| link_ref.label())
274 }
275
276 pub fn reference_label_range(&self) -> Option<rowan::TextRange> {
278 self.reference().and_then(|link_ref| link_ref.label_range())
279 }
280}
281
282pub struct ImageAlt(SyntaxNode);
283
284impl AstNode for ImageAlt {
285 type Language = PanacheLanguage;
286
287 fn can_cast(kind: SyntaxKind) -> bool {
288 kind == SyntaxKind::IMAGE_ALT
289 }
290
291 fn cast(syntax: SyntaxNode) -> Option<Self> {
292 if Self::can_cast(syntax.kind()) {
293 Some(Self(syntax))
294 } else {
295 None
296 }
297 }
298
299 fn syntax(&self) -> &SyntaxNode {
300 &self.0
301 }
302}
303
304impl ImageAlt {
305 pub fn text(&self) -> String {
307 self.0
308 .descendants_with_tokens()
309 .filter_map(|it| it.into_token())
310 .filter(|token| token.kind() == SyntaxKind::TEXT)
311 .map(|token| token.text().to_string())
312 .collect()
313 }
314}
315
316pub struct Figure(SyntaxNode);
317
318impl AstNode for Figure {
319 type Language = PanacheLanguage;
320
321 fn can_cast(kind: SyntaxKind) -> bool {
322 kind == SyntaxKind::FIGURE
323 }
324
325 fn cast(syntax: SyntaxNode) -> Option<Self> {
326 if Self::can_cast(syntax.kind()) {
327 Some(Self(syntax))
328 } else {
329 None
330 }
331 }
332
333 fn syntax(&self) -> &SyntaxNode {
334 &self.0
335 }
336}
337
338impl Figure {
339 pub fn image(&self) -> Option<ImageLink> {
341 support::child(&self.0)
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use super::{AstNode, ImageLink};
348
349 #[test]
350 fn image_reference_label_and_range_are_extracted() {
351 let input = "![Alt text][img]";
352 let tree = crate::parse(input, None);
353 let image = tree
354 .descendants()
355 .find_map(ImageLink::cast)
356 .expect("image link");
357
358 assert_eq!(image.reference_label().as_deref(), Some("img"));
359
360 let range = image.reference_label_range().expect("label range");
361 let start: usize = range.start().into();
362 let end: usize = range.end().into();
363 assert_eq!(&input[start..end], "img");
364 }
365}