on master help sign in
Make vertical selection movement work properly.
commit6e15c9b8745e9708ee5271c8701d41a8393cb038 authorNathan Vegdahl <cessen@cessen.com> dateMon 5 Jul 2021, 18:58 (5 years ago) parent28d2d68
3 files changed+60−24
helix-core/src/line_ending.rs+7 helix-core/src/movement.rs+49−24 helix-core/src/position.rs+4
helix-core/src/line_ending.rs+7
@@ -159,6 +159,13 @@ pub fn line_end_char_index(slice: &RopeSlice, line: usize) -> usize {
159159 .unwrap_or(0)
160160}
161161
162+/// Fetches line `line_idx` from the passed rope slice, sans any line ending.
163+pub fn line_without_line_ending<'a>(slice: &'a RopeSlice, line_idx: usize) -> RopeSlice<'a> {
164+ let start = slice.line_to_char(line_idx);
165+ let end = line_end_char_index(slice, line_idx);
166+ slice.slice(start..end)
167+}
168+
162169/// Returns the char index of the end of the given RopeSlice, not including
163170/// any final line ending.
164171pub fn rope_end_without_line_ending(slice: &RopeSlice) -> usize {
helix-core/src/movement.rs+49−24
@@ -7,9 +7,9 @@ use crate::{
77 coords_at_pos,
88 graphemes::{
99 next_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary,
10− prev_grapheme_boundary,
10+ prev_grapheme_boundary, RopeGraphemes,
1111 },
12− line_ending::get_line_ending,
12+ line_ending::line_without_line_ending,
1313 pos_at_coords, Position, Range, RopeSlice,
1414};
1515
@@ -95,36 +95,61 @@ pub fn move_vertically(
9595 count: usize,
9696 behaviour: Movement,
9797) -> 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)
102+ } else {
103+ range.head
104+ };
99105
106+ // Compute the current position's 2d coordinates.
107+ let Position { row, col } = coords_at_pos(slice, pos);
100108 let horiz = range.horiz.unwrap_or(col as u32);
101109
102− let new_line = match dir {
103− Direction::Backward => row.saturating_sub(count),
104− Direction::Forward => std::cmp::min(
105− row.saturating_add(count),
106− slice.len_lines().saturating_sub(1),
107− ),
108− };
110+ // Compute the new position.
111+ let new_pos = {
112+ let new_row = if dir == Direction::Backward {
113+ row.saturating_sub(count)
114+ } else {
115+ (row + count).min(slice.len_lines().saturating_sub(1))
116+ };
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);
109119
110− // Length of the line sans line-ending.
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)
120+ pos_at_coords(slice, Position::new(new_row, new_col))
114121 };
115122
116− let new_col = std::cmp::min(horiz as usize, new_line_len);
117−
118− let pos = pos_at_coords(slice, Position::new(new_line, new_col));
123+ // Compute the new range according to the type of movement.
124+ match behaviour {
125+ Movement::Move => Range {
126+ anchor: new_pos,
127+ head: new_pos,
128+ horiz: Some(horiz),
129+ },
130+
131+ Movement::Extend => {
132+ let new_head = if new_pos >= range.anchor {
133+ next_grapheme_boundary(slice, new_pos)
134+ } else {
135+ new_pos
136+ };
119137
120− let anchor = match behaviour {
121− Movement::Extend => range.anchor,
122− Movement::Move => pos,
123− };
138+ let new_anchor = if range.anchor <= range.head && range.anchor > new_head {
139+ next_grapheme_boundary(slice, range.anchor)
140+ } else if range.anchor > range.head && range.anchor < new_head {
141+ prev_grapheme_boundary(slice, range.anchor)
142+ } else {
143+ range.anchor
144+ };
124145
125− let mut range = Range::new(anchor, pos);
126− range.horiz = Some(horiz);
127− range
146+ Range {
147+ anchor: new_anchor,
148+ head: new_head,
149+ horiz: Some(horiz),
150+ }
151+ }
152+ }
128153}
129154
130155pub fn move_next_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
helix-core/src/position.rs+4
@@ -53,6 +53,8 @@ impl From<Position> for tree_sitter::Point {
5353}
5454/// Convert a character index to (line, column) coordinates.
5555pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position {
56+ // TODO: this isn't correct. This needs to work in terms of
57+ // visual horizontal position, not graphemes.
5658 let line = text.char_to_line(pos);
5759 let line_start = text.line_to_char(line);
5860 let col = RopeGraphemes::new(text.slice(line_start..pos)).count();
@@ -61,6 +63,8 @@ pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position {
6163
6264/// Convert (line, column) coordinates to a character index.
6365pub fn pos_at_coords(text: RopeSlice, coords: Position) -> usize {
66+ // TODO: this isn't correct. This needs to work in terms of
67+ // visual horizontal position, not graphemes.
6468 let Position { row, col } = coords;
6569 let line_start = text.line_to_char(row);
6670 // line_start + col