Merge pull request #875 from zed-industries/keymap-improvements

Max Brunsfeld created

Keymap improvements

Change summary

Cargo.lock                                   |  20 
assets/keymaps/default.json                  | 573 ++++++++++++---------
assets/keymaps/vim.json                      | 192 ++++---
crates/chat_panel/src/chat_panel.rs          |   5 
crates/go_to_line/src/go_to_line.rs          |  12 
crates/gpui/src/app.rs                       |   4 
crates/language/src/language.rs              |   4 
crates/project/src/project.rs                |  21 
crates/search/src/project_search.rs          |   8 
crates/settings/Cargo.toml                   |   1 
crates/settings/src/keymap_file.rs           |  82 ++
crates/settings/src/settings.rs              | 178 +++---
crates/vim/src/vim_test_context.rs           |   2 
crates/zed/Cargo.toml                        |   2 
crates/zed/src/languages/json.rs             |   8 
crates/zed/src/languages/json/config.toml    |   1 
crates/zed/src/languages/json/highlights.scm |   2 
crates/zed/src/main.rs                       |   4 
crates/zed/src/settings_file.rs              |  11 
crates/zed/src/zed.rs                        |  95 ++-
20 files changed, 732 insertions(+), 493 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2572,6 +2572,12 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "json_comments"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41ee439ee368ba4a77ac70d04f14015415af8600d6c894dc1f11bd79758c57d5"
+
 [[package]]
 name = "json_env_logger"
 version = "0.1.1"
