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 = format!("Jumping to symbol {} at {:#X}", name, address);
140 self.jump_to(*address as usize, true);
141 self.log(NotificationLevel::Debug, &log_message);
142 } else {
143 unreachable!("The scroll should not be greater than the number of symbols")
144 }
145 } else {
146 self.log(NotificationLevel::Error, "No symbols found");
147 }
148 return;
149 } else if symbols.is_empty() {
150 self.log(
151 NotificationLevel::Error,
152 "No symbols matching the search pattern found",
153 );
154 return;
155 }
156
157 let mut find_iter = symbols.iter().skip(scroll);
158 if let Some(symbol) = find_iter.next() {
159 let (address, name) = symbol;
160 self.log(
161 NotificationLevel::Debug,
162 &format!("Jumping to symbol {} at {:#X}", name, address),
163 );
164 self.jump_to(*address as usize, true);
165 } else {
166 unreachable!("The scroll should not be greater than the number of symbols");
167 }
168 }
169
170 pub(super) fn jump_to_fuzzy_comment(
171 &mut self,
172 comment: &str,
173 comments: &[(u64, String)],
174 scroll: usize,
175 ) {
176 if comment.is_empty() {
177 if let Some(comment) = self.comments.iter().nth(scroll) {
178 let (address, _comment) = comment;
179 let log_message = format!("Jumping to comment at {:#X}", address);
180 self.jump_to(*address as usize, false);
181 self.log(NotificationLevel::Debug, &log_message);
182 } else {
183 unreachable!("The scroll should not be greater than the number of comments")
184 }
185 return;
186 } else if comments.is_empty() {
187 self.log(
188 NotificationLevel::Error,
189 "No comments matching the search pattern found",
190 );
191 return;
192 }
193
194 let mut find_iter = comments.iter().skip(scroll);
195 if let Some(comment) = find_iter.next() {
196 let (address, _comment) = comment;
197 self.log(
198 NotificationLevel::Debug,
199 &format!("Jumping to comment at {:#X}", address),
200 );
201 self.jump_to(*address as usize, false);
202 } else {
203 unreachable!("The scroll should not be greater than the number of comments");
204 }
205 }
206
207 pub(super) fn jump_to_symbol(&mut self, symbol: &str) {
208 if let Some(address) = symbol.strip_prefix("0x") {
209 if let Ok(address) = usize::from_str_radix(address, 16) {
210 self.log(
211 NotificationLevel::Debug,
212 &format!("Jumping to address: {:#X}", address),
213 );
214 self.jump_to(address, false);
215 } else {
216 self.log(
217 NotificationLevel::Error,
218 &format!("Invalid address: {}", symbol),
219 );
220 }
221 } else if let Some(address) = symbol.strip_prefix("v0x") {
222 if let Ok(address) = usize::from_str_radix(address, 16) {
223 self.log(
224 NotificationLevel::Debug,
225 &format!("Jumping to virtual address: {:#X}", address),
226 );
227 self.jump_to(address, true);
228 } else {
229 self.log(
230 NotificationLevel::Error,
231 &format!("Invalid virtual address: {}", symbol),
232 );
233 }
234 } else if let Some(address) = self.header.symbol_to_address(symbol) {
235 self.log(
236 NotificationLevel::Debug,
237 &format!("Jumping to symbol {} at {:#X}", symbol, address),
238 );
239 self.jump_to(address as usize, true);
240 } else if let Some(address) = self
241 .header
242 .get_sections()
243 .iter()
244 .find(|x| x.name == symbol)
245 .map(|x| x.file_offset)
246 {
247 self.log(
248 NotificationLevel::Debug,
249 &format!("Jumping to section {} at {:#X}", symbol, address),
250 );
251 self.jump_to(address as usize, false);
252 } else {
253 self.log(
254 NotificationLevel::Error,
255 &format!("Symbol not found: {}", symbol),
256 );
257 }
258 }
259
260 #[allow(clippy::too_many_arguments)]
261 pub(super) fn jump_to_no_self(
262 mut address: usize,
263 data: &Data,
264 screen_size: (u16, u16),
265 vertical_margin: u16,
266 scroll: &mut usize,
267 cursor: &mut (u16, u16),
268 block_size: usize,
269 blocks_per_row: usize,
270 ) {
271 if address >= data.len() {
272 address = data.len().saturating_sub(1);
273 }
274 if screen_size.1 <= vertical_margin {
275 *scroll = 0;
276 *cursor = (0, 0);
277 return;
278 }
279
280 let expected_cursor_position = Self::get_expected_cursor_position_no_self(
281 address,
282 false,
283 block_size,
284 blocks_per_row,
285 screen_size,
286 vertical_margin,
287 *scroll,
288 );
289 let CursorPosition {
290 local_x,
291 line_index,
292 ..
293 } = expected_cursor_position;
294 let y = line_index as isize - *scroll as isize;
295
296 if y < 0 {
297 *scroll = line_index;
298 *cursor = (local_x as u16, 0);
299 } else if y < screen_size.1 as isize - vertical_margin as isize {
300 *cursor = (local_x as u16, y as u16);
301 } else {
302 *scroll = line_index - (screen_size.1 - vertical_margin - 1) as usize;
303 *cursor = (local_x as u16, (screen_size.1 - vertical_margin - 1));
304 }
305 }
306
307 pub(super) fn jump_to(&mut self, mut address: usize, is_virtual: bool) {
308 if is_virtual {
309 if let Some(physical_address) = self.header.virtual_to_physical_address(address as u64)
310 {
311 address = physical_address as usize;
312 } else {
313 self.log(
314 NotificationLevel::Error,
315 &format!("Virtual address {:#X} not found", address),
316 );
317 return;
318 }
319 }
320 Self::jump_to_no_self(
321 address,
322 &self.data,
323 self.screen_size,
324 self.vertical_margin,
325 &mut self.scroll,
326 &mut self.cursor,
327 self.block_size,
328 self.blocks_per_row,
329 )
330 }
331
332 pub(super) fn move_cursor_in_selected_panel(&mut self, dx: isize, dy: isize) {
333 match self.selected_pane {
334 Pane::Hex => self.move_cursor(dx, dy, false),
335 Pane::View => match self.info_mode {
336 InfoMode::Text => self.move_cursor(dx * 2, dy, false),
337 InfoMode::Assembly => self.move_cursor_to_near_instruction(dy),
338 },
339 }
340 }
341
342 pub(super) fn move_cursor(&mut self, dx: isize, dy: isize, best_effort: bool) {
343 if self.screen_size.1 <= self.vertical_margin {
344 return;
345 }
346 let current_position = self.get_cursor_position();
347 let half_byte_delta =
348 dx + (dy * self.block_size as isize * self.blocks_per_row as isize * 2);
349 let half_byte_position =
350 current_position.global_byte_index * 2 + if current_position.high_byte { 0 } else { 1 };
351
352 let mut new_half_byte_position =
353 (half_byte_position as isize).saturating_add(half_byte_delta);
354 if !best_effort
355 && (new_half_byte_position < 0
356 || new_half_byte_position >= self.data.len() as isize * 2)
357 {
358 return;
359 } else if best_effort {
360 let max_half_byte_position = (self.data.len() as isize * 2 - 1).max(0);
361 new_half_byte_position = new_half_byte_position.clamp(0, max_half_byte_position);
362 }
363 let new_global_byte_index = new_half_byte_position as usize / 2;
364 let new_high_byte = new_half_byte_position % 2 == 0;
365
366 let new_selected_row = new_global_byte_index / (self.block_size * self.blocks_per_row);
367 let min_visible_row = self.scroll;
368 let max_visible_row =
369 self.scroll + (self.screen_size.1 - self.vertical_margin) as usize - 1;
370 let new_scroll = if new_selected_row < min_visible_row {
371 new_selected_row
372 } else if new_selected_row > max_visible_row {
373 new_selected_row - (self.screen_size.1 - self.vertical_margin) as usize + 1
374 } else {
375 self.scroll
376 };
377
378 self.scroll = new_scroll;
379
380 self.cursor = self
381 .get_expected_cursor_position(new_global_byte_index, new_high_byte)
382 .cursor
383 .expect("The scroll should be adequate for the cursor to be visible");
384 }
385
386 pub(super) fn move_cursor_page_up(&mut self) {
387 let screen_size_y = self.screen_size.1 as isize - self.vertical_margin as isize;
388 if screen_size_y > 0 {
389 self.move_cursor(0, -screen_size_y, true);
390 }
391 }
392
393 pub(super) fn move_cursor_page_down(&mut self) {
394 let screen_size_y = self.screen_size.1 as isize - self.vertical_margin as isize;
395 if screen_size_y > 0 {
396 self.move_cursor(0, screen_size_y, true);
397 }
398 }
399
400 pub(super) fn move_cursor_to_end(&mut self) {
401 let bytes = self.data.len();
402
403 self.move_cursor(bytes as isize * 2, 0, true);
404 }
405
406 pub(super) fn move_cursor_to_start(&mut self) {
407 let bytes = self.data.len();
408 self.move_cursor(bytes as isize * -2, 0, true);
409 }
410
411 pub(super) fn move_cursor_to_near_instruction(&mut self, instruction_count: isize) {
412 let current_offset = self.get_cursor_position().global_byte_index;
413 if current_offset >= self.assembly_offsets.len() {
414 return;
415 }
416 let current_instruction_index = self.assembly_offsets[current_offset];
417 let mut next_instruction_index = (current_instruction_index as isize + instruction_count)
418 .clamp(
419 0,
420 self.assembly_instructions.len().saturating_sub(1) as isize,
421 ) as usize;
422 while instruction_count < 0
423 && next_instruction_index != 0
424 && next_instruction_index != current_instruction_index
425 && self.assembly_instructions[next_instruction_index].file_address()
426 == self.assembly_instructions[current_instruction_index].file_address()
427 {
428 next_instruction_index = next_instruction_index.saturating_sub(1);
429 }
430
431 let target_address = self.assembly_instructions[next_instruction_index].file_address();
432 self.jump_to(target_address as usize, false);
433 }
434
435 pub(super) fn switch_selected_pane(&mut self) {
436 match self.selected_pane {
437 Pane::Hex => self.selected_pane = Pane::View,
438 Pane::View => self.selected_pane = Pane::Hex,
439 }
440 }
441}
442
443#[cfg(test)]
444mod test {
445 use super::*;
446 #[test]
447 fn test_move_cursor() {
448 let data = vec![0; 0x100];
449 let mut app = App::mockup(data);
450 app.resize_to_size(80, 24);
451
452 app.move_cursor(1, 0, false);
453 assert_eq!(app.cursor, (1, 0));
454 app.move_cursor(0, 1, false);
455 assert_eq!(app.cursor, (1, 1));
456
457 app.move_cursor(0, 0, false);
458 assert_eq!(app.cursor, (1, 1));
459
460 app.move_cursor(0, -1, false);
461 assert_eq!(app.cursor, (1, 0));
462 app.move_cursor(-1, 0, false);
463 assert_eq!(app.cursor, (0, 0));
464
465 app.move_cursor(0, -1, false);
466 assert_eq!(app.cursor, (0, 0));
467 app.move_cursor(-1, 0, false);
468 assert_eq!(app.cursor, (0, 0));
469
470 let current_position = app.get_cursor_position();
471 assert_eq!(current_position.global_byte_index, 0);
472 assert!(current_position.high_byte);
473
474 app.move_cursor(81, 0, false);
475 let current_position = app.get_cursor_position();
476 assert_eq!(current_position.global_byte_index, 40);
477 assert!(!current_position.high_byte);
478
479 app.move_cursor(-1, -1, false);
480 let bytes_per_line = app.block_size * app.blocks_per_row;
481 let current_position = app.get_cursor_position();
482 assert_eq!(current_position.global_byte_index, 40 - bytes_per_line);
483 assert!(current_position.high_byte);
484 }
485
486 #[test]
487 fn test_move_with_no_screen() {
488 let data = vec![0; 0x100];
489 let mut app = App::mockup(data);
490 app.resize_to_size(0, 0);
491
492 app.move_cursor(1, 0, false);
493 assert_eq!(app.cursor, (0, 0));
494 app.move_cursor(0, 1, false);
495 assert_eq!(app.cursor, (0, 0));
496 app.move_cursor(0, -1, false);
497 assert_eq!(app.cursor, (0, 0));
498 app.move_cursor(-1, 0, false);
499 assert_eq!(app.cursor, (0, 0));
500 }
501
502 #[test]
503 fn test_move_with_small_screen() {
504 let data = vec![0; 0x100];
505 let mut app = App::mockup(data);
506 app.resize_to_size(1, 1);
507
508 app.move_cursor(1, 0, false);
509 assert_eq!(app.cursor, (0, 0));
510 app.move_cursor(0, 1, false);
511 assert_eq!(app.cursor, (0, 0));
512 app.move_cursor(0, -1, false);
513 assert_eq!(app.cursor, (0, 0));
514 app.move_cursor(-1, 0, false);
515 assert_eq!(app.cursor, (0, 0));
516 }
517
518 #[test]
519 fn test_move_to_near_instruction() {
520 let data = vec![
521 0x90, 0x90, 0x90, 0x48, 0x89, 0xd8, 0xeb, 0xfe, 0x90, 0x90, 0x90,
522 ];
523 let mut app = App::mockup(data);
524 app.resize_to_size(80, 24);
525
526 app.move_cursor_to_near_instruction(1);
527 let current_position = app.get_cursor_position().global_byte_index;
528 assert_eq!(current_position, 1);
529
530 app.move_cursor_to_near_instruction(2);
531 let current_position = app.get_cursor_position().global_byte_index;
532 assert_eq!(current_position, 3);
533
534 app.move_cursor_to_near_instruction(-1);
535 let current_position = app.get_cursor_position().global_byte_index;
536 assert_eq!(current_position, 2);
537
538 app.move_cursor_to_near_instruction(4);
539 let current_position = app.get_cursor_position().global_byte_index;
540 assert_eq!(current_position, 9);
541
542 app.move_cursor_to_near_instruction(2);
543 let current_position = app.get_cursor_position().global_byte_index;
544 assert_eq!(current_position, 10);
545
546 app.move_cursor_to_near_instruction(100);
547 let current_position = app.get_cursor_position().global_byte_index;
548 assert_eq!(current_position, 10);
549
550 app.move_cursor_to_near_instruction(-100);
551 let current_position = app.get_cursor_position().global_byte_index;
552 assert_eq!(current_position, 0);
553 }
554}