summaryrefslogtreecommitdiffstats
path: root/alacritty_terminal
diff options
context:
space:
mode:
authorChristian Duerr <chrisduerr@users.noreply.github.com>2019-08-01 15:37:01 +0000
committerGitHub <noreply@github.com>2019-08-01 15:37:01 +0000
commit9dddf649a15d103295f4ce97b8ae4c178c9623e0 (patch)
tree609cba8c7eecddc8a2b032e826967bcc04395592 /alacritty_terminal
parentf51c7b067a05dec7863cca9b8bfaf8329b0cfdfc (diff)
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')
-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]
<