flywheel/widget/
scroll_buffer.rs1use std::collections::VecDeque;
7use crate::buffer::Cell;
8
9#[derive(Debug, Clone)]
11pub struct StyledLine {
12 pub content: Vec<Cell>,
14 pub wrapped: bool,
16}
17
18impl StyledLine {
19 pub const fn new(content: Vec<Cell>, wrapped: bool) -> Self {
21 Self { content, wrapped }
22 }
23
24 pub const fn empty() -> Self {
26 Self {
27 content: Vec::new(),
28 wrapped: false,
29 }
30 }
31}
32
33#[derive(Debug)]
38pub struct ScrollBuffer {
39 lines: VecDeque<StyledLine>,
41 max_lines: usize,
43 scroll_offset: usize,
45}
46
47impl ScrollBuffer {
48 pub fn new(max_lines: usize) -> Self {
50 let mut lines = VecDeque::with_capacity(max_lines);
51 lines.push_back(StyledLine::empty());
52
53 Self {
54 lines,
55 max_lines,
56 scroll_offset: 0,
57 }
58 }
59
60 pub fn len(&self) -> usize {
62 self.lines.len()
63 }
64
65 pub fn is_empty(&self) -> bool {
67 self.lines.is_empty()
68 }
69
70 pub fn current_line(&self) -> &StyledLine {
76 self.lines.back().expect("Buffer should never be empty")
77 }
78
79 pub fn current_line_mut(&mut self) -> &mut StyledLine {
85 self.lines.back_mut().expect("Buffer should never be empty")
86 }
87
88 pub fn append(&mut self, cells: impl IntoIterator<Item = Cell>) {
90 self.current_line_mut().content.extend(cells);
91 }
92
93 pub fn newline(&mut self, wrapped: bool) {
99 while self.lines.len() >= self.max_lines {
101 self.lines.pop_front();
102 }
103
104 self.lines.push_back(StyledLine::new(Vec::new(), wrapped));
105 }
106
107 pub fn get(&self, index: usize) -> Option<&StyledLine> {
109 self.lines.get(index)
110 }
111
112 pub fn visible_lines(&self, viewport_height: usize) -> impl Iterator<Item = &StyledLine> {
117 let total = self.lines.len();
118 let end = total.saturating_sub(self.scroll_offset);
119 let start = end.saturating_sub(viewport_height);
120
121 self.lines.range(start..end)
122 }
123
124 pub fn scroll_up(&mut self, lines: usize) {
126 let max_offset = self.lines.len().saturating_sub(1);
127 self.scroll_offset = (self.scroll_offset + lines).min(max_offset);
128 }
129
130 pub const fn scroll_down(&mut self, lines: usize) {
132 self.scroll_offset = self.scroll_offset.saturating_sub(lines);
133 }
134
135 pub const fn scroll_to_bottom(&mut self) {
137 self.scroll_offset = 0;
138 }
139
140 pub const fn at_bottom(&self) -> bool {
142 self.scroll_offset == 0
143 }
144
145 pub fn clear(&mut self) {
147 self.lines.clear();
148 self.lines.push_back(StyledLine::empty());
149 self.scroll_offset = 0;
150 }
151
152 pub fn current_line_len(&self) -> usize {
154 self.current_line().content.len()
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 fn text_to_cells(text: &str) -> Vec<Cell> {
163 text.chars().map(Cell::from_char).collect()
164 }
165
166 #[test]
167 fn test_scroll_buffer_new() {
168 let buf = ScrollBuffer::new(100);
169 assert_eq!(buf.len(), 1);
170 assert!(buf.current_line().content.is_empty());
171 }
172
173 #[test]
174 fn test_scroll_buffer_append() {
175 let mut buf = ScrollBuffer::new(100);
176 buf.append(text_to_cells("Hello"));
177 buf.append(text_to_cells(", world!"));
178
179 let content: String = buf.current_line().content.iter()
180 .map(|c| c.grapheme().unwrap_or(""))
181 .collect();
182 assert_eq!(content, "Hello, world!");
183 }
184
185 #[test]
186 fn test_scroll_buffer_newline() {
187 let mut buf = ScrollBuffer::new(100);
188 buf.append(text_to_cells("Line 1"));
189 buf.newline(false);
190 buf.append(text_to_cells("Line 2"));
191 assert_eq!(buf.len(), 2);
192
193 let l1: String = buf.get(0).unwrap().content.iter().map(|c| c.grapheme().unwrap_or("")).collect();
194 assert_eq!(l1, "Line 1");
195 }
196
197 #[test]
198 fn test_scroll_buffer_capacity() {
199 let mut buf = ScrollBuffer::new(3);
200 buf.append(text_to_cells("Line 1"));
201 buf.newline(false);
202 buf.append(text_to_cells("Line 2"));
203 buf.newline(false);
204 buf.append(text_to_cells("Line 3"));
205 buf.newline(false);
206 buf.append(text_to_cells("Line 4"));
207
208 assert_eq!(buf.len(), 3);
209 let l0: String = buf.get(0).unwrap().content.iter().map(|c| c.grapheme().unwrap_or("")).collect();
211 assert_eq!(l0, "Line 2");
212 }
213
214 #[test]
215 fn test_scroll_buffer_scroll() {
216 let mut buf = ScrollBuffer::new(100);
217 for i in 0..10 {
218 buf.append(text_to_cells(&format!("Line {i}")));
219 buf.newline(false);
220 }
221
222 assert!(buf.at_bottom());
223
224 buf.scroll_up(3);
225 assert!(!buf.at_bottom());
226 assert_eq!(buf.scroll_offset, 3);
227
228 buf.scroll_down(1);
229 assert_eq!(buf.scroll_offset, 2);
230
231 buf.scroll_to_bottom();
232 assert!(buf.at_bottom());
233 }
234}