diff --git a/src/ui/code_editor.rs b/src/ui/code_editor.rs index f921d835..a93b3e0a 100644 --- a/src/ui/code_editor.rs +++ b/src/ui/code_editor.rs @@ -2100,17 +2100,20 @@ impl Tty7App { ) .menu(t(L10nKey::AppMenuSelectAll), Box::new(edit::SelectAll)) .separator() - // The line commands act on the caret's line or every line the - // selection touches; the chords come from gpui-component's - // `CodeEditor` key context, which sits deeper than any of the - // app's own bindings and so wins while the editor has focus. + // The line commands act on every line a cursor or selection + // touches. Their chords live on gpui-component's `CodeEditor` key + // context and are re-added in `keymap::fixed_bindings`, so they + // beat the app's own keys while the editor has focus. .menu( t(L10nKey::EditorToggleComment), Box::new(edit::ToggleLineComment), ) .menu(t(L10nKey::EditorMoveLineUp), Box::new(edit::MoveLineUp)) .menu(t(L10nKey::EditorMoveLineDown), Box::new(edit::MoveLineDown)) - .menu(t(L10nKey::EditorDuplicateLine), Box::new(edit::CopyLineDown)) + .menu( + t(L10nKey::EditorDuplicateLine), + Box::new(edit::CopyLineDown), + ) .menu(t(L10nKey::EditorDeleteLine), Box::new(edit::DeleteLine)) .separator() .menu(t(L10nKey::AppMenuFind), Box::new(edit::Search)) diff --git a/src/ui/keymap.rs b/src/ui/keymap.rs index 14497b41..66ff839b 100644 --- a/src/ui/keymap.rs +++ b/src/ui/keymap.rs @@ -92,6 +92,32 @@ fn fixed_bindings() -> Vec { bindings.push(KeyBinding::new("secondary-alt-up", AddCursorAbove, input)); bindings.push(KeyBinding::new("secondary-alt-down", AddCursorBelow, input)); } + // The code editor's line commands, for the same tie: ⌘/ is the shortcut + // sheet, ⌘↵ fullscreen, ⌘⇧↵ maximize and ⌥↑/⌥↓ pane focus off macOS, all + // context-free. Bound on `CodeEditor`, which only a multi-line code + // editor declares, so every other text field and the terminal keep the + // app's keys. + { + use gpui_component::input::{ + CODE_EDITOR_CONTEXT, CopyLineDown, CopyLineUp, DeleteLine, InsertLineAbove, + InsertLineBelow, MoveLineDown, MoveLineUp, MoveToMatchingBracket, SelectLine, + ToggleBlockComment, ToggleLineComment, + }; + let editor = Some(CODE_EDITOR_CONTEXT); + bindings.extend([ + KeyBinding::new("secondary-/", ToggleLineComment, editor), + KeyBinding::new("alt-shift-a", ToggleBlockComment, editor), + KeyBinding::new("alt-up", MoveLineUp, editor), + KeyBinding::new("alt-down", MoveLineDown, editor), + KeyBinding::new("alt-shift-up", CopyLineUp, editor), + KeyBinding::new("alt-shift-down", CopyLineDown, editor), + KeyBinding::new("secondary-shift-k", DeleteLine, editor), + KeyBinding::new("secondary-enter", InsertLineBelow, editor), + KeyBinding::new("secondary-shift-enter", InsertLineAbove, editor), + KeyBinding::new("secondary-l", SelectLine, editor), + KeyBinding::new("secondary-shift-\\", MoveToMatchingBracket, editor), + ]); + } bindings } @@ -2617,6 +2643,59 @@ mod gpui_tests { .collect() } + /// The code editor's line commands sit on chords the app also spends — + /// ⌘/ is the shortcut sheet, ⌘↵ fullscreen, ⌘⇧↵ maximize, ⌥↑/⌥↓ pane + /// focus off macOS. `fixed_bindings` re-adds them on the `CodeEditor` + /// context the editor declares beside `Input`, after the app's table, so + /// inside the editor they win and everywhere else nothing changes. + #[gpui::test] + fn the_code_editor_chords_win_inside_the_editor(cx: &mut TestAppContext) { + cx.update(|cx| { + running_on_json(cx, "{}"); + let first = |keys: &str, stack: &[&str]| { + let input: Vec = keys + .split(' ') + .map(|k| Keystroke::parse(k).expect("the typed keystroke parses")) + .collect(); + let stack: Vec = stack + .iter() + .map(|c| gpui::KeyContext::parse(c).expect("the context parses")) + .collect(); + cx.key_bindings() + .borrow() + .bindings_for_input(&input, &stack) + .0 + .first() + .map(|b| b.action().name()) + }; + let editor = ["Workspace", "Input CodeEditor"]; + for (keys, action) in [ + ("secondary-/", "input::ToggleLineComment"), + ("alt-up", "input::MoveLineUp"), + ("alt-down", "input::MoveLineDown"), + ("alt-shift-up", "input::CopyLineUp"), + ("alt-shift-down", "input::CopyLineDown"), + ("secondary-shift-k", "input::DeleteLine"), + ("secondary-enter", "input::InsertLineBelow"), + ("secondary-shift-enter", "input::InsertLineAbove"), + ("secondary-l", "input::SelectLine"), + ("secondary-shift-\\", "input::MoveToMatchingBracket"), + ] { + assert_eq!(first(keys, &editor), Some(action), "{keys} in the editor"); + assert_ne!( + first(keys, &["Workspace", "Input"]), + Some(action), + "{keys} in a plain text field" + ); + assert_ne!( + first(keys, &["Workspace", "Terminal"]), + Some(action), + "{keys} in a terminal" + ); + } + }); + } + #[gpui::test] fn a_chord_added_in_config_keeps_the_default_one(cx: &mut TestAppContext) { use gpui::Action as _;