settings_ui: Add vim motions on navigation menu (#39988)

Alvaro Parker created

Closes #ISSUE

Release Notes:

- Added vim motions on settings navigation menu

Change summary

Cargo.lock                              |  1 +
assets/keymaps/vim.json                 | 25 ++++++++++++++++++-------
crates/settings_ui/src/settings_ui.rs   | 15 +++++++++++----
crates/vim/Cargo.toml                   |  1 +
crates/vim/src/test/vim_test_context.rs |  1 +
5 files changed, 32 insertions(+), 11 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -18662,6 +18662,7 @@ dependencies = [
  "serde",
  "serde_json",
  "settings",
+ "settings_ui",
  "task",
  "text",
  "theme",

assets/keymaps/vim.json 🔗

@@ -432,7 +432,7 @@
       "shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
       "shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
       "alt-.": "vim::RepeatFind",
-      
+
       // Changes
       "shift-r": "editor::Paste",
       "`": "vim::ConvertToLowerCase",
@@ -442,14 +442,14 @@
       "ctrl-r": "vim::Redo",
       "y": "vim::HelixYank",
       "p": "vim::HelixPaste",
-      "shift-p": ["vim::HelixPaste", { "before": true }],            
+      "shift-p": ["vim::HelixPaste", { "before": true }],
       ">": "vim::Indent",
       "<": "vim::Outdent",
       "=": "vim::AutoIndent",
       "d": "vim::HelixDelete",
       "c": "vim::HelixSubstitute",
       "alt-c": "vim::HelixSubstituteNoYank",
-      
+
       // Selection manipulation
       "s": "vim::HelixSelectRegex",
       "alt-s": ["editor::SplitSelectionIntoLines", { "keep_selections": true }],
@@ -466,7 +466,7 @@
       "alt-i": "editor::SelectSmallerSyntaxNode",
       "alt-p": "editor::SelectPreviousSyntaxNode",
       "alt-n": "editor::SelectNextSyntaxNode",
-      
+
       // Goto mode
       "g e": "vim::EndOfDocument",
       "g h": "vim::StartOfLine",
@@ -477,11 +477,11 @@
       "g b": "vim::WindowBottom",
       "g r": "editor::FindAllReferences", // zed specific
       "g n": "pane::ActivateNextItem",
-      "shift-l": "pane::ActivateNextItem",      
+      "shift-l": "pane::ActivateNextItem",
       "g p": "pane::ActivatePreviousItem",
       "shift-h": "pane::ActivatePreviousItem",
       "g .": "vim::HelixGotoLastModification", // go to last modification
-      
+
       // Window mode
       "space w h": "workspace::ActivatePaneLeft",
       "space w l": "workspace::ActivatePaneRight",
@@ -512,7 +512,7 @@
       "]": ["vim::PushHelixNext", { "around": true }],
       "[": ["vim::PushHelixPrevious", { "around": true }],
       "g q": "vim::PushRewrap",
-      "g w": "vim::PushRewrap",
+      "g w": "vim::PushRewrap"
       // "tab": "pane::ActivateNextItem",
       // "shift-tab": "pane::ActivatePrevItem",
     }
@@ -1017,5 +1017,16 @@
       // and Windows.
       "alt-l": "editor::AcceptEditPrediction"
     }
+  },
+  {
+    "context": "SettingsWindow > NavigationMenu && !search",
+    "bindings": {
+      "l": "settings_editor::ExpandNavEntry",
+      "h": "settings_editor::CollapseNavEntry",
+      "k": "settings_editor::FocusPreviousNavEntry",
+      "j": "settings_editor::FocusNextNavEntry",
+      "g g": "settings_editor::FocusFirstNavEntry",
+      "shift-g": "settings_editor::FocusLastNavEntry"
+    }
   }
 ]

crates/settings_ui/src/settings_ui.rs 🔗

@@ -7,9 +7,9 @@ use feature_flags::FeatureFlag;
 use fuzzy::StringMatchCandidate;
 use gpui::{
     Action, App, DEFAULT_ADDITIONAL_WINDOW_SIZE, Div, Entity, FocusHandle, Focusable, Global,
-    ListState, ReadGlobal as _, ScrollHandle, Stateful, Subscription, Task, TitlebarOptions,
-    UniformListScrollHandle, Window, WindowBounds, WindowHandle, WindowOptions, actions, div, list,
-    point, prelude::*, px, uniform_list,
+    KeyContext, ListState, ReadGlobal as _, ScrollHandle, Stateful, Subscription, Task,
+    TitlebarOptions, UniformListScrollHandle, Window, WindowBounds, WindowHandle, WindowOptions,
+    actions, div, list, point, prelude::*, px, uniform_list,
 };
 use heck::ToTitleCase as _;
 use project::{Project, WorktreeId};
@@ -2078,8 +2078,15 @@ impl SettingsWindow {
             "Focus Navbar"
         };
 
+        let mut key_context = KeyContext::new_with_defaults();
+        key_context.add("NavigationMenu");
+        key_context.add("menu");
+        if self.search_bar.focus_handle(cx).is_focused(window) {
+            key_context.add("search");
+        }
+
         v_flex()
-            .key_context("NavigationMenu")
+            .key_context(key_context)
             .on_action(cx.listener(|this, _: &CollapseNavEntry, window, cx| {
                 let Some(focused_entry) = this.focused_nav_entry(window, cx) else {
                     return;

crates/vim/Cargo.toml 🔗

@@ -67,6 +67,7 @@ lsp = { workspace = true, features = ["test-support"] }
 parking_lot.workspace = true
 project_panel.workspace = true
 release_channel.workspace = true
+settings_ui.workspace = true
 settings.workspace = true
 perf.workspace = true
 util = { workspace = true, features = ["test-support"] }

crates/vim/src/test/vim_test_context.rs 🔗

@@ -30,6 +30,7 @@ impl VimTestContext {
             editor::init_settings(cx);
             project::Project::init_settings(cx);
             theme::init(theme::LoadThemes::JustBase, cx);
+            settings_ui::init(cx);
         });
     }