mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Share editor keymaps across TUI composer components (#38837)
## What changed - Store the editor portion of `RuntimeKeymap` in an `Arc` so the chat composer and its embedded textarea use the same keymap snapshot. - Initialize the textarea from the composer's default runtime keymap and keep custom binding updates shared between both components. - Use copy-on-write when tests and binding resolution need to mutate editor bindings. ## Testing - Verify that applying custom editor bindings shares the same keymap allocation with the composer and textarea. GitOrigin-RevId: 7f3b0afd6cc784e23deee8819e5c2c37a3709c68
This commit is contained in:
committed by
copyberry
parent
73abda8bfe
commit
9bfaf7a076
@@ -322,6 +322,7 @@ use std::collections::HashSet;
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::Range;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
@@ -515,7 +516,7 @@ pub(crate) struct ChatComposer {
|
||||
toggle_shortcuts_keys: Vec<KeyBinding>,
|
||||
history_search_previous_keys: Vec<KeyBinding>,
|
||||
history_search_next_keys: Vec<KeyBinding>,
|
||||
editor_keymap: EditorKeymap,
|
||||
editor_keymap: Arc<EditorKeymap>,
|
||||
vim_normal_keymap: VimNormalKeymap,
|
||||
}
|
||||
|
||||
@@ -707,6 +708,7 @@ impl ChatComposer {
|
||||
editor_keymap: default_editor_keymap,
|
||||
vim_normal_keymap: default_vim_normal_keymap,
|
||||
};
|
||||
this.draft.textarea.set_keymap_bindings(&default_keymap);
|
||||
// Apply configuration via the setter to keep side-effects centralized.
|
||||
this.set_disable_paste_burst(disable_paste_burst);
|
||||
this
|
||||
@@ -11222,8 +11224,10 @@ mod tests {
|
||||
assert!(matches!(result, InputResult::Submitted { .. }));
|
||||
|
||||
let mut keymap = RuntimeKeymap::defaults();
|
||||
keymap.editor.move_up = vec![key_hint::plain(KeyCode::F(2))];
|
||||
Arc::make_mut(&mut keymap.editor).move_up = vec![key_hint::plain(KeyCode::F(2))];
|
||||
composer.set_keymap_bindings(&keymap);
|
||||
assert!(Arc::ptr_eq(&composer.editor_keymap, &keymap.editor));
|
||||
assert_eq!(Arc::strong_count(&keymap.editor), 3);
|
||||
|
||||
let _ = composer.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
|
||||
assert!(composer.draft.textarea.is_empty());
|
||||
|
||||
@@ -39,6 +39,7 @@ use std::borrow::Cow;
|
||||
use std::cell::Ref;
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
mod vim;
|
||||
@@ -127,7 +128,7 @@ pub(crate) struct TextArea {
|
||||
vim_enabled: bool,
|
||||
vim_mode: VimMode,
|
||||
vim_pending: VimPending,
|
||||
editor_keymap: EditorKeymap,
|
||||
editor_keymap: Arc<EditorKeymap>,
|
||||
vim_normal_keymap: VimNormalKeymap,
|
||||
vim_operator_keymap: VimOperatorKeymap,
|
||||
vim_text_object_keymap: VimTextObjectKeymap,
|
||||
@@ -182,7 +183,7 @@ impl TextArea {
|
||||
/// the kill buffer, so callers can safely apply a live config update while
|
||||
/// preserving the current draft exactly as typed.
|
||||
pub fn set_keymap_bindings(&mut self, keymap: &RuntimeKeymap) {
|
||||
self.editor_keymap = keymap.editor.clone();
|
||||
self.editor_keymap = Arc::clone(&keymap.editor);
|
||||
self.vim_normal_keymap = keymap.vim_normal.clone();
|
||||
self.vim_operator_keymap = keymap.vim_operator.clone();
|
||||
self.vim_text_object_keymap = keymap.vim_text_object.clone();
|
||||
@@ -2971,8 +2972,8 @@ mod tests {
|
||||
let mut t = ta_with("abc\ndef\nghi");
|
||||
t.set_cursor(/*pos*/ 5);
|
||||
let mut keymap = RuntimeKeymap::defaults().editor;
|
||||
keymap.kill_line_start.clear();
|
||||
keymap.kill_whole_line = vec![key_hint::ctrl(KeyCode::Char('u'))];
|
||||
Arc::make_mut(&mut keymap).kill_line_start.clear();
|
||||
Arc::make_mut(&mut keymap).kill_whole_line = vec![key_hint::ctrl(KeyCode::Char('u'))];
|
||||
|
||||
t.input_with_keymap(
|
||||
KeyEvent::new(KeyCode::Char('u'), KeyModifiers::CONTROL),
|
||||
@@ -3222,7 +3223,7 @@ mod tests {
|
||||
let mut t = ta_with("a\nb");
|
||||
t.set_cursor(/*pos*/ 2);
|
||||
let mut keymap = RuntimeKeymap::defaults().editor;
|
||||
keymap.move_up.clear();
|
||||
Arc::make_mut(&mut keymap).move_up.clear();
|
||||
|
||||
t.input_with_keymap(
|
||||
KeyEvent::new(KeyCode::Char('\u{0010}'), KeyModifiers::NONE),
|
||||
@@ -3237,8 +3238,8 @@ mod tests {
|
||||
let mut t = ta_with("a\nb");
|
||||
t.set_cursor(/*pos*/ 0);
|
||||
let mut keymap = RuntimeKeymap::defaults().editor;
|
||||
keymap.move_up.clear();
|
||||
keymap.move_down = vec![crate::key_hint::ctrl(KeyCode::Char('p'))];
|
||||
Arc::make_mut(&mut keymap).move_up.clear();
|
||||
Arc::make_mut(&mut keymap).move_down = vec![crate::key_hint::ctrl(KeyCode::Char('p'))];
|
||||
|
||||
t.input_with_keymap(
|
||||
KeyEvent::new(KeyCode::Char('\u{0010}'), KeyModifiers::NONE),
|
||||
|
||||
@@ -63,7 +63,7 @@ pub(crate) struct RuntimeKeymap {
|
||||
pub(crate) chords: Arc<RuntimeChordKeymap>,
|
||||
pub(crate) chat: ChatKeymap,
|
||||
pub(crate) composer: ComposerKeymap,
|
||||
pub(crate) editor: EditorKeymap,
|
||||
pub(crate) editor: Arc<EditorKeymap>,
|
||||
pub(crate) vim_normal: VimNormalKeymap,
|
||||
pub(crate) vim_operator: VimOperatorKeymap,
|
||||
pub(crate) vim_text_object: VimTextObjectKeymap,
|
||||
@@ -625,7 +625,7 @@ impl RuntimeKeymap {
|
||||
history_search_next: resolve_local!(keymap, defaults, composer, history_search_next),
|
||||
};
|
||||
|
||||
let editor = EditorKeymap {
|
||||
let editor = Arc::new(EditorKeymap {
|
||||
insert_newline: resolve_local!(keymap, defaults, editor, insert_newline),
|
||||
move_left: resolve_local!(keymap, defaults, editor, move_left),
|
||||
move_right: resolve_local!(keymap, defaults, editor, move_right),
|
||||
@@ -643,7 +643,7 @@ impl RuntimeKeymap {
|
||||
kill_whole_line: resolve_local!(keymap, defaults, editor, kill_whole_line),
|
||||
kill_line_end: resolve_local!(keymap, defaults, editor, kill_line_end),
|
||||
yank: resolve_local!(keymap, defaults, editor, yank),
|
||||
};
|
||||
});
|
||||
|
||||
let mut vim_normal = VimNormalKeymap {
|
||||
enter_insert: resolve_local!(keymap, defaults, vim_normal, enter_insert),
|
||||
@@ -1127,7 +1127,7 @@ impl RuntimeKeymap {
|
||||
history_search_previous: default_bindings![ctrl(KeyCode::Char('r'))],
|
||||
history_search_next: default_bindings![ctrl(KeyCode::Char('s'))],
|
||||
},
|
||||
editor: EditorKeymap {
|
||||
editor: Arc::new(EditorKeymap {
|
||||
insert_newline: default_bindings![
|
||||
ctrl(KeyCode::Char('j')),
|
||||
ctrl(KeyCode::Char('m')),
|
||||
@@ -1187,7 +1187,7 @@ impl RuntimeKeymap {
|
||||
kill_whole_line: default_bindings![],
|
||||
kill_line_end: default_bindings![ctrl(KeyCode::Char('k'))],
|
||||
yank: default_bindings![ctrl(KeyCode::Char('y'))],
|
||||
},
|
||||
}),
|
||||
vim_normal: VimNormalKeymap {
|
||||
enter_insert: default_bindings![plain(KeyCode::Char('i')), plain(KeyCode::Insert)],
|
||||
append_after_cursor: default_bindings![plain(KeyCode::Char('a'))],
|
||||
|
||||
@@ -8,6 +8,7 @@ use super::RuntimeKeymap;
|
||||
use crate::key_hint::KeyBinding;
|
||||
use codex_config::types::KeybindingsSpec;
|
||||
use codex_config::types::TuiKeymap;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Config context in which a keymap action is active.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
@@ -94,6 +95,15 @@ pub(super) struct RuntimeActionBinding<'a> {
|
||||
pub(super) bindings: &'a [KeyBinding],
|
||||
}
|
||||
|
||||
macro_rules! runtime_group_mut {
|
||||
($runtime_keymap:expr, editor) => {
|
||||
Arc::make_mut(&mut $runtime_keymap.editor)
|
||||
};
|
||||
($runtime_keymap:expr, $group:ident) => {
|
||||
&mut $runtime_keymap.$group
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! define_runtime_action_bindings {
|
||||
($($context:literal => $context_id:ident, $group:ident, $config_group:ident [$($action:ident),+ $(,)?]),+ $(,)?) => {
|
||||
/// Resolve a config context/action pair to its runtime identity.
|
||||
@@ -176,7 +186,7 @@ macro_rules! define_runtime_action_bindings {
|
||||
$(
|
||||
$(
|
||||
($context, stringify!($action)) => {
|
||||
runtime_keymap.$group.$action.push(binding);
|
||||
runtime_group_mut!(runtime_keymap, $group).$action.push(binding);
|
||||
true
|
||||
}
|
||||
)+
|
||||
|
||||
@@ -5,6 +5,7 @@ use crossterm::event::KeyModifiers;
|
||||
use pretty_assertions::assert_eq;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::unbounded_channel;
|
||||
|
||||
use super::StartupDraftInitialScreen;
|
||||
@@ -483,10 +484,11 @@ fn startup_draft_allows_local_editor_shortcuts_without_startup_actions() {
|
||||
assert_eq!(pump.bottom_pane.composer_text(), "first ");
|
||||
|
||||
let mut keymap = crate::keymap::RuntimeKeymap::defaults();
|
||||
keymap.editor.move_line_start = vec![crate::key_hint::ctrl(KeyCode::Char('z'))];
|
||||
keymap.editor.move_line_end = vec![crate::key_hint::ctrl(KeyCode::Char('v'))];
|
||||
keymap.editor.move_left = vec![crate::key_hint::ctrl(KeyCode::Char('s'))];
|
||||
keymap.editor.insert_newline = vec![crate::key_hint::plain(KeyCode::Enter)];
|
||||
let editor = Arc::make_mut(&mut keymap.editor);
|
||||
editor.move_line_start = vec![crate::key_hint::ctrl(KeyCode::Char('z'))];
|
||||
editor.move_line_end = vec![crate::key_hint::ctrl(KeyCode::Char('v'))];
|
||||
editor.move_left = vec![crate::key_hint::ctrl(KeyCode::Char('s'))];
|
||||
editor.insert_newline = vec![crate::key_hint::plain(KeyCode::Enter)];
|
||||
keymap.composer.submit = vec![crate::key_hint::ctrl(KeyCode::Char('s'))];
|
||||
pump.bottom_pane.set_keymap_bindings(&keymap);
|
||||
handle_startup_draft_key(
|
||||
|
||||
Reference in New Issue
Block a user