1use crate::code::Operation;
2use crate::editor::Editor;
3use crate::selection::Selection;
4
5pub trait Action {
6 fn apply(&mut self, editor: &mut Editor);
7}
8
9pub struct MoveRight {
16 pub shift: bool,
17}
18
19impl Action for MoveRight {
20 fn apply(&mut self, editor: &mut Editor) {
21 let cursor = editor.get_cursor();
22
23 if !self.shift {
24 if let Some(sel) = editor.get_selection() {
25 if !sel.is_empty() {
26 let (_, end) = sel.sorted();
27 editor.set_cursor(end);
28 editor.clear_selection();
29 return;
30 }
31 }
32 }
33
34 if cursor < editor.code_mut().len() {
35 let new_cursor = cursor.saturating_add(1);
36 if self.shift {
37 editor.extend_selection(new_cursor);
38 } else {
39 editor.clear_selection();
40 }
41 editor.set_cursor(new_cursor);
42 }
43 }
44}
45
46pub struct MoveLeft {
53 pub shift: bool,
54}
55
56impl Action for MoveLeft {
57 fn apply(&mut self, editor: &mut Editor) {
58 let cursor = editor.get_cursor();
59
60 if !self.shift {
61 if let Some(sel) = editor.get_selection() {
62 if !sel.is_empty() {
63 let (start, _) = sel.sorted();
64 editor.set_cursor(start);
65 editor.clear_selection();
66 return;
67 }
68 }
69 }
70
71 if cursor > 0 {
72 let new_cursor = cursor.saturating_sub(1);
73 if self.shift {
74 editor.extend_selection(new_cursor);
75 } else {
76 editor.clear_selection();
77 }
78 editor.set_cursor(new_cursor);
79 }
80 }
81}
82
83pub struct MoveUp {
89 pub shift: bool,
90}
91
92impl Action for MoveUp {
93 fn apply(&mut self, editor: &mut Editor) {
94 let cursor = editor.get_cursor();
95 let code = editor.code_mut();
96 let (row, col) = code.point(cursor);
97
98 if row == 0 { return }
99
100 let prev_start = code.line_to_char(row - 1);
101 let prev_len = code.line_len(row - 1);
102 let new_col = col.min(prev_len);
103 let new_cursor = prev_start + new_col;
104
105 if self.shift {
107 editor.extend_selection(new_cursor);
108 } else {
109 editor.clear_selection();
110 }
111
112 editor.set_cursor(new_cursor);
114 }
115}
116
117pub struct MoveDown {
124 pub shift: bool,
125}
126
127impl Action for MoveDown {
128 fn apply(&mut self, editor: &mut Editor) {
129 let cursor = editor.get_cursor();
130 let code = editor.code_mut();
131 let (row, col) = code.point(cursor);
132 let is_last_line = row + 1 >= code.len_lines();
133 if is_last_line { return }
134
135 let next_start = code.line_to_char(row + 1);
136 let next_len = code.line_len(row + 1);
137 let new_col = col.min(next_len);
138 let new_cursor = next_start + new_col;
139
140 if self.shift {
142 editor.extend_selection(new_cursor);
143 } else {
144 editor.clear_selection();
145 }
146
147 editor.set_cursor(new_cursor);
149 }
150}
151
152pub struct InsertText {
154 pub text: String,
155}
156
157impl Action for InsertText {
158 fn apply(&mut self, editor: &mut Editor) {
159 let mut cursor = editor.get_cursor();
161 let mut selection = editor.get_selection();
162
163 let code = editor.code_mut();
165 code.tx();
166 code.set_state_before(cursor, selection);
167
168 if let Some(sel) = &selection {
170 if !sel.is_empty() {
171 let (start, end) = sel.sorted();
172 code.remove(start, end);
173 cursor = start;
174 }
175 }
176 selection = None;
177
178 code.insert(cursor, &self.text);
180 cursor += self.text.chars().count();
181
182 code.set_state_after(cursor, selection);
184 code.commit();
185
186 editor.set_cursor(cursor);
187 editor.set_selection(selection);
188 editor.reset_highlight_cache();
189 }
190}
191
192pub struct InsertNewline;
197
198impl Action for InsertNewline {
199 fn apply(&mut self, editor: &mut Editor) {
200 let cursor = editor.get_cursor();
202 let code = editor.code_mut();
203 let (row, col) = code.point(cursor);
204
205 let indent_level = code.indentation_level(row, col);
207 let indent_text = code.indent().repeat(indent_level);
208
209 let text_to_insert = format!("\n{}", indent_text);
211
212 let mut insert_action = InsertText { text: text_to_insert };
214 insert_action.apply(editor);
215 }
216}
217
218pub struct Delete;
224
225impl Action for Delete {
226 fn apply(&mut self, editor: &mut Editor) {
227 let mut cursor = editor.get_cursor();
229 let mut selection = editor.get_selection();
230
231 let code = editor.code_mut();
233 code.tx();
234 code.set_state_before(cursor, selection);
235
236 if let Some(sel) = &selection && !sel.is_empty() {
237 let (start, end) = sel.sorted();
239 code.remove(start, end);
240 cursor = start;
241 selection = None;
242 } else if cursor > 0 {
243 let (row, col) = code.point(cursor);
245 if code.is_only_indentation_before(row, col) {
246 let from = cursor - col;
247 code.remove(from, cursor);
248 cursor = from;
249 } else {
250 code.remove(cursor - 1, cursor);
251 cursor -= 1;
252 }
253 }
254
255 code.set_state_after(cursor, selection);
257 code.commit();
258
259 editor.set_cursor(cursor);
260 editor.set_selection(selection);
261 editor.reset_highlight_cache();
262 }
263}
264
265pub struct ToggleComment;
266
267impl Action for ToggleComment {
268 fn apply(&mut self, editor: &mut Editor) {
276 let mut cursor = editor.get_cursor();
278 let mut selection = editor.get_selection();
279 let selection_anchor = editor.selection_anchor();
280
281 let code = editor.code_mut();
283
284 code.tx();
285 code.set_state_before(cursor, selection);
286
287 let comment_text = code.comment();
288 let comment_len = comment_text.chars().count();
289
290 let lines_to_handle = if let Some(sel) = &selection && !sel.is_empty() {
292 let (start, end) = sel.sorted();
293 let (start_row, _) = code.point(start);
294 let (end_row, _) = code.point(end);
295 (start_row..=end_row).collect::<Vec<_>>()
296 } else {
297 let (row, _) = code.point(cursor);
298 vec![row]
299 };
300
301 let all_have_comment = lines_to_handle.iter().all(|&line_idx| {
303 let line_start = code.line_to_char(line_idx);
304 let line_len = code.line_len(line_idx);
305 line_start + comment_len <= line_start + line_len
306 && code.slice(line_start, line_start + comment_len) == comment_text
307 });
308
309 let mut comments_added = 0usize;
311 let mut comments_removed = 0usize;
312
313 for &line_idx in lines_to_handle.iter().rev() {
314 let start = code.line_to_char(line_idx);
315 if all_have_comment {
316 let slice = code.slice(start, start + comment_len);
318 if slice == comment_text {
319 code.remove(start, start + comment_len);
320 comments_removed += 1;
321 }
322 } else {
323 code.insert(start, &comment_text);
325 comments_added += 1;
326 }
327 }
328
329 if let Some(sel) = &selection && !sel.is_empty() {
331 let (smin, _) = sel.sorted();
332 let mut anchor = selection_anchor;
333 let is_forward = anchor == smin;
334
335 if is_forward {
336 if !all_have_comment {
337 cursor += comment_len * comments_added;
338 anchor += comment_len;
339 } else {
340 cursor = cursor.saturating_sub(comment_len * comments_removed);
341 anchor = anchor.saturating_sub(comment_len);
342 }
343 } else {
344 if !all_have_comment {
345 cursor += comment_len;
346 anchor += comment_len * comments_added;
347 } else {
348 cursor = cursor.saturating_sub(comment_len);
349 anchor = anchor.saturating_sub(comment_len * comments_removed);
350 }
351 }
352
353 selection = Some(Selection::from_anchor_and_cursor(anchor, cursor));
354 } else {
355 if !all_have_comment {
356 cursor += comment_len;
357 } else {
358 cursor = cursor.saturating_sub(comment_len);
359 }
360 }
361
362 code.set_state_after(cursor, selection);
364 code.commit();
365
366 editor.set_cursor(cursor);
368 editor.set_selection(selection);
369 editor.reset_highlight_cache();
370 }
371}
372
373pub struct Indent;
379
380impl Action for Indent {
381 fn apply(&mut self, editor: &mut Editor) {
382 let mut cursor = editor.get_cursor();
384 let mut selection = editor.get_selection();
385 let selection_anchor = editor.selection_anchor();
386
387 let code = editor.code_mut();
389 code.tx();
390 code.set_state_before(cursor, selection);
391
392 let indent_text = code.indent();
393
394 let lines_to_handle = if let Some(sel) = &selection && !sel.is_empty() {
396 let (start, end) = sel.sorted();
397 let (start_row, _) = code.point(start);
398 let (end_row, _) = code.point(end);
399 (start_row..=end_row).collect::<Vec<_>>()
400 } else {
401 let (row, _) = code.point(cursor);
402 vec![row]
403 };
404
405 let mut indents_added = 0;
407 for &line_idx in lines_to_handle.iter().rev() {
408 let line_start = code.line_to_char(line_idx);
409 code.insert(line_start, &indent_text);
410 indents_added += 1;
411 }
412
413 if let Some(sel) = &selection && !sel.is_empty() {
415 let (smin, _) = sel.sorted();
416 let mut anchor = selection_anchor;
417 let is_forward = anchor == smin;
418
419 if is_forward {
420 cursor += indent_text.len() * indents_added;
421 anchor += indent_text.len();
422 } else {
423 cursor += indent_text.len();
424 anchor += indent_text.len() * indents_added;
425 }
426
427 selection = Some(Selection::from_anchor_and_cursor(anchor, cursor));
428 } else {
429 cursor += indent_text.len();
430 }
431
432 code.set_state_after(cursor, selection);
434 code.commit();
435
436 editor.set_cursor(cursor);
437 editor.set_selection(selection);
438 editor.reset_highlight_cache();
439 }
440}
441
442
443pub struct UnIndent;
449
450impl Action for UnIndent {
451 fn apply(&mut self, editor: &mut Editor) {
452 let mut cursor = editor.get_cursor();
454 let mut selection = editor.get_selection();
455 let selection_anchor = editor.selection_anchor();
456
457 let code = editor.code_mut();
459 code.tx();
460 code.set_state_before(cursor, selection);
461
462 let indent_text = code.indent();
463 let indent_len = indent_text.chars().count();
464
465 let lines_to_handle = if let Some(sel) = &selection && !sel.is_empty() {
467 let (start, end) = sel.sorted();
468 let (start_row, _) = code.point(start);
469 let (end_row, _) = code.point(end);
470 (start_row..=end_row).collect::<Vec<_>>()
471 } else {
472 let (row, _) = code.point(cursor);
473 vec![row]
474 };
475
476 let mut lines_untabbed = 0;
478 for &line_idx in lines_to_handle.iter().rev() {
479 if let Some(indent_cols) = code.find_indent_at_line_start(line_idx) {
480 let remove_count = indent_cols.min(indent_len);
481 if remove_count > 0 {
482 let line_start = code.line_to_char(line_idx);
483 code.remove(line_start, line_start + remove_count);
484 lines_untabbed += 1;
485 }
486 }
487 }
488
489 if let Some(sel) = &selection && !sel.is_empty() {
491 let (smin, _) = sel.sorted();
492 let mut anchor = selection_anchor;
493 let is_forward = anchor == smin;
494
495 if is_forward {
496 cursor = cursor.saturating_sub(indent_len * lines_untabbed);
497 anchor = anchor.saturating_sub(indent_len);
498 } else {
499 cursor = cursor.saturating_sub(indent_len);
500 anchor = anchor.saturating_sub(indent_len * lines_untabbed);
501 }
502
503 selection = Some(Selection::from_anchor_and_cursor(anchor, cursor));
504 } else {
505 cursor = cursor.saturating_sub(indent_len * lines_untabbed);
506 }
507
508 code.set_state_after(cursor, selection);
510 code.commit();
511
512 editor.set_cursor(cursor);
513 editor.set_selection(selection);
514 editor.reset_highlight_cache();
515 }
516}
517
518pub struct SelectAll;
520
521impl Action for SelectAll {
522 fn apply(&mut self, editor: &mut Editor) {
523 let from = 0;
525 let code = editor.code_mut();
526 let to = code.len_chars();
527 let sel = Selection::new(from, to);
528 editor.set_selection(Some(sel));
529 }
530}
531
532pub struct Duplicate;
538
539impl Action for Duplicate {
540 fn apply(&mut self, editor: &mut Editor) {
541 let mut cursor = editor.get_cursor();
543 let mut selection = editor.get_selection();
544 let code = editor.code_mut();
545
546 code.tx();
547 code.set_state_before(cursor, selection);
548
549 if let Some(sel) = &selection {
550 let text = code.slice(sel.start, sel.end);
552 let insert_pos = sel.end;
553 code.insert(insert_pos, &text);
554 cursor = insert_pos + text.chars().count();
555 selection = None;
556 } else {
557 let (line_start, line_end) = code.line_boundaries(cursor);
559 let line_text = code.slice(line_start, line_end);
560 let column = cursor - line_start;
561
562 let insert_pos = line_end;
563 let to_insert = if line_text.ends_with('\n') {
564 line_text.clone()
565 } else {
566 format!("{}\n", line_text)
567 };
568 code.insert(insert_pos, &to_insert);
569
570 let new_line_len = to_insert.trim_end_matches('\n').chars().count();
572 let new_column = column.min(new_line_len);
573 cursor = insert_pos + new_column;
574 }
575
576 code.set_state_after(cursor, selection);
577 code.commit();
578
579 editor.set_cursor(cursor);
581 editor.set_selection(selection);
582 editor.reset_highlight_cache();
583 }
584}
585
586pub struct DeleteLine;
588
589impl Action for DeleteLine {
590 fn apply(&mut self, editor: &mut Editor) {
591 let mut cursor = editor.get_cursor();
593 let mut selection = editor.get_selection();
594 let code = editor.code_mut();
595
596 let (start, end) = code.line_boundaries(cursor);
598
599 if start == end && start == code.len() {
601 return;
602 }
603
604 code.tx();
606 code.set_state_before(cursor, selection);
607 code.remove(start, end);
608 code.set_state_after(start, None);
609 code.commit();
610
611 cursor = start;
613 selection = None;
614 editor.set_cursor(cursor);
615 editor.set_selection(selection);
616 editor.reset_highlight_cache();
617 }
618}
619
620pub struct Cut;
622
623impl Action for Cut {
624 fn apply(&mut self, editor: &mut Editor) {
625 let mut cursor = editor.get_cursor();
627 let mut selection = editor.get_selection();
628
629 let sel = match &selection {
630 Some(sel) if !sel.is_empty() => sel.clone(),
631 _ => return, };
633
634 let text = editor.code_ref().slice(sel.start, sel.end);
636 let _ = editor.set_clipboard(&text);
637
638 let code = editor.code_mut();
640 code.tx();
641 code.set_state_before(cursor, selection);
642 code.remove(sel.start, sel.end);
643 code.set_state_after(sel.start, None);
644 code.commit();
645
646 cursor = sel.start;
648 selection = None;
649 editor.set_cursor(cursor);
650 editor.set_selection(selection);
651 editor.reset_highlight_cache();
652 }
653}
654
655pub struct Copy;
659
660impl Action for Copy {
661 fn apply(&mut self, editor: &mut Editor) {
662 let selection = editor.get_selection();
664
665 let Some(sel) = selection else { return };
667 if sel.is_empty() { return }
668
669 let text = editor.code_ref().slice(sel.start, sel.end);
671 let _ = editor.set_clipboard(&text);
672 }
673}
674
675pub struct Paste;
680
681impl Action for Paste {
682 fn apply(&mut self, editor: &mut Editor) {
683 let Ok(text) = editor.get_clipboard() else { return };
685 if text.is_empty() { return }
686
687 let mut cursor = editor.get_cursor();
689 let mut selection = editor.get_selection();
690 let code = editor.code_mut();
691
692 code.tx();
694 code.set_state_before(cursor, selection);
695
696 if let Some(sel) = &selection {
698 if !sel.is_empty() {
699 let (start, end) = sel.sorted();
700 code.remove(start, end);
701 cursor = start;
702 selection = None;
703 }
704 }
705
706 let inserted = code.smart_paste(cursor, &text);
708 cursor += inserted;
709
710 code.set_state_after(cursor, selection);
712 code.commit();
713
714 editor.set_cursor(cursor);
716 editor.set_selection(selection);
717 editor.reset_highlight_cache();
718 }
719}
720
721pub struct Undo;
726
727impl Action for Undo {
728 fn apply(&mut self, editor: &mut Editor) {
729 let code = editor.code_mut();
731
732 let edits = code.undo();
734 editor.reset_highlight_cache();
735
736 let Some(batch) = edits else { return };
738
739 if let Some(before) = batch.state_before {
741 editor.set_cursor(before.offset);
742 editor.set_selection(before.selection);
743 return;
744 }
745
746 for edit in batch.edits.iter().rev() {
748 match edit.operation {
749 Operation::Insert => {
750 editor.set_cursor(edit.start);
751 }
752 Operation::Remove => {
753 editor.set_cursor(edit.start + edit.text.chars().count());
754 }
755 }
756 }
757 }
758}
759
760pub struct Redo;
765
766impl Action for Redo {
767 fn apply(&mut self, editor: &mut Editor) {
768 let code = editor.code_mut();
770
771 let edits = code.redo();
773 editor.reset_highlight_cache();
774
775 let Some(batch) = edits else { return };
777
778 if let Some(after) = batch.state_after {
780 editor.set_cursor(after.offset);
781 editor.set_selection(after.selection);
782 return;
783 }
784
785 for edit in batch.edits {
787 match edit.operation {
788 Operation::Insert => {
789 editor.set_cursor(edit.start + edit.text.chars().count());
790 }
791 Operation::Remove => {
792 editor.set_cursor(edit.start);
793 }
794 }
795 }
796 }
797}