fix(tui): leave alternate screen during turns — zero output bleeding
The TUI is not active during tool execution. The flow becomes: 1. leave_for_turn() — exit alt screen, enable raw mode 2. cli.run_turn_to() — stdout/stderr goes to normal terminal 3. wait_to_return() — 'Press any key to return to TUI...' 4. reenter_after_turn() — re-enter alt screen, redraw This guarantees zero output bleeding. Replaces suspend/resume approach.
This commit is contained in:
parent
4af649b442
commit
25610f2a06
|
|
@ -7295,26 +7295,19 @@ fn run_tui_repl(mut cli: LiveCli) -> Result<(), Box<dyn std::error::Error>> {
|
|||
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<u8> = 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<u8> = 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.
|
||||
|
|
|
|||
|
|
@ -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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
let _ = self.terminal.show_cursor();
|
||||
|
|
|
|||
Loading…
Reference in New Issue