Skip to main content

satteri_plugin_api/
typed_nodes.rs

1use satteri_arena::{Arena, ArenaNode};
2use satteri_ast::mdast::codec::*;
3use satteri_ast::mdast::MdastNodeType;
4
5/// Position info extracted from an ArenaNode
6#[derive(Debug, Clone, Copy)]
7pub struct NodePosition {
8    pub start_offset: u32,
9    pub end_offset: u32,
10    pub start_line: u32,
11    pub start_column: u32,
12    pub end_line: u32,
13    pub end_column: u32,
14}
15
16impl NodePosition {
17    pub fn from_node(node: &ArenaNode) -> Self {
18        Self {
19            start_offset: node.start_offset,
20            end_offset: node.end_offset,
21            start_line: node.start_line,
22            start_column: node.start_column,
23            end_line: node.end_line,
24            end_column: node.end_column,
25        }
26    }
27}
28
29/// A typed view over a Heading node in the arena.
30pub struct Heading<'a> {
31    pub(crate) node_id: u32,
32    pub(crate) arena: &'a Arena,
33}
34
35impl<'a> Heading<'a> {
36    pub fn id(&self) -> u32 {
37        self.node_id
38    }
39
40    pub fn depth(&self) -> u8 {
41        let data = self.arena.get_type_data(self.node_id);
42        decode_heading_data(data).depth
43    }
44
45    pub fn children(&self) -> &[u32] {
46        self.arena.get_children(self.node_id)
47    }
48
49    pub fn position(&self) -> NodePosition {
50        NodePosition::from_node(self.arena.get_node(self.node_id))
51    }
52}
53
54/// A typed view over a Text node (also used for InlineCode and Html).
55pub struct Text<'a> {
56    pub(crate) node_id: u32,
57    pub(crate) arena: &'a Arena,
58}
59
60impl<'a> Text<'a> {
61    pub fn id(&self) -> u32 {
62        self.node_id
63    }
64
65    pub fn value(&self) -> &str {
66        let data = self.arena.get_type_data(self.node_id);
67        let string_ref = decode_string_ref_data(data);
68        self.arena.get_str(string_ref)
69    }
70
71    pub fn position(&self) -> NodePosition {
72        NodePosition::from_node(self.arena.get_node(self.node_id))
73    }
74}
75
76/// A typed view over a Link node.
77pub struct Link<'a> {
78    pub(crate) node_id: u32,
79    pub(crate) arena: &'a Arena,
80}
81
82impl<'a> Link<'a> {
83    pub fn id(&self) -> u32 {
84        self.node_id
85    }
86
87    pub fn url(&self) -> &str {
88        let data = self.arena.get_type_data(self.node_id);
89        let link = decode_link_data(data);
90        self.arena.get_str(link.url)
91    }
92
93    pub fn title(&self) -> Option<&str> {
94        let data = self.arena.get_type_data(self.node_id);
95        let link = decode_link_data(data);
96        if link.title.len > 0 {
97            Some(self.arena.get_str(link.title))
98        } else {
99            None
100        }
101    }
102
103    pub fn children(&self) -> &[u32] {
104        self.arena.get_children(self.node_id)
105    }
106
107    pub fn position(&self) -> NodePosition {
108        NodePosition::from_node(self.arena.get_node(self.node_id))
109    }
110}
111
112/// A typed view over a Paragraph node.
113pub struct Paragraph<'a> {
114    pub(crate) node_id: u32,
115    pub(crate) arena: &'a Arena,
116}
117
118impl<'a> Paragraph<'a> {
119    pub fn id(&self) -> u32 {
120        self.node_id
121    }
122    pub fn children(&self) -> &[u32] {
123        self.arena.get_children(self.node_id)
124    }
125    pub fn position(&self) -> NodePosition {
126        NodePosition::from_node(self.arena.get_node(self.node_id))
127    }
128}
129
130/// A typed view over an Image node.
131pub struct Image<'a> {
132    pub(crate) node_id: u32,
133    pub(crate) arena: &'a Arena,
134}
135
136impl<'a> Image<'a> {
137    pub fn id(&self) -> u32 {
138        self.node_id
139    }
140
141    pub fn url(&self) -> &str {
142        let data = self.arena.get_type_data(self.node_id);
143        let img = decode_image_data(data);
144        self.arena.get_str(img.url)
145    }
146
147    pub fn alt(&self) -> &str {
148        let data = self.arena.get_type_data(self.node_id);
149        let img = decode_image_data(data);
150        self.arena.get_str(img.alt)
151    }
152
153    pub fn title(&self) -> Option<&str> {
154        let data = self.arena.get_type_data(self.node_id);
155        let img = decode_image_data(data);
156        if img.title.len > 0 {
157            Some(self.arena.get_str(img.title))
158        } else {
159            None
160        }
161    }
162
163    pub fn position(&self) -> NodePosition {
164        NodePosition::from_node(self.arena.get_node(self.node_id))
165    }
166}
167
168/// A typed view over a Code node.
169pub struct Code<'a> {
170    pub(crate) node_id: u32,
171    pub(crate) arena: &'a Arena,
172}
173
174impl<'a> Code<'a> {
175    pub fn id(&self) -> u32 {
176        self.node_id
177    }
178
179    pub fn lang(&self) -> Option<&str> {
180        let data = self.arena.get_type_data(self.node_id);
181        let code = decode_code_data(data);
182        if code.lang.len > 0 {
183            Some(self.arena.get_str(code.lang))
184        } else {
185            None
186        }
187    }
188
189    pub fn meta(&self) -> Option<&str> {
190        let data = self.arena.get_type_data(self.node_id);
191        let code = decode_code_data(data);
192        if code.meta.len > 0 {
193            Some(self.arena.get_str(code.meta))
194        } else {
195            None
196        }
197    }
198
199    /// The code content (the text inside the fence).
200    /// For Code nodes, the value is stored as a StringRef in the source after the CodeData.
201    /// However, in this arena implementation Code stores lang/meta but the value
202    /// is typically the node's text child or stored separately.
203    /// We read it from the source via offset/len stored in the node's position range,
204    /// or from a child Text node. For now, return via children text extraction.
205    pub fn value(&self) -> &str {
206        // Code nodes store lang/meta in type_data; the code value is in a child Text node
207        // or stored as additional StringRef data after CodeData. Check children first.
208        let children = self.arena.get_children(self.node_id);
209        if let Some(&child_id) = children.first() {
210            let child_node = self.arena.get_node(child_id);
211            if child_node.node_type == MdastNodeType::Text as u8 {
212                let data = self.arena.get_type_data(child_id);
213                if !data.is_empty() {
214                    let string_ref = decode_string_ref_data(data);
215                    return self.arena.get_str(string_ref);
216                }
217            }
218        }
219        // Fallback: if no child text, return empty
220        ""
221    }
222
223    pub fn position(&self) -> NodePosition {
224        NodePosition::from_node(self.arena.get_node(self.node_id))
225    }
226}