Add base keymap setting

Mikayla Maki and Nathan created

Format all files

Co-Authored-by: Nathan <nathan@zed.dev>

Change summary

assets/keymaps/atom.json                    | 68 ++++++++++++++++++++
assets/keymaps/jetbrains.json               | 78 +++++++++++++++++++++++
assets/keymaps/sublime_text.json            | 60 +++++++++++++++++
crates/install_cli/src/install_cli.rs       |  9 +-
crates/settings/src/keymap_file.rs          |  6 +
crates/settings/src/settings.rs             | 24 +++++++
crates/settings/src/watched_json.rs         | 31 +++++++-
crates/theme_selector/src/theme_selector.rs |  6 -
crates/welcome/src/welcome.rs               |  8 +
crates/workspace/src/workspace.rs           | 23 +++--
crates/zed/src/main.rs                      | 13 ++-
crates/zed/src/zed.rs                       | 10 ++
12 files changed, 301 insertions(+), 35 deletions(-)

Detailed changes

assets/keymaps/atom.json 🔗

@@ -0,0 +1,68 @@
+[
+  {
+    "bindings": {
+      "cmd-k cmd-p": "workspace::ActivatePreviousPane",
+      "cmd-k cmd-n": "workspace::ActivateNextPane"
+    }
+  },
+  {
+    "context": "Editor",
+    "bindings": {
+      "cmd-b": "editor::GoToDefinition",
+      "cmd-<": "editor::ScrollCursorCenter",
+      "cmd-g": [
+        "editor::SelectNext",
+        {
+          "replace_newest": true
+        }
+      ],
+      "ctrl-shift-down": "editor::AddSelectionBelow",
+      "ctrl-shift-up": "editor::AddSelectionAbove",
+      "cmd-shift-backspace": "editor::DeleteToBeginningOfLine"
+    }
+  },
+  {
+    "context": "Editor && mode == full",
+    "bindings": {
+      "cmd-r": "outline::Toggle"
+    }
+  },
+  {
+    "context": "BufferSearchBar",
+    "bindings": {
+      "cmd-f3": "search::SelectNextMatch",
+      "cmd-shift-f3": "search::SelectPrevMatch"
+    }
+  },
+  {
+    "context": "Workspace",
+    "bindings": {
+      "cmd-\\": "workspace::ToggleLeftSidebar",
+      "cmd-k cmd-b": "workspace::ToggleLeftSidebar",
+      "cmd-t": "file_finder::Toggle",
+      "cmd-shift-r": "project_symbols::Toggle"
+    }
+  },
+  {
+    "context": "Pane",
+    "bindings": {
+      "alt-cmd-/": "search::ToggleRegex",
+      "ctrl-0": "project_panel::ToggleFocus"
+    }
+  },
+  {
+    "context": "ProjectPanel",
+    "bindings": {
+      "ctrl-[": "project_panel::CollapseSelectedEntry",
+      "ctrl-b": "project_panel::CollapseSelectedEntry",
+      "h": "project_panel::CollapseSelectedEntry",
+      "ctrl-]": "project_panel::ExpandSelectedEntry",
+      "ctrl-f": "project_panel::ExpandSelectedEntry",
+      "ctrl-shift-c": "project_panel::CopyPath"
+    }
+  },
+  {
+    "context": "Dock",
+    "bindings": {}
+  }
+]

assets/keymaps/jetbrains.json 🔗

