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.
pull/376/head
Nathan Vegdahl 3 years ago
parent 85d5b399de
commit 753f7f381b

@ -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
use Movement::Extend;
// 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 {
count
range.head
};
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
// Compute the new position.
let mut new_pos = if dir == Direction::Backward {
nth_prev_grapheme_boundary(slice, pos, count)
} else {
count
nth_next_grapheme_boundary(slice, pos, 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);
}
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 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);
};
Range::new(anchor, head)
}
}
// 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
}
}
}

@ -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]

Loading…
Cancel
Save