mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
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:
committed by
GitHub
Unverified
parent
1aed01e99f
commit
7daaabc795
@@ -85,6 +85,14 @@ pub struct Cli {
|
||||
#[arg(long = "add-dir", value_name = "DIR", value_hint = ValueHint::DirPath)]
|
||||
pub add_dir: Vec<PathBuf>,
|
||||
|
||||
/// Disable alternate screen mode
|
||||
///
|
||||
/// Runs the TUI in inline mode, preserving terminal scrollback history. This is useful
|
||||
/// in terminal multiplexers like Zellij that follow the xterm spec strictly and disable
|
||||
/// scrollback in alternate screen buffers.
|
||||
#[arg(long = "no-alt-screen", default_value_t = false)]
|
||||
pub no_alt_screen: bool,
|
||||
|
||||
#[clap(skip)]
|
||||
pub config_overrides: CliConfigOverrides,
|
||||
}
|
||||
|
||||
+42
-1
@@ -22,6 +22,8 @@ use codex_core::config::resolve_oss_provider;
|
||||
use codex_core::find_thread_path_by_id_str;
|
||||
use codex_core::get_platform_sandbox;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::terminal::Multiplexer;
|
||||
use codex_protocol::config_types::AltScreenMode;
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::fs::OpenOptions;
|
||||
@@ -493,7 +495,15 @@ async fn run_ratatui_app(
|
||||
resume_picker::ResumeSelection::StartFresh
|
||||
};
|
||||
|
||||
let Cli { prompt, images, .. } = cli;
|
||||
let Cli {
|
||||
prompt,
|
||||
images,
|
||||
no_alt_screen,
|
||||
..
|
||||
} = cli;
|
||||
|
||||
let use_alt_screen = determine_alt_screen_mode(no_alt_screen, config.tui_alternate_screen);
|
||||
tui.set_alt_screen_enabled(use_alt_screen);
|
||||
|
||||
let app_result = App::run(
|
||||
&mut tui,
|
||||
@@ -527,6 +537,37 @@ fn restore() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine whether to use the terminal's alternate screen buffer.
|
||||
///
|
||||
/// The alternate screen buffer provides a cleaner fullscreen experience without polluting
|
||||
/// the terminal's scrollback history. However, it conflicts with terminal multiplexers like
|
||||
/// Zellij that strictly follow the xterm spec, which disallows scrollback in alternate screen
|
||||
/// buffers. Zellij intentionally disables scrollback in alternate screen mode (see
|
||||
/// https://github.com/zellij-org/zellij/pull/1032) and offers no configuration option to
|
||||
/// change this behavior.
|
||||
///
|
||||
/// This function implements a pragmatic workaround:
|
||||
/// - If `--no-alt-screen` is explicitly passed, always disable alternate screen
|
||||
/// - Otherwise, respect the `tui.alternate_screen` config setting:
|
||||
/// - `always`: Use alternate screen everywhere (original behavior)
|
||||
/// - `never`: Inline mode only, preserves scrollback
|
||||
/// - `auto` (default): Auto-detect the terminal multiplexer and disable alternate screen
|
||||
/// only in Zellij, enabling it everywhere else
|
||||
fn determine_alt_screen_mode(no_alt_screen: bool, tui_alternate_screen: AltScreenMode) -> bool {
|
||||
if no_alt_screen {
|
||||
false
|
||||
} else {
|
||||
match tui_alternate_screen {
|
||||
AltScreenMode::Always => true,
|
||||
AltScreenMode::Never => false,
|
||||
AltScreenMode::Auto => {
|
||||
let terminal_info = codex_core::terminal::terminal_info();
|
||||
!matches!(terminal_info.multiplexer, Some(Multiplexer::Zellij { .. }))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LoginStatus {
|
||||
AuthMode(AuthMode),
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user