@@ -0,0 +1,78 @@
+[
+  {
+    "bindings": {
+      "cmd-shift-[": "pane::ActivatePrevItem",
+      "cmd-shift-]": "pane::ActivateNextItem"
+    }
+  },
+  {
+    "context": "Editor",
+    "bindings": {
+      "ctrl->": "zed::IncreaseBufferFontSize",
+      "ctrl-<": "zed::DecreaseBufferFontSize",
+      "cmd-d": "editor::DuplicateLine",
+      "cmd-pagedown": "editor::MovePageDown",
+      "cmd-pageup": "editor::MovePageUp",
+      "ctrl-alt-shift-b": "editor::SelectToPreviousWordStart",
+      "shift-enter": "editor::NewlineBelow",
+      "cmd--": "editor::Fold",
+      "cmd-=": "editor::UnfoldLines",
+      "alt-shift-g": "editor::SplitSelectionIntoLines",
+      "ctrl-g": [
+        "editor::SelectNext",
+        {
+          "replace_newest": false
+        }
+      ],
+      "cmd-/": [
+        "editor::ToggleComments",
+        {
+          "advance_downwards": true
+        }
+      ],
+      "shift-alt-up": "editor::MoveLineUp",
+      "shift-alt-down": "editor::MoveLineDown",
+      "cmd-[": "pane::GoBack",
+      "cmd-]": "pane::GoForward",
+      "alt-f7": "editor::FindAllReferences",
+      "cmd-alt-f7": "editor::FindAllReferences",
+      "cmd-b": "editor::GoToDefinition",
+      "cmd-alt-b": "editor::GoToDefinition",
+      "cmd-shift-b": "editor::GoToTypeDefinition",
+      "alt-enter": "editor::ToggleCodeActions",
+      "f2": "editor::GoToDiagnostic",
+      "cmd-f2": "editor::GoToPrevDiagnostic",
+      "ctrl-alt-shift-down": "editor::GoToHunk",
+      "ctrl-alt-shift-up": "editor::GoToPrevHunk",
+      "cmd-home": "editor::MoveToBeginning",
+      "cmd-end": "editor::MoveToEnd",
+      "cmd-shift-home": "editor::SelectToBeginning",
+      "cmd-shift-end": "editor::SelectToEnd"
+    }
+  },
+  {
+    "context": "Editor && mode == full",
+    "bindings": {
+      "cmd-f12": "outline::Toggle",
+      "cmd-7": "outline::Toggle",
+      "cmd-shift-o": "file_finder::Toggle",
+      "cmd-l": "go_to_line::Toggle"
+    }
+  },
+  {
+    "context": "Workspace",
+    "bindings": {
+      "cmd-shift-a": "command_palette::Toggle",
+      "cmd-alt-o": "project_symbols::Toggle",
+      "cmd-1": "workspace::ToggleLeftSidebar",
+      "cmd-6": "diagnostics::Deploy",
+      "alt-f12": "dock::FocusDock"
+    }
+  },
+  {
+    "context": "Dock",
+    "bindings": {
+      "alt-f12": "dock::HideDock"
+    }
+  }
+]

assets/keymaps/sublime_text.json 🔗

@@ -0,0 +1,60 @@
+[
+  {
+    "bindings": {
+      "cmd-shift-[": "pane::ActivatePrevItem",
+      "cmd-shift-]": "pane::ActivateNextItem",
+      "ctrl-pagedown": "pane::ActivatePrevItem",
+      "ctrl-pageup": "pane::ActivateNextItem",
+      "ctrl-shift-tab": "pane::ActivateNextItem",
+      "ctrl-tab": "pane::ActivatePrevItem",
+      "cmd-+": "zed::IncreaseBufferFontSize"
+    }
+  },
+  {
+    "context": "Editor",
+    "bindings": {
+      "ctrl-shift-up": "editor::AddSelectionAbove",
+      "ctrl-shift-down": "editor::AddSelectionBelow",
+      "cmd-shift-space": "editor::SelectAll",
+      "ctrl-shift-m": "editor::SelectLargerSyntaxNode",
+      "cmd-shift-a": "editor::SelectLargerSyntaxNode",
+      "shift-f12": "editor::FindAllReferences",
+      "alt-cmd-down": "editor::GoToDefinition",
+      "alt-shift-cmd-down": "editor::FindAllReferences",
+      "ctrl-.": "editor::GoToHunk",
+      "ctrl-,": "editor::GoToPrevHunk",
+      "ctrl-backspace": "editor::DeleteToPreviousWordStart",
+      "ctrl-delete": "editor::DeleteToNextWordEnd"
+    }
+  },
+  {
+    "context": "Editor && mode == full",
+    "bindings": {
+      "cmd-r": "outline::Toggle"
+    }
+  },
+  {
+    "context": "Pane",
+    "bindings": {
+      "f4": "search::SelectNextMatch",
+      "shift-f4": "search::SelectPrevMatch"
+    }
+  },
+  {
+    "context": "Workspace",
+    "bindings": {
+      "ctrl-`": "dock::FocusDock",
+      "cmd-k cmd-b": "workspace::ToggleLeftSidebar",
+      "cmd-t": "file_finder::Toggle",
+      "shift-cmd-r": "project_symbols::Toggle",
+      // Currently busted: https://github.com/zed-industries/feedback/issues/898
+      "ctrl-0": "project_panel::ToggleFocus"
+    }
+  },
+  {
+    "context": "Dock",
+    "bindings": {
+      "ctrl-`": "dock::HideDock"
+    }
+  }
+]

