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}