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...");
|
app.set_status("Thinking...");
|
||||||
|
|
||||||
// ── Turn execution ──
|
// ── Turn execution ──
|
||||||
// Capture stdout/stderr safely with `gag` so child processes
|
// Exit the alternate screen before running the turn so that
|
||||||
// can't bleed output onto the TUI. Then render any captured
|
// any stdout/stderr from tools goes to the normal terminal
|
||||||
// tool output inside the conversation pane.
|
// instead of corrupting the TUI frame. After the turn the
|
||||||
let (result, captured_stdout, captured_stderr) =
|
// user presses a key to return to the TUI, which is then
|
||||||
crate::tui::capture::capture_output(|| {
|
// fully redrawn.
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
app.leave_for_turn()?;
|
||||||
cli.run_turn_to(&trimmed, &mut buf, false)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Render captured stdout/stderr from tools (only if meaningful)
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
if !captured_stdout.is_empty() {
|
let result = cli.run_turn_to(&trimmed, &mut buf, false);
|
||||||
app.push_output("```tool-output\n", false);
|
|
||||||
app.push_output(&captured_stdout, false);
|
// Wait for user acknowledgment before redrawing the TUI.
|
||||||
app.push_output("\n```\n", false);
|
app.wait_to_return()?;
|
||||||
}
|
app.reenter_after_turn()?;
|
||||||
if !captured_stderr.is_empty() {
|
|
||||||
app.push_output("```tool-error\n", false);
|
|
||||||
app.push_output(&captured_stderr, true);
|
|
||||||
app.push_output("\n```\n", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the last assistant message from the session for
|
// Read the last assistant message from the session for
|
||||||
// the conversation pane.
|
// the conversation pane.
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,60 @@ impl TuiApp {
|
||||||
Ok(())
|
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.
|
/// Restore terminal on exit.
|
||||||
pub fn restore_terminal(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn restore_terminal(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let _ = self.terminal.show_cursor();
|
let _ = self.terminal.show_cursor();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue