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::rebuild::{rebuild, Patch};
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 node_count = current_arena.len() as u32;
55 for node_id in 0..node_count {
56 let node = current_arena.get_node(node_id);
57 let node_type_byte = node.node_type;
58
59 let result = dispatch_visitor(
60 plugin.as_mut(),
61 node_type_byte,
62 node_id,
63 ¤t_arena,
64 &mut ctx,
65 );
66
67 match result {
68 VisitResult::Replace(new_node) => {
69 ctx.replace_node(node_id, new_node);
70 }
71 VisitResult::Remove => {
72 ctx.remove_node(node_id);
73 }
74 VisitResult::NoChange => {}
75 }
76 }
77
78 plugin.after(¤t_arena, &mut ctx);
80
81 let (commands, diagnostics) = ctx.take_commands();
82 let has_cmds = !commands.is_empty();
83 all_diagnostics.extend(diagnostics);
84
85 if has_cmds {
86 let patches = commands_to_patches(commands.iter().collect(), ¤t_arena);
88 if !patches.is_empty() {
89 match rebuild(¤t_arena, &patches) {
90 Ok(rebuilt) => current_arena = rebuilt,
91 Err(err) => {
92 all_diagnostics.push(Diagnostic {
95 message: format!("invalid patch combination: {err}"),
96 node_id: None,
97 severity: Severity::Error,
98 });
99 }
100 }
101 }
102 all_commands.extend(commands);
103 }
104 }
107
108 let has_mutations = !all_commands.is_empty();
109
110 PluginRunResult {
111 arena: current_arena,
112 commands: all_commands,
113 diagnostics: all_diagnostics,
114 has_mutations,
115 }
116 }
117}
118
119fn commands_to_patches(commands: Vec<&Command>, arena: &Arena<Mdast>) -> Vec<Patch<Mdast>> {
124 commands
125 .into_iter()
126 .filter_map(|cmd| match cmd {
127 Command::Replace { node_id, new_node } => {
128 built_node_to_arena(new_node, arena.string_pool()).map(|sub| Patch::Replace {
129 node_id: *node_id,
130 new_tree: sub,
131 keep_children: false,
132 })
133 }
134 Command::Remove { node_id } => Some(Patch::Remove { node_id: *node_id }),
135 Command::InsertBefore { node_id, new_node } => {
136 built_node_to_arena(new_node, arena.string_pool()).map(|sub| Patch::InsertBefore {
137 node_id: *node_id,
138 new_tree: sub,
139 })
140 }
141 Command::InsertAfter { node_id, new_node } => {
142 built_node_to_arena(new_node, arena.string_pool()).map(|sub| Patch::InsertAfter {
143 node_id: *node_id,
144 new_tree: sub,
145 })
146 }
147 Command::Wrap {
148 node_id,
149 parent_node,
150 } => built_node_to_arena(parent_node, arena.string_pool()).map(|sub| Patch::Wrap {
151 node_id: *node_id,
152 parent_tree: sub,
153 }),
154 Command::PrependChild {
155 node_id,
156 child_node,
157 } => built_node_to_arena(child_node, arena.string_pool()).map(|sub| {
158 Patch::PrependChild {
159 node_id: *node_id,
160 child_tree: sub,
161 }
162 }),
163 Command::AppendChild {
164 node_id,
165 child_node,
166 } => {
167 built_node_to_arena(child_node, arena.string_pool()).map(|sub| Patch::AppendChild {
168 node_id: *node_id,
169 child_tree: sub,
170 })
171 }
172 Command::SetData { .. } => {
173 None
175 }
176 })
177 .collect()
178}
179
180fn built_node_to_arena(new_node: &NewNode, string_pool: &str) -> Option<Arena<Mdast>> {
183 match new_node {
184 NewNode::Raw(_) => None, NewNode::Built(built) => {
186 let mut builder = ArenaBuilder::<Mdast>::new(string_pool.to_string());
187 emit_built_node(built, &mut builder);
188 Some(builder.finish())
189 }
190 }
191}
192
193fn emit_built_node(built: &BuiltNode, builder: &mut ArenaBuilder<Mdast>) {
195 builder.open_node(built.node_type as u8);
196 if !built.data_bytes.is_empty() {
197 builder.set_data_current(&built.data_bytes);
198 }
199 for child in &built.children {
200 match child {
201 NewNode::Built(child_built) => emit_built_node(child_built, builder),
202 NewNode::Raw(_) => {} }
204 }
205 builder.close_node();
206}
207
208fn dispatch_visitor(
211 plugin: &mut dyn Plugin,
212 node_type_byte: u8,
213 node_id: u32,
214 arena: &Arena<Mdast>,
215 ctx: &mut PluginContext,
216) -> VisitResult {
217 match MdastNodeType::from_u8(node_type_byte) {
218 Some(MdastNodeType::Heading) => plugin.visit_heading(&Heading { node_id, arena }, ctx),
219 Some(MdastNodeType::Paragraph) => {
220 plugin.visit_paragraph(&Paragraph { node_id, arena }, ctx)
221 }
222 Some(MdastNodeType::Text) => plugin.visit_text(&Text { node_id, arena }, ctx),
223 Some(MdastNodeType::Link) => plugin.visit_link(&Link { node_id, arena }, ctx),
224 Some(MdastNodeType::Image) => plugin.visit_image(&Image { node_id, arena }, ctx),
225 Some(MdastNodeType::Code) => plugin.visit_code(&Code { node_id, arena }, ctx),
226 Some(MdastNodeType::List) => plugin.visit_list(&NodeView { node_id, arena }, ctx),
227 Some(MdastNodeType::ListItem) => plugin.visit_list_item(&NodeView { node_id, arena }, ctx),
228 Some(MdastNodeType::Blockquote) => {
229 plugin.visit_blockquote(&NodeView { node_id, arena }, ctx)
230 }
231 Some(MdastNodeType::Emphasis) => plugin.visit_emphasis(&NodeView { node_id, arena }, ctx),
232 Some(MdastNodeType::Strong) => plugin.visit_strong(&NodeView { node_id, arena }, ctx),
233 Some(MdastNodeType::InlineCode) => plugin.visit_inline_code(&Text { node_id, arena }, ctx),
234 Some(MdastNodeType::Html) => plugin.visit_html(&Text { node_id, arena }, ctx),
235 Some(MdastNodeType::Table) => plugin.visit_table(&NodeView { node_id, arena }, ctx),
236 _ => VisitResult::NoChange,
237 }
238}