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 expand_stack: Vec<SelectionSet>,
85 reveal_seq: u64,
94 reveal_mode: RevealMode,
96 fold_cache: RefCell<FoldMapCache>,
108}
109
110#[derive(Debug)]
113struct FoldMapCache {
114 key: (u64, u64),
115 map: FoldMap,
116}
117
118#[derive(Copy, Clone, Debug, PartialEq, Eq)]
120pub enum RevealMode {
121 Fit,
126 FitForce,
129 Center,
131}
132
133#[derive(Copy, Clone, Debug)]
141struct ColumnSelection {
142 anchor: CellCorner,
143 active: CellCorner,
144}
145
146#[derive(Copy, Clone, Debug)]
149struct CellCorner {
150 row: u32,
151 cell: u32,
152}
153
154impl Document {
155 pub fn new(text: &str) -> Result<Self, LoadError> {
161 let buffer = Buffer::new(text)?;
162 let brackets = Brackets::match_text(&buffer.text());
163 Ok(Self {
164 buffer,
165 history: History::new(),
166 selections: SelectionSet::new(0),
167 column: None,
168 highlight: None,
169 brackets,
170 decorations: DecorationStore::new(),
171 autoclose: DecorationStore::new(),
172 folds: FoldSet::new(),
173 find: FindState::new(),
174 line_comment: None,
175 expand_stack: Vec::new(),
176 reveal_seq: 0,
177 reveal_mode: RevealMode::Fit,
178 fold_cache: RefCell::new(FoldMapCache { key: (u64::MAX, u64::MAX), map: FoldMap::empty() }),
180 })
181 }
182
183 #[must_use]
190 pub fn fold_map(&self) -> Ref<'_, FoldMap> {
191 self.ensure_fold_map();
192 Ref::map(self.fold_cache.borrow(), |c| &c.map)
193 }
194
195 fn ensure_fold_map(&self) {
201 let key = (self.buffer.revision().0, self.folds.generation());
202 if self.fold_cache.borrow().key != key {
203 let map = FoldMap::new(&self.folds, &self.brackets, &self.buffer);
204 *self.fold_cache.borrow_mut() = FoldMapCache { key, map };
205 }
206 }
207
208 fn request_reveal(&mut self, mode: RevealMode) {
212 self.reveal_seq += 1;
213 self.reveal_mode = mode;
214 }
215
216 #[must_use]
218 pub fn reveal_mode(&self) -> RevealMode {
219 self.reveal_mode
220 }
221
222 pub fn set_line_comment(&mut self, prefix: Option<&str>) {
226 self.line_comment = prefix.map(str::to_owned);
227 }
228
229 #[must_use]
231 pub fn line_comment(&self) -> Option<&str> {
232 self.line_comment.as_deref()
233 }
234
235 #[must_use]
238 pub fn buffer(&self) -> &Buffer {
239 &self.buffer
240 }
241
242 #[must_use]
244 pub fn selections(&self) -> &SelectionSet {
245 &self.selections
246 }
247
248 pub fn set_selections(&mut self, selections: SelectionSet) {
251 self.reset_transient();
252 self.selections = selections;
253 }
254
255 pub fn move_carets(&mut self, motion: Motion, extend: bool) {
258 self.reset_transient();
259 self.ensure_fold_map();
264 let tab = self.tab_size();
265 let cache = self.fold_cache.borrow();
266 movement::move_selections(&mut self.selections, &self.buffer, &cache.map, tab, motion, extend);
267 }
268
269 pub fn add_caret(&mut self, offset: u32) {
272 self.reset_transient();
273 self.selections.add_caret(offset);
274 }
275
276 pub fn add_next_occurrence(&mut self) {
282 self.reset_transient();
283 let newest = *self.selections.newest();
284 if newest.is_empty() {
285 if let Some((ws, we)) = movement::surrounding_word(&self.buffer, newest.head()) {
288 if ws != we {
289 self.selections.add_selection(ws, we);
290 self.request_reveal(RevealMode::FitForce);
294 }
295 }
296 return;
297 }
298 let needle = self.buffer.slice(newest.start()..newest.end()).into_owned();
301 let len = needle.len() as u32;
302 let taken: Vec<(u32, u32)> =
305 self.selections.all().iter().map(|s| (s.start(), s.end())).collect();
306 let is_taken = |s: u32| {
307 taken.binary_search_by_key(&s, |&(st, _)| st).is_ok_and(|i| taken[i].1 == s + len)
308 };
309 let from = newest.end();
312 let found = scan_from(&self.buffer, &needle, from, self.buffer.len(), &is_taken)
313 .or_else(|| scan_from(&self.buffer, &needle, 0, from, &is_taken));
314 if let Some((start, end)) = found {
315 self.selections.add_selection(start, end);
316 self.request_reveal(RevealMode::FitForce);
319 }
320 }
321
322 pub fn jump_to_bracket(&mut self) {
329 self.reset_transient();
330 let targets: Vec<u32> = self
331 .selections
332 .all()
333 .iter()
334 .map(|s| {
335 let head = s.head();
336 if let Some((b, partner)) = self.brackets.active_pair(head) {
337 if head == b + 1 {
338 partner + 1
339 } else {
340 partner
341 }
342 } else if let Some((_, close)) = self.brackets.enclosing_pair(head) {
343 close
344 } else {
345 head
346 }
347 })
348 .collect();
349 let moved =
350 targets.iter().zip(self.selections.all()).any(|(&t, s)| t != s.head() || !s.is_empty());
351 self.selections = SelectionSet::from_offsets(&targets);
352 for &t in &targets {
353 self.unfold_to_reveal(t); }
355 if moved {
356 self.request_reveal(RevealMode::Fit); }
358 }
359
360 pub fn expand_selection(&mut self) {
366 self.reset_gesture();
367 let before: Vec<(u32, u32)> =
368 self.selections.all().iter().map(|s| (s.start(), s.end())).collect();
369 let ranges: Vec<(u32, u32)> =
370 before.iter().map(|&(s, e)| self.expanded_range(s, e)).collect();
371 if ranges == before {
372 return;
373 }
374 let newest_id = self.selections.newest().id;
375 let newest = self
376 .selections
377 .all()
378 .iter()
379 .position(|s| s.id == newest_id)
380 .expect("the newest selection is in its own set");
381 self.expand_stack.push(self.selections.clone());
382 self.selections = SelectionSet::from_ranges(&ranges, newest);
383 self.request_reveal(RevealMode::Fit); }
385
386 pub fn shrink_selection(&mut self) {
389 self.reset_gesture();
390 if let Some(prev) = self.expand_stack.pop() {
391 self.selections = prev;
392 self.request_reveal(RevealMode::Fit);
393 }
394 }
395
396 fn expanded_range(&self, start: u32, end: u32) -> (u32, u32) {
402 if start == end {
403 if let Some((ws, we)) = movement::surrounding_word(&self.buffer, start) {
404 if ws != we {
405 return (ws, we);
406 }
407 }
408 }
409 if let Some((open, close)) = self.brackets.enclosing_pair_of_range(start, end) {
410 let contents = (crate::row_layout::gap_left_edge(open), close);
411 if contents != (start, end) {
412 return contents;
413 }
414 return (open, close + 1);
415 }
416 (0, self.buffer.len())
417 }
418
419 pub fn add_caret_vertical(&mut self, down: bool) {
429 self.reset_transient();
430 let folds = FoldMap::new(&self.folds, &self.brackets, &self.buffer);
431 let tab = self.tab_size();
432 let delta = if down { 1 } else { -1 };
433 let heads: Vec<u32> = self.selections.all().iter().map(Selection::head).collect();
434 let mut added = false;
435 for head in heads {
436 if let Some(off) = movement::caret_one_display_row(&self.buffer, &folds, tab, head, delta)
437 {
438 self.selections.add_caret(off);
439 added = true;
440 }
441 }
442 if added {
443 self.request_reveal(RevealMode::Fit); }
445 }
446
447 pub fn select_all_occurrences(&mut self) {
456 self.reset_transient();
457 let newest = *self.selections.newest();
458 if newest.is_empty() {
459 let Some((ws, we)) = movement::surrounding_word(&self.buffer, newest.head()) else {
460 return;
461 };
462 if ws == we {
463 return;
464 }
465 self.selections.add_selection(ws, we);
466 }
467 let newest = *self.selections.newest();
468 let existing: Vec<(u32, u32)> =
473 self.selections.all().iter().map(|s| (s.start(), s.end())).collect();
474 let taken: std::collections::HashSet<(u32, u32)> = existing.iter().copied().collect();
475 let occurrences: Vec<(u32, u32)> = {
476 let text = self.buffer.text();
477 let needle = &text[newest.start() as usize..newest.end() as usize];
478 if needle.is_empty() {
479 return;
480 }
481 let len = needle.len() as u32;
482 text.match_indices(needle)
483 .map(|(i, _)| (i as u32, i as u32 + len))
484 .filter(|occ| !taken.contains(occ))
485 .collect()
486 };
487 if occurrences.is_empty() {
488 let head = self.selections.newest().head();
490 self.unfold_to_reveal(head);
491 self.request_reveal(RevealMode::Fit);
492 return;
493 }
494 let wrap = occurrences.partition_point(|&(s, _)| s < newest.end());
500 let reveal = (wrap + occurrences.len() - 1) % occurrences.len();
501 let base = existing.len();
502 let mut ranges = existing;
503 ranges.extend_from_slice(&occurrences);
504 self.selections = SelectionSet::from_ranges(&ranges, base + reveal);
505 let head = self.selections.newest().head();
509 self.unfold_to_reveal(head);
510 self.request_reveal(RevealMode::Fit);
511 }
512
513 pub fn select_find_matches(&mut self) -> bool {
519 let ranges: Vec<(u32, u32)> =
520 self.find_matches_in(0..u32::MAX).map(|(r, _)| (r.start, r.end)).collect();
521 if ranges.is_empty() {
522 return false;
523 }
524 let newest = (self.active_find_match().unwrap_or(0) as usize).min(ranges.len() - 1);
525 self.selections = SelectionSet::from_ranges(&ranges, newest);
526 self.reset_transient(); let head = self.selections.newest().head();
530 self.unfold_to_reveal(head);
531 self.request_reveal(RevealMode::Center); true
533 }
534
535 pub fn collapse_selections(&mut self) {
538 self.reset_transient();
539 self.selections.collapse_to_primary();
540 }
541
542 pub fn select_all(&mut self) {
546 self.reset_transient();
547 let end = self.buffer.len();
548 self.selections.set_single(Selection::from_anchor(SelectionId(0), 0, end));
549 }
550
551 fn reset_transient(&mut self) {
560 self.expand_stack.clear();
561 self.reset_gesture();
562 }
563
564 fn reset_gesture(&mut self) {
568 self.column = None;
569 self.clear_autoclose();
570 self.history.seal();
571 }
572
573 pub(crate) fn add_autoclose_pairs(&mut self, pairs: impl IntoIterator<Item = (u32, u32)>) {
581 self.autoclose.add_sorted_batch(
582 pairs.into_iter().map(|(open, close)| open..close),
583 DecorationKind::AutoClosePair,
584 Stickiness::AlwaysGrows,
585 );
586 }
587
588 pub(crate) fn autoclose_ranges(&self) -> Vec<Range<u32>> {
594 self.autoclose.iter().map(|r| r.range.clone()).collect()
595 }
596
597 pub(crate) fn clear_autoclose(&mut self) {
601 if self.autoclose.is_empty() {
602 return; }
604 self.autoclose.take_matching_in(0..u32::MAX, |_| true);
605 }
606
607 pub(crate) fn validate_autoclose(&mut self) {
613 if self.autoclose.is_empty() {
614 return; }
616 let carets: Vec<u32> = self.selections.all().iter().map(|s| s.head()).collect();
617 let Self { autoclose, buffer, .. } = self;
618 autoclose.take_matching_in(0..u32::MAX, |r| {
619 debug_assert!(
620 matches!(r.kind, DecorationKind::AutoClosePair),
621 "the autoclose store holds only AutoClosePair provenance",
622 );
623 let (start, end) = (r.range.start, r.range.end);
624 let one_line = buffer.offset_to_point(start).row == buffer.offset_to_point(end).row;
625 let occupied = one_line && {
627 let i = carets.partition_point(|&h| h <= start);
628 i < carets.len() && carets[i] <= end
629 };
630 !occupied });
632 }
633
634 pub fn drag_select(&mut self, granularity: Granularity, origin: u32, head: u32) {
640 self.reset_transient();
641 let (anchor, head) = match granularity {
642 Granularity::Char => (origin, head),
643 Granularity::Word => {
644 extend_by_unit(self.word_unit(origin), self.word_unit(head), origin, head)
645 }
646 Granularity::Line => {
647 extend_by_unit(self.line_unit(origin), self.line_unit(head), origin, head)
648 }
649 };
650 self.selections.set_single(Selection::from_anchor(SelectionId(0), anchor, head));
651 }
652
653 fn word_unit(&self, offset: u32) -> (u32, u32) {
655 movement::surrounding_word(&self.buffer, offset).unwrap_or((offset, offset))
656 }
657
658 pub(crate) fn line_unit(&self, offset: u32) -> (u32, u32) {
662 let row = self.buffer.offset_to_point(offset).row;
663 let start = self.buffer.point_to_offset(Point::new(row, 0));
664 let end = if row + 1 < self.buffer.line_count() {
665 self.buffer.point_to_offset(Point::new(row + 1, 0))
666 } else {
667 self.buffer.point_to_offset(Point::new(row, self.buffer.line_len(row)))
668 };
669 (start, end)
670 }
671
672 pub fn column_select(&mut self, dir: ColumnDir) {
683 self.history.seal();
687 self.expand_stack.clear();
688 let folds = FoldMap::new(&self.folds, &self.brackets, &self.buffer);
689 let tab = self.tab_size();
690 let mut col = self.column.unwrap_or_else(|| {
691 let corner = self.caret_corner(&folds, tab);
692 ColumnSelection { anchor: corner, active: corner }
693 });
694 col.active = Self::step_corner(&folds, col.active, dir);
695 self.column = Some(col);
696 self.rebuild_column_box(&folds, tab, col);
697 }
698
699 pub fn column_drag(&mut self, anchor: (u32, u32), active: (u32, u32)) {
709 self.history.seal();
711 self.expand_stack.clear();
712 let folds = FoldMap::new(&self.folds, &self.brackets, &self.buffer);
713 let col = ColumnSelection {
714 anchor: CellCorner { row: anchor.0, cell: anchor.1 },
715 active: CellCorner { row: active.0, cell: active.1 },
716 };
717 self.column = Some(col);
718 self.rebuild_column_box(&folds, self.tab_size(), col);
719 }
720
721 fn caret_corner(&self, folds: &FoldMap, tab: u32) -> CellCorner {
728 let head = self.selections.newest().head();
729 match folds.display_position(&self.buffer, head, tab) {
730 Some(p) => CellCorner {
731 row: folds.to_buffer_row(p.row).0,
732 cell: crate::row_layout::virtual_cell(p.x.cells()),
733 },
734 None => {
735 let p = self.buffer.offset_to_point(head);
736 let layout = folds.row_layout(&self.buffer, BufferRow(p.row), tab);
737 CellCorner { row: p.row, cell: layout.display_cell(p.col) }
738 }
739 }
740 }
741
742 pub fn move_line(&mut self, down: bool) {
747 let sel = *self.selections.newest();
748 let p0 = self.buffer.offset_to_point(sel.start());
749 let p1 = self.buffer.offset_to_point(sel.end());
750 let (r0, r1) = (p0.row, p1.row);
751 let line_count = self.buffer.line_count();
752 let ends_nl = self.buffer.char_before(self.buffer.len()) == Some('\n');
753 let last_movable = if ends_nl { line_count.saturating_sub(2) } else { line_count - 1 };
756 if (down && r1 >= last_movable) || (!down && r0 == 0) {
757 return;
758 }
759 let (a, b) = if down { (r0, r1 + 1) } else { (r0 - 1, r1) };
760 let has_nl = b + 1 < line_count; let region_start = self.buffer.point_to_offset(Point::new(a, 0));
762 let region_end = if has_nl {
763 self.buffer.point_to_offset(Point::new(b + 1, 0))
764 } else {
765 self.buffer.len()
766 };
767 let lines: Vec<Cow<str>> = (a..=b).map(|r| self.buffer.line(r)).collect();
768 let reordered: Vec<Cow<str>> = if down {
769 let mut v = vec![lines[lines.len() - 1].clone()];
771 v.extend_from_slice(&lines[..lines.len() - 1]);
772 v
773 } else {
774 let mut v = lines[1..].to_vec();
776 v.push(lines[0].clone());
777 v
778 };
779 let mut new_text = reordered.join("\n");
780 if has_nl {
781 new_text.push('\n');
782 }
783 let _ = self.edit(vec![EditOp::new(region_start..region_end, &new_text)]);
784 let shift = |r: u32| if down { r + 1 } else { r - 1 };
786 let ns = self.buffer.point_to_offset(Point::new(shift(r0), p0.col));
787 let ne = self.buffer.point_to_offset(Point::new(shift(r1), p1.col));
788 let mut set = SelectionSet::new(0);
789 set.set_single(Selection::from_anchor(SelectionId(0), ns, ne));
790 self.selections = set;
791 self.clear_autoclose();
795 }
796
797 pub fn copy_line(&mut self, down: bool) {
801 let sel = *self.selections.newest();
802 let p0 = self.buffer.offset_to_point(sel.start());
803 let p1 = self.buffer.offset_to_point(sel.end());
804 let (r0, r1) = (p0.row, p1.row);
805 let height = r1 - r0 + 1;
806 let line_count = self.buffer.line_count();
807 let block: String = (r0..=r1).map(|r| self.buffer.line(r)).collect::<Vec<_>>().join("\n");
808 let (at, text) = if !down {
809 (self.buffer.point_to_offset(Point::new(r0, 0)), format!("{block}\n"))
810 } else if r1 + 1 < line_count {
811 (self.buffer.point_to_offset(Point::new(r1 + 1, 0)), format!("{block}\n"))
812 } else {
813 (self.buffer.len(), format!("\n{block}"))
815 };
816 let _ = self.edit(vec![EditOp::new(at..at, &text)]);
817 let (nr0, nr1) = if down { (r0 + height, r1 + height) } else { (r0, r1) };
818 let ns = self.buffer.point_to_offset(Point::new(nr0, p0.col));
819 let ne = self.buffer.point_to_offset(Point::new(nr1, p1.col));
820 let mut set = SelectionSet::new(0);
821 set.set_single(Selection::from_anchor(SelectionId(0), ns, ne));
822 self.selections = set;
823 self.clear_autoclose();
826 }
827
828 fn step_corner(folds: &FoldMap, c: CellCorner, dir: ColumnDir) -> CellCorner {
833 let d = folds.to_display_row(BufferRow(c.row)).index();
834 let row_at = |d: u32| folds.to_buffer_row(DisplayRow(d)).0;
835 match dir {
836 ColumnDir::Up => CellCorner { row: row_at(d.saturating_sub(1)), cell: c.cell },
837 ColumnDir::Down => {
838 CellCorner { row: row_at((d + 1).min(folds.max_display_row().index())), cell: c.cell }
839 }
840 ColumnDir::Left => CellCorner { row: c.row, cell: c.cell.saturating_sub(1) },
841 ColumnDir::Right => CellCorner { row: c.row, cell: c.cell + 1 },
842 }
843 }
844
845 fn rebuild_column_box(&mut self, folds: &FoldMap, tab: u32, col: ColumnSelection) {
853 let da = folds.to_display_row(BufferRow(col.anchor.row)).index();
854 let dv = folds.to_display_row(BufferRow(col.active.row)).index();
855 let (d0, d1) = (da.min(dv), da.max(dv));
856 let mut ranges = Vec::with_capacity((d1 - d0 + 1) as usize);
857 let mut newest = 0;
858 for d in d0..=d1 {
859 let row = folds.to_buffer_row(DisplayRow(d));
860 let anchor_off = folds.hit_row(&self.buffer, row, col.anchor.cell as f32, Bias::Left, tab);
861 let head_off = folds.hit_row(&self.buffer, row, col.active.cell as f32, Bias::Left, tab);
862 if d == dv {
863 newest = ranges.len();
864 }
865 ranges.push((anchor_off, head_off));
866 }
867 self.selections = SelectionSet::from_ranges(&ranges, newest);
868 }
869
870 #[must_use]
874 pub fn text(&self) -> Cow<'_, str> {
875 self.buffer.text()
876 }
877
878 #[must_use]
880 pub fn revision(&self) -> Revision {
881 self.buffer.revision()
882 }
883
884 #[must_use]
887 pub fn snapshot(&self) -> Snapshot {
888 self.buffer.snapshot()
889 }
890
891 pub fn edit(&mut self, ops: Vec<EditOp>) -> Result<Committed, TransactionError> {
898 self.edit_grouped(ops, GroupingHint::discrete())
899 }
900
901 pub fn edit_grouped(
904 &mut self,
905 ops: Vec<EditOp>,
906 hint: GroupingHint,
907 ) -> Result<Committed, TransactionError> {
908 self.column = None; self.expand_stack.clear(); let pre_fold_key = (self.buffer.revision().0, self.folds.generation());
913 let committed = apply(&mut self.buffer, ops)?;
914 if !committed.is_empty() {
915 let bracket_edit = committed
920 .forward_ops()
921 .iter()
922 .chain(committed.inverse_ops())
923 .any(|op| op.text.bytes().any(crate::bracket::is_bracket_byte));
924 self.selections.rebase(committed.patch());
927
928 let tab = self.tab_size();
929 let region = rebase_views(
934 &mut Views {
935 highlight: &mut self.highlight,
936 brackets: &mut self.brackets,
937 decorations: &mut self.decorations,
938 autoclose: &mut self.autoclose,
939 folds: &mut self.folds,
940 find: &mut self.find,
941 },
942 &self.buffer,
943 tab,
944 &committed,
945 );
946
947 self.validate_autoclose();
950 if bracket_edit && !region.is_empty() {
962 self.reconcile_folds_in(region);
963 }
964 {
973 let mut cache = self.fold_cache.borrow_mut();
974 if cache.key == pre_fold_key
975 && self.folds.generation() == pre_fold_key.1
976 && cache.map.apply_patch(committed.patch(), &self.buffer)
977 {
978 cache.key = (self.buffer.revision().0, pre_fold_key.1);
979 } else {
980 cache.key = (u64::MAX, u64::MAX);
981 }
982 }
983 let (patch, forward, inverse) = committed.into_ops();
989 self.history.record(forward, inverse, hint);
990 return Ok(Committed::from_patch(patch));
991 }
992 Ok(committed)
993 }
994
995 pub fn undo(&mut self) -> bool {
1004 self.reset_transient();
1005 let tab = self.tab_size();
1006 let mut caret_home: Option<u32> = None;
1011 let Self {
1012 history, buffer, selections, highlight, brackets, decorations, autoclose, folds, find, ..
1013 } = self;
1014 let mut views = Views { highlight, brackets, decorations, autoclose, folds, find };
1015 let undone = history.undo(buffer, selections, |committed, buffer| {
1016 rebase_views(&mut views, buffer, tab, committed);
1019 if let Some(e) = committed.patch().edits().first() {
1020 caret_home = Some(e.new.start);
1021 }
1022 });
1023 if undone {
1026 self.reconcile_folds();
1027 if let Some(off) = caret_home {
1031 self.selections = SelectionSet::new(off);
1032 }
1033 }
1034 undone
1035 }
1036
1037 pub fn redo(&mut self) -> bool {
1040 self.reset_transient();
1041 let tab = self.tab_size();
1042 let mut caret_home: Option<u32> = None;
1046 let Self {
1047 history, buffer, selections, highlight, brackets, decorations, autoclose, folds, find, ..
1048 } = self;
1049 let mut views = Views { highlight, brackets, decorations, autoclose, folds, find };
1050 let redone = history.redo(buffer, selections, |committed, buffer| {
1051 rebase_views(&mut views, buffer, tab, committed);
1053 if let Some(e) = committed.patch().edits().last() {
1054 caret_home = Some(e.new.end);
1055 }
1056 });
1057 if redone {
1058 self.reconcile_folds();
1059 if let Some(off) = caret_home {
1060 self.selections = SelectionSet::new(off);
1061 }
1062 }
1063 redone
1064 }
1065
1066 #[must_use]
1068 pub fn is_dirty(&self) -> bool {
1069 self.history.is_dirty()
1070 }
1071
1072 pub fn mark_saved(&mut self) {
1074 self.history.mark_saved();
1075 }
1076
1077 #[must_use]
1079 pub fn can_undo(&self) -> bool {
1080 self.history.can_undo()
1081 }
1082
1083 #[must_use]
1086 pub fn can_redo(&self) -> bool {
1087 self.history.can_redo()
1088 }
1089
1090 #[must_use]
1095 pub fn redo_branch_count(&self) -> usize {
1096 self.history.redo_branch_count()
1097 }
1098
1099 pub fn select_redo_branch(&mut self, index: usize) -> bool {
1104 self.history.select_redo_branch(index)
1105 }
1106
1107 pub fn set_undo_limit(&mut self, limit: Option<usize>) {
1112 self.history.set_max_undo(limit);
1113 }
1114
1115 #[must_use]
1118 pub fn undo_depth(&self) -> usize {
1119 self.history.undo_depth()
1120 }
1121
1122 #[must_use]
1124 pub fn serialize(&self, flavor: EolFlavor) -> String {
1125 self.buffer.serialize(flavor)
1126 }
1127
1128 pub fn set_syntax(&mut self, syntax: SyntaxDef, theme: TokenTheme) {
1136 let aim = self.highlight.as_ref().map(HighlightCache::window_aim);
1137 let mut cache = HighlightCache::new(syntax, theme, self.buffer.line_count());
1138 if let Some(aim) = aim {
1139 cache.set_window(aim);
1140 }
1141 self.highlight = Some(cache);
1142 }
1143
1144 pub fn set_theme(&mut self, theme: TokenTheme) {
1149 if let Some(cache) = self.highlight.as_mut() {
1150 cache.set_theme(theme);
1151 }
1152 }
1153
1154 #[must_use]
1159 pub fn decorations(&self) -> &DecorationStore {
1160 &self.decorations
1161 }
1162
1163 pub fn decorations_mut(&mut self) -> &mut DecorationStore {
1165 &mut self.decorations
1166 }
1167
1168 #[cfg(test)]
1171 #[must_use]
1172 pub(crate) fn autoclose_pair_count(&self) -> usize {
1173 self.autoclose.len()
1174 }
1175
1176 #[must_use]
1179 pub fn folds(&self) -> &FoldSet {
1180 &self.folds
1181 }
1182
1183 pub fn folds_mut(&mut self) -> &mut FoldSet {
1187 &mut self.folds
1188 }
1189
1190 pub fn fold(&mut self, header: BufferRow, last: BufferRow) -> bool {
1196 match self.opener_of(header.0, last.0) {
1197 Some(open) => self.folds.fold(open),
1198 None => false,
1199 }
1200 }
1201
1202 pub fn unfold(&mut self, header: BufferRow) -> bool {
1205 match self.folded_opener_on(header.0) {
1206 Some(open) => self.folds.unfold(open),
1207 None => false,
1208 }
1209 }
1210
1211 pub fn toggle_fold(&mut self, header: BufferRow, last: BufferRow) -> bool {
1214 match self.folded_opener_on(header.0) {
1215 Some(open) => self.folds.unfold(open),
1216 None => self.fold(header, last),
1217 }
1218 }
1219
1220 fn folded_opener_on(&self, header: u32) -> Option<u32> {
1225 let start = self.buffer.point_to_offset(Point::new(header, 0));
1226 let end = if header + 1 >= self.buffer.line_count() {
1227 self.buffer.len() + 1 } else {
1229 self.buffer.point_to_offset(Point::new(header + 1, 0))
1230 };
1231 self.folds.first_at_or_after(start).filter(|&o| o < end)
1232 }
1233
1234 fn opener_of(&self, header: u32, last: u32) -> Option<u32> {
1237 self.foldable_pairs_in_rows(header..header + 1)
1239 .into_iter()
1240 .find(|&(_, _, h, l)| h == header && l == last)
1241 .map(|(o, ..)| o)
1242 }
1243
1244 fn foldable_pairs(&self) -> Vec<(u32, u32, u32, u32)> {
1249 self.foldable_pairs_from(&self.brackets.all())
1250 }
1251
1252 pub fn foldable_pairs_in_rows(&self, rows: Range<u32>) -> Vec<(u32, u32, u32, u32)> {
1271 let start = self.buffer.point_to_offset(Point::new(rows.start, 0));
1272 let end = if rows.end >= self.buffer.line_count() {
1273 self.buffer.len() + 1 } else {
1275 self.buffer.point_to_offset(Point::new(rows.end, 0))
1276 };
1277 self.foldable_pairs_from(&self.brackets.in_range(start..end))
1278 }
1279
1280 fn foldable_pairs_from(&self, brackets: &[crate::bracket::Bracket]) -> Vec<(u32, u32, u32, u32)> {
1282 brackets
1283 .iter()
1284 .filter_map(|b| {
1285 let close = b.partner.filter(|_| b.open)?;
1286 crate::row_layout::pair_has_interior(b.offset, close).then(|| {
1288 let header = self.buffer.offset_to_point(b.offset).row;
1289 let last = self.buffer.offset_to_point(close).row;
1290 (b.offset, close, header, last)
1291 })
1292 })
1293 .collect()
1294 }
1295
1296 pub fn toggle_fold_opener(&mut self, opener: u32) -> bool {
1303 let changed = self.folds.toggle(opener);
1304 if changed {
1305 self.reset_transient();
1311 }
1312 if changed && self.folds.is_folded(opener) {
1313 if let Some(close) = self.brackets.foldable_partner(opener) {
1314 let single_line = self.buffer.offset_to_point(opener).row == self.buffer.offset_to_point(close).row;
1315 if single_line {
1316 self.selections.map_each(|s| {
1319 if crate::row_layout::gap_hides_caret(opener, close, s.head()) {
1320 s.move_to_caret(crate::row_layout::gap_left_edge(opener));
1321 }
1322 });
1323 } else {
1324 let tab = self.tab_size();
1333 let Self { selections, buffer, folds, brackets, .. } = self;
1334 let fold_map = crate::fold_map::FoldMap::new(folds, brackets, buffer);
1335 let header = buffer.offset_to_point(opener).row;
1336 let entry = buffer.point_to_offset(Point::new(header, buffer.line_len(header)));
1337 selections.map_each(|s| {
1338 let inside = s.head() > opener && s.head() < close;
1339 if inside && fold_map.display_position(buffer, s.head(), tab).is_none() {
1340 s.move_to_caret(entry);
1341 }
1342 });
1343 }
1344 }
1345 }
1346 changed
1347 }
1348
1349 #[must_use]
1355 pub fn tab_size(&self) -> u32 {
1356 crate::display_map::default_tab_size()
1357 }
1358
1359 #[must_use]
1362 pub fn block_opener_on_row(&self, row: u32) -> Option<u32> {
1363 self.foldable_pairs_in_rows(row..row + 1)
1367 .into_iter()
1368 .filter(|&(_, _, h, l)| h == row && l > h)
1369 .max_by_key(|&(_, _, h, l)| l - h)
1370 .map(|(o, ..)| o)
1371 }
1372
1373 #[must_use]
1381 pub fn fold_opener_at_caret(&self, want_folded: bool) -> Option<u32> {
1382 self.fold_opener_at(self.selections.newest().head(), want_folded)
1383 }
1384
1385 fn fold_opener_at(&self, caret: u32, want_folded: bool) -> Option<u32> {
1392 let is_target = |open: u32, close: u32| {
1393 crate::row_layout::pair_has_interior(open, close)
1394 && self.folds.is_folded(open) == want_folded
1395 };
1396 let touch_left = self.brackets.foldable_partner(caret).map(|close| (caret, close));
1400 let touch_right = caret
1401 .checked_sub(1)
1402 .and_then(|o| self.brackets.at(o))
1403 .filter(|b| !b.open)
1404 .and_then(|b| b.partner.map(|open| (open, b.offset)));
1405 if let Some((open, _)) = [touch_left, touch_right]
1406 .into_iter()
1407 .flatten()
1408 .filter(|&(o, c)| is_target(o, c))
1409 .min_by_key(|&(o, c)| c - o)
1410 {
1411 return Some(open);
1412 }
1413 self.brackets.innermost_enclosing_where(caret, is_target)
1415 }
1416
1417 pub fn fold_at_carets(&mut self, unfold: bool) -> bool {
1424 let mut openers: Vec<u32> = self
1425 .selections
1426 .all()
1427 .iter()
1428 .filter_map(|s| self.fold_opener_at(s.head(), unfold))
1429 .collect();
1430 openers.sort_unstable();
1431 openers.dedup();
1432 if openers.is_empty() {
1433 return false;
1434 }
1435 self.reset_transient();
1442 if unfold {
1443 self.folds.unfold_all(&openers);
1444 } else {
1445 self.folds.fold_all(openers.iter().copied());
1446 }
1447 self.eject_hidden_carets();
1448 true
1449 }
1450
1451 fn eject_hidden_carets(&mut self) {
1455 let Self { selections, buffer, folds, brackets, .. } = self;
1456 if folds.is_empty() {
1457 return;
1458 }
1459 let fold_map = crate::fold_map::FoldMap::new(folds, brackets, buffer);
1460 selections.map_each(|s| {
1461 if let Some(entry) = fold_map.entry_edge_if_hidden(buffer, s.head()) {
1462 s.move_to_caret(entry);
1463 }
1464 });
1465 }
1466
1467 #[must_use]
1473 pub fn collapsible_pairs(&self) -> Vec<(u32, u32, u32, u32)> {
1474 self.foldable_pairs().into_iter().filter(|&(open, ..)| !self.folds.is_folded(open)).collect()
1475 }
1476
1477 #[must_use]
1485 pub fn collapsible_pairs_in_rows(&self, rows: Range<u32>) -> Vec<(u32, u32, u32, u32)> {
1486 self.foldable_pairs_in_rows(rows)
1487 .into_iter()
1488 .filter(|&(open, ..)| !self.folds.is_folded(open))
1489 .collect()
1490 }
1491
1492 #[must_use]
1498 pub fn collapsible_at(&self, offset: u32) -> Option<u32> {
1499 self.collapsible_pairs()
1500 .into_iter()
1501 .filter(|&(open, close, _, _)| open < offset && offset < close)
1502 .min_by_key(|&(open, close, _, _)| close - open)
1503 .map(|(o, ..)| o)
1504 }
1505
1506 fn reconcile_folds(&mut self) {
1513 if self.folds.is_empty() {
1514 return;
1515 }
1516 let brackets = &self.brackets;
1522 self.folds.reconcile(|o| {
1523 brackets
1524 .foldable_partner(o)
1525 .is_some_and(|close| crate::row_layout::pair_has_interior(o, close))
1526 });
1527 }
1528
1529 #[cfg(test)]
1532 pub(crate) fn force_whole_reconcile(&mut self) {
1533 self.reconcile_folds();
1534 }
1535
1536 fn reconcile_folds_in(&mut self, region: core::ops::Range<u32>) {
1547 if self.folds.is_empty() {
1548 return;
1549 }
1550 let candidates: Vec<u32> = self.folds.openers_in(region);
1551 let brackets = &self.brackets;
1552 self.folds.reconcile_only(&candidates, |o| {
1553 brackets
1554 .foldable_partner(o)
1555 .is_some_and(|close| crate::row_layout::pair_has_interior(o, close))
1556 });
1557 }
1558
1559 #[must_use]
1564 pub fn foldable_ranges(&self) -> Vec<(BufferRow, BufferRow)> {
1565 let mut ranges: Vec<(BufferRow, BufferRow)> = self
1566 .brackets
1567 .all()
1568 .iter()
1569 .filter_map(|br| {
1570 let close = br.partner.filter(|_| br.open)?;
1571 let header = self.buffer.offset_to_point(br.offset).row;
1572 let last = self.buffer.offset_to_point(close).row;
1573 (last > header).then_some((BufferRow(header), BufferRow(last)))
1574 })
1575 .collect();
1576 ranges.sort_by_key(|r| (r.0, core::cmp::Reverse(r.1)));
1578 ranges.dedup_by_key(|r| r.0);
1579 ranges
1580 }
1581
1582 #[must_use]
1589 pub fn foldable_ranges_in_rows(&self, rows: Range<u32>) -> Vec<(BufferRow, BufferRow)> {
1590 let start = self.buffer.point_to_offset(Point::new(rows.start, 0));
1591 let end = if rows.end >= self.buffer.line_count() {
1592 self.buffer.len() + 1 } else {
1594 self.buffer.point_to_offset(Point::new(rows.end, 0))
1595 };
1596 let mut ranges: Vec<(BufferRow, BufferRow)> = self
1597 .brackets
1598 .in_range_iter(start..end)
1599 .filter_map(|br| {
1600 let close = br.partner.filter(|_| br.open)?;
1601 let header = self.buffer.offset_to_point(br.offset).row;
1602 let last = self.buffer.offset_to_point(close).row;
1603 (last > header).then_some((BufferRow(header), BufferRow(last)))
1604 })
1605 .collect();
1606 ranges.sort_by_key(|r| (r.0, core::cmp::Reverse(r.1)));
1607 ranges.dedup_by_key(|r| r.0);
1608 ranges
1609 }
1610
1611 pub fn tokenize_highlight(&mut self, target: u32) {
1615 let buffer = &self.buffer;
1620 let Some(cache) = self.highlight.as_mut() else { return };
1621 cache.tokenize_until(target, crate::highlight::HIGHLIGHT_MAX_LINES_PER_CALL, |r| {
1622 buffer.line(r)
1623 });
1624 }
1625
1626 #[must_use]
1633 pub fn highlight_frontier(&self) -> Option<u32> {
1634 self.highlight.as_ref().and_then(HighlightCache::pending)
1635 }
1636
1637 pub fn set_highlight_window(&mut self, rows: Range<u32>) {
1645 if let Some(cache) = self.highlight.as_mut() {
1646 cache.set_window(rows);
1647 }
1648 }
1649
1650 #[must_use]
1653 pub fn highlight_line_spans(&self, row: u32) -> Option<&[HighlightSpan]> {
1654 self.highlight.as_ref().and_then(|c| c.line_spans(row))
1655 }
1656
1657 #[must_use]
1663 pub fn highlight_engine(&self) -> Option<HighlightEngine> {
1664 self.highlight.as_ref().map(HighlightCache::engine)
1665 }
1666
1667 pub fn absorb_highlight(
1687 &mut self,
1688 revision: Revision,
1689 seg: crate::SegmentTokens,
1690 verified: bool,
1691 ) -> bool {
1692 if revision != self.buffer.revision() {
1693 return false;
1694 }
1695 let Some(cache) = self.highlight.as_mut() else { return false };
1696 cache.absorb(seg, verified);
1697 true
1698 }
1699
1700 #[must_use]
1703 pub fn brackets(&self) -> &Brackets {
1704 &self.brackets
1705 }
1706
1707 pub fn set_diagnostics(&mut self, revision: Revision, diags: Vec<Diagnostic>) -> DiagnosticsOutcome {
1717 let current = self.buffer.revision();
1718 if revision != current {
1719 return DiagnosticsOutcome::Stale { current: current.0 };
1720 }
1721 let len = self.buffer.len();
1722 let clipped: Vec<Diagnostic> = diags
1723 .into_iter()
1724 .map(|mut d| {
1725 let (s, e) = (d.span.start.min(len), d.span.end.min(len));
1726 d.span = s.min(e)..e; d
1728 })
1729 .collect();
1730 self.decorations.set_diagnostics(current.0, current.0, clipped)
1731 }
1732
1733 pub fn diagnostics_in(
1737 &self,
1738 range: Range<u32>,
1739 ) -> impl Iterator<Item = (Range<u32>, Severity, std::sync::Arc<str>)> + '_ {
1740 self.decorations.decorations_in(range).filter_map(|r| {
1744 let TrackedRange { range, kind, .. } = r;
1745 match kind {
1746 DecorationKind::Diagnostic { severity, message, .. } => {
1747 Some((range, severity, message))
1748 }
1749 _ => None,
1750 }
1751 })
1752 }
1753
1754 pub fn set_find_query(&mut self, query: Option<FindQuery>, now_ms: u64) {
1762 self.find.set_query(query, &self.buffer, &mut self.decorations, now_ms);
1763 }
1764
1765 #[must_use]
1767 pub fn find_query(&self) -> Option<&FindQuery> {
1768 self.find.query()
1769 }
1770
1771 pub fn set_find_scope(&mut self, scope: Option<Range<u32>>, now_ms: u64) {
1781 self.find.set_scope(scope, &self.buffer, &mut self.decorations, now_ms);
1782 }
1783
1784 #[must_use]
1786 pub fn find_scope(&self) -> Option<Range<u32>> {
1787 self.find.scope()
1788 }
1789
1790 #[must_use]
1797 pub fn find_pattern_error(&self) -> Option<&str> {
1798 self.find.pattern_error()
1799 }
1800
1801 #[must_use]
1813 pub fn caret_word_occurrences(&self, within: Range<u32>) -> Vec<Range<u32>> {
1814 let newest = self.selections.newest();
1815 if !newest.is_empty() {
1816 return Vec::new();
1817 }
1818 let Some((ws, we)) = movement::surrounding_word(&self.buffer, newest.head()) else {
1819 return Vec::new();
1820 };
1821 if ws == we {
1822 return Vec::new();
1823 }
1824 let word = self.buffer.slice(ws..we);
1825 let end = within.end.min(self.buffer.len());
1826 let base = within.start.min(end);
1827 let hay = self.buffer.slice(base..end);
1828 let mut out = Vec::new();
1829 let mut from = 0usize;
1830 while let Some(i) = hay[from..].find(&*word) {
1831 let start = base + (from + i) as u32;
1832 let match_end = start + word.len() as u32;
1833 if movement::surrounding_word(&self.buffer, start) == Some((start, match_end)) {
1835 out.push(start..match_end);
1836 }
1837 from = from + i + word.len();
1838 }
1839 out
1840 }
1841
1842 #[must_use]
1848 pub fn find_match_count(&self) -> usize {
1849 self.decorations.find_count()
1850 }
1851
1852 #[must_use]
1858 pub fn active_find_match(&self) -> Option<u32> {
1859 self.find.active_start().map(|s| self.decorations.find_rank_before(s) as u32)
1860 }
1861
1862 pub fn find_matches_in(&self, range: Range<u32>) -> impl Iterator<Item = (Range<u32>, bool)> + '_ {
1866 let active = self.find.active_id();
1867 self.decorations.decorations_in(range).filter_map(move |r| match &r.kind {
1868 DecorationKind::FindMatch if r.range.start < r.range.end => {
1869 Some((r.range.clone(), Some(r.id) == active))
1870 }
1871 _ => None,
1872 })
1873 }
1874
1875 pub fn overview_marks(
1886 &self,
1887 bounds: &[u32],
1888 sev_out: &mut Vec<(u8, u32)>,
1889 find_out: &mut Vec<Option<u32>>,
1890 ) {
1891 self.decorations.diagnostic_overview(bounds, sev_out);
1892 self.decorations.find_overview(bounds, find_out);
1893 }
1894
1895 pub fn maybe_rescan_find(&mut self, now_ms: u64) -> bool {
1903 self.find.maybe_rescan(&self.buffer, &mut self.decorations, now_ms)
1904 }
1905
1906 pub fn find_next(&mut self, now_ms: u64) -> Option<Range<u32>> {
1910 self.step_find(true, now_ms)
1911 }
1912
1913 pub fn find_prev(&mut self, now_ms: u64) -> Option<Range<u32>> {
1916 self.step_find(false, now_ms)
1917 }
1918
1919 pub fn replace_next(
1933 &mut self,
1934 replacement: &str,
1935 preserve_case: bool,
1936 now_ms: u64,
1937 ) -> Option<Range<u32>> {
1938 self.find.maybe_rescan(&self.buffer, &mut self.decorations, now_ms);
1939 let extent = {
1940 let sel = self.selections.newest();
1941 sel.start()..sel.end()
1942 };
1943 let armed = !extent.is_empty()
1947 && self.find.active_range(&self.decorations).is_some_and(|r| r == extent);
1948 if armed {
1949 let text = self.find.query().cloned().map_or_else(
1952 || replacement.to_string(),
1953 |q| {
1954 crate::find::replacements(
1955 &self.buffer,
1956 &q,
1957 extent.clone(),
1958 replacement,
1959 preserve_case,
1960 )
1961 .into_iter()
1962 .next()
1963 .map_or_else(|| replacement.to_string(), |(_, t)| t)
1964 },
1965 );
1966 if self.edit(vec![EditOp::new(extent, text)]).is_err() {
1967 return None; }
1969 }
1970 self.step_find(true, now_ms)
1971 }
1972
1973 pub fn replace_all(&mut self, replacement: &str, preserve_case: bool) -> usize {
1998 let Some(query) = self.find.query().cloned() else {
1999 return 0; };
2001 let within = self.find.scope().unwrap_or(0..self.buffer.len());
2004 let plan =
2005 crate::find::replacements(&self.buffer, &query, within, replacement, preserve_case);
2006 if plan.is_empty() {
2007 return 0; }
2009 let n = plan.len();
2010 let ops = plan.into_iter().map(|(r, t)| EditOp::new(r, t)).collect();
2011 if self.edit(ops).is_err() {
2012 return 0;
2013 }
2014 n
2015 }
2016
2017 fn step_find(&mut self, forward: bool, now_ms: u64) -> Option<Range<u32>> {
2019 self.find.maybe_rescan(&self.buffer, &mut self.decorations, now_ms);
2023 let sel = self.selections.newest();
2024 let (head, extent) = (sel.head(), sel.start()..sel.end());
2025 let found = if forward {
2026 self.find.find_next(head, extent, &self.decorations)
2027 } else {
2028 self.find.find_prev(head, extent, &self.decorations)
2029 };
2030 if let Some(range) = &found {
2031 self.selections
2033 .set_single(Selection::from_anchor(SelectionId(0), range.start, range.end));
2034 self.reset_transient(); self.unfold_to_reveal(range.start);
2038 self.unfold_to_reveal(range.end);
2039 self.request_reveal(RevealMode::Center); }
2041 found
2042 }
2043
2044 pub fn next_diagnostic(&mut self, forward: bool) -> Option<Range<u32>> {
2051 let sel = self.selections.newest();
2055 let anchor = (sel.start(), sel.end());
2056 let mut all: Vec<Range<u32>> = self.diagnostics_in(0..u32::MAX).map(|(r, ..)| r).collect();
2057 all.sort_by_key(|r| (r.start, r.end));
2058 let target = if forward {
2059 all.iter().find(|r| (r.start, r.end) > anchor).or_else(|| all.first())
2060 } else {
2061 all.iter().rev().find(|r| (r.start, r.end) < anchor).or_else(|| all.last())
2062 }?
2063 .clone();
2064 self.selections
2065 .set_single(Selection::from_anchor(SelectionId(0), target.start, target.end));
2066 self.reset_transient(); self.unfold_to_reveal(target.start);
2068 self.unfold_to_reveal(target.end);
2069 self.request_reveal(RevealMode::Center); Some(target)
2071 }
2072
2073 fn unfold_to_reveal(&mut self, offset: u32) -> bool {
2080 let tab = self.tab_size();
2081 let mut any = false;
2082 loop {
2083 let fm = FoldMap::new(&self.folds, &self.brackets, &self.buffer);
2084 let renders = fm.display_position(&self.buffer, offset, tab).is_some();
2090 let chip_hidden = fm.inline_fold_before(offset).is_some_and(|f| f.hides_caret_at(offset));
2093 if renders && !chip_hidden {
2094 return any;
2095 }
2096 let target = self
2099 .folds
2100 .iter()
2101 .filter_map(|open| self.brackets.foldable_partner(open).map(|c| (open, c)))
2102 .filter(|&(open, close)| open < offset && offset < close)
2103 .min_by_key(|&(open, close)| close - open);
2104 let Some((open, _)) = target else {
2105 return any; };
2107 self.folds.unfold(open);
2108 any = true;
2109 }
2110 }
2111
2112 #[must_use]
2116 pub fn reveal_seq(&self) -> u64 {
2117 self.reveal_seq
2118 }
2119}
2120
2121fn expand_folds_touched(
2130 folds: &mut FoldSet,
2131 brackets: &Brackets,
2132 buffer: &Buffer,
2133 tab: u32,
2134 committed: &Committed,
2135) {
2136 if folds.is_empty() || committed.patch().is_empty() {
2137 return;
2138 }
2139 let mut pts: Vec<u32> = Vec::with_capacity(committed.patch().edits().len() * 2);
2152 for e in committed.patch().edits() {
2153 pts.push(e.new.start);
2154 pts.push(e.new.end);
2155 }
2156 pts.sort_unstable();
2157 pts.dedup();
2158 let (first, last) = (pts[0], *pts.last().expect("edits() is non-empty here"));
2159 let lo = brackets
2160 .enclosing_or_touching(first)
2161 .into_iter()
2162 .filter(|&(o, _)| folds.is_folded(o))
2163 .map(|(o, _)| o)
2164 .min()
2165 .unwrap_or(first);
2166 let candidates: Vec<(u32, u32)> = folds
2170 .openers_in(lo..last.saturating_add(1))
2171 .iter()
2172 .filter_map(|&o| {
2173 let c = brackets.foldable_partner(o)?;
2174 let touches =
2175 pts.partition_point(|&p| p < o) < pts.partition_point(|&p| p <= c.saturating_add(1));
2176 touches.then_some((o, c))
2177 })
2178 .collect();
2179 if candidates.is_empty() {
2180 return;
2181 }
2182 let mut sub = crate::fold_map::FoldSet::new();
2183 for &(open, _) in &candidates {
2184 sub.fold(open);
2185 }
2186 let fold_map = crate::fold_map::FoldMap::new(&sub, brackets, buffer);
2187 let any_hidden = pts.iter().any(|&p| fold_map.display_position(buffer, p, tab).is_none());
2193 let mut starts: Vec<u32> = committed.patch().edits().iter().map(|e| e.new.start).collect();
2196 starts.sort_unstable();
2197 let touched: Vec<u32> = candidates
2198 .iter()
2199 .filter(|&&(opener, close)| {
2200 let single = buffer.offset_to_point(opener).row == buffer.offset_to_point(close).row;
2201 if single {
2202 starts.partition_point(|&s| s <= close) > starts.partition_point(|&s| s <= opener)
2205 } else {
2206 any_hidden
2210 }
2211 })
2212 .map(|&(opener, _)| opener)
2213 .collect();
2214 for opener in touched {
2215 folds.unfold(opener);
2216 }
2217}
2218
2219struct Views<'a> {
2225 highlight: &'a mut Option<HighlightCache>,
2226 brackets: &'a mut Brackets,
2227 decorations: &'a mut DecorationStore,
2228 autoclose: &'a mut DecorationStore,
2232 folds: &'a mut FoldSet,
2233 find: &'a mut FindState,
2234}
2235
2236fn rebase_views(
2247 views: &mut Views,
2248 buffer: &Buffer,
2249 tab: u32,
2250 committed: &Committed,
2251) -> core::ops::Range<u32> {
2252 if let Some(cache) = views.highlight.as_mut() {
2258 let edits = committed.patch().edits();
2259 if !edits.is_empty() {
2260 let mut spans: Vec<(u32, u32, u32)> = Vec::with_capacity(edits.len());
2268 let mut acc: i64 = 0;
2269 for (e, inv) in edits.iter().zip(committed.inverse_ops()) {
2270 let post_sr = buffer.offset_to_point(e.new.start).row;
2271 let post_er = buffer.offset_to_point(e.new.end).row;
2272 let new_lines = post_er - post_sr + 1;
2273 let old_lines = inv.text.bytes().filter(|&b| b == b'\n').count() as u32 + 1;
2274 let pre_start = (i64::from(post_sr) - acc) as u32;
2275 acc += i64::from(new_lines) - i64::from(old_lines);
2276 match spans.last_mut() {
2277 Some(last) if pre_start < last.0 + last.1 => {
2280 let merged_end = (last.0 + last.1).max(pre_start + old_lines);
2281 let combined_delta = (i64::from(last.2) - i64::from(last.1))
2282 + (i64::from(new_lines) - i64::from(old_lines));
2283 last.1 = merged_end - last.0;
2284 last.2 = (i64::from(last.1) + combined_delta) as u32;
2285 }
2286 _ => spans.push((pre_start, old_lines, new_lines)),
2287 }
2288 }
2289 debug_assert_eq!(
2294 spans.iter().map(|&(_, o, n)| i64::from(n) - i64::from(o)).sum::<i64>(),
2295 i64::from(buffer.line_count()) - i64::from(cache.line_count()),
2296 "per-edit line deltas must sum to the buffer's line-count change",
2297 );
2298 cache.on_commit_patch(&spans);
2299 }
2300 }
2301 let reconcile_region = views.brackets.apply_edit(committed.patch(), buffer);
2306 views.decorations.apply_patch(committed.patch());
2310 views.autoclose.apply_patch(committed.patch());
2317 views.find.on_commit(committed.patch(), buffer, views.decorations);
2322 views.folds.apply_patch(committed.patch());
2327 expand_folds_touched(views.folds, views.brackets, buffer, tab, committed);
2335 reconcile_region
2339}
2340
2341fn extend_by_unit(origin_unit: (u32, u32), head_unit: (u32, u32), origin: u32, head: u32) -> (u32, u32) {
2346 let (os, oe) = origin_unit;
2347 let (hs, he) = head_unit;
2348 if head >= origin {
2349 (os, he.max(oe))
2350 } else {
2351 (oe, hs.min(os))
2352 }
2353}
2354
2355fn scan_from(
2366 buffer: &Buffer,
2367 needle: &str,
2368 lo: u32,
2369 hi: u32,
2370 is_taken: &impl Fn(u32) -> bool,
2371) -> Option<(u32, u32)> {
2372 let k = needle.len() as u64;
2373 if k == 0 || lo >= hi {
2374 return None;
2375 }
2376 let len = buffer.len();
2377 let window = u64::from(crate::buffer::SCAN_WINDOW).max(k * 2); let mut pos = lo;
2379 while u64::from(pos) + k <= u64::from(len) {
2380 let win_end = buffer
2381 .clip_offset((u64::from(pos) + window).min(u64::from(len)) as u32, Bias::Right);
2382 let slice = buffer.slice(pos..win_end);
2383 let mut last_end: Option<u32> = None;
2384 for (i, _) in slice.match_indices(needle) {
2385 let start = pos + i as u32;
2386 if u64::from(start) >= u64::from(hi) {
2387 return None; }
2389 last_end = Some(start + k as u32);
2390 if !is_taken(start) {
2391 return Some((start, start + k as u32));
2392 }
2393 }
2394 if win_end >= len {
2395 break;
2396 }
2397 pos = buffer.scan_resume(pos, win_end, k as u32, last_end);
2400 }
2401 None
2402}
2403
2404#[cfg(test)]
2411fn find_next_occurrence(text: &str, needle: &str, from: u32, taken: &[(u32, u32)]) -> Option<(u32, u32)> {
2412 if needle.is_empty() {
2413 return None;
2414 }
2415 let len = needle.len() as u32;
2416 let is_taken = |start: u32| taken.iter().any(|&(s, e)| s == start && e == start + len);
2417 let mut wrapped: Option<u32> = None;
2418 for (idx, _) in text.match_indices(needle) {
2419 let start = idx as u32;
2420 if is_taken(start) {
2421 continue;
2422 }
2423 if start >= from {
2424 return Some((start, start + len));
2425 }
2426 if wrapped.is_none() {
2427 wrapped = Some(start); }
2429 }
2430 wrapped.map(|start| (start, start + len))
2431}
2432
2433#[cfg(test)]
2434mod tests {
2435 use super::*;
2436 use crate::history::{GroupingHint, OpClass};
2437
2438 fn doc(s: &str) -> Document {
2439 Document::new(s).unwrap()
2440 }
2441
2442 fn typ(op: OpClass) -> GroupingHint {
2443 GroupingHint::mergeable(op)
2444 }
2445
2446 fn assert_brackets_fresh(d: &Document) {
2449 let fresh = crate::bracket::Brackets::match_text(&d.text());
2450 assert_eq!(d.brackets().all(), fresh.all(), "incremental brackets diverged from scratch");
2451 }
2452
2453 #[test]
2454 fn brackets_ride_edits_and_undo_redo_through_the_one_mover() {
2455 let mut d = doc("fn a() {\n x[0] = (1 + 2);\n}\n");
2461 let mut rng = 0xC0FFEEu64;
2462 let mut next = move |m: u64| {
2463 rng ^= rng << 13;
2464 rng ^= rng >> 7;
2465 rng ^= rng << 17;
2466 rng % m
2467 };
2468 let inserts = ["{", "}", "(", ")", "[", "]", "\n", "x", "{}", "([)", "\n}\n"];
2469 for i in 0..200 {
2470 match next(4) {
2471 0 | 1 => {
2472 let at = next(d.buffer().len() as u64 + 1) as u32;
2473 let at = d.buffer().clip_offset(at, crate::Bias::Left);
2474 let text = inserts[next(inserts.len() as u64) as usize];
2475 let _ = d.edit(vec![EditOp::insert(at, text)]);
2476 }
2477 2 => {
2478 let a = next(d.buffer().len() as u64 + 1) as u32;
2479 let a = d.buffer().clip_offset(a, crate::Bias::Left);
2480 let b = (a + 1 + next(4) as u32).min(d.buffer().len());
2481 let b = d.buffer().clip_offset(b, crate::Bias::Right);
2482 if a < b {
2483 let _ = d.edit(vec![EditOp::delete(a..b)]);
2484 }
2485 }
2486 _ => {
2487 if next(2) == 0 {
2488 d.undo();
2489 } else {
2490 d.redo();
2491 }
2492 }
2493 }
2494 assert_brackets_fresh(&d);
2495 let _ = i;
2496 }
2497 }
2498
2499 #[test]
2500 fn edit_undo_redo_round_trip() {
2501 let mut d = doc("hello");
2502 d.edit(vec![EditOp::new(0..5, "world")]).unwrap();
2503 assert_eq!(d.text(), "world");
2504 assert!(d.undo());
2505 assert_eq!(d.text(), "hello");
2506 assert!(d.redo());
2507 assert_eq!(d.text(), "world");
2508 assert!(d.undo());
2510 assert!(!d.undo()); assert_eq!(d.text(), "hello");
2512 }
2513
2514 #[test]
2515 fn a_new_edit_invalidates_redo() {
2516 let mut d = doc("");
2517 d.edit(vec![EditOp::insert(0, "a")]).unwrap();
2518 d.edit(vec![EditOp::insert(1, "b")]).unwrap(); d.undo(); d.edit(vec![EditOp::insert(1, "X")]).unwrap(); assert_eq!(d.text(), "aX");
2522 assert!(!d.redo(), "redo must be cleared by the divergent edit");
2523 assert_eq!(d.text(), "aX");
2524 }
2525
2526 #[test]
2527 fn a_retained_branch_is_reachable_from_the_fork() {
2528 let mut d = doc("");
2535 d.edit(vec![EditOp::insert(0, "a")]).unwrap();
2536 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");
2540 assert!(d.undo());
2542 assert_eq!(d.text(), "a");
2543 assert_eq!(d.redo_branch_count(), 2, "the 'b' and 'X' branches both survive");
2544 assert!(d.can_redo());
2545 assert!(d.select_redo_branch(0));
2547 assert!(d.redo());
2548 assert_eq!(d.text(), "ab", "the retained 'b' branch is fully reachable");
2549 assert!(d.undo());
2551 assert!(d.select_redo_branch(1));
2552 assert!(d.redo());
2553 assert_eq!(d.text(), "aX");
2554 }
2555
2556 #[test]
2557 fn opt_in_undo_limit_bounds_reach_and_keeps_recent() {
2558 let mut d = doc("");
2562 d.set_undo_limit(Some(3));
2563 for (i, ch) in "abcde".chars().enumerate() {
2564 d.edit(vec![EditOp::insert(i as u32, ch.to_string())]).unwrap();
2565 }
2566 assert_eq!(d.text(), "abcde");
2567 assert_eq!(d.undo_depth(), 3, "only the last 3 units are retained");
2568 assert!(d.undo()); assert!(d.undo()); assert!(d.undo()); assert_eq!(d.text(), "ab");
2572 assert!(!d.undo(), "the pruned units ('a','b' inserts) are gone");
2573 assert!(!d.can_undo());
2574 assert!(d.redo());
2576 assert_eq!(d.text(), "abc");
2577 }
2578
2579 #[test]
2580 fn pruning_preserves_branches_within_the_window() {
2581 let mut d = doc("");
2584 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));
2590 assert_eq!(d.undo_depth(), 1);
2591 assert!(d.undo()); assert_eq!(d.text(), "a");
2593 assert_eq!(d.redo_branch_count(), 2, "both branches survived the rebuild");
2594 assert!(d.select_redo_branch(0));
2595 assert!(d.redo());
2596 assert_eq!(d.text(), "ab", "the retained branch is still reachable after a prune");
2597 }
2598
2599 #[test]
2600 fn typing_run_merges_into_one_undo_step() {
2601 let mut d = doc("");
2603 for (i, ch) in "hello".chars().enumerate() {
2604 d.edit_grouped(vec![EditOp::insert(i as u32, ch.to_string())], typ(OpClass::Type))
2605 .unwrap();
2606 }
2607 assert_eq!(d.text(), "hello");
2608 assert!(d.undo());
2609 assert_eq!(d.text(), "", "the whole typing run undoes as one unit");
2610 assert!(d.redo());
2611 assert_eq!(d.text(), "hello", "and redoes as one unit");
2612 }
2613
2614 #[test]
2615 fn fold_toggle_seals_undo_and_clears_the_expand_ladder() {
2616 let mut d = doc("m {\n word\n}");
2620 d.set_selections(SelectionSet::new(7)); d.type_char('x');
2622 d.expand_selection(); assert!(d.toggle_fold_opener(2));
2624 d.shrink_selection(); let head = d.selections().newest().head();
2626 let fm = crate::fold_map::FoldMap::new(d.folds(), d.brackets(), d.buffer());
2627 assert!(
2628 fm.display_position(d.buffer(), head, d.tab_size()).is_some(),
2629 "…so the caret cannot be restored into the collapsed fold"
2630 );
2631 d.type_char('y');
2633 assert!(d.undo());
2634 assert!(d.text().contains('x'), "undo reverted only the post-toggle typing");
2635 }
2636
2637 #[test]
2638 fn find_navigation_expands_a_collapsed_inline_fold() {
2639 let mut d = doc("x = [1, 2, 3]\nafter");
2644 assert!(d.toggle_fold_opener(4)); d.set_find_query(Some(FindQuery { text: "2".into(), case_sensitive: false, ..Default::default() }), 0);
2646 d.find_next(0).expect("the match exists");
2647 assert!(!d.folds().is_folded(4), "the inline fold expanded to show the match");
2648 }
2649
2650 #[test]
2651 fn editing_hidden_text_expands_only_the_enclosing_fold() {
2652 let text = "a {\n keep\n}\nb {\n body\n}\nc\n";
2657 let mut d = doc(text);
2658 let opens: Vec<u32> = text.match_indices('{').map(|(i, _)| i as u32).collect();
2659 let (keep, edited) = (opens[0], opens[1]);
2662 assert!(d.toggle_fold_opener(keep));
2663 assert!(d.toggle_fold_opener(edited));
2664 assert!(d.folds().is_folded(keep) && d.folds().is_folded(edited));
2665 let inside = text.find("body").unwrap() as u32 + 2;
2667 d.edit(vec![EditOp::insert(inside, "X")]).unwrap();
2668 assert!(!d.folds().is_folded(edited), "editing inside the second block expands it");
2669 assert!(d.folds().is_folded(keep), "the untouched first block stays folded");
2670 }
2671
2672 #[test]
2673 fn next_diagnostic_advances_past_a_shared_start_offset() {
2674 use crate::{Diagnostic, Severity};
2675 let mut d = doc("abcdef");
2678 let rev = d.revision();
2679 d.set_diagnostics(
2680 rev,
2681 vec![
2682 Diagnostic::new(1..3, Severity::Error, "a"),
2683 Diagnostic::new(1..5, Severity::Warning, "b"),
2684 ],
2685 );
2686 assert_eq!(d.next_diagnostic(true), Some(1..3));
2687 assert_eq!(d.next_diagnostic(true), Some(1..5), "the same-start sibling is reachable");
2688 assert_eq!(d.next_diagnostic(true), Some(1..3), "and the walk wraps");
2689 }
2690
2691 #[test]
2692 fn reveal_classes_and_no_op_verbs() {
2693 use crate::{Diagnostic, Severity};
2694 let mut d = doc("f( ab )");
2698 let seq0 = d.reveal_seq();
2699 d.next_diagnostic(true); assert_eq!(d.reveal_seq(), seq0, "a no-op F8 requests no reveal");
2701 d.set_selections(SelectionSet::new(3));
2702 d.jump_to_bracket(); assert!(d.reveal_seq() > seq0, "a real bracket hop requests a reveal");
2704 assert_eq!(d.reveal_mode(), RevealMode::Fit, "…of the Fit class");
2705 let rev = d.revision();
2706 d.set_diagnostics(rev, vec![Diagnostic::new(1..2, Severity::Error, "e")]);
2707 let seq1 = d.reveal_seq();
2708 d.next_diagnostic(true);
2709 assert!(d.reveal_seq() > seq1);
2710 assert_eq!(d.reveal_mode(), RevealMode::Center, "a diagnostic jump centers");
2711 }
2712
2713 #[test]
2714 fn ctrl_d_force_reveals_while_select_all_holds() {
2715 let mut d = doc("foo bar foo baz foo");
2718 d.set_selections(SelectionSet::new(0));
2719 d.add_next_occurrence(); d.add_next_occurrence(); assert_eq!(d.reveal_mode(), RevealMode::FitForce, "Ctrl+D force-reveals the new cursor");
2722 let mut e = doc("foo bar foo baz foo");
2723 e.set_selections(SelectionSet::new(0));
2724 e.select_all_occurrences();
2725 assert_eq!(e.reveal_mode(), RevealMode::Fit, "Ctrl+Shift+L holds if a cursor is visible");
2726 }
2727
2728 #[test]
2729 fn diagnostic_navigation_wraps_selects_and_reveals() {
2730 use crate::{Diagnostic, Severity};
2731 let mut d = doc("aa bb\ncc\ndd");
2732 let rev = d.revision();
2733 d.set_diagnostics(
2734 rev,
2735 vec![
2736 Diagnostic::new(3..5, Severity::Warning, "w"),
2737 Diagnostic::new(9..11, Severity::Error, "e"),
2738 ],
2739 );
2740 assert_eq!(d.next_diagnostic(true), Some(3..5));
2741 assert_eq!(d.next_diagnostic(true), Some(9..11));
2742 assert_eq!(d.next_diagnostic(true), Some(3..5), "wraps to the first");
2743 assert_eq!(d.next_diagnostic(false), Some(9..11), "prev wraps to the last");
2744 let s = d.selections().newest();
2745 assert_eq!((s.start(), s.end()), (9, 11), "the diagnostic's span is selected");
2746 let mut e = doc("x");
2748 e.set_selections(SelectionSet::new(1));
2749 assert!(e.next_diagnostic(true).is_none());
2750 assert_eq!(e.selections().newest().head(), 1);
2751 let mut f = doc("aa\nfn {\nbb\n}\ncc");
2753 assert!(f.toggle_fold_opener(6));
2754 let rev = f.revision();
2755 f.set_diagnostics(rev, vec![Diagnostic::new(8..10, Severity::Error, "hidden")]);
2756 assert_eq!(f.next_diagnostic(true), Some(8..10));
2757 assert!(!f.folds().is_folded(6), "the fold expanded to show the diagnostic");
2758 }
2759
2760 #[test]
2761 fn caret_word_occurrences_are_whole_word_only() {
2762 let mut d = doc("foo foobar foo\nfoo");
2763 d.set_selections(SelectionSet::new(1)); assert_eq!(
2765 d.caret_word_occurrences(0..u32::MAX),
2766 vec![0..3, 11..14, 15..18],
2767 "the foobar prefix is not a whole-word match"
2768 );
2769 assert_eq!(d.caret_word_occurrences(4..15), vec![11..14], "only in-window matches");
2771 let mut set = SelectionSet::new(0);
2773 set.set_single(Selection::from_anchor(SelectionId(0), 0, 3));
2774 d.set_selections(set);
2775 assert!(d.caret_word_occurrences(0..u32::MAX).is_empty());
2776 let mut w = doc("a b");
2778 w.set_selections(SelectionSet::new(2));
2779 assert!(w.caret_word_occurrences(0..u32::MAX).is_empty());
2780 }
2781
2782 #[test]
2783 fn find_navigation_expands_a_collapsed_fold_to_reveal_the_match() {
2784 let mut d = doc("aa\nfn {\nneedle\n}\nbb");
2787 let opener = 6; assert!(d.toggle_fold_opener(opener));
2789 d.set_find_query(Some(FindQuery { text: "needle".into(), case_sensitive: false, ..Default::default() }), 0);
2790 let m = d.find_next(0).expect("the match exists");
2791 assert!(!d.folds().is_folded(opener), "the fold expanded to reveal the match");
2792 let fm = crate::fold_map::FoldMap::new(d.folds(), d.brackets(), d.buffer());
2793 assert!(
2794 fm.display_position(d.buffer(), m.end, d.tab_size()).is_some(),
2795 "the match head renders"
2796 );
2797 let mut v = doc("aa\nfn {\nx\n}\nbb");
2799 assert!(v.toggle_fold_opener(6));
2800 v.set_find_query(Some(FindQuery { text: "fn".into(), case_sensitive: false, ..Default::default() }), 0);
2801 v.find_next(0).expect("match on the header");
2802 assert!(v.folds().is_folded(6), "a visible match keeps the fold collapsed");
2803 }
2804
2805 #[test]
2806 fn jump_to_bracket_crosses_and_returns() {
2807 let mut d = doc("f( ab )");
2810 d.set_selections(SelectionSet::new(2));
2811 d.jump_to_bracket();
2812 assert_eq!(d.selections().newest().head(), 7);
2813 d.jump_to_bracket();
2814 assert_eq!(d.selections().newest().head(), 2);
2815 d.set_selections(SelectionSet::new(4)); d.jump_to_bracket();
2818 assert_eq!(d.selections().newest().head(), 6, "lands before `)`");
2819 let mut e = doc("plain");
2821 e.set_selections(SelectionSet::new(3));
2822 e.jump_to_bracket();
2823 assert_eq!(e.selections().newest().head(), 3);
2824 }
2825
2826 #[test]
2827 fn expand_selection_climbs_the_bracket_ladder_and_shrinks_back() {
2828 let mut d = doc("m { a(bb) }");
2830 d.set_selections(SelectionSet::new(7)); let sel = |d: &Document| (d.selections().newest().start(), d.selections().newest().end());
2832 d.expand_selection();
2833 assert_eq!(sel(&d), (6, 8), "word first");
2834 d.expand_selection();
2835 assert_eq!(sel(&d), (5, 9), "word == () contents, so the pair incl. brackets");
2836 d.expand_selection();
2837 assert_eq!(sel(&d), (3, 10), "the brace contents");
2838 d.expand_selection();
2839 assert_eq!(sel(&d), (2, 11), "the brace pair incl. brackets");
2840 d.expand_selection();
2841 assert_eq!(sel(&d), (0, 11), "the whole document");
2842 d.expand_selection();
2843 assert_eq!(sel(&d), (0, 11), "fully expanded is a no-op");
2844 d.shrink_selection();
2846 assert_eq!(sel(&d), (2, 11));
2847 d.shrink_selection();
2848 assert_eq!(sel(&d), (3, 10));
2849 d.move_carets(Motion::Right, false);
2851 let caret = sel(&d);
2852 d.shrink_selection();
2853 assert_eq!(sel(&d), caret, "the ladder cleared on the caret move");
2854 }
2855
2856 #[test]
2857 fn add_caret_vertical_stacks_and_stops_at_edges() {
2858 let mut d = doc("aaa\nbbb\nccc");
2861 d.set_selections(SelectionSet::new(5)); d.add_caret_vertical(false); d.add_caret_vertical(true); assert_eq!(d.selections().len(), 3);
2865 d.type_char('X');
2866 assert_eq!(d.text(), "aXaa\nbXbb\ncXcc");
2867 let mut e = doc("aa\nbb");
2869 e.set_selections(SelectionSet::new(1));
2870 e.add_caret_vertical(false);
2871 assert_eq!(e.selections().len(), 1);
2872 }
2873
2874 #[test]
2875 fn add_caret_vertical_skips_a_collapsed_fold() {
2876 let mut d = doc("aa\nfn {\nbb\ncc\n}\ndd");
2877 assert!(d.toggle_fold_opener(6)); d.set_selections(SelectionSet::new(16)); d.add_caret_vertical(false);
2880 let rows: Vec<u32> =
2881 d.selections().all().iter().map(|s| d.buffer().offset_to_point(s.head()).row).collect();
2882 assert_eq!(rows, vec![1, 5], "the new caret lands on the fold header, not a hidden row");
2883 }
2884
2885 #[test]
2886 fn select_all_occurrences_takes_every_match_from_a_word_seed() {
2887 let mut d = doc("foo bar foo baz foo");
2890 d.set_selections(SelectionSet::new(1));
2891 d.select_all_occurrences();
2892 assert_eq!(d.selections().len(), 3);
2893 d.type_char('X');
2894 assert_eq!(d.text(), "X bar X baz X");
2895 let mut e = doc(" ");
2897 e.set_selections(SelectionSet::new(1));
2898 e.select_all_occurrences();
2899 assert_eq!(e.selections().len(), 1);
2900 assert!(e.selections().newest().is_empty());
2901 }
2902
2903 #[test]
2904 fn select_find_matches_turns_matches_into_selections() {
2905 let mut d = doc("foo bar foo baz foo");
2906 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
2907 d.find_next(0); assert!(d.select_find_matches());
2909 assert_eq!(d.selections().len(), 3);
2910 assert_eq!(d.selections().newest().start(), 0, "the ACTIVE match is newest");
2911 let mut e = doc("abc");
2913 assert!(!e.select_find_matches());
2914 assert_eq!(e.selections().len(), 1);
2915 }
2916
2917 #[test]
2918 fn replace_all_swaps_every_match_in_one_undo_step() {
2919 let original = "foo bar foo baz foo";
2922 let mut d = doc(original);
2923 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
2924 assert_eq!(d.replace_all("QUX", false), 3);
2925 assert_eq!(d.text(), "QUX bar QUX baz QUX");
2926 assert!(d.undo());
2927 assert_eq!(d.text(), original, "replace-all must undo as ONE step");
2928 assert!(!d.undo(), "…with no second step hiding behind it");
2929 assert_eq!(d.find_match_count(), 3);
2931 }
2932
2933 #[test]
2934 fn replace_all_is_not_capped_by_the_live_match_set() {
2935 let n = crate::find::FIND_MATCH_CAP + 500;
2939 let mut d = doc(&"a".repeat(n));
2940 d.set_find_query(Some(FindQuery { text: "a".into(), case_sensitive: false, ..Default::default() }), 0);
2941 assert_eq!(d.find_match_count(), crate::find::FIND_MATCH_CAP, "the live set caps");
2942 assert_eq!(d.replace_all("b", false), n, "replace-all must NOT cap");
2943 assert_eq!(d.text(), "b".repeat(n), "every match past the cap replaced too");
2944 }
2945
2946 #[test]
2947 fn replace_next_selects_before_it_replaces() {
2948 let mut d = doc("foo bar foo");
2949 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
2950 assert_eq!(d.replace_next("X", false, 0), Some(0..3));
2953 assert_eq!(d.text(), "foo bar foo", "the first press must not replace");
2954 assert_eq!(d.replace_next("X", false, 0), Some(6..9));
2958 assert_eq!(d.text(), "X bar foo");
2959 assert!(d.undo());
2961 assert_eq!(d.text(), "foo bar foo");
2962 }
2963
2964 #[test]
2965 fn find_scope_restricts_matches_and_replace_all() {
2966 let mut d = doc("foo foo | foo foo");
2967 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
2968 assert_eq!(d.find_match_count(), 4);
2969 d.set_find_scope(Some(0..7), 0);
2971 assert_eq!(d.find_match_count(), 2, "only the in-scope matches survive");
2972 assert_eq!(d.replace_all("X", false), 2, "replace-all must honor the scope");
2974 assert_eq!(d.text(), "X X | foo foo", "the out-of-scope matches are untouched");
2975 d.set_find_scope(None, 0);
2977 assert_eq!(d.find_match_count(), 2, "the two outside are findable again");
2978 }
2979
2980 #[test]
2981 fn the_find_scope_rides_edits_and_drops_when_it_collapses() {
2982 let mut d = doc("aaa foo bbb");
2983 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
2984 d.set_find_scope(Some(4..7), 0); assert_eq!(d.find_match_count(), 1);
2986 d.edit(vec![EditOp::insert(0, "XY")]).unwrap();
2988 assert_eq!(d.find_scope(), Some(6..9));
2989 assert_eq!(d.find_match_count(), 1, "the scope still covers the match");
2990 d.edit(vec![EditOp::delete(6..9)]).unwrap();
2993 assert_eq!(d.find_scope(), None, "a collapsed scope must clear");
2994 }
2995
2996 #[test]
2997 fn an_edit_outside_the_scope_creates_no_matches() {
2998 let mut d = doc("foo | ...");
2999 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
3000 d.set_find_scope(Some(0..3), 0);
3001 assert_eq!(d.find_match_count(), 1);
3002 d.edit(vec![EditOp::insert(6, "foo")]).unwrap();
3005 assert_eq!(d.find_match_count(), 1, "out-of-scope text must not match");
3006 }
3007
3008 #[test]
3014 fn scoped_repair_equals_a_fresh_scoped_scan_under_random_edits() {
3015 let mut seed = 0x5EED_u64;
3016 let mut rng = || {
3017 seed ^= seed << 13;
3018 seed ^= seed >> 7;
3019 seed ^= seed << 17;
3020 seed
3021 };
3022 let base = "foo bar foo baz\nfoo foo bar\nbarfoo foo\n".repeat(3);
3023 let n = base.len() as u32;
3024 for trial in 0..300 {
3025 let mut d = doc(&base);
3026 let a = (rng() as u32) % n;
3027 let b = (rng() as u32) % n;
3028 let (lo, hi) = (a.min(b), a.max(b));
3029 let lo = d.buffer().clip_offset(lo, Bias::Right);
3030 let hi = d.buffer().clip_offset(hi, Bias::Left);
3031 if lo >= hi {
3032 continue;
3033 }
3034 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
3035 d.set_find_scope(Some(lo..hi), 0);
3036 for _ in 0..4 {
3037 let at = (rng() as u32) % (d.buffer().len() + 1);
3038 let at = d.buffer().clip_offset(at, Bias::Left);
3039 let ins = ["foo", "x", "", "\nfoo "][(rng() % 4) as usize];
3040 d.edit(vec![EditOp::insert(at, ins)]).unwrap();
3041 }
3042 let incremental: Vec<_> = d.find_matches_in(0..u32::MAX).map(|(r, _)| r).collect();
3043 let scope = d.find_scope();
3047 let q = d.find_query().cloned().unwrap();
3048 d.set_find_query(None, 0);
3049 d.set_find_query(Some(q), 0);
3050 let fresh: Vec<_> = d.find_matches_in(0..u32::MAX).map(|(r, _)| r).collect();
3051 assert_eq!(incremental, fresh, "trial {trial}, scope {scope:?}");
3052 if let Some(s) = scope {
3054 assert!(
3055 incremental.iter().all(|m| m.start >= s.start && m.end <= s.end),
3056 "trial {trial}: a match escaped the scope {s:?}: {incremental:?}"
3057 );
3058 }
3059 }
3060 }
3061
3062 #[test]
3072 fn line_scoped_repair_equals_a_fresh_scan_under_random_edits() {
3073 let mut seed = 0x00C0_FFEE_u64;
3074 let mut rng = || {
3075 seed ^= seed << 13;
3076 seed ^= seed >> 7;
3077 seed ^= seed << 17;
3078 seed
3079 };
3080 let base = "foo bar foo baz\nfoo foo bar\nbarfoo foo\n\nqux foofoo\n".repeat(3);
3081 let queries = [
3082 FindQuery { text: "foo".into(), whole_word: true, ..Default::default() },
3083 FindQuery { text: "f.o".into(), regex: true, ..Default::default() },
3084 FindQuery { text: r"\w+o".into(), regex: true, ..Default::default() },
3085 FindQuery { text: ".*".into(), regex: true, ..Default::default() },
3087 FindQuery { text: r"\bba\w+".into(), regex: true, ..Default::default() },
3088 FindQuery {
3089 text: "foo|bar".into(),
3090 whole_word: true,
3091 regex: true,
3092 ..Default::default()
3093 },
3094 ];
3095 for (qi, q) in queries.iter().enumerate() {
3096 for trial in 0..40 {
3097 let mut d = doc(&base);
3098 d.set_find_query(Some(q.clone()), 0);
3099 for _ in 0..5 {
3100 let len = d.buffer().len();
3101 if rng() % 3 == 0 && len > 4 {
3104 let a = d.buffer().clip_offset((rng() as u32) % len, Bias::Left);
3105 let b = d
3106 .buffer()
3107 .clip_offset((a + 1 + (rng() as u32) % 6).min(len), Bias::Right);
3108 if a < b {
3109 d.edit(vec![EditOp::delete(a..b)]).unwrap();
3110 }
3111 } else {
3112 let at = d.buffer().clip_offset((rng() as u32) % (len + 1), Bias::Left);
3113 let ins = ["foo", "x", "\n", " ", "\nfoo bar"][(rng() % 5) as usize];
3114 d.edit(vec![EditOp::insert(at, ins)]).unwrap();
3115 }
3116 }
3117 let incremental: Vec<_> = d.find_matches_in(0..u32::MAX).map(|(r, _)| r).collect();
3118 d.set_find_query(None, 0);
3120 d.set_find_query(Some(q.clone()), 0);
3121 let fresh: Vec<_> = d.find_matches_in(0..u32::MAX).map(|(r, _)| r).collect();
3122 assert_eq!(incremental, fresh, "query {qi} ({:?}), trial {trial}", q.text);
3123 }
3124 }
3125 }
3126
3127 #[test]
3128 fn regex_replace_all_is_one_undo_step() {
3129 let original = "a1 b22 c333";
3130 let mut d = doc(original);
3131 d.set_find_query(
3132 Some(FindQuery { text: r"\d+".into(), regex: true, ..Default::default() }),
3133 0,
3134 );
3135 assert_eq!(d.find_match_count(), 3);
3136 assert_eq!(d.replace_all("#", false), 3);
3137 assert_eq!(d.text(), "a# b# c#");
3138 assert!(d.undo());
3139 assert_eq!(d.text(), original, "a regex replace-all undoes as ONE step");
3140 }
3141
3142 #[test]
3143 fn a_regex_replacement_expands_capture_groups() {
3144 let mut d = doc("fn one()\nfn two()");
3146 d.set_find_query(
3147 Some(FindQuery { text: r"fn (\w+)\(\)".into(), regex: true, ..Default::default() }),
3148 0,
3149 );
3150 assert_eq!(d.replace_all("let $1 = 1;", false), 2);
3151 assert_eq!(d.text(), "let one = 1;\nlet two = 1;");
3152 assert!(d.undo());
3153 assert_eq!(d.text(), "fn one()\nfn two()", "still ONE undo step");
3154
3155 let mut e = doc("aaa");
3158 e.set_find_query(Some(FindQuery { text: "aaa".into(), ..Default::default() }), 0);
3159 assert_eq!(e.replace_all("$1", false), 1);
3160 assert_eq!(e.text(), "$1");
3161 }
3162
3163 #[test]
3164 fn replace_next_expands_captures_for_the_active_match_only() {
3165 let mut d = doc("fn one()\nfn two()");
3166 d.set_find_query(
3167 Some(FindQuery { text: r"fn (\w+)\(\)".into(), regex: true, ..Default::default() }),
3168 0,
3169 );
3170 d.replace_next("x", false, 0); d.replace_next("[$1]", false, 0); assert_eq!(d.text(), "[one]\nfn two()");
3173 }
3174
3175 #[test]
3176 fn preserve_case_recases_replacements_to_their_match() {
3177 let mut d = doc("FOO Foo foo fOo");
3180 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
3181 assert_eq!(d.replace_all("bar", true), 4);
3182 assert_eq!(d.text(), "BAR Bar bar bar");
3184
3185 let mut e = doc("FOO Foo foo");
3187 e.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() }), 0);
3188 assert_eq!(e.replace_all("bar", false), 3);
3189 assert_eq!(e.text(), "bar bar bar");
3190 }
3191
3192 #[test]
3193 fn preserve_case_applies_after_regex_expansion() {
3194 let mut d = doc("GETVALUE getvalue");
3197 d.set_find_query(
3198 Some(FindQuery { text: "(get)(value)".into(), regex: true, ..Default::default() }),
3199 0,
3200 );
3201 assert_eq!(d.replace_all("${2}_${1}", true), 2);
3204 assert_eq!(d.text(), "VALUE_GET value_get");
3206 }
3207
3208 #[test]
3209 fn an_unfinished_regex_matches_nothing_and_reports_why() {
3210 let mut d = doc("foo (bar)");
3211 d.set_find_query(
3212 Some(FindQuery { text: "(".into(), regex: true, ..Default::default() }),
3213 0,
3214 );
3215 assert_eq!(d.find_match_count(), 0, "an unfinished pattern matches nothing");
3216 assert!(d.find_pattern_error().is_some(), "…and says why");
3217 d.edit(vec![EditOp::insert(0, "x")]).unwrap();
3219 assert_eq!(d.find_match_count(), 0);
3220 d.set_find_query(
3222 Some(FindQuery { text: r"\(bar\)".into(), regex: true, ..Default::default() }),
3223 0,
3224 );
3225 assert!(d.find_pattern_error().is_none());
3226 assert_eq!(d.find_match_count(), 1);
3227 }
3228
3229 #[test]
3230 fn replace_all_without_a_match_commits_nothing() {
3231 let mut d = doc("foo");
3232 assert_eq!(d.replace_all("x", false), 0, "no query ⇒ no-op");
3233 d.set_find_query(Some(FindQuery { text: "zzz".into(), case_sensitive: false, ..Default::default() }), 0);
3234 assert_eq!(d.replace_all("x", false), 0, "no matches ⇒ no-op");
3235 assert_eq!(d.text(), "foo");
3236 assert!(!d.is_dirty(), "a no-op replace must not open an undo step");
3237 assert!(!d.undo());
3238 }
3239
3240 #[test]
3241 fn caret_moves_seal_the_typing_group() {
3242 let mut d = doc("");
3245 d.type_char('f');
3246 d.type_char('o');
3247 d.move_carets(Motion::Left, false); d.type_char('x'); assert!(d.undo());
3250 assert_eq!(d.text(), "fo", "undo reverts only the run typed after the move");
3251
3252 let mut c = doc("");
3254 c.type_char('a');
3255 c.type_char('b');
3256 c.set_selections(SelectionSet::new(0));
3257 c.type_char('z'); assert!(c.undo());
3259 assert_eq!(c.text(), "ab", "the click sealed the run before it");
3260
3261 let mut b = doc("one\ntwo");
3264 b.set_selections(SelectionSet::new(0));
3265 b.type_char('z'); b.column_drag((0, 2), (1, 2));
3267 b.type_char('q');
3268 assert!(b.undo());
3269 assert_eq!(b.text(), "zone\ntwo", "the box gesture sealed the run before it");
3270 }
3271
3272 #[test]
3277 fn line_duplication_drops_autoclose_provenance() {
3278 let ac_count = |d: &Document| d.autoclose_pair_count();
3280 let mut d = doc("x");
3281 d.move_carets(Motion::Right, false); d.type_char('('); assert_eq!(d.text(), "x()");
3284 assert_eq!(ac_count(&d), 1, "typing '(' arms one AutoClosePair decoration");
3285 d.copy_line(false);
3289 assert_eq!(d.text(), "x()\nx()");
3290 assert_eq!(
3291 ac_count(&d),
3292 0,
3293 "copy_line must clear the provenance decoration, not orphan it in the store",
3294 );
3295 }
3296
3297 #[test]
3303 fn type_bracket_then_far_diagnostics_pair_survives() {
3304 use crate::{Diagnostic, Severity};
3305 let mut d = doc("hello\nworld\n");
3306 let end = d.text().len() as u32; d.set_selections(SelectionSet::new(end));
3308 d.type_char('('); assert_eq!(d.autoclose_pair_count(), 1);
3310 d.set_diagnostics(
3312 d.revision(),
3313 vec![
3314 Diagnostic::new(0..1, Severity::Error, "e0"),
3315 Diagnostic::new(2..3, Severity::Warning, "w0"),
3316 ],
3317 );
3318 d.type_char('x');
3321 assert!(d.text().ends_with("(x)"));
3322 assert_eq!(d.autoclose_pair_count(), 1, "far diagnostics don't disturb the pair");
3323 assert_eq!(d.diagnostics_in(0..u32::MAX).count(), 2, "the diagnostics ride on");
3325 d.type_char(')');
3327 assert!(d.text().ends_with("(x)"), "overtype consumes the tracked close, no doubled )");
3328 assert_eq!(d.autoclose_pair_count(), 0, "overtype consumed the pair");
3329 }
3330
3331 #[test]
3342 fn autoclose_store_moves_identically_to_unified_store() {
3343 use crate::decorations::{DecorationKind, Stickiness};
3344 use crate::{Diagnostic, Severity};
3345 let scenario = |text: &str, carets: &[u32], steps: usize| {
3349 let mut d = doc(text);
3350 d.set_selections(SelectionSet::from_offsets(carets));
3351 d.type_char('('); let pairs = d.autoclose_ranges();
3353 assert_eq!(pairs.len(), carets.len(), "one pair per caret armed");
3354 for r in &pairs {
3357 d.decorations_mut().add_decoration(
3358 r.clone(),
3359 DecorationKind::AutoClosePair,
3360 Stickiness::AlwaysGrows,
3361 );
3362 }
3363 d.set_diagnostics(d.revision(), vec![Diagnostic::new(0..1, Severity::Warning, "d")]);
3364 let unified = |d: &Document| -> Vec<Range<u32>> {
3365 d.decorations()
3366 .iter()
3367 .filter(|r| matches!(r.kind, DecorationKind::AutoClosePair))
3368 .map(|r| r.range.clone())
3369 .collect()
3370 };
3371 assert_eq!(d.autoclose_ranges(), unified(&d), "shadow starts equal");
3372 for step in 0..steps {
3373 d.type_char('z'); assert_eq!(
3375 d.autoclose_ranges(),
3376 unified(&d),
3377 "own store diverged from the unified store at step {step}",
3378 );
3379 }
3380 };
3381 scenario("aa\nbb\ncc\ndd\nee\n", &[2, 5, 8, 11, 14], 8);
3383 scenario("solo line here\n", &[14], 10);
3385 }
3386
3387 #[test]
3388 fn different_classes_do_not_merge() {
3389 let mut d = doc("ab");
3390 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");
3393 assert!(d.undo()); assert_eq!(d.text(), "abc");
3395 assert!(d.undo()); assert_eq!(d.text(), "ab");
3397 }
3398
3399 #[test]
3400 fn dirty_tracking_survives_undo_and_save() {
3401 let mut d = doc("x");
3402 assert!(!d.is_dirty());
3403 d.edit(vec![EditOp::insert(1, "y")]).unwrap(); assert!(d.is_dirty());
3405 d.undo(); assert!(!d.is_dirty(), "undo back to the save point reads clean");
3407 d.redo(); assert!(d.is_dirty());
3409 d.mark_saved();
3410 assert!(!d.is_dirty());
3411 }
3412
3413 #[test]
3414 fn ctrl_d_selects_word_then_adds_occurrences_then_stops() {
3415 let mut d = doc("foo bar foo baz foo"); d.add_next_occurrence();
3418 assert_eq!(d.selections().len(), 1);
3419 let s = d.selections().newest();
3420 assert_eq!((s.start(), s.end()), (0, 3));
3421 d.add_next_occurrence();
3423 assert_eq!(d.selections().len(), 2);
3424 assert_eq!((d.selections().newest().start(), d.selections().newest().end()), (8, 11));
3425 d.add_next_occurrence();
3426 assert_eq!(d.selections().len(), 3);
3427 assert_eq!(d.selections().newest().start(), 16);
3428 d.add_next_occurrence();
3430 assert_eq!(d.selections().len(), 3, "no untaken occurrence remains");
3431 }
3432
3433 #[test]
3434 fn windowed_next_occurrence_equals_whole_text_scan() {
3435 let mut state: u64 = 0x243F_6A88_85A3_08D3;
3440 let mut next = |n: u32| {
3441 state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
3442 ((state >> 33) as u32) % n
3443 };
3444 let needles = ["ab", "ba", "abc", "cab"];
3446 for _ in 0..300 {
3447 let n = 12 + next(60); let text: String = (0..n)
3449 .map(|_| b"abc "[next(4) as usize] as char)
3450 .collect();
3451 let d = doc(&text);
3452 let needle = needles[next(needles.len() as u32) as usize];
3453 let nlen = needle.len() as u32;
3454 let all: Vec<(u32, u32)> = text
3456 .match_indices(needle)
3457 .map(|(i, _)| (i as u32, i as u32 + nlen))
3458 .collect();
3459 let taken: Vec<(u32, u32)> = all.iter().copied().filter(|_| next(2) == 0).collect();
3460 let is_taken = |s: u32| taken.iter().any(|&(st, en)| st == s && en == s + nlen);
3461 let from = next(n + 1);
3462 let got = scan_from(d.buffer(), needle, from, d.buffer().len(), &is_taken)
3463 .or_else(|| scan_from(d.buffer(), needle, 0, from, &is_taken));
3464 let want = find_next_occurrence(&text, needle, from, &taken);
3465 assert_eq!(got, want, "text {text:?} needle {needle:?} from {from} taken {taken:?}");
3466 }
3467 }
3468
3469 #[test]
3470 fn column_select_grows_widens_and_shrinks_back() {
3471 use crate::movement::ColumnDir::{Down, Left, Right, Up};
3472 let mut d = doc("abcd\nabcd\nabcd");
3474 d.set_selections(SelectionSet::new(1));
3475 d.column_select(Down);
3477 d.column_select(Down);
3478 assert_eq!(d.selections().len(), 3);
3479 assert!(d.selections().all().iter().all(|s| s.is_empty() && s.start() % 5 == 1));
3480 d.column_select(Right);
3482 d.column_select(Right);
3483 assert_eq!(d.selections().len(), 3);
3484 assert!(d.selections().all().iter().all(|s| s.end() - s.start() == 2));
3485 d.column_select(Up);
3487 d.column_select(Up);
3488 assert_eq!(d.selections().len(), 1, "shrinks back toward the anchor");
3489 d.column_select(Left);
3491 d.column_select(Left);
3492 assert_eq!(d.selections().len(), 1);
3493 assert!(d.selections().newest().is_empty());
3494 }
3495
3496 #[test]
3497 fn column_select_clamps_each_row_to_its_length() {
3498 use crate::movement::ColumnDir::{Down, Right};
3499 let mut d = doc("abcdef\nab"); d.set_selections(SelectionSet::new(0)); d.column_select(Down); for _ in 0..4 {
3504 d.column_select(Right); }
3506 let rows = d.selections().all();
3507 assert_eq!(rows.len(), 2);
3508 assert_eq!((rows[0].start(), rows[0].end()), (0, 4)); assert_eq!((rows[1].start(), rows[1].end()), (7, 9));
3511 }
3512
3513 #[test]
3514 fn column_drag_builds_a_box_from_two_corners() {
3515 let mut d = doc("abcd\nabcd\nabcd");
3516 d.column_drag((0, 1), (2, 3)); assert_eq!(d.selections().len(), 3);
3518 assert!(d.selections().all().iter().all(|s| s.end() - s.start() == 2));
3519 let mut r = doc("abcdef\nab"); r.column_drag((0, 0), (1, 5)); let rows = r.selections().all();
3523 assert_eq!((rows[0].start(), rows[0].end()), (0, 5)); assert_eq!((rows[1].start(), rows[1].end()), (7, 9)); }
3526
3527 #[test]
3528 fn column_box_is_display_cells_across_tabs() {
3529 let mut d = doc("\tabcd\nwxyz"); d.column_drag((0, 4), (1, 6));
3534 let rows = d.selections().all();
3535 assert_eq!(rows.len(), 2);
3536 assert_eq!((rows[0].start(), rows[0].end()), (1, 3), "cells 4..6 are bytes 1..3 after the tab");
3537 assert_eq!((rows[1].start(), rows[1].end()), (10, 10), "row 1 ends at cell 4; both corners clamp");
3538 }
3539
3540 #[test]
3541 fn column_box_resolves_through_a_collapsed_inline_fold() {
3542 let mut d = doc("f([a, b]) x");
3546 assert!(d.toggle_fold_opener(2)); d.column_drag((0, 6), (0, 9));
3548 let s = d.selections().newest();
3549 assert_eq!((s.start(), s.end()), (7, 10), "cells 6..9 are `]) `, one left of the byte columns");
3550 }
3551
3552 #[test]
3553 fn column_box_spans_visible_rows_only() {
3554 let mut d = doc("aa\nfn {\nbb\ncc\n}\ndd");
3557 assert!(d.toggle_fold_opener(6)); d.column_drag((0, 0), (5, 1));
3559 let rows: Vec<u32> =
3560 d.selections().all().iter().map(|s| d.buffer().offset_to_point(s.start()).row).collect();
3561 assert_eq!(rows, vec![0, 1, 5], "one selection per VISIBLE row");
3562 }
3563
3564 #[test]
3565 fn column_select_steps_display_rows_over_a_fold() {
3566 use crate::movement::ColumnDir::Up;
3567 let mut d = doc("aa\nfn {\nbb\ncc\n}\ndd");
3570 assert!(d.toggle_fold_opener(6));
3571 d.set_selections(SelectionSet::new(16)); d.column_select(Up);
3573 let rows: Vec<u32> =
3574 d.selections().all().iter().map(|s| d.buffer().offset_to_point(s.start()).row).collect();
3575 assert_eq!(rows, vec![1, 5], "the step lands on the fold header, skipping hidden rows");
3576 }
3577
3578 #[test]
3579 fn column_select_anchors_at_the_display_cell_after_a_tab() {
3580 use crate::movement::ColumnDir::Down;
3581 let mut d = doc("\tab\nwwwwww"); d.set_selections(SelectionSet::new(1)); d.column_select(Down);
3587 assert_eq!(d.selections().newest().head(), 8, "cell 4 on row 1 is byte 4 (offset 8)");
3588 }
3589
3590 #[test]
3591 fn any_action_exits_column_mode() {
3592 use crate::movement::ColumnDir::Down;
3593 let mut d = doc("abcd\nabcd\nabcd");
3594 d.set_selections(SelectionSet::new(0));
3595 d.column_select(Down); assert_eq!(d.selections().len(), 2);
3597 d.move_carets(Motion::Right, false); d.column_select(Down);
3601 assert_eq!(d.selections().len(), 2, "re-anchored, not continuing the old box");
3602 }
3603
3604 fn range(d: &Document) -> (u32, u32) {
3605 (d.selections().newest().start(), d.selections().newest().end())
3606 }
3607
3608 #[test]
3609 fn drag_select_word_granularity_keeps_the_origin_word() {
3610 let mut d = doc("foo bar baz\nqux"); d.drag_select(Granularity::Word, 5, 5);
3613 assert_eq!(range(&d), (4, 7));
3614 d.drag_select(Granularity::Word, 5, 9);
3616 assert_eq!(range(&d), (4, 11));
3617 d.drag_select(Granularity::Word, 5, 1);
3619 assert_eq!(range(&d), (0, 7));
3620 let mut w = doc("a b");
3622 w.drag_select(Granularity::Word, 2, 2);
3623 assert!(w.selections().newest().is_empty());
3624 }
3625
3626 #[test]
3627 fn drag_select_line_and_char_granularity() {
3628 let mut d = doc("aa\nbb\ncc"); d.drag_select(Granularity::Line, 1, 1); assert_eq!(range(&d), (0, 3));
3631 d.drag_select(Granularity::Line, 1, 4); assert_eq!(range(&d), (0, 6));
3633 d.drag_select(Granularity::Line, 7, 7); assert_eq!(range(&d), (6, 8));
3635 d.drag_select(Granularity::Char, 2, 8); assert_eq!(range(&d), (2, 8));
3637 }
3638
3639 fn head_row(d: &Document) -> u32 {
3640 d.buffer().offset_to_point(d.selections().newest().head()).row
3641 }
3642
3643 #[test]
3644 fn move_line_swaps_with_neighbour_and_rides() {
3645 let mut d = doc("aaa\nbbb\nccc");
3646 d.set_selections(SelectionSet::new(4)); d.move_line(true); assert_eq!(d.text(), "aaa\nccc\nbbb");
3649 assert_eq!(head_row(&d), 2); d.move_line(false); assert_eq!(d.text(), "aaa\nbbb\nccc");
3652 assert_eq!(head_row(&d), 1);
3653 }
3654
3655 #[test]
3656 fn move_line_is_a_noop_at_the_edges() {
3657 let mut d = doc("aaa\nbbb");
3658 d.set_selections(SelectionSet::new(0));
3659 d.move_line(false); assert_eq!(d.text(), "aaa\nbbb");
3661 d.set_selections(SelectionSet::new(4)); d.move_line(true); assert_eq!(d.text(), "aaa\nbbb");
3664 }
3665
3666 #[test]
3667 fn move_line_down_respects_the_trailing_empty_line() {
3668 let mut d = doc("aaa\nbbb\n"); d.set_selections(SelectionSet::new(4)); d.move_line(true); assert_eq!(d.text(), "aaa\nbbb\n");
3672 }
3673
3674 #[test]
3675 fn copy_line_duplicates_below_and_above() {
3676 let mut d = doc("aaa\nbbb");
3677 d.set_selections(SelectionSet::new(0));
3678 d.copy_line(true); assert_eq!(d.text(), "aaa\naaa\nbbb");
3680 assert_eq!(head_row(&d), 1);
3681 let mut d2 = doc("aaa\nbbb");
3682 d2.set_selections(SelectionSet::new(0));
3683 d2.copy_line(false); assert_eq!(d2.text(), "aaa\naaa\nbbb");
3685 assert_eq!(head_row(&d2), 0);
3686 }
3687
3688 #[test]
3689 fn copy_line_duplicates_the_final_line() {
3690 let mut d = doc("aaa\nbbb"); d.set_selections(SelectionSet::new(4)); d.copy_line(true);
3693 assert_eq!(d.text(), "aaa\nbbb\nbbb");
3694 assert_eq!(head_row(&d), 2);
3695 }
3696
3697 #[test]
3698 fn add_caret_adds_a_second_cursor() {
3699 let mut d = doc("abcdef");
3700 assert_eq!(d.selections().len(), 1);
3701 d.add_caret(3);
3702 assert_eq!(d.selections().len(), 2);
3703 }
3704
3705 #[test]
3706 fn collapse_returns_to_the_primary_caret() {
3707 let mut d = doc("foo foo foo");
3708 d.add_next_occurrence(); d.add_next_occurrence(); assert!(d.selections().len() >= 2);
3711 d.collapse_selections();
3712 assert_eq!(d.selections().len(), 1);
3713 assert!(d.selections().newest().is_empty());
3714 }
3715
3716 #[test]
3717 fn find_next_occurrence_scans_forward_then_wraps() {
3718 assert_eq!(super::find_next_occurrence("aXaYa", "a", 1, &[(0, 1)]), Some((2, 3)));
3720 assert_eq!(super::find_next_occurrence("aXaYa", "a", 5, &[(2, 3), (4, 5)]), Some((0, 1)));
3722 assert_eq!(super::find_next_occurrence("aa", "a", 0, &[(0, 1), (1, 2)]), None);
3724 }
3725
3726 #[test]
3727 fn dirty_after_undo_then_divergent_edit() {
3728 let mut d = doc("abc");
3731 d.edit(vec![EditOp::delete(2..3)]).unwrap(); d.mark_saved();
3733 d.undo(); assert!(d.is_dirty(), "undone away from the save point → dirty");
3735 d.edit(vec![EditOp::delete(0..1)]).unwrap(); assert!(d.is_dirty(), "divergent edit is still dirty, never falsely clean");
3737 }
3738
3739 #[test]
3740 fn highlight_cache_splice_tracks_line_count_across_edits() {
3741 const G: &str = "%YAML 1.2\n---\nname: T\nscope: source.t\ncontexts:\n main:\n - match: '\\w+'\n scope: keyword.t\n";
3743 const TH: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
3744<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3745<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>"#;
3746 let mut d = doc("ab\ncd");
3747 d.set_syntax(
3748 crate::SyntaxDef::from_sublime_syntax(G).unwrap(),
3749 crate::TokenTheme::from_tm_theme(TH).unwrap(),
3750 );
3751 d.tokenize_highlight(d.buffer().line_count());
3752 assert!(d.highlight_line_spans(1).is_some());
3753 d.set_selections(SelectionSet::new(2)); d.enter();
3756 d.tokenize_highlight(d.buffer().line_count());
3757 assert_eq!(d.buffer().line_count(), 3);
3758 assert!(d.highlight_line_spans(2).is_some(), "the new last line is tokenized");
3759 d.backspace();
3761 d.tokenize_highlight(d.buffer().line_count());
3762 assert_eq!(d.buffer().line_count(), 2);
3763 assert!(d.highlight_line_spans(1).is_some());
3764 assert!(d.highlight_line_spans(2).is_none(), "no spans past the buffer");
3765 }
3766
3767 #[test]
3768 fn multi_op_transaction_highlight_equals_a_fresh_document() {
3769 const G: &str = "%YAML 1.2\n---\nname: T\nscope: source.t\ncontexts:\n main:\n - match: '\\w+'\n scope: keyword.t\n";
3776 const TH: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
3777<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3778<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>"#;
3779 let make = |text: &str| -> Document {
3780 let mut d = doc(text);
3781 d.set_syntax(
3782 crate::SyntaxDef::from_sublime_syntax(G).unwrap(),
3783 crate::TokenTheme::from_tm_theme(TH).unwrap(),
3784 );
3785 d.tokenize_highlight(u32::MAX);
3786 d
3787 };
3788 let mut d = make("aa bb cc\ndd ee ff\ngg hh ii\njj kk ll\nmm nn oo");
3791 d.edit_grouped(
3795 vec![
3796 EditOp::new(0..2, "XYZ"), EditOp::new(9..11, "Q"), EditOp::new(15..17, "RR"), EditOp::insert(27, "NEW\n"), ],
3801 typ(OpClass::Type),
3802 )
3803 .unwrap();
3804 d.tokenize_highlight(u32::MAX);
3805
3806 let text = d.text();
3807 let f = make(&text);
3808 assert_eq!(d.buffer().line_count(), f.buffer().line_count(), "line count");
3809 for r in 0..d.buffer().line_count() {
3810 assert_eq!(
3811 d.highlight_line_spans(r),
3812 f.highlight_line_spans(r),
3813 "row {r} live (per-edit invalidation) vs fresh"
3814 );
3815 }
3816 }
3817
3818 #[test]
3823 fn set_syntax_preserves_the_highlight_window_aim() {
3824 const G: &str = "%YAML 1.2\n---\nname: T\nscope: source.t\ncontexts:\n main:\n - match: '\\w+'\n scope: keyword.t\n";
3825 const TH: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
3826<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3827<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>"#;
3828 let syntax = || crate::SyntaxDef::from_sublime_syntax(G).unwrap();
3829 let theme = || crate::TokenTheme::from_tm_theme(TH).unwrap();
3830 let mut d = doc(&"word\n".repeat(3_000));
3831 d.set_syntax(syntax(), theme());
3832 d.set_highlight_window(2_000..2_040);
3834 d.set_syntax(syntax(), theme());
3836 while d.highlight_frontier().is_some() {
3837 d.tokenize_highlight(u32::MAX);
3838 }
3839 assert!(
3840 d.highlight_line_spans(2_020).is_some(),
3841 "the swapped cache must still retain the viewport rows"
3842 );
3843 }
3844
3845 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";
3850 const HL_TH: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
3851<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3852<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>"#;
3853
3854 fn doc_with_syntax(n: usize) -> Document {
3855 let mut d = doc(&"kw word\n".repeat(n));
3856 d.set_syntax(
3857 crate::SyntaxDef::from_sublime_syntax(HL_G).unwrap(),
3858 crate::TokenTheme::from_tm_theme(HL_TH).unwrap(),
3859 );
3860 d
3861 }
3862
3863 #[test]
3864 fn absorb_highlight_drops_a_stale_revision() {
3865 let mut d = doc_with_syntax(2_000);
3866 let engine = d.highlight_engine().unwrap();
3867 let snap = d.snapshot();
3868 let stale_rev = d.revision();
3869 let seg = crate::tokenize_segment(&engine, &snap, 0..2_001, crate::SegmentStart::Fresh, None, None);
3870 d.set_selections(SelectionSet::new(0));
3872 d.type_char('x');
3873 assert_ne!(d.revision(), stale_rev);
3874 assert!(!d.absorb_highlight(stale_rev, seg, true), "stale segment must be dropped");
3876 assert_eq!(d.highlight_frontier(), Some(0), "the frontier is untouched by a dropped absorb");
3877 }
3878
3879 #[test]
3880 fn absorb_highlight_verified_clears_the_frontier() {
3881 let mut d = doc_with_syntax(5_000);
3882 let engine = d.highlight_engine().unwrap();
3883 let snap = d.snapshot();
3884 let rev = d.revision();
3885 let n = d.buffer().line_count();
3886 let seg =
3890 crate::tokenize_segment(&engine, &snap, 0..n, crate::SegmentStart::Fresh, Some(0..n), None);
3891 assert!(d.absorb_highlight(rev, seg, true));
3892 assert_eq!(d.highlight_frontier(), None, "verified absorb clears every dirty row");
3893 }
3894
3895 #[test]
3896 fn absorb_highlight_speculative_shows_spans_but_keeps_dirt() {
3897 let mut d = doc_with_syntax(2_000);
3898 d.set_highlight_window(900..940);
3899 let engine = d.highlight_engine().unwrap();
3900 let snap = d.snapshot();
3901 let rev = d.revision();
3902 let seg = crate::tokenize_segment(
3904 &engine,
3905 &snap,
3906 772..940,
3907 crate::SegmentStart::Fresh,
3908 Some(900..940),
3909 None,
3910 );
3911 assert!(d.absorb_highlight(rev, seg, false));
3912 assert!(d.highlight_line_spans(920).is_some(), "speculative spans are visible");
3914 assert_eq!(d.highlight_frontier(), Some(0), "speculation keeps the frontier");
3916 while d.highlight_frontier().is_some() {
3918 d.tokenize_highlight(u32::MAX);
3919 }
3920 assert!(d.highlight_line_spans(920).is_some());
3921 }
3922
3923 #[test]
3924 fn diagnostics_install_then_ride_a_forward_edit() {
3925 use crate::{Diagnostic, DiagnosticsOutcome, Severity};
3926 let mut d = doc("let x = 1;");
3927 let rev = d.revision();
3928 let out = d.set_diagnostics(rev, vec![Diagnostic::new(4..5, Severity::Error, "unused")]);
3929 assert!(matches!(out, DiagnosticsOutcome::Applied { count: 1 }));
3930 let got: Vec<_> = d.diagnostics_in(0..100).collect();
3931 assert_eq!((got.len(), got[0].0.clone(), got[0].1), (1, 4..5, Severity::Error));
3932 d.edit(vec![EditOp::insert(0, "abcd")]).unwrap();
3934 assert_eq!(d.diagnostics_in(0..100).next().unwrap().0, 8..9, "rode via apply_patch");
3935 }
3936
3937 #[test]
3938 fn stale_diagnostic_set_is_dropped_and_the_prior_kept() {
3939 use crate::{Diagnostic, DiagnosticsOutcome, Severity};
3940 let mut d = doc("abcdef");
3941 let rev = d.revision();
3942 d.set_diagnostics(rev, vec![Diagnostic::new(0..1, Severity::Warning, "w")]);
3943 d.edit(vec![EditOp::insert(0, "z")]).unwrap(); let out = d.set_diagnostics(rev, vec![Diagnostic::new(3..4, Severity::Error, "e")]);
3945 assert!(matches!(out, DiagnosticsOutcome::Stale { .. }), "old-revision set dropped");
3946 let got: Vec<_> = d.diagnostics_in(0..100).collect();
3947 assert_eq!((got.len(), got[0].0.clone(), got[0].1), (1, 1..2, Severity::Warning));
3948 }
3949
3950 #[test]
3951 fn diagnostic_spans_clip_to_the_buffer_length() {
3952 use crate::{Diagnostic, Severity};
3953 let mut d = doc("abc"); let rev = d.revision();
3955 d.set_diagnostics(rev, vec![Diagnostic::new(2..99, Severity::Error, "past eof")]);
3956 assert_eq!(d.diagnostics_in(0..100).next().unwrap().0, 2..3, "clipped to buffer len");
3957 }
3958
3959 #[test]
3960 fn rend04_diagnostic_render_row_shifts_when_lines_inserted_above() {
3961 use crate::{Diagnostic, Point, Severity};
3965 let mut d = doc("L0\nL1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9");
3966 let row5 = d.buffer().point_to_offset(Point { row: 5, col: 0 });
3967 d.set_diagnostics(d.revision(), vec![Diagnostic::new(row5..row5 + 2, Severity::Error, "e")]);
3968 let start = d.diagnostics_in(0..u32::MAX).next().unwrap().0.start;
3969 assert_eq!(d.buffer().offset_to_point(start).row, 5);
3970 d.edit(vec![EditOp::insert(0, "a\nb\nc\n")]).unwrap();
3972 let span = d.diagnostics_in(0..u32::MAX).next().unwrap().0;
3973 assert_eq!(d.buffer().offset_to_point(span.start).row, 8, "5 + 3 inserted lines");
3974 assert_eq!(&d.text()[span.start as usize..span.end as usize], "L5", "still glued to its code");
3975 }
3976
3977 #[test]
3983 fn windowed_fold_queries_match_the_whole_document() {
3984 let mut d = doc(
3985 "top\n\
3986 a {\n b [1, 2]\n c {\n d\n }\n}\n\
3987 mid\n\
3988 e (x,\n y)\n\
3989 f {\n g\n}\n\
3990 end",
3991 );
3992 let n = d.buffer().line_count();
3993 for row in 0..n {
3995 let want = d
3996 .foldable_pairs()
3997 .into_iter()
3998 .filter(|&(_, _, h, l)| h == row && l > h)
3999 .max_by_key(|&(_, _, h, l)| l - h)
4000 .map(|(o, ..)| o);
4001 assert_eq!(d.block_opener_on_row(row), want, "block_opener_on_row row {row}");
4002 }
4003 assert_eq!(d.collapsible_pairs_in_rows(0..n), d.collapsible_pairs());
4005 for (a, b) in [(0u32, 3u32), (2, 6), (3, 4), (7, n)] {
4007 let want: Vec<_> =
4008 d.collapsible_pairs().into_iter().filter(|&(_, _, h, _)| a <= h && h < b).collect();
4009 assert_eq!(d.collapsible_pairs_in_rows(a..b), want, "collapsible_pairs_in_rows {a}..{b}");
4010 }
4011 for row in 0..n {
4013 let windowed: Vec<_> =
4014 d.foldable_ranges_in_rows(row..row + 1).into_iter().filter(|(h, _)| h.0 == row).collect();
4015 let whole: Vec<_> =
4016 d.foldable_ranges().into_iter().filter(|(h, _)| h.0 == row).collect();
4017 assert_eq!(windowed, whole, "foldable_ranges row {row}");
4018 }
4019 let opener = d.block_opener_on_row(1).unwrap();
4021 assert!(d.toggle_fold_opener(opener));
4022 assert_eq!(d.collapsible_pairs_in_rows(0..n), d.collapsible_pairs());
4023 }
4024
4025 #[test]
4026 fn fold_survives_undo_redo() {
4027 use crate::{fold_map::FoldMap, BufferRow};
4032 let mut d = doc("L0\nL1\nL2\nL3\nL4\nblk {\n x\n y\n}\nL9");
4033 assert_eq!(d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect::<Vec<_>>(), vec![(5, 8)]);
4034 assert!(d.fold(BufferRow(5), BufferRow(8)));
4035 let header = |d: &Document| FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(5));
4036 let header8 = |d: &Document| FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(8));
4037 assert_eq!(header(&d), Some(BufferRow(8)));
4038 d.edit(vec![EditOp::insert(0, "a\nb\nc\n")]).unwrap();
4040 assert_eq!(header8(&d), Some(BufferRow(11)));
4041 assert!(d.undo());
4043 assert_eq!(header(&d), Some(BufferRow(8)));
4044 assert!(d.redo());
4045 assert_eq!(header8(&d), Some(BufferRow(11)));
4046 }
4047
4048 #[test]
4049 fn pasting_after_the_collapsed_line_keeps_the_outer_fold() {
4050 use crate::{fold_map::FoldMap, BufferRow, SelectionSet};
4051 let mut d = doc("{\n\t{\n\t}\n}\n");
4053 assert_eq!(d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect::<Vec<_>>(), vec![(0, 3), (1, 2)]);
4054 assert!(d.fold(BufferRow(0), BufferRow(3))); let block = d.buffer().text()[0..9].to_string();
4058 d.set_selections(SelectionSet::new(9));
4059 d.insert_text(&block);
4060 assert_eq!(d.folds().len(), 1, "the outer fold survives the paste");
4063 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(0)), Some(BufferRow(3)));
4064 }
4065
4066 #[test]
4067 fn editing_after_the_brace_keeps_a_folded_block() {
4068 use crate::{fold_map::FoldMap, BufferRow, SelectionSet};
4069 let mut d = doc("{\n}");
4070 assert!(d.fold(BufferRow(0), BufferRow(1)));
4071 d.set_selections(SelectionSet::new(d.buffer().len()));
4073 d.insert_text("abc");
4074 assert_eq!(d.buffer().text(), "{\n}abc");
4075 assert_eq!(d.folds().len(), 1, "fold survives typing after the brace");
4076 let end = d.buffer().len();
4078 d.edit(vec![EditOp::delete(end - 3..end)]).unwrap();
4079 assert_eq!(d.buffer().text(), "{\n}");
4080 assert_eq!(d.folds().len(), 1, "fold survives deleting after the brace");
4081 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(0)), Some(BufferRow(1)));
4082 }
4083
4084 #[test]
4085 fn pressing_return_after_a_folded_eof_block_keeps_it_folded() {
4086 use crate::{fold_map::FoldMap, BufferRow, SelectionSet};
4087 let mut d = doc("{\n}");
4090 assert!(d.fold(BufferRow(0), BufferRow(1)));
4091 d.set_selections(SelectionSet::new(d.buffer().len()));
4094 d.insert_text("\n");
4095 assert_eq!(d.buffer().text(), "{\n}\n");
4096 assert_eq!(d.folds().len(), 1, "the intact block stays folded");
4097 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(0)), Some(BufferRow(1)));
4098 }
4099
4100 #[test]
4101 fn cutting_trailing_content_keeps_an_intact_fold() {
4102 use crate::{fold_map::FoldMap, BufferRow};
4103 let mut d = doc("{\n}\nX");
4105 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();
4112 assert_eq!(d.buffer().text(), "{\n}", "only the trailing content was removed");
4113 assert_eq!(d.folds().len(), 1, "the intact block stays folded");
4114 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(0)), Some(BufferRow(1)));
4115 }
4116
4117 #[test]
4118 fn cutting_across_a_folds_boundary_drops_it_not_drifts_it() {
4119 use crate::{fold_map::FoldMap, BufferRow};
4120 let mut d = doc("{\n}\n{\n}\n{\n}\n");
4122 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();
4129 assert!(d.folds().is_empty(), "fold dropped, not drifted onto the first block");
4132 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(0)), None, "first set not folded");
4133 }
4134
4135 #[test]
4136 fn typing_at_many_carets_over_folds_stays_linear() {
4137 use crate::bracket::ENCLOSING_WALKS;
4138 use crate::row_layout::DISPLAY_POSITION_PROBES;
4139 use crate::{Motion, SelectionSet};
4140 let mut src = String::new();
4147 for i in 0..400 {
4148 src.push_str(&format!(
4149 "// block {i}\nfn pid_{i}(pid: u8) -> u8 {{\n switch pid {{\n 0x0D => {{ return 88; }}\n }}\n}}\n\n"
4150 ));
4151 }
4152 let mut d = doc(&src);
4153 let first = d.buffer().text().find("fn pid").unwrap() as u32;
4154 d.set_selections(SelectionSet::from_ranges(&[(first, first + 6)], 0));
4155 d.select_all_occurrences();
4156 let carets = d.selections().all().len();
4157 assert!(carets >= 400, "select-all found every block ({carets} carets)");
4158 d.move_carets(Motion::LineEnd, false);
4159 d.fold_at_carets(false);
4160 d.move_carets(Motion::LineEnd, false);
4161
4162 ENCLOSING_WALKS.with(|c| c.set(0));
4164 DISPLAY_POSITION_PROBES.with(|c| c.set(0));
4165 d.insert_text("a");
4166 let walks = ENCLOSING_WALKS.with(std::cell::Cell::get);
4167 let probes = DISPLAY_POSITION_PROBES.with(std::cell::Cell::get);
4168 assert!(walks <= 2, "one enclosing walk per commit, not O(carets): {walks} for {carets} carets");
4169 assert!(
4170 probes <= 4 * carets as u64,
4171 "display-position probes are O(edit points), not O(carets²): {probes} for {carets} carets"
4172 );
4173 }
4174
4175 #[test]
4176 fn folded_keystroke_never_resolves_bracket_depth() {
4177 use crate::bracket_tree::BRACKET_VIEW_CALLS;
4178 use crate::SelectionSet;
4179 let mut src = String::new();
4190 for i in 0..300 {
4191 src.push_str(&format!("fn f_{i}() {{\n body\n}}\n"));
4192 }
4193 let mut d = doc(&src);
4194 let openers: Vec<u32> = d
4196 .brackets()
4197 .all()
4198 .iter()
4199 .filter(|b| b.open && d.buffer().char_at(b.offset) == Some('{'))
4200 .map(|b| b.offset)
4201 .collect();
4202 assert_eq!(openers.len(), 300, "one foldable block opener each");
4203 for &o in &openers {
4204 assert!(d.toggle_fold_opener(o));
4205 }
4206 let carets: Vec<(u32, u32)> = openers.iter().map(|&o| (o + 1, o + 1)).collect();
4208 d.set_selections(SelectionSet::from_ranges(&carets, 0));
4209 let _ = d.fold_map(); BRACKET_VIEW_CALLS.with(|c| c.set(0));
4212 d.insert_text("a");
4213 let _ = d.fold_map(); let depth_resolves = BRACKET_VIEW_CALLS.with(std::cell::Cell::get);
4215 assert_eq!(
4216 depth_resolves, 0,
4217 "a fold-heavy keystroke resolved bracket depth {depth_resolves} times over 300 folds \
4218 — the edit path must use foldable_partner, not at()/bracket_view"
4219 );
4220 }
4221
4222 #[test]
4223 fn single_line_pair_folds_inline() {
4224 use crate::{fold_map::FoldMap, SelectionSet};
4225 let mut d = doc("x = [1, 2, 3]");
4227 d.set_selections(SelectionSet::new(6)); let opener = d.fold_opener_at_caret(false).expect("an enclosing foldable pair");
4229 assert_eq!(opener, 4);
4230 assert!(d.toggle_fold_opener(opener));
4231 let m = FoldMap::new(d.folds(), d.brackets(), d.buffer());
4232 assert_eq!(m.display_row_count(), 1, "an inline fold hides no rows");
4233 assert_eq!(m.inline_folds().len(), 1);
4234 assert_eq!((m.inline_folds()[0].row, m.inline_folds()[0].open, m.inline_folds()[0].close), (0, 4, 12));
4235 assert!(d.toggle_fold_opener(4));
4237 assert!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).inline_folds().is_empty());
4238 }
4239
4240 #[test]
4241 fn fold_at_carets_acts_on_every_caret() {
4242 use crate::{fold_map::FoldMap, SelectionSet};
4243 let mut d = doc("a{x}\nb{y}");
4245 d.set_selections(SelectionSet::from_offsets(&[2, 7])); assert!(d.fold_at_carets(false), "Ctrl+Shift+[ folds at every caret");
4247 let m = FoldMap::new(d.folds(), d.brackets(), d.buffer());
4248 assert_eq!(m.inline_folds().len(), 2, "both blocks collapsed, not just the primary");
4249 assert!(d.fold_at_carets(true), "Ctrl+Shift+] unfolds at every caret");
4251 assert!(
4252 FoldMap::new(d.folds(), d.brackets(), d.buffer()).inline_folds().is_empty(),
4253 "both blocks expanded",
4254 );
4255 }
4256
4257 #[test]
4258 fn fold_map_cache_matches_a_fresh_build_across_changes() {
4259 fn consistent(d: &Document) -> bool {
4265 *d.fold_map() == crate::fold_map::FoldMap::new(d.folds(), d.brackets(), d.buffer())
4266 }
4267 let mut d = doc("a {\nx\n}\nb = [1, 2, 3]\nc\n");
4268 assert!(consistent(&d), "empty folds");
4269 let block = d.text().find('{').unwrap() as u32;
4271 let inline = d.text().find('[').unwrap() as u32;
4272 d.toggle_fold_opener(block);
4273 d.toggle_fold_opener(inline);
4274 assert!(consistent(&d), "after folds — rebuilt on the generation");
4275 d.edit(vec![EditOp::insert(0, "Z")]).unwrap();
4278 assert!(consistent(&d), "after a no-line-change edit — shifted in place");
4279 d.edit(vec![EditOp::insert(0, "Y")]).unwrap();
4280 assert!(consistent(&d), "after a second in-place shift (cumulative)");
4281 d.edit(vec![EditOp::insert(0, "\n")]).unwrap();
4283 assert!(consistent(&d), "after a line change — rebuilt");
4284 d.edit(vec![EditOp::insert(0, "W")]).unwrap();
4285 assert!(consistent(&d), "after an edit following a rebuild");
4286 }
4287
4288 #[test]
4289 fn deleting_into_a_folded_interior_rebuilds_not_drifts() {
4290 let consistent = |d: &Document| {
4297 *d.fold_map() == crate::fold_map::FoldMap::new(d.folds(), d.brackets(), d.buffer())
4298 };
4299 for prefix in ["xx\n", ""] {
4300 let mut d = doc(&format!("{prefix}fn foo() {{\n body\n}}\nafter\n"));
4303 let open = d.text().find('{').unwrap() as u32;
4304 d.toggle_fold_opener(open);
4305 let _ = d.fold_map();
4306 assert!(consistent(&d), "{prefix:?}: folded");
4307 let nl = open + d.text()[open as usize..].find('\n').unwrap() as u32;
4308 d.edit(vec![EditOp::delete(nl..nl + 1)]).unwrap(); assert!(consistent(&d), "{prefix:?}: after deleting the header-terminating newline");
4310 }
4311 }
4312
4313 #[test]
4314 fn fold_map_cache_matches_fresh_build_under_random_edits() {
4315 let consistent = |d: &Document| {
4321 *d.fold_map() == crate::fold_map::FoldMap::new(d.folds(), d.brackets(), d.buffer())
4322 };
4323 let mut state = 0xF01D_5EEDu32;
4324 let mut next = || {
4325 state ^= state << 13;
4326 state ^= state >> 17;
4327 state ^= state << 5;
4328 state
4329 };
4330 let mut d = doc(&"fn f() {\n x = [1, 2]\n y\n}\n".repeat(6));
4331 for open in d.collapsible_pairs().iter().map(|&(o, ..)| o).collect::<Vec<_>>() {
4332 d.toggle_fold_opener(open);
4333 }
4334 assert!(consistent(&d), "after collapse-all");
4335 for step in 0..1500 {
4336 let len = d.buffer().len();
4337 if len == 0 {
4338 break;
4339 }
4340 let at = next() % (len + 1);
4341 let _ = match next() % 6 {
4342 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 => {
4347 let end = (at + 1 + next() % 5).min(len); d.edit(vec![EditOp::delete(at..end)])
4349 }
4350 _ => d.edit(vec![EditOp::insert(at, "q")]),
4351 };
4352 assert!(consistent(&d), "step {step}: cache drifted from a fresh build (at {at})");
4353 }
4354 }
4355
4356 #[test]
4357 fn keystroke_and_arrow_do_not_rebuild_the_fold_map_at_scale() {
4358 use crate::fold_map::FOLD_BUILDS;
4364 let mut text = String::new();
4365 for i in 0..300 {
4366 text.push_str(&format!("fn f{i}() {{\n body\n}}\n"));
4367 }
4368 let mut d = doc(&text);
4369 let opens: Vec<u32> = d.collapsible_pairs().into_iter().map(|(o, ..)| o).collect();
4370 assert!(opens.len() >= 300, "every block is foldable");
4371 d.set_selections(SelectionSet::from_offsets(&opens));
4372 d.fold_at_carets(false); d.set_selections(SelectionSet::new(0)); let _ = d.fold_map(); let base = FOLD_BUILDS.with(std::cell::Cell::get);
4378 d.type_char('x');
4379 let _ = d.fold_map();
4380 assert_eq!(
4381 FOLD_BUILDS.with(std::cell::Cell::get) - base,
4382 0,
4383 "a keystroke over {} folds must not rebuild the FoldMap",
4384 opens.len()
4385 );
4386
4387 let base = FOLD_BUILDS.with(std::cell::Cell::get);
4389 d.move_carets(Motion::Down, false);
4390 let _ = d.fold_map();
4391 assert_eq!(
4392 FOLD_BUILDS.with(std::cell::Cell::get) - base,
4393 0,
4394 "an arrow key over {} folds must not rebuild the FoldMap",
4395 opens.len()
4396 );
4397
4398 d.set_selections(SelectionSet::new(0));
4404 let base = FOLD_BUILDS.with(std::cell::Cell::get);
4405 d.edit(vec![EditOp::insert(0, "\n")]).unwrap();
4406 let _ = d.fold_map();
4407 assert_eq!(
4408 FOLD_BUILDS.with(std::cell::Cell::get) - base,
4409 0,
4410 "a newline at the top over {} folds must not rebuild the FoldMap",
4411 opens.len()
4412 );
4413 }
4414
4415 #[test]
4416 fn keystroke_with_document_scale_decorations_does_not_resort_the_store() {
4417 use crate::decorations::DECORATION_SORTS;
4423 use crate::{Diagnostic, Severity};
4424 let mut d = doc(&"let x = 1;\n".repeat(2000)); let rev = d.revision();
4426 let diags: Vec<Diagnostic> = (0..2000u32)
4427 .map(|i| Diagnostic::new(i * 10..i * 10 + 3, Severity::Warning, "w"))
4428 .collect();
4429 d.set_diagnostics(rev, diags); d.set_selections(SelectionSet::new(0));
4431
4432 let base = DECORATION_SORTS.with(std::cell::Cell::get);
4433 d.type_char('z');
4434 assert_eq!(
4435 DECORATION_SORTS.with(std::cell::Cell::get) - base,
4436 0,
4437 "a keystroke with 2000 diagnostics must not re-sort the decoration store"
4438 );
4439 }
4440
4441 #[test]
4442 fn reconcile_scans_the_fold_set_only_when_a_fold_actually_breaks() {
4443 use crate::fold_map::RECONCILE_SCANS;
4451 let mut d = doc("fn f() {\n body\n}\ntail\n");
4452 let open = d.text().find('{').unwrap() as u32;
4453 assert!(d.toggle_fold_opener(open));
4454 d.set_selections(SelectionSet::new(0));
4455
4456 for text in ["a", "\n", "{"] {
4461 let base = RECONCILE_SCANS.with(std::cell::Cell::get);
4462 d.edit(vec![EditOp::insert(0, text)]).unwrap();
4463 assert_eq!(
4464 RECONCILE_SCANS.with(std::cell::Cell::get) - base,
4465 0,
4466 "inserting {text:?} breaks no fold, so it must not scan the fold set"
4467 );
4468 }
4469
4470 let close = d.text().rfind('}').unwrap() as u32;
4472 let base = RECONCILE_SCANS.with(std::cell::Cell::get);
4473 d.edit(vec![EditOp::delete(close..close + 1)]).unwrap();
4474 assert!(
4475 RECONCILE_SCANS.with(std::cell::Cell::get) - base >= 1,
4476 "deleting a brace that breaks a fold must reconcile the fold set"
4477 );
4478 assert!(d.folds().is_empty(), "the broken fold was dropped");
4479 }
4480
4481 #[test]
4482 fn windowed_reconcile_matches_the_whole_set_reconcile_under_random_edits() {
4483 use crate::SelectionSet;
4484 let mut src = String::new();
4492 for i in 0..40 {
4493 src.push_str(&format!("block{i} {{\n a\n b\n}}\n"));
4494 }
4495 let mut d = doc(&src);
4496 for open in d.collapsible_pairs().iter().map(|&(o, ..)| o).collect::<Vec<_>>() {
4497 d.toggle_fold_opener(open);
4498 }
4499 let mut state: u32 = 0x1234_5678;
4501 let mut rng = || {
4502 state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
4503 state
4504 };
4505 for _ in 0..300 {
4506 let len = d.buffer().len();
4507 if len == 0 {
4508 break;
4509 }
4510 let pos = rng() % len;
4511 match rng() % 5 {
4512 0 => {
4513 d.set_selections(SelectionSet::new(pos));
4514 d.insert_text("{");
4515 }
4516 1 => {
4517 d.set_selections(SelectionSet::new(pos));
4518 d.insert_text("}");
4519 }
4520 2 if len > 1 => {
4521 let s = pos.min(len - 1);
4524 d.edit(vec![EditOp::delete(s..s + 1)]).unwrap();
4525 }
4526 _ => {
4527 d.set_selections(SelectionSet::new(pos));
4528 d.insert_text("x");
4529 }
4530 }
4531 let before: Vec<u32> = d.folds().iter().collect();
4532 d.force_whole_reconcile();
4533 let after: Vec<u32> = d.folds().iter().collect();
4534 assert_eq!(before, after, "windowed reconcile missed a fold the whole-set pass caught");
4535 for o in after {
4537 let foldable = d
4538 .brackets()
4539 .at(o)
4540 .filter(|b| b.open)
4541 .and_then(|b| b.partner)
4542 .is_some_and(|c| crate::row_layout::pair_has_interior(o, c));
4543 assert!(foldable, "a surviving fold at {o} is not a foldable pair");
4544 }
4545 }
4546 }
4547
4548 #[test]
4549 fn fold_at_carets_ejects_carets_hidden_by_a_block() {
4550 use crate::SelectionSet;
4551 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));
4554 assert!(d.fold_at_carets(false), "the block folds");
4555 let row = d.buffer().offset_to_point(d.selections().newest().head()).row;
4558 assert_eq!(row, 0, "caret ejected to the header row");
4559 }
4560
4561 #[test]
4562 fn horizontal_movement_hops_an_inline_fold() {
4563 use crate::{Motion, SelectionSet};
4564 let mut d = doc("x = [1, 2, 3]"); d.set_selections(SelectionSet::new(6)); let op = d.fold_opener_at_caret(false).unwrap();
4567 assert!(d.toggle_fold_opener(op));
4568 assert_eq!(d.selections().newest().head(), 5, "caret snapped just after `[`");
4569 d.move_carets(Motion::Right, false);
4570 assert_eq!(d.selections().newest().head(), 12, "Right hops the collapsed interior to `]`");
4571 d.move_carets(Motion::Left, false);
4572 assert_eq!(d.selections().newest().head(), 5, "Left hops back to just after `[`");
4573 }
4574
4575 #[test]
4576 fn collapsible_at_prefers_the_innermost() {
4577 let d = doc("fn m() {\n let x = [1, 2, 3];\n}");
4579 let mut pairs = d.collapsible_pairs();
4580 pairs.sort();
4581 assert_eq!(pairs, vec![(7, 32, 0, 2), (21, 29, 1, 1)]);
4582 assert_eq!(d.collapsible_at(24), Some(21));
4584 assert_eq!(d.collapsible_at(12), Some(7));
4586 assert_eq!(d.collapsible_at(0), None);
4588 }
4589
4590 #[test]
4591 fn collapsible_excludes_the_already_folded() {
4592 let mut d = doc("fn m() {\n let x = [1, 2, 3];\n}");
4593 assert!(d.toggle_fold_opener(7));
4596 let openers: Vec<u32> = d.collapsible_pairs().iter().map(|&(o, ..)| o).collect();
4597 assert_eq!(openers, vec![21]);
4598 assert_eq!(d.collapsible_at(12), None, "the folded block is no longer a target");
4599 assert_eq!(d.collapsible_at(24), Some(21), "the array inside it still is");
4600 }
4601
4602 #[test]
4603 fn nested_inline_folds_reduce_to_the_outer() {
4604 use crate::fold_map::FoldMap;
4605 let mut d = doc("outer_call(a, [1, 2, 3])");
4607 assert!(d.toggle_fold_opener(14)); assert!(d.toggle_fold_opener(10)); let inline = |d: &Document| {
4610 FoldMap::new(d.folds(), d.brackets(), d.buffer()).inline_folds().iter().map(|f| f.open).collect::<Vec<_>>()
4611 };
4612 assert_eq!(inline(&d), vec![10]);
4614 assert!(d.toggle_fold_opener(10));
4616 assert_eq!(inline(&d), vec![14]);
4617 }
4618
4619 #[test]
4620 fn horizontal_movement_hops_the_collapsed_gap() {
4621 use crate::{BufferRow, Motion, Point, SelectionSet};
4622 let mut d = doc("m {\n a\n b\n}\nafter");
4624 assert!(d.fold(BufferRow(0), BufferRow(3))); let caret = |d: &Document| d.buffer().offset_to_point(d.selections().newest().head());
4626 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(0, 3))));
4628 d.move_carets(Motion::Right, false);
4629 assert_eq!(caret(&d), Point::new(3, 0), "Right hops the hidden interior to the closing bracket");
4630 d.move_carets(Motion::Left, false);
4631 assert_eq!(caret(&d), Point::new(0, 3), "Left hops back to the header end");
4632 }
4633
4634 #[test]
4635 fn vertical_move_skips_folds() {
4636 use crate::{BufferRow, Motion, Point, SelectionSet};
4637 let mut d = doc("L0\n{\nL2\nL3\n}\nL5");
4638 assert!(d.fold(BufferRow(1), BufferRow(4))); let row = |d: &Document| d.buffer().offset_to_point(d.selections().newest().head()).row;
4640 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 0))));
4642 d.move_carets(Motion::Down, false);
4643 assert_eq!(row(&d), 5);
4644 d.move_carets(Motion::Up, false);
4646 assert_eq!(row(&d), 1);
4647 }
4648
4649 #[test]
4650 fn typing_into_an_inline_folds_gap_expands_it() {
4651 use crate::SelectionSet;
4652 let mut d = doc("x = [1, 2, 3]");
4656 d.set_selections(SelectionSet::new(6));
4657 assert!(d.toggle_fold_opener(4));
4658 assert_eq!(d.selections().newest().head(), 5, "caret ejected to the left edge");
4659 d.type_char('9');
4660 assert!(!d.folds().is_folded(4), "editing hidden text expands the fold");
4661 assert_eq!(d.text(), "x = [91, 2, 3]");
4662 }
4663
4664 #[test]
4665 fn typing_at_a_collapsed_headers_end_keeps_it_folded() {
4666 use crate::{Point, SelectionSet};
4667 let mut d = doc("m {\n a\n}");
4671 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 0))));
4672 let opener = 2;
4673 assert!(d.toggle_fold_opener(opener));
4674 d.type_char('x'); assert!(d.folds().is_folded(opener), "a visible-part edit leaves the fold alone");
4676 assert!(d.text().starts_with("m {x\n"));
4677 }
4678
4679 #[test]
4680 fn undo_into_a_folded_region_expands_it() {
4681 use crate::{Point, SelectionSet};
4682 let mut d = doc("m {\n a\n}");
4686 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 3))));
4687 d.type_char('b'); let opener = 2;
4689 assert!(d.toggle_fold_opener(opener));
4690 assert!(d.folds().is_folded(opener));
4691 assert!(d.undo());
4692 assert!(!d.folds().is_folded(opener), "undoing hidden text reveals it");
4693 assert!(d.text().contains("\n a\n"), "the typed char was reverted");
4694 }
4695
4696 #[test]
4697 fn folding_a_block_ejects_a_hidden_caret_to_the_header_end() {
4698 use crate::{Point, SelectionSet};
4699 let mut d = doc("m {\n a\n }");
4701 let opener = 2;
4702 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 2))));
4704 assert!(d.toggle_fold_opener(opener));
4705 assert_eq!(
4706 d.selections().newest().head(),
4707 d.buffer().point_to_offset(Point::new(0, 3)),
4708 "the hidden caret ejects to just before the placeholder"
4709 );
4710 assert!(d.toggle_fold_opener(opener)); d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(2, 1))));
4713 assert!(d.toggle_fold_opener(opener));
4714 assert_eq!(d.selections().newest().head(), d.buffer().point_to_offset(Point::new(0, 3)));
4715 assert!(d.toggle_fold_opener(opener)); let on_tail = d.buffer().point_to_offset(Point::new(2, 2));
4718 d.set_selections(SelectionSet::new(on_tail));
4719 assert!(d.toggle_fold_opener(opener));
4720 assert_eq!(d.selections().newest().head(), on_tail, "the visible tail is not ejected");
4721 }
4722
4723 #[test]
4724 fn fold_chord_works_when_the_caret_touches_a_bracket() {
4725 use crate::SelectionSet;
4726 let mut d = doc("x = [1, 2, 3]");
4728 d.set_selections(SelectionSet::new(4));
4730 assert_eq!(d.fold_opener_at_caret(false), Some(4), "touching the opener from the left");
4731 d.set_selections(SelectionSet::new(13));
4733 assert_eq!(d.fold_opener_at_caret(false), Some(4), "touching the closer from the right");
4734 assert!(d.toggle_fold_opener(4));
4736 assert_eq!(d.fold_opener_at_caret(true), Some(4), "touching unfolds too");
4737 assert!(d.toggle_fold_opener(4));
4739 d.set_selections(SelectionSet::new(1));
4740 assert_eq!(d.fold_opener_at_caret(false), None);
4741 }
4742
4743 #[test]
4744 fn fold_chord_at_a_shared_boundary_picks_the_touched_inner_pair() {
4745 use crate::SelectionSet;
4746 let d = {
4750 let mut d = doc("{a [b] c}");
4751 d.set_selections(SelectionSet::new(6));
4752 d
4753 };
4754 assert_eq!(d.fold_opener_at_caret(false), Some(3), "the touched inline pair beats the enclosing block");
4755 }
4756
4757 #[test]
4758 fn vertical_goal_is_a_display_cell_across_tab_stops() {
4759 use crate::{Motion, Point, SelectionSet};
4760 let mut d = doc("\tab\nxxxxxxxxxx");
4764 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(0, 1))));
4765 d.move_carets(Motion::Down, false);
4766 assert_eq!(
4767 d.buffer().offset_to_point(d.selections().newest().head()),
4768 Point::new(1, 4),
4769 "the goal is the visual column, not the byte column"
4770 );
4771 d.move_carets(Motion::Up, false);
4773 assert_eq!(d.buffer().offset_to_point(d.selections().newest().head()), Point::new(0, 1));
4774 }
4775
4776 #[test]
4777 fn vertical_move_through_a_collapsed_tail_stays_visual() {
4778 use crate::{BufferRow, Motion, Point, SelectionSet};
4779 let mut d = doc("m {\na\n} t\nabcdefgh");
4782 assert!(d.fold(BufferRow(0), BufferRow(2)));
4783 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(2, 3))));
4785 d.move_carets(Motion::Down, false);
4786 assert_eq!(
4787 d.buffer().offset_to_point(d.selections().newest().head()),
4788 Point::new(3, 8),
4789 "Down from the far-right tail lands at the row's visual end, not byte col 3"
4790 );
4791 d.move_carets(Motion::Up, false);
4793 assert_eq!(
4794 d.buffer().offset_to_point(d.selections().newest().head()).row,
4795 2,
4796 "a goal over the placeholder tail resolves onto the tail"
4797 );
4798 }
4799
4800 #[test]
4801 fn vertical_landing_snaps_out_of_a_collapsed_inline_gap() {
4802 use crate::{Motion, Point, SelectionSet};
4803 let mut d = doc("aaaaaaaaaa\nx[123456]y");
4807 let opener = d.buffer().text().find('[').unwrap() as u32;
4808 assert!(d.toggle_fold_opener(opener));
4809 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(0, 3))));
4810 d.move_carets(Motion::Down, false);
4811 assert_eq!(
4812 d.selections().newest().head(),
4813 opener + 1,
4814 "a landing on the chip snaps to just after the opening bracket"
4815 );
4816 }
4817
4818 #[test]
4819 fn home_and_end_operate_on_the_collapsed_display_line() {
4820 use crate::{BufferRow, Motion, Point, SelectionSet};
4821 let mut d = doc("L0\n m {\na\n }");
4824 assert!(d.fold(BufferRow(1), BufferRow(3)));
4825 let head = |d: &Document| d.selections().newest().head();
4826 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 2))));
4829 d.move_carets(Motion::LineEnd, false);
4830 assert_eq!(head(&d), d.buffer().point_to_offset(Point::new(3, 3)), "End lands after the tail brace");
4831 d.move_carets(Motion::LineStart, false);
4834 assert_eq!(head(&d), d.buffer().point_to_offset(Point::new(1, 2)), "Home jumps to the header's smart start");
4835 d.move_carets(Motion::LineStart, false);
4837 assert_eq!(head(&d), d.buffer().point_to_offset(Point::new(1, 0)));
4838 assert!(d.unfold(BufferRow(1)));
4840 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 2))));
4841 d.move_carets(Motion::LineEnd, false);
4842 assert_eq!(head(&d), d.buffer().point_to_offset(Point::new(1, 5)), "plain End at the header's own end");
4843 }
4844
4845 #[test]
4846 fn foldable_ranges_from_multiline_brackets() {
4847 let d = doc("mod m {\n fn f() {\n if x {\n a\n }\n }\n g()\n}");
4850 let pairs: Vec<(u32, u32)> = d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect();
4851 assert_eq!(pairs, vec![(0, 7), (1, 5), (2, 4)]);
4853 }
4854
4855 #[test]
4856 fn fold_dropped_when_its_bracket_pair_is_broken() {
4857 use crate::{fold_map::FoldMap, BufferRow};
4858 let mut d = doc("m {\n a\n b\n}\nafter");
4860 assert_eq!(d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect::<Vec<_>>(), vec![(0, 3)]);
4861 assert!(d.fold(BufferRow(0), BufferRow(3)));
4862 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));
4868 let e = d.buffer().point_to_offset(crate::Point::new(4, 0));
4869 d.edit(vec![EditOp::delete(s..e)]).unwrap();
4870 assert!(d.foldable_ranges().is_empty(), "no closing brace ⇒ nothing foldable");
4871 assert!(d.folds().is_empty(), "invalidated fold dropped, not left orphaning hidden rows");
4872 assert_eq!(
4873 FoldMap::new(d.folds(), d.brackets(), d.buffer()).display_row_count(),
4874 d.buffer().line_count(),
4875 "every row is visible again"
4876 );
4877 }
4878
4879 #[test]
4880 fn valid_fold_survives_edit_above() {
4881 use crate::{fold_map::FoldMap, BufferRow, SelectionSet};
4882 let mut d = doc("m {\n a\n b\n}\nafter");
4883 assert!(d.fold(BufferRow(0), BufferRow(3)));
4884 d.set_selections(SelectionSet::new(0));
4887 d.insert_text("top\n");
4888 assert_eq!(d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect::<Vec<_>>(), vec![(1, 4)]);
4889 assert_eq!(d.folds().len(), 1, "the still-valid fold is kept");
4890 let m = FoldMap::new(d.folds(), d.brackets(), d.buffer());
4891 assert_eq!(m.fold_at_header(BufferRow(1)), Some(BufferRow(4)));
4892 assert!(m.is_folded(BufferRow(2)) && m.is_folded(BufferRow(3)) && m.is_folded(BufferRow(4)));
4893 }
4894
4895 #[test]
4896 fn breaking_one_block_keeps_a_sibling_fold() {
4897 use crate::{fold_map::FoldMap, BufferRow};
4898 let mut d = doc("a {\n x\n}\nb {\n y\n}\nafter");
4901 assert_eq!(d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect::<Vec<_>>(), vec![(0, 2), (3, 5)]);
4902 assert!(d.fold(BufferRow(0), BufferRow(2)));
4903 assert!(d.fold(BufferRow(3), BufferRow(5)));
4904 assert_eq!(d.folds().len(), 2);
4905 let s = d.buffer().point_to_offset(crate::Point::new(5, 0));
4908 let e = d.buffer().point_to_offset(crate::Point::new(6, 0));
4909 d.edit(vec![EditOp::delete(s..e)]).unwrap();
4910 assert_eq!(d.folds().len(), 1, "only the broken block's fold drops");
4911 let m = FoldMap::new(d.folds(), d.brackets(), d.buffer());
4912 assert_eq!(m.fold_at_header(BufferRow(0)), Some(BufferRow(2)), "sibling A still collapsed");
4913 assert_eq!(m.fold_at_header(BufferRow(3)), None, "B is no longer folded");
4914 }
4915
4916 #[test]
4917 fn fold_drops_when_its_interior_is_deleted() {
4918 use crate::{fold_map::FoldMap, BufferRow};
4919 let mut d = doc("L0\n{\nL2\nL3\n}\nL5");
4920 assert!(d.fold(BufferRow(1), BufferRow(4))); let start = d.buffer().point_to_offset(crate::Point::new(2, 0));
4923 let end = d.buffer().point_to_offset(crate::Point::new(5, 0));
4924 d.edit(vec![EditOp::delete(start..end)]).unwrap();
4925 assert!(d.folds().is_empty(), "fold dropped when its interior text is deleted");
4926 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).display_row_count(), d.buffer().line_count());
4927 }
4928
4929 #[test]
4930 fn intel05_diagnostic_keeps_byte_offsets_on_a_multibyte_line() {
4931 use crate::{Diagnostic, Severity};
4936 let mut d = doc("aé=x");
4937 d.set_diagnostics(d.revision(), vec![Diagnostic::new(4..5, Severity::Error, "e")]);
4938 let span = d.diagnostics_in(0..100).next().unwrap().0;
4939 assert_eq!(span, 4..5, "diagnostics_in returns raw byte offsets, not cells");
4940 assert_eq!(crate::display_map::expand(&d.buffer().line(0), span.start, 4), 3);
4943 }
4944
4945 #[test]
4946 fn undo_resyncs_brackets_and_highlight_to_the_reverted_text() {
4947 const G: &str = "%YAML 1.2\n---\nname: T\nscope: source.t\ncontexts:\n main:\n - match: '\\w+'\n scope: keyword.t\n";
4948 const TH: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
4949<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4950<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>"#;
4951 let mut d = doc("()");
4952 d.set_syntax(
4953 crate::SyntaxDef::from_sublime_syntax(G).unwrap(),
4954 crate::TokenTheme::from_tm_theme(TH).unwrap(),
4955 );
4956 d.tokenize_highlight(d.buffer().line_count());
4957 d.set_selections(SelectionSet::new(1)); d.edit(vec![EditOp::insert(1, "[]\n")]).unwrap(); d.tokenize_highlight(d.buffer().line_count());
4960 assert_eq!((d.buffer().line_count(), d.brackets().all().len()), (2, 4));
4961 assert!(d.undo(), "there was something to undo");
4962 assert_eq!(d.buffer().text(), "()");
4963 assert_eq!((d.buffer().line_count(), d.brackets().all().len()), (1, 2));
4965 d.tokenize_highlight(d.buffer().line_count());
4966 assert!(d.highlight_line_spans(0).is_some());
4967 assert!(d.highlight_line_spans(1).is_none(), "no phantom line after undo");
4968 }
4969
4970 #[test]
4971 fn select_all_spans_the_whole_buffer_with_head_at_end() {
4972 let mut d = doc("abc\ndef"); d.select_all();
4974 assert_eq!(d.selections().len(), 1);
4975 let s = d.selections().newest();
4976 assert_eq!((s.start(), s.end(), s.head()), (0, 7, 7));
4977 }
4978
4979 fn query(text: &str) -> crate::FindQuery {
4980 crate::FindQuery { text: text.into(), case_sensitive: true, ..Default::default() }
4981 }
4982
4983 #[test]
4984 fn find_matches_ride_an_edit_above_them() {
4985 let mut d = doc("xx target"); d.set_find_query(Some(query("target")), 0);
4988 assert_eq!(d.find_matches_in(0..100).collect::<Vec<_>>(), vec![(3..9, false)]);
4989 d.set_selections(SelectionSet::new(0));
4990 d.edit(vec![EditOp::insert(0, "ab")]).unwrap(); assert_eq!(d.find_matches_in(0..100).collect::<Vec<_>>(), vec![(5..11, false)]);
4994 }
4995
4996 #[test]
4997 fn find_next_prev_start_from_the_caret_and_wrap() {
4998 let mut d = doc("foo bar foo baz foo"); d.set_find_query(Some(query("foo")), 0);
5001 assert_eq!((d.find_match_count(), d.active_find_match()), (3, None));
5002 d.set_selections(SelectionSet::new(5)); assert_eq!(d.find_next(0), Some(8..11)); assert_eq!(d.active_find_match(), Some(1));
5005 assert_eq!(d.find_next(0), Some(16..19)); assert_eq!(d.find_next(0), Some(0..3), "wraps to first");
5007 assert_eq!(d.find_prev(0), Some(16..19), "prev from first wraps to last");
5008 d.set_selections(SelectionSet::new(0));
5009 assert_eq!(d.find_prev(0), Some(16..19), "prev before all wraps to last");
5010 }
5011
5012 #[test]
5013 fn active_match_survives_a_rescan_at_the_same_start() {
5014 let mut d = doc("foo foo foo");
5019 d.set_find_query(Some(query("foo")), 0);
5020 d.set_selections(SelectionSet::new(4)); d.find_next(0);
5022 assert_eq!(d.active_find_match(), Some(1));
5023 d.set_selections(SelectionSet::new(11));
5024 d.edit(vec![EditOp::insert(11, " foo")]).unwrap(); assert_eq!(
5026 (d.find_match_count(), d.active_find_match()),
5027 (4, Some(1)),
5028 "current at the commit, active untouched"
5029 );
5030 assert!(!d.maybe_rescan_find(1000), "nothing stale to rescan (uncapped)");
5031 assert_eq!((d.find_match_count(), d.active_find_match()), (4, Some(1)));
5032 }
5033
5034 #[test]
5035 fn find_matches_ride_undo_and_redo_through_the_shared_mover() {
5036 let mut d = doc("foo bar");
5040 d.set_find_query(Some(query("foo")), 0);
5041 assert_eq!(d.find_matches_in(0..100).next().unwrap().0, 0..3);
5042 d.set_selections(SelectionSet::new(0));
5043 d.edit(vec![EditOp::insert(0, "xy")]).unwrap(); assert_eq!(d.find_matches_in(0..100).next().unwrap().0, 2..5);
5045 d.undo(); assert_eq!(d.buffer().text(), "foo bar");
5047 assert_eq!(d.find_matches_in(0..100).next().unwrap().0, 0..3, "rode the undo");
5048 d.redo(); assert_eq!(d.find_matches_in(0..100).next().unwrap().0, 2..5, "rode the redo");
5050 }
5051
5052 #[test]
5053 fn find_count_reflects_live_matches_after_a_collapse_delete() {
5054 let mut d = doc("foo foo"); d.set_find_query(Some(query("foo")), 0);
5059 assert_eq!(d.find_match_count(), 2);
5060 d.edit(vec![EditOp::delete(0..4)]).unwrap(); assert_eq!(d.buffer().text(), "foo");
5062 assert_eq!(d.find_match_count(), 1, "collapsed match not counted");
5063 assert_eq!(d.find_matches_in(0..100).count(), 1, "count agrees with render");
5064 }
5065
5066 #[test]
5067 fn find_navigation_requests_a_reveal() {
5068 let mut d = doc("foo bar foo");
5069 d.set_find_query(Some(query("foo")), 0);
5070 let before = d.reveal_seq();
5071 assert!(d.find_next(0).is_some());
5072 assert!(d.reveal_seq() > before, "navigation bumps the reveal request");
5073 }
5074
5075 #[test]
5076 fn clearing_the_query_drops_every_match() {
5077 let mut d = doc("foo foo");
5078 d.set_find_query(Some(query("foo")), 0);
5079 assert_eq!(d.find_match_count(), 2);
5080 d.set_find_query(None, 0);
5081 assert_eq!((d.find_match_count(), d.find_query().is_none()), (0, true));
5082 assert_eq!(d.find_matches_in(0..100).count(), 0);
5083 }
5084
5085 #[test]
5086 fn edits_repair_the_match_set_immediately() {
5087 let mut d = doc("foo");
5091 d.set_find_query(Some(query("foo")), 0);
5092 d.edit(vec![EditOp::insert(0, "foo ")]).unwrap(); assert_eq!(d.find_match_count(), 2, "current immediately — no debounce");
5094 assert!(!d.maybe_rescan_find(50), "nothing to rescan");
5095 assert!(!d.maybe_rescan_find(1_000), "uncapped: never rescans");
5096 assert_eq!(d.find_match_count(), 2);
5097 }
5098
5099 fn match_ids(d: &Document) -> Vec<crate::decorations::DecorationId> {
5103 d.decorations
5104 .decorations_in(0..u32::MAX)
5105 .filter(|r| matches!(r.kind, DecorationKind::FindMatch))
5106 .map(|r| r.id)
5107 .collect()
5108 }
5109
5110 fn match_ranges(d: &Document) -> Vec<Range<u32>> {
5112 d.find_matches_in(0..u32::MAX).map(|(r, _)| r).collect()
5113 }
5114
5115 fn xorshift(mut state: u64) -> impl FnMut() -> u64 {
5117 move || {
5118 state ^= state << 13;
5119 state ^= state >> 7;
5120 state ^= state << 17;
5121 state
5122 }
5123 }
5124
5125 #[test]
5126 fn repair_preserves_far_match_ids() {
5127 let mut d = doc("foo bar foo bar baz padding padding foo");
5131 d.set_find_query(Some(query("foo")), 0);
5132 let before = match_ids(&d);
5133 assert_eq!(before.len(), 3);
5134 d.edit(vec![EditOp::insert(22, "zz")]).unwrap();
5136 assert!(!d.maybe_rescan_find(1_000), "always current: nothing to rescan");
5137 assert_eq!(match_ids(&d), before, "untouched matches keep their ids");
5138 assert_eq!(match_ranges(&d), vec![0..3, 8..11, 38..41]);
5139 }
5140
5141 #[test]
5142 fn repair_insert_creates_match_in_window() {
5143 let mut d = doc("fo bar");
5144 d.set_find_query(Some(query("foo")), 0);
5145 assert_eq!(d.find_match_count(), 0);
5146 d.edit(vec![EditOp::insert(2, "o")]).unwrap(); assert_eq!(match_ranges(&d), vec![0..3], "current at the commit");
5148 }
5149
5150 #[test]
5151 fn repair_delete_joins_halves_creates_match() {
5152 let mut d = doc("foXo bar");
5154 d.set_find_query(Some(query("foo")), 0);
5155 assert_eq!(d.find_match_count(), 0);
5156 d.edit(vec![EditOp::delete(2..3)]).unwrap(); assert_eq!(match_ranges(&d), vec![0..3]);
5158 }
5159
5160 #[test]
5161 fn repair_destroys_straddling_match() {
5162 let mut d = doc("afoob");
5164 d.set_find_query(Some(query("foo")), 0);
5165 assert_eq!(match_ranges(&d), vec![1..4]);
5166 d.edit(vec![EditOp::delete(3..5)]).unwrap(); assert_eq!(d.find_match_count(), 0);
5168 }
5169
5170 #[test]
5171 fn repair_interior_insert_invalidates_match() {
5172 let mut d = doc("foo");
5175 d.set_find_query(Some(query("foo")), 0);
5176 assert_eq!(d.find_match_count(), 1);
5177 d.edit(vec![EditOp::insert(1, "x")]).unwrap(); assert_eq!(d.find_match_count(), 0);
5179 }
5180
5181 #[test]
5182 fn repair_multi_edit_transaction() {
5183 let mut d = doc("fo bar fo baz");
5185 d.set_find_query(Some(query("foo")), 0);
5186 d.edit(vec![EditOp::insert(2, "o"), EditOp::insert(9, "o")]).unwrap();
5187 assert_eq!(d.buffer().text(), "foo bar foo baz");
5188 assert_eq!(match_ranges(&d), vec![0..3, 8..11]);
5189 }
5190
5191 #[test]
5192 fn repair_equals_full_scan_property() {
5193 let fq = query("ab");
5198 let mut next = xorshift(0x243F_6A88_85A3_08D3);
5199 let alphabet = ['a', 'b', ' '];
5200 let mut d = doc(&"ab ba a b ab ".repeat(8));
5201 d.set_find_query(Some(fq.clone()), 0);
5202 for step in 0..300 {
5203 let len = d.buffer().len();
5204 match next() % 3 {
5205 0 => {
5206 let at = (next() % u64::from(len + 1)) as u32;
5207 let n = 1 + next() % 3;
5208 let s: String = (0..n).map(|_| alphabet[(next() % 3) as usize]).collect();
5209 d.edit(vec![EditOp::insert(at, s)]).unwrap();
5210 }
5211 1 if len > 0 => {
5212 let a = (next() % u64::from(len)) as u32;
5213 let b = (a + 1 + (next() % 4) as u32).min(len);
5214 d.edit(vec![EditOp::delete(a..b)]).unwrap();
5215 }
5216 _ => {
5217 let a = (next() % u64::from(len + 1)) as u32;
5219 let b = (next() % u64::from(len + 1)) as u32;
5220 let (a, b) = (a.min(b), a.max(b));
5221 if a == b {
5222 d.edit(vec![EditOp::insert(a, "ab")]).unwrap();
5223 } else {
5224 d.edit(vec![EditOp::insert(a, "ba"), EditOp::insert(b, "ab")]).unwrap();
5225 }
5226 }
5227 }
5228 let (oracle, _) = crate::find::scan(&d.buffer().text(), &fq);
5229 assert_eq!(match_ranges(&d), oracle, "step {step} diverged from the oracle");
5230 }
5231 }
5232
5233 #[test]
5234 fn repair_self_overlapping_needle_is_maximal_and_valid() {
5235 let mut next = xorshift(0x9E37_79B9_7F4A_7C15);
5241 let alphabet = ['a', ' '];
5242 let mut d = doc(&"aaa a aaaa aa ".repeat(6));
5243 d.set_find_query(Some(query("aa")), 0);
5244 for step in 0..300 {
5245 let len = d.buffer().len();
5246 if next().is_multiple_of(2) || len == 0 {
5247 let at = (next() % u64::from(len + 1)) as u32;
5248 let n = 1 + next() % 3;
5249 let s: String = (0..n).map(|_| alphabet[(next() % 2) as usize]).collect();
5250 d.edit(vec![EditOp::insert(at, s)]).unwrap();
5251 } else {
5252 let a = (next() % u64::from(len)) as u32;
5253 let b = (a + 1 + (next() % 3) as u32).min(len);
5254 d.edit(vec![EditOp::delete(a..b)]).unwrap();
5255 }
5256 let live = match_ranges(&d);
5257 let text = d.buffer().text();
5258 let bytes = text.as_bytes();
5259 for (i, r) in live.iter().enumerate() {
5260 assert_eq!(
5261 &bytes[r.start as usize..r.end as usize],
5262 b"aa",
5263 "step {step}: stale match {r:?}"
5264 );
5265 if i > 0 {
5266 assert!(live[i - 1].end <= r.start, "step {step}: overlapping matches");
5267 }
5268 }
5269 for p in 0..bytes.len().saturating_sub(1) {
5270 if &bytes[p..p + 2] == b"aa" {
5271 let p = p as u32;
5272 assert!(
5273 live.iter().any(|r| r.start < p + 2 && p < r.end),
5274 "step {step}: placement at {p} misses every match (not maximal)"
5275 );
5276 }
5277 }
5278 }
5279 }
5280
5281 #[test]
5282 fn active_survives_untouched() {
5283 let mut d = doc("foo bar foo");
5286 d.set_find_query(Some(query("foo")), 0);
5287 d.find_next(0); let id = d.find.active_id().expect("a match is active");
5289 d.edit(vec![EditOp::insert(7, "x")]).unwrap(); assert_eq!(d.find.active_id(), Some(id), "untouched active keeps its id");
5291 assert_eq!((d.find_match_count(), d.active_find_match()), (2, Some(0)));
5292 }
5293
5294 #[test]
5295 fn active_transfers_same_start() {
5296 let mut d = doc("foo bar");
5299 d.set_find_query(Some(query("foo")), 0);
5300 d.find_next(0); let old = d.find.active_id().expect("a match is active");
5302 d.edit(vec![EditOp::insert(4, "x")]).unwrap(); let new = d.find.active_id().expect("active transferred");
5304 assert_ne!(new, old, "the decoration was re-minted by the repair");
5305 assert_eq!(d.active_find_match(), Some(0), "still the first match");
5306 assert_eq!(match_ranges(&d), vec![0..3]);
5307 }
5308
5309 #[test]
5310 fn active_cleared_when_destroyed() {
5311 let mut d = doc("foo bar foo");
5312 d.set_find_query(Some(query("foo")), 0);
5313 d.find_next(0); d.edit(vec![EditOp::delete(0..2)]).unwrap(); assert_eq!(d.find.active_id(), None, "destroyed active clears");
5316 assert_eq!((d.find_match_count(), d.active_find_match()), (1, None));
5317 }
5318
5319 #[test]
5320 fn count_and_order_consistent_after_repair() {
5321 let mut d = doc("foo foo foo");
5322 d.set_find_query(Some(query("foo")), 0);
5323 d.set_selections(SelectionSet::new(4));
5324 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();
5327 let ranges: Vec<Range<u32>> = all.iter().map(|(r, _)| r.clone()).collect();
5328 assert_eq!(ranges, vec![0..3, 4..7, 8..11, 12..15]);
5329 assert_eq!(d.find_match_count(), all.len());
5330 assert_eq!(d.active_find_match(), Some(2));
5332 assert_eq!(all.iter().position(|(_, active)| *active), Some(2));
5333 }
5334
5335 #[test]
5336 fn active_start_tracks_the_decoration_under_random_edits() {
5337 let base = "foo foo bar foo baz foo qux foo end";
5348 let mut rng = 0xDEAD_BEEF_0000_1111u64;
5349 let mut next = move || {
5350 rng ^= rng << 13;
5351 rng ^= rng >> 7;
5352 rng ^= rng << 17;
5353 rng
5354 };
5355 for trial in 0..400u32 {
5356 let mut d = doc(base);
5357 d.set_find_query(Some(query("foo")), 0);
5358 d.set_selections(SelectionSet::new(10));
5361 d.find_next(0);
5362 for _step in 0..14 {
5363 if d.find.active_id().is_none() {
5364 d.set_selections(SelectionSet::new(1));
5366 d.find_next(0);
5367 }
5368 let len = d.buffer().len();
5369 let at = match d.find.active_start() {
5372 Some(s) if next() % 3 == 0 => s,
5373 _ => (next() % u64::from(len + 1)) as u32,
5374 };
5375 match next() % 3 {
5376 0 => {
5377 let _ = d.edit(vec![EditOp::insert(at, "z")]);
5378 }
5379 1 => {
5380 let end = (at + 1 + (next() % 3) as u32).min(len);
5381 if end > at {
5382 let _ = d.edit(vec![EditOp::delete(at..end)]);
5383 }
5384 }
5385 _ => {
5386 let a = (next() % u64::from(len + 1)) as u32;
5388 let b = (next() % u64::from(len + 1)) as u32;
5389 let (lo, hi) = (a.min(b), a.max(b));
5390 if lo != hi {
5391 d.set_selections(SelectionSet::from_ranges(&[(lo, lo), (hi, hi)], 0));
5392 d.insert_text("q");
5393 }
5394 }
5395 }
5396 let want = d
5397 .find
5398 .active_id()
5399 .and_then(|id| d.decorations.decoration_range(id))
5400 .map(|r| r.start);
5401 assert_eq!(
5402 d.find.active_start(),
5403 want,
5404 "trial {trial}: active_start drifted from its decoration's live start",
5405 );
5406 }
5407 }
5408 }
5409
5410 #[test]
5411 fn capped_scan_keeps_a_document_prefix() {
5412 let text = "x ".repeat(crate::FIND_MATCH_CAP + 50);
5415 let mut d = doc(&text);
5416 d.set_find_query(Some(query("x")), 0);
5417 assert!(d.find.capped());
5418 let live = match_ranges(&d);
5419 assert_eq!(live.len(), crate::FIND_MATCH_CAP);
5420 assert_eq!(live.last().cloned(), Some(19_998..19_999), "prefix, not a sample");
5421 }
5422
5423 #[test]
5424 fn typing_while_capped_does_not_rescan_the_match_set() {
5425 let text = "x ".repeat(crate::FIND_MATCH_CAP + 50);
5432 let mut d = doc(&text);
5433 d.set_find_query(Some(query("x")), 0);
5434 assert!(d.find.capped());
5435 assert_eq!(d.find_match_count(), crate::FIND_MATCH_CAP);
5436 d.set_selections(crate::SelectionSet::new(0));
5437 crate::perf::reset();
5438 d.type_char('z'); let work = crate::perf::meter();
5440 assert_eq!(d.find_match_count(), crate::FIND_MATCH_CAP, "still capped");
5441 assert!(
5442 work < 2_000,
5443 "capped keystroke charged {work} work units (≈FIND_MATCH_CAP ⇒ the O(M) cap scan survived)"
5444 );
5445 }
5446
5447 #[test]
5448 fn capped_trims_tail_and_coverage() {
5449 let text = "x ".repeat(crate::FIND_MATCH_CAP);
5453 let mut d = doc(&text);
5454 d.set_find_query(Some(query("x")), 0);
5455 assert!(!d.find.capped());
5456 d.edit(vec![EditOp::insert(1_000, "xxx")]).unwrap();
5457 assert!(d.find.capped(), "over the cap after the repair");
5458 let live = match_ranges(&d);
5459 assert_eq!(live.len(), crate::FIND_MATCH_CAP, "trimmed back to the cap");
5460 assert!(live.windows(2).all(|w| w[0].end <= w[1].start), "still non-overlapping");
5461 assert_eq!(live.iter().filter(|r| (1_000..1_004).contains(&r.start)).count(), 4);
5463 assert_eq!(live.last().map(|r| r.start), Some(19_995), "tail trimmed");
5464 }
5465
5466 #[test]
5467 fn capped_refill_is_debounced() {
5468 let text = "x ".repeat(crate::FIND_MATCH_CAP + 50);
5469 let mut d = doc(&text);
5470 d.set_find_query(Some(query("x")), 0); d.edit(vec![EditOp::delete(0..10)]).unwrap();
5473 assert_eq!(d.find_match_count(), crate::FIND_MATCH_CAP - 5);
5474 assert!(!d.maybe_rescan_find(50), "inside the debounce window");
5475 assert_eq!(d.find_match_count(), crate::FIND_MATCH_CAP - 5);
5476 assert!(d.maybe_rescan_find(100), "past the window → refill");
5477 assert_eq!(d.find_match_count(), crate::FIND_MATCH_CAP, "coverage re-grew");
5478 assert!(!d.maybe_rescan_find(300), "idle after the refill: no re-scan");
5479 }
5480
5481 #[test]
5482 fn undo_redo_match_set_equals_fresh_scan() {
5483 let fq = query("ab");
5486 let mut d = doc("ab ab ba ab");
5487 d.set_find_query(Some(fq.clone()), 0);
5488 let check = |d: &Document, at: &str| {
5489 let (oracle, _) = crate::find::scan(&d.buffer().text(), &fq);
5490 assert_eq!(match_ranges(d), oracle, "diverged after {at}");
5491 };
5492 d.edit(vec![EditOp::insert(2, "ab")]).unwrap();
5493 check(&d, "edit 1");
5494 d.edit(vec![EditOp::delete(0..3)]).unwrap();
5495 check(&d, "edit 2");
5496 d.edit(vec![EditOp::insert(5, "b a")]).unwrap();
5497 check(&d, "edit 3");
5498 assert!(d.undo());
5499 check(&d, "undo 1");
5500 assert!(d.undo());
5501 check(&d, "undo 2");
5502 assert!(d.undo());
5503 check(&d, "undo 3");
5504 assert!(d.redo());
5505 check(&d, "redo 1");
5506 assert!(d.redo());
5507 check(&d, "redo 2");
5508 }
5509
5510 #[test]
5511 fn case_fold_repair() {
5512 let fq = crate::FindQuery { text: "foo".into(), case_sensitive: false, ..Default::default() };
5514 let mut d = doc("FO bar");
5515 d.set_find_query(Some(fq), 0);
5516 assert_eq!(d.find_match_count(), 0);
5517 d.edit(vec![EditOp::insert(2, "O")]).unwrap(); assert_eq!(match_ranges(&d), vec![0..3]);
5519 d.edit(vec![EditOp::insert(0, "f")]).unwrap(); assert_eq!(match_ranges(&d), vec![1..4], "re-anchored, still folded");
5521 }
5522}