fix(tui): stop stealing Ctrl+E from composer

fix(tui): stop stealing Ctrl+E from composer
This commit is contained in:
Hunter Bown
2026-05-06 10:13:00 -05:00
committed by GitHub
parent d4185ede5a
commit 7def57203f
4 changed files with 51 additions and 8 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
//! File-tree pane — Ctrl+E toggles a left-side workspace file navigator.
//! File-tree pane — Ctrl+Shift+E toggles a left-side workspace file navigator.
//!
//! Shows the workspace directory tree with expandable directories. Up/Down
//! navigate, Enter expands/collapses directories or inserts `@path` for files,
+29 -6
View File
@@ -1805,10 +1805,9 @@ async fn run_event_loop(
continue;
}
// Ctrl+E toggles the file-tree pane. Visible even when other
// modals are open (the file tree is part of the body layout,
// not a modal overlay).
if key.code == KeyCode::Char('e') && key.modifiers.contains(KeyModifiers::CONTROL) {
// Shifted shortcuts toggle the file-tree pane. Keep plain Ctrl+E
// reserved for the composer end-of-line binding used by shells.
if is_file_tree_toggle_shortcut(&key) {
if let Some(_state) = app.file_tree.as_mut() {
// File tree visible → hide it.
app.file_tree = None;
@@ -2003,7 +2002,9 @@ async fn run_event_loop(
continue;
}
KeyCode::Char('o')
if key.modifiers == KeyModifiers::CONTROL && open_thinking_pager(app) =>
if key.modifiers == KeyModifiers::CONTROL
&& app.input.is_empty()
&& open_thinking_pager(app) =>
{
continue;
}
@@ -2562,7 +2563,10 @@ async fn run_event_loop(
app.move_cursor_end();
}
KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => {
// Ctrl+E: spawn $EDITOR on the composer contents (#91).
app.move_cursor_end();
}
KeyCode::Char('o') if key.modifiers.contains(KeyModifiers::CONTROL) => {
// Ctrl+O: spawn $EDITOR on the composer contents (#91).
// Only fires when no modal is active (the !view_stack
// branch above already returns early in that case) and
// the composer is the focused input target. We accept the
@@ -7557,6 +7561,25 @@ fn is_copy_shortcut(key: &KeyEvent) -> bool {
key.modifiers.contains(KeyModifiers::CONTROL) && key.modifiers.contains(KeyModifiers::SHIFT)
}
fn is_file_tree_toggle_shortcut(key: &KeyEvent) -> bool {
let is_shifted_e = matches!(key.code, KeyCode::Char('E'))
|| (matches!(key.code, KeyCode::Char('e')) && key.modifiers.contains(KeyModifiers::SHIFT));
if !is_shifted_e {
return false;
}
let has_forbidden_modifier =
key.modifiers.contains(KeyModifiers::ALT) || key.modifiers.contains(KeyModifiers::SUPER);
let ctrl_shift_e = key.modifiers.contains(KeyModifiers::CONTROL) && !has_forbidden_modifier;
let cmd_shift_e = key.modifiers.contains(KeyModifiers::SUPER)
&& key.modifiers.contains(KeyModifiers::SHIFT)
&& !key.modifiers.contains(KeyModifiers::CONTROL)
&& !key.modifiers.contains(KeyModifiers::ALT);
ctrl_shift_e || cmd_shift_e
}
fn details_shortcut_modifiers(modifiers: KeyModifiers) -> bool {
modifiers.is_empty()
|| modifiers == KeyModifiers::SHIFT
+20
View File
@@ -378,6 +378,26 @@ fn copy_shortcut_accepts_cmd_and_ctrl_shift_only() {
)));
}
#[test]
fn file_tree_shortcut_does_not_steal_plain_ctrl_e() {
assert!(!is_file_tree_toggle_shortcut(&KeyEvent::new(
KeyCode::Char('e'),
KeyModifiers::CONTROL,
)));
assert!(is_file_tree_toggle_shortcut(&KeyEvent::new(
KeyCode::Char('E'),
KeyModifiers::CONTROL,
)));
assert!(is_file_tree_toggle_shortcut(&KeyEvent::new(
KeyCode::Char('e'),
KeyModifiers::CONTROL | KeyModifiers::SHIFT,
)));
assert!(is_file_tree_toggle_shortcut(&KeyEvent::new(
KeyCode::Char('E'),
KeyModifiers::SUPER | KeyModifiers::SHIFT,
)));
}
#[test]
fn parse_plan_choice_accepts_numbers() {
assert_eq!(parse_plan_choice("1"), Some(PlanChoice::AcceptAgent));
+1 -1
View File
@@ -16,7 +16,7 @@ Bindings are not (yet) user-configurable — tracked for a future release (#436,
| `Shift-Tab` | Cycle reasoning effort: off → high → max → off |
| `Ctrl-R` | Open the resume-session picker |
| `Ctrl-L` | Refresh / clear the screen |
| `Ctrl-T` | Toggle the file-tree sidebar |
| `Ctrl-Shift-E` / `Cmd-Shift-E` | Toggle the file-tree sidebar |
| `Esc` | Close topmost modal · cancel slash menu · dismiss toast |
## Composer