diff --git a/crates/turborepo-ui/src/tui/app.rs b/crates/turborepo-ui/src/tui/app.rs index 816c75c771da9..613df17bf5c13 100644 --- a/crates/turborepo-ui/src/tui/app.rs +++ b/crates/turborepo-ui/src/tui/app.rs @@ -201,6 +201,15 @@ impl App { Ok(()) } + pub fn scroll_terminal_output_by_page(&mut self, direction: Direction) -> Result<(), Error> { + let pane_rows = self.size.pane_rows(); + let task = self.get_full_task_mut()?; + // Scroll by the height of the terminal pane + task.scroll_by(direction, usize::from(pane_rows))?; + + Ok(()) + } + pub fn enter_search(&mut self) -> Result<(), Error> { self.section_focus = LayoutSections::Search { previous_selection: self.active_task()?.to_string(), @@ -550,6 +559,18 @@ impl App { term.resize(pane_rows, pane_cols); }) } + + pub fn jump_to_logs_top(&mut self) -> Result<(), Error> { + let task = self.get_full_task_mut()?; + task.parser.screen_mut().set_scrollback(usize::MAX); + Ok(()) + } + + pub fn jump_to_logs_bottom(&mut self) -> Result<(), Error> { + let task = self.get_full_task_mut()?; + task.parser.screen_mut().set_scrollback(0); + Ok(()) + } } impl App { @@ -817,6 +838,22 @@ fn update( app.is_task_selection_pinned = true; app.scroll_terminal_output(Direction::Down)?; } + Event::PageUp => { + app.is_task_selection_pinned = true; + app.scroll_terminal_output_by_page(Direction::Up)?; + } + Event::PageDown => { + app.is_task_selection_pinned = true; + app.scroll_terminal_output_by_page(Direction::Down)?; + } + Event::JumpToLogsTop => { + app.is_task_selection_pinned = true; + app.jump_to_logs_top()?; + } + Event::JumpToLogsBottom => { + app.is_task_selection_pinned = true; + app.jump_to_logs_bottom()?; + } Event::EnterInteractive => { app.is_task_selection_pinned = true; app.interact()?; diff --git a/crates/turborepo-ui/src/tui/event.rs b/crates/turborepo-ui/src/tui/event.rs index 775a7ef4bcc17..c90065ed50d90 100644 --- a/crates/turborepo-ui/src/tui/event.rs +++ b/crates/turborepo-ui/src/tui/event.rs @@ -29,6 +29,10 @@ pub enum Event { Down, ScrollUp, ScrollDown, + PageUp, + PageDown, + JumpToLogsTop, + JumpToLogsBottom, SetStdin { task: String, stdin: Box, @@ -64,6 +68,7 @@ pub enum Event { SearchBackspace, } +#[derive(Copy, Clone)] pub enum Direction { Up, Down, diff --git a/crates/turborepo-ui/src/tui/input.rs b/crates/turborepo-ui/src/tui/input.rs index 5b6057c5e284f..d3a4c3e6d174a 100644 --- a/crates/turborepo-ui/src/tui/input.rs +++ b/crates/turborepo-ui/src/tui/input.rs @@ -112,6 +112,10 @@ fn translate_key_event(options: InputOptions, key_event: KeyEvent) -> Option Some(Event::ToggleSidebar), KeyCode::Char('u') => Some(Event::ScrollUp), KeyCode::Char('d') => Some(Event::ScrollDown), + KeyCode::PageUp | KeyCode::Char('U') => Some(Event::PageUp), + KeyCode::PageDown | KeyCode::Char('D') => Some(Event::PageDown), + KeyCode::Char('t') => Some(Event::JumpToLogsTop), + KeyCode::Char('b') => Some(Event::JumpToLogsBottom), KeyCode::Char('m') => Some(Event::ToggleHelpPopup), KeyCode::Char('p') => Some(Event::TogglePinnedTask), KeyCode::Up | KeyCode::Char('k') => Some(Event::Up), diff --git a/crates/turborepo-ui/src/tui/pane.rs b/crates/turborepo-ui/src/tui/pane.rs index 74b1b94949e0f..a9b1a39bbb0ba 100644 --- a/crates/turborepo-ui/src/tui/pane.rs +++ b/crates/turborepo-ui/src/tui/pane.rs @@ -11,6 +11,8 @@ const EXIT_INTERACTIVE_HINT: &str = "Ctrl-z - Stop interacting"; const ENTER_INTERACTIVE_HINT: &str = "i - Interact"; const HAS_SELECTION: &str = "c - Copy selection"; const SCROLL_LOGS: &str = "u/d - Scroll logs"; +const PAGE_LOGS: &str = "U/D - Page logs"; +const JUMP_IN_LOGS: &str = "t/b - Jump to top/buttom"; const TASK_LIST_HIDDEN: &str = "h - Show task list"; pub struct TerminalPane<'a, W> { @@ -65,9 +67,9 @@ impl<'a, W> TerminalPane<'a, W> { match self.section { LayoutSections::Pane => build_message_vec(&[EXIT_INTERACTIVE_HINT]), LayoutSections::TaskList if self.has_stdin() => { - build_message_vec(&[ENTER_INTERACTIVE_HINT, SCROLL_LOGS]) + build_message_vec(&[ENTER_INTERACTIVE_HINT, SCROLL_LOGS, PAGE_LOGS, JUMP_IN_LOGS]) } - LayoutSections::TaskList => build_message_vec(&[SCROLL_LOGS]), + LayoutSections::TaskList => build_message_vec(&[SCROLL_LOGS, PAGE_LOGS, JUMP_IN_LOGS]), LayoutSections::Search { results, .. } => { Line::from(format!("/ {}", results.query())).left_aligned() } @@ -104,7 +106,7 @@ mod test { let pane = TerminalPane::new(&term, "foo", &LayoutSections::TaskList, true); assert_eq!( String::from(pane.footer()), - " i - Interact u/d - Scroll logs" + " i - Interact u/d - Scroll logs U/D - Page logs t/b - Jump to top/buttom" ); } @@ -112,6 +114,9 @@ mod test { fn test_footer_non_interactive() { let term: TerminalOutput> = TerminalOutput::new(16, 16, None); let pane = TerminalPane::new(&term, "foo", &LayoutSections::TaskList, true); - assert_eq!(String::from(pane.footer()), " u/d - Scroll logs"); + assert_eq!( + String::from(pane.footer()), + " u/d - Scroll logs U/D - Page logs t/b - Jump to top/buttom" + ); } } diff --git a/crates/turborepo-ui/src/tui/popup.rs b/crates/turborepo-ui/src/tui/popup.rs index 6d11b51b14901..cec811bb2456b 100644 --- a/crates/turborepo-ui/src/tui/popup.rs +++ b/crates/turborepo-ui/src/tui/popup.rs @@ -6,20 +6,25 @@ use ratatui::{ widgets::{Block, List, ListItem, Padding}, }; -const BIND_LIST: [&str; 12] = [ - "m - Toggle this help popup", - "↑ or j - Select previous task", - "↓ or k - Select next task", - "h - Toggle task list", - "p - Toggle pinned task selection", - "/ - Filter tasks to search term", - "ESC - Clear filter", - "i - Interact with task", - "Ctrl+z - Stop interacting with task", - "c - Copy logs selection (Only when logs are selected)", - "u - Scroll logs up", - "d - Scroll logs down", -]; +const BIND_LIST: &[&str] = [ + "m - Toggle this help popup", + "↑ or j - Select previous task", + "↓ or k - Select next task", + "h - Toggle task list", + "p - Toggle pinned task selection", + "/ - Filter tasks to search term", + "ESC - Clear filter", + "i - Interact with task", + "Ctrl+z - Stop interacting with task", + "c - Copy logs selection (Only when logs are selected)", + "u - Scroll logs up", + "d - Scroll logs down", + "Shift+u - Page logs up", + "Shift+d - Page logs down", + "t - Jump to top of logs", + "b - Jump to bottom of logs", +] +.as_slice(); pub fn popup_area(area: Rect) -> Rect { let screen_width = area.width; diff --git a/crates/turborepo-ui/src/tui/term_output.rs b/crates/turborepo-ui/src/tui/term_output.rs index be5d1d6c00d3e..52ac9dbc0c71f 100644 --- a/crates/turborepo-ui/src/tui/term_output.rs +++ b/crates/turborepo-ui/src/tui/term_output.rs @@ -67,10 +67,14 @@ impl TerminalOutput { } pub fn scroll(&mut self, direction: Direction) -> Result<(), Error> { + self.scroll_by(direction, 1) + } + + pub fn scroll_by(&mut self, direction: Direction, magnitude: usize) -> Result<(), Error> { let scrollback = self.parser.screen().scrollback(); let new_scrollback = match direction { - Direction::Up => scrollback + 1, - Direction::Down => scrollback.saturating_sub(1), + Direction::Up => scrollback.saturating_add(magnitude), + Direction::Down => scrollback.saturating_sub(magnitude), }; self.parser.screen_mut().set_scrollback(new_scrollback); Ok(())