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            "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(NotificationLevel::Debug, "Saving and quitting...");
110                if self.data.dirty() {
111                    self.save_file()?;
112                }
113                self.needs_to_exit = true;
114            }
115            Some(false) => {
116                self.log(NotificationLevel::Debug, "Quitting without saving...");
117                self.needs_to_exit = true;
118            }
119            None => {
120                self.log(NotificationLevel::Debug, "Quitting...");
121                if self.data.dirty() {
122                    self.log(NotificationLevel::Warning, "You have unsaved changes.")
123                } else {
124                    self.needs_to_exit = true;
125                }
126            }
127        }
128        Ok(())
129    }
130
131    pub(in crate::app) fn request_quit(&mut self) {
132        if self.data.dirty() {
133            self.popup = Some(PopupState::QuitDirtySave(SimpleChoice::Cancel));
134        } else {
135            self.needs_to_exit = true;
136        }
137    }
138
139    pub(in crate::app) fn request_save(&mut self) {
140        if self.data.dirty() {
141            self.popup = Some(PopupState::Save(BinaryChoice::No));
142        }
143    }
144
145    pub(in crate::app) fn request_save_and_quit(&mut self) {
146        if self.data.dirty() {
147            self.popup = Some(PopupState::SaveAndQuit(BinaryChoice::No));
148        } else {
149            self.needs_to_exit = true;
150        }
151    }
152
153    pub(in crate::app) fn request_open(&mut self) -> Result<(), Box<dyn Error>> {
154        let mut new_popup = None;
155        Self::open_dir(
156            &mut new_popup,
157            &self.get_current_dir(),
158            &mut self.filesystem,
159        )?;
160        self.popup = new_popup;
161        Ok(())
162    }
163
164    pub(in crate::app) fn request_popup_save_as(&mut self) {
165        let path = self.filesystem.pwd().to_string();
166        let cursor = path.len();
167        self.popup = Some(PopupState::SaveAs { path, cursor });
168    }
169
170    pub(in crate::app) fn request_popup_help(&mut self) {
171        self.popup = Some(PopupState::Help(0));
172    }
173
174    pub(in crate::app) fn request_popup_log(&mut self) {
175        self.logger.reset_notification_level();
176        self.popup = Some(PopupState::Log(0));
177    }
178
179    pub(in crate::app) fn request_popup_run(&mut self) {
180        self.popup = Some(PopupState::Run {
181            command: String::new(),
182            cursor: 0,
183            results: self.find_commands(""),
184            scroll: 0,
185        });
186    }
187
188    pub(in crate::app) fn request_popup_find_symbol(&mut self) {
189        self.popup = Some(PopupState::FindSymbol {
190            filter: String::new(),
191            symbols: Vec::new(),
192            cursor: 0,
193            scroll: 0,
194        });
195    }
196
197    pub(in crate::app) fn request_popup_edit_comment(&mut self) {
198        let comment = self
199            .comments
200            .get(&(self.get_cursor_position().global_byte_index as u64))
201            .cloned()
202            .unwrap_or_default();
203        let cursor = comment.len();
204        self.popup = Some(PopupState::EditComment { comment, cursor });
205    }
206
207    pub(in crate::app) fn request_popup_find_comment(&mut self) {
208        self.popup = Some(PopupState::FindComment {
209            filter: String::new(),
210            comments: Vec::new(),
211            cursor: 0,
212            scroll: 0,
213        });
214    }
215
216    pub(in crate::app) fn request_popup_find_text(&mut self) {
217        self.popup = Some(PopupState::FindText {
218            text: self.text_last_searched_string.clone(),
219            cursor: 0,
220        });
221    }
222
223    pub(in crate::app) fn request_popup_text(&mut self) {
224        self.popup = Some(PopupState::InsertText {
225            text: String::new(),
226            cursor: 0,
227        });
228    }
229
230    pub(in crate::app) fn request_popup_patch(&mut self) {
231        self.popup = Some(PopupState::Patch {
232            assembly: String::new(),
233            preview: Ok(Vec::new()),
234            cursor: 0,
235        });
236    }
237
238    pub(in crate::app) fn request_popup_jump(&mut self) {
239        self.popup = Some(PopupState::JumpToAddress {
240            location: String::new(),
241            cursor: 0,
242        });
243    }
244
245    pub(in crate::app) fn request_view_change(&mut self) {
246        match self.info_mode {
247            InfoMode::Text => {
248                self.info_mode = InfoMode::Assembly;
249            }
250            InfoMode::Assembly => {
251                self.info_mode = InfoMode::Text;
252            }
253        }
254    }
255
256    pub(in crate::app) fn undo(&mut self) {
257        if let Some(change) = self.data.undo().cloned() {
258            let instruction_offset = self.get_instruction_at(change.offset()).file_address();
259            let instruction_offset = change
260                .offset()
261                .checked_sub(instruction_offset as usize)
262                .unwrap();
263            self.edit_assembly(change.offset() + instruction_offset);
264        } else {
265            self.log(NotificationLevel::Warning, "Nothing to undo.")
266        }
267    }
268
269    pub(in crate::app) fn redo(&mut self) {
270        if let Some(change) = self.data.redo().cloned() {
271            let instruction_offset = self.get_instruction_at(change.offset()).file_address();
272            let instruction_offset = change
273                .offset()
274                .checked_sub(instruction_offset as usize)
275                .unwrap();
276            self.edit_assembly(change.offset() + instruction_offset);
277        } else {
278            self.log(NotificationLevel::Warning, "Nothing to redo.")
279        }
280    }
281}
282
283#[cfg(test)]
284mod test {
285    use crate::app::asm::assembly_line::AssemblyLine;
286
287    use super::*;
288
289    #[test]
290    fn test_undo_redo() {
291        let mut app = App::mockup(vec![0x90; 4]);
292        app.patch_bytes(&[0, 0], false);
293        if let AssemblyLine::Instruction(instruction) = app.assembly_instructions[1].clone() {
294            assert_eq!(instruction.instruction.mnemonic, "add");
295        } else {
296            panic!("Expected an instruction.")
297        }
298
299        app.undo();
300        assert_eq!(app.data.bytes(), vec![0x90; 4].as_slice());
301        if let AssemblyLine::Instruction(instruction) = app.assembly_instructions[1].clone() {
302            assert_eq!(instruction.instruction.mnemonic, "nop");
303        } else {
304            panic!("Expected an instruction.")
305        }
306
307        app.redo();
308        assert_eq!(app.data.bytes(), vec![0x00, 0x00, 0x90, 0x90].as_slice());
309        if let AssemblyLine::Instruction(instruction) = app.assembly_instructions[1].clone() {
310            assert_eq!(instruction.instruction.mnemonic, "add");
311        } else {
312            panic!("Expected an instruction.")
313        }
314    }
315}