1use crate::decorations::{Decoration, DecorationLayerId};
37use crate::delta::{TextDelta, TextDeltaEdit};
38use crate::diagnostics::Diagnostic;
39use crate::intervals::{FoldRegion, StyleId, StyleLayerId};
40use crate::layout::{
41 WrapIndent, WrapMode, cell_width_at, char_width, visual_x_for_column,
42 wrap_indent_cells_for_line_text,
43};
44use crate::line_ending::LineEnding;
45use crate::search::{CharIndex, SearchMatch, SearchOptions, find_all, find_next, find_prev};
46use crate::snapshot::{Cell, HeadlessGrid, HeadlessLine};
47use crate::{
48 FOLD_PLACEHOLDER_STYLE_ID, FoldingManager, IntervalTree, LayoutEngine, LineIndex, PieceTable,
49 SnapshotGenerator,
50};
51use regex::RegexBuilder;
52use std::cmp::Ordering;
53use std::collections::BTreeMap;
54use unicode_segmentation::UnicodeSegmentation;
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub struct Position {
59 pub line: usize,
61 pub column: usize,
63}
64
65impl Position {
66 pub fn new(line: usize, column: usize) -> Self {
68 Self { line, column }
69 }
70}
71
72impl Ord for Position {
73 fn cmp(&self, other: &Self) -> Ordering {
74 self.line
75 .cmp(&other.line)
76 .then_with(|| self.column.cmp(&other.column))
77 }
78}
79
80impl PartialOrd for Position {
81 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
82 Some(self.cmp(other))
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct Selection {
89 pub start: Position,
91 pub end: Position,
93 pub direction: SelectionDirection,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum SelectionDirection {
100 Forward,
102 Backward,
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub enum TabKeyBehavior {
109 Tab,
111 Spaces,
113}
114
115#[derive(Debug, Clone, PartialEq, Eq)]
117pub enum EditCommand {
118 Insert {
120 offset: usize,
122 text: String,
124 },
125 Delete {
127 start: usize,
129 length: usize,
131 },
132 Replace {
134 start: usize,
136 length: usize,
138 text: String,
140 },
141 InsertText {
143 text: String,
145 },
146 InsertTab,
151 InsertNewline {
156 auto_indent: bool,
158 },
159 Indent,
161 Outdent,
163 DeleteToPrevTabStop,
167 DeleteGraphemeBack,
169 DeleteGraphemeForward,
171 DeleteWordBack,
173 DeleteWordForward,
175 Backspace,
177 DeleteForward,
179 Undo,
181 Redo,
183 EndUndoGroup,
185 ReplaceCurrent {
190 query: String,
192 replacement: String,
194 options: SearchOptions,
196 },
197 ReplaceAll {
202 query: String,
204 replacement: String,
206 options: SearchOptions,
208 },
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
213pub enum CursorCommand {
214 MoveTo {
216 line: usize,
218 column: usize,
220 },
221 MoveBy {
223 delta_line: isize,
225 delta_column: isize,
227 },
228 MoveVisualBy {
233 delta_rows: isize,
235 },
236 MoveToVisual {
238 row: usize,
240 x_cells: usize,
242 },
243 MoveToLineStart,
245 MoveToLineEnd,
247 MoveToVisualLineStart,
249 MoveToVisualLineEnd,
251 MoveGraphemeLeft,
253 MoveGraphemeRight,
255 MoveWordLeft,
257 MoveWordRight,
259 SetSelection {
261 start: Position,
263 end: Position,
265 },
266 ExtendSelection {
268 to: Position,
270 },
271 ClearSelection,
273 SetSelections {
275 selections: Vec<Selection>,
277 primary_index: usize,
279 },
280 ClearSecondarySelections,
282 SetRectSelection {
284 anchor: Position,
286 active: Position,
288 },
289 FindNext {
291 query: String,
293 options: SearchOptions,
295 },
296 FindPrev {
298 query: String,
300 options: SearchOptions,
302 },
303}
304
305#[derive(Debug, Clone, PartialEq, Eq)]
307pub enum ViewCommand {
308 SetViewportWidth {
310 width: usize,
312 },
313 SetWrapMode {
315 mode: WrapMode,
317 },
318 SetWrapIndent {
320 indent: WrapIndent,
322 },
323 SetTabWidth {
325 width: usize,
327 },
328 SetTabKeyBehavior {
330 behavior: TabKeyBehavior,
332 },
333 ScrollTo {
335 line: usize,
337 },
338 GetViewport {
340 start_row: usize,
342 count: usize,
344 },
345}
346
347#[derive(Debug, Clone, PartialEq, Eq)]
349pub enum StyleCommand {
350 AddStyle {
352 start: usize,
354 end: usize,
356 style_id: StyleId,
358 },
359 RemoveStyle {
361 start: usize,
363 end: usize,
365 style_id: StyleId,
367 },
368 Fold {
370 start_line: usize,
372 end_line: usize,
374 },
375 Unfold {
377 start_line: usize,
379 },
380 UnfoldAll,
382}
383
384#[derive(Debug, Clone, PartialEq, Eq)]
386pub enum Command {
387 Edit(EditCommand),
389 Cursor(CursorCommand),
391 View(ViewCommand),
393 Style(StyleCommand),
395}
396
397#[derive(Debug, Clone)]
399pub enum CommandResult {
400 Success,
402 Text(String),
404 Position(Position),
406 Offset(usize),
408 Viewport(HeadlessGrid),
410 SearchMatch {
412 start: usize,
414 end: usize,
416 },
417 SearchNotFound,
419 ReplaceResult {
421 replaced: usize,
423 },
424}
425
426#[derive(Debug, Clone, PartialEq, Eq)]
428pub enum CommandError {
429 InvalidOffset(usize),
431 InvalidPosition {
433 line: usize,
435 column: usize,
437 },
438 InvalidRange {
440 start: usize,
442 end: usize,
444 },
445 EmptyText,
447 Other(String),
449}
450
451impl std::fmt::Display for CommandError {
452 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
453 match self {
454 CommandError::InvalidOffset(offset) => {
455 write!(f, "Invalid offset: {}", offset)
456 }
457 CommandError::InvalidPosition { line, column } => {
458 write!(f, "Invalid position: line {}, column {}", line, column)
459 }
460 CommandError::InvalidRange { start, end } => {
461 write!(f, "Invalid range: {}..{}", start, end)
462 }
463 CommandError::EmptyText => {
464 write!(f, "Text cannot be empty")
465 }
466 CommandError::Other(msg) => {
467 write!(f, "{}", msg)
468 }
469 }
470 }
471}
472
473impl std::error::Error for CommandError {}
474
475#[derive(Debug, Clone)]
476struct SelectionSetSnapshot {
477 selections: Vec<Selection>,
478 primary_index: usize,
479}
480
481#[derive(Debug, Clone, Copy, PartialEq, Eq)]
482enum TextBoundary {
483 Grapheme,
484 Word,
485}
486
487fn byte_offset_for_char_column(text: &str, column: usize) -> usize {
488 if column == 0 {
489 return 0;
490 }
491
492 text.char_indices()
493 .nth(column)
494 .map(|(byte, _)| byte)
495 .unwrap_or_else(|| text.len())
496}
497
498fn char_column_for_byte_offset(text: &str, byte_offset: usize) -> usize {
499 text.get(..byte_offset).unwrap_or(text).chars().count()
500}
501
502fn prev_boundary_column(text: &str, column: usize, boundary: TextBoundary) -> usize {
503 let byte_pos = byte_offset_for_char_column(text, column);
504
505 let mut prev = 0usize;
506 match boundary {
507 TextBoundary::Grapheme => {
508 for (b, _) in text.grapheme_indices(true) {
509 if b >= byte_pos {
510 break;
511 }
512 prev = b;
513 }
514 }
515 TextBoundary::Word => {
516 for (b, _) in text.split_word_bound_indices() {
517 if b >= byte_pos {
518 break;
519 }
520 prev = b;
521 }
522 }
523 }
524
525 char_column_for_byte_offset(text, prev)
526}
527
528fn next_boundary_column(text: &str, column: usize, boundary: TextBoundary) -> usize {
529 let byte_pos = byte_offset_for_char_column(text, column);
530
531 let mut next = text.len();
532 match boundary {
533 TextBoundary::Grapheme => {
534 for (b, _) in text.grapheme_indices(true) {
535 if b > byte_pos {
536 next = b;
537 break;
538 }
539 }
540 }
541 TextBoundary::Word => {
542 for (b, _) in text.split_word_bound_indices() {
543 if b > byte_pos {
544 next = b;
545 break;
546 }
547 }
548 }
549 }
550
551 char_column_for_byte_offset(text, next)
552}
553
554#[derive(Debug, Clone)]
555struct TextEdit {
556 start_before: usize,
557 start_after: usize,
558 deleted_text: String,
559 inserted_text: String,
560}
561
562impl TextEdit {
563 fn deleted_len(&self) -> usize {
564 self.deleted_text.chars().count()
565 }
566
567 fn inserted_len(&self) -> usize {
568 self.inserted_text.chars().count()
569 }
570}
571
572#[derive(Debug, Clone)]
573struct UndoStep {
574 group_id: usize,
575 edits: Vec<TextEdit>,
576 before_selection: SelectionSetSnapshot,
577 after_selection: SelectionSetSnapshot,
578}
579
580#[derive(Debug)]
581struct UndoRedoManager {
582 undo_stack: Vec<UndoStep>,
583 redo_stack: Vec<UndoStep>,
584 max_undo: usize,
585 clean_index: Option<usize>,
588 next_group_id: usize,
589 open_group_id: Option<usize>,
590}
591
592impl UndoRedoManager {
593 fn new(max_undo: usize) -> Self {
594 Self {
595 undo_stack: Vec::new(),
596 redo_stack: Vec::new(),
597 max_undo,
598 clean_index: Some(0),
599 next_group_id: 0,
600 open_group_id: None,
601 }
602 }
603
604 fn can_undo(&self) -> bool {
605 !self.undo_stack.is_empty()
606 }
607
608 fn can_redo(&self) -> bool {
609 !self.redo_stack.is_empty()
610 }
611
612 fn undo_depth(&self) -> usize {
613 self.undo_stack.len()
614 }
615
616 fn redo_depth(&self) -> usize {
617 self.redo_stack.len()
618 }
619
620 fn current_group_id(&self) -> Option<usize> {
621 self.open_group_id
622 }
623
624 fn is_clean(&self) -> bool {
625 self.clean_index == Some(self.undo_stack.len())
626 }
627
628 fn mark_clean(&mut self) {
629 self.clean_index = Some(self.undo_stack.len());
630 self.end_group();
631 }
632
633 fn end_group(&mut self) {
634 self.open_group_id = None;
635 }
636
637 fn clear_redo_and_adjust_clean(&mut self) {
638 if self.redo_stack.is_empty() {
639 return;
640 }
641
642 if let Some(clean_index) = self.clean_index
644 && clean_index > self.undo_stack.len()
645 {
646 self.clean_index = None;
647 }
648
649 self.redo_stack.clear();
650 }
651
652 fn push_step(&mut self, mut step: UndoStep, coalescible_insert: bool) -> usize {
653 self.clear_redo_and_adjust_clean();
654
655 if self.undo_stack.len() >= self.max_undo {
656 self.undo_stack.remove(0);
657 if let Some(clean_index) = self.clean_index {
658 if clean_index == 0 {
659 self.clean_index = None;
660 } else {
661 self.clean_index = Some(clean_index - 1);
662 }
663 }
664 }
665
666 let reuse_open_group = coalescible_insert
667 && self.open_group_id.is_some()
668 && self.clean_index != Some(self.undo_stack.len());
669
670 if reuse_open_group {
671 step.group_id = self.open_group_id.expect("checked");
672 } else {
673 step.group_id = self.next_group_id;
674 self.next_group_id = self.next_group_id.wrapping_add(1);
675 }
676
677 if coalescible_insert {
678 self.open_group_id = Some(step.group_id);
679 } else {
680 self.open_group_id = None;
681 }
682
683 let group_id = step.group_id;
684 self.undo_stack.push(step);
685 group_id
686 }
687
688 fn pop_undo_group(&mut self) -> Option<Vec<UndoStep>> {
689 let last_group_id = self.undo_stack.last().map(|s| s.group_id)?;
690 let mut steps: Vec<UndoStep> = Vec::new();
691
692 while let Some(step) = self.undo_stack.last() {
693 if step.group_id != last_group_id {
694 break;
695 }
696 steps.push(self.undo_stack.pop().expect("checked"));
697 }
698
699 Some(steps)
700 }
701
702 fn pop_redo_group(&mut self) -> Option<Vec<UndoStep>> {
703 let last_group_id = self.redo_stack.last().map(|s| s.group_id)?;
704 let mut steps: Vec<UndoStep> = Vec::new();
705
706 while let Some(step) = self.redo_stack.last() {
707 if step.group_id != last_group_id {
708 break;
709 }
710 steps.push(self.redo_stack.pop().expect("checked"));
711 }
712
713 Some(steps)
714 }
715}
716
717pub struct EditorCore {
738 pub piece_table: PieceTable,
740 pub line_index: LineIndex,
742 pub layout_engine: LayoutEngine,
744 pub interval_tree: IntervalTree,
746 pub style_layers: BTreeMap<StyleLayerId, IntervalTree>,
748 pub diagnostics: Vec<Diagnostic>,
750 pub decorations: BTreeMap<DecorationLayerId, Vec<Decoration>>,
752 pub folding_manager: FoldingManager,
754 pub cursor_position: Position,
756 pub selection: Option<Selection>,
758 pub secondary_selections: Vec<Selection>,
760 pub viewport_width: usize,
762}
763
764impl EditorCore {
765 pub fn new(text: &str, viewport_width: usize) -> Self {
767 let normalized = crate::text::normalize_crlf_to_lf(text);
768 let text = normalized.as_ref();
769
770 let piece_table = PieceTable::new(text);
771 let line_index = LineIndex::from_text(text);
772 let mut layout_engine = LayoutEngine::new(viewport_width);
773
774 let lines = crate::text::split_lines_preserve_trailing(text);
776 let line_refs: Vec<&str> = lines.iter().map(|s| s.as_str()).collect();
777 layout_engine.from_lines(&line_refs);
778
779 Self {
780 piece_table,
781 line_index,
782 layout_engine,
783 interval_tree: IntervalTree::new(),
784 style_layers: BTreeMap::new(),
785 diagnostics: Vec::new(),
786 decorations: BTreeMap::new(),
787 folding_manager: FoldingManager::new(),
788 cursor_position: Position::new(0, 0),
789 selection: None,
790 secondary_selections: Vec::new(),
791 viewport_width,
792 }
793 }
794
795 pub fn empty(viewport_width: usize) -> Self {
797 Self::new("", viewport_width)
798 }
799
800 pub fn get_text(&self) -> String {
802 self.piece_table.get_text()
803 }
804
805 pub fn line_count(&self) -> usize {
807 self.line_index.line_count()
808 }
809
810 pub fn char_count(&self) -> usize {
812 self.piece_table.char_count()
813 }
814
815 pub fn cursor_position(&self) -> Position {
817 self.cursor_position
818 }
819
820 pub fn selection(&self) -> Option<&Selection> {
822 self.selection.as_ref()
823 }
824
825 pub fn secondary_selections(&self) -> &[Selection] {
827 &self.secondary_selections
828 }
829
830 pub fn diagnostics(&self) -> &[Diagnostic] {
832 &self.diagnostics
833 }
834
835 pub fn decorations_for_layer(&self, layer: DecorationLayerId) -> &[Decoration] {
837 self.decorations
838 .get(&layer)
839 .map(Vec::as_slice)
840 .unwrap_or(&[])
841 }
842
843 pub fn get_headless_grid_styled(&self, start_visual_row: usize, count: usize) -> HeadlessGrid {
851 let mut grid = HeadlessGrid::new(start_visual_row, count);
852 if count == 0 {
853 return grid;
854 }
855
856 let tab_width = self.layout_engine.tab_width();
857
858 let total_visual = self.visual_line_count();
859 if start_visual_row >= total_visual {
860 return grid;
861 }
862
863 let end_visual = start_visual_row.saturating_add(count).min(total_visual);
864
865 let mut current_visual = 0usize;
866 let logical_line_count = self.layout_engine.logical_line_count();
867 let regions = self.folding_manager.regions();
868
869 'outer: for logical_line in 0..logical_line_count {
870 if Self::is_logical_line_hidden(regions, logical_line) {
871 continue;
872 }
873
874 let Some(layout) = self.layout_engine.get_line_layout(logical_line) else {
875 continue;
876 };
877
878 let line_text = self
879 .line_index
880 .get_line_text(logical_line)
881 .unwrap_or_default();
882 let line_char_len = line_text.chars().count();
883 let line_start_offset = self.line_index.position_to_char_offset(logical_line, 0);
884
885 for visual_in_line in 0..layout.visual_line_count {
886 if current_visual >= end_visual {
887 break 'outer;
888 }
889
890 if current_visual >= start_visual_row {
891 let segment_start_col = if visual_in_line == 0 {
892 0
893 } else {
894 layout
895 .wrap_points
896 .get(visual_in_line - 1)
897 .map(|wp| wp.char_index)
898 .unwrap_or(0)
899 .min(line_char_len)
900 };
901
902 let segment_end_col = if visual_in_line < layout.wrap_points.len() {
903 layout.wrap_points[visual_in_line]
904 .char_index
905 .min(line_char_len)
906 } else {
907 line_char_len
908 };
909
910 let mut headless_line = HeadlessLine::new(logical_line, visual_in_line > 0);
911 if visual_in_line > 0 {
912 let indent_cells = wrap_indent_cells_for_line_text(
913 &line_text,
914 self.layout_engine.wrap_indent(),
915 self.viewport_width,
916 tab_width,
917 );
918 for _ in 0..indent_cells {
919 headless_line.add_cell(Cell::new(' ', 1));
920 }
921 }
922 let mut x_in_line =
923 visual_x_for_column(&line_text, segment_start_col, tab_width);
924
925 for (col, ch) in line_text
926 .chars()
927 .enumerate()
928 .skip(segment_start_col)
929 .take(segment_end_col.saturating_sub(segment_start_col))
930 {
931 let offset = line_start_offset + col;
932 let styles = self.styles_at_offset(offset);
933 let w = cell_width_at(ch, x_in_line, tab_width);
934 x_in_line = x_in_line.saturating_add(w);
935 headless_line.add_cell(Cell::with_styles(ch, w, styles));
936 }
937
938 if visual_in_line + 1 == layout.visual_line_count
940 && let Some(region) =
941 Self::collapsed_region_starting_at(regions, logical_line)
942 && !region.placeholder.is_empty()
943 {
944 if !headless_line.cells.is_empty() {
945 x_in_line = x_in_line.saturating_add(char_width(' '));
946 headless_line.add_cell(Cell::with_styles(
947 ' ',
948 char_width(' '),
949 vec![FOLD_PLACEHOLDER_STYLE_ID],
950 ));
951 }
952 for ch in region.placeholder.chars() {
953 let w = cell_width_at(ch, x_in_line, tab_width);
954 x_in_line = x_in_line.saturating_add(w);
955 headless_line.add_cell(Cell::with_styles(
956 ch,
957 w,
958 vec![FOLD_PLACEHOLDER_STYLE_ID],
959 ));
960 }
961 }
962
963 grid.add_line(headless_line);
964 }
965
966 current_visual = current_visual.saturating_add(1);
967 }
968 }
969
970 grid
971 }
972
973 pub fn visual_line_count(&self) -> usize {
975 let regions = self.folding_manager.regions();
976 let mut total = 0usize;
977
978 for logical_line in 0..self.layout_engine.logical_line_count() {
979 if Self::is_logical_line_hidden(regions, logical_line) {
980 continue;
981 }
982
983 total = total.saturating_add(
984 self.layout_engine
985 .get_line_layout(logical_line)
986 .map(|l| l.visual_line_count)
987 .unwrap_or(1),
988 );
989 }
990
991 total
992 }
993
994 pub fn visual_to_logical_line(&self, visual_line: usize) -> (usize, usize) {
996 let regions = self.folding_manager.regions();
997 let mut cumulative_visual = 0usize;
998 let mut last_visible = (0usize, 0usize);
999
1000 for logical_line in 0..self.layout_engine.logical_line_count() {
1001 if Self::is_logical_line_hidden(regions, logical_line) {
1002 continue;
1003 }
1004
1005 let visual_count = self
1006 .layout_engine
1007 .get_line_layout(logical_line)
1008 .map(|l| l.visual_line_count)
1009 .unwrap_or(1);
1010
1011 if cumulative_visual + visual_count > visual_line {
1012 return (logical_line, visual_line - cumulative_visual);
1013 }
1014
1015 cumulative_visual = cumulative_visual.saturating_add(visual_count);
1016 last_visible = (logical_line, visual_count.saturating_sub(1));
1017 }
1018
1019 last_visible
1020 }
1021
1022 pub fn logical_position_to_visual(
1024 &self,
1025 logical_line: usize,
1026 column: usize,
1027 ) -> Option<(usize, usize)> {
1028 let regions = self.folding_manager.regions();
1029 let logical_line = Self::closest_visible_line(regions, logical_line)?;
1030 let visual_start = self.visual_start_for_logical_line(logical_line)?;
1031
1032 let tab_width = self.layout_engine.tab_width();
1033
1034 let layout = self.layout_engine.get_line_layout(logical_line)?;
1035 let line_text = self
1036 .line_index
1037 .get_line_text(logical_line)
1038 .unwrap_or_default();
1039
1040 let line_char_len = line_text.chars().count();
1041 let column = column.min(line_char_len);
1042
1043 let mut wrapped_offset = 0usize;
1044 let mut segment_start_col = 0usize;
1045 for wrap_point in &layout.wrap_points {
1046 if column >= wrap_point.char_index {
1047 wrapped_offset = wrapped_offset.saturating_add(1);
1048 segment_start_col = wrap_point.char_index;
1049 } else {
1050 break;
1051 }
1052 }
1053
1054 let seg_start_x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
1055 let mut x_in_line = seg_start_x_in_line;
1056 let mut x_in_segment = 0usize;
1057 for ch in line_text
1058 .chars()
1059 .skip(segment_start_col)
1060 .take(column.saturating_sub(segment_start_col))
1061 {
1062 let w = cell_width_at(ch, x_in_line, tab_width);
1063 x_in_line = x_in_line.saturating_add(w);
1064 x_in_segment = x_in_segment.saturating_add(w);
1065 }
1066
1067 let indent = if wrapped_offset == 0 {
1068 0
1069 } else {
1070 wrap_indent_cells_for_line_text(
1071 &line_text,
1072 self.layout_engine.wrap_indent(),
1073 self.viewport_width,
1074 tab_width,
1075 )
1076 };
1077
1078 Some((
1079 visual_start.saturating_add(wrapped_offset),
1080 indent.saturating_add(x_in_segment),
1081 ))
1082 }
1083
1084 pub fn logical_position_to_visual_allow_virtual(
1089 &self,
1090 logical_line: usize,
1091 column: usize,
1092 ) -> Option<(usize, usize)> {
1093 let regions = self.folding_manager.regions();
1094 let logical_line = Self::closest_visible_line(regions, logical_line)?;
1095 let visual_start = self.visual_start_for_logical_line(logical_line)?;
1096
1097 let tab_width = self.layout_engine.tab_width();
1098
1099 let layout = self.layout_engine.get_line_layout(logical_line)?;
1100 let line_text = self
1101 .line_index
1102 .get_line_text(logical_line)
1103 .unwrap_or_default();
1104
1105 let line_char_len = line_text.chars().count();
1106 let clamped_column = column.min(line_char_len);
1107
1108 let mut wrapped_offset = 0usize;
1109 let mut segment_start_col = 0usize;
1110 for wrap_point in &layout.wrap_points {
1111 if clamped_column >= wrap_point.char_index {
1112 wrapped_offset = wrapped_offset.saturating_add(1);
1113 segment_start_col = wrap_point.char_index;
1114 } else {
1115 break;
1116 }
1117 }
1118
1119 let seg_start_x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
1120 let mut x_in_line = seg_start_x_in_line;
1121 let mut x_in_segment = 0usize;
1122 for ch in line_text
1123 .chars()
1124 .skip(segment_start_col)
1125 .take(clamped_column.saturating_sub(segment_start_col))
1126 {
1127 let w = cell_width_at(ch, x_in_line, tab_width);
1128 x_in_line = x_in_line.saturating_add(w);
1129 x_in_segment = x_in_segment.saturating_add(w);
1130 }
1131
1132 let x_in_segment = x_in_segment + column.saturating_sub(line_char_len);
1133
1134 let indent = if wrapped_offset == 0 {
1135 0
1136 } else {
1137 wrap_indent_cells_for_line_text(
1138 &line_text,
1139 self.layout_engine.wrap_indent(),
1140 self.viewport_width,
1141 tab_width,
1142 )
1143 };
1144
1145 Some((
1146 visual_start.saturating_add(wrapped_offset),
1147 indent.saturating_add(x_in_segment),
1148 ))
1149 }
1150
1151 pub fn visual_position_to_logical(
1158 &self,
1159 visual_row: usize,
1160 x_in_cells: usize,
1161 ) -> Option<Position> {
1162 let total_visual = self.visual_line_count();
1163 if total_visual == 0 {
1164 return Some(Position::new(0, 0));
1165 }
1166
1167 let clamped_row = visual_row.min(total_visual.saturating_sub(1));
1168 let (logical_line, visual_in_logical) = self.visual_to_logical_line(clamped_row);
1169
1170 let layout = self.layout_engine.get_line_layout(logical_line)?;
1171 let line_text = self
1172 .line_index
1173 .get_line_text(logical_line)
1174 .unwrap_or_default();
1175 let line_char_len = line_text.chars().count();
1176
1177 let segment_start_col = if visual_in_logical == 0 {
1178 0
1179 } else {
1180 layout
1181 .wrap_points
1182 .get(visual_in_logical - 1)
1183 .map(|wp| wp.char_index)
1184 .unwrap_or(0)
1185 };
1186
1187 let segment_end_col = layout
1188 .wrap_points
1189 .get(visual_in_logical)
1190 .map(|wp| wp.char_index)
1191 .unwrap_or(line_char_len)
1192 .max(segment_start_col)
1193 .min(line_char_len);
1194
1195 let tab_width = self.layout_engine.tab_width();
1196 let x_in_cells = if visual_in_logical == 0 {
1197 x_in_cells
1198 } else {
1199 let indent = wrap_indent_cells_for_line_text(
1200 &line_text,
1201 self.layout_engine.wrap_indent(),
1202 self.viewport_width,
1203 tab_width,
1204 );
1205 x_in_cells.saturating_sub(indent)
1206 };
1207 let seg_start_x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
1208 let mut x_in_line = seg_start_x_in_line;
1209 let mut x_in_segment = 0usize;
1210 let mut column = segment_start_col;
1211
1212 for (char_idx, ch) in line_text.chars().enumerate().skip(segment_start_col) {
1213 if char_idx >= segment_end_col {
1214 break;
1215 }
1216
1217 let w = cell_width_at(ch, x_in_line, tab_width);
1218 if x_in_segment.saturating_add(w) > x_in_cells {
1219 break;
1220 }
1221
1222 x_in_line = x_in_line.saturating_add(w);
1223 x_in_segment = x_in_segment.saturating_add(w);
1224 column = column.saturating_add(1);
1225 }
1226
1227 Some(Position::new(logical_line, column))
1228 }
1229
1230 fn visual_start_for_logical_line(&self, logical_line: usize) -> Option<usize> {
1231 if logical_line >= self.layout_engine.logical_line_count() {
1232 return None;
1233 }
1234
1235 let regions = self.folding_manager.regions();
1236 if Self::is_logical_line_hidden(regions, logical_line) {
1237 return None;
1238 }
1239
1240 let mut start = 0usize;
1241 for line in 0..logical_line {
1242 if Self::is_logical_line_hidden(regions, line) {
1243 continue;
1244 }
1245 start = start.saturating_add(
1246 self.layout_engine
1247 .get_line_layout(line)
1248 .map(|l| l.visual_line_count)
1249 .unwrap_or(1),
1250 );
1251 }
1252 Some(start)
1253 }
1254
1255 fn is_logical_line_hidden(regions: &[FoldRegion], logical_line: usize) -> bool {
1256 regions.iter().any(|region| {
1257 region.is_collapsed
1258 && logical_line > region.start_line
1259 && logical_line <= region.end_line
1260 })
1261 }
1262
1263 fn collapsed_region_starting_at(
1264 regions: &[FoldRegion],
1265 start_line: usize,
1266 ) -> Option<&FoldRegion> {
1267 regions
1268 .iter()
1269 .filter(|region| {
1270 region.is_collapsed
1271 && region.start_line == start_line
1272 && region.end_line > start_line
1273 })
1274 .min_by_key(|region| region.end_line)
1275 }
1276
1277 fn closest_visible_line(regions: &[FoldRegion], logical_line: usize) -> Option<usize> {
1278 let mut line = logical_line;
1279 if regions.is_empty() {
1280 return Some(line);
1281 }
1282
1283 while Self::is_logical_line_hidden(regions, line) {
1284 let Some(start) = regions
1285 .iter()
1286 .filter(|region| {
1287 region.is_collapsed && line > region.start_line && line <= region.end_line
1288 })
1289 .map(|region| region.start_line)
1290 .max()
1291 else {
1292 break;
1293 };
1294 line = start;
1295 }
1296
1297 if Self::is_logical_line_hidden(regions, line) {
1298 None
1299 } else {
1300 Some(line)
1301 }
1302 }
1303
1304 fn styles_at_offset(&self, offset: usize) -> Vec<StyleId> {
1305 let mut styles: Vec<StyleId> = self
1306 .interval_tree
1307 .query_point(offset)
1308 .iter()
1309 .map(|interval| interval.style_id)
1310 .collect();
1311
1312 for tree in self.style_layers.values() {
1313 styles.extend(
1314 tree.query_point(offset)
1315 .iter()
1316 .map(|interval| interval.style_id),
1317 );
1318 }
1319
1320 styles.sort_unstable();
1321 styles.dedup();
1322 styles
1323 }
1324}
1325
1326pub struct CommandExecutor {
1364 editor: EditorCore,
1366 command_history: Vec<Command>,
1368 undo_redo: UndoRedoManager,
1370 tab_key_behavior: TabKeyBehavior,
1372 line_ending: LineEnding,
1374 preferred_x_cells: Option<usize>,
1376 last_text_delta: Option<TextDelta>,
1378}
1379
1380impl CommandExecutor {
1381 pub fn new(text: &str, viewport_width: usize) -> Self {
1383 Self {
1384 editor: EditorCore::new(text, viewport_width),
1385 command_history: Vec::new(),
1386 undo_redo: UndoRedoManager::new(1000),
1387 tab_key_behavior: TabKeyBehavior::Tab,
1388 line_ending: LineEnding::detect_in_text(text),
1389 preferred_x_cells: None,
1390 last_text_delta: None,
1391 }
1392 }
1393
1394 pub fn empty(viewport_width: usize) -> Self {
1396 Self::new("", viewport_width)
1397 }
1398
1399 pub fn execute(&mut self, command: Command) -> Result<CommandResult, CommandError> {
1401 self.last_text_delta = None;
1402
1403 self.command_history.push(command.clone());
1405
1406 if !matches!(command, Command::Edit(_)) {
1408 self.undo_redo.end_group();
1409 }
1410
1411 match command {
1413 Command::Edit(edit_cmd) => self.execute_edit(edit_cmd),
1414 Command::Cursor(cursor_cmd) => self.execute_cursor(cursor_cmd),
1415 Command::View(view_cmd) => self.execute_view(view_cmd),
1416 Command::Style(style_cmd) => self.execute_style(style_cmd),
1417 }
1418 }
1419
1420 pub fn last_text_delta(&self) -> Option<&TextDelta> {
1422 self.last_text_delta.as_ref()
1423 }
1424
1425 pub fn take_last_text_delta(&mut self) -> Option<TextDelta> {
1427 self.last_text_delta.take()
1428 }
1429
1430 pub fn execute_batch(
1432 &mut self,
1433 commands: Vec<Command>,
1434 ) -> Result<Vec<CommandResult>, CommandError> {
1435 let mut results = Vec::new();
1436
1437 for command in commands {
1438 let result = self.execute(command)?;
1439 results.push(result);
1440 }
1441
1442 Ok(results)
1443 }
1444
1445 pub fn get_command_history(&self) -> &[Command] {
1447 &self.command_history
1448 }
1449
1450 pub fn can_undo(&self) -> bool {
1452 self.undo_redo.can_undo()
1453 }
1454
1455 pub fn can_redo(&self) -> bool {
1457 self.undo_redo.can_redo()
1458 }
1459
1460 pub fn undo_depth(&self) -> usize {
1462 self.undo_redo.undo_depth()
1463 }
1464
1465 pub fn redo_depth(&self) -> usize {
1467 self.undo_redo.redo_depth()
1468 }
1469
1470 pub fn current_change_group(&self) -> Option<usize> {
1472 self.undo_redo.current_group_id()
1473 }
1474
1475 pub fn is_clean(&self) -> bool {
1477 self.undo_redo.is_clean()
1478 }
1479
1480 pub fn mark_clean(&mut self) {
1482 self.undo_redo.mark_clean();
1483 }
1484
1485 pub fn editor(&self) -> &EditorCore {
1487 &self.editor
1488 }
1489
1490 pub fn editor_mut(&mut self) -> &mut EditorCore {
1492 &mut self.editor
1493 }
1494
1495 pub fn tab_key_behavior(&self) -> TabKeyBehavior {
1497 self.tab_key_behavior
1498 }
1499
1500 pub fn set_tab_key_behavior(&mut self, behavior: TabKeyBehavior) {
1502 self.tab_key_behavior = behavior;
1503 }
1504
1505 pub fn line_ending(&self) -> LineEnding {
1507 self.line_ending
1508 }
1509
1510 pub fn set_line_ending(&mut self, line_ending: LineEnding) {
1512 self.line_ending = line_ending;
1513 }
1514
1515 fn execute_edit(&mut self, command: EditCommand) -> Result<CommandResult, CommandError> {
1517 match command {
1518 EditCommand::Undo => self.execute_undo_command(),
1519 EditCommand::Redo => self.execute_redo_command(),
1520 EditCommand::EndUndoGroup => {
1521 self.undo_redo.end_group();
1522 Ok(CommandResult::Success)
1523 }
1524 EditCommand::ReplaceCurrent {
1525 query,
1526 replacement,
1527 options,
1528 } => self.execute_replace_current_command(query, replacement, options),
1529 EditCommand::ReplaceAll {
1530 query,
1531 replacement,
1532 options,
1533 } => self.execute_replace_all_command(query, replacement, options),
1534 EditCommand::DeleteToPrevTabStop => self.execute_delete_to_prev_tab_stop_command(),
1535 EditCommand::DeleteGraphemeBack => {
1536 self.execute_delete_by_boundary_command(false, TextBoundary::Grapheme)
1537 }
1538 EditCommand::DeleteGraphemeForward => {
1539 self.execute_delete_by_boundary_command(true, TextBoundary::Grapheme)
1540 }
1541 EditCommand::DeleteWordBack => {
1542 self.execute_delete_by_boundary_command(false, TextBoundary::Word)
1543 }
1544 EditCommand::DeleteWordForward => {
1545 self.execute_delete_by_boundary_command(true, TextBoundary::Word)
1546 }
1547 EditCommand::Backspace => self.execute_backspace_command(),
1548 EditCommand::DeleteForward => self.execute_delete_forward_command(),
1549 EditCommand::InsertText { text } => self.execute_insert_text_command(text),
1550 EditCommand::InsertTab => self.execute_insert_tab_command(),
1551 EditCommand::InsertNewline { auto_indent } => {
1552 self.execute_insert_newline_command(auto_indent)
1553 }
1554 EditCommand::Indent => self.execute_indent_command(false),
1555 EditCommand::Outdent => self.execute_indent_command(true),
1556 EditCommand::Insert { offset, text } => self.execute_insert_command(offset, text),
1557 EditCommand::Delete { start, length } => self.execute_delete_command(start, length),
1558 EditCommand::Replace {
1559 start,
1560 length,
1561 text,
1562 } => self.execute_replace_command(start, length, text),
1563 }
1564 }
1565
1566 fn execute_undo_command(&mut self) -> Result<CommandResult, CommandError> {
1567 self.undo_redo.end_group();
1568 if !self.undo_redo.can_undo() {
1569 return Err(CommandError::Other("Nothing to undo".to_string()));
1570 }
1571
1572 let before_char_count = self.editor.piece_table.char_count();
1573 let steps = self
1574 .undo_redo
1575 .pop_undo_group()
1576 .ok_or_else(|| CommandError::Other("Nothing to undo".to_string()))?;
1577
1578 let undo_group_id = steps.first().map(|s| s.group_id);
1579 let mut delta_edits: Vec<TextDeltaEdit> = Vec::new();
1580
1581 for step in &steps {
1582 let mut step_edits: Vec<TextDeltaEdit> = step
1583 .edits
1584 .iter()
1585 .map(|edit| TextDeltaEdit {
1586 start: edit.start_after,
1587 deleted_text: edit.inserted_text.clone(),
1588 inserted_text: edit.deleted_text.clone(),
1589 })
1590 .collect();
1591 step_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
1592 delta_edits.extend(step_edits);
1593
1594 self.apply_undo_edits(&step.edits)?;
1595 self.restore_selection_set(step.before_selection.clone());
1596 }
1597
1598 for step in steps {
1600 self.undo_redo.redo_stack.push(step);
1601 }
1602
1603 self.last_text_delta = Some(TextDelta {
1604 before_char_count,
1605 after_char_count: self.editor.piece_table.char_count(),
1606 edits: delta_edits,
1607 undo_group_id,
1608 });
1609
1610 Ok(CommandResult::Success)
1611 }
1612
1613 fn execute_redo_command(&mut self) -> Result<CommandResult, CommandError> {
1614 self.undo_redo.end_group();
1615 if !self.undo_redo.can_redo() {
1616 return Err(CommandError::Other("Nothing to redo".to_string()));
1617 }
1618
1619 let before_char_count = self.editor.piece_table.char_count();
1620 let steps = self
1621 .undo_redo
1622 .pop_redo_group()
1623 .ok_or_else(|| CommandError::Other("Nothing to redo".to_string()))?;
1624
1625 let undo_group_id = steps.first().map(|s| s.group_id);
1626 let mut delta_edits: Vec<TextDeltaEdit> = Vec::new();
1627
1628 for step in &steps {
1629 let mut step_edits: Vec<TextDeltaEdit> = step
1630 .edits
1631 .iter()
1632 .map(|edit| TextDeltaEdit {
1633 start: edit.start_before,
1634 deleted_text: edit.deleted_text.clone(),
1635 inserted_text: edit.inserted_text.clone(),
1636 })
1637 .collect();
1638 step_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
1639 delta_edits.extend(step_edits);
1640
1641 self.apply_redo_edits(&step.edits)?;
1642 self.restore_selection_set(step.after_selection.clone());
1643 }
1644
1645 for step in steps {
1647 self.undo_redo.undo_stack.push(step);
1648 }
1649
1650 self.last_text_delta = Some(TextDelta {
1651 before_char_count,
1652 after_char_count: self.editor.piece_table.char_count(),
1653 edits: delta_edits,
1654 undo_group_id,
1655 });
1656
1657 Ok(CommandResult::Success)
1658 }
1659
1660 fn execute_insert_text_command(&mut self, text: String) -> Result<CommandResult, CommandError> {
1661 if text.is_empty() {
1662 return Ok(CommandResult::Success);
1663 }
1664
1665 let text = crate::text::normalize_crlf_to_lf_string(text);
1666 let before_char_count = self.editor.piece_table.char_count();
1667 let before_selection = self.snapshot_selection_set();
1668
1669 let mut selections: Vec<Selection> =
1673 Vec::with_capacity(1 + self.editor.secondary_selections.len());
1674 let primary_selection = self.editor.selection.clone().unwrap_or(Selection {
1675 start: self.editor.cursor_position,
1676 end: self.editor.cursor_position,
1677 direction: SelectionDirection::Forward,
1678 });
1679 selections.push(primary_selection);
1680 selections.extend(self.editor.secondary_selections.iter().cloned());
1681
1682 let (selections, primary_index) = crate::selection_set::normalize_selections(selections, 0);
1683
1684 let text_char_len = text.chars().count();
1685
1686 struct Op {
1687 selection_index: usize,
1688 start_offset: usize,
1689 start_after: usize,
1690 delete_len: usize,
1691 deleted_text: String,
1692 insert_text: String,
1693 insert_char_len: usize,
1694 }
1695
1696 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
1697
1698 for (selection_index, selection) in selections.iter().enumerate() {
1699 let (range_start_pos, range_end_pos) = if selection.start <= selection.end {
1700 (selection.start, selection.end)
1701 } else {
1702 (selection.end, selection.start)
1703 };
1704
1705 let (start_offset, start_pad) =
1706 self.position_to_char_offset_and_virtual_pad(range_start_pos);
1707 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
1708
1709 let delete_len = end_offset.saturating_sub(start_offset);
1710 let insert_char_len = start_pad + text_char_len;
1711
1712 let deleted_text = if delete_len == 0 {
1713 String::new()
1714 } else {
1715 self.editor.piece_table.get_range(start_offset, delete_len)
1716 };
1717
1718 let mut insert_text = String::with_capacity(text.len() + start_pad);
1719 for _ in 0..start_pad {
1720 insert_text.push(' ');
1721 }
1722 insert_text.push_str(&text);
1723
1724 ops.push(Op {
1725 selection_index,
1726 start_offset,
1727 start_after: start_offset,
1728 delete_len,
1729 deleted_text,
1730 insert_text,
1731 insert_char_len,
1732 });
1733 }
1734
1735 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
1738 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
1739
1740 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
1741 let mut delta: i64 = 0;
1742 for &idx in &asc_indices {
1743 let op = &mut ops[idx];
1744 let effective_start = (op.start_offset as i64 + delta) as usize;
1745 op.start_after = effective_start;
1746 caret_offsets[op.selection_index] = effective_start + op.insert_char_len;
1747 delta += op.insert_char_len as i64 - op.delete_len as i64;
1748 }
1749
1750 let mut desc_indices = asc_indices;
1752 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
1753
1754 for &idx in &desc_indices {
1755 let op = &ops[idx];
1756
1757 if op.delete_len > 0 {
1758 self.editor
1759 .piece_table
1760 .delete(op.start_offset, op.delete_len);
1761 self.editor
1762 .interval_tree
1763 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
1764 for layer_tree in self.editor.style_layers.values_mut() {
1765 layer_tree
1766 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
1767 }
1768 }
1769
1770 if !op.insert_text.is_empty() {
1771 self.editor
1772 .piece_table
1773 .insert(op.start_offset, &op.insert_text);
1774 self.editor
1775 .interval_tree
1776 .update_for_insertion(op.start_offset, op.insert_char_len);
1777 for layer_tree in self.editor.style_layers.values_mut() {
1778 layer_tree.update_for_insertion(op.start_offset, op.insert_char_len);
1779 }
1780 }
1781 }
1782
1783 let updated_text = self.editor.piece_table.get_text();
1785 self.editor.line_index = LineIndex::from_text(&updated_text);
1786 self.rebuild_layout_engine_from_text(&updated_text);
1787
1788 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
1790 for offset in &caret_offsets {
1791 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
1792 let pos = Position::new(line, column);
1793 new_carets.push(Selection {
1794 start: pos,
1795 end: pos,
1796 direction: SelectionDirection::Forward,
1797 });
1798 }
1799
1800 let (new_carets, new_primary_index) =
1801 crate::selection_set::normalize_selections(new_carets, primary_index);
1802 let primary = new_carets
1803 .get(new_primary_index)
1804 .cloned()
1805 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
1806
1807 self.editor.cursor_position = primary.end;
1808 self.editor.selection = None;
1809 self.editor.secondary_selections = new_carets
1810 .into_iter()
1811 .enumerate()
1812 .filter_map(|(idx, sel)| {
1813 if idx == new_primary_index {
1814 None
1815 } else {
1816 Some(sel)
1817 }
1818 })
1819 .collect();
1820
1821 let after_selection = self.snapshot_selection_set();
1822
1823 let edits: Vec<TextEdit> = ops
1824 .into_iter()
1825 .map(|op| TextEdit {
1826 start_before: op.start_offset,
1827 start_after: op.start_after,
1828 deleted_text: op.deleted_text,
1829 inserted_text: op.insert_text,
1830 })
1831 .collect();
1832
1833 let is_pure_insert = edits.iter().all(|e| e.deleted_text.is_empty());
1834 let coalescible_insert = is_pure_insert && !text.contains('\n');
1835
1836 let mut delta_edits: Vec<TextDeltaEdit> = edits
1837 .iter()
1838 .map(|e| TextDeltaEdit {
1839 start: e.start_before,
1840 deleted_text: e.deleted_text.clone(),
1841 inserted_text: e.inserted_text.clone(),
1842 })
1843 .collect();
1844 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
1845
1846 let step = UndoStep {
1847 group_id: 0,
1848 edits,
1849 before_selection,
1850 after_selection,
1851 };
1852 let group_id = self.undo_redo.push_step(step, coalescible_insert);
1853
1854 self.last_text_delta = Some(TextDelta {
1855 before_char_count,
1856 after_char_count: self.editor.piece_table.char_count(),
1857 edits: delta_edits,
1858 undo_group_id: Some(group_id),
1859 });
1860
1861 Ok(CommandResult::Success)
1862 }
1863
1864 fn execute_insert_tab_command(&mut self) -> Result<CommandResult, CommandError> {
1865 let before_char_count = self.editor.piece_table.char_count();
1866 let before_selection = self.snapshot_selection_set();
1867
1868 let mut selections: Vec<Selection> =
1869 Vec::with_capacity(1 + self.editor.secondary_selections.len());
1870 let primary_selection = self.editor.selection.clone().unwrap_or(Selection {
1871 start: self.editor.cursor_position,
1872 end: self.editor.cursor_position,
1873 direction: SelectionDirection::Forward,
1874 });
1875 selections.push(primary_selection);
1876 selections.extend(self.editor.secondary_selections.iter().cloned());
1877
1878 let (selections, primary_index) = crate::selection_set::normalize_selections(selections, 0);
1879
1880 let tab_width = self.editor.layout_engine.tab_width();
1881
1882 struct Op {
1883 selection_index: usize,
1884 start_offset: usize,
1885 start_after: usize,
1886 delete_len: usize,
1887 deleted_text: String,
1888 insert_text: String,
1889 insert_char_len: usize,
1890 }
1891
1892 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
1893
1894 for (selection_index, selection) in selections.iter().enumerate() {
1895 let (range_start_pos, range_end_pos) = if selection.start <= selection.end {
1896 (selection.start, selection.end)
1897 } else {
1898 (selection.end, selection.start)
1899 };
1900
1901 let (start_offset, start_pad) =
1902 self.position_to_char_offset_and_virtual_pad(range_start_pos);
1903 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
1904
1905 let delete_len = end_offset.saturating_sub(start_offset);
1906
1907 let deleted_text = if delete_len == 0 {
1908 String::new()
1909 } else {
1910 self.editor.piece_table.get_range(start_offset, delete_len)
1911 };
1912
1913 let line_text = self
1915 .editor
1916 .line_index
1917 .get_line_text(range_start_pos.line)
1918 .unwrap_or_default();
1919 let line_char_len = line_text.chars().count();
1920 let clamped_col = range_start_pos.column.min(line_char_len);
1921 let x_in_line =
1922 visual_x_for_column(&line_text, clamped_col, tab_width).saturating_add(start_pad);
1923
1924 let mut insert_text = String::new();
1925 for _ in 0..start_pad {
1926 insert_text.push(' ');
1927 }
1928
1929 match self.tab_key_behavior {
1930 TabKeyBehavior::Tab => {
1931 insert_text.push('\t');
1932 ops.push(Op {
1933 selection_index,
1934 start_offset,
1935 start_after: start_offset,
1936 delete_len,
1937 deleted_text,
1938 insert_text,
1939 insert_char_len: start_pad + 1,
1940 });
1941 }
1942 TabKeyBehavior::Spaces => {
1943 let tab_width = tab_width.max(1);
1944 let rem = x_in_line % tab_width;
1945 let spaces = tab_width - rem;
1946 for _ in 0..spaces {
1947 insert_text.push(' ');
1948 }
1949
1950 ops.push(Op {
1951 selection_index,
1952 start_offset,
1953 start_after: start_offset,
1954 delete_len,
1955 deleted_text,
1956 insert_text,
1957 insert_char_len: start_pad + spaces,
1958 });
1959 }
1960 }
1961 }
1962
1963 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
1966 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
1967
1968 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
1969 let mut delta: i64 = 0;
1970 for &idx in &asc_indices {
1971 let op = &mut ops[idx];
1972 let effective_start = (op.start_offset as i64 + delta) as usize;
1973 op.start_after = effective_start;
1974 caret_offsets[op.selection_index] = effective_start + op.insert_char_len;
1975 delta += op.insert_char_len as i64 - op.delete_len as i64;
1976 }
1977
1978 let mut desc_indices = asc_indices;
1980 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
1981
1982 for &idx in &desc_indices {
1983 let op = &ops[idx];
1984
1985 if op.delete_len > 0 {
1986 self.editor
1987 .piece_table
1988 .delete(op.start_offset, op.delete_len);
1989 self.editor
1990 .interval_tree
1991 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
1992 for layer_tree in self.editor.style_layers.values_mut() {
1993 layer_tree
1994 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
1995 }
1996 }
1997
1998 if !op.insert_text.is_empty() {
1999 self.editor
2000 .piece_table
2001 .insert(op.start_offset, &op.insert_text);
2002 self.editor
2003 .interval_tree
2004 .update_for_insertion(op.start_offset, op.insert_char_len);
2005 for layer_tree in self.editor.style_layers.values_mut() {
2006 layer_tree.update_for_insertion(op.start_offset, op.insert_char_len);
2007 }
2008 }
2009 }
2010
2011 let updated_text = self.editor.piece_table.get_text();
2013 self.editor.line_index = LineIndex::from_text(&updated_text);
2014 self.rebuild_layout_engine_from_text(&updated_text);
2015
2016 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
2018 for offset in &caret_offsets {
2019 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
2020 let pos = Position::new(line, column);
2021 new_carets.push(Selection {
2022 start: pos,
2023 end: pos,
2024 direction: SelectionDirection::Forward,
2025 });
2026 }
2027
2028 let (new_carets, new_primary_index) =
2029 crate::selection_set::normalize_selections(new_carets, primary_index);
2030 let primary = new_carets
2031 .get(new_primary_index)
2032 .cloned()
2033 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
2034
2035 self.editor.cursor_position = primary.end;
2036 self.editor.selection = None;
2037 self.editor.secondary_selections = new_carets
2038 .into_iter()
2039 .enumerate()
2040 .filter_map(|(idx, sel)| {
2041 if idx == new_primary_index {
2042 None
2043 } else {
2044 Some(sel)
2045 }
2046 })
2047 .collect();
2048
2049 let after_selection = self.snapshot_selection_set();
2050
2051 let edits: Vec<TextEdit> = ops
2052 .into_iter()
2053 .map(|op| TextEdit {
2054 start_before: op.start_offset,
2055 start_after: op.start_after,
2056 deleted_text: op.deleted_text,
2057 inserted_text: op.insert_text,
2058 })
2059 .collect();
2060
2061 let is_pure_insert = edits.iter().all(|e| e.deleted_text.is_empty());
2062 let coalescible_insert = is_pure_insert;
2063
2064 let mut delta_edits: Vec<TextDeltaEdit> = edits
2065 .iter()
2066 .map(|e| TextDeltaEdit {
2067 start: e.start_before,
2068 deleted_text: e.deleted_text.clone(),
2069 inserted_text: e.inserted_text.clone(),
2070 })
2071 .collect();
2072 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
2073
2074 let step = UndoStep {
2075 group_id: 0,
2076 edits,
2077 before_selection,
2078 after_selection,
2079 };
2080 let group_id = self.undo_redo.push_step(step, coalescible_insert);
2081
2082 self.last_text_delta = Some(TextDelta {
2083 before_char_count,
2084 after_char_count: self.editor.piece_table.char_count(),
2085 edits: delta_edits,
2086 undo_group_id: Some(group_id),
2087 });
2088
2089 Ok(CommandResult::Success)
2090 }
2091
2092 fn leading_whitespace_prefix(line_text: &str) -> String {
2093 line_text
2094 .chars()
2095 .take_while(|ch| *ch == ' ' || *ch == '\t')
2096 .collect()
2097 }
2098
2099 fn indent_unit(&self) -> String {
2100 match self.tab_key_behavior {
2101 TabKeyBehavior::Tab => "\t".to_string(),
2102 TabKeyBehavior::Spaces => " ".repeat(self.editor.layout_engine.tab_width().max(1)),
2103 }
2104 }
2105
2106 fn execute_insert_newline_command(
2107 &mut self,
2108 auto_indent: bool,
2109 ) -> Result<CommandResult, CommandError> {
2110 self.undo_redo.end_group();
2112
2113 let before_char_count = self.editor.piece_table.char_count();
2114 let before_selection = self.snapshot_selection_set();
2115
2116 let mut selections: Vec<Selection> =
2118 Vec::with_capacity(1 + self.editor.secondary_selections.len());
2119 let primary_selection = self.editor.selection.clone().unwrap_or(Selection {
2120 start: self.editor.cursor_position,
2121 end: self.editor.cursor_position,
2122 direction: SelectionDirection::Forward,
2123 });
2124 selections.push(primary_selection);
2125 selections.extend(self.editor.secondary_selections.iter().cloned());
2126
2127 let (selections, primary_index) = crate::selection_set::normalize_selections(selections, 0);
2128
2129 struct Op {
2130 selection_index: usize,
2131 start_offset: usize,
2132 start_after: usize,
2133 delete_len: usize,
2134 deleted_text: String,
2135 insert_text: String,
2136 insert_char_len: usize,
2137 }
2138
2139 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
2140
2141 for (selection_index, selection) in selections.iter().enumerate() {
2142 let (range_start_pos, range_end_pos) =
2143 crate::selection_set::selection_min_max(selection);
2144
2145 let start_offset = self.position_to_char_offset_clamped(range_start_pos);
2146 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
2147
2148 let delete_len = end_offset.saturating_sub(start_offset);
2149 let deleted_text = if delete_len == 0 {
2150 String::new()
2151 } else {
2152 self.editor.piece_table.get_range(start_offset, delete_len)
2153 };
2154
2155 let indent = if auto_indent {
2156 let line_text = self
2157 .editor
2158 .line_index
2159 .get_line_text(range_start_pos.line)
2160 .unwrap_or_default();
2161 Self::leading_whitespace_prefix(&line_text)
2162 } else {
2163 String::new()
2164 };
2165
2166 let insert_text = format!("\n{}", indent);
2167 let insert_char_len = insert_text.chars().count();
2168
2169 ops.push(Op {
2170 selection_index,
2171 start_offset,
2172 start_after: start_offset,
2173 delete_len,
2174 deleted_text,
2175 insert_text,
2176 insert_char_len,
2177 });
2178 }
2179
2180 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
2183 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
2184
2185 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
2186 let mut delta: i64 = 0;
2187 for &idx in &asc_indices {
2188 let op = &mut ops[idx];
2189 let effective_start = (op.start_offset as i64 + delta) as usize;
2190 op.start_after = effective_start;
2191 caret_offsets[op.selection_index] = effective_start + op.insert_char_len;
2192 delta += op.insert_char_len as i64 - op.delete_len as i64;
2193 }
2194
2195 let mut desc_indices = asc_indices;
2197 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
2198
2199 for &idx in &desc_indices {
2200 let op = &ops[idx];
2201
2202 if op.delete_len > 0 {
2203 self.editor
2204 .piece_table
2205 .delete(op.start_offset, op.delete_len);
2206 self.editor
2207 .interval_tree
2208 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
2209 for layer_tree in self.editor.style_layers.values_mut() {
2210 layer_tree
2211 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
2212 }
2213 }
2214
2215 if !op.insert_text.is_empty() {
2216 self.editor
2217 .piece_table
2218 .insert(op.start_offset, &op.insert_text);
2219 self.editor
2220 .interval_tree
2221 .update_for_insertion(op.start_offset, op.insert_char_len);
2222 for layer_tree in self.editor.style_layers.values_mut() {
2223 layer_tree.update_for_insertion(op.start_offset, op.insert_char_len);
2224 }
2225 }
2226 }
2227
2228 let updated_text = self.editor.piece_table.get_text();
2230 self.editor.line_index = LineIndex::from_text(&updated_text);
2231 self.rebuild_layout_engine_from_text(&updated_text);
2232
2233 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
2235 for offset in &caret_offsets {
2236 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
2237 let pos = Position::new(line, column);
2238 new_carets.push(Selection {
2239 start: pos,
2240 end: pos,
2241 direction: SelectionDirection::Forward,
2242 });
2243 }
2244
2245 let (new_carets, new_primary_index) =
2246 crate::selection_set::normalize_selections(new_carets, primary_index);
2247 let primary = new_carets
2248 .get(new_primary_index)
2249 .cloned()
2250 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
2251
2252 self.editor.cursor_position = primary.end;
2253 self.editor.selection = None;
2254 self.editor.secondary_selections = new_carets
2255 .into_iter()
2256 .enumerate()
2257 .filter_map(|(idx, sel)| {
2258 if idx == new_primary_index {
2259 None
2260 } else {
2261 Some(sel)
2262 }
2263 })
2264 .collect();
2265
2266 let after_selection = self.snapshot_selection_set();
2267
2268 let edits: Vec<TextEdit> = ops
2269 .into_iter()
2270 .map(|op| TextEdit {
2271 start_before: op.start_offset,
2272 start_after: op.start_after,
2273 deleted_text: op.deleted_text,
2274 inserted_text: op.insert_text,
2275 })
2276 .collect();
2277
2278 let mut delta_edits: Vec<TextDeltaEdit> = edits
2279 .iter()
2280 .map(|e| TextDeltaEdit {
2281 start: e.start_before,
2282 deleted_text: e.deleted_text.clone(),
2283 inserted_text: e.inserted_text.clone(),
2284 })
2285 .collect();
2286 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
2287
2288 let step = UndoStep {
2289 group_id: 0,
2290 edits,
2291 before_selection,
2292 after_selection,
2293 };
2294 let group_id = self.undo_redo.push_step(step, false);
2295
2296 self.last_text_delta = Some(TextDelta {
2297 before_char_count,
2298 after_char_count: self.editor.piece_table.char_count(),
2299 edits: delta_edits,
2300 undo_group_id: Some(group_id),
2301 });
2302
2303 Ok(CommandResult::Success)
2304 }
2305
2306 fn execute_indent_command(&mut self, outdent: bool) -> Result<CommandResult, CommandError> {
2307 self.undo_redo.end_group();
2308
2309 let before_char_count = self.editor.piece_table.char_count();
2310 let before_selection = self.snapshot_selection_set();
2311 let selections = before_selection.selections.clone();
2312
2313 let mut lines: Vec<usize> = Vec::new();
2314 for sel in &selections {
2315 let (min_pos, max_pos) = crate::selection_set::selection_min_max(sel);
2316 for line in min_pos.line..=max_pos.line {
2317 lines.push(line);
2318 }
2319 }
2320 lines.sort_unstable();
2321 lines.dedup();
2322
2323 if lines.is_empty() {
2324 return Ok(CommandResult::Success);
2325 }
2326
2327 let tab_width = self.editor.layout_engine.tab_width().max(1);
2328 let indent_unit = self.indent_unit();
2329 let indent_chars = indent_unit.chars().count();
2330
2331 #[derive(Debug)]
2332 struct Op {
2333 start_offset: usize,
2334 start_after: usize,
2335 delete_len: usize,
2336 deleted_text: String,
2337 insert_text: String,
2338 insert_len: usize,
2339 }
2340
2341 let mut ops: Vec<Op> = Vec::new();
2342 let mut line_deltas: std::collections::HashMap<usize, isize> =
2343 std::collections::HashMap::new();
2344
2345 for line in lines {
2346 if line >= self.editor.line_index.line_count() {
2347 continue;
2348 }
2349
2350 let start_offset = self.editor.line_index.position_to_char_offset(line, 0);
2351 let line_text = self
2352 .editor
2353 .line_index
2354 .get_line_text(line)
2355 .unwrap_or_default();
2356
2357 if outdent {
2358 let mut remove_len = 0usize;
2359 if let Some(first) = line_text.chars().next() {
2360 if first == '\t' {
2361 remove_len = 1;
2362 } else if first == ' ' {
2363 let leading_spaces = line_text.chars().take_while(|c| *c == ' ').count();
2364 remove_len = leading_spaces.min(tab_width);
2365 }
2366 }
2367
2368 if remove_len == 0 {
2369 continue;
2370 }
2371
2372 let deleted_text = self.editor.piece_table.get_range(start_offset, remove_len);
2373 ops.push(Op {
2374 start_offset,
2375 start_after: start_offset,
2376 delete_len: remove_len,
2377 deleted_text,
2378 insert_text: String::new(),
2379 insert_len: 0,
2380 });
2381 line_deltas.insert(line, -(remove_len as isize));
2382 } else {
2383 if indent_chars == 0 {
2384 continue;
2385 }
2386
2387 ops.push(Op {
2388 start_offset,
2389 start_after: start_offset,
2390 delete_len: 0,
2391 deleted_text: String::new(),
2392 insert_text: indent_unit.clone(),
2393 insert_len: indent_chars,
2394 });
2395 line_deltas.insert(line, indent_chars as isize);
2396 }
2397 }
2398
2399 if ops.is_empty() {
2400 return Ok(CommandResult::Success);
2401 }
2402
2403 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
2405 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
2406
2407 let mut delta: i64 = 0;
2408 for &idx in &asc_indices {
2409 let op = &mut ops[idx];
2410 let effective_start = (op.start_offset as i64 + delta) as usize;
2411 op.start_after = effective_start;
2412 delta += op.insert_len as i64 - op.delete_len as i64;
2413 }
2414
2415 let mut desc_indices = asc_indices;
2417 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
2418
2419 for &idx in &desc_indices {
2420 let op = &ops[idx];
2421
2422 if op.delete_len > 0 {
2423 self.editor
2424 .piece_table
2425 .delete(op.start_offset, op.delete_len);
2426 self.editor
2427 .interval_tree
2428 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
2429 for layer_tree in self.editor.style_layers.values_mut() {
2430 layer_tree
2431 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
2432 }
2433 }
2434
2435 if op.insert_len > 0 {
2436 self.editor
2437 .piece_table
2438 .insert(op.start_offset, &op.insert_text);
2439 self.editor
2440 .interval_tree
2441 .update_for_insertion(op.start_offset, op.insert_len);
2442 for layer_tree in self.editor.style_layers.values_mut() {
2443 layer_tree.update_for_insertion(op.start_offset, op.insert_len);
2444 }
2445 }
2446 }
2447
2448 let updated_text = self.editor.piece_table.get_text();
2450 self.editor.line_index = LineIndex::from_text(&updated_text);
2451 self.rebuild_layout_engine_from_text(&updated_text);
2452
2453 let line_index = &self.editor.line_index;
2455 let apply_delta = |pos: &mut Position, deltas: &std::collections::HashMap<usize, isize>| {
2456 let Some(delta) = deltas.get(&pos.line) else {
2457 return;
2458 };
2459
2460 let new_col = if *delta >= 0 {
2461 pos.column.saturating_add(*delta as usize)
2462 } else {
2463 pos.column.saturating_sub((-*delta) as usize)
2464 };
2465
2466 pos.column = Self::clamp_column_for_line_with_index(line_index, pos.line, new_col);
2467 };
2468
2469 apply_delta(&mut self.editor.cursor_position, &line_deltas);
2470 if let Some(sel) = &mut self.editor.selection {
2471 apply_delta(&mut sel.start, &line_deltas);
2472 apply_delta(&mut sel.end, &line_deltas);
2473 }
2474 for sel in &mut self.editor.secondary_selections {
2475 apply_delta(&mut sel.start, &line_deltas);
2476 apply_delta(&mut sel.end, &line_deltas);
2477 }
2478
2479 self.normalize_cursor_and_selection();
2480 self.preferred_x_cells = self
2481 .editor
2482 .logical_position_to_visual(
2483 self.editor.cursor_position.line,
2484 self.editor.cursor_position.column,
2485 )
2486 .map(|(_, x)| x);
2487
2488 let after_selection = self.snapshot_selection_set();
2489
2490 let edits: Vec<TextEdit> = ops
2491 .into_iter()
2492 .map(|op| TextEdit {
2493 start_before: op.start_offset,
2494 start_after: op.start_after,
2495 deleted_text: op.deleted_text,
2496 inserted_text: op.insert_text,
2497 })
2498 .collect();
2499
2500 let mut delta_edits: Vec<TextDeltaEdit> = edits
2501 .iter()
2502 .map(|e| TextDeltaEdit {
2503 start: e.start_before,
2504 deleted_text: e.deleted_text.clone(),
2505 inserted_text: e.inserted_text.clone(),
2506 })
2507 .collect();
2508 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
2509
2510 let step = UndoStep {
2511 group_id: 0,
2512 edits,
2513 before_selection,
2514 after_selection,
2515 };
2516 let group_id = self.undo_redo.push_step(step, false);
2517
2518 self.last_text_delta = Some(TextDelta {
2519 before_char_count,
2520 after_char_count: self.editor.piece_table.char_count(),
2521 edits: delta_edits,
2522 undo_group_id: Some(group_id),
2523 });
2524
2525 Ok(CommandResult::Success)
2526 }
2527
2528 fn execute_insert_command(
2529 &mut self,
2530 offset: usize,
2531 text: String,
2532 ) -> Result<CommandResult, CommandError> {
2533 if text.is_empty() {
2534 return Err(CommandError::EmptyText);
2535 }
2536
2537 let text = crate::text::normalize_crlf_to_lf_string(text);
2538 let max_offset = self.editor.piece_table.char_count();
2539 if offset > max_offset {
2540 return Err(CommandError::InvalidOffset(offset));
2541 }
2542
2543 let before_char_count = self.editor.piece_table.char_count();
2544 let before_selection = self.snapshot_selection_set();
2545
2546 let affected_line = self.editor.line_index.char_offset_to_position(offset).0;
2547 let inserts_newline = text.contains('\n');
2548
2549 self.editor.piece_table.insert(offset, &text);
2551
2552 let updated_text = self.editor.piece_table.get_text();
2554 self.editor.line_index = LineIndex::from_text(&updated_text);
2555
2556 if inserts_newline {
2558 self.rebuild_layout_engine_from_text(&updated_text);
2559 } else {
2560 let line_text = self
2561 .editor
2562 .line_index
2563 .get_line_text(affected_line)
2564 .unwrap_or_default();
2565 self.editor
2566 .layout_engine
2567 .update_line(affected_line, &line_text);
2568 }
2569
2570 let inserted_len = text.chars().count();
2571
2572 self.editor
2574 .interval_tree
2575 .update_for_insertion(offset, inserted_len);
2576 for layer_tree in self.editor.style_layers.values_mut() {
2577 layer_tree.update_for_insertion(offset, inserted_len);
2578 }
2579
2580 self.normalize_cursor_and_selection();
2582
2583 let after_selection = self.snapshot_selection_set();
2584
2585 let step = UndoStep {
2586 group_id: 0,
2587 edits: vec![TextEdit {
2588 start_before: offset,
2589 start_after: offset,
2590 deleted_text: String::new(),
2591 inserted_text: text.clone(),
2592 }],
2593 before_selection,
2594 after_selection,
2595 };
2596
2597 let coalescible_insert = !text.contains('\n');
2598 let group_id = self.undo_redo.push_step(step, coalescible_insert);
2599
2600 self.last_text_delta = Some(TextDelta {
2601 before_char_count,
2602 after_char_count: self.editor.piece_table.char_count(),
2603 edits: vec![TextDeltaEdit {
2604 start: offset,
2605 deleted_text: String::new(),
2606 inserted_text: text,
2607 }],
2608 undo_group_id: Some(group_id),
2609 });
2610
2611 Ok(CommandResult::Success)
2612 }
2613
2614 fn execute_delete_command(
2615 &mut self,
2616 start: usize,
2617 length: usize,
2618 ) -> Result<CommandResult, CommandError> {
2619 if length == 0 {
2620 return Ok(CommandResult::Success);
2621 }
2622
2623 let before_char_count = self.editor.piece_table.char_count();
2624 let max_offset = self.editor.piece_table.char_count();
2625 if start > max_offset {
2626 return Err(CommandError::InvalidOffset(start));
2627 }
2628 if start + length > max_offset {
2629 return Err(CommandError::InvalidRange {
2630 start,
2631 end: start + length,
2632 });
2633 }
2634
2635 let before_selection = self.snapshot_selection_set();
2636
2637 let deleted_text = self.editor.piece_table.get_range(start, length);
2638 let delta_deleted_text = deleted_text.clone();
2639 let deletes_newline = deleted_text.contains('\n');
2640 let affected_line = self.editor.line_index.char_offset_to_position(start).0;
2641
2642 self.editor.piece_table.delete(start, length);
2644
2645 let updated_text = self.editor.piece_table.get_text();
2647 self.editor.line_index = LineIndex::from_text(&updated_text);
2648
2649 if deletes_newline {
2651 self.rebuild_layout_engine_from_text(&updated_text);
2652 } else {
2653 let line_text = self
2654 .editor
2655 .line_index
2656 .get_line_text(affected_line)
2657 .unwrap_or_default();
2658 self.editor
2659 .layout_engine
2660 .update_line(affected_line, &line_text);
2661 }
2662
2663 self.editor
2665 .interval_tree
2666 .update_for_deletion(start, start + length);
2667 for layer_tree in self.editor.style_layers.values_mut() {
2668 layer_tree.update_for_deletion(start, start + length);
2669 }
2670
2671 self.normalize_cursor_and_selection();
2673
2674 let after_selection = self.snapshot_selection_set();
2675
2676 let step = UndoStep {
2677 group_id: 0,
2678 edits: vec![TextEdit {
2679 start_before: start,
2680 start_after: start,
2681 deleted_text,
2682 inserted_text: String::new(),
2683 }],
2684 before_selection,
2685 after_selection,
2686 };
2687 let group_id = self.undo_redo.push_step(step, false);
2688
2689 self.last_text_delta = Some(TextDelta {
2690 before_char_count,
2691 after_char_count: self.editor.piece_table.char_count(),
2692 edits: vec![TextDeltaEdit {
2693 start,
2694 deleted_text: delta_deleted_text,
2695 inserted_text: String::new(),
2696 }],
2697 undo_group_id: Some(group_id),
2698 });
2699
2700 Ok(CommandResult::Success)
2701 }
2702
2703 fn execute_replace_command(
2704 &mut self,
2705 start: usize,
2706 length: usize,
2707 text: String,
2708 ) -> Result<CommandResult, CommandError> {
2709 let before_char_count = self.editor.piece_table.char_count();
2710 let max_offset = self.editor.piece_table.char_count();
2711 if start > max_offset {
2712 return Err(CommandError::InvalidOffset(start));
2713 }
2714 if start + length > max_offset {
2715 return Err(CommandError::InvalidRange {
2716 start,
2717 end: start + length,
2718 });
2719 }
2720
2721 if length == 0 && text.is_empty() {
2722 return Ok(CommandResult::Success);
2723 }
2724
2725 let text = crate::text::normalize_crlf_to_lf_string(text);
2726 let before_selection = self.snapshot_selection_set();
2727
2728 let deleted_text = if length == 0 {
2729 String::new()
2730 } else {
2731 self.editor.piece_table.get_range(start, length)
2732 };
2733 let delta_deleted_text = deleted_text.clone();
2734 let delta_inserted_text = text.clone();
2735
2736 let affected_line = self.editor.line_index.char_offset_to_position(start).0;
2737 let replace_affects_layout = deleted_text.contains('\n') || text.contains('\n');
2738
2739 if length > 0 {
2741 self.editor.piece_table.delete(start, length);
2742 self.editor
2743 .interval_tree
2744 .update_for_deletion(start, start + length);
2745 for layer_tree in self.editor.style_layers.values_mut() {
2746 layer_tree.update_for_deletion(start, start + length);
2747 }
2748 }
2749
2750 let inserted_len = text.chars().count();
2751 if inserted_len > 0 {
2752 self.editor.piece_table.insert(start, &text);
2753 self.editor
2754 .interval_tree
2755 .update_for_insertion(start, inserted_len);
2756 for layer_tree in self.editor.style_layers.values_mut() {
2757 layer_tree.update_for_insertion(start, inserted_len);
2758 }
2759 }
2760
2761 let updated_text = self.editor.piece_table.get_text();
2763 self.editor.line_index = LineIndex::from_text(&updated_text);
2764
2765 if replace_affects_layout {
2766 self.rebuild_layout_engine_from_text(&updated_text);
2767 } else {
2768 let line_text = self
2769 .editor
2770 .line_index
2771 .get_line_text(affected_line)
2772 .unwrap_or_default();
2773 self.editor
2774 .layout_engine
2775 .update_line(affected_line, &line_text);
2776 }
2777
2778 self.normalize_cursor_and_selection();
2780
2781 let after_selection = self.snapshot_selection_set();
2782
2783 let step = UndoStep {
2784 group_id: 0,
2785 edits: vec![TextEdit {
2786 start_before: start,
2787 start_after: start,
2788 deleted_text,
2789 inserted_text: text,
2790 }],
2791 before_selection,
2792 after_selection,
2793 };
2794 let group_id = self.undo_redo.push_step(step, false);
2795
2796 self.last_text_delta = Some(TextDelta {
2797 before_char_count,
2798 after_char_count: self.editor.piece_table.char_count(),
2799 edits: vec![TextDeltaEdit {
2800 start,
2801 deleted_text: delta_deleted_text,
2802 inserted_text: delta_inserted_text,
2803 }],
2804 undo_group_id: Some(group_id),
2805 });
2806
2807 Ok(CommandResult::Success)
2808 }
2809
2810 fn cursor_char_offset(&self) -> usize {
2811 self.position_to_char_offset_clamped(self.editor.cursor_position)
2812 }
2813
2814 fn primary_selection_char_range(&self) -> Option<SearchMatch> {
2815 let selection = self.editor.selection.as_ref()?;
2816 let (min_pos, max_pos) = crate::selection_set::selection_min_max(selection);
2817 let start = self.position_to_char_offset_clamped(min_pos);
2818 let end = self.position_to_char_offset_clamped(max_pos);
2819 if start == end {
2820 None
2821 } else {
2822 Some(SearchMatch { start, end })
2823 }
2824 }
2825
2826 fn set_primary_selection_by_char_range(&mut self, range: SearchMatch) {
2827 let (start_line, start_col) = self.editor.line_index.char_offset_to_position(range.start);
2828 let (end_line, end_col) = self.editor.line_index.char_offset_to_position(range.end);
2829
2830 self.editor.cursor_position = Position::new(end_line, end_col);
2831 self.editor.secondary_selections.clear();
2832
2833 if range.start == range.end {
2834 self.editor.selection = None;
2835 } else {
2836 self.editor.selection = Some(Selection {
2837 start: Position::new(start_line, start_col),
2838 end: Position::new(end_line, end_col),
2839 direction: SelectionDirection::Forward,
2840 });
2841 }
2842 }
2843
2844 fn execute_find_command(
2845 &mut self,
2846 query: String,
2847 options: SearchOptions,
2848 forward: bool,
2849 ) -> Result<CommandResult, CommandError> {
2850 if query.is_empty() {
2851 return Ok(CommandResult::SearchNotFound);
2852 }
2853
2854 let text = self.editor.piece_table.get_text();
2855 let from = if let Some(selection) = self.primary_selection_char_range() {
2856 if forward {
2857 selection.end
2858 } else {
2859 selection.start
2860 }
2861 } else {
2862 self.cursor_char_offset()
2863 };
2864
2865 let found = if forward {
2866 find_next(&text, &query, options, from)
2867 } else {
2868 find_prev(&text, &query, options, from)
2869 }
2870 .map_err(|err| CommandError::Other(err.to_string()))?;
2871
2872 let Some(m) = found else {
2873 return Ok(CommandResult::SearchNotFound);
2874 };
2875
2876 self.set_primary_selection_by_char_range(m);
2877
2878 Ok(CommandResult::SearchMatch {
2879 start: m.start,
2880 end: m.end,
2881 })
2882 }
2883
2884 fn compile_user_regex(
2885 query: &str,
2886 options: SearchOptions,
2887 ) -> Result<regex::Regex, CommandError> {
2888 RegexBuilder::new(query)
2889 .case_insensitive(!options.case_sensitive)
2890 .multi_line(true)
2891 .build()
2892 .map_err(|err| CommandError::Other(format!("Invalid regex: {}", err)))
2893 }
2894
2895 fn regex_expand_replacement(
2896 re: ®ex::Regex,
2897 text: &str,
2898 index: &CharIndex,
2899 range: SearchMatch,
2900 replacement: &str,
2901 ) -> Result<String, CommandError> {
2902 let start_byte = index.char_to_byte(range.start);
2903 let end_byte = index.char_to_byte(range.end);
2904
2905 let caps = re
2906 .captures_at(text, start_byte)
2907 .ok_or_else(|| CommandError::Other("Regex match not found".to_string()))?;
2908 let whole = caps
2909 .get(0)
2910 .ok_or_else(|| CommandError::Other("Regex match missing capture 0".to_string()))?;
2911 if whole.start() != start_byte || whole.end() != end_byte {
2912 return Err(CommandError::Other(
2913 "Regex match did not align with the selected range".to_string(),
2914 ));
2915 }
2916
2917 let mut expanded = String::new();
2918 caps.expand(replacement, &mut expanded);
2919 Ok(expanded)
2920 }
2921
2922 fn execute_replace_current_command(
2923 &mut self,
2924 query: String,
2925 replacement: String,
2926 options: SearchOptions,
2927 ) -> Result<CommandResult, CommandError> {
2928 if query.is_empty() {
2929 return Err(CommandError::Other("Search query is empty".to_string()));
2930 }
2931
2932 let text = self.editor.piece_table.get_text();
2933 let selection_range = self.primary_selection_char_range();
2934
2935 let mut target = None::<SearchMatch>;
2936 if let Some(range) = selection_range {
2937 let is_match = crate::search::is_match_exact(&text, &query, options, range)
2938 .map_err(|err| CommandError::Other(err.to_string()))?;
2939 if is_match {
2940 target = Some(range);
2941 }
2942 }
2943
2944 if target.is_none() {
2945 let from = self.cursor_char_offset();
2946 target = find_next(&text, &query, options, from)
2947 .map_err(|err| CommandError::Other(err.to_string()))?;
2948 }
2949
2950 let Some(target) = target else {
2951 return Err(CommandError::Other("No match found".to_string()));
2952 };
2953
2954 let index = CharIndex::new(&text);
2955 let inserted_text = if options.regex {
2956 let re = Self::compile_user_regex(&query, options)?;
2957 Self::regex_expand_replacement(&re, &text, &index, target, &replacement)?
2958 } else {
2959 replacement
2960 };
2961 let inserted_text = crate::text::normalize_crlf_to_lf_string(inserted_text);
2962
2963 let deleted_text = self
2964 .editor
2965 .piece_table
2966 .get_range(target.start, target.len());
2967 let before_char_count = self.editor.piece_table.char_count();
2968 let delta_deleted_text = deleted_text.clone();
2969
2970 let before_selection = self.snapshot_selection_set();
2971 self.apply_text_ops(vec![(target.start, target.len(), inserted_text.as_str())])?;
2972
2973 let inserted_len = inserted_text.chars().count();
2974 let new_range = SearchMatch {
2975 start: target.start,
2976 end: target.start + inserted_len,
2977 };
2978 self.set_primary_selection_by_char_range(new_range);
2979 let after_selection = self.snapshot_selection_set();
2980
2981 let step = UndoStep {
2982 group_id: 0,
2983 edits: vec![TextEdit {
2984 start_before: target.start,
2985 start_after: target.start,
2986 deleted_text,
2987 inserted_text: inserted_text.clone(),
2988 }],
2989 before_selection,
2990 after_selection,
2991 };
2992 let group_id = self.undo_redo.push_step(step, false);
2993
2994 self.last_text_delta = Some(TextDelta {
2995 before_char_count,
2996 after_char_count: self.editor.piece_table.char_count(),
2997 edits: vec![TextDeltaEdit {
2998 start: target.start,
2999 deleted_text: delta_deleted_text,
3000 inserted_text,
3001 }],
3002 undo_group_id: Some(group_id),
3003 });
3004
3005 Ok(CommandResult::ReplaceResult { replaced: 1 })
3006 }
3007
3008 fn execute_replace_all_command(
3009 &mut self,
3010 query: String,
3011 replacement: String,
3012 options: SearchOptions,
3013 ) -> Result<CommandResult, CommandError> {
3014 if query.is_empty() {
3015 return Err(CommandError::Other("Search query is empty".to_string()));
3016 }
3017
3018 let replacement = crate::text::normalize_crlf_to_lf_string(replacement);
3019 let text = self.editor.piece_table.get_text();
3020 let matches =
3021 find_all(&text, &query, options).map_err(|err| CommandError::Other(err.to_string()))?;
3022 if matches.is_empty() {
3023 return Err(CommandError::Other("No match found".to_string()));
3024 }
3025 let match_count = matches.len();
3026
3027 let index = CharIndex::new(&text);
3028
3029 struct Op {
3030 start_before: usize,
3031 start_after: usize,
3032 delete_len: usize,
3033 deleted_text: String,
3034 inserted_text: String,
3035 inserted_len: usize,
3036 }
3037
3038 let mut ops: Vec<Op> = Vec::with_capacity(match_count);
3039 if options.regex {
3040 let re = Self::compile_user_regex(&query, options)?;
3041 for m in matches {
3042 let deleted_text = {
3043 let start_byte = index.char_to_byte(m.start);
3044 let end_byte = index.char_to_byte(m.end);
3045 text.get(start_byte..end_byte)
3046 .unwrap_or_default()
3047 .to_string()
3048 };
3049 let inserted_text =
3050 Self::regex_expand_replacement(&re, &text, &index, m, &replacement)?;
3051 let inserted_text = crate::text::normalize_crlf_to_lf_string(inserted_text);
3052 let inserted_len = inserted_text.chars().count();
3053 ops.push(Op {
3054 start_before: m.start,
3055 start_after: m.start,
3056 delete_len: m.len(),
3057 deleted_text,
3058 inserted_text,
3059 inserted_len,
3060 });
3061 }
3062 } else {
3063 let inserted_len = replacement.chars().count();
3064 for m in matches {
3065 let deleted_text = {
3066 let start_byte = index.char_to_byte(m.start);
3067 let end_byte = index.char_to_byte(m.end);
3068 text.get(start_byte..end_byte)
3069 .unwrap_or_default()
3070 .to_string()
3071 };
3072 ops.push(Op {
3073 start_before: m.start,
3074 start_after: m.start,
3075 delete_len: m.len(),
3076 deleted_text,
3077 inserted_text: replacement.clone(),
3078 inserted_len,
3079 });
3080 }
3081 }
3082
3083 ops.sort_by_key(|op| op.start_before);
3084
3085 let mut delta: i64 = 0;
3086 for op in &mut ops {
3087 let effective_start = op.start_before as i64 + delta;
3088 if effective_start < 0 {
3089 return Err(CommandError::Other(
3090 "ReplaceAll produced an invalid intermediate offset".to_string(),
3091 ));
3092 }
3093 op.start_after = effective_start as usize;
3094 delta += op.inserted_len as i64 - op.delete_len as i64;
3095 }
3096
3097 let before_char_count = self.editor.piece_table.char_count();
3098 let before_selection = self.snapshot_selection_set();
3099 let apply_ops: Vec<(usize, usize, &str)> = ops
3100 .iter()
3101 .map(|op| (op.start_before, op.delete_len, op.inserted_text.as_str()))
3102 .collect();
3103 self.apply_text_ops(apply_ops)?;
3104
3105 if let Some(first) = ops.first() {
3106 let caret_end = first.start_after + first.inserted_len;
3107 let select_end = if first.inserted_len == 0 {
3108 first.start_after
3109 } else {
3110 caret_end
3111 };
3112 self.set_primary_selection_by_char_range(SearchMatch {
3113 start: first.start_after,
3114 end: select_end,
3115 });
3116 } else {
3117 self.editor.selection = None;
3118 self.editor.secondary_selections.clear();
3119 }
3120
3121 let after_selection = self.snapshot_selection_set();
3122
3123 let edits: Vec<TextEdit> = ops
3124 .into_iter()
3125 .map(|op| TextEdit {
3126 start_before: op.start_before,
3127 start_after: op.start_after,
3128 deleted_text: op.deleted_text,
3129 inserted_text: op.inserted_text,
3130 })
3131 .collect();
3132
3133 let mut delta_edits: Vec<TextDeltaEdit> = edits
3134 .iter()
3135 .map(|e| TextDeltaEdit {
3136 start: e.start_before,
3137 deleted_text: e.deleted_text.clone(),
3138 inserted_text: e.inserted_text.clone(),
3139 })
3140 .collect();
3141 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
3142
3143 let step = UndoStep {
3144 group_id: 0,
3145 edits,
3146 before_selection,
3147 after_selection,
3148 };
3149 let group_id = self.undo_redo.push_step(step, false);
3150
3151 self.last_text_delta = Some(TextDelta {
3152 before_char_count,
3153 after_char_count: self.editor.piece_table.char_count(),
3154 edits: delta_edits,
3155 undo_group_id: Some(group_id),
3156 });
3157
3158 Ok(CommandResult::ReplaceResult {
3159 replaced: match_count,
3160 })
3161 }
3162
3163 fn execute_backspace_command(&mut self) -> Result<CommandResult, CommandError> {
3164 self.execute_delete_like_command(false)
3165 }
3166
3167 fn execute_delete_forward_command(&mut self) -> Result<CommandResult, CommandError> {
3168 self.execute_delete_like_command(true)
3169 }
3170
3171 fn execute_delete_to_prev_tab_stop_command(&mut self) -> Result<CommandResult, CommandError> {
3172 self.undo_redo.end_group();
3175
3176 let before_selection = self.snapshot_selection_set();
3177 let selections = before_selection.selections.clone();
3178 let primary_index = before_selection.primary_index;
3179
3180 let tab_width = self.editor.layout_engine.tab_width().max(1);
3181
3182 #[derive(Debug)]
3183 struct Op {
3184 selection_index: usize,
3185 start_offset: usize,
3186 delete_len: usize,
3187 deleted_text: String,
3188 start_after: usize,
3189 }
3190
3191 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
3192
3193 for (selection_index, selection) in selections.iter().enumerate() {
3194 let (range_start_pos, range_end_pos) = if selection.start <= selection.end {
3195 (selection.start, selection.end)
3196 } else {
3197 (selection.end, selection.start)
3198 };
3199
3200 let (start_offset, end_offset) = if range_start_pos != range_end_pos {
3201 let start_offset = self.position_to_char_offset_clamped(range_start_pos);
3202 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
3203 if start_offset <= end_offset {
3204 (start_offset, end_offset)
3205 } else {
3206 (end_offset, start_offset)
3207 }
3208 } else {
3209 let caret = selection.end;
3210 let caret_offset = self.position_to_char_offset_clamped(caret);
3211 if caret_offset == 0 {
3212 (0, 0)
3213 } else {
3214 let line_text = self
3215 .editor
3216 .line_index
3217 .get_line_text(caret.line)
3218 .unwrap_or_default();
3219 let line_char_len = line_text.chars().count();
3220 let col = caret.column.min(line_char_len);
3221
3222 let in_leading_whitespace = line_text
3223 .chars()
3224 .take(col)
3225 .all(|ch| ch == ' ' || ch == '\t');
3226
3227 if !in_leading_whitespace {
3228 (caret_offset - 1, caret_offset)
3229 } else {
3230 let x_in_line = visual_x_for_column(&line_text, col, tab_width);
3231 let back = if x_in_line == 0 {
3232 0
3233 } else {
3234 let rem = x_in_line % tab_width;
3235 if rem == 0 { tab_width } else { rem }
3236 };
3237 let target_x = x_in_line.saturating_sub(back);
3238
3239 let mut target_col = col;
3240 while target_col > 0 {
3241 let prev_col = target_col - 1;
3242 let prev_x = visual_x_for_column(&line_text, prev_col, tab_width);
3243 if prev_x < target_x {
3244 break;
3245 }
3246 target_col = prev_col;
3247 if prev_x == target_x {
3248 break;
3249 }
3250 }
3251
3252 let target_offset = self
3253 .editor
3254 .line_index
3255 .position_to_char_offset(caret.line, target_col);
3256 (target_offset, caret_offset)
3257 }
3258 }
3259 };
3260
3261 let delete_len = end_offset.saturating_sub(start_offset);
3262 let deleted_text = if delete_len == 0 {
3263 String::new()
3264 } else {
3265 self.editor.piece_table.get_range(start_offset, delete_len)
3266 };
3267
3268 ops.push(Op {
3269 selection_index,
3270 start_offset,
3271 delete_len,
3272 deleted_text,
3273 start_after: start_offset,
3274 });
3275 }
3276
3277 if !ops.iter().any(|op| op.delete_len > 0) {
3278 return Ok(CommandResult::Success);
3279 }
3280
3281 let before_char_count = self.editor.piece_table.char_count();
3282
3283 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
3285 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
3286
3287 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
3288 let mut delta: i64 = 0;
3289 for &idx in &asc_indices {
3290 let op = &mut ops[idx];
3291 let effective_start = (op.start_offset as i64 + delta) as usize;
3292 op.start_after = effective_start;
3293 caret_offsets[op.selection_index] = effective_start;
3294 delta -= op.delete_len as i64;
3295 }
3296
3297 let mut desc_indices = asc_indices;
3299 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
3300
3301 for &idx in &desc_indices {
3302 let op = &ops[idx];
3303 if op.delete_len == 0 {
3304 continue;
3305 }
3306
3307 self.editor
3308 .piece_table
3309 .delete(op.start_offset, op.delete_len);
3310 self.editor
3311 .interval_tree
3312 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
3313 for layer_tree in self.editor.style_layers.values_mut() {
3314 layer_tree.update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
3315 }
3316 }
3317
3318 let updated_text = self.editor.piece_table.get_text();
3320 self.editor.line_index = LineIndex::from_text(&updated_text);
3321 self.rebuild_layout_engine_from_text(&updated_text);
3322
3323 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
3325 for offset in &caret_offsets {
3326 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
3327 let pos = Position::new(line, column);
3328 new_carets.push(Selection {
3329 start: pos,
3330 end: pos,
3331 direction: SelectionDirection::Forward,
3332 });
3333 }
3334
3335 let (new_carets, new_primary_index) =
3336 crate::selection_set::normalize_selections(new_carets, primary_index);
3337 let primary = new_carets
3338 .get(new_primary_index)
3339 .cloned()
3340 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
3341
3342 self.editor.cursor_position = primary.end;
3343 self.editor.selection = None;
3344 self.editor.secondary_selections = new_carets
3345 .into_iter()
3346 .enumerate()
3347 .filter_map(|(idx, sel)| {
3348 if idx == new_primary_index {
3349 None
3350 } else {
3351 Some(sel)
3352 }
3353 })
3354 .collect();
3355
3356 let after_selection = self.snapshot_selection_set();
3357
3358 let edits: Vec<TextEdit> = ops
3359 .into_iter()
3360 .map(|op| TextEdit {
3361 start_before: op.start_offset,
3362 start_after: op.start_after,
3363 deleted_text: op.deleted_text,
3364 inserted_text: String::new(),
3365 })
3366 .collect();
3367
3368 let mut delta_edits: Vec<TextDeltaEdit> = edits
3369 .iter()
3370 .map(|e| TextDeltaEdit {
3371 start: e.start_before,
3372 deleted_text: e.deleted_text.clone(),
3373 inserted_text: e.inserted_text.clone(),
3374 })
3375 .collect();
3376 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
3377
3378 let step = UndoStep {
3379 group_id: 0,
3380 edits,
3381 before_selection,
3382 after_selection,
3383 };
3384 let group_id = self.undo_redo.push_step(step, false);
3385
3386 self.last_text_delta = Some(TextDelta {
3387 before_char_count,
3388 after_char_count: self.editor.piece_table.char_count(),
3389 edits: delta_edits,
3390 undo_group_id: Some(group_id),
3391 });
3392
3393 Ok(CommandResult::Success)
3394 }
3395
3396 fn execute_delete_by_boundary_command(
3397 &mut self,
3398 forward: bool,
3399 boundary: TextBoundary,
3400 ) -> Result<CommandResult, CommandError> {
3401 self.undo_redo.end_group();
3404
3405 let before_selection = self.snapshot_selection_set();
3406 let selections = before_selection.selections.clone();
3407 let primary_index = before_selection.primary_index;
3408
3409 let doc_char_count = self.editor.piece_table.char_count();
3410
3411 #[derive(Debug)]
3412 struct Op {
3413 selection_index: usize,
3414 start_offset: usize,
3415 delete_len: usize,
3416 deleted_text: String,
3417 start_after: usize,
3418 }
3419
3420 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
3421
3422 for (selection_index, selection) in selections.iter().enumerate() {
3423 let (range_start_pos, range_end_pos) = if selection.start <= selection.end {
3424 (selection.start, selection.end)
3425 } else {
3426 (selection.end, selection.start)
3427 };
3428
3429 let (start_offset, end_offset) = if range_start_pos != range_end_pos {
3430 let start_offset = self.position_to_char_offset_clamped(range_start_pos);
3431 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
3432 if start_offset <= end_offset {
3433 (start_offset, end_offset)
3434 } else {
3435 (end_offset, start_offset)
3436 }
3437 } else {
3438 let caret = selection.end;
3439 let caret_offset = self.position_to_char_offset_clamped(caret);
3440 let line_count = self.editor.line_index.line_count();
3441 let line = caret.line.min(line_count.saturating_sub(1));
3442 let line_text = self
3443 .editor
3444 .line_index
3445 .get_line_text(line)
3446 .unwrap_or_default();
3447 let line_char_len = line_text.chars().count();
3448 let col = caret.column.min(line_char_len);
3449
3450 if forward {
3451 if caret_offset >= doc_char_count {
3452 (caret_offset, caret_offset)
3453 } else if col >= line_char_len {
3454 (caret_offset, (caret_offset + 1).min(doc_char_count))
3455 } else {
3456 let next_col = next_boundary_column(&line_text, col, boundary);
3457 let start_offset =
3458 self.editor.line_index.position_to_char_offset(line, col);
3459 let end_offset = self
3460 .editor
3461 .line_index
3462 .position_to_char_offset(line, next_col);
3463 (start_offset, end_offset)
3464 }
3465 } else if caret_offset == 0 {
3466 (0, 0)
3467 } else if col == 0 {
3468 (caret_offset - 1, caret_offset)
3469 } else {
3470 let prev_col = prev_boundary_column(&line_text, col, boundary);
3471 let start_offset = self
3472 .editor
3473 .line_index
3474 .position_to_char_offset(line, prev_col);
3475 let end_offset = self.editor.line_index.position_to_char_offset(line, col);
3476 (start_offset, end_offset)
3477 }
3478 };
3479
3480 let delete_len = end_offset.saturating_sub(start_offset);
3481 let deleted_text = if delete_len == 0 {
3482 String::new()
3483 } else {
3484 self.editor.piece_table.get_range(start_offset, delete_len)
3485 };
3486
3487 ops.push(Op {
3488 selection_index,
3489 start_offset,
3490 delete_len,
3491 deleted_text,
3492 start_after: start_offset,
3493 });
3494 }
3495
3496 if !ops.iter().any(|op| op.delete_len > 0) {
3497 return Ok(CommandResult::Success);
3498 }
3499
3500 let before_char_count = self.editor.piece_table.char_count();
3501
3502 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
3504 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
3505
3506 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
3507 let mut delta: i64 = 0;
3508 for &idx in &asc_indices {
3509 let op = &mut ops[idx];
3510 let effective_start = (op.start_offset as i64 + delta) as usize;
3511 op.start_after = effective_start;
3512 caret_offsets[op.selection_index] = effective_start;
3513 delta -= op.delete_len as i64;
3514 }
3515
3516 let mut desc_indices = asc_indices;
3518 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
3519
3520 for &idx in &desc_indices {
3521 let op = &ops[idx];
3522 if op.delete_len == 0 {
3523 continue;
3524 }
3525
3526 self.editor
3527 .piece_table
3528 .delete(op.start_offset, op.delete_len);
3529 self.editor
3530 .interval_tree
3531 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
3532 for layer_tree in self.editor.style_layers.values_mut() {
3533 layer_tree.update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
3534 }
3535 }
3536
3537 let updated_text = self.editor.piece_table.get_text();
3539 self.editor.line_index = LineIndex::from_text(&updated_text);
3540 self.rebuild_layout_engine_from_text(&updated_text);
3541
3542 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
3544 for offset in &caret_offsets {
3545 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
3546 let pos = Position::new(line, column);
3547 new_carets.push(Selection {
3548 start: pos,
3549 end: pos,
3550 direction: SelectionDirection::Forward,
3551 });
3552 }
3553
3554 let (new_carets, new_primary_index) =
3555 crate::selection_set::normalize_selections(new_carets, primary_index);
3556 let primary = new_carets
3557 .get(new_primary_index)
3558 .cloned()
3559 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
3560
3561 self.editor.cursor_position = primary.end;
3562 self.editor.selection = None;
3563 self.editor.secondary_selections = new_carets
3564 .into_iter()
3565 .enumerate()
3566 .filter_map(|(idx, sel)| {
3567 if idx == new_primary_index {
3568 None
3569 } else {
3570 Some(sel)
3571 }
3572 })
3573 .collect();
3574
3575 let after_selection = self.snapshot_selection_set();
3576
3577 let edits: Vec<TextEdit> = ops
3578 .into_iter()
3579 .map(|op| TextEdit {
3580 start_before: op.start_offset,
3581 start_after: op.start_after,
3582 deleted_text: op.deleted_text,
3583 inserted_text: String::new(),
3584 })
3585 .collect();
3586
3587 let mut delta_edits: Vec<TextDeltaEdit> = edits
3588 .iter()
3589 .map(|e| TextDeltaEdit {
3590 start: e.start_before,
3591 deleted_text: e.deleted_text.clone(),
3592 inserted_text: e.inserted_text.clone(),
3593 })
3594 .collect();
3595 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
3596
3597 let step = UndoStep {
3598 group_id: 0,
3599 edits,
3600 before_selection,
3601 after_selection,
3602 };
3603 let group_id = self.undo_redo.push_step(step, false);
3604
3605 self.last_text_delta = Some(TextDelta {
3606 before_char_count,
3607 after_char_count: self.editor.piece_table.char_count(),
3608 edits: delta_edits,
3609 undo_group_id: Some(group_id),
3610 });
3611
3612 Ok(CommandResult::Success)
3613 }
3614
3615 fn execute_delete_like_command(
3616 &mut self,
3617 forward: bool,
3618 ) -> Result<CommandResult, CommandError> {
3619 self.undo_redo.end_group();
3622
3623 let before_selection = self.snapshot_selection_set();
3624 let selections = before_selection.selections.clone();
3625 let primary_index = before_selection.primary_index;
3626
3627 let doc_char_count = self.editor.piece_table.char_count();
3628
3629 #[derive(Debug)]
3630 struct Op {
3631 selection_index: usize,
3632 start_offset: usize,
3633 delete_len: usize,
3634 deleted_text: String,
3635 start_after: usize,
3636 }
3637
3638 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
3639
3640 for (selection_index, selection) in selections.iter().enumerate() {
3641 let (range_start_pos, range_end_pos) = if selection.start <= selection.end {
3642 (selection.start, selection.end)
3643 } else {
3644 (selection.end, selection.start)
3645 };
3646
3647 let (start_offset, end_offset) = if range_start_pos != range_end_pos {
3648 let start_offset = self.position_to_char_offset_clamped(range_start_pos);
3649 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
3650 if start_offset <= end_offset {
3651 (start_offset, end_offset)
3652 } else {
3653 (end_offset, start_offset)
3654 }
3655 } else {
3656 let caret_offset = self.position_to_char_offset_clamped(selection.end);
3657 if forward {
3658 if caret_offset >= doc_char_count {
3659 (caret_offset, caret_offset)
3660 } else {
3661 (caret_offset, (caret_offset + 1).min(doc_char_count))
3662 }
3663 } else if caret_offset == 0 {
3664 (0, 0)
3665 } else {
3666 (caret_offset - 1, caret_offset)
3667 }
3668 };
3669
3670 let delete_len = end_offset.saturating_sub(start_offset);
3671 let deleted_text = if delete_len == 0 {
3672 String::new()
3673 } else {
3674 self.editor.piece_table.get_range(start_offset, delete_len)
3675 };
3676
3677 ops.push(Op {
3678 selection_index,
3679 start_offset,
3680 delete_len,
3681 deleted_text,
3682 start_after: start_offset,
3683 });
3684 }
3685
3686 if !ops.iter().any(|op| op.delete_len > 0) {
3687 return Ok(CommandResult::Success);
3688 }
3689
3690 let before_char_count = self.editor.piece_table.char_count();
3691
3692 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
3694 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
3695
3696 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
3697 let mut delta: i64 = 0;
3698 for &idx in &asc_indices {
3699 let op = &mut ops[idx];
3700 let effective_start = (op.start_offset as i64 + delta) as usize;
3701 op.start_after = effective_start;
3702 caret_offsets[op.selection_index] = effective_start;
3703 delta -= op.delete_len as i64;
3704 }
3705
3706 let mut desc_indices = asc_indices;
3708 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
3709
3710 for &idx in &desc_indices {
3711 let op = &ops[idx];
3712 if op.delete_len == 0 {
3713 continue;
3714 }
3715
3716 self.editor
3717 .piece_table
3718 .delete(op.start_offset, op.delete_len);
3719 self.editor
3720 .interval_tree
3721 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
3722 for layer_tree in self.editor.style_layers.values_mut() {
3723 layer_tree.update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
3724 }
3725 }
3726
3727 let updated_text = self.editor.piece_table.get_text();
3729 self.editor.line_index = LineIndex::from_text(&updated_text);
3730 self.rebuild_layout_engine_from_text(&updated_text);
3731
3732 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
3734 for offset in &caret_offsets {
3735 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
3736 let pos = Position::new(line, column);
3737 new_carets.push(Selection {
3738 start: pos,
3739 end: pos,
3740 direction: SelectionDirection::Forward,
3741 });
3742 }
3743
3744 let (new_carets, new_primary_index) =
3745 crate::selection_set::normalize_selections(new_carets, primary_index);
3746 let primary = new_carets
3747 .get(new_primary_index)
3748 .cloned()
3749 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
3750
3751 self.editor.cursor_position = primary.end;
3752 self.editor.selection = None;
3753 self.editor.secondary_selections = new_carets
3754 .into_iter()
3755 .enumerate()
3756 .filter_map(|(idx, sel)| {
3757 if idx == new_primary_index {
3758 None
3759 } else {
3760 Some(sel)
3761 }
3762 })
3763 .collect();
3764
3765 let after_selection = self.snapshot_selection_set();
3766
3767 let edits: Vec<TextEdit> = ops
3768 .into_iter()
3769 .map(|op| TextEdit {
3770 start_before: op.start_offset,
3771 start_after: op.start_after,
3772 deleted_text: op.deleted_text,
3773 inserted_text: String::new(),
3774 })
3775 .collect();
3776
3777 let mut delta_edits: Vec<TextDeltaEdit> = edits
3778 .iter()
3779 .map(|e| TextDeltaEdit {
3780 start: e.start_before,
3781 deleted_text: e.deleted_text.clone(),
3782 inserted_text: String::new(),
3783 })
3784 .collect();
3785 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
3786
3787 let step = UndoStep {
3788 group_id: 0,
3789 edits,
3790 before_selection,
3791 after_selection,
3792 };
3793 let group_id = self.undo_redo.push_step(step, false);
3794
3795 self.last_text_delta = Some(TextDelta {
3796 before_char_count,
3797 after_char_count: self.editor.piece_table.char_count(),
3798 edits: delta_edits,
3799 undo_group_id: Some(group_id),
3800 });
3801
3802 Ok(CommandResult::Success)
3803 }
3804
3805 fn snapshot_selection_set(&self) -> SelectionSetSnapshot {
3806 let mut selections: Vec<Selection> =
3807 Vec::with_capacity(1 + self.editor.secondary_selections.len());
3808
3809 let primary = self.editor.selection.clone().unwrap_or(Selection {
3810 start: self.editor.cursor_position,
3811 end: self.editor.cursor_position,
3812 direction: SelectionDirection::Forward,
3813 });
3814 selections.push(primary);
3815 selections.extend(self.editor.secondary_selections.iter().cloned());
3816
3817 let (selections, primary_index) = crate::selection_set::normalize_selections(selections, 0);
3818 SelectionSetSnapshot {
3819 selections,
3820 primary_index,
3821 }
3822 }
3823
3824 fn restore_selection_set(&mut self, snapshot: SelectionSetSnapshot) {
3825 if snapshot.selections.is_empty() {
3826 self.editor.cursor_position = Position::new(0, 0);
3827 self.editor.selection = None;
3828 self.editor.secondary_selections.clear();
3829 return;
3830 }
3831
3832 let primary = snapshot
3833 .selections
3834 .get(snapshot.primary_index)
3835 .cloned()
3836 .unwrap_or_else(|| snapshot.selections[0].clone());
3837
3838 self.editor.cursor_position = primary.end;
3839 self.editor.selection = if primary.start == primary.end {
3840 None
3841 } else {
3842 Some(primary.clone())
3843 };
3844
3845 self.editor.secondary_selections = snapshot
3846 .selections
3847 .into_iter()
3848 .enumerate()
3849 .filter_map(|(idx, sel)| {
3850 if idx == snapshot.primary_index {
3851 None
3852 } else {
3853 Some(sel)
3854 }
3855 })
3856 .collect();
3857
3858 self.normalize_cursor_and_selection();
3859 }
3860
3861 fn apply_undo_edits(&mut self, edits: &[TextEdit]) -> Result<(), CommandError> {
3862 let mut ops: Vec<(usize, usize, &str)> = Vec::with_capacity(edits.len());
3864 for edit in edits {
3865 let start = edit.start_after;
3866 let delete_len = edit.inserted_len();
3867 let insert_text = edit.deleted_text.as_str();
3868 ops.push((start, delete_len, insert_text));
3869 }
3870 self.apply_text_ops(ops)
3871 }
3872
3873 fn apply_redo_edits(&mut self, edits: &[TextEdit]) -> Result<(), CommandError> {
3874 let mut ops: Vec<(usize, usize, &str)> = Vec::with_capacity(edits.len());
3875 for edit in edits {
3876 let start = edit.start_before;
3877 let delete_len = edit.deleted_len();
3878 let insert_text = edit.inserted_text.as_str();
3879 ops.push((start, delete_len, insert_text));
3880 }
3881 self.apply_text_ops(ops)
3882 }
3883
3884 fn apply_text_ops(&mut self, mut ops: Vec<(usize, usize, &str)>) -> Result<(), CommandError> {
3885 ops.sort_by_key(|(start, _, _)| std::cmp::Reverse(*start));
3887
3888 for (start, delete_len, insert_text) in ops {
3889 let max_offset = self.editor.piece_table.char_count();
3890 if start > max_offset {
3891 return Err(CommandError::InvalidOffset(start));
3892 }
3893 if start + delete_len > max_offset {
3894 return Err(CommandError::InvalidRange {
3895 start,
3896 end: start + delete_len,
3897 });
3898 }
3899
3900 if delete_len > 0 {
3901 self.editor.piece_table.delete(start, delete_len);
3902 self.editor
3903 .interval_tree
3904 .update_for_deletion(start, start + delete_len);
3905 for layer_tree in self.editor.style_layers.values_mut() {
3906 layer_tree.update_for_deletion(start, start + delete_len);
3907 }
3908 }
3909
3910 let insert_len = insert_text.chars().count();
3911 if insert_len > 0 {
3912 self.editor.piece_table.insert(start, insert_text);
3913 self.editor
3914 .interval_tree
3915 .update_for_insertion(start, insert_len);
3916 for layer_tree in self.editor.style_layers.values_mut() {
3917 layer_tree.update_for_insertion(start, insert_len);
3918 }
3919 }
3920 }
3921
3922 let updated_text = self.editor.piece_table.get_text();
3924 self.editor.line_index = LineIndex::from_text(&updated_text);
3925 self.rebuild_layout_engine_from_text(&updated_text);
3926 self.normalize_cursor_and_selection();
3927
3928 Ok(())
3929 }
3930
3931 fn execute_cursor(&mut self, command: CursorCommand) -> Result<CommandResult, CommandError> {
3933 match command {
3934 CursorCommand::MoveTo { line, column } => {
3935 if line >= self.editor.line_index.line_count() {
3936 return Err(CommandError::InvalidPosition { line, column });
3937 }
3938
3939 let clamped_column = self.clamp_column_for_line(line, column);
3940 self.editor.cursor_position = Position::new(line, clamped_column);
3941 self.preferred_x_cells = self
3942 .editor
3943 .logical_position_to_visual(line, clamped_column)
3944 .map(|(_, x)| x);
3945 self.editor.secondary_selections.clear();
3947 Ok(CommandResult::Success)
3948 }
3949 CursorCommand::MoveBy {
3950 delta_line,
3951 delta_column,
3952 } => {
3953 let new_line = if delta_line >= 0 {
3954 self.editor.cursor_position.line + delta_line as usize
3955 } else {
3956 self.editor
3957 .cursor_position
3958 .line
3959 .saturating_sub((-delta_line) as usize)
3960 };
3961
3962 let new_column = if delta_column >= 0 {
3963 self.editor.cursor_position.column + delta_column as usize
3964 } else {
3965 self.editor
3966 .cursor_position
3967 .column
3968 .saturating_sub((-delta_column) as usize)
3969 };
3970
3971 if new_line >= self.editor.line_index.line_count() {
3972 return Err(CommandError::InvalidPosition {
3973 line: new_line,
3974 column: new_column,
3975 });
3976 }
3977
3978 let clamped_column = self.clamp_column_for_line(new_line, new_column);
3979 self.editor.cursor_position = Position::new(new_line, clamped_column);
3980 self.preferred_x_cells = self
3981 .editor
3982 .logical_position_to_visual(new_line, clamped_column)
3983 .map(|(_, x)| x);
3984 Ok(CommandResult::Success)
3985 }
3986 CursorCommand::MoveGraphemeLeft => {
3987 let line_count = self.editor.line_index.line_count();
3988 if line_count == 0 {
3989 return Ok(CommandResult::Success);
3990 }
3991
3992 let mut line = self
3993 .editor
3994 .cursor_position
3995 .line
3996 .min(line_count.saturating_sub(1));
3997 let mut line_text = self
3998 .editor
3999 .line_index
4000 .get_line_text(line)
4001 .unwrap_or_default();
4002 let mut line_char_len = line_text.chars().count();
4003 let mut col = self.editor.cursor_position.column.min(line_char_len);
4004
4005 if col == 0 {
4006 if line == 0 {
4007 return Ok(CommandResult::Success);
4008 }
4009 line = line.saturating_sub(1);
4010 line_text = self
4011 .editor
4012 .line_index
4013 .get_line_text(line)
4014 .unwrap_or_default();
4015 line_char_len = line_text.chars().count();
4016 col = line_char_len;
4017 } else {
4018 col = prev_boundary_column(&line_text, col, TextBoundary::Grapheme);
4019 }
4020
4021 self.editor.cursor_position = Position::new(line, col);
4022 self.preferred_x_cells = self
4023 .editor
4024 .logical_position_to_visual(line, col)
4025 .map(|(_, x)| x);
4026 Ok(CommandResult::Success)
4027 }
4028 CursorCommand::MoveGraphemeRight => {
4029 let line_count = self.editor.line_index.line_count();
4030 if line_count == 0 {
4031 return Ok(CommandResult::Success);
4032 }
4033
4034 let line = self
4035 .editor
4036 .cursor_position
4037 .line
4038 .min(line_count.saturating_sub(1));
4039 let line_text = self
4040 .editor
4041 .line_index
4042 .get_line_text(line)
4043 .unwrap_or_default();
4044 let line_char_len = line_text.chars().count();
4045 let col = self.editor.cursor_position.column.min(line_char_len);
4046
4047 let (line, col) = if col >= line_char_len {
4048 if line + 1 >= line_count {
4049 return Ok(CommandResult::Success);
4050 }
4051 (line + 1, 0)
4052 } else {
4053 (
4054 line,
4055 next_boundary_column(&line_text, col, TextBoundary::Grapheme),
4056 )
4057 };
4058
4059 self.editor.cursor_position = Position::new(line, col);
4060 self.preferred_x_cells = self
4061 .editor
4062 .logical_position_to_visual(line, col)
4063 .map(|(_, x)| x);
4064 Ok(CommandResult::Success)
4065 }
4066 CursorCommand::MoveWordLeft => {
4067 let line_count = self.editor.line_index.line_count();
4068 if line_count == 0 {
4069 return Ok(CommandResult::Success);
4070 }
4071
4072 let mut line = self
4073 .editor
4074 .cursor_position
4075 .line
4076 .min(line_count.saturating_sub(1));
4077 let mut line_text = self
4078 .editor
4079 .line_index
4080 .get_line_text(line)
4081 .unwrap_or_default();
4082 let mut line_char_len = line_text.chars().count();
4083 let mut col = self.editor.cursor_position.column.min(line_char_len);
4084
4085 if col == 0 {
4086 if line == 0 {
4087 return Ok(CommandResult::Success);
4088 }
4089 line = line.saturating_sub(1);
4090 line_text = self
4091 .editor
4092 .line_index
4093 .get_line_text(line)
4094 .unwrap_or_default();
4095 line_char_len = line_text.chars().count();
4096 col = line_char_len;
4097 } else {
4098 col = prev_boundary_column(&line_text, col, TextBoundary::Word);
4099 }
4100
4101 self.editor.cursor_position = Position::new(line, col);
4102 self.preferred_x_cells = self
4103 .editor
4104 .logical_position_to_visual(line, col)
4105 .map(|(_, x)| x);
4106 Ok(CommandResult::Success)
4107 }
4108 CursorCommand::MoveWordRight => {
4109 let line_count = self.editor.line_index.line_count();
4110 if line_count == 0 {
4111 return Ok(CommandResult::Success);
4112 }
4113
4114 let line = self
4115 .editor
4116 .cursor_position
4117 .line
4118 .min(line_count.saturating_sub(1));
4119 let line_text = self
4120 .editor
4121 .line_index
4122 .get_line_text(line)
4123 .unwrap_or_default();
4124 let line_char_len = line_text.chars().count();
4125 let col = self.editor.cursor_position.column.min(line_char_len);
4126
4127 let (line, col) = if col >= line_char_len {
4128 if line + 1 >= line_count {
4129 return Ok(CommandResult::Success);
4130 }
4131 (line + 1, 0)
4132 } else {
4133 (
4134 line,
4135 next_boundary_column(&line_text, col, TextBoundary::Word),
4136 )
4137 };
4138
4139 self.editor.cursor_position = Position::new(line, col);
4140 self.preferred_x_cells = self
4141 .editor
4142 .logical_position_to_visual(line, col)
4143 .map(|(_, x)| x);
4144 Ok(CommandResult::Success)
4145 }
4146 CursorCommand::MoveVisualBy { delta_rows } => {
4147 let Some((current_row, current_x)) = self.editor.logical_position_to_visual(
4148 self.editor.cursor_position.line,
4149 self.editor.cursor_position.column,
4150 ) else {
4151 return Ok(CommandResult::Success);
4152 };
4153
4154 let preferred_x = self.preferred_x_cells.unwrap_or(current_x);
4155 self.preferred_x_cells = Some(preferred_x);
4156
4157 let total_visual = self.editor.visual_line_count();
4158 if total_visual == 0 {
4159 return Ok(CommandResult::Success);
4160 }
4161
4162 let target_row = if delta_rows >= 0 {
4163 current_row.saturating_add(delta_rows as usize)
4164 } else {
4165 current_row.saturating_sub((-delta_rows) as usize)
4166 }
4167 .min(total_visual.saturating_sub(1));
4168
4169 let Some(pos) = self
4170 .editor
4171 .visual_position_to_logical(target_row, preferred_x)
4172 else {
4173 return Ok(CommandResult::Success);
4174 };
4175
4176 self.editor.cursor_position = pos;
4177 Ok(CommandResult::Success)
4178 }
4179 CursorCommand::MoveToVisual { row, x_cells } => {
4180 let Some(pos) = self.editor.visual_position_to_logical(row, x_cells) else {
4181 return Ok(CommandResult::Success);
4182 };
4183
4184 self.editor.cursor_position = pos;
4185 self.preferred_x_cells = Some(x_cells);
4186 self.editor.secondary_selections.clear();
4188 Ok(CommandResult::Success)
4189 }
4190 CursorCommand::MoveToLineStart => {
4191 let line = self.editor.cursor_position.line;
4192 self.editor.cursor_position = Position::new(line, 0);
4193 self.preferred_x_cells = Some(0);
4194 self.editor.secondary_selections.clear();
4195 Ok(CommandResult::Success)
4196 }
4197 CursorCommand::MoveToLineEnd => {
4198 let line = self.editor.cursor_position.line;
4199 let end_col = self.clamp_column_for_line(line, usize::MAX);
4200 self.editor.cursor_position = Position::new(line, end_col);
4201 self.preferred_x_cells = self
4202 .editor
4203 .logical_position_to_visual(line, end_col)
4204 .map(|(_, x)| x);
4205 self.editor.secondary_selections.clear();
4206 Ok(CommandResult::Success)
4207 }
4208 CursorCommand::MoveToVisualLineStart => {
4209 let line = self.editor.cursor_position.line;
4210 let Some(layout) = self.editor.layout_engine.get_line_layout(line) else {
4211 return Ok(CommandResult::Success);
4212 };
4213
4214 let line_text = self
4215 .editor
4216 .line_index
4217 .get_line_text(line)
4218 .unwrap_or_default();
4219 let line_char_len = line_text.chars().count();
4220 let column = self.editor.cursor_position.column.min(line_char_len);
4221
4222 let mut seg_start = 0usize;
4223 for wp in &layout.wrap_points {
4224 if column >= wp.char_index {
4225 seg_start = wp.char_index;
4226 } else {
4227 break;
4228 }
4229 }
4230
4231 self.editor.cursor_position = Position::new(line, seg_start);
4232 self.preferred_x_cells = self
4233 .editor
4234 .logical_position_to_visual(line, seg_start)
4235 .map(|(_, x)| x);
4236 self.editor.secondary_selections.clear();
4237 Ok(CommandResult::Success)
4238 }
4239 CursorCommand::MoveToVisualLineEnd => {
4240 let line = self.editor.cursor_position.line;
4241 let Some(layout) = self.editor.layout_engine.get_line_layout(line) else {
4242 return Ok(CommandResult::Success);
4243 };
4244
4245 let line_text = self
4246 .editor
4247 .line_index
4248 .get_line_text(line)
4249 .unwrap_or_default();
4250 let line_char_len = line_text.chars().count();
4251 let column = self.editor.cursor_position.column.min(line_char_len);
4252
4253 let mut seg_end = line_char_len;
4254 for wp in &layout.wrap_points {
4255 if column < wp.char_index {
4256 seg_end = wp.char_index;
4257 break;
4258 }
4259 }
4260
4261 self.editor.cursor_position = Position::new(line, seg_end);
4262 self.preferred_x_cells = self
4263 .editor
4264 .logical_position_to_visual(line, seg_end)
4265 .map(|(_, x)| x);
4266 self.editor.secondary_selections.clear();
4267 Ok(CommandResult::Success)
4268 }
4269 CursorCommand::SetSelection { start, end } => {
4270 if start.line >= self.editor.line_index.line_count()
4271 || end.line >= self.editor.line_index.line_count()
4272 {
4273 return Err(CommandError::InvalidPosition {
4274 line: start.line.max(end.line),
4275 column: start.column.max(end.column),
4276 });
4277 }
4278
4279 let start = Position::new(
4280 start.line,
4281 self.clamp_column_for_line(start.line, start.column),
4282 );
4283 let end = Position::new(end.line, self.clamp_column_for_line(end.line, end.column));
4284
4285 let direction = if start.line < end.line
4286 || (start.line == end.line && start.column <= end.column)
4287 {
4288 SelectionDirection::Forward
4289 } else {
4290 SelectionDirection::Backward
4291 };
4292
4293 self.editor.selection = Some(Selection {
4294 start,
4295 end,
4296 direction,
4297 });
4298 Ok(CommandResult::Success)
4299 }
4300 CursorCommand::ExtendSelection { to } => {
4301 if to.line >= self.editor.line_index.line_count() {
4302 return Err(CommandError::InvalidPosition {
4303 line: to.line,
4304 column: to.column,
4305 });
4306 }
4307
4308 let to = Position::new(to.line, self.clamp_column_for_line(to.line, to.column));
4309
4310 if let Some(ref mut selection) = self.editor.selection {
4311 selection.end = to;
4312 selection.direction = if selection.start.line < to.line
4313 || (selection.start.line == to.line && selection.start.column <= to.column)
4314 {
4315 SelectionDirection::Forward
4316 } else {
4317 SelectionDirection::Backward
4318 };
4319 } else {
4320 self.editor.selection = Some(Selection {
4322 start: self.editor.cursor_position,
4323 end: to,
4324 direction: if self.editor.cursor_position.line < to.line
4325 || (self.editor.cursor_position.line == to.line
4326 && self.editor.cursor_position.column <= to.column)
4327 {
4328 SelectionDirection::Forward
4329 } else {
4330 SelectionDirection::Backward
4331 },
4332 });
4333 }
4334 Ok(CommandResult::Success)
4335 }
4336 CursorCommand::ClearSelection => {
4337 self.editor.selection = None;
4338 Ok(CommandResult::Success)
4339 }
4340 CursorCommand::SetSelections {
4341 selections,
4342 primary_index,
4343 } => {
4344 let line_count = self.editor.line_index.line_count();
4345 if selections.is_empty() {
4346 return Err(CommandError::Other(
4347 "SetSelections requires a non-empty selection list".to_string(),
4348 ));
4349 }
4350 if primary_index >= selections.len() {
4351 return Err(CommandError::Other(format!(
4352 "Invalid primary_index {} for {} selections",
4353 primary_index,
4354 selections.len()
4355 )));
4356 }
4357
4358 for sel in &selections {
4359 if sel.start.line >= line_count || sel.end.line >= line_count {
4360 return Err(CommandError::InvalidPosition {
4361 line: sel.start.line.max(sel.end.line),
4362 column: sel.start.column.max(sel.end.column),
4363 });
4364 }
4365 }
4366
4367 let (selections, primary_index) =
4368 crate::selection_set::normalize_selections(selections, primary_index);
4369
4370 let primary = selections
4371 .get(primary_index)
4372 .cloned()
4373 .ok_or_else(|| CommandError::Other("Invalid primary selection".to_string()))?;
4374
4375 self.editor.cursor_position = primary.end;
4376 self.editor.selection = if primary.start == primary.end {
4377 None
4378 } else {
4379 Some(primary.clone())
4380 };
4381
4382 self.editor.secondary_selections = selections
4383 .into_iter()
4384 .enumerate()
4385 .filter_map(|(idx, sel)| {
4386 if idx == primary_index {
4387 None
4388 } else {
4389 Some(sel)
4390 }
4391 })
4392 .collect();
4393
4394 Ok(CommandResult::Success)
4395 }
4396 CursorCommand::ClearSecondarySelections => {
4397 self.editor.secondary_selections.clear();
4398 Ok(CommandResult::Success)
4399 }
4400 CursorCommand::SetRectSelection { anchor, active } => {
4401 let line_count = self.editor.line_index.line_count();
4402 if anchor.line >= line_count || active.line >= line_count {
4403 return Err(CommandError::InvalidPosition {
4404 line: anchor.line.max(active.line),
4405 column: anchor.column.max(active.column),
4406 });
4407 }
4408
4409 let (selections, primary_index) =
4410 crate::selection_set::rect_selections(anchor, active);
4411
4412 self.execute_cursor(CursorCommand::SetSelections {
4414 selections,
4415 primary_index,
4416 })?;
4417 Ok(CommandResult::Success)
4418 }
4419 CursorCommand::FindNext { query, options } => {
4420 self.execute_find_command(query, options, true)
4421 }
4422 CursorCommand::FindPrev { query, options } => {
4423 self.execute_find_command(query, options, false)
4424 }
4425 }
4426 }
4427
4428 fn execute_view(&mut self, command: ViewCommand) -> Result<CommandResult, CommandError> {
4430 match command {
4431 ViewCommand::SetViewportWidth { width } => {
4432 if width == 0 {
4433 return Err(CommandError::Other(
4434 "Viewport width must be greater than 0".to_string(),
4435 ));
4436 }
4437
4438 self.editor.viewport_width = width;
4439 self.editor.layout_engine.set_viewport_width(width);
4440 Ok(CommandResult::Success)
4441 }
4442 ViewCommand::SetWrapMode { mode } => {
4443 self.editor.layout_engine.set_wrap_mode(mode);
4444 Ok(CommandResult::Success)
4445 }
4446 ViewCommand::SetWrapIndent { indent } => {
4447 self.editor.layout_engine.set_wrap_indent(indent);
4448 Ok(CommandResult::Success)
4449 }
4450 ViewCommand::SetTabWidth { width } => {
4451 if width == 0 {
4452 return Err(CommandError::Other(
4453 "Tab width must be greater than 0".to_string(),
4454 ));
4455 }
4456
4457 self.editor.layout_engine.set_tab_width(width);
4458 Ok(CommandResult::Success)
4459 }
4460 ViewCommand::SetTabKeyBehavior { behavior } => {
4461 self.tab_key_behavior = behavior;
4462 Ok(CommandResult::Success)
4463 }
4464 ViewCommand::ScrollTo { line } => {
4465 if line >= self.editor.line_index.line_count() {
4466 return Err(CommandError::InvalidPosition { line, column: 0 });
4467 }
4468
4469 Ok(CommandResult::Success)
4472 }
4473 ViewCommand::GetViewport { start_row, count } => {
4474 let text = self.editor.piece_table.get_text();
4475 let generator = SnapshotGenerator::from_text_with_layout_options(
4476 &text,
4477 self.editor.viewport_width,
4478 self.editor.layout_engine.tab_width(),
4479 self.editor.layout_engine.wrap_mode(),
4480 self.editor.layout_engine.wrap_indent(),
4481 );
4482 let grid = generator.get_headless_grid(start_row, count);
4483 Ok(CommandResult::Viewport(grid))
4484 }
4485 }
4486 }
4487
4488 fn execute_style(&mut self, command: StyleCommand) -> Result<CommandResult, CommandError> {
4490 match command {
4491 StyleCommand::AddStyle {
4492 start,
4493 end,
4494 style_id,
4495 } => {
4496 if start >= end {
4497 return Err(CommandError::InvalidRange { start, end });
4498 }
4499
4500 let interval = crate::intervals::Interval::new(start, end, style_id);
4501 self.editor.interval_tree.insert(interval);
4502 Ok(CommandResult::Success)
4503 }
4504 StyleCommand::RemoveStyle {
4505 start,
4506 end,
4507 style_id,
4508 } => {
4509 self.editor.interval_tree.remove(start, end, style_id);
4510 Ok(CommandResult::Success)
4511 }
4512 StyleCommand::Fold {
4513 start_line,
4514 end_line,
4515 } => {
4516 if start_line >= end_line {
4517 return Err(CommandError::InvalidRange {
4518 start: start_line,
4519 end: end_line,
4520 });
4521 }
4522
4523 let mut region = crate::intervals::FoldRegion::new(start_line, end_line);
4524 region.collapse();
4525 self.editor.folding_manager.add_region(region);
4526 Ok(CommandResult::Success)
4527 }
4528 StyleCommand::Unfold { start_line } => {
4529 self.editor.folding_manager.expand_line(start_line);
4530 Ok(CommandResult::Success)
4531 }
4532 StyleCommand::UnfoldAll => {
4533 self.editor.folding_manager.expand_all();
4534 Ok(CommandResult::Success)
4535 }
4536 }
4537 }
4538
4539 fn rebuild_layout_engine_from_text(&mut self, text: &str) {
4540 let lines = crate::text::split_lines_preserve_trailing(text);
4541 let line_refs: Vec<&str> = lines.iter().map(|s| s.as_str()).collect();
4542 self.editor.layout_engine.from_lines(&line_refs);
4543 }
4544
4545 fn position_to_char_offset_clamped(&self, pos: Position) -> usize {
4546 let line_count = self.editor.line_index.line_count();
4547 if line_count == 0 {
4548 return 0;
4549 }
4550
4551 let line = pos.line.min(line_count.saturating_sub(1));
4552 let line_text = self
4553 .editor
4554 .line_index
4555 .get_line_text(line)
4556 .unwrap_or_default();
4557 let line_char_len = line_text.chars().count();
4558 let column = pos.column.min(line_char_len);
4559 self.editor.line_index.position_to_char_offset(line, column)
4560 }
4561
4562 fn position_to_char_offset_and_virtual_pad(&self, pos: Position) -> (usize, usize) {
4563 let line_count = self.editor.line_index.line_count();
4564 if line_count == 0 {
4565 return (0, 0);
4566 }
4567
4568 let line = pos.line.min(line_count.saturating_sub(1));
4569 let line_text = self
4570 .editor
4571 .line_index
4572 .get_line_text(line)
4573 .unwrap_or_default();
4574 let line_char_len = line_text.chars().count();
4575 let clamped_col = pos.column.min(line_char_len);
4576 let offset = self
4577 .editor
4578 .line_index
4579 .position_to_char_offset(line, clamped_col);
4580 let pad = pos.column.saturating_sub(clamped_col);
4581 (offset, pad)
4582 }
4583
4584 fn normalize_cursor_and_selection(&mut self) {
4585 let line_index = &self.editor.line_index;
4586 let line_count = line_index.line_count();
4587 if line_count == 0 {
4588 self.editor.cursor_position = Position::new(0, 0);
4589 self.editor.selection = None;
4590 self.editor.secondary_selections.clear();
4591 return;
4592 }
4593
4594 self.editor.cursor_position =
4595 Self::clamp_position_lenient_with_index(line_index, self.editor.cursor_position);
4596
4597 if let Some(ref mut selection) = self.editor.selection {
4598 selection.start = Self::clamp_position_lenient_with_index(line_index, selection.start);
4599 selection.end = Self::clamp_position_lenient_with_index(line_index, selection.end);
4600 selection.direction = if selection.start.line < selection.end.line
4601 || (selection.start.line == selection.end.line
4602 && selection.start.column <= selection.end.column)
4603 {
4604 SelectionDirection::Forward
4605 } else {
4606 SelectionDirection::Backward
4607 };
4608 }
4609
4610 for selection in &mut self.editor.secondary_selections {
4611 selection.start = Self::clamp_position_lenient_with_index(line_index, selection.start);
4612 selection.end = Self::clamp_position_lenient_with_index(line_index, selection.end);
4613 selection.direction = if selection.start.line < selection.end.line
4614 || (selection.start.line == selection.end.line
4615 && selection.start.column <= selection.end.column)
4616 {
4617 SelectionDirection::Forward
4618 } else {
4619 SelectionDirection::Backward
4620 };
4621 }
4622 }
4623
4624 fn clamp_column_for_line(&self, line: usize, column: usize) -> usize {
4625 Self::clamp_column_for_line_with_index(&self.editor.line_index, line, column)
4626 }
4627
4628 fn clamp_position_lenient_with_index(line_index: &LineIndex, pos: Position) -> Position {
4629 let line_count = line_index.line_count();
4630 if line_count == 0 {
4631 return Position::new(0, 0);
4632 }
4633
4634 let clamped_line = pos.line.min(line_count.saturating_sub(1));
4635 Position::new(clamped_line, pos.column)
4637 }
4638
4639 fn clamp_column_for_line_with_index(
4640 line_index: &LineIndex,
4641 line: usize,
4642 column: usize,
4643 ) -> usize {
4644 let line_start = line_index.position_to_char_offset(line, 0);
4645 let line_end = line_index.position_to_char_offset(line, usize::MAX);
4646 let line_len = line_end.saturating_sub(line_start);
4647 column.min(line_len)
4648 }
4649}
4650
4651#[cfg(test)]
4652mod tests {
4653 use super::*;
4654
4655 #[test]
4656 fn test_edit_insert() {
4657 let mut executor = CommandExecutor::new("Hello", 80);
4658
4659 let result = executor.execute(Command::Edit(EditCommand::Insert {
4660 offset: 5,
4661 text: " World".to_string(),
4662 }));
4663
4664 assert!(result.is_ok());
4665 assert_eq!(executor.editor().get_text(), "Hello World");
4666 }
4667
4668 #[test]
4669 fn test_edit_delete() {
4670 let mut executor = CommandExecutor::new("Hello World", 80);
4671
4672 let result = executor.execute(Command::Edit(EditCommand::Delete {
4673 start: 5,
4674 length: 6,
4675 }));
4676
4677 assert!(result.is_ok());
4678 assert_eq!(executor.editor().get_text(), "Hello");
4679 }
4680
4681 #[test]
4682 fn test_edit_replace() {
4683 let mut executor = CommandExecutor::new("Hello World", 80);
4684
4685 let result = executor.execute(Command::Edit(EditCommand::Replace {
4686 start: 6,
4687 length: 5,
4688 text: "Rust".to_string(),
4689 }));
4690
4691 assert!(result.is_ok());
4692 assert_eq!(executor.editor().get_text(), "Hello Rust");
4693 }
4694
4695 #[test]
4696 fn test_cursor_move_to() {
4697 let mut executor = CommandExecutor::new("Line 1\nLine 2\nLine 3", 80);
4698
4699 let result = executor.execute(Command::Cursor(CursorCommand::MoveTo {
4700 line: 1,
4701 column: 3,
4702 }));
4703
4704 assert!(result.is_ok());
4705 assert_eq!(executor.editor().cursor_position(), Position::new(1, 3));
4706 }
4707
4708 #[test]
4709 fn test_cursor_selection() {
4710 let mut executor = CommandExecutor::new("Hello World", 80);
4711
4712 let result = executor.execute(Command::Cursor(CursorCommand::SetSelection {
4713 start: Position::new(0, 0),
4714 end: Position::new(0, 5),
4715 }));
4716
4717 assert!(result.is_ok());
4718 assert!(executor.editor().selection().is_some());
4719 }
4720
4721 #[test]
4722 fn test_view_set_width() {
4723 let mut executor = CommandExecutor::new("Test", 80);
4724
4725 let result = executor.execute(Command::View(ViewCommand::SetViewportWidth { width: 40 }));
4726
4727 assert!(result.is_ok());
4728 assert_eq!(executor.editor().viewport_width, 40);
4729 }
4730
4731 #[test]
4732 fn test_style_add_remove() {
4733 let mut executor = CommandExecutor::new("Hello World", 80);
4734
4735 let result = executor.execute(Command::Style(StyleCommand::AddStyle {
4737 start: 0,
4738 end: 5,
4739 style_id: 1,
4740 }));
4741 assert!(result.is_ok());
4742
4743 let result = executor.execute(Command::Style(StyleCommand::RemoveStyle {
4745 start: 0,
4746 end: 5,
4747 style_id: 1,
4748 }));
4749 assert!(result.is_ok());
4750 }
4751
4752 #[test]
4753 fn test_batch_execution() {
4754 let mut executor = CommandExecutor::new("", 80);
4755
4756 let commands = vec![
4757 Command::Edit(EditCommand::Insert {
4758 offset: 0,
4759 text: "Hello".to_string(),
4760 }),
4761 Command::Edit(EditCommand::Insert {
4762 offset: 5,
4763 text: " World".to_string(),
4764 }),
4765 ];
4766
4767 let results = executor.execute_batch(commands);
4768 assert!(results.is_ok());
4769 assert_eq!(executor.editor().get_text(), "Hello World");
4770 }
4771
4772 #[test]
4773 fn test_error_invalid_offset() {
4774 let mut executor = CommandExecutor::new("Hello", 80);
4775
4776 let result = executor.execute(Command::Edit(EditCommand::Insert {
4777 offset: 100,
4778 text: "X".to_string(),
4779 }));
4780
4781 assert!(result.is_err());
4782 assert!(matches!(
4783 result.unwrap_err(),
4784 CommandError::InvalidOffset(_)
4785 ));
4786 }
4787}