hex_patch/app/commands/
run_command.rs

1use std::error::Error;
2
3use crate::{
4    app::{
5        info_mode::InfoMode,
6        log::NotificationLevel,
7        popup::{
8            binary_choice::BinaryChoice, popup_state::PopupState, simple_choice::SimpleChoice,
9        },
10        App,
11    },
12    fuzzer::fuzzy_search_in_place,
13    get_app_context,
14};
15
16use super::command_info::CommandInfo;
17
18impl App {
19    pub(in crate::app) fn find_commands(&mut self, command: &str) -> Vec<CommandInfo> {
20        let mut commands = CommandInfo::full_list_of_commands(&self.plugin_manager);
21        //commands.retain(|c| c.command.contains(command));
22        fuzzy_search_in_place(command, &mut commands);
23        commands
24    }
25
26    pub(in crate::app) fn run_command(
27        &mut self,
28        command: &str,
29        scroll: usize,
30    ) -> Result<(), Box<dyn Error>> {
31        let command_opt = self.find_commands(command).into_iter().nth(scroll);
32        let command_info = command_opt.expect(&t!("errors.run_command_scroll_out_of_bounds"));
33        self.popup = None;
34        match command_info.command.as_str() {
35            "quit" => {
36                self.quit(None)?;
37            }
38            "dquit" => {
39                self.quit(Some(false))?;
40            }
41            "xquit" => {
42                self.quit(Some(true))?;
43            }
44            "save" => {
45                if self.data.dirty() {
46                    self.save_file()?;
47                }
48            }
49            "saveas" => {
50                self.request_popup_save_as();
51            }
52            "csave" => {
53                self.save_comments(None);
54            }
55            "help" => {
56                self.request_popup_help();
57            }
58            "open" => {
59                self.request_open()?;
60            }
61            "log" => {
62                self.request_popup_log();
63            }
64            "run" => {
65                self.request_popup_run();
66            }
67            "ftext" => {
68                self.request_popup_find_text();
69            }
70            "fsym" => {
71                self.request_popup_find_symbol();
72            }
73            "fcom" => {
74                self.request_popup_find_comment();
75            }
76            "ecom" => {
77                self.request_popup_edit_comment();
78            }
79            "text" => {
80                self.request_popup_text();
81            }
82            "patch" => {
83                self.request_popup_patch();
84            }
85            "jump" => {
86                self.request_popup_jump();
87            }
88            "view" => {
89                self.request_view_change();
90            }
91            "undo" => {
92                self.undo();
93            }
94            "redo" => {
95                self.redo();
96            }
97            any_other_command => {
98                let mut app_context = get_app_context!(self);
99                self.plugin_manager
100                    .run_command(any_other_command, &mut app_context)?;
101            }
102        }
103        Ok(())
104    }
105
106    pub(in crate::app) fn quit(&mut self, save: Option<bool>) -> Result<(), Box<dyn Error>> {
107        match save {
108            Some(true) => {
109                self.log(
110                    NotificationLevel::Debug,
111                    t!("app.messages.saving_and_quitting"),
112                );
113                if self.data.dirty() {
114                    self.save_file()?;
115                }
116                self.needs_to_exit = true;
117            }
118            Some(false) => {
119                self.log(
120                    NotificationLevel::Debug,
121                    t!("app.messages.quitting_without_saving"),
122                );
123                self.needs_to_exit = true;
124            }
125            None => {
126                self.log(NotificationLevel::Debug, t!("app.messages.quitting"));
127                if self.data.dirty() {
128                    self.log(
129                        NotificationLevel::Warning,
130                        t!("app.messages.unsaved_changes"),
131                    )
132                } else {
133                    self.needs_to_exit = true;
134                }
135            }
136        }
137        Ok(())
138    }
139
140    pub(in crate::app) fn request_quit(&mut self) {
141        if self.data.dirty() {
142            self.popup = Some(PopupState::QuitDirtySave(SimpleChoice::Cancel));
143        } else {
144            self.needs_to_exit = true;
145        }
146    }
147
148    pub(in crate::app) fn request_save(&mut self) {
149        if self.data.dirty() {
150            self.popup = Some(PopupState::Save(BinaryChoice::No));
151        }
152    }
153
154    pub(in crate::app) fn request_save_and_quit(&mut self) {
155        if self.data.dirty() {
156            self.popup = Some(PopupState::SaveAndQuit(BinaryChoice::No));
157        } else {
158            self.needs_to_exit = true;
159        }
160    }
161
162    pub(in crate::app) fn request_open(&mut self) -> Result<(), Box<dyn Error>> {
163        let mut new_popup = None;
164        Self::open_dir(
165            &mut new_popup,
166            &self.get_current_dir(),
167            &mut self.filesystem,
168        )?;
169        self.popup = new_popup;
170        Ok(())
171    }
172
173    pub(in crate::app) fn request_popup_save_as(&mut self) {
174        let path = self.filesystem.pwd().to_string();
175        let cursor = path.len();
176        self.popup = Some(PopupState::SaveAs { path, cursor });
177    }
178
179    pub(in crate::app) fn request_popup_help(&mut self) {
180        self.popup = Some(PopupState::Help(0));
181    }
182
183    pub(in crate::app) fn request_popup_log(&mut self) {
184        self.logger.reset_notification_level();
185        self.popup = Some(PopupState::Log(0));
186    }
187
188    pub(in crate::app) fn request_popup_run(&mut self) {
189        self.popup = Some(PopupState::Run {
190            command: String::new(),
191            cursor: 0,
192            results: self.find_commands(""),
193            scroll: 0,
194        });
195    }
196
197    pub(in crate::app) fn request_popup_find_symbol(&mut self) {
198        self.popup = Some(PopupState::FindSymbol {
199            filter: String::new(),
200            symbols: Vec::new(),
201            cursor: 0,
202            scroll: 0,
203        });
204    }
205
206    pub(in crate::app) fn request_popup_edit_comment(&mut self) {
207        let comment = self
208            .comments
209            .get(&(self.get_cursor_position().global_byte_index as u64))
210            .cloned()
211            .unwrap_or_default();
212        let cursor = comment.len();
213        self.popup = Some(PopupState::EditComment { comment, cursor });
214    }
215
216    pub(in crate::app) fn request_popup_find_comment(&mut self) {
217        self.popup = Some(PopupState::FindComment {
218            filter: String::new(),
219            comments: Vec::new(),
220            cursor: 0,
221            scroll: 0,
222        });
223    }
224
225    pub(in crate::app) fn request_popup_find_text(&mut self) {
226        self.popup = Some(PopupState::FindText {
227            text: self.text_last_searched_string.clone(),
228            cursor: 0,
229        });
230    }
231
232    pub(in crate::app) fn request_popup_text(&mut self) {
233        self.popup = Some(PopupState::InsertText {
234            text: String::new(),
235            cursor: 0,
236        });
237    }
238
239    pub(in crate::app) fn request_popup_patch(&mut self) {
240        self.popup = Some(PopupState::Patch {
241            assembly: String::new(),
242            preview: Ok(Vec::new()),
243            cursor: 0,
244        });
245    }
246
247    pub(in crate::app) fn request_popup_jump(&mut self) {
248        self.popup = Some(PopupState::JumpToAddress {
249            location: String::new(),
250            cursor: 0,
251        });
252    }
253
254    pub(in crate::app) fn request_view_change(&mut self) {
255        match self.info_mode {
256            InfoMode::Text => {
257                self.info_mode = InfoMode::Assembly;
258            }
259            InfoMode::Assembly => {
260                self.info_mode = InfoMode::Text;
261            }
262        }
263    }
264
265    pub(in crate::app) fn undo(&mut self) {
266        if let Some(change) = self.data.undo().cloned() {
267            let instruction_offset = self.get_instruction_at(change.offset()).file_address();
268            let instruction_offset = change
269                .offset()
270                .checked_sub(instruction_offset as usize)
271                .unwrap();
272            self.edit_assembly(change.offset() + instruction_offset);
273        } else {
274            self.log(
275                NotificationLevel::Warning,
276                t!("app.messages.nothing_to_undo"),
277            )
278        }
279    }
280
281    pub(in crate::app) fn redo(&mut self) {
282        if let Some(change) = self.data.redo().cloned() {
283            let instruction_offset = self.get_instruction_at(change.offset()).file_address();
284            let instruction_offset = change
285                .offset()
286                .checked_sub(instruction_offset as usize)
287                .unwrap();
288            self.edit_assembly(change.offset() + instruction_offset);
289        } else {
290            self.log(
291                NotificationLevel::Warning,
292                t!("app.messages.nothing_to_redo"),
293            )
294        }
295    }
296}
297
298#[cfg(test)]
299mod test {
300    use crate::app::asm::assembly_line::AssemblyLine;
301
302    use super::*;
303
304    #[test]
305    fn test_undo_redo() {
306        let mut app = App::mockup(vec![0x90; 4]);
307        app.patch_bytes(&[0, 0], false);
308        if let AssemblyLine::Instruction(instruction) = app.assembly_instructions[1].clone() {
309            assert_eq!(instruction.instruction.mnemonic, "add");
310        } else {
311            panic!("Expected an instruction.")
312        }
313
314        app.undo();
315        assert_eq!(app.data.bytes(), vec![0x90; 4].as_slice());
316        if let AssemblyLine::Instruction(instruction) = app.assembly_instructions[1].clone() {
317            assert_eq!(instruction.instruction.mnemonic, "nop");
318        } else {
319            panic!("Expected an instruction.")
320        }
321
322        app.redo();
323        assert_eq!(app.data.bytes(), vec![0x00, 0x00, 0x90, 0x90].as_slice());
324        if let AssemblyLine::Instruction(instruction) = app.assembly_instructions[1].clone() {
325            assert_eq!(instruction.instruction.mnemonic, "add");
326        } else {
327            panic!("Expected an instruction.")
328        }
329    }
330}