@@ -19,9 +19,7 @@ pub enum Direction {
1919#[derive(Copy, Clone, PartialEq, Eq)]
2020pub enum Granularity {
2121 Character,
22− Word,
2322 Line,
24− // LineBoundary
2523}
2624
2725impl State {
@@ -87,23 +85,26 @@ impl State {
8785 // 2. compose onto a ongoing transaction
8886 // 3. on insert mode leave, that transaction gets stored into undo history
8987
90− pub fn move_pos(
88+ pub fn move_range(
9189 &self,
92− pos: usize,
90+ range: Range,
9391 dir: Direction,
9492 granularity: Granularity,
9593 count: usize,
96− ) -> usize {
94+ extend: bool,
95+ ) -> Range {
9796 let text = &self.doc;
97+ let pos = range.head;
9898 match (dir, granularity) {
9999 (Direction::Backward, Granularity::Character) => {
100100 // Clamp to line
101101 let line = text.char_to_line(pos);
102102 let start = text.line_to_char(line);
103− std::cmp::max(
103+ let pos = std::cmp::max(
104104 nth_prev_grapheme_boundary(&text.slice(..), pos, count),
105105 start,
106− )
106+ );
107+ Range::new(if extend { range.anchor } else { pos }, pos)
107108 }
108109 (Direction::Forward, Granularity::Character) => {
109110 // Clamp to line
@@ -111,16 +112,11 @@ impl State {
111112 // Line end is pos at the start of next line - 1
112113 // subtract another 1 because the line ends with \n
113114 let end = text.line_to_char(line + 1).saturating_sub(2);
114− std::cmp::min(nth_next_grapheme_boundary(&text.slice(..), pos, count), end)
115− }
116− (Direction::Forward, Granularity::Word) => {
117− Self::move_next_word_start(&text.slice(..), pos)
115+ let pos =
116+ std::cmp::min(nth_next_grapheme_boundary(&text.slice(..), pos, count), end);
117+ Range::new(if extend { range.anchor } else { pos }, pos)
118118 }
119− (Direction::Backward, Granularity::Word) => {
120− Self::move_prev_word_start(&text.slice(..), pos)
121− }
122− (_, Granularity::Line) => move_vertically(&text.slice(..), dir, pos, count),
123− _ => pos,
119+ (_, Granularity::Line) => move_vertically(&text.slice(..), dir, range, count, extend),
124120 }
125121 }
126122
@@ -205,10 +201,8 @@ impl State {
205201 // move all selections according to normal cursor move semantics by collapsing it
206202 // into cursors and moving them vertically
207203
208− self.selection.transform(|range| {
209− let pos = self.move_pos(range.head, dir, granularity, count);
210− Range::new(pos, pos)
211− })
204+ self.selection
205+ .transform(|range| self.move_range(range, dir, granularity, count, false))
212206 }
213207
214208 pub fn extend_selection(
@@ -217,10 +211,8 @@ impl State {
217211 granularity: Granularity,
218212 count: usize,
219213 ) -> Selection {
220− self.selection.transform(|range| {
221− let pos = self.move_pos(range.head, dir, granularity, count);
222− Range::new(range.anchor, pos)
223− })
214+ self.selection
215+ .transform(|range| self.move_range(range, dir, granularity, count, true))
224216 }
225217}
226218
@@ -239,8 +231,16 @@ pub fn pos_at_coords(text: &RopeSlice, coords: Position) -> usize {
239231 nth_next_grapheme_boundary(text, line_start, col)
240232}
241233
242−fn move_vertically(text: &RopeSlice, dir: Direction, pos: usize, count: usize) -> usize {
243− let Position { row, col } = coords_at_pos(text, pos);
234+fn move_vertically(
235+ text: &RopeSlice,
236+ dir: Direction,
237+ range: Range,
238+ count: usize,
239+ extend: bool,
240+) -> Range {
241+ let Position { row, col } = coords_at_pos(text, range.head);
242+
243+ let horiz = range.horiz.unwrap_or(col as u32);
244244
245245 let new_line = match dir {
246246 Direction::Backward => row.saturating_sub(count),
@@ -250,14 +250,14 @@ fn move_vertically(text: &RopeSlice, dir: Direction, pos: usize, count: usize) -
250250 // convert to 0-indexed, subtract another 1 because len_chars() counts \n
251251 let new_line_len = text.line(new_line).len_chars().saturating_sub(2);
252252
253− let new_col = if new_line_len < col {
254− // TODO: preserve horiz here
255− new_line_len
256− } else {
257− col
258− };
253+ let new_col = std::cmp::min(horiz as usize, new_line_len);
254+
255+ let pos = pos_at_coords(text, Position::new(new_line, new_col));
259256
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);
260+ range
261261}
262262
263263// used for by-word movement
@@ -346,8 +346,12 @@ mod test {
346346 let pos = pos_at_coords(&text.slice(..), (0, 4).into());
347347 let slice = text.slice(..);
348348
349+ let range = Range::new(pos, pos);
349350 assert_eq!(
350− coords_at_pos(&slice, move_vertically(&slice, Direction::Forward, pos, 1)),
351+ coords_at_pos(
352+ &slice,
353+ move_vertically(&slice, Direction::Forward, range, 1).head
354+ ),
351355 (1, 2).into()
352356 );
353357 }