diff options
Diffstat (limited to 'alacritty_terminal/src/term')
-rw-r--r-- | alacritty_terminal/src/term/color.rs | 203 | ||||
-rw-r--r-- | alacritty_terminal/src/term/mod.rs | 529 | ||||
-rw-r--r-- | alacritty_terminal/src/term/render.rs | 431 | ||||
-rw-r--r-- | alacritty_terminal/src/term/search.rs | 232 |
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)); } } |