diff options
Diffstat (limited to 'helix-core')
-rw-r--r-- | helix-core/src/movement.rs | 81 | ||||
-rw-r--r-- | helix-core/src/state.rs | 113 |
2 files changed, 82 insertions, 112 deletions
diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs index 57abc498..88d6df0a 100644 --- a/helix-core/src/movement.rs +++ b/helix-core/src/movement.rs @@ -1,6 +1,66 @@ use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes}; use crate::{coords_at_pos, pos_at_coords, ChangeSet, Position, Range, Rope, RopeSlice, Selection}; +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum Direction { + Forward, + Backward, +} + +pub fn move_horizontally( + text: RopeSlice, + range: Range, + dir: Direction, + count: usize, + extend: bool, +) -> Range { + let pos = range.head; + let line = text.char_to_line(pos); + // TODO: we can optimize clamping by passing in RopeSlice limited to current line. that way + // we stop calculating past start/end of line. + let pos = match dir { + Direction::Backward => { + let start = text.line_to_char(line); + nth_prev_grapheme_boundary(text, pos, count).max(start) + } + Direction::Forward => { + // Line end is pos at the start of next line - 1 + // subtract another 1 because the line ends with \n + let end = text.line_to_char(line + 1).saturating_sub(2); + nth_next_grapheme_boundary(text, pos, count).min(end) + } + }; + Range::new(if extend { range.anchor } else { pos }, pos) +} + +pub fn move_vertically( + text: RopeSlice, + range: Range, + dir: Direction, + count: usize, + extend: bool, +) -> Range { + let Position { row, col } = coords_at_pos(text, range.head); + + 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), text.len_lines() - 1), + }; + + // convert to 0-indexed, subtract another 1 because len_chars() counts \n + let new_line_len = text.line(new_line).len_chars().saturating_sub(2); + + let new_col = std::cmp::min(horiz as usize, new_line_len); + + let pos = pos_at_coords(text, Position::new(new_line, new_col)); + + let mut range = Range::new(if extend { range.anchor } else { pos }, pos); + range.horiz = Some(horiz); + range +} + pub fn move_next_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize { for _ in 0..count { if pos + 1 == slice.len_chars() { @@ -155,3 +215,24 @@ where *pos = pos.saturating_sub(1); } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_vertical_move() { + let text = Rope::from("abcd\nefg\nwrs"); + let slice = text.slice(..); + let pos = pos_at_coords(slice, (0, 4).into()); + + let range = Range::new(pos, pos); + assert_eq!( + coords_at_pos( + slice, + move_vertically(slice, range, Direction::Forward, 1, false).head + ), + (1, 2).into() + ); + } +} diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs index 9dcd5548..ab475cab 100644 --- a/helix-core/src/state.rs +++ b/helix-core/src/state.rs @@ -1,25 +1,12 @@ -use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes}; -use crate::{coords_at_pos, pos_at_coords, ChangeSet, Position, Range, Rope, RopeSlice, Selection}; +use crate::{Rope, Selection}; /// A state represents the current editor state of a single buffer. #[derive(Clone)] pub struct State { - // TODO: fields should be private but we need to refactor commands.rs first pub doc: Rope, pub selection: Selection, } -#[derive(Copy, Clone, PartialEq, Eq)] -pub enum Direction { - Forward, - Backward, -} -#[derive(Copy, Clone, PartialEq, Eq)] -pub enum Granularity { - Character, - Line, -} - impl State { #[must_use] pub fn new(doc: Rope) -> Self { @@ -51,102 +38,4 @@ impl State { // syntax // foldable // changeFilter/transactionFilter - - pub fn move_range( - &self, - range: Range, - dir: Direction, - granularity: Granularity, - count: usize, - extend: bool, - ) -> Range { - let text = self.doc.slice(..); - let pos = range.head; - let line = text.char_to_line(pos); - // TODO: we can optimize clamping by passing in RopeSlice limited to current line. that way - // we stop calculating past start/end of line. - let pos = match (dir, granularity) { - (Direction::Backward, Granularity::Character) => { - let start = text.line_to_char(line); - nth_prev_grapheme_boundary(text, pos, count).max(start) - } - (Direction::Forward, Granularity::Character) => { - // Line end is pos at the start of next line - 1 - // subtract another 1 because the line ends with \n - let end = text.line_to_char(line + 1).saturating_sub(2); - nth_next_grapheme_boundary(text, pos, count).min(end) - } - (_, Granularity::Line) => return move_vertically(text, dir, range, count, extend), - }; - Range::new(if extend { range.anchor } else { pos }, pos) - } - - pub fn move_selection( - &self, - dir: Direction, - granularity: Granularity, - count: usize, - ) -> Selection { - self.selection - .transform(|range| self.move_range(range, dir, granularity, count, false)) - } - - pub fn extend_selection( - &self, - dir: Direction, - granularity: Granularity, - count: usize, - ) -> Selection { - self.selection - .transform(|range| self.move_range(range, dir, granularity, count, true)) - } -} - -fn move_vertically( - text: RopeSlice, - dir: Direction, - range: Range, - count: usize, - extend: bool, -) -> Range { - let Position { row, col } = coords_at_pos(text, range.head); - - 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), text.len_lines() - 1), - }; - - // convert to 0-indexed, subtract another 1 because len_chars() counts \n - let new_line_len = text.line(new_line).len_chars().saturating_sub(2); - - let new_col = std::cmp::min(horiz as usize, new_line_len); - - let pos = pos_at_coords(text, Position::new(new_line, new_col)); - - let mut range = Range::new(if extend { range.anchor } else { pos }, pos); - range.horiz = Some(horiz); - range -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_vertical_move() { - let text = Rope::from("abcd\nefg\nwrs"); - let slice = text.slice(..); - let pos = pos_at_coords(slice, (0, 4).into()); - - let range = Range::new(pos, pos); - assert_eq!( - coords_at_pos( - slice, - move_vertically(slice, Direction::Forward, range, 1, false).head - ), - (1, 2).into() - ); - } } |