pub const VIEW_ROWS: usize = 21; pub struct ScrollbackView { start: usize, maximum_start: usize, total: usize, } impl ScrollbackView { pub const fn new(total: usize) -> Self { let maximum_start = total.saturating_sub(VIEW_ROWS); Self { start: maximum_start, maximum_start, total, } } pub const fn start(&self) -> usize { self.start } pub const fn total(&self) -> usize { self.total } pub fn end(&self) -> usize { (self.start + VIEW_ROWS).min(self.total) } pub fn older_line(&mut self) -> bool { if self.start == 0 { return false; } self.start -= 1; true } pub fn newer_line(&mut self) -> bool { if self.start == self.maximum_start { return false; } self.start += 1; true } pub fn older_page(&mut self) -> bool { let next = self.start.saturating_sub(VIEW_ROWS); if next == self.start { return false; } self.start = next; true } pub fn newer_page(&mut self) -> bool { let next = (self.start + VIEW_ROWS).min(self.maximum_start); if next == self.start { return false; } self.start = next; true } } #[cfg(test)] mod tests { use super::{ScrollbackView, VIEW_ROWS}; #[test] fn starts_at_latest_page() { let view = ScrollbackView::new(40); assert_eq!(view.start(), 40 - VIEW_ROWS); assert_eq!(view.end(), 40); } #[test] fn clamps_line_navigation() { let mut view = ScrollbackView::new(VIEW_ROWS + 2); assert!(view.older_line()); assert!(view.older_line()); assert!(!view.older_line()); assert!(view.newer_line()); assert!(view.newer_line()); assert!(!view.newer_line()); } #[test] fn pages_include_partial_oldest_page() { let mut view = ScrollbackView::new(VIEW_ROWS * 2 + 10); assert_eq!(view.start(), VIEW_ROWS + 10); assert!(view.older_page()); assert_eq!(view.start(), 10); assert!(view.older_page()); assert_eq!(view.start(), 0); assert!(view.newer_page()); assert_eq!(view.start(), VIEW_ROWS); assert!(view.newer_page()); assert_eq!(view.start(), VIEW_ROWS + 10); } #[test] fn short_transcript_does_not_scroll() { let mut view = ScrollbackView::new(3); assert_eq!(view.start(), 0); assert_eq!(view.end(), 3); assert!(!view.older_line()); assert!(!view.newer_page()); } }