editor: Add SelectPageUp/SelectPageDown actions (#13272)

Thorsten Ball created

This adds two new actions to `editor`:

- `editor::SelectPageUp`
- `editor::SelectPageDown`

On Linux they're bound by default to `shift-pageup` and
`shift-pagedown`, which matches VS Code and JetBrains.

Release Notes:

- N/A

Change summary

assets/keymaps/default-linux.json |  4 +-
crates/editor/src/actions.rs      |  2 +
crates/editor/src/editor.rs       | 36 +++++++++++++++++++++++++++-----
crates/editor/src/element.rs      |  2 +
crates/editor/src/scroll.rs       |  5 ++++
5 files changed, 41 insertions(+), 8 deletions(-)

Detailed changes

assets/keymaps/default-linux.json 🔗

@@ -62,11 +62,11 @@
       "ctrl-up": "editor::LineUp",
       "ctrl-down": "editor::LineDown",
       "pageup": "editor::PageUp",
-      // "shift-pageup": "editor::MovePageUp", todo(linux) should be 'select page up'
+      "shift-pageup": "editor::SelectPageUp",
       "home": "editor::MoveToBeginningOfLine",
       "down": "editor::MoveDown",
       "pagedown": "editor::PageDown",
-      // "shift-pagedown": "editor::MovePageDown", todo(linux) should be 'select page down'
+      "shift-pagedown": "editor::SelectPageDown",
       "end": "editor::MoveToEndOfLine",
       "left": "editor::MoveLeft",
       "right": "editor::MoveRight",

crates/editor/src/actions.rs 🔗

@@ -281,6 +281,8 @@ gpui::actions!(
         SelectToPreviousWordStart,
         SelectToStartOfParagraph,
         SelectUp,
+        SelectPageDown,
+        SelectPageUp,
         ShowCharacterPalette,
         ShowInlineCompletion,
         ShuffleLines,

crates/editor/src/editor.rs 🔗

@@ -6722,6 +6722,20 @@ impl Editor {
         })
     }
 
+    pub fn select_page_up(&mut self, _: &SelectPageUp, cx: &mut ViewContext<Self>) {
+        let Some(row_count) = self.visible_row_count() else {
+            return;
+        };
+
+        let text_layout_details = &self.text_layout_details(cx);
+
+        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
+            s.move_heads_with(|map, head, goal| {
+                movement::up_by_rows(map, head, row_count, goal, false, &text_layout_details)
+            })
+        })
+    }
+
     pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext<Self>) {
         if self.take_rename(true, cx).is_some() {
             return;
@@ -6732,9 +6746,7 @@ impl Editor {
             return;
         }
 
-        let row_count = if let Some(row_count) = self.visible_line_count() {
-            row_count as u32 - 1
-        } else {
+        let Some(row_count) = self.visible_row_count() else {
             return;
         };
 
@@ -6809,6 +6821,20 @@ impl Editor {
         }
     }
 
+    pub fn select_page_down(&mut self, _: &SelectPageDown, cx: &mut ViewContext<Self>) {
+        let Some(row_count) = self.visible_row_count() else {
+            return;
+        };
+
+        let text_layout_details = &self.text_layout_details(cx);
+
+        self.change_selections(Some(Autoscroll::fit()), cx, |s| {
+            s.move_heads_with(|map, head, goal| {
+                movement::down_by_rows(map, head, row_count, goal, false, &text_layout_details)
+            })
+        })
+    }
+
     pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext<Self>) {
         if self.take_rename(true, cx).is_some() {
             return;
@@ -6829,9 +6855,7 @@ impl Editor {
             return;
         }
 
-        let row_count = if let Some(row_count) = self.visible_line_count() {
-            row_count as u32 - 1
-        } else {
+        let Some(row_count) = self.visible_row_count() else {
             return;
         };
 

crates/editor/src/element.rs 🔗

@@ -167,6 +167,8 @@ impl EditorElement {
         register_action(view, cx, Editor::move_up);
         register_action(view, cx, Editor::move_up_by_lines);
         register_action(view, cx, Editor::select_up_by_lines);
+        register_action(view, cx, Editor::select_page_down);
+        register_action(view, cx, Editor::select_page_up);
         register_action(view, cx, Editor::cancel);
         register_action(view, cx, Editor::newline);
         register_action(view, cx, Editor::newline_above);

crates/editor/src/scroll.rs 🔗

@@ -330,6 +330,11 @@ impl Editor {
         self.scroll_manager.visible_line_count
     }
 
+    pub fn visible_row_count(&self) -> Option<u32> {
+        self.visible_line_count()
+            .map(|line_count| line_count as u32 - 1)
+    }
+
     pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext<Self>) {
         let opened_first_time = self.scroll_manager.visible_line_count.is_none();
         self.scroll_manager.visible_line_count = Some(lines);