crates/install_cli/src/install_cli.rs 🔗

@@ -1,11 +1,10 @@
 use std::path::Path;
 
-use anyhow::{Result, anyhow};
-use gpui::{AsyncAppContext, actions};
+use anyhow::{anyhow, Result};
+use gpui::{actions, AsyncAppContext};
 use util::ResultExt;
 
-actions!(cli, [ Install ]);
-
+actions!(cli, [Install]);
 
 pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> {
     let cli_path = cx.platform().path_for_auxiliary_executable("cli")?;
@@ -53,4 +52,4 @@ pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> {
     } else {
         Err(anyhow!("error running osascript"))
     }
-}
+}

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;
@@ -45,6 +45,10 @@ impl KeymapFileContent {
         for path in ["keymaps/default.json", "keymaps/vim.json"] {
             Self::load(path, cx).unwrap();
         }
+
+        if let Some(base_keymap) = cx.global::<Settings>().base_keymap {
+            Self::load(base_keymap.asset_path(), cx).log_err();
+        }
     }
 
     pub fn load(asset_path: &str, cx: &mut MutableAppContext) -> Result<()> {

crates/settings/src/settings.rs 🔗

@@ -24,6 +24,7 @@ use tree_sitter::Query;
 use util::ResultExt as _;
 
 pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
+pub use watched_json::watch_files;
 
 #[derive(Clone)]
 pub struct Settings {
@@ -54,6 +55,24 @@ pub struct Settings {
     pub telemetry_defaults: TelemetrySettings,
     pub telemetry_overrides: TelemetrySettings,
     pub auto_update: bool,
+    pub base_keymap: Option<BaseKeymap>,
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+pub enum BaseKeymap {
+    JetBrains,
+    Sublime,
+    Atom,
+}
+
+impl BaseKeymap {
+    pub fn asset_path(&self) -> &str {
+        match self {
+            BaseKeymap::JetBrains => "keymaps/jetbrains.json",
+            BaseKeymap::Sublime => "keymaps/sublime_text.json",
+            BaseKeymap::Atom => "keymaps/atom.json",
+        }
+    }
 }
 
 #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
@@ -326,6 +345,8 @@ pub struct SettingsFileContent {
     pub telemetry: TelemetrySettings,
     #[serde(default)]
     pub auto_update: Option<bool>,
+    #[serde(default)]
+    pub base_keymap: Option<BaseKeymap>,
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -396,6 +417,7 @@ impl Settings {
             telemetry_defaults: defaults.telemetry,
             telemetry_overrides: Default::default(),
             auto_update: defaults.auto_update.unwrap(),
+            base_keymap: Default::default(),
         }
     }
 
@@ -433,6 +455,7 @@ impl Settings {
         merge(&mut self.vim_mode, data.vim_mode);
         merge(&mut self.autosave, data.autosave);
         merge(&mut self.default_dock_anchor, data.default_dock_anchor);
+        merge(&mut self.base_keymap, Some(data.base_keymap));
 
         // Ensure terminal font is loaded, so we can request it in terminal_element layout
         if let Some(terminal_font) = &data.terminal.font_family {
@@ -610,6 +633,7 @@ impl Settings {
             },
             telemetry_overrides: Default::default(),
             auto_update: true,
+            base_keymap: None,
         }
     }
 

crates/settings/src/watched_json.rs 🔗

@@ -62,7 +62,18 @@ where
     }
 }
 
-pub fn watch_settings_file(
+pub fn watch_files(
+    defaults: Settings,
+    settings_file: WatchedJsonFile<SettingsFileContent>,
+    theme_registry: Arc<ThemeRegistry>,
+    keymap_file: WatchedJsonFile<KeymapFileContent>,
+    cx: &mut MutableAppContext,
+) {
+    watch_settings_file(defaults, settings_file, theme_registry, cx);
+    watch_keymap_file(keymap_file, cx);
+}
+
+pub(crate) fn watch_settings_file(
     defaults: Settings,
     mut file: WatchedJsonFile<SettingsFileContent>,
     theme_registry: Arc<ThemeRegistry>,
@@ -77,13 +88,13 @@ pub fn watch_settings_file(
     .detach();
 }
 
-pub fn keymap_updated(content: KeymapFileContent, cx: &mut MutableAppContext) {
+fn keymap_updated(content: KeymapFileContent, cx: &mut MutableAppContext) {
     cx.clear_bindings();
     KeymapFileContent::load_defaults(cx);
     content.add_to_cx(cx).log_err();
 }
 
-pub fn settings_updated(
+fn settings_updated(
     defaults: &Settings,
     content: SettingsFileContent,
     theme_registry: &Arc<ThemeRegistry>,
@@ -95,10 +106,20 @@ pub fn settings_updated(
     cx.refresh_windows();
 }
 
-pub fn watch_keymap_file(mut file: WatchedJsonFile<KeymapFileContent>, cx: &mut MutableAppContext) {
+fn watch_keymap_file(mut file: WatchedJsonFile<KeymapFileContent>, cx: &mut MutableAppContext) {
     cx.spawn(|mut cx| async move {
+        let mut settings_subscription = None;
         while let Some(content) = file.0.recv().await {
-            cx.update(|cx| keymap_updated(content, cx));
+            cx.update(|cx| {
+                let old_base_keymap = cx.global::<Settings>().base_keymap;
+                keymap_updated(content.clone(), cx);
+                settings_subscription = Some(cx.observe_global::<Settings, _>(move |cx| {
+                    let settings = cx.global::<Settings>();
+                    if settings.base_keymap != old_base_keymap {
+                        keymap_updated(content.clone(), cx);
+                    }
+                }));
+            });
         }
     })
     .detach();

crates/theme_selector/src/theme_selector.rs 🔗

@@ -47,11 +47,7 @@ impl ThemeSelector {
         let mut theme_names = registry
             .list(**cx.default_global::<StaffMode>())
             .collect::<Vec<_>>();
-        theme_names.sort_unstable_by(|a, b| {
-            a.is_light
-                .cmp(&b.is_light)
-                .then(a.name.cmp(&b.name))
-        });
+        theme_names.sort_unstable_by(|a, b| a.is_light.cmp(&b.is_light).then(a.name.cmp(&b.name)));
         let matches = theme_names
             .iter()
             .map(|meta| StringMatch {

crates/welcome/src/welcome.rs 🔗

@@ -84,7 +84,8 @@ impl View for WelcomePage {
                 ])
                 .constrained()
                 .with_max_width(width)
-                .contained().with_uniform_padding(10.)
+                .contained()
+                .with_uniform_padding(10.)
                 .aligned()
                 .boxed(),
         )
@@ -174,7 +175,10 @@ impl WelcomePage {
                             }
                         })
                         .boxed(),
-                    Label::new(label, style.label.text.clone()).contained().with_style(style.label.container).boxed(),
+                    Label::new(label, style.label.text.clone())
+                        .contained()
+                        .with_style(style.label.container)
+                        .boxed(),
                 ])
                 .align_children_center()
                 .boxed()

crates/workspace/src/workspace.rs 🔗

@@ -17,7 +17,7 @@ mod toolbar;
 
 pub use smallvec;
 
-use anyhow::{anyhow, Result, Context};
+use anyhow::{anyhow, Context, Result};
 use call::ActiveCall;
 use client::{
     proto::{self, PeerId},
@@ -267,27 +267,30 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
                 })
         },
     );
-    
+
     cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
         cx.spawn(|workspace, mut cx| async move {
-            let err = install_cli::install_cli(&cx).await.context("Failed to create CLI symlink");
-                        
+            let err = install_cli::install_cli(&cx)
+                .await
+                .context("Failed to create CLI symlink");
+
             cx.update(|cx| {
                 workspace.update(cx, |workspace, cx| {
                     if matches!(err, Err(_)) {
                         err.notify_err(workspace, cx);
                     } else {
                         workspace.show_notification(1, cx, |cx| {
-                            cx.add_view(|_| MessageNotification::new_message("Successfully installed the 'zed' binary"))
+                            cx.add_view(|_| {
+                                MessageNotification::new_message(
+                                    "Successfully installed the `zed` binary",
+                                )
+                            })
                         });
                     }
                 })
             })
-        
-        }).detach();
-        
-        
-        
+        })
+        .detach();
     });
 
     let client = &app_state.client;

