mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
570 lines
36 KiB
Rust
570 lines
36 KiB
Rust
//! Catalog and accessors for keymap actions shown by `/keymap`.
|
|
//!
|
|
//! The descriptor table is the single UI-facing inventory of configurable
|
|
//! actions. Each descriptor ties together the config path segment, user-facing
|
|
//! context label, stable action name, and short description used by the picker
|
|
//! and action menu.
|
|
//!
|
|
//! The accessors below deliberately mirror the descriptor table for both the
|
|
//! editable root config and the resolved runtime keymap. Keeping those matches
|
|
//! in one module makes it easier to audit a new action: if it appears in the
|
|
//! catalog, it must also be readable from runtime state and writable in
|
|
//! `TuiKeymap`.
|
|
|
|
use std::collections::BTreeSet;
|
|
|
|
use codex_config::types::KeybindingsSpec;
|
|
use codex_config::types::TuiKeymap;
|
|
use crossterm::event::KeyEvent;
|
|
|
|
use crate::key_hint::KeyBinding;
|
|
use crate::keymap::RuntimeKeymap;
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub(super) struct KeymapActionDescriptor {
|
|
/// Config context segment, such as `composer` in `tui.keymap.composer.submit`.
|
|
pub(super) context: &'static str,
|
|
/// Human-readable group label shown in the picker.
|
|
pub(super) context_label: &'static str,
|
|
/// Config action segment, such as `submit` in `tui.keymap.composer.submit`.
|
|
pub(super) action: &'static str,
|
|
/// Short user-facing explanation of what the action does.
|
|
pub(super) description: &'static str,
|
|
/// Feature required before the action appears in `/keymap`.
|
|
required_feature: Option<KeymapActionFeature>,
|
|
}
|
|
|
|
const fn action(
|
|
context: &'static str,
|
|
context_label: &'static str,
|
|
action: &'static str,
|
|
description: &'static str,
|
|
) -> KeymapActionDescriptor {
|
|
KeymapActionDescriptor {
|
|
context,
|
|
context_label,
|
|
action,
|
|
description,
|
|
required_feature: None,
|
|
}
|
|
}
|
|
|
|
const fn gated_action(
|
|
context: &'static str,
|
|
context_label: &'static str,
|
|
action: &'static str,
|
|
description: &'static str,
|
|
required_feature: KeymapActionFeature,
|
|
) -> KeymapActionDescriptor {
|
|
KeymapActionDescriptor {
|
|
context,
|
|
context_label,
|
|
action,
|
|
description,
|
|
required_feature: Some(required_feature),
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
enum KeymapActionFeature {
|
|
FastMode,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Default)]
|
|
pub(crate) struct KeymapActionFilter {
|
|
pub(crate) fast_mode_enabled: bool,
|
|
}
|
|
|
|
impl KeymapActionDescriptor {
|
|
pub(super) fn is_visible(self, filter: KeymapActionFilter) -> bool {
|
|
match self.required_feature {
|
|
None => true,
|
|
Some(KeymapActionFeature::FastMode) => filter.fast_mode_enabled,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[rustfmt::skip]
|
|
pub(super) const KEYMAP_ACTIONS: &[KeymapActionDescriptor] = &[
|
|
action("global", "Global", "open_transcript", "Open the transcript overlay."),
|
|
action("global", "Global", "open_external_editor", "Open the current draft in an external editor."),
|
|
action("global", "Global", "copy", "Copy the last agent response to the clipboard."),
|
|
action("global", "Global", "clear_terminal", "Clear the terminal UI."),
|
|
action("global", "Global", "toggle_vim_mode", "Turn Vim composer mode on or off."),
|
|
gated_action("global", "Global", "toggle_fast_mode", "Turn Fast mode on or off.", KeymapActionFeature::FastMode),
|
|
action("global", "Global", "toggle_raw_output", "Toggle raw scrollback mode."),
|
|
action("chat", "Chat", "decrease_reasoning_effort", "Decrease reasoning effort."),
|
|
action("chat", "Chat", "increase_reasoning_effort", "Increase reasoning effort."),
|
|
action("chat", "Chat", "edit_queued_message", "Edit the most recently queued message."),
|
|
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."),
|
|
action("composer", "Composer", "history_search_previous", "Open history search or move to the previous match."),
|
|
action("composer", "Composer", "history_search_next", "Move to the next history search match."),
|
|
action("editor", "Editor", "insert_newline", "Insert a newline in the editor."),
|
|
action("editor", "Editor", "move_left", "Move the cursor left."),
|
|
action("editor", "Editor", "move_right", "Move the cursor right."),
|
|
action("editor", "Editor", "move_up", "Move the cursor up."),
|
|
action("editor", "Editor", "move_down", "Move the cursor down."),
|
|
action("editor", "Editor", "move_word_left", "Move to the beginning of the previous word."),
|
|
action("editor", "Editor", "move_word_right", "Move to the end of the next word."),
|
|
action("editor", "Editor", "move_line_start", "Move to the beginning of the line."),
|
|
action("editor", "Editor", "move_line_end", "Move to the end of the line."),
|
|
action("editor", "Editor", "delete_backward", "Delete one grapheme to the left."),
|
|
action("editor", "Editor", "delete_forward", "Delete one grapheme to the right."),
|
|
action("editor", "Editor", "delete_backward_word", "Delete the previous word."),
|
|
action("editor", "Editor", "delete_forward_word", "Delete the next word."),
|
|
action("editor", "Editor", "kill_line_start", "Delete from cursor to line start."),
|
|
action("editor", "Editor", "kill_whole_line", "Delete the current line."),
|
|
action("editor", "Editor", "kill_line_end", "Delete from cursor to line end."),
|
|
action("editor", "Editor", "yank", "Paste the kill buffer."),
|
|
action("vim_normal", "Vim normal", "enter_insert", "Enter insert mode at the cursor."),
|
|
action("vim_normal", "Vim normal", "append_after_cursor", "Enter insert mode after the cursor."),
|
|
action("vim_normal", "Vim normal", "append_line_end", "Enter insert mode at end of line."),
|
|
action("vim_normal", "Vim normal", "insert_line_start", "Enter insert mode at the first non-blank character."),
|
|
action("vim_normal", "Vim normal", "open_line_below", "Open a new line below and enter insert mode."),
|
|
action("vim_normal", "Vim normal", "open_line_above", "Open a new line above and enter insert mode."),
|
|
action("vim_normal", "Vim normal", "move_left", "Move left in Vim normal mode."),
|
|
action("vim_normal", "Vim normal", "move_right", "Move right in Vim normal mode."),
|
|
action("vim_normal", "Vim normal", "move_up", "Move up or recall older history in Vim normal mode."),
|
|
action("vim_normal", "Vim normal", "move_down", "Move down or recall newer history in Vim normal mode."),
|
|
action("vim_normal", "Vim normal", "move_word_forward", "Move to the start of the next word."),
|
|
action("vim_normal", "Vim normal", "move_word_backward", "Move to the start of the previous word."),
|
|
action("vim_normal", "Vim normal", "move_word_end", "Move to the end of the current or next word."),
|
|
action("vim_normal", "Vim normal", "move_line_start", "Move to the start of the line."),
|
|
action("vim_normal", "Vim normal", "move_line_end", "Move to the end of the line."),
|
|
action("vim_normal", "Vim normal", "delete_char", "Delete the character under the cursor."),
|
|
action("vim_normal", "Vim normal", "delete_to_line_end", "Delete from cursor to end of line."),
|
|
action("vim_normal", "Vim normal", "change_to_line_end", "Change from cursor to end of line and enter insert mode."),
|
|
action("vim_normal", "Vim normal", "yank_line", "Yank the entire line."),
|
|
action("vim_normal", "Vim normal", "paste_after", "Paste after the cursor."),
|
|
action("vim_normal", "Vim normal", "start_delete_operator", "Begin a delete operator and wait for a motion."),
|
|
action("vim_normal", "Vim normal", "start_yank_operator", "Begin a yank operator and wait for a motion."),
|
|
action("vim_normal", "Vim normal", "start_change_operator", "Begin a change operator and wait for a text object."),
|
|
action("vim_normal", "Vim normal", "cancel_operator", "Cancel a pending Vim operator."),
|
|
action("vim_operator", "Vim operator", "delete_line", "Repeat delete operator to delete the whole line."),
|
|
action("vim_operator", "Vim operator", "yank_line", "Repeat yank operator to yank the whole line."),
|
|
action("vim_operator", "Vim operator", "motion_left", "Operator motion left."),
|
|
action("vim_operator", "Vim operator", "motion_right", "Operator motion right."),
|
|
action("vim_operator", "Vim operator", "motion_up", "Operator motion up."),
|
|
action("vim_operator", "Vim operator", "motion_down", "Operator motion down."),
|
|
action("vim_operator", "Vim operator", "motion_word_forward", "Operator motion to start of next word."),
|
|
action("vim_operator", "Vim operator", "motion_word_backward", "Operator motion to start of previous word."),
|
|
action("vim_operator", "Vim operator", "motion_word_end", "Operator motion to end of word."),
|
|
action("vim_operator", "Vim operator", "motion_line_start", "Operator motion to line start."),
|
|
action("vim_operator", "Vim operator", "motion_line_end", "Operator motion to line end."),
|
|
action("vim_operator", "Vim operator", "select_inner_text_object", "Select an inner text object."),
|
|
action("vim_operator", "Vim operator", "select_around_text_object", "Select an around text object."),
|
|
action("vim_operator", "Vim operator", "cancel", "Cancel the pending operator."),
|
|
action("vim_text_object", "Vim text object", "word", "Target the current word."),
|
|
action("vim_text_object", "Vim text object", "big_word", "Target the current WORD."),
|
|
action("vim_text_object", "Vim text object", "parentheses", "Target enclosing parentheses."),
|
|
action("vim_text_object", "Vim text object", "brackets", "Target enclosing brackets."),
|
|
action("vim_text_object", "Vim text object", "braces", "Target enclosing braces."),
|
|
action("vim_text_object", "Vim text object", "double_quote", "Target enclosing double quotes."),
|
|
action("vim_text_object", "Vim text object", "single_quote", "Target enclosing single quotes."),
|
|
action("vim_text_object", "Vim text object", "backtick", "Target enclosing backticks."),
|
|
action("vim_text_object", "Vim text object", "cancel", "Cancel the pending text object."),
|
|
action("pager", "Pager", "scroll_up", "Scroll up by one row."),
|
|
action("pager", "Pager", "scroll_down", "Scroll down by one row."),
|
|
action("pager", "Pager", "page_up", "Scroll up by one page."),
|
|
action("pager", "Pager", "page_down", "Scroll down by one page."),
|
|
action("pager", "Pager", "half_page_up", "Scroll up by half a page."),
|
|
action("pager", "Pager", "half_page_down", "Scroll down by half a page."),
|
|
action("pager", "Pager", "jump_top", "Jump to the beginning."),
|
|
action("pager", "Pager", "jump_bottom", "Jump to the end."),
|
|
action("pager", "Pager", "close", "Close the pager overlay."),
|
|
action("pager", "Pager", "close_transcript", "Close the transcript overlay."),
|
|
action("list", "List", "move_up", "Move list selection up."),
|
|
action("list", "List", "move_down", "Move list selection down."),
|
|
action("list", "List", "move_left", "Move horizontally left in list pickers."),
|
|
action("list", "List", "move_right", "Move horizontally right in list pickers."),
|
|
action("list", "List", "page_up", "Move list selection up by one page."),
|
|
action("list", "List", "page_down", "Move list selection down by one page."),
|
|
action("list", "List", "jump_top", "Jump to the first list item."),
|
|
action("list", "List", "jump_bottom", "Jump to the last list item."),
|
|
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 approval source thread when available."),
|
|
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", "deny", "Choose the explicit deny option when available."),
|
|
action("approval", "Approval", "decline", "Decline and provide corrective guidance."),
|
|
action("approval", "Approval", "cancel", "Cancel an elicitation request."),
|
|
];
|
|
|
|
/// Convert a stable action identifier into a display label.
|
|
///
|
|
/// This is intentionally presentation-only: the returned string must never be
|
|
/// parsed back into an action name, because underscores and casing are part of
|
|
/// the stable config contract.
|
|
pub(super) fn action_label(action: &str) -> String {
|
|
action
|
|
.split('_')
|
|
.map(|word| {
|
|
let mut chars = word.chars();
|
|
let Some(first) = chars.next() else {
|
|
return String::new();
|
|
};
|
|
format!("{}{}", first.to_ascii_uppercase(), chars.as_str())
|
|
})
|
|
.collect::<Vec<_>>()
|
|
.join(" ")
|
|
}
|
|
|
|
#[rustfmt::skip]
|
|
/// Return the mutable root-config binding slot for one catalog action.
|
|
///
|
|
/// The returned `Option<KeybindingsSpec>` distinguishes three states that the
|
|
/// editor must preserve: absent means use fallback/default resolution, `Some`
|
|
/// with one or more keys is a custom binding, and `Some(Many([]))` is an
|
|
/// explicit unbind.
|
|
pub(super) fn binding_slot<'a>(
|
|
keymap: &'a mut TuiKeymap,
|
|
context: &str,
|
|
action: &str,
|
|
) -> Option<&'a mut Option<KeybindingsSpec>> {
|
|
match (context, action) {
|
|
("global", "open_transcript") => Some(&mut keymap.global.open_transcript),
|
|
("global", "open_external_editor") => Some(&mut keymap.global.open_external_editor),
|
|
("global", "copy") => Some(&mut keymap.global.copy),
|
|
("global", "clear_terminal") => Some(&mut keymap.global.clear_terminal),
|
|
("global", "toggle_vim_mode") => Some(&mut keymap.global.toggle_vim_mode),
|
|
("global", "toggle_fast_mode") => Some(&mut keymap.global.toggle_fast_mode),
|
|
("global", "toggle_raw_output") => Some(&mut keymap.global.toggle_raw_output),
|
|
("chat", "decrease_reasoning_effort") => Some(&mut keymap.chat.decrease_reasoning_effort),
|
|
("chat", "increase_reasoning_effort") => Some(&mut keymap.chat.increase_reasoning_effort),
|
|
("chat", "edit_queued_message") => Some(&mut keymap.chat.edit_queued_message),
|
|
("composer", "submit") => Some(&mut keymap.composer.submit),
|
|
("composer", "queue") => Some(&mut keymap.composer.queue),
|
|
("composer", "toggle_shortcuts") => Some(&mut keymap.composer.toggle_shortcuts),
|
|
("composer", "history_search_previous") => Some(&mut keymap.composer.history_search_previous),
|
|
("composer", "history_search_next") => Some(&mut keymap.composer.history_search_next),
|
|
("editor", "insert_newline") => Some(&mut keymap.editor.insert_newline),
|
|
("editor", "move_left") => Some(&mut keymap.editor.move_left),
|
|
("editor", "move_right") => Some(&mut keymap.editor.move_right),
|
|
("editor", "move_up") => Some(&mut keymap.editor.move_up),
|
|
("editor", "move_down") => Some(&mut keymap.editor.move_down),
|
|
("editor", "move_word_left") => Some(&mut keymap.editor.move_word_left),
|
|
("editor", "move_word_right") => Some(&mut keymap.editor.move_word_right),
|
|
("editor", "move_line_start") => Some(&mut keymap.editor.move_line_start),
|
|
("editor", "move_line_end") => Some(&mut keymap.editor.move_line_end),
|
|
("editor", "delete_backward") => Some(&mut keymap.editor.delete_backward),
|
|
("editor", "delete_forward") => Some(&mut keymap.editor.delete_forward),
|
|
("editor", "delete_backward_word") => Some(&mut keymap.editor.delete_backward_word),
|
|
("editor", "delete_forward_word") => Some(&mut keymap.editor.delete_forward_word),
|
|
("editor", "kill_line_start") => Some(&mut keymap.editor.kill_line_start),
|
|
("editor", "kill_whole_line") => Some(&mut keymap.editor.kill_whole_line),
|
|
("editor", "kill_line_end") => Some(&mut keymap.editor.kill_line_end),
|
|
("editor", "yank") => Some(&mut keymap.editor.yank),
|
|
("vim_normal", "enter_insert") => Some(&mut keymap.vim_normal.enter_insert),
|
|
("vim_normal", "append_after_cursor") => Some(&mut keymap.vim_normal.append_after_cursor),
|
|
("vim_normal", "append_line_end") => Some(&mut keymap.vim_normal.append_line_end),
|
|
("vim_normal", "insert_line_start") => Some(&mut keymap.vim_normal.insert_line_start),
|
|
("vim_normal", "open_line_below") => Some(&mut keymap.vim_normal.open_line_below),
|
|
("vim_normal", "open_line_above") => Some(&mut keymap.vim_normal.open_line_above),
|
|
("vim_normal", "move_left") => Some(&mut keymap.vim_normal.move_left),
|
|
("vim_normal", "move_right") => Some(&mut keymap.vim_normal.move_right),
|
|
("vim_normal", "move_up") => Some(&mut keymap.vim_normal.move_up),
|
|
("vim_normal", "move_down") => Some(&mut keymap.vim_normal.move_down),
|
|
("vim_normal", "move_word_forward") => Some(&mut keymap.vim_normal.move_word_forward),
|
|
("vim_normal", "move_word_backward") => Some(&mut keymap.vim_normal.move_word_backward),
|
|
("vim_normal", "move_word_end") => Some(&mut keymap.vim_normal.move_word_end),
|
|
("vim_normal", "move_line_start") => Some(&mut keymap.vim_normal.move_line_start),
|
|
("vim_normal", "move_line_end") => Some(&mut keymap.vim_normal.move_line_end),
|
|
("vim_normal", "delete_char") => Some(&mut keymap.vim_normal.delete_char),
|
|
("vim_normal", "delete_to_line_end") => Some(&mut keymap.vim_normal.delete_to_line_end),
|
|
("vim_normal", "change_to_line_end") => Some(&mut keymap.vim_normal.change_to_line_end),
|
|
("vim_normal", "yank_line") => Some(&mut keymap.vim_normal.yank_line),
|
|
("vim_normal", "paste_after") => Some(&mut keymap.vim_normal.paste_after),
|
|
("vim_normal", "start_delete_operator") => Some(&mut keymap.vim_normal.start_delete_operator),
|
|
("vim_normal", "start_yank_operator") => Some(&mut keymap.vim_normal.start_yank_operator),
|
|
("vim_normal", "start_change_operator") => Some(&mut keymap.vim_normal.start_change_operator),
|
|
("vim_normal", "cancel_operator") => Some(&mut keymap.vim_normal.cancel_operator),
|
|
("vim_operator", "delete_line") => Some(&mut keymap.vim_operator.delete_line),
|
|
("vim_operator", "yank_line") => Some(&mut keymap.vim_operator.yank_line),
|
|
("vim_operator", "motion_left") => Some(&mut keymap.vim_operator.motion_left),
|
|
("vim_operator", "motion_right") => Some(&mut keymap.vim_operator.motion_right),
|
|
("vim_operator", "motion_up") => Some(&mut keymap.vim_operator.motion_up),
|
|
("vim_operator", "motion_down") => Some(&mut keymap.vim_operator.motion_down),
|
|
("vim_operator", "motion_word_forward") => Some(&mut keymap.vim_operator.motion_word_forward),
|
|
("vim_operator", "motion_word_backward") => Some(&mut keymap.vim_operator.motion_word_backward),
|
|
("vim_operator", "motion_word_end") => Some(&mut keymap.vim_operator.motion_word_end),
|
|
("vim_operator", "motion_line_start") => Some(&mut keymap.vim_operator.motion_line_start),
|
|
("vim_operator", "motion_line_end") => Some(&mut keymap.vim_operator.motion_line_end),
|
|
("vim_operator", "select_inner_text_object") => Some(&mut keymap.vim_operator.select_inner_text_object),
|
|
("vim_operator", "select_around_text_object") => Some(&mut keymap.vim_operator.select_around_text_object),
|
|
("vim_operator", "cancel") => Some(&mut keymap.vim_operator.cancel),
|
|
("vim_text_object", "word") => Some(&mut keymap.vim_text_object.word),
|
|
("vim_text_object", "big_word") => Some(&mut keymap.vim_text_object.big_word),
|
|
("vim_text_object", "parentheses") => Some(&mut keymap.vim_text_object.parentheses),
|
|
("vim_text_object", "brackets") => Some(&mut keymap.vim_text_object.brackets),
|
|
("vim_text_object", "braces") => Some(&mut keymap.vim_text_object.braces),
|
|
("vim_text_object", "double_quote") => Some(&mut keymap.vim_text_object.double_quote),
|
|
("vim_text_object", "single_quote") => Some(&mut keymap.vim_text_object.single_quote),
|
|
("vim_text_object", "backtick") => Some(&mut keymap.vim_text_object.backtick),
|
|
("vim_text_object", "cancel") => Some(&mut keymap.vim_text_object.cancel),
|
|
("pager", "scroll_up") => Some(&mut keymap.pager.scroll_up),
|
|
("pager", "scroll_down") => Some(&mut keymap.pager.scroll_down),
|
|
("pager", "page_up") => Some(&mut keymap.pager.page_up),
|
|
("pager", "page_down") => Some(&mut keymap.pager.page_down),
|
|
("pager", "half_page_up") => Some(&mut keymap.pager.half_page_up),
|
|
("pager", "half_page_down") => Some(&mut keymap.pager.half_page_down),
|
|
("pager", "jump_top") => Some(&mut keymap.pager.jump_top),
|
|
("pager", "jump_bottom") => Some(&mut keymap.pager.jump_bottom),
|
|
("pager", "close") => Some(&mut keymap.pager.close),
|
|
("pager", "close_transcript") => Some(&mut keymap.pager.close_transcript),
|
|
("list", "move_up") => Some(&mut keymap.list.move_up),
|
|
("list", "move_down") => Some(&mut keymap.list.move_down),
|
|
("list", "move_left") => Some(&mut keymap.list.move_left),
|
|
("list", "move_right") => Some(&mut keymap.list.move_right),
|
|
("list", "page_up") => Some(&mut keymap.list.page_up),
|
|
("list", "page_down") => Some(&mut keymap.list.page_down),
|
|
("list", "jump_top") => Some(&mut keymap.list.jump_top),
|
|
("list", "jump_bottom") => Some(&mut keymap.list.jump_bottom),
|
|
("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", "deny") => Some(&mut keymap.approval.deny),
|
|
("approval", "decline") => Some(&mut keymap.approval.decline),
|
|
("approval", "cancel") => Some(&mut keymap.approval.cancel),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
#[rustfmt::skip]
|
|
/// Return the resolved runtime bindings for one catalog action.
|
|
///
|
|
/// This reads from [`RuntimeKeymap`] rather than root config so UI labels show
|
|
/// the actual active binding after defaults, global fallback, explicit
|
|
/// unbinding, and duplicate-key validation have already been applied.
|
|
pub(super) fn bindings_for_action<'a>(
|
|
runtime_keymap: &'a RuntimeKeymap,
|
|
context: &str,
|
|
action: &str,
|
|
) -> Option<&'a [KeyBinding]> {
|
|
match (context, action) {
|
|
("global", "open_transcript") => Some(runtime_keymap.app.open_transcript.as_slice()),
|
|
("global", "open_external_editor") => Some(runtime_keymap.app.open_external_editor.as_slice()),
|
|
("global", "copy") => Some(runtime_keymap.app.copy.as_slice()),
|
|
("global", "clear_terminal") => Some(runtime_keymap.app.clear_terminal.as_slice()),
|
|
("global", "toggle_vim_mode") => Some(runtime_keymap.app.toggle_vim_mode.as_slice()),
|
|
("global", "toggle_fast_mode") => Some(runtime_keymap.app.toggle_fast_mode.as_slice()),
|
|
("global", "toggle_raw_output") => Some(runtime_keymap.app.toggle_raw_output.as_slice()),
|
|
("chat", "decrease_reasoning_effort") => Some(runtime_keymap.chat.decrease_reasoning_effort.as_slice()),
|
|
("chat", "increase_reasoning_effort") => Some(runtime_keymap.chat.increase_reasoning_effort.as_slice()),
|
|
("chat", "edit_queued_message") => Some(runtime_keymap.chat.edit_queued_message.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()),
|
|
("composer", "history_search_previous") => Some(runtime_keymap.composer.history_search_previous.as_slice()),
|
|
("composer", "history_search_next") => Some(runtime_keymap.composer.history_search_next.as_slice()),
|
|
("editor", "insert_newline") => Some(runtime_keymap.editor.insert_newline.as_slice()),
|
|
("editor", "move_left") => Some(runtime_keymap.editor.move_left.as_slice()),
|
|
("editor", "move_right") => Some(runtime_keymap.editor.move_right.as_slice()),
|
|
("editor", "move_up") => Some(runtime_keymap.editor.move_up.as_slice()),
|
|
("editor", "move_down") => Some(runtime_keymap.editor.move_down.as_slice()),
|
|
("editor", "move_word_left") => Some(runtime_keymap.editor.move_word_left.as_slice()),
|
|
("editor", "move_word_right") => Some(runtime_keymap.editor.move_word_right.as_slice()),
|
|
("editor", "move_line_start") => Some(runtime_keymap.editor.move_line_start.as_slice()),
|
|
("editor", "move_line_end") => Some(runtime_keymap.editor.move_line_end.as_slice()),
|
|
("editor", "delete_backward") => Some(runtime_keymap.editor.delete_backward.as_slice()),
|
|
("editor", "delete_forward") => Some(runtime_keymap.editor.delete_forward.as_slice()),
|
|
("editor", "delete_backward_word") => Some(runtime_keymap.editor.delete_backward_word.as_slice()),
|
|
("editor", "delete_forward_word") => Some(runtime_keymap.editor.delete_forward_word.as_slice()),
|
|
("editor", "kill_line_start") => Some(runtime_keymap.editor.kill_line_start.as_slice()),
|
|
("editor", "kill_whole_line") => Some(runtime_keymap.editor.kill_whole_line.as_slice()),
|
|
("editor", "kill_line_end") => Some(runtime_keymap.editor.kill_line_end.as_slice()),
|
|
("editor", "yank") => Some(runtime_keymap.editor.yank.as_slice()),
|
|
("vim_normal", "enter_insert") => Some(runtime_keymap.vim_normal.enter_insert.as_slice()),
|
|
("vim_normal", "append_after_cursor") => Some(runtime_keymap.vim_normal.append_after_cursor.as_slice()),
|
|
("vim_normal", "append_line_end") => Some(runtime_keymap.vim_normal.append_line_end.as_slice()),
|
|
("vim_normal", "insert_line_start") => Some(runtime_keymap.vim_normal.insert_line_start.as_slice()),
|
|
("vim_normal", "open_line_below") => Some(runtime_keymap.vim_normal.open_line_below.as_slice()),
|
|
("vim_normal", "open_line_above") => Some(runtime_keymap.vim_normal.open_line_above.as_slice()),
|
|
("vim_normal", "move_left") => Some(runtime_keymap.vim_normal.move_left.as_slice()),
|
|
("vim_normal", "move_right") => Some(runtime_keymap.vim_normal.move_right.as_slice()),
|
|
("vim_normal", "move_up") => Some(runtime_keymap.vim_normal.move_up.as_slice()),
|
|
("vim_normal", "move_down") => Some(runtime_keymap.vim_normal.move_down.as_slice()),
|
|
("vim_normal", "move_word_forward") => Some(runtime_keymap.vim_normal.move_word_forward.as_slice()),
|
|
("vim_normal", "move_word_backward") => Some(runtime_keymap.vim_normal.move_word_backward.as_slice()),
|
|
("vim_normal", "move_word_end") => Some(runtime_keymap.vim_normal.move_word_end.as_slice()),
|
|
("vim_normal", "move_line_start") => Some(runtime_keymap.vim_normal.move_line_start.as_slice()),
|
|
("vim_normal", "move_line_end") => Some(runtime_keymap.vim_normal.move_line_end.as_slice()),
|
|
("vim_normal", "delete_char") => Some(runtime_keymap.vim_normal.delete_char.as_slice()),
|
|
("vim_normal", "delete_to_line_end") => Some(runtime_keymap.vim_normal.delete_to_line_end.as_slice()),
|
|
("vim_normal", "change_to_line_end") => Some(runtime_keymap.vim_normal.change_to_line_end.as_slice()),
|
|
("vim_normal", "yank_line") => Some(runtime_keymap.vim_normal.yank_line.as_slice()),
|
|
("vim_normal", "paste_after") => Some(runtime_keymap.vim_normal.paste_after.as_slice()),
|
|
("vim_normal", "start_delete_operator") => Some(runtime_keymap.vim_normal.start_delete_operator.as_slice()),
|
|
("vim_normal", "start_yank_operator") => Some(runtime_keymap.vim_normal.start_yank_operator.as_slice()),
|
|
("vim_normal", "start_change_operator") => Some(runtime_keymap.vim_normal.start_change_operator.as_slice()),
|
|
("vim_normal", "cancel_operator") => Some(runtime_keymap.vim_normal.cancel_operator.as_slice()),
|
|
("vim_operator", "delete_line") => Some(runtime_keymap.vim_operator.delete_line.as_slice()),
|
|
("vim_operator", "yank_line") => Some(runtime_keymap.vim_operator.yank_line.as_slice()),
|
|
("vim_operator", "motion_left") => Some(runtime_keymap.vim_operator.motion_left.as_slice()),
|
|
("vim_operator", "motion_right") => Some(runtime_keymap.vim_operator.motion_right.as_slice()),
|
|
("vim_operator", "motion_up") => Some(runtime_keymap.vim_operator.motion_up.as_slice()),
|
|
("vim_operator", "motion_down") => Some(runtime_keymap.vim_operator.motion_down.as_slice()),
|
|
("vim_operator", "motion_word_forward") => Some(runtime_keymap.vim_operator.motion_word_forward.as_slice()),
|
|
("vim_operator", "motion_word_backward") => Some(runtime_keymap.vim_operator.motion_word_backward.as_slice()),
|
|
("vim_operator", "motion_word_end") => Some(runtime_keymap.vim_operator.motion_word_end.as_slice()),
|
|
("vim_operator", "motion_line_start") => Some(runtime_keymap.vim_operator.motion_line_start.as_slice()),
|
|
("vim_operator", "motion_line_end") => Some(runtime_keymap.vim_operator.motion_line_end.as_slice()),
|
|
("vim_operator", "select_inner_text_object") => Some(runtime_keymap.vim_operator.select_inner_text_object.as_slice()),
|
|
("vim_operator", "select_around_text_object") => Some(runtime_keymap.vim_operator.select_around_text_object.as_slice()),
|
|
("vim_operator", "cancel") => Some(runtime_keymap.vim_operator.cancel.as_slice()),
|
|
("vim_text_object", "word") => Some(runtime_keymap.vim_text_object.word.as_slice()),
|
|
("vim_text_object", "big_word") => Some(runtime_keymap.vim_text_object.big_word.as_slice()),
|
|
("vim_text_object", "parentheses") => Some(runtime_keymap.vim_text_object.parentheses.as_slice()),
|
|
("vim_text_object", "brackets") => Some(runtime_keymap.vim_text_object.brackets.as_slice()),
|
|
("vim_text_object", "braces") => Some(runtime_keymap.vim_text_object.braces.as_slice()),
|
|
("vim_text_object", "double_quote") => Some(runtime_keymap.vim_text_object.double_quote.as_slice()),
|
|
("vim_text_object", "single_quote") => Some(runtime_keymap.vim_text_object.single_quote.as_slice()),
|
|
("vim_text_object", "backtick") => Some(runtime_keymap.vim_text_object.backtick.as_slice()),
|
|
("vim_text_object", "cancel") => Some(runtime_keymap.vim_text_object.cancel.as_slice()),
|
|
("pager", "scroll_up") => Some(runtime_keymap.pager.scroll_up.as_slice()),
|
|
("pager", "scroll_down") => Some(runtime_keymap.pager.scroll_down.as_slice()),
|
|
("pager", "page_up") => Some(runtime_keymap.pager.page_up.as_slice()),
|
|
("pager", "page_down") => Some(runtime_keymap.pager.page_down.as_slice()),
|
|
("pager", "half_page_up") => Some(runtime_keymap.pager.half_page_up.as_slice()),
|
|
("pager", "half_page_down") => Some(runtime_keymap.pager.half_page_down.as_slice()),
|
|
("pager", "jump_top") => Some(runtime_keymap.pager.jump_top.as_slice()),
|
|
("pager", "jump_bottom") => Some(runtime_keymap.pager.jump_bottom.as_slice()),
|
|
("pager", "close") => Some(runtime_keymap.pager.close.as_slice()),
|
|
("pager", "close_transcript") => Some(runtime_keymap.pager.close_transcript.as_slice()),
|
|
("list", "move_up") => Some(runtime_keymap.list.move_up.as_slice()),
|
|
("list", "move_down") => Some(runtime_keymap.list.move_down.as_slice()),
|
|
("list", "move_left") => Some(runtime_keymap.list.move_left.as_slice()),
|
|
("list", "move_right") => Some(runtime_keymap.list.move_right.as_slice()),
|
|
("list", "page_up") => Some(runtime_keymap.list.page_up.as_slice()),
|
|
("list", "page_down") => Some(runtime_keymap.list.page_down.as_slice()),
|
|
("list", "jump_top") => Some(runtime_keymap.list.jump_top.as_slice()),
|
|
("list", "jump_bottom") => Some(runtime_keymap.list.jump_bottom.as_slice()),
|
|
("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", "deny") => Some(runtime_keymap.approval.deny.as_slice()),
|
|
("approval", "decline") => Some(runtime_keymap.approval.decline.as_slice()),
|
|
("approval", "cancel") => Some(runtime_keymap.approval.cancel.as_slice()),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Format a resolved binding list for compact menu display.
|
|
///
|
|
/// Duplicate runtime variants that normalize to the same config spec are shown
|
|
/// once so compatibility defaults, such as alternate SHIFT reporting forms, do
|
|
/// not look like separate user choices.
|
|
pub(super) fn format_binding_summary(bindings: &[KeyBinding]) -> String {
|
|
let mut seen = BTreeSet::new();
|
|
let specs = bindings
|
|
.iter()
|
|
.filter_map(|binding| super::binding_to_config_key_spec(*binding).ok())
|
|
.filter(|spec| seen.insert(spec.clone()))
|
|
.collect::<Vec<_>>();
|
|
if specs.is_empty() {
|
|
"unbound".to_string()
|
|
} else {
|
|
specs.join(", ")
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub(super) enum KeymapDebugBindingSource {
|
|
Custom,
|
|
CustomGlobal,
|
|
Default,
|
|
}
|
|
|
|
impl KeymapDebugBindingSource {
|
|
pub(super) const fn label(&self) -> &'static str {
|
|
match self {
|
|
Self::Custom => "Custom",
|
|
Self::CustomGlobal => "Custom global",
|
|
Self::Default => "Default",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub(super) struct KeymapDebugActionMatch {
|
|
pub(super) context: &'static str,
|
|
pub(super) action: &'static str,
|
|
pub(super) label: String,
|
|
pub(super) description: &'static str,
|
|
pub(super) source: KeymapDebugBindingSource,
|
|
}
|
|
|
|
pub(super) fn matching_actions_for_key_event(
|
|
runtime_keymap: &RuntimeKeymap,
|
|
keymap_config: &TuiKeymap,
|
|
event: KeyEvent,
|
|
) -> Vec<KeymapDebugActionMatch> {
|
|
KEYMAP_ACTIONS
|
|
.iter()
|
|
.filter_map(|descriptor| {
|
|
let bindings =
|
|
bindings_for_action(runtime_keymap, descriptor.context, descriptor.action)?;
|
|
bindings
|
|
.iter()
|
|
.any(|binding| binding.is_press(event))
|
|
.then(|| KeymapDebugActionMatch {
|
|
context: descriptor.context,
|
|
action: descriptor.action,
|
|
label: action_label(descriptor.action),
|
|
description: descriptor.description,
|
|
source: debug_binding_source(keymap_config, descriptor),
|
|
})
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn debug_binding_source(
|
|
keymap_config: &TuiKeymap,
|
|
descriptor: &KeymapActionDescriptor,
|
|
) -> KeymapDebugBindingSource {
|
|
let mut keymap_config = keymap_config.clone();
|
|
let Some(slot) = binding_slot(&mut keymap_config, descriptor.context, descriptor.action) else {
|
|
return KeymapDebugBindingSource::Default;
|
|
};
|
|
if slot.is_some() {
|
|
return KeymapDebugBindingSource::Custom;
|
|
}
|
|
|
|
let Some(global_slot) = global_fallback_slot(&mut keymap_config, descriptor) else {
|
|
return KeymapDebugBindingSource::Default;
|
|
};
|
|
if global_slot.is_some() {
|
|
KeymapDebugBindingSource::CustomGlobal
|
|
} else {
|
|
KeymapDebugBindingSource::Default
|
|
}
|
|
}
|
|
|
|
fn global_fallback_slot<'a>(
|
|
keymap: &'a mut TuiKeymap,
|
|
descriptor: &KeymapActionDescriptor,
|
|
) -> Option<&'a mut Option<KeybindingsSpec>> {
|
|
if descriptor.context != "composer" {
|
|
return None;
|
|
}
|
|
|
|
match descriptor.action {
|
|
"submit" => Some(&mut keymap.global.submit),
|
|
"queue" => Some(&mut keymap.global.queue),
|
|
"toggle_shortcuts" => Some(&mut keymap.global.toggle_shortcuts),
|
|
_ => None,
|
|
}
|
|
}
|