use std::cmp::min;
use crate::crosswords::grid::Dimensions;
use crate::crosswords::pos::{Boundary, Column, Direction, Line, Pos, Side};
use crate::crosswords::square::Wide;
use crate::crosswords::Crosswords;
use crate::event::EventListener;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ViMotion {
Up,
Down,
Left,
Right,
First,
Last,
FirstOccupied,
High,
Middle,
Low,
SemanticLeft,
SemanticRight,
#[allow(unused)]
SemanticLeftEnd,
SemanticRightEnd,
WordLeft,
WordRight,
#[allow(unused)]
WordLeftEnd,
WordRightEnd,
Bracket,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub struct ViModeCursor {
pub pos: Pos,
}
impl ViModeCursor {
pub fn new(pos: Pos) -> Self {
Self { pos }
}
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn motion<T: EventListener>(
mut self,
term: &mut Crosswords<T>,
motion: ViMotion,
) -> Self {
match motion {
ViMotion::Up => {
if self.pos.row > term.grid.topmost_line() {
self.pos.row -= 1;
}
}
ViMotion::Down => {
if self.pos.row + 1 < term.grid.screen_lines() as i32 {
self.pos.row += 1;
}
}
ViMotion::Left => {
self.pos = term.expand_wide(self.pos, Direction::Left);
let wrap_pos = Pos::new(self.pos.row - 1, term.grid.last_column());
if self.pos.col == 0
&& self.pos.row > term.grid.topmost_line()
&& is_wrap(term, wrap_pos)
{
self.pos = wrap_pos;
} else {
self.pos.col = Column(self.pos.col.saturating_sub(1));
}
}
ViMotion::Right => {
self.pos = term.expand_wide(self.pos, Direction::Right);
if is_wrap(term, self.pos) {
self.pos = Pos::new(self.pos.row + 1, Column(0));
} else {
self.pos.col = min(self.pos.col + 1, term.grid.last_column());
}
}
ViMotion::First => {
self.pos = term.expand_wide(self.pos, Direction::Left);
while self.pos.col == 0
&& self.pos.row > term.grid.topmost_line()
&& is_wrap(term, Pos::new(self.pos.row - 1, term.grid.last_column()))
{
self.pos.row -= 1;
}
self.pos.col = Column(0);
}
ViMotion::Last => self.pos = last(term, self.pos),
ViMotion::FirstOccupied => self.pos = first_occupied(term, self.pos),
ViMotion::High => {
let line = Line(-(term.display_offset() as i32));
let col = first_occupied_in_line(term, line).unwrap_or_default().col;
self.pos = Pos::new(line, col);
}
ViMotion::Middle => {
let display_offset = term.display_offset() as i32;
let line =
Line(-display_offset + term.grid.screen_lines() as i32 / 2 - 1);
let col = first_occupied_in_line(term, line).unwrap_or_default().col;
self.pos = Pos::new(line, col);
}
ViMotion::Low => {
let display_offset = term.display_offset() as i32;
let line = Line(-display_offset + term.grid.screen_lines() as i32 - 1);
let col = first_occupied_in_line(term, line).unwrap_or_default().col;
self.pos = Pos::new(line, col);
}
ViMotion::SemanticLeft => {
self.pos = semantic(term, self.pos, Direction::Left, Side::Left);
}
ViMotion::SemanticRight => {
self.pos = semantic(term, self.pos, Direction::Right, Side::Left);
}
ViMotion::SemanticLeftEnd => {
self.pos = semantic(term, self.pos, Direction::Left, Side::Right);
}
ViMotion::SemanticRightEnd => {
self.pos = semantic(term, self.pos, Direction::Right, Side::Right);
}
ViMotion::WordLeft => {
self.pos = word(term, self.pos, Direction::Left, Side::Left);
}
ViMotion::WordRight => {
self.pos = word(term, self.pos, Direction::Right, Side::Left);
}
ViMotion::WordLeftEnd => {
self.pos = word(term, self.pos, Direction::Left, Side::Right);
}
ViMotion::WordRightEnd => {
self.pos = word(term, self.pos, Direction::Right, Side::Right);
}
ViMotion::Bracket => {
self.pos = term.bracket_search(self.pos).unwrap_or(self.pos)
}
}
term.scroll_to_pos(self.pos);
self
}
#[allow(unused)]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn scroll<T: EventListener>(mut self, term: &Crosswords<T>, lines: i32) -> Self {
let line = (self.pos.row - lines).grid_clamp(&term.grid, Boundary::Grid);
let column = first_occupied_in_line(term, line).unwrap_or_default().col;
self.pos = Pos::new(line, column);
self
}
}
fn last<T: EventListener>(term: &Crosswords<T>, mut pos: Pos) -> Pos {
pos = term.expand_wide(pos, Direction::Right);
let occupied = last_occupied_in_line(term, pos.row).unwrap_or_default();
if pos.col < occupied.col {
occupied
} else if is_wrap(term, pos) {
while is_wrap(term, pos) {
pos.row += 1;
}
last_occupied_in_line(term, pos.row).unwrap_or(pos)
} else {
Pos::new(pos.row, term.grid.last_column())
}
}
fn first_occupied<T: EventListener>(term: &Crosswords<T>, mut pos: Pos) -> Pos {
let last_column = term.grid.last_column();
pos = term.expand_wide(pos, Direction::Left);
let occupied = first_occupied_in_line(term, pos.row)
.unwrap_or_else(|| Pos::new(pos.row, last_column));
if pos == occupied {
let mut occupied = None;
for line in (term.grid.topmost_line().0..pos.row.0)
.rev()
.map(Line::from)
{
if !is_wrap(term, Pos::new(line, last_column)) {
break;
}
occupied = first_occupied_in_line(term, line).or(occupied);
}
let mut line = pos.row;
occupied.unwrap_or_else(|| loop {
if let Some(occupied) = first_occupied_in_line(term, line) {
break occupied;
}
let last_cell = Pos::new(line, last_column);
if !is_wrap(term, last_cell) {
break last_cell;
}
line += 1;
})
} else {
occupied
}
}
fn semantic<T: EventListener>(
term: &mut Crosswords<T>,
mut pos: Pos,
direction: Direction,
side: Side,
) -> Pos {
let expand_semantic = |pos: Pos| {
let cell = &term.grid[pos];
if term.semantic_escape_chars().contains(cell.c())
&& !matches!(cell.wide(), Wide::Spacer | Wide::LeadingSpacer)
{
pos
} else if direction == Direction::Left {
term.semantic_search_left(pos)
} else {
term.semantic_search_right(pos)
}
};
pos = term.expand_wide(pos, direction);
if direction != side && !is_boundary(term, pos, direction) {
pos = expand_semantic(pos);
}
let mut next_pos = advance(term, pos, direction);
while !is_boundary(term, pos, direction) && is_space(term, next_pos) {
pos = next_pos;
next_pos = advance(term, pos, direction);
}
if !is_boundary(term, pos, direction) {
pos = advance(term, pos, direction);
}
if direction == side && !is_boundary(term, pos, direction) {
pos = expand_semantic(pos);
}
pos
}
fn word<T: EventListener>(
term: &mut Crosswords<T>,
mut pos: Pos,
direction: Direction,
side: Side,
) -> Pos {
pos = term.expand_wide(pos, direction);
if direction == side {
let mut next_pos = advance(term, pos, direction);
while !is_boundary(term, pos, direction) && is_space(term, next_pos) {
pos = next_pos;
next_pos = advance(term, pos, direction);
}
let mut next_pos = advance(term, pos, direction);
while !is_boundary(term, pos, direction) && !is_space(term, next_pos) {
pos = next_pos;
next_pos = advance(term, pos, direction);
}
}
if direction != side {
while !is_boundary(term, pos, direction) && !is_space(term, pos) {
pos = advance(term, pos, direction);
}
while !is_boundary(term, pos, direction) && is_space(term, pos) {
pos = advance(term, pos, direction);
}
}
pos
}
fn first_occupied_in_line<T: EventListener>(
term: &Crosswords<T>,
line: Line,
) -> Option<Pos> {
(0..term.grid.columns())
.map(|col| Pos::new(line, Column(col)))
.find(|&pos| !is_space(term, pos))
}
fn last_occupied_in_line<T: EventListener>(
term: &Crosswords<T>,
line: Line,
) -> Option<Pos> {
(0..term.grid.columns())
.map(|col| Pos::new(line, Column(col)))
.rfind(|&pos| !is_space(term, pos))
}
fn advance<T: EventListener>(
term: &Crosswords<T>,
pos: Pos,
direction: Direction,
) -> Pos {
if direction == Direction::Left {
pos.sub(&term.grid, Boundary::Grid, 1)
} else {
pos.add(&term.grid, Boundary::Grid, 1)
}
}
fn is_space<T: EventListener>(term: &Crosswords<T>, pos: Pos) -> bool {
let cell = &term.grid[pos.row][pos.col];
!matches!(cell.wide(), Wide::Spacer | Wide::LeadingSpacer)
&& (cell.c() == '\0' || cell.c() == ' ' || cell.c() == '\t')
}
fn is_wrap<T: EventListener>(term: &Crosswords<T>, pos: Pos) -> bool {
term.grid[pos].wrapline()
}
fn is_boundary<T: EventListener>(
term: &Crosswords<T>,
pos: Pos,
direction: Direction,
) -> bool {
(pos.row <= term.grid.topmost_line() && pos.col == 0 && direction == Direction::Left)
|| (pos.row == term.bottommost_line()
&& pos.col + 1 >= term.grid.columns()
&& direction == Direction::Right)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crosswords::pos::{Column, Line};
use crate::crosswords::CrosswordsSize;
use crate::crosswords::{Crosswords, CursorShape};
use crate::event::VoidListener;
use crate::performer::handler::Handler;
fn term() -> Crosswords<VoidListener> {
let size = CrosswordsSize::new(20, 20);
Crosswords::new(
size,
CursorShape::Underline,
VoidListener,
crate::event::WindowId::from(0),
0,
)
}
#[test]
fn motion_simple() {
let mut term = term();
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(0)));
cursor = cursor.motion(&mut term, ViMotion::Right);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(1)));
cursor = cursor.motion(&mut term, ViMotion::Left);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(0)));
cursor = cursor.motion(&mut term, ViMotion::Down);
assert_eq!(cursor.pos, Pos::new(Line(1), Column(0)));
cursor = cursor.motion(&mut term, ViMotion::Up);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(0)));
}
#[test]
fn simple_wide() {
let mut term = term();
term.grid[Line(0)][Column(0)].set_c('a');
term.grid[Line(0)][Column(1)].set_c('汉');
term.grid[Line(0)][Column(1)].set_wide(Wide::Wide);
term.grid[Line(0)][Column(2)].set_c(' ');
term.grid[Line(0)][Column(2)].set_wide(Wide::Spacer);
term.grid[Line(0)][Column(3)].set_c('a');
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(1)));
cursor = cursor.motion(&mut term, ViMotion::Right);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(3)));
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(2)));
cursor = cursor.motion(&mut term, ViMotion::Left);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(0)));
}
#[test]
fn motion_start_end() {
let mut term = term();
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(0)));
cursor = cursor.motion(&mut term, ViMotion::Last);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(19)));
cursor = cursor.motion(&mut term, ViMotion::First);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(0)));
}
#[test]
fn motion_first_occupied() {
let mut term = term();
term.grid[Line(0)][Column(0)].set_c(' ');
term.grid[Line(0)][Column(1)].set_c('x');
term.grid[Line(0)][Column(2)].set_c(' ');
term.grid[Line(0)][Column(3)].set_c('y');
term.grid[Line(0)][Column(19)].set_wrapline(true);
term.grid[Line(1)][Column(19)].set_wrapline(true);
term.grid[Line(2)][Column(0)].set_c('z');
term.grid[Line(2)][Column(1)].set_c(' ');
let mut cursor = ViModeCursor::new(Pos::new(Line(2), Column(1)));
cursor = cursor.motion(&mut term, ViMotion::FirstOccupied);
assert_eq!(cursor.pos, Pos::new(Line(2), Column(0)));
cursor = cursor.motion(&mut term, ViMotion::FirstOccupied);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(1)));
}
#[test]
fn motion_high_middle_low() {
let mut term = term();
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(0)));
cursor = cursor.motion(&mut term, ViMotion::High);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(0)));
cursor = cursor.motion(&mut term, ViMotion::Middle);
assert_eq!(cursor.pos, Pos::new(Line(9), Column(0)));
cursor = cursor.motion(&mut term, ViMotion::Low);
assert_eq!(cursor.pos, Pos::new(Line(19), Column(0)));
}
#[test]
fn motion_bracket() {
let mut term = term();
term.grid[Line(0)][Column(0)].set_c('(');
term.grid[Line(0)][Column(1)].set_c('x');
term.grid[Line(0)][Column(2)].set_c(')');
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(0)));
cursor = cursor.motion(&mut term, ViMotion::Bracket);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(2)));
cursor = cursor.motion(&mut term, ViMotion::Bracket);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(0)));
}
fn motion_semantic_term() -> Crosswords<VoidListener> {
let mut term = term();
term.grid[Line(0)][Column(0)].set_c('x');
term.grid[Line(0)][Column(1)].set_c(' ');
term.grid[Line(0)][Column(2)].set_c('x');
term.grid[Line(0)][Column(3)].set_c('x');
term.grid[Line(0)][Column(4)].set_c(' ');
term.grid[Line(0)][Column(5)].set_c(' ');
term.grid[Line(0)][Column(6)].set_c(':');
term.grid[Line(0)][Column(7)].set_c(' ');
term.grid[Line(0)][Column(8)].set_c('x');
term.grid[Line(0)][Column(9)].set_c(':');
term.grid[Line(0)][Column(10)].set_c('x');
term.grid[Line(0)][Column(11)].set_c(' ');
term.grid[Line(0)][Column(12)].set_c(' ');
term.grid[Line(0)][Column(13)].set_c(':');
term.grid[Line(0)][Column(14)].set_c(' ');
term.grid[Line(0)][Column(15)].set_c('x');
term
}
#[test]
fn motion_semantic_right_end() {
let mut term = motion_semantic_term();
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(0)));
cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(3)));
cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(6)));
cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(8)));
cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(9)));
cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(10)));
cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(13)));
cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(15)));
}
#[test]
fn motion_semantic_left_start() {
let mut term = motion_semantic_term();
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(15)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeft);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(13)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeft);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(10)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeft);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(9)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeft);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(8)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeft);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(6)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeft);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(2)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeft);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(0)));
}
#[test]
fn motion_semantic_right_start() {
let mut term = motion_semantic_term();
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(0)));
cursor = cursor.motion(&mut term, ViMotion::SemanticRight);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(2)));
cursor = cursor.motion(&mut term, ViMotion::SemanticRight);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(6)));
cursor = cursor.motion(&mut term, ViMotion::SemanticRight);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(8)));
cursor = cursor.motion(&mut term, ViMotion::SemanticRight);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(9)));
cursor = cursor.motion(&mut term, ViMotion::SemanticRight);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(10)));
cursor = cursor.motion(&mut term, ViMotion::SemanticRight);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(13)));
cursor = cursor.motion(&mut term, ViMotion::SemanticRight);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(15)));
}
#[test]
fn motion_semantic_left_end() {
let mut term = motion_semantic_term();
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(15)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(13)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(10)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(9)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(8)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(6)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(3)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(0)));
}
#[test]
fn scroll_semantic() {
let mut term = term();
term.grid.scroll_up(&(Line(0)..Line(20)), 5);
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(0)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeft);
assert_eq!(cursor.pos, Pos::new(Line(-5), Column(0)));
assert_eq!(term.display_offset(), 5);
cursor = cursor.motion(&mut term, ViMotion::SemanticRight);
assert_eq!(cursor.pos, Pos::new(Line(19), Column(19)));
assert_eq!(term.display_offset(), 0);
cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd);
assert_eq!(cursor.pos, Pos::new(Line(-5), Column(0)));
assert_eq!(term.display_offset(), 5);
cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd);
assert_eq!(cursor.pos, Pos::new(Line(19), Column(19)));
assert_eq!(term.display_offset(), 0);
}
#[test]
fn semantic_wide() {
let mut term = term();
term.grid[Line(0)][Column(0)].set_c('a');
term.grid[Line(0)][Column(1)].set_c(' ');
term.grid[Line(0)][Column(2)].set_c('汉');
term.grid[Line(0)][Column(2)].set_wide(Wide::Wide);
term.grid[Line(0)][Column(3)].set_c(' ');
term.grid[Line(0)][Column(3)].set_wide(Wide::Spacer);
term.grid[Line(0)][Column(4)].set_c(' ');
term.grid[Line(0)][Column(5)].set_c('a');
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(2)));
cursor = cursor.motion(&mut term, ViMotion::SemanticRight);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(5)));
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(3)));
cursor = cursor.motion(&mut term, ViMotion::SemanticLeft);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(0)));
}
#[test]
fn motion_word() {
let mut term = term();
term.grid[Line(0)][Column(0)].set_c('a');
term.grid[Line(0)][Column(1)].set_c(';');
term.grid[Line(0)][Column(2)].set_c(' ');
term.grid[Line(0)][Column(3)].set_c(' ');
term.grid[Line(0)][Column(4)].set_c('a');
term.grid[Line(0)][Column(5)].set_c(';');
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(0)));
cursor = cursor.motion(&mut term, ViMotion::WordRightEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(1)));
cursor = cursor.motion(&mut term, ViMotion::WordRightEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(5)));
cursor = cursor.motion(&mut term, ViMotion::WordLeft);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(4)));
cursor = cursor.motion(&mut term, ViMotion::WordLeft);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(0)));
cursor = cursor.motion(&mut term, ViMotion::WordRight);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(4)));
cursor = cursor.motion(&mut term, ViMotion::WordLeftEnd);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(1)));
}
#[test]
fn scroll_word() {
let mut term = term();
term.grid.scroll_up(&(Line(0)..Line(20)), 5);
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(0)));
cursor = cursor.motion(&mut term, ViMotion::WordLeft);
assert_eq!(cursor.pos, Pos::new(Line(-5), Column(0)));
assert_eq!(term.display_offset(), 5);
cursor = cursor.motion(&mut term, ViMotion::WordRight);
assert_eq!(cursor.pos, Pos::new(Line(19), Column(19)));
assert_eq!(term.display_offset(), 0);
cursor = cursor.motion(&mut term, ViMotion::WordLeftEnd);
assert_eq!(cursor.pos, Pos::new(Line(-5), Column(0)));
assert_eq!(term.display_offset(), 5);
cursor = cursor.motion(&mut term, ViMotion::WordRightEnd);
assert_eq!(cursor.pos, Pos::new(Line(19), Column(19)));
assert_eq!(term.display_offset(), 0);
}
#[test]
fn word_wide() {
let mut term = term();
term.grid[Line(0)][Column(0)].set_c('a');
term.grid[Line(0)][Column(1)].set_c(' ');
term.grid[Line(0)][Column(2)].set_c('汉');
term.grid[Line(0)][Column(2)].set_wide(Wide::Wide);
term.grid[Line(0)][Column(3)].set_c(' ');
term.grid[Line(0)][Column(3)].set_wide(Wide::Spacer);
term.grid[Line(0)][Column(4)].set_c(' ');
term.grid[Line(0)][Column(5)].set_c('a');
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(2)));
cursor = cursor.motion(&mut term, ViMotion::WordRight);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(5)));
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(3)));
cursor = cursor.motion(&mut term, ViMotion::WordLeft);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(0)));
}
#[test]
fn scroll_simple() {
let mut term = term();
for _ in 0..20 {
term.newline();
}
let mut cursor = ViModeCursor::new(Pos::new(Line(0), Column(0)));
cursor = cursor.scroll(&term, -1);
assert_eq!(cursor.pos, Pos::new(Line(1), Column(0)));
cursor = cursor.scroll(&term, 1);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(0)));
cursor = cursor.scroll(&term, 1);
assert_eq!(cursor.pos, Pos::new(Line(-1), Column(0)));
}
#[test]
fn scroll_over_top() {
let mut term = term();
for _ in 0..59 {
term.newline();
}
let mut cursor = ViModeCursor::new(Pos::new(Line(19), Column(0)));
cursor = cursor.scroll(&term, 20);
assert_eq!(cursor.pos, Pos::new(Line(-1), Column(0)));
cursor = cursor.scroll(&term, 20);
assert_eq!(cursor.pos, Pos::new(Line(-21), Column(0)));
cursor = cursor.scroll(&term, 20);
assert_eq!(cursor.pos, Pos::new(Line(-40), Column(0)));
cursor = cursor.scroll(&term, 20);
assert_eq!(cursor.pos, Pos::new(Line(-40), Column(0)));
}
#[test]
fn scroll_over_bottom() {
let mut term = term();
for _ in 0..59 {
term.newline();
}
let mut cursor = ViModeCursor::new(Pos::new(Line(-40), Column(0)));
cursor = cursor.scroll(&term, -20);
assert_eq!(cursor.pos, Pos::new(Line(-20), Column(0)));
cursor = cursor.scroll(&term, -20);
assert_eq!(cursor.pos, Pos::new(Line(0), Column(0)));
cursor = cursor.scroll(&term, -20);
assert_eq!(cursor.pos, Pos::new(Line(19), Column(0)));
cursor = cursor.scroll(&term, -20);
assert_eq!(cursor.pos, Pos::new(Line(19), Column(0)));
}
}