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