Skip to main content

indigo_core/
range.rs

1use crate::{
2    cursor::{Cursor, CursorMove, CursorSnapshot, CursorState, GoalColumn},
3    rope::RopeExt as _,
4    text::Text,
5};
6use indigo_kernel::edit::Edit;
7use indigo_wrap::{WBox, WMut, WRef, Wrap, WrapMut, WrapRef};
8use ropey::{Rope, RopeSlice};
9use std::{
10    cmp::max,
11    mem,
12    ops::{Bound, RangeBounds},
13    thread,
14};
15use thiserror::Error;
16
17#[cfg(feature = "arbitrary")]
18use arbitrary::Arbitrary;
19
20#[derive(Debug, Error)]
21pub enum Error {
22    #[error("Error from tail")]
23    Tail(#[source] anyhow::Error),
24
25    #[error("Error from head")]
26    Head(#[source] anyhow::Error),
27}
28
29#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
30#[derive(Debug)]
31pub enum Action {
32    InvalidateGoalColumn,
33    ExtendTo(usize),
34    MoveTo(usize),
35    ExtendLeft(u8),
36    MoveLeft(u8),
37    ExtendRight(u8),
38    MoveRight(u8),
39    ExtendUp(u8),
40    MoveUp(u8),
41    ExtendDown(u8),
42    MoveDown(u8),
43    ExtendUntilPrevByte { byte: u8, count: u8 },
44    MoveUntilPrevByte { byte: u8, count: u8 },
45    ExtendOntoPrevByte { byte: u8, count: u8 },
46    MoveOntoPrevByte { byte: u8, count: u8 },
47    ExtendUntilNextByte { byte: u8, count: u8 },
48    MoveUntilNextByte { byte: u8, count: u8 },
49    ExtendOntoNextByte { byte: u8, count: u8 },
50    MoveOntoNextByte { byte: u8, count: u8 },
51    ExtendToStart,
52    MoveToStart,
53    ExtendToEnd,
54    MoveToEnd,
55    ExtendToBottom,
56    MoveToBottom,
57    ExtendToLineStart,
58    MoveToLineStart,
59    ExtendToLineNonBlankStart,
60    MoveToLineNonBlankStart,
61    ExtendUntilLineEnd,
62    MoveUntilLineEnd,
63    ExtendOntoLineEnd,
64    MoveOntoLineEnd,
65    ExpandToFullLines,
66    Flip,
67    FlipForward,
68    FlipBackward,
69    Collapse,
70    InsertChar(char),
71    Insert(String),
72    DeleteBefore,
73    Delete,
74    DeleteAfter,
75}
76
77#[derive(Clone, Default)]
78pub struct RangeState {
79    pub tail: CursorState,
80    pub head: CursorState,
81    pub goal_column: Option<GoalColumn>,
82}
83
84impl RangeState {
85    pub fn transform(&mut self, ops: &Edit, text: &Rope) {
86        self.tail.transform(ops, text);
87        self.head.transform(ops, text);
88        self.goal_column = None;
89    }
90
91    #[must_use]
92    pub fn start(&self) -> &CursorState {
93        if self.tail.byte_index <= self.head.byte_index {
94            &self.tail
95        } else {
96            &self.head
97        }
98    }
99
100    #[must_use]
101    pub fn end(&self) -> &CursorState {
102        if self.tail.byte_index <= self.head.byte_index {
103            &self.head
104        } else {
105            &self.tail
106        }
107    }
108
109    /// A collapsed range is considered forward.
110    #[must_use]
111    pub fn is_forward(&self) -> bool {
112        self.tail.byte_index <= self.head.byte_index
113    }
114
115    #[must_use]
116    pub fn contains(&self, other: &Self) -> bool {
117        self.start().byte_index <= other.start().byte_index
118            && other.end().byte_index <= self.end().byte_index
119    }
120
121    #[must_use]
122    pub fn with_bounds(&self, start: usize, end: usize) -> Self {
123        let (tail, head) = if self.is_forward() {
124            (
125                CursorState { byte_index: start },
126                CursorState { byte_index: end },
127            )
128        } else {
129            (
130                CursorState { byte_index: end },
131                CursorState { byte_index: start },
132            )
133        };
134        let goal_column = if head.byte_index == self.head.byte_index {
135            self.goal_column
136        } else {
137            None
138        };
139        Self {
140            tail,
141            head,
142            goal_column,
143        }
144    }
145
146    #[must_use]
147    pub fn resolve_goal_column(&self, text: &Text) -> GoalColumn {
148        self.goal_column
149            .unwrap_or_else(|| GoalColumn::Column(text.display_column(self.head.byte_index)))
150    }
151
152    #[must_use]
153    pub fn save(&self, text: &Text) -> RangeSnapshot {
154        RangeSnapshot {
155            tail: self.tail.save(text),
156            head: self.head.save(text),
157            goal_column: self.resolve_goal_column(text),
158        }
159    }
160}
161
162pub struct RangeSnapshot {
163    pub tail: CursorSnapshot,
164    pub head: CursorSnapshot,
165    pub goal_column: GoalColumn,
166}
167
168impl RangeSnapshot {
169    #[must_use]
170    pub fn restore(&self, text: &Text) -> Option<RangeState> {
171        let tail = self.tail.restore(text)?;
172        let head = self.head.restore(text)?;
173        Some(RangeState {
174            tail,
175            head,
176            goal_column: Some(self.goal_column),
177        })
178    }
179}
180
181#[must_use]
182pub struct RangeView<'a, S: Wrap, T: Wrap> {
183    state: S::Wrap<'a, RangeState>,
184    text: T::Wrap<'a, Text>,
185    #[expect(clippy::type_complexity)]
186    on_drop: Option<Box<dyn FnOnce(&mut Self) + 'a>>,
187}
188
189pub type Range<'a> = RangeView<'a, WRef, WRef>;
190
191pub type RangeMove<'a> = RangeView<'a, WMut, WRef>;
192
193pub type RangeEdit<'a> = RangeView<'a, WMut, WMut>;
194
195impl<'a, S: Wrap, T: Wrap> RangeView<'a, S, T> {
196    pub fn on_drop(mut self, f: impl FnOnce(&mut Self) + 'a) -> Self {
197        self.on_drop = Some(Box::new(f));
198        self
199    }
200}
201
202impl<'a, S: WrapRef, T: WrapRef> RangeView<'a, S, T> {
203    pub fn new(
204        text: T::WrapRef<'a, Text>,
205        state: S::WrapRef<'a, RangeState>,
206    ) -> anyhow::Result<Self> {
207        let range_view = Self {
208            state,
209            text,
210            on_drop: None,
211        };
212        range_view.assert_invariants()?;
213        Ok(range_view)
214    }
215
216    pub fn text(&self) -> &Text {
217        &self.text
218    }
219
220    pub fn state(&self) -> &RangeState {
221        &self.state
222    }
223
224    #[must_use]
225    pub fn byte_offsets(&self) -> (usize, usize) {
226        let start = self.state.start().byte_index;
227        let end = self
228            .text
229            .next_grapheme_boundary(self.state.end().byte_index)
230            .expect("Range end is always on a grapheme");
231        (start, end)
232    }
233
234    pub fn slice(&self) -> RopeSlice<'_> {
235        let (start, end) = self.byte_offsets();
236        self.text.slice(start..end)
237    }
238
239    pub fn tail(&self) -> Cursor<'_> {
240        Cursor::new(&self.text, &self.state.tail)
241            .expect("Range text and tail cursor state are always kept valid")
242    }
243
244    pub fn head(&self) -> Cursor<'_> {
245        Cursor::new(&self.text, &self.state.head)
246            .expect("Range text and head cursor state are always kept valid")
247    }
248
249    pub fn start(&self) -> Cursor<'_> {
250        if self.is_forward() {
251            self.tail()
252        } else {
253            self.head()
254        }
255    }
256
257    pub fn end(&self) -> Cursor<'_> {
258        if self.is_forward() {
259            self.head()
260        } else {
261            self.tail()
262        }
263    }
264
265    pub fn goal_column(&self) -> Option<GoalColumn> {
266        self.state.goal_column
267    }
268
269    pub fn byte_length(&self) -> usize {
270        let (start, end) = self.byte_offsets();
271        end - start
272    }
273
274    pub fn grapheme_length(&self) -> usize {
275        match self.byte_length() {
276            0 => unreachable!(),
277            1 => 1,
278            _ => self.slice().graphemes().count(),
279        }
280    }
281
282    /// A collapsed range is considered forward.
283    pub fn is_forward(&self) -> bool {
284        self.state.is_forward()
285    }
286
287    pub fn is_backward(&self) -> bool {
288        !self.is_forward()
289    }
290
291    pub fn is_touching<S2, T2>(&self, other: &RangeView<'_, S2, T2>) -> bool
292    where
293        S2: WrapRef,
294        T2: WrapRef,
295    {
296        let (self_start, self_end) = self.byte_offsets();
297        let (other_start, other_end) = other.byte_offsets();
298        self_end == other_start || other_end == self_start
299    }
300
301    pub fn is_overlapping<S2, T2>(&self, other: &RangeView<'_, S2, T2>) -> bool
302    where
303        S2: WrapRef,
304        T2: WrapRef,
305    {
306        self.state.start().byte_index <= other.state.end().byte_index
307            && other.state.start().byte_index <= self.state.end().byte_index
308    }
309
310    #[must_use]
311    pub fn save(&self) -> RangeSnapshot {
312        self.state.save(&self.text)
313    }
314
315    pub fn assert_invariants(&self) -> anyhow::Result<()> {
316        let _ = Cursor::new(&self.text, &self.state.tail).map_err(Error::Tail)?;
317        let _ = Cursor::new(&self.text, &self.state.head).map_err(Error::Head)?;
318        Ok(())
319    }
320}
321
322impl<S: WrapMut, T: WrapRef> RangeView<'_, S, T> {
323    fn tail_move(&mut self) -> CursorMove<'_> {
324        CursorMove::new(&self.text, &mut self.state.tail)
325            .expect("Range text and tail cursor state are always kept valid")
326            .on_drop(|cursor| cursor.assert_invariants().unwrap())
327    }
328
329    fn head_move(&mut self) -> CursorMove<'_> {
330        CursorMove::new(&self.text, &mut self.state.head)
331            .expect("Range text and head cursor state are always kept valid")
332            .on_drop(|cursor| cursor.assert_invariants().unwrap())
333    }
334
335    fn start_move(&mut self) -> CursorMove<'_> {
336        if self.is_forward() {
337            self.tail_move()
338        } else {
339            self.head_move()
340        }
341    }
342
343    fn end_move(&mut self) -> CursorMove<'_> {
344        if self.is_forward() {
345            self.head_move()
346        } else {
347            self.tail_move()
348        }
349    }
350
351    /// Should be called after performing any non-vertical movement.
352    pub fn invalidate_goal_column(&mut self) {
353        self.state.goal_column = None;
354    }
355
356    fn resolve_goal_column(&mut self) -> GoalColumn {
357        let goal_column = self.state.resolve_goal_column(&self.text);
358        self.state.goal_column = Some(goal_column);
359        goal_column
360    }
361
362    pub fn extend_to(&mut self, byte_index: usize) {
363        self.head_move().move_to(byte_index);
364        self.invalidate_goal_column();
365    }
366
367    pub fn move_to(&mut self, byte_index: usize) {
368        self.extend_to(byte_index);
369        self.collapse();
370    }
371
372    pub fn extend_left(&mut self, count: usize) {
373        self.head_move().move_left(count);
374        self.invalidate_goal_column();
375    }
376
377    pub fn move_left(&mut self, count: usize) {
378        self.extend_left(count);
379        self.collapse();
380    }
381
382    pub fn extend_right(&mut self, count: usize) {
383        self.head_move().move_right(count);
384        self.invalidate_goal_column();
385    }
386
387    pub fn move_right(&mut self, count: usize) {
388        self.extend_right(count);
389        self.collapse();
390    }
391
392    pub fn extend_up(&mut self, count: usize) {
393        if count == 0 {
394            return;
395        }
396        let goal_column = self.resolve_goal_column();
397        self.head_move().move_up(goal_column, count);
398    }
399
400    pub fn move_up(&mut self, count: usize) {
401        self.extend_up(count);
402        self.collapse();
403    }
404
405    pub fn extend_down(&mut self, count: usize) {
406        if count == 0 {
407            return;
408        }
409        let goal_column = self.resolve_goal_column();
410        self.head_move().move_down(goal_column, count);
411    }
412
413    pub fn move_down(&mut self, count: usize) {
414        self.extend_down(count);
415        self.collapse();
416    }
417
418    pub fn extend_until_prev_byte(&mut self, byte: u8, count: usize) {
419        if self.head_move().move_to_prev_byte(byte, count) {
420            self.head_move().move_right(1);
421        }
422        self.invalidate_goal_column();
423    }
424
425    pub fn move_until_prev_byte(&mut self, byte: u8, count: usize) {
426        self.collapse();
427        self.extend_until_prev_byte(byte, count);
428    }
429
430    pub fn extend_onto_prev_byte(&mut self, byte: u8, count: usize) {
431        self.head_move().move_to_prev_byte(byte, count);
432        self.invalidate_goal_column();
433    }
434
435    pub fn move_onto_prev_byte(&mut self, byte: u8, count: usize) {
436        self.collapse();
437        self.extend_onto_prev_byte(byte, count);
438    }
439
440    pub fn extend_until_next_byte(&mut self, byte: u8, count: usize) {
441        if self.head_move().move_to_next_byte(byte, count) {
442            self.head_move().move_left(1);
443        }
444        self.invalidate_goal_column();
445    }
446
447    pub fn move_until_next_byte(&mut self, byte: u8, count: usize) {
448        self.collapse();
449        self.extend_until_next_byte(byte, count);
450    }
451
452    pub fn extend_onto_next_byte(&mut self, byte: u8, count: usize) {
453        self.head_move().move_to_next_byte(byte, count);
454        self.invalidate_goal_column();
455    }
456
457    pub fn move_onto_next_byte(&mut self, byte: u8, count: usize) {
458        self.collapse();
459        self.extend_onto_next_byte(byte, count);
460    }
461
462    pub fn extend_to_start(&mut self) {
463        self.head_move().move_to_start();
464        self.invalidate_goal_column();
465    }
466
467    pub fn move_to_start(&mut self) {
468        self.extend_to_start();
469        self.collapse();
470    }
471
472    pub fn extend_to_end(&mut self) {
473        self.head_move().move_to_end();
474        self.invalidate_goal_column();
475    }
476
477    pub fn move_to_end(&mut self) {
478        self.extend_to_end();
479        self.collapse();
480    }
481
482    pub fn extend_to_bottom(&mut self) {
483        self.head_move().move_to_bottom();
484        self.invalidate_goal_column();
485    }
486
487    pub fn move_to_bottom(&mut self) {
488        self.extend_to_bottom();
489        self.collapse();
490    }
491
492    pub fn extend_to_line_start(&mut self) {
493        self.head_move().move_to_line_start();
494        self.invalidate_goal_column();
495    }
496
497    pub fn move_to_line_start(&mut self) {
498        self.extend_to_line_start();
499        self.collapse();
500    }
501
502    pub fn extend_to_line_non_blank_start(&mut self) {
503        self.head_move().move_to_line_non_blank_start();
504        self.invalidate_goal_column();
505    }
506
507    pub fn move_to_line_non_blank_start(&mut self) {
508        self.extend_to_line_non_blank_start();
509        self.collapse();
510    }
511
512    pub fn extend_until_line_end(&mut self) {
513        self.head_move().move_until_line_end();
514        self.state.goal_column = Some(GoalColumn::UntilLineEnd);
515    }
516
517    pub fn move_until_line_end(&mut self) {
518        self.extend_until_line_end();
519        self.collapse();
520    }
521
522    pub fn extend_onto_line_end(&mut self) {
523        self.head_move().move_to_line_end();
524        self.invalidate_goal_column();
525    }
526
527    pub fn move_onto_line_end(&mut self) {
528        self.extend_onto_line_end();
529        self.collapse();
530    }
531
532    pub fn expand_to_full_lines(&mut self) {
533        self.start_move().move_to_line_start();
534        self.end_move().move_to_line_end();
535        self.state.goal_column = Some(GoalColumn::OntoLineEnd);
536    }
537
538    fn set_bounds(&mut self, byte_range: impl RangeBounds<usize>) {
539        let start = match byte_range.start_bound() {
540            Bound::Included(n) => self
541                .text
542                .snap_to_grapheme_start(*n)
543                .expect("Text is never empty"),
544            // Technically reachable with a `(Bound, Bound)` tuple, but never produced by ranges
545            // syntax sugar.
546            Bound::Excluded(_) => unreachable!("excluded start bound"),
547            Bound::Unbounded => 0,
548        };
549        let end = match byte_range.end_bound() {
550            // The grapheme containing byte `n`.
551            Bound::Included(n) => self
552                .text
553                .snap_to_grapheme_start(*n)
554                .expect("Text is never empty"),
555            // The last grapheme starting before `n`. For an empty range this lands before
556            // `start`, but the `max` below corrects it.
557            Bound::Excluded(n) => self.text.prev_grapheme_boundary(*n).unwrap_or(0),
558            Bound::Unbounded => self
559                .text
560                .last_grapheme_start()
561                .expect("Text is never empty"),
562        };
563        let end = max(start, end);
564        *self.state = self.state.with_bounds(start, end);
565        self.invalidate_goal_column();
566    }
567
568    pub fn flip(&mut self) {
569        fn both(state: &mut RangeState) -> (&mut CursorState, &mut CursorState) {
570            (&mut state.tail, &mut state.head)
571        }
572        let (tail, head) = both(&mut self.state);
573        mem::swap(tail, head);
574        self.invalidate_goal_column();
575    }
576
577    pub fn flip_forward(&mut self) {
578        if self.is_backward() {
579            self.flip();
580        }
581    }
582
583    pub fn flip_backward(&mut self) {
584        if self.is_forward() {
585            self.flip();
586        }
587    }
588
589    pub fn collapse(&mut self) {
590        self.state.tail = self.state.head.clone();
591    }
592
593    pub fn restore(&mut self, snapshot: &RangeSnapshot) -> bool {
594        if let Some(state) = snapshot.restore(&self.text) {
595            *self.state = state;
596            true
597        } else {
598            false
599        }
600    }
601}
602
603impl<S: WrapMut, T: WrapMut> RangeView<'_, S, T> {
604    pub fn expand_to_outer_node(&mut self) -> bool {
605        let (start, end) = self.byte_offsets();
606        let Some(syntax) = self.text.syntax() else {
607            return false;
608        };
609        let Some(node) = syntax.outer_node(start..end) else {
610            return false;
611        };
612        let byte_range = node.byte_range();
613        self.set_bounds(byte_range);
614        true
615    }
616
617    pub fn shrink_to_inner_node(&mut self) -> bool {
618        let (start, end) = self.byte_offsets();
619        let Some(syntax) = self.text.syntax() else {
620            return false;
621        };
622        let Some(node) = syntax.inner_node(start..end) else {
623            return false;
624        };
625        let byte_range = node.byte_range();
626        self.set_bounds(byte_range);
627        true
628    }
629
630    /// Reshape for appending (Kakoune's `a`): collapse to the head, then move onto the following
631    /// grapheme so insertion lands after the original head. On the text's last grapheme there is
632    /// no following grapheme, so a newline is appended first (as Kakoune does) and the cursor
633    /// lands on it.
634    pub fn prepare_append(&mut self) -> Option<Edit> {
635        self.collapse();
636        if self.head().is_at_end() {
637            let mut ops = Edit::new();
638            ops.retain(self.text.len());
639            ops.insert("\n");
640            self.text.apply(&ops).expect("Operations are well formed");
641            self.state.transform(&ops, &self.text);
642            let last = self
643                .text
644                .last_grapheme_start()
645                .expect("Text is never empty");
646            self.state.tail.byte_index = last;
647            self.state.head.byte_index = last;
648            self.invalidate_goal_column();
649            Some(ops)
650        } else {
651            self.head_move().move_right(1);
652            self.collapse();
653            self.invalidate_goal_column();
654            None
655        }
656    }
657
658    pub fn insert_char(&mut self, char: char) -> Edit {
659        self.insert(&char.to_string())
660    }
661
662    /// Insert before the range's first grapheme. Both endpoints stay on their graphemes (i.e.
663    /// end up after the inserted text).
664    #[tracing::instrument(skip_all)]
665    pub fn insert(&mut self, text: &str) -> Edit {
666        debug_assert!(
667            self.grapheme_length() <= 1,
668            "Range collapsed before entering insert mode"
669        );
670        let mut ops = Edit::new();
671        ops.retain(self.state.start().byte_index);
672        ops.insert(text);
673        ops.retain_rest(&self.text)
674            .expect("Operations fit within text");
675        self.text.apply(&ops).expect("Operations are well formed");
676        self.state.transform(&ops, &self.text);
677        self.invalidate_goal_column();
678        ops
679    }
680
681    /// Delete the grapheme before the range's start.
682    #[tracing::instrument(skip_all)]
683    pub fn delete_before(&mut self) -> Option<Edit> {
684        debug_assert!(
685            self.grapheme_length() <= 1,
686            "Range collapsed before entering insert mode"
687        );
688        let start = self.state.start().byte_index;
689        let delete_start = self.text.prev_grapheme_boundary(start)?;
690        let mut ops = Edit::new();
691        ops.retain(delete_start);
692        ops.delete(&self.text.slice(delete_start..start).to_string());
693        ops.retain_rest(&self.text)
694            .expect("Operations fit within text");
695        self.text.apply(&ops).expect("Operations are well formed");
696        self.state.transform(&ops, &self.text);
697        self.invalidate_goal_column();
698        Some(ops)
699    }
700
701    /// Delete the selected graphemes. Deleting through the end of the text re-inserts the
702    /// invariant trailing newline.
703    #[tracing::instrument(skip_all)]
704    pub fn delete(&mut self) -> Edit {
705        let (start, end) = self.byte_offsets();
706        let mut ops = Edit::new();
707        ops.retain(start);
708        ops.delete(&self.text.slice(start..end).to_string());
709        if end == self.text.len() && (start == 0 || self.text.byte(start - 1) != b'\n') {
710            ops.insert("\n");
711        }
712        ops.retain_rest(&self.text)
713            .expect("Operations fit within text");
714        self.text.apply(&ops).expect("Operations are well formed");
715        self.state.transform(&ops, &self.text);
716        self.invalidate_goal_column();
717        ops
718    }
719
720    /// Delete the grapheme under the range's end cursor, unless it is the text's final newline.
721    #[tracing::instrument(skip_all)]
722    pub fn delete_after(&mut self) -> Option<Edit> {
723        debug_assert!(
724            self.grapheme_length() <= 1,
725            "Range collapsed before entering insert mode"
726        );
727        let end = self.state.end().byte_index;
728        let delete_end = self
729            .text
730            .next_grapheme_boundary(end)
731            .expect("Range end is always on a grapheme");
732        if delete_end == self.text.len() {
733            // Deleting the final newline would break the `Text` invariant.
734            return None;
735        }
736        let mut ops = Edit::new();
737        ops.retain(end);
738        ops.delete(&self.text.slice(end..delete_end).to_string());
739        ops.retain_rest(&self.text)
740            .expect("Operations fit within text");
741        self.text.apply(&ops).expect("Operations are well formed");
742        self.state.transform(&ops, &self.text);
743        self.invalidate_goal_column();
744        Some(ops)
745    }
746}
747
748#[expect(clippy::too_many_lines)]
749pub fn handle_action<S: WrapMut, T: WrapMut>(range: &mut RangeView<'_, S, T>, action: &Action) {
750    match action {
751        Action::InvalidateGoalColumn => {
752            range.invalidate_goal_column();
753        }
754        Action::ExtendTo(byte_index) => {
755            range.extend_to(*byte_index);
756        }
757        Action::MoveTo(byte_index) => {
758            range.move_to(*byte_index);
759        }
760        Action::ExtendLeft(count) => {
761            range.extend_left(usize::from(*count));
762        }
763        Action::MoveLeft(count) => {
764            range.move_left(usize::from(*count));
765        }
766        Action::ExtendRight(count) => {
767            range.extend_right(usize::from(*count));
768        }
769        Action::MoveRight(count) => {
770            range.move_right(usize::from(*count));
771        }
772        Action::ExtendUp(count) => {
773            range.extend_up(usize::from(*count));
774        }
775        Action::MoveUp(count) => {
776            range.move_up(usize::from(*count));
777        }
778        Action::ExtendDown(count) => {
779            range.extend_down(usize::from(*count));
780        }
781        Action::MoveDown(count) => {
782            range.move_down(usize::from(*count));
783        }
784        Action::ExtendUntilPrevByte { byte, count } => {
785            range.extend_until_prev_byte(*byte, usize::from(*count));
786        }
787        Action::MoveUntilPrevByte { byte, count } => {
788            range.move_until_prev_byte(*byte, usize::from(*count));
789        }
790        Action::ExtendOntoPrevByte { byte, count } => {
791            range.extend_onto_prev_byte(*byte, usize::from(*count));
792        }
793        Action::MoveOntoPrevByte { byte, count } => {
794            range.move_onto_prev_byte(*byte, usize::from(*count));
795        }
796        Action::ExtendUntilNextByte { byte, count } => {
797            range.extend_until_next_byte(*byte, usize::from(*count));
798        }
799        Action::MoveUntilNextByte { byte, count } => {
800            range.move_until_next_byte(*byte, usize::from(*count));
801        }
802        Action::ExtendOntoNextByte { byte, count } => {
803            range.extend_onto_next_byte(*byte, usize::from(*count));
804        }
805        Action::MoveOntoNextByte { byte, count } => {
806            range.move_onto_next_byte(*byte, usize::from(*count));
807        }
808        Action::ExtendToStart => {
809            range.extend_to_start();
810        }
811        Action::MoveToStart => {
812            range.move_to_start();
813        }
814        Action::ExtendToEnd => {
815            range.extend_to_end();
816        }
817        Action::MoveToEnd => {
818            range.move_to_end();
819        }
820        Action::ExtendToBottom => {
821            range.extend_to_bottom();
822        }
823        Action::MoveToBottom => {
824            range.move_to_bottom();
825        }
826        Action::ExtendToLineStart => {
827            range.extend_to_line_start();
828        }
829        Action::MoveToLineStart => {
830            range.move_to_line_start();
831        }
832        Action::ExtendToLineNonBlankStart => {
833            range.extend_to_line_non_blank_start();
834        }
835        Action::MoveToLineNonBlankStart => {
836            range.move_to_line_non_blank_start();
837        }
838        Action::ExtendUntilLineEnd => {
839            range.extend_until_line_end();
840        }
841        Action::MoveUntilLineEnd => {
842            range.move_until_line_end();
843        }
844        Action::ExtendOntoLineEnd => {
845            range.extend_onto_line_end();
846        }
847        Action::MoveOntoLineEnd => {
848            range.move_onto_line_end();
849        }
850        Action::ExpandToFullLines => {
851            range.expand_to_full_lines();
852        }
853        Action::Flip => {
854            range.flip();
855        }
856        Action::FlipForward => {
857            range.flip_forward();
858        }
859        Action::FlipBackward => {
860            range.flip_backward();
861        }
862        Action::Collapse => {
863            range.collapse();
864        }
865        Action::InsertChar(c) => {
866            range.collapse();
867            range.insert_char(*c);
868        }
869        Action::Insert(text) => {
870            range.collapse();
871            range.insert(text);
872        }
873        Action::DeleteBefore => {
874            range.collapse();
875            range.delete_before();
876        }
877        Action::Delete => {
878            range.delete();
879        }
880        Action::DeleteAfter => {
881            range.collapse();
882            range.delete_after();
883        }
884    }
885}
886
887impl<R> TryFrom<(R, usize, usize)> for RangeView<'_, WBox, WBox>
888where
889    R: Into<Text>,
890{
891    type Error = anyhow::Error;
892    fn try_from((text, tail, head): (R, usize, usize)) -> anyhow::Result<Self> {
893        let text = Box::new(text.into());
894        let state = Box::new(RangeState {
895            tail: CursorState { byte_index: tail },
896            head: CursorState { byte_index: head },
897            goal_column: None,
898        });
899        Self::new(text, state)
900    }
901}
902
903impl<S: Wrap, T: Wrap> Drop for RangeView<'_, S, T> {
904    fn drop(&mut self) {
905        if !thread::panicking()
906            && let Some(f) = self.on_drop.take()
907        {
908            f(self);
909        }
910    }
911}
912
913#[cfg(test)]
914mod tests {
915    use super::*;
916
917    #[hegel::test(test_cases = 3_000)]
918    fn vertical_movement_resolves_and_reuses_goal_column(tc: hegel::TestCase) {
919        use hegel::generators as gs;
920
921        let goal = tc.draw(gs::integers::<usize>().min_value(1).max_value(100));
922        let short_line_length = tc.draw(gs::integers::<usize>().max_value(goal));
923        let first_line = "a".repeat(goal + 1);
924        let short_line = "b".repeat(short_line_length);
925        let third_line = "c".repeat(goal + 1);
926        let text = format!("{first_line}\n{short_line}\n{third_line}\n");
927        let third_line_start = first_line.len() + 1 + short_line.len() + 1;
928        let mut range = RangeView::try_from((Text::from(text.as_str()), goal, goal)).unwrap();
929
930        assert_eq!(range.goal_column(), None);
931        range.move_down(1);
932        assert_eq!(range.goal_column(), Some(GoalColumn::Column(goal)));
933
934        range.move_down(1);
935        assert_eq!(range.head().byte_index(), third_line_start + goal);
936        assert_eq!(range.goal_column(), Some(GoalColumn::Column(goal)));
937
938        range.move_left(1);
939        assert_eq!(range.goal_column(), None);
940    }
941
942    #[test]
943    fn snapshot_materializes_unresolved_goal_column() {
944        let range = RangeView::try_from(("a\t界\n", 2, 2)).unwrap();
945
946        assert_eq!(range.goal_column(), None);
947        let snapshot = range.save();
948
949        assert_eq!(snapshot.goal_column, GoalColumn::Column(9));
950        assert_eq!(range.goal_column(), None);
951    }
952
953    #[test]
954    fn insert_changes_grapheme_boundary() {
955        // combining acute accent (´)
956        let mut text = Text::from("\u{0301}");
957        let mut state = RangeState::default();
958        let mut range = RangeEdit::new(&mut text, &mut state).unwrap();
959        range.insert("e");
960        range.assert_invariants().unwrap();
961    }
962
963    #[test]
964    fn invalid_range_constructor_returns_error() {
965        let range = RangeView::try_from(("~", usize::MAX, 0));
966        assert!(range.is_err());
967    }
968
969    #[test]
970    fn one_grapheme_range_is_minimum() {
971        let range = RangeView::try_from(("xy\n", 1, 1)).unwrap();
972        assert_eq!(range.grapheme_length(), 1);
973        assert_eq!(range.byte_length(), 1);
974        assert_eq!(&range.slice().to_string(), "y");
975        assert!(range.is_forward());
976    }
977
978    #[test]
979    fn flip_is_direction_swap() {
980        let mut range = RangeView::try_from(("abc\n", 0, 2)).unwrap();
981        assert!(range.is_forward());
982        assert_eq!(range.resolve_goal_column(), GoalColumn::Column(2));
983        range.flip();
984        assert!(range.is_backward());
985        assert_eq!(range.tail().byte_index(), 2);
986        assert_eq!(range.head().byte_index(), 0);
987        assert_eq!(range.goal_column(), None);
988        range.collapse();
989        assert!(range.is_forward());
990    }
991
992    #[test]
993    fn delete_one_grapheme_range_deletes_a_character() {
994        let mut range = RangeView::try_from(("abc\n", 1, 1)).unwrap();
995        range.delete();
996        assert_eq!(&range.text().to_string(), "ac\n");
997        assert_eq!(range.head().byte_index(), 1);
998    }
999
1000    #[test]
1001    fn delete_through_end_keeps_trailing_newline() {
1002        // Select "bc\n" (inclusive of the final newline) and delete.
1003        let mut range = RangeView::try_from(("abc\n", 1, 3)).unwrap();
1004        range.delete();
1005        assert_eq!(&range.text().to_string(), "a\n");
1006        range.assert_invariants().unwrap();
1007
1008        // Deleting everything leaves the empty text "\n".
1009        let mut range = RangeView::try_from(("abc\n", 0, 3)).unwrap();
1010        range.delete();
1011        assert_eq!(&range.text().to_string(), "\n");
1012        assert_eq!(range.head().byte_index(), 0);
1013    }
1014
1015    #[test]
1016    fn prepare_append_at_end_materializes_newline() {
1017        // Cursor on the final newline: Kakoune appends a real newline so the insertion point
1018        // exists, and the cursor lands on it.
1019        let mut range = RangeView::try_from(("ab\n", 2, 2)).unwrap();
1020        range.prepare_append();
1021        assert_eq!(&range.text().to_string(), "ab\n\n");
1022        assert_eq!(range.head().byte_index(), 3);
1023        range.insert("x");
1024        assert_eq!(&range.text().to_string(), "ab\nx\n");
1025
1026        // Mid-text `a` just moves onto the next grapheme.
1027        let mut range = RangeView::try_from(("ab\n", 0, 0)).unwrap();
1028        range.prepare_append();
1029        assert_eq!(&range.text().to_string(), "ab\n");
1030        assert_eq!(range.head().byte_index(), 1);
1031    }
1032
1033    #[test]
1034    fn move_to_line_start_from_newline() {
1035        let mut text = Text::from("hello world\n");
1036        let mut state = RangeState::default();
1037        let mut range = RangeEdit::new(&mut text, &mut state).unwrap();
1038        range.move_until_line_end();
1039        range.move_right(1);
1040        assert_eq!(&range.slice().to_string(), "\n");
1041        range.move_to_line_start();
1042        assert_eq!(&range.slice().to_string(), "h");
1043    }
1044
1045    #[test]
1046    fn move_to_line_start_idempotent() {
1047        let mut text = Text::from("");
1048        let mut state = RangeState::default();
1049        let mut range = RangeEdit::new(&mut text, &mut state).unwrap();
1050        range.insert("x\ny");
1051
1052        range.move_to_line_start();
1053        let first_tail_index = range.tail().byte_index();
1054        let first_head_index = range.head().byte_index();
1055        assert_eq!(&range.slice().to_string(), "y");
1056
1057        range.move_to_line_start();
1058        let second_tail_index = range.tail().byte_index();
1059        let second_head_index = range.head().byte_index();
1060        assert_eq!(first_tail_index, second_tail_index);
1061        assert_eq!(first_head_index, second_head_index);
1062        assert_eq!(&range.slice().to_string(), "y");
1063    }
1064
1065    #[test]
1066    fn move_to_line_non_blank_start_idempotent() {
1067        let mut text = Text::from("");
1068        let mut state = RangeState::default();
1069        let mut range = RangeEdit::new(&mut text, &mut state).unwrap();
1070        range.insert(" x");
1071
1072        range.move_to_line_non_blank_start();
1073        let first_tail_index = range.tail().byte_index();
1074        let first_head_index = range.head().byte_index();
1075        assert_eq!(&range.slice().to_string(), "x");
1076
1077        range.move_to_line_non_blank_start();
1078        let second_tail_index = range.tail().byte_index();
1079        let second_head_index = range.head().byte_index();
1080        assert_eq!(first_tail_index, second_tail_index);
1081        assert_eq!(first_head_index, second_head_index);
1082        assert_eq!(&range.slice().to_string(), "x");
1083    }
1084
1085    #[test]
1086    fn move_to_line_non_blank_start_symmetric() {
1087        let mut text = Text::from("");
1088        let mut state = RangeState::default();
1089        let mut range = RangeEdit::new(&mut text, &mut state).unwrap();
1090        range.insert("    foo");
1091
1092        range.move_to_line_start();
1093        range.move_to_line_non_blank_start();
1094        let from_start = range.head().byte_index();
1095
1096        range.move_until_line_end();
1097        range.move_to_line_non_blank_start();
1098        let from_end = range.head().byte_index();
1099
1100        assert_eq!(from_start, from_end);
1101    }
1102
1103    #[hegel::test(test_cases = 1_000)]
1104    fn fuzz(tc: hegel::TestCase) {
1105        use hegel::{TestCase, generators as gs};
1106
1107        struct StateMachine {
1108            text: Text,
1109            state: RangeState,
1110        }
1111        #[hegel::state_machine]
1112        #[expect(clippy::needless_pass_by_value)]
1113        impl StateMachine {
1114            fn range(&mut self) -> RangeEdit<'_> {
1115                RangeEdit::new(&mut self.text, &mut self.state).expect("Range state kept valid")
1116            }
1117            fn count(tc: &TestCase) -> usize {
1118                tc.draw(gs::integers::<usize>().min_value(1).max_value(100))
1119            }
1120            #[rule]
1121            fn extend_to(&mut self, tc: TestCase) {
1122                let byte_index = tc.draw(gs::integers::<usize>().max_value(self.text.len()));
1123                self.range().extend_to(byte_index);
1124            }
1125            #[rule]
1126            fn move_to(&mut self, tc: TestCase) {
1127                let byte_index = tc.draw(gs::integers::<usize>().max_value(self.text.len()));
1128                self.range().move_to(byte_index);
1129            }
1130            #[rule]
1131            fn extend_left(&mut self, tc: TestCase) {
1132                let count = Self::count(&tc);
1133                self.range().extend_left(count);
1134            }
1135            #[rule]
1136            fn move_left(&mut self, tc: TestCase) {
1137                let count = Self::count(&tc);
1138                self.range().move_left(count);
1139            }
1140            #[rule]
1141            fn extend_right(&mut self, tc: TestCase) {
1142                let count = Self::count(&tc);
1143                self.range().extend_right(count);
1144            }
1145            #[rule]
1146            fn move_right(&mut self, tc: TestCase) {
1147                let count = Self::count(&tc);
1148                self.range().move_right(count);
1149            }
1150            #[rule]
1151            fn extend_up(&mut self, tc: TestCase) {
1152                let count = Self::count(&tc);
1153                self.range().extend_up(count);
1154            }
1155            #[rule]
1156            fn move_down(&mut self, tc: TestCase) {
1157                let count = Self::count(&tc);
1158                self.range().move_down(count);
1159            }
1160            #[rule]
1161            fn seek(&mut self, tc: TestCase) {
1162                let byte = tc.draw(gs::integers::<u8>());
1163                let count = Self::count(&tc);
1164                let mut range = self.range();
1165                match tc.draw(gs::integers::<u8>().max_value(7)) {
1166                    0 => range.extend_until_prev_byte(byte, count),
1167                    1 => range.move_until_prev_byte(byte, count),
1168                    2 => range.extend_onto_prev_byte(byte, count),
1169                    3 => range.move_onto_prev_byte(byte, count),
1170                    4 => range.extend_until_next_byte(byte, count),
1171                    5 => range.move_until_next_byte(byte, count),
1172                    6 => range.extend_onto_next_byte(byte, count),
1173                    _ => range.move_onto_next_byte(byte, count),
1174                }
1175            }
1176            #[rule]
1177            fn line_ops(&mut self, tc: TestCase) {
1178                let mut range = self.range();
1179                match tc.draw(gs::integers::<u8>().max_value(7)) {
1180                    0 => range.extend_to_line_start(),
1181                    1 => range.move_to_line_start(),
1182                    2 => range.extend_to_line_non_blank_start(),
1183                    3 => range.move_to_line_non_blank_start(),
1184                    4 => range.extend_until_line_end(),
1185                    5 => range.move_until_line_end(),
1186                    6 => range.extend_onto_line_end(),
1187                    _ => range.move_onto_line_end(),
1188                }
1189            }
1190            #[rule]
1191            fn extremes(&mut self, tc: TestCase) {
1192                let mut range = self.range();
1193                match tc.draw(gs::integers::<u8>().max_value(5)) {
1194                    0 => range.extend_to_start(),
1195                    1 => range.move_to_start(),
1196                    2 => range.extend_to_end(),
1197                    3 => range.move_to_end(),
1198                    4 => range.extend_to_bottom(),
1199                    _ => range.move_to_bottom(),
1200                }
1201            }
1202            #[rule]
1203            fn expand_to_full_lines(&mut self, _: TestCase) {
1204                self.range().expand_to_full_lines();
1205            }
1206            #[rule]
1207            fn expand_to_outer_node(&mut self, _: TestCase) {
1208                let (old_start, old_end) = self.range().byte_offsets();
1209                self.range().expand_to_outer_node();
1210                let (new_start, new_end) = self.range().byte_offsets();
1211                assert!(
1212                    new_start <= old_start && old_end <= new_end,
1213                    "Expanding to a node never shrinks the range"
1214                );
1215            }
1216            #[rule]
1217            fn shrink_to_inner_node(&mut self, _: TestCase) {
1218                let (old_start, old_end) = self.range().byte_offsets();
1219                self.range().shrink_to_inner_node();
1220                let (new_start, new_end) = self.range().byte_offsets();
1221                assert!(
1222                    old_start <= new_start && new_end <= old_end,
1223                    "Shrinking to a node never grows the range"
1224                );
1225            }
1226            #[rule]
1227            fn flip(&mut self, tc: TestCase) {
1228                let mut range = self.range();
1229                match tc.draw(gs::integers::<u8>().max_value(2)) {
1230                    0 => range.flip(),
1231                    1 => range.flip_forward(),
1232                    _ => range.flip_backward(),
1233                }
1234            }
1235            #[rule]
1236            fn collapse(&mut self, _: TestCase) {
1237                self.range().collapse();
1238            }
1239            #[rule]
1240            fn prepare_append(&mut self, _: TestCase) {
1241                self.range().prepare_append();
1242            }
1243            #[rule]
1244            fn insert(&mut self, tc: TestCase) {
1245                let string = tc.draw(gs::text());
1246                let mut range = self.range();
1247                range.collapse();
1248                range.insert(&string);
1249            }
1250            #[rule]
1251            fn delete_before(&mut self, _: TestCase) {
1252                let mut range = self.range();
1253                range.collapse();
1254                range.delete_before();
1255            }
1256            #[rule]
1257            fn delete(&mut self, _: TestCase) {
1258                self.range().delete();
1259            }
1260            #[rule]
1261            fn delete_after(&mut self, _: TestCase) {
1262                let mut range = self.range();
1263                range.collapse();
1264                range.delete_after();
1265            }
1266            #[rule]
1267            fn save_restore_roundtrip(&mut self, tc: TestCase) {
1268                let string = tc.draw(gs::text());
1269                let mut range = self.range();
1270                let snapshot = range.save();
1271                range.collapse();
1272                range.insert(&string);
1273                assert!(range.restore(&snapshot));
1274            }
1275            #[invariant]
1276            fn invariants(&self, _: TestCase) {
1277                let range =
1278                    Range::new(&self.text, &self.state).expect("Range valid after every operation");
1279                assert!(range.grapheme_length() >= 1, "Range is never empty");
1280                assert_eq!(range.byte_length(), range.slice().len());
1281                assert!(range.state().start().byte_index <= range.state().end().byte_index);
1282                let rope = self.text.rope();
1283                assert!(
1284                    rope.len() > 0 && rope.byte(rope.len() - 1) == b'\n',
1285                    "Text keeps its trailing newline"
1286                );
1287            }
1288        }
1289        #[cfg_attr(not(feature = "language-rust"), expect(unused_mut))]
1290        let mut text = Text::new();
1291        #[cfg(feature = "language-rust")]
1292        text.set_language(crate::syntax::Language::Rust);
1293        let machine = StateMachine {
1294            text,
1295            state: RangeState::default(),
1296        };
1297        hegel::stateful::machine(machine).run(tc);
1298    }
1299}