tracexec_tui/
backtrace_popup.rs

1use std::{
2  cell::RefCell,
3  collections::VecDeque,
4  rc::Rc,
5  sync::LazyLock,
6};
7
8use crossterm::event::{
9  KeyCode,
10  KeyEvent,
11};
12use ratatui::{
13  buffer::Buffer,
14  layout::{
15    Alignment,
16    Rect,
17  },
18  style::Styled,
19  text::Line,
20  widgets::{
21    Block,
22    Borders,
23    Clear,
24    StatefulWidgetRef,
25    Widget,
26  },
27};
28use tracexec_core::{
29  event::{
30    ParentEventId,
31    TracerEventDetails,
32  },
33  primitives::local_chan::LocalUnboundedSender,
34};
35use tracing::debug;
36
37use super::{
38  event_list::{
39    Event,
40    EventList,
41  },
42  theme::THEME,
43};
44use crate::action::Action;
45
46pub struct BacktracePopup;
47
48#[derive(Debug)]
49pub struct BacktracePopupState {
50  pub(super) list: EventList,
51  /// Whether there are dead events no longer in memory or not
52  event_loss: bool,
53  should_resize: bool,
54}
55
56type ParentAndEventQueue = VecDeque<(Option<ParentEventId>, Rc<RefCell<Event>>)>;
57
58impl BacktracePopupState {
59  pub fn new(event: Rc<RefCell<Event>>, old_list: &EventList) -> Self {
60    let (trace, event_loss) = Self::collect_backtrace(event, old_list);
61    let mut list = EventList::new(
62      old_list.baseline.clone(),
63      false,
64      old_list.modifier_args.clone(),
65      u64::MAX,
66      false,
67      old_list.has_clipboard,
68      false,
69    );
70    list.rt_modifier = old_list.rt_modifier;
71    for (p, e) in trace {
72      list.dumb_push(
73        e,
74        match p {
75          Some(ParentEventId::Become(_)) => Some(THEME.backtrace_parent_becomes.clone()),
76          Some(ParentEventId::Spawn(_)) => Some(THEME.backtrace_parent_spawns.clone()),
77          None => Some(THEME.backtrace_parent_unknown.clone()),
78        },
79      );
80    }
81    Self {
82      list,
83      event_loss,
84      should_resize: true,
85    }
86  }
87
88  /// Collect the backtrace and whether
89  fn collect_backtrace(event: Rc<RefCell<Event>>, list: &EventList) -> (ParentAndEventQueue, bool) {
90    let mut trace = VecDeque::new();
91    let mut event = event;
92    let event_loss = loop {
93      let e = event.borrow();
94      let TracerEventDetails::Exec(exec) = e.details.as_ref() else {
95        panic!("back trace should only contain exec event")
96      };
97      let parent = exec.parent;
98      drop(e);
99      debug!("backtracing -- {event:?}");
100      trace.push_front((parent, event));
101      if let Some(parent) = parent {
102        let eid = parent.into();
103        if let Some(e) = list.get(eid) {
104          event = e;
105        } else {
106          break true;
107        }
108      } else {
109        break false;
110      }
111    };
112    (trace, event_loss)
113  }
114}
115
116static HELP: LazyLock<Line<'static>> = LazyLock::new(|| {
117  Line::from(vec![
118    "Legend: ".into(),
119    THEME.backtrace_parent_becomes.clone(),
120    " Becomes ".set_style(THEME.cli_flag),
121    THEME.backtrace_parent_spawns.clone(),
122    " Spawns ".set_style(THEME.cli_flag),
123  ])
124});
125
126impl StatefulWidgetRef for BacktracePopup {
127  type State = BacktracePopupState;
128
129  fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
130    Clear.render(area, buf);
131    let screen = buf.area;
132    let help_width = HELP.width() as u16;
133    let start = screen.right().saturating_sub(help_width);
134    (&*HELP).render(Rect::new(start, 0, help_width, 1), buf);
135    let block = Block::new()
136      .title(if !state.event_loss {
137        " Backtrace "
138      } else {
139        " Backtrace (incomplete) "
140      })
141      .borders(Borders::TOP | Borders::BOTTOM)
142      .title_alignment(Alignment::Center);
143    let inner = block.inner(area);
144    block.render(area, buf);
145    if state.should_resize {
146      state.should_resize = false;
147      state.list.max_window_len = inner.height as usize - 2;
148      state.list.set_window((
149        state.list.get_window().0,
150        state.list.get_window().0 + state.list.max_window_len,
151      ));
152    }
153    state.list.render(inner, buf);
154  }
155}
156
157impl BacktracePopupState {
158  pub async fn handle_key_event(
159    &self,
160    ke: KeyEvent,
161    action_tx: &LocalUnboundedSender<Action>,
162  ) -> color_eyre::Result<()> {
163    if ke.code == KeyCode::Char('q') {
164      action_tx.send(Action::CancelCurrentPopup)
165    } else {
166      self.list.handle_key_event(ke, action_tx).await?
167    }
168    Ok(())
169  }
170}