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 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}