fix: add tui.alternate_screen config and --no-alt-screen CLI flag for Zellij scrollback (#8555)

Fixes #2558

Codex uses alternate screen mode (CSI 1049) which, per xterm spec,
doesn't support scrollback. Zellij follows this strictly, so users can't
scroll back through output.

**Changes:**
- Add `tui.alternate_screen` config: `auto` (default), `always`, `never`
- Add `--no-alt-screen` CLI flag
- Auto-detect Zellij and skip alt screen (uses existing `ZELLIJ` env var
detection)

**Usage:**
```bash
# CLI flag
codex --no-alt-screen

# Or in config.toml
[tui]
alternate_screen = "never"
```

With default `auto` mode, Zellij users get working scrollback without
any config changes.

---------

Co-authored-by: Josh McKinney <joshka@openai.com>
This commit is contained in:
Helmut Januschka
2026-01-09 19:38:26 +01:00
committed by GitHub
parent 1aed01e99f
commit 7daaabc795
11 changed files with 310 additions and 3 deletions

View File

@@ -247,6 +247,8 @@ pub struct Tui {
terminal_focused: Arc<AtomicBool>,
enhanced_keys_supported: bool,
notification_backend: Option<DesktopNotificationBackend>,
// When false, enter_alt_screen() becomes a no-op (for Zellij scrollback support)
alt_screen_enabled: bool,
}
impl Tui {
@@ -274,9 +276,15 @@ impl Tui {
terminal_focused: Arc::new(AtomicBool::new(true)),
enhanced_keys_supported,
notification_backend: Some(detect_backend()),
alt_screen_enabled: true,
}
}
/// Set whether alternate screen is enabled. When false, enter_alt_screen() becomes a no-op.
pub fn set_alt_screen_enabled(&mut self, enabled: bool) {
self.alt_screen_enabled = enabled;
}
pub fn frame_requester(&self) -> FrameRequester {
self.frame_requester.clone()
}
@@ -407,6 +415,9 @@ impl Tui {
/// Enter alternate screen and expand the viewport to full terminal size, saving the current
/// inline viewport for restoration when leaving.
pub fn enter_alt_screen(&mut self) -> Result<()> {
if !self.alt_screen_enabled {
return Ok(());
}
let _ = execute!(self.terminal.backend_mut(), EnterAlternateScreen);
// Enable "alternate scroll" so terminals may translate wheel to arrows
let _ = execute!(self.terminal.backend_mut(), EnableAlternateScroll);
@@ -426,6 +437,9 @@ impl Tui {
/// Leave alternate screen and restore the previously saved inline viewport, if any.
pub fn leave_alt_screen(&mut self) -> Result<()> {
if !self.alt_screen_enabled {
return Ok(());
}
// Disable alternate scroll when leaving alt-screen
let _ = execute!(self.terminal.backend_mut(), DisableAlternateScroll);
let _ = execute!(self.terminal.backend_mut(), LeaveAlternateScreen);