Migrate keymap and settings + edit predictions rename (#23834)

smit , Piotr Osiewicz , and Max Brunsfeld created

- [x] snake case keymap properties
- [x] flatten actions
- [x] keymap migration + notfication
- [x] settings migration + notification
- [x] inline completions -> edit predictions 

### future: 
- keymap notification doesn't show up on start up, only on keymap save.
this is existing bug in zed, will be addressed in seperate PR.

Release Notes:

- Added a notification for deprecated settings and keymaps, allowing you
to migrate them with a single click. A backup of your existing keymap
and settings will be created in your home directory.
- Modified some keymap actions and settings for consistency.

---------

Co-authored-by: Piotr Osiewicz <piotr@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

Cargo.lock                                                      |  12 
Cargo.toml                                                      |   2 
assets/keymaps/default-linux.json                               |  60 
assets/keymaps/default-macos.json                               |  56 
assets/keymaps/vim.json                                         | 196 
assets/settings/default.json                                    |  12 
crates/copilot/src/copilot.rs                                   |   6 
crates/copilot/src/copilot_completion_provider.rs               |  34 
crates/editor/src/actions.rs                                    |  43 
crates/editor/src/code_context_menus.rs                         |   1 
crates/editor/src/editor.rs                                     | 100 
crates/editor/src/editor_settings.rs                            |   4 
crates/editor/src/editor_tests.rs                               |   6 
crates/editor/src/element.rs                                    |  10 
crates/editor/src/inline_completion_tests.rs                    |   8 
crates/inline_completion/src/inline_completion.rs               |   4 
crates/inline_completion_button/src/inline_completion_button.rs |  58 
crates/language/src/language_settings.rs                        |  70 
crates/migrator/Cargo.toml                                      |  22 
crates/migrator/LICENSE-GPL                                     |   1 
crates/migrator/src/migrator.rs                                 | 863 +++
crates/picker/src/picker.rs                                     |   1 
crates/project_panel/src/project_panel.rs                       |   2 
crates/search/src/buffer_search.rs                              |   1 
crates/settings/Cargo.toml                                      |   1 
crates/settings/src/keymap_file.rs                              |  85 
crates/settings/src/settings_file.rs                            |   6 
crates/settings/src/settings_store.rs                           |  65 
crates/supermaven/src/supermaven.rs                             |   8 
crates/supermaven/src/supermaven_completion_provider.rs         |   4 
crates/tab_switcher/src/tab_switcher.rs                         |   1 
crates/terminal_view/src/terminal_panel.rs                      | 104 
crates/title_bar/src/application_menu.rs                        |  35 
crates/title_bar/src/title_bar.rs                               |  50 
crates/vim/src/motion.rs                                        |  30 
crates/vim/src/normal/increment.rs                              |   4 
crates/vim/src/normal/paste.rs                                  |   2 
crates/vim/src/normal/search.rs                                 |   6 
crates/vim/src/object.rs                                        |   7 
crates/vim/src/state.rs                                         |   6 
crates/vim/src/surrounds.rs                                     |   8 
crates/vim/src/test.rs                                          |  13 
crates/vim/src/vim.rs                                           | 370 +
crates/workspace/src/pane.rs                                    |  17 
crates/workspace/src/pane_group.rs                              |   9 
crates/workspace/src/workspace.rs                               |  73 
crates/zed/src/zed.rs                                           | 101 
crates/zed/src/zed/inline_completion_registry.rs                |  54 
crates/zed/src/zed/quick_action_bar.rs                          |   4 
crates/zed_actions/src/lib.rs                                   |   7 
crates/zeta/src/init.rs                                         |   4 
crates/zeta/src/onboarding_banner.rs                            |   8 
crates/zeta/src/onboarding_modal.rs                             |   4 
crates/zeta/src/zeta.rs                                         |   2 
docs/src/completions.md                                         |  24 
docs/src/configuring-zed.md                                     |  14 
docs/src/key-bindings.md                                        |   2 
docs/src/vim.md                                                 |  19 
58 files changed, 2,104 insertions(+), 615 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -7836,6 +7836,17 @@ dependencies = [
  "paste",
 ]
 
+[[package]]
+name = "migrator"
+version = "0.1.0"
+dependencies = [
+ "collections",
+ "convert_case 0.7.1",
+ "pretty_assertions",
+ "tree-sitter",
+ "tree-sitter-json",
+]
+
 [[package]]
 name = "mimalloc"
 version = "0.1.43"
@@ -11980,6 +11991,7 @@ dependencies = [
  "gpui",
  "indoc",
  "log",
+ "migrator",
  "paths",
  "pretty_assertions",
  "release_channel",

Cargo.toml 🔗

@@ -81,6 +81,7 @@ members = [
     "crates/markdown_preview",
     "crates/media",
     "crates/menu",
+    "crates/migrator",
     "crates/multi_buffer",
     "crates/node_runtime",
     "crates/notifications",
@@ -279,6 +280,7 @@ markdown = { path = "crates/markdown" }
 markdown_preview = { path = "crates/markdown_preview" }
 media = { path = "crates/media" }
 menu = { path = "crates/menu" }
+migrator = { path = "crates/migrator" }
 multi_buffer = { path = "crates/multi_buffer" }
 node_runtime = { path = "crates/node_runtime" }
 notifications = { path = "crates/notifications" }

assets/keymaps/default-linux.json 🔗

@@ -32,7 +32,7 @@
       "ctrl-q": "zed::Quit",
       "f11": "zed::ToggleFullScreen",
       "ctrl-alt-z": "zeta::RateCompletions",
-      "ctrl-shift-i": "inline_completion::ToggleMenu"
+      "ctrl-shift-i": "edit_prediction::ToggleMenu"
     }
   },
   {
@@ -145,17 +145,17 @@
     }
   },
   {
-    "context": "Editor && mode == full && inline_completion",
+    "context": "Editor && mode == full && edit_prediction",
     "bindings": {
-      "alt-]": "editor::NextInlineCompletion",
-      "alt-[": "editor::PreviousInlineCompletion",
-      "alt-right": "editor::AcceptPartialInlineCompletion"
+      "alt-]": "editor::NextEditPrediction",
+      "alt-[": "editor::PreviousEditPrediction",
+      "alt-right": "editor::AcceptPartialEditPrediction"
     }
   },
   {
-    "context": "Editor && !inline_completion",
+    "context": "Editor && !edit_prediction",
     "bindings": {
-      "alt-\\": "editor::ShowInlineCompletion"
+      "alt-\\": "editor::ShowEditPrediction"
     }
   },
   {
@@ -348,15 +348,15 @@
       "ctrl-k ctrl-l": "editor::ToggleFold",
       "ctrl-k ctrl-[": "editor::FoldRecursive",
       "ctrl-k ctrl-]": "editor::UnfoldRecursive",
-      "ctrl-k ctrl-1": ["editor::FoldAtLevel", { "level": 1 }],
-      "ctrl-k ctrl-2": ["editor::FoldAtLevel", { "level": 2 }],
-      "ctrl-k ctrl-3": ["editor::FoldAtLevel", { "level": 3 }],
-      "ctrl-k ctrl-4": ["editor::FoldAtLevel", { "level": 4 }],
-      "ctrl-k ctrl-5": ["editor::FoldAtLevel", { "level": 5 }],
-      "ctrl-k ctrl-6": ["editor::FoldAtLevel", { "level": 6 }],
-      "ctrl-k ctrl-7": ["editor::FoldAtLevel", { "level": 7 }],
-      "ctrl-k ctrl-8": ["editor::FoldAtLevel", { "level": 8 }],
-      "ctrl-k ctrl-9": ["editor::FoldAtLevel", { "level": 9 }],
+      "ctrl-k ctrl-1": ["editor::FoldAtLevel", 1],
+      "ctrl-k ctrl-2": ["editor::FoldAtLevel", 2],
+      "ctrl-k ctrl-3": ["editor::FoldAtLevel", 3],
+      "ctrl-k ctrl-4": ["editor::FoldAtLevel", 4],
+      "ctrl-k ctrl-5": ["editor::FoldAtLevel", 5],
+      "ctrl-k ctrl-6": ["editor::FoldAtLevel", 6],
+      "ctrl-k ctrl-7": ["editor::FoldAtLevel", 7],
+      "ctrl-k ctrl-8": ["editor::FoldAtLevel", 8],
+      "ctrl-k ctrl-9": ["editor::FoldAtLevel", 9],
       "ctrl-k ctrl-0": "editor::FoldAll",
       "ctrl-k ctrl-j": "editor::UnfoldAll",
       "ctrl-space": "editor::ShowCompletions",
@@ -432,14 +432,14 @@
       "ctrl-alt-s": "workspace::SaveAll",
       "ctrl-k m": "language_selector::Toggle",
       "escape": "workspace::Unfollow",
-      "ctrl-k ctrl-left": ["workspace::ActivatePaneInDirection", "Left"],
-      "ctrl-k ctrl-right": ["workspace::ActivatePaneInDirection", "Right"],
-      "ctrl-k ctrl-up": ["workspace::ActivatePaneInDirection", "Up"],
-      "ctrl-k ctrl-down": ["workspace::ActivatePaneInDirection", "Down"],
-      "ctrl-k shift-left": ["workspace::SwapPaneInDirection", "Left"],
-      "ctrl-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
-      "ctrl-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
-      "ctrl-k shift-down": ["workspace::SwapPaneInDirection", "Down"],
+      "ctrl-k ctrl-left": "workspace::ActivatePaneLeft",
+      "ctrl-k ctrl-right": "workspace::ActivatePaneRight",
+      "ctrl-k ctrl-up": "workspace::ActivatePaneUp",
+      "ctrl-k ctrl-down": "workspace::ActivatePaneDown",
+      "ctrl-k shift-left": "workspace::SwapPaneLeft",
+      "ctrl-k shift-right": "workspace::SwapPaneRight",
+      "ctrl-k shift-up": "workspace::SwapPaneUp",
+      "ctrl-k shift-down": "workspace::SwapPaneDown",
       "ctrl-shift-x": "zed::Extensions",
       "ctrl-shift-r": "task::Rerun",
       "ctrl-alt-r": "task::Rerun",
@@ -453,8 +453,8 @@
   {
     "context": "ApplicationMenu",
     "bindings": {
-      "left": ["app_menu::NavigateApplicationMenuInDirection", "Left"],
-      "right": ["app_menu::NavigateApplicationMenuInDirection", "Right"]
+      "left": "app_menu::ActivateMenuLeft",
+      "right": "app_menu::ActivateMenuRight"
     }
   },
   // Bindings from Sublime Text
@@ -502,16 +502,16 @@
     }
   },
   {
-    "context": "Editor && inline_completion",
+    "context": "Editor && edit_prediction",
     "bindings": {
       // Changing the modifier currently breaks accepting while you also an LSP completions menu open
-      "alt-enter": "editor::AcceptInlineCompletion"
+      "alt-enter": "editor::AcceptEditPrediction"
     }
   },
   {
-    "context": "Editor && inline_completion && !inline_completion_requires_modifier",
+    "context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
     "bindings": {
-      "tab": "editor::AcceptInlineCompletion"
+      "tab": "editor::AcceptEditPrediction"
     }
   },
   {

assets/keymaps/default-macos.json 🔗

@@ -40,7 +40,7 @@
       "fn-f": "zed::ToggleFullScreen",
       "ctrl-cmd-f": "zed::ToggleFullScreen",
       "ctrl-shift-z": "zeta::RateCompletions",
-      "ctrl-shift-i": "inline_completion::ToggleMenu"
+      "ctrl-shift-i": "edit_prediction::ToggleMenu"
     }
   },
   {
@@ -155,19 +155,19 @@
     }
   },
   {
-    "context": "Editor && mode == full && inline_completion",
+    "context": "Editor && mode == full && edit_prediction",
     "use_key_equivalents": true,
     "bindings": {
-      "alt-tab": "editor::NextInlineCompletion",
-      "alt-shift-tab": "editor::PreviousInlineCompletion",
-      "ctrl-cmd-right": "editor::AcceptPartialInlineCompletion"
+      "alt-tab": "editor::NextEditPrediction",
+      "alt-shift-tab": "editor::PreviousEditPrediction",
+      "ctrl-cmd-right": "editor::AcceptPartialEditPrediction"
     }
   },
   {
-    "context": "Editor && !inline_completion",
+    "context": "Editor && !edit_prediction",
     "use_key_equivalents": true,
     "bindings": {
-      "alt-tab": "editor::ShowInlineCompletion"
+      "alt-tab": "editor::ShowEditPrediction"
     }
   },
   {
@@ -413,15 +413,15 @@
       "cmd-k cmd-l": "editor::ToggleFold",
       "cmd-k cmd-[": "editor::FoldRecursive",
       "cmd-k cmd-]": "editor::UnfoldRecursive",
-      "cmd-k cmd-1": ["editor::FoldAtLevel", { "level": 1 }],
-      "cmd-k cmd-2": ["editor::FoldAtLevel", { "level": 2 }],
-      "cmd-k cmd-3": ["editor::FoldAtLevel", { "level": 3 }],
-      "cmd-k cmd-4": ["editor::FoldAtLevel", { "level": 4 }],
-      "cmd-k cmd-5": ["editor::FoldAtLevel", { "level": 5 }],
-      "cmd-k cmd-6": ["editor::FoldAtLevel", { "level": 6 }],
-      "cmd-k cmd-7": ["editor::FoldAtLevel", { "level": 7 }],
-      "cmd-k cmd-8": ["editor::FoldAtLevel", { "level": 8 }],
-      "cmd-k cmd-9": ["editor::FoldAtLevel", { "level": 9 }],
+      "cmd-k cmd-1": ["editor::FoldAtLevel", 1],
+      "cmd-k cmd-2": ["editor::FoldAtLevel", 2],
+      "cmd-k cmd-3": ["editor::FoldAtLevel", 3],
+      "cmd-k cmd-4": ["editor::FoldAtLevel", 4],
+      "cmd-k cmd-5": ["editor::FoldAtLevel", 5],
+      "cmd-k cmd-6": ["editor::FoldAtLevel", 6],
+      "cmd-k cmd-7": ["editor::FoldAtLevel", 7],
+      "cmd-k cmd-8": ["editor::FoldAtLevel", 8],
+      "cmd-k cmd-9": ["editor::FoldAtLevel", 9],
       "cmd-k cmd-0": "editor::FoldAll",
       "cmd-k cmd-j": "editor::UnfoldAll",
       // Using `ctrl-space` in Zed requires disabling the macOS global shortcut.
@@ -509,14 +509,14 @@
       "cmd-alt-s": "workspace::SaveAll",
       "cmd-k m": "language_selector::Toggle",
       "escape": "workspace::Unfollow",
-      "cmd-k cmd-left": ["workspace::ActivatePaneInDirection", "Left"],
-      "cmd-k cmd-right": ["workspace::ActivatePaneInDirection", "Right"],
-      "cmd-k cmd-up": ["workspace::ActivatePaneInDirection", "Up"],
-      "cmd-k cmd-down": ["workspace::ActivatePaneInDirection", "Down"],
-      "cmd-k shift-left": ["workspace::SwapPaneInDirection", "Left"],
-      "cmd-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
-      "cmd-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
-      "cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"],
+      "cmd-k cmd-left": "workspace::ActivatePaneLeft",
+      "cmd-k cmd-right": "workspace::ActivatePaneRight",
+      "cmd-k cmd-up": "workspace::ActivatePaneUp",
+      "cmd-k cmd-down": "workspace::ActivatePaneDown",
+      "cmd-k shift-left": "workspace::SwapPaneLeft",
+      "cmd-k shift-right": "workspace::SwapPaneRight",
+      "cmd-k shift-up": "workspace::SwapPaneUp",
+      "cmd-k shift-down": "workspace::SwapPaneDown",
       "cmd-shift-x": "zed::Extensions"
     }
   },
