mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
Make keybinding hints use configured keys
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 `v4`.
|
||||
/// Today this resolves to `v5`.
|
||||
#[default]
|
||||
Latest,
|
||||
/// Frozen initial configurable keymap defaults.
|
||||
@@ -40,6 +40,8 @@ pub enum TuiKeymapPreset {
|
||||
V3,
|
||||
/// Current defaults with reasoning shortcuts exposed through keymap config.
|
||||
V4,
|
||||
/// Current defaults with approval thread and strict-review shortcuts exposed.
|
||||
V5,
|
||||
}
|
||||
|
||||
/// Normalized string representation of a keybinding (for example `ctrl-a`).
|
||||
@@ -329,12 +331,16 @@ pub struct TuiListKeymap {
|
||||
pub struct TuiApprovalKeymap {
|
||||
/// Open the full-screen approval details view.
|
||||
pub open_fullscreen: Option<KeybindingsSpec>,
|
||||
/// Open the originating thread from an approval prompt.
|
||||
pub open_thread: Option<KeybindingsSpec>,
|
||||
/// Approve the primary option.
|
||||
pub approve: Option<KeybindingsSpec>,
|
||||
/// Approve for session when that option exists.
|
||||
pub approve_for_session: Option<KeybindingsSpec>,
|
||||
/// Approve with exec-policy prefix when that option exists.
|
||||
pub approve_for_prefix: Option<KeybindingsSpec>,
|
||||
/// Grant permissions for this turn with strict auto review.
|
||||
pub approve_with_strict_auto_review: Option<KeybindingsSpec>,
|
||||
/// Decline and provide corrective guidance.
|
||||
pub decline: Option<KeybindingsSpec>,
|
||||
/// Cancel an elicitation request.
|
||||
|
||||
@@ -2198,9 +2198,11 @@
|
||||
"approve": null,
|
||||
"approve_for_prefix": null,
|
||||
"approve_for_session": null,
|
||||
"approve_with_strict_auto_review": null,
|
||||
"cancel": null,
|
||||
"decline": null,
|
||||
"open_fullscreen": null
|
||||
"open_fullscreen": null,
|
||||
"open_thread": null
|
||||
},
|
||||
"chat": {
|
||||
"confirm_edit_previous_message": null,
|
||||
@@ -2416,6 +2418,14 @@
|
||||
],
|
||||
"description": "Approve for session when that option exists."
|
||||
},
|
||||
"approve_with_strict_auto_review": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeybindingsSpec"
|
||||
}
|
||||
],
|
||||
"description": "Grant permissions for this turn with strict auto review."
|
||||
},
|
||||
"cancel": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -2439,6 +2449,14 @@
|
||||
}
|
||||
],
|
||||
"description": "Open the full-screen approval details view."
|
||||
},
|
||||
"open_thread": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeybindingsSpec"
|
||||
}
|
||||
],
|
||||
"description": "Open the originating thread from an approval prompt."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -2757,9 +2775,11 @@
|
||||
"approve": null,
|
||||
"approve_for_prefix": null,
|
||||
"approve_for_session": null,
|
||||
"approve_with_strict_auto_review": null,
|
||||
"cancel": null,
|
||||
"decline": null,
|
||||
"open_fullscreen": null
|
||||
"open_fullscreen": null,
|
||||
"open_thread": null
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
@@ -2952,7 +2972,7 @@
|
||||
"description": "Versioned keymap defaults.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Pointer alias to the latest shipped preset.\n\nToday this resolves to `v4`.",
|
||||
"description": "Pointer alias to the latest shipped preset.\n\nToday this resolves to `v5`.",
|
||||
"enum": [
|
||||
"latest"
|
||||
],
|
||||
@@ -2985,6 +3005,13 @@
|
||||
"v4"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Current defaults with approval thread and strict-review shortcuts exposed.",
|
||||
"enum": [
|
||||
"v5"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -30,6 +30,7 @@ use crate::key_hint::KeyBinding;
|
||||
use crate::key_hint::KeyBindingListExt;
|
||||
use crate::keymap::ApprovalKeymap;
|
||||
use crate::keymap::ListKeymap;
|
||||
use crate::keymap::primary_binding;
|
||||
use crate::render::highlight::highlight_bash_to_lines;
|
||||
use crate::render::renderable::ColumnRenderable;
|
||||
use crate::render::renderable::Renderable;
|
||||
@@ -210,8 +211,13 @@ impl ApprovalOverlay {
|
||||
fn set_current(&mut self, request: ApprovalRequest) {
|
||||
self.current_complete = false;
|
||||
let header = build_header(&request);
|
||||
let (options, params) =
|
||||
Self::build_options(&request, header, &self.features, &self.approval_keymap);
|
||||
let (options, params) = Self::build_options(
|
||||
&request,
|
||||
header,
|
||||
&self.features,
|
||||
&self.approval_keymap,
|
||||
&self.list_keymap,
|
||||
);
|
||||
self.current_request = Some(request);
|
||||
self.options = options;
|
||||
self.list =
|
||||
@@ -223,6 +229,7 @@ impl ApprovalOverlay {
|
||||
header: Box<dyn Renderable>,
|
||||
_features: &Features,
|
||||
approval_keymap: &ApprovalKeymap,
|
||||
list_keymap: &ListKeymap,
|
||||
) -> (Vec<ApprovalOption>, SelectionViewParams) {
|
||||
let (options, title) = match request {
|
||||
ApprovalRequest::Exec {
|
||||
@@ -278,7 +285,7 @@ impl ApprovalOverlay {
|
||||
.collect();
|
||||
|
||||
let params = SelectionViewParams {
|
||||
footer_hint: Some(approval_footer_hint(request)),
|
||||
footer_hint: Some(approval_footer_hint(request, list_keymap, approval_keymap)),
|
||||
items,
|
||||
header,
|
||||
..Default::default()
|
||||
@@ -453,7 +460,7 @@ impl ApprovalOverlay {
|
||||
}
|
||||
|
||||
if key_event.kind == KeyEventKind::Press
|
||||
&& matches!(key_event.code, KeyCode::Char('o'))
|
||||
&& self.approval_keymap.open_thread.is_pressed(*key_event)
|
||||
&& let Some(request) = self.current_request.as_ref()
|
||||
&& request.thread_label().is_some()
|
||||
{
|
||||
@@ -560,20 +567,26 @@ impl Renderable for ApprovalOverlay {
|
||||
}
|
||||
}
|
||||
|
||||
fn approval_footer_hint(request: &ApprovalRequest) -> Line<'static> {
|
||||
fn approval_footer_hint(
|
||||
request: &ApprovalRequest,
|
||||
list_keymap: &ListKeymap,
|
||||
approval_keymap: &ApprovalKeymap,
|
||||
) -> Line<'static> {
|
||||
let accept =
|
||||
primary_binding(&list_keymap.accept).unwrap_or_else(|| key_hint::plain(KeyCode::Enter));
|
||||
let cancel =
|
||||
primary_binding(&list_keymap.cancel).unwrap_or_else(|| key_hint::plain(KeyCode::Esc));
|
||||
let mut spans = vec![
|
||||
"Press ".into(),
|
||||
key_hint::plain(KeyCode::Enter).into(),
|
||||
accept.into(),
|
||||
" to confirm or ".into(),
|
||||
key_hint::plain(KeyCode::Esc).into(),
|
||||
cancel.into(),
|
||||
" to cancel".into(),
|
||||
];
|
||||
if request.thread_label().is_some() {
|
||||
spans.extend([
|
||||
" or ".into(),
|
||||
key_hint::plain(KeyCode::Char('o')).into(),
|
||||
" to open thread".into(),
|
||||
]);
|
||||
if request.thread_label().is_some()
|
||||
&& let Some(open_thread) = primary_binding(&approval_keymap.open_thread)
|
||||
{
|
||||
spans.extend([" or ".into(), open_thread.into(), " to open thread".into()]);
|
||||
}
|
||||
Line::from(spans)
|
||||
}
|
||||
@@ -933,7 +946,7 @@ fn permissions_options(keymap: &ApprovalKeymap) -> Vec<ApprovalOption> {
|
||||
decision: ApprovalDecision::Permissions(
|
||||
PermissionsDecision::GrantForTurnWithStrictAutoReview,
|
||||
),
|
||||
shortcuts: vec![key_hint::plain(KeyCode::Char('r'))],
|
||||
shortcuts: keymap.approve_with_strict_auto_review.clone(),
|
||||
},
|
||||
ApprovalOption {
|
||||
label: "Yes, grant these permissions for this session".to_string(),
|
||||
|
||||
@@ -394,6 +394,9 @@ pub(crate) struct ChatComposer {
|
||||
queue_keys: Vec<KeyBinding>,
|
||||
toggle_shortcuts_keys: Vec<KeyBinding>,
|
||||
editor_keymap: EditorKeymap,
|
||||
footer_toggle_shortcuts_key: Option<KeyBinding>,
|
||||
footer_queue_key: Option<KeyBinding>,
|
||||
footer_insert_newline_key: Option<KeyBinding>,
|
||||
footer_external_editor_key: Option<KeyBinding>,
|
||||
footer_edit_previous_key: Option<KeyBinding>,
|
||||
footer_show_transcript_key: Option<KeyBinding>,
|
||||
@@ -553,6 +556,13 @@ impl ChatComposer {
|
||||
key_hint::shift(KeyCode::Char('?')),
|
||||
],
|
||||
editor_keymap: RuntimeKeymap::defaults().editor,
|
||||
footer_toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
footer_queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
footer_insert_newline_key: Some(if use_shift_enter_hint {
|
||||
key_hint::shift(KeyCode::Enter)
|
||||
} else {
|
||||
key_hint::ctrl(KeyCode::Char('j'))
|
||||
}),
|
||||
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'))),
|
||||
@@ -637,6 +647,15 @@ impl ChatComposer {
|
||||
self.toggle_shortcuts_keys = keymap.composer.toggle_shortcuts.clone();
|
||||
self.editor_keymap = keymap.editor.clone();
|
||||
self.textarea.set_keymap_bindings(keymap);
|
||||
self.footer_toggle_shortcuts_key = primary_binding(&keymap.composer.toggle_shortcuts);
|
||||
self.footer_queue_key = primary_binding(&keymap.composer.queue);
|
||||
let shift_enter = key_hint::shift(KeyCode::Enter);
|
||||
self.footer_insert_newline_key =
|
||||
if self.use_shift_enter_hint && keymap.editor.insert_newline.contains(&shift_enter) {
|
||||
Some(shift_enter)
|
||||
} else {
|
||||
primary_binding(&keymap.editor.insert_newline)
|
||||
};
|
||||
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);
|
||||
@@ -3324,6 +3343,12 @@ impl ChatComposer {
|
||||
is_wsl,
|
||||
status_line_value: self.status_line_value.clone(),
|
||||
status_line_enabled: self.status_line_enabled,
|
||||
toggle_shortcuts_key: self.footer_toggle_shortcuts_key,
|
||||
queue_key: self.footer_queue_key,
|
||||
insert_newline_key: self.footer_insert_newline_key,
|
||||
external_editor_key: self.footer_external_editor_key,
|
||||
edit_previous_key: self.footer_edit_previous_key,
|
||||
show_transcript_key: self.footer_show_transcript_key,
|
||||
reasoning_down_key: self.footer_reasoning_down_key,
|
||||
reasoning_up_key: self.footer_reasoning_up_key,
|
||||
active_agent_label: self.active_agent_label.clone(),
|
||||
@@ -4146,6 +4171,7 @@ impl ChatComposer {
|
||||
Some(single_line_footer_layout(
|
||||
hint_rect,
|
||||
right_width,
|
||||
&footer_props,
|
||||
left_mode_indicator,
|
||||
show_cycle_hint,
|
||||
show_shortcuts_hint,
|
||||
|
||||
@@ -76,6 +76,12 @@ 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) toggle_shortcuts_key: Option<KeyBinding>,
|
||||
pub(crate) queue_key: Option<KeyBinding>,
|
||||
pub(crate) insert_newline_key: Option<KeyBinding>,
|
||||
pub(crate) external_editor_key: Option<KeyBinding>,
|
||||
pub(crate) edit_previous_key: Option<KeyBinding>,
|
||||
pub(crate) show_transcript_key: Option<KeyBinding>,
|
||||
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
|
||||
@@ -276,21 +282,28 @@ struct LeftSideState {
|
||||
fn left_side_line(
|
||||
collaboration_mode_indicator: Option<CollaborationModeIndicator>,
|
||||
state: LeftSideState,
|
||||
props: &FooterProps,
|
||||
) -> Line<'static> {
|
||||
let mut line = Line::from("");
|
||||
match state.hint {
|
||||
SummaryHintKind::None => {}
|
||||
SummaryHintKind::Shortcuts => {
|
||||
line.push_span(key_hint::plain(KeyCode::Char('?')));
|
||||
line.push_span(" for shortcuts".dim());
|
||||
if let Some(key) = props.toggle_shortcuts_key {
|
||||
line.push_span(key);
|
||||
line.push_span(" for shortcuts".dim());
|
||||
}
|
||||
}
|
||||
SummaryHintKind::QueueMessage => {
|
||||
line.push_span(key_hint::plain(KeyCode::Tab));
|
||||
line.push_span(" to queue message".dim());
|
||||
if let Some(key) = props.queue_key {
|
||||
line.push_span(key);
|
||||
line.push_span(" to queue message".dim());
|
||||
}
|
||||
}
|
||||
SummaryHintKind::QueueShort => {
|
||||
line.push_span(key_hint::plain(KeyCode::Tab));
|
||||
line.push_span(" to queue".dim());
|
||||
if let Some(key) = props.queue_key {
|
||||
line.push_span(key);
|
||||
line.push_span(" to queue".dim());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -315,6 +328,7 @@ pub(crate) enum SummaryLeft {
|
||||
pub(crate) fn single_line_footer_layout(
|
||||
area: Rect,
|
||||
context_width: u16,
|
||||
props: &FooterProps,
|
||||
collaboration_mode_indicator: Option<CollaborationModeIndicator>,
|
||||
show_cycle_hint: bool,
|
||||
show_shortcuts_hint: bool,
|
||||
@@ -331,7 +345,7 @@ pub(crate) fn single_line_footer_layout(
|
||||
hint: hint_kind,
|
||||
show_cycle_hint,
|
||||
};
|
||||
let default_line = left_side_line(collaboration_mode_indicator, default_state);
|
||||
let default_line = left_side_line(collaboration_mode_indicator, default_state, props);
|
||||
let default_width = default_line.width() as u16;
|
||||
if default_width > 0 && can_show_left_with_context(area, default_width, context_width) {
|
||||
return (SummaryLeft::Default, true);
|
||||
@@ -341,7 +355,7 @@ pub(crate) fn single_line_footer_layout(
|
||||
if state == default_state {
|
||||
default_line.clone()
|
||||
} else {
|
||||
left_side_line(collaboration_mode_indicator, state)
|
||||
left_side_line(collaboration_mode_indicator, state, props)
|
||||
}
|
||||
};
|
||||
let state_width = |state: LeftSideState| -> u16 { state_line(state).width() as u16 };
|
||||
@@ -450,7 +464,8 @@ pub(crate) fn single_line_footer_layout(
|
||||
// Compute the width without going through `state_line` so we do not
|
||||
// depend on `default_state` (which may still be a queue variant).
|
||||
let mode_only_width =
|
||||
left_side_line(Some(collaboration_mode_indicator), mode_only_state).width() as u16;
|
||||
left_side_line(Some(collaboration_mode_indicator), mode_only_state, props).width()
|
||||
as u16;
|
||||
if !context_requires_cycle_hint
|
||||
&& can_show_left_with_context(area, mode_only_width, context_width)
|
||||
{
|
||||
@@ -458,6 +473,7 @@ pub(crate) fn single_line_footer_layout(
|
||||
SummaryLeft::Custom(left_side_line(
|
||||
Some(collaboration_mode_indicator),
|
||||
mode_only_state,
|
||||
props,
|
||||
)),
|
||||
true, // show_context
|
||||
);
|
||||
@@ -467,6 +483,7 @@ pub(crate) fn single_line_footer_layout(
|
||||
SummaryLeft::Custom(left_side_line(
|
||||
Some(collaboration_mode_indicator),
|
||||
mode_only_state,
|
||||
props,
|
||||
)),
|
||||
false, // show_context
|
||||
);
|
||||
@@ -616,7 +633,7 @@ fn footer_from_props_lines(
|
||||
},
|
||||
show_cycle_hint,
|
||||
};
|
||||
vec![left_side_line(collaboration_mode_indicator, state)]
|
||||
vec![left_side_line(collaboration_mode_indicator, state, props)]
|
||||
}
|
||||
FooterMode::ShortcutOverlay => {
|
||||
let state = ShortcutsState {
|
||||
@@ -624,12 +641,20 @@ fn footer_from_props_lines(
|
||||
esc_backtrack_hint: props.esc_backtrack_hint,
|
||||
is_wsl: props.is_wsl,
|
||||
collaboration_modes_enabled: props.collaboration_modes_enabled,
|
||||
insert_newline_key: props.insert_newline_key,
|
||||
queue_key: props.queue_key,
|
||||
external_editor_key: props.external_editor_key,
|
||||
edit_previous_key: props.edit_previous_key,
|
||||
show_transcript_key: props.show_transcript_key,
|
||||
reasoning_down_key: props.reasoning_down_key,
|
||||
reasoning_up_key: props.reasoning_up_key,
|
||||
};
|
||||
shortcut_overlay_lines(state)
|
||||
}
|
||||
FooterMode::EscHint => vec![esc_hint_line(props.esc_backtrack_hint)],
|
||||
FooterMode::EscHint => vec![esc_hint_line(
|
||||
props.esc_backtrack_hint,
|
||||
props.edit_previous_key,
|
||||
)],
|
||||
FooterMode::ComposerHasDraft => {
|
||||
let state = LeftSideState {
|
||||
hint: if show_queue_hint {
|
||||
@@ -641,7 +666,7 @@ fn footer_from_props_lines(
|
||||
},
|
||||
show_cycle_hint,
|
||||
};
|
||||
vec![left_side_line(collaboration_mode_indicator, state)]
|
||||
vec![left_side_line(collaboration_mode_indicator, state, props)]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -743,6 +768,11 @@ struct ShortcutsState {
|
||||
esc_backtrack_hint: bool,
|
||||
is_wsl: bool,
|
||||
collaboration_modes_enabled: bool,
|
||||
insert_newline_key: Option<KeyBinding>,
|
||||
queue_key: Option<KeyBinding>,
|
||||
external_editor_key: Option<KeyBinding>,
|
||||
edit_previous_key: Option<KeyBinding>,
|
||||
show_transcript_key: Option<KeyBinding>,
|
||||
reasoning_down_key: Option<KeyBinding>,
|
||||
reasoning_up_key: Option<KeyBinding>,
|
||||
}
|
||||
@@ -751,15 +781,15 @@ fn quit_shortcut_reminder_line(key: KeyBinding) -> Line<'static> {
|
||||
Line::from(vec![key.into(), " again to quit".into()]).dim()
|
||||
}
|
||||
|
||||
fn esc_hint_line(esc_backtrack_hint: bool) -> Line<'static> {
|
||||
let esc = key_hint::plain(KeyCode::Esc);
|
||||
fn esc_hint_line(esc_backtrack_hint: bool, key: Option<KeyBinding>) -> Line<'static> {
|
||||
let key = key.unwrap_or_else(|| key_hint::plain(KeyCode::Esc));
|
||||
if esc_backtrack_hint {
|
||||
Line::from(vec![esc.into(), " again to edit previous message".into()]).dim()
|
||||
Line::from(vec![key.into(), " again to edit previous message".into()]).dim()
|
||||
} else {
|
||||
Line::from(vec![
|
||||
esc.into(),
|
||||
key.into(),
|
||||
" ".into(),
|
||||
esc.into(),
|
||||
key.into(),
|
||||
" to edit previous message".into(),
|
||||
])
|
||||
.dim()
|
||||
@@ -957,8 +987,25 @@ impl ShortcutDescriptor {
|
||||
}
|
||||
|
||||
fn overlay_entry(&self, state: ShortcutsState) -> Option<Line<'static>> {
|
||||
let binding = self.binding_for(state)?;
|
||||
let mut line = Line::from(vec![self.prefix.into(), binding.key.into()]);
|
||||
let key = match self.id {
|
||||
ShortcutId::InsertNewline => state
|
||||
.insert_newline_key
|
||||
.or_else(|| self.binding_for(state).map(|binding| binding.key)),
|
||||
ShortcutId::QueueMessageTab => state
|
||||
.queue_key
|
||||
.or_else(|| self.binding_for(state).map(|binding| binding.key)),
|
||||
ShortcutId::ExternalEditor => state
|
||||
.external_editor_key
|
||||
.or_else(|| self.binding_for(state).map(|binding| binding.key)),
|
||||
ShortcutId::EditPrevious => state
|
||||
.edit_previous_key
|
||||
.or_else(|| self.binding_for(state).map(|binding| binding.key)),
|
||||
ShortcutId::ShowTranscript => state
|
||||
.show_transcript_key
|
||||
.or_else(|| self.binding_for(state).map(|binding| binding.key)),
|
||||
_ => self.binding_for(state).map(|binding| binding.key),
|
||||
}?;
|
||||
let mut line = Line::from(vec![self.prefix.into(), key.into()]);
|
||||
match self.id {
|
||||
ShortcutId::EditPrevious => {
|
||||
if state.esc_backtrack_hint {
|
||||
@@ -966,7 +1013,7 @@ impl ShortcutDescriptor {
|
||||
} else {
|
||||
line.extend(vec![
|
||||
" ".into(),
|
||||
key_hint::plain(KeyCode::Esc).into(),
|
||||
key.into(),
|
||||
" to edit previous message".into(),
|
||||
]);
|
||||
}
|
||||
@@ -1248,6 +1295,7 @@ mod tests {
|
||||
let (summary_left, show_context) = single_line_footer_layout(
|
||||
area,
|
||||
right_width,
|
||||
props,
|
||||
left_mode_indicator,
|
||||
show_cycle_hint,
|
||||
show_shortcuts_hint,
|
||||
@@ -1366,6 +1414,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1384,6 +1438,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::shift(KeyCode::Enter)),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1402,6 +1462,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1420,6 +1486,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1438,6 +1510,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1456,6 +1534,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1474,6 +1558,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1492,6 +1582,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1512,6 +1608,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1532,6 +1634,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1548,6 +1656,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1577,6 +1691,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1599,6 +1719,12 @@ 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,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1616,6 +1742,12 @@ 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,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1633,6 +1765,12 @@ 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,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1650,6 +1788,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None, // command timed out / empty
|
||||
status_line_enabled: true,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1673,6 +1817,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1696,6 +1846,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: true,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1722,6 +1878,12 @@ mod tests {
|
||||
"Status line content that should truncate before the mode indicator".to_string(),
|
||||
)),
|
||||
status_line_enabled: true,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1745,6 +1907,12 @@ mod tests {
|
||||
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
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()),
|
||||
@@ -1762,6 +1930,12 @@ 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,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
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()),
|
||||
@@ -1785,6 +1959,12 @@ mod tests {
|
||||
.to_string(),
|
||||
)),
|
||||
status_line_enabled: true,
|
||||
toggle_shortcuts_key: Some(key_hint::plain(KeyCode::Char('?'))),
|
||||
queue_key: Some(key_hint::plain(KeyCode::Tab)),
|
||||
insert_newline_key: Some(key_hint::ctrl(KeyCode::Char('j'))),
|
||||
external_editor_key: Some(key_hint::ctrl(KeyCode::Char('g'))),
|
||||
edit_previous_key: Some(key_hint::plain(KeyCode::Esc)),
|
||||
show_transcript_key: Some(key_hint::ctrl(KeyCode::Char('t'))),
|
||||
reasoning_down_key: Some(key_hint::alt(KeyCode::Char(','))),
|
||||
reasoning_up_key: Some(key_hint::alt(KeyCode::Char('.'))),
|
||||
active_agent_label: None,
|
||||
@@ -1841,6 +2021,11 @@ mod tests {
|
||||
esc_backtrack_hint: false,
|
||||
is_wsl,
|
||||
collaboration_modes_enabled: false,
|
||||
insert_newline_key: None,
|
||||
queue_key: None,
|
||||
external_editor_key: None,
|
||||
edit_previous_key: None,
|
||||
show_transcript_key: None,
|
||||
reasoning_down_key: None,
|
||||
reasoning_up_key: None,
|
||||
})
|
||||
|
||||
@@ -1417,6 +1417,7 @@ mod tests {
|
||||
/*current_name*/ None,
|
||||
Some(&codex_home),
|
||||
Some(94),
|
||||
&crate::keymap::RuntimeKeymap::defaults().list,
|
||||
);
|
||||
let view = new_view(params, tx);
|
||||
|
||||
@@ -1430,6 +1431,7 @@ mod tests {
|
||||
/*current_name*/ None,
|
||||
/*codex_home*/ None,
|
||||
Some(120),
|
||||
&crate::keymap::RuntimeKeymap::defaults().list,
|
||||
);
|
||||
assert!(
|
||||
params.preserve_side_content_bg,
|
||||
|
||||
@@ -4,6 +4,8 @@ use crossterm::event::KeyCode;
|
||||
use ratatui::text::Line;
|
||||
|
||||
use crate::key_hint;
|
||||
use crate::keymap::ListKeymap;
|
||||
use crate::keymap::primary_binding;
|
||||
|
||||
/// Maximum number of rows any popup should attempt to display.
|
||||
/// Keep this consistent across all popups for a uniform feel.
|
||||
@@ -11,11 +13,22 @@ pub(crate) const MAX_POPUP_ROWS: usize = 8;
|
||||
|
||||
/// Standard footer hint text used by popups.
|
||||
pub(crate) fn standard_popup_hint_line() -> Line<'static> {
|
||||
standard_popup_hint_line_for_keymap(&ListKeymap {
|
||||
move_up: Vec::new(),
|
||||
move_down: Vec::new(),
|
||||
accept: vec![key_hint::plain(KeyCode::Enter)],
|
||||
cancel: vec![key_hint::plain(KeyCode::Esc)],
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn standard_popup_hint_line_for_keymap(keymap: &ListKeymap) -> Line<'static> {
|
||||
let accept = primary_binding(&keymap.accept).unwrap_or_else(|| key_hint::plain(KeyCode::Enter));
|
||||
let cancel = primary_binding(&keymap.cancel).unwrap_or_else(|| key_hint::plain(KeyCode::Esc));
|
||||
Line::from(vec![
|
||||
"Press ".into(),
|
||||
key_hint::plain(KeyCode::Enter).into(),
|
||||
accept.into(),
|
||||
" to confirm or ".into(),
|
||||
key_hint::plain(KeyCode::Esc).into(),
|
||||
cancel.into(),
|
||||
" to go back".into(),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ use crate::bottom_pane::SelectionAction;
|
||||
use crate::bottom_pane::SelectionItem;
|
||||
use crate::bottom_pane::SelectionViewParams;
|
||||
use crate::bottom_pane::custom_prompt_view::CustomPromptView;
|
||||
use crate::bottom_pane::popup_consts::standard_popup_hint_line;
|
||||
use crate::bottom_pane::popup_consts::standard_popup_hint_line_for_keymap;
|
||||
use crate::clipboard_paste::paste_image_to_temp_png;
|
||||
use crate::collaboration_modes;
|
||||
use crate::diff_render::display_path_for;
|
||||
@@ -356,6 +356,7 @@ use crate::key_hint;
|
||||
use crate::key_hint::KeyBinding;
|
||||
use crate::key_hint::KeyBindingListExt;
|
||||
use crate::keymap::ChatKeymap;
|
||||
use crate::keymap::ListKeymap;
|
||||
use crate::keymap::RuntimeKeymap;
|
||||
use crate::keymap_setup;
|
||||
#[cfg(test)]
|
||||
@@ -826,6 +827,7 @@ pub(crate) struct ChatWidget {
|
||||
clipboard_lease: Option<crate::clipboard_copy::ClipboardLease>,
|
||||
copy_last_response_binding: Vec<KeyBinding>,
|
||||
chat_keymap: ChatKeymap,
|
||||
list_keymap: ListKeymap,
|
||||
/// Raw markdown of the most recently completed agent response that
|
||||
/// survived any local thread rollback.
|
||||
last_agent_markdown: Option<String>,
|
||||
@@ -2784,7 +2786,7 @@ impl ChatWidget {
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: Some(MULTI_AGENT_ENABLE_TITLE.to_string()),
|
||||
subtitle: Some("Subagents are currently disabled in your config.".to_string()),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
..Default::default()
|
||||
});
|
||||
@@ -2834,7 +2836,7 @@ impl ChatWidget {
|
||||
"Learn more: ".dim(),
|
||||
MEMORIES_DOC_URL.cyan().underlined(),
|
||||
])),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
..Default::default()
|
||||
});
|
||||
@@ -5168,6 +5170,10 @@ impl ChatWidget {
|
||||
.as_ref()
|
||||
.map(|keymap| keymap.chat.clone())
|
||||
.unwrap_or_else(|| RuntimeKeymap::defaults().chat);
|
||||
let list_keymap = runtime_keymap
|
||||
.as_ref()
|
||||
.map(|keymap| keymap.list.clone())
|
||||
.unwrap_or_else(|| RuntimeKeymap::defaults().list);
|
||||
|
||||
let mut widget = Self {
|
||||
app_event_tx: app_event_tx.clone(),
|
||||
@@ -5212,6 +5218,7 @@ impl ChatWidget {
|
||||
clipboard_lease: None,
|
||||
copy_last_response_binding,
|
||||
chat_keymap,
|
||||
list_keymap,
|
||||
running_commands: HashMap::new(),
|
||||
collab_agent_metadata: HashMap::new(),
|
||||
pending_collab_spawn_requests: HashMap::new(),
|
||||
@@ -7782,6 +7789,7 @@ impl ChatWidget {
|
||||
self.config.tui_theme.as_deref(),
|
||||
codex_home.as_deref(),
|
||||
terminal_width,
|
||||
&self.list_keymap,
|
||||
);
|
||||
self.bottom_pane.show_selection_view(params);
|
||||
}
|
||||
@@ -7853,10 +7861,15 @@ 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.list_keymap = runtime_keymap.list.clone();
|
||||
self.bottom_pane.set_keymap_bindings(runtime_keymap);
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
fn standard_popup_hint_line(&self) -> Line<'static> {
|
||||
standard_popup_hint_line_for_keymap(&self.list_keymap)
|
||||
}
|
||||
|
||||
pub(crate) fn return_to_keymap_picker(
|
||||
&mut self,
|
||||
context: &str,
|
||||
@@ -8166,7 +8179,7 @@ impl ChatWidget {
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: Some("Approaching rate limits".to_string()),
|
||||
subtitle: Some(format!("Switch to {switch_model} for lower credit usage?")),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
..Default::default()
|
||||
});
|
||||
@@ -8212,7 +8225,7 @@ impl ChatWidget {
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: Some(title.to_string()),
|
||||
subtitle: Some(prompt.to_string()),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
initial_selected_idx: Some(1),
|
||||
..Default::default()
|
||||
@@ -8361,7 +8374,7 @@ impl ChatWidget {
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
header: Box::new(header),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
..Default::default()
|
||||
});
|
||||
@@ -8394,7 +8407,7 @@ impl ChatWidget {
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: Some("Settings".to_string()),
|
||||
subtitle: Some("Configure settings for Codex.".to_string()),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
..Default::default()
|
||||
});
|
||||
@@ -8479,7 +8492,7 @@ impl ChatWidget {
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
header: Box::new(header),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
..Default::default()
|
||||
});
|
||||
@@ -8516,7 +8529,7 @@ impl ChatWidget {
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
header: Box::new(header),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
..Default::default()
|
||||
});
|
||||
@@ -8639,7 +8652,7 @@ impl ChatWidget {
|
||||
"Pick a quick auto mode or browse all models.",
|
||||
);
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
header,
|
||||
..Default::default()
|
||||
@@ -8744,7 +8757,7 @@ impl ChatWidget {
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: Some("Select Collaboration Mode".to_string()),
|
||||
subtitle: Some("Pick a collaboration preset.".to_string()),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
..Default::default()
|
||||
});
|
||||
@@ -8855,7 +8868,7 @@ impl ChatWidget {
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: Some(PLAN_MODE_REASONING_SCOPE_TITLE.to_string()),
|
||||
subtitle: Some(subtitle),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items: vec![
|
||||
SelectionItem {
|
||||
name: PLAN_MODE_REASONING_SCOPE_PLAN_ONLY.to_string(),
|
||||
@@ -9039,7 +9052,7 @@ impl ChatWidget {
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
header: Box::new(header),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
initial_selected_idx,
|
||||
..Default::default()
|
||||
@@ -9277,7 +9290,7 @@ impl ChatWidget {
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: Some("Update Model Permissions".to_string()),
|
||||
footer_note,
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
header: Box::new(()),
|
||||
..Default::default()
|
||||
@@ -9480,7 +9493,7 @@ impl ChatWidget {
|
||||
];
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
header: Box::new(header),
|
||||
..Default::default()
|
||||
@@ -9591,7 +9604,7 @@ impl ChatWidget {
|
||||
];
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
header: Box::new(header),
|
||||
..Default::default()
|
||||
@@ -9651,7 +9664,7 @@ impl ChatWidget {
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: None,
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
header: Box::new(header),
|
||||
..Default::default()
|
||||
@@ -9728,7 +9741,7 @@ impl ChatWidget {
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: None,
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
header: Box::new(header),
|
||||
..Default::default()
|
||||
@@ -9819,7 +9832,7 @@ impl ChatWidget {
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: None,
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
header: Box::new(header),
|
||||
..Default::default()
|
||||
@@ -11280,7 +11293,7 @@ impl ChatWidget {
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: Some("Select a review preset".into()),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
..Default::default()
|
||||
});
|
||||
@@ -11313,7 +11326,7 @@ impl ChatWidget {
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: Some("Select a base branch".to_string()),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
is_searchable: true,
|
||||
search_placeholder: Some("Type to search branches".to_string()),
|
||||
@@ -11349,7 +11362,7 @@ impl ChatWidget {
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: Some("Select a commit to review".to_string()),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(self.standard_popup_hint_line()),
|
||||
items,
|
||||
is_searchable: true,
|
||||
search_placeholder: Some("Type to search commits".to_string()),
|
||||
@@ -11731,7 +11744,7 @@ pub(crate) fn show_review_commit_picker_with_entries(
|
||||
|
||||
chat.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: Some("Select a commit to review".to_string()),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(chat.standard_popup_hint_line()),
|
||||
items,
|
||||
is_searchable: true,
|
||||
search_placeholder: Some("Type to search commits".to_string()),
|
||||
|
||||
@@ -213,6 +213,7 @@ pub(super) async fn make_chatwidget_manual(
|
||||
clipboard_lease: None,
|
||||
copy_last_response_binding: RuntimeKeymap::defaults().app.copy,
|
||||
chat_keymap: RuntimeKeymap::defaults().chat,
|
||||
list_keymap: RuntimeKeymap::defaults().list,
|
||||
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 `v4`).
|
||||
/// 3. Built-in preset defaults (`latest` currently points to `v5`).
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct RuntimeKeymap {
|
||||
pub(crate) app: AppKeymap,
|
||||
@@ -207,9 +207,11 @@ pub(crate) struct ListKeymap {
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct ApprovalKeymap {
|
||||
pub(crate) open_fullscreen: Vec<KeyBinding>,
|
||||
pub(crate) open_thread: Vec<KeyBinding>,
|
||||
pub(crate) approve: Vec<KeyBinding>,
|
||||
pub(crate) approve_for_session: Vec<KeyBinding>,
|
||||
pub(crate) approve_for_prefix: Vec<KeyBinding>,
|
||||
pub(crate) approve_with_strict_auto_review: Vec<KeyBinding>,
|
||||
pub(crate) decline: Vec<KeyBinding>,
|
||||
pub(crate) cancel: Vec<KeyBinding>,
|
||||
}
|
||||
@@ -489,9 +491,16 @@ impl RuntimeKeymap {
|
||||
|
||||
let approval = ApprovalKeymap {
|
||||
open_fullscreen: resolve_local!(keymap, defaults, approval, open_fullscreen),
|
||||
open_thread: resolve_local!(keymap, defaults, approval, open_thread),
|
||||
approve: resolve_local!(keymap, defaults, approval, approve),
|
||||
approve_for_session: resolve_local!(keymap, defaults, approval, approve_for_session),
|
||||
approve_for_prefix: resolve_local!(keymap, defaults, approval, approve_for_prefix),
|
||||
approve_with_strict_auto_review: resolve_local!(
|
||||
keymap,
|
||||
defaults,
|
||||
approval,
|
||||
approve_with_strict_auto_review
|
||||
),
|
||||
decline: resolve_local!(keymap, defaults, approval, decline),
|
||||
cancel: resolve_local!(keymap, defaults, approval, cancel),
|
||||
};
|
||||
@@ -527,7 +536,8 @@ impl RuntimeKeymap {
|
||||
|
||||
fn defaults_for_preset(preset: TuiKeymapPreset) -> Self {
|
||||
match preset {
|
||||
TuiKeymapPreset::Latest | TuiKeymapPreset::V4 => Self::defaults_v4(),
|
||||
TuiKeymapPreset::Latest | TuiKeymapPreset::V5 => Self::defaults_v5(),
|
||||
TuiKeymapPreset::V4 => Self::defaults_v4(),
|
||||
TuiKeymapPreset::V3 => Self::defaults_v3(),
|
||||
TuiKeymapPreset::V2 => Self::defaults_v2(),
|
||||
TuiKeymapPreset::V1 => Self::defaults_v1(),
|
||||
@@ -703,9 +713,11 @@ impl RuntimeKeymap {
|
||||
KeyModifiers::CONTROL | KeyModifiers::SHIFT,
|
||||
))
|
||||
],
|
||||
open_thread: default_bindings![],
|
||||
approve: default_bindings![plain(KeyCode::Char('y'))],
|
||||
approve_for_session: default_bindings![plain(KeyCode::Char('a'))],
|
||||
approve_for_prefix: default_bindings![plain(KeyCode::Char('p'))],
|
||||
approve_with_strict_auto_review: default_bindings![],
|
||||
decline: default_bindings![plain(KeyCode::Esc), plain(KeyCode::Char('n'))],
|
||||
cancel: default_bindings![plain(KeyCode::Char('c'))],
|
||||
},
|
||||
@@ -771,6 +783,18 @@ impl RuntimeKeymap {
|
||||
defaults
|
||||
}
|
||||
|
||||
/// Current keymap defaults for preset `v5`.
|
||||
///
|
||||
/// This keeps earlier presets intact while moving approval-only shortcuts
|
||||
/// into configurable keymap defaults.
|
||||
fn defaults_v5() -> Self {
|
||||
let mut defaults = Self::defaults_v4();
|
||||
defaults.approval.open_thread = default_bindings![plain(KeyCode::Char('o'))];
|
||||
defaults.approval.approve_with_strict_auto_review =
|
||||
default_bindings![plain(KeyCode::Char('r'))];
|
||||
defaults
|
||||
}
|
||||
|
||||
/// Reject ambiguous bindings in scopes that are evaluated together.
|
||||
///
|
||||
/// We validate in multiple passes because runtime handling has mixed
|
||||
@@ -990,6 +1014,7 @@ impl RuntimeKeymap {
|
||||
"approval",
|
||||
[
|
||||
("open_fullscreen", self.approval.open_fullscreen.as_slice()),
|
||||
("open_thread", self.approval.open_thread.as_slice()),
|
||||
("approve", self.approval.approve.as_slice()),
|
||||
(
|
||||
"approve_for_session",
|
||||
@@ -999,6 +1024,10 @@ impl RuntimeKeymap {
|
||||
"approve_for_prefix",
|
||||
self.approval.approve_for_prefix.as_slice(),
|
||||
),
|
||||
(
|
||||
"approve_with_strict_auto_review",
|
||||
self.approval.approve_with_strict_auto_review.as_slice(),
|
||||
),
|
||||
("decline", self.approval.decline.as_slice()),
|
||||
("cancel", self.approval.cancel.as_slice()),
|
||||
],
|
||||
|
||||
@@ -34,6 +34,7 @@ use crate::bottom_pane::ColumnWidthMode;
|
||||
use crate::bottom_pane::SelectionItem;
|
||||
use crate::bottom_pane::SelectionViewParams;
|
||||
use crate::bottom_pane::popup_consts::standard_popup_hint_line;
|
||||
use crate::bottom_pane::popup_consts::standard_popup_hint_line_for_keymap;
|
||||
use crate::keymap::RuntimeKeymap;
|
||||
use crate::render::renderable::ColumnRenderable;
|
||||
use crate::render::renderable::Renderable;
|
||||
@@ -66,13 +67,8 @@ fn key_binding_span(binding: &str) -> ratatui::text::Span<'static> {
|
||||
}
|
||||
}
|
||||
|
||||
fn keymap_action_menu_hint_line() -> Line<'static> {
|
||||
Line::from(vec![
|
||||
"enter".cyan(),
|
||||
" select · ".dim(),
|
||||
"esc".cyan(),
|
||||
" back".dim(),
|
||||
])
|
||||
fn keymap_action_menu_hint_line(runtime_keymap: &RuntimeKeymap) -> Line<'static> {
|
||||
standard_popup_hint_line_for_keymap(&runtime_keymap.list)
|
||||
}
|
||||
|
||||
fn open_capture_action(
|
||||
@@ -264,7 +260,7 @@ pub(crate) fn build_keymap_action_menu_params(
|
||||
"`tui.keymap.*`".cyan(),
|
||||
" override.".dim(),
|
||||
])),
|
||||
footer_hint: Some(keymap_action_menu_hint_line()),
|
||||
footer_hint: Some(keymap_action_menu_hint_line(runtime_keymap)),
|
||||
items,
|
||||
col_width_mode: ColumnWidthMode::Fixed,
|
||||
..Default::default()
|
||||
@@ -315,7 +311,7 @@ pub(crate) fn build_keymap_replace_binding_menu_params(
|
||||
SelectionViewParams {
|
||||
view_id: Some(KEYMAP_REPLACE_BINDING_MENU_VIEW_ID),
|
||||
header: Box::new(header),
|
||||
footer_hint: Some(keymap_action_menu_hint_line()),
|
||||
footer_hint: Some(keymap_action_menu_hint_line(runtime_keymap)),
|
||||
items,
|
||||
col_width_mode: ColumnWidthMode::Fixed,
|
||||
..Default::default()
|
||||
|
||||
@@ -111,9 +111,11 @@ pub(super) const KEYMAP_ACTIONS: &[KeymapActionDescriptor] = &[
|
||||
action("list", "List", "accept", "Accept the current list selection."),
|
||||
action("list", "List", "cancel", "Cancel and close selection views."),
|
||||
action("approval", "Approval", "open_fullscreen", "Open approval details fullscreen."),
|
||||
action("approval", "Approval", "open_thread", "Open the originating thread from an approval prompt."),
|
||||
action("approval", "Approval", "approve", "Approve the primary option."),
|
||||
action("approval", "Approval", "approve_for_session", "Approve for the session when available."),
|
||||
action("approval", "Approval", "approve_for_prefix", "Approve with an exec-policy prefix when available."),
|
||||
action("approval", "Approval", "approve_with_strict_auto_review", "Grant permissions for this turn with strict auto review."),
|
||||
action("approval", "Approval", "decline", "Decline and provide corrective guidance."),
|
||||
action("approval", "Approval", "cancel", "Cancel an elicitation request."),
|
||||
action("onboarding", "Onboarding", "move_up", "Move onboarding selection up."),
|
||||
@@ -227,9 +229,13 @@ pub(super) fn binding_slot<'a>(
|
||||
("list", "accept") => Some(&mut keymap.list.accept),
|
||||
("list", "cancel") => Some(&mut keymap.list.cancel),
|
||||
("approval", "open_fullscreen") => Some(&mut keymap.approval.open_fullscreen),
|
||||
("approval", "open_thread") => Some(&mut keymap.approval.open_thread),
|
||||
("approval", "approve") => Some(&mut keymap.approval.approve),
|
||||
("approval", "approve_for_session") => Some(&mut keymap.approval.approve_for_session),
|
||||
("approval", "approve_for_prefix") => Some(&mut keymap.approval.approve_for_prefix),
|
||||
("approval", "approve_with_strict_auto_review") => {
|
||||
Some(&mut keymap.approval.approve_with_strict_auto_review)
|
||||
}
|
||||
("approval", "decline") => Some(&mut keymap.approval.decline),
|
||||
("approval", "cancel") => Some(&mut keymap.approval.cancel),
|
||||
("onboarding", "move_up") => Some(&mut keymap.onboarding.move_up),
|
||||
@@ -331,9 +337,13 @@ pub(super) fn bindings_for_action<'a>(
|
||||
("list", "accept") => Some(runtime_keymap.list.accept.as_slice()),
|
||||
("list", "cancel") => Some(runtime_keymap.list.cancel.as_slice()),
|
||||
("approval", "open_fullscreen") => Some(runtime_keymap.approval.open_fullscreen.as_slice()),
|
||||
("approval", "open_thread") => Some(runtime_keymap.approval.open_thread.as_slice()),
|
||||
("approval", "approve") => Some(runtime_keymap.approval.approve.as_slice()),
|
||||
("approval", "approve_for_session") => Some(runtime_keymap.approval.approve_for_session.as_slice()),
|
||||
("approval", "approve_for_prefix") => Some(runtime_keymap.approval.approve_for_prefix.as_slice()),
|
||||
("approval", "approve_with_strict_auto_review") => {
|
||||
Some(runtime_keymap.approval.approve_with_strict_auto_review.as_slice())
|
||||
}
|
||||
("approval", "decline") => Some(runtime_keymap.approval.decline.as_slice()),
|
||||
("approval", "cancel") => Some(runtime_keymap.approval.cancel.as_slice()),
|
||||
("onboarding", "move_up") => Some(runtime_keymap.onboarding.move_up.as_slice()),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Shortcut picker construction for `/keymap`.
|
||||
|
||||
use codex_config::types::TuiKeymap;
|
||||
use crossterm::event::KeyCode;
|
||||
use ratatui::style::Stylize;
|
||||
use ratatui::text::Line;
|
||||
use ratatui::text::Span;
|
||||
@@ -12,7 +13,9 @@ use crate::bottom_pane::SelectionItem;
|
||||
use crate::bottom_pane::SelectionRowDisplay;
|
||||
use crate::bottom_pane::SelectionTab;
|
||||
use crate::bottom_pane::SelectionViewParams;
|
||||
use crate::key_hint;
|
||||
use crate::keymap::RuntimeKeymap;
|
||||
use crate::keymap::primary_binding;
|
||||
use crate::render::renderable::ColumnRenderable;
|
||||
use crate::render::renderable::Renderable;
|
||||
|
||||
@@ -243,7 +246,7 @@ fn build_keymap_picker_params_for_action(
|
||||
SelectionViewParams {
|
||||
view_id: Some(KEYMAP_PICKER_VIEW_ID),
|
||||
header: Box::new(()),
|
||||
footer_hint: Some(keymap_picker_hint_line()),
|
||||
footer_hint: Some(keymap_picker_hint_line(runtime_keymap)),
|
||||
tabs,
|
||||
initial_tab_id: Some(KEYMAP_ALL_TAB_ID.to_string()),
|
||||
is_searchable: true,
|
||||
@@ -380,17 +383,21 @@ fn action_count_line(count: usize) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn keymap_picker_hint_line() -> Line<'static> {
|
||||
fn keymap_picker_hint_line(runtime_keymap: &RuntimeKeymap) -> Line<'static> {
|
||||
let accept = primary_binding(&runtime_keymap.list.accept)
|
||||
.unwrap_or_else(|| key_hint::plain(KeyCode::Enter));
|
||||
let cancel = primary_binding(&runtime_keymap.list.cancel)
|
||||
.unwrap_or_else(|| key_hint::plain(KeyCode::Esc));
|
||||
Line::from(vec![
|
||||
"left/right".cyan(),
|
||||
" group · ".dim(),
|
||||
"enter".cyan(),
|
||||
accept.into(),
|
||||
" edit shortcut · ".dim(),
|
||||
"*".cyan(),
|
||||
" custom · ".dim(),
|
||||
"-".cyan(),
|
||||
" unbound · ".dim(),
|
||||
"esc".cyan(),
|
||||
cancel.into(),
|
||||
" close".dim(),
|
||||
])
|
||||
}
|
||||
|
||||
+9
-9
@@ -5,19 +5,19 @@ expression: "render_picker(params, 120)"
|
||||
|
||||
Keymap
|
||||
All configurable shortcuts.
|
||||
93 actions, 1 customized, 1 unbound.
|
||||
95 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-.
|
||||
› 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
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
source: tui/src/keymap_setup.rs
|
||||
expression: snapshot
|
||||
---
|
||||
tab: All (93 selectable)
|
||||
tab: All (95 selectable)
|
||||
tab: Common (21 selectable)
|
||||
tab: Customized (0) (0 selectable)
|
||||
tab: Unbound (1) (1 selectable)
|
||||
@@ -11,7 +11,7 @@ tab: Composer (3 selectable)
|
||||
tab: Editor (16 selectable)
|
||||
tab: Vim (34 selectable)
|
||||
tab: Navigation (17 selectable)
|
||||
tab: Approval (6 selectable)
|
||||
tab: Approval (8 selectable)
|
||||
Open Transcript | ctrl-t | Global open_transcript Open Transcript Open the transcript overlay. ctrl-t Default
|
||||
Open External Editor | ctrl-g | Global open_external_editor Open External Editor Open the current draft in an external editor. ctrl-g Default
|
||||
Copy | ctrl-o | Global copy Copy Copy the last agent response to the clipboard. ctrl-o Default
|
||||
|
||||
+9
-9
@@ -5,20 +5,20 @@ expression: "render_picker(params, 78)"
|
||||
|
||||
Keymap
|
||||
All configurable shortcuts.
|
||||
93 actions, 0 customized, 1 unbound.
|
||||
95 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-.
|
||||
› 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,19 +5,19 @@ expression: "render_picker(params, 120)"
|
||||
|
||||
Keymap
|
||||
All configurable shortcuts.
|
||||
93 actions, 0 customized, 1 unbound.
|
||||
95 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-.
|
||||
› 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
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::app_event::AppEvent;
|
||||
use crate::bottom_pane::SelectionItem;
|
||||
use crate::bottom_pane::SelectionViewParams;
|
||||
use crate::bottom_pane::SideContentWidth;
|
||||
use crate::bottom_pane::popup_consts::standard_popup_hint_line;
|
||||
use crate::bottom_pane::popup_consts::standard_popup_hint_line_for_keymap;
|
||||
use crate::bottom_pane::popup_content_width;
|
||||
use crate::bottom_pane::side_by_side_layout_widths;
|
||||
use crate::diff_render::DiffLineType;
|
||||
@@ -33,6 +33,7 @@ use crate::diff_render::current_diff_render_style_context;
|
||||
use crate::diff_render::line_number_width;
|
||||
use crate::diff_render::push_wrapped_diff_line_with_style_context;
|
||||
use crate::diff_render::push_wrapped_diff_line_with_syntax_and_style_context;
|
||||
use crate::keymap::ListKeymap;
|
||||
use crate::render::highlight;
|
||||
use crate::render::renderable::Renderable;
|
||||
use crate::status::format_directory_display;
|
||||
@@ -315,6 +316,7 @@ pub(crate) fn build_theme_picker_params(
|
||||
current_name: Option<&str>,
|
||||
codex_home: Option<&Path>,
|
||||
terminal_width: Option<u16>,
|
||||
list_keymap: &ListKeymap,
|
||||
) -> SelectionViewParams {
|
||||
// Snapshot the current theme so we can restore on cancel.
|
||||
let original_theme = highlight::current_syntax_theme();
|
||||
@@ -390,7 +392,7 @@ pub(crate) fn build_theme_picker_params(
|
||||
codex_home_owned.as_deref(),
|
||||
terminal_width,
|
||||
)),
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
footer_hint: Some(standard_popup_hint_line_for_keymap(list_keymap)),
|
||||
items,
|
||||
is_searchable: true,
|
||||
search_placeholder: Some("Type to filter themes...".to_string()),
|
||||
@@ -477,7 +479,10 @@ mod tests {
|
||||
#[test]
|
||||
fn theme_picker_uses_half_width_with_stacked_fallback_preview() {
|
||||
let params = build_theme_picker_params(
|
||||
/*current_name*/ None, /*codex_home*/ None, /*terminal_width*/ None,
|
||||
/*current_name*/ None,
|
||||
/*codex_home*/ None,
|
||||
/*terminal_width*/ None,
|
||||
&crate::keymap::RuntimeKeymap::defaults().list,
|
||||
);
|
||||
assert_eq!(params.side_content_width, SideContentWidth::Half);
|
||||
assert_eq!(params.side_content_min_width, WIDE_PREVIEW_MIN_WIDTH);
|
||||
@@ -487,7 +492,10 @@ mod tests {
|
||||
#[test]
|
||||
fn theme_picker_items_include_search_values_for_preview_mapping() {
|
||||
let params = build_theme_picker_params(
|
||||
/*current_name*/ None, /*codex_home*/ None, /*terminal_width*/ None,
|
||||
/*current_name*/ None,
|
||||
/*codex_home*/ None,
|
||||
/*terminal_width*/ None,
|
||||
&crate::keymap::RuntimeKeymap::defaults().list,
|
||||
);
|
||||
assert!(
|
||||
params.items.iter().all(|item| item.search_value.is_some()),
|
||||
@@ -640,6 +648,7 @@ mod tests {
|
||||
Some("not-a-real-theme"),
|
||||
/*codex_home*/ None,
|
||||
Some(120),
|
||||
&crate::keymap::RuntimeKeymap::defaults().list,
|
||||
);
|
||||
let selected_idx = params
|
||||
.initial_selected_idx
|
||||
|
||||
+6
-5
@@ -36,13 +36,14 @@ Precedence is applied in this order (highest first):
|
||||
|
||||
### Presets
|
||||
|
||||
- `latest`: moving alias for the newest preset; today `latest -> v4`
|
||||
- `v4`: current defaults, exposing reasoning shortcuts (`alt-,` / `alt-.`) in the keymap
|
||||
- `latest`: moving alias for the newest preset; today `latest -> v5`
|
||||
- `v5`: current defaults, exposing approval thread-open and strict auto-review shortcuts in the keymap
|
||||
- `v4`: exposes 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
|
||||
|
||||
When defaults change in the future, a new version (for example `v4`) is added and
|
||||
When defaults change in the future, a new version (for example `v6`) is added and
|
||||
`latest` may move to it. Pin to `v1` if you want stable historical behavior.
|
||||
|
||||
TODO(docs): mirror this preset migration note on developers.openai.com.
|
||||
@@ -72,8 +73,8 @@ TODO(docs): mirror this preset migration note on developers.openai.com.
|
||||
`half_page_down`, `jump_top`, `jump_bottom`, `close`, `close_transcript`,
|
||||
`edit_previous_message`, `edit_next_message`, `confirm_edit_message`
|
||||
- `list`: `move_up`, `move_down`, `accept`, `cancel`
|
||||
- `approval`: `open_fullscreen`, `approve`, `approve_for_session`,
|
||||
`approve_for_prefix`, `decline`, `cancel`
|
||||
- `approval`: `open_fullscreen`, `open_thread`, `approve`, `approve_for_session`,
|
||||
`approve_for_prefix`, `approve_with_strict_auto_review`, `decline`, `cancel`
|
||||
- `onboarding`: `move_up`, `move_down`, `select_first`, `select_second`,
|
||||
`select_third`, `confirm`, `cancel`, `quit`, `toggle_animation`
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
[tui.keymap]
|
||||
# Preset alias for built-in defaults.
|
||||
# "latest" currently points to "v4".
|
||||
# "latest" currently points to "v5".
|
||||
preset = "latest"
|
||||
|
||||
[tui.keymap.global]
|
||||
@@ -149,9 +149,11 @@ cancel = ["esc"]
|
||||
# Approval modal actions.
|
||||
# Include ctrl+shift fallback for terminals that preserve SHIFT on Ctrl letter chords.
|
||||
open_fullscreen = ["ctrl-a", "ctrl-shift-a"]
|
||||
open_thread = ["o"]
|
||||
approve = ["y"]
|
||||
approve_for_session = ["a"]
|
||||
approve_for_prefix = ["p"]
|
||||
approve_with_strict_auto_review = ["r"]
|
||||
decline = ["esc", "n"]
|
||||
cancel = ["c"]
|
||||
|
||||
|
||||
@@ -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 -> v4`.
|
||||
- Today, `latest -> v5`.
|
||||
- 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 (`v4` today)
|
||||
3. Preset default (`v5` today)
|
||||
|
||||
## Default `v1` Compatibility Notes
|
||||
|
||||
@@ -116,9 +116,11 @@ For runtime behavior, safety invariants, and testing guidance, see
|
||||
### `approval`
|
||||
|
||||
- `open_fullscreen`: open full-screen approval details
|
||||
- `open_thread`: open the originating thread from an approval prompt
|
||||
- `approve`: approve primary request
|
||||
- `approve_for_session`: approve-for-session option
|
||||
- `approve_for_prefix`: approve-for-prefix option
|
||||
- `approve_with_strict_auto_review`: grant permissions for this turn with strict auto review
|
||||
- `decline`: decline request
|
||||
- `cancel`: cancel elicitation request
|
||||
- MCP elicitation safety rule: `Esc` is always treated as `cancel` (never
|
||||
|
||||
+3
-3
@@ -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. `v4` is the current baseline; `v1`, `v2`, and `v3` are frozen for historical behavior.
|
||||
3. Today, `latest -> v4`.
|
||||
2. `v5` is the current baseline; `v1`, `v2`, `v3`, and `v4` are frozen for historical behavior.
|
||||
3. Today, `latest -> v5`.
|
||||
|
||||
User guidance:
|
||||
|
||||
@@ -96,7 +96,7 @@ User guidance:
|
||||
Developer policy:
|
||||
|
||||
1. Do not mutate old preset defaults after release.
|
||||
2. Add a new version (for example `v4`) for behavior changes.
|
||||
2. Add a new version (for example `v6`) for behavior changes.
|
||||
3. Update docs and migration notes whenever `latest` changes.
|
||||
|
||||
Migration notes:
|
||||
|
||||
Reference in New Issue
Block a user