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) -> &[String] {
114 let c = self.content.lock().unwrap();
129 let ptr = c.lines.as_ptr();
130 let len = c.lines.len();
131 drop(c);
132 unsafe { std::slice::from_raw_parts(ptr, len) }
135 }
136
137 pub fn line(&self, row: usize) -> Option<&str> {
138 let c = self.content.lock().unwrap();
140 let s_ptr = c.lines.get(row)?.as_str() as *const str;
141 drop(c);
142 Some(unsafe { &*s_ptr })
145 }
146
147 pub fn cursor(&self) -> Position {
148 self.cursor
149 }
150
151 pub fn dirty_gen(&self) -> u64 {
152 self.content.lock().unwrap().dirty_gen
153 }
154
155 pub fn row_count(&self) -> usize {
157 self.content.lock().unwrap().lines.len()
158 }
159
160 pub fn as_string(&self) -> String {
162 self.content.lock().unwrap().lines.join("\n")
163 }
164
165 pub fn set_cursor(&mut self, pos: Position) {
172 let c = self.content.lock().unwrap();
173 let last_row = c.lines.len().saturating_sub(1);
174 let row = pos.row.min(last_row);
175 let line_chars = c.lines[row].chars().count();
176 let col = pos.col.min(line_chars);
177 drop(c);
178 self.cursor = Position::new(row, col);
179 }
180
181 pub fn ensure_cursor_visible(&mut self, viewport: &mut Viewport) {
184 let cursor = self.cursor;
185 let v = *viewport;
186 let wrap_active = !matches!(v.wrap, crate::Wrap::None) && v.text_width > 0;
187 if !wrap_active {
188 viewport.ensure_visible(cursor);
189 return;
190 }
191 if v.height == 0 {
192 return;
193 }
194 if cursor.row < v.top_row {
196 viewport.top_row = cursor.row;
197 viewport.top_col = 0;
198 return;
199 }
200 let height = v.height as usize;
201 loop {
203 let csr = self.cursor_screen_row_from(viewport, viewport.top_row);
204 match csr {
205 Some(row) if row < height => break,
206 _ => {}
207 }
208 let next = {
209 let c = self.content.lock().unwrap();
210 let mut n = viewport.top_row + 1;
211 while n <= cursor.row && c.folds.iter().any(|f| f.hides(n)) {
212 n += 1;
213 }
214 n
215 };
216 if next > cursor.row {
217 viewport.top_row = cursor.row;
218 break;
219 }
220 viewport.top_row = next;
221 }
222 viewport.top_col = 0;
223 }
224
225 pub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize> {
227 if matches!(viewport.wrap, crate::Wrap::None) || viewport.text_width == 0 {
228 return None;
229 }
230 self.cursor_screen_row_from(viewport, viewport.top_row)
231 }
232
233 pub fn screen_rows_between(&self, viewport: &Viewport, start: usize, end: usize) -> usize {
235 if start > end {
236 return 0;
237 }
238 let c = self.content.lock().unwrap();
239 let last = c.lines.len().saturating_sub(1);
240 let end = end.min(last);
241 let v = *viewport;
242 let mut total = 0usize;
243 for r in start..=end {
244 if c.folds.iter().any(|f| f.hides(r)) {
245 continue;
246 }
247 if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
248 total += 1;
249 } else {
250 let line = c.lines.get(r).map(String::as_str).unwrap_or("");
251 total += crate::wrap::wrap_segments(line, v.text_width, v.wrap).len();
252 }
253 }
254 total
255 }
256
257 pub fn max_top_for_height(&self, viewport: &Viewport, height: usize) -> usize {
260 if height == 0 {
261 return 0;
262 }
263 let c = self.content.lock().unwrap();
264 let last = c.lines.len().saturating_sub(1);
265 let mut total = 0usize;
266 let mut row = last;
267 loop {
268 if !c.folds.iter().any(|f| f.hides(row)) {
269 let v = *viewport;
270 total += if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
271 1
272 } else {
273 let line = c.lines.get(row).map(String::as_str).unwrap_or("");
274 crate::wrap::wrap_segments(line, v.text_width, v.wrap).len()
275 };
276 }
277 if total >= height {
278 return row;
279 }
280 if row == 0 {
281 return 0;
282 }
283 row -= 1;
284 }
285 }
286
287 pub fn clamp_position(&self, pos: Position) -> Position {
289 let c = self.content.lock().unwrap();
290 let last_row = c.lines.len().saturating_sub(1);
291 let row = pos.row.min(last_row);
292 let line_chars = c.lines[row].chars().count();
293 let col = pos.col.min(line_chars);
294 Position::new(row, col)
295 }
296
297 pub fn replace_all(&mut self, text: &str) {
300 let new_cursor = {
301 let mut c = self.content.lock().unwrap();
302 let mut lines: Vec<String> = text.split('\n').map(str::to_owned).collect();
303 if lines.is_empty() {
304 lines.push(String::new());
305 }
306 c.lines = lines;
307 let last_row = c.lines.len().saturating_sub(1);
308 let row = self.cursor.row.min(last_row);
309 let line_chars = c.lines[row].chars().count();
310 let col = self.cursor.col.min(line_chars);
311 c.dirty_gen = c.dirty_gen.wrapping_add(1);
312 Position::new(row, col)
313 };
314 self.cursor = new_cursor;
315 }
316
317 pub(crate) fn dirty_gen_bump(&mut self) {
321 let mut c = self.content.lock().unwrap();
322 c.dirty_gen = c.dirty_gen.wrapping_add(1);
323 }
324
325 pub(crate) fn content_lock(&self) -> MutexGuard<'_, Content> {
327 self.content.lock().unwrap()
328 }
329
330 pub(crate) fn content_lock_mut(&mut self) -> MutexGuard<'_, Content> {
332 self.content.lock().unwrap()
333 }
334
335 fn cursor_screen_row_from(&self, viewport: &Viewport, top: usize) -> Option<usize> {
338 let cursor = self.cursor;
339 if cursor.row < top {
340 return None;
341 }
342 let c = self.content.lock().unwrap();
343 let v = *viewport;
344 let mut screen = 0usize;
345 for r in top..=cursor.row {
346 if c.folds.iter().any(|f| f.hides(r)) {
347 continue;
348 }
349 let line = c.lines.get(r).map(String::as_str).unwrap_or("");
350 let segs = crate::wrap::wrap_segments(line, v.text_width, v.wrap);
351 if r == cursor.row {
352 let seg_idx = crate::wrap::segment_for_col(&segs, cursor.col);
353 return Some(screen + seg_idx);
354 }
355 screen += segs.len();
356 }
357 None
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use super::*;
364
365 #[test]
366 fn new_has_one_empty_row() {
367 let b = Buffer::new();
368 assert_eq!(b.row_count(), 1);
369 assert_eq!(b.line(0), Some(""));
370 assert_eq!(b.cursor(), Position::default());
371 }
372
373 #[test]
374 fn from_str_splits_on_newline() {
375 let b = Buffer::from_str("foo\nbar\nbaz");
376 assert_eq!(b.row_count(), 3);
377 assert_eq!(b.line(0), Some("foo"));
378 assert_eq!(b.line(2), Some("baz"));
379 }
380
381 #[test]
382 fn from_str_trailing_newline_keeps_empty_row() {
383 let b = Buffer::from_str("foo\n");
384 assert_eq!(b.row_count(), 2);
385 assert_eq!(b.line(1), Some(""));
386 }
387
388 #[test]
389 fn from_str_empty_input_keeps_one_row() {
390 let b = Buffer::from_str("");
391 assert_eq!(b.row_count(), 1);
392 assert_eq!(b.line(0), Some(""));
393 }
394
395 #[test]
396 fn as_string_round_trips() {
397 let b = Buffer::from_str("a\nb\nc");
398 assert_eq!(b.as_string(), "a\nb\nc");
399 }
400
401 #[test]
402 fn dirty_gen_starts_at_zero() {
403 assert_eq!(Buffer::new().dirty_gen(), 0);
404 }
405
406 fn vp_wrap(width: u16, height: u16) -> Viewport {
407 Viewport {
408 top_row: 0,
409 top_col: 0,
410 width,
411 height,
412 wrap: crate::Wrap::Char,
413 text_width: width,
414 tab_width: 0,
415 }
416 }
417
418 #[test]
419 fn ensure_cursor_visible_wrap_scrolls_when_cursor_below_screen() {
420 let mut b = Buffer::from_str("aaaaaaaaaa\nb\nc");
421 let mut v = vp_wrap(4, 3);
422 b.set_cursor(Position::new(2, 0));
423 b.ensure_cursor_visible(&mut v);
424 assert_eq!(v.top_row, 1);
425 }
426
427 #[test]
428 fn ensure_cursor_visible_wrap_no_scroll_when_visible() {
429 let mut b = Buffer::from_str("aaaaaaaaaa\nb");
430 let mut v = vp_wrap(4, 4);
431 b.set_cursor(Position::new(0, 5));
432 b.ensure_cursor_visible(&mut v);
433 assert_eq!(v.top_row, 0);
434 }
435
436 #[test]
437 fn ensure_cursor_visible_wrap_snaps_top_when_cursor_above() {
438 let mut b = Buffer::from_str("a\nb\nc\nd\ne");
439 let mut v = vp_wrap(4, 2);
440 v.top_row = 3;
441 b.set_cursor(Position::new(1, 0));
442 b.ensure_cursor_visible(&mut v);
443 assert_eq!(v.top_row, 1);
444 }
445
446 #[test]
447 fn screen_rows_between_sums_segments_under_wrap() {
448 let b = Buffer::from_str("aaaaaaaaa\nb\n");
449 let v = vp_wrap(4, 0);
450 assert_eq!(b.screen_rows_between(&v, 0, 0), 3);
451 assert_eq!(b.screen_rows_between(&v, 0, 1), 4);
452 assert_eq!(b.screen_rows_between(&v, 0, 2), 5);
453 assert_eq!(b.screen_rows_between(&v, 1, 2), 2);
454 }
455
456 #[test]
457 fn screen_rows_between_one_per_doc_row_when_wrap_off() {
458 let b = Buffer::from_str("aaaaa\nb\nc");
459 let v = Viewport::default();
460 assert_eq!(b.screen_rows_between(&v, 0, 2), 3);
461 }
462
463 #[test]
464 fn max_top_for_height_walks_back_until_height_reached() {
465 let b = Buffer::from_str("a\nb\nc\nd\neeeeeeee");
466 let v = vp_wrap(4, 0);
467 assert_eq!(b.max_top_for_height(&v, 4), 2);
468 assert_eq!(b.max_top_for_height(&v, 99), 0);
469 }
470
471 #[test]
472 fn cursor_screen_row_returns_none_when_wrap_off() {
473 let b = Buffer::from_str("a");
474 let v = Viewport::default();
475 assert!(b.cursor_screen_row(&v).is_none());
476 }
477
478 #[test]
479 fn cursor_screen_row_under_wrap() {
480 let mut b = Buffer::from_str("aaaaaaaaaa\nb");
481 let v = vp_wrap(4, 0);
482 b.set_cursor(Position::new(0, 5));
483 assert_eq!(b.cursor_screen_row(&v), Some(1));
484 b.set_cursor(Position::new(1, 0));
485 assert_eq!(b.cursor_screen_row(&v), Some(3));
486 }
487
488 #[test]
489 fn ensure_cursor_visible_falls_back_when_wrap_disabled() {
490 let mut b = Buffer::from_str("a\nb\nc\nd\ne");
491 let mut v = Viewport {
492 top_row: 0,
493 top_col: 0,
494 width: 4,
495 height: 2,
496 wrap: crate::Wrap::None,
497 text_width: 4,
498 tab_width: 0,
499 };
500 b.set_cursor(Position::new(4, 0));
501 b.ensure_cursor_visible(&mut v);
502 assert_eq!(v.top_row, 3);
503 }
504
505 #[test]
510 fn buffer_views_independent_cursors() {
511 let a = Buffer::from_str("hello\nworld");
512 let arc = a.content_arc();
513 let mut view_a = Buffer::new_view(Arc::clone(&arc));
514 let mut view_b = Buffer::new_view(Arc::clone(&arc));
515
516 view_a.set_cursor(Position::new(1, 3));
517 assert_eq!(view_b.cursor(), Position::new(0, 0));
519
520 view_b.set_cursor(Position::new(0, 2));
521 assert_eq!(view_a.cursor(), Position::new(1, 3));
523 }
524
525 #[test]
527 fn buffer_views_share_content() {
528 use crate::edit::Edit;
529
530 let a = Buffer::from_str("foo");
531 let arc = a.content_arc();
532 let mut view_a = Buffer::new_view(Arc::clone(&arc));
533 let view_b = Buffer::new_view(Arc::clone(&arc));
534
535 view_a.apply_edit(Edit::InsertStr {
536 at: Position::new(0, 3),
537 text: "bar".into(),
538 });
539
540 assert_eq!(view_a.line(0), Some("foobar"));
541 assert_eq!(view_b.line(0), Some("foobar"));
542 }
543}