use std::cmp::min; use alacritty_config_derive::ConfigDeserialize; use crate::event::EventListener; use crate::grid::{Dimensions, GridCell}; use crate::index::{Boundary, Column, Direction, Line, Point, Side}; use crate::term::cell::Flags; use crate::term::Term; /// Possible vi mode motion movements. #[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)] pub enum ViMotion { /// Move up. Up, /// Move down. Down, /// Move left. Left, /// Move right. Right, /// Move to start of line. First, /// Move to end of line. Last, /// Move to the first non-empty cell. FirstOccupied, /// Move to top of screen. High, /// Move to center of screen. Middle, /// Move to bottom of screen. Low, /// Move to start of semantically separated word. SemanticLeft, /// Move to start of next semantically separated word. SemanticRight, /// Move to end of previous semantically separated word. SemanticLeftEnd, /// Move to end of semantically separated word. SemanticRightEnd, /// Move to start of whitespace separated word. WordLeft, /// Move to start of next whitespace separated word. WordRight, /// Move to end of previous whitespace separated word. WordLeftEnd, /// Move to end of whitespace separated word. WordRightEnd, /// Move to opposing bracket. Bracket, } /// Cursor tracking vi mode position. #[derive(Default, Copy, Clone, PartialEq, Eq)] pub struct ViModeCursor { pub point: Point, } impl ViModeCursor { pub fn new(point: Point) -> Self { Self { point } } /// Move vi mode cursor. #[must_use = "this returns the result of the operation, without modifying the original"] pub fn motion(mut self, term: &mut Term, motion: ViMotion) -> Self { match motion { ViMotion::Up => { if self.point.line > term.topmost_line() { self.point.line -= 1; } }, ViMotion::Down => { if self.point.line + 1 < term.screen_lines() as i32 { self.point.line += 1; } }, ViMotion::Left => { self.point = term.expand_wide(self.point, Direction::Left); let wrap_point = Point::new(self.point.line - 1, term.last_column()); if self.point.column == 0 && self.point.line > term.topmost_line() && is_wrap(term, wrap_point) { self.point = wrap_point; } else { self.point.column = Column(self.point.column.saturating_sub(1)); } }, ViMotion::Right => { self.point = term.expand_wide(self.point, Direction::Right); if is_wrap(term, self.point) { self.point = Point::new(self.point.line + 1, Column(0)); } else { self.point.column = min(self.point.column + 1, term.last_column()); } }, ViMotion::First => { self.point = term.expand_wide(self.point, Direction::Left); while self.point.column == 0 && self.point.line > term.topmost_line() && is_wrap(term, Point::new(self.point.line - 1, term.last_column())) { self.point.line -= 1; } self.point.column = Column(0); }, ViMotion::Last => self.point = last(term, self.point), ViMotion::FirstOccupied => self.point = first_occupied(term, self.point), ViMotion::High => { let line = Line(-(term.grid().display_offset() as i32)); let col = first_occupied_in_line(term, line).unwrap_or_default().column; self.point = Point::new(line, col); }, ViMotion::Middle => { let display_offset = term.grid().display_offset() as i32; let line = Line(-display_offset + term.screen_lines() as i32 / 2 - 1); let col = first_occupied_in_line(term, line).unwrap_or_default().column; self.point = Point::new(line, col); }, ViMotion::Low => { let display_offset = term.grid().display_offset() as i32; let line = Line(-display_offset + term.screen_lines() as i32 - 1); let col = first_occupied_in_line(term, line).unwrap_or_default().column; self.point = Point::new(line, col); }, ViMotion::SemanticLeft => { self.point = semantic(term, self.point, Direction::Left, Side::Left); }, ViMotion::SemanticRight => { self.point = semantic(term, self.point, Direction::Right, Side::Left); }, ViMotion::SemanticLeftEnd => { self.point = semantic(term, self.point, Direction::Left, Side::Right); }, ViMotion::SemanticRightEnd => { self.point = semantic(term, self.point, Direction::Right, Side::Right); }, ViMotion::WordLeft => { self.point = word(term, self.point, Direction::Left, Side::Left); }, ViMotion::WordRight => { self.point = word(term, self.point, Direction::Right, Side::Left); }, ViMotion::WordLeftEnd => { self.point = word(term, self.point, Direction::Left, Side::Right); }, ViMotion::WordRightEnd => { self.point = word(term, self.point, Direction::Right, Side::Right); }, ViMotion::Bracket => self.point = term.bracket_search(self.point).unwrap_or(self.point), } term.scroll_to_point(self.point); self } /// Get target cursor point for vim-like page movement. #[must_use = "this returns the result of the operation, without modifying the original"] pub fn scroll(mut self, term: &Term, lines: i32) -> Self { // Clamp movement to within visible region. let line = (self.point.line - lines).grid_clamp(term, Boundary::Grid); // Find the first occupied cell after scrolling has been performed. let column = first_occupied_in_line(term, line).unwrap_or_default().column; // Move cursor. self.point = Point::new(line, column); self } } /// Find next end of line to move to. fn last(term: &Term, mut point: Point) -> Point { // Expand across wide cells. point = term.expand_wide(point, Direction::Right); // Find last non-empty cell in the current line. let occupied = last_occupied_in_line(term, point.line).unwrap_or_default(); if point.column < occupied.column { // Jump to last occupied cell when not already at or beyond it. occupied } else if is_wrap(term, point) { // Jump to last occupied cell across linewraps. while is_wrap(term, point) { point.line += 1; } last_occupied_in_line(term, point.line).unwrap_or(point) } else { // Jump to last column when beyond the last occupied cell. Point::new(point.line, term.last_column()) } } /// Find next non-empty cell to move to. fn first_occupied(term: &Term, mut point: Point) -> Point { let last_column = term.last_column(); // Expand left across wide chars, since we're searching lines left to right. point = term.expand_wide(point, Direction::Left); // Find first non-empty cell in current line. let occupied = first_occupied_in_line(term, point.line) .unwrap_or_else(|| Point::new(point.line, last_column)); // Jump across wrapped lines if we're already at this line's first occupied cell. if point == occupied { let mut occupied = None; // Search for non-empty cell in previous lines. for line in (term.topmost_line().0..point.line.0).rev().map(Line::from) { if !is_wrap(term, Point::new(line, last_column)) { break; } occupied = first_occupied_in_line(term, line).or(occupied); } // Fallback to the next non-empty cell. let mut line = point.line; occupied.unwrap_or_else(|| loop { if let Some(occupied) = first_occupied_in_line(term, line) { break occupied; } let last_cell = Point::new(line, last_column); if !is_wrap(term, last_cell) { break last_cell; } line += 1; }) } else { occupied } } /// Move by semantically separated word, like w/b/e/ge in vi. fn semantic( term: &mut Term, mut point: Point, direction: Direction, side: Side, ) -> Point { // Expand semantically based on movement direction. let expand_semantic = |point: Point| { // Do not expand when currently on a semantic escape char. let cell = &term.grid()[point]; if term.semantic_escape_chars().contains(cell.c) && !cell.flags.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER) { point } else if direction == Direction::Left { term.semantic_search_left(point) } else { term.semantic_search_right(point) } }; // Make sure we jump above wide chars. point = term.expand_wide(point, direction); // Move to word boundary. if direction != side && !is_boundary(term, point, direction) { point = expand_semantic(point); } // Skip whitespace. let mut next_point = advance(term, point, direction); while !is_boundary(term, point, direction) && is_space(term, next_point) { point = next_point; next_point = advance(term, point, direction); } // Assure minimum movement of one cell. if !is_boundary(term, point, direction) { point = advance(term, point, direction); } // Move to word boundary. if direction == side && !is_boundary(term, point, direction) { point = expand_semantic(point); } point } /// Move by whitespace separated word, like W/B/E/gE in vi. fn word( term: &mut Term, mut point: Point, direction: Direction, side: Side, ) -> Point { // Make sure we jump above wide chars. point = term.expand_wide(point, direction); if direction == side { // Skip whitespace until right before a word. let mut next_point = advance(term, point, direction); while !is_boundary(term, point, direction) && is_space(term, next_point) { point = next_point; next_point = advance(term, point, direction); } // Skip non-whitespace until right inside word boundary. let mut next_point = advance(term, point, direction); while !is_boundary(term, point, direction) && !is_space(term, next_point) { point = next_point; next_point = advance(term, point, direction); } } if direction != side { // Skip non-whitespace until just beyond word. while !is_boundary(term, point, direction) && !is_space(term, point) { point = advance(term, point, direction); } // Skip whitespace until right inside word boundary. while !is_boundary(term, point, direction) && is_space(term, point) { point = advance(term, point, direction); } } point } /// Find first non-empty cell in line. fn first_occupied_in_line(term: &Term, line: Line) -> Option { (0..term.columns()) .map(|col| Point::new(line, Column(col))) .find(|&point| !is_space(term, point)) } /// Find last non-empty cell in line. fn last_occupied_in_line(term: &Term, line: Line) -> Option { (0..term.columns()) .map(|col| Point::new(line, Column(col))) .rfind(|&point| !is_space(term, point)) } /// Advance point based on direction. fn advance(term: &Term, point: Point, direction: Direction) -> Point { if direction == Direction::Left { point.sub(term, Boundary::Grid, 1) } else { point.add(term, Boundary::Grid, 1) } } /// Check if cell at point contains whitespace. fn is_space(term: &Term, point: Point) -> bool { let cell = &term.grid()[point.line][point.column]; !cell.flags().intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER) && (cell.c == ' ' || cell.c == '\t') } /// Check if the cell at a point contains the WRAPLINE flag. fn is_wrap(term: &Term, point: Point) -> bool { term.grid()[point].flags.contains(Flags::WRAPLINE) } /// Check if point is at screen boundary. fn is_boundary(term: &Term, point: Point, direction: Direction) -> bool { (point.line <= term.topmost_line() && point.column == 0 && direction == Direction::Left) || (point.line == term.bottommost_line() && point.column + 1 >= term.columns() && direction == Direction::Right) } #[cfg(test)] mod tests { use super::*; use crate::ansi::Handler; use crate::config::Config; use crate::event::VoidListener; use crate::index::{Column, Line}; use crate::term::test::TermSize; use crate::term::Term; fn term() -> Term { let size = TermSize::new(20, 20); Term::new(&Config::default(), &size, VoidListener) } #[test] fn motion_simple() { let mut term = term(); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::Right); assert_eq!(cursor.point, Point::new(Line(0), Column(1))); cursor = cursor.motion(&mut term, ViMotion::Left); assert_eq!(cursor.point, Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::Down); assert_eq!(cursor.point, Point::new(Line(1), Column(0))); cursor = cursor.motion(&mut term, ViMotion::Up); assert_eq!(cursor.point, Point::new(Line(0), Column(0))); } #[test] fn simple_wide() { let mut term = term(); term.grid_mut()[Line(0)][Column(0)].c = 'a'; term.grid_mut()[Line(0)][Column(1)].c = '汉'; term.grid_mut()[Line(0)][Column(1)].flags.insert(Flags::WIDE_CHAR); term.grid_mut()[Line(0)][Column(2)].c = ' '; term.grid_mut()[Line(0)][Column(2)].flags.insert(Flags::WIDE_CHAR_SPACER); term.grid_mut()[Line(0)][Column(3)].c = 'a'; let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(1))); cursor = cursor.motion(&mut term, ViMotion::Right); assert_eq!(cursor.point, Point::new(Line(0), Column(3))); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(2))); cursor = cursor.motion(&mut term, ViMotion::Left); assert_eq!(cursor.point, Point::new(Line(0), Column(0))); } #[test] fn motion_start_end() { let mut term = term(); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::Last); assert_eq!(cursor.point, Point::new(Line(0), Column(19))); cursor = cursor.motion(&mut term, ViMotion::First); assert_eq!(cursor.point, Point::new(Line(0), Column(0))); } #[test] fn motion_first_occupied() { let mut term = term(); term.grid_mut()[Line(0)][Column(0)].c = ' '; term.grid_mut()[Line(0)][Column(1)].c = 'x'; term.grid_mut()[Line(0)][Column(2)].c = ' '; term.grid_mut()[Line(0)][Column(3)].c = 'y'; term.grid_mut()[Line(0)][Column(19)].flags.insert(Flags::WRAPLINE); term.grid_mut()[Line(1)][Column(19)].flags.insert(Flags::WRAPLINE); term.grid_mut()[Line(2)][Column(0)].c = 'z'; term.grid_mut()[Line(2)][Column(1)].c = ' '; let mut cursor = ViModeCursor::new(Point::new(Line(2), Column(1))); cursor = cursor.motion(&mut term, ViMotion::FirstOccupied); assert_eq!(cursor.point, Point::new(Line(2), Column(0))); cursor = cursor.motion(&mut term, ViMotion::FirstOccupied); assert_eq!(cursor.point, Point::new(Line(0), Column(1))); } #[test] fn motion_high_middle_low() { let mut term = term(); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::High); assert_eq!(cursor.point, Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::Middle); assert_eq!(cursor.point, Point::new(Line(9), Column(0))); cursor = cursor.motion(&mut term, ViMotion::Low); assert_eq!(cursor.point, Point::new(Line(19), Column(0))); } #[test] fn motion_bracket() { let mut term = term(); term.grid_mut()[Line(0)][Column(0)].c = '('; term.grid_mut()[Line(0)][Column(1)].c = 'x'; term.grid_mut()[Line(0)][Column(2)].c = ')'; let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::Bracket); assert_eq!(cursor.point, Point::new(Line(0), Column(2))); cursor = cursor.motion(&mut term, ViMotion::Bracket); assert_eq!(cursor.point, Point::new(Line(0), Column(0))); } fn motion_semantic_term() -> Term { let mut term = term(); term.grid_mut()[Line(0)][Column(0)].c = 'x'; term.grid_mut()[Line(0)][Column(1)].c = ' '; term.grid_mut()[Line(0)][Column(2)].c = 'x'; term.grid_mut()[Line(0)][Column(3)].c = 'x'; term.grid_mut()[Line(0)][Column(4)].c = ' '; term.grid_mut()[Line(0)][Column(5)].c = ' '; term.grid_mut()[Line(0)][Column(6)].c = ':'; term.grid_mut()[Line(0)][Column(7)].c = ' '; term.grid_mut()[Line(0)][Column(8)].c = 'x'; term.grid_mut()[Line(0)][Column(9)].c = ':'; term.grid_mut()[Line(0)][Column(10)].c = 'x'; term.grid_mut()[Line(0)][Column(11)].c = ' '; term.grid_mut()[Line(0)][Column(12)].c = ' '; term.grid_mut()[Line(0)][Column(13)].c = ':'; term.grid_mut()[Line(0)][Column(14)].c = ' '; term.grid_mut()[Line(0)][Column(15)].c = 'x'; term } #[test] fn motion_semantic_right_end() { let mut term = motion_semantic_term(); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(3))); cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(6))); cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(8))); cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(9))); cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(10))); cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(13))); cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(15))); } #[test] fn motion_semantic_left_start() { let mut term = motion_semantic_term(); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(15))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeft); assert_eq!(cursor.point, Point::new(Line(0), Column(13))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeft); assert_eq!(cursor.point, Point::new(Line(0), Column(10))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeft); assert_eq!(cursor.point, Point::new(Line(0), Column(9))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeft); assert_eq!(cursor.point, Point::new(Line(0), Column(8))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeft); assert_eq!(cursor.point, Point::new(Line(0), Column(6))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeft); assert_eq!(cursor.point, Point::new(Line(0), Column(2))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeft); assert_eq!(cursor.point, Point::new(Line(0), Column(0))); } #[test] fn motion_semantic_right_start() { let mut term = motion_semantic_term(); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::SemanticRight); assert_eq!(cursor.point, Point::new(Line(0), Column(2))); cursor = cursor.motion(&mut term, ViMotion::SemanticRight); assert_eq!(cursor.point, Point::new(Line(0), Column(6))); cursor = cursor.motion(&mut term, ViMotion::SemanticRight); assert_eq!(cursor.point, Point::new(Line(0), Column(8))); cursor = cursor.motion(&mut term, ViMotion::SemanticRight); assert_eq!(cursor.point, Point::new(Line(0), Column(9))); cursor = cursor.motion(&mut term, ViMotion::SemanticRight); assert_eq!(cursor.point, Point::new(Line(0), Column(10))); cursor = cursor.motion(&mut term, ViMotion::SemanticRight); assert_eq!(cursor.point, Point::new(Line(0), Column(13))); cursor = cursor.motion(&mut term, ViMotion::SemanticRight); assert_eq!(cursor.point, Point::new(Line(0), Column(15))); } #[test] fn motion_semantic_left_end() { let mut term = motion_semantic_term(); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(15))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(13))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(10))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(9))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(8))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(6))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(3))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(0))); } #[test] fn scroll_semantic() { let mut term = term(); term.grid_mut().scroll_up(&(Line(0)..Line(20)), 5); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeft); assert_eq!(cursor.point, Point::new(Line(-5), Column(0))); assert_eq!(term.grid().display_offset(), 5); cursor = cursor.motion(&mut term, ViMotion::SemanticRight); assert_eq!(cursor.point, Point::new(Line(19), Column(19))); assert_eq!(term.grid().display_offset(), 0); cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd); assert_eq!(cursor.point, Point::new(Line(-5), Column(0))); assert_eq!(term.grid().display_offset(), 5); cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd); assert_eq!(cursor.point, Point::new(Line(19), Column(19))); assert_eq!(term.grid().display_offset(), 0); } #[test] fn semantic_wide() { let mut term = term(); term.grid_mut()[Line(0)][Column(0)].c = 'a'; term.grid_mut()[Line(0)][Column(1)].c = ' '; term.grid_mut()[Line(0)][Column(2)].c = '汉'; term.grid_mut()[Line(0)][Column(2)].flags.insert(Flags::WIDE_CHAR); term.grid_mut()[Line(0)][Column(3)].c = ' '; term.grid_mut()[Line(0)][Column(3)].flags.insert(Flags::WIDE_CHAR_SPACER); term.grid_mut()[Line(0)][Column(4)].c = ' '; term.grid_mut()[Line(0)][Column(5)].c = 'a'; let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(2))); cursor = cursor.motion(&mut term, ViMotion::SemanticRight); assert_eq!(cursor.point, Point::new(Line(0), Column(5))); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(3))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeft); assert_eq!(cursor.point, Point::new(Line(0), Column(0))); } #[test] fn motion_word() { let mut term = term(); term.grid_mut()[Line(0)][Column(0)].c = 'a'; term.grid_mut()[Line(0)][Column(1)].c = ';'; term.grid_mut()[Line(0)][Column(2)].c = ' '; term.grid_mut()[Line(0)][Column(3)].c = ' '; term.grid_mut()[Line(0)][Column(4)].c = 'a'; term.grid_mut()[Line(0)][Column(5)].c = ';'; let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::WordRightEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(1))); cursor = cursor.motion(&mut term, ViMotion::WordRightEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(5))); cursor = cursor.motion(&mut term, ViMotion::WordLeft); assert_eq!(cursor.point, Point::new(Line(0), Column(4))); cursor = cursor.motion(&mut term, ViMotion::WordLeft); assert_eq!(cursor.point, Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::WordRight); assert_eq!(cursor.point, Point::new(Line(0), Column(4))); cursor = cursor.motion(&mut term, ViMotion::WordLeftEnd); assert_eq!(cursor.point, Point::new(Line(0), Column(1))); } #[test] fn scroll_word() { let mut term = term(); term.grid_mut().scroll_up(&(Line(0)..Line(20)), 5); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::WordLeft); assert_eq!(cursor.point, Point::new(Line(-5), Column(0))); assert_eq!(term.grid().display_offset(), 5); cursor = cursor.motion(&mut term, ViMotion::WordRight); assert_eq!(cursor.point, Point::new(Line(19), Column(19))); assert_eq!(term.grid().display_offset(), 0); cursor = cursor.motion(&mut term, ViMotion::WordLeftEnd); assert_eq!(cursor.point, Point::new(Line(-5), Column(0))); assert_eq!(term.grid().display_offset(), 5); cursor = cursor.motion(&mut term, ViMotion::WordRightEnd); assert_eq!(cursor.point, Point::new(Line(19), Column(19))); assert_eq!(term.grid().display_offset(), 0); } #[test] fn word_wide() { let mut term = term(); term.grid_mut()[Line(0)][Column(0)].c = 'a'; term.grid_mut()[Line(0)][Column(1)].c = ' '; term.grid_mut()[Line(0)][Column(2)].c = '汉'; term.grid_mut()[Line(0)][Column(2)].flags.insert(Flags::WIDE_CHAR); term.grid_mut()[Line(0)][Column(3)].c = ' '; term.grid_mut()[Line(0)][Column(3)].flags.insert(Flags::WIDE_CHAR_SPACER); term.grid_mut()[Line(0)][Column(4)].c = ' '; term.grid_mut()[Line(0)][Column(5)].c = 'a'; let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(2))); cursor = cursor.motion(&mut term, ViMotion::WordRight); assert_eq!(cursor.point, Point::new(Line(0), Column(5))); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(3))); cursor = cursor.motion(&mut term, ViMotion::WordLeft); assert_eq!(cursor.point, Point::new(Line(0), Column(0))); } #[test] fn scroll_simple() { let mut term = term(); // Create 1 line of scrollback. for _ in 0..20 { term.newline(); } let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); cursor = cursor.scroll(&term, -1); assert_eq!(cursor.point, Point::new(Line(1), Column(0))); cursor = cursor.scroll(&term, 1); assert_eq!(cursor.point, Point::new(Line(0), Column(0))); cursor = cursor.scroll(&term, 1); assert_eq!(cursor.point, Point::new(Line(-1), Column(0))); } #[test] fn scroll_over_top() { let mut term = term(); // Create 40 lines of scrollback. for _ in 0..59 { term.newline(); } let mut cursor = ViModeCursor::new(Point::new(Line(19), Column(0))); cursor = cursor.scroll(&term, 20); assert_eq!(cursor.point, Point::new(Line(-1), Column(0))); cursor = cursor.scroll(&term, 20); assert_eq!(cursor.point, Point::new(Line(-21), Column(0))); cursor = cursor.scroll(&term, 20); assert_eq!(cursor.point, Point::new(Line(-40), Column(0))); cursor = cursor.scroll(&term, 20); assert_eq!(cursor.point, Point::new(Line(-40), Column(0))); } #[test] fn scroll_over_bottom() { let mut term = term(); // Create 40 lines of scrollback. for _ in 0..59 { term.newline(); } let mut cursor = ViModeCursor::new(Point::new(Line(-40), Column(0))); cursor = cursor.scroll(&term, -20); assert_eq!(cursor.point, Point::new(Line(-20), Column(0))); cursor = cursor.scroll(&term, -20); assert_eq!(cursor.point, Point::new(Line(0), Column(0))); cursor = cursor.scroll(&term, -20); assert_eq!(cursor.point, Point::new(Line(19), Column(0))); cursor = cursor.scroll(&term, -20); assert_eq!(cursor.point, Point::new(Line(19), Column(0))); } }