1use std::{
10 fs,
11 ops::Range,
12 path::{Path, PathBuf},
13};
14
15use std::sync::Arc;
16
17use ropey::{LineType, Rope};
18use serde::{Deserialize, Serialize};
19
20use crate::editor::history::{apply_changeset, BufferOp, ChangeKind, ChangeSet, History};
21use crate::editor::position::Position;
22use crate::editor::selection::Selection;
23use crate::prelude::*;
24use crate::settings::adapters;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
27pub struct BufferId(u64);
28
29impl BufferId {
30 pub fn new() -> Self {
31 use std::sync::atomic::{AtomicU64, Ordering};
32 static COUNTER: AtomicU64 = AtomicU64::new(1);
33 Self(COUNTER.fetch_add(1, Ordering::Relaxed))
34 }
35}
36
37impl Default for BufferId {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)]
44pub struct Version(u64);
45
46impl Version {
47 pub fn new() -> Self {
48 Self(0)
49 }
50
51 pub fn value(&self) -> u64 {
52 self.0
53 }
54
55 #[cfg(test)]
57 pub(crate) fn from_raw(v: u64) -> Self {
58 Self(v)
59 }
60
61 fn increment(&mut self) {
62 self.0 += 1;
63 }
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct SerializableSnapshot {
68 lines: Vec<String>,
69 cursor: Position,
70 #[serde(default)]
71 selection: Option<Selection>,
72 dirty: bool,
73 version: Version,
74}
75
76impl From<&Buffer> for SerializableSnapshot {
77 fn from(buf: &Buffer) -> Self {
78 let lines = buf.lines();
79 SerializableSnapshot {
80 lines,
81 cursor: buf.cursor,
82 selection: buf.selection,
83 dirty: buf.dirty,
84 version: buf.version,
85 }
86 }
87}
88
89impl SerializableSnapshot {
90 pub fn lines(&self) -> &[String] {
91 &self.lines
92 }
93
94 pub fn cursor(&self) -> Position {
95 self.cursor
96 }
97
98 pub fn selection(&self) -> Option<Selection> {
99 self.selection
100 }
101
102 pub fn dirty(&self) -> bool {
103 self.dirty
104 }
105
106 pub fn version(&self) -> Version {
107 self.version
108 }
109}
110
111#[derive(Debug, Clone)]
112pub struct BufferSnapshot {
113 pub text: Rope,
114 pub version: Version,
115}
116
117impl BufferSnapshot {
118 pub fn lines(&self) -> Vec<String> {
119 let line_type = LineType::LF;
120 (0..self.text.len_lines(line_type))
121 .map(|i| {
122 let mut line_text = self.text.line(i, line_type).to_string();
123 if line_text.ends_with('\n') {
124 line_text.pop();
125 }
126 line_text
127 })
128 .collect()
129 }
130
131 pub fn line_count(&self) -> usize {
132 self.text.len_lines(LineType::LF)
133 }
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct Marker {
138 pub line: usize,
139 pub label: String,
140}
141
142#[derive(Debug)]
145struct Transaction {
146 ops: Vec<BufferOp>,
147 kind: ChangeKind,
148 before_cursor: Position,
149 before_selection: Option<Selection>,
150}
151
152#[derive(Debug)]
153pub struct Buffer {
154 id: BufferId,
155 version: Version,
156 text: Rope,
157 pub path: Option<PathBuf>,
158 cursor: Position,
159 selection: Option<Selection>,
160 pub scroll: usize,
161 pub scroll_x: usize,
162 dirty: bool,
163 history: History,
164 pub markers: Vec<Marker>,
165 active_tx: Option<Transaction>,
168 pub file_hash: Option<u64>,
170 pub file_mtime: Option<u64>,
172 pub file_size: Option<u64>,
174 pub external_modification_detected: bool,
176 pub is_saving: bool,
178 cached_lines: Option<Arc<Vec<String>>>,
180}
181
182impl Buffer {
183 pub fn new() -> Self {
184 Self {
185 id: BufferId::new(),
186 version: Version::new(),
187 text: Rope::new(),
188 path: None,
189 cursor: Position::default(),
190 selection: None,
191 scroll: 0,
192 scroll_x: 0,
193 dirty: false,
194 history: History::new(),
195 markers: Vec::new(),
196 active_tx: None,
197 file_hash: None,
198 file_mtime: None,
199 file_size: None,
200 external_modification_detected: false,
201 is_saving: false,
202 cached_lines: None,
203 }
204 }
205
206 pub fn from_text(text: &str) -> Self {
207 let mut buf = Self::new();
208 buf.text = Rope::from_str(text);
209 let line_count = buf.text.len_lines(LineType::LF);
210 if line_count > 0 {
211 let last_line_idx = line_count - 1;
212 let last_line_len = buf.text.line(last_line_idx, LineType::LF).len_chars();
213 buf.cursor = Position::new(last_line_idx, last_line_len);
214 }
215 buf
216 }
217
218 #[cfg(test)]
221 pub(crate) fn from_text_no_merge(text: &str) -> Self {
222 let mut buf = Self::from_text(text);
223 buf.history = History::with_threshold(0);
224 buf
225 }
226
227 pub fn from_lines(lines: Vec<String>, path: Option<PathBuf>) -> Self {
228 let text = if lines.is_empty() {
229 String::new()
230 } else {
231 lines.join("\n")
232 };
233 let mut buf = Self::new();
234 buf.text = Rope::from_str(&text);
235 buf.path = path;
236 buf.cursor = Position::new(0, 0);
237 buf
238 }
239
240 pub fn open(path: &Path) -> Result<Self> {
241 let content = fs::read_to_string(path)?;
242 let mut buf = Self::new();
243 buf.text = Rope::from_str(&content);
244 buf.path = Some(path.to_path_buf());
245 buf.normalize_cursor();
246 Ok(buf)
247 }
248
249 #[allow(clippy::too_many_arguments)]
250 pub fn restore(
251 path: PathBuf,
252 lines: Vec<String>,
253 selection: Option<Selection>,
254 scroll: usize,
255 dirty: bool,
256 markers: Vec<Marker>,
257 undo_stack: Vec<SerializableSnapshot>,
258 redo_stack: Vec<SerializableSnapshot>,
259 ) -> Self {
260 let text = if lines.is_empty() {
261 Rope::new()
262 } else {
263 Rope::from_str(&lines.join("\n"))
264 };
265 let cursor = selection.as_ref().map(|s| s.active).unwrap_or_default();
266 let version = Version::new();
267 let mut history = History::new();
268 history.restore_from_snapshots(undo_stack, redo_stack, version, lines.clone());
269 let file_hash = Self::compute_hash(&text);
270 let file_metadata = Self::get_file_metadata(&path);
271 let file_mtime = file_metadata.as_ref().map(|(m, _)| *m);
272 let file_size = file_metadata.map(|(_, s)| s);
273 let mut buf = Self {
274 id: BufferId::new(),
275 version,
276 text,
277 path: Some(path),
278 cursor,
279 selection,
280 scroll,
281 scroll_x: 0,
282 dirty,
283 history,
284 markers,
285 active_tx: None,
286 file_hash,
287 file_mtime,
288 file_size,
289 external_modification_detected: false,
290 is_saving: false,
291 cached_lines: None,
292 };
293 let sel = buf.selection.take();
297 if let Some(s) = sel {
298 buf.set_selection(Some(Selection {
299 anchor: buf.normalize_position(s.anchor),
300 active: buf.normalize_position(s.active),
301 }));
302 } else {
303 buf.normalize_cursor();
304 }
305 buf
306 }
307
308 pub fn id(&self) -> BufferId {
309 self.id
310 }
311
312 pub fn version(&self) -> Version {
313 self.version
314 }
315
316 pub fn snapshot(&self) -> BufferSnapshot {
317 BufferSnapshot {
318 text: self.text.clone(),
319 version: self.version,
320 }
321 }
322
323 fn line_type(&self) -> LineType {
324 LineType::LF
325 }
326
327 fn pos_to_byte(&self, pos: Position) -> usize {
328 let line_count = self.text.len_lines(self.line_type());
329 if pos.line >= line_count {
330 if pos.line == line_count && pos.column == 0 && line_count > 0 {
331 let last_line_idx = line_count - 1;
332 let last_line = self.text.line(last_line_idx, self.line_type());
333 if last_line.chars().last() == Some('\n') {
334 let last_line_start =
335 self.text.line_to_byte_idx(last_line_idx, self.line_type());
336 return last_line_start + last_line.len() - 1;
337 }
338 }
339 return self.text.len();
340 }
341 let line_start_byte = self.text.line_to_byte_idx(pos.line, self.line_type());
342 let line = self.text.line(pos.line, self.line_type());
343
344 let mut char_count = 0;
345 let mut byte_offset = 0;
346 for ch in line.chars() {
347 if char_count >= pos.column {
348 break;
349 }
350 byte_offset += ch.len_utf8();
351 char_count += 1;
352 }
353 if pos.column > char_count {
354 byte_offset = line.len();
355 }
356 line_start_byte + byte_offset
357 }
358
359 fn byte_to_pos(&self, byte_idx: usize) -> Position {
360 let clamped_byte_idx = byte_idx.min(self.text.len());
361 let line_count = self.text.len_lines(self.line_type());
362
363 if clamped_byte_idx == self.text.len() {
364 if line_count == 0 {
365 return Position::new(0, 0);
366 }
367 let last_line_idx = line_count - 1;
368 let last_line = self.text.line(last_line_idx, self.line_type());
369 let last_line_len = last_line.len_chars();
370 let has_trailing_nl = last_line.chars().last() == Some('\n');
371 if has_trailing_nl {
372 return Position::new(last_line_idx + 1, 0);
373 }
374 return Position::new(last_line_idx, last_line_len);
375 }
376
377 let byte_idx = clamped_byte_idx;
378 let line_idx = self.text.byte_to_line_idx(byte_idx, self.line_type());
379 let line_start_byte = self.text.line_to_byte_idx(line_idx, self.line_type());
380 let char_idx = self.text.byte_to_char_idx(byte_idx);
381 let line_start_char = self.text.byte_to_char_idx(line_start_byte);
382 let line = self.text.line(line_idx, self.line_type());
383 let line_len_chars = line.len_chars();
384 let line_has_trailing_nl = line.chars().last() == Some('\n');
385
386 let column = char_idx - line_start_char;
387 if column >= line_len_chars && line_has_trailing_nl {
388 return Position::new(line_idx + 1, 0);
389 }
390 Position::new(line_idx, column)
391 }
392
393 fn cursor_to_byte(&self) -> usize {
394 self.pos_to_byte(self.cursor)
395 }
396
397 pub fn pos_to_char(&self, pos: Position) -> usize {
399 let line_start_byte = self.text.line_to_byte_idx(pos.line, self.line_type());
400 let line_start_char = self.text.byte_to_char_idx(line_start_byte);
401 line_start_char + pos.column
402 }
403
404 pub fn char_to_pos(&self, char_idx: usize) -> Position {
406 let byte_idx = self.text.char_to_byte_idx(char_idx).min(self.text.len());
407 self.byte_to_pos(byte_idx)
408 }
409
410 pub fn cursor(&self) -> Position {
411 self.cursor
412 }
413
414 pub fn selection(&self) -> Option<Selection> {
415 self.selection
416 }
417
418 pub fn set_cursor(&mut self, pos: Position) {
419 self.selection = None;
420 self.cursor = self.normalize_position(pos);
421 }
422
423 pub fn set_selection(&mut self, selection: Option<Selection>) {
424 self.selection = selection.map(|sel| Selection {
425 anchor: self.normalize_position(sel.anchor),
426 active: self.normalize_position(sel.active),
427 });
428 if let Some(ref sel) = self.selection {
429 self.cursor = sel.active;
430 }
431 }
432
433 pub fn normalize_position(&self, pos: Position) -> Position {
435 let line_count = self.text.len_lines(self.line_type());
436 if line_count == 0 {
437 return Position::new(0, 0);
438 }
439 let line = pos.line.min(line_count - 1);
440 let line_len = self.line_display_len(line);
441 Position::new(line, pos.column.min(line_len))
442 }
443
444 pub fn normalize_cursor(&mut self) {
445 self.cursor = self.normalize_position(self.cursor);
446 }
447
448 fn line_display_len(&self, line_idx: usize) -> usize {
450 let line = self.text.line(line_idx, self.line_type());
451 let has_trailing_nl = line.chars().last() == Some('\n');
452 line.len_chars().saturating_sub(has_trailing_nl as usize)
453 }
454
455 pub fn line_count(&self) -> usize {
456 self.text.len_lines(self.line_type()).max(1)
457 }
458
459 pub fn current_line(&self) -> usize {
460 self.cursor.line
461 }
462
463 pub fn char_count(&self) -> usize {
464 self.text.len_chars()
465 }
466
467 pub fn line(&self, line_idx: usize) -> Option<String> {
468 let actual_line_count = self.text.len_lines(self.line_type());
469 if line_idx >= actual_line_count {
470 return None;
471 }
472 let mut line_text = self.text.line(line_idx, self.line_type()).to_string();
473 if line_text.ends_with('\n') {
474 line_text.pop();
475 }
476 if line_text.ends_with('\r') {
477 line_text.pop();
478 }
479 Some(line_text)
480 }
481
482 pub fn lines(&self) -> Vec<String> {
483 (0..self.text.len_lines(self.line_type()))
484 .map(|i| {
485 let mut s: String = self.text.line(i, self.line_type()).into();
486 if s.ends_with('\n') {
487 s.pop();
488 }
489 if s.ends_with('\r') {
490 s.pop();
491 }
492 s
493 })
494 .collect()
495 }
496
497 pub fn lines_arc(&mut self) -> Arc<Vec<String>> {
501 if let Some(ref cached) = self.cached_lines {
502 return Arc::clone(cached);
503 }
504 let v = self.lines();
505 let arc = Arc::new(v);
506 self.cached_lines = Some(arc.clone());
507 arc
508 }
509
510 pub fn into_lines(self) -> Vec<String> {
514 if let Some(arc) = self.cached_lines {
515 match Arc::try_unwrap(arc) {
516 Ok(v) => v,
517 Err(arc) => (*arc).clone(),
518 }
519 } else {
520 self.lines()
521 }
522 }
523
524 pub fn is_empty(&self) -> bool {
525 self.text.len_chars() == 0
526 }
527
528 pub fn is_dirty(&self) -> bool {
529 self.dirty
530 }
531
532 fn increment_version(&mut self) {
533 self.version.increment();
534 self.dirty = true;
535 self.file_hash = None;
536 self.file_mtime = None;
537 self.file_size = None;
538 self.cached_lines = None;
539 }
540
541 pub fn can_undo(&self) -> bool {
542 self.history.can_undo()
543 }
544
545 pub fn can_redo(&self) -> bool {
546 self.history.can_redo()
547 }
548
549 pub fn history_len(&self) -> usize {
550 self.history.len()
551 }
552
553 pub fn history_position(&self) -> usize {
554 self.history.current_position()
555 }
556
557 pub fn apply_change(&mut self, change: ChangeSet, kind: ChangeKind) {
558 if change.is_empty() {
559 return;
560 }
561
562 if let Some(tx) = &mut self.active_tx {
565 tx.ops.extend(change.ops);
566 return;
567 }
568
569 let merged = matches!(kind, ChangeKind::InsertText | ChangeKind::DeleteText)
574 && self.history.try_merge(change.clone(), kind, self.version);
575
576 if !merged {
577 self.history.push(change, kind, self.version);
578 }
579 }
580
581 pub fn begin_transaction(&mut self, kind: ChangeKind) {
589 debug_assert!(
590 self.active_tx.is_none(),
591 "begin_transaction called while a transaction is already active; \
592 nested transactions are not supported"
593 );
594 if self.active_tx.is_some() {
595 return;
596 }
597 self.active_tx = Some(Transaction {
598 ops: Vec::new(),
599 kind,
600 before_cursor: self.cursor,
601 before_selection: self.selection,
602 });
603 }
604
605 pub fn end_transaction(&mut self) {
618 let tx = match self.active_tx.take() {
619 Some(tx) => tx,
620 None => return,
621 };
622 if tx.ops.is_empty() {
623 return;
624 }
625 let change = ChangeSet::new(
626 tx.ops,
627 tx.before_cursor,
628 self.cursor,
629 tx.before_selection,
630 self.selection,
631 );
632 self.history.push(change, tx.kind, self.version);
635 self.increment_version();
638 }
639
640 pub fn abort_transaction(&mut self) {
648 self.active_tx = None;
649 }
650
651 pub fn in_transaction(&self) -> bool {
653 self.active_tx.is_some()
654 }
655
656 pub fn insert(&mut self, text: &str) {
657 if let Some(sel) = self.selection {
659 let start = sel.min();
660 let end = sel.max();
661 let before_cursor = self.cursor;
662 let before_selection = self.selection;
663
664 let start_byte = self.pos_to_byte(start);
665 let end_byte = self.pos_to_byte(end).min(self.text.len());
666 let old_text: String = self.text.slice(start_byte..end_byte).into();
667
668 self.text.remove(start_byte..end_byte);
669 self.text.insert(start_byte, text);
670
671 let new_char_idx = self.text.byte_to_char_idx(start_byte) + text.chars().count();
672 let after_cursor = self.byte_to_pos(
673 self.text
674 .char_to_byte_idx(new_char_idx)
675 .min(self.text.len()),
676 );
677 self.cursor = after_cursor;
678 self.selection = None;
679
680 let op = BufferOp::Replace {
681 range: start..end,
682 text: text.to_string(),
683 old_text,
684 end_position: after_cursor,
685 };
686 let change = ChangeSet::new(
687 vec![op],
688 before_cursor,
689 after_cursor,
690 before_selection,
691 None,
692 );
693 self.apply_change(change, ChangeKind::Replace);
694 self.increment_version();
695 return;
696 }
697
698 let before_cursor = self.cursor;
699 let before_selection = self.selection;
700
701 let byte_idx = self.cursor_to_byte();
702 let old_char_idx = self.text.byte_to_char_idx(byte_idx);
703 self.text.insert(byte_idx, text);
704
705 let new_char_idx = old_char_idx + text.chars().count();
706 let new_char_byte = self
707 .text
708 .char_to_byte_idx(new_char_idx)
709 .min(self.text.len());
710 let new_cursor = self.byte_to_pos(new_char_byte);
711 self.cursor = new_cursor;
712
713 let after_cursor = new_cursor;
714 let after_selection = None;
715
716 let range = before_cursor..after_cursor;
717 let op = BufferOp::Replace {
718 range,
719 text: text.to_string(),
720 old_text: String::new(),
721 end_position: after_cursor,
722 };
723
724 let change = ChangeSet::new(
725 vec![op],
726 before_cursor,
727 after_cursor,
728 before_selection,
729 after_selection,
730 );
731
732 self.apply_change(change, ChangeKind::InsertText);
733 self.increment_version();
734 }
735
736 fn delete_selection_if_active(&mut self) -> bool {
738 if let Some(sel) = self.selection {
739 self.delete_range(sel.min(), sel.max());
740 true
741 } else {
742 false
743 }
744 }
745
746 pub fn delete_backward(&mut self) {
747 if self.delete_selection_if_active() {
748 return;
749 }
750
751 let byte_idx = self.cursor_to_byte();
752 if byte_idx == 0 {
753 return;
754 }
755
756 let before_cursor = self.cursor;
757 let before_selection = self.selection;
758
759 let char_idx = self.text.byte_to_char_idx(byte_idx);
760 if char_idx == 0 {
761 return;
762 }
763 let prev_char_idx = char_idx - 1;
764 let del_start_byte = self.text.char_to_byte_idx(prev_char_idx);
765 let del_end_byte = byte_idx;
766 let prev_char_byte_idx = del_start_byte;
767 let prev_char = self.text.char(prev_char_byte_idx);
768
769 let new_cursor_pos: Position;
770 if prev_char == '\n' {
771 let line_idx = self.text.byte_to_line_idx(byte_idx, self.line_type());
772 if line_idx > 0 {
773 let new_cursor_col = self.line_display_len(line_idx - 1);
774 new_cursor_pos = Position::new(line_idx - 1, new_cursor_col);
775 } else {
776 new_cursor_pos = before_cursor;
777 }
778 } else {
779 let new_col = before_cursor.column.saturating_sub(1);
780 new_cursor_pos = Position::new(before_cursor.line, new_col);
781 }
782
783 let deleted_text: String = self.text.slice(del_start_byte..del_end_byte).into();
784
785 self.text.remove(del_start_byte..del_end_byte);
786
787 self.cursor = new_cursor_pos;
788 self.selection = None;
789
790 let range = new_cursor_pos..before_cursor;
791 let op = BufferOp::Replace {
792 range,
793 text: String::new(),
794 old_text: deleted_text,
795 end_position: new_cursor_pos,
796 };
797
798 let change = ChangeSet::new(
799 vec![op],
800 before_cursor,
801 new_cursor_pos,
802 before_selection,
803 None,
804 );
805
806 self.apply_change(change, ChangeKind::DeleteText);
807 self.increment_version();
808 }
809
810 pub fn delete_forward(&mut self) {
811 if self.delete_selection_if_active() {
812 return;
813 }
814
815 let byte_idx = self.cursor_to_byte();
816 if byte_idx >= self.text.len() {
817 return;
818 }
819
820 let before_cursor = self.cursor;
821 let before_selection = self.selection;
822
823 let char_idx = self.text.byte_to_char_idx(byte_idx);
825 let next_byte = self
826 .text
827 .char_to_byte_idx(char_idx + 1)
828 .min(self.text.len());
829
830 let deleted_text: String = self.text.slice(byte_idx..next_byte).into();
831 self.text.remove(byte_idx..next_byte);
832
833 let after_cursor = before_cursor;
834 let after_selection = None;
835
836 let range = before_cursor..after_cursor;
837 let op = BufferOp::Replace {
838 range,
839 text: String::new(),
840 old_text: deleted_text,
841 end_position: before_cursor,
842 };
843
844 let change = ChangeSet::new(
845 vec![op],
846 before_cursor,
847 after_cursor,
848 before_selection,
849 after_selection,
850 );
851
852 self.apply_change(change, ChangeKind::DeleteText);
853 self.increment_version();
854 }
855
856 pub fn delete_range(&mut self, start_pos: Position, end_pos: Position) {
857 if start_pos == end_pos {
858 return;
859 }
860
861 let start_byte = self.pos_to_byte(start_pos);
862 let end_byte = self.pos_to_byte(end_pos).min(self.text.len());
863
864 if start_byte >= self.text.len() || start_byte >= end_byte {
865 return;
866 }
867
868 let before_cursor = self.cursor;
869 let before_selection = self.selection;
870
871 let deleted_text: String = self.text.slice(start_byte..end_byte).into();
872 self.text.remove(start_byte..end_byte);
873
874 self.cursor = start_pos;
875 self.selection = None;
876
877 let range = start_pos..end_pos;
878 let op = BufferOp::Replace {
879 range,
880 text: String::new(),
881 old_text: deleted_text,
882 end_position: start_pos,
883 };
884
885 let change = ChangeSet::new(vec![op], before_cursor, start_pos, before_selection, None);
886
887 self.apply_change(change, ChangeKind::DeleteText);
888 self.increment_version();
889 }
890
891 pub fn replace_range(&mut self, start: Position, end: Position, text: &str) {
892 let start_byte = self.pos_to_byte(start);
893 let end_byte = self.pos_to_byte(end).min(self.text.len());
894
895 if start_byte >= self.text.len() && start != end {
896 return;
897 }
898
899 let before_cursor = self.cursor;
900 let before_selection = self.selection;
901
902 let actual_end_byte = end_byte.min(self.text.len());
903 let old_text: String = self.text.slice(start_byte..actual_end_byte).into();
904
905 if old_text == text && before_cursor == start {
906 return;
907 }
908
909 self.text.remove(start_byte..actual_end_byte);
910 self.text.insert(start_byte, text);
911
912 let new_char_idx = self.text.byte_to_char_idx(start_byte) + text.chars().count();
913 let after_cursor = self.byte_to_pos(self.text.char_to_byte_idx(new_char_idx));
914 self.cursor = after_cursor;
915 self.selection = None;
916
917 let range = start..end;
918 let op = BufferOp::Replace {
919 range,
920 text: text.to_string(),
921 old_text,
922 end_position: after_cursor,
923 };
924
925 let change = ChangeSet::new(
926 vec![op],
927 before_cursor,
928 after_cursor,
929 before_selection,
930 None,
931 );
932
933 self.apply_change(change, ChangeKind::Replace);
934 self.increment_version();
935 }
936
937 pub fn insert_newline(&mut self) {
938 let before_cursor = self.cursor;
939 let before_selection = self.selection;
940
941 let byte_idx = self.cursor_to_byte();
942 self.text.insert(byte_idx, "\n");
943
944 let new_char_idx = self.text.byte_to_char_idx(byte_idx) + 1;
945 let new_cursor_byte = self
946 .text
947 .char_to_byte_idx(new_char_idx)
948 .min(self.text.len());
949 let new_cursor = self.byte_to_pos(new_cursor_byte);
950 self.cursor = new_cursor;
951
952 let range = before_cursor..new_cursor;
953 let op = BufferOp::Replace {
954 range,
955 text: "\n".to_string(),
956 old_text: String::new(),
957 end_position: new_cursor,
958 };
959
960 let change = ChangeSet::new(vec![op], before_cursor, new_cursor, before_selection, None);
961
962 self.apply_change(change, ChangeKind::Structural);
963 self.increment_version();
964 }
965
966 pub fn insert_newline_with_indent(&mut self, _use_spaces: bool, _indent_width: usize) {
967 let leading_indent = if self.cursor.line < self.text.len_lines(self.line_type()) {
970 let line = self.text.line(self.cursor.line, self.line_type());
971 line.chars()
972 .take_while(|c| *c == ' ' || *c == '\t')
973 .collect::<String>()
974 } else {
975 String::new()
976 };
977
978 let text = format!("\n{}", leading_indent);
981
982 let before_cursor = self.cursor;
983 let before_selection = self.selection;
984
985 let byte_idx = self.cursor_to_byte();
986 self.text.insert(byte_idx, &text);
987
988 let new_char_idx = self.text.byte_to_char_idx(byte_idx) + text.chars().count();
989 let new_cursor = self.byte_to_pos(self.text.char_to_byte_idx(new_char_idx));
990 self.cursor = new_cursor;
991
992 let range = before_cursor..new_cursor;
993 let op = BufferOp::Replace {
994 range,
995 text,
996 old_text: String::new(),
997 end_position: new_cursor,
998 };
999
1000 let change = ChangeSet::new(vec![op], before_cursor, new_cursor, before_selection, None);
1001
1002 self.apply_change(change, ChangeKind::Structural);
1003 self.increment_version();
1004 }
1005
1006 pub fn delete_word_backward(&mut self) {
1007 let end_byte = self.cursor_to_byte();
1008 if end_byte == 0 {
1009 return;
1010 }
1011
1012 let before_cursor = self.cursor;
1013 let before_selection = self.selection;
1014
1015 let start_pos = self.word_boundary_prev(self.cursor);
1017 let start_byte = self.pos_to_byte(start_pos);
1018
1019 if start_byte < end_byte {
1020 let deleted_text: String = self.text.slice(start_byte..end_byte).into();
1021 self.text.remove(start_byte..end_byte);
1022 let new_pos = self.byte_to_pos(start_byte);
1023 self.cursor = new_pos;
1024
1025 let range = new_pos..before_cursor;
1026 let op = BufferOp::Replace {
1027 range,
1028 text: String::new(),
1029 old_text: deleted_text,
1030 end_position: new_pos,
1031 };
1032
1033 let change = ChangeSet::new(vec![op], before_cursor, new_pos, before_selection, None);
1034
1035 self.apply_change(change, ChangeKind::DeleteText);
1036 self.increment_version();
1037 }
1038 }
1039
1040 pub fn delete_word_forward(&mut self) {
1041 let start = self.cursor_to_byte();
1042 if start >= self.text.len() {
1043 return;
1044 }
1045
1046 let before_cursor = self.cursor;
1047 let before_selection = self.selection;
1048
1049 let mut char_idx = self.text.byte_to_char_idx(start);
1050 let end_char = self.text.len_chars();
1051
1052 let first_char = self.text.char(self.text.char_to_byte_idx(char_idx));
1053 let at_whitespace = first_char.is_whitespace();
1054
1055 while char_idx < end_char
1056 && self
1057 .text
1058 .char(self.text.char_to_byte_idx(char_idx))
1059 .is_whitespace()
1060 {
1061 char_idx += 1;
1062 }
1063
1064 if !at_whitespace {
1065 while char_idx < end_char {
1066 let curr_byte = self.text.char_to_byte_idx(char_idx);
1067 if self.text.char(curr_byte).is_whitespace() {
1068 break;
1069 }
1070 char_idx += 1;
1071 }
1072
1073 while char_idx < end_char
1074 && self
1075 .text
1076 .char(self.text.char_to_byte_idx(char_idx))
1077 .is_whitespace()
1078 {
1079 char_idx += 1;
1080 }
1081 }
1082
1083 let end_byte = if char_idx < end_char {
1084 self.text.char_to_byte_idx(char_idx)
1085 } else {
1086 self.text.len()
1087 };
1088 let start_byte = self
1089 .text
1090 .char_to_byte_idx(self.text.byte_to_char_idx(start));
1091
1092 if start_byte < end_byte {
1093 let deleted_text: String = self.text.slice(start_byte..end_byte).into();
1094 self.text.remove(start_byte..end_byte);
1095
1096 let range = before_cursor..before_cursor;
1097 let op = BufferOp::Replace {
1098 range,
1099 text: String::new(),
1100 old_text: deleted_text,
1101 end_position: before_cursor,
1102 };
1103
1104 let change = ChangeSet::new(
1105 vec![op],
1106 before_cursor,
1107 before_cursor,
1108 before_selection,
1109 None,
1110 );
1111
1112 self.apply_change(change, ChangeKind::DeleteText);
1113 self.increment_version();
1114 }
1115 }
1116
1117 pub fn word_range_at(&self, pos: Position) -> (usize, usize) {
1118 let byte_idx = self.pos_to_byte(pos);
1119 let line_start_byte = self.text.line_to_byte_idx(pos.line, self.line_type());
1120 let line = self.text.line(pos.line, self.line_type());
1121
1122 let mut word_start = 0;
1123 let mut word_end = line.len_chars();
1124 let mut in_word = false;
1125
1126 for (i, (byte_offset, ch)) in line.char_indices().enumerate() {
1127 let abs_byte = line_start_byte + byte_offset;
1128 if abs_byte >= byte_idx && ch.is_alphanumeric() && !in_word {
1129 word_start = i;
1130 in_word = true;
1131 }
1132 if in_word && !ch.is_alphanumeric() {
1133 word_end = i;
1134 break;
1135 }
1136 }
1137
1138 (word_start, word_end)
1139 }
1140
1141 pub fn insert_at(&mut self, pos: Position, text: &str) {
1142 let before_cursor = self.cursor;
1143 let before_selection = self.selection;
1144
1145 let byte_idx = self.pos_to_byte(pos);
1146 self.text.insert(byte_idx, text);
1147
1148 let new_char_idx = self.text.byte_to_char_idx(byte_idx) + text.chars().count();
1149 let after_cursor = self.byte_to_pos(self.text.char_to_byte_idx(new_char_idx));
1150 if pos == before_cursor {
1151 self.cursor = after_cursor;
1152 }
1153
1154 let range = pos..after_cursor;
1155 let op = BufferOp::Replace {
1156 range,
1157 text: text.to_string(),
1158 old_text: String::new(),
1159 end_position: after_cursor,
1160 };
1161
1162 let change = ChangeSet::new(
1163 vec![op],
1164 before_cursor,
1165 after_cursor,
1166 before_selection,
1167 None,
1168 );
1169
1170 self.apply_change(change, ChangeKind::InsertText);
1171 self.increment_version();
1172 }
1173
1174 pub fn insert_text_raw(&mut self, text: &str) {
1175 let normalized = text.replace("\r\n", "\n").replace('\r', "\n");
1176 self.insert(&normalized);
1177 }
1178
1179 pub fn delete_line(&mut self) -> String {
1180 let line_idx = self.cursor.line;
1181 if line_idx >= self.text.len_lines(self.line_type()) {
1182 return String::new();
1183 }
1184
1185 let line_start = self.text.line_to_byte_idx(line_idx, self.line_type());
1186 let line_end = if line_idx + 1 < self.text.len_lines(self.line_type()) {
1187 self.text.line_to_byte_idx(line_idx + 1, self.line_type())
1188 } else {
1189 self.text.len()
1190 };
1191
1192 let deleted: String = self.text.slice(line_start..line_end).into();
1193
1194 let before_cursor = self.cursor;
1195 let before_selection = self.selection;
1196
1197 self.text.remove(line_start..line_end);
1198
1199 if self.text.len_lines(self.line_type()) == 0 {
1200 self.text.insert(0, "");
1201 }
1202
1203 let new_line_idx = line_idx.min(self.text.len_lines(self.line_type()).saturating_sub(1));
1204 let new_pos = Position::new(new_line_idx, 0);
1205 self.cursor = new_pos;
1206 self.normalize_cursor();
1207
1208 let range = new_pos..before_cursor;
1209 let op = BufferOp::Replace {
1210 range,
1211 text: String::new(),
1212 old_text: deleted.clone(),
1213 end_position: new_pos,
1214 };
1215
1216 let change = ChangeSet::new(vec![op], before_cursor, new_pos, before_selection, None);
1217
1218 self.apply_change(change, ChangeKind::DeleteText);
1219 self.increment_version();
1220
1221 deleted
1222 }
1223
1224 pub fn selected_text_all(&self) -> String {
1225 self.selected_text()
1226 }
1227
1228 pub fn cursor_left(&mut self) -> Position {
1229 let pos = self.offset_left(self.cursor);
1230 self.cursor = pos;
1231 self.cursor
1232 }
1233
1234 pub fn offset_left(&self, pos: Position) -> Position {
1235 if pos.column > 0 {
1236 Position::new(pos.line, pos.column - 1)
1237 } else if pos.line > 0 {
1238 let prev_line = pos.line - 1;
1239 let prev_line_len = self.line_display_len(prev_line);
1240 Position::new(prev_line, prev_line_len)
1241 } else {
1242 pos
1243 }
1244 }
1245
1246 pub fn cursor_right(&mut self) -> Position {
1247 let pos = self.offset_right(self.cursor);
1248 self.cursor = pos;
1249 self.cursor
1250 }
1251
1252 pub fn offset_right(&self, pos: Position) -> Position {
1253 let line_len = self.line_display_len(pos.line);
1254 if pos.column < line_len {
1255 Position::new(pos.line, pos.column + 1)
1256 } else if pos.line < self.text.len_lines(self.line_type()) - 1 {
1257 Position::new(pos.line + 1, 0)
1258 } else {
1259 pos
1260 }
1261 }
1262
1263 fn apply_movement(&mut self, new_pos: Position) -> Position {
1265 self.cursor = new_pos;
1266 self.cursor
1267 }
1268
1269 pub fn cursor_up(&mut self) -> Position {
1270 self.apply_movement(self.offset_up(self.cursor))
1271 }
1272
1273 pub fn offset_up(&self, pos: Position) -> Position {
1274 if pos.line == 0 { pos } else { self.offset_up_n(pos, 1) }
1275 }
1276
1277 pub fn cursor_down(&mut self) -> Position {
1278 self.apply_movement(self.offset_down(self.cursor))
1279 }
1280
1281 pub fn offset_down(&self, pos: Position) -> Position {
1282 if pos.line >= self.text.len_lines(self.line_type()).saturating_sub(1) {
1283 pos
1284 } else {
1285 self.offset_down_n(pos, 1)
1286 }
1287 }
1288
1289 pub fn cursor_up_n(&mut self, n: usize) -> Position {
1290 self.apply_movement(self.offset_up_n(self.cursor, n))
1291 }
1292
1293 pub fn offset_up_n(&self, pos: Position, n: usize) -> Position {
1294 let new_line = pos.line.saturating_sub(n);
1295 let line_len = self.line_display_len(new_line);
1296 Position::new(new_line, pos.column.min(line_len))
1297 }
1298
1299 pub fn cursor_down_n(&mut self, n: usize) -> Position {
1300 self.apply_movement(self.offset_down_n(self.cursor, n))
1301 }
1302
1303 pub fn offset_down_n(&self, pos: Position, n: usize) -> Position {
1304 let new_line = (pos.line + n).min(self.text.len_lines(self.line_type()).saturating_sub(1));
1305 let line_len = self.line_display_len(new_line);
1306 Position::new(new_line, pos.column.min(line_len))
1307 }
1308
1309 pub fn cursor_page_up(&mut self, height: usize) -> Position {
1310 self.apply_movement(self.offset_up_n(self.cursor, height))
1311 }
1312
1313 pub fn cursor_page_down(&mut self, height: usize) -> Position {
1314 self.apply_movement(self.offset_down_n(self.cursor, height))
1315 }
1316
1317 pub fn cursor_line_start(&mut self) -> Position {
1318 self.cursor.column = 0;
1319 self.cursor
1320 }
1321
1322 pub fn offset_line_start(&self, pos: Position) -> Position {
1323 Position::new(pos.line, 0)
1324 }
1325
1326 pub fn cursor_line_end(&mut self) -> Position {
1327 self.cursor.column = self.line_display_len(self.cursor.line);
1328 self.cursor
1329 }
1330
1331 pub fn offset_line_end(&self, pos: Position) -> Position {
1332 Position::new(pos.line, self.line_display_len(pos.line))
1333 }
1334
1335 pub fn cursor_word_prev(&mut self) -> Position {
1336 self.apply_movement(self.word_boundary_prev(self.cursor))
1337 }
1338
1339 pub fn cursor_word_next(&mut self) -> Position {
1340 self.apply_movement(self.word_boundary_next_for_selection(self.cursor))
1341 }
1342
1343 pub fn word_boundary_prev(&self, pos: Position) -> Position {
1344 if pos.line > 0 {
1346 let line_slice = self.text.line(pos.line, self.line_type());
1347 let mut first_non_ws_col: Option<usize> = None;
1348 for (i, c) in line_slice.chars().enumerate() {
1349 if !c.is_whitespace() || c == '\n' {
1350 first_non_ws_col = Some(i);
1351 break;
1352 }
1353 }
1354 if pos.column == 0 || first_non_ws_col == Some(pos.column) {
1355 let prev_line = pos.line - 1;
1356 return Position::new(prev_line, self.line_display_len(prev_line));
1357 }
1358 }
1359
1360 let line_start_byte = self.text.line_to_byte_idx(pos.line, self.line_type());
1361 let byte_idx = line_start_byte + self.text.char_to_byte_idx(pos.column);
1362
1363 if byte_idx == 0 {
1364 return pos;
1365 }
1366
1367 let mut char_idx = self.text.byte_to_char_idx(byte_idx);
1369 if char_idx == 0 {
1370 return pos;
1371 }
1372 char_idx = char_idx.saturating_sub(1);
1373
1374 while char_idx > 0 {
1376 let c_byte = self.text.char_to_byte_idx(char_idx);
1377 let c = self.text.char(c_byte);
1378 if !c.is_whitespace() || c == '\n' {
1379 break;
1380 }
1381 char_idx = char_idx.saturating_sub(1);
1382 }
1383
1384 let c_byte = self.text.char_to_byte_idx(char_idx);
1386 let first_char = self.text.char(c_byte);
1387 let is_punct = first_char.is_ascii_punctuation();
1388
1389 while char_idx > 0 {
1391 let prev_byte = self.text.char_to_byte_idx(char_idx.saturating_sub(1));
1392 let prev_char = self.text.char(prev_byte);
1393 if is_punct {
1394 if !prev_char.is_ascii_punctuation() {
1395 break;
1396 }
1397 } else if prev_char.is_whitespace() || prev_char.is_ascii_punctuation() {
1398 break;
1399 }
1400 char_idx = char_idx.saturating_sub(1);
1401 }
1402
1403 self.byte_to_pos(self.text.char_to_byte_idx(char_idx))
1404 }
1405
1406 pub fn word_boundary_next(&self, pos: Position) -> Position {
1407 self.word_boundary_next_impl(pos, true)
1408 }
1409
1410 pub fn word_boundary_next_for_selection(&self, pos: Position) -> Position {
1411 self.word_boundary_next_impl(pos, false)
1412 }
1413
1414 fn word_boundary_next_impl(&self, mut pos: Position, skip_whitespace_after: bool) -> Position {
1415 let end_char = self.text.len_chars();
1416
1417 let mut line_start_byte = self.text.line_to_byte_idx(pos.line, self.line_type());
1420 let mut line_start_char = self.text.byte_to_char_idx(line_start_byte);
1421 let mut line_len_chars = self.text.line(pos.line, self.line_type()).len_chars();
1422 let line_slice = self.text.line(pos.line, self.line_type());
1423 let mut printable_line_len = if line_slice.chars().last() == Some('\n') {
1426 line_len_chars.saturating_sub(1)
1427 } else {
1428 line_len_chars
1429 };
1430 let mut printable_line_end_char_idx = line_start_char + printable_line_len;
1431
1432 let mut char_idx = self.pos_to_char(pos);
1433
1434 if char_idx >= end_char {
1436 return pos;
1437 }
1438
1439
1440 let total_lines = self.text.len_lines(self.line_type());
1443 if char_idx >= printable_line_end_char_idx {
1444 if pos.line + 1 >= total_lines {
1445 return Position::new(pos.line, printable_line_len);
1447 }
1448
1449 pos = Position::new(pos.line + 1, 0);
1451 line_start_byte = self.text.line_to_byte_idx(pos.line, self.line_type());
1452 line_start_char = self.text.byte_to_char_idx(line_start_byte);
1453 line_len_chars = self.text.line(pos.line, self.line_type()).len_chars();
1454 let line_slice = self.text.line(pos.line, self.line_type());
1455 printable_line_len = if line_slice.chars().last() == Some('\n') {
1456 line_len_chars.saturating_sub(1)
1457 } else {
1458 line_len_chars
1459 };
1460 printable_line_end_char_idx = line_start_char + printable_line_len;
1461 let mut next_char_idx = self.pos_to_char(pos);
1465 while next_char_idx < end_char
1466 && self
1467 .text
1468 .char(self.text.char_to_byte_idx(next_char_idx))
1469 .is_whitespace()
1470 {
1471 next_char_idx += 1;
1472 if next_char_idx >= printable_line_end_char_idx {
1473 return Position::new(pos.line, printable_line_len);
1474 }
1475 }
1476 if next_char_idx >= end_char {
1477 return pos;
1478 }
1479 return self.byte_to_pos(self.text.char_to_byte_idx(next_char_idx));
1480 }
1481
1482 while char_idx < end_char
1485 && self
1486 .text
1487 .char(self.text.char_to_byte_idx(char_idx))
1488 .is_whitespace()
1489 {
1490 char_idx += 1;
1491 if char_idx >= printable_line_end_char_idx {
1492 return Position::new(pos.line, printable_line_len);
1493 }
1494 }
1495
1496 if char_idx >= end_char {
1497 return pos;
1498 }
1499
1500 let first_char = self.text.char(self.text.char_to_byte_idx(char_idx));
1502 let is_punct = first_char.is_ascii_punctuation();
1503
1504 while char_idx < end_char {
1507 let c = self.text.char(self.text.char_to_byte_idx(char_idx));
1508 if is_punct {
1509 if !c.is_ascii_punctuation() {
1510 break;
1511 }
1512 } else if c.is_whitespace() || c.is_ascii_punctuation() {
1513 break;
1514 }
1515 char_idx += 1;
1516 if char_idx >= printable_line_end_char_idx {
1517 return Position::new(pos.line, printable_line_len);
1518 }
1519 }
1520
1521 if skip_whitespace_after {
1522 while char_idx < end_char
1523 && self
1524 .text
1525 .char(self.text.char_to_byte_idx(char_idx))
1526 .is_whitespace()
1527 {
1528 char_idx += 1;
1529 if char_idx >= printable_line_end_char_idx {
1530 return Position::new(pos.line, printable_line_len);
1531 }
1532 }
1533 }
1534
1535 if char_idx >= printable_line_end_char_idx {
1537 return Position::new(pos.line, printable_line_len);
1538 }
1539
1540 self.byte_to_pos(self.text.char_to_byte_idx(char_idx))
1541 }
1542
1543 pub fn select_all(&mut self) {
1544 let last_line = self.text.len_lines(self.line_type()).saturating_sub(1);
1545 let pos = Position::new(
1546 last_line,
1547 self.text.line(last_line, self.line_type()).len_chars(),
1548 );
1549 self.selection = Some(Selection::new(Position::new(0, 0), pos));
1550 self.cursor = pos;
1551 }
1552
1553 pub fn selected_text(&self) -> String {
1554 if let Some(sel) = self.selection {
1555 let start = self.pos_to_byte(sel.min());
1556 let end = self.pos_to_byte(sel.max());
1557 self.text.slice(start..end).into()
1558 } else {
1559 String::new()
1560 }
1561 }
1562
1563 pub fn delete_selection(&mut self) -> String {
1564 if let Some(sel) = self.selection {
1565 let start = sel.min();
1566 let end = sel.max();
1567 let text = self.slice(start..end);
1568 self.delete_range(start, end);
1569 text
1570 } else {
1571 String::new()
1572 }
1573 }
1574
1575 pub fn undo(&mut self) -> bool {
1576 let inverse = {
1577 match self.history.undo() {
1578 Some(node) => node.inverse.clone(),
1579 None => return false,
1580 }
1581 };
1582 apply_changeset(self, &inverse);
1583 true
1584 }
1585
1586 pub fn redo(&mut self) -> bool {
1587 let change = {
1588 match self.history.redo() {
1589 Some(node) => node.change.clone(),
1590 None => return false,
1591 }
1592 };
1593 apply_changeset(self, &change);
1594 true
1595 }
1596
1597 fn export_history_snapshots(&self) -> (Vec<SerializableSnapshot>, Vec<SerializableSnapshot>) {
1598 let entries = self.history.timeline_entries();
1600 let cursor = self.history.current_position();
1601
1602 let mut temp = Buffer::from_lines(self.lines(), self.path.clone());
1604 temp.cursor = self.cursor;
1605 temp.selection = self.selection;
1606 temp.dirty = self.dirty;
1607 temp.version = self.version;
1608
1609 let mut undo_rev: Vec<SerializableSnapshot> = Vec::new();
1611 for i in (0..cursor).rev() {
1612 let (change, inverse, version) = &entries[i];
1613 temp.set_cursor(change.after_cursor);
1614 temp.set_selection(change.after_selection);
1615 let snap = SerializableSnapshot {
1616 lines: temp.lines(),
1617 cursor: change.after_cursor,
1618 selection: change.after_selection,
1619 dirty: temp.dirty,
1620 version: *version,
1621 };
1622 undo_rev.push(snap);
1623 apply_changeset(&mut temp, inverse);
1625 }
1626
1627 let base_snap = SerializableSnapshot {
1629 lines: temp.lines(),
1630 cursor: temp.cursor,
1631 selection: temp.selection,
1632 dirty: temp.dirty,
1633 version: temp.version,
1634 };
1635 undo_rev.push(base_snap);
1636 undo_rev.reverse();
1637
1638 let mut redo: Vec<SerializableSnapshot> = Vec::new();
1640 for (change, _inv, version) in entries.iter().skip(cursor) {
1641 apply_changeset(&mut temp, change);
1642 let snap = SerializableSnapshot {
1643 lines: temp.lines(),
1644 cursor: change.after_cursor,
1645 selection: change.after_selection,
1646 dirty: temp.dirty,
1647 version: *version,
1648 };
1649 redo.push(snap);
1650 }
1651
1652 (undo_rev, redo)
1653 }
1654
1655 pub fn undo_stack(&self) -> Vec<SerializableSnapshot> {
1656 let (undo, _redo) = self.export_history_snapshots();
1657 undo
1658 }
1659
1660 pub fn redo_stack(&self) -> Vec<SerializableSnapshot> {
1661 let (_undo, redo) = self.export_history_snapshots();
1662 redo
1663 }
1664
1665 pub fn save(&mut self, settings: &crate::settings::Settings) -> Result<()> {
1666 let path = self
1667 .path
1668 .clone()
1669 .ok_or_else(|| anyhow!("No path associated with buffer"))?;
1670
1671 self.is_saving = true;
1672 let result = self.do_save(&path, settings);
1673 self.is_saving = false;
1674 result
1675 }
1676
1677 fn do_save(&mut self, path: &PathBuf, settings: &crate::settings::Settings) -> Result<()> {
1678 let should_trim = *adapters::editor::trim_on_save(settings);
1679
1680 let mut text = self.text.clone();
1681
1682 if should_trim {
1683 let content = text.to_string();
1684 let had_trailing_newline = content.ends_with('\n');
1685 let trimmed: String = content
1686 .lines()
1687 .map(|l| l.trim_end().to_string())
1688 .collect::<Vec<_>>()
1689 .join("\n");
1690
1691 let trailing_empty = trimmed.lines().rev().take_while(|l| l.is_empty()).count();
1692 let final_lines: String = if trailing_empty > 1 {
1693 trimmed
1694 .lines()
1695 .take(trimmed.lines().count() - (trailing_empty - 1))
1696 .collect::<Vec<_>>()
1697 .join("\n")
1698 } else {
1699 trimmed.clone()
1700 };
1701
1702 let final_with_newline = if had_trailing_newline && !final_lines.ends_with('\n') {
1703 format!("{}\n", final_lines)
1704 } else {
1705 final_lines
1706 };
1707
1708 text = Rope::from_str(&final_with_newline);
1709 self.text = text.clone();
1710 self.normalize_cursor();
1711 }
1712
1713 let content = {
1716 let s = text.to_string();
1717 let without_trailing = s.trim_end_matches('\n');
1718 if s.ends_with('\n') {
1719 format!("{}\n", without_trailing)
1720 } else {
1721 without_trailing.to_string()
1722 }
1723 };
1724
1725 let tmp = path.with_extension("tmp");
1726 fs::write(&tmp, &content)?;
1727 fs::rename(&tmp, path).or_else(|_| {
1728 fs::remove_file(path)?;
1729 fs::rename(&tmp, path)
1730 })?;
1731
1732 self.file_hash = Self::compute_hash(&text);
1733 self.dirty = false;
1734 if let Some((mtime, size)) = Self::get_file_metadata(path) {
1735 self.file_mtime = Some(mtime);
1736 self.file_size = Some(size);
1737 }
1738 Ok(())
1739 }
1740
1741 pub fn compute_hash(text: &Rope) -> Option<u64> {
1742 use std::collections::hash_map::DefaultHasher;
1743 use std::hash::{Hash, Hasher};
1744 let mut hasher = DefaultHasher::new();
1745 text.hash(&mut hasher);
1746 Some(hasher.finish())
1747 }
1748
1749 pub fn compute_file_hash(path: &Path) -> Option<u64> {
1750 use std::collections::hash_map::DefaultHasher;
1751 use std::hash::{Hash, Hasher};
1752 use std::io::Read;
1753 let mut file = fs::File::open(path).ok()?;
1754 let mut contents = Vec::new();
1755 file.read_to_end(&mut contents).ok()?;
1756 let mut hasher = DefaultHasher::new();
1757 contents.hash(&mut hasher);
1758 Some(hasher.finish())
1759 }
1760
1761 pub fn get_file_metadata(path: &Path) -> Option<(u64, u64)> {
1762 let metadata = fs::metadata(path).ok()?;
1763 let mtime = metadata
1764 .modified()
1765 .ok()?
1766 .duration_since(std::time::UNIX_EPOCH)
1767 .ok()?
1768 .as_secs();
1769 let size = metadata.len();
1770 Some((mtime, size))
1771 }
1772
1773 pub fn compute_and_store_file_hash(&mut self) {
1774 if let Some(ref path) = self.path {
1775 self.file_hash = Self::compute_file_hash(path);
1776 if let Some((mtime, size)) = Self::get_file_metadata(path) {
1777 self.file_mtime = Some(mtime);
1778 self.file_size = Some(size);
1779 }
1780 }
1781 }
1782
1783 pub fn check_external_modification(&mut self) -> bool {
1784 if self.is_saving {
1785 return false;
1786 }
1787 if self.is_dirty() {
1788 return false;
1789 }
1790 if let Some(ref path) = self.path
1791 && let Some((current_mtime, current_size)) = Self::get_file_metadata(path) {
1792 let mtime_changed = self.file_mtime.map(|m| m != current_mtime).unwrap_or(false);
1793 let size_changed = self.file_size.map(|s| s != current_size).unwrap_or(false);
1794 if !mtime_changed && !size_changed {
1795 return false;
1796 }
1797 if let Some(current_hash) = Self::compute_file_hash(path)
1798 && let Some(saved_hash) = self.file_hash
1799 && current_hash != saved_hash {
1800 self.external_modification_detected = true;
1801 return true;
1802 }
1803 }
1804 false
1805 }
1806
1807 pub fn reload_from_disk(&mut self) -> Result<()> {
1808 let path = self
1809 .path
1810 .clone()
1811 .ok_or_else(|| anyhow!("No path associated with buffer"))?;
1812 let content = fs::read_to_string(&path)?;
1813 self.text = Rope::from_str(&content);
1814 self.file_hash = Self::compute_hash(&self.text);
1815 if let Some((mtime, size)) = Self::get_file_metadata(&path) {
1816 self.file_mtime = Some(mtime);
1817 self.file_size = Some(size);
1818 }
1819 self.external_modification_detected = false;
1820 self.normalize_cursor();
1821 Ok(())
1822 }
1823
1824 pub fn clear_external_modification(&mut self) {
1825 self.external_modification_detected = false;
1826 }
1827
1828 pub fn scroll_to_cursor(&mut self, height: usize) {
1829 if height == 0 {
1830 return;
1831 }
1832 if self.cursor.line < self.scroll {
1833 self.scroll = self.cursor.line;
1834 } else if self.cursor.line >= self.scroll + height {
1835 self.scroll = self.cursor.line + 1 - height;
1836 }
1837 }
1838
1839 pub fn scroll_to_cursor_visual(
1840 &mut self,
1841 height: usize,
1842 _content_width: usize,
1843 _indent_width: usize,
1844 ) {
1845 self.scroll_to_cursor(height);
1846 }
1847
1848 pub fn scroll_x_to_cursor(&mut self, _content_width: usize, _indent_width: usize) {}
1849
1850 pub fn slice(&self, range: Range<Position>) -> String {
1851 let start_byte = self.pos_to_byte(range.start);
1852 let end_byte = self.pos_to_byte(range.end);
1853 self.text.slice(start_byte..end_byte).into()
1854 }
1855
1856 pub fn text(&self) -> String {
1857 self.text.to_string()
1858 }
1859
1860 pub fn apply_op_without_history(&mut self, op: &BufferOp) {
1861 match op {
1862 BufferOp::Replace { range, text, old_text, .. } => {
1863 let mut start_byte = self.pos_to_byte(range.start);
1865 let end_byte = self.pos_to_byte(range.end).min(self.text.len());
1866
1867 let mut removed = false;
1868
1869 if start_byte < end_byte {
1870 let range_len = end_byte - start_byte;
1872 let old_len = old_text.len();
1873 if old_len > range_len && !old_text.is_empty() {
1874 let tail: String = self.text.slice(start_byte..self.text.len()).into();
1877 if let Some(pos) = tail.find(old_text.as_str()) {
1878 let found_end_byte = start_byte + pos + old_text.len();
1879 self.text.remove(start_byte..found_end_byte);
1880 removed = true;
1881 } else {
1882 self.text.remove(start_byte..end_byte);
1884 removed = true;
1885 }
1886 } else {
1887 self.text.remove(start_byte..end_byte);
1888 removed = true;
1889 }
1890 } else {
1891 if !old_text.is_empty() {
1895 let tail: String = self.text.slice(start_byte..self.text.len()).into();
1897 if let Some(pos) = tail.find(old_text.as_str()) {
1898 let found_end_byte = start_byte + pos + old_text.len();
1899 self.text.remove(start_byte..found_end_byte);
1900 removed = true;
1901 } else {
1902 let whole = self.text.to_string();
1904 if let Some(pos) = whole.find(old_text.as_str()) {
1905 self.text.remove(pos..(pos + old_text.len()));
1906 start_byte = pos;
1907 removed = true;
1908 }
1909 }
1910 }
1911 }
1912
1913 if removed || old_text.is_empty() {
1916 self.text.insert(start_byte, text);
1917 } else {
1918 }
1920 }
1921 BufferOp::MoveCursor { position } => {
1922 self.set_cursor(*position);
1923 }
1924 BufferOp::SetSelection { selection } => {
1925 self.set_selection(*selection);
1926 }
1927 }
1928 }
1929
1930}
1931
1932impl Default for Buffer {
1933 fn default() -> Self {
1934 Self::new()
1935 }
1936}
1937
1938impl From<&Rope> for Buffer {
1939 fn from(rope: &Rope) -> Self {
1940 let mut buf = Self::new();
1941 buf.text = rope.clone();
1942 buf
1943 }
1944}
1945
1946#[cfg(test)]
1947mod tests {
1948 use super::*;
1949
1950 #[test]
1951 fn test_position_mapping() {
1952 let buf = Buffer::from_text("hello\nworld\n");
1953
1954 assert_eq!(buf.byte_to_pos(0), Position::new(0, 0));
1955 assert_eq!(buf.byte_to_pos(5), Position::new(0, 5));
1956 assert_eq!(buf.byte_to_pos(6), Position::new(1, 0));
1957 assert_eq!(buf.byte_to_pos(11), Position::new(1, 5));
1958
1959 assert_eq!(buf.pos_to_byte(Position::new(0, 0)), 0);
1960 assert_eq!(buf.pos_to_byte(Position::new(0, 5)), 5);
1961 assert_eq!(buf.pos_to_byte(Position::new(1, 0)), 6);
1962 assert_eq!(buf.pos_to_byte(Position::new(1, 5)), 11);
1963 }
1964
1965 #[test]
1966 fn test_insert() {
1967 let mut buf = Buffer::from_text("hello");
1968 buf.cursor = Position::new(0, 5);
1969 buf.insert(" world");
1970 assert_eq!(buf.text.to_string(), "hello world");
1971 }
1972
1973 #[test]
1974 fn test_insert_newline() {
1975 let mut buf = Buffer::from_text("hello");
1976 buf.cursor = Position::new(0, 5);
1977 buf.insert_newline();
1978 assert_eq!(buf.text.to_string(), "hello\n");
1979 assert_eq!(buf.cursor, Position::new(1, 0));
1980 }
1981
1982 #[test]
1983 fn test_delete_backward() {
1984 let mut buf = Buffer::from_text("hello world");
1985 buf.cursor = Position::new(0, 5);
1986 buf.delete_backward();
1987 assert_eq!(buf.text.to_string(), "hell world");
1988 assert_eq!(buf.cursor, Position::new(0, 4));
1989 }
1990
1991 #[test]
1992 fn test_delete_selection() {
1993 let mut buf = Buffer::from_text("hello world");
1994 buf.selection = Some(Selection::new(Position::new(0, 0), Position::new(0, 6)));
1995 buf.delete_selection();
1996 assert_eq!(buf.text.to_string(), "world");
1997 }
1998
1999 #[test]
2000 fn test_cursor_movement() {
2001 let mut buf = Buffer::from_text("hello\nworld");
2002
2003 buf.cursor = Position::new(0, 3);
2004 assert_eq!(buf.cursor_right(), Position::new(0, 4));
2005 assert_eq!(buf.cursor_left(), Position::new(0, 3));
2006
2007 buf.cursor = Position::new(0, 0);
2008 assert_eq!(buf.cursor_up(), Position::new(0, 0));
2009
2010 buf.cursor = Position::new(0, 3);
2011 buf.cursor_up();
2012 assert_eq!(buf.cursor.line, 0);
2013
2014 buf.cursor = Position::new(1, 3);
2015 buf.cursor_up();
2016 assert_eq!(buf.cursor.line, 0);
2017 assert_eq!(buf.cursor.column, 3);
2018 }
2019
2020 #[test]
2021 fn test_undo_multiple_steps() {
2022 let mut buf = Buffer::from_text("");
2023
2024 buf.insert("a");
2025 buf.insert("b");
2026 buf.insert("c");
2027
2028 assert_eq!(buf.text(), "abc");
2029 assert_eq!(buf.history_position(), 1);
2031
2032 assert!(buf.undo());
2033 assert_eq!(buf.text(), "");
2034
2035 assert!(!buf.undo());
2036 }
2037
2038 #[test]
2039 fn test_undo_stack_persist_and_restore() {
2040 use std::path::PathBuf;
2041 let mut buf = Buffer::from_text_no_merge("");
2043
2044 buf.begin_transaction(ChangeKind::InsertText);
2045 buf.insert("a");
2046 buf.end_transaction();
2047
2048 buf.begin_transaction(ChangeKind::InsertText);
2049 buf.insert("b");
2050 buf.end_transaction();
2051
2052 assert_eq!(buf.text(), "ab");
2053
2054 let undo_stack = buf.undo_stack();
2055 let redo_stack = buf.redo_stack();
2056
2057 let restored = Buffer::restore(
2058 PathBuf::from("dummy"),
2059 buf.lines(),
2060 buf.selection,
2061 buf.scroll,
2062 buf.dirty,
2063 buf.markers.clone(),
2064 undo_stack.clone(),
2065 redo_stack.clone(),
2066 );
2067 let mut r = restored;
2068
2069 assert!(r.undo());
2071 assert_eq!(r.text(), "a");
2072
2073 assert!(r.undo());
2074 assert_eq!(r.text(), "");
2075 }
2076
2077 #[test]
2078 fn test_reopen_and_excess_undos_do_not_duplicate_tail() {
2079 use tempfile::tempdir;
2080 use crate::project::Project;
2081 use crate::editor::fold::FoldState;
2082 use crate::editor::history::ChangeKind;
2083 use std::fs;
2084
2085 let tmp = tempdir().expect("tempdir");
2086 let project_path = tmp.path().to_path_buf();
2087 let file_a = project_path.join("a.txt");
2088 let file_b = project_path.join("b.txt");
2089
2090 fs::write(&file_a, "alpha\nbeta\n").expect("write a");
2091 fs::write(&file_b, "other\n").expect("write b");
2092
2093 let mut project = Project::new(project_path).expect("project new");
2094
2095 let mut buf_a = project.take_buffer(file_a.clone()).expect("take A");
2097 buf_a.begin_transaction(ChangeKind::InsertText);
2098 buf_a.insert_text_raw("// edit1\n");
2099 buf_a.end_transaction();
2100
2101 buf_a.begin_transaction(ChangeKind::InsertText);
2102 buf_a.insert_text_raw("// edit2\n");
2103 buf_a.end_transaction();
2104
2105 project.stash_buffer(buf_a, FoldState::default());
2106
2107 let buf_b = project.take_buffer(file_b.clone()).expect("take B");
2108 project.stash_buffer(buf_b, FoldState::default());
2109
2110 let mut buf_a2 = project.take_buffer(file_a.clone()).expect("reopen A");
2111
2112 let undo_snapshots = buf_a2.undo_stack().len();
2113 for _ in 0..(undo_snapshots + 3) {
2114 let _ = buf_a2.undo();
2115 }
2116
2117 let expected = fs::read_to_string(&file_a).expect("read file A");
2118 assert_eq!(buf_a2.text(), expected);
2119 }
2120
2121 #[test]
2122 fn test_undo_redo_multiple_steps() {
2123 let mut buf = Buffer::from_text("");
2124
2125 buf.insert("a");
2126 buf.insert("b");
2127
2128 assert_eq!(buf.text(), "ab");
2129 assert_eq!(buf.history_position(), 1);
2130
2131 buf.undo();
2132 assert_eq!(buf.text(), "");
2133
2134 buf.redo();
2135 assert_eq!(buf.text(), "ab");
2136
2137 assert!(!buf.redo());
2138 }
2139
2140 #[test]
2141 fn test_undo_after_new_edit() {
2142 let mut buf = Buffer::from_text("");
2143
2144 buf.insert("a");
2145 buf.insert("b");
2146
2147 buf.undo();
2149 assert_eq!(buf.text(), "");
2150
2151 buf.insert("c");
2152 assert_eq!(buf.text(), "c");
2153
2154 buf.undo();
2155 assert_eq!(buf.text(), "");
2156 }
2157
2158 #[test]
2159 fn test_line_access() {
2160 let buf = Buffer::from_text("hello\nworld\n");
2161
2162 assert_eq!(buf.line_count(), 3);
2163 assert_eq!(buf.line(0).unwrap().to_string(), "hello");
2164 assert_eq!(buf.line(1).unwrap().to_string(), "world");
2165 }
2166
2167 #[test]
2168 fn test_cursor_clamping() {
2169 let mut buf = Buffer::from_text("hello");
2170 buf.cursor = Position::new(100, 100);
2171 buf.normalize_cursor();
2172
2173 assert!(buf.cursor.line < buf.line_count());
2174 assert!(buf.cursor.column <= buf.line(0).unwrap().chars().count());
2175 }
2176
2177 #[test]
2178 fn test_unicode() {
2179 let mut buf = Buffer::from_text("héllo\nwörld");
2180 assert_eq!(buf.line_count(), 2);
2181
2182 buf.cursor = Position::new(0, 6);
2183 buf.insert("!");
2184 let line0 = buf.text.line(0, LineType::LF).to_string();
2185 assert!(
2186 line0.starts_with("héllo"),
2187 "line0 should start with héllo, got: {}",
2188 line0
2189 );
2190 }
2191
2192 #[test]
2193 fn test_snapshot() {
2194 let buf = Buffer::from_text("hello");
2195 let snap = buf.snapshot();
2196 assert_eq!(snap.text.to_string(), "hello");
2197 }
2198
2199 #[test]
2200 fn test_delete_forward() {
2201 let mut buf = Buffer::from_text("hello");
2202 buf.cursor = Position::new(0, 4);
2203 buf.delete_forward();
2204 assert_eq!(buf.text.to_string(), "hell");
2205 }
2206
2207 #[test]
2208 fn test_empty_buffer() {
2209 let buf = Buffer::new();
2210 assert!(buf.is_empty());
2211 assert_eq!(buf.line_count(), 1);
2212 assert_eq!(buf.cursor, Position::new(0, 0));
2213 }
2214
2215 #[test]
2216 fn test_buffer_id() {
2217 let buf1 = Buffer::new();
2218 let buf2 = Buffer::new();
2219 assert_ne!(buf1.id(), buf2.id());
2220 }
2221
2222 #[test]
2223 fn test_version_increment() {
2224 let mut buf = Buffer::from_text("hello");
2225 let v1 = buf.version();
2226 buf.insert(" world");
2227 let v2 = buf.version();
2228 assert!(v2 > v1);
2229 }
2230
2231 #[test]
2232 fn test_fast_typing_merges_into_single_undo() {
2233 let mut buf = Buffer::from_text("");
2234
2235 buf.insert("h");
2236 buf.insert("e");
2237 buf.insert("l");
2238 buf.insert("l");
2239 buf.insert("o");
2240
2241 assert_eq!(buf.text(), "hello");
2242 assert_eq!(buf.history_position(), 1);
2244
2245 buf.undo();
2246 assert_eq!(buf.text(), "");
2247 }
2248
2249 #[test]
2250 fn test_fast_backspace_merges_into_single_undo() {
2251 let mut buf = Buffer::from_text("hello");
2252 buf.set_cursor(Position::new(0, 5));
2253
2254 buf.delete_backward();
2255 buf.delete_backward();
2256 buf.delete_backward();
2257
2258 assert_eq!(buf.text(), "he");
2259 assert_eq!(buf.history_position(), 1);
2260
2261 buf.undo();
2262 assert_eq!(buf.text(), "hello");
2263 }
2264
2265 #[test]
2266 fn test_structural_ops_break_merge_chain() {
2267 let mut buf = Buffer::from_text("");
2268
2269 buf.insert("a");
2270 buf.insert("b"); buf.insert_newline(); buf.insert("c"); assert_eq!(buf.text(), "ab\nc");
2275 assert_eq!(buf.history_position(), 3);
2277
2278 buf.undo();
2279 assert_eq!(buf.text(), "ab\n");
2280
2281 buf.undo();
2282 assert_eq!(buf.text(), "ab");
2283
2284 buf.undo();
2285 assert_eq!(buf.text(), "");
2286 buf.undo();
2287 assert_eq!(buf.text(), "");
2288 }
2289
2290 #[test]
2291 fn test_mixed_undo_steps() {
2292 let mut buf = Buffer::from_text("");
2293
2294 buf.insert("hello");
2295 buf.insert_newline();
2296 buf.insert("world");
2297
2298 buf.undo();
2299 assert_eq!(buf.text(), "hello\n");
2300
2301 buf.undo();
2302 assert_eq!(buf.text(), "hello");
2303
2304 buf.undo();
2305 assert_eq!(buf.text(), "");
2306 }
2307
2308 #[test]
2309 fn test_cross_line_insert_over_selection() {
2310 let mut buf = Buffer::from_text("hello\nworld");
2311 buf.set_selection(Some(Selection::new(
2312 Position::new(0, 2),
2313 Position::new(1, 3),
2314 )));
2315 buf.insert("X");
2316 assert_eq!(buf.text(), "heXld");
2317 }
2318
2319 #[test]
2320 fn test_cross_line_delete_backward_with_selection() {
2321 let mut buf = Buffer::from_text("hello\nworld");
2322 buf.set_selection(Some(Selection::new(
2323 Position::new(0, 5),
2324 Position::new(1, 0),
2325 )));
2326 buf.delete_backward();
2327 assert_eq!(buf.text(), "helloworld");
2328 }
2329
2330 #[test]
2331 fn test_cross_line_selection_delete_all() {
2332 let mut buf = Buffer::from_text("abc\ndef\nghi");
2333 buf.set_selection(Some(Selection::new(
2334 Position::new(0, 0),
2335 Position::new(2, 3),
2336 )));
2337 buf.insert("X");
2338 assert_eq!(buf.text(), "X");
2339 }
2340
2341 #[test]
2342 fn test_cross_line_selection_undo_redo() {
2343 let mut buf = Buffer::from_text("hello\nworld");
2344 buf.set_selection(Some(Selection::new(
2346 Position::new(0, 3),
2347 Position::new(1, 2),
2348 )));
2349 buf.insert("X");
2350 assert_eq!(buf.text(), "helXrld");
2351 assert_eq!(buf.cursor(), Position::new(0, 4)); buf.undo();
2353 assert_eq!(buf.text(), "hello\nworld");
2354 assert_eq!(buf.cursor(), Position::new(1, 2));
2356 buf.redo();
2357 assert_eq!(buf.text(), "helXrld");
2358 assert_eq!(buf.cursor(), Position::new(0, 4));
2359 }
2360
2361 #[test]
2362 fn test_offset_up_excludes_trailing_newline() {
2363 let buf = Buffer::from_text("hello\nworld");
2364 let pos = buf.offset_up(Position::new(1, 5));
2365 assert_eq!(pos, Position::new(0, 5));
2366 }
2367
2368 #[test]
2369 fn test_offset_up_clamps_to_shorter_line() {
2370 let buf = Buffer::from_text("hi\nworld");
2371 let pos = buf.offset_up(Position::new(1, 5));
2372 assert_eq!(pos, Position::new(0, 2));
2373 }
2374
2375 #[test]
2376 fn test_delete_forward_multibyte() {
2377 let mut buf = Buffer::from_text("αβγ");
2378 buf.set_cursor(Position::new(0, 1));
2379 buf.delete_forward();
2380 assert_eq!(buf.text(), "αγ");
2381 }
2382
2383 #[test]
2384 fn test_delete_forward_newline() {
2385 let mut buf = Buffer::from_text("abc\ndef");
2386 buf.set_cursor(Position::new(0, 3));
2387 buf.delete_forward();
2388 assert_eq!(buf.text(), "abcdef");
2389 }
2390
2391 #[test]
2392 fn test_pos_to_char_and_char_to_pos() {
2393 let buf = Buffer::from_text("hello\nworld");
2394 assert_eq!(buf.pos_to_char(Position::new(0, 3)), 3);
2395 assert_eq!(buf.pos_to_char(Position::new(1, 2)), 8);
2397 assert_eq!(buf.char_to_pos(3), Position::new(0, 3));
2398 assert_eq!(buf.char_to_pos(8), Position::new(1, 2));
2399 }
2400
2401 #[test]
2402 fn test_set_selection_normalizes_positions() {
2403 let mut buf = Buffer::from_text("hi\nworld");
2404 buf.set_selection(Some(Selection::new(
2405 Position::new(0, 100), Position::new(1, 3),
2407 )));
2408 let sel = buf.selection().unwrap();
2409 assert_eq!(sel.anchor, Position::new(0, 2));
2410 assert_eq!(sel.active, Position::new(1, 3));
2411 }
2412
2413 #[test]
2414 fn test_newline_undo_roundtrip() {
2415 let mut buf = Buffer::from_text("helloworld");
2416 buf.set_cursor(Position::new(0, 5));
2417 buf.insert_newline();
2418 assert_eq!(buf.text(), "hello\nworld");
2419 assert_eq!(buf.cursor(), Position::new(1, 0));
2420 buf.undo();
2421 assert_eq!(buf.text(), "helloworld");
2422 assert_eq!(buf.cursor(), Position::new(0, 5));
2423 }
2424
2425 #[test]
2428 fn test_insert_at_advances_cursor_when_at_position() {
2429 let mut buf = Buffer::from_text("hello");
2430 buf.set_cursor(Position::new(0, 5));
2431 buf.insert_at(Position::new(0, 5), " world");
2432 assert_eq!(buf.text(), "hello world");
2433 assert_eq!(buf.cursor(), Position::new(0, 11));
2434 }
2435
2436 #[test]
2437 fn test_insert_at_does_not_move_cursor_when_before_cursor() {
2438 let mut buf = Buffer::from_text("world");
2439 buf.set_cursor(Position::new(0, 5));
2440 buf.insert_at(Position::new(0, 0), "hello ");
2441 assert_eq!(buf.text(), "hello world");
2442 assert_eq!(buf.cursor(), Position::new(0, 5));
2443 }
2444
2445 #[test]
2448 fn test_selected_text_single_line() {
2449 let mut buf = Buffer::from_text("hello world");
2450 buf.set_selection(Some(Selection {
2451 anchor: Position::new(0, 0),
2452 active: Position::new(0, 5),
2453 }));
2454 assert_eq!(buf.selected_text(), "hello");
2455 }
2456
2457 #[test]
2458 fn test_selected_text_multiline() {
2459 let mut buf = Buffer::from_text("hello\nworld");
2460 buf.set_selection(Some(Selection {
2461 anchor: Position::new(0, 3),
2462 active: Position::new(1, 3),
2463 }));
2464 assert_eq!(buf.selected_text(), "lo\nwor");
2465 }
2466
2467 #[test]
2468 fn test_selected_text_empty_when_no_selection() {
2469 let buf = Buffer::from_text("hello");
2470 assert_eq!(buf.selected_text(), "");
2471 }
2472
2473 #[test]
2476 fn test_word_range_at_middle_of_word() {
2477 let buf = Buffer::from_text("hello world");
2478 let (start, end) = buf.word_range_at(Position::new(0, 2));
2479 assert_eq!(start, 2);
2481 assert_eq!(end, 5);
2482 }
2483
2484 #[test]
2485 fn test_word_range_at_on_space() {
2486 let buf = Buffer::from_text("hello world");
2487 let _ = buf.word_range_at(Position::new(0, 5));
2489 }
2490
2491 #[test]
2494 fn test_delete_range_same_position_is_noop() {
2495 let mut buf = Buffer::from_text("hello");
2496 let pos = Position::new(0, 2);
2497 buf.delete_range(pos, pos);
2498 assert_eq!(buf.text(), "hello");
2499 }
2500
2501 #[test]
2504 fn test_char_count() {
2505 let buf = Buffer::from_text("hello\nworld");
2506 assert_eq!(buf.char_count(), 11);
2507 }
2508
2509 #[test]
2510 fn test_current_line() {
2511 let mut buf = Buffer::from_text("hello\nworld");
2512 buf.set_cursor(Position::new(1, 0));
2513 assert_eq!(buf.current_line(), 1);
2514 }
2515
2516 #[test]
2517 fn test_slice() {
2518 let buf = Buffer::from_text("hello world");
2519 let s = buf.slice(Position::new(0, 0)..Position::new(0, 5));
2520 assert_eq!(s, "hello");
2521 }
2522
2523 #[test]
2526 fn test_history_len() {
2527 let mut buf = Buffer::from_text("");
2528 assert_eq!(buf.history_len(), 0);
2529 buf.insert("a");
2530 assert!(buf.history_len() > 0);
2531 }
2532
2533 #[test]
2536 fn test_scroll_to_cursor_scrolls_down_when_cursor_below_view() {
2537 let mut buf = Buffer::from_text("a\nb\nc\nd\ne");
2538 buf.set_cursor(Position::new(4, 0));
2539 buf.scroll_to_cursor(3);
2540 assert!(buf.scroll >= 2);
2541 }
2542
2543 #[test]
2544 fn test_scroll_to_cursor_scrolls_up_when_cursor_above_view() {
2545 let mut buf = Buffer::from_text("a\nb\nc\nd\ne");
2546 buf.scroll = 4;
2547 buf.set_cursor(Position::new(0, 0));
2548 buf.scroll_to_cursor(3);
2549 assert_eq!(buf.scroll, 0);
2550 }
2551
2552 #[test]
2555 fn test_offset_down_clamps_column_to_shorter_line() {
2556 let buf = Buffer::from_text("hello world\nhi");
2557 let pos = buf.offset_down(Position::new(0, 8));
2558 assert_eq!(pos, Position::new(1, 2));
2559 }
2560
2561 #[test]
2562 fn test_offset_down_at_last_line_stays() {
2563 let buf = Buffer::from_text("hello");
2564 let pos = buf.offset_down(Position::new(0, 3));
2565 assert_eq!(pos, Position::new(0, 3));
2566 }
2567
2568 #[test]
2569 fn test_offset_down_n_stops_at_last_line() {
2570 let buf = Buffer::from_text("a\nb\nc");
2571 let pos = buf.offset_down_n(Position::new(0, 0), 100);
2572 assert_eq!(pos.line, 2);
2573 }
2574
2575 #[test]
2578 fn test_offset_left_wraps_to_end_of_previous_line() {
2579 let buf = Buffer::from_text("hello\nworld");
2580 let pos = buf.offset_left(Position::new(1, 0));
2581 assert_eq!(pos, Position::new(0, 5));
2582 }
2583
2584 #[test]
2585 fn test_offset_right_wraps_to_start_of_next_line() {
2586 let buf = Buffer::from_text("hello\nworld");
2587 let pos = buf.offset_right(Position::new(0, 5));
2588 assert_eq!(pos, Position::new(1, 0));
2589 }
2590
2591 #[test]
2594 fn test_insert_text_raw_normalizes_crlf() {
2595 let mut buf = Buffer::from_text("");
2596 buf.insert_text_raw("hello\r\nworld");
2597 assert_eq!(buf.text(), "hello\nworld");
2598 }
2599
2600 #[test]
2601 fn test_insert_text_raw_normalizes_cr_only() {
2602 let mut buf = Buffer::from_text("");
2603 buf.insert_text_raw("hello\rworld");
2604 assert_eq!(buf.text(), "hello\nworld");
2605 }
2606
2607 #[test]
2610 fn test_replace_range_same_text_at_cursor_is_noop_for_history() {
2611 let mut buf = Buffer::from_text("hello");
2612 buf.set_cursor(Position::new(0, 0));
2613 let before_history_pos = buf.history_position();
2614 buf.replace_range(Position::new(0, 0), Position::new(0, 5), "hello");
2615 assert_eq!(buf.history_position(), before_history_pos);
2616 assert_eq!(buf.text(), "hello");
2617 }
2618
2619 #[test]
2622 fn test_delete_word_forward_from_whitespace() {
2623 let mut buf = Buffer::from_text("hello world");
2624 buf.set_cursor(Position::new(0, 5));
2625 buf.delete_word_forward();
2626 assert_eq!(buf.text(), "helloworld");
2627 }
2628
2629 #[test]
2632 fn test_delete_line_last_line_leaves_empty() {
2633 let mut buf = Buffer::from_text("hello");
2634 buf.set_cursor(Position::new(0, 0));
2635 buf.delete_line();
2636 assert_eq!(buf.line_count(), 1);
2637 assert_eq!(buf.line(0).unwrap(), "");
2638 }
2639
2640 fn settings_with_trim(trim: bool) -> (crate::settings::Settings, tempfile::TempDir) {
2646 let dir = tempfile::tempdir().unwrap();
2647 let project_config = dir.path().join(".oo").join("config.yaml");
2648 std::fs::create_dir(dir.path().join(".oo")).unwrap();
2649 let override_yaml = format!("editor:\n trim_on_save: {}\n", trim);
2650 std::fs::write(&project_config, &override_yaml).unwrap();
2651 let s = crate::settings::Settings::new(&project_config).unwrap();
2652 (s, dir)
2653 }
2654
2655 fn save_content(content: &str, trim: bool) -> String {
2657 let (settings, dir) = settings_with_trim(trim);
2658 let file = dir.path().join("test.txt");
2659 let mut buf = Buffer::from_text(content);
2660 buf.path = Some(file.clone());
2661 buf.save(&settings).expect("save should succeed");
2662 std::fs::read_to_string(&file).expect("file should exist after save")
2663 }
2664
2665 #[test]
2666 fn save_no_trailing_newline_is_preserved() {
2667 let on_disk = save_content("hello\nworld", false);
2669 assert_eq!(on_disk, "hello\nworld");
2670 }
2671
2672 #[test]
2673 fn save_single_trailing_newline_is_preserved() {
2674 let on_disk = save_content("hello\nworld\n", false);
2676 assert_eq!(on_disk, "hello\nworld\n");
2677 }
2678
2679 #[test]
2680 fn save_multiple_trailing_newlines_collapsed_to_one() {
2681 let on_disk = save_content("hello\nworld\n\n", false);
2683 assert_eq!(on_disk, "hello\nworld\n");
2684
2685 let on_disk = save_content("hello\nworld\n\n\n", false);
2686 assert_eq!(on_disk, "hello\nworld\n");
2687 }
2688
2689 #[test]
2690 fn save_trim_on_save_preserves_trailing_newline() {
2691 let on_disk = save_content("hello \nworld \n", true);
2694 assert_eq!(on_disk, "hello\nworld\n");
2695 }
2696
2697 #[test]
2698 fn save_trim_on_save_no_trailing_newline_not_added() {
2699 let on_disk = save_content("hello \nworld ", true);
2701 assert_eq!(on_disk, "hello\nworld");
2702 }
2703
2704 #[test]
2705 fn save_trim_on_save_collapses_extra_trailing_newlines() {
2706 let on_disk = save_content("hello\nworld\n\n\n", true);
2707 assert_eq!(on_disk, "hello\nworld\n");
2708 }
2709
2710 #[test]
2711 fn restore_clamps_stale_cursor_to_shorter_content() {
2712 use crate::editor::position::Position;
2717 use crate::editor::selection::Selection;
2718 let stale_selection = Some(Selection {
2719 anchor: Position::new(10, 3),
2720 active: Position::new(10, 5),
2721 });
2722 let short_lines = vec!["line0".to_owned(), "line1".to_owned(), "line2".to_owned()];
2723 let buf = Buffer::restore(
2724 std::path::PathBuf::from("/fake/path.rs"),
2725 short_lines,
2726 stale_selection,
2727 0,
2728 true, vec![],
2730 vec![],
2731 vec![],
2732 );
2733 assert!(buf.cursor().line < 3, "cursor.line OOB: {}", buf.cursor().line);
2735 let _ = buf.offset_right(buf.cursor());
2737 let _ = buf.offset_down(buf.cursor());
2738 let _ = buf.offset_line_end(buf.cursor());
2739 }
2740}