Skip to main content

satteri_plugin_api/
context.rs

1use crate::commands::{Command, NewNode};
2use crate::data::{DataMap, DataValue, TypedDataMap};
3use satteri_arena::Arena;
4
5/// Context passed to Rust plugin visitor methods and before/after hooks.
6pub struct PluginContext<'a> {
7    arena: &'a Arena,
8    pub(crate) data_map: &'a mut DataMap,
9    pub(crate) typed_data: &'a mut TypedDataMap,
10    commands: Vec<Command>,
11    diagnostics: Vec<Diagnostic>,
12}
13
14#[derive(Debug, Clone)]
15pub struct Diagnostic {
16    pub message: String,
17    pub node_id: Option<u32>,
18    pub severity: Severity,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Severity {
23    Error,
24    Warning,
25    Info,
26}
27
28impl<'a> PluginContext<'a> {
29    pub(crate) fn new(
30        arena: &'a Arena,
31        data_map: &'a mut DataMap,
32        typed_data: &'a mut TypedDataMap,
33    ) -> Self {
34        Self {
35            arena,
36            data_map,
37            typed_data,
38            commands: Vec::new(),
39            diagnostics: Vec::new(),
40        }
41    }
42
43    // ── Arena access ──────────────────────────────────────────────────────────
44
45    pub fn arena(&self) -> &Arena {
46        self.arena
47    }
48
49    /// Extract all text content from a subtree (depth-first concatenation)
50    pub fn extract_text(&self, node_id: u32) -> String {
51        use satteri_mdast::codec::decode_string_ref_data;
52        use satteri_mdast::MdastNodeType;
53        let node = self.arena.get_node(node_id);
54        if node.node_type == MdastNodeType::Text as u8
55            || node.node_type == MdastNodeType::InlineCode as u8
56        {
57            let data = self.arena.get_type_data(node_id);
58            if !data.is_empty() {
59                let string_ref = decode_string_ref_data(data);
60                return self.arena.get_str(string_ref).to_string();
61            }
62            return String::new();
63        }
64        let children = self.arena.get_children(node_id).to_vec();
65        children
66            .iter()
67            .map(|&child_id| self.extract_text(child_id))
68            .collect::<Vec<_>>()
69            .join("")
70    }
71
72    // ── Untyped data (interoperable with JS node.data) ────────────────────────
73
74    pub fn set_data(&mut self, node_id: u32, key: &str, value: DataValue) {
75        self.data_map.set(node_id, key, value);
76    }
77
78    pub fn get_data(&self, node_id: u32, key: &str) -> Option<&DataValue> {
79        self.data_map.get(node_id, key)
80    }
81
82    // ── Typed data (Rust-only, fast) ──────────────────────────────────────────
83
84    pub fn set_typed_data<T: std::any::Any + Send + Sync>(&mut self, node_id: u32, value: T) {
85        self.typed_data.set(node_id, value);
86    }
87
88    pub fn get_typed_data<T: std::any::Any + Send + Sync>(&self, node_id: u32) -> Option<&T> {
89        self.typed_data.get(node_id)
90    }
91
92    // ── Structural mutation commands ──────────────────────────────────────────
93
94    pub fn replace_node(&mut self, node_id: u32, new_node: NewNode) {
95        self.commands.push(Command::Replace { node_id, new_node });
96    }
97
98    pub fn remove_node(&mut self, node_id: u32) {
99        self.commands.push(Command::Remove { node_id });
100    }
101
102    pub fn insert_before(&mut self, node_id: u32, new_node: NewNode) {
103        self.commands
104            .push(Command::InsertBefore { node_id, new_node });
105    }
106
107    pub fn insert_after(&mut self, node_id: u32, new_node: NewNode) {
108        self.commands
109            .push(Command::InsertAfter { node_id, new_node });
110    }
111
112    pub fn wrap_node(&mut self, node_id: u32, parent_node: NewNode) {
113        self.commands.push(Command::Wrap {
114            node_id,
115            parent_node,
116        });
117    }
118
119    pub fn prepend_child(&mut self, node_id: u32, child_node: NewNode) {
120        self.commands.push(Command::PrependChild {
121            node_id,
122            child_node,
123        });
124    }
125
126    pub fn append_child(&mut self, node_id: u32, child_node: NewNode) {
127        self.commands.push(Command::AppendChild {
128            node_id,
129            child_node,
130        });
131    }
132
133    // ── Diagnostics ───────────────────────────────────────────────────────────
134
135    pub fn report(&mut self, message: impl Into<String>, node_id: Option<u32>, severity: Severity) {
136        self.diagnostics.push(Diagnostic {
137            message: message.into(),
138            node_id,
139            severity,
140        });
141    }
142
143    pub fn error(&mut self, message: impl Into<String>, node_id: Option<u32>) {
144        self.report(message, node_id, Severity::Error);
145    }
146
147    pub fn warn(&mut self, message: impl Into<String>, node_id: Option<u32>) {
148        self.report(message, node_id, Severity::Warning);
149    }
150
151    // ── Take results ─────────────────────────────────────────────────────────
152
153    pub(crate) fn take_commands(self) -> (Vec<Command>, Vec<Diagnostic>) {
154        (self.commands, self.diagnostics)
155    }
156}