hex_patch/app/commands/
run_command.rs1use 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 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}