1use super::{data::Data, info_mode::InfoMode, log::NotificationLevel, pane::Pane, App};
2
3pub struct CursorPosition {
4 pub cursor: Option<(u16, u16)>,
5 pub local_x: usize,
6 pub local_byte_index: usize,
7 pub block_index: usize,
8 pub local_block_index: usize,
9 pub line_index: usize,
10 pub line_byte_index: usize,
11 pub global_byte_index: usize,
12 pub high_byte: bool,
13}
14
15impl CursorPosition {
16 pub fn get_high_byte_offset(&self) -> usize {
17 match &self.high_byte {
18 true => 0,
19 false => 1,
20 }
21 }
22}
23
24impl App {
25 pub(super) fn get_cursor_position_no_self(
26 data: &Data,
27 blocks_per_row: usize,
28 block_size: usize,
29 cursor: (u16, u16),
30 scroll: usize,
31 ) -> CursorPosition {
32 if data.is_empty() || blocks_per_row == 0 {
33 return CursorPosition {
34 cursor: Some((0, 0)),
35 local_x: 0,
36 local_byte_index: 0,
37 block_index: 0,
38 local_block_index: 0,
39 line_index: 0,
40 line_byte_index: 0,
41 global_byte_index: 0,
42 high_byte: false,
43 };
44 }
45 let local_x = cursor.0 as usize % (block_size * 3 + 1);
46 let high_byte = local_x % 3 == 0;
47 let local_byte_index = local_x / 3;
48 let block_index = cursor.0 as usize / (block_size * 3 + 1)
49 + (scroll + cursor.1 as usize) * blocks_per_row;
50 let local_block_index = block_index % blocks_per_row;
51 let line_index = block_index / blocks_per_row;
52 let line_byte_index = local_byte_index + block_size * local_block_index;
53 let global_byte_index = line_byte_index + line_index * block_size * blocks_per_row;
54
55 CursorPosition {
56 cursor: Some(cursor),
57 local_x,
58 local_byte_index,
59 block_index,
60 local_block_index,
61 line_index,
62 line_byte_index,
63 global_byte_index,
64 high_byte,
65 }
66 }
67 pub(super) fn get_cursor_position(&self) -> CursorPosition {
68 Self::get_cursor_position_no_self(
69 &self.data,
70 self.blocks_per_row,
71 self.block_size,
72 self.cursor,
73 self.scroll,
74 )
75 }
76
77 pub(super) fn get_expected_cursor_position_no_self(
78 global_byte_index: usize,
79 high_byte: bool,
80 block_size: usize,
81 blocks_per_row: usize,
82 screen_size: (u16, u16),
83 vertical_margin: u16,
84 scroll: usize,
85 ) -> CursorPosition {
86 let block_index = global_byte_index / block_size;
87 let line_index = block_index / blocks_per_row;
88 let local_block_index = block_index % blocks_per_row;
89 let local_byte_index = global_byte_index % block_size;
90 let local_x = (local_byte_index + local_block_index * block_size) * 3 + local_block_index;
91 let cursor_x = local_x as u16 + if high_byte { 0 } else { 1 };
92 let cursor_y = line_index as isize - scroll as isize;
93 let cursor =
94 if cursor_y < 0 || cursor_y >= screen_size.1 as isize - vertical_margin as isize {
95 None
96 } else {
97 Some((cursor_x, cursor_y as u16))
98 };
99
100 CursorPosition {
101 cursor,
102 local_x,
103 local_byte_index,
104 block_index,
105 local_block_index,
106 line_index,
107 line_byte_index: local_byte_index + local_block_index * block_size,
108 global_byte_index,
109 high_byte,
110 }
111 }
112
113 pub(super) fn get_expected_cursor_position(
114 &self,
115 global_byte_index: usize,
116 high_byte: bool,
117 ) -> CursorPosition {
118 Self::get_expected_cursor_position_no_self(
119 global_byte_index,
120 high_byte,
121 self.block_size,
122 self.blocks_per_row,
123 self.screen_size,
124 self.vertical_margin,
125 self.scroll,
126 )
127 }
128
129 pub(super) fn jump_to_fuzzy_symbol(
130 &mut self,
131 symbol: &str,
132 symbols: &[(u64, String)],
133 scroll: usize,
134 ) {
135 if symbol.is_empty() {
136 if let Some(symbols) = self.header.get_symbols() {
137 if let Some(symbol) = symbols.iter().nth(scroll) {
138 let (address, name) = symbol;
139 let log_message =
140 t!("app.messages.jump_to_symbol", symbol = name, address = address : {:#X});
141 self.jump_to(*address as usize, true);
142 self.log(NotificationLevel::Debug, log_message);
143 } else {
144 unreachable!("The scroll should not be greater than the number of symbols")
145 }
146 } else {
147 self.log(NotificationLevel::Error, t!("errors.no_symbols_found"));
148 }
149 return;
150 } else if symbols.is_empty() {
151 self.log(NotificationLevel::Error, t!("errors.no_matching_symbols"));
152 return;
153 }
154
155 let mut find_iter = symbols.iter().skip(scroll);
156 if let Some(symbol) = find_iter.next() {
157 let (address, name) = symbol;
158 self.log(
159 NotificationLevel::Debug,
160 t!("app.messages.jump_to_symbol", symbol = name, address = address : {:#X}),
161 );
162 self.jump_to(*address as usize, true);
163 } else {
164 unreachable!("The scroll should not be greater than the number of symbols");
165 }
166 }
167
168 pub(super) fn jump_to_fuzzy_comment(
169 &mut self,
170 comment: &str,
171 comments: &[(u64, String)],
172 scroll: usize,
173 ) {
174 if comment.is_empty() {
175 if let Some(comment) = self.comments.iter().nth(scroll) {
176 let (address, _comment) = comment;
177 let log_message = t!("app.messages.jump_to_comment", address = address : {:#X});
178 self.jump_to(*address as usize, false);
179 self.log(NotificationLevel::Debug, log_message);
180 } else {
181 unreachable!("The scroll should not be greater than the number of comments")
182 }
183 return;
184 } else if comments.is_empty() {
185 self.log(NotificationLevel::Error, t!("errors.no_matching_comments"));
186 return;
187 }
188
189 let mut find_iter = comments.iter().skip(scroll);
190 if let Some(comment) = find_iter.next() {
191 let (address, _comment) = comment;
192 self.log(
193 NotificationLevel::Debug,
194 t!("app.messages.jump_to_comment", address = address : {:#X}),
195 );
196 self.jump_to(*address as usize, false);
197 } else {
198 unreachable!("The scroll should not be greater than the number of comments");
199 }
200 }
201
202 pub(super) fn jump_to_symbol(&mut self, symbol: &str) {
203 if let Some(address) = symbol.strip_prefix("0x") {
204 if let Ok(address) = usize::from_str_radix(address, 16) {
205 self.log(
206 NotificationLevel::Debug,
207 t!("app.messages.jump_to_address", address = address : {:#X}),
208 );
209 self.jump_to(address, false);
210 } else {
211 self.log(
212 NotificationLevel::Error,
213 t!("errors.invalid_address", address = symbol),
214 );
215 }
216 } else if let Some(address) = symbol.strip_prefix("v0x") {
217 if let Ok(address) = usize::from_str_radix(address, 16) {
218 self.log(
219 NotificationLevel::Debug,
220 t!("app.messages.jump_to_virtual_address", address = address : {:#X}),
221 );
222 self.jump_to(address, true);
223 } else {
224 self.log(
225 NotificationLevel::Error,
226 t!("errors.invalid_virtual_address", address = symbol),
227 );
228 }
229 } else if let Some(address) = self.header.symbol_to_address(symbol) {
230 self.log(
231 NotificationLevel::Debug,
232 t!("app.messages.jump_to_symbol", symbol = symbol, address = address : {:#X}),
233 );
234 self.jump_to(address as usize, true);
235 } else if let Some(address) = self
236 .header
237 .get_sections()
238 .iter()
239 .find(|x| x.name == symbol)
240 .map(|x| x.file_offset)
241 {
242 self.log(
243 NotificationLevel::Debug,
244 t!("app.messages.jump_to_section", section = symbol, address = address : {:#X}),
245 );
246 self.jump_to(address as usize, false);
247 } else {
248 self.log(
249 NotificationLevel::Error,
250 t!("errors.symbol_not_found", symbol = symbol),
251 );
252 }
253 }
254
255 #[allow(clippy::too_many_arguments)]
256 pub(super) fn jump_to_no_self(
257 mut address: usize,
258 data: &Data,
259 screen_size: (u16, u16),
260 vertical_margin: u16,
261 scroll: &mut usize,
262 cursor: &mut (u16, u16),
263 block_size: usize,
264 blocks_per_row: usize,
265 ) {
266 if address >= data.len() {
267 address = data.len().saturating_sub(1);
268 }
269 if screen_size.1 <= vertical_margin {
270 *scroll = 0;
271 *cursor = (0, 0);
272 return;
273 }
274
275 let expected_cursor_position = Self::get_expected_cursor_position_no_self(
276 address,
277 false,
278 block_size,
279 blocks_per_row,
280 screen_size,
281 vertical_margin,
282 *scroll,
283 );
284 let CursorPosition {
285 local_x,
286 line_index,
287 ..
288 } = expected_cursor_position;
289 let y = line_index as isize - *scroll as isize;
290
291 if y < 0 {
292 *scroll = line_index;
293 *cursor = (local_x as u16, 0);
294 } else if y < screen_size.1 as isize - vertical_margin as isize {
295 *cursor = (local_x as u16, y as u16);
296 } else {
297 *scroll = line_index - (screen_size.1 - vertical_margin - 1) as usize;
298 *cursor = (local_x as u16, (screen_size.1 - vertical_margin - 1));
299 }
300 }
301
302 pub(super) fn jump_to(&mut self, mut address: usize, is_virtual: bool) {
303 if is_virtual {
304 if let Some(physical_address) = self.header.virtual_to_physical_address(address as u64)
305 {
306 address = physical_address as usize;
307 } else {
308 self.log(
309 NotificationLevel::Error,
310 t!("errors.virtual_address_not_found", address = address : {:#X}),
311 );
312 return;
313 }
314 }
315 Self::jump_to_no_self(
316 address,
317 &self.data,
318 self.screen_size,
319 self.vertical_margin,
320 &mut self.scroll,
321 &mut self.cursor,
322 self.block_size,
323 self.blocks_per_row,
324 )
325 }
326
327 pub(super) fn move_cursor_in_selected_panel(&mut self, dx: isize, dy: isize) {
328 match self.selected_pane {
329 Pane::Hex => self.move_cursor(dx, dy, false),
330 Pane::View => match self.info_mode {
331 InfoMode::Text => self.move_cursor(dx * 2, dy, false),
332 InfoMode::Assembly => self.move_cursor_to_near_instruction(dy),
333 },
334 }
335 }
336
337 pub(super) fn move_cursor(&mut self, dx: isize, dy: isize, best_effort: bool) {
338 if self.screen_size.1 <= self.vertical_margin {
339 return;
340 }
341 let current_position = self.get_cursor_position();
342 let half_byte_delta =
343 dx + (dy * self.block_size as isize * self.blocks_per_row as isize * 2);
344 let half_byte_position =
345 current_position.global_byte_index * 2 + if current_position.high_byte { 0 } else { 1 };
346
347 let mut new_half_byte_position =
348 (half_byte_position as isize).saturating_add(half_byte_delta);
349 if !best_effort
350 && (new_half_byte_position < 0
351 || new_half_byte_position >= self.data.len() as isize * 2)
352 {
353 return;
354 } else if best_effort {
355 let max_half_byte_position = (self.data.len() as isize * 2 - 1).max(0);
356 new_half_byte_position = new_half_byte_position.clamp(0, max_half_byte_position);
357 }
358 let new_global_byte_index = new_half_byte_position as usize / 2;
359 let new_high_byte = new_half_byte_position % 2 == 0;
360
361 let new_selected_row = new_global_byte_index / (self.block_size * self.blocks_per_row);
362 let min_visible_row = self.scroll;
363 let max_visible_row =
364 self.scroll + (self.screen_size.1 - self.vertical_margin) as usize - 1;
365 let new_scroll = if new_selected_row < min_visible_row {
366 new_selected_row
367 } else if new_selected_row > max_visible_row {
368 new_selected_row - (self.screen_size.1 - self.vertical_margin) as usize + 1
369 } else {
370 self.scroll
371 };
372
373 self.scroll = new_scroll;
374
375 self.cursor = self
376 .get_expected_cursor_position(new_global_byte_index, new_high_byte)
377 .cursor
378 .expect(&t!("errors.cursor_position"));
379 }
380
381 pub(super) fn move_cursor_page_up(&mut self) {
382 let screen_size_y = self.screen_size.1 as isize - self.vertical_margin as isize;
383 if screen_size_y > 0 {
384 self.move_cursor(0, -screen_size_y, true);
385 }
386 }
387
388 pub(super) fn move_cursor_page_down(&mut self) {
389 let screen_size_y = self.screen_size.1 as isize - self.vertical_margin as isize;
390 if screen_size_y > 0 {
391 self.move_cursor(0, screen_size_y, true);
392 }
393 }
394
395 pub(super) fn move_cursor_to_end(&mut self) {
396 let bytes = self.data.len();
397
398 self.move_cursor(bytes as isize * 2, 0, true);
399 }
400
401 pub(super) fn move_cursor_to_start(&mut self) {
402 let bytes = self.data.len();
403 self.move_cursor(bytes as isize * -2, 0, true);
404 }
405
406 pub(super) fn move_cursor_to_near_instruction(&mut self, instruction_count: isize) {
407 let current_offset = self.get_cursor_position().global_byte_index;
408 if current_offset >= self.assembly_offsets.len() {
409 return;
410 }
411 let current_instruction_index = self.assembly_offsets[current_offset];
412 let mut next_instruction_index = (current_instruction_index as isize + instruction_count)
413 .clamp(
414 0,
415 self.assembly_instructions.len().saturating_sub(1) as isize,
416 ) as usize;
417 while instruction_count < 0
418 && next_instruction_index != 0
419 && next_instruction_index != current_instruction_index
420 && self.assembly_instructions[next_instruction_index].file_address()
421 == self.assembly_instructions[current_instruction_index].file_address()
422 {
423 next_instruction_index = next_instruction_index.saturating_sub(1);
424 }
425
426 let target_address = self.assembly_instructions[next_instruction_index].file_address();
427 self.jump_to(target_address as usize, false);
428 }
429
430 pub(super) fn switch_selected_pane(&mut self) {
431 match self.selected_pane {
432 Pane::Hex => self.selected_pane = Pane::View,
433 Pane::View => self.selected_pane = Pane::Hex,
434 }
435 }
436}
437
438#[cfg(test)]
439mod test {
440 use super::*;
441 #[test]
442 fn test_move_cursor() {
443 let data = vec![0; 0x100];
444 let mut app = App::mockup(data);
445 app.resize_to_size(80, 24);
446
447 app.move_cursor(1, 0, false);
448 assert_eq!(app.cursor, (1, 0));
449 app.move_cursor(0, 1, false);
450 assert_eq!(app.cursor, (1, 1));
451
452 app.move_cursor(0, 0, false);
453 assert_eq!(app.cursor, (1, 1));
454
455 app.move_cursor(0, -1, false);
456 assert_eq!(app.cursor, (1, 0));
457 app.move_cursor(-1, 0, false);
458 assert_eq!(app.cursor, (0, 0));
459
460 app.move_cursor(0, -1, false);
461 assert_eq!(app.cursor, (0, 0));
462 app.move_cursor(-1, 0, false);
463 assert_eq!(app.cursor, (0, 0));
464
465 let current_position = app.get_cursor_position();
466 assert_eq!(current_position.global_byte_index, 0);
467 assert!(current_position.high_byte);
468
469 app.move_cursor(81, 0, false);
470 let current_position = app.get_cursor_position();
471 assert_eq!(current_position.global_byte_index, 40);
472 assert!(!current_position.high_byte);
473
474 app.move_cursor(-1, -1, false);
475 let bytes_per_line = app.block_size * app.blocks_per_row;
476 let current_position = app.get_cursor_position();
477 assert_eq!(current_position.global_byte_index, 40 - bytes_per_line);
478 assert!(current_position.high_byte);
479 }
480
481 #[test]
482 fn test_move_with_no_screen() {
483 let data = vec![0; 0x100];
484 let mut app = App::mockup(data);
485 app.resize_to_size(0, 0);
486
487 app.move_cursor(1, 0, false);
488 assert_eq!(app.cursor, (0, 0));
489 app.move_cursor(0, 1, false);
490 assert_eq!(app.cursor, (0, 0));
491 app.move_cursor(0, -1, false);
492 assert_eq!(app.cursor, (0, 0));
493 app.move_cursor(-1, 0, false);
494 assert_eq!(app.cursor, (0, 0));
495 }
496
497 #[test]
498 fn test_move_with_small_screen() {
499 let data = vec![0; 0x100];
500 let mut app = App::mockup(data);
501 app.resize_to_size(1, 1);
502
503 app.move_cursor(1, 0, false);
504 assert_eq!(app.cursor, (0, 0));
505 app.move_cursor(0, 1, false);
506 assert_eq!(app.cursor, (0, 0));
507 app.move_cursor(0, -1, false);
508 assert_eq!(app.cursor, (0, 0));
509 app.move_cursor(-1, 0, false);
510 assert_eq!(app.cursor, (0, 0));
511 }
512
513 #[test]
514 fn test_move_to_near_instruction() {
515 let data = vec![
516 0x90, 0x90, 0x90, 0x48, 0x89, 0xd8, 0xeb, 0xfe, 0x90, 0x90, 0x90,
517 ];
518 let mut app = App::mockup(data);
519 app.resize_to_size(80, 24);
520
521 app.move_cursor_to_near_instruction(1);
522 let current_position = app.get_cursor_position().global_byte_index;
523 assert_eq!(current_position, 1);
524
525 app.move_cursor_to_near_instruction(2);
526 let current_position = app.get_cursor_position().global_byte_index;
527 assert_eq!(current_position, 3);
528
529 app.move_cursor_to_near_instruction(-1);
530 let current_position = app.get_cursor_position().global_byte_index;
531 assert_eq!(current_position, 2);
532
533 app.move_cursor_to_near_instruction(4);
534 let current_position = app.get_cursor_position().global_byte_index;
535 assert_eq!(current_position, 9);
536
537 app.move_cursor_to_near_instruction(2);
538 let current_position = app.get_cursor_position().global_byte_index;
539 assert_eq!(current_position, 10);
540
541 app.move_cursor_to_near_instruction(100);
542 let current_position = app.get_cursor_position().global_byte_index;
543 assert_eq!(current_position, 10);
544
545 app.move_cursor_to_near_instruction(-100);
546 let current_position = app.get_cursor_position().global_byte_index;
547 assert_eq!(current_position, 0);
548 }
549}