1use crate::decorations::{Decoration, DecorationLayerId, DecorationPlacement};
37use crate::delta::{TextDelta, TextDeltaEdit};
38use crate::diagnostics::Diagnostic;
39use crate::intervals::{FoldRegion, IntervalTextEdit, StyleId, StyleLayerId};
40use crate::layout::{
41 cell_width_at, char_width, visual_x_for_column, wrap_indent_cells_for_line_text,
42};
43use crate::line_ending::LineEnding;
44use crate::search::{CharIndex, SearchMatch, SearchOptions, find_all, find_next, find_prev};
45use crate::snapshot::{
46 Cell, ComposedCell, ComposedCellSource, ComposedGrid, ComposedLine, ComposedLineKind,
47 HeadlessGrid, HeadlessLine, MinimapGrid, MinimapLine,
48};
49use crate::snippets::{SnippetNavigation, SnippetSession, parse_snippet};
50#[cfg(debug_assertions)]
51use crate::storage::PieceTable;
52use crate::visual_rows::VisualRowIndex;
53use crate::{FOLD_PLACEHOLDER_STYLE_ID, FoldingManager, IntervalTree, LayoutEngine, LineIndex};
54use editor_core_lang::{CommentConfig, IndentStyle, IndentationConfig};
55use regex::RegexBuilder;
56use std::cell::RefCell;
57use std::collections::{BTreeMap, HashMap};
58use std::time::Duration;
59use unicode_segmentation::UnicodeSegmentation;
60
61const DEFAULT_COMMAND_HISTORY_LIMIT: usize = 1000;
62#[path = "model.rs"]
63mod model;
64pub use self::model::{
65 AutoPair, AutoPairsConfig, Command, CommandError, CommandResult, CursorCommand, EditCommand,
66 ExpandSelectionDirection, ExpandSelectionUnit, Position, Selection, SelectionDirection,
67 StyleCommand, TabKeyBehavior, TextEditSpec, ViewCommand,
68};
69
70#[path = "undo.rs"]
71mod undo;
72use self::undo::{TextEdit, UndoRedoManager, UndoStep};
73pub use self::undo::{
74 UndoHistoryRestoreError, UndoHistorySelectionSet, UndoHistorySnapshot, UndoHistoryStep,
75 UndoHistoryTextEdit,
76};
77
78#[path = "render_grid.rs"]
79mod render_grid;
80
81#[path = "cursor_ops.rs"]
82mod cursor_ops;
83pub use self::cursor_ops::WordBoundaryConfig;
84use self::cursor_ops::{
85 TextBoundary, leading_horizontal_whitespace, next_boundary_column, prev_boundary_column,
86};
87
88#[path = "line_ops.rs"]
89mod line_ops;
90
91#[path = "edit_ops.rs"]
92mod edit_ops;
93
94#[derive(Debug, Clone, PartialEq, Eq)]
95struct SelectionSetSnapshot {
96 selections: Vec<Selection>,
97 primary_index: usize,
98}
99
100pub struct EditorCore {
120 #[cfg(debug_assertions)]
122 piece_table_shadow: PieceTable,
123 line_index: LineIndex,
125 layout_engine: LayoutEngine,
127 interval_tree: IntervalTree,
129 style_layers: BTreeMap<StyleLayerId, IntervalTree>,
131 diagnostics: Vec<Diagnostic>,
133 decorations: BTreeMap<DecorationLayerId, Vec<Decoration>>,
135 document_symbols: crate::DocumentOutline,
137 folding_manager: FoldingManager,
139 cursor_position: Position,
141 selection: Option<Selection>,
143 secondary_selections: Vec<Selection>,
145 viewport_width: usize,
147 word_boundary: WordBoundaryConfig,
148 visual_row_index_cache: RefCell<Option<VisualRowIndex>>,
149}
150
151impl EditorCore {
152 pub fn new(text: &str, viewport_width: usize) -> Self {
154 let normalized = crate::text::normalize_crlf_to_lf(text);
155 let text = normalized.as_ref();
156
157 #[cfg(debug_assertions)]
158 let piece_table_shadow = PieceTable::new(text);
159 let line_index = LineIndex::from_text(text);
160 let mut layout_engine = LayoutEngine::new(viewport_width);
161
162 let lines = crate::text::split_lines_preserve_trailing(text);
164 let line_refs: Vec<&str> = lines.iter().map(|s| s.as_str()).collect();
165 layout_engine.from_lines(&line_refs);
166
167 Self {
168 #[cfg(debug_assertions)]
169 piece_table_shadow,
170 line_index,
171 layout_engine,
172 interval_tree: IntervalTree::new(),
173 style_layers: BTreeMap::new(),
174 diagnostics: Vec::new(),
175 decorations: BTreeMap::new(),
176 document_symbols: crate::DocumentOutline::default(),
177 folding_manager: FoldingManager::new(),
178 cursor_position: Position::new(0, 0),
179 selection: None,
180 secondary_selections: Vec::new(),
181 viewport_width,
182 word_boundary: WordBoundaryConfig::default(),
183 visual_row_index_cache: RefCell::new(None),
184 }
185 }
186
187 pub fn empty(viewport_width: usize) -> Self {
189 Self::new("", viewport_width)
190 }
191
192 pub fn get_text(&self) -> String {
194 self.line_index.text_buffer().get_text()
195 }
196
197 pub fn text_range(&self, start: usize, len: usize) -> String {
199 self.line_index.text_buffer().get_range(start, len)
200 }
201
202 pub fn line_count(&self) -> usize {
204 self.line_index.line_count()
205 }
206
207 pub fn char_count(&self) -> usize {
209 self.line_index.text_buffer().len_chars()
210 }
211
212 pub fn set_word_boundary_ascii_boundary_chars(&mut self, boundary_chars: &str) {
216 self.word_boundary.set_ascii_boundary_chars(boundary_chars);
217 }
218
219 pub fn reset_word_boundary_defaults(&mut self) {
221 self.word_boundary = WordBoundaryConfig::default();
222 }
223
224 pub fn cursor_position(&self) -> Position {
226 self.cursor_position
227 }
228
229 pub fn selection(&self) -> Option<&Selection> {
231 self.selection.as_ref()
232 }
233
234 pub fn secondary_selections(&self) -> &[Selection] {
236 &self.secondary_selections
237 }
238
239 pub(crate) fn set_cursor_state(
241 &mut self,
242 cursor_position: Position,
243 selection: Option<Selection>,
244 secondary_selections: Vec<Selection>,
245 ) {
246 self.cursor_position = cursor_position;
247 self.selection = selection;
248 self.secondary_selections = secondary_selections;
249 }
250
251 pub fn line_index(&self) -> &LineIndex {
253 &self.line_index
254 }
255
256 pub fn layout_engine(&self) -> &LayoutEngine {
258 &self.layout_engine
259 }
260
261 pub fn interval_tree(&self) -> &IntervalTree {
263 &self.interval_tree
264 }
265
266 pub fn style_layers(&self) -> &BTreeMap<StyleLayerId, IntervalTree> {
268 &self.style_layers
269 }
270
271 pub fn style_layer(&self, layer: StyleLayerId) -> Option<&IntervalTree> {
273 self.style_layers.get(&layer)
274 }
275
276 pub fn diagnostics(&self) -> &[Diagnostic] {
278 &self.diagnostics
279 }
280
281 pub fn decorations(&self) -> &BTreeMap<DecorationLayerId, Vec<Decoration>> {
283 &self.decorations
284 }
285
286 pub fn decorations_for_layer(&self, layer: DecorationLayerId) -> &[Decoration] {
288 self.decorations
289 .get(&layer)
290 .map(Vec::as_slice)
291 .unwrap_or(&[])
292 }
293
294 pub fn document_symbols(&self) -> &crate::DocumentOutline {
296 &self.document_symbols
297 }
298
299 pub fn folding_manager(&self) -> &FoldingManager {
301 &self.folding_manager
302 }
303
304 pub fn viewport_width(&self) -> usize {
306 self.viewport_width
307 }
308
309 pub(crate) fn set_view_options(
311 &mut self,
312 viewport_width: usize,
313 wrap_mode: crate::WrapMode,
314 wrap_indent: crate::WrapIndent,
315 tab_width: usize,
316 ) {
317 let viewport_width = viewport_width.max(1);
318 let tab_width = tab_width.max(1);
319
320 let changed = self.viewport_width != viewport_width
321 || self.layout_engine.viewport_width() != viewport_width
322 || self.layout_engine.wrap_mode() != wrap_mode
323 || self.layout_engine.wrap_indent() != wrap_indent
324 || self.layout_engine.tab_width() != tab_width;
325
326 self.viewport_width = viewport_width;
327 self.layout_engine.set_viewport_width(viewport_width);
328 self.layout_engine.set_wrap_mode(wrap_mode);
329 self.layout_engine.set_wrap_indent(wrap_indent);
330 self.layout_engine.set_tab_width(tab_width);
331
332 if changed {
333 self.reflow_layout_from_line_index();
334 }
335 }
336
337 pub(crate) fn insert_style_interval(&mut self, interval: crate::intervals::Interval) {
339 self.interval_tree.insert(interval);
340 }
341
342 pub(crate) fn remove_style_interval(&mut self, start: usize, end: usize, style_id: StyleId) {
344 self.interval_tree.remove(start, end, style_id);
345 }
346
347 pub(crate) fn replace_style_layer(
349 &mut self,
350 layer: StyleLayerId,
351 intervals: Vec<crate::intervals::Interval>,
352 ) {
353 if intervals.is_empty() {
354 self.style_layers.remove(&layer);
355 return;
356 }
357
358 let tree = self.style_layers.entry(layer).or_default();
359 tree.clear();
360 for interval in intervals {
361 if interval.start < interval.end {
362 tree.insert(interval);
363 }
364 }
365 }
366
367 pub(crate) fn clear_style_layer(&mut self, layer: StyleLayerId) {
369 self.style_layers.remove(&layer);
370 }
371
372 pub(crate) fn replace_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) {
374 self.diagnostics = diagnostics;
375 }
376
377 pub(crate) fn clear_diagnostics(&mut self) {
379 self.diagnostics.clear();
380 }
381
382 pub(crate) fn replace_decorations(
384 &mut self,
385 layer: DecorationLayerId,
386 mut decorations: Vec<Decoration>,
387 ) {
388 decorations.sort_unstable_by_key(|d| (d.range.start, d.range.end));
389 self.decorations.insert(layer, decorations);
390 }
391
392 pub(crate) fn clear_decorations(&mut self, layer: DecorationLayerId) {
394 self.decorations.remove(&layer);
395 }
396
397 pub(crate) fn replace_document_symbols(&mut self, symbols: crate::DocumentOutline) {
399 self.document_symbols = symbols;
400 }
401
402 pub(crate) fn clear_document_symbols(&mut self) {
404 self.document_symbols = crate::DocumentOutline::default();
405 }
406
407 pub(crate) fn replace_folding_regions(
409 &mut self,
410 regions: Vec<FoldRegion>,
411 preserve_collapsed: bool,
412 ) {
413 if preserve_collapsed {
414 self.folding_manager
415 .replace_derived_regions_preserving_collapsed(regions);
416 } else {
417 self.folding_manager.replace_derived_regions(regions);
418 }
419 self.invalidate_visual_row_index_cache();
420 }
421
422 pub(crate) fn clear_derived_folding_regions(&mut self) {
424 self.folding_manager.clear_derived_regions();
425 self.invalidate_visual_row_index_cache();
426 }
427
428 pub(crate) fn toggle_fold_at_line(&mut self, line: usize) -> bool {
430 let affected = self
431 .folding_manager
432 .regions()
433 .iter()
434 .filter(|region| region.start_line == line && region.end_line > region.start_line)
435 .min_by_key(|region| region.end_line)
436 .map(|region| (region.start_line, region.end_line));
437 let toggled = self.folding_manager.toggle_region_starting_at_line(line);
438 if toggled {
439 if let Some((start, end)) = affected {
440 self.sync_visual_row_index_for_logical_range(start, end);
441 } else {
442 self.invalidate_visual_row_index_cache();
443 }
444 }
445 toggled
446 }
447
448 pub(crate) fn expand_all_folds(&mut self) {
450 let had_collapsed = self
451 .folding_manager
452 .regions()
453 .iter()
454 .any(|region| region.is_collapsed);
455 self.folding_manager.expand_all();
456 if had_collapsed {
457 self.invalidate_visual_row_index_cache();
458 }
459 }
460
461 pub fn invalidate_visual_row_index_cache(&mut self) {
463 *self.visual_row_index_cache.borrow_mut() = None;
464 }
465
466 fn visual_row_count_for_logical_line(&self, logical_line: usize) -> usize {
467 if logical_line >= self.layout_engine.logical_line_count() {
468 return 0;
469 }
470 if Self::is_logical_line_hidden(self.folding_manager.regions(), logical_line) {
471 return 0;
472 }
473
474 self.layout_engine
475 .get_line_layout(logical_line)
476 .map(|layout| layout.visual_line_count)
477 .unwrap_or(1)
478 .max(1)
479 }
480
481 fn sync_visual_row_index_for_logical_range(&mut self, start_line: usize, end_line: usize) {
482 if self.visual_row_index_cache.borrow().is_none() {
483 return;
484 }
485
486 let line_count = self.layout_engine.logical_line_count();
487 if line_count == 0 || start_line >= line_count {
488 return;
489 }
490
491 let end_line = end_line.min(line_count.saturating_sub(1));
492 let counts = (start_line..=end_line)
493 .map(|line| (line, self.visual_row_count_for_logical_line(line)))
494 .collect::<Vec<_>>();
495
496 let mut cache = self.visual_row_index_cache.borrow_mut();
497 let Some(index) = cache.as_mut() else {
498 return;
499 };
500
501 if index.logical_line_count() != line_count {
502 *cache = None;
503 return;
504 }
505
506 for (line, count) in counts {
507 if !index.set_line_visual_count(line, count) {
508 *cache = None;
509 return;
510 }
511 }
512 }
513
514 fn sync_visual_row_index_after_text_change(
515 &mut self,
516 start_line: usize,
517 deleted_newlines: usize,
518 inserted_newlines: usize,
519 ) {
520 if self.visual_row_index_cache.borrow().is_none() {
521 return;
522 }
523
524 let line_delta = inserted_newlines as isize - deleted_newlines as isize;
525 if line_delta != 0 {
526 let line_count = self.layout_engine.logical_line_count();
527 let mut cache = self.visual_row_index_cache.borrow_mut();
528 let Some(index) = cache.as_mut() else {
529 return;
530 };
531
532 if line_delta > 0 {
533 let inserted = line_delta as usize;
534 if index.logical_line_count().saturating_add(inserted) != line_count {
535 *cache = None;
536 return;
537 }
538 index.insert_lines(
539 start_line.saturating_add(1),
540 std::iter::repeat_n(0, inserted),
541 );
542 } else {
543 let removed = (-line_delta) as usize;
544 if index.logical_line_count().saturating_sub(removed) != line_count
545 || !index.remove_lines(start_line.saturating_add(1), removed)
546 {
547 *cache = None;
548 return;
549 }
550 }
551 }
552
553 let touch_lines = deleted_newlines.max(inserted_newlines).saturating_add(1);
554 self.sync_visual_row_index_for_logical_range(
555 start_line,
556 start_line.saturating_add(touch_lines),
557 );
558 }
559
560 pub(crate) fn reflow_layout_from_line_index(&mut self) {
562 let lines: Vec<String> = (0..self.line_index.line_count())
563 .map(|line| self.line_index.get_line_text(line).unwrap_or_default())
564 .collect();
565 self.layout_engine
566 .recalculate_all_from_lines(lines.iter().map(String::as_str));
567 self.invalidate_visual_row_index_cache();
568 }
569
570 fn with_visual_row_index<R>(&self, f: impl FnOnce(&VisualRowIndex) -> R) -> R {
571 if self.visual_row_index_cache.borrow().is_none() {
572 let index = self.build_visual_row_index();
573 *self.visual_row_index_cache.borrow_mut() = Some(index);
574 }
575 let cache = self.visual_row_index_cache.borrow();
576 let index = cache
577 .as_ref()
578 .expect("visual-row cache should be initialized");
579 f(index)
580 }
581
582 fn build_visual_row_index(&self) -> VisualRowIndex {
583 let counts = (0..self.layout_engine.logical_line_count())
584 .map(|logical_line| self.visual_row_count_for_logical_line(logical_line))
585 .collect();
586 VisualRowIndex::from_line_visual_counts(counts)
587 }
588
589 pub fn visual_line_count(&self) -> usize {
591 self.with_visual_row_index(|index| index.total_visual_lines())
592 }
593
594 pub fn visual_to_logical_line(&self, visual_line: usize) -> (usize, usize) {
596 self.with_visual_row_index(|index| {
597 if index.total_visual_lines() == 0 {
598 return (0, 0);
599 }
600 let clamped_visual = visual_line.min(index.total_visual_lines().saturating_sub(1));
601 index
602 .span_for_visual_row(clamped_visual)
603 .map(|(span, visual_in_logical)| (span.logical_line, visual_in_logical))
604 .unwrap_or((0, 0))
605 })
606 }
607
608 pub fn logical_position_to_visual(
610 &self,
611 logical_line: usize,
612 column: usize,
613 ) -> Option<(usize, usize)> {
614 let regions = self.folding_manager.regions();
615 let logical_line = Self::closest_visible_line(regions, logical_line)?;
616 let visual_start = self.visual_start_for_logical_line(logical_line)?;
617
618 let tab_width = self.layout_engine.tab_width();
619
620 let layout = self.layout_engine.get_line_layout(logical_line)?;
621 let line_text = self
622 .line_index
623 .get_line_text(logical_line)
624 .unwrap_or_default();
625
626 let line_char_len = line_text.chars().count();
627 let column = column.min(line_char_len);
628
629 let mut wrapped_offset = 0usize;
630 let mut segment_start_col = 0usize;
631 for wrap_point in &layout.wrap_points {
632 if column >= wrap_point.char_index {
633 wrapped_offset = wrapped_offset.saturating_add(1);
634 segment_start_col = wrap_point.char_index;
635 } else {
636 break;
637 }
638 }
639
640 let seg_start_x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
641 let mut x_in_line = seg_start_x_in_line;
642 let mut x_in_segment = 0usize;
643 for ch in line_text
644 .chars()
645 .skip(segment_start_col)
646 .take(column.saturating_sub(segment_start_col))
647 {
648 let w = cell_width_at(ch, x_in_line, tab_width);
649 x_in_line = x_in_line.saturating_add(w);
650 x_in_segment = x_in_segment.saturating_add(w);
651 }
652
653 let indent = if wrapped_offset == 0 {
654 0
655 } else {
656 wrap_indent_cells_for_line_text(
657 &line_text,
658 self.layout_engine.wrap_indent(),
659 self.viewport_width,
660 tab_width,
661 )
662 };
663
664 Some((
665 visual_start.saturating_add(wrapped_offset),
666 indent.saturating_add(x_in_segment),
667 ))
668 }
669
670 pub fn logical_position_to_visual_allow_virtual(
675 &self,
676 logical_line: usize,
677 column: usize,
678 ) -> Option<(usize, usize)> {
679 let regions = self.folding_manager.regions();
680 let logical_line = Self::closest_visible_line(regions, logical_line)?;
681 let visual_start = self.visual_start_for_logical_line(logical_line)?;
682
683 let tab_width = self.layout_engine.tab_width();
684
685 let layout = self.layout_engine.get_line_layout(logical_line)?;
686 let line_text = self
687 .line_index
688 .get_line_text(logical_line)
689 .unwrap_or_default();
690
691 let line_char_len = line_text.chars().count();
692 let clamped_column = column.min(line_char_len);
693
694 let mut wrapped_offset = 0usize;
695 let mut segment_start_col = 0usize;
696 for wrap_point in &layout.wrap_points {
697 if clamped_column >= wrap_point.char_index {
698 wrapped_offset = wrapped_offset.saturating_add(1);
699 segment_start_col = wrap_point.char_index;
700 } else {
701 break;
702 }
703 }
704
705 let seg_start_x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
706 let mut x_in_line = seg_start_x_in_line;
707 let mut x_in_segment = 0usize;
708 for ch in line_text
709 .chars()
710 .skip(segment_start_col)
711 .take(clamped_column.saturating_sub(segment_start_col))
712 {
713 let w = cell_width_at(ch, x_in_line, tab_width);
714 x_in_line = x_in_line.saturating_add(w);
715 x_in_segment = x_in_segment.saturating_add(w);
716 }
717
718 let x_in_segment = x_in_segment + column.saturating_sub(line_char_len);
719
720 let indent = if wrapped_offset == 0 {
721 0
722 } else {
723 wrap_indent_cells_for_line_text(
724 &line_text,
725 self.layout_engine.wrap_indent(),
726 self.viewport_width,
727 tab_width,
728 )
729 };
730
731 Some((
732 visual_start.saturating_add(wrapped_offset),
733 indent.saturating_add(x_in_segment),
734 ))
735 }
736
737 pub fn visual_position_to_logical(
744 &self,
745 visual_row: usize,
746 x_in_cells: usize,
747 ) -> Option<Position> {
748 let total_visual = self.visual_line_count();
749 if total_visual == 0 {
750 return Some(Position::new(0, 0));
751 }
752
753 let clamped_row = visual_row.min(total_visual.saturating_sub(1));
754 let (logical_line, visual_in_logical) = self.visual_to_logical_line(clamped_row);
755
756 let layout = self.layout_engine.get_line_layout(logical_line)?;
757 let line_text = self
758 .line_index
759 .get_line_text(logical_line)
760 .unwrap_or_default();
761 let line_char_len = line_text.chars().count();
762
763 let segment_start_col = if visual_in_logical == 0 {
764 0
765 } else {
766 layout
767 .wrap_points
768 .get(visual_in_logical - 1)
769 .map(|wp| wp.char_index)
770 .unwrap_or(0)
771 };
772
773 let segment_end_col = layout
774 .wrap_points
775 .get(visual_in_logical)
776 .map(|wp| wp.char_index)
777 .unwrap_or(line_char_len)
778 .max(segment_start_col)
779 .min(line_char_len);
780
781 let tab_width = self.layout_engine.tab_width();
782 let x_in_cells = if visual_in_logical == 0 {
783 x_in_cells
784 } else {
785 let indent = wrap_indent_cells_for_line_text(
786 &line_text,
787 self.layout_engine.wrap_indent(),
788 self.viewport_width,
789 tab_width,
790 );
791 x_in_cells.saturating_sub(indent)
792 };
793 let seg_start_x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
794 let mut x_in_line = seg_start_x_in_line;
795 let mut x_in_segment = 0usize;
796 let mut column = segment_start_col;
797
798 for (char_idx, ch) in line_text.chars().enumerate().skip(segment_start_col) {
799 if char_idx >= segment_end_col {
800 break;
801 }
802
803 let w = cell_width_at(ch, x_in_line, tab_width);
804 if x_in_segment.saturating_add(w) > x_in_cells {
805 break;
806 }
807
808 x_in_line = x_in_line.saturating_add(w);
809 x_in_segment = x_in_segment.saturating_add(w);
810 column = column.saturating_add(1);
811 }
812
813 Some(Position::new(logical_line, column))
814 }
815
816 fn visual_start_for_logical_line(&self, logical_line: usize) -> Option<usize> {
817 if logical_line >= self.layout_engine.logical_line_count() {
818 return None;
819 }
820 self.with_visual_row_index(|index| {
821 index
822 .span_for_logical_line(logical_line)
823 .map(|span| span.start_visual_row)
824 })
825 }
826
827 fn is_logical_line_hidden(regions: &[FoldRegion], logical_line: usize) -> bool {
828 regions.iter().any(|region| {
829 region.is_collapsed
830 && logical_line > region.start_line
831 && logical_line <= region.end_line
832 })
833 }
834
835 fn collapsed_region_starting_at(
836 regions: &[FoldRegion],
837 start_line: usize,
838 ) -> Option<&FoldRegion> {
839 regions
840 .iter()
841 .filter(|region| {
842 region.is_collapsed
843 && region.start_line == start_line
844 && region.end_line > start_line
845 })
846 .min_by_key(|region| region.end_line)
847 }
848
849 fn closest_visible_line(regions: &[FoldRegion], logical_line: usize) -> Option<usize> {
850 let mut line = logical_line;
851 if regions.is_empty() {
852 return Some(line);
853 }
854
855 while Self::is_logical_line_hidden(regions, line) {
856 let Some(start) = regions
857 .iter()
858 .filter(|region| {
859 region.is_collapsed && line > region.start_line && line <= region.end_line
860 })
861 .map(|region| region.start_line)
862 .max()
863 else {
864 break;
865 };
866 line = start;
867 }
868
869 if Self::is_logical_line_hidden(regions, line) {
870 None
871 } else {
872 Some(line)
873 }
874 }
875
876 fn fold_right_boundary_bracket_char(&self, region: &FoldRegion) -> Option<char> {
877 let end_line_text = self.line_index.get_line_text(region.end_line)?;
878
879 if let Some(ch) = end_line_text.chars().find(|c| !c.is_whitespace())
881 && matches!(ch, '}' | ')' | ']')
882 {
883 return Some(ch);
884 }
885
886 for ch in end_line_text.chars().rev() {
888 if ch.is_whitespace() {
889 continue;
890 }
891 if matches!(ch, '}' | ')' | ']') {
892 return Some(ch);
893 }
894 if matches!(ch, ';' | ',') {
895 continue;
896 }
897 break;
898 }
899
900 None
901 }
902
903 fn styles_at_offset(&self, offset: usize) -> Vec<StyleId> {
904 let mut styles: Vec<StyleId> = self
905 .interval_tree
906 .query_point(offset)
907 .iter()
908 .map(|interval| interval.style_id)
909 .collect();
910
911 for tree in self.style_layers.values() {
912 styles.extend(
913 tree.query_point(offset)
914 .iter()
915 .map(|interval| interval.style_id),
916 );
917 }
918
919 styles.sort_unstable();
920 styles.dedup();
921 styles
922 }
923}
924
925pub struct CommandExecutor {
963 editor: EditorCore,
965 command_history: Vec<Command>,
967 command_history_limit: usize,
969 undo_redo: UndoRedoManager,
971 tab_key_behavior: TabKeyBehavior,
973 indentation_config: IndentationConfig,
975 auto_pairs: AutoPairsConfig,
977 snippet_session: Option<SnippetSession>,
979 line_ending: LineEnding,
981 preferred_x_cells: Option<usize>,
983 last_text_delta: Option<TextDelta>,
985}
986
987impl CommandExecutor {
988 pub fn new(text: &str, viewport_width: usize) -> Self {
990 Self {
991 editor: EditorCore::new(text, viewport_width),
992 command_history: Vec::with_capacity(DEFAULT_COMMAND_HISTORY_LIMIT),
993 command_history_limit: DEFAULT_COMMAND_HISTORY_LIMIT,
994 undo_redo: UndoRedoManager::new(1000),
995 tab_key_behavior: TabKeyBehavior::Spaces,
996 indentation_config: IndentationConfig::default(),
997 auto_pairs: AutoPairsConfig::default(),
998 snippet_session: None,
999 line_ending: LineEnding::detect_in_text(text),
1000 preferred_x_cells: None,
1001 last_text_delta: None,
1002 }
1003 }
1004
1005 pub fn empty(viewport_width: usize) -> Self {
1007 Self::new("", viewport_width)
1008 }
1009
1010 fn update_interval_trees_for_text_edits(&mut self, edits: &[IntervalTextEdit]) {
1011 if edits.is_empty() {
1012 return;
1013 }
1014
1015 self.editor.interval_tree.update_for_text_edits(edits);
1016 for layer_tree in self.editor.style_layers.values_mut() {
1017 layer_tree.update_for_text_edits(edits);
1018 }
1019 }
1020
1021 fn record_command_history(&mut self, command: &Command) {
1022 if self.command_history_limit == 0 {
1023 return;
1024 }
1025
1026 self.command_history.push(command.history_summary());
1027 self.trim_command_history_to_limit();
1028 }
1029
1030 fn trim_command_history_to_limit(&mut self) {
1031 if self.command_history_limit == 0 {
1032 self.command_history.clear();
1033 return;
1034 }
1035
1036 let excess = self
1037 .command_history
1038 .len()
1039 .saturating_sub(self.command_history_limit);
1040 if excess > 0 {
1041 self.command_history.drain(..excess);
1042 }
1043 }
1044
1045 pub fn execute(&mut self, command: Command) -> Result<CommandResult, CommandError> {
1047 self.last_text_delta = None;
1048
1049 if matches!(
1053 &command,
1054 Command::Cursor(
1055 CursorCommand::SnippetNextPlaceholder | CursorCommand::SnippetPrevPlaceholder
1056 )
1057 ) {
1058 } else if matches!(&command, Command::Cursor(_))
1060 || matches!(
1061 &command,
1062 Command::Edit(
1063 EditCommand::Undo | EditCommand::Redo | EditCommand::ApplyTextEdits { .. }
1064 )
1065 )
1066 {
1067 self.snippet_session = None;
1068 }
1069
1070 self.record_command_history(&command);
1072
1073 let skip_snippet_delta =
1074 matches!(&command, Command::Edit(EditCommand::ApplySnippet { .. }));
1075
1076 if matches!(
1086 command,
1087 Command::Cursor(_) | Command::Edit(EditCommand::Undo | EditCommand::Redo)
1088 ) {
1089 self.undo_redo.end_group();
1090 }
1091
1092 let result = match command {
1094 Command::Edit(edit_cmd) => self.execute_edit(edit_cmd),
1095 Command::Cursor(cursor_cmd) => self.execute_cursor(cursor_cmd),
1096 Command::View(view_cmd) => self.execute_view(view_cmd),
1097 Command::Style(style_cmd) => self.execute_style(style_cmd),
1098 }?;
1099
1100 if !skip_snippet_delta
1105 && let (Some(delta), Some(session)) =
1106 (self.last_text_delta.as_ref(), self.snippet_session.as_mut())
1107 {
1108 session.apply_delta(delta);
1109 }
1110
1111 Ok(result)
1112 }
1113
1114 pub fn last_text_delta(&self) -> Option<&TextDelta> {
1116 self.last_text_delta.as_ref()
1117 }
1118
1119 pub fn take_last_text_delta(&mut self) -> Option<TextDelta> {
1121 self.last_text_delta.take()
1122 }
1123
1124 pub fn execute_batch(
1126 &mut self,
1127 commands: Vec<Command>,
1128 ) -> Result<Vec<CommandResult>, CommandError> {
1129 let mut results = Vec::new();
1130
1131 for command in commands {
1132 let result = self.execute(command)?;
1133 results.push(result);
1134 }
1135
1136 Ok(results)
1137 }
1138
1139 pub fn get_command_history(&self) -> &[Command] {
1144 &self.command_history
1145 }
1146
1147 pub fn command_history_limit(&self) -> usize {
1149 self.command_history_limit
1150 }
1151
1152 pub fn set_command_history_limit(&mut self, limit: usize) {
1154 self.command_history_limit = limit;
1155 self.trim_command_history_to_limit();
1156 }
1157
1158 pub fn can_undo(&self) -> bool {
1160 self.undo_redo.can_undo()
1161 }
1162
1163 pub fn can_redo(&self) -> bool {
1165 self.undo_redo.can_redo()
1166 }
1167
1168 pub fn undo_depth(&self) -> usize {
1170 self.undo_redo.undo_depth()
1171 }
1172
1173 pub fn redo_depth(&self) -> usize {
1175 self.undo_redo.redo_depth()
1176 }
1177
1178 pub fn redo_branch_count(&self) -> usize {
1184 self.undo_redo.redo_branch_count()
1185 }
1186
1187 pub fn selected_redo_branch_index(&self) -> Option<usize> {
1189 self.undo_redo.selected_redo_branch_index()
1190 }
1191
1192 pub fn select_redo_branch(&mut self, index: usize) -> Result<(), CommandError> {
1194 self.undo_redo.end_group();
1195 self.undo_redo.select_redo_branch(index)
1196 }
1197
1198 pub fn current_change_group(&self) -> Option<usize> {
1200 self.undo_redo.current_group_id()
1201 }
1202
1203 pub fn undo_coalescing_timeout(&self) -> Duration {
1205 self.undo_redo.coalescing_timeout()
1206 }
1207
1208 pub fn set_undo_coalescing_timeout(&mut self, timeout: Duration) {
1210 self.undo_redo.set_coalescing_timeout(timeout);
1211 }
1212
1213 pub fn is_clean(&self) -> bool {
1215 self.undo_redo.is_clean()
1216 }
1217
1218 pub fn mark_clean(&mut self) {
1220 self.undo_redo.mark_clean();
1221 }
1222
1223 pub fn undo_history_snapshot(&self) -> UndoHistorySnapshot {
1227 self.undo_redo.snapshot()
1228 }
1229
1230 pub fn restore_undo_history(
1236 &mut self,
1237 snapshot: UndoHistorySnapshot,
1238 ) -> Result<(), UndoHistoryRestoreError> {
1239 self.last_text_delta = None;
1240 self.undo_redo.restore_from_snapshot(snapshot)
1241 }
1242
1243 pub fn editor(&self) -> &EditorCore {
1245 &self.editor
1246 }
1247
1248 pub fn editor_mut(&mut self) -> &mut EditorCore {
1255 &mut self.editor
1256 }
1257
1258 pub fn tab_key_behavior(&self) -> TabKeyBehavior {
1260 self.tab_key_behavior
1261 }
1262
1263 pub fn set_tab_key_behavior(&mut self, behavior: TabKeyBehavior) {
1265 self.tab_key_behavior = behavior;
1266 }
1267
1268 pub fn indentation_config(&self) -> &IndentationConfig {
1271 &self.indentation_config
1272 }
1273
1274 pub fn set_indentation_config(&mut self, config: IndentationConfig) {
1277 self.indentation_config = config;
1278 }
1279
1280 pub fn auto_pairs_config(&self) -> &AutoPairsConfig {
1282 &self.auto_pairs
1283 }
1284
1285 pub fn set_auto_pairs_config(&mut self, config: AutoPairsConfig) {
1287 self.auto_pairs = config;
1288 }
1289
1290 pub fn set_auto_pairs_enabled(&mut self, enabled: bool) {
1292 self.auto_pairs.enabled = enabled;
1293 }
1294
1295 pub fn has_active_snippet_session(&self) -> bool {
1297 self.snippet_session
1298 .as_ref()
1299 .map(|s| s.is_active())
1300 .unwrap_or(false)
1301 }
1302
1303 pub fn snippet_session(&self) -> Option<&SnippetSession> {
1305 self.snippet_session.as_ref()
1306 }
1307
1308 pub fn set_snippet_session(&mut self, session: Option<SnippetSession>) {
1310 self.snippet_session = session;
1311 }
1312
1313 pub fn preferred_x_cells(&self) -> Option<usize> {
1315 self.preferred_x_cells
1316 }
1317
1318 pub fn set_preferred_x_cells(&mut self, preferred_x_cells: Option<usize>) {
1320 self.preferred_x_cells = preferred_x_cells;
1321 }
1322
1323 pub fn line_ending(&self) -> LineEnding {
1325 self.line_ending
1326 }
1327
1328 pub fn set_line_ending(&mut self, line_ending: LineEnding) {
1330 self.line_ending = line_ending;
1331 }
1332
1333 fn execute_view(&mut self, command: ViewCommand) -> Result<CommandResult, CommandError> {
1335 match command {
1336 ViewCommand::SetViewportWidth { width } => {
1337 if width == 0 {
1338 return Err(CommandError::Other(
1339 "Viewport width must be greater than 0".to_string(),
1340 ));
1341 }
1342
1343 self.editor.set_view_options(
1344 width,
1345 self.editor.layout_engine.wrap_mode(),
1346 self.editor.layout_engine.wrap_indent(),
1347 self.editor.layout_engine.tab_width(),
1348 );
1349 Ok(CommandResult::Success)
1350 }
1351 ViewCommand::SetWrapMode { mode } => {
1352 self.editor.set_view_options(
1353 self.editor.viewport_width,
1354 mode,
1355 self.editor.layout_engine.wrap_indent(),
1356 self.editor.layout_engine.tab_width(),
1357 );
1358 Ok(CommandResult::Success)
1359 }
1360 ViewCommand::SetWrapIndent { indent } => {
1361 self.editor.set_view_options(
1362 self.editor.viewport_width,
1363 self.editor.layout_engine.wrap_mode(),
1364 indent,
1365 self.editor.layout_engine.tab_width(),
1366 );
1367 Ok(CommandResult::Success)
1368 }
1369 ViewCommand::SetTabWidth { width } => {
1370 if width == 0 {
1371 return Err(CommandError::Other(
1372 "Tab width must be greater than 0".to_string(),
1373 ));
1374 }
1375
1376 self.editor.set_view_options(
1377 self.editor.viewport_width,
1378 self.editor.layout_engine.wrap_mode(),
1379 self.editor.layout_engine.wrap_indent(),
1380 width,
1381 );
1382 Ok(CommandResult::Success)
1383 }
1384 ViewCommand::SetTabKeyBehavior { behavior } => {
1385 self.tab_key_behavior = behavior;
1386 Ok(CommandResult::Success)
1387 }
1388 ViewCommand::SetIndentationConfig { config } => {
1389 self.indentation_config = config;
1390 Ok(CommandResult::Success)
1391 }
1392 ViewCommand::SetAutoPairsConfig { config } => {
1393 self.set_auto_pairs_config(config);
1394 Ok(CommandResult::Success)
1395 }
1396 ViewCommand::SetAutoPairsEnabled { enabled } => {
1397 self.set_auto_pairs_enabled(enabled);
1398 Ok(CommandResult::Success)
1399 }
1400 ViewCommand::SetWordBoundaryAsciiBoundaryChars { boundary_chars } => {
1401 self.editor
1402 .set_word_boundary_ascii_boundary_chars(&boundary_chars);
1403 Ok(CommandResult::Success)
1404 }
1405 ViewCommand::ResetWordBoundaryDefaults => {
1406 self.editor.reset_word_boundary_defaults();
1407 Ok(CommandResult::Success)
1408 }
1409 ViewCommand::ScrollTo { line } => {
1410 if line >= self.editor.line_index.line_count() {
1411 return Err(CommandError::InvalidPosition { line, column: 0 });
1412 }
1413
1414 Ok(CommandResult::Success)
1417 }
1418 ViewCommand::GetViewport { start_row, count } => {
1419 let grid = self.editor.get_headless_grid_styled(start_row, count);
1420 Ok(CommandResult::Viewport(grid))
1421 }
1422 }
1423 }
1424
1425 fn execute_style(&mut self, command: StyleCommand) -> Result<CommandResult, CommandError> {
1427 match command {
1428 StyleCommand::AddStyle {
1429 start,
1430 end,
1431 style_id,
1432 } => {
1433 if start >= end {
1434 return Err(CommandError::InvalidRange { start, end });
1435 }
1436
1437 let interval = crate::intervals::Interval::new(start, end, style_id);
1438 self.editor.insert_style_interval(interval);
1439 Ok(CommandResult::Success)
1440 }
1441 StyleCommand::RemoveStyle {
1442 start,
1443 end,
1444 style_id,
1445 } => {
1446 self.editor.remove_style_interval(start, end, style_id);
1447 Ok(CommandResult::Success)
1448 }
1449 StyleCommand::Fold {
1450 start_line,
1451 end_line,
1452 } => {
1453 if start_line >= end_line {
1454 return Err(CommandError::InvalidRange {
1455 start: start_line,
1456 end: end_line,
1457 });
1458 }
1459
1460 let mut region = crate::intervals::FoldRegion::new(start_line, end_line);
1461 region.collapse();
1462 self.editor.folding_manager.add_region(region);
1463 self.editor
1464 .sync_visual_row_index_for_logical_range(start_line, end_line);
1465 Ok(CommandResult::Success)
1466 }
1467 StyleCommand::Unfold { start_line } => {
1468 let affected = self
1469 .editor
1470 .folding_manager
1471 .innermost_region_bounds_for_line(start_line);
1472 self.editor.folding_manager.expand_line(start_line);
1473 if let Some((start, end)) = affected {
1474 self.editor
1475 .sync_visual_row_index_for_logical_range(start, end);
1476 }
1477 Ok(CommandResult::Success)
1478 }
1479 StyleCommand::UnfoldAll => {
1480 let affected = self
1481 .editor
1482 .folding_manager
1483 .regions()
1484 .iter()
1485 .filter(|region| region.is_collapsed)
1486 .fold(None::<(usize, usize)>, |acc, region| match acc {
1487 Some((start, end)) => {
1488 Some((start.min(region.start_line), end.max(region.end_line)))
1489 }
1490 None => Some((region.start_line, region.end_line)),
1491 });
1492 self.editor.folding_manager.expand_all();
1493 if let Some((start, end)) = affected {
1494 self.editor
1495 .sync_visual_row_index_for_logical_range(start, end);
1496 }
1497 Ok(CommandResult::Success)
1498 }
1499 StyleCommand::UpdateBracketMatchHighlights => {
1500 self.execute_update_bracket_match_highlights_command()
1501 }
1502 StyleCommand::ClearBracketMatchHighlights => {
1503 self.execute_clear_bracket_match_highlights_command()
1504 }
1505 }
1506 }
1507}
1508
1509#[cfg(test)]
1510mod tests {
1511 use super::*;
1512
1513 #[test]
1514 fn test_edit_insert() {
1515 let mut executor = CommandExecutor::new("Hello", 80);
1516
1517 let result = executor.execute(Command::Edit(EditCommand::Insert {
1518 offset: 5,
1519 text: " World".to_string(),
1520 }));
1521
1522 assert!(result.is_ok());
1523 assert_eq!(executor.editor().get_text(), "Hello World");
1524 }
1525
1526 #[test]
1527 fn test_edit_delete() {
1528 let mut executor = CommandExecutor::new("Hello World", 80);
1529
1530 let result = executor.execute(Command::Edit(EditCommand::Delete {
1531 start: 5,
1532 length: 6,
1533 }));
1534
1535 assert!(result.is_ok());
1536 assert_eq!(executor.editor().get_text(), "Hello");
1537 }
1538
1539 #[test]
1540 fn test_edit_replace() {
1541 let mut executor = CommandExecutor::new("Hello World", 80);
1542
1543 let result = executor.execute(Command::Edit(EditCommand::Replace {
1544 start: 6,
1545 length: 5,
1546 text: "Rust".to_string(),
1547 }));
1548
1549 assert!(result.is_ok());
1550 assert_eq!(executor.editor().get_text(), "Hello Rust");
1551 }
1552
1553 #[test]
1554 fn test_cursor_move_to() {
1555 let mut executor = CommandExecutor::new("Line 1\nLine 2\nLine 3", 80);
1556
1557 let result = executor.execute(Command::Cursor(CursorCommand::MoveTo {
1558 line: 1,
1559 column: 3,
1560 }));
1561
1562 assert!(result.is_ok());
1563 assert_eq!(executor.editor().cursor_position(), Position::new(1, 3));
1564 }
1565
1566 #[test]
1567 fn test_cursor_selection() {
1568 let mut executor = CommandExecutor::new("Hello World", 80);
1569
1570 let result = executor.execute(Command::Cursor(CursorCommand::SetSelection {
1571 start: Position::new(0, 0),
1572 end: Position::new(0, 5),
1573 }));
1574
1575 assert!(result.is_ok());
1576 assert!(executor.editor().selection().is_some());
1577 }
1578
1579 #[test]
1580 fn test_view_set_width() {
1581 let mut executor = CommandExecutor::new("Test", 80);
1582
1583 let result = executor.execute(Command::View(ViewCommand::SetViewportWidth { width: 40 }));
1584
1585 assert!(result.is_ok());
1586 assert_eq!(executor.editor().viewport_width(), 40);
1587 }
1588
1589 #[test]
1590 fn test_style_add_remove() {
1591 let mut executor = CommandExecutor::new("Hello World", 80);
1592
1593 let result = executor.execute(Command::Style(StyleCommand::AddStyle {
1595 start: 0,
1596 end: 5,
1597 style_id: 1,
1598 }));
1599 assert!(result.is_ok());
1600
1601 let result = executor.execute(Command::Style(StyleCommand::RemoveStyle {
1603 start: 0,
1604 end: 5,
1605 style_id: 1,
1606 }));
1607 assert!(result.is_ok());
1608 }
1609
1610 #[test]
1611 fn test_batch_execution() {
1612 let mut executor = CommandExecutor::new("", 80);
1613
1614 let commands = vec![
1615 Command::Edit(EditCommand::Insert {
1616 offset: 0,
1617 text: "Hello".to_string(),
1618 }),
1619 Command::Edit(EditCommand::Insert {
1620 offset: 5,
1621 text: " World".to_string(),
1622 }),
1623 ];
1624
1625 let results = executor.execute_batch(commands);
1626 assert!(results.is_ok());
1627 assert_eq!(executor.editor().get_text(), "Hello World");
1628 }
1629
1630 #[test]
1631 fn test_error_invalid_offset() {
1632 let mut executor = CommandExecutor::new("Hello", 80);
1633
1634 let result = executor.execute(Command::Edit(EditCommand::Insert {
1635 offset: 100,
1636 text: "X".to_string(),
1637 }));
1638
1639 assert!(result.is_err());
1640 assert!(matches!(
1641 result.unwrap_err(),
1642 CommandError::InvalidOffset(_)
1643 ));
1644 }
1645}