Bugfix: Console locked

This commit is contained in:
Jan-Peter von Hunnius 2025-09-29 20:40:06 +02:00
parent 0278a96a8d
commit 77ede95bce
2 changed files with 90 additions and 68 deletions

View file

@ -16,5 +16,3 @@ crossterm = "0.27"
ratatui = "0.24" ratatui = "0.24"
[dev-dependencies] [dev-dependencies]
# Property-based testing
proptest = "1.0"

View file

@ -84,64 +84,76 @@ impl ConsoleApp {
pub fn run(&mut self) -> Result<(), Box<dyn std::error::Error>> { pub fn run(&mut self) -> Result<(), Box<dyn std::error::Error>> {
// Setup terminal // Setup terminal
enable_raw_mode()?; enable_raw_mode().map_err(|e| format!("Failed to enable raw mode: {}", e))?;
let mut stdout = io::stdout(); let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; execute!(stdout, EnterAlternateScreen).map_err(|e| format!("Failed to enter alternate screen: {}", e))?;
let backend = CrosstermBackend::new(stdout); let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?; let mut terminal = Terminal::new(backend)?;
self.running = true; self.running = true;
let mut last_draw = Instant::now();
// Main event loop // Main event loop
while self.running { while self.running {
let now = Instant::now();
// Handle input // Handle input
if event::poll(Duration::from_millis(50))? { if let Ok(true) = event::poll(Duration::from_millis(10)) {
if let Event::Key(key) = event::read()? { if let Ok(Event::Key(key)) = event::read() {
self.handle_key_event(key.code); self.handle_key_event(key.code);
} }
} }
// Update display if needed // Update display at regular intervals
if self.last_update.elapsed() >= Duration::from_millis(self.config.refresh_rate_ms) { if now.duration_since(last_draw) >= Duration::from_millis(self.config.refresh_rate_ms) {
terminal.draw(|f| self.draw_ui(f))?; if let Err(e) = terminal.draw(|f| self.draw_ui(f)) {
self.last_update = Instant::now(); eprintln!("DEBUG: Failed to draw UI: {}", e);
break;
}
last_draw = now;
} }
// Check if system should still be running // Check if system should still be running
if let Ok(system) = self.system.lock() { if let Ok(system) = self.system.lock() {
if !system.is_running() && !self.show_help { if !system.is_running() && !self.show_help {
// System stopped, show final state // System stopped, show final state briefly
terminal.draw(|f| self.draw_ui(f))?; if let Err(e) = terminal.draw(|f| self.draw_ui(f)) {
thread::sleep(Duration::from_millis(1000)); eprintln!("DEBUG: Failed to draw final UI: {}", e);
}
thread::sleep(Duration::from_millis(500));
break; break;
} }
} }
// Small delay to prevent busy waiting
thread::sleep(Duration::from_millis(1));
} }
// Restore terminal // Restore terminal
disable_raw_mode()?; disable_raw_mode().map_err(|e| format!("Failed to disable raw mode: {}", e))?;
execute!( execute!(
terminal.backend_mut(), terminal.backend_mut(),
LeaveAlternateScreen, LeaveAlternateScreen
DisableMouseCapture ).map_err(|e| format!("Failed to leave alternate screen: {}", e))?;
)?; terminal.show_cursor().map_err(|e| format!("Failed to show cursor: {}", e))?;
terminal.show_cursor()?;
Ok(()) Ok(())
} }
fn handle_key_event(&mut self, key: KeyCode) { fn handle_key_event(&mut self, key: KeyCode) {
match key { match key {
KeyCode::Char('q') | KeyCode::Esc => { KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => {
println!("DEBUG: Quit key pressed, stopping console");
self.running = false; self.running = false;
if let Ok(mut system) = self.system.lock() { if let Ok(mut system) = self.system.lock() {
system.stop(); system.stop();
} }
} }
KeyCode::Char('h') => { KeyCode::Char('h') | KeyCode::Char('H') => {
self.show_help = !self.show_help; self.show_help = !self.show_help;
} }
KeyCode::Char('r') => { KeyCode::Char('r') | KeyCode::Char('R') => {
if let Ok(mut system) = self.system.lock() { if let Ok(mut system) = self.system.lock() {
if system.is_running() { if system.is_running() {
system.stop(); system.stop();
@ -150,7 +162,7 @@ impl ConsoleApp {
} }
} }
} }
KeyCode::Char('s') => { KeyCode::Char('s') | KeyCode::Char('S') => {
if let Ok(mut system) = self.system.lock() { if let Ok(mut system) = self.system.lock() {
system.stop(); system.stop();
} }
@ -170,8 +182,10 @@ impl ConsoleApp {
self.command_buffer.clear(); self.command_buffer.clear();
} }
KeyCode::Char(c) => { KeyCode::Char(c) => {
if c.is_ascii_alphabetic() || c.is_ascii_digit() {
self.command_buffer.push(c); self.command_buffer.push(c);
} }
}
_ => {} _ => {}
} }
} }
@ -180,6 +194,7 @@ impl ConsoleApp {
let cmd = self.command_buffer.trim().to_lowercase(); let cmd = self.command_buffer.trim().to_lowercase();
match cmd.as_str() { match cmd.as_str() {
"quit" | "exit" | "q" => { "quit" | "exit" | "q" => {
println!("DEBUG: Executing quit command");
self.running = false; self.running = false;
if let Ok(mut system) = self.system.lock() { if let Ok(mut system) = self.system.lock() {
system.stop(); system.stop();
@ -206,6 +221,7 @@ impl ConsoleApp {
} }
_ => { _ => {
// Unknown command - could show error message // Unknown command - could show error message
println!("DEBUG: Unknown command: {}", cmd);
} }
} }
} }
@ -218,36 +234,37 @@ impl ConsoleApp {
return; return;
} }
// Create main layout // Create main layout with proper constraints
let chunks = Layout::default() let chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([ .constraints([
Constraint::Length(3), // Title bar Constraint::Length(4), // Title bar
Constraint::Min(10), // Main content Constraint::Min(8), // Main content
Constraint::Length(3), // Command bar Constraint::Length(3), // Command bar
]) ])
.split(size); .split(size);
// Title bar // Title bar
let title = Paragraph::new(vec![ let title_text = vec![
Line::from(vec![ Line::from(vec![
Span::styled("Intel MCS-4 Emulator Console", Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)), Span::styled("Intel MCS-4 Emulator Console", Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),
]), ]),
Line::from(vec![ Line::from(vec![
Span::raw("Commands: "), Span::raw("Commands: "),
Span::styled("q", Style::default().fg(Color::Yellow)), Span::styled("q/Q", Style::default().fg(Color::Yellow)),
Span::raw("uit, "), Span::raw("=quit, "),
Span::styled("r", Style::default().fg(Color::Yellow)), Span::styled("r/R", Style::default().fg(Color::Yellow)),
Span::raw("un, "), Span::raw("=run, "),
Span::styled("s", Style::default().fg(Color::Yellow)), Span::styled("s/S", Style::default().fg(Color::Yellow)),
Span::raw("top, "), Span::raw("=stop, "),
Span::styled("h", Style::default().fg(Color::Yellow)), Span::styled("h/H", Style::default().fg(Color::Yellow)),
Span::raw("elp, "), Span::raw("=help"),
Span::styled("Tab", Style::default().fg(Color::Yellow)),
Span::raw(" to switch panes"),
]), ]),
]) ];
.block(Block::default().borders(Borders::ALL).title("Status"));
let title = Paragraph::new(title_text)
.block(Block::default().borders(Borders::ALL).title("Status"))
.wrap(Wrap { trim: true });
f.render_widget(title, chunks[0]); f.render_widget(title, chunks[0]);
// Main content area // Main content area
@ -315,9 +332,10 @@ impl ConsoleApp {
.split(area); .split(area);
// System information // System information
let system_info = if let Ok(system) = self.system.lock() { let system_info = match self.system.lock() {
let info = system.get_system_info(); Ok(system) => {
vec![ match system.get_system_info() {
info => vec![
Line::from(vec![Span::raw(format!("System: {}", info.name))]), Line::from(vec![Span::raw(format!("System: {}", info.name))]),
Line::from(vec![Span::raw(format!("Description: {}", info.description))]), Line::from(vec![Span::raw(format!("Description: {}", info.description))]),
Line::from(vec![Span::raw(format!("Components: {}", info.component_count))]), Line::from(vec![Span::raw(format!("Components: {}", info.component_count))]),
@ -326,8 +344,11 @@ impl ConsoleApp {
Line::from(vec![Span::raw(format!("RAM Size: {} nibbles", info.ram_size))]), Line::from(vec![Span::raw(format!("RAM Size: {} nibbles", info.ram_size))]),
Line::from(vec![Span::raw(format!("Running: {}", system.is_running()))]), Line::from(vec![Span::raw(format!("Running: {}", system.is_running()))]),
] ]
} else { }
}
Err(_) => {
vec![Line::from(vec![Span::raw("System information unavailable")])] vec![Line::from(vec![Span::raw("System information unavailable")])]
}
}; };
let system_widget = Paragraph::new(system_info) let system_widget = Paragraph::new(system_info)
@ -354,7 +375,8 @@ impl ConsoleApp {
let mut ram_info = vec![Line::from(vec![Span::raw("RAM Contents:")])]; let mut ram_info = vec![Line::from(vec![Span::raw("RAM Contents:")])];
// Try to get actual RAM data from the system // Try to get actual RAM data from the system
if let Ok(_system) = self.system.lock() { match self.system.lock() {
Ok(_system) => {
// This is a simplified version - in a real implementation, // This is a simplified version - in a real implementation,
// we would need to access the actual RAM components // we would need to access the actual RAM components
// For now, show a more informative placeholder // For now, show a more informative placeholder
@ -372,9 +394,11 @@ impl ConsoleApp {
bank_data.push(']'); bank_data.push(']');
ram_info.push(Line::from(vec![Span::raw(bank_data)])); ram_info.push(Line::from(vec![Span::raw(bank_data)]));
} }
} else { }
Err(_) => {
ram_info.push(Line::from(vec![Span::raw("Unable to access RAM data")])); ram_info.push(Line::from(vec![Span::raw("Unable to access RAM data")]));
} }
}
let ram_widget = Paragraph::new(ram_info) let ram_widget = Paragraph::new(ram_info)
.block(Block::default().borders(Borders::ALL).title("RAM Contents")) .block(Block::default().borders(Borders::ALL).title("RAM Contents"))