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. The builder in PluginContext
30/// creates these to queue as patches applied in place.
31#[derive(Debug, Clone)]
32pub enum NewNode {
33    /// A raw Markdown string that Rust parses (the `raw` escape hatch)
34    Raw(String),
35    /// A fully specified node (built with NodeBuilder)
36    Built(BuiltNode),
37}
38
39/// A node specification built with NodeBuilder
40#[derive(Debug, Clone)]
41pub struct BuiltNode {
42    pub node_type: MdastNodeType,
43    pub children: Vec<NewNode>,
44    /// Type-specific data bytes (same format as arena type_data)
45    pub data_bytes: Vec<u8>,
46    /// Optional position override
47    pub position: Option<crate::typed_nodes::NodePosition>,
48}
49
50/// Builder for constructing new nodes to pass to commands.
51pub struct NodeBuilder {
52    node_type: MdastNodeType,
53    children: Vec<NewNode>,
54    data_bytes: Vec<u8>,
55}
56
57impl NodeBuilder {
58    pub fn new(node_type: MdastNodeType) -> Self {
59        Self {
60            node_type,
61            children: Vec::new(),
62            data_bytes: Vec::new(),
63        }
64    }
65
66    /// Add a child node (another builder or raw string)
67    pub fn child(mut self, child: NewNode) -> Self {
68        self.children.push(child);
69        self
70    }
71
72    /// Add multiple children
73    pub fn children(mut self, children: impl IntoIterator<Item = NewNode>) -> Self {
74        self.children.extend(children);
75        self
76    }
77
78    /// Set raw type-data bytes (use codec encode_* functions)
79    pub fn data_bytes(mut self, bytes: Vec<u8>) -> Self {
80        self.data_bytes = bytes;
81        self
82    }
83
84    /// Finalize into a NewNode
85    pub fn build(self) -> NewNode {
86        NewNode::Built(BuiltNode {
87            node_type: self.node_type,
88            children: self.children,
89            data_bytes: self.data_bytes,
90            position: None,
91        })
92    }
93}
94
95/// Convenience constructors
96impl NodeBuilder {
97    pub fn heading(depth: u8) -> Self {
98        use satteri_ast::mdast::codec::encode_heading_data;
99        Self::new(MdastNodeType::Heading).data_bytes(encode_heading_data(depth))
100    }
101
102    pub fn paragraph() -> Self {
103        Self::new(MdastNodeType::Paragraph)
104    }
105
106    pub fn text(value_offset: u32, value_len: u32) -> Self {
107        use satteri_arena::{StringRef, encode_string_ref_data};
108        let string_ref = StringRef {
109            offset: value_offset,
110            len: value_len,
111        };
112        Self::new(MdastNodeType::Text).data_bytes(encode_string_ref_data(string_ref))
113    }
114
115    /// Create a text node with a raw string (for when we don't have source offsets)
116    /// This uses NewNode::Raw internally
117    pub fn raw(markdown: impl Into<String>) -> NewNode {
118        NewNode::Raw(markdown.into())
119    }
120}