1use std::sync::{Arc, Mutex, MutexGuard};
2
3use crate::content::Content;
4use crate::{Position, Viewport};
5
6pub struct Buffer {
43 pub(crate) content: Arc<Mutex<Content>>,
45 cursor: Position,
48}
49
50impl Default for Buffer {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56impl Buffer {
57 pub fn new() -> Self {
61 Self {
62 content: Arc::new(Mutex::new(Content::new())),
63 cursor: Position::default(),
64 }
65 }
66
67 #[allow(clippy::should_implement_trait)]
72 pub fn from_str(text: &str) -> Self {
73 Self {
74 content: Arc::new(Mutex::new(Content::from_str(text))),
75 cursor: Position::default(),
76 }
77 }
78
79 pub fn new_view(content: Arc<Mutex<Content>>) -> Self {
99 Self {
100 content,
101 cursor: Position::default(),
102 }
103 }
104
105 pub fn content_arc(&self) -> Arc<Mutex<Content>> {
108 Arc::clone(&self.content)
109 }
110
111 pub fn lines(&self) -> Vec<String> {
120 self.content_lock().lines.clone()
121 }
122
123 pub fn line(&self, row: usize) -> Option<String> {
129 self.content_lock().lines.get(row).cloned()
130 }
131
132 pub fn cursor(&self) -> Position {
133 self.cursor
134 }
135
136 pub fn dirty_gen(&self) -> u64 {
137 self.content.lock().unwrap().dirty_gen
138 }
139
140 pub fn row_count(&self) -> usize {
142 self.content.lock().unwrap().lines.len()
143 }
144
145 pub fn as_string(&self) -> String {
147 self.content.lock().unwrap().lines.join("\n")
148 }
149
150 pub fn set_cursor(&mut self, pos: Position) {
157 let c = self.content.lock().unwrap();
158 let last_row = c.lines.len().saturating_sub(1);
159 let row = pos.row.min(last_row);
160 let line_chars = c.lines[row].chars().count();
161 let col = pos.col.min(line_chars);
162 drop(c);
163 self.cursor = Position::new(row, col);
164 }
165
166 pub fn ensure_cursor_visible(&mut self, viewport: &mut Viewport) {
169 let cursor = self.cursor;
170 let v = *viewport;
171 let wrap_active = !matches!(v.wrap, crate::Wrap::None) && v.text_width > 0;
172 if !wrap_active {
173 viewport.ensure_visible(cursor);
174 return;
175 }
176 if v.height == 0 {
177 return;
178 }
179 if cursor.row < v.top_row {
181 viewport.top_row = cursor.row;
182 viewport.top_col = 0;
183 return;
184 }
185 let height = v.height as usize;
186 loop {
188 let csr = self.cursor_screen_row_from(viewport, viewport.top_row);
189 match csr {
190 Some(row) if row < height => break,
191 _ => {}
192 }
193 let next = {
194 let c = self.content.lock().unwrap();
195 let mut n = viewport.top_row + 1;
196 while n <= cursor.row && c.folds.iter().any(|f| f.hides(n)) {
197 n += 1;
198 }
199 n
200 };
201 if next > cursor.row {
202 viewport.top_row = cursor.row;
203 break;
204 }
205 viewport.top_row = next;
206 }
207 viewport.top_col = 0;
208 }
209
210 pub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize> {
212 if matches!(viewport.wrap, crate::Wrap::None) || viewport.text_width == 0 {
213 return None;
214 }
215 self.cursor_screen_row_from(viewport, viewport.top_row)
216 }
217
218 pub fn screen_rows_between(&self, viewport: &Viewport, start: usize, end: usize) -> usize {
220 if start > end {
221 return 0;
222 }
223 let c = self.content.lock().unwrap();
224 let last = c.lines.len().saturating_sub(1);
225 let end = end.min(last);
226 let v = *viewport;
227 let mut total = 0usize;
228 for r in start..=end {
229 if c.folds.iter().any(|f| f.hides(r)) {
230 continue;
231 }
232 if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
233 total += 1;
234 } else {
235 let line = c.lines.get(r).map(String::as_str).unwrap_or("");
236 total += crate::wrap::wrap_segments(line, v.text_width, v.wrap).len();
237 }
238 }
239 total
240 }
241
242 pub fn max_top_for_height(&self, viewport: &Viewport, height: usize) -> usize {
245 if height == 0 {
246 return 0;
247 }
248 let c = self.content.lock().unwrap();
249 let last = c.lines.len().saturating_sub(1);
250 let mut total = 0usize;
251 let mut row = last;
252 loop {
253 if !c.folds.iter().any(|f| f.hides(row)) {
254 let v = *viewport;
255 total += if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
256 1
257 } else {
258 let line = c.lines.get(row).map(String::as_str).unwrap_or("");
259 crate::wrap::wrap_segments(line, v.text_width, v.wrap).len()
260 };
261 }
262 if total >= height {
263 return row;
264 }
265 if row == 0 {
266 return 0;
267 }
268 row -= 1;
269 }
270 }
271
272 pub fn clamp_position(&self, pos: Position) -> Position {
274 let c = self.content.lock().unwrap();
275 let last_row = c.lines.len().saturating_sub(1);
276 let row = pos.row.min(last_row);
277 let line_chars = c.lines[row].chars().count();
278 let col = pos.col.min(line_chars);
279 Position::new(row, col)
280 }
281
282 pub fn replace_all(&mut self, text: &str) {
285 let new_cursor = {
286 let mut c = self.content.lock().unwrap();
287 let mut lines: Vec<String> = text.split('\n').map(str::to_owned).collect();
288 if lines.is_empty() {
289 lines.push(String::new());
290 }
291 c.lines = lines;
292 let last_row = c.lines.len().saturating_sub(1);
293 let row = self.cursor.row.min(last_row);
294 let line_chars = c.lines[row].chars().count();
295 let col = self.cursor.col.min(line_chars);
296 c.dirty_gen = c.dirty_gen.wrapping_add(1);
297 Position::new(row, col)
298 };
299 self.cursor = new_cursor;
300 }
301
302 pub(crate) fn dirty_gen_bump(&mut self) {
306 let mut c = self.content.lock().unwrap();
307 c.dirty_gen = c.dirty_gen.wrapping_add(1);
308 }
309
310 pub(crate) fn content_lock(&self) -> MutexGuard<'_, Content> {
312 self.content.lock().unwrap()
313 }
314
315 pub(crate) fn content_lock_mut(&mut self) -> MutexGuard<'_, Content> {
317 self.content.lock().unwrap()
318 }
319
320 fn cursor_screen_row_from(&self, viewport: &Viewport, top: usize) -> Option<usize> {
323 let cursor = self.cursor;
324 if cursor.row < top {
325 return None;
326 }
327 let c = self.content.lock().unwrap();
328 let v = *viewport;
329 let mut screen = 0usize;
330 for r in top..=cursor.row {
331 if c.folds.iter().any(|f| f.hides(r)) {
332 continue;
333 }
334 let line = c.lines.get(r).map(String::as_str).unwrap_or("");
335 let segs = crate::wrap::wrap_segments(line, v.text_width, v.wrap);
336 if r == cursor.row {
337 let seg_idx = crate::wrap::segment_for_col(&segs, cursor.col);
338 return Some(screen + seg_idx);
339 }
340 screen += segs.len();
341 }
342 None
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 fn new_has_one_empty_row() {
352 let b = Buffer::new();
353 assert_eq!(b.row_count(), 1);
354 assert_eq!(b.line(0).as_deref(), Some(""));
355 assert_eq!(b.cursor(), Position::default());
356 }
357
358 #[test]
359 fn from_str_splits_on_newline() {
360 let b = Buffer::from_str("foo\nbar\nbaz");
361 assert_eq!(b.row_count(), 3);
362 assert_eq!(b.line(0).as_deref(), Some("foo"));
363 assert_eq!(b.line(2).as_deref(), Some("baz"));
364 }
365
366 #[test]
367 fn from_str_trailing_newline_keeps_empty_row() {
368 let b = Buffer::from_str("foo\n");
369 assert_eq!(b.row_count(), 2);
370 assert_eq!(b.line(1).as_deref(), Some(""));
371 }
372
373 #[test]
374 fn from_str_empty_input_keeps_one_row() {
375 let b = Buffer::from_str("");
376 assert_eq!(b.row_count(), 1);
377 assert_eq!(b.line(0).as_deref(), Some(""));
378 }
379
380 #[test]
381 fn as_string_round_trips() {
382 let b = Buffer::from_str("a\nb\nc");
383 assert_eq!(b.as_string(), "a\nb\nc");
384 }
385
386 #[test]
387 fn dirty_gen_starts_at_zero() {
388 assert_eq!(Buffer::new().dirty_gen(), 0);
389 }
390
391 fn vp_wrap(width: u16, height: u16) -> Viewport {
392 Viewport {
393 top_row: 0,
394 top_col: 0,
395 width,
396 height,
397 wrap: crate::Wrap::Char,
398 text_width: width,
399 tab_width: 0,
400 }
401 }
402
403 #[test]
404 fn ensure_cursor_visible_wrap_scrolls_when_cursor_below_screen() {
405 let mut b = Buffer::from_str("aaaaaaaaaa\nb\nc");
406 let mut v = vp_wrap(4, 3);
407 b.set_cursor(Position::new(2, 0));
408 b.ensure_cursor_visible(&mut v);
409 assert_eq!(v.top_row, 1);
410 }
411
412 #[test]
413 fn ensure_cursor_visible_wrap_no_scroll_when_visible() {
414 let mut b = Buffer::from_str("aaaaaaaaaa\nb");
415 let mut v = vp_wrap(4, 4);
416 b.set_cursor(Position::new(0, 5));
417 b.ensure_cursor_visible(&mut v);
418 assert_eq!(v.top_row, 0);
419 }
420
421 #[test]
422 fn ensure_cursor_visible_wrap_snaps_top_when_cursor_above() {
423 let mut b = Buffer::from_str("a\nb\nc\nd\ne");
424 let mut v = vp_wrap(4, 2);
425 v.top_row = 3;
426 b.set_cursor(Position::new(1, 0));
427 b.ensure_cursor_visible(&mut v);
428 assert_eq!(v.top_row, 1);
429 }
430
431 #[test]
432 fn screen_rows_between_sums_segments_under_wrap() {
433 let b = Buffer::from_str("aaaaaaaaa\nb\n");
434 let v = vp_wrap(4, 0);
435 assert_eq!(b.screen_rows_between(&v, 0, 0), 3);
436 assert_eq!(b.screen_rows_between(&v, 0, 1), 4);
437 assert_eq!(b.screen_rows_between(&v, 0, 2), 5);
438 assert_eq!(b.screen_rows_between(&v, 1, 2), 2);
439 }
440
441 #[test]
442 fn screen_rows_between_one_per_doc_row_when_wrap_off() {
443 let b = Buffer::from_str("aaaaa\nb\nc");
444 let v = Viewport::default();
445 assert_eq!(b.screen_rows_between(&v, 0, 2), 3);
446 }
447
448 #[test]
449 fn max_top_for_height_walks_back_until_height_reached() {
450 let b = Buffer::from_str("a\nb\nc\nd\neeeeeeee");
451 let v = vp_wrap(4, 0);
452 assert_eq!(b.max_top_for_height(&v, 4), 2);
453 assert_eq!(b.max_top_for_height(&v, 99), 0);
454 }
455
456 #[test]
457 fn cursor_screen_row_returns_none_when_wrap_off() {
458 let b = Buffer::from_str("a");
459 let v = Viewport::default();
460 assert!(b.cursor_screen_row(&v).is_none());
461 }
462
463 #[test]
464 fn cursor_screen_row_under_wrap() {
465 let mut b = Buffer::from_str("aaaaaaaaaa\nb");
466 let v = vp_wrap(4, 0);
467 b.set_cursor(Position::new(0, 5));
468 assert_eq!(b.cursor_screen_row(&v), Some(1));
469 b.set_cursor(Position::new(1, 0));
470 assert_eq!(b.cursor_screen_row(&v), Some(3));
471 }
472
473 #[test]
474 fn ensure_cursor_visible_falls_back_when_wrap_disabled() {
475 let mut b = Buffer::from_str("a\nb\nc\nd\ne");
476 let mut v = Viewport {
477 top_row: 0,
478 top_col: 0,
479 width: 4,
480 height: 2,
481 wrap: crate::Wrap::None,
482 text_width: 4,
483 tab_width: 0,
484 };
485 b.set_cursor(Position::new(4, 0));
486 b.ensure_cursor_visible(&mut v);
487 assert_eq!(v.top_row, 3);
488 }
489
490 #[test]
495 fn buffer_views_independent_cursors() {
496 let a = Buffer::from_str("hello\nworld");
497 let arc = a.content_arc();
498 let mut view_a = Buffer::new_view(Arc::clone(&arc));
499 let mut view_b = Buffer::new_view(Arc::clone(&arc));
500
501 view_a.set_cursor(Position::new(1, 3));
502 assert_eq!(view_b.cursor(), Position::new(0, 0));
504
505 view_b.set_cursor(Position::new(0, 2));
506 assert_eq!(view_a.cursor(), Position::new(1, 3));
508 }
509
510 #[test]
512 fn buffer_views_share_content() {
513 use crate::edit::Edit;
514
515 let a = Buffer::from_str("foo");
516 let arc = a.content_arc();
517 let mut view_a = Buffer::new_view(Arc::clone(&arc));
518 let view_b = Buffer::new_view(Arc::clone(&arc));
519
520 view_a.apply_edit(Edit::InsertStr {
521 at: Position::new(0, 3),
522 text: "bar".into(),
523 });
524
525 assert_eq!(view_a.line(0).as_deref(), Some("foobar"));
526 assert_eq!(view_b.line(0).as_deref(), Some("foobar"));
527 }
528}