aboutsummaryrefslogtreecommitdiff
path: root/helix-core/src/movement.rs
diff options
context:
space:
mode:
Diffstat (limited to 'helix-core/src/movement.rs')
-rw-r--r--helix-core/src/movement.rs81
1 files changed, 81 insertions, 0 deletions
diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs
index 57abc498..88d6df0a 100644
--- a/helix-core/src/movement.rs
+++ b/helix-core/src/movement.rs
@@ -1,6 +1,66 @@
use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
use crate::{coords_at_pos, pos_at_coords, ChangeSet, Position, Range, Rope, RopeSlice, Selection};
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub enum Direction {
+ Forward,
+ Backward,
+}
+
+pub fn move_horizontally(
+ text: RopeSlice,
+ range: Range,
+ dir: Direction,
+ count: usize,
+ extend: bool,
+) -> Range {
+ let pos = range.head;
+ let line = text.char_to_line(pos);
+ // TODO: we can optimize clamping by passing in RopeSlice limited to current line. that way
+ // we stop calculating past start/end of line.
+ let pos = match dir {
+ Direction::Backward => {
+ let start = text.line_to_char(line);
+ nth_prev_grapheme_boundary(text, pos, count).max(start)
+ }
+ Direction::Forward => {
+ // Line end is pos at the start of next line - 1
+ // subtract another 1 because the line ends with \n
+ let end = text.line_to_char(line + 1).saturating_sub(2);
+ nth_next_grapheme_boundary(text, pos, count).min(end)
+ }
+ };
+ Range::new(if extend { range.anchor } else { pos }, pos)
+}
+
+pub fn move_vertically(
+ text: RopeSlice,
+ range: Range,
+ dir: Direction,
+ count: usize,
+ extend: bool,
+) -> Range {
+ let Position { row, col } = coords_at_pos(text, range.head);
+
+ let horiz = range.horiz.unwrap_or(col as u32);
+
+ let new_line = match dir {
+ Direction::Backward => row.saturating_sub(count),
+ Direction::Forward => std::cmp::min(row.saturating_add(count), text.len_lines() - 1),
+ };
+
+ // convert to 0-indexed, subtract another 1 because len_chars() counts \n
+ let new_line_len = text.line(new_line).len_chars().saturating_sub(2);
+
+ let new_col = std::cmp::min(horiz as usize, new_line_len);
+
+ let pos = pos_at_coords(text, Position::new(new_line, new_col));
+
+ let mut range = Range::new(if extend { range.anchor } else { pos }, pos);
+ range.horiz = Some(horiz);
+ range
+}
+
pub fn move_next_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
for _ in 0..count {
if pos + 1 == slice.len_chars() {
@@ -155,3 +215,24 @@ where
*pos = pos.saturating_sub(1);
}
}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_vertical_move() {
+ let text = Rope::from("abcd\nefg\nwrs");
+ let slice = text.slice(..);
+ let pos = pos_at_coords(slice, (0, 4).into());
+
+ let range = Range::new(pos, pos);
+ assert_eq!(
+ coords_at_pos(
+ slice,
+ move_vertically(slice, range, Direction::Forward, 1, false).head
+ ),
+ (1, 2).into()
+ );
+ }
+}