1use crate::{Position, Viewport};
2
3pub struct Buffer {
26 lines: Vec<String>,
30 cursor: Position,
33 dirty_gen: u64,
36 pub(crate) folds: Vec<crate::folds::Fold>,
39}
40
41impl Default for Buffer {
42 fn default() -> Self {
43 Self::new()
44 }
45}
46
47impl Buffer {
48 pub fn new() -> Self {
51 Self {
52 lines: vec![String::new()],
53 cursor: Position::default(),
54 dirty_gen: 0,
55 folds: Vec::new(),
56 }
57 }
58
59 #[allow(clippy::should_implement_trait)]
64 pub fn from_str(text: &str) -> Self {
65 let mut lines: Vec<String> = text.split('\n').map(str::to_owned).collect();
66 if lines.is_empty() {
67 lines.push(String::new());
68 }
69 Self {
70 lines,
71 cursor: Position::default(),
72 dirty_gen: 0,
73 folds: Vec::new(),
74 }
75 }
76
77 pub fn lines(&self) -> &[String] {
78 &self.lines
79 }
80
81 pub fn line(&self, row: usize) -> Option<&str> {
82 self.lines.get(row).map(String::as_str)
83 }
84
85 pub fn cursor(&self) -> Position {
86 self.cursor
87 }
88
89 pub fn dirty_gen(&self) -> u64 {
90 self.dirty_gen
91 }
92
93 pub fn set_cursor(&mut self, pos: Position) {
98 let last_row = self.lines.len().saturating_sub(1);
99 let row = pos.row.min(last_row);
100 let line_chars = self.lines[row].chars().count();
101 let col = pos.col.min(line_chars);
102 self.cursor = Position::new(row, col);
103 }
104
105 pub fn ensure_cursor_visible(&mut self, viewport: &mut Viewport) {
115 let cursor = self.cursor;
116 let v = *viewport;
117 let wrap_active = !matches!(v.wrap, crate::Wrap::None) && v.text_width > 0;
118 if !wrap_active {
119 viewport.ensure_visible(cursor);
120 return;
121 }
122 if v.height == 0 {
123 return;
124 }
125 if cursor.row < v.top_row {
127 viewport.top_row = cursor.row;
128 viewport.top_col = 0;
129 return;
130 }
131 let height = v.height as usize;
132 loop {
135 let csr = self.cursor_screen_row_from(viewport, viewport.top_row);
136 match csr {
137 Some(row) if row < height => break,
138 _ => {}
139 }
140 let mut next = viewport.top_row + 1;
143 while next <= cursor.row && self.folds.iter().any(|f| f.hides(next)) {
144 next += 1;
145 }
146 if next > cursor.row {
147 viewport.top_row = cursor.row;
150 break;
151 }
152 viewport.top_row = next;
153 }
154 viewport.top_col = 0;
155 }
156
157 pub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize> {
162 if matches!(viewport.wrap, crate::Wrap::None) || viewport.text_width == 0 {
163 return None;
164 }
165 self.cursor_screen_row_from(viewport, viewport.top_row)
166 }
167
168 pub fn screen_rows_between(&self, viewport: &Viewport, start: usize, end: usize) -> usize {
173 if start > end {
174 return 0;
175 }
176 let last = self.lines.len().saturating_sub(1);
177 let end = end.min(last);
178 let v = *viewport;
179 let mut total = 0usize;
180 for r in start..=end {
181 if self.folds.iter().any(|f| f.hides(r)) {
182 continue;
183 }
184 if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
185 total += 1;
186 } else {
187 let line = self.lines.get(r).map(String::as_str).unwrap_or("");
188 total += crate::wrap::wrap_segments(line, v.text_width, v.wrap).len();
189 }
190 }
191 total
192 }
193
194 pub fn max_top_for_height(&self, viewport: &Viewport, height: usize) -> usize {
200 if height == 0 {
201 return 0;
202 }
203 let last = self.lines.len().saturating_sub(1);
204 let mut total = 0usize;
205 let mut row = last;
206 loop {
207 if !self.folds.iter().any(|f| f.hides(row)) {
208 let v = *viewport;
209 total += if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
210 1
211 } else {
212 let line = self.lines.get(row).map(String::as_str).unwrap_or("");
213 crate::wrap::wrap_segments(line, v.text_width, v.wrap).len()
214 };
215 }
216 if total >= height {
217 return row;
218 }
219 if row == 0 {
220 return 0;
221 }
222 row -= 1;
223 }
224 }
225
226 fn cursor_screen_row_from(&self, viewport: &Viewport, top: usize) -> Option<usize> {
230 let cursor = self.cursor;
231 if cursor.row < top {
232 return None;
233 }
234 let v = *viewport;
235 let mut screen = 0usize;
236 for r in top..=cursor.row {
237 if self.folds.iter().any(|f| f.hides(r)) {
238 continue;
239 }
240 let line = self.lines.get(r).map(String::as_str).unwrap_or("");
241 let segs = crate::wrap::wrap_segments(line, v.text_width, v.wrap);
242 if r == cursor.row {
243 let seg_idx = crate::wrap::segment_for_col(&segs, cursor.col);
244 return Some(screen + seg_idx);
245 }
246 screen += segs.len();
247 }
248 None
249 }
250
251 pub fn clamp_position(&self, pos: Position) -> Position {
255 let last_row = self.lines.len().saturating_sub(1);
256 let row = pos.row.min(last_row);
257 let line_chars = self.lines[row].chars().count();
258 let col = pos.col.min(line_chars);
259 Position::new(row, col)
260 }
261
262 pub(crate) fn lines_mut(&mut self) -> &mut Vec<String> {
265 &mut self.lines
266 }
267
268 pub(crate) fn dirty_gen_bump(&mut self) {
271 self.dirty_gen = self.dirty_gen.wrapping_add(1);
272 }
273
274 pub fn replace_all(&mut self, text: &str) {
279 let mut lines: Vec<String> = text.split('\n').map(str::to_owned).collect();
280 if lines.is_empty() {
281 lines.push(String::new());
282 }
283 self.lines = lines;
284 let cursor = self.clamp_position(self.cursor);
286 self.cursor = cursor;
287 self.dirty_gen_bump();
288 }
289
290 pub fn as_string(&self) -> String {
294 self.lines.join("\n")
295 }
296
297 pub fn row_count(&self) -> usize {
299 self.lines.len()
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn new_has_one_empty_row() {
309 let b = Buffer::new();
310 assert_eq!(b.row_count(), 1);
311 assert_eq!(b.line(0), Some(""));
312 assert_eq!(b.cursor(), Position::default());
313 }
314
315 #[test]
316 fn from_str_splits_on_newline() {
317 let b = Buffer::from_str("foo\nbar\nbaz");
318 assert_eq!(b.row_count(), 3);
319 assert_eq!(b.line(0), Some("foo"));
320 assert_eq!(b.line(2), Some("baz"));
321 }
322
323 #[test]
324 fn from_str_trailing_newline_keeps_empty_row() {
325 let b = Buffer::from_str("foo\n");
326 assert_eq!(b.row_count(), 2);
327 assert_eq!(b.line(1), Some(""));
328 }
329
330 #[test]
331 fn from_str_empty_input_keeps_one_row() {
332 let b = Buffer::from_str("");
333 assert_eq!(b.row_count(), 1);
334 assert_eq!(b.line(0), Some(""));
335 }
336
337 #[test]
338 fn as_string_round_trips() {
339 let b = Buffer::from_str("a\nb\nc");
340 assert_eq!(b.as_string(), "a\nb\nc");
341 }
342
343 #[test]
344 fn dirty_gen_starts_at_zero() {
345 assert_eq!(Buffer::new().dirty_gen(), 0);
346 }
347
348 fn vp_wrap(width: u16, height: u16) -> Viewport {
349 Viewport {
350 top_row: 0,
351 top_col: 0,
352 width,
353 height,
354 wrap: crate::Wrap::Char,
355 text_width: width,
356 }
357 }
358
359 #[test]
360 fn ensure_cursor_visible_wrap_scrolls_when_cursor_below_screen() {
361 let mut b = Buffer::from_str("aaaaaaaaaa\nb\nc");
362 let mut v = vp_wrap(4, 3);
363 b.set_cursor(Position::new(2, 0));
367 b.ensure_cursor_visible(&mut v);
368 assert_eq!(v.top_row, 1);
369 }
370
371 #[test]
372 fn ensure_cursor_visible_wrap_no_scroll_when_visible() {
373 let mut b = Buffer::from_str("aaaaaaaaaa\nb");
374 let mut v = vp_wrap(4, 4);
375 b.set_cursor(Position::new(0, 5));
378 b.ensure_cursor_visible(&mut v);
379 assert_eq!(v.top_row, 0);
380 }
381
382 #[test]
383 fn ensure_cursor_visible_wrap_snaps_top_when_cursor_above() {
384 let mut b = Buffer::from_str("a\nb\nc\nd\ne");
385 let mut v = vp_wrap(4, 2);
386 v.top_row = 3;
387 b.set_cursor(Position::new(1, 0));
388 b.ensure_cursor_visible(&mut v);
389 assert_eq!(v.top_row, 1);
390 }
391
392 #[test]
393 fn screen_rows_between_sums_segments_under_wrap() {
394 let b = Buffer::from_str("aaaaaaaaa\nb\n");
396 let v = vp_wrap(4, 0);
397 assert_eq!(b.screen_rows_between(&v, 0, 0), 3);
399 assert_eq!(b.screen_rows_between(&v, 0, 1), 4);
400 assert_eq!(b.screen_rows_between(&v, 0, 2), 5);
401 assert_eq!(b.screen_rows_between(&v, 1, 2), 2);
402 }
403
404 #[test]
405 fn screen_rows_between_one_per_doc_row_when_wrap_off() {
406 let b = Buffer::from_str("aaaaa\nb\nc");
407 let v = Viewport::default();
408 assert_eq!(b.screen_rows_between(&v, 0, 2), 3);
409 }
410
411 #[test]
412 fn max_top_for_height_walks_back_until_height_reached() {
413 let b = Buffer::from_str("a\nb\nc\nd\neeeeeeee");
415 let v = vp_wrap(4, 0);
416 assert_eq!(b.max_top_for_height(&v, 4), 2);
419 assert_eq!(b.max_top_for_height(&v, 99), 0);
421 }
422
423 #[test]
424 fn cursor_screen_row_returns_none_when_wrap_off() {
425 let b = Buffer::from_str("a");
426 let v = Viewport::default();
427 assert!(b.cursor_screen_row(&v).is_none());
428 }
429
430 #[test]
431 fn cursor_screen_row_under_wrap() {
432 let mut b = Buffer::from_str("aaaaaaaaaa\nb");
433 let v = vp_wrap(4, 0);
434 b.set_cursor(Position::new(0, 5));
435 assert_eq!(b.cursor_screen_row(&v), Some(1));
437 b.set_cursor(Position::new(1, 0));
438 assert_eq!(b.cursor_screen_row(&v), Some(3));
440 }
441
442 #[test]
443 fn ensure_cursor_visible_falls_back_when_wrap_disabled() {
444 let mut b = Buffer::from_str("a\nb\nc\nd\ne");
445 let mut v = Viewport {
446 top_row: 0,
447 top_col: 0,
448 width: 4,
449 height: 2,
450 wrap: crate::Wrap::None,
451 text_width: 4,
452 };
453 b.set_cursor(Position::new(4, 0));
454 b.ensure_cursor_visible(&mut v);
455 assert_eq!(v.top_row, 3);
458 }
459}