Skip to main content

miden_debug/ui/
mod.rs

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