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:
parent
1aed01e99f
commit
7daaabc795
11 changed files with 310 additions and 3 deletions
|
|
@ -95,7 +95,6 @@ function detectPackageManager() {
|
|||
return "bun";
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
__dirname.includes(".bun/install/global") ||
|
||||
__dirname.includes(".bun\\install\\global")
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ use crate::protocol::AskForApproval;
|
|||
use crate::protocol::SandboxPolicy;
|
||||
use codex_app_server_protocol::Tools;
|
||||
use codex_app_server_protocol::UserSavedConfig;
|
||||
use codex_protocol::config_types::AltScreenMode;
|
||||
use codex_protocol::config_types::ForcedLoginMethod;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
|
|
@ -236,6 +237,14 @@ pub struct Config {
|
|||
/// consistently to both mouse wheels and trackpads.
|
||||
pub tui_scroll_invert: bool,
|
||||
|
||||
/// Controls whether the TUI uses the terminal's alternate screen buffer.
|
||||
///
|
||||
/// This is the same `tui.alternate_screen` value from `config.toml` (see [`Tui`]).
|
||||
/// - `auto` (default): Disable alternate screen in Zellij, enable elsewhere.
|
||||
/// - `always`: Always use alternate screen (original behavior).
|
||||
/// - `never`: Never use alternate screen (inline mode, preserves scrollback).
|
||||
pub tui_alternate_screen: AltScreenMode,
|
||||
|
||||
/// The directory that should be treated as the current working directory
|
||||
/// for the session. All relative paths inside the business-logic layer are
|
||||
/// resolved against this path.
|
||||
|
|
@ -1443,6 +1452,11 @@ impl Config {
|
|||
.as_ref()
|
||||
.and_then(|t| t.scroll_wheel_like_max_duration_ms),
|
||||
tui_scroll_invert: cfg.tui.as_ref().map(|t| t.scroll_invert).unwrap_or(false),
|
||||
tui_alternate_screen: cfg
|
||||
.tui
|
||||
.as_ref()
|
||||
.map(|t| t.alternate_screen)
|
||||
.unwrap_or_default(),
|
||||
otel: {
|
||||
let t: OtelConfigToml = cfg.otel.unwrap_or_default();
|
||||
let log_user_prompt = t.log_user_prompt.unwrap_or(false);
|
||||
|
|
@ -1641,6 +1655,7 @@ persistence = "none"
|
|||
scroll_wheel_tick_detect_max_ms: None,
|
||||
scroll_wheel_like_max_duration_ms: None,
|
||||
scroll_invert: false,
|
||||
alternate_screen: AltScreenMode::Auto,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -3276,6 +3291,7 @@ model_verbosity = "high"
|
|||
tui_scroll_wheel_tick_detect_max_ms: None,
|
||||
tui_scroll_wheel_like_max_duration_ms: None,
|
||||
tui_scroll_invert: false,
|
||||
tui_alternate_screen: AltScreenMode::Auto,
|
||||
otel: OtelConfig::default(),
|
||||
},
|
||||
o3_profile_config
|
||||
|
|
@ -3361,6 +3377,7 @@ model_verbosity = "high"
|
|||
tui_scroll_wheel_tick_detect_max_ms: None,
|
||||
tui_scroll_wheel_like_max_duration_ms: None,
|
||||
tui_scroll_invert: false,
|
||||
tui_alternate_screen: AltScreenMode::Auto,
|
||||
otel: OtelConfig::default(),
|
||||
};
|
||||
|
||||
|
|
@ -3461,6 +3478,7 @@ model_verbosity = "high"
|
|||
tui_scroll_wheel_tick_detect_max_ms: None,
|
||||
tui_scroll_wheel_like_max_duration_ms: None,
|
||||
tui_scroll_invert: false,
|
||||
tui_alternate_screen: AltScreenMode::Auto,
|
||||
otel: OtelConfig::default(),
|
||||
};
|
||||
|
||||
|
|
@ -3547,6 +3565,7 @@ model_verbosity = "high"
|
|||
tui_scroll_wheel_tick_detect_max_ms: None,
|
||||
tui_scroll_wheel_like_max_duration_ms: None,
|
||||
tui_scroll_invert: false,
|
||||
tui_alternate_screen: AltScreenMode::Auto,
|
||||
otel: OtelConfig::default(),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
// Note this file should generally be restricted to simple struct/enum
|
||||
// definitions that do not contain business logic.
|
||||
|
||||
pub use codex_protocol::config_types::AltScreenMode;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -523,6 +524,17 @@ pub struct Tui {
|
|||
/// wheel and trackpad input.
|
||||
#[serde(default)]
|
||||
pub scroll_invert: bool,
|
||||
|
||||
/// Controls whether the TUI uses the terminal's alternate screen buffer.
|
||||
///
|
||||
/// - `auto` (default): Disable alternate screen in Zellij, enable elsewhere.
|
||||
/// - `always`: Always use alternate screen (original behavior).
|
||||
/// - `never`: Never use alternate screen (inline mode only, preserves scrollback).
|
||||
///
|
||||
/// Using alternate screen provides a cleaner fullscreen experience but prevents
|
||||
/// scrollback in terminal multiplexers like Zellij that follow the xterm spec.
|
||||
#[serde(default)]
|
||||
pub alternate_screen: AltScreenMode,
|
||||
}
|
||||
|
||||
const fn default_true() -> bool {
|
||||
|
|
|
|||
|
|
@ -80,3 +80,38 @@ pub enum TrustLevel {
|
|||
Trusted,
|
||||
Untrusted,
|
||||
}
|
||||
|
||||
/// Controls whether the TUI uses the terminal's alternate screen buffer.
|
||||
///
|
||||
/// **Background:** 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 specification, which defines
|
||||
/// that alternate screen buffers should not have scrollback.
|
||||
///
|
||||
/// **Zellij's behavior:** Zellij intentionally disables scrollback in alternate screen mode
|
||||
/// (see https://github.com/zellij-org/zellij/pull/1032) to comply with the xterm spec. This
|
||||
/// is by design and not configurable in Zellij—there is no option to enable scrollback in
|
||||
/// alternate screen mode.
|
||||
///
|
||||
/// **Solution:** This setting provides a pragmatic workaround:
|
||||
/// - `auto` (default): Automatically detect the terminal multiplexer. If running in Zellij,
|
||||
/// disable alternate screen to preserve scrollback. Enable it everywhere else.
|
||||
/// - `always`: Always use alternate screen mode (original behavior before this fix).
|
||||
/// - `never`: Never use alternate screen mode. Runs in inline mode, preserving scrollback
|
||||
/// in all multiplexers.
|
||||
///
|
||||
/// The CLI flag `--no-alt-screen` can override this setting at runtime.
|
||||
#[derive(
|
||||
Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum AltScreenMode {
|
||||
/// Auto-detect: disable alternate screen in Zellij, enable elsewhere.
|
||||
#[default]
|
||||
Auto,
|
||||
/// Always use alternate screen (original behavior).
|
||||
Always,
|
||||
/// Never use alternate screen (inline mode only).
|
||||
Never,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -85,6 +85,11 @@ pub struct Cli {
|
|||
#[arg(long = "add-dir", value_name = "DIR", value_hint = ValueHint::DirPath)]
|
||||
pub add_dir: Vec<PathBuf>,
|
||||
|
||||
/// Disable alternate screen mode for better scrollback in terminal multiplexers like Zellij.
|
||||
/// This runs the TUI in inline mode, preserving terminal scrollback history.
|
||||
#[arg(long = "no-alt-screen", default_value_t = false)]
|
||||
pub no_alt_screen: bool,
|
||||
|
||||
#[clap(skip)]
|
||||
pub config_overrides: CliConfigOverrides,
|
||||
}
|
||||
|
|
@ -109,6 +114,7 @@ impl From<codex_tui::Cli> for Cli {
|
|||
cwd: cli.cwd,
|
||||
web_search: cli.web_search,
|
||||
add_dir: cli.add_dir,
|
||||
no_alt_screen: cli.no_alt_screen,
|
||||
config_overrides: cli.config_overrides,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -515,12 +517,39 @@ async fn run_ratatui_app(
|
|||
resume_picker::ResumeSelection::StartFresh
|
||||
};
|
||||
|
||||
let Cli { prompt, images, .. } = cli;
|
||||
let Cli {
|
||||
prompt,
|
||||
images,
|
||||
no_alt_screen,
|
||||
..
|
||||
} = cli;
|
||||
|
||||
// Run the main chat + transcript UI on the terminal's alternate screen so
|
||||
// the entire viewport can be used without polluting normal scrollback. This
|
||||
// mirrors the behavior of the legacy TUI but keeps inline mode available
|
||||
// for smaller prompts like onboarding and model migration.
|
||||
//
|
||||
// However, alternate screen prevents scrollback in terminal multiplexers like
|
||||
// Zellij that strictly follow the xterm spec (which disallows scrollback in
|
||||
// alternate screen buffers). This auto-detects the terminal and disables
|
||||
// alternate screen in Zellij while keeping it enabled elsewhere.
|
||||
let use_alt_screen = if no_alt_screen {
|
||||
// CLI flag explicitly disables alternate screen
|
||||
false
|
||||
} else {
|
||||
match config.tui_alternate_screen {
|
||||
AltScreenMode::Always => true,
|
||||
AltScreenMode::Never => false,
|
||||
AltScreenMode::Auto => {
|
||||
// Auto-detect: disable in Zellij, enable elsewhere
|
||||
let terminal_info = codex_core::terminal::terminal_info();
|
||||
!matches!(terminal_info.multiplexer, Some(Multiplexer::Zellij { .. }))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Set flag on Tui so all enter_alt_screen() calls respect the setting
|
||||
tui.set_alt_screen_enabled(use_alt_screen);
|
||||
let _ = tui.enter_alt_screen();
|
||||
|
||||
let app_result = App::run(
|
||||
|
|
|
|||
|
|
@ -143,6 +143,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 {
|
||||
|
|
@ -170,9 +172,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()
|
||||
}
|
||||
|
|
@ -309,6 +317,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(());
|
||||
}
|
||||
if !self.alt_screen_nesting.enter() {
|
||||
self.alt_screen_active.store(true, Ordering::Relaxed);
|
||||
return Ok(());
|
||||
|
|
@ -330,6 +341,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(());
|
||||
}
|
||||
if !self.alt_screen_nesting.leave() {
|
||||
self.alt_screen_active
|
||||
.store(self.alt_screen_nesting.is_active(), Ordering::Relaxed);
|
||||
|
|
|
|||
130
docs/tui-alternate-screen.md
Normal file
130
docs/tui-alternate-screen.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# TUI Alternate Screen and Terminal Multiplexers
|
||||
|
||||
## Overview
|
||||
|
||||
This document explains the design decision behind Codex's alternate screen handling, particularly in terminal multiplexers like Zellij. This addresses a fundamental conflict between fullscreen TUI behavior and terminal scrollback history preservation.
|
||||
|
||||
## The Problem
|
||||
|
||||
### Fullscreen TUI Benefits
|
||||
|
||||
Codex's TUI uses the terminal's **alternate screen buffer** to provide a clean fullscreen experience. This approach:
|
||||
|
||||
- Uses the entire viewport without polluting the terminal's scrollback history
|
||||
- Provides a dedicated environment for the chat interface
|
||||
- Mirrors the behavior of other terminal applications (vim, tmux, etc.)
|
||||
|
||||
### The Zellij Conflict
|
||||
|
||||
Terminal multiplexers like **Zellij** strictly follow the xterm specification, which defines that alternate screen buffers should **not** have scrollback. This is intentional design, not a bug:
|
||||
|
||||
- **Zellij PR:** https://github.com/zellij-org/zellij/pull/1032
|
||||
- **Rationale:** The xterm spec explicitly states that alternate screen mode disallows scrollback
|
||||
- **Configurability:** This is not configurable in Zellij—there is no option to enable scrollback in alternate screen mode
|
||||
|
||||
When using Codex's TUI in Zellij, users cannot scroll back through the conversation history because:
|
||||
|
||||
1. The TUI runs in alternate screen mode (fullscreen)
|
||||
2. Zellij disables scrollback in alternate screen buffers (per xterm spec)
|
||||
3. The entire conversation becomes inaccessible via normal terminal scrolling
|
||||
|
||||
## The Solution
|
||||
|
||||
Codex implements a **pragmatic workaround** with three modes, controlled by `tui.alternate_screen` in `config.toml`:
|
||||
|
||||
### 1. `auto` (default)
|
||||
|
||||
- **Behavior:** Automatically detect the terminal multiplexer
|
||||
- **In Zellij:** Disable alternate screen mode (inline mode, preserves scrollback)
|
||||
- **Elsewhere:** Enable alternate screen mode (fullscreen experience)
|
||||
- **Rationale:** Provides the best UX in each environment
|
||||
|
||||
### 2. `always`
|
||||
|
||||
- **Behavior:** Always use alternate screen mode (original behavior)
|
||||
- **Use case:** Users who prefer fullscreen and don't use Zellij, or who have found a workaround
|
||||
|
||||
### 3. `never`
|
||||
|
||||
- **Behavior:** Never use alternate screen mode (inline mode)
|
||||
- **Use case:** Users who always want scrollback history preserved
|
||||
- **Trade-off:** Pollutes the terminal scrollback with TUI output
|
||||
|
||||
## Runtime Override
|
||||
|
||||
The `--no-alt-screen` CLI flag can override the config setting at runtime:
|
||||
|
||||
```bash
|
||||
codex --no-alt-screen
|
||||
```
|
||||
|
||||
This runs the TUI in inline mode regardless of the configuration, useful for:
|
||||
|
||||
- One-off sessions where scrollback is critical
|
||||
- Debugging terminal-related issues
|
||||
- Testing alternate screen behavior
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Auto-Detection
|
||||
|
||||
The `auto` mode detects Zellij by checking the `ZELLIJ` environment variable:
|
||||
|
||||
```rust
|
||||
let terminal_info = codex_core::terminal::terminal_info();
|
||||
!matches!(terminal_info.multiplexer, Some(Multiplexer::Zellij { .. }))
|
||||
```
|
||||
|
||||
This detection happens in the helper function `determine_alt_screen_mode()` in `codex-rs/tui/src/lib.rs`.
|
||||
|
||||
### Configuration Schema
|
||||
|
||||
The `AltScreenMode` enum is defined in `codex-rs/protocol/src/config_types.rs` and serializes to lowercase TOML:
|
||||
|
||||
```toml
|
||||
[tui]
|
||||
# Options: auto, always, never
|
||||
alternate_screen = "auto"
|
||||
```
|
||||
|
||||
### Why Not Just Disable Alternate Screen in Zellij Permanently?
|
||||
|
||||
We use `auto` detection instead of always disabling in Zellij because:
|
||||
|
||||
1. Many Zellij users don't care about scrollback and prefer the fullscreen experience
|
||||
2. Some users may use tmux inside Zellij, creating a chain of multiplexers
|
||||
3. Provides user choice without requiring manual configuration
|
||||
|
||||
## Related Issues and References
|
||||
|
||||
- **Original Issue:** [GitHub #2558](https://github.com/openai/codex/issues/2558) - "No scrollback in Zellij"
|
||||
- **Implementation PR:** [GitHub #8555](https://github.com/openai/codex/pull/8555)
|
||||
- **Zellij PR:** https://github.com/zellij-org/zellij/pull/1032 (why scrollback is disabled)
|
||||
- **xterm Spec:** Alternate screen buffers should not have scrollback
|
||||
|
||||
## Future Considerations
|
||||
|
||||
### Alternative Approaches Considered
|
||||
|
||||
1. **Implement custom scrollback in TUI:** Would require significant architectural changes to buffer and render all historical output
|
||||
2. **Request Zellij to add a config option:** Not viable—Zellij maintainers explicitly chose this behavior to follow the spec
|
||||
3. **Disable alternate screen unconditionally:** Would degrade UX for non-Zellij users
|
||||
|
||||
### Transcript Pager
|
||||
|
||||
Codex's transcript pager (opened with Ctrl+T) provides an alternative way to review conversation history, even in fullscreen mode. However, this is not as seamless as natural scrollback.
|
||||
|
||||
## For Developers
|
||||
|
||||
When modifying TUI code, remember:
|
||||
|
||||
- The `determine_alt_screen_mode()` function encapsulates all the logic
|
||||
- Configuration is in `config.tui_alternate_screen`
|
||||
- CLI flag is in `cli.no_alt_screen`
|
||||
- The behavior is applied via `tui.set_alt_screen_enabled()`
|
||||
|
||||
If you encounter issues with terminal state after running Codex, you can restore your terminal with:
|
||||
|
||||
```bash
|
||||
reset
|
||||
```
|
||||
Loading…
Add table
Reference in a new issue