1use std::borrow::Cow;
2use std::cell::Cell;
3use std::collections::{BTreeSet, VecDeque};
4use std::io;
5use std::sync::{Arc, Mutex};
6
7use i8051::{ControlFlow, Cpu, CpuContext, Flag, Register};
8use ratatui::text::Text;
9use ratatui::{
10 Frame, Terminal,
11 backend::CrosstermBackend,
12 crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers},
13 layout::{Constraint, Direction, Layout, Rect},
14 style::{Color, Modifier, Style},
15 text::{Line, Span},
16 widgets::{Paragraph, Wrap},
17};
18use tracing::Level;
19use tracing_subscriber::Layer;
20
21pub use ratatui::crossterm;
23
24#[derive(Clone)]
26struct TracingCollectorInner {
27 max_events: usize,
28 events: Arc<Mutex<VecDeque<(Level, String)>>>,
29}
30
31#[derive(Clone)]
33pub struct TracingCollector {
34 inner: TracingCollectorInner,
35}
36
37impl TracingCollector {
38 pub fn new(max_events: usize) -> Self {
39 Self {
40 inner: TracingCollectorInner {
41 max_events,
42 events: Arc::new(Mutex::new(VecDeque::with_capacity(max_events))),
43 },
44 }
45 }
46
47 pub fn with_last_n(&self, n: usize, mut f: impl FnMut(Level, &str)) {
48 let events = self.inner.events.lock().unwrap();
49 for event in events.iter().rev().take(n) {
50 f(event.0, &event.1);
51 }
52 }
53
54 pub fn clear(&self) {
56 self.inner.events.lock().unwrap().clear();
57 }
58
59 fn add_event(&self, level: Level, message: String) {
60 let mut events = self.inner.events.lock().unwrap();
61 if events.len() >= self.inner.max_events {
62 events.pop_front();
63 }
64 events.push_back((level, message));
65 }
66}
67
68impl<S> Layer<S> for TracingCollector
69where
70 S: tracing::Subscriber,
71{
72 fn on_event(
73 &self,
74 event: &tracing::Event<'_>,
75 _ctx: tracing_subscriber::layer::Context<'_, S>,
76 ) {
77 struct Filter(bool);
78 impl tracing::field::Visit for Filter {
79 fn record_debug(&mut self, _: &tracing::field::Field, _: &dyn std::fmt::Debug) {}
80 fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
81 if field.name() == "log.module_path" && value.starts_with("mio::") {
82 self.0 = true;
83 }
84 }
85 }
86
87 let mut filter = Filter(false);
88 event.record(&mut filter);
89 if filter.0 {
90 return;
91 }
92
93 let mut visitor = EventVisitor::default();
95 event.record(&mut visitor);
96
97 let level = event.metadata().level();
98 let message = visitor.message;
99
100 self.add_event(*level, message);
101 }
102}
103
104#[derive(Default)]
106struct EventVisitor {
107 message: String,
108}
109
110impl tracing::field::Visit for EventVisitor {
111 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
112 if field.name() == "message" {
113 self.message = format!("{:?}", value);
114 if self.message.starts_with('"') && self.message.ends_with('"') {
116 self.message = self.message[1..self.message.len() - 1].to_string();
117 }
118 } else if self.message.is_empty() {
119 self.message = format!("{} = {:?}", field.name(), value);
121 } else {
122 self.message
124 .push_str(&format!(", {} = {:?}", field.name(), value));
125 }
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub enum DebuggerState {
131 Paused,
133 Running,
135 Quit,
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140enum DebuggerFocus {
141 Code,
142 Reg(Register),
143 Output,
144}
145
146impl DebuggerFocus {
147 const ORDER: [DebuggerFocus; 15] = [
148 DebuggerFocus::Code,
149 DebuggerFocus::Reg(Register::PC),
150 DebuggerFocus::Reg(Register::SP),
151 DebuggerFocus::Reg(Register::R(0)),
152 DebuggerFocus::Reg(Register::R(1)),
153 DebuggerFocus::Reg(Register::R(2)),
154 DebuggerFocus::Reg(Register::R(3)),
155 DebuggerFocus::Reg(Register::R(4)),
156 DebuggerFocus::Reg(Register::R(5)),
157 DebuggerFocus::Reg(Register::R(6)),
158 DebuggerFocus::Reg(Register::R(7)),
159 DebuggerFocus::Reg(Register::A),
160 DebuggerFocus::Reg(Register::B),
161 DebuggerFocus::Reg(Register::DPTR),
162 DebuggerFocus::Output,
163 ];
164
165 pub fn next(self) -> Self {
166 let index = Self::ORDER.iter().position(|r| r == &self).unwrap_or(0);
167 Self::ORDER[(index + 1) % Self::ORDER.len()]
168 }
169
170 pub fn prev(self) -> Self {
171 let index = Self::ORDER.iter().position(|r| r == &self).unwrap_or(0);
172 if index == 0 {
173 Self::ORDER[Self::ORDER.len() - 1]
174 } else {
175 Self::ORDER[index - 1]
176 }
177 }
178
179 #[allow(dead_code)]
181 pub fn edit_length(self) -> usize {
182 use DebuggerFocus::*;
183 match self {
184 Code | Output => 0,
185 Reg(Register::PC) | Reg(Register::DPTR) => 4,
186 Reg(Register::SP) | Reg(Register::A) | Reg(Register::B) | Reg(Register::R(_)) => 2,
187 _ => 0,
188 }
189 }
190}
191
192struct DebuggerInternalState {
193 focus: DebuggerFocus,
195 state: DebuggerState,
197 edit_buffer: String,
199 is_editing: bool,
201 code_window: CodeWindowState,
203}
204
205struct CodeWindowState {
206 start: Cell<u32>,
208 last_height: Cell<usize>,
210 focus: Option<u32>,
212}
213
214pub struct DebuggerConfig {
216 pub step_key_label: Cow<'static, str>,
217 pub run_key_label: Cow<'static, str>,
218 pub toggle_breakpoint_key_label: Cow<'static, str>,
219}
220
221impl Default for DebuggerConfig {
222 fn default() -> Self {
223 Self {
224 step_key_label: Cow::Borrowed("s"),
225 run_key_label: Cow::Borrowed("r"),
226 toggle_breakpoint_key_label: Cow::Borrowed("b"),
227 }
228 }
229}
230
231pub struct Debugger {
233 config: DebuggerConfig,
234 breakpoints: BTreeSet<u32>,
235 terminal: Terminal<CrosstermBackend<io::Stdout>>,
236 state: DebuggerInternalState,
237 tracing_collector: TracingCollector,
238}
239
240impl Debugger {
241 pub fn new(config: DebuggerConfig) -> io::Result<Self> {
243 let stdout = io::stdout();
244 let backend = CrosstermBackend::new(stdout);
245 let terminal = Terminal::new(backend)?;
246
247 Ok(Self {
248 config,
249 breakpoints: BTreeSet::new(),
250 terminal,
251 state: DebuggerInternalState {
252 focus: DebuggerFocus::Code,
253 state: DebuggerState::Paused,
254 edit_buffer: String::new(),
255 is_editing: false,
256 code_window: CodeWindowState {
257 start: Cell::new(0),
258 last_height: Cell::new(10),
259 focus: None,
260 },
261 },
262 tracing_collector: TracingCollector::new(1000),
263 })
264 }
265
266 pub fn tracing_collector(&self) -> TracingCollector {
268 self.tracing_collector.clone()
269 }
270
271 pub fn breakpoints(&self) -> &BTreeSet<u32> {
273 &self.breakpoints
274 }
275
276 pub fn breakpoints_mut(&mut self) -> &mut BTreeSet<u32> {
278 &mut self.breakpoints
279 }
280
281 pub fn debugger_state(&self) -> DebuggerState {
283 self.state.state
284 }
285
286 pub fn pause(&mut self) {
288 self.state.state = DebuggerState::Paused;
289 self.state.code_window.focus = None;
290 }
291
292 pub fn toggle_breakpoint(&mut self, addr: u32) {
294 if self.breakpoints.contains(&addr) {
295 self.breakpoints.remove(&addr);
296 } else {
297 self.breakpoints.insert(addr);
298 }
299 }
300
301 pub fn handle_event(&mut self, event: Event, cpu: &mut Cpu, ctx: &mut impl CpuContext) -> bool {
304 if self.state.is_editing {
306 match event {
307 Event::Key(KeyEvent {
308 code: KeyCode::Esc, ..
309 }) => {
310 self.state.is_editing = false;
312 self.state.edit_buffer.clear();
313 return false;
314 }
315 Event::Key(KeyEvent {
316 code: KeyCode::Enter,
317 ..
318 }) => {
319 if let DebuggerFocus::Reg(register) = self.state.focus {
321 if let Ok(value) = u32::from_str_radix(&self.state.edit_buffer, 16) {
322 self.apply_register_edit(cpu, ctx, register, value);
323 }
324 }
325 self.state.is_editing = false;
326 self.state.edit_buffer.clear();
327 return false;
328 }
329 Event::Key(KeyEvent {
330 code: KeyCode::Backspace,
331 ..
332 }) => {
333 self.state.edit_buffer.pop();
334 return false;
335 }
336 Event::Key(KeyEvent {
337 code: KeyCode::Char(c),
338 ..
339 }) if c.is_ascii_hexdigit() => {
340 let max_len = match self.state.focus {
342 DebuggerFocus::Reg(Register::PC) | DebuggerFocus::Reg(Register::DPTR) => 4,
343 _ => 2,
344 };
345 if self.state.edit_buffer.len() < max_len {
346 self.state.edit_buffer.push(c.to_ascii_uppercase());
347 }
348 return false;
349 }
350 _ => return false,
351 }
352 }
353
354 match event {
356 Event::Key(KeyEvent {
357 code: KeyCode::Char('c'),
358 modifiers: KeyModifiers::CONTROL,
359 ..
360 }) => {
361 if self.state.state == DebuggerState::Running {
362 self.state.state = DebuggerState::Paused;
363 }
364 false
365 }
366 Event::Key(KeyEvent {
367 code: KeyCode::Tab,
368 modifiers,
369 ..
370 }) => {
371 if modifiers.contains(KeyModifiers::SHIFT) {
372 self.state.focus = self.state.focus.prev();
373 } else {
374 self.state.focus = self.state.focus.next();
375 }
376 false
377 }
378 Event::Key(KeyEvent {
379 code: KeyCode::BackTab,
380 ..
381 }) => {
382 self.state.focus = self.state.focus.prev();
384 false
385 }
386 Event::Key(KeyEvent {
387 code: KeyCode::Char('q'),
388 ..
389 }) => {
390 self.state.state = DebuggerState::Quit;
391 true
392 }
393 Event::Key(KeyEvent {
394 code: KeyCode::Char('s'),
395 ..
396 }) if self.state.focus == DebuggerFocus::Code => {
397 self.state.code_window.focus = None;
399 true
400 }
401 Event::Key(KeyEvent {
402 code: KeyCode::Char('r'),
403 ..
404 }) if self.state.focus == DebuggerFocus::Code => {
405 self.state.state = match self.state.state {
407 DebuggerState::Paused => DebuggerState::Running,
408 DebuggerState::Running => DebuggerState::Paused,
409 DebuggerState::Quit => DebuggerState::Quit,
410 };
411 false
412 }
413 Event::Key(KeyEvent {
414 code: KeyCode::Char('b'),
415 ..
416 }) if self.state.focus == DebuggerFocus::Code => {
417 let addr = self.state.code_window.focus.unwrap_or(cpu.pc_ext(ctx));
419 self.toggle_breakpoint(addr);
420 false
421 }
422 Event::Key(KeyEvent {
423 code: KeyCode::Up, ..
424 }) if self.state.focus == DebuggerFocus::Code => {
425 let current = self.state.code_window.focus.unwrap_or(cpu.pc_ext(ctx));
427
428 let mut start = self.state.code_window.start.get();
431 if start == current {
432 start = start.saturating_sub(10);
433 }
434
435 let instructions = cpu.decode_range(ctx, start, current, 100);
437 if let Some(current) = instructions
438 .iter()
439 .position(|instruction| instruction.pc() == current)
440 {
441 self.state.code_window.start.set(instructions[0].pc());
442 self.state.code_window.focus =
443 Some(instructions[current.saturating_sub(1)].pc());
444 } else {
445 self.state.code_window.start.set(start);
447 self.state.code_window.focus = Some(current.saturating_sub(1));
448 }
449 false
450 }
451 Event::Key(KeyEvent {
452 code: KeyCode::Down,
453 ..
454 }) if self.state.focus == DebuggerFocus::Code => {
455 let current = self.state.code_window.focus.unwrap_or(cpu.pc_ext(ctx));
457 let height = self.state.code_window.last_height.get();
458 let instructions =
459 cpu.decode_range(ctx, self.state.code_window.start.get(), current, 100);
460 let len = cpu.decode(ctx, current).len();
461 if let Some(current) = instructions
462 .iter()
463 .position(|instruction| instruction.pc() == current)
464 {
465 let scroll = height.saturating_sub(current);
466 if scroll <= 2 {
467 self.state.code_window.start.set(instructions[2].pc());
468 } else {
469 self.state.code_window.start.set(instructions[0].pc());
470 }
471 }
472 self.state.code_window.focus = Some(current.saturating_add(len as u32));
473
474 false
475 }
476 Event::Key(KeyEvent {
477 code: KeyCode::Char('c'),
478 ..
479 }) if self.state.focus == DebuggerFocus::Output => {
480 self.tracing_collector.clear();
482 false
483 }
484 Event::Key(KeyEvent {
485 code: KeyCode::Char(c),
486 ..
487 }) if matches!(self.state.focus, DebuggerFocus::Reg(_)) && c.is_ascii_hexdigit() => {
488 self.state.is_editing = true;
490 self.state.edit_buffer.clear();
491 self.state.edit_buffer.push(c.to_ascii_uppercase());
492 false
493 }
494 _ => false,
495 }
496 }
497
498 fn apply_register_edit(
499 &mut self,
500 cpu: &mut Cpu,
501 _ctx: &mut impl CpuContext,
502 register: Register,
503 value: u32,
504 ) {
505 cpu.register_set(register, value as _);
506 }
507
508 pub fn render(&mut self, cpu: &Cpu, ctx: &mut impl CpuContext) -> io::Result<()> {
510 let config = &self.config;
511 let breakpoints = &self.breakpoints;
512 let focus = self.state.focus;
513 let state = self.state.state;
514 let is_editing = self.state.is_editing;
515 let edit_buffer = &self.state.edit_buffer;
516 let code_window = &self.state.code_window;
517 let trace_events = &self.tracing_collector;
518
519 self.terminal.draw(|f| {
520 render_frame(
521 f,
522 cpu,
523 ctx,
524 config,
525 breakpoints,
526 focus,
527 state,
528 is_editing,
529 edit_buffer,
530 &code_window,
531 &trace_events,
532 );
533 })?;
534 Ok(())
535 }
536}
537
538fn render_frame(
539 f: &mut Frame,
540 cpu: &Cpu,
541 ctx: &mut impl CpuContext,
542 config: &DebuggerConfig,
543 breakpoints: &BTreeSet<u32>,
544 focus: DebuggerFocus,
545 state: DebuggerState,
546 is_editing: bool,
547 edit_buffer: &str,
548 code_window: &CodeWindowState,
549 trace_events: &TracingCollector,
550) {
551 let main_layout = Layout::default()
553 .direction(Direction::Vertical)
554 .constraints([
555 Constraint::Min(0), Constraint::Length(1), ])
558 .split(f.area());
559
560 let content_area = main_layout[0];
561 let status_area = main_layout[1];
562
563 let main_chunks = Layout::default()
565 .direction(Direction::Horizontal)
566 .constraints([
567 Constraint::Percentage(50),
568 Constraint::Length(1), Constraint::Percentage(50),
570 ])
571 .split(content_area);
572
573 let right_chunks = Layout::default()
574 .direction(Direction::Vertical)
575 .constraints([Constraint::Min(10), Constraint::Percentage(50)])
576 .split(main_chunks[2]);
577
578 if state == DebuggerState::Paused {
580 render_disassembly(f, main_chunks[0], cpu, ctx, breakpoints, focus, code_window);
581 }
582
583 let separator_lines: Vec<Line> = (0..main_chunks[1].height)
585 .map(|_| Line::from("│"))
586 .collect();
587 let separator = Paragraph::new(separator_lines).style(Style::default().fg(Color::DarkGray));
588 f.render_widget(separator, main_chunks[1]);
589
590 render_registers(f, right_chunks[0], cpu, focus, is_editing, edit_buffer);
591 render_output(f, right_chunks[1], trace_events, focus);
592
593 let status_text = format!(
595 " {} │ {}: Step │ {}: Run │ {}: Breakpoint │ Tab/Shift+Tab: Switch │ q: Quit ",
596 if state == DebuggerState::Running {
597 "RUNNING"
598 } else {
599 "PAUSED "
600 },
601 config.step_key_label,
602 config.run_key_label,
603 config.toggle_breakpoint_key_label,
604 );
605
606 let status_style = if state == DebuggerState::Running {
607 Style::default()
608 .bg(Color::Green)
609 .fg(Color::Black)
610 .add_modifier(Modifier::BOLD)
611 } else {
612 Style::default()
613 .bg(Color::Blue)
614 .fg(Color::White)
615 .add_modifier(Modifier::BOLD)
616 };
617
618 let status_bar = Paragraph::new(Line::from(Span::styled(status_text, status_style)));
619 f.render_widget(status_bar, status_area);
620}
621
622fn render_disassembly(
623 f: &mut Frame,
624 area: Rect,
625 cpu: &Cpu,
626 ctx: &mut impl CpuContext,
627 breakpoints: &BTreeSet<u32>,
628 focus: DebuggerFocus,
629 code_window: &CodeWindowState,
630) {
631 let pc = cpu.pc_ext(ctx);
632 let mut lines = Vec::new();
633 let code_window_focus = matches!(focus, DebuggerFocus::Code);
634
635 let available_height = area.height as usize;
637 let max_lines = available_height.min(100);
638 code_window.last_height.set(max_lines);
639
640 let focus_addr = code_window.focus.unwrap_or(pc);
642 let mut instructions = cpu.decode_range(ctx, code_window.start.get(), focus_addr, max_lines);
643 if !instructions
644 .iter()
645 .any(|instruction| instruction.pc() == focus_addr)
646 {
647 instructions = cpu.decode_range(ctx, focus_addr.saturating_sub(10), focus_addr, max_lines);
648 }
649 code_window.start.set(instructions.first().unwrap().pc());
650
651 fn control_flow_addr(
652 cpu: &Cpu,
653 ctx: &impl CpuContext,
654 control_flow: ControlFlow,
655 ) -> Option<(&'static str, u32)> {
656 match control_flow {
657 ControlFlow::Call(_, pc) => Some((" ↦ ", cpu.pc_ext_addr(ctx, pc))),
658 ControlFlow::Choice(_, pc) => Some((" ↔ ", cpu.pc_ext_addr(ctx, pc))),
659 _ => None,
660 }
661 }
662
663 let control_flow = instructions
664 .iter()
665 .find(|instruction| instruction.pc() == focus_addr)
666 .map(|instruction| instruction.control_flow())
667 .and_then(|control_flow| control_flow_addr(cpu, ctx, control_flow));
668 let control_flow_pc = instructions
669 .iter()
670 .find(|instruction| instruction.pc() == pc)
671 .map(|instruction| instruction.control_flow())
672 .and_then(|control_flow| control_flow_addr(cpu, ctx, control_flow));
673
674 for instruction in instructions {
675 let bytes_str = instruction
676 .bytes()
677 .iter()
678 .map(|b| format!("{:02X}", b))
679 .collect::<Vec<_>>()
680 .join(" ");
681
682 let current_pc = instruction.pc();
683 let has_breakpoint = breakpoints.contains(¤t_pc);
684 let is_current = current_pc == pc;
685 let is_focused = current_pc == focus_addr;
686
687 let bp_marker = if has_breakpoint { "●" } else { " " };
688 let pc_marker = if is_current { ">" } else { " " };
689
690 let control_flow_marker = control_flow.and_then(|(marker, addr)| {
691 if addr == current_pc {
692 Some(marker)
693 } else {
694 None
695 }
696 });
697 let control_flow_marker_pc = control_flow_pc.and_then(|(marker, addr)| {
698 if addr == current_pc {
699 Some(marker)
700 } else {
701 None
702 }
703 });
704
705 let line_text = format!(
706 "{}{} {:04X}: {:12} {} ",
707 bp_marker,
708 pc_marker,
709 current_pc,
710 bytes_str,
711 instruction.decode(),
712 );
713
714 let style = if is_current {
715 Style::default()
716 .fg(Color::Yellow)
717 .add_modifier(Modifier::BOLD)
718 } else if has_breakpoint {
719 Style::default().fg(Color::Red)
720 } else {
721 Style::default()
722 }
723 .bg(if is_focused {
724 if is_current {
725 Color::Yellow
726 } else {
727 Color::Green
728 }
729 } else if code_window_focus {
730 Color::Black
731 } else {
732 Color::Reset
733 });
734
735 let mut line = Line::default().patch_style(style).spans([line_text]);
736 line.push_span(Span::styled(
737 control_flow_marker
738 .or(control_flow_marker_pc)
739 .unwrap_or_default(),
740 Style::default().bg(Color::Blue),
741 ));
742 let text = Text::from(line);
743 lines.push(text);
744 }
745
746 for (row, line) in area.rows().zip(lines) {
747 f.render_widget(line, row);
748 }
749}
750
751fn render_registers(
752 f: &mut Frame,
753 area: Rect,
754 cpu: &Cpu,
755 focus: DebuggerFocus,
756 is_editing: bool,
757 edit_buffer: &str,
758) {
759 let mut lines = Vec::new();
760
761 let is_reg_focused = matches!(focus, DebuggerFocus::Reg(_));
762
763 let format_value = |reg: Register, default_value: String| -> String {
765 if is_editing && focus == DebuggerFocus::Reg(reg) {
766 let max_len: usize = match reg {
768 Register::PC | Register::DPTR => 4,
769 _ => 2,
770 };
771 format!("{}_", edit_buffer) + &"_".repeat(max_len.saturating_sub(edit_buffer.len() + 1))
772 } else {
773 default_value
774 }
775 };
776
777 let reg_style = |reg: Register| {
779 if focus == DebuggerFocus::Reg(reg) {
780 if is_editing {
781 Style::default()
782 .fg(if is_reg_focused {
783 Color::LightGreen
784 } else {
785 Color::Green
786 })
787 .add_modifier(Modifier::BOLD | Modifier::UNDERLINED)
788 } else {
789 Style::default()
790 .fg(if is_reg_focused {
791 Color::LightYellow
792 } else {
793 Color::Yellow
794 })
795 .add_modifier(Modifier::BOLD)
796 }
797 } else {
798 Style::default()
799 }
800 };
801
802 let sp = cpu.sp();
804 let pc_sp_line = vec![
805 Span::styled(
806 format!(
807 "PC:{}",
808 format_value(Register::PC, format!("{:04X}", cpu.pc))
809 ),
810 reg_style(Register::PC),
811 ),
812 Span::raw(" "),
813 Span::styled(
814 format!("SP:{}", format_value(Register::SP, format!("{:02X}", sp))),
815 reg_style(Register::SP),
816 ),
817 Span::raw(" "),
818 Span::styled(
819 format!(
820 "IP:{}",
821 format_value(Register::IP, format!("{:02X}", cpu.ip()))
822 ),
823 reg_style(Register::IP),
824 ),
825 Span::raw(" "),
826 Span::styled(
827 format!(
828 "IE:{}",
829 format_value(Register::IE, format!("{:02X}", cpu.ie()))
830 ),
831 reg_style(Register::IE),
832 ),
833 ];
834 lines.push(Line::from(pc_sp_line));
835
836 let mut r_spans = Vec::new();
838 for i in 0..8 {
839 if i > 0 {
840 r_spans.push(Span::raw(" "));
841 }
842 r_spans.push(Span::styled(
843 format!(
844 "R{}:{}",
845 i,
846 format_value(Register::R(i), format!("{:02X}", cpu.r(i)))
847 ),
848 reg_style(Register::R(i)),
849 ));
850 }
851 lines.push(Line::from(r_spans));
852
853 let abc_line = vec![
855 Span::styled(
856 format!(
857 "A:{}",
858 format_value(Register::A, format!("{:02X}", cpu.a()))
859 ),
860 reg_style(Register::A),
861 ),
862 Span::raw(" "),
863 Span::styled(
864 format!(
865 "B:{}",
866 format_value(Register::B, format!("{:02X}", cpu.b()))
867 ),
868 reg_style(Register::B),
869 ),
870 Span::raw(" "),
871 Span::styled(
872 format!(
873 "DPTR:{}",
874 format_value(Register::DPTR, format!("{:04X}", cpu.dptr()))
875 ),
876 reg_style(Register::DPTR),
877 ),
878 ];
879 lines.push(Line::from(abc_line));
880 lines.push(Line::from(""));
881
882 let mut flag_spans = Vec::new();
884 for (i, flag) in Flag::all().iter().enumerate() {
885 if i > 0 {
886 flag_spans.push(Span::raw(" "));
887 }
888 let style = if cpu.psw(*flag) {
889 Style::default().add_modifier(Modifier::BOLD)
890 } else {
891 Style::default().add_modifier(Modifier::DIM)
892 };
893 flag_spans.push(Span::styled(flag.short_name(), style));
894 }
895 lines.push(Line::from(flag_spans));
896 lines.push(Line::from(""));
897
898 lines.push(Line::from(Span::styled(
900 "Internal RAM:",
901 Style::default().add_modifier(Modifier::BOLD),
902 )));
903
904 let sp = cpu.sp() as usize;
906 let register_bank =
907 (if cpu.psw(Flag::RS1) { 2 } else { 0 }) + (if cpu.psw(Flag::RS0) { 1 } else { 0 });
908 let bank_start = register_bank * 8;
909 let bank_end = bank_start + 8;
910
911 for row in 0..8 {
913 let addr = row * 16;
914 let mut spans = vec![Span::styled(
915 format!("{:02X}: ", addr),
916 Style::default().add_modifier(Modifier::DIM),
917 )];
918
919 for col in 0..16 {
920 let byte_addr = addr + col;
921 let byte_val = cpu.internal_ram[byte_addr];
922 let in_current_register_bank = byte_addr >= bank_start && byte_addr < bank_end;
923
924 let style = if byte_addr == sp.wrapping_add(1) {
926 Style::default().bg(Color::Green).fg(Color::Black)
928 } else if in_current_register_bank {
929 Style::default().bg(Color::Yellow).fg(Color::Black)
931 } else {
932 Style::default()
933 };
934
935 if in_current_register_bank && byte_addr < bank_end - 1 {
936 spans.push(Span::styled(format!("{:02X} ", byte_val), style));
937 } else {
938 spans.push(Span::styled(format!("{:02X}", byte_val), style));
939 spans.push(Span::raw(" "));
940 }
941 }
942
943 lines.push(Line::from(spans));
944 }
945
946 let paragraph = Paragraph::new(lines)
947 .wrap(Wrap { trim: false })
948 .style(Style::default().bg(if is_reg_focused {
949 Color::Black
950 } else {
951 Color::Reset
952 }));
953
954 f.render_widget(paragraph, area);
955}
956
957fn render_output(f: &mut Frame, area: Rect, trace_events: &TracingCollector, focus: DebuggerFocus) {
958 let is_output_focused = matches!(focus, DebuggerFocus::Output);
959
960 let mut lines = vec![Line::from(Span::styled(
962 "Trace Output:",
963 Style::default().add_modifier(Modifier::BOLD | Modifier::UNDERLINED),
964 ))];
965
966 let available_lines = area.height.saturating_sub(1) as usize; trace_events.with_last_n(available_lines, &mut |level: Level, event: &str| {
969 let level_color = match level {
971 Level::ERROR => Color::Red,
972 Level::WARN => Color::Yellow,
973 Level::INFO => Color::Green,
974 Level::DEBUG => Color::Cyan,
975 Level::TRACE => Color::DarkGray,
976 };
977
978 lines.push(Line::from(vec![Span::styled(
979 event.to_string(),
980 Style::default().fg(level_color),
981 )]));
982 });
983
984 let paragraph = Paragraph::new(lines)
985 .wrap(Wrap { trim: false })
986 .style(Style::default().bg(if is_output_focused {
987 Color::Black
988 } else {
989 Color::Reset
990 }));
991
992 f.render_widget(paragraph, area);
993}
994
995impl Debugger {
996 pub fn enter(&mut self) -> io::Result<()> {
998 crossterm::terminal::enable_raw_mode()?;
999 crossterm::execute!(io::stdout(), crossterm::terminal::EnterAlternateScreen,)?;
1000 self.terminal.clear()?;
1001 Ok(())
1002 }
1003
1004 pub fn exit(&mut self) -> io::Result<()> {
1006 crossterm::terminal::disable_raw_mode()?;
1007 crossterm::execute!(io::stdout(), crossterm::terminal::LeaveAlternateScreen,)?;
1008 Ok(())
1009 }
1010}
1011
1012impl Drop for Debugger {
1013 fn drop(&mut self) {
1014 let _ = self.exit();
1015 }
1016}