summaryrefslogtreecommitdiffstats
path: root/alacritty_terminal
diff options
context:
space:
mode:
authorChristian Duerr <contact@christianduerr.com>2020-11-13 05:40:09 +0000
committerGitHub <noreply@github.com>2020-11-13 08:40:09 +0300
commit9a7844987693909925b8663d8aa905231d291410 (patch)
treee99b99bcbd0fc40aa1938fc203357321ec024777 /alacritty_terminal
parentb6d94e7b13eb9a14edea843a79c6d86b5b6d8803 (diff)
Add ability to select text during search
This removes the restriction of not being able to select text while the search is active, making it a bit less jarring of a UX when the user tries to interact with the terminal during search. Since the selection was used during vi-less search to highlight the focused match, there is now an option for a focused match color, which uses the inverted normal match color by default. This focused match is used for both search modes. Other mouse interactions are now also possible during search, like opening URLs or clicking inside of mouse mode applications.
Diffstat (limited to 'alacritty_terminal')
-rw-r--r--alacritty_terminal/src/config/colors.rs16
-rw-r--r--alacritty_terminal/src/grid/mod.rs34
-rw-r--r--alacritty_terminal/src/term/mod.rs216
3 files changed, 151 insertions, 115 deletions
diff --git a/alacritty_terminal/src/config/colors.rs b/alacritty_terminal/src/config/colors.rs
index 13a30bef..a292fde4 100644
--- a/alacritty_terminal/src/config/colors.rs
+++ b/alacritty_terminal/src/config/colors.rs
@@ -15,7 +15,7 @@ pub struct Colors {
#[serde(deserialize_with = "failure_default")]
pub vi_mode_cursor: CursorColors,
#[serde(deserialize_with = "failure_default")]
- pub selection: SelectionColors,
+ pub selection: InvertedCellColors,
#[serde(deserialize_with = "failure_default")]
normal: NormalColors,
#[serde(deserialize_with = "failure_default")]
@@ -124,16 +124,16 @@ impl CursorColors {
#[serde(default)]
#[derive(Deserialize, Debug, Copy, Clone, Default, PartialEq, Eq)]
-pub struct SelectionColors {
- #[serde(deserialize_with = "failure_default")]
- text: DefaultBackgroundCellRgb,
+pub struct InvertedCellColors {
+ #[serde(deserialize_with = "failure_default", alias = "text")]
+ foreground: DefaultBackgroundCellRgb,
#[serde(deserialize_with = "failure_default")]
background: DefaultForegroundCellRgb,
}
-impl SelectionColors {
- pub fn text(self) -> CellRgb {
- self.text.0
+impl InvertedCellColors {
+ pub fn foreground(self) -> CellRgb {
+ self.foreground.0
}
pub fn background(self) -> CellRgb {
@@ -147,6 +147,8 @@ pub struct SearchColors {
#[serde(deserialize_with = "failure_default")]
pub matches: MatchColors,
#[serde(deserialize_with = "failure_default")]
+ pub focused_match: InvertedCellColors,
+ #[serde(deserialize_with = "failure_default")]
bar: BarColors,
}
diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs
index 70dbc936..21e7e2f9 100644
--- a/alacritty_terminal/src/grid/mod.rs
+++ b/alacritty_terminal/src/grid/mod.rs
@@ -1,7 +1,7 @@
//! 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, RangeTo};
+use std::ops::{Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo};
use serde::{Deserialize, Serialize};
@@ -368,6 +368,30 @@ impl<T> Grid<T> {
}
}
+ // Clamp a buffer point based range to the viewport.
+ //
+ // This will make sure the content within the range is visible and return `None` whenever the
+ // entire range is outside the visible region.
+ pub fn clamp_buffer_range_to_visible(
+ &self,
+ range: &RangeInclusive<Point<usize>>,
+ ) -> Option<RangeInclusive<Point>> {
+ let start = range.start();
+ let end = range.end();
+
+ // Check if the range is completely offscreen
+ let viewport_end = self.display_offset;
+ let viewport_start = viewport_end + self.lines.0 - 1;
+ if end.line > viewport_start || start.line < viewport_end {
+ return None;
+ }
+
+ let start = self.clamp_buffer_to_visible(*start);
+ let end = self.clamp_buffer_to_visible(*end);
+
+ Some(start..=end)
+ }
+
/// Convert viewport relative point to global buffer indexing.
#[inline]
pub fn visible_to_buffer(&self, point: Point) -> Point<usize> {
@@ -759,12 +783,8 @@ impl<'a, T: 'a> DisplayIter<'a, T> {
self.offset
}
- pub fn column(&self) -> Column {
- self.col
- }
-
- pub fn line(&self) -> Line {
- self.line
+ pub fn point(&self) -> Point {
+ Point::new(self.line, self.col)
}
}
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs
index 6084d8b0..6e20f0e1 100644
--- a/alacritty_terminal/src/term/mod.rs
+++ b/alacritty_terminal/src/term/mod.rs
@@ -136,12 +136,44 @@ pub struct RenderableCellsIter<'a, C> {
inner: DisplayIter<'a, Cell>,
grid: &'a Grid<Cell>,
cursor: RenderableCursor,
+ show_cursor: bool,
config: &'a Config<C>,
colors: &'a color::List,
selection: Option<SelectionRange<Line>>,
search: RenderableSearch<'a>,
}
+impl<'a, C> Iterator for RenderableCellsIter<'a, C> {
+ type Item = RenderableCell;
+
+ /// Gets the next renderable cell.
+ ///
+ /// Skips empty (background) cells and applies any flags to the cell state
+ /// (eg. invert fg and bg colors).
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ if self.show_cursor && self.cursor.point == self.inner.point() {
+ // Handle cursor rendering.
+ if self.cursor.rendered {
+ return self.next_cursor_cell();
+ } else {
+ return self.next_cursor();
+ }
+ } else {
+ // Handle non-cursor cells.
+ let cell = self.inner.next()?;
+ let cell = RenderableCell::new(self, cell);
+
+ // Skip empty cells.
+ if !cell.is_empty() {
+ return Some(cell);
+ }
+ }
+ }
+ }
+}
+
impl<'a, C> RenderableCellsIter<'a, C> {
/// Create the renderable cells iterator.
///
@@ -150,44 +182,63 @@ impl<'a, C> RenderableCellsIter<'a, C> {
fn new<T>(
term: &'a Term<T>,
config: &'a Config<C>,
- selection: Option<SelectionRange>,
+ show_cursor: bool,
) -> RenderableCellsIter<'a, C> {
- let grid = &term.grid;
+ RenderableCellsIter {
+ cursor: term.renderable_cursor(config),
+ show_cursor,
+ grid: &term.grid,
+ inner: term.grid.display_iter(),
+ selection: term.visible_selection(),
+ config,
+ colors: &term.colors,
+ search: RenderableSearch::new(term),
+ }
+ }
- let selection_range = selection.and_then(|span| {
- let (limit_start, limit_end) = if span.is_block {
- (span.start.col, span.end.col)
- } else {
- (Column(0), grid.cols() - 1)
+ /// Get the next renderable cell as the cell below the cursor.
+ fn next_cursor_cell(&mut self) -> Option<RenderableCell> {
+ // Handle cell below cursor.
+ let cell = self.inner.next()?;
+ let mut cell = RenderableCell::new(self, cell);
+
+ if self.cursor.key.style == CursorStyle::Block {
+ cell.fg = match self.cursor.cursor_color {
+ // Apply cursor color, or invert the cursor if it has a fixed background
+ // close to the cell's background.
+ CellRgb::Rgb(col) if col.contrast(cell.bg) < MIN_CURSOR_CONTRAST => cell.bg,
+ _ => self.cursor.text_color.color(cell.fg, cell.bg),
};
+ }
- // Do not render completely offscreen selection.
- let viewport_end = grid.display_offset();
- let viewport_start = viewport_end + grid.screen_lines().0 - 1;
- if span.end.line > viewport_start || span.start.line < viewport_end {
- return None;
- }
+ Some(cell)
+ }
- // Get on-screen lines of the selection's locations.
- let mut start = grid.clamp_buffer_to_visible(span.start);
- let mut end = grid.clamp_buffer_to_visible(span.end);
+ /// Get the next renderable cell as the cursor.
+ fn next_cursor(&mut self) -> Option<RenderableCell> {
+ // Handle cursor.
+ self.cursor.rendered = true;
- // Trim start/end with partially visible block selection.
- start.col = max(limit_start, start.col);
- end.col = min(limit_end, end.col);
+ let buffer_point = self.grid.visible_to_buffer(self.cursor.point);
+ let cell = Indexed {
+ inner: &self.grid[buffer_point.line][buffer_point.col],
+ column: self.cursor.point.col,
+ line: self.cursor.point.line,
+ };
- Some(SelectionRange::new(start, end, span.is_block))
- });
+ let mut cell = RenderableCell::new(self, cell);
+ cell.inner = RenderableCellContent::Cursor(self.cursor.key);
- RenderableCellsIter {
- cursor: term.renderable_cursor(config),
- grid,
- inner: grid.display_iter(),
- selection: selection_range,
- config,
- colors: &term.colors,
- search: RenderableSearch::new(term),
+ // Apply cursor color, or invert the cursor if it has a fixed background close
+ // to the cell's background.
+ if !matches!(
+ self.cursor.cursor_color,
+ CellRgb::Rgb(color) if color.contrast(cell.bg) < MIN_CURSOR_CONTRAST
+ ) {
+ cell.fg = self.cursor.cursor_color.color(cell.fg, cell.bg);
}
+
+ Some(cell)
}
/// Check selection state of a cell.
@@ -265,6 +316,7 @@ pub struct RenderableCell {
pub bg: Rgb,
pub bg_alpha: f32,
pub flags: Flags,
+ pub is_match: bool,
}
impl RenderableCell {
@@ -282,9 +334,11 @@ impl RenderableCell {
Self::compute_bg_alpha(cell.bg)
};
+ let mut is_match = false;
+
if iter.is_selected(point) {
let config_bg = iter.config.colors.selection.background();
- let selected_fg = iter.config.colors.selection.text().color(fg_rgb, bg_rgb);
+ let selected_fg = iter.config.colors.selection.foreground().color(fg_rgb, bg_rgb);
bg_rgb = config_bg.color(fg_rgb, bg_rgb);
fg_rgb = selected_fg;
@@ -306,6 +360,8 @@ impl RenderableCell {
if config_bg != CellRgb::CellBackground {
bg_alpha = 1.0;
}
+
+ is_match = true;
}
let zerowidth = cell.zerowidth().map(|zerowidth| zerowidth.to_vec());
@@ -318,6 +374,7 @@ impl RenderableCell {
bg: bg_rgb,
bg_alpha,
flags: cell.flags,
+ is_match,
}
}
@@ -391,73 +448,6 @@ impl RenderableCell {
}
}
-impl<'a, C> Iterator for RenderableCellsIter<'a, C> {
- type Item = RenderableCell;
-
- /// Gets the next renderable cell.
- ///
- /// Skips empty (background) cells and applies any flags to the cell state
- /// (eg. invert fg and bg colors).
- #[inline]
- fn next(&mut self) -> Option<Self::Item> {
- loop {
- if self.cursor.point.line == self.inner.line()
- && self.cursor.point.col == self.inner.column()
- {
- if self.cursor.rendered {
- // Handle cell below cursor.
- let cell = self.inner.next()?;
- let mut cell = RenderableCell::new(self, cell);
-
- if self.cursor.key.style == CursorStyle::Block {
- cell.fg = match self.cursor.cursor_color {
- // Apply cursor color, or invert the cursor if it has a fixed background
- // close to the cell's background.
- CellRgb::Rgb(col) if col.contrast(cell.bg) < MIN_CURSOR_CONTRAST => {
- cell.bg
- },
- _ => self.cursor.text_color.color(cell.fg, cell.bg),
- };
- }
-
- return Some(cell);
- } else {
- // Handle cursor.
- self.cursor.rendered = true;
-
- let buffer_point = self.grid.visible_to_buffer(self.cursor.point);
- let cell = Indexed {
- inner: &self.grid[buffer_point.line][buffer_point.col],
- column: self.cursor.point.col,
- line: self.cursor.point.line,
- };
-
- let mut cell = RenderableCell::new(self, cell);
- cell.inner = RenderableCellContent::Cursor(self.cursor.key);
-
- // Apply cursor color, or invert the cursor if it has a fixed background close
- // to the cell's background.
- if !matches!(
- self.cursor.cursor_color,
- CellRgb::Rgb(color) if color.contrast(cell.bg) < MIN_CURSOR_CONTRAST
- ) {
- cell.fg = self.cursor.cursor_color.color(cell.fg, cell.bg);
- }
-
- return Some(cell);
- }
- } else {
- let cell = self.inner.next()?;
- let cell = RenderableCell::new(self, cell);
-
- if !cell.is_empty() {
- return Some(cell);
- }
- }
- }
- }
-}
-
pub mod mode {
use bitflags::bitflags;
@@ -1049,10 +1039,34 @@ impl<T> Term<T> {
/// 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.
- pub fn renderable_cells<'b, C>(&'b self, config: &'b Config<C>) -> RenderableCellsIter<'_, C> {
- let selection = self.selection.as_ref().and_then(|s| s.to_range(self));
+ pub fn renderable_cells<'b, C>(
+ &'b self,
+ config: &'b Config<C>,
+ show_cursor: bool,
+ ) -> RenderableCellsIter<'_, C> {
+ RenderableCellsIter::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);
- RenderableCellsIter::new(&self, config, selection)
+ Some(SelectionRange::new(start, end, selection.is_block))
}
/// Resize terminal to new dimensions.
@@ -2836,7 +2850,7 @@ mod benches {
mem::swap(&mut terminal.grid, &mut grid);
b.iter(|| {
- let iter = terminal.renderable_cells(&config);
+ let iter = terminal.renderable_cells(&config, true);
for cell in iter {
test::black_box(cell);
}