Skip to main content

miden_debug/ui/
mod.rs

1#[cfg(feature = "tui")]
2use std::boxed::Box;
3
4#[cfg(feature = "tui")]
5mod action;
6#[cfg(feature = "tui")]
7mod app;
8#[cfg(feature = "tui")]
9mod duration;
10#[cfg(feature = "tui")]
11mod pages;
12#[cfg(feature = "tui")]
13mod panes;
14pub(crate) mod state;
15#[cfg(feature = "tui")]
16mod syntax_highlighting;
17#[cfg(feature = "tui")]
18mod tui;
19
20#[cfg(feature = "tui")]
21use log::LevelFilter;
22#[cfg(feature = "tui")]
23use miden_assembly_syntax::diagnostics::{IntoDiagnostic, Report};
24
25pub use self::state::{DebugMode, State};
26#[cfg(feature = "tui")]
27use self::{action::Action, app::App};
28#[cfg(feature = "tui")]
29use crate::config::DebuggerConfig;
30
31#[cfg(feature = "tui")]
32#[allow(dead_code)]
33pub fn run(config: Box<DebuggerConfig>, logger: Box<dyn log::Log>) -> Result<(), Report> {
34    run_with_log_level(config, logger, LevelFilter::Trace)
35}
36
37#[cfg(feature = "tui")]
38pub fn run_with_log_level(
39    config: Box<DebuggerConfig>,
40    logger: Box<dyn log::Log>,
41    max_level: LevelFilter,
42) -> Result<(), Report> {
43    let mut builder = tokio::runtime::Builder::new_current_thread();
44    let rt = builder.enable_all().build().into_diagnostic()?;
45    rt.block_on(async move { start_ui(config, logger, max_level).await })
46}
47
48/// Launch the TUI debugger with a pre-built [State].
49///
50/// This is the programmatic entry point used by transaction debugging, where
51/// the caller constructs a [State] with pre-recorded event replay data.
52#[cfg(feature = "tui")]
53pub fn run_with_state(state: State, logger: Box<dyn log::Log>) -> Result<(), Report> {
54    run_with_state_and_log_level(state, logger, LevelFilter::Trace)
55}
56
57/// Launch the TUI debugger with a pre-built [State] and log level filter.
58#[cfg(feature = "tui")]
59pub fn run_with_state_and_log_level(
60    state: State,
61    logger: Box<dyn log::Log>,
62    max_level: LevelFilter,
63) -> Result<(), Report> {
64    let mut builder = tokio::runtime::Builder::new_current_thread();
65    let rt = builder.enable_all().build().into_diagnostic()?;
66    rt.block_on(async move { start_ui_with_state(state, logger, max_level).await })
67}
68
69/// Replay a recorded execution snapshot in the TUI debugger.
70///
71/// Reads a [`ReplaySnapshot`](crate::exec::ReplaySnapshot) written during a recorded DAP session
72/// (e.g. `miden-client exec --start-debug-adapter ... --record <FILE>`) and re-runs the same
73/// program with its captured inputs, forests, and event log fed back through the event-replay
74/// host — so the transaction can be stepped through offline, without the original host.
75#[cfg(feature = "tui")]
76pub fn run_replay_and_log_level(
77    snapshot_path: &std::path::Path,
78    logger: Box<dyn log::Log>,
79    max_level: LevelFilter,
80) -> Result<(), Report> {
81    use std::sync::Arc;
82
83    use miden_assembly::DefaultSourceManager;
84
85    use crate::exec::ReplaySnapshot;
86
87    let snapshot = ReplaySnapshot::read_from_file(snapshot_path)
88        .map_err(|err| Report::msg(format!("{err}")))?;
89    // The snapshot does not carry source files; the debugger falls back to disassembly, exactly
90    // as it does for a raw program with no debug info.
91    let source_manager = Arc::new(DefaultSourceManager::default());
92    let state = State::new_for_transaction(
93        snapshot.package,
94        snapshot.stack_inputs,
95        snapshot.advice_inputs,
96        snapshot.options,
97        source_manager,
98        snapshot.mast_forests,
99        snapshot.event_log,
100    )?;
101    run_with_state_and_log_level(state, logger, max_level)
102}
103
104#[cfg(feature = "tui")]
105#[allow(dead_code)]
106pub async fn start_ui(
107    config: Box<DebuggerConfig>,
108    logger: Box<dyn log::Log>,
109    max_level: LevelFilter,
110) -> Result<(), Report> {
111    use ratatui::crossterm as term;
112
113    crate::logger::DebugLogger::install_with_max_level(logger, max_level).into_diagnostic()?;
114
115    let original_hook = std::panic::take_hook();
116    std::panic::set_hook(Box::new(move |panic_info| {
117        let _ = term::terminal::disable_raw_mode();
118        let _ = term::execute!(std::io::stdout(), term::terminal::LeaveAlternateScreen);
119        original_hook(panic_info);
120    }));
121
122    let mut app = App::new(config).await?;
123    app.run().await?;
124
125    Ok(())
126}
127
128#[cfg(feature = "tui")]
129async fn start_ui_with_state(
130    state: State,
131    logger: Box<dyn log::Log>,
132    max_level: LevelFilter,
133) -> Result<(), Report> {
134    use ratatui::crossterm as term;
135
136    crate::logger::DebugLogger::install_with_max_level(logger, max_level).into_diagnostic()?;
137
138    let original_hook = std::panic::take_hook();
139    std::panic::set_hook(Box::new(move |panic_info| {
140        let _ = term::terminal::disable_raw_mode();
141        let _ = term::execute!(std::io::stdout(), term::terminal::LeaveAlternateScreen);
142        original_hook(panic_info);
143    }));
144
145    let mut app = App::from_state(state).await?;
146    app.run().await?;
147
148    Ok(())
149}