mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
Draw cursor with rect renderer
This commit makes cursors being drawn via rects, thus it's always above underlines/strikeouts. Also, since the cursor isn't a glyph anymore, it can't be obscured due to atlas switching while glyphs are rendered. Fixes #4404. Fixes #3471.
This commit is contained in:
parent
fdc10d270e
commit
12fbd0051c
11 changed files with 649 additions and 668 deletions
|
@ -45,6 +45,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Crash due to clipboard not being properly released on Wayland
|
||||
- Shadow artifacts when resizing transparent windows on macOS
|
||||
- Missing glyph symbols not being rendered for missing glyphs on macOS and Windows
|
||||
- Underline cursor being obscured by underline
|
||||
- Cursor not being rendered with a lot of unicode glyphs visible
|
||||
|
||||
### Removed
|
||||
|
||||
|
|
|
@ -1,112 +1,92 @@
|
|||
//! Helpers for creating different cursor glyphs from font metrics.
|
||||
|
||||
use crossfont::{BitmapBuffer, Metrics, RasterizedGlyph};
|
||||
//! Convert a cursor into an iterator of rects.
|
||||
|
||||
use alacritty_terminal::ansi::CursorShape;
|
||||
use alacritty_terminal::term::color::Rgb;
|
||||
use alacritty_terminal::term::render::RenderableCursor;
|
||||
use alacritty_terminal::term::SizeInfo;
|
||||
|
||||
pub fn get_cursor_glyph(
|
||||
cursor: CursorShape,
|
||||
metrics: Metrics,
|
||||
offset_x: i8,
|
||||
offset_y: i8,
|
||||
is_wide: bool,
|
||||
cursor_thickness: f32,
|
||||
) -> RasterizedGlyph {
|
||||
// Calculate the cell metrics.
|
||||
//
|
||||
// NOTE: With Rust 1.47+ `f64 as usize` is defined to clamp automatically:
|
||||
// https://github.com/rust-lang/rust/commit/14d608f1d8a0b84da5f3bccecb3efb3d35f980dc
|
||||
let height = (metrics.line_height + f64::from(offset_y)).max(1.) as usize;
|
||||
let mut width = (metrics.average_advance + f64::from(offset_x)).max(1.) as usize;
|
||||
let line_width = (cursor_thickness * width as f32).round().max(1.) as usize;
|
||||
use crate::renderer::rects::RenderRect;
|
||||
|
||||
// Double the cursor width if it's above a double-width glyph.
|
||||
if is_wide {
|
||||
width *= 2;
|
||||
}
|
||||
|
||||
match cursor {
|
||||
CursorShape::HollowBlock => get_box_cursor_glyph(height, width, line_width),
|
||||
CursorShape::Underline => get_underline_cursor_glyph(width, line_width),
|
||||
CursorShape::Beam => get_beam_cursor_glyph(height, line_width),
|
||||
CursorShape::Block => get_block_cursor_glyph(height, width),
|
||||
CursorShape::Hidden => RasterizedGlyph::default(),
|
||||
}
|
||||
/// Trait for conversion into the iterator.
|
||||
pub trait IntoRects {
|
||||
/// Consume the cursor for an iterator of rects.
|
||||
fn rects(self, size_info: &SizeInfo, thickness: f32) -> CursorRects;
|
||||
}
|
||||
|
||||
/// Return a custom underline cursor character.
|
||||
pub fn get_underline_cursor_glyph(width: usize, line_width: usize) -> RasterizedGlyph {
|
||||
// Create a new rectangle, the height is relative to the font width.
|
||||
let buffer = BitmapBuffer::RGB(vec![255u8; width * line_width * 3]);
|
||||
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 y = point.line.0 as f32 * size_info.cell_height() + size_info.padding_y();
|
||||
|
||||
// Create a custom glyph with the rectangle data attached to it.
|
||||
RasterizedGlyph {
|
||||
character: ' ',
|
||||
top: line_width as i32,
|
||||
left: 0,
|
||||
height: line_width as i32,
|
||||
width: width as i32,
|
||||
buffer,
|
||||
}
|
||||
}
|
||||
let mut width = size_info.cell_width();
|
||||
let height = size_info.cell_height();
|
||||
|
||||
/// Return a custom beam cursor character.
|
||||
pub fn get_beam_cursor_glyph(height: usize, line_width: usize) -> RasterizedGlyph {
|
||||
// Create a new rectangle that is at least one pixel wide
|
||||
let buffer = BitmapBuffer::RGB(vec![255u8; line_width * height * 3]);
|
||||
if self.is_wide() {
|
||||
width *= 2.;
|
||||
}
|
||||
|
||||
// Create a custom glyph with the rectangle data attached to it
|
||||
RasterizedGlyph {
|
||||
character: ' ',
|
||||
top: height as i32,
|
||||
left: 0,
|
||||
height: height as i32,
|
||||
width: line_width as i32,
|
||||
buffer,
|
||||
}
|
||||
}
|
||||
let thickness = (thickness * width as f32).round().max(1.);
|
||||
|
||||
/// Returns a custom box cursor character.
|
||||
pub fn get_box_cursor_glyph(height: usize, width: usize, line_width: usize) -> RasterizedGlyph {
|
||||
// Create a new box outline rectangle.
|
||||
let mut buffer = Vec::with_capacity(width * height * 3);
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
if y < line_width
|
||||
|| y >= height - line_width
|
||||
|| x < line_width
|
||||
|| x >= width - line_width
|
||||
{
|
||||
buffer.append(&mut vec![255u8; 3]);
|
||||
} else {
|
||||
buffer.append(&mut vec![0u8; 3]);
|
||||
}
|
||||
match self.shape() {
|
||||
CursorShape::Beam => beam(x, y, height, thickness, self.color()),
|
||||
CursorShape::Underline => underline(x, y, width, height, thickness, self.color()),
|
||||
CursorShape::HollowBlock => hollow(x, y, width, height, thickness, self.color()),
|
||||
_ => CursorRects::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a custom glyph with the rectangle data attached to it.
|
||||
RasterizedGlyph {
|
||||
character: ' ',
|
||||
top: height as i32,
|
||||
left: 0,
|
||||
height: height as i32,
|
||||
width: width as i32,
|
||||
buffer: BitmapBuffer::RGB(buffer),
|
||||
/// Cursor rect iterator.
|
||||
#[derive(Default)]
|
||||
pub struct CursorRects {
|
||||
rects: [Option<RenderRect>; 4],
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl From<RenderRect> for CursorRects {
|
||||
fn from(rect: RenderRect) -> Self {
|
||||
Self { rects: [Some(rect), None, None, None], index: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a custom block cursor character.
|
||||
pub fn get_block_cursor_glyph(height: usize, width: usize) -> RasterizedGlyph {
|
||||
// Create a completely filled glyph.
|
||||
let buffer = BitmapBuffer::RGB(vec![255u8; width * height * 3]);
|
||||
impl Iterator for CursorRects {
|
||||
type Item = RenderRect;
|
||||
|
||||
// Create a custom glyph with the rectangle data attached to it.
|
||||
RasterizedGlyph {
|
||||
character: ' ',
|
||||
top: height as i32,
|
||||
left: 0,
|
||||
height: height as i32,
|
||||
width: width as i32,
|
||||
buffer,
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let rect = self.rects.get_mut(self.index)?;
|
||||
self.index += 1;
|
||||
rect.take()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an iterator yielding a single beam rect.
|
||||
fn beam(x: f32, y: f32, height: f32, thickness: f32, color: Rgb) -> CursorRects {
|
||||
RenderRect::new(x, y, thickness, height, color, 1.).into()
|
||||
}
|
||||
|
||||
/// Create an iterator yielding a single underline rect.
|
||||
fn underline(x: f32, y: f32, width: f32, height: f32, thickness: f32, color: Rgb) -> CursorRects {
|
||||
let y = y + height - thickness;
|
||||
RenderRect::new(x, y, width, thickness, color, 1.).into()
|
||||
}
|
||||
|
||||
/// Create an iterator yielding a rect for each side of the hollow block cursor.
|
||||
fn hollow(x: f32, y: f32, width: f32, height: f32, thickness: f32, color: Rgb) -> CursorRects {
|
||||
let top_line = RenderRect::new(x, y, width, thickness, color, 1.);
|
||||
|
||||
let vertical_y = y + thickness;
|
||||
let vertical_height = height - 2. * thickness;
|
||||
let left_line = RenderRect::new(x, vertical_y, thickness, vertical_height, color, 1.);
|
||||
|
||||
let bottom_y = y + height - thickness;
|
||||
let bottom_line = RenderRect::new(x, bottom_y, width, thickness, color, 1.);
|
||||
|
||||
let right_x = x + width - thickness;
|
||||
let right_line = RenderRect::new(right_x, vertical_y, thickness, vertical_height, color, 1.);
|
||||
|
||||
CursorRects {
|
||||
rects: [Some(top_line), Some(bottom_line), Some(left_line), Some(right_line)],
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ use crate::config::window::Dimensions;
|
|||
#[cfg(not(windows))]
|
||||
use crate::config::window::StartupMode;
|
||||
use crate::config::Config;
|
||||
use crate::cursor::IntoRects;
|
||||
use crate::event::{Mouse, SearchState};
|
||||
use crate::message_bar::{MessageBuffer, MessageType};
|
||||
use crate::meter::Meter;
|
||||
|
@ -246,7 +247,7 @@ impl Display {
|
|||
|
||||
// Clear screen.
|
||||
let background_color = config.colors.primary.background;
|
||||
renderer.with_api(&config.ui_config, config.cursor, &size_info, |api| {
|
||||
renderer.with_api(&config.ui_config, &size_info, |api| {
|
||||
api.clear(background_color);
|
||||
});
|
||||
|
||||
|
@ -268,7 +269,7 @@ impl Display {
|
|||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
if is_x11 {
|
||||
window.swap_buffers();
|
||||
renderer.with_api(&config.ui_config, config.cursor, &size_info, |api| {
|
||||
renderer.with_api(&config.ui_config, &size_info, |api| {
|
||||
api.finish();
|
||||
});
|
||||
}
|
||||
|
@ -450,7 +451,14 @@ impl Display {
|
|||
.and_then(|focused_match| terminal.grid().clamp_buffer_range_to_visible(focused_match));
|
||||
let cursor_hidden = self.cursor_hidden || search_state.regex().is_some();
|
||||
|
||||
let grid_cells = terminal.renderable_cells(config, !cursor_hidden).collect::<Vec<_>>();
|
||||
// Collect renderable content before the terminal is dropped.
|
||||
let mut content = terminal.renderable_content(config, !cursor_hidden);
|
||||
let mut grid_cells = Vec::new();
|
||||
while let Some(cell) = content.next() {
|
||||
grid_cells.push(cell);
|
||||
}
|
||||
let cursor = content.cursor();
|
||||
|
||||
let visual_bell_intensity = terminal.visual_bell.intensity();
|
||||
let background_color = terminal.background_color();
|
||||
let cursor_point = terminal.grid().cursor.point;
|
||||
|
@ -471,7 +479,7 @@ impl Display {
|
|||
// Drop terminal as early as possible to free lock.
|
||||
drop(terminal);
|
||||
|
||||
self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |api| {
|
||||
self.renderer.with_api(&config.ui_config, &size_info, |api| {
|
||||
api.clear(background_color);
|
||||
});
|
||||
|
||||
|
@ -482,7 +490,7 @@ impl Display {
|
|||
{
|
||||
let _sampler = self.meter.sampler();
|
||||
|
||||
self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |mut api| {
|
||||
self.renderer.with_api(&config.ui_config, &size_info, |mut api| {
|
||||
// Iterate over all non-empty cells in the grid.
|
||||
for mut cell in grid_cells {
|
||||
// Invert the active match in vi-less search.
|
||||
|
@ -538,6 +546,13 @@ impl Display {
|
|||
}
|
||||
}
|
||||
|
||||
// Push the cursor rects for rendering.
|
||||
if let Some(cursor) = cursor {
|
||||
for rect in cursor.rects(&size_info, config.cursor.thickness()) {
|
||||
rects.push(rect);
|
||||
}
|
||||
}
|
||||
|
||||
// Push visual bell after url/underline/strikeout rects.
|
||||
if visual_bell_intensity != 0. {
|
||||
let visual_bell_rect = RenderRect::new(
|
||||
|
@ -576,7 +591,7 @@ impl Display {
|
|||
// Relay messages to the user.
|
||||
let fg = config.colors.primary.background;
|
||||
for (i, message_text) in text.iter().enumerate() {
|
||||
self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |mut api| {
|
||||
self.renderer.with_api(&config.ui_config, &size_info, |mut api| {
|
||||
api.render_string(glyph_cache, start_line + i, &message_text, fg, None);
|
||||
});
|
||||
}
|
||||
|
@ -621,7 +636,7 @@ impl Display {
|
|||
// On X11 `swap_buffers` does not block for vsync. However the next OpenGl command
|
||||
// will block to synchronize (this is `glClear` in Alacritty), which causes a
|
||||
// permanent one frame delay.
|
||||
self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |api| {
|
||||
self.renderer.with_api(&config.ui_config, &size_info, |api| {
|
||||
api.finish();
|
||||
});
|
||||
}
|
||||
|
@ -668,7 +683,7 @@ impl Display {
|
|||
|
||||
let fg = config.colors.search_bar_foreground();
|
||||
let bg = config.colors.search_bar_background();
|
||||
self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |mut api| {
|
||||
self.renderer.with_api(&config.ui_config, &size_info, |mut api| {
|
||||
api.render_string(glyph_cache, size_info.screen_lines(), &text, fg, Some(bg));
|
||||
});
|
||||
}
|
||||
|
@ -684,7 +699,7 @@ impl Display {
|
|||
let fg = config.colors.primary.background;
|
||||
let bg = config.colors.normal.red;
|
||||
|
||||
self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |mut api| {
|
||||
self.renderer.with_api(&config.ui_config, &size_info, |mut api| {
|
||||
api.render_string(glyph_cache, size_info.screen_lines() - 2, &timing[..], fg, Some(bg));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -26,8 +26,7 @@ use alacritty_terminal::event::EventListener;
|
|||
use alacritty_terminal::grid::{Dimensions, Scroll};
|
||||
use alacritty_terminal::index::{Column, Direction, Line, Point, Side};
|
||||
use alacritty_terminal::selection::SelectionType;
|
||||
use alacritty_terminal::term::mode::TermMode;
|
||||
use alacritty_terminal::term::{ClipboardType, SizeInfo, Term};
|
||||
use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode};
|
||||
use alacritty_terminal::vi_mode::ViMotion;
|
||||
|
||||
use crate::clipboard::Clipboard;
|
||||
|
|
|
@ -15,15 +15,14 @@ use fnv::FnvHasher;
|
|||
use log::{debug, error, info};
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use alacritty_terminal::config::Cursor;
|
||||
use alacritty_terminal::index::{Column, Line};
|
||||
use alacritty_terminal::term::cell::Flags;
|
||||
use alacritty_terminal::term::color::Rgb;
|
||||
use alacritty_terminal::term::{CursorKey, RenderableCell, RenderableCellContent, SizeInfo};
|
||||
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::cursor;
|
||||
use crate::gl;
|
||||
use crate::gl::types::*;
|
||||
use crate::renderer::rects::{RectRenderer, RenderRect};
|
||||
|
@ -116,9 +115,6 @@ pub struct GlyphCache {
|
|||
/// Cache of buffered glyphs.
|
||||
cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>,
|
||||
|
||||
/// Cache of buffered cursor glyphs.
|
||||
cursor_cache: HashMap<CursorKey, Glyph, BuildHasherDefault<FnvHasher>>,
|
||||
|
||||
/// Rasterizer for loading new glyphs.
|
||||
rasterizer: Rasterizer,
|
||||
|
||||
|
@ -164,7 +160,6 @@ impl GlyphCache {
|
|||
|
||||
let mut cache = Self {
|
||||
cache: HashMap::default(),
|
||||
cursor_cache: HashMap::default(),
|
||||
rasterizer,
|
||||
font_size: font.size(),
|
||||
font_key: regular,
|
||||
|
@ -328,7 +323,6 @@ impl GlyphCache {
|
|||
pub fn clear_glyph_cache<L: LoadGlyph>(&mut self, loader: &mut L) {
|
||||
loader.clear();
|
||||
self.cache = HashMap::default();
|
||||
self.cursor_cache = HashMap::default();
|
||||
|
||||
self.load_common_glyphs(loader);
|
||||
}
|
||||
|
@ -459,7 +453,6 @@ pub struct RenderApi<'a> {
|
|||
current_atlas: &'a mut usize,
|
||||
program: &'a mut TextShaderProgram,
|
||||
config: &'a UIConfig,
|
||||
cursor_config: Cursor,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -693,13 +686,7 @@ impl QuadRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn with_api<F, T>(
|
||||
&mut self,
|
||||
config: &UIConfig,
|
||||
cursor_config: Cursor,
|
||||
props: &SizeInfo,
|
||||
func: F,
|
||||
) -> T
|
||||
pub fn with_api<F, T>(&mut self, config: &UIConfig, props: &SizeInfo, func: F) -> T
|
||||
where
|
||||
F: FnOnce(RenderApi<'_>) -> T,
|
||||
{
|
||||
|
@ -720,7 +707,6 @@ impl QuadRenderer {
|
|||
current_atlas: &mut self.current_atlas,
|
||||
program: &mut self.program,
|
||||
config,
|
||||
cursor_config,
|
||||
});
|
||||
|
||||
unsafe {
|
||||
|
@ -848,10 +834,11 @@ impl<'a> RenderApi<'a> {
|
|||
let cells = string
|
||||
.chars()
|
||||
.enumerate()
|
||||
.map(|(i, c)| RenderableCell {
|
||||
.map(|(i, character)| RenderableCell {
|
||||
line,
|
||||
column: Column(i),
|
||||
inner: RenderableCellContent::Chars((c, None)),
|
||||
character,
|
||||
zerowidth: None,
|
||||
flags: Flags::empty(),
|
||||
bg_alpha,
|
||||
fg,
|
||||
|
@ -881,26 +868,6 @@ impl<'a> RenderApi<'a> {
|
|||
}
|
||||
|
||||
pub fn render_cell(&mut self, mut cell: RenderableCell, glyph_cache: &mut GlyphCache) {
|
||||
let (mut character, zerowidth) = match cell.inner {
|
||||
RenderableCellContent::Cursor(cursor_key) => {
|
||||
// Raw cell pixel buffers like cursors don't need to go through font lookup.
|
||||
let metrics = glyph_cache.metrics;
|
||||
let glyph = glyph_cache.cursor_cache.entry(cursor_key).or_insert_with(|| {
|
||||
self.load_glyph(&cursor::get_cursor_glyph(
|
||||
cursor_key.shape,
|
||||
metrics,
|
||||
self.config.font.offset.x,
|
||||
self.config.font.offset.y,
|
||||
cursor_key.is_wide,
|
||||
self.cursor_config.thickness(),
|
||||
))
|
||||
});
|
||||
self.add_render_item(&cell, glyph);
|
||||
return;
|
||||
},
|
||||
RenderableCellContent::Chars((c, ref mut zerowidth)) => (c, zerowidth.take()),
|
||||
};
|
||||
|
||||
// Get font key for cell.
|
||||
let font_key = match cell.flags & Flags::BOLD_ITALIC {
|
||||
Flags::BOLD_ITALIC => glyph_cache.bold_italic_key,
|
||||
|
@ -911,11 +878,12 @@ impl<'a> RenderApi<'a> {
|
|||
|
||||
// Ignore hidden cells and render tabs as spaces to prevent font issues.
|
||||
let hidden = cell.flags.contains(Flags::HIDDEN);
|
||||
if character == '\t' || hidden {
|
||||
character = ' ';
|
||||
if cell.character == '\t' || hidden {
|
||||
cell.character = ' ';
|
||||
}
|
||||
|
||||
let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, character };
|
||||
let mut glyph_key =
|
||||
GlyphKey { font_key, size: glyph_cache.font_size, character: cell.character };
|
||||
|
||||
// Add cell to batch.
|
||||
match glyph_cache.get(glyph_key, self) {
|
||||
|
@ -930,9 +898,9 @@ impl<'a> RenderApi<'a> {
|
|||
}
|
||||
|
||||
// Render visible zero-width characters.
|
||||
if let Some(zerowidth) = zerowidth.filter(|_| !hidden) {
|
||||
for character in zerowidth.iter() {
|
||||
glyph_key.character = *character;
|
||||
if let Some(zerowidth) = cell.zerowidth.take().filter(|_| !hidden) {
|
||||
for character in zerowidth {
|
||||
glyph_key.character = character;
|
||||
if let Ok(glyph) = glyph_cache.get(glyph_key, self) {
|
||||
self.add_render_item(&cell, &glyph);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,8 @@ 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::{RenderableCell, SizeInfo};
|
||||
use alacritty_terminal::term::render::RenderableCell;
|
||||
use alacritty_terminal::term::SizeInfo;
|
||||
|
||||
use crate::gl;
|
||||
use crate::gl::types::*;
|
||||
|
|
|
@ -8,7 +8,8 @@ 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::{RenderableCell, RenderableCellContent, SizeInfo};
|
||||
use alacritty_terminal::term::render::RenderableCell;
|
||||
use alacritty_terminal::term::SizeInfo;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::event::Mouse;
|
||||
|
@ -72,12 +73,6 @@ impl Urls {
|
|||
|
||||
// Update tracked URLs.
|
||||
pub fn update(&mut self, num_cols: Column, cell: &RenderableCell) {
|
||||
// Convert cell to character.
|
||||
let c = match &cell.inner {
|
||||
RenderableCellContent::Chars((c, _zerowidth)) => *c,
|
||||
RenderableCellContent::Cursor(_) => return,
|
||||
};
|
||||
|
||||
let point: Point = cell.into();
|
||||
let mut end = point;
|
||||
|
||||
|
@ -107,7 +102,7 @@ impl Urls {
|
|||
}
|
||||
|
||||
// Advance parser.
|
||||
let last_state = mem::replace(&mut self.state, self.locator.advance(c));
|
||||
let last_state = mem::replace(&mut self.state, self.locator.advance(cell.character));
|
||||
match (self.state, last_state) {
|
||||
(UrlLocation::Url(_length, end_offset), UrlLocation::Scheme) => {
|
||||
// Create empty URL.
|
||||
|
@ -204,8 +199,9 @@ mod tests {
|
|||
fn text_to_cells(text: &str) -> Vec<RenderableCell> {
|
||||
text.chars()
|
||||
.enumerate()
|
||||
.map(|(i, c)| RenderableCell {
|
||||
inner: RenderableCellContent::Chars((c, None)),
|
||||
.map(|(i, character)| RenderableCell {
|
||||
character,
|
||||
zerowidth: None,
|
||||
line: Line(0),
|
||||
column: Column(i),
|
||||
fg: Default::default(),
|
||||
|
|
|
@ -8,7 +8,7 @@ use std::ops::{self, Add, AddAssign, Deref, Range, Sub, SubAssign};
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::grid::Dimensions;
|
||||
use crate::term::RenderableCell;
|
||||
use crate::term::render::RenderableCell;
|
||||
|
||||
/// The side of a cell.
|
||||
pub type Side = Direction;
|
||||
|
|
|
@ -9,8 +9,10 @@ use std::convert::TryFrom;
|
|||
use std::mem;
|
||||
use std::ops::{Bound, Range, RangeBounds};
|
||||
|
||||
use crate::grid::Dimensions;
|
||||
use crate::ansi::CursorShape;
|
||||
use crate::grid::{Dimensions, Grid, GridCell};
|
||||
use crate::index::{Column, Line, Point, Side};
|
||||
use crate::term::cell::Flags;
|
||||
use crate::term::Term;
|
||||
|
||||
/// A Point and side within that point.
|
||||
|
@ -42,14 +44,67 @@ impl<L> SelectionRange<L> {
|
|||
Self { start, end, is_block }
|
||||
}
|
||||
|
||||
pub fn contains(&self, col: Column, line: L) -> bool
|
||||
/// Check if a point lies within the selection.
|
||||
pub fn contains(&self, point: Point<L>) -> bool
|
||||
where
|
||||
L: PartialEq + PartialOrd,
|
||||
{
|
||||
self.start.line <= line
|
||||
&& self.end.line >= line
|
||||
&& (self.start.col <= col || (self.start.line != line && !self.is_block))
|
||||
&& (self.end.col >= col || (self.end.line != line && !self.is_block))
|
||||
self.start.line <= point.line
|
||||
&& self.end.line >= point.line
|
||||
&& (self.start.col <= point.col || (self.start.line != point.line && !self.is_block))
|
||||
&& (self.end.col >= point.col || (self.end.line != point.line && !self.is_block))
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectionRange<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,
|
||||
{
|
||||
// Do not invert block cursor at selection boundaries.
|
||||
if cursor_shape == CursorShape::Block
|
||||
&& cursor_point == point
|
||||
&& (self.start == point
|
||||
|| self.end == point
|
||||
|| (self.is_block
|
||||
&& ((self.start.line == point.line && self.end.col == point.col)
|
||||
|| (self.end.line == point.line && self.start.col == point.col))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Point itself is selected.
|
||||
if self.contains(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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
//! Exports the `Term` type which is a high-level API for the Grid.
|
||||
|
||||
use std::cmp::{max, min};
|
||||
use std::iter::Peekable;
|
||||
use std::ops::{Index, IndexMut, Range, RangeInclusive};
|
||||
use std::ops::{Index, IndexMut, Range};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{io, iter, mem, ptr, str};
|
||||
use std::{io, mem, ptr, str};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use log::{debug, trace};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
@ -16,27 +16,23 @@ use crate::ansi::{
|
|||
};
|
||||
use crate::config::{BellAnimation, BellConfig, Config};
|
||||
use crate::event::{Event, EventListener};
|
||||
use crate::grid::{Dimensions, DisplayIter, Grid, IndexRegion, Indexed, Scroll};
|
||||
use crate::grid::{Dimensions, Grid, IndexRegion, 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::{CellRgb, Rgb, DIM_FACTOR};
|
||||
use crate::term::search::{RegexIter, RegexSearch};
|
||||
use crate::term::color::Rgb;
|
||||
use crate::term::render::RenderableContent;
|
||||
use crate::term::search::RegexSearch;
|
||||
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;
|
||||
|
||||
/// Minimum contrast between a fixed cursor color and the cell's background.
|
||||
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;
|
||||
|
||||
/// Default tab interval, corresponding to terminfo `it` value.
|
||||
const INITIAL_TABSTOPS: usize = 8;
|
||||
|
||||
|
@ -48,433 +44,41 @@ pub const MIN_COLS: usize = 2;
|
|||
/// Minimum number of visible lines.
|
||||
pub const MIN_SCREEN_LINES: usize = 1;
|
||||
|
||||
/// Cursor storing all information relevant for rendering.
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone, Deserialize)]
|
||||
struct RenderableCursor {
|
||||
text_color: CellRgb,
|
||||
cursor_color: CellRgb,
|
||||
key: CursorKey,
|
||||
point: Point,
|
||||
rendered: bool,
|
||||
}
|
||||
|
||||
/// A key for caching cursor glyphs.
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)]
|
||||
pub struct CursorKey {
|
||||
pub shape: CursorShape,
|
||||
pub is_wide: bool,
|
||||
}
|
||||
|
||||
type MatchIter<'a> = Box<dyn Iterator<Item = RangeInclusive<Point<usize>>> + 'a>;
|
||||
|
||||
/// Regex search highlight tracking.
|
||||
pub 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 {
|
||||
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
|
||||
bitflags! {
|
||||
pub struct TermMode: u32 {
|
||||
const NONE = 0;
|
||||
const SHOW_CURSOR = 0b0000_0000_0000_0000_0001;
|
||||
const APP_CURSOR = 0b0000_0000_0000_0000_0010;
|
||||
const APP_KEYPAD = 0b0000_0000_0000_0000_0100;
|
||||
const MOUSE_REPORT_CLICK = 0b0000_0000_0000_0000_1000;
|
||||
const BRACKETED_PASTE = 0b0000_0000_0000_0001_0000;
|
||||
const SGR_MOUSE = 0b0000_0000_0000_0010_0000;
|
||||
const MOUSE_MOTION = 0b0000_0000_0000_0100_0000;
|
||||
const LINE_WRAP = 0b0000_0000_0000_1000_0000;
|
||||
const LINE_FEED_NEW_LINE = 0b0000_0000_0001_0000_0000;
|
||||
const ORIGIN = 0b0000_0000_0010_0000_0000;
|
||||
const INSERT = 0b0000_0000_0100_0000_0000;
|
||||
const FOCUS_IN_OUT = 0b0000_0000_1000_0000_0000;
|
||||
const ALT_SCREEN = 0b0000_0001_0000_0000_0000;
|
||||
const MOUSE_DRAG = 0b0000_0010_0000_0000_0000;
|
||||
const MOUSE_MODE = 0b0000_0010_0000_0100_1000;
|
||||
const UTF8_MOUSE = 0b0000_0100_0000_0000_0000;
|
||||
const ALTERNATE_SCROLL = 0b0000_1000_0000_0000_0000;
|
||||
const VI = 0b0001_0000_0000_0000_0000;
|
||||
const URGENCY_HINTS = 0b0010_0000_0000_0000_0000;
|
||||
const ANY = std::u32::MAX;
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator that yields cells needing render.
|
||||
///
|
||||
/// Yields cells that require work to be displayed (that is, not a an empty
|
||||
/// background cell). Additionally, this manages some state of the grid only
|
||||
/// relevant for rendering like temporarily changing the cell with the cursor.
|
||||
///
|
||||
/// This manages the cursor during a render. The cursor location is inverted to
|
||||
/// draw it, and reverted after drawing to maintain state.
|
||||
pub struct RenderableCellsIter<'a, C> {
|
||||
inner: DisplayIter<'a, Cell>,
|
||||
grid: &'a Grid<Cell>,
|
||||
cursor: RenderableCursor,
|
||||
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.cursor.point == self.inner.point() {
|
||||
// Handle cursor rendering.
|
||||
if self.cursor.rendered {
|
||||
return self.next_cursor_cell();
|
||||
} else {
|
||||
return Some(self.next_cursor());
|
||||
}
|
||||
} else {
|
||||
// Handle non-cursor cells.
|
||||
let cell = self.inner.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Default for TermMode {
|
||||
fn default() -> TermMode {
|
||||
TermMode::SHOW_CURSOR
|
||||
| TermMode::LINE_WRAP
|
||||
| TermMode::ALTERNATE_SCROLL
|
||||
| TermMode::URGENCY_HINTS
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C> RenderableCellsIter<'a, C> {
|
||||
/// Create the renderable cells iterator.
|
||||
///
|
||||
/// The cursor and terminal mode are required for properly displaying the
|
||||
/// cursor.
|
||||
fn new<T>(
|
||||
term: &'a Term<T>,
|
||||
config: &'a Config<C>,
|
||||
show_cursor: bool,
|
||||
) -> RenderableCellsIter<'a, C> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.shape == CursorShape::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),
|
||||
};
|
||||
}
|
||||
|
||||
Some(cell)
|
||||
}
|
||||
|
||||
/// Get the next renderable cell as the cursor.
|
||||
fn next_cursor(&mut self) -> RenderableCell {
|
||||
// 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);
|
||||
}
|
||||
|
||||
cell
|
||||
}
|
||||
|
||||
/// Check selection state of a cell.
|
||||
fn is_selected(&self, point: Point) -> bool {
|
||||
let selection = match self.selection {
|
||||
Some(selection) => selection,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
// Do not invert block cursor at selection boundaries.
|
||||
if self.cursor.key.shape == CursorShape::Block
|
||||
&& self.cursor.point == point
|
||||
&& (selection.start == point
|
||||
|| selection.end == point
|
||||
|| (selection.is_block
|
||||
&& ((selection.start.line == point.line && selection.end.col == point.col)
|
||||
|| (selection.end.line == point.line && selection.start.col == point.col))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Point itself is selected.
|
||||
if selection.contains(point.col, point.line) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let num_cols = self.grid.cols();
|
||||
|
||||
// Convert to absolute coordinates to adjust for the display offset.
|
||||
let buffer_point = self.grid.visible_to_buffer(point);
|
||||
let cell = &self.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 = self.grid.visible_to_buffer(prev);
|
||||
let next = point.add(num_cols, 1);
|
||||
|
||||
// Check trailing spacer.
|
||||
selection.contains(next.col, next.line)
|
||||
// Check line-wrapping, leading spacer.
|
||||
|| (self.grid[buffer_prev].flags.contains(Flags::LEADING_WIDE_CHAR_SPACER)
|
||||
&& selection.contains(prev.col, prev.line))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum RenderableCellContent {
|
||||
Chars((char, Option<Vec<char>>)),
|
||||
Cursor(CursorKey),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RenderableCell {
|
||||
/// A _Display_ line (not necessarily an _Active_ line).
|
||||
pub line: Line,
|
||||
pub column: Column,
|
||||
pub inner: RenderableCellContent,
|
||||
pub fg: Rgb,
|
||||
pub bg: Rgb,
|
||||
pub bg_alpha: f32,
|
||||
pub flags: Flags,
|
||||
pub is_match: bool,
|
||||
}
|
||||
|
||||
impl RenderableCell {
|
||||
fn new<'a, C>(iter: &mut RenderableCellsIter<'a, C>, cell: Indexed<&Cell>) -> Self {
|
||||
let point = Point::new(cell.line, cell.column);
|
||||
|
||||
// Lookup RGB values.
|
||||
let mut fg_rgb = Self::compute_fg_rgb(iter.config, iter.colors, cell.fg, cell.flags);
|
||||
let mut bg_rgb = Self::compute_bg_rgb(iter.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 mut is_match = false;
|
||||
|
||||
if iter.is_selected(point) {
|
||||
let config_bg = iter.config.colors.selection.background;
|
||||
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;
|
||||
|
||||
if fg_rgb == bg_rgb && !cell.flags.contains(Flags::HIDDEN) {
|
||||
// Reveal inversed text when fg/bg is the same.
|
||||
fg_rgb = iter.colors[NamedColor::Background];
|
||||
bg_rgb = iter.colors[NamedColor::Foreground];
|
||||
bg_alpha = 1.0;
|
||||
} else if config_bg != CellRgb::CellBackground {
|
||||
bg_alpha = 1.0;
|
||||
}
|
||||
} else if iter.search.advance(iter.grid.visible_to_buffer(point)) {
|
||||
// Highlight the cell if it is part of a search match.
|
||||
let config_bg = iter.config.colors.search.matches.background;
|
||||
let matched_fg = iter.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;
|
||||
}
|
||||
|
||||
let zerowidth = cell.zerowidth().map(|zerowidth| zerowidth.to_vec());
|
||||
|
||||
RenderableCell {
|
||||
line: cell.line,
|
||||
column: cell.column,
|
||||
inner: RenderableCellContent::Chars((cell.c, zerowidth)),
|
||||
fg: fg_rgb,
|
||||
bg: bg_rgb,
|
||||
bg_alpha,
|
||||
flags: cell.flags,
|
||||
is_match,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.bg_alpha == 0.
|
||||
&& !self.flags.intersects(Flags::UNDERLINE | Flags::STRIKEOUT | Flags::DOUBLE_UNDERLINE)
|
||||
&& self.inner == RenderableCellContent::Chars((' ', None))
|
||||
}
|
||||
|
||||
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]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
}
|
||||
}
|
||||
|
||||
#[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],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod mode {
|
||||
use bitflags::bitflags;
|
||||
|
||||
bitflags! {
|
||||
pub struct TermMode: u32 {
|
||||
const NONE = 0;
|
||||
const SHOW_CURSOR = 0b0000_0000_0000_0000_0001;
|
||||
const APP_CURSOR = 0b0000_0000_0000_0000_0010;
|
||||
const APP_KEYPAD = 0b0000_0000_0000_0000_0100;
|
||||
const MOUSE_REPORT_CLICK = 0b0000_0000_0000_0000_1000;
|
||||
const BRACKETED_PASTE = 0b0000_0000_0000_0001_0000;
|
||||
const SGR_MOUSE = 0b0000_0000_0000_0010_0000;
|
||||
const MOUSE_MOTION = 0b0000_0000_0000_0100_0000;
|
||||
const LINE_WRAP = 0b0000_0000_0000_1000_0000;
|
||||
const LINE_FEED_NEW_LINE = 0b0000_0000_0001_0000_0000;
|
||||
const ORIGIN = 0b0000_0000_0010_0000_0000;
|
||||
const INSERT = 0b0000_0000_0100_0000_0000;
|
||||
const FOCUS_IN_OUT = 0b0000_0000_1000_0000_0000;
|
||||
const ALT_SCREEN = 0b0000_0001_0000_0000_0000;
|
||||
const MOUSE_DRAG = 0b0000_0010_0000_0000_0000;
|
||||
const MOUSE_MODE = 0b0000_0010_0000_0100_1000;
|
||||
const UTF8_MOUSE = 0b0000_0100_0000_0000_0000;
|
||||
const ALTERNATE_SCROLL = 0b0000_1000_0000_0000_0000;
|
||||
const VI = 0b0001_0000_0000_0000_0000;
|
||||
const URGENCY_HINTS = 0b0010_0000_0000_0000_0000;
|
||||
const ANY = std::u32::MAX;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TermMode {
|
||||
fn default() -> TermMode {
|
||||
TermMode::SHOW_CURSOR
|
||||
| TermMode::LINE_WRAP
|
||||
| TermMode::ALTERNATE_SCROLL
|
||||
| TermMode::URGENCY_HINTS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use crate::term::mode::TermMode;
|
||||
|
||||
pub struct VisualBell {
|
||||
/// Visual bell animation.
|
||||
animation: BellAnimation,
|
||||
|
@ -1017,17 +621,19 @@ impl<T> Term<T> {
|
|||
&mut self.grid
|
||||
}
|
||||
|
||||
/// Iterate over the *renderable* cells in the terminal.
|
||||
/// 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.
|
||||
pub fn renderable_cells<'b, C>(
|
||||
/// 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,
|
||||
) -> RenderableCellsIter<'_, C> {
|
||||
RenderableCellsIter::new(&self, config, show_cursor)
|
||||
) -> RenderableContent<'_, T, C> {
|
||||
RenderableContent::new(&self, config, show_cursor)
|
||||
}
|
||||
|
||||
/// Get the selection within the viewport.
|
||||
|
@ -1393,67 +999,6 @@ impl<T> Term<T> {
|
|||
|
||||
cursor_cell
|
||||
}
|
||||
|
||||
/// Get rendering information about the active cursor.
|
||||
fn renderable_cursor<C>(&self, config: &Config<C>, show_cursor: bool) -> RenderableCursor {
|
||||
let vi_mode = self.mode.contains(TermMode::VI);
|
||||
|
||||
// Cursor position.
|
||||
let mut point = if vi_mode {
|
||||
self.vi_mode_cursor.point
|
||||
} else {
|
||||
let mut point = self.grid.cursor.point;
|
||||
point.line += self.grid.display_offset();
|
||||
point
|
||||
};
|
||||
|
||||
// Cursor shape.
|
||||
let hidden = !show_cursor
|
||||
|| (!self.mode.contains(TermMode::SHOW_CURSOR) && !vi_mode)
|
||||
|| point.line >= self.screen_lines();
|
||||
|
||||
let cursor_shape = if hidden {
|
||||
point.line = Line(0);
|
||||
CursorShape::Hidden
|
||||
} else if !self.is_focused && config.cursor.unfocused_hollow {
|
||||
CursorShape::HollowBlock
|
||||
} else {
|
||||
let cursor_style = self.cursor_style.unwrap_or(self.default_cursor_style);
|
||||
|
||||
if vi_mode {
|
||||
self.vi_mode_cursor_style.unwrap_or(cursor_style).shape
|
||||
} else {
|
||||
cursor_style.shape
|
||||
}
|
||||
};
|
||||
|
||||
// Cursor colors.
|
||||
let color = if vi_mode { config.colors.vi_mode_cursor } else { config.colors.cursor };
|
||||
let cursor_color = if self.color_modified[NamedColor::Cursor as usize] {
|
||||
CellRgb::Rgb(self.colors[NamedColor::Cursor])
|
||||
} else {
|
||||
color.background
|
||||
};
|
||||
let text_color = color.foreground;
|
||||
|
||||
// Expand across wide cell when inside wide char or spacer.
|
||||
let buffer_point = self.visible_to_buffer(point);
|
||||
let cell = &self.grid[buffer_point.line][buffer_point.col];
|
||||
let is_wide = if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
|
||||
point.col -= 1;
|
||||
true
|
||||
} else {
|
||||
cell.flags.contains(Flags::WIDE_CHAR)
|
||||
};
|
||||
|
||||
RenderableCursor {
|
||||
text_color,
|
||||
cursor_color,
|
||||
key: CursorKey { shape: cursor_shape, is_wide },
|
||||
point,
|
||||
rendered: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Dimensions for Term<T> {
|
||||
|
|
420
alacritty_terminal/src/term/render.rs
Normal file
420
alacritty_terminal/src/term/render.rs
Normal file
|
@ -0,0 +1,420 @@
|
|||
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,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue