1use crate::delta::TextDelta;
37use crate::intervals::{FoldRegion, Interval, StyleId, StyleLayerId};
38use crate::processing::{DocumentProcessor, ProcessingEdit};
39use crate::snapshot::{ComposedGrid, HeadlessGrid};
40use crate::{
41 Command, CommandError, CommandExecutor, CommandResult, CursorCommand, Decoration,
42 DecorationLayerId, Diagnostic, EditCommand, EditorCore, LineEnding, Position, Selection,
43 SelectionDirection, StyleCommand, ViewCommand,
44};
45use std::collections::HashSet;
46use std::ops::Range;
47use std::sync::Arc;
48
49#[derive(Debug, Clone)]
51pub struct DocumentState {
52 pub line_count: usize,
54 pub char_count: usize,
56 pub byte_count: usize,
58 pub is_modified: bool,
60 pub version: u64,
62}
63
64#[derive(Debug, Clone)]
66pub struct CursorState {
67 pub position: Position,
69 pub offset: usize,
71 pub multi_cursors: Vec<Position>,
73 pub selection: Option<Selection>,
75 pub selections: Vec<Selection>,
77 pub primary_selection_index: usize,
79}
80
81#[derive(Debug, Clone)]
83pub struct ViewportState {
84 pub width: usize,
86 pub height: Option<usize>,
88 pub scroll_top: usize,
90 pub sub_row_offset: u16,
92 pub overscan_rows: usize,
94 pub visible_lines: Range<usize>,
96 pub prefetch_lines: Range<usize>,
98 pub total_visual_lines: usize,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub struct SmoothScrollState {
105 pub top_visual_row: usize,
107 pub sub_row_offset: u16,
109 pub overscan_rows: usize,
111}
112
113#[derive(Debug, Clone)]
115pub struct UndoRedoState {
116 pub can_undo: bool,
118 pub can_redo: bool,
120 pub undo_depth: usize,
122 pub redo_depth: usize,
124 pub current_change_group: Option<usize>,
126}
127
128#[derive(Debug, Clone)]
130pub struct FoldingState {
131 pub regions: Vec<FoldRegion>,
133 pub collapsed_line_count: usize,
135 pub visible_logical_lines: usize,
137 pub total_visual_lines: usize,
139}
140
141#[derive(Debug, Clone)]
143pub struct DiagnosticsState {
144 pub diagnostics_count: usize,
146}
147
148#[derive(Debug, Clone)]
150pub struct DecorationsState {
151 pub layer_count: usize,
153 pub decoration_count: usize,
155}
156
157#[derive(Debug, Clone)]
159pub struct StyleState {
160 pub style_count: usize,
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub enum StateChangeType {
167 DocumentModified,
169 CursorMoved,
171 SelectionChanged,
173 ViewportChanged,
175 FoldingChanged,
177 StyleChanged,
179 DecorationsChanged,
181 DiagnosticsChanged,
183 SymbolsChanged,
185}
186
187#[derive(Debug, Clone)]
189pub struct StateChange {
190 pub change_type: StateChangeType,
192 pub old_version: u64,
194 pub new_version: u64,
196 pub affected_region: Option<Range<usize>>,
198 pub text_delta: Option<Arc<TextDelta>>,
200}
201
202impl StateChange {
203 pub fn new(change_type: StateChangeType, old_version: u64, new_version: u64) -> Self {
205 Self {
206 change_type,
207 old_version,
208 new_version,
209 affected_region: None,
210 text_delta: None,
211 }
212 }
213
214 pub fn with_region(mut self, region: Range<usize>) -> Self {
216 self.affected_region = Some(region);
217 self
218 }
219
220 pub fn with_text_delta(mut self, delta: Arc<TextDelta>) -> Self {
222 self.text_delta = Some(delta);
223 self
224 }
225}
226
227#[derive(Debug, Clone)]
229pub struct EditorState {
230 pub document: DocumentState,
232 pub cursor: CursorState,
234 pub viewport: ViewportState,
236 pub undo_redo: UndoRedoState,
238 pub folding: FoldingState,
240 pub diagnostics: DiagnosticsState,
242 pub decorations: DecorationsState,
244 pub style: StyleState,
246}
247
248pub type StateChangeCallback = Box<dyn FnMut(&StateChange) + Send>;
250
251pub struct EditorStateManager {
298 executor: CommandExecutor,
300 state_version: u64,
302 is_modified: bool,
304 callbacks: Vec<StateChangeCallback>,
306 scroll_top: usize,
308 scroll_sub_row_offset: u16,
310 overscan_rows: usize,
312 viewport_height: Option<usize>,
314 last_text_delta: Option<Arc<TextDelta>>,
316}
317
318impl EditorStateManager {
319 pub fn new(text: &str, viewport_width: usize) -> Self {
321 Self {
322 executor: CommandExecutor::new(text, viewport_width),
323 state_version: 0,
324 is_modified: false,
325 callbacks: Vec::new(),
326 scroll_top: 0,
327 scroll_sub_row_offset: 0,
328 overscan_rows: 0,
329 viewport_height: None,
330 last_text_delta: None,
331 }
332 }
333
334 pub fn empty(viewport_width: usize) -> Self {
336 Self::new("", viewport_width)
337 }
338
339 pub fn editor(&self) -> &EditorCore {
341 self.executor.editor()
342 }
343
344 pub fn editor_mut(&mut self) -> &mut EditorCore {
346 self.executor.editor_mut()
347 }
348
349 pub fn line_ending(&self) -> LineEnding {
351 self.executor.line_ending()
352 }
353
354 pub fn set_line_ending(&mut self, line_ending: LineEnding) {
356 self.executor.set_line_ending(line_ending);
357 }
358
359 pub fn get_text_for_saving(&self) -> String {
361 let text = self.editor().get_text();
362 self.line_ending().apply_to_text(&text)
363 }
364
365 pub fn execute(&mut self, command: Command) -> Result<CommandResult, CommandError> {
372 let change_type = Self::change_type_for_command(&command);
373 let is_delete_like = matches!(
374 &command,
375 Command::Edit(EditCommand::Backspace | EditCommand::DeleteForward)
376 );
377
378 let cursor_before = self.executor.editor().cursor_position();
380 let selection_before = self.executor.editor().selection().cloned();
381 let secondary_before = self.executor.editor().secondary_selections().to_vec();
382 let viewport_width_before = self.executor.editor().viewport_width;
383 let char_count_before = self.executor.editor().char_count();
384
385 let result = self.executor.execute(command)?;
386 let char_count_after = self.executor.editor().char_count();
387 let delta_present = self.executor.last_text_delta().is_some();
388
389 if let Some(change_type) = change_type {
390 let changed = match change_type {
391 StateChangeType::CursorMoved => {
392 self.executor.editor().cursor_position() != cursor_before
393 || self.executor.editor().secondary_selections()
394 != secondary_before.as_slice()
395 }
396 StateChangeType::SelectionChanged => {
397 self.executor.editor().cursor_position() != cursor_before
398 || self.executor.editor().selection().cloned() != selection_before
399 || self.executor.editor().secondary_selections()
400 != secondary_before.as_slice()
401 }
402 StateChangeType::ViewportChanged => {
403 self.executor.editor().viewport_width != viewport_width_before
404 }
405 StateChangeType::DocumentModified => {
406 if is_delete_like {
409 char_count_after != char_count_before
410 } else {
411 delta_present
412 }
413 }
414 StateChangeType::FoldingChanged
416 | StateChangeType::StyleChanged
417 | StateChangeType::DecorationsChanged
418 | StateChangeType::DiagnosticsChanged
419 | StateChangeType::SymbolsChanged => true,
420 };
421
422 if changed {
423 if matches!(change_type, StateChangeType::DocumentModified) {
424 let is_modified = !self.executor.is_clean();
425 let delta = self.executor.take_last_text_delta().map(Arc::new);
426 self.last_text_delta = delta.clone();
427 self.mark_modified_internal(change_type, Some(is_modified), delta);
428 } else {
429 self.mark_modified_internal(change_type, None, None);
430 }
431 }
432 }
433
434 Ok(result)
435 }
436
437 fn change_type_for_command(command: &Command) -> Option<StateChangeType> {
438 match command {
439 Command::Edit(EditCommand::InsertText { text }) if text.is_empty() => None,
440 Command::Edit(EditCommand::Delete { length: 0, .. }) => None,
441 Command::Edit(EditCommand::Replace {
442 length: 0, text, ..
443 }) if text.is_empty() => None,
444 Command::Edit(EditCommand::EndUndoGroup) => None,
445 Command::Edit(_) => Some(StateChangeType::DocumentModified),
446 Command::Cursor(
447 CursorCommand::MoveTo { .. }
448 | CursorCommand::MoveBy { .. }
449 | CursorCommand::MoveVisualBy { .. }
450 | CursorCommand::MoveToVisual { .. }
451 | CursorCommand::MoveToLineStart
452 | CursorCommand::MoveToLineEnd
453 | CursorCommand::MoveToVisualLineStart
454 | CursorCommand::MoveToVisualLineEnd
455 | CursorCommand::MoveGraphemeLeft
456 | CursorCommand::MoveGraphemeRight
457 | CursorCommand::MoveWordLeft
458 | CursorCommand::MoveWordRight,
459 ) => Some(StateChangeType::CursorMoved),
460 Command::Cursor(
461 CursorCommand::SetSelection { .. }
462 | CursorCommand::ExtendSelection { .. }
463 | CursorCommand::ClearSelection
464 | CursorCommand::SetSelections { .. }
465 | CursorCommand::ClearSecondarySelections
466 | CursorCommand::SetRectSelection { .. }
467 | CursorCommand::SelectLine
468 | CursorCommand::SelectWord
469 | CursorCommand::ExpandSelection
470 | CursorCommand::AddCursorAbove
471 | CursorCommand::AddCursorBelow
472 | CursorCommand::AddNextOccurrence { .. }
473 | CursorCommand::AddAllOccurrences { .. }
474 | CursorCommand::FindNext { .. }
475 | CursorCommand::FindPrev { .. },
476 ) => Some(StateChangeType::SelectionChanged),
477 Command::View(
478 ViewCommand::SetViewportWidth { .. }
479 | ViewCommand::SetWrapMode { .. }
480 | ViewCommand::SetWrapIndent { .. }
481 | ViewCommand::SetTabWidth { .. },
482 ) => Some(StateChangeType::ViewportChanged),
483 Command::View(
484 ViewCommand::SetTabKeyBehavior { .. }
485 | ViewCommand::ScrollTo { .. }
486 | ViewCommand::GetViewport { .. },
487 ) => None,
488 Command::Style(StyleCommand::AddStyle { .. } | StyleCommand::RemoveStyle { .. }) => {
489 Some(StateChangeType::StyleChanged)
490 }
491 Command::Style(
492 StyleCommand::Fold { .. } | StyleCommand::Unfold { .. } | StyleCommand::UnfoldAll,
493 ) => Some(StateChangeType::FoldingChanged),
494 }
495 }
496
497 pub fn version(&self) -> u64 {
499 self.state_version
500 }
501
502 pub fn set_viewport_height(&mut self, height: usize) {
504 self.viewport_height = Some(height);
505 }
506
507 pub fn set_scroll_top(&mut self, scroll_top: usize) {
509 let old_scroll = self.scroll_top;
510 self.scroll_top = scroll_top;
511
512 if old_scroll != scroll_top {
513 self.notify_change(StateChangeType::ViewportChanged);
514 }
515 }
516
517 pub fn set_scroll_sub_row_offset(&mut self, sub_row_offset: u16) {
519 let old = self.scroll_sub_row_offset;
520 self.scroll_sub_row_offset = sub_row_offset;
521 if old != sub_row_offset {
522 self.notify_change(StateChangeType::ViewportChanged);
523 }
524 }
525
526 pub fn set_overscan_rows(&mut self, overscan_rows: usize) {
528 let old = self.overscan_rows;
529 self.overscan_rows = overscan_rows;
530 if old != overscan_rows {
531 self.notify_change(StateChangeType::ViewportChanged);
532 }
533 }
534
535 pub fn set_smooth_scroll_state(&mut self, state: SmoothScrollState) {
537 let mut changed = false;
538 if self.scroll_top != state.top_visual_row {
539 self.scroll_top = state.top_visual_row;
540 changed = true;
541 }
542 if self.scroll_sub_row_offset != state.sub_row_offset {
543 self.scroll_sub_row_offset = state.sub_row_offset;
544 changed = true;
545 }
546 if self.overscan_rows != state.overscan_rows {
547 self.overscan_rows = state.overscan_rows;
548 changed = true;
549 }
550 if changed {
551 self.notify_change(StateChangeType::ViewportChanged);
552 }
553 }
554
555 pub fn get_smooth_scroll_state(&self) -> SmoothScrollState {
557 SmoothScrollState {
558 top_visual_row: self.scroll_top,
559 sub_row_offset: self.scroll_sub_row_offset,
560 overscan_rows: self.overscan_rows,
561 }
562 }
563
564 pub fn get_full_state(&self) -> EditorState {
566 EditorState {
567 document: self.get_document_state(),
568 cursor: self.get_cursor_state(),
569 viewport: self.get_viewport_state(),
570 undo_redo: self.get_undo_redo_state(),
571 folding: self.get_folding_state(),
572 diagnostics: self.get_diagnostics_state(),
573 decorations: self.get_decorations_state(),
574 style: self.get_style_state(),
575 }
576 }
577
578 pub fn get_document_state(&self) -> DocumentState {
580 let editor = self.executor.editor();
581 DocumentState {
582 line_count: editor.line_count(),
583 char_count: editor.char_count(),
584 byte_count: editor.get_text().len(),
585 is_modified: self.is_modified,
586 version: self.state_version,
587 }
588 }
589
590 pub fn get_cursor_state(&self) -> CursorState {
592 let editor = self.executor.editor();
593 let mut selections: Vec<Selection> =
594 Vec::with_capacity(1 + editor.secondary_selections().len());
595
596 let primary = editor.selection().cloned().unwrap_or(Selection {
597 start: editor.cursor_position(),
598 end: editor.cursor_position(),
599 direction: SelectionDirection::Forward,
600 });
601 selections.push(primary);
602 selections.extend(editor.secondary_selections().iter().cloned());
603
604 let (selections, primary_selection_index) =
605 crate::selection_set::normalize_selections(selections, 0);
606 let primary = selections
607 .get(primary_selection_index)
608 .cloned()
609 .unwrap_or(Selection {
610 start: editor.cursor_position(),
611 end: editor.cursor_position(),
612 direction: SelectionDirection::Forward,
613 });
614
615 let position = primary.end;
616 let offset = editor
617 .line_index
618 .position_to_char_offset(position.line, position.column);
619
620 let selection = if primary.start == primary.end {
621 None
622 } else {
623 Some(primary)
624 };
625
626 let multi_cursors: Vec<Position> = selections
627 .iter()
628 .enumerate()
629 .filter_map(|(idx, sel)| {
630 if idx == primary_selection_index {
631 None
632 } else {
633 Some(sel.end)
634 }
635 })
636 .collect();
637
638 CursorState {
639 position,
640 offset,
641 multi_cursors,
642 selection,
643 selections,
644 primary_selection_index,
645 }
646 }
647
648 pub fn get_viewport_state(&self) -> ViewportState {
650 let editor = self.executor.editor();
651 let total_visual_lines = editor.visual_line_count();
652 let clamped_top = self.scroll_top.min(total_visual_lines);
653 let visible_end = if let Some(height) = self.viewport_height {
654 clamped_top.saturating_add(height)
655 } else {
656 total_visual_lines
657 };
658 let visible_lines = clamped_top..visible_end.min(total_visual_lines);
659 let prefetch_start = visible_lines.start.saturating_sub(self.overscan_rows);
660 let prefetch_end = visible_lines
661 .end
662 .saturating_add(self.overscan_rows)
663 .min(total_visual_lines);
664
665 ViewportState {
666 width: editor.viewport_width,
667 height: self.viewport_height,
668 scroll_top: clamped_top,
669 sub_row_offset: self.scroll_sub_row_offset,
670 overscan_rows: self.overscan_rows,
671 visible_lines,
672 prefetch_lines: prefetch_start..prefetch_end,
673 total_visual_lines,
674 }
675 }
676
677 pub fn get_undo_redo_state(&self) -> UndoRedoState {
679 UndoRedoState {
680 can_undo: self.executor.can_undo(),
681 can_redo: self.executor.can_redo(),
682 undo_depth: self.executor.undo_depth(),
683 redo_depth: self.executor.redo_depth(),
684 current_change_group: self.executor.current_change_group(),
685 }
686 }
687
688 pub fn get_folding_state(&self) -> FoldingState {
690 let editor = self.executor.editor();
691 let regions = editor.folding_manager.regions().to_vec();
692 let collapsed_line_count: usize = regions
693 .iter()
694 .filter(|r| r.is_collapsed)
695 .map(|r| r.end_line - r.start_line)
696 .sum();
697
698 let visible_logical_lines = editor.line_count() - collapsed_line_count;
699
700 FoldingState {
701 regions,
702 collapsed_line_count,
703 visible_logical_lines,
704 total_visual_lines: editor.visual_line_count(),
705 }
706 }
707
708 pub fn get_style_state(&self) -> StyleState {
710 let editor = self.executor.editor();
711 let layered_count: usize = editor.style_layers.values().map(|t| t.len()).sum();
712 StyleState {
713 style_count: editor.interval_tree.len() + layered_count,
714 }
715 }
716
717 pub fn get_diagnostics_state(&self) -> DiagnosticsState {
719 let editor = self.executor.editor();
720 DiagnosticsState {
721 diagnostics_count: editor.diagnostics.len(),
722 }
723 }
724
725 pub fn get_decorations_state(&self) -> DecorationsState {
727 let editor = self.executor.editor();
728 let decoration_count: usize = editor.decorations.values().map(|d| d.len()).sum();
729 DecorationsState {
730 layer_count: editor.decorations.len(),
731 decoration_count,
732 }
733 }
734
735 pub fn get_styles_in_range(&self, start: usize, end: usize) -> Vec<(usize, usize, StyleId)> {
737 let editor = self.executor.editor();
738 let mut result: Vec<(usize, usize, StyleId)> = editor
739 .interval_tree
740 .query_range(start, end)
741 .iter()
742 .map(|interval| (interval.start, interval.end, interval.style_id))
743 .collect();
744
745 for tree in editor.style_layers.values() {
746 result.extend(
747 tree.query_range(start, end)
748 .iter()
749 .map(|interval| (interval.start, interval.end, interval.style_id)),
750 );
751 }
752
753 result.sort_unstable_by_key(|(s, e, id)| (*s, *e, *id));
754 result
755 }
756
757 pub fn get_styles_at(&self, offset: usize) -> Vec<StyleId> {
759 let editor = self.executor.editor();
760 let mut styles: Vec<StyleId> = editor
761 .interval_tree
762 .query_point(offset)
763 .iter()
764 .map(|interval| interval.style_id)
765 .collect();
766
767 for tree in editor.style_layers.values() {
768 styles.extend(
769 tree.query_point(offset)
770 .iter()
771 .map(|interval| interval.style_id),
772 );
773 }
774
775 styles.sort_unstable();
776 styles.dedup();
777 styles
778 }
779
780 pub fn replace_style_layer(&mut self, layer: StyleLayerId, intervals: Vec<Interval>) {
785 let editor = self.executor.editor_mut();
786
787 if intervals.is_empty() {
788 editor.style_layers.remove(&layer);
789 self.mark_modified(StateChangeType::StyleChanged);
790 return;
791 }
792
793 let tree = editor.style_layers.entry(layer).or_default();
794 tree.clear();
795
796 for interval in intervals {
797 if interval.start < interval.end {
798 tree.insert(interval);
799 }
800 }
801
802 self.mark_modified(StateChangeType::StyleChanged);
803 }
804
805 pub fn clear_style_layer(&mut self, layer: StyleLayerId) {
807 let editor = self.executor.editor_mut();
808 editor.style_layers.remove(&layer);
809 self.mark_modified(StateChangeType::StyleChanged);
810 }
811
812 pub fn replace_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) {
814 let editor = self.executor.editor_mut();
815 editor.diagnostics = diagnostics;
816 self.mark_modified(StateChangeType::DiagnosticsChanged);
817 }
818
819 pub fn clear_diagnostics(&mut self) {
821 let editor = self.executor.editor_mut();
822 editor.diagnostics.clear();
823 self.mark_modified(StateChangeType::DiagnosticsChanged);
824 }
825
826 pub fn replace_document_symbols(&mut self, symbols: crate::DocumentOutline) {
828 let editor = self.executor.editor_mut();
829 editor.document_symbols = symbols;
830 self.mark_modified(StateChangeType::SymbolsChanged);
831 }
832
833 pub fn clear_document_symbols(&mut self) {
835 let editor = self.executor.editor_mut();
836 editor.document_symbols = crate::DocumentOutline::default();
837 self.mark_modified(StateChangeType::SymbolsChanged);
838 }
839
840 pub fn replace_decorations(
842 &mut self,
843 layer: DecorationLayerId,
844 mut decorations: Vec<Decoration>,
845 ) {
846 decorations.sort_unstable_by_key(|d| (d.range.start, d.range.end));
847 let editor = self.executor.editor_mut();
848 editor.decorations.insert(layer, decorations);
849 self.mark_modified(StateChangeType::DecorationsChanged);
850 }
851
852 pub fn clear_decorations(&mut self, layer: DecorationLayerId) {
854 let editor = self.executor.editor_mut();
855 editor.decorations.remove(&layer);
856 self.mark_modified(StateChangeType::DecorationsChanged);
857 }
858
859 pub fn replace_folding_regions(
864 &mut self,
865 mut regions: Vec<FoldRegion>,
866 preserve_collapsed: bool,
867 ) {
868 if preserve_collapsed {
869 let collapsed: HashSet<(usize, usize)> = self
870 .editor()
871 .folding_manager
872 .derived_regions()
873 .iter()
874 .filter(|r| r.is_collapsed)
875 .map(|r| (r.start_line, r.end_line))
876 .collect();
877
878 for region in &mut regions {
879 if collapsed.contains(&(region.start_line, region.end_line)) {
880 region.is_collapsed = true;
881 }
882 }
883 }
884
885 self.editor_mut()
886 .folding_manager
887 .replace_derived_regions(regions);
888 self.editor_mut().invalidate_visual_row_index_cache();
889 self.mark_modified(StateChangeType::FoldingChanged);
890 }
891
892 pub fn clear_folding_regions(&mut self) {
894 self.editor_mut().folding_manager.clear_derived_regions();
895 self.editor_mut().invalidate_visual_row_index_cache();
896 self.mark_modified(StateChangeType::FoldingChanged);
897 }
898
899 pub fn apply_processing_edits<I>(&mut self, edits: I)
901 where
902 I: IntoIterator<Item = ProcessingEdit>,
903 {
904 for edit in edits {
905 match edit {
906 ProcessingEdit::ReplaceStyleLayer { layer, intervals } => {
907 self.replace_style_layer(layer, intervals);
908 }
909 ProcessingEdit::ClearStyleLayer { layer } => {
910 self.clear_style_layer(layer);
911 }
912 ProcessingEdit::ReplaceFoldingRegions {
913 regions,
914 preserve_collapsed,
915 } => {
916 self.replace_folding_regions(regions, preserve_collapsed);
917 }
918 ProcessingEdit::ClearFoldingRegions => {
919 self.clear_folding_regions();
920 }
921 ProcessingEdit::ReplaceDiagnostics { diagnostics } => {
922 self.replace_diagnostics(diagnostics);
923 }
924 ProcessingEdit::ClearDiagnostics => {
925 self.clear_diagnostics();
926 }
927 ProcessingEdit::ReplaceDecorations { layer, decorations } => {
928 self.replace_decorations(layer, decorations);
929 }
930 ProcessingEdit::ClearDecorations { layer } => {
931 self.clear_decorations(layer);
932 }
933 ProcessingEdit::ReplaceDocumentSymbols { symbols } => {
934 self.replace_document_symbols(symbols);
935 }
936 ProcessingEdit::ClearDocumentSymbols => {
937 self.clear_document_symbols();
938 }
939 }
940 }
941 }
942
943 pub fn apply_processor<P>(&mut self, processor: &mut P) -> Result<(), P::Error>
945 where
946 P: DocumentProcessor,
947 {
948 let edits = processor.process(self)?;
949 self.apply_processing_edits(edits);
950 Ok(())
951 }
952
953 pub fn get_viewport_content(&self, start_row: usize, count: usize) -> HeadlessGrid {
955 let editor = self.executor.editor();
956 let text = editor.get_text();
957 let generator = crate::SnapshotGenerator::from_text_with_layout_options(
958 &text,
959 editor.viewport_width,
960 editor.layout_engine.tab_width(),
961 editor.layout_engine.wrap_mode(),
962 editor.layout_engine.wrap_indent(),
963 );
964 generator.get_headless_grid(start_row, count)
965 }
966
967 pub fn get_viewport_content_styled(
972 &self,
973 start_visual_row: usize,
974 count: usize,
975 ) -> HeadlessGrid {
976 self.executor
977 .editor()
978 .get_headless_grid_styled(start_visual_row, count)
979 }
980
981 pub fn get_minimap_content(&self, start_visual_row: usize, count: usize) -> crate::MinimapGrid {
983 self.executor
984 .editor()
985 .get_minimap_grid(start_visual_row, count)
986 }
987
988 pub fn get_viewport_content_composed(
993 &self,
994 start_visual_row: usize,
995 count: usize,
996 ) -> ComposedGrid {
997 self.executor
998 .editor()
999 .get_headless_grid_composed(start_visual_row, count)
1000 }
1001
1002 pub fn total_visual_lines(&self) -> usize {
1004 self.executor.editor().visual_line_count()
1005 }
1006
1007 pub fn visual_to_logical_line(&self, visual_row: usize) -> (usize, usize) {
1009 self.executor.editor().visual_to_logical_line(visual_row)
1010 }
1011
1012 pub fn logical_position_to_visual(&self, line: usize, column: usize) -> Option<(usize, usize)> {
1014 self.executor
1015 .editor()
1016 .logical_position_to_visual(line, column)
1017 }
1018
1019 pub fn visual_position_to_logical(
1021 &self,
1022 visual_row: usize,
1023 x_cells: usize,
1024 ) -> Option<Position> {
1025 self.executor
1026 .editor()
1027 .visual_position_to_logical(visual_row, x_cells)
1028 }
1029
1030 pub fn subscribe<F>(&mut self, callback: F)
1032 where
1033 F: FnMut(&StateChange) + Send + 'static,
1034 {
1035 self.callbacks.push(Box::new(callback));
1036 }
1037
1038 pub fn has_changed_since(&self, version: u64) -> bool {
1040 self.state_version > version
1041 }
1042
1043 pub fn mark_modified(&mut self, change_type: StateChangeType) {
1045 self.mark_modified_internal(change_type, None, None);
1046 }
1047
1048 fn mark_modified_internal(
1049 &mut self,
1050 change_type: StateChangeType,
1051 is_modified_override: Option<bool>,
1052 delta: Option<Arc<TextDelta>>,
1053 ) {
1054 let old_version = self.state_version;
1055 self.state_version += 1;
1056
1057 if matches!(change_type, StateChangeType::DocumentModified) {
1059 self.is_modified = is_modified_override.unwrap_or(true);
1060 }
1061
1062 let mut change = StateChange::new(change_type, old_version, self.state_version);
1063 if let Some(delta) = delta {
1064 change = change.with_text_delta(delta);
1065 }
1066 self.notify_callbacks(&change);
1067 }
1068
1069 pub fn mark_saved(&mut self) {
1071 self.executor.mark_clean();
1072 self.is_modified = false;
1073 }
1074
1075 fn notify_change(&mut self, change_type: StateChangeType) {
1077 let change = StateChange::new(change_type, self.state_version, self.state_version);
1078 self.notify_callbacks(&change);
1079 }
1080
1081 pub fn last_text_delta(&self) -> Option<&TextDelta> {
1083 self.last_text_delta.as_deref()
1084 }
1085
1086 pub fn take_last_text_delta(&mut self) -> Option<Arc<TextDelta>> {
1088 self.last_text_delta.take()
1089 }
1090
1091 fn notify_callbacks(&mut self, change: &StateChange) {
1093 for callback in &mut self.callbacks {
1094 callback(change);
1095 }
1096 }
1097}
1098
1099#[cfg(test)]
1100mod tests {
1101 use super::*;
1102
1103 #[test]
1104 fn test_document_state() {
1105 let manager = EditorStateManager::new("Hello World\nLine 2", 80);
1106 let doc_state = manager.get_document_state();
1107
1108 assert_eq!(doc_state.line_count, 2);
1109 assert_eq!(doc_state.char_count, 18); assert!(!doc_state.is_modified);
1111 assert_eq!(doc_state.version, 0);
1112 }
1113
1114 #[test]
1115 fn test_cursor_state() {
1116 let manager = EditorStateManager::new("Hello World", 80);
1117 let cursor_state = manager.get_cursor_state();
1118
1119 assert_eq!(cursor_state.position, Position::new(0, 0));
1120 assert_eq!(cursor_state.offset, 0);
1121 assert!(cursor_state.selection.is_none());
1122 }
1123
1124 #[test]
1125 fn test_viewport_state() {
1126 let mut manager = EditorStateManager::new("Line 1\nLine 2\nLine 3", 80);
1127 manager.set_viewport_height(10);
1128 manager.set_scroll_top(1);
1129
1130 let viewport_state = manager.get_viewport_state();
1131
1132 assert_eq!(viewport_state.width, 80);
1133 assert_eq!(viewport_state.height, Some(10));
1134 assert_eq!(viewport_state.scroll_top, 1);
1135 assert_eq!(viewport_state.visible_lines, 1..3);
1136 }
1137
1138 #[test]
1139 fn test_folding_state() {
1140 let manager = EditorStateManager::new("Line 1\nLine 2\nLine 3", 80);
1141 let folding_state = manager.get_folding_state();
1142
1143 assert_eq!(folding_state.regions.len(), 0);
1144 assert_eq!(folding_state.collapsed_line_count, 0);
1145 assert_eq!(folding_state.visible_logical_lines, 3);
1146 }
1147
1148 #[test]
1149 fn test_style_state() {
1150 let manager = EditorStateManager::new("Hello World", 80);
1151 let style_state = manager.get_style_state();
1152
1153 assert_eq!(style_state.style_count, 0);
1154 }
1155
1156 #[test]
1157 fn test_full_state() {
1158 let manager = EditorStateManager::new("Test", 80);
1159 let full_state = manager.get_full_state();
1160
1161 assert_eq!(full_state.document.line_count, 1);
1162 assert_eq!(full_state.cursor.position, Position::new(0, 0));
1163 assert_eq!(full_state.viewport.width, 80);
1164 }
1165
1166 #[test]
1167 fn test_version_tracking() {
1168 let mut manager = EditorStateManager::new("Test", 80);
1169
1170 assert_eq!(manager.version(), 0);
1171 assert!(!manager.has_changed_since(0));
1172
1173 manager.mark_modified(StateChangeType::DocumentModified);
1174
1175 assert_eq!(manager.version(), 1);
1176 assert!(manager.has_changed_since(0));
1177 assert!(!manager.has_changed_since(1));
1178 }
1179
1180 #[test]
1181 fn test_modification_tracking() {
1182 let mut manager = EditorStateManager::new("Test", 80);
1183
1184 assert!(!manager.get_document_state().is_modified);
1185
1186 manager.mark_modified(StateChangeType::DocumentModified);
1187 assert!(manager.get_document_state().is_modified);
1188
1189 manager.mark_saved();
1190 assert!(!manager.get_document_state().is_modified);
1191 }
1192
1193 #[test]
1194 fn test_undo_redo_state_and_dirty_tracking() {
1195 let mut manager = EditorStateManager::empty(80);
1196
1197 let state = manager.get_undo_redo_state();
1198 assert!(!state.can_undo);
1199 assert!(!state.can_redo);
1200
1201 manager
1202 .execute(Command::Edit(EditCommand::InsertText {
1203 text: "abc".to_string(),
1204 }))
1205 .unwrap();
1206
1207 assert!(manager.get_document_state().is_modified);
1208 let state = manager.get_undo_redo_state();
1209 assert!(state.can_undo);
1210 assert!(!state.can_redo);
1211 assert_eq!(state.undo_depth, 1);
1212
1213 manager.execute(Command::Edit(EditCommand::Undo)).unwrap();
1214 assert!(!manager.get_document_state().is_modified);
1215 let state = manager.get_undo_redo_state();
1216 assert!(!state.can_undo);
1217 assert!(state.can_redo);
1218
1219 manager.execute(Command::Edit(EditCommand::Redo)).unwrap();
1220 assert!(manager.get_document_state().is_modified);
1221 let state = manager.get_undo_redo_state();
1222 assert!(state.can_undo);
1223 assert!(!state.can_redo);
1224 }
1225
1226 #[test]
1227 fn test_insert_tab_undo_restores_clean_state() {
1228 let mut manager = EditorStateManager::empty(80);
1229 assert!(!manager.get_document_state().is_modified);
1230
1231 manager
1232 .execute(Command::Edit(EditCommand::InsertTab))
1233 .unwrap();
1234 assert!(manager.get_document_state().is_modified);
1235
1236 manager.execute(Command::Edit(EditCommand::Undo)).unwrap();
1237 assert!(!manager.get_document_state().is_modified);
1238 }
1239
1240 #[test]
1241 fn test_insert_tab_spaces_undo_restores_clean_state() {
1242 let mut manager = EditorStateManager::empty(80);
1243 manager
1244 .execute(Command::View(ViewCommand::SetTabKeyBehavior {
1245 behavior: crate::TabKeyBehavior::Spaces,
1246 }))
1247 .unwrap();
1248
1249 manager
1250 .execute(Command::Edit(EditCommand::InsertTab))
1251 .unwrap();
1252 assert!(manager.get_document_state().is_modified);
1253
1254 manager.execute(Command::Edit(EditCommand::Undo)).unwrap();
1255 assert!(!manager.get_document_state().is_modified);
1256 }
1257
1258 #[test]
1259 fn test_state_change_callback() {
1260 use std::sync::{Arc, Mutex};
1261
1262 let mut manager = EditorStateManager::new("Test", 80);
1263
1264 let callback_called = Arc::new(Mutex::new(false));
1265 let callback_called_clone = callback_called.clone();
1266
1267 manager.subscribe(move |_change| {
1268 *callback_called_clone.lock().unwrap() = true;
1269 });
1270
1271 manager.mark_modified(StateChangeType::CursorMoved);
1272
1273 assert!(*callback_called.lock().unwrap());
1275 }
1276
1277 #[test]
1278 fn test_execute_cursor_noop_does_not_bump_version() {
1279 let mut manager = EditorStateManager::new("A", 80);
1280 assert_eq!(manager.version(), 0);
1281
1282 manager
1284 .execute(Command::Cursor(CursorCommand::MoveBy {
1285 delta_line: 0,
1286 delta_column: -1,
1287 }))
1288 .unwrap();
1289 assert_eq!(manager.editor().cursor_position(), Position::new(0, 0));
1290 assert_eq!(manager.version(), 0);
1291
1292 manager
1294 .execute(Command::Cursor(CursorCommand::MoveTo {
1295 line: 0,
1296 column: usize::MAX,
1297 }))
1298 .unwrap();
1299 assert_eq!(manager.editor().cursor_position(), Position::new(0, 1));
1300 assert_eq!(manager.version(), 1);
1301
1302 let version_before = manager.version();
1304 manager
1305 .execute(Command::Cursor(CursorCommand::MoveBy {
1306 delta_line: 0,
1307 delta_column: 1,
1308 }))
1309 .unwrap();
1310 assert_eq!(manager.editor().cursor_position(), Position::new(0, 1));
1311 assert_eq!(manager.version(), version_before);
1312 }
1313
1314 #[test]
1315 fn test_viewport_height() {
1316 let mut manager = EditorStateManager::new("Test", 80);
1317
1318 assert_eq!(manager.get_viewport_state().height, None);
1319
1320 manager.set_viewport_height(20);
1321 assert_eq!(manager.get_viewport_state().height, Some(20));
1322 }
1323
1324 #[test]
1325 fn test_scroll_position() {
1326 let mut manager = EditorStateManager::new("Line 1\nLine 2\nLine 3\nLine 4", 80);
1327 manager.set_viewport_height(2);
1328
1329 assert_eq!(manager.get_viewport_state().scroll_top, 0);
1330 assert_eq!(manager.get_viewport_state().visible_lines, 0..2);
1331
1332 manager.set_scroll_top(2);
1333 assert_eq!(manager.get_viewport_state().scroll_top, 2);
1334 assert_eq!(manager.get_viewport_state().visible_lines, 2..4);
1335 }
1336
1337 #[test]
1338 fn test_get_styles() {
1339 let mut manager = EditorStateManager::new("Hello World", 80);
1340
1341 manager
1343 .editor_mut()
1344 .interval_tree
1345 .insert(crate::intervals::Interval::new(0, 5, 1));
1346
1347 let styles = manager.get_styles_in_range(0, 10);
1348 assert_eq!(styles.len(), 1);
1349 assert_eq!(styles[0], (0, 5, 1));
1350
1351 let styles_at = manager.get_styles_at(3);
1352 assert_eq!(styles_at.len(), 1);
1353 assert_eq!(styles_at[0], 1);
1354 }
1355
1356 #[test]
1357 fn test_replace_style_layer_affects_queries() {
1358 let mut manager = EditorStateManager::new("Hello", 80);
1359
1360 manager.replace_style_layer(
1361 StyleLayerId::SEMANTIC_TOKENS,
1362 vec![Interval::new(0, 1, 100)],
1363 );
1364
1365 assert_eq!(manager.get_styles_at(0), vec![100]);
1366
1367 manager
1369 .editor_mut()
1370 .interval_tree
1371 .insert(Interval::new(0, 5, 1));
1372
1373 assert_eq!(manager.get_styles_at(0), vec![1, 100]);
1374 }
1375
1376 #[test]
1377 fn test_viewport_content_styled_wraps_and_includes_styles() {
1378 let mut manager = EditorStateManager::new("abcdef", 3);
1379
1380 manager.replace_style_layer(StyleLayerId::SIMPLE_SYNTAX, vec![Interval::new(1, 4, 7)]);
1382
1383 let grid = manager.get_viewport_content_styled(0, 10);
1384 assert_eq!(grid.actual_line_count(), 2);
1385
1386 let line0 = &grid.lines[0];
1387 assert_eq!(line0.logical_line_index, 0);
1388 assert!(!line0.is_wrapped_part);
1389 assert_eq!(line0.cells.len(), 3);
1390 assert_eq!(line0.cells[0].ch, 'a');
1391 assert_eq!(line0.cells[1].ch, 'b');
1392 assert_eq!(line0.cells[2].ch, 'c');
1393 assert_eq!(line0.cells[0].styles, Vec::<StyleId>::new());
1394 assert_eq!(line0.cells[1].styles, vec![7]);
1395 assert_eq!(line0.cells[2].styles, vec![7]);
1396
1397 let line1 = &grid.lines[1];
1398 assert_eq!(line1.logical_line_index, 0);
1399 assert!(line1.is_wrapped_part);
1400 assert_eq!(line1.cells.len(), 3);
1401 assert_eq!(line1.cells[0].ch, 'd');
1402 assert_eq!(line1.cells[0].styles, vec![7]);
1403 assert_eq!(line1.cells[1].ch, 'e');
1404 assert_eq!(line1.cells[1].styles, Vec::<StyleId>::new());
1405 }
1406
1407 #[test]
1408 fn test_smooth_scroll_state_and_prefetch_lines() {
1409 let mut manager = EditorStateManager::new("a\nb\nc\nd\n", 80);
1410 manager.set_viewport_height(2);
1411 manager.set_scroll_top(1);
1412 manager.set_scroll_sub_row_offset(123);
1413 manager.set_overscan_rows(2);
1414
1415 let smooth = manager.get_smooth_scroll_state();
1416 assert_eq!(
1417 smooth,
1418 SmoothScrollState {
1419 top_visual_row: 1,
1420 sub_row_offset: 123,
1421 overscan_rows: 2
1422 }
1423 );
1424
1425 let viewport = manager.get_viewport_state();
1426 assert_eq!(viewport.visible_lines, 1..3);
1427 assert_eq!(viewport.sub_row_offset, 123);
1428 assert_eq!(viewport.overscan_rows, 2);
1429 assert_eq!(viewport.prefetch_lines, 0..5);
1430 assert_eq!(viewport.total_visual_lines, 5);
1431 }
1432
1433 #[test]
1434 fn test_minimap_content_returns_lightweight_summary() {
1435 let mut manager = EditorStateManager::new("abc def\n", 80);
1436 manager.replace_style_layer(StyleLayerId::SIMPLE_SYNTAX, vec![Interval::new(0, 3, 9)]);
1437
1438 let minimap = manager.get_minimap_content(0, 1);
1439 assert_eq!(minimap.actual_line_count(), 1);
1440 let line = &minimap.lines[0];
1441 assert_eq!(line.logical_line_index, 0);
1442 assert_eq!(line.visual_in_logical, 0);
1443 assert_eq!(line.char_offset_start, 0);
1444 assert_eq!(line.char_offset_end, 7);
1445 assert!(line.total_cells >= line.non_whitespace_cells);
1446 assert_eq!(line.dominant_style, Some(9));
1447 assert!(!line.is_fold_placeholder_appended);
1448 }
1449}