1use crate::delta::TextDelta;
37use crate::intervals::{FoldRegion, Interval, StyleId, StyleLayerId};
38use crate::processing::{DocumentProcessor, ProcessingEdit};
39use crate::snapshot::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 visible_lines: Range<usize>,
92}
93
94#[derive(Debug, Clone)]
96pub struct UndoRedoState {
97 pub can_undo: bool,
99 pub can_redo: bool,
101 pub undo_depth: usize,
103 pub redo_depth: usize,
105 pub current_change_group: Option<usize>,
107}
108
109#[derive(Debug, Clone)]
111pub struct FoldingState {
112 pub regions: Vec<FoldRegion>,
114 pub collapsed_line_count: usize,
116 pub visible_logical_lines: usize,
118 pub total_visual_lines: usize,
120}
121
122#[derive(Debug, Clone)]
124pub struct DiagnosticsState {
125 pub diagnostics_count: usize,
127}
128
129#[derive(Debug, Clone)]
131pub struct DecorationsState {
132 pub layer_count: usize,
134 pub decoration_count: usize,
136}
137
138#[derive(Debug, Clone)]
140pub struct StyleState {
141 pub style_count: usize,
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum StateChangeType {
148 DocumentModified,
150 CursorMoved,
152 SelectionChanged,
154 ViewportChanged,
156 FoldingChanged,
158 StyleChanged,
160 DecorationsChanged,
162 DiagnosticsChanged,
164}
165
166#[derive(Debug, Clone)]
168pub struct StateChange {
169 pub change_type: StateChangeType,
171 pub old_version: u64,
173 pub new_version: u64,
175 pub affected_region: Option<Range<usize>>,
177 pub text_delta: Option<Arc<TextDelta>>,
179}
180
181impl StateChange {
182 pub fn new(change_type: StateChangeType, old_version: u64, new_version: u64) -> Self {
184 Self {
185 change_type,
186 old_version,
187 new_version,
188 affected_region: None,
189 text_delta: None,
190 }
191 }
192
193 pub fn with_region(mut self, region: Range<usize>) -> Self {
195 self.affected_region = Some(region);
196 self
197 }
198
199 pub fn with_text_delta(mut self, delta: Arc<TextDelta>) -> Self {
201 self.text_delta = Some(delta);
202 self
203 }
204}
205
206#[derive(Debug, Clone)]
208pub struct EditorState {
209 pub document: DocumentState,
211 pub cursor: CursorState,
213 pub viewport: ViewportState,
215 pub undo_redo: UndoRedoState,
217 pub folding: FoldingState,
219 pub diagnostics: DiagnosticsState,
221 pub decorations: DecorationsState,
223 pub style: StyleState,
225}
226
227pub type StateChangeCallback = Box<dyn FnMut(&StateChange) + Send>;
229
230pub struct EditorStateManager {
277 executor: CommandExecutor,
279 state_version: u64,
281 is_modified: bool,
283 callbacks: Vec<StateChangeCallback>,
285 scroll_top: usize,
287 viewport_height: Option<usize>,
289 last_text_delta: Option<Arc<TextDelta>>,
291}
292
293impl EditorStateManager {
294 pub fn new(text: &str, viewport_width: usize) -> Self {
296 Self {
297 executor: CommandExecutor::new(text, viewport_width),
298 state_version: 0,
299 is_modified: false,
300 callbacks: Vec::new(),
301 scroll_top: 0,
302 viewport_height: None,
303 last_text_delta: None,
304 }
305 }
306
307 pub fn empty(viewport_width: usize) -> Self {
309 Self::new("", viewport_width)
310 }
311
312 pub fn editor(&self) -> &EditorCore {
314 self.executor.editor()
315 }
316
317 pub fn editor_mut(&mut self) -> &mut EditorCore {
319 self.executor.editor_mut()
320 }
321
322 pub fn line_ending(&self) -> LineEnding {
324 self.executor.line_ending()
325 }
326
327 pub fn set_line_ending(&mut self, line_ending: LineEnding) {
329 self.executor.set_line_ending(line_ending);
330 }
331
332 pub fn get_text_for_saving(&self) -> String {
334 let text = self.editor().get_text();
335 self.line_ending().apply_to_text(&text)
336 }
337
338 pub fn execute(&mut self, command: Command) -> Result<CommandResult, CommandError> {
345 let change_type = Self::change_type_for_command(&command);
346 let is_delete_like = matches!(
347 &command,
348 Command::Edit(EditCommand::Backspace | EditCommand::DeleteForward)
349 );
350
351 let cursor_before = self.executor.editor().cursor_position();
353 let selection_before = self.executor.editor().selection().cloned();
354 let secondary_before = self.executor.editor().secondary_selections().to_vec();
355 let viewport_width_before = self.executor.editor().viewport_width;
356 let char_count_before = self.executor.editor().char_count();
357
358 let result = self.executor.execute(command)?;
359 let char_count_after = self.executor.editor().char_count();
360 let delta_present = self.executor.last_text_delta().is_some();
361
362 if let Some(change_type) = change_type {
363 let changed = match change_type {
364 StateChangeType::CursorMoved => {
365 self.executor.editor().cursor_position() != cursor_before
366 || self.executor.editor().secondary_selections()
367 != secondary_before.as_slice()
368 }
369 StateChangeType::SelectionChanged => {
370 self.executor.editor().cursor_position() != cursor_before
371 || self.executor.editor().selection().cloned() != selection_before
372 || self.executor.editor().secondary_selections()
373 != secondary_before.as_slice()
374 }
375 StateChangeType::ViewportChanged => {
376 self.executor.editor().viewport_width != viewport_width_before
377 }
378 StateChangeType::DocumentModified => {
379 if is_delete_like {
382 char_count_after != char_count_before
383 } else {
384 delta_present
385 }
386 }
387 StateChangeType::FoldingChanged
389 | StateChangeType::StyleChanged
390 | StateChangeType::DecorationsChanged
391 | StateChangeType::DiagnosticsChanged => true,
392 };
393
394 if changed {
395 if matches!(change_type, StateChangeType::DocumentModified) {
396 let is_modified = !self.executor.is_clean();
397 let delta = self.executor.take_last_text_delta().map(Arc::new);
398 self.last_text_delta = delta.clone();
399 self.mark_modified_internal(change_type, Some(is_modified), delta);
400 } else {
401 self.mark_modified_internal(change_type, None, None);
402 }
403 }
404 }
405
406 Ok(result)
407 }
408
409 fn change_type_for_command(command: &Command) -> Option<StateChangeType> {
410 match command {
411 Command::Edit(EditCommand::InsertText { text }) if text.is_empty() => None,
412 Command::Edit(EditCommand::Delete { length: 0, .. }) => None,
413 Command::Edit(EditCommand::Replace {
414 length: 0, text, ..
415 }) if text.is_empty() => None,
416 Command::Edit(EditCommand::EndUndoGroup) => None,
417 Command::Edit(_) => Some(StateChangeType::DocumentModified),
418 Command::Cursor(
419 CursorCommand::MoveTo { .. }
420 | CursorCommand::MoveBy { .. }
421 | CursorCommand::MoveVisualBy { .. }
422 | CursorCommand::MoveToVisual { .. }
423 | CursorCommand::MoveToLineStart
424 | CursorCommand::MoveToLineEnd
425 | CursorCommand::MoveToVisualLineStart
426 | CursorCommand::MoveToVisualLineEnd
427 | CursorCommand::MoveGraphemeLeft
428 | CursorCommand::MoveGraphemeRight
429 | CursorCommand::MoveWordLeft
430 | CursorCommand::MoveWordRight,
431 ) => Some(StateChangeType::CursorMoved),
432 Command::Cursor(
433 CursorCommand::SetSelection { .. }
434 | CursorCommand::ExtendSelection { .. }
435 | CursorCommand::ClearSelection
436 | CursorCommand::SetSelections { .. }
437 | CursorCommand::ClearSecondarySelections
438 | CursorCommand::SetRectSelection { .. }
439 | CursorCommand::FindNext { .. }
440 | CursorCommand::FindPrev { .. },
441 ) => Some(StateChangeType::SelectionChanged),
442 Command::View(
443 ViewCommand::SetViewportWidth { .. }
444 | ViewCommand::SetWrapMode { .. }
445 | ViewCommand::SetWrapIndent { .. }
446 | ViewCommand::SetTabWidth { .. },
447 ) => Some(StateChangeType::ViewportChanged),
448 Command::View(
449 ViewCommand::SetTabKeyBehavior { .. }
450 | ViewCommand::ScrollTo { .. }
451 | ViewCommand::GetViewport { .. },
452 ) => None,
453 Command::Style(StyleCommand::AddStyle { .. } | StyleCommand::RemoveStyle { .. }) => {
454 Some(StateChangeType::StyleChanged)
455 }
456 Command::Style(
457 StyleCommand::Fold { .. } | StyleCommand::Unfold { .. } | StyleCommand::UnfoldAll,
458 ) => Some(StateChangeType::FoldingChanged),
459 }
460 }
461
462 pub fn version(&self) -> u64 {
464 self.state_version
465 }
466
467 pub fn set_viewport_height(&mut self, height: usize) {
469 self.viewport_height = Some(height);
470 }
471
472 pub fn set_scroll_top(&mut self, scroll_top: usize) {
474 let old_scroll = self.scroll_top;
475 self.scroll_top = scroll_top;
476
477 if old_scroll != scroll_top {
478 self.notify_change(StateChangeType::ViewportChanged);
479 }
480 }
481
482 pub fn get_full_state(&self) -> EditorState {
484 EditorState {
485 document: self.get_document_state(),
486 cursor: self.get_cursor_state(),
487 viewport: self.get_viewport_state(),
488 undo_redo: self.get_undo_redo_state(),
489 folding: self.get_folding_state(),
490 diagnostics: self.get_diagnostics_state(),
491 decorations: self.get_decorations_state(),
492 style: self.get_style_state(),
493 }
494 }
495
496 pub fn get_document_state(&self) -> DocumentState {
498 let editor = self.executor.editor();
499 DocumentState {
500 line_count: editor.line_count(),
501 char_count: editor.char_count(),
502 byte_count: editor.get_text().len(),
503 is_modified: self.is_modified,
504 version: self.state_version,
505 }
506 }
507
508 pub fn get_cursor_state(&self) -> CursorState {
510 let editor = self.executor.editor();
511 let mut selections: Vec<Selection> =
512 Vec::with_capacity(1 + editor.secondary_selections().len());
513
514 let primary = editor.selection().cloned().unwrap_or(Selection {
515 start: editor.cursor_position(),
516 end: editor.cursor_position(),
517 direction: SelectionDirection::Forward,
518 });
519 selections.push(primary);
520 selections.extend(editor.secondary_selections().iter().cloned());
521
522 let (selections, primary_selection_index) =
523 crate::selection_set::normalize_selections(selections, 0);
524 let primary = selections
525 .get(primary_selection_index)
526 .cloned()
527 .unwrap_or(Selection {
528 start: editor.cursor_position(),
529 end: editor.cursor_position(),
530 direction: SelectionDirection::Forward,
531 });
532
533 let position = primary.end;
534 let offset = editor
535 .line_index
536 .position_to_char_offset(position.line, position.column);
537
538 let selection = if primary.start == primary.end {
539 None
540 } else {
541 Some(primary)
542 };
543
544 let multi_cursors: Vec<Position> = selections
545 .iter()
546 .enumerate()
547 .filter_map(|(idx, sel)| {
548 if idx == primary_selection_index {
549 None
550 } else {
551 Some(sel.end)
552 }
553 })
554 .collect();
555
556 CursorState {
557 position,
558 offset,
559 multi_cursors,
560 selection,
561 selections,
562 primary_selection_index,
563 }
564 }
565
566 pub fn get_viewport_state(&self) -> ViewportState {
568 let editor = self.executor.editor();
569 let total_visual_lines = editor.visual_line_count();
570 let visible_end = if let Some(height) = self.viewport_height {
571 self.scroll_top + height
572 } else {
573 total_visual_lines
574 };
575
576 ViewportState {
577 width: editor.viewport_width,
578 height: self.viewport_height,
579 scroll_top: self.scroll_top,
580 visible_lines: self.scroll_top..visible_end.min(total_visual_lines),
581 }
582 }
583
584 pub fn get_undo_redo_state(&self) -> UndoRedoState {
586 UndoRedoState {
587 can_undo: self.executor.can_undo(),
588 can_redo: self.executor.can_redo(),
589 undo_depth: self.executor.undo_depth(),
590 redo_depth: self.executor.redo_depth(),
591 current_change_group: self.executor.current_change_group(),
592 }
593 }
594
595 pub fn get_folding_state(&self) -> FoldingState {
597 let editor = self.executor.editor();
598 let regions = editor.folding_manager.regions().to_vec();
599 let collapsed_line_count: usize = regions
600 .iter()
601 .filter(|r| r.is_collapsed)
602 .map(|r| r.end_line - r.start_line)
603 .sum();
604
605 let visible_logical_lines = editor.line_count() - collapsed_line_count;
606
607 FoldingState {
608 regions,
609 collapsed_line_count,
610 visible_logical_lines,
611 total_visual_lines: editor.visual_line_count(),
612 }
613 }
614
615 pub fn get_style_state(&self) -> StyleState {
617 let editor = self.executor.editor();
618 let layered_count: usize = editor.style_layers.values().map(|t| t.len()).sum();
619 StyleState {
620 style_count: editor.interval_tree.len() + layered_count,
621 }
622 }
623
624 pub fn get_diagnostics_state(&self) -> DiagnosticsState {
626 let editor = self.executor.editor();
627 DiagnosticsState {
628 diagnostics_count: editor.diagnostics.len(),
629 }
630 }
631
632 pub fn get_decorations_state(&self) -> DecorationsState {
634 let editor = self.executor.editor();
635 let decoration_count: usize = editor.decorations.values().map(|d| d.len()).sum();
636 DecorationsState {
637 layer_count: editor.decorations.len(),
638 decoration_count,
639 }
640 }
641
642 pub fn get_styles_in_range(&self, start: usize, end: usize) -> Vec<(usize, usize, StyleId)> {
644 let editor = self.executor.editor();
645 let mut result: Vec<(usize, usize, StyleId)> = editor
646 .interval_tree
647 .query_range(start, end)
648 .iter()
649 .map(|interval| (interval.start, interval.end, interval.style_id))
650 .collect();
651
652 for tree in editor.style_layers.values() {
653 result.extend(
654 tree.query_range(start, end)
655 .iter()
656 .map(|interval| (interval.start, interval.end, interval.style_id)),
657 );
658 }
659
660 result.sort_unstable_by_key(|(s, e, id)| (*s, *e, *id));
661 result
662 }
663
664 pub fn get_styles_at(&self, offset: usize) -> Vec<StyleId> {
666 let editor = self.executor.editor();
667 let mut styles: Vec<StyleId> = editor
668 .interval_tree
669 .query_point(offset)
670 .iter()
671 .map(|interval| interval.style_id)
672 .collect();
673
674 for tree in editor.style_layers.values() {
675 styles.extend(
676 tree.query_point(offset)
677 .iter()
678 .map(|interval| interval.style_id),
679 );
680 }
681
682 styles.sort_unstable();
683 styles.dedup();
684 styles
685 }
686
687 pub fn replace_style_layer(&mut self, layer: StyleLayerId, intervals: Vec<Interval>) {
692 let editor = self.executor.editor_mut();
693
694 if intervals.is_empty() {
695 editor.style_layers.remove(&layer);
696 self.mark_modified(StateChangeType::StyleChanged);
697 return;
698 }
699
700 let tree = editor.style_layers.entry(layer).or_default();
701 tree.clear();
702
703 for interval in intervals {
704 if interval.start < interval.end {
705 tree.insert(interval);
706 }
707 }
708
709 self.mark_modified(StateChangeType::StyleChanged);
710 }
711
712 pub fn clear_style_layer(&mut self, layer: StyleLayerId) {
714 let editor = self.executor.editor_mut();
715 editor.style_layers.remove(&layer);
716 self.mark_modified(StateChangeType::StyleChanged);
717 }
718
719 pub fn replace_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) {
721 let editor = self.executor.editor_mut();
722 editor.diagnostics = diagnostics;
723 self.mark_modified(StateChangeType::DiagnosticsChanged);
724 }
725
726 pub fn clear_diagnostics(&mut self) {
728 let editor = self.executor.editor_mut();
729 editor.diagnostics.clear();
730 self.mark_modified(StateChangeType::DiagnosticsChanged);
731 }
732
733 pub fn replace_decorations(
735 &mut self,
736 layer: DecorationLayerId,
737 mut decorations: Vec<Decoration>,
738 ) {
739 decorations.sort_unstable_by_key(|d| (d.range.start, d.range.end));
740 let editor = self.executor.editor_mut();
741 editor.decorations.insert(layer, decorations);
742 self.mark_modified(StateChangeType::DecorationsChanged);
743 }
744
745 pub fn clear_decorations(&mut self, layer: DecorationLayerId) {
747 let editor = self.executor.editor_mut();
748 editor.decorations.remove(&layer);
749 self.mark_modified(StateChangeType::DecorationsChanged);
750 }
751
752 pub fn replace_folding_regions(
757 &mut self,
758 mut regions: Vec<FoldRegion>,
759 preserve_collapsed: bool,
760 ) {
761 if preserve_collapsed {
762 let collapsed: HashSet<(usize, usize)> = self
763 .editor()
764 .folding_manager
765 .regions()
766 .iter()
767 .filter(|r| r.is_collapsed)
768 .map(|r| (r.start_line, r.end_line))
769 .collect();
770
771 for region in &mut regions {
772 if collapsed.contains(&(region.start_line, region.end_line)) {
773 region.is_collapsed = true;
774 }
775 }
776 }
777
778 self.editor_mut().folding_manager.replace_regions(regions);
779 self.mark_modified(StateChangeType::FoldingChanged);
780 }
781
782 pub fn clear_folding_regions(&mut self) {
784 self.editor_mut().folding_manager.clear();
785 self.mark_modified(StateChangeType::FoldingChanged);
786 }
787
788 pub fn apply_processing_edits<I>(&mut self, edits: I)
790 where
791 I: IntoIterator<Item = ProcessingEdit>,
792 {
793 for edit in edits {
794 match edit {
795 ProcessingEdit::ReplaceStyleLayer { layer, intervals } => {
796 self.replace_style_layer(layer, intervals);
797 }
798 ProcessingEdit::ClearStyleLayer { layer } => {
799 self.clear_style_layer(layer);
800 }
801 ProcessingEdit::ReplaceFoldingRegions {
802 regions,
803 preserve_collapsed,
804 } => {
805 self.replace_folding_regions(regions, preserve_collapsed);
806 }
807 ProcessingEdit::ClearFoldingRegions => {
808 self.clear_folding_regions();
809 }
810 ProcessingEdit::ReplaceDiagnostics { diagnostics } => {
811 self.replace_diagnostics(diagnostics);
812 }
813 ProcessingEdit::ClearDiagnostics => {
814 self.clear_diagnostics();
815 }
816 ProcessingEdit::ReplaceDecorations { layer, decorations } => {
817 self.replace_decorations(layer, decorations);
818 }
819 ProcessingEdit::ClearDecorations { layer } => {
820 self.clear_decorations(layer);
821 }
822 }
823 }
824 }
825
826 pub fn apply_processor<P>(&mut self, processor: &mut P) -> Result<(), P::Error>
828 where
829 P: DocumentProcessor,
830 {
831 let edits = processor.process(self)?;
832 self.apply_processing_edits(edits);
833 Ok(())
834 }
835
836 pub fn get_viewport_content(&self, start_row: usize, count: usize) -> HeadlessGrid {
838 let editor = self.executor.editor();
839 let text = editor.get_text();
840 let generator = crate::SnapshotGenerator::from_text_with_layout_options(
841 &text,
842 editor.viewport_width,
843 editor.layout_engine.tab_width(),
844 editor.layout_engine.wrap_mode(),
845 editor.layout_engine.wrap_indent(),
846 );
847 generator.get_headless_grid(start_row, count)
848 }
849
850 pub fn get_viewport_content_styled(
855 &self,
856 start_visual_row: usize,
857 count: usize,
858 ) -> HeadlessGrid {
859 self.executor
860 .editor()
861 .get_headless_grid_styled(start_visual_row, count)
862 }
863
864 pub fn subscribe<F>(&mut self, callback: F)
866 where
867 F: FnMut(&StateChange) + Send + 'static,
868 {
869 self.callbacks.push(Box::new(callback));
870 }
871
872 pub fn has_changed_since(&self, version: u64) -> bool {
874 self.state_version > version
875 }
876
877 pub fn mark_modified(&mut self, change_type: StateChangeType) {
879 self.mark_modified_internal(change_type, None, None);
880 }
881
882 fn mark_modified_internal(
883 &mut self,
884 change_type: StateChangeType,
885 is_modified_override: Option<bool>,
886 delta: Option<Arc<TextDelta>>,
887 ) {
888 let old_version = self.state_version;
889 self.state_version += 1;
890
891 if matches!(change_type, StateChangeType::DocumentModified) {
893 self.is_modified = is_modified_override.unwrap_or(true);
894 }
895
896 let mut change = StateChange::new(change_type, old_version, self.state_version);
897 if let Some(delta) = delta {
898 change = change.with_text_delta(delta);
899 }
900 self.notify_callbacks(&change);
901 }
902
903 pub fn mark_saved(&mut self) {
905 self.executor.mark_clean();
906 self.is_modified = false;
907 }
908
909 fn notify_change(&mut self, change_type: StateChangeType) {
911 let change = StateChange::new(change_type, self.state_version, self.state_version);
912 self.notify_callbacks(&change);
913 }
914
915 pub fn last_text_delta(&self) -> Option<&TextDelta> {
917 self.last_text_delta.as_deref()
918 }
919
920 pub fn take_last_text_delta(&mut self) -> Option<Arc<TextDelta>> {
922 self.last_text_delta.take()
923 }
924
925 fn notify_callbacks(&mut self, change: &StateChange) {
927 for callback in &mut self.callbacks {
928 callback(change);
929 }
930 }
931}
932
933#[cfg(test)]
934mod tests {
935 use super::*;
936
937 #[test]
938 fn test_document_state() {
939 let manager = EditorStateManager::new("Hello World\nLine 2", 80);
940 let doc_state = manager.get_document_state();
941
942 assert_eq!(doc_state.line_count, 2);
943 assert_eq!(doc_state.char_count, 18); assert!(!doc_state.is_modified);
945 assert_eq!(doc_state.version, 0);
946 }
947
948 #[test]
949 fn test_cursor_state() {
950 let manager = EditorStateManager::new("Hello World", 80);
951 let cursor_state = manager.get_cursor_state();
952
953 assert_eq!(cursor_state.position, Position::new(0, 0));
954 assert_eq!(cursor_state.offset, 0);
955 assert!(cursor_state.selection.is_none());
956 }
957
958 #[test]
959 fn test_viewport_state() {
960 let mut manager = EditorStateManager::new("Line 1\nLine 2\nLine 3", 80);
961 manager.set_viewport_height(10);
962 manager.set_scroll_top(1);
963
964 let viewport_state = manager.get_viewport_state();
965
966 assert_eq!(viewport_state.width, 80);
967 assert_eq!(viewport_state.height, Some(10));
968 assert_eq!(viewport_state.scroll_top, 1);
969 assert_eq!(viewport_state.visible_lines, 1..3);
970 }
971
972 #[test]
973 fn test_folding_state() {
974 let manager = EditorStateManager::new("Line 1\nLine 2\nLine 3", 80);
975 let folding_state = manager.get_folding_state();
976
977 assert_eq!(folding_state.regions.len(), 0);
978 assert_eq!(folding_state.collapsed_line_count, 0);
979 assert_eq!(folding_state.visible_logical_lines, 3);
980 }
981
982 #[test]
983 fn test_style_state() {
984 let manager = EditorStateManager::new("Hello World", 80);
985 let style_state = manager.get_style_state();
986
987 assert_eq!(style_state.style_count, 0);
988 }
989
990 #[test]
991 fn test_full_state() {
992 let manager = EditorStateManager::new("Test", 80);
993 let full_state = manager.get_full_state();
994
995 assert_eq!(full_state.document.line_count, 1);
996 assert_eq!(full_state.cursor.position, Position::new(0, 0));
997 assert_eq!(full_state.viewport.width, 80);
998 }
999
1000 #[test]
1001 fn test_version_tracking() {
1002 let mut manager = EditorStateManager::new("Test", 80);
1003
1004 assert_eq!(manager.version(), 0);
1005 assert!(!manager.has_changed_since(0));
1006
1007 manager.mark_modified(StateChangeType::DocumentModified);
1008
1009 assert_eq!(manager.version(), 1);
1010 assert!(manager.has_changed_since(0));
1011 assert!(!manager.has_changed_since(1));
1012 }
1013
1014 #[test]
1015 fn test_modification_tracking() {
1016 let mut manager = EditorStateManager::new("Test", 80);
1017
1018 assert!(!manager.get_document_state().is_modified);
1019
1020 manager.mark_modified(StateChangeType::DocumentModified);
1021 assert!(manager.get_document_state().is_modified);
1022
1023 manager.mark_saved();
1024 assert!(!manager.get_document_state().is_modified);
1025 }
1026
1027 #[test]
1028 fn test_undo_redo_state_and_dirty_tracking() {
1029 let mut manager = EditorStateManager::empty(80);
1030
1031 let state = manager.get_undo_redo_state();
1032 assert!(!state.can_undo);
1033 assert!(!state.can_redo);
1034
1035 manager
1036 .execute(Command::Edit(EditCommand::InsertText {
1037 text: "abc".to_string(),
1038 }))
1039 .unwrap();
1040
1041 assert!(manager.get_document_state().is_modified);
1042 let state = manager.get_undo_redo_state();
1043 assert!(state.can_undo);
1044 assert!(!state.can_redo);
1045 assert_eq!(state.undo_depth, 1);
1046
1047 manager.execute(Command::Edit(EditCommand::Undo)).unwrap();
1048 assert!(!manager.get_document_state().is_modified);
1049 let state = manager.get_undo_redo_state();
1050 assert!(!state.can_undo);
1051 assert!(state.can_redo);
1052
1053 manager.execute(Command::Edit(EditCommand::Redo)).unwrap();
1054 assert!(manager.get_document_state().is_modified);
1055 let state = manager.get_undo_redo_state();
1056 assert!(state.can_undo);
1057 assert!(!state.can_redo);
1058 }
1059
1060 #[test]
1061 fn test_insert_tab_undo_restores_clean_state() {
1062 let mut manager = EditorStateManager::empty(80);
1063 assert!(!manager.get_document_state().is_modified);
1064
1065 manager
1066 .execute(Command::Edit(EditCommand::InsertTab))
1067 .unwrap();
1068 assert!(manager.get_document_state().is_modified);
1069
1070 manager.execute(Command::Edit(EditCommand::Undo)).unwrap();
1071 assert!(!manager.get_document_state().is_modified);
1072 }
1073
1074 #[test]
1075 fn test_insert_tab_spaces_undo_restores_clean_state() {
1076 let mut manager = EditorStateManager::empty(80);
1077 manager
1078 .execute(Command::View(ViewCommand::SetTabKeyBehavior {
1079 behavior: crate::TabKeyBehavior::Spaces,
1080 }))
1081 .unwrap();
1082
1083 manager
1084 .execute(Command::Edit(EditCommand::InsertTab))
1085 .unwrap();
1086 assert!(manager.get_document_state().is_modified);
1087
1088 manager.execute(Command::Edit(EditCommand::Undo)).unwrap();
1089 assert!(!manager.get_document_state().is_modified);
1090 }
1091
1092 #[test]
1093 fn test_state_change_callback() {
1094 use std::sync::{Arc, Mutex};
1095
1096 let mut manager = EditorStateManager::new("Test", 80);
1097
1098 let callback_called = Arc::new(Mutex::new(false));
1099 let callback_called_clone = callback_called.clone();
1100
1101 manager.subscribe(move |_change| {
1102 *callback_called_clone.lock().unwrap() = true;
1103 });
1104
1105 manager.mark_modified(StateChangeType::CursorMoved);
1106
1107 assert!(*callback_called.lock().unwrap());
1109 }
1110
1111 #[test]
1112 fn test_execute_cursor_noop_does_not_bump_version() {
1113 let mut manager = EditorStateManager::new("A", 80);
1114 assert_eq!(manager.version(), 0);
1115
1116 manager
1118 .execute(Command::Cursor(CursorCommand::MoveBy {
1119 delta_line: 0,
1120 delta_column: -1,
1121 }))
1122 .unwrap();
1123 assert_eq!(manager.editor().cursor_position(), Position::new(0, 0));
1124 assert_eq!(manager.version(), 0);
1125
1126 manager
1128 .execute(Command::Cursor(CursorCommand::MoveTo {
1129 line: 0,
1130 column: usize::MAX,
1131 }))
1132 .unwrap();
1133 assert_eq!(manager.editor().cursor_position(), Position::new(0, 1));
1134 assert_eq!(manager.version(), 1);
1135
1136 let version_before = manager.version();
1138 manager
1139 .execute(Command::Cursor(CursorCommand::MoveBy {
1140 delta_line: 0,
1141 delta_column: 1,
1142 }))
1143 .unwrap();
1144 assert_eq!(manager.editor().cursor_position(), Position::new(0, 1));
1145 assert_eq!(manager.version(), version_before);
1146 }
1147
1148 #[test]
1149 fn test_viewport_height() {
1150 let mut manager = EditorStateManager::new("Test", 80);
1151
1152 assert_eq!(manager.get_viewport_state().height, None);
1153
1154 manager.set_viewport_height(20);
1155 assert_eq!(manager.get_viewport_state().height, Some(20));
1156 }
1157
1158 #[test]
1159 fn test_scroll_position() {
1160 let mut manager = EditorStateManager::new("Line 1\nLine 2\nLine 3\nLine 4", 80);
1161 manager.set_viewport_height(2);
1162
1163 assert_eq!(manager.get_viewport_state().scroll_top, 0);
1164 assert_eq!(manager.get_viewport_state().visible_lines, 0..2);
1165
1166 manager.set_scroll_top(2);
1167 assert_eq!(manager.get_viewport_state().scroll_top, 2);
1168 assert_eq!(manager.get_viewport_state().visible_lines, 2..4);
1169 }
1170
1171 #[test]
1172 fn test_get_styles() {
1173 let mut manager = EditorStateManager::new("Hello World", 80);
1174
1175 manager
1177 .editor_mut()
1178 .interval_tree
1179 .insert(crate::intervals::Interval::new(0, 5, 1));
1180
1181 let styles = manager.get_styles_in_range(0, 10);
1182 assert_eq!(styles.len(), 1);
1183 assert_eq!(styles[0], (0, 5, 1));
1184
1185 let styles_at = manager.get_styles_at(3);
1186 assert_eq!(styles_at.len(), 1);
1187 assert_eq!(styles_at[0], 1);
1188 }
1189
1190 #[test]
1191 fn test_replace_style_layer_affects_queries() {
1192 let mut manager = EditorStateManager::new("Hello", 80);
1193
1194 manager.replace_style_layer(
1195 StyleLayerId::SEMANTIC_TOKENS,
1196 vec![Interval::new(0, 1, 100)],
1197 );
1198
1199 assert_eq!(manager.get_styles_at(0), vec![100]);
1200
1201 manager
1203 .editor_mut()
1204 .interval_tree
1205 .insert(Interval::new(0, 5, 1));
1206
1207 assert_eq!(manager.get_styles_at(0), vec![1, 100]);
1208 }
1209
1210 #[test]
1211 fn test_viewport_content_styled_wraps_and_includes_styles() {
1212 let mut manager = EditorStateManager::new("abcdef", 3);
1213
1214 manager.replace_style_layer(StyleLayerId::SIMPLE_SYNTAX, vec![Interval::new(1, 4, 7)]);
1216
1217 let grid = manager.get_viewport_content_styled(0, 10);
1218 assert_eq!(grid.actual_line_count(), 2);
1219
1220 let line0 = &grid.lines[0];
1221 assert_eq!(line0.logical_line_index, 0);
1222 assert!(!line0.is_wrapped_part);
1223 assert_eq!(line0.cells.len(), 3);
1224 assert_eq!(line0.cells[0].ch, 'a');
1225 assert_eq!(line0.cells[1].ch, 'b');
1226 assert_eq!(line0.cells[2].ch, 'c');
1227 assert_eq!(line0.cells[0].styles, Vec::<StyleId>::new());
1228 assert_eq!(line0.cells[1].styles, vec![7]);
1229 assert_eq!(line0.cells[2].styles, vec![7]);
1230
1231 let line1 = &grid.lines[1];
1232 assert_eq!(line1.logical_line_index, 0);
1233 assert!(line1.is_wrapped_part);
1234 assert_eq!(line1.cells.len(), 3);
1235 assert_eq!(line1.cells[0].ch, 'd');
1236 assert_eq!(line1.cells[0].styles, vec![7]);
1237 assert_eq!(line1.cells[1].ch, 'e');
1238 assert_eq!(line1.cells[1].styles, Vec::<StyleId>::new());
1239 }
1240}