Skip to main content

planus_inspector/
lib.rs

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    /// If anything was changed, then the new byte index is returned
225    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                // TODO: implement
380                // self.toggle_modal(ModalState::XRefs {
381                //     xrefs: VecWithIndex::new(vec!["lol".to_owned()], 0),
382                // });
383                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            // Navigation
521            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                        // The last element in the list is current view state, so we do nothing
663                        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}