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