1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2025-04-21 18:02:37 -04:00
alacritty/alacritty/src/url.rs
Christian Duerr 3fb631b91c
Fix cut off full width glyphs in last column
This resolves the issue with full width glyphs getting rendered in the
last column. Since they need at least two glyphs, it is not possible to
properly render them in the last column.

Instead of rendering half of the glyph in the last column, with the
other half cut off, an additional spacer is now inserted before the wide
glyph. This means that the specific glyph in question is then three
cells wide.

Fixes #2385.
2020-01-09 23:06:41 +00:00

247 lines
7.1 KiB
Rust

use std::cmp::min;
use std::mem;
use glutin::event::{ElementState, ModifiersState};
use urlocator::{UrlLocation, UrlLocator};
use font::Metrics;
use alacritty_terminal::index::Point;
use alacritty_terminal::term::cell::Flags;
use alacritty_terminal::term::color::Rgb;
use alacritty_terminal::term::{RenderableCell, RenderableCellContent, SizeInfo};
use crate::config::{Config, RelaxedEq};
use crate::event::Mouse;
use crate::renderer::rects::{RenderLine, RenderRect};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Url {
lines: Vec<RenderLine>,
end_offset: u16,
num_cols: usize,
}
impl Url {
pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> {
let end = self.end();
self.lines
.iter()
.filter(|line| line.start <= end)
.map(|line| {
let mut rect_line = *line;
rect_line.end = min(line.end, end);
rect_line.rects(Flags::UNDERLINE, metrics, size)
})
.flatten()
.collect()
}
pub fn start(&self) -> Point {
self.lines[0].start
}
pub fn end(&self) -> Point {
self.lines[self.lines.len() - 1].end.sub(self.num_cols, self.end_offset as usize, false)
}
}
pub struct Urls {
locator: UrlLocator,
urls: Vec<Url>,
scheme_buffer: Vec<RenderableCell>,
last_point: Option<Point>,
state: UrlLocation,
}
impl Default for Urls {
fn default() -> Self {
Self {
locator: UrlLocator::new(),
scheme_buffer: Vec::new(),
urls: Vec::new(),
state: UrlLocation::Reset,
last_point: None,
}
}
}
impl Urls {
pub fn new() -> Self {
Self::default()
}
// Update tracked URLs
pub fn update(&mut self, num_cols: usize, cell: RenderableCell) {
// Convert cell to character
let c = match cell.inner {
RenderableCellContent::Chars(chars) => chars[0],
RenderableCellContent::Cursor(_) => return,
};
let point: Point = cell.into();
let end = point;
// Reset URL when empty cells have been skipped
if point != Point::default() && Some(point.sub(num_cols, 1, false)) != self.last_point {
self.reset();
}
self.last_point = Some(end);
// Extend current state if a wide char spacer is encountered
if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
if let UrlLocation::Url(_, mut end_offset) = self.state {
if end_offset != 0 {
end_offset += 1;
}
self.extend_url(point, end, cell.fg, end_offset);
}
return;
}
// Advance parser
let last_state = mem::replace(&mut self.state, self.locator.advance(c));
match (self.state, last_state) {
(UrlLocation::Url(_length, end_offset), UrlLocation::Scheme) => {
// Create empty URL
self.urls.push(Url { lines: Vec::new(), end_offset, num_cols });
// Push schemes into URL
for scheme_cell in self.scheme_buffer.split_off(0) {
let point = scheme_cell.into();
self.extend_url(point, point, scheme_cell.fg, end_offset);
}
// Push the new cell into URL
self.extend_url(point, end, cell.fg, end_offset);
},
(UrlLocation::Url(_length, end_offset), UrlLocation::Url(..)) => {
self.extend_url(point, end, cell.fg, end_offset);
},
(UrlLocation::Scheme, _) => self.scheme_buffer.push(cell),
(UrlLocation::Reset, _) => self.reset(),
_ => (),
}
// Reset at un-wrapped linebreak
if cell.column.0 + 1 == num_cols && !cell.flags.contains(Flags::WRAPLINE) {
self.reset();
}
}
// Extend the last URL
fn extend_url(&mut self, start: Point, end: Point, color: Rgb, end_offset: u16) {
let url = self.urls.last_mut().unwrap();
// If color changed, we need to insert a new line
if url.lines.last().map(|last| last.color) == Some(color) {
url.lines.last_mut().unwrap().end = end;
} else {
url.lines.push(RenderLine { color, start, end });
}
// Update excluded cells at the end of the URL
url.end_offset = end_offset;
}
pub fn highlighted(
&self,
config: &Config,
mouse: &Mouse,
mods: ModifiersState,
mouse_mode: bool,
selection: bool,
) -> Option<Url> {
// Make sure all prerequisites for highlighting are met
if selection
|| (mouse_mode && !mods.shift())
|| !mouse.inside_grid
|| config.ui_config.mouse.url.launcher.is_none()
|| !config.ui_config.mouse.url.mods().relaxed_eq(mods)
|| mouse.left_button_state == ElementState::Pressed
{
return None;
}
for url in &self.urls {
if (url.start()..=url.end()).contains(&Point::new(mouse.line, mouse.column)) {
return Some(url.clone());
}
}
None
}
fn reset(&mut self) {
self.locator = UrlLocator::new();
self.state = UrlLocation::Reset;
self.scheme_buffer.clear();
}
}
#[cfg(test)]
mod test {
use super::*;
use alacritty_terminal::index::{Column, Line};
use alacritty_terminal::term::cell::MAX_ZEROWIDTH_CHARS;
fn text_to_cells(text: &str) -> Vec<RenderableCell> {
text.chars()
.enumerate()
.map(|(i, c)| RenderableCell {
inner: RenderableCellContent::Chars([c; MAX_ZEROWIDTH_CHARS + 1]),
line: Line(0),
column: Column(i),
fg: Default::default(),
bg: Default::default(),
bg_alpha: 0.,
flags: Flags::empty(),
})
.collect()
}
#[test]
fn multi_color_url() {
let mut input = text_to_cells("test https://example.org ing");
let num_cols = input.len();
input[10].fg = Rgb { r: 0xff, g: 0x00, b: 0xff };
let mut urls = Urls::new();
for cell in input {
urls.update(num_cols, cell);
}
let url = urls.urls.first().unwrap();
assert_eq!(url.start().col, Column(5));
assert_eq!(url.end().col, Column(23));
}
#[test]
fn multiple_urls() {
let input = text_to_cells("test git:a git:b git:c ing");
let num_cols = input.len();
let mut urls = Urls::new();
for cell in input {
urls.update(num_cols, cell);
}
assert_eq!(urls.urls.len(), 3);
assert_eq!(urls.urls[0].start().col, Column(5));
assert_eq!(urls.urls[0].end().col, Column(9));
assert_eq!(urls.urls[1].start().col, Column(11));
assert_eq!(urls.urls[1].end().col, Column(15));
assert_eq!(urls.urls[2].start().col, Column(17));
assert_eq!(urls.urls[2].end().col, Column(21));
}
}