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 "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}