mirror of
https://github.com/openai/codex.git
synced 2026-10-26 18:38:47 +00:00
feat(tui): make reasoning shortcuts configurable
This commit is contained in:
@@ -29,7 +29,7 @@ use std::collections::BTreeMap;
|
||||
pub enum TuiKeymapPreset {
|
||||
/// Pointer alias to the latest shipped preset.
|
||||
///
|
||||
/// Today this resolves to `v3`.
|
||||
/// Today this resolves to `v4`.
|
||||
#[default]
|
||||
Latest,
|
||||
/// Frozen initial configurable keymap defaults.
|
||||
@@ -38,6 +38,8 @@ pub enum TuiKeymapPreset {
|
||||
V2,
|
||||
/// Current defaults with the copy shortcut exposed through keymap config.
|
||||
V3,
|
||||
/// Current defaults with reasoning shortcuts exposed through keymap config.
|
||||
V4,
|
||||
}
|
||||
|
||||
/// Normalized string representation of a keybinding (for example `ctrl-a`).
|
||||
@@ -116,6 +118,10 @@ pub struct TuiGlobalKeymap {
|
||||
pub toggle_shortcuts: Option<KeybindingsSpec>,
|
||||
/// Toggle Vim mode for the composer input.
|
||||
pub toggle_vim_mode: Option<KeybindingsSpec>,
|
||||
/// Lower the active reasoning level when possible.
|
||||
pub reasoning_down: Option<KeybindingsSpec>,
|
||||
/// Raise the active reasoning level when possible.
|
||||
pub reasoning_up: Option<KeybindingsSpec>,
|
||||
}
|
||||
|
||||
/// Chat context keybindings. These override corresponding `global` actions.
|
||||
@@ -126,6 +132,10 @@ pub struct TuiChatKeymap {
|
||||
pub edit_previous_message: Option<KeybindingsSpec>,
|
||||
/// Confirm editing the selected previous message.
|
||||
pub confirm_edit_previous_message: Option<KeybindingsSpec>,
|
||||
/// Lower the active reasoning level when possible.
|
||||
pub reasoning_down: Option<KeybindingsSpec>,
|
||||
/// Raise the active reasoning level when possible.
|
||||
pub reasoning_up: Option<KeybindingsSpec>,
|
||||
}
|
||||
|
||||
/// Composer context keybindings. These override corresponding `global` actions.
|
||||
|
||||
@@ -2204,7 +2204,9 @@
|
||||
},
|
||||
"chat": {
|
||||
"confirm_edit_previous_message": null,
|
||||
"edit_previous_message": null
|
||||
"edit_previous_message": null,
|
||||
"reasoning_down": null,
|
||||
"reasoning_up": null
|
||||
},
|
||||
"composer": {
|
||||
"queue": null,
|
||||
@@ -2236,6 +2238,8 @@
|
||||
"open_external_editor": null,
|
||||
"open_transcript": null,
|
||||
"queue": null,
|
||||
"reasoning_down": null,
|
||||
"reasoning_up": null,
|
||||
"submit": null,
|
||||
"toggle_shortcuts": null,
|
||||
"toggle_vim_mode": null
|
||||
@@ -2458,6 +2462,22 @@
|
||||
}
|
||||
],
|
||||
"description": "In an empty composer, begin or advance \"edit previous message\" flow."
|
||||
},
|
||||
"reasoning_down": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeybindingsSpec"
|
||||
}
|
||||
],
|
||||
"description": "Lower the active reasoning level when possible."
|
||||
},
|
||||
"reasoning_up": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeybindingsSpec"
|
||||
}
|
||||
],
|
||||
"description": "Raise the active reasoning level when possible."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -2680,6 +2700,22 @@
|
||||
],
|
||||
"description": "Queue the current composer draft while a task is running."
|
||||
},
|
||||
"reasoning_down": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeybindingsSpec"
|
||||
}
|
||||
],
|
||||
"description": "Lower the active reasoning level when possible."
|
||||
},
|
||||
"reasoning_up": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeybindingsSpec"
|
||||
}
|
||||
],
|
||||
"description": "Raise the active reasoning level when possible."
|
||||
},
|
||||
"submit": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -2734,7 +2770,9 @@
|
||||
],
|
||||
"default": {
|
||||
"confirm_edit_previous_message": null,
|
||||
"edit_previous_message": null
|
||||
"edit_previous_message": null,
|
||||
"reasoning_down": null,
|
||||
"reasoning_up": null
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
@@ -2787,6 +2825,8 @@
|
||||
"open_external_editor": null,
|
||||
"open_transcript": null,
|
||||
"queue": null,
|
||||
"reasoning_down": null,
|
||||
"reasoning_up": null,
|
||||
"submit": null,
|
||||
"toggle_shortcuts": null,
|
||||
"toggle_vim_mode": null
|
||||
@@ -2912,7 +2952,7 @@
|
||||
"description": "Versioned keymap defaults.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Pointer alias to the latest shipped preset.\n\nToday this resolves to `v3`.",
|
||||
"description": "Pointer alias to the latest shipped preset.\n\nToday this resolves to `v4`.",
|
||||
"enum": [
|
||||
"latest"
|
||||
],
|
||||
@@ -2938,6 +2978,13 @@
|
||||
"v3"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Current defaults with reasoning shortcuts exposed through keymap config.",
|
||||
"enum": [
|
||||
"v4"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -397,6 +397,8 @@ pub(crate) struct ChatComposer {
|
||||
footer_external_editor_key: Option<KeyBinding>,
|
||||
footer_edit_previous_key: Option<KeyBinding>,
|
||||
footer_show_transcript_key: Option<KeyBinding>,
|
||||
footer_reasoning_down_key: Option<KeyBinding>,
|
||||
footer_reasoning_up_key: Option<KeyBinding>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -554,6 +556,8 @@ impl ChatComposer {
|
||||
footer_external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
footer_edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
footer_show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
footer_reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
footer_reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
};
|
||||
// Apply configuration via the setter to keep side-effects centralized.
|
||||
this.set_disable_paste_burst(disable_paste_burst);
|
||||
@@ -636,6 +640,8 @@ impl ChatComposer {
|
||||
self.footer_external_editor_key = primary_binding(&keymap.app.open_external_editor);
|
||||
self.footer_edit_previous_key = primary_binding(&keymap.chat.edit_previous_message);
|
||||
self.footer_show_transcript_key = primary_binding(&keymap.app.open_transcript);
|
||||
self.footer_reasoning_down_key = primary_binding(&keymap.chat.reasoning_down);
|
||||
self.footer_reasoning_up_key = primary_binding(&keymap.chat.reasoning_up);
|
||||
}
|
||||
|
||||
pub fn set_collaboration_mode_indicator(
|
||||
@@ -3318,6 +3324,8 @@ impl ChatComposer {
|
||||
is_wsl,
|
||||
status_line_value: self.status_line_value.clone(),
|
||||
status_line_enabled: self.status_line_enabled,
|
||||
reasoning_down_key: self.footer_reasoning_down_key,
|
||||
reasoning_up_key: self.footer_reasoning_up_key,
|
||||
active_agent_label: self.active_agent_label.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,8 @@ pub(crate) struct FooterProps {
|
||||
pub(crate) quit_shortcut_key: KeyBinding,
|
||||
pub(crate) status_line_value: Option<Line<'static>>,
|
||||
pub(crate) status_line_enabled: bool,
|
||||
pub(crate) reasoning_down_key: Option<KeyBinding>,
|
||||
pub(crate) reasoning_up_key: Option<KeyBinding>,
|
||||
/// Active thread label shown when the footer is rendering contextual information instead of an
|
||||
/// instructional hint.
|
||||
///
|
||||
@@ -622,6 +624,8 @@ fn footer_from_props_lines(
|
||||
esc_backtrack_hint: props.esc_backtrack_hint,
|
||||
is_wsl: props.is_wsl,
|
||||
collaboration_modes_enabled: props.collaboration_modes_enabled,
|
||||
reasoning_down_key: props.reasoning_down_key,
|
||||
reasoning_up_key: props.reasoning_up_key,
|
||||
};
|
||||
shortcut_overlay_lines(state)
|
||||
}
|
||||
@@ -739,6 +743,8 @@ struct ShortcutsState {
|
||||
esc_backtrack_hint: bool,
|
||||
is_wsl: bool,
|
||||
collaboration_modes_enabled: bool,
|
||||
reasoning_down_key: Option<KeyBinding>,
|
||||
reasoning_up_key: Option<KeyBinding>,
|
||||
}
|
||||
|
||||
fn quit_shortcut_reminder_line(key: KeyBinding) -> Line<'static> {
|
||||
@@ -791,11 +797,15 @@ fn shortcut_overlay_lines(state: ShortcutsState) -> Vec<Line<'static>> {
|
||||
ShortcutId::Quit => quit = text,
|
||||
ShortcutId::ShowTranscript => show_transcript = text,
|
||||
ShortcutId::ChangeMode => change_mode = text,
|
||||
ShortcutId::ReasoningDown => reasoning_down = text,
|
||||
ShortcutId::ReasoningUp => reasoning_up = text,
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(key) = state.reasoning_down_key {
|
||||
reasoning_down = Line::from(vec![key.into(), " reasoning down".into()]);
|
||||
}
|
||||
if let Some(key) = state.reasoning_up_key {
|
||||
reasoning_up = Line::from(vec![key.into(), " reasoning up".into()]);
|
||||
}
|
||||
|
||||
let mut ordered = vec![
|
||||
commands,
|
||||
@@ -899,8 +909,6 @@ enum ShortcutId {
|
||||
Quit,
|
||||
ShowTranscript,
|
||||
ChangeMode,
|
||||
ReasoningDown,
|
||||
ReasoningUp,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -1092,24 +1100,6 @@ const SHORTCUTS: &[ShortcutDescriptor] = &[
|
||||
prefix: "",
|
||||
label: " to change mode",
|
||||
},
|
||||
ShortcutDescriptor {
|
||||
id: ShortcutId::ReasoningDown,
|
||||
bindings: &[ShortcutBinding {
|
||||
key: key_hint::alt(KeyCode::Char(',')),
|
||||
condition: DisplayCondition::Always,
|
||||
}],
|
||||
prefix: "",
|
||||
label: " reasoning down",
|
||||
},
|
||||
ShortcutDescriptor {
|
||||
id: ShortcutId::ReasoningUp,
|
||||
bindings: &[ShortcutBinding {
|
||||
key: key_hint::alt(KeyCode::Char('.')),
|
||||
condition: DisplayCondition::Always,
|
||||
}],
|
||||
prefix: "",
|
||||
label: " reasoning up",
|
||||
},
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1376,6 +1366,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
},
|
||||
);
|
||||
@@ -1392,6 +1384,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
},
|
||||
);
|
||||
@@ -1408,6 +1402,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
},
|
||||
);
|
||||
@@ -1424,6 +1420,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
},
|
||||
);
|
||||
@@ -1440,6 +1438,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
},
|
||||
);
|
||||
@@ -1456,6 +1456,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
},
|
||||
);
|
||||
@@ -1472,6 +1474,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
},
|
||||
);
|
||||
@@ -1488,6 +1492,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
},
|
||||
Some(72),
|
||||
@@ -1506,6 +1512,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
},
|
||||
/*percent*/ None,
|
||||
@@ -1524,6 +1532,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
},
|
||||
);
|
||||
@@ -1538,6 +1548,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
};
|
||||
|
||||
@@ -1565,6 +1577,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
};
|
||||
|
||||
@@ -1585,6 +1599,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: Some(Line::from("Status line content".to_string())),
|
||||
status_line_enabled: true,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
};
|
||||
|
||||
@@ -1600,6 +1616,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: Some(Line::from("Status line content".to_string())),
|
||||
status_line_enabled: true,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
};
|
||||
|
||||
@@ -1615,6 +1633,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: Some(Line::from("Status line content".to_string())),
|
||||
status_line_enabled: true,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
};
|
||||
|
||||
@@ -1630,6 +1650,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None, // command timed out / empty
|
||||
status_line_enabled: true,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
};
|
||||
|
||||
@@ -1651,6 +1673,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
};
|
||||
|
||||
@@ -1672,6 +1696,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: true,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
};
|
||||
|
||||
@@ -1696,6 +1722,8 @@ mod tests {
|
||||
"Status line content that should truncate before the mode indicator".to_string(),
|
||||
)),
|
||||
status_line_enabled: true,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
};
|
||||
|
||||
@@ -1717,6 +1745,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: Some("Robie [explorer]".to_string()),
|
||||
};
|
||||
|
||||
@@ -1732,6 +1762,8 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: Some(Line::from("Status line content".to_string())),
|
||||
status_line_enabled: true,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: Some("Robie [explorer]".to_string()),
|
||||
};
|
||||
|
||||
@@ -1753,6 +1785,8 @@ mod tests {
|
||||
.to_string(),
|
||||
)),
|
||||
status_line_enabled: true,
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
};
|
||||
|
||||
@@ -1807,6 +1841,8 @@ mod tests {
|
||||
esc_backtrack_hint: false,
|
||||
is_wsl,
|
||||
collaboration_modes_enabled: false,
|
||||
reasoning_down_key: None,
|
||||
reasoning_up_key: None,
|
||||
})
|
||||
.expect("shortcut binding")
|
||||
.key;
|
||||
|
||||
@@ -355,6 +355,7 @@ use crate::history_cell::WebSearchCell;
|
||||
use crate::key_hint;
|
||||
use crate::key_hint::KeyBinding;
|
||||
use crate::key_hint::KeyBindingListExt;
|
||||
use crate::keymap::ChatKeymap;
|
||||
use crate::keymap::RuntimeKeymap;
|
||||
use crate::keymap_setup;
|
||||
#[cfg(test)]
|
||||
@@ -824,6 +825,7 @@ pub(crate) struct ChatWidget {
|
||||
/// Holds the platform clipboard lease so copied text remains available while supported.
|
||||
clipboard_lease: Option<crate::clipboard_copy::ClipboardLease>,
|
||||
copy_last_response_binding: Vec<KeyBinding>,
|
||||
chat_keymap: ChatKeymap,
|
||||
/// Raw markdown of the most recently completed agent response that
|
||||
/// survived any local thread rollback.
|
||||
last_agent_markdown: Option<String>,
|
||||
@@ -5162,6 +5164,10 @@ impl ChatWidget {
|
||||
.as_ref()
|
||||
.map(|keymap| keymap.app.copy.clone())
|
||||
.unwrap_or_else(|| RuntimeKeymap::defaults().app.copy);
|
||||
let chat_keymap = runtime_keymap
|
||||
.as_ref()
|
||||
.map(|keymap| keymap.chat.clone())
|
||||
.unwrap_or_else(|| RuntimeKeymap::defaults().chat);
|
||||
|
||||
let mut widget = Self {
|
||||
app_event_tx: app_event_tx.clone(),
|
||||
@@ -5205,6 +5211,7 @@ impl ChatWidget {
|
||||
plan_stream_controller: None,
|
||||
clipboard_lease: None,
|
||||
copy_last_response_binding,
|
||||
chat_keymap,
|
||||
running_commands: HashMap::new(),
|
||||
collab_agent_metadata: HashMap::new(),
|
||||
pending_collab_spawn_requests: HashMap::new(),
|
||||
@@ -7845,6 +7852,7 @@ impl ChatWidget {
|
||||
) {
|
||||
self.config.tui_keymap = keymap_config;
|
||||
self.copy_last_response_binding = runtime_keymap.app.copy.clone();
|
||||
self.chat_keymap = runtime_keymap.chat.clone();
|
||||
self.bottom_pane.set_keymap_bindings(runtime_keymap);
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyEventKind;
|
||||
use crossterm::event::KeyModifiers;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use super::ChatWidget;
|
||||
use crate::app_event::AppEvent;
|
||||
use crate::key_hint::KeyBindingListExt;
|
||||
use crate::keymap::ChatKeymap;
|
||||
|
||||
/// Direction requested by a reasoning-level shortcut.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -32,15 +32,17 @@ pub(super) enum ReasoningShortcutDirection {
|
||||
}
|
||||
|
||||
impl ReasoningShortcutDirection {
|
||||
fn from_key_event(key_event: KeyEvent) -> Option<Self> {
|
||||
if key_event.kind != KeyEventKind::Press || key_event.modifiers != KeyModifiers::ALT {
|
||||
fn from_key_event(key_event: KeyEvent, keymap: &ChatKeymap) -> Option<Self> {
|
||||
if key_event.kind != KeyEventKind::Press {
|
||||
return None;
|
||||
}
|
||||
|
||||
match key_event.code {
|
||||
KeyCode::Char(',') => Some(Self::Lower),
|
||||
KeyCode::Char('.') => Some(Self::Raise),
|
||||
_ => None,
|
||||
if keymap.reasoning_down.is_pressed(key_event) {
|
||||
Some(Self::Lower)
|
||||
} else if keymap.reasoning_up.is_pressed(key_event) {
|
||||
Some(Self::Raise)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +68,9 @@ impl ChatWidget {
|
||||
/// persisting them. In Plan mode, shortcuts apply only to the active
|
||||
/// Plan-mode override and skip the global-vs-Plan scope prompt.
|
||||
pub(super) fn handle_reasoning_shortcut(&mut self, key_event: KeyEvent) -> bool {
|
||||
let Some(direction) = ReasoningShortcutDirection::from_key_event(key_event) else {
|
||||
let Some(direction) =
|
||||
ReasoningShortcutDirection::from_key_event(key_event, &self.chat_keymap)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -176,8 +180,43 @@ fn effort_rank(effort: ReasoningEffortConfig) -> i32 {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyModifiers;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn reasoning_shortcut_direction_uses_configured_chat_bindings() {
|
||||
let keymap = ChatKeymap {
|
||||
edit_previous_message: Vec::new(),
|
||||
confirm_edit_previous_message: Vec::new(),
|
||||
reasoning_down: vec![crate::key_hint::ctrl(KeyCode::Char(','))],
|
||||
reasoning_up: vec![crate::key_hint::ctrl(KeyCode::Char('.'))],
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
ReasoningShortcutDirection::from_key_event(
|
||||
KeyEvent::new(KeyCode::Char(','), KeyModifiers::CONTROL),
|
||||
&keymap,
|
||||
),
|
||||
Some(ReasoningShortcutDirection::Lower)
|
||||
);
|
||||
assert_eq!(
|
||||
ReasoningShortcutDirection::from_key_event(
|
||||
KeyEvent::new(KeyCode::Char('.'), KeyModifiers::CONTROL),
|
||||
&keymap,
|
||||
),
|
||||
Some(ReasoningShortcutDirection::Raise)
|
||||
);
|
||||
assert_eq!(
|
||||
ReasoningShortcutDirection::from_key_event(
|
||||
KeyEvent::new(KeyCode::Char(','), KeyModifiers::ALT),
|
||||
&keymap,
|
||||
),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_reasoning_effort_raises_from_default_anchor() {
|
||||
let choices = vec![
|
||||
|
||||
@@ -212,6 +212,7 @@ pub(super) async fn make_chatwidget_manual(
|
||||
plan_stream_controller: None,
|
||||
clipboard_lease: None,
|
||||
copy_last_response_binding: RuntimeKeymap::defaults().app.copy,
|
||||
chat_keymap: RuntimeKeymap::defaults().chat,
|
||||
pending_guardian_review_status: PendingGuardianReviewStatus::default(),
|
||||
terminal_title_status_kind: TerminalTitleStatusKind::Working,
|
||||
last_agent_markdown: None,
|
||||
|
||||
@@ -33,7 +33,7 @@ use std::collections::HashMap;
|
||||
///
|
||||
/// 1. Context-specific binding (`tui.keymap.<context>`).
|
||||
/// 2. `tui.keymap.global` for actions that support global fallback.
|
||||
/// 3. Built-in preset defaults (`latest` currently points to `v3`).
|
||||
/// 3. Built-in preset defaults (`latest` currently points to `v4`).
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct RuntimeKeymap {
|
||||
pub(crate) app: AppKeymap,
|
||||
@@ -72,6 +72,10 @@ pub(crate) struct ChatKeymap {
|
||||
pub(crate) edit_previous_message: Vec<KeyBinding>,
|
||||
/// Confirm edit-previous selection.
|
||||
pub(crate) confirm_edit_previous_message: Vec<KeyBinding>,
|
||||
/// Lower the active reasoning level when possible.
|
||||
pub(crate) reasoning_down: Vec<KeyBinding>,
|
||||
/// Raise the active reasoning level when possible.
|
||||
pub(crate) reasoning_up: Vec<KeyBinding>,
|
||||
}
|
||||
|
||||
/// Composer-level keybindings validated in the second app-scope conflict pass.
|
||||
@@ -376,6 +380,8 @@ impl RuntimeKeymap {
|
||||
chat,
|
||||
confirm_edit_previous_message
|
||||
),
|
||||
reasoning_down: resolve_with_global!(keymap, defaults, chat, reasoning_down),
|
||||
reasoning_up: resolve_with_global!(keymap, defaults, chat, reasoning_up),
|
||||
};
|
||||
|
||||
let composer = ComposerKeymap {
|
||||
@@ -521,7 +527,8 @@ impl RuntimeKeymap {
|
||||
|
||||
fn defaults_for_preset(preset: TuiKeymapPreset) -> Self {
|
||||
match preset {
|
||||
TuiKeymapPreset::Latest | TuiKeymapPreset::V3 => Self::defaults_v3(),
|
||||
TuiKeymapPreset::Latest | TuiKeymapPreset::V4 => Self::defaults_v4(),
|
||||
TuiKeymapPreset::V3 => Self::defaults_v3(),
|
||||
TuiKeymapPreset::V2 => Self::defaults_v2(),
|
||||
TuiKeymapPreset::V1 => Self::defaults_v1(),
|
||||
}
|
||||
@@ -543,6 +550,8 @@ impl RuntimeKeymap {
|
||||
chat: ChatKeymap {
|
||||
edit_previous_message: default_bindings![plain(KeyCode::Esc)],
|
||||
confirm_edit_previous_message: default_bindings![plain(KeyCode::Enter)],
|
||||
reasoning_down: default_bindings![],
|
||||
reasoning_up: default_bindings![],
|
||||
},
|
||||
composer: ComposerKeymap {
|
||||
submit: default_bindings![plain(KeyCode::Enter)],
|
||||
@@ -751,6 +760,17 @@ impl RuntimeKeymap {
|
||||
defaults
|
||||
}
|
||||
|
||||
/// Current keymap defaults for preset `v4`.
|
||||
///
|
||||
/// This keeps earlier presets intact while moving reasoning-step shortcuts
|
||||
/// into configurable keymap defaults.
|
||||
fn defaults_v4() -> Self {
|
||||
let mut defaults = Self::defaults_v3();
|
||||
defaults.chat.reasoning_down = default_bindings![alt(KeyCode::Char(','))];
|
||||
defaults.chat.reasoning_up = default_bindings![alt(KeyCode::Char('.'))];
|
||||
defaults
|
||||
}
|
||||
|
||||
/// Reject ambiguous bindings in scopes that are evaluated together.
|
||||
///
|
||||
/// We validate in multiple passes because runtime handling has mixed
|
||||
@@ -782,6 +802,8 @@ impl RuntimeKeymap {
|
||||
"confirm_edit_previous_message",
|
||||
self.chat.confirm_edit_previous_message.as_slice(),
|
||||
),
|
||||
("reasoning_down", self.chat.reasoning_down.as_slice()),
|
||||
("reasoning_up", self.chat.reasoning_up.as_slice()),
|
||||
],
|
||||
)?;
|
||||
|
||||
@@ -1281,6 +1303,19 @@ mod tests {
|
||||
assert_eq!(runtime.app.copy, vec![key_hint::ctrl(KeyCode::Char('o'))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_latest_reasoning_bindings_are_alt_comma_and_alt_period() {
|
||||
let runtime = RuntimeKeymap::defaults();
|
||||
assert_eq!(
|
||||
runtime.chat.reasoning_down,
|
||||
vec![key_hint::alt(KeyCode::Char(','))]
|
||||
);
|
||||
assert_eq!(
|
||||
runtime.chat.reasoning_up,
|
||||
vec![key_hint::alt(KeyCode::Char('.'))]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_global_copy_binding_reports_global_path() {
|
||||
let mut keymap = TuiKeymap::default();
|
||||
@@ -1303,6 +1338,18 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolves_reasoning_binding_from_global_fallback() {
|
||||
let mut keymap = TuiKeymap::default();
|
||||
keymap.global.reasoning_up = Some(one("ctrl-."));
|
||||
|
||||
let runtime = RuntimeKeymap::from_config(&keymap).expect("config should parse");
|
||||
assert_eq!(
|
||||
runtime.chat.reasoning_up,
|
||||
vec![key_hint::ctrl(KeyCode::Char('.'))]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_conflicting_editor_bindings() {
|
||||
let mut keymap = TuiKeymap::default();
|
||||
|
||||
@@ -773,7 +773,7 @@ mod tests {
|
||||
line.push_str(symbol);
|
||||
}
|
||||
}
|
||||
line
|
||||
line.trim_end().to_string()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
@@ -880,6 +880,8 @@ mod tests {
|
||||
"Global.open_external_editor",
|
||||
"Global.copy",
|
||||
"Global.toggle_vim_mode",
|
||||
"Chat.reasoning_down",
|
||||
"Chat.reasoning_up",
|
||||
"Editor.delete_backward_word",
|
||||
"Editor.delete_forward_word",
|
||||
"Editor.move_word_left",
|
||||
|
||||
@@ -38,6 +38,8 @@ pub(super) const KEYMAP_ACTIONS: &[KeymapActionDescriptor] = &[
|
||||
action("global", "Global", "toggle_vim_mode", "Turn Vim composer mode on or off."),
|
||||
action("chat", "Chat", "edit_previous_message", "Begin or advance edit-previous-message when the composer is empty."),
|
||||
action("chat", "Chat", "confirm_edit_previous_message", "Confirm the selected previous message to edit."),
|
||||
action("chat", "Chat", "reasoning_down", "Lower the active reasoning level when possible."),
|
||||
action("chat", "Chat", "reasoning_up", "Raise the active reasoning level when possible."),
|
||||
action("composer", "Composer", "submit", "Submit the current composer draft."),
|
||||
action("composer", "Composer", "queue", "Queue the draft while a task is running."),
|
||||
action("composer", "Composer", "toggle_shortcuts", "Show or hide the composer shortcut overlay."),
|
||||
@@ -152,6 +154,8 @@ pub(super) fn binding_slot<'a>(
|
||||
("global", "toggle_vim_mode") => Some(&mut keymap.global.toggle_vim_mode),
|
||||
("chat", "edit_previous_message") => Some(&mut keymap.chat.edit_previous_message),
|
||||
("chat", "confirm_edit_previous_message") => Some(&mut keymap.chat.confirm_edit_previous_message),
|
||||
("chat", "reasoning_down") => Some(&mut keymap.chat.reasoning_down),
|
||||
("chat", "reasoning_up") => Some(&mut keymap.chat.reasoning_up),
|
||||
("composer", "submit") => Some(&mut keymap.composer.submit),
|
||||
("composer", "queue") => Some(&mut keymap.composer.queue),
|
||||
("composer", "toggle_shortcuts") => Some(&mut keymap.composer.toggle_shortcuts),
|
||||
@@ -254,6 +258,8 @@ pub(super) fn bindings_for_action<'a>(
|
||||
("global", "toggle_vim_mode") => Some(runtime_keymap.app.toggle_vim_mode.as_slice()),
|
||||
("chat", "edit_previous_message") => Some(runtime_keymap.chat.edit_previous_message.as_slice()),
|
||||
("chat", "confirm_edit_previous_message") => Some(runtime_keymap.chat.confirm_edit_previous_message.as_slice()),
|
||||
("chat", "reasoning_down") => Some(runtime_keymap.chat.reasoning_down.as_slice()),
|
||||
("chat", "reasoning_up") => Some(runtime_keymap.chat.reasoning_up.as_slice()),
|
||||
("composer", "submit") => Some(runtime_keymap.composer.submit.as_slice()),
|
||||
("composer", "queue") => Some(runtime_keymap.composer.queue.as_slice()),
|
||||
("composer", "toggle_shortcuts") => Some(runtime_keymap.composer.toggle_shortcuts.as_slice()),
|
||||
|
||||
@@ -61,6 +61,8 @@ const KEYMAP_COMMON_ACTIONS: &[(&str, &str)] = &[
|
||||
("global", "open_external_editor"),
|
||||
("global", "copy"),
|
||||
("global", "toggle_vim_mode"),
|
||||
("chat", "reasoning_down"),
|
||||
("chat", "reasoning_up"),
|
||||
("editor", "delete_backward_word"),
|
||||
("editor", "delete_forward_word"),
|
||||
("editor", "move_word_left"),
|
||||
|
||||
+2
-2
@@ -8,9 +8,9 @@ Copy | ctrl-o | Global copy Copy Copy the last agent response to the clipboard.
|
||||
Toggle Vim Mode | unbound | Global toggle_vim_mode Toggle Vim Mode Turn Vim composer mode on or off. unbound Default
|
||||
Edit Previous Message | esc | Chat edit_previous_message Edit Previous Message Begin or advance edit-previous-message when the composer is empty. esc Default
|
||||
Confirm Edit Previous Message | enter | Chat confirm_edit_previous_message Confirm Edit Previous Message Confirm the selected previous message to edit. enter Default
|
||||
Reasoning Down | alt-, | Chat reasoning_down Reasoning Down Lower the active reasoning level when possible. alt-, Default
|
||||
Reasoning Up | alt-. | Chat reasoning_up Reasoning Up Raise the active reasoning level when possible. alt-. Default
|
||||
Submit | enter | Composer submit Submit Submit the current composer draft. enter Default
|
||||
Queue | tab | Composer queue Queue Queue the draft while a task is running. tab Default
|
||||
Toggle Shortcuts | ?, shift-? | Composer toggle_shortcuts Toggle Shortcuts Show or hide the composer shortcut overlay. ?, shift-? Default
|
||||
Insert Newline | ctrl-j, ctrl-m, enter, shift-enter | Editor insert_newline Insert Newline Insert a newline in the editor. ctrl-j, ctrl-m, enter, shift-enter Default
|
||||
Move Left | left, ctrl-b | Editor move_left Move Left Move the cursor left. left, ctrl-b Default
|
||||
Move Right | right, ctrl-f | Editor move_right Move Right Move the cursor right. right, ctrl-f Default
|
||||
|
||||
+18
-18
@@ -2,22 +2,22 @@
|
||||
source: tui/src/keymap_setup.rs
|
||||
expression: "render_picker(params, 120)"
|
||||
---
|
||||
|
||||
Keymap
|
||||
All configurable shortcuts.
|
||||
91 actions, 1 customized, 1 unbound.
|
||||
|
||||
[All] Common Customized (1) Unbound (1) App Composer Editor Vim Navigation Approval
|
||||
|
||||
Type to search shortcuts
|
||||
› Global Open Transcript ctrl-t
|
||||
Global Open External Editor ctrl-g
|
||||
Global Copy ctrl-o
|
||||
Global - Toggle Vim Mode unbound
|
||||
Chat Edit Previous Message esc
|
||||
Chat Confirm Edit Previous Message enter
|
||||
Composer * Submit ctrl-enter
|
||||
Composer Queue tab
|
||||
|
||||
Open Transcript: Open the transcript overlay.
|
||||
|
||||
Keymap
|
||||
All configurable shortcuts.
|
||||
93 actions, 1 customized, 1 unbound.
|
||||
|
||||
[All] Common Customized (1) Unbound (1) App Composer Editor Vim Navigation Approval
|
||||
|
||||
Type to search shortcuts
|
||||
› Global Open Transcript ctrl-t
|
||||
Global Open External Editor ctrl-g
|
||||
Global Copy ctrl-o
|
||||
Global - Toggle Vim Mode unbound
|
||||
Chat Edit Previous Message esc
|
||||
Chat Confirm Edit Previous Message enter
|
||||
Chat Reasoning Down alt-,
|
||||
Chat Reasoning Up alt-.
|
||||
|
||||
Open Transcript: Open the transcript overlay.
|
||||
left/right group · enter edit shortcut · * custom · - unbound · esc close
|
||||
|
||||
+5
-5
@@ -2,11 +2,11 @@
|
||||
source: tui/src/keymap_setup.rs
|
||||
expression: snapshot
|
||||
---
|
||||
tab: All (91 selectable)
|
||||
tab: Common (19 selectable)
|
||||
tab: All (93 selectable)
|
||||
tab: Common (21 selectable)
|
||||
tab: Customized (0) (0 selectable)
|
||||
tab: Unbound (1) (1 selectable)
|
||||
tab: App (6 selectable)
|
||||
tab: App (8 selectable)
|
||||
tab: Composer (3 selectable)
|
||||
tab: Editor (16 selectable)
|
||||
tab: Vim (34 selectable)
|
||||
@@ -18,9 +18,9 @@ Copy | ctrl-o | Global copy Copy Copy the last agent response to the clipboard.
|
||||
Toggle Vim Mode | unbound | Global toggle_vim_mode Toggle Vim Mode Turn Vim composer mode on or off. unbound Default
|
||||
Edit Previous Message | esc | Chat edit_previous_message Edit Previous Message Begin or advance edit-previous-message when the composer is empty. esc Default
|
||||
Confirm Edit Previous Message | enter | Chat confirm_edit_previous_message Confirm Edit Previous Message Confirm the selected previous message to edit. enter Default
|
||||
Reasoning Down | alt-, | Chat reasoning_down Reasoning Down Lower the active reasoning level when possible. alt-, Default
|
||||
Reasoning Up | alt-. | Chat reasoning_up Reasoning Up Raise the active reasoning level when possible. alt-. Default
|
||||
Submit | enter | Composer submit Submit Submit the current composer draft. enter Default
|
||||
Queue | tab | Composer queue Queue Queue the draft while a task is running. tab Default
|
||||
Toggle Shortcuts | ?, shift-? | Composer toggle_shortcuts Toggle Shortcuts Show or hide the composer shortcut overlay. ?, shift-? Default
|
||||
Insert Newline | ctrl-j, ctrl-m, enter, shift-enter | Editor insert_newline Insert Newline Insert a newline in the editor. ctrl-j, ctrl-m, enter, shift-enter Default
|
||||
Move Left | left, ctrl-b | Editor move_left Move Left Move the cursor left. left, ctrl-b Default
|
||||
Move Right | right, ctrl-f | Editor move_right Move Right Move the cursor right. right, ctrl-f Default
|
||||
|
||||
+19
-19
@@ -2,23 +2,23 @@
|
||||
source: tui/src/keymap_setup.rs
|
||||
expression: "render_picker(params, 78)"
|
||||
---
|
||||
|
||||
Keymap
|
||||
All configurable shortcuts.
|
||||
91 actions, 0 customized, 1 unbound.
|
||||
|
||||
[All] Common Customized (0) Unbound (1) App Composer Editor Vim
|
||||
Navigation Approval
|
||||
|
||||
Type to search shortcuts
|
||||
› Global Open Transcript ctrl-t
|
||||
Global Open External Editor ctrl-g
|
||||
Global Copy ctrl-o
|
||||
Global - Toggle Vim Mode unbound
|
||||
Chat Edit Previous Message esc
|
||||
Chat Confirm Edit Previous Message enter
|
||||
Composer Submit enter
|
||||
Composer Queue tab
|
||||
|
||||
Open Transcript: Open the transcript overlay.
|
||||
|
||||
Keymap
|
||||
All configurable shortcuts.
|
||||
93 actions, 0 customized, 1 unbound.
|
||||
|
||||
[All] Common Customized (0) Unbound (1) App Composer Editor Vim
|
||||
Navigation Approval
|
||||
|
||||
Type to search shortcuts
|
||||
› Global Open Transcript ctrl-t
|
||||
Global Open External Editor ctrl-g
|
||||
Global Copy ctrl-o
|
||||
Global - Toggle Vim Mode unbound
|
||||
Chat Edit Previous Message esc
|
||||
Chat Confirm Edit Previous Message enter
|
||||
Chat Reasoning Down alt-,
|
||||
Chat Reasoning Up alt-.
|
||||
|
||||
Open Transcript: Open the transcript overlay.
|
||||
left/right group · enter edit shortcut · * custom · - unbound · esc close
|
||||
|
||||
+18
-18
@@ -2,22 +2,22 @@
|
||||
source: tui/src/keymap_setup.rs
|
||||
expression: "render_picker(params, 120)"
|
||||
---
|
||||
|
||||
Keymap
|
||||
All configurable shortcuts.
|
||||
91 actions, 0 customized, 1 unbound.
|
||||
|
||||
[All] Common Customized (0) Unbound (1) App Composer Editor Vim Navigation Approval
|
||||
|
||||
Type to search shortcuts
|
||||
› Global Open Transcript ctrl-t
|
||||
Global Open External Editor ctrl-g
|
||||
Global Copy ctrl-o
|
||||
Global - Toggle Vim Mode unbound
|
||||
Chat Edit Previous Message esc
|
||||
Chat Confirm Edit Previous Message enter
|
||||
Composer Submit enter
|
||||
Composer Queue tab
|
||||
|
||||
Open Transcript: Open the transcript overlay.
|
||||
|
||||
Keymap
|
||||
All configurable shortcuts.
|
||||
93 actions, 0 customized, 1 unbound.
|
||||
|
||||
[All] Common Customized (0) Unbound (1) App Composer Editor Vim Navigation Approval
|
||||
|
||||
Type to search shortcuts
|
||||
› Global Open Transcript ctrl-t
|
||||
Global Open External Editor ctrl-g
|
||||
Global Copy ctrl-o
|
||||
Global - Toggle Vim Mode unbound
|
||||
Chat Edit Previous Message esc
|
||||
Chat Confirm Edit Previous Message enter
|
||||
Chat Reasoning Down alt-,
|
||||
Chat Reasoning Up alt-.
|
||||
|
||||
Open Transcript: Open the transcript overlay.
|
||||
left/right group · enter edit shortcut · * custom · - unbound · esc close
|
||||
|
||||
+6
-4
@@ -36,8 +36,9 @@ Precedence is applied in this order (highest first):
|
||||
|
||||
### Presets
|
||||
|
||||
- `latest`: moving alias for the newest preset; today `latest -> v3`
|
||||
- `v3`: current defaults, exposing `copy = "ctrl-o"` in the keymap
|
||||
- `latest`: moving alias for the newest preset; today `latest -> v4`
|
||||
- `v4`: current defaults, exposing reasoning shortcuts (`alt-,` / `alt-.`) in the keymap
|
||||
- `v3`: exposes `copy = "ctrl-o"` in the keymap
|
||||
- `v2`: restores `alt-d` as a `delete_forward_word` alias
|
||||
- `v1`: frozen initial configurable keymap defaults
|
||||
|
||||
@@ -50,8 +51,9 @@ TODO(docs): mirror this preset migration note on developers.openai.com.
|
||||
|
||||
- `global`: `open_transcript`, `open_external_editor`, `copy`, `edit_previous_message`,
|
||||
`confirm_edit_previous_message`, `submit`, `queue`, `toggle_shortcuts`,
|
||||
`toggle_vim_mode`
|
||||
- `chat`: `edit_previous_message`, `confirm_edit_previous_message`
|
||||
`toggle_vim_mode`, `reasoning_down`, `reasoning_up`
|
||||
- `chat`: `edit_previous_message`, `confirm_edit_previous_message`,
|
||||
`reasoning_down`, `reasoning_up`
|
||||
- `composer`: `submit`, `queue`, `toggle_shortcuts`
|
||||
- `editor`: `insert_newline`, `move_left`, `move_right`, `move_up`, `move_down`,
|
||||
`move_word_left`, `move_word_right`, `move_line_start`, `move_line_end`,
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
[tui.keymap]
|
||||
# Preset alias for built-in defaults.
|
||||
# "latest" currently points to "v3".
|
||||
# "latest" currently points to "v4".
|
||||
preset = "latest"
|
||||
|
||||
[tui.keymap.global]
|
||||
@@ -42,11 +42,17 @@ toggle_shortcuts = ["?", "shift-?"]
|
||||
# Toggle Vim mode for the composer input.
|
||||
# Empty list keeps this unbound by default.
|
||||
toggle_vim_mode = []
|
||||
# Lower active reasoning level when possible.
|
||||
reasoning_down = "alt-,"
|
||||
# Raise active reasoning level when possible.
|
||||
reasoning_up = "alt-."
|
||||
|
||||
[tui.keymap.chat]
|
||||
# Overrides [tui.keymap.global] for chat-only behavior.
|
||||
edit_previous_message = "esc"
|
||||
confirm_edit_previous_message = "enter"
|
||||
reasoning_down = "alt-,"
|
||||
reasoning_up = "alt-."
|
||||
|
||||
[tui.keymap.composer]
|
||||
# Overrides [tui.keymap.global] for composer behavior.
|
||||
|
||||
@@ -7,14 +7,14 @@ For runtime behavior, safety invariants, and testing guidance, see
|
||||
## Preset behavior
|
||||
|
||||
- `latest` is an alias to the newest shipped preset.
|
||||
- Today, `latest -> v3`.
|
||||
- Today, `latest -> v4`.
|
||||
- To keep stable behavior over time, pin `preset = "v1"`.
|
||||
|
||||
## Precedence
|
||||
|
||||
1. `tui.keymap.<context>.<action>`
|
||||
2. `tui.keymap.global.<action>` (chat/composer fallback actions only)
|
||||
3. Preset default (`v3` today)
|
||||
3. Preset default (`v4` today)
|
||||
|
||||
## Default `v1` Compatibility Notes
|
||||
|
||||
@@ -43,11 +43,15 @@ For runtime behavior, safety invariants, and testing guidance, see
|
||||
- `queue`: queue current draft while a task is running
|
||||
- `toggle_shortcuts`: toggle composer shortcut overlay
|
||||
- `toggle_vim_mode`: toggle Vim mode for composer input
|
||||
- `reasoning_down`: lower active reasoning level when possible
|
||||
- `reasoning_up`: raise active reasoning level when possible
|
||||
|
||||
### `chat`
|
||||
|
||||
- `edit_previous_message`: chat override for edit-previous flow
|
||||
- `confirm_edit_previous_message`: chat override for edit confirmation
|
||||
- `reasoning_down`: chat override for lowering active reasoning level
|
||||
- `reasoning_up`: chat override for raising active reasoning level
|
||||
|
||||
### `composer`
|
||||
|
||||
|
||||
+2
-2
@@ -85,8 +85,8 @@ at `https://github.com/openai/codex/blob/main/docs/default-keymap.toml`.
|
||||
Preset semantics:
|
||||
|
||||
1. `latest` is an alias to the newest shipped preset.
|
||||
2. `v3` is the current baseline; `v1` and `v2` are frozen for historical behavior.
|
||||
3. Today, `latest -> v3`.
|
||||
2. `v4` is the current baseline; `v1`, `v2`, and `v3` are frozen for historical behavior.
|
||||
3. Today, `latest -> v4`.
|
||||
|
||||
User guidance:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user