panache_parser/syntax/
attributes.rs1use crate::parser::utils::attributes::try_parse_trailing_attributes;
2use crate::syntax::{AstNode, PanacheLanguage, SyntaxKind, SyntaxNode};
3
4#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5pub struct AttributeNode(SyntaxNode);
6
7impl AstNode for AttributeNode {
8 type Language = PanacheLanguage;
9
10 fn can_cast(kind: SyntaxKind) -> bool {
11 matches!(kind, SyntaxKind::ATTRIBUTE | SyntaxKind::DIV_INFO)
12 }
13
14 fn cast(node: SyntaxNode) -> Option<Self> {
15 Self::can_cast(node.kind()).then(|| AttributeNode(node))
16 }
17
18 fn syntax(&self) -> &SyntaxNode {
19 &self.0
20 }
21}
22
23impl AttributeNode {
24 pub fn id(&self) -> Option<String> {
25 let text = self.0.text().to_string();
26 try_parse_trailing_attributes(&text)
27 .and_then(|(attrs, _)| attrs.identifier)
28 .filter(|id| !id.is_empty())
29 }
30
31 pub fn id_value_range(&self) -> Option<rowan::TextRange> {
32 let id = self.id()?;
33 let text = self.0.text().to_string();
34 let marker = text.find(&format!("#{}", id))?;
35 let node_start: usize = self.0.text_range().start().into();
36 let start = rowan::TextSize::from((node_start + marker + 1) as u32);
37 let end = rowan::TextSize::from((node_start + marker + 1 + id.len()) as u32);
38 Some(rowan::TextRange::new(start, end))
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 #[test]
47 fn attribute_node_extracts_div_info_id_and_range() {
48 let config = crate::ParserOptions {
49 flavor: crate::options::Flavor::RMarkdown,
50 ..Default::default()
51 };
52 let tree = crate::parse("::: {#mu .exercise}\ntext\n:::\n", Some(config));
53 let node = tree
54 .descendants()
55 .find_map(AttributeNode::cast)
56 .expect("attribute node");
57 assert_eq!(node.id().as_deref(), Some("mu"));
58
59 let range = node.id_value_range().expect("id range");
60 let start: usize = range.start().into();
61 let end: usize = range.end().into();
62 assert_eq!(&tree.text().to_string()[start..end], "mu");
63 }
64}