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 #[must_use]
1783 pub fn caret_word_occurrences(&self, within: Range<u32>) -> Vec<Range<u32>> {
1784 let newest = self.selections.newest();
1785 if !newest.is_empty() {
1786 return Vec::new();
1787 }
1788 let Some((ws, we)) = movement::surrounding_word(&self.buffer, newest.head()) else {
1789 return Vec::new();
1790 };
1791 if ws == we {
1792 return Vec::new();
1793 }
1794 let word = self.buffer.slice(ws..we);
1795 let end = within.end.min(self.buffer.len());
1796 let base = within.start.min(end);
1797 let hay = self.buffer.slice(base..end);
1798 let mut out = Vec::new();
1799 let mut from = 0usize;
1800 while let Some(i) = hay[from..].find(&*word) {
1801 let start = base + (from + i) as u32;
1802 let match_end = start + word.len() as u32;
1803 if movement::surrounding_word(&self.buffer, start) == Some((start, match_end)) {
1805 out.push(start..match_end);
1806 }
1807 from = from + i + word.len();
1808 }
1809 out
1810 }
1811
1812 #[must_use]
1818 pub fn find_match_count(&self) -> usize {
1819 self.decorations.find_count()
1820 }
1821
1822 #[must_use]
1828 pub fn active_find_match(&self) -> Option<u32> {
1829 self.find.active_start().map(|s| self.decorations.find_rank_before(s) as u32)
1830 }
1831
1832 pub fn find_matches_in(&self, range: Range<u32>) -> impl Iterator<Item = (Range<u32>, bool)> + '_ {
1836 let active = self.find.active_id();
1837 self.decorations.decorations_in(range).filter_map(move |r| match &r.kind {
1838 DecorationKind::FindMatch if r.range.start < r.range.end => {
1839 Some((r.range.clone(), Some(r.id) == active))
1840 }
1841 _ => None,
1842 })
1843 }
1844
1845 pub fn overview_marks(
1856 &self,
1857 bounds: &[u32],
1858 sev_out: &mut Vec<(u8, u32)>,
1859 find_out: &mut Vec<Option<u32>>,
1860 ) {
1861 self.decorations.diagnostic_overview(bounds, sev_out);
1862 self.decorations.find_overview(bounds, find_out);
1863 }
1864
1865 pub fn maybe_rescan_find(&mut self, now_ms: u64) -> bool {
1873 self.find.maybe_rescan(&self.buffer, &mut self.decorations, now_ms)
1874 }
1875
1876 pub fn find_next(&mut self, now_ms: u64) -> Option<Range<u32>> {
1880 self.step_find(true, now_ms)
1881 }
1882
1883 pub fn find_prev(&mut self, now_ms: u64) -> Option<Range<u32>> {
1886 self.step_find(false, now_ms)
1887 }
1888
1889 fn step_find(&mut self, forward: bool, now_ms: u64) -> Option<Range<u32>> {
1891 self.find.maybe_rescan(&self.buffer, &mut self.decorations, now_ms);
1895 let sel = self.selections.newest();
1896 let (head, extent) = (sel.head(), sel.start()..sel.end());
1897 let found = if forward {
1898 self.find.find_next(head, extent, &self.decorations)
1899 } else {
1900 self.find.find_prev(head, extent, &self.decorations)
1901 };
1902 if let Some(range) = &found {
1903 self.selections
1905 .set_single(Selection::from_anchor(SelectionId(0), range.start, range.end));
1906 self.reset_transient(); self.unfold_to_reveal(range.start);
1910 self.unfold_to_reveal(range.end);
1911 self.request_reveal(RevealMode::Center); }
1913 found
1914 }
1915
1916 pub fn next_diagnostic(&mut self, forward: bool) -> Option<Range<u32>> {
1923 let sel = self.selections.newest();
1927 let anchor = (sel.start(), sel.end());
1928 let mut all: Vec<Range<u32>> = self.diagnostics_in(0..u32::MAX).map(|(r, ..)| r).collect();
1929 all.sort_by_key(|r| (r.start, r.end));
1930 let target = if forward {
1931 all.iter().find(|r| (r.start, r.end) > anchor).or_else(|| all.first())
1932 } else {
1933 all.iter().rev().find(|r| (r.start, r.end) < anchor).or_else(|| all.last())
1934 }?
1935 .clone();
1936 self.selections
1937 .set_single(Selection::from_anchor(SelectionId(0), target.start, target.end));
1938 self.reset_transient(); self.unfold_to_reveal(target.start);
1940 self.unfold_to_reveal(target.end);
1941 self.request_reveal(RevealMode::Center); Some(target)
1943 }
1944
1945 fn unfold_to_reveal(&mut self, offset: u32) -> bool {
1952 let tab = self.tab_size();
1953 let mut any = false;
1954 loop {
1955 let fm = FoldMap::new(&self.folds, &self.brackets, &self.buffer);
1956 let renders = fm.display_position(&self.buffer, offset, tab).is_some();
1962 let chip_hidden = fm.inline_fold_before(offset).is_some_and(|f| f.hides_caret_at(offset));
1965 if renders && !chip_hidden {
1966 return any;
1967 }
1968 let target = self
1971 .folds
1972 .iter()
1973 .filter_map(|open| self.brackets.foldable_partner(open).map(|c| (open, c)))
1974 .filter(|&(open, close)| open < offset && offset < close)
1975 .min_by_key(|&(open, close)| close - open);
1976 let Some((open, _)) = target else {
1977 return any; };
1979 self.folds.unfold(open);
1980 any = true;
1981 }
1982 }
1983
1984 #[must_use]
1988 pub fn reveal_seq(&self) -> u64 {
1989 self.reveal_seq
1990 }
1991}
1992
1993fn expand_folds_touched(
2002 folds: &mut FoldSet,
2003 brackets: &Brackets,
2004 buffer: &Buffer,
2005 tab: u32,
2006 committed: &Committed,
2007) {
2008 if folds.is_empty() || committed.patch().is_empty() {
2009 return;
2010 }
2011 let mut pts: Vec<u32> = Vec::with_capacity(committed.patch().edits().len() * 2);
2024 for e in committed.patch().edits() {
2025 pts.push(e.new.start);
2026 pts.push(e.new.end);
2027 }
2028 pts.sort_unstable();
2029 pts.dedup();
2030 let (first, last) = (pts[0], *pts.last().expect("edits() is non-empty here"));
2031 let lo = brackets
2032 .enclosing_or_touching(first)
2033 .into_iter()
2034 .filter(|&(o, _)| folds.is_folded(o))
2035 .map(|(o, _)| o)
2036 .min()
2037 .unwrap_or(first);
2038 let candidates: Vec<(u32, u32)> = folds
2042 .openers_in(lo..last.saturating_add(1))
2043 .iter()
2044 .filter_map(|&o| {
2045 let c = brackets.foldable_partner(o)?;
2046 let touches =
2047 pts.partition_point(|&p| p < o) < pts.partition_point(|&p| p <= c.saturating_add(1));
2048 touches.then_some((o, c))
2049 })
2050 .collect();
2051 if candidates.is_empty() {
2052 return;
2053 }
2054 let mut sub = crate::fold_map::FoldSet::new();
2055 for &(open, _) in &candidates {
2056 sub.fold(open);
2057 }
2058 let fold_map = crate::fold_map::FoldMap::new(&sub, brackets, buffer);
2059 let any_hidden = pts.iter().any(|&p| fold_map.display_position(buffer, p, tab).is_none());
2065 let mut starts: Vec<u32> = committed.patch().edits().iter().map(|e| e.new.start).collect();
2068 starts.sort_unstable();
2069 let touched: Vec<u32> = candidates
2070 .iter()
2071 .filter(|&&(opener, close)| {
2072 let single = buffer.offset_to_point(opener).row == buffer.offset_to_point(close).row;
2073 if single {
2074 starts.partition_point(|&s| s <= close) > starts.partition_point(|&s| s <= opener)
2077 } else {
2078 any_hidden
2082 }
2083 })
2084 .map(|&(opener, _)| opener)
2085 .collect();
2086 for opener in touched {
2087 folds.unfold(opener);
2088 }
2089}
2090
2091struct Views<'a> {
2097 highlight: &'a mut Option<HighlightCache>,
2098 brackets: &'a mut Brackets,
2099 decorations: &'a mut DecorationStore,
2100 autoclose: &'a mut DecorationStore,
2104 folds: &'a mut FoldSet,
2105 find: &'a mut FindState,
2106}
2107
2108fn rebase_views(
2119 views: &mut Views,
2120 buffer: &Buffer,
2121 tab: u32,
2122 committed: &Committed,
2123) -> core::ops::Range<u32> {
2124 if let Some(cache) = views.highlight.as_mut() {
2130 let edits = committed.patch().edits();
2131 if !edits.is_empty() {
2132 let mut spans: Vec<(u32, u32, u32)> = Vec::with_capacity(edits.len());
2140 let mut acc: i64 = 0;
2141 for (e, inv) in edits.iter().zip(committed.inverse_ops()) {
2142 let post_sr = buffer.offset_to_point(e.new.start).row;
2143 let post_er = buffer.offset_to_point(e.new.end).row;
2144 let new_lines = post_er - post_sr + 1;
2145 let old_lines = inv.text.bytes().filter(|&b| b == b'\n').count() as u32 + 1;
2146 let pre_start = (i64::from(post_sr) - acc) as u32;
2147 acc += i64::from(new_lines) - i64::from(old_lines);
2148 match spans.last_mut() {
2149 Some(last) if pre_start < last.0 + last.1 => {
2152 let merged_end = (last.0 + last.1).max(pre_start + old_lines);
2153 let combined_delta = (i64::from(last.2) - i64::from(last.1))
2154 + (i64::from(new_lines) - i64::from(old_lines));
2155 last.1 = merged_end - last.0;
2156 last.2 = (i64::from(last.1) + combined_delta) as u32;
2157 }
2158 _ => spans.push((pre_start, old_lines, new_lines)),
2159 }
2160 }
2161 debug_assert_eq!(
2166 spans.iter().map(|&(_, o, n)| i64::from(n) - i64::from(o)).sum::<i64>(),
2167 i64::from(buffer.line_count()) - i64::from(cache.line_count()),
2168 "per-edit line deltas must sum to the buffer's line-count change",
2169 );
2170 cache.on_commit_patch(&spans);
2171 }
2172 }
2173 let reconcile_region = views.brackets.apply_edit(committed.patch(), buffer);
2178 views.decorations.apply_patch(committed.patch());
2182 views.autoclose.apply_patch(committed.patch());
2189 views.find.on_commit(committed.patch(), buffer, views.decorations);
2194 views.folds.apply_patch(committed.patch());
2199 expand_folds_touched(views.folds, views.brackets, buffer, tab, committed);
2207 reconcile_region
2211}
2212
2213fn extend_by_unit(origin_unit: (u32, u32), head_unit: (u32, u32), origin: u32, head: u32) -> (u32, u32) {
2218 let (os, oe) = origin_unit;
2219 let (hs, he) = head_unit;
2220 if head >= origin {
2221 (os, he.max(oe))
2222 } else {
2223 (oe, hs.min(os))
2224 }
2225}
2226
2227fn scan_from(
2238 buffer: &Buffer,
2239 needle: &str,
2240 lo: u32,
2241 hi: u32,
2242 is_taken: &impl Fn(u32) -> bool,
2243) -> Option<(u32, u32)> {
2244 let k = needle.len() as u64;
2245 if k == 0 || lo >= hi {
2246 return None;
2247 }
2248 let len = buffer.len();
2249 let window = u64::from(crate::buffer::SCAN_WINDOW).max(k * 2); let mut pos = lo;
2251 while u64::from(pos) + k <= u64::from(len) {
2252 let win_end = buffer
2253 .clip_offset((u64::from(pos) + window).min(u64::from(len)) as u32, Bias::Right);
2254 let slice = buffer.slice(pos..win_end);
2255 let mut last_end: Option<u32> = None;
2256 for (i, _) in slice.match_indices(needle) {
2257 let start = pos + i as u32;
2258 if u64::from(start) >= u64::from(hi) {
2259 return None; }
2261 last_end = Some(start + k as u32);
2262 if !is_taken(start) {
2263 return Some((start, start + k as u32));
2264 }
2265 }
2266 if win_end >= len {
2267 break;
2268 }
2269 pos = buffer.scan_resume(pos, win_end, k as u32, last_end);
2272 }
2273 None
2274}
2275
2276#[cfg(test)]
2283fn find_next_occurrence(text: &str, needle: &str, from: u32, taken: &[(u32, u32)]) -> Option<(u32, u32)> {
2284 if needle.is_empty() {
2285 return None;
2286 }
2287 let len = needle.len() as u32;
2288 let is_taken = |start: u32| taken.iter().any(|&(s, e)| s == start && e == start + len);
2289 let mut wrapped: Option<u32> = None;
2290 for (idx, _) in text.match_indices(needle) {
2291 let start = idx as u32;
2292 if is_taken(start) {
2293 continue;
2294 }
2295 if start >= from {
2296 return Some((start, start + len));
2297 }
2298 if wrapped.is_none() {
2299 wrapped = Some(start); }
2301 }
2302 wrapped.map(|start| (start, start + len))
2303}
2304
2305#[cfg(test)]
2306mod tests {
2307 use super::*;
2308 use crate::history::{GroupingHint, OpClass};
2309
2310 fn doc(s: &str) -> Document {
2311 Document::new(s).unwrap()
2312 }
2313
2314 fn typ(op: OpClass) -> GroupingHint {
2315 GroupingHint::mergeable(op)
2316 }
2317
2318 fn assert_brackets_fresh(d: &Document) {
2321 let fresh = crate::bracket::Brackets::match_text(&d.text());
2322 assert_eq!(d.brackets().all(), fresh.all(), "incremental brackets diverged from scratch");
2323 }
2324
2325 #[test]
2326 fn brackets_ride_edits_and_undo_redo_through_the_one_mover() {
2327 let mut d = doc("fn a() {\n x[0] = (1 + 2);\n}\n");
2333 let mut rng = 0xC0FFEEu64;
2334 let mut next = move |m: u64| {
2335 rng ^= rng << 13;
2336 rng ^= rng >> 7;
2337 rng ^= rng << 17;
2338 rng % m
2339 };
2340 let inserts = ["{", "}", "(", ")", "[", "]", "\n", "x", "{}", "([)", "\n}\n"];
2341 for i in 0..200 {
2342 match next(4) {
2343 0 | 1 => {
2344 let at = next(d.buffer().len() as u64 + 1) as u32;
2345 let at = d.buffer().clip_offset(at, crate::Bias::Left);
2346 let text = inserts[next(inserts.len() as u64) as usize];
2347 let _ = d.edit(vec![EditOp::insert(at, text)]);
2348 }
2349 2 => {
2350 let a = next(d.buffer().len() as u64 + 1) as u32;
2351 let a = d.buffer().clip_offset(a, crate::Bias::Left);
2352 let b = (a + 1 + next(4) as u32).min(d.buffer().len());
2353 let b = d.buffer().clip_offset(b, crate::Bias::Right);
2354 if a < b {
2355 let _ = d.edit(vec![EditOp::delete(a..b)]);
2356 }
2357 }
2358 _ => {
2359 if next(2) == 0 {
2360 d.undo();
2361 } else {
2362 d.redo();
2363 }
2364 }
2365 }
2366 assert_brackets_fresh(&d);
2367 let _ = i;
2368 }
2369 }
2370
2371 #[test]
2372 fn edit_undo_redo_round_trip() {
2373 let mut d = doc("hello");
2374 d.edit(vec![EditOp::new(0..5, "world")]).unwrap();
2375 assert_eq!(d.text(), "world");
2376 assert!(d.undo());
2377 assert_eq!(d.text(), "hello");
2378 assert!(d.redo());
2379 assert_eq!(d.text(), "world");
2380 assert!(d.undo());
2382 assert!(!d.undo()); assert_eq!(d.text(), "hello");
2384 }
2385
2386 #[test]
2387 fn a_new_edit_invalidates_redo() {
2388 let mut d = doc("");
2389 d.edit(vec![EditOp::insert(0, "a")]).unwrap();
2390 d.edit(vec![EditOp::insert(1, "b")]).unwrap(); d.undo(); d.edit(vec![EditOp::insert(1, "X")]).unwrap(); assert_eq!(d.text(), "aX");
2394 assert!(!d.redo(), "redo must be cleared by the divergent edit");
2395 assert_eq!(d.text(), "aX");
2396 }
2397
2398 #[test]
2399 fn a_retained_branch_is_reachable_from_the_fork() {
2400 let mut d = doc("");
2407 d.edit(vec![EditOp::insert(0, "a")]).unwrap();
2408 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");
2412 assert!(d.undo());
2414 assert_eq!(d.text(), "a");
2415 assert_eq!(d.redo_branch_count(), 2, "the 'b' and 'X' branches both survive");
2416 assert!(d.can_redo());
2417 assert!(d.select_redo_branch(0));
2419 assert!(d.redo());
2420 assert_eq!(d.text(), "ab", "the retained 'b' branch is fully reachable");
2421 assert!(d.undo());
2423 assert!(d.select_redo_branch(1));
2424 assert!(d.redo());
2425 assert_eq!(d.text(), "aX");
2426 }
2427
2428 #[test]
2429 fn opt_in_undo_limit_bounds_reach_and_keeps_recent() {
2430 let mut d = doc("");
2434 d.set_undo_limit(Some(3));
2435 for (i, ch) in "abcde".chars().enumerate() {
2436 d.edit(vec![EditOp::insert(i as u32, ch.to_string())]).unwrap();
2437 }
2438 assert_eq!(d.text(), "abcde");
2439 assert_eq!(d.undo_depth(), 3, "only the last 3 units are retained");
2440 assert!(d.undo()); assert!(d.undo()); assert!(d.undo()); assert_eq!(d.text(), "ab");
2444 assert!(!d.undo(), "the pruned units ('a','b' inserts) are gone");
2445 assert!(!d.can_undo());
2446 assert!(d.redo());
2448 assert_eq!(d.text(), "abc");
2449 }
2450
2451 #[test]
2452 fn pruning_preserves_branches_within_the_window() {
2453 let mut d = doc("");
2456 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));
2462 assert_eq!(d.undo_depth(), 1);
2463 assert!(d.undo()); assert_eq!(d.text(), "a");
2465 assert_eq!(d.redo_branch_count(), 2, "both branches survived the rebuild");
2466 assert!(d.select_redo_branch(0));
2467 assert!(d.redo());
2468 assert_eq!(d.text(), "ab", "the retained branch is still reachable after a prune");
2469 }
2470
2471 #[test]
2472 fn typing_run_merges_into_one_undo_step() {
2473 let mut d = doc("");
2475 for (i, ch) in "hello".chars().enumerate() {
2476 d.edit_grouped(vec![EditOp::insert(i as u32, ch.to_string())], typ(OpClass::Type))
2477 .unwrap();
2478 }
2479 assert_eq!(d.text(), "hello");
2480 assert!(d.undo());
2481 assert_eq!(d.text(), "", "the whole typing run undoes as one unit");
2482 assert!(d.redo());
2483 assert_eq!(d.text(), "hello", "and redoes as one unit");
2484 }
2485
2486 #[test]
2487 fn fold_toggle_seals_undo_and_clears_the_expand_ladder() {
2488 let mut d = doc("m {\n word\n}");
2492 d.set_selections(SelectionSet::new(7)); d.type_char('x');
2494 d.expand_selection(); assert!(d.toggle_fold_opener(2));
2496 d.shrink_selection(); let head = d.selections().newest().head();
2498 let fm = crate::fold_map::FoldMap::new(d.folds(), d.brackets(), d.buffer());
2499 assert!(
2500 fm.display_position(d.buffer(), head, d.tab_size()).is_some(),
2501 "…so the caret cannot be restored into the collapsed fold"
2502 );
2503 d.type_char('y');
2505 assert!(d.undo());
2506 assert!(d.text().contains('x'), "undo reverted only the post-toggle typing");
2507 }
2508
2509 #[test]
2510 fn find_navigation_expands_a_collapsed_inline_fold() {
2511 let mut d = doc("x = [1, 2, 3]\nafter");
2516 assert!(d.toggle_fold_opener(4)); d.set_find_query(Some(FindQuery { text: "2".into(), case_sensitive: false }), 0);
2518 d.find_next(0).expect("the match exists");
2519 assert!(!d.folds().is_folded(4), "the inline fold expanded to show the match");
2520 }
2521
2522 #[test]
2523 fn editing_hidden_text_expands_only_the_enclosing_fold() {
2524 let text = "a {\n keep\n}\nb {\n body\n}\nc\n";
2529 let mut d = doc(text);
2530 let opens: Vec<u32> = text.match_indices('{').map(|(i, _)| i as u32).collect();
2531 let (keep, edited) = (opens[0], opens[1]);
2534 assert!(d.toggle_fold_opener(keep));
2535 assert!(d.toggle_fold_opener(edited));
2536 assert!(d.folds().is_folded(keep) && d.folds().is_folded(edited));
2537 let inside = text.find("body").unwrap() as u32 + 2;
2539 d.edit(vec![EditOp::insert(inside, "X")]).unwrap();
2540 assert!(!d.folds().is_folded(edited), "editing inside the second block expands it");
2541 assert!(d.folds().is_folded(keep), "the untouched first block stays folded");
2542 }
2543
2544 #[test]
2545 fn next_diagnostic_advances_past_a_shared_start_offset() {
2546 use crate::{Diagnostic, Severity};
2547 let mut d = doc("abcdef");
2550 let rev = d.revision();
2551 d.set_diagnostics(
2552 rev,
2553 vec![
2554 Diagnostic::new(1..3, Severity::Error, "a"),
2555 Diagnostic::new(1..5, Severity::Warning, "b"),
2556 ],
2557 );
2558 assert_eq!(d.next_diagnostic(true), Some(1..3));
2559 assert_eq!(d.next_diagnostic(true), Some(1..5), "the same-start sibling is reachable");
2560 assert_eq!(d.next_diagnostic(true), Some(1..3), "and the walk wraps");
2561 }
2562
2563 #[test]
2564 fn reveal_classes_and_no_op_verbs() {
2565 use crate::{Diagnostic, Severity};
2566 let mut d = doc("f( ab )");
2570 let seq0 = d.reveal_seq();
2571 d.next_diagnostic(true); assert_eq!(d.reveal_seq(), seq0, "a no-op F8 requests no reveal");
2573 d.set_selections(SelectionSet::new(3));
2574 d.jump_to_bracket(); assert!(d.reveal_seq() > seq0, "a real bracket hop requests a reveal");
2576 assert_eq!(d.reveal_mode(), RevealMode::Fit, "…of the Fit class");
2577 let rev = d.revision();
2578 d.set_diagnostics(rev, vec![Diagnostic::new(1..2, Severity::Error, "e")]);
2579 let seq1 = d.reveal_seq();
2580 d.next_diagnostic(true);
2581 assert!(d.reveal_seq() > seq1);
2582 assert_eq!(d.reveal_mode(), RevealMode::Center, "a diagnostic jump centers");
2583 }
2584
2585 #[test]
2586 fn ctrl_d_force_reveals_while_select_all_holds() {
2587 let mut d = doc("foo bar foo baz foo");
2590 d.set_selections(SelectionSet::new(0));
2591 d.add_next_occurrence(); d.add_next_occurrence(); assert_eq!(d.reveal_mode(), RevealMode::FitForce, "Ctrl+D force-reveals the new cursor");
2594 let mut e = doc("foo bar foo baz foo");
2595 e.set_selections(SelectionSet::new(0));
2596 e.select_all_occurrences();
2597 assert_eq!(e.reveal_mode(), RevealMode::Fit, "Ctrl+Shift+L holds if a cursor is visible");
2598 }
2599
2600 #[test]
2601 fn diagnostic_navigation_wraps_selects_and_reveals() {
2602 use crate::{Diagnostic, Severity};
2603 let mut d = doc("aa bb\ncc\ndd");
2604 let rev = d.revision();
2605 d.set_diagnostics(
2606 rev,
2607 vec![
2608 Diagnostic::new(3..5, Severity::Warning, "w"),
2609 Diagnostic::new(9..11, Severity::Error, "e"),
2610 ],
2611 );
2612 assert_eq!(d.next_diagnostic(true), Some(3..5));
2613 assert_eq!(d.next_diagnostic(true), Some(9..11));
2614 assert_eq!(d.next_diagnostic(true), Some(3..5), "wraps to the first");
2615 assert_eq!(d.next_diagnostic(false), Some(9..11), "prev wraps to the last");
2616 let s = d.selections().newest();
2617 assert_eq!((s.start(), s.end()), (9, 11), "the diagnostic's span is selected");
2618 let mut e = doc("x");
2620 e.set_selections(SelectionSet::new(1));
2621 assert!(e.next_diagnostic(true).is_none());
2622 assert_eq!(e.selections().newest().head(), 1);
2623 let mut f = doc("aa\nfn {\nbb\n}\ncc");
2625 assert!(f.toggle_fold_opener(6));
2626 let rev = f.revision();
2627 f.set_diagnostics(rev, vec![Diagnostic::new(8..10, Severity::Error, "hidden")]);
2628 assert_eq!(f.next_diagnostic(true), Some(8..10));
2629 assert!(!f.folds().is_folded(6), "the fold expanded to show the diagnostic");
2630 }
2631
2632 #[test]
2633 fn caret_word_occurrences_are_whole_word_only() {
2634 let mut d = doc("foo foobar foo\nfoo");
2635 d.set_selections(SelectionSet::new(1)); assert_eq!(
2637 d.caret_word_occurrences(0..u32::MAX),
2638 vec![0..3, 11..14, 15..18],
2639 "the foobar prefix is not a whole-word match"
2640 );
2641 assert_eq!(d.caret_word_occurrences(4..15), vec![11..14], "only in-window matches");
2643 let mut set = SelectionSet::new(0);
2645 set.set_single(Selection::from_anchor(SelectionId(0), 0, 3));
2646 d.set_selections(set);
2647 assert!(d.caret_word_occurrences(0..u32::MAX).is_empty());
2648 let mut w = doc("a b");
2650 w.set_selections(SelectionSet::new(2));
2651 assert!(w.caret_word_occurrences(0..u32::MAX).is_empty());
2652 }
2653
2654 #[test]
2655 fn find_navigation_expands_a_collapsed_fold_to_reveal_the_match() {
2656 let mut d = doc("aa\nfn {\nneedle\n}\nbb");
2659 let opener = 6; assert!(d.toggle_fold_opener(opener));
2661 d.set_find_query(Some(FindQuery { text: "needle".into(), case_sensitive: false }), 0);
2662 let m = d.find_next(0).expect("the match exists");
2663 assert!(!d.folds().is_folded(opener), "the fold expanded to reveal the match");
2664 let fm = crate::fold_map::FoldMap::new(d.folds(), d.brackets(), d.buffer());
2665 assert!(
2666 fm.display_position(d.buffer(), m.end, d.tab_size()).is_some(),
2667 "the match head renders"
2668 );
2669 let mut v = doc("aa\nfn {\nx\n}\nbb");
2671 assert!(v.toggle_fold_opener(6));
2672 v.set_find_query(Some(FindQuery { text: "fn".into(), case_sensitive: false }), 0);
2673 v.find_next(0).expect("match on the header");
2674 assert!(v.folds().is_folded(6), "a visible match keeps the fold collapsed");
2675 }
2676
2677 #[test]
2678 fn jump_to_bracket_crosses_and_returns() {
2679 let mut d = doc("f( ab )");
2682 d.set_selections(SelectionSet::new(2));
2683 d.jump_to_bracket();
2684 assert_eq!(d.selections().newest().head(), 7);
2685 d.jump_to_bracket();
2686 assert_eq!(d.selections().newest().head(), 2);
2687 d.set_selections(SelectionSet::new(4)); d.jump_to_bracket();
2690 assert_eq!(d.selections().newest().head(), 6, "lands before `)`");
2691 let mut e = doc("plain");
2693 e.set_selections(SelectionSet::new(3));
2694 e.jump_to_bracket();
2695 assert_eq!(e.selections().newest().head(), 3);
2696 }
2697
2698 #[test]
2699 fn expand_selection_climbs_the_bracket_ladder_and_shrinks_back() {
2700 let mut d = doc("m { a(bb) }");
2702 d.set_selections(SelectionSet::new(7)); let sel = |d: &Document| (d.selections().newest().start(), d.selections().newest().end());
2704 d.expand_selection();
2705 assert_eq!(sel(&d), (6, 8), "word first");
2706 d.expand_selection();
2707 assert_eq!(sel(&d), (5, 9), "word == () contents, so the pair incl. brackets");
2708 d.expand_selection();
2709 assert_eq!(sel(&d), (3, 10), "the brace contents");
2710 d.expand_selection();
2711 assert_eq!(sel(&d), (2, 11), "the brace pair incl. brackets");
2712 d.expand_selection();
2713 assert_eq!(sel(&d), (0, 11), "the whole document");
2714 d.expand_selection();
2715 assert_eq!(sel(&d), (0, 11), "fully expanded is a no-op");
2716 d.shrink_selection();
2718 assert_eq!(sel(&d), (2, 11));
2719 d.shrink_selection();
2720 assert_eq!(sel(&d), (3, 10));
2721 d.move_carets(Motion::Right, false);
2723 let caret = sel(&d);
2724 d.shrink_selection();
2725 assert_eq!(sel(&d), caret, "the ladder cleared on the caret move");
2726 }
2727
2728 #[test]
2729 fn add_caret_vertical_stacks_and_stops_at_edges() {
2730 let mut d = doc("aaa\nbbb\nccc");
2733 d.set_selections(SelectionSet::new(5)); d.add_caret_vertical(false); d.add_caret_vertical(true); assert_eq!(d.selections().len(), 3);
2737 d.type_char('X');
2738 assert_eq!(d.text(), "aXaa\nbXbb\ncXcc");
2739 let mut e = doc("aa\nbb");
2741 e.set_selections(SelectionSet::new(1));
2742 e.add_caret_vertical(false);
2743 assert_eq!(e.selections().len(), 1);
2744 }
2745
2746 #[test]
2747 fn add_caret_vertical_skips_a_collapsed_fold() {
2748 let mut d = doc("aa\nfn {\nbb\ncc\n}\ndd");
2749 assert!(d.toggle_fold_opener(6)); d.set_selections(SelectionSet::new(16)); d.add_caret_vertical(false);
2752 let rows: Vec<u32> =
2753 d.selections().all().iter().map(|s| d.buffer().offset_to_point(s.head()).row).collect();
2754 assert_eq!(rows, vec![1, 5], "the new caret lands on the fold header, not a hidden row");
2755 }
2756
2757 #[test]
2758 fn select_all_occurrences_takes_every_match_from_a_word_seed() {
2759 let mut d = doc("foo bar foo baz foo");
2762 d.set_selections(SelectionSet::new(1));
2763 d.select_all_occurrences();
2764 assert_eq!(d.selections().len(), 3);
2765 d.type_char('X');
2766 assert_eq!(d.text(), "X bar X baz X");
2767 let mut e = doc(" ");
2769 e.set_selections(SelectionSet::new(1));
2770 e.select_all_occurrences();
2771 assert_eq!(e.selections().len(), 1);
2772 assert!(e.selections().newest().is_empty());
2773 }
2774
2775 #[test]
2776 fn select_find_matches_turns_matches_into_selections() {
2777 let mut d = doc("foo bar foo baz foo");
2778 d.set_find_query(Some(FindQuery { text: "foo".into(), case_sensitive: false }), 0);
2779 d.find_next(0); assert!(d.select_find_matches());
2781 assert_eq!(d.selections().len(), 3);
2782 assert_eq!(d.selections().newest().start(), 0, "the ACTIVE match is newest");
2783 let mut e = doc("abc");
2785 assert!(!e.select_find_matches());
2786 assert_eq!(e.selections().len(), 1);
2787 }
2788
2789 #[test]
2790 fn caret_moves_seal_the_typing_group() {
2791 let mut d = doc("");
2794 d.type_char('f');
2795 d.type_char('o');
2796 d.move_carets(Motion::Left, false); d.type_char('x'); assert!(d.undo());
2799 assert_eq!(d.text(), "fo", "undo reverts only the run typed after the move");
2800
2801 let mut c = doc("");
2803 c.type_char('a');
2804 c.type_char('b');
2805 c.set_selections(SelectionSet::new(0));
2806 c.type_char('z'); assert!(c.undo());
2808 assert_eq!(c.text(), "ab", "the click sealed the run before it");
2809
2810 let mut b = doc("one\ntwo");
2813 b.set_selections(SelectionSet::new(0));
2814 b.type_char('z'); b.column_drag((0, 2), (1, 2));
2816 b.type_char('q');
2817 assert!(b.undo());
2818 assert_eq!(b.text(), "zone\ntwo", "the box gesture sealed the run before it");
2819 }
2820
2821 #[test]
2826 fn line_duplication_drops_autoclose_provenance() {
2827 let ac_count = |d: &Document| d.autoclose_pair_count();
2829 let mut d = doc("x");
2830 d.move_carets(Motion::Right, false); d.type_char('('); assert_eq!(d.text(), "x()");
2833 assert_eq!(ac_count(&d), 1, "typing '(' arms one AutoClosePair decoration");
2834 d.copy_line(false);
2838 assert_eq!(d.text(), "x()\nx()");
2839 assert_eq!(
2840 ac_count(&d),
2841 0,
2842 "copy_line must clear the provenance decoration, not orphan it in the store",
2843 );
2844 }
2845
2846 #[test]
2852 fn type_bracket_then_far_diagnostics_pair_survives() {
2853 use crate::{Diagnostic, Severity};
2854 let mut d = doc("hello\nworld\n");
2855 let end = d.text().len() as u32; d.set_selections(SelectionSet::new(end));
2857 d.type_char('('); assert_eq!(d.autoclose_pair_count(), 1);
2859 d.set_diagnostics(
2861 d.revision(),
2862 vec![
2863 Diagnostic::new(0..1, Severity::Error, "e0"),
2864 Diagnostic::new(2..3, Severity::Warning, "w0"),
2865 ],
2866 );
2867 d.type_char('x');
2870 assert!(d.text().ends_with("(x)"));
2871 assert_eq!(d.autoclose_pair_count(), 1, "far diagnostics don't disturb the pair");
2872 assert_eq!(d.diagnostics_in(0..u32::MAX).count(), 2, "the diagnostics ride on");
2874 d.type_char(')');
2876 assert!(d.text().ends_with("(x)"), "overtype consumes the tracked close, no doubled )");
2877 assert_eq!(d.autoclose_pair_count(), 0, "overtype consumed the pair");
2878 }
2879
2880 #[test]
2891 fn autoclose_store_moves_identically_to_unified_store() {
2892 use crate::decorations::{DecorationKind, Stickiness};
2893 use crate::{Diagnostic, Severity};
2894 let scenario = |text: &str, carets: &[u32], steps: usize| {
2898 let mut d = doc(text);
2899 d.set_selections(SelectionSet::from_offsets(carets));
2900 d.type_char('('); let pairs = d.autoclose_ranges();
2902 assert_eq!(pairs.len(), carets.len(), "one pair per caret armed");
2903 for r in &pairs {
2906 d.decorations_mut().add_decoration(
2907 r.clone(),
2908 DecorationKind::AutoClosePair,
2909 Stickiness::AlwaysGrows,
2910 );
2911 }
2912 d.set_diagnostics(d.revision(), vec![Diagnostic::new(0..1, Severity::Warning, "d")]);
2913 let unified = |d: &Document| -> Vec<Range<u32>> {
2914 d.decorations()
2915 .iter()
2916 .filter(|r| matches!(r.kind, DecorationKind::AutoClosePair))
2917 .map(|r| r.range.clone())
2918 .collect()
2919 };
2920 assert_eq!(d.autoclose_ranges(), unified(&d), "shadow starts equal");
2921 for step in 0..steps {
2922 d.type_char('z'); assert_eq!(
2924 d.autoclose_ranges(),
2925 unified(&d),
2926 "own store diverged from the unified store at step {step}",
2927 );
2928 }
2929 };
2930 scenario("aa\nbb\ncc\ndd\nee\n", &[2, 5, 8, 11, 14], 8);
2932 scenario("solo line here\n", &[14], 10);
2934 }
2935
2936 #[test]
2937 fn different_classes_do_not_merge() {
2938 let mut d = doc("ab");
2939 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");
2942 assert!(d.undo()); assert_eq!(d.text(), "abc");
2944 assert!(d.undo()); assert_eq!(d.text(), "ab");
2946 }
2947
2948 #[test]
2949 fn dirty_tracking_survives_undo_and_save() {
2950 let mut d = doc("x");
2951 assert!(!d.is_dirty());
2952 d.edit(vec![EditOp::insert(1, "y")]).unwrap(); assert!(d.is_dirty());
2954 d.undo(); assert!(!d.is_dirty(), "undo back to the save point reads clean");
2956 d.redo(); assert!(d.is_dirty());
2958 d.mark_saved();
2959 assert!(!d.is_dirty());
2960 }
2961
2962 #[test]
2963 fn ctrl_d_selects_word_then_adds_occurrences_then_stops() {
2964 let mut d = doc("foo bar foo baz foo"); d.add_next_occurrence();
2967 assert_eq!(d.selections().len(), 1);
2968 let s = d.selections().newest();
2969 assert_eq!((s.start(), s.end()), (0, 3));
2970 d.add_next_occurrence();
2972 assert_eq!(d.selections().len(), 2);
2973 assert_eq!((d.selections().newest().start(), d.selections().newest().end()), (8, 11));
2974 d.add_next_occurrence();
2975 assert_eq!(d.selections().len(), 3);
2976 assert_eq!(d.selections().newest().start(), 16);
2977 d.add_next_occurrence();
2979 assert_eq!(d.selections().len(), 3, "no untaken occurrence remains");
2980 }
2981
2982 #[test]
2983 fn windowed_next_occurrence_equals_whole_text_scan() {
2984 let mut state: u64 = 0x243F_6A88_85A3_08D3;
2989 let mut next = |n: u32| {
2990 state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
2991 ((state >> 33) as u32) % n
2992 };
2993 let needles = ["ab", "ba", "abc", "cab"];
2995 for _ in 0..300 {
2996 let n = 12 + next(60); let text: String = (0..n)
2998 .map(|_| [b'a', b'b', b'c', b' '][next(4) as usize] as char)
2999 .collect();
3000 let d = doc(&text);
3001 let needle = needles[next(needles.len() as u32) as usize];
3002 let nlen = needle.len() as u32;
3003 let all: Vec<(u32, u32)> = text
3005 .match_indices(needle)
3006 .map(|(i, _)| (i as u32, i as u32 + nlen))
3007 .collect();
3008 let taken: Vec<(u32, u32)> = all.iter().copied().filter(|_| next(2) == 0).collect();
3009 let is_taken = |s: u32| taken.iter().any(|&(st, en)| st == s && en == s + nlen);
3010 let from = next(n + 1);
3011 let got = scan_from(d.buffer(), needle, from, d.buffer().len(), &is_taken)
3012 .or_else(|| scan_from(d.buffer(), needle, 0, from, &is_taken));
3013 let want = find_next_occurrence(&text, needle, from, &taken);
3014 assert_eq!(got, want, "text {text:?} needle {needle:?} from {from} taken {taken:?}");
3015 }
3016 }
3017
3018 #[test]
3019 fn column_select_grows_widens_and_shrinks_back() {
3020 use crate::movement::ColumnDir::{Down, Left, Right, Up};
3021 let mut d = doc("abcd\nabcd\nabcd");
3023 d.set_selections(SelectionSet::new(1));
3024 d.column_select(Down);
3026 d.column_select(Down);
3027 assert_eq!(d.selections().len(), 3);
3028 assert!(d.selections().all().iter().all(|s| s.is_empty() && s.start() % 5 == 1));
3029 d.column_select(Right);
3031 d.column_select(Right);
3032 assert_eq!(d.selections().len(), 3);
3033 assert!(d.selections().all().iter().all(|s| s.end() - s.start() == 2));
3034 d.column_select(Up);
3036 d.column_select(Up);
3037 assert_eq!(d.selections().len(), 1, "shrinks back toward the anchor");
3038 d.column_select(Left);
3040 d.column_select(Left);
3041 assert_eq!(d.selections().len(), 1);
3042 assert!(d.selections().newest().is_empty());
3043 }
3044
3045 #[test]
3046 fn column_select_clamps_each_row_to_its_length() {
3047 use crate::movement::ColumnDir::{Down, Right};
3048 let mut d = doc("abcdef\nab"); d.set_selections(SelectionSet::new(0)); d.column_select(Down); for _ in 0..4 {
3053 d.column_select(Right); }
3055 let rows = d.selections().all();
3056 assert_eq!(rows.len(), 2);
3057 assert_eq!((rows[0].start(), rows[0].end()), (0, 4)); assert_eq!((rows[1].start(), rows[1].end()), (7, 9));
3060 }
3061
3062 #[test]
3063 fn column_drag_builds_a_box_from_two_corners() {
3064 let mut d = doc("abcd\nabcd\nabcd");
3065 d.column_drag((0, 1), (2, 3)); assert_eq!(d.selections().len(), 3);
3067 assert!(d.selections().all().iter().all(|s| s.end() - s.start() == 2));
3068 let mut r = doc("abcdef\nab"); r.column_drag((0, 0), (1, 5)); let rows = r.selections().all();
3072 assert_eq!((rows[0].start(), rows[0].end()), (0, 5)); assert_eq!((rows[1].start(), rows[1].end()), (7, 9)); }
3075
3076 #[test]
3077 fn column_box_is_display_cells_across_tabs() {
3078 let mut d = doc("\tabcd\nwxyz"); d.column_drag((0, 4), (1, 6));
3083 let rows = d.selections().all();
3084 assert_eq!(rows.len(), 2);
3085 assert_eq!((rows[0].start(), rows[0].end()), (1, 3), "cells 4..6 are bytes 1..3 after the tab");
3086 assert_eq!((rows[1].start(), rows[1].end()), (10, 10), "row 1 ends at cell 4; both corners clamp");
3087 }
3088
3089 #[test]
3090 fn column_box_resolves_through_a_collapsed_inline_fold() {
3091 let mut d = doc("f([a, b]) x");
3095 assert!(d.toggle_fold_opener(2)); d.column_drag((0, 6), (0, 9));
3097 let s = d.selections().newest();
3098 assert_eq!((s.start(), s.end()), (7, 10), "cells 6..9 are `]) `, one left of the byte columns");
3099 }
3100
3101 #[test]
3102 fn column_box_spans_visible_rows_only() {
3103 let mut d = doc("aa\nfn {\nbb\ncc\n}\ndd");
3106 assert!(d.toggle_fold_opener(6)); d.column_drag((0, 0), (5, 1));
3108 let rows: Vec<u32> =
3109 d.selections().all().iter().map(|s| d.buffer().offset_to_point(s.start()).row).collect();
3110 assert_eq!(rows, vec![0, 1, 5], "one selection per VISIBLE row");
3111 }
3112
3113 #[test]
3114 fn column_select_steps_display_rows_over_a_fold() {
3115 use crate::movement::ColumnDir::Up;
3116 let mut d = doc("aa\nfn {\nbb\ncc\n}\ndd");
3119 assert!(d.toggle_fold_opener(6));
3120 d.set_selections(SelectionSet::new(16)); d.column_select(Up);
3122 let rows: Vec<u32> =
3123 d.selections().all().iter().map(|s| d.buffer().offset_to_point(s.start()).row).collect();
3124 assert_eq!(rows, vec![1, 5], "the step lands on the fold header, skipping hidden rows");
3125 }
3126
3127 #[test]
3128 fn column_select_anchors_at_the_display_cell_after_a_tab() {
3129 use crate::movement::ColumnDir::Down;
3130 let mut d = doc("\tab\nwwwwww"); d.set_selections(SelectionSet::new(1)); d.column_select(Down);
3136 assert_eq!(d.selections().newest().head(), 8, "cell 4 on row 1 is byte 4 (offset 8)");
3137 }
3138
3139 #[test]
3140 fn any_action_exits_column_mode() {
3141 use crate::movement::ColumnDir::Down;
3142 let mut d = doc("abcd\nabcd\nabcd");
3143 d.set_selections(SelectionSet::new(0));
3144 d.column_select(Down); assert_eq!(d.selections().len(), 2);
3146 d.move_carets(Motion::Right, false); d.column_select(Down);
3150 assert_eq!(d.selections().len(), 2, "re-anchored, not continuing the old box");
3151 }
3152
3153 fn range(d: &Document) -> (u32, u32) {
3154 (d.selections().newest().start(), d.selections().newest().end())
3155 }
3156
3157 #[test]
3158 fn drag_select_word_granularity_keeps_the_origin_word() {
3159 let mut d = doc("foo bar baz\nqux"); d.drag_select(Granularity::Word, 5, 5);
3162 assert_eq!(range(&d), (4, 7));
3163 d.drag_select(Granularity::Word, 5, 9);
3165 assert_eq!(range(&d), (4, 11));
3166 d.drag_select(Granularity::Word, 5, 1);
3168 assert_eq!(range(&d), (0, 7));
3169 let mut w = doc("a b");
3171 w.drag_select(Granularity::Word, 2, 2);
3172 assert!(w.selections().newest().is_empty());
3173 }
3174
3175 #[test]
3176 fn drag_select_line_and_char_granularity() {
3177 let mut d = doc("aa\nbb\ncc"); d.drag_select(Granularity::Line, 1, 1); assert_eq!(range(&d), (0, 3));
3180 d.drag_select(Granularity::Line, 1, 4); assert_eq!(range(&d), (0, 6));
3182 d.drag_select(Granularity::Line, 7, 7); assert_eq!(range(&d), (6, 8));
3184 d.drag_select(Granularity::Char, 2, 8); assert_eq!(range(&d), (2, 8));
3186 }
3187
3188 fn head_row(d: &Document) -> u32 {
3189 d.buffer().offset_to_point(d.selections().newest().head()).row
3190 }
3191
3192 #[test]
3193 fn move_line_swaps_with_neighbour_and_rides() {
3194 let mut d = doc("aaa\nbbb\nccc");
3195 d.set_selections(SelectionSet::new(4)); d.move_line(true); assert_eq!(d.text(), "aaa\nccc\nbbb");
3198 assert_eq!(head_row(&d), 2); d.move_line(false); assert_eq!(d.text(), "aaa\nbbb\nccc");
3201 assert_eq!(head_row(&d), 1);
3202 }
3203
3204 #[test]
3205 fn move_line_is_a_noop_at_the_edges() {
3206 let mut d = doc("aaa\nbbb");
3207 d.set_selections(SelectionSet::new(0));
3208 d.move_line(false); assert_eq!(d.text(), "aaa\nbbb");
3210 d.set_selections(SelectionSet::new(4)); d.move_line(true); assert_eq!(d.text(), "aaa\nbbb");
3213 }
3214
3215 #[test]
3216 fn move_line_down_respects_the_trailing_empty_line() {
3217 let mut d = doc("aaa\nbbb\n"); d.set_selections(SelectionSet::new(4)); d.move_line(true); assert_eq!(d.text(), "aaa\nbbb\n");
3221 }
3222
3223 #[test]
3224 fn copy_line_duplicates_below_and_above() {
3225 let mut d = doc("aaa\nbbb");
3226 d.set_selections(SelectionSet::new(0));
3227 d.copy_line(true); assert_eq!(d.text(), "aaa\naaa\nbbb");
3229 assert_eq!(head_row(&d), 1);
3230 let mut d2 = doc("aaa\nbbb");
3231 d2.set_selections(SelectionSet::new(0));
3232 d2.copy_line(false); assert_eq!(d2.text(), "aaa\naaa\nbbb");
3234 assert_eq!(head_row(&d2), 0);
3235 }
3236
3237 #[test]
3238 fn copy_line_duplicates_the_final_line() {
3239 let mut d = doc("aaa\nbbb"); d.set_selections(SelectionSet::new(4)); d.copy_line(true);
3242 assert_eq!(d.text(), "aaa\nbbb\nbbb");
3243 assert_eq!(head_row(&d), 2);
3244 }
3245
3246 #[test]
3247 fn add_caret_adds_a_second_cursor() {
3248 let mut d = doc("abcdef");
3249 assert_eq!(d.selections().len(), 1);
3250 d.add_caret(3);
3251 assert_eq!(d.selections().len(), 2);
3252 }
3253
3254 #[test]
3255 fn collapse_returns_to_the_primary_caret() {
3256 let mut d = doc("foo foo foo");
3257 d.add_next_occurrence(); d.add_next_occurrence(); assert!(d.selections().len() >= 2);
3260 d.collapse_selections();
3261 assert_eq!(d.selections().len(), 1);
3262 assert!(d.selections().newest().is_empty());
3263 }
3264
3265 #[test]
3266 fn find_next_occurrence_scans_forward_then_wraps() {
3267 assert_eq!(super::find_next_occurrence("aXaYa", "a", 1, &[(0, 1)]), Some((2, 3)));
3269 assert_eq!(super::find_next_occurrence("aXaYa", "a", 5, &[(2, 3), (4, 5)]), Some((0, 1)));
3271 assert_eq!(super::find_next_occurrence("aa", "a", 0, &[(0, 1), (1, 2)]), None);
3273 }
3274
3275 #[test]
3276 fn dirty_after_undo_then_divergent_edit() {
3277 let mut d = doc("abc");
3280 d.edit(vec![EditOp::delete(2..3)]).unwrap(); d.mark_saved();
3282 d.undo(); assert!(d.is_dirty(), "undone away from the save point → dirty");
3284 d.edit(vec![EditOp::delete(0..1)]).unwrap(); assert!(d.is_dirty(), "divergent edit is still dirty, never falsely clean");
3286 }
3287
3288 #[test]
3289 fn highlight_cache_splice_tracks_line_count_across_edits() {
3290 const G: &str = "%YAML 1.2\n---\nname: T\nscope: source.t\ncontexts:\n main:\n - match: '\\w+'\n scope: keyword.t\n";
3292 const TH: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
3293<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3294<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>"#;
3295 let mut d = doc("ab\ncd");
3296 d.set_syntax(
3297 crate::SyntaxDef::from_sublime_syntax(G).unwrap(),
3298 crate::TokenTheme::from_tm_theme(TH).unwrap(),
3299 );
3300 d.tokenize_highlight(d.buffer().line_count());
3301 assert!(d.highlight_line_spans(1).is_some());
3302 d.set_selections(SelectionSet::new(2)); d.enter();
3305 d.tokenize_highlight(d.buffer().line_count());
3306 assert_eq!(d.buffer().line_count(), 3);
3307 assert!(d.highlight_line_spans(2).is_some(), "the new last line is tokenized");
3308 d.backspace();
3310 d.tokenize_highlight(d.buffer().line_count());
3311 assert_eq!(d.buffer().line_count(), 2);
3312 assert!(d.highlight_line_spans(1).is_some());
3313 assert!(d.highlight_line_spans(2).is_none(), "no spans past the buffer");
3314 }
3315
3316 #[test]
3317 fn multi_op_transaction_highlight_equals_a_fresh_document() {
3318 const G: &str = "%YAML 1.2\n---\nname: T\nscope: source.t\ncontexts:\n main:\n - match: '\\w+'\n scope: keyword.t\n";
3325 const TH: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
3326<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3327<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>"#;
3328 let make = |text: &str| -> Document {
3329 let mut d = doc(text);
3330 d.set_syntax(
3331 crate::SyntaxDef::from_sublime_syntax(G).unwrap(),
3332 crate::TokenTheme::from_tm_theme(TH).unwrap(),
3333 );
3334 d.tokenize_highlight(u32::MAX);
3335 d
3336 };
3337 let mut d = make("aa bb cc\ndd ee ff\ngg hh ii\njj kk ll\nmm nn oo");
3340 d.edit_grouped(
3344 vec![
3345 EditOp::new(0..2, "XYZ"), EditOp::new(9..11, "Q"), EditOp::new(15..17, "RR"), EditOp::insert(27, "NEW\n"), ],
3350 typ(OpClass::Type),
3351 )
3352 .unwrap();
3353 d.tokenize_highlight(u32::MAX);
3354
3355 let text = d.text();
3356 let f = make(&text);
3357 assert_eq!(d.buffer().line_count(), f.buffer().line_count(), "line count");
3358 for r in 0..d.buffer().line_count() {
3359 assert_eq!(
3360 d.highlight_line_spans(r),
3361 f.highlight_line_spans(r),
3362 "row {r} live (per-edit invalidation) vs fresh"
3363 );
3364 }
3365 }
3366
3367 #[test]
3372 fn set_syntax_preserves_the_highlight_window_aim() {
3373 const G: &str = "%YAML 1.2\n---\nname: T\nscope: source.t\ncontexts:\n main:\n - match: '\\w+'\n scope: keyword.t\n";
3374 const TH: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
3375<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3376<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>"#;
3377 let syntax = || crate::SyntaxDef::from_sublime_syntax(G).unwrap();
3378 let theme = || crate::TokenTheme::from_tm_theme(TH).unwrap();
3379 let mut d = doc(&"word\n".repeat(3_000));
3380 d.set_syntax(syntax(), theme());
3381 d.set_highlight_window(2_000..2_040);
3383 d.set_syntax(syntax(), theme());
3385 while d.highlight_frontier().is_some() {
3386 d.tokenize_highlight(u32::MAX);
3387 }
3388 assert!(
3389 d.highlight_line_spans(2_020).is_some(),
3390 "the swapped cache must still retain the viewport rows"
3391 );
3392 }
3393
3394 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";
3399 const HL_TH: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
3400<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3401<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>"#;
3402
3403 fn doc_with_syntax(n: usize) -> Document {
3404 let mut d = doc(&"kw word\n".repeat(n));
3405 d.set_syntax(
3406 crate::SyntaxDef::from_sublime_syntax(HL_G).unwrap(),
3407 crate::TokenTheme::from_tm_theme(HL_TH).unwrap(),
3408 );
3409 d
3410 }
3411
3412 #[test]
3413 fn absorb_highlight_drops_a_stale_revision() {
3414 let mut d = doc_with_syntax(2_000);
3415 let engine = d.highlight_engine().unwrap();
3416 let snap = d.snapshot();
3417 let stale_rev = d.revision();
3418 let seg = crate::tokenize_segment(&engine, &snap, 0..2_001, crate::SegmentStart::Fresh, None, None);
3419 d.set_selections(SelectionSet::new(0));
3421 d.type_char('x');
3422 assert_ne!(d.revision(), stale_rev);
3423 assert!(!d.absorb_highlight(stale_rev, seg, true), "stale segment must be dropped");
3425 assert_eq!(d.highlight_frontier(), Some(0), "the frontier is untouched by a dropped absorb");
3426 }
3427
3428 #[test]
3429 fn absorb_highlight_verified_clears_the_frontier() {
3430 let mut d = doc_with_syntax(5_000);
3431 let engine = d.highlight_engine().unwrap();
3432 let snap = d.snapshot();
3433 let rev = d.revision();
3434 let n = d.buffer().line_count();
3435 let seg =
3439 crate::tokenize_segment(&engine, &snap, 0..n, crate::SegmentStart::Fresh, Some(0..n), None);
3440 assert!(d.absorb_highlight(rev, seg, true));
3441 assert_eq!(d.highlight_frontier(), None, "verified absorb clears every dirty row");
3442 }
3443
3444 #[test]
3445 fn absorb_highlight_speculative_shows_spans_but_keeps_dirt() {
3446 let mut d = doc_with_syntax(2_000);
3447 d.set_highlight_window(900..940);
3448 let engine = d.highlight_engine().unwrap();
3449 let snap = d.snapshot();
3450 let rev = d.revision();
3451 let seg = crate::tokenize_segment(
3453 &engine,
3454 &snap,
3455 772..940,
3456 crate::SegmentStart::Fresh,
3457 Some(900..940),
3458 None,
3459 );
3460 assert!(d.absorb_highlight(rev, seg, false));
3461 assert!(d.highlight_line_spans(920).is_some(), "speculative spans are visible");
3463 assert_eq!(d.highlight_frontier(), Some(0), "speculation keeps the frontier");
3465 while d.highlight_frontier().is_some() {
3467 d.tokenize_highlight(u32::MAX);
3468 }
3469 assert!(d.highlight_line_spans(920).is_some());
3470 }
3471
3472 #[test]
3473 fn diagnostics_install_then_ride_a_forward_edit() {
3474 use crate::{Diagnostic, DiagnosticsOutcome, Severity};
3475 let mut d = doc("let x = 1;");
3476 let rev = d.revision();
3477 let out = d.set_diagnostics(rev, vec![Diagnostic::new(4..5, Severity::Error, "unused")]);
3478 assert!(matches!(out, DiagnosticsOutcome::Applied { count: 1 }));
3479 let got: Vec<_> = d.diagnostics_in(0..100).collect();
3480 assert_eq!((got.len(), got[0].0.clone(), got[0].1), (1, 4..5, Severity::Error));
3481 d.edit(vec![EditOp::insert(0, "abcd")]).unwrap();
3483 assert_eq!(d.diagnostics_in(0..100).next().unwrap().0, 8..9, "rode via apply_patch");
3484 }
3485
3486 #[test]
3487 fn stale_diagnostic_set_is_dropped_and_the_prior_kept() {
3488 use crate::{Diagnostic, DiagnosticsOutcome, Severity};
3489 let mut d = doc("abcdef");
3490 let rev = d.revision();
3491 d.set_diagnostics(rev, vec![Diagnostic::new(0..1, Severity::Warning, "w")]);
3492 d.edit(vec![EditOp::insert(0, "z")]).unwrap(); let out = d.set_diagnostics(rev, vec![Diagnostic::new(3..4, Severity::Error, "e")]);
3494 assert!(matches!(out, DiagnosticsOutcome::Stale { .. }), "old-revision set dropped");
3495 let got: Vec<_> = d.diagnostics_in(0..100).collect();
3496 assert_eq!((got.len(), got[0].0.clone(), got[0].1), (1, 1..2, Severity::Warning));
3497 }
3498
3499 #[test]
3500 fn diagnostic_spans_clip_to_the_buffer_length() {
3501 use crate::{Diagnostic, Severity};
3502 let mut d = doc("abc"); let rev = d.revision();
3504 d.set_diagnostics(rev, vec![Diagnostic::new(2..99, Severity::Error, "past eof")]);
3505 assert_eq!(d.diagnostics_in(0..100).next().unwrap().0, 2..3, "clipped to buffer len");
3506 }
3507
3508 #[test]
3509 fn rend04_diagnostic_render_row_shifts_when_lines_inserted_above() {
3510 use crate::{Diagnostic, Point, Severity};
3514 let mut d = doc("L0\nL1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9");
3515 let row5 = d.buffer().point_to_offset(Point { row: 5, col: 0 });
3516 d.set_diagnostics(d.revision(), vec![Diagnostic::new(row5..row5 + 2, Severity::Error, "e")]);
3517 let start = d.diagnostics_in(0..u32::MAX).next().unwrap().0.start;
3518 assert_eq!(d.buffer().offset_to_point(start).row, 5);
3519 d.edit(vec![EditOp::insert(0, "a\nb\nc\n")]).unwrap();
3521 let span = d.diagnostics_in(0..u32::MAX).next().unwrap().0;
3522 assert_eq!(d.buffer().offset_to_point(span.start).row, 8, "5 + 3 inserted lines");
3523 assert_eq!(&d.text()[span.start as usize..span.end as usize], "L5", "still glued to its code");
3524 }
3525
3526 #[test]
3532 fn windowed_fold_queries_match_the_whole_document() {
3533 let mut d = doc(
3534 "top\n\
3535 a {\n b [1, 2]\n c {\n d\n }\n}\n\
3536 mid\n\
3537 e (x,\n y)\n\
3538 f {\n g\n}\n\
3539 end",
3540 );
3541 let n = d.buffer().line_count();
3542 for row in 0..n {
3544 let want = d
3545 .foldable_pairs()
3546 .into_iter()
3547 .filter(|&(_, _, h, l)| h == row && l > h)
3548 .max_by_key(|&(_, _, h, l)| l - h)
3549 .map(|(o, ..)| o);
3550 assert_eq!(d.block_opener_on_row(row), want, "block_opener_on_row row {row}");
3551 }
3552 assert_eq!(d.collapsible_pairs_in_rows(0..n), d.collapsible_pairs());
3554 for (a, b) in [(0u32, 3u32), (2, 6), (3, 4), (7, n)] {
3556 let want: Vec<_> =
3557 d.collapsible_pairs().into_iter().filter(|&(_, _, h, _)| a <= h && h < b).collect();
3558 assert_eq!(d.collapsible_pairs_in_rows(a..b), want, "collapsible_pairs_in_rows {a}..{b}");
3559 }
3560 for row in 0..n {
3562 let windowed: Vec<_> =
3563 d.foldable_ranges_in_rows(row..row + 1).into_iter().filter(|(h, _)| h.0 == row).collect();
3564 let whole: Vec<_> =
3565 d.foldable_ranges().into_iter().filter(|(h, _)| h.0 == row).collect();
3566 assert_eq!(windowed, whole, "foldable_ranges row {row}");
3567 }
3568 let opener = d.block_opener_on_row(1).unwrap();
3570 assert!(d.toggle_fold_opener(opener));
3571 assert_eq!(d.collapsible_pairs_in_rows(0..n), d.collapsible_pairs());
3572 }
3573
3574 #[test]
3575 fn fold_survives_undo_redo() {
3576 use crate::{fold_map::FoldMap, BufferRow};
3581 let mut d = doc("L0\nL1\nL2\nL3\nL4\nblk {\n x\n y\n}\nL9");
3582 assert_eq!(d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect::<Vec<_>>(), vec![(5, 8)]);
3583 assert!(d.fold(BufferRow(5), BufferRow(8)));
3584 let header = |d: &Document| FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(5));
3585 let header8 = |d: &Document| FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(8));
3586 assert_eq!(header(&d), Some(BufferRow(8)));
3587 d.edit(vec![EditOp::insert(0, "a\nb\nc\n")]).unwrap();
3589 assert_eq!(header8(&d), Some(BufferRow(11)));
3590 assert!(d.undo());
3592 assert_eq!(header(&d), Some(BufferRow(8)));
3593 assert!(d.redo());
3594 assert_eq!(header8(&d), Some(BufferRow(11)));
3595 }
3596
3597 #[test]
3598 fn pasting_after_the_collapsed_line_keeps_the_outer_fold() {
3599 use crate::{fold_map::FoldMap, BufferRow, SelectionSet};
3600 let mut d = doc("{\n\t{\n\t}\n}\n");
3602 assert_eq!(d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect::<Vec<_>>(), vec![(0, 3), (1, 2)]);
3603 assert!(d.fold(BufferRow(0), BufferRow(3))); let block = d.buffer().text()[0..9].to_string();
3607 d.set_selections(SelectionSet::new(9));
3608 d.insert_text(&block);
3609 assert_eq!(d.folds().len(), 1, "the outer fold survives the paste");
3612 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(0)), Some(BufferRow(3)));
3613 }
3614
3615 #[test]
3616 fn editing_after_the_brace_keeps_a_folded_block() {
3617 use crate::{fold_map::FoldMap, BufferRow, SelectionSet};
3618 let mut d = doc("{\n}");
3619 assert!(d.fold(BufferRow(0), BufferRow(1)));
3620 d.set_selections(SelectionSet::new(d.buffer().len()));
3622 d.insert_text("abc");
3623 assert_eq!(d.buffer().text(), "{\n}abc");
3624 assert_eq!(d.folds().len(), 1, "fold survives typing after the brace");
3625 let end = d.buffer().len();
3627 d.edit(vec![EditOp::delete(end - 3..end)]).unwrap();
3628 assert_eq!(d.buffer().text(), "{\n}");
3629 assert_eq!(d.folds().len(), 1, "fold survives deleting after the brace");
3630 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(0)), Some(BufferRow(1)));
3631 }
3632
3633 #[test]
3634 fn pressing_return_after_a_folded_eof_block_keeps_it_folded() {
3635 use crate::{fold_map::FoldMap, BufferRow, SelectionSet};
3636 let mut d = doc("{\n}");
3639 assert!(d.fold(BufferRow(0), BufferRow(1)));
3640 d.set_selections(SelectionSet::new(d.buffer().len()));
3643 d.insert_text("\n");
3644 assert_eq!(d.buffer().text(), "{\n}\n");
3645 assert_eq!(d.folds().len(), 1, "the intact block stays folded");
3646 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(0)), Some(BufferRow(1)));
3647 }
3648
3649 #[test]
3650 fn cutting_trailing_content_keeps_an_intact_fold() {
3651 use crate::{fold_map::FoldMap, BufferRow};
3652 let mut d = doc("{\n}\nX");
3654 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();
3661 assert_eq!(d.buffer().text(), "{\n}", "only the trailing content was removed");
3662 assert_eq!(d.folds().len(), 1, "the intact block stays folded");
3663 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(0)), Some(BufferRow(1)));
3664 }
3665
3666 #[test]
3667 fn cutting_across_a_folds_boundary_drops_it_not_drifts_it() {
3668 use crate::{fold_map::FoldMap, BufferRow};
3669 let mut d = doc("{\n}\n{\n}\n{\n}\n");
3671 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();
3678 assert!(d.folds().is_empty(), "fold dropped, not drifted onto the first block");
3681 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).fold_at_header(BufferRow(0)), None, "first set not folded");
3682 }
3683
3684 #[test]
3685 fn typing_at_many_carets_over_folds_stays_linear() {
3686 use crate::bracket::ENCLOSING_WALKS;
3687 use crate::row_layout::DISPLAY_POSITION_PROBES;
3688 use crate::{Motion, SelectionSet};
3689 let mut src = String::new();
3696 for i in 0..400 {
3697 src.push_str(&format!(
3698 "// block {i}\nfn pid_{i}(pid: u8) -> u8 {{\n switch pid {{\n 0x0D => {{ return 88; }}\n }}\n}}\n\n"
3699 ));
3700 }
3701 let mut d = doc(&src);
3702 let first = d.buffer().text().find("fn pid").unwrap() as u32;
3703 d.set_selections(SelectionSet::from_ranges(&[(first, first + 6)], 0));
3704 d.select_all_occurrences();
3705 let carets = d.selections().all().len();
3706 assert!(carets >= 400, "select-all found every block ({carets} carets)");
3707 d.move_carets(Motion::LineEnd, false);
3708 d.fold_at_carets(false);
3709 d.move_carets(Motion::LineEnd, false);
3710
3711 ENCLOSING_WALKS.with(|c| c.set(0));
3713 DISPLAY_POSITION_PROBES.with(|c| c.set(0));
3714 d.insert_text("a");
3715 let walks = ENCLOSING_WALKS.with(std::cell::Cell::get);
3716 let probes = DISPLAY_POSITION_PROBES.with(std::cell::Cell::get);
3717 assert!(walks <= 2, "one enclosing walk per commit, not O(carets): {walks} for {carets} carets");
3718 assert!(
3719 probes <= 4 * carets as u64,
3720 "display-position probes are O(edit points), not O(carets²): {probes} for {carets} carets"
3721 );
3722 }
3723
3724 #[test]
3725 fn folded_keystroke_never_resolves_bracket_depth() {
3726 use crate::bracket_tree::BRACKET_VIEW_CALLS;
3727 use crate::SelectionSet;
3728 let mut src = String::new();
3739 for i in 0..300 {
3740 src.push_str(&format!("fn f_{i}() {{\n body\n}}\n"));
3741 }
3742 let mut d = doc(&src);
3743 let openers: Vec<u32> = d
3745 .brackets()
3746 .all()
3747 .iter()
3748 .filter(|b| b.open && d.buffer().char_at(b.offset) == Some('{'))
3749 .map(|b| b.offset)
3750 .collect();
3751 assert_eq!(openers.len(), 300, "one foldable block opener each");
3752 for &o in &openers {
3753 assert!(d.toggle_fold_opener(o));
3754 }
3755 let carets: Vec<(u32, u32)> = openers.iter().map(|&o| (o + 1, o + 1)).collect();
3757 d.set_selections(SelectionSet::from_ranges(&carets, 0));
3758 let _ = d.fold_map(); BRACKET_VIEW_CALLS.with(|c| c.set(0));
3761 d.insert_text("a");
3762 let _ = d.fold_map(); let depth_resolves = BRACKET_VIEW_CALLS.with(std::cell::Cell::get);
3764 assert_eq!(
3765 depth_resolves, 0,
3766 "a fold-heavy keystroke resolved bracket depth {depth_resolves} times over 300 folds \
3767 — the edit path must use foldable_partner, not at()/bracket_view"
3768 );
3769 }
3770
3771 #[test]
3772 fn single_line_pair_folds_inline() {
3773 use crate::{fold_map::FoldMap, SelectionSet};
3774 let mut d = doc("x = [1, 2, 3]");
3776 d.set_selections(SelectionSet::new(6)); let opener = d.fold_opener_at_caret(false).expect("an enclosing foldable pair");
3778 assert_eq!(opener, 4);
3779 assert!(d.toggle_fold_opener(opener));
3780 let m = FoldMap::new(d.folds(), d.brackets(), d.buffer());
3781 assert_eq!(m.display_row_count(), 1, "an inline fold hides no rows");
3782 assert_eq!(m.inline_folds().len(), 1);
3783 assert_eq!((m.inline_folds()[0].row, m.inline_folds()[0].open, m.inline_folds()[0].close), (0, 4, 12));
3784 assert!(d.toggle_fold_opener(4));
3786 assert!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).inline_folds().is_empty());
3787 }
3788
3789 #[test]
3790 fn fold_at_carets_acts_on_every_caret() {
3791 use crate::{fold_map::FoldMap, SelectionSet};
3792 let mut d = doc("a{x}\nb{y}");
3794 d.set_selections(SelectionSet::from_offsets(&[2, 7])); assert!(d.fold_at_carets(false), "Ctrl+Shift+[ folds at every caret");
3796 let m = FoldMap::new(d.folds(), d.brackets(), d.buffer());
3797 assert_eq!(m.inline_folds().len(), 2, "both blocks collapsed, not just the primary");
3798 assert!(d.fold_at_carets(true), "Ctrl+Shift+] unfolds at every caret");
3800 assert!(
3801 FoldMap::new(d.folds(), d.brackets(), d.buffer()).inline_folds().is_empty(),
3802 "both blocks expanded",
3803 );
3804 }
3805
3806 #[test]
3807 fn fold_map_cache_matches_a_fresh_build_across_changes() {
3808 fn consistent(d: &Document) -> bool {
3814 *d.fold_map() == crate::fold_map::FoldMap::new(d.folds(), d.brackets(), d.buffer())
3815 }
3816 let mut d = doc("a {\nx\n}\nb = [1, 2, 3]\nc\n");
3817 assert!(consistent(&d), "empty folds");
3818 let block = d.text().find('{').unwrap() as u32;
3820 let inline = d.text().find('[').unwrap() as u32;
3821 d.toggle_fold_opener(block);
3822 d.toggle_fold_opener(inline);
3823 assert!(consistent(&d), "after folds — rebuilt on the generation");
3824 d.edit(vec![EditOp::insert(0, "Z")]).unwrap();
3827 assert!(consistent(&d), "after a no-line-change edit — shifted in place");
3828 d.edit(vec![EditOp::insert(0, "Y")]).unwrap();
3829 assert!(consistent(&d), "after a second in-place shift (cumulative)");
3830 d.edit(vec![EditOp::insert(0, "\n")]).unwrap();
3832 assert!(consistent(&d), "after a line change — rebuilt");
3833 d.edit(vec![EditOp::insert(0, "W")]).unwrap();
3834 assert!(consistent(&d), "after an edit following a rebuild");
3835 }
3836
3837 #[test]
3838 fn deleting_into_a_folded_interior_rebuilds_not_drifts() {
3839 let consistent = |d: &Document| {
3846 *d.fold_map() == crate::fold_map::FoldMap::new(d.folds(), d.brackets(), d.buffer())
3847 };
3848 for prefix in ["xx\n", ""] {
3849 let mut d = doc(&format!("{prefix}fn foo() {{\n body\n}}\nafter\n"));
3852 let open = d.text().find('{').unwrap() as u32;
3853 d.toggle_fold_opener(open);
3854 let _ = d.fold_map();
3855 assert!(consistent(&d), "{prefix:?}: folded");
3856 let nl = open + d.text()[open as usize..].find('\n').unwrap() as u32;
3857 d.edit(vec![EditOp::delete(nl..nl + 1)]).unwrap(); assert!(consistent(&d), "{prefix:?}: after deleting the header-terminating newline");
3859 }
3860 }
3861
3862 #[test]
3863 fn fold_map_cache_matches_fresh_build_under_random_edits() {
3864 let consistent = |d: &Document| {
3870 *d.fold_map() == crate::fold_map::FoldMap::new(d.folds(), d.brackets(), d.buffer())
3871 };
3872 let mut state = 0xF01D_5EEDu32;
3873 let mut next = || {
3874 state ^= state << 13;
3875 state ^= state >> 17;
3876 state ^= state << 5;
3877 state
3878 };
3879 let mut d = doc(&"fn f() {\n x = [1, 2]\n y\n}\n".repeat(6));
3880 for open in d.collapsible_pairs().iter().map(|&(o, ..)| o).collect::<Vec<_>>() {
3881 d.toggle_fold_opener(open);
3882 }
3883 assert!(consistent(&d), "after collapse-all");
3884 for step in 0..1500 {
3885 let len = d.buffer().len();
3886 if len == 0 {
3887 break;
3888 }
3889 let at = next() % (len + 1);
3890 let _ = match next() % 6 {
3891 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 => {
3896 let end = (at + 1 + next() % 5).min(len); d.edit(vec![EditOp::delete(at..end)])
3898 }
3899 _ => d.edit(vec![EditOp::insert(at, "q")]),
3900 };
3901 assert!(consistent(&d), "step {step}: cache drifted from a fresh build (at {at})");
3902 }
3903 }
3904
3905 #[test]
3906 fn keystroke_and_arrow_do_not_rebuild_the_fold_map_at_scale() {
3907 use crate::fold_map::FOLD_BUILDS;
3913 let mut text = String::new();
3914 for i in 0..300 {
3915 text.push_str(&format!("fn f{i}() {{\n body\n}}\n"));
3916 }
3917 let mut d = doc(&text);
3918 let opens: Vec<u32> = d.collapsible_pairs().into_iter().map(|(o, ..)| o).collect();
3919 assert!(opens.len() >= 300, "every block is foldable");
3920 d.set_selections(SelectionSet::from_offsets(&opens));
3921 d.fold_at_carets(false); d.set_selections(SelectionSet::new(0)); let _ = d.fold_map(); let base = FOLD_BUILDS.with(std::cell::Cell::get);
3927 d.type_char('x');
3928 let _ = d.fold_map();
3929 assert_eq!(
3930 FOLD_BUILDS.with(std::cell::Cell::get) - base,
3931 0,
3932 "a keystroke over {} folds must not rebuild the FoldMap",
3933 opens.len()
3934 );
3935
3936 let base = FOLD_BUILDS.with(std::cell::Cell::get);
3938 d.move_carets(Motion::Down, false);
3939 let _ = d.fold_map();
3940 assert_eq!(
3941 FOLD_BUILDS.with(std::cell::Cell::get) - base,
3942 0,
3943 "an arrow key over {} folds must not rebuild the FoldMap",
3944 opens.len()
3945 );
3946
3947 d.set_selections(SelectionSet::new(0));
3953 let base = FOLD_BUILDS.with(std::cell::Cell::get);
3954 d.edit(vec![EditOp::insert(0, "\n")]).unwrap();
3955 let _ = d.fold_map();
3956 assert_eq!(
3957 FOLD_BUILDS.with(std::cell::Cell::get) - base,
3958 0,
3959 "a newline at the top over {} folds must not rebuild the FoldMap",
3960 opens.len()
3961 );
3962 }
3963
3964 #[test]
3965 fn keystroke_with_document_scale_decorations_does_not_resort_the_store() {
3966 use crate::decorations::DECORATION_SORTS;
3972 use crate::{Diagnostic, Severity};
3973 let mut d = doc(&"let x = 1;\n".repeat(2000)); let rev = d.revision();
3975 let diags: Vec<Diagnostic> = (0..2000u32)
3976 .map(|i| Diagnostic::new(i * 10..i * 10 + 3, Severity::Warning, "w"))
3977 .collect();
3978 d.set_diagnostics(rev, diags); d.set_selections(SelectionSet::new(0));
3980
3981 let base = DECORATION_SORTS.with(std::cell::Cell::get);
3982 d.type_char('z');
3983 assert_eq!(
3984 DECORATION_SORTS.with(std::cell::Cell::get) - base,
3985 0,
3986 "a keystroke with 2000 diagnostics must not re-sort the decoration store"
3987 );
3988 }
3989
3990 #[test]
3991 fn reconcile_scans_the_fold_set_only_when_a_fold_actually_breaks() {
3992 use crate::fold_map::RECONCILE_SCANS;
4000 let mut d = doc("fn f() {\n body\n}\ntail\n");
4001 let open = d.text().find('{').unwrap() as u32;
4002 assert!(d.toggle_fold_opener(open));
4003 d.set_selections(SelectionSet::new(0));
4004
4005 for text in ["a", "\n", "{"] {
4010 let base = RECONCILE_SCANS.with(std::cell::Cell::get);
4011 d.edit(vec![EditOp::insert(0, text)]).unwrap();
4012 assert_eq!(
4013 RECONCILE_SCANS.with(std::cell::Cell::get) - base,
4014 0,
4015 "inserting {text:?} breaks no fold, so it must not scan the fold set"
4016 );
4017 }
4018
4019 let close = d.text().rfind('}').unwrap() as u32;
4021 let base = RECONCILE_SCANS.with(std::cell::Cell::get);
4022 d.edit(vec![EditOp::delete(close..close + 1)]).unwrap();
4023 assert!(
4024 RECONCILE_SCANS.with(std::cell::Cell::get) - base >= 1,
4025 "deleting a brace that breaks a fold must reconcile the fold set"
4026 );
4027 assert!(d.folds().is_empty(), "the broken fold was dropped");
4028 }
4029
4030 #[test]
4031 fn windowed_reconcile_matches_the_whole_set_reconcile_under_random_edits() {
4032 use crate::SelectionSet;
4033 let mut src = String::new();
4041 for i in 0..40 {
4042 src.push_str(&format!("block{i} {{\n a\n b\n}}\n"));
4043 }
4044 let mut d = doc(&src);
4045 for open in d.collapsible_pairs().iter().map(|&(o, ..)| o).collect::<Vec<_>>() {
4046 d.toggle_fold_opener(open);
4047 }
4048 let mut state: u32 = 0x1234_5678;
4050 let mut rng = || {
4051 state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
4052 state
4053 };
4054 for _ in 0..300 {
4055 let len = d.buffer().len();
4056 if len == 0 {
4057 break;
4058 }
4059 let pos = rng() % len;
4060 match rng() % 5 {
4061 0 => {
4062 d.set_selections(SelectionSet::new(pos));
4063 d.insert_text("{");
4064 }
4065 1 => {
4066 d.set_selections(SelectionSet::new(pos));
4067 d.insert_text("}");
4068 }
4069 2 if len > 1 => {
4070 let s = pos.min(len - 1);
4073 d.edit(vec![EditOp::delete(s..s + 1)]).unwrap();
4074 }
4075 _ => {
4076 d.set_selections(SelectionSet::new(pos));
4077 d.insert_text("x");
4078 }
4079 }
4080 let before: Vec<u32> = d.folds().iter().collect();
4081 d.force_whole_reconcile();
4082 let after: Vec<u32> = d.folds().iter().collect();
4083 assert_eq!(before, after, "windowed reconcile missed a fold the whole-set pass caught");
4084 for o in after {
4086 let foldable = d
4087 .brackets()
4088 .at(o)
4089 .filter(|b| b.open)
4090 .and_then(|b| b.partner)
4091 .is_some_and(|c| crate::row_layout::pair_has_interior(o, c));
4092 assert!(foldable, "a surviving fold at {o} is not a foldable pair");
4093 }
4094 }
4095 }
4096
4097 #[test]
4098 fn fold_at_carets_ejects_carets_hidden_by_a_block() {
4099 use crate::SelectionSet;
4100 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));
4103 assert!(d.fold_at_carets(false), "the block folds");
4104 let row = d.buffer().offset_to_point(d.selections().newest().head()).row;
4107 assert_eq!(row, 0, "caret ejected to the header row");
4108 }
4109
4110 #[test]
4111 fn horizontal_movement_hops_an_inline_fold() {
4112 use crate::{Motion, SelectionSet};
4113 let mut d = doc("x = [1, 2, 3]"); d.set_selections(SelectionSet::new(6)); let op = d.fold_opener_at_caret(false).unwrap();
4116 assert!(d.toggle_fold_opener(op));
4117 assert_eq!(d.selections().newest().head(), 5, "caret snapped just after `[`");
4118 d.move_carets(Motion::Right, false);
4119 assert_eq!(d.selections().newest().head(), 12, "Right hops the collapsed interior to `]`");
4120 d.move_carets(Motion::Left, false);
4121 assert_eq!(d.selections().newest().head(), 5, "Left hops back to just after `[`");
4122 }
4123
4124 #[test]
4125 fn collapsible_at_prefers_the_innermost() {
4126 let d = doc("fn m() {\n let x = [1, 2, 3];\n}");
4128 let mut pairs = d.collapsible_pairs();
4129 pairs.sort();
4130 assert_eq!(pairs, vec![(7, 32, 0, 2), (21, 29, 1, 1)]);
4131 assert_eq!(d.collapsible_at(24), Some(21));
4133 assert_eq!(d.collapsible_at(12), Some(7));
4135 assert_eq!(d.collapsible_at(0), None);
4137 }
4138
4139 #[test]
4140 fn collapsible_excludes_the_already_folded() {
4141 let mut d = doc("fn m() {\n let x = [1, 2, 3];\n}");
4142 assert!(d.toggle_fold_opener(7));
4145 let openers: Vec<u32> = d.collapsible_pairs().iter().map(|&(o, ..)| o).collect();
4146 assert_eq!(openers, vec![21]);
4147 assert_eq!(d.collapsible_at(12), None, "the folded block is no longer a target");
4148 assert_eq!(d.collapsible_at(24), Some(21), "the array inside it still is");
4149 }
4150
4151 #[test]
4152 fn nested_inline_folds_reduce_to_the_outer() {
4153 use crate::fold_map::FoldMap;
4154 let mut d = doc("outer_call(a, [1, 2, 3])");
4156 assert!(d.toggle_fold_opener(14)); assert!(d.toggle_fold_opener(10)); let inline = |d: &Document| {
4159 FoldMap::new(d.folds(), d.brackets(), d.buffer()).inline_folds().iter().map(|f| f.open).collect::<Vec<_>>()
4160 };
4161 assert_eq!(inline(&d), vec![10]);
4163 assert!(d.toggle_fold_opener(10));
4165 assert_eq!(inline(&d), vec![14]);
4166 }
4167
4168 #[test]
4169 fn horizontal_movement_hops_the_collapsed_gap() {
4170 use crate::{BufferRow, Motion, Point, SelectionSet};
4171 let mut d = doc("m {\n a\n b\n}\nafter");
4173 assert!(d.fold(BufferRow(0), BufferRow(3))); let caret = |d: &Document| d.buffer().offset_to_point(d.selections().newest().head());
4175 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(0, 3))));
4177 d.move_carets(Motion::Right, false);
4178 assert_eq!(caret(&d), Point::new(3, 0), "Right hops the hidden interior to the closing bracket");
4179 d.move_carets(Motion::Left, false);
4180 assert_eq!(caret(&d), Point::new(0, 3), "Left hops back to the header end");
4181 }
4182
4183 #[test]
4184 fn vertical_move_skips_folds() {
4185 use crate::{BufferRow, Motion, Point, SelectionSet};
4186 let mut d = doc("L0\n{\nL2\nL3\n}\nL5");
4187 assert!(d.fold(BufferRow(1), BufferRow(4))); let row = |d: &Document| d.buffer().offset_to_point(d.selections().newest().head()).row;
4189 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 0))));
4191 d.move_carets(Motion::Down, false);
4192 assert_eq!(row(&d), 5);
4193 d.move_carets(Motion::Up, false);
4195 assert_eq!(row(&d), 1);
4196 }
4197
4198 #[test]
4199 fn typing_into_an_inline_folds_gap_expands_it() {
4200 use crate::SelectionSet;
4201 let mut d = doc("x = [1, 2, 3]");
4205 d.set_selections(SelectionSet::new(6));
4206 assert!(d.toggle_fold_opener(4));
4207 assert_eq!(d.selections().newest().head(), 5, "caret ejected to the left edge");
4208 d.type_char('9');
4209 assert!(!d.folds().is_folded(4), "editing hidden text expands the fold");
4210 assert_eq!(d.text(), "x = [91, 2, 3]");
4211 }
4212
4213 #[test]
4214 fn typing_at_a_collapsed_headers_end_keeps_it_folded() {
4215 use crate::{Point, SelectionSet};
4216 let mut d = doc("m {\n a\n}");
4220 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 0))));
4221 let opener = 2;
4222 assert!(d.toggle_fold_opener(opener));
4223 d.type_char('x'); assert!(d.folds().is_folded(opener), "a visible-part edit leaves the fold alone");
4225 assert!(d.text().starts_with("m {x\n"));
4226 }
4227
4228 #[test]
4229 fn undo_into_a_folded_region_expands_it() {
4230 use crate::{Point, SelectionSet};
4231 let mut d = doc("m {\n a\n}");
4235 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 3))));
4236 d.type_char('b'); let opener = 2;
4238 assert!(d.toggle_fold_opener(opener));
4239 assert!(d.folds().is_folded(opener));
4240 assert!(d.undo());
4241 assert!(!d.folds().is_folded(opener), "undoing hidden text reveals it");
4242 assert!(d.text().contains("\n a\n"), "the typed char was reverted");
4243 }
4244
4245 #[test]
4246 fn folding_a_block_ejects_a_hidden_caret_to_the_header_end() {
4247 use crate::{Point, SelectionSet};
4248 let mut d = doc("m {\n a\n }");
4250 let opener = 2;
4251 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 2))));
4253 assert!(d.toggle_fold_opener(opener));
4254 assert_eq!(
4255 d.selections().newest().head(),
4256 d.buffer().point_to_offset(Point::new(0, 3)),
4257 "the hidden caret ejects to just before the placeholder"
4258 );
4259 assert!(d.toggle_fold_opener(opener)); d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(2, 1))));
4262 assert!(d.toggle_fold_opener(opener));
4263 assert_eq!(d.selections().newest().head(), d.buffer().point_to_offset(Point::new(0, 3)));
4264 assert!(d.toggle_fold_opener(opener)); let on_tail = d.buffer().point_to_offset(Point::new(2, 2));
4267 d.set_selections(SelectionSet::new(on_tail));
4268 assert!(d.toggle_fold_opener(opener));
4269 assert_eq!(d.selections().newest().head(), on_tail, "the visible tail is not ejected");
4270 }
4271
4272 #[test]
4273 fn fold_chord_works_when_the_caret_touches_a_bracket() {
4274 use crate::SelectionSet;
4275 let mut d = doc("x = [1, 2, 3]");
4277 d.set_selections(SelectionSet::new(4));
4279 assert_eq!(d.fold_opener_at_caret(false), Some(4), "touching the opener from the left");
4280 d.set_selections(SelectionSet::new(13));
4282 assert_eq!(d.fold_opener_at_caret(false), Some(4), "touching the closer from the right");
4283 assert!(d.toggle_fold_opener(4));
4285 assert_eq!(d.fold_opener_at_caret(true), Some(4), "touching unfolds too");
4286 assert!(d.toggle_fold_opener(4));
4288 d.set_selections(SelectionSet::new(1));
4289 assert_eq!(d.fold_opener_at_caret(false), None);
4290 }
4291
4292 #[test]
4293 fn fold_chord_at_a_shared_boundary_picks_the_touched_inner_pair() {
4294 use crate::SelectionSet;
4295 let d = {
4299 let mut d = doc("{a [b] c}");
4300 d.set_selections(SelectionSet::new(6));
4301 d
4302 };
4303 assert_eq!(d.fold_opener_at_caret(false), Some(3), "the touched inline pair beats the enclosing block");
4304 }
4305
4306 #[test]
4307 fn vertical_goal_is_a_display_cell_across_tab_stops() {
4308 use crate::{Motion, Point, SelectionSet};
4309 let mut d = doc("\tab\nxxxxxxxxxx");
4313 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(0, 1))));
4314 d.move_carets(Motion::Down, false);
4315 assert_eq!(
4316 d.buffer().offset_to_point(d.selections().newest().head()),
4317 Point::new(1, 4),
4318 "the goal is the visual column, not the byte column"
4319 );
4320 d.move_carets(Motion::Up, false);
4322 assert_eq!(d.buffer().offset_to_point(d.selections().newest().head()), Point::new(0, 1));
4323 }
4324
4325 #[test]
4326 fn vertical_move_through_a_collapsed_tail_stays_visual() {
4327 use crate::{BufferRow, Motion, Point, SelectionSet};
4328 let mut d = doc("m {\na\n} t\nabcdefgh");
4331 assert!(d.fold(BufferRow(0), BufferRow(2)));
4332 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(2, 3))));
4334 d.move_carets(Motion::Down, false);
4335 assert_eq!(
4336 d.buffer().offset_to_point(d.selections().newest().head()),
4337 Point::new(3, 8),
4338 "Down from the far-right tail lands at the row's visual end, not byte col 3"
4339 );
4340 d.move_carets(Motion::Up, false);
4342 assert_eq!(
4343 d.buffer().offset_to_point(d.selections().newest().head()).row,
4344 2,
4345 "a goal over the placeholder tail resolves onto the tail"
4346 );
4347 }
4348
4349 #[test]
4350 fn vertical_landing_snaps_out_of_a_collapsed_inline_gap() {
4351 use crate::{Motion, Point, SelectionSet};
4352 let mut d = doc("aaaaaaaaaa\nx[123456]y");
4356 let opener = d.buffer().text().find('[').unwrap() as u32;
4357 assert!(d.toggle_fold_opener(opener));
4358 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(0, 3))));
4359 d.move_carets(Motion::Down, false);
4360 assert_eq!(
4361 d.selections().newest().head(),
4362 opener + 1,
4363 "a landing on the chip snaps to just after the opening bracket"
4364 );
4365 }
4366
4367 #[test]
4368 fn home_and_end_operate_on_the_collapsed_display_line() {
4369 use crate::{BufferRow, Motion, Point, SelectionSet};
4370 let mut d = doc("L0\n m {\na\n }");
4373 assert!(d.fold(BufferRow(1), BufferRow(3)));
4374 let head = |d: &Document| d.selections().newest().head();
4375 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 2))));
4378 d.move_carets(Motion::LineEnd, false);
4379 assert_eq!(head(&d), d.buffer().point_to_offset(Point::new(3, 3)), "End lands after the tail brace");
4380 d.move_carets(Motion::LineStart, false);
4383 assert_eq!(head(&d), d.buffer().point_to_offset(Point::new(1, 2)), "Home jumps to the header's smart start");
4384 d.move_carets(Motion::LineStart, false);
4386 assert_eq!(head(&d), d.buffer().point_to_offset(Point::new(1, 0)));
4387 assert!(d.unfold(BufferRow(1)));
4389 d.set_selections(SelectionSet::new(d.buffer().point_to_offset(Point::new(1, 2))));
4390 d.move_carets(Motion::LineEnd, false);
4391 assert_eq!(head(&d), d.buffer().point_to_offset(Point::new(1, 5)), "plain End at the header's own end");
4392 }
4393
4394 #[test]
4395 fn foldable_ranges_from_multiline_brackets() {
4396 let d = doc("mod m {\n fn f() {\n if x {\n a\n }\n }\n g()\n}");
4399 let pairs: Vec<(u32, u32)> = d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect();
4400 assert_eq!(pairs, vec![(0, 7), (1, 5), (2, 4)]);
4402 }
4403
4404 #[test]
4405 fn fold_dropped_when_its_bracket_pair_is_broken() {
4406 use crate::{fold_map::FoldMap, BufferRow};
4407 let mut d = doc("m {\n a\n b\n}\nafter");
4409 assert_eq!(d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect::<Vec<_>>(), vec![(0, 3)]);
4410 assert!(d.fold(BufferRow(0), BufferRow(3)));
4411 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));
4417 let e = d.buffer().point_to_offset(crate::Point::new(4, 0));
4418 d.edit(vec![EditOp::delete(s..e)]).unwrap();
4419 assert!(d.foldable_ranges().is_empty(), "no closing brace ⇒ nothing foldable");
4420 assert!(d.folds().is_empty(), "invalidated fold dropped, not left orphaning hidden rows");
4421 assert_eq!(
4422 FoldMap::new(d.folds(), d.brackets(), d.buffer()).display_row_count(),
4423 d.buffer().line_count(),
4424 "every row is visible again"
4425 );
4426 }
4427
4428 #[test]
4429 fn valid_fold_survives_edit_above() {
4430 use crate::{fold_map::FoldMap, BufferRow, SelectionSet};
4431 let mut d = doc("m {\n a\n b\n}\nafter");
4432 assert!(d.fold(BufferRow(0), BufferRow(3)));
4433 d.set_selections(SelectionSet::new(0));
4436 d.insert_text("top\n");
4437 assert_eq!(d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect::<Vec<_>>(), vec![(1, 4)]);
4438 assert_eq!(d.folds().len(), 1, "the still-valid fold is kept");
4439 let m = FoldMap::new(d.folds(), d.brackets(), d.buffer());
4440 assert_eq!(m.fold_at_header(BufferRow(1)), Some(BufferRow(4)));
4441 assert!(m.is_folded(BufferRow(2)) && m.is_folded(BufferRow(3)) && m.is_folded(BufferRow(4)));
4442 }
4443
4444 #[test]
4445 fn breaking_one_block_keeps_a_sibling_fold() {
4446 use crate::{fold_map::FoldMap, BufferRow};
4447 let mut d = doc("a {\n x\n}\nb {\n y\n}\nafter");
4450 assert_eq!(d.foldable_ranges().iter().map(|(h, l)| (h.0, l.0)).collect::<Vec<_>>(), vec![(0, 2), (3, 5)]);
4451 assert!(d.fold(BufferRow(0), BufferRow(2)));
4452 assert!(d.fold(BufferRow(3), BufferRow(5)));
4453 assert_eq!(d.folds().len(), 2);
4454 let s = d.buffer().point_to_offset(crate::Point::new(5, 0));
4457 let e = d.buffer().point_to_offset(crate::Point::new(6, 0));
4458 d.edit(vec![EditOp::delete(s..e)]).unwrap();
4459 assert_eq!(d.folds().len(), 1, "only the broken block's fold drops");
4460 let m = FoldMap::new(d.folds(), d.brackets(), d.buffer());
4461 assert_eq!(m.fold_at_header(BufferRow(0)), Some(BufferRow(2)), "sibling A still collapsed");
4462 assert_eq!(m.fold_at_header(BufferRow(3)), None, "B is no longer folded");
4463 }
4464
4465 #[test]
4466 fn fold_drops_when_its_interior_is_deleted() {
4467 use crate::{fold_map::FoldMap, BufferRow};
4468 let mut d = doc("L0\n{\nL2\nL3\n}\nL5");
4469 assert!(d.fold(BufferRow(1), BufferRow(4))); let start = d.buffer().point_to_offset(crate::Point::new(2, 0));
4472 let end = d.buffer().point_to_offset(crate::Point::new(5, 0));
4473 d.edit(vec![EditOp::delete(start..end)]).unwrap();
4474 assert!(d.folds().is_empty(), "fold dropped when its interior text is deleted");
4475 assert_eq!(FoldMap::new(d.folds(), d.brackets(), d.buffer()).display_row_count(), d.buffer().line_count());
4476 }
4477
4478 #[test]
4479 fn intel05_diagnostic_keeps_byte_offsets_on_a_multibyte_line() {
4480 use crate::{Diagnostic, Severity};
4485 let mut d = doc("aé=x");
4486 d.set_diagnostics(d.revision(), vec![Diagnostic::new(4..5, Severity::Error, "e")]);
4487 let span = d.diagnostics_in(0..100).next().unwrap().0;
4488 assert_eq!(span, 4..5, "diagnostics_in returns raw byte offsets, not cells");
4489 assert_eq!(crate::display_map::expand(&d.buffer().line(0), span.start, 4), 3);
4492 }
4493
4494 #[test]
4495 fn undo_resyncs_brackets_and_highlight_to_the_reverted_text() {
4496 const G: &str = "%YAML 1.2\n---\nname: T\nscope: source.t\ncontexts:\n main:\n - match: '\\w+'\n scope: keyword.t\n";
4497 const TH: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
4498<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4499<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>"#;
4500 let mut d = doc("()");
4501 d.set_syntax(
4502 crate::SyntaxDef::from_sublime_syntax(G).unwrap(),
4503 crate::TokenTheme::from_tm_theme(TH).unwrap(),
4504 );
4505 d.tokenize_highlight(d.buffer().line_count());
4506 d.set_selections(SelectionSet::new(1)); d.edit(vec![EditOp::insert(1, "[]\n")]).unwrap(); d.tokenize_highlight(d.buffer().line_count());
4509 assert_eq!((d.buffer().line_count(), d.brackets().all().len()), (2, 4));
4510 assert!(d.undo(), "there was something to undo");
4511 assert_eq!(d.buffer().text(), "()");
4512 assert_eq!((d.buffer().line_count(), d.brackets().all().len()), (1, 2));
4514 d.tokenize_highlight(d.buffer().line_count());
4515 assert!(d.highlight_line_spans(0).is_some());
4516 assert!(d.highlight_line_spans(1).is_none(), "no phantom line after undo");
4517 }
4518
4519 #[test]
4520 fn select_all_spans_the_whole_buffer_with_head_at_end() {
4521 let mut d = doc("abc\ndef"); d.select_all();
4523 assert_eq!(d.selections().len(), 1);
4524 let s = d.selections().newest();
4525 assert_eq!((s.start(), s.end(), s.head()), (0, 7, 7));
4526 }
4527
4528 fn query(text: &str) -> crate::FindQuery {
4529 crate::FindQuery { text: text.into(), case_sensitive: true }
4530 }
4531
4532 #[test]
4533 fn find_matches_ride_an_edit_above_them() {
4534 let mut d = doc("xx target"); d.set_find_query(Some(query("target")), 0);
4537 assert_eq!(d.find_matches_in(0..100).collect::<Vec<_>>(), vec![(3..9, false)]);
4538 d.set_selections(SelectionSet::new(0));
4539 d.edit(vec![EditOp::insert(0, "ab")]).unwrap(); assert_eq!(d.find_matches_in(0..100).collect::<Vec<_>>(), vec![(5..11, false)]);
4543 }
4544
4545 #[test]
4546 fn find_next_prev_start_from_the_caret_and_wrap() {
4547 let mut d = doc("foo bar foo baz foo"); d.set_find_query(Some(query("foo")), 0);
4550 assert_eq!((d.find_match_count(), d.active_find_match()), (3, None));
4551 d.set_selections(SelectionSet::new(5)); assert_eq!(d.find_next(0), Some(8..11)); assert_eq!(d.active_find_match(), Some(1));
4554 assert_eq!(d.find_next(0), Some(16..19)); assert_eq!(d.find_next(0), Some(0..3), "wraps to first");
4556 assert_eq!(d.find_prev(0), Some(16..19), "prev from first wraps to last");
4557 d.set_selections(SelectionSet::new(0));
4558 assert_eq!(d.find_prev(0), Some(16..19), "prev before all wraps to last");
4559 }
4560
4561 #[test]
4562 fn active_match_survives_a_rescan_at_the_same_start() {
4563 let mut d = doc("foo foo foo");
4568 d.set_find_query(Some(query("foo")), 0);
4569 d.set_selections(SelectionSet::new(4)); d.find_next(0);
4571 assert_eq!(d.active_find_match(), Some(1));
4572 d.set_selections(SelectionSet::new(11));
4573 d.edit(vec![EditOp::insert(11, " foo")]).unwrap(); assert_eq!(
4575 (d.find_match_count(), d.active_find_match()),
4576 (4, Some(1)),
4577 "current at the commit, active untouched"
4578 );
4579 assert!(!d.maybe_rescan_find(1000), "nothing stale to rescan (uncapped)");
4580 assert_eq!((d.find_match_count(), d.active_find_match()), (4, Some(1)));
4581 }
4582
4583 #[test]
4584 fn find_matches_ride_undo_and_redo_through_the_shared_mover() {
4585 let mut d = doc("foo bar");
4589 d.set_find_query(Some(query("foo")), 0);
4590 assert_eq!(d.find_matches_in(0..100).next().unwrap().0, 0..3);
4591 d.set_selections(SelectionSet::new(0));
4592 d.edit(vec![EditOp::insert(0, "xy")]).unwrap(); assert_eq!(d.find_matches_in(0..100).next().unwrap().0, 2..5);
4594 d.undo(); assert_eq!(d.buffer().text(), "foo bar");
4596 assert_eq!(d.find_matches_in(0..100).next().unwrap().0, 0..3, "rode the undo");
4597 d.redo(); assert_eq!(d.find_matches_in(0..100).next().unwrap().0, 2..5, "rode the redo");
4599 }
4600
4601 #[test]
4602 fn find_count_reflects_live_matches_after_a_collapse_delete() {
4603 let mut d = doc("foo foo"); d.set_find_query(Some(query("foo")), 0);
4608 assert_eq!(d.find_match_count(), 2);
4609 d.edit(vec![EditOp::delete(0..4)]).unwrap(); assert_eq!(d.buffer().text(), "foo");
4611 assert_eq!(d.find_match_count(), 1, "collapsed match not counted");
4612 assert_eq!(d.find_matches_in(0..100).count(), 1, "count agrees with render");
4613 }
4614
4615 #[test]
4616 fn find_navigation_requests_a_reveal() {
4617 let mut d = doc("foo bar foo");
4618 d.set_find_query(Some(query("foo")), 0);
4619 let before = d.reveal_seq();
4620 assert!(d.find_next(0).is_some());
4621 assert!(d.reveal_seq() > before, "navigation bumps the reveal request");
4622 }
4623
4624 #[test]
4625 fn clearing_the_query_drops_every_match() {
4626 let mut d = doc("foo foo");
4627 d.set_find_query(Some(query("foo")), 0);
4628 assert_eq!(d.find_match_count(), 2);
4629 d.set_find_query(None, 0);
4630 assert_eq!((d.find_match_count(), d.find_query().is_none()), (0, true));
4631 assert_eq!(d.find_matches_in(0..100).count(), 0);
4632 }
4633
4634 #[test]
4635 fn edits_repair_the_match_set_immediately() {
4636 let mut d = doc("foo");
4640 d.set_find_query(Some(query("foo")), 0);
4641 d.edit(vec![EditOp::insert(0, "foo ")]).unwrap(); assert_eq!(d.find_match_count(), 2, "current immediately — no debounce");
4643 assert!(!d.maybe_rescan_find(50), "nothing to rescan");
4644 assert!(!d.maybe_rescan_find(1_000), "uncapped: never rescans");
4645 assert_eq!(d.find_match_count(), 2);
4646 }
4647
4648 fn match_ids(d: &Document) -> Vec<crate::decorations::DecorationId> {
4652 d.decorations
4653 .decorations_in(0..u32::MAX)
4654 .filter(|r| matches!(r.kind, DecorationKind::FindMatch))
4655 .map(|r| r.id)
4656 .collect()
4657 }
4658
4659 fn match_ranges(d: &Document) -> Vec<Range<u32>> {
4661 d.find_matches_in(0..u32::MAX).map(|(r, _)| r).collect()
4662 }
4663
4664 fn xorshift(mut state: u64) -> impl FnMut() -> u64 {
4666 move || {
4667 state ^= state << 13;
4668 state ^= state >> 7;
4669 state ^= state << 17;
4670 state
4671 }
4672 }
4673
4674 #[test]
4675 fn repair_preserves_far_match_ids() {
4676 let mut d = doc("foo bar foo bar baz padding padding foo");
4680 d.set_find_query(Some(query("foo")), 0);
4681 let before = match_ids(&d);
4682 assert_eq!(before.len(), 3);
4683 d.edit(vec![EditOp::insert(22, "zz")]).unwrap();
4685 assert!(!d.maybe_rescan_find(1_000), "always current: nothing to rescan");
4686 assert_eq!(match_ids(&d), before, "untouched matches keep their ids");
4687 assert_eq!(match_ranges(&d), vec![0..3, 8..11, 38..41]);
4688 }
4689
4690 #[test]
4691 fn repair_insert_creates_match_in_window() {
4692 let mut d = doc("fo bar");
4693 d.set_find_query(Some(query("foo")), 0);
4694 assert_eq!(d.find_match_count(), 0);
4695 d.edit(vec![EditOp::insert(2, "o")]).unwrap(); assert_eq!(match_ranges(&d), vec![0..3], "current at the commit");
4697 }
4698
4699 #[test]
4700 fn repair_delete_joins_halves_creates_match() {
4701 let mut d = doc("foXo bar");
4703 d.set_find_query(Some(query("foo")), 0);
4704 assert_eq!(d.find_match_count(), 0);
4705 d.edit(vec![EditOp::delete(2..3)]).unwrap(); assert_eq!(match_ranges(&d), vec![0..3]);
4707 }
4708
4709 #[test]
4710 fn repair_destroys_straddling_match() {
4711 let mut d = doc("afoob");
4713 d.set_find_query(Some(query("foo")), 0);
4714 assert_eq!(match_ranges(&d), vec![1..4]);
4715 d.edit(vec![EditOp::delete(3..5)]).unwrap(); assert_eq!(d.find_match_count(), 0);
4717 }
4718
4719 #[test]
4720 fn repair_interior_insert_invalidates_match() {
4721 let mut d = doc("foo");
4724 d.set_find_query(Some(query("foo")), 0);
4725 assert_eq!(d.find_match_count(), 1);
4726 d.edit(vec![EditOp::insert(1, "x")]).unwrap(); assert_eq!(d.find_match_count(), 0);
4728 }
4729
4730 #[test]
4731 fn repair_multi_edit_transaction() {
4732 let mut d = doc("fo bar fo baz");
4734 d.set_find_query(Some(query("foo")), 0);
4735 d.edit(vec![EditOp::insert(2, "o"), EditOp::insert(9, "o")]).unwrap();
4736 assert_eq!(d.buffer().text(), "foo bar foo baz");
4737 assert_eq!(match_ranges(&d), vec![0..3, 8..11]);
4738 }
4739
4740 #[test]
4741 fn repair_equals_full_scan_property() {
4742 let fq = query("ab");
4747 let mut next = xorshift(0x243F_6A88_85A3_08D3);
4748 let alphabet = ['a', 'b', ' '];
4749 let mut d = doc(&"ab ba a b ab ".repeat(8));
4750 d.set_find_query(Some(fq.clone()), 0);
4751 for step in 0..300 {
4752 let len = d.buffer().len();
4753 match next() % 3 {
4754 0 => {
4755 let at = (next() % u64::from(len + 1)) as u32;
4756 let n = 1 + next() % 3;
4757 let s: String = (0..n).map(|_| alphabet[(next() % 3) as usize]).collect();
4758 d.edit(vec![EditOp::insert(at, s)]).unwrap();
4759 }
4760 1 if len > 0 => {
4761 let a = (next() % u64::from(len)) as u32;
4762 let b = (a + 1 + (next() % 4) as u32).min(len);
4763 d.edit(vec![EditOp::delete(a..b)]).unwrap();
4764 }
4765 _ => {
4766 let a = (next() % u64::from(len + 1)) as u32;
4768 let b = (next() % u64::from(len + 1)) as u32;
4769 let (a, b) = (a.min(b), a.max(b));
4770 if a == b {
4771 d.edit(vec![EditOp::insert(a, "ab")]).unwrap();
4772 } else {
4773 d.edit(vec![EditOp::insert(a, "ba"), EditOp::insert(b, "ab")]).unwrap();
4774 }
4775 }
4776 }
4777 let (oracle, _) = crate::find::scan(&d.buffer().text(), &fq);
4778 assert_eq!(match_ranges(&d), oracle, "step {step} diverged from the oracle");
4779 }
4780 }
4781
4782 #[test]
4783 fn repair_self_overlapping_needle_is_maximal_and_valid() {
4784 let mut next = xorshift(0x9E37_79B9_7F4A_7C15);
4790 let alphabet = ['a', ' '];
4791 let mut d = doc(&"aaa a aaaa aa ".repeat(6));
4792 d.set_find_query(Some(query("aa")), 0);
4793 for step in 0..300 {
4794 let len = d.buffer().len();
4795 if next().is_multiple_of(2) || len == 0 {
4796 let at = (next() % u64::from(len + 1)) as u32;
4797 let n = 1 + next() % 3;
4798 let s: String = (0..n).map(|_| alphabet[(next() % 2) as usize]).collect();
4799 d.edit(vec![EditOp::insert(at, s)]).unwrap();
4800 } else {
4801 let a = (next() % u64::from(len)) as u32;
4802 let b = (a + 1 + (next() % 3) as u32).min(len);
4803 d.edit(vec![EditOp::delete(a..b)]).unwrap();
4804 }
4805 let live = match_ranges(&d);
4806 let text = d.buffer().text();
4807 let bytes = text.as_bytes();
4808 for (i, r) in live.iter().enumerate() {
4809 assert_eq!(
4810 &bytes[r.start as usize..r.end as usize],
4811 b"aa",
4812 "step {step}: stale match {r:?}"
4813 );
4814 if i > 0 {
4815 assert!(live[i - 1].end <= r.start, "step {step}: overlapping matches");
4816 }
4817 }
4818 for p in 0..bytes.len().saturating_sub(1) {
4819 if &bytes[p..p + 2] == b"aa" {
4820 let p = p as u32;
4821 assert!(
4822 live.iter().any(|r| r.start < p + 2 && p < r.end),
4823 "step {step}: placement at {p} misses every match (not maximal)"
4824 );
4825 }
4826 }
4827 }
4828 }
4829
4830 #[test]
4831 fn active_survives_untouched() {
4832 let mut d = doc("foo bar foo");
4835 d.set_find_query(Some(query("foo")), 0);
4836 d.find_next(0); let id = d.find.active_id().expect("a match is active");
4838 d.edit(vec![EditOp::insert(7, "x")]).unwrap(); assert_eq!(d.find.active_id(), Some(id), "untouched active keeps its id");
4840 assert_eq!((d.find_match_count(), d.active_find_match()), (2, Some(0)));
4841 }
4842
4843 #[test]
4844 fn active_transfers_same_start() {
4845 let mut d = doc("foo bar");
4848 d.set_find_query(Some(query("foo")), 0);
4849 d.find_next(0); let old = d.find.active_id().expect("a match is active");
4851 d.edit(vec![EditOp::insert(4, "x")]).unwrap(); let new = d.find.active_id().expect("active transferred");
4853 assert_ne!(new, old, "the decoration was re-minted by the repair");
4854 assert_eq!(d.active_find_match(), Some(0), "still the first match");
4855 assert_eq!(match_ranges(&d), vec![0..3]);
4856 }
4857
4858 #[test]
4859 fn active_cleared_when_destroyed() {
4860 let mut d = doc("foo bar foo");
4861 d.set_find_query(Some(query("foo")), 0);
4862 d.find_next(0); d.edit(vec![EditOp::delete(0..2)]).unwrap(); assert_eq!(d.find.active_id(), None, "destroyed active clears");
4865 assert_eq!((d.find_match_count(), d.active_find_match()), (1, None));
4866 }
4867
4868 #[test]
4869 fn count_and_order_consistent_after_repair() {
4870 let mut d = doc("foo foo foo");
4871 d.set_find_query(Some(query("foo")), 0);
4872 d.set_selections(SelectionSet::new(4));
4873 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();
4876 let ranges: Vec<Range<u32>> = all.iter().map(|(r, _)| r.clone()).collect();
4877 assert_eq!(ranges, vec![0..3, 4..7, 8..11, 12..15]);
4878 assert_eq!(d.find_match_count(), all.len());
4879 assert_eq!(d.active_find_match(), Some(2));
4881 assert_eq!(all.iter().position(|(_, active)| *active), Some(2));
4882 }
4883
4884 #[test]
4885 fn active_start_tracks_the_decoration_under_random_edits() {
4886 let base = "foo foo bar foo baz foo qux foo end";
4897 let mut rng = 0xDEAD_BEEF_0000_1111u64;
4898 let mut next = move || {
4899 rng ^= rng << 13;
4900 rng ^= rng >> 7;
4901 rng ^= rng << 17;
4902 rng
4903 };
4904 for trial in 0..400u32 {
4905 let mut d = doc(base);
4906 d.set_find_query(Some(query("foo")), 0);
4907 d.set_selections(SelectionSet::new(10));
4910 d.find_next(0);
4911 for _step in 0..14 {
4912 if d.find.active_id().is_none() {
4913 d.set_selections(SelectionSet::new(1));
4915 d.find_next(0);
4916 }
4917 let len = d.buffer().len();
4918 let at = match d.find.active_start() {
4921 Some(s) if next() % 3 == 0 => s,
4922 _ => (next() % u64::from(len + 1)) as u32,
4923 };
4924 match next() % 3 {
4925 0 => {
4926 let _ = d.edit(vec![EditOp::insert(at, "z")]);
4927 }
4928 1 => {
4929 let end = (at + 1 + (next() % 3) as u32).min(len);
4930 if end > at {
4931 let _ = d.edit(vec![EditOp::delete(at..end)]);
4932 }
4933 }
4934 _ => {
4935 let a = (next() % u64::from(len + 1)) as u32;
4937 let b = (next() % u64::from(len + 1)) as u32;
4938 let (lo, hi) = (a.min(b), a.max(b));
4939 if lo != hi {
4940 d.set_selections(SelectionSet::from_ranges(&[(lo, lo), (hi, hi)], 0));
4941 d.insert_text("q");
4942 }
4943 }
4944 }
4945 let want = d
4946 .find
4947 .active_id()
4948 .and_then(|id| d.decorations.decoration_range(id))
4949 .map(|r| r.start);
4950 assert_eq!(
4951 d.find.active_start(),
4952 want,
4953 "trial {trial}: active_start drifted from its decoration's live start",
4954 );
4955 }
4956 }
4957 }
4958
4959 #[test]
4960 fn capped_scan_keeps_a_document_prefix() {
4961 let text = "x ".repeat(crate::FIND_MATCH_CAP + 50);
4964 let mut d = doc(&text);
4965 d.set_find_query(Some(query("x")), 0);
4966 assert!(d.find.capped());
4967 let live = match_ranges(&d);
4968 assert_eq!(live.len(), crate::FIND_MATCH_CAP);
4969 assert_eq!(live.last().cloned(), Some(19_998..19_999), "prefix, not a sample");
4970 }
4971
4972 #[test]
4973 fn typing_while_capped_does_not_rescan_the_match_set() {
4974 let text = "x ".repeat(crate::FIND_MATCH_CAP + 50);
4981 let mut d = doc(&text);
4982 d.set_find_query(Some(query("x")), 0);
4983 assert!(d.find.capped());
4984 assert_eq!(d.find_match_count(), crate::FIND_MATCH_CAP);
4985 d.set_selections(crate::SelectionSet::new(0));
4986 crate::perf::reset();
4987 d.type_char('z'); let work = crate::perf::meter();
4989 assert_eq!(d.find_match_count(), crate::FIND_MATCH_CAP, "still capped");
4990 assert!(
4991 work < 2_000,
4992 "capped keystroke charged {work} work units (≈FIND_MATCH_CAP ⇒ the O(M) cap scan survived)"
4993 );
4994 }
4995
4996 #[test]
4997 fn capped_trims_tail_and_coverage() {
4998 let text = "x ".repeat(crate::FIND_MATCH_CAP);
5002 let mut d = doc(&text);
5003 d.set_find_query(Some(query("x")), 0);
5004 assert!(!d.find.capped());
5005 d.edit(vec![EditOp::insert(1_000, "xxx")]).unwrap();
5006 assert!(d.find.capped(), "over the cap after the repair");
5007 let live = match_ranges(&d);
5008 assert_eq!(live.len(), crate::FIND_MATCH_CAP, "trimmed back to the cap");
5009 assert!(live.windows(2).all(|w| w[0].end <= w[1].start), "still non-overlapping");
5010 assert_eq!(live.iter().filter(|r| (1_000..1_004).contains(&r.start)).count(), 4);
5012 assert_eq!(live.last().map(|r| r.start), Some(19_995), "tail trimmed");
5013 }
5014
5015 #[test]
5016 fn capped_refill_is_debounced() {
5017 let text = "x ".repeat(crate::FIND_MATCH_CAP + 50);
5018 let mut d = doc(&text);
5019 d.set_find_query(Some(query("x")), 0); d.edit(vec![EditOp::delete(0..10)]).unwrap();
5022 assert_eq!(d.find_match_count(), crate::FIND_MATCH_CAP - 5);
5023 assert!(!d.maybe_rescan_find(50), "inside the debounce window");
5024 assert_eq!(d.find_match_count(), crate::FIND_MATCH_CAP - 5);
5025 assert!(d.maybe_rescan_find(100), "past the window → refill");
5026 assert_eq!(d.find_match_count(), crate::FIND_MATCH_CAP, "coverage re-grew");
5027 assert!(!d.maybe_rescan_find(300), "idle after the refill: no re-scan");
5028 }
5029
5030 #[test]
5031 fn undo_redo_match_set_equals_fresh_scan() {
5032 let fq = query("ab");
5035 let mut d = doc("ab ab ba ab");
5036 d.set_find_query(Some(fq.clone()), 0);
5037 let check = |d: &Document, at: &str| {
5038 let (oracle, _) = crate::find::scan(&d.buffer().text(), &fq);
5039 assert_eq!(match_ranges(d), oracle, "diverged after {at}");
5040 };
5041 d.edit(vec![EditOp::insert(2, "ab")]).unwrap();
5042 check(&d, "edit 1");
5043 d.edit(vec![EditOp::delete(0..3)]).unwrap();
5044 check(&d, "edit 2");
5045 d.edit(vec![EditOp::insert(5, "b a")]).unwrap();
5046 check(&d, "edit 3");
5047 assert!(d.undo());
5048 check(&d, "undo 1");
5049 assert!(d.undo());
5050 check(&d, "undo 2");
5051 assert!(d.undo());
5052 check(&d, "undo 3");
5053 assert!(d.redo());
5054 check(&d, "redo 1");
5055 assert!(d.redo());
5056 check(&d, "redo 2");
5057 }
5058
5059 #[test]
5060 fn case_fold_repair() {
5061 let fq = crate::FindQuery { text: "foo".into(), case_sensitive: false };
5063 let mut d = doc("FO bar");
5064 d.set_find_query(Some(fq), 0);
5065 assert_eq!(d.find_match_count(), 0);
5066 d.edit(vec![EditOp::insert(2, "O")]).unwrap(); assert_eq!(match_ranges(&d), vec![0..3]);
5068 d.edit(vec![EditOp::insert(0, "f")]).unwrap(); assert_eq!(match_ranges(&d), vec![1..4], "re-anchored, still folded");
5070 }
5071}