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)]
15pub struct Buffer {
16 pub id: BufferId,
17 pub path: Option<PathBuf>,
18 pub rope: Rope,
19 pub modified: bool,
20 pub encoding: Encoding,
21 pub line_ending: LineEnding,
22 pub undo: UndoTree,
23}
24
25impl Buffer {
26 #[must_use]
27 pub fn empty(id: BufferId) -> Self {
28 Self {
29 id,
30 path: None,
31 rope: Rope::new(),
32 modified: false,
33 encoding: Encoding::default(),
34 line_ending: LineEnding::default(),
35 undo: UndoTree::new(),
36 }
37 }
38
39 #[must_use]
40 pub fn from_str(id: BufferId, src: &str) -> Self {
41 let line_ending = LineEnding::detect(src);
42 Self {
43 id,
44 path: None,
45 rope: Rope::from_str(src),
46 modified: false,
47 encoding: Encoding::default(),
48 line_ending,
49 undo: UndoTree::new(),
50 }
51 }
52
53 pub fn open(id: BufferId, path: impl AsRef<Path>) -> Result<Self, BufferError> {
54 let path = path.as_ref().to_path_buf();
55 let src = if path.exists() {
56 std::fs::read_to_string(&path)?
57 } else {
58 String::new()
59 };
60 let line_ending = LineEnding::detect(&src);
61 Ok(Self {
62 id,
63 path: Some(path),
64 rope: Rope::from_str(&src),
65 modified: false,
66 encoding: Encoding::default(),
67 line_ending,
68 undo: UndoTree::new(),
69 })
70 }
71
72 pub fn save(&mut self) -> Result<(), BufferError> {
73 let path = self.path.clone().ok_or(BufferError::NoPath)?;
74 let mut src = String::new();
75 for line in self.rope.lines() {
76 src.push_str(&line.to_string());
77 }
78 std::fs::write(&path, src)?;
79 self.modified = false;
80 Ok(())
81 }
82
83 pub fn save_as(&mut self, path: impl AsRef<Path>) -> Result<(), BufferError> {
84 self.path = Some(path.as_ref().to_path_buf());
85 self.save()
86 }
87
88 #[must_use]
91 pub fn line_count(&self) -> u32 {
92 u32::try_from(self.rope.len_lines()).unwrap_or(u32::MAX)
93 }
94
95 #[must_use]
96 pub fn byte_count(&self) -> usize {
97 self.rope.len_bytes()
98 }
99
100 #[must_use]
101 pub fn char_count(&self) -> usize {
102 self.rope.len_chars()
103 }
104
105 pub fn line(&self, n: u32) -> Option<String> {
106 let n = n as usize;
107 if n >= self.rope.len_lines() {
108 return None;
109 }
110 Some(self.rope.line(n).to_string())
111 }
112
113 pub fn line_len_chars(&self, n: u32) -> u32 {
114 let n = n as usize;
115 if n >= self.rope.len_lines() {
116 return 0;
117 }
118 let line = self.rope.line(n);
119 let mut len = line.len_chars();
120 if line.chars().last().is_some_and(|c| c == '\n' || c == '\r') {
122 len = len.saturating_sub(1);
123 }
124 u32::try_from(len).unwrap_or(u32::MAX)
125 }
126
127 #[must_use]
129 pub fn clamp(&self, pos: Position) -> Position {
130 let line = pos.line.min(self.line_count().saturating_sub(1));
131 let col = pos.column.min(self.line_len_chars(line));
132 Position::new(line, col)
133 }
134
135 pub fn position_to_char(&self, pos: Position) -> Result<usize, BufferError> {
136 let line = pos.line as usize;
137 if line >= self.rope.len_lines() {
138 return Err(BufferError::InvalidPosition {
139 line: pos.line,
140 column: pos.column,
141 total_lines: self.line_count(),
142 });
143 }
144 let line_start = self.rope.line_to_char(line);
145 let line_slice = self.rope.line(line);
146 let max_col = line_slice.len_chars();
147 let col = (pos.column as usize).min(max_col);
148 Ok(line_start + col)
149 }
150
151 #[must_use]
152 pub fn char_to_position(&self, ch: usize) -> Position {
153 let line = self.rope.char_to_line(ch.min(self.rope.len_chars()));
154 let line_start = self.rope.line_to_char(line);
155 let col = ch.saturating_sub(line_start);
156 Position::new(
157 u32::try_from(line).unwrap_or(u32::MAX),
158 u32::try_from(col).unwrap_or(u32::MAX),
159 )
160 }
161
162 pub fn slice(&self, range: Range) -> Result<String, BufferError> {
163 let r = range.normalized();
164 let a = self.position_to_char(r.start)?;
165 let b = self.position_to_char(r.end)?;
166 Ok(self.rope.slice(a..b).to_string())
167 }
168
169 pub fn apply(&mut self, edit: &Edit) -> Result<UndoEntry, BufferError> {
172 let range = edit.range.normalized();
173 let start_char = self.position_to_char(range.start)?;
174 let end_char = self.position_to_char(range.end)?;
175 let previous_text = self.rope.slice(start_char..end_char).to_string();
176
177 let (inserted_len_chars, reverse_kind) = match &edit.kind {
178 EditKind::Insert { text } => {
179 self.rope.insert(start_char, text);
180 (text.chars().count(), EditKind::Delete)
181 }
182 EditKind::Delete => {
183 self.rope.remove(start_char..end_char);
184 (
185 0usize,
186 EditKind::Insert {
187 text: previous_text.clone(),
188 },
189 )
190 }
191 EditKind::Replace { text } => {
192 self.rope.remove(start_char..end_char);
193 self.rope.insert(start_char, text);
194 (
195 text.chars().count(),
196 EditKind::Replace {
197 text: previous_text.clone(),
198 },
199 )
200 }
201 };
202 self.modified = true;
203
204 let reverse_end_char = start_char + inserted_len_chars;
206 let reverse_range = Range::new(
207 self.char_to_position(start_char),
208 self.char_to_position(reverse_end_char),
209 );
210 let reverse_edit = Edit {
211 range: reverse_range,
212 kind: reverse_kind,
213 };
214 let entry = UndoEntry {
215 applied: edit.clone(),
216 reverse: reverse_edit,
217 };
218 self.undo.push(entry.clone());
219 Ok(entry)
220 }
221
222 pub fn undo(&mut self) -> Result<UndoEntry, BufferError> {
223 let entry = self.undo.pop_undo().ok_or(BufferError::NothingToUndo)?;
224 let r = entry.reverse.range.normalized();
226 let a = self.position_to_char(r.start)?;
227 let b = self.position_to_char(r.end)?;
228 match &entry.reverse.kind {
229 EditKind::Insert { text } => {
230 self.rope.insert(a, text);
231 }
232 EditKind::Delete => {
233 self.rope.remove(a..b);
234 }
235 EditKind::Replace { text } => {
236 self.rope.remove(a..b);
237 self.rope.insert(a, text);
238 }
239 }
240 self.modified = true;
241 Ok(entry)
242 }
243
244 pub fn redo(&mut self) -> Result<UndoEntry, BufferError> {
245 let entry = self.undo.pop_redo().ok_or(BufferError::NothingToRedo)?;
246 let r = entry.applied.range.normalized();
247 let a = self.position_to_char(r.start)?;
248 let b = self.position_to_char(r.end)?;
249 match &entry.applied.kind {
250 EditKind::Insert { text } => {
251 self.rope.insert(a, text);
252 }
253 EditKind::Delete => {
254 self.rope.remove(a..b);
255 }
256 EditKind::Replace { text } => {
257 self.rope.remove(a..b);
258 self.rope.insert(a, text);
259 }
260 }
261 self.modified = true;
262 Ok(entry)
263 }
264
265 #[must_use]
266 pub fn to_string(&self) -> String {
267 self.rope.to_string()
268 }
269}
270
271#[derive(Debug, Default, Clone)]
273pub struct BufferSet {
274 buffers: HashMap<BufferId, Buffer>,
275 next_id: u64,
276}
277
278impl BufferSet {
279 #[must_use]
280 pub fn new() -> Self {
281 Self::default()
282 }
283
284 pub fn next_id(&mut self) -> BufferId {
285 self.next_id += 1;
286 BufferId(self.next_id)
287 }
288
289 pub fn open(&mut self, path: impl AsRef<Path>) -> Result<BufferId, BufferError> {
290 let id = self.next_id();
291 let buf = Buffer::open(id, path)?;
292 self.buffers.insert(id, buf);
293 Ok(id)
294 }
295
296 pub fn scratch(&mut self, src: &str) -> BufferId {
297 let id = self.next_id();
298 self.buffers.insert(id, Buffer::from_str(id, src));
299 id
300 }
301
302 #[must_use]
303 pub fn get(&self, id: BufferId) -> Option<&Buffer> {
304 self.buffers.get(&id)
305 }
306
307 pub fn get_mut(&mut self, id: BufferId) -> Option<&mut Buffer> {
308 self.buffers.get_mut(&id)
309 }
310
311 #[must_use]
312 pub fn ids(&self) -> Vec<BufferId> {
313 let mut v: Vec<_> = self.buffers.keys().copied().collect();
314 v.sort();
315 v
316 }
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize)]
320pub struct BufferSummary {
321 pub id: BufferId,
322 pub path: Option<PathBuf>,
323 pub line_count: u32,
324 pub modified: bool,
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330 use escriba_core::{Edit, Position, Range};
331
332 fn buf(src: &str) -> Buffer {
333 Buffer::from_str(BufferId(1), src)
334 }
335
336 #[test]
337 fn empty_has_one_line() {
338 let b = Buffer::empty(BufferId(1));
339 assert_eq!(b.line_count(), 1);
340 }
341
342 #[test]
343 fn line_counts() {
344 let b = buf("a\nb\nc\n");
345 assert_eq!(b.line_count(), 4); }
347
348 #[test]
349 fn position_char_round_trip() {
350 let b = buf("hello\nworld\n");
351 let p = Position::new(1, 3);
352 let c = b.position_to_char(p).unwrap();
353 assert_eq!(b.char_to_position(c), p);
354 }
355
356 #[test]
357 fn insert_then_undo() {
358 let mut b = buf("hello");
359 let e = Edit::insert(Position::new(0, 5), " world");
360 b.apply(&e).unwrap();
361 assert_eq!(b.to_string(), "hello world");
362 b.undo().unwrap();
363 assert_eq!(b.to_string(), "hello");
364 }
365
366 #[test]
367 fn delete_then_redo() {
368 let mut b = buf("hello world");
369 let e = Edit::delete(Range::new(Position::new(0, 5), Position::new(0, 11)));
370 b.apply(&e).unwrap();
371 assert_eq!(b.to_string(), "hello");
372 b.undo().unwrap();
373 assert_eq!(b.to_string(), "hello world");
374 b.redo().unwrap();
375 assert_eq!(b.to_string(), "hello");
376 }
377
378 #[test]
379 fn replace_is_delete_plus_insert() {
380 let mut b = buf("hello world");
381 let e = Edit::replace(
382 Range::new(Position::new(0, 6), Position::new(0, 11)),
383 "tatara",
384 );
385 b.apply(&e).unwrap();
386 assert_eq!(b.to_string(), "hello tatara");
387 b.undo().unwrap();
388 assert_eq!(b.to_string(), "hello world");
389 }
390
391 #[test]
392 fn clamp_constrains_position() {
393 let b = buf("ab\ncd");
394 assert_eq!(b.line_count(), 2);
395 assert_eq!(b.clamp(Position::new(0, 99)), Position::new(0, 2));
396 assert_eq!(b.clamp(Position::new(99, 0)), Position::new(1, 0));
397 }
398
399 #[test]
400 fn slice_returns_text() {
401 let b = buf("hello world");
402 let s = b
403 .slice(Range::new(Position::new(0, 6), Position::new(0, 11)))
404 .unwrap();
405 assert_eq!(s, "world");
406 }
407
408 #[test]
409 fn save_round_trip() {
410 let dir = tempfile::tempdir().unwrap();
411 let path = dir.path().join("demo.txt");
412 let mut b = Buffer::from_str(BufferId(1), "hello\n");
413 b.save_as(&path).unwrap();
414 let b2 = Buffer::open(BufferId(2), &path).unwrap();
415 assert_eq!(b2.to_string(), "hello\n");
416 }
417
418 #[test]
419 fn buffer_set_tracks_ids() {
420 let mut set = BufferSet::new();
421 let a = set.scratch("one");
422 let b = set.scratch("two");
423 assert_ne!(a, b);
424 assert_eq!(set.ids().len(), 2);
425 assert_eq!(set.get(a).unwrap().to_string(), "one");
426 }
427}