Load workspace and editor key bindings from a JSON file

Max Brunsfeld created

Change summary

crates/editor/src/editor.rs            | 240 ++++-----------------------
crates/gpui/src/app.rs                 |   3 
crates/gpui/src/keymap.rs              |  24 +-
crates/server/src/rpc.rs               |  11 
crates/workspace/src/menu.rs           |  18 -
crates/workspace/src/pane.rs           |  40 +--
crates/workspace/src/pane_group.rs     |   1 
crates/workspace/src/workspace.rs      |  27 ---
crates/zed/assets/keymaps/default.json | 199 +++++++++++++++++++++++
crates/zed/src/keymap_file.rs          |  38 ++++
crates/zed/src/main.rs                 |   2 
crates/zed/src/zed.rs                  |  16 
12 files changed, 326 insertions(+), 293 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -23,7 +23,6 @@ use gpui::{
     fonts::{self, HighlightStyle, TextStyle},
     geometry::vector::{vec2f, Vector2F},
     impl_actions, impl_internal_actions,
-    keymap::Binding,
     platform::CursorStyle,
     text_layout, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity,
     ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle,
@@ -81,7 +80,7 @@ pub struct Scroll(pub Vector2F);
 #[derive(Clone)]
 pub struct Select(pub SelectPhase);
 
-#[derive(Clone)]
+#[derive(Clone, Deserialize)]
 pub struct Input(pub String);
 
 #[derive(Clone, Deserialize)]
@@ -97,13 +96,22 @@ pub struct SelectToEndOfLine {
 }
 
 #[derive(Clone, Deserialize)]
-pub struct ToggleCodeActions(#[serde(default)] pub bool);
+pub struct ToggleCodeActions {
+    #[serde(default)]
+    pub deployed_from_indicator: bool,
+}
 
-#[derive(Clone, Deserialize)]
-pub struct ConfirmCompletion(#[serde(default)] pub Option<usize>);
+#[derive(Clone, Default, Deserialize)]
+pub struct ConfirmCompletion {
+    #[serde(default)]
+    pub item_ix: Option<usize>,
+}
 
-#[derive(Clone, Deserialize)]
-pub struct ConfirmCodeAction(#[serde(default)] pub Option<usize>);
+#[derive(Clone, Default, Deserialize)]
+pub struct ConfirmCodeAction {
+    #[serde(default)]
+    pub item_ix: Option<usize>,
+}
 
 actions!(
     editor,
@@ -185,6 +193,7 @@ actions!(
 impl_actions!(
     editor,
     [
+        Input,
         SelectNext,
         SelectToBeginningOfLine,
         SelectToEndOfLine,
@@ -194,7 +203,7 @@ impl_actions!(
     ]
 );
 
-impl_internal_actions!(editor, [Scroll, Select, Input]);
+impl_internal_actions!(editor, [Scroll, Select]);
 
 enum DocumentHighlightRead {}
 enum DocumentHighlightWrite {}
@@ -206,187 +215,6 @@ pub enum Direction {
 }
 
 pub fn init(cx: &mut MutableAppContext) {
-    cx.add_bindings(vec![
-        Binding::new("escape", Cancel, Some("Editor")),
-        Binding::new("backspace", Backspace, Some("Editor")),
-        Binding::new("ctrl-h", Backspace, Some("Editor")),
-        Binding::new("delete", Delete, Some("Editor")),
-        Binding::new("ctrl-d", Delete, Some("Editor")),
-        Binding::new("enter", Newline, Some("Editor && mode == full")),
-        Binding::new(
-            "alt-enter",
-            Input("\n".into()),
-            Some("Editor && mode == auto_height"),
-        ),
-        Binding::new(
-            "enter",
-            ConfirmCompletion(None),
-            Some("Editor && showing_completions"),
-        ),
-        Binding::new(
-            "enter",
-            ConfirmCodeAction(None),
-            Some("Editor && showing_code_actions"),
-        ),
-        Binding::new("enter", ConfirmRename, Some("Editor && renaming")),
-        Binding::new("tab", Tab, Some("Editor")),
-        Binding::new("shift-tab", TabPrev, Some("Editor")),
-        Binding::new(
-            "tab",
-            ConfirmCompletion(None),
-            Some("Editor && showing_completions"),
-        ),
-        Binding::new("cmd-[", Outdent, Some("Editor")),
-        Binding::new("cmd-]", Indent, Some("Editor")),
-        Binding::new("ctrl-shift-K", DeleteLine, Some("Editor")),
-        Binding::new("alt-backspace", DeleteToPreviousWordStart, Some("Editor")),
-        Binding::new("alt-h", DeleteToPreviousWordStart, Some("Editor")),
-        Binding::new(
-            "ctrl-alt-backspace",
-            DeleteToPreviousSubwordStart,
-            Some("Editor"),
-        ),
-        Binding::new("ctrl-alt-h", DeleteToPreviousSubwordStart, Some("Editor")),
-        Binding::new("alt-delete", DeleteToNextWordEnd, Some("Editor")),
-        Binding::new("alt-d", DeleteToNextWordEnd, Some("Editor")),
-        Binding::new("ctrl-alt-delete", DeleteToNextSubwordEnd, Some("Editor")),
-        Binding::new("ctrl-alt-d", DeleteToNextSubwordEnd, Some("Editor")),
-        Binding::new("cmd-backspace", DeleteToBeginningOfLine, Some("Editor")),
-        Binding::new("cmd-delete", DeleteToEndOfLine, Some("Editor")),
-        Binding::new("ctrl-k", CutToEndOfLine, Some("Editor")),
-        Binding::new("cmd-shift-D", DuplicateLine, Some("Editor")),
-        Binding::new("ctrl-cmd-up", MoveLineUp, Some("Editor")),
-        Binding::new("ctrl-cmd-down", MoveLineDown, Some("Editor")),
-        Binding::new("cmd-x", Cut, Some("Editor")),
-        Binding::new("cmd-c", Copy, Some("Editor")),
-        Binding::new("cmd-v", Paste, Some("Editor")),
-        Binding::new("cmd-z", Undo, Some("Editor")),
-        Binding::new("cmd-shift-Z", Redo, Some("Editor")),
-        Binding::new("up", MoveUp, Some("Editor")),
-        Binding::new("down", MoveDown, Some("Editor")),
-        Binding::new("left", MoveLeft, Some("Editor")),
-        Binding::new("right", MoveRight, Some("Editor")),
-        Binding::new("ctrl-p", MoveUp, Some("Editor")),
-        Binding::new("ctrl-n", MoveDown, Some("Editor")),
-        Binding::new("ctrl-b", MoveLeft, Some("Editor")),
-        Binding::new("ctrl-f", MoveRight, Some("Editor")),
-        Binding::new("alt-left", MoveToPreviousWordStart, Some("Editor")),
-        Binding::new("alt-b", MoveToPreviousWordStart, Some("Editor")),
-        Binding::new("ctrl-alt-left", MoveToPreviousSubwordStart, Some("Editor")),
-        Binding::new("ctrl-alt-b", MoveToPreviousSubwordStart, Some("Editor")),
-        Binding::new("alt-right", MoveToNextWordEnd, Some("Editor")),
-        Binding::new("alt-f", MoveToNextWordEnd, Some("Editor")),
-        Binding::new("ctrl-alt-right", MoveToNextSubwordEnd, Some("Editor")),
-        Binding::new("ctrl-alt-f", MoveToNextSubwordEnd, Some("Editor")),
-        Binding::new("cmd-left", MoveToBeginningOfLine, Some("Editor")),
-        Binding::new("ctrl-a", MoveToBeginningOfLine, Some("Editor")),
-        Binding::new("cmd-right", MoveToEndOfLine, Some("Editor")),
-        Binding::new("ctrl-e", MoveToEndOfLine, Some("Editor")),
-        Binding::new("cmd-up", MoveToBeginning, Some("Editor")),
-        Binding::new("cmd-down", MoveToEnd, Some("Editor")),
-        Binding::new("shift-up", SelectUp, Some("Editor")),
-        Binding::new("ctrl-shift-P", SelectUp, Some("Editor")),
-        Binding::new("shift-down", SelectDown, Some("Editor")),
-        Binding::new("ctrl-shift-N", SelectDown, Some("Editor")),
-        Binding::new("shift-left", SelectLeft, Some("Editor")),
-        Binding::new("ctrl-shift-B", SelectLeft, Some("Editor")),
-        Binding::new("shift-right", SelectRight, Some("Editor")),
-        Binding::new("ctrl-shift-F", SelectRight, Some("Editor")),
-        Binding::new("alt-shift-left", SelectToPreviousWordStart, Some("Editor")),
-        Binding::new("alt-shift-B", SelectToPreviousWordStart, Some("Editor")),
-        Binding::new(
-            "ctrl-alt-shift-left",
-            SelectToPreviousSubwordStart,
-            Some("Editor"),
-        ),
-        Binding::new(
-            "ctrl-alt-shift-B",
-            SelectToPreviousSubwordStart,
-            Some("Editor"),
-        ),
-        Binding::new("alt-shift-right", SelectToNextWordEnd, Some("Editor")),
-        Binding::new("alt-shift-F", SelectToNextWordEnd, Some("Editor")),
-        Binding::new(
-            "cmd-shift-left",
-            SelectToBeginningOfLine {
-                stop_at_soft_wraps: true,
-            },
-            Some("Editor"),
-        ),
-        Binding::new(
-            "ctrl-alt-shift-right",
-            SelectToNextSubwordEnd,
-            Some("Editor"),
-        ),
-        Binding::new("ctrl-alt-shift-F", SelectToNextSubwordEnd, Some("Editor")),
-        Binding::new(
-            "ctrl-shift-A",
-            SelectToBeginningOfLine {
-                stop_at_soft_wraps: true,
-            },
-            Some("Editor"),
-        ),
-        Binding::new(
-            "cmd-shift-right",
-            SelectToEndOfLine {
-                stop_at_soft_wraps: true,
-            },
-            Some("Editor"),
-        ),
-        Binding::new(
-            "ctrl-shift-E",
-            SelectToEndOfLine {
-                stop_at_soft_wraps: true,
-            },
-            Some("Editor"),
-        ),
-        Binding::new("cmd-shift-up", SelectToBeginning, Some("Editor")),
-        Binding::new("cmd-shift-down", SelectToEnd, Some("Editor")),
-        Binding::new("cmd-a", SelectAll, Some("Editor")),
-        Binding::new("cmd-l", SelectLine, Some("Editor")),
-        Binding::new("cmd-shift-L", SplitSelectionIntoLines, Some("Editor")),
-        Binding::new("cmd-alt-up", AddSelectionAbove, Some("Editor")),
-        Binding::new("cmd-ctrl-p", AddSelectionAbove, Some("Editor")),
-        Binding::new("cmd-alt-down", AddSelectionBelow, Some("Editor")),
-        Binding::new("cmd-ctrl-n", AddSelectionBelow, Some("Editor")),
-        Binding::new(
-            "cmd-d",
-            SelectNext {
-                replace_newest: false,
-            },
-            Some("Editor"),
-        ),
-        Binding::new(
-            "cmd-k cmd-d",
-            SelectNext {
-                replace_newest: true,
-            },
-            Some("Editor"),
-        ),
-        Binding::new("cmd-/", ToggleComments, Some("Editor")),
-        Binding::new("alt-up", SelectLargerSyntaxNode, Some("Editor")),
-        Binding::new("ctrl-w", SelectLargerSyntaxNode, Some("Editor")),
-        Binding::new("alt-down", SelectSmallerSyntaxNode, Some("Editor")),
-        Binding::new("ctrl-shift-W", SelectSmallerSyntaxNode, Some("Editor")),
-        Binding::new("cmd-u", UndoSelection, Some("Editor")),
-        Binding::new("cmd-shift-U", RedoSelection, Some("Editor")),
-        Binding::new("f8", GoToNextDiagnostic, Some("Editor")),
-        Binding::new("shift-f8", GoToPrevDiagnostic, Some("Editor")),
-        Binding::new("f2", Rename, Some("Editor")),
-        Binding::new("f12", GoToDefinition, Some("Editor")),
-        Binding::new("alt-shift-f12", FindAllReferences, Some("Editor")),
-        Binding::new("ctrl-m", MoveToEnclosingBracket, Some("Editor")),
-        Binding::new("pageup", PageUp, Some("Editor")),
-        Binding::new("pagedown", PageDown, Some("Editor")),
-        Binding::new("alt-cmd-[", Fold, Some("Editor")),
-        Binding::new("alt-cmd-]", UnfoldLines, Some("Editor")),
-        Binding::new("alt-cmd-f", FoldSelectedRanges, Some("Editor")),
-        Binding::new("ctrl-space", ShowCompletions, Some("Editor")),
-        Binding::new("cmd-.", ToggleCodeActions(false), Some("Editor")),
-        Binding::new("alt-enter", OpenExcerpts, Some("Editor")),
-        Binding::new("cmd-f10", RestartLanguageServer, Some("Editor")),
-    ]);
-
     cx.add_action(Editor::open_new);
     cx.add_action(|this: &mut Editor, action: &Scroll, cx| this.set_scroll_position(action.0, cx));
     cx.add_action(Editor::select);
@@ -396,6 +224,7 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(Editor::backspace);
     cx.add_action(Editor::delete);
     cx.add_action(Editor::tab);
+    cx.add_action(Editor::tab_prev);
     cx.add_action(Editor::indent);
     cx.add_action(Editor::outdent);
     cx.add_action(Editor::delete_line);
@@ -849,7 +678,9 @@ impl CompletionsMenu {
                     )
                     .with_cursor_style(CursorStyle::PointingHand)
                     .on_mouse_down(move |cx| {
-                        cx.dispatch_action(ConfirmCompletion(Some(item_ix)));
+                        cx.dispatch_action(ConfirmCompletion {
+                            item_ix: Some(item_ix),
+                        });
                     })
                     .boxed(),
                 );
@@ -975,7 +806,9 @@ impl CodeActionsMenu {
                         })
                         .with_cursor_style(CursorStyle::PointingHand)
                         .on_mouse_down(move |cx| {
-                            cx.dispatch_action(ConfirmCodeAction(Some(item_ix)));
+                            cx.dispatch_action(ConfirmCodeAction {
+                                item_ix: Some(item_ix),
+                            });
                         })
                         .boxed(),
                     );
@@ -2473,7 +2306,7 @@ impl Editor {
 
     pub fn confirm_completion(
         &mut self,
-        ConfirmCompletion(completion_ix): &ConfirmCompletion,
+        action: &ConfirmCompletion,
         cx: &mut ViewContext<Self>,
     ) -> Option<Task<Result<()>>> {
         use language::ToOffset as _;
@@ -2486,7 +2319,7 @@ impl Editor {
 
         let mat = completions_menu
             .matches
-            .get(completion_ix.unwrap_or(completions_menu.selected_item))?;
+            .get(action.item_ix.unwrap_or(completions_menu.selected_item))?;
         let buffer_handle = completions_menu.buffer;
         let completion = completions_menu.completions.get(mat.candidate_id)?;
 
@@ -2576,11 +2409,7 @@ impl Editor {
         }))
     }
 
-    pub fn toggle_code_actions(
-        &mut self,
-        &ToggleCodeActions(deployed_from_indicator): &ToggleCodeActions,
-        cx: &mut ViewContext<Self>,
-    ) {
+    pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
         if matches!(
             self.context_menu.as_ref(),
             Some(ContextMenu::CodeActions(_))
@@ -2590,6 +2419,7 @@ impl Editor {
             return;
         }
 
+        let deployed_from_indicator = action.deployed_from_indicator;
         let mut task = self.code_actions_task.take();
         cx.spawn_weak(|this, mut cx| async move {
             while let Some(prev_task) = task {
@@ -2624,7 +2454,7 @@ impl Editor {
 
     pub fn confirm_code_action(
         workspace: &mut Workspace,
-        ConfirmCodeAction(action_ix): &ConfirmCodeAction,
+        action: &ConfirmCodeAction,
         cx: &mut ViewContext<Workspace>,
     ) -> Option<Task<Result<()>>> {
         let editor = workspace.active_item(cx)?.act_as::<Editor>(cx)?;
@@ -2635,7 +2465,7 @@ impl Editor {
         } else {
             return None;
         };
-        let action_ix = action_ix.unwrap_or(actions_menu.selected_item);
+        let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
         let action = actions_menu.actions.get(action_ix)?.clone();
         let title = action.lsp_action.title.clone();
         let buffer = actions_menu.buffer;
@@ -2862,7 +2692,9 @@ impl Editor {
                 .with_cursor_style(CursorStyle::PointingHand)
                 .with_padding(Padding::uniform(3.))
                 .on_mouse_down(|cx| {
-                    cx.dispatch_action(ToggleCodeActions(true));
+                    cx.dispatch_action(ToggleCodeActions {
+                        deployed_from_indicator: true,
+                    });
                 })
                 .boxed(),
             )
@@ -4558,7 +4390,7 @@ impl Editor {
         self.go_to_diagnostic(Direction::Next, cx)
     }
 
-    fn go_to_prev_diagnostic(&mut self, _: &GoToNextDiagnostic, cx: &mut ViewContext<Self>) {
+    fn go_to_prev_diagnostic(&mut self, _: &GoToPrevDiagnostic, cx: &mut ViewContext<Self>) {
         self.go_to_diagnostic(Direction::Prev, cx)
     }
 
@@ -9394,7 +9226,7 @@ mod tests {
         let apply_additional_edits = editor.update(cx, |editor, cx| {
             editor.move_down(&MoveDown, cx);
             let apply_additional_edits = editor
-                .confirm_completion(&ConfirmCompletion(None), cx)
+                .confirm_completion(&ConfirmCompletion::default(), cx)
                 .unwrap();
             assert_eq!(
                 editor.text(cx),
@@ -9477,7 +9309,7 @@ mod tests {
 
         let apply_additional_edits = editor.update(cx, |editor, cx| {
             let apply_additional_edits = editor
-                .confirm_completion(&ConfirmCompletion(None), cx)
+                .confirm_completion(&ConfirmCompletion::default(), cx)
                 .unwrap();
             assert_eq!(
                 editor.text(cx),

crates/gpui/src/app.rs 🔗

@@ -10,7 +10,7 @@ use crate::{
     AssetCache, AssetSource, ClipboardItem, FontCache, PathPromptOptions, TextLayoutCache,
 };
 pub use action::*;
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, Context, Result};
 use collections::btree_map;
 use keymap::MatchResult;
 use lazy_static::lazy_static;
@@ -870,6 +870,7 @@ impl MutableAppContext {
             .get(name)
             .ok_or_else(|| anyhow!("unknown action {}", name))?;
         callback(argument.unwrap_or("{}"))
+            .with_context(|| format!("invalid data for action {}", name))
     }
 
     pub fn add_action<A, V, F>(&mut self, handler: F)

crates/gpui/src/keymap.rs 🔗

@@ -1,5 +1,5 @@
 use crate::Action;
-use anyhow::anyhow;
+use anyhow::{anyhow, Result};
 use std::{
     any::Any,
     collections::{HashMap, HashSet},
@@ -168,20 +168,26 @@ impl Keymap {
 
 impl Binding {
     pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
+        Self::load(keystrokes, Box::new(action), context).unwrap()
+    }
+
+    pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
         let context = if let Some(context) = context {
-            Some(ContextPredicate::parse(context).unwrap())
+            Some(ContextPredicate::parse(context)?)
         } else {
             None
         };
 
-        Self {
-            keystrokes: keystrokes
-                .split_whitespace()
-                .map(|key| Keystroke::parse(key).unwrap())
-                .collect(),
-            action: Box::new(action),
+        let keystrokes = keystrokes
+            .split_whitespace()
+            .map(|key| Keystroke::parse(key))
+            .collect::<Result<_>>()?;
+
+        Ok(Self {
+            keystrokes,
+            action,
             context,
-        }
+        })
     }
 }
 

crates/server/src/rpc.rs 🔗

@@ -2419,7 +2419,7 @@ mod tests {
             .condition(&cx_b, |editor, _| editor.context_menu_visible())
             .await;
         editor_b.update(cx_b, |editor, cx| {
-            editor.confirm_completion(&ConfirmCompletion(Some(0)), cx);
+            editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
             assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
         });
 
@@ -3608,7 +3608,12 @@ mod tests {
 
         // Toggle code actions and wait for them to display.
         editor_b.update(cx_b, |editor, cx| {
-            editor.toggle_code_actions(&ToggleCodeActions(false), cx);
+            editor.toggle_code_actions(
+                &ToggleCodeActions {
+                    deployed_from_indicator: false,
+                },
+                cx,
+            );
         });
         editor_b
             .condition(&cx_b, |editor, _| editor.context_menu_visible())
@@ -3619,7 +3624,7 @@ mod tests {
         // Confirming the code action will trigger a resolve request.
         let confirm_action = workspace_b
             .update(cx_b, |workspace, cx| {
-                Editor::confirm_code_action(workspace, &ConfirmCodeAction(Some(0)), cx)
+                Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
             })
             .unwrap();
         fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(

crates/workspace/src/menu.rs 🔗

@@ -1,18 +1,4 @@
-use gpui::{actions, keymap::Binding, MutableAppContext};
-
-actions!(
+gpui::actions!(
     menu,
-    [Confirm, SelectPrev, SelectNext, SelectFirst, SelectLast,]
+    [Confirm, SelectPrev, SelectNext, SelectFirst, SelectLast]
 );
-
-pub fn init(cx: &mut MutableAppContext) {
-    cx.add_bindings([
-        Binding::new("up", SelectPrev, Some("menu")),
-        Binding::new("ctrl-p", SelectPrev, Some("menu")),
-        Binding::new("down", SelectNext, Some("menu")),
-        Binding::new("ctrl-n", SelectNext, Some("menu")),
-        Binding::new("cmd-up", SelectFirst, Some("menu")),
-        Binding::new("cmd-down", SelectLast, Some("menu")),
-        Binding::new("enter", Confirm, Some("menu")),
-    ]);
-}

crates/workspace/src/pane.rs 🔗

@@ -8,7 +8,6 @@ use gpui::{
     elements::*,
     geometry::{rect::RectF, vector::vec2f},
     impl_actions, impl_internal_actions,
-    keymap::Binding,
     platform::{CursorStyle, NavigationDirection},
     AppContext, Entity, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View,
     ViewContext, ViewHandle, WeakViewHandle,
@@ -41,14 +40,20 @@ pub struct CloseItem {
 #[derive(Clone, Deserialize)]
 pub struct ActivateItem(pub usize);
 
-#[derive(Clone)]
-pub struct GoBack(pub Option<WeakViewHandle<Pane>>);
+#[derive(Clone, Deserialize)]
+pub struct GoBack {
+    #[serde(skip_deserializing)]
+    pub pane: Option<WeakViewHandle<Pane>>,
+}
 
-#[derive(Clone)]
-pub struct GoForward(pub Option<WeakViewHandle<Pane>>);
+#[derive(Clone, Deserialize)]
+pub struct GoForward {
+    #[serde(skip_deserializing)]
+    pub pane: Option<WeakViewHandle<Pane>>,
+}
 
-impl_actions!(pane, [Split]);
-impl_internal_actions!(pane, [CloseItem, ActivateItem, GoBack, GoForward]);
+impl_actions!(pane, [Split, GoBack, GoForward]);
+impl_internal_actions!(pane, [CloseItem, ActivateItem]);
 
 const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
 
@@ -75,7 +80,7 @@ pub fn init(cx: &mut MutableAppContext) {
         Pane::go_back(
             workspace,
             action
-                .0
+                .pane
                 .as_ref()
                 .and_then(|weak_handle| weak_handle.upgrade(cx)),
             cx,
@@ -86,26 +91,13 @@ pub fn init(cx: &mut MutableAppContext) {
         Pane::go_forward(
             workspace,
             action
-                .0
+                .pane
                 .as_ref()
                 .and_then(|weak_handle| weak_handle.upgrade(cx)),
             cx,
         )
         .detach();
     });
-
-    cx.add_bindings(vec![
-        Binding::new("shift-cmd-{", ActivatePrevItem, Some("Pane")),
-        Binding::new("shift-cmd-}", ActivateNextItem, Some("Pane")),
-        Binding::new("cmd-w", CloseActiveItem, Some("Pane")),
-        Binding::new("alt-cmd-w", CloseInactiveItems, Some("Pane")),
-        Binding::new("cmd-k up", Split(SplitDirection::Up), Some("Pane")),
-        Binding::new("cmd-k down", Split(SplitDirection::Down), Some("Pane")),
-        Binding::new("cmd-k left", Split(SplitDirection::Left), Some("Pane")),
-        Binding::new("cmd-k right", Split(SplitDirection::Right), Some("Pane")),
-        Binding::new("ctrl--", GoBack(None), Some("Pane")),
-        Binding::new("shift-ctrl-_", GoForward(None), Some("Pane")),
-    ]);
 }
 
 pub enum Event {
@@ -815,8 +807,8 @@ impl View for Pane {
         .on_navigate_mouse_down(move |direction, cx| {
             let this = this.clone();
             match direction {
-                NavigationDirection::Back => cx.dispatch_action(GoBack(Some(this))),
-                NavigationDirection::Forward => cx.dispatch_action(GoForward(Some(this))),
+                NavigationDirection::Back => cx.dispatch_action(GoBack { pane: Some(this) }),
+                NavigationDirection::Forward => cx.dispatch_action(GoForward { pane: Some(this) }),
             }
 
             true

crates/workspace/src/pane_group.rs 🔗

@@ -256,7 +256,6 @@ impl PaneAxis {
 }
 
 #[derive(Clone, Copy, Debug, Deserialize)]
-#[serde(rename_all = "snake_case")]
 pub enum SplitDirection {
     Up,
     Down,

crates/workspace/src/workspace.rs 🔗

@@ -19,7 +19,6 @@ use gpui::{
     geometry::{rect::RectF, vector::vec2f, PathBuilder},
     impl_internal_actions,
     json::{self, to_string_pretty, ToJson},
-    keymap::Binding,
     platform::{CursorStyle, WindowOptions},
     AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Border, ClipboardItem, Entity,
     ImageData, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task,
@@ -32,7 +31,7 @@ pub use pane_group::*;
 use postage::prelude::Stream;
 use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, Worktree};
 use settings::Settings;
-use sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItemFocus};
+use sidebar::{Side, Sidebar, ToggleSidebarItem, ToggleSidebarItemFocus};
 use status_bar::StatusBar;
 pub use status_bar::StatusItemView;
 use std::{
@@ -108,7 +107,6 @@ impl_internal_actions!(
 
 pub fn init(client: &Arc<Client>, cx: &mut MutableAppContext) {
     pane::init(cx);
-    menu::init(cx);
 
     cx.add_global_action(open);
     cx.add_global_action(move |action: &OpenPaths, cx: &mut MutableAppContext| {
@@ -144,29 +142,6 @@ pub fn init(client: &Arc<Client>, cx: &mut MutableAppContext) {
     cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
         workspace.activate_next_pane(cx)
     });
-    cx.add_bindings(vec![
-        Binding::new("ctrl-alt-cmd-f", FollowNextCollaborator, None),
-        Binding::new("cmd-s", Save, None),
-        Binding::new("cmd-alt-i", DebugElements, None),
-        Binding::new("cmd-k cmd-left", ActivatePreviousPane, None),
-        Binding::new("cmd-k cmd-right", ActivateNextPane, None),
-        Binding::new(
-            "cmd-shift-!",
-            ToggleSidebarItem(SidebarItemId {
-                side: Side::Left,
-                item_index: 0,
-            }),
-            None,
-        ),
-        Binding::new(
-            "cmd-1",
-            ToggleSidebarItemFocus(SidebarItemId {
-                side: Side::Left,
-                item_index: 0,
-            }),
-            None,
-        ),
-    ]);
 
     client.add_view_request_handler(Workspace::handle_follow);
     client.add_view_message_handler(Workspace::handle_unfollow);

crates/zed/assets/keymaps/default.json 🔗

@@ -0,0 +1,199 @@
+{
+    "": {
+        "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
+        "cmd-s": "workspace::Save",
+        "cmd-alt-i": "workspace::DebugElements",
+        "cmd-k cmd-left": "workspace::ActivatePreviousPane",
+        "cmd-k cmd-right": "workspace::ActivateNextPane",
+        "cmd-=": "zed::IncreaseBufferFontSize",
+        "cmd--": "zed::DecreaseBufferFontSize",
+        "cmd-,": "zed::OpenSettings"
+    },
+    "menu": {
+        "up": "menu::SelectPrev",
+        "ctrl-p": "menu::SelectPrev",
+        "down": "menu::SelectNext",
+        "ctrl-n": "menu::SelectNext",
+        "cmd-up": "menu::SelectFirst",
+        "cmd-down": "menu::SelectLast",
+        "enter": "menu::Confirm"
+    },
+    "Pane": {
+        "shift-cmd-{": "pane::ActivatePrevItem",
+        "shift-cmd-}": "pane::ActivateNextItem",
+        "cmd-w": "pane::CloseActiveItem",
+        "alt-cmd-w": "pane::CloseInactiveItems",
+        "ctrl--": "pane::GoBack",
+        "shift-ctrl-_": "pane::GoForward",
+        "cmd-k up": [
+            "pane::Split",
+            "Up"
+        ],
+        "cmd-k down": [
+            "pane::Split",
+            "Down"
+        ],
+        "cmd-k left": [
+            "pane::Split",
+            "Left"
+        ],
+        "cmd-k right": [
+            "pane::Split",
+            "Right"
+        ]
+    },
+    "Editor": {
+        "escape": "editor::Cancel",
+        "backspace": "editor::Backspace",
+        "ctrl-h": "editor::Backspace",
+        "delete": "editor::Delete",
+        "ctrl-d": "editor::Delete",
+        "tab": "editor::Tab",
+        "shift-tab": "editor::TabPrev",
+        "cmd-[": "editor::Outdent",
+        "cmd-]": "editor::Indent",
+        "ctrl-shift-K": "editor::DeleteLine",
+        "alt-backspace": "editor::DeleteToPreviousWordStart",
+        "alt-h": "editor::DeleteToPreviousWordStart",
+        "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
+        "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
+        "alt-delete": "editor::DeleteToNextWordEnd",
+        "alt-d": "editor::DeleteToNextWordEnd",
+        "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd",
+        "ctrl-alt-d": "editor::DeleteToNextSubwordEnd",
+        "cmd-backspace": "editor::DeleteToBeginningOfLine",
+        "cmd-delete": "editor::DeleteToEndOfLine",
+        "ctrl-k": "editor::CutToEndOfLine",
+        "cmd-shift-D": "editor::DuplicateLine",
+        "ctrl-cmd-up": "editor::MoveLineUp",
+        "ctrl-cmd-down": "editor::MoveLineDown",
+        "cmd-x": "editor::Cut",
+        "cmd-c": "editor::Copy",
+        "cmd-v": "editor::Paste",
+        "cmd-z": "editor::Undo",
+        "cmd-shift-Z": "editor::Redo",
+        "up": "editor::MoveUp",
+        "down": "editor::MoveDown",
+        "left": "editor::MoveLeft",
+        "right": "editor::MoveRight",
+        "ctrl-p": "editor::MoveUp",
+        "ctrl-n": "editor::MoveDown",
+        "ctrl-b": "editor::MoveLeft",
+        "ctrl-f": "editor::MoveRight",
+        "alt-left": "editor::MoveToPreviousWordStart",
+        "alt-b": "editor::MoveToPreviousWordStart",
+        "ctrl-alt-left": "editor::MoveToPreviousSubwordStart",
+        "ctrl-alt-b": "editor::MoveToPreviousSubwordStart",
+        "alt-right": "editor::MoveToNextWordEnd",
+        "alt-f": "editor::MoveToNextWordEnd",
+        "ctrl-alt-right": "editor::MoveToNextSubwordEnd",
+        "ctrl-alt-f": "editor::MoveToNextSubwordEnd",
+        "cmd-left": "editor::MoveToBeginningOfLine",
+        "ctrl-a": "editor::MoveToBeginningOfLine",
+        "cmd-right": "editor::MoveToEndOfLine",
+        "ctrl-e": "editor::MoveToEndOfLine",
+        "cmd-up": "editor::MoveToBeginning",
+        "cmd-down": "editor::MoveToEnd",
+        "shift-up": "editor::SelectUp",
+        "ctrl-shift-P": "editor::SelectUp",
+        "shift-down": "editor::SelectDown",
+        "ctrl-shift-N": "editor::SelectDown",
+        "shift-left": "editor::SelectLeft",
+        "ctrl-shift-B": "editor::SelectLeft",
+        "shift-right": "editor::SelectRight",
+        "ctrl-shift-F": "editor::SelectRight",
+        "alt-shift-left": "editor::SelectToPreviousWordStart",
+        "alt-shift-B": "editor::SelectToPreviousWordStart",
+        "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart",
+        "ctrl-alt-shift-B": "editor::SelectToPreviousSubwordStart",
+        "alt-shift-right": "editor::SelectToNextWordEnd",
+        "alt-shift-F": "editor::SelectToNextWordEnd",
+        "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd",
+        "cmd-shift-up": "editor::SelectToBeginning",
+        "cmd-shift-down": "editor::SelectToEnd",
+        "cmd-a": "editor::SelectAll",
+        "cmd-l": "editor::SelectLine",
+        "cmd-shift-L": "editor::SplitSelectionIntoLines",
+        "cmd-alt-up": "editor::AddSelectionAbove",
+        "cmd-ctrl-p": "editor::AddSelectionAbove",
+        "cmd-alt-down": "editor::AddSelectionBelow",
+        "cmd-ctrl-n": "editor::AddSelectionBelow",
+        "ctrl-alt-shift-F": "editor::SelectToNextSubwordEnd",
+        "cmd-shift-left": [
+            "editor::SelectToBeginningOfLine",
+            {
+                "stop_at_soft_wraps": true
+            }
+        ],
+        "ctrl-shift-A": [
+            "editor::SelectToBeginningOfLine",
+            {
+                "stop_at_soft_wraps": true
+            }
+        ],
+        "cmd-shift-right": [
+            "editor::SelectToEndOfLine",
+            {
+                "stop_at_soft_wraps": true
+            }
+        ],
+        "ctrl-shift-E": [
+            "editor::SelectToEndOfLine",
+            {
+                "stop_at_soft_wraps": true
+            }
+        ],
+        "cmd-d": [
+            "editor::SelectNext",
+            {
+                "replace_newest": false
+            }
+        ],
+        "cmd-k cmd-d": [
+            "editor::SelectNext",
+            {
+                "replace_newest": true
+            }
+        ],
+        "cmd-/": "editor::ToggleComments",
+        "alt-up": "editor::SelectLargerSyntaxNode",
+        "ctrl-w": "editor::SelectLargerSyntaxNode",
+        "alt-down": "editor::SelectSmallerSyntaxNode",
+        "ctrl-shift-W": "editor::SelectSmallerSyntaxNode",
+        "cmd-u": "editor::UndoSelection",
+        "cmd-shift-U": "editor::RedoSelection",
+        "f8": "editor::GoToNextDiagnostic",
+        "shift-f8": "editor::GoToPrevDiagnostic",
+        "f2": "editor::Rename",
+        "f12": "editor::GoToDefinition",
+        "alt-shift-f12": "editor::FindAllReferences",
+        "ctrl-m": "editor::MoveToEnclosingBracket",
+        "pageup": "editor::PageUp",
+        "pagedown": "editor::PageDown",
+        "alt-cmd-[": "editor::Fold",
+        "alt-cmd-]": "editor::UnfoldLines",
+        "alt-cmd-f": "editor::FoldSelectedRanges",
+        "ctrl-space": "editor::ShowCompletions",
+        "cmd-.": "editor::ToggleCodeActions",
+        "alt-enter": "editor::OpenExcerpts",
+        "cmd-f10": "editor::RestartLanguageServer"
+    },
+    "Editor && renaming": {
+        "enter": "editor::ConfirmRename"
+    },
+    "Editor && showing_completions": {
+        "enter": "editor::ConfirmCompletion"
+    },
+    "Editor && showing_code_actions": {
+        "enter": "editor::ConfirmCodeAction"
+    },
+    "Editor && mode == full": {
+        "enter": "editor::Newline"
+    },
+    "Editor && mode == auto_height": {
+        "alt-enter": [
+            "editor::Input",
+            "\n"
+        ]
+    }
+}

crates/zed/src/keymap_file.rs 🔗

@@ -0,0 +1,38 @@
+use anyhow::Result;
+use collections::BTreeMap;
+use gpui::{keymap::Binding, MutableAppContext};
+use serde::Deserialize;
+use serde_json::value::RawValue;
+
+#[derive(Deserialize)]
+struct ActionWithData<'a>(#[serde(borrow)] &'a str, #[serde(borrow)] &'a RawValue);
+type ActionSetsByContext<'a> = BTreeMap<&'a str, ActionsByKeystroke<'a>>;
+type ActionsByKeystroke<'a> = BTreeMap<&'a str, &'a RawValue>;
+
+pub fn load_keymap(cx: &mut MutableAppContext, content: &str) -> Result<()> {
+    let actions: ActionSetsByContext = serde_json::from_str(content)?;
+    for (context, actions) in actions {
+        let context = if context.is_empty() {
+            None
+        } else {
+            Some(context)
+        };
+        cx.add_bindings(
+            actions
+                .into_iter()
+                .map(|(keystroke, action)| {
+                    let action = action.get();
+                    let action = if action.starts_with('[') {
+                        let ActionWithData(name, data) = serde_json::from_str(action)?;
+                        cx.deserialize_action(name, Some(data.get()))
+                    } else {
+                        let name = serde_json::from_str(action)?;
+                        cx.deserialize_action(name, None)
+                    }?;
+                    Binding::load(keystroke, action, context)
+                })
+                .collect::<Result<Vec<_>>>()?,
+        )
+    }
+    Ok(())
+}

crates/zed/src/main.rs 🔗

@@ -145,8 +145,8 @@ fn main() {
             build_workspace: &build_workspace,
         });
         journal::init(app_state.clone(), cx);
-        zed::init(&app_state, cx);
         theme_selector::init(cx);
+        zed::init(&app_state, cx);
 
         cx.set_menus(menus::menus(&app_state.clone()));
 

crates/zed/src/zed.rs 🔗

@@ -1,10 +1,12 @@
 pub mod assets;
+mod keymap_file;
 pub mod languages;
 pub mod menus;
 pub mod settings_file;
 #[cfg(any(test, feature = "test-support"))]
 pub mod test;
 
+use assets::Assets;
 use breadcrumbs::Breadcrumbs;
 use chat_panel::ChatPanel;
 pub use client;
@@ -14,7 +16,6 @@ pub use editor;
 use gpui::{
     actions,
     geometry::vector::vec2f,
-    keymap::Binding,
     platform::{WindowBounds, WindowOptions},
     ModelHandle, ViewContext,
 };
@@ -104,11 +105,11 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
 
     workspace::lsp_status::init(cx);
 
-    cx.add_bindings(vec![
-        Binding::new("cmd-=", IncreaseBufferFontSize, None),
-        Binding::new("cmd--", DecreaseBufferFontSize, None),
-        Binding::new("cmd-,", OpenSettings, None),
-    ])
+    keymap_file::load_keymap(
+        cx,
+        std::str::from_utf8(Assets::get("keymaps/default.json").unwrap().data.as_ref()).unwrap(),
+    )
+    .unwrap();
 }
 
 pub fn build_workspace(
@@ -208,9 +209,8 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) {
 
 #[cfg(test)]
 mod tests {
-    use crate::assets::Assets;
-
     use super::*;
+    use crate::assets::Assets;
     use editor::{DisplayPoint, Editor};
     use gpui::{AssetSource, MutableAppContext, TestAppContext, ViewHandle};
     use project::{Fs, ProjectPath};