Skip to main content

indigo_core/
selection.rs

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    /// Merge sorted ranges sharing a grapheme into the earlier range. Ranges that merely touch
85    /// stay separate. The primary range follows the range it merges into.
86    ///
87    /// When to merge:
88    ///
89    /// - Merge after selection motions and seeks (e.g. `h`/`j`/`k`/`l`/`f`/`t`/`x`, mouse
90    ///   clicks/drags, etc).
91    /// - Merge after `<a-o>`/`<a-O>`, which edit the buffer without entering insert mode.
92    /// - Merge before `d`/`c`/`r`, but not after, so cursors that coincide after deleting stay
93    ///   separate.
94    /// - Merge after undo and redo. Kakoune instead replaces the selection with the modified
95    ///   ranges, which are always merged.
96    /// - Merge before `R`. Kakoune doesn't; it replaces one range at a time, so coincident
97    ///   cursors replace successive graphemes. Merging keeps `R` a single edit, for now.
98    ///
99    /// When not to merge:
100    ///
101    /// - Never merge in insert mode or when entering it (`i`/`a`/`I`/`A`/`o`/`O`), so coincident
102    ///   cursors each insert their own text. Kakoune does merge on insert-mode cursor movement
103    ///   (e.g. arrows), which Indigo doesn't support yet.
104    /// - Never merge after `;`, `<a-;>`, or `<a-:>`.
105    /// - Never merge after `s` or `<a-s>`; they only sort.
106    ///
107    /// Except for `R`, we follow Kakoune.
108    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    /// Whether every range in `other` lies within the range at the same index in `self`.
127    #[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    /// The exclusive byte offset one past a range's last grapheme.
247    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    /// Select every regex match within each range. Empty (zero-width) matches become
317    /// one-grapheme selections at the match position, as in Kakoune. Returns `false` and leaves
318    /// the selection unmodified when nothing matched.
319    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                // Skip an empty match one past the searched slice (Kakoune does the same);
332                // there is no grapheme there to select.
333                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                    // An inclusive range can't be zero-width; select the grapheme at the
339                    // match position.
340                    tail_index
341                } else {
342                    // The inclusive end is the last grapheme the match touches.
343                    rope.prev_grapheme_boundary(start + needle.end())
344                        .unwrap_or(0)
345                        .max(tail_index)
346                };
347                // Preserve the original range's direction, like Kakoune's `keep_direction`.
348                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            // TODO: Assert grapheme length is 1 (i.e. collapsed)
480            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    /// Paste every value at each range, then select each value separately (like Kakoune).
499    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        // The last value pasted at the primary range becomes primary.
525        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            // Unlike Kakoune; see `SelectionState::merge_overlapping`.
532            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        // The last byte of the output so far, to keep the `Text` invariant.
546        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            // The text to replace; empty when only inserting.
550            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                // Like Kakoune, replacing text with itself isn't an edit.
579                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                // The edit reaches the end of the text; restore the `Text` invariant.
590                ops.insert("\n");
591                output_position += 1;
592                last_byte = Some(b'\n');
593            }
594            // Like Kakoune, pasting selects a restored final newline, but replacing doesn't.
595            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        // A paste that changes nothing only moves the selection; keep it out of the history.
606        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            // An empty value selects the insertion point.
611            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                // The final newline was replaced; restore the `Text` invariant.
651                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 the deletions reach the end of the text, the last surviving byte must still be a
700        // newline; otherwise re-insert one. Walk back through ranges that abut each other to
701        // find the last surviving byte.
702        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                // Deleting the final newline would break the `Text` invariant.
736                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        // Selects from "b" through the "o" on the third line (inclusive).
837        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        // Kakoune's `r` replaces newlines too; replacing the final newline re-inserts one.
866        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        // Two abutting ranges together delete through the end of the text ("\nb\n" in total);
880        // the last surviving byte ('a') is not a newline, so one is re-inserted.
881        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        // `x*` matches empty at every position; each empty match becomes a one-grapheme
895        // selection, and the empty match at the end of the searched slice is skipped.
896        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(&regex));
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(&regex));
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(&regex));
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        // Selecting everything (each line's newline included) splits into whole lines.
942        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}