diff options
Diffstat (limited to 'alacritty_terminal/src/term/render.rs')
-rw-r--r-- | alacritty_terminal/src/term/render.rs | 431 |
1 files changed, 0 insertions, 431 deletions
diff --git a/alacritty_terminal/src/term/render.rs b/alacritty_terminal/src/term/render.rs deleted file mode 100644 index fbb5a732..00000000 --- a/alacritty_terminal/src/term/render.rs +++ /dev/null @@ -1,431 +0,0 @@ -use std::cmp::max; -use std::iter; -use std::iter::Peekable; -use std::mem; -use std::ops::RangeInclusive; - -use crate::ansi::{Color, CursorShape, NamedColor}; -use crate::config::Config; -use crate::grid::{Dimensions, DisplayIter, Indexed}; -use crate::index::{Column, Direction, Line, Point}; -use crate::selection::SelectionRange; -use crate::term::cell::{Cell, Flags}; -use crate::term::color::{self, CellRgb, Rgb, DIM_FACTOR}; -use crate::term::search::RegexIter; -use crate::term::{Term, TermMode}; - -/// Minimum contrast between a fixed cursor color and the cell's background. -pub const MIN_CURSOR_CONTRAST: f64 = 1.5; - -/// Maximum number of linewraps followed outside of the viewport during search highlighting. -const MAX_SEARCH_LINES: usize = 100; - -/// Renderable terminal content. -/// -/// This provides the terminal cursor and an iterator over all non-empty cells. -pub struct RenderableContent<'a, T, C> { - term: &'a Term<T>, - config: &'a Config<C>, - display_iter: DisplayIter<'a, Cell>, - selection: Option<SelectionRange<Line>>, - search: RenderableSearch<'a>, - cursor: Option<RenderableCursor>, - cursor_shape: CursorShape, - cursor_point: Point, -} - -impl<'a, T, C> RenderableContent<'a, T, C> { - pub fn new(term: &'a Term<T>, config: &'a Config<C>, show_cursor: bool) -> Self { - // Cursor position. - let vi_mode = term.mode.contains(TermMode::VI); - let mut cursor_point = if vi_mode { - term.vi_mode_cursor.point - } else { - let mut point = term.grid.cursor.point; - point.line += term.grid.display_offset(); - point - }; - - // Cursor shape. - let cursor_shape = if !show_cursor - || (!term.mode.contains(TermMode::SHOW_CURSOR) && !vi_mode) - || cursor_point.line >= term.screen_lines() - { - cursor_point.line = Line(0); - CursorShape::Hidden - } else if !term.is_focused && config.cursor.unfocused_hollow { - CursorShape::HollowBlock - } else { - let cursor_style = term.cursor_style.unwrap_or(term.default_cursor_style); - - if vi_mode { - term.vi_mode_cursor_style.unwrap_or(cursor_style).shape - } else { - cursor_style.shape - } - }; - - Self { - display_iter: term.grid.display_iter(), - selection: term.visible_selection(), - search: RenderableSearch::new(term), - cursor: None, - cursor_shape, - cursor_point, - config, - term, - } - } - - /// Get the terminal cursor. - pub fn cursor(mut self) -> Option<RenderableCursor> { - // Drain the iterator to make sure the cursor is created. - while self.next().is_some() && self.cursor.is_none() {} - - self.cursor - } - - /// Assemble the information required to render the terminal cursor. - /// - /// This will return `None` when there is no cursor visible. - fn renderable_cursor(&mut self, cell: &RenderableCell) -> Option<RenderableCursor> { - if self.cursor_shape == CursorShape::Hidden { - return None; - } - - // Expand across wide cell when inside wide char or spacer. - let is_wide = if cell.flags.contains(Flags::WIDE_CHAR_SPACER) { - self.cursor_point.col -= 1; - true - } else { - cell.flags.contains(Flags::WIDE_CHAR) - }; - - // Cursor colors. - let color = if self.term.mode.contains(TermMode::VI) { - self.config.colors.vi_mode_cursor - } else { - self.config.colors.cursor - }; - let mut cursor_color = if self.term.color_modified[NamedColor::Cursor as usize] { - CellRgb::Rgb(self.term.colors[NamedColor::Cursor]) - } else { - color.background - }; - let mut text_color = color.foreground; - - // Invert the cursor if it has a fixed background close to the cell's background. - if matches!( - cursor_color, - CellRgb::Rgb(color) if color.contrast(cell.bg) < MIN_CURSOR_CONTRAST - ) { - cursor_color = CellRgb::CellForeground; - text_color = CellRgb::CellBackground; - } - - // Convert from cell colors to RGB. - let text_color = text_color.color(cell.fg, cell.bg); - let cursor_color = cursor_color.color(cell.fg, cell.bg); - - Some(RenderableCursor { - point: self.cursor_point, - shape: self.cursor_shape, - cursor_color, - text_color, - is_wide, - }) - } -} - -impl<'a, T, C> Iterator for RenderableContent<'a, T, C> { - type Item = RenderableCell; - - /// Gets the next renderable cell. - /// - /// Skips empty (background) cells and applies any flags to the cell state - /// (eg. invert fg and bg colors). - #[inline] - fn next(&mut self) -> Option<Self::Item> { - loop { - if self.cursor_point == self.display_iter.point() { - // Handle cell at cursor position. - let cell = self.display_iter.next()?; - let mut cell = RenderableCell::new(self, cell); - - // Store the cursor which should be rendered. - self.cursor = self.renderable_cursor(&cell).map(|cursor| { - if cursor.shape == CursorShape::Block { - cell.fg = cursor.text_color; - cell.bg = cursor.cursor_color; - - // Since we draw Block cursor by drawing cell below it with a proper color, - // we must adjust alpha to make it visible. - cell.bg_alpha = 1.; - } - - cursor - }); - - return Some(cell); - } else { - // Handle non-cursor cells. - let cell = self.display_iter.next()?; - let cell = RenderableCell::new(self, cell); - - // Skip empty cells and wide char spacers. - if !cell.is_empty() && !cell.flags.contains(Flags::WIDE_CHAR_SPACER) { - return Some(cell); - } - } - } - } -} - -/// Cell ready for rendering. -#[derive(Clone, Debug)] -pub struct RenderableCell { - pub character: char, - pub zerowidth: Option<Vec<char>>, - pub line: Line, - pub column: Column, - pub fg: Rgb, - pub bg: Rgb, - pub bg_alpha: f32, - pub flags: Flags, - pub is_match: bool, -} - -impl RenderableCell { - fn new<'a, T, C>(content: &mut RenderableContent<'a, T, C>, cell: Indexed<&Cell>) -> Self { - let point = Point::new(cell.line, cell.column); - - // Lookup RGB values. - let mut fg_rgb = - Self::compute_fg_rgb(content.config, &content.term.colors, cell.fg, cell.flags); - let mut bg_rgb = Self::compute_bg_rgb(&content.term.colors, cell.bg); - - let mut bg_alpha = if cell.flags.contains(Flags::INVERSE) { - mem::swap(&mut fg_rgb, &mut bg_rgb); - 1.0 - } else { - Self::compute_bg_alpha(cell.bg) - }; - - let grid = content.term.grid(); - let is_selected = content.selection.map_or(false, |selection| { - selection.contains_cell(grid, point, content.cursor_point, content.cursor_shape) - }); - let mut is_match = false; - - if is_selected { - let config_bg = content.config.colors.selection.background; - let selected_fg = content.config.colors.selection.foreground.color(fg_rgb, bg_rgb); - bg_rgb = config_bg.color(fg_rgb, bg_rgb); - fg_rgb = selected_fg; - - if fg_rgb == bg_rgb && !cell.flags.contains(Flags::HIDDEN) { - // Reveal inversed text when fg/bg is the same. - fg_rgb = content.term.colors[NamedColor::Background]; - bg_rgb = content.term.colors[NamedColor::Foreground]; - bg_alpha = 1.0; - } else if config_bg != CellRgb::CellBackground { - bg_alpha = 1.0; - } - } else if content.search.advance(grid.visible_to_buffer(point)) { - // Highlight the cell if it is part of a search match. - let config_bg = content.config.colors.search.matches.background; - let matched_fg = content.config.colors.search.matches.foreground.color(fg_rgb, bg_rgb); - bg_rgb = config_bg.color(fg_rgb, bg_rgb); - fg_rgb = matched_fg; - - if config_bg != CellRgb::CellBackground { - bg_alpha = 1.0; - } - - is_match = true; - } - - RenderableCell { - character: cell.c, - zerowidth: cell.zerowidth().map(|zerowidth| zerowidth.to_vec()), - line: cell.line, - column: cell.column, - fg: fg_rgb, - bg: bg_rgb, - bg_alpha, - flags: cell.flags, - is_match, - } - } - - /// Position of the cell. - pub fn point(&self) -> Point { - Point::new(self.line, self.column) - } - - /// Check if cell contains any renderable content. - fn is_empty(&self) -> bool { - self.bg_alpha == 0. - && !self.flags.intersects(Flags::UNDERLINE | Flags::STRIKEOUT | Flags::DOUBLE_UNDERLINE) - && self.character == ' ' - && self.zerowidth.is_none() - } - - /// Get the RGB color from a cell's foreground color. - fn compute_fg_rgb<C>(config: &Config<C>, colors: &color::List, fg: Color, flags: Flags) -> Rgb { - match fg { - Color::Spec(rgb) => match flags & Flags::DIM { - Flags::DIM => rgb * DIM_FACTOR, - _ => rgb, - }, - Color::Named(ansi) => { - match (config.draw_bold_text_with_bright_colors, flags & Flags::DIM_BOLD) { - // If no bright foreground is set, treat it like the BOLD flag doesn't exist. - (_, Flags::DIM_BOLD) - if ansi == NamedColor::Foreground - && config.colors.primary.bright_foreground.is_none() => - { - colors[NamedColor::DimForeground] - }, - // Draw bold text in bright colors *and* contains bold flag. - (true, Flags::BOLD) => colors[ansi.to_bright()], - // Cell is marked as dim and not bold. - (_, Flags::DIM) | (false, Flags::DIM_BOLD) => colors[ansi.to_dim()], - // None of the above, keep original color.. - _ => colors[ansi], - } - }, - Color::Indexed(idx) => { - let idx = match ( - config.draw_bold_text_with_bright_colors, - flags & Flags::DIM_BOLD, - idx, - ) { - (true, Flags::BOLD, 0..=7) => idx as usize + 8, - (false, Flags::DIM, 8..=15) => idx as usize - 8, - (false, Flags::DIM, 0..=7) => NamedColor::DimBlack as usize + idx as usize, - _ => idx as usize, - }; - - colors[idx] - }, - } - } - - /// Get the RGB color from a cell's background color. - #[inline] - fn compute_bg_rgb(colors: &color::List, bg: Color) -> Rgb { - match bg { - Color::Spec(rgb) => rgb, - Color::Named(ansi) => colors[ansi], - Color::Indexed(idx) => colors[idx], - } - } - - /// Compute background alpha based on cell's original color. - /// - /// Since an RGB color matching the background should not be transparent, this is computed - /// using the named input color, rather than checking the RGB of the background after its color - /// is computed. - #[inline] - fn compute_bg_alpha(bg: Color) -> f32 { - if bg == Color::Named(NamedColor::Background) { - 0. - } else { - 1. - } - } -} - -/// Cursor storing all information relevant for rendering. -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub struct RenderableCursor { - shape: CursorShape, - cursor_color: Rgb, - text_color: Rgb, - is_wide: bool, - point: Point, -} - -impl RenderableCursor { - pub fn color(&self) -> Rgb { - self.cursor_color - } - - pub fn shape(&self) -> CursorShape { - self.shape - } - - pub fn is_wide(&self) -> bool { - self.is_wide - } - - pub fn point(&self) -> Point { - self.point - } -} - -type MatchIter<'a> = Box<dyn Iterator<Item = RangeInclusive<Point<usize>>> + 'a>; - -/// Regex search highlight tracking. -struct RenderableSearch<'a> { - iter: Peekable<MatchIter<'a>>, -} - -impl<'a> RenderableSearch<'a> { - /// Create a new renderable search iterator. - fn new<T>(term: &'a Term<T>) -> Self { - // Avoid constructing search if there is none. - if term.regex_search.is_none() { - let iter: MatchIter<'a> = Box::new(iter::empty()); - return Self { iter: iter.peekable() }; - } - - let viewport_end = term.grid().display_offset(); - let viewport_start = viewport_end + term.screen_lines().0 - 1; - - // Compute start of the first and end of the last line. - let start_point = Point::new(viewport_start, Column(0)); - let mut start = term.line_search_left(start_point); - let end_point = Point::new(viewport_end, term.cols() - 1); - let mut end = term.line_search_right(end_point); - - // Set upper bound on search before/after the viewport to prevent excessive blocking. - if start.line > viewport_start + MAX_SEARCH_LINES { - if start.line == 0 { - // Do not highlight anything if this line is the last. - let iter: MatchIter<'a> = Box::new(iter::empty()); - return Self { iter: iter.peekable() }; - } else { - // Start at next line if this one is too long. - start.line -= 1; - } - } - end.line = max(end.line, viewport_end.saturating_sub(MAX_SEARCH_LINES)); - - // Create an iterater for the current regex search for all visible matches. - let iter: MatchIter<'a> = Box::new( - RegexIter::new(start, end, Direction::Right, &term) - .skip_while(move |rm| rm.end().line > viewport_start) - .take_while(move |rm| rm.start().line >= viewport_end), - ); - - Self { iter: iter.peekable() } - } - - /// Advance the search tracker to the next point. - /// - /// This will return `true` if the point passed is part of a search match. - fn advance(&mut self, point: Point<usize>) -> bool { - while let Some(regex_match) = &self.iter.peek() { - if regex_match.start() > &point { - break; - } else if regex_match.end() < &point { - let _ = self.iter.next(); - } else { - return true; - } - } - false - } -} |