bugstalker 0.4.5

BugStalker is a modern and lightweight debugger for rust applications.
Documentation
use crate::debugger::register::debug::BreakCondition;
use crate::ui;
use crate::ui::proto::ClientExchanger;
use crate::ui::tui::app::port::UserEvent;
use crate::ui::tui::config::CommonAction;
use crate::ui::tui::utils::mstextarea::MultiSpanTextarea;
use crate::ui::tui::{Id, Msg};
use std::sync::Arc;
use tuirealm::command::{Cmd, Direction, Position};
use tuirealm::props::{Borders, Style, TextSpan};
use tuirealm::ratatui::layout::Alignment;
use tuirealm::ratatui::prelude::Color;
use tuirealm::ratatui::widgets::BorderType;
use tuirealm::{
    AttrValue, Attribute, Component, Event, MockComponent, Sub, SubClause, SubEventClause,
};

#[derive(MockComponent)]
pub struct Asm {
    component: MultiSpanTextarea,
    exchanger: Arc<ClientExchanger>,
}

impl Asm {
    pub fn new(exchanger: Arc<ClientExchanger>) -> anyhow::Result<Self> {
        let component = MultiSpanTextarea::default()
            .borders(
                Borders::default()
                    .modifiers(BorderType::Rounded)
                    .color(Color::LightYellow),
            )
            .inactive(Style::default().fg(Color::Gray))
            .title("Assembler code for function", Alignment::Center)
            .step(4)
            .highlighted_str("");

        let mut this = Self {
            component,
            exchanger,
        };

        this.update_asm_view();

        Ok(this)
    }

    fn update_asm_view(&mut self) {
        let Ok(asm) = self.exchanger.request_sync(|dbg| dbg.disasm()) else {
            return;
        };

        if let Ok(asm) = asm {
            if let Some(ref fn_name) = asm.name {
                self.component.attr(
                    Attribute::Title,
                    AttrValue::Title((
                        format!("Assembler code for function ({fn_name})"),
                        Alignment::Center,
                    )),
                );
            }

            let mut line_in_focus = None;
            let mut lines = vec![];
            for instr in asm.instructions.into_iter() {
                let addr_span = TextSpan::new(format!("{} ", instr.address)).fg(Color::Blue);
                let mnemonic_span =
                    TextSpan::new(format!("{} ", instr.mnemonic.as_deref().unwrap_or("???")))
                        .fg(Color::Red);
                let operands_span =
                    TextSpan::new(instr.operands.as_deref().unwrap_or("???")).fg(Color::Green);

                let mut line = vec![addr_span, mnemonic_span, operands_span];

                if asm.addr_in_focus == instr.address {
                    line_in_focus = Some(lines.len());
                    line.iter_mut().for_each(|text| text.fg = Color::LightRed)
                }

                lines.push(line);
            }

            self.component.text_rows(lines);

            if let Some(line) = line_in_focus {
                self.component.states.list_index = line;
            }
        }
    }

    pub fn subscriptions() -> Vec<Sub<Id, UserEvent>> {
        vec![
            Sub::new(
                SubEventClause::User(UserEvent::Breakpoint {
                    pc: Default::default(),
                    num: 0,
                    file: None,
                    line: None,
                    function: None,
                }),
                SubClause::Always,
            ),
            Sub::new(
                SubEventClause::User(UserEvent::Watchpoint {
                    pc: Default::default(),
                    num: 0,
                    file: None,
                    line: None,
                    cond: BreakCondition::DataReadsWrites,
                    old_value: None,
                    new_value: None,
                    end_of_scope: false,
                }),
                SubClause::Always,
            ),
            Sub::new(
                SubEventClause::User(UserEvent::Step {
                    pc: Default::default(),
                    file: None,
                    line: None,
                    function: None,
                }),
                SubClause::Always,
            ),
        ]
    }
}

impl Component<Msg, UserEvent> for Asm {
    fn on(&mut self, ev: Event<UserEvent>) -> Option<Msg> {
        match ev {
            Event::Keyboard(key_event) => {
                let keymap = &ui::config::current().tui_keymap;
                if let Some(action) = keymap.get_common(&key_event) {
                    match action {
                        CommonAction::Up => {
                            self.perform(Cmd::Move(Direction::Up));
                        }
                        CommonAction::Down => {
                            self.perform(Cmd::Move(Direction::Down));
                        }
                        CommonAction::ScrollUp => {
                            self.perform(Cmd::Scroll(Direction::Up));
                        }
                        CommonAction::ScrollDown => {
                            self.perform(Cmd::Scroll(Direction::Down));
                        }
                        CommonAction::GotoBegin => {
                            self.perform(Cmd::GoTo(Position::Begin));
                        }
                        CommonAction::GotoEnd => {
                            self.perform(Cmd::GoTo(Position::End));
                        }
                        _ => {}
                    }
                }
            }

            Event::User(UserEvent::Breakpoint { .. })
            | Event::User(UserEvent::Step { .. })
            | Event::User(UserEvent::Watchpoint { .. }) => {
                self.update_asm_view();
            }
            _ => {}
        };
        Some(Msg::None)
    }
}