1pub mod app;
2pub mod ui;
3pub mod vec_with_index;
4
5use std::{io, time::Duration};
6
7use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers};
8use planus_buffer_inspection::{
9 object_info::ObjectName,
10 object_mapping::{Interpretation, Line, ObjectIndex, ObjectMapping},
11 InspectableFlatbuffer, Object,
12};
13use planus_types::intermediate::DeclarationIndex;
14use ratatui::{backend::Backend, layout::Rect, Terminal};
15
16use crate::{
17 ui::{FoldState, Node, TreeState, TreeStateLine},
18 vec_with_index::VecWithIndex,
19};
20
21#[derive(Copy, Clone, Debug)]
22pub enum ActiveWindow {
23 ObjectView,
24 HexView,
25}
26
27#[derive(Debug)]
28pub enum ModalState<'a> {
29 GoToByte {
30 input: String,
31 },
32 XRefs {
33 xrefs: VecWithIndex<String>,
34 },
35 ViewHistory {
36 index: usize,
37 },
38 HelpMenu,
39 TreeView {
40 header: &'static str,
41 state: TreeState<'a>,
42 },
43}
44
45pub struct Inspector<'a> {
46 pub object_mapping: ObjectMapping<'a>,
47 pub buffer: InspectableFlatbuffer<'a>,
48 pub should_quit: bool,
49 pub view_stack: Vec<ViewState<'a>>,
50 pub active_window: ActiveWindow,
51 pub view_state: ViewState<'a>,
52 pub hex_view_state: HexViewState,
53 pub modal: Option<ModalState<'a>>,
54}
55
56pub struct HexViewState {
57 pub line_size: usize,
58 pub line_pos: usize,
59 pub max_offset_hex_digits: usize,
60}
61
62impl HexViewState {
63 pub fn new(buffer_size: usize) -> Self {
64 let max_offset = buffer_size.max(1) as u64;
65 let max_offset_ilog2 = 63 - max_offset.leading_zeros();
66 let max_offset_hex_digits = max_offset_ilog2 / 4 + 1;
67 let max_offset_hex_digits = (max_offset_hex_digits + (max_offset_hex_digits & 1)) as usize;
68 Self {
69 line_size: 0,
70 line_pos: 0,
71 max_offset_hex_digits,
72 }
73 }
74
75 pub fn update_for_area(&mut self, buffer_len: usize, byte_index: usize, area: Rect) {
76 let remaining_width = area.width as usize - self.max_offset_hex_digits - 2;
77 let eight_groups = (remaining_width + 1) / 25;
78 self.line_size = eight_groups * 8;
79
80 if area.height != 0 && self.line_size != 0 {
81 let cursor_line = byte_index / self.line_size;
82 let mut first_line = self.line_pos / self.line_size;
83 let max_line_index = (buffer_len - 1) / self.line_size;
84
85 if area.height <= 4 {
86 first_line = first_line.clamp(
87 (cursor_line + 1).saturating_sub(area.height as usize),
88 cursor_line,
89 );
90 } else {
91 first_line = first_line.clamp(
92 (cursor_line + 2).saturating_sub(area.height as usize),
93 cursor_line.saturating_sub(1),
94 );
95 }
96 first_line = first_line.min((max_line_index + 1).saturating_sub(area.height as usize));
97 self.line_pos = first_line * self.line_size;
98 }
99 }
100}
101
102pub struct Ranges {
103 pub inner_range: Option<std::ops::Range<usize>>,
104 pub outer_range: Option<std::ops::Range<usize>>,
105}
106
107#[derive(PartialEq, Eq, PartialOrd, Ord)]
108pub enum RangeMatch {
109 Inner,
110 Outer,
111}
112
113impl Ranges {
114 pub fn best_match(&self, value: usize) -> Option<RangeMatch> {
115 if let Some(inner_range) = &self.inner_range {
116 if inner_range.contains(&value) {
117 return Some(RangeMatch::Inner);
118 }
119 }
120
121 if let Some(outer_range) = &self.outer_range {
122 if outer_range.contains(&value) {
123 return Some(RangeMatch::Outer);
124 }
125 }
126
127 None
128 }
129}
130
131#[derive(Clone, Debug)]
132pub struct ViewState<'a> {
133 pub byte_index: usize,
134 pub info_view_data: Option<InfoViewData<'a>>,
135}
136
137#[derive(Clone, Debug)]
138pub struct InfoViewData<'a> {
139 pub first_line_shown: usize,
140 pub lines: VecWithIndex<Line<'a>>,
141 pub interpretations: VecWithIndex<Interpretation>,
142 pub root_object_index: ObjectIndex,
143}
144
145impl<'a> InfoViewData<'a> {
146 fn new_from_root_object(
147 object_mapping: &ObjectMapping<'a>,
148 buffer: &InspectableFlatbuffer<'a>,
149 object: Object<'a>,
150 ) -> Self {
151 let root_object_index = object_mapping
152 .root_objects
153 .get_index_of(&object)
154 .unwrap_or_else(|| panic!("{object:?}, {:?}", object_mapping.root_objects));
155 let mut interpretations = Vec::new();
156 let mut interpretation_index = None;
157 object_mapping.get_interpretations_cb(object.offset(), buffer, |interpretation| {
158 if interpretation.root_object_index == root_object_index {
159 interpretation_index.get_or_insert(interpretations.len());
160 }
161 interpretations.push(interpretation);
162 });
163 Self {
164 first_line_shown: 0,
165 lines: VecWithIndex::new(
166 object_mapping
167 .line_tree(root_object_index, buffer)
168 .flatten(buffer),
169 0,
170 ),
171 root_object_index,
172 interpretations: VecWithIndex::new(interpretations, interpretation_index.unwrap()),
173 }
174 }
175
176 fn new_from_byte_index<Score: Ord>(
177 object_mapping: &ObjectMapping<'a>,
178 buffer: &InspectableFlatbuffer<'a>,
179 byte_index: usize,
180 interpretation_score: impl Fn(&Interpretation) -> Score,
181 ) -> Option<Self> {
182 let interpretations = object_mapping.get_interpretations(byte_index as u32, buffer);
183 if interpretations.is_empty() {
184 return None;
185 }
186
187 let (interpretation_index, interpretation) = interpretations
188 .iter()
189 .enumerate()
190 .min_by_key(|(i, interpretation)| (interpretation_score(interpretation), *i))
191 .unwrap();
192 let root_object_index = interpretation.root_object_index;
193 let line_index = *interpretation.lines.last().unwrap();
194 Some(Self {
195 first_line_shown: 0,
196 lines: VecWithIndex::new(
197 object_mapping
198 .line_tree(root_object_index, buffer)
199 .flatten(buffer),
200 line_index,
201 ),
202 interpretations: VecWithIndex::new(interpretations, interpretation_index),
203 root_object_index,
204 })
205 }
206
207 fn set_interpretation_index(
208 &mut self,
209 object_mapping: &ObjectMapping<'a>,
210 buffer: &InspectableFlatbuffer<'a>,
211 interpretation_index: usize,
212 ) {
213 if self.interpretations.try_set_index(interpretation_index) {
214 self.root_object_index = self.interpretations.cur().root_object_index;
215 self.lines = VecWithIndex::new(
216 object_mapping
217 .line_tree(self.root_object_index, buffer)
218 .flatten(buffer),
219 *self.interpretations.cur().lines.last().unwrap(),
220 );
221 }
222 }
223
224 fn set_line_pos(
226 &mut self,
227 object_mapping: &ObjectMapping<'a>,
228 buffer: &InspectableFlatbuffer<'a>,
229 line_index: usize,
230 ) -> Option<usize> {
231 if self.lines.try_set_index(line_index) {
232 let byte_index = self.lines.cur().start as usize;
233 let start_line_index = self.lines.cur().start_line_index;
234 let interpretations = object_mapping.get_interpretations(byte_index as u32, buffer);
235 let interpretation_index = interpretations
236 .iter()
237 .position(|interpretation| {
238 interpretation.root_object_index == self.root_object_index
239 && interpretation.lines.contains(&start_line_index)
240 })
241 .unwrap();
242 self.interpretations = VecWithIndex::new(interpretations, interpretation_index);
243 Some(byte_index)
244 } else {
245 None
246 }
247 }
248}
249
250impl<'a> ViewState<'a> {
251 fn new_for_object(
252 object_mapping: &ObjectMapping<'a>,
253 buffer: &InspectableFlatbuffer<'a>,
254 object: Object<'a>,
255 ) -> Self {
256 Self {
257 byte_index: object.offset() as usize,
258 info_view_data: Some(InfoViewData::new_from_root_object(
259 object_mapping,
260 buffer,
261 object,
262 )),
263 }
264 }
265
266 fn set_byte_pos(
267 &mut self,
268 object_mapping: &ObjectMapping<'a>,
269 buffer: &InspectableFlatbuffer<'a>,
270 byte_index: usize,
271 ) {
272 if self.byte_index != byte_index {
273 self.byte_index = byte_index;
274
275 let current_root_object_index =
276 self.info_view_data.as_ref().map(|d| d.root_object_index);
277 let current_line_index = self.info_view_data.as_ref().map_or(0, |d| d.lines.index());
278 self.info_view_data = InfoViewData::new_from_byte_index(
279 object_mapping,
280 buffer,
281 byte_index,
282 |interpretation| {
283 if Some(interpretation.root_object_index) == current_root_object_index {
284 (
285 0,
286 interpretation
287 .lines
288 .iter()
289 .map(|line| line.abs_diff(current_line_index))
290 .min()
291 .unwrap_or(usize::MAX),
292 )
293 } else {
294 (1, 0)
295 }
296 },
297 );
298 }
299 }
300
301 pub fn set_line_pos(
302 &mut self,
303 object_mapping: &ObjectMapping<'a>,
304 buffer: &InspectableFlatbuffer<'a>,
305 line_index: usize,
306 ) {
307 if let Some(info_view_date) = &mut self.info_view_data {
308 if let Some(byte_index) =
309 info_view_date.set_line_pos(object_mapping, buffer, line_index)
310 {
311 self.byte_index = byte_index;
312 }
313 }
314 }
315
316 pub fn hex_ranges(&self) -> Ranges {
317 Ranges {
318 inner_range: self
319 .info_view_data
320 .as_ref()
321 .map(|d| d.lines.cur().start as usize..d.lines.cur().end as usize),
322 outer_range: self.info_view_data.as_ref().and_then(|d| {
323 d.lines
324 .first()
325 .map(|line| line.start as usize..line.end as usize)
326 }),
327 }
328 }
329}
330
331impl<'a> Inspector<'a> {
332 pub fn new(buffer: InspectableFlatbuffer<'a>, root_table_index: DeclarationIndex) -> Self {
333 let object_mapping = buffer.calculate_object_mapping(root_table_index);
334 Self {
335 buffer,
336 view_state: ViewState::new_for_object(
337 &object_mapping,
338 &buffer,
339 object_mapping.root_object,
340 ),
341 object_mapping,
342 should_quit: false,
343 view_stack: Vec::new(),
344 active_window: ActiveWindow::ObjectView,
345 hex_view_state: HexViewState::new(buffer.buffer.len()),
346 modal: None,
347 }
348 }
349
350 pub fn on_key(&mut self, key: KeyEvent) -> bool {
351 let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
352 let mut should_draw = match key.code {
353 KeyCode::Tab => match self.active_window {
354 ActiveWindow::HexView => {
355 if self.view_state.info_view_data.is_some() {
356 self.active_window = ActiveWindow::ObjectView;
357 true
358 } else {
359 false
360 }
361 }
362 ActiveWindow::ObjectView => {
363 self.active_window = ActiveWindow::HexView;
364 self.view_state.set_byte_pos(
365 &self.object_mapping,
366 &self.buffer,
367 self.view_state.byte_index,
368 );
369 true
370 }
371 },
372 KeyCode::Char('g') => {
373 self.toggle_modal(ModalState::GoToByte {
374 input: String::new(),
375 });
376 true
377 }
378 KeyCode::Char('x') => {
379 true
384 }
385 KeyCode::Char('h') => {
386 self.toggle_modal(ModalState::ViewHistory {
387 index: self.view_stack.len(),
388 });
389 true
390 }
391 KeyCode::Char('i') => {
392 if let Some(info_view_data) = &self.view_state.info_view_data {
393 let state = TreeState {
394 lines: VecWithIndex::new(
395 info_view_data
396 .interpretations
397 .iter()
398 .enumerate()
399 .map(|(i, interpretation)| {
400 let (object, _) = self
401 .object_mapping
402 .root_objects
403 .get_index(interpretation.root_object_index)
404 .unwrap();
405 let mut view_state = self.view_state.clone();
406 view_state
407 .info_view_data
408 .as_mut()
409 .unwrap()
410 .set_interpretation_index(
411 &self.object_mapping,
412 &self.buffer,
413 i,
414 );
415 TreeStateLine {
416 indent_level: 0,
417 node: Node {
418 text: object.print_object(&self.buffer),
419 view_state: Some(view_state),
420 children: None,
421 },
422 fold_state: FoldState::NoChildren,
423 }
424 })
425 .collect(),
426 info_view_data.interpretations.index(),
427 ),
428 };
429 self.toggle_modal(ModalState::TreeView {
430 header: " Interpretations ",
431 state,
432 });
433
434 true
435 } else {
436 false
437 }
438 }
439 KeyCode::Char('?') => {
440 self.toggle_modal(ModalState::HelpMenu);
441 true
442 }
443 KeyCode::Char('q') => {
444 self.should_quit = true;
445 false
446 }
447 KeyCode::Char('c') if ctrl => {
448 self.should_quit = true;
449 false
450 }
451 KeyCode::Char('c') => {
452 if let Some(info_view_data) = &mut self.view_state.info_view_data {
453 info_view_data.set_interpretation_index(
454 &self.object_mapping,
455 &self.buffer,
456 (info_view_data.interpretations.index() + 1)
457 % info_view_data.interpretations.len(),
458 );
459 true
460 } else {
461 false
462 }
463 }
464 KeyCode::Enter if self.modal.is_none() => {
465 if let Some(info_view_data) = &mut self.view_state.info_view_data {
466 if let Object::Offset(offset_object) = &info_view_data.lines.cur().object {
467 if let Ok(inner) = offset_object.follow_offset(&self.buffer) {
468 let old_view_state = std::mem::replace(
469 &mut self.view_state,
470 ViewState::new_for_object(
471 &self.object_mapping,
472 &self.buffer,
473 inner,
474 ),
475 );
476 self.view_stack.push(old_view_state);
477 }
478 }
479 }
480 true
481 }
482 KeyCode::Esc if self.modal.is_none() => {
483 if let Some(view_state) = self.view_stack.pop() {
484 self.view_state = view_state;
485 true
486 } else {
487 self.should_quit = true;
488 false
489 }
490 }
491 KeyCode::Backspace if self.modal.is_none() => {
492 if let Some(view_state) = self.view_stack.pop() {
493 self.view_state = view_state;
494 true
495 } else {
496 false
497 }
498 }
499 _ => false,
500 };
501
502 if let Some(modal_state) = self.modal.take() {
503 self.modal = self.modal_view_key(key, modal_state);
504 should_draw = true;
505 }
506
507 should_draw = should_draw
508 || match self.active_window {
509 ActiveWindow::ObjectView => self.object_view_key(key),
510 ActiveWindow::HexView => self.hex_view_key(key),
511 };
512
513 should_draw
514 }
515
516 pub fn hex_view_key(&mut self, key: KeyEvent) -> bool {
517 let mut current_byte = self.view_state.byte_index;
518 let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
519 match key.code {
520 KeyCode::Up => {
522 if let Some(next) = current_byte.checked_sub(self.hex_view_state.line_size) {
523 current_byte = next;
524 }
525 }
526 KeyCode::Down => {
527 let next = current_byte.saturating_add(self.hex_view_state.line_size);
528 if next < self.buffer.buffer.len() {
529 current_byte = next;
530 }
531 }
532 KeyCode::PageUp => {
533 current_byte = current_byte.saturating_sub(8 * self.hex_view_state.line_size);
534 }
535 KeyCode::PageDown => {
536 current_byte = self
537 .view_state
538 .byte_index
539 .saturating_add(8 * self.hex_view_state.line_size);
540 }
541 KeyCode::Left if ctrl => {
542 if let Some(range) = self.view_state.hex_ranges().inner_range {
543 current_byte = range.start.saturating_sub(1);
544 } else {
545 current_byte = current_byte.saturating_sub(1);
546 }
547 }
548 KeyCode::Left => {
549 current_byte = current_byte.saturating_sub(1);
550 }
551 KeyCode::Right if ctrl => {
552 if let Some(range) = self.view_state.hex_ranges().inner_range {
553 current_byte = range.end;
554 } else {
555 current_byte = current_byte.saturating_add(1);
556 }
557 }
558 KeyCode::Right => {
559 current_byte = current_byte.saturating_add(1);
560 }
561 KeyCode::Home => {
562 current_byte = 0;
563 }
564 KeyCode::End => {
565 current_byte = self.buffer.buffer.len() - 1;
566 }
567 _ => (),
568 };
569 self.update_byte_pos(current_byte)
570 }
571
572 fn object_view_key(&mut self, key: KeyEvent) -> bool {
573 let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
574 if let Some(info_view_data) = &mut self.view_state.info_view_data {
575 let line_step = 5;
576 let cur_index = info_view_data.lines.index();
577 let last_index = info_view_data.lines.len() - 1;
578 let line = match key.code {
579 KeyCode::Left => info_view_data.lines.cur().parent_line_index,
580 KeyCode::Right => {
581 let cur = info_view_data.lines.cur();
582 if cur.start_line_index != cur.end_line_index {
583 cur.start_line_index + 1
584 } else {
585 return false;
586 }
587 }
588 KeyCode::Up if ctrl => {
589 let start_line_index = info_view_data.lines.cur().start_line_index;
590 if start_line_index != cur_index {
591 start_line_index
592 } else {
593 cur_index.saturating_sub(1)
594 }
595 }
596 KeyCode::Up => cur_index.saturating_sub(1),
597 KeyCode::PageUp => cur_index.saturating_sub(line_step),
598 KeyCode::Down if ctrl => {
599 let end_line_index = info_view_data.lines.cur().end_line_index;
600 if end_line_index != cur_index {
601 end_line_index
602 } else {
603 cur_index.saturating_add(1)
604 }
605 }
606 KeyCode::Down => cur_index + 1,
607 KeyCode::PageDown => last_index.min(cur_index + line_step),
608 KeyCode::Home => 0,
609 KeyCode::End => last_index,
610 _ => return false,
611 };
612 self.view_state
613 .set_line_pos(&self.object_mapping, &self.buffer, line);
614 }
615 true
616 }
617
618 fn modal_view_key(
619 &mut self,
620 key: KeyEvent,
621 mut modal_state: ModalState<'a>,
622 ) -> Option<ModalState<'a>> {
623 if let KeyCode::Esc = key.code {
624 return None;
625 }
626
627 match &mut modal_state {
628 ModalState::GoToByte { input } => match key.code {
629 KeyCode::Char(c @ '0'..='9')
630 | KeyCode::Char(c @ 'a'..='f')
631 | KeyCode::Char(c @ 'A'..='F') => {
632 if input.len() < 16 {
633 input.push(c.to_ascii_lowercase());
634 }
635 }
636 KeyCode::Enter => {
637 let addr = usize::from_str_radix(input, 16).unwrap();
638 self.view_stack.push(self.view_state.clone());
639 self.update_byte_pos(addr);
640 self.active_window = ActiveWindow::HexView;
641 return None;
642 }
643 KeyCode::Backspace => {
644 input.pop();
645 }
646 _ => (),
647 },
648 ModalState::XRefs { .. } => (),
649 ModalState::ViewHistory { index } => match key.code {
650 KeyCode::Up => {
651 *index = index.saturating_sub(1);
652 }
653 KeyCode::Down => {
654 *index = index.saturating_add(1).min(self.view_stack.len());
655 }
656 KeyCode::Enter => {
657 if *index < self.view_stack.len() {
658 self.view_stack.truncate(*index + 1);
659 self.view_state = self.view_stack.pop().unwrap();
660 return None;
661 } else {
662 return None;
664 }
665 }
666 _ => (),
667 },
668 ModalState::HelpMenu => (),
669 ModalState::TreeView { state, .. } => match key.code {
670 KeyCode::Up => {
671 state
672 .lines
673 .try_set_index(state.lines.index().saturating_sub(1));
674 }
675 KeyCode::Down => {
676 state
677 .lines
678 .try_set_index(state.lines.index().saturating_add(1));
679 }
680 KeyCode::Right => state.toggle_fold(),
681 KeyCode::Enter => {
682 let line = state.lines.cur();
683 if let Some(view_state) = &line.node.view_state {
684 let old_view_state =
685 std::mem::replace(&mut self.view_state, view_state.clone());
686 self.view_stack.push(old_view_state);
687 return None;
688 }
689 }
690 _ => (),
691 },
692 }
693
694 Some(modal_state)
695 }
696
697 fn toggle_modal(&mut self, modal: ModalState<'a>) {
698 if self.modal.as_ref().map(std::mem::discriminant) == Some(std::mem::discriminant(&modal)) {
699 self.modal = None;
700 } else {
701 self.modal = Some(modal);
702 }
703 }
704
705 fn update_byte_pos(&mut self, current_byte: usize) -> bool {
706 let current_byte = current_byte.min(self.buffer.buffer.len() - 1);
707 if current_byte != self.view_state.byte_index {
708 self.view_state
709 .set_byte_pos(&self.object_mapping, &self.buffer, current_byte);
710
711 true
712 } else {
713 false
714 }
715 }
716}
717
718pub fn run_inspector<B: Backend<Error = std::io::Error>>(
719 terminal: &mut Terminal<B>,
720 mut inspector: Inspector,
721) -> io::Result<()> {
722 let mut should_draw = true;
723 loop {
724 if should_draw {
725 terminal.draw(|f| ui::draw(f, &mut inspector))?;
726 should_draw = false;
727 }
728
729 if crossterm::event::poll(Duration::from_secs(1))? {
730 match event::read()? {
731 Event::Key(key) => {
732 should_draw = inspector.on_key(key);
733 }
734 Event::Resize(_, _) => should_draw = true,
735 _ => (),
736 }
737 }
738 if inspector.should_quit {
739 return Ok(());
740 }
741 }
742}