1use crate::commands::{BuiltNode, Command, NewNode};
2use crate::context::{Diagnostic, PluginContext};
3use crate::data::{DataMap, TypedDataMap};
4use crate::plugin::{NodeView, Plugin, VisitResult};
5use crate::typed_nodes::*;
6use satteri_arena::{Arena, ArenaBuilder};
7use satteri_ast::mdast::MdastNodeType;
8use satteri_ast::rebuild::{rebuild, Patch};
9
10pub struct PluginRunResult {
12 pub arena: Arena,
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,
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 current_arena = rebuild(¤t_arena, &patches);
90 }
91 all_commands.extend(commands);
92 }
93 }
96
97 let has_mutations = !all_commands.is_empty();
98
99 PluginRunResult {
100 arena: current_arena,
101 commands: all_commands,
102 diagnostics: all_diagnostics,
103 has_mutations,
104 }
105 }
106}
107
108fn commands_to_patches(commands: Vec<&Command>, arena: &Arena) -> Vec<Patch> {
113 commands
114 .into_iter()
115 .filter_map(|cmd| match cmd {
116 Command::Replace { node_id, new_node } => built_node_to_arena(new_node, arena.source())
117 .map(|sub| Patch::Replace {
118 node_id: *node_id,
119 new_tree: sub,
120 keep_children: false,
121 }),
122 Command::Remove { node_id } => Some(Patch::Remove { node_id: *node_id }),
123 Command::InsertBefore { node_id, new_node } => {
124 built_node_to_arena(new_node, arena.source()).map(|sub| Patch::InsertBefore {
125 node_id: *node_id,
126 new_tree: sub,
127 })
128 }
129 Command::InsertAfter { node_id, new_node } => {
130 built_node_to_arena(new_node, arena.source()).map(|sub| Patch::InsertAfter {
131 node_id: *node_id,
132 new_tree: sub,
133 })
134 }
135 Command::Wrap {
136 node_id,
137 parent_node,
138 } => built_node_to_arena(parent_node, arena.source()).map(|sub| Patch::Wrap {
139 node_id: *node_id,
140 parent_tree: sub,
141 }),
142 Command::PrependChild {
143 node_id,
144 child_node,
145 } => built_node_to_arena(child_node, arena.source()).map(|sub| Patch::PrependChild {
146 node_id: *node_id,
147 child_tree: sub,
148 }),
149 Command::AppendChild {
150 node_id,
151 child_node,
152 } => built_node_to_arena(child_node, arena.source()).map(|sub| Patch::AppendChild {
153 node_id: *node_id,
154 child_tree: sub,
155 }),
156 Command::SetData { .. } => {
157 None
159 }
160 })
161 .collect()
162}
163
164fn built_node_to_arena(new_node: &NewNode, source: &str) -> Option<Arena> {
167 match new_node {
168 NewNode::Raw(_) => None, NewNode::Built(built) => {
170 let mut builder = ArenaBuilder::new(source.to_string());
171 emit_built_node(built, &mut builder);
172 Some(builder.finish())
173 }
174 }
175}
176
177fn emit_built_node(built: &BuiltNode, builder: &mut ArenaBuilder) {
179 builder.open_node(built.node_type as u8);
180 if !built.data_bytes.is_empty() {
181 builder.set_data_current(&built.data_bytes);
182 }
183 for child in &built.children {
184 match child {
185 NewNode::Built(child_built) => emit_built_node(child_built, builder),
186 NewNode::Raw(_) => {} }
188 }
189 builder.close_node();
190}
191
192fn dispatch_visitor(
195 plugin: &mut dyn Plugin,
196 node_type_byte: u8,
197 node_id: u32,
198 arena: &Arena,
199 ctx: &mut PluginContext,
200) -> VisitResult {
201 match MdastNodeType::from_u8(node_type_byte) {
202 Some(MdastNodeType::Heading) => plugin.visit_heading(&Heading { node_id, arena }, ctx),
203 Some(MdastNodeType::Paragraph) => {
204 plugin.visit_paragraph(&Paragraph { node_id, arena }, ctx)
205 }
206 Some(MdastNodeType::Text) => plugin.visit_text(&Text { node_id, arena }, ctx),
207 Some(MdastNodeType::Link) => plugin.visit_link(&Link { node_id, arena }, ctx),
208 Some(MdastNodeType::Image) => plugin.visit_image(&Image { node_id, arena }, ctx),
209 Some(MdastNodeType::Code) => plugin.visit_code(&Code { node_id, arena }, ctx),
210 Some(MdastNodeType::List) => plugin.visit_list(&NodeView { node_id, arena }, ctx),
211 Some(MdastNodeType::ListItem) => plugin.visit_list_item(&NodeView { node_id, arena }, ctx),
212 Some(MdastNodeType::Blockquote) => {
213 plugin.visit_blockquote(&NodeView { node_id, arena }, ctx)
214 }
215 Some(MdastNodeType::Emphasis) => plugin.visit_emphasis(&NodeView { node_id, arena }, ctx),
216 Some(MdastNodeType::Strong) => plugin.visit_strong(&NodeView { node_id, arena }, ctx),
217 Some(MdastNodeType::InlineCode) => plugin.visit_inline_code(&Text { node_id, arena }, ctx),
218 Some(MdastNodeType::Html) => plugin.visit_html(&Text { node_id, arena }, ctx),
219 Some(MdastNodeType::Table) => plugin.visit_table(&NodeView { node_id, arena }, ctx),
220 _ => VisitResult::NoChange,
221 }
222}