use crate::Editor;
use crate::buf_helpers::{buf_line, buf_line_chars};
use hjkl_buffer::{char_col_to_visual_col, visual_col_to_char_col};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Move {
Vertical { row: usize },
Jump { row: usize, col: usize },
Horizontal { col: usize },
Raw { row: usize, col: usize },
}
impl<H: crate::types::Host> Editor<hjkl_buffer::View, H> {
pub fn move_cursor(&mut self, m: Move) {
match m {
Move::Vertical { row } => {
let (current_row, current_col) = self.cursor();
let current_line = buf_line(&self.buffer, current_row).unwrap_or_default();
let want = self.sticky_col().unwrap_or_else(|| {
char_col_to_visual_col(¤t_line, current_col, self.settings().tabstop)
});
self.set_sticky_col(Some(want));
let line = buf_line(&self.buffer, row).unwrap_or_default();
let char_col = visual_col_to_char_col(&line, want, self.settings().tabstop);
let max_col = buf_line_chars(&self.buffer, row).saturating_sub(1);
self.set_cursor_quiet(row, char_col.min(max_col));
}
Move::Jump { row, col } => self.jump_cursor(row, col),
Move::Horizontal { col } => {
let row = self.cursor().0;
self.jump_cursor(row, col);
}
Move::Raw { row, col } => self.set_cursor_quiet(row, col),
}
}
}
#[cfg(test)]
mod tests {
use super::Move;
use crate::Editor;
use crate::types::{DefaultHost, Options};
use hjkl_buffer::View;
fn editor(text: &str) -> Editor<View, DefaultHost> {
Editor::new(View::from_str(text), DefaultHost::new(), Options::default())
}
#[test]
fn vertical_clamps_but_preserves_the_unclamped_want() {
let mut e = editor("abcdefghij\nab\nabcdefghij");
e.move_cursor(Move::Horizontal { col: 7 });
assert_eq!(e.cursor(), (0, 7));
assert_eq!(e.sticky_col(), Some(7));
e.move_cursor(Move::Vertical { row: 1 });
assert_eq!(e.cursor(), (1, 1));
assert_eq!(e.sticky_col(), Some(7));
e.move_cursor(Move::Vertical { row: 2 });
assert_eq!(e.cursor(), (2, 7));
assert_eq!(e.sticky_col(), Some(7));
}
#[test]
fn vertical_over_an_empty_row_collapses_to_col_zero() {
let mut e = editor("abcdef\n\nabcdef");
e.move_cursor(Move::Horizontal { col: 4 });
e.move_cursor(Move::Vertical { row: 1 });
assert_eq!(e.cursor(), (1, 0));
assert_eq!(e.sticky_col(), Some(4));
e.move_cursor(Move::Vertical { row: 2 });
assert_eq!(e.cursor(), (2, 4));
}
#[test]
fn vertical_bootstraps_the_want_from_the_current_column() {
let mut e = editor("abcdef\nabcdef");
e.move_cursor(Move::Raw { row: 0, col: 3 });
assert_eq!(e.sticky_col(), None);
e.move_cursor(Move::Vertical { row: 1 });
assert_eq!(e.cursor(), (1, 3));
assert_eq!(e.sticky_col(), Some(3));
}
#[test]
fn jump_sets_the_want_to_the_landed_column() {
let mut e = editor("abcdefghij\nabcdefghij");
e.move_cursor(Move::Horizontal { col: 9 });
assert_eq!(e.sticky_col(), Some(9));
e.move_cursor(Move::Jump { row: 1, col: 2 });
assert_eq!(e.cursor(), (1, 2));
assert_eq!(e.sticky_col(), Some(2), "a jump resets curswant");
}
#[test]
fn horizontal_keeps_the_row_and_sets_the_want() {
let mut e = editor("abcdefghij\nabcdefghij");
e.move_cursor(Move::Jump { row: 1, col: 8 });
e.move_cursor(Move::Horizontal { col: 3 });
assert_eq!(e.cursor(), (1, 3));
assert_eq!(e.sticky_col(), Some(3));
}
#[test]
fn raw_leaves_the_want_untouched() {
let mut e = editor("abcdefghij\nabcdefghij");
e.move_cursor(Move::Horizontal { col: 6 });
assert_eq!(e.sticky_col(), Some(6));
e.move_cursor(Move::Raw { row: 1, col: 1 });
assert_eq!(e.cursor(), (1, 1));
assert_eq!(e.sticky_col(), Some(6), "Raw must not disturb curswant");
let mut fresh = editor("abcdefghij");
fresh.move_cursor(Move::Raw { row: 0, col: 4 });
assert_eq!(fresh.cursor(), (0, 4));
assert_eq!(fresh.sticky_col(), None);
}
#[test]
fn vertical_bootstraps_display_want_from_the_current_column() {
let mut e = editor("\tabcdef\n\txyz");
e.move_cursor(Move::Raw { row: 0, col: 2 });
assert_eq!(e.sticky_col(), None);
e.move_cursor(Move::Vertical { row: 1 });
assert_eq!(e.cursor(), (1, 2));
assert_eq!(e.sticky_col(), Some(5));
}
#[test]
fn vertical_converts_display_want_to_a_character_column() {
let mut e = editor("\tabcdef\n\txyz");
e.move_cursor(Move::Horizontal { col: 2 });
assert_eq!(e.sticky_col(), Some(5));
e.move_cursor(Move::Vertical { row: 1 });
assert_eq!(e.cursor(), (1, 2));
assert_eq!(e.sticky_col(), Some(5));
}
#[test]
fn vertical_matches_the_apply_sticky_col_clamp() {
for (want, row, expected) in [(7usize, 1usize, 1usize), (0, 1, 0), (1, 1, 1), (99, 0, 9)] {
let mut e = editor("abcdefghij\nab");
e.move_cursor(Move::Horizontal { col: 0 });
e.set_sticky_col(Some(want));
e.move_cursor(Move::Vertical { row });
assert_eq!(e.cursor(), (row, expected), "want {want} onto row {row}");
assert_eq!(e.sticky_col(), Some(want), "want {want} must survive");
}
}
}