Repository: zellij-org/zellij (language: rust)

Issue:
Add ScrollToTop and assign default key bindings
Hi, thanks for your great work! I'm wondering why there is not a `ScrollToTop` action paired with `ScrollToBottom`. If that's just because no one has requested, I would like to request this feature. I usually opens a new panel to run a new command, and it's convenient if I can jump to the top with one key press to see the original command.

Besides, I would also like to propose adding two default key bindings: `Home` key in scroll mode for `ScrollToTop`, `End` key in scroll mode for `ScrollToBottom`.

Thanks!


Discussion / hints:
afaik，there is a scroll-to-bottom action for now, and the shortcut key is Ctrl+c
> afaik，there is a scroll-to-bottom action for now, and the shortcut key is Ctrl+c

Yes I'm aware of that, but it seems more intuitive to use the home and the end key to navigate.
There is a configuration item in config.kdl that can modify the shortcut, because I don't use this action very much, I haven't confirmed it
I also want this feature.

There is currently no way to scroll to the *top* of scrollback, only the bottom, because a `ScrollToTop` action does not exist, only a `ScrollToBottom` action.

Fix patch:
diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs
index 5b5e437ee0..2ca58cc448 100644
--- a/zellij-server/src/route.rs
+++ b/zellij-server/src/route.rs
@@ -207,6 +207,12 @@ pub(crate) fn route_action(
                 .send_to_screen(ScreenInstruction::ScrollToBottom(client_id))
                 .with_context(err_context)?;
         },
