1use crate::decorations::{Decoration, DecorationLayerId, DecorationPlacement};
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::{
47 Cell, ComposedCell, ComposedCellSource, ComposedGrid, ComposedLine, ComposedLineKind,
48 HeadlessGrid, HeadlessLine, MinimapGrid, MinimapLine,
49};
50use crate::{
51 FOLD_PLACEHOLDER_STYLE_ID, FoldingManager, IntervalTree, LayoutEngine, LineIndex, PieceTable,
52};
53use editor_core_lang::CommentConfig;
54use regex::RegexBuilder;
55use std::cell::RefCell;
56use std::cmp::Ordering;
57use std::collections::{BTreeMap, HashMap};
58use unicode_segmentation::UnicodeSegmentation;
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub struct Position {
63 pub line: usize,
65 pub column: usize,
67}
68
69impl Position {
70 pub fn new(line: usize, column: usize) -> Self {
72 Self { line, column }
73 }
74}
75
76impl Ord for Position {
77 fn cmp(&self, other: &Self) -> Ordering {
78 self.line
79 .cmp(&other.line)
80 .then_with(|| self.column.cmp(&other.column))
81 }
82}
83
84impl PartialOrd for Position {
85 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
86 Some(self.cmp(other))
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct Selection {
93 pub start: Position,
95 pub end: Position,
97 pub direction: SelectionDirection,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum SelectionDirection {
104 Forward,
106 Backward,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum TabKeyBehavior {
113 Tab,
115 Spaces,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
124pub struct TextEditSpec {
125 pub start: usize,
127 pub end: usize,
129 pub text: String,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
135pub enum EditCommand {
136 Insert {
138 offset: usize,
140 text: String,
142 },
143 Delete {
145 start: usize,
147 length: usize,
149 },
150 Replace {
152 start: usize,
154 length: usize,
156 text: String,
158 },
159 InsertText {
161 text: String,
163 },
164 InsertTab,
169 InsertNewline {
174 auto_indent: bool,
176 },
177 Indent,
179 Outdent,
181 DuplicateLines,
186 DeleteLines,
191 MoveLinesUp,
196 MoveLinesDown,
201 JoinLines,
205 SplitLine,
209 ToggleComment {
212 config: CommentConfig,
214 },
215 ApplyTextEdits {
220 edits: Vec<TextEditSpec>,
222 },
223 DeleteToPrevTabStop,
227 DeleteGraphemeBack,
229 DeleteGraphemeForward,
231 DeleteWordBack,
233 DeleteWordForward,
235 Backspace,
237 DeleteForward,
239 Undo,
241 Redo,
243 EndUndoGroup,
245 ReplaceCurrent {
250 query: String,
252 replacement: String,
254 options: SearchOptions,
256 },
257 ReplaceAll {
262 query: String,
264 replacement: String,
266 options: SearchOptions,
268 },
269}
270
271#[derive(Debug, Clone, PartialEq, Eq)]
273pub enum CursorCommand {
274 MoveTo {
276 line: usize,
278 column: usize,
280 },
281 MoveBy {
283 delta_line: isize,
285 delta_column: isize,
287 },
288 MoveVisualBy {
293 delta_rows: isize,
295 },
296 MoveToVisual {
298 row: usize,
300 x_cells: usize,
302 },
303 MoveToLineStart,
305 MoveToLineEnd,
307 MoveToVisualLineStart,
309 MoveToVisualLineEnd,
311 MoveGraphemeLeft,
313 MoveGraphemeRight,
315 MoveWordLeft,
317 MoveWordRight,
319 SetSelection {
321 start: Position,
323 end: Position,
325 },
326 ExtendSelection {
328 to: Position,
330 },
331 ClearSelection,
333 SetSelections {
335 selections: Vec<Selection>,
337 primary_index: usize,
339 },
340 ClearSecondarySelections,
342 SetRectSelection {
344 anchor: Position,
346 active: Position,
348 },
349 SelectLine,
351 SelectWord,
353 ExpandSelection,
358 AddCursorAbove,
360 AddCursorBelow,
362 AddNextOccurrence {
364 options: SearchOptions,
366 },
367 AddAllOccurrences {
369 options: SearchOptions,
371 },
372 FindNext {
374 query: String,
376 options: SearchOptions,
378 },
379 FindPrev {
381 query: String,
383 options: SearchOptions,
385 },
386}
387
388#[derive(Debug, Clone, PartialEq, Eq)]
390pub enum ViewCommand {
391 SetViewportWidth {
393 width: usize,
395 },
396 SetWrapMode {
398 mode: WrapMode,
400 },
401 SetWrapIndent {
403 indent: WrapIndent,
405 },
406 SetTabWidth {
408 width: usize,
410 },
411 SetTabKeyBehavior {
413 behavior: TabKeyBehavior,
415 },
416 ScrollTo {
418 line: usize,
420 },
421 GetViewport {
423 start_row: usize,
425 count: usize,
427 },
428}
429
430#[derive(Debug, Clone, PartialEq, Eq)]
432pub enum StyleCommand {
433 AddStyle {
435 start: usize,
437 end: usize,
439 style_id: StyleId,
441 },
442 RemoveStyle {
444 start: usize,
446 end: usize,
448 style_id: StyleId,
450 },
451 Fold {
453 start_line: usize,
455 end_line: usize,
457 },
458 Unfold {
460 start_line: usize,
462 },
463 UnfoldAll,
465}
466
467#[derive(Debug, Clone, PartialEq, Eq)]
469pub enum Command {
470 Edit(EditCommand),
472 Cursor(CursorCommand),
474 View(ViewCommand),
476 Style(StyleCommand),
478}
479
480#[derive(Debug, Clone)]
482pub enum CommandResult {
483 Success,
485 Text(String),
487 Position(Position),
489 Offset(usize),
491 Viewport(HeadlessGrid),
493 SearchMatch {
495 start: usize,
497 end: usize,
499 },
500 SearchNotFound,
502 ReplaceResult {
504 replaced: usize,
506 },
507}
508
509#[derive(Debug, Clone, PartialEq, Eq)]
511pub enum CommandError {
512 InvalidOffset(usize),
514 InvalidPosition {
516 line: usize,
518 column: usize,
520 },
521 InvalidRange {
523 start: usize,
525 end: usize,
527 },
528 EmptyText,
530 Other(String),
532}
533
534impl std::fmt::Display for CommandError {
535 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
536 match self {
537 CommandError::InvalidOffset(offset) => {
538 write!(f, "Invalid offset: {}", offset)
539 }
540 CommandError::InvalidPosition { line, column } => {
541 write!(f, "Invalid position: line {}, column {}", line, column)
542 }
543 CommandError::InvalidRange { start, end } => {
544 write!(f, "Invalid range: {}..{}", start, end)
545 }
546 CommandError::EmptyText => {
547 write!(f, "Text cannot be empty")
548 }
549 CommandError::Other(msg) => {
550 write!(f, "{}", msg)
551 }
552 }
553 }
554}
555
556impl std::error::Error for CommandError {}
557
558#[derive(Debug, Clone)]
559struct SelectionSetSnapshot {
560 selections: Vec<Selection>,
561 primary_index: usize,
562}
563
564#[derive(Debug, Clone, Copy, PartialEq, Eq)]
565enum TextBoundary {
566 Grapheme,
567 Word,
568}
569
570fn byte_offset_for_char_column(text: &str, column: usize) -> usize {
571 if column == 0 {
572 return 0;
573 }
574
575 text.char_indices()
576 .nth(column)
577 .map(|(byte, _)| byte)
578 .unwrap_or_else(|| text.len())
579}
580
581fn char_column_for_byte_offset(text: &str, byte_offset: usize) -> usize {
582 text.get(..byte_offset).unwrap_or(text).chars().count()
583}
584
585fn prev_boundary_column(text: &str, column: usize, boundary: TextBoundary) -> usize {
586 let byte_pos = byte_offset_for_char_column(text, column);
587
588 let mut prev = 0usize;
589 match boundary {
590 TextBoundary::Grapheme => {
591 for (b, _) in text.grapheme_indices(true) {
592 if b >= byte_pos {
593 break;
594 }
595 prev = b;
596 }
597 }
598 TextBoundary::Word => {
599 for (b, _) in text.split_word_bound_indices() {
600 if b >= byte_pos {
601 break;
602 }
603 prev = b;
604 }
605 }
606 }
607
608 char_column_for_byte_offset(text, prev)
609}
610
611fn next_boundary_column(text: &str, column: usize, boundary: TextBoundary) -> usize {
612 let byte_pos = byte_offset_for_char_column(text, column);
613
614 let mut next = text.len();
615 match boundary {
616 TextBoundary::Grapheme => {
617 for (b, _) in text.grapheme_indices(true) {
618 if b > byte_pos {
619 next = b;
620 break;
621 }
622 }
623 }
624 TextBoundary::Word => {
625 for (b, _) in text.split_word_bound_indices() {
626 if b > byte_pos {
627 next = b;
628 break;
629 }
630 }
631 }
632 }
633
634 char_column_for_byte_offset(text, next)
635}
636
637#[derive(Debug, Clone)]
638struct TextEdit {
639 start_before: usize,
640 start_after: usize,
641 deleted_text: String,
642 inserted_text: String,
643}
644
645impl TextEdit {
646 fn deleted_len(&self) -> usize {
647 self.deleted_text.chars().count()
648 }
649
650 fn inserted_len(&self) -> usize {
651 self.inserted_text.chars().count()
652 }
653}
654
655#[derive(Debug, Clone)]
656struct UndoStep {
657 group_id: usize,
658 edits: Vec<TextEdit>,
659 before_selection: SelectionSetSnapshot,
660 after_selection: SelectionSetSnapshot,
661}
662
663#[derive(Debug)]
664struct UndoRedoManager {
665 undo_stack: Vec<UndoStep>,
666 redo_stack: Vec<UndoStep>,
667 max_undo: usize,
668 clean_index: Option<usize>,
671 next_group_id: usize,
672 open_group_id: Option<usize>,
673}
674
675impl UndoRedoManager {
676 fn new(max_undo: usize) -> Self {
677 Self {
678 undo_stack: Vec::new(),
679 redo_stack: Vec::new(),
680 max_undo,
681 clean_index: Some(0),
682 next_group_id: 0,
683 open_group_id: None,
684 }
685 }
686
687 fn can_undo(&self) -> bool {
688 !self.undo_stack.is_empty()
689 }
690
691 fn can_redo(&self) -> bool {
692 !self.redo_stack.is_empty()
693 }
694
695 fn undo_depth(&self) -> usize {
696 self.undo_stack.len()
697 }
698
699 fn redo_depth(&self) -> usize {
700 self.redo_stack.len()
701 }
702
703 fn current_group_id(&self) -> Option<usize> {
704 self.open_group_id
705 }
706
707 fn is_clean(&self) -> bool {
708 self.clean_index == Some(self.undo_stack.len())
709 }
710
711 fn mark_clean(&mut self) {
712 self.clean_index = Some(self.undo_stack.len());
713 self.end_group();
714 }
715
716 fn end_group(&mut self) {
717 self.open_group_id = None;
718 }
719
720 fn clear_redo_and_adjust_clean(&mut self) {
721 if self.redo_stack.is_empty() {
722 return;
723 }
724
725 if let Some(clean_index) = self.clean_index
727 && clean_index > self.undo_stack.len()
728 {
729 self.clean_index = None;
730 }
731
732 self.redo_stack.clear();
733 }
734
735 fn push_step(&mut self, mut step: UndoStep, coalescible_insert: bool) -> usize {
736 self.clear_redo_and_adjust_clean();
737
738 if self.undo_stack.len() >= self.max_undo {
739 self.undo_stack.remove(0);
740 if let Some(clean_index) = self.clean_index {
741 if clean_index == 0 {
742 self.clean_index = None;
743 } else {
744 self.clean_index = Some(clean_index - 1);
745 }
746 }
747 }
748
749 let reuse_open_group = coalescible_insert
750 && self.open_group_id.is_some()
751 && self.clean_index != Some(self.undo_stack.len());
752
753 if reuse_open_group {
754 step.group_id = self.open_group_id.expect("checked");
755 } else {
756 step.group_id = self.next_group_id;
757 self.next_group_id = self.next_group_id.wrapping_add(1);
758 }
759
760 if coalescible_insert {
761 self.open_group_id = Some(step.group_id);
762 } else {
763 self.open_group_id = None;
764 }
765
766 let group_id = step.group_id;
767 self.undo_stack.push(step);
768 group_id
769 }
770
771 fn pop_undo_group(&mut self) -> Option<Vec<UndoStep>> {
772 let last_group_id = self.undo_stack.last().map(|s| s.group_id)?;
773 let mut steps: Vec<UndoStep> = Vec::new();
774
775 while let Some(step) = self.undo_stack.last() {
776 if step.group_id != last_group_id {
777 break;
778 }
779 steps.push(self.undo_stack.pop().expect("checked"));
780 }
781
782 Some(steps)
783 }
784
785 fn pop_redo_group(&mut self) -> Option<Vec<UndoStep>> {
786 let last_group_id = self.redo_stack.last().map(|s| s.group_id)?;
787 let mut steps: Vec<UndoStep> = Vec::new();
788
789 while let Some(step) = self.redo_stack.last() {
790 if step.group_id != last_group_id {
791 break;
792 }
793 steps.push(self.redo_stack.pop().expect("checked"));
794 }
795
796 Some(steps)
797 }
798}
799
800#[derive(Debug, Clone, Copy)]
801struct VisualRowSpan {
802 logical_line: usize,
803 start_visual_row: usize,
804 visual_line_count: usize,
805}
806
807#[derive(Debug, Clone, Default)]
808struct VisualRowIndex {
809 spans: Vec<VisualRowSpan>,
810 total_visual_lines: usize,
811}
812
813impl VisualRowIndex {
814 fn total_visual_lines(&self) -> usize {
815 self.total_visual_lines
816 }
817
818 fn span_index_for_visual_row(&self, visual_row: usize) -> Option<usize> {
819 let idx = self
820 .spans
821 .partition_point(|span| span.start_visual_row + span.visual_line_count <= visual_row);
822 (idx < self.spans.len()).then_some(idx)
823 }
824
825 fn span_for_visual_row(&self, visual_row: usize) -> Option<(VisualRowSpan, usize)> {
826 let idx = self.span_index_for_visual_row(visual_row)?;
827 let span = self.spans[idx];
828 Some((span, visual_row.saturating_sub(span.start_visual_row)))
829 }
830
831 fn span_for_logical_line(&self, logical_line: usize) -> Option<VisualRowSpan> {
832 let idx = self
833 .spans
834 .binary_search_by_key(&logical_line, |span| span.logical_line)
835 .ok()?;
836 Some(self.spans[idx])
837 }
838}
839
840pub struct EditorCore {
861 pub piece_table: PieceTable,
863 pub line_index: LineIndex,
865 pub layout_engine: LayoutEngine,
867 pub interval_tree: IntervalTree,
869 pub style_layers: BTreeMap<StyleLayerId, IntervalTree>,
871 pub diagnostics: Vec<Diagnostic>,
873 pub decorations: BTreeMap<DecorationLayerId, Vec<Decoration>>,
875 pub document_symbols: crate::DocumentOutline,
877 pub folding_manager: FoldingManager,
879 pub cursor_position: Position,
881 pub selection: Option<Selection>,
883 pub secondary_selections: Vec<Selection>,
885 pub viewport_width: usize,
887 visual_row_index_cache: RefCell<Option<VisualRowIndex>>,
888}
889
890impl EditorCore {
891 pub fn new(text: &str, viewport_width: usize) -> Self {
893 let normalized = crate::text::normalize_crlf_to_lf(text);
894 let text = normalized.as_ref();
895
896 let piece_table = PieceTable::new(text);
897 let line_index = LineIndex::from_text(text);
898 let mut layout_engine = LayoutEngine::new(viewport_width);
899
900 let lines = crate::text::split_lines_preserve_trailing(text);
902 let line_refs: Vec<&str> = lines.iter().map(|s| s.as_str()).collect();
903 layout_engine.from_lines(&line_refs);
904
905 Self {
906 piece_table,
907 line_index,
908 layout_engine,
909 interval_tree: IntervalTree::new(),
910 style_layers: BTreeMap::new(),
911 diagnostics: Vec::new(),
912 decorations: BTreeMap::new(),
913 document_symbols: crate::DocumentOutline::default(),
914 folding_manager: FoldingManager::new(),
915 cursor_position: Position::new(0, 0),
916 selection: None,
917 secondary_selections: Vec::new(),
918 viewport_width,
919 visual_row_index_cache: RefCell::new(None),
920 }
921 }
922
923 pub fn empty(viewport_width: usize) -> Self {
925 Self::new("", viewport_width)
926 }
927
928 pub fn get_text(&self) -> String {
930 self.piece_table.get_text()
931 }
932
933 pub fn line_count(&self) -> usize {
935 self.line_index.line_count()
936 }
937
938 pub fn char_count(&self) -> usize {
940 self.piece_table.char_count()
941 }
942
943 pub fn cursor_position(&self) -> Position {
945 self.cursor_position
946 }
947
948 pub fn selection(&self) -> Option<&Selection> {
950 self.selection.as_ref()
951 }
952
953 pub fn secondary_selections(&self) -> &[Selection] {
955 &self.secondary_selections
956 }
957
958 pub fn diagnostics(&self) -> &[Diagnostic] {
960 &self.diagnostics
961 }
962
963 pub fn decorations_for_layer(&self, layer: DecorationLayerId) -> &[Decoration] {
965 self.decorations
966 .get(&layer)
967 .map(Vec::as_slice)
968 .unwrap_or(&[])
969 }
970
971 pub fn invalidate_visual_row_index_cache(&mut self) {
973 *self.visual_row_index_cache.borrow_mut() = None;
974 }
975
976 fn with_visual_row_index<R>(&self, f: impl FnOnce(&VisualRowIndex) -> R) -> R {
977 if self.visual_row_index_cache.borrow().is_none() {
978 let index = self.build_visual_row_index();
979 *self.visual_row_index_cache.borrow_mut() = Some(index);
980 }
981 let cache = self.visual_row_index_cache.borrow();
982 let index = cache
983 .as_ref()
984 .expect("visual-row cache should be initialized");
985 f(index)
986 }
987
988 fn build_visual_row_index(&self) -> VisualRowIndex {
989 let regions = self.folding_manager.regions();
990 let mut spans = Vec::new();
991 let mut total_visual = 0usize;
992
993 for logical_line in 0..self.layout_engine.logical_line_count() {
994 if Self::is_logical_line_hidden(regions, logical_line) {
995 continue;
996 }
997
998 let visual_line_count = self
999 .layout_engine
1000 .get_line_layout(logical_line)
1001 .map(|l| l.visual_line_count)
1002 .unwrap_or(1)
1003 .max(1);
1004
1005 spans.push(VisualRowSpan {
1006 logical_line,
1007 start_visual_row: total_visual,
1008 visual_line_count,
1009 });
1010 total_visual = total_visual.saturating_add(visual_line_count);
1011 }
1012
1013 VisualRowIndex {
1014 spans,
1015 total_visual_lines: total_visual,
1016 }
1017 }
1018
1019 pub fn get_headless_grid_styled(&self, start_visual_row: usize, count: usize) -> HeadlessGrid {
1027 self.with_visual_row_index(|index| {
1028 let mut grid = HeadlessGrid::new(start_visual_row, count);
1029 if count == 0 {
1030 return grid;
1031 }
1032
1033 let total_visual = index.total_visual_lines();
1034 if start_visual_row >= total_visual {
1035 return grid;
1036 }
1037
1038 let tab_width = self.layout_engine.tab_width();
1039 let end_visual = start_visual_row.saturating_add(count).min(total_visual);
1040 let regions = self.folding_manager.regions();
1041
1042 let Some(mut span_idx) = index.span_index_for_visual_row(start_visual_row) else {
1043 return grid;
1044 };
1045 let mut current_visual = start_visual_row;
1046 let mut visual_in_line =
1047 start_visual_row.saturating_sub(index.spans[span_idx].start_visual_row);
1048
1049 while current_visual < end_visual && span_idx < index.spans.len() {
1050 let span = index.spans[span_idx];
1051 let logical_line = span.logical_line;
1052
1053 let Some(layout) = self.layout_engine.get_line_layout(logical_line) else {
1054 let remaining_in_span = span.visual_line_count.saturating_sub(visual_in_line);
1055 current_visual = current_visual.saturating_add(remaining_in_span);
1056 span_idx = span_idx.saturating_add(1);
1057 visual_in_line = 0;
1058 continue;
1059 };
1060
1061 let line_text = self
1062 .line_index
1063 .get_line_text(logical_line)
1064 .unwrap_or_default();
1065 let line_char_len = line_text.chars().count();
1066 let line_start_offset = self.line_index.position_to_char_offset(logical_line, 0);
1067
1068 let segment_start_col = if visual_in_line == 0 {
1069 0
1070 } else {
1071 layout
1072 .wrap_points
1073 .get(visual_in_line - 1)
1074 .map(|wp| wp.char_index)
1075 .unwrap_or(0)
1076 .min(line_char_len)
1077 };
1078
1079 let segment_end_col = if visual_in_line < layout.wrap_points.len() {
1080 layout.wrap_points[visual_in_line]
1081 .char_index
1082 .min(line_char_len)
1083 } else {
1084 line_char_len
1085 };
1086
1087 let mut headless_line = HeadlessLine::new(logical_line, visual_in_line > 0);
1088 let mut segment_x_start_cells = 0usize;
1089 if visual_in_line > 0 {
1090 let indent_cells = wrap_indent_cells_for_line_text(
1091 &line_text,
1092 self.layout_engine.wrap_indent(),
1093 self.viewport_width,
1094 tab_width,
1095 );
1096 segment_x_start_cells = indent_cells;
1097 for _ in 0..indent_cells {
1098 headless_line.add_cell(Cell::new(' ', 1));
1099 }
1100 }
1101 let mut x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
1102
1103 for (col, ch) in line_text
1104 .chars()
1105 .enumerate()
1106 .skip(segment_start_col)
1107 .take(segment_end_col.saturating_sub(segment_start_col))
1108 {
1109 let offset = line_start_offset + col;
1110 let styles = self.styles_at_offset(offset);
1111 let w = cell_width_at(ch, x_in_line, tab_width);
1112 x_in_line = x_in_line.saturating_add(w);
1113 headless_line.add_cell(Cell::with_styles(ch, w, styles));
1114 }
1115
1116 headless_line.set_visual_metadata(
1117 visual_in_line,
1118 line_start_offset.saturating_add(segment_start_col),
1119 line_start_offset.saturating_add(segment_end_col),
1120 segment_x_start_cells,
1121 );
1122 headless_line.set_fold_placeholder_appended(false);
1123
1124 if visual_in_line + 1 == layout.visual_line_count
1126 && let Some(region) = Self::collapsed_region_starting_at(regions, logical_line)
1127 && !region.placeholder.is_empty()
1128 {
1129 if !headless_line.cells.is_empty() {
1130 x_in_line = x_in_line.saturating_add(char_width(' '));
1131 headless_line.add_cell(Cell::with_styles(
1132 ' ',
1133 char_width(' '),
1134 vec![FOLD_PLACEHOLDER_STYLE_ID],
1135 ));
1136 }
1137 for ch in region.placeholder.chars() {
1138 let w = cell_width_at(ch, x_in_line, tab_width);
1139 x_in_line = x_in_line.saturating_add(w);
1140 headless_line.add_cell(Cell::with_styles(
1141 ch,
1142 w,
1143 vec![FOLD_PLACEHOLDER_STYLE_ID],
1144 ));
1145 }
1146 headless_line.set_fold_placeholder_appended(true);
1147 }
1148
1149 grid.add_line(headless_line);
1150 current_visual = current_visual.saturating_add(1);
1151 visual_in_line = visual_in_line.saturating_add(1);
1152 if visual_in_line >= span.visual_line_count {
1153 span_idx = span_idx.saturating_add(1);
1154 visual_in_line = 0;
1155 }
1156 }
1157
1158 grid
1159 })
1160 }
1161
1162 pub fn get_minimap_grid(&self, start_visual_row: usize, count: usize) -> MinimapGrid {
1167 self.with_visual_row_index(|index| {
1168 let mut grid = MinimapGrid::new(start_visual_row, count);
1169 if count == 0 {
1170 return grid;
1171 }
1172
1173 let total_visual = index.total_visual_lines();
1174 if start_visual_row >= total_visual {
1175 return grid;
1176 }
1177
1178 let tab_width = self.layout_engine.tab_width();
1179 let end_visual = start_visual_row.saturating_add(count).min(total_visual);
1180 let regions = self.folding_manager.regions();
1181
1182 let Some(mut span_idx) = index.span_index_for_visual_row(start_visual_row) else {
1183 return grid;
1184 };
1185 let mut current_visual = start_visual_row;
1186 let mut visual_in_line =
1187 start_visual_row.saturating_sub(index.spans[span_idx].start_visual_row);
1188
1189 while current_visual < end_visual && span_idx < index.spans.len() {
1190 let span = index.spans[span_idx];
1191 let logical_line = span.logical_line;
1192
1193 let Some(layout) = self.layout_engine.get_line_layout(logical_line) else {
1194 let remaining_in_span = span.visual_line_count.saturating_sub(visual_in_line);
1195 current_visual = current_visual.saturating_add(remaining_in_span);
1196 span_idx = span_idx.saturating_add(1);
1197 visual_in_line = 0;
1198 continue;
1199 };
1200
1201 let line_text = self
1202 .line_index
1203 .get_line_text(logical_line)
1204 .unwrap_or_default();
1205 let line_char_len = line_text.chars().count();
1206 let line_start_offset = self.line_index.position_to_char_offset(logical_line, 0);
1207
1208 let segment_start_col = if visual_in_line == 0 {
1209 0
1210 } else {
1211 layout
1212 .wrap_points
1213 .get(visual_in_line - 1)
1214 .map(|wp| wp.char_index)
1215 .unwrap_or(0)
1216 .min(line_char_len)
1217 };
1218
1219 let segment_end_col = if visual_in_line < layout.wrap_points.len() {
1220 layout.wrap_points[visual_in_line]
1221 .char_index
1222 .min(line_char_len)
1223 } else {
1224 line_char_len
1225 };
1226
1227 let mut total_cells = 0usize;
1228 let mut non_whitespace_cells = 0usize;
1229 let mut dominant_style_counts: HashMap<StyleId, usize> = HashMap::new();
1230 if visual_in_line > 0 {
1231 let indent_cells = wrap_indent_cells_for_line_text(
1232 &line_text,
1233 self.layout_engine.wrap_indent(),
1234 self.viewport_width,
1235 tab_width,
1236 );
1237 total_cells = total_cells.saturating_add(indent_cells);
1238 }
1239 let mut x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
1240
1241 for (col, ch) in line_text
1242 .chars()
1243 .enumerate()
1244 .skip(segment_start_col)
1245 .take(segment_end_col.saturating_sub(segment_start_col))
1246 {
1247 let offset = line_start_offset + col;
1248 let styles = self.styles_at_offset(offset);
1249 let w = cell_width_at(ch, x_in_line, tab_width);
1250 x_in_line = x_in_line.saturating_add(w);
1251 total_cells = total_cells.saturating_add(w);
1252 if !ch.is_whitespace() {
1253 non_whitespace_cells = non_whitespace_cells.saturating_add(w);
1254 }
1255 if let Some(style) = styles.first().copied() {
1256 let entry = dominant_style_counts.entry(style).or_insert(0);
1257 *entry = entry.saturating_add(w);
1258 }
1259 }
1260
1261 let mut placeholder_appended = false;
1262 if visual_in_line + 1 == layout.visual_line_count
1263 && let Some(region) = Self::collapsed_region_starting_at(regions, logical_line)
1264 && !region.placeholder.is_empty()
1265 {
1266 placeholder_appended = true;
1267 if total_cells > 0 {
1268 total_cells = total_cells.saturating_add(char_width(' '));
1269 }
1270 for ch in region.placeholder.chars() {
1271 let w = cell_width_at(ch, x_in_line, tab_width);
1272 x_in_line = x_in_line.saturating_add(w);
1273 total_cells = total_cells.saturating_add(w);
1274 if !ch.is_whitespace() {
1275 non_whitespace_cells = non_whitespace_cells.saturating_add(w);
1276 }
1277 let entry = dominant_style_counts
1278 .entry(FOLD_PLACEHOLDER_STYLE_ID)
1279 .or_insert(0);
1280 *entry = entry.saturating_add(w);
1281 }
1282 }
1283
1284 let dominant_style = dominant_style_counts
1285 .into_iter()
1286 .max_by(|a, b| a.1.cmp(&b.1).then_with(|| b.0.cmp(&a.0)))
1287 .map(|(style, _)| style);
1288
1289 grid.lines.push(MinimapLine {
1290 logical_line_index: logical_line,
1291 visual_in_logical: visual_in_line,
1292 char_offset_start: line_start_offset.saturating_add(segment_start_col),
1293 char_offset_end: line_start_offset.saturating_add(segment_end_col),
1294 total_cells,
1295 non_whitespace_cells,
1296 dominant_style,
1297 is_fold_placeholder_appended: placeholder_appended,
1298 });
1299
1300 current_visual = current_visual.saturating_add(1);
1301 visual_in_line = visual_in_line.saturating_add(1);
1302 if visual_in_line >= span.visual_line_count {
1303 span_idx = span_idx.saturating_add(1);
1304 visual_in_line = 0;
1305 }
1306 }
1307
1308 grid
1309 })
1310 }
1311
1312 pub fn get_headless_grid_composed(
1324 &self,
1325 start_visual_row: usize,
1326 count: usize,
1327 ) -> ComposedGrid {
1328 let mut grid = ComposedGrid::new(start_visual_row, count);
1329 if count == 0 {
1330 return grid;
1331 }
1332
1333 #[derive(Debug, Clone)]
1334 struct VirtualText {
1335 anchor: usize,
1336 text: String,
1337 styles: Vec<StyleId>,
1338 }
1339
1340 let mut inline_before: HashMap<usize, Vec<VirtualText>> = HashMap::new();
1342 let mut inline_after: HashMap<usize, Vec<VirtualText>> = HashMap::new();
1343 let mut above_by_line: BTreeMap<usize, Vec<VirtualText>> = BTreeMap::new();
1344
1345 for decorations in self.decorations.values() {
1346 for deco in decorations {
1347 let Some(text) = deco.text.as_ref() else {
1348 continue;
1349 };
1350 if text.is_empty() {
1351 continue;
1352 }
1353
1354 let anchor = match deco.placement {
1355 DecorationPlacement::After => deco.range.end,
1356 DecorationPlacement::Before | DecorationPlacement::AboveLine => {
1357 deco.range.start
1358 }
1359 };
1360 let vt = VirtualText {
1361 anchor,
1362 text: text.clone(),
1363 styles: deco.styles.clone(),
1364 };
1365
1366 match deco.placement {
1367 DecorationPlacement::Before => {
1368 inline_before.entry(anchor).or_default().push(vt);
1369 }
1370 DecorationPlacement::After => {
1371 inline_after.entry(anchor).or_default().push(vt);
1372 }
1373 DecorationPlacement::AboveLine => {
1374 let line = self.line_index.char_offset_to_position(anchor).0;
1375 above_by_line.entry(line).or_default().push(vt);
1376 }
1377 }
1378 }
1379 }
1380
1381 let regions = self.folding_manager.regions();
1383 let mut total_composed = 0usize;
1384 for logical_line in 0..self.layout_engine.logical_line_count() {
1385 if Self::is_logical_line_hidden(regions, logical_line) {
1386 continue;
1387 }
1388
1389 if let Some(above) = above_by_line.get(&logical_line) {
1390 total_composed = total_composed.saturating_add(above.len());
1391 }
1392
1393 total_composed = total_composed.saturating_add(
1394 self.layout_engine
1395 .get_line_layout(logical_line)
1396 .map(|l| l.visual_line_count)
1397 .unwrap_or(1),
1398 );
1399 }
1400
1401 if start_visual_row >= total_composed {
1402 return grid;
1403 }
1404
1405 let end_visual = start_visual_row.saturating_add(count).min(total_composed);
1406 let tab_width = self.layout_engine.tab_width();
1407
1408 let mut current_visual = 0usize;
1409
1410 for logical_line in 0..self.layout_engine.logical_line_count() {
1411 if Self::is_logical_line_hidden(regions, logical_line) {
1412 continue;
1413 }
1414
1415 if let Some(above) = above_by_line.get(&logical_line) {
1417 for vt in above {
1418 if current_visual >= end_visual {
1419 return grid;
1420 }
1421
1422 if current_visual >= start_visual_row {
1423 let mut x_render = 0usize;
1424 let mut cells: Vec<ComposedCell> = Vec::new();
1425 for ch in vt.text.chars() {
1426 let w = cell_width_at(ch, x_render, tab_width);
1427 x_render = x_render.saturating_add(w);
1428 cells.push(ComposedCell {
1429 ch,
1430 width: w,
1431 styles: vt.styles.clone(),
1432 source: ComposedCellSource::Virtual {
1433 anchor_offset: vt.anchor,
1434 },
1435 });
1436 }
1437
1438 grid.lines.push(ComposedLine {
1439 kind: ComposedLineKind::VirtualAboveLine { logical_line },
1440 cells,
1441 });
1442 }
1443
1444 current_visual = current_visual.saturating_add(1);
1445 }
1446 }
1447
1448 let Some(layout) = self.layout_engine.get_line_layout(logical_line) else {
1449 continue;
1450 };
1451
1452 let line_text = self
1453 .line_index
1454 .get_line_text(logical_line)
1455 .unwrap_or_default();
1456 let line_char_len = line_text.chars().count();
1457 let line_start_offset = self.line_index.position_to_char_offset(logical_line, 0);
1458
1459 for visual_in_line in 0..layout.visual_line_count {
1460 if current_visual >= end_visual {
1461 return grid;
1462 }
1463
1464 if current_visual < start_visual_row {
1465 current_visual = current_visual.saturating_add(1);
1466 continue;
1467 }
1468
1469 let segment_start_col = if visual_in_line == 0 {
1470 0
1471 } else {
1472 layout
1473 .wrap_points
1474 .get(visual_in_line - 1)
1475 .map(|wp| wp.char_index)
1476 .unwrap_or(0)
1477 .min(line_char_len)
1478 };
1479
1480 let segment_end_col = if visual_in_line < layout.wrap_points.len() {
1481 layout.wrap_points[visual_in_line]
1482 .char_index
1483 .min(line_char_len)
1484 } else {
1485 line_char_len
1486 };
1487
1488 let segment_start_offset = line_start_offset + segment_start_col;
1489
1490 let mut cells: Vec<ComposedCell> = Vec::new();
1491
1492 let mut x_render = 0usize;
1493 if visual_in_line > 0 {
1494 let indent_cells = wrap_indent_cells_for_line_text(
1495 &line_text,
1496 self.layout_engine.wrap_indent(),
1497 self.viewport_width,
1498 tab_width,
1499 );
1500 x_render = x_render.saturating_add(indent_cells);
1501 for _ in 0..indent_cells {
1502 cells.push(ComposedCell {
1503 ch: ' ',
1504 width: 1,
1505 styles: Vec::new(),
1506 source: ComposedCellSource::Virtual {
1507 anchor_offset: segment_start_offset,
1508 },
1509 });
1510 }
1511 }
1512
1513 let mut x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
1514
1515 let push_virtual = |anchor: usize,
1516 list: &[VirtualText],
1517 cells: &mut Vec<ComposedCell>,
1518 x_render: &mut usize| {
1519 for vt in list {
1520 for ch in vt.text.chars() {
1521 let w = cell_width_at(ch, *x_render, tab_width);
1522 *x_render = x_render.saturating_add(w);
1523 cells.push(ComposedCell {
1524 ch,
1525 width: w,
1526 styles: vt.styles.clone(),
1527 source: ComposedCellSource::Virtual {
1528 anchor_offset: anchor,
1529 },
1530 });
1531 }
1532 }
1533 };
1534
1535 for (col, ch) in line_text
1536 .chars()
1537 .enumerate()
1538 .skip(segment_start_col)
1539 .take(segment_end_col.saturating_sub(segment_start_col))
1540 {
1541 let offset = line_start_offset + col;
1542
1543 if let Some(list) = inline_before.get(&offset) {
1544 push_virtual(offset, list, &mut cells, &mut x_render);
1545 }
1546 if let Some(list) = inline_after.get(&offset) {
1547 push_virtual(offset, list, &mut cells, &mut x_render);
1548 }
1549
1550 let styles = self.styles_at_offset(offset);
1551 let w = cell_width_at(ch, x_in_line, tab_width);
1552 x_in_line = x_in_line.saturating_add(w);
1553 x_render = x_render.saturating_add(w);
1554 cells.push(ComposedCell {
1555 ch,
1556 width: w,
1557 styles,
1558 source: ComposedCellSource::Document { offset },
1559 });
1560 }
1561
1562 if visual_in_line + 1 == layout.visual_line_count {
1564 let eol_offset = line_start_offset + line_char_len;
1565 if let Some(list) = inline_before.get(&eol_offset) {
1566 push_virtual(eol_offset, list, &mut cells, &mut x_render);
1567 }
1568 if let Some(list) = inline_after.get(&eol_offset) {
1569 push_virtual(eol_offset, list, &mut cells, &mut x_render);
1570 }
1571
1572 if let Some(region) = Self::collapsed_region_starting_at(regions, logical_line)
1574 && !region.placeholder.is_empty()
1575 {
1576 if !cells.is_empty() {
1577 x_render = x_render.saturating_add(char_width(' '));
1578 cells.push(ComposedCell {
1579 ch: ' ',
1580 width: char_width(' '),
1581 styles: vec![FOLD_PLACEHOLDER_STYLE_ID],
1582 source: ComposedCellSource::Virtual {
1583 anchor_offset: eol_offset,
1584 },
1585 });
1586 }
1587 for ch in region.placeholder.chars() {
1588 let w = cell_width_at(ch, x_render, tab_width);
1589 x_render = x_render.saturating_add(w);
1590 cells.push(ComposedCell {
1591 ch,
1592 width: w,
1593 styles: vec![FOLD_PLACEHOLDER_STYLE_ID],
1594 source: ComposedCellSource::Virtual {
1595 anchor_offset: eol_offset,
1596 },
1597 });
1598 }
1599 }
1600 }
1601
1602 grid.lines.push(ComposedLine {
1603 kind: ComposedLineKind::Document {
1604 logical_line,
1605 visual_in_logical: visual_in_line,
1606 },
1607 cells,
1608 });
1609
1610 current_visual = current_visual.saturating_add(1);
1611 }
1612 }
1613
1614 grid
1615 }
1616
1617 pub fn visual_line_count(&self) -> usize {
1619 self.with_visual_row_index(|index| index.total_visual_lines())
1620 }
1621
1622 pub fn visual_to_logical_line(&self, visual_line: usize) -> (usize, usize) {
1624 self.with_visual_row_index(|index| {
1625 if index.total_visual_lines() == 0 {
1626 return (0, 0);
1627 }
1628 let clamped_visual = visual_line.min(index.total_visual_lines().saturating_sub(1));
1629 index
1630 .span_for_visual_row(clamped_visual)
1631 .map(|(span, visual_in_logical)| (span.logical_line, visual_in_logical))
1632 .unwrap_or((0, 0))
1633 })
1634 }
1635
1636 pub fn logical_position_to_visual(
1638 &self,
1639 logical_line: usize,
1640 column: usize,
1641 ) -> Option<(usize, usize)> {
1642 let regions = self.folding_manager.regions();
1643 let logical_line = Self::closest_visible_line(regions, logical_line)?;
1644 let visual_start = self.visual_start_for_logical_line(logical_line)?;
1645
1646 let tab_width = self.layout_engine.tab_width();
1647
1648 let layout = self.layout_engine.get_line_layout(logical_line)?;
1649 let line_text = self
1650 .line_index
1651 .get_line_text(logical_line)
1652 .unwrap_or_default();
1653
1654 let line_char_len = line_text.chars().count();
1655 let column = column.min(line_char_len);
1656
1657 let mut wrapped_offset = 0usize;
1658 let mut segment_start_col = 0usize;
1659 for wrap_point in &layout.wrap_points {
1660 if column >= wrap_point.char_index {
1661 wrapped_offset = wrapped_offset.saturating_add(1);
1662 segment_start_col = wrap_point.char_index;
1663 } else {
1664 break;
1665 }
1666 }
1667
1668 let seg_start_x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
1669 let mut x_in_line = seg_start_x_in_line;
1670 let mut x_in_segment = 0usize;
1671 for ch in line_text
1672 .chars()
1673 .skip(segment_start_col)
1674 .take(column.saturating_sub(segment_start_col))
1675 {
1676 let w = cell_width_at(ch, x_in_line, tab_width);
1677 x_in_line = x_in_line.saturating_add(w);
1678 x_in_segment = x_in_segment.saturating_add(w);
1679 }
1680
1681 let indent = if wrapped_offset == 0 {
1682 0
1683 } else {
1684 wrap_indent_cells_for_line_text(
1685 &line_text,
1686 self.layout_engine.wrap_indent(),
1687 self.viewport_width,
1688 tab_width,
1689 )
1690 };
1691
1692 Some((
1693 visual_start.saturating_add(wrapped_offset),
1694 indent.saturating_add(x_in_segment),
1695 ))
1696 }
1697
1698 pub fn logical_position_to_visual_allow_virtual(
1703 &self,
1704 logical_line: usize,
1705 column: usize,
1706 ) -> Option<(usize, usize)> {
1707 let regions = self.folding_manager.regions();
1708 let logical_line = Self::closest_visible_line(regions, logical_line)?;
1709 let visual_start = self.visual_start_for_logical_line(logical_line)?;
1710
1711 let tab_width = self.layout_engine.tab_width();
1712
1713 let layout = self.layout_engine.get_line_layout(logical_line)?;
1714 let line_text = self
1715 .line_index
1716 .get_line_text(logical_line)
1717 .unwrap_or_default();
1718
1719 let line_char_len = line_text.chars().count();
1720 let clamped_column = column.min(line_char_len);
1721
1722 let mut wrapped_offset = 0usize;
1723 let mut segment_start_col = 0usize;
1724 for wrap_point in &layout.wrap_points {
1725 if clamped_column >= wrap_point.char_index {
1726 wrapped_offset = wrapped_offset.saturating_add(1);
1727 segment_start_col = wrap_point.char_index;
1728 } else {
1729 break;
1730 }
1731 }
1732
1733 let seg_start_x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
1734 let mut x_in_line = seg_start_x_in_line;
1735 let mut x_in_segment = 0usize;
1736 for ch in line_text
1737 .chars()
1738 .skip(segment_start_col)
1739 .take(clamped_column.saturating_sub(segment_start_col))
1740 {
1741 let w = cell_width_at(ch, x_in_line, tab_width);
1742 x_in_line = x_in_line.saturating_add(w);
1743 x_in_segment = x_in_segment.saturating_add(w);
1744 }
1745
1746 let x_in_segment = x_in_segment + column.saturating_sub(line_char_len);
1747
1748 let indent = if wrapped_offset == 0 {
1749 0
1750 } else {
1751 wrap_indent_cells_for_line_text(
1752 &line_text,
1753 self.layout_engine.wrap_indent(),
1754 self.viewport_width,
1755 tab_width,
1756 )
1757 };
1758
1759 Some((
1760 visual_start.saturating_add(wrapped_offset),
1761 indent.saturating_add(x_in_segment),
1762 ))
1763 }
1764
1765 pub fn visual_position_to_logical(
1772 &self,
1773 visual_row: usize,
1774 x_in_cells: usize,
1775 ) -> Option<Position> {
1776 let total_visual = self.visual_line_count();
1777 if total_visual == 0 {
1778 return Some(Position::new(0, 0));
1779 }
1780
1781 let clamped_row = visual_row.min(total_visual.saturating_sub(1));
1782 let (logical_line, visual_in_logical) = self.visual_to_logical_line(clamped_row);
1783
1784 let layout = self.layout_engine.get_line_layout(logical_line)?;
1785 let line_text = self
1786 .line_index
1787 .get_line_text(logical_line)
1788 .unwrap_or_default();
1789 let line_char_len = line_text.chars().count();
1790
1791 let segment_start_col = if visual_in_logical == 0 {
1792 0
1793 } else {
1794 layout
1795 .wrap_points
1796 .get(visual_in_logical - 1)
1797 .map(|wp| wp.char_index)
1798 .unwrap_or(0)
1799 };
1800
1801 let segment_end_col = layout
1802 .wrap_points
1803 .get(visual_in_logical)
1804 .map(|wp| wp.char_index)
1805 .unwrap_or(line_char_len)
1806 .max(segment_start_col)
1807 .min(line_char_len);
1808
1809 let tab_width = self.layout_engine.tab_width();
1810 let x_in_cells = if visual_in_logical == 0 {
1811 x_in_cells
1812 } else {
1813 let indent = wrap_indent_cells_for_line_text(
1814 &line_text,
1815 self.layout_engine.wrap_indent(),
1816 self.viewport_width,
1817 tab_width,
1818 );
1819 x_in_cells.saturating_sub(indent)
1820 };
1821 let seg_start_x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
1822 let mut x_in_line = seg_start_x_in_line;
1823 let mut x_in_segment = 0usize;
1824 let mut column = segment_start_col;
1825
1826 for (char_idx, ch) in line_text.chars().enumerate().skip(segment_start_col) {
1827 if char_idx >= segment_end_col {
1828 break;
1829 }
1830
1831 let w = cell_width_at(ch, x_in_line, tab_width);
1832 if x_in_segment.saturating_add(w) > x_in_cells {
1833 break;
1834 }
1835
1836 x_in_line = x_in_line.saturating_add(w);
1837 x_in_segment = x_in_segment.saturating_add(w);
1838 column = column.saturating_add(1);
1839 }
1840
1841 Some(Position::new(logical_line, column))
1842 }
1843
1844 fn visual_start_for_logical_line(&self, logical_line: usize) -> Option<usize> {
1845 if logical_line >= self.layout_engine.logical_line_count() {
1846 return None;
1847 }
1848 self.with_visual_row_index(|index| {
1849 index
1850 .span_for_logical_line(logical_line)
1851 .map(|span| span.start_visual_row)
1852 })
1853 }
1854
1855 fn is_logical_line_hidden(regions: &[FoldRegion], logical_line: usize) -> bool {
1856 regions.iter().any(|region| {
1857 region.is_collapsed
1858 && logical_line > region.start_line
1859 && logical_line <= region.end_line
1860 })
1861 }
1862
1863 fn collapsed_region_starting_at(
1864 regions: &[FoldRegion],
1865 start_line: usize,
1866 ) -> Option<&FoldRegion> {
1867 regions
1868 .iter()
1869 .filter(|region| {
1870 region.is_collapsed
1871 && region.start_line == start_line
1872 && region.end_line > start_line
1873 })
1874 .min_by_key(|region| region.end_line)
1875 }
1876
1877 fn closest_visible_line(regions: &[FoldRegion], logical_line: usize) -> Option<usize> {
1878 let mut line = logical_line;
1879 if regions.is_empty() {
1880 return Some(line);
1881 }
1882
1883 while Self::is_logical_line_hidden(regions, line) {
1884 let Some(start) = regions
1885 .iter()
1886 .filter(|region| {
1887 region.is_collapsed && line > region.start_line && line <= region.end_line
1888 })
1889 .map(|region| region.start_line)
1890 .max()
1891 else {
1892 break;
1893 };
1894 line = start;
1895 }
1896
1897 if Self::is_logical_line_hidden(regions, line) {
1898 None
1899 } else {
1900 Some(line)
1901 }
1902 }
1903
1904 fn styles_at_offset(&self, offset: usize) -> Vec<StyleId> {
1905 let mut styles: Vec<StyleId> = self
1906 .interval_tree
1907 .query_point(offset)
1908 .iter()
1909 .map(|interval| interval.style_id)
1910 .collect();
1911
1912 for tree in self.style_layers.values() {
1913 styles.extend(
1914 tree.query_point(offset)
1915 .iter()
1916 .map(|interval| interval.style_id),
1917 );
1918 }
1919
1920 styles.sort_unstable();
1921 styles.dedup();
1922 styles
1923 }
1924}
1925
1926pub struct CommandExecutor {
1964 editor: EditorCore,
1966 command_history: Vec<Command>,
1968 undo_redo: UndoRedoManager,
1970 tab_key_behavior: TabKeyBehavior,
1972 line_ending: LineEnding,
1974 preferred_x_cells: Option<usize>,
1976 last_text_delta: Option<TextDelta>,
1978}
1979
1980impl CommandExecutor {
1981 pub fn new(text: &str, viewport_width: usize) -> Self {
1983 Self {
1984 editor: EditorCore::new(text, viewport_width),
1985 command_history: Vec::new(),
1986 undo_redo: UndoRedoManager::new(1000),
1987 tab_key_behavior: TabKeyBehavior::Tab,
1988 line_ending: LineEnding::detect_in_text(text),
1989 preferred_x_cells: None,
1990 last_text_delta: None,
1991 }
1992 }
1993
1994 pub fn empty(viewport_width: usize) -> Self {
1996 Self::new("", viewport_width)
1997 }
1998
1999 pub fn execute(&mut self, command: Command) -> Result<CommandResult, CommandError> {
2001 self.last_text_delta = None;
2002
2003 self.command_history.push(command.clone());
2005
2006 let affects_visual_rows = matches!(
2007 &command,
2008 Command::Edit(_)
2009 | Command::View(
2010 ViewCommand::SetViewportWidth { .. }
2011 | ViewCommand::SetWrapMode { .. }
2012 | ViewCommand::SetWrapIndent { .. }
2013 | ViewCommand::SetTabWidth { .. }
2014 )
2015 | Command::Style(
2016 StyleCommand::Fold { .. }
2017 | StyleCommand::Unfold { .. }
2018 | StyleCommand::UnfoldAll
2019 )
2020 );
2021 if affects_visual_rows {
2022 self.editor.invalidate_visual_row_index_cache();
2023 }
2024
2025 if !matches!(command, Command::Edit(_)) {
2027 self.undo_redo.end_group();
2028 }
2029
2030 match command {
2032 Command::Edit(edit_cmd) => self.execute_edit(edit_cmd),
2033 Command::Cursor(cursor_cmd) => self.execute_cursor(cursor_cmd),
2034 Command::View(view_cmd) => self.execute_view(view_cmd),
2035 Command::Style(style_cmd) => self.execute_style(style_cmd),
2036 }
2037 }
2038
2039 pub fn last_text_delta(&self) -> Option<&TextDelta> {
2041 self.last_text_delta.as_ref()
2042 }
2043
2044 pub fn take_last_text_delta(&mut self) -> Option<TextDelta> {
2046 self.last_text_delta.take()
2047 }
2048
2049 pub fn execute_batch(
2051 &mut self,
2052 commands: Vec<Command>,
2053 ) -> Result<Vec<CommandResult>, CommandError> {
2054 let mut results = Vec::new();
2055
2056 for command in commands {
2057 let result = self.execute(command)?;
2058 results.push(result);
2059 }
2060
2061 Ok(results)
2062 }
2063
2064 pub fn get_command_history(&self) -> &[Command] {
2066 &self.command_history
2067 }
2068
2069 pub fn can_undo(&self) -> bool {
2071 self.undo_redo.can_undo()
2072 }
2073
2074 pub fn can_redo(&self) -> bool {
2076 self.undo_redo.can_redo()
2077 }
2078
2079 pub fn undo_depth(&self) -> usize {
2081 self.undo_redo.undo_depth()
2082 }
2083
2084 pub fn redo_depth(&self) -> usize {
2086 self.undo_redo.redo_depth()
2087 }
2088
2089 pub fn current_change_group(&self) -> Option<usize> {
2091 self.undo_redo.current_group_id()
2092 }
2093
2094 pub fn is_clean(&self) -> bool {
2096 self.undo_redo.is_clean()
2097 }
2098
2099 pub fn mark_clean(&mut self) {
2101 self.undo_redo.mark_clean();
2102 }
2103
2104 pub fn editor(&self) -> &EditorCore {
2106 &self.editor
2107 }
2108
2109 pub fn editor_mut(&mut self) -> &mut EditorCore {
2111 &mut self.editor
2112 }
2113
2114 pub fn tab_key_behavior(&self) -> TabKeyBehavior {
2116 self.tab_key_behavior
2117 }
2118
2119 pub fn set_tab_key_behavior(&mut self, behavior: TabKeyBehavior) {
2121 self.tab_key_behavior = behavior;
2122 }
2123
2124 pub fn preferred_x_cells(&self) -> Option<usize> {
2126 self.preferred_x_cells
2127 }
2128
2129 pub fn set_preferred_x_cells(&mut self, preferred_x_cells: Option<usize>) {
2131 self.preferred_x_cells = preferred_x_cells;
2132 }
2133
2134 pub fn line_ending(&self) -> LineEnding {
2136 self.line_ending
2137 }
2138
2139 pub fn set_line_ending(&mut self, line_ending: LineEnding) {
2141 self.line_ending = line_ending;
2142 }
2143
2144 fn execute_edit(&mut self, command: EditCommand) -> Result<CommandResult, CommandError> {
2146 match command {
2147 EditCommand::Undo => self.execute_undo_command(),
2148 EditCommand::Redo => self.execute_redo_command(),
2149 EditCommand::EndUndoGroup => {
2150 self.undo_redo.end_group();
2151 Ok(CommandResult::Success)
2152 }
2153 EditCommand::ReplaceCurrent {
2154 query,
2155 replacement,
2156 options,
2157 } => self.execute_replace_current_command(query, replacement, options),
2158 EditCommand::ReplaceAll {
2159 query,
2160 replacement,
2161 options,
2162 } => self.execute_replace_all_command(query, replacement, options),
2163 EditCommand::DeleteToPrevTabStop => self.execute_delete_to_prev_tab_stop_command(),
2164 EditCommand::DeleteGraphemeBack => {
2165 self.execute_delete_by_boundary_command(false, TextBoundary::Grapheme)
2166 }
2167 EditCommand::DeleteGraphemeForward => {
2168 self.execute_delete_by_boundary_command(true, TextBoundary::Grapheme)
2169 }
2170 EditCommand::DeleteWordBack => {
2171 self.execute_delete_by_boundary_command(false, TextBoundary::Word)
2172 }
2173 EditCommand::DeleteWordForward => {
2174 self.execute_delete_by_boundary_command(true, TextBoundary::Word)
2175 }
2176 EditCommand::Backspace => self.execute_backspace_command(),
2177 EditCommand::DeleteForward => self.execute_delete_forward_command(),
2178 EditCommand::InsertText { text } => self.execute_insert_text_command(text),
2179 EditCommand::InsertTab => self.execute_insert_tab_command(),
2180 EditCommand::InsertNewline { auto_indent } => {
2181 self.execute_insert_newline_command(auto_indent)
2182 }
2183 EditCommand::Indent => self.execute_indent_command(false),
2184 EditCommand::Outdent => self.execute_indent_command(true),
2185 EditCommand::DuplicateLines => self.execute_duplicate_lines_command(),
2186 EditCommand::DeleteLines => self.execute_delete_lines_command(),
2187 EditCommand::MoveLinesUp => self.execute_move_lines_command(true),
2188 EditCommand::MoveLinesDown => self.execute_move_lines_command(false),
2189 EditCommand::JoinLines => self.execute_join_lines_command(),
2190 EditCommand::SplitLine => self.execute_insert_newline_command(false),
2191 EditCommand::ToggleComment { config } => self.execute_toggle_comment_command(config),
2192 EditCommand::ApplyTextEdits { edits } => self.execute_apply_text_edits_command(edits),
2193 EditCommand::Insert { offset, text } => self.execute_insert_command(offset, text),
2194 EditCommand::Delete { start, length } => self.execute_delete_command(start, length),
2195 EditCommand::Replace {
2196 start,
2197 length,
2198 text,
2199 } => self.execute_replace_command(start, length, text),
2200 }
2201 }
2202
2203 fn execute_undo_command(&mut self) -> Result<CommandResult, CommandError> {
2204 self.undo_redo.end_group();
2205 if !self.undo_redo.can_undo() {
2206 return Err(CommandError::Other("Nothing to undo".to_string()));
2207 }
2208
2209 let before_char_count = self.editor.piece_table.char_count();
2210 let steps = self
2211 .undo_redo
2212 .pop_undo_group()
2213 .ok_or_else(|| CommandError::Other("Nothing to undo".to_string()))?;
2214
2215 let undo_group_id = steps.first().map(|s| s.group_id);
2216 let mut delta_edits: Vec<TextDeltaEdit> = Vec::new();
2217
2218 for step in &steps {
2219 let mut step_edits: Vec<TextDeltaEdit> = step
2220 .edits
2221 .iter()
2222 .map(|edit| TextDeltaEdit {
2223 start: edit.start_after,
2224 deleted_text: edit.inserted_text.clone(),
2225 inserted_text: edit.deleted_text.clone(),
2226 })
2227 .collect();
2228 step_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
2229 delta_edits.extend(step_edits);
2230
2231 self.apply_undo_edits(&step.edits)?;
2232 self.restore_selection_set(step.before_selection.clone());
2233 }
2234
2235 for step in steps {
2237 self.undo_redo.redo_stack.push(step);
2238 }
2239
2240 self.last_text_delta = Some(TextDelta {
2241 before_char_count,
2242 after_char_count: self.editor.piece_table.char_count(),
2243 edits: delta_edits,
2244 undo_group_id,
2245 });
2246
2247 Ok(CommandResult::Success)
2248 }
2249
2250 fn execute_redo_command(&mut self) -> Result<CommandResult, CommandError> {
2251 self.undo_redo.end_group();
2252 if !self.undo_redo.can_redo() {
2253 return Err(CommandError::Other("Nothing to redo".to_string()));
2254 }
2255
2256 let before_char_count = self.editor.piece_table.char_count();
2257 let steps = self
2258 .undo_redo
2259 .pop_redo_group()
2260 .ok_or_else(|| CommandError::Other("Nothing to redo".to_string()))?;
2261
2262 let undo_group_id = steps.first().map(|s| s.group_id);
2263 let mut delta_edits: Vec<TextDeltaEdit> = Vec::new();
2264
2265 for step in &steps {
2266 let mut step_edits: Vec<TextDeltaEdit> = step
2267 .edits
2268 .iter()
2269 .map(|edit| TextDeltaEdit {
2270 start: edit.start_before,
2271 deleted_text: edit.deleted_text.clone(),
2272 inserted_text: edit.inserted_text.clone(),
2273 })
2274 .collect();
2275 step_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
2276 delta_edits.extend(step_edits);
2277
2278 self.apply_redo_edits(&step.edits)?;
2279 self.restore_selection_set(step.after_selection.clone());
2280 }
2281
2282 for step in steps {
2284 self.undo_redo.undo_stack.push(step);
2285 }
2286
2287 self.last_text_delta = Some(TextDelta {
2288 before_char_count,
2289 after_char_count: self.editor.piece_table.char_count(),
2290 edits: delta_edits,
2291 undo_group_id,
2292 });
2293
2294 Ok(CommandResult::Success)
2295 }
2296
2297 fn execute_insert_text_command(&mut self, text: String) -> Result<CommandResult, CommandError> {
2298 if text.is_empty() {
2299 return Ok(CommandResult::Success);
2300 }
2301
2302 let text = crate::text::normalize_crlf_to_lf_string(text);
2303 let before_char_count = self.editor.piece_table.char_count();
2304 let before_selection = self.snapshot_selection_set();
2305
2306 let mut selections: Vec<Selection> =
2310 Vec::with_capacity(1 + self.editor.secondary_selections.len());
2311 let primary_selection = self.editor.selection.clone().unwrap_or(Selection {
2312 start: self.editor.cursor_position,
2313 end: self.editor.cursor_position,
2314 direction: SelectionDirection::Forward,
2315 });
2316 selections.push(primary_selection);
2317 selections.extend(self.editor.secondary_selections.iter().cloned());
2318
2319 let (selections, primary_index) = crate::selection_set::normalize_selections(selections, 0);
2320
2321 let text_char_len = text.chars().count();
2322
2323 struct Op {
2324 selection_index: usize,
2325 start_offset: usize,
2326 start_after: usize,
2327 delete_len: usize,
2328 deleted_text: String,
2329 insert_text: String,
2330 insert_char_len: usize,
2331 }
2332
2333 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
2334
2335 for (selection_index, selection) in selections.iter().enumerate() {
2336 let (range_start_pos, range_end_pos) = if selection.start <= selection.end {
2337 (selection.start, selection.end)
2338 } else {
2339 (selection.end, selection.start)
2340 };
2341
2342 let (start_offset, start_pad) =
2343 self.position_to_char_offset_and_virtual_pad(range_start_pos);
2344 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
2345
2346 let delete_len = end_offset.saturating_sub(start_offset);
2347 let insert_char_len = start_pad + text_char_len;
2348
2349 let deleted_text = if delete_len == 0 {
2350 String::new()
2351 } else {
2352 self.editor.piece_table.get_range(start_offset, delete_len)
2353 };
2354
2355 let mut insert_text = String::with_capacity(text.len() + start_pad);
2356 for _ in 0..start_pad {
2357 insert_text.push(' ');
2358 }
2359 insert_text.push_str(&text);
2360
2361 ops.push(Op {
2362 selection_index,
2363 start_offset,
2364 start_after: start_offset,
2365 delete_len,
2366 deleted_text,
2367 insert_text,
2368 insert_char_len,
2369 });
2370 }
2371
2372 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
2375 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
2376
2377 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
2378 let mut delta: i64 = 0;
2379 for &idx in &asc_indices {
2380 let op = &mut ops[idx];
2381 let effective_start = (op.start_offset as i64 + delta) as usize;
2382 op.start_after = effective_start;
2383 caret_offsets[op.selection_index] = effective_start + op.insert_char_len;
2384 delta += op.insert_char_len as i64 - op.delete_len as i64;
2385 }
2386
2387 let mut desc_indices = asc_indices;
2389 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
2390
2391 for &idx in &desc_indices {
2392 let op = &ops[idx];
2393
2394 let edit_line = self
2395 .editor
2396 .line_index
2397 .char_offset_to_position(op.start_offset)
2398 .0;
2399 let deleted_newlines = op
2400 .deleted_text
2401 .as_bytes()
2402 .iter()
2403 .filter(|b| **b == b'\n')
2404 .count();
2405 let inserted_newlines = op
2406 .insert_text
2407 .as_bytes()
2408 .iter()
2409 .filter(|b| **b == b'\n')
2410 .count();
2411 let line_delta = inserted_newlines as isize - deleted_newlines as isize;
2412 if line_delta != 0 {
2413 self.editor
2414 .folding_manager
2415 .apply_line_delta(edit_line, line_delta);
2416 }
2417
2418 if op.delete_len > 0 {
2419 self.editor
2420 .piece_table
2421 .delete(op.start_offset, op.delete_len);
2422 self.editor
2423 .interval_tree
2424 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
2425 for layer_tree in self.editor.style_layers.values_mut() {
2426 layer_tree
2427 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
2428 }
2429 }
2430
2431 if !op.insert_text.is_empty() {
2432 self.editor
2433 .piece_table
2434 .insert(op.start_offset, &op.insert_text);
2435 self.editor
2436 .interval_tree
2437 .update_for_insertion(op.start_offset, op.insert_char_len);
2438 for layer_tree in self.editor.style_layers.values_mut() {
2439 layer_tree.update_for_insertion(op.start_offset, op.insert_char_len);
2440 }
2441 }
2442
2443 self.apply_text_change_to_line_index_and_layout(
2444 op.start_offset,
2445 &op.deleted_text,
2446 &op.insert_text,
2447 );
2448 }
2449
2450 self.editor
2451 .folding_manager
2452 .clamp_to_line_count(self.editor.line_index.line_count());
2453
2454 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
2456 for offset in &caret_offsets {
2457 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
2458 let pos = Position::new(line, column);
2459 new_carets.push(Selection {
2460 start: pos,
2461 end: pos,
2462 direction: SelectionDirection::Forward,
2463 });
2464 }
2465
2466 let (new_carets, new_primary_index) =
2467 crate::selection_set::normalize_selections(new_carets, primary_index);
2468 let primary = new_carets
2469 .get(new_primary_index)
2470 .cloned()
2471 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
2472
2473 self.editor.cursor_position = primary.end;
2474 self.editor.selection = None;
2475 self.editor.secondary_selections = new_carets
2476 .into_iter()
2477 .enumerate()
2478 .filter_map(|(idx, sel)| {
2479 if idx == new_primary_index {
2480 None
2481 } else {
2482 Some(sel)
2483 }
2484 })
2485 .collect();
2486
2487 let after_selection = self.snapshot_selection_set();
2488
2489 let edits: Vec<TextEdit> = ops
2490 .into_iter()
2491 .map(|op| TextEdit {
2492 start_before: op.start_offset,
2493 start_after: op.start_after,
2494 deleted_text: op.deleted_text,
2495 inserted_text: op.insert_text,
2496 })
2497 .collect();
2498
2499 let is_pure_insert = edits.iter().all(|e| e.deleted_text.is_empty());
2500 let coalescible_insert = is_pure_insert && !text.contains('\n');
2501
2502 let mut delta_edits: Vec<TextDeltaEdit> = edits
2503 .iter()
2504 .map(|e| TextDeltaEdit {
2505 start: e.start_before,
2506 deleted_text: e.deleted_text.clone(),
2507 inserted_text: e.inserted_text.clone(),
2508 })
2509 .collect();
2510 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
2511
2512 let step = UndoStep {
2513 group_id: 0,
2514 edits,
2515 before_selection,
2516 after_selection,
2517 };
2518 let group_id = self.undo_redo.push_step(step, coalescible_insert);
2519
2520 self.last_text_delta = Some(TextDelta {
2521 before_char_count,
2522 after_char_count: self.editor.piece_table.char_count(),
2523 edits: delta_edits,
2524 undo_group_id: Some(group_id),
2525 });
2526
2527 Ok(CommandResult::Success)
2528 }
2529
2530 fn execute_insert_tab_command(&mut self) -> Result<CommandResult, CommandError> {
2531 let before_char_count = self.editor.piece_table.char_count();
2532 let before_selection = self.snapshot_selection_set();
2533
2534 let mut selections: Vec<Selection> =
2535 Vec::with_capacity(1 + self.editor.secondary_selections.len());
2536 let primary_selection = self.editor.selection.clone().unwrap_or(Selection {
2537 start: self.editor.cursor_position,
2538 end: self.editor.cursor_position,
2539 direction: SelectionDirection::Forward,
2540 });
2541 selections.push(primary_selection);
2542 selections.extend(self.editor.secondary_selections.iter().cloned());
2543
2544 let (selections, primary_index) = crate::selection_set::normalize_selections(selections, 0);
2545
2546 let tab_width = self.editor.layout_engine.tab_width();
2547
2548 struct Op {
2549 selection_index: usize,
2550 start_offset: usize,
2551 start_after: usize,
2552 delete_len: usize,
2553 deleted_text: String,
2554 insert_text: String,
2555 insert_char_len: usize,
2556 }
2557
2558 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
2559
2560 for (selection_index, selection) in selections.iter().enumerate() {
2561 let (range_start_pos, range_end_pos) = if selection.start <= selection.end {
2562 (selection.start, selection.end)
2563 } else {
2564 (selection.end, selection.start)
2565 };
2566
2567 let (start_offset, start_pad) =
2568 self.position_to_char_offset_and_virtual_pad(range_start_pos);
2569 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
2570
2571 let delete_len = end_offset.saturating_sub(start_offset);
2572
2573 let deleted_text = if delete_len == 0 {
2574 String::new()
2575 } else {
2576 self.editor.piece_table.get_range(start_offset, delete_len)
2577 };
2578
2579 let line_text = self
2581 .editor
2582 .line_index
2583 .get_line_text(range_start_pos.line)
2584 .unwrap_or_default();
2585 let line_char_len = line_text.chars().count();
2586 let clamped_col = range_start_pos.column.min(line_char_len);
2587 let x_in_line =
2588 visual_x_for_column(&line_text, clamped_col, tab_width).saturating_add(start_pad);
2589
2590 let mut insert_text = String::new();
2591 for _ in 0..start_pad {
2592 insert_text.push(' ');
2593 }
2594
2595 match self.tab_key_behavior {
2596 TabKeyBehavior::Tab => {
2597 insert_text.push('\t');
2598 ops.push(Op {
2599 selection_index,
2600 start_offset,
2601 start_after: start_offset,
2602 delete_len,
2603 deleted_text,
2604 insert_text,
2605 insert_char_len: start_pad + 1,
2606 });
2607 }
2608 TabKeyBehavior::Spaces => {
2609 let tab_width = tab_width.max(1);
2610 let rem = x_in_line % tab_width;
2611 let spaces = tab_width - rem;
2612 for _ in 0..spaces {
2613 insert_text.push(' ');
2614 }
2615
2616 ops.push(Op {
2617 selection_index,
2618 start_offset,
2619 start_after: start_offset,
2620 delete_len,
2621 deleted_text,
2622 insert_text,
2623 insert_char_len: start_pad + spaces,
2624 });
2625 }
2626 }
2627 }
2628
2629 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
2632 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
2633
2634 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
2635 let mut delta: i64 = 0;
2636 for &idx in &asc_indices {
2637 let op = &mut ops[idx];
2638 let effective_start = (op.start_offset as i64 + delta) as usize;
2639 op.start_after = effective_start;
2640 caret_offsets[op.selection_index] = effective_start + op.insert_char_len;
2641 delta += op.insert_char_len as i64 - op.delete_len as i64;
2642 }
2643
2644 let mut desc_indices = asc_indices;
2646 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
2647
2648 for &idx in &desc_indices {
2649 let op = &ops[idx];
2650
2651 let edit_line = self
2652 .editor
2653 .line_index
2654 .char_offset_to_position(op.start_offset)
2655 .0;
2656 let deleted_newlines = op
2657 .deleted_text
2658 .as_bytes()
2659 .iter()
2660 .filter(|b| **b == b'\n')
2661 .count();
2662 let inserted_newlines = op
2663 .insert_text
2664 .as_bytes()
2665 .iter()
2666 .filter(|b| **b == b'\n')
2667 .count();
2668 let line_delta = inserted_newlines as isize - deleted_newlines as isize;
2669 if line_delta != 0 {
2670 self.editor
2671 .folding_manager
2672 .apply_line_delta(edit_line, line_delta);
2673 }
2674
2675 if op.delete_len > 0 {
2676 self.editor
2677 .piece_table
2678 .delete(op.start_offset, op.delete_len);
2679 self.editor
2680 .interval_tree
2681 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
2682 for layer_tree in self.editor.style_layers.values_mut() {
2683 layer_tree
2684 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
2685 }
2686 }
2687
2688 if !op.insert_text.is_empty() {
2689 self.editor
2690 .piece_table
2691 .insert(op.start_offset, &op.insert_text);
2692 self.editor
2693 .interval_tree
2694 .update_for_insertion(op.start_offset, op.insert_char_len);
2695 for layer_tree in self.editor.style_layers.values_mut() {
2696 layer_tree.update_for_insertion(op.start_offset, op.insert_char_len);
2697 }
2698 }
2699
2700 self.apply_text_change_to_line_index_and_layout(
2701 op.start_offset,
2702 &op.deleted_text,
2703 &op.insert_text,
2704 );
2705 }
2706
2707 self.editor
2708 .folding_manager
2709 .clamp_to_line_count(self.editor.line_index.line_count());
2710
2711 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
2713 for offset in &caret_offsets {
2714 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
2715 let pos = Position::new(line, column);
2716 new_carets.push(Selection {
2717 start: pos,
2718 end: pos,
2719 direction: SelectionDirection::Forward,
2720 });
2721 }
2722
2723 let (new_carets, new_primary_index) =
2724 crate::selection_set::normalize_selections(new_carets, primary_index);
2725 let primary = new_carets
2726 .get(new_primary_index)
2727 .cloned()
2728 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
2729
2730 self.editor.cursor_position = primary.end;
2731 self.editor.selection = None;
2732 self.editor.secondary_selections = new_carets
2733 .into_iter()
2734 .enumerate()
2735 .filter_map(|(idx, sel)| {
2736 if idx == new_primary_index {
2737 None
2738 } else {
2739 Some(sel)
2740 }
2741 })
2742 .collect();
2743
2744 let after_selection = self.snapshot_selection_set();
2745
2746 let edits: Vec<TextEdit> = ops
2747 .into_iter()
2748 .map(|op| TextEdit {
2749 start_before: op.start_offset,
2750 start_after: op.start_after,
2751 deleted_text: op.deleted_text,
2752 inserted_text: op.insert_text,
2753 })
2754 .collect();
2755
2756 let is_pure_insert = edits.iter().all(|e| e.deleted_text.is_empty());
2757 let coalescible_insert = is_pure_insert;
2758
2759 let mut delta_edits: Vec<TextDeltaEdit> = edits
2760 .iter()
2761 .map(|e| TextDeltaEdit {
2762 start: e.start_before,
2763 deleted_text: e.deleted_text.clone(),
2764 inserted_text: e.inserted_text.clone(),
2765 })
2766 .collect();
2767 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
2768
2769 let step = UndoStep {
2770 group_id: 0,
2771 edits,
2772 before_selection,
2773 after_selection,
2774 };
2775 let group_id = self.undo_redo.push_step(step, coalescible_insert);
2776
2777 self.last_text_delta = Some(TextDelta {
2778 before_char_count,
2779 after_char_count: self.editor.piece_table.char_count(),
2780 edits: delta_edits,
2781 undo_group_id: Some(group_id),
2782 });
2783
2784 Ok(CommandResult::Success)
2785 }
2786
2787 fn leading_whitespace_prefix(line_text: &str) -> String {
2788 line_text
2789 .chars()
2790 .take_while(|ch| *ch == ' ' || *ch == '\t')
2791 .collect()
2792 }
2793
2794 fn indent_unit(&self) -> String {
2795 match self.tab_key_behavior {
2796 TabKeyBehavior::Tab => "\t".to_string(),
2797 TabKeyBehavior::Spaces => " ".repeat(self.editor.layout_engine.tab_width().max(1)),
2798 }
2799 }
2800
2801 fn execute_insert_newline_command(
2802 &mut self,
2803 auto_indent: bool,
2804 ) -> Result<CommandResult, CommandError> {
2805 self.undo_redo.end_group();
2807
2808 let before_char_count = self.editor.piece_table.char_count();
2809 let before_selection = self.snapshot_selection_set();
2810
2811 let mut selections: Vec<Selection> =
2813 Vec::with_capacity(1 + self.editor.secondary_selections.len());
2814 let primary_selection = self.editor.selection.clone().unwrap_or(Selection {
2815 start: self.editor.cursor_position,
2816 end: self.editor.cursor_position,
2817 direction: SelectionDirection::Forward,
2818 });
2819 selections.push(primary_selection);
2820 selections.extend(self.editor.secondary_selections.iter().cloned());
2821
2822 let (selections, primary_index) = crate::selection_set::normalize_selections(selections, 0);
2823
2824 struct Op {
2825 selection_index: usize,
2826 start_offset: usize,
2827 start_after: usize,
2828 delete_len: usize,
2829 deleted_text: String,
2830 insert_text: String,
2831 insert_char_len: usize,
2832 }
2833
2834 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
2835
2836 for (selection_index, selection) in selections.iter().enumerate() {
2837 let (range_start_pos, range_end_pos) =
2838 crate::selection_set::selection_min_max(selection);
2839
2840 let start_offset = self.position_to_char_offset_clamped(range_start_pos);
2841 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
2842
2843 let delete_len = end_offset.saturating_sub(start_offset);
2844 let deleted_text = if delete_len == 0 {
2845 String::new()
2846 } else {
2847 self.editor.piece_table.get_range(start_offset, delete_len)
2848 };
2849
2850 let indent = if auto_indent {
2851 let line_text = self
2852 .editor
2853 .line_index
2854 .get_line_text(range_start_pos.line)
2855 .unwrap_or_default();
2856 Self::leading_whitespace_prefix(&line_text)
2857 } else {
2858 String::new()
2859 };
2860
2861 let insert_text = format!("\n{}", indent);
2862 let insert_char_len = insert_text.chars().count();
2863
2864 ops.push(Op {
2865 selection_index,
2866 start_offset,
2867 start_after: start_offset,
2868 delete_len,
2869 deleted_text,
2870 insert_text,
2871 insert_char_len,
2872 });
2873 }
2874
2875 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
2878 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
2879
2880 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
2881 let mut delta: i64 = 0;
2882 for &idx in &asc_indices {
2883 let op = &mut ops[idx];
2884 let effective_start = (op.start_offset as i64 + delta) as usize;
2885 op.start_after = effective_start;
2886 caret_offsets[op.selection_index] = effective_start + op.insert_char_len;
2887 delta += op.insert_char_len as i64 - op.delete_len as i64;
2888 }
2889
2890 let mut desc_indices = asc_indices;
2892 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
2893
2894 for &idx in &desc_indices {
2895 let op = &ops[idx];
2896
2897 if op.delete_len > 0 {
2898 self.editor
2899 .piece_table
2900 .delete(op.start_offset, op.delete_len);
2901 self.editor
2902 .interval_tree
2903 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
2904 for layer_tree in self.editor.style_layers.values_mut() {
2905 layer_tree
2906 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
2907 }
2908 }
2909
2910 if !op.insert_text.is_empty() {
2911 self.editor
2912 .piece_table
2913 .insert(op.start_offset, &op.insert_text);
2914 self.editor
2915 .interval_tree
2916 .update_for_insertion(op.start_offset, op.insert_char_len);
2917 for layer_tree in self.editor.style_layers.values_mut() {
2918 layer_tree.update_for_insertion(op.start_offset, op.insert_char_len);
2919 }
2920 }
2921
2922 self.apply_text_change_to_line_index_and_layout(
2923 op.start_offset,
2924 &op.deleted_text,
2925 &op.insert_text,
2926 );
2927 }
2928
2929 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
2931 for offset in &caret_offsets {
2932 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
2933 let pos = Position::new(line, column);
2934 new_carets.push(Selection {
2935 start: pos,
2936 end: pos,
2937 direction: SelectionDirection::Forward,
2938 });
2939 }
2940
2941 let (new_carets, new_primary_index) =
2942 crate::selection_set::normalize_selections(new_carets, primary_index);
2943 let primary = new_carets
2944 .get(new_primary_index)
2945 .cloned()
2946 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
2947
2948 self.editor.cursor_position = primary.end;
2949 self.editor.selection = None;
2950 self.editor.secondary_selections = new_carets
2951 .into_iter()
2952 .enumerate()
2953 .filter_map(|(idx, sel)| {
2954 if idx == new_primary_index {
2955 None
2956 } else {
2957 Some(sel)
2958 }
2959 })
2960 .collect();
2961
2962 let after_selection = self.snapshot_selection_set();
2963
2964 let edits: Vec<TextEdit> = ops
2965 .into_iter()
2966 .map(|op| TextEdit {
2967 start_before: op.start_offset,
2968 start_after: op.start_after,
2969 deleted_text: op.deleted_text,
2970 inserted_text: op.insert_text,
2971 })
2972 .collect();
2973
2974 let mut delta_edits: Vec<TextDeltaEdit> = edits
2975 .iter()
2976 .map(|e| TextDeltaEdit {
2977 start: e.start_before,
2978 deleted_text: e.deleted_text.clone(),
2979 inserted_text: e.inserted_text.clone(),
2980 })
2981 .collect();
2982 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
2983
2984 let step = UndoStep {
2985 group_id: 0,
2986 edits,
2987 before_selection,
2988 after_selection,
2989 };
2990 let group_id = self.undo_redo.push_step(step, false);
2991
2992 self.last_text_delta = Some(TextDelta {
2993 before_char_count,
2994 after_char_count: self.editor.piece_table.char_count(),
2995 edits: delta_edits,
2996 undo_group_id: Some(group_id),
2997 });
2998
2999 Ok(CommandResult::Success)
3000 }
3001
3002 fn execute_indent_command(&mut self, outdent: bool) -> Result<CommandResult, CommandError> {
3003 self.undo_redo.end_group();
3004
3005 let before_char_count = self.editor.piece_table.char_count();
3006 let before_selection = self.snapshot_selection_set();
3007 let selections = before_selection.selections.clone();
3008
3009 let mut lines: Vec<usize> = Vec::new();
3010 for sel in &selections {
3011 let (min_pos, max_pos) = crate::selection_set::selection_min_max(sel);
3012 for line in min_pos.line..=max_pos.line {
3013 lines.push(line);
3014 }
3015 }
3016 lines.sort_unstable();
3017 lines.dedup();
3018
3019 if lines.is_empty() {
3020 return Ok(CommandResult::Success);
3021 }
3022
3023 let tab_width = self.editor.layout_engine.tab_width().max(1);
3024 let indent_unit = self.indent_unit();
3025 let indent_chars = indent_unit.chars().count();
3026
3027 #[derive(Debug)]
3028 struct Op {
3029 start_offset: usize,
3030 start_after: usize,
3031 delete_len: usize,
3032 deleted_text: String,
3033 insert_text: String,
3034 insert_len: usize,
3035 }
3036
3037 let mut ops: Vec<Op> = Vec::new();
3038 let mut line_deltas: std::collections::HashMap<usize, isize> =
3039 std::collections::HashMap::new();
3040
3041 for line in lines {
3042 if line >= self.editor.line_index.line_count() {
3043 continue;
3044 }
3045
3046 let start_offset = self.editor.line_index.position_to_char_offset(line, 0);
3047 let line_text = self
3048 .editor
3049 .line_index
3050 .get_line_text(line)
3051 .unwrap_or_default();
3052
3053 if outdent {
3054 let mut remove_len = 0usize;
3055 if let Some(first) = line_text.chars().next() {
3056 if first == '\t' {
3057 remove_len = 1;
3058 } else if first == ' ' {
3059 let leading_spaces = line_text.chars().take_while(|c| *c == ' ').count();
3060 remove_len = leading_spaces.min(tab_width);
3061 }
3062 }
3063
3064 if remove_len == 0 {
3065 continue;
3066 }
3067
3068 let deleted_text = self.editor.piece_table.get_range(start_offset, remove_len);
3069 ops.push(Op {
3070 start_offset,
3071 start_after: start_offset,
3072 delete_len: remove_len,
3073 deleted_text,
3074 insert_text: String::new(),
3075 insert_len: 0,
3076 });
3077 line_deltas.insert(line, -(remove_len as isize));
3078 } else {
3079 if indent_chars == 0 {
3080 continue;
3081 }
3082
3083 ops.push(Op {
3084 start_offset,
3085 start_after: start_offset,
3086 delete_len: 0,
3087 deleted_text: String::new(),
3088 insert_text: indent_unit.clone(),
3089 insert_len: indent_chars,
3090 });
3091 line_deltas.insert(line, indent_chars as isize);
3092 }
3093 }
3094
3095 if ops.is_empty() {
3096 return Ok(CommandResult::Success);
3097 }
3098
3099 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
3101 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
3102
3103 let mut delta: i64 = 0;
3104 for &idx in &asc_indices {
3105 let op = &mut ops[idx];
3106 let effective_start = (op.start_offset as i64 + delta) as usize;
3107 op.start_after = effective_start;
3108 delta += op.insert_len as i64 - op.delete_len as i64;
3109 }
3110
3111 let mut desc_indices = asc_indices;
3113 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
3114
3115 for &idx in &desc_indices {
3116 let op = &ops[idx];
3117
3118 if op.delete_len > 0 {
3119 self.editor
3120 .piece_table
3121 .delete(op.start_offset, op.delete_len);
3122 self.editor
3123 .interval_tree
3124 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
3125 for layer_tree in self.editor.style_layers.values_mut() {
3126 layer_tree
3127 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
3128 }
3129 }
3130
3131 if op.insert_len > 0 {
3132 self.editor
3133 .piece_table
3134 .insert(op.start_offset, &op.insert_text);
3135 self.editor
3136 .interval_tree
3137 .update_for_insertion(op.start_offset, op.insert_len);
3138 for layer_tree in self.editor.style_layers.values_mut() {
3139 layer_tree.update_for_insertion(op.start_offset, op.insert_len);
3140 }
3141 }
3142
3143 self.apply_text_change_to_line_index_and_layout(
3144 op.start_offset,
3145 &op.deleted_text,
3146 &op.insert_text,
3147 );
3148 }
3149
3150 let line_index = &self.editor.line_index;
3152 let apply_delta = |pos: &mut Position, deltas: &std::collections::HashMap<usize, isize>| {
3153 let Some(delta) = deltas.get(&pos.line) else {
3154 return;
3155 };
3156
3157 let new_col = if *delta >= 0 {
3158 pos.column.saturating_add(*delta as usize)
3159 } else {
3160 pos.column.saturating_sub((-*delta) as usize)
3161 };
3162
3163 pos.column = Self::clamp_column_for_line_with_index(line_index, pos.line, new_col);
3164 };
3165
3166 apply_delta(&mut self.editor.cursor_position, &line_deltas);
3167 if let Some(sel) = &mut self.editor.selection {
3168 apply_delta(&mut sel.start, &line_deltas);
3169 apply_delta(&mut sel.end, &line_deltas);
3170 }
3171 for sel in &mut self.editor.secondary_selections {
3172 apply_delta(&mut sel.start, &line_deltas);
3173 apply_delta(&mut sel.end, &line_deltas);
3174 }
3175
3176 self.normalize_cursor_and_selection();
3177 self.preferred_x_cells = self
3178 .editor
3179 .logical_position_to_visual(
3180 self.editor.cursor_position.line,
3181 self.editor.cursor_position.column,
3182 )
3183 .map(|(_, x)| x);
3184
3185 let after_selection = self.snapshot_selection_set();
3186
3187 let edits: Vec<TextEdit> = ops
3188 .into_iter()
3189 .map(|op| TextEdit {
3190 start_before: op.start_offset,
3191 start_after: op.start_after,
3192 deleted_text: op.deleted_text,
3193 inserted_text: op.insert_text,
3194 })
3195 .collect();
3196
3197 let mut delta_edits: Vec<TextDeltaEdit> = edits
3198 .iter()
3199 .map(|e| TextDeltaEdit {
3200 start: e.start_before,
3201 deleted_text: e.deleted_text.clone(),
3202 inserted_text: e.inserted_text.clone(),
3203 })
3204 .collect();
3205 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
3206
3207 let step = UndoStep {
3208 group_id: 0,
3209 edits,
3210 before_selection,
3211 after_selection,
3212 };
3213 let group_id = self.undo_redo.push_step(step, false);
3214
3215 self.last_text_delta = Some(TextDelta {
3216 before_char_count,
3217 after_char_count: self.editor.piece_table.char_count(),
3218 edits: delta_edits,
3219 undo_group_id: Some(group_id),
3220 });
3221
3222 Ok(CommandResult::Success)
3223 }
3224
3225 fn selection_char_range(&self, selection: &Selection) -> SearchMatch {
3226 let (min_pos, max_pos) = crate::selection_set::selection_min_max(selection);
3227 let start = self.position_to_char_offset_clamped(min_pos);
3228 let end = self.position_to_char_offset_clamped(max_pos);
3229 SearchMatch {
3230 start: start.min(end),
3231 end: start.max(end),
3232 }
3233 }
3234
3235 fn selected_line_blocks(selections: &[Selection]) -> Vec<(usize, usize)> {
3236 let mut lines: Vec<usize> = Vec::new();
3237 for sel in selections {
3238 let (min_pos, max_pos) = crate::selection_set::selection_min_max(sel);
3239 for line in min_pos.line..=max_pos.line {
3240 lines.push(line);
3241 }
3242 }
3243
3244 lines.sort_unstable();
3245 lines.dedup();
3246
3247 let mut blocks: Vec<(usize, usize)> = Vec::new();
3248 for line in lines {
3249 if let Some((_, end)) = blocks.last_mut()
3250 && *end + 1 == line
3251 {
3252 *end = line;
3253 continue;
3254 }
3255 blocks.push((line, line));
3256 }
3257 blocks
3258 }
3259
3260 fn slice_text_for_lines(&self, start_line: usize, end_line: usize) -> String {
3261 let line_count = self.editor.line_index.line_count();
3262 if line_count == 0 || start_line >= line_count || start_line > end_line {
3263 return String::new();
3264 }
3265
3266 let mut out = String::new();
3267 for line in start_line..=end_line.min(line_count - 1) {
3268 let text = self
3269 .editor
3270 .line_index
3271 .get_line_text(line)
3272 .unwrap_or_default();
3273 out.push_str(&text);
3274 if line + 1 < line_count {
3276 out.push('\n');
3277 }
3278 }
3279 out
3280 }
3281
3282 fn execute_duplicate_lines_command(&mut self) -> Result<CommandResult, CommandError> {
3283 self.undo_redo.end_group();
3284
3285 let before_char_count = self.editor.piece_table.char_count();
3286 let before_selection = self.snapshot_selection_set();
3287 let selections = before_selection.selections.clone();
3288 let primary_index = before_selection.primary_index;
3289
3290 let line_count = self.editor.line_index.line_count();
3291 if line_count == 0 {
3292 return Ok(CommandResult::Success);
3293 }
3294
3295 let blocks = Self::selected_line_blocks(&selections);
3296 if blocks.is_empty() {
3297 return Ok(CommandResult::Success);
3298 }
3299
3300 let doc_text = self.editor.piece_table.get_text();
3301 let doc_ends_with_newline = doc_text.ends_with('\n');
3302
3303 struct Op {
3304 start_before: usize,
3305 start_after: usize,
3306 deleted_text: String,
3307 inserted_text: String,
3308 inserted_len: usize,
3309 }
3310
3311 let mut ops: Vec<Op> = Vec::new();
3312
3313 for (start_line, end_line) in blocks {
3314 if start_line >= line_count {
3315 continue;
3316 }
3317 let end_line = end_line.min(line_count - 1);
3318
3319 let insertion_offset = if end_line + 1 < line_count {
3320 self.editor
3321 .line_index
3322 .position_to_char_offset(end_line + 1, 0)
3323 } else {
3324 before_char_count
3325 };
3326
3327 let block_text = self.slice_text_for_lines(start_line, end_line);
3328 if block_text.is_empty() && before_char_count == 0 {
3329 continue;
3330 }
3331
3332 let mut inserted_text = block_text;
3333 if insertion_offset == before_char_count
3334 && !doc_ends_with_newline
3335 && before_char_count > 0
3336 {
3337 inserted_text.insert(0, '\n');
3338 }
3339
3340 let inserted_len = inserted_text.chars().count();
3341 if inserted_len == 0 {
3342 continue;
3343 }
3344
3345 ops.push(Op {
3346 start_before: insertion_offset,
3347 start_after: insertion_offset,
3348 deleted_text: String::new(),
3349 inserted_text,
3350 inserted_len,
3351 });
3352 }
3353
3354 if ops.is_empty() {
3355 return Ok(CommandResult::Success);
3356 }
3357
3358 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
3360 asc_indices.sort_by_key(|&idx| ops[idx].start_before);
3361
3362 let mut delta: i64 = 0;
3363 for &idx in &asc_indices {
3364 let op = &mut ops[idx];
3365 let effective_start = op.start_before as i64 + delta;
3366 if effective_start < 0 {
3367 return Err(CommandError::Other(
3368 "DuplicateLines produced an invalid intermediate offset".to_string(),
3369 ));
3370 }
3371 op.start_after = effective_start as usize;
3372 delta += op.inserted_len as i64;
3373 }
3374
3375 let apply_ops: Vec<(usize, usize, &str)> = ops
3376 .iter()
3377 .map(|op| (op.start_before, 0usize, op.inserted_text.as_str()))
3378 .collect();
3379 self.apply_text_ops(apply_ops)?;
3380
3381 let mut mapped: Vec<Selection> = Vec::with_capacity(selections.len());
3383
3384 let mut block_info: Vec<(usize, usize, usize, usize)> = Vec::new(); let mut cumulative = 0usize;
3387 let mut blocks = Self::selected_line_blocks(&selections);
3388 blocks.sort_by_key(|(s, _)| *s);
3389 for (s, e) in blocks {
3390 let size = e.saturating_sub(s) + 1;
3391 block_info.push((s, e, size, cumulative));
3392 cumulative = cumulative.saturating_add(size);
3393 }
3394
3395 let line_index = &self.editor.line_index;
3396 for sel in selections {
3397 let mut start = sel.start;
3398 let mut end = sel.end;
3399
3400 let map_line = |line: usize, info: &[(usize, usize, usize, usize)]| -> usize {
3401 for (s, e, size, shift_before) in info {
3403 if line >= *s && line <= *e {
3404 return line + *shift_before + *size;
3405 }
3406 if line < *s {
3407 break;
3408 }
3409 }
3410
3411 let mut shift = 0usize;
3413 for (s, e, size, shift_before) in info {
3414 let _ = shift_before;
3415 if *e < line {
3416 shift = shift.saturating_add(*size);
3417 } else if line < *s {
3418 break;
3419 }
3420 }
3421 line + shift
3422 };
3423
3424 start.line = map_line(start.line, &block_info);
3425 end.line = map_line(end.line, &block_info);
3426
3427 start.column =
3428 Self::clamp_column_for_line_with_index(line_index, start.line, start.column);
3429 end.column = Self::clamp_column_for_line_with_index(line_index, end.line, end.column);
3430
3431 mapped.push(Selection {
3432 start,
3433 end,
3434 direction: crate::selection_set::selection_direction(start, end),
3435 });
3436 }
3437
3438 let (mapped, mapped_primary) =
3439 crate::selection_set::normalize_selections(mapped, primary_index);
3440 self.execute_cursor(CursorCommand::SetSelections {
3441 selections: mapped,
3442 primary_index: mapped_primary,
3443 })?;
3444
3445 let after_selection = self.snapshot_selection_set();
3446
3447 let edits: Vec<TextEdit> = ops
3448 .into_iter()
3449 .map(|op| TextEdit {
3450 start_before: op.start_before,
3451 start_after: op.start_after,
3452 deleted_text: op.deleted_text,
3453 inserted_text: op.inserted_text,
3454 })
3455 .collect();
3456
3457 let mut delta_edits: Vec<TextDeltaEdit> = edits
3458 .iter()
3459 .map(|e| TextDeltaEdit {
3460 start: e.start_before,
3461 deleted_text: e.deleted_text.clone(),
3462 inserted_text: e.inserted_text.clone(),
3463 })
3464 .collect();
3465 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
3466
3467 let step = UndoStep {
3468 group_id: 0,
3469 edits,
3470 before_selection,
3471 after_selection,
3472 };
3473 let group_id = self.undo_redo.push_step(step, false);
3474
3475 self.last_text_delta = Some(TextDelta {
3476 before_char_count,
3477 after_char_count: self.editor.piece_table.char_count(),
3478 edits: delta_edits,
3479 undo_group_id: Some(group_id),
3480 });
3481
3482 Ok(CommandResult::Success)
3483 }
3484
3485 fn execute_delete_lines_command(&mut self) -> Result<CommandResult, CommandError> {
3486 self.undo_redo.end_group();
3487
3488 let before_char_count = self.editor.piece_table.char_count();
3489 let before_selection = self.snapshot_selection_set();
3490 let selections = before_selection.selections.clone();
3491 let primary_selection = selections
3492 .get(before_selection.primary_index)
3493 .cloned()
3494 .unwrap_or_else(|| selections[0].clone());
3495
3496 let line_count = self.editor.line_index.line_count();
3497 if line_count == 0 {
3498 return Ok(CommandResult::Success);
3499 }
3500
3501 let blocks = Self::selected_line_blocks(&selections);
3502 if blocks.is_empty() {
3503 return Ok(CommandResult::Success);
3504 }
3505
3506 struct Op {
3507 start_before: usize,
3508 start_after: usize,
3509 delete_len: usize,
3510 deleted_text: String,
3511 }
3512
3513 let mut ops: Vec<Op> = Vec::new();
3514 let mut primary_op_index = 0usize;
3515
3516 for (idx, (start_line, end_line)) in blocks.into_iter().enumerate() {
3517 if start_line >= line_count {
3518 continue;
3519 }
3520
3521 let end_line = end_line.min(line_count - 1);
3522 let mut start_offset = self
3523 .editor
3524 .line_index
3525 .position_to_char_offset(start_line, 0);
3526 let end_offset = if end_line + 1 < line_count {
3527 self.editor
3528 .line_index
3529 .position_to_char_offset(end_line + 1, 0)
3530 } else {
3531 before_char_count
3532 };
3533
3534 if end_line + 1 >= line_count && start_offset > 0 {
3535 start_offset = start_offset.saturating_sub(1);
3537 }
3538
3539 if end_offset <= start_offset {
3540 continue;
3541 }
3542
3543 let delete_len = end_offset - start_offset;
3544 let deleted_text = self.editor.piece_table.get_range(start_offset, delete_len);
3545
3546 if crate::selection_set::selection_contains_position_inclusive(
3547 &primary_selection,
3548 Position::new(start_line, 0),
3549 ) {
3550 primary_op_index = idx;
3551 }
3552
3553 ops.push(Op {
3554 start_before: start_offset,
3555 start_after: start_offset,
3556 delete_len,
3557 deleted_text,
3558 });
3559 }
3560
3561 if ops.is_empty() {
3562 return Ok(CommandResult::Success);
3563 }
3564
3565 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
3567 asc_indices.sort_by_key(|&idx| ops[idx].start_before);
3568
3569 let mut delta: i64 = 0;
3570 for &idx in &asc_indices {
3571 let op = &mut ops[idx];
3572 let effective_start = op.start_before as i64 + delta;
3573 if effective_start < 0 {
3574 return Err(CommandError::Other(
3575 "DeleteLines produced an invalid intermediate offset".to_string(),
3576 ));
3577 }
3578 op.start_after = effective_start as usize;
3579 delta -= op.delete_len as i64;
3580 }
3581
3582 let apply_ops: Vec<(usize, usize, &str)> = ops
3583 .iter()
3584 .map(|op| (op.start_before, op.delete_len, ""))
3585 .collect();
3586 self.apply_text_ops(apply_ops)?;
3587
3588 let mut new_carets: Vec<Selection> = Vec::with_capacity(ops.len());
3590 for op in &ops {
3591 let (line, column) = self
3592 .editor
3593 .line_index
3594 .char_offset_to_position(op.start_after);
3595 let pos = Position::new(line, column);
3596 new_carets.push(Selection {
3597 start: pos,
3598 end: pos,
3599 direction: SelectionDirection::Forward,
3600 });
3601 }
3602
3603 let primary_index = primary_op_index.min(new_carets.len().saturating_sub(1));
3604 self.execute_cursor(CursorCommand::SetSelections {
3605 selections: new_carets,
3606 primary_index,
3607 })?;
3608
3609 let after_selection = self.snapshot_selection_set();
3610
3611 let edits: Vec<TextEdit> = ops
3612 .into_iter()
3613 .map(|op| TextEdit {
3614 start_before: op.start_before,
3615 start_after: op.start_after,
3616 deleted_text: op.deleted_text,
3617 inserted_text: String::new(),
3618 })
3619 .collect();
3620
3621 let mut delta_edits: Vec<TextDeltaEdit> = edits
3622 .iter()
3623 .map(|e| TextDeltaEdit {
3624 start: e.start_before,
3625 deleted_text: e.deleted_text.clone(),
3626 inserted_text: String::new(),
3627 })
3628 .collect();
3629 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
3630
3631 let step = UndoStep {
3632 group_id: 0,
3633 edits,
3634 before_selection,
3635 after_selection,
3636 };
3637 let group_id = self.undo_redo.push_step(step, false);
3638
3639 self.last_text_delta = Some(TextDelta {
3640 before_char_count,
3641 after_char_count: self.editor.piece_table.char_count(),
3642 edits: delta_edits,
3643 undo_group_id: Some(group_id),
3644 });
3645
3646 Ok(CommandResult::Success)
3647 }
3648
3649 fn execute_move_lines_command(&mut self, up: bool) -> Result<CommandResult, CommandError> {
3650 self.undo_redo.end_group();
3651
3652 let before_char_count = self.editor.piece_table.char_count();
3653 let before_selection = self.snapshot_selection_set();
3654 let selections = before_selection.selections.clone();
3655 let primary_index = before_selection.primary_index;
3656
3657 let line_count = self.editor.line_index.line_count();
3658 if line_count <= 1 {
3659 return Ok(CommandResult::Success);
3660 }
3661
3662 let blocks = Self::selected_line_blocks(&selections);
3663 if blocks.is_empty() {
3664 return Ok(CommandResult::Success);
3665 }
3666
3667 #[derive(Debug, Clone, Copy)]
3668 struct Block {
3669 start: usize,
3670 end: usize,
3671 }
3672
3673 let mut moved_blocks: Vec<Block> = Vec::new();
3674 for (start, end) in blocks {
3675 let start = start.min(line_count - 1);
3676 let end = end.min(line_count - 1);
3677 if up {
3678 if start == 0 {
3679 continue;
3680 }
3681 } else if end + 1 >= line_count {
3682 continue;
3683 }
3684 moved_blocks.push(Block { start, end });
3685 }
3686
3687 if moved_blocks.is_empty() {
3688 return Ok(CommandResult::Success);
3689 }
3690
3691 struct Op {
3692 start_before: usize,
3693 start_after: usize,
3694 delete_len: usize,
3695 deleted_text: String,
3696 inserted_text: String,
3697 }
3698
3699 let mut ops: Vec<Op> = Vec::with_capacity(moved_blocks.len());
3700
3701 for block in &moved_blocks {
3702 let (range_start_line, range_end_line) = if up {
3703 (block.start - 1, block.end)
3704 } else {
3705 (block.start, block.end + 1)
3706 };
3707
3708 let start_offset = self
3709 .editor
3710 .line_index
3711 .position_to_char_offset(range_start_line, 0);
3712 let end_offset = if range_end_line + 1 < line_count {
3713 self.editor
3714 .line_index
3715 .position_to_char_offset(range_end_line + 1, 0)
3716 } else {
3717 before_char_count
3718 };
3719
3720 if end_offset <= start_offset {
3721 continue;
3722 }
3723
3724 let deleted_text = self
3725 .editor
3726 .piece_table
3727 .get_range(start_offset, end_offset - start_offset);
3728
3729 let block_text = self.slice_text_for_lines(block.start, block.end);
3730
3731 let inserted_text = if up {
3732 let above_text = self.slice_text_for_lines(block.start - 1, block.start - 1);
3733 format!("{}{}", block_text, above_text)
3734 } else {
3735 let below_text = self.slice_text_for_lines(block.end + 1, block.end + 1);
3736 format!("{}{}", below_text, block_text)
3737 };
3738
3739 ops.push(Op {
3740 start_before: start_offset,
3741 start_after: start_offset,
3742 delete_len: end_offset - start_offset,
3743 deleted_text,
3744 inserted_text,
3745 });
3746 }
3747
3748 if ops.is_empty() {
3749 return Ok(CommandResult::Success);
3750 }
3751
3752 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
3754 asc_indices.sort_by_key(|&idx| ops[idx].start_before);
3755
3756 let mut delta: i64 = 0;
3757 for &idx in &asc_indices {
3758 let op = &mut ops[idx];
3759 let effective_start = op.start_before as i64 + delta;
3760 if effective_start < 0 {
3761 return Err(CommandError::Other(
3762 "MoveLines produced an invalid intermediate offset".to_string(),
3763 ));
3764 }
3765 op.start_after = effective_start as usize;
3766 let inserted_len = op.inserted_text.chars().count() as i64;
3767 delta += inserted_len - op.delete_len as i64;
3768 }
3769
3770 let apply_ops: Vec<(usize, usize, &str)> = ops
3771 .iter()
3772 .map(|op| (op.start_before, op.delete_len, op.inserted_text.as_str()))
3773 .collect();
3774 self.apply_text_ops(apply_ops)?;
3775
3776 let line_index = &self.editor.line_index;
3778 let mut mapped: Vec<Selection> = Vec::with_capacity(selections.len());
3779
3780 for sel in selections {
3781 let mut start = sel.start;
3782 let mut end = sel.end;
3783
3784 let map_line = |line: usize, moved_blocks: &[Block], up: bool| -> usize {
3785 for block in moved_blocks {
3786 let size = block.end.saturating_sub(block.start) + 1;
3787 if line >= block.start && line <= block.end {
3788 return if up { line - 1 } else { line + 1 };
3789 }
3790 if up && line == block.start - 1 {
3791 return line + size;
3792 }
3793 if !up && line == block.end + 1 {
3794 return line.saturating_sub(size);
3795 }
3796 }
3797 line
3798 };
3799
3800 start.line = map_line(start.line, &moved_blocks, up);
3801 end.line = map_line(end.line, &moved_blocks, up);
3802
3803 start.column =
3804 Self::clamp_column_for_line_with_index(line_index, start.line, start.column);
3805 end.column = Self::clamp_column_for_line_with_index(line_index, end.line, end.column);
3806
3807 mapped.push(Selection {
3808 start,
3809 end,
3810 direction: crate::selection_set::selection_direction(start, end),
3811 });
3812 }
3813
3814 let (mapped, mapped_primary) =
3815 crate::selection_set::normalize_selections(mapped, primary_index);
3816 self.execute_cursor(CursorCommand::SetSelections {
3817 selections: mapped,
3818 primary_index: mapped_primary,
3819 })?;
3820
3821 let after_selection = self.snapshot_selection_set();
3822
3823 let edits: Vec<TextEdit> = ops
3824 .into_iter()
3825 .map(|op| TextEdit {
3826 start_before: op.start_before,
3827 start_after: op.start_after,
3828 deleted_text: op.deleted_text,
3829 inserted_text: op.inserted_text,
3830 })
3831 .collect();
3832
3833 let mut delta_edits: Vec<TextDeltaEdit> = edits
3834 .iter()
3835 .map(|e| TextDeltaEdit {
3836 start: e.start_before,
3837 deleted_text: e.deleted_text.clone(),
3838 inserted_text: e.inserted_text.clone(),
3839 })
3840 .collect();
3841 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
3842
3843 let step = UndoStep {
3844 group_id: 0,
3845 edits,
3846 before_selection,
3847 after_selection,
3848 };
3849 let group_id = self.undo_redo.push_step(step, false);
3850
3851 self.last_text_delta = Some(TextDelta {
3852 before_char_count,
3853 after_char_count: self.editor.piece_table.char_count(),
3854 edits: delta_edits,
3855 undo_group_id: Some(group_id),
3856 });
3857
3858 Ok(CommandResult::Success)
3859 }
3860
3861 fn execute_join_lines_command(&mut self) -> Result<CommandResult, CommandError> {
3862 self.undo_redo.end_group();
3863
3864 let before_char_count = self.editor.piece_table.char_count();
3865 let before_selection = self.snapshot_selection_set();
3866 let selections = before_selection.selections.clone();
3867
3868 let line_count = self.editor.line_index.line_count();
3869 if line_count <= 1 {
3870 return Ok(CommandResult::Success);
3871 }
3872
3873 let mut join_lines: Vec<usize> = Vec::new();
3874 for sel in &selections {
3875 let (min_pos, max_pos) = crate::selection_set::selection_min_max(sel);
3876 if min_pos.line >= line_count {
3877 continue;
3878 }
3879 let last = max_pos.line.min(line_count - 1);
3880 if min_pos.line == last {
3881 join_lines.push(last);
3882 } else {
3883 for line in min_pos.line..last {
3884 join_lines.push(line);
3885 }
3886 }
3887 }
3888
3889 join_lines.sort_unstable();
3890 join_lines.dedup();
3891 join_lines.retain(|l| *l + 1 < line_count);
3892
3893 if join_lines.is_empty() {
3894 return Ok(CommandResult::Success);
3895 }
3896
3897 struct Op {
3898 start_before: usize,
3899 start_after: usize,
3900 delete_len: usize,
3901 deleted_text: String,
3902 inserted_text: String,
3903 inserted_len: usize,
3904 }
3905
3906 let mut ops: Vec<Op> = Vec::with_capacity(join_lines.len());
3907
3908 join_lines.sort_by_key(|l| std::cmp::Reverse(*l));
3910
3911 for line in join_lines {
3912 let line_text = self
3913 .editor
3914 .line_index
3915 .get_line_text(line)
3916 .unwrap_or_default();
3917 let next_text = self
3918 .editor
3919 .line_index
3920 .get_line_text(line + 1)
3921 .unwrap_or_default();
3922
3923 let line_len = line_text.chars().count();
3924 let join_offset = self
3925 .editor
3926 .line_index
3927 .position_to_char_offset(line, line_len);
3928 let leading_ws = next_text
3929 .chars()
3930 .take_while(|c| *c == ' ' || *c == '\t')
3931 .count();
3932 let end_offset = self
3933 .editor
3934 .line_index
3935 .position_to_char_offset(line + 1, leading_ws);
3936
3937 if end_offset <= join_offset {
3938 continue;
3939 }
3940
3941 let left_ends_with_ws = line_text
3942 .chars()
3943 .last()
3944 .is_some_and(|c| c == ' ' || c == '\t');
3945 let right_trimmed_empty = next_text.chars().nth(leading_ws).is_none();
3946 let insert_space = !left_ends_with_ws && !line_text.is_empty() && !right_trimmed_empty;
3947
3948 let inserted_text = if insert_space {
3949 " ".to_string()
3950 } else {
3951 String::new()
3952 };
3953 let inserted_len = inserted_text.chars().count();
3954 let delete_len = end_offset - join_offset;
3955 let deleted_text = self.editor.piece_table.get_range(join_offset, delete_len);
3956
3957 ops.push(Op {
3958 start_before: join_offset,
3959 start_after: join_offset,
3960 delete_len,
3961 deleted_text,
3962 inserted_text,
3963 inserted_len,
3964 });
3965 }
3966
3967 if ops.is_empty() {
3968 return Ok(CommandResult::Success);
3969 }
3970
3971 ops.sort_by_key(|op| op.start_before);
3972
3973 let mut delta: i64 = 0;
3974 for op in &mut ops {
3975 let effective_start = op.start_before as i64 + delta;
3976 if effective_start < 0 {
3977 return Err(CommandError::Other(
3978 "JoinLines produced an invalid intermediate offset".to_string(),
3979 ));
3980 }
3981 op.start_after = effective_start as usize;
3982 delta += op.inserted_len as i64 - op.delete_len as i64;
3983 }
3984
3985 let apply_ops: Vec<(usize, usize, &str)> = ops
3986 .iter()
3987 .map(|op| (op.start_before, op.delete_len, op.inserted_text.as_str()))
3988 .collect();
3989 self.apply_text_ops(apply_ops)?;
3990
3991 let mut new_carets: Vec<Selection> = Vec::with_capacity(ops.len());
3993 for op in &ops {
3994 let caret_offset = op.start_after + op.inserted_len;
3995 let (line, column) = self.editor.line_index.char_offset_to_position(caret_offset);
3996 let pos = Position::new(line, column);
3997 new_carets.push(Selection {
3998 start: pos,
3999 end: pos,
4000 direction: SelectionDirection::Forward,
4001 });
4002 }
4003
4004 let (new_carets, primary_index) = crate::selection_set::normalize_selections(new_carets, 0);
4005 self.execute_cursor(CursorCommand::SetSelections {
4006 selections: new_carets,
4007 primary_index,
4008 })?;
4009
4010 let after_selection = self.snapshot_selection_set();
4011
4012 let edits: Vec<TextEdit> = ops
4013 .into_iter()
4014 .map(|op| TextEdit {
4015 start_before: op.start_before,
4016 start_after: op.start_after,
4017 deleted_text: op.deleted_text,
4018 inserted_text: op.inserted_text,
4019 })
4020 .collect();
4021
4022 let mut delta_edits: Vec<TextDeltaEdit> = edits
4023 .iter()
4024 .map(|e| TextDeltaEdit {
4025 start: e.start_before,
4026 deleted_text: e.deleted_text.clone(),
4027 inserted_text: e.inserted_text.clone(),
4028 })
4029 .collect();
4030 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
4031
4032 let step = UndoStep {
4033 group_id: 0,
4034 edits,
4035 before_selection,
4036 after_selection,
4037 };
4038 let group_id = self.undo_redo.push_step(step, false);
4039
4040 self.last_text_delta = Some(TextDelta {
4041 before_char_count,
4042 after_char_count: self.editor.piece_table.char_count(),
4043 edits: delta_edits,
4044 undo_group_id: Some(group_id),
4045 });
4046
4047 Ok(CommandResult::Success)
4048 }
4049
4050 fn execute_toggle_comment_command(
4051 &mut self,
4052 config: CommentConfig,
4053 ) -> Result<CommandResult, CommandError> {
4054 if !config.has_line() && !config.has_block() {
4055 return Err(CommandError::Other(
4056 "ToggleComment requires at least one comment token".to_string(),
4057 ));
4058 }
4059
4060 self.undo_redo.end_group();
4061
4062 let before_char_count = self.editor.piece_table.char_count();
4063 let before_selection = self.snapshot_selection_set();
4064 let selections = before_selection.selections.clone();
4065 let primary_index = before_selection.primary_index;
4066
4067 let line_count = self.editor.line_index.line_count();
4068 if line_count == 0 {
4069 return Ok(CommandResult::Success);
4070 }
4071
4072 let all_single_line_selections = selections.iter().all(|sel| {
4073 let (min_pos, max_pos) = crate::selection_set::selection_min_max(sel);
4074 min_pos.line == max_pos.line && min_pos != max_pos
4075 });
4076
4077 if config.has_block()
4078 && all_single_line_selections
4079 && let (Some(block_start), Some(block_end)) =
4080 (config.block_start.as_deref(), config.block_end.as_deref())
4081 {
4082 return self.execute_toggle_block_comment_inline(
4083 block_start,
4084 block_end,
4085 before_char_count,
4086 before_selection,
4087 selections,
4088 primary_index,
4089 );
4090 }
4091
4092 if config.has_line()
4093 && let Some(token) = config.line.as_deref()
4094 {
4095 return self.execute_toggle_line_comment(
4096 token,
4097 before_char_count,
4098 before_selection,
4099 selections,
4100 primary_index,
4101 );
4102 }
4103
4104 if config.has_block()
4105 && let (Some(block_start), Some(block_end)) =
4106 (config.block_start.as_deref(), config.block_end.as_deref())
4107 {
4108 return self.execute_toggle_block_comment_lines(
4109 block_start,
4110 block_end,
4111 before_char_count,
4112 before_selection,
4113 selections,
4114 primary_index,
4115 );
4116 }
4117
4118 Ok(CommandResult::Success)
4119 }
4120
4121 fn execute_apply_text_edits_command(
4122 &mut self,
4123 mut edits: Vec<TextEditSpec>,
4124 ) -> Result<CommandResult, CommandError> {
4125 self.undo_redo.end_group();
4126
4127 if edits.is_empty() {
4128 return Ok(CommandResult::Success);
4129 }
4130
4131 let before_char_count = self.editor.piece_table.char_count();
4132 let before_selection = self.snapshot_selection_set();
4133
4134 let max_offset = before_char_count;
4135
4136 for edit in &mut edits {
4137 if edit.start > edit.end {
4138 return Err(CommandError::InvalidRange {
4139 start: edit.start,
4140 end: edit.end,
4141 });
4142 }
4143 if edit.end > max_offset {
4144 return Err(CommandError::InvalidRange {
4145 start: edit.start,
4146 end: edit.end,
4147 });
4148 }
4149 edit.text = crate::text::normalize_crlf_to_lf_string(edit.text.clone());
4150 }
4151
4152 edits.sort_by_key(|e| (e.start, e.end));
4153
4154 let mut prev_end = 0usize;
4156 for (idx, edit) in edits.iter().enumerate() {
4157 if idx > 0 && edit.start < prev_end {
4158 return Err(CommandError::Other(
4159 "ApplyTextEdits requires non-overlapping edits".to_string(),
4160 ));
4161 }
4162 prev_end = prev_end.max(edit.end);
4163 }
4164
4165 struct Op {
4166 start_before: usize,
4167 start_after: usize,
4168 delete_len: usize,
4169 deleted_text: String,
4170 inserted_text: String,
4171 inserted_len: usize,
4172 }
4173
4174 let mut ops: Vec<Op> = Vec::with_capacity(edits.len());
4175 for edit in edits {
4176 let delete_len = edit.end.saturating_sub(edit.start);
4177 let deleted_text = if delete_len == 0 {
4178 String::new()
4179 } else {
4180 self.editor.piece_table.get_range(edit.start, delete_len)
4181 };
4182
4183 let inserted_text = edit.text;
4184 let inserted_len = inserted_text.chars().count();
4185
4186 ops.push(Op {
4187 start_before: edit.start,
4188 start_after: edit.start,
4189 delete_len,
4190 deleted_text,
4191 inserted_text,
4192 inserted_len,
4193 });
4194 }
4195
4196 let mut delta: i64 = 0;
4198 for op in &mut ops {
4199 let effective_start = op.start_before as i64 + delta;
4200 if effective_start < 0 {
4201 return Err(CommandError::Other(
4202 "ApplyTextEdits produced an invalid intermediate offset".to_string(),
4203 ));
4204 }
4205 op.start_after = effective_start as usize;
4206 delta += op.inserted_len as i64 - op.delete_len as i64;
4207 }
4208
4209 let apply_ops: Vec<(usize, usize, &str)> = ops
4210 .iter()
4211 .map(|op| (op.start_before, op.delete_len, op.inserted_text.as_str()))
4212 .collect();
4213 self.apply_text_ops(apply_ops)?;
4214
4215 let after_selection = self.snapshot_selection_set();
4216
4217 let edits: Vec<TextEdit> = ops
4218 .into_iter()
4219 .map(|op| TextEdit {
4220 start_before: op.start_before,
4221 start_after: op.start_after,
4222 deleted_text: op.deleted_text,
4223 inserted_text: op.inserted_text,
4224 })
4225 .collect();
4226
4227 let mut delta_edits: Vec<TextDeltaEdit> = edits
4228 .iter()
4229 .map(|e| TextDeltaEdit {
4230 start: e.start_before,
4231 deleted_text: e.deleted_text.clone(),
4232 inserted_text: e.inserted_text.clone(),
4233 })
4234 .collect();
4235 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
4236
4237 let step = UndoStep {
4238 group_id: 0,
4239 edits,
4240 before_selection,
4241 after_selection,
4242 };
4243 let group_id = self.undo_redo.push_step(step, false);
4244
4245 self.last_text_delta = Some(TextDelta {
4246 before_char_count,
4247 after_char_count: self.editor.piece_table.char_count(),
4248 edits: delta_edits,
4249 undo_group_id: Some(group_id),
4250 });
4251
4252 Ok(CommandResult::Success)
4253 }
4254
4255 fn execute_toggle_line_comment(
4256 &mut self,
4257 token: &str,
4258 before_char_count: usize,
4259 before_selection: SelectionSetSnapshot,
4260 selections: Vec<Selection>,
4261 _primary_index: usize,
4262 ) -> Result<CommandResult, CommandError> {
4263 let token = token.trim_end();
4264 if token.is_empty() {
4265 return Ok(CommandResult::Success);
4266 }
4267
4268 let token_len = token.chars().count();
4269 let insert_text = format!("{} ", token);
4270 let insert_len = insert_text.chars().count();
4271
4272 let mut lines: Vec<usize> = Vec::new();
4274 for sel in &selections {
4275 let (min_pos, max_pos) = crate::selection_set::selection_min_max(sel);
4276 for line in min_pos.line..=max_pos.line {
4277 lines.push(line);
4278 }
4279 }
4280 lines.sort_unstable();
4281 lines.dedup();
4282 lines.retain(|l| *l < self.editor.line_index.line_count());
4283
4284 if lines.is_empty() {
4285 return Ok(CommandResult::Success);
4286 }
4287
4288 let mut non_empty = 0usize;
4290 let mut all_commented = true;
4291 for line in &lines {
4292 let line_text = self
4293 .editor
4294 .line_index
4295 .get_line_text(*line)
4296 .unwrap_or_default();
4297 let indent = line_text
4298 .chars()
4299 .take_while(|c| *c == ' ' || *c == '\t')
4300 .count();
4301 let indent_byte = byte_offset_for_char_column(&line_text, indent);
4302 let rest = line_text.get(indent_byte..).unwrap_or("");
4303 if rest.is_empty() {
4304 continue;
4305 }
4306 non_empty += 1;
4307 if !rest.starts_with(token) {
4308 all_commented = false;
4309 break;
4310 }
4311 }
4312
4313 let should_uncomment = non_empty > 0 && all_commented;
4314
4315 struct Op {
4316 start_before: usize,
4317 start_after: usize,
4318 delete_len: usize,
4319 deleted_text: String,
4320 inserted_text: String,
4321 inserted_len: usize,
4322 line: usize,
4323 indent_col: usize,
4324 col_delta: isize,
4325 }
4326
4327 let mut ops: Vec<Op> = Vec::new();
4328
4329 for line in lines {
4330 let line_text = self
4331 .editor
4332 .line_index
4333 .get_line_text(line)
4334 .unwrap_or_default();
4335 let indent = line_text
4336 .chars()
4337 .take_while(|c| *c == ' ' || *c == '\t')
4338 .count();
4339 let indent_byte = byte_offset_for_char_column(&line_text, indent);
4340 let rest = line_text.get(indent_byte..).unwrap_or("");
4341
4342 let start_offset = self.editor.line_index.position_to_char_offset(line, indent);
4343
4344 if should_uncomment {
4345 if rest.is_empty() || !rest.starts_with(token) {
4346 continue;
4347 }
4348
4349 let mut remove_len = token_len;
4350 if let Some(ch) = line_text.chars().nth(indent + token_len)
4351 && ch == ' '
4352 {
4353 remove_len += 1;
4354 }
4355
4356 if remove_len == 0 {
4357 continue;
4358 }
4359
4360 let deleted_text = self.editor.piece_table.get_range(start_offset, remove_len);
4361 ops.push(Op {
4362 start_before: start_offset,
4363 start_after: start_offset,
4364 delete_len: remove_len,
4365 deleted_text,
4366 inserted_text: String::new(),
4367 inserted_len: 0,
4368 line,
4369 indent_col: indent,
4370 col_delta: -(remove_len as isize),
4371 });
4372 } else {
4373 ops.push(Op {
4374 start_before: start_offset,
4375 start_after: start_offset,
4376 delete_len: 0,
4377 deleted_text: String::new(),
4378 inserted_text: insert_text.clone(),
4379 inserted_len: insert_len,
4380 line,
4381 indent_col: indent,
4382 col_delta: insert_len as isize,
4383 });
4384 }
4385 }
4386
4387 if ops.is_empty() {
4388 return Ok(CommandResult::Success);
4389 }
4390
4391 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
4393 asc_indices.sort_by_key(|&idx| ops[idx].start_before);
4394
4395 let mut delta: i64 = 0;
4396 for &idx in &asc_indices {
4397 let op = &mut ops[idx];
4398 let effective_start = op.start_before as i64 + delta;
4399 if effective_start < 0 {
4400 return Err(CommandError::Other(
4401 "ToggleComment produced an invalid intermediate offset".to_string(),
4402 ));
4403 }
4404 op.start_after = effective_start as usize;
4405 delta += op.inserted_len as i64 - op.delete_len as i64;
4406 }
4407
4408 let apply_ops: Vec<(usize, usize, &str)> = ops
4409 .iter()
4410 .map(|op| (op.start_before, op.delete_len, op.inserted_text.as_str()))
4411 .collect();
4412 self.apply_text_ops(apply_ops)?;
4413
4414 use std::collections::HashMap;
4416 let mut line_deltas: HashMap<usize, (usize, isize)> = HashMap::new();
4417 for op in &ops {
4418 line_deltas.insert(op.line, (op.indent_col, op.col_delta));
4419 }
4420
4421 let line_index = &self.editor.line_index;
4422 let apply_delta = |pos: &mut Position, deltas: &HashMap<usize, (usize, isize)>| {
4423 let Some((indent_col, delta)) = deltas.get(&pos.line) else {
4424 return;
4425 };
4426 if pos.column < *indent_col {
4427 return;
4428 }
4429
4430 let new_col = if *delta >= 0 {
4431 pos.column.saturating_add(*delta as usize)
4432 } else {
4433 pos.column.saturating_sub((-*delta) as usize)
4434 };
4435
4436 pos.column = Self::clamp_column_for_line_with_index(line_index, pos.line, new_col);
4437 };
4438
4439 apply_delta(&mut self.editor.cursor_position, &line_deltas);
4440 if let Some(sel) = &mut self.editor.selection {
4441 apply_delta(&mut sel.start, &line_deltas);
4442 apply_delta(&mut sel.end, &line_deltas);
4443 }
4444 for sel in &mut self.editor.secondary_selections {
4445 apply_delta(&mut sel.start, &line_deltas);
4446 apply_delta(&mut sel.end, &line_deltas);
4447 }
4448
4449 self.normalize_cursor_and_selection();
4450 let after_selection = self.snapshot_selection_set();
4451
4452 let edits: Vec<TextEdit> = ops
4453 .into_iter()
4454 .map(|op| TextEdit {
4455 start_before: op.start_before,
4456 start_after: op.start_after,
4457 deleted_text: op.deleted_text,
4458 inserted_text: op.inserted_text,
4459 })
4460 .collect();
4461
4462 let mut delta_edits: Vec<TextDeltaEdit> = edits
4463 .iter()
4464 .map(|e| TextDeltaEdit {
4465 start: e.start_before,
4466 deleted_text: e.deleted_text.clone(),
4467 inserted_text: e.inserted_text.clone(),
4468 })
4469 .collect();
4470 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
4471
4472 let step = UndoStep {
4473 group_id: 0,
4474 edits,
4475 before_selection,
4476 after_selection,
4477 };
4478 let group_id = self.undo_redo.push_step(step, false);
4479
4480 self.last_text_delta = Some(TextDelta {
4481 before_char_count,
4482 after_char_count: self.editor.piece_table.char_count(),
4483 edits: delta_edits,
4484 undo_group_id: Some(group_id),
4485 });
4486
4487 Ok(CommandResult::Success)
4488 }
4489
4490 fn execute_toggle_block_comment_inline(
4491 &mut self,
4492 block_start: &str,
4493 block_end: &str,
4494 before_char_count: usize,
4495 before_selection: SelectionSetSnapshot,
4496 selections: Vec<Selection>,
4497 primary_index: usize,
4498 ) -> Result<CommandResult, CommandError> {
4499 let start_len = block_start.chars().count();
4500 let end_len = block_end.chars().count();
4501 if start_len == 0 || end_len == 0 {
4502 return Ok(CommandResult::Success);
4503 }
4504
4505 let mut selection_ranges: Vec<SearchMatch> = selections
4506 .iter()
4507 .map(|s| self.selection_char_range(s))
4508 .filter(|r| r.start < r.end)
4509 .collect();
4510
4511 if selection_ranges.is_empty() {
4512 return Ok(CommandResult::Success);
4513 }
4514
4515 selection_ranges.sort_by_key(|r| (r.start, r.end));
4516
4517 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
4518 enum TokenOpKind {
4519 Start,
4520 End,
4521 }
4522
4523 struct Op {
4525 start_before: usize,
4526 start_after: usize,
4527 delete_len: usize,
4528 deleted_text: String,
4529 inserted_text: String,
4530 inserted_len: usize,
4531 sel_id: usize,
4532 kind: TokenOpKind,
4533 }
4534
4535 let mut ops: Vec<Op> = Vec::new();
4536
4537 for (sel_id, range) in selection_ranges.iter().enumerate() {
4538 let start = range.start;
4539 let end = range.end;
4540
4541 let already_wrapped = start >= start_len
4542 && end + end_len <= before_char_count
4543 && self
4544 .editor
4545 .piece_table
4546 .get_range(start - start_len, start_len)
4547 == block_start
4548 && self.editor.piece_table.get_range(end, end_len) == block_end;
4549
4550 if already_wrapped {
4551 let deleted_end = self.editor.piece_table.get_range(end, end_len);
4553 ops.push(Op {
4554 start_before: end,
4555 start_after: end,
4556 delete_len: end_len,
4557 deleted_text: deleted_end,
4558 inserted_text: String::new(),
4559 inserted_len: 0,
4560 sel_id,
4561 kind: TokenOpKind::End,
4562 });
4563
4564 let start_token_offset = start - start_len;
4565 let deleted_start = self
4566 .editor
4567 .piece_table
4568 .get_range(start_token_offset, start_len);
4569 ops.push(Op {
4570 start_before: start_token_offset,
4571 start_after: start_token_offset,
4572 delete_len: start_len,
4573 deleted_text: deleted_start,
4574 inserted_text: String::new(),
4575 inserted_len: 0,
4576 sel_id,
4577 kind: TokenOpKind::Start,
4578 });
4579 } else {
4580 ops.push(Op {
4582 start_before: end,
4583 start_after: end,
4584 delete_len: 0,
4585 deleted_text: String::new(),
4586 inserted_text: block_end.to_string(),
4587 inserted_len: end_len,
4588 sel_id,
4589 kind: TokenOpKind::End,
4590 });
4591 ops.push(Op {
4592 start_before: start,
4593 start_after: start,
4594 delete_len: 0,
4595 deleted_text: String::new(),
4596 inserted_text: block_start.to_string(),
4597 inserted_len: start_len,
4598 sel_id,
4599 kind: TokenOpKind::Start,
4600 });
4601 }
4602 }
4603
4604 if ops.is_empty() {
4605 return Ok(CommandResult::Success);
4606 }
4607
4608 ops.sort_by_key(|op| op.start_before);
4609
4610 let mut delta: i64 = 0;
4611 for op in &mut ops {
4612 let effective_start = op.start_before as i64 + delta;
4613 if effective_start < 0 {
4614 return Err(CommandError::Other(
4615 "ToggleComment produced an invalid intermediate offset".to_string(),
4616 ));
4617 }
4618 op.start_after = effective_start as usize;
4619 delta += op.inserted_len as i64 - op.delete_len as i64;
4620 }
4621
4622 let apply_ops: Vec<(usize, usize, &str)> = ops
4623 .iter()
4624 .map(|op| (op.start_before, op.delete_len, op.inserted_text.as_str()))
4625 .collect();
4626 self.apply_text_ops(apply_ops)?;
4627
4628 let mut new_starts: Vec<usize> = vec![0; selection_ranges.len()];
4630 let mut new_ends: Vec<usize> = vec![0; selection_ranges.len()];
4631
4632 for op in &ops {
4633 match op.kind {
4634 TokenOpKind::Start => {
4635 new_starts[op.sel_id] = if op.inserted_len > 0 {
4636 op.start_after + start_len
4637 } else {
4638 op.start_after
4639 };
4640 }
4641 TokenOpKind::End => {
4642 new_ends[op.sel_id] = op.start_after;
4643 }
4644 }
4645 }
4646
4647 let mut next_selections: Vec<Selection> = Vec::with_capacity(selection_ranges.len());
4648 for i in 0..selection_ranges.len() {
4649 let start = new_starts[i].min(new_ends[i]);
4650 let end = new_starts[i].max(new_ends[i]);
4651 let (start_line, start_col) = self.editor.line_index.char_offset_to_position(start);
4652 let (end_line, end_col) = self.editor.line_index.char_offset_to_position(end);
4653 next_selections.push(Selection {
4654 start: Position::new(start_line, start_col),
4655 end: Position::new(end_line, end_col),
4656 direction: SelectionDirection::Forward,
4657 });
4658 }
4659
4660 self.execute_cursor(CursorCommand::SetSelections {
4661 selections: next_selections,
4662 primary_index: primary_index.min(selection_ranges.len().saturating_sub(1)),
4663 })?;
4664
4665 let after_selection = self.snapshot_selection_set();
4666
4667 let edits: Vec<TextEdit> = ops
4668 .into_iter()
4669 .map(|op| TextEdit {
4670 start_before: op.start_before,
4671 start_after: op.start_after,
4672 deleted_text: op.deleted_text,
4673 inserted_text: op.inserted_text,
4674 })
4675 .collect();
4676
4677 let mut delta_edits: Vec<TextDeltaEdit> = edits
4678 .iter()
4679 .map(|e| TextDeltaEdit {
4680 start: e.start_before,
4681 deleted_text: e.deleted_text.clone(),
4682 inserted_text: e.inserted_text.clone(),
4683 })
4684 .collect();
4685 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
4686
4687 let step = UndoStep {
4688 group_id: 0,
4689 edits,
4690 before_selection,
4691 after_selection,
4692 };
4693 let group_id = self.undo_redo.push_step(step, false);
4694
4695 self.last_text_delta = Some(TextDelta {
4696 before_char_count,
4697 after_char_count: self.editor.piece_table.char_count(),
4698 edits: delta_edits,
4699 undo_group_id: Some(group_id),
4700 });
4701
4702 Ok(CommandResult::Success)
4703 }
4704
4705 fn execute_toggle_block_comment_lines(
4706 &mut self,
4707 block_start: &str,
4708 block_end: &str,
4709 before_char_count: usize,
4710 before_selection: SelectionSetSnapshot,
4711 selections: Vec<Selection>,
4712 primary_index: usize,
4713 ) -> Result<CommandResult, CommandError> {
4714 let start_len = block_start.chars().count();
4715 let end_len = block_end.chars().count();
4716 if start_len == 0 || end_len == 0 {
4717 return Ok(CommandResult::Success);
4718 }
4719
4720 let mut ranges: Vec<(usize, usize)> = Vec::new();
4721 for sel in &selections {
4722 let (min_pos, max_pos) = crate::selection_set::selection_min_max(sel);
4723 let start_line = min_pos.line.min(self.editor.line_index.line_count() - 1);
4724 let end_line = max_pos.line.min(self.editor.line_index.line_count() - 1);
4725
4726 let start = self
4727 .editor
4728 .line_index
4729 .position_to_char_offset(start_line, 0);
4730 let end_line_text = self
4731 .editor
4732 .line_index
4733 .get_line_text(end_line)
4734 .unwrap_or_default();
4735 let end = self
4736 .editor
4737 .line_index
4738 .position_to_char_offset(end_line, end_line_text.chars().count());
4739 if start < end {
4740 ranges.push((start, end));
4741 }
4742 }
4743
4744 ranges.sort_unstable();
4745 ranges.dedup();
4746
4747 if ranges.is_empty() {
4748 return Ok(CommandResult::Success);
4749 }
4750
4751 let mut all_wrapped = true;
4753 for (start, end) in &ranges {
4754 if *end < *start + start_len + end_len {
4755 all_wrapped = false;
4756 break;
4757 }
4758 let text = self.editor.piece_table.get_range(*start, end - start);
4759 if !text.starts_with(block_start) || !text.ends_with(block_end) {
4760 all_wrapped = false;
4761 break;
4762 }
4763 }
4764
4765 struct Op {
4766 start_before: usize,
4767 start_after: usize,
4768 delete_len: usize,
4769 deleted_text: String,
4770 inserted_text: String,
4771 inserted_len: usize,
4772 }
4773
4774 let mut ops: Vec<Op> = Vec::new();
4775
4776 for (start, end) in &ranges {
4777 if all_wrapped {
4778 let end_token_start = end.saturating_sub(end_len);
4780 let deleted_end = self.editor.piece_table.get_range(end_token_start, end_len);
4781 ops.push(Op {
4782 start_before: end_token_start,
4783 start_after: end_token_start,
4784 delete_len: end_len,
4785 deleted_text: deleted_end,
4786 inserted_text: String::new(),
4787 inserted_len: 0,
4788 });
4789
4790 let deleted_start = self.editor.piece_table.get_range(*start, start_len);
4791 ops.push(Op {
4792 start_before: *start,
4793 start_after: *start,
4794 delete_len: start_len,
4795 deleted_text: deleted_start,
4796 inserted_text: String::new(),
4797 inserted_len: 0,
4798 });
4799 } else {
4800 ops.push(Op {
4802 start_before: *end,
4803 start_after: *end,
4804 delete_len: 0,
4805 deleted_text: String::new(),
4806 inserted_text: block_end.to_string(),
4807 inserted_len: end_len,
4808 });
4809 ops.push(Op {
4810 start_before: *start,
4811 start_after: *start,
4812 delete_len: 0,
4813 deleted_text: String::new(),
4814 inserted_text: block_start.to_string(),
4815 inserted_len: start_len,
4816 });
4817 }
4818 }
4819
4820 ops.sort_by_key(|op| op.start_before);
4821 let mut delta: i64 = 0;
4822 for op in &mut ops {
4823 let effective_start = op.start_before as i64 + delta;
4824 if effective_start < 0 {
4825 return Err(CommandError::Other(
4826 "ToggleComment produced an invalid intermediate offset".to_string(),
4827 ));
4828 }
4829 op.start_after = effective_start as usize;
4830 delta += op.inserted_len as i64 - op.delete_len as i64;
4831 }
4832
4833 let apply_ops: Vec<(usize, usize, &str)> = ops
4834 .iter()
4835 .map(|op| (op.start_before, op.delete_len, op.inserted_text.as_str()))
4836 .collect();
4837 self.apply_text_ops(apply_ops)?;
4838
4839 let (primary_start, primary_end) = ranges
4841 .get(primary_index.min(ranges.len().saturating_sub(1)))
4842 .copied()
4843 .unwrap_or((0, 0));
4844 let caret_offset = primary_end.max(primary_start);
4845 let (line, column) = self.editor.line_index.char_offset_to_position(caret_offset);
4846 let pos = Position::new(line, column);
4847 self.execute_cursor(CursorCommand::SetSelections {
4848 selections: vec![Selection {
4849 start: pos,
4850 end: pos,
4851 direction: SelectionDirection::Forward,
4852 }],
4853 primary_index: 0,
4854 })?;
4855
4856 let after_selection = self.snapshot_selection_set();
4857
4858 let edits: Vec<TextEdit> = ops
4859 .into_iter()
4860 .map(|op| TextEdit {
4861 start_before: op.start_before,
4862 start_after: op.start_after,
4863 deleted_text: op.deleted_text,
4864 inserted_text: op.inserted_text,
4865 })
4866 .collect();
4867
4868 let mut delta_edits: Vec<TextDeltaEdit> = edits
4869 .iter()
4870 .map(|e| TextDeltaEdit {
4871 start: e.start_before,
4872 deleted_text: e.deleted_text.clone(),
4873 inserted_text: e.inserted_text.clone(),
4874 })
4875 .collect();
4876 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
4877
4878 let step = UndoStep {
4879 group_id: 0,
4880 edits,
4881 before_selection,
4882 after_selection,
4883 };
4884 let group_id = self.undo_redo.push_step(step, false);
4885
4886 self.last_text_delta = Some(TextDelta {
4887 before_char_count,
4888 after_char_count: self.editor.piece_table.char_count(),
4889 edits: delta_edits,
4890 undo_group_id: Some(group_id),
4891 });
4892
4893 Ok(CommandResult::Success)
4894 }
4895
4896 fn is_word_char(ch: char) -> bool {
4897 ch == '_' || ch.is_alphanumeric()
4898 }
4899
4900 fn word_range_in_line(line_text: &str, column: usize) -> Option<(usize, usize)> {
4901 if line_text.is_empty() {
4902 return None;
4903 }
4904
4905 let mut parts: Vec<(usize, usize, &str)> = Vec::new();
4906 for (start, part) in line_text.split_word_bound_indices() {
4907 let end = start + part.len();
4908 parts.push((start, end, part));
4909 }
4910 if parts.is_empty() {
4911 return None;
4912 }
4913
4914 let byte_pos =
4915 byte_offset_for_char_column(line_text, column.min(line_text.chars().count()));
4916
4917 let mut part_idx = parts
4918 .iter()
4919 .position(|(s, e, _)| *s <= byte_pos && byte_pos < *e)
4920 .or_else(|| parts.iter().position(|(s, _, _)| *s == byte_pos))
4921 .unwrap_or_else(|| parts.len().saturating_sub(1));
4922
4923 let pick_part = |idx: usize, parts: &[(usize, usize, &str)]| -> Option<(usize, usize)> {
4924 let (s, e, text) = parts.get(idx)?;
4925 if text.chars().any(Self::is_word_char) {
4926 Some((*s, *e))
4927 } else {
4928 None
4929 }
4930 };
4931
4932 if let Some((s, e)) = pick_part(part_idx, &parts) {
4934 return Some((
4935 char_column_for_byte_offset(line_text, s),
4936 char_column_for_byte_offset(line_text, e),
4937 ));
4938 }
4939
4940 for idx in part_idx + 1..parts.len() {
4942 if let Some((s, e)) = pick_part(idx, &parts) {
4943 return Some((
4944 char_column_for_byte_offset(line_text, s),
4945 char_column_for_byte_offset(line_text, e),
4946 ));
4947 }
4948 }
4949
4950 while part_idx > 0 {
4952 part_idx -= 1;
4953 if let Some((s, e)) = pick_part(part_idx, &parts) {
4954 return Some((
4955 char_column_for_byte_offset(line_text, s),
4956 char_column_for_byte_offset(line_text, e),
4957 ));
4958 }
4959 }
4960
4961 None
4962 }
4963
4964 fn execute_select_line_command(&mut self) -> Result<CommandResult, CommandError> {
4965 let snapshot = self.snapshot_selection_set();
4966 let selections = snapshot.selections;
4967 let primary_index = snapshot.primary_index;
4968
4969 let line_count = self.editor.line_index.line_count();
4970 if line_count == 0 {
4971 return Ok(CommandResult::Success);
4972 }
4973
4974 let mut next: Vec<Selection> = Vec::with_capacity(selections.len());
4975 for sel in selections {
4976 let (min_pos, max_pos) = crate::selection_set::selection_min_max(&sel);
4977 let start_line = min_pos.line.min(line_count.saturating_sub(1));
4978 let end_line = max_pos.line.min(line_count.saturating_sub(1));
4979
4980 let start = Position::new(start_line, 0);
4981 let end = if end_line + 1 < line_count {
4982 Position::new(end_line + 1, 0)
4983 } else {
4984 let line_text = self
4985 .editor
4986 .line_index
4987 .get_line_text(end_line)
4988 .unwrap_or_default();
4989 Position::new(end_line, line_text.chars().count())
4990 };
4991
4992 next.push(Selection {
4993 start,
4994 end,
4995 direction: SelectionDirection::Forward,
4996 });
4997 }
4998
4999 self.execute_cursor(CursorCommand::SetSelections {
5000 selections: next,
5001 primary_index,
5002 })?;
5003 Ok(CommandResult::Success)
5004 }
5005
5006 fn execute_select_word_command(&mut self) -> Result<CommandResult, CommandError> {
5007 let snapshot = self.snapshot_selection_set();
5008 let selections = snapshot.selections;
5009 let primary_index = snapshot.primary_index;
5010
5011 let line_count = self.editor.line_index.line_count();
5012 if line_count == 0 {
5013 return Ok(CommandResult::Success);
5014 }
5015
5016 let mut next: Vec<Selection> = Vec::with_capacity(selections.len());
5017
5018 for sel in selections {
5019 if sel.start != sel.end {
5021 next.push(sel);
5022 continue;
5023 }
5024
5025 let caret = sel.end;
5026 let line = caret.line.min(line_count.saturating_sub(1));
5027 let line_text = self
5028 .editor
5029 .line_index
5030 .get_line_text(line)
5031 .unwrap_or_default();
5032 let col = caret.column.min(line_text.chars().count());
5033
5034 let Some((start_col, end_col)) = Self::word_range_in_line(&line_text, col) else {
5035 next.push(sel);
5036 continue;
5037 };
5038
5039 let start = Position::new(line, start_col);
5040 let end = Position::new(line, end_col);
5041
5042 next.push(Selection {
5043 start,
5044 end,
5045 direction: SelectionDirection::Forward,
5046 });
5047 }
5048
5049 self.execute_cursor(CursorCommand::SetSelections {
5050 selections: next,
5051 primary_index,
5052 })?;
5053 Ok(CommandResult::Success)
5054 }
5055
5056 fn execute_expand_selection_command(&mut self) -> Result<CommandResult, CommandError> {
5057 let snapshot = self.snapshot_selection_set();
5061 if snapshot.selections.iter().any(|s| s.start != s.end) {
5062 self.execute_select_line_command()
5063 } else {
5064 self.execute_select_word_command()
5065 }
5066 }
5067
5068 fn execute_add_cursor_vertical_command(
5069 &mut self,
5070 above: bool,
5071 ) -> Result<CommandResult, CommandError> {
5072 let snapshot = self.snapshot_selection_set();
5073 let mut selections = snapshot.selections;
5074 let primary_index = snapshot.primary_index;
5075
5076 let line_count = self.editor.line_index.line_count();
5077 if line_count == 0 {
5078 return Ok(CommandResult::Success);
5079 }
5080
5081 let mut extra: Vec<Selection> = Vec::new();
5082 for sel in &selections {
5083 let caret = sel.end;
5084 let target_line = if above {
5085 if caret.line == 0 {
5086 continue;
5087 }
5088 caret.line - 1
5089 } else {
5090 let next = caret.line + 1;
5091 if next >= line_count {
5092 continue;
5093 }
5094 next
5095 };
5096
5097 let col = self.clamp_column_for_line(target_line, caret.column);
5098 let pos = Position::new(target_line, col);
5099 extra.push(Selection {
5100 start: pos,
5101 end: pos,
5102 direction: SelectionDirection::Forward,
5103 });
5104 }
5105
5106 if extra.is_empty() {
5107 return Ok(CommandResult::Success);
5108 }
5109
5110 selections.extend(extra);
5111
5112 self.execute_cursor(CursorCommand::SetSelections {
5113 selections,
5114 primary_index,
5115 })?;
5116 Ok(CommandResult::Success)
5117 }
5118
5119 fn selection_query(
5120 &self,
5121 selections: &[Selection],
5122 primary_index: usize,
5123 ) -> Option<(String, Option<SearchMatch>)> {
5124 let primary = selections.get(primary_index)?;
5125 let range = self.selection_char_range(primary);
5126
5127 if range.start != range.end {
5128 let len = range.end - range.start;
5129 return Some((
5130 self.editor.piece_table.get_range(range.start, len),
5131 Some(range),
5132 ));
5133 }
5134
5135 let caret = primary.end;
5136 let line_text = self
5137 .editor
5138 .line_index
5139 .get_line_text(caret.line)
5140 .unwrap_or_default();
5141 let col = caret.column.min(line_text.chars().count());
5142 let (start_col, end_col) = Self::word_range_in_line(&line_text, col)?;
5143 if start_col == end_col {
5144 return None;
5145 }
5146
5147 let start = self
5148 .editor
5149 .line_index
5150 .position_to_char_offset(caret.line, start_col);
5151 let end = self
5152 .editor
5153 .line_index
5154 .position_to_char_offset(caret.line, end_col);
5155 let range = SearchMatch {
5156 start,
5157 end: end.max(start),
5158 };
5159 Some((
5160 self.editor
5161 .piece_table
5162 .get_range(range.start, range.end.saturating_sub(range.start)),
5163 Some(range),
5164 ))
5165 }
5166
5167 fn execute_add_next_occurrence_command(
5168 &mut self,
5169 options: SearchOptions,
5170 ) -> Result<CommandResult, CommandError> {
5171 let snapshot = self.snapshot_selection_set();
5172 let mut selections = snapshot.selections;
5173 let primary_index = snapshot.primary_index;
5174
5175 let Some((query, primary_range)) = self.selection_query(&selections, primary_index) else {
5176 return Ok(CommandResult::Success);
5177 };
5178 if query.is_empty() {
5179 return Ok(CommandResult::Success);
5180 }
5181
5182 if let Some(primary_range) = primary_range
5184 && primary_range.start != primary_range.end
5185 {
5186 let current = selections
5187 .get(primary_index)
5188 .map(|s| self.selection_char_range(s))
5189 .unwrap_or(SearchMatch { start: 0, end: 0 });
5190 if current.start == current.end {
5191 let (start_line, start_col) = self
5192 .editor
5193 .line_index
5194 .char_offset_to_position(primary_range.start);
5195 let (end_line, end_col) = self
5196 .editor
5197 .line_index
5198 .char_offset_to_position(primary_range.end);
5199 if let Some(sel) = selections.get_mut(primary_index) {
5200 *sel = Selection {
5201 start: Position::new(start_line, start_col),
5202 end: Position::new(end_line, end_col),
5203 direction: SelectionDirection::Forward,
5204 };
5205 }
5206 }
5207 }
5208
5209 let text = self.editor.piece_table.get_text();
5210
5211 let mut ranges: Vec<SearchMatch> = selections
5212 .iter()
5213 .map(|s| self.selection_char_range(s))
5214 .filter(|r| r.start != r.end)
5215 .collect();
5216
5217 if let Some(primary_range) = primary_range
5218 && primary_range.start != primary_range.end
5219 && !ranges
5220 .iter()
5221 .any(|r| r.start == primary_range.start && r.end == primary_range.end)
5222 {
5223 ranges.push(primary_range);
5224 }
5225
5226 let mut existing: Vec<(usize, usize)> = ranges
5227 .iter()
5228 .map(|r| (r.start.min(r.end), r.end.max(r.start)))
5229 .collect();
5230 existing.sort_unstable();
5231
5232 let from = existing.iter().map(|(_, end)| *end).max().unwrap_or(0);
5233
5234 let mut search_from = from;
5235 let mut wrapped = false;
5236 let mut found: Option<SearchMatch> = None;
5237
5238 loop {
5239 let next = find_next(&text, &query, options, search_from)
5240 .map_err(|err| CommandError::Other(err.to_string()))?;
5241
5242 let Some(m) = next else {
5243 if wrapped {
5244 break;
5245 }
5246 wrapped = true;
5247 search_from = 0;
5248 continue;
5249 };
5250
5251 let overlaps = existing.iter().any(|(s, e)| m.start < *e && m.end > *s);
5252
5253 if overlaps {
5254 if m.end >= text.chars().count() {
5255 break;
5256 }
5257 search_from = m.end + 1;
5258 continue;
5259 }
5260
5261 found = Some(m);
5262 break;
5263 }
5264
5265 let Some(m) = found else {
5266 return Ok(CommandResult::Success);
5267 };
5268
5269 let (start_line, start_col) = self.editor.line_index.char_offset_to_position(m.start);
5270 let (end_line, end_col) = self.editor.line_index.char_offset_to_position(m.end);
5271
5272 selections.push(Selection {
5273 start: Position::new(start_line, start_col),
5274 end: Position::new(end_line, end_col),
5275 direction: SelectionDirection::Forward,
5276 });
5277
5278 let new_primary_index = selections.len().saturating_sub(1);
5279 self.execute_cursor(CursorCommand::SetSelections {
5280 selections,
5281 primary_index: new_primary_index,
5282 })?;
5283
5284 Ok(CommandResult::Success)
5285 }
5286
5287 fn execute_add_all_occurrences_command(
5288 &mut self,
5289 options: SearchOptions,
5290 ) -> Result<CommandResult, CommandError> {
5291 let snapshot = self.snapshot_selection_set();
5292 let selections = snapshot.selections;
5293 let primary_index = snapshot.primary_index;
5294
5295 let Some((query, primary_range)) = self.selection_query(&selections, primary_index) else {
5296 return Ok(CommandResult::Success);
5297 };
5298 if query.is_empty() {
5299 return Ok(CommandResult::Success);
5300 }
5301
5302 let text = self.editor.piece_table.get_text();
5303 let matches =
5304 find_all(&text, &query, options).map_err(|err| CommandError::Other(err.to_string()))?;
5305
5306 if matches.is_empty() {
5307 return Ok(CommandResult::Success);
5308 }
5309
5310 let mut out: Vec<Selection> = Vec::with_capacity(matches.len());
5311 let mut next_primary = 0usize;
5312 let primary_range = primary_range.filter(|r| r.start != r.end);
5313
5314 for (idx, m) in matches.iter().enumerate() {
5315 let (start_line, start_col) = self.editor.line_index.char_offset_to_position(m.start);
5316 let (end_line, end_col) = self.editor.line_index.char_offset_to_position(m.end);
5317 out.push(Selection {
5318 start: Position::new(start_line, start_col),
5319 end: Position::new(end_line, end_col),
5320 direction: SelectionDirection::Forward,
5321 });
5322
5323 if let Some(pr) = primary_range
5324 && pr.start == m.start
5325 && pr.end == m.end
5326 {
5327 next_primary = idx;
5328 }
5329 }
5330
5331 self.execute_cursor(CursorCommand::SetSelections {
5332 selections: out,
5333 primary_index: next_primary,
5334 })?;
5335
5336 Ok(CommandResult::Success)
5337 }
5338
5339 fn execute_insert_command(
5340 &mut self,
5341 offset: usize,
5342 text: String,
5343 ) -> Result<CommandResult, CommandError> {
5344 if text.is_empty() {
5345 return Err(CommandError::EmptyText);
5346 }
5347
5348 let text = crate::text::normalize_crlf_to_lf_string(text);
5349 let max_offset = self.editor.piece_table.char_count();
5350 if offset > max_offset {
5351 return Err(CommandError::InvalidOffset(offset));
5352 }
5353
5354 let before_char_count = self.editor.piece_table.char_count();
5355 let before_selection = self.snapshot_selection_set();
5356
5357 let affected_line = self.editor.line_index.char_offset_to_position(offset).0;
5358 let inserted_newlines = text.as_bytes().iter().filter(|b| **b == b'\n').count();
5359
5360 self.editor.piece_table.insert(offset, &text);
5362
5363 self.apply_text_change_to_line_index_and_layout(offset, "", &text);
5365
5366 if inserted_newlines > 0 {
5367 self.editor
5368 .folding_manager
5369 .apply_line_delta(affected_line, inserted_newlines as isize);
5370 self.editor
5371 .folding_manager
5372 .clamp_to_line_count(self.editor.line_index.line_count());
5373 }
5374
5375 let inserted_len = text.chars().count();
5376
5377 self.editor
5379 .interval_tree
5380 .update_for_insertion(offset, inserted_len);
5381 for layer_tree in self.editor.style_layers.values_mut() {
5382 layer_tree.update_for_insertion(offset, inserted_len);
5383 }
5384
5385 self.normalize_cursor_and_selection();
5387
5388 let after_selection = self.snapshot_selection_set();
5389
5390 let step = UndoStep {
5391 group_id: 0,
5392 edits: vec![TextEdit {
5393 start_before: offset,
5394 start_after: offset,
5395 deleted_text: String::new(),
5396 inserted_text: text.clone(),
5397 }],
5398 before_selection,
5399 after_selection,
5400 };
5401
5402 let coalescible_insert = !text.contains('\n');
5403 let group_id = self.undo_redo.push_step(step, coalescible_insert);
5404
5405 self.last_text_delta = Some(TextDelta {
5406 before_char_count,
5407 after_char_count: self.editor.piece_table.char_count(),
5408 edits: vec![TextDeltaEdit {
5409 start: offset,
5410 deleted_text: String::new(),
5411 inserted_text: text,
5412 }],
5413 undo_group_id: Some(group_id),
5414 });
5415
5416 Ok(CommandResult::Success)
5417 }
5418
5419 fn execute_delete_command(
5420 &mut self,
5421 start: usize,
5422 length: usize,
5423 ) -> Result<CommandResult, CommandError> {
5424 if length == 0 {
5425 return Ok(CommandResult::Success);
5426 }
5427
5428 let before_char_count = self.editor.piece_table.char_count();
5429 let max_offset = self.editor.piece_table.char_count();
5430 if start > max_offset {
5431 return Err(CommandError::InvalidOffset(start));
5432 }
5433 if start + length > max_offset {
5434 return Err(CommandError::InvalidRange {
5435 start,
5436 end: start + length,
5437 });
5438 }
5439
5440 let before_selection = self.snapshot_selection_set();
5441
5442 let deleted_text = self.editor.piece_table.get_range(start, length);
5443 let delta_deleted_text = deleted_text.clone();
5444 let deleted_newlines = deleted_text
5445 .as_bytes()
5446 .iter()
5447 .filter(|b| **b == b'\n')
5448 .count();
5449 let affected_line = self.editor.line_index.char_offset_to_position(start).0;
5450
5451 self.editor.piece_table.delete(start, length);
5453
5454 self.apply_text_change_to_line_index_and_layout(start, &delta_deleted_text, "");
5456
5457 if deleted_newlines > 0 {
5458 self.editor
5459 .folding_manager
5460 .apply_line_delta(affected_line, -(deleted_newlines as isize));
5461 self.editor
5462 .folding_manager
5463 .clamp_to_line_count(self.editor.line_index.line_count());
5464 }
5465
5466 self.editor
5468 .interval_tree
5469 .update_for_deletion(start, start + length);
5470 for layer_tree in self.editor.style_layers.values_mut() {
5471 layer_tree.update_for_deletion(start, start + length);
5472 }
5473
5474 self.normalize_cursor_and_selection();
5476
5477 let after_selection = self.snapshot_selection_set();
5478
5479 let step = UndoStep {
5480 group_id: 0,
5481 edits: vec![TextEdit {
5482 start_before: start,
5483 start_after: start,
5484 deleted_text,
5485 inserted_text: String::new(),
5486 }],
5487 before_selection,
5488 after_selection,
5489 };
5490 let group_id = self.undo_redo.push_step(step, false);
5491
5492 self.last_text_delta = Some(TextDelta {
5493 before_char_count,
5494 after_char_count: self.editor.piece_table.char_count(),
5495 edits: vec![TextDeltaEdit {
5496 start,
5497 deleted_text: delta_deleted_text,
5498 inserted_text: String::new(),
5499 }],
5500 undo_group_id: Some(group_id),
5501 });
5502
5503 Ok(CommandResult::Success)
5504 }
5505
5506 fn execute_replace_command(
5507 &mut self,
5508 start: usize,
5509 length: usize,
5510 text: String,
5511 ) -> Result<CommandResult, CommandError> {
5512 let before_char_count = self.editor.piece_table.char_count();
5513 let max_offset = self.editor.piece_table.char_count();
5514 if start > max_offset {
5515 return Err(CommandError::InvalidOffset(start));
5516 }
5517 if start + length > max_offset {
5518 return Err(CommandError::InvalidRange {
5519 start,
5520 end: start + length,
5521 });
5522 }
5523
5524 if length == 0 && text.is_empty() {
5525 return Ok(CommandResult::Success);
5526 }
5527
5528 let text = crate::text::normalize_crlf_to_lf_string(text);
5529 let before_selection = self.snapshot_selection_set();
5530
5531 let deleted_text = if length == 0 {
5532 String::new()
5533 } else {
5534 self.editor.piece_table.get_range(start, length)
5535 };
5536 let delta_deleted_text = deleted_text.clone();
5537 let delta_inserted_text = text.clone();
5538
5539 let affected_line = self.editor.line_index.char_offset_to_position(start).0;
5540 let deleted_newlines = deleted_text
5541 .as_bytes()
5542 .iter()
5543 .filter(|b| **b == b'\n')
5544 .count();
5545 let inserted_newlines = text.as_bytes().iter().filter(|b| **b == b'\n').count();
5546 let line_delta = inserted_newlines as isize - deleted_newlines as isize;
5547
5548 if length > 0 {
5550 self.editor.piece_table.delete(start, length);
5551 self.editor
5552 .interval_tree
5553 .update_for_deletion(start, start + length);
5554 for layer_tree in self.editor.style_layers.values_mut() {
5555 layer_tree.update_for_deletion(start, start + length);
5556 }
5557 }
5558
5559 let inserted_len = text.chars().count();
5560 if inserted_len > 0 {
5561 self.editor.piece_table.insert(start, &text);
5562 self.editor
5563 .interval_tree
5564 .update_for_insertion(start, inserted_len);
5565 for layer_tree in self.editor.style_layers.values_mut() {
5566 layer_tree.update_for_insertion(start, inserted_len);
5567 }
5568 }
5569
5570 self.apply_text_change_to_line_index_and_layout(start, &deleted_text, &text);
5572
5573 if line_delta != 0 {
5574 self.editor
5575 .folding_manager
5576 .apply_line_delta(affected_line, line_delta);
5577 self.editor
5578 .folding_manager
5579 .clamp_to_line_count(self.editor.line_index.line_count());
5580 }
5581
5582 self.normalize_cursor_and_selection();
5584
5585 let after_selection = self.snapshot_selection_set();
5586
5587 let step = UndoStep {
5588 group_id: 0,
5589 edits: vec![TextEdit {
5590 start_before: start,
5591 start_after: start,
5592 deleted_text,
5593 inserted_text: text,
5594 }],
5595 before_selection,
5596 after_selection,
5597 };
5598 let group_id = self.undo_redo.push_step(step, false);
5599
5600 self.last_text_delta = Some(TextDelta {
5601 before_char_count,
5602 after_char_count: self.editor.piece_table.char_count(),
5603 edits: vec![TextDeltaEdit {
5604 start,
5605 deleted_text: delta_deleted_text,
5606 inserted_text: delta_inserted_text,
5607 }],
5608 undo_group_id: Some(group_id),
5609 });
5610
5611 Ok(CommandResult::Success)
5612 }
5613
5614 fn cursor_char_offset(&self) -> usize {
5615 self.position_to_char_offset_clamped(self.editor.cursor_position)
5616 }
5617
5618 fn primary_selection_char_range(&self) -> Option<SearchMatch> {
5619 let selection = self.editor.selection.as_ref()?;
5620 let (min_pos, max_pos) = crate::selection_set::selection_min_max(selection);
5621 let start = self.position_to_char_offset_clamped(min_pos);
5622 let end = self.position_to_char_offset_clamped(max_pos);
5623 if start == end {
5624 None
5625 } else {
5626 Some(SearchMatch { start, end })
5627 }
5628 }
5629
5630 fn set_primary_selection_by_char_range(&mut self, range: SearchMatch) {
5631 let (start_line, start_col) = self.editor.line_index.char_offset_to_position(range.start);
5632 let (end_line, end_col) = self.editor.line_index.char_offset_to_position(range.end);
5633
5634 self.editor.cursor_position = Position::new(end_line, end_col);
5635 self.editor.secondary_selections.clear();
5636
5637 if range.start == range.end {
5638 self.editor.selection = None;
5639 } else {
5640 self.editor.selection = Some(Selection {
5641 start: Position::new(start_line, start_col),
5642 end: Position::new(end_line, end_col),
5643 direction: SelectionDirection::Forward,
5644 });
5645 }
5646 }
5647
5648 fn execute_find_command(
5649 &mut self,
5650 query: String,
5651 options: SearchOptions,
5652 forward: bool,
5653 ) -> Result<CommandResult, CommandError> {
5654 if query.is_empty() {
5655 return Ok(CommandResult::SearchNotFound);
5656 }
5657
5658 let text = self.editor.piece_table.get_text();
5659 let from = if let Some(selection) = self.primary_selection_char_range() {
5660 if forward {
5661 selection.end
5662 } else {
5663 selection.start
5664 }
5665 } else {
5666 self.cursor_char_offset()
5667 };
5668
5669 let found = if forward {
5670 find_next(&text, &query, options, from)
5671 } else {
5672 find_prev(&text, &query, options, from)
5673 }
5674 .map_err(|err| CommandError::Other(err.to_string()))?;
5675
5676 let Some(m) = found else {
5677 return Ok(CommandResult::SearchNotFound);
5678 };
5679
5680 self.set_primary_selection_by_char_range(m);
5681
5682 Ok(CommandResult::SearchMatch {
5683 start: m.start,
5684 end: m.end,
5685 })
5686 }
5687
5688 fn compile_user_regex(
5689 query: &str,
5690 options: SearchOptions,
5691 ) -> Result<regex::Regex, CommandError> {
5692 RegexBuilder::new(query)
5693 .case_insensitive(!options.case_sensitive)
5694 .multi_line(true)
5695 .build()
5696 .map_err(|err| CommandError::Other(format!("Invalid regex: {}", err)))
5697 }
5698
5699 fn regex_expand_replacement(
5700 re: ®ex::Regex,
5701 text: &str,
5702 index: &CharIndex,
5703 range: SearchMatch,
5704 replacement: &str,
5705 ) -> Result<String, CommandError> {
5706 let start_byte = index.char_to_byte(range.start);
5707 let end_byte = index.char_to_byte(range.end);
5708
5709 let caps = re
5710 .captures_at(text, start_byte)
5711 .ok_or_else(|| CommandError::Other("Regex match not found".to_string()))?;
5712 let whole = caps
5713 .get(0)
5714 .ok_or_else(|| CommandError::Other("Regex match missing capture 0".to_string()))?;
5715 if whole.start() != start_byte || whole.end() != end_byte {
5716 return Err(CommandError::Other(
5717 "Regex match did not align with the selected range".to_string(),
5718 ));
5719 }
5720
5721 let mut expanded = String::new();
5722 caps.expand(replacement, &mut expanded);
5723 Ok(expanded)
5724 }
5725
5726 fn execute_replace_current_command(
5727 &mut self,
5728 query: String,
5729 replacement: String,
5730 options: SearchOptions,
5731 ) -> Result<CommandResult, CommandError> {
5732 if query.is_empty() {
5733 return Err(CommandError::Other("Search query is empty".to_string()));
5734 }
5735
5736 let text = self.editor.piece_table.get_text();
5737 let selection_range = self.primary_selection_char_range();
5738
5739 let mut target = None::<SearchMatch>;
5740 if let Some(range) = selection_range {
5741 let is_match = crate::search::is_match_exact(&text, &query, options, range)
5742 .map_err(|err| CommandError::Other(err.to_string()))?;
5743 if is_match {
5744 target = Some(range);
5745 }
5746 }
5747
5748 if target.is_none() {
5749 let from = self.cursor_char_offset();
5750 target = find_next(&text, &query, options, from)
5751 .map_err(|err| CommandError::Other(err.to_string()))?;
5752 }
5753
5754 let Some(target) = target else {
5755 return Err(CommandError::Other("No match found".to_string()));
5756 };
5757
5758 let index = CharIndex::new(&text);
5759 let inserted_text = if options.regex {
5760 let re = Self::compile_user_regex(&query, options)?;
5761 Self::regex_expand_replacement(&re, &text, &index, target, &replacement)?
5762 } else {
5763 replacement
5764 };
5765 let inserted_text = crate::text::normalize_crlf_to_lf_string(inserted_text);
5766
5767 let deleted_text = self
5768 .editor
5769 .piece_table
5770 .get_range(target.start, target.len());
5771 let before_char_count = self.editor.piece_table.char_count();
5772 let delta_deleted_text = deleted_text.clone();
5773
5774 let before_selection = self.snapshot_selection_set();
5775 self.apply_text_ops(vec![(target.start, target.len(), inserted_text.as_str())])?;
5776
5777 let inserted_len = inserted_text.chars().count();
5778 let new_range = SearchMatch {
5779 start: target.start,
5780 end: target.start + inserted_len,
5781 };
5782 self.set_primary_selection_by_char_range(new_range);
5783 let after_selection = self.snapshot_selection_set();
5784
5785 let step = UndoStep {
5786 group_id: 0,
5787 edits: vec![TextEdit {
5788 start_before: target.start,
5789 start_after: target.start,
5790 deleted_text,
5791 inserted_text: inserted_text.clone(),
5792 }],
5793 before_selection,
5794 after_selection,
5795 };
5796 let group_id = self.undo_redo.push_step(step, false);
5797
5798 self.last_text_delta = Some(TextDelta {
5799 before_char_count,
5800 after_char_count: self.editor.piece_table.char_count(),
5801 edits: vec![TextDeltaEdit {
5802 start: target.start,
5803 deleted_text: delta_deleted_text,
5804 inserted_text,
5805 }],
5806 undo_group_id: Some(group_id),
5807 });
5808
5809 Ok(CommandResult::ReplaceResult { replaced: 1 })
5810 }
5811
5812 fn execute_replace_all_command(
5813 &mut self,
5814 query: String,
5815 replacement: String,
5816 options: SearchOptions,
5817 ) -> Result<CommandResult, CommandError> {
5818 if query.is_empty() {
5819 return Err(CommandError::Other("Search query is empty".to_string()));
5820 }
5821
5822 let replacement = crate::text::normalize_crlf_to_lf_string(replacement);
5823 let text = self.editor.piece_table.get_text();
5824 let matches =
5825 find_all(&text, &query, options).map_err(|err| CommandError::Other(err.to_string()))?;
5826 if matches.is_empty() {
5827 return Err(CommandError::Other("No match found".to_string()));
5828 }
5829 let match_count = matches.len();
5830
5831 let index = CharIndex::new(&text);
5832
5833 struct Op {
5834 start_before: usize,
5835 start_after: usize,
5836 delete_len: usize,
5837 deleted_text: String,
5838 inserted_text: String,
5839 inserted_len: usize,
5840 }
5841
5842 let mut ops: Vec<Op> = Vec::with_capacity(match_count);
5843 if options.regex {
5844 let re = Self::compile_user_regex(&query, options)?;
5845 for m in matches {
5846 let deleted_text = {
5847 let start_byte = index.char_to_byte(m.start);
5848 let end_byte = index.char_to_byte(m.end);
5849 text.get(start_byte..end_byte)
5850 .unwrap_or_default()
5851 .to_string()
5852 };
5853 let inserted_text =
5854 Self::regex_expand_replacement(&re, &text, &index, m, &replacement)?;
5855 let inserted_text = crate::text::normalize_crlf_to_lf_string(inserted_text);
5856 let inserted_len = inserted_text.chars().count();
5857 ops.push(Op {
5858 start_before: m.start,
5859 start_after: m.start,
5860 delete_len: m.len(),
5861 deleted_text,
5862 inserted_text,
5863 inserted_len,
5864 });
5865 }
5866 } else {
5867 let inserted_len = replacement.chars().count();
5868 for m in matches {
5869 let deleted_text = {
5870 let start_byte = index.char_to_byte(m.start);
5871 let end_byte = index.char_to_byte(m.end);
5872 text.get(start_byte..end_byte)
5873 .unwrap_or_default()
5874 .to_string()
5875 };
5876 ops.push(Op {
5877 start_before: m.start,
5878 start_after: m.start,
5879 delete_len: m.len(),
5880 deleted_text,
5881 inserted_text: replacement.clone(),
5882 inserted_len,
5883 });
5884 }
5885 }
5886
5887 ops.sort_by_key(|op| op.start_before);
5888
5889 let mut delta: i64 = 0;
5890 for op in &mut ops {
5891 let effective_start = op.start_before as i64 + delta;
5892 if effective_start < 0 {
5893 return Err(CommandError::Other(
5894 "ReplaceAll produced an invalid intermediate offset".to_string(),
5895 ));
5896 }
5897 op.start_after = effective_start as usize;
5898 delta += op.inserted_len as i64 - op.delete_len as i64;
5899 }
5900
5901 let before_char_count = self.editor.piece_table.char_count();
5902 let before_selection = self.snapshot_selection_set();
5903 let apply_ops: Vec<(usize, usize, &str)> = ops
5904 .iter()
5905 .map(|op| (op.start_before, op.delete_len, op.inserted_text.as_str()))
5906 .collect();
5907 self.apply_text_ops(apply_ops)?;
5908
5909 if let Some(first) = ops.first() {
5910 let caret_end = first.start_after + first.inserted_len;
5911 let select_end = if first.inserted_len == 0 {
5912 first.start_after
5913 } else {
5914 caret_end
5915 };
5916 self.set_primary_selection_by_char_range(SearchMatch {
5917 start: first.start_after,
5918 end: select_end,
5919 });
5920 } else {
5921 self.editor.selection = None;
5922 self.editor.secondary_selections.clear();
5923 }
5924
5925 let after_selection = self.snapshot_selection_set();
5926
5927 let edits: Vec<TextEdit> = ops
5928 .into_iter()
5929 .map(|op| TextEdit {
5930 start_before: op.start_before,
5931 start_after: op.start_after,
5932 deleted_text: op.deleted_text,
5933 inserted_text: op.inserted_text,
5934 })
5935 .collect();
5936
5937 let mut delta_edits: Vec<TextDeltaEdit> = edits
5938 .iter()
5939 .map(|e| TextDeltaEdit {
5940 start: e.start_before,
5941 deleted_text: e.deleted_text.clone(),
5942 inserted_text: e.inserted_text.clone(),
5943 })
5944 .collect();
5945 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
5946
5947 let step = UndoStep {
5948 group_id: 0,
5949 edits,
5950 before_selection,
5951 after_selection,
5952 };
5953 let group_id = self.undo_redo.push_step(step, false);
5954
5955 self.last_text_delta = Some(TextDelta {
5956 before_char_count,
5957 after_char_count: self.editor.piece_table.char_count(),
5958 edits: delta_edits,
5959 undo_group_id: Some(group_id),
5960 });
5961
5962 Ok(CommandResult::ReplaceResult {
5963 replaced: match_count,
5964 })
5965 }
5966
5967 fn execute_backspace_command(&mut self) -> Result<CommandResult, CommandError> {
5968 self.execute_delete_like_command(false)
5969 }
5970
5971 fn execute_delete_forward_command(&mut self) -> Result<CommandResult, CommandError> {
5972 self.execute_delete_like_command(true)
5973 }
5974
5975 fn execute_delete_to_prev_tab_stop_command(&mut self) -> Result<CommandResult, CommandError> {
5976 self.undo_redo.end_group();
5979
5980 let before_selection = self.snapshot_selection_set();
5981 let selections = before_selection.selections.clone();
5982 let primary_index = before_selection.primary_index;
5983
5984 let tab_width = self.editor.layout_engine.tab_width().max(1);
5985
5986 #[derive(Debug)]
5987 struct Op {
5988 selection_index: usize,
5989 start_offset: usize,
5990 delete_len: usize,
5991 deleted_text: String,
5992 start_after: usize,
5993 }
5994
5995 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
5996
5997 for (selection_index, selection) in selections.iter().enumerate() {
5998 let (range_start_pos, range_end_pos) = if selection.start <= selection.end {
5999 (selection.start, selection.end)
6000 } else {
6001 (selection.end, selection.start)
6002 };
6003
6004 let (start_offset, end_offset) = if range_start_pos != range_end_pos {
6005 let start_offset = self.position_to_char_offset_clamped(range_start_pos);
6006 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
6007 if start_offset <= end_offset {
6008 (start_offset, end_offset)
6009 } else {
6010 (end_offset, start_offset)
6011 }
6012 } else {
6013 let caret = selection.end;
6014 let caret_offset = self.position_to_char_offset_clamped(caret);
6015 if caret_offset == 0 {
6016 (0, 0)
6017 } else {
6018 let line_text = self
6019 .editor
6020 .line_index
6021 .get_line_text(caret.line)
6022 .unwrap_or_default();
6023 let line_char_len = line_text.chars().count();
6024 let col = caret.column.min(line_char_len);
6025
6026 let in_leading_whitespace = line_text
6027 .chars()
6028 .take(col)
6029 .all(|ch| ch == ' ' || ch == '\t');
6030
6031 if !in_leading_whitespace {
6032 (caret_offset - 1, caret_offset)
6033 } else {
6034 let x_in_line = visual_x_for_column(&line_text, col, tab_width);
6035 let back = if x_in_line == 0 {
6036 0
6037 } else {
6038 let rem = x_in_line % tab_width;
6039 if rem == 0 { tab_width } else { rem }
6040 };
6041 let target_x = x_in_line.saturating_sub(back);
6042
6043 let mut target_col = col;
6044 while target_col > 0 {
6045 let prev_col = target_col - 1;
6046 let prev_x = visual_x_for_column(&line_text, prev_col, tab_width);
6047 if prev_x < target_x {
6048 break;
6049 }
6050 target_col = prev_col;
6051 if prev_x == target_x {
6052 break;
6053 }
6054 }
6055
6056 let target_offset = self
6057 .editor
6058 .line_index
6059 .position_to_char_offset(caret.line, target_col);
6060 (target_offset, caret_offset)
6061 }
6062 }
6063 };
6064
6065 let delete_len = end_offset.saturating_sub(start_offset);
6066 let deleted_text = if delete_len == 0 {
6067 String::new()
6068 } else {
6069 self.editor.piece_table.get_range(start_offset, delete_len)
6070 };
6071
6072 ops.push(Op {
6073 selection_index,
6074 start_offset,
6075 delete_len,
6076 deleted_text,
6077 start_after: start_offset,
6078 });
6079 }
6080
6081 if !ops.iter().any(|op| op.delete_len > 0) {
6082 return Ok(CommandResult::Success);
6083 }
6084
6085 let before_char_count = self.editor.piece_table.char_count();
6086
6087 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
6089 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
6090
6091 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
6092 let mut delta: i64 = 0;
6093 for &idx in &asc_indices {
6094 let op = &mut ops[idx];
6095 let effective_start = (op.start_offset as i64 + delta) as usize;
6096 op.start_after = effective_start;
6097 caret_offsets[op.selection_index] = effective_start;
6098 delta -= op.delete_len as i64;
6099 }
6100
6101 let mut desc_indices = asc_indices;
6103 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
6104
6105 for &idx in &desc_indices {
6106 let op = &ops[idx];
6107 if op.delete_len == 0 {
6108 continue;
6109 }
6110
6111 let edit_line = self
6112 .editor
6113 .line_index
6114 .char_offset_to_position(op.start_offset)
6115 .0;
6116 let deleted_newlines = op
6117 .deleted_text
6118 .as_bytes()
6119 .iter()
6120 .filter(|b| **b == b'\n')
6121 .count();
6122 if deleted_newlines > 0 {
6123 self.editor
6124 .folding_manager
6125 .apply_line_delta(edit_line, -(deleted_newlines as isize));
6126 }
6127
6128 self.editor
6129 .piece_table
6130 .delete(op.start_offset, op.delete_len);
6131 self.editor
6132 .interval_tree
6133 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
6134 for layer_tree in self.editor.style_layers.values_mut() {
6135 layer_tree.update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
6136 }
6137
6138 self.apply_text_change_to_line_index_and_layout(op.start_offset, &op.deleted_text, "");
6139 }
6140
6141 self.editor
6142 .folding_manager
6143 .clamp_to_line_count(self.editor.line_index.line_count());
6144
6145 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
6147 for offset in &caret_offsets {
6148 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
6149 let pos = Position::new(line, column);
6150 new_carets.push(Selection {
6151 start: pos,
6152 end: pos,
6153 direction: SelectionDirection::Forward,
6154 });
6155 }
6156
6157 let (new_carets, new_primary_index) =
6158 crate::selection_set::normalize_selections(new_carets, primary_index);
6159 let primary = new_carets
6160 .get(new_primary_index)
6161 .cloned()
6162 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
6163
6164 self.editor.cursor_position = primary.end;
6165 self.editor.selection = None;
6166 self.editor.secondary_selections = new_carets
6167 .into_iter()
6168 .enumerate()
6169 .filter_map(|(idx, sel)| {
6170 if idx == new_primary_index {
6171 None
6172 } else {
6173 Some(sel)
6174 }
6175 })
6176 .collect();
6177
6178 let after_selection = self.snapshot_selection_set();
6179
6180 let edits: Vec<TextEdit> = ops
6181 .into_iter()
6182 .map(|op| TextEdit {
6183 start_before: op.start_offset,
6184 start_after: op.start_after,
6185 deleted_text: op.deleted_text,
6186 inserted_text: String::new(),
6187 })
6188 .collect();
6189
6190 let mut delta_edits: Vec<TextDeltaEdit> = edits
6191 .iter()
6192 .map(|e| TextDeltaEdit {
6193 start: e.start_before,
6194 deleted_text: e.deleted_text.clone(),
6195 inserted_text: e.inserted_text.clone(),
6196 })
6197 .collect();
6198 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
6199
6200 let step = UndoStep {
6201 group_id: 0,
6202 edits,
6203 before_selection,
6204 after_selection,
6205 };
6206 let group_id = self.undo_redo.push_step(step, false);
6207
6208 self.last_text_delta = Some(TextDelta {
6209 before_char_count,
6210 after_char_count: self.editor.piece_table.char_count(),
6211 edits: delta_edits,
6212 undo_group_id: Some(group_id),
6213 });
6214
6215 Ok(CommandResult::Success)
6216 }
6217
6218 fn execute_delete_by_boundary_command(
6219 &mut self,
6220 forward: bool,
6221 boundary: TextBoundary,
6222 ) -> Result<CommandResult, CommandError> {
6223 self.undo_redo.end_group();
6226
6227 let before_selection = self.snapshot_selection_set();
6228 let selections = before_selection.selections.clone();
6229 let primary_index = before_selection.primary_index;
6230
6231 let doc_char_count = self.editor.piece_table.char_count();
6232
6233 #[derive(Debug)]
6234 struct Op {
6235 selection_index: usize,
6236 start_offset: usize,
6237 delete_len: usize,
6238 deleted_text: String,
6239 start_after: usize,
6240 }
6241
6242 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
6243
6244 for (selection_index, selection) in selections.iter().enumerate() {
6245 let (range_start_pos, range_end_pos) = if selection.start <= selection.end {
6246 (selection.start, selection.end)
6247 } else {
6248 (selection.end, selection.start)
6249 };
6250
6251 let (start_offset, end_offset) = if range_start_pos != range_end_pos {
6252 let start_offset = self.position_to_char_offset_clamped(range_start_pos);
6253 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
6254 if start_offset <= end_offset {
6255 (start_offset, end_offset)
6256 } else {
6257 (end_offset, start_offset)
6258 }
6259 } else {
6260 let caret = selection.end;
6261 let caret_offset = self.position_to_char_offset_clamped(caret);
6262 let line_count = self.editor.line_index.line_count();
6263 let line = caret.line.min(line_count.saturating_sub(1));
6264 let line_text = self
6265 .editor
6266 .line_index
6267 .get_line_text(line)
6268 .unwrap_or_default();
6269 let line_char_len = line_text.chars().count();
6270 let col = caret.column.min(line_char_len);
6271
6272 if forward {
6273 if caret_offset >= doc_char_count {
6274 (caret_offset, caret_offset)
6275 } else if col >= line_char_len {
6276 (caret_offset, (caret_offset + 1).min(doc_char_count))
6277 } else {
6278 let next_col = next_boundary_column(&line_text, col, boundary);
6279 let start_offset =
6280 self.editor.line_index.position_to_char_offset(line, col);
6281 let end_offset = self
6282 .editor
6283 .line_index
6284 .position_to_char_offset(line, next_col);
6285 (start_offset, end_offset)
6286 }
6287 } else if caret_offset == 0 {
6288 (0, 0)
6289 } else if col == 0 {
6290 (caret_offset - 1, caret_offset)
6291 } else {
6292 let prev_col = prev_boundary_column(&line_text, col, boundary);
6293 let start_offset = self
6294 .editor
6295 .line_index
6296 .position_to_char_offset(line, prev_col);
6297 let end_offset = self.editor.line_index.position_to_char_offset(line, col);
6298 (start_offset, end_offset)
6299 }
6300 };
6301
6302 let delete_len = end_offset.saturating_sub(start_offset);
6303 let deleted_text = if delete_len == 0 {
6304 String::new()
6305 } else {
6306 self.editor.piece_table.get_range(start_offset, delete_len)
6307 };
6308
6309 ops.push(Op {
6310 selection_index,
6311 start_offset,
6312 delete_len,
6313 deleted_text,
6314 start_after: start_offset,
6315 });
6316 }
6317
6318 if !ops.iter().any(|op| op.delete_len > 0) {
6319 return Ok(CommandResult::Success);
6320 }
6321
6322 let before_char_count = self.editor.piece_table.char_count();
6323
6324 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
6326 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
6327
6328 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
6329 let mut delta: i64 = 0;
6330 for &idx in &asc_indices {
6331 let op = &mut ops[idx];
6332 let effective_start = (op.start_offset as i64 + delta) as usize;
6333 op.start_after = effective_start;
6334 caret_offsets[op.selection_index] = effective_start;
6335 delta -= op.delete_len as i64;
6336 }
6337
6338 let mut desc_indices = asc_indices;
6340 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
6341
6342 for &idx in &desc_indices {
6343 let op = &ops[idx];
6344 if op.delete_len == 0 {
6345 continue;
6346 }
6347
6348 self.editor
6349 .piece_table
6350 .delete(op.start_offset, op.delete_len);
6351 self.editor
6352 .interval_tree
6353 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
6354 for layer_tree in self.editor.style_layers.values_mut() {
6355 layer_tree.update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
6356 }
6357
6358 self.apply_text_change_to_line_index_and_layout(op.start_offset, &op.deleted_text, "");
6359 }
6360
6361 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
6363 for offset in &caret_offsets {
6364 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
6365 let pos = Position::new(line, column);
6366 new_carets.push(Selection {
6367 start: pos,
6368 end: pos,
6369 direction: SelectionDirection::Forward,
6370 });
6371 }
6372
6373 let (new_carets, new_primary_index) =
6374 crate::selection_set::normalize_selections(new_carets, primary_index);
6375 let primary = new_carets
6376 .get(new_primary_index)
6377 .cloned()
6378 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
6379
6380 self.editor.cursor_position = primary.end;
6381 self.editor.selection = None;
6382 self.editor.secondary_selections = new_carets
6383 .into_iter()
6384 .enumerate()
6385 .filter_map(|(idx, sel)| {
6386 if idx == new_primary_index {
6387 None
6388 } else {
6389 Some(sel)
6390 }
6391 })
6392 .collect();
6393
6394 let after_selection = self.snapshot_selection_set();
6395
6396 let edits: Vec<TextEdit> = ops
6397 .into_iter()
6398 .map(|op| TextEdit {
6399 start_before: op.start_offset,
6400 start_after: op.start_after,
6401 deleted_text: op.deleted_text,
6402 inserted_text: String::new(),
6403 })
6404 .collect();
6405
6406 let mut delta_edits: Vec<TextDeltaEdit> = edits
6407 .iter()
6408 .map(|e| TextDeltaEdit {
6409 start: e.start_before,
6410 deleted_text: e.deleted_text.clone(),
6411 inserted_text: e.inserted_text.clone(),
6412 })
6413 .collect();
6414 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
6415
6416 let step = UndoStep {
6417 group_id: 0,
6418 edits,
6419 before_selection,
6420 after_selection,
6421 };
6422 let group_id = self.undo_redo.push_step(step, false);
6423
6424 self.last_text_delta = Some(TextDelta {
6425 before_char_count,
6426 after_char_count: self.editor.piece_table.char_count(),
6427 edits: delta_edits,
6428 undo_group_id: Some(group_id),
6429 });
6430
6431 Ok(CommandResult::Success)
6432 }
6433
6434 fn execute_delete_like_command(
6435 &mut self,
6436 forward: bool,
6437 ) -> Result<CommandResult, CommandError> {
6438 self.undo_redo.end_group();
6441
6442 let before_selection = self.snapshot_selection_set();
6443 let selections = before_selection.selections.clone();
6444 let primary_index = before_selection.primary_index;
6445
6446 let doc_char_count = self.editor.piece_table.char_count();
6447
6448 #[derive(Debug)]
6449 struct Op {
6450 selection_index: usize,
6451 start_offset: usize,
6452 delete_len: usize,
6453 deleted_text: String,
6454 start_after: usize,
6455 }
6456
6457 let mut ops: Vec<Op> = Vec::with_capacity(selections.len());
6458
6459 for (selection_index, selection) in selections.iter().enumerate() {
6460 let (range_start_pos, range_end_pos) = if selection.start <= selection.end {
6461 (selection.start, selection.end)
6462 } else {
6463 (selection.end, selection.start)
6464 };
6465
6466 let (start_offset, end_offset) = if range_start_pos != range_end_pos {
6467 let start_offset = self.position_to_char_offset_clamped(range_start_pos);
6468 let end_offset = self.position_to_char_offset_clamped(range_end_pos);
6469 if start_offset <= end_offset {
6470 (start_offset, end_offset)
6471 } else {
6472 (end_offset, start_offset)
6473 }
6474 } else {
6475 let caret_offset = self.position_to_char_offset_clamped(selection.end);
6476 if forward {
6477 if caret_offset >= doc_char_count {
6478 (caret_offset, caret_offset)
6479 } else {
6480 (caret_offset, (caret_offset + 1).min(doc_char_count))
6481 }
6482 } else if caret_offset == 0 {
6483 (0, 0)
6484 } else {
6485 (caret_offset - 1, caret_offset)
6486 }
6487 };
6488
6489 let delete_len = end_offset.saturating_sub(start_offset);
6490 let deleted_text = if delete_len == 0 {
6491 String::new()
6492 } else {
6493 self.editor.piece_table.get_range(start_offset, delete_len)
6494 };
6495
6496 ops.push(Op {
6497 selection_index,
6498 start_offset,
6499 delete_len,
6500 deleted_text,
6501 start_after: start_offset,
6502 });
6503 }
6504
6505 if !ops.iter().any(|op| op.delete_len > 0) {
6506 return Ok(CommandResult::Success);
6507 }
6508
6509 let before_char_count = self.editor.piece_table.char_count();
6510
6511 let mut asc_indices: Vec<usize> = (0..ops.len()).collect();
6513 asc_indices.sort_by_key(|&idx| ops[idx].start_offset);
6514
6515 let mut caret_offsets: Vec<usize> = vec![0; ops.len()];
6516 let mut delta: i64 = 0;
6517 for &idx in &asc_indices {
6518 let op = &mut ops[idx];
6519 let effective_start = (op.start_offset as i64 + delta) as usize;
6520 op.start_after = effective_start;
6521 caret_offsets[op.selection_index] = effective_start;
6522 delta -= op.delete_len as i64;
6523 }
6524
6525 let mut desc_indices = asc_indices;
6527 desc_indices.sort_by_key(|&idx| std::cmp::Reverse(ops[idx].start_offset));
6528
6529 for &idx in &desc_indices {
6530 let op = &ops[idx];
6531 if op.delete_len == 0 {
6532 continue;
6533 }
6534
6535 self.editor
6536 .piece_table
6537 .delete(op.start_offset, op.delete_len);
6538 self.editor
6539 .interval_tree
6540 .update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
6541 for layer_tree in self.editor.style_layers.values_mut() {
6542 layer_tree.update_for_deletion(op.start_offset, op.start_offset + op.delete_len);
6543 }
6544
6545 self.apply_text_change_to_line_index_and_layout(op.start_offset, &op.deleted_text, "");
6546 }
6547
6548 let mut new_carets: Vec<Selection> = Vec::with_capacity(caret_offsets.len());
6550 for offset in &caret_offsets {
6551 let (line, column) = self.editor.line_index.char_offset_to_position(*offset);
6552 let pos = Position::new(line, column);
6553 new_carets.push(Selection {
6554 start: pos,
6555 end: pos,
6556 direction: SelectionDirection::Forward,
6557 });
6558 }
6559
6560 let (new_carets, new_primary_index) =
6561 crate::selection_set::normalize_selections(new_carets, primary_index);
6562 let primary = new_carets
6563 .get(new_primary_index)
6564 .cloned()
6565 .ok_or_else(|| CommandError::Other("Invalid primary caret".to_string()))?;
6566
6567 self.editor.cursor_position = primary.end;
6568 self.editor.selection = None;
6569 self.editor.secondary_selections = new_carets
6570 .into_iter()
6571 .enumerate()
6572 .filter_map(|(idx, sel)| {
6573 if idx == new_primary_index {
6574 None
6575 } else {
6576 Some(sel)
6577 }
6578 })
6579 .collect();
6580
6581 let after_selection = self.snapshot_selection_set();
6582
6583 let edits: Vec<TextEdit> = ops
6584 .into_iter()
6585 .map(|op| TextEdit {
6586 start_before: op.start_offset,
6587 start_after: op.start_after,
6588 deleted_text: op.deleted_text,
6589 inserted_text: String::new(),
6590 })
6591 .collect();
6592
6593 let mut delta_edits: Vec<TextDeltaEdit> = edits
6594 .iter()
6595 .map(|e| TextDeltaEdit {
6596 start: e.start_before,
6597 deleted_text: e.deleted_text.clone(),
6598 inserted_text: String::new(),
6599 })
6600 .collect();
6601 delta_edits.sort_by_key(|e| std::cmp::Reverse(e.start));
6602
6603 let step = UndoStep {
6604 group_id: 0,
6605 edits,
6606 before_selection,
6607 after_selection,
6608 };
6609 let group_id = self.undo_redo.push_step(step, false);
6610
6611 self.last_text_delta = Some(TextDelta {
6612 before_char_count,
6613 after_char_count: self.editor.piece_table.char_count(),
6614 edits: delta_edits,
6615 undo_group_id: Some(group_id),
6616 });
6617
6618 Ok(CommandResult::Success)
6619 }
6620
6621 fn snapshot_selection_set(&self) -> SelectionSetSnapshot {
6622 let mut selections: Vec<Selection> =
6623 Vec::with_capacity(1 + self.editor.secondary_selections.len());
6624
6625 let primary = self.editor.selection.clone().unwrap_or(Selection {
6626 start: self.editor.cursor_position,
6627 end: self.editor.cursor_position,
6628 direction: SelectionDirection::Forward,
6629 });
6630 selections.push(primary);
6631 selections.extend(self.editor.secondary_selections.iter().cloned());
6632
6633 let (selections, primary_index) = crate::selection_set::normalize_selections(selections, 0);
6634 SelectionSetSnapshot {
6635 selections,
6636 primary_index,
6637 }
6638 }
6639
6640 fn restore_selection_set(&mut self, snapshot: SelectionSetSnapshot) {
6641 if snapshot.selections.is_empty() {
6642 self.editor.cursor_position = Position::new(0, 0);
6643 self.editor.selection = None;
6644 self.editor.secondary_selections.clear();
6645 return;
6646 }
6647
6648 let primary = snapshot
6649 .selections
6650 .get(snapshot.primary_index)
6651 .cloned()
6652 .unwrap_or_else(|| snapshot.selections[0].clone());
6653
6654 self.editor.cursor_position = primary.end;
6655 self.editor.selection = if primary.start == primary.end {
6656 None
6657 } else {
6658 Some(primary.clone())
6659 };
6660
6661 self.editor.secondary_selections = snapshot
6662 .selections
6663 .into_iter()
6664 .enumerate()
6665 .filter_map(|(idx, sel)| {
6666 if idx == snapshot.primary_index {
6667 None
6668 } else {
6669 Some(sel)
6670 }
6671 })
6672 .collect();
6673
6674 self.normalize_cursor_and_selection();
6675 }
6676
6677 fn apply_undo_edits(&mut self, edits: &[TextEdit]) -> Result<(), CommandError> {
6678 let mut ops: Vec<(usize, usize, &str)> = Vec::with_capacity(edits.len());
6680 for edit in edits {
6681 let start = edit.start_after;
6682 let delete_len = edit.inserted_len();
6683 let insert_text = edit.deleted_text.as_str();
6684 ops.push((start, delete_len, insert_text));
6685 }
6686 self.apply_text_ops(ops)
6687 }
6688
6689 fn apply_redo_edits(&mut self, edits: &[TextEdit]) -> Result<(), CommandError> {
6690 let mut ops: Vec<(usize, usize, &str)> = Vec::with_capacity(edits.len());
6691 for edit in edits {
6692 let start = edit.start_before;
6693 let delete_len = edit.deleted_len();
6694 let insert_text = edit.inserted_text.as_str();
6695 ops.push((start, delete_len, insert_text));
6696 }
6697 self.apply_text_ops(ops)
6698 }
6699
6700 fn apply_text_ops(&mut self, mut ops: Vec<(usize, usize, &str)>) -> Result<(), CommandError> {
6701 ops.sort_by_key(|(start, _, _)| std::cmp::Reverse(*start));
6703
6704 for (start, delete_len, insert_text) in ops {
6705 let max_offset = self.editor.piece_table.char_count();
6706 if start > max_offset {
6707 return Err(CommandError::InvalidOffset(start));
6708 }
6709 if start + delete_len > max_offset {
6710 return Err(CommandError::InvalidRange {
6711 start,
6712 end: start + delete_len,
6713 });
6714 }
6715
6716 let edit_line = self.editor.line_index.char_offset_to_position(start).0;
6717 let deleted_text = if delete_len > 0 {
6718 self.editor.piece_table.get_range(start, delete_len)
6719 } else {
6720 String::new()
6721 };
6722 let deleted_newlines = deleted_text
6723 .as_bytes()
6724 .iter()
6725 .filter(|b| **b == b'\n')
6726 .count();
6727 let inserted_newlines = insert_text
6728 .as_bytes()
6729 .iter()
6730 .filter(|b| **b == b'\n')
6731 .count();
6732 let line_delta = inserted_newlines as isize - deleted_newlines as isize;
6733 if line_delta != 0 {
6734 self.editor
6735 .folding_manager
6736 .apply_line_delta(edit_line, line_delta);
6737 }
6738
6739 if delete_len > 0 {
6740 self.editor.piece_table.delete(start, delete_len);
6741 self.editor
6742 .interval_tree
6743 .update_for_deletion(start, start + delete_len);
6744 for layer_tree in self.editor.style_layers.values_mut() {
6745 layer_tree.update_for_deletion(start, start + delete_len);
6746 }
6747 }
6748
6749 let insert_len = insert_text.chars().count();
6750 if insert_len > 0 {
6751 self.editor.piece_table.insert(start, insert_text);
6752 self.editor
6753 .interval_tree
6754 .update_for_insertion(start, insert_len);
6755 for layer_tree in self.editor.style_layers.values_mut() {
6756 layer_tree.update_for_insertion(start, insert_len);
6757 }
6758 }
6759
6760 self.apply_text_change_to_line_index_and_layout(start, &deleted_text, insert_text);
6761 }
6762
6763 self.editor
6764 .folding_manager
6765 .clamp_to_line_count(self.editor.line_index.line_count());
6766 self.normalize_cursor_and_selection();
6767
6768 Ok(())
6769 }
6770
6771 fn execute_cursor(&mut self, command: CursorCommand) -> Result<CommandResult, CommandError> {
6773 match command {
6774 CursorCommand::MoveTo { line, column } => {
6775 if line >= self.editor.line_index.line_count() {
6776 return Err(CommandError::InvalidPosition { line, column });
6777 }
6778
6779 let clamped_column = self.clamp_column_for_line(line, column);
6780 self.editor.cursor_position = Position::new(line, clamped_column);
6781 self.preferred_x_cells = self
6782 .editor
6783 .logical_position_to_visual(line, clamped_column)
6784 .map(|(_, x)| x);
6785 self.editor.secondary_selections.clear();
6787 Ok(CommandResult::Success)
6788 }
6789 CursorCommand::MoveBy {
6790 delta_line,
6791 delta_column,
6792 } => {
6793 let new_line = if delta_line >= 0 {
6794 self.editor.cursor_position.line + delta_line as usize
6795 } else {
6796 self.editor
6797 .cursor_position
6798 .line
6799 .saturating_sub((-delta_line) as usize)
6800 };
6801
6802 let new_column = if delta_column >= 0 {
6803 self.editor.cursor_position.column + delta_column as usize
6804 } else {
6805 self.editor
6806 .cursor_position
6807 .column
6808 .saturating_sub((-delta_column) as usize)
6809 };
6810
6811 if new_line >= self.editor.line_index.line_count() {
6812 return Err(CommandError::InvalidPosition {
6813 line: new_line,
6814 column: new_column,
6815 });
6816 }
6817
6818 let clamped_column = self.clamp_column_for_line(new_line, new_column);
6819 self.editor.cursor_position = Position::new(new_line, clamped_column);
6820 self.preferred_x_cells = self
6821 .editor
6822 .logical_position_to_visual(new_line, clamped_column)
6823 .map(|(_, x)| x);
6824 Ok(CommandResult::Success)
6825 }
6826 CursorCommand::MoveGraphemeLeft => {
6827 let line_count = self.editor.line_index.line_count();
6828 if line_count == 0 {
6829 return Ok(CommandResult::Success);
6830 }
6831
6832 let mut line = self
6833 .editor
6834 .cursor_position
6835 .line
6836 .min(line_count.saturating_sub(1));
6837 let mut line_text = self
6838 .editor
6839 .line_index
6840 .get_line_text(line)
6841 .unwrap_or_default();
6842 let mut line_char_len = line_text.chars().count();
6843 let mut col = self.editor.cursor_position.column.min(line_char_len);
6844
6845 if col == 0 {
6846 if line == 0 {
6847 return Ok(CommandResult::Success);
6848 }
6849 line = line.saturating_sub(1);
6850 line_text = self
6851 .editor
6852 .line_index
6853 .get_line_text(line)
6854 .unwrap_or_default();
6855 line_char_len = line_text.chars().count();
6856 col = line_char_len;
6857 } else {
6858 col = prev_boundary_column(&line_text, col, TextBoundary::Grapheme);
6859 }
6860
6861 self.editor.cursor_position = Position::new(line, col);
6862 self.preferred_x_cells = self
6863 .editor
6864 .logical_position_to_visual(line, col)
6865 .map(|(_, x)| x);
6866 Ok(CommandResult::Success)
6867 }
6868 CursorCommand::MoveGraphemeRight => {
6869 let line_count = self.editor.line_index.line_count();
6870 if line_count == 0 {
6871 return Ok(CommandResult::Success);
6872 }
6873
6874 let line = self
6875 .editor
6876 .cursor_position
6877 .line
6878 .min(line_count.saturating_sub(1));
6879 let line_text = self
6880 .editor
6881 .line_index
6882 .get_line_text(line)
6883 .unwrap_or_default();
6884 let line_char_len = line_text.chars().count();
6885 let col = self.editor.cursor_position.column.min(line_char_len);
6886
6887 let (line, col) = if col >= line_char_len {
6888 if line + 1 >= line_count {
6889 return Ok(CommandResult::Success);
6890 }
6891 (line + 1, 0)
6892 } else {
6893 (
6894 line,
6895 next_boundary_column(&line_text, col, TextBoundary::Grapheme),
6896 )
6897 };
6898
6899 self.editor.cursor_position = Position::new(line, col);
6900 self.preferred_x_cells = self
6901 .editor
6902 .logical_position_to_visual(line, col)
6903 .map(|(_, x)| x);
6904 Ok(CommandResult::Success)
6905 }
6906 CursorCommand::MoveWordLeft => {
6907 let line_count = self.editor.line_index.line_count();
6908 if line_count == 0 {
6909 return Ok(CommandResult::Success);
6910 }
6911
6912 let mut line = self
6913 .editor
6914 .cursor_position
6915 .line
6916 .min(line_count.saturating_sub(1));
6917 let mut line_text = self
6918 .editor
6919 .line_index
6920 .get_line_text(line)
6921 .unwrap_or_default();
6922 let mut line_char_len = line_text.chars().count();
6923 let mut col = self.editor.cursor_position.column.min(line_char_len);
6924
6925 if col == 0 {
6926 if line == 0 {
6927 return Ok(CommandResult::Success);
6928 }
6929 line = line.saturating_sub(1);
6930 line_text = self
6931 .editor
6932 .line_index
6933 .get_line_text(line)
6934 .unwrap_or_default();
6935 line_char_len = line_text.chars().count();
6936 col = line_char_len;
6937 } else {
6938 col = prev_boundary_column(&line_text, col, TextBoundary::Word);
6939 }
6940
6941 self.editor.cursor_position = Position::new(line, col);
6942 self.preferred_x_cells = self
6943 .editor
6944 .logical_position_to_visual(line, col)
6945 .map(|(_, x)| x);
6946 Ok(CommandResult::Success)
6947 }
6948 CursorCommand::MoveWordRight => {
6949 let line_count = self.editor.line_index.line_count();
6950 if line_count == 0 {
6951 return Ok(CommandResult::Success);
6952 }
6953
6954 let line = self
6955 .editor
6956 .cursor_position
6957 .line
6958 .min(line_count.saturating_sub(1));
6959 let line_text = self
6960 .editor
6961 .line_index
6962 .get_line_text(line)
6963 .unwrap_or_default();
6964 let line_char_len = line_text.chars().count();
6965 let col = self.editor.cursor_position.column.min(line_char_len);
6966
6967 let (line, col) = if col >= line_char_len {
6968 if line + 1 >= line_count {
6969 return Ok(CommandResult::Success);
6970 }
6971 (line + 1, 0)
6972 } else {
6973 (
6974 line,
6975 next_boundary_column(&line_text, col, TextBoundary::Word),
6976 )
6977 };
6978
6979 self.editor.cursor_position = Position::new(line, col);
6980 self.preferred_x_cells = self
6981 .editor
6982 .logical_position_to_visual(line, col)
6983 .map(|(_, x)| x);
6984 Ok(CommandResult::Success)
6985 }
6986 CursorCommand::MoveVisualBy { delta_rows } => {
6987 let Some((current_row, current_x)) = self.editor.logical_position_to_visual(
6988 self.editor.cursor_position.line,
6989 self.editor.cursor_position.column,
6990 ) else {
6991 return Ok(CommandResult::Success);
6992 };
6993
6994 let preferred_x = self.preferred_x_cells.unwrap_or(current_x);
6995 self.preferred_x_cells = Some(preferred_x);
6996
6997 let total_visual = self.editor.visual_line_count();
6998 if total_visual == 0 {
6999 return Ok(CommandResult::Success);
7000 }
7001
7002 let target_row = if delta_rows >= 0 {
7003 current_row.saturating_add(delta_rows as usize)
7004 } else {
7005 current_row.saturating_sub((-delta_rows) as usize)
7006 }
7007 .min(total_visual.saturating_sub(1));
7008
7009 let Some(pos) = self
7010 .editor
7011 .visual_position_to_logical(target_row, preferred_x)
7012 else {
7013 return Ok(CommandResult::Success);
7014 };
7015
7016 self.editor.cursor_position = pos;
7017 Ok(CommandResult::Success)
7018 }
7019 CursorCommand::MoveToVisual { row, x_cells } => {
7020 let Some(pos) = self.editor.visual_position_to_logical(row, x_cells) else {
7021 return Ok(CommandResult::Success);
7022 };
7023
7024 self.editor.cursor_position = pos;
7025 self.preferred_x_cells = Some(x_cells);
7026 self.editor.secondary_selections.clear();
7028 Ok(CommandResult::Success)
7029 }
7030 CursorCommand::MoveToLineStart => {
7031 let line = self.editor.cursor_position.line;
7032 self.editor.cursor_position = Position::new(line, 0);
7033 self.preferred_x_cells = Some(0);
7034 self.editor.secondary_selections.clear();
7035 Ok(CommandResult::Success)
7036 }
7037 CursorCommand::MoveToLineEnd => {
7038 let line = self.editor.cursor_position.line;
7039 let end_col = self.clamp_column_for_line(line, usize::MAX);
7040 self.editor.cursor_position = Position::new(line, end_col);
7041 self.preferred_x_cells = self
7042 .editor
7043 .logical_position_to_visual(line, end_col)
7044 .map(|(_, x)| x);
7045 self.editor.secondary_selections.clear();
7046 Ok(CommandResult::Success)
7047 }
7048 CursorCommand::MoveToVisualLineStart => {
7049 let line = self.editor.cursor_position.line;
7050 let Some(layout) = self.editor.layout_engine.get_line_layout(line) else {
7051 return Ok(CommandResult::Success);
7052 };
7053
7054 let line_text = self
7055 .editor
7056 .line_index
7057 .get_line_text(line)
7058 .unwrap_or_default();
7059 let line_char_len = line_text.chars().count();
7060 let column = self.editor.cursor_position.column.min(line_char_len);
7061
7062 let mut seg_start = 0usize;
7063 for wp in &layout.wrap_points {
7064 if column >= wp.char_index {
7065 seg_start = wp.char_index;
7066 } else {
7067 break;
7068 }
7069 }
7070
7071 self.editor.cursor_position = Position::new(line, seg_start);
7072 self.preferred_x_cells = self
7073 .editor
7074 .logical_position_to_visual(line, seg_start)
7075 .map(|(_, x)| x);
7076 self.editor.secondary_selections.clear();
7077 Ok(CommandResult::Success)
7078 }
7079 CursorCommand::MoveToVisualLineEnd => {
7080 let line = self.editor.cursor_position.line;
7081 let Some(layout) = self.editor.layout_engine.get_line_layout(line) else {
7082 return Ok(CommandResult::Success);
7083 };
7084
7085 let line_text = self
7086 .editor
7087 .line_index
7088 .get_line_text(line)
7089 .unwrap_or_default();
7090 let line_char_len = line_text.chars().count();
7091 let column = self.editor.cursor_position.column.min(line_char_len);
7092
7093 let mut seg_end = line_char_len;
7094 for wp in &layout.wrap_points {
7095 if column < wp.char_index {
7096 seg_end = wp.char_index;
7097 break;
7098 }
7099 }
7100
7101 self.editor.cursor_position = Position::new(line, seg_end);
7102 self.preferred_x_cells = self
7103 .editor
7104 .logical_position_to_visual(line, seg_end)
7105 .map(|(_, x)| x);
7106 self.editor.secondary_selections.clear();
7107 Ok(CommandResult::Success)
7108 }
7109 CursorCommand::SetSelection { start, end } => {
7110 if start.line >= self.editor.line_index.line_count()
7111 || end.line >= self.editor.line_index.line_count()
7112 {
7113 return Err(CommandError::InvalidPosition {
7114 line: start.line.max(end.line),
7115 column: start.column.max(end.column),
7116 });
7117 }
7118
7119 let start = Position::new(
7120 start.line,
7121 self.clamp_column_for_line(start.line, start.column),
7122 );
7123 let end = Position::new(end.line, self.clamp_column_for_line(end.line, end.column));
7124
7125 let direction = if start.line < end.line
7126 || (start.line == end.line && start.column <= end.column)
7127 {
7128 SelectionDirection::Forward
7129 } else {
7130 SelectionDirection::Backward
7131 };
7132
7133 self.editor.selection = Some(Selection {
7134 start,
7135 end,
7136 direction,
7137 });
7138 Ok(CommandResult::Success)
7139 }
7140 CursorCommand::ExtendSelection { to } => {
7141 if to.line >= self.editor.line_index.line_count() {
7142 return Err(CommandError::InvalidPosition {
7143 line: to.line,
7144 column: to.column,
7145 });
7146 }
7147
7148 let to = Position::new(to.line, self.clamp_column_for_line(to.line, to.column));
7149
7150 if let Some(ref mut selection) = self.editor.selection {
7151 selection.end = to;
7152 selection.direction = if selection.start.line < to.line
7153 || (selection.start.line == to.line && selection.start.column <= to.column)
7154 {
7155 SelectionDirection::Forward
7156 } else {
7157 SelectionDirection::Backward
7158 };
7159 } else {
7160 self.editor.selection = Some(Selection {
7162 start: self.editor.cursor_position,
7163 end: to,
7164 direction: if self.editor.cursor_position.line < to.line
7165 || (self.editor.cursor_position.line == to.line
7166 && self.editor.cursor_position.column <= to.column)
7167 {
7168 SelectionDirection::Forward
7169 } else {
7170 SelectionDirection::Backward
7171 },
7172 });
7173 }
7174 Ok(CommandResult::Success)
7175 }
7176 CursorCommand::ClearSelection => {
7177 self.editor.selection = None;
7178 Ok(CommandResult::Success)
7179 }
7180 CursorCommand::SetSelections {
7181 selections,
7182 primary_index,
7183 } => {
7184 let line_count = self.editor.line_index.line_count();
7185 if selections.is_empty() {
7186 return Err(CommandError::Other(
7187 "SetSelections requires a non-empty selection list".to_string(),
7188 ));
7189 }
7190 if primary_index >= selections.len() {
7191 return Err(CommandError::Other(format!(
7192 "Invalid primary_index {} for {} selections",
7193 primary_index,
7194 selections.len()
7195 )));
7196 }
7197
7198 for sel in &selections {
7199 if sel.start.line >= line_count || sel.end.line >= line_count {
7200 return Err(CommandError::InvalidPosition {
7201 line: sel.start.line.max(sel.end.line),
7202 column: sel.start.column.max(sel.end.column),
7203 });
7204 }
7205 }
7206
7207 let (selections, primary_index) =
7208 crate::selection_set::normalize_selections(selections, primary_index);
7209
7210 let primary = selections
7211 .get(primary_index)
7212 .cloned()
7213 .ok_or_else(|| CommandError::Other("Invalid primary selection".to_string()))?;
7214
7215 self.editor.cursor_position = primary.end;
7216 self.editor.selection = if primary.start == primary.end {
7217 None
7218 } else {
7219 Some(primary.clone())
7220 };
7221
7222 self.editor.secondary_selections = selections
7223 .into_iter()
7224 .enumerate()
7225 .filter_map(|(idx, sel)| {
7226 if idx == primary_index {
7227 None
7228 } else {
7229 Some(sel)
7230 }
7231 })
7232 .collect();
7233
7234 Ok(CommandResult::Success)
7235 }
7236 CursorCommand::ClearSecondarySelections => {
7237 self.editor.secondary_selections.clear();
7238 Ok(CommandResult::Success)
7239 }
7240 CursorCommand::SetRectSelection { anchor, active } => {
7241 let line_count = self.editor.line_index.line_count();
7242 if anchor.line >= line_count || active.line >= line_count {
7243 return Err(CommandError::InvalidPosition {
7244 line: anchor.line.max(active.line),
7245 column: anchor.column.max(active.column),
7246 });
7247 }
7248
7249 let (selections, primary_index) =
7250 crate::selection_set::rect_selections(anchor, active);
7251
7252 self.execute_cursor(CursorCommand::SetSelections {
7254 selections,
7255 primary_index,
7256 })?;
7257 Ok(CommandResult::Success)
7258 }
7259 CursorCommand::SelectLine => self.execute_select_line_command(),
7260 CursorCommand::SelectWord => self.execute_select_word_command(),
7261 CursorCommand::ExpandSelection => self.execute_expand_selection_command(),
7262 CursorCommand::AddCursorAbove => self.execute_add_cursor_vertical_command(true),
7263 CursorCommand::AddCursorBelow => self.execute_add_cursor_vertical_command(false),
7264 CursorCommand::AddNextOccurrence { options } => {
7265 self.execute_add_next_occurrence_command(options)
7266 }
7267 CursorCommand::AddAllOccurrences { options } => {
7268 self.execute_add_all_occurrences_command(options)
7269 }
7270 CursorCommand::FindNext { query, options } => {
7271 self.execute_find_command(query, options, true)
7272 }
7273 CursorCommand::FindPrev { query, options } => {
7274 self.execute_find_command(query, options, false)
7275 }
7276 }
7277 }
7278
7279 fn execute_view(&mut self, command: ViewCommand) -> Result<CommandResult, CommandError> {
7281 match command {
7282 ViewCommand::SetViewportWidth { width } => {
7283 if width == 0 {
7284 return Err(CommandError::Other(
7285 "Viewport width must be greater than 0".to_string(),
7286 ));
7287 }
7288
7289 self.editor.viewport_width = width;
7290 self.editor.layout_engine.set_viewport_width(width);
7291 Ok(CommandResult::Success)
7292 }
7293 ViewCommand::SetWrapMode { mode } => {
7294 self.editor.layout_engine.set_wrap_mode(mode);
7295 Ok(CommandResult::Success)
7296 }
7297 ViewCommand::SetWrapIndent { indent } => {
7298 self.editor.layout_engine.set_wrap_indent(indent);
7299 Ok(CommandResult::Success)
7300 }
7301 ViewCommand::SetTabWidth { width } => {
7302 if width == 0 {
7303 return Err(CommandError::Other(
7304 "Tab width must be greater than 0".to_string(),
7305 ));
7306 }
7307
7308 self.editor.layout_engine.set_tab_width(width);
7309 Ok(CommandResult::Success)
7310 }
7311 ViewCommand::SetTabKeyBehavior { behavior } => {
7312 self.tab_key_behavior = behavior;
7313 Ok(CommandResult::Success)
7314 }
7315 ViewCommand::ScrollTo { line } => {
7316 if line >= self.editor.line_index.line_count() {
7317 return Err(CommandError::InvalidPosition { line, column: 0 });
7318 }
7319
7320 Ok(CommandResult::Success)
7323 }
7324 ViewCommand::GetViewport { start_row, count } => {
7325 let grid = self.editor.get_headless_grid_styled(start_row, count);
7326 Ok(CommandResult::Viewport(grid))
7327 }
7328 }
7329 }
7330
7331 fn execute_style(&mut self, command: StyleCommand) -> Result<CommandResult, CommandError> {
7333 match command {
7334 StyleCommand::AddStyle {
7335 start,
7336 end,
7337 style_id,
7338 } => {
7339 if start >= end {
7340 return Err(CommandError::InvalidRange { start, end });
7341 }
7342
7343 let interval = crate::intervals::Interval::new(start, end, style_id);
7344 self.editor.interval_tree.insert(interval);
7345 Ok(CommandResult::Success)
7346 }
7347 StyleCommand::RemoveStyle {
7348 start,
7349 end,
7350 style_id,
7351 } => {
7352 self.editor.interval_tree.remove(start, end, style_id);
7353 Ok(CommandResult::Success)
7354 }
7355 StyleCommand::Fold {
7356 start_line,
7357 end_line,
7358 } => {
7359 if start_line >= end_line {
7360 return Err(CommandError::InvalidRange {
7361 start: start_line,
7362 end: end_line,
7363 });
7364 }
7365
7366 let mut region = crate::intervals::FoldRegion::new(start_line, end_line);
7367 region.collapse();
7368 self.editor.folding_manager.add_region(region);
7369 Ok(CommandResult::Success)
7370 }
7371 StyleCommand::Unfold { start_line } => {
7372 self.editor.folding_manager.expand_line(start_line);
7373 Ok(CommandResult::Success)
7374 }
7375 StyleCommand::UnfoldAll => {
7376 self.editor.folding_manager.expand_all();
7377 Ok(CommandResult::Success)
7378 }
7379 }
7380 }
7381
7382 fn apply_text_change_to_line_index_and_layout(
7383 &mut self,
7384 start_offset: usize,
7385 deleted_text: &str,
7386 inserted_text: &str,
7387 ) {
7388 let start_line = self
7389 .editor
7390 .line_index
7391 .char_offset_to_position(start_offset)
7392 .0;
7393
7394 let deleted_chars = deleted_text.chars().count();
7395 if deleted_chars > 0 {
7396 self.editor.line_index.delete(start_offset, deleted_chars);
7397 }
7398 if !inserted_text.is_empty() {
7399 self.editor.line_index.insert(start_offset, inserted_text);
7400 }
7401
7402 let deleted_newlines = deleted_text
7403 .as_bytes()
7404 .iter()
7405 .filter(|b| **b == b'\n')
7406 .count();
7407 let inserted_newlines = inserted_text
7408 .as_bytes()
7409 .iter()
7410 .filter(|b| **b == b'\n')
7411 .count();
7412
7413 let line_delta = inserted_newlines as isize - deleted_newlines as isize;
7414 if line_delta > 0 {
7415 for i in 0..(line_delta as usize) {
7416 let line = start_line.saturating_add(1).saturating_add(i);
7417 let line_text = self
7418 .editor
7419 .line_index
7420 .get_line_text(line)
7421 .unwrap_or_default();
7422 self.editor.layout_engine.insert_line(line, &line_text);
7423 }
7424 } else if line_delta < 0 {
7425 for _ in 0..((-line_delta) as usize) {
7426 self.editor
7427 .layout_engine
7428 .delete_line(start_line.saturating_add(1));
7429 }
7430 }
7431
7432 let touch_lines = deleted_newlines.max(inserted_newlines).saturating_add(1);
7435 let end_line = start_line.saturating_add(touch_lines);
7436
7437 let line_count = self.editor.line_index.line_count();
7438 if line_count == 0 {
7439 return;
7440 }
7441 let last_line = line_count.saturating_sub(1);
7442 for line in start_line..=end_line.min(last_line) {
7443 let line_text = self
7444 .editor
7445 .line_index
7446 .get_line_text(line)
7447 .unwrap_or_default();
7448 self.editor.layout_engine.update_line(line, &line_text);
7449 }
7450 }
7451
7452 fn position_to_char_offset_clamped(&self, pos: Position) -> usize {
7453 let line_count = self.editor.line_index.line_count();
7454 if line_count == 0 {
7455 return 0;
7456 }
7457
7458 let line = pos.line.min(line_count.saturating_sub(1));
7459 let line_text = self
7460 .editor
7461 .line_index
7462 .get_line_text(line)
7463 .unwrap_or_default();
7464 let line_char_len = line_text.chars().count();
7465 let column = pos.column.min(line_char_len);
7466 self.editor.line_index.position_to_char_offset(line, column)
7467 }
7468
7469 fn position_to_char_offset_and_virtual_pad(&self, pos: Position) -> (usize, usize) {
7470 let line_count = self.editor.line_index.line_count();
7471 if line_count == 0 {
7472 return (0, 0);
7473 }
7474
7475 let line = pos.line.min(line_count.saturating_sub(1));
7476 let line_text = self
7477 .editor
7478 .line_index
7479 .get_line_text(line)
7480 .unwrap_or_default();
7481 let line_char_len = line_text.chars().count();
7482 let clamped_col = pos.column.min(line_char_len);
7483 let offset = self
7484 .editor
7485 .line_index
7486 .position_to_char_offset(line, clamped_col);
7487 let pad = pos.column.saturating_sub(clamped_col);
7488 (offset, pad)
7489 }
7490
7491 fn normalize_cursor_and_selection(&mut self) {
7492 let line_index = &self.editor.line_index;
7493 let line_count = line_index.line_count();
7494 if line_count == 0 {
7495 self.editor.cursor_position = Position::new(0, 0);
7496 self.editor.selection = None;
7497 self.editor.secondary_selections.clear();
7498 return;
7499 }
7500
7501 self.editor.cursor_position =
7502 Self::clamp_position_lenient_with_index(line_index, self.editor.cursor_position);
7503
7504 if let Some(ref mut selection) = self.editor.selection {
7505 selection.start = Self::clamp_position_lenient_with_index(line_index, selection.start);
7506 selection.end = Self::clamp_position_lenient_with_index(line_index, selection.end);
7507 selection.direction = if selection.start.line < selection.end.line
7508 || (selection.start.line == selection.end.line
7509 && selection.start.column <= selection.end.column)
7510 {
7511 SelectionDirection::Forward
7512 } else {
7513 SelectionDirection::Backward
7514 };
7515 }
7516
7517 for selection in &mut self.editor.secondary_selections {
7518 selection.start = Self::clamp_position_lenient_with_index(line_index, selection.start);
7519 selection.end = Self::clamp_position_lenient_with_index(line_index, selection.end);
7520 selection.direction = if selection.start.line < selection.end.line
7521 || (selection.start.line == selection.end.line
7522 && selection.start.column <= selection.end.column)
7523 {
7524 SelectionDirection::Forward
7525 } else {
7526 SelectionDirection::Backward
7527 };
7528 }
7529 }
7530
7531 fn clamp_column_for_line(&self, line: usize, column: usize) -> usize {
7532 Self::clamp_column_for_line_with_index(&self.editor.line_index, line, column)
7533 }
7534
7535 fn clamp_position_lenient_with_index(line_index: &LineIndex, pos: Position) -> Position {
7536 let line_count = line_index.line_count();
7537 if line_count == 0 {
7538 return Position::new(0, 0);
7539 }
7540
7541 let clamped_line = pos.line.min(line_count.saturating_sub(1));
7542 Position::new(clamped_line, pos.column)
7544 }
7545
7546 fn clamp_column_for_line_with_index(
7547 line_index: &LineIndex,
7548 line: usize,
7549 column: usize,
7550 ) -> usize {
7551 let line_start = line_index.position_to_char_offset(line, 0);
7552 let line_end = line_index.position_to_char_offset(line, usize::MAX);
7553 let line_len = line_end.saturating_sub(line_start);
7554 column.min(line_len)
7555 }
7556}
7557
7558#[cfg(test)]
7559mod tests {
7560 use super::*;
7561
7562 #[test]
7563 fn test_edit_insert() {
7564 let mut executor = CommandExecutor::new("Hello", 80);
7565
7566 let result = executor.execute(Command::Edit(EditCommand::Insert {
7567 offset: 5,
7568 text: " World".to_string(),
7569 }));
7570
7571 assert!(result.is_ok());
7572 assert_eq!(executor.editor().get_text(), "Hello World");
7573 }
7574
7575 #[test]
7576 fn test_edit_delete() {
7577 let mut executor = CommandExecutor::new("Hello World", 80);
7578
7579 let result = executor.execute(Command::Edit(EditCommand::Delete {
7580 start: 5,
7581 length: 6,
7582 }));
7583
7584 assert!(result.is_ok());
7585 assert_eq!(executor.editor().get_text(), "Hello");
7586 }
7587
7588 #[test]
7589 fn test_edit_replace() {
7590 let mut executor = CommandExecutor::new("Hello World", 80);
7591
7592 let result = executor.execute(Command::Edit(EditCommand::Replace {
7593 start: 6,
7594 length: 5,
7595 text: "Rust".to_string(),
7596 }));
7597
7598 assert!(result.is_ok());
7599 assert_eq!(executor.editor().get_text(), "Hello Rust");
7600 }
7601
7602 #[test]
7603 fn test_cursor_move_to() {
7604 let mut executor = CommandExecutor::new("Line 1\nLine 2\nLine 3", 80);
7605
7606 let result = executor.execute(Command::Cursor(CursorCommand::MoveTo {
7607 line: 1,
7608 column: 3,
7609 }));
7610
7611 assert!(result.is_ok());
7612 assert_eq!(executor.editor().cursor_position(), Position::new(1, 3));
7613 }
7614
7615 #[test]
7616 fn test_cursor_selection() {
7617 let mut executor = CommandExecutor::new("Hello World", 80);
7618
7619 let result = executor.execute(Command::Cursor(CursorCommand::SetSelection {
7620 start: Position::new(0, 0),
7621 end: Position::new(0, 5),
7622 }));
7623
7624 assert!(result.is_ok());
7625 assert!(executor.editor().selection().is_some());
7626 }
7627
7628 #[test]
7629 fn test_view_set_width() {
7630 let mut executor = CommandExecutor::new("Test", 80);
7631
7632 let result = executor.execute(Command::View(ViewCommand::SetViewportWidth { width: 40 }));
7633
7634 assert!(result.is_ok());
7635 assert_eq!(executor.editor().viewport_width, 40);
7636 }
7637
7638 #[test]
7639 fn test_style_add_remove() {
7640 let mut executor = CommandExecutor::new("Hello World", 80);
7641
7642 let result = executor.execute(Command::Style(StyleCommand::AddStyle {
7644 start: 0,
7645 end: 5,
7646 style_id: 1,
7647 }));
7648 assert!(result.is_ok());
7649
7650 let result = executor.execute(Command::Style(StyleCommand::RemoveStyle {
7652 start: 0,
7653 end: 5,
7654 style_id: 1,
7655 }));
7656 assert!(result.is_ok());
7657 }
7658
7659 #[test]
7660 fn test_batch_execution() {
7661 let mut executor = CommandExecutor::new("", 80);
7662
7663 let commands = vec![
7664 Command::Edit(EditCommand::Insert {
7665 offset: 0,
7666 text: "Hello".to_string(),
7667 }),
7668 Command::Edit(EditCommand::Insert {
7669 offset: 5,
7670 text: " World".to_string(),
7671 }),
7672 ];
7673
7674 let results = executor.execute_batch(commands);
7675 assert!(results.is_ok());
7676 assert_eq!(executor.editor().get_text(), "Hello World");
7677 }
7678
7679 #[test]
7680 fn test_error_invalid_offset() {
7681 let mut executor = CommandExecutor::new("Hello", 80);
7682
7683 let result = executor.execute(Command::Edit(EditCommand::Insert {
7684 offset: 100,
7685 text: "X".to_string(),
7686 }));
7687
7688 assert!(result.is_err());
7689 assert!(matches!(
7690 result.unwrap_err(),
7691 CommandError::InvalidOffset(_)
7692 ));
7693 }
7694}