diff --git a/CHANGELOG.md b/CHANGELOG.md index 67b8acda..d31bf64c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## 0.8.0-dev +### Packaging + +- Updated shell completions + ### Added - IME composition preview not appearing on Windows @@ -15,9 +19,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Crash due to assertion failure on 32-bit architectures -### Packaging +### Removed -- Updated shell completions +- Config field `visual_bell`, you should use `bell` instead ## 0.7.1 diff --git a/Cargo.lock b/Cargo.lock index 9e8f0cd7..7a3c8b5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,7 +61,7 @@ dependencies = [ [[package]] name = "alacritty_terminal" -version = "0.12.1-dev" +version = "0.13.0-dev" dependencies = [ "alacritty_config_derive", "base64 0.12.3", diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index 73724fbe..923659d2 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" [dependencies.alacritty_terminal] path = "../alacritty_terminal" -version = "0.12.1-dev" +version = "0.13.0-dev" default-features = false [dependencies.alacritty_config_derive] diff --git a/alacritty_terminal/src/config/bell.rs b/alacritty/src/config/bell.rs similarity index 94% rename from alacritty_terminal/src/config/bell.rs rename to alacritty/src/config/bell.rs index 825a7b1f..2516e2b3 100644 --- a/alacritty_terminal/src/config/bell.rs +++ b/alacritty/src/config/bell.rs @@ -2,8 +2,8 @@ use std::time::Duration; use alacritty_config_derive::ConfigDeserialize; -use crate::config::Program; -use crate::term::color::Rgb; +use alacritty_terminal::config::Program; +use alacritty_terminal::term::color::Rgb; #[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)] pub struct BellConfig { diff --git a/alacritty_terminal/src/config/colors.rs b/alacritty/src/config/color.rs similarity index 99% rename from alacritty_terminal/src/config/colors.rs rename to alacritty/src/config/color.rs index 88ca5057..cd5d964d 100644 --- a/alacritty_terminal/src/config/colors.rs +++ b/alacritty/src/config/color.rs @@ -2,8 +2,7 @@ use serde::de::Error as SerdeError; use serde::{Deserialize, Deserializer}; use alacritty_config_derive::ConfigDeserialize; - -use crate::term::color::{CellRgb, Rgb}; +use alacritty_terminal::term::color::{CellRgb, Rgb}; #[derive(ConfigDeserialize, Clone, Debug, Default, PartialEq, Eq)] pub struct Colors { diff --git a/alacritty/src/config/mod.rs b/alacritty/src/config/mod.rs index 0673ffd5..a782f5fe 100644 --- a/alacritty/src/config/mod.rs +++ b/alacritty/src/config/mod.rs @@ -1,5 +1,5 @@ use std::fmt::{self, Display, Formatter}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::{env, fs, io}; use log::{error, info}; @@ -9,6 +9,8 @@ use serde_yaml::Value; use alacritty_terminal::config::{Config as TermConfig, LOG_TARGET_CONFIG}; +pub mod bell; +pub mod color; pub mod debug; pub mod font; pub mod monitor; @@ -123,10 +125,10 @@ pub fn load(options: &Options) -> Config { } /// Attempt to reload the configuration file. -pub fn reload(config_path: &PathBuf, options: &Options) -> Result { +pub fn reload(config_path: &Path, options: &Options) -> Result { // Load config, propagating errors. let config_options = options.config_options().clone(); - let mut config = load_from(&config_path, config_options)?; + let mut config = load_from(config_path, config_options)?; // Override config with CLI options. options.override_config(&mut config); @@ -135,7 +137,7 @@ pub fn reload(config_path: &PathBuf, options: &Options) -> Result { } /// Load configuration file and log errors. -fn load_from(path: &PathBuf, cli_config: Value) -> Result { +fn load_from(path: &Path, cli_config: Value) -> Result { match read_config(path, cli_config) { Ok(config) => Ok(config), Err(err) => { @@ -146,7 +148,7 @@ fn load_from(path: &PathBuf, cli_config: Value) -> Result { } /// Deserialize configuration file from path. -fn read_config(path: &PathBuf, cli_config: Value) -> Result { +fn read_config(path: &Path, cli_config: Value) -> Result { let mut config_paths = Vec::new(); let mut config_value = parse_config(&path, &mut config_paths, IMPORT_RECURSION_LIMIT)?; @@ -162,7 +164,7 @@ fn read_config(path: &PathBuf, cli_config: Value) -> Result { /// Deserialize all configuration files as generic Value. fn parse_config( - path: &PathBuf, + path: &Path, config_paths: &mut Vec, recursion_limit: usize, ) -> Result { diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs index 25f9fb91..b3b3021a 100644 --- a/alacritty/src/config/ui_config.rs +++ b/alacritty/src/config/ui_config.rs @@ -6,7 +6,9 @@ use serde::{Deserialize, Deserializer}; use alacritty_config_derive::ConfigDeserialize; use alacritty_terminal::config::{Percentage, LOG_TARGET_CONFIG}; +use crate::config::bell::BellConfig; use crate::config::bindings::{self, Binding, KeyBinding, MouseBinding}; +use crate::config::color::Colors; use crate::config::debug::Debug; use crate::config::font::Font; use crate::config::mouse::Mouse; @@ -31,6 +33,15 @@ pub struct UIConfig { /// Live config reload. pub live_config_reload: bool, + /// Bell configuration. + pub bell: BellConfig, + + /// RGB values for colors. + pub colors: Colors, + + /// Should draw bold text with brighter colors instead of bold font. + pub draw_bold_text_with_bright_colors: bool, + /// Path where config was loaded from. #[config(skip)] pub config_paths: Vec, @@ -58,6 +69,9 @@ impl Default for UIConfig { key_bindings: Default::default(), mouse_bindings: Default::default(), background_opacity: Default::default(), + bell: Default::default(), + colors: Default::default(), + draw_bold_text_with_bright_colors: Default::default(), } } } diff --git a/alacritty/src/display/bell.rs b/alacritty/src/display/bell.rs new file mode 100644 index 00000000..1aee3ba6 --- /dev/null +++ b/alacritty/src/display/bell.rs @@ -0,0 +1,122 @@ +use std::time::{Duration, Instant}; + +use crate::config::bell::{BellAnimation, BellConfig}; + +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, +} + +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(&mut self, bell_config: &BellConfig) { + 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, + } + } +} + +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 +} diff --git a/alacritty/src/display/color.rs b/alacritty/src/display/color.rs new file mode 100644 index 00000000..6e0de048 --- /dev/null +++ b/alacritty/src/display/color.rs @@ -0,0 +1,167 @@ +use std::ops::{Index, IndexMut}; + +use log::trace; + +use alacritty_terminal::ansi::NamedColor; +use alacritty_terminal::term::color::{Rgb, COUNT}; + +use crate::config::color::Colors; + +/// Factor for automatic computation of dim colors. +pub const DIM_FACTOR: f32 = 0.66; + +#[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[NamedColor::Black] = colors.normal.black; + self[NamedColor::Red] = colors.normal.red; + self[NamedColor::Green] = colors.normal.green; + self[NamedColor::Yellow] = colors.normal.yellow; + self[NamedColor::Blue] = colors.normal.blue; + self[NamedColor::Magenta] = colors.normal.magenta; + self[NamedColor::Cyan] = colors.normal.cyan; + self[NamedColor::White] = colors.normal.white; + + // Brights. + self[NamedColor::BrightBlack] = colors.bright.black; + self[NamedColor::BrightRed] = colors.bright.red; + self[NamedColor::BrightGreen] = colors.bright.green; + self[NamedColor::BrightYellow] = colors.bright.yellow; + self[NamedColor::BrightBlue] = colors.bright.blue; + self[NamedColor::BrightMagenta] = colors.bright.magenta; + self[NamedColor::BrightCyan] = colors.bright.cyan; + self[NamedColor::BrightWhite] = colors.bright.white; + self[NamedColor::BrightForeground] = + colors.primary.bright_foreground.unwrap_or(colors.primary.foreground); + + // Foreground and background. + self[NamedColor::Foreground] = colors.primary.foreground; + self[NamedColor::Background] = colors.primary.background; + + // Dims. + self[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[NamedColor::DimBlack] = dim.black; + self[NamedColor::DimRed] = dim.red; + self[NamedColor::DimGreen] = dim.green; + self[NamedColor::DimYellow] = dim.yellow; + self[NamedColor::DimBlue] = dim.blue; + self[NamedColor::DimMagenta] = dim.magenta; + self[NamedColor::DimCyan] = dim.cyan; + self[NamedColor::DimWhite] = dim.white; + }, + None => { + trace!("Deriving dim colors from normal colors"); + self[NamedColor::DimBlack] = colors.normal.black * DIM_FACTOR; + self[NamedColor::DimRed] = colors.normal.red * DIM_FACTOR; + self[NamedColor::DimGreen] = colors.normal.green * DIM_FACTOR; + self[NamedColor::DimYellow] = colors.normal.yellow * DIM_FACTOR; + self[NamedColor::DimBlue] = colors.normal.blue * DIM_FACTOR; + self[NamedColor::DimMagenta] = colors.normal.magenta * DIM_FACTOR; + self[NamedColor::DimCyan] = colors.normal.cyan * DIM_FACTOR; + self[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 Index for List { + type Output = Rgb; + + #[inline] + fn index(&self, idx: usize) -> &Self::Output { + &self.0[idx] + } +} + +impl IndexMut for List { + #[inline] + fn index_mut(&mut self, idx: usize) -> &mut Self::Output { + &mut self.0[idx] + } +} + +impl Index for List { + type Output = Rgb; + + #[inline] + fn index(&self, idx: NamedColor) -> &Self::Output { + &self.0[idx as usize] + } +} + +impl IndexMut for List { + #[inline] + fn index_mut(&mut self, idx: NamedColor) -> &mut Self::Output { + &mut self.0[idx as usize] + } +} diff --git a/alacritty_terminal/src/term/render.rs b/alacritty/src/display/content.rs similarity index 58% rename from alacritty_terminal/src/term/render.rs rename to alacritty/src/display/content.rs index fbb5a732..81c2977f 100644 --- a/alacritty_terminal/src/term/render.rs +++ b/alacritty/src/display/content.rs @@ -1,18 +1,21 @@ 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}; +use alacritty_terminal::ansi::{Color, CursorShape, NamedColor}; +use alacritty_terminal::config::Config; +use alacritty_terminal::event::EventListener; +use alacritty_terminal::grid::{Dimensions, Indexed}; +use alacritty_terminal::index::{Column, Direction, Line, Point}; +use alacritty_terminal::term::cell::{Cell, Flags}; +use alacritty_terminal::term::color::{CellRgb, Rgb}; +use alacritty_terminal::term::search::{RegexIter, RegexSearch}; +use alacritty_terminal::term::{ + RenderableContent as TerminalContent, RenderableCursor as TerminalCursor, Term, TermMode, +}; + +use crate::config::ui_config::UIConfig; +use crate::display::color::{List, DIM_FACTOR}; /// Minimum contrast between a fixed cursor color and the cell's background. pub const MIN_CURSOR_CONTRAST: f64 = 1.5; @@ -23,58 +26,40 @@ 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, - config: &'a Config, - display_iter: DisplayIter<'a, Cell>, - selection: Option>, - search: RenderableSearch<'a>, +pub struct RenderableContent<'a> { + terminal_content: TerminalContent<'a>, + terminal_cursor: TerminalCursor, cursor: Option, - cursor_shape: CursorShape, - cursor_point: Point, + search: RenderableSearch, + config: &'a Config, + colors: &'a List, } -impl<'a, T, C> RenderableContent<'a, T, C> { - pub fn new(term: &'a Term, config: &'a Config, 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 - }; +impl<'a> RenderableContent<'a> { + pub fn new( + term: &'a Term, + dfas: Option<&RegexSearch>, + config: &'a Config, + colors: &'a List, + show_cursor: bool, + ) -> Self { + let search = dfas.map(|dfas| RenderableSearch::new(&term, dfas)).unwrap_or_default(); + let terminal_content = term.renderable_content(); - // 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 + // Copy the cursor and override its shape if necessary. + let mut terminal_cursor = terminal_content.cursor; + if !show_cursor { + terminal_cursor.shape = 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, + terminal_cursor.shape = CursorShape::HollowBlock; } + + Self { cursor: None, terminal_content, terminal_cursor, search, config, colors } + } + + /// Viewport offset. + pub fn display_offset(&self) -> usize { + self.terminal_content.display_offset } /// Get the terminal cursor. @@ -85,33 +70,35 @@ impl<'a, T, C> RenderableContent<'a, T, C> { self.cursor } + /// Get the RGB value for a color index. + pub fn color(&self, color: usize) -> Rgb { + self.terminal_content.colors[color].unwrap_or(self.colors[color]) + } + /// 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 { - if self.cursor_shape == CursorShape::Hidden { + if self.terminal_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; + self.terminal_cursor.point.column -= 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 + let color = if self.terminal_content.mode.contains(TermMode::VI) { + self.config.ui_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 + self.config.ui_config.colors.cursor }; + let mut cursor_color = + self.terminal_content.colors[NamedColor::Cursor].map_or(color.background, CellRgb::Rgb); let mut text_color = color.foreground; // Invert the cursor if it has a fixed background close to the cell's background. @@ -128,8 +115,8 @@ impl<'a, T, C> RenderableContent<'a, T, C> { let cursor_color = cursor_color.color(cell.fg, cell.bg); Some(RenderableCursor { - point: self.cursor_point, - shape: self.cursor_shape, + point: self.terminal_cursor.point, + shape: self.terminal_cursor.shape, cursor_color, text_color, is_wide, @@ -137,7 +124,7 @@ impl<'a, T, C> RenderableContent<'a, T, C> { } } -impl<'a, T, C> Iterator for RenderableContent<'a, T, C> { +impl<'a> Iterator for RenderableContent<'a> { type Item = RenderableCell; /// Gets the next renderable cell. @@ -147,11 +134,10 @@ impl<'a, T, C> Iterator for RenderableContent<'a, T, C> { #[inline] fn next(&mut self) -> Option { 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); + let cell = self.terminal_content.display_iter.next()?; + let mut cell = RenderableCell::new(self, cell); + if self.terminal_cursor.point == cell.point { // Store the cursor which should be rendered. self.cursor = self.renderable_cursor(&cell).map(|cursor| { if cursor.shape == CursorShape::Block { @@ -167,15 +153,9 @@ impl<'a, T, C> Iterator for RenderableContent<'a, T, C> { }); return Some(cell); - } else { - // Handle non-cursor cells. - let cell = self.display_iter.next()?; - let cell = RenderableCell::new(self, cell); - + } else if !cell.is_empty() && !cell.flags.contains(Flags::WIDE_CHAR_SPACER) { // Skip empty cells and wide char spacers. - if !cell.is_empty() && !cell.flags.contains(Flags::WIDE_CHAR_SPACER) { - return Some(cell); - } + return Some(cell); } } } @@ -186,8 +166,7 @@ impl<'a, T, C> Iterator for RenderableContent<'a, T, C> { pub struct RenderableCell { pub character: char, pub zerowidth: Option>, - pub line: Line, - pub column: Column, + pub point: Point, pub fg: Rgb, pub bg: Rgb, pub bg_alpha: f32, @@ -196,13 +175,10 @@ pub struct RenderableCell { } 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); - + fn new<'a>(content: &mut RenderableContent<'a>, cell: Indexed<&Cell, Line>) -> Self { // 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 fg_rgb = Self::compute_fg_rgb(content, cell.fg, cell.flags); + let mut bg_rgb = Self::compute_bg_rgb(content, cell.bg); let mut bg_alpha = if cell.flags.contains(Flags::INVERSE) { mem::swap(&mut fg_rgb, &mut bg_rgb); @@ -211,30 +187,31 @@ impl RenderableCell { 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 is_selected = content + .terminal_content + .selection + .map_or(false, |selection| selection.contains_cell(&cell, content.terminal_cursor)); let mut is_match = false; + let colors = &content.config.ui_config.colors; if is_selected { - let config_bg = content.config.colors.selection.background; - let selected_fg = content.config.colors.selection.foreground.color(fg_rgb, bg_rgb); + let config_bg = colors.selection.background; + let selected_fg = 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]; + fg_rgb = content.color(NamedColor::Background as usize); + bg_rgb = content.color(NamedColor::Foreground as usize); bg_alpha = 1.0; } else if config_bg != CellRgb::CellBackground { bg_alpha = 1.0; } - } else if content.search.advance(grid.visible_to_buffer(point)) { + } else if content.search.advance(cell.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); + let config_bg = colors.search.matches.background; + let matched_fg = colors.search.matches.foreground.color(fg_rgb, bg_rgb); bg_rgb = config_bg.color(fg_rgb, bg_rgb); fg_rgb = matched_fg; @@ -248,8 +225,7 @@ impl RenderableCell { RenderableCell { character: cell.c, zerowidth: cell.zerowidth().map(|zerowidth| zerowidth.to_vec()), - line: cell.line, - column: cell.column, + point: cell.point, fg: fg_rgb, bg: bg_rgb, bg_alpha, @@ -258,11 +234,6 @@ impl RenderableCell { } } - /// 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. @@ -272,32 +243,35 @@ impl RenderableCell { } /// Get the RGB color from a cell's foreground color. - fn compute_fg_rgb(config: &Config, colors: &color::List, fg: Color, flags: Flags) -> Rgb { + fn compute_fg_rgb(content: &mut RenderableContent<'_>, fg: Color, flags: Flags) -> Rgb { + let ui_config = &content.config.ui_config; 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) { + match (ui_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() => + && ui_config.colors.primary.bright_foreground.is_none() => { - colors[NamedColor::DimForeground] + content.color(NamedColor::DimForeground as usize) }, // Draw bold text in bright colors *and* contains bold flag. - (true, Flags::BOLD) => colors[ansi.to_bright()], + (true, Flags::BOLD) => content.color(ansi.to_bright() as usize), // Cell is marked as dim and not bold. - (_, Flags::DIM) | (false, Flags::DIM_BOLD) => colors[ansi.to_dim()], + (_, Flags::DIM) | (false, Flags::DIM_BOLD) => { + content.color(ansi.to_dim() as usize) + }, // None of the above, keep original color.. - _ => colors[ansi], + _ => content.color(ansi as usize), } }, Color::Indexed(idx) => { let idx = match ( - config.draw_bold_text_with_bright_colors, + ui_config.draw_bold_text_with_bright_colors, flags & Flags::DIM_BOLD, idx, ) { @@ -307,18 +281,18 @@ impl RenderableCell { _ => idx as usize, }; - colors[idx] + content.color(idx) }, } } /// Get the RGB color from a cell's background color. #[inline] - fn compute_bg_rgb(colors: &color::List, bg: Color) -> Rgb { + fn compute_bg_rgb(content: &mut RenderableContent<'_>, bg: Color) -> Rgb { match bg { Color::Spec(rgb) => rgb, - Color::Named(ansi) => colors[ansi], - Color::Indexed(idx) => colors[idx], + Color::Named(ansi) => content.color(ansi as usize), + Color::Indexed(idx) => content.color(idx as usize), } } @@ -365,22 +339,19 @@ impl RenderableCursor { } } -type MatchIter<'a> = Box>> + 'a>; - /// Regex search highlight tracking. -struct RenderableSearch<'a> { - iter: Peekable>, +#[derive(Default)] +pub struct RenderableSearch { + /// All visible search matches. + matches: Vec>, + + /// Index of the last match checked. + index: usize, } -impl<'a> RenderableSearch<'a> { +impl RenderableSearch { /// Create a new renderable search iterator. - fn new(term: &'a Term) -> 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() }; - } - + pub fn new(term: &Term, dfas: &RegexSearch) -> Self { let viewport_end = term.grid().display_offset(); let viewport_start = viewport_end + term.screen_lines().0 - 1; @@ -394,8 +365,7 @@ impl<'a> RenderableSearch<'a> { 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() }; + return Self::default(); } else { // Start at next line if this one is too long. start.line -= 1; @@ -404,24 +374,27 @@ impl<'a> RenderableSearch<'a> { 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), - ); + let iter = RegexIter::new(start, end, Direction::Right, term, dfas) + .skip_while(move |rm| rm.end().line > viewport_start) + .take_while(move |rm| rm.start().line >= viewport_end) + .map(|rm| { + let viewport_start = term.grid().clamp_buffer_to_visible(*rm.start()); + let viewport_end = term.grid().clamp_buffer_to_visible(*rm.end()); + viewport_start..=viewport_end + }); - Self { iter: iter.peekable() } + Self { matches: iter.collect(), index: 0 } } /// 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) -> bool { - while let Some(regex_match) = &self.iter.peek() { + fn advance(&mut self, point: Point) -> bool { + while let Some(regex_match) = self.matches.get(self.index) { if regex_match.start() > &point { break; } else if regex_match.end() < &point { - let _ = self.iter.next(); + self.index += 1; } else { return true; } diff --git a/alacritty/src/cursor.rs b/alacritty/src/display/cursor.rs similarity index 95% rename from alacritty/src/cursor.rs rename to alacritty/src/display/cursor.rs index a9fba66a..0750459d 100644 --- a/alacritty/src/cursor.rs +++ b/alacritty/src/display/cursor.rs @@ -2,9 +2,9 @@ use alacritty_terminal::ansi::CursorShape; use alacritty_terminal::term::color::Rgb; -use alacritty_terminal::term::render::RenderableCursor; use alacritty_terminal::term::SizeInfo; +use crate::display::content::RenderableCursor; use crate::renderer::rects::RenderRect; /// Trait for conversion into the iterator. @@ -16,7 +16,7 @@ pub trait IntoRects { impl IntoRects for RenderableCursor { fn rects(self, size_info: &SizeInfo, thickness: f32) -> CursorRects { let point = self.point(); - let x = point.col.0 as f32 * size_info.cell_width() + size_info.padding_x(); + let x = point.column.0 as f32 * size_info.cell_width() + size_info.padding_x(); let y = point.line.0 as f32 * size_info.cell_height() + size_info.padding_y(); let mut width = size_info.cell_width(); diff --git a/alacritty/src/meter.rs b/alacritty/src/display/meter.rs similarity index 100% rename from alacritty/src/meter.rs rename to alacritty/src/display/meter.rs diff --git a/alacritty/src/display.rs b/alacritty/src/display/mod.rs similarity index 92% rename from alacritty/src/display.rs rename to alacritty/src/display/mod.rs index 5e885b53..2a55402e 100644 --- a/alacritty/src/display.rs +++ b/alacritty/src/display/mod.rs @@ -22,6 +22,7 @@ use wayland_client::{Display as WaylandDisplay, EventQueue}; use crossfont::{self, Rasterize, Rasterizer}; +use alacritty_terminal::ansi::NamedColor; use alacritty_terminal::event::{EventListener, OnResize}; use alacritty_terminal::grid::Dimensions as _; use alacritty_terminal::index::{Column, Direction, Line, Point}; @@ -33,14 +34,27 @@ use crate::config::window::Dimensions; #[cfg(not(windows))] use crate::config::window::StartupMode; use crate::config::Config; -use crate::cursor::IntoRects; +use crate::display::bell::VisualBell; +use crate::display::color::List; +use crate::display::content::RenderableContent; +use crate::display::cursor::IntoRects; +use crate::display::meter::Meter; +use crate::display::window::Window; use crate::event::{Mouse, SearchState}; use crate::message_bar::{MessageBuffer, MessageType}; -use crate::meter::Meter; use crate::renderer::rects::{RenderLines, RenderRect}; use crate::renderer::{self, GlyphCache, QuadRenderer}; use crate::url::{Url, Urls}; -use crate::window::{self, Window}; + +pub mod content; +pub mod cursor; +pub mod window; + +mod bell; +mod color; +mod meter; +#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] +mod wayland_theme; const FORWARD_SEARCH_LABEL: &str = "Search: "; const BACKWARD_SEARCH_LABEL: &str = "Backward Search: "; @@ -162,6 +176,11 @@ pub struct Display { /// UI cursor visibility for blinking. pub cursor_hidden: bool, + pub visual_bell: VisualBell, + + /// Mapped RGB values for each terminal color. + pub colors: List, + renderer: QuadRenderer, glyph_cache: GlyphCache, meter: Meter, @@ -246,7 +265,7 @@ impl Display { renderer.resize(&size_info); // Clear screen. - let background_color = config.colors.primary.background; + let background_color = config.ui_config.colors.primary.background; renderer.with_api(&config.ui_config, &size_info, |api| { api.clear(background_color); }); @@ -307,6 +326,8 @@ impl Display { #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] wayland_event_queue, cursor_hidden: false, + visual_bell: VisualBell::from(&config.ui_config.bell), + colors: List::from(&config.ui_config.colors), }) } @@ -435,7 +456,7 @@ impl Display { /// A reference to Term whose state is being drawn must be provided. /// /// This call may block if vsync is enabled. - pub fn draw( + pub fn draw( &mut self, terminal: MutexGuard<'_, Term>, message_buffer: &MessageBuffer, @@ -452,16 +473,17 @@ impl Display { let cursor_hidden = self.cursor_hidden || search_state.regex().is_some(); // Collect renderable content before the terminal is dropped. - let mut content = terminal.renderable_content(config, !cursor_hidden); + let dfas = search_state.dfas(); + let colors = &self.colors; + let mut content = RenderableContent::new(&terminal, dfas, config, colors, !cursor_hidden); let mut grid_cells = Vec::new(); while let Some(cell) = content.next() { grid_cells.push(cell); } + let background_color = content.color(NamedColor::Background as usize); + let display_offset = content.display_offset(); let cursor = content.cursor(); - let visual_bell_intensity = terminal.visual_bell.intensity(); - let display_offset = terminal.grid().display_offset(); - let background_color = terminal.background_color(); let cursor_point = terminal.grid().cursor.point; let total_lines = terminal.grid().total_lines(); let metrics = self.glyph_cache.font_metrics(); @@ -496,9 +518,9 @@ impl Display { if cell.is_match && viewport_match .as_ref() - .map_or(false, |viewport_match| viewport_match.contains(&cell.point())) + .map_or(false, |viewport_match| viewport_match.contains(&cell.point)) { - let colors = config.colors.search.focused_match; + let colors = config.ui_config.colors.search.focused_match; let match_fg = colors.foreground.color(cell.fg, cell.bg); cell.bg = colors.background.color(cell.fg, cell.bg); cell.fg = match_fg; @@ -560,13 +582,14 @@ impl Display { } // Push visual bell after url/underline/strikeout rects. + let visual_bell_intensity = self.visual_bell.intensity(); if visual_bell_intensity != 0. { let visual_bell_rect = RenderRect::new( 0., 0., size_info.width(), size_info.height(), - config.bell().color, + config.ui_config.bell.color, visual_bell_intensity as f32, ); rects.push(visual_bell_rect); @@ -581,8 +604,8 @@ impl Display { let y = size_info.cell_height().mul_add(start_line.0 as f32, size_info.padding_y()); let bg = match message.ty() { - MessageType::Error => config.colors.normal.red, - MessageType::Warning => config.colors.normal.yellow, + MessageType::Error => config.ui_config.colors.normal.red, + MessageType::Warning => config.ui_config.colors.normal.yellow, }; let message_bar_rect = @@ -596,7 +619,7 @@ impl Display { // Relay messages to the user. let glyph_cache = &mut self.glyph_cache; - let fg = config.colors.primary.background; + let fg = config.ui_config.colors.primary.background; for (i, message_text) in text.iter().enumerate() { let point = Point::new(start_line + i, Column(0)); self.renderer.with_api(&config.ui_config, &size_info, |mut api| { @@ -650,6 +673,12 @@ impl Display { } } + /// Update to a new configuration. + pub fn update_config(&mut self, config: &Config) { + self.visual_bell.update_config(&config.ui_config.bell); + self.colors = List::from(&config.ui_config.colors); + } + /// Format search regex to account for the cursor and fullwidth characters. fn format_search(size_info: &SizeInfo, search_regex: &str, search_label: &str) -> String { // Add spacers for wide chars. @@ -690,8 +719,8 @@ impl Display { let text = format!("{:<1$}", text, num_cols); let point = Point::new(size_info.screen_lines(), Column(0)); - let fg = config.colors.search_bar_foreground(); - let bg = config.colors.search_bar_background(); + let fg = config.ui_config.colors.search_bar_foreground(); + let bg = config.ui_config.colors.search_bar_background(); self.renderer.with_api(&config.ui_config, &size_info, |mut api| { api.render_string(glyph_cache, point, fg, bg, &text); @@ -708,8 +737,8 @@ impl Display { let timing = format!("{:.3} usec", self.meter.average()); let point = Point::new(size_info.screen_lines() - 2, Column(0)); - let fg = config.colors.primary.background; - let bg = config.colors.normal.red; + let fg = config.ui_config.colors.primary.background; + let bg = config.ui_config.colors.normal.red; self.renderer.with_api(&config.ui_config, &size_info, |mut api| { api.render_string(glyph_cache, point, fg, bg, &timing); @@ -727,12 +756,12 @@ impl Display { ) { let text = format!("[{}/{}]", line, total_lines - 1); let column = Column(size_info.cols().0.saturating_sub(text.len())); - let colors = &config.colors; + let colors = &config.ui_config.colors; let fg = colors.line_indicator.foreground.unwrap_or(colors.primary.background); let bg = colors.line_indicator.background.unwrap_or(colors.primary.foreground); // Do not render anything if it would obscure the vi mode cursor. - if vi_mode_point.map_or(true, |point| point.line.0 != 0 || point.col < column) { + if vi_mode_point.map_or(true, |point| point.line.0 != 0 || point.column < column) { let glyph_cache = &mut self.glyph_cache; self.renderer.with_api(&config.ui_config, &size_info, |mut api| { api.render_string(glyph_cache, Point::new(Line(0), column), fg, bg, &text); diff --git a/alacritty/src/wayland_theme.rs b/alacritty/src/display/wayland_theme.rs similarity index 98% rename from alacritty/src/wayland_theme.rs rename to alacritty/src/display/wayland_theme.rs index 5d3bd922..1932ae01 100644 --- a/alacritty/src/wayland_theme.rs +++ b/alacritty/src/display/wayland_theme.rs @@ -1,8 +1,9 @@ use glutin::platform::unix::{ARGBColor, Button, ButtonState, Element, Theme as WaylandTheme}; -use alacritty_terminal::config::Colors; use alacritty_terminal::term::color::Rgb; +use crate::config::color::Colors; + const INACTIVE_OPACITY: u8 = 127; #[derive(Debug, Clone)] diff --git a/alacritty/src/window.rs b/alacritty/src/display/window.rs similarity index 98% rename from alacritty/src/window.rs rename to alacritty/src/display/window.rs index 3661d406..b500e8f2 100644 --- a/alacritty/src/window.rs +++ b/alacritty/src/display/window.rs @@ -14,9 +14,8 @@ use { wayland_client::{Attached, EventQueue, Proxy}, glutin::platform::unix::EventLoopWindowTargetExtUnix, - alacritty_terminal::config::Colors, - - crate::wayland_theme::AlacrittyWaylandTheme, + crate::config::color::Colors, + crate::display::wayland_theme::AlacrittyWaylandTheme, }; #[rustfmt::skip] @@ -59,7 +58,7 @@ use crate::gl; /// Window icon for `_NET_WM_ICON` property. #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] -static WINDOW_ICON: &[u8] = include_bytes!("../alacritty.png"); +static WINDOW_ICON: &[u8] = include_bytes!("../../alacritty.png"); /// This should match the definition of IDI_ICON from `windows.rc`. #[cfg(windows)] @@ -206,7 +205,7 @@ impl Window { #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] let wayland_surface = if is_wayland { // Apply client side decorations theme. - let theme = AlacrittyWaylandTheme::new(&config.colors); + let theme = AlacrittyWaylandTheme::new(&config.ui_config.colors); windowed_context.window().set_wayland_theme(theme); // Attach surface to Alacritty's internal wayland queue to handle frame callbacks. @@ -422,7 +421,7 @@ impl Window { /// Adjust the IME editor position according to the new location of the cursor. pub fn update_ime_position(&mut self, point: Point, size: &SizeInfo) { - let nspot_x = f64::from(size.padding_x() + point.col.0 as f32 * size.cell_width()); + let nspot_x = f64::from(size.padding_x() + point.column.0 as f32 * size.cell_width()); let nspot_y = f64::from(size.padding_y() + (point.line.0 + 1) as f32 * size.cell_height()); self.window().set_ime_position(PhysicalPosition::new(nspot_x, nspot_y)); diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index d83469ee..26f781dc 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -12,7 +12,7 @@ use std::fs::File; use std::io::Write; use std::mem; use std::ops::RangeInclusive; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[cfg(not(any(target_os = "macos", windows)))] use std::sync::atomic::Ordering; use std::sync::Arc; @@ -35,6 +35,7 @@ use alacritty_terminal::grid::{Dimensions, Scroll}; use alacritty_terminal::index::{Boundary, Column, Direction, Line, Point, Side}; use alacritty_terminal::selection::{Selection, SelectionType}; use alacritty_terminal::sync::FairMutex; +use alacritty_terminal::term::search::{Match, RegexSearch}; use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode}; #[cfg(not(windows))] use alacritty_terminal::tty; @@ -44,6 +45,7 @@ use crate::clipboard::Clipboard; use crate::config; use crate::config::Config; use crate::daemon::start_daemon; +use crate::display::window::Window; use crate::display::{Display, DisplayUpdate}; use crate::input::{self, ActionContext as _, FONT_SIZE_STEP}; #[cfg(target_os = "macos")] @@ -51,7 +53,6 @@ use crate::macos; use crate::message_bar::{Message, MessageBuffer}; use crate::scheduler::{Scheduler, TimerId}; use crate::url::{Url, Urls}; -use crate::window::Window; /// Duration after the last user input until an unlimited search is performed. pub const TYPING_SEARCH_DELAY: Duration = Duration::from_millis(500); @@ -102,13 +103,17 @@ pub struct SearchState { /// Search regex and history. /// - /// When a search is currently active, the first element will be what the user can modify in - /// the current search session. While going through history, the [`history_index`] will point - /// to the element in history which is currently being previewed. + /// During an active search, the first element is the user's current input. + /// + /// While going through history, the [`SearchState::history_index`] will point to the element + /// in history which is currently being previewed. history: VecDeque, /// Current position in the search history. history_index: Option, + + /// Compiled search automatons. + dfas: Option, } impl SearchState { @@ -131,6 +136,11 @@ impl SearchState { self.focused_match.as_ref() } + /// Active search dfas. + pub fn dfas(&self) -> Option<&RegexSearch> { + self.dfas.as_ref() + } + /// Search regex text if a search is active. fn regex_mut(&mut self) -> Option<&mut String> { self.history_index.and_then(move |index| self.history.get_mut(index)) @@ -146,6 +156,7 @@ impl Default for SearchState { history_index: Default::default(), history: Default::default(), origin: Default::default(), + dfas: Default::default(), } } } @@ -154,31 +165,37 @@ pub struct ActionContext<'a, N, T> { pub notifier: &'a mut N, pub terminal: &'a mut Term, pub clipboard: &'a mut Clipboard, - pub size_info: &'a mut SizeInfo, pub mouse: &'a mut Mouse, pub received_count: &'a mut usize, pub suppress_chars: &'a mut bool, pub modifiers: &'a mut ModifiersState, - pub window: &'a mut Window, + pub display: &'a mut Display, pub message_buffer: &'a mut MessageBuffer, pub display_update_pending: &'a mut DisplayUpdate, pub config: &'a mut Config, pub event_loop: &'a EventLoopWindowTarget, - pub urls: &'a Urls, pub scheduler: &'a mut Scheduler, pub search_state: &'a mut SearchState, - cursor_hidden: &'a mut bool, cli_options: &'a CLIOptions, font_size: &'a mut Size, + dirty: bool, } impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionContext<'a, N, T> { + #[inline] fn write_to_pty>>(&mut self, val: B) { self.notifier.notify(val); } + /// Request a redraw. + #[inline] + fn mark_dirty(&mut self) { + self.dirty = true; + } + + #[inline] fn size_info(&self) -> SizeInfo { - *self.size_info + self.display.size_info } fn scroll(&mut self, scroll: Scroll) { @@ -202,8 +219,10 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon { let point = self.size_info().pixels_to_coords(self.mouse().x, self.mouse().y); let cell_side = self.mouse().cell_side; - self.update_selection(Point { line: point.line, col: point.col }, cell_side); + self.update_selection(point, cell_side); } + + self.dirty = true; } fn copy_selection(&mut self, ty: ClipboardType) { @@ -220,7 +239,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon fn clear_selection(&mut self) { self.terminal.selection = None; - self.terminal.dirty = true; + self.dirty = true; } fn update_selection(&mut self, mut point: Point, side: Side) { @@ -243,13 +262,13 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon } self.terminal.selection = Some(selection); - self.terminal.dirty = true; + self.dirty = true; } fn start_selection(&mut self, ty: SelectionType, point: Point, side: Side) { let point = self.terminal.visible_to_buffer(point); self.terminal.selection = Some(Selection::new(ty, point, side)); - self.terminal.dirty = true; + self.dirty = true; } fn toggle_selection(&mut self, ty: SelectionType, point: Point, side: Side) { @@ -259,7 +278,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon }, Some(selection) if !selection.is_empty() => { selection.ty = ty; - self.terminal.dirty = true; + self.dirty = true; }, _ => self.start_selection(ty, point, side), } @@ -269,8 +288,8 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon let x = self.mouse.x as usize; let y = self.mouse.y as usize; - if self.size_info.contains_point(x, y) { - Some(self.size_info.pixels_to_coords(x, y)) + if self.display.size_info.contains_point(x, y) { + Some(self.display.size_info.pixels_to_coords(x, y)) } else { None } @@ -309,12 +328,12 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon #[inline] fn window(&self) -> &Window { - self.window + &self.display.window } #[inline] fn window_mut(&mut self) -> &mut Window { - self.window + &mut self.display.window } #[inline] @@ -387,17 +406,21 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon } } + fn highlighted_url(&self) -> Option<&Url> { + self.display.highlighted_url.as_ref() + } + fn change_font_size(&mut self, delta: f32) { *self.font_size = max(*self.font_size + delta, Size::new(FONT_SIZE_STEP)); let font = self.config.ui_config.font.clone().with_size(*self.font_size); self.display_update_pending.set_font(font); - self.terminal.dirty = true; + self.dirty = true; } fn reset_font_size(&mut self) { *self.font_size = self.config.ui_config.font.size(); self.display_update_pending.set_font(self.config.ui_config.font.clone()); - self.terminal.dirty = true; + self.dirty = true; } #[inline] @@ -405,6 +428,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon if !self.message_buffer.is_empty() { self.display_update_pending.dirty = true; self.message_buffer.pop(); + self.dirty = true; } } @@ -437,7 +461,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon } self.display_update_pending.dirty = true; - self.terminal.dirty = true; + self.dirty = true; } #[inline] @@ -458,8 +482,6 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon #[inline] fn cancel_search(&mut self) { - self.terminal.cancel_search(); - if self.terminal.mode().contains(TermMode::VI) { // Recover pre-search state in vi mode. self.search_reset_state(); @@ -471,6 +493,8 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon self.update_selection(end, Side::Right); } + self.search_state.dfas = None; + self.exit_search(); } @@ -591,6 +615,29 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon self.search_state.origin = origin_relative; } + /// Find the next search match. + fn search_next( + &mut self, + origin: Point, + direction: Direction, + side: Side, + ) -> Option { + self.search_state + .dfas + .as_ref() + .and_then(|dfas| self.terminal.search_next(dfas, origin, direction, side, None)) + } + + #[inline] + fn search_direction(&self) -> Direction { + self.search_state.direction + } + + #[inline] + fn search_active(&self) -> bool { + self.search_state.history_index.is_some() + } + /// Handle keyboard typing start. /// /// This will temporarily disable some features like terminal cursor blinking or the mouse @@ -603,24 +650,27 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon let blink_interval = self.config.cursor.blink_interval(); if let Some(timer) = self.scheduler.get_mut(TimerId::BlinkCursor) { timer.deadline = Instant::now() + Duration::from_millis(blink_interval); - *self.cursor_hidden = false; - self.terminal.dirty = true; + self.display.cursor_hidden = false; + self.dirty = true; } // Hide mouse cursor. if self.config.ui_config.mouse.hide_when_typing { - self.window.set_mouse_visible(false); + self.display.window.set_mouse_visible(false); } } + /// Toggle the vi mode status. #[inline] - fn search_direction(&self) -> Direction { - self.search_state.direction - } + fn toggle_vi_mode(&mut self) { + if !self.terminal.mode().contains(TermMode::VI) { + self.clear_selection(); + } - #[inline] - fn search_active(&self) -> bool { - self.search_state.history_index.is_some() + self.cancel_search(); + self.terminal.toggle_vi_mode(); + + self.dirty = true; } fn message(&self) -> Option<&Message> { @@ -636,7 +686,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon } fn urls(&self) -> &Urls { - self.urls + &self.display.urls } fn clipboard_mut(&mut self) -> &mut Clipboard { @@ -657,22 +707,22 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { // Hide cursor while typing into the search bar. if self.config.ui_config.mouse.hide_when_typing { - self.window.set_mouse_visible(false); + self.display.window.set_mouse_visible(false); } if regex.is_empty() { // Stop search if there's nothing to search for. self.search_reset_state(); - self.terminal.cancel_search(); + self.search_state.dfas = None; } else { - // Create terminal search from the new regex string. - self.terminal.start_search(®ex); + // Create search dfas for the new regex string. + self.search_state.dfas = RegexSearch::new(®ex).ok(); // Update search highlighting. self.goto_match(MAX_SEARCH_WHILE_TYPING); } - self.terminal.dirty = true; + self.dirty = true; } /// Reset terminal to the state before search was started. @@ -697,22 +747,26 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { // Reset vi mode cursor. let mut origin = self.search_state.origin; origin.line = min(origin.line, self.terminal.screen_lines() - 1); - origin.col = min(origin.col, self.terminal.cols() - 1); + origin.column = min(origin.column, self.terminal.cols() - 1); self.terminal.vi_mode_cursor.point = origin; + + self.dirty = true; } /// Jump to the first regex match from the search origin. fn goto_match(&mut self, mut limit: Option) { - if self.search_state.history_index.is_none() { - return; - } + let dfas = match &self.search_state.dfas { + Some(dfas) => dfas, + None => return, + }; // Limit search only when enough lines are available to run into the limit. limit = limit.filter(|&limit| limit <= self.terminal.total_lines()); // Jump to the next match. let direction = self.search_state.direction; - match self.terminal.search_next(self.absolute_origin(), direction, Side::Left, limit) { + let origin = self.absolute_origin(); + match self.terminal.search_next(dfas, origin, direction, Side::Left, limit) { Some(regex_match) => { let old_offset = self.terminal.grid().display_offset() as isize; @@ -752,7 +806,7 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { }, } - self.terminal.dirty = true; + self.dirty = true; } /// Cleanup the search state. @@ -767,7 +821,7 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { self.display_update_pending.dirty = true; self.search_state.history_index = None; - self.terminal.dirty = true; + self.dirty = true; // Clear focused match. self.search_state.focused_match = None; @@ -781,7 +835,7 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { fn absolute_origin(&self) -> Point { let mut relative_origin = self.search_state.origin; relative_origin.line = min(relative_origin.line, self.terminal.screen_lines() - 1); - relative_origin.col = min(relative_origin.col, self.terminal.cols() - 1); + relative_origin.column = min(relative_origin.column, self.terminal.cols() - 1); let mut origin = self.terminal.visible_to_buffer(relative_origin); origin.line = (origin.line as isize + self.search_state.display_offset_delta) as usize; origin @@ -809,8 +863,8 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { TimerId::BlinkCursor, ) } else { - *self.cursor_hidden = false; - self.terminal.dirty = true; + self.display.cursor_hidden = false; + self.dirty = true; } } } @@ -1012,27 +1066,26 @@ impl Processor { notifier: &mut self.notifier, mouse: &mut self.mouse, clipboard: &mut self.clipboard, - size_info: &mut self.display.size_info, received_count: &mut self.received_count, suppress_chars: &mut self.suppress_chars, modifiers: &mut self.modifiers, message_buffer: &mut self.message_buffer, display_update_pending: &mut display_update_pending, - window: &mut self.display.window, + display: &mut self.display, font_size: &mut self.font_size, config: &mut self.config, - urls: &self.display.urls, scheduler: &mut scheduler, search_state: &mut self.search_state, cli_options: &self.cli_options, - cursor_hidden: &mut self.display.cursor_hidden, + dirty: false, event_loop, }; - let mut processor = input::Processor::new(context, &self.display.highlighted_url); + let mut processor = input::Processor::new(context); for event in self.event_queue.drain(..) { Processor::handle_event(event, &mut processor); } + let dirty = processor.ctx.dirty; // Process DisplayUpdate events. if display_update_pending.dirty { @@ -1045,11 +1098,9 @@ impl Processor { return; } - if terminal.dirty { - terminal.dirty = false; - + if dirty { // Request immediate re-draw if visual bell animation is not finished yet. - if !terminal.visual_bell.completed() { + if !self.display.visual_bell.completed() { let event: Event = TerminalEvent::Wakeup.into(); self.event_queue.push(event.into()); @@ -1079,7 +1130,7 @@ impl Processor { /// Doesn't take self mutably due to borrow checking. fn handle_event( event: GlutinEvent<'_, Event>, - processor: &mut input::Processor<'_, T, ActionContext<'_, N, T>>, + processor: &mut input::Processor>, ) where T: EventListener, { @@ -1095,40 +1146,48 @@ impl Processor { // Resize to event's dimensions, since no resize event is emitted on Wayland. display_update_pending.set_dimensions(PhysicalSize::new(width, height)); - processor.ctx.window.dpr = scale_factor; - processor.ctx.terminal.dirty = true; + processor.ctx.window_mut().dpr = scale_factor; + processor.ctx.dirty = true; }, Event::Message(message) => { processor.ctx.message_buffer.push(message); processor.ctx.display_update_pending.dirty = true; - processor.ctx.terminal.dirty = true; + processor.ctx.dirty = true; }, Event::SearchNext => processor.ctx.goto_match(None), Event::ConfigReload(path) => Self::reload_config(&path, processor), Event::Scroll(scroll) => processor.ctx.scroll(scroll), Event::BlinkCursor => { - *processor.ctx.cursor_hidden ^= true; - processor.ctx.terminal.dirty = true; + processor.ctx.display.cursor_hidden ^= true; + processor.ctx.dirty = true; }, Event::TerminalEvent(event) => match event { TerminalEvent::Title(title) => { let ui_config = &processor.ctx.config.ui_config; if ui_config.window.dynamic_title { - processor.ctx.window.set_title(&title); + processor.ctx.window_mut().set_title(&title); } }, TerminalEvent::ResetTitle => { let ui_config = &processor.ctx.config.ui_config; if ui_config.window.dynamic_title { - processor.ctx.window.set_title(&ui_config.window.title); + processor.ctx.display.window.set_title(&ui_config.window.title); } }, - TerminalEvent::Wakeup => processor.ctx.terminal.dirty = true, + TerminalEvent::Wakeup => processor.ctx.dirty = true, TerminalEvent::Bell => { - let bell_command = processor.ctx.config.bell().command.as_ref(); - let _ = bell_command.map(|cmd| start_daemon(cmd.program(), cmd.args())); + // Set window urgency. if processor.ctx.terminal.mode().contains(TermMode::URGENCY_HINTS) { - processor.ctx.window.set_urgent(!processor.ctx.terminal.is_focused); + let focused = processor.ctx.terminal.is_focused; + processor.ctx.window_mut().set_urgent(!focused); + } + + // Ring visual bell. + processor.ctx.display.visual_bell.ring(); + + // Execute bell command. + if let Some(bell_command) = &processor.ctx.config.ui_config.bell.command { + start_daemon(bell_command.program(), bell_command.args()); } }, TerminalEvent::ClipboardStore(clipboard_type, content) => { @@ -1138,6 +1197,10 @@ impl Processor { let text = format(processor.ctx.clipboard.load(clipboard_type).as_str()); processor.ctx.write_to_pty(text.into_bytes()); }, + TerminalEvent::ColorRequest(index, format) => { + let text = format(processor.ctx.display.colors[index]); + processor.ctx.write_to_pty(text.into_bytes()); + }, TerminalEvent::MouseCursorDirty => processor.reset_mouse_cursor(), TerminalEvent::Exit => (), TerminalEvent::CursorBlinkingChange(_) => { @@ -1145,7 +1208,7 @@ impl Processor { }, }, }, - GlutinEvent::RedrawRequested(_) => processor.ctx.terminal.dirty = true, + GlutinEvent::RedrawRequested(_) => processor.ctx.dirty = true, GlutinEvent::WindowEvent { event, window_id, .. } => { match event { WindowEvent::CloseRequested => processor.ctx.terminal.exit(), @@ -1159,37 +1222,37 @@ impl Processor { } processor.ctx.display_update_pending.set_dimensions(size); - processor.ctx.terminal.dirty = true; + processor.ctx.dirty = true; }, WindowEvent::KeyboardInput { input, is_synthetic: false, .. } => { processor.key_input(input); }, - WindowEvent::ReceivedCharacter(c) => processor.received_char(c), - WindowEvent::MouseInput { state, button, .. } => { - processor.ctx.window.set_mouse_visible(true); - processor.mouse_input(state, button); - processor.ctx.terminal.dirty = true; - }, WindowEvent::ModifiersChanged(modifiers) => { processor.modifiers_input(modifiers) }, + WindowEvent::ReceivedCharacter(c) => processor.received_char(c), + WindowEvent::MouseInput { state, button, .. } => { + processor.ctx.window_mut().set_mouse_visible(true); + processor.mouse_input(state, button); + processor.ctx.dirty = true; + }, WindowEvent::CursorMoved { position, .. } => { - processor.ctx.window.set_mouse_visible(true); + processor.ctx.window_mut().set_mouse_visible(true); processor.mouse_moved(position); }, WindowEvent::MouseWheel { delta, phase, .. } => { - processor.ctx.window.set_mouse_visible(true); + processor.ctx.window_mut().set_mouse_visible(true); processor.mouse_wheel_input(delta, phase); }, WindowEvent::Focused(is_focused) => { - if window_id == processor.ctx.window.window_id() { + if window_id == processor.ctx.window().window_id() { processor.ctx.terminal.is_focused = is_focused; - processor.ctx.terminal.dirty = true; + processor.ctx.dirty = true; if is_focused { - processor.ctx.window.set_urgent(false); + processor.ctx.window_mut().set_urgent(false); } else { - processor.ctx.window.set_mouse_visible(true); + processor.ctx.window_mut().set_mouse_visible(true); } processor.ctx.update_cursor_blinking(); @@ -1203,8 +1266,8 @@ impl Processor { WindowEvent::CursorLeft { .. } => { processor.ctx.mouse.inside_text_area = false; - if processor.highlighted_url.is_some() { - processor.ctx.terminal.dirty = true; + if processor.ctx.highlighted_url().is_some() { + processor.ctx.dirty = true; } }, WindowEvent::KeyboardInput { is_synthetic: true, .. } @@ -1253,10 +1316,9 @@ impl Processor { } } - fn reload_config( - path: &PathBuf, - processor: &mut input::Processor<'_, T, ActionContext<'_, N, T>>, - ) where + /// Reload the configuration files from disk. + fn reload_config(path: &Path, processor: &mut input::Processor>) + where T: EventListener, { if !processor.ctx.message_buffer.is_empty() { @@ -1269,6 +1331,7 @@ impl Processor { Err(_) => return, }; + processor.ctx.display.update_config(&config); processor.ctx.terminal.update_config(&config); // Reload cursor if its thickness has changed. @@ -1300,12 +1363,12 @@ impl Processor { if !config.ui_config.window.dynamic_title || processor.ctx.config.ui_config.window.title != config.ui_config.window.title { - processor.ctx.window.set_title(&config.ui_config.window.title); + processor.ctx.window_mut().set_title(&config.ui_config.window.title); } #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] if processor.ctx.event_loop.is_wayland() { - processor.ctx.window.set_wayland_theme(&config.colors); + processor.ctx.window_mut().set_wayland_theme(&config.ui_config.colors); } // Set subpixel anti-aliasing. @@ -1314,14 +1377,14 @@ impl Processor { // Disable shadows for transparent windows on macOS. #[cfg(target_os = "macos")] - processor.ctx.window.set_has_shadow(config.ui_config.background_opacity() >= 1.0); + processor.ctx.window_mut().set_has_shadow(config.ui_config.background_opacity() >= 1.0); *processor.ctx.config = config; // Update cursor blinking. processor.ctx.update_cursor_blinking(); - processor.ctx.terminal.dirty = true; + processor.ctx.dirty = true; } /// Submit the pending changes to the `Display`. diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 155fab07..778dffc7 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -26,17 +26,18 @@ use alacritty_terminal::event::EventListener; use alacritty_terminal::grid::{Dimensions, Scroll}; use alacritty_terminal::index::{Boundary, Column, Direction, Line, Point, Side}; use alacritty_terminal::selection::SelectionType; +use alacritty_terminal::term::search::Match; use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode}; use alacritty_terminal::vi_mode::ViMotion; use crate::clipboard::Clipboard; use crate::config::{Action, Binding, BindingMode, Config, Key, SearchAction, ViAction}; use crate::daemon::start_daemon; +use crate::display::window::Window; use crate::event::{ClickState, Event, Mouse, TYPING_SEARCH_DELAY}; use crate::message_bar::{self, Message}; use crate::scheduler::{Scheduler, TimerId}; use crate::url::{Url, Urls}; -use crate::window::Window; /// Font size change interval. pub const FONT_SIZE_STEP: f32 = 0.5; @@ -54,20 +55,20 @@ const SELECTION_SCROLLING_STEP: f64 = 20.; /// /// An escape sequence may be emitted in case specific keys or key combinations /// are activated. -pub struct Processor<'a, T: EventListener, A: ActionContext> { +pub struct Processor> { pub ctx: A, - pub highlighted_url: &'a Option, _phantom: PhantomData, } pub trait ActionContext { - fn write_to_pty>>(&mut self, data: B); + fn write_to_pty>>(&mut self, _data: B) {} + fn mark_dirty(&mut self) {} fn size_info(&self) -> SizeInfo; - fn copy_selection(&mut self, ty: ClipboardType); - fn start_selection(&mut self, ty: SelectionType, point: Point, side: Side); - fn toggle_selection(&mut self, ty: SelectionType, point: Point, side: Side); - fn update_selection(&mut self, point: Point, side: Side); - fn clear_selection(&mut self); + fn copy_selection(&mut self, _ty: ClipboardType) {} + fn start_selection(&mut self, _ty: SelectionType, _point: Point, _side: Side) {} + fn toggle_selection(&mut self, _ty: SelectionType, _point: Point, _side: Side) {} + fn update_selection(&mut self, _point: Point, _side: Side) {} + fn clear_selection(&mut self) {} fn selection_is_empty(&self) -> bool; fn mouse_mut(&mut self) -> &mut Mouse; fn mouse(&self) -> &Mouse; @@ -75,34 +76,42 @@ pub trait ActionContext { fn received_count(&mut self) -> &mut usize; fn suppress_chars(&mut self) -> &mut bool; fn modifiers(&mut self) -> &mut ModifiersState; - fn scroll(&mut self, scroll: Scroll); + fn scroll(&mut self, _scroll: Scroll) {} fn window(&self) -> &Window; fn window_mut(&mut self) -> &mut Window; fn terminal(&self) -> &Term; fn terminal_mut(&mut self) -> &mut Term; - fn spawn_new_instance(&mut self); - fn change_font_size(&mut self, delta: f32); - fn reset_font_size(&mut self); - fn pop_message(&mut self); + fn spawn_new_instance(&mut self) {} + fn change_font_size(&mut self, _delta: f32) {} + fn reset_font_size(&mut self) {} + fn pop_message(&mut self) {} fn message(&self) -> Option<&Message>; fn config(&self) -> &Config; fn event_loop(&self) -> &EventLoopWindowTarget; fn urls(&self) -> &Urls; - fn launch_url(&self, url: Url); + fn launch_url(&self, _url: Url) {} + fn highlighted_url(&self) -> Option<&Url>; fn mouse_mode(&self) -> bool; fn clipboard_mut(&mut self) -> &mut Clipboard; fn scheduler_mut(&mut self) -> &mut Scheduler; - fn start_search(&mut self, direction: Direction); - fn confirm_search(&mut self); - fn cancel_search(&mut self); - fn search_input(&mut self, c: char); - fn search_pop_word(&mut self); - fn search_history_previous(&mut self); - fn search_history_next(&mut self); - fn advance_search_origin(&mut self, direction: Direction); + fn start_search(&mut self, _direction: Direction) {} + fn confirm_search(&mut self) {} + fn cancel_search(&mut self) {} + fn search_input(&mut self, _c: char) {} + fn search_pop_word(&mut self) {} + fn search_history_previous(&mut self) {} + fn search_history_next(&mut self) {} + fn search_next( + &mut self, + origin: Point, + direction: Direction, + side: Side, + ) -> Option; + fn advance_search_origin(&mut self, _direction: Direction) {} fn search_direction(&self) -> Direction; fn search_active(&self) -> bool; - fn on_typing_start(&mut self); + fn on_typing_start(&mut self) {} + fn toggle_vi_mode(&mut self) {} } trait Execute { @@ -120,8 +129,8 @@ impl Execute for Binding { impl Action { fn toggle_selection(ctx: &mut A, ty: SelectionType) where - T: EventListener, A: ActionContext, + T: EventListener, { let cursor_point = ctx.terminal().vi_mode_cursor.point; ctx.toggle_selection(ty, cursor_point, Side::Left); @@ -151,10 +160,11 @@ impl Execute for Action { start_daemon(program, args); }, - Action::ToggleViMode => ctx.terminal_mut().toggle_vi_mode(), + Action::ToggleViMode => ctx.toggle_vi_mode(), Action::ViMotion(motion) => { ctx.on_typing_start(); - ctx.terminal_mut().vi_motion(motion) + ctx.terminal_mut().vi_motion(motion); + ctx.mark_dirty(); }, Action::ViAction(ViAction::ToggleNormalSelection) => { Self::toggle_selection(ctx, SelectionType::Simple) @@ -183,9 +193,9 @@ impl Execute for Action { Direction::Left => vi_point.sub_absolute(terminal, Boundary::Wrap, 1), }; - let regex_match = terminal.search_next(origin, direction, Side::Left, None); - if let Some(regex_match) = regex_match { + if let Some(regex_match) = ctx.search_next(origin, direction, Side::Left) { ctx.terminal_mut().vi_goto_point(*regex_match.start()); + ctx.mark_dirty(); } }, Action::ViAction(ViAction::SearchPrevious) => { @@ -197,9 +207,9 @@ impl Execute for Action { Direction::Left => vi_point.sub_absolute(terminal, Boundary::Wrap, 1), }; - let regex_match = terminal.search_next(origin, direction, Side::Left, None); - if let Some(regex_match) = regex_match { + if let Some(regex_match) = ctx.search_next(origin, direction, Side::Left) { ctx.terminal_mut().vi_goto_point(*regex_match.start()); + ctx.mark_dirty(); } }, Action::ViAction(ViAction::SearchStart) => { @@ -208,9 +218,9 @@ impl Execute for Action { .visible_to_buffer(terminal.vi_mode_cursor.point) .sub_absolute(terminal, Boundary::Wrap, 1); - let regex_match = terminal.search_next(origin, Direction::Left, Side::Left, None); - if let Some(regex_match) = regex_match { + if let Some(regex_match) = ctx.search_next(origin, Direction::Left, Side::Left) { ctx.terminal_mut().vi_goto_point(*regex_match.start()); + ctx.mark_dirty(); } }, Action::ViAction(ViAction::SearchEnd) => { @@ -219,9 +229,9 @@ impl Execute for Action { .visible_to_buffer(terminal.vi_mode_cursor.point) .add_absolute(terminal, Boundary::Wrap, 1); - let regex_match = terminal.search_next(origin, Direction::Right, Side::Right, None); - if let Some(regex_match) = regex_match { + if let Some(regex_match) = ctx.search_next(origin, Direction::Right, Side::Right) { ctx.terminal_mut().vi_goto_point(*regex_match.end()); + ctx.mark_dirty(); } }, Action::SearchAction(SearchAction::SearchFocusNext) => { @@ -328,6 +338,7 @@ impl Execute for Action { // Move vi mode cursor. ctx.terminal_mut().vi_mode_cursor.point.line = Line(0); ctx.terminal_mut().vi_motion(ViMotion::FirstOccupied); + ctx.mark_dirty(); }, Action::ScrollToBottom => { ctx.scroll(Scroll::Bottom); @@ -339,6 +350,7 @@ impl Execute for Action { // Move to beginning twice, to always jump across linewraps. term.vi_motion(ViMotion::FirstOccupied); term.vi_motion(ViMotion::FirstOccupied); + ctx.mark_dirty(); }, Action::ClearHistory => ctx.terminal_mut().clear_screen(ClearMode::Saved), Action::ClearLogNotice => ctx.pop_message(), @@ -387,9 +399,9 @@ impl From for CursorIcon { } } -impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { - pub fn new(ctx: A, highlighted_url: &'a Option) -> Self { - Self { ctx, highlighted_url, _phantom: Default::default() } +impl> Processor { + pub fn new(ctx: A) -> Self { + Self { ctx, _phantom: Default::default() } } #[inline] @@ -415,7 +427,7 @@ impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { let cell_side = self.get_mouse_side(); let cell_changed = - point.line != self.ctx.mouse().line || point.col != self.ctx.mouse().column; + point.line != self.ctx.mouse().line || point.column != self.ctx.mouse().column; // Update mouse state and check for URL change. let mouse_state = self.mouse_state(); @@ -433,7 +445,7 @@ impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { self.ctx.mouse_mut().inside_text_area = inside_text_area; self.ctx.mouse_mut().cell_side = cell_side; self.ctx.mouse_mut().line = point.line; - self.ctx.mouse_mut().column = point.col; + self.ctx.mouse_mut().column = point.column; // Don't launch URLs if mouse has moved. self.ctx.mouse_mut().block_url_launcher = true; @@ -757,7 +769,7 @@ impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { // Try to restore vi mode cursor position, to keep it above its previous content. let term = self.ctx.terminal_mut(); term.vi_mode_cursor.point = term.grid().clamp_buffer_to_visible(absolute); - term.vi_mode_cursor.point.col = absolute.col; + term.vi_mode_cursor.point.column = absolute.column; // Update selection. if term.mode().contains(TermMode::VI) { @@ -986,12 +998,13 @@ impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { /// Trigger redraw when URL highlight changed. #[inline] fn update_url_state(&mut self, mouse_state: &MouseState) { + let highlighted_url = self.ctx.highlighted_url(); if let MouseState::Url(url) = mouse_state { - if Some(url) != self.highlighted_url.as_ref() { - self.ctx.terminal_mut().dirty = true; + if Some(url) != highlighted_url { + self.ctx.mark_dirty(); } - } else if self.highlighted_url.is_some() { - self.ctx.terminal_mut().dirty = true; + } else if highlighted_url.is_some() { + self.ctx.mark_dirty(); } } @@ -1084,10 +1097,7 @@ mod tests { const KEY: VirtualKeyCode = VirtualKeyCode::Key0; struct MockEventProxy; - - impl EventListener for MockEventProxy { - fn send_event(&self, _event: TerminalEvent) {} - } + impl EventListener for MockEventProxy {} struct ActionContext<'a, T> { pub terminal: &'a mut Term, @@ -1103,39 +1113,14 @@ mod tests { } impl<'a, T: EventListener> super::ActionContext for ActionContext<'a, T> { - fn write_to_pty>>(&mut self, _val: B) {} - - fn update_selection(&mut self, _point: Point, _side: Side) {} - - fn start_selection(&mut self, _ty: SelectionType, _point: Point, _side: Side) {} - - fn toggle_selection(&mut self, _ty: SelectionType, _point: Point, _side: Side) {} - - fn copy_selection(&mut self, _: ClipboardType) {} - - fn clear_selection(&mut self) {} - - fn spawn_new_instance(&mut self) {} - - fn change_font_size(&mut self, _delta: f32) {} - - fn reset_font_size(&mut self) {} - - fn start_search(&mut self, _direction: Direction) {} - - fn confirm_search(&mut self) {} - - fn cancel_search(&mut self) {} - - fn search_input(&mut self, _c: char) {} - - fn search_pop_word(&mut self) {} - - fn search_history_previous(&mut self) {} - - fn search_history_next(&mut self) {} - - fn advance_search_origin(&mut self, _direction: Direction) {} + fn search_next( + &mut self, + _origin: Point, + _direction: Direction, + _side: Side, + ) -> Option { + None + } fn search_direction(&self) -> Direction { Direction::Right @@ -1234,17 +1219,13 @@ mod tests { unimplemented!(); } - fn launch_url(&self, _: Url) { + fn highlighted_url(&self) -> Option<&Url> { unimplemented!(); } fn scheduler_mut(&mut self) -> &mut Scheduler { unimplemented!(); } - - fn on_typing_start(&mut self) { - unimplemented!(); - } } macro_rules! test_clickstate { @@ -1293,7 +1274,7 @@ mod tests { config: &cfg, }; - let mut processor = Processor::new(context, &None); + let mut processor = Processor::new(context); let event: GlutinEvent::<'_, TerminalEvent> = $input; if let GlutinEvent::WindowEvent { diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs index 1bcf64b9..30eff73d 100644 --- a/alacritty/src/main.rs +++ b/alacritty/src/main.rs @@ -32,7 +32,6 @@ use alacritty_terminal::tty; mod cli; mod clipboard; mod config; -mod cursor; mod daemon; mod display; mod event; @@ -41,16 +40,11 @@ mod logging; #[cfg(target_os = "macos")] mod macos; mod message_bar; -mod meter; #[cfg(windows)] mod panic; mod renderer; mod scheduler; mod url; -mod window; - -#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] -mod wayland_theme; mod gl { #![allow(clippy::all)] diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index 39e53b82..1f203332 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -17,11 +17,11 @@ use unicode_width::UnicodeWidthChar; use alacritty_terminal::index::Point; use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::color::Rgb; -use alacritty_terminal::term::render::RenderableCell; use alacritty_terminal::term::SizeInfo; use crate::config::font::{Font, FontDescription}; use crate::config::ui_config::{Delta, UIConfig}; +use crate::display::content::RenderableCell; use crate::gl; use crate::gl::types::*; use crate::renderer::rects::{RectRenderer, RenderRect}; @@ -480,8 +480,8 @@ impl Batch { cell_flags.set(RenderingGlyphFlags::WIDE_CHAR, cell.flags.contains(Flags::WIDE_CHAR)); self.instances.push(InstanceData { - col: cell.column.0 as u16, - row: cell.line.0 as u16, + col: cell.point.column.0 as u16, + row: cell.point.line.0 as u16, top: glyph.top, left: glyph.left, @@ -829,8 +829,7 @@ impl<'a> RenderApi<'a> { .chars() .enumerate() .map(|(i, character)| RenderableCell { - line: point.line, - column: point.col + i, + point: Point::new(point.line, point.column + i), character, zerowidth: None, flags: Flags::empty(), diff --git a/alacritty/src/renderer/rects.rs b/alacritty/src/renderer/rects.rs index cfd17379..80886c95 100644 --- a/alacritty/src/renderer/rects.rs +++ b/alacritty/src/renderer/rects.rs @@ -6,9 +6,9 @@ use crossfont::Metrics; use alacritty_terminal::index::{Column, Point}; use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::color::Rgb; -use alacritty_terminal::term::render::RenderableCell; use alacritty_terminal::term::SizeInfo; +use crate::display::content::RenderableCell; use crate::gl; use crate::gl::types::*; use crate::renderer; @@ -105,8 +105,8 @@ impl RenderLine { mut thickness: f32, color: Rgb, ) -> RenderRect { - let start_x = start.col.0 as f32 * size.cell_width(); - let end_x = (end.col.0 + 1) as f32 * size.cell_width(); + let start_x = start.column.0 as f32 * size.cell_width(); + let end_x = (end.column.0 + 1) as f32 * size.cell_width(); let width = end_x - start_x; // Make sure lines are always visible. @@ -169,16 +169,16 @@ impl RenderLines { } // Include wide char spacer if the current cell is a wide char. - let mut end: Point = cell.into(); + let mut end = cell.point; if cell.flags.contains(Flags::WIDE_CHAR) { - end.col += 1; + end.column += 1; } // Check if there's an active line. if let Some(line) = self.inner.get_mut(&flag).and_then(|lines| lines.last_mut()) { if cell.fg == line.color - && cell.column == line.end.col + 1 - && cell.line == line.end.line + && cell.point.column == line.end.column + 1 + && cell.point.line == line.end.line { // Update the length of the line. line.end = end; @@ -187,7 +187,7 @@ impl RenderLines { } // Start new line if there currently is none. - let line = RenderLine { start: cell.into(), end, color: cell.fg }; + let line = RenderLine { start: cell.point, end, color: cell.fg }; match self.inner.get_mut(&flag) { Some(lines) => lines.push(line), None => { diff --git a/alacritty/src/url.rs b/alacritty/src/url.rs index f4bf8205..d86b514a 100644 --- a/alacritty/src/url.rs +++ b/alacritty/src/url.rs @@ -8,10 +8,10 @@ use urlocator::{UrlLocation, UrlLocator}; use alacritty_terminal::index::{Column, Point}; use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::color::Rgb; -use alacritty_terminal::term::render::RenderableCell; use alacritty_terminal::term::SizeInfo; use crate::config::Config; +use crate::display::content::RenderableCell; use crate::event::Mouse; use crate::renderer::rects::{RenderLine, RenderRect}; @@ -73,12 +73,12 @@ impl Urls { // Update tracked URLs. pub fn update(&mut self, num_cols: Column, cell: &RenderableCell) { - let point: Point = cell.into(); + let point = cell.point; let mut end = point; // Include the following wide char spacer. if cell.flags.contains(Flags::WIDE_CHAR) { - end.col += 1; + end.column += 1; } // Reset URL when empty cells have been skipped. @@ -119,13 +119,13 @@ impl Urls { (UrlLocation::Url(_length, end_offset), UrlLocation::Url(..)) => { self.extend_url(point, end, cell.fg, end_offset); }, - (UrlLocation::Scheme, _) => self.scheme_buffer.push((cell.into(), cell.fg)), + (UrlLocation::Scheme, _) => self.scheme_buffer.push((cell.point, cell.fg)), (UrlLocation::Reset, _) => self.reset(), _ => (), } // Reset at un-wrapped linebreak. - if cell.column + 1 == num_cols && !cell.flags.contains(Flags::WRAPLINE) { + if cell.point.column + 1 == num_cols && !cell.flags.contains(Flags::WRAPLINE) { self.reset(); } } @@ -202,8 +202,7 @@ mod tests { .map(|(i, character)| RenderableCell { character, zerowidth: None, - line: Line(0), - column: Column(i), + point: Point::new(Line(0), Column(i)), fg: Default::default(), bg: Default::default(), bg_alpha: 0., @@ -227,8 +226,8 @@ mod tests { } let url = urls.urls.first().unwrap(); - assert_eq!(url.start().col, Column(5)); - assert_eq!(url.end().col, Column(23)); + assert_eq!(url.start().column, Column(5)); + assert_eq!(url.end().column, Column(23)); } #[test] @@ -244,14 +243,14 @@ mod tests { assert_eq!(urls.urls.len(), 3); - assert_eq!(urls.urls[0].start().col, Column(5)); - assert_eq!(urls.urls[0].end().col, Column(9)); + assert_eq!(urls.urls[0].start().column, Column(5)); + assert_eq!(urls.urls[0].end().column, Column(9)); - assert_eq!(urls.urls[1].start().col, Column(11)); - assert_eq!(urls.urls[1].end().col, Column(15)); + assert_eq!(urls.urls[1].start().column, Column(11)); + assert_eq!(urls.urls[1].end().column, Column(15)); - assert_eq!(urls.urls[2].start().col, Column(17)); - assert_eq!(urls.urls[2].end().col, Column(21)); + assert_eq!(urls.urls[2].start().column, Column(17)); + assert_eq!(urls.urls[2].end().column, Column(21)); } #[test] @@ -267,10 +266,10 @@ mod tests { assert_eq!(urls.urls.len(), 2); - assert_eq!(urls.urls[0].start().col, Column(5)); - assert_eq!(urls.urls[0].end().col, Column(17)); + assert_eq!(urls.urls[0].start().column, Column(5)); + assert_eq!(urls.urls[0].end().column, Column(17)); - assert_eq!(urls.urls[1].start().col, Column(20)); - assert_eq!(urls.urls[1].end().col, Column(28)); + assert_eq!(urls.urls[1].start().column, Column(20)); + assert_eq!(urls.urls[1].end().column, Column(28)); } } diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml index ee228a3f..41b8b1f8 100644 --- a/alacritty_terminal/Cargo.toml +++ b/alacritty_terminal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alacritty_terminal" -version = "0.12.1-dev" +version = "0.13.0-dev" authors = ["Christian Duerr ", "Joe Wilm "] license = "Apache-2.0" description = "Library for writing terminal emulators" diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs index 2bd445ea..8f2264af 100644 --- a/alacritty_terminal/src/ansi.rs +++ b/alacritty_terminal/src/ansi.rs @@ -306,7 +306,7 @@ pub trait Handler { fn set_color(&mut self, _: usize, _: Rgb) {} /// Write a foreground/background color escape sequence with the current color. - fn dynamic_color_sequence(&mut self, _: &mut W, _: u8, _: usize, _: &str) {} + fn dynamic_color_sequence(&mut self, _: u8, _: usize, _: &str) {} /// Reset an indexed color to original value. fn reset_color(&mut self, _: usize) {} @@ -778,10 +778,10 @@ where } #[inline] - fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, _c: char) { + fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, action: char) { debug!( - "[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}", - params, intermediates, ignore + "[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}, action: {:?}", + params, intermediates, ignore, action ); } @@ -798,7 +798,6 @@ where // TODO replace OSC parsing with parser combinators. #[inline] fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { - let writer = &mut self.writer; let terminator = if bell_terminated { "\x07" } else { "\x1b\\" }; fn unhandled(params: &[&[u8]]) { @@ -868,7 +867,6 @@ where self.handler.set_color(index, color); } else if param == b"?" { self.handler.dynamic_color_sequence( - writer, dynamic_code, index, terminator, diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs index 9b6f695f..59449faa 100644 --- a/alacritty_terminal/src/config/mod.rs +++ b/alacritty_terminal/src/config/mod.rs @@ -6,14 +6,10 @@ use serde::Deserialize; use alacritty_config_derive::ConfigDeserialize; -mod bell; -mod colors; mod scrolling; use crate::ansi::{CursorShape, CursorStyle}; -pub use crate::config::bell::{BellAnimation, BellConfig}; -pub use crate::config::colors::Colors; pub use crate::config::scrolling::Scrolling; pub const LOG_TARGET_CONFIG: &str = "alacritty_config_derive"; @@ -27,11 +23,6 @@ pub struct Config { /// TERM env variable. pub env: HashMap, - /// Should draw bold text with brighter colors instead of bold font. - pub draw_bold_text_with_bright_colors: bool, - - pub colors: Colors, - pub selection: Selection, /// Path to a shell program to run on startup. @@ -53,19 +44,6 @@ pub struct Config { /// Remain open after child process exits. #[config(skip)] pub hold: bool, - - /// Bell configuration. - bell: BellConfig, - - #[config(deprecated = "use `bell` instead")] - pub visual_bell: Option, -} - -impl Config { - #[inline] - pub fn bell(&self) -> &BellConfig { - self.visual_bell.as_ref().unwrap_or(&self.bell) - } } #[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)] @@ -190,9 +168,9 @@ impl CursorBlinking { } } -impl Into for CursorBlinking { - fn into(self) -> bool { - self == Self::On || self == Self::Always +impl From for bool { + fn from(blinking: CursorBlinking) -> bool { + blinking == CursorBlinking::On || blinking == CursorBlinking::Always } } diff --git a/alacritty_terminal/src/event.rs b/alacritty_terminal/src/event.rs index 351b7bc2..a1252570 100644 --- a/alacritty_terminal/src/event.rs +++ b/alacritty_terminal/src/event.rs @@ -2,18 +2,49 @@ use std::borrow::Cow; use std::fmt::{self, Debug, Formatter}; use std::sync::Arc; +use crate::term::color::Rgb; use crate::term::{ClipboardType, SizeInfo}; +/// Terminal event. +/// +/// These events instruct the UI over changes that can't be handled by the terminal emulation layer +/// itself. #[derive(Clone)] pub enum Event { + /// Grid has changed possibly requiring a mouse cursor shape change. MouseCursorDirty, + + /// Window title change. Title(String), + + /// Reset to the default window title. ResetTitle, + + /// Request to store a text string in the clipboard. ClipboardStore(ClipboardType, String), + + /// Request to write the contents of the clipboard to the PTY. + /// + /// The attached function is a formatter which will corectly transform the clipboard content + /// into the expected escape sequence format. ClipboardLoad(ClipboardType, Arc String + Sync + Send + 'static>), + + /// Request to write the RGB value of a color to the PTY. + /// + /// The attached function is a formatter which will corectly transform the RGB color into the + /// expected escape sequence format. + ColorRequest(usize, Arc String + Sync + Send + 'static>), + + /// Cursor blinking state has changed. CursorBlinkingChange(bool), + + /// New terminal content available. Wakeup, + + /// Terminal bell ring. Bell, + + /// Shutdown request. Exit, } @@ -25,6 +56,7 @@ impl Debug for Event { Event::ResetTitle => write!(f, "ResetTitle"), Event::ClipboardStore(ty, text) => write!(f, "ClipboardStore({:?}, {})", ty, text), Event::ClipboardLoad(ty, _) => write!(f, "ClipboardLoad({:?})", ty), + Event::ColorRequest(index, _) => write!(f, "ColorRequest({})", index), Event::Wakeup => write!(f, "Wakeup"), Event::Bell => write!(f, "Bell"), Event::Exit => write!(f, "Exit"), @@ -48,5 +80,9 @@ pub trait OnResize { /// Event Loop for notifying the renderer about terminal events. pub trait EventListener { - fn send_event(&self, event: Event); + fn send_event(&self, _event: Event) {} } + +/// Placeholder implementation for tests. +#[cfg(test)] +impl EventListener for () {} diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs index 4b3c86dc..7949489a 100644 --- a/alacritty_terminal/src/grid/mod.rs +++ b/alacritty_terminal/src/grid/mod.rs @@ -1,7 +1,8 @@ //! A specialized 2D grid implementation optimized for use in a terminal. use std::cmp::{max, min}; -use std::ops::{Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo}; +use std::iter::{Map, TakeWhile}; +use std::ops::{Bound, Deref, Index, IndexMut, Range, RangeBounds, RangeInclusive}; use serde::{Deserialize, Serialize}; @@ -18,37 +19,6 @@ mod tests; pub use self::row::Row; use self::storage::Storage; -/// Bidirectional iterator. -pub trait BidirectionalIterator: Iterator { - fn prev(&mut self) -> Option; -} - -/// An item in the grid along with its Line and Column. -pub struct Indexed { - pub inner: T, - pub line: Line, - pub column: Column, -} - -impl Deref for Indexed { - type Target = T; - - #[inline] - fn deref(&self) -> &T { - &self.inner - } -} - -impl ::std::cmp::PartialEq for Grid { - fn eq(&self, other: &Self) -> bool { - // Compare struct fields and check result of grid comparison. - self.raw.eq(&other.raw) - && self.cols.eq(&other.cols) - && self.lines.eq(&other.lines) - && self.display_offset.eq(&other.display_offset) - } -} - pub trait GridCell: Sized { /// Check if the cell contains any content. fn is_empty(&self) -> bool; @@ -99,6 +69,15 @@ impl IndexMut for Charsets { } } +#[derive(Debug, Copy, Clone)] +pub enum Scroll { + Delta(isize), + PageUp, + PageDown, + Top, + Bottom, +} + /// Grid based terminal content storage. /// /// ```notrust @@ -157,15 +136,6 @@ pub struct Grid { max_scroll_limit: usize, } -#[derive(Debug, Copy, Clone)] -pub enum Scroll { - Delta(isize), - PageUp, - PageDown, - Top, - Bottom, -} - impl Grid { pub fn new(lines: Line, cols: Column, max_scroll_limit: usize) -> Grid { Grid { @@ -341,15 +311,15 @@ impl Grid { D: PartialEq, { // Determine how many lines to scroll up by. - let end = Point { line: 0, col: self.cols() }; + let end = Point { line: 0, column: self.cols() }; let mut iter = self.iter_from(end); while let Some(cell) = iter.prev() { - if !cell.is_empty() || iter.cur.line >= *self.lines { + if !cell.is_empty() || cell.point.line >= *self.lines { break; } } - debug_assert!(iter.cur.line <= *self.lines); - let positions = self.lines - iter.cur.line; + debug_assert!(iter.point.line <= *self.lines); + let positions = self.lines - iter.point.line; let region = Line(0)..self.screen_lines(); // Reset display offset. @@ -383,8 +353,33 @@ impl Grid { } } -#[allow(clippy::len_without_is_empty)] impl Grid { + /// Reset a visible region within the grid. + pub fn reset_region>(&mut self, bounds: R) + where + T: ResetDiscriminant + GridCell + Clone + Default, + D: PartialEq, + { + let start = match bounds.start_bound() { + Bound::Included(line) => *line, + Bound::Excluded(line) => *line + 1, + Bound::Unbounded => Line(0), + }; + + let end = match bounds.end_bound() { + Bound::Included(line) => *line + 1, + Bound::Excluded(line) => *line, + Bound::Unbounded => self.screen_lines(), + }; + + debug_assert!(start < self.screen_lines()); + debug_assert!(end <= self.screen_lines()); + + for row in start.0..end.0 { + self.raw[Line(row)].reset(&self.cursor.template); + } + } + /// Clamp a buffer point to the visible region. pub fn clamp_buffer_to_visible(&self, point: Point) -> Point { if point.line < self.display_offset { @@ -424,12 +419,7 @@ impl Grid { /// Convert viewport relative point to global buffer indexing. #[inline] pub fn visible_to_buffer(&self, point: Point) -> Point { - Point { line: self.lines.0 + self.display_offset - point.line.0 - 1, col: point.col } - } - - #[inline] - pub fn display_iter(&self) -> DisplayIter<'_, T> { - DisplayIter::new(self) + Point { line: self.lines.0 + self.display_offset - point.line.0 - 1, column: point.column } } #[inline] @@ -459,7 +449,27 @@ impl Grid { #[inline] pub fn iter_from(&self, point: Point) -> GridIterator<'_, T> { - GridIterator { grid: self, cur: point } + GridIterator { grid: self, point } + } + + /// Iterator over all visible cells. + #[inline] + pub fn display_iter(&self) -> DisplayIter<'_, T> { + let start = Point::new(self.display_offset + self.lines.0, self.cols() - 1); + let end = Point::new(self.display_offset, self.cols()); + + let iter = GridIterator { grid: self, point: start }; + + let display_offset = self.display_offset; + let lines = self.lines.0; + + let take_while: DisplayIterTakeFun<'_, T> = + Box::new(move |indexed: &Indexed<&T>| indexed.point <= end); + let map: DisplayIterMapFun<'_, T> = Box::new(move |indexed: Indexed<&T>| { + let line = Line(lines + display_offset - indexed.point.line - 1); + Indexed { point: Point::new(line, indexed.point.column), cell: indexed.cell } + }); + iter.take_while(take_while).map(map) } #[inline] @@ -470,7 +480,81 @@ impl Grid { #[inline] pub fn cursor_cell(&mut self) -> &mut T { let point = self.cursor.point; - &mut self[&point] + &mut self[point.line][point.column] + } +} + +impl PartialEq for Grid { + fn eq(&self, other: &Self) -> bool { + // Compare struct fields and check result of grid comparison. + self.raw.eq(&other.raw) + && self.cols.eq(&other.cols) + && self.lines.eq(&other.lines) + && self.display_offset.eq(&other.display_offset) + } +} + +impl Index for Grid { + type Output = Row; + + #[inline] + fn index(&self, index: Line) -> &Row { + &self.raw[index] + } +} + +impl IndexMut for Grid { + #[inline] + fn index_mut(&mut self, index: Line) -> &mut Row { + &mut self.raw[index] + } +} + +impl Index for Grid { + type Output = Row; + + #[inline] + fn index(&self, index: usize) -> &Row { + &self.raw[index] + } +} + +impl IndexMut for Grid { + #[inline] + fn index_mut(&mut self, index: usize) -> &mut Row { + &mut self.raw[index] + } +} + +impl Index> for Grid { + type Output = T; + + #[inline] + fn index(&self, point: Point) -> &T { + &self[point.line][point.column] + } +} + +impl IndexMut> for Grid { + #[inline] + fn index_mut(&mut self, point: Point) -> &mut T { + &mut self[point.line][point.column] + } +} + +impl Index for Grid { + type Output = T; + + #[inline] + fn index(&self, point: Point) -> &T { + &self[point.line][point.column] + } +} + +impl IndexMut for Grid { + #[inline] + fn index_mut(&mut self, point: Point) -> &mut T { + &mut self[point.line][point.column] } } @@ -524,325 +608,85 @@ impl Dimensions for (Line, Column) { } } +#[derive(Debug, PartialEq)] +pub struct Indexed { + pub point: Point, + pub cell: T, +} + +impl Deref for Indexed { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + &self.cell + } +} + +/// Grid cell iterator. pub struct GridIterator<'a, T> { /// Immutable grid reference. grid: &'a Grid, /// Current position of the iterator within the grid. - cur: Point, + point: Point, } impl<'a, T> GridIterator<'a, T> { + /// Current iteratior position. pub fn point(&self) -> Point { - self.cur + self.point } + /// Cell at the current iteratior position. pub fn cell(&self) -> &'a T { - &self.grid[self.cur] + &self.grid[self.point] } } impl<'a, T> Iterator for GridIterator<'a, T> { - type Item = &'a T; + type Item = Indexed<&'a T>; fn next(&mut self) -> Option { let last_col = self.grid.cols() - 1; - - match self.cur { - Point { line, col } if line == 0 && col == last_col => return None, - Point { col, .. } if (col == last_col) => { - self.cur.line -= 1; - self.cur.col = Column(0); + match self.point { + Point { line, column: col } if line == 0 && col == last_col => return None, + Point { column: col, .. } if (col == last_col) => { + self.point.line -= 1; + self.point.column = Column(0); }, - _ => self.cur.col += Column(1), + _ => self.point.column += Column(1), } - Some(&self.grid[self.cur]) + Some(Indexed { cell: &self.grid[self.point], point: self.point }) } } +/// Bidirectional iterator. +pub trait BidirectionalIterator: Iterator { + fn prev(&mut self) -> Option; +} + impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { fn prev(&mut self) -> Option { let last_col = self.grid.cols() - 1; - match self.cur { - Point { line, col: Column(0) } if line == self.grid.total_lines() - 1 => return None, - Point { col: Column(0), .. } => { - self.cur.line += 1; - self.cur.col = last_col; + match self.point { + Point { line, column: Column(0) } if line == self.grid.total_lines() - 1 => { + return None }, - _ => self.cur.col -= Column(1), + Point { column: Column(0), .. } => { + self.point.line += 1; + self.point.column = last_col; + }, + _ => self.point.column -= Column(1), } - Some(&self.grid[self.cur]) + Some(Indexed { cell: &self.grid[self.point], point: self.point }) } } -/// Index active region by line. -impl Index for Grid { - type Output = Row; - - #[inline] - fn index(&self, index: Line) -> &Row { - &self.raw[index] - } -} - -/// Index with buffer offset. -impl Index for Grid { - type Output = Row; - - #[inline] - fn index(&self, index: usize) -> &Row { - &self.raw[index] - } -} - -impl IndexMut for Grid { - #[inline] - fn index_mut(&mut self, index: Line) -> &mut Row { - &mut self.raw[index] - } -} - -impl IndexMut for Grid { - #[inline] - fn index_mut(&mut self, index: usize) -> &mut Row { - &mut self.raw[index] - } -} - -impl<'point, T> Index<&'point Point> for Grid { - type Output = T; - - #[inline] - fn index<'a>(&'a self, point: &Point) -> &'a T { - &self[point.line][point.col] - } -} - -impl<'point, T> IndexMut<&'point Point> for Grid { - #[inline] - fn index_mut<'a, 'b>(&'a mut self, point: &'b Point) -> &'a mut T { - &mut self[point.line][point.col] - } -} - -impl Index> for Grid { - type Output = T; - - #[inline] - fn index(&self, point: Point) -> &T { - &self[point.line][point.col] - } -} - -impl IndexMut> for Grid { - #[inline] - fn index_mut(&mut self, point: Point) -> &mut T { - &mut self[point.line][point.col] - } -} - -/// A subset of lines in the grid. -/// -/// May be constructed using Grid::region(..). -pub struct Region<'a, T> { - start: Line, - end: Line, - raw: &'a Storage, -} - -/// A mutable subset of lines in the grid. -/// -/// May be constructed using Grid::region_mut(..). -pub struct RegionMut<'a, T> { - start: Line, - end: Line, - raw: &'a mut Storage, -} - -impl<'a, T> RegionMut<'a, T> { - /// Call the provided function for every item in this region. - pub fn each(self, func: F) { - for row in self { - for item in row { - func(item) - } - } - } -} - -pub trait IndexRegion { - /// Get an immutable region of Self. - fn region(&self, _: I) -> Region<'_, T>; - - /// Get a mutable region of Self. - fn region_mut(&mut self, _: I) -> RegionMut<'_, T>; -} - -impl IndexRegion, T> for Grid { - fn region(&self, index: Range) -> Region<'_, T> { - assert!(index.start < self.screen_lines()); - assert!(index.end <= self.screen_lines()); - assert!(index.start <= index.end); - Region { start: index.start, end: index.end, raw: &self.raw } - } - - fn region_mut(&mut self, index: Range) -> RegionMut<'_, T> { - assert!(index.start < self.screen_lines()); - assert!(index.end <= self.screen_lines()); - assert!(index.start <= index.end); - RegionMut { start: index.start, end: index.end, raw: &mut self.raw } - } -} - -impl IndexRegion, T> for Grid { - fn region(&self, index: RangeTo) -> Region<'_, T> { - assert!(index.end <= self.screen_lines()); - Region { start: Line(0), end: index.end, raw: &self.raw } - } - - fn region_mut(&mut self, index: RangeTo) -> RegionMut<'_, T> { - assert!(index.end <= self.screen_lines()); - RegionMut { start: Line(0), end: index.end, raw: &mut self.raw } - } -} - -impl IndexRegion, T> for Grid { - fn region(&self, index: RangeFrom) -> Region<'_, T> { - assert!(index.start < self.screen_lines()); - Region { start: index.start, end: self.screen_lines(), raw: &self.raw } - } - - fn region_mut(&mut self, index: RangeFrom) -> RegionMut<'_, T> { - assert!(index.start < self.screen_lines()); - RegionMut { start: index.start, end: self.screen_lines(), raw: &mut self.raw } - } -} - -impl IndexRegion for Grid { - fn region(&self, _: RangeFull) -> Region<'_, T> { - Region { start: Line(0), end: self.screen_lines(), raw: &self.raw } - } - - fn region_mut(&mut self, _: RangeFull) -> RegionMut<'_, T> { - RegionMut { start: Line(0), end: self.screen_lines(), raw: &mut self.raw } - } -} - -pub struct RegionIter<'a, T> { - end: Line, - cur: Line, - raw: &'a Storage, -} - -pub struct RegionIterMut<'a, T> { - end: Line, - cur: Line, - raw: &'a mut Storage, -} - -impl<'a, T> IntoIterator for Region<'a, T> { - type IntoIter = RegionIter<'a, T>; - type Item = &'a Row; - - fn into_iter(self) -> Self::IntoIter { - RegionIter { end: self.end, cur: self.start, raw: self.raw } - } -} - -impl<'a, T> IntoIterator for RegionMut<'a, T> { - type IntoIter = RegionIterMut<'a, T>; - type Item = &'a mut Row; - - fn into_iter(self) -> Self::IntoIter { - RegionIterMut { end: self.end, cur: self.start, raw: self.raw } - } -} - -impl<'a, T> Iterator for RegionIter<'a, T> { - type Item = &'a Row; - - fn next(&mut self) -> Option { - if self.cur < self.end { - let index = self.cur; - self.cur += 1; - Some(&self.raw[index]) - } else { - None - } - } -} - -impl<'a, T> Iterator for RegionIterMut<'a, T> { - type Item = &'a mut Row; - - fn next(&mut self) -> Option { - if self.cur < self.end { - let index = self.cur; - self.cur += 1; - unsafe { Some(&mut *(&mut self.raw[index] as *mut _)) } - } else { - None - } - } -} - -/// Iterates over the visible area accounting for buffer transform. -pub struct DisplayIter<'a, T> { - grid: &'a Grid, - offset: usize, - limit: usize, - col: Column, - line: Line, -} - -impl<'a, T: 'a> DisplayIter<'a, T> { - pub fn new(grid: &'a Grid) -> DisplayIter<'a, T> { - let offset = grid.display_offset + *grid.screen_lines() - 1; - let limit = grid.display_offset; - let col = Column(0); - let line = Line(0); - - DisplayIter { grid, offset, col, limit, line } - } - - pub fn offset(&self) -> usize { - self.offset - } - - pub fn point(&self) -> Point { - Point::new(self.line, self.col) - } -} - -impl<'a, T: 'a> Iterator for DisplayIter<'a, T> { - type Item = Indexed<&'a T>; - - #[inline] - fn next(&mut self) -> Option { - // Return None if we've reached the end. - if self.offset == self.limit && self.grid.cols() == self.col { - return None; - } - - // Get the next item. - let item = Some(Indexed { - inner: &self.grid.raw[self.offset][self.col], - line: self.line, - column: self.col, - }); - - // Update line/col to point to next item. - self.col += 1; - if self.col == self.grid.cols() && self.offset != self.limit { - self.offset -= 1; - - self.col = Column(0); - self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); - } - - item - } -} +pub type DisplayIter<'a, T> = + Map, DisplayIterTakeFun<'a, T>>, DisplayIterMapFun<'a, T>>; +type DisplayIterTakeFun<'a, T> = Box) -> bool>; +type DisplayIterMapFun<'a, T> = Box) -> Indexed<&'a T, Line>>; diff --git a/alacritty_terminal/src/grid/resize.rs b/alacritty_terminal/src/grid/resize.rs index 1a16e09e..40492c3a 100644 --- a/alacritty_terminal/src/grid/resize.rs +++ b/alacritty_terminal/src/grid/resize.rs @@ -113,7 +113,7 @@ impl Grid { // Remove the linewrap special case, by moving the cursor outside of the grid. if self.cursor.input_needs_wrap && reflow { self.cursor.input_needs_wrap = false; - self.cursor.point.col += 1; + self.cursor.point.column += 1; } let mut rows = self.raw.take_all(); @@ -171,11 +171,11 @@ impl Grid { let mut target = self.cursor.point.sub(cols, num_wrapped); // Clamp to the last column, if no content was reflown with the cursor. - if target.col.0 == 0 && row.is_clear() { + if target.column.0 == 0 && row.is_clear() { self.cursor.input_needs_wrap = true; target = target.sub(cols, 1); } - self.cursor.point.col = target.col; + self.cursor.point.column = target.column; // Get required cursor line changes. Since `num_wrapped` is smaller than `cols` // this will always be either `0` or `1`. @@ -248,7 +248,7 @@ impl Grid { // Remove the linewrap special case, by moving the cursor outside of the grid. if self.cursor.input_needs_wrap && reflow { self.cursor.input_needs_wrap = false; - self.cursor.point.col += 1; + self.cursor.point.column += 1; } let mut new_raw = Vec::with_capacity(self.raw.len()); @@ -262,7 +262,7 @@ impl Grid { // width it is then later reflown. let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0; if i == cursor_buffer_line { - self.cursor.point.col += buffered.len(); + self.cursor.point.column += buffered.len(); } row.append_front(buffered); @@ -274,7 +274,7 @@ impl Grid { Some(wrapped) if reflow => wrapped, _ => { let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0; - if reflow && i == cursor_buffer_line && self.cursor.point.col > cols { + if reflow && i == cursor_buffer_line && self.cursor.point.column > cols { // If there are empty cells before the cursor, we assume it is explicit // whitespace and need to wrap it like normal content. Vec::new() @@ -333,17 +333,17 @@ impl Grid { } else { // Reflow cursor if a line below it is deleted. let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0; - if (i == cursor_buffer_line && self.cursor.point.col < cols) + if (i == cursor_buffer_line && self.cursor.point.column < cols) || i < cursor_buffer_line { self.cursor.point.line.0 = self.cursor.point.line.saturating_sub(1); } // Reflow the cursor if it is on this line beyond the width. - if i == cursor_buffer_line && self.cursor.point.col >= cols { + if i == cursor_buffer_line && self.cursor.point.column >= cols { // Since only a single new line is created, we subtract only `cols` // from the cursor instead of reflowing it completely. - self.cursor.point.col -= cols; + self.cursor.point.column -= cols; } // Make sure new row is at least as long as new width. @@ -363,17 +363,17 @@ impl Grid { // Reflow the primary cursor, or clamp it if reflow is disabled. if !reflow { - self.cursor.point.col = min(self.cursor.point.col, cols - 1); - } else if self.cursor.point.col == cols + self.cursor.point.column = min(self.cursor.point.column, cols - 1); + } else if self.cursor.point.column == cols && !self[self.cursor.point.line][cols - 1].flags().contains(Flags::WRAPLINE) { self.cursor.input_needs_wrap = true; - self.cursor.point.col -= 1; + self.cursor.point.column -= 1; } else { self.cursor.point = self.cursor.point.add(cols, 0); } // Clamp the saved cursor to the grid. - self.saved_cursor.point.col = min(self.saved_cursor.point.col, cols - 1); + self.saved_cursor.point.column = min(self.saved_cursor.point.column, cols - 1); } } diff --git a/alacritty_terminal/src/grid/tests.rs b/alacritty_terminal/src/grid/tests.rs index f178226c..269ab636 100644 --- a/alacritty_terminal/src/grid/tests.rs +++ b/alacritty_terminal/src/grid/tests.rs @@ -123,6 +123,10 @@ fn scroll_down() { // Test that GridIterator works. #[test] fn test_iter() { + let assert_indexed = |value: usize, indexed: Option>| { + assert_eq!(Some(&value), indexed.map(|indexed| indexed.cell)); + }; + let mut grid = Grid::::new(Line(5), Column(5), 0); for i in 0..5 { for j in 0..5 { @@ -130,33 +134,33 @@ fn test_iter() { } } - let mut iter = grid.iter_from(Point { line: 4, col: Column(0) }); + let mut iter = grid.iter_from(Point::new(4, Column(0))); assert_eq!(None, iter.prev()); - assert_eq!(Some(&1), iter.next()); - assert_eq!(Column(1), iter.point().col); + assert_indexed(1, iter.next()); + assert_eq!(Column(1), iter.point().column); assert_eq!(4, iter.point().line); - assert_eq!(Some(&2), iter.next()); - assert_eq!(Some(&3), iter.next()); - assert_eq!(Some(&4), iter.next()); + assert_indexed(2, iter.next()); + assert_indexed(3, iter.next()); + assert_indexed(4, iter.next()); // Test line-wrapping. - assert_eq!(Some(&5), iter.next()); - assert_eq!(Column(0), iter.point().col); + assert_indexed(5, iter.next()); + assert_eq!(Column(0), iter.point().column); assert_eq!(3, iter.point().line); - assert_eq!(Some(&4), iter.prev()); - assert_eq!(Column(4), iter.point().col); + assert_indexed(4, iter.prev()); + assert_eq!(Column(4), iter.point().column); assert_eq!(4, iter.point().line); // Make sure iter.cell() returns the current iterator position. assert_eq!(&4, iter.cell()); // Test that iter ends at end of grid. - let mut final_iter = grid.iter_from(Point { line: 0, col: Column(4) }); + let mut final_iter = grid.iter_from(Point { line: 0, column: Column(4) }); assert_eq!(None, final_iter.next()); - assert_eq!(Some(&23), final_iter.prev()); + assert_indexed(23, final_iter.prev()); } #[test] diff --git a/alacritty_terminal/src/index.rs b/alacritty_terminal/src/index.rs index 4b0d6943..e8e52c80 100644 --- a/alacritty_terminal/src/index.rs +++ b/alacritty_terminal/src/index.rs @@ -8,7 +8,6 @@ use std::ops::{self, Add, AddAssign, Deref, Range, Sub, SubAssign}; use serde::{Deserialize, Serialize}; use crate::grid::Dimensions; -use crate::term::render::RenderableCell; /// The side of a cell. pub type Side = Direction; @@ -48,12 +47,12 @@ pub enum Boundary { #[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, Eq, PartialEq)] pub struct Point { pub line: L, - pub col: Column, + pub column: Column, } impl Point { pub fn new(line: L, col: Column) -> Point { - Point { line, col } + Point { line, column: col } } #[inline] @@ -63,10 +62,10 @@ impl Point { L: Copy + Default + Into + Add + Sub, { let num_cols = num_cols.0; - let line_changes = (rhs + num_cols - 1).saturating_sub(self.col.0) / num_cols; + let line_changes = (rhs + num_cols - 1).saturating_sub(self.column.0) / num_cols; if self.line.into() >= Line(line_changes) { self.line = self.line - line_changes; - self.col = Column((num_cols + self.col.0 - rhs % num_cols) % num_cols); + self.column = Column((num_cols + self.column.0 - rhs % num_cols) % num_cols); self } else { Point::new(L::default(), Column(0)) @@ -80,8 +79,8 @@ impl Point { L: Copy + Default + Into + Add + Sub, { let num_cols = num_cols.0; - self.line = self.line + (rhs + self.col.0) / num_cols; - self.col = Column((self.col.0 + rhs) % num_cols); + self.line = self.line + (rhs + self.column.0) / num_cols; + self.column = Column((self.column.0 + rhs) % num_cols); self } } @@ -96,13 +95,13 @@ impl Point { let total_lines = dimensions.total_lines(); let num_cols = dimensions.cols().0; - self.line += (rhs + num_cols - 1).saturating_sub(self.col.0) / num_cols; - self.col = Column((num_cols + self.col.0 - rhs % num_cols) % num_cols); + self.line += (rhs + num_cols - 1).saturating_sub(self.column.0) / num_cols; + self.column = Column((num_cols + self.column.0 - rhs % num_cols) % num_cols); if self.line >= total_lines { match boundary { Boundary::Clamp => Point::new(total_lines - 1, Column(0)), - Boundary::Wrap => Point::new(self.line - total_lines, self.col), + Boundary::Wrap => Point::new(self.line - total_lines, self.column), } } else { self @@ -117,17 +116,17 @@ impl Point { { let num_cols = dimensions.cols(); - let line_delta = (rhs + self.col.0) / num_cols.0; + let line_delta = (rhs + self.column.0) / num_cols.0; if self.line >= line_delta { self.line -= line_delta; - self.col = Column((self.col.0 + rhs) % num_cols.0); + self.column = Column((self.column.0 + rhs) % num_cols.0); self } else { match boundary { Boundary::Clamp => Point::new(0, num_cols - 1), Boundary::Wrap => { - let col = Column((self.col.0 + rhs) % num_cols.0); + let col = Column((self.column.0 + rhs) % num_cols.0); let line = dimensions.total_lines() + self.line - line_delta; Point::new(line, col) }, @@ -144,7 +143,7 @@ impl PartialOrd for Point { impl Ord for Point { fn cmp(&self, other: &Point) -> Ordering { - match (self.line.cmp(&other.line), self.col.cmp(&other.col)) { + match (self.line.cmp(&other.line), self.column.cmp(&other.column)) { (Ordering::Equal, ord) | (ord, _) => ord, } } @@ -158,7 +157,7 @@ impl PartialOrd for Point { impl Ord for Point { fn cmp(&self, other: &Point) -> Ordering { - match (self.line.cmp(&other.line), self.col.cmp(&other.col)) { + match (self.line.cmp(&other.line), self.column.cmp(&other.column)) { (Ordering::Equal, ord) => ord, (Ordering::Less, _) => Ordering::Greater, (Ordering::Greater, _) => Ordering::Less, @@ -168,31 +167,25 @@ impl Ord for Point { impl From> for Point { fn from(point: Point) -> Self { - Point::new(point.line as isize, point.col) + Point::new(point.line as isize, point.column) } } impl From> for Point { fn from(point: Point) -> Self { - Point::new(Line(point.line), point.col) + Point::new(Line(point.line), point.column) } } impl From> for Point { fn from(point: Point) -> Self { - Point::new(point.line as usize, point.col) + Point::new(point.line as usize, point.column) } } impl From for Point { fn from(point: Point) -> Self { - Point::new(point.line.0, point.col) - } -} - -impl From<&RenderableCell> for Point { - fn from(cell: &RenderableCell) -> Self { - Point::new(cell.line, cell.column) + Point::new(point.line.0, point.column) } } @@ -485,7 +478,7 @@ mod tests { let result = point.sub(num_cols, 1); - assert_eq!(result, Point::new(0, point.col - 1)); + assert_eq!(result, Point::new(0, point.column - 1)); } #[test] @@ -515,7 +508,7 @@ mod tests { let result = point.add(num_cols, 1); - assert_eq!(result, Point::new(0, point.col + 1)); + assert_eq!(result, Point::new(0, point.column + 1)); } #[test] @@ -534,7 +527,7 @@ mod tests { let result = point.add_absolute(&(Line(1), Column(42)), Boundary::Clamp, 1); - assert_eq!(result, Point::new(0, point.col + 1)); + assert_eq!(result, Point::new(0, point.column + 1)); } #[test] @@ -588,7 +581,7 @@ mod tests { let result = point.sub_absolute(&(Line(1), Column(42)), Boundary::Clamp, 1); - assert_eq!(result, Point::new(0, point.col - 1)); + assert_eq!(result, Point::new(0, point.column - 1)); } #[test] diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs index 9402fc21..afb98c0d 100644 --- a/alacritty_terminal/src/selection.rs +++ b/alacritty_terminal/src/selection.rs @@ -10,10 +10,10 @@ use std::mem; use std::ops::{Bound, Range, RangeBounds}; use crate::ansi::CursorShape; -use crate::grid::{Dimensions, Grid, GridCell}; +use crate::grid::{Dimensions, GridCell, Indexed}; use crate::index::{Column, Line, Point, Side}; -use crate::term::cell::Flags; -use crate::term::Term; +use crate::term::cell::{Cell, Flags}; +use crate::term::{RenderableCursor, Term}; /// A Point and side within that point. #[derive(Debug, Copy, Clone, PartialEq)] @@ -43,68 +43,42 @@ impl SelectionRange { pub fn new(start: Point, end: Point, is_block: bool) -> Self { Self { start, end, is_block } } - - /// Check if a point lies within the selection. - pub fn contains(&self, point: Point) -> bool - where - L: PartialEq + PartialOrd, - { - self.start.line <= point.line - && self.end.line >= point.line - && (self.start.col <= point.col || (self.start.line != point.line && !self.is_block)) - && (self.end.col >= point.col || (self.end.line != point.line && !self.is_block)) - } } impl SelectionRange { + /// Check if a point lies within the selection. + pub fn contains(&self, point: Point) -> bool { + self.start.line <= point.line + && self.end.line >= point.line + && (self.start.column <= point.column + || (self.start.line != point.line && !self.is_block)) + && (self.end.column >= point.column || (self.end.line != point.line && !self.is_block)) + } + /// Check if the cell at a point is part of the selection. - pub fn contains_cell( - &self, - grid: &Grid, - point: Point, - cursor_point: Point, - cursor_shape: CursorShape, - ) -> bool - where - T: GridCell, - { + pub fn contains_cell(&self, indexed: &Indexed<&Cell, Line>, cursor: RenderableCursor) -> bool { // Do not invert block cursor at selection boundaries. - if cursor_shape == CursorShape::Block - && cursor_point == point - && (self.start == point - || self.end == point + if cursor.shape == CursorShape::Block + && cursor.point == indexed.point + && (self.start == indexed.point + || self.end == indexed.point || (self.is_block - && ((self.start.line == point.line && self.end.col == point.col) - || (self.end.line == point.line && self.start.col == point.col)))) + && ((self.start.line == indexed.point.line + && self.end.column == indexed.point.column) + || (self.end.line == indexed.point.line + && self.start.column == indexed.point.column)))) { return false; } // Point itself is selected. - if self.contains(point) { + if self.contains(indexed.point) { return true; } - let num_cols = grid.cols(); - - // Convert to absolute coordinates to adjust for the display offset. - let buffer_point = grid.visible_to_buffer(point); - let cell = &grid[buffer_point]; - - // Check if wide char's spacers are selected. - if cell.flags().contains(Flags::WIDE_CHAR) { - let prev = point.sub(num_cols, 1); - let buffer_prev = grid.visible_to_buffer(prev); - let next = point.add(num_cols, 1); - - // Check trailing spacer. - self.contains(next) - // Check line-wrapping, leading spacer. - || (grid[buffer_prev].flags().contains(Flags::LEADING_WIDE_CHAR_SPACER) - && self.contains(prev)) - } else { - false - } + // Check if a wide char's trailing spacer is selected. + indexed.cell.flags().contains(Flags::WIDE_CHAR) + && self.contains(Point::new(indexed.point.line, indexed.point.column + 1)) } } @@ -184,7 +158,7 @@ impl Selection { // Clamp selection to start of region. if start.point.line >= range_top && range_top != num_lines { if self.ty != SelectionType::Block { - start.point.col = Column(0); + start.point.column = Column(0); start.side = Side::Left; } start.point.line = range_top - 1; @@ -204,7 +178,7 @@ impl Selection { // Clamp selection to end of region. if end.point.line < range_bottom { if self.ty != SelectionType::Block { - end.point.col = Column(num_cols - 1); + end.point.column = Column(num_cols - 1); end.side = Side::Right; } end.point.line = range_bottom; @@ -228,7 +202,7 @@ impl Selection { || (start.side == Side::Right && end.side == Side::Left && (start.point.line == end.point.line) - && start.point.col + 1 == end.point.col) + && start.point.column + 1 == end.point.column) }, SelectionType::Block => { let (start, end) = (self.region.start, self.region.end); @@ -236,11 +210,11 @@ impl Selection { // Block selection is empty when the points' columns and sides are identical // or two cells with adjacent columns have the sides right -> left, // regardless of their lines - (start.point.col == end.point.col && start.side == end.side) - || (start.point.col + 1 == end.point.col + (start.point.column == end.point.column && start.side == end.side) + || (start.point.column + 1 == end.point.column && start.side == Side::Right && end.side == Side::Left) - || (end.point.col + 1 == start.point.col + || (end.point.column + 1 == start.point.column && start.side == Side::Left && end.side == Side::Right) }, @@ -277,7 +251,8 @@ impl Selection { let (start, end) = (self.region.start.point, self.region.end.point); let (start_side, end_side) = match self.ty { SelectionType::Block - if start.col > end.col || (start.col == end.col && start.line < end.line) => + if start.column > end.column + || (start.column == end.column && start.line < end.line) => { (Side::Right, Side::Left) }, @@ -317,7 +292,7 @@ impl Selection { /// Bring start and end points in the correct order. fn points_need_swap(start: Point, end: Point) -> bool { - start.line < end.line || start.line == end.line && start.col > end.col + start.line < end.line || start.line == end.line && start.column > end.column } /// Clamp selection inside grid to prevent OOB. @@ -337,7 +312,7 @@ impl Selection { // Clamp to grid if it is still partially visible. if !is_block { start.side = Side::Left; - start.point.col = Column(0); + start.point.column = Column(0); } start.point.line = lines - 1; } @@ -352,7 +327,7 @@ impl Selection { ) -> SelectionRange { if start == end { if let Some(matching) = term.bracket_search(start) { - if (matching.line == start.line && matching.col < start.col) + if (matching.line == start.line && matching.column < start.column) || (matching.line > start.line) { start = matching; @@ -394,20 +369,20 @@ impl Selection { // Remove last cell if selection ends to the left of a cell. if end.side == Side::Left && start.point != end.point { // Special case when selection ends to left of first cell. - if end.point.col == Column(0) { - end.point.col = num_cols - 1; + if end.point.column == Column(0) { + end.point.column = num_cols - 1; end.point.line += 1; } else { - end.point.col -= 1; + end.point.column -= 1; } } // Remove first cell if selection starts at the right of a cell. if start.side == Side::Right && start.point != end.point { - start.point.col += 1; + start.point.column += 1; // Wrap to next line when selection starts to the right of last column. - if start.point.col == num_cols { + if start.point.column == num_cols { start.point = Point::new(start.point.line.saturating_sub(1), Column(0)); } } @@ -421,19 +396,19 @@ impl Selection { } // Always go top-left -> bottom-right. - if start.point.col > end.point.col { + if start.point.column > end.point.column { mem::swap(&mut start.side, &mut end.side); - mem::swap(&mut start.point.col, &mut end.point.col); + mem::swap(&mut start.point.column, &mut end.point.column); } // Remove last cell if selection ends to the left of a cell. - if end.side == Side::Left && start.point != end.point && end.point.col.0 > 0 { - end.point.col -= 1; + if end.side == Side::Left && start.point != end.point && end.point.column.0 > 0 { + end.point.column -= 1; } // Remove first cell if selection starts at the right of a cell. if start.side == Side::Right && start.point != end.point { - start.point.col += 1; + start.point.column += 1; } Some(SelectionRange { start: start.point, end: end.point, is_block: true }) @@ -454,18 +429,12 @@ mod tests { use super::*; use crate::config::MockConfig; - use crate::event::{Event, EventListener}; use crate::index::{Column, Line, Point, Side}; use crate::term::{SizeInfo, Term}; - struct Mock; - impl EventListener for Mock { - fn send_event(&self, _event: Event) {} - } - - fn term(height: usize, width: usize) -> Term { + fn term(height: usize, width: usize) -> Term<()> { let size = SizeInfo::new(width as f32, height as f32, 1.0, 1.0, 0.0, 0.0, false); - Term::new(&MockConfig::default(), size, Mock) + Term::new(&MockConfig::default(), size, ()) } /// Test case of single cell selection. @@ -475,7 +444,7 @@ mod tests { /// 3. [BE] #[test] fn single_cell_left_to_right() { - let location = Point { line: 0, col: Column(0) }; + let location = Point { line: 0, column: Column(0) }; let mut selection = Selection::new(SelectionType::Simple, location, Side::Left); selection.update(location, Side::Right); @@ -493,7 +462,7 @@ mod tests { /// 3. [EB] #[test] fn single_cell_right_to_left() { - let location = Point { line: 0, col: Column(0) }; + let location = Point { line: 0, column: Column(0) }; let mut selection = Selection::new(SelectionType::Simple, location, Side::Right); selection.update(location, Side::Left); diff --git a/alacritty_terminal/src/term/color.rs b/alacritty_terminal/src/term/color.rs index 88af6de6..a67e2c6e 100644 --- a/alacritty_terminal/src/term/color.rs +++ b/alacritty_terminal/src/term/color.rs @@ -7,14 +7,11 @@ use serde::de::{Error as _, Visitor}; use serde::{Deserialize, Deserializer, Serialize}; use serde_yaml::Value; -use crate::ansi; -use crate::config::Colors; +use crate::ansi::NamedColor; +/// Number of terminal colors. pub const COUNT: usize = 269; -/// Factor for automatic computation of dim colors used by terminal. -pub const DIM_FACTOR: f32 = 0.66; - #[derive(Debug, Eq, PartialEq, Copy, Clone, Default, Serialize)] pub struct Rgb { pub r: u8, @@ -42,8 +39,9 @@ impl Rgb { 0.2126 * r_luminance + 0.7152 * g_luminance + 0.0722 * b_luminance } - /// Implementation of W3C's contrast algorithm: - /// https://www.w3.org/TR/WCAG20/#contrast-ratiodef + /// Implementation of [W3C's contrast algorithm]. + /// + /// [W3C's contrast algorithm]: https://www.w3.org/TR/WCAG20/#contrast-ratiodef pub fn contrast(self, other: Rgb) -> f64 { let self_luminance = self.luminance(); let other_luminance = other.luminance(); @@ -231,188 +229,57 @@ impl<'de> Deserialize<'de> for CellRgb { } } -/// List of indexed colors. +/// Array of indexed colors. /// -/// The first 16 entries are the standard ansi named colors. Items 16..232 are -/// the color cube. Items 233..256 are the grayscale ramp. Item 256 is -/// the configured foreground color, item 257 is the configured background -/// color, item 258 is the cursor color. Following that are 8 positions for dim colors. -/// Item 267 is the bright foreground color, 268 the dim foreground. +/// | Indices | Description | +/// | -------- | ----------------- | +/// | 0..16 | Named ANSI colors | +/// | 16..232 | Color cube | +/// | 233..256 | Grayscale ramp | +/// | 256 | Foreground | +/// | 257 | Background | +/// | 258 | Cursor | +/// | 259..267 | Dim colors | +/// | 267 | Bright foreground | +/// | 268 | Dim background | #[derive(Copy, Clone)] -pub struct List([Rgb; COUNT]); +pub struct Colors([Option; 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 Default for Colors { + fn default() -> Self { + Self([None; COUNT]) } } -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[..]") - } -} - -impl Index for List { - type Output = Rgb; +impl Index for Colors { + type Output = Option; #[inline] - fn index(&self, idx: ansi::NamedColor) -> &Self::Output { - &self.0[idx as usize] + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] } } -impl IndexMut for List { +impl IndexMut for Colors { #[inline] - fn index_mut(&mut self, idx: ansi::NamedColor) -> &mut Self::Output { - &mut self.0[idx as usize] + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index] } } -impl Index for List { - type Output = Rgb; +impl Index for Colors { + type Output = Option; #[inline] - fn index(&self, idx: usize) -> &Self::Output { - &self.0[idx] + fn index(&self, index: NamedColor) -> &Self::Output { + &self.0[index as usize] } } -impl IndexMut for List { +impl IndexMut for Colors { #[inline] - fn index_mut(&mut self, idx: usize) -> &mut Self::Output { - &mut self.0[idx] - } -} - -impl Index for List { - type Output = Rgb; - - #[inline] - fn index(&self, idx: u8) -> &Self::Output { - &self.0[idx as usize] - } -} - -impl IndexMut for List { - #[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, -} - -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(&mut self, config: &Config) { - 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 { - /// 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 { 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, @@ -409,9 +273,6 @@ pub struct Term { /// term is set. title_stack: Vec>, - /// Current forward and backward buffer search regexes. - regex_search: Option, - /// Information about cell dimensions. cell_width: usize, cell_height: usize, @@ -425,7 +286,6 @@ impl Term { { self.grid.scroll_display(scroll); self.event_proxy.send_event(Event::MouseCursorDirty); - self.dirty = true; } pub fn new(config: &Config, size: SizeInfo, event_proxy: T) -> Term { @@ -440,11 +300,7 @@ impl Term { 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 Term { 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 Term { 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 Term { 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 Term { 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 Term { 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 Term { text } + #[inline] pub fn visible_to_buffer(&self, point: Point) -> Point { 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 Term { &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, - show_cursor: bool, - ) -> RenderableContent<'_, T, C> { - RenderableContent::new(&self, config, show_cursor) - } - - /// Get the selection within the viewport. - pub fn visible_selection(&self) -> Option> { - 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 Term { 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 Term { 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 Term { self.selection = None; } + /// Get the selection within the viewport. + fn visible_selection(&self) -> Option> { + 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 Term { 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 Term { { 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 Term { // 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 Term { 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 Term { /// Jump to the end of a wide cell. pub fn expand_wide(&self, mut point: Point, direction: Direction) -> Point { - 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 Term { 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 Handler for Term { // 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 Handler for Term { 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 Handler for Term { 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 Handler for Term { 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 Handler for Term { 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 Handler for Term { }; 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 Handler for Term { 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 Handler for Term { 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 Handler for Term { }, 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 Handler for Term { 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 Handler for Term { } 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 Handler for Term { 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 Handler for Term { #[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 Handler for Term { #[inline] fn bell(&mut self) { trace!("Bell"); - self.visual_bell.ring(); self.event_proxy.send_event(Event::Bell); } @@ -1341,7 +1159,7 @@ impl Handler for Term { #[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 Handler for Term { 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 Handler for Term { // 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 Handler for Term { 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 Handler for Term { 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 Handler for Term { #[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( - &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 Handler for Term { // 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 Handler for Term { }, 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 Handler for Term { }, 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 Handler for Term { 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 Handler for Term { 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 Handler for Term { 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 for TabStops { } } +/// Terminal cursor rendering information. +#[derive(Copy, Clone)] +pub struct RenderableCursor { + pub shape: CursorShape, + pub point: Point, +} + +impl RenderableCursor { + fn new(term: &Term) -> 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>, + pub cursor: RenderableCursor, + pub display_offset: usize, + pub colors: &'a color::Colors, + pub mode: TermMode, +} + +impl<'a> RenderableContent<'a> { + fn new(term: &'a Term) -> 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 = 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 = 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 = 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/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 Term { - /// 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, direction: Direction, side: Side, @@ -75,14 +62,15 @@ impl Term { 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, side: Side, max_lines: Option, @@ -100,7 +88,7 @@ impl Term { _ => 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 Term { // 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 Term { /// Find the next match to the left of the origin. fn next_match_left( &self, + dfas: &RegexSearch, origin: Point, side: Side, max_lines: Option, @@ -135,7 +124,7 @@ impl Term { _ => 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 Term { // 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 Term { /// 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, end: Point) -> Option { - let RegexSearch { left_fdfa: fdfa, left_rdfa: rdfa, .. } = self.regex_search.as_ref()?; - + pub fn regex_search_left( + &self, + dfas: &RegexSearch, + start: Point, + end: Point, + ) -> Option { // 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 Term { /// 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, end: Point) -> Option { - let RegexSearch { right_fdfa: fdfa, right_rdfa: rdfa, .. } = self.regex_search.as_ref()?; - + pub fn regex_search_right( + &self, + dfas: &RegexSearch, + start: Point, + end: Point, + ) -> Option { // 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 Term { // 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 Term { 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 Term { 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 Term { /// Find next matching bracket. pub fn bracket_search(&self, point: Point) -> Option> { - 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 Term { 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 Term { 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 Term { // 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 Term { point.line += 1; } - point.col = Column(0); + point.column = Column(0); point } @@ -425,7 +419,7 @@ impl Term { 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, end: Point, direction: Direction, + dfas: &'a RegexSearch, term: &'a Term, done: bool, } @@ -446,8 +441,9 @@ impl<'a, T> RegexIter<'a, T> { end: Point, direction: Direction, term: &'a Term, + 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 self.direction { - Direction::Right => self.term.regex_search_right(self.point, self.end), - Direction::Left => self.term.regex_search_left(self.point, self.end), + Direction::Right => self.term.regex_search_right(self.dfas, self.point, self.end), + Direction::Left => self.term.regex_search_left(self.dfas, self.point, self.end), } } } @@ -498,7 +494,7 @@ mod tests { #[test] fn regex_right() { #[rustfmt::skip] - let mut term = mock_term("\ + let term = mock_term("\ testing66\r\n\ Alacritty\n\ 123\r\n\ @@ -507,18 +503,18 @@ mod tests { "); // Check regex across wrapped and unwrapped lines. - term.regex_search = Some(RegexSearch::new("Ala.*123").unwrap()); + let dfas = RegexSearch::new("Ala.*123").unwrap(); let start = Point::new(3, Column(0)); let end = Point::new(0, Column(2)); let match_start = Point::new(3, Column(0)); let match_end = Point::new(2, Column(2)); - assert_eq!(term.regex_search_right(start, end), Some(match_start..=match_end)); + assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end)); } #[test] fn regex_left() { #[rustfmt::skip] - let mut term = mock_term("\ + let term = mock_term("\ testing66\r\n\ Alacritty\n\ 123\r\n\ @@ -527,209 +523,209 @@ mod tests { "); // Check regex across wrapped and unwrapped lines. - term.regex_search = Some(RegexSearch::new("Ala.*123").unwrap()); + let dfas = RegexSearch::new("Ala.*123").unwrap(); let start = Point::new(0, Column(2)); let end = Point::new(3, Column(0)); let match_start = Point::new(3, Column(0)); let match_end = Point::new(2, Column(2)); - assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end)); + assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } #[test] fn nested_regex() { #[rustfmt::skip] - let mut term = mock_term("\ + let term = mock_term("\ Ala -> Alacritty -> critty\r\n\ critty\ "); // Greedy stopped at linebreak. - term.regex_search = Some(RegexSearch::new("Ala.*critty").unwrap()); + let dfas = RegexSearch::new("Ala.*critty").unwrap(); let start = Point::new(1, Column(0)); let end = Point::new(1, Column(25)); - assert_eq!(term.regex_search_right(start, end), Some(start..=end)); + assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); // Greedy stopped at dead state. - term.regex_search = Some(RegexSearch::new("Ala[^y]*critty").unwrap()); + let dfas = RegexSearch::new("Ala[^y]*critty").unwrap(); let start = Point::new(1, Column(0)); let end = Point::new(1, Column(15)); - assert_eq!(term.regex_search_right(start, end), Some(start..=end)); + assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); } #[test] fn no_match_right() { #[rustfmt::skip] - let mut term = mock_term("\ + let term = mock_term("\ first line\n\ broken second\r\n\ third\ "); - term.regex_search = Some(RegexSearch::new("nothing").unwrap()); + let dfas = RegexSearch::new("nothing").unwrap(); let start = Point::new(2, Column(0)); let end = Point::new(0, Column(4)); - assert_eq!(term.regex_search_right(start, end), None); + assert_eq!(term.regex_search_right(&dfas, start, end), None); } #[test] fn no_match_left() { #[rustfmt::skip] - let mut term = mock_term("\ + let term = mock_term("\ first line\n\ broken second\r\n\ third\ "); - term.regex_search = Some(RegexSearch::new("nothing").unwrap()); + let dfas = RegexSearch::new("nothing").unwrap(); let start = Point::new(0, Column(4)); let end = Point::new(2, Column(0)); - assert_eq!(term.regex_search_left(start, end), None); + assert_eq!(term.regex_search_left(&dfas, start, end), None); } #[test] fn include_linebreak_left() { #[rustfmt::skip] - let mut term = mock_term("\ + let term = mock_term("\ testing123\r\n\ xxx\ "); // Make sure the cell containing the linebreak is not skipped. - term.regex_search = Some(RegexSearch::new("te.*123").unwrap()); + let dfas = RegexSearch::new("te.*123").unwrap(); let start = Point::new(0, Column(0)); let end = Point::new(1, Column(0)); let match_start = Point::new(1, Column(0)); let match_end = Point::new(1, Column(9)); - assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end)); + assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } #[test] fn include_linebreak_right() { #[rustfmt::skip] - let mut term = mock_term("\ + let term = mock_term("\ xxx\r\n\ testing123\ "); // Make sure the cell containing the linebreak is not skipped. - term.regex_search = Some(RegexSearch::new("te.*123").unwrap()); + let dfas = RegexSearch::new("te.*123").unwrap(); let start = Point::new(1, Column(2)); let end = Point::new(0, Column(9)); let match_start = Point::new(0, Column(0)); - assert_eq!(term.regex_search_right(start, end), Some(match_start..=end)); + assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=end)); } #[test] fn skip_dead_cell() { - let mut term = mock_term("alacritty"); + let term = mock_term("alacritty"); // Make sure dead state cell is skipped when reversing. - term.regex_search = Some(RegexSearch::new("alacrit").unwrap()); + let dfas = RegexSearch::new("alacrit").unwrap(); let start = Point::new(0, Column(0)); let end = Point::new(0, Column(6)); - assert_eq!(term.regex_search_right(start, end), Some(start..=end)); + assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); } #[test] fn reverse_search_dead_recovery() { - let mut term = mock_term("zooo lense"); + let term = mock_term("zooo lense"); // Make sure the reverse DFA operates the same as a forward DFA. - term.regex_search = Some(RegexSearch::new("zoo").unwrap()); + let dfas = RegexSearch::new("zoo").unwrap(); let start = Point::new(0, Column(9)); let end = Point::new(0, Column(0)); let match_start = Point::new(0, Column(0)); let match_end = Point::new(0, Column(2)); - assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end)); + assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } #[test] fn multibyte_unicode() { - let mut term = mock_term("testвосибing"); + let term = mock_term("testвосибing"); - term.regex_search = Some(RegexSearch::new("te.*ing").unwrap()); + let dfas = RegexSearch::new("te.*ing").unwrap(); let start = Point::new(0, Column(0)); let end = Point::new(0, Column(11)); - assert_eq!(term.regex_search_right(start, end), Some(start..=end)); + assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); - term.regex_search = Some(RegexSearch::new("te.*ing").unwrap()); + let dfas = RegexSearch::new("te.*ing").unwrap(); let start = Point::new(0, Column(11)); let end = Point::new(0, Column(0)); - assert_eq!(term.regex_search_left(start, end), Some(end..=start)); + assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start)); } #[test] fn fullwidth() { - let mut term = mock_term("a🦇x🦇"); + let term = mock_term("a🦇x🦇"); - term.regex_search = Some(RegexSearch::new("[^ ]*").unwrap()); + let dfas = RegexSearch::new("[^ ]*").unwrap(); let start = Point::new(0, Column(0)); let end = Point::new(0, Column(5)); - assert_eq!(term.regex_search_right(start, end), Some(start..=end)); + assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); - term.regex_search = Some(RegexSearch::new("[^ ]*").unwrap()); + let dfas = RegexSearch::new("[^ ]*").unwrap(); let start = Point::new(0, Column(5)); let end = Point::new(0, Column(0)); - assert_eq!(term.regex_search_left(start, end), Some(end..=start)); + assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start)); } #[test] fn singlecell_fullwidth() { - let mut term = mock_term("🦇"); + let term = mock_term("🦇"); - term.regex_search = Some(RegexSearch::new("🦇").unwrap()); + let dfas = RegexSearch::new("🦇").unwrap(); let start = Point::new(0, Column(0)); let end = Point::new(0, Column(1)); - assert_eq!(term.regex_search_right(start, end), Some(start..=end)); + assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); - term.regex_search = Some(RegexSearch::new("🦇").unwrap()); + let dfas = RegexSearch::new("🦇").unwrap(); let start = Point::new(0, Column(1)); let end = Point::new(0, Column(0)); - assert_eq!(term.regex_search_left(start, end), Some(end..=start)); + assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start)); } #[test] fn wrapping() { #[rustfmt::skip] - let mut term = mock_term("\ + let term = mock_term("\ xxx\r\n\ xxx\ "); - term.regex_search = Some(RegexSearch::new("xxx").unwrap()); + let dfas = RegexSearch::new("xxx").unwrap(); let start = Point::new(0, Column(2)); let end = Point::new(1, Column(2)); let match_start = Point::new(1, Column(0)); - assert_eq!(term.regex_search_right(start, end), Some(match_start..=end)); + assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=end)); - term.regex_search = Some(RegexSearch::new("xxx").unwrap()); + let dfas = RegexSearch::new("xxx").unwrap(); let start = Point::new(1, Column(0)); let end = Point::new(0, Column(0)); let match_end = Point::new(0, Column(2)); - assert_eq!(term.regex_search_left(start, end), Some(end..=match_end)); + assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=match_end)); } #[test] fn wrapping_into_fullwidth() { #[rustfmt::skip] - let mut term = mock_term("\ + let term = mock_term("\ 🦇xx\r\n\ xx🦇\ "); - term.regex_search = Some(RegexSearch::new("🦇x").unwrap()); + let dfas = RegexSearch::new("🦇x").unwrap(); let start = Point::new(0, Column(0)); let end = Point::new(1, Column(3)); let match_start = Point::new(1, Column(0)); let match_end = Point::new(1, Column(2)); - assert_eq!(term.regex_search_right(start, end), Some(match_start..=match_end)); + assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end)); - term.regex_search = Some(RegexSearch::new("x🦇").unwrap()); + let dfas = RegexSearch::new("x🦇").unwrap(); let start = Point::new(1, Column(2)); let end = Point::new(0, Column(0)); let match_start = Point::new(0, Column(1)); let match_end = Point::new(0, Column(3)); - assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end)); + assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } #[test] @@ -741,32 +737,32 @@ mod tests { "); term.grid[1][Column(3)].flags.insert(Flags::LEADING_WIDE_CHAR_SPACER); - term.regex_search = Some(RegexSearch::new("🦇x").unwrap()); + let dfas = RegexSearch::new("🦇x").unwrap(); let start = Point::new(1, Column(0)); let end = Point::new(0, Column(3)); let match_start = Point::new(1, Column(3)); let match_end = Point::new(0, Column(2)); - assert_eq!(term.regex_search_right(start, end), Some(match_start..=match_end)); + assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end)); - term.regex_search = Some(RegexSearch::new("🦇x").unwrap()); + let dfas = RegexSearch::new("🦇x").unwrap(); let start = Point::new(0, Column(3)); let end = Point::new(1, Column(0)); let match_start = Point::new(1, Column(3)); let match_end = Point::new(0, Column(2)); - assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end)); + assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); - term.regex_search = Some(RegexSearch::new("x🦇").unwrap()); + let dfas = RegexSearch::new("x🦇").unwrap(); let start = Point::new(1, Column(0)); let end = Point::new(0, Column(3)); let match_start = Point::new(1, Column(2)); let match_end = Point::new(0, Column(1)); - assert_eq!(term.regex_search_right(start, end), Some(match_start..=match_end)); + assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end)); - term.regex_search = Some(RegexSearch::new("x🦇").unwrap()); + let dfas = RegexSearch::new("x🦇").unwrap(); let start = Point::new(0, Column(3)); let end = Point::new(1, Column(0)); let match_start = Point::new(1, Column(2)); let match_end = Point::new(0, Column(1)); - assert_eq!(term.regex_search_left(start, end), Some(match_start..=match_end)); + assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } } diff --git a/alacritty_terminal/src/vi_mode.rs b/alacritty_terminal/src/vi_mode.rs index 46439a75..1b3390d6 100644 --- a/alacritty_terminal/src/vi_mode.rs +++ b/alacritty_terminal/src/vi_mode.rs @@ -81,13 +81,13 @@ impl ViModeCursor { ViMotion::Left => { buffer_point = term.expand_wide(buffer_point, Direction::Left); let wrap_point = Point::new(buffer_point.line + 1, cols - 1); - if buffer_point.col.0 == 0 + if buffer_point.column.0 == 0 && buffer_point.line + 1 < term.total_lines() && is_wrap(term, wrap_point) { buffer_point = wrap_point; } else { - buffer_point.col = Column(buffer_point.col.saturating_sub(1)); + buffer_point.column = Column(buffer_point.column.saturating_sub(1)); } }, ViMotion::Right => { @@ -95,34 +95,34 @@ impl ViModeCursor { if is_wrap(term, buffer_point) { buffer_point = Point::new(buffer_point.line - 1, Column(0)); } else { - buffer_point.col = min(buffer_point.col + 1, cols - 1); + buffer_point.column = min(buffer_point.column + 1, cols - 1); } }, ViMotion::First => { buffer_point = term.expand_wide(buffer_point, Direction::Left); - while buffer_point.col.0 == 0 + while buffer_point.column.0 == 0 && buffer_point.line + 1 < term.total_lines() && is_wrap(term, Point::new(buffer_point.line + 1, cols - 1)) { buffer_point.line += 1; } - buffer_point.col = Column(0); + buffer_point.column = Column(0); }, ViMotion::Last => buffer_point = last(term, buffer_point), ViMotion::FirstOccupied => buffer_point = first_occupied(term, buffer_point), ViMotion::High => { let line = display_offset + lines.0 - 1; - let col = first_occupied_in_line(term, line).unwrap_or_default().col; + let col = first_occupied_in_line(term, line).unwrap_or_default().column; buffer_point = Point::new(line, col); }, ViMotion::Middle => { let line = display_offset + lines.0 / 2; - let col = first_occupied_in_line(term, line).unwrap_or_default().col; + let col = first_occupied_in_line(term, line).unwrap_or_default().column; buffer_point = Point::new(line, col); }, ViMotion::Low => { let line = display_offset; - let col = first_occupied_in_line(term, line).unwrap_or_default().col; + let col = first_occupied_in_line(term, line).unwrap_or_default().column; buffer_point = Point::new(line, col); }, ViMotion::SemanticLeft => { @@ -181,7 +181,7 @@ impl ViModeCursor { let buffer_point = term.visible_to_buffer(self.point); let mut target_line = buffer_point.line as isize + lines; target_line = max(0, min(term.total_lines() as isize - 1, target_line)); - let col = first_occupied_in_line(term, target_line as usize).unwrap_or_default().col; + let col = first_occupied_in_line(term, target_line as usize).unwrap_or_default().column; // Move cursor. self.point = Point::new(Line(line as usize), col); @@ -200,7 +200,7 @@ fn last(term: &Term, mut point: Point) -> Point { // Find last non-empty cell in the current line. let occupied = last_occupied_in_line(term, point.line).unwrap_or_default(); - if point.col < occupied.col { + if point.column < occupied.column { // Jump to last occupied cell when not already at or beyond it. occupied } else if is_wrap(term, point) { @@ -269,7 +269,7 @@ fn semantic( // Expand semantically based on movement direction. let expand_semantic = |point: Point| { // Do not expand when currently on a semantic escape char. - let cell = &term.grid()[point.line][point.col]; + let cell = &term.grid()[point.line][point.column]; if term.semantic_escape_chars().contains(cell.c) && !cell.flags.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER) { @@ -375,21 +375,21 @@ fn advance(term: &Term, point: Point, direction: Direction) -> Poin /// Check if cell at point contains whitespace. fn is_space(term: &Term, point: Point) -> bool { - let cell = &term.grid()[point.line][point.col]; + let cell = &term.grid()[point.line][point.column]; !cell.flags().intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER) && (cell.c == ' ' || cell.c == '\t') } fn is_wrap(term: &Term, point: Point) -> bool { - point.line != 0 && term.grid()[point.line][point.col].flags.contains(Flags::WRAPLINE) + point.line != 0 && term.grid()[point.line][point.column].flags.contains(Flags::WRAPLINE) } /// Check if point is at screen boundary. fn is_boundary(term: &Term, point: Point, direction: Direction) -> bool { let total_lines = term.total_lines(); let num_cols = term.cols(); - (point.line + 1 >= total_lines && point.col.0 == 0 && direction == Direction::Left) - || (point.line == 0 && point.col + 1 >= num_cols && direction == Direction::Right) + (point.line + 1 >= total_lines && point.column.0 == 0 && direction == Direction::Left) + || (point.line == 0 && point.column + 1 >= num_cols && direction == Direction::Right) } #[cfg(test)] @@ -397,18 +397,12 @@ mod tests { use super::*; use crate::config::MockConfig; - use crate::event::Event; use crate::index::{Column, Line}; use crate::term::{SizeInfo, Term}; - struct Mock; - impl EventListener for Mock { - fn send_event(&self, _event: Event) {} - } - - fn term() -> Term { + fn term() -> Term<()> { let size = SizeInfo::new(20., 20., 1.0, 1.0, 0.0, 0.0, false); - Term::new(&MockConfig::default(), size, Mock) + Term::new(&MockConfig::default(), size, ()) } #[test] @@ -515,7 +509,7 @@ mod tests { assert_eq!(cursor.point, Point::new(Line(0), Column(0))); } - fn motion_semantic_term() -> Term { + fn motion_semantic_term() -> Term<()> { let mut term = term(); term.grid_mut()[Line(0)][Column(0)].c = 'x'; diff --git a/alacritty_terminal/tests/ref.rs b/alacritty_terminal/tests/ref.rs index b55a45e9..a0a831d5 100644 --- a/alacritty_terminal/tests/ref.rs +++ b/alacritty_terminal/tests/ref.rs @@ -84,7 +84,9 @@ struct RefConfig { history_size: u32, } +#[derive(Copy, Clone)] struct Mock; + impl EventListener for Mock { fn send_event(&self, _event: Event) {} }