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("Scroll out of bounds for run_command.");
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            "help" => {
53                self.request_popup_help();
54            }
55            "open" => {
56                self.request_open()?;
57            }
58            "log" => {
59                self.request_popup_log();
60            }
61            "run" => {
62                self.request_popup_run();
63            }
64            "ftext" => {
65                self.request_popup_find_text();
66            }
67            "fsym" => {
68                self.request_popup_find_symbol();
69            }
70            "text" => {
71                self.request_popup_text();
72            }
73            "patch" => {
74                self.request_popup_patch();
75            }
76            "jump" => {
77                self.request_popup_jump();
78            }
79            "view" => {
80                self.request_view_change();
81            }
82            "undo" => {
83                self.undo();
84            }
85            "redo" => {
86                self.redo();
87            }
88            any_other_command => {
89                let mut app_context = get_app_context!(self);
90                self.plugin_manager
91                    .run_command(any_other_command, &mut app_context)?;
92            }
93        }
94        Ok(())
95    }
96
97    pub(in crate::app) fn quit(&mut self, save: Option<bool>) -> Result<(), Box<dyn Error>> {
98        match save {
99            Some(true) => {
100                self.log(NotificationLevel::Debug, "Saving and quitting...");
101                if self.data.dirty() {
102                    self.save_file()?;
103                }
104                self.needs_to_exit = true;
105            }
106            Some(false) => {
107                self.log(NotificationLevel::Debug, "Quitting without saving...");
108                self.needs_to_exit = true;
109            }
110            None => {
111                self.log(NotificationLevel::Debug, "Quitting...");
112                if self.data.dirty() {
113                    self.log(NotificationLevel::Warning, "You have unsaved changes.")
114                } else {
115                    self.needs_to_exit = true;
116                }
117            }
118        }
119        Ok(())
120    }
121
122    pub(in crate::app) fn request_quit(&mut self) {
123        if self.data.dirty() {
124            self.popup = Some(PopupState::QuitDirtySave(SimpleChoice::Cancel));
125        } else {
126            self.needs_to_exit = true;
127        }
128    }
129
130    pub(in crate::app) fn request_save(&mut self) {
131        if self.data.dirty() {
132            self.popup = Some(PopupState::Save(BinaryChoice::No));
133        }
134    }
135
136    pub(in crate::app) fn request_save_and_quit(&mut self) {
137        if self.data.dirty() {
138            self.popup = Some(PopupState::SaveAndQuit(BinaryChoice::No));
139        } else {
140            self.needs_to_exit = true;
141        }
142    }
143
144    pub(in crate::app) fn request_open(&mut self) -> Result<(), Box<dyn Error>> {
145        let mut new_popup = None;
146        Self::open_dir(
147            &mut new_popup,
148            &self.get_current_dir(),
149            &mut self.filesystem,
150        )?;
151        self.popup = new_popup;
152        Ok(())
153    }
154
155    pub(in crate::app) fn request_popup_save_as(&mut self) {
156        let path = self.filesystem.pwd().to_string();
157        let cursor = path.len();
158        self.popup = Some(PopupState::SaveAs { path, cursor });
159    }
160
161    pub(in crate::app) fn request_popup_help(&mut self) {
162        self.popup = Some(PopupState::Help(0));
163    }
164
165    pub(in crate::app) fn request_popup_log(&mut self) {
166        self.logger.reset_notification_level();
167        self.popup = Some(PopupState::Log(0));
168    }
169
170    pub(in crate::app) fn request_popup_run(&mut self) {
171        self.popup = Some(PopupState::Run {
172            command: String::new(),
173            cursor: 0,
174            results: self.find_commands(""),
175            scroll: 0,
176        });
177    }
178
179    pub(in crate::app) fn request_popup_find_symbol(&mut self) {
180        self.popup = Some(PopupState::FindSymbol {
181            filter: String::new(),
182            symbols: Vec::new(),
183            cursor: 0,
184            scroll: 0,
185        });
186    }
187
188    pub(in crate::app) fn request_popup_find_text(&mut self) {
189        self.popup = Some(PopupState::FindText {
190            text: self.text_last_searched_string.clone(),
191            cursor: 0,
192        });
193    }
194
195    pub(in crate::app) fn request_popup_text(&mut self) {
196        self.popup = Some(PopupState::InsertText {
197            text: String::new(),
198            cursor: 0,
199        });
200    }
201
202    pub(in crate::app) fn request_popup_patch(&mut self) {
203        self.popup = Some(PopupState::Patch {
204            assembly: String::new(),
205            preview: Ok(Vec::new()),
206            cursor: 0,
207        });
208    }
209
210    pub(in crate::app) fn request_popup_jump(&mut self) {
211        self.popup = Some(PopupState::JumpToAddress {
212            location: String::new(),
213            cursor: 0,
214        });
215    }
216
217    pub(in crate::app) fn request_view_change(&mut self) {
218        match self.info_mode {
219            InfoMode::Text => {
220                self.info_mode = InfoMode::Assembly;
221            }
222            InfoMode::Assembly => {
223                self.info_mode = InfoMode::Text;
224            }
225        }
226    }
227
228    pub(in crate::app) fn undo(&mut self) {
229        if let Some(change) = self.data.undo().cloned() {
230            let instruction_offset = self.get_instruction_at(change.offset()).file_address();
231            let instruction_offset = change
232                .offset()
233                .checked_sub(instruction_offset as usize)
234                .unwrap();
235            self.edit_assembly(change.offset() + instruction_offset);
236        } else {
237            self.log(NotificationLevel::Warning, "Nothing to undo.")
238        }
239    }
240
241    pub(in crate::app) fn redo(&mut self) {
242        if let Some(change) = self.data.redo().cloned() {
243            let instruction_offset = self.get_instruction_at(change.offset()).file_address();
244            let instruction_offset = change
245                .offset()
246                .checked_sub(instruction_offset as usize)
247                .unwrap();
248            self.edit_assembly(change.offset() + instruction_offset);
249        } else {
250            self.log(NotificationLevel::Warning, "Nothing to redo.")
251        }
252    }
253}
254
255#[cfg(test)]
256mod test {
257    use crate::app::asm::assembly_line::AssemblyLine;
258
259    use super::*;
260
261    #[test]
262    fn test_undo_redo() {
263        let mut app = App::mockup(vec![0x90; 4]);
264        app.patch_bytes(&[0, 0], false);
265        if let AssemblyLine::Instruction(instruction) = app.assembly_instructions[1].clone() {
266            assert_eq!(instruction.instruction.mnemonic, "add");
267        } else {
268            panic!("Expected an instruction.")
269        }
270
271        app.undo();
272        assert_eq!(app.data.bytes(), vec![0x90; 4].as_slice());
273        if let AssemblyLine::Instruction(instruction) = app.assembly_instructions[1].clone() {
274            assert_eq!(instruction.instruction.mnemonic, "nop");
275        } else {
276            panic!("Expected an instruction.")
277        }
278
279        app.redo();
280        assert_eq!(app.data.bytes(), vec![0x00, 0x00, 0x90, 0x90].as_slice());
281        if let AssemblyLine::Instruction(instruction) = app.assembly_instructions[1].clone() {
282            assert_eq!(instruction.instruction.mnemonic, "add");
283        } else {
284            panic!("Expected an instruction.")
285        }
286    }
287}