hex_patch/app/plugins/
plugin_manager.rs

1use std::path::{Path, PathBuf};
2
3use crossterm::event::{KeyEvent, MouseEvent};
4
5use crate::{
6    app::{commands::command_info::CommandInfo, log::NotificationLevel},
7    headers::custom_header::CustomHeader,
8};
9
10use super::{
11    app_context::AppContext,
12    event::{Event, Events},
13    plugin::Plugin,
14    popup_context::PopupContext,
15    ui_location::ui_location::UiLocation,
16};
17
18#[derive(Default, Debug)]
19pub struct PluginManager {
20    plugins: Vec<Plugin>,
21    on_open: Vec<usize>,
22    on_save: Vec<usize>,
23    on_edit: Vec<usize>,
24    on_key: Vec<usize>,
25    on_mouse: Vec<usize>,
26}
27
28impl PluginManager {
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    pub fn load(path: Option<&Path>, app_context: &mut AppContext) -> std::io::Result<Self> {
34        let mut plugin_manager = Self {
35            plugins: Self::load_plugins(path, app_context)?,
36            ..Default::default()
37        };
38
39        for (i, plugin) in plugin_manager.plugins.iter().enumerate() {
40            let handlers = plugin.get_event_handlers();
41            if handlers.contains(Events::ON_OPEN) {
42                plugin_manager.on_open.push(i);
43            }
44            if handlers.contains(Events::ON_SAVE) {
45                plugin_manager.on_save.push(i);
46            }
47            if handlers.contains(Events::ON_EDIT) {
48                plugin_manager.on_edit.push(i);
49            }
50            if handlers.contains(Events::ON_KEY) {
51                plugin_manager.on_key.push(i);
52            }
53            if handlers.contains(Events::ON_MOUSE) {
54                plugin_manager.on_mouse.push(i);
55            }
56        }
57        Ok(plugin_manager)
58    }
59
60    fn get_default_plugin_path() -> Option<PathBuf> {
61        let config = dirs::config_dir()?;
62        Some(config.join("HexPatch").join("plugins"))
63    }
64
65    fn load_plugins(
66        path: Option<&Path>,
67        app_context: &mut AppContext,
68    ) -> std::io::Result<Vec<Plugin>> {
69        let mut plugins = Vec::new();
70        let path = match path {
71            Some(path) => path.to_path_buf(),
72            None => Self::get_default_plugin_path().ok_or(std::io::Error::new(
73                std::io::ErrorKind::Other,
74                "Could not get default plugin path",
75            ))?,
76        };
77        std::fs::create_dir_all(&path)?;
78        for entry in std::fs::read_dir(path)? {
79            let entry = entry?;
80            let path = entry.path();
81            if path.is_file() && path.extension().unwrap_or_default() == "lua" {
82                match Plugin::new_from_file(&path.to_string_lossy(), app_context) {
83                    Ok(plugin) => {
84                        plugins.push(plugin);
85                    }
86                    Err(e) => app_context.logger.log(
87                        NotificationLevel::Error,
88                        &format!(
89                            "Could not load plugin \"{}\": {}",
90                            path.to_string_lossy(),
91                            e
92                        ),
93                    ),
94                }
95            }
96        }
97        Ok(plugins)
98    }
99
100    pub fn on_open(&mut self, app_context: &mut AppContext) {
101        for i in self.on_open.iter() {
102            app_context.plugin_index = Some(*i);
103            let event = Event::Open;
104            self.plugins[*i].handle(event, app_context);
105        }
106    }
107
108    pub fn on_save(&mut self, app_context: &mut AppContext) {
109        for i in self.on_save.iter() {
110            app_context.plugin_index = Some(*i);
111            let event = Event::Save;
112            self.plugins[*i].handle(event, app_context);
113        }
114    }
115
116    pub fn on_edit(&mut self, new_bytes: &mut Vec<u8>, app_context: &mut AppContext) {
117        for i in self.on_edit.iter() {
118            app_context.plugin_index = Some(*i);
119            let event = Event::Edit { new_bytes };
120            self.plugins[*i].handle(event, app_context);
121        }
122    }
123
124    pub fn on_key(&mut self, event: KeyEvent, app_context: &mut AppContext) {
125        for i in self.on_key.iter() {
126            app_context.plugin_index = Some(*i);
127            let event = Event::Key { event };
128            self.plugins[*i].handle(event, app_context);
129        }
130    }
131
132    pub fn on_mouse(
133        &mut self,
134        event: MouseEvent,
135        location: Option<UiLocation>,
136        app_context: &mut AppContext,
137    ) {
138        for i in self.on_mouse.iter() {
139            app_context.plugin_index = Some(*i);
140            let event = Event::Mouse {
141                event,
142                location: location.clone(),
143            };
144            self.plugins[*i].handle(event, app_context);
145        }
146    }
147
148    pub fn on_focus(&mut self, app_context: &mut AppContext) {
149        for i in self.on_open.iter() {
150            app_context.plugin_index = Some(*i);
151            let event = Event::Focus;
152            self.plugins[*i].handle(event, app_context);
153        }
154    }
155
156    pub fn on_blur(&mut self, app_context: &mut AppContext) {
157        for i in self.on_open.iter() {
158            app_context.plugin_index = Some(*i);
159            let event = Event::Blur;
160            self.plugins[*i].handle(event, app_context);
161        }
162    }
163
164    pub fn on_paste(&mut self, text: impl AsRef<str>, app_context: &mut AppContext) {
165        for i in self.on_open.iter() {
166            app_context.plugin_index = Some(*i);
167            let event = Event::Paste {
168                text: text.as_ref().to_string(),
169            };
170            self.plugins[*i].handle(event, app_context);
171        }
172    }
173
174    pub fn on_resize(&mut self, width: u16, height: u16, app_context: &mut AppContext) {
175        for i in self.on_open.iter() {
176            app_context.plugin_index = Some(*i);
177            let event = Event::Resize { width, height };
178            self.plugins[*i].handle(event, app_context);
179        }
180    }
181
182    pub fn get_commands(&self) -> Vec<&CommandInfo> {
183        let mut commands = Vec::new();
184        let command_count = self.plugins.iter().map(|p| p.get_commands().len()).sum();
185        commands.reserve(command_count);
186        for plugin in self.plugins.iter() {
187            commands.extend(plugin.get_commands());
188        }
189        commands
190    }
191
192    pub fn run_command(&mut self, command: &str, app_context: &mut AppContext) -> mlua::Result<()> {
193        let mut found = false;
194        for (i, plugin) in self.plugins.iter_mut().enumerate() {
195            if let Some(_command_info) = plugin.get_commands().iter().find(|c| c.command == command)
196            {
197                app_context.plugin_index = Some(i);
198                plugin.run_command(command, app_context)?;
199                found = true;
200                break;
201            }
202        }
203        if !found {
204            Err(mlua::Error::external(format!(
205                "Command \"{}\" not found",
206                command
207            )))
208        } else {
209            Ok(())
210        }
211    }
212
213    pub fn fill_popup(
214        &mut self,
215        plugin_index: usize,
216        callback: impl AsRef<str>,
217        popup_context: PopupContext,
218        app_context: AppContext,
219    ) -> mlua::Result<()> {
220        self.plugins[plugin_index].fill_popup(callback, popup_context, app_context)
221    }
222
223    pub fn try_parse_header(&mut self, app_context: &mut AppContext) -> Option<CustomHeader> {
224        for (i, plugin) in self.plugins.iter_mut().enumerate() {
225            app_context.plugin_index = Some(i);
226            if let Some(header) = plugin.try_parse_header(app_context) {
227                return Some(header);
228            }
229        }
230        None
231    }
232}
233
234#[cfg(test)]
235mod test {
236    use crate::{
237        app::{log::NotificationLevel, App},
238        get_app_context,
239    };
240
241    use super::*;
242
243    #[test]
244    fn test_load_plugins() {
245        let mut app = App::mockup(vec![0; 0x100]);
246        app.logger.clear();
247        let mut app_context = get_app_context!(app);
248        let test_plugins_path = Path::new("test").join("plugins");
249        app.plugin_manager =
250            PluginManager::load(Some(&test_plugins_path), &mut app_context).unwrap();
251        assert_eq!(app.plugin_manager.plugins.len(), 2);
252
253        app.plugin_manager
254            .run_command("p1c1", &mut app_context)
255            .unwrap();
256        app.plugin_manager
257            .run_command("p1c2", &mut app_context)
258            .unwrap();
259        app.plugin_manager
260            .run_command("p2c1", &mut app_context)
261            .unwrap();
262        app.plugin_manager
263            .run_command("p2c2", &mut app_context)
264            .unwrap();
265
266        app.plugin_manager.on_open(&mut app_context);
267        // If there was an error, the logger will have a message
268        assert_ne!(
269            app_context.logger.get_notification_level(),
270            NotificationLevel::Error
271        );
272
273        let messages: Vec<_> = app_context.logger.iter().collect();
274        assert_eq!(messages.len(), 5, "{:?}", messages);
275        assert_eq!(messages[0].message, "Plugin 1 Command 1 called");
276        assert_eq!(messages[1].message, "Plugin 1 Command 2 called");
277        assert_eq!(messages[2].message, "Plugin 2 Command 1 called");
278        assert_eq!(messages[3].message, "Plugin 2 Command 2 called");
279        assert_eq!(messages[4].message, "Plugin 1 on_open called");
280    }
281}