@@ -7,9 +7,9 @@ use crate::{
7 coords_at_pos,
7 coords_at_pos,
8 graphemes::{
8 graphemes::{
9 next_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary,
9 next_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary,
10− prev_grapheme_boundary,
10+ prev_grapheme_boundary, RopeGraphemes,
12− line_ending::get_line_ending,
12+ line_ending::line_without_line_ending,
13 pos_at_coords, Position, Range, RopeSlice,
13 pos_at_coords, Position, Range, RopeSlice,
@@ -95,36 +95,61 @@ pub fn move_vertically(
95 count: usize,
95 count: usize,
96 behaviour: Movement,
96 behaviour: Movement,
97) -> Range {
97) -> Range {
98− let Position { row, col } = coords_at_pos(slice, range.head);
98+ // Shift back one grapheme if needed, to account for
99+ // the cursor being visually 1-width.
100+ let pos = if range.head > range.anchor {
101+ prev_grapheme_boundary(slice, range.head)
106+ // Compute the current position's 2d coordinates.
107+ let Position { row, col } = coords_at_pos(slice, pos);
100 let horiz = range.horiz.unwrap_or(col as u32);
108 let horiz = range.horiz.unwrap_or(col as u32);
102− let new_line = match dir {
110+ // Compute the new position.
112+ let new_row = if dir == Direction::Backward {
103− Direction::Backward => row.saturating_sub(count),
113+ row.saturating_sub(count)
104− Direction::Forward => std::cmp::min(
114+ } else {
105− row.saturating_add(count),
106− slice.len_lines().saturating_sub(1),
115+ (row + count).min(slice.len_lines().saturating_sub(1))
108− };
117+ let max_col = RopeGraphemes::new(line_without_line_ending(&slice, new_row)).count();
118+ let new_col = col.max(horiz as usize).min(max_col);
110− // Length of the line sans line-ending.
120+ pos_at_coords(slice, Position::new(new_row, new_col))
111− let new_line_len = {
112− let line = slice.line(new_line);
113− line.len_chars() - get_line_ending(&line).map(|le| le.len_chars()).unwrap_or(0)
116− let new_col = std::cmp::min(horiz as usize, new_line_len);
123+ // Compute the new range according to the type of movement.
117−
124+ match behaviour {
118− let pos = pos_at_coords(slice, Position::new(new_line, new_col));
125+ Movement::Move => Range {
131+ Movement::Extend => {
132+ let new_head = if new_pos >= range.anchor {
133+ next_grapheme_boundary(slice, new_pos)
120− let anchor = match behaviour {
138+ let new_anchor = if range.anchor <= range.head && range.anchor > new_head {
121− Movement::Extend => range.anchor,
139+ next_grapheme_boundary(slice, range.anchor)
122− Movement::Move => pos,
140+ } else if range.anchor > range.head && range.anchor < new_head {
141+ prev_grapheme_boundary(slice, range.anchor)
125− let mut range = Range::new(anchor, pos);
146+ Range {
126− range.horiz = Some(horiz);
149+ horiz: Some(horiz),
130pub fn move_next_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
155pub fn move_next_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {