on master help sign in
Change the `Range` type and associated functions to gap indexing.
c1b0a71Nathan Vegdahl
helix-core/src/selection.rs+126−56
@@ -1,5 +1,5 @@
1−//! Selections are the primary editing construct. Even a single cursor is defined as an empty
1+//! Selections are the primary editing construct. Even a single cursor is
2−//! single selection range.
2+//! defined as a single empty or 1-wide selection range.
3//!
3//!
4//! All positioning is done via `char` offsets into the buffer.
4//! All positioning is done via `char` offsets into the buffer.
5use crate::{Assoc, ChangeSet, Rope, RopeSlice};
5use crate::{Assoc, ChangeSet, Rope, RopeSlice};
@@ -15,16 +15,39 @@ fn abs_difference(x: usize, y: usize) -> usize {
15 }
15 }
16}
16}
17
17
18−/// A single selection range. Anchor-inclusive, head-exclusive.
18+/// A single selection range.
19+///
20+/// The range consists of an "anchor" and "head" position in
21+/// the text. The head is the part that the user moves when
22+/// directly extending the selection. The head and anchor
23+/// can be in any order: either can precede or follow the
24+/// other in the text, and they can share the same position
25+/// for a zero-width range.
26+///
27+/// Below are some example `Range` configurations to better
28+/// illustrate. The anchor and head indices are show as
29+/// "(anchor, head)", followed by example text with "[" and "]"
30+/// inserted to visually represent the anchor and head positions:
31+///
32+/// - (0, 3): [Som]e text.
33+/// - (3, 0): ]Som[e text.
34+/// - (2, 7): So[me te]xt.
35+/// - (1, 1): S[]ome text.
36+///
37+/// Ranges are considered to be inclusive on the left and
38+/// exclusive on the right, regardless of anchor-head ordering.
39+/// This means, for example, that non-zero-width ranges that
40+/// are directly adjecent, sharing an edge, do not overlap.
41+/// However, a zero-width range will overlap with the shared
42+/// left-edge of another range.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct Range {
44pub struct Range {
21− // TODO: optimize into u32
22 /// The anchor of the range: the side that doesn't move when extending.
45 /// The anchor of the range: the side that doesn't move when extending.
23 pub anchor: usize,
46 pub anchor: usize,
24 /// The head of the range, moved when extending.
47 /// The head of the range, moved when extending.
25 pub head: usize,
48 pub head: usize,
26 pub horiz: Option<u32>,
49 pub horiz: Option<u32>,
27−} // TODO: might be cheaper to store normalized as from/to and an inverted flag
50+}
28
51
29impl Range {
52impl Range {
30 pub fn new(anchor: usize, head: usize) -> Self {
53 pub fn new(anchor: usize, head: usize) -> Self {
@@ -62,25 +85,14 @@ impl Range {
62 /// Check two ranges for overlap.
85 /// Check two ranges for overlap.
63 #[must_use]
86 #[must_use]
64 pub fn overlaps(&self, other: &Self) -> bool {
87 pub fn overlaps(&self, other: &Self) -> bool {
65− // cursor overlap is checked differently
88+ // To my eye, it's non-obvious why this works, but I arrived
66− if self.is_empty() {
89+ // at it after transforming the slower version that explicitly
67− let pos = self.head;
90+ // enumerated more cases. The unit tests are thorough.
68− pos >= other.from() && other.to() >= pos
69− } else {
70− self.to() > other.from() && other.to() > self.from()
91+ self.from() == other.from() || (self.to() > other.from() && other.to() > self.from())
71− }
72 }
92 }
73
93
74 pub fn contains(&self, pos: usize) -> bool {
94 pub fn contains(&self, pos: usize) -> bool {
75− if self.is_empty() {
76− return false;
77− }
78−
79− if self.anchor < self.head {
80− self.anchor <= pos && pos < self.head
95+ self.from() <= pos && pos < self.to()
81− } else {
82− self.head < pos && pos <= self.anchor
83− }
84 }
96 }
85
97
86 /// Map a range through a set of changes. Returns a new range representing the same position
98 /// Map a range through a set of changes. Returns a new range representing the same position
@@ -89,10 +101,10 @@ impl Range {
89 let anchor = changes.map_pos(self.anchor, Assoc::After);
101 let anchor = changes.map_pos(self.anchor, Assoc::After);
90 let head = changes.map_pos(self.head, Assoc::After);
102 let head = changes.map_pos(self.head, Assoc::After);
91
103
92− // TODO: possibly unnecessary
104+ // We want to return a new `Range` with `horiz == None` every time,
93− if self.anchor == anchor && self.head == head {
105+ // even if the anchor and head haven't changed, because we don't
94− return self;
106+ // know if the *visual* position hasn't changed due to
95− }
107+ // character-width or grapheme changes earlier in the text.
96 Self {
108 Self {
97 anchor,
109 anchor,
98 head,
110 head,
@@ -103,22 +115,20 @@ impl Range {
103 /// Extend the range to cover at least `from` `to`.
115 /// Extend the range to cover at least `from` `to`.
104 #[must_use]
116 #[must_use]
105 pub fn extend(&self, from: usize, to: usize) -> Self {
117 pub fn extend(&self, from: usize, to: usize) -> Self {
106− if from <= self.anchor && to >= self.anchor {
118+ debug_assert!(from <= to);
107− return Self {
108− anchor: from,
109− head: to,
110− horiz: None,
111− };
112− }
113
119
114− Self {
120+ if self.anchor <= self.head {
115− anchor: self.anchor,
121+ Self {
116− head: if abs_difference(from, self.anchor) > abs_difference(to, self.anchor) {
122+ anchor: self.anchor.min(from),
117− from
123+ head: self.head.max(to),
124+ horiz: None,
125+ }
118− } else {
126+ } else {
119− to
127+ Self {
120− },
128+ anchor: self.anchor.max(to),
129+ head: self.head.min(from),
121− horiz: None,
130+ horiz: None,
131+ }
122 }
132 }
123 }
133 }
124
134
@@ -126,7 +136,7 @@ impl Range {
126
136
127 #[inline]
137 #[inline]
128 pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> Cow<'b, str> {
138 pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> Cow<'b, str> {
129− Cow::from(text.slice(self.from()..self.to() + 1))
139+ text.slice(self.from()..self.to()).into()
130 }
140 }
131}
141}
132
142
@@ -355,7 +365,7 @@ pub fn select_on_matches(
355
365
356 let start = text.byte_to_char(start_byte + mat.start());
366 let start = text.byte_to_char(start_byte + mat.start());
357 let end = text.byte_to_char(start_byte + mat.end());
367 let end = text.byte_to_char(start_byte + mat.end());
358− result.push(Range::new(start, end.saturating_sub(1)));
368+ result.push(Range::new(start, end));
359 }
369 }
360 }
370 }
361
371
@@ -376,6 +386,12 @@ pub fn split_on_matches(
376 let mut result = SmallVec::with_capacity(selection.len());
386 let mut result = SmallVec::with_capacity(selection.len());
377
387
378 for sel in selection {
388 for sel in selection {
389+ // Special case: zero-width selection.
390+ if sel.from() == sel.to() {
391+ result.push(*sel);
392+ continue;
393+ }
394+
379 // TODO: can't avoid occasional allocations since Regex can't operate on chunks yet
395 // TODO: can't avoid occasional allocations since Regex can't operate on chunks yet
380 let fragment = sel.fragment(text);
396 let fragment = sel.fragment(text);
381
397
@@ -388,13 +404,12 @@ pub fn split_on_matches(
388
404
389 for mat in regex.find_iter(&fragment) {
405 for mat in regex.find_iter(&fragment) {
390 // TODO: retain range direction
406 // TODO: retain range direction
391−
392 let end = text.byte_to_char(start_byte + mat.start());
407 let end = text.byte_to_char(start_byte + mat.start());
393− result.push(Range::new(start, end.saturating_sub(1)));
408+ result.push(Range::new(start, end));
394 start = text.byte_to_char(start_byte + mat.end());
409 start = text.byte_to_char(start_byte + mat.end());
395 }
410 }
396
411
397− if start <= sel_end {
412+ if start < sel_end {
398 result.push(Range::new(start, sel_end));
413 result.push(Range::new(start, sel_end));
399 }
414 }
400 }
415 }
@@ -475,7 +490,7 @@ mod test {
475 .collect::<Vec<String>>()
490 .collect::<Vec<String>>()
476 .join(",");
491 .join(",");
477
492
478− assert_eq!(res, "8/10,10/12");
493+ assert_eq!(res, "8/10,10/12,12/12");
479 }
494 }
480
495
481 #[test]
496 #[test]
@@ -489,35 +504,90 @@ mod test {
489 assert_eq!(range.contains(13), false);
504 assert_eq!(range.contains(13), false);
490
505
491 let range = Range::new(9, 6);
506 let range = Range::new(9, 6);
492− assert_eq!(range.contains(9), true);
507+ assert_eq!(range.contains(9), false);
493 assert_eq!(range.contains(7), true);
508 assert_eq!(range.contains(7), true);
494− assert_eq!(range.contains(6), false);
509+ assert_eq!(range.contains(6), true);
510+ }
511+
512+ #[test]
513+ fn test_overlaps() {
514+ // Two non-zero-width ranges, no overlap.
515+ assert!(!Range::new(0, 3).overlaps(&Range::new(3, 6)));
516+ assert!(!Range::new(0, 3).overlaps(&Range::new(6, 3)));
517+ assert!(!Range::new(3, 0).overlaps(&Range::new(3, 6)));
518+ assert!(!Range::new(3, 0).overlaps(&Range::new(6, 3)));
519+ assert!(!Range::new(3, 6).overlaps(&Range::new(0, 3)));
520+ assert!(!Range::new(3, 6).overlaps(&Range::new(3, 0)));
521+ assert!(!Range::new(6, 3).overlaps(&Range::new(0, 3)));
522+ assert!(!Range::new(6, 3).overlaps(&Range::new(3, 0)));
523+
524+ // Two non-zero-width ranges, overlap.
525+ assert!(Range::new(0, 4).overlaps(&Range::new(3, 6)));
526+ assert!(Range::new(0, 4).overlaps(&Range::new(6, 3)));
527+ assert!(Range::new(4, 0).overlaps(&Range::new(3, 6)));
528+ assert!(Range::new(4, 0).overlaps(&Range::new(6, 3)));
529+ assert!(Range::new(3, 6).overlaps(&Range::new(0, 4)));
530+ assert!(Range::new(3, 6).overlaps(&Range::new(4, 0)));
531+ assert!(Range::new(6, 3).overlaps(&Range::new(0, 4)));
532+ assert!(Range::new(6, 3).overlaps(&Range::new(4, 0)));
533+
534+ // Zero-width and non-zero-width range, no overlap.
535+ assert!(!Range::new(0, 3).overlaps(&Range::new(3, 3)));
536+ assert!(!Range::new(3, 0).overlaps(&Range::new(3, 3)));
537+ assert!(!Range::new(3, 3).overlaps(&Range::new(0, 3)));
538+ assert!(!Range::new(3, 3).overlaps(&Range::new(3, 0)));
539+
540+ // Zero-width and non-zero-width range, overlap.
541+ assert!(Range::new(1, 4).overlaps(&Range::new(1, 1)));
542+ assert!(Range::new(4, 1).overlaps(&Range::new(1, 1)));
543+ assert!(Range::new(1, 1).overlaps(&Range::new(1, 4)));
544+ assert!(Range::new(1, 1).overlaps(&Range::new(4, 1)));
545+
546+ assert!(Range::new(1, 4).overlaps(&Range::new(3, 3)));
547+ assert!(Range::new(4, 1).overlaps(&Range::new(3, 3)));
548+ assert!(Range::new(3, 3).overlaps(&Range::new(1, 4)));
549+ assert!(Range::new(3, 3).overlaps(&Range::new(4, 1)));
550+
551+ // Two zero-width ranges, no overlap.
552+ assert!(!Range::new(0, 0).overlaps(&Range::new(1, 1)));
553+ assert!(!Range::new(1, 1).overlaps(&Range::new(0, 0)));
554+
555+ // Two zero-width ranges, overlap.
556+ assert!(Range::new(1, 1).overlaps(&Range::new(1, 1)));
495 }
557 }
496
558
497 #[test]
559 #[test]
498 fn test_split_on_matches() {
560 fn test_split_on_matches() {
499 use crate::regex::Regex;
561 use crate::regex::Regex;
500
562
501− let text = Rope::from("abcd efg wrs xyz 123 456");
563+ let text = Rope::from(" abcd efg wrs xyz 123 456");
502
564
503− let selection = Selection::new(smallvec![Range::new(0, 8), Range::new(10, 19),], 0);
565+ let selection = Selection::new(smallvec![Range::new(0, 9), Range::new(11, 20),], 0);
504
566
505 let result = split_on_matches(text.slice(..), &selection, &Regex::new(r"\s+").unwrap());
567 let result = split_on_matches(text.slice(..), &selection, &Regex::new(r"\s+").unwrap());
506
568
507 assert_eq!(
569 assert_eq!(
508 result.ranges(),
570 result.ranges(),
509 &[
571 &[
572+ // We get a leading zero-width range when there's
573+ // a leading match because ranges are inclusive on
574+ // the left. Imagine, for example, if the entire
575+ // selection range were matched: you'd still want
576+ // at least one range to remain after the split.
510− Range::new(0, 3),
577+ Range::new(0, 0),
511− Range::new(5, 7),
578+ Range::new(1, 5),
579+ Range::new(6, 9),
512− Range::new(10, 11),
580+ Range::new(11, 13),
513− Range::new(15, 17),
581+ Range::new(16, 19),
514− Range::new(19, 19),
582+ // In contrast to the comment above, there is no
583+ // _trailing_ zero-width range despite the trailing
584+ // match, because ranges are exclusive on the right.
515 ]
585 ]
516 );
586 );
517
587
518 assert_eq!(
588 assert_eq!(
519 result.fragments(text.slice(..)).collect::<Vec<_>>(),
589 result.fragments(text.slice(..)).collect::<Vec<_>>(),
520− &["abcd", "efg", "rs", "xyz", "1"]
590+ &["", "abcd", "efg", "rs", "xyz"]
521 );
591 );
522 }
592 }
523}
593}