//! Line and Column newtypes for strongly typed tty/grid/terminal APIs. /// Indexing types and implementations for Grid and Line. use std::cmp::{max, min, Ord, Ordering}; use std::fmt; use std::ops::{Add, AddAssign, Deref, Sub, SubAssign}; use serde::{Deserialize, Serialize}; use alacritty_config_derive::SerdeReplace; use crate::grid::Dimensions; /// The side of a cell. pub type Side = Direction; /// Horizontal direction. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Direction { Left, Right, } impl Direction { #[must_use] pub fn opposite(self) -> Self { match self { Side::Right => Side::Left, Side::Left => Side::Right, } } } /// Terminal grid boundaries. pub enum Boundary { /// Cursor's range of motion in the grid. /// /// This is equal to the viewport when the user isn't scrolled into the history. Cursor, /// Topmost line in history until the bottommost line in the terminal. Grid, /// Unbounded. None, } /// Index in the grid using row, column notation. #[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, Eq, PartialEq)] pub struct Point { pub line: L, pub column: C, } impl Point { pub fn new(line: L, column: C) -> Point { Point { line, column } } } impl Point { /// Subtract a number of columns from a point. #[inline] #[must_use = "this returns the result of the operation, without modifying the original"] pub fn sub(mut self, dimensions: &D, boundary: Boundary, rhs: usize) -> Self where D: Dimensions, { let cols = dimensions.columns(); let line_changes = (rhs + cols - 1).saturating_sub(self.column.0) / cols; self.line -= line_changes; self.column = Column((cols + self.column.0 - rhs % cols) % cols); self.grid_clamp(dimensions, boundary) } /// Add a number of columns to a point. #[inline] #[must_use = "this returns the result of the operation, without modifying the original"] pub fn add(mut self, dimensions: &D, boundary: Boundary, rhs: usize) -> Self where D: Dimensions, { let cols = dimensions.columns(); self.line += (rhs + self.column.0) / cols; self.column = Column((self.column.0 + rhs) % cols); self.grid_clamp(dimensions, boundary) } /// Clamp a point to a grid boundary. #[inline] #[must_use = "this returns the result of the operation, without modifying the original"] pub fn grid_clamp(mut self, dimensions: &D, boundary: Boundary) -> Self where D: Dimensions, { let last_column = dimensions.last_column(); self.column = min(self.column, last_column); let topmost_line = dimensions.topmost_line(); let bottommost_line = dimensions.bottommost_line(); match boundary { Boundary::Cursor if self.line < 0 => Point::new(Line(0), Column(0)), Boundary::Grid if self.line < topmost_line => Point::new(topmost_line, Column(0)), Boundary::Cursor | Boundary::Grid if self.line > bottommost_line => { Point::new(bottommost_line, last_column) }, Boundary::None => { self.line = self.line.grid_clamp(dimensions, boundary); self }, _ => self, } } } impl PartialOrd for Point { fn partial_cmp(&self, other: &Point) -> Option { Some(self.cmp(other)) } } impl Ord for Point { fn cmp(&self, other: &Point) -> Ordering { match (self.line.cmp(&other.line), self.column.cmp(&other.column)) { (Ordering::Equal, ord) | (ord, _) => ord, } } } /// A line. /// /// Newtype to avoid passing values incorrectly. #[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd)] pub struct Line(pub i32); impl Line { /// Clamp a line to a grid boundary. #[must_use] pub fn grid_clamp(self, dimensions: &D, boundary: Boundary) -> Self { match boundary { Boundary::Cursor => max(Line(0), min(dimensions.bottommost_line(), self)), Boundary::Grid => { let bottommost_line = dimensions.bottommost_line(); let topmost_line = dimensions.topmost_line(); max(topmost_line, min(bottommost_line, self)) }, Boundary::None => { let screen_lines = dimensions.screen_lines() as i32; let total_lines = dimensions.total_lines() as i32; if self >= screen_lines { let topmost_line = dimensions.topmost_line(); let extra = (self.0 - screen_lines) % total_lines; topmost_line + extra } else { let bottommost_line = dimensions.bottommost_line(); let extra = (self.0 - screen_lines + 1) % total_lines; bottommost_line + extra } }, } } } impl fmt::Display for Line { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl From for Line { fn from(source: usize) -> Self { Self(source as i32) } } impl Add for Line { type Output = Line; #[inline] fn add(self, rhs: usize) -> Line { self + rhs as i32 } } impl AddAssign for Line { #[inline] fn add_assign(&mut self, rhs: usize) { *self += rhs as i32; } } impl Sub for Line { type Output = Line; #[inline] fn sub(self, rhs: usize) -> Line { self - rhs as i32 } } impl SubAssign for Line { #[inline] fn sub_assign(&mut self, rhs: usize) { *self -= rhs as i32; } } impl PartialOrd for Line { #[inline] fn partial_cmp(&self, other: &usize) -> Option { self.0.partial_cmp(&(*other as i32)) } } impl PartialEq for Line { #[inline] fn eq(&self, other: &usize) -> bool { self.0.eq(&(*other as i32)) } } /// A column. /// /// Newtype to avoid passing values incorrectly. #[derive( SerdeReplace, Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd, )] pub struct Column(pub usize); impl fmt::Display for Column { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } macro_rules! ops { ($ty:ty, $construct:expr, $primitive:ty) => { impl Deref for $ty { type Target = $primitive; #[inline] fn deref(&self) -> &$primitive { &self.0 } } impl From<$primitive> for $ty { #[inline] fn from(val: $primitive) -> $ty { $construct(val) } } impl Add<$ty> for $ty { type Output = $ty; #[inline] fn add(self, rhs: $ty) -> $ty { $construct(self.0 + rhs.0) } } impl AddAssign<$ty> for $ty { #[inline] fn add_assign(&mut self, rhs: $ty) { self.0 += rhs.0; } } impl Add<$primitive> for $ty { type Output = $ty; #[inline] fn add(self, rhs: $primitive) -> $ty { $construct(self.0 + rhs) } } impl AddAssign<$primitive> for $ty { #[inline] fn add_assign(&mut self, rhs: $primitive) { self.0 += rhs } } impl Sub<$ty> for $ty { type Output = $ty; #[inline] fn sub(self, rhs: $ty) -> $ty { $construct(self.0 - rhs.0) } } impl SubAssign<$ty> for $ty { #[inline] fn sub_assign(&mut self, rhs: $ty) { self.0 -= rhs.0; } } impl Sub<$primitive> for $ty { type Output = $ty; #[inline] fn sub(self, rhs: $primitive) -> $ty { $construct(self.0 - rhs) } } impl SubAssign<$primitive> for $ty { #[inline] fn sub_assign(&mut self, rhs: $primitive) { self.0 -= rhs } } impl PartialEq<$ty> for $primitive { #[inline] fn eq(&self, other: &$ty) -> bool { self.eq(&other.0) } } impl PartialEq<$primitive> for $ty { #[inline] fn eq(&self, other: &$primitive) -> bool { self.0.eq(other) } } impl PartialOrd<$ty> for $primitive { #[inline] fn partial_cmp(&self, other: &$ty) -> Option { self.partial_cmp(&other.0) } } impl PartialOrd<$primitive> for $ty { #[inline] fn partial_cmp(&self, other: &$primitive) -> Option { self.0.partial_cmp(other) } } }; } ops!(Column, Column, usize); ops!(Line, Line, i32); #[cfg(test)] mod tests { use super::*; #[test] fn location_ordering() { assert!(Point::new(Line(0), Column(0)) == Point::new(Line(0), Column(0))); assert!(Point::new(Line(1), Column(0)) > Point::new(Line(0), Column(0))); assert!(Point::new(Line(0), Column(1)) > Point::new(Line(0), Column(0))); assert!(Point::new(Line(1), Column(1)) > Point::new(Line(0), Column(0))); assert!(Point::new(Line(1), Column(1)) > Point::new(Line(0), Column(1))); assert!(Point::new(Line(1), Column(1)) > Point::new(Line(1), Column(0))); assert!(Point::new(Line(0), Column(0)) > Point::new(Line(-1), Column(0))); } #[test] fn sub() { let size = (10, 42); let point = Point::new(Line(0), Column(13)); let result = point.sub(&size, Boundary::Cursor, 1); assert_eq!(result, Point::new(Line(0), point.column - 1)); } #[test] fn sub_wrap() { let size = (10, 42); let point = Point::new(Line(1), Column(0)); let result = point.sub(&size, Boundary::Cursor, 1); assert_eq!(result, Point::new(Line(0), size.last_column())); } #[test] fn sub_clamp() { let size = (10, 42); let point = Point::new(Line(0), Column(0)); let result = point.sub(&size, Boundary::Cursor, 1); assert_eq!(result, point); } #[test] fn sub_grid_clamp() { let size = (0, 42); let point = Point::new(Line(0), Column(0)); let result = point.sub(&size, Boundary::Grid, 1); assert_eq!(result, point); } #[test] fn sub_none_clamp() { let size = (10, 42); let point = Point::new(Line(0), Column(0)); let result = point.sub(&size, Boundary::None, 1); assert_eq!(result, Point::new(Line(9), Column(41))); } #[test] fn add() { let size = (10, 42); let point = Point::new(Line(0), Column(13)); let result = point.add(&size, Boundary::Cursor, 1); assert_eq!(result, Point::new(Line(0), point.column + 1)); } #[test] fn add_wrap() { let size = (10, 42); let point = Point::new(Line(0), size.last_column()); let result = point.add(&size, Boundary::Cursor, 1); assert_eq!(result, Point::new(Line(1), Column(0))); } #[test] fn add_clamp() { let size = (10, 42); let point = Point::new(Line(9), Column(41)); let result = point.add(&size, Boundary::Cursor, 1); assert_eq!(result, point); } #[test] fn add_grid_clamp() { let size = (10, 42); let point = Point::new(Line(9), Column(41)); let result = point.add(&size, Boundary::Grid, 1); assert_eq!(result, point); } #[test] fn add_none_clamp() { let size = (10, 42); let point = Point::new(Line(9), Column(41)); let result = point.add(&size, Boundary::None, 1); assert_eq!(result, Point::new(Line(0), Column(0))); } }