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        data::Data,
11        log::{logger::Logger, NotificationLevel},
12        pane::Pane,
13        popup::popup_state::PopupState,
14        settings::Settings,
15        App,
16    },
17    headers::Header,
18};
19
20use super::{
21    exported_commands::ExportedCommands, exported_header_parsers::ExportedHeaderParsers,
22    instruction_info::InstructionInfo, plugin_instant::PluginInstant,
23};
24
25#[macro_export]
26macro_rules! get_app_context {
27    ($app:ident) => {
28        $crate::app::plugins::app_context::AppContext::new(
29            $app.get_cursor_position().global_byte_index,
30            $app.get_current_instruction().map(|i| i.into()),
31            $app.screen_size.1,
32            $app.screen_size.0,
33            $app.blocks_per_row,
34            $app.block_size,
35            $app.vertical_margin,
36            &mut $app.scroll,
37            &mut $app.cursor,
38            &mut $app.data,
39            &$app.header,
40            &mut $app.settings,
41            &mut $app.logger,
42            &mut $app.popup,
43            &mut $app.fullscreen,
44            &mut $app.selected_pane,
45        )
46    };
47}
48
49pub struct AppContext<'app> {
50    pub exported_commands: Arc<Mutex<ExportedCommands>>,
51    pub exported_header_parsers: Arc<Mutex<ExportedHeaderParsers>>,
52    pub plugin_index: Option<usize>,
53
54    pub screen_height: u16,
55    pub screen_width: u16,
56    pub blocks_per_row: usize,
57    pub block_size: usize,
58    pub vertical_margin: u16,
59    pub data: Arc<Mutex<&'app mut Data>>,
60    pub scroll: &'app mut usize,
61    pub cursor: &'app mut (u16, u16),
62    pub offset: usize,
63    pub current_instruction: Option<InstructionInfo>,
64    pub header: &'app Header,
65    pub settings: &'app mut Settings,
66    pub logger: &'app mut Logger,
67    pub popup: Arc<Mutex<&'app mut Option<PopupState>>>,
68    pub fullscreen: Arc<Mutex<&'app mut bool>>,
69    pub selected_pane: Arc<Mutex<&'app mut Pane>>,
70}
71
72impl<'app> AppContext<'app> {
73    #[allow(clippy::too_many_arguments)]
74    pub fn new(
75        offset: usize,
76        current_instruction: Option<InstructionInfo>,
77        screen_height: u16,
78        screen_width: u16,
79        blocks_per_row: usize,
80        block_size: usize,
81        vertical_margin: u16,
82        scroll: &'app mut usize,
83        cursor: &'app mut (u16, u16),
84        data: &'app mut Data,
85        header: &'app Header,
86        settings: &'app mut Settings,
87        logger: &'app mut Logger,
88        popup: &'app mut Option<PopupState>,
89        fullscreen: &'app mut bool,
90        selected_pane: &'app mut Pane,
91    ) -> Self {
92        Self {
93            exported_commands: Arc::new(Mutex::new(ExportedCommands::default())),
94            exported_header_parsers: Arc::new(Mutex::new(ExportedHeaderParsers::default())),
95            plugin_index: None,
96            screen_height,
97            screen_width,
98            blocks_per_row,
99            block_size,
100            vertical_margin,
101            data: Arc::new(Mutex::new(data)),
102            scroll,
103            cursor,
104            offset,
105            current_instruction,
106            header,
107            settings,
108            logger,
109            popup: Arc::new(Mutex::new(popup)),
110            fullscreen: Arc::new(Mutex::new(fullscreen)),
111            selected_pane: Arc::new(Mutex::new(selected_pane)),
112        }
113    }
114
115    pub fn reset_exported_commands(&mut self) {
116        self.exported_commands = Arc::new(Mutex::new(ExportedCommands::default()));
117    }
118
119    pub fn reset_exported_header_parsers(&mut self) {
120        self.exported_header_parsers = Arc::new(Mutex::new(ExportedHeaderParsers::default()));
121    }
122
123    pub fn set_exported_commands(&mut self, exported_commands: ExportedCommands) {
124        self.exported_commands = Arc::new(Mutex::new(exported_commands));
125    }
126
127    pub fn set_exported_header_parsers(&mut self, exported_header_parsers: ExportedHeaderParsers) {
128        self.exported_header_parsers = Arc::new(Mutex::new(exported_header_parsers));
129    }
130
131    pub fn take_exported_commands(&mut self) -> ExportedCommands {
132        self.exported_commands.lock().unwrap().take()
133    }
134
135    pub fn take_exported_header_parsers(&mut self) -> ExportedHeaderParsers {
136        self.exported_header_parsers.lock().unwrap().take()
137    }
138
139    pub fn to_lua<'scope, 'env>(
140        &'env mut self,
141        lua: &'scope Lua,
142        scope: &'scope Scope<'scope, 'env>,
143    ) -> mlua::Table {
144        let context = lua.create_table().unwrap();
145        context
146            .set(
147                "log",
148                scope
149                    .create_function_mut(|_, (level, message): (u8, String)| {
150                        self.logger.log(NotificationLevel::from(level), &message);
151                        Ok(())
152                    })
153                    .unwrap(),
154            )
155            .unwrap();
156
157        let exported_commands = self.exported_commands.clone();
158        context
159            .set(
160                "add_command",
161                scope
162                    .create_function_mut(move |lua, (command, description): (String, String)| {
163                        if let Ok(_command_fn) = lua.globals().get::<Function>(command.clone()) {
164                            exported_commands
165                                .lock()
166                                .unwrap()
167                                .add_command(command, description);
168                            Ok(())
169                        } else {
170                            Err(mlua::Error::external(format!(
171                                "Function '{}' not found but needed to export the command",
172                                command
173                            )))
174                        }
175                    })
176                    .unwrap(),
177            )
178            .unwrap();
179
180        let exported_commands = self.exported_commands.clone();
181        context
182            .set(
183                "remove_command",
184                scope
185                    .create_function_mut(move |_, command: String| {
186                        if exported_commands.lock().unwrap().remove_command(&command) {
187                            Ok(())
188                        } else {
189                            Err(mlua::Error::external(format!(
190                                "Command '{}' not found",
191                                command
192                            )))
193                        }
194                    })
195                    .unwrap(),
196            )
197            .unwrap();
198
199        let exported_header_parsers = self.exported_header_parsers.clone();
200        context
201            .set(
202                "add_header_parser",
203                scope
204                    .create_function_mut(move |lua, callback: String| {
205                        if let Ok(_header_parser_fn) =
206                            lua.globals().get::<Function>(callback.clone())
207                        {
208                            exported_header_parsers
209                                .lock()
210                                .unwrap()
211                                .add_header_parser(callback);
212                            Ok(())
213                        } else {
214                            Err(mlua::Error::external(format!(
215                                "Function '{}' not found but needed to export the header parser",
216                                callback
217                            )))
218                        }
219                    })
220                    .unwrap(),
221            )
222            .unwrap();
223
224        let exported_header_parsers = self.exported_header_parsers.clone();
225        context
226            .set(
227                "remove_header_parser",
228                scope
229                    .create_function_mut(move |_, callback: String| {
230                        if exported_header_parsers
231                            .lock()
232                            .unwrap()
233                            .remove_header_parser(&callback)
234                        {
235                            Ok(())
236                        } else {
237                            Err(mlua::Error::external(format!(
238                                "Header parser '{}' not found",
239                                callback
240                            )))
241                        }
242                    })
243                    .unwrap(),
244            )
245            .unwrap();
246
247        context
248            .set(
249                "open_popup",
250                scope
251                    .create_function_mut(|_, callback: String| {
252                        let mut popup = self.popup.lock().unwrap();
253                        if popup.is_some() {
254                            Err(mlua::Error::external("Popup already open"))
255                        } else if lua.globals().get::<Function>(callback.clone()).is_err() {
256                            Err(mlua::Error::external(format!(
257                                "Function '{}' not found but needed to open the popup",
258                                callback
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
445        context
446    }
447}