summaryrefslogtreecommitdiff
path: root/alacritty_terminal
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty_terminal')
-rw-r--r--alacritty_terminal/Cargo.toml1
-rw-r--r--alacritty_terminal/src/grid/mod.rs40
-rw-r--r--alacritty_terminal/src/grid/tests.rs15
-rw-r--r--alacritty_terminal/src/index.rs6
-rw-r--r--alacritty_terminal/src/input.rs64
-rw-r--r--alacritty_terminal/src/term/mod.rs179
-rw-r--r--alacritty_terminal/src/url.rs331
7 files changed, 190 insertions, 446 deletions
diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml
index 3b9f9918..eb72e7d8 100644
--- a/alacritty_terminal/Cargo.toml
+++ b/alacritty_terminal/Cargo.toml
@@ -32,6 +32,7 @@ terminfo = "0.6.1"
url = "1.7.1"
crossbeam-channel = "0.3.8"
copypasta = { path = "../copypasta" }
+rfind_url = "0.4.0"
[target.'cfg(unix)'.dependencies]
nix = "0.14.1"
diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs
index 1925a6f4..e2cda175 100644
--- a/alacritty_terminal/src/grid/mod.rs
+++ b/alacritty_terminal/src/grid/mod.rs
@@ -120,7 +120,7 @@ pub enum Scroll {
}
#[derive(Copy, Clone)]
-pub enum ViewportPosition {
+enum ViewportPosition {
Visible(Line),
Above,
Below,
@@ -141,11 +141,25 @@ impl<T: GridCell + Copy + Clone> Grid<T> {
}
}
- pub fn visible_to_buffer(&self, point: Point) -> Point<usize> {
- Point { line: self.visible_line_to_buffer(point.line), col: point.col }
+ pub fn buffer_to_visible(&self, point: impl Into<Point<usize>>) -> Point<usize> {
+ let mut point = point.into();
+
+ match self.buffer_line_to_visible(point.line) {
+ ViewportPosition::Visible(line) => point.line = line.0,
+ ViewportPosition::Above => {
+ point.col = Column(0);
+ point.line = 0;
+ },
+ ViewportPosition::Below => {
+ point.col = self.num_cols();
+ point.line = self.num_lines().0 - 1;
+ },
+ }
+
+ point
}
- pub fn buffer_line_to_visible(&self, line: usize) -> ViewportPosition {
+ fn buffer_line_to_visible(&self, line: usize) -> ViewportPosition {
let offset = line.saturating_sub(self.display_offset);
if line < self.display_offset {
ViewportPosition::Below
@@ -156,7 +170,11 @@ impl<T: GridCell + Copy + Clone> Grid<T> {
}
}
- pub fn visible_line_to_buffer(&self, line: Line) -> usize {
+ pub fn visible_to_buffer(&self, point: Point) -> Point<usize> {
+ Point { line: self.visible_line_to_buffer(point.line), col: point.col }
+ }
+
+ fn visible_line_to_buffer(&self, line: Line) -> usize {
self.line_to_offset(line) + self.display_offset
}
@@ -596,7 +614,17 @@ pub struct GridIterator<'a, T> {
grid: &'a Grid<T>,
/// Current position of the iterator within the grid.
- pub cur: Point<usize>,
+ cur: Point<usize>,
+}
+
+impl<'a, T> GridIterator<'a, T> {
+ pub fn point(&self) -> Point<usize> {
+ self.cur
+ }
+
+ pub fn cell(&self) -> &'a T {
+ &self.grid[self.cur.line][self.cur.col]
+ }
}
impl<'a, T> Iterator for GridIterator<'a, T> {
diff --git a/alacritty_terminal/src/grid/tests.rs b/alacritty_terminal/src/grid/tests.rs
index a352e747..d28e7833 100644
--- a/alacritty_terminal/src/grid/tests.rs
+++ b/alacritty_terminal/src/grid/tests.rs
@@ -109,8 +109,8 @@ fn test_iter() {
assert_eq!(None, iter.prev());
assert_eq!(Some(&1), iter.next());
- assert_eq!(Column(1), iter.cur.col);
- assert_eq!(4, iter.cur.line);
+ assert_eq!(Column(1), iter.point().col);
+ assert_eq!(4, iter.point().line);
assert_eq!(Some(&2), iter.next());
assert_eq!(Some(&3), iter.next());
@@ -118,12 +118,15 @@ fn test_iter() {
// test linewrapping
assert_eq!(Some(&5), iter.next());
- assert_eq!(Column(0), iter.cur.col);
- assert_eq!(3, iter.cur.line);
+ assert_eq!(Column(0), iter.point().col);
+ assert_eq!(3, iter.point().line);
assert_eq!(Some(&4), iter.prev());
- assert_eq!(Column(4), iter.cur.col);
- assert_eq!(4, iter.cur.line);
+ assert_eq!(Column(4), iter.point().col);
+ assert_eq!(4, iter.point().line);
+
+ // Make sure iter.cell() returns the current iterator position
+ assert_eq!(&4, iter.cell());
// test that iter ends at end of grid
let mut final_iter = grid.iter_from(Point { line: 0, col: Column(4) });
diff --git a/alacritty_terminal/src/index.rs b/alacritty_terminal/src/index.rs
index f6ea4ad3..7d1a8e91 100644
--- a/alacritty_terminal/src/index.rs
+++ b/alacritty_terminal/src/index.rs
@@ -59,6 +59,12 @@ impl From<Point<usize>> for Point<isize> {
}
}
+impl From<Point<usize>> for Point<Line> {
+ fn from(point: Point<usize>) -> Self {
+ Point::new(Line(point.line), point.col)
+ }
+}
+
impl From<Point<isize>> for Point<usize> {
fn from(point: Point<isize>) -> Self {
Point::new(point.line as usize, point.col)
diff --git a/alacritty_terminal/src/input.rs b/alacritty_terminal/src/input.rs
index 9443a1a9..8eceef16 100644
--- a/alacritty_terminal/src/input.rs
+++ b/alacritty_terminal/src/input.rs
@@ -18,27 +18,25 @@
//! In order to figure that out, state about which modifier keys are pressed
//! needs to be tracked. Additionally, we need a bit of a state machine to
//! determine what to do when a non-modifier key is pressed.
+use crate::url::Url;
use std::borrow::Cow;
use std::mem;
-use std::ops::RangeInclusive;
use std::time::Instant;
use glutin::{
ElementState, KeyboardInput, ModifiersState, MouseButton, MouseCursor, MouseScrollDelta,
TouchPhase,
};
-use unicode_width::UnicodeWidthStr;
use crate::ansi::{ClearMode, Handler};
use crate::clipboard::ClipboardType;
use crate::config::{self, Key};
use crate::event::{ClickState, Mouse};
use crate::grid::Scroll;
-use crate::index::{Column, Line, Linear, Point, Side};
+use crate::index::{Column, Line, Point, Side};
use crate::message_bar::{self, Message};
use crate::term::mode::TermMode;
-use crate::term::{Search, SizeInfo, Term};
-use crate::url::Url;
+use crate::term::{SizeInfo, Term};
use crate::util::start_daemon;
pub const FONT_SIZE_STEP: f32 = 0.5;
@@ -392,15 +390,18 @@ enum MousePosition {
impl<'a, A: ActionContext + 'a> Processor<'a, A> {
fn mouse_position(&mut self, point: Point) -> MousePosition {
+ let buffer_point = self.ctx.terminal().visible_to_buffer(point);
+
+ // Check message bar before URL to ignore URLs in the message bar
if let Some(message) = self.message_at_point(Some(point)) {
if self.message_close_at_point(point, message) {
MousePosition::MessageBarButton
} else {
MousePosition::MessageBar
}
- // Check for url should be after check for message bar, since we're not looking into
- // message bar content.
- } else if let Some(url) = self.ctx.terminal().url_search(point.into()) {
+ } else if let Some(url) =
+ self.ctx.terminal().urls().drain(..).find(|url| url.contains(buffer_point))
+ {
MousePosition::Url(url)
} else {
MousePosition::Terminal
@@ -443,7 +444,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
&& (!self.ctx.terminal().mode().intersects(mouse_mode) || modifiers.shift)
&& self.mouse_config.url.launcher.is_some()
{
- let url_bounds = self.url_bounds_at_point(url, point);
+ let url_bounds = url.linear_bounds(self.ctx.terminal());
self.ctx.terminal_mut().set_url_highlight(url_bounds);
self.ctx.terminal_mut().set_mouse_cursor(MouseCursor::Hand);
self.ctx.terminal_mut().dirty = true;
@@ -485,47 +486,6 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
}
}
- fn url_bounds_at_point(&self, url: Url, point: Point) -> RangeInclusive<Linear> {
- let Url { origin, text } = url;
- let cols = self.ctx.size_info().cols().0;
-
- // Calculate the URL's start position
- let lines_before = (origin + cols - point.col.0 - 1) / cols;
- let (start_col, start_line) = if lines_before > point.line.0 {
- (0, 0)
- } else {
- let start_col = (cols + point.col.0 - origin % cols) % cols;
- let start_line = point.line.0 - lines_before;
- (start_col, start_line)
- };
-
- let start = Point::new(start_line, Column(start_col));
-
- // Calculate the URL's highlight end position
- let len = text.width();
- let url_end_col_denormilized = point.col.0 + len - origin;
-
- // This means that url ends at the last cell of the line
- let end_col = if url_end_col_denormilized % cols == 0 {
- cols - 1
- } else {
- url_end_col_denormilized % cols - 1
- };
-
- let end_line = if end_col == cols - 1 {
- point.line.0 + (url_end_col_denormilized) / cols - 1
- } else {
- point.line.0 + (url_end_col_denormilized) / cols
- };
-
- let end = Point::new(end_line, Column(end_col));
-
- let start = Linear::from_point(Column(cols), start);
- let end = Linear::from_point(Column(cols), end);
-
- RangeInclusive::new(start, end)
- }
-
fn get_mouse_side(&self) -> Side {
let size_info = self.ctx.size_info();
let x = self.ctx.mouse().x;
@@ -705,7 +665,9 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
return None;
}
- let text = self.ctx.terminal().url_search(point.into())?.text;
+ let point = self.ctx.terminal().visible_to_buffer(point);
+ let url = self.ctx.terminal().urls().drain(..).find(|url| url.contains(point))?;
+ let text = self.ctx.terminal().url_to_string(&url);
let launcher = self.mouse_config.url.launcher.as_ref()?;
let mut args = launcher.args().to_vec();
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs
index 691a4fe1..0cc2cd6d 100644
--- a/alacritty_terminal/src/term/mod.rs
+++ b/alacritty_terminal/src/term/mod.rs
@@ -20,17 +20,17 @@ use std::{io, mem, ptr};
use font::{self, Size};
use glutin::MouseCursor;
+use rfind_url::{Parser, ParserState};
use unicode_width::UnicodeWidthChar;
use crate::ansi::{
- self, Attr, CharsetIndex, Color, CursorStyle, Handler, NamedColor, StandardCharset,
+ self, Attr, CharsetIndex, Color, CursorStyle, Handler, NamedColor, StandardCharset, TermInfo,
};
use crate::clipboard::{Clipboard, ClipboardType};
use crate::config::{Config, VisualBellAnimation};
use crate::cursor::CursorKey;
use crate::grid::{
BidirectionalIterator, DisplayIter, Grid, GridCell, IndexRegion, Indexed, Scroll,
- ViewportPosition,
};
use crate::index::{self, Column, Contains, IndexRange, Line, Linear, Point};
use crate::input::FONT_SIZE_STEP;
@@ -38,7 +38,7 @@ use crate::message_bar::MessageBuffer;
use crate::selection::{self, Selection, SelectionRange, Span};
use crate::term::cell::{Cell, Flags, LineLength};
use crate::term::color::Rgb;
-use crate::url::{Url, UrlParser};
+use crate::url::Url;
#[cfg(windows)]
use crate::tty;
@@ -58,8 +58,6 @@ pub trait Search {
fn semantic_search_left(&self, _: Point<usize>) -> Point<usize>;
/// Find the nearest semantic boundary _to the point_ of provided point.
fn semantic_search_right(&self, _: Point<usize>) -> Point<usize>;
- /// Find the nearest URL boundary in both directions.
- fn url_search(&self, _: Point<usize>) -> Option<Url>;
/// Find the nearest matching bracket.
fn bracket_search(&self, _: Point<usize>) -> Option<Point<usize>>;
}
@@ -77,11 +75,11 @@ impl Search for Term {
break;
}
- if iter.cur.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) {
+ if iter.point().col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) {
break; // cut off if on new line or hit escape char
}
- point = iter.cur;
+ point = iter.point();
}
point
@@ -99,7 +97,7 @@ impl Search for Term {
break;
}
- point = iter.cur;
+ point = iter.point();
if point.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) {
break; // cut off if on new line or hit escape char
@@ -109,40 +107,6 @@ impl Search for Term {
point
}
- fn url_search(&self, mut point: Point<usize>) -> Option<Url> {
- let last_col = self.grid.num_cols() - 1;
-
- // Switch first line from top to bottom
- point.line = self.grid.num_lines().0 - point.line - 1;
-
- // Remove viewport scroll offset
- point.line += self.grid.display_offset();
-
- // Create forwards and backwards iterators
- let mut iterf = self.grid.iter_from(point);
- point.col += 1;
- let mut iterb = self.grid.iter_from(point);
-
- // Find URLs
- let mut url_parser = UrlParser::new();
- while let Some(cell) = iterb.prev() {
- if (iterb.cur.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE))
- || url_parser.advance_left(cell)
- {
- break;
- }
- }
-
- while let Some(cell) = iterf.next() {
- if url_parser.advance_right(cell)
- || (iterf.cur.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE))
- {
- break;
- }
- }
- url_parser.url()
- }
-
fn bracket_search(&self, point: Point<usize>) -> Option<Point<usize>> {
let start_char = self.grid[point.line][point.col].c;
@@ -175,7 +139,7 @@ impl Search for Term {
// Check if the bracket matches
if c == end_char && skip_pairs == 0 {
- return Some(iter.cur);
+ return Some(iter.point());
} else if c == start_char {
skip_pairs += 1;
} else if c == end_char {
@@ -235,44 +199,27 @@ impl<'a> RenderableCellsIter<'a> {
let cursor_offset = grid.line_to_offset(term.cursor.point.line);
let inner = grid.display_iter();
- let selection_range = selection.and_then(|span| {
- // Get on-screen lines of the selection's locations
- let start_line = grid.buffer_line_to_visible(span.start.line);
- let end_line = grid.buffer_line_to_visible(span.end.line);
-
- // Limit block selection columns to within start/end points
- let (limit_start, limit_end) =
- if span.is_block { (span.start.col, span.end.col) } else { (Column(0), Column(0)) };
-
- // Get start/end locations based on what part of selection is on screen
- let locations = match (start_line, end_line) {
- (ViewportPosition::Visible(start_line), ViewportPosition::Visible(end_line)) => {
- Some((start_line, span.start.col, end_line, span.end.col))
- },
- (ViewportPosition::Visible(start_line), ViewportPosition::Above) => {
- Some((start_line, span.start.col, Line(0), limit_end))
- },
- (ViewportPosition::Below, ViewportPosition::Visible(end_line)) => {
- Some((grid.num_lines(), limit_start, end_line, span.end.col))
- },
- (ViewportPosition::Below, ViewportPosition::Above) => {
- Some((grid.num_lines(), limit_start, Line(0), limit_end))
- },
- _ => None,
+ let selection_range = selection.map(|span| {
+ let (limit_start, limit_end) = if span.is_block {
+ (span.end.col, span.start.col)
+ } else {
+ (Column(0), term.cols() - 1)
};
- locations.map(|(start_line, start_col, end_line, end_col)| {
- // start and end *lines* are swapped as we switch from buffer to
- // Line coordinates.
- let mut end = Point { line: start_line, col: start_col };
- let mut start = Point { line: end_line, col: end_col };
+ // Get on-screen lines of the selection's locations
+ let mut start = term.buffer_to_visible(span.start);
+ let mut end = term.buffer_to_visible(span.end);
- if start > end {
- ::std::mem::swap(&mut start, &mut end);
- }
+ // Start and end lines are swapped as we switch from buffer to line coordinates
+ if start > end {
+ mem::swap(&mut start, &mut end);
+ }
- SelectionRange::new(start, end, span.is_block)
- })
+ // Trim start/end with partially visible block selection
+ start.col = max(limit_start, start.col);
+ end.col = min(limit_end, end.col);
+
+ SelectionRange::new(start.into(), end.into(), span.is_block)
});
// Load cursor glyph
@@ -1108,10 +1055,14 @@ impl Term {
Some(res)
}
- pub(crate) fn visible_to_buffer(&self, point: Point) -> Point<usize> {
+ pub fn visible_to_buffer(&self, point: Point) -> Point<usize> {
self.grid.visible_to_buffer(point)
}
+ pub fn buffer_to_visible(&self, point: impl Into<Point<usize>>) -> Point<usize> {
+ self.grid.buffer_to_visible(point)
+ }
+
/// Convert the given pixel values to a grid coordinate
///
/// The mouse coordinates are expected to be relative to the top left. The
@@ -1368,9 +1319,79 @@ impl Term {
pub fn clipboard(&mut self) -> &mut Clipboard {
&mut self.clipboard
}
+
+ pub fn urls(&self) -> Vec<Url> {
+ let display_offset = self.grid.display_offset();
+ let num_cols = self.grid.num_cols().0;
+ let last_col = Column(num_cols - 1);
+ let last_line = self.grid.num_lines() - 1;
+
+ let grid_end_point = Point::new(0, last_col);
+ let mut iter = self.grid.iter_from(grid_end_point);
+
+ let mut parser = Parser::new();
+ let mut extra_url_len = 0;
+ let mut urls = Vec::new();
+
+ let mut c = Some(iter.cell());
+ while let Some(cell) = c {
+ let point = iter.point();
+ c = iter.prev();
+
+ // Skip double-width cell but extend URL length
+ if cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) {
+ extra_url_len += 1;
+ continue;
+ }
+
+ // Interrupt URLs on line break
+ if point.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) {
+ extra_url_len = 0;
+ parser.reset();
+ }
+
+ match parser.advance(cell.c) {
+ ParserState::Url(length) => {
+ urls.push(Url::new(point, length + extra_url_len, num_cols))
+ },
+ ParserState::NoUrl => {
+ extra_url_len = 0;
+
+ // Stop searching for URLs once the viewport has been left without active URL
+ if point.line > last_line.0 + display_offset {
+ break;
+ }
+ },
+ _ => (),
+ }
+ }
+
+ urls
+ }
+
+ pub fn url_to_string(&self, url: &Url) -> String {
+ let mut url_text = String::new();
+
+ let mut iter = self.grid.iter_from(url.start);
+
+ let mut c = Some(iter.cell());
+ while let Some(cell) = c {
+ if !cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) {
+ url_text.push(cell.c);
+ }
+
+ if iter.point() == url.end {
+ break;
+ }
+
+ c = iter.next();
+ }
+
+ url_text
+ }
}
-impl ansi::TermInfo for Term {
+impl TermInfo for Term {
#[inline]
fn lines(&self) -> Line {
self.grid.num_lines()
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)
}
}