aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/url.rs
diff options
context:
space:
mode:
authorAyose <ayosec@gmail.com>2021-03-10 20:21:36 +0000
committerbetaboon <betaboon@0x80.ninja>2021-08-17 15:44:40 +0200
commit0fd3793a963015e8f91021e35dbb2169d15c3211 (patch)
treea83c1f53a123f84376a58cf2a5114ad581092e0a /alacritty/src/url.rs
parentc24d7dfd0d2d8849f0398d7cb1a65d6562ee7a0d (diff)
downloadr-alacritty-0fd3793a963015e8f91021e35dbb2169d15c3211.tar.gz
r-alacritty-0fd3793a963015e8f91021e35dbb2169d15c3211.tar.bz2
r-alacritty-0fd3793a963015e8f91021e35dbb2169d15c3211.zip
Add Sixel support
Fixes #910
Diffstat (limited to 'alacritty/src/url.rs')
-rw-r--r--alacritty/src/url.rs276
1 files changed, 276 insertions, 0 deletions
diff --git a/alacritty/src/url.rs b/alacritty/src/url.rs
new file mode 100644
index 00000000..84b8584f
--- /dev/null
+++ b/alacritty/src/url.rs
@@ -0,0 +1,276 @@
+use std::cmp::min;
+use std::mem;
+
+use crossfont::Metrics;
+use glutin::event::{ElementState, ModifiersState};
+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::SizeInfo;
+
+use crate::config::Config;
+use crate::display::content::RenderableCell;
+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: Column,
+}
+
+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)
+ }
+}
+
+pub struct Urls {
+ locator: UrlLocator,
+ urls: Vec<Url>,
+ scheme_buffer: Vec<(Point, Rgb)>,
+ 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: Column, cell: &RenderableCell) {
+ let point = cell.point;
+ let mut end = point;
+
+ // Include the following wide char spacer.
+ if cell.flags.contains(Flags::WIDE_CHAR) {
+ end.column += 1;
+ }
+
+ // 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(end);
+
+ // Extend current state if a leading wide char spacer is encountered.
+ if cell.flags.intersects(Flags::LEADING_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(cell.character));
+ 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_point, scheme_fg) in self.scheme_buffer.split_off(0) {
+ self.extend_url(scheme_point, scheme_point, scheme_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.point, cell.fg)),
+ (UrlLocation::Reset, _) => self.reset(),
+ _ => (),
+ }
+
+ // Reset at un-wrapped linebreak.
+ if cell.point.column + 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;
+ }
+
+ /// Find URL below the mouse cursor.
+ pub fn highlighted(
+ &self,
+ config: &Config,
+ mouse: &Mouse,
+ mods: ModifiersState,
+ mouse_mode: bool,
+ selection: bool,
+ ) -> Option<Url> {
+ // Require additional shift in mouse mode.
+ let mut required_mods = config.ui_config.mouse.url.mods();
+ if mouse_mode {
+ required_mods |= ModifiersState::SHIFT;
+ }
+
+ // Make sure all prerequisites for highlighting are met.
+ if selection
+ || !mouse.inside_text_area
+ || config.ui_config.mouse.url.launcher.is_none()
+ || required_mods != mods
+ || mouse.left_button_state == ElementState::Pressed
+ {
+ return None;
+ }
+
+ self.find_at(Point::new(mouse.line, mouse.column))
+ }
+
+ /// Find URL at location.
+ pub fn find_at(&self, point: Point) -> Option<Url> {
+ for url in &self.urls {
+ if (url.start()..=url.end()).contains(&point) {
+ return Some(url.clone());
+ }
+ }
+ None
+ }
+
+ fn reset(&mut self) {
+ self.locator = UrlLocator::new();
+ self.state = UrlLocation::Reset;
+ self.scheme_buffer.clear();
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use alacritty_terminal::index::{Column, Line};
+
+ fn text_to_cells(text: &str) -> Vec<RenderableCell> {
+ text.chars()
+ .enumerate()
+ .map(|(i, character)| RenderableCell {
+ character,
+ zerowidth: None,
+ graphic: None,
+ point: Point::new(Line(0), Column(i)),
+ fg: Default::default(),
+ bg: Default::default(),
+ bg_alpha: 0.,
+ flags: Flags::empty(),
+ is_match: false,
+ })
+ .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(Column(num_cols), &cell);
+ }
+
+ let url = urls.urls.first().unwrap();
+ assert_eq!(url.start().column, Column(5));
+ assert_eq!(url.end().column, 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(Column(num_cols), &cell);
+ }
+
+ assert_eq!(urls.urls.len(), 3);
+
+ assert_eq!(urls.urls[0].start().column, Column(5));
+ assert_eq!(urls.urls[0].end().column, Column(9));
+
+ assert_eq!(urls.urls[1].start().column, Column(11));
+ assert_eq!(urls.urls[1].end().column, Column(15));
+
+ assert_eq!(urls.urls[2].start().column, Column(17));
+ assert_eq!(urls.urls[2].end().column, Column(21));
+ }
+
+ #[test]
+ fn wide_urls() {
+ let input = text_to_cells("test https://こんにちは (http:여보세요) ing");
+ let num_cols = input.len() + 9;
+
+ let mut urls = Urls::new();
+
+ for cell in input {
+ urls.update(Column(num_cols), &cell);
+ }
+
+ assert_eq!(urls.urls.len(), 2);
+
+ assert_eq!(urls.urls[0].start().column, Column(5));
+ assert_eq!(urls.urls[0].end().column, Column(17));
+
+ assert_eq!(urls.urls[1].start().column, Column(20));
+ assert_eq!(urls.urls[1].end().column, Column(28));
+ }
+}