1use crate::buffer::Buffer;
2use crate::history::History;
3use crate::selection::{Position, Selection};
4use crate::transaction::{EditStep, Transaction};
5
6#[derive(Debug)]
9pub struct Editor {
10 buffer: Buffer,
11 selection: Selection,
12 history: History,
13 sticky_col: Option<usize>,
15}
16
17impl Editor {
18 pub fn new(text: &str) -> Self {
20 Self {
21 buffer: Buffer::from_text(text),
22 selection: Selection::cursor(Position::zero()),
23 history: History::new(),
24 sticky_col: None,
25 }
26 }
27
28 pub fn empty() -> Self {
30 Self::new("")
31 }
32
33 pub fn buffer(&self) -> &Buffer {
36 &self.buffer
37 }
38
39 pub fn text(&self) -> String {
40 self.buffer.text()
41 }
42
43 pub fn selection(&self) -> Selection {
44 self.selection
45 }
46
47 pub fn cursor(&self) -> Position {
48 self.selection.head
49 }
50
51 pub fn version(&self) -> u64 {
52 self.buffer.version()
53 }
54
55 pub fn is_dirty(&self) -> bool {
56 self.history.is_dirty()
57 }
58
59 pub fn can_undo(&self) -> bool {
60 self.history.can_undo()
61 }
62
63 pub fn can_redo(&self) -> bool {
64 self.history.can_redo()
65 }
66
67 pub fn mark_clean(&mut self) {
68 self.history.mark_clean();
69 }
70
71 pub fn selected_text(&self) -> String {
73 if self.selection.is_cursor() {
74 return String::new();
75 }
76 let start = self.buffer.pos_to_char(self.selection.start());
77 let end = self.buffer.pos_to_char(self.selection.end());
78 self.buffer.rope().slice(start..end).to_string()
79 }
80
81 pub fn insert(&mut self, text: &str) {
85 let cursor_before = self.cursor();
86 let (offset, step) = if self.selection.is_cursor() {
87 let offset = self.buffer.pos_to_char(self.cursor());
88 let step = EditStep::insert(offset, text);
89 (offset, step)
90 } else {
91 let start = self.buffer.pos_to_char(self.selection.start());
92 let end = self.buffer.pos_to_char(self.selection.end());
93 let deleted: String = self.buffer.rope().slice(start..end).to_string();
94 let step = EditStep::replace(start, deleted, text.to_string());
95 (start, step)
96 };
97
98 self.apply_step(&step);
99
100 let new_offset = offset + text.chars().count();
101 let new_pos = self.buffer.char_to_pos(new_offset);
102 self.selection = Selection::cursor(new_pos);
103 self.sticky_col = None;
104
105 let tx = Transaction::single(step).with_cursors(cursor_before, new_pos);
106 self.history.push(tx);
107 }
108
109 pub fn backspace(&mut self) {
111 if !self.selection.is_cursor() {
112 self.delete_selection();
113 return;
114 }
115
116 let offset = self.buffer.pos_to_char(self.cursor());
117 if offset == 0 {
118 return;
119 }
120
121 let cursor_before = self.cursor();
122 let prev_char: String = self.buffer.rope().slice((offset - 1)..offset).to_string();
123 let step = EditStep::delete(offset - 1, prev_char);
124
125 self.apply_step(&step);
126 let new_pos = self.buffer.char_to_pos(offset - 1);
127 self.selection = Selection::cursor(new_pos);
128 self.sticky_col = None;
129
130 let tx = Transaction::single(step).with_cursors(cursor_before, new_pos);
131 self.history.push(tx);
132 }
133
134 pub fn delete_forward(&mut self) {
136 if !self.selection.is_cursor() {
137 self.delete_selection();
138 return;
139 }
140
141 let offset = self.buffer.pos_to_char(self.cursor());
142 if offset >= self.buffer.len_chars() {
143 return;
144 }
145
146 let cursor_before = self.cursor();
147 let next_char: String = self.buffer.rope().slice(offset..(offset + 1)).to_string();
148 let step = EditStep::delete(offset, next_char);
149
150 self.apply_step(&step);
151 let new_pos = self.buffer.char_to_pos(offset);
153 self.selection = Selection::cursor(new_pos);
154 self.sticky_col = None;
155
156 let tx = Transaction::single(step).with_cursors(cursor_before, new_pos);
157 self.history.push(tx);
158 }
159
160 pub fn delete_selection(&mut self) {
162 if self.selection.is_cursor() {
163 return;
164 }
165
166 let cursor_before = self.cursor();
167 let start = self.buffer.pos_to_char(self.selection.start());
168 let end = self.buffer.pos_to_char(self.selection.end());
169 let deleted: String = self.buffer.rope().slice(start..end).to_string();
170 let step = EditStep::delete(start, deleted);
171
172 self.apply_step(&step);
173 let new_pos = self.buffer.char_to_pos(start);
174 self.selection = Selection::cursor(new_pos);
175 self.sticky_col = None;
176
177 let tx = Transaction::single(step).with_cursors(cursor_before, new_pos);
178 self.history.push(tx);
179 }
180
181 pub fn insert_newline(&mut self) {
183 self.insert("\n");
184 }
185
186 pub fn apply_transaction(&mut self, tx: Transaction) {
192 if tx.steps.is_empty() {
193 return;
194 }
195 let cursor_before = self.cursor();
196 for step in &tx.steps {
197 self.apply_step(step);
198 }
199 let cursor_after = tx.steps.last().map(|step| {
201 self.buffer.char_to_pos(step.offset + step.inserted_len())
202 }).unwrap_or(cursor_before);
203 self.selection = Selection::cursor(self.buffer.clamp_pos(cursor_after));
204 self.sticky_col = None;
205
206 let tx = tx.with_cursors(cursor_before, cursor_after);
207 self.history.push(tx);
208 }
209
210 pub fn set_cursor(&mut self, pos: Position) {
214 let clamped = self.buffer.clamp_pos(pos);
215 self.selection = Selection::cursor(clamped);
216 self.sticky_col = None;
217 }
218
219 pub fn set_selection(&mut self, anchor: Position, head: Position) {
221 let anchor = self.buffer.clamp_pos(anchor);
222 let head = self.buffer.clamp_pos(head);
223 self.selection = Selection::new(anchor, head);
224 self.sticky_col = None;
225 }
226
227 pub fn extend_selection(&mut self, head: Position) {
229 let head = self.buffer.clamp_pos(head);
230 self.selection = Selection::new(self.selection.anchor, head);
231 }
232
233 pub fn select_all(&mut self) {
235 let last_line = self.buffer.len_lines().saturating_sub(1);
236 let last_col = self.buffer.line_len(last_line);
237 self.selection = Selection::new(
238 Position::zero(),
239 Position::new(last_line, last_col),
240 );
241 self.sticky_col = None;
242 }
243
244 pub fn move_left(&mut self) {
246 if !self.selection.is_cursor() {
247 self.set_cursor(self.selection.start());
249 return;
250 }
251 let offset = self.buffer.pos_to_char(self.cursor());
252 if offset > 0 {
253 self.set_cursor(self.buffer.char_to_pos(offset - 1));
254 }
255 }
256
257 pub fn move_right(&mut self) {
259 if !self.selection.is_cursor() {
260 self.set_cursor(self.selection.end());
261 return;
262 }
263 let offset = self.buffer.pos_to_char(self.cursor());
264 if offset < self.buffer.len_chars() {
265 self.set_cursor(self.buffer.char_to_pos(offset + 1));
266 }
267 }
268
269 pub fn move_up(&mut self) {
271 let pos = self.cursor();
272 if pos.line == 0 {
273 self.set_cursor(Position::new(0, 0));
274 return;
275 }
276 let target_col = self.sticky_col.unwrap_or(pos.col);
277 let new_line = pos.line - 1;
278 let max_col = self.buffer.line_len(new_line);
279 let new_pos = Position::new(new_line, target_col.min(max_col));
280 self.selection = Selection::cursor(new_pos);
281 self.sticky_col = Some(target_col);
282 }
283
284 pub fn move_down(&mut self) {
286 let pos = self.cursor();
287 let last_line = self.buffer.len_lines().saturating_sub(1);
288 if pos.line >= last_line {
289 let max_col = self.buffer.line_len(last_line);
290 self.set_cursor(Position::new(last_line, max_col));
291 return;
292 }
293 let target_col = self.sticky_col.unwrap_or(pos.col);
294 let new_line = pos.line + 1;
295 let max_col = self.buffer.line_len(new_line);
296 let new_pos = Position::new(new_line, target_col.min(max_col));
297 self.selection = Selection::cursor(new_pos);
298 self.sticky_col = Some(target_col);
299 }
300
301 pub fn move_to_line_start(&mut self) {
303 self.set_cursor(Position::new(self.cursor().line, 0));
304 }
305
306 pub fn move_to_line_end(&mut self) {
308 let line = self.cursor().line;
309 let col = self.buffer.line_len(line);
310 self.set_cursor(Position::new(line, col));
311 }
312
313 pub fn move_to_start(&mut self) {
315 self.set_cursor(Position::zero());
316 }
317
318 pub fn move_to_end(&mut self) {
320 let last_line = self.buffer.len_lines().saturating_sub(1);
321 let last_col = self.buffer.line_len(last_line);
322 self.set_cursor(Position::new(last_line, last_col));
323 }
324
325 pub fn move_word_left(&mut self) {
329 if !self.selection.is_cursor() {
330 self.set_cursor(self.selection.start());
331 return;
332 }
333 let pos = self.find_word_boundary_left();
334 self.set_cursor(pos);
335 }
336
337 pub fn move_word_right(&mut self) {
339 if !self.selection.is_cursor() {
340 self.set_cursor(self.selection.end());
341 return;
342 }
343 let pos = self.find_word_boundary_right();
344 self.set_cursor(pos);
345 }
346
347 pub fn extend_selection_left(&mut self) {
351 let offset = self.buffer.pos_to_char(self.selection.head);
352 if offset > 0 {
353 let new_head = self.buffer.char_to_pos(offset - 1);
354 self.selection = Selection::new(self.selection.anchor, new_head);
355 self.sticky_col = None;
356 }
357 }
358
359 pub fn extend_selection_right(&mut self) {
361 let offset = self.buffer.pos_to_char(self.selection.head);
362 if offset < self.buffer.len_chars() {
363 let new_head = self.buffer.char_to_pos(offset + 1);
364 self.selection = Selection::new(self.selection.anchor, new_head);
365 self.sticky_col = None;
366 }
367 }
368
369 pub fn extend_selection_up(&mut self) {
371 let head = self.selection.head;
372 if head.line == 0 {
373 self.extend_selection(Position::new(0, 0));
374 return;
375 }
376 let target_col = self.sticky_col.unwrap_or(head.col);
377 let new_line = head.line - 1;
378 let max_col = self.buffer.line_len(new_line);
379 let new_head = Position::new(new_line, target_col.min(max_col));
380 self.selection = Selection::new(self.selection.anchor, new_head);
381 self.sticky_col = Some(target_col);
382 }
383
384 pub fn extend_selection_down(&mut self) {
386 let head = self.selection.head;
387 let last_line = self.buffer.len_lines().saturating_sub(1);
388 if head.line >= last_line {
389 let max_col = self.buffer.line_len(last_line);
390 self.extend_selection(Position::new(last_line, max_col));
391 return;
392 }
393 let target_col = self.sticky_col.unwrap_or(head.col);
394 let new_line = head.line + 1;
395 let max_col = self.buffer.line_len(new_line);
396 let new_head = Position::new(new_line, target_col.min(max_col));
397 self.selection = Selection::new(self.selection.anchor, new_head);
398 self.sticky_col = Some(target_col);
399 }
400
401 pub fn extend_selection_word_left(&mut self) {
403 let pos = self.find_word_boundary_left();
404 self.selection = Selection::new(self.selection.anchor, pos);
405 self.sticky_col = None;
406 }
407
408 pub fn extend_selection_word_right(&mut self) {
410 let pos = self.find_word_boundary_right();
411 self.selection = Selection::new(self.selection.anchor, pos);
412 self.sticky_col = None;
413 }
414
415 pub fn extend_selection_to_line_start(&mut self) {
417 let head = self.selection.head;
418 self.selection = Selection::new(self.selection.anchor, Position::new(head.line, 0));
419 self.sticky_col = None;
420 }
421
422 pub fn extend_selection_to_line_end(&mut self) {
424 let head = self.selection.head;
425 let col = self.buffer.line_len(head.line);
426 self.selection = Selection::new(self.selection.anchor, Position::new(head.line, col));
427 self.sticky_col = None;
428 }
429
430 pub fn extend_selection_to_start(&mut self) {
432 self.selection = Selection::new(self.selection.anchor, Position::zero());
433 self.sticky_col = None;
434 }
435
436 pub fn extend_selection_to_end(&mut self) {
438 let last_line = self.buffer.len_lines().saturating_sub(1);
439 let last_col = self.buffer.line_len(last_line);
440 self.selection = Selection::new(self.selection.anchor, Position::new(last_line, last_col));
441 self.sticky_col = None;
442 }
443
444 pub fn page_up(&mut self, page_lines: usize) {
446 let pos = self.cursor();
447 let target_col = self.sticky_col.unwrap_or(pos.col);
448 let new_line = pos.line.saturating_sub(page_lines);
449 let max_col = self.buffer.line_len(new_line);
450 let new_pos = Position::new(new_line, target_col.min(max_col));
451 self.selection = Selection::cursor(new_pos);
452 self.sticky_col = Some(target_col);
453 }
454
455 pub fn page_down(&mut self, page_lines: usize) {
457 let pos = self.cursor();
458 let last_line = self.buffer.len_lines().saturating_sub(1);
459 let target_col = self.sticky_col.unwrap_or(pos.col);
460 let new_line = (pos.line + page_lines).min(last_line);
461 let max_col = self.buffer.line_len(new_line);
462 let new_pos = Position::new(new_line, target_col.min(max_col));
463 self.selection = Selection::cursor(new_pos);
464 self.sticky_col = Some(target_col);
465 }
466
467 pub fn select_word(&mut self) {
471 let offset = self.buffer.pos_to_char(self.cursor());
472 let text = self.buffer.text();
473 let chars: Vec<char> = text.chars().collect();
474
475 if chars.is_empty() {
476 return;
477 }
478
479 let idx = offset.min(chars.len().saturating_sub(1));
480 let is_word = |c: char| c.is_alphanumeric() || c == '_';
481
482 if !is_word(chars[idx]) {
484 let start = self.buffer.char_to_pos(idx);
485 let end = self.buffer.char_to_pos(idx + 1);
486 self.selection = Selection::new(start, end);
487 self.sticky_col = None;
488 return;
489 }
490
491 let mut start = idx;
493 while start > 0 && is_word(chars[start - 1]) {
494 start -= 1;
495 }
496 let mut end = idx;
497 while end < chars.len() && is_word(chars[end]) {
498 end += 1;
499 }
500
501 self.selection = Selection::new(
502 self.buffer.char_to_pos(start),
503 self.buffer.char_to_pos(end),
504 );
505 self.sticky_col = None;
506 }
507
508 pub fn select_line(&mut self) {
510 let line = self.cursor().line;
511 let line_start = Position::new(line, 0);
512 let last_line = self.buffer.len_lines().saturating_sub(1);
513 let line_end = if line < last_line {
514 Position::new(line + 1, 0)
516 } else {
517 Position::new(line, self.buffer.line_len(line))
518 };
519 self.selection = Selection::new(line_start, line_end);
520 self.sticky_col = None;
521 }
522
523 pub fn delete_word_back(&mut self) {
527 if !self.selection.is_cursor() {
528 self.delete_selection();
529 return;
530 }
531 let word_start = self.find_word_boundary_left();
532 let cursor = self.cursor();
533 if word_start != cursor {
534 self.set_selection(word_start, cursor);
535 self.delete_selection();
536 }
537 }
538
539 pub fn delete_word_forward(&mut self) {
541 if !self.selection.is_cursor() {
542 self.delete_selection();
543 return;
544 }
545 let word_end = self.find_word_boundary_right();
546 let cursor = self.cursor();
547 if word_end != cursor {
548 self.set_selection(cursor, word_end);
549 self.delete_selection();
550 }
551 }
552
553 pub fn indent(&mut self) {
557 if self.selection.is_cursor() {
558 self.insert(" ");
559 return;
560 }
561 let start_line = self.selection.start().line;
563 let end_line = self.selection.end().line;
564 let mut steps = Vec::new();
565 let mut offset_delta: isize = 0;
566 for line in start_line..=end_line {
567 let line_start = self.buffer.line_to_char(line);
568 let adjusted = (line_start as isize + offset_delta) as usize;
569 steps.push(EditStep::insert(adjusted, " "));
570 offset_delta += 2;
571 }
572 if !steps.is_empty() {
573 self.apply_transaction(Transaction::new(steps));
574 }
575 }
576
577 pub fn outdent(&mut self) {
579 let start_line = self.selection.start().line;
580 let end_line = self.selection.end().line;
581 let mut steps = Vec::new();
582 let mut offset_delta: isize = 0;
583 for line in start_line..=end_line {
584 let line_text = self.buffer.line(line).to_string();
585 let spaces = line_text.chars().take(2).take_while(|&c| c == ' ').count();
586 if spaces > 0 {
587 let line_start = self.buffer.line_to_char(line);
588 let adjusted = (line_start as isize + offset_delta) as usize;
589 let removed: String = line_text.chars().take(spaces).collect();
590 steps.push(EditStep::delete(adjusted, removed));
591 offset_delta -= spaces as isize;
592 }
593 }
594 if !steps.is_empty() {
595 self.apply_transaction(Transaction::new(steps));
596 }
597 }
598
599 pub fn duplicate_lines(&mut self) {
603 let start_line = self.selection.start().line;
604 let end_line = self.selection.end().line;
605
606 let mut lines_text = String::new();
608 for line in start_line..=end_line {
609 let line_content = self.buffer.line(line).to_string();
610 lines_text.push_str(&line_content);
611 }
612 if !lines_text.ends_with('\n') {
614 lines_text.push('\n');
615 }
616
617 let insert_after = if end_line < self.buffer.len_lines().saturating_sub(1) {
619 self.buffer.line_to_char(end_line + 1)
620 } else {
621 self.buffer.len_chars()
623 };
624
625 let cursor_before = self.cursor();
626 let step = EditStep::insert(insert_after, &lines_text);
627 let tx = Transaction::single(step);
628 self.apply_transaction(tx);
629
630 let _new_line = end_line + 1 + (self.cursor().line.saturating_sub(end_line).saturating_sub(1));
632 let cursor_after = Position::new(cursor_before.line + (end_line - start_line + 1), cursor_before.col);
633 self.set_cursor(self.buffer.clamp_pos(cursor_after));
634 }
635
636 pub fn word_start_before_cursor(&self) -> Position {
639 self.find_word_boundary_left()
640 }
641
642 fn find_word_boundary_left(&self) -> Position {
646 let offset = self.buffer.pos_to_char(self.selection.head);
647 if offset == 0 {
648 return Position::zero();
649 }
650 let text = self.buffer.text();
651 let chars: Vec<char> = text.chars().collect();
652 let mut pos = offset;
653
654 while pos > 0 && chars[pos - 1].is_whitespace() {
656 pos -= 1;
657 }
658 let is_word = |c: char| c.is_alphanumeric() || c == '_';
660 if pos > 0 && is_word(chars[pos - 1]) {
661 while pos > 0 && is_word(chars[pos - 1]) {
662 pos -= 1;
663 }
664 } else if pos > 0 {
665 while pos > 0 && !chars[pos - 1].is_whitespace() && !is_word(chars[pos - 1]) {
667 pos -= 1;
668 }
669 }
670
671 self.buffer.char_to_pos(pos)
672 }
673
674 fn find_word_boundary_right(&self) -> Position {
676 let offset = self.buffer.pos_to_char(self.selection.head);
677 let total = self.buffer.len_chars();
678 if offset >= total {
679 return self.buffer.char_to_pos(total);
680 }
681 let text = self.buffer.text();
682 let chars: Vec<char> = text.chars().collect();
683 let mut pos = offset;
684
685 let is_word = |c: char| c.is_alphanumeric() || c == '_';
686 if pos < chars.len() && is_word(chars[pos]) {
688 while pos < chars.len() && is_word(chars[pos]) {
689 pos += 1;
690 }
691 } else if pos < chars.len() && !chars[pos].is_whitespace() {
692 while pos < chars.len() && !chars[pos].is_whitespace() && !is_word(chars[pos]) {
694 pos += 1;
695 }
696 }
697 while pos < chars.len() && chars[pos].is_whitespace() {
699 pos += 1;
700 }
701
702 self.buffer.char_to_pos(pos)
703 }
704
705 pub fn undo(&mut self) {
709 if let Some(inverse) = self.history.undo() {
710 for step in &inverse.steps {
711 self.apply_step(step);
712 }
713 let pos = inverse
715 .cursor_after
716 .unwrap_or_else(|| {
717 inverse.steps.last().map(|step| {
718 self.buffer.char_to_pos(step.offset + step.inserted_len())
719 }).unwrap_or(Position::zero())
720 });
721 self.selection = Selection::cursor(self.buffer.clamp_pos(pos));
722 self.sticky_col = None;
723 }
724 }
725
726 pub fn redo(&mut self) {
728 if let Some(tx) = self.history.redo() {
729 for step in &tx.steps {
730 self.apply_step(step);
731 }
732 let pos = tx
733 .cursor_after
734 .unwrap_or_else(|| {
735 tx.steps.last().map(|step| {
736 self.buffer.char_to_pos(step.offset + step.inserted_len())
737 }).unwrap_or(Position::zero())
738 });
739 self.selection = Selection::cursor(self.buffer.clamp_pos(pos));
740 self.sticky_col = None;
741 }
742 }
743
744 fn apply_step(&mut self, step: &EditStep) {
748 if !step.deleted.is_empty() && !step.inserted.is_empty() {
749 self.buffer.replace(step.offset, step.offset + step.deleted_len(), &step.inserted);
750 } else if !step.inserted.is_empty() {
751 self.buffer.insert(step.offset, &step.inserted);
752 } else if !step.deleted.is_empty() {
753 self.buffer.delete(step.offset, step.offset + step.deleted_len());
754 }
755 }
756}
757
758#[cfg(test)]
759mod tests {
760 use super::*;
761
762 #[test]
763 fn new_editor() {
764 let ed = Editor::new("hello");
765 assert_eq!(ed.text(), "hello");
766 assert_eq!(ed.cursor(), Position::zero());
767 assert!(!ed.is_dirty());
768 }
769
770 #[test]
771 fn insert_at_cursor() {
772 let mut ed = Editor::empty();
773 ed.insert("hello");
774 assert_eq!(ed.text(), "hello");
775 assert_eq!(ed.cursor(), Position::new(0, 5));
776 assert!(ed.is_dirty());
777 }
778
779 #[test]
780 fn insert_multiline() {
781 let mut ed = Editor::empty();
782 ed.insert("line1\nline2");
783 assert_eq!(ed.text(), "line1\nline2");
784 assert_eq!(ed.cursor(), Position::new(1, 5));
785 }
786
787 #[test]
788 fn backspace() {
789 let mut ed = Editor::new("abc");
790 ed.set_cursor(Position::new(0, 3));
791 ed.backspace();
792 assert_eq!(ed.text(), "ab");
793 assert_eq!(ed.cursor(), Position::new(0, 2));
794 }
795
796 #[test]
797 fn backspace_at_start() {
798 let mut ed = Editor::new("abc");
799 ed.set_cursor(Position::new(0, 0));
800 ed.backspace();
801 assert_eq!(ed.text(), "abc"); }
803
804 #[test]
805 fn backspace_joins_lines() {
806 let mut ed = Editor::new("abc\ndef");
807 ed.set_cursor(Position::new(1, 0));
808 ed.backspace();
809 assert_eq!(ed.text(), "abcdef");
810 assert_eq!(ed.cursor(), Position::new(0, 3));
811 }
812
813 #[test]
814 fn delete_forward() {
815 let mut ed = Editor::new("abc");
816 ed.set_cursor(Position::new(0, 0));
817 ed.delete_forward();
818 assert_eq!(ed.text(), "bc");
819 assert_eq!(ed.cursor(), Position::new(0, 0));
820 }
821
822 #[test]
823 fn delete_selection() {
824 let mut ed = Editor::new("hello world");
825 ed.set_selection(Position::new(0, 5), Position::new(0, 11));
826 ed.delete_selection();
827 assert_eq!(ed.text(), "hello");
828 assert_eq!(ed.cursor(), Position::new(0, 5));
829 }
830
831 #[test]
832 fn insert_replaces_selection() {
833 let mut ed = Editor::new("hello world");
834 ed.set_selection(Position::new(0, 6), Position::new(0, 11));
835 ed.insert("rust");
836 assert_eq!(ed.text(), "hello rust");
837 assert_eq!(ed.cursor(), Position::new(0, 10));
838 }
839
840 #[test]
841 fn undo_redo() {
842 let mut ed = Editor::empty();
843 ed.insert("hello world");
844 assert_eq!(ed.text(), "hello world");
845
846 ed.undo();
847 assert_eq!(ed.text(), "");
848
849 ed.redo();
850 assert_eq!(ed.text(), "hello world");
851 }
852
853 #[test]
854 fn undo_coalesced_typing() {
855 let mut ed = Editor::empty();
856 ed.insert("h");
857 ed.insert("e");
858 ed.insert("l");
859 ed.insert("l");
860 ed.insert("o");
861
862 ed.undo();
864 assert_eq!(ed.text(), "");
865 }
866
867 #[test]
868 fn undo_newline_breaks_coalescing() {
869 let mut ed = Editor::empty();
870 ed.insert("a");
871 ed.insert("b");
872 ed.insert("\n");
873 ed.insert("c");
874
875 ed.undo();
877 assert_eq!(ed.text(), "ab\n");
878 ed.undo();
879 assert_eq!(ed.text(), "ab");
880 ed.undo();
881 assert_eq!(ed.text(), "");
882 }
883
884 #[test]
885 fn cursor_movement() {
886 let mut ed = Editor::new("abc\ndef\nghi");
887
888 ed.move_to_end();
889 assert_eq!(ed.cursor(), Position::new(2, 3));
890
891 ed.move_to_start();
892 assert_eq!(ed.cursor(), Position::new(0, 0));
893
894 ed.move_right();
895 assert_eq!(ed.cursor(), Position::new(0, 1));
896
897 ed.move_to_line_end();
898 assert_eq!(ed.cursor(), Position::new(0, 3));
899
900 ed.move_down();
901 assert_eq!(ed.cursor(), Position::new(1, 3));
902
903 ed.move_to_line_start();
904 assert_eq!(ed.cursor(), Position::new(1, 0));
905 }
906
907 #[test]
908 fn sticky_column_on_vertical_movement() {
909 let mut ed = Editor::new("long line\nhi\nlong line");
910 ed.set_cursor(Position::new(0, 8));
911
912 ed.move_down(); assert_eq!(ed.cursor(), Position::new(1, 2));
914
915 ed.move_down(); assert_eq!(ed.cursor(), Position::new(2, 8));
917 }
918
919 #[test]
920 fn select_all() {
921 let mut ed = Editor::new("abc\ndef");
922 ed.select_all();
923 assert_eq!(ed.selected_text(), "abc\ndef");
924 }
925
926 #[test]
927 fn move_left_collapses_selection() {
928 let mut ed = Editor::new("hello");
929 ed.set_selection(Position::new(0, 1), Position::new(0, 4));
930 ed.move_left();
931 assert!(ed.selection().is_cursor());
932 assert_eq!(ed.cursor(), Position::new(0, 1));
933 }
934
935 #[test]
936 fn move_right_collapses_selection() {
937 let mut ed = Editor::new("hello");
938 ed.set_selection(Position::new(0, 1), Position::new(0, 4));
939 ed.move_right();
940 assert!(ed.selection().is_cursor());
941 assert_eq!(ed.cursor(), Position::new(0, 4));
942 }
943
944 #[test]
945 fn dirty_tracking() {
946 let mut ed = Editor::new("hello");
947 assert!(!ed.is_dirty());
948
949 ed.insert(" world");
950 assert!(ed.is_dirty());
951
952 ed.mark_clean();
953 assert!(!ed.is_dirty());
954
955 ed.insert("!");
956 assert!(ed.is_dirty());
957
958 ed.undo();
959 assert!(!ed.is_dirty());
960 }
961
962 #[test]
963 fn unicode_editing() {
964 let mut ed = Editor::new("café");
965 ed.set_cursor(Position::new(0, 4));
966 ed.backspace();
967 assert_eq!(ed.text(), "caf");
968
969 ed.insert("é");
970 assert_eq!(ed.text(), "café");
971 }
972
973 #[test]
974 fn empty_doc_operations() {
975 let mut ed = Editor::empty();
976 ed.backspace(); ed.delete_forward(); ed.move_left(); ed.move_up(); assert_eq!(ed.text(), "");
981 assert_eq!(ed.cursor(), Position::zero());
982 }
983
984 #[test]
985 fn extend_selection() {
986 let mut ed = Editor::new("hello world");
987 ed.set_cursor(Position::new(0, 0));
988 ed.extend_selection(Position::new(0, 5));
989 assert_eq!(ed.selected_text(), "hello");
990 assert!(!ed.selection().is_cursor());
991 }
992
993 #[test]
994 fn multiple_undo_redo_cycles() {
995 let mut ed = Editor::empty();
996 ed.insert("a");
997 ed.insert("b");
998 ed.insert("c");
999 assert_eq!(ed.text(), "abc");
1001
1002 ed.undo();
1003 assert_eq!(ed.text(), "");
1004
1005 ed.redo();
1006 assert_eq!(ed.text(), "abc");
1007
1008 ed.insert("d");
1010 assert!(!ed.can_redo());
1011 assert_eq!(ed.text(), "abcd");
1012 }
1013
1014 #[test]
1015 fn apply_transaction_multi_step() {
1016 let mut ed = Editor::new("Hello\nWorld");
1017 let tx = Transaction::new(vec![
1021 EditStep::replace(0, "Hello".to_string(), "> Hello".to_string()),
1022 EditStep::replace(8, "World".to_string(), "> World".to_string()),
1023 ]);
1024 ed.apply_transaction(tx);
1025 assert_eq!(ed.text(), "> Hello\n> World");
1026
1027 ed.undo();
1029 assert_eq!(ed.text(), "Hello\nWorld");
1030
1031 ed.redo();
1033 assert_eq!(ed.text(), "> Hello\n> World");
1034 }
1035
1036 #[test]
1037 fn apply_transaction_empty() {
1038 let mut ed = Editor::new("hello");
1039 ed.apply_transaction(Transaction::new(vec![]));
1040 assert_eq!(ed.text(), "hello");
1041 assert!(!ed.can_undo());
1043 }
1044
1045 #[test]
1046 fn undo_forward_delete_cursor_position() {
1047 let mut ed = Editor::new("hello world");
1048 ed.set_cursor(Position::new(0, 5));
1049 ed.delete_forward(); ed.delete_forward(); ed.delete_forward(); assert_eq!(ed.text(), "hellorld");
1053
1054 ed.undo();
1055 assert_eq!(ed.cursor(), Position::new(0, 5));
1057 }
1058
1059 #[test]
1062 fn word_movement() {
1063 let mut ed = Editor::new("hello world foo");
1064 ed.set_cursor(Position::new(0, 0));
1065
1066 ed.move_word_right();
1067 assert_eq!(ed.cursor(), Position::new(0, 6)); ed.move_word_right();
1070 assert_eq!(ed.cursor(), Position::new(0, 12)); ed.move_word_left();
1073 assert_eq!(ed.cursor(), Position::new(0, 6)); }
1075
1076 #[test]
1077 fn select_word() {
1078 let mut ed = Editor::new("hello world");
1079 ed.set_cursor(Position::new(0, 7)); ed.select_word();
1081 assert_eq!(ed.selected_text(), "world");
1082 }
1083
1084 #[test]
1085 fn select_line() {
1086 let mut ed = Editor::new("line1\nline2\nline3");
1087 ed.set_cursor(Position::new(1, 2));
1088 ed.select_line();
1089 assert_eq!(ed.selected_text(), "line2\n");
1090 }
1091
1092 #[test]
1093 fn extend_selection_directions() {
1094 let mut ed = Editor::new("abc\ndef");
1095 ed.set_cursor(Position::new(0, 1));
1096
1097 ed.extend_selection_right();
1098 assert_eq!(ed.selected_text(), "b");
1099
1100 ed.extend_selection_right();
1101 assert_eq!(ed.selected_text(), "bc");
1102
1103 ed.extend_selection_left();
1104 assert_eq!(ed.selected_text(), "b");
1105
1106 ed.extend_selection_down();
1107 assert_eq!(ed.selected_text(), "bc\nde");
1109 }
1110
1111 #[test]
1112 fn delete_word_back() {
1113 let mut ed = Editor::new("hello world");
1114 ed.set_cursor(Position::new(0, 11));
1115 ed.delete_word_back();
1116 assert_eq!(ed.text(), "hello ");
1117 }
1118
1119 #[test]
1120 fn delete_word_forward() {
1121 let mut ed = Editor::new("hello world");
1122 ed.set_cursor(Position::new(0, 0));
1123 ed.delete_word_forward();
1124 assert_eq!(ed.text(), "world");
1125 }
1126
1127 #[test]
1128 fn indent_outdent() {
1129 let mut ed = Editor::new("line1\nline2");
1130 ed.set_selection(Position::new(0, 0), Position::new(1, 5));
1131 ed.indent();
1132 assert_eq!(ed.text(), " line1\n line2");
1133
1134 ed.undo();
1136 assert_eq!(ed.text(), "line1\nline2");
1137
1138 ed.set_selection(Position::new(0, 0), Position::new(1, 5));
1140 ed.indent();
1141 ed.set_selection(Position::new(0, 0), Position::new(1, 7));
1142 ed.outdent();
1143 assert_eq!(ed.text(), "line1\nline2");
1144 }
1145
1146 #[test]
1147 fn duplicate_lines() {
1148 let mut ed = Editor::new("line1\nline2\nline3");
1149 ed.set_cursor(Position::new(1, 0)); ed.duplicate_lines();
1151 assert_eq!(ed.text(), "line1\nline2\nline2\nline3");
1152 }
1153
1154 #[test]
1155 fn extend_selection_word() {
1156 let mut ed = Editor::new("hello world");
1157 ed.set_cursor(Position::new(0, 0));
1158 ed.extend_selection_word_right();
1159 assert_eq!(ed.selected_text(), "hello ");
1161 }
1162
1163 #[test]
1164 fn extend_selection_to_line_bounds() {
1165 let mut ed = Editor::new("hello world");
1166 ed.set_cursor(Position::new(0, 5));
1167 ed.extend_selection_to_line_start();
1168 assert_eq!(ed.selected_text(), "hello");
1169
1170 ed.set_cursor(Position::new(0, 5));
1171 ed.extend_selection_to_line_end();
1172 assert_eq!(ed.selected_text(), " world");
1173 }
1174
1175 #[test]
1176 fn word_start_before_cursor_at_end_of_dotted() {
1177 let mut ed = Editor::new("foo.bar");
1178 ed.set_cursor(Position::new(0, 7)); let pos = ed.word_start_before_cursor();
1180 assert_eq!(pos, Position::new(0, 4)); }
1182
1183 #[test]
1184 fn word_start_before_cursor_at_col_zero() {
1185 let mut ed = Editor::new("hello");
1186 ed.set_cursor(Position::new(0, 0));
1187 let pos = ed.word_start_before_cursor();
1188 assert_eq!(pos, Position::new(0, 0));
1189 }
1190
1191 #[test]
1192 fn word_start_before_cursor_middle_of_word() {
1193 let mut ed = Editor::new("hello");
1194 ed.set_cursor(Position::new(0, 3)); let pos = ed.word_start_before_cursor();
1196 assert_eq!(pos, Position::new(0, 0)); }
1198}
1199