From 753f7f381b96f591be3c0e37a93b90776c5405cb Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Wed, 7 Jul 2021 17:24:39 -0700 Subject: 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. --- helix-core/src/movement.rs | 91 +++++++++++++-------------------------------- helix-core/src/selection.rs | 23 ++++++++++++ 2 files changed, 49 insertions(+), 65 deletions(-) diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs index bc56f9a4..0e2a2a42 100644 --- a/helix-core/src/movement.rs +++ b/helix-core/src/movement.rs @@ -32,60 +32,31 @@ pub fn move_horizontally( count: usize, behaviour: Movement, ) -> Range { - match (behaviour, dir) { - (Movement::Move, Direction::Backward) => { - let count = if range.anchor < range.head { - count + 1 - } else { - count - }; - let pos = nth_prev_grapheme_boundary(slice, range.head, count); - Range::new(pos, pos) - } - (Movement::Move, Direction::Forward) => { - let count = if range.anchor < range.head { - count - 1 - } else { - count - }; - let pos = nth_next_grapheme_boundary(slice, range.head, count); - Range::new(pos, pos) - } - (Movement::Extend, Direction::Backward) => { - // Ensure a valid initial selection state. - let range = range.min_width_1(slice); - - // Do the main movement. - let mut head = nth_prev_grapheme_boundary(slice, range.head, count); - let mut anchor = range.anchor; - - // If the head and anchor crossed over each other, we need to - // fiddle around to make it behave like a 1-wide cursor. - if head <= anchor && range.head > range.anchor { - anchor = next_grapheme_boundary(slice, anchor); - head = prev_grapheme_boundary(slice, head); - } + use Movement::Extend; - Range::new(anchor, head) - } - (Movement::Extend, Direction::Forward) => { - // Ensure a valid initial selection state. - let range = range.min_width_1(slice); - - // Do the main movement. - let mut head = nth_next_grapheme_boundary(slice, range.head, count); - let mut anchor = range.anchor; - - // If the head and anchor crossed over each other, we need to - // fiddle around to make it behave like a 1-wide cursor. - if head >= anchor && range.head < range.anchor { - anchor = prev_grapheme_boundary(slice, anchor); - head = next_grapheme_boundary(slice, head); - } + // Shift back one grapheme if needed, to account for + // the cursor being visually 1-width. + let pos = if range.head > range.anchor { + prev_grapheme_boundary(slice, range.head) + } else { + range.head + }; - Range::new(anchor, head) - } - } + // Compute the new position. + let mut new_pos = if dir == Direction::Backward { + nth_prev_grapheme_boundary(slice, pos, count) + } else { + nth_next_grapheme_boundary(slice, pos, count) + }; + + // Shift forward one grapheme if needed, for the + // visual 1-width cursor. + if behaviour == Extend && new_pos >= range.anchor { + new_pos = next_grapheme_boundary(slice, new_pos); + }; + + // Compute the final new range. + range.put(slice, behaviour == Extend, new_pos) } pub fn move_vertically( @@ -135,19 +106,9 @@ pub fn move_vertically( new_pos }; - let new_anchor = if range.anchor <= range.head && range.anchor > new_head { - next_grapheme_boundary(slice, range.anchor) - } else if range.anchor > range.head && range.anchor < new_head { - prev_grapheme_boundary(slice, range.anchor) - } else { - range.anchor - }; - - Range { - anchor: new_anchor, - head: new_head, - horiz: Some(horiz), - } + let mut new_range = range.put(slice, true, new_head); + new_range.horiz = Some(horiz); + new_range } } } diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 64ff51d8..8951899b 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -5,6 +5,7 @@ use crate::{ graphemes::{ ensure_grapheme_boundary_next, ensure_grapheme_boundary_prev, next_grapheme_boundary, + prev_grapheme_boundary, }, Assoc, ChangeSet, RopeSlice, }; @@ -208,6 +209,28 @@ impl Range { } } + /// Moves the `Range` to `char_idx`. If `extend == true`, then only the head + /// is moved to `char_idx`, and the anchor is adjusted only as needed to + /// preserve 1-width range semantics. + /// + /// This method assumes that the range and `char_idx` are already properly + /// grapheme-aligned. + #[must_use] + #[inline] + pub fn put(self, text: RopeSlice, extend: bool, char_idx: usize) -> Range { + let anchor = if !extend { + char_idx + } else if self.head >= self.anchor && char_idx < self.anchor { + next_grapheme_boundary(text, self.anchor) + } else if self.head < self.anchor && char_idx >= self.anchor { + prev_grapheme_boundary(text, self.anchor) + } else { + self.anchor + }; + + Range::new(anchor, char_idx) + } + // groupAt #[inline] -- cgit v1.2.3-70-g09d2