1use crate::intervals::{FoldRegion, Interval, StyleId, StyleLayerId};
37use crate::processing::{DocumentProcessor, ProcessingEdit};
38use crate::snapshot::HeadlessGrid;
39use crate::{
40 Command, CommandError, CommandExecutor, CommandResult, CursorCommand, EditCommand, EditorCore,
41 Position, Selection, SelectionDirection, StyleCommand, ViewCommand,
42};
43use std::collections::HashSet;
44use std::ops::Range;
45
46#[derive(Debug, Clone)]
48pub struct DocumentState {
49 pub line_count: usize,
51 pub char_count: usize,
53 pub byte_count: usize,
55 pub is_modified: bool,
57 pub version: u64,
59}
60
61#[derive(Debug, Clone)]
63pub struct CursorState {
64 pub position: Position,
66 pub offset: usize,
68 pub multi_cursors: Vec<Position>,
70 pub selection: Option<Selection>,
72 pub selections: Vec<Selection>,
74 pub primary_selection_index: usize,
76}
77
78#[derive(Debug, Clone)]
80pub struct ViewportState {
81 pub width: usize,
83 pub height: Option<usize>,
85 pub scroll_top: usize,
87 pub visible_lines: Range<usize>,
89}
90
91#[derive(Debug, Clone)]
93pub struct UndoRedoState {
94 pub can_undo: bool,
96 pub can_redo: bool,
98 pub undo_depth: usize,
100 pub redo_depth: usize,
102 pub current_change_group: Option<usize>,
104}
105
106#[derive(Debug, Clone)]
108pub struct FoldingState {
109 pub regions: Vec<FoldRegion>,
111 pub collapsed_line_count: usize,
113 pub visible_logical_lines: usize,
115 pub total_visual_lines: usize,
117}
118
119#[derive(Debug, Clone)]
121pub struct StyleState {
122 pub style_count: usize,
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum StateChangeType {
129 DocumentModified,
131 CursorMoved,
133 SelectionChanged,
135 ViewportChanged,
137 FoldingChanged,
139 StyleChanged,
141}
142
143#[derive(Debug, Clone)]
145pub struct StateChange {
146 pub change_type: StateChangeType,
148 pub old_version: u64,
150 pub new_version: u64,
152 pub affected_region: Option<Range<usize>>,
154}
155
156impl StateChange {
157 pub fn new(change_type: StateChangeType, old_version: u64, new_version: u64) -> Self {
159 Self {
160 change_type,
161 old_version,
162 new_version,
163 affected_region: None,
164 }
165 }
166
167 pub fn with_region(mut self, region: Range<usize>) -> Self {
169 self.affected_region = Some(region);
170 self
171 }
172}
173
174#[derive(Debug, Clone)]
176pub struct EditorState {
177 pub document: DocumentState,
179 pub cursor: CursorState,
181 pub viewport: ViewportState,
183 pub undo_redo: UndoRedoState,
185 pub folding: FoldingState,
187 pub style: StyleState,
189}
190
191pub type StateChangeCallback = Box<dyn FnMut(&StateChange) + Send>;
193
194pub struct EditorStateManager {
241 executor: CommandExecutor,
243 state_version: u64,
245 is_modified: bool,
247 callbacks: Vec<StateChangeCallback>,
249 scroll_top: usize,
251 viewport_height: Option<usize>,
253}
254
255impl EditorStateManager {
256 pub fn new(text: &str, viewport_width: usize) -> Self {
258 Self {
259 executor: CommandExecutor::new(text, viewport_width),
260 state_version: 0,
261 is_modified: false,
262 callbacks: Vec::new(),
263 scroll_top: 0,
264 viewport_height: None,
265 }
266 }
267
268 pub fn empty(viewport_width: usize) -> Self {
270 Self::new("", viewport_width)
271 }
272
273 pub fn editor(&self) -> &EditorCore {
275 self.executor.editor()
276 }
277
278 pub fn editor_mut(&mut self) -> &mut EditorCore {
280 self.executor.editor_mut()
281 }
282
283 pub fn execute(&mut self, command: Command) -> Result<CommandResult, CommandError> {
290 let change_type = Self::change_type_for_command(&command);
291 let is_delete_like = matches!(
292 &command,
293 Command::Edit(EditCommand::Backspace | EditCommand::DeleteForward)
294 );
295
296 let cursor_before = self.executor.editor().cursor_position();
298 let selection_before = self.executor.editor().selection().cloned();
299 let secondary_before = self.executor.editor().secondary_selections().to_vec();
300 let viewport_width_before = self.executor.editor().viewport_width;
301 let char_count_before = self.executor.editor().char_count();
302
303 let result = self.executor.execute(command)?;
304 let char_count_after = self.executor.editor().char_count();
305
306 if let Some(change_type) = change_type {
307 let changed = match change_type {
308 StateChangeType::CursorMoved => {
309 self.executor.editor().cursor_position() != cursor_before
310 || self.executor.editor().secondary_selections()
311 != secondary_before.as_slice()
312 }
313 StateChangeType::SelectionChanged => {
314 self.executor.editor().cursor_position() != cursor_before
315 || self.executor.editor().selection().cloned() != selection_before
316 || self.executor.editor().secondary_selections()
317 != secondary_before.as_slice()
318 }
319 StateChangeType::ViewportChanged => {
320 self.executor.editor().viewport_width != viewport_width_before
321 }
322 StateChangeType::DocumentModified => {
323 if is_delete_like {
326 char_count_after != char_count_before
327 } else {
328 true
329 }
330 }
331 StateChangeType::FoldingChanged | StateChangeType::StyleChanged => true,
333 };
334
335 if changed {
336 if matches!(change_type, StateChangeType::DocumentModified) {
337 let is_modified = !self.executor.is_clean();
338 self.mark_modified_internal(change_type, Some(is_modified));
339 } else {
340 self.mark_modified_internal(change_type, None);
341 }
342 }
343 }
344
345 Ok(result)
346 }
347
348 fn change_type_for_command(command: &Command) -> Option<StateChangeType> {
349 match command {
350 Command::Edit(EditCommand::InsertText { text }) if text.is_empty() => None,
351 Command::Edit(EditCommand::Delete { length: 0, .. }) => None,
352 Command::Edit(EditCommand::Replace {
353 length: 0, text, ..
354 }) if text.is_empty() => None,
355 Command::Edit(EditCommand::EndUndoGroup) => None,
356 Command::Edit(_) => Some(StateChangeType::DocumentModified),
357 Command::Cursor(CursorCommand::MoveTo { .. } | CursorCommand::MoveBy { .. }) => {
358 Some(StateChangeType::CursorMoved)
359 }
360 Command::Cursor(
361 CursorCommand::SetSelection { .. }
362 | CursorCommand::ExtendSelection { .. }
363 | CursorCommand::ClearSelection
364 | CursorCommand::SetSelections { .. }
365 | CursorCommand::ClearSecondarySelections
366 | CursorCommand::SetRectSelection { .. }
367 | CursorCommand::FindNext { .. }
368 | CursorCommand::FindPrev { .. },
369 ) => Some(StateChangeType::SelectionChanged),
370 Command::View(
371 ViewCommand::SetViewportWidth { .. } | ViewCommand::SetTabWidth { .. },
372 ) => Some(StateChangeType::ViewportChanged),
373 Command::View(
374 ViewCommand::SetTabKeyBehavior { .. }
375 | ViewCommand::ScrollTo { .. }
376 | ViewCommand::GetViewport { .. },
377 ) => None,
378 Command::Style(StyleCommand::AddStyle { .. } | StyleCommand::RemoveStyle { .. }) => {
379 Some(StateChangeType::StyleChanged)
380 }
381 Command::Style(
382 StyleCommand::Fold { .. } | StyleCommand::Unfold { .. } | StyleCommand::UnfoldAll,
383 ) => Some(StateChangeType::FoldingChanged),
384 }
385 }
386
387 pub fn version(&self) -> u64 {
389 self.state_version
390 }
391
392 pub fn set_viewport_height(&mut self, height: usize) {
394 self.viewport_height = Some(height);
395 }
396
397 pub fn set_scroll_top(&mut self, scroll_top: usize) {
399 let old_scroll = self.scroll_top;
400 self.scroll_top = scroll_top;
401
402 if old_scroll != scroll_top {
403 self.notify_change(StateChangeType::ViewportChanged);
404 }
405 }
406
407 pub fn get_full_state(&self) -> EditorState {
409 EditorState {
410 document: self.get_document_state(),
411 cursor: self.get_cursor_state(),
412 viewport: self.get_viewport_state(),
413 undo_redo: self.get_undo_redo_state(),
414 folding: self.get_folding_state(),
415 style: self.get_style_state(),
416 }
417 }
418
419 pub fn get_document_state(&self) -> DocumentState {
421 let editor = self.executor.editor();
422 DocumentState {
423 line_count: editor.line_count(),
424 char_count: editor.char_count(),
425 byte_count: editor.get_text().len(),
426 is_modified: self.is_modified,
427 version: self.state_version,
428 }
429 }
430
431 pub fn get_cursor_state(&self) -> CursorState {
433 let editor = self.executor.editor();
434 let mut selections: Vec<Selection> =
435 Vec::with_capacity(1 + editor.secondary_selections().len());
436
437 let primary = editor.selection().cloned().unwrap_or(Selection {
438 start: editor.cursor_position(),
439 end: editor.cursor_position(),
440 direction: SelectionDirection::Forward,
441 });
442 selections.push(primary);
443 selections.extend(editor.secondary_selections().iter().cloned());
444
445 let (selections, primary_selection_index) =
446 crate::selection_set::normalize_selections(selections, 0);
447 let primary = selections
448 .get(primary_selection_index)
449 .cloned()
450 .unwrap_or(Selection {
451 start: editor.cursor_position(),
452 end: editor.cursor_position(),
453 direction: SelectionDirection::Forward,
454 });
455
456 let position = primary.end;
457 let offset = editor
458 .line_index
459 .position_to_char_offset(position.line, position.column);
460
461 let selection = if primary.start == primary.end {
462 None
463 } else {
464 Some(primary)
465 };
466
467 let multi_cursors: Vec<Position> = selections
468 .iter()
469 .enumerate()
470 .filter_map(|(idx, sel)| {
471 if idx == primary_selection_index {
472 None
473 } else {
474 Some(sel.end)
475 }
476 })
477 .collect();
478
479 CursorState {
480 position,
481 offset,
482 multi_cursors,
483 selection,
484 selections,
485 primary_selection_index,
486 }
487 }
488
489 pub fn get_viewport_state(&self) -> ViewportState {
491 let editor = self.executor.editor();
492 let total_visual_lines = editor.visual_line_count();
493 let visible_end = if let Some(height) = self.viewport_height {
494 self.scroll_top + height
495 } else {
496 total_visual_lines
497 };
498
499 ViewportState {
500 width: editor.viewport_width,
501 height: self.viewport_height,
502 scroll_top: self.scroll_top,
503 visible_lines: self.scroll_top..visible_end.min(total_visual_lines),
504 }
505 }
506
507 pub fn get_undo_redo_state(&self) -> UndoRedoState {
509 UndoRedoState {
510 can_undo: self.executor.can_undo(),
511 can_redo: self.executor.can_redo(),
512 undo_depth: self.executor.undo_depth(),
513 redo_depth: self.executor.redo_depth(),
514 current_change_group: self.executor.current_change_group(),
515 }
516 }
517
518 pub fn get_folding_state(&self) -> FoldingState {
520 let editor = self.executor.editor();
521 let regions = editor.folding_manager.regions().to_vec();
522 let collapsed_line_count: usize = regions
523 .iter()
524 .filter(|r| r.is_collapsed)
525 .map(|r| r.end_line - r.start_line)
526 .sum();
527
528 let visible_logical_lines = editor.line_count() - collapsed_line_count;
529
530 FoldingState {
531 regions,
532 collapsed_line_count,
533 visible_logical_lines,
534 total_visual_lines: editor.visual_line_count(),
535 }
536 }
537
538 pub fn get_style_state(&self) -> StyleState {
540 let editor = self.executor.editor();
541 let layered_count: usize = editor.style_layers.values().map(|t| t.len()).sum();
542 StyleState {
543 style_count: editor.interval_tree.len() + layered_count,
544 }
545 }
546
547 pub fn get_styles_in_range(&self, start: usize, end: usize) -> Vec<(usize, usize, StyleId)> {
549 let editor = self.executor.editor();
550 let mut result: Vec<(usize, usize, StyleId)> = editor
551 .interval_tree
552 .query_range(start, end)
553 .iter()
554 .map(|interval| (interval.start, interval.end, interval.style_id))
555 .collect();
556
557 for tree in editor.style_layers.values() {
558 result.extend(
559 tree.query_range(start, end)
560 .iter()
561 .map(|interval| (interval.start, interval.end, interval.style_id)),
562 );
563 }
564
565 result.sort_unstable_by_key(|(s, e, id)| (*s, *e, *id));
566 result
567 }
568
569 pub fn get_styles_at(&self, offset: usize) -> Vec<StyleId> {
571 let editor = self.executor.editor();
572 let mut styles: Vec<StyleId> = editor
573 .interval_tree
574 .query_point(offset)
575 .iter()
576 .map(|interval| interval.style_id)
577 .collect();
578
579 for tree in editor.style_layers.values() {
580 styles.extend(
581 tree.query_point(offset)
582 .iter()
583 .map(|interval| interval.style_id),
584 );
585 }
586
587 styles.sort_unstable();
588 styles.dedup();
589 styles
590 }
591
592 pub fn replace_style_layer(&mut self, layer: StyleLayerId, intervals: Vec<Interval>) {
597 let editor = self.executor.editor_mut();
598
599 if intervals.is_empty() {
600 editor.style_layers.remove(&layer);
601 self.mark_modified(StateChangeType::StyleChanged);
602 return;
603 }
604
605 let tree = editor.style_layers.entry(layer).or_default();
606 tree.clear();
607
608 for interval in intervals {
609 if interval.start < interval.end {
610 tree.insert(interval);
611 }
612 }
613
614 self.mark_modified(StateChangeType::StyleChanged);
615 }
616
617 pub fn clear_style_layer(&mut self, layer: StyleLayerId) {
619 let editor = self.executor.editor_mut();
620 editor.style_layers.remove(&layer);
621 self.mark_modified(StateChangeType::StyleChanged);
622 }
623
624 pub fn replace_folding_regions(
629 &mut self,
630 mut regions: Vec<FoldRegion>,
631 preserve_collapsed: bool,
632 ) {
633 if preserve_collapsed {
634 let collapsed: HashSet<(usize, usize)> = self
635 .editor()
636 .folding_manager
637 .regions()
638 .iter()
639 .filter(|r| r.is_collapsed)
640 .map(|r| (r.start_line, r.end_line))
641 .collect();
642
643 for region in &mut regions {
644 if collapsed.contains(&(region.start_line, region.end_line)) {
645 region.is_collapsed = true;
646 }
647 }
648 }
649
650 self.editor_mut().folding_manager.replace_regions(regions);
651 self.mark_modified(StateChangeType::FoldingChanged);
652 }
653
654 pub fn clear_folding_regions(&mut self) {
656 self.editor_mut().folding_manager.clear();
657 self.mark_modified(StateChangeType::FoldingChanged);
658 }
659
660 pub fn apply_processing_edits<I>(&mut self, edits: I)
662 where
663 I: IntoIterator<Item = ProcessingEdit>,
664 {
665 for edit in edits {
666 match edit {
667 ProcessingEdit::ReplaceStyleLayer { layer, intervals } => {
668 self.replace_style_layer(layer, intervals);
669 }
670 ProcessingEdit::ClearStyleLayer { layer } => {
671 self.clear_style_layer(layer);
672 }
673 ProcessingEdit::ReplaceFoldingRegions {
674 regions,
675 preserve_collapsed,
676 } => {
677 self.replace_folding_regions(regions, preserve_collapsed);
678 }
679 ProcessingEdit::ClearFoldingRegions => {
680 self.clear_folding_regions();
681 }
682 }
683 }
684 }
685
686 pub fn apply_processor<P>(&mut self, processor: &mut P) -> Result<(), P::Error>
688 where
689 P: DocumentProcessor,
690 {
691 let edits = processor.process(self)?;
692 self.apply_processing_edits(edits);
693 Ok(())
694 }
695
696 pub fn get_viewport_content(&self, start_row: usize, count: usize) -> HeadlessGrid {
698 let editor = self.executor.editor();
699 let text = editor.get_text();
700 let generator = crate::SnapshotGenerator::from_text_with_tab_width(
701 &text,
702 editor.viewport_width,
703 editor.layout_engine.tab_width(),
704 );
705 generator.get_headless_grid(start_row, count)
706 }
707
708 pub fn get_viewport_content_styled(
713 &self,
714 start_visual_row: usize,
715 count: usize,
716 ) -> HeadlessGrid {
717 self.executor
718 .editor()
719 .get_headless_grid_styled(start_visual_row, count)
720 }
721
722 pub fn subscribe<F>(&mut self, callback: F)
724 where
725 F: FnMut(&StateChange) + Send + 'static,
726 {
727 self.callbacks.push(Box::new(callback));
728 }
729
730 pub fn has_changed_since(&self, version: u64) -> bool {
732 self.state_version > version
733 }
734
735 pub fn mark_modified(&mut self, change_type: StateChangeType) {
737 self.mark_modified_internal(change_type, None);
738 }
739
740 fn mark_modified_internal(
741 &mut self,
742 change_type: StateChangeType,
743 is_modified_override: Option<bool>,
744 ) {
745 let old_version = self.state_version;
746 self.state_version += 1;
747
748 if matches!(change_type, StateChangeType::DocumentModified) {
750 self.is_modified = is_modified_override.unwrap_or(true);
751 }
752
753 let change = StateChange::new(change_type, old_version, self.state_version);
754 self.notify_callbacks(&change);
755 }
756
757 pub fn mark_saved(&mut self) {
759 self.executor.mark_clean();
760 self.is_modified = false;
761 }
762
763 fn notify_change(&mut self, change_type: StateChangeType) {
765 let change = StateChange::new(change_type, self.state_version, self.state_version);
766 self.notify_callbacks(&change);
767 }
768
769 fn notify_callbacks(&mut self, change: &StateChange) {
771 for callback in &mut self.callbacks {
772 callback(change);
773 }
774 }
775}
776
777#[cfg(test)]
778mod tests {
779 use super::*;
780
781 #[test]
782 fn test_document_state() {
783 let manager = EditorStateManager::new("Hello World\nLine 2", 80);
784 let doc_state = manager.get_document_state();
785
786 assert_eq!(doc_state.line_count, 2);
787 assert_eq!(doc_state.char_count, 18); assert!(!doc_state.is_modified);
789 assert_eq!(doc_state.version, 0);
790 }
791
792 #[test]
793 fn test_cursor_state() {
794 let manager = EditorStateManager::new("Hello World", 80);
795 let cursor_state = manager.get_cursor_state();
796
797 assert_eq!(cursor_state.position, Position::new(0, 0));
798 assert_eq!(cursor_state.offset, 0);
799 assert!(cursor_state.selection.is_none());
800 }
801
802 #[test]
803 fn test_viewport_state() {
804 let mut manager = EditorStateManager::new("Line 1\nLine 2\nLine 3", 80);
805 manager.set_viewport_height(10);
806 manager.set_scroll_top(1);
807
808 let viewport_state = manager.get_viewport_state();
809
810 assert_eq!(viewport_state.width, 80);
811 assert_eq!(viewport_state.height, Some(10));
812 assert_eq!(viewport_state.scroll_top, 1);
813 assert_eq!(viewport_state.visible_lines, 1..3);
814 }
815
816 #[test]
817 fn test_folding_state() {
818 let manager = EditorStateManager::new("Line 1\nLine 2\nLine 3", 80);
819 let folding_state = manager.get_folding_state();
820
821 assert_eq!(folding_state.regions.len(), 0);
822 assert_eq!(folding_state.collapsed_line_count, 0);
823 assert_eq!(folding_state.visible_logical_lines, 3);
824 }
825
826 #[test]
827 fn test_style_state() {
828 let manager = EditorStateManager::new("Hello World", 80);
829 let style_state = manager.get_style_state();
830
831 assert_eq!(style_state.style_count, 0);
832 }
833
834 #[test]
835 fn test_full_state() {
836 let manager = EditorStateManager::new("Test", 80);
837 let full_state = manager.get_full_state();
838
839 assert_eq!(full_state.document.line_count, 1);
840 assert_eq!(full_state.cursor.position, Position::new(0, 0));
841 assert_eq!(full_state.viewport.width, 80);
842 }
843
844 #[test]
845 fn test_version_tracking() {
846 let mut manager = EditorStateManager::new("Test", 80);
847
848 assert_eq!(manager.version(), 0);
849 assert!(!manager.has_changed_since(0));
850
851 manager.mark_modified(StateChangeType::DocumentModified);
852
853 assert_eq!(manager.version(), 1);
854 assert!(manager.has_changed_since(0));
855 assert!(!manager.has_changed_since(1));
856 }
857
858 #[test]
859 fn test_modification_tracking() {
860 let mut manager = EditorStateManager::new("Test", 80);
861
862 assert!(!manager.get_document_state().is_modified);
863
864 manager.mark_modified(StateChangeType::DocumentModified);
865 assert!(manager.get_document_state().is_modified);
866
867 manager.mark_saved();
868 assert!(!manager.get_document_state().is_modified);
869 }
870
871 #[test]
872 fn test_undo_redo_state_and_dirty_tracking() {
873 let mut manager = EditorStateManager::empty(80);
874
875 let state = manager.get_undo_redo_state();
876 assert!(!state.can_undo);
877 assert!(!state.can_redo);
878
879 manager
880 .execute(Command::Edit(EditCommand::InsertText {
881 text: "abc".to_string(),
882 }))
883 .unwrap();
884
885 assert!(manager.get_document_state().is_modified);
886 let state = manager.get_undo_redo_state();
887 assert!(state.can_undo);
888 assert!(!state.can_redo);
889 assert_eq!(state.undo_depth, 1);
890
891 manager.execute(Command::Edit(EditCommand::Undo)).unwrap();
892 assert!(!manager.get_document_state().is_modified);
893 let state = manager.get_undo_redo_state();
894 assert!(!state.can_undo);
895 assert!(state.can_redo);
896
897 manager.execute(Command::Edit(EditCommand::Redo)).unwrap();
898 assert!(manager.get_document_state().is_modified);
899 let state = manager.get_undo_redo_state();
900 assert!(state.can_undo);
901 assert!(!state.can_redo);
902 }
903
904 #[test]
905 fn test_insert_tab_undo_restores_clean_state() {
906 let mut manager = EditorStateManager::empty(80);
907 assert!(!manager.get_document_state().is_modified);
908
909 manager
910 .execute(Command::Edit(EditCommand::InsertTab))
911 .unwrap();
912 assert!(manager.get_document_state().is_modified);
913
914 manager.execute(Command::Edit(EditCommand::Undo)).unwrap();
915 assert!(!manager.get_document_state().is_modified);
916 }
917
918 #[test]
919 fn test_insert_tab_spaces_undo_restores_clean_state() {
920 let mut manager = EditorStateManager::empty(80);
921 manager
922 .execute(Command::View(ViewCommand::SetTabKeyBehavior {
923 behavior: crate::TabKeyBehavior::Spaces,
924 }))
925 .unwrap();
926
927 manager
928 .execute(Command::Edit(EditCommand::InsertTab))
929 .unwrap();
930 assert!(manager.get_document_state().is_modified);
931
932 manager.execute(Command::Edit(EditCommand::Undo)).unwrap();
933 assert!(!manager.get_document_state().is_modified);
934 }
935
936 #[test]
937 fn test_state_change_callback() {
938 use std::sync::{Arc, Mutex};
939
940 let mut manager = EditorStateManager::new("Test", 80);
941
942 let callback_called = Arc::new(Mutex::new(false));
943 let callback_called_clone = callback_called.clone();
944
945 manager.subscribe(move |_change| {
946 *callback_called_clone.lock().unwrap() = true;
947 });
948
949 manager.mark_modified(StateChangeType::CursorMoved);
950
951 assert!(*callback_called.lock().unwrap());
953 }
954
955 #[test]
956 fn test_execute_cursor_noop_does_not_bump_version() {
957 let mut manager = EditorStateManager::new("A", 80);
958 assert_eq!(manager.version(), 0);
959
960 manager
962 .execute(Command::Cursor(CursorCommand::MoveBy {
963 delta_line: 0,
964 delta_column: -1,
965 }))
966 .unwrap();
967 assert_eq!(manager.editor().cursor_position(), Position::new(0, 0));
968 assert_eq!(manager.version(), 0);
969
970 manager
972 .execute(Command::Cursor(CursorCommand::MoveTo {
973 line: 0,
974 column: usize::MAX,
975 }))
976 .unwrap();
977 assert_eq!(manager.editor().cursor_position(), Position::new(0, 1));
978 assert_eq!(manager.version(), 1);
979
980 let version_before = manager.version();
982 manager
983 .execute(Command::Cursor(CursorCommand::MoveBy {
984 delta_line: 0,
985 delta_column: 1,
986 }))
987 .unwrap();
988 assert_eq!(manager.editor().cursor_position(), Position::new(0, 1));
989 assert_eq!(manager.version(), version_before);
990 }
991
992 #[test]
993 fn test_viewport_height() {
994 let mut manager = EditorStateManager::new("Test", 80);
995
996 assert_eq!(manager.get_viewport_state().height, None);
997
998 manager.set_viewport_height(20);
999 assert_eq!(manager.get_viewport_state().height, Some(20));
1000 }
1001
1002 #[test]
1003 fn test_scroll_position() {
1004 let mut manager = EditorStateManager::new("Line 1\nLine 2\nLine 3\nLine 4", 80);
1005 manager.set_viewport_height(2);
1006
1007 assert_eq!(manager.get_viewport_state().scroll_top, 0);
1008 assert_eq!(manager.get_viewport_state().visible_lines, 0..2);
1009
1010 manager.set_scroll_top(2);
1011 assert_eq!(manager.get_viewport_state().scroll_top, 2);
1012 assert_eq!(manager.get_viewport_state().visible_lines, 2..4);
1013 }
1014
1015 #[test]
1016 fn test_get_styles() {
1017 let mut manager = EditorStateManager::new("Hello World", 80);
1018
1019 manager
1021 .editor_mut()
1022 .interval_tree
1023 .insert(crate::intervals::Interval::new(0, 5, 1));
1024
1025 let styles = manager.get_styles_in_range(0, 10);
1026 assert_eq!(styles.len(), 1);
1027 assert_eq!(styles[0], (0, 5, 1));
1028
1029 let styles_at = manager.get_styles_at(3);
1030 assert_eq!(styles_at.len(), 1);
1031 assert_eq!(styles_at[0], 1);
1032 }
1033
1034 #[test]
1035 fn test_replace_style_layer_affects_queries() {
1036 let mut manager = EditorStateManager::new("Hello", 80);
1037
1038 manager.replace_style_layer(
1039 StyleLayerId::SEMANTIC_TOKENS,
1040 vec![Interval::new(0, 1, 100)],
1041 );
1042
1043 assert_eq!(manager.get_styles_at(0), vec![100]);
1044
1045 manager
1047 .editor_mut()
1048 .interval_tree
1049 .insert(Interval::new(0, 5, 1));
1050
1051 assert_eq!(manager.get_styles_at(0), vec![1, 100]);
1052 }
1053
1054 #[test]
1055 fn test_viewport_content_styled_wraps_and_includes_styles() {
1056 let mut manager = EditorStateManager::new("abcdef", 3);
1057
1058 manager.replace_style_layer(StyleLayerId::SIMPLE_SYNTAX, vec![Interval::new(1, 4, 7)]);
1060
1061 let grid = manager.get_viewport_content_styled(0, 10);
1062 assert_eq!(grid.actual_line_count(), 2);
1063
1064 let line0 = &grid.lines[0];
1065 assert_eq!(line0.logical_line_index, 0);
1066 assert!(!line0.is_wrapped_part);
1067 assert_eq!(line0.cells.len(), 3);
1068 assert_eq!(line0.cells[0].ch, 'a');
1069 assert_eq!(line0.cells[1].ch, 'b');
1070 assert_eq!(line0.cells[2].ch, 'c');
1071 assert_eq!(line0.cells[0].styles, Vec::<StyleId>::new());
1072 assert_eq!(line0.cells[1].styles, vec![7]);
1073 assert_eq!(line0.cells[2].styles, vec![7]);
1074
1075 let line1 = &grid.lines[1];
1076 assert_eq!(line1.logical_line_index, 0);
1077 assert!(line1.is_wrapped_part);
1078 assert_eq!(line1.cells.len(), 3);
1079 assert_eq!(line1.cells[0].ch, 'd');
1080 assert_eq!(line1.cells[0].styles, vec![7]);
1081 assert_eq!(line1.cells[1].ch, 'e');
1082 assert_eq!(line1.cells[1].styles, Vec::<StyleId>::new());
1083 }
1084}