diff --git a/crates/gpui/src/keymap/binding.rs b/crates/gpui/src/keymap/binding.rs index cbe934212f..ffc4656ff7 100644 --- a/crates/gpui/src/keymap/binding.rs +++ b/crates/gpui/src/keymap/binding.rs @@ -10,6 +10,7 @@ pub struct KeyBinding { pub(crate) action: Box, pub(crate) keystrokes: SmallVec<[Keystroke; 2]>, pub(crate) context_predicate: Option>, + pub(crate) meta: Option, } impl Clone for KeyBinding { @@ -18,6 +19,7 @@ impl Clone for KeyBinding { action: self.action.boxed_clone(), keystrokes: self.keystrokes.clone(), context_predicate: self.context_predicate.clone(), + meta: self.meta, } } } @@ -59,9 +61,21 @@ impl KeyBinding { keystrokes, action, context_predicate, + meta: None, }) } + /// Set the metadata for this binding. + pub fn with_meta(mut self, meta: KeyBindingMetaIndex) -> Self { + self.meta = Some(meta); + self + } + + /// Set the metadata for this binding. + pub fn set_meta(&mut self, meta: KeyBindingMetaIndex) { + self.meta = Some(meta); + } + /// Check if the given keystrokes match this binding. pub fn match_keystrokes(&self, typed: &[Keystroke]) -> Option { if self.keystrokes.len() < typed.len() { @@ -91,6 +105,11 @@ impl KeyBinding { pub fn predicate(&self) -> Option> { self.context_predicate.as_ref().map(|rc| rc.clone()) } + + /// Get the metadata for this binding + pub fn meta(&self) -> Option { + self.meta + } } impl std::fmt::Debug for KeyBinding { @@ -102,3 +121,9 @@ impl std::fmt::Debug for KeyBinding { .finish() } } + +/// A unique identifier for retrieval of metadata associated with a key binding. +/// Intended to be used as an index or key into a user-defined store of metadata +/// associated with the binding, such as the source of the binding. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct KeyBindingMetaIndex(pub u32); diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index 96736f512a..551920c8a0 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -3,7 +3,7 @@ use collections::{BTreeMap, HashMap, IndexMap}; use fs::Fs; use gpui::{ Action, ActionBuildError, App, InvalidKeystrokeError, KEYSTROKE_PARSE_EXPECTED_MESSAGE, - KeyBinding, KeyBindingContextPredicate, NoAction, + KeyBinding, KeyBindingContextPredicate, KeyBindingMetaIndex, NoAction, }; use schemars::{ JsonSchema, @@ -151,9 +151,21 @@ impl KeymapFile { parse_json_with_comments::(content) } - pub fn load_asset(asset_path: &str, cx: &App) -> anyhow::Result> { + pub fn load_asset( + asset_path: &str, + source: Option, + cx: &App, + ) -> anyhow::Result> { match Self::load(asset_str::(asset_path).as_ref(), cx) { - KeymapFileLoadResult::Success { key_bindings } => Ok(key_bindings), + KeymapFileLoadResult::Success { mut key_bindings } => match source { + Some(source) => Ok({ + for key_binding in &mut key_bindings { + key_binding.set_meta(source.meta()); + } + key_bindings + }), + None => Ok(key_bindings), + }, KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => { anyhow::bail!("Error loading built-in keymap \"{asset_path}\": {error_message}",) } @@ -619,6 +631,61 @@ impl KeymapFile { } } +#[derive(Clone, Copy)] +pub enum KeybindSource { + User, + Default, + Base, + Vim, +} + +impl KeybindSource { + const BASE: KeyBindingMetaIndex = KeyBindingMetaIndex(0); + const DEFAULT: KeyBindingMetaIndex = KeyBindingMetaIndex(1); + const VIM: KeyBindingMetaIndex = KeyBindingMetaIndex(2); + const USER: KeyBindingMetaIndex = KeyBindingMetaIndex(3); + + pub fn name(&self) -> &'static str { + match self { + KeybindSource::User => "User", + KeybindSource::Default => "Default", + KeybindSource::Base => "Base", + KeybindSource::Vim => "Vim", + } + } + + pub fn meta(&self) -> KeyBindingMetaIndex { + match self { + KeybindSource::User => Self::USER, + KeybindSource::Default => Self::DEFAULT, + KeybindSource::Base => Self::BASE, + KeybindSource::Vim => Self::VIM, + } + } + + pub fn from_meta(index: KeyBindingMetaIndex) -> Self { + match index { + _ if index == Self::USER => KeybindSource::User, + _ if index == Self::USER => KeybindSource::Base, + _ if index == Self::DEFAULT => KeybindSource::Default, + _ if index == Self::VIM => KeybindSource::Vim, + _ => unreachable!(), + } + } +} + +impl From for KeybindSource { + fn from(index: KeyBindingMetaIndex) -> Self { + Self::from_meta(index) + } +} + +impl From for KeyBindingMetaIndex { + fn from(source: KeybindSource) -> Self { + return source.meta(); + } +} + #[cfg(test)] mod tests { use crate::KeymapFile; diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 2ecb38b5c6..a01414b0b2 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -15,7 +15,8 @@ pub use editable_setting_control::*; pub use json_schema::*; pub use key_equivalents::*; pub use keymap_file::{ - KeyBindingValidator, KeyBindingValidatorRegistration, KeymapFile, KeymapFileLoadResult, + KeyBindingValidator, KeyBindingValidatorRegistration, KeybindSource, KeymapFile, + KeymapFileLoadResult, }; pub use settings_file::*; pub use settings_store::{ diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index 8e2bbad3bb..c8b055a67e 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -146,7 +146,7 @@ fn load_embedded_fonts(cx: &App) -> anyhow::Result<()> { } fn load_storybook_keymap(cx: &mut App) { - cx.bind_keys(KeymapFile::load_asset("keymaps/storybook.json", cx).unwrap()); + cx.bind_keys(KeymapFile::load_asset("keymaps/storybook.json", None, cx).unwrap()); } pub fn init(cx: &mut App) { diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index f8acecc9b1..3abec1c2eb 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -74,8 +74,12 @@ impl VimTestContext { .unwrap(); cx.bind_keys(default_key_bindings); if enabled { - let vim_key_bindings = - settings::KeymapFile::load_asset("keymaps/vim.json", cx).unwrap(); + let vim_key_bindings = settings::KeymapFile::load_asset( + "keymaps/vim.json", + Some(settings::KeybindSource::Vim), + cx, + ) + .unwrap(); cx.bind_keys(vim_key_bindings); } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 4cab84678c..62e29eb7e2 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -47,8 +47,8 @@ use release_channel::{AppCommitSha, ReleaseChannel}; use rope::Rope; use search::project_search::ProjectSearchBar; use settings::{ - DEFAULT_KEYMAP_PATH, InvalidSettingsError, KeymapFile, KeymapFileLoadResult, Settings, - SettingsStore, VIM_KEYMAP_PATH, initial_local_debug_tasks_content, + DEFAULT_KEYMAP_PATH, InvalidSettingsError, KeybindSource, KeymapFile, KeymapFileLoadResult, + Settings, SettingsStore, VIM_KEYMAP_PATH, initial_local_debug_tasks_content, initial_project_settings_content, initial_tasks_content, update_settings_file, }; use std::path::PathBuf; @@ -1403,10 +1403,15 @@ fn show_markdown_app_notification( .detach(); } -fn reload_keymaps(cx: &mut App, user_key_bindings: Vec) { +fn reload_keymaps(cx: &mut App, mut user_key_bindings: Vec) { cx.clear_key_bindings(); load_default_keymap(cx); + + for key_binding in &mut user_key_bindings { + key_binding.set_meta(KeybindSource::User.meta()); + } cx.bind_keys(user_key_bindings); + cx.set_menus(app_menus()); // On Windows, this is set in the `update_jump_list` method of the `HistoryManager`. #[cfg(not(target_os = "windows"))] @@ -1422,14 +1427,18 @@ pub fn load_default_keymap(cx: &mut App) { return; } - cx.bind_keys(KeymapFile::load_asset(DEFAULT_KEYMAP_PATH, cx).unwrap()); + cx.bind_keys( + KeymapFile::load_asset(DEFAULT_KEYMAP_PATH, Some(KeybindSource::Default), cx).unwrap(), + ); if let Some(asset_path) = base_keymap.asset_path() { - cx.bind_keys(KeymapFile::load_asset(asset_path, cx).unwrap()); + cx.bind_keys(KeymapFile::load_asset(asset_path, Some(KeybindSource::Base), cx).unwrap()); } if VimModeSetting::get_global(cx).0 || vim_mode_setting::HelixModeSetting::get_global(cx).0 { - cx.bind_keys(KeymapFile::load_asset(VIM_KEYMAP_PATH, cx).unwrap()); + cx.bind_keys( + KeymapFile::load_asset(VIM_KEYMAP_PATH, Some(KeybindSource::Vim), cx).unwrap(), + ); } }