1use crate::commands::{BuiltNode, Command, NewNode};
2use crate::context::{Diagnostic, PluginContext, Severity};
3use crate::data::{DataMap, TypedDataMap};
4use crate::plugin::{NodeView, Plugin, VisitResult};
5use crate::typed_nodes::*;
6use satteri_arena::{Arena, ArenaBuilder, Mdast};
7use satteri_ast::mdast::MdastNodeType;
8use satteri_ast::patch::{Patch, PatchContent, apply_patches_strict};
9
10pub struct PluginRunResult {
12 pub arena: Arena<Mdast>,
14 pub commands: Vec<Command>,
15 pub diagnostics: Vec<Diagnostic>,
16 pub has_mutations: bool,
17}
18
19pub struct PluginRunner {
21 plugins: Vec<Box<dyn Plugin>>,
22}
23
24impl PluginRunner {
25 pub fn new(plugins: Vec<Box<dyn Plugin>>) -> Self {
26 Self { plugins }
27 }
28
29 pub fn init(&mut self) {
31 for plugin in &mut self.plugins {
32 plugin.init();
33 }
34 }
35
36 pub fn run(
38 &mut self,
39 arena: Arena<Mdast>,
40 data_map: &mut DataMap,
41 typed_data: &mut TypedDataMap,
42 ) -> PluginRunResult {
43 let mut all_commands: Vec<Command> = Vec::new();
44 let mut all_diagnostics: Vec<Diagnostic> = Vec::new();
45 let mut current_arena = arena;
46
47 for plugin in &mut self.plugins {
48 let mut ctx = PluginContext::new(¤t_arena, data_map, typed_data);
49
50 plugin.before(¤t_arena, &mut ctx);
52
53 let mut stack: Vec<u32> = if current_arena.is_empty() {
55 Vec::new()
56 } else {
57 vec![0]
58 };
59 while let Some(node_id) = stack.pop() {
60 let node = current_arena.get_node(node_id);
61 let node_type_byte = node.node_type;
62
63 let result = dispatch_visitor(
64 plugin.as_mut(),
65 node_type_byte,
66 node_id,
67 ¤t_arena,
68 &mut ctx,
69 );
70
71 match result {
72 VisitResult::Replace(new_node) => {
73 ctx.replace_node(node_id, new_node);
74 }
75 VisitResult::Remove => {
76 ctx.remove_node(node_id);
77 }
78 VisitResult::NoChange => {}
79 }
80
81 for &child_id in current_arena.get_children(node_id).iter().rev() {
82 stack.push(child_id);
83 }
84 }
85
86 plugin.after(¤t_arena, &mut ctx);
88
89 let (commands, diagnostics) = ctx.take_commands();
90 let has_cmds = !commands.is_empty();
91 all_diagnostics.extend(diagnostics);
92
93 if has_cmds {
94 let patches = commands_to_patches(commands.iter().collect(), ¤t_arena);
95 if !patches.is_empty()
96 && let Err(err) = apply_patches_strict(&mut current_arena, &patches)
97 {
98 all_diagnostics.push(Diagnostic {
99 message: format!("invalid patch combination: {err}"),
100 node_id: None,
101 severity: Severity::Error,
102 });
103 }
104 all_commands.extend(commands);
105 }
106 }
109
110 let has_mutations = !all_commands.is_empty();
111
112 PluginRunResult {
113 arena: current_arena,
114 commands: all_commands,
115 diagnostics: all_diagnostics,
116 has_mutations,
117 }
118 }
119}
120
121fn commands_to_patches(commands: Vec<&Command>, arena: &Arena<Mdast>) -> Vec<Patch<Mdast>> {
126 commands
127 .into_iter()
128 .filter_map(|cmd| match cmd {
129 Command::Replace { node_id, new_node } => {
130 built_node_to_arena(new_node, arena.string_pool()).map(|sub| Patch::Replace {
131 node_id: *node_id,
132 new_tree: PatchContent::Tree(sub),
133 keep_children: false,
134 })
135 }
136 Command::Remove { node_id } => Some(Patch::Remove { node_id: *node_id }),
137 Command::InsertBefore { node_id, new_node } => {
138 built_node_to_arena(new_node, arena.string_pool()).map(|sub| Patch::InsertBefore {
139 node_id: *node_id,
140 new_tree: PatchContent::Tree(sub),
141 })
142 }
143 Command::InsertAfter { node_id, new_node } => {
144 built_node_to_arena(new_node, arena.string_pool()).map(|sub| Patch::InsertAfter {
145 node_id: *node_id,
146 new_tree: PatchContent::Tree(sub),
147 })
148 }
149 Command::Wrap {
150 node_id,
151 parent_node,
152 } => built_node_to_arena(parent_node, arena.string_pool()).map(|sub| Patch::Wrap {
153 node_id: *node_id,
154 parent_tree: PatchContent::Tree(sub),
155 }),
156 Command::PrependChild {
157 node_id,
158 child_node,
159 } => built_node_to_arena(child_node, arena.string_pool()).map(|sub| {
160 Patch::PrependChild {
161 node_id: *node_id,
162 child_tree: PatchContent::Tree(sub),
163 }
164 }),
165 Command::AppendChild {
166 node_id,
167 child_node,
168 } => {
169 built_node_to_arena(child_node, arena.string_pool()).map(|sub| Patch::AppendChild {
170 node_id: *node_id,
171 child_tree: PatchContent::Tree(sub),
172 })
173 }
174 Command::SetData { .. } => {
175 None
177 }
178 })
179 .collect()
180}
181
182fn built_node_to_arena(new_node: &NewNode, string_pool: &str) -> Option<Arena<Mdast>> {
185 match new_node {
186 NewNode::Raw(_) => None, NewNode::Built(built) => {
188 let mut builder = ArenaBuilder::<Mdast>::new(string_pool.to_string());
189 emit_built_node(built, &mut builder);
190 Some(builder.finish())
191 }
192 }
193}
194
195fn emit_built_node(built: &BuiltNode, builder: &mut ArenaBuilder<Mdast>) {
197 builder.open_node(built.node_type as u8);
198 if !built.data_bytes.is_empty() {
199 builder.set_data_current(&built.data_bytes);
200 }
201 for child in &built.children {
202 match child {
203 NewNode::Built(child_built) => emit_built_node(child_built, builder),
204 NewNode::Raw(_) => {} }
206 }
207 builder.close_node();
208}
209
210fn dispatch_visitor(
213 plugin: &mut dyn Plugin,
214 node_type_byte: u8,
215 node_id: u32,
216 arena: &Arena<Mdast>,
217 ctx: &mut PluginContext,
218) -> VisitResult {
219 match MdastNodeType::from_u8(node_type_byte) {
220 Some(MdastNodeType::Heading) => plugin.visit_heading(&Heading { node_id, arena }, ctx),
221 Some(MdastNodeType::Paragraph) => {
222 plugin.visit_paragraph(&Paragraph { node_id, arena }, ctx)
223 }
224 Some(MdastNodeType::Text) => plugin.visit_text(&Text { node_id, arena }, ctx),
225 Some(MdastNodeType::Link) => plugin.visit_link(&Link { node_id, arena }, ctx),
226 Some(MdastNodeType::Image) => plugin.visit_image(&Image { node_id, arena }, ctx),
227 Some(MdastNodeType::Code) => plugin.visit_code(&Code { node_id, arena }, ctx),
228 Some(MdastNodeType::List) => plugin.visit_list(&NodeView { node_id, arena }, ctx),
229 Some(MdastNodeType::ListItem) => plugin.visit_list_item(&NodeView { node_id, arena }, ctx),
230 Some(MdastNodeType::Blockquote) => {
231 plugin.visit_blockquote(&NodeView { node_id, arena }, ctx)
232 }
233 Some(MdastNodeType::Emphasis) => plugin.visit_emphasis(&NodeView { node_id, arena }, ctx),
234 Some(MdastNodeType::Strong) => plugin.visit_strong(&NodeView { node_id, arena }, ctx),
235 Some(MdastNodeType::InlineCode) => plugin.visit_inline_code(&Text { node_id, arena }, ctx),
236 Some(MdastNodeType::Html) => plugin.visit_html(&Text { node_id, arena }, ctx),
237 Some(MdastNodeType::Table) => plugin.visit_table(&NodeView { node_id, arena }, ctx),
238 _ => VisitResult::NoChange,
239 }
240}