+        Action::ScrollToTop => {
+            session
+                .senders
+                .send_to_screen(ScreenInstruction::ScrollToTop(client_id))
+                .with_context(err_context)?;
+        },
         Action::PageScrollUp => {
             session
                 .senders
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs
index b2fdf4f80a..fdca620d91 100644
--- a/zellij-server/src/screen.rs
+++ b/zellij-server/src/screen.rs
@@ -161,6 +161,7 @@ pub enum ScreenInstruction {
     ScrollDown(ClientId),
     ScrollDownAt(Position, ClientId),
     ScrollToBottom(ClientId),
+    ScrollToTop(ClientId),
     PageScrollUp(ClientId),
     PageScrollDown(ClientId),
     HalfPageScrollUp(ClientId),
@@ -294,6 +295,7 @@ impl From<&ScreenInstruction> for ScreenContext {
             ScreenInstruction::ScrollUp(..) => ScreenContext::ScrollUp,
             ScreenInstruction::ScrollDown(..) => ScreenContext::ScrollDown,
             ScreenInstruction::ScrollToBottom(..) => ScreenContext::ScrollToBottom,
+            ScreenInstruction::ScrollToTop(..) => ScreenContext::ScrollToTop,
             ScreenInstruction::PageScrollUp(..) => ScreenContext::PageScrollUp,
             ScreenInstruction::PageScrollDown(..) => ScreenContext::PageScrollDown,
             ScreenInstruction::HalfPageScrollUp(..) => ScreenContext::HalfPageScrollUp,
@@ -1744,6 +1746,16 @@ pub(crate) fn screen_thread_main(
                 screen.render()?;
                 screen.unblock_input()?;
             },
+            ScreenInstruction::ScrollToTop(client_id) => {
+                active_tab_and_connected_client_id!(
+                    screen,
+                    client_id,
+                    |tab: &mut Tab, client_id: ClientId| tab
+                        .scroll_active_terminal_to_top(client_id), ?
+                );
+                screen.render()?;
+                screen.unblock_input()?;
+            },
             ScreenInstruction::PageScrollUp(client_id) => {
                 active_tab_and_connected_client_id!(
                     screen,
diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs
index 093237eae9..0dcd7ba58e 100644
--- a/zellij-server/src/tab/mod.rs
+++ b/zellij-server/src/tab/mod.rs
@@ -2046,6 +2046,16 @@ impl Tab {
         Ok(())
     }
 
+    pub fn scroll_active_terminal_to_top(&mut self, client_id: ClientId) -> Result<()> {
+        if let Some(active_pane) = self.get_active_pane_or_floating_pane_mut(client_id) {
+            active_pane.clear_scroll();
+            if let Some(size) = active_pane.get_line_number() {
+                active_pane.scroll_up(size, client_id);
+            }
+        }
+        Ok(())
+    }
+
     pub fn clear_active_terminal_scroll(&mut self, client_id: ClientId) -> Result<()> {
         // TODO: is this a thing?
         let err_context =
diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs
index 1d62e7d078..82c34eecd6 100644
--- a/zellij-utils/src/cli.rs
+++ b/zellij-utils/src/cli.rs
@@ -216,6 +216,8 @@ pub enum CliAction {
     ScrollDown,
     /// Scroll down to bottom in focus pane.
     ScrollToBottom,
+    /// Scroll up to top in focus pane.
+    ScrollToTop,
     /// Scroll up one page in focus pane.
     PageScrollUp,
     /// Scroll down one page in focus pane.
diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs
index 7fbed99ed4..96f5468aef 100644
--- a/zellij-utils/src/errors.rs
+++ b/zellij-utils/src/errors.rs
@@ -261,6 +261,7 @@ pub enum ScreenContext {
     ScrollDown,
     ScrollDownAt,
     ScrollToBottom,
+    ScrollToTop,
     PageScrollUp,
     PageScrollDown,
     HalfPageScrollUp,
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index 7bb13540ce..1207023aad 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -128,6 +128,8 @@ pub enum Action {
     ScrollDownAt(Position),
     /// Scroll down to bottom in focus pane.
     ScrollToBottom,
+    /// Scroll up to top in focus pane.
+    ScrollToTop,
     /// Scroll up one page in focus pane.
     PageScrollUp,
     /// Scroll down one page in focus pane.
@@ -226,6 +228,7 @@ impl Action {
             CliAction::ScrollUp => Ok(vec![Action::ScrollUp]),
             CliAction::ScrollDown => Ok(vec![Action::ScrollDown]),
             CliAction::ScrollToBottom => Ok(vec![Action::ScrollToBottom]),
+            CliAction::ScrollToTop => Ok(vec![Action::ScrollToTop]),
             CliAction::PageScrollUp => Ok(vec![Action::PageScrollUp]),
             CliAction::PageScrollDown => Ok(vec![Action::PageScrollDown]),
             CliAction::HalfPageScrollUp => Ok(vec![Action::HalfPageScrollUp]),
diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs
index db001fb5f8..2a81300ca8 100644
--- a/zellij-utils/src/kdl/mod.rs
+++ b/zellij-utils/src/kdl/mod.rs
@@ -43,6 +43,7 @@ macro_rules! parse_kdl_action_arguments {
                 "ScrollUp" => Ok(Action::ScrollUp),
                 "ScrollDown" => Ok(Action::ScrollDown),
                 "ScrollToBottom" => Ok(Action::ScrollToBottom),
+                "ScrollToTop" => Ok(Action::ScrollToTop),
                 "PageScrollUp" => Ok(Action::PageScrollUp),
                 "PageScrollDown" => Ok(Action::PageScrollDown),
                 "HalfPageScrollUp" => Ok(Action::HalfPageScrollUp),
@@ -636,6 +637,9 @@ impl TryFrom<&KdlNode> for Action {
             "ScrollToBottom" => {
                 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
             },
+            "ScrollToTop" => {
+                parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
+            },
             "PageScrollUp" => {
                 parse_kdl_action_arguments!(action_name, action_arguments, kdl_action)
             },


Test patch:
diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs
index dd212c3220..e9b399618a 100644
--- a/zellij-server/src/unit/screen_tests.rs
+++ b/zellij-server/src/unit/screen_tests.rs
@@ -1520,6 +1520,53 @@ pub fn send_cli_scroll_to_bottom_action() {
     assert_snapshot!(format!("{}", snapshot_count));
 }
 
+#[test]
+pub fn send_cli_scroll_to_top_action() {
+    let size = Size { cols: 80, rows: 10 };
+    let client_id = 10; // fake client id should not appear in the screen's state
+    let mut initial_layout = PaneLayout::default();
+    initial_layout.children_split_direction = SplitDirection::Vertical;
+    initial_layout.children = vec![PaneLayout::default(), PaneLayout::default()];
+    let mut mock_screen = MockScreen::new(size);
+    let session_metadata = mock_screen.clone_session_metadata();
+    let screen_thread = mock_screen.run(Some(initial_layout));
+    let received_server_instructions = Arc::new(Mutex::new(vec![]));
+    let server_receiver = mock_screen.server_receiver.take().unwrap();
+    let server_instruction = log_actions_in_thread!(
+        received_server_instructions,
+        ServerInstruction::KillSession,
+        server_receiver
+    );
+    let scroll_to_top_action = CliAction::ScrollToTop;
+    let mut pane_contents = String::new();
+    for i in 0..20 {
+        pane_contents.push_str(&format!("fill pane up with something {}\n\r", i));
+    }
+    let _ = mock_screen.to_screen.send(ScreenInstruction::PtyBytes(
+        0,
+        pane_contents.as_bytes().to_vec(),
+    ));
+    std::thread::sleep(std::time::Duration::from_millis(100));
+    // scroll to top
+    send_cli_action_to_server(
+        &session_metadata,
+        scroll_to_top_action.clone(),
+        &mut mock_screen,
+        client_id,
+    );
+    std::thread::sleep(std::time::Duration::from_millis(100));
+    mock_screen.teardown(vec![server_instruction, screen_thread]);
+    let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
+        received_server_instructions.lock().unwrap().iter(),
+        size,
+    );
+    let snapshot_count = snapshots.len();
+    for (_cursor_coordinates, snapshot) in snapshots {
+        assert_snapshot!(format!("{}", snapshot));
+    }
+    assert_snapshot!(format!("{}", snapshot_count));
+}
+
 #[test]
 pub fn send_cli_page_scroll_up_action() {
     let size = Size { cols: 80, rows: 10 };
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_scroll_to_top_action-2.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_scroll_to_top_action-2.snap
new file mode 100644
index 0000000000..e0a5a3fd9a
--- /dev/null
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_scroll_to_top_action-2.snap
@@ -0,0 +1,16 @@
+---
+source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 1565
+expression: "format!(\"{}\", snapshot)"
+---
+00 (C): ┌ Pane #1 ───────────── SCROLL:  13/13 ┐┌ Pane #2 ─────────────────────────────┐
+01 (C): │fill pane up with something 0         ││                                      │
+02 (C): │fill pane up with something 1         ││                                      │
+03 (C): │fill pane up with something 2         ││                                      │
+04 (C): │fill pane up with something 3         ││                                      │
+05 (C): │fill pane up with something 4         ││                                      │
+06 (C): │fill pane up with something 5         ││                                      │
+07 (C): │fill pane up with something 6         ││                                      │
+08 (C): │fill pane up with something 7         ││                                      │
+09 (C): └──────────────────────────────────────┘└──────────────────────────────────────┘
+
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_scroll_to_top_action-3.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_scroll_to_top_action-3.snap
new file mode 100644
index 0000000000..526a865ca4
--- /dev/null
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_scroll_to_top_action-3.snap
@@ -0,0 +1,6 @@
+---
+source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 1567
+expression: "format!(\"{}\", snapshot_count)"
+---
+2
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_scroll_to_top_action.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_scroll_to_top_action.snap
new file mode 100644
index 0000000000..0555f99f39
--- /dev/null
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_scroll_to_top_action.snap
@@ -0,0 +1,16 @@
+---
+source: zellij-server/src/./unit/screen_tests.rs
+assertion_line: 1565
+expression: "format!(\"{}\", snapshot)"
+---
+00 (C): ┌ Pane #1 ─────────────────────────────┐┌ Pane #2 ─────────────────────────────┐
+01 (C): │                                      ││                                      │
+02 (C): │                                      ││                                      │
+03 (C): │                                      ││                                      │
+04 (C): │                                      ││                                      │
+05 (C): │                                      ││                                      │
+06 (C): │                                      ││                                      │
+07 (C): │                                      ││                                      │
+08 (C): │                                      ││                                      │
+09 (C): └──────────────────────────────────────┘└──────────────────────────────────────┘
+