1use crate::delta::TextDelta;
39use crate::intervals::{FoldRegion, Interval, StyleId, StyleLayerId};
40use crate::processing::{DocumentProcessor, ProcessingEdit};
41use crate::snapshot::{ComposedGrid, HeadlessGrid};
42use crate::{
43 AnchorBias, Command, CommandError, CommandExecutor, CommandResult, CursorCommand, Decoration,
44 DecorationLayerId, Diagnostic, EditCommand, EditorCore, LineEnding, Position, Selection,
45 SelectionDirection, StyleCommand, TextAnchor, UndoHistoryRestoreError, UndoHistorySnapshot,
46 ViewCommand,
47};
48use std::collections::BTreeMap;
49use std::ops::Range;
50use std::sync::Arc;
51
52#[derive(Debug, Clone)]
54pub struct DocumentState {
55 pub line_count: usize,
57 pub char_count: usize,
59 pub byte_count: usize,
61 pub is_modified: bool,
63 pub version: u64,
65}
66
67#[derive(Debug, Clone)]
69pub struct CursorState {
70 pub position: Position,
72 pub offset: usize,
74 pub multi_cursors: Vec<Position>,
76 pub selection: Option<Selection>,
78 pub selections: Vec<Selection>,
80 pub primary_selection_index: usize,
82}
83
84#[derive(Debug, Clone)]
86pub struct ViewportState {
87 pub width: usize,
89 pub height: Option<usize>,
91 pub scroll_top: usize,
93 pub sub_row_offset: u16,
95 pub overscan_rows: usize,
97 pub visible_lines: Range<usize>,
99 pub prefetch_lines: Range<usize>,
101 pub total_visual_lines: usize,
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub struct SmoothScrollState {
108 pub top_visual_row: usize,
110 pub sub_row_offset: u16,
112 pub overscan_rows: usize,
114}
115
116#[derive(Debug, Clone)]
118pub struct UndoRedoState {
119 pub can_undo: bool,
121 pub can_redo: bool,
123 pub undo_depth: usize,
125 pub redo_depth: usize,
127 pub redo_branch_count: usize,
129 pub selected_redo_branch_index: Option<usize>,
131 pub current_change_group: Option<usize>,
133}
134
135#[derive(Debug, Clone)]
137pub struct FoldingState {
138 pub regions: Vec<FoldRegion>,
140 pub collapsed_line_count: usize,
142 pub visible_logical_lines: usize,
144 pub total_visual_lines: usize,
146}
147
148#[derive(Debug, Clone)]
150pub struct DiagnosticsState {
151 pub diagnostics_count: usize,
153}
154
155#[derive(Debug, Clone)]
157pub struct DecorationsState {
158 pub layer_count: usize,
160 pub decoration_count: usize,
162}
163
164#[derive(Debug, Clone)]
166pub struct StyleState {
167 pub style_count: usize,
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173pub enum StateChangeType {
174 DocumentModified,
176 CursorMoved,
178 SelectionChanged,
180 NavigationChanged,
182 ViewportChanged,
184 FoldingChanged,
186 StyleChanged,
188 DecorationsChanged,
190 DiagnosticsChanged,
192 SymbolsChanged,
194}
195
196#[derive(Debug, Clone)]
198pub struct StateChange {
199 pub change_type: StateChangeType,
201 pub old_version: u64,
203 pub new_version: u64,
205 pub affected_region: Option<Range<usize>>,
207 pub text_delta: Option<Arc<TextDelta>>,
209}
210
211impl StateChange {
212 pub fn new(change_type: StateChangeType, old_version: u64, new_version: u64) -> Self {
214 Self {
215 change_type,
216 old_version,
217 new_version,
218 affected_region: None,
219 text_delta: None,
220 }
221 }
222
223 pub fn with_region(mut self, region: Range<usize>) -> Self {
225 self.affected_region = Some(region);
226 self
227 }
228
229 pub fn with_text_delta(mut self, delta: Arc<TextDelta>) -> Self {
231 self.text_delta = Some(delta);
232 self
233 }
234}
235
236#[derive(Debug, Clone)]
238pub struct EditorState {
239 pub document: DocumentState,
241 pub cursor: CursorState,
243 pub viewport: ViewportState,
245 pub undo_redo: UndoRedoState,
247 pub folding: FoldingState,
249 pub diagnostics: DiagnosticsState,
251 pub decorations: DecorationsState,
253 pub style: StyleState,
255}
256
257pub type StateChangeCallback = Box<dyn FnMut(&StateChange) + Send>;
259
260#[derive(Debug, Default, Clone, PartialEq, Eq)]
261struct BookmarkSet {
262 anchors: Vec<TextAnchor>,
263}
264
265impl BookmarkSet {
266 fn toggle_line_start(&mut self, line_start_offset: usize) -> bool {
267 let anchor = TextAnchor::new(line_start_offset, AnchorBias::Left);
268 match self
269 .anchors
270 .binary_search_by_key(&anchor.offset, |a| a.offset)
271 {
272 Ok(idx) => {
273 self.anchors.remove(idx);
274 false
275 }
276 Err(idx) => {
277 self.anchors.insert(idx, anchor);
278 true
279 }
280 }
281 }
282
283 fn clear(&mut self) {
284 self.anchors.clear();
285 }
286
287 fn apply_delta(&mut self, delta: &TextDelta) {
288 for a in &mut self.anchors {
289 a.apply_delta(delta);
290 }
291 self.anchors.sort_by_key(|a| a.offset);
292 self.anchors.dedup_by_key(|a| a.offset);
293 }
294
295 fn line_numbers(&self, line_index: &crate::LineIndex) -> Vec<usize> {
296 let mut lines: Vec<usize> = self
297 .anchors
298 .iter()
299 .map(|a| line_index.char_offset_to_position(a.offset).0)
300 .collect();
301 lines.sort_unstable();
302 lines.dedup();
303 lines
304 }
305
306 fn next_after_line_start(&self, current_line_start: usize) -> Option<TextAnchor> {
307 self.anchors
308 .iter()
309 .copied()
310 .find(|a| a.offset > current_line_start)
311 .or_else(|| self.anchors.first().copied())
312 }
313
314 fn prev_before_line_start(&self, current_line_start: usize) -> Option<TextAnchor> {
315 self.anchors
316 .iter()
317 .copied()
318 .rfind(|a| a.offset < current_line_start)
319 .or_else(|| self.anchors.last().copied())
320 }
321}
322
323#[derive(Debug, Default, Clone, PartialEq, Eq)]
324struct MarkSet {
325 marks: BTreeMap<String, TextAnchor>,
326}
327
328impl MarkSet {
329 fn set(&mut self, name: String, offset: usize) {
330 self.marks
331 .insert(name, TextAnchor::new(offset, AnchorBias::Right));
332 }
333
334 fn get(&self, name: &str) -> Option<TextAnchor> {
335 self.marks.get(name).copied()
336 }
337
338 fn remove(&mut self, name: &str) -> bool {
339 self.marks.remove(name).is_some()
340 }
341
342 fn clear(&mut self) {
343 self.marks.clear();
344 }
345
346 fn names(&self) -> Vec<String> {
347 self.marks.keys().cloned().collect()
348 }
349
350 fn apply_delta(&mut self, delta: &TextDelta) {
351 for anchor in self.marks.values_mut() {
352 anchor.apply_delta(delta);
353 }
354 }
355}
356
357#[derive(Debug, Default, Clone, PartialEq, Eq)]
358struct JumpList {
359 back: Vec<TextAnchor>,
360 forward: Vec<TextAnchor>,
361 max_len: usize,
362}
363
364impl JumpList {
365 fn new(max_len: usize) -> Self {
366 Self {
367 back: Vec::new(),
368 forward: Vec::new(),
369 max_len: max_len.max(1),
370 }
371 }
372
373 fn record(&mut self, offset: usize) {
374 let anchor = TextAnchor::new(offset, AnchorBias::Right);
375 if self.back.last().is_some_and(|last| *last == anchor) {
376 return;
377 }
378
379 self.back.push(anchor);
380 self.forward.clear();
381
382 if self.back.len() > self.max_len {
383 let overflow = self.back.len() - self.max_len;
384 self.back.drain(0..overflow);
385 }
386 }
387
388 fn back(&mut self, current_offset: usize) -> Option<TextAnchor> {
389 let current = TextAnchor::new(current_offset, AnchorBias::Right);
390 let target = self.back.pop()?;
391 if !self.forward.last().is_some_and(|last| *last == current) {
392 self.forward.push(current);
393 }
394 Some(target)
395 }
396
397 fn forward(&mut self, current_offset: usize) -> Option<TextAnchor> {
398 let current = TextAnchor::new(current_offset, AnchorBias::Right);
399 let target = self.forward.pop()?;
400 if !self.back.last().is_some_and(|last| *last == current) {
401 self.back.push(current);
402 }
403 Some(target)
404 }
405
406 fn clear(&mut self) {
407 self.back.clear();
408 self.forward.clear();
409 }
410
411 fn apply_delta(&mut self, delta: &TextDelta) {
412 for a in self.back.iter_mut().chain(self.forward.iter_mut()) {
413 a.apply_delta(delta);
414 }
415 }
416}
417
418pub struct EditorStateManager {
468 executor: CommandExecutor,
470 state_version: u64,
472 is_modified: bool,
474 callbacks: Vec<StateChangeCallback>,
476 scroll_top: usize,
478 scroll_sub_row_offset: u16,
480 overscan_rows: usize,
482 viewport_height: Option<usize>,
484 last_text_delta: Option<Arc<TextDelta>>,
486 bookmarks: BookmarkSet,
487 marks: MarkSet,
488 jump_list: JumpList,
489}
490
491impl EditorStateManager {
492 pub fn new(text: &str, viewport_width: usize) -> Self {
494 Self {
495 executor: CommandExecutor::new(text, viewport_width),
496 state_version: 0,
497 is_modified: false,
498 callbacks: Vec::new(),
499 scroll_top: 0,
500 scroll_sub_row_offset: 0,
501 overscan_rows: 0,
502 viewport_height: None,
503 last_text_delta: None,
504 bookmarks: BookmarkSet::default(),
505 marks: MarkSet::default(),
506 jump_list: JumpList::new(200),
507 }
508 }
509
510 pub fn empty(viewport_width: usize) -> Self {
512 Self::new("", viewport_width)
513 }
514
515 pub fn editor(&self) -> &EditorCore {
517 self.executor.editor()
518 }
519
520 pub fn editor_mut(&mut self) -> &mut EditorCore {
527 self.executor.editor_mut()
528 }
529
530 pub fn line_ending(&self) -> LineEnding {
532 self.executor.line_ending()
533 }
534
535 pub fn set_line_ending(&mut self, line_ending: LineEnding) {
537 self.executor.set_line_ending(line_ending);
538 }
539
540 pub fn has_active_snippet_session(&self) -> bool {
542 self.executor.has_active_snippet_session()
543 }
544
545 pub fn get_text_for_saving(&self) -> String {
547 let text = self.editor().get_text();
548 self.line_ending().apply_to_text(&text)
549 }
550
551 pub fn execute(&mut self, command: Command) -> Result<CommandResult, CommandError> {
558 let change_type = Self::change_type_for_command(&command);
559 let is_delete_like = matches!(
560 &command,
561 Command::Edit(EditCommand::Backspace | EditCommand::DeleteForward)
562 );
563
564 let cursor_before = self.executor.editor().cursor_position();
566 let selection_before = self.executor.editor().selection().cloned();
567 let secondary_before = self.executor.editor().secondary_selections().to_vec();
568 let viewport_width_before = self.executor.editor().viewport_width();
569 let char_count_before = self.executor.editor().char_count();
570
571 let result = self.executor.execute(command)?;
572 let char_count_after = self.executor.editor().char_count();
573 let delta_present = self.executor.last_text_delta().is_some();
574
575 if let Some(change_type) = change_type {
576 let changed = match change_type {
577 StateChangeType::CursorMoved => {
578 self.executor.editor().cursor_position() != cursor_before
579 || self.executor.editor().secondary_selections()
580 != secondary_before.as_slice()
581 }
582 StateChangeType::SelectionChanged => {
583 self.executor.editor().cursor_position() != cursor_before
584 || self.executor.editor().selection().cloned() != selection_before
585 || self.executor.editor().secondary_selections()
586 != secondary_before.as_slice()
587 }
588 StateChangeType::ViewportChanged => {
589 self.executor.editor().viewport_width() != viewport_width_before
590 }
591 StateChangeType::DocumentModified => {
592 if is_delete_like {
595 char_count_after != char_count_before
596 } else {
597 delta_present
598 }
599 }
600 StateChangeType::NavigationChanged => true,
601 StateChangeType::FoldingChanged
603 | StateChangeType::StyleChanged
604 | StateChangeType::DecorationsChanged
605 | StateChangeType::DiagnosticsChanged
606 | StateChangeType::SymbolsChanged => true,
607 };
608
609 if changed {
610 if matches!(change_type, StateChangeType::DocumentModified) {
611 let is_modified = !self.executor.is_clean();
612 let delta = self.executor.take_last_text_delta().map(Arc::new);
613 if let Some(ref delta) = delta {
614 self.bookmarks.apply_delta(delta);
615 self.marks.apply_delta(delta);
616 self.jump_list.apply_delta(delta);
617 }
618 self.last_text_delta = delta.clone();
619 self.mark_modified_internal(change_type, Some(is_modified), delta);
620 } else {
621 self.mark_modified_internal(change_type, None, None);
622 }
623 }
624 }
625
626 Ok(result)
627 }
628
629 fn change_type_for_command(command: &Command) -> Option<StateChangeType> {
630 match command {
631 Command::Edit(EditCommand::Delete { length: 0, .. }) => None,
632 Command::Edit(EditCommand::Replace {
633 length: 0, text, ..
634 }) if text.is_empty() => None,
635 Command::Edit(EditCommand::EndUndoGroup) => None,
636 Command::Edit(_) => Some(StateChangeType::DocumentModified),
637 Command::Cursor(
638 CursorCommand::MoveTo { .. }
639 | CursorCommand::MoveBy { .. }
640 | CursorCommand::MoveVisualBy { .. }
641 | CursorCommand::MoveToVisual { .. }
642 | CursorCommand::MoveToLineStart
643 | CursorCommand::MoveToLineEnd
644 | CursorCommand::MoveToVisualLineStart
645 | CursorCommand::MoveToVisualLineEnd
646 | CursorCommand::MoveGraphemeLeft
647 | CursorCommand::MoveGraphemeRight
648 | CursorCommand::MoveWordLeft
649 | CursorCommand::MoveWordRight
650 | CursorCommand::MoveToMatchingBracket,
651 ) => Some(StateChangeType::CursorMoved),
652 Command::Cursor(
653 CursorCommand::SetSelection { .. }
654 | CursorCommand::ExtendSelection { .. }
655 | CursorCommand::ClearSelection
656 | CursorCommand::SetSelections { .. }
657 | CursorCommand::ClearSecondarySelections
658 | CursorCommand::SetRectSelection { .. }
659 | CursorCommand::SelectLine
660 | CursorCommand::SelectWord
661 | CursorCommand::ExpandSelection
662 | CursorCommand::ExpandSelectionBy { .. }
663 | CursorCommand::SnippetNextPlaceholder
664 | CursorCommand::SnippetPrevPlaceholder
665 | CursorCommand::AddCursorAbove
666 | CursorCommand::AddCursorBelow
667 | CursorCommand::AddNextOccurrence { .. }
668 | CursorCommand::AddAllOccurrences { .. }
669 | CursorCommand::FindNext { .. }
670 | CursorCommand::FindPrev { .. },
671 ) => Some(StateChangeType::SelectionChanged),
672 Command::View(
673 ViewCommand::SetViewportWidth { .. }
674 | ViewCommand::SetWrapMode { .. }
675 | ViewCommand::SetWrapIndent { .. }
676 | ViewCommand::SetTabWidth { .. },
677 ) => Some(StateChangeType::ViewportChanged),
678 Command::View(
679 ViewCommand::SetTabKeyBehavior { .. }
680 | ViewCommand::SetIndentationConfig { .. }
681 | ViewCommand::SetAutoPairsConfig { .. }
682 | ViewCommand::SetAutoPairsEnabled { .. }
683 | ViewCommand::SetWordBoundaryAsciiBoundaryChars { .. }
684 | ViewCommand::ResetWordBoundaryDefaults
685 | ViewCommand::ScrollTo { .. }
686 | ViewCommand::GetViewport { .. },
687 ) => None,
688 Command::Style(
689 StyleCommand::AddStyle { .. }
690 | StyleCommand::RemoveStyle { .. }
691 | StyleCommand::UpdateBracketMatchHighlights
692 | StyleCommand::ClearBracketMatchHighlights,
693 ) => Some(StateChangeType::StyleChanged),
694 Command::Style(
695 StyleCommand::Fold { .. } | StyleCommand::Unfold { .. } | StyleCommand::UnfoldAll,
696 ) => Some(StateChangeType::FoldingChanged),
697 }
698 }
699
700 pub fn version(&self) -> u64 {
702 self.state_version
703 }
704
705 pub fn set_viewport_height(&mut self, height: usize) {
707 self.viewport_height = Some(height);
708 }
709
710 pub fn set_scroll_top(&mut self, scroll_top: usize) {
712 let old_scroll = self.scroll_top;
713 self.scroll_top = scroll_top;
714
715 if old_scroll != scroll_top {
716 self.notify_change(StateChangeType::ViewportChanged);
717 }
718 }
719
720 pub fn set_scroll_sub_row_offset(&mut self, sub_row_offset: u16) {
722 let old = self.scroll_sub_row_offset;
723 self.scroll_sub_row_offset = sub_row_offset;
724 if old != sub_row_offset {
725 self.notify_change(StateChangeType::ViewportChanged);
726 }
727 }
728
729 pub fn set_overscan_rows(&mut self, overscan_rows: usize) {
731 let old = self.overscan_rows;
732 self.overscan_rows = overscan_rows;
733 if old != overscan_rows {
734 self.notify_change(StateChangeType::ViewportChanged);
735 }
736 }
737
738 pub fn set_smooth_scroll_state(&mut self, state: SmoothScrollState) {
740 let mut changed = false;
741 if self.scroll_top != state.top_visual_row {
742 self.scroll_top = state.top_visual_row;
743 changed = true;
744 }
745 if self.scroll_sub_row_offset != state.sub_row_offset {
746 self.scroll_sub_row_offset = state.sub_row_offset;
747 changed = true;
748 }
749 if self.overscan_rows != state.overscan_rows {
750 self.overscan_rows = state.overscan_rows;
751 changed = true;
752 }
753 if changed {
754 self.notify_change(StateChangeType::ViewportChanged);
755 }
756 }
757
758 pub fn get_smooth_scroll_state(&self) -> SmoothScrollState {
760 SmoothScrollState {
761 top_visual_row: self.scroll_top,
762 sub_row_offset: self.scroll_sub_row_offset,
763 overscan_rows: self.overscan_rows,
764 }
765 }
766
767 pub fn get_full_state(&self) -> EditorState {
769 EditorState {
770 document: self.get_document_state(),
771 cursor: self.get_cursor_state(),
772 viewport: self.get_viewport_state(),
773 undo_redo: self.get_undo_redo_state(),
774 folding: self.get_folding_state(),
775 diagnostics: self.get_diagnostics_state(),
776 decorations: self.get_decorations_state(),
777 style: self.get_style_state(),
778 }
779 }
780
781 pub fn get_document_state(&self) -> DocumentState {
783 let editor = self.executor.editor();
784 DocumentState {
785 line_count: editor.line_count(),
786 char_count: editor.char_count(),
787 byte_count: editor.get_text().len(),
788 is_modified: self.is_modified,
789 version: self.state_version,
790 }
791 }
792
793 pub fn get_cursor_state(&self) -> CursorState {
795 let editor = self.executor.editor();
796 let mut selections: Vec<Selection> =
797 Vec::with_capacity(1 + editor.secondary_selections().len());
798
799 let primary = editor.selection().cloned().unwrap_or(Selection {
800 start: editor.cursor_position(),
801 end: editor.cursor_position(),
802 direction: SelectionDirection::Forward,
803 });
804 selections.push(primary);
805 selections.extend(editor.secondary_selections().iter().cloned());
806
807 let (selections, primary_selection_index) =
808 crate::selection_set::normalize_selections(selections, 0);
809 let primary = selections
810 .get(primary_selection_index)
811 .cloned()
812 .unwrap_or(Selection {
813 start: editor.cursor_position(),
814 end: editor.cursor_position(),
815 direction: SelectionDirection::Forward,
816 });
817
818 let position = primary.end;
819 let offset = editor
820 .line_index()
821 .position_to_char_offset(position.line, position.column);
822
823 let selection = if primary.start == primary.end {
824 None
825 } else {
826 Some(primary)
827 };
828
829 let multi_cursors: Vec<Position> = selections
830 .iter()
831 .enumerate()
832 .filter_map(|(idx, sel)| {
833 if idx == primary_selection_index {
834 None
835 } else {
836 Some(sel.end)
837 }
838 })
839 .collect();
840
841 CursorState {
842 position,
843 offset,
844 multi_cursors,
845 selection,
846 selections,
847 primary_selection_index,
848 }
849 }
850
851 pub fn get_viewport_state(&self) -> ViewportState {
853 let editor = self.executor.editor();
854 let total_visual_lines = editor.visual_line_count();
855 let clamped_top = self.scroll_top.min(total_visual_lines);
856 let visible_end = if let Some(height) = self.viewport_height {
857 clamped_top.saturating_add(height)
858 } else {
859 total_visual_lines
860 };
861 let visible_lines = clamped_top..visible_end.min(total_visual_lines);
862 let prefetch_start = visible_lines.start.saturating_sub(self.overscan_rows);
863 let prefetch_end = visible_lines
864 .end
865 .saturating_add(self.overscan_rows)
866 .min(total_visual_lines);
867
868 ViewportState {
869 width: editor.viewport_width(),
870 height: self.viewport_height,
871 scroll_top: clamped_top,
872 sub_row_offset: self.scroll_sub_row_offset,
873 overscan_rows: self.overscan_rows,
874 visible_lines,
875 prefetch_lines: prefetch_start..prefetch_end,
876 total_visual_lines,
877 }
878 }
879
880 pub fn get_undo_redo_state(&self) -> UndoRedoState {
882 UndoRedoState {
883 can_undo: self.executor.can_undo(),
884 can_redo: self.executor.can_redo(),
885 undo_depth: self.executor.undo_depth(),
886 redo_depth: self.executor.redo_depth(),
887 redo_branch_count: self.executor.redo_branch_count(),
888 selected_redo_branch_index: self.executor.selected_redo_branch_index(),
889 current_change_group: self.executor.current_change_group(),
890 }
891 }
892
893 pub fn get_folding_state(&self) -> FoldingState {
895 let editor = self.executor.editor();
896 let regions = editor.folding_manager().regions().to_vec();
897 let collapsed_line_count: usize = regions
898 .iter()
899 .filter(|r| r.is_collapsed)
900 .map(|r| r.end_line - r.start_line)
901 .sum();
902
903 let visible_logical_lines = editor.line_count() - collapsed_line_count;
904
905 FoldingState {
906 regions,
907 collapsed_line_count,
908 visible_logical_lines,
909 total_visual_lines: editor.visual_line_count(),
910 }
911 }
912
913 pub fn get_style_state(&self) -> StyleState {
915 let editor = self.executor.editor();
916 let layered_count: usize = editor.style_layers().values().map(|t| t.len()).sum();
917 StyleState {
918 style_count: editor.interval_tree().len() + layered_count,
919 }
920 }
921
922 pub fn get_diagnostics_state(&self) -> DiagnosticsState {
924 let editor = self.executor.editor();
925 DiagnosticsState {
926 diagnostics_count: editor.diagnostics().len(),
927 }
928 }
929
930 pub fn get_decorations_state(&self) -> DecorationsState {
932 let editor = self.executor.editor();
933 let decoration_count: usize = editor.decorations().values().map(|d| d.len()).sum();
934 DecorationsState {
935 layer_count: editor.decorations().len(),
936 decoration_count,
937 }
938 }
939
940 pub fn get_styles_in_range(&self, start: usize, end: usize) -> Vec<(usize, usize, StyleId)> {
942 let editor = self.executor.editor();
943 let mut result: Vec<(usize, usize, StyleId)> = editor
944 .interval_tree()
945 .query_range(start, end)
946 .iter()
947 .map(|interval| (interval.start, interval.end, interval.style_id))
948 .collect();
949
950 for tree in editor.style_layers().values() {
951 result.extend(
952 tree.query_range(start, end)
953 .iter()
954 .map(|interval| (interval.start, interval.end, interval.style_id)),
955 );
956 }
957
958 result.sort_unstable_by_key(|(s, e, id)| (*s, *e, *id));
959 result
960 }
961
962 pub fn get_styles_at(&self, offset: usize) -> Vec<StyleId> {
964 let editor = self.executor.editor();
965 let mut styles: Vec<StyleId> = editor
966 .interval_tree()
967 .query_point(offset)
968 .iter()
969 .map(|interval| interval.style_id)
970 .collect();
971
972 for tree in editor.style_layers().values() {
973 styles.extend(
974 tree.query_point(offset)
975 .iter()
976 .map(|interval| interval.style_id),
977 );
978 }
979
980 styles.sort_unstable();
981 styles.dedup();
982 styles
983 }
984
985 pub fn replace_style_layer(&mut self, layer: StyleLayerId, intervals: Vec<Interval>) {
990 self.executor
991 .editor_mut()
992 .replace_style_layer(layer, intervals);
993 self.mark_modified(StateChangeType::StyleChanged);
994 }
995
996 pub fn clear_style_layer(&mut self, layer: StyleLayerId) {
998 self.executor.editor_mut().clear_style_layer(layer);
999 self.mark_modified(StateChangeType::StyleChanged);
1000 }
1001
1002 pub fn replace_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) {
1004 self.executor.editor_mut().replace_diagnostics(diagnostics);
1005 self.mark_modified(StateChangeType::DiagnosticsChanged);
1006 }
1007
1008 pub fn clear_diagnostics(&mut self) {
1010 self.executor.editor_mut().clear_diagnostics();
1011 self.mark_modified(StateChangeType::DiagnosticsChanged);
1012 }
1013
1014 pub fn replace_document_symbols(&mut self, symbols: crate::DocumentOutline) {
1016 self.executor.editor_mut().replace_document_symbols(symbols);
1017 self.mark_modified(StateChangeType::SymbolsChanged);
1018 }
1019
1020 pub fn clear_document_symbols(&mut self) {
1022 self.executor.editor_mut().clear_document_symbols();
1023 self.mark_modified(StateChangeType::SymbolsChanged);
1024 }
1025
1026 pub fn replace_decorations(&mut self, layer: DecorationLayerId, decorations: Vec<Decoration>) {
1028 self.executor
1029 .editor_mut()
1030 .replace_decorations(layer, decorations);
1031 self.mark_modified(StateChangeType::DecorationsChanged);
1032 }
1033
1034 pub fn clear_decorations(&mut self, layer: DecorationLayerId) {
1036 self.executor.editor_mut().clear_decorations(layer);
1037 self.mark_modified(StateChangeType::DecorationsChanged);
1038 }
1039
1040 pub fn replace_folding_regions(&mut self, regions: Vec<FoldRegion>, preserve_collapsed: bool) {
1045 self.executor
1046 .editor_mut()
1047 .replace_folding_regions(regions, preserve_collapsed);
1048 self.mark_modified(StateChangeType::FoldingChanged);
1049 }
1050
1051 pub fn clear_folding_regions(&mut self) {
1053 self.executor.editor_mut().clear_derived_folding_regions();
1054 self.mark_modified(StateChangeType::FoldingChanged);
1055 }
1056
1057 pub fn toggle_fold_at_current_line(&mut self) -> bool {
1059 let line = self.executor.editor().cursor_position().line;
1060 let toggled = self.executor.editor_mut().toggle_fold_at_line(line);
1061 if toggled {
1062 self.mark_modified(StateChangeType::FoldingChanged);
1063 }
1064 toggled
1065 }
1066
1067 pub fn expand_all_folds(&mut self) {
1069 let had_collapsed = self
1070 .executor
1071 .editor()
1072 .folding_manager()
1073 .regions()
1074 .iter()
1075 .any(|region| region.is_collapsed);
1076 self.executor.editor_mut().expand_all_folds();
1077 if had_collapsed {
1078 self.mark_modified(StateChangeType::FoldingChanged);
1079 }
1080 }
1081
1082 pub fn apply_processing_edits<I>(&mut self, edits: I)
1084 where
1085 I: IntoIterator<Item = ProcessingEdit>,
1086 {
1087 for edit in edits {
1088 match edit {
1089 ProcessingEdit::ReplaceStyleLayer { layer, intervals } => {
1090 self.replace_style_layer(layer, intervals);
1091 }
1092 ProcessingEdit::ClearStyleLayer { layer } => {
1093 self.clear_style_layer(layer);
1094 }
1095 ProcessingEdit::ReplaceFoldingRegions {
1096 regions,
1097 preserve_collapsed,
1098 } => {
1099 self.replace_folding_regions(regions, preserve_collapsed);
1100 }
1101 ProcessingEdit::ClearFoldingRegions => {
1102 self.clear_folding_regions();
1103 }
1104 ProcessingEdit::ReplaceDiagnostics { diagnostics } => {
1105 self.replace_diagnostics(diagnostics);
1106 }
1107 ProcessingEdit::ClearDiagnostics => {
1108 self.clear_diagnostics();
1109 }
1110 ProcessingEdit::ReplaceDecorations { layer, decorations } => {
1111 self.replace_decorations(layer, decorations);
1112 }
1113 ProcessingEdit::ClearDecorations { layer } => {
1114 self.clear_decorations(layer);
1115 }
1116 ProcessingEdit::ReplaceDocumentSymbols { symbols } => {
1117 self.replace_document_symbols(symbols);
1118 }
1119 ProcessingEdit::ClearDocumentSymbols => {
1120 self.clear_document_symbols();
1121 }
1122 }
1123 }
1124 }
1125
1126 pub fn apply_processor<P>(&mut self, processor: &mut P) -> Result<(), P::Error>
1128 where
1129 P: DocumentProcessor,
1130 {
1131 let edits = processor.process(self)?;
1132 self.apply_processing_edits(edits);
1133 Ok(())
1134 }
1135
1136 pub fn get_viewport_content(&self, start_row: usize, count: usize) -> HeadlessGrid {
1138 let editor = self.executor.editor();
1139 let text = editor.get_text();
1140 let generator = crate::SnapshotGenerator::from_text_with_layout_options(
1141 &text,
1142 editor.viewport_width(),
1143 editor.layout_engine().tab_width(),
1144 editor.layout_engine().wrap_mode(),
1145 editor.layout_engine().wrap_indent(),
1146 );
1147 generator.get_headless_grid(start_row, count)
1148 }
1149
1150 pub fn get_viewport_content_styled(
1155 &self,
1156 start_visual_row: usize,
1157 count: usize,
1158 ) -> HeadlessGrid {
1159 self.executor
1160 .editor()
1161 .get_headless_grid_styled(start_visual_row, count)
1162 }
1163
1164 pub fn get_minimap_content(&self, start_visual_row: usize, count: usize) -> crate::MinimapGrid {
1166 self.executor
1167 .editor()
1168 .get_minimap_grid(start_visual_row, count)
1169 }
1170
1171 pub fn get_viewport_content_composed(
1176 &self,
1177 start_visual_row: usize,
1178 count: usize,
1179 ) -> ComposedGrid {
1180 self.executor
1181 .editor()
1182 .get_headless_grid_composed(start_visual_row, count)
1183 }
1184
1185 pub fn total_visual_lines(&self) -> usize {
1187 self.executor.editor().visual_line_count()
1188 }
1189
1190 pub fn visual_to_logical_line(&self, visual_row: usize) -> (usize, usize) {
1192 self.executor.editor().visual_to_logical_line(visual_row)
1193 }
1194
1195 pub fn logical_position_to_visual(&self, line: usize, column: usize) -> Option<(usize, usize)> {
1197 self.executor
1198 .editor()
1199 .logical_position_to_visual(line, column)
1200 }
1201
1202 pub fn visual_position_to_logical(
1204 &self,
1205 visual_row: usize,
1206 x_cells: usize,
1207 ) -> Option<Position> {
1208 self.executor
1209 .editor()
1210 .visual_position_to_logical(visual_row, x_cells)
1211 }
1212
1213 pub fn subscribe<F>(&mut self, callback: F)
1215 where
1216 F: FnMut(&StateChange) + Send + 'static,
1217 {
1218 self.callbacks.push(Box::new(callback));
1219 }
1220
1221 pub fn has_changed_since(&self, version: u64) -> bool {
1223 self.state_version > version
1224 }
1225
1226 pub fn mark_modified(&mut self, change_type: StateChangeType) {
1228 self.mark_modified_internal(change_type, None, None);
1229 }
1230
1231 fn mark_modified_internal(
1232 &mut self,
1233 change_type: StateChangeType,
1234 is_modified_override: Option<bool>,
1235 delta: Option<Arc<TextDelta>>,
1236 ) {
1237 let old_version = self.state_version;
1238 self.state_version += 1;
1239
1240 if matches!(change_type, StateChangeType::DocumentModified) {
1242 self.is_modified = is_modified_override.unwrap_or(true);
1243 }
1244
1245 let mut change = StateChange::new(change_type, old_version, self.state_version);
1246 if let Some(delta) = delta {
1247 change = change.with_text_delta(delta);
1248 }
1249 self.notify_callbacks(&change);
1250 }
1251
1252 pub fn mark_saved(&mut self) {
1254 self.executor.mark_clean();
1255 self.is_modified = false;
1256 }
1257
1258 pub fn undo_history_snapshot(&self) -> UndoHistorySnapshot {
1260 self.executor.undo_history_snapshot()
1261 }
1262
1263 pub fn restore_undo_history(
1269 &mut self,
1270 snapshot: UndoHistorySnapshot,
1271 ) -> Result<(), UndoHistoryRestoreError> {
1272 self.last_text_delta = None;
1273 self.executor.restore_undo_history(snapshot)?;
1274 self.is_modified = !self.executor.is_clean();
1275 Ok(())
1276 }
1277
1278 fn notify_change(&mut self, change_type: StateChangeType) {
1280 let change = StateChange::new(change_type, self.state_version, self.state_version);
1281 self.notify_callbacks(&change);
1282 }
1283
1284 pub fn last_text_delta(&self) -> Option<&TextDelta> {
1286 self.last_text_delta.as_deref()
1287 }
1288
1289 pub fn take_last_text_delta(&mut self) -> Option<Arc<TextDelta>> {
1291 self.last_text_delta.take()
1292 }
1293
1294 pub fn toggle_bookmark_at_cursor_line(&mut self) -> bool {
1299 let line = self.executor.editor().cursor_position().line;
1300 let line_start = self
1301 .executor
1302 .editor()
1303 .line_index()
1304 .position_to_char_offset(line, 0);
1305 let added = self.bookmarks.toggle_line_start(line_start);
1306 self.mark_modified_internal(StateChangeType::NavigationChanged, None, None);
1307 added
1308 }
1309
1310 pub fn bookmark_lines(&self) -> Vec<usize> {
1312 self.bookmarks
1313 .line_numbers(self.executor.editor().line_index())
1314 }
1315
1316 pub fn clear_bookmarks(&mut self) {
1318 self.bookmarks.clear();
1319 self.mark_modified_internal(StateChangeType::NavigationChanged, None, None);
1320 }
1321
1322 pub fn goto_next_bookmark(&mut self) -> Result<Option<Position>, CommandError> {
1326 let line = self.executor.editor().cursor_position().line;
1327 let current_line_start = self
1328 .executor
1329 .editor()
1330 .line_index()
1331 .position_to_char_offset(line, 0);
1332
1333 let Some(target) = self.bookmarks.next_after_line_start(current_line_start) else {
1334 return Ok(None);
1335 };
1336
1337 let (line, column) = self
1338 .executor
1339 .editor()
1340 .line_index()
1341 .char_offset_to_position(target.offset);
1342 self.execute(Command::Cursor(CursorCommand::MoveTo { line, column }))?;
1343 let _ = self.execute(Command::Cursor(CursorCommand::ClearSelection))?;
1344 Ok(Some(Position::new(line, column)))
1345 }
1346
1347 pub fn goto_prev_bookmark(&mut self) -> Result<Option<Position>, CommandError> {
1351 let line = self.executor.editor().cursor_position().line;
1352 let current_line_start = self
1353 .executor
1354 .editor()
1355 .line_index()
1356 .position_to_char_offset(line, 0);
1357
1358 let Some(target) = self.bookmarks.prev_before_line_start(current_line_start) else {
1359 return Ok(None);
1360 };
1361
1362 let (line, column) = self
1363 .executor
1364 .editor()
1365 .line_index()
1366 .char_offset_to_position(target.offset);
1367 self.execute(Command::Cursor(CursorCommand::MoveTo { line, column }))?;
1368 let _ = self.execute(Command::Cursor(CursorCommand::ClearSelection))?;
1369 Ok(Some(Position::new(line, column)))
1370 }
1371
1372 pub fn set_mark_at_cursor(&mut self, name: String) -> Result<(), CommandError> {
1374 if name.trim().is_empty() {
1375 return Err(CommandError::Other("Mark name cannot be empty".to_string()));
1376 }
1377
1378 let pos = self.executor.editor().cursor_position();
1379 let offset = self
1380 .executor
1381 .editor()
1382 .line_index()
1383 .position_to_char_offset(pos.line, pos.column);
1384 self.marks.set(name, offset);
1385 self.mark_modified_internal(StateChangeType::NavigationChanged, None, None);
1386 Ok(())
1387 }
1388
1389 pub fn goto_mark(&mut self, name: &str) -> Result<Option<Position>, CommandError> {
1393 let Some(anchor) = self.marks.get(name) else {
1394 return Ok(None);
1395 };
1396 let (line, column) = self
1397 .executor
1398 .editor()
1399 .line_index()
1400 .char_offset_to_position(anchor.offset);
1401 self.execute(Command::Cursor(CursorCommand::MoveTo { line, column }))?;
1402 let _ = self.execute(Command::Cursor(CursorCommand::ClearSelection))?;
1403 Ok(Some(Position::new(line, column)))
1404 }
1405
1406 pub fn clear_mark(&mut self, name: &str) -> bool {
1410 let existed = self.marks.remove(name);
1411 if existed {
1412 self.mark_modified_internal(StateChangeType::NavigationChanged, None, None);
1413 }
1414 existed
1415 }
1416
1417 pub fn mark_names(&self) -> Vec<String> {
1419 self.marks.names()
1420 }
1421
1422 pub fn clear_all_marks(&mut self) {
1424 self.marks.clear();
1425 self.mark_modified_internal(StateChangeType::NavigationChanged, None, None);
1426 }
1427
1428 pub fn push_jump_location(&mut self) {
1433 let pos = self.executor.editor().cursor_position();
1434 let offset = self
1435 .executor
1436 .editor()
1437 .line_index()
1438 .position_to_char_offset(pos.line, pos.column);
1439 self.jump_list.record(offset);
1440 self.mark_modified_internal(StateChangeType::NavigationChanged, None, None);
1441 }
1442
1443 pub fn jump_back(&mut self) -> Result<Option<Position>, CommandError> {
1447 let pos = self.executor.editor().cursor_position();
1448 let current_offset = self
1449 .executor
1450 .editor()
1451 .line_index()
1452 .position_to_char_offset(pos.line, pos.column);
1453
1454 let Some(target) = self.jump_list.back(current_offset) else {
1455 return Ok(None);
1456 };
1457
1458 self.mark_modified_internal(StateChangeType::NavigationChanged, None, None);
1459
1460 let (line, column) = self
1461 .executor
1462 .editor()
1463 .line_index()
1464 .char_offset_to_position(target.offset);
1465 self.execute(Command::Cursor(CursorCommand::MoveTo { line, column }))?;
1466 let _ = self.execute(Command::Cursor(CursorCommand::ClearSelection))?;
1467 Ok(Some(Position::new(line, column)))
1468 }
1469
1470 pub fn jump_forward(&mut self) -> Result<Option<Position>, CommandError> {
1474 let pos = self.executor.editor().cursor_position();
1475 let current_offset = self
1476 .executor
1477 .editor()
1478 .line_index()
1479 .position_to_char_offset(pos.line, pos.column);
1480
1481 let Some(target) = self.jump_list.forward(current_offset) else {
1482 return Ok(None);
1483 };
1484
1485 self.mark_modified_internal(StateChangeType::NavigationChanged, None, None);
1486
1487 let (line, column) = self
1488 .executor
1489 .editor()
1490 .line_index()
1491 .char_offset_to_position(target.offset);
1492 self.execute(Command::Cursor(CursorCommand::MoveTo { line, column }))?;
1493 let _ = self.execute(Command::Cursor(CursorCommand::ClearSelection))?;
1494 Ok(Some(Position::new(line, column)))
1495 }
1496
1497 pub fn clear_jump_list(&mut self) {
1499 self.jump_list.clear();
1500 self.mark_modified_internal(StateChangeType::NavigationChanged, None, None);
1501 }
1502
1503 fn notify_callbacks(&mut self, change: &StateChange) {
1505 for callback in &mut self.callbacks {
1506 callback(change);
1507 }
1508 }
1509}
1510
1511#[cfg(test)]
1512mod tests {
1513 use super::*;
1514
1515 #[test]
1516 fn test_document_state() {
1517 let manager = EditorStateManager::new("Hello World\nLine 2", 80);
1518 let doc_state = manager.get_document_state();
1519
1520 assert_eq!(doc_state.line_count, 2);
1521 assert_eq!(doc_state.char_count, 18); assert!(!doc_state.is_modified);
1523 assert_eq!(doc_state.version, 0);
1524 }
1525
1526 #[test]
1527 fn test_cursor_state() {
1528 let manager = EditorStateManager::new("Hello World", 80);
1529 let cursor_state = manager.get_cursor_state();
1530
1531 assert_eq!(cursor_state.position, Position::new(0, 0));
1532 assert_eq!(cursor_state.offset, 0);
1533 assert!(cursor_state.selection.is_none());
1534 }
1535
1536 #[test]
1537 fn test_viewport_state() {
1538 let mut manager = EditorStateManager::new("Line 1\nLine 2\nLine 3", 80);
1539 manager.set_viewport_height(10);
1540 manager.set_scroll_top(1);
1541
1542 let viewport_state = manager.get_viewport_state();
1543
1544 assert_eq!(viewport_state.width, 80);
1545 assert_eq!(viewport_state.height, Some(10));
1546 assert_eq!(viewport_state.scroll_top, 1);
1547 assert_eq!(viewport_state.visible_lines, 1..3);
1548 }
1549
1550 #[test]
1551 fn test_folding_state() {
1552 let manager = EditorStateManager::new("Line 1\nLine 2\nLine 3", 80);
1553 let folding_state = manager.get_folding_state();
1554
1555 assert_eq!(folding_state.regions.len(), 0);
1556 assert_eq!(folding_state.collapsed_line_count, 0);
1557 assert_eq!(folding_state.visible_logical_lines, 3);
1558 }
1559
1560 #[test]
1561 fn test_style_state() {
1562 let manager = EditorStateManager::new("Hello World", 80);
1563 let style_state = manager.get_style_state();
1564
1565 assert_eq!(style_state.style_count, 0);
1566 }
1567
1568 #[test]
1569 fn test_full_state() {
1570 let manager = EditorStateManager::new("Test", 80);
1571 let full_state = manager.get_full_state();
1572
1573 assert_eq!(full_state.document.line_count, 1);
1574 assert_eq!(full_state.cursor.position, Position::new(0, 0));
1575 assert_eq!(full_state.viewport.width, 80);
1576 }
1577
1578 #[test]
1579 fn test_version_tracking() {
1580 let mut manager = EditorStateManager::new("Test", 80);
1581
1582 assert_eq!(manager.version(), 0);
1583 assert!(!manager.has_changed_since(0));
1584
1585 manager.mark_modified(StateChangeType::DocumentModified);
1586
1587 assert_eq!(manager.version(), 1);
1588 assert!(manager.has_changed_since(0));
1589 assert!(!manager.has_changed_since(1));
1590 }
1591
1592 #[test]
1593 fn test_modification_tracking() {
1594 let mut manager = EditorStateManager::new("Test", 80);
1595
1596 assert!(!manager.get_document_state().is_modified);
1597
1598 manager.mark_modified(StateChangeType::DocumentModified);
1599 assert!(manager.get_document_state().is_modified);
1600
1601 manager.mark_saved();
1602 assert!(!manager.get_document_state().is_modified);
1603 }
1604
1605 #[test]
1606 fn test_undo_redo_state_and_dirty_tracking() {
1607 let mut manager = EditorStateManager::empty(80);
1608
1609 let state = manager.get_undo_redo_state();
1610 assert!(!state.can_undo);
1611 assert!(!state.can_redo);
1612
1613 manager
1614 .execute(Command::Edit(EditCommand::InsertText {
1615 text: "abc".to_string(),
1616 }))
1617 .unwrap();
1618
1619 assert!(manager.get_document_state().is_modified);
1620 let state = manager.get_undo_redo_state();
1621 assert!(state.can_undo);
1622 assert!(!state.can_redo);
1623 assert_eq!(state.undo_depth, 1);
1624
1625 manager.execute(Command::Edit(EditCommand::Undo)).unwrap();
1626 assert!(!manager.get_document_state().is_modified);
1627 let state = manager.get_undo_redo_state();
1628 assert!(!state.can_undo);
1629 assert!(state.can_redo);
1630
1631 manager.execute(Command::Edit(EditCommand::Redo)).unwrap();
1632 assert!(manager.get_document_state().is_modified);
1633 let state = manager.get_undo_redo_state();
1634 assert!(state.can_undo);
1635 assert!(!state.can_redo);
1636 }
1637
1638 #[test]
1639 fn test_insert_tab_undo_restores_clean_state() {
1640 let mut manager = EditorStateManager::empty(80);
1641 assert!(!manager.get_document_state().is_modified);
1642
1643 manager
1644 .execute(Command::Edit(EditCommand::InsertTab))
1645 .unwrap();
1646 assert!(manager.get_document_state().is_modified);
1647
1648 manager.execute(Command::Edit(EditCommand::Undo)).unwrap();
1649 assert!(!manager.get_document_state().is_modified);
1650 }
1651
1652 #[test]
1653 fn test_insert_tab_spaces_undo_restores_clean_state() {
1654 let mut manager = EditorStateManager::empty(80);
1655 manager
1656 .execute(Command::View(ViewCommand::SetTabKeyBehavior {
1657 behavior: crate::TabKeyBehavior::Spaces,
1658 }))
1659 .unwrap();
1660
1661 manager
1662 .execute(Command::Edit(EditCommand::InsertTab))
1663 .unwrap();
1664 assert!(manager.get_document_state().is_modified);
1665
1666 manager.execute(Command::Edit(EditCommand::Undo)).unwrap();
1667 assert!(!manager.get_document_state().is_modified);
1668 }
1669
1670 #[test]
1671 fn test_state_change_callback() {
1672 use std::sync::{Arc, Mutex};
1673
1674 let mut manager = EditorStateManager::new("Test", 80);
1675
1676 let callback_called = Arc::new(Mutex::new(false));
1677 let callback_called_clone = callback_called.clone();
1678
1679 manager.subscribe(move |_change| {
1680 *callback_called_clone.lock().unwrap() = true;
1681 });
1682
1683 manager.mark_modified(StateChangeType::CursorMoved);
1684
1685 assert!(*callback_called.lock().unwrap());
1687 }
1688
1689 #[test]
1690 fn test_execute_cursor_noop_does_not_bump_version() {
1691 let mut manager = EditorStateManager::new("A", 80);
1692 assert_eq!(manager.version(), 0);
1693
1694 manager
1696 .execute(Command::Cursor(CursorCommand::MoveBy {
1697 delta_line: 0,
1698 delta_column: -1,
1699 }))
1700 .unwrap();
1701 assert_eq!(manager.editor().cursor_position(), Position::new(0, 0));
1702 assert_eq!(manager.version(), 0);
1703
1704 manager
1706 .execute(Command::Cursor(CursorCommand::MoveTo {
1707 line: 0,
1708 column: usize::MAX,
1709 }))
1710 .unwrap();
1711 assert_eq!(manager.editor().cursor_position(), Position::new(0, 1));
1712 assert_eq!(manager.version(), 1);
1713
1714 let version_before = manager.version();
1716 manager
1717 .execute(Command::Cursor(CursorCommand::MoveBy {
1718 delta_line: 0,
1719 delta_column: 1,
1720 }))
1721 .unwrap();
1722 assert_eq!(manager.editor().cursor_position(), Position::new(0, 1));
1723 assert_eq!(manager.version(), version_before);
1724 }
1725
1726 #[test]
1727 fn test_viewport_height() {
1728 let mut manager = EditorStateManager::new("Test", 80);
1729
1730 assert_eq!(manager.get_viewport_state().height, None);
1731
1732 manager.set_viewport_height(20);
1733 assert_eq!(manager.get_viewport_state().height, Some(20));
1734 }
1735
1736 #[test]
1737 fn test_scroll_position() {
1738 let mut manager = EditorStateManager::new("Line 1\nLine 2\nLine 3\nLine 4", 80);
1739 manager.set_viewport_height(2);
1740
1741 assert_eq!(manager.get_viewport_state().scroll_top, 0);
1742 assert_eq!(manager.get_viewport_state().visible_lines, 0..2);
1743
1744 manager.set_scroll_top(2);
1745 assert_eq!(manager.get_viewport_state().scroll_top, 2);
1746 assert_eq!(manager.get_viewport_state().visible_lines, 2..4);
1747 }
1748
1749 #[test]
1750 fn test_get_styles() {
1751 let mut manager = EditorStateManager::new("Hello World", 80);
1752
1753 manager
1755 .editor_mut()
1756 .insert_style_interval(crate::intervals::Interval::new(0, 5, 1));
1757
1758 let styles = manager.get_styles_in_range(0, 10);
1759 assert_eq!(styles.len(), 1);
1760 assert_eq!(styles[0], (0, 5, 1));
1761
1762 let styles_at = manager.get_styles_at(3);
1763 assert_eq!(styles_at.len(), 1);
1764 assert_eq!(styles_at[0], 1);
1765 }
1766
1767 #[test]
1768 fn test_replace_style_layer_affects_queries() {
1769 let mut manager = EditorStateManager::new("Hello", 80);
1770
1771 manager.replace_style_layer(
1772 StyleLayerId::SEMANTIC_TOKENS,
1773 vec![Interval::new(0, 1, 100)],
1774 );
1775
1776 assert_eq!(manager.get_styles_at(0), vec![100]);
1777
1778 manager
1780 .editor_mut()
1781 .insert_style_interval(Interval::new(0, 5, 1));
1782
1783 assert_eq!(manager.get_styles_at(0), vec![1, 100]);
1784 }
1785
1786 #[test]
1787 fn test_viewport_content_styled_wraps_and_includes_styles() {
1788 let mut manager = EditorStateManager::new("abcdef", 3);
1789
1790 manager.replace_style_layer(StyleLayerId::SIMPLE_SYNTAX, vec![Interval::new(1, 4, 7)]);
1792
1793 let grid = manager.get_viewport_content_styled(0, 10);
1794 assert_eq!(grid.actual_line_count(), 2);
1795
1796 let line0 = &grid.lines[0];
1797 assert_eq!(line0.logical_line_index, 0);
1798 assert!(!line0.is_wrapped_part);
1799 assert_eq!(line0.cells.len(), 3);
1800 assert_eq!(line0.cells[0].ch, 'a');
1801 assert_eq!(line0.cells[1].ch, 'b');
1802 assert_eq!(line0.cells[2].ch, 'c');
1803 assert_eq!(line0.cells[0].styles, Vec::<StyleId>::new());
1804 assert_eq!(line0.cells[1].styles, vec![7]);
1805 assert_eq!(line0.cells[2].styles, vec![7]);
1806
1807 let line1 = &grid.lines[1];
1808 assert_eq!(line1.logical_line_index, 0);
1809 assert!(line1.is_wrapped_part);
1810 assert_eq!(line1.cells.len(), 3);
1811 assert_eq!(line1.cells[0].ch, 'd');
1812 assert_eq!(line1.cells[0].styles, vec![7]);
1813 assert_eq!(line1.cells[1].ch, 'e');
1814 assert_eq!(line1.cells[1].styles, Vec::<StyleId>::new());
1815 }
1816
1817 #[test]
1818 fn test_smooth_scroll_state_and_prefetch_lines() {
1819 let mut manager = EditorStateManager::new("a\nb\nc\nd\n", 80);
1820 manager.set_viewport_height(2);
1821 manager.set_scroll_top(1);
1822 manager.set_scroll_sub_row_offset(123);
1823 manager.set_overscan_rows(2);
1824
1825 let smooth = manager.get_smooth_scroll_state();
1826 assert_eq!(
1827 smooth,
1828 SmoothScrollState {
1829 top_visual_row: 1,
1830 sub_row_offset: 123,
1831 overscan_rows: 2
1832 }
1833 );
1834
1835 let viewport = manager.get_viewport_state();
1836 assert_eq!(viewport.visible_lines, 1..3);
1837 assert_eq!(viewport.sub_row_offset, 123);
1838 assert_eq!(viewport.overscan_rows, 2);
1839 assert_eq!(viewport.prefetch_lines, 0..5);
1840 assert_eq!(viewport.total_visual_lines, 5);
1841 }
1842
1843 #[test]
1844 fn test_minimap_content_returns_lightweight_summary() {
1845 let mut manager = EditorStateManager::new("abc def\n", 80);
1846 manager.replace_style_layer(StyleLayerId::SIMPLE_SYNTAX, vec![Interval::new(0, 3, 9)]);
1847
1848 let minimap = manager.get_minimap_content(0, 1);
1849 assert_eq!(minimap.actual_line_count(), 1);
1850 let line = &minimap.lines[0];
1851 assert_eq!(line.logical_line_index, 0);
1852 assert_eq!(line.visual_in_logical, 0);
1853 assert_eq!(line.char_offset_start, 0);
1854 assert_eq!(line.char_offset_end, 7);
1855 assert!(line.total_cells >= line.non_whitespace_cells);
1856 assert_eq!(line.dominant_style, Some(9));
1857 assert!(!line.is_fold_placeholder_appended);
1858 }
1859}