| use std::cmp::min; |
| |
| #[cfg(feature = "serde")] |
| use serde::{Deserialize, Serialize}; |
| |
| 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(Debug, Copy, Clone, PartialEq, Eq)] |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "lowercase"))] |
| pub enum ViMotion { |
| /// Move up. |
| Up, |
| /// Move down. |
| Down, |
| /// Move left. |
| Left, |
| /// Move right. |
| Right, |
| /// First column, or beginning of the line when already at the first column. |
| First, |
| /// Last column, or beginning of the line when already at the last column. |
| Last, |
| /// First non-empty cell in this terminal row, or first non-empty cell |
| /// of the line when already at the first cell of the row. |
| 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<T: EventListener>(mut self, term: &mut Term<T>, 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<T: EventListener>(mut self, term: &Term<T>, 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<T>(term: &Term<T>, 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<T>(term: &Term<T>, 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<T: EventListener>( |
| term: &Term<T>, |
| 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<T: EventListener>( |
| term: &Term<T>, |
| 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<T>(term: &Term<T>, line: Line) -> Option<Point> { |
| (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<T>(term: &Term<T>, line: Line) -> Option<Point> { |
| (0..term.columns()) |
| .map(|col| Point::new(line, Column(col))) |
| .rfind(|&point| !is_space(term, point)) |
| } |
| |
| /// Advance point based on direction. |
| fn advance<T>(term: &Term<T>, 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<T>(term: &Term<T>, 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<T>(term: &Term<T>, point: Point) -> bool { |
| term.grid()[point].flags.contains(Flags::WRAPLINE) |
| } |
| |
| /// Check if point is at screen boundary. |
| fn is_boundary<T>(term: &Term<T>, 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::event::VoidListener; |
| use crate::index::{Column, Line}; |
| use crate::term::test::TermSize; |
| use crate::term::{Config, Term}; |
| use crate::vte::ansi::Handler; |
| |
| fn term() -> Term<VoidListener> { |
| 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<VoidListener> { |
| 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))); |
| } |
| } |