Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions codex-rs/core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::config_loader::merge_toml_values;
use crate::config_profile::ConfigProfile;
use crate::config_types::DEFAULT_OTEL_ENVIRONMENT;
use crate::config_types::History;
use crate::config_types::KeybindingMode;
use crate::config_types::McpServerConfig;
use crate::config_types::McpServerTransportConfig;
use crate::config_types::Notifications;
Expand Down Expand Up @@ -134,6 +135,9 @@ pub struct Config {
/// and turn completions when not focused.
pub tui_notifications: Notifications,

/// Preferred keybinding mode for interactive TUI components.
pub keybinding_mode: KeybindingMode,

/// 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.
Expand Down Expand Up @@ -1132,6 +1136,11 @@ impl Config {
.as_ref()
.map(|t| t.notifications.clone())
.unwrap_or_default(),
keybinding_mode: cfg
.tui
.as_ref()
.map(|t| t.keybinding_mode)
.unwrap_or_default(),
otel: {
let t: OtelConfigToml = cfg.otel.unwrap_or_default();
let log_user_prompt = t.log_user_prompt.unwrap_or(false);
Expand Down Expand Up @@ -1921,6 +1930,7 @@ model_verbosity = "high"
windows_wsl_setup_acknowledged: false,
disable_paste_burst: false,
tui_notifications: Default::default(),
keybinding_mode: KeybindingMode::default(),
otel: OtelConfig::default(),
},
o3_profile_config
Expand Down Expand Up @@ -1983,6 +1993,7 @@ model_verbosity = "high"
windows_wsl_setup_acknowledged: false,
disable_paste_burst: false,
tui_notifications: Default::default(),
keybinding_mode: KeybindingMode::default(),
otel: OtelConfig::default(),
};

Expand Down Expand Up @@ -2060,6 +2071,7 @@ model_verbosity = "high"
windows_wsl_setup_acknowledged: false,
disable_paste_burst: false,
tui_notifications: Default::default(),
keybinding_mode: KeybindingMode::default(),
otel: OtelConfig::default(),
};

Expand Down Expand Up @@ -2123,6 +2135,7 @@ model_verbosity = "high"
windows_wsl_setup_acknowledged: false,
disable_paste_burst: false,
tui_notifications: Default::default(),
keybinding_mode: KeybindingMode::default(),
otel: OtelConfig::default(),
};

Expand Down
12 changes: 12 additions & 0 deletions codex-rs/core/src/config_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ pub struct McpServerConfig {
pub tool_timeout_sec: Option<Duration>,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
#[serde(rename_all = "kebab-case")]
pub enum KeybindingMode {
#[default]
Emacs,
Vim,
}

impl<'de> Deserialize<'de> for McpServerConfig {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
Expand Down Expand Up @@ -299,6 +307,10 @@ pub struct Tui {
/// Defaults to `false`.
#[serde(default)]
pub notifications: Notifications,

/// Keybinding style to use throughout the TUI (e.g. composer, transcript pager).
#[serde(default)]
pub keybinding_mode: KeybindingMode,
}

#[derive(Deserialize, Debug, Clone, PartialEq, Default)]
Expand Down
21 changes: 20 additions & 1 deletion codex-rs/tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use codex_core::AuthManager;
use codex_core::ConversationManager;
use codex_core::config::Config;
use codex_core::config::persist_model_selection;
use codex_core::config_types::KeybindingMode;
use codex_core::model_family::find_family_for_model;
use codex_core::protocol::SessionSource;
use codex_core::protocol::TokenUsage;
Expand Down Expand Up @@ -423,7 +424,25 @@ impl App {
} => {
// Enter alternate screen and set viewport to full size.
let _ = tui.enter_alt_screen();
self.overlay = Some(Overlay::new_transcript(self.transcript_cells.clone()));
self.overlay = Some(Overlay::new_transcript(
self.transcript_cells.clone(),
self.config.keybinding_mode,
));
tui.frame_requester().schedule_frame();
}
KeyEvent {
code: KeyCode::Char('t'),
modifiers: crossterm::event::KeyModifiers::NONE,
kind: KeyEventKind::Press | KeyEventKind::Repeat,
..
} if self.config.keybinding_mode == KeybindingMode::Vim
&& self.chat_widget.composer_in_vim_normal_mode() =>
{
let _ = tui.enter_alt_screen();
self.overlay = Some(Overlay::new_transcript(
self.transcript_cells.clone(),
self.config.keybinding_mode,
));
tui.frame_requester().schedule_frame();
}
// Esc primes/advances backtracking only in normal (not working) mode
Expand Down
5 changes: 4 additions & 1 deletion codex-rs/tui/src/app_backtrack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,10 @@ impl App {
/// Open transcript overlay (enters alternate screen and shows full transcript).
pub(crate) fn open_transcript_overlay(&mut self, tui: &mut tui::Tui) {
let _ = tui.enter_alt_screen();
self.overlay = Some(Overlay::new_transcript(self.transcript_cells.clone()));
self.overlay = Some(Overlay::new_transcript(
self.transcript_cells.clone(),
self.config.keybinding_mode,
));
tui.frame_requester().schedule_frame();
}

Expand Down
Loading