@@ -580,17 +580,17 @@
     }
   },
   {
-    "context": "Editor && inline_completion",
+    "context": "Editor && edit_prediction",
     "bindings": {
       // Changing the modifier currently breaks accepting while you also an LSP completions menu open
-      "alt-tab": "editor::AcceptInlineCompletion"
+      "alt-tab": "editor::AcceptEditPrediction"
     }
   },
   {
-    "context": "Editor && inline_completion && !inline_completion_requires_modifier",
+    "context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
     "use_key_equivalents": true,
     "bindings": {
-      "tab": "editor::AcceptInlineCompletion"
+      "tab": "editor::AcceptEditPrediction"
     }
   },
   {

assets/keymaps/vim.json 🔗

@@ -2,8 +2,8 @@
   {
     "context": "VimControl && !menu",
     "bindings": {
-      "i": ["vim::PushOperator", { "Object": { "around": false } }],
-      "a": ["vim::PushOperator", { "Object": { "around": true } }],
+      "i": ["vim::PushObject", { "around": false }],
+      "a": ["vim::PushObject", { "around": true }],
       "left": "vim::Left",
       "h": "vim::Left",
       "backspace": "vim::Backspace",
@@ -54,10 +54,10 @@
       // "b": "vim::PreviousSubwordStart",
       // "e": "vim::NextSubwordEnd",
       // "g e": "vim::PreviousSubwordEnd",
-      "shift-w": ["vim::NextWordStart", { "ignorePunctuation": true }],
-      "shift-e": ["vim::NextWordEnd", { "ignorePunctuation": true }],
-      "shift-b": ["vim::PreviousWordStart", { "ignorePunctuation": true }],
-      "g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
+      "shift-w": ["vim::NextWordStart", { "ignore_punctuation": true }],
+      "shift-e": ["vim::NextWordEnd", { "ignore_punctuation": true }],
+      "shift-b": ["vim::PreviousWordStart", { "ignore_punctuation": true }],
+      "g shift-e": ["vim::PreviousWordEnd", { "ignore_punctuation": true }],
       "/": "vim::Search",
       "g /": "pane::DeploySearch",
       "?": ["vim::Search", { "backwards": true }],
@@ -70,20 +70,20 @@
       "[ {": ["vim::UnmatchedBackward", { "char": "{" }],
       "] )": ["vim::UnmatchedForward", { "char": ")" }],
       "[ (": ["vim::UnmatchedBackward", { "char": "(" }],
-      "f": ["vim::PushOperator", { "FindForward": { "before": false } }],
-      "t": ["vim::PushOperator", { "FindForward": { "before": true } }],
-      "shift-f": ["vim::PushOperator", { "FindBackward": { "after": false } }],
-      "shift-t": ["vim::PushOperator", { "FindBackward": { "after": true } }],
-      "m": ["vim::PushOperator", "Mark"],
-      "'": ["vim::PushOperator", { "Jump": { "line": true } }],
-      "`": ["vim::PushOperator", { "Jump": { "line": false } }],
+      "f": ["vim::PushFindForward", { "before": false }],
+      "t": ["vim::PushFindForward", { "before": true }],
+      "shift-f": ["vim::PushFindBackward", { "after": false }],
+      "shift-t": ["vim::PushFindBackward", { "after": true }],
+      "m": "vim::PushMark",
+      "'": ["vim::PushJump", { "line": true }],
+      "`": ["vim::PushJump", { "line": false }],
       ";": "vim::RepeatFind",
       ",": "vim::RepeatFindReversed",
       "ctrl-o": "pane::GoBack",
       "ctrl-i": "pane::GoForward",
       "ctrl-]": "editor::GoToDefinition",
-      "escape": ["vim::SwitchMode", "Normal"],
-      "ctrl-[": ["vim::SwitchMode", "Normal"],
+      "escape": "vim::SwitchToNormalMode",
+      "ctrl-[": "vim::SwitchToNormalMode",
       "v": "vim::ToggleVisual",
       "shift-v": "vim::ToggleVisualLine",
       "ctrl-g": "vim::ShowLocation",
@@ -102,7 +102,7 @@
       "ctrl-e": "vim::LineDown",
       "ctrl-y": "vim::LineUp",
       // "g" commands
-      "g r": ["vim::PushOperator", "ReplaceWithRegister"],
+      "g r": "vim::PushReplaceWithRegister",
       "g g": "vim::StartOfDocument",
       "g h": "editor::Hover",
       "g t": "pane::ActivateNextItem",
@@ -125,17 +125,17 @@
       "g .": "editor::ToggleCodeActions", // zed specific
       "g shift-a": "editor::FindAllReferences", // zed specific
       "g space": "editor::OpenExcerpts", // zed specific
-      "g *": ["vim::MoveToNext", { "partialWord": true }],
-      "g #": ["vim::MoveToPrev", { "partialWord": true }],
-      "g j": ["vim::Down", { "displayLines": true }],
-      "g down": ["vim::Down", { "displayLines": true }],
-      "g k": ["vim::Up", { "displayLines": true }],
-      "g up": ["vim::Up", { "displayLines": true }],
-      "g $": ["vim::EndOfLine", { "displayLines": true }],
-      "g end": ["vim::EndOfLine", { "displayLines": true }],
-      "g 0": ["vim::StartOfLine", { "displayLines": true }],
-      "g home": ["vim::StartOfLine", { "displayLines": true }],
-      "g ^": ["vim::FirstNonWhitespace", { "displayLines": true }],
+      "g *": ["vim::MoveToNext", { "partial_word": true }],
+      "g #": ["vim::MoveToPrev", { "partial_word": true }],
+      "g j": ["vim::Down", { "display_lines": true }],
+      "g down": ["vim::Down", { "display_lines": true }],
+      "g k": ["vim::Up", { "display_lines": true }],
+      "g up": ["vim::Up", { "display_lines": true }],
+      "g $": ["vim::EndOfLine", { "display_lines": true }],
+      "g end": ["vim::EndOfLine", { "display_lines": true }],
+      "g 0": ["vim::StartOfLine", { "display_lines": true }],
+      "g home": ["vim::StartOfLine", { "display_lines": true }],
+      "g ^": ["vim::FirstNonWhitespace", { "display_lines": true }],
       "g v": "vim::RestoreVisualSelection",
       "g ]": "editor::GoToDiagnostic",
       "g [": "editor::GoToPrevDiagnostic",
@@ -147,7 +147,7 @@
       "shift-l": "vim::WindowBottom",
       "q": "vim::ToggleRecord",
       "shift-q": "vim::ReplayLastRecording",
-      "@": ["vim::PushOperator", "ReplayRegister"],
+      "@": "vim::PushReplayRegister",
       // z commands
       "z enter": ["workspace::SendKeystrokes", "z t ^"],
       "z -": ["workspace::SendKeystrokes", "z b ^"],
@@ -166,8 +166,8 @@
       "z f": "editor::FoldSelectedRanges",
       "z shift-m": "editor::FoldAll",
       "z shift-r": "editor::UnfoldAll",
-      "shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }],
-      "shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
+      "shift-z shift-q": ["pane::CloseActiveItem", { "save_intent": "skip" }],
+      "shift-z shift-z": ["pane::CloseActiveItem", { "save_intent": "save_all" }],
       // Count support
       "1": ["vim::Number", 1],
       "2": ["vim::Number", 2],
@@ -194,13 +194,13 @@
       "escape": "editor::Cancel",
       ":": "command_palette::Toggle",
       ".": "vim::Repeat",
-      "c": ["vim::PushOperator", "Change"],
+      "c": "vim::PushChange",
       "shift-c": "vim::ChangeToEndOfLine",
-      "d": ["vim::PushOperator", "Delete"],
+      "d": "vim::PushDelete",
       "shift-d": "vim::DeleteToEndOfLine",
       "shift-j": "vim::JoinLines",
       "g shift-j": "vim::JoinLinesNoWhitespace",
-      "y": ["vim::PushOperator", "Yank"],
+      "y": "vim::PushYank",
       "shift-y": "vim::YankLine",
       "i": "vim::InsertBefore",
       "shift-i": "vim::InsertFirstNonWhitespace",
@@ -217,19 +217,19 @@
       "shift-p": ["vim::Paste", { "before": true }],
       "u": "vim::Undo",
       "ctrl-r": "vim::Redo",
-      "r": ["vim::PushOperator", "Replace"],
+      "r": "vim::PushReplace",
       "s": "vim::Substitute",
       "shift-s": "vim::SubstituteLine",
-      ">": ["vim::PushOperator", "Indent"],
-      "<": ["vim::PushOperator", "Outdent"],
-      "=": ["vim::PushOperator", "AutoIndent"],
-      "!": ["vim::PushOperator", "ShellCommand"],
-      "g u": ["vim::PushOperator", "Lowercase"],
-      "g shift-u": ["vim::PushOperator", "Uppercase"],
-      "g ~": ["vim::PushOperator", "OppositeCase"],
-      "\"": ["vim::PushOperator", "Register"],
-      "g w": ["vim::PushOperator", "Rewrap"],
-      "g q": ["vim::PushOperator", "Rewrap"],
+      ">": "vim::PushIndent",
+      "<": "vim::PushOutdent",
+      "=": "vim::PushAutoIndent",
+      "!": "vim::PushShellCommand",
+      "g u": "vim::PushLowercase",
+      "g shift-u": "vim::PushUppercase",
+      "g ~": "vim::PushOppositeCase",
+      "\"": "vim::PushRegister",
+      "g w": "vim::PushRewrap",
+      "g q": "vim::PushRewrap",
       "ctrl-pagedown": "pane::ActivateNextItem",
       "ctrl-pageup": "pane::ActivatePrevItem",
       "insert": "vim::InsertBefore",
@@ -240,7 +240,7 @@
       "[ d": "editor::GoToPrevDiagnostic",
       "] c": "editor::GoToHunk",
       "[ c": "editor::GoToPrevHunk",
-      "g c": ["vim::PushOperator", "ToggleComments"]
+      "g c": "vim::PushToggleComments"
     }
   },
   {
@@ -265,14 +265,14 @@
       "y": "vim::VisualYank",
       "shift-y": "vim::VisualYankLine",
       "p": "vim::Paste",
-      "shift-p": ["vim::Paste", { "preserveClipboard": true }],
+      "shift-p": ["vim::Paste", { "preserve_clipboard": true }],
       "c": "vim::Substitute",
       "s": "vim::Substitute",
       "shift-r": "vim::SubstituteLine",
       "shift-s": "vim::SubstituteLine",
       "~": "vim::ChangeCase",
-      "*": ["vim::MoveToNext", { "partialWord": true }],
-      "#": ["vim::MoveToPrev", { "partialWord": true }],
+      "*": ["vim::MoveToNext", { "partial_word": true }],
+      "#": ["vim::MoveToPrev", { "partial_word": true }],
       "ctrl-a": "vim::Increment",
       "ctrl-x": "vim::Decrement",
       "g ctrl-a": ["vim::Increment", { "step": true }],
@@ -283,19 +283,19 @@
       "g shift-a": "vim::VisualInsertEndOfLine",
       "shift-j": "vim::JoinLines",
       "g shift-j": "vim::JoinLinesNoWhitespace",
-      "r": ["vim::PushOperator", "Replace"],
-      "ctrl-c": ["vim::SwitchMode", "Normal"],
-      "ctrl-[": ["vim::SwitchMode", "Normal"],
-      "escape": ["vim::SwitchMode", "Normal"],
+      "r": "vim::PushReplace",
+      "ctrl-c": "vim::SwitchToNormalMode",
+      "ctrl-[": "vim::SwitchToNormalMode",
+      "escape": "vim::SwitchToNormalMode",
       ">": "vim::Indent",
       "<": "vim::Outdent",
       "=": "vim::AutoIndent",
       "!": "vim::ShellCommand",
-      "i": ["vim::PushOperator", { "Object": { "around": false } }],
-      "a": ["vim::PushOperator", { "Object": { "around": true } }],
+      "i": ["vim::PushObject", { "around": false }],
+      "a": ["vim::PushObject", { "around": true }],
       "g c": "vim::ToggleComments",
       "g q": "vim::Rewrap",
-      "\"": ["vim::PushOperator", "Register"],
+      "\"": "vim::PushRegister",
       // tree-sitter related commands
       "[ x": "editor::SelectLargerSyntaxNode",
       "] x": "editor::SelectSmallerSyntaxNode"
@@ -310,19 +310,19 @@
       "ctrl-x": null,
       "ctrl-x ctrl-o": "editor::ShowCompletions",
       "ctrl-x ctrl-a": "assistant::InlineAssist", // zed specific
-      "ctrl-x ctrl-c": "editor::ShowInlineCompletion", // zed specific
+      "ctrl-x ctrl-c": "editor::ShowEditPrediction", // zed specific
       "ctrl-x ctrl-l": "editor::ToggleCodeActions", // zed specific
       "ctrl-x ctrl-z": "editor::Cancel",
       "ctrl-w": "editor::DeleteToPreviousWordStart",
       "ctrl-u": "editor::DeleteToBeginningOfLine",
       "ctrl-t": "vim::Indent",
       "ctrl-d": "vim::Outdent",
-      "ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
-      "ctrl-v": ["vim::PushOperator", { "Literal": {} }],
+      "ctrl-k": ["vim::PushDigraph", {}],
+      "ctrl-v": ["vim::PushLiteral", {}],
       "ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
-      "ctrl-q": ["vim::PushOperator", { "Literal": {} }],
-      "ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
-      "ctrl-r": ["vim::PushOperator", "Register"],
+      "ctrl-q": ["vim::PushLiteral", {}],
+      "ctrl-shift-q": ["vim::PushLiteral", {}],
+      "ctrl-r": "vim::PushRegister",
       "insert": "vim::ToggleReplace",
       "ctrl-o": "vim::TemporaryNormal"
     }
@@ -357,11 +357,11 @@
       "ctrl-c": "vim::NormalBefore",
       "ctrl-[": "vim::NormalBefore",
       "escape": "vim::NormalBefore",
-      "ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
-      "ctrl-v": ["vim::PushOperator", { "Literal": {} }],
+      "ctrl-k": ["vim::PushDigraph", {}],
+      "ctrl-v": ["vim::PushLiteral", {}],
       "ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
-      "ctrl-q": ["vim::PushOperator", { "Literal": {} }],
-      "ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
+      "ctrl-q": ["vim::PushLiteral", {}],
+      "ctrl-shift-q": ["vim::PushLiteral", {}],
       "backspace": "vim::UndoReplace",
       "tab": "vim::Tab",
       "enter": "vim::Enter",
@@ -376,9 +376,9 @@
       "ctrl-c": "vim::ClearOperators",
       "ctrl-[": "vim::ClearOperators",
       "escape": "vim::ClearOperators",
-      "ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
-      "ctrl-v": ["vim::PushOperator", { "Literal": {} }],
-      "ctrl-q": ["vim::PushOperator", { "Literal": {} }]
+      "ctrl-k": ["vim::PushDigraph", {}],
+      "ctrl-v": ["vim::PushLiteral", {}],
+      "ctrl-q": ["vim::PushLiteral", {}]
     }
   },
   {
@@ -394,10 +394,10 @@
     "context": "vim_operator == a || vim_operator == i || vim_operator == cs",
     "bindings": {
       "w": "vim::Word",
-      "shift-w": ["vim::Word", { "ignorePunctuation": true }],
+      "shift-w": ["vim::Word", { "ignore_punctuation": true }],
       // Subword TextObject
       // "w": "vim::Subword",
-      // "shift-w": ["vim::Subword", { "ignorePunctuation": true }],
+      // "shift-w": ["vim::Subword", { "ignore_punctuation": true }],
       "t": "vim::Tag",
       "s": "vim::Sentence",
       "p": "vim::Paragraph",
@@ -420,7 +420,7 @@
       ">": "vim::AngleBrackets",
       "a": "vim::Argument",
       "i": "vim::IndentObj",
-      "shift-i": ["vim::IndentObj", { "includeBelow": true }],
+      "shift-i": ["vim::IndentObj", { "include_below": true }],
       "f": "vim::Method",
       "c": "vim::Class",
       "e": "vim::EntireFile"
@@ -431,14 +431,14 @@
     "bindings": {
       "c": "vim::CurrentLine",
       "d": "editor::Rename", // zed specific
-      "s": ["vim::PushOperator", { "ChangeSurrounds": {} }]
+      "s": ["vim::PushChangeSurrounds", {}]
     }
   },
   {
     "context": "vim_operator == d",
     "bindings": {
       "d": "vim::CurrentLine",
-      "s": ["vim::PushOperator", "DeleteSurrounds"],
+      "s": "vim::PushDeleteSurrounds",
       "o": "editor::ToggleSelectedDiffHunks", // "d o"
       "p": "editor::RevertSelectedHunks" // "d p"
     }
@@ -477,7 +477,7 @@
     "context": "vim_operator == y",
     "bindings": {
       "y": "vim::CurrentLine",
-      "s": ["vim::PushOperator", { "AddSurrounds": {} }]
+      "s": ["vim::PushAddSurrounds", {}]
     }
   },
   {
@@ -571,30 +571,30 @@
     "bindings": {
       // window related commands (ctrl-w X)
       "ctrl-w": null,
-      "ctrl-w left": ["workspace::ActivatePaneInDirection", "Left"],
-      "ctrl-w right": ["workspace::ActivatePaneInDirection", "Right"],
-      "ctrl-w up": ["workspace::ActivatePaneInDirection", "Up"],
-      "ctrl-w down": ["workspace::ActivatePaneInDirection", "Down"],
-      "ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
-      "ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
-      "ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
-      "ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
-      "ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
-      "ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
-      "ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
-      "ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
-      "ctrl-w shift-left": ["workspace::SwapPaneInDirection", "Left"],
-      "ctrl-w shift-right": ["workspace::SwapPaneInDirection", "Right"],
-      "ctrl-w shift-up": ["workspace::SwapPaneInDirection", "Up"],
-      "ctrl-w shift-down": ["workspace::SwapPaneInDirection", "Down"],
-      "ctrl-w shift-h": ["workspace::SwapPaneInDirection", "Left"],
-      "ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
-      "ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
-      "ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
-      "ctrl-w >": ["vim::ResizePane", "Widen"],
-      "ctrl-w <": ["vim::ResizePane", "Narrow"],
-      "ctrl-w -": ["vim::ResizePane", "Shorten"],
-      "ctrl-w +": ["vim::ResizePane", "Lengthen"],
+      "ctrl-w left": "workspace::ActivatePaneLeft",
+      "ctrl-w right": "workspace::ActivatePaneRight",
+      "ctrl-w up": "workspace::ActivatePaneUp",
+      "ctrl-w down": "workspace::ActivatePaneDown",
+      "ctrl-w ctrl-h": "workspace::ActivatePaneLeft",
+      "ctrl-w ctrl-l": "workspace::ActivatePaneRight",
+      "ctrl-w ctrl-k": "workspace::ActivatePaneUp",
+      "ctrl-w ctrl-j": "workspace::ActivatePaneDown",
+      "ctrl-w h": "workspace::ActivatePaneLeft",
+      "ctrl-w l": "workspace::ActivatePaneRight",
+      "ctrl-w k": "workspace::ActivatePaneUp",
+      "ctrl-w j": "workspace::ActivatePaneDown",
+      "ctrl-w shift-left": "workspace::SwapPaneLeft",
+      "ctrl-w shift-right": "workspace::SwapPaneRight",
+      "ctrl-w shift-up": "workspace::SwapPaneUp",
+      "ctrl-w shift-down": "workspace::SwapPaneDown",
+      "ctrl-w shift-h": "workspace::SwapPaneLeft",
+      "ctrl-w shift-l": "workspace::SwapPaneRight",
+      "ctrl-w shift-k": "workspace::SwapPaneUp",
+      "ctrl-w shift-j": "workspace::SwapPaneDown",
+      "ctrl-w >": "vim::ResizePaneRight",
+      "ctrl-w <": "vim::ResizePaneLeft",
+      "ctrl-w -": "vim::ResizePaneDown",
+      "ctrl-w +": "vim::ResizePaneUp",
       "ctrl-w _": "vim::MaximizePane",
       "ctrl-w =": "vim::ResetPaneSizes",
       "ctrl-w g t": "pane::ActivateNextItem",

assets/settings/default.json 🔗

@@ -25,7 +25,7 @@
   // Features that can be globally enabled or disabled
   "features": {
     // Which edit prediction provider to use.
-    "inline_completion_provider": "copilot"
+    "edit_prediction_provider": "copilot"
   },
   // The name of a font to use for rendering text in the editor
   "buffer_font_family": "Zed Plex Mono",
@@ -170,7 +170,7 @@
   "show_signature_help_after_edits": false,
   /// Whether to show the edit predictions next to the completions provided by a language server.
   /// Only has an effect if edit prediction provider supports it.
-  "show_inline_completions_in_menu": true,
+  "show_edit_predictions_in_menu": true,
   // Whether to show wrap guides (vertical rulers) in the editor.
   // Setting this to true will show a guide at the 'preferred_line_length' value
   // if 'soft_wrap' is set to 'preferred_line_length', and will show any
@@ -204,11 +204,11 @@
   // no matter how they were inserted.
   "always_treat_brackets_as_autoclosed": false,
   // Controls whether edit predictions are shown immediately (true)
-  // or manually by triggering `editor::ShowInlineCompletion` (false).
-  "show_inline_completions": true,
+  // or manually by triggering `editor::ShowEditPrediction` (false).
+  "show_edit_predictions": true,
   // Controls whether edit predictions are shown in a given language scope.
   // Example: ["string", "comment"]
-  "inline_completions_disabled_in": [],
+  "edit_predictions_disabled_in": [],
   // Whether to show tabs and spaces in the editor.
   // This setting can take four values:
   //
@@ -781,7 +781,7 @@
   // 2. Load direnv configuration through the shell hook, works for POSIX shells and fish.
   //      "load_direnv": "shell_hook"
   "load_direnv": "direct",
-  "inline_completions": {
+  "edit_predictions": {
     // A list of globs representing files that edit predictions should be disabled for.
     "disabled_globs": [
       "**/.env*",

crates/copilot/src/copilot.rs 🔗

@@ -17,7 +17,7 @@ use gpui::{
 use http_client::github::get_release_by_tag_name;
 use http_client::HttpClient;
 use language::{
-    language_settings::{all_language_settings, language_settings, InlineCompletionProvider},
+    language_settings::{all_language_settings, language_settings, EditPredictionProvider},
     point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,
     ToPointUtf16,
 };
@@ -368,8 +368,8 @@ impl Copilot {
         let server_id = self.server_id;
         let http = self.http.clone();
         let node_runtime = self.node_runtime.clone();
-        if all_language_settings(None, cx).inline_completions.provider
-            == InlineCompletionProvider::Copilot
+        if all_language_settings(None, cx).edit_predictions.provider
+            == EditPredictionProvider::Copilot
         {
             if matches!(self.server, CopilotServer::Disabled) {
                 let start_task = cx

crates/copilot/src/copilot_completion_provider.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{Completion, Copilot};
 use anyhow::Result;
 use gpui::{App, Context, Entity, EntityId, Task};
-use inline_completion::{Direction, InlineCompletion, InlineCompletionProvider};
+use inline_completion::{Direction, EditPredictionProvider, InlineCompletion};
 use language::{language_settings::AllLanguageSettings, Buffer, OffsetRangeExt, ToOffset};
 use project::Project;
 use settings::Settings;
@@ -48,7 +48,7 @@ impl CopilotCompletionProvider {
     }
 }
 
-impl InlineCompletionProvider for CopilotCompletionProvider {
+impl EditPredictionProvider for CopilotCompletionProvider {
     fn name() -> &'static str {
         "copilot"
     }
@@ -301,7 +301,7 @@ mod tests {
         .await;
         let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
         cx.update_editor(|editor, window, cx| {
-            editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
+            editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
         });
 
         cx.set_state(indoc! {"
@@ -436,8 +436,8 @@ mod tests {
             assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
             assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
 
-            // AcceptInlineCompletion when there is an active suggestion inserts it.
-            editor.accept_inline_completion(&Default::default(), window, cx);
+            // AcceptEditPrediction when there is an active suggestion inserts it.
+            editor.accept_edit_prediction(&Default::default(), window, cx);
             assert!(!editor.has_active_inline_completion());
             assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
             assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
@@ -482,7 +482,7 @@ mod tests {
         );
 
         cx.update_editor(|editor, window, cx| {
-            editor.next_inline_completion(&Default::default(), window, cx)
+            editor.next_edit_prediction(&Default::default(), window, cx)
         });
         executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
         cx.update_editor(|editor, window, cx| {
@@ -496,8 +496,8 @@ mod tests {
             assert_eq!(editor.text(cx), "fn foo() {\n    \n}");
             assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
 
-            // Using AcceptInlineCompletion again accepts the suggestion.
-            editor.accept_inline_completion(&Default::default(), window, cx);
+            // Using AcceptEditPrediction again accepts the suggestion.
+            editor.accept_edit_prediction(&Default::default(), window, cx);
             assert!(!editor.has_active_inline_completion());
             assert_eq!(editor.text(cx), "fn foo() {\n    let x = 4;\n}");
             assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
@@ -526,7 +526,7 @@ mod tests {
         .await;
         let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
         cx.update_editor(|editor, window, cx| {
-            editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
+            editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
         });
 
         // Setup the editor with a completion request.
@@ -650,7 +650,7 @@ mod tests {
         .await;
         let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
         cx.update_editor(|editor, window, cx| {
-            editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
+            editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
         });
 
         cx.set_state(indoc! {"
@@ -669,7 +669,7 @@ mod tests {
             vec![],
         );
         cx.update_editor(|editor, window, cx| {
-            editor.next_inline_completion(&Default::default(), window, cx)
+            editor.next_edit_prediction(&Default::default(), window, cx)
         });
         executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
         cx.update_editor(|editor, window, cx| {
@@ -740,7 +740,7 @@ mod tests {
         let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
         editor
             .update(cx, |editor, window, cx| {
-                editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
+                editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
             })
             .unwrap();
 
@@ -758,7 +758,7 @@ mod tests {
             editor.change_selections(None, window, cx, |s| {
                 s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
             });
-            editor.next_inline_completion(&Default::default(), window, cx);
+            editor.next_edit_prediction(&Default::default(), window, cx);
         });
         executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
         _ = editor.update(cx, |editor, _, cx| {
@@ -834,7 +834,7 @@ mod tests {
         .await;
         let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
         cx.update_editor(|editor, window, cx| {
-            editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
+            editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
         });
 
         cx.set_state(indoc! {"
@@ -862,7 +862,7 @@ mod tests {
             vec![],
         );
         cx.update_editor(|editor, window, cx| {
-            editor.next_inline_completion(&Default::default(), window, cx)
+            editor.next_edit_prediction(&Default::default(), window, cx)
         });
         executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
         cx.update_editor(|editor, _, cx| {
@@ -930,7 +930,7 @@ mod tests {
     async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut TestAppContext) {
         init_test(cx, |settings| {
             settings
-                .inline_completions
+                .edit_predictions
                 .get_or_insert(Default::default())
                 .disabled_globs = Some(vec![".env*".to_string()]);
         });
@@ -992,7 +992,7 @@ mod tests {
         let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
         editor
             .update(cx, |editor, window, cx| {
-                editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
+                editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
             })
             .unwrap();
 

crates/editor/src/actions.rs 🔗

@@ -3,56 +3,64 @@ use super::*;
 use gpui::{action_as, action_with_deprecated_aliases};
 use schemars::JsonSchema;
 use util::serde::default_true;
-
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct SelectNext {
     #[serde(default)]
     pub replace_newest: bool,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct SelectPrevious {
     #[serde(default)]
     pub replace_newest: bool,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct MoveToBeginningOfLine {
     #[serde(default = "default_true")]
     pub stop_at_soft_wraps: bool,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct SelectToBeginningOfLine {
     #[serde(default)]
     pub(super) stop_at_soft_wraps: bool,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct MovePageUp {
     #[serde(default)]
     pub(super) center_cursor: bool,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct MovePageDown {
     #[serde(default)]
     pub(super) center_cursor: bool,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct MoveToEndOfLine {
     #[serde(default = "default_true")]
     pub stop_at_soft_wraps: bool,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct SelectToEndOfLine {
     #[serde(default)]
     pub(super) stop_at_soft_wraps: bool,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct ToggleCodeActions {
     // Display row from which the action was deployed.
     #[serde(default)]
@@ -61,24 +69,28 @@ pub struct ToggleCodeActions {
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct ConfirmCompletion {
     #[serde(default)]
     pub item_ix: Option<usize>,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct ComposeCompletion {
     #[serde(default)]
     pub item_ix: Option<usize>,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct ConfirmCodeAction {
     #[serde(default)]
     pub item_ix: Option<usize>,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct ToggleComments {
     #[serde(default)]
     pub advance_downwards: bool,
@@ -87,60 +99,70 @@ pub struct ToggleComments {
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct FoldAt {
     #[serde(skip)]
     pub buffer_row: MultiBufferRow,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct UnfoldAt {
     #[serde(skip)]
     pub buffer_row: MultiBufferRow,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct MoveUpByLines {
     #[serde(default)]
     pub(super) lines: u32,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct MoveDownByLines {
     #[serde(default)]
     pub(super) lines: u32,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct SelectUpByLines {
     #[serde(default)]
     pub(super) lines: u32,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct SelectDownByLines {
     #[serde(default)]
     pub(super) lines: u32,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct ExpandExcerpts {
     #[serde(default)]
     pub(super) lines: u32,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct ExpandExcerptsUp {
     #[serde(default)]
     pub(super) lines: u32,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct ExpandExcerptsDown {
     #[serde(default)]
     pub(super) lines: u32,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct ShowCompletions {
     #[serde(default)]
     pub(super) trigger: Option<String>,
@@ -150,23 +172,24 @@ pub struct ShowCompletions {
 pub struct HandleInput(pub String);
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct DeleteToNextWordEnd {
     #[serde(default)]
     pub ignore_newlines: bool,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct DeleteToPreviousWordStart {
     #[serde(default)]
     pub ignore_newlines: bool,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
-pub struct FoldAtLevel {
-    pub level: u32,
-}
+pub struct FoldAtLevel(pub u32);
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct SpawnNearestTask {
     #[serde(default)]
     pub reveal: task::RevealStrategy,
@@ -216,9 +239,9 @@ impl_actions!(
 gpui::actions!(
     editor,
     [
-        AcceptInlineCompletion,
+        AcceptEditPrediction,
         AcceptPartialCopilotSuggestion,
-        AcceptPartialInlineCompletion,
+        AcceptPartialEditPrediction,
         AddSelectionAbove,
         AddSelectionBelow,
         ApplyAllDiffHunks,
@@ -310,7 +333,7 @@ gpui::actions!(
         Newline,
         NewlineAbove,
         NewlineBelow,
-        NextInlineCompletion,
+        NextEditPrediction,
         NextScreen,
         OpenContextMenu,
         OpenExcerpts,
@@ -325,7 +348,7 @@ gpui::actions!(
         PageDown,
         PageUp,
         Paste,
-        PreviousInlineCompletion,
+        PreviousEditPrediction,
         Redo,
         RedoSelection,
         Rename,
@@ -361,7 +384,7 @@ gpui::actions!(
         SelectToStartOfParagraph,
         SelectUp,
         ShowCharacterPalette,
-        ShowInlineCompletion,
+        ShowEditPrediction,
         ShowSignatureHelp,
         ShuffleLines,
         SortLinesCaseInsensitive,
@@ -375,7 +398,7 @@ gpui::actions!(
         ToggleGitBlameInline,
         ToggleIndentGuides,
         ToggleInlayHints,
-        ToggleInlineCompletions,
+        ToggleEditPrediction,
         ToggleLineNumbers,
         SwapSelectionEnds,
         SetMark,

crates/editor/src/editor.rs 🔗

@@ -90,7 +90,7 @@ use hover_popover::{hide_hover, HoverState};
 use indent_guides::ActiveIndentGuidesState;
 use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
 pub use inline_completion::Direction;
-use inline_completion::{InlineCompletionProvider, InlineCompletionProviderHandle};
+use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
 pub use items::MAX_TAB_TITLE_LEN;
 use itertools::Itertools;
 use language::{
@@ -674,7 +674,7 @@ pub struct Editor {
     pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
     gutter_hovered: bool,
     hovered_link_state: Option<HoveredLinkState>,
-    inline_completion_provider: Option<RegisteredInlineCompletionProvider>,
+    edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
     code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
     active_inline_completion: Option<InlineCompletionState>,
     /// Used to prevent flickering as the user types while the menu is open
@@ -1371,7 +1371,7 @@ impl Editor {
             hover_state: Default::default(),
             pending_mouse_down: None,
             hovered_link_state: Default::default(),
-            inline_completion_provider: None,
+            edit_prediction_provider: None,
             active_inline_completion: None,
             stale_inline_completion_in_menu: None,
             previewing_inline_completion: false,
@@ -1524,10 +1524,10 @@ impl Editor {
 
         if self.has_active_inline_completion() {
             key_context.add("copilot_suggestion");
-            key_context.add("inline_completion");
+            key_context.add("edit_prediction");
 
-            if showing_completions || self.inline_completion_requires_modifier(cx) {
-                key_context.add("inline_completion_requires_modifier");
+            if showing_completions || self.edit_prediction_requires_modifier(cx) {
+                key_context.add("edit_prediction_requires_modifier");
             }
         }
 
@@ -1737,15 +1737,15 @@ impl Editor {
         self.semantics_provider = provider;
     }
 
-    pub fn set_inline_completion_provider<T>(
+    pub fn set_edit_prediction_provider<T>(
         &mut self,
         provider: Option<Entity<T>>,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) where
-        T: InlineCompletionProvider,
+        T: EditPredictionProvider,
     {
-        self.inline_completion_provider =
+        self.edit_prediction_provider =
             provider.map(|provider| RegisteredInlineCompletionProvider {
                 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
                     if this.focus_handle.is_focused(window) {
@@ -1877,7 +1877,7 @@ impl Editor {
 
     pub fn toggle_inline_completions(
         &mut self,
-        _: &ToggleInlineCompletions,
+        _: &ToggleEditPrediction,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -1900,11 +1900,11 @@ impl Editor {
 
     pub fn set_show_inline_completions(
         &mut self,
-        show_inline_completions: Option<bool>,
+        show_edit_predictions: Option<bool>,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
-        self.show_inline_completions_override = show_inline_completions;
+        self.show_inline_completions_override = show_edit_predictions;
         self.refresh_inline_completion(false, true, window, cx);
     }
 
@@ -1932,7 +1932,7 @@ impl Editor {
 
         scope.override_name().map_or(false, |scope_name| {
             settings
-                .inline_completions_disabled_in
+                .edit_predictions_disabled_in
                 .iter()
                 .any(|s| s == scope_name)
         })
@@ -3015,7 +3015,7 @@ impl Editor {
             }
 
             let trigger_in_words =
-                this.show_inline_completions_in_menu(cx) || !had_active_inline_completion;
+                this.show_edit_predictions_in_menu(cx) || !had_active_inline_completion;
             this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
             linked_editing_ranges::refresh_linked_ranges(this, window, cx);
             this.refresh_inline_completion(true, false, window, cx);
@@ -3908,7 +3908,7 @@ impl Editor {
                         *editor.context_menu.borrow_mut() =
                             Some(CodeContextMenu::Completions(menu));
 
-                        if editor.show_inline_completions_in_menu(cx) {
+                        if editor.show_edit_predictions_in_menu(cx) {
                             editor.update_visible_inline_completion(window, cx);
                         } else {
                             editor.discard_inline_completion(false, cx);
@@ -3922,7 +3922,7 @@ impl Editor {
                         // If it was already hidden and we don't show inline
                         // completions in the menu, we should also show the
                         // inline-completion when available.
-                        if was_hidden && editor.show_inline_completions_in_menu(cx) {
+                        if was_hidden && editor.show_edit_predictions_in_menu(cx) {
                             editor.update_visible_inline_completion(window, cx);
                         }
                     }
@@ -3972,7 +3972,7 @@ impl Editor {
 
         let entries = completions_menu.entries.borrow();
         let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
-        if self.show_inline_completions_in_menu(cx) {
+        if self.show_edit_predictions_in_menu(cx) {
             self.discard_inline_completion(true, cx);
         }
         let candidate_id = mat.candidate_id;
@@ -4653,7 +4653,7 @@ impl Editor {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Option<()> {
-        let provider = self.inline_completion_provider()?;
+        let provider = self.edit_prediction_provider()?;
         let cursor = self.selections.newest_anchor().head();
         let (buffer, cursor_buffer_position) =
             self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
@@ -4694,7 +4694,7 @@ impl Editor {
         }
     }
 
-    fn inline_completion_requires_modifier(&self, cx: &App) -> bool {
+    fn edit_prediction_requires_modifier(&self, cx: &App) -> bool {
         let cursor = self.selections.newest_anchor().head();
 
         self.buffer
@@ -4731,7 +4731,7 @@ impl Editor {
                     buffer.file(),
                     cx,
                 )
-                .show_inline_completions
+                .show_edit_predictions
         }
     }
 
@@ -4753,7 +4753,7 @@ impl Editor {
         cx: &App,
     ) -> bool {
         maybe!({
-            let provider = self.inline_completion_provider()?;
+            let provider = self.edit_prediction_provider()?;
             if !provider.is_enabled(&buffer, buffer_position, cx) {
                 return Some(false);
             }
@@ -4773,7 +4773,7 @@ impl Editor {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Option<()> {
-        let provider = self.inline_completion_provider()?;
+        let provider = self.edit_prediction_provider()?;
         let cursor = self.selections.newest_anchor().head();
         let (buffer, cursor_buffer_position) =
             self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
@@ -4791,7 +4791,7 @@ impl Editor {
 
     pub fn show_inline_completion(
         &mut self,
-        _: &ShowInlineCompletion,
+        _: &ShowEditPrediction,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -4826,9 +4826,9 @@ impl Editor {
         .detach();
     }
 
-    pub fn next_inline_completion(
+    pub fn next_edit_prediction(
         &mut self,
-        _: &NextInlineCompletion,
+        _: &NextEditPrediction,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -4844,9 +4844,9 @@ impl Editor {
         }
     }
 
-    pub fn previous_inline_completion(
+    pub fn previous_edit_prediction(
         &mut self,
-        _: &PreviousInlineCompletion,
+        _: &PreviousEditPrediction,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -4862,9 +4862,9 @@ impl Editor {
         }
     }
 
-    pub fn accept_inline_completion(
+    pub fn accept_edit_prediction(
         &mut self,
-        _: &AcceptInlineCompletion,
+        _: &AcceptEditPrediction,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -4885,7 +4885,7 @@ impl Editor {
             }
         }
 
-        if self.show_inline_completions_in_menu(cx) {
+        if self.show_edit_predictions_in_menu(cx) {
             self.hide_context_menu(window, cx);
         }
 
@@ -4904,7 +4904,7 @@ impl Editor {
                 });
             }
             InlineCompletion::Edit { edits, .. } => {
-                if let Some(provider) = self.inline_completion_provider() {
+                if let Some(provider) = self.edit_prediction_provider() {
                     provider.accept(cx);
                 }
 
@@ -4931,7 +4931,7 @@ impl Editor {
 
     pub fn accept_partial_inline_completion(
         &mut self,
-        _: &AcceptPartialInlineCompletion,
+        _: &AcceptPartialEditPrediction,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -4988,7 +4988,7 @@ impl Editor {
                     self.refresh_inline_completion(true, true, window, cx);
                     cx.notify();
                 } else {
-                    self.accept_inline_completion(&Default::default(), window, cx);
+                    self.accept_edit_prediction(&Default::default(), window, cx);
                 }
             }
         }
@@ -5003,7 +5003,7 @@ impl Editor {
             self.report_inline_completion_event(false, cx);
         }
 
-        if let Some(provider) = self.inline_completion_provider() {
+        if let Some(provider) = self.edit_prediction_provider() {
             provider.discard(cx);
         }
 
@@ -5011,7 +5011,7 @@ impl Editor {
     }
 
     fn report_inline_completion_event(&self, accepted: bool, cx: &App) {
-        let Some(provider) = self.inline_completion_provider() else {
+        let Some(provider) = self.edit_prediction_provider() else {
             return;
         };
 
@@ -5064,7 +5064,7 @@ impl Editor {
         cx: &App,
     ) -> bool {
         if self.previewing_inline_completion
-            || !self.show_inline_completions_in_menu(cx)
+            || !self.show_edit_predictions_in_menu(cx)
             || !self.should_show_inline_completions(cx)
         {
             return false;
@@ -5074,7 +5074,7 @@ impl Editor {
             return true;
         }
 
-        has_completion && self.inline_completion_requires_modifier(cx)
+        has_completion && self.edit_prediction_requires_modifier(cx)
     }
 
     fn update_inline_completion_preview(
@@ -5083,7 +5083,7 @@ impl Editor {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
-        if !self.show_inline_completions_in_menu(cx) {
+        if !self.show_edit_predictions_in_menu(cx) {
             return;
         }
 
@@ -5103,7 +5103,7 @@ impl Editor {
         let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
         let excerpt_id = cursor.excerpt_id;
 
-        let show_in_menu = self.show_inline_completions_in_menu(cx);
+        let show_in_menu = self.show_edit_predictions_in_menu(cx);
         let completions_menu_has_precedence = !show_in_menu
             && (self.context_menu.borrow().is_some()
                 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
@@ -5123,7 +5123,7 @@ impl Editor {
         }
 
         self.take_active_inline_completion(cx);
-        let provider = self.inline_completion_provider()?;
+        let provider = self.edit_prediction_provider()?;
 
         let (buffer, cursor_buffer_position) =
             self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
@@ -5258,20 +5258,20 @@ impl Editor {
         Some(())
     }
 
-    pub fn inline_completion_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
-        Some(self.inline_completion_provider.as_ref()?.provider.clone())
+    pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
+        Some(self.edit_prediction_provider.as_ref()?.provider.clone())
     }
 
-    fn show_inline_completions_in_menu(&self, cx: &App) -> bool {
+    fn show_edit_predictions_in_menu(&self, cx: &App) -> bool {
         let by_provider = matches!(
             self.menu_inline_completions_policy,
             MenuInlineCompletionsPolicy::ByProvider
         );
 
         by_provider
-            && EditorSettings::get_global(cx).show_inline_completions_in_menu
+            && EditorSettings::get_global(cx).show_edit_predictions_in_menu
             && self
-                .inline_completion_provider()
+                .edit_prediction_provider()
                 .map_or(false, |provider| provider.show_completions_in_menu())
     }
 
@@ -5524,7 +5524,7 @@ impl Editor {
         window: &Window,
         cx: &mut Context<Editor>,
     ) -> Option<AnyElement> {
-        let provider = self.inline_completion_provider.as_ref()?;
+        let provider = self.edit_prediction_provider.as_ref()?;
 
         if provider.provider.needs_terms_acceptance(cx) {
             return Some(
@@ -11808,7 +11808,7 @@ impl Editor {
             return;
         }
 
-        let fold_at_level = fold_at.level;
+        let fold_at_level = fold_at.0;
         let snapshot = self.buffer.read(cx).snapshot(cx);
         let mut to_fold = Vec::new();
         let mut stack = vec![(0, snapshot.max_row().0, 1)];
@@ -14202,14 +14202,14 @@ impl Editor {
             .get("vim_mode")
             == Some(&serde_json::Value::Bool(true));
 
-        let edit_predictions_provider = all_language_settings(file, cx).inline_completions.provider;
+        let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
         let copilot_enabled = edit_predictions_provider
-            == language::language_settings::InlineCompletionProvider::Copilot;
+            == language::language_settings::EditPredictionProvider::Copilot;
         let copilot_enabled_for_language = self
             .buffer
             .read(cx)
             .settings_at(0, cx)
-            .show_inline_completions;
+            .show_edit_predictions;
 
         let project = project.read(cx);
         telemetry::event!(

crates/editor/src/editor_settings.rs 🔗

@@ -35,7 +35,7 @@ pub struct EditorSettings {
     pub auto_signature_help: bool,
     pub show_signature_help_after_edits: bool,
     pub jupyter: Jupyter,
-    pub show_inline_completions_in_menu: bool,
+    pub show_edit_predictions_in_menu: bool,
 }
 
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -372,7 +372,7 @@ pub struct EditorSettingsContent {
     /// Only has an effect if edit prediction provider supports it.
     ///
     /// Default: true
-    pub show_inline_completions_in_menu: Option<bool>,
+    pub show_edit_predictions_in_menu: Option<bool>,
 
     /// Jupyter REPL settings.
     pub jupyter: Option<JupyterContent>,

crates/editor/src/editor_tests.rs 🔗

@@ -1159,7 +1159,7 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
     });
 
     _ = editor.update(cx, |editor, window, cx| {
-        editor.fold_at_level(&FoldAtLevel { level: 2 }, window, cx);
+        editor.fold_at_level(&FoldAtLevel(2), window, cx);
         assert_eq!(
             editor.display_text(cx),
             "
@@ -1183,7 +1183,7 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
             .unindent(),
         );
 
-        editor.fold_at_level(&FoldAtLevel { level: 1 }, window, cx);
+        editor.fold_at_level(&FoldAtLevel(1), window, cx);
         assert_eq!(
             editor.display_text(cx),
             "
@@ -1198,7 +1198,7 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
         );
 
         editor.unfold_all(&UnfoldAll, window, cx);
-        editor.fold_at_level(&FoldAtLevel { level: 0 }, window, cx);
+        editor.fold_at_level(&FoldAtLevel(0), window, cx);
         assert_eq!(
             editor.display_text(cx),
             "

crates/editor/src/element.rs 🔗

@@ -475,8 +475,8 @@ impl EditorElement {
             }
         });
         register_action(editor, window, Editor::show_signature_help);
-        register_action(editor, window, Editor::next_inline_completion);
-        register_action(editor, window, Editor::previous_inline_completion);
+        register_action(editor, window, Editor::next_edit_prediction);
+        register_action(editor, window, Editor::previous_edit_prediction);
         register_action(editor, window, Editor::show_inline_completion);
         register_action(editor, window, Editor::context_menu_first);
         register_action(editor, window, Editor::context_menu_prev);
@@ -486,7 +486,7 @@ impl EditorElement {
         register_action(editor, window, Editor::unique_lines_case_insensitive);
         register_action(editor, window, Editor::unique_lines_case_sensitive);
         register_action(editor, window, Editor::accept_partial_inline_completion);
-        register_action(editor, window, Editor::accept_inline_completion);
+        register_action(editor, window, Editor::accept_edit_prediction);
         register_action(editor, window, Editor::revert_file);
         register_action(editor, window, Editor::revert_selected_hunks);
         register_action(editor, window, Editor::apply_all_diff_hunks);
@@ -3197,7 +3197,7 @@ impl EditorElement {
                     #[cfg(target_os = "macos")]
                     {
                         // let bindings = window.bindings_for_action_in(
-                        //     &crate::AcceptInlineCompletion,
+                        //     &crate::AcceptEditPrediction,
                         //     &self.editor.focus_handle(cx),
                         // );
 
@@ -5770,7 +5770,7 @@ fn inline_completion_accept_indicator(
             }
         }
     } else {
-        let bindings = window.bindings_for_action_in(&crate::AcceptInlineCompletion, &focus_handle);
+        let bindings = window.bindings_for_action_in(&crate::AcceptEditPrediction, &focus_handle);
         if let Some(keystroke) = bindings
             .last()
             .and_then(|binding| binding.keystrokes().first())

crates/editor/src/inline_completion_tests.rs 🔗

@@ -1,6 +1,6 @@
 use gpui::{prelude::*, Entity};
 use indoc::indoc;
-use inline_completion::InlineCompletionProvider;
+use inline_completion::EditPredictionProvider;
 use language::{Language, LanguageConfig};
 use multi_buffer::{Anchor, MultiBufferSnapshot, ToPoint};
 use project::Project;
@@ -315,7 +315,7 @@ fn assert_editor_active_move_completion(
 
 fn accept_completion(cx: &mut EditorTestContext) {
     cx.update_editor(|editor, window, cx| {
-        editor.accept_inline_completion(&crate::AcceptInlineCompletion, window, cx)
+        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
     })
 }
 
@@ -345,7 +345,7 @@ fn assign_editor_completion_provider(
     cx: &mut EditorTestContext,
 ) {
     cx.update_editor(|editor, window, cx| {
-        editor.set_inline_completion_provider(Some(provider), window, cx);
+        editor.set_edit_prediction_provider(Some(provider), window, cx);
     })
 }
 
@@ -363,7 +363,7 @@ impl FakeInlineCompletionProvider {
     }
 }
 
-impl InlineCompletionProvider for FakeInlineCompletionProvider {
+impl EditPredictionProvider for FakeInlineCompletionProvider {
     fn name() -> &'static str {
         "fake-completion-provider"
     }

crates/inline_completion/src/inline_completion.rs 🔗

@@ -38,7 +38,7 @@ impl DataCollectionState {
     }
 }
 
-pub trait InlineCompletionProvider: 'static + Sized {
+pub trait EditPredictionProvider: 'static + Sized {
     fn name() -> &'static str;
     fn display_name() -> &'static str;
     fn show_completions_in_menu() -> bool;
@@ -126,7 +126,7 @@ pub trait InlineCompletionProviderHandle {
 
 impl<T> InlineCompletionProviderHandle for Entity<T>
 where
-    T: InlineCompletionProvider,
+    T: EditPredictionProvider,
 {
     fn name(&self) -> &'static str {
         T::name()

crates/inline_completion_button/src/inline_completion_button.rs 🔗

@@ -1,7 +1,7 @@
 use anyhow::Result;
 use client::UserStore;
 use copilot::{Copilot, Status};
-use editor::{actions::ShowInlineCompletion, scroll::Autoscroll, Editor};
+use editor::{actions::ShowEditPrediction, scroll::Autoscroll, Editor};
 use feature_flags::{
     FeatureFlagAppExt, PredictEditsFeatureFlag, PredictEditsRateCompletionsFeatureFlag,
 };
@@ -13,9 +13,7 @@ use gpui::{
 };
 use indoc::indoc;
 use language::{
-    language_settings::{
-        self, all_language_settings, AllLanguageSettings, InlineCompletionProvider,
-    },
+    language_settings::{self, all_language_settings, AllLanguageSettings, EditPredictionProvider},
     File, Language,
 };
 use regex::Regex;
@@ -37,7 +35,7 @@ use zed_actions::OpenBrowser;
 use zeta::RateCompletionModal;
 
 actions!(zeta, [RateCompletions]);
-actions!(inline_completion, [ToggleMenu]);
+actions!(edit_prediction, [ToggleMenu]);
 
 const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot";
 
@@ -49,7 +47,7 @@ pub struct InlineCompletionButton {
     editor_focus_handle: Option<FocusHandle>,
     language: Option<Arc<Language>>,
     file: Option<Arc<dyn File>>,
-    inline_completion_provider: Option<Arc<dyn inline_completion::InlineCompletionProviderHandle>>,
+    edit_prediction_provider: Option<Arc<dyn inline_completion::InlineCompletionProviderHandle>>,
     fs: Arc<dyn Fs>,
     workspace: WeakEntity<Workspace>,
     user_store: Entity<UserStore>,
@@ -67,10 +65,10 @@ impl Render for InlineCompletionButton {
     fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let all_language_settings = all_language_settings(None, cx);
 
-        match all_language_settings.inline_completions.provider {
-            InlineCompletionProvider::None => div(),
+        match all_language_settings.edit_predictions.provider {
+            EditPredictionProvider::None => div(),
 
-            InlineCompletionProvider::Copilot => {
+            EditPredictionProvider::Copilot => {
                 let Some(copilot) = Copilot::global(cx) else {
                     return div();
                 };
@@ -146,7 +144,7 @@ impl Render for InlineCompletionButton {
                 )
             }
 
-            InlineCompletionProvider::Supermaven => {
+            EditPredictionProvider::Supermaven => {
                 let Some(supermaven) = Supermaven::global(cx) else {
                     return div();
                 };
@@ -196,7 +194,7 @@ impl Render for InlineCompletionButton {
                                             set_completion_provider(
                                                 fs.clone(),
                                                 cx,
-                                                InlineCompletionProvider::Copilot,
+                                                EditPredictionProvider::Copilot,
                                             )
                                         },
                                     )
@@ -226,7 +224,7 @@ impl Render for InlineCompletionButton {
                 );
             }
 
-            InlineCompletionProvider::Zed => {
+            EditPredictionProvider::Zed => {
                 if !cx.has_flag::<PredictEditsFeatureFlag>() {
                     return div();
                 }
@@ -307,7 +305,7 @@ impl Render for InlineCompletionButton {
                     .with_handle(self.popover_menu_handle.clone());
 
                 let is_refreshing = self
-                    .inline_completion_provider
+                    .edit_prediction_provider
                     .as_ref()
                     .map_or(false, |provider| provider.is_refreshing(cx));
 
@@ -352,7 +350,7 @@ impl InlineCompletionButton {
             editor_focus_handle: None,
             language: None,
             file: None,
-            inline_completion_provider: None,
+            edit_prediction_provider: None,
             popover_menu_handle,
             workspace,
             fs,
@@ -375,11 +373,7 @@ impl InlineCompletionButton {
                 .entry("Use Supermaven", None, {
                     let fs = fs.clone();
                     move |_window, cx| {
-                        set_completion_provider(
-                            fs.clone(),
-                            cx,
-                            InlineCompletionProvider::Supermaven,
-                        )
+                        set_completion_provider(fs.clone(), cx, EditPredictionProvider::Supermaven)
                     }
                 })
         })
@@ -394,7 +388,7 @@ impl InlineCompletionButton {
             let fs = fs.clone();
             let language_enabled =
                 language_settings::language_settings(Some(language.name()), None, cx)
-                    .show_inline_completions;
+                    .show_edit_predictions;
 
             menu = menu.toggleable_entry(
                 language.name(),
@@ -418,7 +412,7 @@ impl InlineCompletionButton {
         );
         menu = menu.separator().header("Privacy Settings");
 
-        if let Some(provider) = &self.inline_completion_provider {
+        if let Some(provider) = &self.edit_prediction_provider {
             let data_collection = provider.data_collection_state(cx);
             if data_collection.is_supported() {
                 let provider = provider.clone();
@@ -491,12 +485,12 @@ impl InlineCompletionButton {
                 .separator()
                 .entry(
                     "Predict Edit at Cursor",
-                    Some(Box::new(ShowInlineCompletion)),
+                    Some(Box::new(ShowEditPrediction)),
                     {
                         let editor_focus_handle = editor_focus_handle.clone();
 
                         move |window, cx| {
-                            editor_focus_handle.dispatch_action(&ShowInlineCompletion, window, cx);
+                            editor_focus_handle.dispatch_action(&ShowEditPrediction, window, cx);
                         }
                     },
                 )
@@ -579,7 +573,7 @@ impl InlineCompletionButton {
                 .unwrap_or(true),
             )
         };
-        self.inline_completion_provider = editor.inline_completion_provider();
+        self.edit_prediction_provider = editor.edit_prediction_provider();
         self.language = language.cloned();
         self.file = file;
         self.editor_focus_handle = Some(editor.focus_handle(cx));
@@ -664,7 +658,7 @@ async fn open_disabled_globs_setting_in_editor(
 
             // Ensure that we always have "inline_completions { "disabled_globs": [] }"
             let edits = settings.edits_for_update::<AllLanguageSettings>(&text, |file| {
-                file.inline_completions
+                file.edit_predictions
                     .get_or_insert_with(Default::default)
                     .disabled_globs
                     .get_or_insert_with(Vec::new);
@@ -696,17 +690,17 @@ async fn open_disabled_globs_setting_in_editor(
 }
 
 fn toggle_inline_completions_globally(fs: Arc<dyn Fs>, cx: &mut App) {
-    let show_inline_completions = all_language_settings(None, cx).show_inline_completions(None, cx);
+    let show_edit_predictions = all_language_settings(None, cx).show_inline_completions(None, cx);
     update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
-        file.defaults.show_inline_completions = Some(!show_inline_completions)
+        file.defaults.show_edit_predictions = Some(!show_edit_predictions)
     });
 }
 
-fn set_completion_provider(fs: Arc<dyn Fs>, cx: &mut App, provider: InlineCompletionProvider) {
+fn set_completion_provider(fs: Arc<dyn Fs>, cx: &mut App, provider: EditPredictionProvider) {
     update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
         file.features
             .get_or_insert(Default::default())
-            .inline_completion_provider = Some(provider);
+            .edit_prediction_provider = Some(provider);
     });
 }
 
@@ -715,13 +709,13 @@ fn toggle_show_inline_completions_for_language(
     fs: Arc<dyn Fs>,
     cx: &mut App,
 ) {
-    let show_inline_completions =
+    let show_edit_predictions =
         all_language_settings(None, cx).show_inline_completions(Some(&language), cx);
     update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
         file.languages
             .entry(language.name())
             .or_default()
-            .show_inline_completions = Some(!show_inline_completions);
+            .show_edit_predictions = Some(!show_edit_predictions);
     });
 }
 
@@ -729,6 +723,6 @@ fn hide_copilot(fs: Arc<dyn Fs>, cx: &mut App) {
     update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
         file.features
             .get_or_insert(Default::default())
-            .inline_completion_provider = Some(InlineCompletionProvider::None);
+            .edit_prediction_provider = Some(EditPredictionProvider::None);
     });
 }

crates/language/src/language_settings.rs 🔗

@@ -60,7 +60,7 @@ pub fn all_language_settings<'a>(
 #[derive(Debug, Clone)]
 pub struct AllLanguageSettings {
     /// The edit prediction settings.
-    pub inline_completions: InlineCompletionSettings,
+    pub edit_predictions: EditPredictionSettings,
     defaults: LanguageSettings,
     languages: HashMap<LanguageName, LanguageSettings>,
     pub(crate) file_types: HashMap<Arc<str>, GlobSet>,
@@ -110,11 +110,11 @@ pub struct LanguageSettings {
     /// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language.
     pub language_servers: Vec<String>,
     /// Controls whether edit predictions are shown immediately (true)
-    /// or manually by triggering `editor::ShowInlineCompletion` (false).
-    pub show_inline_completions: bool,
+    /// or manually by triggering `editor::ShowEditPrediction` (false).
+    pub show_edit_predictions: bool,
     /// Controls whether edit predictions are shown in the given language
     /// scopes.
-    pub inline_completions_disabled_in: Vec<String>,
+    pub edit_predictions_disabled_in: Vec<String>,
     /// Whether to show tabs and spaces in the editor.
     pub show_whitespaces: ShowWhitespaceSetting,
     /// Whether to start a new line with a comment when a previous line is a comment as well.
@@ -198,7 +198,7 @@ impl LanguageSettings {
 /// The provider that supplies edit predictions.
 #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
 #[serde(rename_all = "snake_case")]
-pub enum InlineCompletionProvider {
+pub enum EditPredictionProvider {
     None,
     #[default]
     Copilot,
@@ -206,13 +206,13 @@ pub enum InlineCompletionProvider {
     Zed,
 }
 
-impl InlineCompletionProvider {
+impl EditPredictionProvider {
     pub fn is_zed(&self) -> bool {
         match self {
-            InlineCompletionProvider::Zed => true,
-            InlineCompletionProvider::None
-            | InlineCompletionProvider::Copilot
-            | InlineCompletionProvider::Supermaven => false,
+            EditPredictionProvider::Zed => true,
+            EditPredictionProvider::None
+            | EditPredictionProvider::Copilot
+            | EditPredictionProvider::Supermaven => false,
         }
     }
 }
@@ -220,9 +220,9 @@ impl InlineCompletionProvider {
 /// The settings for edit predictions, such as [GitHub Copilot](https://github.com/features/copilot)
 /// or [Supermaven](https://supermaven.com).
 #[derive(Clone, Debug, Default)]
-pub struct InlineCompletionSettings {
+pub struct EditPredictionSettings {
     /// The provider that supplies edit predictions.
-    pub provider: InlineCompletionProvider,
+    pub provider: EditPredictionProvider,
     /// A list of globs representing files that edit predictions should be disabled for.
     pub disabled_globs: Vec<GlobMatcher>,
     /// When to show edit predictions previews in buffer.
@@ -248,7 +248,7 @@ pub struct AllLanguageSettingsContent {
     pub features: Option<FeaturesContent>,
     /// The edit prediction settings.
     #[serde(default)]
-    pub inline_completions: Option<InlineCompletionSettingsContent>,
+    pub edit_predictions: Option<InlineCompletionSettingsContent>,
     /// The default language settings.
     #[serde(flatten)]
     pub defaults: LanguageSettingsContent,
@@ -347,11 +347,11 @@ pub struct LanguageSettingsContent {
     #[serde(default)]
     pub language_servers: Option<Vec<String>>,
     /// Controls whether edit predictions are shown immediately (true)
-    /// or manually by triggering `editor::ShowInlineCompletion` (false).
+    /// or manually by triggering `editor::ShowEditPrediction` (false).
     ///
     /// Default: true
     #[serde(default)]
-    pub show_inline_completions: Option<bool>,
+    pub show_edit_predictions: Option<bool>,
     /// Controls whether edit predictions are shown in the given language
     /// scopes.
     ///
@@ -359,7 +359,7 @@ pub struct LanguageSettingsContent {
     ///
     /// Default: []
     #[serde(default)]
-    pub inline_completions_disabled_in: Option<Vec<String>>,
+    pub edit_predictions_disabled_in: Option<Vec<String>>,
     /// Whether to show tabs and spaces in the editor.
     #[serde(default)]
     pub show_whitespaces: Option<ShowWhitespaceSetting>,
@@ -442,7 +442,7 @@ pub struct FeaturesContent {
     /// Whether the GitHub Copilot feature is enabled.
     pub copilot: Option<bool>,
     /// Determines which edit prediction provider to use.
-    pub inline_completion_provider: Option<InlineCompletionProvider>,
+    pub edit_prediction_provider: Option<EditPredictionProvider>,
 }
 
 /// Controls the soft-wrapping behavior in the editor.
@@ -906,7 +906,7 @@ impl AllLanguageSettings {
     /// Returns whether edit predictions are enabled for the given path.
     pub fn inline_completions_enabled_for_path(&self, path: &Path) -> bool {
         !self
-            .inline_completions
+            .edit_predictions
             .disabled_globs
             .iter()
             .any(|glob| glob.is_match(path))
@@ -915,12 +915,12 @@ impl AllLanguageSettings {
     /// Returns whether edit predictions are enabled for the given language and path.
     pub fn show_inline_completions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
         self.language(None, language.map(|l| l.name()).as_ref(), cx)
-            .show_inline_completions
+            .show_edit_predictions
     }
 
     /// Returns the edit predictions preview mode for the given language and path.
     pub fn inline_completions_preview_mode(&self) -> InlineCompletionPreviewMode {
-        self.inline_completions.inline_preview
+        self.edit_predictions.inline_preview
     }
 }
 
@@ -1015,18 +1015,18 @@ impl settings::Settings for AllLanguageSettings {
         }
 
         let mut copilot_enabled = default_value.features.as_ref().and_then(|f| f.copilot);
-        let mut inline_completion_provider = default_value
+        let mut edit_prediction_provider = default_value
             .features
             .as_ref()
-            .and_then(|f| f.inline_completion_provider);
+            .and_then(|f| f.edit_prediction_provider);
         let mut inline_completions_preview = default_value
-            .inline_completions
+            .edit_predictions
             .as_ref()
             .map(|inline_completions| inline_completions.inline_preview)
             .ok_or_else(Self::missing_default)?;
 
         let mut completion_globs: HashSet<&String> = default_value
-            .inline_completions
+            .edit_predictions
             .as_ref()
             .and_then(|c| c.disabled_globs.as_ref())
             .map(|globs| globs.iter().collect())
@@ -1051,12 +1051,12 @@ impl settings::Settings for AllLanguageSettings {
             if let Some(provider) = user_settings
                 .features
                 .as_ref()
-                .and_then(|f| f.inline_completion_provider)
+                .and_then(|f| f.edit_prediction_provider)
             {
-                inline_completion_provider = Some(provider);
+                edit_prediction_provider = Some(provider);
             }
 
-            if let Some(inline_completions) = user_settings.inline_completions.as_ref() {
+            if let Some(inline_completions) = user_settings.edit_predictions.as_ref() {
                 inline_completions_preview = inline_completions.inline_preview;
 
                 if let Some(disabled_globs) = inline_completions.disabled_globs.as_ref() {
@@ -1102,13 +1102,13 @@ impl settings::Settings for AllLanguageSettings {
         }
 
         Ok(Self {
-            inline_completions: InlineCompletionSettings {
-                provider: if let Some(provider) = inline_completion_provider {
+            edit_predictions: EditPredictionSettings {
+                provider: if let Some(provider) = edit_prediction_provider {
                     provider
                 } else if copilot_enabled.unwrap_or(true) {
-                    InlineCompletionProvider::Copilot
+                    EditPredictionProvider::Copilot
                 } else {
-                    InlineCompletionProvider::None
+                    EditPredictionProvider::None
                 },
                 disabled_globs: completion_globs
                     .iter()
@@ -1219,12 +1219,12 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
     );
     merge(&mut settings.language_servers, src.language_servers.clone());
     merge(
-        &mut settings.show_inline_completions,
-        src.show_inline_completions,
+        &mut settings.show_edit_predictions,
+        src.show_edit_predictions,
     );
     merge(
-        &mut settings.inline_completions_disabled_in,
-        src.inline_completions_disabled_in.clone(),
+        &mut settings.edit_predictions_disabled_in,
+        src.edit_predictions_disabled_in.clone(),
     );
     merge(&mut settings.show_whitespaces, src.show_whitespaces);
     merge(

crates/migrator/Cargo.toml 🔗

@@ -0,0 +1,22 @@
+[package]
+name = "migrator"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/migrator.rs"
+doctest = false
+
+[dependencies]
+collections.workspace = true
+tree-sitter-json.workspace = true
+tree-sitter.workspace = true
+convert_case.workspace = true
+
+[dev-dependencies]
+pretty_assertions.workspace = true

crates/migrator/src/migrator.rs 🔗

@@ -0,0 +1,863 @@
+use collections::HashMap;
+use convert_case::{Case, Casing};
+use std::{cmp::Reverse, ops::Range, sync::LazyLock};
+use tree_sitter::{Query, QueryMatch};
+
+fn migrate(text: &str, patterns: MigrationPatterns, query: &Query) -> Option<String> {
+    let mut parser = tree_sitter::Parser::new();
+    parser
+        .set_language(&tree_sitter_json::LANGUAGE.into())
+        .unwrap();
+    let syntax_tree = parser.parse(&text, None).unwrap();
+
+    let mut cursor = tree_sitter::QueryCursor::new();
+    let matches = cursor.matches(query, syntax_tree.root_node(), text.as_bytes());
+
+    let mut edits = vec![];
+    for mat in matches {
+        if let Some((_, callback)) = patterns.get(mat.pattern_index) {
+            edits.extend(callback(&text, &mat, query));
+        }
+    }
+
+    edits.sort_by_key(|(range, _)| (range.start, Reverse(range.end)));
+    edits.dedup_by(|(range_b, _), (range_a, _)| {
+        range_a.contains(&range_b.start) || range_a.contains(&range_b.end)
+    });
+
+    if edits.is_empty() {
+        None
+    } else {
+        let mut text = text.to_string();
+        for (range, replacement) in edits.into_iter().rev() {
+            text.replace_range(range, &replacement);
+        }
+        Some(text)
+    }
+}
+
+pub fn migrate_keymap(text: &str) -> Option<String> {
+    let transformed_text = migrate(
+        text,
+        KEYMAP_MIGRATION_TRANSFORMATION_PATTERNS,
+        &KEYMAP_MIGRATION_TRANSFORMATION_QUERY,
+    );
+    let replacement_text = migrate(
+        &transformed_text.as_ref().unwrap_or(&text.to_string()),
+        KEYMAP_MIGRATION_REPLACEMENT_PATTERNS,
+        &KEYMAP_MIGRATION_REPLACEMENT_QUERY,
+    );
+    replacement_text.or(transformed_text)
+}
+
+pub fn migrate_settings(text: &str) -> Option<String> {
+    migrate(
+        &text,
+        SETTINGS_MIGRATION_PATTERNS,
+        &SETTINGS_MIGRATION_QUERY,
+    )
+}
+
+type MigrationPatterns = &'static [(
+    &'static str,
+    fn(&str, &QueryMatch, &Query) -> Option<(Range<usize>, String)>,
+)];
+
+static KEYMAP_MIGRATION_TRANSFORMATION_PATTERNS: MigrationPatterns = &[
+    (ACTION_ARRAY_PATTERN, replace_array_with_single_string),
+    (
+        ACTION_ARGUMENT_OBJECT_PATTERN,
+        replace_action_argument_object_with_single_value,
+    ),
+    (ACTION_STRING_PATTERN, rename_string_action),
+    (CONTEXT_PREDICATE_PATTERN, rename_context_key),
+];
+
+static KEYMAP_MIGRATION_TRANSFORMATION_QUERY: LazyLock<Query> = LazyLock::new(|| {
+    Query::new(
+        &tree_sitter_json::LANGUAGE.into(),
+        &KEYMAP_MIGRATION_TRANSFORMATION_PATTERNS
+            .iter()
+            .map(|pattern| pattern.0)
+            .collect::<String>(),
+    )
+    .unwrap()
+});
+
+const ACTION_ARRAY_PATTERN: &str = r#"(document
+    (array
+   	    (object
+            (pair
+                key: (string (string_content) @name)
+                value: (
+                    (object
+                        (pair
+                            key: (string)
+                            value: ((array
+                                . (string (string_content) @action_name)
+                                . (string (string_content) @argument)
+                                .)) @array
+                        )
+                    )
+                )
+            )
+        )
+    )
+    (#eq? @name "bindings")
+)"#;
+
+fn replace_array_with_single_string(
+    contents: &str,
+    mat: &QueryMatch,
+    query: &Query,
+) -> Option<(Range<usize>, String)> {
+    let array_ix = query.capture_index_for_name("array").unwrap();
+    let action_name_ix = query.capture_index_for_name("action_name").unwrap();
+    let argument_ix = query.capture_index_for_name("argument").unwrap();
+
+    let action_name = contents.get(
+        mat.nodes_for_capture_index(action_name_ix)
+            .next()?
+            .byte_range(),
+    )?;
+    let argument = contents.get(
+        mat.nodes_for_capture_index(argument_ix)
+            .next()?
+            .byte_range(),
+    )?;
+
+    let replacement = TRANSFORM_ARRAY.get(&(action_name, argument))?;
+    let replacement_as_string = format!("\"{replacement}\"");
+    let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range();
+
+    Some((range_to_replace, replacement_as_string))
+}
+
+#[rustfmt::skip]
+static TRANSFORM_ARRAY: LazyLock<HashMap<(&str, &str), &str>> = LazyLock::new(|| {
+    HashMap::from_iter([
+        // activate
+        (("workspace::ActivatePaneInDirection", "Up"), "workspace::ActivatePaneUp"),
+        (("workspace::ActivatePaneInDirection", "Down"), "workspace::ActivatePaneDown"),
+        (("workspace::ActivatePaneInDirection", "Left"), "workspace::ActivatePaneLeft"),
+        (("workspace::ActivatePaneInDirection", "Right"), "workspace::ActivatePaneRight"),
+        // swap
+        (("workspace::SwapPaneInDirection", "Up"), "workspace::SwapPaneUp"),
+        (("workspace::SwapPaneInDirection", "Down"), "workspace::SwapPaneDown"),
+        (("workspace::SwapPaneInDirection", "Left"), "workspace::SwapPaneLeft"),
+        (("workspace::SwapPaneInDirection", "Right"), "workspace::SwapPaneRight"),
+        // menu
+        (("app_menu::NavigateApplicationMenuInDirection", "Left"), "app_menu::ActivateMenuLeft"),
+        (("app_menu::NavigateApplicationMenuInDirection", "Right"), "app_menu::ActivateMenuRight"),
+        // vim push
+        (("vim::PushOperator", "Change"), "vim::PushChange"),
+        (("vim::PushOperator", "Delete"), "vim::PushDelete"),
+        (("vim::PushOperator", "Yank"), "vim::PushYank"),
+        (("vim::PushOperator", "Replace"), "vim::PushReplace"),
+        (("vim::PushOperator", "DeleteSurrounds"), "vim::PushDeleteSurrounds"),
+        (("vim::PushOperator", "Mark"), "vim::PushMark"),
+        (("vim::PushOperator", "Indent"), "vim::PushIndent"),
+        (("vim::PushOperator", "Outdent"), "vim::PushOutdent"),
+        (("vim::PushOperator", "AutoIndent"), "vim::PushAutoIndent"),
+        (("vim::PushOperator", "Rewrap"), "vim::PushRewrap"),
+        (("vim::PushOperator", "ShellCommand"), "vim::PushShellCommand"),
+        (("vim::PushOperator", "Lowercase"), "vim::PushLowercase"),
+        (("vim::PushOperator", "Uppercase"), "vim::PushUppercase"),
+        (("vim::PushOperator", "OppositeCase"), "vim::PushOppositeCase"),
+        (("vim::PushOperator", "Register"), "vim::PushRegister"),
+        (("vim::PushOperator", "RecordRegister"), "vim::PushRecordRegister"),
+        (("vim::PushOperator", "ReplayRegister"), "vim::PushReplayRegister"),
+        (("vim::PushOperator", "ReplaceWithRegister"), "vim::PushReplaceWithRegister"),
+        (("vim::PushOperator", "ToggleComments"), "vim::PushToggleComments"),
+        // vim switch
+        (("vim::SwitchMode", "Normal"), "vim::SwitchToNormalMode"),
+        (("vim::SwitchMode", "Insert"), "vim::SwitchToInsertMode"),
+        (("vim::SwitchMode", "Replace"), "vim::SwitchToReplaceMode"),
+        (("vim::SwitchMode", "Visual"), "vim::SwitchToVisualMode"),
+        (("vim::SwitchMode", "VisualLine"), "vim::SwitchToVisualLineMode"),
+        (("vim::SwitchMode", "VisualBlock"), "vim::SwitchToVisualBlockMode"),
+        (("vim::SwitchMode", "HelixNormal"), "vim::SwitchToHelixNormalMode"),
+        // vim resize
+        (("vim::ResizePane", "Widen"), "vim::ResizePaneRight"),
+        (("vim::ResizePane", "Narrow"), "vim::ResizePaneLeft"),
+        (("vim::ResizePane", "Shorten"), "vim::ResizePaneDown"),
+        (("vim::ResizePane", "Lengthen"), "vim::ResizePaneUp"),
+    ])
+});
+
+const ACTION_ARGUMENT_OBJECT_PATTERN: &str = r#"(document
+    (array
+        (object
+            (pair
+                key: (string (string_content) @name)
+                value: (
+                    (object
+                        (pair
+                            key: (string)
+                            value: ((array
+                                . (string (string_content) @action_name)
+                                . (object
+                                    (pair
+                                    key: (string (string_content) @action_key)
+                                    value: (_)  @argument))
+                                . ) @array
+                            ))
+                        )
+                    )
+                )
+            )
+        )
+        (#eq? @name "bindings")
+)"#;
+
+/// [ "editor::FoldAtLevel", { "level": 1 } ] -> [ "editor::FoldAtLevel", 1 ]
+fn replace_action_argument_object_with_single_value(
+    contents: &str,
+    mat: &QueryMatch,
+    query: &Query,
+) -> Option<(Range<usize>, String)> {
+    let array_ix = query.capture_index_for_name("array").unwrap();
+    let action_name_ix = query.capture_index_for_name("action_name").unwrap();
+    let action_key_ix = query.capture_index_for_name("action_key").unwrap();
+    let argument_ix = query.capture_index_for_name("argument").unwrap();
+
+    let action_name = contents.get(
+        mat.nodes_for_capture_index(action_name_ix)
+            .next()?
+            .byte_range(),
+    )?;
+    let action_key = contents.get(
+        mat.nodes_for_capture_index(action_key_ix)
+            .next()?
+            .byte_range(),
+    )?;
+    let argument = contents.get(
+        mat.nodes_for_capture_index(argument_ix)
+            .next()?
+            .byte_range(),
+    )?;
+
+    let new_action_name = UNWRAP_OBJECTS.get(&action_name)?.get(&action_key)?;
+
+    let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range();
+    let replacement = format!("[\"{}\", {}]", new_action_name, argument);
+    Some((range_to_replace, replacement))
+}
+
+// "ctrl-k ctrl-1": [ "editor::PushOperator", { "Object": {} } ] -> [ "editor::vim::PushObject", {} ]
+static UNWRAP_OBJECTS: LazyLock<HashMap<&str, HashMap<&str, &str>>> = LazyLock::new(|| {
+    HashMap::from_iter([
+        (
+            "editor::FoldAtLevel",
+            HashMap::from_iter([("level", "editor::FoldAtLevel")]),
+        ),
+        (
+            "vim::PushOperator",
+            HashMap::from_iter([
+                ("Object", "vim::PushObject"),
+                ("FindForward", "vim::PushFindForward"),
+                ("FindBackward", "vim::PushFindBackward"),
+                ("Sneak", "vim::PushSneak"),
+                ("SneakBackward", "vim::PushSneakBackward"),
+                ("AddSurrounds", "vim::PushAddSurrounds"),
+                ("ChangeSurrounds", "vim::PushChangeSurrounds"),
+                ("Jump", "vim::PushJump"),
+                ("Digraph", "vim::PushDigraph"),
+                ("Literal", "vim::PushLiteral"),
+            ]),
+        ),
+    ])
+});
+
+static KEYMAP_MIGRATION_REPLACEMENT_PATTERNS: MigrationPatterns = &[(
+    ACTION_ARGUMENT_SNAKE_CASE_PATTERN,
+    action_argument_snake_case,
+)];
+
+static KEYMAP_MIGRATION_REPLACEMENT_QUERY: LazyLock<Query> = LazyLock::new(|| {
+    Query::new(
+        &tree_sitter_json::LANGUAGE.into(),
+        &KEYMAP_MIGRATION_REPLACEMENT_PATTERNS
+            .iter()
+            .map(|pattern| pattern.0)
+            .collect::<String>(),
+    )
+    .unwrap()
+});
+
+const ACTION_STRING_PATTERN: &str = r#"(document
+    (array
+        (object
+            (pair
+                key: (string (string_content) @name)
+                value: (
+                    (object
+                        (pair
+                            key: (string)
+                            value: (string (string_content) @action_name)
+                        )
+                    )
+                )
+            )
+        )
+    )
+    (#eq? @name "bindings")
+)"#;
+
+fn rename_string_action(
+    contents: &str,
+    mat: &QueryMatch,
+    query: &Query,
+) -> Option<(Range<usize>, String)> {
+    let action_name_ix = query.capture_index_for_name("action_name").unwrap();
+    let action_name_range = mat
+        .nodes_for_capture_index(action_name_ix)
+        .next()?
+        .byte_range();
+    let action_name = contents.get(action_name_range.clone())?;
+    let new_action_name = STRING_REPLACE.get(&action_name)?;
+    Some((action_name_range, new_action_name.to_string()))
+}
+
+// "ctrl-k ctrl-1": "inline_completion::ToggleMenu" -> "edit_prediction::ToggleMenu"
+#[rustfmt::skip]
+static STRING_REPLACE: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
+    HashMap::from_iter([
+        ("inline_completion::ToggleMenu", "edit_prediction::ToggleMenu"),
+        ("editor::NextInlineCompletion", "editor::NextEditPrediction"),
+        ("editor::PreviousInlineCompletion", "editor::PreviousEditPrediction"),
+        ("editor::AcceptPartialInlineCompletion", "editor::AcceptPartialEditPrediction"),
+        ("editor::ShowInlineCompletion", "editor::ShowEditPrediction"),
+        ("editor::AcceptInlineCompletion", "editor::AcceptEditPrediction"),
+        ("editor::ToggleInlineCompletions", "editor::ToggleEditPrediction"),
+    ])
+});
+
+const CONTEXT_PREDICATE_PATTERN: &str = r#"
+(array
+    (object
+        (pair
+            key: (string (string_content) @name)
+            value: (string (string_content) @context_predicate)
+        )
+    )
+)
+(#eq? @name "context")
+"#;
+
+fn rename_context_key(
+    contents: &str,
+    mat: &QueryMatch,
+    query: &Query,
+) -> Option<(Range<usize>, String)> {
+    let context_predicate_ix = query.capture_index_for_name("context_predicate").unwrap();
+    let context_predicate_range = mat
+        .nodes_for_capture_index(context_predicate_ix)
+        .next()?
+        .byte_range();
+    let old_predicate = contents.get(context_predicate_range.clone())?.to_string();
+    let mut new_predicate = old_predicate.to_string();
+    for (old_key, new_key) in CONTEXT_REPLACE.iter() {
+        new_predicate = new_predicate.replace(old_key, new_key);
+    }
+    if new_predicate != old_predicate {
+        Some((context_predicate_range, new_predicate.to_string()))
+    } else {
+        None
+    }
+}
+
+const ACTION_ARGUMENT_SNAKE_CASE_PATTERN: &str = r#"(document
+    (array
+        (object
+            (pair
+                key: (string (string_content) @name)
+                value: (
+                    (object
+                        (pair
+                            key: (string)
+                            value: ((array
+                                . (string (string_content) @action_name)
+                                . (object
+                                    (pair
+                                    key: (string (string_content) @argument_key)
+                                    value: (_)  @argument_value))
+                                . ) @array
+                            ))
+                        )
+                    )
+                )
+            )
+        )
+    (#eq? @name "bindings")
+)"#;
+
+fn is_snake_case(text: &str) -> bool {
+    text == text.to_case(Case::Snake)
+}
+
+fn to_snake_case(text: &str) -> String {
+    text.to_case(Case::Snake)
+}
+
+/// [ "editor::FoldAtLevel", { "SomeKey": "Value" } ] -> [ "editor::FoldAtLevel", { "some_key" : "value" } ]
+fn action_argument_snake_case(
+    contents: &str,
+    mat: &QueryMatch,
+    query: &Query,
+) -> Option<(Range<usize>, String)> {
+    let array_ix = query.capture_index_for_name("array").unwrap();
+    let action_name_ix = query.capture_index_for_name("action_name").unwrap();
+    let argument_key_ix = query.capture_index_for_name("argument_key").unwrap();
+    let argument_value_ix = query.capture_index_for_name("argument_value").unwrap();
+    let action_name = contents.get(
+        mat.nodes_for_capture_index(action_name_ix)
+            .next()?
+            .byte_range(),
+    )?;
+
+    let argument_key = contents.get(
+        mat.nodes_for_capture_index(argument_key_ix)
+            .next()?
+            .byte_range(),
+    )?;
+
+    let argument_value_node = mat.nodes_for_capture_index(argument_value_ix).next()?;
+    let argument_value = contents.get(argument_value_node.byte_range())?;
+
+    let mut needs_replacement = false;
+    let mut new_key = argument_key.to_string();
+    if !is_snake_case(argument_key) {
+        new_key = to_snake_case(argument_key);
+        needs_replacement = true;
+    }
+
+    let mut new_value = argument_value.to_string();
+    if argument_value_node.kind() == "string" {
+        let inner_value = argument_value.trim_matches('"');
+        if !is_snake_case(inner_value) {
+            new_value = format!("\"{}\"", to_snake_case(inner_value));
+            needs_replacement = true;
+        }
+    }
+
+    if !needs_replacement {
+        return None;
+    }
+
+    let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range();
+    let replacement = format!(
+        "[\"{}\", {{ \"{}\": {} }}]",
+        action_name, new_key, new_value
+    );
+
+    Some((range_to_replace, replacement))
+}
+
+// "context": "Editor && inline_completion && !showing_completions" -> "Editor && edit_prediction && !showing_completions"
+pub static CONTEXT_REPLACE: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
+    HashMap::from_iter([
+        ("inline_completion", "edit_prediction"),
+        (
+            "inline_completion_requires_modifier",
+            "edit_prediction_requires_modifier",
+        ),
+    ])
+});
+
+static SETTINGS_MIGRATION_PATTERNS: MigrationPatterns = &[
+    (SETTINGS_STRING_REPLACE_QUERY, replace_setting_name),
+    (SETTINGS_REPLACE_NESTED_KEY, replace_setting_nested_key),
+    (
+        SETTINGS_REPLACE_IN_LANGUAGES_QUERY,
+        replace_setting_in_languages,
+    ),
+];
+
+static SETTINGS_MIGRATION_QUERY: LazyLock<Query> = LazyLock::new(|| {
+    Query::new(
+        &tree_sitter_json::LANGUAGE.into(),
+        &SETTINGS_MIGRATION_PATTERNS
+            .iter()
+            .map(|pattern| pattern.0)
+            .collect::<String>(),
+    )
+    .unwrap()
+});
+
+static SETTINGS_STRING_REPLACE_QUERY: &str = r#"(document
+    (object
+        (pair
+            key: (string (string_content) @name)
+            value: (_)
+        )
+    )
+)"#;
+
+fn replace_setting_name(
+    contents: &str,
+    mat: &QueryMatch,
+    query: &Query,
+) -> Option<(Range<usize>, String)> {
+    let setting_capture_ix = query.capture_index_for_name("name").unwrap();
+    let setting_name_range = mat
+        .nodes_for_capture_index(setting_capture_ix)
+        .next()?
+        .byte_range();
+    let setting_name = contents.get(setting_name_range.clone())?;
+    let new_setting_name = SETTINGS_STRING_REPLACE.get(&setting_name)?;
+    Some((setting_name_range, new_setting_name.to_string()))
+}
+
+#[rustfmt::skip]
+pub static SETTINGS_STRING_REPLACE: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
+    HashMap::from_iter([
+        ("show_inline_completions_in_menu", "show_edit_predictions_in_menu"),
+        ("show_inline_completions", "show_edit_predictions"),
+        ("inline_completions_disabled_in", "edit_predictions_disabled_in"),
+        ("inline_completions", "edit_predictions")
+    ])
+});
+
+static SETTINGS_REPLACE_NESTED_KEY: &str = r#"
+(object
+  (pair
+    key: (string (string_content) @parent_key)
+    value: (object
+        (pair
+            key: (string (string_content) @setting_name)
+            value: (_) @value
+        )
+    )
+  )
+)
+"#;
+
+fn replace_setting_nested_key(
+    contents: &str,
+    mat: &QueryMatch,
+    query: &Query,
+) -> Option<(Range<usize>, String)> {
+    let parent_object_capture_ix = query.capture_index_for_name("parent_key").unwrap();
+    let parent_object_range = mat
+        .nodes_for_capture_index(parent_object_capture_ix)
+        .next()?
+        .byte_range();
+    let parent_object_name = contents.get(parent_object_range.clone())?;
+
+    let setting_name_ix = query.capture_index_for_name("setting_name").unwrap();
+    let setting_range = mat
+        .nodes_for_capture_index(setting_name_ix)
+        .next()?
+        .byte_range();
+    let setting_name = contents.get(setting_range.clone())?;
+
+    let new_setting_name = SETTINGS_NESTED_STRING_REPLACE
+        .get(&parent_object_name)?
+        .get(setting_name)?;
+
+    Some((setting_range, new_setting_name.to_string()))
+}
+
+// "features": {
+//   "inline_completion_provider": "copilot"
+// },
+pub static SETTINGS_NESTED_STRING_REPLACE: LazyLock<
+    HashMap<&'static str, HashMap<&'static str, &'static str>>,
+> = LazyLock::new(|| {
+    HashMap::from_iter([(
+        "features",
+        HashMap::from_iter([("inline_completion_provider", "edit_prediction_provider")]),
+    )])
+});
+
+static SETTINGS_REPLACE_IN_LANGUAGES_QUERY: &str = r#"
+(object
+  (pair
+    key: (string (string_content) @languages)
+    value: (object
+    (pair
+        key: (string)
+        value: (object
+            (pair
+                key: (string (string_content) @setting_name)
+                value: (_) @value
+            )
+        )
+    ))
+  )
+)
+(#eq? @languages "languages")
+"#;
+
+fn replace_setting_in_languages(
+    contents: &str,
+    mat: &QueryMatch,
+    query: &Query,
+) -> Option<(Range<usize>, String)> {
+    let setting_capture_ix = query.capture_index_for_name("setting_name").unwrap();
+    let setting_name_range = mat
+        .nodes_for_capture_index(setting_capture_ix)
+        .next()?
+        .byte_range();
+    let setting_name = contents.get(setting_name_range.clone())?;
+    let new_setting_name = LANGUAGE_SETTINGS_REPLACE.get(&setting_name)?;
+
+    Some((setting_name_range, new_setting_name.to_string()))
+}
+
+#[rustfmt::skip]
+static LANGUAGE_SETTINGS_REPLACE: LazyLock<
+    HashMap<&'static str, &'static str>,
+> = LazyLock::new(|| {
+    HashMap::from_iter([
+        ("show_inline_completions", "show_edit_predictions"),
+        ("inline_completions_disabled_in", "edit_predictions_disabled_in"),
+    ])
+});
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    fn assert_migrate_keymap(input: &str, output: Option<&str>) {
+        let migrated = migrate_keymap(&input);
+        pretty_assertions::assert_eq!(migrated.as_deref(), output);
+    }
+
+    fn assert_migrate_settings(input: &str, output: Option<&str>) {
+        let migrated = migrate_settings(&input);
+        pretty_assertions::assert_eq!(migrated.as_deref(), output);
+    }
+
+    #[test]
+    fn test_replace_array_with_single_string() {
+        assert_migrate_keymap(
+            r#"
+            [
+                {
+                    "bindings": {
+                        "cmd-1": ["workspace::ActivatePaneInDirection", "Up"]
+                    }
+                }
+            ]
+            "#,
+            Some(
+                r#"
+            [
+                {
+                    "bindings": {
+                        "cmd-1": "workspace::ActivatePaneUp"
+                    }
+                }
+            ]
+            "#,
+            ),
+        )
+    }
+
+    #[test]
+    fn test_replace_action_argument_object_with_single_value() {
+        assert_migrate_keymap(
+            r#"
+            [
+                {
+                    "bindings": {
+                        "cmd-1": ["editor::FoldAtLevel", { "level": 1 }]
+                    }
+                }
+            ]
+            "#,
+            Some(
+                r#"
+            [
+                {
+                    "bindings": {
+                        "cmd-1": ["editor::FoldAtLevel", 1]
+                    }
+                }
+            ]
+            "#,
+            ),
+        )
+    }
+
+    #[test]
+    fn test_replace_action_argument_object_with_single_value_2() {
+        assert_migrate_keymap(
+            r#"
+            [
+                {
+                    "bindings": {
+                        "cmd-1": ["vim::PushOperator", { "Object": { "some" : "value" } }]
+                    }
+                }
+            ]
+            "#,
+            Some(
+                r#"
+            [
+                {
+                    "bindings": {
+                        "cmd-1": ["vim::PushObject", { "some" : "value" }]
+                    }
+                }
+            ]
+            "#,
+            ),
+        )
+    }
+
+    #[test]
+    fn test_rename_string_action() {
+        assert_migrate_keymap(
+            r#"
+                [
+                    {
+                        "bindings": {
+                            "cmd-1": "inline_completion::ToggleMenu"
+                        }
+                    }
+                ]
+            "#,
+            Some(
+                r#"
+                [
+                    {
+                        "bindings": {
+                            "cmd-1": "edit_prediction::ToggleMenu"
+                        }
+                    }
+                ]
+            "#,
+            ),
+        )
+    }
+
+    #[test]
+    fn test_rename_context_key() {
+        assert_migrate_keymap(
+            r#"
+                [
+                    {
+                        "context": "Editor && inline_completion && !showing_completions"
+                    }
+                ]
+            "#,
+            Some(
+                r#"
+                [
+                    {
+                        "context": "Editor && edit_prediction && !showing_completions"
+                    }
+                ]
+            "#,
+            ),
+        )
+    }
+
+    #[test]
+    fn test_action_argument_snake_case() {
+        // First performs transformations, then replacements
+        assert_migrate_keymap(
+            r#"
+            [
+                {
+                    "bindings": {
+                        "cmd-1": ["vim::PushOperator", { "Object": { "SomeKey": "Value" } }],
+                        "cmd-2": ["vim::SomeOtherAction", { "OtherKey": "Value" }],
+                        "cmd-3": ["vim::SomeDifferentAction", { "OtherKey": true }],
+                        "cmd-4": ["vim::OneMore", { "OtherKey": 4 }]
+                    }
+                }
+            ]
+            "#,
+            Some(
+                r#"
+            [
+                {
+                    "bindings": {
+                        "cmd-1": ["vim::PushObject", { "some_key": "value" }],
+                        "cmd-2": ["vim::SomeOtherAction", { "other_key": "value" }],
+                        "cmd-3": ["vim::SomeDifferentAction", { "other_key": true }],
+                        "cmd-4": ["vim::OneMore", { "other_key": 4 }]
+                    }
+                }
+            ]
+            "#,
+            ),
+        )
+    }
+
+    #[test]
+    fn test_replace_setting_name() {
+        assert_migrate_settings(
+            r#"
+                {
+                    "show_inline_completions_in_menu": true,
+                    "show_inline_completions": true,
+                    "inline_completions_disabled_in": ["string"],
+                    "inline_completions": { "some" : "value" }
+                }
+            "#,
+            Some(
+                r#"
+                {
+                    "show_edit_predictions_in_menu": true,
+                    "show_edit_predictions": true,
+                    "edit_predictions_disabled_in": ["string"],
+                    "edit_predictions": { "some" : "value" }
+                }
+            "#,
+            ),
+        )
+    }
+
+    #[test]
+    fn test_nested_string_replace_for_settings() {
+        assert_migrate_settings(
+            r#"
+                {
+                    "features": {
+                        "inline_completion_provider": "zed"
+                    },
+                }
+            "#,
+            Some(
+                r#"
+                {
+                    "features": {
+                        "edit_prediction_provider": "zed"
+                    },
+                }
+            "#,
+            ),
+        )
+    }
+
+    #[test]
+    fn test_replace_settings_in_languages() {
+        assert_migrate_settings(
+            r#"
+                {
+                    "languages": {
+                        "Astro": {
+                            "show_inline_completions": true
+                        }
+                    }
+                }
+            "#,
+            Some(
+                r#"
+                {
+                    "languages": {
+                        "Astro": {
+                            "show_edit_predictions": true
+                        }
+                    }
+                }
+            "#,
+            ),
+        )
+    }
+}

crates/picker/src/picker.rs 🔗

@@ -26,6 +26,7 @@ actions!(picker, [ConfirmCompletion]);
 /// ConfirmInput is an alternative editor action which - instead of selecting active picker entry - treats pickers editor input literally,
 /// performing some kind of action on it.
 #[derive(Clone, PartialEq, Deserialize, JsonSchema, Default)]
+#[serde(deny_unknown_fields)]
 pub struct ConfirmInput {
     pub secondary: bool,
 }

crates/project_panel/src/project_panel.rs 🔗

@@ -162,12 +162,14 @@ struct EntryDetails {
 }
 
 #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
+#[serde(deny_unknown_fields)]
 struct Delete {
     #[serde(default)]
     pub skip_prompt: bool,
 }
 
 #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
+#[serde(deny_unknown_fields)]
 struct Trash {
     #[serde(default)]
     pub skip_prompt: bool,

crates/search/src/buffer_search.rs 🔗

@@ -44,6 +44,7 @@ use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults};
 const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50;
 
 #[derive(PartialEq, Clone, Deserialize, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct Deploy {
     #[serde(default = "util::serde::default_true")]
     pub focus: bool,

crates/settings/Cargo.toml 🔗

@@ -35,6 +35,7 @@ smallvec.workspace = true
 tree-sitter-json.workspace = true
 tree-sitter.workspace = true
 util.workspace = true
+migrator.workspace = true
 
 [dev-dependencies]
 fs = { workspace = true, features = ["test-support"] }

crates/settings/src/keymap_file.rs 🔗

@@ -1,22 +1,24 @@
-use std::rc::Rc;
-
-use crate::{settings_store::parse_json_with_comments, SettingsAssets};
-use anyhow::anyhow;
+use anyhow::{anyhow, Context as _, Result};
 use collections::{HashMap, IndexMap};
+use fs::Fs;
 use gpui::{
     Action, ActionBuildError, App, InvalidKeystrokeError, KeyBinding, KeyBindingContextPredicate,
     NoAction, SharedString, KEYSTROKE_PARSE_EXPECTED_MESSAGE,
 };
+use migrator::migrate_keymap;
 use schemars::{
     gen::{SchemaGenerator, SchemaSettings},
     schema::{ArrayValidation, InstanceType, Schema, SchemaObject, SubschemaValidation},
     JsonSchema,
 };
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
 use serde_json::Value;
-use std::fmt::Write;
+use std::rc::Rc;
+use std::{fmt::Write, sync::Arc};
 use util::{asset_str, markdown::MarkdownString};
 
+use crate::{settings_store::parse_json_with_comments, SettingsAssets};
+
 // Note that the doc comments on these are shown by json-language-server when editing the keymap, so
 // they should be considered user-facing documentation. Documentation is not handled well with
 // schemars-0.8 - when there are newlines, it is rendered as plaintext (see
@@ -28,12 +30,12 @@ use util::{asset_str, markdown::MarkdownString};
 
 /// Keymap configuration consisting of sections. Each section may have a context predicate which
 /// determines whether its bindings are used.
-#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
+#[derive(Debug, Deserialize, Default, Clone, JsonSchema, Serialize)]
 #[serde(transparent)]
 pub struct KeymapFile(Vec<KeymapSection>);
 
 /// Keymap section which binds keystrokes to actions.
-#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
+#[derive(Debug, Deserialize, Default, Clone, JsonSchema, Serialize)]
 pub struct KeymapSection {
     /// Determines when these bindings are active. When just a name is provided, like `Editor` or
     /// `Workspace`, the bindings will be active in that context. Boolean expressions like `X && Y`,
@@ -78,9 +80,9 @@ impl KeymapSection {
 /// Unlike the other json types involved in keymaps (including actions), this doc-comment will not
 /// be included in the generated JSON schema, as it manually defines its `JsonSchema` impl. The
 /// actual schema used for it is automatically generated in `KeymapFile::generate_json_schema`.
-#[derive(Debug, Deserialize, Default, Clone)]
+#[derive(Debug, Deserialize, Default, Clone, Serialize)]
 #[serde(transparent)]
-pub struct KeymapAction(Value);
+pub struct KeymapAction(pub(crate) Value);
 
 impl std::fmt::Display for KeymapAction {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -114,9 +116,11 @@ impl JsonSchema for KeymapAction {
 pub enum KeymapFileLoadResult {
     Success {
         key_bindings: Vec<KeyBinding>,
+        keymap_file: KeymapFile,
     },
     SomeFailedToLoad {
         key_bindings: Vec<KeyBinding>,
+        keymap_file: KeymapFile,
         error_message: MarkdownString,
     },
     JsonParseFailure {
@@ -150,6 +154,7 @@ impl KeymapFile {
             KeymapFileLoadResult::SomeFailedToLoad {
                 key_bindings,
                 error_message,
+                ..
             } if key_bindings.is_empty() => Err(anyhow!(
                 "Error loading built-in keymap \"{asset_path}\": {error_message}"
             )),
@@ -164,7 +169,7 @@ impl KeymapFile {
     #[cfg(feature = "test-support")]
     pub fn load_panic_on_failure(content: &str, cx: &App) -> Vec<KeyBinding> {
         match Self::load(content, cx) {
-            KeymapFileLoadResult::Success { key_bindings } => key_bindings,
+            KeymapFileLoadResult::Success { key_bindings, .. } => key_bindings,
             KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => {
                 panic!("{error_message}");
             }
@@ -180,6 +185,7 @@ impl KeymapFile {
         if content.is_empty() {
             return KeymapFileLoadResult::Success {
                 key_bindings: Vec::new(),
+                keymap_file: KeymapFile(Vec::new()),
             };
         }
         let keymap_file = match parse_json_with_comments::<Self>(content) {
@@ -266,7 +272,10 @@ impl KeymapFile {
         }
 
         if errors.is_empty() {
-            KeymapFileLoadResult::Success { key_bindings }
+            KeymapFileLoadResult::Success {
+                key_bindings,
+                keymap_file,
+            }
         } else {
             let mut error_message = "Errors in user keymap file.\n".to_owned();
             for (context, section_errors) in errors {
@@ -284,6 +293,7 @@ impl KeymapFile {
             }
             KeymapFileLoadResult::SomeFailedToLoad {
                 key_bindings,
+                keymap_file,
                 error_message: MarkdownString(error_message),
             }
         }
@@ -551,6 +561,55 @@ impl KeymapFile {
     pub fn sections(&self) -> impl DoubleEndedIterator<Item = &KeymapSection> {
         self.0.iter()
     }
+
+    async fn load_keymap_file(fs: &Arc<dyn Fs>) -> Result<String> {
+        match fs.load(paths::keymap_file()).await {
+            result @ Ok(_) => result,
+            Err(err) => {
+                if let Some(e) = err.downcast_ref::<std::io::Error>() {
+                    if e.kind() == std::io::ErrorKind::NotFound {
+                        return Ok(crate::initial_keymap_content().to_string());
+                    }
+                }
+                Err(err)
+            }
+        }
+    }
+
+    pub fn should_migrate_keymap(keymap_file: Self) -> bool {
+        let Ok(old_text) = serde_json::to_string(&keymap_file) else {
+            return false;
+        };
+        migrate_keymap(&old_text).is_some()
+    }
+
+    pub async fn migrate_keymap(fs: Arc<dyn Fs>) -> Result<()> {
+        let old_text = Self::load_keymap_file(&fs).await?;
+        let Some(new_text) = migrate_keymap(&old_text) else {
+            return Ok(());
+        };
+        let initial_path = paths::keymap_file().as_path();
+        if fs.is_file(initial_path).await {
+            let backup_path = paths::home_dir().join(".zed_keymap_backup");
+            fs.atomic_write(backup_path, old_text)
+                .await
+                .with_context(|| {
+                    "Failed to create settings backup in home directory".to_string()
+                })?;
+            let resolved_path = fs.canonicalize(initial_path).await.with_context(|| {
+                format!("Failed to canonicalize keymap path {:?}", initial_path)
+            })?;
+            fs.atomic_write(resolved_path.clone(), new_text)
+                .await
+                .with_context(|| format!("Failed to write keymap to file {:?}", resolved_path))?;
+        } else {
+            fs.atomic_write(initial_path.to_path_buf(), new_text)
+                .await
+                .with_context(|| format!("Failed to write keymap to file {:?}", initial_path))?;
+        }
+
+        Ok(())
+    }
 }
 
 // Double quotes a string and wraps it in backticks for markdown inline code..
@@ -560,7 +619,7 @@ fn inline_code_string(text: &str) -> MarkdownString {
 
 #[cfg(test)]
 mod tests {
-    use crate::KeymapFile;
+    use super::KeymapFile;
 
     #[test]
     fn can_deserialize_keymap_with_trailing_comma() {

crates/settings/src/settings_file.rs 🔗

@@ -81,7 +81,7 @@ pub fn watch_config_file(
 pub fn handle_settings_file_changes(
     mut user_settings_file_rx: mpsc::UnboundedReceiver<String>,
     cx: &mut App,
-    settings_changed: impl Fn(Option<anyhow::Error>, &mut App) + 'static,
+    settings_changed: impl Fn(Result<serde_json::Value, anyhow::Error>, &mut App) + 'static,
 ) {
     let user_settings_content = cx
         .background_executor()
@@ -92,7 +92,7 @@ pub fn handle_settings_file_changes(
         if let Err(err) = &result {
             log::error!("Failed to load user settings: {err}");
         }
-        settings_changed(result.err(), cx);
+        settings_changed(result, cx);
     });
     cx.spawn(move |cx| async move {
         while let Some(user_settings_content) = user_settings_file_rx.next().await {
@@ -101,7 +101,7 @@ pub fn handle_settings_file_changes(
                 if let Err(err) = &result {
                     log::error!("Failed to load user settings: {err}");
                 }
-                settings_changed(result.err(), cx);
+                settings_changed(result, cx);
                 cx.refresh_windows();
             });
             if result.is_err() {

crates/settings/src/settings_store.rs 🔗

@@ -4,6 +4,7 @@ use ec4rs::{ConfigParser, PropertiesSource, Section};
 use fs::Fs;
 use futures::{channel::mpsc, future::LocalBoxFuture, FutureExt, StreamExt};
 use gpui::{App, AsyncApp, BorrowAppContext, Global, Task, UpdateGlobal};
+use migrator::migrate_settings;
 use paths::{local_settings_file_relative_path, EDITORCONFIG_NAME};
 use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};
 use serde::{de::DeserializeOwned, Deserialize, Serialize};
@@ -17,7 +18,9 @@ use std::{
     sync::{Arc, LazyLock},
 };
 use tree_sitter::Query;
-use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _};
+use util::RangeExt;
+
+use util::{merge_non_null_json_value_into, ResultExt as _};
 
 pub type EditorconfigProperties = ec4rs::Properties;
 
@@ -544,7 +547,11 @@ impl SettingsStore {
     }
 
     /// Sets the user settings via a JSON string.
-    pub fn set_user_settings(&mut self, user_settings_content: &str, cx: &mut App) -> Result<()> {
+    pub fn set_user_settings(
+        &mut self,
+        user_settings_content: &str,
+        cx: &mut App,
+    ) -> Result<serde_json::Value> {
         let settings: serde_json::Value = if user_settings_content.is_empty() {
             parse_json_with_comments("{}")?
         } else {
@@ -552,9 +559,9 @@ impl SettingsStore {
         };
 
         anyhow::ensure!(settings.is_object(), "settings must be an object");
-        self.raw_user_settings = settings;
+        self.raw_user_settings = settings.clone();
         self.recompute_values(None, cx)?;
-        Ok(())
+        Ok(settings)
     }
 
     pub fn set_server_settings(
@@ -988,6 +995,52 @@ impl SettingsStore {
         properties.use_fallbacks();
         Some(properties)
     }
+
+    pub fn should_migrate_settings(settings: &serde_json::Value) -> bool {
+        let Ok(old_text) = serde_json::to_string(settings) else {
+            return false;
+        };
+        migrate_settings(&old_text).is_some()
+    }
+
+    pub fn migrate_settings(&self, fs: Arc<dyn Fs>) {
+        self.setting_file_updates_tx
+            .unbounded_send(Box::new(move |_: AsyncApp| {
+                async move {
+                    let old_text = Self::load_settings(&fs).await?;
+                    let Some(new_text) = migrate_settings(&old_text) else {
+                        return anyhow::Ok(());
+                    };
+                    let initial_path = paths::settings_file().as_path();
+                    if fs.is_file(initial_path).await {
+                        let backup_path = paths::home_dir().join(".zed_settings_backup");
+                        fs.atomic_write(backup_path, old_text)
+                            .await
+                            .with_context(|| {
+                                "Failed to create settings backup in home directory".to_string()
+                            })?;
+                        let resolved_path =
+                            fs.canonicalize(initial_path).await.with_context(|| {
+                                format!("Failed to canonicalize settings path {:?}", initial_path)
+                            })?;
+                        fs.atomic_write(resolved_path.clone(), new_text)
+                            .await
+                            .with_context(|| {
+                                format!("Failed to write settings to file {:?}", resolved_path)
+                            })?;
+                    } else {
+                        fs.atomic_write(initial_path.to_path_buf(), new_text)
+                            .await
+                            .with_context(|| {
+                                format!("Failed to write settings to file {:?}", initial_path)
+                            })?;
+                    }
+                    anyhow::Ok(())
+                }
+                .boxed_local()
+            }))
+            .ok();
+    }
 }
 
 #[derive(Debug, Clone, PartialEq)]
@@ -1235,7 +1288,9 @@ fn replace_value_in_json_text(
 
         let found_key = text
             .get(key_range.clone())
-            .map(|key_text| key_text == format!("\"{}\"", key_path[depth]))
+            .map(|key_text| {
+                depth < key_path.len() && key_text == format!("\"{}\"", key_path[depth])
+            })
             .unwrap_or(false);
 
         if found_key {

crates/supermaven/src/supermaven.rs 🔗

@@ -31,16 +31,16 @@ pub fn init(client: Arc<Client>, cx: &mut App) {
     let supermaven = cx.new(|_| Supermaven::Starting);
     Supermaven::set_global(supermaven.clone(), cx);
 
-    let mut provider = all_language_settings(None, cx).inline_completions.provider;
-    if provider == language::language_settings::InlineCompletionProvider::Supermaven {
+    let mut provider = all_language_settings(None, cx).edit_predictions.provider;
+    if provider == language::language_settings::EditPredictionProvider::Supermaven {
         supermaven.update(cx, |supermaven, cx| supermaven.start(client.clone(), cx));
     }
 
     cx.observe_global::<SettingsStore>(move |cx| {
-        let new_provider = all_language_settings(None, cx).inline_completions.provider;
+        let new_provider = all_language_settings(None, cx).edit_predictions.provider;
         if new_provider != provider {
             provider = new_provider;
-            if provider == language::language_settings::InlineCompletionProvider::Supermaven {
+            if provider == language::language_settings::EditPredictionProvider::Supermaven {
                 supermaven.update(cx, |supermaven, cx| supermaven.start(client.clone(), cx));
             } else {
                 supermaven.update(cx, |supermaven, _cx| supermaven.stop());

crates/supermaven/src/supermaven_completion_provider.rs 🔗

@@ -2,7 +2,7 @@ use crate::{Supermaven, SupermavenCompletionStateId};
 use anyhow::Result;
 use futures::StreamExt as _;
 use gpui::{App, Context, Entity, EntityId, Task};
-use inline_completion::{Direction, InlineCompletion, InlineCompletionProvider};
+use inline_completion::{Direction, EditPredictionProvider, InlineCompletion};
 use language::{Anchor, Buffer, BufferSnapshot};
 use project::Project;
 use std::{
@@ -97,7 +97,7 @@ fn completion_from_diff(
     }
 }
 
-impl InlineCompletionProvider for SupermavenCompletionProvider {
+impl EditPredictionProvider for SupermavenCompletionProvider {
     fn name() -> &'static str {
         "supermaven"
     }

crates/tab_switcher/src/tab_switcher.rs 🔗

@@ -25,6 +25,7 @@ use workspace::{
 const PANEL_WIDTH_REMS: f32 = 28.;
 
 #[derive(PartialEq, Clone, Deserialize, JsonSchema, Default)]
+#[serde(deny_unknown_fields)]
 pub struct Toggle {
     #[serde(default)]
     pub select_last: bool,

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -35,10 +35,11 @@ use workspace::{
     item::SerializableItem,
     move_active_item, move_item, pane,
     ui::IconName,
-    ActivateNextPane, ActivatePane, ActivatePaneInDirection, ActivatePreviousPane,
-    DraggedSelection, DraggedTab, ItemId, MoveItemToPane, MoveItemToPaneInDirection, NewTerminal,
-    Pane, PaneGroup, SplitDirection, SplitDown, SplitLeft, SplitRight, SplitUp,
-    SwapPaneInDirection, ToggleZoom, Workspace,
+    ActivateNextPane, ActivatePane, ActivatePaneDown, ActivatePaneLeft, ActivatePaneRight,
+    ActivatePaneUp, ActivatePreviousPane, DraggedSelection, DraggedTab, ItemId, MoveItemToPane,
+    MoveItemToPaneInDirection, NewTerminal, Pane, PaneGroup, SplitDirection, SplitDown, SplitLeft,
+    SplitRight, SplitUp, SwapPaneDown, SwapPaneLeft, SwapPaneRight, SwapPaneUp, ToggleZoom,
+    Workspace,
 };
 
 use anyhow::{anyhow, Context as _, Result};
@@ -889,6 +890,37 @@ impl TerminalPanel {
             is_enabled_in_workspace(workspace.read(cx), cx)
         })
     }
+
+    fn activate_pane_in_direction(
+        &mut self,
+        direction: SplitDirection,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if let Some(pane) = self
+            .center
+            .find_pane_in_direction(&self.active_pane, direction, cx)
+        {
+            window.focus(&pane.focus_handle(cx));
+        } else {
+            self.workspace
+                .update(cx, |workspace, cx| {
+                    workspace.activate_pane_in_direction(direction, window, cx)
+                })
+                .ok();
+        }
+    }
+
+    fn swap_pane_in_direction(&mut self, direction: SplitDirection, cx: &mut Context<Self>) {
+        if let Some(to) = self
+            .center
+            .find_pane_in_direction(&self.active_pane, direction, cx)
+            .cloned()
+        {
+            self.center.swap(&self.active_pane, &to);
+            cx.notify();
+        }
+    }
 }
 
 fn is_enabled_in_workspace(workspace: &Workspace, cx: &App) -> bool {
@@ -1145,24 +1177,28 @@ impl Render for TerminalPanel {
             .ok()
             .map(|div| {
                 div.on_action({
-                    cx.listener(
-                        |terminal_panel, action: &ActivatePaneInDirection, window, cx| {
-                            if let Some(pane) = terminal_panel.center.find_pane_in_direction(
-                                &terminal_panel.active_pane,
-                                action.0,
-                                cx,
-                            ) {
-                                window.focus(&pane.focus_handle(cx));
-                            } else {
-                                terminal_panel
-                                    .workspace
-                                    .update(cx, |workspace, cx| {
-                                        workspace.activate_pane_in_direction(action.0, window, cx)
-                                    })
-                                    .ok();
-                            }
-                        },
-                    )
+                    cx.listener(|terminal_panel, _: &ActivatePaneLeft, window, cx| {
+                        terminal_panel.activate_pane_in_direction(SplitDirection::Left, window, cx);
+                    })
+                })
+                .on_action({
+                    cx.listener(|terminal_panel, _: &ActivatePaneRight, window, cx| {
+                        terminal_panel.activate_pane_in_direction(
+                            SplitDirection::Right,
+                            window,
+                            cx,
+                        );
+                    })
+                })
+                .on_action({
+                    cx.listener(|terminal_panel, _: &ActivatePaneUp, window, cx| {
+                        terminal_panel.activate_pane_in_direction(SplitDirection::Up, window, cx);
+                    })
+                })
+                .on_action({
+                    cx.listener(|terminal_panel, _: &ActivatePaneDown, window, cx| {
+                        terminal_panel.activate_pane_in_direction(SplitDirection::Down, window, cx);
+                    })
                 })
                 .on_action(
                     cx.listener(|terminal_panel, _action: &ActivateNextPane, window, cx| {
@@ -1210,18 +1246,18 @@ impl Render for TerminalPanel {
                         }
                     }),
                 )
-                .on_action(
-                    cx.listener(|terminal_panel, action: &SwapPaneInDirection, _, cx| {
-                        if let Some(to) = terminal_panel
-                            .center
-                            .find_pane_in_direction(&terminal_panel.active_pane, action.0, cx)
-                            .cloned()
-                        {
-                            terminal_panel.center.swap(&terminal_panel.active_pane, &to);
-                            cx.notify();
-                        }
-                    }),
-                )
+                .on_action(cx.listener(|terminal_panel, _: &SwapPaneLeft, _, cx| {
+                    terminal_panel.swap_pane_in_direction(SplitDirection::Left, cx);
+                }))
+                .on_action(cx.listener(|terminal_panel, _: &SwapPaneRight, _, cx| {
+                    terminal_panel.swap_pane_in_direction(SplitDirection::Right, cx);
+                }))
+                .on_action(cx.listener(|terminal_panel, _: &SwapPaneUp, _, cx| {
+                    terminal_panel.swap_pane_in_direction(SplitDirection::Up, cx);
+                }))
+                .on_action(cx.listener(|terminal_panel, _: &SwapPaneDown, _, cx| {
+                    terminal_panel.swap_pane_in_direction(SplitDirection::Down, cx);
+                }))
                 .on_action(
                     cx.listener(|terminal_panel, action: &MoveItemToPane, window, cx| {
                         let Some(&target_pane) =

crates/title_bar/src/application_menu.rs 🔗

@@ -1,19 +1,31 @@
-use gpui::{impl_actions, Entity, OwnedMenu, OwnedMenuItem};
+use gpui::{Entity, OwnedMenu, OwnedMenuItem};
+
+#[cfg(not(target_os = "macos"))]
+use gpui::{actions, impl_actions};
+
+#[cfg(not(target_os = "macos"))]
 use schemars::JsonSchema;
+#[cfg(not(target_os = "macos"))]
 use serde::Deserialize;
+
 use smallvec::SmallVec;
 use ui::{prelude::*, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip};
 
-impl_actions!(
-    app_menu,
-    [OpenApplicationMenu, NavigateApplicationMenuInDirection]
-);
+#[cfg(not(target_os = "macos"))]
+impl_actions!(app_menu, [OpenApplicationMenu]);
 
+#[cfg(not(target_os = "macos"))]
+actions!(app_menu, [ActivateMenuRight, ActivateMenuLeft]);
+
+#[cfg(not(target_os = "macos"))]
 #[derive(Clone, Deserialize, JsonSchema, PartialEq, Default)]
 pub struct OpenApplicationMenu(String);
 
-#[derive(Clone, Deserialize, JsonSchema, PartialEq, Default)]
-pub struct NavigateApplicationMenuInDirection(String);
+#[cfg(not(target_os = "macos"))]
+pub enum ActivateDirection {
+    Left,
+    Right,
+}
 
 #[derive(Clone)]
 struct MenuEntry {
@@ -190,7 +202,7 @@ impl ApplicationMenu {
     #[cfg(not(target_os = "macos"))]
     pub fn navigate_menus_in_direction(
         &mut self,
-        action: &NavigateApplicationMenuInDirection,
+        direction: ActivateDirection,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -202,22 +214,21 @@ impl ApplicationMenu {
             return;
         };
 
-        let next_index = match action.0.as_str() {
-            "Left" => {
+        let next_index = match direction {
+            ActivateDirection::Left => {
                 if current_index == 0 {
                     self.entries.len() - 1
                 } else {
                     current_index - 1
                 }
             }
-            "Right" => {
+            ActivateDirection::Right => {
                 if current_index == self.entries.len() - 1 {
                     0
                 } else {
                     current_index + 1
                 }
             }
-            _ => return,
         };
 
         self.entries[current_index].handle.hide(cx);

crates/title_bar/src/title_bar.rs 🔗

@@ -9,7 +9,9 @@ mod stories;
 use crate::application_menu::ApplicationMenu;
 
 #[cfg(not(target_os = "macos"))]
-use crate::application_menu::{NavigateApplicationMenuInDirection, OpenApplicationMenu};
+use crate::application_menu::{
+    ActivateDirection, ActivateMenuLeft, ActivateMenuRight, OpenApplicationMenu,
+};
 
 use crate::platforms::{platform_linux, platform_mac, platform_windows};
 use auto_update::AutoUpdateStatus;
@@ -78,22 +80,36 @@ pub fn init(cx: &mut App) {
         });
 
         #[cfg(not(target_os = "macos"))]
-        workspace.register_action(
-            |workspace, action: &NavigateApplicationMenuInDirection, window, cx| {
-                if let Some(titlebar) = workspace
-                    .titlebar_item()
-                    .and_then(|item| item.downcast::<TitleBar>().ok())
-                {
-                    titlebar.update(cx, |titlebar, cx| {
-                        if let Some(ref menu) = titlebar.application_menu {
-                            menu.update(cx, |menu, cx| {
-                                menu.navigate_menus_in_direction(action, window, cx)
-                            });
-                        }
-                    });
-                }
-            },
-        );
+        workspace.register_action(|workspace, _: &ActivateMenuRight, window, cx| {
+            if let Some(titlebar) = workspace
+                .titlebar_item()
+                .and_then(|item| item.downcast::<TitleBar>().ok())
+            {
+                titlebar.update(cx, |titlebar, cx| {
+                    if let Some(ref menu) = titlebar.application_menu {
+                        menu.update(cx, |menu, cx| {
+                            menu.navigate_menus_in_direction(ActivateDirection::Right, window, cx)
+                        });
+                    }
+                });
+            }
+        });
+
+        #[cfg(not(target_os = "macos"))]
+        workspace.register_action(|workspace, _: &ActivateMenuLeft, window, cx| {
+            if let Some(titlebar) = workspace
+                .titlebar_item()
+                .and_then(|item| item.downcast::<TitleBar>().ok())
+            {
+                titlebar.update(cx, |titlebar, cx| {
+                    if let Some(ref menu) = titlebar.application_menu {
+                        menu.update(cx, |menu, cx| {
+                            menu.navigate_menus_in_direction(ActivateDirection::Left, window, cx)
+                        });
+                    }
+                });
+            }
+        });
     })
     .detach();
 }

crates/vim/src/motion.rs 🔗

@@ -141,105 +141,105 @@ pub enum Motion {
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 struct NextWordStart {
     #[serde(default)]
     ignore_punctuation: bool,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 struct NextWordEnd {
     #[serde(default)]
     ignore_punctuation: bool,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 struct PreviousWordStart {
     #[serde(default)]
     ignore_punctuation: bool,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 struct PreviousWordEnd {
     #[serde(default)]
     ignore_punctuation: bool,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub(crate) struct NextSubwordStart {
     #[serde(default)]
     pub(crate) ignore_punctuation: bool,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub(crate) struct NextSubwordEnd {
     #[serde(default)]
     pub(crate) ignore_punctuation: bool,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub(crate) struct PreviousSubwordStart {
     #[serde(default)]
     pub(crate) ignore_punctuation: bool,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub(crate) struct PreviousSubwordEnd {
     #[serde(default)]
     pub(crate) ignore_punctuation: bool,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub(crate) struct Up {
     #[serde(default)]
     pub(crate) display_lines: bool,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub(crate) struct Down {
     #[serde(default)]
     pub(crate) display_lines: bool,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 struct FirstNonWhitespace {
     #[serde(default)]
     display_lines: bool,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 struct EndOfLine {
     #[serde(default)]
     display_lines: bool,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub struct StartOfLine {
     #[serde(default)]
     pub(crate) display_lines: bool,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 struct UnmatchedForward {
     #[serde(default)]
     char: char,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 struct UnmatchedBackward {
     #[serde(default)]
     char: char,

crates/vim/src/normal/increment.rs 🔗

@@ -8,14 +8,14 @@ use std::ops::Range;
 use crate::{state::Mode, Vim};
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 struct Increment {
     #[serde(default)]
     step: bool,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 struct Decrement {
     #[serde(default)]
     step: bool,

crates/vim/src/normal/paste.rs 🔗

@@ -13,7 +13,7 @@ use crate::{
 };
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub struct Paste {
     #[serde(default)]
     before: bool,

crates/vim/src/normal/search.rs 🔗

@@ -16,7 +16,7 @@ use crate::{
 };
 
 #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub(crate) struct MoveToNext {
     #[serde(default = "default_true")]
     case_sensitive: bool,
@@ -27,7 +27,7 @@ pub(crate) struct MoveToNext {
 }
 
 #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub(crate) struct MoveToPrev {
     #[serde(default = "default_true")]
     case_sensitive: bool,
@@ -38,6 +38,7 @@ pub(crate) struct MoveToPrev {
 }
 
 #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
+#[serde(deny_unknown_fields)]
 pub(crate) struct Search {
     #[serde(default)]
     backwards: bool,
@@ -46,6 +47,7 @@ pub(crate) struct Search {
 }
 
 #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
+#[serde(deny_unknown_fields)]
 pub struct FindCommand {
     pub query: String,
     pub backwards: bool,

crates/vim/src/object.rs 🔗

@@ -19,6 +19,7 @@ use serde::Deserialize;
 use ui::Context;
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
 pub enum Object {
     Word { ignore_punctuation: bool },
     Subword { ignore_punctuation: bool },
@@ -44,20 +45,20 @@ pub enum Object {
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 struct Word {
     #[serde(default)]
     ignore_punctuation: bool,
 }
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 struct Subword {
     #[serde(default)]
     ignore_punctuation: bool,
 }
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 struct IndentObj {
     #[serde(default)]
     include_below: bool,

crates/vim/src/state.rs 🔗

@@ -10,7 +10,6 @@ use gpui::{
     Action, App, BorrowAppContext, ClipboardEntry, ClipboardItem, Entity, Global, WeakEntity,
 };
 use language::Point;
-use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsStore};
 use std::borrow::BorrowMut;
@@ -18,7 +17,7 @@ use std::{fmt::Display, ops::Range, sync::Arc};
 use ui::{Context, SharedString};
 use workspace::searchable::Direction;
 
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, JsonSchema, Serialize)]
+#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
 pub enum Mode {
     Normal,
     Insert,
@@ -59,7 +58,7 @@ impl Default for Mode {
     }
 }
 
-#[derive(Clone, Debug, PartialEq, Eq, Deserialize, JsonSchema)]
+#[derive(Clone, Debug, PartialEq)]
 pub enum Operator {
     Change,
     Delete,
@@ -82,7 +81,6 @@ pub enum Operator {
     },
     AddSurrounds {
         // Typically no need to configure this as `SendKeystrokes` can be used - see #23088.
-        #[serde(skip)]
         target: Option<SurroundsType>,
     },
     ChangeSurrounds {

crates/vim/src/surrounds.rs 🔗

@@ -554,11 +554,7 @@ mod test {
     use gpui::KeyBinding;
     use indoc::indoc;
 
-    use crate::{
-        state::{Mode, Operator},
-        test::VimTestContext,
-        PushOperator,
-    };
+    use crate::{state::Mode, test::VimTestContext, PushAddSurrounds};
 
     #[gpui::test]
     async fn test_add_surrounds(cx: &mut gpui::TestAppContext) {
@@ -749,7 +745,7 @@ mod test {
         cx.update(|_, cx| {
             cx.bind_keys([KeyBinding::new(
                 "shift-s",
-                PushOperator(Operator::AddSurrounds { target: None }),
+                PushAddSurrounds {},
                 Some("vim_mode == visual"),
             )])
         });

crates/vim/src/test.rs 🔗

@@ -17,12 +17,7 @@ use indoc::indoc;
 use search::BufferSearchBar;
 use workspace::WorkspaceSettings;
 
-use crate::{
-    insert::NormalBefore,
-    motion,
-    state::{Mode, Operator},
-    PushOperator,
-};
+use crate::{insert::NormalBefore, motion, state::Mode, PushSneak, PushSneakBackward};
 
 #[gpui::test]
 async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
@@ -1347,17 +1342,17 @@ async fn test_sneak(cx: &mut gpui::TestAppContext) {
         cx.bind_keys([
             KeyBinding::new(
                 "s",
-                PushOperator(Operator::Sneak { first_char: None }),
+                PushSneak { first_char: None },
                 Some("vim_mode == normal"),
             ),
             KeyBinding::new(
                 "S",
-                PushOperator(Operator::SneakBackward { first_char: None }),
+                PushSneakBackward { first_char: None },
                 Some("vim_mode == normal"),
             ),
             KeyBinding::new(
                 "S",
-                PushOperator(Operator::SneakBackward { first_char: None }),
+                PushSneakBackward { first_char: None },
                 Some("vim_mode == visual"),
             ),
         ])

crates/vim/src/vim.rs 🔗

@@ -35,6 +35,7 @@ use language::{CursorShape, Point, Selection, SelectionGoal, TransactionId};
 pub use mode_indicator::ModeIndicator;
 use motion::Motion;
 use normal::search::SearchSubmit;
+use object::Object;
 use schemars::JsonSchema;
 use serde::Deserialize;
 use serde_derive::Serialize;
@@ -45,55 +46,138 @@ use surrounds::SurroundsType;
 use theme::ThemeSettings;
 use ui::{px, IntoElement, SharedString};
 use vim_mode_setting::VimModeSetting;
-use workspace::{self, Pane, ResizeIntent, Workspace};
+use workspace::{self, Pane, Workspace};
 
 use crate::state::ReplayableAction;
 
-/// Used to resize the current pane
+/// Number is used to manage vim's count. Pushing a digit
+/// multiplies the current value by 10 and adds the digit.
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-pub struct ResizePane(pub ResizeIntent);
+struct Number(usize);
 
-/// An Action to Switch between modes
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-pub struct SwitchMode(pub Mode);
+struct SelectRegister(String);
 
-/// PushOperator is used to put vim into a "minor" mode,
-/// where it's waiting for a specific next set of keystrokes.
-/// For example 'd' needs a motion to complete.
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-pub struct PushOperator(pub Operator);
+#[serde(deny_unknown_fields)]
+struct PushObject {
+    around: bool,
+}
 
-/// Number is used to manage vim's count. Pushing a digit
-/// multiplies the current value by 10 and adds the digit.
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-struct Number(usize);
+#[serde(deny_unknown_fields)]
+struct PushFindForward {
+    before: bool,
+}
 
 #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
-struct SelectRegister(String);
+#[serde(deny_unknown_fields)]
+struct PushFindBackward {
+    after: bool,
+}
+
+#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
+#[serde(deny_unknown_fields)]
+struct PushSneak {
+    first_char: Option<char>,
+}
+
+#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
+#[serde(deny_unknown_fields)]
+struct PushSneakBackward {
+    first_char: Option<char>,
+}
+
+#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
+#[serde(deny_unknown_fields)]
+struct PushAddSurrounds {}
+
+#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
+#[serde(deny_unknown_fields)]
+struct PushChangeSurrounds {
+    target: Option<Object>,
+}
+
+#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
+#[serde(deny_unknown_fields)]
+struct PushJump {
+    line: bool,
+}
+
+#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
+#[serde(deny_unknown_fields)]
+struct PushDigraph {
+    first_char: Option<char>,
+}
+
+#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
+#[serde(deny_unknown_fields)]
+struct PushLiteral {
+    prefix: Option<String>,
+}
 
 actions!(
     vim,
     [
+        SwitchToNormalMode,
+        SwitchToInsertMode,
+        SwitchToReplaceMode,
+        SwitchToVisualMode,
+        SwitchToVisualLineMode,
+        SwitchToVisualBlockMode,
+        SwitchToHelixNormalMode,
         ClearOperators,
         Tab,
         Enter,
         InnerObject,
-        FindForward,
-        FindBackward,
         MaximizePane,
         OpenDefaultKeymap,
         ResetPaneSizes,
-        Sneak,
-        SneakBackward,
+        ResizePaneRight,
+        ResizePaneLeft,
+        ResizePaneUp,
+        ResizePaneDown,
+        PushChange,
+        PushDelete,
+        PushYank,
+        PushReplace,
+        PushDeleteSurrounds,
+        PushMark,
+        PushIndent,
+        PushOutdent,
+        PushAutoIndent,
+        PushRewrap,
+        PushShellCommand,
+        PushLowercase,
+        PushUppercase,
+        PushOppositeCase,
+        PushRegister,
+        PushRecordRegister,
+        PushReplayRegister,
+        PushReplaceWithRegister,
+        PushToggleComments,
     ]
 );
 
 // in the workspace namespace so it's not filtered out when vim is disabled.
-actions!(workspace, [ToggleVimMode]);
+actions!(workspace, [ToggleVimMode,]);
 
 impl_actions!(
     vim,
-    [ResizePane, SwitchMode, PushOperator, Number, SelectRegister]
+    [
+        Number,
+        SelectRegister,
+        PushObject,
+        PushFindForward,
+        PushFindBackward,
+        PushSneak,
+        PushSneakBackward,
+        PushAddSurrounds,
+        PushChangeSurrounds,
+        PushJump,
+        PushDigraph,
+        PushLiteral
+    ]
 );
 
 /// Initializes the `vim` crate.
@@ -142,7 +226,7 @@ pub fn init(cx: &mut App) {
             workspace.resize_pane(Axis::Vertical, desired_size - size.size.height, window, cx)
         });
 
-        workspace.register_action(|workspace, action: &ResizePane, window, cx| {
+        workspace.register_action(|workspace, _: &ResizePaneRight, window, cx| {
             let count = Vim::take_count(cx).unwrap_or(1) as f32;
             let theme = ThemeSettings::get_global(cx);
             let Ok(font_id) = window.text_system().font_id(&theme.buffer_font) else {
@@ -154,16 +238,36 @@ pub fn init(cx: &mut App) {
             else {
                 return;
             };
-            let height = theme.buffer_font_size() * theme.buffer_line_height.value();
+            workspace.resize_pane(Axis::Horizontal, width.width * count, window, cx);
+        });
 
-            let (axis, amount) = match action.0 {
-                ResizeIntent::Lengthen => (Axis::Vertical, height),
-                ResizeIntent::Shorten => (Axis::Vertical, height * -1.),
-                ResizeIntent::Widen => (Axis::Horizontal, width.width),
-                ResizeIntent::Narrow => (Axis::Horizontal, width.width * -1.),
+        workspace.register_action(|workspace, _: &ResizePaneLeft, window, cx| {
+            let count = Vim::take_count(cx).unwrap_or(1) as f32;
+            let theme = ThemeSettings::get_global(cx);
+            let Ok(font_id) = window.text_system().font_id(&theme.buffer_font) else {
+                return;
+            };
+            let Ok(width) = window
+                .text_system()
+                .advance(font_id, theme.buffer_font_size(), 'm')
+            else {
+                return;
             };
+            workspace.resize_pane(Axis::Horizontal, -width.width * count, window, cx);
+        });
 
-            workspace.resize_pane(axis, amount * count, window, cx);
+        workspace.register_action(|workspace, _: &ResizePaneUp, window, cx| {
+            let count = Vim::take_count(cx).unwrap_or(1) as f32;
+            let theme = ThemeSettings::get_global(cx);
+            let height = theme.buffer_font_size() * theme.buffer_line_height.value();
+            workspace.resize_pane(Axis::Vertical, height * count, window, cx);
+        });
+
+        workspace.register_action(|workspace, _: &ResizePaneDown, window, cx| {
+            let count = Vim::take_count(cx).unwrap_or(1) as f32;
+            let theme = ThemeSettings::get_global(cx);
+            let height = theme.buffer_font_size() * theme.buffer_line_height.value();
+            workspace.resize_pane(Axis::Vertical, -height * count, window, cx);
         });
 
         workspace.register_action(|workspace, _: &SearchSubmit, window, cx| {
@@ -330,12 +434,212 @@ impl Vim {
         });
 
         vim.update(cx, |_, cx| {
-            Vim::action(editor, cx, |vim, action: &SwitchMode, window, cx| {
-                vim.switch_mode(action.0, false, window, cx)
+            Vim::action(editor, cx, |vim, _: &SwitchToNormalMode, window, cx| {
+                vim.switch_mode(Mode::Normal, false, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &SwitchToInsertMode, window, cx| {
+                vim.switch_mode(Mode::Insert, false, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &SwitchToReplaceMode, window, cx| {
+                vim.switch_mode(Mode::Replace, false, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &SwitchToVisualMode, window, cx| {
+                vim.switch_mode(Mode::Visual, false, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &SwitchToVisualLineMode, window, cx| {
+                vim.switch_mode(Mode::VisualLine, false, window, cx)
+            });
+
+            Vim::action(
+                editor,
+                cx,
+                |vim, _: &SwitchToVisualBlockMode, window, cx| {
+                    vim.switch_mode(Mode::VisualBlock, false, window, cx)
+                },
+            );
+
+            Vim::action(
+                editor,
+                cx,
+                |vim, _: &SwitchToHelixNormalMode, window, cx| {
+                    vim.switch_mode(Mode::HelixNormal, false, window, cx)
+                },
+            );
+
+            Vim::action(editor, cx, |vim, action: &PushObject, window, cx| {
+                vim.push_operator(
+                    Operator::Object {
+                        around: action.around,
+                    },
+                    window,
+                    cx,
+                )
+            });
+
+            Vim::action(editor, cx, |vim, action: &PushFindForward, window, cx| {
+                vim.push_operator(
+                    Operator::FindForward {
+                        before: action.before,
+                    },
+                    window,
+                    cx,
+                )
+            });
+
+            Vim::action(editor, cx, |vim, action: &PushFindBackward, window, cx| {
+                vim.push_operator(
+                    Operator::FindBackward {
+                        after: action.after,
+                    },
+                    window,
+                    cx,
+                )
+            });
+
+            Vim::action(editor, cx, |vim, action: &PushSneak, window, cx| {
+                vim.push_operator(
+                    Operator::Sneak {
+                        first_char: action.first_char,
+                    },
+                    window,
+                    cx,
+                )
+            });
+
+            Vim::action(editor, cx, |vim, action: &PushSneakBackward, window, cx| {
+                vim.push_operator(
+                    Operator::SneakBackward {
+                        first_char: action.first_char,
+                    },
+                    window,
+                    cx,
+                )
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushAddSurrounds, window, cx| {
+                vim.push_operator(Operator::AddSurrounds { target: None }, window, cx)
+            });
+
+            Vim::action(
+                editor,
+                cx,
+                |vim, action: &PushChangeSurrounds, window, cx| {
+                    vim.push_operator(
+                        Operator::ChangeSurrounds {
+                            target: action.target,
+                        },
+                        window,
+                        cx,
+                    )
+                },
+            );
+
+            Vim::action(editor, cx, |vim, action: &PushJump, window, cx| {
+                vim.push_operator(Operator::Jump { line: action.line }, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, action: &PushDigraph, window, cx| {
+                vim.push_operator(
+                    Operator::Digraph {
+                        first_char: action.first_char,
+                    },
+                    window,
+                    cx,
+                )
+            });
+
+            Vim::action(editor, cx, |vim, action: &PushLiteral, window, cx| {
+                vim.push_operator(
+                    Operator::Literal {
+                        prefix: action.prefix.clone(),
+                    },
+                    window,
+                    cx,
+                )
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushChange, window, cx| {
+                vim.push_operator(Operator::Change, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushDelete, window, cx| {
+                vim.push_operator(Operator::Delete, window, cx)
             });
 
-            Vim::action(editor, cx, |vim, action: &PushOperator, window, cx| {
-                vim.push_operator(action.0.clone(), window, cx)
+            Vim::action(editor, cx, |vim, _: &PushYank, window, cx| {
+                vim.push_operator(Operator::Yank, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushReplace, window, cx| {
+                vim.push_operator(Operator::Replace, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushDeleteSurrounds, window, cx| {
+                vim.push_operator(Operator::DeleteSurrounds, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushMark, window, cx| {
+                vim.push_operator(Operator::Mark, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushIndent, window, cx| {
+                vim.push_operator(Operator::Indent, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushOutdent, window, cx| {
+                vim.push_operator(Operator::Outdent, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushAutoIndent, window, cx| {
+                vim.push_operator(Operator::AutoIndent, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushRewrap, window, cx| {
+                vim.push_operator(Operator::Rewrap, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushShellCommand, window, cx| {
+                vim.push_operator(Operator::ShellCommand, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushLowercase, window, cx| {
+                vim.push_operator(Operator::Lowercase, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushUppercase, window, cx| {
+                vim.push_operator(Operator::Uppercase, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushOppositeCase, window, cx| {
+                vim.push_operator(Operator::OppositeCase, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushRegister, window, cx| {
+                vim.push_operator(Operator::Register, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushRecordRegister, window, cx| {
+                vim.push_operator(Operator::RecordRegister, window, cx)
+            });
+
+            Vim::action(editor, cx, |vim, _: &PushReplayRegister, window, cx| {
+                vim.push_operator(Operator::ReplayRegister, window, cx)
+            });
+
+            Vim::action(
+                editor,
+                cx,
+                |vim, _: &PushReplaceWithRegister, window, cx| {
+                    vim.push_operator(Operator::ReplaceWithRegister, window, cx)
+                },
+            );
+
+            Vim::action(editor, cx, |vim, _: &PushToggleComments, window, cx| {
+                vim.push_operator(Operator::ToggleComments, window, cx)
             });
 
             Vim::action(editor, cx, |vim, _: &ClearOperators, window, cx| {
@@ -1275,8 +1579,8 @@ impl Vim {
 
                 if self.mode == Mode::Normal {
                     self.update_editor(window, cx, |_, editor, window, cx| {
-                        editor.accept_inline_completion(
-                            &editor::actions::AcceptInlineCompletion {},
+                        editor.accept_edit_prediction(
+                            &editor::actions::AcceptEditPrediction {},
                             window,
                             cx,
                         );

crates/workspace/src/pane.rs 🔗

@@ -72,7 +72,7 @@ impl DraggedSelection {
 }
 
 #[derive(Clone, Copy, PartialEq, Debug, Deserialize, JsonSchema)]
-#[serde(rename_all = "camelCase")]
+#[serde(rename_all = "snake_case")]
 pub enum SaveIntent {
     /// write all files (even if unchanged)
     /// prompt before overwriting on-disk changes
@@ -96,13 +96,13 @@ pub enum SaveIntent {
 pub struct ActivateItem(pub usize);
 
 #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub struct CloseActiveItem {
     pub save_intent: Option<SaveIntent>,
 }
 
 #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub struct CloseInactiveItems {
     pub save_intent: Option<SaveIntent>,
     #[serde(default)]
@@ -110,7 +110,7 @@ pub struct CloseInactiveItems {
 }
 
 #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub struct CloseAllItems {
     pub save_intent: Option<SaveIntent>,
     #[serde(default)]
@@ -118,34 +118,35 @@ pub struct CloseAllItems {
 }
 
 #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub struct CloseCleanItems {
     #[serde(default)]
     pub close_pinned: bool,
 }
 
 #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub struct CloseItemsToTheRight {
     #[serde(default)]
     pub close_pinned: bool,
 }
 
 #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub struct CloseItemsToTheLeft {
     #[serde(default)]
     pub close_pinned: bool,
 }
 
 #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub struct RevealInProjectPanel {
     #[serde(skip)]
     pub entry_id: Option<u64>,
 }
 
 #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
+#[serde(deny_unknown_fields)]
 pub struct DeploySearch {
     #[serde(default)]
     pub replace_enabled: bool,

crates/workspace/src/pane_group.rs 🔗

@@ -725,6 +725,7 @@ impl PaneAxis {
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
 pub enum SplitDirection {
     Up,
     Down,
@@ -807,14 +808,6 @@ impl SplitDirection {
     }
 }
 
-#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, PartialEq)]
-pub enum ResizeIntent {
-    Lengthen,
-    Shorten,
-    Widen,
-    Narrow,
-}
-
 mod element {
     use std::mem;
     use std::{cell::RefCell, iter, rc::Rc, sync::Arc};

crates/workspace/src/workspace.rs 🔗

@@ -170,12 +170,7 @@ pub struct OpenPaths {
 pub struct ActivatePane(pub usize);
 
 #[derive(Clone, Deserialize, PartialEq, JsonSchema)]
-pub struct ActivatePaneInDirection(pub SplitDirection);
-
-#[derive(Clone, Deserialize, PartialEq, JsonSchema)]
-pub struct SwapPaneInDirection(pub SplitDirection);
-
-#[derive(Clone, Deserialize, PartialEq, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct MoveItemToPane {
     pub destination: usize,
     #[serde(default = "default_true")]
@@ -183,6 +178,7 @@ pub struct MoveItemToPane {
 }
 
 #[derive(Clone, Deserialize, PartialEq, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct MoveItemToPaneInDirection {
     pub direction: SplitDirection,
     #[serde(default = "default_true")]
@@ -190,25 +186,25 @@ pub struct MoveItemToPaneInDirection {
 }
 
 #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub struct SaveAll {
     pub save_intent: Option<SaveIntent>,
 }
 
 #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub struct Save {
     pub save_intent: Option<SaveIntent>,
 }
 
 #[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub struct CloseAllItemsAndPanes {
     pub save_intent: Option<SaveIntent>,
 }
 
 #[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema)]
-#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
 pub struct CloseInactiveTabsAndPanes {
     pub save_intent: Option<SaveIntent>,
 }
@@ -217,6 +213,7 @@ pub struct CloseInactiveTabsAndPanes {
 pub struct SendKeystrokes(pub String);
 
 #[derive(Clone, Deserialize, PartialEq, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct Reload {
     pub binary_path: Option<PathBuf>,
 }
@@ -235,7 +232,6 @@ impl_actions!(
     workspace,
     [
         ActivatePane,
-        ActivatePaneInDirection,
         CloseAllItemsAndPanes,
         CloseInactiveTabsAndPanes,
         MoveItemToPane,
@@ -244,11 +240,24 @@ impl_actions!(
         Reload,
         Save,
         SaveAll,
-        SwapPaneInDirection,
         SendKeystrokes,
     ]
 );
 
+actions!(
+    workspace,
+    [
+        ActivatePaneLeft,
+        ActivatePaneRight,
+        ActivatePaneUp,
+        ActivatePaneDown,
+        SwapPaneLeft,
+        SwapPaneRight,
+        SwapPaneUp,
+        SwapPaneDown,
+    ]
+);
+
 #[derive(PartialEq, Eq, Debug)]
 pub enum CloseIntent {
     /// Quit the program entirely.
@@ -301,6 +310,7 @@ impl PartialEq for Toast {
 }
 
 #[derive(Debug, Default, Clone, Deserialize, PartialEq, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct OpenTerminal {
     pub working_directory: PathBuf,
 }
@@ -4821,29 +4831,38 @@ impl Workspace {
                     workspace.activate_previous_window(cx)
                 }),
             )
-            .on_action(
-                cx.listener(|workspace, action: &ActivatePaneInDirection, window, cx| {
-                    workspace.activate_pane_in_direction(action.0, window, cx)
-                }),
-            )
+            .on_action(cx.listener(|workspace, _: &ActivatePaneLeft, window, cx| {
+                workspace.activate_pane_in_direction(SplitDirection::Left, window, cx)
+            }))
+            .on_action(cx.listener(|workspace, _: &ActivatePaneRight, window, cx| {
+                workspace.activate_pane_in_direction(SplitDirection::Right, window, cx)
+            }))
+            .on_action(cx.listener(|workspace, _: &ActivatePaneUp, window, cx| {
+                workspace.activate_pane_in_direction(SplitDirection::Up, window, cx)
+            }))
+            .on_action(cx.listener(|workspace, _: &ActivatePaneDown, window, cx| {
+                workspace.activate_pane_in_direction(SplitDirection::Down, window, cx)
+            }))
             .on_action(cx.listener(|workspace, _: &ActivateNextPane, window, cx| {
                 workspace.activate_next_pane(window, cx)
             }))
-            .on_action(
-                cx.listener(|workspace, action: &ActivatePaneInDirection, window, cx| {
-                    workspace.activate_pane_in_direction(action.0, window, cx)
-                }),
-            )
             .on_action(cx.listener(
                 |workspace, action: &MoveItemToPaneInDirection, window, cx| {
                     workspace.move_item_to_pane_in_direction(action, window, cx)
                 },
             ))
-            .on_action(
-                cx.listener(|workspace, action: &SwapPaneInDirection, _, cx| {
-                    workspace.swap_pane_in_direction(action.0, cx)
-                }),
-            )
+            .on_action(cx.listener(|workspace, _: &SwapPaneLeft, _, cx| {
+                workspace.swap_pane_in_direction(SplitDirection::Left, cx)
+            }))
+            .on_action(cx.listener(|workspace, _: &SwapPaneRight, _, cx| {
+                workspace.swap_pane_in_direction(SplitDirection::Right, cx)
+            }))
+            .on_action(cx.listener(|workspace, _: &SwapPaneUp, _, cx| {
+                workspace.swap_pane_in_direction(SplitDirection::Up, cx)
+            }))
+            .on_action(cx.listener(|workspace, _: &SwapPaneDown, _, cx| {
+                workspace.swap_pane_in_direction(SplitDirection::Down, cx)
+            }))
             .on_action(cx.listener(|this, _: &ToggleLeftDock, window, cx| {
                 this.toggle_dock(DockPosition::Left, window, cx);
             }))

crates/zed/src/zed.rs 🔗

@@ -20,6 +20,7 @@ use command_palette_hooks::CommandPaletteFilter;
 use editor::ProposedChangesEditorToolbar;
 use editor::{scroll::Autoscroll, Editor, MultiBuffer};
 use feature_flags::{FeatureFlagAppExt, FeatureFlagViewExt, GitUiFeatureFlag};
+use fs::Fs;
 use futures::{channel::mpsc, select_biased, StreamExt};
 use gpui::{
     actions, point, px, Action, App, AppContext as _, AsyncApp, Context, DismissEvent, Element,
@@ -1144,18 +1145,34 @@ pub fn handle_keymap_file_changes(
             cx.update(|cx| {
                 let load_result = KeymapFile::load(&user_keymap_content, cx);
                 match load_result {
-                    KeymapFileLoadResult::Success { key_bindings } => {
+                    KeymapFileLoadResult::Success {
+                        key_bindings,
+                        keymap_file,
+                    } => {
                         reload_keymaps(cx, key_bindings);
                         dismiss_app_notification(&notification_id, cx);
+                        show_keymap_migration_notification_if_needed(
+                            keymap_file,
+                            notification_id.clone(),
+                            cx,
+                        );
                     }
                     KeymapFileLoadResult::SomeFailedToLoad {
                         key_bindings,
+                        keymap_file,
                         error_message,
                     } => {
                         if !key_bindings.is_empty() {
                             reload_keymaps(cx, key_bindings);
                         }
-                        show_keymap_file_load_error(notification_id.clone(), error_message, cx)
+                        dismiss_app_notification(&notification_id, cx);
+                        if !show_keymap_migration_notification_if_needed(
+                            keymap_file,
+                            notification_id.clone(),
+                            cx,
+                        ) {
+                            show_keymap_file_load_error(notification_id.clone(), error_message, cx);
+                        }
                     }
                     KeymapFileLoadResult::JsonParseFailure { error } => {
                         show_keymap_file_json_error(notification_id.clone(), &error, cx)
@@ -1187,6 +1204,61 @@ fn show_keymap_file_json_error(
     });
 }
 
+fn show_keymap_migration_notification_if_needed(
+    keymap_file: KeymapFile,
+    notification_id: NotificationId,
+    cx: &mut App,
+) -> bool {
+    if !KeymapFile::should_migrate_keymap(keymap_file) {
+        return false;
+    }
+    show_app_notification(notification_id, cx, move |cx| {
+        cx.new(move |_cx| {
+            let message = "A newer version of Zed has simplified several keymaps. Your existing keymaps may be deprecated. You can migrate them by clicking below. A backup will be created in your home directory.";
+            let button_text = "Backup and Migrate Keymap";
+            MessageNotification::new_from_builder(move |_, _| {
+                gpui::div().text_xs().child(message).into_any()
+            })
+            .primary_message(button_text)
+            .primary_on_click(move |_, cx| {
+                let fs = <dyn Fs>::global(cx);
+                cx.spawn(move |weak_notification, mut cx| async move {
+                    KeymapFile::migrate_keymap(fs).await.ok();
+                    weak_notification.update(&mut cx, |_, cx| {
+                        cx.emit(DismissEvent);
+                    }).ok();
+                }).detach();
+            })
+        })
+    });
+    return true;
+}
+
+fn show_settings_migration_notification_if_needed(
+    notification_id: NotificationId,
+    settings: serde_json::Value,
+    cx: &mut App,
+) {
+    if !SettingsStore::should_migrate_settings(&settings) {
+        return;
+    }
+    show_app_notification(notification_id, cx, move |cx| {
+        cx.new(move |_cx| {
+            let message = "A newer version of Zed has updated some settings. Your existing settings may be deprecated. You can migrate them by clicking below. A backup will be created in your home directory.";
+            let button_text = "Backup and Migrate Settings";
+            MessageNotification::new_from_builder(move |_, _| {
+                gpui::div().text_xs().child(message).into_any()
+            })
+            .primary_message(button_text)
+            .primary_on_click(move |_, cx| {
+                let fs = <dyn Fs>::global(cx);
+                cx.update_global(|store: &mut SettingsStore, _| store.migrate_settings(fs));
+                cx.emit(DismissEvent);
+            })
+        })
+    });
+}
+
 fn show_keymap_file_load_error(
     notification_id: NotificationId,
     markdown_error_message: MarkdownString,
@@ -1259,12 +1331,12 @@ pub fn load_default_keymap(cx: &mut App) {
     }
 }
 
-pub fn handle_settings_changed(error: Option<anyhow::Error>, cx: &mut App) {
+pub fn handle_settings_changed(result: Result<serde_json::Value, anyhow::Error>, cx: &mut App) {
     struct SettingsParseErrorNotification;
     let id = NotificationId::unique::<SettingsParseErrorNotification>();
 
-    match error {
-        Some(error) => {
+    match result {
+        Err(error) => {
             if let Some(InvalidSettingsError::LocalSettings { .. }) =
                 error.downcast_ref::<InvalidSettingsError>()
             {
@@ -1283,7 +1355,10 @@ pub fn handle_settings_changed(error: Option<anyhow::Error>, cx: &mut App) {
                 })
             });
         }
-        None => dismiss_app_notification(&id, cx),
+        Ok(settings) => {
+            dismiss_app_notification(&id, cx);
+            show_settings_migration_notification_if_needed(id, settings, cx);
+        }
     }
 }
 
@@ -3925,24 +4000,28 @@ mod tests {
                     "vim::FindCommand"
                     | "vim::Literal"
                     | "vim::ResizePane"
-                    | "vim::SwitchMode"
-                    | "vim::PushOperator"
+                    | "vim::PushObject"
+                    | "vim::PushFindForward"
+                    | "vim::PushFindBackward"
+                    | "vim::PushSneak"
+                    | "vim::PushSneakBackward"
+                    | "vim::PushChangeSurrounds"
+                    | "vim::PushJump"
+                    | "vim::PushDigraph"
+                    | "vim::PushLiteral"
                     | "vim::Number"
                     | "vim::SelectRegister"
                     | "terminal::SendText"
                     | "terminal::SendKeystroke"
                     | "app_menu::OpenApplicationMenu"
-                    | "app_menu::NavigateApplicationMenuInDirection"
                     | "picker::ConfirmInput"
                     | "editor::HandleInput"
                     | "editor::FoldAtLevel"
                     | "pane::ActivateItem"
                     | "workspace::ActivatePane"
-                    | "workspace::ActivatePaneInDirection"
                     | "workspace::MoveItemToPane"
                     | "workspace::MoveItemToPaneInDirection"
                     | "workspace::OpenTerminal"
-                    | "workspace::SwapPaneInDirection"
                     | "workspace::SendKeystrokes"
                     | "zed::OpenBrowser"
                     | "zed::OpenZedUrl" => {}

crates/zed/src/zed/inline_completion_registry.rs 🔗

@@ -4,7 +4,7 @@ use copilot::{Copilot, CopilotCompletionProvider};
 use editor::{Editor, EditorMode};
 use feature_flags::{FeatureFlagAppExt, PredictEditsFeatureFlag};
 use gpui::{AnyWindowHandle, App, AppContext, Context, Entity, WeakEntity};
-use language::language_settings::{all_language_settings, InlineCompletionProvider};
+use language::language_settings::{all_language_settings, EditPredictionProvider};
 use settings::SettingsStore;
 use std::{cell::RefCell, rc::Rc, sync::Arc};
 use supermaven::{Supermaven, SupermavenCompletionProvider};
@@ -41,8 +41,8 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
             editors
                 .borrow_mut()
                 .insert(editor_handle, window.window_handle());
-            let provider = all_language_settings(None, cx).inline_completions.provider;
-            assign_inline_completion_provider(
+            let provider = all_language_settings(None, cx).edit_predictions.provider;
+            assign_edit_prediction_provider(
                 editor,
                 provider,
                 &client,
@@ -54,11 +54,11 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
     })
     .detach();
 
-    let mut provider = all_language_settings(None, cx).inline_completions.provider;
+    let mut provider = all_language_settings(None, cx).edit_predictions.provider;
     for (editor, window) in editors.borrow().iter() {
         _ = window.update(cx, |_window, window, cx| {
             _ = editor.update(cx, |editor, cx| {
-                assign_inline_completion_provider(
+                assign_edit_prediction_provider(
                     editor,
                     provider,
                     &client,
@@ -79,8 +79,8 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
         let client = client.clone();
         let user_store = user_store.clone();
         move |active, cx| {
-            let provider = all_language_settings(None, cx).inline_completions.provider;
-            assign_inline_completion_providers(&editors, provider, &client, user_store.clone(), cx);
+            let provider = all_language_settings(None, cx).edit_predictions.provider;
+            assign_edit_prediction_providers(&editors, provider, &client, user_store.clone(), cx);
             if active && !cx.is_action_available(&zeta::ClearHistory) {
                 cx.on_action(clear_zeta_edit_history);
             }
@@ -93,7 +93,7 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
         let client = client.clone();
         let user_store = user_store.clone();
         move |cx| {
-            let new_provider = all_language_settings(None, cx).inline_completions.provider;
+            let new_provider = all_language_settings(None, cx).edit_predictions.provider;
 
             if new_provider != provider {
                 let tos_accepted = user_store
@@ -109,7 +109,7 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
                 );
 
                 provider = new_provider;
-                assign_inline_completion_providers(
+                assign_edit_prediction_providers(
                     &editors,
                     provider,
                     &client,
@@ -119,7 +119,7 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
 
                 if !tos_accepted {
                     match provider {
-                        InlineCompletionProvider::Zed => {
+                        EditPredictionProvider::Zed => {
                             let Some(window) = cx.active_window() else {
                                 return;
                             };
@@ -133,9 +133,9 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
                                 })
                                 .ok();
                         }
-                        InlineCompletionProvider::None
-                        | InlineCompletionProvider::Copilot
-                        | InlineCompletionProvider::Supermaven => {}
+                        EditPredictionProvider::None
+                        | EditPredictionProvider::Copilot
+                        | EditPredictionProvider::Supermaven => {}
                     }
                 }
             }
@@ -150,9 +150,9 @@ fn clear_zeta_edit_history(_: &zeta::ClearHistory, cx: &mut App) {
     }
 }
 
-fn assign_inline_completion_providers(
+fn assign_edit_prediction_providers(
     editors: &Rc<RefCell<HashMap<WeakEntity<Editor>, AnyWindowHandle>>>,
-    provider: InlineCompletionProvider,
+    provider: EditPredictionProvider,
     client: &Arc<Client>,
     user_store: Entity<UserStore>,
     cx: &mut App,
@@ -160,7 +160,7 @@ fn assign_inline_completion_providers(
     for (editor, window) in editors.borrow().iter() {
         _ = window.update(cx, |_window, window, cx| {
             _ = editor.update(cx, |editor, cx| {
-                assign_inline_completion_provider(
+                assign_edit_prediction_provider(
                     editor,
                     provider,
                     &client,
@@ -187,7 +187,7 @@ fn register_backward_compatible_actions(editor: &mut Editor, cx: &mut Context<Ed
     editor
         .register_action(cx.listener(
             |editor, _: &copilot::NextSuggestion, window: &mut Window, cx: &mut Context<Editor>| {
-                editor.next_inline_completion(&Default::default(), window, cx);
+                editor.next_edit_prediction(&Default::default(), window, cx);
             },
         ))
         .detach();
@@ -197,7 +197,7 @@ fn register_backward_compatible_actions(editor: &mut Editor, cx: &mut Context<Ed
              _: &copilot::PreviousSuggestion,
              window: &mut Window,
              cx: &mut Context<Editor>| {
-                editor.previous_inline_completion(&Default::default(), window, cx);
+                editor.previous_edit_prediction(&Default::default(), window, cx);
             },
         ))
         .detach();
@@ -213,9 +213,9 @@ fn register_backward_compatible_actions(editor: &mut Editor, cx: &mut Context<Ed
         .detach();
 }
 
-fn assign_inline_completion_provider(
+fn assign_edit_prediction_provider(
     editor: &mut Editor,
-    provider: InlineCompletionProvider,
+    provider: EditPredictionProvider,
     client: &Arc<Client>,
     user_store: Entity<UserStore>,
     window: &mut Window,
@@ -225,8 +225,8 @@ fn assign_inline_completion_provider(
     let singleton_buffer = editor.buffer().read(cx).as_singleton();
 
     match provider {
-        InlineCompletionProvider::None => {}
-        InlineCompletionProvider::Copilot => {
+        EditPredictionProvider::None => {}
+        EditPredictionProvider::Copilot => {
             if let Some(copilot) = Copilot::global(cx) {
                 if let Some(buffer) = singleton_buffer {
                     if buffer.read(cx).file().is_some() {
@@ -236,16 +236,16 @@ fn assign_inline_completion_provider(
                     }
                 }
                 let provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
-                editor.set_inline_completion_provider(Some(provider), window, cx);
+                editor.set_edit_prediction_provider(Some(provider), window, cx);
             }
         }
-        InlineCompletionProvider::Supermaven => {
+        EditPredictionProvider::Supermaven => {
             if let Some(supermaven) = Supermaven::global(cx) {
                 let provider = cx.new(|_| SupermavenCompletionProvider::new(supermaven));
-                editor.set_inline_completion_provider(Some(provider), window, cx);
+                editor.set_edit_prediction_provider(Some(provider), window, cx);
             }
         }
-        InlineCompletionProvider::Zed => {
+        EditPredictionProvider::Zed => {
             if cx.has_flag::<PredictEditsFeatureFlag>()
                 || (cfg!(debug_assertions) && client.status().borrow().is_connected())
             {
@@ -280,7 +280,7 @@ fn assign_inline_completion_provider(
                 let provider =
                     cx.new(|_| zeta::ZetaInlineCompletionProvider::new(zeta, data_collection));
 
-                editor.set_inline_completion_provider(Some(provider), window, cx);
+                editor.set_edit_prediction_provider(Some(provider), window, cx);
             }
         }
     }

crates/zed/src/zed/quick_action_bar.rs 🔗

@@ -301,14 +301,14 @@ impl Render for QuickActionBar {
                             .toggleable(IconPosition::Start, inline_completion_enabled && show_inline_completions)
                             .disabled(!inline_completion_enabled)
                             .action(Some(
-                                editor::actions::ToggleInlineCompletions.boxed_clone(),
+                                editor::actions::ToggleEditPrediction.boxed_clone(),
                             )).handler({
                                 let editor = editor.clone();
                                 move |window, cx| {
                                     editor
                                         .update(cx, |editor, cx| {
                                             editor.toggle_inline_completions(
-                                                &editor::actions::ToggleInlineCompletions,
+                                                &editor::actions::ToggleEditPrediction,
                                                 window,
                                                 cx,
                                             );

crates/zed_actions/src/lib.rs 🔗

@@ -12,11 +12,13 @@ use serde::{Deserialize, Serialize};
 pub fn init() {}
 
 #[derive(Clone, PartialEq, Deserialize, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct OpenBrowser {
     pub url: String,
 }
 
 #[derive(Clone, PartialEq, Deserialize, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct OpenZedUrl {
     pub url: String,
 }
@@ -69,6 +71,7 @@ pub mod theme_selector {
     use serde::Deserialize;
 
     #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
+    #[serde(deny_unknown_fields)]
     pub struct Toggle {
         /// A list of theme names to filter the theme selector down to.
         pub themes_filter: Option<Vec<String>>,
@@ -83,6 +86,7 @@ pub mod icon_theme_selector {
     use serde::Deserialize;
 
     #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
+    #[serde(deny_unknown_fields)]
     pub struct Toggle {
         /// A list of icon theme names to filter the theme selector down to.
         pub themes_filter: Option<Vec<String>>,
@@ -99,6 +103,7 @@ pub mod assistant {
     actions!(assistant, [ToggleFocus, DeployPromptLibrary]);
 
     #[derive(Clone, Default, Deserialize, PartialEq, JsonSchema)]
+    #[serde(deny_unknown_fields)]
     pub struct InlineAssist {
         pub prompt: Option<String>,
     }
@@ -107,6 +112,7 @@ pub mod assistant {
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct OpenRecent {
     #[serde(default)]
     pub create_new_window: bool,
@@ -154,6 +160,7 @@ impl Spawn {
 
 /// Rerun the last task.
 #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
+#[serde(deny_unknown_fields)]
 pub struct Rerun {
     /// Controls whether the task context is reevaluated prior to execution of a task.
     /// If it is not, environment variables such as ZED_COLUMN, ZED_FILE are gonna be the same as in the last execution of a task

crates/zeta/src/init.rs 🔗

@@ -5,7 +5,7 @@ use feature_flags::{
     FeatureFlagAppExt as _, PredictEditsFeatureFlag, PredictEditsRateCompletionsFeatureFlag,
 };
 use gpui::actions;
-use language::language_settings::{AllLanguageSettings, InlineCompletionProvider};
+use language::language_settings::{AllLanguageSettings, EditPredictionProvider};
 use settings::update_settings_file;
 use ui::App;
 use workspace::Workspace;
@@ -44,7 +44,7 @@ pub fn init(cx: &mut App) {
                 move |file, _| {
                     file.features
                         .get_or_insert(Default::default())
-                        .inline_completion_provider = Some(InlineCompletionProvider::None)
+                        .edit_prediction_provider = Some(EditPredictionProvider::None)
                 },
             );
 

crates/zeta/src/onboarding_banner.rs 🔗

@@ -1,7 +1,7 @@
 use chrono::Utc;
 use feature_flags::{FeatureFlagAppExt as _, PredictEditsFeatureFlag};
 use gpui::Subscription;
-use language::language_settings::{all_language_settings, InlineCompletionProvider};
+use language::language_settings::{all_language_settings, EditPredictionProvider};
 use settings::SettingsStore;
 use ui::{prelude::*, ButtonLike, Tooltip};
 use util::ResultExt;
@@ -11,7 +11,7 @@ use crate::onboarding_event;
 /// Prompts the user to try Zed's Edit Prediction feature
 pub struct ZedPredictBanner {
     dismissed: bool,
-    provider: InlineCompletionProvider,
+    provider: EditPredictionProvider,
     _subscription: Subscription,
 }
 
@@ -19,7 +19,7 @@ impl ZedPredictBanner {
     pub fn new(cx: &mut Context<Self>) -> Self {
         Self {
             dismissed: get_dismissed(),
-            provider: all_language_settings(None, cx).inline_completions.provider,
+            provider: all_language_settings(None, cx).edit_predictions.provider,
             _subscription: cx.observe_global::<SettingsStore>(Self::handle_settings_changed),
         }
     }
@@ -29,7 +29,7 @@ impl ZedPredictBanner {
     }
 
     fn handle_settings_changed(&mut self, cx: &mut Context<Self>) {
-        let new_provider = all_language_settings(None, cx).inline_completions.provider;
+        let new_provider = all_language_settings(None, cx).edit_predictions.provider;
 
         if new_provider == self.provider {
             return;

crates/zeta/src/onboarding_modal.rs 🔗

@@ -9,7 +9,7 @@ use gpui::{
     ease_in_out, svg, Animation, AnimationExt as _, ClickEvent, DismissEvent, Entity, EventEmitter,
     FocusHandle, Focusable, MouseDownEvent, Render,
 };
-use language::language_settings::{AllLanguageSettings, InlineCompletionProvider};
+use language::language_settings::{AllLanguageSettings, EditPredictionProvider};
 use settings::{update_settings_file, Settings};
 use ui::{prelude::*, Checkbox, TintColor};
 use util::ResultExt;
@@ -105,7 +105,7 @@ impl ZedPredictModal {
                 update_settings_file::<AllLanguageSettings>(this.fs.clone(), cx, move |file, _| {
                     file.features
                         .get_or_insert(Default::default())
-                        .inline_completion_provider = Some(InlineCompletionProvider::Zed);
+                        .edit_prediction_provider = Some(EditPredictionProvider::Zed);
                 });
 
                 cx.emit(DismissEvent);

crates/zeta/src/zeta.rs 🔗

@@ -1500,7 +1500,7 @@ impl ZetaInlineCompletionProvider {
     }
 }
 
-impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvider {
+impl inline_completion::EditPredictionProvider for ZetaInlineCompletionProvider {
     fn name() -> &'static str {
         "zed-predict"
     }

docs/src/completions.md 🔗

@@ -29,7 +29,7 @@ To use GitHub Copilot (enabled by default), add the following to your `settings.
 ```json
 {
   "features": {
-    "inline_completion_provider": "copilot"
+    "edit_prediction_provider": "copilot"
   }
 }
 ```
@@ -43,7 +43,7 @@ To use Supermaven, add the following to your `settings.json`:
 ```json
 {
   "features": {
-    "inline_completion_provider": "supermaven"
+    "edit_prediction_provider": "supermaven"
   }
 }
 ```
@@ -56,23 +56,23 @@ Once you have configured an Edit Prediction provider, you can start using edit p
 
 There are a number of actions/shortcuts available to interact with edit predictions:
 
-- `editor: accept inline completion` (`tab`): To accept the current edit prediction
-- `editor: accept partial inline completion` (`ctrl-cmd-right`): To accept the current edit prediction up to the next word boundary
-- `editor: show inline completion` (`alt-tab`): Trigger an edit prediction request manually
-- `editor: next inline completion` (`alt-tab`): To cycle to the next edit prediction
-- `editor: previous inline completion` (`alt-shift-tab`): To cycle to the previous edit prediction
+- `editor: accept edit prediction` (`tab`): To accept the current edit prediction
+- `editor: accept partial edit prediction` (`ctrl-cmd-right`): To accept the current edit prediction up to the next word boundary
+- `editor: show edit prediction` (`alt-tab`): Trigger an edit prediction request manually
+- `editor: next edit prediction` (`alt-tab`): To cycle to the next edit prediction
+- `editor: previous edit prediction` (`alt-shift-tab`): To cycle to the previous edit prediction
 
-### Disabling Inline-Completions
+### Disabling Edit Prediction
 
-To disable completions that appear automatically as you type, add the following to your `settings.json`:
+To disable predictions that appear automatically as you type, add the following to your `settings.json`:
 
 ```json
 {
-  "show_inline_completions": false
+  "show_edit_predictions": false
 }
 ```
 
-You can trigger edit predictions manually by executing `editor: show inline completion` (`alt-tab`).
+You can trigger edit predictions manually by executing `editor: show edit prediction` (`alt-tab`).
 
 You can also add this as a language-specific setting in your `settings.json` to disable edit predictions for a specific language:
 
@@ -80,7 +80,7 @@ You can also add this as a language-specific setting in your `settings.json` to
 {
   "language": {
     "python": {
-      "show_inline_completions": false
+      "show_edit_predictions": false
     }
   }
 }

docs/src/configuring-zed.md 🔗

@@ -378,11 +378,11 @@ There are two options to choose from:
 ## Edit Predictions
 
 - Description: Settings for edit predictions.
-- Setting: `inline_completions`
+- Setting: `edit_predictions`
 - Default:
 
 ```json
-  "inline_completions": {
+  "edit_predictions": {
     "disabled_globs": [
       "**/.env*",
       "**/*.pem",
@@ -409,7 +409,7 @@ List of `string` values
 ## Edit Predictions Disabled in
 
 - Description: A list of language scopes in which edit predictions should be disabled.
-- Setting: `inline_completions_disabled_in`
+- Setting: `edit_predictions_disabled_in`
 - Default: `[]`
 
 **Options**
@@ -434,7 +434,7 @@ List of `string` values
 {
   "languages": {
     "Go": {
-      "inline_completions_disabled_in": ["comment", "string"]
+      "edit_predictions_disabled_in": ["comment", "string"]
     }
   }
 }
@@ -1478,7 +1478,7 @@ The following settings can be overridden for each specific language:
 - [`hard_tabs`](#hard-tabs)
 - [`preferred_line_length`](#preferred-line-length)
 - [`remove_trailing_whitespace_on_save`](#remove-trailing-whitespace-on-save)
-- [`show_inline_completions`](#show-inline-completions)
+- [`show_edit_predictions`](#show-edit-predictions)
 - [`show_whitespaces`](#show-whitespaces)
 - [`soft_wrap`](#soft-wrap)
 - [`tab_size`](#tab-size)
@@ -1654,8 +1654,8 @@ Or to set a `socks5` proxy:
 
 ## Show Edit Predictions
 
-- Description: Whether to show edit predictions as you type or manually by triggering `editor::ShowInlineCompletion`.
-- Setting: `show_inline_completions`
+- Description: Whether to show edit predictions as you type or manually by triggering `editor::ShowEditPrediction`.
+- Setting: `show_edit_predictions`
 - Default: `true`
 
 **Options**

docs/src/key-bindings.md 🔗

@@ -119,7 +119,7 @@ command palette, by looking in the default keymaps for
 or
 [Linux](https://github.com/zed-industries/zed/blob/main/assets/keymaps/default-linux.json), or by using Zed's autocomplete in your keymap file.
 
-Most actions do not require any arguments, and so you can bind them as strings: `"ctrl-a": "language_selector::Toggle"`. Some require a single argument, and must be bound as an array: `"ctrl-a": ["workspace::ActivatePaneInDirection", "down"]`. Some actions require multiple arguments, and are bound as an array of a string and an object: `"ctrl-a": ["pane::DeploySearch", { "replace_enabled": true }]`.
+Most actions do not require any arguments, and so you can bind them as strings: `"ctrl-a": "language_selector::Toggle"`. Some require a single argument, and must be bound as an array: `"cmd-1": ["workspace::ActivatePane", 0]`. Some actions require multiple arguments, and are bound as an array of a string and an object: `"ctrl-a": ["pane::DeploySearch", { "replace_enabled": true }]`.
 
 ### Precedence
 

docs/src/vim.md 🔗

@@ -368,10 +368,10 @@ But you cannot use the same shortcuts to move between all the editor docks (the
 {
   "context": "Dock",
   "bindings": {
-    "ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
-    "ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
-    "ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
-    "ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"]
+    "ctrl-w h": "workspace::ActivatePaneLeft",
+    "ctrl-w l": "workspace::ActivatePaneRight",
+    "ctrl-w k": "workspace::ActivatePaneUp",
+    "ctrl-w j": "workspace::ActivatePaneDown"
     // ... or other keybindings
   }
 }
@@ -399,12 +399,7 @@ Vim mode comes with shortcuts to surround the selection in normal mode (`ys`), b
 {
   "context": "vim_mode == visual",
   "bindings": {
-    "shift-s": [
-      "vim::PushOperator",
-      {
-        "AddSurrounds": {}
-      }
-    ]
+    "shift-s": ["vim::PushAddSurrounds", {}]
   }
 }
 ```
@@ -416,8 +411,8 @@ The [Sneak motion](https://github.com/justinmk/vim-sneak) feature allows for qui
   {
     "context": "vim_mode == normal || vim_mode == visual",
     "bindings": {
-      "s": ["vim::PushOperator", { "Sneak": {} }],
-      "S": ["vim::PushOperator", { "SneakBackward": {} }]
+      "s": ["vim::PushSneak", {}],
+      "S": ["vim::PushSneakBackward", {}]
     }
   }
 ]