1use std::sync::{Arc, Mutex, MutexGuard};
2
3use crate::content::Content;
4use crate::{Position, Viewport};
5
6pub struct Buffer {
44 pub(crate) content: Arc<Mutex<Content>>,
46 cursor: Position,
49}
50
51impl Default for Buffer {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57impl Buffer {
58 pub fn new() -> Self {
62 Self {
63 content: Arc::new(Mutex::new(Content::new())),
64 cursor: Position::default(),
65 }
66 }
67
68 #[allow(clippy::should_implement_trait)]
73 pub fn from_str(text: &str) -> Self {
74 Self {
75 content: Arc::new(Mutex::new(Content::from_str(text))),
76 cursor: Position::default(),
77 }
78 }
79
80 pub fn new_view(content: Arc<Mutex<Content>>) -> Self {
100 Self {
101 content,
102 cursor: Position::default(),
103 }
104 }
105
106 pub fn content_arc(&self) -> Arc<Mutex<Content>> {
109 Arc::clone(&self.content)
110 }
111
112 pub fn cursor(&self) -> Position {
115 self.cursor
116 }
117
118 pub fn dirty_gen(&self) -> u64 {
119 self.content.lock().unwrap().dirty_gen
120 }
121
122 pub fn row_count(&self) -> usize {
124 self.content.lock().unwrap().text.len_lines()
125 }
126
127 pub fn as_string(&self) -> String {
132 self.content.lock().unwrap().text.to_string()
133 }
134
135 pub fn set_cursor(&mut self, pos: Position) {
142 let c = self.content.lock().unwrap();
143 let n = c.text.len_lines();
144 let last_row = n.saturating_sub(1);
145 let row = pos.row.min(last_row);
146 let line_chars = rope_line_char_count(&c.text, row);
147 let col = pos.col.min(line_chars);
148 drop(c);
149 self.cursor = Position::new(row, col);
150 }
151
152 pub fn ensure_cursor_visible(&mut self, viewport: &mut Viewport) {
155 let cursor = self.cursor;
156 let v = *viewport;
157 let wrap_active = !matches!(v.wrap, crate::Wrap::None) && v.text_width > 0;
158 if !wrap_active {
159 viewport.ensure_visible(cursor);
160 return;
161 }
162 if v.height == 0 {
163 return;
164 }
165 if cursor.row < v.top_row {
167 viewport.top_row = cursor.row;
168 viewport.top_col = 0;
169 return;
170 }
171 let height = v.height as usize;
172 loop {
174 let csr = self.cursor_screen_row_from(viewport, viewport.top_row);
175 match csr {
176 Some(row) if row < height => break,
177 _ => {}
178 }
179 let next = {
180 let c = self.content.lock().unwrap();
181 let mut n = viewport.top_row + 1;
182 while n <= cursor.row && c.folds.iter().any(|f| f.hides(n)) {
183 n += 1;
184 }
185 n
186 };
187 if next > cursor.row {
188 viewport.top_row = cursor.row;
189 break;
190 }
191 viewport.top_row = next;
192 }
193 viewport.top_col = 0;
194 }
195
196 pub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize> {
198 if matches!(viewport.wrap, crate::Wrap::None) || viewport.text_width == 0 {
199 return None;
200 }
201 self.cursor_screen_row_from(viewport, viewport.top_row)
202 }
203
204 pub fn screen_rows_between(&self, viewport: &Viewport, start: usize, end: usize) -> usize {
206 if start > end {
207 return 0;
208 }
209 let c = self.content.lock().unwrap();
210 let n = c.text.len_lines();
211 let last = n.saturating_sub(1);
212 let end = end.min(last);
213 let v = *viewport;
214 let mut total = 0usize;
215 for r in start..=end {
216 if c.folds.iter().any(|f| f.hides(r)) {
217 continue;
218 }
219 if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
220 total += 1;
221 } else {
222 let line = rope_line_str(&c.text, r);
223 total += crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
224 }
225 }
226 total
227 }
228
229 pub fn max_top_for_height(&self, viewport: &Viewport, height: usize) -> usize {
232 if height == 0 {
233 return 0;
234 }
235 let c = self.content.lock().unwrap();
236 let n = c.text.len_lines();
237 let last = n.saturating_sub(1);
238 let mut total = 0usize;
239 let mut row = last;
240 loop {
241 if !c.folds.iter().any(|f| f.hides(row)) {
242 let v = *viewport;
243 total += if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
244 1
245 } else {
246 let line = rope_line_str(&c.text, row);
247 crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len()
248 };
249 }
250 if total >= height {
251 return row;
252 }
253 if row == 0 {
254 return 0;
255 }
256 row -= 1;
257 }
258 }
259
260 pub fn clamp_position(&self, pos: Position) -> Position {
262 let c = self.content.lock().unwrap();
263 let n = c.text.len_lines();
264 let last_row = n.saturating_sub(1);
265 let row = pos.row.min(last_row);
266 let line_chars = rope_line_char_count(&c.text, row);
267 let col = pos.col.min(line_chars);
268 Position::new(row, col)
269 }
270
271 pub fn replace_all(&mut self, text: &str) {
274 let new_cursor = {
275 let mut c = self.content.lock().unwrap();
276 c.text = ropey::Rope::from_str(text);
277 let n = c.text.len_lines();
278 let last_row = n.saturating_sub(1);
279 let row = self.cursor.row.min(last_row);
280 let line_chars = rope_line_char_count(&c.text, row);
281 let col = self.cursor.col.min(line_chars);
282 c.dirty_gen = c.dirty_gen.wrapping_add(1);
283 c.cached_joined = None;
284 c.cached_byte_len = None;
285 Position::new(row, col)
286 };
287 self.cursor = new_cursor;
288 }
289
290 pub(crate) fn dirty_gen_bump(&mut self) {
294 let mut c = self.content.lock().unwrap();
295 c.dirty_gen = c.dirty_gen.wrapping_add(1);
296 c.cached_joined = None;
297 c.cached_byte_len = None;
298 }
299
300 pub fn byte_len(&self) -> usize {
306 let mut c = self.content.lock().unwrap();
307 let dg = c.dirty_gen;
308 if let Some((cached_dg, len)) = c.cached_byte_len
309 && cached_dg == dg
310 {
311 return len;
312 }
313 let total = c.text.len_bytes();
314 c.cached_byte_len = Some((dg, total));
315 total
316 }
317
318 pub fn content_joined(&self) -> std::sync::Arc<String> {
327 let mut c = self.content.lock().unwrap();
328 let dg = c.dirty_gen;
329 if let Some((cached_dg, ref s)) = c.cached_joined
330 && cached_dg == dg
331 {
332 return std::sync::Arc::clone(s);
333 }
334 let joined = std::sync::Arc::new(c.text.to_string());
335 c.cached_joined = Some((dg, std::sync::Arc::clone(&joined)));
336 joined
337 }
338
339 pub fn rope(&self) -> ropey::Rope {
348 self.content.lock().unwrap().text.clone()
349 }
350
351 pub(crate) fn content_lock(&self) -> MutexGuard<'_, Content> {
353 self.content.lock().unwrap()
354 }
355
356 pub(crate) fn content_lock_mut(&mut self) -> MutexGuard<'_, Content> {
358 self.content.lock().unwrap()
359 }
360
361 fn cursor_screen_row_from(&self, viewport: &Viewport, top: usize) -> Option<usize> {
364 let cursor = self.cursor;
365 if cursor.row < top {
366 return None;
367 }
368 let c = self.content.lock().unwrap();
369 let v = *viewport;
370 let mut screen = 0usize;
371 for r in top..=cursor.row {
372 if c.folds.iter().any(|f| f.hides(r)) {
373 continue;
374 }
375 let line = rope_line_str(&c.text, r);
376 let segs = crate::wrap::wrap_segments(&line, v.text_width, v.wrap);
377 if r == cursor.row {
378 let seg_idx = crate::wrap::segment_for_col(&segs, cursor.col);
379 return Some(screen + seg_idx);
380 }
381 screen += segs.len();
382 }
383 None
384 }
385}
386
387pub fn rope_line_str(rope: &ropey::Rope, row: usize) -> String {
392 let mut s = rope.line(row).to_string();
393 if s.ends_with('\n') {
395 s.pop();
396 }
397 s
398}
399
400pub fn rope_line_bytes(rope: &ropey::Rope, row: usize) -> usize {
402 let slice = rope.line(row);
403 let bytes = slice.len_bytes();
404 if row + 1 < rope.len_lines() && bytes > 0 {
406 bytes - 1
407 } else {
408 bytes
409 }
410}
411
412pub(crate) fn rope_line_char_count(rope: &ropey::Rope, row: usize) -> usize {
414 let slice = rope.line(row);
415 let chars = slice.len_chars();
416 if row + 1 < rope.len_lines() && chars > 0 {
418 chars - 1
419 } else {
420 chars
421 }
422}
423
424pub(crate) fn pos_to_char_idx(rope: &ropey::Rope, row: usize, col: usize) -> usize {
426 let line_start = rope.line_to_char(row);
427 let line_char_count = rope_line_char_count(rope, row);
428 line_start + col.min(line_char_count)
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434
435 #[test]
436 fn new_has_one_empty_row() {
437 let b = Buffer::new();
438 assert_eq!(b.row_count(), 1);
439 assert_eq!(rope_line_str(&b.rope(), 0), "");
440 assert_eq!(b.cursor(), Position::default());
441 }
442
443 #[test]
444 fn from_str_splits_on_newline() {
445 let b = Buffer::from_str("foo\nbar\nbaz");
446 assert_eq!(b.row_count(), 3);
447 assert_eq!(rope_line_str(&b.rope(), 0), "foo");
448 assert_eq!(rope_line_str(&b.rope(), 2), "baz");
449 }
450
451 #[test]
452 fn from_str_trailing_newline_keeps_empty_row() {
453 let b = Buffer::from_str("foo\n");
454 assert_eq!(b.row_count(), 2);
455 assert_eq!(rope_line_str(&b.rope(), 1), "");
456 }
457
458 #[test]
459 fn from_str_empty_input_keeps_one_row() {
460 let b = Buffer::from_str("");
461 assert_eq!(b.row_count(), 1);
462 assert_eq!(rope_line_str(&b.rope(), 0), "");
463 }
464
465 #[test]
466 fn as_string_round_trips() {
467 let b = Buffer::from_str("a\nb\nc");
468 assert_eq!(b.as_string(), "a\nb\nc");
469 }
470
471 #[test]
472 fn dirty_gen_starts_at_zero() {
473 assert_eq!(Buffer::new().dirty_gen(), 0);
474 }
475
476 fn vp_wrap(width: u16, height: u16) -> Viewport {
477 Viewport {
478 top_row: 0,
479 top_col: 0,
480 width,
481 height,
482 wrap: crate::Wrap::Char,
483 text_width: width,
484 tab_width: 0,
485 }
486 }
487
488 #[test]
489 fn ensure_cursor_visible_wrap_scrolls_when_cursor_below_screen() {
490 let mut b = Buffer::from_str("aaaaaaaaaa\nb\nc");
491 let mut v = vp_wrap(4, 3);
492 b.set_cursor(Position::new(2, 0));
493 b.ensure_cursor_visible(&mut v);
494 assert_eq!(v.top_row, 1);
495 }
496
497 #[test]
498 fn ensure_cursor_visible_wrap_no_scroll_when_visible() {
499 let mut b = Buffer::from_str("aaaaaaaaaa\nb");
500 let mut v = vp_wrap(4, 4);
501 b.set_cursor(Position::new(0, 5));
502 b.ensure_cursor_visible(&mut v);
503 assert_eq!(v.top_row, 0);
504 }
505
506 #[test]
507 fn ensure_cursor_visible_wrap_snaps_top_when_cursor_above() {
508 let mut b = Buffer::from_str("a\nb\nc\nd\ne");
509 let mut v = vp_wrap(4, 2);
510 v.top_row = 3;
511 b.set_cursor(Position::new(1, 0));
512 b.ensure_cursor_visible(&mut v);
513 assert_eq!(v.top_row, 1);
514 }
515
516 #[test]
517 fn screen_rows_between_sums_segments_under_wrap() {
518 let b = Buffer::from_str("aaaaaaaaa\nb\n");
519 let v = vp_wrap(4, 0);
520 assert_eq!(b.screen_rows_between(&v, 0, 0), 3);
521 assert_eq!(b.screen_rows_between(&v, 0, 1), 4);
522 assert_eq!(b.screen_rows_between(&v, 0, 2), 5);
523 assert_eq!(b.screen_rows_between(&v, 1, 2), 2);
524 }
525
526 #[test]
527 fn screen_rows_between_one_per_doc_row_when_wrap_off() {
528 let b = Buffer::from_str("aaaaa\nb\nc");
529 let v = Viewport::default();
530 assert_eq!(b.screen_rows_between(&v, 0, 2), 3);
531 }
532
533 #[test]
534 fn max_top_for_height_walks_back_until_height_reached() {
535 let b = Buffer::from_str("a\nb\nc\nd\neeeeeeee");
536 let v = vp_wrap(4, 0);
537 assert_eq!(b.max_top_for_height(&v, 4), 2);
538 assert_eq!(b.max_top_for_height(&v, 99), 0);
539 }
540
541 #[test]
542 fn cursor_screen_row_returns_none_when_wrap_off() {
543 let b = Buffer::from_str("a");
544 let v = Viewport::default();
545 assert!(b.cursor_screen_row(&v).is_none());
546 }
547
548 #[test]
549 fn cursor_screen_row_under_wrap() {
550 let mut b = Buffer::from_str("aaaaaaaaaa\nb");
551 let v = vp_wrap(4, 0);
552 b.set_cursor(Position::new(0, 5));
553 assert_eq!(b.cursor_screen_row(&v), Some(1));
554 b.set_cursor(Position::new(1, 0));
555 assert_eq!(b.cursor_screen_row(&v), Some(3));
556 }
557
558 #[test]
559 fn ensure_cursor_visible_falls_back_when_wrap_disabled() {
560 let mut b = Buffer::from_str("a\nb\nc\nd\ne");
561 let mut v = Viewport {
562 top_row: 0,
563 top_col: 0,
564 width: 4,
565 height: 2,
566 wrap: crate::Wrap::None,
567 text_width: 4,
568 tab_width: 0,
569 };
570 b.set_cursor(Position::new(4, 0));
571 b.ensure_cursor_visible(&mut v);
572 assert_eq!(v.top_row, 3);
573 }
574
575 #[test]
580 fn buffer_views_independent_cursors() {
581 let a = Buffer::from_str("hello\nworld");
582 let arc = a.content_arc();
583 let mut view_a = Buffer::new_view(Arc::clone(&arc));
584 let mut view_b = Buffer::new_view(Arc::clone(&arc));
585
586 view_a.set_cursor(Position::new(1, 3));
587 assert_eq!(view_b.cursor(), Position::new(0, 0));
589
590 view_b.set_cursor(Position::new(0, 2));
591 assert_eq!(view_a.cursor(), Position::new(1, 3));
593 }
594
595 #[test]
597 fn buffer_views_share_content() {
598 use crate::edit::Edit;
599
600 let a = Buffer::from_str("foo");
601 let arc = a.content_arc();
602 let mut view_a = Buffer::new_view(Arc::clone(&arc));
603 let view_b = Buffer::new_view(Arc::clone(&arc));
604
605 view_a.apply_edit(Edit::InsertStr {
606 at: Position::new(0, 3),
607 text: "bar".into(),
608 });
609
610 assert_eq!(rope_line_str(&view_a.rope(), 0), "foobar");
611 assert_eq!(rope_line_str(&view_b.rope(), 0), "foobar");
612 }
613}