1use std::borrow::Cow;
2
3use ratatui::{
4 layout::Rect,
5 style::{
6 Styled,
7 Stylize,
8 },
9 text::{
10 Line,
11 Span,
12 Text,
13 },
14 widgets::{
15 Paragraph,
16 Wrap,
17 },
18};
19use tracexec_core::event::EventStatus;
20
21use super::{
22 sized_paragraph::SizedParagraph,
23 theme::THEME,
24};
25
26pub fn cli_flag<'a, T>(f: T) -> Span<'a>
27where
28 T: Into<Cow<'a, str>> + Styled<Item = Span<'a>>,
29{
30 f.set_style(THEME.cli_flag)
31}
32
33pub fn help_key<'a, T>(k: T) -> Span<'a>
34where
35 T: Into<Cow<'a, str>> + Styled<Item = Span<'a>>,
36{
37 let mut key_string = String::from("\u{00a0}");
38 key_string.push_str(&k.into());
39 key_string.push('\u{00a0}');
40 key_string.set_style(THEME.help_key)
41}
42pub fn help_desc<'a, T>(d: T) -> Span<'a>
43where
44 T: Into<Cow<'a, str>> + Styled<Item = Span<'a>>,
45{
46 let mut desc_string = String::from("\u{00a0}");
47 desc_string.push_str(&d.into());
48 desc_string.push('\u{00a0}');
49 desc_string.set_style(THEME.help_desc)
50}
51
52pub fn fancy_help_desc<'a, T>(d: T) -> Span<'a>
53where
54 T: Into<Cow<'a, str>> + Styled<Item = Span<'a>>,
55{
56 let mut desc_string = String::from("\u{00a0}");
57 desc_string.push_str(&d.into());
58 desc_string.push('\u{00a0}');
59 desc_string.set_style(THEME.fancy_help_desc)
60}
61
62macro_rules! help_item {
63 ($key: expr, $desc: expr) => {{
64 [
65 crate::help::help_key($key),
66 crate::help::help_desc($desc),
67 "\u{200b}".into(),
68 ]
69 }};
70}
71
72pub(crate) use help_item;
73
74pub fn help<'a>(area: Rect) -> SizedParagraph<'a> {
75 let line1 = Line::default().spans(vec![
76 "W".bold().black(),
77 "elcome to tracexec! The TUI consists of at most two panes: the event list and optionally the pseudo terminal if ".into(),
78 cli_flag("--tty/-t"),
79 " is enabled. The event list displays the events emitted by the tracer. \
80 The active pane's border is highlighted in cyan. \
81 To switch active pane, press ".into(),
82 help_key("Ctrl+S"),
83 ". To send ".into(),
84 help_key("Ctrl+S"),
85 " to the pseudo terminal, press ".into(),
86 help_key("Alt+S"),
87 " when event list is active. The keybinding list at the bottom of the screen shows the available keys for currently active pane or popup.".into(),
88 ]);
89 let line2 = Line::default().spans(vec![
90 "Y".bold().black(),
91 "ou can navigate the event list using the arrow keys or ".into(),
92 help_key("H/J/K/L"),
93 ". To scroll faster, use ".into(),
94 help_key("Ctrl+↑/↓/←/→/H/J/K/L"),
95 " or ".into(),
96 help_key("PgUp/PgDn"),
97 ". Use ".into(),
98 help_key("(Shift +) Home/End"),
99 " to scroll to the (line start/line end)/top/bottom. Press ".into(),
100 help_key("F"),
101 " to toggle follow mode, which will keep the list scrolled to bottom. ".into(),
102 "To change pane size, press ".into(),
103 help_key("G/S"),
104 " when the active pane is event list. ".into(),
105 "To switch between horizontal and vertical layout, press ".into(),
106 help_key("Alt+L"),
107 ". To view the details of the selected event, press ".into(),
108 help_key("V"),
109 ". To copy the selected event to the clipboard, press ".into(),
110 help_key("C"),
111 " then select what to copy. To jump to the parent exec event of the currently selected event, press ".into(),
112 help_key("U"),
113 ". To show the backtrace of the currently selected event, press ".into(),
114 help_key("T"),
115 ". To quit, press ".into(),
116 help_key("Q"),
117 " while the event list is active.".into(),
118 ]);
119 let line3 = Line::default().spans(vec![
120 "W".bold(),
121 "hen the pseudo terminal is active, you can interact with the terminal using the keyboard."
122 .into(),
123 ]);
124 let line4 =
125 Line::default().spans(vec![
126 "E".bold().black(),
127 "ach exec event in the event list consists of four parts, the pid, the status of the process,\
128 the comm of the process (before exec), and the commandline to reproduce the exec event. \
129 The pid is colored according to the result of the execve{,at} syscall.
130 The status can be one of the following: "
131 .into(),
132 help_key(<&str>::from(EventStatus::ExecENOENT)),
133 help_desc("Exec failed (ENOENT)"),
134 ", ".into(),
135 help_key(<&str>::from(EventStatus::ExecFailure)),
136 help_desc("Exec failed"),
137 ", ".into(),
138 help_key(<&str>::from(EventStatus::ProcessRunning)),
139 help_desc("Running"),
140 ", ".into(),
141 help_key(<&str>::from(EventStatus::ProcessPaused)),
142 help_desc("Paused"),
143 ", ".into(),
144 help_key(<&str>::from(EventStatus::ProcessDetached)),
145 help_desc("Detached"),
146 ", ".into(),
147 help_key(<&str>::from(EventStatus::ProcessExitedNormally)),
148 help_desc("Exited normally"),
149 ", ".into(),
150 help_key(<&str>::from(EventStatus::ProcessExitedAbnormally(1))),
151 help_desc("Exited abnormally"),
152 ", ".into(),
153 help_key(<&str>::from(EventStatus::ProcessTerminated)),
154 help_desc("Terminated"),
155 ", ".into(),
156 help_key(<&str>::from(EventStatus::ProcessKilled)),
157 help_desc("Killed"),
158 ", ".into(),
159 help_key(<&str>::from(EventStatus::ProcessSegfault)),
160 help_desc("Segfault"),
161 ", ".into(),
162 help_key(<&str>::from(EventStatus::ProcessInterrupted)),
163 help_desc("Interrupted"),
164 ", ".into(),
165 help_key(<&str>::from(EventStatus::ProcessIllegalInstruction)),
166 help_desc("Illegal instruction"),
167 ", ".into(),
168 help_key(<&str>::from(EventStatus::ProcessAborted)),
169 help_desc("Aborted"),
170 ", ".into(),
171 help_key(<&str>::from(EventStatus::ProcessSignaled(nix::sys::signal::Signal::SIGURG.into()))),
172 help_desc("Signaled"),
173 ", ".into(),
174 help_key(<&str>::from(EventStatus::InternalError)),
175 help_desc("An internal error occurred"),
176 ]);
177
178 let line5 = Line::default()
179 .spans(vec![
180 "P".bold().black(),
181 "ress ".into(),
182 help_key("Any Key"),
183 " to close this help popup.".into(),
184 ])
185 .centered();
186 let paragraph =
187 Paragraph::new(Text::from_iter([line1, line2, line3, line4, line5])).wrap(Wrap { trim: false });
188 let perhaps_a_suitable_width = area.width.saturating_sub(6) as usize;
189 SizedParagraph::new(paragraph, perhaps_a_suitable_width)
190}