on master help sign in
Implement `Range::put()` which manages range movements and extensions.

In particular, this wraps the annoying logic involved in keeping the cursor width to 1 grapheme.

commit753f7f381b96f591be3c0e37a93b90776c5405cb authorNathan Vegdahl <cessen@cessen.com> dateWed 7 Jul 2021, 17:24 (5 years ago) parent85d5b39
2 files changed+49−65
helix-core/src/movement.rs+26−65 helix-core/src/selection.rs+23
helix-core/src/movement.rs+26−65
@@ -32,60 +32,31 @@ pub fn move_horizontally(
3232 count: usize,
3333 behaviour: Movement,
3434) -> Range {
35− match (behaviour, dir) {
36− (Movement::Move, Direction::Backward) => {
37− let count = if range.anchor < range.head {
38− count + 1
39− } else {
40− count
41− };
42− let pos = nth_prev_grapheme_boundary(slice, range.head, count);
43− Range::new(pos, pos)
44− }
45− (Movement::Move, Direction::Forward) => {
46− let count = if range.anchor < range.head {
47− count - 1
48− } else {
49− count
50− };
51− let pos = nth_next_grapheme_boundary(slice, range.head, count);
52− Range::new(pos, pos)
53− }
54− (Movement::Extend, Direction::Backward) => {
55− // Ensure a valid initial selection state.
56− let range = range.min_width_1(slice);
57−
58− // Do the main movement.
59− let mut head = nth_prev_grapheme_boundary(slice, range.head, count);
60− let mut anchor = range.anchor;
61−
62− // If the head and anchor crossed over each other, we need to
63− // fiddle around to make it behave like a 1-wide cursor.
64− if head <= anchor && range.head > range.anchor {
65− anchor = next_grapheme_boundary(slice, anchor);
66− head = prev_grapheme_boundary(slice, head);
67− }
35+ use Movement::Extend;
6836
69− Range::new(anchor, head)
70− }
71− (Movement::Extend, Direction::Forward) => {
72− // Ensure a valid initial selection state.
73− let range = range.min_width_1(slice);
74−
75− // Do the main movement.
76− let mut head = nth_next_grapheme_boundary(slice, range.head, count);
77− let mut anchor = range.anchor;
78−
79− // If the head and anchor crossed over each other, we need to
80− // fiddle around to make it behave like a 1-wide cursor.
81− if head >= anchor && range.head < range.anchor {
82− anchor = prev_grapheme_boundary(slice, anchor);
83− head = next_grapheme_boundary(slice, head);
84− }
37+ // Shift back one grapheme if needed, to account for
38+ // the cursor being visually 1-width.
39+ let pos = if range.head > range.anchor {
40+ prev_grapheme_boundary(slice, range.head)
41+ } else {
42+ range.head
43+ };
8544
86− Range::new(anchor, head)
87− }
88− }
45+ // Compute the new position.
46+ let mut new_pos = if dir == Direction::Backward {
47+ nth_prev_grapheme_boundary(slice, pos, count)
48+ } else {
49+ nth_next_grapheme_boundary(slice, pos, count)
50+ };
51+
52+ // Shift forward one grapheme if needed, for the
53+ // visual 1-width cursor.
54+ if behaviour == Extend && new_pos >= range.anchor {
55+ new_pos = next_grapheme_boundary(slice, new_pos);
56+ };
57+
58+ // Compute the final new range.
59+ range.put(slice, behaviour == Extend, new_pos)
8960}
9061
9162pub fn move_vertically(
@@ -135,19 +106,9 @@ pub fn move_vertically(
135106 new_pos
136107 };
137108
138− let new_anchor = if range.anchor <= range.head && range.anchor > new_head {
139− next_grapheme_boundary(slice, range.anchor)
140− } else if range.anchor > range.head && range.anchor < new_head {
141− prev_grapheme_boundary(slice, range.anchor)
142− } else {
143− range.anchor
144− };
145−
146− Range {
147− anchor: new_anchor,
148− head: new_head,
149− horiz: Some(horiz),
150− }
109+ let mut new_range = range.put(slice, true, new_head);
110+ new_range.horiz = Some(horiz);
111+ new_range
151112 }
152113 }
153114}
helix-core/src/selection.rs+23
@@ -5,6 +5,7 @@
55use crate::{
66 graphemes::{
77 ensure_grapheme_boundary_next, ensure_grapheme_boundary_prev, next_grapheme_boundary,
8+ prev_grapheme_boundary,
89 },
910 Assoc, ChangeSet, RopeSlice,
1011};
@@ -208,6 +209,28 @@ impl Range {
208209 }
209210 }
210211
212+ /// Moves the `Range` to `char_idx`. If `extend == true`, then only the head
213+ /// is moved to `char_idx`, and the anchor is adjusted only as needed to
214+ /// preserve 1-width range semantics.
215+ ///
216+ /// This method assumes that the range and `char_idx` are already properly
217+ /// grapheme-aligned.
218+ #[must_use]
219+ #[inline]
220+ pub fn put(self, text: RopeSlice, extend: bool, char_idx: usize) -> Range {
221+ let anchor = if !extend {
222+ char_idx
223+ } else if self.head >= self.anchor && char_idx < self.anchor {
224+ next_grapheme_boundary(text, self.anchor)
225+ } else if self.head < self.anchor && char_idx >= self.anchor {
226+ prev_grapheme_boundary(text, self.anchor)
227+ } else {
228+ self.anchor
229+ };
230+
231+ Range::new(anchor, char_idx)
232+ }
233+
211234 // groupAt
212235
213236 #[inline]