1use crate::{
2 cursor::CursorState,
3 range::{Range, RangeEdit, RangeMove, RangeSnapshot, RangeState},
4 rope::{LINE_TYPE, RegexCursorInput, RopeExt as _},
5 text::Text,
6};
7use indigo_kernel::edit::{self, Edit};
8use indigo_wrap::{WMut, WRef, Wrap, WrapMut, WrapRef};
9use regex_cursor::engines::meta::Regex;
10use ropey::{Rope, RopeSlice};
11use std::{cmp::max, iter::zip, mem, thread};
12use thiserror::Error;
13
14#[derive(Debug, Error)]
15pub enum Error {
16 #[error("Selection has no ranges")]
17 Empty,
18
19 #[error("Primary range index {index} is not within {length} ranges")]
20 PrimaryOutOfRange { index: usize, length: usize },
21
22 #[error("Range {index} starts before the range preceding it")]
23 Unsorted { index: usize },
24
25 #[error("Error from range {index}")]
26 Range {
27 index: usize,
28 #[source]
29 source: anyhow::Error,
30 },
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum PasteMode {
35 Before,
36 After,
37 Replace,
38}
39
40fn is_linewise(values: &[String], paste_mode: PasteMode) -> bool {
41 paste_mode != PasteMode::Replace && values.iter().all(|value| value.ends_with('\n'))
42}
43
44#[derive(Clone)]
45pub struct SelectionState {
46 pub ranges: Vec<RangeState>,
47 pub primary_range: usize,
48}
49
50impl SelectionState {
51 pub fn transform(&mut self, ops: &Edit, text: &Rope) {
52 let mut offsets: Vec<usize> = self
53 .ranges
54 .iter()
55 .flat_map(|range| [range.tail.byte_index, range.head.byte_index])
56 .collect();
57
58 ops.map_positions(&mut offsets, edit::Bias::Forward);
59
60 for (i, range) in self.ranges.iter_mut().enumerate() {
61 range.tail.byte_index = text
62 .snap_to_grapheme_start(offsets[i * 2])
63 .expect("Text is never empty");
64 range.head.byte_index = text
65 .snap_to_grapheme_start(offsets[i * 2 + 1])
66 .expect("Text is never empty");
67 range.goal_column = None;
68 }
69 }
70
71 pub fn sort(&mut self) {
72 let mut ranges: Vec<_> = mem::take(&mut self.ranges)
73 .into_iter()
74 .enumerate()
75 .collect();
76 ranges.sort_by_key(|(_, range)| (range.start().byte_index, range.end().byte_index));
77 self.primary_range = ranges
78 .iter()
79 .position(|(i, _)| *i == self.primary_range)
80 .expect("Primary range index is always kept valid");
81 self.ranges = ranges.into_iter().map(|(_, range)| range).collect();
82 }
83
84 pub fn merge_overlapping(&mut self) {
109 let mut primary_range = 0;
110 for (i, range) in mem::take(&mut self.ranges).into_iter().enumerate() {
111 if let Some(last) = self.ranges.last_mut()
112 && range.start().byte_index <= last.end().byte_index
113 {
114 let end = max(last.end().byte_index, range.end().byte_index);
115 *last = last.with_bounds(last.start().byte_index, end);
116 } else {
117 self.ranges.push(range);
118 }
119 if i == self.primary_range {
120 primary_range = self.ranges.len() - 1;
121 }
122 }
123 self.primary_range = primary_range;
124 }
125
126 #[must_use]
128 pub fn contains(&self, other: &Self) -> bool {
129 self.ranges.len() == other.ranges.len()
130 && zip(&self.ranges, &other.ranges).all(|(outer, inner)| outer.contains(inner))
131 }
132
133 #[must_use]
134 pub fn save(&self, text: &Text) -> SelectionSnapshot {
135 let ranges = self.ranges.iter().map(|range| range.save(text)).collect();
136 let primary_range = self.primary_range;
137 SelectionSnapshot {
138 ranges,
139 primary_range,
140 }
141 }
142}
143
144impl Default for SelectionState {
145 fn default() -> Self {
146 Self {
147 ranges: vec![RangeState::default()],
148 primary_range: 0,
149 }
150 }
151}
152
153pub struct SelectionSnapshot {
154 pub ranges: Vec<RangeSnapshot>,
155 pub primary_range: usize,
156}
157
158impl SelectionSnapshot {
159 #[must_use]
160 pub fn restore(&self, text: &Text) -> Option<SelectionState> {
161 let ranges = self.ranges.iter().try_fold(
162 Vec::with_capacity(self.ranges.len()),
163 |mut ranges, range| {
164 ranges.push(range.restore(text)?);
165 Some(ranges)
166 },
167 )?;
168 let primary_range = self.primary_range;
169 Some(SelectionState {
170 ranges,
171 primary_range,
172 })
173 }
174}
175
176#[must_use]
177pub struct SelectionView<'a, S: Wrap, T: Wrap> {
178 state: S::Wrap<'a, SelectionState>,
179 text: T::Wrap<'a, Text>,
180 #[expect(clippy::type_complexity)]
181 on_drop: Option<Box<dyn FnOnce(&mut Self) + 'a>>,
182}
183
184pub type Selection<'a> = SelectionView<'a, WRef, WRef>;
185
186pub type SelectionMove<'a> = SelectionView<'a, WMut, WRef>;
187
188pub type SelectionEdit<'a> = SelectionView<'a, WMut, WMut>;
189
190impl<'a, S: Wrap, T: Wrap> SelectionView<'a, S, T> {
191 pub fn on_drop(mut self, f: impl FnOnce(&mut Self) + 'a) -> Self {
192 self.on_drop = Some(Box::new(f));
193 self
194 }
195}
196
197impl<'a, S: WrapRef, T: WrapRef> SelectionView<'a, S, T> {
198 pub fn new(
199 text: T::WrapRef<'a, Text>,
200 state: S::WrapRef<'a, SelectionState>,
201 ) -> anyhow::Result<Self> {
202 let selection_view = Self {
203 state,
204 text,
205 on_drop: None,
206 };
207 selection_view.assert_invariants()?;
208 Ok(selection_view)
209 }
210
211 pub fn text(&self) -> &Text {
212 &self.text
213 }
214
215 pub fn state(&self) -> &SelectionState {
216 &self.state
217 }
218
219 pub fn get(&self, index: usize) -> Option<Range<'_>> {
220 let range_state = self.state.ranges.get(index)?;
221 let range = Range::new(&self.text, range_state)
222 .expect("Selection text and range state are always kept valid");
223 Some(range)
224 }
225
226 pub fn get_primary(&self) -> Range<'_> {
227 self.get(self.state.primary_range)
228 .expect("Primary range index is always kept valid")
229 }
230
231 pub fn for_each(&self, mut f: impl FnMut(usize, Range<'_>)) {
232 for (i, range_state) in self.state.ranges.iter().enumerate() {
233 let range = Range::new(&self.text, range_state)
234 .expect("Selection text and range state are always kept valid");
235 f(i, range);
236 }
237 }
238
239 pub fn contents(&self) -> impl Iterator<Item = RopeSlice<'_>> {
240 self.state.ranges.iter().map(|range| {
241 let start = range.start().byte_index;
242 self.text.slice(start..self.end_exclusive(range))
243 })
244 }
245
246 fn end_exclusive(&self, range: &RangeState) -> usize {
248 self.text
249 .next_grapheme_boundary(range.end().byte_index)
250 .expect("Range end is always on a grapheme")
251 }
252
253 pub fn assert_invariants(&self) -> anyhow::Result<()> {
254 if self.state.ranges.is_empty() {
255 anyhow::bail!(Error::Empty);
256 }
257 if self.state.primary_range >= self.state.ranges.len() {
258 anyhow::bail!(Error::PrimaryOutOfRange {
259 index: self.state.primary_range,
260 length: self.state.ranges.len(),
261 });
262 }
263 for (index, range_state) in self.state.ranges.iter().enumerate() {
264 let _ = Range::new(&self.text, range_state)
265 .map_err(|source| Error::Range { index, source })?;
266 }
267 let ranges = &self.state.ranges;
268 for (index, (previous, range)) in zip(ranges, &ranges[1..]).enumerate() {
269 if range.start().byte_index < previous.start().byte_index {
270 anyhow::bail!(Error::Unsorted { index: index + 1 });
271 }
272 }
273 Ok(())
274 }
275}
276
277impl<S: WrapMut, T: WrapRef> SelectionView<'_, S, T> {
278 fn unchecked_get_move(&mut self, index: usize) -> Option<RangeMove<'_>> {
279 let range_state = self.state.ranges.get_mut(index)?;
280 let range = RangeMove::new(&self.text, range_state)
281 .expect("Selection text and range state are always kept valid")
282 .on_drop(|range| range.assert_invariants().unwrap());
283 Some(range)
284 }
285
286 pub fn for_each_move(&mut self, mut f: impl FnMut(RangeMove<'_>)) {
287 for i in 0..self.state.ranges.len() {
288 let range = self.unchecked_get_move(i).unwrap();
289 f(range);
290 }
291 self.state.sort();
292 }
293
294 pub fn merge_overlapping(&mut self) {
295 self.state.merge_overlapping();
296 }
297
298 pub fn keep_primary(&mut self) {
299 if self.state.primary_range != 0 {
300 let index = self.state.primary_range;
301 self.state.ranges.swap(0, index);
302 self.state.primary_range = 0;
303 }
304 self.state.ranges.truncate(1);
305 }
306
307 pub fn rotate_forward(&mut self, count: usize) {
308 self.state.primary_range = (self.state.primary_range + count) % self.state.ranges.len();
309 }
310
311 pub fn rotate_backward(&mut self, count: usize) {
312 let length = self.state.ranges.len();
313 self.state.primary_range = (self.state.primary_range + length - (count % length)) % length;
314 }
315
316 pub fn select_regex(&mut self, regex: &Regex) -> bool {
320 let mut ranges = Vec::new();
321 for range_state in &self.state.ranges {
322 let rope = self.text.rope();
323 let start = range_state.start().byte_index;
324 let end_exclusive = rope
325 .next_grapheme_boundary(range_state.end().byte_index)
326 .expect("Range end is always on a grapheme");
327 let slice_length = end_exclusive - start;
328 let input =
329 regex_cursor::Input::new(RegexCursorInput::from(rope.slice(start..end_exclusive)));
330 for needle in regex.find_iter(input) {
331 if needle.start() >= slice_length {
334 continue;
335 }
336 let tail_index = rope.floor_grapheme_boundary(start + needle.start());
337 let head_index = if needle.start() == needle.end() {
338 tail_index
341 } else {
342 rope.prev_grapheme_boundary(start + needle.end())
344 .unwrap_or(0)
345 .max(tail_index)
346 };
347 let (tail, head) = if range_state.is_forward() {
349 (tail_index, head_index)
350 } else {
351 (head_index, tail_index)
352 };
353 ranges.push(RangeState {
354 tail: CursorState { byte_index: tail },
355 head: CursorState { byte_index: head },
356 goal_column: None,
357 });
358 }
359 }
360 let matched = !ranges.is_empty();
361 if matched {
362 self.state.primary_range = ranges.len() - 1;
363 self.state.ranges = ranges;
364 self.state.sort();
365 }
366 matched
367 }
368
369 pub fn select_all(&mut self) {
370 let mut range = RangeState::default();
371 range.head.byte_index = self
372 .text
373 .last_grapheme_start()
374 .expect("Text is never empty");
375 self.state.ranges = vec![range];
376 self.state.primary_range = 0;
377 }
378
379 pub fn split_into_lines(&mut self) {
380 self.split_at(|range| {
381 let start = range.start().byte_index();
382 let end = range.end().byte_index();
383
384 let start_line = range.text().byte_to_line_idx(start, LINE_TYPE);
385 let end_line = range.text().byte_to_line_idx(end, LINE_TYPE);
386
387 ((start_line + 1)..=end_line)
388 .map(|line| range.text().line_to_byte_idx(line, LINE_TYPE))
389 .collect()
390 });
391 }
392
393 fn split_at(&mut self, mut f: impl FnMut(Range<'_>) -> Vec<usize>) {
394 let old_primary_range = self.state.primary_range;
395 let old_primary_head = self.state.ranges[old_primary_range].head.byte_index;
396 let mut primary_range = 0;
397 let mut ranges = Vec::new();
398
399 for range_state in &self.state.ranges {
400 let range = Range::new(&self.text, range_state)
401 .expect("Selection text and range state are always kept valid");
402
403 let start = range.start().byte_index();
404 let end = range.end().byte_index();
405
406 let mut boundaries = f(range);
407 boundaries.sort_unstable();
408 boundaries.dedup();
409 boundaries.retain(|boundary| start < *boundary && *boundary <= end);
410
411 let mut segment_start = start;
412 for boundary in boundaries {
413 let segment_end = self
414 .text
415 .prev_grapheme_boundary(boundary)
416 .expect("Cut point is after the range start");
417 ranges.push(range_state.with_bounds(segment_start, segment_end));
418 segment_start = boundary;
419 }
420 ranges.push(range_state.with_bounds(segment_start, end));
421 }
422
423 for (i, range) in ranges.iter().enumerate() {
424 if range.head.byte_index == old_primary_head {
425 primary_range = i;
426 break;
427 }
428 }
429
430 self.state.ranges = ranges;
431 self.state.primary_range = primary_range;
432 self.state.sort();
433 }
434
435 fn invalidate_goal_columns(&mut self) {
436 for i in 0..self.state.ranges.len() {
437 let mut range = self.unchecked_get_move(i).unwrap();
438 range.invalidate_goal_column();
439 }
440 }
441}
442
443impl<S: WrapMut, T: WrapMut> SelectionView<'_, S, T> {
444 fn unchecked_get_edit(&mut self, index: usize) -> Option<RangeEdit<'_>> {
445 let range_state = self.state.ranges.get_mut(index)?;
446 let range = RangeEdit::new(&mut self.text, range_state)
447 .expect("Selection text and range state are always kept valid")
448 .on_drop(|range| range.assert_invariants().unwrap());
449 Some(range)
450 }
451
452 pub fn for_each_edit(&mut self, mut f: impl FnMut(RangeEdit<'_>)) {
453 for i in 0..self.state.ranges.len() {
454 let version = self.text.version();
455 let range = self.unchecked_get_edit(i).unwrap();
456 f(range);
457 if let Some(opss) = self.text.ops_since(version) {
458 for j in 0..self.state.ranges.len() {
459 if i == j {
460 continue;
461 }
462 for ops in opss {
463 self.state.ranges[j].transform(ops, &self.text);
464 }
465 }
466 }
467 }
468 self.state.sort();
469 }
470
471 pub fn insert_char(&mut self, char: char) -> Edit {
472 self.insert(&char.to_string())
473 }
474
475 pub fn insert(&mut self, text: &str) -> Edit {
476 let mut ops = Edit::new();
477 let mut previous = 0;
478 for range in &self.state.ranges {
479 ops.retain(range.start().byte_index - previous);
481 ops.insert(text);
482 previous = range.start().byte_index;
483 }
484 ops.retain_rest(&self.text)
485 .expect("Operations fit within text");
486 self.text.apply(&ops).expect("Operations are well formed");
487 self.state.transform(&ops, &self.text);
488 self.invalidate_goal_columns();
489 ops
490 }
491
492 pub fn paste(&mut self, values: &[String], paste_mode: PasteMode) -> Edit {
493 const NOTHING: &[String] = &[String::new()];
494 let values = if values.is_empty() { NOTHING } else { values };
495 self.paste_with(values, paste_mode, is_linewise(values, paste_mode))
496 }
497
498 pub fn paste_all(&mut self, values: &[String], paste_mode: PasteMode) -> Edit {
500 if values.is_empty() {
501 return Edit::identity(&self.text);
502 }
503 let linewise = is_linewise(values, paste_mode);
504 let ops = self.paste_with(&[values.concat()], paste_mode, linewise);
505 let mut ranges = Vec::with_capacity(self.state.ranges.len() * values.len());
506 for range in &self.state.ranges {
507 let mut start = range.start().byte_index;
508 for value in values {
509 let last = max(start, (start + value.len()).saturating_sub(1));
510 ranges.push(
511 range.with_bounds(
512 self.text
513 .snap_to_grapheme_start(start)
514 .expect("Text is never empty"),
515 self.text
516 .snap_to_grapheme_start(last)
517 .expect("Text is never empty"),
518 ),
519 );
520 start += value.len();
521 }
522 }
523 self.state.ranges = ranges;
524 self.state.primary_range = self.state.primary_range * values.len() + values.len() - 1;
526 ops
527 }
528
529 fn paste_with(&mut self, values: &[String], paste_mode: PasteMode, linewise: bool) -> Edit {
530 if paste_mode == PasteMode::Replace {
531 self.state.merge_overlapping();
533 }
534 debug_assert!(
535 paste_mode == PasteMode::Before
536 || self
537 .state
538 .ranges
539 .is_sorted_by_key(|range| range.end().byte_index),
540 "pasting after relies on selection ranges' ends being sorted",
541 );
542 let mut ops = Edit::new();
543 let mut previous = 0;
544 let mut output_position = 0;
545 let mut last_byte = None;
547 let mut spans = Vec::with_capacity(self.state.ranges.len());
548 for (i, range) in self.state.ranges.iter().enumerate() {
549 let at = |offset| offset..offset;
551 let replaced = match (paste_mode, linewise) {
552 (PasteMode::Before, false) => at(range.start().byte_index),
553 (PasteMode::Before, true) => {
554 let line = self
555 .text
556 .byte_to_line_idx(range.start().byte_index, LINE_TYPE);
557 at(self.text.line_to_byte_idx(line, LINE_TYPE))
558 }
559 (PasteMode::After, false) => at(self.end_exclusive(range)),
560 (PasteMode::After, true) => {
561 let line = self
562 .text
563 .byte_to_line_idx(range.end().byte_index, LINE_TYPE);
564 at(self.text.line_to_byte_idx(line + 1, LINE_TYPE))
565 }
566 (PasteMode::Replace, _) => range.start().byte_index..self.end_exclusive(range),
567 };
568 let retained = replaced.start - previous;
569 ops.retain(retained);
570 output_position += retained;
571 if retained > 0 {
572 last_byte = Some(self.text.byte(replaced.start - 1));
573 }
574 let start = output_position;
575 let value = &values[i % values.len()];
576 let old = self.text.slice(replaced.clone());
577 if old == value.as_str() {
578 ops.retain(old.len());
580 } else {
581 ops.delete(&old.to_string());
582 ops.insert(value);
583 }
584 output_position += value.len();
585 if let Some(&byte) = value.as_bytes().last() {
586 last_byte = Some(byte);
587 }
588 if replaced.end == self.text.len() && last_byte != Some(b'\n') {
589 ops.insert("\n");
591 output_position += 1;
592 last_byte = Some(b'\n');
593 }
594 let end = if paste_mode == PasteMode::Replace {
596 start + value.len()
597 } else {
598 output_position
599 };
600 spans.push((start, end));
601 previous = replaced.end;
602 }
603 ops.retain_rest(&self.text)
604 .expect("Operations fit within text");
605 if ops != Edit::identity(&self.text) {
607 self.text.apply(&ops).expect("Operations are well formed");
608 }
609 for (range, (start, end_exclusive)) in zip(&mut self.state.ranges, spans) {
610 let last = max(start, end_exclusive.saturating_sub(1));
612 let start = self
613 .text
614 .snap_to_grapheme_start(start)
615 .expect("Text is never empty");
616 let end = self
617 .text
618 .snap_to_grapheme_start(last)
619 .expect("Text is never empty");
620 *range = range.with_bounds(start, end);
621 }
622 self.invalidate_goal_columns();
623 ops
624 }
625
626 pub fn replace_each(&mut self, byte: u8) -> Edit {
627 self.state.merge_overlapping();
628 let replacement = char::from(byte).to_string();
629 let mut ops = Edit::new();
630 let mut previous = 0;
631 let mut output_position = 0;
632 let mut ranges = Vec::with_capacity(self.state.ranges.len());
633 for range in &self.state.ranges {
634 let start = range.start().byte_index;
635 let end_exclusive = self
636 .text
637 .next_grapheme_boundary(range.end().byte_index)
638 .expect("Range end is always on a grapheme");
639 let retained = start - previous;
640 ops.retain(retained);
641 output_position += retained;
642 let replacement_start = output_position;
643 for grapheme in self.text.rope().slice(start..end_exclusive).graphemes() {
644 ops.delete(&grapheme.to_string());
645 ops.insert(&replacement);
646 output_position += replacement.len();
647 }
648 ranges.push(range.with_bounds(replacement_start, output_position - replacement.len()));
649 if end_exclusive == self.text.len() {
650 ops.insert("\n");
652 }
653 previous = end_exclusive;
654 }
655 ops.retain_rest(&self.text)
656 .expect("Operations fit within text");
657 self.text.apply(&ops).expect("Operations are well formed");
658 self.state.ranges = ranges;
659 self.invalidate_goal_columns();
660 ops
661 }
662
663 pub fn delete_before(&mut self) -> Edit {
664 let mut ops = Edit::new();
665 let mut previous = 0;
666 for range in &self.state.ranges {
667 let start = range.start().byte_index;
668 if let Some(prev_boundary) = self.text.prev_grapheme_boundary(start)
669 && prev_boundary >= previous
670 {
671 ops.retain(prev_boundary - previous);
672 ops.delete(&self.text.slice(prev_boundary..start).to_string());
673 previous = start;
674 }
675 }
676 ops.retain_rest(&self.text)
677 .expect("Operations fit within text");
678 self.text.apply(&ops).expect("Operations are well formed");
679 self.state.transform(&ops, &self.text);
680 self.invalidate_goal_columns();
681 ops
682 }
683
684 pub fn delete(&mut self) -> Edit {
685 self.state.merge_overlapping();
686 let spans: Vec<(usize, usize)> = self
687 .state
688 .ranges
689 .iter()
690 .map(|range| (range.start().byte_index, self.end_exclusive(range)))
691 .collect();
692 let mut ops = Edit::new();
693 let mut previous = 0;
694 for &(start, end_exclusive) in &spans {
695 ops.retain(start - previous);
696 ops.delete(&self.text.slice(start..end_exclusive).to_string());
697 previous = end_exclusive;
698 }
699 if let Some(&(_, last_end_exclusive)) = spans.last()
703 && last_end_exclusive == self.text.len()
704 {
705 let mut position = spans[spans.len() - 1].0;
706 for &(start, end_exclusive) in spans[..spans.len() - 1].iter().rev() {
707 if end_exclusive == position {
708 position = start;
709 } else {
710 break;
711 }
712 }
713 if position == 0 || self.text.byte(position - 1) != b'\n' {
714 ops.insert("\n");
715 }
716 }
717 ops.retain_rest(&self.text)
718 .expect("Operations fit within text");
719 self.text.apply(&ops).expect("Operations are well formed");
720 self.state.transform(&ops, &self.text);
721 self.invalidate_goal_columns();
722 ops
723 }
724
725 pub fn delete_after(&mut self) -> Edit {
726 let mut ops = Edit::new();
727 let mut previous = 0;
728 for range in &self.state.ranges {
729 let end = range.end().byte_index;
730 let next_boundary = self
731 .text
732 .next_grapheme_boundary(end)
733 .expect("Range end is always on a grapheme");
734 if next_boundary == self.text.len() {
735 continue;
737 }
738 if end >= previous {
739 ops.retain(end - previous);
740 ops.delete(&self.text.slice(end..next_boundary).to_string());
741 previous = next_boundary;
742 }
743 }
744 ops.retain_rest(&self.text)
745 .expect("Operations fit within text");
746 self.text.apply(&ops).expect("Operations are well formed");
747 self.state.transform(&ops, &self.text);
748 self.invalidate_goal_columns();
749 ops
750 }
751}
752
753impl<S: Wrap, T: Wrap> Drop for SelectionView<'_, S, T> {
754 fn drop(&mut self) {
755 if !thread::panicking()
756 && let Some(f) = self.on_drop.take()
757 {
758 f(self);
759 }
760 }
761}
762
763#[cfg(test)]
764mod tests {
765 use super::*;
766 use hegel::generators as gs;
767 use std::collections::BTreeSet;
768
769 fn range(tail: usize, head: usize) -> RangeState {
770 RangeState {
771 tail: CursorState { byte_index: tail },
772 head: CursorState { byte_index: head },
773 goal_column: None,
774 }
775 }
776
777 fn split_ranges(text: &str, ranges: Vec<RangeState>) -> SelectionState {
778 let mut text = Text::from(text);
779 let mut state = SelectionState {
780 ranges,
781 primary_range: 0,
782 };
783 SelectionEdit::new(&mut text, &mut state)
784 .unwrap()
785 .split_into_lines();
786 state
787 }
788
789 #[hegel::test(test_cases = 5_000)]
790 fn sort_and_merge_overlapping_ranges(tc: hegel::TestCase) {
791 let count = tc.draw(gs::integers::<usize>().min_value(1).max_value(8));
792 let ranges: Vec<RangeState> = (0..count)
793 .map(|_| {
794 let tail = tc.draw(gs::integers::<usize>().max_value(20));
795 let head = tc.draw(gs::integers::<usize>().max_value(20));
796 range(tail, head)
797 })
798 .collect();
799 let primary_range = tc.draw(gs::integers::<usize>().max_value(count - 1));
800 let covered = |ranges: &[RangeState]| -> BTreeSet<usize> {
801 ranges
802 .iter()
803 .flat_map(|range| range.start().byte_index..=range.end().byte_index)
804 .collect()
805 };
806
807 let mut state = SelectionState {
808 ranges: ranges.clone(),
809 primary_range,
810 };
811 state.sort();
812 state.merge_overlapping();
813
814 for (previous, range) in zip(&state.ranges, &state.ranges[1..]) {
815 assert!(
816 previous.end().byte_index < range.start().byte_index,
817 "Ranges are sorted and share no grapheme"
818 );
819 }
820 assert_eq!(covered(&ranges), covered(&state.ranges));
821 assert!(state.ranges[state.primary_range].contains(&ranges[primary_range]));
822 assert!(
823 state
824 .ranges
825 .iter()
826 .all(|range| ranges.iter().any(|original| {
827 original.start().byte_index == range.start().byte_index
828 && original.is_forward() == range.is_forward()
829 })),
830 "A merged range keeps the direction of its earliest range"
831 );
832 }
833
834 #[test]
835 fn split_into_lines_keeps_partial_endpoints() {
836 let state = split_ranges("abcdef\nghijkl\nmnopqr\n", vec![range(1, 15)]);
838
839 assert_eq!(state.primary_range, 2);
840 assert_eq!(state.ranges.len(), 3);
841 assert_eq!(state.ranges[0].tail.byte_index, 1);
842 assert_eq!(state.ranges[0].head.byte_index, 6);
843 assert_eq!(state.ranges[1].tail.byte_index, 7);
844 assert_eq!(state.ranges[1].head.byte_index, 13);
845 assert_eq!(state.ranges[2].tail.byte_index, 14);
846 assert_eq!(state.ranges[2].head.byte_index, 15);
847 }
848
849 #[test]
850 fn split_into_lines_preserves_backward_direction() {
851 let state = split_ranges("abcdef\nghijkl\nmnopqr\n", vec![range(15, 1)]);
852
853 assert_eq!(state.primary_range, 0);
854 assert_eq!(state.ranges.len(), 3);
855 assert_eq!(state.ranges[0].tail.byte_index, 6);
856 assert_eq!(state.ranges[0].head.byte_index, 1);
857 assert_eq!(state.ranges[1].tail.byte_index, 13);
858 assert_eq!(state.ranges[1].head.byte_index, 7);
859 assert_eq!(state.ranges[2].tail.byte_index, 15);
860 assert_eq!(state.ranges[2].head.byte_index, 14);
861 }
862
863 #[test]
864 fn replace_each_replaces_newlines() {
865 let mut text = Text::from("ab\ncd\n");
867 let mut state = SelectionState::default();
868 let mut selection = SelectionEdit::new(&mut text, &mut state).unwrap();
869 selection.select_all();
870 selection.replace_each(b'X');
871 drop(selection);
872 assert_eq!(&text.to_string(), "XXXXXX\n");
873 assert_eq!(state.ranges[0].tail.byte_index, 0);
874 assert_eq!(state.ranges[0].head.byte_index, 5);
875 }
876
877 #[test]
878 fn delete_abutting_ranges_keeps_trailing_newline() {
879 let mut text = Text::from("a\nb\n");
882 let mut state = SelectionState {
883 ranges: vec![range(1, 1), range(2, 3)],
884 primary_range: 0,
885 };
886 let mut selection = SelectionEdit::new(&mut text, &mut state).unwrap();
887 selection.delete();
888 drop(selection);
889 assert_eq!(&text.to_string(), "a\n");
890 }
891
892 #[test]
893 fn select_regex_empty_matches_select_one_grapheme() {
894 let regex = Regex::new("x*").unwrap();
897 let mut text = Text::from("abc\n");
898 let mut state = SelectionState::default();
899 let mut selection = SelectionEdit::new(&mut text, &mut state).unwrap();
900 selection.select_all();
901 assert!(selection.select_regex(®ex));
902 drop(selection);
903 let positions: Vec<(usize, usize)> = state
904 .ranges
905 .iter()
906 .map(|range| (range.tail.byte_index, range.head.byte_index))
907 .collect();
908 assert_eq!(positions, vec![(0, 0), (1, 1), (2, 2), (3, 3)]);
909 }
910
911 #[test]
912 fn select_regex_no_match_leaves_selection_unmodified() {
913 let regex = Regex::new("xyz").unwrap();
914 let mut text = Text::from("abc\n");
915 let mut state = SelectionState::default();
916 let mut selection = SelectionEdit::new(&mut text, &mut state).unwrap();
917 selection.select_all();
918 assert!(!selection.select_regex(®ex));
919 drop(selection);
920 assert_eq!(state.ranges.len(), 1);
921 assert_eq!(state.ranges[0].tail.byte_index, 0);
922 assert_eq!(state.ranges[0].head.byte_index, 3);
923 }
924
925 #[test]
926 fn select_regex_match_end_is_inclusive() {
927 let regex = Regex::new("o+").unwrap();
928 let mut text = Text::from("foo bar\n");
929 let mut state = SelectionState::default();
930 let mut selection = SelectionEdit::new(&mut text, &mut state).unwrap();
931 selection.select_all();
932 assert!(selection.select_regex(®ex));
933 drop(selection);
934 assert_eq!(state.ranges.len(), 1);
935 assert_eq!(state.ranges[0].tail.byte_index, 1);
936 assert_eq!(state.ranges[0].head.byte_index, 2);
937 }
938
939 #[test]
940 fn split_into_lines_splits_full_lines() {
941 let state = split_ranges("a\nb\nc\n", vec![range(0, 5)]);
943
944 assert_eq!(state.primary_range, 2);
945 assert_eq!(state.ranges.len(), 3);
946 assert_eq!(state.ranges[0].tail.byte_index, 0);
947 assert_eq!(state.ranges[0].head.byte_index, 1);
948 assert_eq!(state.ranges[1].tail.byte_index, 2);
949 assert_eq!(state.ranges[1].head.byte_index, 3);
950 assert_eq!(state.ranges[2].tail.byte_index, 4);
951 assert_eq!(state.ranges[2].head.byte_index, 5);
952 }
953}