diff options
author | Christian Duerr <chrisduerr@users.noreply.github.com> | 2019-08-01 15:37:01 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-01 15:37:01 +0000 |
commit | 9dddf649a15d103295f4ce97b8ae4c178c9623e0 (patch) | |
tree | 609cba8c7eecddc8a2b032e826967bcc04395592 /alacritty_terminal/src/url.rs | |
parent | f51c7b067a05dec7863cca9b8bfaf8329b0cfdfc (diff) | |
download | r-alacritty-9dddf649a15d103295f4ce97b8ae4c178c9623e0.tar.gz r-alacritty-9dddf649a15d103295f4ce97b8ae4c178c9623e0.tar.bz2 r-alacritty-9dddf649a15d103295f4ce97b8ae4c178c9623e0.zip |
Switch to rfind_url for URL detection
This switches to rfind_url for detecting URLs inside the grid. Instead
of expanding at the cursor position, the complete terminal is searched
from the bottom until the visible region is left with no active URL.
Instead of having the field `cur` publicly accessibly on the
`DisplayIterator`, there are the two methods `DisplayIterator::point`
and `DisplayIterator::cell` for accessing the current element of the
iterator now. This allows accessing the current element right after
creating the iterator.
Fixes #2629.
Fixes #2627.
Diffstat (limited to 'alacritty_terminal/src/url.rs')
-rw-r--r-- | alacritty_terminal/src/url.rs | 331 |
1 files changed, 27 insertions, 304 deletions
diff --git a/alacritty_terminal/src/url.rs b/alacritty_terminal/src/url.rs index c3c70267..f1b7934b 100644 --- a/alacritty_terminal/src/url.rs +++ b/alacritty_terminal/src/url.rs @@ -1,318 +1,41 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +use crate::ansi::TermInfo; +use crate::index::{Column, Linear, Point}; +use crate::term::Term; +use std::ops::RangeInclusive; -use unicode_width::UnicodeWidthChar; - -use crate::term::cell::{Cell, Flags}; - -// See https://tools.ietf.org/html/rfc3987#page-13 -const URL_SEPARATOR_CHARS: [char; 10] = ['<', '>', '"', ' ', '{', '}', '|', '\\', '^', '`']; -const URL_DENY_END_CHARS: [char; 7] = ['.', ',', ';', ':', '?', '!', '(']; -const URL_SCHEMES: [&str; 8] = - ["http://", "https://", "mailto:", "news:", "file://", "git://", "ssh://", "ftp://"]; - -/// URL text and origin of the original click position. -#[derive(Debug, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)] pub struct Url { - pub text: String, - pub origin: usize, + pub start: Point<usize>, + pub end: Point<usize>, } -/// Parser for streaming inside-out detection of URLs. -pub struct UrlParser { - state: String, - origin: usize, -} - -impl UrlParser { - pub fn new() -> Self { - UrlParser { state: String::new(), origin: 0 } - } - - /// Advance the parser one character to the left. - pub fn advance_left(&mut self, cell: &Cell) -> bool { - if cell.flags.contains(Flags::WIDE_CHAR_SPACER) { - self.origin += 1; - return false; - } +impl Url { + pub fn new(start: Point<usize>, length: usize, num_cols: usize) -> Self { + let unwrapped_end_col = start.col.0 + length - 1; + let end_col = unwrapped_end_col % num_cols; + let end_line = start.line - unwrapped_end_col / num_cols; - if self.advance(cell.c, 0) { - true - } else { - self.origin += 1; - false - } + Url { end: Point::new(end_line, Column(end_col)), start } } - /// Advance the parser one character to the right. - pub fn advance_right(&mut self, cell: &Cell) -> bool { - if cell.flags.contains(Flags::WIDE_CHAR_SPACER) { - return false; - } - - self.advance(cell.c, self.state.len()) + pub fn contains(&self, point: impl Into<Point<usize>>) -> bool { + let point = point.into(); + point.line <= self.start.line + && point.line >= self.end.line + && (point.line != self.start.line || point.col >= self.start.col) + && (point.line != self.end.line || point.col <= self.end.col) } - /// Returns the URL if the parser has found any. - pub fn url(mut self) -> Option<Url> { - // Remove non-alphabetical characters before the scheme - // https://tools.ietf.org/html/rfc3986#section-3.1 - if let Some(index) = self.state.find("://") { - let iter = - self.state.char_indices().rev().skip_while(|(byte_index, _)| *byte_index >= index); - for (byte_index, c) in iter { - match c { - 'a'..='z' | 'A'..='Z' => (), - _ => { - self.origin = - self.origin.saturating_sub(byte_index + c.width().unwrap_or(1)); - self.state = self.state.split_off(byte_index + c.len_utf8()); - break; - }, - } - } - } - - // Remove non-matching parenthesis and brackets - let mut open_parens_count: isize = 0; - let mut open_bracks_count: isize = 0; - for (i, c) in self.state.char_indices() { - match c { - '(' => open_parens_count += 1, - ')' if open_parens_count > 0 => open_parens_count -= 1, - '[' => open_bracks_count += 1, - ']' if open_bracks_count > 0 => open_bracks_count -= 1, - ')' | ']' => { - self.state.truncate(i); - break; - }, - _ => (), - } - } - - // Track number of quotes - let mut num_quotes = self.state.chars().filter(|&c| c == '\'').count(); - - // Remove all characters which aren't allowed at the end of a URL - while !self.state.is_empty() - && (URL_DENY_END_CHARS.contains(&self.state.chars().last().unwrap()) - || (num_quotes % 2 != 0 && self.state.ends_with('\'')) - || self.state.ends_with("''") - || self.state.ends_with("()")) - { - if self.state.pop().unwrap() == '\'' { - num_quotes -= 1; - } - } - - // Check if string is valid url - if self.origin > 0 && url::Url::parse(&self.state).is_ok() { - for scheme in &URL_SCHEMES { - if self.state.starts_with(scheme) { - return Some(Url { origin: self.origin - 1, text: self.state }); - } - } - } - - None - } + pub fn linear_bounds(&self, terminal: &Term) -> RangeInclusive<Linear> { + let mut start = self.start; + let mut end = self.end; - fn advance(&mut self, c: char, pos: usize) -> bool { - if URL_SEPARATOR_CHARS.contains(&c) - || (c >= '\u{00}' && c <= '\u{1F}') - || (c >= '\u{7F}' && c <= '\u{9F}') - { - true - } else { - self.state.insert(pos, c); - false - } - } -} - -#[cfg(test)] -mod tests { - use std::mem; - - use unicode_width::UnicodeWidthChar; - - use crate::clipboard::Clipboard; - use crate::grid::Grid; - use crate::index::{Column, Line, Point}; - use crate::message_bar::MessageBuffer; - use crate::term::cell::{Cell, Flags}; - use crate::term::{Search, SizeInfo, Term}; - - fn url_create_term(input: &str) -> Term { - let size = SizeInfo { - width: 21.0, - height: 51.0, - cell_width: 3.0, - cell_height: 3.0, - padding_x: 0.0, - padding_y: 0.0, - dpr: 1.0, - }; - - let width = input.chars().map(|c| if c.width() == Some(2) { 2 } else { 1 }).sum(); - let mut term = - Term::new(&Default::default(), size, MessageBuffer::new(), Clipboard::new_nop()); - let mut grid: Grid<Cell> = Grid::new(Line(1), Column(width), 0, Cell::default()); - - let mut i = 0; - for c in input.chars() { - grid[Line(0)][Column(i)].c = c; - - if c.width() == Some(2) { - grid[Line(0)][Column(i)].flags.insert(Flags::WIDE_CHAR); - grid[Line(0)][Column(i + 1)].flags.insert(Flags::WIDE_CHAR_SPACER); - grid[Line(0)][Column(i + 1)].c = ' '; - i += 1; - } - - i += 1; - } - - mem::swap(term.grid_mut(), &mut grid); - - term - } - - fn url_test(input: &str, expected: &str) { - let term = url_create_term(input); - let url = term.url_search(Point::new(0, Column(15))); - assert_eq!(url.map(|u| u.text), Some(expected.into())); - } - - #[test] - fn url_skip_invalid() { - let term = url_create_term("no url here"); - let url = term.url_search(Point::new(0, Column(4))); - assert_eq!(url, None); - - let term = url_create_term(" https://example.org"); - let url = term.url_search(Point::new(0, Column(0))); - assert_eq!(url, None); - } - - #[test] - fn url_origin() { - let term = url_create_term(" test https://example.org "); - let url = term.url_search(Point::new(0, Column(10))); - assert_eq!(url.map(|u| u.origin), Some(4)); - - let term = url_create_term("https://example.org"); - let url = term.url_search(Point::new(0, Column(0))); - assert_eq!(url.map(|u| u.origin), Some(0)); - - let term = url_create_term("https://全.org"); - let url = term.url_search(Point::new(0, Column(10))); - assert_eq!(url.map(|u| u.origin), Some(10)); - - let term = url_create_term("https://全.org"); - let url = term.url_search(Point::new(0, Column(8))); - assert_eq!(url.map(|u| u.origin), Some(8)); - - let term = url_create_term("https://全.org"); - let url = term.url_search(Point::new(0, Column(9))); - assert_eq!(url.map(|u| u.origin), Some(9)); - - let term = url_create_term("test@https://example.org"); - let url = term.url_search(Point::new(0, Column(9))); - assert_eq!(url.map(|u| u.origin), Some(4)); - - let term = url_create_term("test全https://example.org"); - let url = term.url_search(Point::new(0, Column(9))); - assert_eq!(url.map(|u| u.origin), Some(3)); - } - - #[test] - fn url_matching_chars() { - url_test("(https://example.org/test(ing))", "https://example.org/test(ing)"); - url_test("https://example.org/test(ing)", "https://example.org/test(ing)"); - url_test("((https://example.org))", "https://example.org"); - url_test(")https://example.org(", "https://example.org"); - url_test("https://example.org)", "https://example.org"); - url_test("https://example.org(", "https://example.org"); - url_test("(https://one.org/)(https://two.org/)", "https://one.org/"); - - url_test("https://[2001:db8:a0b:12f0::1]:80", "https://[2001:db8:a0b:12f0::1]:80"); - url_test("([(https://example.org/test(ing))])", "https://example.org/test(ing)"); - url_test("https://example.org/]()", "https://example.org/"); - url_test("[https://example.org]", "https://example.org"); - - url_test("'https://example.org/test'ing'''", "https://example.org/test'ing'"); - url_test("https://example.org/test'ing'", "https://example.org/test'ing'"); - url_test("'https://example.org'", "https://example.org"); - url_test("'https://example.org", "https://example.org"); - url_test("https://example.org'", "https://example.org"); - - url_test("(https://example.org/test全)", "https://example.org/test全"); - } - - #[test] - fn url_detect_end() { - url_test("https://example.org/test\u{00}ing", "https://example.org/test"); - url_test("https://example.org/test\u{1F}ing", "https://example.org/test"); - url_test("https://example.org/test\u{7F}ing", "https://example.org/test"); - url_test("https://example.org/test\u{9F}ing", "https://example.org/test"); - url_test("https://example.org/test\ting", "https://example.org/test"); - url_test("https://example.org/test ing", "https://example.org/test"); - } - - #[test] - fn url_remove_end_chars() { - url_test("https://example.org/test?ing", "https://example.org/test?ing"); - url_test("https://example.org.,;:)'!/?", "https://example.org"); - url_test("https://example.org'.", "https://example.org"); - url_test("https://example.org/test/?;:", "https://example.org/test/"); - } - - #[test] - fn url_remove_start_chars() { - url_test("complicated:https://example.org", "https://example.org"); - url_test("test.https://example.org", "https://example.org"); - url_test(",https://example.org", "https://example.org"); - url_test("\u{2502}https://example.org", "https://example.org"); - } - - #[test] - fn url_unicode() { - url_test("https://xn--example-2b07f.org", "https://xn--example-2b07f.org"); - url_test("https://example.org/\u{2008A}", "https://example.org/\u{2008A}"); - url_test("https://example.org/\u{f17c}", "https://example.org/\u{f17c}"); - url_test("https://üñîçøðé.com/ä", "https://üñîçøðé.com/ä"); - } + start = terminal.buffer_to_visible(start); + end = terminal.buffer_to_visible(end); - #[test] - fn url_schemes() { - url_test("mailto://example.org", "mailto://example.org"); - url_test("https://example.org", "https://example.org"); - url_test("http://example.org", "http://example.org"); - url_test("news://example.org", "news://example.org"); - url_test("file://example.org", "file://example.org"); - url_test("git://example.org", "git://example.org"); - url_test("ssh://example.org", "ssh://example.org"); - url_test("ftp://example.org", "ftp://example.org"); + let start = Linear::from_point(terminal.cols(), start); + let end = Linear::from_point(terminal.cols(), end); - assert_eq!(url_create_term("mailto.example.org").url_search(Point::default()), None); - assert_eq!(url_create_term("https:example.org").url_search(Point::default()), None); - assert_eq!(url_create_term("http:example.org").url_search(Point::default()), None); - assert_eq!(url_create_term("news.example.org").url_search(Point::default()), None); - assert_eq!(url_create_term("file:example.org").url_search(Point::default()), None); - assert_eq!(url_create_term("git:example.org").url_search(Point::default()), None); - assert_eq!(url_create_term("ssh:example.org").url_search(Point::default()), None); - assert_eq!(url_create_term("ftp:example.org").url_search(Point::default()), None); + RangeInclusive::new(start, end) } } |