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(&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}