on master help sign in
Better validation method APIs for `Range`.
77a266eNathan Vegdahl
helix-core/src/graphemes.rs+9
@@ -71,6 +71,8 @@ pub fn nth_prev_grapheme_boundary(slice: RopeSlice, char_idx: usize, n: usize) -
71}
71}
72
72
73/// Finds the previous grapheme boundary before the given char position.
73/// Finds the previous grapheme boundary before the given char position.
74+#[must_use]
75+#[inline(always)]
74pub fn prev_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> usize {
76pub fn prev_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> usize {
75 nth_prev_grapheme_boundary(slice, char_idx, 1)
77 nth_prev_grapheme_boundary(slice, char_idx, 1)
76}
78}
@@ -117,12 +119,16 @@ pub fn nth_next_grapheme_boundary(slice: RopeSlice, char_idx: usize, n: usize) -
117}
119}
118
120
119/// Finds the next grapheme boundary after the given char position.
121/// Finds the next grapheme boundary after the given char position.
122+#[must_use]
123+#[inline(always)]
120pub fn next_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> usize {
124pub fn next_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> usize {
121 nth_next_grapheme_boundary(slice, char_idx, 1)
125 nth_next_grapheme_boundary(slice, char_idx, 1)
122}
126}
123
127
124/// Returns the passed char index if it's already a grapheme boundary,
128/// Returns the passed char index if it's already a grapheme boundary,
125/// or the next grapheme boundary char index if not.
129/// or the next grapheme boundary char index if not.
130+#[must_use]
131+#[inline]
126pub fn ensure_grapheme_boundary_next(slice: RopeSlice, char_idx: usize) -> usize {
132pub fn ensure_grapheme_boundary_next(slice: RopeSlice, char_idx: usize) -> usize {
127 if char_idx == 0 {
133 if char_idx == 0 {
128 char_idx
134 char_idx
@@ -133,6 +139,8 @@ pub fn ensure_grapheme_boundary_next(slice: RopeSlice, char_idx: usize) -> usize
133
139
134/// Returns the passed char index if it's already a grapheme boundary,
140/// Returns the passed char index if it's already a grapheme boundary,
135/// or the prev grapheme boundary char index if not.
141/// or the prev grapheme boundary char index if not.
142+#[must_use]
143+#[inline]
136pub fn ensure_grapheme_boundary_prev(slice: RopeSlice, char_idx: usize) -> usize {
144pub fn ensure_grapheme_boundary_prev(slice: RopeSlice, char_idx: usize) -> usize {
137 if char_idx == slice.len_chars() {
145 if char_idx == slice.len_chars() {
138 char_idx
146 char_idx
@@ -142,6 +150,7 @@ pub fn ensure_grapheme_boundary_prev(slice: RopeSlice, char_idx: usize) -> usize
142}
150}
143
151
144/// Returns whether the given char position is a grapheme boundary.
152/// Returns whether the given char position is a grapheme boundary.
153+#[must_use]
145pub fn is_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> bool {
154pub fn is_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> bool {
146 // Bounds check
155 // Bounds check
147 debug_assert!(char_idx <= slice.len_chars());
156 debug_assert!(char_idx <= slice.len_chars());
helix-core/src/selection.rs+141−108
@@ -137,58 +137,61 @@ impl Range {
137 }
137 }
138 }
138 }
139
139
140− /// Compute the ends of the range, shifted (if needed) to align with
140+ /// Compute a possibly new range from this range, attempting to ensure
141− /// grapheme boundaries.
141+ /// a minimum range width of 1 char by shifting the head in the forward
142+ /// direction as needed.
142 ///
143 ///
143− /// This should generally be used for cursor validation.
144+ /// This method will never shift the anchor, and will only shift the
145+ /// head in the forward direction. Therefore, this method can fail
146+ /// at ensuring the minimum width if and only if the passed range is
147+ /// both zero-width and at the end of the `RopeSlice`.
144 ///
148 ///
145− /// Always succeeds.
149+ /// If the input range is grapheme-boundary aligned, the returned range
150+ /// will also be. Specifically, if the head needs to shift to achieve
151+ /// the minimum width, it will shift to the next grapheme boundary.
146 #[must_use]
152 #[must_use]
153+ #[inline]
147− pub fn aligned_range(&self, slice: RopeSlice) -> (usize, usize) {
154+ pub fn min_width_1(&self, slice: RopeSlice) -> Self {
148 if self.anchor == self.head {
155 if self.anchor == self.head {
149− let pos = ensure_grapheme_boundary_prev(slice, self.anchor);
156+ Range {
150− (pos, pos)
157+ anchor: self.anchor,
158+ head: next_grapheme_boundary(slice, self.head),
159+ horiz: self.horiz,
160+ }
151 } else {
161 } else {
152− (
162+ *self
153− ensure_grapheme_boundary_prev(slice, self.from()),
154− ensure_grapheme_boundary_next(slice, self.to()),
155− )
156 }
163 }
157 }
164 }
158
165
159− /// Same as `ensure_grapheme_validity()` + attempts to ensure a minimum
166+ /// Compute a possibly new range from this range, with its ends
160− /// char width in the direction of the head.
167+ /// shifted as needed to align with grapheme boundaries.
161− ///
162− /// This should generally be used as a pre-pass for operations that
163− /// require a minimum selection width to achieve their intended behavior.
164− ///
165− /// This will fail at ensuring the minimum width only if the passed
166− /// `RopeSlice` is too short in the direction of the head, in which
167− /// case the range will fill the available length in that direction.
168 ///
168 ///
169− /// Ensuring grapheme-boundary alignment always succeeds.
169+ /// Zero-width ranges will always stay zero-width, and non-zero-width
170+ /// ranges will never collapse to zero-width.
170 #[must_use]
171 #[must_use]
171− pub fn min_width_range(&self, slice: RopeSlice, min_char_width: usize) -> (usize, usize) {
172+ pub fn grapheme_aligned(&self, slice: RopeSlice) -> Self {
172− if min_char_width == 0 {
173+ let (new_anchor, new_head) = if self.anchor == self.head {
173− return self.aligned_range(slice);
174+ let pos = ensure_grapheme_boundary_prev(slice, self.anchor);
174− }
175+ (pos, pos)
175−
176− if self.anchor <= self.head {
176+ } else if self.anchor < self.head {
177+ (
177− let anchor = ensure_grapheme_boundary_prev(slice, self.anchor);
178+ ensure_grapheme_boundary_prev(slice, self.anchor),
178− let head = ensure_grapheme_boundary_next(
179+ ensure_grapheme_boundary_next(slice, self.head),
179− slice,
180+ )
180− self.head
181− .max(anchor + min_char_width)
182− .min(slice.len_chars()),
183− );
184− (anchor, head)
185 } else {
181 } else {
182+ (
186− let anchor = ensure_grapheme_boundary_next(slice, self.anchor);
183+ ensure_grapheme_boundary_next(slice, self.anchor),
187− let head = ensure_grapheme_boundary_prev(
184+ ensure_grapheme_boundary_prev(slice, self.head),
188− slice,
189− self.head.min(anchor.saturating_sub(min_char_width)),
190− );
185+ )
191− (head, anchor)
186+ };
187+ Range {
188+ anchor: new_anchor,
189+ head: new_head,
190+ horiz: if new_anchor == self.anchor {
191+ self.horiz
192+ } else {
193+ None
194+ },
192 }
195 }
193 }
196 }
194
197
@@ -571,97 +574,127 @@ mod test {
571
574
572 #[test]
575 #[test]
573 fn test_overlaps() {
576 fn test_overlaps() {
577+ fn overlaps(a: (usize, usize), b: (usize, usize)) -> bool {
578+ Range::new(a.0, a.1).overlaps(&Range::new(b.0, b.1))
579+ }
580+
574 // Two non-zero-width ranges, no overlap.
581 // Two non-zero-width ranges, no overlap.
575− assert!(!Range::new(0, 3).overlaps(&Range::new(3, 6)));
582+ assert!(!overlaps((0, 3), (3, 6)));
576− assert!(!Range::new(0, 3).overlaps(&Range::new(6, 3)));
583+ assert!(!overlaps((0, 3), (6, 3)));
577− assert!(!Range::new(3, 0).overlaps(&Range::new(3, 6)));
584+ assert!(!overlaps((3, 0), (3, 6)));
578− assert!(!Range::new(3, 0).overlaps(&Range::new(6, 3)));
585+ assert!(!overlaps((3, 0), (6, 3)));
579− assert!(!Range::new(3, 6).overlaps(&Range::new(0, 3)));
586+ assert!(!overlaps((3, 6), (0, 3)));
580− assert!(!Range::new(3, 6).overlaps(&Range::new(3, 0)));
587+ assert!(!overlaps((3, 6), (3, 0)));
581− assert!(!Range::new(6, 3).overlaps(&Range::new(0, 3)));
588+ assert!(!overlaps((6, 3), (0, 3)));
582− assert!(!Range::new(6, 3).overlaps(&Range::new(3, 0)));
589+ assert!(!overlaps((6, 3), (3, 0)));
583
590
584 // Two non-zero-width ranges, overlap.
591 // Two non-zero-width ranges, overlap.
585− assert!(Range::new(0, 4).overlaps(&Range::new(3, 6)));
592+ assert!(overlaps((0, 4), (3, 6)));
586− assert!(Range::new(0, 4).overlaps(&Range::new(6, 3)));
593+ assert!(overlaps((0, 4), (6, 3)));
587− assert!(Range::new(4, 0).overlaps(&Range::new(3, 6)));
594+ assert!(overlaps((4, 0), (3, 6)));
588− assert!(Range::new(4, 0).overlaps(&Range::new(6, 3)));
595+ assert!(overlaps((4, 0), (6, 3)));
589− assert!(Range::new(3, 6).overlaps(&Range::new(0, 4)));
596+ assert!(overlaps((3, 6), (0, 4)));
590− assert!(Range::new(3, 6).overlaps(&Range::new(4, 0)));
597+ assert!(overlaps((3, 6), (4, 0)));
591− assert!(Range::new(6, 3).overlaps(&Range::new(0, 4)));
598+ assert!(overlaps((6, 3), (0, 4)));
592− assert!(Range::new(6, 3).overlaps(&Range::new(4, 0)));
599+ assert!(overlaps((6, 3), (4, 0)));
593
600
594 // Zero-width and non-zero-width range, no overlap.
601 // Zero-width and non-zero-width range, no overlap.
595− assert!(!Range::new(0, 3).overlaps(&Range::new(3, 3)));
602+ assert!(!overlaps((0, 3), (3, 3)));
596− assert!(!Range::new(3, 0).overlaps(&Range::new(3, 3)));
603+ assert!(!overlaps((3, 0), (3, 3)));
597− assert!(!Range::new(3, 3).overlaps(&Range::new(0, 3)));
604+ assert!(!overlaps((3, 3), (0, 3)));
598− assert!(!Range::new(3, 3).overlaps(&Range::new(3, 0)));
605+ assert!(!overlaps((3, 3), (3, 0)));
599
606
600 // Zero-width and non-zero-width range, overlap.
607 // Zero-width and non-zero-width range, overlap.
601− assert!(Range::new(1, 4).overlaps(&Range::new(1, 1)));
608+ assert!(overlaps((1, 4), (1, 1)));
602− assert!(Range::new(4, 1).overlaps(&Range::new(1, 1)));
609+ assert!(overlaps((4, 1), (1, 1)));
603− assert!(Range::new(1, 1).overlaps(&Range::new(1, 4)));
610+ assert!(overlaps((1, 1), (1, 4)));
604− assert!(Range::new(1, 1).overlaps(&Range::new(4, 1)));
611+ assert!(overlaps((1, 1), (4, 1)));
605
612
606− assert!(Range::new(1, 4).overlaps(&Range::new(3, 3)));
613+ assert!(overlaps((1, 4), (3, 3)));
607− assert!(Range::new(4, 1).overlaps(&Range::new(3, 3)));
614+ assert!(overlaps((4, 1), (3, 3)));
608− assert!(Range::new(3, 3).overlaps(&Range::new(1, 4)));
615+ assert!(overlaps((3, 3), (1, 4)));
609− assert!(Range::new(3, 3).overlaps(&Range::new(4, 1)));
616+ assert!(overlaps((3, 3), (4, 1)));
610
617
611 // Two zero-width ranges, no overlap.
618 // Two zero-width ranges, no overlap.
612− assert!(!Range::new(0, 0).overlaps(&Range::new(1, 1)));
619+ assert!(!overlaps((0, 0), (1, 1)));
613− assert!(!Range::new(1, 1).overlaps(&Range::new(0, 0)));
620+ assert!(!overlaps((1, 1), (0, 0)));
614
621
615 // Two zero-width ranges, overlap.
622 // Two zero-width ranges, overlap.
616− assert!(Range::new(1, 1).overlaps(&Range::new(1, 1)));
623+ assert!(overlaps((1, 1), (1, 1)));
617 }
624 }
618
625
619 #[test]
626 #[test]
620− fn test_aligned_range() {
627+ fn test_graphem_aligned() {
621 let r = Rope::from_str("\r\nHi\r\n");
628 let r = Rope::from_str("\r\nHi\r\n");
622 let s = r.slice(..);
629 let s = r.slice(..);
623
630
631+ // Zero-width.
624− assert_eq!(Range::new(0, 0).aligned_range(s), (0, 0));
632+ assert_eq!(Range::new(0, 0).grapheme_aligned(s), Range::new(0, 0));
625− assert_eq!(Range::new(0, 1).aligned_range(s), (0, 2));
633+ assert_eq!(Range::new(1, 1).grapheme_aligned(s), Range::new(0, 0));
634+ assert_eq!(Range::new(2, 2).grapheme_aligned(s), Range::new(2, 2));
626− assert_eq!(Range::new(1, 1).aligned_range(s), (0, 0));
635+ assert_eq!(Range::new(3, 3).grapheme_aligned(s), Range::new(3, 3));
636+ assert_eq!(Range::new(4, 4).grapheme_aligned(s), Range::new(4, 4));
637+ assert_eq!(Range::new(5, 5).grapheme_aligned(s), Range::new(4, 4));
638+ assert_eq!(Range::new(6, 6).grapheme_aligned(s), Range::new(6, 6));
639+
640+ // Forward.
627− assert_eq!(Range::new(1, 2).aligned_range(s), (0, 2));
641+ assert_eq!(Range::new(0, 1).grapheme_aligned(s), Range::new(0, 2));
628− assert_eq!(Range::new(2, 2).aligned_range(s), (2, 2));
642+ assert_eq!(Range::new(1, 2).grapheme_aligned(s), Range::new(0, 2));
629− assert_eq!(Range::new(2, 3).aligned_range(s), (2, 3));
643+ assert_eq!(Range::new(2, 3).grapheme_aligned(s), Range::new(2, 3));
630− assert_eq!(Range::new(1, 3).aligned_range(s), (0, 3));
644+ assert_eq!(Range::new(3, 4).grapheme_aligned(s), Range::new(3, 4));
631− assert_eq!(Range::new(3, 5).aligned_range(s), (3, 6));
645+ assert_eq!(Range::new(4, 5).grapheme_aligned(s), Range::new(4, 6));
632− assert_eq!(Range::new(4, 5).aligned_range(s), (4, 6));
646+ assert_eq!(Range::new(5, 6).grapheme_aligned(s), Range::new(4, 6));
647+
633− assert_eq!(Range::new(5, 5).aligned_range(s), (4, 4));
648+ assert_eq!(Range::new(0, 2).grapheme_aligned(s), Range::new(0, 2));
634− assert_eq!(Range::new(6, 6).aligned_range(s), (6, 6));
649+ assert_eq!(Range::new(1, 3).grapheme_aligned(s), Range::new(0, 3));
650+ assert_eq!(Range::new(2, 4).grapheme_aligned(s), Range::new(2, 4));
651+ assert_eq!(Range::new(3, 5).grapheme_aligned(s), Range::new(3, 6));
652+ assert_eq!(Range::new(4, 6).grapheme_aligned(s), Range::new(4, 6));
653+
654+ // Reverse.
655+ assert_eq!(Range::new(1, 0).grapheme_aligned(s), Range::new(2, 0));
656+ assert_eq!(Range::new(2, 1).grapheme_aligned(s), Range::new(2, 0));
657+ assert_eq!(Range::new(3, 2).grapheme_aligned(s), Range::new(3, 2));
658+ assert_eq!(Range::new(4, 3).grapheme_aligned(s), Range::new(4, 3));
659+ assert_eq!(Range::new(5, 4).grapheme_aligned(s), Range::new(6, 4));
660+ assert_eq!(Range::new(6, 5).grapheme_aligned(s), Range::new(6, 4));
661+
662+ assert_eq!(Range::new(2, 0).grapheme_aligned(s), Range::new(2, 0));
663+ assert_eq!(Range::new(3, 1).grapheme_aligned(s), Range::new(3, 0));
664+ assert_eq!(Range::new(4, 2).grapheme_aligned(s), Range::new(4, 2));
665+ assert_eq!(Range::new(5, 3).grapheme_aligned(s), Range::new(6, 3));
666+ assert_eq!(Range::new(6, 4).grapheme_aligned(s), Range::new(6, 4));
635 }
667 }
636
668
637 #[test]
669 #[test]
638− fn test_min_width_range() {
670+ fn test_min_width_1() {
639 let r = Rope::from_str("\r\nHi\r\n");
671 let r = Rope::from_str("\r\nHi\r\n");
640 let s = r.slice(..);
672 let s = r.slice(..);
641
673
642− assert_eq!(Range::new(0, 0).min_width_range(s, 1), (0, 2));
674+ // Zero-width.
643− assert_eq!(Range::new(0, 1).min_width_range(s, 1), (0, 2));
675+ assert_eq!(Range::new(0, 0).min_width_1(s), Range::new(0, 2));
644− assert_eq!(Range::new(1, 1).min_width_range(s, 1), (0, 2));
676+ assert_eq!(Range::new(1, 1).min_width_1(s), Range::new(1, 2));
645− assert_eq!(Range::new(1, 2).min_width_range(s, 1), (0, 2));
646− assert_eq!(Range::new(2, 2).min_width_range(s, 1), (2, 3));
677+ assert_eq!(Range::new(2, 2).min_width_1(s), Range::new(2, 3));
647− assert_eq!(Range::new(2, 3).min_width_range(s, 1), (2, 3));
678+ assert_eq!(Range::new(3, 3).min_width_1(s), Range::new(3, 4));
648− assert_eq!(Range::new(1, 3).min_width_range(s, 1), (0, 3));
679+ assert_eq!(Range::new(4, 4).min_width_1(s), Range::new(4, 6));
649− assert_eq!(Range::new(3, 5).min_width_range(s, 1), (3, 6));
680+ assert_eq!(Range::new(5, 5).min_width_1(s), Range::new(5, 6));
650− assert_eq!(Range::new(4, 5).min_width_range(s, 1), (4, 6));
681+ assert_eq!(Range::new(6, 6).min_width_1(s), Range::new(6, 6));
682+
683+ // Forward.
651− assert_eq!(Range::new(5, 5).min_width_range(s, 1), (4, 6));
684+ assert_eq!(Range::new(0, 1).min_width_1(s), Range::new(0, 1));
652− assert_eq!(Range::new(6, 6).min_width_range(s, 1), (6, 6));
685+ assert_eq!(Range::new(1, 2).min_width_1(s), Range::new(1, 2));
653−
654− assert_eq!(Range::new(1, 0).min_width_range(s, 1), (0, 2));
686+ assert_eq!(Range::new(2, 3).min_width_1(s), Range::new(2, 3));
655− assert_eq!(Range::new(2, 1).min_width_range(s, 1), (0, 2));
687+ assert_eq!(Range::new(3, 4).min_width_1(s), Range::new(3, 4));
656− assert_eq!(Range::new(3, 2).min_width_range(s, 1), (2, 3));
688+ assert_eq!(Range::new(4, 5).min_width_1(s), Range::new(4, 5));
657− assert_eq!(Range::new(3, 1).min_width_range(s, 1), (0, 3));
689+ assert_eq!(Range::new(5, 6).min_width_1(s), Range::new(5, 6));
690+
691+ // Reverse.
658− assert_eq!(Range::new(5, 3).min_width_range(s, 1), (3, 6));
692+ assert_eq!(Range::new(1, 0).min_width_1(s), Range::new(1, 0));
659− assert_eq!(Range::new(5, 4).min_width_range(s, 1), (4, 6));
693+ assert_eq!(Range::new(2, 1).min_width_1(s), Range::new(2, 1));
660−
661− assert_eq!(Range::new(3, 4).min_width_range(s, 3), (3, 6));
694+ assert_eq!(Range::new(3, 2).min_width_1(s), Range::new(3, 2));
662− assert_eq!(Range::new(4, 3).min_width_range(s, 3), (0, 4));
695+ assert_eq!(Range::new(4, 3).min_width_1(s), Range::new(4, 3));
663− assert_eq!(Range::new(3, 4).min_width_range(s, 20), (3, 6));
696+ assert_eq!(Range::new(5, 4).min_width_1(s), Range::new(5, 4));
664− assert_eq!(Range::new(4, 3).min_width_range(s, 20), (0, 4));
697+ assert_eq!(Range::new(6, 5).min_width_1(s), Range::new(6, 5));
665 }
698 }
666
699
667 #[test]
700 #[test]