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}