mirror of https://github.com/helix-editor/helix
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
parent
85d5b399de
commit
753f7f381b
|
@ -32,60 +32,31 @@ pub fn move_horizontally(
|
||||||
count: usize,
|
count: usize,
|
||||||
behaviour: Movement,
|
behaviour: Movement,
|
||||||
) -> Range {
|
) -> Range {
|
||||||
match (behaviour, dir) {
|
use Movement::Extend;
|
||||||
(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.
|
// Shift back one grapheme if needed, to account for
|
||||||
let mut head = nth_prev_grapheme_boundary(slice, range.head, count);
|
// the cursor being visually 1-width.
|
||||||
let mut anchor = range.anchor;
|
let pos = if range.head > range.anchor {
|
||||||
|
prev_grapheme_boundary(slice, range.head)
|
||||||
|
} else {
|
||||||
|
range.head
|
||||||
|
};
|
||||||
|
|
||||||
// If the head and anchor crossed over each other, we need to
|
// Compute the new position.
|
||||||
// fiddle around to make it behave like a 1-wide cursor.
|
let mut new_pos = if dir == Direction::Backward {
|
||||||
if head <= anchor && range.head > range.anchor {
|
nth_prev_grapheme_boundary(slice, pos, count)
|
||||||
anchor = next_grapheme_boundary(slice, anchor);
|
} else {
|
||||||
head = prev_grapheme_boundary(slice, head);
|
nth_next_grapheme_boundary(slice, pos, count)
|
||||||
}
|
};
|
||||||
|
|
||||||
Range::new(anchor, head)
|
// Shift forward one grapheme if needed, for the
|
||||||
}
|
// visual 1-width cursor.
|
||||||
(Movement::Extend, Direction::Forward) => {
|
if behaviour == Extend && new_pos >= range.anchor {
|
||||||
// Ensure a valid initial selection state.
|
new_pos = next_grapheme_boundary(slice, new_pos);
|
||||||
let range = range.min_width_1(slice);
|
};
|
||||||
|
|
||||||
// Do the main movement.
|
// Compute the final new range.
|
||||||
let mut head = nth_next_grapheme_boundary(slice, range.head, count);
|
range.put(slice, behaviour == Extend, new_pos)
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
Range::new(anchor, head)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_vertically(
|
pub fn move_vertically(
|
||||||
|
@ -135,19 +106,9 @@ pub fn move_vertically(
|
||||||
new_pos
|
new_pos
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_anchor = if range.anchor <= range.head && range.anchor > new_head {
|
let mut new_range = range.put(slice, true, new_head);
|
||||||
next_grapheme_boundary(slice, range.anchor)
|
new_range.horiz = Some(horiz);
|
||||||
} else if range.anchor > range.head && range.anchor < new_head {
|
new_range
|
||||||
prev_grapheme_boundary(slice, range.anchor)
|
|
||||||
} else {
|
|
||||||
range.anchor
|
|
||||||
};
|
|
||||||
|
|
||||||
Range {
|
|
||||||
anchor: new_anchor,
|
|
||||||
head: new_head,
|
|
||||||
horiz: Some(horiz),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
graphemes::{
|
graphemes::{
|
||||||
ensure_grapheme_boundary_next, ensure_grapheme_boundary_prev, next_grapheme_boundary,
|
ensure_grapheme_boundary_next, ensure_grapheme_boundary_prev, next_grapheme_boundary,
|
||||||
|
prev_grapheme_boundary,
|
||||||
},
|
},
|
||||||
Assoc, ChangeSet, RopeSlice,
|
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
|
// groupAt
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
Loading…
Reference in New Issue