1use crate::{Position, Wrap};
2
3#[derive(Debug, Clone, Copy, Default)]
16pub struct Viewport {
17 pub top_row: usize,
18 pub top_col: usize,
19 pub width: u16,
20 pub height: u16,
21 pub wrap: Wrap,
24 pub text_width: u16,
29 pub tab_width: u16,
34}
35
36impl Viewport {
37 pub const fn new() -> Self {
38 Self {
39 top_row: 0,
40 top_col: 0,
41 width: 0,
42 height: 0,
43 wrap: Wrap::None,
44 text_width: 0,
45 tab_width: 0,
46 }
47 }
48
49 pub fn effective_tab_width(self) -> usize {
52 if self.tab_width == 0 {
53 4
54 } else {
55 self.tab_width as usize
56 }
57 }
58
59 pub fn bottom_row(self) -> usize {
63 self.top_row
64 .saturating_add((self.height as usize).max(1).saturating_sub(1))
65 }
66
67 pub fn contains(self, pos: Position) -> bool {
69 let in_rows = pos.row >= self.top_row && pos.row <= self.bottom_row();
70 let in_cols = pos.col >= self.top_col
71 && pos.col < self.top_col.saturating_add((self.width as usize).max(1));
72 in_rows && in_cols
73 }
74
75 pub fn ensure_visible(&mut self, pos: Position) {
79 if self.height == 0 || self.width == 0 {
80 return;
81 }
82 let rows = self.height as usize;
83 if pos.row < self.top_row {
84 self.top_row = pos.row;
85 } else if pos.row >= self.top_row + rows {
86 self.top_row = pos.row + 1 - rows;
87 }
88 let cols = self.width as usize;
89 if pos.col < self.top_col {
90 self.top_col = pos.col;
91 } else if pos.col >= self.top_col + cols {
92 self.top_col = pos.col + 1 - cols;
93 }
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 fn vp(top_row: usize, height: u16) -> Viewport {
102 Viewport {
103 top_row,
104 top_col: 0,
105 width: 80,
106 height,
107 wrap: Wrap::None,
108 text_width: 80,
109 tab_width: 0,
110 }
111 }
112
113 #[test]
114 fn contains_inside_window() {
115 let v = vp(10, 5);
116 assert!(v.contains(Position::new(10, 0)));
117 assert!(v.contains(Position::new(14, 79)));
118 }
119
120 #[test]
121 fn contains_outside_window() {
122 let v = vp(10, 5);
123 assert!(!v.contains(Position::new(9, 0)));
124 assert!(!v.contains(Position::new(15, 0)));
125 assert!(!v.contains(Position::new(12, 80)));
126 }
127
128 #[test]
129 fn ensure_visible_scrolls_down() {
130 let mut v = vp(0, 5);
131 v.ensure_visible(Position::new(10, 0));
132 assert_eq!(v.top_row, 6);
133 }
134
135 #[test]
136 fn ensure_visible_scrolls_up() {
137 let mut v = vp(20, 5);
138 v.ensure_visible(Position::new(15, 0));
139 assert_eq!(v.top_row, 15);
140 }
141
142 #[test]
143 fn ensure_visible_no_scroll_when_inside() {
144 let mut v = vp(10, 5);
145 v.ensure_visible(Position::new(12, 4));
146 assert_eq!(v.top_row, 10);
147 }
148
149 #[test]
150 fn ensure_visible_zero_dim_is_noop() {
151 let mut v = Viewport::default();
152 v.ensure_visible(Position::new(100, 100));
153 assert_eq!(v.top_row, 0);
154 assert_eq!(v.top_col, 0);
155 }
156}