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, tracing_collector: TracingCollector) -> 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,
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 && let Ok(value) = u32::from_str_radix(&self.state.edit_buffer, 16)
322 {
323 self.apply_register_edit(cpu, ctx, register, value);
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
538#[allow(clippy::too_many_arguments)]
539fn render_frame(
540 f: &mut Frame,
541 cpu: &Cpu,
542 ctx: &mut impl CpuContext,
543 config: &DebuggerConfig,
544 breakpoints: &BTreeSet<u32>,
545 focus: DebuggerFocus,
546 state: DebuggerState,
547 is_editing: bool,
548 edit_buffer: &str,
549 code_window: &CodeWindowState,
550 trace_events: &TracingCollector,
551) {
552 let main_layout = Layout::default()
554 .direction(Direction::Vertical)
555 .constraints([
556 Constraint::Min(0), Constraint::Length(1), ])
559 .split(f.area());
560
561 let content_area = main_layout[0];
562 let status_area = main_layout[1];
563
564 let main_chunks = Layout::default()
566 .direction(Direction::Horizontal)
567 .constraints([
568 Constraint::Percentage(50),
569 Constraint::Length(1), Constraint::Percentage(50),
571 ])
572 .split(content_area);
573
574 let right_chunks = Layout::default()
575 .direction(Direction::Vertical)
576 .constraints([Constraint::Min(10), Constraint::Percentage(50)])
577 .split(main_chunks[2]);
578
579 if state == DebuggerState::Paused {
581 render_disassembly(f, main_chunks[0], cpu, ctx, breakpoints, focus, code_window);
582 }
583
584 let separator_lines: Vec<Line> = (0..main_chunks[1].height)
586 .map(|_| Line::from("│"))
587 .collect();
588 let separator = Paragraph::new(separator_lines).style(Style::default().fg(Color::DarkGray));
589 f.render_widget(separator, main_chunks[1]);
590
591 render_registers(f, right_chunks[0], cpu, focus, is_editing, edit_buffer);
592 render_output(f, right_chunks[1], trace_events, focus);
593
594 let status_text = format!(
596 " {} │ {}: Step │ {}: Run │ {}: Breakpoint │ Tab/Shift+Tab: Switch │ q: Quit ",
597 if state == DebuggerState::Running {
598 "RUNNING"
599 } else {
600 "PAUSED "
601 },
602 config.step_key_label,
603 config.run_key_label,
604 config.toggle_breakpoint_key_label,
605 );
606
607 let status_style = if state == DebuggerState::Running {
608 Style::default()
609 .bg(Color::Green)
610 .fg(Color::Black)
611 .add_modifier(Modifier::BOLD)
612 } else {
613 Style::default()
614 .bg(Color::Blue)
615 .fg(Color::White)
616 .add_modifier(Modifier::BOLD)
617 };
618
619 let status_bar = Paragraph::new(Line::from(Span::styled(status_text, status_style)));
620 f.render_widget(status_bar, status_area);
621}
622
623fn render_disassembly(
624 f: &mut Frame,
625 area: Rect,
626 cpu: &Cpu,
627 ctx: &mut impl CpuContext,
628 breakpoints: &BTreeSet<u32>,
629 focus: DebuggerFocus,
630 code_window: &CodeWindowState,
631) {
632 let pc = cpu.pc_ext(ctx);
633 let mut lines = Vec::new();
634 let code_window_focus = matches!(focus, DebuggerFocus::Code);
635
636 let available_height = area.height as usize;
638 let max_lines = available_height.min(100);
639 code_window.last_height.set(max_lines);
640
641 let focus_addr = code_window.focus.unwrap_or(pc);
643 let mut instructions = cpu.decode_range(ctx, code_window.start.get(), focus_addr, max_lines);
644 if !instructions
645 .iter()
646 .any(|instruction| instruction.pc() == focus_addr)
647 {
648 instructions = cpu.decode_range(ctx, focus_addr.saturating_sub(10), focus_addr, max_lines);
649 }
650 code_window.start.set(instructions.first().unwrap().pc());
651
652 fn control_flow_addr(
653 cpu: &Cpu,
654 ctx: &impl CpuContext,
655 control_flow: ControlFlow,
656 ) -> Option<(&'static str, u32)> {
657 match control_flow {
658 ControlFlow::Call(_, pc) => Some((" ↦ ", cpu.pc_ext_addr(ctx, pc))),
659 ControlFlow::Choice(_, pc) => Some((" ↔ ", cpu.pc_ext_addr(ctx, pc))),
660 _ => None,
661 }
662 }
663
664 let control_flow = instructions
665 .iter()
666 .find(|instruction| instruction.pc() == focus_addr)
667 .map(|instruction| instruction.control_flow())
668 .and_then(|control_flow| control_flow_addr(cpu, ctx, control_flow));
669 let control_flow_pc = instructions
670 .iter()
671 .find(|instruction| instruction.pc() == pc)
672 .map(|instruction| instruction.control_flow())
673 .and_then(|control_flow| control_flow_addr(cpu, ctx, control_flow));
674
675 for instruction in instructions {
676 let bytes_str = instruction
677 .bytes()
678 .iter()
679 .map(|b| format!("{:02X}", b))
680 .collect::<Vec<_>>()
681 .join(" ");
682
683 let current_pc = instruction.pc();
684 let has_breakpoint = breakpoints.contains(¤t_pc);
685 let is_current = current_pc == pc;
686 let is_focused = current_pc == focus_addr;
687
688 let bp_marker = if has_breakpoint { "●" } else { " " };
689 let pc_marker = if is_current { ">" } else { " " };
690
691 let control_flow_marker = control_flow.and_then(|(marker, addr)| {
692 if addr == current_pc {
693 Some(marker)
694 } else {
695 None
696 }
697 });
698 let control_flow_marker_pc = control_flow_pc.and_then(|(marker, addr)| {
699 if addr == current_pc {
700 Some(marker)
701 } else {
702 None
703 }
704 });
705
706 let line_text = format!(
707 "{}{} {:04X}: {:12} {} ",
708 bp_marker,
709 pc_marker,
710 current_pc,
711 bytes_str,
712 instruction.decode(),
713 );
714
715 let style = if is_current {
716 Style::default()
717 .fg(Color::Yellow)
718 .add_modifier(Modifier::BOLD)
719 } else if has_breakpoint {
720 Style::default().fg(Color::Red)
721 } else {
722 Style::default()
723 }
724 .bg(if is_focused {
725 if is_current {
726 Color::Yellow
727 } else {
728 Color::Green
729 }
730 } else if code_window_focus {
731 Color::Black
732 } else {
733 Color::Reset
734 });
735
736 let mut line = Line::default().patch_style(style).spans([line_text]);
737 line.push_span(Span::styled(
738 control_flow_marker
739 .or(control_flow_marker_pc)
740 .unwrap_or_default(),
741 Style::default().bg(Color::Blue),
742 ));
743 let text = Text::from(line);
744 lines.push(text);
745 }
746
747 for (row, line) in area.rows().zip(lines) {
748 f.render_widget(line, row);
749 }
750}
751
752fn render_registers(
753 f: &mut Frame,
754 area: Rect,
755 cpu: &Cpu,
756 focus: DebuggerFocus,
757 is_editing: bool,
758 edit_buffer: &str,
759) {
760 let mut lines = Vec::new();
761
762 let is_reg_focused = matches!(focus, DebuggerFocus::Reg(_));
763
764 let format_value = |reg: Register, default_value: String| -> String {
766 if is_editing && focus == DebuggerFocus::Reg(reg) {
767 let max_len: usize = match reg {
769 Register::PC | Register::DPTR => 4,
770 _ => 2,
771 };
772 format!("{}_", edit_buffer) + &"_".repeat(max_len.saturating_sub(edit_buffer.len() + 1))
773 } else {
774 default_value
775 }
776 };
777
778 let reg_style = |reg: Register| {
780 if focus == DebuggerFocus::Reg(reg) {
781 if is_editing {
782 Style::default()
783 .fg(if is_reg_focused {
784 Color::LightGreen
785 } else {
786 Color::Green
787 })
788 .add_modifier(Modifier::BOLD | Modifier::UNDERLINED)
789 } else {
790 Style::default()
791 .fg(if is_reg_focused {
792 Color::LightYellow
793 } else {
794 Color::Yellow
795 })
796 .add_modifier(Modifier::BOLD)
797 }
798 } else {
799 Style::default()
800 }
801 };
802
803 let sp = cpu.sp();
805 let pc_sp_line = vec![
806 Span::styled(
807 format!(
808 "PC:{}",
809 format_value(Register::PC, format!("{:04X}", cpu.pc))
810 ),
811 reg_style(Register::PC),
812 ),
813 Span::raw(" "),
814 Span::styled(
815 format!("SP:{}", format_value(Register::SP, format!("{:02X}", sp))),
816 reg_style(Register::SP),
817 ),
818 Span::raw(" "),
819 Span::styled(
820 format!(
821 "IP:{}",
822 format_value(Register::IP, format!("{:02X}", cpu.ip()))
823 ),
824 reg_style(Register::IP),
825 ),
826 Span::raw(" "),
827 Span::styled(
828 format!(
829 "IE:{}",
830 format_value(Register::IE, format!("{:02X}", cpu.ie()))
831 ),
832 reg_style(Register::IE),
833 ),
834 ];
835 lines.push(Line::from(pc_sp_line));
836
837 let mut r_spans = Vec::new();
839 for i in 0..8 {
840 if i > 0 {
841 r_spans.push(Span::raw(" "));
842 }
843 r_spans.push(Span::styled(
844 format!(
845 "R{}:{}",
846 i,
847 format_value(Register::R(i), format!("{:02X}", cpu.r(i)))
848 ),
849 reg_style(Register::R(i)),
850 ));
851 }
852 lines.push(Line::from(r_spans));
853
854 let abc_line = vec![
856 Span::styled(
857 format!(
858 "A:{}",
859 format_value(Register::A, format!("{:02X}", cpu.a()))
860 ),
861 reg_style(Register::A),
862 ),
863 Span::raw(" "),
864 Span::styled(
865 format!(
866 "B:{}",
867 format_value(Register::B, format!("{:02X}", cpu.b()))
868 ),
869 reg_style(Register::B),
870 ),
871 Span::raw(" "),
872 Span::styled(
873 format!(
874 "DPTR:{}",
875 format_value(Register::DPTR, format!("{:04X}", cpu.dptr()))
876 ),
877 reg_style(Register::DPTR),
878 ),
879 ];
880 lines.push(Line::from(abc_line));
881 lines.push(Line::from(""));
882
883 let mut flag_spans = Vec::new();
885 for (i, flag) in Flag::all().iter().enumerate() {
886 if i > 0 {
887 flag_spans.push(Span::raw(" "));
888 }
889 let style = if cpu.psw(*flag) {
890 Style::default().add_modifier(Modifier::BOLD)
891 } else {
892 Style::default().add_modifier(Modifier::DIM)
893 };
894 flag_spans.push(Span::styled(flag.short_name(), style));
895 }
896 lines.push(Line::from(flag_spans));
897 lines.push(Line::from(""));
898
899 lines.push(Line::from(Span::styled(
901 "Internal RAM:",
902 Style::default().add_modifier(Modifier::BOLD),
903 )));
904
905 let sp = cpu.sp() as usize;
907 let register_bank =
908 (if cpu.psw(Flag::RS1) { 2 } else { 0 }) + (if cpu.psw(Flag::RS0) { 1 } else { 0 });
909 let bank_start = register_bank * 8;
910 let bank_end = bank_start + 8;
911
912 for row in 0..8 {
914 let addr = row * 16;
915 let mut spans = vec![Span::styled(
916 format!("{:02X}: ", addr),
917 Style::default().add_modifier(Modifier::DIM),
918 )];
919
920 for col in 0..16 {
921 let byte_addr = addr + col;
922 let byte_val = cpu.internal_ram[byte_addr];
923 let in_current_register_bank = byte_addr >= bank_start && byte_addr < bank_end;
924
925 let style = if byte_addr == sp.wrapping_add(1) {
927 Style::default().bg(Color::Green).fg(Color::Black)
929 } else if in_current_register_bank {
930 Style::default().bg(Color::Yellow).fg(Color::Black)
932 } else {
933 Style::default()
934 };
935
936 if in_current_register_bank && byte_addr < bank_end - 1 {
937 spans.push(Span::styled(format!("{:02X} ", byte_val), style));
938 } else {
939 spans.push(Span::styled(format!("{:02X}", byte_val), style));
940 spans.push(Span::raw(" "));
941 }
942 }
943
944 lines.push(Line::from(spans));
945 }
946
947 let paragraph = Paragraph::new(lines)
948 .wrap(Wrap { trim: false })
949 .style(Style::default().bg(if is_reg_focused {
950 Color::Black
951 } else {
952 Color::Reset
953 }));
954
955 f.render_widget(paragraph, area);
956}
957
958fn render_output(f: &mut Frame, area: Rect, trace_events: &TracingCollector, focus: DebuggerFocus) {
959 let is_output_focused = matches!(focus, DebuggerFocus::Output);
960
961 let mut lines = vec![Line::from(Span::styled(
963 "Trace Output:",
964 Style::default().add_modifier(Modifier::BOLD | Modifier::UNDERLINED),
965 ))];
966
967 let available_lines = area.height.saturating_sub(1) as usize; trace_events.with_last_n(available_lines, &mut |level: Level, event: &str| {
970 let level_color = match level {
972 Level::ERROR => Color::Red,
973 Level::WARN => Color::Yellow,
974 Level::INFO => Color::Green,
975 Level::DEBUG => Color::Cyan,
976 Level::TRACE => Color::DarkGray,
977 };
978
979 lines.push(Line::from(vec![Span::styled(
980 event.to_string(),
981 Style::default().fg(level_color),
982 )]));
983 });
984
985 let paragraph = Paragraph::new(lines)
986 .wrap(Wrap { trim: false })
987 .style(Style::default().bg(if is_output_focused {
988 Color::Black
989 } else {
990 Color::Reset
991 }));
992
993 f.render_widget(paragraph, area);
994}
995
996impl Debugger {
997 pub fn enter(&mut self) -> io::Result<()> {
999 crossterm::terminal::enable_raw_mode()?;
1000 crossterm::execute!(io::stdout(), crossterm::terminal::EnterAlternateScreen,)?;
1001 self.terminal.clear()?;
1002 Ok(())
1003 }
1004
1005 pub fn exit(&mut self) -> io::Result<()> {
1007 crossterm::terminal::disable_raw_mode()?;
1008 crossterm::execute!(io::stdout(), crossterm::terminal::LeaveAlternateScreen,)?;
1009 Ok(())
1010 }
1011}
1012
1013impl Drop for Debugger {
1014 fn drop(&mut self) {
1015 let _ = self.exit();
1016 }
1017}