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}