@@ -2643,7 +2649,7 @@ dependencies = [
  "text",
  "theme",
  "tree-sitter",
- "tree-sitter-json",
+ "tree-sitter-json 0.19.0",
  "tree-sitter-rust",
  "unindent",
  "util",
@@ -4304,6 +4310,7 @@ dependencies = [
  "assets",
  "collections",
  "gpui",
+ "json_comments",
  "schemars",
  "serde",
  "serde_json",
@@ -5193,6 +5200,15 @@ dependencies = [
  "tree-sitter",
 ]
 
+[[package]]
+name = "tree-sitter-json"
+version = "0.20.0"
+source = "git+https://github.com/tree-sitter/tree-sitter-json?rev=137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8#137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
 [[package]]
 name = "tree-sitter-markdown"
 version = "0.0.1"
@@ -5837,7 +5853,7 @@ dependencies = [
  "toml",
  "tree-sitter",
  "tree-sitter-c",
- "tree-sitter-json",
+ "tree-sitter-json 0.20.0",
  "tree-sitter-markdown",
  "tree-sitter-rust",
  "tree-sitter-typescript",

assets/keymaps/default.json 🔗

@@ -1,264 +1,339 @@
-{
-    "*": {
-        "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
-        "cmd-s": "workspace::Save",
-        "cmd-alt-i": "zed::DebugElements",
-        "cmd-k cmd-left": "workspace::ActivatePreviousPane",
-        "cmd-k cmd-right": "workspace::ActivateNextPane",
-        "cmd-=": "zed::IncreaseBufferFontSize",
-        "cmd--": "zed::DecreaseBufferFontSize",
-        "cmd-,": "zed::OpenSettings"
+[
+    // Standard macOS bindings
+    {
+        "bindings": {
+            "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",
+            "escape": "menu::Cancel",
+            "ctrl-c": "menu::Cancel",
+            "shift-cmd-{": "pane::ActivatePrevItem",
+            "shift-cmd-}": "pane::ActivateNextItem",
+            "cmd-w": "pane::CloseActiveItem",
+            "alt-cmd-w": "pane::CloseInactiveItems",
+            "cmd-s": "workspace::Save",
+            "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",
-        "escape": "menu::Cancel"
+    {
+        "context": "Editor",
+        "bindings": {
+            "escape": "editor::Cancel",
+            "backspace": "editor::Backspace",
+            "ctrl-h": "editor::Backspace",
+            "delete": "editor::Delete",
+            "ctrl-d": "editor::Delete",
+            "tab": "editor::Tab",
+            "shift-tab": "editor::TabPrev",
+            "ctrl-k": "editor::CutToEndOfLine",
+            "cmd-backspace": "editor::DeleteToBeginningOfLine",
+            "cmd-delete": "editor::DeleteToEndOfLine",
+            "alt-backspace": "editor::DeleteToPreviousWordStart",
+            "alt-delete": "editor::DeleteToNextWordEnd",
+            "alt-h": "editor::DeleteToPreviousWordStart",
+            "alt-d": "editor::DeleteToNextWordEnd",
+            "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",
+            "alt-right": "editor::MoveToNextWordEnd",
+            "alt-f": "editor::MoveToNextWordEnd",
+            "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",
+            "alt-shift-right": "editor::SelectToNextWordEnd",
+            "alt-shift-F": "editor::SelectToNextWordEnd",
+            "cmd-shift-up": "editor::SelectToBeginning",
+            "cmd-shift-down": "editor::SelectToEnd",
+            "cmd-a": "editor::SelectAll",
+            "cmd-l": "editor::SelectLine",
+            "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
+                }
+            ],
+            "pageup": "editor::PageUp",
+            "pagedown": "editor::PageDown"
+        }
     },
-    "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"
-        ],
-        "cmd-shift-F": "project_search::ToggleFocus",
-        "cmd-f": "project_search::ToggleFocus",
-        "cmd-g": "search::SelectNextMatch",
-        "cmd-shift-G": "search::SelectPrevMatch"
+    {
+        "context": "Editor && mode == full",
+        "bindings": {
+            "enter": "editor::Newline",
+            "cmd-f": [
+                "buffer_search::Deploy",
+                {
+                    "focus": true
+                }
+            ],
+            "cmd-e": [
+                "buffer_search::Deploy",
+                {
+                    "focus": false
+                }
+            ]
+        }
     },
-    "Workspace": {
-        "cmd-shift-F": "project_search::Deploy",
-        "cmd-k cmd-t": "theme_selector::Toggle",
-        "cmd-k t": "theme_selector::Reload",
-        "cmd-t": "project_symbols::Toggle",
-        "cmd-p": "file_finder::Toggle",
-        "cmd-shift-P": "command_palette::Toggle",
-        "alt-shift-D": "diagnostics::Deploy",
-        "ctrl-alt-cmd-j": "journal::NewJournalEntry",
-        "cmd-1": [
-            "workspace::ToggleSidebarItemFocus",
-            {
-                "side": "Left",
-                "item_index": 0
-            }
-        ],
-        "cmd-shift-!": [
-            "workspace::ToggleSidebarItem",
-            {
-                "side": "Left",
-                "item_index": 0
-            }
-        ]
+    {
+        "context": "Editor && mode == auto_height",
+        "bindings": {
+            "alt-enter": [
+                "editor::Input",
+                "\n"
+            ]
+        }
     },
-    "ProjectSearchBar": {
-        "enter": "project_search::Search",
-        "cmd-enter": "project_search::SearchInNew"
+    {
+        "context": "Pane",
+        "bindings": {
+            "cmd-f": "project_search::ToggleFocus",
+            "cmd-g": "search::SelectNextMatch",
+            "cmd-shift-G": "search::SelectPrevMatch"
+        }
     },
-    "BufferSearchBar": {
-        "escape": "buffer_search::Dismiss",
-        "cmd-f": "buffer_search::FocusEditor",
-        "enter": "search::SelectNextMatch",
-        "shift-enter": "search::SelectPrevMatch"
+    {
+        "context": "BufferSearchBar",
+        "bindings": {
+            "escape": "buffer_search::Dismiss",
+            "cmd-f": "buffer_search::FocusEditor",
+            "enter": "search::SelectNextMatch",
+            "shift-enter": "search::SelectPrevMatch"
+        }
     },
-    "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"
+    // Bindings from VS Code
+    {
+        "context": "Editor",
+        "bindings": {
+            "cmd-[": "editor::Outdent",
+            "cmd-]": "editor::Indent",
+            "cmd-alt-up": "editor::AddSelectionAbove",
+            "cmd-ctrl-p": "editor::AddSelectionAbove",
+            "cmd-alt-down": "editor::AddSelectionBelow",
+            "cmd-ctrl-n": "editor::AddSelectionBelow",
+            "cmd-d": [
+                "editor::SelectNext",
+                {
+                    "replace_newest": false
+                }
+            ],
+            "cmd-k cmd-d": [
+                "editor::SelectNext",
+                {
+                    "replace_newest": true
+                }
+            ],
+            "cmd-/": "editor::ToggleComments",
+            "alt-up": "editor::SelectLargerSyntaxNode",
+            "alt-down": "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",
+            "alt-cmd-[": "editor::Fold",
+            "alt-cmd-]": "editor::UnfoldLines",
+            "ctrl-space": "editor::ShowCompletions",
+            "cmd-.": "editor::ToggleCodeActions"
+        }
     },
-    "Editor && renaming": {
-        "enter": "editor::ConfirmRename"
+    {
+        "context": "Editor && mode == full",
+        "bindings": {
+            "cmd-shift-O": "outline::Toggle",
+            "ctrl-g": "go_to_line::Toggle"
+        }
     },
-    "Editor && showing_completions": {
-        "enter": "editor::ConfirmCompletion",
-        "tab": "editor::ConfirmCompletion"
+    {
+        "context": "Pane",
+        "bindings": {
+            "ctrl--": "pane::GoBack",
+            "shift-ctrl-_": "pane::GoForward",
+            "cmd-shift-F": "project_search::ToggleFocus"
+        }
     },
-    "Editor && showing_code_actions": {
-        "enter": "editor::ConfirmCodeAction"
+    {
+        "context": "Workspace",
+        "bindings": {
+            "cmd-shift-F": "project_search::Deploy",
+            "cmd-k cmd-t": "theme_selector::Toggle",
+            "cmd-k t": "theme_selector::Reload",
+            "cmd-t": "project_symbols::Toggle",
+            "cmd-p": "file_finder::Toggle",
+            "cmd-shift-P": "command_palette::Toggle"
+        }
     },
-    "Editor && mode == full": {
-        "enter": "editor::Newline",
-        "cmd-f": [
-            "buffer_search::Deploy",
-            {
-                "focus": true
-            }
-        ],
-        "cmd-e": [
-            "buffer_search::Deploy",
-            {
-                "focus": false
-            }
-        ],
-        "cmd-shift-O": "outline::Toggle",
-        "ctrl-g": "go_to_line::Toggle"
+    // Bindings from Sublime Text
+    {
+        "context": "Editor",
+        "bindings": {
+            "ctrl-shift-K": "editor::DeleteLine",
+            "cmd-shift-D": "editor::DuplicateLine",
+            "cmd-shift-L": "editor::SplitSelectionIntoLines",
+            "ctrl-cmd-up": "editor::MoveLineUp",
+            "ctrl-cmd-down": "editor::MoveLineDown",
+            "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
+            "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
+            "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd",
+            "ctrl-alt-d": "editor::DeleteToNextSubwordEnd",
+            "ctrl-alt-left": "editor::MoveToPreviousSubwordStart",
+            "ctrl-alt-b": "editor::MoveToPreviousSubwordStart",
+            "ctrl-alt-right": "editor::MoveToNextSubwordEnd",
+            "ctrl-alt-f": "editor::MoveToNextSubwordEnd",
+            "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart",
+            "ctrl-alt-shift-B": "editor::SelectToPreviousSubwordStart",
+            "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd",
+            "ctrl-alt-shift-F": "editor::SelectToNextSubwordEnd"
+        }
     },
-    "Editor && mode == auto_height": {
-        "alt-enter": [
-            "editor::Input",
-            "\n"
-        ]
+    {
+        "bindings": {
+            "cmd-k cmd-left": "workspace::ActivatePreviousPane",
+            "cmd-k cmd-right": "workspace::ActivateNextPane"
+        }
     },
-    "GoToLine": {
-        "escape": "go_to_line::Toggle",
-        "enter": "go_to_line::Confirm"
+    {
+        "context": "Pane",
+        "bindings": {
+            "cmd-k up": [
+                "pane::Split",
+                "Up"
+            ],
+            "cmd-k down": [
+                "pane::Split",
+                "Down"
+            ],
+            "cmd-k left": [
+                "pane::Split",
+                "Left"
+            ],
+            "cmd-k right": [
+                "pane::Split",
+                "Right"
+            ]
+        }
     },
-    "ChatPanel": {
-        "enter": "chat_panel::Send"
+    // Bindings that should be unified with bindings for more general actions
+    {
+        "context": "Editor && renaming",
+        "bindings": {
+            "enter": "editor::ConfirmRename"
+        }
     },
-    "ProjectPanel": {
-        "left": "project_panel::CollapseSelectedEntry",
-        "right": "project_panel::ExpandSelectedEntry"
+    {
+        "context": "Editor && showing_completions",
+        "bindings": {
+            "enter": "editor::ConfirmCompletion",
+            "tab": "editor::ConfirmCompletion"
+        }
+    },
+    {
+        "context": "Editor && showing_code_actions",
+        "bindings": {
+            "enter": "editor::ConfirmCodeAction"
+        }
+    },
+    // Custom bindings
+    {
+        "bindings": {
+            "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
+            "cmd-alt-i": "zed::DebugElements",
+            "alt-cmd-,": "zed::OpenKeymap"
+        }
+    },
+    {
+        "context": "Editor",
+        "bindings": {
+            "ctrl-w": "editor::SelectLargerSyntaxNode",
+            "ctrl-shift-W": "editor::SelectSmallerSyntaxNode",
+            "alt-cmd-f": "editor::FoldSelectedRanges",
+            "alt-enter": "editor::OpenExcerpts",
+            "cmd-f10": "editor::RestartLanguageServer"
+        }
+    },
+    {
+        "context": "ProjectSearchBar",
+        "bindings": {
+            "cmd-enter": "project_search::SearchInNew"
+        }
+    },
+    {
+        "context": "Workspace",
+        "bindings": {
+            "alt-shift-D": "diagnostics::Deploy",
+            "ctrl-alt-cmd-j": "journal::NewJournalEntry",
+            "cmd-1": [
+                "workspace::ToggleSidebarItemFocus",
+                {
+                    "side": "Left",
+                    "item_index": 0
+                }
+            ],
+            "cmd-shift-!": [
+                "workspace::ToggleSidebarItem",
+                {
+                    "side": "Left",
+                    "item_index": 0
+                }
+            ]
+        }
+    },
+    {
+        "context": "ProjectPanel",
+        "bindings": {
+            "left": "project_panel::CollapseSelectedEntry",
+            "right": "project_panel::ExpandSelectedEntry"
+        }
     }
-}
+]

assets/keymaps/vim.json 🔗

@@ -1,93 +1,111 @@
-{
-    "Editor && VimControl": {
-        "i": [
-            "vim::SwitchMode",
-            "Insert"
-        ],
-        "g": [
-            "vim::PushOperator",
-            {
-                "Namespace": "G"
-            }
-        ],
-        "h": "vim::Left",
-        "j": "vim::Down",
-        "k": "vim::Up",
-        "l": "vim::Right",
-        "0": "vim::StartOfLine",
-        "shift-$": "vim::EndOfLine",
-        "shift-G": "vim::EndOfDocument",
-        "w": "vim::NextWordStart",
-        "shift-W": [
-            "vim::NextWordStart",
-            {
-                "ignorePunctuation": true
-            }
-        ],
-        "e": "vim::NextWordEnd",
-        "shift-E": [
-            "vim::NextWordEnd",
-            {
-                "ignorePunctuation": true
-            }
-        ],
-        "b": "vim::PreviousWordStart",
-        "shift-B": [
-            "vim::PreviousWordStart",
-            {
-                "ignorePunctuation": true
-            }
-        ],
-        "escape": [
-            "vim::SwitchMode",
-            "Normal"
-        ]
+[
+    {
+        "context": "Editor && VimControl",
+        "bindings": {
+            "i": [
+                "vim::SwitchMode",
+                "Insert"
+            ],
+            "g": [
+                "vim::PushOperator",
+                {
+                    "Namespace": "G"
+                }
+            ],
+            "h": "vim::Left",
+            "j": "vim::Down",
+            "k": "vim::Up",
+            "l": "vim::Right",
+            "0": "vim::StartOfLine",
+            "shift-$": "vim::EndOfLine",
+            "shift-G": "vim::EndOfDocument",
+            "w": "vim::NextWordStart",
+            "shift-W": [
+                "vim::NextWordStart",
+                {
+                    "ignorePunctuation": true
+                }
+            ],
+            "e": "vim::NextWordEnd",
+            "shift-E": [
+                "vim::NextWordEnd",
+                {
+                    "ignorePunctuation": true
+                }
+            ],
+            "b": "vim::PreviousWordStart",
+            "shift-B": [
+                "vim::PreviousWordStart",
+                {
+                    "ignorePunctuation": true
+                }
+            ],
+            "escape": [
+                "vim::SwitchMode",
+                "Normal"
+            ]
+        }
     },
-    "Editor && vim_operator == g": {
-        "g": "vim::StartOfDocument"
+    {
+        "context": "Editor && vim_operator == g",
+        "bindings": {
+            "g": "vim::StartOfDocument"
+        }
     },
-    "Editor && vim_mode == insert": {
-        "escape": "vim::NormalBefore",
-        "ctrl-c": "vim::NormalBefore"
+    {
+        "context": "Editor && vim_mode == insert",
+        "bindings": {
+            "escape": "vim::NormalBefore",
+            "ctrl-c": "vim::NormalBefore"
+        }
     },
-    "Editor && vim_mode == normal": {
-        "c": [
-            "vim::PushOperator",
-            "Change"
-        ],
-        "d": [
-            "vim::PushOperator",
-            "Delete"
-        ]
+    {
+        "context": "Editor && vim_mode == normal",
+        "bindings": {
+            "c": [
+                "vim::PushOperator",
+                "Change"
+            ],
+            "d": [
+                "vim::PushOperator",
+                "Delete"
+            ]
+        }
     },
-    "Editor && vim_operator == c": {
-        "w": [
-            "vim::NextWordEnd",
-            {
-                "ignorePunctuation": false
-            }
-        ],
-        "shift-W": [
-            "vim::NextWordEnd",
-            {
-                "ignorePunctuation": true
-            }
-        ]
+    {
+        "context": "Editor && vim_operator == c",
+        "bindings": {
+            "w": [
+                "vim::NextWordEnd",
+                {
+                    "ignorePunctuation": false
+                }
+            ],
+            "shift-W": [
+                "vim::NextWordEnd",
+                {
+                    "ignorePunctuation": true
+                }
+            ]
+        }
     },
-    "Editor && vim_operator == d": {
-        "w": [
-            "vim::NextWordStart",
-            {
-                "ignorePunctuation": false,
-                "stopAtNewline": true
-            }
-        ],
-        "shift-W": [
-            "vim::NextWordStart",
-            {
-                "ignorePunctuation": true,
-                "stopAtNewline": true
-            }
-        ]
+    {
+        "context": "Editor && vim_operator == d",
+        "bindings": {
+            "w": [
+                "vim::NextWordStart",
+                {
+                    "ignorePunctuation": false,
+                    "stopAtNewline": true
+                }
+            ],
+            "shift-W": [
+                "vim::NextWordStart",
+                {
+                    "ignorePunctuation": true,
+                    "stopAtNewline": true
+                }
+            ]
+        }
     }
-}
+]

crates/chat_panel/src/chat_panel.rs 🔗

@@ -16,6 +16,7 @@ use settings::{Settings, SoftWrap};
 use std::sync::Arc;
 use time::{OffsetDateTime, UtcOffset};
 use util::{ResultExt, TryFutureExt};
+use workspace::menu::Confirm;
 
 const MESSAGE_LOADING_THRESHOLD: usize = 50;
 
@@ -32,7 +33,7 @@ pub struct ChatPanel {
 
 pub enum Event {}
 
-actions!(chat_panel, [Send, LoadMoreMessages]);
+actions!(chat_panel, [LoadMoreMessages]);
 
 pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(ChatPanel::send);
@@ -345,7 +346,7 @@ impl ChatPanel {
         .boxed()
     }
 
-    fn send(&mut self, _: &Send, cx: &mut ViewContext<Self>) {
+    fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
         if let Some((channel, _)) = self.active_channel.as_ref() {
             let body = self.input_editor.update(cx, |editor, cx| {
                 let body = editor.text(cx);

crates/go_to_line/src/go_to_line.rs 🔗

@@ -5,13 +5,17 @@ use gpui::{
 };
 use settings::Settings;
 use text::{Bias, Point};
-use workspace::Workspace;
+use workspace::{
+    menu::{Cancel, Confirm},
+    Workspace,
+};
 
-actions!(go_to_line, [Toggle, Confirm]);
+actions!(go_to_line, [Toggle]);
 
 pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(GoToLine::toggle);
     cx.add_action(GoToLine::confirm);
+    cx.add_action(GoToLine::cancel);
 }
 
 pub struct GoToLine {
@@ -66,6 +70,10 @@ impl GoToLine {
         }
     }
 
+    fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
+        cx.emit(Event::Dismissed);
+    }
+
     fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
         self.prev_scroll_position.take();
         self.active_editor.update(cx, |active_editor, cx| {

crates/gpui/src/app.rs 🔗

@@ -1306,6 +1306,10 @@ impl MutableAppContext {
         }
     }
 
+    pub fn all_action_names<'a>(&'a self) -> impl Iterator<Item = &'static str> + 'a {
+        self.action_deserializers.keys().copied()
+    }
+
     pub fn available_actions(
         &self,
         window_id: usize,

crates/language/src/language.rs 🔗

@@ -102,6 +102,10 @@ pub trait LspAdapter: 'static + Send + Sync {
     fn disk_based_diagnostics_progress_token(&self) -> Option<&'static str> {
         None
     }
+
+    fn id_for_language(&self, _name: &str) -> Option<String> {
+        None
+    }
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]

crates/project/src/project.rs 🔗

@@ -1132,7 +1132,19 @@ impl Project {
             if file.is_local() {
                 let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
                 let initial_snapshot = buffer.text_snapshot();
-                let language_server = self.language_server_for_buffer(buffer, cx).cloned();
+
+                let mut language_server = None;
+                let mut language_id = None;
+                if let Some(language) = buffer.language() {
+                    let worktree_id = file.worktree_id(cx);
+                    if let Some(adapter) = language.lsp_adapter() {
+                        language_id = adapter.id_for_language(language.name().as_ref());
+                        language_server = self
+                            .language_servers
+                            .get(&(worktree_id, adapter.name()))
+                            .cloned();
+                    }
+                }
 
                 if let Some(local_worktree) = file.worktree.read(cx).as_local() {
                     if let Some(diagnostics) = local_worktree.diagnostics_for_path(file.path()) {
@@ -1147,7 +1159,7 @@ impl Project {
                             lsp::DidOpenTextDocumentParams {
                                 text_document: lsp::TextDocumentItem::new(
                                     uri,
-                                    Default::default(),
+                                    language_id.unwrap_or_default(),
                                     0,
                                     initial_snapshot.text(),
                                 ),
@@ -1437,7 +1449,7 @@ impl Project {
 
                     this.update(&mut cx, |this, cx| {
                         this.language_servers
-                            .insert(key.clone(), (adapter, language_server.clone()));
+                            .insert(key.clone(), (adapter.clone(), language_server.clone()));
                         this.language_server_statuses.insert(
                             server_id,
                             LanguageServerStatus {
@@ -1494,12 +1506,13 @@ impl Project {
                                     .or_insert_with(|| vec![(0, buffer.text_snapshot())]);
                                 let (version, initial_snapshot) = versions.last().unwrap();
                                 let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
+                                let language_id = adapter.id_for_language(language.name().as_ref());
                                 language_server
                                     .notify::<lsp::notification::DidOpenTextDocument>(
                                         lsp::DidOpenTextDocumentParams {
                                             text_document: lsp::TextDocumentItem::new(
                                                 uri,
-                                                Default::default(),
+                                                language_id.unwrap_or_default(),
                                                 *version,
                                                 initial_snapshot.text(),
                                             ),

crates/search/src/project_search.rs 🔗

@@ -17,9 +17,11 @@ use std::{
     path::PathBuf,
 };
 use util::ResultExt as _;
-use workspace::{Item, ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace};
+use workspace::{
+    menu::Confirm, Item, ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace,
+};
 
-actions!(project_search, [Deploy, Search, SearchInNew, ToggleFocus]);
+actions!(project_search, [Deploy, SearchInNew, ToggleFocus]);
 
 const MAX_TAB_TITLE_LEN: usize = 24;
 
@@ -530,7 +532,7 @@ impl ProjectSearchBar {
         }
     }
 
-    fn search(&mut self, _: &Search, cx: &mut ViewContext<Self>) {
+    fn search(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
         if let Some(search_view) = self.active_project_search.as_ref() {
             search_view.update(cx, |search_view, cx| search_view.search(cx));
         }

crates/settings/Cargo.toml 🔗

@@ -17,6 +17,7 @@ gpui = { path = "../gpui" }
 theme = { path = "../theme" }
 util = { path = "../util" }
 anyhow = "1.0.38"
+json_comments = "0.2"
 schemars = "0.8"
 serde = { version = "1", features = ["derive", "rc"] }
 serde_json = { version = "1.0.64", features = ["preserve_order"] }

crates/settings/src/keymap_file.rs 🔗

@@ -1,20 +1,45 @@
+use crate::parse_json_with_comments;
 use anyhow::{Context, Result};
 use assets::Assets;
 use collections::BTreeMap;
 use gpui::{keymap::Binding, MutableAppContext};
+use schemars::{
+    gen::{SchemaGenerator, SchemaSettings},
+    schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation},
+    JsonSchema,
+};
 use serde::Deserialize;
-use serde_json::value::RawValue;
+use serde_json::{value::RawValue, Value};
+
+#[derive(Deserialize, Default, Clone, JsonSchema)]
+#[serde(transparent)]
+pub struct KeymapFileContent(Vec<KeymapBlock>);
+
+#[derive(Deserialize, Default, Clone, JsonSchema)]
+pub struct KeymapBlock {
+    #[serde(default)]
+    context: Option<String>,
+    bindings: BTreeMap<String, KeymapAction>,
+}
 
 #[derive(Deserialize, Default, Clone)]
 #[serde(transparent)]
-pub struct KeymapFile(BTreeMap<String, ActionsByKeystroke>);
+pub struct KeymapAction(Box<RawValue>);
 
-type ActionsByKeystroke = BTreeMap<String, Box<RawValue>>;
+impl JsonSchema for KeymapAction {
+    fn schema_name() -> String {
+        "KeymapAction".into()
+    }
+
+    fn json_schema(_: &mut SchemaGenerator) -> Schema {
+        Schema::Bool(true)
+    }
+}
 
 #[derive(Deserialize)]
-struct ActionWithData<'a>(#[serde(borrow)] &'a str, #[serde(borrow)] &'a RawValue);
+struct ActionWithData(Box<str>, Box<RawValue>);
 
-impl KeymapFile {
+impl KeymapFileContent {
     pub fn load_defaults(cx: &mut MutableAppContext) {
         for path in ["keymaps/default.json", "keymaps/vim.json"] {
             Self::load(path, cx).unwrap();
@@ -24,17 +49,16 @@ impl KeymapFile {
     pub fn load(asset_path: &str, cx: &mut MutableAppContext) -> Result<()> {
         let content = Assets::get(asset_path).unwrap().data;
         let content_str = std::str::from_utf8(content.as_ref()).unwrap();
-        Ok(serde_json::from_str::<Self>(content_str)?.add(cx)?)
+        Ok(parse_json_with_comments::<Self>(content_str)?.add(cx)?)
     }
 
     pub fn add(self, cx: &mut MutableAppContext) -> Result<()> {
-        for (context, actions) in self.0 {
-            let context = if context == "*" { None } else { Some(context) };
+        for KeymapBlock { context, bindings } in self.0 {
             cx.add_bindings(
-                actions
+                bindings
                     .into_iter()
                     .map(|(keystroke, action)| {
-                        let action = action.get();
+                        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
@@ -42,7 +66,7 @@ impl KeymapFile {
                         // 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)?;
-                            cx.deserialize_action(name, Some(data.get()))
+                            cx.deserialize_action(&name, Some(data.get()))
                         } else {
                             let name = serde_json::from_str(action)?;
                             cx.deserialize_action(name, None)
@@ -60,3 +84,39 @@ impl KeymapFile {
         Ok(())
     }
 }
+
+pub fn keymap_file_json_schema(action_names: &[&'static str]) -> serde_json::Value {
+    let mut root_schema = SchemaSettings::draft07()
+        .with(|settings| settings.option_add_null_type = false)
+        .into_generator()
+        .into_root_schema_for::<KeymapFileContent>();
+
+    let action_schema = Schema::Object(SchemaObject {
+        subschemas: Some(Box::new(SubschemaValidation {
+            one_of: Some(vec![
+                Schema::Object(SchemaObject {
+                    instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
+                    enum_values: Some(
+                        action_names
+                            .into_iter()
+                            .map(|name| Value::String(name.to_string()))
+                            .collect(),
+                    ),
+                    ..Default::default()
+                }),
+                Schema::Object(SchemaObject {
+                    instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Array))),
+                    ..Default::default()
+                }),
+            ]),
+            ..Default::default()
+        })),
+        ..Default::default()
+    });
+
+    root_schema
+        .definitions
+        .insert("KeymapAction".to_owned(), action_schema);
+
+    serde_json::to_value(root_schema).unwrap()
+}

crates/settings/src/settings.rs 🔗

@@ -9,13 +9,13 @@ use schemars::{
     },
     JsonSchema,
 };
-use serde::Deserialize;
+use serde::{de::DeserializeOwned, Deserialize};
 use serde_json::Value;
 use std::{collections::HashMap, sync::Arc};
 use theme::{Theme, ThemeRegistry};
 use util::ResultExt as _;
 
-pub use keymap_file::KeymapFile;
+pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
 
 #[derive(Clone)]
 pub struct Settings {
@@ -78,90 +78,6 @@ impl Settings {
         })
     }
 
-    pub fn file_json_schema(
-        theme_names: Vec<String>,
-        language_names: Vec<String>,
-    ) -> serde_json::Value {
-        let settings = SchemaSettings::draft07().with(|settings| {
-            settings.option_add_null_type = false;
-        });
-        let generator = SchemaGenerator::new(settings);
-        let mut root_schema = generator.into_root_schema_for::<SettingsFileContent>();
-
-        // Construct theme names reference type
-        let theme_names = theme_names
-            .into_iter()
-            .map(|name| Value::String(name))
-            .collect();
-        let theme_names_schema = Schema::Object(SchemaObject {
-            instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
-            enum_values: Some(theme_names),
-            ..Default::default()
-        });
-        root_schema
-            .definitions
-            .insert("ThemeName".to_owned(), theme_names_schema);
-
-        // Construct language overrides reference type
-        let language_override_schema_reference = Schema::Object(SchemaObject {
-            reference: Some("#/definitions/LanguageOverride".to_owned()),
-            ..Default::default()
-        });
-        let language_overrides_properties = language_names
-            .into_iter()
-            .map(|name| {
-                (
-                    name,
-                    Schema::Object(SchemaObject {
-                        subschemas: Some(Box::new(SubschemaValidation {
-                            all_of: Some(vec![language_override_schema_reference.clone()]),
-                            ..Default::default()
-                        })),
-                        ..Default::default()
-                    }),
-                )
-            })
-            .collect();
-        let language_overrides_schema = Schema::Object(SchemaObject {
-            instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
-            object: Some(Box::new(ObjectValidation {
-                properties: language_overrides_properties,
-                ..Default::default()
-            })),
-            ..Default::default()
-        });
-        root_schema
-            .definitions
-            .insert("LanguageOverrides".to_owned(), language_overrides_schema);
-
-        // Modify theme property to use new theme reference type
-        let settings_file_schema = root_schema.schema.object.as_mut().unwrap();
-        let language_overrides_schema_reference = Schema::Object(SchemaObject {
-            reference: Some("#/definitions/ThemeName".to_owned()),
-            ..Default::default()
-        });
-        settings_file_schema.properties.insert(
-            "theme".to_owned(),
-            Schema::Object(SchemaObject {
-                subschemas: Some(Box::new(SubschemaValidation {
-                    all_of: Some(vec![language_overrides_schema_reference]),
-                    ..Default::default()
-                })),
-                ..Default::default()
-            }),
-        );
-
-        // Modify language_overrides property to use LanguageOverrides reference
-        settings_file_schema.properties.insert(
-            "language_overrides".to_owned(),
-            Schema::Object(SchemaObject {
-                reference: Some("#/definitions/LanguageOverrides".to_owned()),
-                ..Default::default()
-            }),
-        );
-        serde_json::to_value(root_schema).unwrap()
-    }
-
     pub fn with_overrides(
         mut self,
         language_name: impl Into<Arc<str>>,
@@ -249,6 +165,90 @@ impl Settings {
     }
 }
 
+pub fn settings_file_json_schema(
+    theme_names: Vec<String>,
+    language_names: Vec<String>,
+) -> serde_json::Value {
+    let settings = SchemaSettings::draft07().with(|settings| {
+        settings.option_add_null_type = false;
+    });
+    let generator = SchemaGenerator::new(settings);
+    let mut root_schema = generator.into_root_schema_for::<SettingsFileContent>();
+
+    // Construct theme names reference type
+    let theme_names = theme_names
+        .into_iter()
+        .map(|name| Value::String(name))
+        .collect();
+    let theme_names_schema = Schema::Object(SchemaObject {
+        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
+        enum_values: Some(theme_names),
+        ..Default::default()
+    });
+    root_schema
+        .definitions
+        .insert("ThemeName".to_owned(), theme_names_schema);
+
+    // Construct language overrides reference type
+    let language_override_schema_reference = Schema::Object(SchemaObject {
+        reference: Some("#/definitions/LanguageOverride".to_owned()),
+        ..Default::default()
+    });
+    let language_overrides_properties = language_names
+        .into_iter()
+        .map(|name| {
+            (
+                name,
+                Schema::Object(SchemaObject {
+                    subschemas: Some(Box::new(SubschemaValidation {
+                        all_of: Some(vec![language_override_schema_reference.clone()]),
+                        ..Default::default()
+                    })),
+                    ..Default::default()
+                }),
+            )
+        })
+        .collect();
+    let language_overrides_schema = Schema::Object(SchemaObject {
+        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
+        object: Some(Box::new(ObjectValidation {
+            properties: language_overrides_properties,
+            ..Default::default()
+        })),
+        ..Default::default()
+    });
+    root_schema
+        .definitions
+        .insert("LanguageOverrides".to_owned(), language_overrides_schema);
+
+    // Modify theme property to use new theme reference type
+    let settings_file_schema = root_schema.schema.object.as_mut().unwrap();
+    let language_overrides_schema_reference = Schema::Object(SchemaObject {
+        reference: Some("#/definitions/ThemeName".to_owned()),
+        ..Default::default()
+    });
+    settings_file_schema.properties.insert(
+        "theme".to_owned(),
+        Schema::Object(SchemaObject {
+            subschemas: Some(Box::new(SubschemaValidation {
+                all_of: Some(vec![language_overrides_schema_reference]),
+                ..Default::default()
+            })),
+            ..Default::default()
+        }),
+    );
+
+    // Modify language_overrides property to use LanguageOverrides reference
+    settings_file_schema.properties.insert(
+        "language_overrides".to_owned(),
+        Schema::Object(SchemaObject {
+            reference: Some("#/definitions/LanguageOverrides".to_owned()),
+            ..Default::default()
+        }),
+    );
+    serde_json::to_value(root_schema).unwrap()
+}
+
 fn merge<T: Copy>(target: &mut T, value: Option<T>) {
     if let Some(value) = value {
         *target = value;
@@ -260,3 +260,9 @@ fn merge_option<T: Copy>(target: &mut Option<T>, value: Option<T>) {
         *target = value;
     }
 }
+
+pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T> {
+    Ok(serde_json::from_reader(
+        json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()),
+    )?)
+}

crates/vim/src/vim_test_context.rs 🔗

@@ -24,7 +24,7 @@ impl<'a> VimTestContext<'a> {
             editor::init(cx);
             crate::init(cx);
 
-            settings::KeymapFile::load("keymaps/vim.json", cx).unwrap();
+            settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap();
         });
 
         let params = cx.update(WorkspaceParams::test);

crates/zed/Cargo.toml 🔗

@@ -86,7 +86,7 @@ tiny_http = "0.8"
 toml = "0.5"
 tree-sitter = "0.20.4"
 tree-sitter-c = "0.20.1"
-tree-sitter-json = "0.19.0"
+tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8" }
 tree-sitter-rust = "0.20.1"
 tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
 tree-sitter-typescript = "0.20.1"

crates/zed/src/languages/json.rs 🔗

@@ -127,4 +127,12 @@ impl LspAdapter for JsonLspAdapter {
             "provideFormatter": true
         }))
     }
+
+    fn id_for_language(&self, name: &str) -> Option<String> {
+        if name == "JSON" {
+            Some("jsonc".into())
+        } else {
+            None
+        }
+    }
 }

crates/zed/src/main.rs 🔗

@@ -17,7 +17,7 @@ use gpui::{App, AssetSource, AsyncAppContext, Task};
 use log::LevelFilter;
 use parking_lot::Mutex;
 use project::Fs;
-use settings::{self, KeymapFile, Settings, SettingsFileContent};
+use settings::{self, KeymapFileContent, Settings, SettingsFileContent};
 use smol::process::Command;
 use std::{env, fs, path::PathBuf, sync::Arc, thread, time::Duration};
 use theme::{ThemeRegistry, DEFAULT_THEME_NAME};
@@ -309,7 +309,7 @@ fn load_config_files(
     fs: Arc<dyn Fs>,
 ) -> oneshot::Receiver<(
     WatchedJsonFile<SettingsFileContent>,
-    WatchedJsonFile<KeymapFile>,
+    WatchedJsonFile<KeymapFileContent>,
 )> {
     let executor = app.background();
     let (tx, rx) = oneshot::channel();

crates/zed/src/settings_file.rs 🔗

@@ -4,7 +4,7 @@ use postage::sink::Sink as _;
 use postage::{prelude::Stream, watch};
 use project::Fs;
 use serde::Deserialize;
-use settings::{KeymapFile, Settings, SettingsFileContent};
+use settings::{parse_json_with_comments, KeymapFileContent, Settings, SettingsFileContent};
 use std::{path::Path, sync::Arc, time::Duration};
 use theme::ThemeRegistry;
 use util::ResultExt;
@@ -44,7 +44,7 @@ where
             fs.load(&path)
                 .await
                 .log_err()
-                .and_then(|data| serde_json::from_str(&data).log_err())
+                .and_then(|data| parse_json_with_comments(&data).log_err())
         } else {
             Some(T::default())
         }
@@ -76,11 +76,14 @@ pub fn settings_from_files(
     })
 }
 
-pub async fn watch_keymap_file(mut file: WatchedJsonFile<KeymapFile>, mut cx: AsyncAppContext) {
+pub async fn watch_keymap_file(
+    mut file: WatchedJsonFile<KeymapFileContent>,
+    mut cx: AsyncAppContext,
+) {
     while let Some(content) = file.0.recv().await {
         cx.update(|cx| {
             cx.clear_bindings();
-            settings::KeymapFile::load_defaults(cx);
+            settings::KeymapFileContent::load_defaults(cx);
             content.add(cx).log_err();
         });
     }

crates/zed/src/zed.rs 🔗

@@ -25,7 +25,7 @@ pub use project::{self, fs};
 use project_panel::ProjectPanel;
 use search::{BufferSearchBar, ProjectSearchBar};
 use serde_json::to_string_pretty;
-use settings::Settings;
+use settings::{keymap_file_json_schema, settings_file_json_schema, Settings};
 use std::{
     path::{Path, PathBuf},
     sync::Arc,
@@ -41,6 +41,7 @@ actions!(
         Quit,
         DebugElements,
         OpenSettings,
+        OpenKeymap,
         IncreaseBufferFontSize,
         DecreaseBufferFontSize,
         InstallCommandLineInterface,
@@ -78,39 +79,13 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
     cx.add_action({
         let app_state = app_state.clone();
         move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
-            let app_state = app_state.clone();
-            cx.spawn(move |workspace, mut cx| async move {
-                let fs = &app_state.fs;
-                if !fs.is_file(&SETTINGS_PATH).await {
-                    fs.create_dir(&ROOT_PATH).await?;
-                    fs.create_file(&SETTINGS_PATH, Default::default()).await?;
-                }
-
-                workspace
-                    .update(&mut cx, |workspace, cx| {
-                        if workspace.project().read(cx).is_local() {
-                            workspace.open_paths(&[SETTINGS_PATH.clone()], cx)
-                        } else {
-                            let (_, workspace) =
-                                cx.add_window((app_state.build_window_options)(), |cx| {
-                                    let project = Project::local(
-                                        app_state.client.clone(),
-                                        app_state.user_store.clone(),
-                                        app_state.languages.clone(),
-                                        app_state.fs.clone(),
-                                        cx,
-                                    );
-                                    (app_state.build_workspace)(project, &app_state, cx)
-                                });
-                            workspace.update(cx, |workspace, cx| {
-                                workspace.open_paths(&[SETTINGS_PATH.clone()], cx)
-                            })
-                        }
-                    })
-                    .await;
-                Ok::<_, anyhow::Error>(())
-            })
-            .detach_and_log_err(cx);
+            open_config_file(&SETTINGS_PATH, app_state.clone(), cx);
+        }
+    });
+    cx.add_action({
+        let app_state = app_state.clone();
+        move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| {
+            open_config_file(&KEYMAP_PATH, app_state.clone(), cx);
         }
     });
     cx.add_action(
@@ -137,8 +112,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
     );
 
     workspace::lsp_status::init(cx);
-
-    settings::KeymapFile::load_defaults(cx);
+    settings::KeymapFileContent::load_defaults(cx);
 }
 
 pub fn build_workspace(
@@ -179,13 +153,18 @@ pub fn build_workspace(
     let theme_names = app_state.themes.list().collect();
     let language_names = app_state.languages.language_names();
 
-    project.update(cx, |project, _| {
+    project.update(cx, |project, cx| {
+        let action_names = cx.all_action_names().collect::<Vec<_>>();
         project.set_language_server_settings(serde_json::json!({
             "json": {
                 "schemas": [
                     {
-                        "fileMatch": "**/.zed/settings.json",
-                        "schema": Settings::file_json_schema(theme_names, language_names),
+                        "fileMatch": [".zed/settings.json"],
+                        "schema": settings_file_json_schema(theme_names, language_names),
+                    },
+                    {
+                        "fileMatch": [".zed/keymap.json"],
+                        "schema": keymap_file_json_schema(&action_names),
                     }
                 ]
             }
@@ -289,6 +268,44 @@ async fn install_cli(cx: &AsyncAppContext) -> Result<()> {
     }
 }
 
+fn open_config_file(
+    path: &'static Path,
+    app_state: Arc<AppState>,
+    cx: &mut ViewContext<Workspace>,
+) {
+    cx.spawn(|workspace, mut cx| async move {
+        let fs = &app_state.fs;
+        if !fs.is_file(path).await {
+            fs.create_dir(&ROOT_PATH).await?;
+            fs.create_file(path, Default::default()).await?;
+        }
+
+        workspace
+            .update(&mut cx, |workspace, cx| {
+                if workspace.project().read(cx).is_local() {
+                    workspace.open_paths(&[path.to_path_buf()], cx)
+                } else {
+                    let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
+                        let project = Project::local(
+                            app_state.client.clone(),
+                            app_state.user_store.clone(),
+                            app_state.languages.clone(),
+                            app_state.fs.clone(),
+                            cx,
+                        );
+                        (app_state.build_workspace)(project, &app_state, cx)
+                    });
+                    workspace.update(cx, |workspace, cx| {
+                        workspace.open_paths(&[path.to_path_buf()], cx)
+                    })
+                }
+            })
+            .await;
+        Ok::<_, anyhow::Error>(())
+    })
+    .detach_and_log_err(cx)
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;