Added experimental keymaps support

Mikayla Maki created

Change summary

assets/keymaps/default.json                    |  7 -----
assets/keymaps/experiments/modal_terminal.json |  9 +++++++
crates/settings/src/keymap_file.rs             | 20 ++++++++++------
crates/settings/src/settings.rs                | 24 ++++++++++++++++++++
crates/terminal/src/terminal.rs                |  5 +++
crates/zed/src/main.rs                         |  9 ++++---
6 files changed, 54 insertions(+), 20 deletions(-)

Detailed changes

assets/keymaps/default.json 🔗

@@ -425,12 +425,5 @@
             "cmd-v": "terminal::Paste",
             "cmd-k": "terminal::Clear"
         }
-    },
-    {
-        "context": "ModalTerminal",
-        "bindings": {
-            "ctrl-cmd-space": "terminal::ShowCharacterPalette",
-            "shift-escape": "terminal::DeployModal"
-        }
     }
 ]

crates/settings/src/keymap_file.rs 🔗

@@ -1,4 +1,4 @@
-use crate::parse_json_with_comments;
+use crate::{parse_json_with_comments, Settings};
 use anyhow::{Context, Result};
 use assets::Assets;
 use collections::BTreeMap;
@@ -10,6 +10,7 @@ use schemars::{
 };
 use serde::Deserialize;
 use serde_json::{value::RawValue, Value};
+use util::ResultExt;
 
 #[derive(Deserialize, Default, Clone, JsonSchema)]
 #[serde(transparent)]
@@ -41,7 +42,9 @@ struct ActionWithData(Box<str>, Box<RawValue>);
 
 impl KeymapFileContent {
     pub fn load_defaults(cx: &mut MutableAppContext) {
-        for path in ["keymaps/default.json", "keymaps/vim.json"] {
+        let mut paths = vec!["keymaps/default.json", "keymaps/vim.json"];
+        paths.extend(cx.global::<Settings>().experiments.keymap_files());
+        for path in paths {
             Self::load(path, cx).unwrap();
         }
     }
@@ -56,26 +59,27 @@ impl KeymapFileContent {
         for KeymapBlock { context, bindings } in self.0 {
             let bindings = bindings
                 .into_iter()
-                .map(|(keystroke, action)| {
+                .filter_map(|(keystroke, action)| {
                     let action = action.0.get();
 
                     // This is a workaround for a limitation in serde: serde-rs/json#497
                     // We want to deserialize the action data as a `RawValue` so that we can
                     // deserialize the action itself dynamically directly from the JSON
                     // string. But `RawValue` currently does not work inside of an untagged enum.
-                    let action = if action.starts_with('[') {
-                        let ActionWithData(name, data) = serde_json::from_str(action)?;
+                    if action.starts_with('[') {
+                        let ActionWithData(name, data) = serde_json::from_str(action).log_err()?;
                         cx.deserialize_action(&name, Some(data.get()))
                     } else {
-                        let name = serde_json::from_str(action)?;
+                        let name = serde_json::from_str(action).log_err()?;
                         cx.deserialize_action(name, None)
                     }
                     .with_context(|| {
                         format!(
                             "invalid binding value for keystroke {keystroke}, context {context:?}"
                         )
-                    })?;
-                    Binding::load(&keystroke, action, context.as_deref())
+                    })
+                    .log_err()
+                    .map(|action| Binding::load(&keystroke, action, context.as_deref()))
                 })
                 .collect::<Result<Vec<_>>>()?;
 

crates/settings/src/settings.rs 🔗

@@ -20,6 +20,7 @@ pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
 
 #[derive(Clone)]
 pub struct Settings {
+    pub experiments: FeatureFlags,
     pub projects_online_by_default: bool,
     pub buffer_font_family: FamilyId,
     pub default_buffer_font_size: f32,
@@ -38,6 +39,25 @@ pub struct Settings {
     pub theme: Arc<Theme>,
 }
 
+#[derive(Copy, Clone, Debug, Default, Deserialize, JsonSchema)]
+pub struct FeatureFlags {
+    modal_terminal: Option<bool>,
+}
+
+impl FeatureFlags {
+    pub fn keymap_files(&self) -> Vec<&'static str> {
+        let mut res = vec![];
+        if self.modal_terminal() {
+            res.push("keymaps/experiments/modal_terminal.json")
+        }
+        res
+    }
+
+    pub fn modal_terminal(&self) -> bool {
+        self.modal_terminal.unwrap_or_default()
+    }
+}
+
 #[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
 pub struct EditorSettings {
     pub tab_size: Option<NonZeroU32>,
@@ -139,6 +159,7 @@ pub enum WorkingDirectory {
 
 #[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
 pub struct SettingsFileContent {
+    pub experiments: Option<FeatureFlags>,
     #[serde(default)]
     pub projects_online_by_default: Option<bool>,
     #[serde(default)]
@@ -189,6 +210,7 @@ impl Settings {
         .unwrap();
 
         Self {
+            experiments: FeatureFlags::default(),
             buffer_font_family: font_cache
                 .load_family(&[defaults.buffer_font_family.as_ref().unwrap()])
                 .unwrap(),
@@ -247,6 +269,7 @@ impl Settings {
         );
         merge(&mut self.vim_mode, data.vim_mode);
         merge(&mut self.autosave, data.autosave);
+        merge(&mut self.experiments, data.experiments);
 
         // Ensure terminal font is loaded, so we can request it in terminal_element layout
         if let Some(terminal_font) = &data.terminal.font_family {
@@ -308,6 +331,7 @@ impl Settings {
     #[cfg(any(test, feature = "test-support"))]
     pub fn test(cx: &gpui::AppContext) -> Settings {
         Settings {
+            experiments: FeatureFlags::default(),
             buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(),
             buffer_font_size: 14.,
             default_buffer_font_size: 14.,

crates/terminal/src/terminal.rs 🔗

@@ -46,7 +46,10 @@ use crate::mappings::{
 
 ///Initialize and register all of our action handlers
 pub fn init(cx: &mut MutableAppContext) {
-    cx.add_action(deploy_modal);
+    let settings = cx.global::<Settings>();
+    if settings.experiments.modal_terminal() {
+        cx.add_action(deploy_modal);
+    }
 
     terminal_view::init(cx);
     connected_view::init(cx);

crates/zed/src/main.rs 🔗

@@ -95,6 +95,11 @@ fn main() {
             .spawn(languages::init(languages.clone(), cx.background().clone()));
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
 
+        let (settings_file, keymap_file) = cx.background().block(config_files).unwrap();
+
+        watch_settings_file(default_settings, settings_file, themes.clone(), cx);
+        watch_keymap_file(keymap_file, cx);
+
         context_menu::init(cx);
         project::Project::init(&client);
         client::Channel::init(&client);
@@ -114,10 +119,6 @@ fn main() {
         terminal::init(cx);
 
         let db = cx.background().block(db);
-        let (settings_file, keymap_file) = cx.background().block(config_files).unwrap();
-
-        watch_settings_file(default_settings, settings_file, themes.clone(), cx);
-        watch_keymap_file(keymap_file, cx);
         cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx))
             .detach();