miden-debug 0.7.0

An interactive debugger for Miden VM programs
Documentation
use std::collections::VecDeque;

use crossterm::event::KeyCode;
use miden_assembly_syntax::diagnostics::Report;
use ratatui::{prelude::*, widgets::*};

use crate::{
    logger::{DebugLogger, LogEntry},
    ui::{
        action::Action,
        panes::Pane,
        state::{InputMode, State},
        tui::{EventResponse, Frame},
    },
};

pub struct DebugPane {
    logger: &'static DebugLogger,
    entries: VecDeque<LogEntry>,
    selected_entry: Option<usize>,
}
impl Default for DebugPane {
    fn default() -> Self {
        Self {
            logger: DebugLogger::get(),
            entries: Default::default(),
            selected_entry: None,
        }
    }
}

impl DebugPane {
    fn level_color(level: log::Level) -> Color {
        use log::Level;
        match level {
            Level::Trace => Color::LightCyan,
            Level::Debug => Color::LightMagenta,
            Level::Info => Color::LightGreen,
            Level::Warn => Color::LightYellow,
            Level::Error => Color::LightRed,
        }
    }
}

impl Pane for DebugPane {
    fn height_constraint(&self) -> Constraint {
        Constraint::Fill(3)
    }

    fn handle_key_events(
        &mut self,
        key: crossterm::event::KeyEvent,
        state: &mut State,
    ) -> Result<Option<EventResponse<Action>>, Report> {
        match state.input_mode {
            InputMode::Normal => {
                let response = match key.code {
                    KeyCode::Down | KeyCode::Char('j') | KeyCode::Char('J') => {
                        EventResponse::Stop(Action::Down)
                    }
                    KeyCode::Up | KeyCode::Char('k') | KeyCode::Char('K') => {
                        EventResponse::Stop(Action::Up)
                    }
                    KeyCode::Esc => EventResponse::Stop(Action::ClosePopup),
                    _ => {
                        return Ok(Some(EventResponse::Stop(Action::Noop)));
                    }
                };
                Ok(Some(response))
            }
            InputMode::Insert => Ok(Some(EventResponse::Stop(Action::Noop))),
            InputMode::Command => Ok(Some(EventResponse::Stop(Action::Noop))),
        }
    }

    fn update(&mut self, action: Action, _state: &mut State) -> Result<Option<Action>, Report> {
        let added = self.logger.take_captured();
        self.entries.extend(added);
        match action {
            Action::Down => {
                let selected_entry = self
                    .selected_entry
                    .map(|s| s.saturating_add(1) % self.entries.len())
                    .unwrap_or(self.entries.len().saturating_sub(1));
                self.selected_entry = Some(selected_entry);
                return Ok(Some(Action::Update));
            }
            Action::Up => {
                let selected_entry = self
                    .selected_entry
                    .map(|s| s.wrapping_sub(1) % self.entries.len())
                    .unwrap_or(self.entries.len().saturating_sub(1));
                self.selected_entry = Some(selected_entry);
                return Ok(Some(Action::Update));
            }
            _ => {}
        }
        Ok(None)
    }

    fn draw(&mut self, frame: &mut Frame<'_>, area: Rect, _state: &State) -> Result<(), Report> {
        frame.render_widget(Clear, area);
        let items = self.entries.iter().map(|entry| {
            Line::from(vec![
                Span::styled(format!(" {:6} | ", entry.level), Self::level_color(entry.level)),
                Span::styled(entry.message.as_str(), Self::level_color(entry.level)),
            ])
        });
        let selected = if self.entries.is_empty() {
            None
        } else {
            Some(self.selected_entry.unwrap_or(self.entries.len().saturating_sub(1)))
        };
        let list = List::new(items)
            .block(Block::default().borders(Borders::ALL))
            .highlight_symbol(symbols::scrollbar::HORIZONTAL.end)
            .highlight_spacing(HighlightSpacing::Always)
            .highlight_style(Style::default().add_modifier(Modifier::BOLD));
        let mut list_state = ListState::default().with_selected(selected);

        frame.render_stateful_widget(list, area, &mut list_state);
        frame.render_widget(
            Block::default()
                .borders(Borders::ALL)
                .title("Debug Log")
                .style(Style::default()),
            area,
        );
        Ok(())
    }
}