Skip to main content

satteri_plugin_api/
commands.rs

1use satteri_ast::mdast::MdastNodeType;
2
3/// A structural mutation command queued during plugin execution.
4/// Applied after the plugin finishes (same as JS).
5#[derive(Debug, Clone)]
6pub enum Command {
7    /// Replace a node with a new subtree
8    Replace { node_id: u32, new_node: NewNode },
9    /// Remove a node entirely
10    Remove { node_id: u32 },
11    /// Insert a new node before the target
12    InsertBefore { node_id: u32, new_node: NewNode },
13    /// Insert a new node after the target
14    InsertAfter { node_id: u32, new_node: NewNode },
15    /// Wrap a node in a new parent
16    Wrap { node_id: u32, parent_node: NewNode },
17    /// Prepend a child to a node
18    PrependChild { node_id: u32, child_node: NewNode },
19    /// Append a child to a node
20    AppendChild { node_id: u32, child_node: NewNode },
21    /// Set a scalar field on a node (used for simple property changes)
22    SetData {
23        node_id: u32,
24        key: String,
25        value: crate::data::DataValue,
26    },
27}
28
29/// A new node to be inserted into the arena.
30/// In Phase 5, this is a simple enum. The builder in PluginContext
31/// creates these to queue for arena rebuild.
32#[derive(Debug, Clone)]
33pub enum NewNode {
34    /// A raw Markdown string that Rust parses (the `raw` escape hatch)
35    Raw(String),
36    /// A fully specified node (built with NodeBuilder)
37    Built(BuiltNode),
38}
39
40/// A node specification built with NodeBuilder
41#[derive(Debug, Clone)]
42pub struct BuiltNode {
43    pub node_type: MdastNodeType,
44    pub children: Vec<NewNode>,
45    /// Type-specific data bytes (same format as arena type_data)
46    pub data_bytes: Vec<u8>,
47    /// Optional position override
48    pub position: Option<crate::typed_nodes::NodePosition>,
49}
50
51/// Builder for constructing new nodes to pass to commands.
52pub struct NodeBuilder {
53    node_type: MdastNodeType,
54    children: Vec<NewNode>,
55    data_bytes: Vec<u8>,
56}
57
58impl NodeBuilder {
59    pub fn new(node_type: MdastNodeType) -> Self {
60        Self {
61            node_type,
62            children: Vec::new(),
63            data_bytes: Vec::new(),
64        }
65    }
66
67    /// Add a child node (another builder or raw string)
68    pub fn child(mut self, child: NewNode) -> Self {
69        self.children.push(child);
70        self
71    }
72
73    /// Add multiple children
74    pub fn children(mut self, children: impl IntoIterator<Item = NewNode>) -> Self {
75        self.children.extend(children);
76        self
77    }
78
79    /// Set raw type-data bytes (use codec encode_* functions)
80    pub fn data_bytes(mut self, bytes: Vec<u8>) -> Self {
81        self.data_bytes = bytes;
82        self
83    }
84
85    /// Finalize into a NewNode
86    pub fn build(self) -> NewNode {
87        NewNode::Built(BuiltNode {
88            node_type: self.node_type,
89            children: self.children,
90            data_bytes: self.data_bytes,
91            position: None,
92        })
93    }
94}
95
96/// Convenience constructors
97impl NodeBuilder {
98    pub fn heading(depth: u8) -> Self {
99        use satteri_ast::mdast::codec::encode_heading_data;
100        Self::new(MdastNodeType::Heading).data_bytes(encode_heading_data(depth))
101    }
102
103    pub fn paragraph() -> Self {
104        Self::new(MdastNodeType::Paragraph)
105    }
106
107    pub fn text(value_offset: u32, value_len: u32) -> Self {
108        use satteri_arena::{encode_string_ref_data, StringRef};
109        let string_ref = StringRef {
110            offset: value_offset,
111            len: value_len,
112        };
113        Self::new(MdastNodeType::Text).data_bytes(encode_string_ref_data(string_ref))
114    }
115
116    /// Create a text node with a raw string (for when we don't have source offsets)
117    /// This uses NewNode::Raw internally
118    pub fn raw(markdown: impl Into<String>) -> NewNode {
119        NewNode::Raw(markdown.into())
120    }
121}