use crate::commands::{BuiltNode, Command, NewNode};
use crate::context::{Diagnostic, PluginContext};
use crate::data::{DataMap, TypedDataMap};
use crate::plugin::{NodeView, Plugin, VisitResult};
use crate::typed_nodes::*;
use satteri_arena::{Arena, ArenaBuilder};
use satteri_ast::mdast::MdastNodeType;
use satteri_ast::rebuild::{rebuild, Patch};
pub struct PluginRunResult {
pub arena: Arena,
pub commands: Vec<Command>,
pub diagnostics: Vec<Diagnostic>,
pub has_mutations: bool,
}
pub struct PluginRunner {
plugins: Vec<Box<dyn Plugin>>,
}
impl PluginRunner {
pub fn new(plugins: Vec<Box<dyn Plugin>>) -> Self {
Self { plugins }
}
pub fn init(&mut self) {
for plugin in &mut self.plugins {
plugin.init();
}
}
pub fn run(
&mut self,
arena: Arena,
data_map: &mut DataMap,
typed_data: &mut TypedDataMap,
) -> PluginRunResult {
let mut all_commands: Vec<Command> = Vec::new();
let mut all_diagnostics: Vec<Diagnostic> = Vec::new();
let mut current_arena = arena;
for plugin in &mut self.plugins {
let mut ctx = PluginContext::new(¤t_arena, data_map, typed_data);
plugin.before(¤t_arena, &mut ctx);
let node_count = current_arena.len() as u32;
for node_id in 0..node_count {
let node = current_arena.get_node(node_id);
let node_type_byte = node.node_type;
let result = dispatch_visitor(
plugin.as_mut(),
node_type_byte,
node_id,
¤t_arena,
&mut ctx,
);
match result {
VisitResult::Replace(new_node) => {
ctx.replace_node(node_id, new_node);
}
VisitResult::Remove => {
ctx.remove_node(node_id);
}
VisitResult::NoChange => {}
}
}
plugin.after(¤t_arena, &mut ctx);
let (commands, diagnostics) = ctx.take_commands();
let has_cmds = !commands.is_empty();
all_diagnostics.extend(diagnostics);
if has_cmds {
let patches = commands_to_patches(commands.iter().collect(), ¤t_arena);
if !patches.is_empty() {
current_arena = rebuild(¤t_arena, &patches);
}
all_commands.extend(commands);
}
}
let has_mutations = !all_commands.is_empty();
PluginRunResult {
arena: current_arena,
commands: all_commands,
diagnostics: all_diagnostics,
has_mutations,
}
}
}
fn commands_to_patches(commands: Vec<&Command>, arena: &Arena) -> Vec<Patch> {
commands
.into_iter()
.filter_map(|cmd| match cmd {
Command::Replace { node_id, new_node } => built_node_to_arena(new_node, arena.source())
.map(|sub| Patch::Replace {
node_id: *node_id,
new_tree: sub,
keep_children: false,
}),
Command::Remove { node_id } => Some(Patch::Remove { node_id: *node_id }),
Command::InsertBefore { node_id, new_node } => {
built_node_to_arena(new_node, arena.source()).map(|sub| Patch::InsertBefore {
node_id: *node_id,
new_tree: sub,
})
}
Command::InsertAfter { node_id, new_node } => {
built_node_to_arena(new_node, arena.source()).map(|sub| Patch::InsertAfter {
node_id: *node_id,
new_tree: sub,
})
}
Command::Wrap {
node_id,
parent_node,
} => built_node_to_arena(parent_node, arena.source()).map(|sub| Patch::Wrap {
node_id: *node_id,
parent_tree: sub,
}),
Command::PrependChild {
node_id,
child_node,
} => built_node_to_arena(child_node, arena.source()).map(|sub| Patch::PrependChild {
node_id: *node_id,
child_tree: sub,
}),
Command::AppendChild {
node_id,
child_node,
} => built_node_to_arena(child_node, arena.source()).map(|sub| Patch::AppendChild {
node_id: *node_id,
child_tree: sub,
}),
Command::SetData { .. } => {
None
}
})
.collect()
}
fn built_node_to_arena(new_node: &NewNode, source: &str) -> Option<Arena> {
match new_node {
NewNode::Raw(_) => None, NewNode::Built(built) => {
let mut builder = ArenaBuilder::new(source.to_string());
emit_built_node(built, &mut builder);
Some(builder.finish())
}
}
}
fn emit_built_node(built: &BuiltNode, builder: &mut ArenaBuilder) {
builder.open_node(built.node_type as u8);
if !built.data_bytes.is_empty() {
builder.set_data_current(&built.data_bytes);
}
for child in &built.children {
match child {
NewNode::Built(child_built) => emit_built_node(child_built, builder),
NewNode::Raw(_) => {} }
}
builder.close_node();
}
fn dispatch_visitor(
plugin: &mut dyn Plugin,
node_type_byte: u8,
node_id: u32,
arena: &Arena,
ctx: &mut PluginContext,
) -> VisitResult {
match MdastNodeType::from_u8(node_type_byte) {
Some(MdastNodeType::Heading) => plugin.visit_heading(&Heading { node_id, arena }, ctx),
Some(MdastNodeType::Paragraph) => {
plugin.visit_paragraph(&Paragraph { node_id, arena }, ctx)
}
Some(MdastNodeType::Text) => plugin.visit_text(&Text { node_id, arena }, ctx),
Some(MdastNodeType::Link) => plugin.visit_link(&Link { node_id, arena }, ctx),
Some(MdastNodeType::Image) => plugin.visit_image(&Image { node_id, arena }, ctx),
Some(MdastNodeType::Code) => plugin.visit_code(&Code { node_id, arena }, ctx),
Some(MdastNodeType::List) => plugin.visit_list(&NodeView { node_id, arena }, ctx),
Some(MdastNodeType::ListItem) => plugin.visit_list_item(&NodeView { node_id, arena }, ctx),
Some(MdastNodeType::Blockquote) => {
plugin.visit_blockquote(&NodeView { node_id, arena }, ctx)
}
Some(MdastNodeType::Emphasis) => plugin.visit_emphasis(&NodeView { node_id, arena }, ctx),
Some(MdastNodeType::Strong) => plugin.visit_strong(&NodeView { node_id, arena }, ctx),
Some(MdastNodeType::InlineCode) => plugin.visit_inline_code(&Text { node_id, arena }, ctx),
Some(MdastNodeType::Html) => plugin.visit_html(&Text { node_id, arena }, ctx),
Some(MdastNodeType::Table) => plugin.visit_table(&NodeView { node_id, arena }, ctx),
_ => VisitResult::NoChange,
}
}