From bf4ca8707c4c900d82f8f547f0ecb1579f6121f5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 9 Jul 2026 17:59:17 +0300 Subject: [PATCH 1/3] Add Zed base keymap and make it the default The default keymap has drifted from VSCode's actual bindings over time, so what was labeled "VSCode (Default)" was really Zed's own keymap. Name it accordingly and keep "VSCode" as a separate base keymap option. --- assets/settings/default.json | 16 +++---- crates/onboarding/src/basics_page.rs | 22 ++++++---- crates/settings/src/base_keymap_setting.rs | 18 +++++--- .../settings_content/src/settings_content.rs | 4 +- docs/src/key-bindings.md | 3 +- docs/src/reference/all-settings.md | 42 +++++++++++++++---- 6 files changed, 75 insertions(+), 30 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 0ecebd2dde9153..98f758c9f3fd17 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -13,16 +13,18 @@ }, "icon_theme": "Zed (Default)", // The name of a base set of key bindings to use. - // This setting can take six values, each named after another - // text editor: + // This setting can take the following values: // - // 1. "VSCode" - // 2. "Atom" - // 3. "JetBrains" - // 4. "None" + // 1. "Zed" + // 2. "VSCode" + // 3. "Atom" + // 4. "JetBrains" // 5. "SublimeText" // 6. "TextMate" - "base_keymap": "VSCode", + // 7. "Emacs" + // 8. "Cursor" + // 9. "None" + "base_keymap": "Zed", // The name of a font to use for rendering text in the editor // ".ZedMono" currently aliases to Lilex // but this may change in the future. diff --git a/crates/onboarding/src/basics_page.rs b/crates/onboarding/src/basics_page.rs index 40fe15c08945ac..f2b1466c716f0b 100644 --- a/crates/onboarding/src/basics_page.rs +++ b/crates/onboarding/src/basics_page.rs @@ -331,19 +331,24 @@ fn render_telemetry_section(tab_index: &mut isize, cx: &App) -> impl IntoElement fn render_base_keymap_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement { let base_keymap = match BaseKeymap::get_global(cx) { - BaseKeymap::VSCode => Some(0), - BaseKeymap::JetBrains => Some(1), - BaseKeymap::SublimeText => Some(2), - BaseKeymap::Atom => Some(3), - BaseKeymap::Emacs => Some(4), - BaseKeymap::Cursor => Some(5), - BaseKeymap::TextMate | BaseKeymap::None => None, + BaseKeymap::Zed => Some(0), + BaseKeymap::VSCode => Some(1), + BaseKeymap::JetBrains => Some(2), + BaseKeymap::SublimeText => Some(3), + BaseKeymap::Atom => Some(4), + BaseKeymap::Emacs => Some(5), + BaseKeymap::Cursor => Some(6), + BaseKeymap::TextMate => Some(7), + BaseKeymap::None => None, }; return v_flex().gap_2().child(Label::new("Base Keymap")).child( ToggleButtonGroup::two_rows( "base_keymap_selection", [ + ToggleButtonWithIcon::new("Zed", IconName::AiZed, |_, _, cx| { + write_keymap_base(BaseKeymap::Zed, cx); + }), ToggleButtonWithIcon::new("VS Code", IconName::EditorVsCode, |_, _, cx| { write_keymap_base(BaseKeymap::VSCode, cx); }), @@ -364,6 +369,9 @@ fn render_base_keymap_section(tab_index: &mut isize, cx: &mut App) -> impl IntoE ToggleButtonWithIcon::new("Cursor", IconName::EditorCursor, |_, _, cx| { write_keymap_base(BaseKeymap::Cursor, cx); }), + ToggleButtonWithIcon::new("TextMate", IconName::Keyboard, |_, _, cx| { + write_keymap_base(BaseKeymap::TextMate, cx); + }), ], ) .when_some(base_keymap, |this, base_keymap| { diff --git a/crates/settings/src/base_keymap_setting.rs b/crates/settings/src/base_keymap_setting.rs index 8e872dae4076c3..ce88182c96c9da 100644 --- a/crates/settings/src/base_keymap_setting.rs +++ b/crates/settings/src/base_keymap_setting.rs @@ -7,12 +7,13 @@ use settings::{RegisterSetting, Settings}; /// Base key bindings scheme. Base keymaps can be overridden with user keymaps. /// -/// Default: VSCode +/// Default: Zed #[derive( Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default, RegisterSetting, )] pub enum BaseKeymap { #[default] + Zed, VSCode, JetBrains, SublimeText, @@ -26,6 +27,7 @@ pub enum BaseKeymap { impl From for BaseKeymap { fn from(value: BaseKeymapContent) -> Self { match value { + BaseKeymapContent::Zed => Self::Zed, BaseKeymapContent::VSCode => Self::VSCode, BaseKeymapContent::JetBrains => Self::JetBrains, BaseKeymapContent::SublimeText => Self::SublimeText, @@ -40,6 +42,7 @@ impl From for BaseKeymap { impl Into for BaseKeymap { fn into(self) -> BaseKeymapContent { match self { + BaseKeymap::Zed => BaseKeymapContent::Zed, BaseKeymap::VSCode => BaseKeymapContent::VSCode, BaseKeymap::JetBrains => BaseKeymapContent::JetBrains, BaseKeymap::SublimeText => BaseKeymapContent::SublimeText, @@ -55,6 +58,7 @@ impl Into for BaseKeymap { impl Display for BaseKeymap { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { + BaseKeymap::Zed => write!(f, "Zed"), BaseKeymap::VSCode => write!(f, "VS Code"), BaseKeymap::JetBrains => write!(f, "JetBrains"), BaseKeymap::SublimeText => write!(f, "Sublime Text"), @@ -69,8 +73,9 @@ impl Display for BaseKeymap { impl BaseKeymap { #[cfg(target_os = "macos")] - pub const OPTIONS: [(&'static str, Self); 7] = [ - ("VS Code (Default)", Self::VSCode), + pub const OPTIONS: [(&'static str, Self); 8] = [ + ("Zed (Default)", Self::Zed), + ("VS Code", Self::VSCode), ("Atom", Self::Atom), ("JetBrains", Self::JetBrains), ("Sublime Text", Self::SublimeText), @@ -80,8 +85,9 @@ impl BaseKeymap { ]; #[cfg(not(target_os = "macos"))] - pub const OPTIONS: [(&'static str, Self); 6] = [ - ("VS Code (Default)", Self::VSCode), + pub const OPTIONS: [(&'static str, Self); 7] = [ + ("Zed (Default)", Self::Zed), + ("VS Code", Self::VSCode), ("Atom", Self::Atom), ("JetBrains", Self::JetBrains), ("Sublime Text", Self::SublimeText), @@ -99,6 +105,7 @@ impl BaseKeymap { BaseKeymap::Emacs => Some("keymaps/macos/emacs.json"), BaseKeymap::Cursor => Some("keymaps/macos/cursor.json"), BaseKeymap::VSCode => None, + BaseKeymap::Zed => None, BaseKeymap::None => None, } @@ -111,6 +118,7 @@ impl BaseKeymap { BaseKeymap::Cursor => Some("keymaps/linux/cursor.json"), BaseKeymap::TextMate => None, BaseKeymap::VSCode => None, + BaseKeymap::Zed => None, BaseKeymap::None => None, } } diff --git a/crates/settings_content/src/settings_content.rs b/crates/settings_content/src/settings_content.rs index 0edd11fdea74dc..3b446feabfc9c6 100644 --- a/crates/settings_content/src/settings_content.rs +++ b/crates/settings_content/src/settings_content.rs @@ -459,7 +459,7 @@ pub struct ExtensionsSettingsContent { /// Base key bindings scheme. Base keymaps can be overridden with user keymaps. /// -/// Default: VSCode +/// Default: Zed #[derive( Copy, Clone, @@ -475,6 +475,7 @@ pub struct ExtensionsSettingsContent { )] pub enum BaseKeymapContent { #[default] + Zed, VSCode, JetBrains, SublimeText, @@ -487,6 +488,7 @@ pub enum BaseKeymapContent { impl strum::VariantNames for BaseKeymapContent { const VARIANTS: &'static [&'static str] = &[ + "Zed", "VSCode", "JetBrains", "Sublime Text", diff --git a/docs/src/key-bindings.md b/docs/src/key-bindings.md index 490293c9eba398..b137d1217e7597 100644 --- a/docs/src/key-bindings.md +++ b/docs/src/key-bindings.md @@ -12,7 +12,8 @@ Zed's key binding system is fully customizable. You can rebind any action, creat If you're used to a specific editor's defaults, you can change your `base_keymap` through the settings window ({#kb zed::OpenSettings}) or directly through your `settings.json` file ({#kb zed::OpenSettingsFile}). We currently support: -- VS Code (default) +- Zed (default) +- VS Code - Atom - Emacs (Beta) - JetBrains diff --git a/docs/src/reference/all-settings.md b/docs/src/reference/all-settings.md index 25024fc908bf89..c768650c76421a 100644 --- a/docs/src/reference/all-settings.md +++ b/docs/src/reference/all-settings.md @@ -304,39 +304,39 @@ Note that a save will be triggered when an unsaved tab is closed, even if this i - Description: Base key bindings scheme. Base keymaps can be overridden with user keymaps. - Setting: `base_keymap` -- Default: `VSCode` +- Default: `Zed` **Options** -1. VS Code +1. Zed ```json [settings] { - "base_keymap": "VSCode" + "base_keymap": "Zed" } ``` -2. Atom +2. VS Code ```json [settings] { - "base_keymap": "Atom" + "base_keymap": "VSCode" } ``` -3. JetBrains +3. Atom ```json [settings] { - "base_keymap": "JetBrains" + "base_keymap": "Atom" } ``` -4. None +4. JetBrains ```json [settings] { - "base_keymap": "None" + "base_keymap": "JetBrains" } ``` @@ -356,6 +356,30 @@ Note that a save will be triggered when an unsaved tab is closed, even if this i } ``` +7. Emacs + +```json [settings] +{ + "base_keymap": "Emacs" +} +``` + +8. Cursor + +```json [settings] +{ + "base_keymap": "Cursor" +} +``` + +9. None + +```json [settings] +{ + "base_keymap": "None" +} +``` + ## Buffer Font Family - Description: The name of a font to use for rendering text in the editor. From 29e3b5b0e9a619db0fc8c2cdd7e4ac26f6407f8b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 9 Jul 2026 18:13:54 +0300 Subject: [PATCH 2/3] Fix VSCode base keymap discrepancies Give the VSCode base keymap its own overlay assets instead of being an alias for the default (now Zed) keymap, and fix the bindings where Zed's defaults diverge from actual VS Code: Format Document (shift-alt-f), Format Selection, insert cursors at line ends (shift-alt-i), toggle word wrap (alt-z), Zen Mode (cmd-k z / ctrl-k z), parameter hints, AST selection expansion (macOS), find regex toggle (macOS), block comment (Linux), open definition to the side, and F5/F10 debugger bindings. --- assets/keymaps/linux/vscode.json | 58 +++++++++++++++++ assets/keymaps/macos/vscode.json | 74 ++++++++++++++++++++++ crates/settings/src/base_keymap_setting.rs | 4 +- crates/zed/src/zed.rs | 32 ++++++++++ 4 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 assets/keymaps/linux/vscode.json create mode 100644 assets/keymaps/macos/vscode.json diff --git a/assets/keymaps/linux/vscode.json b/assets/keymaps/linux/vscode.json new file mode 100644 index 00000000000000..92c49294c060b4 --- /dev/null +++ b/assets/keymaps/linux/vscode.json @@ -0,0 +1,58 @@ +[ + // VS Code for Linux (and Windows). See: https://code.visualstudio.com/docs/reference/default-keybindings + // + // Zed's default keymap is close to VS Code's, so this overlay only contains + // the bindings where the two diverge. + { + "context": "Editor", + "use_key_equivalents": true, + "bindings": { + "shift-alt-f": "editor::Format", // Format Document (Windows-style; Linux `ctrl-shift-i` is a Zed default already) + "ctrl-k ctrl-f": "editor::FormatSelections", // Format Selection + "shift-alt-i": "editor::SplitSelectionIntoLines", // Insert cursor at end of each line selected + "alt-z": "editor::ToggleSoftWrap", // Toggle Word Wrap + "ctrl-shift-space": "editor::ShowSignatureHelp", // Trigger Parameter Hints + "ctrl-shift-a": "editor::ToggleBlockComments", // Toggle Block Comment + "ctrl-k f12": "editor::GoToDefinitionSplit", // Open Definition to the Side + }, + }, + { + "context": "Editor && mode == full", + "use_key_equivalents": true, + "bindings": { + // In VS Code `ctrl-k z` is Zen Mode; the word wrap toggle moves to `alt-z`. + "ctrl-k z": "workspace::ToggleCenteredLayout", + "ctrl-i": "assistant::InlineAssist", // Inline Chat + }, + }, + { + "context": "Workspace", + "use_key_equivalents": true, + "bindings": { + "f5": "debugger::Start", // Start Debugging + "ctrl-alt-i": "agent::ToggleFocus", // Open Chat + }, + }, + { + "context": "Workspace && debugger_running", + "use_key_equivalents": true, + "bindings": { + "f5": null, + }, + }, + { + "context": "Workspace && debugger_stopped", + "use_key_equivalents": true, + "bindings": { + "f5": "debugger::Continue", + }, + }, + { + "context": "Workspace && debugger_session", + "use_key_equivalents": true, + "bindings": { + "f10": "debugger::StepOver", + "f11": "debugger::StepInto", + }, + }, +] diff --git a/assets/keymaps/macos/vscode.json b/assets/keymaps/macos/vscode.json new file mode 100644 index 00000000000000..69c9adcf365b4a --- /dev/null +++ b/assets/keymaps/macos/vscode.json @@ -0,0 +1,74 @@ +[ + // VS Code for macOS. See: https://code.visualstudio.com/docs/reference/default-keybindings + // + // Zed's default keymap is close to VS Code's, so this overlay only contains + // the bindings where the two diverge. + { + "context": "Editor", + "use_key_equivalents": true, + "bindings": { + "shift-alt-f": "editor::Format", // Format Document + "cmd-k cmd-f": "editor::FormatSelections", // Format Selection + "shift-alt-i": "editor::SplitSelectionIntoLines", // Insert cursor at end of each line selected + "alt-z": "editor::ToggleSoftWrap", // Toggle Word Wrap + "cmd-shift-space": "editor::ShowSignatureHelp", // Trigger Parameter Hints + "ctrl-shift-cmd-right": "editor::SelectLargerSyntaxNode", // Expand AST Selection + "ctrl-shift-cmd-left": "editor::SelectSmallerSyntaxNode", // Shrink AST Selection + "cmd-k cmd-c": ["editor::ToggleComments", { "advance_downwards": false }], // Add Line Comment (closest analog) + "cmd-k f12": "editor::GoToDefinitionSplit", // Open Definition to the Side + }, + }, + { + "context": "Editor && mode == full", + "use_key_equivalents": true, + "bindings": { + // In VS Code `cmd-k z` is Zen Mode; the word wrap toggle moves to `alt-z`. + "cmd-k z": "workspace::ToggleCenteredLayout", + "cmd-i": "assistant::InlineAssist", // Inline Chat + }, + }, + { + "context": "Terminal", + "use_key_equivalents": true, + "bindings": { + "cmd-i": "assistant::InlineAssist", // Terminal Inline Chat + }, + }, + { + "context": "BufferSearchBar || ProjectSearchBar", + "use_key_equivalents": true, + "bindings": { + "alt-cmd-r": "search::ToggleRegex", // Toggle Find Regex + }, + }, + { + "context": "Workspace", + "use_key_equivalents": true, + "bindings": { + "f5": "debugger::Start", // Start Debugging + "ctrl-cmd-i": "agent::ToggleFocus", // Open Chat + }, + }, + { + "context": "Workspace && debugger_running", + "use_key_equivalents": true, + "bindings": { + "f5": null, + }, + }, + { + "context": "Workspace && debugger_stopped", + "use_key_equivalents": true, + "bindings": { + "f5": "debugger::Continue", + }, + }, + { + "context": "Workspace && debugger_session", + "use_key_equivalents": true, + "bindings": { + "f10": "debugger::StepOver", + "f11": "debugger::StepInto", + }, + }, +] diff --git a/crates/settings/src/base_keymap_setting.rs b/crates/settings/src/base_keymap_setting.rs index ce88182c96c9da..28fc665de5f009 100644 --- a/crates/settings/src/base_keymap_setting.rs +++ b/crates/settings/src/base_keymap_setting.rs @@ -104,7 +104,7 @@ impl BaseKeymap { BaseKeymap::TextMate => Some("keymaps/macos/textmate.json"), BaseKeymap::Emacs => Some("keymaps/macos/emacs.json"), BaseKeymap::Cursor => Some("keymaps/macos/cursor.json"), - BaseKeymap::VSCode => None, + BaseKeymap::VSCode => Some("keymaps/macos/vscode.json"), BaseKeymap::Zed => None, BaseKeymap::None => None, } @@ -117,7 +117,7 @@ impl BaseKeymap { BaseKeymap::Emacs => Some("keymaps/linux/emacs.json"), BaseKeymap::Cursor => Some("keymaps/linux/cursor.json"), BaseKeymap::TextMate => None, - BaseKeymap::VSCode => None, + BaseKeymap::VSCode => Some("keymaps/linux/vscode.json"), BaseKeymap::Zed => None, BaseKeymap::None => None, } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 19fe94d3ef5d4a..dd107911903fd4 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -5287,6 +5287,8 @@ mod tests { use workspace::ActivatePreviousPane; // From the JetBrains keymap use workspace::ActivatePreviousItem; + // From the VSCode keymap + use debugger_ui::Start; app_state .fs @@ -5379,6 +5381,36 @@ mod tests { ], line!(), ); + + // Test the VSCode keymap overlay + app_state + .fs + .save( + paths::settings_file(), + &r#"{"base_keymap": "VSCode"}"#.into(), + Default::default(), + ) + .await + .unwrap(); + + executor.run_until_parked(); + + window + .update(cx, |_, _, cx| { + workspace.update(cx, |workspace, cx| { + workspace.register_action(|_, _: &Start, _window, _cx| {}); + cx.notify(); + }); + }) + .unwrap(); + executor.run_until_parked(); + + assert_key_bindings_for( + window.into(), + cx, + vec![("backspace", &ActionB), ("f5", &Start)], + line!(), + ); } #[gpui::test] From d574774e115206867b11e8324e9bf75778965254 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 23 Jul 2026 18:35:10 +0300 Subject: [PATCH 3/3] Allow base keymaps to disable actions with `null` --- assets/keymaps/linux/vscode.json | 14 +++++ assets/keymaps/macos/vscode.json | 8 +++ crates/gpui/src/keymap.rs | 96 +++++++++++++++++++++++++++----- 3 files changed, 105 insertions(+), 13 deletions(-) diff --git a/assets/keymaps/linux/vscode.json b/assets/keymaps/linux/vscode.json index 92c49294c060b4..a333e19ff9acf3 100644 --- a/assets/keymaps/linux/vscode.json +++ b/assets/keymaps/linux/vscode.json @@ -25,6 +25,20 @@ "ctrl-i": "assistant::InlineAssist", // Inline Chat }, }, + { + "context": "!AcpThread > Editor && mode == full", + "use_key_equivalents": true, + "bindings": { + "ctrl-enter": "editor::NewlineBelow", // Insert Line Below (Inline Chat lives on `ctrl-i`) + }, + }, + { + "context": "Terminal", + "use_key_equivalents": true, + "bindings": { + "ctrl-enter": null, + }, + }, { "context": "Workspace", "use_key_equivalents": true, diff --git a/assets/keymaps/macos/vscode.json b/assets/keymaps/macos/vscode.json index 69c9adcf365b4a..532671e5080d50 100644 --- a/assets/keymaps/macos/vscode.json +++ b/assets/keymaps/macos/vscode.json @@ -27,11 +27,19 @@ "cmd-i": "assistant::InlineAssist", // Inline Chat }, }, + { + "context": "!AcpThread > Editor && mode == full", + "use_key_equivalents": true, + "bindings": { + "ctrl-enter": null, // Inline Chat lives on `cmd-i` in VS Code + }, + }, { "context": "Terminal", "use_key_equivalents": true, "bindings": { "cmd-i": "assistant::InlineAssist", // Terminal Inline Chat + "ctrl-enter": null, }, }, { diff --git a/crates/gpui/src/keymap.rs b/crates/gpui/src/keymap.rs index ade499b890bd82..bef0d03a2b9518 100644 --- a/crates/gpui/src/keymap.rs +++ b/crates/gpui/src/keymap.rs @@ -158,9 +158,10 @@ impl Keymap { /// In the case of multiple bindings at the same depth, the ones added to the keymap later take /// precedence. User bindings are added after built-in bindings so that they take precedence. /// - /// If a user has disabled a binding with `"x": null` it will not be returned. Disabled bindings + /// If a binding has been disabled with `"x": null` it will not be returned. Disabled bindings /// are evaluated with the same precedence rules so you can disable a rule in a given context - /// only. + /// only. A disabled binding only suppresses bindings from sources with equal or weaker + /// precedence: a base keymap null hides default bindings, but user bindings still apply. pub fn bindings_for_input( &self, input: &[impl AsKeystroke], @@ -191,20 +192,20 @@ impl Keymap { let mut bindings: SmallVec<[_; 1]> = SmallVec::new(); let mut first_binding_index = None; let mut unbound_bindings: Vec<&KeyBinding> = Vec::new(); + // A `NoAction` binding suppresses out-ranked bindings from sources with + // equal or weaker precedence, while bindings from stronger sources (a + // smaller meta, e.g. a user binding vs a base keymap null) still apply. + // Bindings without a meta are treated as user bindings. + let mut no_action_meta: Option = None; for (_, ix, binding) in matched_bindings { + let meta = binding.meta.map_or(0, |meta| meta.0); if is_no_action(&*binding.action) { - // Only break if this is a user-defined NoAction binding - // This allows user keymaps to override base keymap NoAction bindings - if let Some(meta) = binding.meta { - if meta.0 == 0 { - break; - } - } else { - // If no meta is set, assume it's a user binding for safety - break; - } - // For non-user NoAction bindings, continue searching for user overrides + no_action_meta = Some(no_action_meta.map_or(meta, |existing| existing.min(meta))); + continue; + } + + if no_action_meta.is_some_and(|no_action_meta| meta >= no_action_meta) { continue; } @@ -570,6 +571,75 @@ mod tests { assert!(!pending); } + #[test] + fn test_disable_weaker_sources_only() { + const USER: KeyBindingMetaIndex = KeyBindingMetaIndex(0); + const VIM: KeyBindingMetaIndex = KeyBindingMetaIndex(1); + const BASE: KeyBindingMetaIndex = KeyBindingMetaIndex(2); + const DEFAULT: KeyBindingMetaIndex = KeyBindingMetaIndex(3); + + let editor_context = || [KeyContext::parse("editor").unwrap()]; + let ctrl_x = || [Keystroke::parse("ctrl-x").unwrap()]; + + // A base keymap null disables a default binding in the same context. + let mut keymap = Keymap::default(); + keymap.add_bindings([ + KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")).with_meta(DEFAULT), + KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(BASE), + ]); + let (result, _) = keymap.bindings_for_input(&ctrl_x(), &editor_context()); + assert!(result.is_empty()); + + // A user binding is not affected by base keymap or default nulls. + let mut keymap = Keymap::default(); + keymap.add_bindings([ + KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(DEFAULT), + KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(BASE), + KeyBinding::new("ctrl-x", ActionBeta {}, None).with_meta(USER), + ]); + let (result, _) = keymap.bindings_for_input(&ctrl_x(), &editor_context()); + assert_eq!(result.len(), 1); + assert!(result[0].action.partial_eq(&ActionBeta {})); + + // A user binding at a shallower context is not disabled by a deeper + // base keymap null. + let mut keymap = Keymap::default(); + keymap.add_bindings([ + KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(BASE), + KeyBinding::new("ctrl-x", ActionBeta {}, Some("workspace")).with_meta(USER), + ]); + let (result, _) = keymap.bindings_for_input( + &ctrl_x(), + &[ + KeyContext::parse("workspace").unwrap(), + KeyContext::parse("editor").unwrap(), + ], + ); + assert_eq!(result.len(), 1); + assert!(result[0].action.partial_eq(&ActionBeta {})); + + // A vim binding survives a base keymap null, and a user null disables + // everything. + let mut keymap = Keymap::default(); + keymap.add_bindings([ + KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")).with_meta(DEFAULT), + KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(BASE), + KeyBinding::new("ctrl-x", ActionGamma {}, Some("editor")).with_meta(VIM), + ]); + let (result, _) = keymap.bindings_for_input(&ctrl_x(), &editor_context()); + assert_eq!(result.len(), 1); + assert!(result[0].action.partial_eq(&ActionGamma {})); + + let mut keymap = Keymap::default(); + keymap.add_bindings([ + KeyBinding::new("ctrl-x", ActionAlpha {}, Some("editor")).with_meta(DEFAULT), + KeyBinding::new("ctrl-x", ActionGamma {}, Some("editor")).with_meta(VIM), + KeyBinding::new("ctrl-x", NoAction {}, Some("editor")).with_meta(USER), + ]); + let (result, _) = keymap.bindings_for_input(&ctrl_x(), &editor_context()); + assert!(result.is_empty()); + } + #[test] fn test_fail_to_disable() { // disabled at the wrong level