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 '{command}' not found but needed to export the command"
177                            )))
178                        }
179                    })
180                    .unwrap(),
181            )
182            .unwrap();
183
184        let exported_commands = self.exported_commands.clone();
185        context
186            .set(
187                "remove_command",
188                scope
189                    .create_function_mut(move |_, command: String| {
190                        if exported_commands.lock().unwrap().remove_command(&command) {
191                            Ok(())
192                        } else {
193                            Err(mlua::Error::external(format!(
194                                "Command '{command}' not found"
195                            )))
196                        }
197                    })
198                    .unwrap(),
199            )
200            .unwrap();
201
202        let exported_header_parsers = self.exported_header_parsers.clone();
203        context
204            .set(
205                "add_header_parser",
206                scope
207                    .create_function_mut(move |lua, callback: String| {
208                        if let Ok(_header_parser_fn) =
209                            lua.globals().get::<Function>(callback.clone())
210                        {
211                            exported_header_parsers
212                                .lock()
213                                .unwrap()
214                                .add_header_parser(callback);
215                            Ok(())
216                        } else {
217                            Err(mlua::Error::external(format!(
218                                "Function '{callback}' not found but needed to export the header parser"
219                            )))
220                        }
221                    })
222                    .unwrap(),
223            )
224            .unwrap();
225
226        let exported_header_parsers = self.exported_header_parsers.clone();
227        context
228            .set(
229                "remove_header_parser",
230                scope
231                    .create_function_mut(move |_, callback: String| {
232                        if exported_header_parsers
233                            .lock()
234                            .unwrap()
235                            .remove_header_parser(&callback)
236                        {
237                            Ok(())
238                        } else {
239                            Err(mlua::Error::external(format!(
240                                "Header parser '{callback}' not found"
241                            )))
242                        }
243                    })
244                    .unwrap(),
245            )
246            .unwrap();
247
248        context
249            .set(
250                "open_popup",
251                scope
252                    .create_function_mut(|_, callback: String| {
253                        let mut popup = self.popup.lock().unwrap();
254                        if popup.is_some() {
255                            Err(mlua::Error::external("Popup already open"))
256                        } else if lua.globals().get::<Function>(callback.clone()).is_err() {
257                            Err(mlua::Error::external(format!(
258                                "Function '{callback}' not found but needed to open the popup"
259                            )))
260                        } else {
261                            **popup = Some(PopupState::Custom {
262                                plugin_index: self.plugin_index.unwrap(),
263                                callback,
264                            });
265                            Ok(())
266                        }
267                    })
268                    .unwrap(),
269            )
270            .unwrap();
271
272        context
273            .set(
274                "get_popup",
275                scope
276                    .create_function(|_, ()| {
277                        let popup = self.popup.lock().unwrap();
278                        if let Some(PopupState::Custom {
279                            plugin_index,
280                            callback,
281                        }) = *popup as &Option<PopupState>
282                        {
283                            if self.plugin_index.unwrap() != *plugin_index {
284                                Ok(mlua::Value::Nil)
285                            } else {
286                                Ok(mlua::Value::String(
287                                    lua.create_string(callback.as_str()).unwrap(),
288                                ))
289                            }
290                        } else {
291                            Ok(mlua::Value::Nil)
292                        }
293                    })
294                    .unwrap(),
295            )
296            .unwrap();
297
298        context
299            .set(
300                "close_popup",
301                scope
302                    .create_function_mut(|_, expected_callback: Option<String>| {
303                        let mut popup = self.popup.lock().unwrap();
304                        if let Some(PopupState::Custom {
305                            plugin_index,
306                            callback,
307                        }) = *popup as &mut Option<PopupState>
308                        {
309                            if expected_callback.is_some()
310                                && expected_callback.as_ref() != Some(callback)
311                            {
312                                Err(mlua::Error::external(
313                                    "A popup is open but not the one expected.",
314                                ))
315                            } else if self.plugin_index.unwrap() != *plugin_index {
316                                Err(mlua::Error::external(
317                                    "A popup is open but not from this plugin.",
318                                ))
319                            } else {
320                                **popup = None;
321                                Ok(())
322                            }
323                        } else {
324                            Err(mlua::Error::external("No plugin related popup is open."))
325                        }
326                    })
327                    .unwrap(),
328            )
329            .unwrap();
330
331        context.set("screen_height", self.screen_height).unwrap();
332        context.set("screen_width", self.screen_width).unwrap();
333        let data = lua.create_table().unwrap();
334        data.set("len", self.data.lock().unwrap().len()).unwrap();
335        data.set(
336            "get",
337            scope
338                .create_function_mut(|_, (_this, index): (Table, usize)| {
339                    let data = self.data.lock().unwrap();
340                    match data.get(index) {
341                        Some(byte) => Ok(byte),
342                        None => Err(mlua::Error::external("Index out of bounds")),
343                    }
344                })
345                .unwrap(),
346        )
347        .unwrap();
348        data.set(
349            "set",
350            scope
351                .create_function_mut(|_, (_this, index, byte): (Table, usize, u8)| {
352                    let mut data = self.data.lock().unwrap();
353                    data.set(index, byte)
354                })
355                .unwrap(),
356        )
357        .unwrap();
358        context.set("data", data).unwrap();
359        context.set("offset", self.offset).unwrap();
360        context
361            .set("current_instruction", self.current_instruction.clone())
362            .unwrap();
363        context
364            .set("header", scope.create_userdata_ref(self.header).unwrap())
365            .unwrap();
366        context
367            .set(
368                "settings",
369                scope.create_any_userdata_ref_mut(self.settings).unwrap(),
370            )
371            .unwrap();
372        context
373            .set(
374                "get_instant_now",
375                scope
376                    .create_function(|_, ()| Ok(PluginInstant::now()))
377                    .unwrap(),
378            )
379            .unwrap();
380        context
381            .set(
382                "jump_to",
383                scope
384                    .create_function_mut(|_, file_address: usize| {
385                        App::jump_to_no_self(
386                            file_address,
387                            self.data.lock().unwrap().deref(),
388                            (self.screen_width, self.screen_height),
389                            self.vertical_margin,
390                            self.scroll,
391                            self.cursor,
392                            self.block_size,
393                            self.blocks_per_row,
394                        );
395                        Ok(())
396                    })
397                    .unwrap(),
398            )
399            .unwrap();
400        context
401            .set(
402                "get_fullscreen",
403                scope
404                    .create_function(|_, ()| {
405                        let fullscreen = self.fullscreen.lock().unwrap();
406                        Ok(**fullscreen)
407                    })
408                    .unwrap(),
409            )
410            .unwrap();
411        context
412            .set(
413                "set_fullscreen",
414                scope
415                    .create_function_mut(|_, fullscreen: bool| {
416                        **self.fullscreen.lock().unwrap() = fullscreen;
417                        Ok(())
418                    })
419                    .unwrap(),
420            )
421            .unwrap();
422        context
423            .set(
424                "get_selected_pane",
425                scope
426                    .create_function(|_, ()| {
427                        let selected_pane = self.selected_pane.lock().unwrap();
428                        Ok(**selected_pane)
429                    })
430                    .unwrap(),
431            )
432            .unwrap();
433        context
434            .set(
435                "set_selected_pane",
436                scope
437                    .create_function_mut(|_, selected_pane: Pane| {
438                        **self.selected_pane.lock().unwrap() = selected_pane;
439                        Ok(())
440                    })
441                    .unwrap(),
442            )
443            .unwrap();
444        context
445            .set(
446                "get_comments",
447                scope
448                    .create_function(|lua, ()| {
449                        let comments = self.comments.lock().unwrap();
450                        let table = lua.create_table().unwrap();
451                        for (address, comment) in comments.iter() {
452                            table.set(*address, comment.clone()).unwrap();
453                        }
454                        Ok(table)
455                    })
456                    .unwrap(),
457            )
458            .unwrap();
459        context
460            .set(
461                "get_comment",
462                scope
463                    .create_function(|_, address: u64| {
464                        let comments = self.comments.lock().unwrap();
465                        Ok(comments.get(&address).cloned())
466                    })
467                    .unwrap(),
468            )
469            .unwrap();
470        context
471            .set(
472                "set_comment",
473                scope
474                    .create_function_mut(|_, (address, comment): (u64, Option<String>)| {
475                        let mut comments = self.comments.lock().unwrap();
476                        if let Some(comment) = comment {
477                            if comment.is_empty() {
478                                comments.remove(&address);
479                            } else {
480                                comments.insert(address, comment);
481                            }
482                        } else {
483                            comments.remove(&address);
484                        }
485                        Ok(())
486                    })
487                    .unwrap(),
488            )
489            .unwrap();
490
491        context
492    }
493}