1use core::ops::Range;
13
14use std::borrow::Cow;
15
16use crate::bracket::Brackets;
17use crate::buffer::{Buffer, EolFlavor, LoadError, Revision, Snapshot};
18use crate::coords::{Bias, Point};
19use crate::decorations::{
20 DecorationKind, DecorationStore, Diagnostic, DiagnosticsOutcome, Severity, Stickiness,
21 TrackedRange,
22};
23use crate::display_map::{BufferRow, DisplayRow};
24use crate::find::{FindQuery, FindState};
25use std::cell::{Ref, RefCell};
26
27use crate::fold_map::{FoldMap, FoldSet};
28use crate::highlight::{HighlightCache, HighlightEngine, HighlightSpan, SyntaxDef, TokenTheme};
29use crate::history::{GroupingHint, History};
30use crate::movement::{self, ColumnDir, Granularity, Motion};
31use crate::selection::{Selection, SelectionId, SelectionSet};
32use crate::transaction::{apply, Committed, EditOp, TransactionError};
33
34#[derive(Debug)]
36pub struct Document {
37 pub(crate) buffer: Buffer,
38 history: History,
39 pub(crate) selections: SelectionSet,
40 column: Option<ColumnSelection>,
44 highlight: Option<HighlightCache>,
47 brackets: Brackets,
50 decorations: DecorationStore,
54 autoclose: DecorationStore,
67 folds: FoldSet,
72 find: FindState,
77 line_comment: Option<String>,
81 bracket_string_delims: Vec<u8>,
86 bracket_char_delim: Option<u8>,
87 expand_stack: Vec<SelectionSet>,
91 reveal_seq: u64,
100 reveal_mode: RevealMode,
102 observe_changes: bool,
107 changes: Vec<EditOp>,
108 fold_cache: RefCell<FoldMapCache>,
120}
121
122#[derive(Debug)]
125struct FoldMapCache {
126 key: (u64, u64),
127 map: FoldMap,
128}
129
130#[derive(Copy, Clone, Debug, PartialEq, Eq)]
132pub enum RevealMode {
133 Fit,
138 FitForce,
141 Center,
143}
144
145#[derive(Copy, Clone, Debug)]
153struct ColumnSelection {
154 anchor: CellCorner,
155 active: CellCorner,
156}
157
158#[derive(Copy, Clone, Debug)]
161struct CellCorner {
162 row: u32,
163 cell: u32,
164}
165
166impl Document {
167 pub fn new(text: &str) -> Result<Self, LoadError> {
173 let buffer = Buffer::new(text)?;
174 let brackets = Brackets::match_text(&buffer.text());
175 Ok(Self {
176 buffer,
177 history: History::new(),
178 selections: SelectionSet::new(0),
179 column: None,
180 highlight: None,
181 brackets,
182 decorations: DecorationStore::new(),
183 autoclose: DecorationStore::new(),
184 folds: FoldSet::new(),
185 find: FindState::new(),
186 line_comment: None,
187 bracket_string_delims: Vec::new(),
188 bracket_char_delim: None,
189 expand_stack: Vec::new(),
190 reveal_seq: 0,
191 reveal_mode: RevealMode::Fit,
192 observe_changes: false,
193 changes: Vec::new(),
194 fold_cache: RefCell::new(FoldMapCache { key: (u64::MAX, u64::MAX), map: FoldMap::empty() }),
196 })
197 }
198
199 #[must_use]
206 pub fn fold_map(&self) -> Ref<'_, FoldMap> {
207 self.ensure_fold_map();
208 Ref::map(self.fold_cache.borrow(), |c| &c.map)
209 }
210
211 fn ensure_fold_map(&self) {
217 let key = (self.buffer.revision().0, self.folds.generation());
218 if self.fold_cache.borrow().key != key {
219 let map = FoldMap::new(&self.folds, &self.brackets, &self.buffer);
220 *self.fold_cache.borrow_mut() = FoldMapCache { key, map };
221 }
222 }
223
224 fn request_reveal(&mut self, mode: RevealMode) {
228 self.reveal_seq += 1;
229 self.reveal_mode = mode;
230 }
231
232 #[must_use]
234 pub fn reveal_mode(&self) -> RevealMode {
235 self.reveal_mode
236 }
237
238 pub fn set_line_comment(&mut self, prefix: Option<&str>) {
242 self.line_comment = prefix.map(str::to_owned);
243 self.rebuild_brackets();
246 }
247
248 pub fn set_bracket_lexing(&mut self, string_delims: Vec<u8>, char_delim: Option<u8>) {
255 self.bracket_string_delims = string_delims;
256 self.bracket_char_delim = char_delim;
257 self.rebuild_brackets();
258 }
259
260 fn bracket_config(&self) -> crate::bracket::BracketConfig {
262 crate::bracket::BracketConfig {
263 line_comments: self.line_comment.iter().cloned().collect(),
264 string_delims: self.bracket_string_delims.clone(),
265 char_delim: self.bracket_char_delim,
266 }
267 }
268
269 fn rebuild_brackets(&mut self) {
272 let cfg = self.bracket_config();
273 self.brackets = crate::bracket::Brackets::match_text_with(&self.buffer.text(), &cfg);
274 }
275
276 #[must_use]
278 pub fn line_comment(&self) -> Option<&str> {
279 self.line_comment.as_deref()
280 }
281
282 #[must_use]
285 pub fn buffer(&self) -> &Buffer {
286 &self.buffer
287 }
288
289 #[must_use]
291 pub fn selections(&self) -> &SelectionSet {
292 &self.selections
293 }
294
295 pub fn set_selections(&mut self, selections: SelectionSet) {
298 self.reset_transient();
299 self.selections = selections;
300 }
301
302 pub fn move_carets(&mut self, motion: Motion, extend: bool) {
305 self.reset_transient();
306 self.ensure_fold_map();
311 let tab = self.tab_size();
312 let cache = self.fold_cache.borrow();
313 movement::move_selections(&mut self.selections, &self.buffer, &cache.map, tab, motion, extend);
314 }
315
316 pub fn add_caret(&mut self, offset: u32) {
319 self.reset_transient();
320 self.selections.add_caret(offset);
321 }
322
323 pub fn add_next_occurrence(&mut self) {
329 self.reset_transient();
330 let newest = *self.selections.newest();
331 if newest.is_empty() {
332 if let Some((ws, we)) = movement::surrounding_word(&self.buffer, newest.head()) {
335 if ws != we {
336 self.selections.add_selection(ws, we);
337 self.request_reveal(RevealMode::FitForce);
341 }
342 }
343 return;
344 }
345 let needle = self.buffer.slice(newest.start()..newest.end()).into_owned();
348 let len = needle.len() as u32;
349 let taken: Vec<(u32, u32)> =
352 self.selections.all().iter().map(|s| (s.start(), s.end())).collect();
353 let is_taken = |s: u32| {
354 taken.binary_search_by_key(&s, |&(st, _)| st).is_ok_and(|i| taken[i].1 == s + len)
355 };
356 let from = newest.end();
359 let found = scan_from(&self.buffer, &needle, from, self.buffer.len(), &is_taken)
360 .or_else(|| scan_from(&self.buffer, &needle, 0, from, &is_taken));
361 if let Some((start, end)) = found {
362 self.selections.add_selection(start, end);
363 self.request_reveal(RevealMode::FitForce);
366 }
367 }
368
369 pub fn jump_to_bracket(&mut self) {
376 self.reset_transient();
377 let targets: Vec<u32> = self
378 .selections
379 .all()
380 .iter()
381 .map(|s| {
382 let head = s.head();
383 if let Some((b, partner)) = self.brackets.active_pair(head) {
384 if head == b + 1 {
385 partner + 1
386 } else {
387 partner
388 }
389 } else if let Some((_, close)) = self.brackets.enclosing_pair(head) {
390 close
391 } else {
392 head
393 }
394 })
395 .collect();
396 let moved =
397 targets.iter().zip(self.selections.all()).any(|(&t, s)| t != s.head() || !s.is_empty());
398 self.selections = SelectionSet::from_offsets(&targets);
399 for &t in &targets {
400 self.unfold_to_reveal(t); }
402 if moved {
403 self.request_reveal(RevealMode::Fit); }
405 }
406
407 pub fn expand_selection(&mut self) {
413 self.reset_gesture();
414 let before: Vec<(u32, u32)> =
415 self.selections.all().iter().map(|s| (s.start(), s.end())).collect();
416 let ranges: Vec<(u32, u32)> =
417 before.iter().map(|&(s, e)| self.expanded_range(s, e)).collect();
418 if ranges == before {
419 return;
420 }
421 let newest_id = self.selections.newest().id;
422 let newest = self
423 .selections
424 .all()
425 .iter()
426 .position(|s| s.id == newest_id)
427 .expect("the newest selection is in its own set");
428 self.expand_stack.push(self.selections.clone());
429 self.selections = SelectionSet::from_ranges(&ranges, newest);
430 self.request_reveal(RevealMode::Fit); }
432
433 pub fn shrink_selection(&mut self) {
436 self.reset_gesture();
437 if let Some(prev) = self.expand_stack.pop() {
438 self.selections = prev;
439 self.request_reveal(RevealMode::Fit);
440 }
441 }
442
443 fn expanded_range(&self, start: u32, end: u32) -> (u32, u32) {
449 if start == end {
450 if let Some((ws, we)) = movement::surrounding_word(&self.buffer, start) {
451 if ws != we {
452 return (ws, we);
453 }
454 }
455 }
456 if let Some((open, close)) = self.brackets.enclosing_pair_of_range(start, end) {
457 let contents = (crate::row_layout::gap_left_edge(open), close);
458 if contents != (start, end) {
459 return contents;
460 }
461 return (open, close + 1);
462 }
463 (0, self.buffer.len())
464 }
465
466 pub fn add_caret_vertical(&mut self, down: bool) {
476 self.reset_transient();
477 let folds = FoldMap::new(&self.folds, &self.brackets, &self.buffer);
478 let tab = self.tab_size();
479 let delta = if down { 1 } else { -1 };
480 let heads: Vec<u32> = self.selections.all().iter().map(Selection::head).collect();
481 let mut added = false;
482 for head in heads {
483 if let Some(off) = movement::caret_one_display_row(&self.buffer, &folds, tab, head, delta)
484 {
485 self.selections.add_caret(off);
486 added = true;
487 }
488 }
489 if added {
490 self.request_reveal(RevealMode::Fit); }
492 }
493
494 pub fn select_all_occurrences(&mut self) {
503 self.reset_transient();
504 let newest = *self.selections.newest();
505 if newest.is_empty() {
506 let Some((ws, we)) = movement::surrounding_word(&self.buffer, newest.head()) else {
507 return;
508 };
509 if ws == we {
510 return;
511 }
512 self.selections.add_selection(ws, we);
513 }
514 let newest = *self.selections.newest();
515 let existing: Vec<(u32, u32)> =
520 self.selections.all().iter().map(|s| (s.start(), s.end())).collect();
521 let taken: std::collections::HashSet<(u32, u32)> = existing.iter().copied().collect();
522 let occurrences: Vec<(u32, u32)> = {
523 let text = self.buffer.text();
524 let needle = &text[newest.start() as usize..newest.end() as usize];
525 if needle.is_empty() {
526 return;
527 }
528 let len = needle.len() as u32;
529 text.match_indices(needle)
530 .map(|(i, _)| (i as u32, i as u32 + len))
531 .filter(|occ| !taken.contains(occ))
532 .collect()
533 };
534 if occurrences.is_empty() {
535 let head = self.selections.newest().head();
537 self.unfold_to_reveal(head);
538 self.request_reveal(RevealMode::Fit);
539 return;
540 }
541 let wrap = occurrences.partition_point(|&(s, _)| s < newest.end());
547 let reveal = (wrap + occurrences.len() - 1) % occurrences.len();
548 let base = existing.len();
549 let mut ranges = existing;
550 ranges.extend_from_slice(&occurrences);
551 self.selections = SelectionSet::from_ranges(&ranges, base + reveal);
552 let head = self.selections.newest().head();
556 self.unfold_to_reveal(head);
557 self.request_reveal(RevealMode::Fit);
558 }
559
560 pub fn select_find_matches(&mut self) -> bool {
566 let ranges: Vec<(u32, u32)> =
567 self.find_matches_in(0..u32::MAX).map(|(r, _)| (r.start, r.end)).collect();
568 if ranges.is_empty() {
569 return false;
570 }
571 let newest = (self.active_find_match().unwrap_or(0) as usize).min(ranges.len() - 1);
572 self.selections = SelectionSet::from_ranges(&ranges, newest);
573 self.reset_transient(); let head = self.selections.newest().head();
577 self.unfold_to_reveal(head);
578 self.request_reveal(RevealMode::Center); true
580 }
581
582 pub fn collapse_selections(&mut self) {
585 self.reset_transient();
586 self.selections.collapse_to_primary();
587 }
588
589 pub fn select_all(&mut self) {
593 self.reset_transient();
594 let end = self.buffer.len();
595 self.selections.set_single(Selection::from_anchor(SelectionId(0), 0, end));
596 }
597
598 fn reset_transient(&mut self) {
607 self.expand_stack.clear();
608 self.reset_gesture();
609 }
610
611 fn reset_gesture(&mut self) {
615 self.column = None;
616 self.clear_autoclose();
617 self.history.seal();
618 }
619
620 pub(crate) fn add_autoclose_pairs(&mut self, pairs: impl IntoIterator<Item = (u32, u32)>) {
628 self.autoclose.add_sorted_batch(
629 pairs.into_iter().map(|(open, close)| open..close),
630 DecorationKind::AutoClosePair,
631 Stickiness::AlwaysGrows,
632 );
633 }
634
635 pub(crate) fn autoclose_ranges(&self) -> Vec<Range<u32>> {
641 self.autoclose.iter().map(|r| r.range.clone()).collect()
642 }
643
644 pub(crate) fn clear_autoclose(&mut self) {
648 if self.autoclose.is_empty() {
649 return; }
651 self.autoclose.take_matching_in(0..u32::MAX, |_| true);
652 }
653
654 pub(crate) fn validate_autoclose(&mut self) {
660 if self.autoclose.is_empty() {
661 return; }
663 let carets: Vec<u32> = self.selections.all().iter().map(|s| s.head()).collect();
664 let Self { autoclose, buffer, .. } = self;
665 autoclose.take_matching_in(0..u32::MAX, |r| {
666 debug_assert!(
667 matches!(r.kind, DecorationKind::AutoClosePair),
668 "the autoclose store holds only AutoClosePair provenance",
669 );
670 let (start, end) = (r.range.start, r.range.end);
671 let one_line = buffer.offset_to_point(start).row == buffer.offset_to_point(end).row;
672 let occupied = one_line && {
674 let i = carets.partition_point(|&h| h <= start);
675 i < carets.len() && carets[i] <= end
676 };
677 !occupied });
679 }
680
681 pub fn drag_select(&mut self, granularity: Granularity, origin: u32, head: u32) {
687 self.reset_transient();
688 let (anchor, head) = match granularity {
689 Granularity::Char => (origin, head),
690 Granularity::Word => {
691 extend_by_unit(self.word_unit(origin), self.word_unit(head), origin, head)
692 }
693 Granularity::Line => {
694 extend_by_unit(self.line_unit(origin), self.line_unit(head), origin, head)
695 }
696 };
697 self.selections.set_single(Selection::from_anchor(SelectionId(0), anchor, head));
698 }
699
700 fn word_unit(&self, offset: u32) -> (u32, u32) {
702 movement::surrounding_word(&self.buffer, offset).unwrap_or((offset, offset))
703 }
704
705 pub(crate) fn line_unit(&self, offset: u32) -> (u32, u32) {
709 let row = self.buffer.offset_to_point(offset).row;
710 let start = self.buffer.point_to_offset(Point::new(row, 0));
711 let end = if row + 1 < self.buffer.line_count() {
712 self.buffer.point_to_offset(Point::new(row + 1, 0))
713 } else {
714 self.buffer.point_to_offset(Point::new(row, self.buffer.line_len(row)))
715 };
716 (start, end)
717 }
718
719 pub fn column_select(&mut self, dir: ColumnDir) {
730 self.history.seal();
734 self.expand_stack.clear();
735 let folds = FoldMap::new(&self.folds, &self.brackets, &self.buffer);
736 let tab = self.tab_size();
737 let mut col = self.column.unwrap_or_else(|| {
738 let corner = self.caret_corner(&folds, tab);
739 ColumnSelection { anchor: corner, active: corner }
740 });
741 col.active = Self::step_corner(&folds, col.active, dir);
742 self.column = Some(col);
743 self.rebuild_column_box(&folds, tab, col);
744 }
745
746 pub fn column_drag(&mut self, anchor: (u32, u32), active: (u32, u32)) {
756 self.history.seal();
758 self.expand_stack.clear();
759 let folds = FoldMap::new(&self.folds, &self.brackets, &self.buffer);
760 let col = ColumnSelection {
761 anchor: CellCorner { row: anchor.0, cell: anchor.1 },
762 active: CellCorner { row: active.0, cell: active.1 },
763 };
764 self.column = Some(col);
765 self.rebuild_column_box(&folds, self.tab_size(), col);
766 }
767
768 fn caret_corner(&self, folds: &FoldMap, tab: u32) -> CellCorner {
775 let head = self.selections.newest().head();
776 match folds.display_position(&self.buffer, head, tab) {
777 Some(p) => CellCorner {
778 row: folds.to_buffer_row(p.row).0,
779 cell: crate::row_layout::virtual_cell(p.x.cells()),
780 },
781 None => {
782 let p = self.buffer.offset_to_point(head);
783 let layout = folds.row_layout(&self.buffer, BufferRow(p.row), tab);
784 CellCorner { row: p.row, cell: layout.display_cell(p.col) }
785 }
786 }
787 }
788
789 pub fn move_line(&mut self, down: bool) {
794 let sel = *self.selections.newest();
795 let p0 = self.buffer.offset_to_point(sel.start());
796 let p1 = self.buffer.offset_to_point(sel.end());
797 let (r0, r1) = (p0.row, p1.row);
798 let line_count = self.buffer.line_count();
799 let ends_nl = self.buffer.char_before(self.buffer.len()) == Some('\n');
800 let last_movable = if ends_nl { line_count.saturating_sub(2) } else { line_count - 1 };
803 if (down && r1 >= last_movable) || (!down && r0 == 0) {
804 return;
805 }
806 let (a, b) = if down { (r0, r1 + 1) } else { (r0 - 1, r1) };
807 let has_nl = b + 1 < line_count; let region_start = self.buffer.point_to_offset(Point::new(a, 0));
809 let region_end = if has_nl {
810 self.buffer.point_to_offset(Point::new(b + 1, 0))
811 } else {
812 self.buffer.len()
813 };
814 let lines: Vec<Cow<str>> = (a..=b).map(|r| self.buffer.line(r)).collect();
815 let reordered: Vec<Cow<str>> = if down {
816 let mut v = vec![lines[lines.len() - 1].clone()];
818 v.extend_from_slice(&lines[..lines.len() - 1]);
819 v
820 } else {
821 let mut v = lines[1..].to_vec();
823 v.push(lines[0].clone());
824 v
825 };
826 let mut new_text = reordered.join("\n");
827 if has_nl {
828 new_text.push('\n');
829 }
830 let _ = self.edit(vec![EditOp::new(region_start..region_end, &new_text)]);
831 let shift = |r: u32| if down { r + 1 } else { r - 1 };
833 let ns = self.buffer.point_to_offset(Point::new(shift(r0), p0.col));
834 let ne = self.buffer.point_to_offset(Point::new(shift(r1), p1.col));
835 let mut set = SelectionSet::new(0);
836 set.set_single(Selection::from_anchor(SelectionId(0), ns, ne));
837 self.selections = set;
838 self.clear_autoclose();
842 }
843
844 pub fn copy_line(&mut self, down: bool) {
848 let sel = *self.selections.newest();
849 let p0 = self.buffer.offset_to_point(sel.start());
850 let p1 = self.buffer.offset_to_point(sel.end());
851 let (r0, r1) = (p0.row, p1.row);
852 let height = r1 - r0 + 1;
853 let line_count = self.buffer.line_count();
854 let block: String = (r0..=r1).map(|r| self.buffer.line(r)).collect::<Vec<_>>().join("\n");
855 let (at, text) = if !down {
856 (self.buffer.point_to_offset(Point::new(r0, 0)), format!("{block}\n"))
857 } else if r1 + 1 < line_count {
858 (self.buffer.point_to_offset(Point::new(r1 + 1, 0)), format!("{block}\n"))
859 } else {
860 (self.buffer.len(), format!("\n{block}"))
862 };
863 let _ = self.edit(vec![EditOp::new(at..at, &text)]);
864 let (nr0, nr1) = if down { (r0 + height, r1 + height) } else { (r0, r1) };
865 let ns = self.buffer.point_to_offset(Point::new(nr0, p0.col));
866 let ne = self.buffer.point_to_offset(Point::new(nr1, p1.col));
867 let mut set = SelectionSet::new(0);
868 set.set_single(Selection::from_anchor(SelectionId(0), ns, ne));
869 self.selections = set;
870 self.clear_autoclose();
873 }
874
875 fn step_corner(folds: &FoldMap, c: CellCorner, dir: ColumnDir) -> CellCorner {
880 let d = folds.to_display_row(BufferRow(c.row)).index();
881 let row_at = |d: u32| folds.to_buffer_row(DisplayRow(d)).0;
882 match dir {
883 ColumnDir::Up => CellCorner { row: row_at(d.saturating_sub(1)), cell: c.cell },
884 ColumnDir::Down => {
885 CellCorner { row: row_at((d + 1).min(folds.max_display_row().index())), cell: c.cell }
886 }
887 ColumnDir::Left => CellCorner { row: c.row, cell: c.cell.saturating_sub(1) },
888 ColumnDir::Right => CellCorner { row: c.row, cell: c.cell + 1 },
889 }
890 }
891
892 fn rebuild_column_box(&mut self, folds: &FoldMap, tab: u32, col: ColumnSelection) {
900 let da = folds.to_display_row(BufferRow(col.anchor.row)).index();
901 let dv = folds.to_display_row(BufferRow(col.active.row)).index();
902 let (d0, d1) = (da.min(dv), da.max(dv));
903 let mut ranges = Vec::with_capacity((d1 - d0 + 1) as usize);
904 let mut newest = 0;
905 for d in d0..=d1 {
906 let row = folds.to_buffer_row(DisplayRow(d));
907 let anchor_off = folds.hit_row(&self.buffer, row, col.anchor.cell as f32, Bias::Left, tab);
908 let head_off = folds.hit_row(&self.buffer, row, col.active.cell as f32, Bias::Left, tab);
909 if d == dv {
910 newest = ranges.len();
911 }
912 ranges.push((anchor_off, head_off));
913 }
914 self.selections = SelectionSet::from_ranges(&ranges, newest);
915 }
916
917 #[must_use]
921 pub fn text(&self) -> Cow<'_, str> {
922 self.buffer.text()
923 }
924
925 #[must_use]
927 pub fn revision(&self) -> Revision {
928 self.buffer.revision()
929 }
930
931 #[must_use]
934 pub fn snapshot(&self) -> Snapshot {
935 self.buffer.snapshot()
936 }
937
938 pub fn edit(&mut self, ops: Vec<EditOp>) -> Result<Committed, TransactionError> {
945 self.edit_grouped(ops, GroupingHint::discrete())
946 }
947
948 pub fn edit_grouped(
951 &mut self,
952 ops: Vec<EditOp>,
953 hint: GroupingHint,
954 ) -> Result<Committed, TransactionError> {
955 self.column = None; self.expand_stack.clear(); let pre_fold_key = (self.buffer.revision().0, self.folds.generation());
960 let committed = apply(&mut self.buffer, ops)?;
961 if !committed.is_empty() {
962 let bracket_edit = committed
967 .forward_ops()
968 .iter()
969 .chain(committed.inverse_ops())
970 .any(|op| op.text.bytes().any(crate::bracket::is_bracket_byte));
971 self.selections.rebase(committed.patch());
974
975 let tab = self.tab_size();
976 let bracket_cfg = self.bracket_config();
979 let region = rebase_views(
984 &mut Views {
985 highlight: &mut self.highlight,
986 brackets: &mut self.brackets,
987 decorations: &mut self.decorations,
988 autoclose: &mut self.autoclose,
989 folds: &mut self.folds,
990 find: &mut self.find,
991 },
992 &self.buffer,
993 tab,
994 &committed,
995 &bracket_cfg,
996 );
997
998 self.validate_autoclose();
1001 if bracket_edit && !region.is_empty() {
1013 self.reconcile_folds_in(region);
1014 }
1015 {
1024 let mut cache = self.fold_cache.borrow_mut();
1025 if cache.key == pre_fold_key
1026 && self.folds.generation() == pre_fold_key.1
1027 && cache.map.apply_patch(committed.patch(), &self.buffer)
1028 {
1029 cache.key = (self.buffer.revision().0, pre_fold_key.1);
1030 } else {
1031 cache.key = (u64::MAX, u64::MAX);
1032 }
1033 }
1034 if self.observe_changes {
1041 let mut ops = committed.forward_ops().to_vec();
1042 ops.sort_by_key(|op| core::cmp::Reverse(op.range.start));
1043 self.changes.extend(ops);
1044 }
1045 let (patch, forward, inverse) = committed.into_ops();
1051 self.history.record(forward, inverse, hint);
1052 return Ok(Committed::from_patch(patch));
1053 }
1054 Ok(committed)
1055 }
1056
1057 pub fn observe_changes(&mut self, on: bool) {
1064 self.observe_changes = on;
1065 if !on {
1066 self.changes.clear();
1067 }
1068 }
1069
1070 #[must_use]
1076 pub fn drain_changes(&mut self) -> Vec<EditOp> {
1077 core::mem::take(&mut self.changes)
1078 }
1079
1080 pub fn undo(&mut self) -> bool {
1089 self.reset_transient();
1090 let tab = self.tab_size();
1091 let mut caret_home: Option<u32> = None;
1096 let bracket_cfg = self.bracket_config();
1097 let Self {
1098 history, buffer, selections, highlight, brackets, decorations, autoclose, folds, find, ..
1099 } = self;
1100 let mut views = Views { highlight, brackets, decorations, autoclose, folds, find };
1101 let undone = history.undo(buffer, selections, |committed, buffer| {
1102 rebase_views(&mut views, buffer, tab, committed, &bracket_cfg);
1105 if let Some(e) = committed.patch().edits().first() {
1106 caret_home = Some(e.new.start);
1107 }
1108 });
1109 if undone {
1112 self.reconcile_folds();
1113 if let Some(off) = caret_home {
1117 self.selections = SelectionSet::new(off);
1118 }
1119 }
1120 undone
1121 }
1122
1123 pub fn redo(&mut self) -> bool {
1126 self.reset_transient();
1127 let tab = self.tab_size();
1128 let mut caret_home: Option<u32> = None;
1132 let bracket_cfg = self.bracket_config();
1133 let Self {
1134 history, buffer, selections, highlight, brackets, decorations, autoclose, folds, find, ..
1135 } = self;
1136 let mut views = Views { highlight, brackets, decorations, autoclose, folds, find };
1137 let redone = history.redo(buffer, selections, |committed, buffer| {
1138 rebase_views(&mut views, buffer, tab, committed, &bracket_cfg);
1140 if let Some(e) = committed.patch().edits().last() {
1141 caret_home = Some(e.new.end);
1142 }
1143 });
1144 if redone {
1145 self.reconcile_folds();
1146 if let Some(off) = caret_home {
1147 self.selections = SelectionSet::new(off);
1148 }
1149 }
1150 redone
1151 }
1152
1153 #[must_use]
1155 pub fn is_dirty(&self) -> bool {
1156 self.history.is_dirty()
1157 }
1158
1159 pub fn mark_saved(&mut self) {
1161 self.history.mark_saved();
1162 }
1163
1164 #[must_use]
1166 pub fn can_undo(&self) -> bool {
1167 self.history.can_undo()
1168 }
1169
1170 #[must_use]
1173 pub fn can_redo(&self) -> bool {
1174 self.history.can_redo()
1175 }
1176
1177 #[must_use]
1182 pub fn redo_branch_count(&self) -> usize {
1183 self.history.redo_branch_count()
1184 }
1185
1186 pub fn select_redo_branch(&mut self, index: usize) -> bool {
1191 self.history.select_redo_branch(index)
1192 }
1193
1194 pub fn set_undo_limit(&mut self, limit: Option<usize>) {
1199 self.history.set_max_undo(limit);
1200 }
1201
1202 #[must_use]
1205 pub fn undo_depth(&self) -> usize {
1206 self.history.undo_depth()
1207 }
1208
1209 #[must_use]
1211 pub fn serialize(&self, flavor: EolFlavor) -> String {
1212 self.buffer.serialize(flavor)
1213 }
1214
1215 pub fn set_syntax(&mut self, syntax: SyntaxDef, theme: TokenTheme) {
1223 let aim = self.highlight.as_ref().map(HighlightCache::window_aim);
1224 let mut cache = HighlightCache::new(syntax, theme, self.buffer.line_count());
1225 if let Some(aim) = aim {
1226 cache.set_window(aim);
1227 }
1228 self.highlight = Some(cache);
1229 }
1230
1231 pub fn set_theme(&mut self, theme: TokenTheme) {
1236 if let Some(cache) = self.highlight.as_mut() {
1237 cache.set_theme(theme);
1238 }
1239 }
1240
1241 #[must_use]
1246 pub fn decorations(&self) -> &DecorationStore {
1247 &self.decorations
1248 }
1249
1250 pub fn decorations_mut(&mut self) -> &mut DecorationStore {
1252 &mut self.decorations
1253 }
1254
1255 #[cfg(test)]
1258 #[must_use]
1259 pub(crate) fn autoclose_pair_count(&self) -> usize {
1260 self.autoclose.len()
1261 }
1262
1263 #[must_use]
1266 pub fn folds(&self) -> &FoldSet {
1267 &self.folds
1268 }
1269
1270 pub fn folds_mut(&mut self) -> &mut FoldSet {
1274 &mut self.folds
1275 }
1276
1277 pub fn fold(&mut self, header: BufferRow, last: BufferRow) -> bool {
1283 match self.opener_of(header.0, last.0) {
1284 Some(open) => self.folds.fold(open),
1285 None => false,
1286 }
1287 }
1288
1289 pub fn unfold(&mut self, header: BufferRow) -> bool {
1292 match self.folded_opener_on(header.0) {
1293 Some(open) => self.folds.unfold(open),
1294 None => false,
1295 }
1296 }
1297
1298 pub fn toggle_fold(&mut self, header: BufferRow, last: BufferRow) -> bool {
1301 match self.folded_opener_on(header.0) {
1302 Some(open) => self.folds.unfold(open),
1303 None => self.fold(header, last),
1304 }
1305 }
1306
1307 fn folded_opener_on(&self, header: u32) -> Option<u32> {
1312 let start = self.buffer.point_to_offset(Point::new(header, 0));
1313 let end = if header + 1 >= self.buffer.line_count() {
1314 self.buffer.len() + 1 } else {
1316 self.buffer.point_to_offset(Point::new(header + 1, 0))
1317 };
1318 self.folds.first_at_or_after(start).filter(|&o| o < end)
1319 }
1320
1321 fn opener_of(&self, header: u32, last: u32) -> Option<u32> {
1324 self.foldable_pairs_in_rows(header..header + 1)
1326 .into_iter()
1327 .find(|&(_, _, h, l)| h == header && l == last)
1328 .map(|(o, ..)| o)
1329 }
1330
1331 fn foldable_pairs(&self) -> Vec<(u32, u32, u32, u32)> {
1336 self.foldable_pairs_from(&self.brackets.all())
1337 }
1338
1339 pub fn foldable_pairs_in_rows(&self, rows: Range<u32>) -> Vec<(u32, u32, u32, u32)> {
1358 let start = self.buffer.point_to_offset(Point::new(rows.start, 0));
1359 let end = if rows.end >= self.buffer.line_count() {
1360 self.buffer.len() + 1 } else {
1362 self.buffer.point_to_offset(Point::new(rows.end, 0))
1363 };
1364 self.foldable_pairs_from(&self.brackets.in_range(start..end))
1365 }
1366
1367 fn foldable_pairs_from(&self, brackets: &[crate::bracket::Bracket]) -> Vec<(u32, u32, u32, u32)> {
1369 brackets
1370 .iter()
1371 .filter_map(|b| {
1372 let close = b.partner.filter(|_| b.open)?;
1373 crate::row_layout::pair_has_interior(b.offset, close).then(|| {
1375 let header = self.buffer.offset_to_point(b.offset).row;
1376 let last = self.buffer.offset_to_point(close).row;
1377 (b.offset, close, header, last)
1378 })
1379 })
1380 .collect()
1381 }
1382
1383 pub fn toggle_fold_opener(&mut self, opener: u32) -> bool {
1390 let changed = self.folds.toggle(opener);
1391 if changed {
1392 self.reset_transient();
1398 }
1399 if changed && self.folds.is_folded(opener) {
1400 if let Some(close) = self.brackets.foldable_partner(opener) {
1401 let single_line = self.buffer.offset_to_point(opener).row == self.buffer.offset_to_point(close).row;
1402 if single_line {
1403 self.selections.map_each(|s| {
1406 if crate::row_layout::gap_hides_caret(opener, close, s.head()) {
1407 s.move_to_caret(crate::row_layout::gap_left_edge(opener));
1408 }
1409 });
1410 } else {
1411 let tab = self.tab_size();
1420 let Self { selections, buffer, folds, brackets, .. } = self;
1421 let fold_map = crate::fold_map::FoldMap::new(folds, brackets, buffer);
1422 let header = buffer.offset_to_point(opener).row;
1423 let entry = buffer.point_to_offset(Point::new(header, buffer.line_len(header)));
1424 selections.map_each(|s| {
1425 let inside = s.head() > opener && s.head() < close;
1426 if inside && fold_map.display_position(buffer, s.head(), tab).is_none() {
1427 s.move_to_caret(entry);
1428 }
1429 });
1430 }
1431 }
1432 }
1433 changed
1434 }
1435
1436 #[must_use]
1442 pub fn tab_size(&self) -> u32 {
1443 crate::display_map::default_tab_size()
1444 }
1445
1446 #[must_use]
1449 pub fn block_opener_on_row(&self, row: u32) -> Option<u32> {
1450 self.foldable_pairs_in_rows(row..row + 1)
1454 .into_iter()
1455 .filter(|&(_, _, h, l)| h == row && l > h)
1456 .max_by_key(|&(_, _, h, l)| l - h)
1457 .map(|(o, ..)| o)
1458 }
1459
1460 #[must_use]
1468 pub fn fold_opener_at_caret(&self, want_folded: bool) -> Option<u32> {
1469 self.fold_opener_at(self.selections.newest().head(), want_folded)
1470 }
1471
1472 fn fold_opener_at(&self, caret: u32, want_folded: bool) -> Option<u32> {
1479 let is_target = |open: u32, close: u32| {
1480 crate::row_layout::pair_has_interior(open, close)
1481 && self.folds.is_folded(open) == want_folded
1482 };
1483 let touch_left = self.brackets.foldable_partner(caret).map(|close| (caret, close));
1487 let touch_right = caret
1488 .checked_sub(1)
1489 .and_then(|o| self.brackets.at(o))
1490 .filter(|b| !b.open)
1491 .and_then(|b| b.partner.map(|open| (open, b.offset)));
1492 if let Some((open, _)) = [touch_left, touch_right]
1493 .into_iter()
1494 .flatten()
1495 .filter(|&(o, c)| is_target(o, c))
1496 .min_by_key(|&(o, c)| c - o)
1497 {
1498 return Some(open);
1499 }
1500 self.brackets.innermost_enclosing_where(caret, is_target)
1502 }
1503
1504 pub fn fold_at_carets(&mut self, unfold: bool) -> bool {
1511 let mut openers: Vec<u32> = self
1512 .selections
1513 .all()
1514 .iter()
1515 .filter_map(|s| self.fold_opener_at(s.head(), unfold))
1516 .collect();
1517 openers.sort_unstable();
1518 openers.dedup();
1519 if openers.is_empty() {
1520 return false;
1521 }
1522 self.reset_transient();
1529 if unfold {
1530 self.folds.unfold_all(&openers);
1531 } else {
1532 self.folds.fold_all(openers.iter().copied());
1533 }
1534 self.eject_hidden_carets();
1535 true
1536 }
1537
1538 fn eject_hidden_carets(&mut self) {
1542 let Self { selections, buffer, folds, brackets, .. } = self;
1543 if folds.is_empty() {
1544 return;
1545 }
1546 let fold_map = crate::fold_map::FoldMap::new(folds, brackets, buffer);
1547 selections.map_each(|s| {
1548 if let Some(entry) = fold_map.entry_edge_if_hidden(buffer, s.head()) {
1549 s.move_to_caret(entry);
1550 }
1551 });
1552 }
1553
1554 #[must_use]
1560 pub fn collapsible_pairs(&self) -> Vec<(u32, u32, u32, u32)> {
1561 self.foldable_pairs().into_iter().filter(|&(open, ..)| !self.folds.is_folded(open)).collect()
1562 }
1563
1564 #[must_use]
1572 pub fn collapsible_pairs_in_rows(&self, rows: Range<u32>) -> Vec<(u32, u32, u32, u32)> {
1573 self.foldable_pairs_in_rows(rows)
1574 .into_iter()
1575 .filter(|&(open, ..)| !self.folds.is_folded(open))
1576 .collect()
1577 }
1578
1579 #[must_use]
1585 pub fn collapsible_at(&self, offset: u32) -> Option<u32> {
1586 self.collapsible_pairs()
1587 .into_iter()
1588 .filter(|&(open, close, _, _)| open < offset && offset < close)
1589 .min_by_key(|&(open, close, _, _)| close - open)
1590 .map(|(o, ..)| o)
1591 }
1592
1593 fn reconcile_folds(&mut self) {
1600 if self.folds.is_empty() {
1601 return;
1602 }
1603 let brackets = &self.brackets;
1609 self.folds.reconcile(|o| {
1610 brackets
1611 .foldable_partner(o)
1612 .is_some_and(|close| crate::row_layout::pair_has_interior(o, close))
1613 });
1614 }
1615
1616 #[cfg(test)]
1619 pub(crate) fn force_whole_reconcile(&mut self) {
1620 self.reconcile_folds();
1621 }
1622
1623 fn reconcile_folds_in(&mut self, region: core::ops::Range<u32>) {
1634 if self.folds.is_empty() {
1635 return;
1636 }
1637 let candidates: Vec<u32> = self.folds.openers_in(region);
1638 let brackets = &self.brackets;
1639 self.folds.reconcile_only(&candidates, |o| {
1640 brackets
1641 .foldable_partner(o)
1642 .is_some_and(|close| crate::row_layout::pair_has_interior(o, close))
1643 });
1644 }
1645
1646 #[must_use]
1651 pub fn foldable_ranges(&self) -> Vec<(BufferRow, BufferRow)> {
1652 let mut ranges: Vec<(BufferRow, BufferRow)> = self
1653 .brackets
1654 .all()
1655 .iter()
1656 .filter_map(|br| {
1657 let close = br.partner.filter(|_| br.open)?;
1658 let header = self.buffer.offset_to_point(br.offset).row;
1659 let last = self.buffer.offset_to_point(close).row;
1660 (last > header).then_some((BufferRow(header), BufferRow(last)))
1661 })
1662 .collect();
1663 ranges.sort_by_key(|r| (r.0, core::cmp::Reverse(r.1)));
1665 ranges.dedup_by_key(|r| r.0);
1666 ranges
1667 }
1668
1669 #[must_use]
1676 pub fn foldable_ranges_in_rows(&self, rows: Range<u32>) -> Vec<(BufferRow, BufferRow)> {
1677 let start = self.buffer.point_to_offset(Point::new(rows.start, 0));
1678 let end = if rows.end >= self.buffer.line_count() {
1679 self.buffer.len() + 1 } else {
1681 self.buffer.point_to_offset(Point::new(rows.end, 0))
1682 };
1683 let mut ranges: Vec<(BufferRow, BufferRow)> = self
1684 .brackets
1685 .in_range_iter(start..end)
1686 .filter_map(|br| {
1687 let close = br.partner.filter(|_| br.open)?;
1688 let header = self.buffer.offset_to_point(br.offset).row;
1689 let last = self.buffer.offset_to_point(close).row;
1690 (last > header).then_some((BufferRow(header), BufferRow(last)))
1691 })
1692 .collect();
1693 ranges.sort_by_key(|r| (r.0, core::cmp::Reverse(r.1)));
1694 ranges.dedup_by_key(|r| r.0);
1695 ranges
1696 }
1697
1698 pub fn tokenize_highlight(&mut self, target: u32) {
1702 let buffer = &self.buffer;
1707 let Some(cache) = self.highlight.as_mut() else { return };
1708 cache.tokenize_until(target, crate::highlight::HIGHLIGHT_MAX_LINES_PER_CALL, |r| {
1709 buffer.line(r)
1710 });
1711 }
1712
1713 #[must_use]
1720 pub fn highlight_frontier(&self) -> Option<u32> {
1721 self.highlight.as_ref().and_then(HighlightCache::pending)
1722 }
1723
1724 pub fn set_highlight_window(&mut self, rows: Range<u32>) {
1732 if let Some(cache) = self.highlight.as_mut() {
1733 cache.set_window(rows);
1734 }
1735 }
1736
1737 #[must_use]
1740 pub fn highlight_line_spans(&self, row: u32) -> Option<&[HighlightSpan]> {
1741 self.highlight.as_ref().and_then(|c| c.line_spans(row))
1742 }
1743
1744 #[must_use]
1750 pub fn highlight_engine(&self) -> Option<HighlightEngine> {
1751 self.highlight.as_ref().map(HighlightCache::engine)
1752 }
1753
1754 pub fn absorb_highlight(
1774 &mut self,
1775 revision: Revision,
1776 seg: crate::SegmentTokens,
1777 verified: bool,
1778 ) -> bool {
1779 if revision != self.buffer.revision() {
1780 return false;
1781 }
1782 let Some(cache) = self.highlight.as_mut() else { return false };
1783 cache.absorb(seg, verified);
1784 true
1785 }
1786
1787 #[must_use]
1790 pub fn brackets(&self) -> &Brackets {
1791 &self.brackets
1792 }
1793
1794 pub fn set_diagnostics(&mut self, revision: Revision, diags: Vec<Diagnostic>) -> DiagnosticsOutcome {
1804 let current = self.buffer.revision();
1805 if revision != current {
1806 return DiagnosticsOutcome::Stale { current: current.0 };
1807 }
1808 let len = self.buffer.len();
1809 let clipped: Vec<Diagnostic> = diags
1810 .into_iter()
1811 .map(|mut d| {
1812 let (s, e) = (d.span.start.min(len), d.span.end.min(len));
1813 d.span = s.min(e)..e; d
1815 })
1816 .collect();
1817 self.decorations.set_diagnostics(current.0, current.0, clipped)
1818 }
1819
1820 pub fn diagnostics_in(
1824 &self,
1825 range: Range<u32>,
1826 ) -> impl Iterator<Item = (Range<u32>, Severity, std::sync::Arc<str>)> + '_ {
1827 self.decorations.decorations_in(range).filter_map(|r| {
1831 let TrackedRange { range, kind, .. } = r;
1832 match kind {
1833 DecorationKind::Diagnostic { severity, message, .. } => {
1834 Some((range, severity, message))
1835 }
1836 _ => None,
1837 }
1838 })
1839 }
1840
1841 pub fn set_find_query(&mut self, query: Option<FindQuery>, now_ms: u64) {
1849 self.find.set_query(query, &self.buffer, &mut self.decorations, now_ms);
1850 }
1851
1852 #[must_use]
1854 pub fn find_query(&self) -> Option<&FindQuery> {
1855 self.find.query()
1856 }
1857
1858 pub fn set_find_scope(&mut self, scope: Option<Range<u32>>, now_ms: u64) {
1868 self.find.set_scope(scope, &self.buffer, &mut self.decorations, now_ms);
1869 }
1870
1871 #[must_use]
1873 pub fn find_scope(&self) -> Option<Range<u32>> {
1874 self.find.scope()
1875 }
1876
1877 #[must_use]
1884 pub fn find_pattern_error(&self) -> Option<&str> {
1885 self.find.pattern_error()
1886 }
1887
1888 #[must_use]
1900 pub fn caret_word_occurrences(&self, within: Range<u32>) -> Vec<Range<u32>> {
1901 let newest = self.selections.newest();
1902 if !newest.is_empty() {
1903 return Vec::new();
1904 }
1905 let Some((ws, we)) = movement::surrounding_word(&self.buffer, newest.head()) else {
1906 return Vec::new();
1907 };
1908 if ws == we {
1909 return Vec::new();
1910 }
1911 let word = self.buffer.slice(ws..we);
1912 let end = within.end.min(self.buffer.len());
1913 let base = within.start.min(end);
1914 let hay = self.buffer.slice(base..end);
1915 let mut out = Vec::new();
1916 let mut from = 0usize;
1917 while let Some(i) = hay[from..].find(&*word) {
1918 let start = base + (from + i) as u32;
1919 let match_end = start + word.len() as u32;
1920 if movement::surrounding_word(&self.buffer, start) == Some((start, match_end)) {
1922 out.push(start..match_end);
1923 }
1924 from = from + i + word.len();
1925 }
1926 out
1927 }
1928
1929 #[must_use]
1935 pub fn find_match_count(&self) -> usize {
1936 self.decorations.find_count()
1937 }
1938
1939 #[must_use]
1945 pub fn active_find_match(&self) -> Option<u32> {
1946 self.find.active_start().map(|s| self.decorations.find_rank_before(s) as u32)
1947 }
1948
1949 pub fn find_matches_in(&self, range: Range<u32>) -> impl Iterator<Item = (Range<u32>, bool)> + '_ {
1953 let active = self.find.active_id();
1954 self.decorations.decorations_in(range).filter_map(move |r| match &r.kind {
1955 DecorationKind::FindMatch if r.range.start < r.range.end => {
1956 Some((r.range.clone(), Some(r.id) == active))
1957 }
1958 _ => None,
1959 })
1960 }
1961
1962 pub fn overview_marks(
1973 &self,
1974 bounds: &[u32],
1975 sev_out: &mut Vec<(u8, u32)>,
1976 find_out: &mut Vec<Option<u32>>,
1977 ) {
1978 self.decorations.diagnostic_overview(bounds, sev_out);
1979 self.decorations.find_overview(bounds, find_out);
1980 }
1981
1982 pub fn maybe_rescan_find(&mut self, now_ms: u64) -> bool {
1990 self.find.maybe_rescan(&self.buffer, &mut self.decorations, now_ms)
1991 }
1992
1993 pub fn find_next(&mut self, now_ms: u64) -> Option<Range<u32>> {
1997 self.step_find(true, now_ms)
1998 }
1999
2000 pub fn find_prev(&mut self, now_ms: u64) -> Option<Range<u32>> {
2003 self.step_find(false, now_ms)
2004 }
2005
2006 pub fn replace_next(
2020 &mut self,
2021 replacement: &str,
2022 preserve_case: bool,
2023 now_ms: u64,
2024 ) -> Option<Range<u32>> {
2025 self.find.maybe_rescan(&self.buffer, &mut self.decorations, now_ms);
2026 let extent = {
2027 let sel = self.selections.newest();
2028 sel.start()..sel.end()
2029 };
2030 let armed = !extent.is_empty()
2034 && self.find.active_range(&self.decorations).is_some_and(|r| r == extent);
2035 if armed {
2036 let text = self.find.query().cloned().map_or_else(
2039 || replacement.to_string(),
2040 |q| {
2041 crate::find::replacements(
2042 &self.buffer,
2043 &q,
2044 extent.clone(),
2045 replacement,
2046 preserve_case,
2047 )
2048 .into_iter()
2049 .next()
2050 .map_or_else(|| replacement.to_string(), |(_, t)| t)
2051 },
2052 );
2053 if self.edit(vec![EditOp::new(extent, text)]).is_err() {
2054 return None; }
2056 }
2057 self.step_find(true, now_ms)
2058 }
2059
2060 pub fn replace_all(&mut self, replacement: &str, preserve_case: bool) -> usize {
2085 let Some(query) = self.find.query().cloned() else {
2086 return 0; };
2088 let within = self.find.scope().unwrap_or(0..self.buffer.len());
2091 let plan =
2092 crate::find::replacements(&self.buffer, &query, within, replacement, preserve_case);
2093 if plan.is_empty() {
2094 return 0; }
2096 let n = plan.len();
2097 let ops = plan.into_iter().map(|(r, t)| EditOp::new(r, t)).collect();
2098 if self.edit(ops).is_err() {
2099 return 0;
2100 }
2101 n
2102 }
2103
2104 fn step_find(&mut self, forward: bool, now_ms: u64) -> Option<Range<u32>> {
2106 self.find.maybe_rescan(&self.buffer, &mut self.decorations, now_ms);
2110 let sel = self.selections.newest();
2111 let (head, extent) = (sel.head(), sel.start()..sel.end());
2112 let found = if forward {
2113 self.find.find_next(head, extent, &self.decorations)
2114 } else {
2115 self.find.find_prev(head, extent, &self.decorations)
2116 };
2117 if let Some(range) = &found {
2118 self.selections
2120 .set_single(Selection::from_anchor(SelectionId(0), range.start, range.end));
2121 self.reset_transient(); self.unfold_to_reveal(range.start);
2125 self.unfold_to_reveal(range.end);
2126 self.request_reveal(RevealMode::Center); }
2128 found
2129 }
2130
2131 pub fn next_diagnostic(&mut self, forward: bool) -> Option<Range<u32>> {
2138 let sel = self.selections.newest();
2142 let anchor = (sel.start(), sel.end());
2143 let mut all: Vec<Range<u32>> = self.diagnostics_in(0..u32::MAX).map(|(r, ..)| r).collect();
2144 all.sort_by_key(|r| (r.start, r.end));
2145 let target = if forward {
2146 all.iter().find(|r| (r.start, r.end) > anchor).or_else(|| all.first())
2147 } else {
2148 all.iter().rev().find(|r| (r.start, r.end) < anchor).or_else(|| all.last())
2149 }?
2150 .clone();
2151 self.selections
2152 .set_single(Selection::from_anchor(SelectionId(0), target.start, target.end));
2153 self.reset_transient(); self.unfold_to_reveal(target.start);
2155 self.unfold_to_reveal(target.end);
2156 self.request_reveal(RevealMode::Center); Some(target)
2158 }
2159
2160 fn unfold_to_reveal(&mut self, offset: u32) -> bool {
2167 let tab = self.tab_size();
2168 let mut any = false;
2169 loop {
2170 let fm = FoldMap::new(&self.folds, &self.brackets, &self.buffer);
2171 let renders = fm.display_position(&self.buffer, offset, tab).is_some();
2177 let chip_hidden = fm.inline_fold_before(offset).is_some_and(|f| f.hides_caret_at(offset));
2180 if renders && !chip_hidden {
2181 return any;
2182 }
2183 let target = self
2186 .folds
2187 .iter()
2188 .filter_map(|open| self.brackets.foldable_partner(open).map(|c| (open, c)))
2189 .filter(|&(open, close)| open < offset && offset < close)
2190 .min_by_key(|&(open, close)| close - open);
2191 let Some((open, _)) = target else {
2192 return any; };
2194 self.folds.unfold(open);
2195 any = true;
2196 }
2197 }
2198
2199 #[must_use]
2203 pub fn reveal_seq(&self) -> u64 {
2204 self.reveal_seq
2205 }
2206}
2207
2208fn expand_folds_touched(
2217 folds: &mut FoldSet,
2218 brackets: &Brackets,
2219 buffer: &Buffer,
2220 tab: u32,
2221 committed: &Committed,
2222) {
2223 if folds.is_empty() || committed.patch().is_empty() {
2224 return;
2225 }
2226 let mut pts: Vec<u32> = Vec::with_capacity(committed.patch().edits().len() * 2);
2239 for e in committed.patch().edits() {
2240 pts.push(e.new.start);
2241 pts.push(e.new.end);
2242 }
2243 pts.sort_unstable();
2244 pts.dedup();
2245 let (first, last) = (pts[0], *pts.last().expect("edits() is non-empty here"));
2246 let lo = brackets
2247 .enclosing_or_touching(first)
2248 .into_iter()
2249 .filter(|&(o, _)| folds.is_folded(o))
2250 .map(|(o, _)| o)
2251 .min()
2252 .unwrap_or(first);
2253 let candidates: Vec<(u32, u32)> = folds
2257 .openers_in(lo..last.saturating_add(1))
2258 .iter()
2259 .filter_map(|&o| {
2260 let c = brackets.foldable_partner(o)?;
2261 let touches =
2262 pts.partition_point(|&p| p < o) < pts.partition_point(|&p| p <= c.saturating_add(1));
2263 touches.then_some((o, c))
2264 })
2265 .collect();
2266 if candidates.is_empty() {
2267 return;
2268 }
2269 let mut sub = crate::fold_map::FoldSet::new();
2270 for &(open, _) in &candidates {
2271 sub.fold(open);
2272 }
2273 let fold_map = crate::fold_map::FoldMap::new(&sub, brackets, buffer);
2274 let any_hidden = pts.iter().any(|&p| fold_map.display_position(buffer, p, tab).is_none());
2280 let mut starts: Vec<u32> = committed.patch().edits().iter().map(|e| e.new.start).collect();
2283 starts.sort_unstable();
2284 let touched: Vec<u32> = candidates
2285 .iter()
2286 .filter(|&&(opener, close)| {
2287 let single = buffer.offset_to_point(opener).row == buffer.offset_to_point(close).row;
2288 if single {
2289 starts.partition_point(|&s| s <= close) > starts.partition_point(|&s| s <= opener)
2292 } else {
2293 any_hidden
2297 }
2298 })
2299 .map(|&(opener, _)| opener)
2300 .collect();
2301 for opener in touched {
2302 folds.unfold(opener);
2303 }
2304}
2305
2306struct Views<'a> {
2312 highlight: &'a mut Option<HighlightCache>,
2313 brackets: &'a mut Brackets,
2314 decorations: &'a mut DecorationStore,
2315 autoclose: &'a mut DecorationStore,
2319 folds: &'a mut FoldSet,
2320 find: &'a mut FindState,
2321}
2322
2323fn rebase_views(
2334 views: &mut Views,
2335 buffer: &Buffer,
2336 tab: u32,
2337 committed: &Committed,
2338 bracket_cfg: &crate::bracket::BracketConfig,
2339) -> core::ops::Range<u32> {
2340 if let Some(cache) = views.highlight.as_mut() {
2346 let edits = committed.patch().edits();
2347 if !edits.is_empty() {
2348 let mut spans: Vec<(u32, u32, u32)> = Vec::with_capacity(edits.len());
2356 let mut acc: i64 = 0;
2357 for (e, inv) in edits.iter().zip(committed.inverse_ops()) {
2358 let post_sr = buffer.offset_to_point(e.new.start).row;
2359 let post_er = buffer.offset_to_point(e.new.end).row;
2360 let new_lines = post_er - post_sr + 1;
2361 let old_lines = inv.text.bytes().filter(|&b| b == b'\n').count() as u32 + 1;
2362 let pre_start = (i64::from(post_sr) - acc) as u32;
2363 acc += i64::from(new_lines) - i64::from(old_lines);
2364 match spans.last_mut() {
2365 Some(last) if pre_start < last.0 + last.1 => {
2368 let merged_end = (last.0 + last.1).max(pre_start + old_lines);
2369 let combined_delta = (i64::from(last.2) - i64::from(last.1))
2370 + (i64::from(new_lines) - i64::from(old_lines));
2371 last.1 = merged_end - last.0;
2372 last.2 = (i64::from(last.1) + combined_delta) as u32;
2373 }
2374 _ => spans.push((pre_start, old_lines, new_lines)),
2375 }
2376 }
2377 debug_assert_eq!(
2382 spans.iter().map(|&(_, o, n)| i64::from(n) - i64::from(o)).sum::<i64>(),
2383 i64::from(buffer.line_count()) - i64::from(cache.line_count()),
2384 "per-edit line deltas must sum to the buffer's line-count change",
2385 );
2386 cache.on_commit_patch(&spans);
2387 }
2388 }
2389 let reconcile_region = views.brackets.apply_edit(committed.patch(), buffer, bracket_cfg);
2394 views.decorations.apply_patch(committed.patch());
2398 views.autoclose.apply_patch(committed.patch());
2405 views.find.on_commit(committed.patch(), buffer, views.decorations);
2410 views.folds.apply_patch(committed.patch());
2415 expand_folds_touched(views.folds, views.brackets, buffer, tab, committed);
2423 reconcile_region
2427}
2428
2429fn extend_by_unit(origin_unit: (u32, u32), head_unit: (u32, u32), origin: u32, head: u32) -> (u32, u32) {
2434 let (os, oe) = origin_unit;
2435 let (hs, he) = head_unit;
2436 if head >= origin {
2437 (os, he.max(oe))
2438 } else {
2439 (oe, hs.min(os))
2440 }
2441}
2442
2443fn scan_from(
2454 buffer: &Buffer,
2455 needle: &str,
2456 lo: u32,
2457 hi: u32,
2458 is_taken: &impl Fn(u32) -> bool,
2459) -> Option<(u32, u32)> {
2460 let k = needle.len() as u64;
2461 if k == 0 || lo >= hi {
2462 return None;
2463 }
2464 let len = buffer.len();
2465 let window = u64::from(crate::buffer::SCAN_WINDOW).max(k * 2); let mut pos = lo;
2467 while u64::from(pos) + k <= u64::from(len) {
2468 let win_end = buffer
2469 .clip_offset((u64::from(pos) + window).min(u64::from(len)) as u32, Bias::Right);
2470 let slice = buffer.slice(pos..win_end);
2471 let mut last_end: Option<u32> = None;
2472 for (i, _) in slice.match_indices(needle) {
2473 let start = pos + i as u32;
2474 if u64::from(start) >= u64::from(hi) {
2475 return None; }
2477 last_end = Some(start + k as u32);
2478 if !is_taken(start) {
2479 return Some((start, start + k as u32));
2480 }
2481 }
2482 if win_end >= len {
2483 break;
2484 }
2485 pos = buffer.scan_resume(pos, win_end, k as u32, last_end);
2488 }
2489 None
2490}
2491
2492#[cfg(test)]
2499fn find_next_occurrence(text: &str, needle: &str, from: u32, taken: &[(u32, u32)]) -> Option<(u32, u32)> {
2500 if needle.is_empty() {
2501 return None;
2502 }
2503 let len = needle.len() as u32;
2504 let is_taken = |start: u32| taken.iter().any(|&(s, e)| s == start && e == start + len);
2505 let mut wrapped: Option<u32> = None;
2506 for (idx, _) in text.match_indices(needle) {
2507 let start = idx as u32;
2508 if is_taken(start) {
2509 continue;
2510 }
2511 if start >= from {
2512 return Some((start, start + len));
2513 }
2514 if wrapped.is_none() {
2515 wrapped = Some(start); }
2517 }
2518 wrapped.map(|start| (start, start + len))
2519}
2520
2521#[cfg(test)]
2522mod tests {
2523 use super::*;
2524 use crate::history::{GroupingHint, OpClass};
2525
2526 fn doc(s: &str) -> Document {
2527 Document::new(s).unwrap()
2528 }
2529
2530 fn typ(op: OpClass) -> GroupingHint {
2531 GroupingHint::mergeable(op)
2532 }
2533
2534 fn assert_brackets_fresh(d: &Document) {
2537 let fresh = crate::bracket::Brackets::match_text(&d.text());
2538 assert_eq!(d.brackets().all(), fresh.all(), "incremental brackets diverged from scratch");
2539 }
2540
2541 #[test]
2542 fn brackets_ride_edits_and_undo_redo_through_the_one_mover() {
2543 let mut d = doc("fn a() {\n x[0] = (1 + 2);\n}\n");
2549 let mut rng = 0xC0FFEEu64;
2550 let mut next = move |m: u64| {
2551 rng ^= rng << 13;
2552 rng ^= rng >> 7;
2553 rng ^= rng << 17;
2554 rng % m
2555 };
2556 let inserts = ["{", "}", "(", ")", "[", "]", "\n", "x", "{}", "([)", "\n}\n"];
2557 for i in 0..200 {
2558 match next(4) {
2559 0 | 1 => {
2560 let at = next(d.buffer().len() as u64 + 1) as u32;
2561 let at = d.buffer().clip_offset(at, crate::Bias::Left);
2562 let text = inserts[next(inserts.len() as u64) as usize];
2563 let _ = d.edit(vec![EditOp::insert(at, text)]);
2564 }
2565 2 => {
2566 let a = next(d.buffer().len() as u64 + 1) as u32;
2567 let a = d.buffer().clip_offset(a, crate::Bias::Left);
2568 let b = (a + 1 + next(4) as u32).min(d.buffer().len());
2569 let b = d.buffer().clip_offset(b, crate::Bias::Right);
2570 if a < b {
2571 let _ = d.edit(vec![EditOp::delete(a..b)]);
2572 }
2573 }
2574 _ => {
2575 if next(2) == 0 {
2576 d.undo();
2577 } else {
2578 d.redo();
2579 }
2580 }
2581 }
2582 assert_brackets_fresh(&d);
2583 let _ = i;
2584 }
2585 }
2586
2587 #[test]
2588 fn edit_undo_redo_round_trip() {
2589 let mut d = doc("hello");
2590 d.edit(vec![EditOp::new(0..5, "world")]).unwrap();
2591 assert_eq!(d.text(), "world");
2592 assert!(d.undo());
2593 assert_eq!(d.text(), "hello");
2594 assert!(d.redo());
2595 assert_eq!(d.text(), "world");
2596 assert!(d.undo());
2598 assert!(!d.undo()); assert_eq!(d.text(), "hello");
2600 }
2601
2602 #[test]
2603 fn a_new_edit_invalidates_redo() {
2604 let mut d = doc("");
2605 d.edit(vec![EditOp::insert(0, "a")]).unwrap();
2606 d.edit(vec![EditOp::insert(1, "b")]).unwrap(); d.undo(); d.edit(vec![EditOp::insert(1, "X")]).unwrap(); assert_eq!(d.text(), "aX");
2610 assert!(!d.redo(), "redo must be cleared by the divergent edit");
2611 assert_eq!(d.text(), "aX");
2612 }
2613
2614 #[test]
2615 fn a_retained_branch_is_reachable_from_the_fork() {
2616 let mut d = doc("");
2623 d.edit(vec![EditOp::insert(0, "a")]).unwrap();
2624 d.edit(vec![EditOp::insert(1, "b")]).unwrap(); d.undo(); d.edit(vec![EditOp::insert(1, "X")]).unwrap(); assert!(!d.redo(), "plain redo does nothing at the tip of the new branch");
2628 assert!(d.undo());
2630 assert_eq!(d.text(), "a");
2631 assert_eq!(d.redo_branch_count(), 2, "the 'b' and 'X' branches both survive");
2632 assert!(d.can_redo());
2633 assert!(d.select_redo_branch(0));
2635 assert!(d.redo());
2636 assert_eq!(d.text(), "ab", "the retained 'b' branch is fully reachable");
2637 assert!(d.undo());
2639 assert!(d.select_redo_branch(1));
2640 assert!(d.redo());
2641 assert_eq!(d.text(), "aX");
2642 }
2643
2644 #[test]
2645 fn opt_in_undo_limit_bounds_reach_and_keeps_recent() {
2646 let mut d = doc("");
2650 d.set_undo_limit(Some(3));
2651 for (i, ch) in "abcde".chars().enumerate() {
2652 d.edit(vec![EditOp::insert(i as u32, ch.to_string())]).unwrap();
2653 }
2654 assert_eq!(d.text(), "abcde");
2655 assert_eq!(d.undo_depth(), 3, "only the last 3 units are retained");
2656 assert!(d.undo()); assert!(d.undo()); assert!(d.undo()); assert_eq!(d.text(), "ab");
2660 assert!(!d.undo(), "the pruned units ('a','b' inserts) are gone");
2661 assert!(!d.can_undo());
2662 assert!(d.redo());
2664 assert_eq!(d.text(), "abc");
2665 }
2666
2667 #[test]
2668 fn pruning_preserves_branches_within_the_window() {
2669 let mut d = doc("");
2672 d.edit(vec![EditOp::insert(0, "a")]).unwrap(); d.edit(vec![EditOp::insert(1, "b")]).unwrap(); d.undo(); d.edit(vec![EditOp::insert(1, "X")]).unwrap(); d.set_undo_limit(Some(1));
2678 assert_eq!(d.undo_depth(), 1);
2679 assert!(d.undo()); assert_eq!(d.text(), "a");
2681 assert_eq!(d.redo_branch_count(), 2, "both branches survived the rebuild");
2682 assert!(d.select_redo_branch(0));
2683 assert!(d.redo());
2684 assert_eq!(d.text(), "ab", "the retained branch is still reachable after a prune");
2685 }
2686
2687 #[test]
2688 fn typing_run_merges_into_one_undo_step() {
2689 let mut d = doc("");
2691 for (i, ch) in "hello".chars().enumerate() {
2692 d.edit_grouped(vec![EditOp::insert(i as u32, ch.to_string())], typ(OpClass::Type))
2693 .unwrap();
2694 }
2695 assert_eq!(d.text(), "hello");
2696 assert!(d.undo());
2697 assert_eq!(d.text(), "", "the whole typing run undoes as one unit");
2698 assert!(d.redo());
2699 assert_eq!(d.text(), "hello", "and redoes as one unit");
2700 }
2701
2702 #[test]
2703 fn fold_toggle_seals_undo_and_clears_the_expand_ladder() {
2704 let mut d = doc("m {\n word\n}");
2708 d.set_selections(SelectionSet::new(7)); d.type_char('x');
2710 d.expand_selection(); assert!(d.toggle_fold_opener(2));
2712 d.shrink_selection(); let head = d.selections().newest().head();
2714 let fm = crate::fold_map::FoldMap::new(d.folds(), d.brackets(), d.buffer());
2715 assert!(
2716 fm.display_position(d.buffer(), head, d.tab_size()).is_some(),
2717 "…so the caret cannot be restored into the collapsed fold"
2718 );
2719 d.type_char('y');
2721 assert!(d.undo());
2722 assert!(d.text().contains('x'), "undo reverted only the post-toggle typing");
2723 }
2724
2725 #[test]
2726 fn find_navigation_expands_a_collapsed_inline_fold() {
2727 let mut d = doc("x = [1, 2, 3]\nafter");
2732 assert!(d.toggle_fold_opener(4)); d.set_find_query(Some(FindQuery { text: "2".into(), case_sensitive: false, ..Default::default() }), 0);
2734 d.find_next(0).expect("the match exists");
2735 assert!(!d.folds().is_folded(4), "the inline fold expanded to show the match");
2736 }
2737
2738 #[test]
2739 fn editing_hidden_text_expands_only_the_enclosing_fold() {
2740 let text = "a {\n keep\n}\nb {\n body\n}\nc\n";
2745 let mut d = doc(text);
2746 let opens: Vec<u32> = text.match_indices('{').map(|(i, _)| i as u32).collect();
2747 let (keep, edited) = (opens[0], opens[1]);
2750 assert!(d.toggle_fold_opener(keep));
2751 assert!(d.toggle_fold_opener(edited));
2752 assert!(d.folds().is_folded(keep) && d.folds().is_folded(edited));
2753 let inside = text.find("body").unwrap() as u32 + 2;
2755 d.edit(vec![EditOp::insert(inside, "X")]).unwrap();
2756 assert!(!d.folds().is_folded(edited), "editing inside the second block expands it");
2757 assert!(d.folds().is_folded(keep), "the untouched first block stays folded");
2758 }
2759
2760 #[test]
2761 fn next_diagnostic_advances_past_a_shared_start_offset() {
2762 use crate::{Diagnostic, Severity};
2763 let mut d = doc("abcdef");
2766 let rev = d.revision();
2767 d.set_diagnostics(
2768 rev,
2769 vec![
2770 Diagnostic::new(1..3, Severity::Error, "a"),
2771 Diagnostic::new(1..5, Severity::Warning, "b"),
2772 ],
2773 );
2774 assert_eq!(d.next_diagnostic(true), Some(1..3));
2775 assert_eq!(d.next_diagnostic(true), Some(1..5), "the same-start sibling is reachable");
2776 assert_eq!(d.next_diagnostic(true), Some(1..3), "and the walk wraps");
2777 }
2778
2779 #[test]
2780 fn reveal_classes_and_no_op_verbs() {
2781 use crate::{Diagnostic, Severity};
2782 let mut d = doc("f( ab )");
2786 let seq0 = d.reveal_seq();
2787 d.next_diagnostic(true); assert_eq!(d.reveal_seq(), seq0, "a no-op F8 requests no reveal");
2789 d.set_selections(SelectionSet::new(3));
2790 d.jump_to_bracket(); assert!(d.reveal_seq() > seq0, "a real bracket hop requests a reveal");
2792 assert_eq!(d.reveal_mode(), RevealMode::Fit, "…of the Fit class");
2793 let rev = d.revision();
2794 d.set_diagnostics(rev, vec![Diagnostic::new(1..2, Severity::Error, "e")]);
2795 let seq1 = d.reveal_seq();
2796 d.next_diagnostic(true);
2797 assert!(d.reveal_seq() > seq1);
2798 assert_eq!(d.reveal_mode(), RevealMode::Center, "a diagnostic jump centers");
2799 }
2800
2801 #[test]
2802 fn ctrl_d_force_reveals_while_select_all_holds() {
2803 let mut d = doc("foo bar foo baz foo");
2806 d.set_selections(SelectionSet::new(0));
2807 d.add_next_occurrence(); d.add_next_occurrence(); assert_eq!(d.reveal_mode(), RevealMode::FitForce, "Ctrl+D force-reveals the new cursor");
2810 let mut e = doc("foo bar foo baz foo");
2811 e.set_selections(SelectionSet::new(0));
2812 e.select_all_occurrences();
2813 assert_eq!(e.reveal_mode(), RevealMode::Fit, "Ctrl+Shift+L holds if a cursor is visible");
2814 }
2815
2816 #[test]
2817 fn diagnostic_navigation_wraps_selects_and_reveals() {
2818 use crate::{Diagnostic, Severity};
2819 let mut d = doc("aa bb\ncc\ndd");
2820 let rev = d.revision();
2821 d.set_diagnostics(
2822 rev,
2823 vec![
2824 Diagnostic::new(3..5, Severity::Warning, "w"),
2825 Diagnostic::new(9..11, Severity::Error, "e"),
2826 ],
2827 );
2828 assert_eq!(d.next_diagnostic(true), Some(3..5));
2829 assert_eq!(d.next_diagnostic(true), Some(9..11));
2830 assert_eq!(d.next_diagnostic(true), Some(3..5), "wraps to the first");
2831 assert_eq!(d.next_diagnostic(false), Some(9..11), "prev wraps to the last");
2832 let s = d.selections().newest();
2833 assert_eq!((s.start(), s.end()), (9, 11), "the diagnostic's span is selected");
2834 let mut e = doc("x");
2836 e.set_selections(SelectionSet::new(1));
2837 assert!(e.next_diagnostic(true).is_none());
2838 assert_eq!(e.selections().newest().head(), 1);
2839 let mut f = doc("aa\nfn {\nbb\n}\ncc");
2841 assert!(f.toggle_fold_opener(6));
2842 let rev = f.revision();
2843 f.set_diagnostics(rev, vec![Diagnostic::new(8..10, Severity::Error, "hidden")]);
2844 assert_eq!(f.next_diagnostic(true), Some(8..10));
2845 assert!(!f.folds().is_folded(6), "the fold expanded to show the diagnostic");
2846 }
2847
2848 #[test]
2849 fn caret_word_occurrences_are_whole_word_only() {
2850 let mut d = doc("foo foobar foo\nfoo");
2851 d.set_selections(SelectionSet::new(1)); assert_eq!(
2853 d.caret_word_occurrences(0..u32::MAX),
2854 vec![0..3, 11..14, 15..18],
2855 "the foobar prefix is not a whole-word match"
2856 );
2857 assert_eq!(d.caret_word_occurrences(4..15), vec![11..14], "only in-window matches");
2859 let mut set = SelectionSet::new(0);
2861 set.set_single(Selection::from_anchor(SelectionId(0), 0, 3));
2862 d.set_selections(set);
2863 assert!(d.caret_word_occurrences(0..u32::MAX).is_empty());
2864 let mut w = doc("a b");
2866 w.set_selections(SelectionSet::new(2));
2867 assert!(w.caret_word_occurrences(0..u32::MAX).is_empty());
2868 }
2869
2870 #[test]
2871 fn find_navigation_expands_a_collapsed_fold_to_reveal_the_match() {
2872 let mut d = doc("aa\nfn {\nneedle\n}\nbb");
2875 let opener = 6; assert!(d.toggle_fold_opener(opener));
2877 d.set_find_query(Some(FindQuery { text: "needle".into(), case_sensitive: false, ..Default::default() }), 0);
2878 let m = d.find_next(0).expect("the match exists");
2879 assert!(!d.folds().is_folded(opener), "the fold expanded to reveal the match");
2880 let fm = crate::fold_map::FoldMap::new(d.folds(), d.brackets(), d.buffer());
2881 assert!(
2882 fm.display_position(d.buffer(), m.end, d.tab_size()).is_some(),
2883 "the match head renders"
2884 );
2885 let mut v = doc("aa\nfn {\nx\n}\nbb");
2887 assert!(v.toggle_fold_opener(6));
2888 v.set_find_query(Some(FindQuery { text: "fn".into(), case_sensitive: false, ..Default::default() }), 0);
2889 v.find_next(0).expect("match on the header");
2890 assert!(v.folds().is_folded(6), "a visible match keeps the fold collapsed");
2891 }
2892
2893 #[test]
2894 fn jump_to_bracket_crosses_and_returns() {
2895 let mut d = doc("f( ab )");
2898 d.set_selections(SelectionSet::new(2));
2899 d.jump_to_bracket();
2900 assert_eq!(d.selections().newest().head(), 7);
2901 d.jump_to_bracket();
2902 assert_eq!(d.selections().newest().head(), 2);
2903 d.set_selections(SelectionSet::new(4)); d.jump_to_bracket();
2906 assert_eq!(d.selections().newest().head(), 6, "lands before `)`");
2907 let mut e = doc("plain");
2909 e.set_selections(SelectionSet::new(3));
2910 e.jump_to_bracket();
2911 assert_eq!(e.selections().newest().head(), 3);
2912 }
2913
2914 #[test]
2915 fn expand_selection_climbs_the_bracket_ladder_and_shrinks_back() {
2916 let mut d = doc("m { a(bb) }");
2918 d.set_selections(SelectionSet::new(7)); let sel = |d: &Document| (d.selections().newest().start(), d.selections().newest().end());
2920 d.expand_selection();
2921 assert_eq!(sel(&d), (6, 8), "word first");
2922 d.expand_selection();
2923 assert_eq!(sel(&d), (5, 9), "word == () contents, so the pair incl. brackets");
2924 d.expand_selection();
2925 assert_eq!(sel(&d), (3, 10), "the brace contents");
2926 d.expand_selection();
2927 assert_eq!(sel(&d), (2, 11), "the brace pair incl. brackets");
2928 d.expand_selection();
2929 assert_eq!(sel(&d), (0, 11), "the whole document");
2930 d.expand_selection();
2931 assert_eq!(sel(&d), (0, 11), "fully expanded is a no-op");
2932 d.shrink_selection();
2934 assert_eq!(sel(&d), (2, 11));
2935 d.shrink_selection();
2936 assert_eq!(sel(&d), (3, 10));
2937 d.move_carets(Motion::Right, false);
2939 let caret = sel(&d);
2940 d.shrink_selection();
2941 assert_eq!(sel(&d), caret, "the ladder cleared on the caret move");
2942 }
2943
2944 #[test]
2945 fn add_caret_vertical_stacks_and_stops_at_edges() {
2946 let mut d = doc("aaa\nbbb\nccc");
2949 d.set_selections(SelectionSet::new(5)); d.add_caret_vertical(false); d.add_caret_vertical(true); assert_eq!(d.selections().len(), 3);
2953 d.type_char('X');
2954 assert_eq!(d.text(), "aXaa\nbXbb\ncXcc");
2955 let mut e = doc("aa\nbb");
2957 e.set_selections(SelectionSet::new(1));
2958 e.add_caret_vertical(false);
2959 assert_eq!(e.selections().len(), 1);
2960 }
2961
2962 #[test]
2963 fn add_caret_vertical_skips_a_collapsed_fold() {
2964 let mut d = doc("aa\nfn {\nbb\ncc\n}\ndd");
2965 assert!(d.toggle_fold_opener(6)); d.set_selections(SelectionSet::new(16)); d.add_caret_vertical(false);
2968 let rows: Vec<u32> =
2969 d.selections().all().iter().map(|s| d.buffer().offset_to_point(s.head()).row).collect();
2970 assert_eq!(rows, vec![1, 5], "the new caret lands on the fold header, not a hidden row");
2971 }
2972
2973 #[test]
2974 fn select_all_occurrences_takes_every_match_from_a_word_seed() {
2975 let mut d = doc("foo bar foo baz foo");
2978 d.set_selections(SelectionSet::new(1));
2979 d.select_all_occurrences();
2980 assert_eq!(d.selections().len(), 3);
2981 d.type_char('X');
2982 assert_eq!(d.text(), "X bar X baz X");
2983 let mut e = doc(" ");
2985 e.set_selections(SelectionSet::new(1));
2986 e.select_all_occurrences();
2987 assert_eq!(e.selections().len(), 1);
2988 assert!(e.selections().newest().is_empty());
2989 }
2990
2991 #[test]
2992 fn select_find_matches_turns_matches_into_selections() {
2993 let mut d = doc("foo bar foo baz foo");
2994 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
2995 d.find_next(0); assert!(d.select_find_matches());
2997 assert_eq!(d.selections().len(), 3);
2998 assert_eq!(d.selections().newest().start(), 0, "the ACTIVE match is newest");
2999 let mut e = doc("abc");
3001 assert!(!e.select_find_matches());
3002 assert_eq!(e.selections().len(), 1);
3003 }
3004
3005 #[test]
3006 fn replace_all_swaps_every_match_in_one_undo_step() {
3007 let original = "foo bar foo baz foo";
3010 let mut d = doc(original);
3011 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
3012 assert_eq!(d.replace_all("QUX", false), 3);
3013 assert_eq!(d.text(), "QUX bar QUX baz QUX");
3014 assert!(d.undo());
3015 assert_eq!(d.text(), original, "replace-all must undo as ONE step");
3016 assert!(!d.undo(), "…with no second step hiding behind it");
3017 assert_eq!(d.find_match_count(), 3);
3019 }
3020
3021 #[test]
3022 fn replace_all_is_not_capped_by_the_live_match_set() {
3023 let n = crate::find::FIND_MATCH_CAP + 500;
3027 let mut d = doc(&"a".repeat(n));
3028 d.set_find_query(Some(FindQuery { text: "a".into(), case_sensitive: false, ..Default::default() }), 0);
3029 assert_eq!(d.find_match_count(), crate::find::FIND_MATCH_CAP, "the live set caps");
3030 assert_eq!(d.replace_all("b", false), n, "replace-all must NOT cap");
3031 assert_eq!(d.text(), "b".repeat(n), "every match past the cap replaced too");
3032 }
3033
3034 #[test]
3035 fn replace_next_selects_before_it_replaces() {
3036 let mut d = doc("foo bar foo");
3037 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
3038 assert_eq!(d.replace_next("X", false, 0), Some(0..3));
3041 assert_eq!(d.text(), "foo bar foo", "the first press must not replace");
3042 assert_eq!(d.replace_next("X", false, 0), Some(6..9));
3046 assert_eq!(d.text(), "X bar foo");
3047 assert!(d.undo());
3049 assert_eq!(d.text(), "foo bar foo");
3050 }
3051
3052 #[test]
3053 fn find_scope_restricts_matches_and_replace_all() {
3054 let mut d = doc("foo foo | foo foo");
3055 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
3056 assert_eq!(d.find_match_count(), 4);
3057 d.set_find_scope(Some(0..7), 0);
3059 assert_eq!(d.find_match_count(), 2, "only the in-scope matches survive");
3060 assert_eq!(d.replace_all("X", false), 2, "replace-all must honor the scope");
3062 assert_eq!(d.text(), "X X | foo foo", "the out-of-scope matches are untouched");
3063 d.set_find_scope(None, 0);
3065 assert_eq!(d.find_match_count(), 2, "the two outside are findable again");
3066 }
3067
3068 #[test]
3069 fn the_find_scope_rides_edits_and_drops_when_it_collapses() {
3070 let mut d = doc("aaa foo bbb");
3071 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
3072 d.set_find_scope(Some(4..7), 0); assert_eq!(d.find_match_count(), 1);
3074 d.edit(vec![EditOp::insert(0, "XY")]).unwrap();
3076 assert_eq!(d.find_scope(), Some(6..9));
3077 assert_eq!(d.find_match_count(), 1, "the scope still covers the match");
3078 d.edit(vec![EditOp::delete(6..9)]).unwrap();
3081 assert_eq!(d.find_scope(), None, "a collapsed scope must clear");
3082 }
3083
3084 #[test]
3085 fn an_edit_outside_the_scope_creates_no_matches() {
3086 let mut d = doc("foo | ...");
3087 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
3088 d.set_find_scope(Some(0..3), 0);
3089 assert_eq!(d.find_match_count(), 1);
3090 d.edit(vec![EditOp::insert(6, "foo")]).unwrap();
3093 assert_eq!(d.find_match_count(), 1, "out-of-scope text must not match");
3094 }
3095
3096 #[test]
3102 fn scoped_repair_equals_a_fresh_scoped_scan_under_random_edits() {
3103 let mut seed = 0x5EED_u64;
3104 let mut rng = || {
3105 seed ^= seed << 13;
3106 seed ^= seed >> 7;
3107 seed ^= seed << 17;
3108 seed
3109 };
3110 let base = "foo bar foo baz\nfoo foo bar\nbarfoo foo\n".repeat(3);
3111 let n = base.len() as u32;
3112 for trial in 0..300 {
3113 let mut d = doc(&base);
3114 let a = (rng() as u32) % n;
3115 let b = (rng() as u32) % n;
3116 let (lo, hi) = (a.min(b), a.max(b));
3117 let lo = d.buffer().clip_offset(lo, Bias::Right);
3118 let hi = d.buffer().clip_offset(hi, Bias::Left);
3119 if lo >= hi {
3120 continue;
3121 }
3122 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
3123 d.set_find_scope(Some(lo..hi), 0);
3124 for _ in 0..4 {
3125 let at = (rng() as u32) % (d.buffer().len() + 1);
3126 let at = d.buffer().clip_offset(at, Bias::Left);
3127 let ins = ["foo", "x", "", "\nfoo "][(rng() % 4) as usize];
3128 d.edit(vec![EditOp::insert(at, ins)]).unwrap();
3129 }
3130 let incremental: Vec<_> = d.find_matches_in(0..u32::MAX).map(|(r, _)| r).collect();
3131 let scope = d.find_scope();
3135 let q = d.find_query().cloned().unwrap();
3136 d.set_find_query(None, 0);
3137 d.set_find_query(Some(q), 0);
3138 let fresh: Vec<_> = d.find_matches_in(0..u32::MAX).map(|(r, _)| r).collect();
3139 assert_eq!(incremental, fresh, "trial {trial}, scope {scope:?}");
3140 if let Some(s) = scope {
3142 assert!(
3143 incremental.iter().all(|m| m.start >= s.start && m.end <= s.end),
3144 "trial {trial}: a match escaped the scope {s:?}: {incremental:?}"
3145 );
3146 }
3147 }
3148 }
3149
3150 #[test]
3160 fn line_scoped_repair_equals_a_fresh_scan_under_random_edits() {
3161 let mut seed = 0x00C0_FFEE_u64;
3162 let mut rng = || {
3163 seed ^= seed << 13;
3164 seed ^= seed >> 7;
3165 seed ^= seed << 17;
3166 seed
3167 };
3168 let base = "foo bar foo baz\nfoo foo bar\nbarfoo foo\n\nqux foofoo\n".repeat(3);
3169 let queries = [
3170 FindQuery { text: "foo".into(), whole_word: true, ..Default::default() },
3171 FindQuery { text: "f.o".into(), regex: true, ..Default::default() },
3172 FindQuery { text: r"\w+o".into(), regex: true, ..Default::default() },
3173 FindQuery { text: ".*".into(), regex: true, ..Default::default() },
3175 FindQuery { text: r"\bba\w+".into(), regex: true, ..Default::default() },
3176 FindQuery {
3177 text: "foo|bar".into(),
3178 whole_word: true,
3179 regex: true,
3180 ..Default::default()
3181 },
3182 ];
3183 for (qi, q) in queries.iter().enumerate() {
3184 for trial in 0..40 {
3185 let mut d = doc(&base);
3186 d.set_find_query(Some(q.clone()), 0);
3187 for _ in 0..5 {
3188 let len = d.buffer().len();
3189 if rng() % 3 == 0 && len > 4 {
3192 let a = d.buffer().clip_offset((rng() as u32) % len, Bias::Left);
3193 let b = d
3194 .buffer()
3195 .clip_offset((a + 1 + (rng() as u32) % 6).min(len), Bias::Right);
3196 if a < b {
3197 d.edit(vec![EditOp::delete(a..b)]).unwrap();
3198 }
3199 } else {
3200 let at = d.buffer().clip_offset((rng() as u32) % (len + 1), Bias::Left);
3201 let ins = ["foo", "x", "\n", " ", "\nfoo bar"][(rng() % 5) as usize];
3202 d.edit(vec![EditOp::insert(at, ins)]).unwrap();
3203 }
3204 }
3205 let incremental: Vec<_> = d.find_matches_in(0..u32::MAX).map(|(r, _)| r).collect();
3206 d.set_find_query(None, 0);
3208 d.set_find_query(Some(q.clone()), 0);
3209 let fresh: Vec<_> = d.find_matches_in(0..u32::MAX).map(|(r, _)| r).collect();
3210 assert_eq!(incremental, fresh, "query {qi} ({:?}), trial {trial}", q.text);
3211 }
3212 }
3213 }
3214
3215 #[test]
3216 fn regex_replace_all_is_one_undo_step() {
3217 let original = "a1 b22 c333";
3218 let mut d = doc(original);
3219 d.set_find_query(
3220 Some(FindQuery { text: r"\d+".into(), regex: true, ..Default::default() }),
3221 0,
3222 );
3223 assert_eq!(d.find_match_count(), 3);
3224 assert_eq!(d.replace_all("#", false), 3);
3225 assert_eq!(d.text(), "a# b# c#");
3226 assert!(d.undo());
3227 assert_eq!(d.text(), original, "a regex replace-all undoes as ONE step");
3228 }
3229
3230 #[test]
3231 fn a_regex_replacement_expands_capture_groups() {
3232 let mut d = doc("fn one()\nfn two()");
3234 d.set_find_query(
3235 Some(FindQuery { text: r"fn (\w+)\(\)".into(), regex: true, ..Default::default() }),
3236 0,
3237 );
3238 assert_eq!(d.replace_all("let $1 = 1;", false), 2);
3239 assert_eq!(d.text(), "let one = 1;\nlet two = 1;");
3240 assert!(d.undo());
3241 assert_eq!(d.text(), "fn one()\nfn two()", "still ONE undo step");
3242
3243 let mut e = doc("aaa");
3246 e.set_find_query(Some(FindQuery { text: "aaa".into(), ..Default::default() }), 0);
3247 assert_eq!(e.replace_all("$1", false), 1);
3248 assert_eq!(e.text(), "$1");
3249 }
3250
3251 #[test]
3252 fn replace_next_expands_captures_for_the_active_match_only() {
3253 let mut d = doc("fn one()\nfn two()");
3254 d.set_find_query(
3255 Some(FindQuery { text: r"fn (\w+)\(\)".into(), regex: true, ..Default::default() }),
3256 0,
3257 );
3258 d.replace_next("x", false, 0); d.replace_next("[$1]", false, 0); assert_eq!(d.text(), "[one]\nfn two()");
3261 }
3262
3263 #[test]
3264 fn preserve_case_recases_replacements_to_their_match() {
3265 let mut d = doc("FOO Foo foo fOo");
3268 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
3269 assert_eq!(d.replace_all("bar", true), 4);
3270 assert_eq!(d.text(), "BAR Bar bar bar");
3272
3273 let mut e = doc("FOO Foo foo");
3275 e.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
3276 assert_eq!(e.replace_all("bar", false), 3);
3277 assert_eq!(e.text(), "bar bar bar");
3278 }
3279
3280 #[test]
3281 fn preserve_case_applies_after_regex_expansion() {
3282 let mut d = doc("GETVALUE getvalue");
3285 d.set_find_query(
3286 Some(FindQuery { text: "(get)(value)".into(), regex: true, ..Default::default() }),
3287 0,
3288 );
3289 assert_eq!(d.replace_all("${2}_${1}", true), 2);
3292 assert_eq!(d.text(), "VALUE_GET value_get");
3294 }
3295
3296 #[test]
3297 fn an_unfinished_regex_matches_nothing_and_reports_why() {
3298 let mut d = doc("foo (bar)");
3299 d.set_find_query(
3300 Some(FindQuery { text: "(".into(), regex: true, ..Default::default() }),
3301 0,
3302 );
3303 assert_eq!(d.find_match_count(), 0, "an unfinished pattern matches nothing");
3304 assert!(d.find_pattern_error().is_some(), "…and says why");
3305 d.edit(vec![EditOp::insert(0, "x")]).unwrap();
3307 assert_eq!(d.find_match_count(), 0);
3308 d.set_find_query(
3310 Some(FindQuery { text: r"\(bar\)".into(), regex: true, ..Default::default() }),
3311 0,
3312 );
3313 assert!(d.find_pattern_error().is_none());
3314 assert_eq!(d.find_match_count(), 1);
3315 }
3316
3317 #[test]
3318 fn replace_all_without_a_match_commits_nothing() {
3319 let mut d = doc("foo");
3320 assert_eq!(d.replace_all("x", false), 0, "no query ⇒ no-op");
3321 d.set_find_query(Some(FindQuery { text: "zzz".into(), case_sensitive: false, ..Default::default() }), 0);
3322 assert_eq!(d.replace_all("x", false), 0, "no matches ⇒ no-op");
3323 assert_eq!(d.text(), "foo");
3324 assert!(!d.is_dirty(), "a no-op replace must not open an undo step");
3325 assert!(!d.undo());
3326 }
3327
3328 #[test]
3329 fn caret_moves_seal_the_typing_group() {
3330 let mut d = doc("");
3333 d.type_char('f');
3334 d.type_char('o');
3335 d.move_carets(Motion::Left, false); d.type_char('x'); assert!(d.undo());
3338 assert_eq!(d.text(), "fo", "undo reverts only the run typed after the move");
3339
3340 let mut c = doc("");
3342 c.type_char('a');
3343 c.type_char('b');
3344 c.set_selections(SelectionSet::new(0));
3345 c.type_char('z'); assert!(c.undo());
3347 assert_eq!(c.text(), "ab", "the click sealed the run before it");
3348
3349 let mut b = doc("one\ntwo");
3352 b.set_selections(SelectionSet::new(0));
3353 b.type_char('z'); b.column_drag((0, 2), (1, 2));
3355 b.type_char('q');
3356 assert!(b.undo());
3357 assert_eq!(b.text(), "zone\ntwo", "the box gesture sealed the run before it");
3358 }
3359
3360 #[test]
3365 fn line_duplication_drops_autoclose_provenance() {
3366 let ac_count = |d: &Document| d.autoclose_pair_count();
3368 let mut d = doc("x");
3369 d.move_carets(Motion::Right, false); d.type_char('('); assert_eq!(d.text(), "x()");
3372 assert_eq!(ac_count(&d), 1, "typing '(' arms one AutoClosePair decoration");
3373 d.copy_line(false);
3377 assert_eq!(d.text(), "x()\nx()");
3378 assert_eq!(
3379 ac_count(&d),
3380 0,
3381 "copy_line must clear the provenance decoration, not orphan it in the store",
3382 );
3383 }
3384
3385 #[test]
3391 fn type_bracket_then_far_diagnostics_pair_survives() {
3392 use crate::{Diagnostic, Severity};
3393 let mut d = doc("hello\nworld\n");
3394 let end = d.text().len() as u32; d.set_selections(SelectionSet::new(end));
3396 d.type_char('('); assert_eq!(d.autoclose_pair_count(), 1);
3398 d.set_diagnostics(
3400 d.revision(),
3401 vec![
3402 Diagnostic::new(0..1, Severity::Error, "e0"),
3403 Diagnostic::new(2..3, Severity::Warning, "w0"),
3404 ],
3405 );
3406 d.type_char('x');
3409 assert!(d.text().ends_with("(x)"));
3410 assert_eq!(d.autoclose_pair_count(), 1, "far diagnostics don't disturb the pair");
3411 assert_eq!(d.diagnostics_in(0..u32::MAX).count(), 2, "the diagnostics ride on");
3413 d.type_char(')');
3415 assert!(d.text().ends_with("(x)"), "overtype consumes the tracked close, no doubled )");
3416 assert_eq!(d.autoclose_pair_count(), 0, "overtype consumed the pair");
3417 }
3418
3419 #[test]
3430 fn autoclose_store_moves_identically_to_unified_store() {
3431 use crate::decorations::{DecorationKind, Stickiness};
3432 use crate::{Diagnostic, Severity};
3433 let scenario = |text: &str, carets: &[u32], steps: usize| {
3437 let mut d = doc(text);
3438 d.set_selections(SelectionSet::from_offsets(carets));
3439 d.type_char('('); let pairs = d.autoclose_ranges();
3441 assert_eq!(pairs.len(), carets.len(), "one pair per caret armed");
3442 for r in &pairs {
3445 d.decorations_mut().add_decoration(
3446 r.clone(),
3447 DecorationKind::AutoClosePair,
3448 Stickiness::AlwaysGrows,
3449 );
3450 }
3451 d.set_diagnostics(d.revision(), vec![Diagnostic::new(0..1, Severity::Warning, "d")]);
3452 let unified = |d: &Document| -> Vec<Range<u32>> {
3453 d.decorations()
3454 .iter()
3455 .filter(|r| matches!(r.kind, DecorationKind::AutoClosePair))
3456 .map(|r| r.range.clone())
3457 .collect()
3458 };
3459 assert_eq!(d.autoclose_ranges(), unified(&d), "shadow starts equal");
3460 for step in 0..steps {
3461 d.type_char('z'); assert_eq!(
3463 d.autoclose_ranges(),
3464 unified(&d),
3465 "own store diverged from the unified store at step {step}",
3466 );
3467 }
3468 };
3469 scenario("aa\nbb\ncc\ndd\nee\n", &[2, 5, 8, 11, 14], 8);
3471 scenario("solo line here\n", &[14], 10);
3473 }
3474
3475 #[test]
3476 fn different_classes_do_not_merge() {
3477 let mut d = doc("ab");
3478 d.edit_grouped(vec![EditOp::insert(2, "c")], typ(OpClass::Type)).unwrap(); d.edit_grouped(vec![EditOp::delete(0..1)], typ(OpClass::Delete)).unwrap(); assert_eq!(d.text(), "bc");
3481 assert!(d.undo()); assert_eq!(d.text(), "abc");
3483 assert!(d.undo()); assert_eq!(d.text(), "ab");
3485 }
3486
3487 #[test]
3488 fn dirty_tracking_survives_undo_and_save() {
3489 let mut d = doc("x");
3490 assert!(!d.is_dirty());
3491 d.edit(vec![EditOp::insert(1, "y")]).unwrap(); assert!(d.is_dirty());
3493 d.undo(); assert!(!d.is_dirty(), "undo back to the save point reads clean");
3495 d.redo(); assert!(d.is_dirty());
3497 d.mark_saved();
3498 assert!(!d.is_dirty());
3499 }
3500
3501 #[test]
3502 fn ctrl_d_selects_word_then_adds_occurrences_then_stops() {
3503 let mut d = doc("foo bar foo baz foo"); d.add_next_occurrence();
3506 assert_eq!(d.selections().len(), 1);
3507 let s = d.selections().newest();
3508 assert_eq!((s.start(), s.end()), (0, 3));
3509 d.add_next_occurrence();
3511 assert_eq!(d.selections().len(), 2);
3512 assert_eq!((d.selections().newest().start(), d.selections().newest().end()), (8, 11));
3513 d.add_next_occurrence();
3514 assert_eq!(d.selections().len(), 3);
3515 assert_eq!(d.selections().newest().start(), 16);
3516 d.add_next_occurrence();
3518 assert_eq!(d.selections().len(), 3, "no untaken occurrence remains");
3519 }
3520
3521 #[test]
3522 fn windowed_next_occurrence_equals_whole_text_scan() {
3523 let mut state: u64 = 0x243F_6A88_85A3_08D3;
3528 let mut next = |n: u32| {
3529 state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
3530 ((state >> 33) as u32) % n
3531 };
3532 let needles = ["ab", "ba", "abc", "cab"];
3534 for _ in 0..300 {
3535 let n = 12 + next(60); let text: String = (0..n)
3537 .map(|_| b"abc "[next(4) as usize] as char)
3538 .collect();
3539 let d = doc(&text);
3540 let needle = needles[next(needles.len() as u32) as usize];
3541 let nlen = needle.len() as u32;
3542 let all: Vec<(u32, u32)> = text
3544 .match_indices(needle)
3545 .map(|(i, _)| (i as u32, i as u32 + nlen))
3546 .collect();
3547 let taken: Vec<(u32, u32)> = all.iter().copied().filter(|_| next(2) == 0).collect();
3548 let is_taken = |s: u32| taken.iter().any(|&(st, en)| st == s && en == s + nlen);
3549 let from = next(n + 1);
3550 let got = scan_from(d.buffer(), needle, from, d.buffer().len(), &is_taken)
3551 .or_else(|| scan_from(d.buffer(), needle, 0, from, &is_taken));
3552 let want = find_next_occurrence(&text, needle, from, &taken);
3553 assert_eq!(got, want, "text {text:?} needle {needle:?} from {from} taken {taken:?}");
3554 }
3555 }
3556
3557 #[test]
3558 fn column_select_grows_widens_and_shrinks_back() {
3559 use crate::movement::ColumnDir::{Down, Left, Right, Up};
3560 let mut d = doc("abcd\nabcd\nabcd");
3562 d.set_selections(SelectionSet::new(1));
3563 d.column_select(Down);
3565 d.column_select(Down);
3566 assert_eq!(d.selections().len(), 3);
3567 assert!(d.selections().all().iter().all(|s| s.is_empty() && s.start() % 5 == 1));
3568 d.column_select(Right);
3570 d.column_select(Right);
3571 assert_eq!(d.selections().len(), 3);
3572 assert!(d.selections().all().iter().all(|s| s.end() - s.start() == 2));
3573 d.column_select(Up);
3575 d.column_select(Up);
3576 assert_eq!(d.selections().len(), 1, "shrinks back toward the anchor");
3577 d.column_select(Left);
3579 d.column_select(Left);
3580 assert_eq!(d.selections().len(), 1);
3581 assert!(d.selections().newest().is_empty());
3582 }
3583
3584 #[test]
3585 fn column_select_clamps_each_row_to_its_length() {
3586 use crate::movement::ColumnDir::{Down, Right};
3587 let mut d = doc("abcdef\nab"); d.set_selections(SelectionSet::new(0)); d.column_select(Down); for _ in 0..4 {
3592 d.column_select(Right); }
3594 let rows = d.selections().all();
3595 assert_eq!(rows.len(), 2);
3596 assert_eq!((rows[0].start(), rows[0].end()), (0, 4)); assert_eq!((rows[1].start(), rows[1].end()), (7, 9));
3599 }
3600
3601 #[test]
3602 fn column_drag_builds_a_box_from_two_corners() {
3603 let mut d = doc("abcd\nabcd\nabcd");
3604 d.column_drag((0, 1), (2, 3)); assert_eq!(d.selections().len(), 3);
3606 assert!(d.selections().all().iter().all(|s| s.end() - s.start() == 2));
3607 let mut r = doc("abcdef\nab"); r.column_drag((0, 0), (1, 5)); let rows = r.selections().all();
3611 assert_eq!((rows[0].start(), rows[0].end()), (0, 5)); assert_eq!((rows[1].start(), rows[1].end()), (7, 9)); }
3614
3615 #[test]
3616 fn column_box_is_display_cells_across_tabs() {
3617 let mut d = doc("\tabcd\nwxyz"); d.column_drag((0, 4), (1, 6));
3622 let rows = d.selections().all();
3623 assert_eq!(rows.len(), 2);
3624 assert_eq!((rows[0].start(), rows[0].end()), (1, 3), "cells 4..6 are bytes 1..3 after the tab");
3625 assert_eq!((rows[1].start(), rows[1].end()), (10, 10), "row 1 ends at cell 4; both corners clamp");
3626 }
3627
3628 #[test]
3629 fn column_box_resolves_through_a_collapsed_inline_fold() {
3630 let mut d = doc("f([a, b]) x");
3634 assert!(d.toggle_fold_opener(2)); d.column_drag((0, 6), (0, 9));
3636 let s = d.selections().newest();
3637 assert_eq!((s.start(), s.end()), (7, 10), "cells 6..9 are `]) `, one left of the byte columns");
3638 }
3639
3640 #[test]
3641 fn column_box_spans_visible_rows_only() {
3642 let mut d = doc("aa\nfn {\nbb\ncc\n}\ndd");
3645 assert!(d.toggle_fold_opener(6)); d.column_drag((0, 0), (5, 1));
3647 let rows: Vec<u32> =
3648 d.selections().all().iter().map(|s| d.buffer().offset_to_point(s.start()).row).collect();
3649 assert_eq!(rows, vec![0, 1, 5], "one selection per VISIBLE row");
3650 }
3651
3652 #[test]
3653 fn column_select_steps_display_rows_over_a_fold() {
3654 use crate::movement::ColumnDir::Up;
3655 let mut d = doc("aa\nfn {\nbb\ncc\n}\ndd");
3658 assert!(d.toggle_fold_opener(6));
3659 d.set_selections(SelectionSet::new(16)); d.column_select(Up);
3661 let rows: Vec<u32> =
3662 d.selections().all().iter().map(|s| d.buffer().offset_to_point(s.start()).row).collect();
3663 assert_eq!(rows, vec![1, 5], "the step lands on the fold header, skipping hidden rows");
3664 }
3665
3666 #[test]
3667 fn column_select_anchors_at_the_display_cell_after_a_tab() {
3668 use crate::movement::ColumnDir::Down;
3669 let mut d = doc("\tab\nwwwwww"); d.set_selections(SelectionSet::new(1)); d.column_select(Down);
3675 assert_eq!(d.selections().newest().head(), 8, "cell 4 on row 1 is byte 4 (offset 8)");
3676 }
3677
3678 #[test]
3679 fn any_action_exits_column_mode() {
3680 use crate::movement::ColumnDir::Down;
3681 let mut d = doc("abcd\nabcd\nabcd");
3682 d.set_selections(SelectionSet::new(0));
3683 d.column_select(Down); assert_eq!(d.selections().len(), 2);
3685 d.move_carets(Motion::Right, false); d.column_select(Down);
3689 assert_eq!(d.selections().len(), 2, "re-anchored, not continuing the old box");
3690 }
3691
3692 fn range(d: &Document) -> (u32, u32) {
3693 (d.selections().newest().start(), d.selections().newest().end())
3694 }
3695
3696 #[test]
3697 fn drag_select_word_granularity_keeps_the_origin_word() {
3698 let mut d = doc("foo bar baz\nqux"); d.drag_select(Granularity::Word, 5, 5);
3701 assert_eq!(range(&d), (4, 7));
3702 d.drag_select(Granularity::Word, 5, 9);
3704 assert_eq!(range(&d), (4, 11));
3705 d.drag_select(Granularity::Word, 5, 1);
3707 assert_eq!(range(&d), (0, 7));
3708 let mut w = doc("a b");
3710 w.drag_select(Granularity::Word, 2, 2);
3711 assert!(w.selections().newest().is_empty());
3712 }
3713
3714 #[test]
3715 fn drag_select_line_and_char_granularity() {
3716 let mut d = doc("aa\nbb\ncc"); d.drag_select(Granularity::Line, 1, 1); assert_eq!(range(&d), (0, 3));
3719 d.drag_select(Granularity::Line, 1, 4); assert_eq!(range(&d), (0, 6));
3721 d.drag_select(Granularity::Line, 7, 7); assert_eq!(range(&d), (6, 8));
3723 d.drag_select(Granularity::Char, 2, 8); assert_eq!(range(&d), (2, 8));
3725 }
3726
3727 fn head_row(d: &Document) -> u32 {
3728 d.buffer().offset_to_point(d.selections().newest().head()).row
3729 }
3730
3731 #[test]
3732 fn move_line_swaps_with_neighbour_and_rides() {
3733 let mut d = doc("aaa\nbbb\nccc");
3734 d.set_selections(SelectionSet::new(4)); d.move_line(true); assert_eq!(d.text(), "aaa\nccc\nbbb");
3737 assert_eq!(head_row(&d), 2); d.move_line(false); assert_eq!(d.text(), "aaa\nbbb\nccc");
3740 assert_eq!(head_row(&d), 1);
3741 }
3742
3743 #[test]
3744 fn move_line_is_a_noop_at_the_edges() {
3745 let mut d = doc("aaa\nbbb");
3746 d.set_selections(SelectionSet::new(0));
3747 d.move_line(false); assert_eq!(d.text(), "aaa\nbbb");
3749 d.set_selections(SelectionSet::new(4)); d.move_line(true); assert_eq!(d.text(), "aaa\nbbb");
3752 }
3753
3754 #[test]
3755 fn move_line_down_respects_the_trailing_empty_line() {
3756 let mut d = doc("aaa\nbbb\n"); d.set_selections(SelectionSet::new(4)); d.move_line(true); assert_eq!(d.text(), "aaa\nbbb\n");
3760 }
3761
3762 #[test]
3763 fn copy_line_duplicates_below_and_above() {
3764 let mut d = doc("aaa\nbbb");
3765 d.set_selections(SelectionSet::new(0));
3766 d.copy_line(true); assert_eq!(d.text(), "aaa\naaa\nbbb");
3768 assert_eq!(head_row(&d), 1);
3769 let mut d2 = doc("aaa\nbbb");
3770 d2.set_selections(SelectionSet::new(0));
3771 d2.copy_line(false); assert_eq!(d2.text(), "aaa\naaa\nbbb");
3773 assert_eq!(head_row(&d2), 0);
3774 }
3775
3776 #[test]
3777 fn copy_line_duplicates_the_final_line() {
3778 let mut d = doc("aaa\nbbb"); d.set_selections(SelectionSet::new(4)); d.copy_line(true);
3781 assert_eq!(d.text(), "aaa\nbbb\nbbb");
3782 assert_eq!(head_row(&d), 2);
3783 }
3784
3785 #[test]
3786 fn add_caret_adds_a_second_cursor() {
3787 let mut d = doc("abcdef");
3788 assert_eq!(d.selections().len(), 1);
3789 d.add_caret(3);
3790 assert_eq!(d.selections().len(), 2);
3791 }
3792
3793 #[test]
3794 fn collapse_returns_to_the_primary_caret() {
3795 let mut d = doc("foo foo foo");
3796 d.add_next_occurrence(); d.add_next_occurrence(); assert!(d.selections().len() >= 2);
3799 d.collapse_selections();
3800 assert_eq!(d.selections().len(), 1);
3801 assert!(d.selections().newest().is_empty());
3802 }
3803
3804 #[test]
3805 fn find_next_occurrence_scans_forward_then_wraps() {
3806 assert_eq!(super::find_next_occurrence("aXaYa", "a", 1, &[(0, 1)]), Some((2, 3)));
3808 assert_eq!(super::find_next_occurrence("aXaYa", "a", 5, &[(2, 3), (4, 5)]), Some((0, 1)));
3810 assert_eq!(super::find_next_occurrence("aa", "a", 0, &[(0, 1), (1, 2)]), None);
3812 }
3813
3814 #[test]
3815 fn dirty_after_undo_then_divergent_edit() {
3816 let mut d = doc("abc");
3819 d.edit(vec![EditOp::delete(2..3)]).unwrap(); d.mark_saved();
3821 d.undo(); assert!(d.is_dirty(), "undone away from the save point → dirty");
3823 d.edit(vec![EditOp::delete(0..1)]).unwrap(); assert!(d.is_dirty(), "divergent edit is still dirty, never falsely clean");
3825 }
3826
3827 #[test]
3828 fn highlight_cache_splice_tracks_line_count_across_edits() {
3829 const G: &str = "%YAML 1.2\n---\nname: T\nscope: source.t\ncontexts:\n main:\n - match: '\\w+'\n scope: keyword.t\n";
3831 const TH: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
3832<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3833<plist version="1.0"><dict><key>settings</key><array><dict><key>settings</key><dict><key>foreground</key><string>#FFFFFF</string></dict></dict></array></dict></plist>"#;
3834 let mut d = doc("ab\ncd");
3835 d.set_syntax(
3836 crate::SyntaxDef::from_sublime_syntax(G).unwrap(),
3837 crate::TokenTheme::from_tm_theme(TH).unwrap(),
3838 );
3839 d.tokenize_highlight(d.buffer().line_count());
3840 assert!(d.highlight_line_spans(1).is_some());
3841 d.set_selections(SelectionSet::new(2)); d.enter();
3844 d.tokenize_highlight(d.buffer().line_count());
3845 assert_eq!(d.buffer().line_count(), 3);
3846 assert!(d.highlight_line_spans(2).is_some(), "the new last line is tokenized");
3847 d.backspace();
3849 d.tokenize_highlight(d.buffer().line_count());
3850 assert_eq!(d.buffer().line_count(), 2);
3851 assert!(d.highlight_line_spans(1).is_some());
3852 assert!(d.highlight_line_spans(2).is_none(), "no spans past the buffer");
3853 }
3854
3855 #[test]
3856 fn multi_op_transaction_highlight_equals_a_fresh_document() {
3857 const G: &str = "%YAML 1.2\n---\nname: T\nscope: source.t\ncontexts:\n main:\n - match: '\\w+'\n scope: keyword.t\n";
3864 const TH: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
3865<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3866<plist version="1.0"><dict><key>settings</key><array><dict><key>settings</key><dict><key>foreground</key><string>#FFFFFF</string></dict></dict></array></dict></plist>"#;
3867 let make = |text: &str| -> Document {
3868 let mut d = doc(text);
3869 d.set_syntax(
3870 crate::SyntaxDef::from_sublime_syntax(G).unwrap(),
3871 crate::TokenTheme::from_tm_theme(TH).unwrap(),
3872 );
3873 d.tokenize_highlight(u32::MAX);
3874 d
3875 };
3876 let mut d = make("aa bb cc\ndd ee ff\ngg hh ii\njj kk ll\nmm nn oo");
3879 d.edit_grouped(
3883 vec![
3884 EditOp::new(0..2, "XYZ"), EditOp::new(9..11, "Q"), EditOp::new(15..17, "RR"), EditOp::insert(27, "NEW\n"), ],
3889 typ(OpClass::Type),
3890 )
3891 .unwrap();
3892 d.tokenize_highlight(u32::MAX);
3893
3894 let text = d.text();
3895 let f = make(&text);
3896 assert_eq!(d.buffer().line_count(), f.buffer().line_count(), "line count");
3897 for r in 0..d.buffer().line_count() {
3898 assert_eq!(
3899 d.highlight_line_spans(r),
3900 f.highlight_line_spans(r),
3901 "row {r} live (per-edit invalidation) vs fresh"
3902 );
3903 }
3904 }
3905
3906 #[test]
3911 fn set_syntax_preserves_the_highlight_window_aim() {
3912 const G: &str = "%YAML 1.2\n---\nname: T\nscope: source.t\ncontexts:\n main:\n - match: '\\w+'\n scope: keyword.t\n";
3913 const TH: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
3914<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3915<plist version="1.0"><dict><key>settings</key><array><dict><key>settings</key><dict><key>foreground</key><string>#FFFFFF</string></dict></dict></array></dict></plist>"#;
3916 let syntax = || crate::SyntaxDef::from_sublime_syntax(G).unwrap();
3917 let theme = || crate::TokenTheme::from_tm_theme(TH).unwrap();
3918 let mut d = doc(&"word\n".repeat(3_000));
3919 d.set_syntax(syntax(), theme());
3920 d.set_highlight_window(2_000..2_040);
3922 d.set_syntax(syntax(), theme());
3924 while d.highlight_frontier().is_some() {
3925 d.tokenize_highlight(u32::MAX);
3926 }
3927 assert!(
3928 d.highlight_line_spans(2_020).is_some(),
3929 "the swapped cache must still retain the viewport rows"
3930 );
3931 }
3932
3933 const HL_G: &str = "%YAML 1.2\n---\nname: T\nscope: source.t\ncontexts:\n main:\n - match: '\\bkw\\b'\n scope: keyword.t\n";
3938 const HL_TH: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
3939<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3940<plist version="1.0"><dict><key>settings</key><array><dict><key>settings</key><dict><key>foreground</key><string>#FFFFFF</string></dict></dict></array></dict></plist>"#;
3941
3942 fn doc_with_syntax(n: usize) -> Document {
3943 let mut d = doc(&"kw word\n".repeat(n));
3944 d.set_syntax(
3945 crate::SyntaxDef::from_sublime_syntax(HL_G).unwrap(),
3946 crate::TokenTheme::from_tm_theme(HL_TH).unwrap(),
3947 );
3948 d
3949 }
3950
3951 #[test]
3952 fn absorb_highlight_drops_a_stale_revision() {
3953 let mut d = doc_with_syntax(2_000);
3954 let engine = d.highlight_engine().unwrap();
3955 let snap = d.snapshot();
3956 let stale_rev = d.revision();
3957 let seg = crate::tokenize_segment(&engine, &snap, 0..2_001, crate::SegmentStart::Fresh, None, None);
3958 d.set_selections(SelectionSet::new(0));
3960 d.type_char('x');
3961 assert_ne!(d.revision(), stale_rev);
3962 assert!(!d.absorb_highlight(stale_rev, seg, true), "stale segment must be dropped");
3964 assert_eq!(d.highlight_frontier(), Some(0), "the frontier is untouched by a dropped absorb");
3965 }
3966
3967 #[test]
3968 fn absorb_highlight_verified_clears_the_frontier() {
3969 let mut d = doc_with_syntax(5_000);
3970 let engine = d.highlight_engine().unwrap();
3971 let snap = d.snapshot();
3972 let rev = d.revision();
3973 let n = d.buffer().line_count();
3974 let seg =
3978 crate::tokenize_segment(&engine, &snap, 0..n, crate::SegmentStart::Fresh, Some(0..n), None);
3979 assert!(d.absorb_highlight(rev, seg, true));
3980 assert_eq!(d.highlight_frontier(), None, "verified absorb clears every dirty row");
3981 }
3982
3983 #[test]
3984 fn absorb_highlight_speculative_shows_spans_but_keeps_dirt() {
3985 let mut d = doc_with_syntax(2_000);
3986 d.set_highlight_window(900..940);
3987 let engine = d.highlight_engine().unwrap();
3988 let snap = d.snapshot();
3989 let rev = d.revision();
3990 let seg = crate::tokenize_segment(
3992 &engine,
3993 &snap,
3994 772..940,
3995 crate::SegmentStart::Fresh,
3996 Some(900..940),
3997 None,
3998 );
3999 assert!(d.absorb_highlight(rev, seg, false));
4000 assert!(d.highlight_line_spans(920).is_some(), "speculative spans are visible");
4002 assert_eq!(d.highlight_frontier(), Some(0), "speculation keeps the frontier");
4004 while d.highlight_frontier().is_some() {
4006 d.tokenize_highlight(u32::MAX);
4007 }
4008 assert!(d.highlight_line_spans(920).is_some());
4009 }
4010
4011 #[test]
4012 fn diagnostics_install_then_ride_a_forward_edit() {
4013 use crate::{Diagnostic, DiagnosticsOutcome, Severity};
4014 let mut d = doc("let x = 1;");
4015 let rev = d.revision();
4016 let out = d.set_diagnostics(rev, vec![Diagnostic::new(4..5, Severity::Error, "unused")]);
4017 assert!(matches!(out, DiagnosticsOutcome::Applied { count: 1 }));
4018 let got: Vec<_> = d.diagnostics_in(0..100).collect();
4019 assert_eq!((got.len(), got[0].0.clone(), got[0].1), (1, 4..5, Severity::Error));
4020 d.edit(vec![EditOp::insert(0, "abcd")]).unwrap();
4022 assert_eq!(d.diagnostics_in(0..100).next().unwrap().0, 8..9, "rode via apply_patch");
4023 }
4024
4025 #[test]
4026 fn stale_diagnostic_set_is_dropped_and_the_prior_kept() {
4027 use crate::{Diagnostic, DiagnosticsOutcome, Severity};
4028 let mut d = doc("abcdef");
4029 let rev = d.revision();
4030 d.set_diagnostics(rev, vec![Diagnostic::new(0..1, Severity::Warning, "w")]);
4031 d.edit(vec![EditOp::insert(0, "z")]).unwrap(); let out = d.set_diagnostics(rev, vec![Diagnostic::new(3..4, Severity::Error, "e")]);
4033 assert!(matches!(out, DiagnosticsOutcome::Stale { .. }), "old-revision set dropped");
4034 let got: Vec<_> = d.diagnostics_in(0..100).collect();
4035 assert_eq!((got.len(), got[0].0.clone(), got[0].1), (1, 1..2, Severity::Warning));
4036 }
4037
4038 #[test]
4039 fn diagnostic_spans_clip_to_the_buffer_length() {
4040 use crate::{Diagnostic, Severity};
4041 let mut d = doc("abc"); let rev = d.revision();
4043 d.set_diagnostics(rev, vec![Diagnostic::new(2..99, Severity::Error, "past eof")]);
4044 assert_eq!(d.diagnostics_in(0..100).next().unwrap().0, 2..3, "clipped to buffer len");
4045 }
4046
4047 #[test]
4048 fn rend04_diagnostic_render_row_shifts_when_lines_inserted_above() {
4049 use crate::{Diagnostic, Point, Severity};
4053 let mut d = doc("L0\nL1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9");
4054 let row5 = d.buffer().point_to_offset(Point { row: 5, col: 0 });
4055 d.set_diagnostics(d.revision(), vec![Diagnostic::new(row5..row5 + 2, Severity::Error, "e")]);
4056 let start = d.diagnostics_in(0..u32::MAX).next().unwrap().0.start;
4057 assert_eq!(d.buffer().offset_to_point(start).row, 5);
4058 d.edit(vec![EditOp::insert(0, "a\nb\nc\n")]).unwrap();
4060 let span = d.diagnostics_in(0..u32::MAX).next().unwrap().0;
4061 assert_eq!(d.buffer().offset_to_point(span.start).row, 8, "5 + 3 inserted lines");
4062 assert_eq!(&d.text()[span.start as usize..span.end as usize], "L5", "still glued to its code");
4063 }
4064
4065 #[test]
4071 fn windowed_fold_queries_match_the_whole_document() {
4072 let mut d = doc(
4073 "top\n\
4074 a {\n b [1, 2]\n c {\n d\n }\n}\n\
4075 mid\n\
4076 e (x,\n y)\n\
4077 f {\n g\n}\n\
4078 end",
4079 );
4080 let n = d.buffer().line_count();
4081 for row in 0..n {
4083 let want = d
4084 .foldable_pairs()
4085 .into_iter()
4086 .filter(|&(_, _, h, l)| h == row && l > h)
4087 .max_by_key(|&(_, _, h, l)| l - h)
4088 .map(|(o, ..)| o);
4089 assert_eq!(d.block_opener_on_row(row), want, "block_opener_on_row row {row}");
4090 }
4091 assert_eq!(d.collapsible_pairs_in_rows(0..n), d.collapsible_pairs());
4093 for (a, b) in [(0u32, 3u32), (2, 6), (3, 4), (7, n)] {
4095 let want: Vec<_> =
4096 d.collapsible_pairs().into_iter().filter(|&(_, _, h, _)| a <= h && h < b).collect();
4097 assert_eq!(d.collapsible_pairs_in_rows(a..b), want, "collapsible_pairs_in_rows {a}..{b}");
4098 }
4099 for row in 0..n {
4101 let windowed: Vec<_> =
4102 d.foldable_ranges_in_rows(row..row + 1).into_iter().filter(|(h, _)| h.0 == row).collect();
4103 let whole: Vec<_> =
4104 d.foldable_ranges().into_iter().filter(|(h, _)| h.0 == row).collect();
4105 assert_eq!(windowed, whole, "foldable_ranges row {row}");
4106 }
4107 let opener = d.block_opener_on_row(1).unwrap();
4109 assert!(d.toggle_fold_opener(opener));
4110 assert_eq!(d.collapsible_pairs_in_rows(0..n), d.collapsible_pairs());
4111 }
4112
4113 #[test]
4114 fn fold_survives_undo_redo() {
4115 use crate::{fold_map::FoldMap, BufferRow};
4120 let mut d = doc("L0\nL1\nL2\nL3\nL4\nblk {\n x\n y\n}\nL9");
4121 assert_eq!(d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect::<Vec<_>>(), vec![(5, 8)]);
4122 assert!(d.fold(BufferRow(5), BufferRow(8)));
4123 let header = |d: &Document| FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(5));
4124 let header8 = |d: &Document| FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(8));
4125 assert_eq!(header(&d), Some(BufferRow(8)));
4126 d.edit(vec![EditOp::insert(0, "a\nb\nc\n")]).unwrap();
4128 assert_eq!(header8(&d), Some(BufferRow(11)));
4129 assert!(d.undo());
4131 assert_eq!(header(&d), Some(BufferRow(8)));
4132 assert!(d.redo());
4133 assert_eq!(header8(&d), Some(BufferRow(11)));
4134 }
4135
4136 #[test]
4137 fn pasting_after_the_collapsed_line_keeps_the_outer_fold() {
4138 use crate::{fold_map::FoldMap, BufferRow, SelectionSet};
4139 let mut d = doc("{\n\t{\n\t}\n}\n");
4141 assert_eq!(d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect::<Vec<_>>(), vec![(0, 3), (1, 2)]);
4142 assert!(d.fold(BufferRow(0), BufferRow(3))); let block = d.buffer().text()[0..9].to_string();
4146 d.set_selections(SelectionSet::new(9));
4147 d.insert_text(&block);
4148 assert_eq!(d.folds().len(), 1, "the outer fold survives the paste");
4151 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(0)), Some(BufferRow(3)));
4152 }
4153
4154 #[test]
4155 fn editing_after_the_brace_keeps_a_folded_block() {
4156 use crate::{fold_map::FoldMap, BufferRow, SelectionSet};
4157 let mut d = doc("{\n}");
4158 assert!(d.fold(BufferRow(0), BufferRow(1)));
4159 d.set_selections(SelectionSet::new(d.buffer().len()));
4161 d.insert_text("abc");
4162 assert_eq!(d.buffer().text(), "{\n}abc");
4163 assert_eq!(d.folds().len(), 1, "fold survives typing after the brace");
4164 let end = d.buffer().len();
4166 d.edit(vec![EditOp::delete(end - 3..end)]).unwrap();
4167 assert_eq!(d.buffer().text(), "{\n}");
4168 assert_eq!(d.folds().len(), 1, "fold survives deleting after the brace");
4169 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(0)), Some(BufferRow(1)));
4170 }
4171
4172 #[test]
4173 fn pressing_return_after_a_folded_eof_block_keeps_it_folded() {
4174 use crate::{fold_map::FoldMap, BufferRow, SelectionSet};
4175 let mut d = doc("{\n}");
4178 assert!(d.fold(BufferRow(0), BufferRow(1)));
4179 d.set_selections(SelectionSet::new(d.buffer().len()));
4182 d.insert_text("\n");
4183 assert_eq!(d.buffer().text(), "{\n}\n");
4184 assert_eq!(d.folds().len(), 1, "the intact block stays folded");
4185 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(0)), Some(BufferRow(1)));
4186 }
4187
4188 #[test]
4189 fn cutting_trailing_content_keeps_an_intact_fold() {
4190 use crate::{fold_map::FoldMap, BufferRow};
4191 let mut d = doc("{\n}\nX");
4193 assert!(d.fold(BufferRow(0), BufferRow(1))); let a = d.buffer().point_to_offset(crate::Point::new(1, 1)); let b = d.buffer().point_to_offset(crate::Point::new(2, 1)); d.edit(vec![EditOp::delete(a..b)]).unwrap();
4200 assert_eq!(d.buffer().text(), "{\n}", "only the trailing content was removed");
4201 assert_eq!(d.folds().len(), 1, "the intact block stays folded");
4202 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(0)), Some(BufferRow(1)));
4203 }
4204
4205 #[test]
4206 fn cutting_across_a_folds_boundary_drops_it_not_drifts_it() {
4207 use crate::{fold_map::FoldMap, BufferRow};
4208 let mut d = doc("{\n}\n{\n}\n{\n}\n");
4210 assert!(d.fold(BufferRow(2), BufferRow(3))); let a = d.buffer().point_to_offset(crate::Point::new(1, 1)); let b = d.buffer().point_to_offset(crate::Point::new(3, 1)); d.edit(vec![EditOp::delete(a..b)]).unwrap();
4217 assert!(d.folds().is_empty(), "fold dropped, not drifted onto the first block");
4220 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(0)), None, "first set not folded");
4221 }
4222
4223 #[test]
4224 fn typing_at_many_carets_over_folds_stays_linear() {
4225 use crate::bracket::ENCLOSING_WALKS;
4226 use crate::row_layout::DISPLAY_POSITION_PROBES;
4227 use crate::{Motion, SelectionSet};
4228 let mut src = String::new();
4235 for i in 0..400 {
4236 src.push_str(&format!(
4237 "// block {i}\nfn pid_{i}(pid: u8) -> u8 {{\n switch pid {{\n 0x0D => {{ return 88; }}\n }}\n}}\n\n"
4238 ));
4239 }
4240 let mut d = doc(&src);
4241 let first = d.buffer().text().find("fn pid").unwrap() as u32;
4242 d.set_selections(SelectionSet::from_ranges(&[(first, first + 6)], 0));
4243 d.select_all_occurrences();
4244 let carets = d.selections().all().len();
4245 assert!(carets >= 400, "select-all found every block ({carets} carets)");
4246 d.move_carets(Motion::LineEnd, false);
4247 d.fold_at_carets(false);
4248 d.move_carets(Motion::LineEnd, false);
4249
4250 ENCLOSING_WALKS.with(|c| c.set(0));
4252 DISPLAY_POSITION_PROBES.with(|c| c.set(0));
4253 d.insert_text("a");
4254 let walks = ENCLOSING_WALKS.with(std::cell::Cell::get);
4255 let probes = DISPLAY_POSITION_PROBES.with(std::cell::Cell::get);
4256 assert!(walks <= 2, "one enclosing walk per commit, not O(carets): {walks} for {carets} carets");
4257 assert!(
4258 probes <= 4 * carets as u64,
4259 "display-position probes are O(edit points), not O(carets²): {probes} for {carets} carets"
4260 );
4261 }
4262
4263 #[test]
4264 fn folded_keystroke_never_resolves_bracket_depth() {
4265 use crate::bracket_tree::BRACKET_VIEW_CALLS;
4266 use crate::SelectionSet;
4267 let mut src = String::new();
4278 for i in 0..300 {
4279 src.push_str(&format!("fn f_{i}() {{\n body\n}}\n"));
4280 }
4281 let mut d = doc(&src);
4282 let openers: Vec<u32> = d
4284 .brackets()
4285 .all()
4286 .iter()
4287 .filter(|b| b.open && d.buffer().char_at(b.offset) == Some('{'))
4288 .map(|b| b.offset)
4289 .collect();
4290 assert_eq!(openers.len(), 300, "one foldable block opener each");
4291 for &o in &openers {
4292 assert!(d.toggle_fold_opener(o));
4293 }
4294 let carets: Vec<(u32, u32)> = openers.iter().map(|&o| (o + 1, o + 1)).collect();
4296 d.set_selections(SelectionSet::from_ranges(&carets, 0));
4297 let _ = d.fold_map(); BRACKET_VIEW_CALLS.with(|c| c.set(0));
4300 d.insert_text("a");
4301 let _ = d.fold_map(); let depth_resolves = BRACKET_VIEW_CALLS.with(std::cell::Cell::get);
4303 assert_eq!(
4304 depth_resolves, 0,
4305 "a fold-heavy keystroke resolved bracket depth {depth_resolves} times over 300 folds \
4306 — the edit path must use foldable_partner, not at()/bracket_view"
4307 );
4308 }
4309
4310 #[test]
4311 fn single_line_pair_folds_inline() {
4312 use crate::{fold_map::FoldMap, SelectionSet};
4313 let mut d = doc("x = [1, 2, 3]");
4315 d.set_selections(SelectionSet::new(6)); let opener = d.fold_opener_at_caret(false).expect("an enclosing foldable pair");
4317 assert_eq!(opener, 4);
4318 assert!(d.toggle_fold_opener(opener));
4319 let m = FoldMap::new(d.folds(), d.brackets(), d.buffer());
4320 assert_eq!(m.display_row_count(), 1, "an inline fold hides no rows");
4321 assert_eq!(m.inline_folds().len(), 1);
4322 assert_eq!((m.inline_folds()[0].row, m.inline_folds()[0].open, m.inline_folds()[0].close), (0, 4, 12));
4323 assert!(d.toggle_fold_opener(4));
4325 assert!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).inline_folds().is_empty());
4326 }
4327
4328 #[test]
4329 fn fold_at_carets_acts_on_every_caret() {
4330 use crate::{fold_map::FoldMap, SelectionSet};
4331 let mut d = doc("a{x}\nb{y}");
4333 d.set_selections(SelectionSet::from_offsets(&[2, 7])); assert!(d.fold_at_carets(false), "Ctrl+Shift+[ folds at every caret");
4335 let m = FoldMap::new(d.folds(), d.brackets(), d.buffer());
4336 assert_eq!(m.inline_folds().len(), 2, "both blocks collapsed, not just the primary");
4337 assert!(d.fold_at_carets(true), "Ctrl+Shift+] unfolds at every caret");
4339 assert!(
4340 FoldMap::new(d.folds(), d.brackets(), d.buffer()).inline_folds().is_empty(),
4341 "both blocks expanded",
4342 );
4343 }
4344
4345 #[test]
4346 fn fold_map_cache_matches_a_fresh_build_across_changes() {
4347 fn consistent(d: &Document) -> bool {
4353 *d.fold_map() == crate::fold_map::FoldMap::new(d.folds(), d.brackets(), d.buffer())
4354 }
4355 let mut d = doc("a {\nx\n}\nb = [1, 2, 3]\nc\n");
4356 assert!(consistent(&d), "empty folds");
4357 let block = d.text().find('{').unwrap() as u32;
4359 let inline = d.text().find('[').unwrap() as u32;
4360 d.toggle_fold_opener(block);
4361 d.toggle_fold_opener(inline);
4362 assert!(consistent(&d), "after folds — rebuilt on the generation");
4363 d.edit(vec![EditOp::insert(0, "Z")]).unwrap();
4366 assert!(consistent(&d), "after a no-line-change edit — shifted in place");
4367 d.edit(vec![EditOp::insert(0, "Y")]).unwrap();
4368 assert!(consistent(&d), "after a second in-place shift (cumulative)");
4369 d.edit(vec![EditOp::insert(0, "\n")]).unwrap();
4371 assert!(consistent(&d), "after a line change — rebuilt");
4372 d.edit(vec![EditOp::insert(0, "W")]).unwrap();
4373 assert!(consistent(&d), "after an edit following a rebuild");
4374 }
4375
4376 #[test]
4377 fn deleting_into_a_folded_interior_rebuilds_not_drifts() {
4378 let consistent = |d: &Document| {
4385 *d.fold_map() == crate::fold_map::FoldMap::new(d.folds(), d.brackets(), d.buffer())
4386 };
4387 for prefix in ["xx\n", ""] {
4388 let mut d = doc(&format!("{prefix}fn foo() {{\n body\n}}\nafter\n"));
4391 let open = d.text().find('{').unwrap() as u32;
4392 d.toggle_fold_opener(open);
4393 let _ = d.fold_map();
4394 assert!(consistent(&d), "{prefix:?}: folded");
4395 let nl = open + d.text()[open as usize..].find('\n').unwrap() as u32;
4396 d.edit(vec![EditOp::delete(nl..nl + 1)]).unwrap(); assert!(consistent(&d), "{prefix:?}: after deleting the header-terminating newline");
4398 }
4399 }
4400
4401 #[test]
4402 fn fold_map_cache_matches_fresh_build_under_random_edits() {
4403 let consistent = |d: &Document| {
4409 *d.fold_map() == crate::fold_map::FoldMap::new(d.folds(), d.brackets(), d.buffer())
4410 };
4411 let mut state = 0xF01D_5EEDu32;
4412 let mut next = || {
4413 state ^= state << 13;
4414 state ^= state >> 17;
4415 state ^= state << 5;
4416 state
4417 };
4418 let mut d = doc(&"fn f() {\n x = [1, 2]\n y\n}\n".repeat(6));
4419 for open in d.collapsible_pairs().iter().map(|&(o, ..)| o).collect::<Vec<_>>() {
4420 d.toggle_fold_opener(open);
4421 }
4422 assert!(consistent(&d), "after collapse-all");
4423 for step in 0..1500 {
4424 let len = d.buffer().len();
4425 if len == 0 {
4426 break;
4427 }
4428 let at = next() % (len + 1);
4429 let _ = match next() % 6 {
4430 0 => d.edit(vec![EditOp::insert(at, "\n")]), 1 => d.edit(vec![EditOp::insert(at, "z")]), 2 => d.edit(vec![EditOp::insert(at, "ab\ncd")]), 3 => d.edit(vec![EditOp::insert(at, " ")]), _ if at < len => {
4435 let end = (at + 1 + next() % 5).min(len); d.edit(vec![EditOp::delete(at..end)])
4437 }
4438 _ => d.edit(vec![EditOp::insert(at, "q")]),
4439 };
4440 assert!(consistent(&d), "step {step}: cache drifted from a fresh build (at {at})");
4441 }
4442 }
4443
4444 #[test]
4445 fn keystroke_and_arrow_do_not_rebuild_the_fold_map_at_scale() {
4446 use crate::fold_map::FOLD_BUILDS;
4452 let mut text = String::new();
4453 for i in 0..300 {
4454 text.push_str(&format!("fn f{i}() {{\n body\n}}\n"));
4455 }
4456 let mut d = doc(&text);
4457 let opens: Vec<u32> = d.collapsible_pairs().into_iter().map(|(o, ..)| o).collect();
4458 assert!(opens.len() >= 300, "every block is foldable");
4459 d.set_selections(SelectionSet::from_offsets(&opens));
4460 d.fold_at_carets(false); d.set_selections(SelectionSet::new(0)); let _ = d.fold_map(); let base = FOLD_BUILDS.with(std::cell::Cell::get);
4466 d.type_char('x');
4467 let _ = d.fold_map();
4468 assert_eq!(
4469 FOLD_BUILDS.with(std::cell::Cell::get) - base,
4470 0,
4471 "a keystroke over {} folds must not rebuild the FoldMap",
4472 opens.len()
4473 );
4474
4475 let base = FOLD_BUILDS.with(std::cell::Cell::get);
4477 d.move_carets(Motion::Down, false);
4478 let _ = d.fold_map();
4479 assert_eq!(
4480 FOLD_BUILDS.with(std::cell::Cell::get) - base,
4481 0,
4482 "an arrow key over {} folds must not rebuild the FoldMap",
4483 opens.len()
4484 );
4485
4486 d.set_selections(SelectionSet::new(0));
4492 let base = FOLD_BUILDS.with(std::cell::Cell::get);
4493 d.edit(vec![EditOp::insert(0, "\n")]).unwrap();
4494 let _ = d.fold_map();
4495 assert_eq!(
4496 FOLD_BUILDS.with(std::cell::Cell::get) - base,
4497 0,
4498 "a newline at the top over {} folds must not rebuild the FoldMap",
4499 opens.len()
4500 );
4501 }
4502
4503 #[test]
4504 fn keystroke_with_document_scale_decorations_does_not_resort_the_store() {
4505 use crate::decorations::DECORATION_SORTS;
4511 use crate::{Diagnostic, Severity};
4512 let mut d = doc(&"let x = 1;\n".repeat(2000)); let rev = d.revision();
4514 let diags: Vec<Diagnostic> = (0..2000u32)
4515 .map(|i| Diagnostic::new(i * 10..i * 10 + 3, Severity::Warning, "w"))
4516 .collect();
4517 d.set_diagnostics(rev, diags); d.set_selections(SelectionSet::new(0));
4519
4520 let base = DECORATION_SORTS.with(std::cell::Cell::get);
4521 d.type_char('z');
4522 assert_eq!(
4523 DECORATION_SORTS.with(std::cell::Cell::get) - base,
4524 0,
4525 "a keystroke with 2000 diagnostics must not re-sort the decoration store"
4526 );
4527 }
4528
4529 #[test]
4530 fn reconcile_scans_the_fold_set_only_when_a_fold_actually_breaks() {
4531 use crate::fold_map::RECONCILE_SCANS;
4539 let mut d = doc("fn f() {\n body\n}\ntail\n");
4540 let open = d.text().find('{').unwrap() as u32;
4541 assert!(d.toggle_fold_opener(open));
4542 d.set_selections(SelectionSet::new(0));
4543
4544 for text in ["a", "\n", "{"] {
4549 let base = RECONCILE_SCANS.with(std::cell::Cell::get);
4550 d.edit(vec![EditOp::insert(0, text)]).unwrap();
4551 assert_eq!(
4552 RECONCILE_SCANS.with(std::cell::Cell::get) - base,
4553 0,
4554 "inserting {text:?} breaks no fold, so it must not scan the fold set"
4555 );
4556 }
4557
4558 let close = d.text().rfind('}').unwrap() as u32;
4560 let base = RECONCILE_SCANS.with(std::cell::Cell::get);
4561 d.edit(vec![EditOp::delete(close..close + 1)]).unwrap();
4562 assert!(
4563 RECONCILE_SCANS.with(std::cell::Cell::get) - base >= 1,
4564 "deleting a brace that breaks a fold must reconcile the fold set"
4565 );
4566 assert!(d.folds().is_empty(), "the broken fold was dropped");
4567 }
4568
4569 #[test]
4570 fn windowed_reconcile_matches_the_whole_set_reconcile_under_random_edits() {
4571 use crate::SelectionSet;
4572 let mut src = String::new();
4580 for i in 0..40 {
4581 src.push_str(&format!("block{i} {{\n a\n b\n}}\n"));
4582 }
4583 let mut d = doc(&src);
4584 for open in d.collapsible_pairs().iter().map(|&(o, ..)| o).collect::<Vec<_>>() {
4585 d.toggle_fold_opener(open);
4586 }
4587 let mut state: u32 = 0x1234_5678;
4589 let mut rng = || {
4590 state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
4591 state
4592 };
4593 for _ in 0..300 {
4594 let len = d.buffer().len();
4595 if len == 0 {
4596 break;
4597 }
4598 let pos = rng() % len;
4599 match rng() % 5 {
4600 0 => {
4601 d.set_selections(SelectionSet::new(pos));
4602 d.insert_text("{");
4603 }
4604 1 => {
4605 d.set_selections(SelectionSet::new(pos));
4606 d.insert_text("}");
4607 }
4608 2 if len > 1 => {
4609 let s = pos.min(len - 1);
4612 d.edit(vec![EditOp::delete(s..s + 1)]).unwrap();
4613 }
4614 _ => {
4615 d.set_selections(SelectionSet::new(pos));
4616 d.insert_text("x");
4617 }
4618 }
4619 let before: Vec<u32> = d.folds().iter().collect();
4620 d.force_whole_reconcile();
4621 let after: Vec<u32> = d.folds().iter().collect();
4622 assert_eq!(before, after, "windowed reconcile missed a fold the whole-set pass caught");
4623 for o in after {
4625 let foldable = d
4626 .brackets()
4627 .at(o)
4628 .filter(|b| b.open)
4629 .and_then(|b| b.partner)
4630 .is_some_and(|c| crate::row_layout::pair_has_interior(o, c));
4631 assert!(foldable, "a surviving fold at {o} is not a foldable pair");
4632 }
4633 }
4634 }
4635
4636 #[test]
4637 fn fold_at_carets_ejects_carets_hidden_by_a_block() {
4638 use crate::SelectionSet;
4639 let mut d = doc("a {\nhidden\n}\nb"); let inside = d.buffer().point_to_offset(Point::new(1, 3)); d.set_selections(SelectionSet::new(inside));
4642 assert!(d.fold_at_carets(false), "the block folds");
4643 let row = d.buffer().offset_to_point(d.selections().newest().head()).row;
4646 assert_eq!(row, 0, "caret ejected to the header row");
4647 }
4648
4649 #[test]
4650 fn horizontal_movement_hops_an_inline_fold() {
4651 use crate::{Motion, SelectionSet};
4652 let mut d = doc("x = [1, 2, 3]"); d.set_selections(SelectionSet::new(6)); let op = d.fold_opener_at_caret(false).unwrap();
4655 assert!(d.toggle_fold_opener(op));
4656 assert_eq!(d.selections().newest().head(), 5, "caret snapped just after `[`");
4657 d.move_carets(Motion::Right, false);
4658 assert_eq!(d.selections().newest().head(), 12, "Right hops the collapsed interior to `]`");
4659 d.move_carets(Motion::Left, false);
4660 assert_eq!(d.selections().newest().head(), 5, "Left hops back to just after `[`");
4661 }
4662
4663 #[test]
4664 fn collapsible_at_prefers_the_innermost() {
4665 let d = doc("fn m() {\n let x = [1, 2, 3];\n}");
4667 let mut pairs = d.collapsible_pairs();
4668 pairs.sort();
4669 assert_eq!(pairs, vec![(7, 32, 0, 2), (21, 29, 1, 1)]);
4670 assert_eq!(d.collapsible_at(24), Some(21));
4672 assert_eq!(d.collapsible_at(12), Some(7));
4674 assert_eq!(d.collapsible_at(0), None);
4676 }
4677
4678 #[test]
4679 fn collapsible_excludes_the_already_folded() {
4680 let mut d = doc("fn m() {\n let x = [1, 2, 3];\n}");
4681 assert!(d.toggle_fold_opener(7));
4684 let openers: Vec<u32> = d.collapsible_pairs().iter().map(|&(o, ..)| o).collect();
4685 assert_eq!(openers, vec![21]);
4686 assert_eq!(d.collapsible_at(12), None, "the folded block is no longer a target");
4687 assert_eq!(d.collapsible_at(24), Some(21), "the array inside it still is");
4688 }
4689
4690 #[test]
4691 fn nested_inline_folds_reduce_to_the_outer() {
4692 use crate::fold_map::FoldMap;
4693 let mut d = doc("outer_call(a, [1, 2, 3])");
4695 assert!(d.toggle_fold_opener(14)); assert!(d.toggle_fold_opener(10)); let inline = |d: &Document| {
4698 FoldMap::new(d.folds(), d.brackets(), d.buffer()).inline_folds().iter().map(|f| f.open).collect::<Vec<_>>()
4699 };
4700 assert_eq!(inline(&d), vec![10]);
4702 assert!(d.toggle_fold_opener(10));
4704 assert_eq!(inline(&d), vec![14]);
4705 }
4706
4707 #[test]
4708 fn horizontal_movement_hops_the_collapsed_gap() {
4709 use crate::{BufferRow, Motion, Point, SelectionSet};
4710 let mut d = doc("m {\n a\n b\n}\nafter");
4712 assert!(d.fold(BufferRow(0), BufferRow(3))); let caret = |d: &Document| d.buffer().offset_to_point(d.selections().newest().head());
4714 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(0, 3))));
4716 d.move_carets(Motion::Right, false);
4717 assert_eq!(caret(&d), Point::new(3, 0), "Right hops the hidden interior to the closing bracket");
4718 d.move_carets(Motion::Left, false);
4719 assert_eq!(caret(&d), Point::new(0, 3), "Left hops back to the header end");
4720 }
4721
4722 #[test]
4723 fn vertical_move_skips_folds() {
4724 use crate::{BufferRow, Motion, Point, SelectionSet};
4725 let mut d = doc("L0\n{\nL2\nL3\n}\nL5");
4726 assert!(d.fold(BufferRow(1), BufferRow(4))); let row = |d: &Document| d.buffer().offset_to_point(d.selections().newest().head()).row;
4728 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 0))));
4730 d.move_carets(Motion::Down, false);
4731 assert_eq!(row(&d), 5);
4732 d.move_carets(Motion::Up, false);
4734 assert_eq!(row(&d), 1);
4735 }
4736
4737 #[test]
4738 fn typing_into_an_inline_folds_gap_expands_it() {
4739 use crate::SelectionSet;
4740 let mut d = doc("x = [1, 2, 3]");
4744 d.set_selections(SelectionSet::new(6));
4745 assert!(d.toggle_fold_opener(4));
4746 assert_eq!(d.selections().newest().head(), 5, "caret ejected to the left edge");
4747 d.type_char('9');
4748 assert!(!d.folds().is_folded(4), "editing hidden text expands the fold");
4749 assert_eq!(d.text(), "x = [91, 2, 3]");
4750 }
4751
4752 #[test]
4753 fn typing_at_a_collapsed_headers_end_keeps_it_folded() {
4754 use crate::{Point, SelectionSet};
4755 let mut d = doc("m {\n a\n}");
4759 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 0))));
4760 let opener = 2;
4761 assert!(d.toggle_fold_opener(opener));
4762 d.type_char('x'); assert!(d.folds().is_folded(opener), "a visible-part edit leaves the fold alone");
4764 assert!(d.text().starts_with("m {x\n"));
4765 }
4766
4767 #[test]
4768 fn undo_into_a_folded_region_expands_it() {
4769 use crate::{Point, SelectionSet};
4770 let mut d = doc("m {\n a\n}");
4774 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 3))));
4775 d.type_char('b'); let opener = 2;
4777 assert!(d.toggle_fold_opener(opener));
4778 assert!(d.folds().is_folded(opener));
4779 assert!(d.undo());
4780 assert!(!d.folds().is_folded(opener), "undoing hidden text reveals it");
4781 assert!(d.text().contains("\n a\n"), "the typed char was reverted");
4782 }
4783
4784 #[test]
4785 fn folding_a_block_ejects_a_hidden_caret_to_the_header_end() {
4786 use crate::{Point, SelectionSet};
4787 let mut d = doc("m {\n a\n }");
4789 let opener = 2;
4790 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 2))));
4792 assert!(d.toggle_fold_opener(opener));
4793 assert_eq!(
4794 d.selections().newest().head(),
4795 d.buffer().point_to_offset(Point::new(0, 3)),
4796 "the hidden caret ejects to just before the placeholder"
4797 );
4798 assert!(d.toggle_fold_opener(opener)); d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(2, 1))));
4801 assert!(d.toggle_fold_opener(opener));
4802 assert_eq!(d.selections().newest().head(), d.buffer().point_to_offset(Point::new(0, 3)));
4803 assert!(d.toggle_fold_opener(opener)); let on_tail = d.buffer().point_to_offset(Point::new(2, 2));
4806 d.set_selections(SelectionSet::new(on_tail));
4807 assert!(d.toggle_fold_opener(opener));
4808 assert_eq!(d.selections().newest().head(), on_tail, "the visible tail is not ejected");
4809 }
4810
4811 #[test]
4812 fn fold_chord_works_when_the_caret_touches_a_bracket() {
4813 use crate::SelectionSet;
4814 let mut d = doc("x = [1, 2, 3]");
4816 d.set_selections(SelectionSet::new(4));
4818 assert_eq!(d.fold_opener_at_caret(false), Some(4), "touching the opener from the left");
4819 d.set_selections(SelectionSet::new(13));
4821 assert_eq!(d.fold_opener_at_caret(false), Some(4), "touching the closer from the right");
4822 assert!(d.toggle_fold_opener(4));
4824 assert_eq!(d.fold_opener_at_caret(true), Some(4), "touching unfolds too");
4825 assert!(d.toggle_fold_opener(4));
4827 d.set_selections(SelectionSet::new(1));
4828 assert_eq!(d.fold_opener_at_caret(false), None);
4829 }
4830
4831 #[test]
4832 fn fold_chord_at_a_shared_boundary_picks_the_touched_inner_pair() {
4833 use crate::SelectionSet;
4834 let d = {
4838 let mut d = doc("{a [b] c}");
4839 d.set_selections(SelectionSet::new(6));
4840 d
4841 };
4842 assert_eq!(d.fold_opener_at_caret(false), Some(3), "the touched inline pair beats the enclosing block");
4843 }
4844
4845 #[test]
4846 fn vertical_goal_is_a_display_cell_across_tab_stops() {
4847 use crate::{Motion, Point, SelectionSet};
4848 let mut d = doc("\tab\nxxxxxxxxxx");
4852 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(0, 1))));
4853 d.move_carets(Motion::Down, false);
4854 assert_eq!(
4855 d.buffer().offset_to_point(d.selections().newest().head()),
4856 Point::new(1, 4),
4857 "the goal is the visual column, not the byte column"
4858 );
4859 d.move_carets(Motion::Up, false);
4861 assert_eq!(d.buffer().offset_to_point(d.selections().newest().head()), Point::new(0, 1));
4862 }
4863
4864 #[test]
4865 fn vertical_move_through_a_collapsed_tail_stays_visual() {
4866 use crate::{BufferRow, Motion, Point, SelectionSet};
4867 let mut d = doc("m {\na\n} t\nabcdefgh");
4870 assert!(d.fold(BufferRow(0), BufferRow(2)));
4871 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(2, 3))));
4873 d.move_carets(Motion::Down, false);
4874 assert_eq!(
4875 d.buffer().offset_to_point(d.selections().newest().head()),
4876 Point::new(3, 8),
4877 "Down from the far-right tail lands at the row's visual end, not byte col 3"
4878 );
4879 d.move_carets(Motion::Up, false);
4881 assert_eq!(
4882 d.buffer().offset_to_point(d.selections().newest().head()).row,
4883 2,
4884 "a goal over the placeholder tail resolves onto the tail"
4885 );
4886 }
4887
4888 #[test]
4889 fn vertical_landing_snaps_out_of_a_collapsed_inline_gap() {
4890 use crate::{Motion, Point, SelectionSet};
4891 let mut d = doc("aaaaaaaaaa\nx[123456]y");
4895 let opener = d.buffer().text().find('[').unwrap() as u32;
4896 assert!(d.toggle_fold_opener(opener));
4897 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(0, 3))));
4898 d.move_carets(Motion::Down, false);
4899 assert_eq!(
4900 d.selections().newest().head(),
4901 opener + 1,
4902 "a landing on the chip snaps to just after the opening bracket"
4903 );
4904 }
4905
4906 #[test]
4907 fn home_and_end_operate_on_the_collapsed_display_line() {
4908 use crate::{BufferRow, Motion, Point, SelectionSet};
4909 let mut d = doc("L0\n m {\na\n }");
4912 assert!(d.fold(BufferRow(1), BufferRow(3)));
4913 let head = |d: &Document| d.selections().newest().head();
4914 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 2))));
4917 d.move_carets(Motion::LineEnd, false);
4918 assert_eq!(head(&d), d.buffer().point_to_offset(Point::new(3, 3)), "End lands after the tail brace");
4919 d.move_carets(Motion::LineStart, false);
4922 assert_eq!(head(&d), d.buffer().point_to_offset(Point::new(1, 2)), "Home jumps to the header's smart start");
4923 d.move_carets(Motion::LineStart, false);
4925 assert_eq!(head(&d), d.buffer().point_to_offset(Point::new(1, 0)));
4926 assert!(d.unfold(BufferRow(1)));
4928 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 2))));
4929 d.move_carets(Motion::LineEnd, false);
4930 assert_eq!(head(&d), d.buffer().point_to_offset(Point::new(1, 5)), "plain End at the header's own end");
4931 }
4932
4933 #[test]
4934 fn foldable_ranges_from_multiline_brackets() {
4935 let d = doc("mod m {\n fn f() {\n if x {\n a\n }\n }\n g()\n}");
4938 let pairs: Vec<(u32, u32)> = d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect();
4939 assert_eq!(pairs, vec![(0, 7), (1, 5), (2, 4)]);
4941 }
4942
4943 #[test]
4944 fn fold_dropped_when_its_bracket_pair_is_broken() {
4945 use crate::{fold_map::FoldMap, BufferRow};
4946 let mut d = doc("m {\n a\n b\n}\nafter");
4948 assert_eq!(d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect::<Vec<_>>(), vec![(0, 3)]);
4949 assert!(d.fold(BufferRow(0), BufferRow(3)));
4950 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).display_row_count(), 2); let s = d.buffer().point_to_offset(crate::Point::new(3, 0));
4956 let e = d.buffer().point_to_offset(crate::Point::new(4, 0));
4957 d.edit(vec![EditOp::delete(s..e)]).unwrap();
4958 assert!(d.foldable_ranges().is_empty(), "no closing brace ⇒ nothing foldable");
4959 assert!(d.folds().is_empty(), "invalidated fold dropped, not left orphaning hidden rows");
4960 assert_eq!(
4961 FoldMap::new(d.folds(), d.brackets(), d.buffer()).display_row_count(),
4962 d.buffer().line_count(),
4963 "every row is visible again"
4964 );
4965 }
4966
4967 #[test]
4968 fn valid_fold_survives_edit_above() {
4969 use crate::{fold_map::FoldMap, BufferRow, SelectionSet};
4970 let mut d = doc("m {\n a\n b\n}\nafter");
4971 assert!(d.fold(BufferRow(0), BufferRow(3)));
4972 d.set_selections(SelectionSet::new(0));
4975 d.insert_text("top\n");
4976 assert_eq!(d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect::<Vec<_>>(), vec![(1, 4)]);
4977 assert_eq!(d.folds().len(), 1, "the still-valid fold is kept");
4978 let m = FoldMap::new(d.folds(), d.brackets(), d.buffer());
4979 assert_eq!(m.fold_at_header(BufferRow(1)), Some(BufferRow(4)));
4980 assert!(m.is_folded(BufferRow(2)) && m.is_folded(BufferRow(3)) && m.is_folded(BufferRow(4)));
4981 }
4982
4983 #[test]
4984 fn breaking_one_block_keeps_a_sibling_fold() {
4985 use crate::{fold_map::FoldMap, BufferRow};
4986 let mut d = doc("a {\n x\n}\nb {\n y\n}\nafter");
4989 assert_eq!(d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect::<Vec<_>>(), vec![(0, 2), (3, 5)]);
4990 assert!(d.fold(BufferRow(0), BufferRow(2)));
4991 assert!(d.fold(BufferRow(3), BufferRow(5)));
4992 assert_eq!(d.folds().len(), 2);
4993 let s = d.buffer().point_to_offset(crate::Point::new(5, 0));
4996 let e = d.buffer().point_to_offset(crate::Point::new(6, 0));
4997 d.edit(vec![EditOp::delete(s..e)]).unwrap();
4998 assert_eq!(d.folds().len(), 1, "only the broken block's fold drops");
4999 let m = FoldMap::new(d.folds(), d.brackets(), d.buffer());
5000 assert_eq!(m.fold_at_header(BufferRow(0)), Some(BufferRow(2)), "sibling A still collapsed");
5001 assert_eq!(m.fold_at_header(BufferRow(3)), None, "B is no longer folded");
5002 }
5003
5004 #[test]
5005 fn fold_drops_when_its_interior_is_deleted() {
5006 use crate::{fold_map::FoldMap, BufferRow};
5007 let mut d = doc("L0\n{\nL2\nL3\n}\nL5");
5008 assert!(d.fold(BufferRow(1), BufferRow(4))); let start = d.buffer().point_to_offset(crate::Point::new(2, 0));
5011 let end = d.buffer().point_to_offset(crate::Point::new(5, 0));
5012 d.edit(vec![EditOp::delete(start..end)]).unwrap();
5013 assert!(d.folds().is_empty(), "fold dropped when its interior text is deleted");
5014 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).display_row_count(), d.buffer().line_count());
5015 }
5016
5017 #[test]
5018 fn intel05_diagnostic_keeps_byte_offsets_on_a_multibyte_line() {
5019 use crate::{Diagnostic, Severity};
5024 let mut d = doc("aé=x");
5025 d.set_diagnostics(d.revision(), vec![Diagnostic::new(4..5, Severity::Error, "e")]);
5026 let span = d.diagnostics_in(0..100).next().unwrap().0;
5027 assert_eq!(span, 4..5, "diagnostics_in returns raw byte offsets, not cells");
5028 assert_eq!(crate::display_map::expand(&d.buffer().line(0), span.start, 4), 3);
5031 }
5032
5033 #[test]
5034 fn undo_resyncs_brackets_and_highlight_to_the_reverted_text() {
5035 const G: &str = "%YAML 1.2\n---\nname: T\nscope: source.t\ncontexts:\n main:\n - match: '\\w+'\n scope: keyword.t\n";
5036 const TH: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
5037<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
5038<plist version="1.0"><dict><key>settings</key><array><dict><key>settings</key><dict><key>foreground</key><string>#FFFFFF</string></dict></dict></array></dict></plist>"#;
5039 let mut d = doc("()");
5040 d.set_syntax(
5041 crate::SyntaxDef::from_sublime_syntax(G).unwrap(),
5042 crate::TokenTheme::from_tm_theme(TH).unwrap(),
5043 );
5044 d.tokenize_highlight(d.buffer().line_count());
5045 d.set_selections(SelectionSet::new(1)); d.edit(vec![EditOp::insert(1, "[]\n")]).unwrap(); d.tokenize_highlight(d.buffer().line_count());
5048 assert_eq!((d.buffer().line_count(), d.brackets().all().len()), (2, 4));
5049 assert!(d.undo(), "there was something to undo");
5050 assert_eq!(d.buffer().text(), "()");
5051 assert_eq!((d.buffer().line_count(), d.brackets().all().len()), (1, 2));
5053 d.tokenize_highlight(d.buffer().line_count());
5054 assert!(d.highlight_line_spans(0).is_some());
5055 assert!(d.highlight_line_spans(1).is_none(), "no phantom line after undo");
5056 }
5057
5058 #[test]
5059 fn select_all_spans_the_whole_buffer_with_head_at_end() {
5060 let mut d = doc("abc\ndef"); d.select_all();
5062 assert_eq!(d.selections().len(), 1);
5063 let s = d.selections().newest();
5064 assert_eq!((s.start(), s.end(), s.head()), (0, 7, 7));
5065 }
5066
5067 fn query(text: &str) -> crate::FindQuery {
5068 crate::FindQuery { text: text.into(), case_sensitive: true, ..Default::default() }
5069 }
5070
5071 #[test]
5072 fn find_matches_ride_an_edit_above_them() {
5073 let mut d = doc("xx target"); d.set_find_query(Some(query("target")), 0);
5076 assert_eq!(d.find_matches_in(0..100).collect::<Vec<_>>(), vec![(3..9, false)]);
5077 d.set_selections(SelectionSet::new(0));
5078 d.edit(vec![EditOp::insert(0, "ab")]).unwrap(); assert_eq!(d.find_matches_in(0..100).collect::<Vec<_>>(), vec![(5..11, false)]);
5082 }
5083
5084 #[test]
5085 fn find_next_prev_start_from_the_caret_and_wrap() {
5086 let mut d = doc("foo bar foo baz foo"); d.set_find_query(Some(query("foo")), 0);
5089 assert_eq!((d.find_match_count(), d.active_find_match()), (3, None));
5090 d.set_selections(SelectionSet::new(5)); assert_eq!(d.find_next(0), Some(8..11)); assert_eq!(d.active_find_match(), Some(1));
5093 assert_eq!(d.find_next(0), Some(16..19)); assert_eq!(d.find_next(0), Some(0..3), "wraps to first");
5095 assert_eq!(d.find_prev(0), Some(16..19), "prev from first wraps to last");
5096 d.set_selections(SelectionSet::new(0));
5097 assert_eq!(d.find_prev(0), Some(16..19), "prev before all wraps to last");
5098 }
5099
5100 #[test]
5101 fn active_match_survives_a_rescan_at_the_same_start() {
5102 let mut d = doc("foo foo foo");
5107 d.set_find_query(Some(query("foo")), 0);
5108 d.set_selections(SelectionSet::new(4)); d.find_next(0);
5110 assert_eq!(d.active_find_match(), Some(1));
5111 d.set_selections(SelectionSet::new(11));
5112 d.edit(vec![EditOp::insert(11, " foo")]).unwrap(); assert_eq!(
5114 (d.find_match_count(), d.active_find_match()),
5115 (4, Some(1)),
5116 "current at the commit, active untouched"
5117 );
5118 assert!(!d.maybe_rescan_find(1000), "nothing stale to rescan (uncapped)");
5119 assert_eq!((d.find_match_count(), d.active_find_match()), (4, Some(1)));
5120 }
5121
5122 #[test]
5123 fn find_matches_ride_undo_and_redo_through_the_shared_mover() {
5124 let mut d = doc("foo bar");
5128 d.set_find_query(Some(query("foo")), 0);
5129 assert_eq!(d.find_matches_in(0..100).next().unwrap().0, 0..3);
5130 d.set_selections(SelectionSet::new(0));
5131 d.edit(vec![EditOp::insert(0, "xy")]).unwrap(); assert_eq!(d.find_matches_in(0..100).next().unwrap().0, 2..5);
5133 d.undo(); assert_eq!(d.buffer().text(), "foo bar");
5135 assert_eq!(d.find_matches_in(0..100).next().unwrap().0, 0..3, "rode the undo");
5136 d.redo(); assert_eq!(d.find_matches_in(0..100).next().unwrap().0, 2..5, "rode the redo");
5138 }
5139
5140 #[test]
5141 fn find_count_reflects_live_matches_after_a_collapse_delete() {
5142 let mut d = doc("foo foo"); d.set_find_query(Some(query("foo")), 0);
5147 assert_eq!(d.find_match_count(), 2);
5148 d.edit(vec![EditOp::delete(0..4)]).unwrap(); assert_eq!(d.buffer().text(), "foo");
5150 assert_eq!(d.find_match_count(), 1, "collapsed match not counted");
5151 assert_eq!(d.find_matches_in(0..100).count(), 1, "count agrees with render");
5152 }
5153
5154 #[test]
5155 fn find_navigation_requests_a_reveal() {
5156 let mut d = doc("foo bar foo");
5157 d.set_find_query(Some(query("foo")), 0);
5158 let before = d.reveal_seq();
5159 assert!(d.find_next(0).is_some());
5160 assert!(d.reveal_seq() > before, "navigation bumps the reveal request");
5161 }
5162
5163 #[test]
5164 fn clearing_the_query_drops_every_match() {
5165 let mut d = doc("foo foo");
5166 d.set_find_query(Some(query("foo")), 0);
5167 assert_eq!(d.find_match_count(), 2);
5168 d.set_find_query(None, 0);
5169 assert_eq!((d.find_match_count(), d.find_query().is_none()), (0, true));
5170 assert_eq!(d.find_matches_in(0..100).count(), 0);
5171 }
5172
5173 #[test]
5174 fn edits_repair_the_match_set_immediately() {
5175 let mut d = doc("foo");
5179 d.set_find_query(Some(query("foo")), 0);
5180 d.edit(vec![EditOp::insert(0, "foo ")]).unwrap(); assert_eq!(d.find_match_count(), 2, "current immediately — no debounce");
5182 assert!(!d.maybe_rescan_find(50), "nothing to rescan");
5183 assert!(!d.maybe_rescan_find(1_000), "uncapped: never rescans");
5184 assert_eq!(d.find_match_count(), 2);
5185 }
5186
5187 fn match_ids(d: &Document) -> Vec<crate::decorations::DecorationId> {
5191 d.decorations
5192 .decorations_in(0..u32::MAX)
5193 .filter(|r| matches!(r.kind, DecorationKind::FindMatch))
5194 .map(|r| r.id)
5195 .collect()
5196 }
5197
5198 fn match_ranges(d: &Document) -> Vec<Range<u32>> {
5200 d.find_matches_in(0..u32::MAX).map(|(r, _)| r).collect()
5201 }
5202
5203 fn xorshift(mut state: u64) -> impl FnMut() -> u64 {
5205 move || {
5206 state ^= state << 13;
5207 state ^= state >> 7;
5208 state ^= state << 17;
5209 state
5210 }
5211 }
5212
5213 #[test]
5214 fn repair_preserves_far_match_ids() {
5215 let mut d = doc("foo bar foo bar baz padding padding foo");
5219 d.set_find_query(Some(query("foo")), 0);
5220 let before = match_ids(&d);
5221 assert_eq!(before.len(), 3);
5222 d.edit(vec![EditOp::insert(22, "zz")]).unwrap();
5224 assert!(!d.maybe_rescan_find(1_000), "always current: nothing to rescan");
5225 assert_eq!(match_ids(&d), before, "untouched matches keep their ids");
5226 assert_eq!(match_ranges(&d), vec![0..3, 8..11, 38..41]);
5227 }
5228
5229 #[test]
5230 fn repair_insert_creates_match_in_window() {
5231 let mut d = doc("fo bar");
5232 d.set_find_query(Some(query("foo")), 0);
5233 assert_eq!(d.find_match_count(), 0);
5234 d.edit(vec![EditOp::insert(2, "o")]).unwrap(); assert_eq!(match_ranges(&d), vec![0..3], "current at the commit");
5236 }
5237
5238 #[test]
5239 fn repair_delete_joins_halves_creates_match() {
5240 let mut d = doc("foXo bar");
5242 d.set_find_query(Some(query("foo")), 0);
5243 assert_eq!(d.find_match_count(), 0);
5244 d.edit(vec![EditOp::delete(2..3)]).unwrap(); assert_eq!(match_ranges(&d), vec![0..3]);
5246 }
5247
5248 #[test]
5249 fn repair_destroys_straddling_match() {
5250 let mut d = doc("afoob");
5252 d.set_find_query(Some(query("foo")), 0);
5253 assert_eq!(match_ranges(&d), vec![1..4]);
5254 d.edit(vec![EditOp::delete(3..5)]).unwrap(); assert_eq!(d.find_match_count(), 0);
5256 }
5257
5258 #[test]
5259 fn repair_interior_insert_invalidates_match() {
5260 let mut d = doc("foo");
5263 d.set_find_query(Some(query("foo")), 0);
5264 assert_eq!(d.find_match_count(), 1);
5265 d.edit(vec![EditOp::insert(1, "x")]).unwrap(); assert_eq!(d.find_match_count(), 0);
5267 }
5268
5269 #[test]
5270 fn repair_multi_edit_transaction() {
5271 let mut d = doc("fo bar fo baz");
5273 d.set_find_query(Some(query("foo")), 0);
5274 d.edit(vec![EditOp::insert(2, "o"), EditOp::insert(9, "o")]).unwrap();
5275 assert_eq!(d.buffer().text(), "foo bar foo baz");
5276 assert_eq!(match_ranges(&d), vec![0..3, 8..11]);
5277 }
5278
5279 #[test]
5280 fn repair_equals_full_scan_property() {
5281 let fq = query("ab");
5286 let mut next = xorshift(0x243F_6A88_85A3_08D3);
5287 let alphabet = ['a', 'b', ' '];
5288 let mut d = doc(&"ab ba a b ab ".repeat(8));
5289 d.set_find_query(Some(fq.clone()), 0);
5290 for step in 0..300 {
5291 let len = d.buffer().len();
5292 match next() % 3 {
5293 0 => {
5294 let at = (next() % u64::from(len + 1)) as u32;
5295 let n = 1 + next() % 3;
5296 let s: String = (0..n).map(|_| alphabet[(next() % 3) as usize]).collect();
5297 d.edit(vec![EditOp::insert(at, s)]).unwrap();
5298 }
5299 1 if len > 0 => {
5300 let a = (next() % u64::from(len)) as u32;
5301 let b = (a + 1 + (next() % 4) as u32).min(len);
5302 d.edit(vec![EditOp::delete(a..b)]).unwrap();
5303 }
5304 _ => {
5305 let a = (next() % u64::from(len + 1)) as u32;
5307 let b = (next() % u64::from(len + 1)) as u32;
5308 let (a, b) = (a.min(b), a.max(b));
5309 if a == b {
5310 d.edit(vec![EditOp::insert(a, "ab")]).unwrap();
5311 } else {
5312 d.edit(vec![EditOp::insert(a, "ba"), EditOp::insert(b, "ab")]).unwrap();
5313 }
5314 }
5315 }
5316 let (oracle, _) = crate::find::scan(&d.buffer().text(), &fq);
5317 assert_eq!(match_ranges(&d), oracle, "step {step} diverged from the oracle");
5318 }
5319 }
5320
5321 #[test]
5322 fn repair_self_overlapping_needle_is_maximal_and_valid() {
5323 let mut next = xorshift(0x9E37_79B9_7F4A_7C15);
5329 let alphabet = ['a', ' '];
5330 let mut d = doc(&"aaa a aaaa aa ".repeat(6));
5331 d.set_find_query(Some(query("aa")), 0);
5332 for step in 0..300 {
5333 let len = d.buffer().len();
5334 if next().is_multiple_of(2) || len == 0 {
5335 let at = (next() % u64::from(len + 1)) as u32;
5336 let n = 1 + next() % 3;
5337 let s: String = (0..n).map(|_| alphabet[(next() % 2) as usize]).collect();
5338 d.edit(vec![EditOp::insert(at, s)]).unwrap();
5339 } else {
5340 let a = (next() % u64::from(len)) as u32;
5341 let b = (a + 1 + (next() % 3) as u32).min(len);
5342 d.edit(vec![EditOp::delete(a..b)]).unwrap();
5343 }
5344 let live = match_ranges(&d);
5345 let text = d.buffer().text();
5346 let bytes = text.as_bytes();
5347 for (i, r) in live.iter().enumerate() {
5348 assert_eq!(
5349 &bytes[r.start as usize..r.end as usize],
5350 b"aa",
5351 "step {step}: stale match {r:?}"
5352 );
5353 if i > 0 {
5354 assert!(live[i - 1].end <= r.start, "step {step}: overlapping matches");
5355 }
5356 }
5357 for p in 0..bytes.len().saturating_sub(1) {
5358 if &bytes[p..p + 2] == b"aa" {
5359 let p = p as u32;
5360 assert!(
5361 live.iter().any(|r| r.start < p + 2 && p < r.end),
5362 "step {step}: placement at {p} misses every match (not maximal)"
5363 );
5364 }
5365 }
5366 }
5367 }
5368
5369 #[test]
5370 fn active_survives_untouched() {
5371 let mut d = doc("foo bar foo");
5374 d.set_find_query(Some(query("foo")), 0);
5375 d.find_next(0); let id = d.find.active_id().expect("a match is active");
5377 d.edit(vec![EditOp::insert(7, "x")]).unwrap(); assert_eq!(d.find.active_id(), Some(id), "untouched active keeps its id");
5379 assert_eq!((d.find_match_count(), d.active_find_match()), (2, Some(0)));
5380 }
5381
5382 #[test]
5383 fn active_transfers_same_start() {
5384 let mut d = doc("foo bar");
5387 d.set_find_query(Some(query("foo")), 0);
5388 d.find_next(0); let old = d.find.active_id().expect("a match is active");
5390 d.edit(vec![EditOp::insert(4, "x")]).unwrap(); let new = d.find.active_id().expect("active transferred");
5392 assert_ne!(new, old, "the decoration was re-minted by the repair");
5393 assert_eq!(d.active_find_match(), Some(0), "still the first match");
5394 assert_eq!(match_ranges(&d), vec![0..3]);
5395 }
5396
5397 #[test]
5398 fn active_cleared_when_destroyed() {
5399 let mut d = doc("foo bar foo");
5400 d.set_find_query(Some(query("foo")), 0);
5401 d.find_next(0); d.edit(vec![EditOp::delete(0..2)]).unwrap(); assert_eq!(d.find.active_id(), None, "destroyed active clears");
5404 assert_eq!((d.find_match_count(), d.active_find_match()), (1, None));
5405 }
5406
5407 #[test]
5408 fn count_and_order_consistent_after_repair() {
5409 let mut d = doc("foo foo foo");
5410 d.set_find_query(Some(query("foo")), 0);
5411 d.set_selections(SelectionSet::new(4));
5412 d.find_next(0); d.edit(vec![EditOp::insert(0, "foo ")]).unwrap(); let all: Vec<(Range<u32>, bool)> = d.find_matches_in(0..u32::MAX).collect();
5415 let ranges: Vec<Range<u32>> = all.iter().map(|(r, _)| r.clone()).collect();
5416 assert_eq!(ranges, vec![0..3, 4..7, 8..11, 12..15]);
5417 assert_eq!(d.find_match_count(), all.len());
5418 assert_eq!(d.active_find_match(), Some(2));
5420 assert_eq!(all.iter().position(|(_, active)| *active), Some(2));
5421 }
5422
5423 #[test]
5424 fn active_start_tracks_the_decoration_under_random_edits() {
5425 let base = "foo foo bar foo baz foo qux foo end";
5436 let mut rng = 0xDEAD_BEEF_0000_1111u64;
5437 let mut next = move || {
5438 rng ^= rng << 13;
5439 rng ^= rng >> 7;
5440 rng ^= rng << 17;
5441 rng
5442 };
5443 for trial in 0..400u32 {
5444 let mut d = doc(base);
5445 d.set_find_query(Some(query("foo")), 0);
5446 d.set_selections(SelectionSet::new(10));
5449 d.find_next(0);
5450 for _step in 0..14 {
5451 if d.find.active_id().is_none() {
5452 d.set_selections(SelectionSet::new(1));
5454 d.find_next(0);
5455 }
5456 let len = d.buffer().len();
5457 let at = match d.find.active_start() {
5460 Some(s) if next() % 3 == 0 => s,
5461 _ => (next() % u64::from(len + 1)) as u32,
5462 };
5463 match next() % 3 {
5464 0 => {
5465 let _ = d.edit(vec![EditOp::insert(at, "z")]);
5466 }
5467 1 => {
5468 let end = (at + 1 + (next() % 3) as u32).min(len);
5469 if end > at {
5470 let _ = d.edit(vec![EditOp::delete(at..end)]);
5471 }
5472 }
5473 _ => {
5474 let a = (next() % u64::from(len + 1)) as u32;
5476 let b = (next() % u64::from(len + 1)) as u32;
5477 let (lo, hi) = (a.min(b), a.max(b));
5478 if lo != hi {
5479 d.set_selections(SelectionSet::from_ranges(&[(lo, lo), (hi, hi)], 0));
5480 d.insert_text("q");
5481 }
5482 }
5483 }
5484 let want = d
5485 .find
5486 .active_id()
5487 .and_then(|id| d.decorations.decoration_range(id))
5488 .map(|r| r.start);
5489 assert_eq!(
5490 d.find.active_start(),
5491 want,
5492 "trial {trial}: active_start drifted from its decoration's live start",
5493 );
5494 }
5495 }
5496 }
5497
5498 #[test]
5499 fn capped_scan_keeps_a_document_prefix() {
5500 let text = "x ".repeat(crate::FIND_MATCH_CAP + 50);
5503 let mut d = doc(&text);
5504 d.set_find_query(Some(query("x")), 0);
5505 assert!(d.find.capped());
5506 let live = match_ranges(&d);
5507 assert_eq!(live.len(), crate::FIND_MATCH_CAP);
5508 assert_eq!(live.last().cloned(), Some(19_998..19_999), "prefix, not a sample");
5509 }
5510
5511 #[test]
5512 fn typing_while_capped_does_not_rescan_the_match_set() {
5513 let text = "x ".repeat(crate::FIND_MATCH_CAP + 50);
5520 let mut d = doc(&text);
5521 d.set_find_query(Some(query("x")), 0);
5522 assert!(d.find.capped());
5523 assert_eq!(d.find_match_count(), crate::FIND_MATCH_CAP);
5524 d.set_selections(crate::SelectionSet::new(0));
5525 crate::perf::reset();
5526 d.type_char('z'); let work = crate::perf::meter();
5528 assert_eq!(d.find_match_count(), crate::FIND_MATCH_CAP, "still capped");
5529 assert!(
5530 work < 2_000,
5531 "capped keystroke charged {work} work units (≈FIND_MATCH_CAP ⇒ the O(M) cap scan survived)"
5532 );
5533 }
5534
5535 #[test]
5536 fn capped_trims_tail_and_coverage() {
5537 let text = "x ".repeat(crate::FIND_MATCH_CAP);
5541 let mut d = doc(&text);
5542 d.set_find_query(Some(query("x")), 0);
5543 assert!(!d.find.capped());
5544 d.edit(vec![EditOp::insert(1_000, "xxx")]).unwrap();
5545 assert!(d.find.capped(), "over the cap after the repair");
5546 let live = match_ranges(&d);
5547 assert_eq!(live.len(), crate::FIND_MATCH_CAP, "trimmed back to the cap");
5548 assert!(live.windows(2).all(|w| w[0].end <= w[1].start), "still non-overlapping");
5549 assert_eq!(live.iter().filter(|r| (1_000..1_004).contains(&r.start)).count(), 4);
5551 assert_eq!(live.last().map(|r| r.start), Some(19_995), "tail trimmed");
5552 }
5553
5554 #[test]
5555 fn capped_refill_is_debounced() {
5556 let text = "x ".repeat(crate::FIND_MATCH_CAP + 50);
5557 let mut d = doc(&text);
5558 d.set_find_query(Some(query("x")), 0); d.edit(vec![EditOp::delete(0..10)]).unwrap();
5561 assert_eq!(d.find_match_count(), crate::FIND_MATCH_CAP - 5);
5562 assert!(!d.maybe_rescan_find(50), "inside the debounce window");
5563 assert_eq!(d.find_match_count(), crate::FIND_MATCH_CAP - 5);
5564 assert!(d.maybe_rescan_find(100), "past the window → refill");
5565 assert_eq!(d.find_match_count(), crate::FIND_MATCH_CAP, "coverage re-grew");
5566 assert!(!d.maybe_rescan_find(300), "idle after the refill: no re-scan");
5567 }
5568
5569 #[test]
5570 fn undo_redo_match_set_equals_fresh_scan() {
5571 let fq = query("ab");
5574 let mut d = doc("ab ab ba ab");
5575 d.set_find_query(Some(fq.clone()), 0);
5576 let check = |d: &Document, at: &str| {
5577 let (oracle, _) = crate::find::scan(&d.buffer().text(), &fq);
5578 assert_eq!(match_ranges(d), oracle, "diverged after {at}");
5579 };
5580 d.edit(vec![EditOp::insert(2, "ab")]).unwrap();
5581 check(&d, "edit 1");
5582 d.edit(vec![EditOp::delete(0..3)]).unwrap();
5583 check(&d, "edit 2");
5584 d.edit(vec![EditOp::insert(5, "b a")]).unwrap();
5585 check(&d, "edit 3");
5586 assert!(d.undo());
5587 check(&d, "undo 1");
5588 assert!(d.undo());
5589 check(&d, "undo 2");
5590 assert!(d.undo());
5591 check(&d, "undo 3");
5592 assert!(d.redo());
5593 check(&d, "redo 1");
5594 assert!(d.redo());
5595 check(&d, "redo 2");
5596 }
5597
5598 #[test]
5599 fn case_fold_repair() {
5600 let fq = crate::FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() };
5602 let mut d = doc("FO bar");
5603 d.set_find_query(Some(fq), 0);
5604 assert_eq!(d.find_match_count(), 0);
5605 d.edit(vec![EditOp::insert(2, "O")]).unwrap(); assert_eq!(match_ranges(&d), vec![0..3]);
5607 d.edit(vec![EditOp::insert(0, "f")]).unwrap(); assert_eq!(match_ranges(&d), vec![1..4], "re-anchored, still folded");
5609 }
5610}