summaryrefslogtreecommitdiff
path: root/alacritty_terminal
diff options
context:
space:
mode:
authorChristian Duerr <contact@christianduerr.com>2021-01-24 21:45:36 +0000
committerGitHub <noreply@github.com>2021-01-24 21:45:36 +0000
commit530de00049c2afcc562d36ccdb3e6afa2fe396a5 (patch)
tree3dabbcef3fc4a2041f9027d82243aa0d70928153 /alacritty_terminal
parent7291702f6b4fff10f2470f084abe0785b95659a0 (diff)
downloadalacritty-530de00049c2afcc562d36ccdb3e6afa2fe396a5.tar.gz
alacritty-530de00049c2afcc562d36ccdb3e6afa2fe396a5.zip
Move renderable cell transformation to alacritty
This refactors a large chunk of the alacritty_terminal API to expose all data necessary for rendering uniformly through the `renderable_content` call. This also no longer transforms the cells for rendering by a GUI but instead just reports the content from a terminal emulation perspective. The transformation into renderable cells is now done inside the alacritty crate. Since the terminal itself only ever needs to know about modified color RGB values, the configuration for colors was moved to the alacritty UI code.
Diffstat (limited to 'alacritty_terminal')
-rw-r--r--alacritty_terminal/Cargo.toml2
-rw-r--r--alacritty_terminal/src/ansi.rs10
-rw-r--r--alacritty_terminal/src/config/bell.rs70
-rw-r--r--alacritty_terminal/src/config/colors.rs213
-rw-r--r--alacritty_terminal/src/config/mod.rs28
-rw-r--r--alacritty_terminal/src/event.rs38
-rw-r--r--alacritty_terminal/src/grid/mod.rs506
-rw-r--r--alacritty_terminal/src/grid/resize.rs26
-rw-r--r--alacritty_terminal/src/grid/tests.rs28
-rw-r--r--alacritty_terminal/src/index.rs51
-rw-r--r--alacritty_terminal/src/selection.rs125
-rw-r--r--alacritty_terminal/src/term/color.rs203
-rw-r--r--alacritty_terminal/src/term/mod.rs529
-rw-r--r--alacritty_terminal/src/term/render.rs431
-rw-r--r--alacritty_terminal/src/term/search.rs232
-rw-r--r--alacritty_terminal/src/vi_mode.rs42
-rw-r--r--alacritty_terminal/tests/ref.rs2
17 files changed, 681 insertions, 1855 deletions
diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml
index ee228a3f..41b8b1f8 100644
--- a/alacritty_terminal/Cargo.toml
+++ b/alacritty_terminal/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "alacritty_terminal"
-version = "0.12.1-dev"
+version = "0.13.0-dev"
authors = ["Christian Duerr <contact@christianduerr.com>", "Joe Wilm <joe@jwilm.com>"]
license = "Apache-2.0"
description = "Library for writing terminal emulators"
diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs
index 2bd445ea..8f2264af 100644
--- a/alacritty_terminal/src/ansi.rs
+++ b/alacritty_terminal/src/ansi.rs
@@ -306,7 +306,7 @@ pub trait Handler {
fn set_color(&mut self, _: usize, _: Rgb) {}
/// Write a foreground/background color escape sequence with the current color.
- fn dynamic_color_sequence<W: io::Write>(&mut self, _: &mut W, _: u8, _: usize, _: &str) {}
+ fn dynamic_color_sequence(&mut self, _: u8, _: usize, _: &str) {}
/// Reset an indexed color to original value.
fn reset_color(&mut self, _: usize) {}
@@ -778,10 +778,10 @@ where
}
#[inline]
- fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, _c: char) {
+ fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, action: char) {
debug!(
- "[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}",
- params, intermediates, ignore
+ "[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}, action: {:?}",
+ params, intermediates, ignore, action
);
}
@@ -798,7 +798,6 @@ where
// TODO replace OSC parsing with parser combinators.
#[inline]
fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
- let writer = &mut self.writer;
let terminator = if bell_terminated { "\x07" } else { "\x1b\\" };
fn unhandled(params: &[&[u8]]) {
@@ -868,7 +867,6 @@ where
self.handler.set_color(index, color);
} else if param == b"?" {
self.handler.dynamic_color_sequence(
- writer,
dynamic_code,
index,
terminator,
diff --git a/alacritty_terminal/src/config/bell.rs b/alacritty_terminal/src/config/bell.rs
deleted file mode 100644
index 825a7b1f..00000000
--- a/alacritty_terminal/src/config/bell.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-use std::time::Duration;
-
-use alacritty_config_derive::ConfigDeserialize;
-
-use crate::config::Program;
-use crate::term::color::Rgb;
-
-#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)]
-pub struct BellConfig {
- /// Visual bell animation function.
- pub animation: BellAnimation,
-
- /// Command to run on bell.
- pub command: Option<Program>,
-
- /// Visual bell flash color.
- pub color: Rgb,
-
- /// Visual bell duration in milliseconds.
- duration: u16,
-}
-
-impl Default for BellConfig {
- fn default() -> Self {
- Self {
- color: Rgb { r: 255, g: 255, b: 255 },
- animation: Default::default(),
- command: Default::default(),
- duration: Default::default(),
- }
- }
-}
-
-impl BellConfig {
- pub fn duration(&self) -> Duration {
- Duration::from_millis(self.duration as u64)
- }
-}
-
-/// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert
-/// Penner's Easing Functions.
-#[derive(ConfigDeserialize, Clone, Copy, Debug, PartialEq, Eq)]
-pub enum BellAnimation {
- // CSS animation.
- Ease,
- // CSS animation.
- EaseOut,
- // Penner animation.
- EaseOutSine,
- // Penner animation.
- EaseOutQuad,
- // Penner animation.
- EaseOutCubic,
- // Penner animation.
- EaseOutQuart,
- // Penner animation.
- EaseOutQuint,
- // Penner animation.
- EaseOutExpo,
- // Penner animation.
- EaseOutCirc,
- // Penner animation.
- Linear,
-}
-
-impl Default for BellAnimation {
- fn default() -> Self {
- BellAnimation::EaseOutExpo
- }
-}
diff --git a/alacritty_terminal/src/config/colors.rs b/alacritty_terminal/src/config/colors.rs
deleted file mode 100644
index 88ca5057..00000000
--- a/alacritty_terminal/src/config/colors.rs
+++ /dev/null
@@ -1,213 +0,0 @@
-use serde::de::Error as SerdeError;
-use serde::{Deserialize, Deserializer};
-
-use alacritty_config_derive::ConfigDeserialize;
-
-use crate::term::color::{CellRgb, Rgb};
-
-#[derive(ConfigDeserialize, Clone, Debug, Default, PartialEq, Eq)]
-pub struct Colors {
- pub primary: PrimaryColors,
- pub cursor: InvertedCellColors,
- pub vi_mode_cursor: InvertedCellColors,
- pub selection: InvertedCellColors,
- pub normal: NormalColors,
- pub bright: BrightColors,
- pub dim: Option<DimColors>,
- pub indexed_colors: Vec<IndexedColor>,
- pub search: SearchColors,
- pub line_indicator: LineIndicatorColors,
-}
-
-impl Colors {
- pub fn search_bar_foreground(&self) -> Rgb {
- self.search.bar.foreground.unwrap_or(self.primary.background)
- }
-
- pub fn search_bar_background(&self) -> Rgb {
- self.search.bar.background.unwrap_or(self.primary.foreground)
- }
-}
-
-#[derive(ConfigDeserialize, Copy, Clone, Default, Debug, PartialEq, Eq)]
-pub struct LineIndicatorColors {
- pub foreground: Option<Rgb>,
- pub background: Option<Rgb>,
-}
-
-#[derive(Deserialize, Copy, Clone, Default, Debug, PartialEq, Eq)]
-pub struct IndexedColor {
- pub color: Rgb,
-
- index: ColorIndex,
-}
-
-impl IndexedColor {
- #[inline]
- pub fn index(&self) -> u8 {
- self.index.0
- }
-}
-
-#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
-struct ColorIndex(u8);
-
-impl<'de> Deserialize<'de> for ColorIndex {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- let index = u8::deserialize(deserializer)?;
-
- if index < 16 {
- Err(SerdeError::custom(
- "Config error: indexed_color's index is {}, but a value bigger than 15 was \
- expected; ignoring setting",
- ))
- } else {
- Ok(Self(index))
- }
- }
-}
-
-#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)]
-pub struct InvertedCellColors {
- #[config(alias = "text")]
- pub foreground: CellRgb,
- #[config(alias = "cursor")]
- pub background: CellRgb,
-}
-
-impl Default for InvertedCellColors {
- fn default() -> Self {
- Self { foreground: CellRgb::CellBackground, background: CellRgb::CellForeground }
- }
-}
-
-#[derive(ConfigDeserialize, Debug, Copy, Clone, Default, PartialEq, Eq)]
-pub struct SearchColors {
- pub focused_match: InvertedCellColors,
- pub matches: MatchColors,
- bar: BarColors,
-}
-
-#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)]
-pub struct MatchColors {
- pub foreground: CellRgb,
- pub background: CellRgb,
-}
-
-impl Default for MatchColors {
- fn default() -> Self {
- Self {
- background: CellRgb::Rgb(Rgb { r: 0xff, g: 0xff, b: 0xff }),
- foreground: CellRgb::Rgb(Rgb { r: 0x00, g: 0x00, b: 0x00 }),
- }
- }
-}
-
-#[derive(ConfigDeserialize, Debug, Copy, Clone, Default, PartialEq, Eq)]
-pub struct BarColors {
- foreground: Option<Rgb>,
- background: Option<Rgb>,
-}
-
-#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)]
-pub struct PrimaryColors {
- pub foreground: Rgb,
- pub background: Rgb,
- pub bright_foreground: Option<Rgb>,
- pub dim_foreground: Option<Rgb>,
-}
-
-impl Default for PrimaryColors {
- fn default() -> Self {
- PrimaryColors {
- background: Rgb { r: 0x1d, g: 0x1f, b: 0x21 },
- foreground: Rgb { r: 0xc5, g: 0xc8, b: 0xc6 },
- bright_foreground: Default::default(),
- dim_foreground: Default::default(),
- }
- }
-}
-
-#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)]
-pub struct NormalColors {
- pub black: Rgb,
- pub red: Rgb,
- pub green: Rgb,
- pub yellow: Rgb,
- pub blue: Rgb,
- pub magenta: Rgb,
- pub cyan: Rgb,
- pub white: Rgb,
-}
-
-impl Default for NormalColors {
- fn default() -> Self {
- NormalColors {
- black: Rgb { r: 0x1d, g: 0x1f, b: 0x21 },
- red: Rgb { r: 0xcc, g: 0x66, b: 0x66 },
- green: Rgb { r: 0xb5, g: 0xbd, b: 0x68 },
- yellow: Rgb { r: 0xf0, g: 0xc6, b: 0x74 },
- blue: Rgb { r: 0x81, g: 0xa2, b: 0xbe },
- magenta: Rgb { r: 0xb2, g: 0x94, b: 0xbb },
- cyan: Rgb { r: 0x8a, g: 0xbe, b: 0xb7 },
- white: Rgb { r: 0xc5, g: 0xc8, b: 0xc6 },
- }
- }
-}
-
-#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)]
-pub struct BrightColors {
- pub black: Rgb,
- pub red: Rgb,
- pub green: Rgb,
- pub yellow: Rgb,
- pub blue: Rgb,
- pub magenta: Rgb,
- pub cyan: Rgb,
- pub white: Rgb,
-}
-
-impl Default for BrightColors {
- fn default() -> Self {
- BrightColors {
- black: Rgb { r: 0x66, g: 0x66, b: 0x66 },
- red: Rgb { r: 0xd5, g: 0x4e, b: 0x53 },
- green: Rgb { r: 0xb9, g: 0xca, b: 0x4a },
- yellow: Rgb { r: 0xe7, g: 0xc5, b: 0x47 },
- blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda },
- magenta: Rgb { r: 0xc3, g: 0x97, b: 0xd8 },
- cyan: Rgb { r: 0x70, g: 0xc0, b: 0xb1 },
- white: Rgb { r: 0xea, g: 0xea, b: 0xea },
- }
- }
-}
-
-#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)]
-pub struct DimColors {
- pub black: Rgb,
- pub red: Rgb,
- pub green: Rgb,
- pub yellow: Rgb,
- pub blue: Rgb,
- pub magenta: Rgb,
- pub cyan: Rgb,
- pub white: Rgb,
-}
-
-impl Default for DimColors {
- fn default() -> Self {
- DimColors {
- black: Rgb { r: 0x13, g: 0x14, b: 0x15 },
- red: Rgb { r: 0x86, g: 0x43, b: 0x43 },
- green: Rgb { r: 0x77, g: 0x7c, b: 0x44 },
- yellow: Rgb { r: 0x9e, g: 0x82, b: 0x4c },
- blue: Rgb { r: 0x55, g: 0x6a, b: 0x7d },
- magenta: Rgb { r: 0x75, g: 0x61, b: 0x7b },
- cyan: Rgb { r: 0x5b, g: 0x7d, b: 0x78 },
- white: Rgb { r: 0x82, g: 0x84, b: 0x82 },
- }
- }
-}
diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs
index 9b6f695f..59449faa 100644
--- a/alacritty_terminal/src/config/mod.rs
+++ b/alacritty_terminal/src/config/mod.rs
@@ -6,14 +6,10 @@ use serde::Deserialize;
use alacritty_config_derive::ConfigDeserialize;
-mod bell;
-mod colors;
mod scrolling;
use crate::ansi::{CursorShape, CursorStyle};
-pub use crate::config::bell::{BellAnimation, BellConfig};
-pub use crate::config::colors::Colors;
pub use crate::config::scrolling::Scrolling;
pub const LOG_TARGET_CONFIG: &str = "alacritty_config_derive";
@@ -27,11 +23,6 @@ pub struct Config<T> {
/// TERM env variable.
pub env: HashMap<String, String>,
- /// Should draw bold text with brighter colors instead of bold font.
- pub draw_bold_text_with_bright_colors: bool,
-
- pub colors: Colors,
-
pub selection: Selection,
/// Path to a shell program to run on startup.
@@ -53,19 +44,6 @@ pub struct Config<T> {
/// Remain open after child process exits.
#[config(skip)]
pub hold: bool,
-
- /// Bell configuration.
- bell: BellConfig,
-
- #[config(deprecated = "use `bell` instead")]
- pub visual_bell: Option<BellConfig>,
-}
-
-impl<T> Config<T> {
- #[inline]
- pub fn bell(&self) -> &BellConfig {
- self.visual_bell.as_ref().unwrap_or(&self.bell)
- }
}
#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)]
@@ -190,9 +168,9 @@ impl CursorBlinking {
}
}
-impl Into<bool> for CursorBlinking {
- fn into(self) -> bool {
- self == Self::On || self == Self::Always
+impl From<CursorBlinking> for bool {
+ fn from(blinking: CursorBlinking) -> bool {
+ blinking == CursorBlinking::On || blinking == CursorBlinking::Always
}
}
diff --git a/alacritty_terminal/src/event.rs b/alacritty_terminal/src/event.rs
index 351b7bc2..a1252570 100644
--- a/alacritty_terminal/src/event.rs
+++ b/alacritty_terminal/src/event.rs
@@ -2,18 +2,49 @@ use std::borrow::Cow;
use std::fmt::{self, Debug, Formatter};
use std::sync::Arc;
+use crate::term::color::Rgb;
use crate::term::{ClipboardType, SizeInfo};
+/// Terminal event.
+///
+/// These events instruct the UI over changes that can't be handled by the terminal emulation layer
+/// itself.
#[derive(Clone)]
pub enum Event {
+ /// Grid has changed possibly requiring a mouse cursor shape change.
MouseCursorDirty,
+
+ /// Window title change.
Title(String),
+
+ /// Reset to the default window title.
ResetTitle,
+
+ /// Request to store a text string in the clipboard.
ClipboardStore(ClipboardType, String),
+
+ /// Request to write the contents of the clipboard to the PTY.
+ ///
+ /// The attached function is a formatter which will corectly transform the clipboard content
+ /// into the expected escape sequence format.
ClipboardLoad(ClipboardType, Arc<dyn Fn(&str) -> String + Sync + Send + 'static>),
+
+ /// Request to write the RGB value of a color to the PTY.
+ ///
+ /// The attached function is a formatter which will corectly transform the RGB color into the
+ /// expected escape sequence format.
+ ColorRequest(usize, Arc<dyn Fn(Rgb) -> String + Sync + Send + 'static>),
+
+ /// Cursor blinking state has changed.
CursorBlinkingChange(bool),
+
+ /// New terminal content available.
Wakeup,
+
+ /// Terminal bell ring.
Bell,
+
+ /// Shutdown request.
Exit,
}
@@ -25,6 +56,7 @@ impl Debug for Event {
Event::ResetTitle => write!(f, "ResetTitle"),
Event::ClipboardStore(ty, text) => write!(f, "ClipboardStore({:?}, {})", ty, text),
Event::ClipboardLoad(ty, _) => write!(f, "ClipboardLoad({:?})", ty),
+ Event::ColorRequest(index, _) => write!(f, "ColorRequest({})", index),
Event::Wakeup => write!(f, "Wakeup"),
Event::Bell => write!(f, "Bell"),
Event::Exit => write!(f, "Exit"),
@@ -48,5 +80,9 @@ pub trait OnResize {
/// Event Loop for notifying the renderer about terminal events.
pub trait EventListener {
- fn send_event(&self, event: Event);
+ fn send_event(&self, _event: Event) {}
}
+
+/// Placeholder implementation for tests.
+#[cfg(test)]
+impl EventListener for () {}
diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs
index 4b3c86dc..7949489a 100644
--- a/alacritty_terminal/src/grid/mod.rs
+++ b/alacritty_terminal/src/grid/mod.rs
@@ -1,7 +1,8 @@
//! A specialized 2D grid implementation optimized for use in a terminal.
use std::cmp::{max, min};
-use std::ops::{Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo};
+use std::iter::{Map, TakeWhile};
+use std::ops::{Bound, Deref, Index, IndexMut, Range, RangeBounds, RangeInclusive};
use serde::{Deserialize, Serialize};
@@ -18,37 +19,6 @@ mod tests;
pub use self::row::Row;
use self::storage::Storage;
-/// Bidirectional iterator.
-pub trait BidirectionalIterator: Iterator {
- fn prev(&mut self) -> Option<Self::Item>;
-}
-
-/// An item in the grid along with its Line and Column.
-pub struct Indexed<T> {
- pub inner: T,
- pub line: Line,
- pub column: Column,
-}
-
-impl<T> Deref for Indexed<T> {
- type Target = T;
-
- #[inline]
- fn deref(&self) -> &T {
- &self.inner
- }
-}
-
-impl<T: PartialEq> ::std::cmp::PartialEq for Grid<T> {
- fn eq(&self, other: &Self) -> bool {
- // Compare struct fields and check result of grid comparison.
- self.raw.eq(&other.raw)
- && self.cols.eq(&other.cols)
- && self.lines.eq(&other.lines)
- && self.display_offset.eq(&other.display_offset)
- }
-}
-
pub trait GridCell: Sized {
/// Check if the cell contains any content.
fn is_empty(&self) -> bool;
@@ -99,6 +69,15 @@ impl IndexMut<CharsetIndex> for Charsets {
}
}
+#[derive(Debug, Copy, Clone)]
+pub enum Scroll {
+ Delta(isize),
+ PageUp,
+ PageDown,
+ Top,
+ Bottom,
+}
+
/// Grid based terminal content storage.
///
/// ```notrust
@@ -157,15 +136,6 @@ pub struct Grid<T> {
max_scroll_limit: usize,
}
-#[derive(Debug, Copy, Clone)]
-pub enum Scroll {
- Delta(isize),
- PageUp,
- PageDown,
- Top,
- Bottom,
-}
-
impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
pub fn new(lines: Line, cols: Column, max_scroll_limit: usize) -> Grid<T> {
Grid {
@@ -341,15 +311,15 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
D: PartialEq,
{
// Determine how many lines to scroll up by.
- let end = Point { line: 0, col: self.cols() };
+ let end = Point { line: 0, column: self.cols() };
let mut iter = self.iter_from(end);
while let Some(cell) = iter.prev() {
- if !cell.is_empty() || iter.cur.line >= *self.lines {
+ if !cell.is_empty() || cell.point.line >= *self.lines {
break;
}
}
- debug_assert!(iter.cur.line <= *self.lines);
- let positions = self.lines - iter.cur.line;
+ debug_assert!(iter.point.line <= *self.lines);
+ let positions = self.lines - iter.point.line;
let region = Line(0)..self.screen_lines();
// Reset display offset.
@@ -383,8 +353,33 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
}
}
-#[allow(clippy::len_without_is_empty)]
impl<T> Grid<T> {
+ /// Reset a visible region within the grid.
+ pub fn reset_region<D, R: RangeBounds<Line>>(&mut self, bounds: R)
+ where
+ T: ResetDiscriminant<D> + GridCell + Clone + Default,
+ D: PartialEq,
+ {
+ let start = match bounds.start_bound() {
+ Bound::Included(line) => *line,
+ Bound::Excluded(line) => *line + 1,
+ Bound::Unbounded => Line(0),
+ };
+
+ let end = match bounds.end_bound() {
+ Bound::Included(line) => *line + 1,
+ Bound::Excluded(line) => *line,
+ Bound::Unbounded => self.screen_lines(),
+ };
+
+ debug_assert!(start < self.screen_lines());
+ debug_assert!(end <= self.screen_lines());
+
+ for row in start.0..end.0 {
+ self.raw[Line(row)].reset(&self.cursor.template);
+ }
+ }
+
/// Clamp a buffer point to the visible region.
pub fn clamp_buffer_to_visible(&self, point: Point<usize>) -> Point {
if point.line < self.display_offset {
@@ -424,12 +419,7 @@ impl<T> Grid<T> {
/// Convert viewport relative point to global buffer indexing.
#[inline]
pub fn visible_to_buffer(&self, point: Point) -> Point<usize> {
- Point { line: self.lines.0 + self.display_offset - point.line.0 - 1, col: point.col }
- }
-
- #[inline]
- pub fn display_iter(&self) -> DisplayIter<'_, T> {
- DisplayIter::new(self)
+ Point { line: self.lines.0 + self.display_offset - point.line.0 - 1, column: point.column }
}
#[inline]
@@ -459,126 +449,51 @@ impl<T> Grid<T> {
#[inline]
pub fn iter_from(&self, point: Point<usize>) -> GridIterator<'_, T> {
- GridIterator { grid: self, cur: point }
- }
-
- #[inline]
- pub fn display_offset(&self) -> usize {
- self.display_offset
+ GridIterator { grid: self, point }
}
+ /// Iterator over all visible cells.
#[inline]
- pub fn cursor_cell(&mut self) -> &mut T {
- let point = self.cursor.point;
- &mut self[&point]
- }
-}
-
-/// Grid dimensions.
-pub trait Dimensions {
- /// Total number of lines in the buffer, this includes scrollback and visible lines.
- fn total_lines(&self) -> usize;
+ pub fn display_iter(&self) -> DisplayIter<'_, T> {
+ let start = Point::new(self.display_offset + self.lines.0, self.cols() - 1);
+ let end = Point::new(self.display_offset, self.cols());
- /// Height of the viewport in lines.
- fn screen_lines(&self) -> Line;
+ let iter = GridIterator { grid: self, point: start };
- /// Width of the terminal in columns.
- fn cols(&self) -> Column;
+ let display_offset = self.display_offset;
+ let lines = self.lines.0;
- /// Number of invisible lines part of the scrollback history.
- #[inline]
- fn history_size(&self) -> usize {
- self.total_lines() - self.screen_lines().0
- }
-}
-
-impl<G> Dimensions for Grid<G> {
- #[inline]
- fn total_lines(&self) -> usize {
- self.raw.len()
+ let take_while: DisplayIterTakeFun<'_, T> =
+ Box::new(move |indexed: &Indexed<&T>| indexed.point <= end);
+ let map: DisplayIterMapFun<'_, T> = Box::new(move |indexed: Indexed<&T>| {
+ let line = Line(lines + display_offset - indexed.point.line - 1);
+ Indexed { point: Point::new(line, indexed.point.column), cell: indexed.cell }
+ });
+ iter.take_while(take_while).map(map)
}
#[inline]
- fn screen_lines(&self) -> Line {
- self.lines
+ pub fn display_offset(&self) -> usize {
+ self.display_offset
}
#[inline]
- fn cols(&self) -> Column {
- self.cols
- }
-}
-
-#[cfg(test)]
-impl Dimensions for (Line, Column) {
- fn total_lines(&self) -> usize {
- *self.0
- }
-
- fn screen_lines(&self) -> Line {
- self.0
- }
-
- fn cols(&self) -> Column {
- self.1
- }
-}
-
-pub struct GridIterator<'a, T> {
- /// Immutable grid reference.
- grid: &'a Grid<T>,
-
- /// Current position of the iterator within the grid.
- cur: Point<usize>,
-}
-
-impl<'a, T> GridIterator<'a, T> {
- pub fn point(&self) -> Point<usize> {
- self.cur
- }
-
- pub fn cell(&self) -> &'a T {
- &self.grid[self.cur]
- }
-}
-
-impl<'a, T> Iterator for GridIterator<'a, T> {
- type Item = &'a T;
-
- fn next(&mut self) -> Option<Self::Item> {
- let last_col = self.grid.cols() - 1;
-
- match self.cur {
- Point { line, col } if line == 0 && col == last_col => return None,
- Point { col, .. } if (col == last_col) => {
- self.cur.line -= 1;
- self.cur.col = Column(0);
- },
- _ => self.cur.col += Column(1),
- }
-
- Some(&self.grid[self.cur])
+ pub fn cursor_cell(&mut self) -> &mut T {
+ let point = self.cursor.point;
+ &mut self[point.line][point.column]
}
}
-impl<'a, T> BidirectionalIterator for GridIterator<'a, T> {
- fn prev(&mut self) -> Option<Self::Item> {
- let last_col = self.grid.cols() - 1;
-
- match self.cur {
- Point { line, col: Column(0) } if line == self.grid.total_lines() - 1 => return None,
- Point { col: Column(0), .. } => {
- self.cur.line += 1;
- self.cur.col = last_col;
- },
- _ => self.cur.col -= Column(1),
- }
-
- Some(&self.grid[self.cur])
+impl<T: PartialEq> PartialEq for Grid<T> {
+ fn eq(&self, other: &Self) -> bool {
+ // Compare struct fields and check result of grid comparison.
+ self.raw.eq(&other.raw)
+ && self.cols.eq(&other.cols)
+ && self.lines.eq(&other.lines)
+ && self.display_offset.eq(&other.display_offset)
}
}
-/// Index active region by line.
impl<T> Index<Line> for Grid<T> {
type Output = Row<T>;
@@ -588,16 +503,6 @@ impl<T> Index<Line> for Grid<T> {
}
}
-/// Index with buffer offset.
-impl<T> Index<usize> for Grid<T> {
- type Output = Row<T>;
-
- #[inline]
- fn index(&self, index: usize) -> &Row<T> {
- &self.raw[index]
- }
-}
-
impl<T> IndexMut<Line> for Grid<T> {
#[inline]
fn index_mut(&mut self, index: Line) -> &mut Row<T> {
@@ -605,26 +510,19 @@ impl<T> IndexMut<Line> for Grid<T> {
}
}
-impl<T> IndexMut<usize> for Grid<T> {
- #[inline]
- fn index_mut(&mut self, index: usize) -> &mut Row<T> {
- &mut self.raw[index]
- }
-}
-
-impl<'point, T> Index<&'point Point> for Grid<T> {
- type Output = T;
+impl<T> Index<usize> for Grid<T> {
+ type Output = Row<T>;
#[inline]
- fn index<'a>(&'a self, point: &Point) -> &'a T {
- &self[point.line][point.col]
+ fn index(&self, index: usize) -> &Row<T> {
+ &self.raw[index]
}
}
-impl<'point, T> IndexMut<&'point Point> for Grid<T> {
+impl<T> IndexMut<usize> for Grid<T> {
#[inline]
- fn index_mut<'a, 'b>(&'a mut self, point: &'b Point) -> &'a mut T {
- &mut self[point.line][point.col]
+ fn index_mut(&mut self, index: usize) -> &mut Row<T> {
+ &mut self.raw[index]
}
}
@@ -633,216 +531,162 @@ impl<T> Index<Point<usize>> for Grid<T> {
#[inline]
fn index(&self, point: Point<usize>) -> &T {
- &self[point.line][point.col]
+ &self[point.line][point.column]
}
}
impl<T> IndexMut<Point<usize>> for Grid<T> {
#[inline]
fn index_mut(&mut self, point: Point<usize>) -> &mut T {
- &mut self[point.line][point.col]
+ &mut self[point.line][point.column]
}
}
-/// A subset of lines in the grid.
-///
-/// May be constructed using Grid::region(..).
-pub struct Region<'a, T> {
- start: Line,
- end: Line,
- raw: &'a Storage<T>,
-}
+impl<T> Index<Point> for Grid<T> {
+ type Output = T;
-/// A mutable subset of lines in the grid.
-///
-/// May be constructed using Grid::region_mut(..).
-pub struct RegionMut<'a, T> {
- start: Line,
- end: Line,
- raw: &'a mut Storage<T>,
+ #[inline]
+ fn index(&self, point: Point) -> &T {
+ &self[point.line][point.column]
+ }
}
-impl<'a, T> RegionMut<'a, T> {
- /// Call the provided function for every item in this region.
- pub fn each<F: Fn(&mut T)>(self, func: F) {
- for row in self {
- for item in row {
- func(item)
- }
- }
+impl<T> IndexMut<Point> for Grid<T> {
+ #[inline]
+ fn index_mut(&mut self, point: Point) -> &mut T {
+ &mut self[point.line][point.column]
}
}
-pub trait IndexRegion<I, T> {
- /// Get an immutable region of Self.
- fn region(&self, _: I) -> Region<'_, T>;
+/// Grid dimensions.
+pub trait Dimensions {
+ /// Total number of lines in the buffer, this includes scrollback and visible lines.
+ fn total_lines(&self) -> usize;
- /// Get a mutable region of Self.
- fn region_mut(&mut self, _: I) -> RegionMut<'_, T>;
-}
+ /// Height of the viewport in lines.
+ fn screen_lines(&self) -> Line;
-impl<T> IndexRegion<Range<Line>, T> for Grid<T> {
- fn region(&self, index: Range<Line>) -> Region<'_, T> {
- assert!(index.start < self.screen_lines());
- assert!(index.end <= self.screen_lines());
- assert!(index.start <= index.end);
- Region { start: index.start, end: index.end, raw: &self.raw }
- }
+ /// Width of the terminal in columns.
+ fn cols(&self) -> Column;
- fn region_mut(&mut self, index: Range<Line>) -> RegionMut<'_, T> {
- assert!(index.start < self.screen_lines());
- assert!(index.end <= self.screen_lines());
- assert!(index.start <= index.end);
- RegionMut { start: index.start, end: index.end, raw: &mut self.raw }
+ /// Number of invisible lines part of the scrollback history.
+ #[inline]
+ fn history_size(&self) -> usize {
+ self.total_lines() - self.screen_lines().0
}
}
-impl<T> IndexRegion<RangeTo<Line>, T> for Grid<T> {
- fn region(&self, index: RangeTo<Line>) -> Region<'_, T> {
- assert!(index.end <= self.screen_lines());
- Region { start: Line(0), end: index.end, raw: &self.raw }
- }
-
- fn region_mut(&mut self, index: RangeTo<Line>) -> RegionMut<'_, T> {
- assert!(index.end <= self.screen_lines());
- RegionMut { start: Line(0), end: index.end, raw: &mut self.raw }
+impl<G> Dimensions for Grid<G> {
+ #[inline]
+ fn total_lines(&self) -> usize {
+ self.raw.len()
}
-}
-impl<T> IndexRegion<RangeFrom<Line>, T> for Grid<T> {
- fn region(&self, index: RangeFrom<Line>) -> Region<'_, T> {
- assert!(index.start < self.screen_lines());
- Region { start: index.start, end: self.screen_lines(), raw: &self.raw }
+ #[inline]
+ fn screen_lines(&self) -> Line {
+ self.lines
}
- fn region_mut(&mut self, index: RangeFrom<Line>) -> RegionMut<'_, T> {
- assert!(index.start < self.screen_lines());
- RegionMut { start: index.start, end: self.screen_lines(), raw: &mut self.raw }
+ #[inline]
+ fn cols(&self) -> Column {
+ self.cols
}
}
-impl<T> IndexRegion<RangeFull, T> for Grid<T> {
- fn region(&self, _: RangeFull) -> Region<'_, T> {
- Region { start: Line(0), end: self.screen_lines(), raw: &self.raw }
- }
-
- fn region_mut(&mut self, _: RangeFull) -> RegionMut<'_, T> {
- RegionMut { start: Line(0), end: self.screen_lines(), raw: &mut self.raw }
+#[cfg(test)]
+impl Dimensions for (Line, Column) {
+ fn total_lines(&self) -> usize {
+ *self.0
}
-}
-
-pub struct RegionIter<'a, T> {
- end: Line,
- cur: Line,
- raw: &'a Storage<T>,
-}
-pub struct RegionIterMut<'a, T> {
- end: Line,
- cur: Line,
- raw: &'a mut Storage<T>,
-}
-
-impl<'a, T> IntoIterator for Region<'a, T> {
- type IntoIter = RegionIter<'a, T>;
- type Item = &'a Row<T>;
-
- fn into_iter(self) -> Self::IntoIter {
- RegionIter { end: self.end, cur: self.start, raw: self.raw }
+ fn screen_lines(&self) -> Line {
+ self.0
}
-}
-impl<'a, T> IntoIterator for RegionMut<'a, T> {
- type IntoIter = RegionIterMut<'a, T>;
- type Item = &'a mut Row<T>;
-
- fn into_iter(self) -> Self::IntoIter {
- RegionIterMut { end: self.end, cur: self.start, raw: self.raw }
+ fn cols(&self) -> Column {
+ self.1
}
}
-impl<'a, T> Iterator for RegionIter<'a, T> {
- type Item = &'a Row<T>;
-
- fn next(&mut self) -> Option<Self::Item> {
- if self.cur < self.end {
- let index = self.cur;
- self.cur += 1;
- Some(&self.raw[index])
- } else {
- None
- }
- }
+#[derive(Debug, PartialEq)]
+pub struct Indexed<T, L = usize> {
+ pub point: Point<L>,
+ pub cell: T,
}
-impl<'a, T> Iterator for RegionIterMut<'a, T> {
- type Item = &'a mut Row<T>;
+impl<T, L> Deref for Indexed<T, L> {
+ type Target = T;
- fn next(&mut self) -> Option<Self::Item> {
- if self.cur < self.end {
- let index = self.cur;
- self.cur += 1;
- unsafe { Some(&mut *(&mut self.raw[index] as *mut _)) }
- } else {
- None
- }
+ #[inline]
+ fn deref(&self) -> &T {
+ &self.cell
}
}
-/// Iterates over the visible area accounting for buffer transform.
-pub struct DisplayIter<'a, T> {
+/// Grid cell iterator.
+pub struct GridIterator<'a, T> {
+ /// Immutable grid reference.
grid: &'a Grid<T>,
- offset: usize,
- limit: usize,
- col: Column,
- line: Line,
-}
-impl<'a, T: 'a> DisplayIter<'a, T> {
- pub fn new(grid: &'a Grid<T>) -> DisplayIter<'a, T> {
- let offset = grid.display_offset + *grid.screen_lines() - 1;
- let limit = grid.display_offset;
- let col = Column(0);
- let line = Line(0);
-
- DisplayIter { grid, offset, col, limit, line }
- }
+ /// Current position of the iterator within the grid.
+ point: Point<usize>,
+}
- pub fn offset(&self) -> usize {
- self.offset
+impl<'a, T> GridIterator<'a, T> {
+ /// Current iteratior position.
+ pub fn point(&self) -> Point<usize> {
+ self.point
}
- pub fn point(&self) -> Point {
- Point::new(self.line, self.col)
+ /// Cell at the current iteratior position.
+ pub fn cell(&self) -> &'a T {
+ &self.grid[self.point]
}
}
-impl<'a, T: 'a> Iterator for DisplayIter<'a, T> {
+impl<'a, T> Iterator for GridIterator<'a, T> {
type Item = Indexed<&'a T>;
- #[inline]
fn next(&mut self) -> Option<Self::Item> {
- // Return None if we've reached the end.
- if self.offset == self.limit && self.grid.cols() == self.col {
- return None;
+ let last_col = self.grid.cols() - 1;
+ match self.point {
+ Point { line, column: col } if line == 0 && col == last_col => return None,
+ Point { column: col, .. } if (col == last_col) => {
+ self.point.line -= 1;
+ self.point.column = Column(0);
+ },
+ _ => self.point.column += Column(1),
}
- // Get the next item.
- let item = Some(Indexed {
- inner: &self.grid.raw[self.offset][self.col],
- line: self.line,
- column: self.col,
- });
+ Some(Indexed { cell: &self.grid[self.point], point: self.point })
+ }
+}
+
+/// Bidirectional iterator.
+pub trait BidirectionalIterator: Iterator {
+ fn prev(&mut self) -> Option<Self::Item>;
+}
- // Update line/col to point to next item.
- self.col += 1;
- if self.col == self.grid.cols() && self.offset != self.limit {
- self.offset -= 1;
+impl<'a, T> BidirectionalIterator for GridIterator<'a, T> {
+ fn prev(&mut self) -> Option<Self::Item> {
+ let last_col = self.grid.cols() - 1;
- self.col = Column(0);
- self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit));
+ match self.point {
+ Point { line, column: Column(0) } if line == self.grid.total_lines() - 1 => {
+ return None
+ },
+ Point { column: Column(0), .. } => {
+ self.point.line += 1;
+ self.point.column = last_col;
+ },
+ _ => self.point.column -= Column(1),
}
- item
+ Some(Indexed { cell: &self.grid[self.point], point: self.point })
}
}
+
+pub type DisplayIter<'a, T> =
+ Map<TakeWhile<GridIterator<'a, T>, DisplayIterTakeFun<'a, T>>, DisplayIterMapFun<'a, T>>;
+type DisplayIterTakeFun<'a, T> = Box<dyn Fn(&Indexed<&'a T>) -> bool>;
+type DisplayIterMapFun<'a, T> = Box<dyn FnMut(Indexed<&'a T>) -> Indexed<&'a T, Line>>;
diff --git a/alacritty_terminal/src/grid/resize.rs b/alacritty_terminal/src/grid/resize.rs
index 1a16e09e..40492c3a 100644
--- a/alacritty_terminal/src/grid/resize.rs
+++ b/alacritty_terminal/src/grid/resize.rs
@@ -113,7 +113,7 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
// Remove the linewrap special case, by moving the cursor outside of the grid.
if self.cursor.input_needs_wrap && reflow {
self.cursor.input_needs_wrap = false;
- self.cursor.point.col += 1;
+ self.cursor.point.column += 1;
}
let mut rows = self.raw.take_all();
@@ -171,11 +171,11 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
let mut target = self.cursor.point.sub(cols, num_wrapped);
// Clamp to the last column, if no content was reflown with the cursor.
- if target.col.0 == 0 && row.is_clear() {
+ if target.column.0 == 0 && row.is_clear() {
self.cursor.input_needs_wrap = true;
target = target.sub(cols, 1);
}
- self.cursor.point.col = target.col;
+ self.cursor.point.column = target.column;
// Get required cursor line changes. Since `num_wrapped` is smaller than `cols`
// this will always be either `0` or `1`.
@@ -248,7 +248,7 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
// Remove the linewrap special case, by moving the cursor outside of the grid.
if self.cursor.input_needs_wrap && reflow {
self.cursor.input_needs_wrap = false;
- self.cursor.point.col += 1;
+ self.cursor.point.column += 1;
}
let mut new_raw = Vec::with_capacity(self.raw.len());
@@ -262,7 +262,7 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
// width it is then later reflown.
let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0;
if i == cursor_buffer_line {
- self.cursor.point.col += buffered.len();
+ self.cursor.point.column += buffered.len();
}
row.append_front(buffered);
@@ -274,7 +274,7 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
Some(wrapped) if reflow => wrapped,
_ => {
let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0;
- if reflow && i == cursor_buffer_line && self.cursor.point.col > cols {
+ if reflow && i == cursor_buffer_line && self.cursor.point.column > cols {
// If there are empty cells before the cursor, we assume it is explicit
// whitespace and need to wrap it like normal content.
Vec::new()
@@ -333,17 +333,17 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
} else {
// Reflow cursor if a line below it is deleted.
let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0;
- if (i == cursor_buffer_line && self.cursor.point.col < cols)
+ if (i == cursor_buffer_line && self.cursor.point.column < cols)
|| i < cursor_buffer_line
{
self.cursor.point.line.0 = self.cursor.point.line.saturating_sub(1);
}
// Reflow the cursor if it is on this line beyond the width.
- if i == cursor_buffer_line && self.cursor.point.col >= cols {
+ if i == cursor_buffer_line && self.cursor.point.column >= cols {
// Since only a single new line is created, we subtract only `cols`
// from the cursor instead of reflowing it completely.
- self.cursor.point.col -= cols;
+ self.cursor.point.column -= cols;
}
// Make sure new row is at least as long as new width.
@@ -363,17 +363,17 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
// Reflow the primary cursor, or clamp it if reflow is disabled.
if !reflow {
- self.cursor.point.col = min(self.cursor.point.col, cols - 1);
- } else if self.cursor.point.col == cols
+ self.cursor.point.column = min(self.cursor.point.column, cols - 1);
+ } else if self.cursor.point.column == cols
&& !self[self.cursor.point.line][cols - 1].flags().contains(Flags::WRAPLINE)
{
self.cursor.input_needs_wrap = true;
- self.cursor.point.col -= 1;
+ self.cursor.point.column -= 1;
} else {
self.cursor.point = self.cursor.point.add(cols, 0);
}
// Clamp the saved cursor to the grid.
- self.saved_cursor.point.col = min(self.saved_cursor.point.col, cols - 1);
+ self.saved_cursor.point.column = min(self.saved_cursor.point.column, cols - 1);
}
}
diff --git a/alacritty_terminal/src/grid/tests.rs b/alacritty_terminal/src/grid/tests.rs
index f178226c..269ab636 100644
--- a/alacritty_terminal/src/grid/tests.rs
+++ b/alacritty_terminal/src/grid/tests.rs
@@ -123,6 +123,10 @@ fn scroll_down() {
// Test that GridIterator works.
#[test]
fn test_iter() {
+ let assert_indexed = |value: usize, indexed: Option<Indexed<&usize>>| {
+ assert_eq!(Some(&value), indexed.map(|indexed| indexed.cell));
+ };
+
let mut grid = Grid::<usize>::new(Line(5), Column(5), 0);
for i in 0..5 {
for j in 0..5 {
@@ -130,33 +134,33 @@ fn test_iter() {
}
}
- let mut iter = grid.iter_from(Point { line: 4, col: Column(0) });
+ let mut iter = grid.iter_from(Point::new(4, Column(0)));
assert_eq!(None, iter.prev());
- assert_eq!(Some(&1), iter.next());
- assert_eq!(Column(1), iter.point().col);
+ assert_indexed(1, iter.next());
+ assert_eq!(Column(1), iter.point().column);
assert_eq!(4, iter.point().line);
- assert_eq!(Some(&2), iter.next());
- assert_eq!(Some(&3), iter.next());
- assert_eq!(Some(&4), iter.next());
+ assert_indexed(2, iter.next());
+ assert_indexed(3, iter.next());
+ assert_indexed(4, iter.next());
// Test line-wrapping.
- assert_eq!(Some(&5), iter.next());
- assert_eq!(Column(0), iter.point().col);
+ assert_indexed(5, iter.next());
+ assert_eq!(Column(0), iter.point().column);
assert_eq!(3, iter.point().line);
- assert_eq!(Some(&4), iter.prev());
- assert_eq!(Column(4), iter.point().col);
+ assert_indexed(4, iter.prev());
+ assert_eq!(Column(4), iter.point().column);
assert_eq!(4, iter.point().line);
// Make sure iter.cell() returns the current iterator position.
assert_eq!(&4, iter.cell());
// Test that iter ends at end of grid.
- let mut final_iter = grid.iter_from(Point { line: 0, col: Column(4) });
+ let mut final_iter = grid.iter_from(Point { line: 0, column: Column(4) });
assert_eq!(None, final_iter.next());
- assert_eq!(Some(&23), final_iter.prev());
+ assert_indexed(23, final_iter.prev());
}
#[test]
diff --git a/alacritty_terminal/src/index.rs b/alacritty_terminal/src/index.rs
index 4b0d6943..e8e52c80 100644
--- a/alacritty_terminal/src/index.rs
+++ b/alacritty_terminal/src/index.rs
@@ -8,7 +8,6 @@ use std::ops::{self, Add, AddAssign, Deref, Range, Sub, SubAssign};
use serde::{Deserialize, Serialize};
use crate::grid::Dimensions;
-use crate::term::render::RenderableCell;
/// The side of a cell.
pub type Side = Direction;
@@ -48,12 +47,12 @@ pub enum Boundary {
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, Eq, PartialEq)]
pub struct Point<L = Line> {
pub line: L,
- pub col: Column,
+ pub column: Column,
}
impl<L> Point<L> {
pub fn new(line: L, col: Column) -> Point<L> {
- Point { line, col }
+ Point { line, column: col }
}
#[inline]
@@ -63,10 +62,10 @@ impl<L> Point<L> {
L: Copy + Default + Into<Line> + Add<usize, Output = L> + Sub<usize, Output = L>,
{
let num_cols = num_cols.0;
- let line_changes = (rhs + num_cols - 1).saturating_sub(self.col.0) / num_cols;
+ let line_changes = (rhs + num_cols - 1).saturating_sub(self.column.0) / num_cols;
if self.line.into() >= Line(line_changes) {
self.line = self.line - line_changes;
- self.col = Column((num_cols + self.col.0 - rhs % num_cols) % num_cols);
+ self.column = Column((num_cols + self.column.0 - rhs % num_cols) % num_cols);
self
} else {
Point::new(L::default(), Column(0))
@@ -80,8 +79,8 @@ impl<L> Point<L> {
L: Copy + Default + Into<Line> + Add<usize, Output = L> + Sub<usize, Output = L>,
{
let num_cols = num_cols.0;
- self.line = self.line + (rhs + self.col.0) / num_cols;
- self.col = Column((self.col.0 + rhs) % num_cols);
+ self.line = self.line + (rhs + self.column.0) / num_cols;
+ self.column = Column((self.column.0 + rhs) % num_cols);
self
}
}
@@ -96,13 +95,13 @@ impl Point<usize> {
let total_lines = dimensions.total_lines();
let num_cols = dimensions.cols().0;
- self.line += (rhs + num_cols - 1).saturating_sub(self.col.0) / num_cols;
- self.col = Column((num_cols + self.col.0 - rhs % num_cols) % num_cols);
+ self.line += (rhs + num_cols - 1).saturating_sub(self.column.0) / num_cols;
+ self.column = Column((num_cols + self.column.0 - rhs % num_cols) % num_cols);
if self.line >= total_lines {
match boundary {
Boundary::Clamp => Point::new(total_lines - 1, Column(0)),
- Boundary::Wrap => Point::new(self.line - total_lines, self.col),
+ Boundary::Wrap => Point::new(self.line - total_lines, self.column),
}
} else {
self
@@ -117,17 +116,17 @@ impl Point<usize> {
{
let num_cols = dimensions.cols();
- let line_delta = (rhs + self.col.0) / num_cols.0;
+ let line_delta = (rhs + self.column.0) / num_cols.0;
if self.line >= line_delta {
self.line -= line_delta;
- self.col = Column((self.col.0 + rhs) % num_cols.0);
+ self.column = Column((self.column.0 + rhs) % num_cols.0);
self
} else {
match boundary {
Boundary::Clamp => Point::new(0, num_cols - 1),
Boundary::Wrap => {
- let col = Column((self.col.0 + rhs) % num_cols.0);
+ let col = Column((self.column.0 + rhs) % num_cols.0);
let line = dimensions.total_lines() + self.line - line_delta;
Point::new(line, col)
},
@@ -144,7 +143,7 @@ impl PartialOrd for Point {
impl Ord for Point {
fn cmp(&self, other: &Point) -> Ordering {
- match (self.line.cmp(&other.line), self.col.cmp(&other.col)) {
+ match (self.line.cmp(&other.line), self.column.cmp(&other.column)) {
(Ordering::Equal, ord) | (ord, _) => ord,
}
}
@@ -158,7 +157,7 @@ impl PartialOrd for Point<usize> {
impl Ord for Point<usize> {
fn cmp(&self, other: &Point<usize>) -> Ordering {
- match (self.line.cmp(&other.line), self.col.cmp(&other.col)) {
+ match (self.line.cmp(&other.line), self.column.cmp(&other.column)) {
(Ordering::Equal, ord) => ord,
(Ordering::Less, _) => Ordering::Greater,
(Ordering::Greater, _) => Ordering::Less,
@@ -168,31 +167,25 @@ impl Ord for Point<usize> {
impl From<Point<usize>> for Point<isize> {
fn from(point: Point<usize>) -> Self {
- Point::new(point.line as isize, point.col)
+ Point::new(point.line as isize, point.column)
}
}
impl From<Point<usize>> for Point<Line> {
fn from(point: Point<usize>) -> Self {
- Point::new(Line(point.line), point.col)
+ Point::new(Line(point.line), point.column)
}
}
impl From<Point<isize>> for Point<usize> {
fn from(point: Point<isize>) -> Self {
- Point::new(point.line as usize, point.col)
+ Point::new(point.line as usize, point.column)
}
}
impl From<Point> for Point<usize> {
fn from(point: Point) -> Self {
- Point::new(point.line.0, point.col)
- }
-}
-
-impl From<&RenderableCell> for Point<Line> {
- fn from(cell: &RenderableCell) -> Self {
- Point::new(cell.line, cell.column)
+ Point::new(point.line.0, point.column)
}
}
@@ -485,7 +478,7 @@ mod tests {
let result = point.sub(num_cols, 1);
- assert_eq!(result, Point::new(0, point.col - 1));
+ assert_eq!(result, Point::new(0, point.column - 1));
}
#[test]
@@ -515,7 +508,7 @@ mod tests {
let result = point.add(num_cols, 1);
- assert_eq!(result, Point::new(0, point.col + 1));
+ assert_eq!(result, Point::new(0, point.column + 1));
}
#[test]
@@ -534,7 +527,7 @@ mod tests {
let result = point.add_absolute(&(Line(1), Column(42)), Boundary::Clamp, 1);
- assert_eq!(result, Point::new(0, point.col + 1));
+ assert_eq!(result, Point::new(0, point.column + 1));
}
#[test]
@@ -588,7 +581,7 @@ mod tests {
let result = point.sub_absolute(&(Line(1), Column(42)), Boundary::Clamp, 1);
- assert_eq!(result, Point::new(0, point.col - 1));
+ assert_eq!(result, Point::new(0, point.column - 1));
}
#[test]
diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs
index 9402fc21..afb98c0d 100644
--- a/alacritty_terminal/src/selection.rs
+++ b/alacritty_terminal/src/selection.rs
@@ -10,10 +10,10 @@ use std::mem;
use std::ops::{Bound, Range, RangeBounds};
use crate::ansi::CursorShape;
-use crate::grid::{Dimensions, Grid, GridCell};
+use crate::grid::{Dimensions, GridCell, Indexed};
use crate::index::{Column, Line, Point, Side};
-use crate::term::cell::Flags;
-use crate::term::Term;
+use crate::term::cell::{Cell, Flags};
+use crate::term::{RenderableCursor, Term};
/// A Point and side within that point.
#[derive(Debug, Copy, Clone, PartialEq)]
@@ -43,68 +43,42 @@ impl<L> SelectionRange<L> {
pub fn new(start: Point<L>, end: Point<L>, is_block: bool) -> Self {
Self { start, end, is_block }
}
+}
+impl SelectionRange<Line> {
/// Check if a point lies within the selection.
- pub fn contains(&self, point: Point<L>) -> bool
- where
- L: PartialEq + PartialOrd,
- {
+ pub fn contains(&self, point: Point) -> bool {
self.start.line <= point.line
&& self.end.line >= point.line
- && (self.start.col <= point.col || (self.start.line != point.line && !self.is_block))
- && (self.end.col >= point.col || (self.end.line != point.line && !self.is_block))
+ && (self.start.column <= point.column
+ || (self.start.line != point.line && !self.is_block))
+ && (self.end.column >= point.column || (self.end.line != point.line && !self.is_block))
}
-}
-impl SelectionRange<Line> {
/// Check if the cell at a point is part of the selection.
- pub fn contains_cell<T>(
- &self,
- grid: &Grid<T>,
- point: Point,
- cursor_point: Point,
- cursor_shape: CursorShape,
- ) -> bool
- where
- T: GridCell,
- {
+ pub fn contains_cell(&self, indexed: &Indexed<&Cell, Line>, cursor: RenderableCursor) -> bool {
// Do not invert block cursor at selection boundaries.
- if cursor_shape == CursorShape::Block
- && cursor_point == point
- && (self.start == point
- || self.end == point
+ if cursor.shape == CursorShape::Block
+ && cursor.point == indexed.point
+ && (self.start == indexed.point
+ || self.end == indexed.point
|| (self.is_block
- && ((self.start.line == point.line && self.end.col == point.col)
- || (self.end.line == point.line && self.start.col == point.col))))
+ && ((self.start.line == indexed.point.line
+ && self.end.column == indexed.point.column)
+ || (self.end.line == indexed.point.line
+ && self.start.column == indexed.point.column))))
{
return false;
}
// Point itself is selected.
- if self.contains(point) {
+ if self.contains(indexed.point) {
return true;
}
- let num_cols = grid.cols();
-
- // Convert to absolute coordinates to adjust for the display offset.
- let buffer_point = grid.visible_to_buffer(point);
- let cell = &grid[buffer_point];
-
- // Check if wide char's spacers are selected.
- if cell.flags().contains(Flags::WIDE_CHAR) {
- let prev = point.sub(num_cols, 1);
- let buffer_prev = grid.visible_to_buffer(prev);
- let next = point.add(num_cols, 1);
-
- // Check trailing spacer.
- self.contains(next)
- // Check line-wrapping, leading spacer.
- || (grid[buffer_prev].flags().contains(Flags::LEADING_WIDE_CHAR_SPACER)
- && self.contains(prev))
- } else {
- false
- }
+ // Check if a wide char's trailing spacer is selected.
+ indexed.cell.flags().contains(Flags::WIDE_CHAR)
+ && self.contains(Point::new(indexed.point.line, indexed.point.column + 1))
}
}
@@ -184,7 +158,7 @@ impl Selection {
// Clamp selection to start of region.
if start.point.line >= range_top && range_top != num_lines {
if self.ty != SelectionType::Block {
- start.point.col = Column(0);
+ start.point.column = Column(0);
start.side = Side::Left;
}
start.point.line = range_top - 1;
@@ -204,7 +178,7 @@ impl Selection {
// Clamp selection to end of region.
if end.point.line < range_bottom {
if self.ty != SelectionType::Block {
- end.point.col = Column(num_cols - 1);
+ end.point.column = Column(num_cols - 1);
end.side = Side::Right;
}
end.point.line = range_bottom;
@@ -228,7 +202,7 @@ impl Selection {
|| (start.side == Side::Right
&& end.side == Side::Left
&& (start.point.line == end.point.line)
- && start.point.col + 1 == end.point.col)
+ && start.point.column + 1 == end.point.column)
},
SelectionType::Block => {
let (start, end) = (self.region.start, self.region.end);
@@ -236,11 +210,11 @@ impl Selection {
// Block selection is empty when the points' columns and sides are identical
// or two cells with adjacent columns have the sides right -> left,
// regardless of their lines
- (start.point.col == end.point.col && start.side == end.side)
- || (start.point.col + 1 == end.point.col
+ (start.point.column == end.point.column && start.side == end.side)
+ || (start.point.column + 1 == end.point.column
&& start.side == Side::Right
&& end.side == Side::Left)
- || (end.point.col + 1 == start.point.col
+ || (end.point.column + 1 == start.point.column
&& start.side == Side::Left
&& end.side == Side::Right)
},
@@ -277,7 +251,8 @@ impl Selection {
let (start, end) = (self.region.start.point, self.region.end.point);
let (start_side, end_side) = match self.ty {
SelectionType::Block
- if start.col > end.col || (start.col == end.col && start.line < end.line) =>
+ if start.column > end.column
+ || (start.column == end.column && start.line < end.line) =>
{
(Side::Right, Side::Left)
},
@@ -317,7 +292,7 @@ impl Selection {
/// Bring start and end points in the correct order.
fn points_need_swap(start: Point<usize>, end: Point<usize>) -> bool {
- start.line < end.line || start.line == end.line && start.col > end.col
+ start.line < end.line || start.line == end.line && start.column > end.column
}
/// Clamp selection inside grid to prevent OOB.
@@ -337,7 +312,7 @@ impl Selection {
// Clamp to grid if it is still partially visible.
if !is_block {
start.side = Side::Left;
- start.point.col = Column(0);
+ start.point.column = Column(0);
}
start.point.line = lines - 1;
}
@@ -352,7 +327,7 @@ impl Selection {
) -> SelectionRange {
if start == end {
if let Some(matching) = term.bracket_search(start) {
- if (matching.line == start.line && matching.col < start.col)
+ if (matching.line == start.line && matching.column < start.column)
|| (matching.line > start.line)
{
start = matching;
@@ -394,20 +369,20 @@ impl Selection {
// Remove last cell if selection ends to the left of a cell.
if end.side == Side::Left && start.point != end.point {
// Special case when selection ends to left of first cell.
- if end.point.col == Column(0) {
- end.point.col = num_cols - 1;
+ if end.point.column == Column(0) {
+ end.point.column = num_cols - 1;
end.point.line += 1;
} else {
- end.point.col -= 1;
+ end.point.column -= 1;
}
}
// Remove first cell if selection starts at the right of a cell.
if start.side == Side::Right && start.point != end.point {
- start.point.col += 1;
+ start.point.column += 1;
// Wrap to next line when selection starts to the right of last column.
- if start.point.col == num_cols {
+ if start.point.column == num_cols {
start.point = Point::new(start.point.line.saturating_sub(1), Column(0));
}
}
@@ -421,19 +396,19 @@ impl Selection {
}
// Always go top-left -> bottom-right.
- if start.point.col > end.point.col {
+ if start.point.column > end.point.column {
mem::swap(&mut start.side, &mut end.side);
- mem::swap(&mut start.point.col, &mut end.point.col);
+ mem::swap(&mut start.point.column, &mut end.point.column);
}
// Remove last cell if selection ends to the left of a cell.
- if end.side == Side::Left && start.point != end.point && end.point.col.0 > 0 {
- end.point.col -= 1;
+ if end.side == Side::Left && start.point != end.point && end.point.column.0 > 0 {
+ end.point.column -= 1;
}
// Remove first cell if selection starts at the right of a cell.
if start.side == Side::Right && start.point != end.point {
- start.point.col += 1;
+ start.point.column += 1;
}
Some(SelectionRange { start: start.point, end: end.point, is_block: true })
@@ -454,18 +429,12 @@ mod tests {
use super::*;
use crate::config::MockConfig;
- use crate::event::{Event, EventListener};
use crate::index::{Column, Line, Point, Side};
use crate::term::{SizeInfo, Term};
- struct Mock;
- impl EventListener for Mock {
- fn send_event(&self, _event: Event) {}
- }
-
- fn term(height: usize, width: usize) -> Term<Mock> {
+ fn term(height: usize, width: usize) -> Term<()> {
let size = SizeInfo::new(width as f32, height as f32, 1.0, 1.0, 0.0, 0.0, false);
- Term::new(&MockConfig::default(), size, Mock)
+ Term::new(&MockConfig::default(), size, ())
}
/// Test case of single cell selection.
@@ -475,7 +444,7 @@ mod tests {
/// 3. [BE]
#[test]
fn single_cell_left_to_right() {
- let location = Point { line: 0, col: Column(0) };
+ let location = Point { line: 0, column: Column(0) };
let mut selection = Selection::new(SelectionType::Simple, location, Side::Left);
selection.update(location, Side::Right);
@@ -493,7 +462,7 @@ mod tests {
/// 3. [EB]
#[test]
fn single_cell_right_to_left() {
- let location = Point { line: 0, col: Column(0) };
+ let location = Point { line: 0, column: Column(0) };
let mut selection = Selection::new(SelectionType::Simple, location, Side::Right);
selection.update(location, Side::Left);
diff --git a/alacritty_terminal/src/term/color.rs b/alacritty_terminal/src/term/color.rs
index 88af6de6..a67e2c6e 100644
--- a/alacritty_terminal/src/term/color.rs
+++ b/alacritty_terminal/src/term/color.rs
@@ -7,14 +7,11 @@ use serde::de::{Error as _, Visitor};
use serde::{Deserialize, Deserializer, Serialize};
use serde_yaml::Value;
-use crate::ansi;
-use crate::config::Colors;
+use crate::ansi::NamedColor;
+/// Number of terminal colors.
pub const COUNT: usize = 269;
-/// Factor for automatic computation of dim colors used by terminal.
-pub const DIM_FACTOR: f32 = 0.66;
-
#[derive(Debug, Eq, PartialEq, Copy, Clone, Default, Serialize)]
pub struct Rgb {
pub r: u8,
@@ -42,8 +39,9 @@ impl Rgb {
0.2126 * r_luminance + 0.7152 * g_luminance + 0.0722 * b_luminance
}
- /// Implementation of W3C's contrast algorithm:
- /// https://www.w3.org/TR/WCAG20/#contrast-ratiodef
+ /// Implementation of [W3C's contrast algorithm].
+ ///
+ /// [W3C's contrast algorithm]: https://www.w3.org/TR/WCAG20/#contrast-ratiodef
pub fn contrast(self, other: Rgb) -> f64 {
let self_luminance = self.luminance();
let other_luminance = other.luminance();
@@ -231,188 +229,57 @@ impl<'de> Deserialize<'de> for CellRgb {
}
}
-/// List of indexed colors.
+/// Array of indexed colors.
///
-/// The first 16 entries are the standard ansi named colors. Items 16..232 are
-/// the color cube. Items 233..256 are the grayscale ramp. Item 256 is
-/// the configured foreground color, item 257 is the configured background
-/// color, item 258 is the cursor color. Following that are 8 positions for dim colors.
-/// Item 267 is the bright foreground color, 268 the dim foreground.
+/// | Indices | Description |
+/// | -------- | ----------------- |
+/// | 0..16 | Named ANSI colors |
+/// | 16..232 | Color cube |
+/// | 233..256 | Grayscale ramp |
+/// | 256 | Foreground |
+/// | 257 | Background |
+/// | 258 | Cursor |
+/// | 259..267 | Dim colors |
+/// | 267 | Bright foreground |
+/// | 268 | Dim background |
#[derive(Copy, Clone)]
-pub struct List([Rgb; COUNT]);
-
-impl<'a> From<&'a Colors> for List {
- fn from(colors: &Colors) -> List {
- // Type inference fails without this annotation.
- let mut list = List([Rgb::default(); COUNT]);
-
- list.fill_named(colors);
- list.fill_cube(colors);
- list.fill_gray_ramp(colors);
-
- list
- }
-}
-
-impl List {
- pub fn fill_named(&mut self, colors: &Colors) {
- // Normals.
- self[ansi::NamedColor::Black] = colors.normal.black;
- self[ansi::NamedColor::Red] = colors.normal.red;
- self[ansi::NamedColor::Green] = colors.normal.green;
- self[ansi::NamedColor::Yellow] = colors.normal.yellow;
- self[ansi::NamedColor::Blue] = colors.normal.blue;
- self[ansi::NamedColor::Magenta] = colors.normal.magenta;
- self[ansi::NamedColor::Cyan] = colors.normal.cyan;
- self[ansi::NamedColor::White] = colors.normal.white;
-
- // Brights.
- self[ansi::NamedColor::BrightBlack] = colors.bright.black;
- self[ansi::NamedColor::BrightRed] = colors.bright.red;
- self[ansi::NamedColor::BrightGreen] = colors.bright.green;
- self[ansi::NamedColor::BrightYellow] = colors.bright.yellow;
- self[ansi::NamedColor::BrightBlue] = colors.bright.blue;
- self[ansi::NamedColor::BrightMagenta] = colors.bright.magenta;
- self[ansi::NamedColor::BrightCyan] = colors.bright.cyan;
- self[ansi::NamedColor::BrightWhite] = colors.bright.white;
- self[ansi::NamedColor::BrightForeground] =
- colors.primary.bright_foreground.unwrap_or(colors.primary.foreground);
-
- // Foreground and background.
- self[ansi::NamedColor::Foreground] = colors.primary.foreground;
- self[ansi::NamedColor::Background] = colors.primary.background;
-
- // Dims.
- self[ansi::NamedColor::DimForeground] =
- colors.primary.dim_foreground.unwrap_or(colors.primary.foreground * DIM_FACTOR);
- match colors.dim {
- Some(ref dim) => {
- trace!("Using config-provided dim colors");
- self[ansi::NamedColor::DimBlack] = dim.black;
- self[ansi::NamedColor::DimRed] = dim.red;
- self[ansi::NamedColor::DimGreen] = dim.green;
- self[ansi::NamedColor::DimYellow] = dim.yellow;
- self[ansi::NamedColor::DimBlue] = dim.blue;
- self[ansi::NamedColor::DimMagenta] = dim.magenta;
- self[ansi::NamedColor::DimCyan] = dim.cyan;
- self[ansi::NamedColor::DimWhite] = dim.white;
- },
- None => {
- trace!("Deriving dim colors from normal colors");
- self[ansi::NamedColor::DimBlack] = colors.normal.black * DIM_FACTOR;
- self[ansi::NamedColor::DimRed] = colors.normal.red * DIM_FACTOR;
- self[ansi::NamedColor::DimGreen] = colors.normal.green * DIM_FACTOR;
- self[ansi::NamedColor::DimYellow] = colors.normal.yellow * DIM_FACTOR;
- self[ansi::NamedColor::DimBlue] = colors.normal.blue * DIM_FACTOR;
- self[ansi::NamedColor::DimMagenta] = colors.normal.magenta * DIM_FACTOR;
- self[ansi::NamedColor::DimCyan] = colors.normal.cyan * DIM_FACTOR;
- self[ansi::NamedColor::DimWhite] = colors.normal.white * DIM_FACTOR;
- },
- }
- }
-
- pub fn fill_cube(&mut self, colors: &Colors) {
- let mut index: usize = 16;
- // Build colors.
- for r in 0..6 {
- for g in 0..6 {
- for b in 0..6 {
- // Override colors 16..232 with the config (if present).
- if let Some(indexed_color) =
- colors.indexed_colors.iter().find(|ic| ic.index() == index as u8)
- {
- self[index] = indexed_color.color;
- } else {
- self[index] = Rgb {
- r: if r == 0 { 0 } else { r * 40 + 55 },
- b: if b == 0 { 0 } else { b * 40 + 55 },
- g: if g == 0 { 0 } else { g * 40 + 55 },
- };
- }
- index += 1;
- }
- }
- }
-
- debug_assert!(index == 232);
- }
-
- pub fn fill_gray_ramp(&mut self, colors: &Colors) {
- let mut index: usize = 232;
-
- for i in 0..24 {
- // Index of the color is number of named colors + number of cube colors + i.
- let color_index = 16 + 216 + i;
-
- // Override colors 232..256 with the config (if present).
- if let Some(indexed_color) =
- colors.indexed_colors.iter().find(|ic| ic.index() == color_index)
- {
- self[index] = indexed_color.color;
- index += 1;
- continue;
- }
-
- let value = i * 10 + 8;
- self[index] = Rgb { r: value, g: value, b: value };
- index += 1;
- }
-
- debug_assert!(index == 256);
- }
-}
-
-impl fmt::Debug for List {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.write_str("List[..]")
- }
-}
+pub struct Colors([Option<Rgb>; COUNT]);
-impl Index<ansi::NamedColor> for List {
- type Output = Rgb;
-
- #[inline]
- fn index(&self, idx: ansi::NamedColor) -> &Self::Output {
- &self.0[idx as usize]
- }
-}
-
-impl IndexMut<ansi::NamedColor> for List {
- #[inline]
- fn index_mut(&mut self, idx: ansi::NamedColor) -> &mut Self::Output {
- &mut self.0[idx as usize]
+impl Default for Colors {
+ fn default() -> Self {
+ Self([None; COUNT])
}
}
-impl Index<usize> for List {
- type Output = Rgb;
+impl Index<usize> for Colors {
+ type Output = Option<Rgb>;
#[inline]
- fn index(&self, idx: usize) -> &Self::Output {
- &self.0[idx]
+ fn index(&self, index: usize) -> &Self::Output {
+ &self.0[index]
}
}
-impl IndexMut<usize> for List {
+impl IndexMut<usize> for Colors {
#[inline]
- fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
- &mut self.0[idx]
+ fn index_mut(&mut self, index: usize) -> &mut Self::Output {
+ &mut self.0[index]
}
}
-impl Index<u8> for List {
- type Output = Rgb;
+impl Index<NamedColor> for Colors {
+ type Output = Option<Rgb>;
#[inline]
- fn index(&self, idx: u8) -> &Self::Output {
- &self.0[idx as usize]
+ fn index(&self, index: NamedColor) -> &Self::Output {
+ &self.0[index as usize]
}
}
-impl IndexMut<u8> for List {
+impl IndexMut<NamedColor> for Colors {
#[inline]
- fn index_mut(&mut self, idx: u8) -> &mut Self::Output {
- &mut self.0[idx as usize]
+ fn index_mut(&mut self, index: NamedColor) -> &mut Self::Output {
+ &mut self.0[index as usize]
}
}
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs
index 62bbc7c4..4ece1b52 100644
--- a/alacritty_terminal/src/term/mod.rs
+++ b/alacritty_terminal/src/term/mod.rs
@@ -3,7 +3,6 @@
use std::cmp::{max, min};
use std::ops::{Index, IndexMut, Range};
use std::sync::Arc;
-use std::time::{Duration, Instant};
use std::{io, mem, ptr, str};
use bitflags::bitflags;
@@ -14,27 +13,18 @@ use unicode_width::UnicodeWidthChar;
use crate::ansi::{
self, Attr, CharsetIndex, Color, CursorShape, CursorStyle, Handler, NamedColor, StandardCharset,
};
-use crate::config::{BellAnimation, BellConfig, Config};
+use crate::config::Config;
use crate::event::{Event, EventListener};
-use crate::grid::{Dimensions, Grid, IndexRegion, Scroll};
+use crate::grid::{Dimensions, DisplayIter, Grid, Scroll};
use crate::index::{self, Boundary, Column, Direction, IndexRange, Line, Point, Side};
use crate::selection::{Selection, SelectionRange};
use crate::term::cell::{Cell, Flags, LineLength};
-use crate::term::color::Rgb;
-use crate::term::render::RenderableContent;
-use crate::term::search::RegexSearch;
+use crate::term::color::{Colors, Rgb};
use crate::vi_mode::{ViModeCursor, ViMotion};
pub mod cell;
pub mod color;
-pub mod render;
-mod search;
-
-/// Max size of the window title stack.
-const TITLE_STACK_MAX_DEPTH: usize = 4096;
-
-/// Default tab interval, corresponding to terminfo `it` value.
-const INITIAL_TABSTOPS: usize = 8;
+pub mod search;
/// Minimum number of columns.
///
@@ -44,6 +34,12 @@ pub const MIN_COLS: usize = 2;
/// Minimum number of visible lines.
pub const MIN_SCREEN_LINES: usize = 1;
+/// Max size of the window title stack.
+const TITLE_STACK_MAX_DEPTH: usize = 4096;
+
+/// Default tab interval, corresponding to terminfo `it` value.
+const INITIAL_TABSTOPS: usize = 8;
+
bitflags! {
pub struct TermMode: u32 {
const NONE = 0;
@@ -79,126 +75,6 @@ impl Default for TermMode {
}
}
-pub struct VisualBell {
- /// Visual bell animation.
- animation: BellAnimation,
-
- /// Visual bell duration.
- duration: Duration,
-
- /// The last time the visual bell rang, if at all.
- start_time: Option<Instant>,
-}
-
-fn cubic_bezier(p0: f64, p1: f64, p2: f64, p3: f64, x: f64) -> f64 {
- (1.0 - x).powi(3) * p0
- + 3.0 * (1.0 - x).powi(2) * x * p1
- + 3.0 * (1.0 - x) * x.powi(2) * p2
- + x.powi(3) * p3
-}
-
-impl VisualBell {
- /// Ring the visual bell, and return its intensity.
- pub fn ring(&mut self) -> f64 {
- let now = Instant::now();
- self.start_time = Some(now);
- self.intensity_at_instant(now)
- }
-
- /// Get the currently intensity of the visual bell. The bell's intensity
- /// ramps down from 1.0 to 0.0 at a rate determined by the bell's duration.
- pub fn intensity(&self) -> f64 {
- self.intensity_at_instant(Instant::now())
- }
-
- /// Check whether or not the visual bell has completed "ringing".
- pub fn completed(&mut self) -> bool {
- match self.start_time {
- Some(earlier) => {
- if Instant::now().duration_since(earlier) >= self.duration {
- self.start_time = None;
- }
- false
- },
- None => true,
- }
- }
-
- /// Get the intensity of the visual bell at a particular instant. The bell's
- /// intensity ramps down from 1.0 to 0.0 at a rate determined by the bell's
- /// duration.
- pub fn intensity_at_instant(&self, instant: Instant) -> f64 {
- // If `duration` is zero, then the VisualBell is disabled; therefore,
- // its `intensity` is zero.
- if self.duration == Duration::from_secs(0) {
- return 0.0;
- }
-
- match self.start_time {
- // Similarly, if `start_time` is `None`, then the VisualBell has not
- // been "rung"; therefore, its `intensity` is zero.
- None => 0.0,
-
- Some(earlier) => {
- // Finally, if the `instant` at which we wish to compute the
- // VisualBell's `intensity` occurred before the VisualBell was
- // "rung", then its `intensity` is also zero.
- if instant < earlier {
- return 0.0;
- }
-
- let elapsed = instant.duration_since(earlier);
- let elapsed_f =
- elapsed.as_secs() as f64 + f64::from(elapsed.subsec_nanos()) / 1e9f64;
- let duration_f = self.duration.as_secs() as f64
- + f64::from(self.duration.subsec_nanos()) / 1e9f64;
-
- // Otherwise, we compute a value `time` from 0.0 to 1.0
- // inclusive that represents the ratio of `elapsed` time to the
- // `duration` of the VisualBell.
- let time = (elapsed_f / duration_f).min(1.0);
-
- // We use this to compute the inverse `intensity` of the
- // VisualBell. When `time` is 0.0, `inverse_intensity` is 0.0,
- // and when `time` is 1.0, `inverse_intensity` is 1.0.
- let inverse_intensity = match self.animation {
- BellAnimation::Ease | BellAnimation::EaseOut => {
- cubic_bezier(0.25, 0.1, 0.25, 1.0, time)
- },
- BellAnimation::EaseOutSine => cubic_bezier(0.39, 0.575, 0.565, 1.0, time),
- BellAnimation::EaseOutQuad => cubic_bezier(0.25, 0.46, 0.45, 0.94, time),
- BellAnimation::EaseOutCubic => cubic_bezier(0.215, 0.61, 0.355, 1.0, time),
- BellAnimation::EaseOutQuart => cubic_bezier(0.165, 0.84, 0.44, 1.0, time),
- BellAnimation::EaseOutQuint => cubic_bezier(0.23, 1.0, 0.32, 1.0, time),
- BellAnimation::EaseOutExpo => cubic_bezier(0.19, 1.0, 0.22, 1.0, time),
- BellAnimation::EaseOutCirc => cubic_bezier(0.075, 0.82, 0.165, 1.0, time),
- BellAnimation::Linear => time,
- };
-
- // Since we want the `intensity` of the VisualBell to decay over
- // `time`, we subtract the `inverse_intensity` from 1.0.
- 1.0 - inverse_intensity
- },
- }
- }
-
- pub fn update_config<C>(&mut self, config: &Config<C>) {
- let bell_config = config.bell();
- self.animation = bell_config.animation;
- self.duration = bell_config.duration();
- }
-}
-
-impl From<&BellConfig> for VisualBell {
- fn from(bell_config: &BellConfig) -> VisualBell {
- VisualBell {
- animation: bell_config.animation,
- duration: bell_config.duration(),
- start_time: None,
- }
- }
-}
-
/// Terminal size info.
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
pub struct SizeInfo {
@@ -287,7 +163,7 @@ impl SizeInfo {
Point {
line: min(line, Line(self.screen_lines.saturating_sub(1))),
- col: min(col, Column(self.cols.saturating_sub(1))),
+ column: min(col, Column(self.cols.saturating_sub(1))),
}
}
@@ -339,12 +215,6 @@ impl SizeInfo {
}
pub struct Term<T> {
- /// Terminal requires redraw.
- pub dirty: bool,
-
- /// Visual bell configuration and status.
- pub visual_bell: VisualBell,
-
/// Terminal focus controlling the cursor shape.
pub is_focused: bool,
@@ -381,14 +251,8 @@ pub struct Term<T> {
semantic_escape_chars: String,
- /// Colors used for rendering.
- colors: color::List,
-
- /// Is color in `colors` modified or not.
- color_modified: [bool; color::COUNT],
-
- /// Original colors from config.
- original_colors: color::List,
+ /// Modified terminal colors.
+ colors: Colors,
/// Current style of the cursor.
cursor_style: Option<CursorStyle>,
@@ -409,9 +273,6 @@ pub struct Term<T> {
/// term is set.
title_stack: Vec<Option<String>>,
- /// Current forward and backward buffer search regexes.
- regex_search: Option<RegexSearch>,
-
/// Information about cell dimensions.
cell_width: usize,
cell_height: usize,
@@ -425,7 +286,6 @@ impl<T> Term<T> {
{
self.grid.scroll_display(scroll);
self.event_proxy.send_event(Event::MouseCursorDirty);
- self.dirty = true;
}
pub fn new<C>(config: &Config<C>, size: SizeInfo, event_proxy: T) -> Term<T> {
@@ -440,11 +300,7 @@ impl<T> Term<T> {
let scroll_region = Line(0)..grid.screen_lines();
- let colors = color::List::from(&config.colors);
-
Term {
- dirty: false,
- visual_bell: config.bell().into(),
grid,
inactive_grid: alt,
active_charset: Default::default(),
@@ -452,9 +308,7 @@ impl<T> Term<T> {
tabs,
mode: Default::default(),
scroll_region,
- colors,
- color_modified: [false; color::COUNT],
- original_colors: colors,
+ colors: color::Colors::default(),
semantic_escape_chars: config.selection.semantic_escape_chars.to_owned(),
cursor_style: None,
default_cursor_style: config.cursor.style(),
@@ -464,7 +318,6 @@ impl<T> Term<T> {
title: None,
title_stack: Vec::new(),
selection: None,
- regex_search: None,
cell_width: size.cell_width as usize,
cell_height: size.cell_height as usize,
}
@@ -475,15 +328,6 @@ impl<T> Term<T> {
T: EventListener,
{
self.semantic_escape_chars = config.selection.semantic_escape_chars.to_owned();
- self.original_colors.fill_named(&config.colors);
- self.original_colors.fill_cube(&config.colors);
- self.original_colors.fill_gray_ramp(&config.colors);
- for i in 0..color::COUNT {
- if !self.color_modified[i] {
- self.colors[i] = self.original_colors[i];
- }
- }
- self.visual_bell.update_config(config);
self.default_cursor_style = config.cursor.style();
self.vi_mode_cursor_style = config.cursor.vi_mode_style();
@@ -510,14 +354,14 @@ impl<T> Term<T> {
if is_block {
for line in (end.line + 1..=start.line).rev() {
- res += &self.line_to_string(line, start.col..end.col, start.col.0 != 0);
+ res += &self.line_to_string(line, start.column..end.column, start.column.0 != 0);
// If the last column is included, newline is appended automatically.
- if end.col != self.cols() - 1 {
+ if end.column != self.cols() - 1 {
res += "\n";
}
}
- res += &self.line_to_string(end.line, start.col..end.col, true);
+ res += &self.line_to_string(end.line, start.column..end.column, true);
} else {
res = self.bounds_to_string(start, end);
}
@@ -530,8 +374,8 @@ impl<T> Term<T> {
let mut res = String::new();
for line in (end.line..=start.line).rev() {
- let start_col = if line == start.line { start.col } else { Column(0) };
- let end_col = if line == end.line { end.col } else { self.cols() - 1 };
+ let start_col = if line == start.line { start.column } else { Column(0) };
+ let end_col = if line == end.line { end.column } else { self.cols() - 1 };
res += &self.line_to_string(line, start_col..end_col, line == end.line);
}
@@ -603,10 +447,20 @@ impl<T> Term<T> {
text
}
+ #[inline]
pub fn visible_to_buffer(&self, point: Point) -> Point<usize> {
self.grid.visible_to_buffer(point)
}
+ /// Terminal content required for rendering.
+ #[inline]
+ pub fn renderable_content(&self) -> RenderableContent<'_>
+ where
+ T: EventListener,
+ {
+ RenderableContent::new(self)
+ }
+
/// Access to the raw grid data structure.
///
/// This is a bit of a hack; when the window is closed, the event processor
@@ -621,43 +475,6 @@ impl<T> Term<T> {
&mut self.grid
}
- /// Terminal content required for rendering.
- ///
- /// A renderable cell is any cell which has content other than the default background color.
- /// Cells with an alternate background color are considered renderable, as are cells with any
- /// text content.
- ///
- /// The cursor itself is always considered renderable and provided separately.
- pub fn renderable_content<'b, C>(
- &'b self,
- config: &'b Config<C>,
- show_cursor: bool,
- ) -> RenderableContent<'_, T, C> {
- RenderableContent::new(&self, config, show_cursor)
- }
-
- /// Get the selection within the viewport.
- pub fn visible_selection(&self) -> Option<SelectionRange<Line>> {
- let selection = self.selection.as_ref()?.to_range(self)?;
-
- // Set horizontal limits for block selection.
- let (limit_start, limit_end) = if selection.is_block {
- (selection.start.col, selection.end.col)
- } else {
- (Column(0), self.cols() - 1)
- };
-
- let range = self.grid.clamp_buffer_range_to_visible(&(selection.start..=selection.end))?;
- let mut start = *range.start();
- let mut end = *range.end();
-
- // Trim start/end with partially visible block selection.
- start.col = max(limit_start, start.col);
- end.col = min(limit_end, end.col);
-
- Some(SelectionRange::new(start, end, selection.is_block))
- }
-
/// Resize terminal to new dimensions.
pub fn resize(&mut self, size: SizeInfo) {
self.cell_width = size.cell_width as usize;
@@ -699,7 +516,7 @@ impl<T> Term<T> {
self.inactive_grid.resize(is_alt, num_lines, num_cols);
// Clamp vi cursor to viewport.
- self.vi_mode_cursor.point.col = min(self.vi_mode_cursor.point.col, num_cols - 1);
+ self.vi_mode_cursor.point.column = min(self.vi_mode_cursor.point.column, num_cols - 1);
self.vi_mode_cursor.point.line = min(self.vi_mode_cursor.point.line, num_lines - 1);
// Reset scrolling region.
@@ -722,8 +539,7 @@ impl<T> Term<T> {
self.grid.saved_cursor = self.grid.cursor.clone();
// Reset alternate screen contents.
- let bg = self.inactive_grid.cursor.template.bg;
- self.inactive_grid.region_mut(..).each(|cell| *cell = bg.into());
+ self.inactive_grid.reset_region(..);
}
mem::swap(&mut self.grid, &mut self.inactive_grid);
@@ -731,6 +547,28 @@ impl<T> Term<T> {
self.selection = None;
}
+ /// Get the selection within the viewport.
+ fn visible_selection(&self) -> Option<SelectionRange<Line>> {
+ let selection = self.selection.as_ref()?.to_range(self)?;
+
+ // Set horizontal limits for block selection.
+ let (limit_start, limit_end) = if selection.is_block {
+ (selection.start.column, selection.end.column)
+ } else {
+ (Column(0), self.cols() - 1)
+ };
+
+ let range = self.grid.clamp_buffer_range_to_visible(&(selection.start..=selection.end))?;
+ let mut start = *range.start();
+ let mut end = *range.end();
+
+ // Trim start/end with partially visible block selection.
+ start.column = max(limit_start, start.column);
+ end.column = min(limit_end, end.column);
+
+ Some(SelectionRange::new(start, end, selection.is_block))
+ }
+
/// Scroll screen down.
///
/// Text moves down; clear at bottom
@@ -789,13 +627,7 @@ impl<T> Term<T> {
self.set_scrolling_region(1, None);
// Clear grid.
- let bg = self.grid.cursor.template.bg;
- self.grid.region_mut(..).each(|cell| *cell = bg.into());
- }
-
- #[inline]
- pub fn background_color(&self) -> Rgb {
- self.colors[NamedColor::Background]
+ self.grid.reset_region(..);
}
#[inline]
@@ -814,26 +646,15 @@ impl<T> Term<T> {
{
self.mode ^= TermMode::VI;
- let vi_mode = self.mode.contains(TermMode::VI);
-
- // Do not clear selection when entering search.
- if self.regex_search.is_none() || !vi_mode {
- self.selection = None;
- }
-
- if vi_mode {
+ if self.mode.contains(TermMode::VI) {
// Reset vi mode cursor position to match primary cursor.
let cursor = self.grid.cursor.point;
let line = min(cursor.line + self.grid.display_offset(), self.screen_lines() - 1);
- self.vi_mode_cursor = ViModeCursor::new(Point::new(line, cursor.col));
- } else {
- self.cancel_search();
+ self.vi_mode_cursor = ViModeCursor::new(Point::new(line, cursor.column));
}
// Update UI about cursor blinking state changes.
self.event_proxy.send_event(Event::CursorBlinkingChange(self.cursor_style().blinking));
-
- self.dirty = true;
}
/// Move vi mode cursor.
@@ -850,8 +671,6 @@ impl<T> Term<T> {
// Move cursor.
self.vi_mode_cursor = self.vi_mode_cursor.motion(self, motion);
self.vi_mode_recompute_selection();
-
- self.dirty = true;
}
/// Move vi cursor to absolute point in grid.
@@ -867,8 +686,6 @@ impl<T> Term<T> {
self.vi_mode_cursor.point = self.grid.clamp_buffer_to_visible(point);
self.vi_mode_recompute_selection();
-
- self.dirty = true;
}
/// Update the active selection to match the vi mode cursor position.
@@ -910,17 +727,17 @@ impl<T> Term<T> {
/// Jump to the end of a wide cell.
pub fn expand_wide(&self, mut point: Point<usize>, direction: Direction) -> Point<usize> {
- let flags = self.grid[point.line][point.col].flags;
+ let flags = self.grid[point.line][point.column].flags;
match direction {
Direction::Right if flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) => {
- point.col = Column(1);
+ point.column = Column(1);
point.line -= 1;
},
- Direction::Right if flags.contains(Flags::WIDE_CHAR) => point.col += 1,
+ Direction::Right if flags.contains(Flags::WIDE_CHAR) => point.column += 1,
Direction::Left if flags.intersects(Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER) => {
if flags.contains(Flags::WIDE_CHAR_SPACER) {
- point.col -= 1;
+ point.column -= 1;
}
let prev = point.sub_absolute(self, Boundary::Clamp, 1);
@@ -973,16 +790,13 @@ impl<T> Term<T> {
self.grid.cursor.point.line += 1;
}
- self.grid.cursor.point.col = Column(0);
+ self.grid.cursor.point.column = Column(0);
self.grid.cursor.input_needs_wrap = false;
}
/// Write `c` to the cell at the cursor position.
#[inline(always)]
- fn write_at_cursor(&mut self, c: char) -> &mut Cell
- where
- T: EventListener,
- {
+ fn write_at_cursor(&mut self, c: char) -> &mut Cell {
let c = self.grid.cursor.charsets[self.active_charset].map(c);
let fg = self.grid.cursor.template.fg;
let bg = self.grid.cursor.template.bg;
@@ -1031,7 +845,7 @@ impl<T: EventListener> Handler for Term<T> {
// Handle zero-width characters.
if width == 0 {
// Get previous column.
- let mut col = self.grid.cursor.point.col.0;
+ let mut col = self.grid.cursor.point.column.0;
if !self.grid.cursor.input_needs_wrap {
col = col.saturating_sub(1);
}
@@ -1054,9 +868,10 @@ impl<T: EventListener> Handler for Term<T> {
let num_cols = self.cols();
// If in insert mode, first shift cells to the right.
- if self.mode.contains(TermMode::INSERT) && self.grid.cursor.point.col + width < num_cols {
+ if self.mode.contains(TermMode::INSERT) && self.grid.cursor.point.column + width < num_cols
+ {
let line = self.grid.cursor.point.line;
- let col = self.grid.cursor.point.col;
+ let col = self.grid.cursor.point.column;
let row = &mut self.grid[line][..];
for col in (col.0..(num_cols - width).0).rev() {
@@ -1067,7 +882,7 @@ impl<T: EventListener> Handler for Term<T> {
if width == 1 {
self.write_at_cursor(c);
} else {
- if self.grid.cursor.point.col + 1 >= num_cols {
+ if self.grid.cursor.point.column + 1 >= num_cols {
if self.mode.contains(TermMode::LINE_WRAP) {
// Insert placeholder before wide char if glyph does not fit in this row.
self.write_at_cursor(' ').flags.insert(Flags::LEADING_WIDE_CHAR_SPACER);
@@ -1083,12 +898,12 @@ impl<T: EventListener> Handler for Term<T> {
self.write_at_cursor(c).flags.insert(Flags::WIDE_CHAR);
// Write spacer to cell following the wide glyph.
- self.grid.cursor.point.col += 1;
+ self.grid.cursor.point.column += 1;
self.write_at_cursor(' ').flags.insert(Flags::WIDE_CHAR_SPACER);
}
- if self.grid.cursor.point.col + 1 < num_cols {
- self.grid.cursor.point.col += 1;
+ if self.grid.cursor.point.column + 1 < num_cols {
+ self.grid.cursor.point.column += 1;
} else {
self.grid.cursor.input_needs_wrap = true;
}
@@ -1098,10 +913,13 @@ impl<T: EventListener> Handler for Term<T> {
fn decaln(&mut self) {
trace!("Decalnning");
- self.grid.region_mut(..).each(|cell| {
- *cell = Cell::default();
- cell.c = 'E';
- });
+ for line in 0..self.screen_lines().0 {
+ for column in 0..self.cols().0 {
+ let cell = &mut self.grid[line][Column(column)];
+ *cell = Cell::default();
+ cell.c = 'E';
+ }
+ }
}
#[inline]
@@ -1114,14 +932,14 @@ impl<T: EventListener> Handler for Term<T> {
};
self.grid.cursor.point.line = min(line + y_offset, max_y);
- self.grid.cursor.point.col = min(col, self.cols() - 1);
+ self.grid.cursor.point.column = min(col, self.cols() - 1);
self.grid.cursor.input_needs_wrap = false;
}
#[inline]
fn goto_line(&mut self, line: Line) {
trace!("Going to line: {}", line);
- self.goto(line, self.grid.cursor.point.col)
+ self.goto(line, self.grid.cursor.point.column)
}
#[inline]
@@ -1136,10 +954,10 @@ impl<T: EventListener> Handler for Term<T> {
let bg = cursor.template.bg;
// Ensure inserting within terminal bounds
- let count = min(count, self.cols() - cursor.point.col);
+ let count = min(count, self.cols() - cursor.point.column);
- let source = cursor.point.col;
- let destination = cursor.point.col + count;
+ let source = cursor.point.column;
+ let destination = cursor.point.column + count;
let num_cells = (self.cols() - destination).0;
let line = cursor.point.line;
@@ -1160,28 +978,29 @@ impl<T: EventListener> Handler for Term<T> {
fn move_up(&mut self, lines: Line) {
trace!("Moving up: {}", lines);
let move_to = Line(self.grid.cursor.point.line.0.saturating_sub(lines.0));
- self.goto(move_to, self.grid.cursor.point.col)
+ self.goto(move_to, self.grid.cursor.point.column)
}
#[inline]
fn move_down(&mut self, lines: Line) {
trace!("Moving down: {}", lines);
let move_to = self.grid.cursor.point.line + lines;
- self.goto(move_to, self.grid.cursor.point.col)
+ self.goto(move_to, self.grid.cursor.point.column)
}
#[inline]
fn move_forward(&mut self, cols: Column) {
trace!("Moving forward: {}", cols);
let num_cols = self.cols();
- self.grid.cursor.point.col = min(self.grid.cursor.point.col + cols, num_cols - 1);
+ self.grid.cursor.point.column = min(self.grid.cursor.point.column + cols, num_cols - 1);
self.grid.cursor.input_needs_wrap = false;
}
#[inline]
fn move_backward(&mut self, cols: Column) {
trace!("Moving backward: {}", cols);
- self.grid.cursor.point.col = Column(self.grid.cursor.point.col.saturating_sub(cols.0));
+ self.grid.cursor.point.column =
+ Column(self.grid.cursor.point.column.saturating_sub(cols.0));
self.grid.cursor.input_needs_wrap = false;
}
@@ -1210,7 +1029,7 @@ impl<T: EventListener> Handler for Term<T> {
},
6 => {
let pos = self.grid.cursor.point;
- let response = format!("\x1b[{};{}R", pos.line + 1, pos.col + 1);
+ let response = format!("\x1b[{};{}R", pos.line + 1, pos.column + 1);
let _ = writer.write_all(response.as_bytes());
},
_ => debug!("unknown device status query: {}", arg),
@@ -1240,7 +1059,7 @@ impl<T: EventListener> Handler for Term<T> {
return;
}
- while self.grid.cursor.point.col < self.cols() && count != 0 {
+ while self.grid.cursor.point.column < self.cols() && count != 0 {
count -= 1;
let c = self.grid.cursor.charsets[self.active_charset].map('\t');
@@ -1250,13 +1069,13 @@ impl<T: EventListener> Handler for Term<T> {
}
loop {
- if (self.grid.cursor.point.col + 1) == self.cols() {
+ if (self.grid.cursor.point.column + 1) == self.cols() {
break;
}
- self.grid.cursor.point.col += 1;
+ self.grid.cursor.point.column += 1;
- if self.tabs[self.grid.cursor.point.col] {
+ if self.tabs[self.grid.cursor.point.column] {
break;
}
}
@@ -1268,8 +1087,8 @@ impl<T: EventListener> Handler for Term<T> {
fn backspace(&mut self) {
trace!("Backspace");
- if self.grid.cursor.point.col > Column(0) {
- self.grid.cursor.point.col -= 1;
+ if self.grid.cursor.point.column > Column(0) {
+ self.grid.cursor.point.column -= 1;
self.grid.cursor.input_needs_wrap = false;
}
}
@@ -1278,7 +1097,7 @@ impl<T: EventListener> Handler for Term<T> {
#[inline]
fn carriage_return(&mut self) {
trace!("Carriage return");
- self.grid.cursor.point.col = Column(0);
+ self.grid.cursor.point.column = Column(0);
self.grid.cursor.input_needs_wrap = false;
}
@@ -1298,7 +1117,6 @@ impl<T: EventListener> Handler for Term<T> {
#[inline]
fn bell(&mut self) {
trace!("Bell");
- self.visual_bell.ring();
self.event_proxy.send_event(Event::Bell);
}
@@ -1341,7 +1159,7 @@ impl<T: EventListener> Handler for Term<T> {
#[inline]
fn set_horizontal_tabstop(&mut self) {
trace!("Setting horizontal tabstop");
- self.tabs[self.grid.cursor.point.col] = true;
+ self.tabs[self.grid.cursor.point.column] = true;
}
#[inline]
@@ -1382,9 +1200,9 @@ impl<T: EventListener> Handler for Term<T> {
fn erase_chars(&mut self, count: Column) {
let cursor = &self.grid.cursor;
- trace!("Erasing chars: count={}, col={}", count, cursor.point.col);
+ trace!("Erasing chars: count={}, col={}", count, cursor.point.column);
- let start = cursor.point.col;
+ let start = cursor.point.column;
let end = min(start + count, self.cols());
// Cleared cells have current background color set.
@@ -1405,7 +1223,7 @@ impl<T: EventListener> Handler for Term<T> {
// Ensure deleting within terminal bounds.
let count = min(count, cols);
- let start = cursor.point.col;
+ let start = cursor.point.column;
let end = min(start + count, cols - 1);
let num_cells = (cols - end).0;
@@ -1429,14 +1247,14 @@ impl<T: EventListener> Handler for Term<T> {
trace!("Moving backward {} tabs", count);
for _ in 0..count {
- let mut col = self.grid.cursor.point.col;
+ let mut col = self.grid.cursor.point.column;
for i in (0..(col.0)).rev() {
if self.tabs[index::Column(i)] {
col = index::Column(i);
break;
}
}
- self.grid.cursor.point.col = col;
+ self.grid.cursor.point.column = col;
}
}
@@ -1471,12 +1289,12 @@ impl<T: EventListener> Handler for Term<T> {
match mode {
ansi::LineClearMode::Right => {
- for cell in &mut row[point.col..] {
+ for cell in &mut row[point.column..] {
*cell = bg.into();
}
},
ansi::LineClearMode::Left => {
- for cell in &mut row[..=point.col] {
+ for cell in &mut row[..=point.column] {
*cell = bg.into();
}
},
@@ -1498,34 +1316,31 @@ impl<T: EventListener> Handler for Term<T> {
#[inline]
fn set_color(&mut self, index: usize, color: Rgb) {
trace!("Setting color[{}] = {:?}", index, color);
- self.colors[index] = color;
- self.color_modified[index] = true;
+ self.colors[index] = Some(color);
}
/// Write a foreground/background color escape sequence with the current color.
#[inline]
- fn dynamic_color_sequence<W: io::Write>(
- &mut self,
- writer: &mut W,
- code: u8,
- index: usize,
- terminator: &str,
- ) {
- trace!("Writing escape sequence for dynamic color code {}: color[{}]", code, index);
- let color = self.colors[index];
- let response = format!(
- "\x1b]{};rgb:{1:02x}{1:02x}/{2:02x}{2:02x}/{3:02x}{3:02x}{4}",
- code, color.r, color.g, color.b, terminator
- );
- let _ = writer.write_all(response.as_bytes());
+ fn dynamic_color_sequence(&mut self, code: u8, index: usize, terminator: &str) {
+ trace!("Requested write of escape sequence for color code {}: color[{}]", code, index);
+
+ let terminator = terminator.to_owned();
+ self.event_proxy.send_event(Event::ColorRequest(
+ index,
+ Arc::new(move |color| {
+ format!(
+ "\x1b]{};rgb:{1:02x}{1:02x}/{2:02x}{2:02x}/{3:02x}{3:02x}{4}",
+ code, color.r, color.g, color.b, terminator
+ )
+ }),
+ ));
}
/// Reset the indexed color to original value.
#[inline]
fn reset_color(&mut self, index: usize) {
trace!("Resetting color[{}]", index);
- self.colors[index] = self.original_colors[index];
- self.color_modified[index] = false;
+ self.colors[index] = None;
}
/// Store data into clipboard.
@@ -1579,11 +1394,11 @@ impl<T: EventListener> Handler for Term<T> {
// If clearing more than one line.
if cursor.line > Line(1) {
// Fully clear all lines before the current line.
- self.grid.region_mut(..cursor.line).each(|cell| *cell = bg.into());
+ self.grid.reset_region(..cursor.line);
}
// Clear up to the current column in the current line.
- let end = min(cursor.col + 1, self.cols());
+ let end = min(cursor.column + 1, self.cols());
for cell in &mut self.grid[cursor.line][..end] {
*cell = bg.into();
}
@@ -1595,12 +1410,12 @@ impl<T: EventListener> Handler for Term<T> {
},
ansi::ClearMode::Below => {
let cursor = self.grid.cursor.point;
- for cell in &mut self.grid[cursor.line][cursor.col..] {
+ for cell in &mut self.grid[cursor.line][cursor.column..] {
*cell = bg.into();
}
if cursor.line.0 < num_lines - 1 {
- self.grid.region_mut((cursor.line + 1)..).each(|cell| *cell = bg.into());
+ self.grid.reset_region((cursor.line + 1)..);
}
self.selection =
@@ -1608,7 +1423,7 @@ impl<T: EventListener> Handler for Term<T> {
},
ansi::ClearMode::All => {
if self.mode.contains(TermMode::ALT_SCREEN) {
- self.grid.region_mut(..).each(|cell| *cell = bg.into());
+ self.grid.reset_region(..);
} else {
self.grid.clear_viewport();
}
@@ -1630,7 +1445,7 @@ impl<T: EventListener> Handler for Term<T> {
trace!("Clearing tabs: {:?}", mode);
match mode {
ansi::TabulationClearMode::Current => {
- self.tabs[self.grid.cursor.point.col] = false;
+ self.tabs[self.grid.cursor.point.column] = false;
},
ansi::TabulationClearMode::All => {
self.tabs.clear_all();
@@ -1645,8 +1460,6 @@ impl<T: EventListener> Handler for Term<T> {
mem::swap(&mut self.grid, &mut self.inactive_grid);
}
self.active_charset = Default::default();
- self.colors = self.original_colors;
- self.color_modified = [false; color::COUNT];
self.cursor_style = None;
self.grid.reset();
self.inactive_grid.reset();
@@ -1655,7 +1468,6 @@ impl<T: EventListener> Handler for Term<T> {
self.title_stack = Vec::new();
self.title = None;
self.selection = None;
- self.regex_search = None;
// Preserve vi mode across resets.
self.mode &= TermMode::VI;
@@ -2006,6 +1818,59 @@ impl IndexMut<Column> for TabStops {
}
}
+/// Terminal cursor rendering information.
+#[derive(Copy, Clone)]
+pub struct RenderableCursor {
+ pub shape: CursorShape,
+ pub point: Point,
+}
+
+impl RenderableCursor {
+ fn new<T>(term: &Term<T>) -> Self {
+ // Cursor position.
+ let vi_mode = term.mode().contains(TermMode::VI);
+ let point = if vi_mode { term.vi_mode_cursor.point } else { term.grid().cursor.point };
+
+ // Cursor shape.
+ let absolute_line = term.screen_lines() - point.line - 1;
+ let display_offset = term.grid().display_offset();
+ let shape = if !vi_mode
+ && (!term.mode().contains(TermMode::SHOW_CURSOR) || absolute_line.0 < display_offset)
+ {
+ CursorShape::Hidden
+ } else {
+ term.cursor_style().shape
+ };
+
+ Self { shape, point }
+ }
+}
+
+/// Visible terminal content.
+///
+/// This contains all content required to render the current terminal view.
+pub struct RenderableContent<'a> {
+ pub display_iter: DisplayIter<'a, Cell>,
+ pub selection: Option<SelectionRange<Line>>,
+ pub cursor: RenderableCursor,
+ pub display_offset: usize,
+ pub colors: &'a color::Colors,
+ pub mode: TermMode,
+}
+
+impl<'a> RenderableContent<'a> {
+ fn new<T>(term: &'a Term<T>) -> Self {
+ Self {
+ display_iter: term.grid().display_iter(),
+ display_offset: term.grid().display_offset(),
+ cursor: RenderableCursor::new(term),
+ selection: term.visible_selection(),
+ colors: &term.colors,
+ mode: *term.mode(),
+ }
+ }
+}
+
/// Terminal test helpers.
pub mod test {
use super::*;
@@ -2079,21 +1944,15 @@ mod tests {
use crate::ansi::{self, CharsetIndex, Handler, StandardCharset};
use crate::config::MockConfig;
- use crate::event::{Event, EventListener};
use crate::grid::{Grid, Scroll};
use crate::index::{Column, Line, Point, Side};
use crate::selection::{Selection, SelectionType};
use crate::term::cell::{Cell, Flags};
- struct Mock;
- impl EventListener for Mock {
- fn send_event(&self, _event: Event) {}
- }
-
#[test]
fn semantic_selection_works() {
let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false);
- let mut term = Term::new(&MockConfig::default(), size, Mock);
+ let mut term = Term::new(&MockConfig::default(), size, ());
let mut grid: Grid<Cell> = Grid::new(Line(3), Column(5), 0);
for i in 0..5 {
for j in 0..2 {
@@ -2113,7 +1972,7 @@ mod tests {
{
term.selection = Some(Selection::new(
SelectionType::Semantic,
- Point { line: 2, col: Column(1) },
+ Point { line: 2, column: Column(1) },
Side::Left,
));
assert_eq!(term.selection_to_string(), Some(String::from("aa")));
@@ -2122,7 +1981,7 @@ mod tests {
{
term.selection = Some(Selection::new(
SelectionType::Semantic,
- Point { line: 2, col: Column(4) },
+ Point { line: 2, column: Column(4) },
Side::Left,
));
assert_eq!(term.selection_to_string(), Some(String::from("aaa")));
@@ -2131,7 +1990,7 @@ mod tests {
{
term.selection = Some(Selection::new(
SelectionType::Semantic,
- Point { line: 1, col: Column(1) },
+ Point { line: 1, column: Column(1) },
Side::Left,
));
assert_eq!(term.selection_to_string(), Some(String::from("aaa")));
@@ -2141,7 +2000,7 @@ mod tests {
#[test]
fn line_selection_works() {
let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false);
- let mut term = Term::new(&MockConfig::default(), size, Mock);
+ let mut term = Term::new(&MockConfig::default(), size, ());
let mut grid: Grid<Cell> = Grid::new(Line(1), Column(5), 0);
for i in 0..5 {
grid[Line(0)][Column(i)].c = 'a';
@@ -2153,7 +2012,7 @@ mod tests {
term.selection = Some(Selection::new(
SelectionType::Lines,
- Point { line: 0, col: Column(3) },
+ Point { line: 0, column: Column(3) },
Side::Left,
));
assert_eq!(term.selection_to_string(), Some(String::from("\"aa\"a\n")));
@@ -2162,7 +2021,7 @@ mod tests {
#[test]
fn selecting_empty_line() {
let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false);
- let mut term = Term::new(&MockConfig::default(), size, Mock);
+ let mut term = Term::new(&MockConfig::default(), size, ());
let mut grid: Grid<Cell> = Grid::new(Line(3), Column(3), 0);
for l in 0..3 {
if l != 1 {
@@ -2175,8 +2034,8 @@ mod tests {
mem::swap(&mut term.grid, &mut grid);
let mut selection =
- Selection::new(SelectionType::Simple, Point { line: 2, col: Column(0) }, Side::Left);
- selection.update(Point { line: 0, col: Column(2) }, Side::Right);
+ Selection::new(SelectionType::Simple, Point { line: 2, column: Column(0) }, Side::Left);
+ selection.update(Point { line: 0, column: Column(2) }, Side::Right);
term.selection = Some(selection);
assert_eq!(term.selection_to_string(), Some("aaa\n\naaa\n".into()));
}
@@ -2197,18 +2056,18 @@ mod tests {
#[test]
fn input_line_drawing_character() {
let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false);
- let mut term = Term::new(&MockConfig::default(), size, Mock);
+ let mut term = Term::new(&MockConfig::default(), size, ());
let cursor = Point::new(Line(0), Column(0));
term.configure_charset(CharsetIndex::G0, StandardCharset::SpecialCharacterAndLineDrawing);
term.input('a');
- assert_eq!(term.grid()[&cursor].c, '▒');
+ assert_eq!(term.grid()[cursor].c, '▒');
}
#[test]
fn clear_saved_lines() {
let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false);
- let mut term = Term::new(&MockConfig::default(), size, Mock);
+ let mut term = Term::new(&MockConfig::default(), size, ());
// Add one line of scrollback.
term.grid.scroll_up(&(Line(0)..Line(1)), Line(1));
@@ -2230,7 +2089,7 @@ mod tests {
#[test]
fn grow_lines_updates_active_cursor_pos() {
let mut size = SizeInfo::new(100.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
- let mut term = Term::new(&MockConfig::default(), size, Mock);
+ let mut term = Term::new(&MockConfig::default(), size, ());
// Create 10 lines of scrollback.
for _ in 0..19 {
@@ -2250,7 +2109,7 @@ mod tests {
#[test]
fn grow_lines_updates_inactive_cursor_pos() {
let mut size = SizeInfo::new(100.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
- let mut term = Term::new(&MockConfig::default(), size, Mock);
+ let mut term = Term::new(&MockConfig::default(), size, ());
// Create 10 lines of scrollback.
for _ in 0..19 {
@@ -2276,7 +2135,7 @@ mod tests {
#[test]
fn shrink_lines_updates_active_cursor_pos() {
let mut size = SizeInfo::new(100.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
- let mut term = Term::new(&MockConfig::default(), size, Mock);
+ let mut term = Term::new(&MockConfig::default(), size, ());
// Create 10 lines of scrollback.
for _ in 0..19 {
@@ -2296,7 +2155,7 @@ mod tests {
#[test]
fn shrink_lines_updates_inactive_cursor_pos() {
let mut size = SizeInfo::new(100.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
- let mut term = Term::new(&MockConfig::default(), size, Mock);
+ let mut term = Term::new(&MockConfig::default(), size, ());
// Create 10 lines of scrollback.
for _ in 0..19 {
@@ -2322,7 +2181,7 @@ mod tests {
#[test]
fn window_title() {
let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false);
- let mut term = Term::new(&MockConfig::default(), size, Mock);
+ let mut term = Term::new(&MockConfig::default(), size, ());
// Title None by default.
assert_eq!(term.title, None);
diff --git a/alacritty_terminal/src/term/render.rs b/alacritty_terminal/src/term/render.rs
deleted file mode 100644
index fbb5a732..00000000
--- a/alacritty_terminal/src/term/render.rs
+++ /dev/null
@@ -1,431 +0,0 @@
-use std::cmp::max;
-use std::iter;
-use std::iter::Peekable;
-use std::mem;
-use std::ops::RangeInclusive;
-
-use crate::ansi::{Color, CursorShape, NamedColor};
-use crate::config::Config;
-use crate::grid::{Dimensions, DisplayIter, Indexed};
-use crate::index::{Column, Direction, Line, Point};
-use crate::selection::SelectionRange;
-use crate::term::cell::{Cell, Flags};
-use crate::term::color::{self, CellRgb, Rgb, DIM_FACTOR};
-use crate::term::search::RegexIter;
-use crate::term::{Term, TermMode};
-
-/// Minimum contrast between a fixed cursor color and the cell's background.
-pub const MIN_CURSOR_CONTRAST: f64 = 1.5;
-
-/// Maximum number of linewraps followed outside of the viewport during search highlighting.
-const MAX_SEARCH_LINES: usize = 100;
-
-/// Renderable terminal content.
-///
-/// This provides the terminal cursor and an iterator over all non-empty cells.
-pub struct RenderableContent<'a, T, C> {
- term: &'a Term<T>,
- config: &'a Config<C>,
- display_iter: DisplayIter<'a, Cell>,
- selection: Option<SelectionRange<Line>>,
- search: RenderableSearch<'a>,
- cursor: Option<RenderableCursor>,
- cursor_shape: CursorShape,
- cursor_point: Point,
-}
-
-impl<'a, T, C> RenderableContent<'a, T, C> {
- pub fn new(term: &'a Term<T>, config: &'a Config<C>, show_cursor: bool) -> Self {
- // Cursor position.
- let vi_mode = term.mode.contains(TermMode::VI);
- let mut cursor_point = if vi_mode {
- term.vi_mode_cursor.point
- } else {
- let mut point = term.grid.cursor.point;
- point.line += term.grid.display_offset();
- point
- };
-
- // Cursor shape.
- let cursor_shape = if !show_cursor
- || (!term.mode.contains(TermMode::SHOW_CURSOR) && !vi_mode)
- || cursor_point.line >= term.screen_lines()
- {
- cursor_point.line = Line(0);
- CursorShape::Hidden
- } else if !term.is_focused && config.cursor.unfocused_hollow {
- CursorShape::HollowBlock
- } else {
- let cursor_style = term.cursor_style.unwrap_or(term.default_cursor_style);
-
- if vi_mode {
- term.vi_mode_cursor_style.unwrap_or(cursor_style).shape
- } else {
- cursor_style.shape
- }
- };
-
- Self {
- display_iter: term.grid.display_iter(),
- selection: term.visible_selection(),
- search: RenderableSearch::new(term),
- cursor: None,
- cursor_shape,
- cursor_point,
- config,
- term,
- }
- }
-
- /// Get the terminal cursor.
- pub fn cursor(mut self) -> Option<RenderableCursor> {
- // Drain the iterator to make sure the cursor is created.
- while self.next().is_some() && self.cursor.is_none() {}
-
- self.cursor
- }
-
- /// Assemble the information required to render the terminal cursor.
- ///
- /// This will return `None` when there is no cursor visible.
- fn renderable_cursor(&mut self, cell: &RenderableCell) -> Option<RenderableCursor> {
- if self.cursor_shape == CursorShape::Hidden {
- return None;
- }
-
- // Expand across wide cell when inside wide char or spacer.
- let is_wide = if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
- self.cursor_point.col -= 1;
- true
- } else {
- cell.flags.contains(Flags::WIDE_CHAR)
- };
-
- // Cursor colors.
- let color = if self.term.mode.contains(TermMode::VI) {
- self.config.colors.vi_mode_cursor
- } else {
- self.config.colors.cursor
- };
- let mut cursor_color = if self.term.color_modified[NamedColor::Cursor as usize] {
- CellRgb::Rgb(self.term.colors[NamedColor::Cursor])
- } else {
- color.background
- };
- let mut text_color = color.foreground;
-
- // Invert the cursor if it has a fixed background close to the cell's background.
- if matches!(
- cursor_color,
- CellRgb::Rgb(color) if color.contrast(cell.bg) < MIN_CURSOR_CONTRAST
- ) {
- cursor_color = CellRgb::CellForeground;
- text_color = CellRgb::CellBackground;
- }
-
- // Convert from cell colors to RGB.
- let text_color = text_color.color(cell.fg, cell.bg);
- let cursor_color = cursor_color.color(cell.fg, cell.bg);
-
- Some(RenderableCursor {
- point: self.cursor_point,
- shape: self.cursor_shape,
- cursor_color,
- text_color,
- is_wide,
- })
- }
-}
-
-impl<'a, T, C> Iterator for RenderableContent<'a, T, C> {
- type Item = RenderableCell;
-
- /// Gets the next renderable cell.
- ///
- /// Skips empty (background) cells and applies any flags to the cell state
- /// (eg. invert fg and bg colors).
- #[inline]
- fn next(&mut self) -> Option<Self::Item> {
- loop {
- if self.cursor_point == self.display_iter.point() {
- // Handle cell at cursor position.
- let cell = self.display_iter.next()?;
- let mut cell = RenderableCell::new(self, cell);
-
- // Store the cursor which should be rendered.
- self.cursor = self.renderable_cursor(&cell).map(|cursor| {
- if cursor.shape == CursorShape::Block {
- cell.fg = cursor.text_color;
- cell.bg = cursor.cursor_color;
-
- // Since we draw Block cursor by drawing cell below it with a proper color,
- // we must adjust alpha to make it visible.
- cell.bg_alpha = 1.;
- }
-
- cursor
- });
-
- return Some(cell);
- } else {
- // Handle non-cursor cells.
- let cell = self.display_iter.next()?;
- let cell = RenderableCell::new(self, cell);
-
- // Skip empty cells and wide char spacers.
- if !cell.is_empty() && !cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
- return Some(cell);
- }
- }
- }
- }
-}
-
-/// Cell ready for rendering.
-#[derive(Clone, Debug)]
-pub struct RenderableCell {
- pub character: char,
- pub zerowidth: Option<Vec<char>>,
- pub line: Line,
- pub column: Column,
- pub fg: Rgb,
- pub bg: Rgb,
- pub bg_alpha: f32,
- pub flags: Flags,
- pub is_match: bool,
-}
-
-impl RenderableCell {
- fn new<'a, T, C>(content: &mut RenderableContent<'a, T, C>, cell: Indexed<&Cell>) -> Self {
- let point = Point::new(cell.line, cell.column);
-
- // Lookup RGB values.
- let mut fg_rgb =
- Self::compute_fg_rgb(content.config, &content.term.colors, cell.fg, cell.flags);
- let mut bg_rgb = Self::compute_bg_rgb(&content.term.colors, cell.bg);
-
- let mut bg_alpha = if cell.flags.contains(Flags::INVERSE) {
- mem::swap(&mut fg_rgb, &mut bg_rgb);
- 1.0
- } else {
- Self::compute_bg_alpha(cell.bg)
- };
-
- let grid = content.term.grid();
- let is_selected = content.selection.map_or(false, |selection| {
- selection.contains_cell(grid, point, content.cursor_point, content.cursor_shape)
- });
- let mut is_match = false;
-
- if is_selected {
- let config_bg = content.config.colors.selection.background;
- let selected_fg = content.config.colors.selection.foreground.color(fg_rgb, bg_rgb);
- bg_rgb = config_bg.color(fg_rgb, bg_rgb);
- fg_rgb = selected_fg;
-
- if fg_rgb == bg_rgb && !cell.flags.contains(Flags::HIDDEN) {
- // Reveal inversed text when fg/bg is the same.
- fg_rgb = content.term.colors[NamedColor::Background];
- bg_rgb = content.term.colors[NamedColor::Foreground];
- bg_alpha = 1.0;
- } else if config_bg != CellRgb::CellBackground {
- bg_alpha = 1.0;
- }
- } else if content.search.advance(grid.visible_to_buffer(point)) {
- // Highlight the cell if it is part of a search match.
- let config_bg = content.config.colors.search.matches.background;
- let matched_fg = content.config.colors.search.matches.foreground.color(fg_rgb, bg_rgb);
- bg_rgb = config_bg.color(fg_rgb, bg_rgb);
- fg_rgb = matched_fg;
-
- if config_bg != CellRgb::CellBackground {
- bg_alpha = 1.0;
- }
-
- is_match = true;
- }
-
- RenderableCell {
- character: cell.c,
- zerowidth: cell.zerowidth().map(|zerowidth| zerowidth.to_vec()),
- line: cell.line,
- column: cell.column,
- fg: fg_rgb,
- bg: bg_rgb,
- bg_alpha,
- flags: cell.flags,
- is_match,
- }
- }
-
- /// Position of the cell.
- pub fn point(&self) -> Point {
- Point::new(self.line, self.column)
- }
-
- /// Check if cell contains any renderable content.
- fn is_empty(&self) -> bool {
- self.bg_alpha == 0.
- && !self.flags.intersects(Flags::UNDERLINE | Flags::STRIKEOUT | Flags::DOUBLE_UNDERLINE)
- && self.character == ' '
- && self.zerowidth.is_none()
- }
-
- /// Get the RGB color from a cell's foreground color.
- fn compute_fg_rgb<C>(config: &Config<C>, colors: &color::List, fg: Color, flags: Flags) -> Rgb {
- match fg {
- Color::Spec(rgb) => match flags & Flags::DIM {
- Flags::DIM => rgb * DIM_FACTOR,
- _ => rgb,
- },
- Color::Named(ansi) => {
- match (config.draw_bold_text_with_bright_colors, flags & Flags::DIM_BOLD) {
- // If no bright foreground is set, treat it like the BOLD flag doesn't exist.
- (_, Flags::DIM_BOLD)
- if ansi == NamedColor::Foreground
- && config.colors.primary.bright_foreground.is_none() =>
- {
- colors[NamedColor::DimForeground]
- },
- // Draw bold text in bright colors *and* contains bold flag.
- (true, Flags::BOLD) => colors[ansi.to_bright()],
- // Cell is marked as dim and not bold.
- (_, Flags::DIM) | (false, Flags::DIM_BOLD) => colors[ansi.to_dim()],
- // None of the above, keep original color..
- _ => colors[ansi],
- }
- },
- Color::Indexed(idx) => {
- let idx = match (
- config.draw_bold_text_with_bright_colors,
- flags & Flags::DIM_BOLD,
- idx,
- ) {
- (true, Flags::BOLD, 0..=7) => idx as usize + 8,
- (false, Flags::DIM, 8..=15) => idx as usize - 8,
- (false, Flags::DIM, 0..=7) => NamedColor::DimBlack as usize + idx as usize,
- _ => idx as usize,
- };
-
- colors[idx]
- },
- }
- }
-
- /// Get the RGB color from a cell's background color.
- #[inline]
- fn compute_bg_rgb(colors: &color::List, bg: Color) -> Rgb {
- match bg {
- Color::Spec(rgb) => rgb,
- Color::Named(ansi) => colors[ansi],
- Color::Indexed(idx) => colors[idx],
- }
- }
-
- /// Compute background alpha based on cell's original color.
- ///
- /// Since an RGB color matching the background should not be transparent, this is computed
- /// using the named input color, rather than checking the RGB of the background after its color
- /// is computed.
- #[inline]
- fn compute_bg_alpha(bg: Color) -> f32 {
- if bg == Color::Named(NamedColor::Background) {
- 0.
- } else {
- 1.
- }
- }
-}
-
-/// Cursor storing all information relevant for rendering.
-#[derive(Debug, Eq, PartialEq, Copy, Clone)]
-pub struct RenderableCursor {
- shape: CursorShape,
- cursor_color: Rgb,
- text_color: Rgb,
- is_wide: bool,
- point: Point,
-}
-
-impl RenderableCursor {
- pub fn color(&self) -> Rgb {
- self.cursor_color
- }
-
- pub fn shape(&self) -> CursorShape {
- self.shape
- }
-
- pub fn is_wide(&self) -> bool {
- self.is_wide
- }
-
- pub fn point(&self) -> Point {
- self.point
- }
-}
-
-type MatchIter<'a> = Box<dyn Iterator<Item = RangeInclusive<Point<usize>>> + 'a>;
-
-/// Regex search highlight tracking.
-struct RenderableSearch<'a> {
- iter: Peekable<MatchIter<'a>>,
-}
-
-impl<'a> RenderableSearch<'a> {
- /// Create a new renderable search iterator.
- fn new<T>(term: &'a Term<T>) -> Self {
- // Avoid constructing search if there is none.
- if term.regex_search.is_none() {
- let iter: MatchIter<'a> = Box::new(iter::empty());
- return Self { iter: iter.peekable() };
- }
-
- let viewport_end = term.grid().display_offset();
- let viewport_start = viewport_end + term.screen_lines().0 - 1;
-
- // Compute start of the first and end of the last line.
- let start_point = Point::new(viewport_start, Column(0));
- let mut start = term.line_search_left(start_point);
- let end_point = Point::new(viewport_end, term.cols() - 1);
- let mut end = term.line_search_right(end_point);
-
- // Set upper bound on search before/after the viewport to prevent excessive blocking.
- if start.line > viewport_start + MAX_SEARCH_LINES {
- if start.line == 0 {
- // Do not highlight anything if this line is the last.
- let iter: MatchIter<'a> = Box::new(iter::empty());
- return Self { iter: iter.peekable() };
- } else {
- // Start at next line if this one is too long.
- start.line -= 1;
- }
- }
- end.line = max(end.line, viewport_end.saturating_sub(MAX_SEARCH_LINES));
-
- // Create an iterater for the current regex search for all visible matches.
- let iter: MatchIter<'a> = Box::new(
- RegexIter::new(start, end, Direction::Right, &term)
- .skip_while(move |rm| rm.end().line > viewport_start)
- .take_while(move |rm| rm.start().line >= viewport_end),
- );
-
- Self { iter: iter.peekable() }
- }
-
- /// Advance the search tracker to the next point.
- ///
- /// This will return `true` if the point passed is part of a search match.
- fn advance(&mut self, point: Point<usize>) -> bool {
- while let Some(regex_match) = &self.iter.peek() {
- if regex_match.start() > &point {
- break;
- } else if regex_match.end() < &point {
- let _ = self.iter.next();
- } else {
- return true;
- }
- }
- false
- }
-}
diff --git a/alacritty_terminal/src/term/search.rs b/alacritty_terminal/src/term/search.rs
index b7aaaaba..ee0cfdcb 100644
--- a/alacritty_terminal/src/term/search.rs
+++ b/alacritty_terminal/src/term/search.rs
@@ -4,7 +4,7 @@ use std::ops::RangeInclusive;
use regex_automata::{dense, DenseDFA, Error as RegexError, DFA};
-use crate::grid::{BidirectionalIterator, Dimensions, GridIterator};
+use crate::grid::{BidirectionalIterator, Dimensions, GridIterator, Indexed};
use crate::index::{Boundary, Column, Direction, Point, Side};
use crate::term::cell::{Cell, Flags};
use crate::term::Term;
@@ -48,23 +48,10 @@ impl RegexSearch {
}
impl<T> Term<T> {
- /// Enter terminal buffer search mode.
- #[inline]
- pub fn start_search(&mut self, search: &str) {
- self.regex_search = RegexSearch::new(search).ok();
- self.dirty = true;
- }
-
- /// Cancel active terminal buffer search.
- #[inline]
- pub fn cancel_search(&mut self) {
- self.regex_search = None;
- self.dirty = true;
- }
-
/// Get next search match in the specified direction.
pub fn search_next(
&self,
+ dfas: &RegexSearch,
mut origin: Point<usize>,
direction: Direction,
side: Side,
@@ -75,14 +62,15 @@ impl<T> Term<T> {
max_lines = max_lines.filter(|max_lines| max_lines + 1 < self.total_lines());
match direction {
- Direction::Right => self.next_match_right(origin, side, max_lines),
- Direction::Left => self.next_match_left(origin, side, max_lines),
+ Direction::Right => self.next_match_right(dfas, origin, side, max_lines),
+ Direction::Left => self.next_match_left(dfas, origin, side, max_lines),
}
}
/// Find the next match to the right of the origin.
fn next_match_right(
&self,
+ dfas: &RegexSearch,
origin: Point<usize>,
side: Side,
max_lines: Option<usize>,
@@ -100,7 +88,7 @@ impl<T> Term<T> {
_ => end.sub_absolute(self, Boundary::Wrap, 1),
};
- let mut regex_iter = RegexIter::new(start, end, Direction::Right, &self).peekable();
+ let mut regex_iter = RegexIter::new(start, end, Direction::Right, &self, dfas).peekable();
// Check if there's any match at all.
let first_match = regex_iter.peek()?.clone();
@@ -112,7 +100,7 @@ impl<T> Term<T> {
// If the match's point is beyond the origin, we're done.
match_point.line > start.line
|| match_point.line < origin.line
- || (match_point.line == origin.line && match_point.col >= origin.col)
+ || (match_point.line == origin.line && match_point.column >= origin.column)
})
.unwrap_or(first_match);
@@ -122,6 +110,7 @@ impl<T> Term<T> {
/// Find the next match to the left of the origin.
fn next_match_left(
&self,
+ dfas: &RegexSearch,
origin: Point<usize>,
side: Side,
max_lines: Option<usize>,
@@ -135,7 +124,7 @@ impl<T> Term<T> {
_ => end.add_absolute(self, Boundary::Wrap, 1),
};
- let mut regex_iter = RegexIter::new(start, end, Direction::Left, &self).peekable();
+ let mut regex_iter = RegexIter::new(start, end, Direction::Left, &self, dfas).peekable();
// Check if there's any match at all.
let first_match = regex_iter.peek()?.clone();
@@ -147,7 +136,7 @@ impl<T> Term<T> {
// If the match's point is beyond the origin, we're done.
match_point.line < start.line
|| match_point.line > origin.line
- || (match_point.line == origin.line && match_point.col <= origin.col)
+ || (match_point.line == origin.line && match_point.column <= origin.column)
})
.unwrap_or(first_match);
@@ -165,12 +154,15 @@ impl<T> Term<T> {
/// Find the next regex match to the left of the origin point.
///
/// The origin is always included in the regex.
- pub fn regex_search_left(&self, start: Point<usize>, end: Point<usize>) -> Option<Match> {
- let RegexSearch { left_fdfa: fdfa, left_rdfa: rdfa, .. } = self.regex_search.as_ref()?;
-
+ pub fn regex_search_left(
+ &self,
+ dfas: &RegexSearch,
+ start: Point<usize>,
+ end: Point<usize>,
+ ) -> Option<Match> {
// Find start and end of match.
- let match_start = self.regex_search(start, end, Direction::Left, &fdfa)?;
- let match_end = self.regex_search(match_start, start, Direction::Right, &rdfa)?;
+ let match_start = self.regex_search(start, end, Direction::Left, &dfas.left_fdfa)?;
+ let match_end = self.regex_search(match_start, start, Direction::Right, &dfas.left_rdfa)?;
Some(match_start..=match_end)
}
@@ -178,12 +170,15 @@ impl<T> Term<T> {
/// Find the next regex match to the right of the origin point.
///
/// The origin is always included in the regex.
- pub fn regex_search_right(&self, start: Point<usize>, end: Point<usize>) -> Option<Match> {
- let RegexSearch { right_fdfa: fdfa, right_rdfa: rdfa, .. } = self.regex_search.as_ref()?;
-
+ pub fn regex_search_right(
+ &self,
+ dfas: &RegexSearch,
+ start: Point<usize>,
+ end: Point<usize>,
+ ) -> Option<Match> {
// Find start and end of match.
- let match_end = self.regex_search(start, end, Direction::Right, &fdfa)?;
- let match_start = self.regex_search(match_end, start, Direction::Left, &rdfa)?;
+ let match_end = self.regex_search(start, end, Direction::Right, &dfas.right_fdfa)?;
+ let match_start = self.regex_search(match_end, start, Direction::Left, &dfas.right_rdfa)?;
Some(match_start..=match_end)
}
@@ -251,10 +246,10 @@ impl<T> Term<T> {
// Advance grid cell iterator.
let mut cell = match next(&mut iter) {
- Some(cell) => cell,
+ Some(Indexed { cell, .. }) => cell,
None => {
// Wrap around to other end of the scrollback buffer.
- let start = Point::new(last_line - point.line, last_col - point.col);
+ let start = Point::new(last_line - point.line, last_col - point.column);
iter = self.grid.iter_from(start);
iter.cell()
},
@@ -266,8 +261,8 @@ impl<T> Term<T> {
let last_point = mem::replace(&mut point, iter.point());
// Handle linebreaks.
- if (last_point.col == last_col && point.col == Column(0) && !last_wrapped)
- || (last_point.col == Column(0) && point.col == last_col && !wrapped)
+ if (last_point.column == last_col && point.column == Column(0) && !last_wrapped)
+ || (last_point.column == Column(0) && point.column == last_col && !wrapped)
{
match regex_match {
Some(_) => break,
@@ -293,13 +288,13 @@ impl<T> Term<T> {
iter.next();
},
Direction::Right if cell.flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) => {
- if let Some(new_cell) = iter.next() {
+ if let Some(Indexed { cell: new_cell, .. }) = iter.next() {
*cell = new_cell;
}
iter.next();
},
Direction::Left if cell.flags.contains(Flags::WIDE_CHAR_SPACER) => {
- if let Some(new_cell) = iter.prev() {
+ if let Some(Indexed { cell: new_cell, .. }) = iter.prev() {
*cell = new_cell;
}
@@ -314,7 +309,7 @@ impl<T> Term<T> {
/// Find next matching bracket.
pub fn bracket_search(&self, point: Point<usize>) -> Option<Point<usize>> {
- let start_char = self.grid[point.line][point.col].c;
+ let start_char = self.grid[point.line][point.column].c;
// Find the matching bracket we're looking for
let (forward, end_char) = BRACKET_PAIRS.iter().find_map(|(open, close)| {
@@ -338,17 +333,17 @@ impl<T> Term<T> {
let cell = if forward { iter.next() } else { iter.prev() };
// Break if there are no more cells
- let c = match cell {
- Some(cell) => cell.c,
+ let cell = match cell {
+ Some(cell) => cell,
None => break,
};
// Check if the bracket matches
- if c == end_char && skip_pairs == 0 {
- return Some(iter.point());
- } else if c == start_char {
+ if cell.c == end_char && skip_pairs == 0 {
+ return Some(cell.point);
+ } else if cell.c == start_char {
skip_pairs += 1;
- } else if c == end_char {
+ } else if cell.c == end_char {
skip_pairs -= 1;
}
}
@@ -370,11 +365,11 @@ impl<T> Term<T> {
break;
}
- if iter.point().col == last_col && !cell.flags.contains(Flags::WRAPLINE) {
+ if cell.point.column == last_col && !cell.flags.contains(Flags::WRAPLINE) {
break; // cut off if on new line or hit escape char
}
- point = iter.point();
+ point = cell.point;
}
point
@@ -385,18 +380,17 @@ impl<T> Term<T> {
// Limit the starting point to the last line in the history
point.line = min(point.line, self.total_lines() - 1);
- let mut iter = self.grid.iter_from(point);
+ let wide = Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER;
let last_col = self.cols() - 1;
- let wide = Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER;
- while let Some(cell) = iter.next() {
+ for cell in self.grid.iter_from(point) {
if !cell.flags.intersects(wide) && self.semantic_escape_chars.contains(cell.c) {
break;
}
- point = iter.point();
+ point = cell.point;
- if point.col == last_col && !cell.flags.contains(Flags::WRAPLINE) {
+ if point.column == last_col && !cell.flags.contains(Flags::WRAPLINE) {
break; // cut off if on new line or hit escape char
}
}
@@ -412,7 +406,7 @@ impl<T> Term<T> {
point.line += 1;
}
- point.col = Column(0);
+ point.column = Column(0);
point
}
@@ -425,7 +419,7 @@ impl<T> Term<T> {
point.line -= 1;
}
- point.col = self.cols() - 1;
+ point.column = self.cols() - 1;
point
}
@@ -436,6 +430,7 @@ pub struct RegexIter<'a, T> {
point: Point<usize>,
end: Point<usize>,
direction: Direction,
+ dfas: &'a RegexSearch,
term: &'a Term<T>,
done: bool,
}
@@ -446,8 +441,9 @@ impl<'a, T> RegexIter<'a, T> {
end: Point<usize>,
direction: Direction,
term: &'a Term<T>,
+ dfas: &'a RegexSearch,
) -> Self {
- Self { point: start, done: false, end, direction, term }
+ Self { point: start, done: false, end, direction, term, dfas }
}
/// Skip one cell, advancing the origin point to the next one.
@@ -463,8 +459,8 @@ impl<'a, T> RegexIter<'a, T> {
/// Get the next match in the specified direction.
fn next_match(&self) -> Option<Match> {
match self.direction {
- Direction::Right => self.term.regex_search_right(self.point, self.end),
- Direction::Left => self.term.regex_search_left(self.point, self.end),
+ Direction::Right => self.term.regex_search_right(self.dfas, self.point, self.end),
+ Direction::Left => self.term.regex_search_left(self.dfas, self.point, self.end),
}
}
}
@@ -498,7 +494,7 @@ mod tests {
#[test]
fn regex_right() {
#[rustfmt::skip]
- let mut term = mock_term("\
+ let term = mock_term("\
testing66\r\n\
Alacritty\n\
123\r\n\
@@ -507,18 +503,18 @@ mod tests {
");
// Check regex across wrapped and unwrapped lines.
- term.regex_search = Some(RegexSearch::new("Ala.*123").unwrap());
+ let dfas = RegexSearch::new("Ala.*123").unwrap();
let start = Point::new(3, Column(0));
let end = Point::new(0, Column(2));
let match_start = Point::new(3, Column(0));
let match_end = Point::new(2, Column(2));
- assert_eq!(term.regex_search_right(start, end), Some(match_start..=match_end));
+ assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end));
}
#[test]
fn regex_left() {
#[rustfmt::skip]
- let mut term = mock_term("\
+ let term = mock_term("\
testing66\r\n\
Alacritty\n\
123\r\n\
@@ -527,209 +523,209 @@ mod tests {
");
// Check regex across wrapped and unwrapped lines.
- term.regex_search = Some(RegexSearch::new("Ala.*123").unwrap());
+ let dfas = RegexSearch::new("Ala.*123").unwrap();
let start = Point::new(0, Column(2));
let end = Point::new(3, Column(0));
let match_start = Point::new(3, Column(0));
let match_end = Point::new(2, Column(2));
- assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end));
+ assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
}
#[test]
fn nested_regex() {
#[rustfmt::skip]
- let mut term = mock_term("\
+ let term = mock_term("\
Ala -> Alacritty -> critty\r\n\
critty\
");
// Greedy stopped at linebreak.
- term.regex_search = Some(RegexSearch::new("Ala.*critty").unwrap());
+ let dfas = RegexSearch::new("Ala.*critty").unwrap();
let start = Point::new(1, Column(0));
let end = Point::new(1, Column(25));
- assert_eq!(term.regex_search_right(start, end), Some(start..=end));
+ assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
// Greedy stopped at dead state.
- term.regex_search = Some(RegexSearch::new("Ala[^y]*critty").unwrap());
+ let dfas = RegexSearch::new("Ala[^y]*critty").unwrap();
let start = Point::new(1, Column(0));
let end = Point::new(1, Column(15));
- assert_eq!(term.regex_search_right(start, end), Some(start..=end));
+ assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
}
#[test]
fn no_match_right() {
#[rustfmt::skip]
- let mut term = mock_term("\
+ let term = mock_term("\
first line\n\
broken second\r\n\
third\
");
- term.regex_search = Some(RegexSearch::new("nothing").unwrap());
+ let dfas = RegexSearch::new("nothing").unwrap();
let start = Point::new(2, Column(0));
let end = Point::new(0, Column(4));
- assert_eq!(term.regex_search_right(start, end), None);
+ assert_eq!(term.regex_search_right(&dfas, start, end), None);
}
#[test]
fn no_match_left() {
#[rustfmt::skip]
- let mut term = mock_term("\
+ let term = mock_term("\
first line\n\
broken second\r\n\
third\
");
- term.regex_search = Some(RegexSearch::new("nothing").unwrap());
+ let dfas = RegexSearch::new("nothing").unwrap();
let start = Point::new(0, Column(4));
let end = Point::new(2, Column(0));
- assert_eq!(term.regex_search_left(start, end), None);
+ assert_eq!(term.regex_search_left(&dfas, start, end), None);
}
#[test]
fn include_linebreak_left() {
#[rustfmt::skip]
- let mut term = mock_term("\
+ let term = mock_term("\
testing123\r\n\
xxx\
");
// Make sure the cell containing the linebreak is not skipped.
- term.regex_search = Some(RegexSearch::new("te.*123").unwrap());
+ let dfas = RegexSearch::new("te.*123").unwrap();
let start = Point::new(0, Column(0));
let end = Point::new(1, Column(0));
let match_start = Point::new(1, Column(0));
let match_end = Point::new(1, Column(9));
- assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end));
+ assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
}
#[test]
fn include_linebreak_right() {
#[rustfmt::skip]
- let mut term = mock_term("\
+ let term = mock_term("\
xxx\r\n\
testing123\
");
// Make sure the cell containing the linebreak is not skipped.
- term.regex_search = Some(RegexSearch::new("te.*123").unwrap());
+ let dfas = RegexSearch::new("te.*123").unwrap();
let start = Point::new(1, Column(2));
let end = Point::new(0, Column(9));
let match_start = Point::new(0, Column(0));
- assert_eq!(term.regex_search_right(start, end), Some(match_start..=end));
+ assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=end));
}
#[test]
fn skip_dead_cell() {
- let mut term = mock_term("alacritty");
+ let term = mock_term("alacritty");
// Make sure dead state cell is skipped when reversing.
- term.regex_search = Some(RegexSearch::new("alacrit").unwrap());
+ let dfas = RegexSearch::new("alacrit").unwrap();
let start = Point::new(0, Column(0));
let end = Point::new(0, Column(6));
- assert_eq!(term.regex_search_right(start, end), Some(start..=end));
+ assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
}
#[test]
fn reverse_search_dead_recovery() {
- let mut term = mock_term("zooo lense");
+ let term = mock_term("zooo lense");
// Make sure the reverse DFA operates the same as a forward DFA.
- term.regex_search = Some(RegexSearch::new("zoo").unwrap());
+ let dfas = RegexSearch::new("zoo").unwrap();
let start = Point::new(0, Column(9));
let end = Point::new(0, Column(0));
let match_start = Point::new(0, Column(0));
let match_end = Point::new(0, Column(2));
- assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end));
+ assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
}
#[test]
fn multibyte_unicode() {
- let mut term = mock_term("testвосибing");
+ let term = mock_term("testвосибing");
- term.regex_search = Some(RegexSearch::new("te.*ing").unwrap());
+ let dfas = RegexSearch::new("te.*ing").unwrap();
let start = Point::new(0, Column(0));
let end = Point::new(0, Column(11));
- assert_eq!(term.regex_search_right(start, end), Some(start..=end));
+ assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
- term.regex_search = Some(RegexSearch::new("te.*ing").unwrap());
+ let dfas = RegexSearch::new("te.*ing").unwrap();
let start = Point::new(0, Column(11));
let end = Point::new(0, Column(0));
- assert_eq!(term.regex_search_left(start, end), Some(end..=start));
+ assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start));
}
#[test]
fn fullwidth() {
- let mut term = mock_term("a🦇x🦇");
+ let term = mock_term("a🦇x🦇");
- term.regex_search = Some(RegexSearch::new("[^ ]*").unwrap());
+ let dfas = RegexSearch::new("[^ ]*").unwrap();
let start = Point::new(0, Column(0));
let end = Point::new(0, Column(5));
- assert_eq!(term.regex_search_right(start, end), Some(start..=end));
+ assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
- term.regex_search = Some(RegexSearch::new("[^ ]*").unwrap());
+ let dfas = RegexSearch::new("[^ ]*").unwrap();
let start = Point::new(0, Column(5));
let end = Point::new(0, Column(0));
- assert_eq!(term.regex_search_left(start, end), Some(end..=start));
+ assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start));
}
#[test]
fn singlecell_fullwidth() {
- let mut term = mock_term("🦇");
+ let term = mock_term("🦇");
- term.regex_search = Some(RegexSearch::new("🦇").unwrap());
+ let dfas = RegexSearch::new("🦇").unwrap();
let start = Point::new(0, Column(0));
let end = Point::new(0, Column(1));
- assert_eq!(term.regex_search_right(start, end), Some(start..=end));
+ assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
- term.regex_search = Some(RegexSearch::new("🦇").unwrap());
+ let dfas = RegexSearch::new("🦇").unwrap();
let start = Point::new(0, Column(1));
let end = Point::new(0, Column(0));
- assert_eq!(term.regex_search_left(start, end), Some(end..=start));
+ assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start));
}
#[test]
fn wrapping() {
#[rustfmt::skip]
- let mut term = mock_term("\
+ let term = mock_term("\
xxx\r\n\
xxx\
");
- term.regex_search = Some(RegexSearch::new("xxx").unwrap());
+ let dfas = RegexSearch::new("xxx").unwrap();
let start = Point::new(0, Column(2));
let end = Point::new(1, Column(2));
let match_start = Point::new(1, Column(0));
- assert_eq!(term.regex_search_right(start, end), Some(match_start..=end));
+ assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=end));
- term.regex_search = Some(RegexSearch::new("xxx").unwrap());
+ let dfas = RegexSearch::new("xxx").unwrap();
let start = Point::new(1, Column(0));
let end = Point::new(0, Column(0));
let match_end = Point::new(0, Column(2));
- assert_eq!(term.regex_search_left(start, end), Some(end..=match_end));
+ assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=match_end));
}
#[test]
fn wrapping_into_fullwidth() {
#[rustfmt::skip]
- let mut term = mock_term("\
+ let term = mock_term("\
🦇xx\r\n\
xx🦇\
");
- term.regex_search = Some(RegexSearch::new("🦇x").unwrap());
+ let dfas = RegexSearch::new("🦇x").unwrap();
let start = Point::new(0, Column(0));
let end = Point::new(1, Column(3));
let match_start = Point::new(1, Column(0));
let match_end = Point::new(1, Column(2));
- assert_eq!(term.regex_search_right(start, end), Some(match_start..=match_end));
+ assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end));
- term.regex_search = Some(RegexSearch::new("x🦇").unwrap());
+ let dfas = RegexSearch::new("x🦇").unwrap();
let start = Point::new(1, Column(2));
let end = Point::new(0, Column(0));
let match_start = Point::new(0, Column(1));
let match_end = Point::new(0, Column(3));
- assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end));
+ assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
}
#[test]
@@ -741,32 +737,32 @@ mod tests {
");
term.grid[1][Column(3)].flags.insert(Flags::LEADING_WIDE_CHAR_SPACER);
- term.regex_search = Some(RegexSearch::new("🦇x").unwrap());
+ let dfas = RegexSearch::new("🦇x").unwrap();
let start = Point::new(1, Column(0));
let end = Point::new(0, Column(3));
let match_start = Point::new(1, Column(3));
let match_end = Point::new(0, Column(2));
- assert_eq!(term.regex_search_right(start, end), Some(match_start..=match_end));
+ assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end));
- term.regex_search = Some(RegexSearch::new("🦇x").unwrap());
+ let dfas = RegexSearch::new("🦇x").unwrap();
let start = Point::new(0, Column(3));
let end = Point::new(1, Column(0));
let match_start = Point::new(1, Column(3));
let match_end = Point::new(0, Column(2));
- assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end));
+ assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
- term.regex_search = Some(RegexSearch::new("x🦇").unwrap());
+ let dfas = RegexSearch::new("x🦇").unwrap();
let start = Point::new(1, Column(0));
let end = Point::new(0, Column(3));
let match_start = Point::new(1, Column(2));
let match_end = Point::new(0, Column(1));
- assert_eq!(term.regex_search_right(start, end), Some(match_start..=match_end));
+ assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end));
- term.regex_search = Some(RegexSearch::new("x🦇").unwrap());
+ let dfas = RegexSearch::new("x🦇").unwrap();
let start = Point::new(0, Column(3));
let end = Point::new(1, Column(0));
let match_start = Point::new(1, Column(2));
let match_end = Point::new(0, Column(1));
- assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end));
+ assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
}
}
diff --git a/alacritty_terminal/src/vi_mode.rs b/alacritty_terminal/src/vi_mode.rs
index 46439a75..1b3390d6 100644
--- a/alacritty_terminal/src/vi_mode.rs
+++ b/alacritty_terminal/src/vi_mode.rs
@@ -81,13 +81,13 @@ impl ViModeCursor {
ViMotion::Left => {
buffer_point = term.expand_wide(buffer_point, Direction::Left);
let wrap_point = Point::new(buffer_point.line + 1, cols - 1);
- if buffer_point.col.0 == 0
+ if buffer_point.column.0 == 0
&& buffer_point.line + 1 < term.total_lines()
&& is_wrap(term, wrap_point)
{
buffer_point = wrap_point;
} else {
- buffer_point.col = Column(buffer_point.col.saturating_sub(1));
+ buffer_point.column = Column(buffer_point.column.saturating_sub(1));
}
},
ViMotion::Right => {
@@ -95,34 +95,34 @@ impl ViModeCursor {
if is_wrap(term, buffer_point) {
buffer_point = Point::new(buffer_point.line - 1, Column(0));
} else {
- buffer_point.col = min(buffer_point.col + 1, cols - 1);
+ buffer_point.column = min(buffer_point.column + 1, cols - 1);
}
},
ViMotion::First => {
buffer_point = term.expand_wide(buffer_point, Direction::Left);
- while buffer_point.col.0 == 0
+ while buffer_point.column.0 == 0
&& buffer_point.line + 1 < term.total_lines()
&& is_wrap(term, Point::new(buffer_point.line + 1, cols - 1))
{
buffer_point.line += 1;
}
- buffer_point.col = Column(0);
+ buffer_point.column = Column(0);
},
ViMotion::Last => buffer_point = last(term, buffer_point),
ViMotion::FirstOccupied => buffer_point = first_occupied(term, buffer_point),
ViMotion::High => {
let line = display_offset + lines.0 - 1;
- let col = first_occupied_in_line(term, line).unwrap_or_default().col;
+ let col = first_occupied_in_line(term, line).unwrap_or_default().column;
buffer_point = Point::new(line, col);
},
ViMotion::Middle => {
let line = display_offset + lines.0 / 2;
- let col = first_occupied_in_line(term, line).unwrap_or_default().col;
+ let col = first_occupied_in_line(term, line).unwrap_or_default().column;
buffer_point = Point::new(line, col);
},
ViMotion::Low => {
let line = display_offset;
- let col = first_occupied_in_line(term, line).unwrap_or_default().col;
+ let col = first_occupied_in_line(term, line).unwrap_or_default().column;
buffer_point = Point::new(line, col);
},
ViMotion::SemanticLeft => {
@@ -181,7 +181,7 @@ impl ViModeCursor {
let buffer_point = term.visible_to_buffer(self.point);
let mut target_line = buffer_point.line as isize + lines;
target_line = max(0, min(term.total_lines() as isize - 1, target_line));
- let col = first_occupied_in_line(term, target_line as usize).unwrap_or_default().col;
+ let col = first_occupied_in_line(term, target_line as usize).unwrap_or_default().column;
// Move cursor.
self.point = Point::new(Line(line as usize), col);
@@ -200,7 +200,7 @@ fn last<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> {
// Find last non-empty cell in the current line.
let occupied = last_occupied_in_line(term, point.line).unwrap_or_default();
- if point.col < occupied.col {
+ if point.column < occupied.column {
// Jump to last occupied cell when not already at or beyond it.
occupied
} else if is_wrap(term, point) {
@@ -269,7 +269,7 @@ fn semantic<T: EventListener>(
// Expand semantically based on movement direction.
let expand_semantic = |point: Point<usize>| {
// Do not expand when currently on a semantic escape char.
- let cell = &term.grid()[point.line][point.col];
+ let cell = &term.grid()[point.line][point.column];
if term.semantic_escape_chars().contains(cell.c)
&& !cell.flags.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER)
{
@@ -375,21 +375,21 @@ fn advance<T>(term: &Term<T>, point: Point<usize>, direction: Direction) -> Poin
/// Check if cell at point contains whitespace.
fn is_space<T>(term: &Term<T>, point: Point<usize>) -> bool {
- let cell = &term.grid()[point.line][point.col];
+ 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')
}
fn is_wrap<T>(term: &Term<T>, point: Point<usize>) -> bool {
- point.line != 0 && term.grid()[point.line][point.col].flags.contains(Flags::WRAPLINE)
+ point.line != 0 && term.grid()[point.line][point.column].flags.contains(Flags::WRAPLINE)
}
/// Check if point is at screen boundary.
fn is_boundary<T>(term: &Term<T>, point: Point<usize>, direction: Direction) -> bool {
let total_lines = term.total_lines();
let num_cols = term.cols();
- (point.line + 1 >= total_lines && point.col.0 == 0 && direction == Direction::Left)
- || (point.line == 0 && point.col + 1 >= num_cols && direction == Direction::Right)
+ (point.line + 1 >= total_lines && point.column.0 == 0 && direction == Direction::Left)
+ || (point.line == 0 && point.column + 1 >= num_cols && direction == Direction::Right)
}
#[cfg(test)]
@@ -397,18 +397,12 @@ mod tests {
use super::*;
use crate::config::MockConfig;
- use crate::event::Event;
use crate::index::{Column, Line};
use crate::term::{SizeInfo, Term};
- struct Mock;
- impl EventListener for Mock {
- fn send_event(&self, _event: Event) {}
- }
-
- fn term() -> Term<Mock> {
+ fn term() -> Term<()> {
let size = SizeInfo::new(20., 20., 1.0, 1.0, 0.0, 0.0, false);
- Term::new(&MockConfig::default(), size, Mock)
+ Term::new(&MockConfig::default(), size, ())
}
#[test]
@@ -515,7 +509,7 @@ mod tests {
assert_eq!(cursor.point, Point::new(Line(0), Column(0)));
}
- fn motion_semantic_term() -> Term<Mock> {
+ fn motion_semantic_term() -> Term<()> {
let mut term = term();
term.grid_mut()[Line(0)][Column(0)].c = 'x';
diff --git a/alacritty_terminal/tests/ref.rs b/alacritty_terminal/tests/ref.rs
index b55a45e9..a0a831d5 100644
--- a/alacritty_terminal/tests/ref.rs
+++ b/alacritty_terminal/tests/ref.rs
@@ -84,7 +84,9 @@ struct RefConfig {
history_size: u32,
}
+#[derive(Copy, Clone)]
struct Mock;
+
impl EventListener for Mock {
fn send_event(&self, _event: Event) {}
}