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 tab_width: 0,
357 }
358 }
359
360 #[test]
361 fn ensure_cursor_visible_wrap_scrolls_when_cursor_below_screen() {
362 let mut b = Buffer::from_str("aaaaaaaaaa\nb\nc");
363 let mut v = vp_wrap(4, 3);
364 b.set_cursor(Position::new(2, 0));
368 b.ensure_cursor_visible(&mut v);
369 assert_eq!(v.top_row, 1);
370 }
371
372 #[test]
373 fn ensure_cursor_visible_wrap_no_scroll_when_visible() {
374 let mut b = Buffer::from_str("aaaaaaaaaa\nb");
375 let mut v = vp_wrap(4, 4);
376 b.set_cursor(Position::new(0, 5));
379 b.ensure_cursor_visible(&mut v);
380 assert_eq!(v.top_row, 0);
381 }
382
383 #[test]
384 fn ensure_cursor_visible_wrap_snaps_top_when_cursor_above() {
385 let mut b = Buffer::from_str("a\nb\nc\nd\ne");
386 let mut v = vp_wrap(4, 2);
387 v.top_row = 3;
388 b.set_cursor(Position::new(1, 0));
389 b.ensure_cursor_visible(&mut v);
390 assert_eq!(v.top_row, 1);
391 }
392
393 #[test]
394 fn screen_rows_between_sums_segments_under_wrap() {
395 let b = Buffer::from_str("aaaaaaaaa\nb\n");
397 let v = vp_wrap(4, 0);
398 assert_eq!(b.screen_rows_between(&v, 0, 0), 3);
400 assert_eq!(b.screen_rows_between(&v, 0, 1), 4);
401 assert_eq!(b.screen_rows_between(&v, 0, 2), 5);
402 assert_eq!(b.screen_rows_between(&v, 1, 2), 2);
403 }
404
405 #[test]
406 fn screen_rows_between_one_per_doc_row_when_wrap_off() {
407 let b = Buffer::from_str("aaaaa\nb\nc");
408 let v = Viewport::default();
409 assert_eq!(b.screen_rows_between(&v, 0, 2), 3);
410 }
411
412 #[test]
413 fn max_top_for_height_walks_back_until_height_reached() {
414 let b = Buffer::from_str("a\nb\nc\nd\neeeeeeee");
416 let v = vp_wrap(4, 0);
417 assert_eq!(b.max_top_for_height(&v, 4), 2);
420 assert_eq!(b.max_top_for_height(&v, 99), 0);
422 }
423
424 #[test]
425 fn cursor_screen_row_returns_none_when_wrap_off() {
426 let b = Buffer::from_str("a");
427 let v = Viewport::default();
428 assert!(b.cursor_screen_row(&v).is_none());
429 }
430
431 #[test]
432 fn cursor_screen_row_under_wrap() {
433 let mut b = Buffer::from_str("aaaaaaaaaa\nb");
434 let v = vp_wrap(4, 0);
435 b.set_cursor(Position::new(0, 5));
436 assert_eq!(b.cursor_screen_row(&v), Some(1));
438 b.set_cursor(Position::new(1, 0));
439 assert_eq!(b.cursor_screen_row(&v), Some(3));
441 }
442
443 #[test]
444 fn ensure_cursor_visible_falls_back_when_wrap_disabled() {
445 let mut b = Buffer::from_str("a\nb\nc\nd\ne");
446 let mut v = Viewport {
447 top_row: 0,
448 top_col: 0,
449 width: 4,
450 height: 2,
451 wrap: crate::Wrap::None,
452 text_width: 4,
453 tab_width: 0,
454 };
455 b.set_cursor(Position::new(4, 0));
456 b.ensure_cursor_visible(&mut v);
457 assert_eq!(v.top_row, 3);
460 }
461}