summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md3
-rw-r--r--alacritty.yml3
-rw-r--r--alacritty/src/display.rs28
-rw-r--r--alacritty/src/event.rs46
-rw-r--r--alacritty/src/input.rs18
-rw-r--r--alacritty/src/renderer/mod.rs1
-rw-r--r--alacritty/src/url.rs1
-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
10 files changed, 227 insertions, 139 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f8d615b7..5570807d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -100,6 +100,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Unicode 13 support
- Option to run command on bell which can be set in `bell.command`
- Fallback to program specified in `$SHELL` variable on Linux/BSD if it is present
+- Ability to make selections while search is active
### Changed
@@ -120,6 +121,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- URLs are no longer highlighted without a clearly delimited scheme
- Renamed config option `visual_bell` to `bell`
- Moved config option `dynamic_title` to `window.dynamic_title`
+- When searching without vi mode, matches are only selected once search is cancelled
### Fixed
@@ -141,6 +143,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Ingoring of default FreeType properties
- Alacritty crashing at startup when the configured font does not exist
- Font size rounding error
+- Opening URLs while search is active
### Removed
diff --git a/alacritty.yml b/alacritty.yml
index 7795136d..2c96d23e 100644
--- a/alacritty.yml
+++ b/alacritty.yml
@@ -227,6 +227,9 @@
#matches:
# foreground: '#000000'
# background: '#ffffff'
+ #focused_match:
+ # foreground: CellBackground
+ # background: CellForeground
#bar:
# background: '#c5c8c6'
diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs
index 6d683336..af21001e 100644
--- a/alacritty/src/display.rs
+++ b/alacritty/src/display.rs
@@ -27,7 +27,7 @@ use crossfont::{self, Rasterize, Rasterizer};
use alacritty_terminal::event::{EventListener, OnResize};
use alacritty_terminal::index::{Column, Direction, Point};
use alacritty_terminal::selection::Selection;
-use alacritty_terminal::term::{RenderableCell, SizeInfo, Term, TermMode};
+use alacritty_terminal::term::{SizeInfo, Term, TermMode};
use alacritty_terminal::term::{MIN_COLS, MIN_SCREEN_LINES};
use crate::config::font::Font;
@@ -437,7 +437,13 @@ impl Display {
mods: ModifiersState,
search_state: &SearchState,
) {
- let grid_cells: Vec<RenderableCell> = terminal.renderable_cells(config).collect();
+ // Convert search match from viewport to absolute indexing.
+ let search_active = search_state.regex().is_some();
+ let viewport_match = search_state
+ .focused_match()
+ .and_then(|focused_match| terminal.grid().clamp_buffer_range_to_visible(focused_match));
+
+ let grid_cells = terminal.renderable_cells(config, !search_active).collect::<Vec<_>>();
let visual_bell_intensity = terminal.visual_bell.intensity();
let background_color = terminal.background_color();
let cursor_point = terminal.grid().cursor.point;
@@ -471,7 +477,21 @@ impl Display {
self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |mut api| {
// Iterate over all non-empty cells in the grid.
- for cell in grid_cells {
+ for mut cell in grid_cells {
+ // Invert the active match in vi-less search.
+ let cell_point = Point::new(cell.line, cell.column);
+ if cell.is_match
+ && viewport_match
+ .as_ref()
+ .map_or(false, |viewport_match| viewport_match.contains(&cell_point))
+ {
+ let colors = 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;
+ cell.bg_alpha = 1.0;
+ }
+
// Update URL underlines.
urls.update(size_info.cols(), &cell);
@@ -525,7 +545,7 @@ impl Display {
}
if let Some(message) = message_buffer.message() {
- let search_offset = if search_state.regex().is_some() { 1 } else { 0 };
+ let search_offset = if search_active { 1 } else { 0 };
let text = message.text(&size_info);
// Create a new rectangle for the background.
diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs
index 20f087c3..c1f81300 100644
--- a/alacritty/src/event.rs
+++ b/alacritty/src/event.rs
@@ -9,6 +9,7 @@ use std::fs;
use std::fs::File;
use std::io::Write;
use std::mem;
+use std::ops::RangeInclusive;
use std::path::PathBuf;
#[cfg(not(any(target_os = "macos", windows)))]
use std::sync::atomic::Ordering;
@@ -94,6 +95,9 @@ pub struct SearchState {
/// Search origin in viewport coordinates relative to original display offset.
origin: Point,
+
+ /// Focused match during active search.
+ focused_match: Option<RangeInclusive<Point<usize>>>,
}
impl SearchState {
@@ -110,6 +114,11 @@ impl SearchState {
pub fn direction(&self) -> Direction {
self.direction
}
+
+ /// Focused match during vi-less search.
+ pub fn focused_match(&self) -> Option<&RangeInclusive<Point<usize>>> {
+ self.focused_match.as_ref()
+ }
}
impl Default for SearchState {
@@ -118,6 +127,7 @@ impl Default for SearchState {
direction: Direction::Right,
display_offset_delta: 0,
origin: Point::default(),
+ focused_match: None,
regex: None,
}
}
@@ -209,7 +219,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
selection.update(absolute_point, side);
// Move vi cursor and expand selection.
- if self.terminal.mode().contains(TermMode::VI) {
+ if self.terminal.mode().contains(TermMode::VI) && !self.search_active() {
self.terminal.vi_mode_cursor.point = point;
selection.include_all();
}
@@ -385,6 +395,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
let num_lines = self.terminal.screen_lines();
let num_cols = self.terminal.cols();
+ self.search_state.focused_match = None;
self.search_state.regex = Some(String::new());
self.search_state.direction = direction;
@@ -393,9 +404,6 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
self.search_state.origin = if self.terminal.mode().contains(TermMode::VI) {
self.terminal.vi_mode_cursor.point
} else {
- // Clear search, since it is used as the active match.
- self.terminal.selection = None;
-
match direction {
Direction::Right => Point::new(Line(0), Column(0)),
Direction::Left => Point::new(num_lines - 2, num_cols - 1),
@@ -420,9 +428,15 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
fn cancel_search(&mut self) {
self.terminal.cancel_search();
- // Recover pre-search state in vi mode.
if self.terminal.mode().contains(TermMode::VI) {
+ // Recover pre-search state in vi mode.
self.search_reset_state();
+ } else if let Some(focused_match) = &self.search_state.focused_match {
+ // Create a selection for the focused match.
+ let start = self.terminal.grid().clamp_buffer_to_visible(*focused_match.start());
+ let end = self.terminal.grid().clamp_buffer_to_visible(*focused_match.end());
+ self.start_selection(SelectionType::Simple, start, Side::Left);
+ self.update_selection(end, Side::Right);
}
self.exit_search();
@@ -431,8 +445,8 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
#[inline]
fn push_search(&mut self, c: char) {
if let Some(regex) = self.search_state.regex.as_mut() {
- // Prevent previous search selections from sticking around when not in vi mode.
if !self.terminal.mode().contains(TermMode::VI) {
+ // Clear selection so we do not obstruct any matches.
self.terminal.selection = None;
}
@@ -533,8 +547,8 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
self.search_reset_state();
self.terminal.cancel_search();
- // Restart search without vi mode to clear the search origin.
if !self.terminal.mode().contains(TermMode::VI) {
+ // Restart search without vi mode to clear the search origin.
self.start_search(self.search_state.direction);
}
} else {
@@ -554,6 +568,9 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
self.terminal.scroll_display(Scroll::Delta(self.search_state.display_offset_delta));
self.search_state.display_offset_delta = 0;
+ // Clear focused match.
+ self.search_state.focused_match = None;
+
// Reset vi mode cursor.
let mut origin = self.search_state.origin;
origin.line = min(origin.line, self.terminal.screen_lines() - 1);
@@ -586,12 +603,11 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
} else {
// Select the match when vi mode is not active.
self.terminal.scroll_to_point(*regex_match.start());
- let start = self.terminal.grid().clamp_buffer_to_visible(*regex_match.start());
- let end = self.terminal.grid().clamp_buffer_to_visible(*regex_match.end());
- self.start_selection(SelectionType::Simple, start, Side::Left);
- self.update_selection(end, Side::Right);
}
+ // Update the focused match.
+ self.search_state.focused_match = Some(regex_match);
+
// Store number of lines the viewport had to be moved.
let display_offset = self.terminal.grid().display_offset();
self.search_state.display_offset_delta += old_offset - display_offset as isize;
@@ -611,13 +627,16 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
TimerId::DelayedSearch,
);
}
+
+ // Clear focused match.
+ self.search_state.focused_match = None;
},
}
self.search_state.regex = Some(regex);
}
- /// Close the search bar.
+ /// Cleanup the search state.
fn exit_search(&mut self) {
// Move vi cursor down if resize will pull content from history.
if self.terminal.history_size() != 0
@@ -630,6 +649,9 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
self.display_update_pending.dirty = true;
self.search_state.regex = None;
self.terminal.dirty = true;
+
+ // Clear focused match.
+ self.search_state.focused_match = None;
}
/// Get the absolute position of the search origin.
diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs
index 9c75753a..348db610 100644
--- a/alacritty/src/input.rs
+++ b/alacritty/src/input.rs
@@ -360,14 +360,13 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
#[inline]
pub fn mouse_moved(&mut self, position: PhysicalPosition<f64>) {
- let search_active = self.ctx.search_active();
let size_info = self.ctx.size_info();
let (x, y) = position.into();
let lmb_pressed = self.ctx.mouse().left_button_state == ElementState::Pressed;
let rmb_pressed = self.ctx.mouse().right_button_state == ElementState::Pressed;
- if !self.ctx.selection_is_empty() && (lmb_pressed || rmb_pressed) && !search_active {
+ if !self.ctx.selection_is_empty() && (lmb_pressed || rmb_pressed) {
self.update_selection_scrolling(y);
}
@@ -405,9 +404,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
// Don't launch URLs if mouse has moved.
self.ctx.mouse_mut().block_url_launcher = true;
- if (lmb_pressed || rmb_pressed)
- && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode())
- && !search_active
+ if (lmb_pressed || rmb_pressed) && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode())
{
self.ctx.update_selection(point, cell_side);
} else if inside_text_area
@@ -600,7 +597,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
self.ctx.update_selection(point, cell_side);
// Move vi mode cursor to mouse click position.
- if self.ctx.terminal().mode().contains(TermMode::VI) {
+ if self.ctx.terminal().mode().contains(TermMode::VI) && !self.ctx.search_active() {
self.ctx.terminal_mut().vi_mode_cursor.point = point;
}
}
@@ -635,7 +632,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
};
// Move vi mode cursor to mouse click position.
- if self.ctx.terminal().mode().contains(TermMode::VI) {
+ if self.ctx.terminal().mode().contains(TermMode::VI) && !self.ctx.search_active() {
self.ctx.terminal_mut().vi_mode_cursor.point = point;
}
}
@@ -791,7 +788,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
};
self.ctx.window_mut().set_mouse_cursor(new_icon);
- } else if !self.ctx.search_active() {
+ } else {
match state {
ElementState::Pressed => {
self.process_mouse_bindings(button);
@@ -963,6 +960,11 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
/// The provided mode, mods, and key must match what is allowed by a binding
/// for its action to be executed.
fn process_mouse_bindings(&mut self, button: MouseButton) {
+ // Ignore bindings while search is active.
+ if self.ctx.search_active() {
+ return;
+ }
+
let mods = *self.ctx.modifiers();
let mode = *self.ctx.terminal().mode();
let mouse_mode = self.ctx.mouse_mode();
diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs
index f628d24f..b347556d 100644
--- a/alacritty/src/renderer/mod.rs
+++ b/alacritty/src/renderer/mod.rs
@@ -958,6 +958,7 @@ impl<'a> RenderApi<'a> {
bg_alpha,
fg,
bg: bg.unwrap_or(Rgb { r: 0, g: 0, b: 0 }),
+ is_match: false,
})
.collect::<Vec<_>>();
diff --git a/alacritty/src/url.rs b/alacritty/src/url.rs
index f3f60dd3..5d35667d 100644
--- a/alacritty/src/url.rs
+++ b/alacritty/src/url.rs
@@ -207,6 +207,7 @@ mod tests {
bg: Default::default(),
bg_alpha: 0.,
flags: Flags::empty(),
+ is_match: false,
})
.collect()
}
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);
}