fix(tui): keep interactive sessions in alternate screen (#1158)
This commit is contained in:
@@ -84,7 +84,7 @@ struct Cli {
|
||||
api_key: Option<String>,
|
||||
#[arg(long)]
|
||||
base_url: Option<String>,
|
||||
#[arg(long = "no-alt-screen")]
|
||||
#[arg(long = "no-alt-screen", hide = true)]
|
||||
no_alt_screen: bool,
|
||||
#[arg(long = "mouse-capture", conflicts_with = "no_mouse_capture")]
|
||||
mouse_capture: bool,
|
||||
@@ -1356,9 +1356,9 @@ fn build_tui_command(
|
||||
if let Some(profile) = cli.profile.as_ref() {
|
||||
cmd.arg("--profile").arg(profile);
|
||||
}
|
||||
if cli.no_alt_screen {
|
||||
cmd.arg("--no-alt-screen");
|
||||
}
|
||||
// Accepted for older scripts, but no longer forwarded: the interactive TUI
|
||||
// always owns the alternate screen to avoid host scrollback hijacking.
|
||||
let _ = cli.no_alt_screen;
|
||||
if cli.mouse_capture {
|
||||
cmd.arg("--mouse-capture");
|
||||
}
|
||||
@@ -2569,7 +2569,6 @@ mod tests {
|
||||
"--api-key",
|
||||
"--approval-policy",
|
||||
"--sandbox-mode",
|
||||
"--no-alt-screen",
|
||||
"--mouse-capture",
|
||||
"--no-mouse-capture",
|
||||
"--skip-onboarding",
|
||||
|
||||
+9
-23
@@ -145,8 +145,9 @@ struct Cli {
|
||||
#[arg(short = 'c', long = "continue")]
|
||||
continue_session: bool,
|
||||
|
||||
/// Disable the alternate screen buffer (inline mode)
|
||||
#[arg(long = "no-alt-screen")]
|
||||
/// Deprecated compatibility flag; the interactive TUI always owns the
|
||||
/// alternate screen so terminal scrollback cannot hijack the viewport.
|
||||
#[arg(long = "no-alt-screen", hide = true)]
|
||||
no_alt_screen: bool,
|
||||
|
||||
/// Enable TUI mouse capture for internal scrolling and transcript selection
|
||||
@@ -3623,23 +3624,8 @@ fn parse_sandbox_policy(
|
||||
}
|
||||
}
|
||||
|
||||
fn should_use_alt_screen(cli: &Cli, config: &Config) -> bool {
|
||||
if cli.no_alt_screen {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mode = config
|
||||
.tui
|
||||
.as_ref()
|
||||
.and_then(|tui| tui.alternate_screen.as_deref())
|
||||
.unwrap_or("auto")
|
||||
.to_ascii_lowercase();
|
||||
|
||||
match mode.as_str() {
|
||||
"always" => true,
|
||||
"never" => false,
|
||||
_ => true,
|
||||
}
|
||||
fn should_use_alt_screen(_cli: &Cli, _config: &Config) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn should_use_mouse_capture(cli: &Cli, config: &Config, use_alt_screen: bool) -> bool {
|
||||
@@ -4590,15 +4576,15 @@ mod terminal_mode_tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_alt_screen_flag_disables_alternate_screen() {
|
||||
fn no_alt_screen_flag_is_accepted_but_keeps_alternate_screen() {
|
||||
let cli = parse_cli(&["deepseek", "--no-alt-screen"]);
|
||||
let config = Config::default();
|
||||
|
||||
assert!(!should_use_alt_screen(&cli, &config));
|
||||
assert!(should_use_alt_screen(&cli, &config));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_can_disable_alternate_screen() {
|
||||
fn config_never_is_accepted_but_keeps_alternate_screen() {
|
||||
let cli = parse_cli(&["deepseek"]);
|
||||
let config = Config {
|
||||
tui: Some(crate::config::TuiConfig {
|
||||
@@ -4612,7 +4598,7 @@ mod terminal_mode_tests {
|
||||
..Config::default()
|
||||
};
|
||||
|
||||
assert!(!should_use_alt_screen(&cli, &config));
|
||||
assert!(should_use_alt_screen(&cli, &config));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -135,7 +135,7 @@ const SIDEBAR_VISIBLE_MIN_WIDTH: u16 = 100;
|
||||
const DEFAULT_TERMINAL_PROBE_TIMEOUT_MS: u64 = 500;
|
||||
|
||||
type AppTerminal = Terminal<ColorCompatBackend<Stdout>>;
|
||||
const TERMINAL_ORIGIN_RESET: &[u8] = b"\x1b[r\x1b[?6l\x1b[H\x1b[2J";
|
||||
const TERMINAL_ORIGIN_RESET: &[u8] = b"\x1b[r\x1b[?6l\x1b[H\x1b[2J\x1b[3J";
|
||||
|
||||
/// Run the interactive TUI event loop.
|
||||
///
|
||||
@@ -6291,7 +6291,9 @@ fn reset_terminal_viewport(terminal: &mut AppTerminal) -> Result<()> {
|
||||
// Reset scroll margins and origin mode before clearing. Some interactive
|
||||
// child processes leave DECSTBM/DECOM behind; if ratatui's diff renderer
|
||||
// then writes "row 0", terminals can place it relative to the leaked
|
||||
// scroll region and the whole viewport appears shifted down.
|
||||
// scroll region and the whole viewport appears shifted down. CSI 3J also
|
||||
// erases saved scrollback so a focus/resize recapture cannot leave the
|
||||
// host terminal's scrollbar above the live TUI.
|
||||
terminal.backend_mut().write_all(TERMINAL_ORIGIN_RESET)?;
|
||||
terminal.backend_mut().flush()?;
|
||||
terminal.clear()?;
|
||||
|
||||
@@ -46,9 +46,15 @@ fn terminal_origin_reset_resets_scroll_region_origin_and_clears() {
|
||||
"must reset scroll margins and origin mode before repaint"
|
||||
);
|
||||
assert!(
|
||||
TERMINAL_ORIGIN_RESET.ends_with(b"\x1b[H\x1b[2J"),
|
||||
TERMINAL_ORIGIN_RESET.ends_with(b"\x1b[H\x1b[2J\x1b[3J"),
|
||||
"must home the cursor and clear the viewport"
|
||||
);
|
||||
assert!(
|
||||
TERMINAL_ORIGIN_RESET
|
||||
.windows(b"\x1b[3J".len())
|
||||
.any(|sequence| sequence == b"\x1b[3J"),
|
||||
"must erase saved scrollback when reclaiming the viewport"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -452,7 +452,7 @@ If you are upgrading from older releases:
|
||||
- `[notifications].include_summary` (bool, optional): defaults to
|
||||
`false`. When `true`, the notification body includes the elapsed
|
||||
duration and the turn's cost in the configured display currency.
|
||||
- `tui.alternate_screen` (string, optional): `auto`, `always`, or `never`. `auto` and `always` use the TUI-owned alternate screen so transcript scrolling stays inside the app; `--no-alt-screen` forces inline mode. Set `never` or run with `--no-alt-screen` only when you intentionally want real terminal scrollback.
|
||||
- `tui.alternate_screen` (string, optional): `auto`, `always`, or `never`. This is retained for config compatibility, but interactive sessions now always use the TUI-owned alternate screen so host terminal scrollback cannot hijack the viewport.
|
||||
- `tui.mouse_capture` (bool, optional, default `true` on non-Windows terminals when the alternate screen is active; `false` on Windows and inside JetBrains JediTerm — PyCharm/IDEA/CLion/etc. — where mouse-event escapes leak into the input stream as garbled text, see #878 / #898): enable internal mouse scrolling, transcript selection, and right-click context actions. TUI-owned drag selection copies only user/assistant transcript text. Set this to `false` or run with `--no-mouse-capture` for raw terminal selection; set it to `true` or run with `--mouse-capture` to opt in anywhere it's defaulted off.
|
||||
- `tui.terminal_probe_timeout_ms` (int, optional, default `500`): startup terminal-mode probe timeout in milliseconds. Values are clamped to `100..=5000`; timeout emits a warning and aborts startup instead of hanging indefinitely.
|
||||
- `tui.osc8_links` (bool, optional, default `true`): emit OSC 8 escape sequences around URLs in transcript output so terminals that support them (iTerm2, Terminal.app 13+, Ghostty, Kitty, WezTerm, Alacritty, recent gnome-terminal/konsole) render them as Cmd+click hyperlinks. Terminals without OSC 8 support render the plain URL and ignore the escape. Set `false` for terminals that misrender the sequence; selection/clipboard output always strips the escapes.
|
||||
|
||||
@@ -83,7 +83,6 @@ Run `deepseek --help` for the canonical list. Common flags:
|
||||
- `-r, --resume <ID|PREFIX|latest>`: resume a saved session
|
||||
- `-c, --continue`: resume the most recent session in this workspace
|
||||
- `--max-subagents <N>`: clamp to `1..=20`
|
||||
- `--no-alt-screen`: run inline without the alternate screen buffer
|
||||
- `--mouse-capture` / `--no-mouse-capture`: opt in or out of internal mouse scrolling, transcript selection, and right-click context actions. Mouse capture is enabled by default on non-Windows terminals so drag selection copies only user/assistant transcript text; hold Shift while dragging or use `--no-mouse-capture` for raw terminal selection. It defaults off on Windows (CMD/terminal mouse-escape spam in the prompt) and inside JetBrains JediTerm — PyCharm/IDEA/CLion/etc. — where the terminal advertises mouse support but forwards SGR mouse events as raw text (#878, #898). Use `--mouse-capture` to opt in anywhere it's defaulted off.
|
||||
- `--profile <NAME>`: select config profile
|
||||
- `--config <PATH>`: config file path
|
||||
|
||||
Reference in New Issue
Block a user