crates/zed/src/main.rs 🔗

@@ -38,7 +38,7 @@ use std::{
 use terminal_view::{get_working_directory, TerminalView};
 
 use fs::RealFs;
-use settings::watched_json::{watch_keymap_file, watch_settings_file, WatchedJsonFile};
+use settings::watched_json::WatchedJsonFile;
 use theme::ThemeRegistry;
 #[cfg(debug_assertions)]
 use util::StaffMode;
@@ -123,7 +123,14 @@ fn main() {
             fs.clone(),
         ));
 
-        watch_settings_file(default_settings, settings_file_content, themes.clone(), cx);
+        settings::watch_files(
+            default_settings,
+            settings_file_content,
+            themes.clone(),
+            keymap_file,
+            cx,
+        );
+
         if !stdout_is_a_pty() {
             upload_previous_panics(http.clone(), cx);
         }
@@ -136,8 +143,6 @@ fn main() {
         languages::init(languages.clone());
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
 
-        watch_keymap_file(keymap_file, cx);
-
         cx.set_global(client.clone());
 
         context_menu::init(cx);

crates/zed/src/zed.rs 🔗

@@ -21,7 +21,7 @@ use gpui::{
     geometry::vector::vec2f,
     impl_actions,
     platform::{WindowBounds, WindowOptions},
-    AssetSource,  Platform, PromptLevel, TitlebarOptions, ViewContext, WindowKind,
+    AssetSource, Platform, PromptLevel, TitlebarOptions, ViewContext, WindowKind,
 };
 use language::Rope;
 pub use lsp;
@@ -144,8 +144,12 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
         });
     });
     cx.add_global_action(move |_: &install_cli::Install, cx| {
-        cx.spawn(|cx| async move { install_cli::install_cli(&cx).await.context("error creating CLI symlink") })
-            .detach_and_log_err(cx);
+        cx.spawn(|cx| async move {
+            install_cli::install_cli(&cx)
+                .await
+                .context("error creating CLI symlink")
+        })
+        .detach_and_log_err(cx);
     });
     cx.add_action({
         let app_state = app_state.clone();