use crate::{Position, Wrap};
#[derive(Debug, Clone, Copy, Default)]
pub struct Viewport {
pub top_row: usize,
pub top_col: usize,
pub width: u16,
pub height: u16,
pub wrap: Wrap,
pub text_width: u16,
pub tab_width: u16,
}
impl Viewport {
pub const fn new() -> Self {
Self {
top_row: 0,
top_col: 0,
width: 0,
height: 0,
wrap: Wrap::None,
text_width: 0,
tab_width: 0,
}
}
pub fn effective_tab_width(self) -> usize {
if self.tab_width == 0 {
4
} else {
self.tab_width as usize
}
}
pub fn bottom_row(self) -> usize {
self.top_row
.saturating_add((self.height as usize).max(1).saturating_sub(1))
}
pub fn contains(self, pos: Position) -> bool {
let in_rows = pos.row >= self.top_row && pos.row <= self.bottom_row();
let in_cols = pos.col >= self.top_col
&& pos.col < self.top_col.saturating_add((self.width as usize).max(1));
in_rows && in_cols
}
pub fn ensure_visible(&mut self, pos: Position) {
if self.height == 0 || self.width == 0 {
return;
}
let rows = self.height as usize;
if pos.row < self.top_row {
self.top_row = pos.row;
} else if pos.row >= self.top_row + rows {
self.top_row = pos.row + 1 - rows;
}
let cols = self.width as usize;
if pos.col < self.top_col {
self.top_col = pos.col;
} else if pos.col >= self.top_col + cols {
self.top_col = pos.col + 1 - cols;
}
}
}
pub fn is_big_viewport_jump(prev_top: usize, cur_top: usize, viewport_height: usize) -> bool {
prev_top.abs_diff(cur_top) > viewport_height
}
pub fn over_provisioned_range(
viewport_top: usize,
viewport_height: usize,
line_count: usize,
) -> (usize, usize) {
let top = viewport_top.saturating_sub(viewport_height);
let max_height = line_count.saturating_sub(top);
let height = viewport_height.saturating_mul(3).min(max_height);
(top, height)
}
#[cfg(test)]
mod tests {
use super::*;
fn vp(top_row: usize, height: u16) -> Viewport {
Viewport {
top_row,
top_col: 0,
width: 80,
height,
wrap: Wrap::None,
text_width: 80,
tab_width: 0,
}
}
#[test]
fn contains_inside_window() {
let v = vp(10, 5);
assert!(v.contains(Position::new(10, 0)));
assert!(v.contains(Position::new(14, 79)));
}
#[test]
fn contains_outside_window() {
let v = vp(10, 5);
assert!(!v.contains(Position::new(9, 0)));
assert!(!v.contains(Position::new(15, 0)));
assert!(!v.contains(Position::new(12, 80)));
}
#[test]
fn ensure_visible_scrolls_down() {
let mut v = vp(0, 5);
v.ensure_visible(Position::new(10, 0));
assert_eq!(v.top_row, 6);
}
#[test]
fn ensure_visible_scrolls_up() {
let mut v = vp(20, 5);
v.ensure_visible(Position::new(15, 0));
assert_eq!(v.top_row, 15);
}
#[test]
fn ensure_visible_no_scroll_when_inside() {
let mut v = vp(10, 5);
v.ensure_visible(Position::new(12, 4));
assert_eq!(v.top_row, 10);
}
#[test]
fn ensure_visible_zero_dim_is_noop() {
let mut v = Viewport::default();
v.ensure_visible(Position::new(100, 100));
assert_eq!(v.top_row, 0);
assert_eq!(v.top_col, 0);
}
#[test]
fn over_provisioned_range_middle_of_buffer() {
let (top, height) = over_provisioned_range(100, 30, 1000);
assert_eq!(top, 70);
assert_eq!(height, 90);
}
#[test]
fn over_provisioned_range_top_of_buffer() {
let (top, height) = over_provisioned_range(5, 30, 1000);
assert_eq!(top, 0);
assert_eq!(height, 90);
}
#[test]
fn over_provisioned_range_bottom_of_buffer_clamps_height() {
let (top, height) = over_provisioned_range(30, 30, 50);
assert_eq!(top, 0);
assert_eq!(height, 50);
}
#[test]
fn over_provisioned_range_zero_viewport_height() {
let (top, height) = over_provisioned_range(10, 0, 100);
assert_eq!(top, 10);
assert_eq!(height, 0);
}
#[test]
fn over_provisioned_range_zero_line_count() {
let (top, height) = over_provisioned_range(0, 30, 0);
assert_eq!(top, 0);
assert_eq!(height, 0);
}
#[test]
fn is_big_viewport_jump_within_one_height_is_not_big() {
assert!(!is_big_viewport_jump(100, 100, 30));
assert!(!is_big_viewport_jump(100, 130, 30));
assert!(!is_big_viewport_jump(100, 70, 30));
}
#[test]
fn is_big_viewport_jump_past_one_height_is_big() {
assert!(is_big_viewport_jump(500, 0, 30));
assert!(is_big_viewport_jump(0, 9999, 30));
assert!(is_big_viewport_jump(0, 31, 30));
assert!(!is_big_viewport_jump(0, 30, 30));
}
#[test]
fn over_provisioned_range_covers_viewport() {
let viewport_top = 100;
let viewport_height = 30;
let line_count = 1000;
let (top, height) = over_provisioned_range(viewport_top, viewport_height, line_count);
assert!(top <= viewport_top, "top must not exceed viewport_top");
assert!(
top + height >= viewport_top + viewport_height,
"oversize range must end at or past the viewport's bottom"
);
assert!(
top + height <= line_count,
"oversize range must stay inside the buffer"
);
}
}