hex_patch/app/plugins/
app_context.rs

1use std::{
2    ops::Deref,
3    sync::{Arc, Mutex},
4};
5
6use mlua::{Function, Lua, Scope, Table};
7
8use crate::{
9    app::{
10        comments::Comments,
11        data::Data,
12        log::{logger::Logger, NotificationLevel},
13        pane::Pane,
14        popup::popup_state::PopupState,
15        settings::Settings,
16        App,
17    },
18    headers::Header,
19};
20
21use super::{
22    exported_commands::ExportedCommands, exported_header_parsers::ExportedHeaderParsers,
23    instruction_info::InstructionInfo, plugin_instant::PluginInstant,
24};
25
26#[macro_export]
27macro_rules! get_app_context {
28    ($app:ident) => {
29        $crate::app::plugins::app_context::AppContext::new(
30            $app.get_cursor_position().global_byte_index,
31            $app.get_current_instruction().map(|i| i.into()),
32            $app.screen_size.1,
33            $app.screen_size.0,
34            $app.blocks_per_row,
35            $app.block_size,
36            $app.vertical_margin,
37            &mut $app.scroll,
38            &mut $app.cursor,
39            &mut $app.data,
40            &$app.header,
41            &mut $app.settings,
42            &mut $app.logger,
43            &mut $app.popup,
44            &mut $app.fullscreen,
45            &mut $app.selected_pane,
46            &mut $app.comments,
47        )
48    };
49}
50
51pub struct AppContext<'app> {
52    pub exported_commands: Arc<Mutex<ExportedCommands>>,
53    pub exported_header_parsers: Arc<Mutex<ExportedHeaderParsers>>,
54    pub plugin_index: Option<usize>,
55
56    pub screen_height: u16,
57    pub screen_width: u16,
58    pub blocks_per_row: usize,
59    pub block_size: usize,
60    pub vertical_margin: u16,
61    pub data: Arc<Mutex<&'app mut Data>>,
62    pub scroll: &'app mut usize,
63    pub cursor: &'app mut (u16, u16),
64    pub offset: usize,
65    pub current_instruction: Option<InstructionInfo>,
66    pub header: &'app Header,
67    pub settings: &'app mut Settings,
68    pub logger: &'app mut Logger,
69    pub popup: Arc<Mutex<&'app mut Option<PopupState>>>,
70    pub fullscreen: Arc<Mutex<&'app mut bool>>,
71    pub selected_pane: Arc<Mutex<&'app mut Pane>>,
72    pub comments: Arc<Mutex<&'app mut Comments>>,
73}
74
75impl<'app> AppContext<'app> {
76    #[allow(clippy::too_many_arguments)]
77    pub fn new(
78        offset: usize,
79        current_instruction: Option<InstructionInfo>,
80        screen_height: u16,
81        screen_width: u16,
82        blocks_per_row: usize,
83        block_size: usize,
84        vertical_margin: u16,
85        scroll: &'app mut usize,
86        cursor: &'app mut (u16, u16),
87        data: &'app mut Data,
88        header: &'app Header,
89        settings: &'app mut Settings,
90        logger: &'app mut Logger,
91        popup: &'app mut Option<PopupState>,
92        fullscreen: &'app mut bool,
93        selected_pane: &'app mut Pane,
94        comments: &'app mut Comments,
95    ) -> Self {
96        Self {
97            exported_commands: Arc::new(Mutex::new(ExportedCommands::default())),
98            exported_header_parsers: Arc::new(Mutex::new(ExportedHeaderParsers::default())),
99            plugin_index: None,
100            screen_height,
101            screen_width,
102            blocks_per_row,
103            block_size,
104            vertical_margin,
105            data: Arc::new(Mutex::new(data)),
106            scroll,
107            cursor,
108            offset,
109            current_instruction,
110            header,
111            settings,
112            logger,
113            popup: Arc::new(Mutex::new(popup)),
114            fullscreen: Arc::new(Mutex::new(fullscreen)),
115            selected_pane: Arc::new(Mutex::new(selected_pane)),
116            comments: Arc::new(Mutex::new(comments)),
117        }
118    }
119
120    pub fn reset_exported_commands(&mut self) {
121        self.exported_commands = Arc::new(Mutex::new(ExportedCommands::default()));
122    }
123
124    pub fn reset_exported_header_parsers(&mut self) {
125        self.exported_header_parsers = Arc::new(Mutex::new(ExportedHeaderParsers::default()));
126    }
127
128    pub fn set_exported_commands(&mut self, exported_commands: ExportedCommands) {
129        self.exported_commands = Arc::new(Mutex::new(exported_commands));
130    }
131
132    pub fn set_exported_header_parsers(&mut self, exported_header_parsers: ExportedHeaderParsers) {
133        self.exported_header_parsers = Arc::new(Mutex::new(exported_header_parsers));
134    }
135
136    pub fn take_exported_commands(&mut self) -> ExportedCommands {
137        self.exported_commands.lock().unwrap().take()
138    }
139
140    pub fn take_exported_header_parsers(&mut self) -> ExportedHeaderParsers {
141        self.exported_header_parsers.lock().unwrap().take()
142    }
143
144    pub fn to_lua<'scope, 'env>(
145        &'env mut self,
146        lua: &'scope Lua,
147        scope: &'scope Scope<'scope, 'env>,
148    ) -> mlua::Table {
149        let context = lua.create_table().unwrap();
150        context
151            .set(
152                "log",
153                scope
154                    .create_function_mut(|_, (level, message): (u8, String)| {
155                        self.logger.log(NotificationLevel::from(level), &message);
156                        Ok(())
157                    })
158                    .unwrap(),
159            )
160            .unwrap();
161
162        let exported_commands = self.exported_commands.clone();
163        context
164            .set(
165                "add_command",
166                scope
167                    .create_function_mut(move |lua, (command, description): (String, String)| {
168                        if let Ok(_command_fn) = lua.globals().get::<Function>(command.clone()) {
169                            exported_commands
170                                .lock()
171                                .unwrap()
172                                .add_command(command, description);
173                            Ok(())
174                        } else {
175                            Err(mlua::Error::external(format!(
176                                "Function '{}' not found but needed to export the command",
177                                command
178                            )))
179                        }
180                    })
181                    .unwrap(),
182            )
183            .unwrap();
184
185        let exported_commands = self.exported_commands.clone();
186        context
187            .set(
188                "remove_command",
189                scope
190                    .create_function_mut(move |_, command: String| {
191                        if exported_commands.lock().unwrap().remove_command(&command) {
192                            Ok(())
193                        } else {
194                            Err(mlua::Error::external(format!(
195                                "Command '{}' not found",
196                                command
197                            )))
198                        }
199                    })
200                    .unwrap(),
201            )
202            .unwrap();
203
204        let exported_header_parsers = self.exported_header_parsers.clone();
205        context
206            .set(
207                "add_header_parser",
208                scope
209                    .create_function_mut(move |lua, callback: String| {
210                        if let Ok(_header_parser_fn) =
211                            lua.globals().get::<Function>(callback.clone())
212                        {
213                            exported_header_parsers
214                                .lock()
215                                .unwrap()
216                                .add_header_parser(callback);
217                            Ok(())
218                        } else {
219                            Err(mlua::Error::external(format!(
220                                "Function '{}' not found but needed to export the header parser",
221                                callback
222                            )))
223                        }
224                    })
225                    .unwrap(),
226            )
227            .unwrap();
228
229        let exported_header_parsers = self.exported_header_parsers.clone();
230        context
231            .set(
232                "remove_header_parser",
233                scope
234                    .create_function_mut(move |_, callback: String| {
235                        if exported_header_parsers
236                            .lock()
237                            .unwrap()
238                            .remove_header_parser(&callback)
239                        {
240                            Ok(())
241                        } else {
242                            Err(mlua::Error::external(format!(
243                                "Header parser '{}' not found",
244                                callback
245                            )))
246                        }
247                    })
248                    .unwrap(),
249            )
250            .unwrap();
251
252        context
253            .set(
254                "open_popup",
255                scope
256                    .create_function_mut(|_, callback: String| {
257                        let mut popup = self.popup.lock().unwrap();
258                        if popup.is_some() {
259                            Err(mlua::Error::external("Popup already open"))
260                        } else if lua.globals().get::<Function>(callback.clone()).is_err() {
261                            Err(mlua::Error::external(format!(
262                                "Function '{}' not found but needed to open the popup",
263                                callback
264                            )))
265                        } else {
266                            **popup = Some(PopupState::Custom {
267                                plugin_index: self.plugin_index.unwrap(),
268                                callback,
269                            });
270                            Ok(())
271                        }
272                    })
273                    .unwrap(),
274            )
275            .unwrap();
276
277        context
278            .set(
279                "get_popup",
280                scope
281                    .create_function(|_, ()| {
282                        let popup = self.popup.lock().unwrap();
283                        if let Some(PopupState::Custom {
284                            plugin_index,
285                            callback,
286                        }) = *popup as &Option<PopupState>
287                        {
288                            if self.plugin_index.unwrap() != *plugin_index {
289                                Ok(mlua::Value::Nil)
290                            } else {
291                                Ok(mlua::Value::String(
292                                    lua.create_string(callback.as_str()).unwrap(),
293                                ))
294                            }
295                        } else {
296                            Ok(mlua::Value::Nil)
297                        }
298                    })
299                    .unwrap(),
300            )
301            .unwrap();
302
303        context
304            .set(
305                "close_popup",
306                scope
307                    .create_function_mut(|_, expected_callback: Option<String>| {
308                        let mut popup = self.popup.lock().unwrap();
309                        if let Some(PopupState::Custom {
310                            plugin_index,
311                            callback,
312                        }) = *popup as &mut Option<PopupState>
313                        {
314                            if expected_callback.is_some()
315                                && expected_callback.as_ref() != Some(callback)
316                            {
317                                Err(mlua::Error::external(
318                                    "A popup is open but not the one expected.",
319                                ))
320                            } else if self.plugin_index.unwrap() != *plugin_index {
321                                Err(mlua::Error::external(
322                                    "A popup is open but not from this plugin.",
323                                ))
324                            } else {
325                                **popup = None;
326                                Ok(())
327                            }
328                        } else {
329                            Err(mlua::Error::external("No plugin related popup is open."))
330                        }
331                    })
332                    .unwrap(),
333            )
334            .unwrap();
335
336        context.set("screen_height", self.screen_height).unwrap();
337        context.set("screen_width", self.screen_width).unwrap();
338        let data = lua.create_table().unwrap();
339        data.set("len", self.data.lock().unwrap().len()).unwrap();
340        data.set(
341            "get",
342            scope
343                .create_function_mut(|_, (_this, index): (Table, usize)| {
344                    let data = self.data.lock().unwrap();
345                    match data.get(index) {
346                        Some(byte) => Ok(byte),
347                        None => Err(mlua::Error::external("Index out of bounds")),
348                    }
349                })
350                .unwrap(),
351        )
352        .unwrap();
353        data.set(
354            "set",
355            scope
356                .create_function_mut(|_, (_this, index, byte): (Table, usize, u8)| {
357                    let mut data = self.data.lock().unwrap();
358                    data.set(index, byte)
359                })
360                .unwrap(),
361        )
362        .unwrap();
363        context.set("data", data).unwrap();
364        context.set("offset", self.offset).unwrap();
365        context
366            .set("current_instruction", self.current_instruction.clone())
367            .unwrap();
368        context
369            .set("header", scope.create_userdata_ref(self.header).unwrap())
370            .unwrap();
371        context
372            .set(
373                "settings",
374                scope.create_any_userdata_ref_mut(self.settings).unwrap(),
375            )
376            .unwrap();
377        context
378            .set(
379                "get_instant_now",
380                scope
381                    .create_function(|_, ()| Ok(PluginInstant::now()))
382                    .unwrap(),
383            )
384            .unwrap();
385        context
386            .set(
387                "jump_to",
388                scope
389                    .create_function_mut(|_, file_address: usize| {
390                        App::jump_to_no_self(
391                            file_address,
392                            self.data.lock().unwrap().deref(),
393                            (self.screen_width, self.screen_height),
394                            self.vertical_margin,
395                            self.scroll,
396                            self.cursor,
397                            self.block_size,
398                            self.blocks_per_row,
399                        );
400                        Ok(())
401                    })
402                    .unwrap(),
403            )
404            .unwrap();
405        context
406            .set(
407                "get_fullscreen",
408                scope
409                    .create_function(|_, ()| {
410                        let fullscreen = self.fullscreen.lock().unwrap();
411                        Ok(**fullscreen)
412                    })
413                    .unwrap(),
414            )
415            .unwrap();
416        context
417            .set(
418                "set_fullscreen",
419                scope
420                    .create_function_mut(|_, fullscreen: bool| {
421                        **self.fullscreen.lock().unwrap() = fullscreen;
422                        Ok(())
423                    })
424                    .unwrap(),
425            )
426            .unwrap();
427        context
428            .set(
429                "get_selected_pane",
430                scope
431                    .create_function(|_, ()| {
432                        let selected_pane = self.selected_pane.lock().unwrap();
433                        Ok(**selected_pane)
434                    })
435                    .unwrap(),
436            )
437            .unwrap();
438        context
439            .set(
440                "set_selected_pane",
441                scope
442                    .create_function_mut(|_, selected_pane: Pane| {
443                        **self.selected_pane.lock().unwrap() = selected_pane;
444                        Ok(())
445                    })
446                    .unwrap(),
447            )
448            .unwrap();
449        context
450            .set(
451                "get_comments",
452                scope
453                    .create_function(|lua, ()| {
454                        let comments = self.comments.lock().unwrap();
455                        let table = lua.create_table().unwrap();
456                        for (address, comment) in comments.iter() {
457                            table.set(*address, comment.clone()).unwrap();
458                        }
459                        Ok(table)
460                    })
461                    .unwrap(),
462            )
463            .unwrap();
464        context
465            .set(
466                "get_comment",
467                scope
468                    .create_function(|_, address: u64| {
469                        let comments = self.comments.lock().unwrap();
470                        Ok(comments.get(&address).cloned())
471                    })
472                    .unwrap(),
473            )
474            .unwrap();
475        context
476            .set(
477                "set_comment",
478                scope
479                    .create_function_mut(|_, (address, comment): (u64, Option<String>)| {
480                        let mut comments = self.comments.lock().unwrap();
481                        if let Some(comment) = comment {
482                            if comment.is_empty() {
483                                comments.remove(&address);
484                            } else {
485                                comments.insert(address, comment);
486                            }
487                        } else {
488                            comments.remove(&address);
489                        }
490                        Ok(())
491                    })
492                    .unwrap(),
493            )
494            .unwrap();
495
496        context
497    }
498}