1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4use escriba_core::{BufferId, Edit, EditKind, Position, Range};
5use ropey::Rope;
6use serde::{Deserialize, Serialize};
7
8use crate::encoding::Encoding;
9use crate::error::BufferError;
10use crate::line_ending::LineEnding;
11use crate::undo::{UndoEntry, UndoTree};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
26pub struct TextRev(pub u64);
27
28impl TextRev {
29 #[must_use]
30 pub const fn next(self) -> Self {
31 Self(self.0.wrapping_add(1))
32 }
33}
34
35#[derive(Debug, Clone)]
36pub struct Buffer {
37 pub id: BufferId,
38 pub path: Option<PathBuf>,
39 pub rope: Rope,
40 pub modified: bool,
41 pub encoding: Encoding,
42 pub line_ending: LineEnding,
43 pub undo: UndoTree,
44 text_rev: TextRev,
48}
49
50impl Buffer {
51 #[must_use]
52 pub fn empty(id: BufferId) -> Self {
53 Self {
54 id,
55 path: None,
56 rope: Rope::new(),
57 modified: false,
58 encoding: Encoding::default(),
59 line_ending: LineEnding::default(),
60 undo: UndoTree::new(),
61 text_rev: TextRev::default(),
62 }
63 }
64
65 #[must_use]
68 pub const fn text_rev(&self) -> TextRev {
69 self.text_rev
70 }
71
72 #[must_use]
73 pub fn from_str(id: BufferId, src: &str) -> Self {
74 let line_ending = LineEnding::detect(src);
75 Self {
76 id,
77 path: None,
78 rope: Rope::from_str(src),
79 modified: false,
80 encoding: Encoding::default(),
81 line_ending,
82 undo: UndoTree::new(),
83 text_rev: TextRev::default(),
84 }
85 }
86
87 pub fn open(id: BufferId, path: impl AsRef<Path>) -> Result<Self, BufferError> {
88 let path = path.as_ref().to_path_buf();
89 let src = if path.exists() {
90 std::fs::read_to_string(&path)?
91 } else {
92 String::new()
93 };
94 let line_ending = LineEnding::detect(&src);
95 Ok(Self {
96 id,
97 path: Some(path),
98 rope: Rope::from_str(&src),
99 modified: false,
100 encoding: Encoding::default(),
101 line_ending,
102 undo: UndoTree::new(),
103 text_rev: TextRev::default(),
104 })
105 }
106
107 pub fn save(&mut self) -> Result<(), BufferError> {
108 let path = self.path.clone().ok_or(BufferError::NoPath)?;
109 let mut src = String::new();
110 for line in self.rope.lines() {
111 src.push_str(&line.to_string());
112 }
113 std::fs::write(&path, src)?;
114 self.modified = false;
115 Ok(())
116 }
117
118 pub fn save_as(&mut self, path: impl AsRef<Path>) -> Result<(), BufferError> {
119 self.path = Some(path.as_ref().to_path_buf());
120 self.save()
121 }
122
123 #[must_use]
126 pub fn line_count(&self) -> u32 {
127 u32::try_from(self.rope.len_lines()).unwrap_or(u32::MAX)
128 }
129
130 #[must_use]
131 pub fn byte_count(&self) -> usize {
132 self.rope.len_bytes()
133 }
134
135 #[must_use]
136 pub fn char_count(&self) -> usize {
137 self.rope.len_chars()
138 }
139
140 pub fn line(&self, n: u32) -> Option<String> {
141 let n = n as usize;
142 if n >= self.rope.len_lines() {
143 return None;
144 }
145 Some(self.rope.line(n).to_string())
146 }
147
148 pub fn line_len_chars(&self, n: u32) -> u32 {
149 let n = n as usize;
150 if n >= self.rope.len_lines() {
151 return 0;
152 }
153 let line = self.rope.line(n);
154 let mut len = line.len_chars();
155 if line.chars().last().is_some_and(|c| c == '\n' || c == '\r') {
157 len = len.saturating_sub(1);
158 }
159 u32::try_from(len).unwrap_or(u32::MAX)
160 }
161
162 #[must_use]
164 pub fn clamp(&self, pos: Position) -> Position {
165 let line = pos.line.min(self.line_count().saturating_sub(1));
166 let col = pos.column.min(self.line_len_chars(line));
167 Position::new(line, col)
168 }
169
170 pub fn position_to_char(&self, pos: Position) -> Result<usize, BufferError> {
171 let line = pos.line as usize;
172 if line >= self.rope.len_lines() {
173 return Err(BufferError::InvalidPosition {
174 line: pos.line,
175 column: pos.column,
176 total_lines: self.line_count(),
177 });
178 }
179 let line_start = self.rope.line_to_char(line);
180 let line_slice = self.rope.line(line);
181 let max_col = line_slice.len_chars();
182 let col = (pos.column as usize).min(max_col);
183 Ok(line_start + col)
184 }
185
186 #[must_use]
187 pub fn char_to_position(&self, ch: usize) -> Position {
188 let line = self.rope.char_to_line(ch.min(self.rope.len_chars()));
189 let line_start = self.rope.line_to_char(line);
190 let col = ch.saturating_sub(line_start);
191 Position::new(
192 u32::try_from(line).unwrap_or(u32::MAX),
193 u32::try_from(col).unwrap_or(u32::MAX),
194 )
195 }
196
197 pub fn slice(&self, range: Range) -> Result<String, BufferError> {
198 let r = range.normalized();
199 let a = self.position_to_char(r.start)?;
200 let b = self.position_to_char(r.end)?;
201 Ok(self.rope.slice(a..b).to_string())
202 }
203
204 pub fn apply(&mut self, edit: &Edit) -> Result<UndoEntry, BufferError> {
207 let range = edit.range.normalized();
208 let start_char = self.position_to_char(range.start)?;
209 let end_char = self.position_to_char(range.end)?;
210 let previous_text = self.rope.slice(start_char..end_char).to_string();
211
212 self.text_rev = self.text_rev.next();
216
217 let (inserted_len_chars, reverse_kind) = match &edit.kind {
218 EditKind::Insert { text } => {
219 self.rope.insert(start_char, text);
220 (text.chars().count(), EditKind::Delete)
221 }
222 EditKind::Delete => {
223 self.rope.remove(start_char..end_char);
224 (
225 0usize,
226 EditKind::Insert {
227 text: previous_text.clone(),
228 },
229 )
230 }
231 EditKind::Replace { text } => {
232 self.rope.remove(start_char..end_char);
233 self.rope.insert(start_char, text);
234 (
235 text.chars().count(),
236 EditKind::Replace {
237 text: previous_text.clone(),
238 },
239 )
240 }
241 };
242 self.modified = true;
243
244 let reverse_end_char = start_char + inserted_len_chars;
246 let reverse_range = Range::new(
247 self.char_to_position(start_char),
248 self.char_to_position(reverse_end_char),
249 );
250 let reverse_edit = Edit {
251 range: reverse_range,
252 kind: reverse_kind,
253 };
254 let entry = UndoEntry {
255 applied: edit.clone(),
256 reverse: reverse_edit,
257 };
258 self.undo.push(entry.clone());
259 Ok(entry)
260 }
261
262 pub fn undo(&mut self) -> Result<UndoEntry, BufferError> {
263 let entry = self.undo.pop_undo().ok_or(BufferError::NothingToUndo)?;
264 let r = entry.reverse.range.normalized();
266 let a = self.position_to_char(r.start)?;
267 let b = self.position_to_char(r.end)?;
268 self.text_rev = self.text_rev.next();
278 match &entry.reverse.kind {
279 EditKind::Insert { text } => {
280 self.rope.insert(a, text);
281 }
282 EditKind::Delete => {
283 self.rope.remove(a..b);
284 }
285 EditKind::Replace { text } => {
286 self.rope.remove(a..b);
287 self.rope.insert(a, text);
288 }
289 }
290 self.modified = true;
291 Ok(entry)
292 }
293
294 pub fn redo(&mut self) -> Result<UndoEntry, BufferError> {
295 let entry = self.undo.pop_redo().ok_or(BufferError::NothingToRedo)?;
296 let r = entry.applied.range.normalized();
297 let a = self.position_to_char(r.start)?;
298 let b = self.position_to_char(r.end)?;
299 self.text_rev = self.text_rev.next();
309 match &entry.applied.kind {
310 EditKind::Insert { text } => {
311 self.rope.insert(a, text);
312 }
313 EditKind::Delete => {
314 self.rope.remove(a..b);
315 }
316 EditKind::Replace { text } => {
317 self.rope.remove(a..b);
318 self.rope.insert(a, text);
319 }
320 }
321 self.modified = true;
322 Ok(entry)
323 }
324
325 #[must_use]
326 pub fn to_string(&self) -> String {
327 self.rope.to_string()
328 }
329}
330
331#[derive(Debug, Default, Clone)]
333pub struct BufferSet {
334 buffers: HashMap<BufferId, Buffer>,
335 next_id: u64,
336}
337
338impl BufferSet {
339 #[must_use]
340 pub fn new() -> Self {
341 Self::default()
342 }
343
344 pub fn next_id(&mut self) -> BufferId {
345 self.next_id += 1;
346 BufferId(self.next_id)
347 }
348
349 #[must_use]
364 pub fn find_by_path(&self, path: impl AsRef<Path>) -> Option<BufferId> {
365 let want = path.as_ref();
366 let want_canon = std::fs::canonicalize(want).ok();
367 self.buffers
368 .iter()
369 .find(|(_, b)| {
370 let Some(have) = b.path.as_deref() else {
371 return false;
372 };
373 match (&want_canon, std::fs::canonicalize(have).ok()) {
374 (Some(a), Some(b)) => *a == b,
375 _ => have == want,
378 }
379 })
380 .map(|(id, _)| *id)
381 }
382
383 pub fn open(&mut self, path: impl AsRef<Path>) -> Result<BufferId, BufferError> {
399 let path = path.as_ref();
400 if let Some(existing) = self.find_by_path(path) {
401 return Ok(existing);
402 }
403 let id = self.next_id();
404 let buf = Buffer::open(id, path)?;
405 self.buffers.insert(id, buf);
406 Ok(id)
407 }
408
409 pub fn scratch(&mut self, src: &str) -> BufferId {
410 let id = self.next_id();
411 self.buffers.insert(id, Buffer::from_str(id, src));
412 id
413 }
414
415 pub fn close(&mut self, id: BufferId) -> Option<Buffer> {
427 self.buffers.remove(&id)
428 }
429
430 #[must_use]
431 pub fn get(&self, id: BufferId) -> Option<&Buffer> {
432 self.buffers.get(&id)
433 }
434
435 pub fn get_mut(&mut self, id: BufferId) -> Option<&mut Buffer> {
436 self.buffers.get_mut(&id)
437 }
438
439 #[must_use]
440 pub fn ids(&self) -> Vec<BufferId> {
441 let mut v: Vec<_> = self.buffers.keys().copied().collect();
442 v.sort();
443 v
444 }
445}
446
447#[derive(Debug, Clone, Serialize, Deserialize)]
448pub struct BufferSummary {
449 pub id: BufferId,
450 pub path: Option<PathBuf>,
451 pub line_count: u32,
452 pub modified: bool,
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458 use escriba_core::{Edit, Position, Range};
459
460 fn buf(src: &str) -> Buffer {
461 Buffer::from_str(BufferId(1), src)
462 }
463
464 #[test]
465 fn empty_has_one_line() {
466 let b = Buffer::empty(BufferId(1));
467 assert_eq!(b.line_count(), 1);
468 }
469
470 #[test]
471 fn line_counts() {
472 let b = buf("a\nb\nc\n");
473 assert_eq!(b.line_count(), 4); }
475
476 #[test]
477 fn position_char_round_trip() {
478 let b = buf("hello\nworld\n");
479 let p = Position::new(1, 3);
480 let c = b.position_to_char(p).unwrap();
481 assert_eq!(b.char_to_position(c), p);
482 }
483
484 #[test]
485 fn insert_then_undo() {
486 let mut b = buf("hello");
487 let e = Edit::insert(Position::new(0, 5), " world");
488 b.apply(&e).unwrap();
489 assert_eq!(b.to_string(), "hello world");
490 b.undo().unwrap();
491 assert_eq!(b.to_string(), "hello");
492 }
493
494 #[test]
495 fn delete_then_redo() {
496 let mut b = buf("hello world");
497 let e = Edit::delete(Range::new(Position::new(0, 5), Position::new(0, 11)));
498 b.apply(&e).unwrap();
499 assert_eq!(b.to_string(), "hello");
500 b.undo().unwrap();
501 assert_eq!(b.to_string(), "hello world");
502 b.redo().unwrap();
503 assert_eq!(b.to_string(), "hello");
504 }
505
506 #[test]
507 fn replace_is_delete_plus_insert() {
508 let mut b = buf("hello world");
509 let e = Edit::replace(
510 Range::new(Position::new(0, 6), Position::new(0, 11)),
511 "tatara",
512 );
513 b.apply(&e).unwrap();
514 assert_eq!(b.to_string(), "hello tatara");
515 b.undo().unwrap();
516 assert_eq!(b.to_string(), "hello world");
517 }
518
519 #[test]
520 fn clamp_constrains_position() {
521 let b = buf("ab\ncd");
522 assert_eq!(b.line_count(), 2);
523 assert_eq!(b.clamp(Position::new(0, 99)), Position::new(0, 2));
524 assert_eq!(b.clamp(Position::new(99, 0)), Position::new(1, 0));
525 }
526
527 #[test]
528 fn slice_returns_text() {
529 let b = buf("hello world");
530 let s = b
531 .slice(Range::new(Position::new(0, 6), Position::new(0, 11)))
532 .unwrap();
533 assert_eq!(s, "world");
534 }
535
536 #[test]
537 fn save_round_trip() {
538 let dir = tempfile::tempdir().unwrap();
539 let path = dir.path().join("demo.txt");
540 let mut b = Buffer::from_str(BufferId(1), "hello\n");
541 b.save_as(&path).unwrap();
542 let b2 = Buffer::open(BufferId(2), &path).unwrap();
543 assert_eq!(b2.to_string(), "hello\n");
544 }
545
546 #[test]
547 fn buffer_set_tracks_ids() {
548 let mut set = BufferSet::new();
549 let a = set.scratch("one");
550 let b = set.scratch("two");
551 assert_ne!(a, b);
552 assert_eq!(set.ids().len(), 2);
553 assert_eq!(set.get(a).unwrap().to_string(), "one");
554 }
555}
556
557#[cfg(test)]
558mod text_rev_tests {
559 use super::*;
560 use escriba_core::{Edit, Position, Range};
561
562 fn buf(src: &str) -> Buffer {
563 Buffer::from_str(BufferId(0), src)
564 }
565
566 #[test]
567 fn a_fresh_buffer_starts_at_revision_zero() {
568 assert_eq!(buf("hello").text_rev(), TextRev(0));
569 }
570
571 #[test]
572 fn an_applied_edit_advances_the_revision() {
573 let mut b = buf("hello");
574 let before = b.text_rev();
575 b.apply(&Edit::insert(Position::new(0, 0), "X".to_string()))
576 .expect("insert applies");
577 assert_ne!(
578 b.text_rev(),
579 before,
580 "a text change must expire old offsets"
581 );
582 }
583
584 #[test]
585 fn each_edit_advances_it_again() {
586 let mut b = buf("hello");
587 let mut seen = vec![b.text_rev()];
588 for _ in 0..3 {
589 b.apply(&Edit::insert(Position::new(0, 0), "X".to_string()))
590 .expect("insert applies");
591 let now = b.text_rev();
592 assert!(!seen.contains(&now), "revisions must not repeat: {now:?}");
593 seen.push(now);
594 }
595 }
596
597 #[test]
598 fn a_rejected_edit_does_not_advance_the_revision() {
599 let mut b = buf("hello");
602 let before = b.text_rev();
603 let out_of_range = Range {
604 start: Position::new(99, 0),
605 end: Position::new(99, 1),
606 };
607 assert!(
608 b.apply(&Edit::delete(out_of_range)).is_err(),
609 "the edit must fail"
610 );
611 assert_eq!(b.text_rev(), before, "a failed edit changed no text");
612 }
613
614 #[test]
615 fn reading_the_buffer_does_not_advance_the_revision() {
616 let b = buf("hello");
619 let before = b.text_rev();
620 let _ = b.to_string();
621 let _ = b.text_rev();
622 assert_eq!(b.text_rev(), before);
623 }
624}
625
626#[cfg(test)]
627mod undo_rev_tests {
628 use super::*;
629 use escriba_core::{Edit, Position};
630
631 #[test]
632 fn undo_and_redo_advance_the_revision() {
633 let mut b = Buffer::from_str(BufferId(0), "hello");
637 b.apply(&Edit::insert(Position::new(0, 0), "X".to_string()))
638 .expect("insert applies");
639 let after_edit = b.text_rev();
640
641 b.undo().expect("undo applies");
642 assert_ne!(b.text_rev(), after_edit, "undo must advance the revision");
643 let after_undo = b.text_rev();
644
645 b.redo().expect("redo applies");
646 assert_ne!(b.text_rev(), after_undo, "redo must advance it again");
647 }
648
649 #[test]
650 fn a_rejected_undo_does_not_advance_the_revision() {
651 let mut b = Buffer::from_str(BufferId(0), "hello");
652 let before = b.text_rev();
653 assert!(b.undo().is_err(), "nothing to undo");
654 assert_eq!(b.text_rev(), before, "a failed undo changed no text");
655 }
656}