diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 40064912..002ba90e 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -7295,26 +7295,19 @@ fn run_tui_repl(mut cli: LiveCli) -> Result<(), Box> { app.set_status("Thinking..."); // ── Turn execution ── - // Capture stdout/stderr safely with `gag` so child processes - // can't bleed output onto the TUI. Then render any captured - // tool output inside the conversation pane. - let (result, captured_stdout, captured_stderr) = - crate::tui::capture::capture_output(|| { - let mut buf: Vec = Vec::new(); - cli.run_turn_to(&trimmed, &mut buf, false) - }); + // Exit the alternate screen before running the turn so that + // any stdout/stderr from tools goes to the normal terminal + // instead of corrupting the TUI frame. After the turn the + // user presses a key to return to the TUI, which is then + // fully redrawn. + app.leave_for_turn()?; - // Render captured stdout/stderr from tools (only if meaningful) - if !captured_stdout.is_empty() { - app.push_output("```tool-output\n", false); - app.push_output(&captured_stdout, false); - app.push_output("\n```\n", false); - } - if !captured_stderr.is_empty() { - app.push_output("```tool-error\n", false); - app.push_output(&captured_stderr, true); - app.push_output("\n```\n", false); - } + let mut buf: Vec = Vec::new(); + let result = cli.run_turn_to(&trimmed, &mut buf, false); + + // Wait for user acknowledgment before redrawing the TUI. + app.wait_to_return()?; + app.reenter_after_turn()?; // Read the last assistant message from the session for // the conversation pane. diff --git a/rust/crates/rusty-claude-cli/src/tui/app.rs b/rust/crates/rusty-claude-cli/src/tui/app.rs index c77e0580..b0903274 100644 --- a/rust/crates/rusty-claude-cli/src/tui/app.rs +++ b/rust/crates/rusty-claude-cli/src/tui/app.rs @@ -108,6 +108,60 @@ impl TuiApp { Ok(()) } + /// Leave the TUI before a turn: exit alt screen, disable raw mode, + /// leave cursor visible, and clear. Stdout during the turn now goes + /// to the normal terminal instead of corrupting the TUI frame. + pub fn leave_for_turn(&mut self) -> Result<(), Box> { + let _ = self.terminal.show_cursor(); + disable_raw_mode()?; + crossterm::execute!( + io::stdout(), + crossterm::terminal::LeaveAlternateScreen, + crossterm::terminal::Clear(ClearType::All), + crossterm::cursor::MoveTo(0, 0), + crossterm::cursor::Show + )?; + io::stdout().flush()?; + Ok(()) + } + + /// Wait for one keypress before re-entering the TUI. + /// Gives the user a chance to read any tool output that printed + /// to the terminal during the turn. + pub fn wait_to_return(&mut self) -> Result<(), Box> { + enable_raw_mode()?; + crossterm::execute!( + io::stdout(), + crossterm::style::Print("\nPress any key to return to TUI..."), + crossterm::cursor::MoveTo(0, 0) + )?; + io::stdout().flush()?; + + // Block until a key is pressed + loop { + if event::poll(std::time::Duration::from_millis(100))? { + if let Event::Key(_) = event::read()? { + break; + } + } + } + Ok(()) + } + + /// Re-enter the TUI after a turn: alt screen, raw mode, clear, redraw. + pub fn reenter_after_turn(&mut self) -> Result<(), Box> { + crossterm::execute!( + io::stdout(), + crossterm::terminal::EnterAlternateScreen, + crossterm::terminal::Clear(ClearType::All), + crossterm::cursor::Hide + )?; + io::stdout().flush()?; + enable_raw_mode()?; + self.terminal.clear()?; + Ok(()) + } + /// Restore terminal on exit. pub fn restore_terminal(&mut self) -> Result<(), Box> { let _ = self.terminal.show_cursor();