From 00c2a300592a38e33b9e03000c1ba67d232bebee Mon Sep 17 00:00:00 2001 From: smit <0xtimsb@gmail.com> Date: Fri, 7 Feb 2025 21:17:07 +0530 Subject: [PATCH] Migrate keymap and settings + edit predictions rename (#23834) - [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 Co-authored-by: Max Brunsfeld --- 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 +- .../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 +- .../src/inline_completion.rs | 4 +- .../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 +- .../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 | 374 +++++++- 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 +- .../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, 2106 insertions(+), 617 deletions(-) create mode 100644 crates/migrator/Cargo.toml create mode 120000 crates/migrator/LICENSE-GPL create mode 100644 crates/migrator/src/migrator.rs diff --git a/Cargo.lock b/Cargo.lock index eae4c7bea9..3888da0dd0 100644 --- a/Cargo.lock +++ b/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", diff --git a/Cargo.toml b/Cargo.toml index e108955b78..217cdd9d1f 100644 --- a/Cargo.toml +++ b/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" } diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 7fd499f204..217af10cc6 100644 --- a/assets/keymaps/default-linux.json +++ b/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" } }, { diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 1ddd31e053..fe3d7c413e 100644 --- a/assets/keymaps/default-macos.json +++ b/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" } }, { diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index bad0b4e604..af1822d706 100644 --- a/assets/keymaps/vim.json +++ b/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", diff --git a/assets/settings/default.json b/assets/settings/default.json index ca5247d19b..3369807b32 100644 --- a/assets/settings/default.json +++ b/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*", diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 6b65b8057c..497f36f603 100644 --- a/crates/copilot/src/copilot.rs +++ b/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 diff --git a/crates/copilot/src/copilot_completion_provider.rs b/crates/copilot/src/copilot_completion_provider.rs index 51aa112849..0e494056ec 100644 --- a/crates/copilot/src/copilot_completion_provider.rs +++ b/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(); diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index fe2ae0be49..27266f3dd6 100644 --- a/crates/editor/src/actions.rs +++ b/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, } #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[serde(deny_unknown_fields)] pub struct ComposeCompletion { #[serde(default)] pub item_ix: Option, } #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[serde(deny_unknown_fields)] pub struct ConfirmCodeAction { #[serde(default)] pub item_ix: Option, } #[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, @@ -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, diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index f2752fb532..a967750ccc 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -517,7 +517,6 @@ impl CompletionsMenu { } else { None }; - let color_swatch = completion .color() .map(|color| div().size_4().bg(color).rounded_sm()); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 16fb9b23e4..24ec46ca61 100644 --- a/crates/editor/src/editor.rs +++ b/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>>>, gutter_hovered: bool, hovered_link_state: Option, - inline_completion_provider: Option, + edit_prediction_provider: Option, code_action_providers: Vec>, active_inline_completion: Option, /// 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( + pub fn set_edit_prediction_provider( &mut self, provider: Option>, window: &mut Window, cx: &mut Context, ) 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, ) { @@ -1900,11 +1900,11 @@ impl Editor { pub fn set_show_inline_completions( &mut self, - show_inline_completions: Option, + show_edit_predictions: Option, window: &mut Window, cx: &mut Context, ) { - 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, ) -> 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, ) -> 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, ) { @@ -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, ) { @@ -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, ) { @@ -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, ) { @@ -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, ) { @@ -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, ) { - 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> { - Some(self.inline_completion_provider.as_ref()?.provider.clone()) + pub fn edit_prediction_provider(&self) -> Option> { + 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, ) -> Option { - 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!( diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 098ff62dae..9203a8f953 100644 --- a/crates/editor/src/editor_settings.rs +++ b/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, + pub show_edit_predictions_in_menu: Option, /// Jupyter REPL settings. pub jupyter: Option, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 5fed6d1643..5247c629c0 100644 --- a/crates/editor/src/editor_tests.rs +++ b/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), " diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index ab211f3d3e..04bcf72262 100644 --- a/crates/editor/src/element.rs +++ b/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()) diff --git a/crates/editor/src/inline_completion_tests.rs b/crates/editor/src/inline_completion_tests.rs index a5b9e8a3e0..258a878094 100644 --- a/crates/editor/src/inline_completion_tests.rs +++ b/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" } diff --git a/crates/inline_completion/src/inline_completion.rs b/crates/inline_completion/src/inline_completion.rs index 7810d99785..6a1754c377 100644 --- a/crates/inline_completion/src/inline_completion.rs +++ b/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 InlineCompletionProviderHandle for Entity where - T: InlineCompletionProvider, + T: EditPredictionProvider, { fn name(&self) -> &'static str { T::name() diff --git a/crates/inline_completion_button/src/inline_completion_button.rs b/crates/inline_completion_button/src/inline_completion_button.rs index 19cb8b4843..5ba3d52772 100644 --- a/crates/inline_completion_button/src/inline_completion_button.rs +++ b/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, language: Option>, file: Option>, - inline_completion_provider: Option>, + edit_prediction_provider: Option>, fs: Arc, workspace: WeakEntity, user_store: Entity, @@ -67,10 +65,10 @@ impl Render for InlineCompletionButton { fn render(&mut self, _: &mut Window, cx: &mut Context) -> 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::() { 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::(&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, 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::(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, cx: &mut App, provider: InlineCompletionProvider) { +fn set_completion_provider(fs: Arc, cx: &mut App, provider: EditPredictionProvider) { update_settings_file::(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, 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::(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, cx: &mut App) { update_settings_file::(fs, cx, move |file, _| { file.features .get_or_insert(Default::default()) - .inline_completion_provider = Some(InlineCompletionProvider::None); + .edit_prediction_provider = Some(EditPredictionProvider::None); }); } diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index fb8eb28a61..c8f47f7c83 100644 --- a/crates/language/src/language_settings.rs +++ b/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, pub(crate) file_types: HashMap, 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, /// 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, + pub edit_predictions_disabled_in: Vec, /// 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, /// When to show edit predictions previews in buffer. @@ -248,7 +248,7 @@ pub struct AllLanguageSettingsContent { pub features: Option, /// The edit prediction settings. #[serde(default)] - pub inline_completions: Option, + pub edit_predictions: Option, /// The default language settings. #[serde(flatten)] pub defaults: LanguageSettingsContent, @@ -347,11 +347,11 @@ pub struct LanguageSettingsContent { #[serde(default)] pub language_servers: Option>, /// 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, + pub show_edit_predictions: Option, /// 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>, + pub edit_predictions_disabled_in: Option>, /// Whether to show tabs and spaces in the editor. #[serde(default)] pub show_whitespaces: Option, @@ -442,7 +442,7 @@ pub struct FeaturesContent { /// Whether the GitHub Copilot feature is enabled. pub copilot: Option, /// Determines which edit prediction provider to use. - pub inline_completion_provider: Option, + pub edit_prediction_provider: Option, } /// 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>, 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( diff --git a/crates/migrator/Cargo.toml b/crates/migrator/Cargo.toml new file mode 100644 index 0000000000..f5be671960 --- /dev/null +++ b/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 diff --git a/crates/migrator/LICENSE-GPL b/crates/migrator/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/migrator/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/migrator/src/migrator.rs b/crates/migrator/src/migrator.rs new file mode 100644 index 0000000000..0d02a45fb8 --- /dev/null +++ b/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 { + 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 { + 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 { + migrate( + &text, + SETTINGS_MIGRATION_PATTERNS, + &SETTINGS_MIGRATION_QUERY, + ) +} + +type MigrationPatterns = &'static [( + &'static str, + fn(&str, &QueryMatch, &Query) -> Option<(Range, 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 = LazyLock::new(|| { + Query::new( + &tree_sitter_json::LANGUAGE.into(), + &KEYMAP_MIGRATION_TRANSFORMATION_PATTERNS + .iter() + .map(|pattern| pattern.0) + .collect::(), + ) + .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, 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> = 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, 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>> = 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 = LazyLock::new(|| { + Query::new( + &tree_sitter_json::LANGUAGE.into(), + &KEYMAP_MIGRATION_REPLACEMENT_PATTERNS + .iter() + .map(|pattern| pattern.0) + .collect::(), + ) + .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, 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> = 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, 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, 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> = 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 = LazyLock::new(|| { + Query::new( + &tree_sitter_json::LANGUAGE.into(), + &SETTINGS_MIGRATION_PATTERNS + .iter() + .map(|pattern| pattern.0) + .collect::(), + ) + .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, 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> = 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, 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, 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 + } + } + } + "#, + ), + ) + } +} diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index cf7793730c..f004e50787 100644 --- a/crates/picker/src/picker.rs +++ b/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, } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c4af13f6cb..c308e8ca4e 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/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, diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index d8cba0d0a6..e983682225 100644 --- a/crates/search/src/buffer_search.rs +++ b/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, diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 4ef3e27c94..b2d46f6ee8 100644 --- a/crates/settings/Cargo.toml +++ b/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"] } diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index 3cb0d2c3a8..58c7915b91 100644 --- a/crates/settings/src/keymap_file.rs +++ b/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); /// 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, + keymap_file: KeymapFile, }, SomeFailedToLoad { key_bindings: Vec, + 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 { 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::(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 { self.0.iter() } + + async fn load_keymap_file(fs: &Arc) -> Result { + match fs.load(paths::keymap_file()).await { + result @ Ok(_) => result, + Err(err) => { + if let Some(e) = err.downcast_ref::() { + 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) -> 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() { diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 622c42d006..ba93391804 100644 --- a/crates/settings/src/settings_file.rs +++ b/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, cx: &mut App, - settings_changed: impl Fn(Option, &mut App) + 'static, + settings_changed: impl Fn(Result, &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() { diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index b0ecd056cc..2337f7fef3 100644 --- a/crates/settings/src/settings_store.rs +++ b/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 { 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) { + 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 { diff --git a/crates/supermaven/src/supermaven.rs b/crates/supermaven/src/supermaven.rs index f64ac48b48..a289c7f68b 100644 --- a/crates/supermaven/src/supermaven.rs +++ b/crates/supermaven/src/supermaven.rs @@ -31,16 +31,16 @@ pub fn init(client: Arc, 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::(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()); diff --git a/crates/supermaven/src/supermaven_completion_provider.rs b/crates/supermaven/src/supermaven_completion_provider.rs index b14d7d54c2..3e70a1c576 100644 --- a/crates/supermaven/src/supermaven_completion_provider.rs +++ b/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" } diff --git a/crates/tab_switcher/src/tab_switcher.rs b/crates/tab_switcher/src/tab_switcher.rs index 5af89a92d2..0446444b88 100644 --- a/crates/tab_switcher/src/tab_switcher.rs +++ b/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, diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index f2e74b71f4..14a3e111b3 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/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, + ) { + 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) { + 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) = diff --git a/crates/title_bar/src/application_menu.rs b/crates/title_bar/src/application_menu.rs index eb4acfaf67..955550596d 100644 --- a/crates/title_bar/src/application_menu.rs +++ b/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, ) { @@ -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); diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 4b3b89decb..801e701e78 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/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::().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::().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::().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(); } diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index a097a6ebb2..b8a375860a 100644 --- a/crates/vim/src/motion.rs +++ b/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, diff --git a/crates/vim/src/normal/increment.rs b/crates/vim/src/normal/increment.rs index 36bcce1b8e..56b91cdd1c 100644 --- a/crates/vim/src/normal/increment.rs +++ b/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, diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index eefd92d0d1..417a4aa67e 100644 --- a/crates/vim/src/normal/paste.rs +++ b/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, diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 948141c4e7..89564af52e 100644 --- a/crates/vim/src/normal/search.rs +++ b/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, diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index cd1269e264..285f79095a 100644 --- a/crates/vim/src/object.rs +++ b/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, diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index a4994fb4d3..4c09984a75 100644 --- a/crates/vim/src/state.rs +++ b/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, }, ChangeSurrounds { diff --git a/crates/vim/src/surrounds.rs b/crates/vim/src/surrounds.rs index fd774d6159..fcf33d9f77 100644 --- a/crates/vim/src/surrounds.rs +++ b/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"), )]) }); diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 34ae719fbf..6c336375ae 100644 --- a/crates/vim/src/test.rs +++ b/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"), ), ]) diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index f479acee31..a3a3afc443 100644 --- a/crates/vim/src/vim.rs +++ b/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,24 +46,10 @@ 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 -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] -pub struct ResizePane(pub ResizeIntent); - -/// An Action to Switch between modes -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] -pub struct SwitchMode(pub Mode); - -/// 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); - /// 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)] @@ -71,29 +58,126 @@ struct Number(usize); #[derive(Clone, Deserialize, JsonSchema, PartialEq)] struct SelectRegister(String); +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[serde(deny_unknown_fields)] +struct PushObject { + around: bool, +} + +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[serde(deny_unknown_fields)] +struct PushFindForward { + before: bool, +} + +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[serde(deny_unknown_fields)] +struct PushFindBackward { + after: bool, +} + +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[serde(deny_unknown_fields)] +struct PushSneak { + first_char: Option, +} + +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[serde(deny_unknown_fields)] +struct PushSneakBackward { + first_char: Option, +} + +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[serde(deny_unknown_fields)] +struct PushAddSurrounds {} + +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[serde(deny_unknown_fields)] +struct PushChangeSurrounds { + target: Option, +} + +#[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, +} + +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[serde(deny_unknown_fields)] +struct PushLiteral { + prefix: Option, +} + 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, action: &PushOperator, window, cx| { - vim.push_operator(action.0.clone(), 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, _: &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, ); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 4480f4acbf..d687f3bfd7 100644 --- a/crates/workspace/src/pane.rs +++ b/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, } #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] -#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct CloseInactiveItems { pub save_intent: Option, #[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, #[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, } #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] +#[serde(deny_unknown_fields)] pub struct DeploySearch { #[serde(default)] pub replace_enabled: bool, diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 7409dead14..0b2cc55e3b 100644 --- a/crates/workspace/src/pane_group.rs +++ b/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}; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 0472d1ce98..2778382f3e 100644 --- a/crates/workspace/src/workspace.rs +++ b/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, } #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema)] -#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct Save { pub save_intent: Option, } #[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema)] -#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct CloseAllItemsAndPanes { pub save_intent: Option, } #[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema)] -#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct CloseInactiveTabsAndPanes { pub save_intent: Option, } @@ -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, } @@ -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); })) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 3aa578de48..56a493bed8 100644 --- a/crates/zed/src/zed.rs +++ b/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(¬ification_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(¬ification_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 = ::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 = ::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, cx: &mut App) { +pub fn handle_settings_changed(result: Result, cx: &mut App) { struct SettingsParseErrorNotification; let id = NotificationId::unique::(); - match error { - Some(error) => { + match result { + Err(error) => { if let Some(InvalidSettingsError::LocalSettings { .. }) = error.downcast_ref::() { @@ -1283,7 +1355,10 @@ pub fn handle_settings_changed(error: Option, 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" => {} diff --git a/crates/zed/src/zed/inline_completion_registry.rs b/crates/zed/src/zed/inline_completion_registry.rs index 6e2879a6c9..8639ad51f9 100644 --- a/crates/zed/src/zed/inline_completion_registry.rs +++ b/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, user_store: Entity, 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, user_store: Entity, 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, user_store: Entity, 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, user_store: Entity, 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, user_store: Entity, 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, user_store: Entity, 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, user_store: Entity, 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, AnyWindowHandle>>>, - provider: InlineCompletionProvider, + provider: EditPredictionProvider, client: &Arc, user_store: Entity, 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| { - 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| { - 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, user_store: Entity, 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::() || (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); } } } diff --git a/crates/zed/src/zed/quick_action_bar.rs b/crates/zed/src/zed/quick_action_bar.rs index 96e839d523..67161de75f 100644 --- a/crates/zed/src/zed/quick_action_bar.rs +++ b/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, ); diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 10075e5e12..2299bf58bc 100644 --- a/crates/zed_actions/src/lib.rs +++ b/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>, @@ -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>, @@ -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, } @@ -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 diff --git a/crates/zeta/src/init.rs b/crates/zeta/src/init.rs index a45f5a58a2..ee0d810e5b 100644 --- a/crates/zeta/src/init.rs +++ b/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) }, ); diff --git a/crates/zeta/src/onboarding_banner.rs b/crates/zeta/src/onboarding_banner.rs index 713b84604a..4b5ef95c61 100644 --- a/crates/zeta/src/onboarding_banner.rs +++ b/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 { 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::(Self::handle_settings_changed), } } @@ -29,7 +29,7 @@ impl ZedPredictBanner { } fn handle_settings_changed(&mut self, cx: &mut Context) { - 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; diff --git a/crates/zeta/src/onboarding_modal.rs b/crates/zeta/src/onboarding_modal.rs index 41fc289a5f..7ba7f4b50b 100644 --- a/crates/zeta/src/onboarding_modal.rs +++ b/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::(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); diff --git a/crates/zeta/src/zeta.rs b/crates/zeta/src/zeta.rs index a2be4811fa..7741e52f31 100644 --- a/crates/zeta/src/zeta.rs +++ b/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" } diff --git a/docs/src/completions.md b/docs/src/completions.md index f7f0520092..80c2d150c7 100644 --- a/docs/src/completions.md +++ b/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 } } } diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index 07374cc25c..779c9e2a59 100644 --- a/docs/src/configuring-zed.md +++ b/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** diff --git a/docs/src/key-bindings.md b/docs/src/key-bindings.md index 4d0a33ce55..7482c8563c 100644 --- a/docs/src/key-bindings.md +++ b/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 diff --git a/docs/src/vim.md b/docs/src/vim.md index 25e44dda3f..0555cd817e 100644 --- a/docs/src/vim.md +++ b/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", {}] } } ]