@@ -19,9 +19,7 @@ pub enum Direction {
19#[derive(Copy, Clone, PartialEq, Eq)]
19#[derive(Copy, Clone, PartialEq, Eq)]
20pub enum Granularity {
20pub enum Granularity {
21 Character,
21 Character,
27impl State {
25impl State {
@@ -87,23 +85,26 @@ impl State {
87 // 2. compose onto a ongoing transaction
85 // 2. compose onto a ongoing transaction
88 // 3. on insert mode leave, that transaction gets stored into undo history
86 // 3. on insert mode leave, that transaction gets stored into undo history
90− pub fn move_pos(
88+ pub fn move_range(
92− pos: usize,
90+ range: Range,
93 dir: Direction,
91 dir: Direction,
94 granularity: Granularity,
92 granularity: Granularity,
95 count: usize,
93 count: usize,
96− ) -> usize {
94+ extend: bool,
97 let text = &self.doc;
96 let text = &self.doc;
97+ let pos = range.head;
98 match (dir, granularity) {
98 match (dir, granularity) {
99 (Direction::Backward, Granularity::Character) => {
99 (Direction::Backward, Granularity::Character) => {
100 // Clamp to line
100 // Clamp to line
101 let line = text.char_to_line(pos);
101 let line = text.char_to_line(pos);
102 let start = text.line_to_char(line);
102 let start = text.line_to_char(line);
103− std::cmp::max(
103+ let pos = std::cmp::max(
104 nth_prev_grapheme_boundary(&text.slice(..), pos, count),
104 nth_prev_grapheme_boundary(&text.slice(..), pos, count),
107+ Range::new(if extend { range.anchor } else { pos }, pos)
108 (Direction::Forward, Granularity::Character) => {
109 (Direction::Forward, Granularity::Character) => {
109 // Clamp to line
110 // Clamp to line
@@ -111,16 +112,11 @@ impl State {
111 // Line end is pos at the start of next line - 1
112 // Line end is pos at the start of next line - 1
112 // subtract another 1 because the line ends with \n
113 // subtract another 1 because the line ends with \n
113 let end = text.line_to_char(line + 1).saturating_sub(2);
114 let end = text.line_to_char(line + 1).saturating_sub(2);
114− std::cmp::min(nth_next_grapheme_boundary(&text.slice(..), pos, count), end)
116+ std::cmp::min(nth_next_grapheme_boundary(&text.slice(..), pos, count), end);
115− }
117+ Range::new(if extend { range.anchor } else { pos }, pos)
116− (Direction::Forward, Granularity::Word) => {
117− Self::move_next_word_start(&text.slice(..), pos)
119− (Direction::Backward, Granularity::Word) => {
120− Self::move_prev_word_start(&text.slice(..), pos)
122− (_, Granularity::Line) => move_vertically(&text.slice(..), dir, pos, count),
119+ (_, Granularity::Line) => move_vertically(&text.slice(..), dir, range, count, extend),
@@ -205,10 +201,8 @@ impl State {
205 // move all selections according to normal cursor move semantics by collapsing it
201 // move all selections according to normal cursor move semantics by collapsing it
206 // into cursors and moving them vertically
202 // into cursors and moving them vertically
208− self.selection.transform(|range| {
204+ self.selection
209− let pos = self.move_pos(range.head, dir, granularity, count);
205+ .transform(|range| self.move_range(range, dir, granularity, count, false))
210− Range::new(pos, pos)
214 pub fn extend_selection(
208 pub fn extend_selection(
@@ -217,10 +211,8 @@ impl State {
217 granularity: Granularity,
211 granularity: Granularity,
218 count: usize,
212 count: usize,
219 ) -> Selection {
213 ) -> Selection {
220− self.selection.transform(|range| {
214+ self.selection
221− let pos = self.move_pos(range.head, dir, granularity, count);
215+ .transform(|range| self.move_range(range, dir, granularity, count, true))
222− Range::new(range.anchor, pos)
@@ -239,8 +231,16 @@ pub fn pos_at_coords(text: &RopeSlice, coords: Position) -> usize {
239 nth_next_grapheme_boundary(text, line_start, col)
231 nth_next_grapheme_boundary(text, line_start, col)
242−fn move_vertically(text: &RopeSlice, dir: Direction, pos: usize, count: usize) -> usize {
234+fn move_vertically(
243− let Position { row, col } = coords_at_pos(text, pos);
241+ let Position { row, col } = coords_at_pos(text, range.head);
243+ let horiz = range.horiz.unwrap_or(col as u32);
245 let new_line = match dir {
245 let new_line = match dir {
246 Direction::Backward => row.saturating_sub(count),
246 Direction::Backward => row.saturating_sub(count),
@@ -250,14 +250,14 @@ fn move_vertically(text: &RopeSlice, dir: Direction, pos: usize, count: usize) -
250 // convert to 0-indexed, subtract another 1 because len_chars() counts \n
250 // convert to 0-indexed, subtract another 1 because len_chars() counts \n
251 let new_line_len = text.line(new_line).len_chars().saturating_sub(2);
251 let new_line_len = text.line(new_line).len_chars().saturating_sub(2);
253− let new_col = if new_line_len < col {
253+ let new_col = std::cmp::min(horiz as usize, new_line_len);
254− // TODO: preserve horiz here
254+
255− new_line_len
255+ let pos = pos_at_coords(text, Position::new(new_line, new_col));
260− pos_at_coords(text, Position::new(new_line, new_col))
257+ let mut range = Range::new(if extend { range.anchor } else { pos }, pos);
258+ use std::convert::TryInto;
259+ range.horiz = Some(horiz);
263// used for by-word movement
263// used for by-word movement
@@ -346,8 +346,12 @@ mod test {
346 let pos = pos_at_coords(&text.slice(..), (0, 4).into());
346 let pos = pos_at_coords(&text.slice(..), (0, 4).into());
347 let slice = text.slice(..);
347 let slice = text.slice(..);
349+ let range = Range::new(pos, pos);
349 assert_eq!(
350 assert_eq!(
350− coords_at_pos(&slice, move_vertically(&slice, Direction::Forward, pos, 1)),
353+ move_vertically(&slice, Direction::Forward, range, 1).head
351 (1, 2).into()
355 (1, 2).into()