diff options
Diffstat (limited to 'helix-core/src/movement.rs')
-rw-r--r-- | helix-core/src/movement.rs | 73 |
1 files changed, 49 insertions, 24 deletions
diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs index e01786eb..62311ee4 100644 --- a/helix-core/src/movement.rs +++ b/helix-core/src/movement.rs @@ -7,9 +7,9 @@ use crate::{ coords_at_pos, graphemes::{ next_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary, - prev_grapheme_boundary, + prev_grapheme_boundary, RopeGraphemes, }, - line_ending::get_line_ending, + line_ending::line_without_line_ending, pos_at_coords, Position, Range, RopeSlice, }; @@ -95,36 +95,61 @@ pub fn move_vertically( count: usize, behaviour: Movement, ) -> Range { - let Position { row, col } = coords_at_pos(slice, range.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 + }; + // Compute the current position's 2d coordinates. + let Position { row, col } = coords_at_pos(slice, pos); 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), - slice.len_lines().saturating_sub(1), - ), - }; + // Compute the new position. + let new_pos = { + let new_row = if dir == Direction::Backward { + row.saturating_sub(count) + } else { + (row + count).min(slice.len_lines().saturating_sub(1)) + }; + let max_col = RopeGraphemes::new(line_without_line_ending(&slice, new_row)).count(); + let new_col = col.max(horiz as usize).min(max_col); - // Length of the line sans line-ending. - let new_line_len = { - let line = slice.line(new_line); - line.len_chars() - get_line_ending(&line).map(|le| le.len_chars()).unwrap_or(0) + pos_at_coords(slice, Position::new(new_row, new_col)) }; - let new_col = std::cmp::min(horiz as usize, new_line_len); - - let pos = pos_at_coords(slice, Position::new(new_line, new_col)); + // Compute the new range according to the type of movement. + match behaviour { + Movement::Move => Range { + anchor: new_pos, + head: new_pos, + horiz: Some(horiz), + }, + + Movement::Extend => { + let new_head = if new_pos >= range.anchor { + next_grapheme_boundary(slice, new_pos) + } else { + new_pos + }; - let anchor = match behaviour { - Movement::Extend => range.anchor, - Movement::Move => 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 + }; - let mut range = Range::new(anchor, pos); - range.horiz = Some(horiz); - range + Range { + anchor: new_anchor, + head: new_head, + horiz: Some(horiz), + } + } + } } pub fn move_next_word_start(slice: RopeSlice, range: Range, count: usize) -> Range { |