1use std::hash::{DefaultHasher, Hash, Hasher};
2
3use ropey::Rope;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct Cursor {
8 pub line: usize,
10 pub col: usize,
12 col_memory: usize,
14}
15
16impl Cursor {
17 pub const fn new() -> Self {
19 Self {
20 line: 0,
21 col: 0,
22 col_memory: 0,
23 }
24 }
25
26 pub const fn at(line: usize, col: usize) -> Self {
28 Self {
29 line,
30 col,
31 col_memory: col,
32 }
33 }
34
35 const fn set_col(&mut self, col: usize) {
37 self.col = col;
38 self.col_memory = col;
39 }
40}
41
42impl Default for Cursor {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum Direction {
51 Up,
52 Down,
53 Left,
54 Right,
55}
56
57pub struct EditorBuffer {
62 rope: Rope,
63 cursor: Cursor,
64 clean_hash: u64,
66}
67
68impl EditorBuffer {
69 pub fn from_text(text: &str) -> Self {
71 let rope = Rope::from_str(text);
72 let clean_hash = Self::rope_hash(&rope);
73 Self {
74 rope,
75 cursor: Cursor::new(),
76 clean_hash,
77 }
78 }
79
80 pub fn empty() -> Self {
82 Self::from_text("")
83 }
84
85 pub const fn cursor(&self) -> Cursor {
87 self.cursor
88 }
89
90 pub fn is_dirty(&self) -> bool {
92 Self::rope_hash(&self.rope) != self.clean_hash
93 }
94
95 pub fn mark_clean(&mut self) {
97 self.clean_hash = Self::rope_hash(&self.rope);
98 }
99
100 pub fn line_count(&self) -> usize {
102 self.rope.len_lines()
103 }
104
105 pub fn line_at(&self, line_idx: usize) -> Option<String> {
107 if line_idx >= self.rope.len_lines() {
108 return None;
109 }
110 let line = self.rope.line(line_idx);
111 let s = line.to_string();
112 Some(s.trim_end_matches('\n').trim_end_matches('\r').to_string())
114 }
115
116 pub fn line_len(&self, line_idx: usize) -> usize {
118 self.line_at(line_idx).map_or(0, |s| s.len())
119 }
120
121 pub fn text(&self) -> String {
123 self.rope.to_string()
124 }
125
126 pub fn insert_char(&mut self, ch: char) {
128 let char_idx = self.cursor_char_idx();
129 self.rope.insert_char(char_idx, ch);
130 self.cursor.set_col(self.cursor.col + ch.len_utf8());
131 }
132
133 pub fn insert_str(&mut self, s: &str) {
135 if s.is_empty() {
136 return;
137 }
138 let char_idx = self.cursor_char_idx();
139 self.rope.insert(char_idx, s);
140
141 let lines: Vec<&str> = s.split('\n').collect();
143 if lines.len() > 1 {
144 self.cursor.line += lines.len() - 1;
145 self.cursor.set_col(lines.last().map_or(0, |l| l.len()));
146 } else {
147 self.cursor.set_col(self.cursor.col + s.len());
148 }
149 }
150
151 pub fn split_line(&mut self) {
153 let char_idx = self.cursor_char_idx();
154 self.rope.insert_char(char_idx, '\n');
155 self.cursor.line += 1;
156 self.cursor.set_col(0);
157 }
158
159 pub fn delete_back(&mut self) -> bool {
163 if self.cursor.col == 0 && self.cursor.line == 0 {
164 return false;
165 }
166
167 if self.cursor.col == 0 {
168 let prev_line_len = self.line_len(self.cursor.line - 1);
170 let char_idx = self.cursor_char_idx();
171 self.rope.remove(char_idx - 1..char_idx);
173 self.cursor.line -= 1;
174 self.cursor.set_col(prev_line_len);
175 } else {
176 let char_idx = self.cursor_char_idx();
178 let line = self.rope.line(self.cursor.line);
180 let line_str = line.to_string();
181 let before = line_str.get(..self.cursor.col).unwrap_or(&line_str);
182 let prev_char_len = before.chars().next_back().map_or(1, char::len_utf8);
183 self.rope.remove(char_idx - 1..char_idx);
184 self.cursor.set_col(self.cursor.col - prev_char_len);
185 }
186
187 true
188 }
189
190 pub fn delete_forward(&mut self) -> bool {
194 let line_len = self.line_len(self.cursor.line);
195
196 if self.cursor.col >= line_len && self.cursor.line + 1 >= self.line_count() {
197 return false;
198 }
199
200 let char_idx = self.cursor_char_idx();
201 self.rope.remove(char_idx..=char_idx);
202
203 true
204 }
205
206 pub fn move_cursor(&mut self, direction: Direction) {
208 match direction {
209 Direction::Left => self.move_left(),
210 Direction::Right => self.move_right(),
211 Direction::Up => self.move_up(),
212 Direction::Down => self.move_down(),
213 }
214 }
215
216 pub const fn move_home(&mut self) {
218 self.cursor.set_col(0);
219 }
220
221 pub fn move_end(&mut self) {
223 let len = self.line_len(self.cursor.line);
224 self.cursor.set_col(len);
225 }
226
227 pub fn move_word_left(&mut self) {
229 if self.cursor.col == 0 {
230 if self.cursor.line > 0 {
231 self.cursor.line -= 1;
232 self.cursor.set_col(self.line_len(self.cursor.line));
233 }
234 return;
235 }
236
237 let line = self.line_at(self.cursor.line).unwrap_or_default();
238 let before = line.get(..self.cursor.col).unwrap_or(&line);
239 let trimmed = before.trim_end();
240
241 if trimmed.is_empty() {
242 self.cursor.set_col(0);
243 return;
244 }
245
246 let pos = trimmed
248 .rfind(|c: char| !c.is_alphanumeric() && c != '_')
249 .map_or(0, |i| i + 1);
250 self.cursor.set_col(pos);
251 }
252
253 pub fn move_word_right(&mut self) {
255 let line_len = self.line_len(self.cursor.line);
256
257 if self.cursor.col >= line_len {
258 if self.cursor.line + 1 < self.line_count() {
259 self.cursor.line += 1;
260 self.cursor.set_col(0);
261 }
262 return;
263 }
264
265 let line = self.line_at(self.cursor.line).unwrap_or_default();
266 let after = line.get(self.cursor.col..).unwrap_or_default();
267
268 let word_end = after
270 .find(|c: char| !c.is_alphanumeric() && c != '_')
271 .unwrap_or(after.len());
272
273 let rest = after.get(word_end..).unwrap_or_default();
275 let space_end = rest
276 .find(|c: char| c.is_alphanumeric() || c == '_')
277 .unwrap_or(rest.len());
278
279 self.cursor.set_col(self.cursor.col + word_end + space_end);
280 }
281
282 pub fn move_to(&mut self, line: usize, col: usize) {
284 let max_line = self.line_count().saturating_sub(1);
285 self.cursor.line = line.min(max_line);
286 let max_col = self.line_len(self.cursor.line);
287 self.cursor.set_col(col.min(max_col));
288 }
289
290 pub const fn move_to_start(&mut self) {
292 self.cursor.line = 0;
293 self.cursor.set_col(0);
294 }
295
296 pub fn move_to_end(&mut self) {
298 let last_line = self.line_count().saturating_sub(1);
299 self.cursor.line = last_line;
300 self.cursor.set_col(self.line_len(last_line));
301 }
302
303 fn rope_hash(rope: &Rope) -> u64 {
307 let mut hasher = DefaultHasher::new();
308 for chunk in rope.chunks() {
309 chunk.hash(&mut hasher);
310 }
311 hasher.finish()
312 }
313
314 fn cursor_char_idx(&self) -> usize {
316 let line_start = self.rope.line_to_char(self.cursor.line);
317 let line = self.rope.line(self.cursor.line);
318 let line_str: String = line.chars().collect();
319 let byte_col = self.cursor.col.min(line_str.len());
321 let char_offset = line_str
322 .get(..byte_col)
323 .unwrap_or(&line_str)
324 .chars()
325 .count();
326 line_start + char_offset
327 }
328
329 fn move_left(&mut self) {
330 if self.cursor.col > 0 {
331 let line = self.line_at(self.cursor.line).unwrap_or_default();
332 let before = line.get(..self.cursor.col).unwrap_or(&line);
333 let prev_char_len = before.chars().next_back().map_or(1, char::len_utf8);
334 self.cursor.set_col(self.cursor.col - prev_char_len);
335 } else if self.cursor.line > 0 {
336 self.cursor.line -= 1;
337 self.cursor.set_col(self.line_len(self.cursor.line));
338 }
339 }
340
341 fn move_right(&mut self) {
342 let line_len = self.line_len(self.cursor.line);
343 if self.cursor.col < line_len {
344 let line = self.line_at(self.cursor.line).unwrap_or_default();
345 let next_char_len = line
346 .get(self.cursor.col..)
347 .unwrap_or_default()
348 .chars()
349 .next()
350 .map_or(1, char::len_utf8);
351 self.cursor.set_col(self.cursor.col + next_char_len);
352 } else if self.cursor.line + 1 < self.line_count() {
353 self.cursor.line += 1;
354 self.cursor.set_col(0);
355 }
356 }
357
358 fn move_up(&mut self) {
359 if self.cursor.line > 0 {
360 self.cursor.line -= 1;
361 let max_col = self.line_len(self.cursor.line);
362 self.cursor.col = self.cursor.col_memory.min(max_col);
363 }
364 }
365
366 fn move_down(&mut self) {
367 if self.cursor.line + 1 < self.line_count() {
368 self.cursor.line += 1;
369 let max_col = self.line_len(self.cursor.line);
370 self.cursor.col = self.cursor.col_memory.min(max_col);
371 }
372 }
373}
374
375impl std::fmt::Debug for EditorBuffer {
376 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377 f.debug_struct("EditorBuffer")
378 .field(
379 "rope",
380 &format_args!("Rope({} lines)", self.rope.len_lines()),
381 )
382 .field("cursor", &self.cursor)
383 .field("dirty", &self.is_dirty())
384 .finish_non_exhaustive()
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[test]
395 fn test_empty_buffer_has_one_line() {
396 let buf = EditorBuffer::empty();
397 assert_eq!(buf.line_count(), 1);
398 assert_eq!(buf.line_at(0), Some(String::new()));
399 }
400
401 #[test]
402 fn test_from_text_preserves_content() {
403 let buf = EditorBuffer::from_text("hello\nworld");
404 assert_eq!(buf.line_count(), 2);
405 assert_eq!(buf.line_at(0), Some("hello".to_string()));
406 assert_eq!(buf.line_at(1), Some("world".to_string()));
407 }
408
409 #[test]
410 fn test_from_text_trailing_newline() {
411 let buf = EditorBuffer::from_text("hello\n");
412 assert_eq!(buf.line_count(), 2);
413 assert_eq!(buf.line_at(0), Some("hello".to_string()));
414 assert_eq!(buf.line_at(1), Some(String::new()));
415 }
416
417 #[test]
418 fn test_line_at_out_of_bounds_returns_none() {
419 let buf = EditorBuffer::from_text("hello");
420 assert_eq!(buf.line_at(1), None);
421 }
422
423 #[test]
424 fn test_line_len() {
425 let buf = EditorBuffer::from_text("hello\nhi");
426 assert_eq!(buf.line_len(0), 5);
427 assert_eq!(buf.line_len(1), 2);
428 }
429
430 #[test]
431 fn test_text_roundtrip() {
432 let content = "line one\nline two\nline three";
433 let buf = EditorBuffer::from_text(content);
434 assert_eq!(buf.text(), content);
435 }
436
437 #[test]
440 fn test_cursor_starts_at_origin() {
441 let buf = EditorBuffer::from_text("hello\nworld");
442 assert_eq!(buf.cursor(), Cursor::at(0, 0));
443 }
444
445 #[test]
448 fn test_new_buffer_is_clean() {
449 let buf = EditorBuffer::from_text("hello");
450 assert!(!buf.is_dirty());
451 }
452
453 #[test]
454 fn test_insert_marks_dirty() {
455 let mut buf = EditorBuffer::from_text("hello");
456 buf.insert_char('!');
457 assert!(buf.is_dirty());
458 }
459
460 #[test]
461 fn test_mark_clean_resets_dirty() {
462 let mut buf = EditorBuffer::from_text("hello");
463 buf.insert_char('!');
464 buf.mark_clean();
465 assert!(!buf.is_dirty());
466 }
467
468 #[test]
469 fn test_undo_to_original_is_not_dirty() {
470 let mut buf = EditorBuffer::from_text("hello");
471 buf.insert_char('!');
472 assert!(buf.is_dirty());
473 buf.delete_back();
474 assert!(
475 !buf.is_dirty(),
476 "buffer should be clean after undoing to original content"
477 );
478 }
479
480 #[test]
481 fn test_mark_clean_updates_baseline() {
482 let mut buf = EditorBuffer::from_text("hello");
483 buf.insert_char('!');
484 buf.mark_clean();
485 buf.insert_char('?');
487 assert!(buf.is_dirty());
488 buf.delete_back();
489 assert!(!buf.is_dirty());
490 }
491
492 #[test]
495 fn test_insert_char_at_start() {
496 let mut buf = EditorBuffer::from_text("hello");
497 buf.insert_char('H');
498 assert_eq!(buf.line_at(0), Some("Hhello".to_string()));
499 assert_eq!(buf.cursor(), Cursor::at(0, 1));
500 }
501
502 #[test]
503 fn test_insert_char_at_end() {
504 let mut buf = EditorBuffer::from_text("hello");
505 buf.move_end();
506 buf.insert_char('!');
507 assert_eq!(buf.line_at(0), Some("hello!".to_string()));
508 assert_eq!(buf.cursor(), Cursor::at(0, 6));
509 }
510
511 #[test]
512 fn test_insert_char_in_middle() {
513 let mut buf = EditorBuffer::from_text("hllo");
514 buf.move_cursor(Direction::Right); buf.insert_char('e');
516 assert_eq!(buf.line_at(0), Some("hello".to_string()));
517 assert_eq!(buf.cursor(), Cursor::at(0, 2));
518 }
519
520 #[test]
521 fn test_insert_multibyte_char() {
522 let mut buf = EditorBuffer::from_text("hello");
523 buf.move_end();
524 buf.insert_char('é');
525 assert_eq!(buf.line_at(0), Some("helloé".to_string()));
526 }
527
528 #[test]
531 fn test_insert_str_single_line() {
532 let mut buf = EditorBuffer::from_text("hd");
533 buf.move_cursor(Direction::Right);
534 buf.insert_str("ello worl");
535 assert_eq!(buf.line_at(0), Some("hello world".to_string()));
536 }
537
538 #[test]
539 fn test_insert_str_empty_is_noop() {
540 let mut buf = EditorBuffer::from_text("hello");
541 buf.insert_str("");
542 assert!(!buf.is_dirty());
543 assert_eq!(buf.text(), "hello");
544 }
545
546 #[test]
549 fn test_split_line_at_end() {
550 let mut buf = EditorBuffer::from_text("hello");
551 buf.move_end();
552 buf.split_line();
553 assert_eq!(buf.line_count(), 2);
554 assert_eq!(buf.line_at(0), Some("hello".to_string()));
555 assert_eq!(buf.line_at(1), Some(String::new()));
556 assert_eq!(buf.cursor(), Cursor::at(1, 0));
557 }
558
559 #[test]
560 fn test_split_line_at_start() {
561 let mut buf = EditorBuffer::from_text("hello");
562 buf.split_line();
563 assert_eq!(buf.line_count(), 2);
564 assert_eq!(buf.line_at(0), Some(String::new()));
565 assert_eq!(buf.line_at(1), Some("hello".to_string()));
566 assert_eq!(buf.cursor(), Cursor::at(1, 0));
567 }
568
569 #[test]
570 fn test_split_line_in_middle() {
571 let mut buf = EditorBuffer::from_text("hello world");
572 buf.move_to(0, 5);
573 buf.split_line();
574 assert_eq!(buf.line_at(0), Some("hello".to_string()));
575 assert_eq!(buf.line_at(1), Some(" world".to_string()));
576 assert_eq!(buf.cursor(), Cursor::at(1, 0));
577 }
578
579 #[test]
582 fn test_delete_back_at_start_is_noop() {
583 let mut buf = EditorBuffer::from_text("hello");
584 assert!(!buf.delete_back());
585 assert_eq!(buf.text(), "hello");
586 }
587
588 #[test]
589 fn test_delete_back_removes_char() {
590 let mut buf = EditorBuffer::from_text("hello");
591 buf.move_to(0, 5);
592 buf.delete_back();
593 assert_eq!(buf.line_at(0), Some("hell".to_string()));
594 assert_eq!(buf.cursor(), Cursor::at(0, 4));
595 }
596
597 #[test]
598 fn test_delete_back_joins_lines() {
599 let mut buf = EditorBuffer::from_text("hello\nworld");
600 buf.move_to(1, 0);
601 buf.delete_back();
602 assert_eq!(buf.line_count(), 1);
603 assert_eq!(buf.line_at(0), Some("helloworld".to_string()));
604 assert_eq!(buf.cursor(), Cursor::at(0, 5));
605 }
606
607 #[test]
610 fn test_delete_forward_at_end_is_noop() {
611 let mut buf = EditorBuffer::from_text("hello");
612 buf.move_end();
613 assert!(!buf.delete_forward());
614 }
615
616 #[test]
617 fn test_delete_forward_removes_char() {
618 let mut buf = EditorBuffer::from_text("hello");
619 buf.delete_forward();
620 assert_eq!(buf.line_at(0), Some("ello".to_string()));
621 assert_eq!(buf.cursor(), Cursor::at(0, 0));
622 }
623
624 #[test]
625 fn test_delete_forward_joins_lines() {
626 let mut buf = EditorBuffer::from_text("hello\nworld");
627 buf.move_to(0, 5);
628 buf.delete_forward();
629 assert_eq!(buf.line_count(), 1);
630 assert_eq!(buf.line_at(0), Some("helloworld".to_string()));
631 assert_eq!(buf.cursor(), Cursor::at(0, 5));
632 }
633
634 #[test]
637 fn test_move_left_at_start_is_noop() {
638 let mut buf = EditorBuffer::from_text("hello");
639 buf.move_cursor(Direction::Left);
640 assert_eq!(buf.cursor(), Cursor::at(0, 0));
641 }
642
643 #[test]
644 fn test_move_left_decreases_col() {
645 let mut buf = EditorBuffer::from_text("hello");
646 buf.move_to(0, 3);
647 buf.move_cursor(Direction::Left);
648 assert_eq!(buf.cursor(), Cursor::at(0, 2));
649 }
650
651 #[test]
652 fn test_move_left_wraps_to_prev_line() {
653 let mut buf = EditorBuffer::from_text("hello\nworld");
654 buf.move_to(1, 0);
655 buf.move_cursor(Direction::Left);
656 assert_eq!(buf.cursor(), Cursor::at(0, 5));
657 }
658
659 #[test]
660 fn test_move_right_at_end_is_noop() {
661 let mut buf = EditorBuffer::from_text("hello");
662 buf.move_end();
663 buf.move_cursor(Direction::Right);
664 assert_eq!(buf.cursor(), Cursor::at(0, 5));
665 }
666
667 #[test]
668 fn test_move_right_increases_col() {
669 let mut buf = EditorBuffer::from_text("hello");
670 buf.move_cursor(Direction::Right);
671 assert_eq!(buf.cursor(), Cursor::at(0, 1));
672 }
673
674 #[test]
675 fn test_move_right_wraps_to_next_line() {
676 let mut buf = EditorBuffer::from_text("hello\nworld");
677 buf.move_to(0, 5);
678 buf.move_cursor(Direction::Right);
679 assert_eq!(buf.cursor(), Cursor::at(1, 0));
680 }
681
682 #[test]
685 fn test_move_up_at_first_line_is_noop() {
686 let mut buf = EditorBuffer::from_text("hello\nworld");
687 buf.move_cursor(Direction::Up);
688 assert_eq!(buf.cursor(), Cursor::at(0, 0));
689 }
690
691 #[test]
692 fn test_move_down_at_last_line_is_noop() {
693 let mut buf = EditorBuffer::from_text("hello\nworld");
694 buf.move_to(1, 0);
695 buf.move_cursor(Direction::Down);
696 assert_eq!(buf.cursor(), Cursor::at(1, 0));
697 }
698
699 #[test]
700 fn test_move_up_preserves_column() {
701 let mut buf = EditorBuffer::from_text("hello\nworld");
702 buf.move_to(1, 3);
703 buf.move_cursor(Direction::Up);
704 assert_eq!(buf.cursor(), Cursor::at(0, 3));
705 }
706
707 #[test]
708 fn test_move_down_preserves_column() {
709 let mut buf = EditorBuffer::from_text("hello\nworld");
710 buf.move_to(0, 3);
711 buf.move_cursor(Direction::Down);
712 assert_eq!(buf.cursor(), Cursor::at(1, 3));
713 }
714
715 #[test]
716 fn test_move_up_clamps_to_shorter_line() {
717 let mut buf = EditorBuffer::from_text("hi\nhello");
718 buf.move_to(1, 4);
719 buf.move_cursor(Direction::Up);
720 assert_eq!(buf.cursor().line, 0);
722 assert_eq!(buf.cursor().col, 2);
723 }
724
725 #[test]
726 fn test_move_down_clamps_to_shorter_line() {
727 let mut buf = EditorBuffer::from_text("hello\nhi");
728 buf.move_to(0, 4);
729 buf.move_cursor(Direction::Down);
730 assert_eq!(buf.cursor().line, 1);
732 assert_eq!(buf.cursor().col, 2);
733 }
734
735 #[test]
738 fn test_column_memory_across_short_line() {
739 let mut buf = EditorBuffer::from_text("hello\nhi\nworld");
740 buf.move_to(0, 4);
741 buf.move_cursor(Direction::Down); assert_eq!(buf.cursor().line, 1);
743 assert_eq!(buf.cursor().col, 2);
744 buf.move_cursor(Direction::Down); assert_eq!(buf.cursor().line, 2);
746 assert_eq!(buf.cursor().col, 4);
747 }
748
749 #[test]
752 fn test_move_home() {
753 let mut buf = EditorBuffer::from_text("hello");
754 buf.move_to(0, 3);
755 buf.move_home();
756 assert_eq!(buf.cursor(), Cursor::at(0, 0));
757 }
758
759 #[test]
760 fn test_move_end() {
761 let mut buf = EditorBuffer::from_text("hello");
762 buf.move_end();
763 assert_eq!(buf.cursor(), Cursor::at(0, 5));
764 }
765
766 #[test]
769 fn test_move_word_left_from_middle_of_word() {
770 let mut buf = EditorBuffer::from_text("hello world");
771 buf.move_to(0, 8); buf.move_word_left();
773 assert_eq!(buf.cursor().col, 6); }
775
776 #[test]
777 fn test_move_word_left_from_start_of_word() {
778 let mut buf = EditorBuffer::from_text("hello world");
779 buf.move_to(0, 6); buf.move_word_left();
781 assert_eq!(buf.cursor().col, 0); }
783
784 #[test]
785 fn test_move_word_left_at_start_of_line_wraps() {
786 let mut buf = EditorBuffer::from_text("hello\nworld");
787 buf.move_to(1, 0);
788 buf.move_word_left();
789 assert_eq!(buf.cursor(), Cursor::at(0, 5)); }
791
792 #[test]
793 fn test_move_word_right_from_start() {
794 let mut buf = EditorBuffer::from_text("hello world");
795 buf.move_word_right();
796 assert_eq!(buf.cursor().col, 6); }
798
799 #[test]
800 fn test_move_word_right_at_end_of_line_wraps() {
801 let mut buf = EditorBuffer::from_text("hello\nworld");
802 buf.move_to(0, 5);
803 buf.move_word_right();
804 assert_eq!(buf.cursor(), Cursor::at(1, 0));
805 }
806
807 #[test]
810 fn test_move_to_clamps_line() {
811 let mut buf = EditorBuffer::from_text("hello");
812 buf.move_to(100, 0);
813 assert_eq!(buf.cursor().line, 0);
814 }
815
816 #[test]
817 fn test_move_to_clamps_col() {
818 let mut buf = EditorBuffer::from_text("hello");
819 buf.move_to(0, 100);
820 assert_eq!(buf.cursor().col, 5);
821 }
822
823 #[test]
826 fn test_move_to_start() {
827 let mut buf = EditorBuffer::from_text("hello\nworld");
828 buf.move_to(1, 3);
829 buf.move_to_start();
830 assert_eq!(buf.cursor(), Cursor::at(0, 0));
831 }
832
833 #[test]
834 fn test_move_to_end() {
835 let mut buf = EditorBuffer::from_text("hello\nworld");
836 buf.move_to_end();
837 assert_eq!(buf.cursor(), Cursor::at(1, 5));
838 }
839
840 #[test]
843 fn test_insert_and_navigate_multibyte() {
844 let mut buf = EditorBuffer::from_text("café");
845 buf.move_end();
846 assert_eq!(buf.cursor().col, 5); buf.move_cursor(Direction::Left);
848 assert_eq!(buf.cursor().col, 3); }
850
851 #[test]
852 fn test_delete_back_multibyte() {
853 let mut buf = EditorBuffer::from_text("café");
854 buf.move_end();
855 buf.delete_back();
856 assert_eq!(buf.line_at(0), Some("caf".to_string()));
857 }
858
859 #[test]
862 fn test_type_then_backspace_then_type() {
863 let mut buf = EditorBuffer::from_text("");
864 buf.insert_char('h');
865 buf.insert_char('e');
866 buf.insert_char('l');
867 buf.delete_back();
868 buf.insert_char('l');
869 buf.insert_char('p');
870 assert_eq!(buf.line_at(0), Some("help".to_string()));
871 }
872
873 #[test]
874 fn test_split_and_rejoin() {
875 let mut buf = EditorBuffer::from_text("helloworld");
876 buf.move_to(0, 5);
877 buf.split_line();
878 assert_eq!(buf.line_count(), 2);
879 assert_eq!(buf.line_at(0), Some("hello".to_string()));
880 assert_eq!(buf.line_at(1), Some("world".to_string()));
881
882 buf.delete_back();
883 assert_eq!(buf.line_count(), 1);
884 assert_eq!(buf.line_at(0), Some("helloworld".to_string()));
885 }
886}