summaryrefslogtreecommitdiff
path: root/alacritty_terminal/src/term
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty_terminal/src/term')
-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
4 files changed, 343 insertions, 1052 deletions
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));
}
}