Add keybindings to jump to first / last file in project panel (#11073)

blufony created

Release Notes:
- vim: Support `g g`/`G` to go to top/bottom of the project panel.

In vim type environments i usually expect to be able to jump to the top
and bottom and i was confused as to why that wasn't possible in the
project panel. So i added it. If anyone using different keymaps also
thinks this might be useful i would be happy to add other defaults. I
think for vim mode it is the most useful though, because you tend to not
use your mouse in vim mode.

This is my first contribution to any rust project, but it seemed like a
good starting point.
The function select_last() is inspired by select_first.
I was also thinking about adding a bigger jump keybinding, that would
jump for example 5 entries up / down, with vim default keybindings "{" /
"}".
FYI: I tested this on linux only and don't have access to any macos
machines.


- N/A

Change summary

assets/keymaps/vim.json                   |  4 ++
crates/project_panel/src/project_panel.rs | 29 +++++++++++++++++++++---
2 files changed, 28 insertions(+), 5 deletions(-)

Detailed changes

assets/keymaps/vim.json 🔗

@@ -625,7 +625,9 @@
       "t": "project_panel::OpenPermanent",
       "v": "project_panel::OpenPermanent",
       "p": "project_panel::Open",
-      "x": "project_panel::RevealInFinder"
+      "x": "project_panel::RevealInFinder",
+      "shift-g": "menu::SelectLast",
+      "g g": "menu::SelectFirst"
     }
   }
 ]

crates/project_panel/src/project_panel.rs 🔗

@@ -16,7 +16,7 @@ use gpui::{
     ParentElement, Pixels, Point, PromptLevel, Render, Stateful, Styled, Subscription, Task,
     UniformListScrollHandle, View, ViewContext, VisualContext as _, WeakView, WindowContext,
 };
-use menu::{Confirm, SelectNext, SelectPrev};
+use menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev};
 use project::{Entry, EntryKind, Fs, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
 use project_panel_settings::{ProjectPanelDockPosition, ProjectPanelSettings};
 use serde::{Deserialize, Serialize};
@@ -644,7 +644,7 @@ impl ProjectPanel {
             self.autoscroll(cx);
             cx.notify();
         } else {
-            self.select_first(cx);
+            self.select_first(&SelectFirst {}, cx);
         }
     }
 
@@ -989,11 +989,11 @@ impl ProjectPanel {
                 }
             }
         } else {
-            self.select_first(cx);
+            self.select_first(&SelectFirst {}, cx);
         }
     }
 
-    fn select_first(&mut self, cx: &mut ViewContext<Self>) {
+    fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
         let worktree = self
             .visible_entries
             .first()
@@ -1012,6 +1012,25 @@ impl ProjectPanel {
         }
     }
 
+    fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
+        let worktree = self
+            .visible_entries
+            .last()
+            .and_then(|(worktree_id, _)| self.project.read(cx).worktree_for_id(*worktree_id, cx));
+        if let Some(worktree) = worktree {
+            let worktree = worktree.read(cx);
+            let worktree_id = worktree.id();
+            if let Some(last_entry) = worktree.entries(true).last() {
+                self.selection = Some(Selection {
+                    worktree_id,
+                    entry_id: last_entry.id,
+                });
+                self.autoscroll(cx);
+                cx.notify();
+            }
+        }
+    }
+
     fn autoscroll(&mut self, cx: &mut ViewContext<Self>) {
         if let Some((_, _, index)) = self.selection.and_then(|s| self.index_for_selection(s)) {
             self.scroll_handle.scroll_to_item(index);
@@ -1751,6 +1770,8 @@ impl Render for ProjectPanel {
                 .key_context(self.dispatch_context(cx))
                 .on_action(cx.listener(Self::select_next))
                 .on_action(cx.listener(Self::select_prev))
+                .on_action(cx.listener(Self::select_first))
+                .on_action(cx.listener(Self::select_last))
                 .on_action(cx.listener(Self::expand_selected_entry))
                 .on_action(cx.listener(Self::collapse_selected_entry))
                 .on_action(cx.listener(Self::collapse_all_entries))