diff options
author | Christian Duerr <contact@christianduerr.com> | 2019-11-22 17:09:01 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-11-22 17:09:01 +0100 |
commit | 624b3e2189df93145559ebba96e0a7754845c033 (patch) | |
tree | 5fb5cbf0fe0cfa0f1a96348ee102f50638cd9970 | |
parent | 5ea4fb6034fe52bd87a402d1208f58be74486c7a (diff) | |
download | alacritty-624b3e2189df93145559ebba96e0a7754845c033.tar.gz alacritty-624b3e2189df93145559ebba96e0a7754845c033.zip |
Fix excessive allocations in URL parser
Fixes #3002.
-rw-r--r-- | alacritty/src/url.rs | 154 |
1 files changed, 105 insertions, 49 deletions
diff --git a/alacritty/src/url.rs b/alacritty/src/url.rs index 43503f60..5c10bcfd 100644 --- a/alacritty/src/url.rs +++ b/alacritty/src/url.rs @@ -9,6 +9,7 @@ use font::Metrics; use alacritty_terminal::index::Point; use alacritty_terminal::renderer::rects::{RenderLine, RenderRect}; use alacritty_terminal::term::cell::Flags; +use alacritty_terminal::term::color::Rgb; use alacritty_terminal::term::{RenderableCell, RenderableCellContent, SizeInfo}; use crate::config::{Config, RelaxedEq}; @@ -48,6 +49,7 @@ impl Url { pub struct Urls { locator: UrlLocator, urls: Vec<Url>, + scheme_buffer: Vec<RenderableCell>, last_point: Option<Point>, state: UrlLocation, } @@ -56,6 +58,7 @@ impl Default for Urls { fn default() -> Self { Self { locator: UrlLocator::new(), + scheme_buffer: Vec::new(), urls: Vec::new(), state: UrlLocation::Reset, last_point: None, @@ -82,63 +85,41 @@ impl Urls { }; let point: Point = cell.into(); + let mut end = point; // Reset URL when empty cells have been skipped if point != Point::default() && Some(point.sub(num_cols, 1)) != self.last_point { self.reset(); } - self.last_point = Some(point); + + // Extend by one cell for double-width characters + if cell.flags.contains(Flags::WIDE_CHAR) { + end.col += 1; + } + + self.last_point = Some(end); // Advance parser let last_state = mem::replace(&mut self.state, self.locator.advance(c)); match (self.state, last_state) { - (UrlLocation::Url(_length, end_offset), _) => { - let mut end = point; - - // Extend by one cell for double-width characters - if cell.flags.contains(Flags::WIDE_CHAR) { - end.col += 1; - - self.last_point = Some(end); + (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); } - if let Some(url) = self.urls.last_mut() { - let last_index = url.lines.len() - 1; - let last_line = &mut url.lines[last_index]; - - if last_line.color == cell.fg { - // Update existing line - last_line.end = end; - } else { - // Create new line with different color - url.lines.push(RenderLine { start: point, end, color: cell.fg }); - } - - // Update offset - url.end_offset = end_offset; - } - }, - (UrlLocation::Reset, UrlLocation::Scheme) => { - self.urls.pop(); - }, - (UrlLocation::Scheme, UrlLocation::Reset) => { - self.urls.push(Url { - lines: vec![RenderLine { start: point, end: point, color: cell.fg }], - end_offset: 0, - num_cols, - }); + // Push the new cell into URL + self.extend_url(point, end, cell.fg, end_offset); }, - (UrlLocation::Scheme, _) => { - if let Some(url) = self.urls.last_mut() { - if let Some(last_line) = url.lines.last_mut() { - if last_line.color == cell.fg { - last_line.end = point; - } else { - url.lines.push(RenderLine { start: point, end: point, color: cell.fg }); - } - } - } + (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(), _ => (), } @@ -148,6 +129,21 @@ impl Urls { } } + // 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, @@ -177,12 +173,72 @@ impl Urls { } fn reset(&mut self) { - // Remove temporarily stored scheme URLs - if let UrlLocation::Scheme = self.state { - self.urls.pop(); - } - 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)); } } |