foundry_debugger/tui/
mod.rs1use eyre::Result;
4use foundry_tui::{TuiFallbackReason, TuiMode, run_app_if_interactive, tui_mode};
5
6mod context;
7use crate::debugger::DebuggerContext;
8use context::TUIContext;
9
10mod draw;
11mod storage;
12
13#[derive(Debug)]
15pub enum ExitReason {
16 CharExit,
18}
19
20pub struct TUI<'a> {
22 debugger_context: &'a mut DebuggerContext,
23}
24
25impl<'a> TUI<'a> {
26 pub const fn new(debugger_context: &'a mut DebuggerContext) -> Self {
28 Self { debugger_context }
29 }
30
31 pub fn try_run(&mut self) -> Result<ExitReason> {
33 self.run_inner()
34 }
35
36 #[instrument(target = "debugger", name = "run", skip_all, ret)]
37 fn run_inner(&mut self) -> Result<ExitReason> {
38 let mut cx = TUIContext::new(self.debugger_context);
39 cx.init();
40 match run_app_if_interactive(&mut cx)? {
41 Some(exit_reason) => Ok(exit_reason),
42 None => {
43 let message = match tui_mode() {
44 TuiMode::Fallback(reason) => non_interactive_debugger_message(reason),
45 TuiMode::Interactive => String::from(
46 "Cannot open the debugger TUI in this environment. Re-run in an \
47 interactive terminal.",
48 ),
49 };
50 eyre::bail!("{message} {}", debugger_dump_hint());
51 }
52 }
53 }
54}
55
56fn non_interactive_debugger_message(reason: TuiFallbackReason) -> String {
57 format!(
58 "Cannot open the debugger TUI because {}. Re-run in an interactive terminal.",
59 reason.as_str()
60 )
61}
62
63const fn debugger_dump_hint() -> &'static str {
64 "Pass `--dump <PATH>` to export debugger steps."
65}
66
67#[cfg(test)]
68mod tests {
69 use super::{TuiFallbackReason, debugger_dump_hint, non_interactive_debugger_message};
70 use crate::{DebugNode, Debugger};
71 use std::{env, ffi::OsString};
72
73 struct EnvVarGuard {
74 key: &'static str,
75 previous: Option<OsString>,
76 }
77
78 impl EnvVarGuard {
79 fn set(key: &'static str, value: &str) -> Self {
80 let previous = env::var_os(key);
81 unsafe { env::set_var(key, value) };
82 Self { key, previous }
83 }
84 }
85
86 impl Drop for EnvVarGuard {
87 fn drop(&mut self) {
88 unsafe {
89 match &self.previous {
90 Some(value) => env::set_var(self.key, value),
91 None => env::remove_var(self.key),
92 }
93 }
94 }
95 }
96
97 #[test]
98 fn fallback_message_includes_reason() {
99 let msg = non_interactive_debugger_message(TuiFallbackReason::Ci);
100 assert!(msg.contains("running in CI"));
101 assert!(!msg.contains("--dump <PATH>"));
102 }
103
104 #[test]
105 fn dump_hint_includes_dump_flag() {
106 assert!(debugger_dump_hint().contains("--dump <PATH>"));
107 }
108
109 #[test]
110 fn debugger_tui_falls_back_in_ci_with_dump_hint() {
111 let _ci = EnvVarGuard::set("CI", "1");
112 let mut debugger = Debugger::new(
113 vec![DebugNode::default()],
114 Default::default(),
115 Default::default(),
116 Default::default(),
117 );
118
119 let message = debugger.try_run_tui().unwrap_err().to_string();
120
121 assert!(message.contains("running in CI"));
122 assert!(message.contains("--dump <PATH>"));
123 }
124}