478 lines
12 KiB
Rust
478 lines
12 KiB
Rust
//! 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<L = Line, C = Column> {
|
|
pub line: L,
|
|
pub column: C,
|
|
}
|
|
|
|
impl<L, C> Point<L, C> {
|
|
pub fn new(line: L, column: C) -> Point<L, C> {
|
|
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<D>(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<D>(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<D>(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<L: Ord, C: Ord> PartialOrd for Point<L, C> {
|
|
fn partial_cmp(&self, other: &Point<L, C>) -> Option<Ordering> {
|
|
Some(self.cmp(other))
|
|
}
|
|
}
|
|
|
|
impl<L: Ord, C: Ord> Ord for Point<L, C> {
|
|
fn cmp(&self, other: &Point<L, C>) -> 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<D: Dimensions>(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<usize> for Line {
|
|
fn from(source: usize) -> Self {
|
|
Self(source as i32)
|
|
}
|
|
}
|
|
|
|
impl Add<usize> for Line {
|
|
type Output = Line;
|
|
|
|
#[inline]
|
|
fn add(self, rhs: usize) -> Line {
|
|
self + rhs as i32
|
|
}
|
|
}
|
|
|
|
impl AddAssign<usize> for Line {
|
|
#[inline]
|
|
fn add_assign(&mut self, rhs: usize) {
|
|
*self += rhs as i32;
|
|
}
|
|
}
|
|
|
|
impl Sub<usize> for Line {
|
|
type Output = Line;
|
|
|
|
#[inline]
|
|
fn sub(self, rhs: usize) -> Line {
|
|
self - rhs as i32
|
|
}
|
|
}
|
|
|
|
impl SubAssign<usize> for Line {
|
|
#[inline]
|
|
fn sub_assign(&mut self, rhs: usize) {
|
|
*self -= rhs as i32;
|
|
}
|
|
}
|
|
|
|
impl PartialOrd<usize> for Line {
|
|
#[inline]
|
|
fn partial_cmp(&self, other: &usize) -> Option<Ordering> {
|
|
self.0.partial_cmp(&(*other as i32))
|
|
}
|
|
}
|
|
|
|
impl PartialEq<usize> 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<Ordering> {
|
|
self.partial_cmp(&other.0)
|
|
}
|
|
}
|
|
|
|
impl PartialOrd<$primitive> for $ty {
|
|
#[inline]
|
|
fn partial_cmp(&self, other: &$primitive) -> Option<Ordering> {
|
|
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)));
|
|
}
|
|
}
|