tracexec_tui/
backtrace_popup.rs1use 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 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 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}