mirror of
https://github.com/openai/codex.git
synced 2026-04-26 23:55:25 +00:00
## TL;DR Fixes the issues when using Codex CLI with Zellij multiplexer. Before this PR there would be no scrollback when using it inside a zellij terminal. ## Problem Addresses #2558 Zellij does not support ANSI scroll-region manipulation (`DECSTBM` / Reverse Index) or the alternate screen buffer in the way traditional terminals do. When codex's TUI runs inside Zellij, two things break: (1) inline history insertion corrupts the display because the scroll-region escape sequences are silently dropped or mishandled, and (2) the composer textarea renders with inherited background/foreground styles that produce unreadable text against Zellij's pane chrome. ## Mental model The fix introduces a **Zellij mode** — a runtime boolean detected once at startup via `codex_terminal_detection::terminal_info().is_zellij()` — that gates two subsystems onto Zellij-safe terminal strategies: - **History insertion** (`insert_history.rs`): Instead of using `DECSTBM` scroll regions and Reverse Index (`ESC M`) to slide content above the viewport, Zellij mode scrolls the screen by emitting `\n` at the bottom row and then writes history lines at absolute positions. This avoids every escape sequence Zellij mishandles. - **Viewport expansion** (`tui.rs`): When the viewport grows taller than available space, the standard path uses `scroll_region_up` on the backend. Zellij mode instead emits newlines at the screen bottom to push content up, then invalidates the ratatui diff buffer so the next draw is a full repaint. - **Composer rendering** (`chat_composer.rs`, `textarea.rs`): All text rendering in the input area uses an explicit `base_style` with `Color::Reset` foreground, preventing Zellij's pane styling from bleeding into the textarea. The prompt chevron (`›`) and placeholder text use explicit color constants instead of relying on `.bold()` / `.dim()` modifiers that render inconsistently under Zellij. ## Non-goals - This change does not fix or improve Zellij's terminal emulation itself. - It does not rearchitect the inline viewport model; it adds a parallel code path gated on detection. - It does not touch the alternate-screen disable logic (that already existed and continues to use `is_zellij` via the same detection). ## Tradeoffs - **Code duplication in `insert_history.rs`**: The Zellij and Standard branches share the line-rendering loop (color setup, span merging, `write_spans`) but differ in the scrolling preamble. The duplication is intentional — merging them would force a complex conditional state machine that's harder to reason about than two flat sequences. - **`invalidate_viewport` after every Zellij history flush or viewport expansion**: This forces a full repaint on every draw cycle in Zellij, which is more expensive than ratatui's normal diff-based rendering. This is necessary because Zellij's lack of scroll-region support means the diff buffer's assumptions about what's on screen are invalid after we manually move content. - **Explicit colors vs semantic modifiers**: Replacing `.bold()` / `.dim()` with `Color::Cyan` / `Color::DarkGray` / `Color::White` in the Zellij branch sacrifices theme-awareness for correctness. If the project ever adopts a theming system, Zellij styling will need to participate. ## Architecture The Zellij detection flag flows through three layers: 1. **`codex_terminal_detection`** — `TerminalInfo::is_zellij()` (new convenience method) reads the already-detected `Multiplexer` variant. 2. **`Tui` struct** — caches `is_zellij` at construction; passes it into `update_inline_viewport`, `flush_pending_history_lines`, and `insert_history_lines_with_mode`. 3. **`ChatComposer` struct** — independently caches `is_zellij` at construction; uses it in `render_textarea` for style decisions. The two caches (`Tui.is_zellij` and `ChatComposer.is_zellij`) are read from the same global `OnceLock<TerminalInfo>`, so they always agree. ## Observability No new logging, metrics, or tracing is introduced. Diagnosis depends on: - Whether `ZELLIJ` or `ZELLIJ_SESSION_NAME` env vars are set (the detection heuristic). - Visual inspection of the rendered TUI inside Zellij vs a standard terminal. - The insta snapshot `zellij_empty_composer` captures the Zellij-mode render path. ## Tests - `terminal_info_reports_is_zellij` — unit test in `terminal-detection` confirming the convenience method. - `zellij_empty_composer_snapshot` — insta snapshot in `chat_composer` validating the Zellij render path for an empty composer. - `vt100_zellij_mode_inserts_history_and_updates_viewport` — integration test in `insert_history` verifying that Zellij-mode history insertion writes content and shifts the viewport. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
844 lines
22 KiB
Rust
844 lines
22 KiB
Rust
use super::*;
|
|
use pretty_assertions::assert_eq;
|
|
use std::collections::HashMap;
|
|
|
|
struct FakeEnvironment {
|
|
vars: HashMap<String, String>,
|
|
tmux_client_info: TmuxClientInfo,
|
|
}
|
|
|
|
impl FakeEnvironment {
|
|
fn new() -> Self {
|
|
Self {
|
|
vars: HashMap::new(),
|
|
tmux_client_info: TmuxClientInfo::default(),
|
|
}
|
|
}
|
|
|
|
fn with_var(mut self, key: &str, value: &str) -> Self {
|
|
self.vars.insert(key.to_string(), value.to_string());
|
|
self
|
|
}
|
|
|
|
fn with_tmux_client_info(mut self, termtype: Option<&str>, termname: Option<&str>) -> Self {
|
|
self.tmux_client_info = TmuxClientInfo {
|
|
termtype: termtype.map(ToString::to_string),
|
|
termname: termname.map(ToString::to_string),
|
|
};
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Environment for FakeEnvironment {
|
|
fn var(&self, name: &str) -> Option<String> {
|
|
self.vars.get(name).cloned()
|
|
}
|
|
|
|
fn tmux_client_info(&self) -> TmuxClientInfo {
|
|
self.tmux_client_info.clone()
|
|
}
|
|
}
|
|
|
|
fn terminal_info(
|
|
name: TerminalName,
|
|
term_program: Option<&str>,
|
|
version: Option<&str>,
|
|
term: Option<&str>,
|
|
multiplexer: Option<Multiplexer>,
|
|
) -> TerminalInfo {
|
|
TerminalInfo {
|
|
name,
|
|
term_program: term_program.map(ToString::to_string),
|
|
version: version.map(ToString::to_string),
|
|
term: term.map(ToString::to_string),
|
|
multiplexer,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn detects_term_program() {
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TERM_PROGRAM", "iTerm.app")
|
|
.with_var("TERM_PROGRAM_VERSION", "3.5.0")
|
|
.with_var("WEZTERM_VERSION", "2024.2");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Iterm2,
|
|
Some("iTerm.app"),
|
|
Some("3.5.0"),
|
|
/*term*/ None,
|
|
/*multiplexer*/ None,
|
|
),
|
|
"term_program_with_version_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"iTerm.app/3.5.0",
|
|
"term_program_with_version_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TERM_PROGRAM", "iTerm.app")
|
|
.with_var("TERM_PROGRAM_VERSION", "");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Iterm2,
|
|
Some("iTerm.app"),
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"term_program_without_version_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"iTerm.app",
|
|
"term_program_without_version_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TERM_PROGRAM", "iTerm.app")
|
|
.with_var("WEZTERM_VERSION", "2024.2");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Iterm2,
|
|
Some("iTerm.app"),
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"term_program_overrides_wezterm_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"iTerm.app",
|
|
"term_program_overrides_wezterm_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn terminal_info_reports_is_zellij() {
|
|
let zellij = terminal_info(
|
|
TerminalName::Unknown,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
Some(Multiplexer::Zellij {}),
|
|
);
|
|
assert!(zellij.is_zellij());
|
|
|
|
let non_zellij = terminal_info(
|
|
TerminalName::Unknown,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
Some(Multiplexer::Tmux { version: None }),
|
|
);
|
|
assert!(!non_zellij.is_zellij());
|
|
}
|
|
|
|
#[test]
|
|
fn detects_iterm2() {
|
|
let env = FakeEnvironment::new().with_var("ITERM_SESSION_ID", "w0t1p0");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Iterm2,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"iterm_session_id_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"iTerm.app",
|
|
"iterm_session_id_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_apple_terminal() {
|
|
let env = FakeEnvironment::new().with_var("TERM_PROGRAM", "Apple_Terminal");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::AppleTerminal,
|
|
Some("Apple_Terminal"),
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None,
|
|
),
|
|
"apple_term_program_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"Apple_Terminal",
|
|
"apple_term_program_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new().with_var("TERM_SESSION_ID", "A1B2C3");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::AppleTerminal,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"apple_term_session_id_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"Apple_Terminal",
|
|
"apple_term_session_id_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_ghostty() {
|
|
let env = FakeEnvironment::new().with_var("TERM_PROGRAM", "Ghostty");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Ghostty,
|
|
Some("Ghostty"),
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"ghostty_term_program_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"Ghostty",
|
|
"ghostty_term_program_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_vscode() {
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TERM_PROGRAM", "vscode")
|
|
.with_var("TERM_PROGRAM_VERSION", "1.86.0");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::VsCode,
|
|
Some("vscode"),
|
|
Some("1.86.0"),
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"vscode_term_program_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"vscode/1.86.0",
|
|
"vscode_term_program_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_warp_terminal() {
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TERM_PROGRAM", "WarpTerminal")
|
|
.with_var("TERM_PROGRAM_VERSION", "v0.2025.12.10.08.12.stable_03");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::WarpTerminal,
|
|
Some("WarpTerminal"),
|
|
Some("v0.2025.12.10.08.12.stable_03"),
|
|
/*term*/ None,
|
|
/*multiplexer*/ None,
|
|
),
|
|
"warp_term_program_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"WarpTerminal/v0.2025.12.10.08.12.stable_03",
|
|
"warp_term_program_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_tmux_multiplexer() {
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TMUX", "/tmp/tmux-1000/default,123,0")
|
|
.with_var("TERM_PROGRAM", "tmux")
|
|
.with_tmux_client_info(Some("xterm-256color"), Some("screen-256color"));
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Unknown,
|
|
Some("xterm-256color"),
|
|
/*version*/ None,
|
|
Some("screen-256color"),
|
|
Some(Multiplexer::Tmux { version: None }),
|
|
),
|
|
"tmux_multiplexer_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"xterm-256color",
|
|
"tmux_multiplexer_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_zellij_multiplexer() {
|
|
let env = FakeEnvironment::new().with_var("ZELLIJ", "1");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
TerminalInfo {
|
|
name: TerminalName::Unknown,
|
|
term_program: None,
|
|
version: None,
|
|
term: None,
|
|
multiplexer: Some(Multiplexer::Zellij {}),
|
|
},
|
|
"zellij_multiplexer"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_tmux_client_termtype() {
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TMUX", "/tmp/tmux-1000/default,123,0")
|
|
.with_var("TERM_PROGRAM", "tmux")
|
|
.with_tmux_client_info(Some("WezTerm"), /*termname*/ None);
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::WezTerm,
|
|
Some("WezTerm"),
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
Some(Multiplexer::Tmux { version: None }),
|
|
),
|
|
"tmux_client_termtype_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"WezTerm",
|
|
"tmux_client_termtype_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_tmux_client_termname() {
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TMUX", "/tmp/tmux-1000/default,123,0")
|
|
.with_var("TERM_PROGRAM", "tmux")
|
|
.with_tmux_client_info(/*termtype*/ None, Some("xterm-256color"));
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Unknown,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
Some("xterm-256color"),
|
|
Some(Multiplexer::Tmux { version: None })
|
|
),
|
|
"tmux_client_termname_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"xterm-256color",
|
|
"tmux_client_termname_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_tmux_term_program_uses_client_termtype() {
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TMUX", "/tmp/tmux-1000/default,123,0")
|
|
.with_var("TERM_PROGRAM", "tmux")
|
|
.with_var("TERM_PROGRAM_VERSION", "3.6a")
|
|
.with_tmux_client_info(Some("ghostty 1.2.3"), Some("xterm-ghostty"));
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Ghostty,
|
|
Some("ghostty"),
|
|
Some("1.2.3"),
|
|
Some("xterm-ghostty"),
|
|
Some(Multiplexer::Tmux {
|
|
version: Some("3.6a".to_string()),
|
|
}),
|
|
),
|
|
"tmux_term_program_client_termtype_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"ghostty/1.2.3",
|
|
"tmux_term_program_client_termtype_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_wezterm() {
|
|
let env = FakeEnvironment::new().with_var("WEZTERM_VERSION", "2024.2");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::WezTerm,
|
|
/*term_program*/ None,
|
|
Some("2024.2"),
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"wezterm_version_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"WezTerm/2024.2",
|
|
"wezterm_version_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TERM_PROGRAM", "WezTerm")
|
|
.with_var("TERM_PROGRAM_VERSION", "2024.2");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::WezTerm,
|
|
Some("WezTerm"),
|
|
Some("2024.2"),
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"wezterm_term_program_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"WezTerm/2024.2",
|
|
"wezterm_term_program_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new().with_var("WEZTERM_VERSION", "");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::WezTerm,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"wezterm_empty_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"WezTerm",
|
|
"wezterm_empty_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_kitty() {
|
|
let env = FakeEnvironment::new().with_var("KITTY_WINDOW_ID", "1");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Kitty,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"kitty_window_id_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"kitty",
|
|
"kitty_window_id_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TERM_PROGRAM", "kitty")
|
|
.with_var("TERM_PROGRAM_VERSION", "0.30.1");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Kitty,
|
|
Some("kitty"),
|
|
Some("0.30.1"),
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"kitty_term_program_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"kitty/0.30.1",
|
|
"kitty_term_program_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TERM", "xterm-kitty")
|
|
.with_var("ALACRITTY_SOCKET", "/tmp/alacritty");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Kitty,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"kitty_term_over_alacritty_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"kitty",
|
|
"kitty_term_over_alacritty_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_alacritty() {
|
|
let env = FakeEnvironment::new().with_var("ALACRITTY_SOCKET", "/tmp/alacritty");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Alacritty,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"alacritty_socket_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"Alacritty",
|
|
"alacritty_socket_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TERM_PROGRAM", "Alacritty")
|
|
.with_var("TERM_PROGRAM_VERSION", "0.13.2");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Alacritty,
|
|
Some("Alacritty"),
|
|
Some("0.13.2"),
|
|
/*term*/ None,
|
|
/*multiplexer*/ None,
|
|
),
|
|
"alacritty_term_program_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"Alacritty/0.13.2",
|
|
"alacritty_term_program_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new().with_var("TERM", "alacritty");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Alacritty,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"alacritty_term_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"Alacritty",
|
|
"alacritty_term_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_konsole() {
|
|
let env = FakeEnvironment::new().with_var("KONSOLE_VERSION", "230800");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Konsole,
|
|
/*term_program*/ None,
|
|
Some("230800"),
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"konsole_version_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"Konsole/230800",
|
|
"konsole_version_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TERM_PROGRAM", "Konsole")
|
|
.with_var("TERM_PROGRAM_VERSION", "230800");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Konsole,
|
|
Some("Konsole"),
|
|
Some("230800"),
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"konsole_term_program_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"Konsole/230800",
|
|
"konsole_term_program_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new().with_var("KONSOLE_VERSION", "");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Konsole,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"konsole_empty_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"Konsole",
|
|
"konsole_empty_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_gnome_terminal() {
|
|
let env = FakeEnvironment::new().with_var("GNOME_TERMINAL_SCREEN", "1");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::GnomeTerminal,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"gnome_terminal_screen_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"gnome-terminal",
|
|
"gnome_terminal_screen_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TERM_PROGRAM", "gnome-terminal")
|
|
.with_var("TERM_PROGRAM_VERSION", "3.50");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::GnomeTerminal,
|
|
Some("gnome-terminal"),
|
|
Some("3.50"),
|
|
/*term*/ None,
|
|
/*multiplexer*/ None,
|
|
),
|
|
"gnome_terminal_term_program_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"gnome-terminal/3.50",
|
|
"gnome_terminal_term_program_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_vte() {
|
|
let env = FakeEnvironment::new().with_var("VTE_VERSION", "7000");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Vte,
|
|
/*term_program*/ None,
|
|
Some("7000"),
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"vte_version_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"VTE/7000",
|
|
"vte_version_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TERM_PROGRAM", "VTE")
|
|
.with_var("TERM_PROGRAM_VERSION", "7000");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Vte,
|
|
Some("VTE"),
|
|
Some("7000"),
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"vte_term_program_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"VTE/7000",
|
|
"vte_term_program_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new().with_var("VTE_VERSION", "");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Vte,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"vte_empty_info"
|
|
);
|
|
assert_eq!(terminal.user_agent_token(), "VTE", "vte_empty_user_agent");
|
|
}
|
|
|
|
#[test]
|
|
fn detects_windows_terminal() {
|
|
let env = FakeEnvironment::new().with_var("WT_SESSION", "1");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::WindowsTerminal,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"wt_session_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"WindowsTerminal",
|
|
"wt_session_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new()
|
|
.with_var("TERM_PROGRAM", "WindowsTerminal")
|
|
.with_var("TERM_PROGRAM_VERSION", "1.21");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::WindowsTerminal,
|
|
Some("WindowsTerminal"),
|
|
Some("1.21"),
|
|
/*term*/ None,
|
|
/*multiplexer*/ None,
|
|
),
|
|
"windows_terminal_term_program_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"WindowsTerminal/1.21",
|
|
"windows_terminal_term_program_user_agent"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn detects_term_fallbacks() {
|
|
let env = FakeEnvironment::new().with_var("TERM", "xterm-256color");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Unknown,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
Some("xterm-256color"),
|
|
/*multiplexer*/ None,
|
|
),
|
|
"term_fallback_info"
|
|
);
|
|
assert_eq!(
|
|
terminal.user_agent_token(),
|
|
"xterm-256color",
|
|
"term_fallback_user_agent"
|
|
);
|
|
|
|
let env = FakeEnvironment::new().with_var("TERM", "dumb");
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Dumb,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
Some("dumb"),
|
|
/*multiplexer*/ None
|
|
),
|
|
"dumb_term_info"
|
|
);
|
|
assert_eq!(terminal.user_agent_token(), "dumb", "dumb_term_user_agent");
|
|
|
|
let env = FakeEnvironment::new();
|
|
let terminal = detect_terminal_info_from_env(&env);
|
|
assert_eq!(
|
|
terminal,
|
|
terminal_info(
|
|
TerminalName::Unknown,
|
|
/*term_program*/ None,
|
|
/*version*/ None,
|
|
/*term*/ None,
|
|
/*multiplexer*/ None
|
|
),
|
|
"unknown_info"
|
|
);
|
|
assert_eq!(terminal.user_agent_token(), "unknown", "unknown_user_agent");
|
|
}
|