Add select_first and select_last bindings to outline view

Max Brunsfeld , Antonio Scandurra , and Nathan Sobo created

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/editor/src/editor.rs                 | 10 ++++++++++
crates/file_finder/src/file_finder.rs       | 13 +++++--------
crates/gpui/src/keymap.rs                   | 19 +------------------
crates/outline/src/outline.rs               | 23 +++++++++++++++--------
crates/project_panel/src/project_panel.rs   | 11 +++++------
crates/theme_selector/src/theme_selector.rs | 13 +++++++------
crates/workspace/src/menu.rs                | 19 +++++++++++++++++++
crates/workspace/src/workspace.rs           |  5 ++++-
8 files changed, 66 insertions(+), 47 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -2390,6 +2390,11 @@ impl Editor {
     }
 
     pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
+        if matches!(self.mode, EditorMode::SingleLine) {
+            cx.propagate_action();
+            return;
+        }
+
         let selection = Selection {
             id: post_inc(&mut self.next_selection_id),
             start: 0,
@@ -2407,6 +2412,11 @@ impl Editor {
     }
 
     pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext<Self>) {
+        if matches!(self.mode, EditorMode::SingleLine) {
+            cx.propagate_action();
+            return;
+        }
+
         let cursor = self.buffer.read(cx).read(cx).len();
         let selection = Selection {
             id: post_inc(&mut self.next_selection_id),

crates/file_finder/src/file_finder.rs 🔗

@@ -3,11 +3,7 @@ use fuzzy::PathMatch;
 use gpui::{
     action,
     elements::*,
-    keymap::{
-        self,
-        menu::{SelectNext, SelectPrev},
-        Binding,
-    },
+    keymap::{self, Binding},
     AppContext, Axis, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View,
     ViewContext, ViewHandle, WeakViewHandle,
 };
@@ -22,7 +18,10 @@ use std::{
     },
 };
 use util::post_inc;
-use workspace::{Settings, Workspace};
+use workspace::{
+    menu::{Confirm, SelectNext, SelectPrev},
+    Settings, Workspace,
+};
 
 pub struct FileFinder {
     handle: WeakViewHandle<Self>,
@@ -40,7 +39,6 @@ pub struct FileFinder {
 }
 
 action!(Toggle);
-action!(Confirm);
 action!(Select, ProjectPath);
 
 pub fn init(cx: &mut MutableAppContext) {
@@ -53,7 +51,6 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_bindings(vec![
         Binding::new("cmd-p", Toggle, None),
         Binding::new("escape", Toggle, Some("FileFinder")),
-        Binding::new("enter", Confirm, Some("FileFinder")),
     ]);
 }
 

crates/gpui/src/keymap.rs 🔗

@@ -23,6 +23,7 @@ struct Pending {
     context: Option<Context>,
 }
 
+#[derive(Default)]
 pub struct Keymap(Vec<Binding>);
 
 pub struct Binding {
@@ -153,24 +154,6 @@ impl Keymap {
     }
 }
 
-pub mod menu {
-    use crate::action;
-
-    action!(SelectPrev);
-    action!(SelectNext);
-}
-
-impl Default for Keymap {
-    fn default() -> Self {
-        Self(vec![
-            Binding::new("up", menu::SelectPrev, Some("menu")),
-            Binding::new("ctrl-p", menu::SelectPrev, Some("menu")),
-            Binding::new("down", menu::SelectNext, Some("menu")),
-            Binding::new("ctrl-n", menu::SelectNext, Some("menu")),
-        ])
-    }
-}
-
 impl Binding {
     pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
         let context = if let Some(context) = context {

crates/outline/src/outline.rs 🔗

@@ -8,11 +8,7 @@ use gpui::{
     elements::*,
     fonts::{self, HighlightStyle},
     geometry::vector::Vector2F,
-    keymap::{
-        self,
-        menu::{SelectNext, SelectPrev},
-        Binding,
-    },
+    keymap::{self, Binding},
     AppContext, Axis, Entity, MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
     WeakViewHandle,
 };
@@ -24,21 +20,24 @@ use std::{
     ops::Range,
     sync::Arc,
 };
-use workspace::{Settings, Workspace};
+use workspace::{
+    menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev},
+    Settings, Workspace,
+};
 
 action!(Toggle);
-action!(Confirm);
 
 pub fn init(cx: &mut MutableAppContext) {
     cx.add_bindings([
         Binding::new("cmd-shift-O", Toggle, Some("Editor")),
         Binding::new("escape", Toggle, Some("OutlineView")),
-        Binding::new("enter", Confirm, Some("OutlineView")),
     ]);
     cx.add_action(OutlineView::toggle);
     cx.add_action(OutlineView::confirm);
     cx.add_action(OutlineView::select_prev);
     cx.add_action(OutlineView::select_next);
+    cx.add_action(OutlineView::select_first);
+    cx.add_action(OutlineView::select_last);
 }
 
 struct OutlineView {
@@ -197,6 +196,14 @@ impl OutlineView {
         }
     }
 
+    fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
+        self.select(0, true, false, cx);
+    }
+
+    fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
+        self.select(self.matches.len().saturating_sub(1), true, false, cx);
+    }
+
     fn select(&mut self, index: usize, navigate: bool, center: bool, cx: &mut ViewContext<Self>) {
         self.selected_match_index = index;
         self.list_state.scroll_to(if center {

crates/project_panel/src/project_panel.rs 🔗

@@ -4,11 +4,7 @@ use gpui::{
         Align, ConstrainedBox, Empty, Flex, Label, MouseEventHandler, ParentElement, ScrollTarget,
         Svg, UniformList, UniformListState,
     },
-    keymap::{
-        self,
-        menu::{SelectNext, SelectPrev},
-        Binding,
-    },
+    keymap::{self, Binding},
     platform::CursorStyle,
     AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, ReadModel, View,
     ViewContext, ViewHandle, WeakViewHandle,
@@ -20,7 +16,10 @@ use std::{
     ffi::OsStr,
     ops::Range,
 };
-use workspace::{Settings, Workspace};
+use workspace::{
+    menu::{SelectNext, SelectPrev},
+    Settings, Workspace,
+};
 
 pub struct ProjectPanel {
     project: ModelHandle<Project>,

crates/theme_selector/src/theme_selector.rs 🔗

@@ -3,7 +3,7 @@ use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
 use gpui::{
     action,
     elements::*,
-    keymap::{self, menu, Binding},
+    keymap::{self, Binding},
     AppContext, Axis, Element, ElementBox, Entity, MutableAppContext, RenderContext, View,
     ViewContext, ViewHandle,
 };
@@ -11,7 +11,10 @@ use parking_lot::Mutex;
 use postage::watch;
 use std::{cmp, sync::Arc};
 use theme::ThemeRegistry;
-use workspace::{AppState, Settings, Workspace};
+use workspace::{
+    menu::{Confirm, SelectNext, SelectPrev},
+    AppState, Settings, Workspace,
+};
 
 #[derive(Clone)]
 pub struct ThemeSelectorParams {
@@ -30,7 +33,6 @@ pub struct ThemeSelector {
     selected_index: usize,
 }
 
-action!(Confirm);
 action!(Toggle, ThemeSelectorParams);
 action!(Reload, ThemeSelectorParams);
 
@@ -45,7 +47,6 @@ pub fn init(params: ThemeSelectorParams, cx: &mut MutableAppContext) {
         Binding::new("cmd-k cmd-t", Toggle(params.clone()), None),
         Binding::new("cmd-k t", Reload(params.clone()), None),
         Binding::new("escape", Toggle(params.clone()), Some("ThemeSelector")),
-        Binding::new("enter", Confirm, Some("ThemeSelector")),
     ]);
 }
 
@@ -136,7 +137,7 @@ impl ThemeSelector {
         }
     }
 
-    fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
+    fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
         if self.selected_index > 0 {
             self.selected_index -= 1;
         }
@@ -145,7 +146,7 @@ impl ThemeSelector {
         cx.notify();
     }
 
-    fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
+    fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
         if self.selected_index + 1 < self.matches.len() {
             self.selected_index += 1;
         }

crates/workspace/src/menu.rs 🔗

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

crates/workspace/src/workspace.rs 🔗

@@ -1,3 +1,4 @@
+pub mod menu;
 pub mod pane;
 pub mod pane_group;
 pub mod settings;
@@ -48,6 +49,9 @@ action!(Save);
 action!(DebugElements);
 
 pub fn init(cx: &mut MutableAppContext) {
+    pane::init(cx);
+    menu::init(cx);
+
     cx.add_global_action(open);
     cx.add_global_action(move |action: &OpenPaths, cx: &mut MutableAppContext| {
         open_paths(&action.0.paths, &action.0.app_state, cx).detach();
@@ -84,7 +88,6 @@ pub fn init(cx: &mut MutableAppContext) {
             None,
         ),
     ]);
-    pane::init(cx);
 }
 
 pub struct AppState {