1use crate::intervals::{FoldRegion, StyleId, StyleLayerId};
37use crate::layout::char_width;
38use crate::search::{CharIndex, SearchMatch, SearchOptions, find_all, find_next, find_prev};
39use crate::snapshot::{Cell, HeadlessGrid, HeadlessLine};
40use crate::{
41 FOLD_PLACEHOLDER_STYLE_ID, FoldingManager, IntervalTree, LayoutEngine, LineIndex, PieceTable,
42 SnapshotGenerator,
43};
44use regex::RegexBuilder;
45use std::cmp::Ordering;
46use std::collections::BTreeMap;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub struct Position {
51 pub line: usize,
53 pub column: usize,
55}
56
57impl Position {
58 pub fn new(line: usize, column: usize) -> Self {
60 Self { line, column }
61 }
62}
63
64impl Ord for Position {
65 fn cmp(&self, other: &Self) -> Ordering {
66 self.line
67 .cmp(&other.line)
68 .then_with(|| self.column.cmp(&other.column))
69 }
70}
71
72impl PartialOrd for Position {
73 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
74 Some(self.cmp(other))
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct Selection {
81 pub start: Position,
83 pub end: Position,
85 pub direction: SelectionDirection,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum SelectionDirection {
92 Forward,
94 Backward,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
100pub enum EditCommand {
101 Insert {
103 offset: usize,
105 text: String,
107 },
108 Delete {
110 start: usize,
112 length: usize,
114 },
115 Replace {
117 start: usize,
119 length: usize,
121 text: String,
123 },
124 InsertText {
126 text: String,
128 },
129 Backspace,
131 DeleteForward,
133 Undo,
135 Redo,
137 EndUndoGroup,
139 ReplaceCurrent {
144 query: String,
146 replacement: String,
148 options: SearchOptions,
150 },
151 ReplaceAll {
156 query: String,
158 replacement: String,
160 options: SearchOptions,
162 },
163}
164
165#[derive(Debug, Clone, PartialEq, Eq)]
167pub enum CursorCommand {
168 MoveTo {
170 line: usize,
172 column: usize,
174 },
175 MoveBy {
177 delta_line: isize,
179 delta_column: isize,
181 },
182 SetSelection {
184 start: Position,
186 end: Position,
188 },
189 ExtendSelection {
191 to: Position,
193 },
194 ClearSelection,
196 SetSelections {
198 selections: Vec<Selection>,
200 primary_index: usize,
202 },
203 ClearSecondarySelections,
205 SetRectSelection {
207 anchor: Position,
209 active: Position,
211 },
212 FindNext {
214 query: String,
216 options: SearchOptions,
218 },
219 FindPrev {
221 query: String,
223 options: SearchOptions,
225 },
226}
227
228#[derive(Debug, Clone, PartialEq, Eq)]
230pub enum ViewCommand {
231 SetViewportWidth {
233 width: usize,
235 },
236 ScrollTo {
238 line: usize,
240 },
241 GetViewport {
243 start_row: usize,
245 count: usize,
247 },
248}
249
250#[derive(Debug, Clone, PartialEq, Eq)]
252pub enum StyleCommand {
253 AddStyle {
255 start: usize,
257 end: usize,
259 style_id: StyleId,
261 },
262 RemoveStyle {
264 start: usize,
266 end: usize,
268 style_id: StyleId,
270 },
271 Fold {
273 start_line: usize,
275 end_line: usize,
277 },
278 Unfold {
280 start_line: usize,
282 },
283 UnfoldAll,
285}
286
287#[derive(Debug, Clone, PartialEq, Eq)]
289pub enum Command {
290 Edit(EditCommand),
292 Cursor(CursorCommand),
294 View(ViewCommand),
296 Style(StyleCommand),
298}
299
300#[derive(Debug, Clone)]
302pub enum CommandResult {
303 Success,
305 Text(String),
307 Position(Position),
309 Offset(usize),
311 Viewport(HeadlessGrid),
313 SearchMatch {
315 start: usize,
317 end: usize,
319 },
320 SearchNotFound,
322 ReplaceResult {
324 replaced: usize,
326 },
327}
328
329#[derive(Debug, Clone, PartialEq, Eq)]
331pub enum CommandError {
332 InvalidOffset(usize),
334 InvalidPosition {
336 line: usize,
338 column: usize,
340 },
341 InvalidRange {
343 start: usize,
345 end: usize,
347 },
348 EmptyText,
350 Other(String),
352}
353
354impl std::fmt::Display for CommandError {
355 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
356 match self {
357 CommandError::InvalidOffset(offset) => {
358 write!(f, "Invalid offset: {}", offset)
359 }
360 CommandError::InvalidPosition { line, column } => {
361 write!(f, "Invalid position: line {}, column {}", line, column)
362 }
363 CommandError::InvalidRange { start, end } => {
364 write!(f, "Invalid range: {}..{}", start, end)
365 }
366 CommandError::EmptyText => {
367 write!(f, "Text cannot be empty")
368 }
369 CommandError::Other(msg) => {
370 write!(f, "{}", msg)
371 }
372 }
373 }
374}
375
376impl std::error::Error for CommandError {}
377
378#[derive(Debug, Clone)]
379struct SelectionSetSnapshot {
380 selections: Vec<Selection>,
381 primary_index: usize,
382}
383
384#[derive(Debug, Clone)]
385struct TextEdit {
386 start_before: usize,
387 start_after: usize,
388 deleted_text: String,
389 inserted_text: String,
390}
391
392impl TextEdit {
393 fn deleted_len(&self) -> usize {
394 self.deleted_text.chars().count()
395 }
396
397 fn inserted_len(&self) -> usize {
398 self.inserted_text.chars().count()
399 }
400}
401
402#[derive(Debug, Clone)]
403struct UndoStep {
404 group_id: usize,
405 edits: Vec<TextEdit>,
406 before_selection: SelectionSetSnapshot,
407 after_selection: SelectionSetSnapshot,
408}
409
410#[derive(Debug)]
411struct UndoRedoManager {
412 undo_stack: Vec<UndoStep>,
413 redo_stack: Vec<UndoStep>,
414 max_undo: usize,
415 clean_index: Option<usize>,
418 next_group_id: usize,
419 open_group_id: Option<usize>,
420}
421
422impl UndoRedoManager {
423 fn new(max_undo: usize) -> Self {
424 Self {
425 undo_stack: Vec::new(),
426 redo_stack: Vec::new(),
427 max_undo,
428 clean_index: Some(0),
429 next_group_id: 0,
430 open_group_id: None,
431 }
432 }
433
434 fn can_undo(&self) -> bool {
435 !self.undo_stack.is_empty()
436 }
437
438 fn can_redo(&self) -> bool {
439 !self.redo_stack.is_empty()
440 }
441
442 fn undo_depth(&self) -> usize {
443 self.undo_stack.len()
444 }
445
446 fn redo_depth(&self) -> usize {
447 self.redo_stack.len()
448 }
449
450 fn current_group_id(&self) -> Option<usize> {
451 self.open_group_id
452 }
453
454 fn is_clean(&self) -> bool {
455 self.clean_index == Some(self.undo_stack.len())
456 }
457
458 fn mark_clean(&mut self) {
459 self.clean_index = Some(self.undo_stack.len());
460 self.end_group();
461 }
462
463 fn end_group(&mut self) {
464 self.open_group_id = None;
465 }
466
467 fn clear_redo_and_adjust_clean(&mut self) {
468 if self.redo_stack.is_empty() {
469 return;
470 }
471
472 if let Some(clean_index) = self.clean_index
474 && clean_index > self.undo_stack.len()
475 {
476 self.clean_index = None;
477 }
478
479 self.redo_stack.clear();
480 }
481
482 fn push_step(&mut self, mut step: UndoStep, coalescible_insert: bool) {
483 self.clear_redo_and_adjust_clean();
484
485 if self.undo_stack.len() >= self.max_undo {
486 self.undo_stack.remove(0);
487 if let Some(clean_index) = self.clean_index {
488 if clean_index == 0 {
489 self.clean_index = None;
490 } else {
491 self.clean_index = Some(clean_index - 1);
492 }
493 }
494 }
495
496 let reuse_open_group = coalescible_insert
497 && self.open_group_id.is_some()
498 && self.clean_index != Some(self.undo_stack.len());
499
500 if reuse_open_group {
501 step.group_id = self.open_group_id.expect("checked");
502 } else {
503 step.group_id = self.next_group_id;
504 self.next_group_id = self.next_group_id.wrapping_add(1);
505 }
506
507 if coalescible_insert {
508 self.open_group_id = Some(step.group_id);
509 } else {
510 self.open_group_id = None;
511 }
512
513 self.undo_stack.push(step);
514 }
515
516 fn pop_undo_group(&mut self) -> Option<Vec<UndoStep>> {
517 let last_group_id = self.undo_stack.last().map(|s| s.group_id)?;
518 let mut steps: Vec<UndoStep> = Vec::new();
519
520 while let Some(step) = self.undo_stack.last() {
521 if step.group_id != last_group_id {
522 break;
523 }
524 steps.push(self.undo_stack.pop().expect("checked"));
525 }
526
527 Some(steps)
528 }
529
530 fn pop_redo_group(&mut self) -> Option<Vec<UndoStep>> {
531 let last_group_id = self.redo_stack.last().map(|s| s.group_id)?;
532 let mut steps: Vec<UndoStep> = Vec::new();
533
534 while let Some(step) = self.redo_stack.last() {
535 if step.group_id != last_group_id {
536 break;
537 }
538 steps.push(self.redo_stack.pop().expect("checked"));
539 }
540
541 Some(steps)
542 }
543}
544
545pub struct EditorCore {
566 pub piece_table: PieceTable,
568 pub line_index: LineIndex,
570 pub layout_engine: LayoutEngine,
572 pub interval_tree: IntervalTree,
574 pub style_layers: BTreeMap<StyleLayerId, IntervalTree>,
576 pub folding_manager: FoldingManager,
578 pub cursor_position: Position,
580 pub selection: Option<Selection>,
582 pub secondary_selections: Vec<Selection>,
584 pub viewport_width: usize,
586}
587
588impl EditorCore {
589 pub fn new(text: &str, viewport_width: usize) -> Self {
591 let piece_table = PieceTable::new(text);
592 let line_index = LineIndex::from_text(text);
593 let mut layout_engine = LayoutEngine::new(viewport_width);
594
595 let lines = crate::text::split_lines_preserve_trailing(text);
597 let line_refs: Vec<&str> = lines.iter().map(|s| s.as_str()).collect();
598 layout_engine.from_lines(&line_refs);
599
600 Self {
601 piece_table,
602 line_index,
603 layout_engine,
604 interval_tree: IntervalTree::new(),
605 style_layers: BTreeMap::new(),
606 folding_manager: FoldingManager::new(),
607 cursor_position: Position::new(0, 0),
608 selection: None,
609 secondary_selections: Vec::new(),
610 viewport_width,
611 }
612 }
613
614 pub fn empty(viewport_width: usize) -> Self {
616 Self::new("", viewport_width)
617 }
618
619 pub fn get_text(&self) -> String {
621 self.piece_table.get_text()
622 }
623
624 pub fn line_count(&self) -> usize {
626 self.line_index.line_count()
627 }
628
629 pub fn char_count(&self) -> usize {
631 self.piece_table.char_count()
632 }
633
634 pub fn cursor_position(&self) -> Position {
636 self.cursor_position
637 }
638
639 pub fn selection(&self) -> Option<&Selection> {
641 self.selection.as_ref()
642 }
643
644 pub fn secondary_selections(&self) -> &[Selection] {
646 &self.secondary_selections
647 }
648
649 pub fn get_headless_grid_styled(&self, start_visual_row: usize, count: usize) -> HeadlessGrid {
657 let mut grid = HeadlessGrid::new(start_visual_row, count);
658 if count == 0 {
659 return grid;
660 }
661
662 let total_visual = self.visual_line_count();
663 if start_visual_row >= total_visual {
664 return grid;
665 }
666
667 let end_visual = start_visual_row.saturating_add(count).min(total_visual);
668
669 let mut current_visual = 0usize;
670 let logical_line_count = self.layout_engine.logical_line_count();
671 let regions = self.folding_manager.regions();
672
673 'outer: for logical_line in 0..logical_line_count {
674 if Self::is_logical_line_hidden(regions, logical_line) {
675 continue;
676 }
677
678 let Some(layout) = self.layout_engine.get_line_layout(logical_line) else {
679 continue;
680 };
681
682 let line_text = self
683 .line_index
684 .get_line_text(logical_line)
685 .unwrap_or_default();
686 let line_char_len = line_text.chars().count();
687 let line_start_offset = self.line_index.position_to_char_offset(logical_line, 0);
688
689 for visual_in_line in 0..layout.visual_line_count {
690 if current_visual >= end_visual {
691 break 'outer;
692 }
693
694 if current_visual >= start_visual_row {
695 let segment_start_col = if visual_in_line == 0 {
696 0
697 } else {
698 layout
699 .wrap_points
700 .get(visual_in_line - 1)
701 .map(|wp| wp.char_index)
702 .unwrap_or(0)
703 .min(line_char_len)
704 };
705
706 let segment_end_col = if visual_in_line < layout.wrap_points.len() {
707 layout.wrap_points[visual_in_line]
708 .char_index
709 .min(line_char_len)
710 } else {
711 line_char_len
712 };
713
714 let mut headless_line = HeadlessLine::new(logical_line, visual_in_line > 0);
715
716 for (col, ch) in line_text
717 .chars()
718 .enumerate()
719 .skip(segment_start_col)
720 .take(segment_end_col.saturating_sub(segment_start_col))
721 {
722 let offset = line_start_offset + col;
723 let styles = self.styles_at_offset(offset);
724 headless_line.add_cell(Cell::with_styles(ch, char_width(ch), styles));
725 }
726
727 if visual_in_line + 1 == layout.visual_line_count
729 && let Some(region) =
730 Self::collapsed_region_starting_at(regions, logical_line)
731 && !region.placeholder.is_empty()
732 {
733 if !headless_line.cells.is_empty() {
734 headless_line.add_cell(Cell::with_styles(
735 ' ',
736 char_width(' '),
737 vec![FOLD_PLACEHOLDER_STYLE_ID],
738 ));
739 }
740 for ch in region.placeholder.chars() {
741 headless_line.add_cell(Cell::with_styles(
742 ch,
743 char_width(ch),
744 vec![FOLD_PLACEHOLDER_STYLE_ID],
745 ));
746 }
747 }
748
749 grid.add_line(headless_line);
750 }
751
752 current_visual = current_visual.saturating_add(1);
753 }
754 }
755
756 grid
757 }
758
759 pub fn visual_line_count(&self) -> usize {
761 let regions = self.folding_manager.regions();
762 let mut total = 0usize;
763
764 for logical_line in 0..self.layout_engine.logical_line_count() {
765 if Self::is_logical_line_hidden(regions, logical_line) {
766 continue;
767 }
768
769 total = total.saturating_add(
770 self.layout_engine
771 .get_line_layout(logical_line)
772 .map(|l| l.visual_line_count)
773 .unwrap_or(1),
774 );
775 }
776
777 total
778 }
779
780 pub fn visual_to_logical_line(&self, visual_line: usize) -> (usize, usize) {
782 let regions = self.folding_manager.regions();
783 let mut cumulative_visual = 0usize;
784 let mut last_visible = (0usize, 0usize);
785
786 for logical_line in 0..self.layout_engine.logical_line_count() {
787 if Self::is_logical_line_hidden(regions, logical_line) {
788 continue;
789 }
790
791 let visual_count = self
792 .layout_engine
793 .get_line_layout(logical_line)
794 .map(|l| l.visual_line_count)
795 .unwrap_or(1);
796
797 if cumulative_visual + visual_count > visual_line {
798 return (logical_line, visual_line - cumulative_visual);
799 }
800
801 cumulative_visual = cumulative_visual.saturating_add(visual_count);
802 last_visible = (logical_line, visual_count.saturating_sub(1));
803 }
804
805 last_visible
806 }
807
808 pub fn logical_position_to_visual(
810 &self,
811 logical_line: usize,
812 column: usize,
813 ) -> Option<(usize, usize)> {
814 let regions = self.folding_manager.regions();
815 let logical_line = Self::closest_visible_line(regions, logical_line)?;
816 let visual_start = self.visual_start_for_logical_line(logical_line)?;
817
818 let layout = self.layout_engine.get_line_layout(logical_line)?;
819 let line_text = self
820 .line_index
821 .get_line_text(logical_line)
822 .unwrap_or_default();
823
824 let line_char_len = line_text.chars().count();
825 let column = column.min(line_char_len);
826
827 let mut wrapped_offset = 0usize;
828 let mut segment_start_col = 0usize;
829 for wrap_point in &layout.wrap_points {
830 if column >= wrap_point.char_index {
831 wrapped_offset = wrapped_offset.saturating_add(1);
832 segment_start_col = wrap_point.char_index;
833 } else {
834 break;
835 }
836 }
837
838 let x_in_segment: usize = line_text
839 .chars()
840 .skip(segment_start_col)
841 .take(column.saturating_sub(segment_start_col))
842 .map(char_width)
843 .sum();
844
845 Some((visual_start.saturating_add(wrapped_offset), x_in_segment))
846 }
847
848 pub fn logical_position_to_visual_allow_virtual(
853 &self,
854 logical_line: usize,
855 column: usize,
856 ) -> Option<(usize, usize)> {
857 let regions = self.folding_manager.regions();
858 let logical_line = Self::closest_visible_line(regions, logical_line)?;
859 let visual_start = self.visual_start_for_logical_line(logical_line)?;
860
861 let layout = self.layout_engine.get_line_layout(logical_line)?;
862 let line_text = self
863 .line_index
864 .get_line_text(logical_line)
865 .unwrap_or_default();
866
867 let line_char_len = line_text.chars().count();
868 let clamped_column = column.min(line_char_len);
869
870 let mut wrapped_offset = 0usize;
871 let mut segment_start_col = 0usize;
872 for wrap_point in &layout.wrap_points {
873 if clamped_column >= wrap_point.char_index {
874 wrapped_offset = wrapped_offset.saturating_add(1);
875 segment_start_col = wrap_point.char_index;
876 } else {
877 break;
878 }
879 }
880
881 let x_in_segment: usize = line_text
882 .chars()
883 .skip(segment_start_col)
884 .take(clamped_column.saturating_sub(segment_start_col))
885 .map(char_width)
886 .sum();
887
888 let x_in_segment = x_in_segment + column.saturating_sub(line_char_len);
889
890 Some((visual_start.saturating_add(wrapped_offset), x_in_segment))
891 }
892
893 fn visual_start_for_logical_line(&self, logical_line: usize) -> Option<usize> {
894 if logical_line >= self.layout_engine.logical_line_count() {
895 return None;
896 }
897
898 let regions = self.folding_manager.regions();
899 if Self::is_logical_line_hidden(regions, logical_line) {
900 return None;
901 }
902
903 let mut start = 0usize;
904 for line in 0..logical_line {
905 if Self::is_logical_line_hidden(regions, line) {
906 continue;
907 }
908 start = start.saturating_add(
909 self.layout_engine
910 .get_line_layout(line)
911 .map(|l| l.visual_line_count)
912 .unwrap_or(1),
913 );
914 }
915 Some(start)
916 }
917
918 fn is_logical_line_hidden(regions: &[FoldRegion], logical_line: usize) -> bool {
919 regions.iter().any(|region| {
920 region.is_collapsed
921 && logical_line > region.start_line
922 && logical_line <= region.end_line
923 })
924 }
925
926 fn collapsed_region_starting_at(
927 regions: &[FoldRegion],
928 start_line: usize,
929 ) -> Option<&FoldRegion> {
930 regions
931 .iter()
932 .filter(|region| {
933 region.is_collapsed
934 && region.start_line == start_line
935 && region.end_line > start_line
936 })
937 .min_by_key(|region| region.end_line)
938 }
939
940 fn closest_visible_line(regions: &[FoldRegion], logical_line: usize) -> Option<usize> {
941 let mut line = logical_line;
942 if regions.is_empty() {
943 return Some(line);
944 }
945
946 while Self::is_logical_line_hidden(regions, line) {
947 let Some(start) = regions
948 .iter()
949 .filter(|region| {
950 region.is_collapsed && line > region.start_line && line <= region.end_line
951 })
952 .map(|region| region.start_line)
953 .max()
954 else {
955 break;
956 };
957 line = start;
958 }
959
960 if Self::is_logical_line_hidden(regions, line) {
961 None
962 } else {
963 Some(line)
964 }
965 }
966
967 fn styles_at_offset(&self, offset: usize) -> Vec<StyleId> {
968 let mut styles: Vec<StyleId> = self
969 .interval_tree
970 .query_point(offset)
971 .iter()
972 .map(|interval| interval.style_id)
973 .collect();
974
975 for tree in self.style_layers.values() {
976 styles.extend(
977 tree.query_point(offset)
978 .iter()
979 .map(|interval| interval.style_id),
980 );
981 }
982
983 styles.sort_unstable();
984 styles.dedup();
985 styles
986 }
987}
988
989pub struct CommandExecutor {
1027 editor: EditorCore,
1029 command_history: Vec<Command>,
1031 undo_redo: UndoRedoManager,
1033}
1034
1035impl CommandExecutor {
1036 pub fn new(text: &str, viewport_width: usize) -> Self {
1038 Self {
1039 editor: EditorCore::new(text, viewport_width),
1040 command_history: Vec::new(),
1041 undo_redo: UndoRedoManager::new(1000),
1042 }
1043 }
1044
1045 pub fn empty(viewport_width: usize) -> Self {
1047 Self::new("", viewport_width)
1048 }
1049
1050 pub fn execute(&mut self, command: Command) -> Result<CommandResult, CommandError> {
1052 self.command_history.push(command.clone());
1054
1055 if !matches!(command, Command::Edit(_)) {
1057 self.undo_redo.end_group();
1058 }
1059
1060 match command {
1062 Command::Edit(edit_cmd) => self.execute_edit(edit_cmd),
1063 Command::Cursor(cursor_cmd) => self.execute_cursor(cursor_cmd),
1064 Command::View(view_cmd) => self.execute_view(view_cmd),
1065 Command::Style(style_cmd) => self.execute_style(style_cmd),
1066 }
1067 }
1068
1069 pub fn execute_batch(
1071 &mut self,
1072 commands: Vec<Command>,
1073 ) -> Result<Vec<CommandResult>, CommandError> {
1074 let mut results = Vec::new();
1075
1076 for command in commands {
1077 let result = self.execute(command)?;
1078 results.push(result);
1079 }
1080
1081 Ok(results)
1082 }
1083
1084 pub fn get_command_history(&self) -> &[Command] {
1086 &self.command_history
1087 }
1088
1089 pub fn can_undo(&self) -> bool {
1091 self.undo_redo.can_undo()
1092 }
1093
1094 pub fn can_redo(&self) -> bool {
1096 self.undo_redo.can_redo()
1097 }
1098
1099 pub fn undo_depth(&self) -> usize {
1101 self.undo_redo.undo_depth()
1102 }
1103
1104 pub fn redo_depth(&self) -> usize {
1106 self.undo_redo.redo_depth()
1107 }
1108
1109 pub fn current_change_group(&self) -> Option<usize> {
1111 self.undo_redo.current_group_id()
1112 }
1113
1114 pub fn is_clean(&self) -> bool {
1116 self.undo_redo.is_clean()
1117 }
1118
1119 pub fn mark_clean(&mut self) {
1121 self.undo_redo.mark_clean();
1122 }
1123
1124 pub fn editor(&self) -> &EditorCore {
1126 &self.editor
1127 }
1128
1129 pub fn editor_mut(&mut self) -> &mut EditorCore {
1131 &mut self.editor
1132 }
1133
1134 fn execute_edit(&mut self, command: EditCommand) -> Result<CommandResult, CommandError> {
1136 match command {
1137 EditCommand::Undo => self.execute_undo_command(),
1138 EditCommand::Redo => self.execute_redo_command(),
1139 EditCommand::EndUndoGroup => {
1140 self.undo_redo.end_group();
1141 Ok(CommandResult::Success)
1142 }
1143 EditCommand::ReplaceCurrent {
1144 query,
1145 replacement,
1146 options,
1147 } => self.execute_replace_current_command(query, replacement, options),
1148 EditCommand::ReplaceAll {
1149 query,
1150 replacement,
1151 options,
1152 } => self.execute_replace_all_command(query, replacement, options),
1153 EditCommand::Backspace => self.execute_backspace_command(),
1154 EditCommand::DeleteForward => self.execute_delete_forward_command(),
1155 EditCommand::InsertText { text } => self.execute_insert_text_command(text),
1156 EditCommand::Insert { offset, text } => self.execute_insert_command(offset, text),
1157 EditCommand::Delete { start, length } => self.execute_delete_command(start, length),
1158 EditCommand::Replace {
1159 start,
1160 length,
1161 text,
1162 } => self.execute_replace_command(start, length, text),
1163 }
1164 }
1165
1166 fn execute_undo_command(&mut self) -> Result<CommandResult, CommandError> {
1167 self.undo_redo.end_group();
1168 if !self.undo_redo.can_undo() {
1169 return Err(CommandError::Other("Nothing to undo".to_string()));
1170 }
1171
1172 let steps = self
1173 .undo_redo
1174 .pop_undo_group()
1175 .ok_or_else(|| CommandError::Other("Nothing to undo".to_string()))?;
1176
1177 for step in &steps {
1178 self.apply_undo_edits(&step.edits)?;
1179 self.restore_selection_set(step.before_selection.clone());
1180 }
1181
1182 for step in steps {
1184 self.undo_redo.redo_stack.push(step);
1185 }
1186
1187 Ok(CommandResult::Success)
1188 }
1189
1190 fn execute_redo_command(&mut self) -> Result<CommandResult, CommandError> {
1191 self.undo_redo.end_group();
1192 if !self.undo_redo.can_redo() {
1193 return Err(CommandError::Other("Nothing to redo".to_string()));
1194 }
1195
1196 let steps = self
1197 .undo_redo
1198 .pop_redo_group()
1199 .ok_or_else(|| CommandError::Other("Nothing to redo".to_string()))?;
1200
1201 for step in &steps {
1202 self.apply_redo_edits(&step.edits)?;
1203 self.restore_selection_set(step.after_selection.clone());
1204 }
1205
1206 for step in steps {
1208 self.undo_redo.undo_stack.push(step);
1209 }
1210
1211 Ok(CommandResult::Success)
1212 }
1213
1214 fn execute_insert_text_command(&mut self, text: String) -> Result<CommandResult, CommandError> {
1215 if text.is_empty() {
1216 return Ok(CommandResult::Success);
1217 }
1218
1219 let before_selection = self.snapshot_selection_set();
1220
1221 let mut selections: Vec<Selection> =
1225 Vec::with_capacity(1 + self.editor.secondary_selections.len());
1226 let primary_selection = self.editor.selection.clone().unwrap_or(Selection {
1227 start: self.editor.cursor_position,
1228 end: self.editor.cursor_position,
1229 direction: SelectionDirection::Forward,
1230 });
1231 selections.push(primary_selection);
1232 selections.extend(self.editor.secondary_selections.iter().cloned());
1233
1234 let (selections, primary_index) = crate::selection_set::normalize_selections(selections, 0);
1235
1236 let text_char_len = text.chars().count();
1237
1238 struct Op {
1239 selection_index: usize,
1240 start_offset: usize,
1241 start_after: usize,
1242 delete_len: usize,
1243 deleted_text: String,
1244 insert_text: String,
1245 insert_char_len: usize,
1246 }
1247
1248 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
1249
1250 for (selection_index, selection) in selections.iter().enumerate() {
1251 let (range_start_pos, range_end_pos) = if selection.start <= selection.end {
1252 (selection.start, selection.end)
1253 } else {
1254 (selection.end, selection.start)
1255 };
1256
1257 let (start_offset, start_pad) =
1258 self.position_to_char_offset_and_virtual_pad(range_start_pos);
1259 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
1260
1261 let delete_len = end_offset.saturating_sub(start_offset);
1262 let insert_char_len = start_pad + text_char_len;
1263
1264 let deleted_text = if delete_len == 0 {
1265 String::new()
1266 } else {
1267 self.editor.piece_table.get_range(start_offset, delete_len)
1268 };
1269
1270 let mut insert_text = String::with_capacity(text.len() + start_pad);
1271 for _ in 0..start_pad {
1272 insert_text.push(' ');
1273 }
1274 insert_text.push_str(&text);
1275
1276 ops.push(Op {
1277 selection_index,
1278 start_offset,
1279 start_after: start_offset,
1280 delete_len,
1281 deleted_text,
1282 insert_text,
1283 insert_char_len,
1284 });
1285 }
1286
1287 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
1290 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
1291
1292 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
1293 let mut delta: i64 = 0;
1294 for &idx in &asc_indices {
1295 let op = &mut ops[idx];
1296 let effective_start = (op.start_offset as i64 + delta) as usize;
1297 op.start_after = effective_start;
1298 caret_offsets[op.selection_index] = effective_start + op.insert_char_len;
1299 delta += op.insert_char_len as i64 - op.delete_len as i64;
1300 }
1301
1302 let mut desc_indices = asc_indices;
1304 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
1305
1306 for &idx in &desc_indices {
1307 let op = &ops[idx];
1308
1309 if op.delete_len > 0 {
1310 self.editor
1311 .piece_table
1312 .delete(op.start_offset, op.delete_len);
1313 self.editor
1314 .interval_tree
1315 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
1316 for layer_tree in self.editor.style_layers.values_mut() {
1317 layer_tree
1318 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
1319 }
1320 }
1321
1322 if !op.insert_text.is_empty() {
1323 self.editor
1324 .piece_table
1325 .insert(op.start_offset, &op.insert_text);
1326 self.editor
1327 .interval_tree
1328 .update_for_insertion(op.start_offset, op.insert_char_len);
1329 for layer_tree in self.editor.style_layers.values_mut() {
1330 layer_tree.update_for_insertion(op.start_offset, op.insert_char_len);
1331 }
1332 }
1333 }
1334
1335 let updated_text = self.editor.piece_table.get_text();
1337 self.editor.line_index = LineIndex::from_text(&updated_text);
1338 self.rebuild_layout_engine_from_text(&updated_text);
1339
1340 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
1342 for offset in &caret_offsets {
1343 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
1344 let pos = Position::new(line, column);
1345 new_carets.push(Selection {
1346 start: pos,
1347 end: pos,
1348 direction: SelectionDirection::Forward,
1349 });
1350 }
1351
1352 let (new_carets, new_primary_index) =
1353 crate::selection_set::normalize_selections(new_carets, primary_index);
1354 let primary = new_carets
1355 .get(new_primary_index)
1356 .cloned()
1357 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
1358
1359 self.editor.cursor_position = primary.end;
1360 self.editor.selection = None;
1361 self.editor.secondary_selections = new_carets
1362 .into_iter()
1363 .enumerate()
1364 .filter_map(|(idx, sel)| {
1365 if idx == new_primary_index {
1366 None
1367 } else {
1368 Some(sel)
1369 }
1370 })
1371 .collect();
1372
1373 let after_selection = self.snapshot_selection_set();
1374
1375 let edits: Vec<TextEdit> = ops
1376 .into_iter()
1377 .map(|op| TextEdit {
1378 start_before: op.start_offset,
1379 start_after: op.start_after,
1380 deleted_text: op.deleted_text,
1381 inserted_text: op.insert_text,
1382 })
1383 .collect();
1384
1385 let is_pure_insert = edits.iter().all(|e| e.deleted_text.is_empty());
1386 let coalescible_insert = is_pure_insert && !text.contains('\n');
1387
1388 let step = UndoStep {
1389 group_id: 0,
1390 edits,
1391 before_selection,
1392 after_selection,
1393 };
1394 self.undo_redo.push_step(step, coalescible_insert);
1395
1396 Ok(CommandResult::Success)
1397 }
1398
1399 fn execute_insert_command(
1400 &mut self,
1401 offset: usize,
1402 text: String,
1403 ) -> Result<CommandResult, CommandError> {
1404 if text.is_empty() {
1405 return Err(CommandError::EmptyText);
1406 }
1407
1408 let max_offset = self.editor.piece_table.char_count();
1409 if offset > max_offset {
1410 return Err(CommandError::InvalidOffset(offset));
1411 }
1412
1413 let before_selection = self.snapshot_selection_set();
1414
1415 let affected_line = self.editor.line_index.char_offset_to_position(offset).0;
1416 let inserts_newline = text.contains('\n');
1417
1418 self.editor.piece_table.insert(offset, &text);
1420
1421 let updated_text = self.editor.piece_table.get_text();
1423 self.editor.line_index = LineIndex::from_text(&updated_text);
1424
1425 if inserts_newline {
1427 self.rebuild_layout_engine_from_text(&updated_text);
1428 } else {
1429 let line_text = self
1430 .editor
1431 .line_index
1432 .get_line_text(affected_line)
1433 .unwrap_or_default();
1434 self.editor
1435 .layout_engine
1436 .update_line(affected_line, &line_text);
1437 }
1438
1439 let inserted_len = text.chars().count();
1440
1441 self.editor
1443 .interval_tree
1444 .update_for_insertion(offset, inserted_len);
1445 for layer_tree in self.editor.style_layers.values_mut() {
1446 layer_tree.update_for_insertion(offset, inserted_len);
1447 }
1448
1449 self.normalize_cursor_and_selection();
1451
1452 let after_selection = self.snapshot_selection_set();
1453
1454 let step = UndoStep {
1455 group_id: 0,
1456 edits: vec![TextEdit {
1457 start_before: offset,
1458 start_after: offset,
1459 deleted_text: String::new(),
1460 inserted_text: text.clone(),
1461 }],
1462 before_selection,
1463 after_selection,
1464 };
1465
1466 let coalescible_insert = !text.contains('\n');
1467 self.undo_redo.push_step(step, coalescible_insert);
1468
1469 Ok(CommandResult::Success)
1470 }
1471
1472 fn execute_delete_command(
1473 &mut self,
1474 start: usize,
1475 length: usize,
1476 ) -> Result<CommandResult, CommandError> {
1477 if length == 0 {
1478 return Ok(CommandResult::Success);
1479 }
1480
1481 let max_offset = self.editor.piece_table.char_count();
1482 if start > max_offset {
1483 return Err(CommandError::InvalidOffset(start));
1484 }
1485 if start + length > max_offset {
1486 return Err(CommandError::InvalidRange {
1487 start,
1488 end: start + length,
1489 });
1490 }
1491
1492 let before_selection = self.snapshot_selection_set();
1493
1494 let deleted_text = self.editor.piece_table.get_range(start, length);
1495 let deletes_newline = deleted_text.contains('\n');
1496 let affected_line = self.editor.line_index.char_offset_to_position(start).0;
1497
1498 self.editor.piece_table.delete(start, length);
1500
1501 let updated_text = self.editor.piece_table.get_text();
1503 self.editor.line_index = LineIndex::from_text(&updated_text);
1504
1505 if deletes_newline {
1507 self.rebuild_layout_engine_from_text(&updated_text);
1508 } else {
1509 let line_text = self
1510 .editor
1511 .line_index
1512 .get_line_text(affected_line)
1513 .unwrap_or_default();
1514 self.editor
1515 .layout_engine
1516 .update_line(affected_line, &line_text);
1517 }
1518
1519 self.editor
1521 .interval_tree
1522 .update_for_deletion(start, start + length);
1523 for layer_tree in self.editor.style_layers.values_mut() {
1524 layer_tree.update_for_deletion(start, start + length);
1525 }
1526
1527 self.normalize_cursor_and_selection();
1529
1530 let after_selection = self.snapshot_selection_set();
1531
1532 let step = UndoStep {
1533 group_id: 0,
1534 edits: vec![TextEdit {
1535 start_before: start,
1536 start_after: start,
1537 deleted_text,
1538 inserted_text: String::new(),
1539 }],
1540 before_selection,
1541 after_selection,
1542 };
1543 self.undo_redo.push_step(step, false);
1544
1545 Ok(CommandResult::Success)
1546 }
1547
1548 fn execute_replace_command(
1549 &mut self,
1550 start: usize,
1551 length: usize,
1552 text: String,
1553 ) -> Result<CommandResult, CommandError> {
1554 let max_offset = self.editor.piece_table.char_count();
1555 if start > max_offset {
1556 return Err(CommandError::InvalidOffset(start));
1557 }
1558 if start + length > max_offset {
1559 return Err(CommandError::InvalidRange {
1560 start,
1561 end: start + length,
1562 });
1563 }
1564
1565 if length == 0 && text.is_empty() {
1566 return Ok(CommandResult::Success);
1567 }
1568
1569 let before_selection = self.snapshot_selection_set();
1570
1571 let deleted_text = if length == 0 {
1572 String::new()
1573 } else {
1574 self.editor.piece_table.get_range(start, length)
1575 };
1576
1577 let affected_line = self.editor.line_index.char_offset_to_position(start).0;
1578 let replace_affects_layout = deleted_text.contains('\n') || text.contains('\n');
1579
1580 if length > 0 {
1582 self.editor.piece_table.delete(start, length);
1583 self.editor
1584 .interval_tree
1585 .update_for_deletion(start, start + length);
1586 for layer_tree in self.editor.style_layers.values_mut() {
1587 layer_tree.update_for_deletion(start, start + length);
1588 }
1589 }
1590
1591 let inserted_len = text.chars().count();
1592 if inserted_len > 0 {
1593 self.editor.piece_table.insert(start, &text);
1594 self.editor
1595 .interval_tree
1596 .update_for_insertion(start, inserted_len);
1597 for layer_tree in self.editor.style_layers.values_mut() {
1598 layer_tree.update_for_insertion(start, inserted_len);
1599 }
1600 }
1601
1602 let updated_text = self.editor.piece_table.get_text();
1604 self.editor.line_index = LineIndex::from_text(&updated_text);
1605
1606 if replace_affects_layout {
1607 self.rebuild_layout_engine_from_text(&updated_text);
1608 } else {
1609 let line_text = self
1610 .editor
1611 .line_index
1612 .get_line_text(affected_line)
1613 .unwrap_or_default();
1614 self.editor
1615 .layout_engine
1616 .update_line(affected_line, &line_text);
1617 }
1618
1619 self.normalize_cursor_and_selection();
1621
1622 let after_selection = self.snapshot_selection_set();
1623
1624 let step = UndoStep {
1625 group_id: 0,
1626 edits: vec![TextEdit {
1627 start_before: start,
1628 start_after: start,
1629 deleted_text,
1630 inserted_text: text,
1631 }],
1632 before_selection,
1633 after_selection,
1634 };
1635 self.undo_redo.push_step(step, false);
1636
1637 Ok(CommandResult::Success)
1638 }
1639
1640 fn cursor_char_offset(&self) -> usize {
1641 self.position_to_char_offset_clamped(self.editor.cursor_position)
1642 }
1643
1644 fn primary_selection_char_range(&self) -> Option<SearchMatch> {
1645 let selection = self.editor.selection.as_ref()?;
1646 let (min_pos, max_pos) = crate::selection_set::selection_min_max(selection);
1647 let start = self.position_to_char_offset_clamped(min_pos);
1648 let end = self.position_to_char_offset_clamped(max_pos);
1649 if start == end {
1650 None
1651 } else {
1652 Some(SearchMatch { start, end })
1653 }
1654 }
1655
1656 fn set_primary_selection_by_char_range(&mut self, range: SearchMatch) {
1657 let (start_line, start_col) = self.editor.line_index.char_offset_to_position(range.start);
1658 let (end_line, end_col) = self.editor.line_index.char_offset_to_position(range.end);
1659
1660 self.editor.cursor_position = Position::new(end_line, end_col);
1661 self.editor.secondary_selections.clear();
1662
1663 if range.start == range.end {
1664 self.editor.selection = None;
1665 } else {
1666 self.editor.selection = Some(Selection {
1667 start: Position::new(start_line, start_col),
1668 end: Position::new(end_line, end_col),
1669 direction: SelectionDirection::Forward,
1670 });
1671 }
1672 }
1673
1674 fn execute_find_command(
1675 &mut self,
1676 query: String,
1677 options: SearchOptions,
1678 forward: bool,
1679 ) -> Result<CommandResult, CommandError> {
1680 if query.is_empty() {
1681 return Ok(CommandResult::SearchNotFound);
1682 }
1683
1684 let text = self.editor.piece_table.get_text();
1685 let from = if let Some(selection) = self.primary_selection_char_range() {
1686 if forward {
1687 selection.end
1688 } else {
1689 selection.start
1690 }
1691 } else {
1692 self.cursor_char_offset()
1693 };
1694
1695 let found = if forward {
1696 find_next(&text, &query, options, from)
1697 } else {
1698 find_prev(&text, &query, options, from)
1699 }
1700 .map_err(|err| CommandError::Other(err.to_string()))?;
1701
1702 let Some(m) = found else {
1703 return Ok(CommandResult::SearchNotFound);
1704 };
1705
1706 self.set_primary_selection_by_char_range(m);
1707
1708 Ok(CommandResult::SearchMatch {
1709 start: m.start,
1710 end: m.end,
1711 })
1712 }
1713
1714 fn compile_user_regex(
1715 query: &str,
1716 options: SearchOptions,
1717 ) -> Result<regex::Regex, CommandError> {
1718 RegexBuilder::new(query)
1719 .case_insensitive(!options.case_sensitive)
1720 .multi_line(true)
1721 .build()
1722 .map_err(|err| CommandError::Other(format!("Invalid regex: {}", err)))
1723 }
1724
1725 fn regex_expand_replacement(
1726 re: ®ex::Regex,
1727 text: &str,
1728 index: &CharIndex,
1729 range: SearchMatch,
1730 replacement: &str,
1731 ) -> Result<String, CommandError> {
1732 let start_byte = index.char_to_byte(range.start);
1733 let end_byte = index.char_to_byte(range.end);
1734
1735 let caps = re
1736 .captures_at(text, start_byte)
1737 .ok_or_else(|| CommandError::Other("Regex match not found".to_string()))?;
1738 let whole = caps
1739 .get(0)
1740 .ok_or_else(|| CommandError::Other("Regex match missing capture 0".to_string()))?;
1741 if whole.start() != start_byte || whole.end() != end_byte {
1742 return Err(CommandError::Other(
1743 "Regex match did not align with the selected range".to_string(),
1744 ));
1745 }
1746
1747 let mut expanded = String::new();
1748 caps.expand(replacement, &mut expanded);
1749 Ok(expanded)
1750 }
1751
1752 fn execute_replace_current_command(
1753 &mut self,
1754 query: String,
1755 replacement: String,
1756 options: SearchOptions,
1757 ) -> Result<CommandResult, CommandError> {
1758 if query.is_empty() {
1759 return Err(CommandError::Other("Search query is empty".to_string()));
1760 }
1761
1762 let text = self.editor.piece_table.get_text();
1763 let selection_range = self.primary_selection_char_range();
1764
1765 let mut target = None::<SearchMatch>;
1766 if let Some(range) = selection_range {
1767 let is_match = crate::search::is_match_exact(&text, &query, options, range)
1768 .map_err(|err| CommandError::Other(err.to_string()))?;
1769 if is_match {
1770 target = Some(range);
1771 }
1772 }
1773
1774 if target.is_none() {
1775 let from = self.cursor_char_offset();
1776 target = find_next(&text, &query, options, from)
1777 .map_err(|err| CommandError::Other(err.to_string()))?;
1778 }
1779
1780 let Some(target) = target else {
1781 return Err(CommandError::Other("No match found".to_string()));
1782 };
1783
1784 let index = CharIndex::new(&text);
1785 let inserted_text = if options.regex {
1786 let re = Self::compile_user_regex(&query, options)?;
1787 Self::regex_expand_replacement(&re, &text, &index, target, &replacement)?
1788 } else {
1789 replacement
1790 };
1791
1792 let deleted_text = self
1793 .editor
1794 .piece_table
1795 .get_range(target.start, target.len());
1796
1797 let before_selection = self.snapshot_selection_set();
1798 self.apply_text_ops(vec![(target.start, target.len(), inserted_text.as_str())])?;
1799
1800 let inserted_len = inserted_text.chars().count();
1801 let new_range = SearchMatch {
1802 start: target.start,
1803 end: target.start + inserted_len,
1804 };
1805 self.set_primary_selection_by_char_range(new_range);
1806 let after_selection = self.snapshot_selection_set();
1807
1808 let step = UndoStep {
1809 group_id: 0,
1810 edits: vec![TextEdit {
1811 start_before: target.start,
1812 start_after: target.start,
1813 deleted_text,
1814 inserted_text: inserted_text.clone(),
1815 }],
1816 before_selection,
1817 after_selection,
1818 };
1819 self.undo_redo.push_step(step, false);
1820
1821 Ok(CommandResult::ReplaceResult { replaced: 1 })
1822 }
1823
1824 fn execute_replace_all_command(
1825 &mut self,
1826 query: String,
1827 replacement: String,
1828 options: SearchOptions,
1829 ) -> Result<CommandResult, CommandError> {
1830 if query.is_empty() {
1831 return Err(CommandError::Other("Search query is empty".to_string()));
1832 }
1833
1834 let text = self.editor.piece_table.get_text();
1835 let matches =
1836 find_all(&text, &query, options).map_err(|err| CommandError::Other(err.to_string()))?;
1837 if matches.is_empty() {
1838 return Err(CommandError::Other("No match found".to_string()));
1839 }
1840 let match_count = matches.len();
1841
1842 let index = CharIndex::new(&text);
1843
1844 struct Op {
1845 start_before: usize,
1846 start_after: usize,
1847 delete_len: usize,
1848 deleted_text: String,
1849 inserted_text: String,
1850 inserted_len: usize,
1851 }
1852
1853 let mut ops: Vec<Op> = Vec::with_capacity(match_count);
1854 if options.regex {
1855 let re = Self::compile_user_regex(&query, options)?;
1856 for m in matches {
1857 let deleted_text = {
1858 let start_byte = index.char_to_byte(m.start);
1859 let end_byte = index.char_to_byte(m.end);
1860 text.get(start_byte..end_byte)
1861 .unwrap_or_default()
1862 .to_string()
1863 };
1864 let inserted_text =
1865 Self::regex_expand_replacement(&re, &text, &index, m, &replacement)?;
1866 let inserted_len = inserted_text.chars().count();
1867 ops.push(Op {
1868 start_before: m.start,
1869 start_after: m.start,
1870 delete_len: m.len(),
1871 deleted_text,
1872 inserted_text,
1873 inserted_len,
1874 });
1875 }
1876 } else {
1877 let inserted_len = replacement.chars().count();
1878 for m in matches {
1879 let deleted_text = {
1880 let start_byte = index.char_to_byte(m.start);
1881 let end_byte = index.char_to_byte(m.end);
1882 text.get(start_byte..end_byte)
1883 .unwrap_or_default()
1884 .to_string()
1885 };
1886 ops.push(Op {
1887 start_before: m.start,
1888 start_after: m.start,
1889 delete_len: m.len(),
1890 deleted_text,
1891 inserted_text: replacement.clone(),
1892 inserted_len,
1893 });
1894 }
1895 }
1896
1897 ops.sort_by_key(|op| op.start_before);
1898
1899 let mut delta: i64 = 0;
1900 for op in &mut ops {
1901 let effective_start = op.start_before as i64 + delta;
1902 if effective_start < 0 {
1903 return Err(CommandError::Other(
1904 "ReplaceAll produced an invalid intermediate offset".to_string(),
1905 ));
1906 }
1907 op.start_after = effective_start as usize;
1908 delta += op.inserted_len as i64 - op.delete_len as i64;
1909 }
1910
1911 let before_selection = self.snapshot_selection_set();
1912 let apply_ops: Vec<(usize, usize, &str)> = ops
1913 .iter()
1914 .map(|op| (op.start_before, op.delete_len, op.inserted_text.as_str()))
1915 .collect();
1916 self.apply_text_ops(apply_ops)?;
1917
1918 if let Some(first) = ops.first() {
1919 let caret_end = first.start_after + first.inserted_len;
1920 let select_end = if first.inserted_len == 0 {
1921 first.start_after
1922 } else {
1923 caret_end
1924 };
1925 self.set_primary_selection_by_char_range(SearchMatch {
1926 start: first.start_after,
1927 end: select_end,
1928 });
1929 } else {
1930 self.editor.selection = None;
1931 self.editor.secondary_selections.clear();
1932 }
1933
1934 let after_selection = self.snapshot_selection_set();
1935
1936 let edits: Vec<TextEdit> = ops
1937 .into_iter()
1938 .map(|op| TextEdit {
1939 start_before: op.start_before,
1940 start_after: op.start_after,
1941 deleted_text: op.deleted_text,
1942 inserted_text: op.inserted_text,
1943 })
1944 .collect();
1945
1946 let step = UndoStep {
1947 group_id: 0,
1948 edits,
1949 before_selection,
1950 after_selection,
1951 };
1952 self.undo_redo.push_step(step, false);
1953
1954 Ok(CommandResult::ReplaceResult {
1955 replaced: match_count,
1956 })
1957 }
1958
1959 fn execute_backspace_command(&mut self) -> Result<CommandResult, CommandError> {
1960 self.execute_delete_like_command(false)
1961 }
1962
1963 fn execute_delete_forward_command(&mut self) -> Result<CommandResult, CommandError> {
1964 self.execute_delete_like_command(true)
1965 }
1966
1967 fn execute_delete_like_command(
1968 &mut self,
1969 forward: bool,
1970 ) -> Result<CommandResult, CommandError> {
1971 self.undo_redo.end_group();
1974
1975 let before_selection = self.snapshot_selection_set();
1976 let selections = before_selection.selections.clone();
1977 let primary_index = before_selection.primary_index;
1978
1979 let doc_char_count = self.editor.piece_table.char_count();
1980
1981 #[derive(Debug)]
1982 struct Op {
1983 selection_index: usize,
1984 start_offset: usize,
1985 delete_len: usize,
1986 deleted_text: String,
1987 start_after: usize,
1988 }
1989
1990 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
1991
1992 for (selection_index, selection) in selections.iter().enumerate() {
1993 let (range_start_pos, range_end_pos) = if selection.start <= selection.end {
1994 (selection.start, selection.end)
1995 } else {
1996 (selection.end, selection.start)
1997 };
1998
1999 let (start_offset, end_offset) = if range_start_pos != range_end_pos {
2000 let start_offset = self.position_to_char_offset_clamped(range_start_pos);
2001 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
2002 if start_offset <= end_offset {
2003 (start_offset, end_offset)
2004 } else {
2005 (end_offset, start_offset)
2006 }
2007 } else {
2008 let caret_offset = self.position_to_char_offset_clamped(selection.end);
2009 if forward {
2010 if caret_offset >= doc_char_count {
2011 (caret_offset, caret_offset)
2012 } else {
2013 (caret_offset, (caret_offset + 1).min(doc_char_count))
2014 }
2015 } else if caret_offset == 0 {
2016 (0, 0)
2017 } else {
2018 (caret_offset - 1, caret_offset)
2019 }
2020 };
2021
2022 let delete_len = end_offset.saturating_sub(start_offset);
2023 let deleted_text = if delete_len == 0 {
2024 String::new()
2025 } else {
2026 self.editor.piece_table.get_range(start_offset, delete_len)
2027 };
2028
2029 ops.push(Op {
2030 selection_index,
2031 start_offset,
2032 delete_len,
2033 deleted_text,
2034 start_after: start_offset,
2035 });
2036 }
2037
2038 if !ops.iter().any(|op| op.delete_len > 0) {
2039 return Ok(CommandResult::Success);
2040 }
2041
2042 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
2044 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
2045
2046 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
2047 let mut delta: i64 = 0;
2048 for &idx in &asc_indices {
2049 let op = &mut ops[idx];
2050 let effective_start = (op.start_offset as i64 + delta) as usize;
2051 op.start_after = effective_start;
2052 caret_offsets[op.selection_index] = effective_start;
2053 delta -= op.delete_len as i64;
2054 }
2055
2056 let mut desc_indices = asc_indices;
2058 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
2059
2060 for &idx in &desc_indices {
2061 let op = &ops[idx];
2062 if op.delete_len == 0 {
2063 continue;
2064 }
2065
2066 self.editor
2067 .piece_table
2068 .delete(op.start_offset, op.delete_len);
2069 self.editor
2070 .interval_tree
2071 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
2072 for layer_tree in self.editor.style_layers.values_mut() {
2073 layer_tree.update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
2074 }
2075 }
2076
2077 let updated_text = self.editor.piece_table.get_text();
2079 self.editor.line_index = LineIndex::from_text(&updated_text);
2080 self.rebuild_layout_engine_from_text(&updated_text);
2081
2082 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
2084 for offset in &caret_offsets {
2085 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
2086 let pos = Position::new(line, column);
2087 new_carets.push(Selection {
2088 start: pos,
2089 end: pos,
2090 direction: SelectionDirection::Forward,
2091 });
2092 }
2093
2094 let (new_carets, new_primary_index) =
2095 crate::selection_set::normalize_selections(new_carets, primary_index);
2096 let primary = new_carets
2097 .get(new_primary_index)
2098 .cloned()
2099 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
2100
2101 self.editor.cursor_position = primary.end;
2102 self.editor.selection = None;
2103 self.editor.secondary_selections = new_carets
2104 .into_iter()
2105 .enumerate()
2106 .filter_map(|(idx, sel)| {
2107 if idx == new_primary_index {
2108 None
2109 } else {
2110 Some(sel)
2111 }
2112 })
2113 .collect();
2114
2115 let after_selection = self.snapshot_selection_set();
2116
2117 let edits: Vec<TextEdit> = ops
2118 .into_iter()
2119 .map(|op| TextEdit {
2120 start_before: op.start_offset,
2121 start_after: op.start_after,
2122 deleted_text: op.deleted_text,
2123 inserted_text: String::new(),
2124 })
2125 .collect();
2126
2127 let step = UndoStep {
2128 group_id: 0,
2129 edits,
2130 before_selection,
2131 after_selection,
2132 };
2133 self.undo_redo.push_step(step, false);
2134
2135 Ok(CommandResult::Success)
2136 }
2137
2138 fn snapshot_selection_set(&self) -> SelectionSetSnapshot {
2139 let mut selections: Vec<Selection> =
2140 Vec::with_capacity(1 + self.editor.secondary_selections.len());
2141
2142 let primary = self.editor.selection.clone().unwrap_or(Selection {
2143 start: self.editor.cursor_position,
2144 end: self.editor.cursor_position,
2145 direction: SelectionDirection::Forward,
2146 });
2147 selections.push(primary);
2148 selections.extend(self.editor.secondary_selections.iter().cloned());
2149
2150 let (selections, primary_index) = crate::selection_set::normalize_selections(selections, 0);
2151 SelectionSetSnapshot {
2152 selections,
2153 primary_index,
2154 }
2155 }
2156
2157 fn restore_selection_set(&mut self, snapshot: SelectionSetSnapshot) {
2158 if snapshot.selections.is_empty() {
2159 self.editor.cursor_position = Position::new(0, 0);
2160 self.editor.selection = None;
2161 self.editor.secondary_selections.clear();
2162 return;
2163 }
2164
2165 let primary = snapshot
2166 .selections
2167 .get(snapshot.primary_index)
2168 .cloned()
2169 .unwrap_or_else(|| snapshot.selections[0].clone());
2170
2171 self.editor.cursor_position = primary.end;
2172 self.editor.selection = if primary.start == primary.end {
2173 None
2174 } else {
2175 Some(primary.clone())
2176 };
2177
2178 self.editor.secondary_selections = snapshot
2179 .selections
2180 .into_iter()
2181 .enumerate()
2182 .filter_map(|(idx, sel)| {
2183 if idx == snapshot.primary_index {
2184 None
2185 } else {
2186 Some(sel)
2187 }
2188 })
2189 .collect();
2190
2191 self.normalize_cursor_and_selection();
2192 }
2193
2194 fn apply_undo_edits(&mut self, edits: &[TextEdit]) -> Result<(), CommandError> {
2195 let mut ops: Vec<(usize, usize, &str)> = Vec::with_capacity(edits.len());
2197 for edit in edits {
2198 let start = edit.start_after;
2199 let delete_len = edit.inserted_len();
2200 let insert_text = edit.deleted_text.as_str();
2201 ops.push((start, delete_len, insert_text));
2202 }
2203 self.apply_text_ops(ops)
2204 }
2205
2206 fn apply_redo_edits(&mut self, edits: &[TextEdit]) -> Result<(), CommandError> {
2207 let mut ops: Vec<(usize, usize, &str)> = Vec::with_capacity(edits.len());
2208 for edit in edits {
2209 let start = edit.start_before;
2210 let delete_len = edit.deleted_len();
2211 let insert_text = edit.inserted_text.as_str();
2212 ops.push((start, delete_len, insert_text));
2213 }
2214 self.apply_text_ops(ops)
2215 }
2216
2217 fn apply_text_ops(&mut self, mut ops: Vec<(usize, usize, &str)>) -> Result<(), CommandError> {
2218 ops.sort_by_key(|(start, _, _)| std::cmp::Reverse(*start));
2220
2221 for (start, delete_len, insert_text) in ops {
2222 let max_offset = self.editor.piece_table.char_count();
2223 if start > max_offset {
2224 return Err(CommandError::InvalidOffset(start));
2225 }
2226 if start + delete_len > max_offset {
2227 return Err(CommandError::InvalidRange {
2228 start,
2229 end: start + delete_len,
2230 });
2231 }
2232
2233 if delete_len > 0 {
2234 self.editor.piece_table.delete(start, delete_len);
2235 self.editor
2236 .interval_tree
2237 .update_for_deletion(start, start + delete_len);
2238 for layer_tree in self.editor.style_layers.values_mut() {
2239 layer_tree.update_for_deletion(start, start + delete_len);
2240 }
2241 }
2242
2243 let insert_len = insert_text.chars().count();
2244 if insert_len > 0 {
2245 self.editor.piece_table.insert(start, insert_text);
2246 self.editor
2247 .interval_tree
2248 .update_for_insertion(start, insert_len);
2249 for layer_tree in self.editor.style_layers.values_mut() {
2250 layer_tree.update_for_insertion(start, insert_len);
2251 }
2252 }
2253 }
2254
2255 let updated_text = self.editor.piece_table.get_text();
2257 self.editor.line_index = LineIndex::from_text(&updated_text);
2258 self.rebuild_layout_engine_from_text(&updated_text);
2259 self.normalize_cursor_and_selection();
2260
2261 Ok(())
2262 }
2263
2264 fn execute_cursor(&mut self, command: CursorCommand) -> Result<CommandResult, CommandError> {
2266 match command {
2267 CursorCommand::MoveTo { line, column } => {
2268 if line >= self.editor.line_index.line_count() {
2269 return Err(CommandError::InvalidPosition { line, column });
2270 }
2271
2272 let clamped_column = self.clamp_column_for_line(line, column);
2273 self.editor.cursor_position = Position::new(line, clamped_column);
2274 self.editor.secondary_selections.clear();
2276 Ok(CommandResult::Success)
2277 }
2278 CursorCommand::MoveBy {
2279 delta_line,
2280 delta_column,
2281 } => {
2282 let new_line = if delta_line >= 0 {
2283 self.editor.cursor_position.line + delta_line as usize
2284 } else {
2285 self.editor
2286 .cursor_position
2287 .line
2288 .saturating_sub((-delta_line) as usize)
2289 };
2290
2291 let new_column = if delta_column >= 0 {
2292 self.editor.cursor_position.column + delta_column as usize
2293 } else {
2294 self.editor
2295 .cursor_position
2296 .column
2297 .saturating_sub((-delta_column) as usize)
2298 };
2299
2300 if new_line >= self.editor.line_index.line_count() {
2301 return Err(CommandError::InvalidPosition {
2302 line: new_line,
2303 column: new_column,
2304 });
2305 }
2306
2307 let clamped_column = self.clamp_column_for_line(new_line, new_column);
2308 self.editor.cursor_position = Position::new(new_line, clamped_column);
2309 Ok(CommandResult::Success)
2310 }
2311 CursorCommand::SetSelection { start, end } => {
2312 if start.line >= self.editor.line_index.line_count()
2313 || end.line >= self.editor.line_index.line_count()
2314 {
2315 return Err(CommandError::InvalidPosition {
2316 line: start.line.max(end.line),
2317 column: start.column.max(end.column),
2318 });
2319 }
2320
2321 let start = Position::new(
2322 start.line,
2323 self.clamp_column_for_line(start.line, start.column),
2324 );
2325 let end = Position::new(end.line, self.clamp_column_for_line(end.line, end.column));
2326
2327 let direction = if start.line < end.line
2328 || (start.line == end.line && start.column <= end.column)
2329 {
2330 SelectionDirection::Forward
2331 } else {
2332 SelectionDirection::Backward
2333 };
2334
2335 self.editor.selection = Some(Selection {
2336 start,
2337 end,
2338 direction,
2339 });
2340 Ok(CommandResult::Success)
2341 }
2342 CursorCommand::ExtendSelection { to } => {
2343 if to.line >= self.editor.line_index.line_count() {
2344 return Err(CommandError::InvalidPosition {
2345 line: to.line,
2346 column: to.column,
2347 });
2348 }
2349
2350 let to = Position::new(to.line, self.clamp_column_for_line(to.line, to.column));
2351
2352 if let Some(ref mut selection) = self.editor.selection {
2353 selection.end = to;
2354 selection.direction = if selection.start.line < to.line
2355 || (selection.start.line == to.line && selection.start.column <= to.column)
2356 {
2357 SelectionDirection::Forward
2358 } else {
2359 SelectionDirection::Backward
2360 };
2361 } else {
2362 self.editor.selection = Some(Selection {
2364 start: self.editor.cursor_position,
2365 end: to,
2366 direction: if self.editor.cursor_position.line < to.line
2367 || (self.editor.cursor_position.line == to.line
2368 && self.editor.cursor_position.column <= to.column)
2369 {
2370 SelectionDirection::Forward
2371 } else {
2372 SelectionDirection::Backward
2373 },
2374 });
2375 }
2376 Ok(CommandResult::Success)
2377 }
2378 CursorCommand::ClearSelection => {
2379 self.editor.selection = None;
2380 Ok(CommandResult::Success)
2381 }
2382 CursorCommand::SetSelections {
2383 selections,
2384 primary_index,
2385 } => {
2386 let line_count = self.editor.line_index.line_count();
2387 if selections.is_empty() {
2388 return Err(CommandError::Other(
2389 "SetSelections requires a non-empty selection list".to_string(),
2390 ));
2391 }
2392 if primary_index >= selections.len() {
2393 return Err(CommandError::Other(format!(
2394 "Invalid primary_index {} for {} selections",
2395 primary_index,
2396 selections.len()
2397 )));
2398 }
2399
2400 for sel in &selections {
2401 if sel.start.line >= line_count || sel.end.line >= line_count {
2402 return Err(CommandError::InvalidPosition {
2403 line: sel.start.line.max(sel.end.line),
2404 column: sel.start.column.max(sel.end.column),
2405 });
2406 }
2407 }
2408
2409 let (selections, primary_index) =
2410 crate::selection_set::normalize_selections(selections, primary_index);
2411
2412 let primary = selections
2413 .get(primary_index)
2414 .cloned()
2415 .ok_or_else(|| CommandError::Other("Invalid primary selection".to_string()))?;
2416
2417 self.editor.cursor_position = primary.end;
2418 self.editor.selection = if primary.start == primary.end {
2419 None
2420 } else {
2421 Some(primary.clone())
2422 };
2423
2424 self.editor.secondary_selections = selections
2425 .into_iter()
2426 .enumerate()
2427 .filter_map(|(idx, sel)| {
2428 if idx == primary_index {
2429 None
2430 } else {
2431 Some(sel)
2432 }
2433 })
2434 .collect();
2435
2436 Ok(CommandResult::Success)
2437 }
2438 CursorCommand::ClearSecondarySelections => {
2439 self.editor.secondary_selections.clear();
2440 Ok(CommandResult::Success)
2441 }
2442 CursorCommand::SetRectSelection { anchor, active } => {
2443 let line_count = self.editor.line_index.line_count();
2444 if anchor.line >= line_count || active.line >= line_count {
2445 return Err(CommandError::InvalidPosition {
2446 line: anchor.line.max(active.line),
2447 column: anchor.column.max(active.column),
2448 });
2449 }
2450
2451 let (selections, primary_index) =
2452 crate::selection_set::rect_selections(anchor, active);
2453
2454 self.execute_cursor(CursorCommand::SetSelections {
2456 selections,
2457 primary_index,
2458 })?;
2459 Ok(CommandResult::Success)
2460 }
2461 CursorCommand::FindNext { query, options } => {
2462 self.execute_find_command(query, options, true)
2463 }
2464 CursorCommand::FindPrev { query, options } => {
2465 self.execute_find_command(query, options, false)
2466 }
2467 }
2468 }
2469
2470 fn execute_view(&mut self, command: ViewCommand) -> Result<CommandResult, CommandError> {
2472 match command {
2473 ViewCommand::SetViewportWidth { width } => {
2474 if width == 0 {
2475 return Err(CommandError::Other(
2476 "Viewport width must be greater than 0".to_string(),
2477 ));
2478 }
2479
2480 self.editor.viewport_width = width;
2481 self.editor.layout_engine.set_viewport_width(width);
2482 Ok(CommandResult::Success)
2483 }
2484 ViewCommand::ScrollTo { line } => {
2485 if line >= self.editor.line_index.line_count() {
2486 return Err(CommandError::InvalidPosition { line, column: 0 });
2487 }
2488
2489 Ok(CommandResult::Success)
2492 }
2493 ViewCommand::GetViewport { start_row, count } => {
2494 let text = self.editor.piece_table.get_text();
2495 let generator = SnapshotGenerator::from_text(&text, self.editor.viewport_width);
2496 let grid = generator.get_headless_grid(start_row, count);
2497 Ok(CommandResult::Viewport(grid))
2498 }
2499 }
2500 }
2501
2502 fn execute_style(&mut self, command: StyleCommand) -> Result<CommandResult, CommandError> {
2504 match command {
2505 StyleCommand::AddStyle {
2506 start,
2507 end,
2508 style_id,
2509 } => {
2510 if start >= end {
2511 return Err(CommandError::InvalidRange { start, end });
2512 }
2513
2514 let interval = crate::intervals::Interval::new(start, end, style_id);
2515 self.editor.interval_tree.insert(interval);
2516 Ok(CommandResult::Success)
2517 }
2518 StyleCommand::RemoveStyle {
2519 start,
2520 end,
2521 style_id,
2522 } => {
2523 self.editor.interval_tree.remove(start, end, style_id);
2524 Ok(CommandResult::Success)
2525 }
2526 StyleCommand::Fold {
2527 start_line,
2528 end_line,
2529 } => {
2530 if start_line >= end_line {
2531 return Err(CommandError::InvalidRange {
2532 start: start_line,
2533 end: end_line,
2534 });
2535 }
2536
2537 let mut region = crate::intervals::FoldRegion::new(start_line, end_line);
2538 region.collapse();
2539 self.editor.folding_manager.add_region(region);
2540 Ok(CommandResult::Success)
2541 }
2542 StyleCommand::Unfold { start_line } => {
2543 self.editor.folding_manager.expand_line(start_line);
2544 Ok(CommandResult::Success)
2545 }
2546 StyleCommand::UnfoldAll => {
2547 self.editor.folding_manager.expand_all();
2548 Ok(CommandResult::Success)
2549 }
2550 }
2551 }
2552
2553 fn rebuild_layout_engine_from_text(&mut self, text: &str) {
2554 let lines = crate::text::split_lines_preserve_trailing(text);
2555 let line_refs: Vec<&str> = lines.iter().map(|s| s.as_str()).collect();
2556 self.editor.layout_engine.from_lines(&line_refs);
2557 }
2558
2559 fn position_to_char_offset_clamped(&self, pos: Position) -> usize {
2560 let line_count = self.editor.line_index.line_count();
2561 if line_count == 0 {
2562 return 0;
2563 }
2564
2565 let line = pos.line.min(line_count.saturating_sub(1));
2566 let line_text = self
2567 .editor
2568 .line_index
2569 .get_line_text(line)
2570 .unwrap_or_default();
2571 let line_char_len = line_text.chars().count();
2572 let column = pos.column.min(line_char_len);
2573 self.editor.line_index.position_to_char_offset(line, column)
2574 }
2575
2576 fn position_to_char_offset_and_virtual_pad(&self, pos: Position) -> (usize, usize) {
2577 let line_count = self.editor.line_index.line_count();
2578 if line_count == 0 {
2579 return (0, 0);
2580 }
2581
2582 let line = pos.line.min(line_count.saturating_sub(1));
2583 let line_text = self
2584 .editor
2585 .line_index
2586 .get_line_text(line)
2587 .unwrap_or_default();
2588 let line_char_len = line_text.chars().count();
2589 let clamped_col = pos.column.min(line_char_len);
2590 let offset = self
2591 .editor
2592 .line_index
2593 .position_to_char_offset(line, clamped_col);
2594 let pad = pos.column.saturating_sub(clamped_col);
2595 (offset, pad)
2596 }
2597
2598 fn normalize_cursor_and_selection(&mut self) {
2599 let line_index = &self.editor.line_index;
2600 let line_count = line_index.line_count();
2601 if line_count == 0 {
2602 self.editor.cursor_position = Position::new(0, 0);
2603 self.editor.selection = None;
2604 self.editor.secondary_selections.clear();
2605 return;
2606 }
2607
2608 self.editor.cursor_position =
2609 Self::clamp_position_lenient_with_index(line_index, self.editor.cursor_position);
2610
2611 if let Some(ref mut selection) = self.editor.selection {
2612 selection.start = Self::clamp_position_lenient_with_index(line_index, selection.start);
2613 selection.end = Self::clamp_position_lenient_with_index(line_index, selection.end);
2614 selection.direction = if selection.start.line < selection.end.line
2615 || (selection.start.line == selection.end.line
2616 && selection.start.column <= selection.end.column)
2617 {
2618 SelectionDirection::Forward
2619 } else {
2620 SelectionDirection::Backward
2621 };
2622 }
2623
2624 for selection in &mut self.editor.secondary_selections {
2625 selection.start = Self::clamp_position_lenient_with_index(line_index, selection.start);
2626 selection.end = Self::clamp_position_lenient_with_index(line_index, selection.end);
2627 selection.direction = if selection.start.line < selection.end.line
2628 || (selection.start.line == selection.end.line
2629 && selection.start.column <= selection.end.column)
2630 {
2631 SelectionDirection::Forward
2632 } else {
2633 SelectionDirection::Backward
2634 };
2635 }
2636 }
2637
2638 fn clamp_column_for_line(&self, line: usize, column: usize) -> usize {
2639 Self::clamp_column_for_line_with_index(&self.editor.line_index, line, column)
2640 }
2641
2642 fn clamp_position_lenient_with_index(line_index: &LineIndex, pos: Position) -> Position {
2643 let line_count = line_index.line_count();
2644 if line_count == 0 {
2645 return Position::new(0, 0);
2646 }
2647
2648 let clamped_line = pos.line.min(line_count.saturating_sub(1));
2649 Position::new(clamped_line, pos.column)
2651 }
2652
2653 fn clamp_column_for_line_with_index(
2654 line_index: &LineIndex,
2655 line: usize,
2656 column: usize,
2657 ) -> usize {
2658 let line_start = line_index.position_to_char_offset(line, 0);
2659 let line_end = line_index.position_to_char_offset(line, usize::MAX);
2660 let line_len = line_end.saturating_sub(line_start);
2661 column.min(line_len)
2662 }
2663}
2664
2665#[cfg(test)]
2666mod tests {
2667 use super::*;
2668
2669 #[test]
2670 fn test_edit_insert() {
2671 let mut executor = CommandExecutor::new("Hello", 80);
2672
2673 let result = executor.execute(Command::Edit(EditCommand::Insert {
2674 offset: 5,
2675 text: " World".to_string(),
2676 }));
2677
2678 assert!(result.is_ok());
2679 assert_eq!(executor.editor().get_text(), "Hello World");
2680 }
2681
2682 #[test]
2683 fn test_edit_delete() {
2684 let mut executor = CommandExecutor::new("Hello World", 80);
2685
2686 let result = executor.execute(Command::Edit(EditCommand::Delete {
2687 start: 5,
2688 length: 6,
2689 }));
2690
2691 assert!(result.is_ok());
2692 assert_eq!(executor.editor().get_text(), "Hello");
2693 }
2694
2695 #[test]
2696 fn test_edit_replace() {
2697 let mut executor = CommandExecutor::new("Hello World", 80);
2698
2699 let result = executor.execute(Command::Edit(EditCommand::Replace {
2700 start: 6,
2701 length: 5,
2702 text: "Rust".to_string(),
2703 }));
2704
2705 assert!(result.is_ok());
2706 assert_eq!(executor.editor().get_text(), "Hello Rust");
2707 }
2708
2709 #[test]
2710 fn test_cursor_move_to() {
2711 let mut executor = CommandExecutor::new("Line 1\nLine 2\nLine 3", 80);
2712
2713 let result = executor.execute(Command::Cursor(CursorCommand::MoveTo {
2714 line: 1,
2715 column: 3,
2716 }));
2717
2718 assert!(result.is_ok());
2719 assert_eq!(executor.editor().cursor_position(), Position::new(1, 3));
2720 }
2721
2722 #[test]
2723 fn test_cursor_selection() {
2724 let mut executor = CommandExecutor::new("Hello World", 80);
2725
2726 let result = executor.execute(Command::Cursor(CursorCommand::SetSelection {
2727 start: Position::new(0, 0),
2728 end: Position::new(0, 5),
2729 }));
2730
2731 assert!(result.is_ok());
2732 assert!(executor.editor().selection().is_some());
2733 }
2734
2735 #[test]
2736 fn test_view_set_width() {
2737 let mut executor = CommandExecutor::new("Test", 80);
2738
2739 let result = executor.execute(Command::View(ViewCommand::SetViewportWidth { width: 40 }));
2740
2741 assert!(result.is_ok());
2742 assert_eq!(executor.editor().viewport_width, 40);
2743 }
2744
2745 #[test]
2746 fn test_style_add_remove() {
2747 let mut executor = CommandExecutor::new("Hello World", 80);
2748
2749 let result = executor.execute(Command::Style(StyleCommand::AddStyle {
2751 start: 0,
2752 end: 5,
2753 style_id: 1,
2754 }));
2755 assert!(result.is_ok());
2756
2757 let result = executor.execute(Command::Style(StyleCommand::RemoveStyle {
2759 start: 0,
2760 end: 5,
2761 style_id: 1,
2762 }));
2763 assert!(result.is_ok());
2764 }
2765
2766 #[test]
2767 fn test_batch_execution() {
2768 let mut executor = CommandExecutor::new("", 80);
2769
2770 let commands = vec![
2771 Command::Edit(EditCommand::Insert {
2772 offset: 0,
2773 text: "Hello".to_string(),
2774 }),
2775 Command::Edit(EditCommand::Insert {
2776 offset: 5,
2777 text: " World".to_string(),
2778 }),
2779 ];
2780
2781 let results = executor.execute_batch(commands);
2782 assert!(results.is_ok());
2783 assert_eq!(executor.editor().get_text(), "Hello World");
2784 }
2785
2786 #[test]
2787 fn test_error_invalid_offset() {
2788 let mut executor = CommandExecutor::new("Hello", 80);
2789
2790 let result = executor.execute(Command::Edit(EditCommand::Insert {
2791 offset: 100,
2792 text: "X".to_string(),
2793 }));
2794
2795 assert!(result.is_err());
2796 assert!(matches!(
2797 result.unwrap_err(),
2798 CommandError::InvalidOffset(_)
2799 ));
2800 }
2801}