1use crate::intervals::{FoldRegion, StyleId, StyleLayerId};
37use crate::layout::{cell_width_at, char_width, visual_x_for_column};
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, Copy, PartialEq, Eq)]
100pub enum TabKeyBehavior {
101 Tab,
103 Spaces,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq)]
109pub enum EditCommand {
110 Insert {
112 offset: usize,
114 text: String,
116 },
117 Delete {
119 start: usize,
121 length: usize,
123 },
124 Replace {
126 start: usize,
128 length: usize,
130 text: String,
132 },
133 InsertText {
135 text: String,
137 },
138 InsertTab,
143 Backspace,
145 DeleteForward,
147 Undo,
149 Redo,
151 EndUndoGroup,
153 ReplaceCurrent {
158 query: String,
160 replacement: String,
162 options: SearchOptions,
164 },
165 ReplaceAll {
170 query: String,
172 replacement: String,
174 options: SearchOptions,
176 },
177}
178
179#[derive(Debug, Clone, PartialEq, Eq)]
181pub enum CursorCommand {
182 MoveTo {
184 line: usize,
186 column: usize,
188 },
189 MoveBy {
191 delta_line: isize,
193 delta_column: isize,
195 },
196 SetSelection {
198 start: Position,
200 end: Position,
202 },
203 ExtendSelection {
205 to: Position,
207 },
208 ClearSelection,
210 SetSelections {
212 selections: Vec<Selection>,
214 primary_index: usize,
216 },
217 ClearSecondarySelections,
219 SetRectSelection {
221 anchor: Position,
223 active: Position,
225 },
226 FindNext {
228 query: String,
230 options: SearchOptions,
232 },
233 FindPrev {
235 query: String,
237 options: SearchOptions,
239 },
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
244pub enum ViewCommand {
245 SetViewportWidth {
247 width: usize,
249 },
250 SetTabWidth {
252 width: usize,
254 },
255 SetTabKeyBehavior {
257 behavior: TabKeyBehavior,
259 },
260 ScrollTo {
262 line: usize,
264 },
265 GetViewport {
267 start_row: usize,
269 count: usize,
271 },
272}
273
274#[derive(Debug, Clone, PartialEq, Eq)]
276pub enum StyleCommand {
277 AddStyle {
279 start: usize,
281 end: usize,
283 style_id: StyleId,
285 },
286 RemoveStyle {
288 start: usize,
290 end: usize,
292 style_id: StyleId,
294 },
295 Fold {
297 start_line: usize,
299 end_line: usize,
301 },
302 Unfold {
304 start_line: usize,
306 },
307 UnfoldAll,
309}
310
311#[derive(Debug, Clone, PartialEq, Eq)]
313pub enum Command {
314 Edit(EditCommand),
316 Cursor(CursorCommand),
318 View(ViewCommand),
320 Style(StyleCommand),
322}
323
324#[derive(Debug, Clone)]
326pub enum CommandResult {
327 Success,
329 Text(String),
331 Position(Position),
333 Offset(usize),
335 Viewport(HeadlessGrid),
337 SearchMatch {
339 start: usize,
341 end: usize,
343 },
344 SearchNotFound,
346 ReplaceResult {
348 replaced: usize,
350 },
351}
352
353#[derive(Debug, Clone, PartialEq, Eq)]
355pub enum CommandError {
356 InvalidOffset(usize),
358 InvalidPosition {
360 line: usize,
362 column: usize,
364 },
365 InvalidRange {
367 start: usize,
369 end: usize,
371 },
372 EmptyText,
374 Other(String),
376}
377
378impl std::fmt::Display for CommandError {
379 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380 match self {
381 CommandError::InvalidOffset(offset) => {
382 write!(f, "Invalid offset: {}", offset)
383 }
384 CommandError::InvalidPosition { line, column } => {
385 write!(f, "Invalid position: line {}, column {}", line, column)
386 }
387 CommandError::InvalidRange { start, end } => {
388 write!(f, "Invalid range: {}..{}", start, end)
389 }
390 CommandError::EmptyText => {
391 write!(f, "Text cannot be empty")
392 }
393 CommandError::Other(msg) => {
394 write!(f, "{}", msg)
395 }
396 }
397 }
398}
399
400impl std::error::Error for CommandError {}
401
402#[derive(Debug, Clone)]
403struct SelectionSetSnapshot {
404 selections: Vec<Selection>,
405 primary_index: usize,
406}
407
408#[derive(Debug, Clone)]
409struct TextEdit {
410 start_before: usize,
411 start_after: usize,
412 deleted_text: String,
413 inserted_text: String,
414}
415
416impl TextEdit {
417 fn deleted_len(&self) -> usize {
418 self.deleted_text.chars().count()
419 }
420
421 fn inserted_len(&self) -> usize {
422 self.inserted_text.chars().count()
423 }
424}
425
426#[derive(Debug, Clone)]
427struct UndoStep {
428 group_id: usize,
429 edits: Vec<TextEdit>,
430 before_selection: SelectionSetSnapshot,
431 after_selection: SelectionSetSnapshot,
432}
433
434#[derive(Debug)]
435struct UndoRedoManager {
436 undo_stack: Vec<UndoStep>,
437 redo_stack: Vec<UndoStep>,
438 max_undo: usize,
439 clean_index: Option<usize>,
442 next_group_id: usize,
443 open_group_id: Option<usize>,
444}
445
446impl UndoRedoManager {
447 fn new(max_undo: usize) -> Self {
448 Self {
449 undo_stack: Vec::new(),
450 redo_stack: Vec::new(),
451 max_undo,
452 clean_index: Some(0),
453 next_group_id: 0,
454 open_group_id: None,
455 }
456 }
457
458 fn can_undo(&self) -> bool {
459 !self.undo_stack.is_empty()
460 }
461
462 fn can_redo(&self) -> bool {
463 !self.redo_stack.is_empty()
464 }
465
466 fn undo_depth(&self) -> usize {
467 self.undo_stack.len()
468 }
469
470 fn redo_depth(&self) -> usize {
471 self.redo_stack.len()
472 }
473
474 fn current_group_id(&self) -> Option<usize> {
475 self.open_group_id
476 }
477
478 fn is_clean(&self) -> bool {
479 self.clean_index == Some(self.undo_stack.len())
480 }
481
482 fn mark_clean(&mut self) {
483 self.clean_index = Some(self.undo_stack.len());
484 self.end_group();
485 }
486
487 fn end_group(&mut self) {
488 self.open_group_id = None;
489 }
490
491 fn clear_redo_and_adjust_clean(&mut self) {
492 if self.redo_stack.is_empty() {
493 return;
494 }
495
496 if let Some(clean_index) = self.clean_index
498 && clean_index > self.undo_stack.len()
499 {
500 self.clean_index = None;
501 }
502
503 self.redo_stack.clear();
504 }
505
506 fn push_step(&mut self, mut step: UndoStep, coalescible_insert: bool) {
507 self.clear_redo_and_adjust_clean();
508
509 if self.undo_stack.len() >= self.max_undo {
510 self.undo_stack.remove(0);
511 if let Some(clean_index) = self.clean_index {
512 if clean_index == 0 {
513 self.clean_index = None;
514 } else {
515 self.clean_index = Some(clean_index - 1);
516 }
517 }
518 }
519
520 let reuse_open_group = coalescible_insert
521 && self.open_group_id.is_some()
522 && self.clean_index != Some(self.undo_stack.len());
523
524 if reuse_open_group {
525 step.group_id = self.open_group_id.expect("checked");
526 } else {
527 step.group_id = self.next_group_id;
528 self.next_group_id = self.next_group_id.wrapping_add(1);
529 }
530
531 if coalescible_insert {
532 self.open_group_id = Some(step.group_id);
533 } else {
534 self.open_group_id = None;
535 }
536
537 self.undo_stack.push(step);
538 }
539
540 fn pop_undo_group(&mut self) -> Option<Vec<UndoStep>> {
541 let last_group_id = self.undo_stack.last().map(|s| s.group_id)?;
542 let mut steps: Vec<UndoStep> = Vec::new();
543
544 while let Some(step) = self.undo_stack.last() {
545 if step.group_id != last_group_id {
546 break;
547 }
548 steps.push(self.undo_stack.pop().expect("checked"));
549 }
550
551 Some(steps)
552 }
553
554 fn pop_redo_group(&mut self) -> Option<Vec<UndoStep>> {
555 let last_group_id = self.redo_stack.last().map(|s| s.group_id)?;
556 let mut steps: Vec<UndoStep> = Vec::new();
557
558 while let Some(step) = self.redo_stack.last() {
559 if step.group_id != last_group_id {
560 break;
561 }
562 steps.push(self.redo_stack.pop().expect("checked"));
563 }
564
565 Some(steps)
566 }
567}
568
569pub struct EditorCore {
590 pub piece_table: PieceTable,
592 pub line_index: LineIndex,
594 pub layout_engine: LayoutEngine,
596 pub interval_tree: IntervalTree,
598 pub style_layers: BTreeMap<StyleLayerId, IntervalTree>,
600 pub folding_manager: FoldingManager,
602 pub cursor_position: Position,
604 pub selection: Option<Selection>,
606 pub secondary_selections: Vec<Selection>,
608 pub viewport_width: usize,
610}
611
612impl EditorCore {
613 pub fn new(text: &str, viewport_width: usize) -> Self {
615 let piece_table = PieceTable::new(text);
616 let line_index = LineIndex::from_text(text);
617 let mut layout_engine = LayoutEngine::new(viewport_width);
618
619 let lines = crate::text::split_lines_preserve_trailing(text);
621 let line_refs: Vec<&str> = lines.iter().map(|s| s.as_str()).collect();
622 layout_engine.from_lines(&line_refs);
623
624 Self {
625 piece_table,
626 line_index,
627 layout_engine,
628 interval_tree: IntervalTree::new(),
629 style_layers: BTreeMap::new(),
630 folding_manager: FoldingManager::new(),
631 cursor_position: Position::new(0, 0),
632 selection: None,
633 secondary_selections: Vec::new(),
634 viewport_width,
635 }
636 }
637
638 pub fn empty(viewport_width: usize) -> Self {
640 Self::new("", viewport_width)
641 }
642
643 pub fn get_text(&self) -> String {
645 self.piece_table.get_text()
646 }
647
648 pub fn line_count(&self) -> usize {
650 self.line_index.line_count()
651 }
652
653 pub fn char_count(&self) -> usize {
655 self.piece_table.char_count()
656 }
657
658 pub fn cursor_position(&self) -> Position {
660 self.cursor_position
661 }
662
663 pub fn selection(&self) -> Option<&Selection> {
665 self.selection.as_ref()
666 }
667
668 pub fn secondary_selections(&self) -> &[Selection] {
670 &self.secondary_selections
671 }
672
673 pub fn get_headless_grid_styled(&self, start_visual_row: usize, count: usize) -> HeadlessGrid {
681 let mut grid = HeadlessGrid::new(start_visual_row, count);
682 if count == 0 {
683 return grid;
684 }
685
686 let tab_width = self.layout_engine.tab_width();
687
688 let total_visual = self.visual_line_count();
689 if start_visual_row >= total_visual {
690 return grid;
691 }
692
693 let end_visual = start_visual_row.saturating_add(count).min(total_visual);
694
695 let mut current_visual = 0usize;
696 let logical_line_count = self.layout_engine.logical_line_count();
697 let regions = self.folding_manager.regions();
698
699 'outer: for logical_line in 0..logical_line_count {
700 if Self::is_logical_line_hidden(regions, logical_line) {
701 continue;
702 }
703
704 let Some(layout) = self.layout_engine.get_line_layout(logical_line) else {
705 continue;
706 };
707
708 let line_text = self
709 .line_index
710 .get_line_text(logical_line)
711 .unwrap_or_default();
712 let line_char_len = line_text.chars().count();
713 let line_start_offset = self.line_index.position_to_char_offset(logical_line, 0);
714
715 for visual_in_line in 0..layout.visual_line_count {
716 if current_visual >= end_visual {
717 break 'outer;
718 }
719
720 if current_visual >= start_visual_row {
721 let segment_start_col = if visual_in_line == 0 {
722 0
723 } else {
724 layout
725 .wrap_points
726 .get(visual_in_line - 1)
727 .map(|wp| wp.char_index)
728 .unwrap_or(0)
729 .min(line_char_len)
730 };
731
732 let segment_end_col = if visual_in_line < layout.wrap_points.len() {
733 layout.wrap_points[visual_in_line]
734 .char_index
735 .min(line_char_len)
736 } else {
737 line_char_len
738 };
739
740 let mut headless_line = HeadlessLine::new(logical_line, visual_in_line > 0);
741 let mut x_in_line =
742 visual_x_for_column(&line_text, segment_start_col, tab_width);
743
744 for (col, ch) in line_text
745 .chars()
746 .enumerate()
747 .skip(segment_start_col)
748 .take(segment_end_col.saturating_sub(segment_start_col))
749 {
750 let offset = line_start_offset + col;
751 let styles = self.styles_at_offset(offset);
752 let w = cell_width_at(ch, x_in_line, tab_width);
753 x_in_line = x_in_line.saturating_add(w);
754 headless_line.add_cell(Cell::with_styles(ch, w, styles));
755 }
756
757 if visual_in_line + 1 == layout.visual_line_count
759 && let Some(region) =
760 Self::collapsed_region_starting_at(regions, logical_line)
761 && !region.placeholder.is_empty()
762 {
763 if !headless_line.cells.is_empty() {
764 x_in_line = x_in_line.saturating_add(char_width(' '));
765 headless_line.add_cell(Cell::with_styles(
766 ' ',
767 char_width(' '),
768 vec![FOLD_PLACEHOLDER_STYLE_ID],
769 ));
770 }
771 for ch in region.placeholder.chars() {
772 let w = cell_width_at(ch, x_in_line, tab_width);
773 x_in_line = x_in_line.saturating_add(w);
774 headless_line.add_cell(Cell::with_styles(
775 ch,
776 w,
777 vec![FOLD_PLACEHOLDER_STYLE_ID],
778 ));
779 }
780 }
781
782 grid.add_line(headless_line);
783 }
784
785 current_visual = current_visual.saturating_add(1);
786 }
787 }
788
789 grid
790 }
791
792 pub fn visual_line_count(&self) -> usize {
794 let regions = self.folding_manager.regions();
795 let mut total = 0usize;
796
797 for logical_line in 0..self.layout_engine.logical_line_count() {
798 if Self::is_logical_line_hidden(regions, logical_line) {
799 continue;
800 }
801
802 total = total.saturating_add(
803 self.layout_engine
804 .get_line_layout(logical_line)
805 .map(|l| l.visual_line_count)
806 .unwrap_or(1),
807 );
808 }
809
810 total
811 }
812
813 pub fn visual_to_logical_line(&self, visual_line: usize) -> (usize, usize) {
815 let regions = self.folding_manager.regions();
816 let mut cumulative_visual = 0usize;
817 let mut last_visible = (0usize, 0usize);
818
819 for logical_line in 0..self.layout_engine.logical_line_count() {
820 if Self::is_logical_line_hidden(regions, logical_line) {
821 continue;
822 }
823
824 let visual_count = self
825 .layout_engine
826 .get_line_layout(logical_line)
827 .map(|l| l.visual_line_count)
828 .unwrap_or(1);
829
830 if cumulative_visual + visual_count > visual_line {
831 return (logical_line, visual_line - cumulative_visual);
832 }
833
834 cumulative_visual = cumulative_visual.saturating_add(visual_count);
835 last_visible = (logical_line, visual_count.saturating_sub(1));
836 }
837
838 last_visible
839 }
840
841 pub fn logical_position_to_visual(
843 &self,
844 logical_line: usize,
845 column: usize,
846 ) -> Option<(usize, usize)> {
847 let regions = self.folding_manager.regions();
848 let logical_line = Self::closest_visible_line(regions, logical_line)?;
849 let visual_start = self.visual_start_for_logical_line(logical_line)?;
850
851 let tab_width = self.layout_engine.tab_width();
852
853 let layout = self.layout_engine.get_line_layout(logical_line)?;
854 let line_text = self
855 .line_index
856 .get_line_text(logical_line)
857 .unwrap_or_default();
858
859 let line_char_len = line_text.chars().count();
860 let column = column.min(line_char_len);
861
862 let mut wrapped_offset = 0usize;
863 let mut segment_start_col = 0usize;
864 for wrap_point in &layout.wrap_points {
865 if column >= wrap_point.char_index {
866 wrapped_offset = wrapped_offset.saturating_add(1);
867 segment_start_col = wrap_point.char_index;
868 } else {
869 break;
870 }
871 }
872
873 let seg_start_x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
874 let mut x_in_line = seg_start_x_in_line;
875 let mut x_in_segment = 0usize;
876 for ch in line_text
877 .chars()
878 .skip(segment_start_col)
879 .take(column.saturating_sub(segment_start_col))
880 {
881 let w = cell_width_at(ch, x_in_line, tab_width);
882 x_in_line = x_in_line.saturating_add(w);
883 x_in_segment = x_in_segment.saturating_add(w);
884 }
885
886 Some((visual_start.saturating_add(wrapped_offset), x_in_segment))
887 }
888
889 pub fn logical_position_to_visual_allow_virtual(
894 &self,
895 logical_line: usize,
896 column: usize,
897 ) -> Option<(usize, usize)> {
898 let regions = self.folding_manager.regions();
899 let logical_line = Self::closest_visible_line(regions, logical_line)?;
900 let visual_start = self.visual_start_for_logical_line(logical_line)?;
901
902 let tab_width = self.layout_engine.tab_width();
903
904 let layout = self.layout_engine.get_line_layout(logical_line)?;
905 let line_text = self
906 .line_index
907 .get_line_text(logical_line)
908 .unwrap_or_default();
909
910 let line_char_len = line_text.chars().count();
911 let clamped_column = column.min(line_char_len);
912
913 let mut wrapped_offset = 0usize;
914 let mut segment_start_col = 0usize;
915 for wrap_point in &layout.wrap_points {
916 if clamped_column >= wrap_point.char_index {
917 wrapped_offset = wrapped_offset.saturating_add(1);
918 segment_start_col = wrap_point.char_index;
919 } else {
920 break;
921 }
922 }
923
924 let seg_start_x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
925 let mut x_in_line = seg_start_x_in_line;
926 let mut x_in_segment = 0usize;
927 for ch in line_text
928 .chars()
929 .skip(segment_start_col)
930 .take(clamped_column.saturating_sub(segment_start_col))
931 {
932 let w = cell_width_at(ch, x_in_line, tab_width);
933 x_in_line = x_in_line.saturating_add(w);
934 x_in_segment = x_in_segment.saturating_add(w);
935 }
936
937 let x_in_segment = x_in_segment + column.saturating_sub(line_char_len);
938
939 Some((visual_start.saturating_add(wrapped_offset), x_in_segment))
940 }
941
942 fn visual_start_for_logical_line(&self, logical_line: usize) -> Option<usize> {
943 if logical_line >= self.layout_engine.logical_line_count() {
944 return None;
945 }
946
947 let regions = self.folding_manager.regions();
948 if Self::is_logical_line_hidden(regions, logical_line) {
949 return None;
950 }
951
952 let mut start = 0usize;
953 for line in 0..logical_line {
954 if Self::is_logical_line_hidden(regions, line) {
955 continue;
956 }
957 start = start.saturating_add(
958 self.layout_engine
959 .get_line_layout(line)
960 .map(|l| l.visual_line_count)
961 .unwrap_or(1),
962 );
963 }
964 Some(start)
965 }
966
967 fn is_logical_line_hidden(regions: &[FoldRegion], logical_line: usize) -> bool {
968 regions.iter().any(|region| {
969 region.is_collapsed
970 && logical_line > region.start_line
971 && logical_line <= region.end_line
972 })
973 }
974
975 fn collapsed_region_starting_at(
976 regions: &[FoldRegion],
977 start_line: usize,
978 ) -> Option<&FoldRegion> {
979 regions
980 .iter()
981 .filter(|region| {
982 region.is_collapsed
983 && region.start_line == start_line
984 && region.end_line > start_line
985 })
986 .min_by_key(|region| region.end_line)
987 }
988
989 fn closest_visible_line(regions: &[FoldRegion], logical_line: usize) -> Option<usize> {
990 let mut line = logical_line;
991 if regions.is_empty() {
992 return Some(line);
993 }
994
995 while Self::is_logical_line_hidden(regions, line) {
996 let Some(start) = regions
997 .iter()
998 .filter(|region| {
999 region.is_collapsed && line > region.start_line && line <= region.end_line
1000 })
1001 .map(|region| region.start_line)
1002 .max()
1003 else {
1004 break;
1005 };
1006 line = start;
1007 }
1008
1009 if Self::is_logical_line_hidden(regions, line) {
1010 None
1011 } else {
1012 Some(line)
1013 }
1014 }
1015
1016 fn styles_at_offset(&self, offset: usize) -> Vec<StyleId> {
1017 let mut styles: Vec<StyleId> = self
1018 .interval_tree
1019 .query_point(offset)
1020 .iter()
1021 .map(|interval| interval.style_id)
1022 .collect();
1023
1024 for tree in self.style_layers.values() {
1025 styles.extend(
1026 tree.query_point(offset)
1027 .iter()
1028 .map(|interval| interval.style_id),
1029 );
1030 }
1031
1032 styles.sort_unstable();
1033 styles.dedup();
1034 styles
1035 }
1036}
1037
1038pub struct CommandExecutor {
1076 editor: EditorCore,
1078 command_history: Vec<Command>,
1080 undo_redo: UndoRedoManager,
1082 tab_key_behavior: TabKeyBehavior,
1084}
1085
1086impl CommandExecutor {
1087 pub fn new(text: &str, viewport_width: usize) -> Self {
1089 Self {
1090 editor: EditorCore::new(text, viewport_width),
1091 command_history: Vec::new(),
1092 undo_redo: UndoRedoManager::new(1000),
1093 tab_key_behavior: TabKeyBehavior::Tab,
1094 }
1095 }
1096
1097 pub fn empty(viewport_width: usize) -> Self {
1099 Self::new("", viewport_width)
1100 }
1101
1102 pub fn execute(&mut self, command: Command) -> Result<CommandResult, CommandError> {
1104 self.command_history.push(command.clone());
1106
1107 if !matches!(command, Command::Edit(_)) {
1109 self.undo_redo.end_group();
1110 }
1111
1112 match command {
1114 Command::Edit(edit_cmd) => self.execute_edit(edit_cmd),
1115 Command::Cursor(cursor_cmd) => self.execute_cursor(cursor_cmd),
1116 Command::View(view_cmd) => self.execute_view(view_cmd),
1117 Command::Style(style_cmd) => self.execute_style(style_cmd),
1118 }
1119 }
1120
1121 pub fn execute_batch(
1123 &mut self,
1124 commands: Vec<Command>,
1125 ) -> Result<Vec<CommandResult>, CommandError> {
1126 let mut results = Vec::new();
1127
1128 for command in commands {
1129 let result = self.execute(command)?;
1130 results.push(result);
1131 }
1132
1133 Ok(results)
1134 }
1135
1136 pub fn get_command_history(&self) -> &[Command] {
1138 &self.command_history
1139 }
1140
1141 pub fn can_undo(&self) -> bool {
1143 self.undo_redo.can_undo()
1144 }
1145
1146 pub fn can_redo(&self) -> bool {
1148 self.undo_redo.can_redo()
1149 }
1150
1151 pub fn undo_depth(&self) -> usize {
1153 self.undo_redo.undo_depth()
1154 }
1155
1156 pub fn redo_depth(&self) -> usize {
1158 self.undo_redo.redo_depth()
1159 }
1160
1161 pub fn current_change_group(&self) -> Option<usize> {
1163 self.undo_redo.current_group_id()
1164 }
1165
1166 pub fn is_clean(&self) -> bool {
1168 self.undo_redo.is_clean()
1169 }
1170
1171 pub fn mark_clean(&mut self) {
1173 self.undo_redo.mark_clean();
1174 }
1175
1176 pub fn editor(&self) -> &EditorCore {
1178 &self.editor
1179 }
1180
1181 pub fn editor_mut(&mut self) -> &mut EditorCore {
1183 &mut self.editor
1184 }
1185
1186 pub fn tab_key_behavior(&self) -> TabKeyBehavior {
1188 self.tab_key_behavior
1189 }
1190
1191 pub fn set_tab_key_behavior(&mut self, behavior: TabKeyBehavior) {
1193 self.tab_key_behavior = behavior;
1194 }
1195
1196 fn execute_edit(&mut self, command: EditCommand) -> Result<CommandResult, CommandError> {
1198 match command {
1199 EditCommand::Undo => self.execute_undo_command(),
1200 EditCommand::Redo => self.execute_redo_command(),
1201 EditCommand::EndUndoGroup => {
1202 self.undo_redo.end_group();
1203 Ok(CommandResult::Success)
1204 }
1205 EditCommand::ReplaceCurrent {
1206 query,
1207 replacement,
1208 options,
1209 } => self.execute_replace_current_command(query, replacement, options),
1210 EditCommand::ReplaceAll {
1211 query,
1212 replacement,
1213 options,
1214 } => self.execute_replace_all_command(query, replacement, options),
1215 EditCommand::Backspace => self.execute_backspace_command(),
1216 EditCommand::DeleteForward => self.execute_delete_forward_command(),
1217 EditCommand::InsertText { text } => self.execute_insert_text_command(text),
1218 EditCommand::InsertTab => self.execute_insert_tab_command(),
1219 EditCommand::Insert { offset, text } => self.execute_insert_command(offset, text),
1220 EditCommand::Delete { start, length } => self.execute_delete_command(start, length),
1221 EditCommand::Replace {
1222 start,
1223 length,
1224 text,
1225 } => self.execute_replace_command(start, length, text),
1226 }
1227 }
1228
1229 fn execute_undo_command(&mut self) -> Result<CommandResult, CommandError> {
1230 self.undo_redo.end_group();
1231 if !self.undo_redo.can_undo() {
1232 return Err(CommandError::Other("Nothing to undo".to_string()));
1233 }
1234
1235 let steps = self
1236 .undo_redo
1237 .pop_undo_group()
1238 .ok_or_else(|| CommandError::Other("Nothing to undo".to_string()))?;
1239
1240 for step in &steps {
1241 self.apply_undo_edits(&step.edits)?;
1242 self.restore_selection_set(step.before_selection.clone());
1243 }
1244
1245 for step in steps {
1247 self.undo_redo.redo_stack.push(step);
1248 }
1249
1250 Ok(CommandResult::Success)
1251 }
1252
1253 fn execute_redo_command(&mut self) -> Result<CommandResult, CommandError> {
1254 self.undo_redo.end_group();
1255 if !self.undo_redo.can_redo() {
1256 return Err(CommandError::Other("Nothing to redo".to_string()));
1257 }
1258
1259 let steps = self
1260 .undo_redo
1261 .pop_redo_group()
1262 .ok_or_else(|| CommandError::Other("Nothing to redo".to_string()))?;
1263
1264 for step in &steps {
1265 self.apply_redo_edits(&step.edits)?;
1266 self.restore_selection_set(step.after_selection.clone());
1267 }
1268
1269 for step in steps {
1271 self.undo_redo.undo_stack.push(step);
1272 }
1273
1274 Ok(CommandResult::Success)
1275 }
1276
1277 fn execute_insert_text_command(&mut self, text: String) -> Result<CommandResult, CommandError> {
1278 if text.is_empty() {
1279 return Ok(CommandResult::Success);
1280 }
1281
1282 let before_selection = self.snapshot_selection_set();
1283
1284 let mut selections: Vec<Selection> =
1288 Vec::with_capacity(1 + self.editor.secondary_selections.len());
1289 let primary_selection = self.editor.selection.clone().unwrap_or(Selection {
1290 start: self.editor.cursor_position,
1291 end: self.editor.cursor_position,
1292 direction: SelectionDirection::Forward,
1293 });
1294 selections.push(primary_selection);
1295 selections.extend(self.editor.secondary_selections.iter().cloned());
1296
1297 let (selections, primary_index) = crate::selection_set::normalize_selections(selections, 0);
1298
1299 let text_char_len = text.chars().count();
1300
1301 struct Op {
1302 selection_index: usize,
1303 start_offset: usize,
1304 start_after: usize,
1305 delete_len: usize,
1306 deleted_text: String,
1307 insert_text: String,
1308 insert_char_len: usize,
1309 }
1310
1311 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
1312
1313 for (selection_index, selection) in selections.iter().enumerate() {
1314 let (range_start_pos, range_end_pos) = if selection.start <= selection.end {
1315 (selection.start, selection.end)
1316 } else {
1317 (selection.end, selection.start)
1318 };
1319
1320 let (start_offset, start_pad) =
1321 self.position_to_char_offset_and_virtual_pad(range_start_pos);
1322 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
1323
1324 let delete_len = end_offset.saturating_sub(start_offset);
1325 let insert_char_len = start_pad + text_char_len;
1326
1327 let deleted_text = if delete_len == 0 {
1328 String::new()
1329 } else {
1330 self.editor.piece_table.get_range(start_offset, delete_len)
1331 };
1332
1333 let mut insert_text = String::with_capacity(text.len() + start_pad);
1334 for _ in 0..start_pad {
1335 insert_text.push(' ');
1336 }
1337 insert_text.push_str(&text);
1338
1339 ops.push(Op {
1340 selection_index,
1341 start_offset,
1342 start_after: start_offset,
1343 delete_len,
1344 deleted_text,
1345 insert_text,
1346 insert_char_len,
1347 });
1348 }
1349
1350 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
1353 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
1354
1355 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
1356 let mut delta: i64 = 0;
1357 for &idx in &asc_indices {
1358 let op = &mut ops[idx];
1359 let effective_start = (op.start_offset as i64 + delta) as usize;
1360 op.start_after = effective_start;
1361 caret_offsets[op.selection_index] = effective_start + op.insert_char_len;
1362 delta += op.insert_char_len as i64 - op.delete_len as i64;
1363 }
1364
1365 let mut desc_indices = asc_indices;
1367 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
1368
1369 for &idx in &desc_indices {
1370 let op = &ops[idx];
1371
1372 if op.delete_len > 0 {
1373 self.editor
1374 .piece_table
1375 .delete(op.start_offset, op.delete_len);
1376 self.editor
1377 .interval_tree
1378 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
1379 for layer_tree in self.editor.style_layers.values_mut() {
1380 layer_tree
1381 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
1382 }
1383 }
1384
1385 if !op.insert_text.is_empty() {
1386 self.editor
1387 .piece_table
1388 .insert(op.start_offset, &op.insert_text);
1389 self.editor
1390 .interval_tree
1391 .update_for_insertion(op.start_offset, op.insert_char_len);
1392 for layer_tree in self.editor.style_layers.values_mut() {
1393 layer_tree.update_for_insertion(op.start_offset, op.insert_char_len);
1394 }
1395 }
1396 }
1397
1398 let updated_text = self.editor.piece_table.get_text();
1400 self.editor.line_index = LineIndex::from_text(&updated_text);
1401 self.rebuild_layout_engine_from_text(&updated_text);
1402
1403 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
1405 for offset in &caret_offsets {
1406 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
1407 let pos = Position::new(line, column);
1408 new_carets.push(Selection {
1409 start: pos,
1410 end: pos,
1411 direction: SelectionDirection::Forward,
1412 });
1413 }
1414
1415 let (new_carets, new_primary_index) =
1416 crate::selection_set::normalize_selections(new_carets, primary_index);
1417 let primary = new_carets
1418 .get(new_primary_index)
1419 .cloned()
1420 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
1421
1422 self.editor.cursor_position = primary.end;
1423 self.editor.selection = None;
1424 self.editor.secondary_selections = new_carets
1425 .into_iter()
1426 .enumerate()
1427 .filter_map(|(idx, sel)| {
1428 if idx == new_primary_index {
1429 None
1430 } else {
1431 Some(sel)
1432 }
1433 })
1434 .collect();
1435
1436 let after_selection = self.snapshot_selection_set();
1437
1438 let edits: Vec<TextEdit> = ops
1439 .into_iter()
1440 .map(|op| TextEdit {
1441 start_before: op.start_offset,
1442 start_after: op.start_after,
1443 deleted_text: op.deleted_text,
1444 inserted_text: op.insert_text,
1445 })
1446 .collect();
1447
1448 let is_pure_insert = edits.iter().all(|e| e.deleted_text.is_empty());
1449 let coalescible_insert = is_pure_insert && !text.contains('\n');
1450
1451 let step = UndoStep {
1452 group_id: 0,
1453 edits,
1454 before_selection,
1455 after_selection,
1456 };
1457 self.undo_redo.push_step(step, coalescible_insert);
1458
1459 Ok(CommandResult::Success)
1460 }
1461
1462 fn execute_insert_tab_command(&mut self) -> Result<CommandResult, CommandError> {
1463 let before_selection = self.snapshot_selection_set();
1464
1465 let mut selections: Vec<Selection> =
1466 Vec::with_capacity(1 + self.editor.secondary_selections.len());
1467 let primary_selection = self.editor.selection.clone().unwrap_or(Selection {
1468 start: self.editor.cursor_position,
1469 end: self.editor.cursor_position,
1470 direction: SelectionDirection::Forward,
1471 });
1472 selections.push(primary_selection);
1473 selections.extend(self.editor.secondary_selections.iter().cloned());
1474
1475 let (selections, primary_index) = crate::selection_set::normalize_selections(selections, 0);
1476
1477 let tab_width = self.editor.layout_engine.tab_width();
1478
1479 struct Op {
1480 selection_index: usize,
1481 start_offset: usize,
1482 start_after: usize,
1483 delete_len: usize,
1484 deleted_text: String,
1485 insert_text: String,
1486 insert_char_len: usize,
1487 }
1488
1489 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
1490
1491 for (selection_index, selection) in selections.iter().enumerate() {
1492 let (range_start_pos, range_end_pos) = if selection.start <= selection.end {
1493 (selection.start, selection.end)
1494 } else {
1495 (selection.end, selection.start)
1496 };
1497
1498 let (start_offset, start_pad) =
1499 self.position_to_char_offset_and_virtual_pad(range_start_pos);
1500 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
1501
1502 let delete_len = end_offset.saturating_sub(start_offset);
1503
1504 let deleted_text = if delete_len == 0 {
1505 String::new()
1506 } else {
1507 self.editor.piece_table.get_range(start_offset, delete_len)
1508 };
1509
1510 let line_text = self
1512 .editor
1513 .line_index
1514 .get_line_text(range_start_pos.line)
1515 .unwrap_or_default();
1516 let line_char_len = line_text.chars().count();
1517 let clamped_col = range_start_pos.column.min(line_char_len);
1518 let x_in_line =
1519 visual_x_for_column(&line_text, clamped_col, tab_width).saturating_add(start_pad);
1520
1521 let mut insert_text = String::new();
1522 for _ in 0..start_pad {
1523 insert_text.push(' ');
1524 }
1525
1526 match self.tab_key_behavior {
1527 TabKeyBehavior::Tab => {
1528 insert_text.push('\t');
1529 ops.push(Op {
1530 selection_index,
1531 start_offset,
1532 start_after: start_offset,
1533 delete_len,
1534 deleted_text,
1535 insert_text,
1536 insert_char_len: start_pad + 1,
1537 });
1538 }
1539 TabKeyBehavior::Spaces => {
1540 let tab_width = tab_width.max(1);
1541 let rem = x_in_line % tab_width;
1542 let spaces = tab_width - rem;
1543 for _ in 0..spaces {
1544 insert_text.push(' ');
1545 }
1546
1547 ops.push(Op {
1548 selection_index,
1549 start_offset,
1550 start_after: start_offset,
1551 delete_len,
1552 deleted_text,
1553 insert_text,
1554 insert_char_len: start_pad + spaces,
1555 });
1556 }
1557 }
1558 }
1559
1560 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
1563 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
1564
1565 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
1566 let mut delta: i64 = 0;
1567 for &idx in &asc_indices {
1568 let op = &mut ops[idx];
1569 let effective_start = (op.start_offset as i64 + delta) as usize;
1570 op.start_after = effective_start;
1571 caret_offsets[op.selection_index] = effective_start + op.insert_char_len;
1572 delta += op.insert_char_len as i64 - op.delete_len as i64;
1573 }
1574
1575 let mut desc_indices = asc_indices;
1577 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
1578
1579 for &idx in &desc_indices {
1580 let op = &ops[idx];
1581
1582 if op.delete_len > 0 {
1583 self.editor
1584 .piece_table
1585 .delete(op.start_offset, op.delete_len);
1586 self.editor
1587 .interval_tree
1588 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
1589 for layer_tree in self.editor.style_layers.values_mut() {
1590 layer_tree
1591 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
1592 }
1593 }
1594
1595 if !op.insert_text.is_empty() {
1596 self.editor
1597 .piece_table
1598 .insert(op.start_offset, &op.insert_text);
1599 self.editor
1600 .interval_tree
1601 .update_for_insertion(op.start_offset, op.insert_char_len);
1602 for layer_tree in self.editor.style_layers.values_mut() {
1603 layer_tree.update_for_insertion(op.start_offset, op.insert_char_len);
1604 }
1605 }
1606 }
1607
1608 let updated_text = self.editor.piece_table.get_text();
1610 self.editor.line_index = LineIndex::from_text(&updated_text);
1611 self.rebuild_layout_engine_from_text(&updated_text);
1612
1613 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
1615 for offset in &caret_offsets {
1616 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
1617 let pos = Position::new(line, column);
1618 new_carets.push(Selection {
1619 start: pos,
1620 end: pos,
1621 direction: SelectionDirection::Forward,
1622 });
1623 }
1624
1625 let (new_carets, new_primary_index) =
1626 crate::selection_set::normalize_selections(new_carets, primary_index);
1627 let primary = new_carets
1628 .get(new_primary_index)
1629 .cloned()
1630 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
1631
1632 self.editor.cursor_position = primary.end;
1633 self.editor.selection = None;
1634 self.editor.secondary_selections = new_carets
1635 .into_iter()
1636 .enumerate()
1637 .filter_map(|(idx, sel)| {
1638 if idx == new_primary_index {
1639 None
1640 } else {
1641 Some(sel)
1642 }
1643 })
1644 .collect();
1645
1646 let after_selection = self.snapshot_selection_set();
1647
1648 let edits: Vec<TextEdit> = ops
1649 .into_iter()
1650 .map(|op| TextEdit {
1651 start_before: op.start_offset,
1652 start_after: op.start_after,
1653 deleted_text: op.deleted_text,
1654 inserted_text: op.insert_text,
1655 })
1656 .collect();
1657
1658 let is_pure_insert = edits.iter().all(|e| e.deleted_text.is_empty());
1659 let coalescible_insert = is_pure_insert;
1660
1661 let step = UndoStep {
1662 group_id: 0,
1663 edits,
1664 before_selection,
1665 after_selection,
1666 };
1667 self.undo_redo.push_step(step, coalescible_insert);
1668
1669 Ok(CommandResult::Success)
1670 }
1671
1672 fn execute_insert_command(
1673 &mut self,
1674 offset: usize,
1675 text: String,
1676 ) -> Result<CommandResult, CommandError> {
1677 if text.is_empty() {
1678 return Err(CommandError::EmptyText);
1679 }
1680
1681 let max_offset = self.editor.piece_table.char_count();
1682 if offset > max_offset {
1683 return Err(CommandError::InvalidOffset(offset));
1684 }
1685
1686 let before_selection = self.snapshot_selection_set();
1687
1688 let affected_line = self.editor.line_index.char_offset_to_position(offset).0;
1689 let inserts_newline = text.contains('\n');
1690
1691 self.editor.piece_table.insert(offset, &text);
1693
1694 let updated_text = self.editor.piece_table.get_text();
1696 self.editor.line_index = LineIndex::from_text(&updated_text);
1697
1698 if inserts_newline {
1700 self.rebuild_layout_engine_from_text(&updated_text);
1701 } else {
1702 let line_text = self
1703 .editor
1704 .line_index
1705 .get_line_text(affected_line)
1706 .unwrap_or_default();
1707 self.editor
1708 .layout_engine
1709 .update_line(affected_line, &line_text);
1710 }
1711
1712 let inserted_len = text.chars().count();
1713
1714 self.editor
1716 .interval_tree
1717 .update_for_insertion(offset, inserted_len);
1718 for layer_tree in self.editor.style_layers.values_mut() {
1719 layer_tree.update_for_insertion(offset, inserted_len);
1720 }
1721
1722 self.normalize_cursor_and_selection();
1724
1725 let after_selection = self.snapshot_selection_set();
1726
1727 let step = UndoStep {
1728 group_id: 0,
1729 edits: vec![TextEdit {
1730 start_before: offset,
1731 start_after: offset,
1732 deleted_text: String::new(),
1733 inserted_text: text.clone(),
1734 }],
1735 before_selection,
1736 after_selection,
1737 };
1738
1739 let coalescible_insert = !text.contains('\n');
1740 self.undo_redo.push_step(step, coalescible_insert);
1741
1742 Ok(CommandResult::Success)
1743 }
1744
1745 fn execute_delete_command(
1746 &mut self,
1747 start: usize,
1748 length: usize,
1749 ) -> Result<CommandResult, CommandError> {
1750 if length == 0 {
1751 return Ok(CommandResult::Success);
1752 }
1753
1754 let max_offset = self.editor.piece_table.char_count();
1755 if start > max_offset {
1756 return Err(CommandError::InvalidOffset(start));
1757 }
1758 if start + length > max_offset {
1759 return Err(CommandError::InvalidRange {
1760 start,
1761 end: start + length,
1762 });
1763 }
1764
1765 let before_selection = self.snapshot_selection_set();
1766
1767 let deleted_text = self.editor.piece_table.get_range(start, length);
1768 let deletes_newline = deleted_text.contains('\n');
1769 let affected_line = self.editor.line_index.char_offset_to_position(start).0;
1770
1771 self.editor.piece_table.delete(start, length);
1773
1774 let updated_text = self.editor.piece_table.get_text();
1776 self.editor.line_index = LineIndex::from_text(&updated_text);
1777
1778 if deletes_newline {
1780 self.rebuild_layout_engine_from_text(&updated_text);
1781 } else {
1782 let line_text = self
1783 .editor
1784 .line_index
1785 .get_line_text(affected_line)
1786 .unwrap_or_default();
1787 self.editor
1788 .layout_engine
1789 .update_line(affected_line, &line_text);
1790 }
1791
1792 self.editor
1794 .interval_tree
1795 .update_for_deletion(start, start + length);
1796 for layer_tree in self.editor.style_layers.values_mut() {
1797 layer_tree.update_for_deletion(start, start + length);
1798 }
1799
1800 self.normalize_cursor_and_selection();
1802
1803 let after_selection = self.snapshot_selection_set();
1804
1805 let step = UndoStep {
1806 group_id: 0,
1807 edits: vec![TextEdit {
1808 start_before: start,
1809 start_after: start,
1810 deleted_text,
1811 inserted_text: String::new(),
1812 }],
1813 before_selection,
1814 after_selection,
1815 };
1816 self.undo_redo.push_step(step, false);
1817
1818 Ok(CommandResult::Success)
1819 }
1820
1821 fn execute_replace_command(
1822 &mut self,
1823 start: usize,
1824 length: usize,
1825 text: String,
1826 ) -> Result<CommandResult, CommandError> {
1827 let max_offset = self.editor.piece_table.char_count();
1828 if start > max_offset {
1829 return Err(CommandError::InvalidOffset(start));
1830 }
1831 if start + length > max_offset {
1832 return Err(CommandError::InvalidRange {
1833 start,
1834 end: start + length,
1835 });
1836 }
1837
1838 if length == 0 && text.is_empty() {
1839 return Ok(CommandResult::Success);
1840 }
1841
1842 let before_selection = self.snapshot_selection_set();
1843
1844 let deleted_text = if length == 0 {
1845 String::new()
1846 } else {
1847 self.editor.piece_table.get_range(start, length)
1848 };
1849
1850 let affected_line = self.editor.line_index.char_offset_to_position(start).0;
1851 let replace_affects_layout = deleted_text.contains('\n') || text.contains('\n');
1852
1853 if length > 0 {
1855 self.editor.piece_table.delete(start, length);
1856 self.editor
1857 .interval_tree
1858 .update_for_deletion(start, start + length);
1859 for layer_tree in self.editor.style_layers.values_mut() {
1860 layer_tree.update_for_deletion(start, start + length);
1861 }
1862 }
1863
1864 let inserted_len = text.chars().count();
1865 if inserted_len > 0 {
1866 self.editor.piece_table.insert(start, &text);
1867 self.editor
1868 .interval_tree
1869 .update_for_insertion(start, inserted_len);
1870 for layer_tree in self.editor.style_layers.values_mut() {
1871 layer_tree.update_for_insertion(start, inserted_len);
1872 }
1873 }
1874
1875 let updated_text = self.editor.piece_table.get_text();
1877 self.editor.line_index = LineIndex::from_text(&updated_text);
1878
1879 if replace_affects_layout {
1880 self.rebuild_layout_engine_from_text(&updated_text);
1881 } else {
1882 let line_text = self
1883 .editor
1884 .line_index
1885 .get_line_text(affected_line)
1886 .unwrap_or_default();
1887 self.editor
1888 .layout_engine
1889 .update_line(affected_line, &line_text);
1890 }
1891
1892 self.normalize_cursor_and_selection();
1894
1895 let after_selection = self.snapshot_selection_set();
1896
1897 let step = UndoStep {
1898 group_id: 0,
1899 edits: vec![TextEdit {
1900 start_before: start,
1901 start_after: start,
1902 deleted_text,
1903 inserted_text: text,
1904 }],
1905 before_selection,
1906 after_selection,
1907 };
1908 self.undo_redo.push_step(step, false);
1909
1910 Ok(CommandResult::Success)
1911 }
1912
1913 fn cursor_char_offset(&self) -> usize {
1914 self.position_to_char_offset_clamped(self.editor.cursor_position)
1915 }
1916
1917 fn primary_selection_char_range(&self) -> Option<SearchMatch> {
1918 let selection = self.editor.selection.as_ref()?;
1919 let (min_pos, max_pos) = crate::selection_set::selection_min_max(selection);
1920 let start = self.position_to_char_offset_clamped(min_pos);
1921 let end = self.position_to_char_offset_clamped(max_pos);
1922 if start == end {
1923 None
1924 } else {
1925 Some(SearchMatch { start, end })
1926 }
1927 }
1928
1929 fn set_primary_selection_by_char_range(&mut self, range: SearchMatch) {
1930 let (start_line, start_col) = self.editor.line_index.char_offset_to_position(range.start);
1931 let (end_line, end_col) = self.editor.line_index.char_offset_to_position(range.end);
1932
1933 self.editor.cursor_position = Position::new(end_line, end_col);
1934 self.editor.secondary_selections.clear();
1935
1936 if range.start == range.end {
1937 self.editor.selection = None;
1938 } else {
1939 self.editor.selection = Some(Selection {
1940 start: Position::new(start_line, start_col),
1941 end: Position::new(end_line, end_col),
1942 direction: SelectionDirection::Forward,
1943 });
1944 }
1945 }
1946
1947 fn execute_find_command(
1948 &mut self,
1949 query: String,
1950 options: SearchOptions,
1951 forward: bool,
1952 ) -> Result<CommandResult, CommandError> {
1953 if query.is_empty() {
1954 return Ok(CommandResult::SearchNotFound);
1955 }
1956
1957 let text = self.editor.piece_table.get_text();
1958 let from = if let Some(selection) = self.primary_selection_char_range() {
1959 if forward {
1960 selection.end
1961 } else {
1962 selection.start
1963 }
1964 } else {
1965 self.cursor_char_offset()
1966 };
1967
1968 let found = if forward {
1969 find_next(&text, &query, options, from)
1970 } else {
1971 find_prev(&text, &query, options, from)
1972 }
1973 .map_err(|err| CommandError::Other(err.to_string()))?;
1974
1975 let Some(m) = found else {
1976 return Ok(CommandResult::SearchNotFound);
1977 };
1978
1979 self.set_primary_selection_by_char_range(m);
1980
1981 Ok(CommandResult::SearchMatch {
1982 start: m.start,
1983 end: m.end,
1984 })
1985 }
1986
1987 fn compile_user_regex(
1988 query: &str,
1989 options: SearchOptions,
1990 ) -> Result<regex::Regex, CommandError> {
1991 RegexBuilder::new(query)
1992 .case_insensitive(!options.case_sensitive)
1993 .multi_line(true)
1994 .build()
1995 .map_err(|err| CommandError::Other(format!("Invalid regex: {}", err)))
1996 }
1997
1998 fn regex_expand_replacement(
1999 re: ®ex::Regex,
2000 text: &str,
2001 index: &CharIndex,
2002 range: SearchMatch,
2003 replacement: &str,
2004 ) -> Result<String, CommandError> {
2005 let start_byte = index.char_to_byte(range.start);
2006 let end_byte = index.char_to_byte(range.end);
2007
2008 let caps = re
2009 .captures_at(text, start_byte)
2010 .ok_or_else(|| CommandError::Other("Regex match not found".to_string()))?;
2011 let whole = caps
2012 .get(0)
2013 .ok_or_else(|| CommandError::Other("Regex match missing capture 0".to_string()))?;
2014 if whole.start() != start_byte || whole.end() != end_byte {
2015 return Err(CommandError::Other(
2016 "Regex match did not align with the selected range".to_string(),
2017 ));
2018 }
2019
2020 let mut expanded = String::new();
2021 caps.expand(replacement, &mut expanded);
2022 Ok(expanded)
2023 }
2024
2025 fn execute_replace_current_command(
2026 &mut self,
2027 query: String,
2028 replacement: String,
2029 options: SearchOptions,
2030 ) -> Result<CommandResult, CommandError> {
2031 if query.is_empty() {
2032 return Err(CommandError::Other("Search query is empty".to_string()));
2033 }
2034
2035 let text = self.editor.piece_table.get_text();
2036 let selection_range = self.primary_selection_char_range();
2037
2038 let mut target = None::<SearchMatch>;
2039 if let Some(range) = selection_range {
2040 let is_match = crate::search::is_match_exact(&text, &query, options, range)
2041 .map_err(|err| CommandError::Other(err.to_string()))?;
2042 if is_match {
2043 target = Some(range);
2044 }
2045 }
2046
2047 if target.is_none() {
2048 let from = self.cursor_char_offset();
2049 target = find_next(&text, &query, options, from)
2050 .map_err(|err| CommandError::Other(err.to_string()))?;
2051 }
2052
2053 let Some(target) = target else {
2054 return Err(CommandError::Other("No match found".to_string()));
2055 };
2056
2057 let index = CharIndex::new(&text);
2058 let inserted_text = if options.regex {
2059 let re = Self::compile_user_regex(&query, options)?;
2060 Self::regex_expand_replacement(&re, &text, &index, target, &replacement)?
2061 } else {
2062 replacement
2063 };
2064
2065 let deleted_text = self
2066 .editor
2067 .piece_table
2068 .get_range(target.start, target.len());
2069
2070 let before_selection = self.snapshot_selection_set();
2071 self.apply_text_ops(vec![(target.start, target.len(), inserted_text.as_str())])?;
2072
2073 let inserted_len = inserted_text.chars().count();
2074 let new_range = SearchMatch {
2075 start: target.start,
2076 end: target.start + inserted_len,
2077 };
2078 self.set_primary_selection_by_char_range(new_range);
2079 let after_selection = self.snapshot_selection_set();
2080
2081 let step = UndoStep {
2082 group_id: 0,
2083 edits: vec![TextEdit {
2084 start_before: target.start,
2085 start_after: target.start,
2086 deleted_text,
2087 inserted_text: inserted_text.clone(),
2088 }],
2089 before_selection,
2090 after_selection,
2091 };
2092 self.undo_redo.push_step(step, false);
2093
2094 Ok(CommandResult::ReplaceResult { replaced: 1 })
2095 }
2096
2097 fn execute_replace_all_command(
2098 &mut self,
2099 query: String,
2100 replacement: String,
2101 options: SearchOptions,
2102 ) -> Result<CommandResult, CommandError> {
2103 if query.is_empty() {
2104 return Err(CommandError::Other("Search query is empty".to_string()));
2105 }
2106
2107 let text = self.editor.piece_table.get_text();
2108 let matches =
2109 find_all(&text, &query, options).map_err(|err| CommandError::Other(err.to_string()))?;
2110 if matches.is_empty() {
2111 return Err(CommandError::Other("No match found".to_string()));
2112 }
2113 let match_count = matches.len();
2114
2115 let index = CharIndex::new(&text);
2116
2117 struct Op {
2118 start_before: usize,
2119 start_after: usize,
2120 delete_len: usize,
2121 deleted_text: String,
2122 inserted_text: String,
2123 inserted_len: usize,
2124 }
2125
2126 let mut ops: Vec<Op> = Vec::with_capacity(match_count);
2127 if options.regex {
2128 let re = Self::compile_user_regex(&query, options)?;
2129 for m in matches {
2130 let deleted_text = {
2131 let start_byte = index.char_to_byte(m.start);
2132 let end_byte = index.char_to_byte(m.end);
2133 text.get(start_byte..end_byte)
2134 .unwrap_or_default()
2135 .to_string()
2136 };
2137 let inserted_text =
2138 Self::regex_expand_replacement(&re, &text, &index, m, &replacement)?;
2139 let inserted_len = inserted_text.chars().count();
2140 ops.push(Op {
2141 start_before: m.start,
2142 start_after: m.start,
2143 delete_len: m.len(),
2144 deleted_text,
2145 inserted_text,
2146 inserted_len,
2147 });
2148 }
2149 } else {
2150 let inserted_len = replacement.chars().count();
2151 for m in matches {
2152 let deleted_text = {
2153 let start_byte = index.char_to_byte(m.start);
2154 let end_byte = index.char_to_byte(m.end);
2155 text.get(start_byte..end_byte)
2156 .unwrap_or_default()
2157 .to_string()
2158 };
2159 ops.push(Op {
2160 start_before: m.start,
2161 start_after: m.start,
2162 delete_len: m.len(),
2163 deleted_text,
2164 inserted_text: replacement.clone(),
2165 inserted_len,
2166 });
2167 }
2168 }
2169
2170 ops.sort_by_key(|op| op.start_before);
2171
2172 let mut delta: i64 = 0;
2173 for op in &mut ops {
2174 let effective_start = op.start_before as i64 + delta;
2175 if effective_start < 0 {
2176 return Err(CommandError::Other(
2177 "ReplaceAll produced an invalid intermediate offset".to_string(),
2178 ));
2179 }
2180 op.start_after = effective_start as usize;
2181 delta += op.inserted_len as i64 - op.delete_len as i64;
2182 }
2183
2184 let before_selection = self.snapshot_selection_set();
2185 let apply_ops: Vec<(usize, usize, &str)> = ops
2186 .iter()
2187 .map(|op| (op.start_before, op.delete_len, op.inserted_text.as_str()))
2188 .collect();
2189 self.apply_text_ops(apply_ops)?;
2190
2191 if let Some(first) = ops.first() {
2192 let caret_end = first.start_after + first.inserted_len;
2193 let select_end = if first.inserted_len == 0 {
2194 first.start_after
2195 } else {
2196 caret_end
2197 };
2198 self.set_primary_selection_by_char_range(SearchMatch {
2199 start: first.start_after,
2200 end: select_end,
2201 });
2202 } else {
2203 self.editor.selection = None;
2204 self.editor.secondary_selections.clear();
2205 }
2206
2207 let after_selection = self.snapshot_selection_set();
2208
2209 let edits: Vec<TextEdit> = ops
2210 .into_iter()
2211 .map(|op| TextEdit {
2212 start_before: op.start_before,
2213 start_after: op.start_after,
2214 deleted_text: op.deleted_text,
2215 inserted_text: op.inserted_text,
2216 })
2217 .collect();
2218
2219 let step = UndoStep {
2220 group_id: 0,
2221 edits,
2222 before_selection,
2223 after_selection,
2224 };
2225 self.undo_redo.push_step(step, false);
2226
2227 Ok(CommandResult::ReplaceResult {
2228 replaced: match_count,
2229 })
2230 }
2231
2232 fn execute_backspace_command(&mut self) -> Result<CommandResult, CommandError> {
2233 self.execute_delete_like_command(false)
2234 }
2235
2236 fn execute_delete_forward_command(&mut self) -> Result<CommandResult, CommandError> {
2237 self.execute_delete_like_command(true)
2238 }
2239
2240 fn execute_delete_like_command(
2241 &mut self,
2242 forward: bool,
2243 ) -> Result<CommandResult, CommandError> {
2244 self.undo_redo.end_group();
2247
2248 let before_selection = self.snapshot_selection_set();
2249 let selections = before_selection.selections.clone();
2250 let primary_index = before_selection.primary_index;
2251
2252 let doc_char_count = self.editor.piece_table.char_count();
2253
2254 #[derive(Debug)]
2255 struct Op {
2256 selection_index: usize,
2257 start_offset: usize,
2258 delete_len: usize,
2259 deleted_text: String,
2260 start_after: usize,
2261 }
2262
2263 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
2264
2265 for (selection_index, selection) in selections.iter().enumerate() {
2266 let (range_start_pos, range_end_pos) = if selection.start <= selection.end {
2267 (selection.start, selection.end)
2268 } else {
2269 (selection.end, selection.start)
2270 };
2271
2272 let (start_offset, end_offset) = if range_start_pos != range_end_pos {
2273 let start_offset = self.position_to_char_offset_clamped(range_start_pos);
2274 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
2275 if start_offset <= end_offset {
2276 (start_offset, end_offset)
2277 } else {
2278 (end_offset, start_offset)
2279 }
2280 } else {
2281 let caret_offset = self.position_to_char_offset_clamped(selection.end);
2282 if forward {
2283 if caret_offset >= doc_char_count {
2284 (caret_offset, caret_offset)
2285 } else {
2286 (caret_offset, (caret_offset + 1).min(doc_char_count))
2287 }
2288 } else if caret_offset == 0 {
2289 (0, 0)
2290 } else {
2291 (caret_offset - 1, caret_offset)
2292 }
2293 };
2294
2295 let delete_len = end_offset.saturating_sub(start_offset);
2296 let deleted_text = if delete_len == 0 {
2297 String::new()
2298 } else {
2299 self.editor.piece_table.get_range(start_offset, delete_len)
2300 };
2301
2302 ops.push(Op {
2303 selection_index,
2304 start_offset,
2305 delete_len,
2306 deleted_text,
2307 start_after: start_offset,
2308 });
2309 }
2310
2311 if !ops.iter().any(|op| op.delete_len > 0) {
2312 return Ok(CommandResult::Success);
2313 }
2314
2315 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
2317 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
2318
2319 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
2320 let mut delta: i64 = 0;
2321 for &idx in &asc_indices {
2322 let op = &mut ops[idx];
2323 let effective_start = (op.start_offset as i64 + delta) as usize;
2324 op.start_after = effective_start;
2325 caret_offsets[op.selection_index] = effective_start;
2326 delta -= op.delete_len as i64;
2327 }
2328
2329 let mut desc_indices = asc_indices;
2331 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
2332
2333 for &idx in &desc_indices {
2334 let op = &ops[idx];
2335 if op.delete_len == 0 {
2336 continue;
2337 }
2338
2339 self.editor
2340 .piece_table
2341 .delete(op.start_offset, op.delete_len);
2342 self.editor
2343 .interval_tree
2344 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
2345 for layer_tree in self.editor.style_layers.values_mut() {
2346 layer_tree.update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
2347 }
2348 }
2349
2350 let updated_text = self.editor.piece_table.get_text();
2352 self.editor.line_index = LineIndex::from_text(&updated_text);
2353 self.rebuild_layout_engine_from_text(&updated_text);
2354
2355 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
2357 for offset in &caret_offsets {
2358 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
2359 let pos = Position::new(line, column);
2360 new_carets.push(Selection {
2361 start: pos,
2362 end: pos,
2363 direction: SelectionDirection::Forward,
2364 });
2365 }
2366
2367 let (new_carets, new_primary_index) =
2368 crate::selection_set::normalize_selections(new_carets, primary_index);
2369 let primary = new_carets
2370 .get(new_primary_index)
2371 .cloned()
2372 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
2373
2374 self.editor.cursor_position = primary.end;
2375 self.editor.selection = None;
2376 self.editor.secondary_selections = new_carets
2377 .into_iter()
2378 .enumerate()
2379 .filter_map(|(idx, sel)| {
2380 if idx == new_primary_index {
2381 None
2382 } else {
2383 Some(sel)
2384 }
2385 })
2386 .collect();
2387
2388 let after_selection = self.snapshot_selection_set();
2389
2390 let edits: Vec<TextEdit> = ops
2391 .into_iter()
2392 .map(|op| TextEdit {
2393 start_before: op.start_offset,
2394 start_after: op.start_after,
2395 deleted_text: op.deleted_text,
2396 inserted_text: String::new(),
2397 })
2398 .collect();
2399
2400 let step = UndoStep {
2401 group_id: 0,
2402 edits,
2403 before_selection,
2404 after_selection,
2405 };
2406 self.undo_redo.push_step(step, false);
2407
2408 Ok(CommandResult::Success)
2409 }
2410
2411 fn snapshot_selection_set(&self) -> SelectionSetSnapshot {
2412 let mut selections: Vec<Selection> =
2413 Vec::with_capacity(1 + self.editor.secondary_selections.len());
2414
2415 let primary = self.editor.selection.clone().unwrap_or(Selection {
2416 start: self.editor.cursor_position,
2417 end: self.editor.cursor_position,
2418 direction: SelectionDirection::Forward,
2419 });
2420 selections.push(primary);
2421 selections.extend(self.editor.secondary_selections.iter().cloned());
2422
2423 let (selections, primary_index) = crate::selection_set::normalize_selections(selections, 0);
2424 SelectionSetSnapshot {
2425 selections,
2426 primary_index,
2427 }
2428 }
2429
2430 fn restore_selection_set(&mut self, snapshot: SelectionSetSnapshot) {
2431 if snapshot.selections.is_empty() {
2432 self.editor.cursor_position = Position::new(0, 0);
2433 self.editor.selection = None;
2434 self.editor.secondary_selections.clear();
2435 return;
2436 }
2437
2438 let primary = snapshot
2439 .selections
2440 .get(snapshot.primary_index)
2441 .cloned()
2442 .unwrap_or_else(|| snapshot.selections[0].clone());
2443
2444 self.editor.cursor_position = primary.end;
2445 self.editor.selection = if primary.start == primary.end {
2446 None
2447 } else {
2448 Some(primary.clone())
2449 };
2450
2451 self.editor.secondary_selections = snapshot
2452 .selections
2453 .into_iter()
2454 .enumerate()
2455 .filter_map(|(idx, sel)| {
2456 if idx == snapshot.primary_index {
2457 None
2458 } else {
2459 Some(sel)
2460 }
2461 })
2462 .collect();
2463
2464 self.normalize_cursor_and_selection();
2465 }
2466
2467 fn apply_undo_edits(&mut self, edits: &[TextEdit]) -> Result<(), CommandError> {
2468 let mut ops: Vec<(usize, usize, &str)> = Vec::with_capacity(edits.len());
2470 for edit in edits {
2471 let start = edit.start_after;
2472 let delete_len = edit.inserted_len();
2473 let insert_text = edit.deleted_text.as_str();
2474 ops.push((start, delete_len, insert_text));
2475 }
2476 self.apply_text_ops(ops)
2477 }
2478
2479 fn apply_redo_edits(&mut self, edits: &[TextEdit]) -> Result<(), CommandError> {
2480 let mut ops: Vec<(usize, usize, &str)> = Vec::with_capacity(edits.len());
2481 for edit in edits {
2482 let start = edit.start_before;
2483 let delete_len = edit.deleted_len();
2484 let insert_text = edit.inserted_text.as_str();
2485 ops.push((start, delete_len, insert_text));
2486 }
2487 self.apply_text_ops(ops)
2488 }
2489
2490 fn apply_text_ops(&mut self, mut ops: Vec<(usize, usize, &str)>) -> Result<(), CommandError> {
2491 ops.sort_by_key(|(start, _, _)| std::cmp::Reverse(*start));
2493
2494 for (start, delete_len, insert_text) in ops {
2495 let max_offset = self.editor.piece_table.char_count();
2496 if start > max_offset {
2497 return Err(CommandError::InvalidOffset(start));
2498 }
2499 if start + delete_len > max_offset {
2500 return Err(CommandError::InvalidRange {
2501 start,
2502 end: start + delete_len,
2503 });
2504 }
2505
2506 if delete_len > 0 {
2507 self.editor.piece_table.delete(start, delete_len);
2508 self.editor
2509 .interval_tree
2510 .update_for_deletion(start, start + delete_len);
2511 for layer_tree in self.editor.style_layers.values_mut() {
2512 layer_tree.update_for_deletion(start, start + delete_len);
2513 }
2514 }
2515
2516 let insert_len = insert_text.chars().count();
2517 if insert_len > 0 {
2518 self.editor.piece_table.insert(start, insert_text);
2519 self.editor
2520 .interval_tree
2521 .update_for_insertion(start, insert_len);
2522 for layer_tree in self.editor.style_layers.values_mut() {
2523 layer_tree.update_for_insertion(start, insert_len);
2524 }
2525 }
2526 }
2527
2528 let updated_text = self.editor.piece_table.get_text();
2530 self.editor.line_index = LineIndex::from_text(&updated_text);
2531 self.rebuild_layout_engine_from_text(&updated_text);
2532 self.normalize_cursor_and_selection();
2533
2534 Ok(())
2535 }
2536
2537 fn execute_cursor(&mut self, command: CursorCommand) -> Result<CommandResult, CommandError> {
2539 match command {
2540 CursorCommand::MoveTo { line, column } => {
2541 if line >= self.editor.line_index.line_count() {
2542 return Err(CommandError::InvalidPosition { line, column });
2543 }
2544
2545 let clamped_column = self.clamp_column_for_line(line, column);
2546 self.editor.cursor_position = Position::new(line, clamped_column);
2547 self.editor.secondary_selections.clear();
2549 Ok(CommandResult::Success)
2550 }
2551 CursorCommand::MoveBy {
2552 delta_line,
2553 delta_column,
2554 } => {
2555 let new_line = if delta_line >= 0 {
2556 self.editor.cursor_position.line + delta_line as usize
2557 } else {
2558 self.editor
2559 .cursor_position
2560 .line
2561 .saturating_sub((-delta_line) as usize)
2562 };
2563
2564 let new_column = if delta_column >= 0 {
2565 self.editor.cursor_position.column + delta_column as usize
2566 } else {
2567 self.editor
2568 .cursor_position
2569 .column
2570 .saturating_sub((-delta_column) as usize)
2571 };
2572
2573 if new_line >= self.editor.line_index.line_count() {
2574 return Err(CommandError::InvalidPosition {
2575 line: new_line,
2576 column: new_column,
2577 });
2578 }
2579
2580 let clamped_column = self.clamp_column_for_line(new_line, new_column);
2581 self.editor.cursor_position = Position::new(new_line, clamped_column);
2582 Ok(CommandResult::Success)
2583 }
2584 CursorCommand::SetSelection { start, end } => {
2585 if start.line >= self.editor.line_index.line_count()
2586 || end.line >= self.editor.line_index.line_count()
2587 {
2588 return Err(CommandError::InvalidPosition {
2589 line: start.line.max(end.line),
2590 column: start.column.max(end.column),
2591 });
2592 }
2593
2594 let start = Position::new(
2595 start.line,
2596 self.clamp_column_for_line(start.line, start.column),
2597 );
2598 let end = Position::new(end.line, self.clamp_column_for_line(end.line, end.column));
2599
2600 let direction = if start.line < end.line
2601 || (start.line == end.line && start.column <= end.column)
2602 {
2603 SelectionDirection::Forward
2604 } else {
2605 SelectionDirection::Backward
2606 };
2607
2608 self.editor.selection = Some(Selection {
2609 start,
2610 end,
2611 direction,
2612 });
2613 Ok(CommandResult::Success)
2614 }
2615 CursorCommand::ExtendSelection { to } => {
2616 if to.line >= self.editor.line_index.line_count() {
2617 return Err(CommandError::InvalidPosition {
2618 line: to.line,
2619 column: to.column,
2620 });
2621 }
2622
2623 let to = Position::new(to.line, self.clamp_column_for_line(to.line, to.column));
2624
2625 if let Some(ref mut selection) = self.editor.selection {
2626 selection.end = to;
2627 selection.direction = if selection.start.line < to.line
2628 || (selection.start.line == to.line && selection.start.column <= to.column)
2629 {
2630 SelectionDirection::Forward
2631 } else {
2632 SelectionDirection::Backward
2633 };
2634 } else {
2635 self.editor.selection = Some(Selection {
2637 start: self.editor.cursor_position,
2638 end: to,
2639 direction: if self.editor.cursor_position.line < to.line
2640 || (self.editor.cursor_position.line == to.line
2641 && self.editor.cursor_position.column <= to.column)
2642 {
2643 SelectionDirection::Forward
2644 } else {
2645 SelectionDirection::Backward
2646 },
2647 });
2648 }
2649 Ok(CommandResult::Success)
2650 }
2651 CursorCommand::ClearSelection => {
2652 self.editor.selection = None;
2653 Ok(CommandResult::Success)
2654 }
2655 CursorCommand::SetSelections {
2656 selections,
2657 primary_index,
2658 } => {
2659 let line_count = self.editor.line_index.line_count();
2660 if selections.is_empty() {
2661 return Err(CommandError::Other(
2662 "SetSelections requires a non-empty selection list".to_string(),
2663 ));
2664 }
2665 if primary_index >= selections.len() {
2666 return Err(CommandError::Other(format!(
2667 "Invalid primary_index {} for {} selections",
2668 primary_index,
2669 selections.len()
2670 )));
2671 }
2672
2673 for sel in &selections {
2674 if sel.start.line >= line_count || sel.end.line >= line_count {
2675 return Err(CommandError::InvalidPosition {
2676 line: sel.start.line.max(sel.end.line),
2677 column: sel.start.column.max(sel.end.column),
2678 });
2679 }
2680 }
2681
2682 let (selections, primary_index) =
2683 crate::selection_set::normalize_selections(selections, primary_index);
2684
2685 let primary = selections
2686 .get(primary_index)
2687 .cloned()
2688 .ok_or_else(|| CommandError::Other("Invalid primary selection".to_string()))?;
2689
2690 self.editor.cursor_position = primary.end;
2691 self.editor.selection = if primary.start == primary.end {
2692 None
2693 } else {
2694 Some(primary.clone())
2695 };
2696
2697 self.editor.secondary_selections = selections
2698 .into_iter()
2699 .enumerate()
2700 .filter_map(|(idx, sel)| {
2701 if idx == primary_index {
2702 None
2703 } else {
2704 Some(sel)
2705 }
2706 })
2707 .collect();
2708
2709 Ok(CommandResult::Success)
2710 }
2711 CursorCommand::ClearSecondarySelections => {
2712 self.editor.secondary_selections.clear();
2713 Ok(CommandResult::Success)
2714 }
2715 CursorCommand::SetRectSelection { anchor, active } => {
2716 let line_count = self.editor.line_index.line_count();
2717 if anchor.line >= line_count || active.line >= line_count {
2718 return Err(CommandError::InvalidPosition {
2719 line: anchor.line.max(active.line),
2720 column: anchor.column.max(active.column),
2721 });
2722 }
2723
2724 let (selections, primary_index) =
2725 crate::selection_set::rect_selections(anchor, active);
2726
2727 self.execute_cursor(CursorCommand::SetSelections {
2729 selections,
2730 primary_index,
2731 })?;
2732 Ok(CommandResult::Success)
2733 }
2734 CursorCommand::FindNext { query, options } => {
2735 self.execute_find_command(query, options, true)
2736 }
2737 CursorCommand::FindPrev { query, options } => {
2738 self.execute_find_command(query, options, false)
2739 }
2740 }
2741 }
2742
2743 fn execute_view(&mut self, command: ViewCommand) -> Result<CommandResult, CommandError> {
2745 match command {
2746 ViewCommand::SetViewportWidth { width } => {
2747 if width == 0 {
2748 return Err(CommandError::Other(
2749 "Viewport width must be greater than 0".to_string(),
2750 ));
2751 }
2752
2753 self.editor.viewport_width = width;
2754 self.editor.layout_engine.set_viewport_width(width);
2755 Ok(CommandResult::Success)
2756 }
2757 ViewCommand::SetTabWidth { width } => {
2758 if width == 0 {
2759 return Err(CommandError::Other(
2760 "Tab width must be greater than 0".to_string(),
2761 ));
2762 }
2763
2764 self.editor.layout_engine.set_tab_width(width);
2765 Ok(CommandResult::Success)
2766 }
2767 ViewCommand::SetTabKeyBehavior { behavior } => {
2768 self.tab_key_behavior = behavior;
2769 Ok(CommandResult::Success)
2770 }
2771 ViewCommand::ScrollTo { line } => {
2772 if line >= self.editor.line_index.line_count() {
2773 return Err(CommandError::InvalidPosition { line, column: 0 });
2774 }
2775
2776 Ok(CommandResult::Success)
2779 }
2780 ViewCommand::GetViewport { start_row, count } => {
2781 let text = self.editor.piece_table.get_text();
2782 let generator = SnapshotGenerator::from_text_with_tab_width(
2783 &text,
2784 self.editor.viewport_width,
2785 self.editor.layout_engine.tab_width(),
2786 );
2787 let grid = generator.get_headless_grid(start_row, count);
2788 Ok(CommandResult::Viewport(grid))
2789 }
2790 }
2791 }
2792
2793 fn execute_style(&mut self, command: StyleCommand) -> Result<CommandResult, CommandError> {
2795 match command {
2796 StyleCommand::AddStyle {
2797 start,
2798 end,
2799 style_id,
2800 } => {
2801 if start >= end {
2802 return Err(CommandError::InvalidRange { start, end });
2803 }
2804
2805 let interval = crate::intervals::Interval::new(start, end, style_id);
2806 self.editor.interval_tree.insert(interval);
2807 Ok(CommandResult::Success)
2808 }
2809 StyleCommand::RemoveStyle {
2810 start,
2811 end,
2812 style_id,
2813 } => {
2814 self.editor.interval_tree.remove(start, end, style_id);
2815 Ok(CommandResult::Success)
2816 }
2817 StyleCommand::Fold {
2818 start_line,
2819 end_line,
2820 } => {
2821 if start_line >= end_line {
2822 return Err(CommandError::InvalidRange {
2823 start: start_line,
2824 end: end_line,
2825 });
2826 }
2827
2828 let mut region = crate::intervals::FoldRegion::new(start_line, end_line);
2829 region.collapse();
2830 self.editor.folding_manager.add_region(region);
2831 Ok(CommandResult::Success)
2832 }
2833 StyleCommand::Unfold { start_line } => {
2834 self.editor.folding_manager.expand_line(start_line);
2835 Ok(CommandResult::Success)
2836 }
2837 StyleCommand::UnfoldAll => {
2838 self.editor.folding_manager.expand_all();
2839 Ok(CommandResult::Success)
2840 }
2841 }
2842 }
2843
2844 fn rebuild_layout_engine_from_text(&mut self, text: &str) {
2845 let lines = crate::text::split_lines_preserve_trailing(text);
2846 let line_refs: Vec<&str> = lines.iter().map(|s| s.as_str()).collect();
2847 self.editor.layout_engine.from_lines(&line_refs);
2848 }
2849
2850 fn position_to_char_offset_clamped(&self, pos: Position) -> usize {
2851 let line_count = self.editor.line_index.line_count();
2852 if line_count == 0 {
2853 return 0;
2854 }
2855
2856 let line = pos.line.min(line_count.saturating_sub(1));
2857 let line_text = self
2858 .editor
2859 .line_index
2860 .get_line_text(line)
2861 .unwrap_or_default();
2862 let line_char_len = line_text.chars().count();
2863 let column = pos.column.min(line_char_len);
2864 self.editor.line_index.position_to_char_offset(line, column)
2865 }
2866
2867 fn position_to_char_offset_and_virtual_pad(&self, pos: Position) -> (usize, usize) {
2868 let line_count = self.editor.line_index.line_count();
2869 if line_count == 0 {
2870 return (0, 0);
2871 }
2872
2873 let line = pos.line.min(line_count.saturating_sub(1));
2874 let line_text = self
2875 .editor
2876 .line_index
2877 .get_line_text(line)
2878 .unwrap_or_default();
2879 let line_char_len = line_text.chars().count();
2880 let clamped_col = pos.column.min(line_char_len);
2881 let offset = self
2882 .editor
2883 .line_index
2884 .position_to_char_offset(line, clamped_col);
2885 let pad = pos.column.saturating_sub(clamped_col);
2886 (offset, pad)
2887 }
2888
2889 fn normalize_cursor_and_selection(&mut self) {
2890 let line_index = &self.editor.line_index;
2891 let line_count = line_index.line_count();
2892 if line_count == 0 {
2893 self.editor.cursor_position = Position::new(0, 0);
2894 self.editor.selection = None;
2895 self.editor.secondary_selections.clear();
2896 return;
2897 }
2898
2899 self.editor.cursor_position =
2900 Self::clamp_position_lenient_with_index(line_index, self.editor.cursor_position);
2901
2902 if let Some(ref mut selection) = self.editor.selection {
2903 selection.start = Self::clamp_position_lenient_with_index(line_index, selection.start);
2904 selection.end = Self::clamp_position_lenient_with_index(line_index, selection.end);
2905 selection.direction = if selection.start.line < selection.end.line
2906 || (selection.start.line == selection.end.line
2907 && selection.start.column <= selection.end.column)
2908 {
2909 SelectionDirection::Forward
2910 } else {
2911 SelectionDirection::Backward
2912 };
2913 }
2914
2915 for selection in &mut self.editor.secondary_selections {
2916 selection.start = Self::clamp_position_lenient_with_index(line_index, selection.start);
2917 selection.end = Self::clamp_position_lenient_with_index(line_index, selection.end);
2918 selection.direction = if selection.start.line < selection.end.line
2919 || (selection.start.line == selection.end.line
2920 && selection.start.column <= selection.end.column)
2921 {
2922 SelectionDirection::Forward
2923 } else {
2924 SelectionDirection::Backward
2925 };
2926 }
2927 }
2928
2929 fn clamp_column_for_line(&self, line: usize, column: usize) -> usize {
2930 Self::clamp_column_for_line_with_index(&self.editor.line_index, line, column)
2931 }
2932
2933 fn clamp_position_lenient_with_index(line_index: &LineIndex, pos: Position) -> Position {
2934 let line_count = line_index.line_count();
2935 if line_count == 0 {
2936 return Position::new(0, 0);
2937 }
2938
2939 let clamped_line = pos.line.min(line_count.saturating_sub(1));
2940 Position::new(clamped_line, pos.column)
2942 }
2943
2944 fn clamp_column_for_line_with_index(
2945 line_index: &LineIndex,
2946 line: usize,
2947 column: usize,
2948 ) -> usize {
2949 let line_start = line_index.position_to_char_offset(line, 0);
2950 let line_end = line_index.position_to_char_offset(line, usize::MAX);
2951 let line_len = line_end.saturating_sub(line_start);
2952 column.min(line_len)
2953 }
2954}
2955
2956#[cfg(test)]
2957mod tests {
2958 use super::*;
2959
2960 #[test]
2961 fn test_edit_insert() {
2962 let mut executor = CommandExecutor::new("Hello", 80);
2963
2964 let result = executor.execute(Command::Edit(EditCommand::Insert {
2965 offset: 5,
2966 text: " World".to_string(),
2967 }));
2968
2969 assert!(result.is_ok());
2970 assert_eq!(executor.editor().get_text(), "Hello World");
2971 }
2972
2973 #[test]
2974 fn test_edit_delete() {
2975 let mut executor = CommandExecutor::new("Hello World", 80);
2976
2977 let result = executor.execute(Command::Edit(EditCommand::Delete {
2978 start: 5,
2979 length: 6,
2980 }));
2981
2982 assert!(result.is_ok());
2983 assert_eq!(executor.editor().get_text(), "Hello");
2984 }
2985
2986 #[test]
2987 fn test_edit_replace() {
2988 let mut executor = CommandExecutor::new("Hello World", 80);
2989
2990 let result = executor.execute(Command::Edit(EditCommand::Replace {
2991 start: 6,
2992 length: 5,
2993 text: "Rust".to_string(),
2994 }));
2995
2996 assert!(result.is_ok());
2997 assert_eq!(executor.editor().get_text(), "Hello Rust");
2998 }
2999
3000 #[test]
3001 fn test_cursor_move_to() {
3002 let mut executor = CommandExecutor::new("Line 1\nLine 2\nLine 3", 80);
3003
3004 let result = executor.execute(Command::Cursor(CursorCommand::MoveTo {
3005 line: 1,
3006 column: 3,
3007 }));
3008
3009 assert!(result.is_ok());
3010 assert_eq!(executor.editor().cursor_position(), Position::new(1, 3));
3011 }
3012
3013 #[test]
3014 fn test_cursor_selection() {
3015 let mut executor = CommandExecutor::new("Hello World", 80);
3016
3017 let result = executor.execute(Command::Cursor(CursorCommand::SetSelection {
3018 start: Position::new(0, 0),
3019 end: Position::new(0, 5),
3020 }));
3021
3022 assert!(result.is_ok());
3023 assert!(executor.editor().selection().is_some());
3024 }
3025
3026 #[test]
3027 fn test_view_set_width() {
3028 let mut executor = CommandExecutor::new("Test", 80);
3029
3030 let result = executor.execute(Command::View(ViewCommand::SetViewportWidth { width: 40 }));
3031
3032 assert!(result.is_ok());
3033 assert_eq!(executor.editor().viewport_width, 40);
3034 }
3035
3036 #[test]
3037 fn test_style_add_remove() {
3038 let mut executor = CommandExecutor::new("Hello World", 80);
3039
3040 let result = executor.execute(Command::Style(StyleCommand::AddStyle {
3042 start: 0,
3043 end: 5,
3044 style_id: 1,
3045 }));
3046 assert!(result.is_ok());
3047
3048 let result = executor.execute(Command::Style(StyleCommand::RemoveStyle {
3050 start: 0,
3051 end: 5,
3052 style_id: 1,
3053 }));
3054 assert!(result.is_ok());
3055 }
3056
3057 #[test]
3058 fn test_batch_execution() {
3059 let mut executor = CommandExecutor::new("", 80);
3060
3061 let commands = vec![
3062 Command::Edit(EditCommand::Insert {
3063 offset: 0,
3064 text: "Hello".to_string(),
3065 }),
3066 Command::Edit(EditCommand::Insert {
3067 offset: 5,
3068 text: " World".to_string(),
3069 }),
3070 ];
3071
3072 let results = executor.execute_batch(commands);
3073 assert!(results.is_ok());
3074 assert_eq!(executor.editor().get_text(), "Hello World");
3075 }
3076
3077 #[test]
3078 fn test_error_invalid_offset() {
3079 let mut executor = CommandExecutor::new("Hello", 80);
3080
3081 let result = executor.execute(Command::Edit(EditCommand::Insert {
3082 offset: 100,
3083 text: "X".to_string(),
3084 }));
3085
3086 assert!(result.is_err());
3087 assert!(matches!(
3088 result.unwrap_err(),
3089 CommandError::InvalidOffset(_)
3090 ));
3091 }
3092}