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);
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 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 }
294
295 pub fn id(&self) -> BufferId {
296 self.id
297 }
298
299 pub fn version(&self) -> Version {
300 self.version
301 }
302
303 pub fn snapshot(&self) -> BufferSnapshot {
304 BufferSnapshot {
305 text: self.text.clone(),
306 version: self.version,
307 }
308 }
309
310 fn line_type(&self) -> LineType {
311 LineType::LF
312 }
313
314 fn pos_to_byte(&self, pos: Position) -> usize {
315 let line_count = self.text.len_lines(self.line_type());
316 if pos.line >= line_count {
317 if pos.line == line_count && pos.column == 0 && line_count > 0 {
318 let last_line_idx = line_count - 1;
319 let last_line = self.text.line(last_line_idx, self.line_type());
320 if last_line.chars().last() == Some('\n') {
321 let last_line_start =
322 self.text.line_to_byte_idx(last_line_idx, self.line_type());
323 return last_line_start + last_line.len() - 1;
324 }
325 }
326 return self.text.len();
327 }
328 let line_start_byte = self.text.line_to_byte_idx(pos.line, self.line_type());
329 let line = self.text.line(pos.line, self.line_type());
330
331 let mut char_count = 0;
332 let mut byte_offset = 0;
333 for ch in line.chars() {
334 if char_count >= pos.column {
335 break;
336 }
337 byte_offset += ch.len_utf8();
338 char_count += 1;
339 }
340 if pos.column > char_count {
341 byte_offset = line.len();
342 }
343 line_start_byte + byte_offset
344 }
345
346 fn byte_to_pos(&self, byte_idx: usize) -> Position {
347 let clamped_byte_idx = byte_idx.min(self.text.len());
348 let line_count = self.text.len_lines(self.line_type());
349
350 if clamped_byte_idx == self.text.len() {
351 if line_count == 0 {
352 return Position::new(0, 0);
353 }
354 let last_line_idx = line_count - 1;
355 let last_line = self.text.line(last_line_idx, self.line_type());
356 let last_line_len = last_line.len_chars();
357 let has_trailing_nl = last_line.chars().last() == Some('\n');
358 if has_trailing_nl {
359 return Position::new(last_line_idx + 1, 0);
360 }
361 return Position::new(last_line_idx, last_line_len);
362 }
363
364 let byte_idx = clamped_byte_idx;
365 let line_idx = self.text.byte_to_line_idx(byte_idx, self.line_type());
366 let line_start_byte = self.text.line_to_byte_idx(line_idx, self.line_type());
367 let char_idx = self.text.byte_to_char_idx(byte_idx);
368 let line_start_char = self.text.byte_to_char_idx(line_start_byte);
369 let line = self.text.line(line_idx, self.line_type());
370 let line_len_chars = line.len_chars();
371 let line_has_trailing_nl = line.chars().last() == Some('\n');
372
373 let column = char_idx - line_start_char;
374 if column >= line_len_chars && line_has_trailing_nl {
375 return Position::new(line_idx + 1, 0);
376 }
377 Position::new(line_idx, column)
378 }
379
380 fn cursor_to_byte(&self) -> usize {
381 self.pos_to_byte(self.cursor)
382 }
383
384 pub fn pos_to_char(&self, pos: Position) -> usize {
386 let line_start_byte = self.text.line_to_byte_idx(pos.line, self.line_type());
387 let line_start_char = self.text.byte_to_char_idx(line_start_byte);
388 line_start_char + pos.column
389 }
390
391 pub fn char_to_pos(&self, char_idx: usize) -> Position {
393 let byte_idx = self.text.char_to_byte_idx(char_idx).min(self.text.len());
394 self.byte_to_pos(byte_idx)
395 }
396
397 pub fn cursor(&self) -> Position {
398 self.cursor
399 }
400
401 pub fn selection(&self) -> Option<Selection> {
402 self.selection
403 }
404
405 pub fn set_cursor(&mut self, pos: Position) {
406 self.selection = None;
407 self.cursor = self.normalize_position(pos);
408 }
409
410 pub fn set_selection(&mut self, selection: Option<Selection>) {
411 self.selection = selection.map(|sel| Selection {
412 anchor: self.normalize_position(sel.anchor),
413 active: self.normalize_position(sel.active),
414 });
415 if let Some(ref sel) = self.selection {
416 self.cursor = sel.active;
417 }
418 }
419
420 pub fn normalize_position(&self, pos: Position) -> Position {
422 let line_count = self.text.len_lines(self.line_type());
423 if line_count == 0 {
424 return Position::new(0, 0);
425 }
426 let line = pos.line.min(line_count - 1);
427 let line_len = self.line_display_len(line);
428 Position::new(line, pos.column.min(line_len))
429 }
430
431 pub fn normalize_cursor(&mut self) {
432 self.cursor = self.normalize_position(self.cursor);
433 }
434
435 fn line_display_len(&self, line_idx: usize) -> usize {
437 let line = self.text.line(line_idx, self.line_type());
438 let has_trailing_nl = line.chars().last() == Some('\n');
439 line.len_chars().saturating_sub(has_trailing_nl as usize)
440 }
441
442 pub fn line_count(&self) -> usize {
443 self.text.len_lines(self.line_type()).max(1)
444 }
445
446 pub fn current_line(&self) -> usize {
447 self.cursor.line
448 }
449
450 pub fn char_count(&self) -> usize {
451 self.text.len_chars()
452 }
453
454 pub fn line(&self, line_idx: usize) -> Option<String> {
455 let actual_line_count = self.text.len_lines(self.line_type());
456 if line_idx >= actual_line_count {
457 return None;
458 }
459 let mut line_text = self.text.line(line_idx, self.line_type()).to_string();
460 if line_text.ends_with('\n') {
461 line_text.pop();
462 }
463 if line_text.ends_with('\r') {
464 line_text.pop();
465 }
466 Some(line_text)
467 }
468
469 pub fn lines(&self) -> Vec<String> {
470 (0..self.text.len_lines(self.line_type()))
471 .map(|i| {
472 let mut s: String = self.text.line(i, self.line_type()).into();
473 if s.ends_with('\n') {
474 s.pop();
475 }
476 if s.ends_with('\r') {
477 s.pop();
478 }
479 s
480 })
481 .collect()
482 }
483
484 pub fn lines_arc(&mut self) -> Arc<Vec<String>> {
488 if let Some(ref cached) = self.cached_lines {
489 return Arc::clone(cached);
490 }
491 let v = self.lines();
492 let arc = Arc::new(v);
493 self.cached_lines = Some(arc.clone());
494 arc
495 }
496
497 pub fn is_empty(&self) -> bool {
498 self.text.len_chars() == 0
499 }
500
501 pub fn is_dirty(&self) -> bool {
502 self.dirty
503 }
504
505 fn increment_version(&mut self) {
506 self.version.increment();
507 self.dirty = true;
508 self.file_hash = None;
509 self.file_mtime = None;
510 self.file_size = None;
511 self.cached_lines = None;
512 }
513
514 pub fn can_undo(&self) -> bool {
515 self.history.can_undo()
516 }
517
518 pub fn can_redo(&self) -> bool {
519 self.history.can_redo()
520 }
521
522 pub fn history_len(&self) -> usize {
523 self.history.len()
524 }
525
526 pub fn history_position(&self) -> usize {
527 self.history.current_position()
528 }
529
530 pub fn apply_change(&mut self, change: ChangeSet, kind: ChangeKind) {
531 if change.is_empty() {
532 return;
533 }
534
535 if let Some(tx) = &mut self.active_tx {
538 tx.ops.extend(change.ops);
539 return;
540 }
541
542 let merged = matches!(kind, ChangeKind::InsertText | ChangeKind::DeleteText)
547 && self.history.try_merge(change.clone(), kind, self.version);
548
549 if !merged {
550 self.history.push(change, kind, self.version);
551 }
552 }
553
554 pub fn begin_transaction(&mut self, kind: ChangeKind) {
562 debug_assert!(
563 self.active_tx.is_none(),
564 "begin_transaction called while a transaction is already active; \
565 nested transactions are not supported"
566 );
567 if self.active_tx.is_some() {
568 return;
569 }
570 self.active_tx = Some(Transaction {
571 ops: Vec::new(),
572 kind,
573 before_cursor: self.cursor,
574 before_selection: self.selection,
575 });
576 }
577
578 pub fn end_transaction(&mut self) {
591 let tx = match self.active_tx.take() {
592 Some(tx) => tx,
593 None => return,
594 };
595 if tx.ops.is_empty() {
596 return;
597 }
598 let change = ChangeSet::new(
599 tx.ops,
600 tx.before_cursor,
601 self.cursor,
602 tx.before_selection,
603 self.selection,
604 );
605 self.history.push(change, tx.kind, self.version);
608 self.increment_version();
611 }
612
613 pub fn abort_transaction(&mut self) {
621 self.active_tx = None;
622 }
623
624 pub fn in_transaction(&self) -> bool {
626 self.active_tx.is_some()
627 }
628
629 pub fn insert(&mut self, text: &str) {
630 if let Some(sel) = self.selection {
632 let start = sel.min();
633 let end = sel.max();
634 let before_cursor = self.cursor;
635 let before_selection = self.selection;
636
637 let start_byte = self.pos_to_byte(start);
638 let end_byte = self.pos_to_byte(end).min(self.text.len());
639 let old_text: String = self.text.slice(start_byte..end_byte).into();
640
641 self.text.remove(start_byte..end_byte);
642 self.text.insert(start_byte, text);
643
644 let new_char_idx = self.text.byte_to_char_idx(start_byte) + text.chars().count();
645 let after_cursor = self.byte_to_pos(
646 self.text
647 .char_to_byte_idx(new_char_idx)
648 .min(self.text.len()),
649 );
650 self.cursor = after_cursor;
651 self.selection = None;
652
653 let op = BufferOp::Replace {
654 range: start..end,
655 text: text.to_string(),
656 old_text,
657 end_position: after_cursor,
658 };
659 let change = ChangeSet::new(
660 vec![op],
661 before_cursor,
662 after_cursor,
663 before_selection,
664 None,
665 );
666 self.apply_change(change, ChangeKind::Replace);
667 self.increment_version();
668 return;
669 }
670
671 let before_cursor = self.cursor;
672 let before_selection = self.selection;
673
674 let byte_idx = self.cursor_to_byte();
675 let old_char_idx = self.text.byte_to_char_idx(byte_idx);
676 self.text.insert(byte_idx, text);
677
678 let new_char_idx = old_char_idx + text.chars().count();
679 let new_char_byte = self
680 .text
681 .char_to_byte_idx(new_char_idx)
682 .min(self.text.len());
683 let new_cursor = self.byte_to_pos(new_char_byte);
684 self.cursor = new_cursor;
685
686 let after_cursor = new_cursor;
687 let after_selection = None;
688
689 let range = before_cursor..after_cursor;
690 let op = BufferOp::Replace {
691 range,
692 text: text.to_string(),
693 old_text: String::new(),
694 end_position: after_cursor,
695 };
696
697 let change = ChangeSet::new(
698 vec![op],
699 before_cursor,
700 after_cursor,
701 before_selection,
702 after_selection,
703 );
704
705 self.apply_change(change, ChangeKind::InsertText);
706 self.increment_version();
707 }
708
709 fn delete_selection_if_active(&mut self) -> bool {
711 if let Some(sel) = self.selection {
712 self.delete_range(sel.min(), sel.max());
713 true
714 } else {
715 false
716 }
717 }
718
719 pub fn delete_backward(&mut self) {
720 if self.delete_selection_if_active() {
721 return;
722 }
723
724 let byte_idx = self.cursor_to_byte();
725 if byte_idx == 0 {
726 return;
727 }
728
729 let before_cursor = self.cursor;
730 let before_selection = self.selection;
731
732 let char_idx = self.text.byte_to_char_idx(byte_idx);
733 if char_idx == 0 {
734 return;
735 }
736 let prev_char_idx = char_idx - 1;
737 let del_start_byte = self.text.char_to_byte_idx(prev_char_idx);
738 let del_end_byte = byte_idx;
739 let prev_char_byte_idx = del_start_byte;
740 let prev_char = self.text.char(prev_char_byte_idx);
741
742 let new_cursor_pos: Position;
743 if prev_char == '\n' {
744 let line_idx = self.text.byte_to_line_idx(byte_idx, self.line_type());
745 if line_idx > 0 {
746 let new_cursor_col = self.line_display_len(line_idx - 1);
747 new_cursor_pos = Position::new(line_idx - 1, new_cursor_col);
748 } else {
749 new_cursor_pos = before_cursor;
750 }
751 } else {
752 let new_col = before_cursor.column.saturating_sub(1);
753 new_cursor_pos = Position::new(before_cursor.line, new_col);
754 }
755
756 let deleted_text: String = self.text.slice(del_start_byte..del_end_byte).into();
757
758 self.text.remove(del_start_byte..del_end_byte);
759
760 self.cursor = new_cursor_pos;
761 self.selection = None;
762
763 let range = new_cursor_pos..before_cursor;
764 let op = BufferOp::Replace {
765 range,
766 text: String::new(),
767 old_text: deleted_text,
768 end_position: new_cursor_pos,
769 };
770
771 let change = ChangeSet::new(
772 vec![op],
773 before_cursor,
774 new_cursor_pos,
775 before_selection,
776 None,
777 );
778
779 self.apply_change(change, ChangeKind::DeleteText);
780 self.increment_version();
781 }
782
783 pub fn delete_forward(&mut self) {
784 if self.delete_selection_if_active() {
785 return;
786 }
787
788 let byte_idx = self.cursor_to_byte();
789 if byte_idx >= self.text.len() {
790 return;
791 }
792
793 let before_cursor = self.cursor;
794 let before_selection = self.selection;
795
796 let char_idx = self.text.byte_to_char_idx(byte_idx);
798 let next_byte = self
799 .text
800 .char_to_byte_idx(char_idx + 1)
801 .min(self.text.len());
802
803 let deleted_text: String = self.text.slice(byte_idx..next_byte).into();
804 self.text.remove(byte_idx..next_byte);
805
806 let after_cursor = before_cursor;
807 let after_selection = None;
808
809 let range = before_cursor..after_cursor;
810 let op = BufferOp::Replace {
811 range,
812 text: String::new(),
813 old_text: deleted_text,
814 end_position: before_cursor,
815 };
816
817 let change = ChangeSet::new(
818 vec![op],
819 before_cursor,
820 after_cursor,
821 before_selection,
822 after_selection,
823 );
824
825 self.apply_change(change, ChangeKind::DeleteText);
826 self.increment_version();
827 }
828
829 pub fn delete_range(&mut self, start_pos: Position, end_pos: Position) {
830 if start_pos == end_pos {
831 return;
832 }
833
834 let start_byte = self.pos_to_byte(start_pos);
835 let end_byte = self.pos_to_byte(end_pos).min(self.text.len());
836
837 if start_byte >= self.text.len() || start_byte >= end_byte {
838 return;
839 }
840
841 let before_cursor = self.cursor;
842 let before_selection = self.selection;
843
844 let deleted_text: String = self.text.slice(start_byte..end_byte).into();
845 self.text.remove(start_byte..end_byte);
846
847 self.cursor = start_pos;
848 self.selection = None;
849
850 let range = start_pos..end_pos;
851 let op = BufferOp::Replace {
852 range,
853 text: String::new(),
854 old_text: deleted_text,
855 end_position: start_pos,
856 };
857
858 let change = ChangeSet::new(vec![op], before_cursor, start_pos, before_selection, None);
859
860 self.apply_change(change, ChangeKind::DeleteText);
861 self.increment_version();
862 }
863
864 pub fn replace_range(&mut self, start: Position, end: Position, text: &str) {
865 let start_byte = self.pos_to_byte(start);
866 let end_byte = self.pos_to_byte(end).min(self.text.len());
867
868 if start_byte >= self.text.len() && start != end {
869 return;
870 }
871
872 let before_cursor = self.cursor;
873 let before_selection = self.selection;
874
875 let actual_end_byte = end_byte.min(self.text.len());
876 let old_text: String = self.text.slice(start_byte..actual_end_byte).into();
877
878 if old_text == text && before_cursor == start {
879 return;
880 }
881
882 self.text.remove(start_byte..actual_end_byte);
883 self.text.insert(start_byte, text);
884
885 let new_char_idx = self.text.byte_to_char_idx(start_byte) + text.chars().count();
886 let after_cursor = self.byte_to_pos(self.text.char_to_byte_idx(new_char_idx));
887 self.cursor = after_cursor;
888 self.selection = None;
889
890 let range = start..end;
891 let op = BufferOp::Replace {
892 range,
893 text: text.to_string(),
894 old_text,
895 end_position: after_cursor,
896 };
897
898 let change = ChangeSet::new(
899 vec![op],
900 before_cursor,
901 after_cursor,
902 before_selection,
903 None,
904 );
905
906 self.apply_change(change, ChangeKind::Replace);
907 self.increment_version();
908 }
909
910 pub fn insert_newline(&mut self) {
911 let before_cursor = self.cursor;
912 let before_selection = self.selection;
913
914 let byte_idx = self.cursor_to_byte();
915 self.text.insert(byte_idx, "\n");
916
917 let new_char_idx = self.text.byte_to_char_idx(byte_idx) + 1;
918 let new_cursor_byte = self
919 .text
920 .char_to_byte_idx(new_char_idx)
921 .min(self.text.len());
922 let new_cursor = self.byte_to_pos(new_cursor_byte);
923 self.cursor = new_cursor;
924
925 let range = before_cursor..new_cursor;
926 let op = BufferOp::Replace {
927 range,
928 text: "\n".to_string(),
929 old_text: String::new(),
930 end_position: new_cursor,
931 };
932
933 let change = ChangeSet::new(vec![op], before_cursor, new_cursor, before_selection, None);
934
935 self.apply_change(change, ChangeKind::Structural);
936 self.increment_version();
937 }
938
939 pub fn insert_newline_with_indent(&mut self, _use_spaces: bool, _indent_width: usize) {
940 let leading_indent = if self.cursor.line < self.text.len_lines(self.line_type()) {
943 let line = self.text.line(self.cursor.line, self.line_type());
944 line.chars()
945 .take_while(|c| *c == ' ' || *c == '\t')
946 .collect::<String>()
947 } else {
948 String::new()
949 };
950
951 let text = format!("\n{}", leading_indent);
954
955 let before_cursor = self.cursor;
956 let before_selection = self.selection;
957
958 let byte_idx = self.cursor_to_byte();
959 self.text.insert(byte_idx, &text);
960
961 let new_char_idx = self.text.byte_to_char_idx(byte_idx) + text.chars().count();
962 let new_cursor = self.byte_to_pos(self.text.char_to_byte_idx(new_char_idx));
963 self.cursor = new_cursor;
964
965 let range = before_cursor..new_cursor;
966 let op = BufferOp::Replace {
967 range,
968 text,
969 old_text: String::new(),
970 end_position: new_cursor,
971 };
972
973 let change = ChangeSet::new(vec![op], before_cursor, new_cursor, before_selection, None);
974
975 self.apply_change(change, ChangeKind::Structural);
976 self.increment_version();
977 }
978
979 pub fn delete_word_backward(&mut self) {
980 let end_byte = self.cursor_to_byte();
981 if end_byte == 0 {
982 return;
983 }
984
985 let before_cursor = self.cursor;
986 let before_selection = self.selection;
987
988 let start_pos = self.word_boundary_prev(self.cursor);
990 let start_byte = self.pos_to_byte(start_pos);
991
992 if start_byte < end_byte {
993 let deleted_text: String = self.text.slice(start_byte..end_byte).into();
994 self.text.remove(start_byte..end_byte);
995 let new_pos = self.byte_to_pos(start_byte);
996 self.cursor = new_pos;
997
998 let range = new_pos..before_cursor;
999 let op = BufferOp::Replace {
1000 range,
1001 text: String::new(),
1002 old_text: deleted_text,
1003 end_position: new_pos,
1004 };
1005
1006 let change = ChangeSet::new(vec![op], before_cursor, new_pos, before_selection, None);
1007
1008 self.apply_change(change, ChangeKind::DeleteText);
1009 self.increment_version();
1010 }
1011 }
1012
1013 pub fn delete_word_forward(&mut self) {
1014 let start = self.cursor_to_byte();
1015 if start >= self.text.len() {
1016 return;
1017 }
1018
1019 let before_cursor = self.cursor;
1020 let before_selection = self.selection;
1021
1022 let mut char_idx = self.text.byte_to_char_idx(start);
1023 let end_char = self.text.len_chars();
1024
1025 let first_char = self.text.char(self.text.char_to_byte_idx(char_idx));
1026 let at_whitespace = first_char.is_whitespace();
1027
1028 while char_idx < end_char
1029 && self
1030 .text
1031 .char(self.text.char_to_byte_idx(char_idx))
1032 .is_whitespace()
1033 {
1034 char_idx += 1;
1035 }
1036
1037 if !at_whitespace {
1038 while char_idx < end_char {
1039 let curr_byte = self.text.char_to_byte_idx(char_idx);
1040 if self.text.char(curr_byte).is_whitespace() {
1041 break;
1042 }
1043 char_idx += 1;
1044 }
1045
1046 while char_idx < end_char
1047 && self
1048 .text
1049 .char(self.text.char_to_byte_idx(char_idx))
1050 .is_whitespace()
1051 {
1052 char_idx += 1;
1053 }
1054 }
1055
1056 let end_byte = if char_idx < end_char {
1057 self.text.char_to_byte_idx(char_idx)
1058 } else {
1059 self.text.len()
1060 };
1061 let start_byte = self
1062 .text
1063 .char_to_byte_idx(self.text.byte_to_char_idx(start));
1064
1065 if start_byte < end_byte {
1066 let deleted_text: String = self.text.slice(start_byte..end_byte).into();
1067 self.text.remove(start_byte..end_byte);
1068
1069 let range = before_cursor..before_cursor;
1070 let op = BufferOp::Replace {
1071 range,
1072 text: String::new(),
1073 old_text: deleted_text,
1074 end_position: before_cursor,
1075 };
1076
1077 let change = ChangeSet::new(
1078 vec![op],
1079 before_cursor,
1080 before_cursor,
1081 before_selection,
1082 None,
1083 );
1084
1085 self.apply_change(change, ChangeKind::DeleteText);
1086 self.increment_version();
1087 }
1088 }
1089
1090 pub fn word_range_at(&self, pos: Position) -> (usize, usize) {
1091 let byte_idx = self.pos_to_byte(pos);
1092 let line_start_byte = self.text.line_to_byte_idx(pos.line, self.line_type());
1093 let line = self.text.line(pos.line, self.line_type());
1094
1095 let mut word_start = 0;
1096 let mut word_end = line.len_chars();
1097 let mut in_word = false;
1098
1099 for (i, (byte_offset, ch)) in line.char_indices().enumerate() {
1100 let abs_byte = line_start_byte + byte_offset;
1101 if abs_byte >= byte_idx && ch.is_alphanumeric() && !in_word {
1102 word_start = i;
1103 in_word = true;
1104 }
1105 if in_word && !ch.is_alphanumeric() {
1106 word_end = i;
1107 break;
1108 }
1109 }
1110
1111 (word_start, word_end)
1112 }
1113
1114 pub fn insert_at(&mut self, pos: Position, text: &str) {
1115 let before_cursor = self.cursor;
1116 let before_selection = self.selection;
1117
1118 let byte_idx = self.pos_to_byte(pos);
1119 self.text.insert(byte_idx, text);
1120
1121 let new_char_idx = self.text.byte_to_char_idx(byte_idx) + text.chars().count();
1122 let after_cursor = self.byte_to_pos(self.text.char_to_byte_idx(new_char_idx));
1123 if pos == before_cursor {
1124 self.cursor = after_cursor;
1125 }
1126
1127 let range = pos..after_cursor;
1128 let op = BufferOp::Replace {
1129 range,
1130 text: text.to_string(),
1131 old_text: String::new(),
1132 end_position: after_cursor,
1133 };
1134
1135 let change = ChangeSet::new(
1136 vec![op],
1137 before_cursor,
1138 after_cursor,
1139 before_selection,
1140 None,
1141 );
1142
1143 self.apply_change(change, ChangeKind::InsertText);
1144 self.increment_version();
1145 }
1146
1147 pub fn insert_text_raw(&mut self, text: &str) {
1148 let normalized = text.replace("\r\n", "\n").replace('\r', "\n");
1149 self.insert(&normalized);
1150 }
1151
1152 pub fn delete_line(&mut self) -> String {
1153 let line_idx = self.cursor.line;
1154 if line_idx >= self.text.len_lines(self.line_type()) {
1155 return String::new();
1156 }
1157
1158 let line_start = self.text.line_to_byte_idx(line_idx, self.line_type());
1159 let line_end = if line_idx + 1 < self.text.len_lines(self.line_type()) {
1160 self.text.line_to_byte_idx(line_idx + 1, self.line_type())
1161 } else {
1162 self.text.len()
1163 };
1164
1165 let deleted: String = self.text.slice(line_start..line_end).into();
1166
1167 let before_cursor = self.cursor;
1168 let before_selection = self.selection;
1169
1170 self.text.remove(line_start..line_end);
1171
1172 if self.text.len_lines(self.line_type()) == 0 {
1173 self.text.insert(0, "");
1174 }
1175
1176 let new_line_idx = line_idx.min(self.text.len_lines(self.line_type()).saturating_sub(1));
1177 let new_pos = Position::new(new_line_idx, 0);
1178 self.cursor = new_pos;
1179 self.normalize_cursor();
1180
1181 let range = new_pos..before_cursor;
1182 let op = BufferOp::Replace {
1183 range,
1184 text: String::new(),
1185 old_text: deleted.clone(),
1186 end_position: new_pos,
1187 };
1188
1189 let change = ChangeSet::new(vec![op], before_cursor, new_pos, before_selection, None);
1190
1191 self.apply_change(change, ChangeKind::DeleteText);
1192 self.increment_version();
1193
1194 deleted
1195 }
1196
1197 pub fn selected_text_all(&self) -> String {
1198 self.selected_text()
1199 }
1200
1201 pub fn cursor_left(&mut self) -> Position {
1202 let pos = self.offset_left(self.cursor);
1203 self.cursor = pos;
1204 self.cursor
1205 }
1206
1207 pub fn offset_left(&self, pos: Position) -> Position {
1208 if pos.column > 0 {
1209 Position::new(pos.line, pos.column - 1)
1210 } else if pos.line > 0 {
1211 let prev_line = pos.line - 1;
1212 let prev_line_len = self.line_display_len(prev_line);
1213 Position::new(prev_line, prev_line_len)
1214 } else {
1215 pos
1216 }
1217 }
1218
1219 pub fn cursor_right(&mut self) -> Position {
1220 let pos = self.offset_right(self.cursor);
1221 self.cursor = pos;
1222 self.cursor
1223 }
1224
1225 pub fn offset_right(&self, pos: Position) -> Position {
1226 let line_len = self.line_display_len(pos.line);
1227 if pos.column < line_len {
1228 Position::new(pos.line, pos.column + 1)
1229 } else if pos.line < self.text.len_lines(self.line_type()) - 1 {
1230 Position::new(pos.line + 1, 0)
1231 } else {
1232 pos
1233 }
1234 }
1235
1236 fn apply_movement(&mut self, new_pos: Position) -> Position {
1238 self.cursor = new_pos;
1239 self.cursor
1240 }
1241
1242 pub fn cursor_up(&mut self) -> Position {
1243 self.apply_movement(self.offset_up(self.cursor))
1244 }
1245
1246 pub fn offset_up(&self, pos: Position) -> Position {
1247 if pos.line == 0 { pos } else { self.offset_up_n(pos, 1) }
1248 }
1249
1250 pub fn cursor_down(&mut self) -> Position {
1251 self.apply_movement(self.offset_down(self.cursor))
1252 }
1253
1254 pub fn offset_down(&self, pos: Position) -> Position {
1255 if pos.line >= self.text.len_lines(self.line_type()).saturating_sub(1) {
1256 pos
1257 } else {
1258 self.offset_down_n(pos, 1)
1259 }
1260 }
1261
1262 pub fn cursor_up_n(&mut self, n: usize) -> Position {
1263 self.apply_movement(self.offset_up_n(self.cursor, n))
1264 }
1265
1266 pub fn offset_up_n(&self, pos: Position, n: usize) -> Position {
1267 let new_line = pos.line.saturating_sub(n);
1268 let line_len = self.line_display_len(new_line);
1269 Position::new(new_line, pos.column.min(line_len))
1270 }
1271
1272 pub fn cursor_down_n(&mut self, n: usize) -> Position {
1273 self.apply_movement(self.offset_down_n(self.cursor, n))
1274 }
1275
1276 pub fn offset_down_n(&self, pos: Position, n: usize) -> Position {
1277 let new_line = (pos.line + n).min(self.text.len_lines(self.line_type()).saturating_sub(1));
1278 let line_len = self.line_display_len(new_line);
1279 Position::new(new_line, pos.column.min(line_len))
1280 }
1281
1282 pub fn cursor_page_up(&mut self, height: usize) -> Position {
1283 self.apply_movement(self.offset_up_n(self.cursor, height))
1284 }
1285
1286 pub fn cursor_page_down(&mut self, height: usize) -> Position {
1287 self.apply_movement(self.offset_down_n(self.cursor, height))
1288 }
1289
1290 pub fn cursor_line_start(&mut self) -> Position {
1291 self.cursor.column = 0;
1292 self.cursor
1293 }
1294
1295 pub fn offset_line_start(&self, pos: Position) -> Position {
1296 Position::new(pos.line, 0)
1297 }
1298
1299 pub fn cursor_line_end(&mut self) -> Position {
1300 self.cursor.column = self.line_display_len(self.cursor.line);
1301 self.cursor
1302 }
1303
1304 pub fn offset_line_end(&self, pos: Position) -> Position {
1305 Position::new(pos.line, self.line_display_len(pos.line))
1306 }
1307
1308 pub fn cursor_word_prev(&mut self) -> Position {
1309 self.apply_movement(self.word_boundary_prev(self.cursor))
1310 }
1311
1312 pub fn cursor_word_next(&mut self) -> Position {
1313 self.apply_movement(self.word_boundary_next_for_selection(self.cursor))
1314 }
1315
1316 pub fn word_boundary_prev(&self, pos: Position) -> Position {
1317 if pos.line > 0 {
1319 let line_slice = self.text.line(pos.line, self.line_type());
1320 let mut first_non_ws_col: Option<usize> = None;
1321 for (i, c) in line_slice.chars().enumerate() {
1322 if !c.is_whitespace() || c == '\n' {
1323 first_non_ws_col = Some(i);
1324 break;
1325 }
1326 }
1327 if pos.column == 0 || first_non_ws_col == Some(pos.column) {
1328 let prev_line = pos.line - 1;
1329 return Position::new(prev_line, self.line_display_len(prev_line));
1330 }
1331 }
1332
1333 let line_start_byte = self.text.line_to_byte_idx(pos.line, self.line_type());
1334 let byte_idx = line_start_byte + self.text.char_to_byte_idx(pos.column);
1335
1336 if byte_idx == 0 {
1337 return pos;
1338 }
1339
1340 let mut char_idx = self.text.byte_to_char_idx(byte_idx);
1342 if char_idx == 0 {
1343 return pos;
1344 }
1345 char_idx = char_idx.saturating_sub(1);
1346
1347 while char_idx > 0 {
1349 let c_byte = self.text.char_to_byte_idx(char_idx);
1350 let c = self.text.char(c_byte);
1351 if !c.is_whitespace() || c == '\n' {
1352 break;
1353 }
1354 char_idx = char_idx.saturating_sub(1);
1355 }
1356
1357 let c_byte = self.text.char_to_byte_idx(char_idx);
1359 let first_char = self.text.char(c_byte);
1360 let is_punct = first_char.is_ascii_punctuation();
1361
1362 while char_idx > 0 {
1364 let prev_byte = self.text.char_to_byte_idx(char_idx.saturating_sub(1));
1365 let prev_char = self.text.char(prev_byte);
1366 if is_punct {
1367 if !prev_char.is_ascii_punctuation() {
1368 break;
1369 }
1370 } else if prev_char.is_whitespace() || prev_char.is_ascii_punctuation() {
1371 break;
1372 }
1373 char_idx = char_idx.saturating_sub(1);
1374 }
1375
1376 self.byte_to_pos(self.text.char_to_byte_idx(char_idx))
1377 }
1378
1379 pub fn word_boundary_next(&self, pos: Position) -> Position {
1380 self.word_boundary_next_impl(pos, true)
1381 }
1382
1383 pub fn word_boundary_next_for_selection(&self, pos: Position) -> Position {
1384 self.word_boundary_next_impl(pos, false)
1385 }
1386
1387 fn word_boundary_next_impl(&self, mut pos: Position, skip_whitespace_after: bool) -> Position {
1388 let end_char = self.text.len_chars();
1389
1390 let mut line_start_byte = self.text.line_to_byte_idx(pos.line, self.line_type());
1393 let mut line_start_char = self.text.byte_to_char_idx(line_start_byte);
1394 let mut line_len_chars = self.text.line(pos.line, self.line_type()).len_chars();
1395 let line_slice = self.text.line(pos.line, self.line_type());
1396 let mut printable_line_len = if line_slice.chars().last() == Some('\n') {
1399 line_len_chars.saturating_sub(1)
1400 } else {
1401 line_len_chars
1402 };
1403 let mut printable_line_end_char_idx = line_start_char + printable_line_len;
1404
1405 let mut char_idx = self.pos_to_char(pos);
1406
1407 if char_idx >= end_char {
1409 return pos;
1410 }
1411
1412
1413 let total_lines = self.text.len_lines(self.line_type());
1416 if char_idx >= printable_line_end_char_idx {
1417 if pos.line + 1 >= total_lines {
1418 return Position::new(pos.line, printable_line_len);
1420 }
1421
1422 pos = Position::new(pos.line + 1, 0);
1424 line_start_byte = self.text.line_to_byte_idx(pos.line, self.line_type());
1425 line_start_char = self.text.byte_to_char_idx(line_start_byte);
1426 line_len_chars = self.text.line(pos.line, self.line_type()).len_chars();
1427 let line_slice = self.text.line(pos.line, self.line_type());
1428 printable_line_len = if line_slice.chars().last() == Some('\n') {
1429 line_len_chars.saturating_sub(1)
1430 } else {
1431 line_len_chars
1432 };
1433 printable_line_end_char_idx = line_start_char + printable_line_len;
1434 let mut next_char_idx = self.pos_to_char(pos);
1438 while next_char_idx < end_char
1439 && self
1440 .text
1441 .char(self.text.char_to_byte_idx(next_char_idx))
1442 .is_whitespace()
1443 {
1444 next_char_idx += 1;
1445 if next_char_idx >= printable_line_end_char_idx {
1446 return Position::new(pos.line, printable_line_len);
1447 }
1448 }
1449 if next_char_idx >= end_char {
1450 return pos;
1451 }
1452 return self.byte_to_pos(self.text.char_to_byte_idx(next_char_idx));
1453 }
1454
1455 while char_idx < end_char
1458 && self
1459 .text
1460 .char(self.text.char_to_byte_idx(char_idx))
1461 .is_whitespace()
1462 {
1463 char_idx += 1;
1464 if char_idx >= printable_line_end_char_idx {
1465 return Position::new(pos.line, printable_line_len);
1466 }
1467 }
1468
1469 if char_idx >= end_char {
1470 return pos;
1471 }
1472
1473 let first_char = self.text.char(self.text.char_to_byte_idx(char_idx));
1475 let is_punct = first_char.is_ascii_punctuation();
1476
1477 while char_idx < end_char {
1480 let c = self.text.char(self.text.char_to_byte_idx(char_idx));
1481 if is_punct {
1482 if !c.is_ascii_punctuation() {
1483 break;
1484 }
1485 } else if c.is_whitespace() || c.is_ascii_punctuation() {
1486 break;
1487 }
1488 char_idx += 1;
1489 if char_idx >= printable_line_end_char_idx {
1490 return Position::new(pos.line, printable_line_len);
1491 }
1492 }
1493
1494 if skip_whitespace_after {
1495 while char_idx < end_char
1496 && self
1497 .text
1498 .char(self.text.char_to_byte_idx(char_idx))
1499 .is_whitespace()
1500 {
1501 char_idx += 1;
1502 if char_idx >= printable_line_end_char_idx {
1503 return Position::new(pos.line, printable_line_len);
1504 }
1505 }
1506 }
1507
1508 if char_idx >= printable_line_end_char_idx {
1510 return Position::new(pos.line, printable_line_len);
1511 }
1512
1513 self.byte_to_pos(self.text.char_to_byte_idx(char_idx))
1514 }
1515
1516 pub fn select_all(&mut self) {
1517 let last_line = self.text.len_lines(self.line_type()).saturating_sub(1);
1518 let pos = Position::new(
1519 last_line,
1520 self.text.line(last_line, self.line_type()).len_chars(),
1521 );
1522 self.selection = Some(Selection::new(Position::new(0, 0), pos));
1523 self.cursor = pos;
1524 }
1525
1526 pub fn selected_text(&self) -> String {
1527 if let Some(sel) = self.selection {
1528 let start = self.pos_to_byte(sel.min());
1529 let end = self.pos_to_byte(sel.max());
1530 self.text.slice(start..end).into()
1531 } else {
1532 String::new()
1533 }
1534 }
1535
1536 pub fn delete_selection(&mut self) -> String {
1537 if let Some(sel) = self.selection {
1538 let start = sel.min();
1539 let end = sel.max();
1540 let text = self.slice(start..end);
1541 self.delete_range(start, end);
1542 text
1543 } else {
1544 String::new()
1545 }
1546 }
1547
1548 pub fn undo(&mut self) -> bool {
1549 let inverse = {
1550 match self.history.undo() {
1551 Some(node) => node.inverse.clone(),
1552 None => return false,
1553 }
1554 };
1555 apply_changeset(self, &inverse);
1556 true
1557 }
1558
1559 pub fn redo(&mut self) -> bool {
1560 let change = {
1561 match self.history.redo() {
1562 Some(node) => node.change.clone(),
1563 None => return false,
1564 }
1565 };
1566 apply_changeset(self, &change);
1567 true
1568 }
1569
1570 pub fn undo_stack(&self) -> Vec<SerializableSnapshot> {
1571 Vec::new()
1572 }
1573
1574 pub fn redo_stack(&self) -> Vec<SerializableSnapshot> {
1575 Vec::new()
1576 }
1577
1578 pub fn save(&mut self, settings: &crate::settings::Settings) -> Result<()> {
1579 let path = self
1580 .path
1581 .clone()
1582 .ok_or_else(|| anyhow!("No path associated with buffer"))?;
1583
1584 self.is_saving = true;
1585 let result = self.do_save(&path, settings);
1586 self.is_saving = false;
1587 result
1588 }
1589
1590 fn do_save(&mut self, path: &PathBuf, settings: &crate::settings::Settings) -> Result<()> {
1591 let should_trim = *adapters::editor::trim_on_save(settings);
1592
1593 let mut text = self.text.clone();
1594
1595 if should_trim {
1596 let content = text.to_string();
1597 let trimmed: String = content
1598 .lines()
1599 .map(|l| l.trim_end().to_string())
1600 .collect::<Vec<_>>()
1601 .join("\n");
1602
1603 let trailing_empty = trimmed.lines().rev().take_while(|l| l.is_empty()).count();
1604 let final_lines: String = if trailing_empty > 1 {
1605 trimmed
1606 .lines()
1607 .take(trimmed.lines().count() - (trailing_empty - 1))
1608 .collect::<Vec<_>>()
1609 .join("\n")
1610 } else {
1611 trimmed.clone()
1612 };
1613
1614 text = Rope::from_str(&final_lines);
1615
1616 if !final_lines.ends_with('\n') && !final_lines.is_empty() {
1617 text.insert(text.len(), "\n");
1618 }
1619
1620 self.text = text.clone();
1621 self.normalize_cursor();
1622 }
1623
1624 let content = if text.len_lines(self.line_type()) > 0 {
1625 let last_line = text.len_lines(self.line_type()) - 1;
1626 let last_line_len = text.line(last_line, self.line_type()).len_chars();
1627 if last_line_len == 0 {
1628 text.to_string().trim_end_matches('\n').to_string()
1629 } else {
1630 text.to_string()
1631 }
1632 } else {
1633 text.to_string()
1634 };
1635
1636 let tmp = path.with_extension("tmp");
1637 fs::write(&tmp, &content)?;
1638 fs::rename(&tmp, path).or_else(|_| {
1639 fs::remove_file(path)?;
1640 fs::rename(&tmp, path)
1641 })?;
1642
1643 self.file_hash = Self::compute_hash(&text);
1644 self.dirty = false;
1645 if let Some((mtime, size)) = Self::get_file_metadata(path) {
1646 self.file_mtime = Some(mtime);
1647 self.file_size = Some(size);
1648 }
1649 Ok(())
1650 }
1651
1652 pub fn compute_hash(text: &Rope) -> Option<u64> {
1653 use std::collections::hash_map::DefaultHasher;
1654 use std::hash::{Hash, Hasher};
1655 let mut hasher = DefaultHasher::new();
1656 text.hash(&mut hasher);
1657 Some(hasher.finish())
1658 }
1659
1660 pub fn compute_file_hash(path: &Path) -> Option<u64> {
1661 use std::collections::hash_map::DefaultHasher;
1662 use std::hash::{Hash, Hasher};
1663 use std::io::Read;
1664 let mut file = fs::File::open(path).ok()?;
1665 let mut contents = Vec::new();
1666 file.read_to_end(&mut contents).ok()?;
1667 let mut hasher = DefaultHasher::new();
1668 contents.hash(&mut hasher);
1669 Some(hasher.finish())
1670 }
1671
1672 pub fn get_file_metadata(path: &Path) -> Option<(u64, u64)> {
1673 let metadata = fs::metadata(path).ok()?;
1674 let mtime = metadata
1675 .modified()
1676 .ok()?
1677 .duration_since(std::time::UNIX_EPOCH)
1678 .ok()?
1679 .as_secs();
1680 let size = metadata.len();
1681 Some((mtime, size))
1682 }
1683
1684 pub fn compute_and_store_file_hash(&mut self) {
1685 if let Some(ref path) = self.path {
1686 self.file_hash = Self::compute_file_hash(path);
1687 if let Some((mtime, size)) = Self::get_file_metadata(path) {
1688 self.file_mtime = Some(mtime);
1689 self.file_size = Some(size);
1690 }
1691 }
1692 }
1693
1694 pub fn check_external_modification(&mut self) -> bool {
1695 if self.is_saving {
1696 return false;
1697 }
1698 if self.is_dirty() {
1699 return false;
1700 }
1701 if let Some(ref path) = self.path
1702 && let Some((current_mtime, current_size)) = Self::get_file_metadata(path) {
1703 let mtime_changed = self.file_mtime.map(|m| m != current_mtime).unwrap_or(false);
1704 let size_changed = self.file_size.map(|s| s != current_size).unwrap_or(false);
1705 if !mtime_changed && !size_changed {
1706 return false;
1707 }
1708 if let Some(current_hash) = Self::compute_file_hash(path)
1709 && let Some(saved_hash) = self.file_hash
1710 && current_hash != saved_hash {
1711 self.external_modification_detected = true;
1712 return true;
1713 }
1714 }
1715 false
1716 }
1717
1718 pub fn reload_from_disk(&mut self) -> Result<()> {
1719 let path = self
1720 .path
1721 .clone()
1722 .ok_or_else(|| anyhow!("No path associated with buffer"))?;
1723 let content = fs::read_to_string(&path)?;
1724 self.text = Rope::from_str(&content);
1725 self.file_hash = Self::compute_hash(&self.text);
1726 if let Some((mtime, size)) = Self::get_file_metadata(&path) {
1727 self.file_mtime = Some(mtime);
1728 self.file_size = Some(size);
1729 }
1730 self.external_modification_detected = false;
1731 self.normalize_cursor();
1732 Ok(())
1733 }
1734
1735 pub fn clear_external_modification(&mut self) {
1736 self.external_modification_detected = false;
1737 }
1738
1739 pub fn scroll_to_cursor(&mut self, height: usize) {
1740 if height == 0 {
1741 return;
1742 }
1743 if self.cursor.line < self.scroll {
1744 self.scroll = self.cursor.line;
1745 } else if self.cursor.line >= self.scroll + height {
1746 self.scroll = self.cursor.line + 1 - height;
1747 }
1748 }
1749
1750 pub fn scroll_to_cursor_visual(
1751 &mut self,
1752 height: usize,
1753 _content_width: usize,
1754 _indent_width: usize,
1755 ) {
1756 self.scroll_to_cursor(height);
1757 }
1758
1759 pub fn scroll_x_to_cursor(&mut self, _content_width: usize, _indent_width: usize) {}
1760
1761 pub fn slice(&self, range: Range<Position>) -> String {
1762 let start_byte = self.pos_to_byte(range.start);
1763 let end_byte = self.pos_to_byte(range.end);
1764 self.text.slice(start_byte..end_byte).into()
1765 }
1766
1767 pub fn text(&self) -> String {
1768 self.text.to_string()
1769 }
1770
1771 pub fn apply_op_without_history(&mut self, op: &BufferOp) {
1772 match op {
1773 BufferOp::Replace { range, text, .. } => {
1774 self.replace_range_direct(range.start, range.end, text);
1775 }
1776 BufferOp::MoveCursor { position } => {
1777 self.set_cursor(*position);
1778 }
1779 BufferOp::SetSelection { selection } => {
1780 self.set_selection(*selection);
1781 }
1782 }
1783 }
1784
1785 fn replace_range_direct(&mut self, start: Position, end: Position, text: &str) {
1786 let start_byte = self.pos_to_byte(start);
1787 let end_byte = self.pos_to_byte(end).min(self.text.len());
1788
1789 if start_byte < end_byte {
1790 self.text.remove(start_byte..end_byte);
1791 }
1792 self.text.insert(start_byte, text);
1793 }
1794}
1795
1796impl Default for Buffer {
1797 fn default() -> Self {
1798 Self::new()
1799 }
1800}
1801
1802impl From<&Rope> for Buffer {
1803 fn from(rope: &Rope) -> Self {
1804 let mut buf = Self::new();
1805 buf.text = rope.clone();
1806 buf
1807 }
1808}
1809
1810#[cfg(test)]
1811mod tests {
1812 use super::*;
1813
1814 #[test]
1815 fn test_position_mapping() {
1816 let buf = Buffer::from_text("hello\nworld\n");
1817
1818 assert_eq!(buf.byte_to_pos(0), Position::new(0, 0));
1819 assert_eq!(buf.byte_to_pos(5), Position::new(0, 5));
1820 assert_eq!(buf.byte_to_pos(6), Position::new(1, 0));
1821 assert_eq!(buf.byte_to_pos(11), Position::new(1, 5));
1822
1823 assert_eq!(buf.pos_to_byte(Position::new(0, 0)), 0);
1824 assert_eq!(buf.pos_to_byte(Position::new(0, 5)), 5);
1825 assert_eq!(buf.pos_to_byte(Position::new(1, 0)), 6);
1826 assert_eq!(buf.pos_to_byte(Position::new(1, 5)), 11);
1827 }
1828
1829 #[test]
1830 fn test_insert() {
1831 let mut buf = Buffer::from_text("hello");
1832 buf.cursor = Position::new(0, 5);
1833 buf.insert(" world");
1834 assert_eq!(buf.text.to_string(), "hello world");
1835 }
1836
1837 #[test]
1838 fn test_insert_newline() {
1839 let mut buf = Buffer::from_text("hello");
1840 buf.cursor = Position::new(0, 5);
1841 buf.insert_newline();
1842 assert_eq!(buf.text.to_string(), "hello\n");
1843 assert_eq!(buf.cursor, Position::new(1, 0));
1844 }
1845
1846 #[test]
1847 fn test_delete_backward() {
1848 let mut buf = Buffer::from_text("hello world");
1849 buf.cursor = Position::new(0, 5);
1850 buf.delete_backward();
1851 assert_eq!(buf.text.to_string(), "hell world");
1852 assert_eq!(buf.cursor, Position::new(0, 4));
1853 }
1854
1855 #[test]
1856 fn test_delete_selection() {
1857 let mut buf = Buffer::from_text("hello world");
1858 buf.selection = Some(Selection::new(Position::new(0, 0), Position::new(0, 6)));
1859 buf.delete_selection();
1860 assert_eq!(buf.text.to_string(), "world");
1861 }
1862
1863 #[test]
1864 fn test_cursor_movement() {
1865 let mut buf = Buffer::from_text("hello\nworld");
1866
1867 buf.cursor = Position::new(0, 3);
1868 assert_eq!(buf.cursor_right(), Position::new(0, 4));
1869 assert_eq!(buf.cursor_left(), Position::new(0, 3));
1870
1871 buf.cursor = Position::new(0, 0);
1872 assert_eq!(buf.cursor_up(), Position::new(0, 0));
1873
1874 buf.cursor = Position::new(0, 3);
1875 buf.cursor_up();
1876 assert_eq!(buf.cursor.line, 0);
1877
1878 buf.cursor = Position::new(1, 3);
1879 buf.cursor_up();
1880 assert_eq!(buf.cursor.line, 0);
1881 assert_eq!(buf.cursor.column, 3);
1882 }
1883
1884 #[test]
1885 fn test_undo_multiple_steps() {
1886 let mut buf = Buffer::from_text("");
1887
1888 buf.insert("a");
1889 buf.insert("b");
1890 buf.insert("c");
1891
1892 assert_eq!(buf.text(), "abc");
1893 assert_eq!(buf.history_position(), 1);
1895
1896 assert!(buf.undo());
1897 assert_eq!(buf.text(), "");
1898
1899 assert!(!buf.undo());
1900 }
1901
1902 #[test]
1903 fn test_undo_redo_multiple_steps() {
1904 let mut buf = Buffer::from_text("");
1905
1906 buf.insert("a");
1907 buf.insert("b");
1908
1909 assert_eq!(buf.text(), "ab");
1910 assert_eq!(buf.history_position(), 1);
1911
1912 buf.undo();
1913 assert_eq!(buf.text(), "");
1914
1915 buf.redo();
1916 assert_eq!(buf.text(), "ab");
1917
1918 assert!(!buf.redo());
1919 }
1920
1921 #[test]
1922 fn test_undo_after_new_edit() {
1923 let mut buf = Buffer::from_text("");
1924
1925 buf.insert("a");
1926 buf.insert("b");
1927
1928 buf.undo();
1930 assert_eq!(buf.text(), "");
1931
1932 buf.insert("c");
1933 assert_eq!(buf.text(), "c");
1934
1935 buf.undo();
1936 assert_eq!(buf.text(), "");
1937 }
1938
1939 #[test]
1940 fn test_line_access() {
1941 let buf = Buffer::from_text("hello\nworld\n");
1942
1943 assert_eq!(buf.line_count(), 3);
1944 assert_eq!(buf.line(0).unwrap().to_string(), "hello");
1945 assert_eq!(buf.line(1).unwrap().to_string(), "world");
1946 }
1947
1948 #[test]
1949 fn test_cursor_clamping() {
1950 let mut buf = Buffer::from_text("hello");
1951 buf.cursor = Position::new(100, 100);
1952 buf.normalize_cursor();
1953
1954 assert!(buf.cursor.line < buf.line_count());
1955 assert!(buf.cursor.column <= buf.line(0).unwrap().chars().count());
1956 }
1957
1958 #[test]
1959 fn test_unicode() {
1960 let mut buf = Buffer::from_text("héllo\nwörld");
1961 assert_eq!(buf.line_count(), 2);
1962
1963 buf.cursor = Position::new(0, 6);
1964 buf.insert("!");
1965 let line0 = buf.text.line(0, LineType::LF).to_string();
1966 assert!(
1967 line0.starts_with("héllo"),
1968 "line0 should start with héllo, got: {}",
1969 line0
1970 );
1971 }
1972
1973 #[test]
1974 fn test_snapshot() {
1975 let buf = Buffer::from_text("hello");
1976 let snap = buf.snapshot();
1977 assert_eq!(snap.text.to_string(), "hello");
1978 }
1979
1980 #[test]
1981 fn test_delete_forward() {
1982 let mut buf = Buffer::from_text("hello");
1983 buf.cursor = Position::new(0, 4);
1984 buf.delete_forward();
1985 assert_eq!(buf.text.to_string(), "hell");
1986 }
1987
1988 #[test]
1989 fn test_empty_buffer() {
1990 let buf = Buffer::new();
1991 assert!(buf.is_empty());
1992 assert_eq!(buf.line_count(), 1);
1993 assert_eq!(buf.cursor, Position::new(0, 0));
1994 }
1995
1996 #[test]
1997 fn test_buffer_id() {
1998 let buf1 = Buffer::new();
1999 let buf2 = Buffer::new();
2000 assert_ne!(buf1.id(), buf2.id());
2001 }
2002
2003 #[test]
2004 fn test_version_increment() {
2005 let mut buf = Buffer::from_text("hello");
2006 let v1 = buf.version();
2007 buf.insert(" world");
2008 let v2 = buf.version();
2009 assert!(v2 > v1);
2010 }
2011
2012 #[test]
2013 fn test_fast_typing_merges_into_single_undo() {
2014 let mut buf = Buffer::from_text("");
2015
2016 buf.insert("h");
2017 buf.insert("e");
2018 buf.insert("l");
2019 buf.insert("l");
2020 buf.insert("o");
2021
2022 assert_eq!(buf.text(), "hello");
2023 assert_eq!(buf.history_position(), 1);
2025
2026 buf.undo();
2027 assert_eq!(buf.text(), "");
2028 }
2029
2030 #[test]
2031 fn test_fast_backspace_merges_into_single_undo() {
2032 let mut buf = Buffer::from_text("hello");
2033 buf.set_cursor(Position::new(0, 5));
2034
2035 buf.delete_backward();
2036 buf.delete_backward();
2037 buf.delete_backward();
2038
2039 assert_eq!(buf.text(), "he");
2040 assert_eq!(buf.history_position(), 1);
2041
2042 buf.undo();
2043 assert_eq!(buf.text(), "hello");
2044 }
2045
2046 #[test]
2047 fn test_structural_ops_break_merge_chain() {
2048 let mut buf = Buffer::from_text("");
2049
2050 buf.insert("a");
2051 buf.insert("b"); buf.insert_newline(); buf.insert("c"); assert_eq!(buf.text(), "ab\nc");
2056 assert_eq!(buf.history_position(), 3);
2058
2059 buf.undo();
2060 assert_eq!(buf.text(), "ab\n");
2061
2062 buf.undo();
2063 assert_eq!(buf.text(), "ab");
2064
2065 buf.undo();
2066 assert_eq!(buf.text(), "");
2067 buf.undo();
2068 assert_eq!(buf.text(), "");
2069 }
2070
2071 #[test]
2072 fn test_mixed_undo_steps() {
2073 let mut buf = Buffer::from_text("");
2074
2075 buf.insert("hello");
2076 buf.insert_newline();
2077 buf.insert("world");
2078
2079 buf.undo();
2080 assert_eq!(buf.text(), "hello\n");
2081
2082 buf.undo();
2083 assert_eq!(buf.text(), "hello");
2084
2085 buf.undo();
2086 assert_eq!(buf.text(), "");
2087 }
2088
2089 #[test]
2090 fn test_cross_line_insert_over_selection() {
2091 let mut buf = Buffer::from_text("hello\nworld");
2092 buf.set_selection(Some(Selection::new(
2093 Position::new(0, 2),
2094 Position::new(1, 3),
2095 )));
2096 buf.insert("X");
2097 assert_eq!(buf.text(), "heXld");
2098 }
2099
2100 #[test]
2101 fn test_cross_line_delete_backward_with_selection() {
2102 let mut buf = Buffer::from_text("hello\nworld");
2103 buf.set_selection(Some(Selection::new(
2104 Position::new(0, 5),
2105 Position::new(1, 0),
2106 )));
2107 buf.delete_backward();
2108 assert_eq!(buf.text(), "helloworld");
2109 }
2110
2111 #[test]
2112 fn test_cross_line_selection_delete_all() {
2113 let mut buf = Buffer::from_text("abc\ndef\nghi");
2114 buf.set_selection(Some(Selection::new(
2115 Position::new(0, 0),
2116 Position::new(2, 3),
2117 )));
2118 buf.insert("X");
2119 assert_eq!(buf.text(), "X");
2120 }
2121
2122 #[test]
2123 fn test_cross_line_selection_undo_redo() {
2124 let mut buf = Buffer::from_text("hello\nworld");
2125 buf.set_selection(Some(Selection::new(
2127 Position::new(0, 3),
2128 Position::new(1, 2),
2129 )));
2130 buf.insert("X");
2131 assert_eq!(buf.text(), "helXrld");
2132 assert_eq!(buf.cursor(), Position::new(0, 4)); buf.undo();
2134 assert_eq!(buf.text(), "hello\nworld");
2135 assert_eq!(buf.cursor(), Position::new(1, 2));
2137 buf.redo();
2138 assert_eq!(buf.text(), "helXrld");
2139 assert_eq!(buf.cursor(), Position::new(0, 4));
2140 }
2141
2142 #[test]
2143 fn test_offset_up_excludes_trailing_newline() {
2144 let buf = Buffer::from_text("hello\nworld");
2145 let pos = buf.offset_up(Position::new(1, 5));
2146 assert_eq!(pos, Position::new(0, 5));
2147 }
2148
2149 #[test]
2150 fn test_offset_up_clamps_to_shorter_line() {
2151 let buf = Buffer::from_text("hi\nworld");
2152 let pos = buf.offset_up(Position::new(1, 5));
2153 assert_eq!(pos, Position::new(0, 2));
2154 }
2155
2156 #[test]
2157 fn test_delete_forward_multibyte() {
2158 let mut buf = Buffer::from_text("αβγ");
2159 buf.set_cursor(Position::new(0, 1));
2160 buf.delete_forward();
2161 assert_eq!(buf.text(), "αγ");
2162 }
2163
2164 #[test]
2165 fn test_delete_forward_newline() {
2166 let mut buf = Buffer::from_text("abc\ndef");
2167 buf.set_cursor(Position::new(0, 3));
2168 buf.delete_forward();
2169 assert_eq!(buf.text(), "abcdef");
2170 }
2171
2172 #[test]
2173 fn test_pos_to_char_and_char_to_pos() {
2174 let buf = Buffer::from_text("hello\nworld");
2175 assert_eq!(buf.pos_to_char(Position::new(0, 3)), 3);
2176 assert_eq!(buf.pos_to_char(Position::new(1, 2)), 8);
2178 assert_eq!(buf.char_to_pos(3), Position::new(0, 3));
2179 assert_eq!(buf.char_to_pos(8), Position::new(1, 2));
2180 }
2181
2182 #[test]
2183 fn test_set_selection_normalizes_positions() {
2184 let mut buf = Buffer::from_text("hi\nworld");
2185 buf.set_selection(Some(Selection::new(
2186 Position::new(0, 100), Position::new(1, 3),
2188 )));
2189 let sel = buf.selection().unwrap();
2190 assert_eq!(sel.anchor, Position::new(0, 2));
2191 assert_eq!(sel.active, Position::new(1, 3));
2192 }
2193
2194 #[test]
2195 fn test_newline_undo_roundtrip() {
2196 let mut buf = Buffer::from_text("helloworld");
2197 buf.set_cursor(Position::new(0, 5));
2198 buf.insert_newline();
2199 assert_eq!(buf.text(), "hello\nworld");
2200 assert_eq!(buf.cursor(), Position::new(1, 0));
2201 buf.undo();
2202 assert_eq!(buf.text(), "helloworld");
2203 assert_eq!(buf.cursor(), Position::new(0, 5));
2204 }
2205
2206 #[test]
2209 fn test_insert_at_advances_cursor_when_at_position() {
2210 let mut buf = Buffer::from_text("hello");
2211 buf.set_cursor(Position::new(0, 5));
2212 buf.insert_at(Position::new(0, 5), " world");
2213 assert_eq!(buf.text(), "hello world");
2214 assert_eq!(buf.cursor(), Position::new(0, 11));
2215 }
2216
2217 #[test]
2218 fn test_insert_at_does_not_move_cursor_when_before_cursor() {
2219 let mut buf = Buffer::from_text("world");
2220 buf.set_cursor(Position::new(0, 5));
2221 buf.insert_at(Position::new(0, 0), "hello ");
2222 assert_eq!(buf.text(), "hello world");
2223 assert_eq!(buf.cursor(), Position::new(0, 5));
2224 }
2225
2226 #[test]
2229 fn test_selected_text_single_line() {
2230 let mut buf = Buffer::from_text("hello world");
2231 buf.set_selection(Some(Selection {
2232 anchor: Position::new(0, 0),
2233 active: Position::new(0, 5),
2234 }));
2235 assert_eq!(buf.selected_text(), "hello");
2236 }
2237
2238 #[test]
2239 fn test_selected_text_multiline() {
2240 let mut buf = Buffer::from_text("hello\nworld");
2241 buf.set_selection(Some(Selection {
2242 anchor: Position::new(0, 3),
2243 active: Position::new(1, 3),
2244 }));
2245 assert_eq!(buf.selected_text(), "lo\nwor");
2246 }
2247
2248 #[test]
2249 fn test_selected_text_empty_when_no_selection() {
2250 let buf = Buffer::from_text("hello");
2251 assert_eq!(buf.selected_text(), "");
2252 }
2253
2254 #[test]
2257 fn test_word_range_at_middle_of_word() {
2258 let buf = Buffer::from_text("hello world");
2259 let (start, end) = buf.word_range_at(Position::new(0, 2));
2260 assert_eq!(start, 2);
2262 assert_eq!(end, 5);
2263 }
2264
2265 #[test]
2266 fn test_word_range_at_on_space() {
2267 let buf = Buffer::from_text("hello world");
2268 let _ = buf.word_range_at(Position::new(0, 5));
2270 }
2271
2272 #[test]
2275 fn test_delete_range_same_position_is_noop() {
2276 let mut buf = Buffer::from_text("hello");
2277 let pos = Position::new(0, 2);
2278 buf.delete_range(pos, pos);
2279 assert_eq!(buf.text(), "hello");
2280 }
2281
2282 #[test]
2285 fn test_char_count() {
2286 let buf = Buffer::from_text("hello\nworld");
2287 assert_eq!(buf.char_count(), 11);
2288 }
2289
2290 #[test]
2291 fn test_current_line() {
2292 let mut buf = Buffer::from_text("hello\nworld");
2293 buf.set_cursor(Position::new(1, 0));
2294 assert_eq!(buf.current_line(), 1);
2295 }
2296
2297 #[test]
2298 fn test_slice() {
2299 let buf = Buffer::from_text("hello world");
2300 let s = buf.slice(Position::new(0, 0)..Position::new(0, 5));
2301 assert_eq!(s, "hello");
2302 }
2303
2304 #[test]
2307 fn test_history_len() {
2308 let mut buf = Buffer::from_text("");
2309 assert_eq!(buf.history_len(), 0);
2310 buf.insert("a");
2311 assert!(buf.history_len() > 0);
2312 }
2313
2314 #[test]
2317 fn test_scroll_to_cursor_scrolls_down_when_cursor_below_view() {
2318 let mut buf = Buffer::from_text("a\nb\nc\nd\ne");
2319 buf.set_cursor(Position::new(4, 0));
2320 buf.scroll_to_cursor(3);
2321 assert!(buf.scroll >= 2);
2322 }
2323
2324 #[test]
2325 fn test_scroll_to_cursor_scrolls_up_when_cursor_above_view() {
2326 let mut buf = Buffer::from_text("a\nb\nc\nd\ne");
2327 buf.scroll = 4;
2328 buf.set_cursor(Position::new(0, 0));
2329 buf.scroll_to_cursor(3);
2330 assert_eq!(buf.scroll, 0);
2331 }
2332
2333 #[test]
2336 fn test_offset_down_clamps_column_to_shorter_line() {
2337 let buf = Buffer::from_text("hello world\nhi");
2338 let pos = buf.offset_down(Position::new(0, 8));
2339 assert_eq!(pos, Position::new(1, 2));
2340 }
2341
2342 #[test]
2343 fn test_offset_down_at_last_line_stays() {
2344 let buf = Buffer::from_text("hello");
2345 let pos = buf.offset_down(Position::new(0, 3));
2346 assert_eq!(pos, Position::new(0, 3));
2347 }
2348
2349 #[test]
2350 fn test_offset_down_n_stops_at_last_line() {
2351 let buf = Buffer::from_text("a\nb\nc");
2352 let pos = buf.offset_down_n(Position::new(0, 0), 100);
2353 assert_eq!(pos.line, 2);
2354 }
2355
2356 #[test]
2359 fn test_offset_left_wraps_to_end_of_previous_line() {
2360 let buf = Buffer::from_text("hello\nworld");
2361 let pos = buf.offset_left(Position::new(1, 0));
2362 assert_eq!(pos, Position::new(0, 5));
2363 }
2364
2365 #[test]
2366 fn test_offset_right_wraps_to_start_of_next_line() {
2367 let buf = Buffer::from_text("hello\nworld");
2368 let pos = buf.offset_right(Position::new(0, 5));
2369 assert_eq!(pos, Position::new(1, 0));
2370 }
2371
2372 #[test]
2375 fn test_insert_text_raw_normalizes_crlf() {
2376 let mut buf = Buffer::from_text("");
2377 buf.insert_text_raw("hello\r\nworld");
2378 assert_eq!(buf.text(), "hello\nworld");
2379 }
2380
2381 #[test]
2382 fn test_insert_text_raw_normalizes_cr_only() {
2383 let mut buf = Buffer::from_text("");
2384 buf.insert_text_raw("hello\rworld");
2385 assert_eq!(buf.text(), "hello\nworld");
2386 }
2387
2388 #[test]
2391 fn test_replace_range_same_text_at_cursor_is_noop_for_history() {
2392 let mut buf = Buffer::from_text("hello");
2393 buf.set_cursor(Position::new(0, 0));
2394 let before_history_pos = buf.history_position();
2395 buf.replace_range(Position::new(0, 0), Position::new(0, 5), "hello");
2396 assert_eq!(buf.history_position(), before_history_pos);
2397 assert_eq!(buf.text(), "hello");
2398 }
2399
2400 #[test]
2403 fn test_delete_word_forward_from_whitespace() {
2404 let mut buf = Buffer::from_text("hello world");
2405 buf.set_cursor(Position::new(0, 5));
2406 buf.delete_word_forward();
2407 assert_eq!(buf.text(), "helloworld");
2408 }
2409
2410 #[test]
2413 fn test_delete_line_last_line_leaves_empty() {
2414 let mut buf = Buffer::from_text("hello");
2415 buf.set_cursor(Position::new(0, 0));
2416 buf.delete_line();
2417 assert_eq!(buf.line_count(), 1);
2418 assert_eq!(buf.line(0).unwrap(), "");
2419 }
2420}