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 #[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 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 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 Bound::Excluded(_) => unreachable!("excluded start bound"),
547 Bound::Unbounded => 0,
548 };
549 let end = match byte_range.end_bound() {
550 Bound::Included(n) => self
552 .text
553 .snap_to_grapheme_start(*n)
554 .expect("Text is never empty"),
555 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 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 #[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 #[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 #[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 #[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 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 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 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 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 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 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}