diff options
author | Christian Duerr <chrisduerr@users.noreply.github.com> | 2019-03-19 19:14:17 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-03-19 19:14:17 +0000 |
commit | a672f7d553ac17d5aaef8dc667dee31d5815677d (patch) | |
tree | 0289e3deb60e3fdce6025fded1ec9c3299bc893d /src | |
parent | eb7a1ea803ba54d3b8cd6af49255eb8fbe0d7544 (diff) | |
download | alacritty-a672f7d553ac17d5aaef8dc667dee31d5815677d.tar.gz alacritty-a672f7d553ac17d5aaef8dc667dee31d5815677d.zip |
Add URL hover highlighting
This changes the cursor whenever it moves to a cell which contains
part of a URL.
When a URL is hovered over, all characters that are recognized as part
of the URL will be underlined and the mouse cursor shape will be
changed. After the cursor leaves the URL, the previous hover state is
restored.
This also changes the behavior when clicking an illegal character right
in front of a URL. Previously this would still launch the URL, but strip
the illegal character. Now these clicks are ignored to make sure there's
no mismatch between underline and legal URL click positions
Diffstat (limited to 'src')
-rw-r--r-- | src/ansi.rs | 2 | ||||
-rw-r--r-- | src/event.rs | 11 | ||||
-rw-r--r-- | src/grid/mod.rs | 60 | ||||
-rw-r--r-- | src/grid/tests.rs | 12 | ||||
-rw-r--r-- | src/index.rs | 77 | ||||
-rw-r--r-- | src/input.rs | 108 | ||||
-rw-r--r-- | src/lib.rs | 7 | ||||
-rw-r--r-- | src/selection.rs | 3 | ||||
-rw-r--r-- | src/term/mod.rs | 25 | ||||
-rw-r--r-- | src/url.rs | 141 | ||||
-rw-r--r-- | src/window.rs | 11 |
11 files changed, 309 insertions, 148 deletions
diff --git a/src/ansi.rs b/src/ansi.rs index 5d6f1e14..dce83dac 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -19,9 +19,9 @@ use std::str; use vte; use base64; +use glutin::MouseCursor; use crate::index::{Column, Line, Contains}; -use crate::MouseCursor; use crate::term::color::Rgb; // Parse color arguments diff --git a/src/event.rs b/src/event.rs index 121c0c42..76e7e111 100644 --- a/src/event.rs +++ b/src/event.rs @@ -10,7 +10,7 @@ use std::env; use serde_json as json; use parking_lot::MutexGuard; -use glutin::{self, ModifiersState, Event, ElementState, MouseButton}; +use glutin::{self, ModifiersState, Event, ElementState, MouseButton, MouseCursor}; use copypasta::{Clipboard, Load, Store, Buffer as ClipboardBuffer}; use glutin::dpi::PhysicalSize; @@ -222,6 +222,13 @@ pub enum ClickState { TripleClick, } +/// Temporary save state for restoring mouse cursor and underline after unhovering a URL. +pub struct UrlHoverSaveState { + pub mouse_cursor: MouseCursor, + pub underlined: Vec<bool>, + pub start: Point<usize>, +} + /// State of the mouse pub struct Mouse { pub x: usize, @@ -238,6 +245,7 @@ pub struct Mouse { pub lines_scrolled: f32, pub block_url_launcher: bool, pub last_button: MouseButton, + pub url_hover_save: Option<UrlHoverSaveState>, } impl Default for Mouse { @@ -257,6 +265,7 @@ impl Default for Mouse { lines_scrolled: 0.0, block_url_launcher: false, last_button: MouseButton::Other(0), + url_hover_save: None, } } } diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 837d9b0e..c555400d 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -17,7 +17,7 @@ use std::cmp::{min, max, Ordering}; use std::ops::{Deref, Range, Index, IndexMut, RangeTo, RangeFrom, RangeFull}; -use crate::index::{self, Point, Line, Column, IndexRange}; +use crate::index::{self, Point, Line, Column, IndexRange, PointIterator}; use crate::selection::Selection; mod row; @@ -105,14 +105,6 @@ pub struct Grid<T> { max_scroll_limit: usize, } -pub struct GridIterator<'a, T> { - /// Immutable grid reference - grid: &'a Grid<T>, - - /// Current position of the iterator within the grid. - pub cur: Point<usize>, -} - #[derive(Copy, Clone)] pub enum Scroll { Lines(isize), @@ -587,7 +579,7 @@ impl<T> Grid<T> { pub fn iter_from(&self, point: Point<usize>) -> GridIterator<'_, T> { GridIterator { grid: self, - cur: point, + point_iter: point.iter(self.num_cols() - 1, self.len() - 1), } } @@ -602,43 +594,28 @@ impl<T> Grid<T> { } } +pub struct GridIterator<'a, T> { + point_iter: PointIterator<usize>, + grid: &'a Grid<T>, +} + +impl<'a, T> GridIterator<'a, T> { + pub fn cur(&self) -> Point<usize> { + self.point_iter.cur + } +} + impl<'a, T> Iterator for GridIterator<'a, T> { type Item = &'a T; fn next(&mut self) -> Option<Self::Item> { - let last_col = self.grid.num_cols() - Column(1); - match self.cur { - Point { line, col } if line == 0 && col == last_col => None, - Point { col, .. } if - (col == last_col) => { - self.cur.line -= 1; - self.cur.col = Column(0); - Some(&self.grid[self.cur.line][self.cur.col]) - }, - _ => { - self.cur.col += Column(1); - Some(&self.grid[self.cur.line][self.cur.col]) - } - } + self.point_iter.next().map(|p| &self.grid[p.line][p.col]) } } impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { fn prev(&mut self) -> Option<Self::Item> { - let num_cols = self.grid.num_cols(); - - match self.cur { - Point { line, col: Column(0) } if line == self.grid.len() - 1 => None, - Point { col: Column(0), .. } => { - self.cur.line += 1; - self.cur.col = num_cols - Column(1); - Some(&self.grid[self.cur.line][self.cur.col]) - }, - _ => { - self.cur.col -= Column(1); - Some(&self.grid[self.cur.line][self.cur.col]) - } - } + self.point_iter.prev().map(|p| &self.grid[p.line][p.col]) } } @@ -669,6 +646,13 @@ impl<T> IndexMut<index::Line> for Grid<T> { } } +impl<T> IndexMut<usize> for Grid<T> { + #[inline] + fn index_mut(&mut self, index: usize) -> &mut Row<T> { + &mut self.raw[index] + } +} + impl<'point, T> Index<&'point Point> for Grid<T> { type Output = T; diff --git a/src/grid/tests.rs b/src/grid/tests.rs index 82edda69..33d772a2 100644 --- a/src/grid/tests.rs +++ b/src/grid/tests.rs @@ -112,8 +112,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.cur().col); + assert_eq!(4, iter.cur().line); assert_eq!(Some(&2), iter.next()); assert_eq!(Some(&3), iter.next()); @@ -121,12 +121,12 @@ 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.cur().col); + assert_eq!(3, iter.cur().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.cur().col); + assert_eq!(4, iter.cur().line); // test that iter ends at end of grid diff --git a/src/index.rs b/src/index.rs index afeaa271..6dbfbc40 100644 --- a/src/index.rs +++ b/src/index.rs @@ -17,7 +17,9 @@ /// Indexing types and implementations for Grid and Line use std::cmp::{Ord, Ordering}; use std::fmt; -use std::ops::{self, Deref, Add, Range, RangeInclusive}; +use std::ops::{self, Deref, Range, RangeInclusive, Add, Sub, AddAssign, SubAssign}; + +use crate::grid::BidirectionalIterator; /// The side of a cell #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -70,6 +72,67 @@ impl From<Point> for Point<usize> { } } +impl<T> Point<T> +where + T: Copy + Default + SubAssign<usize> + PartialEq, +{ + pub fn iter(&self, last_col: Column, last_line: T) -> PointIterator<T> { + PointIterator { + cur: *self, + last_col, + last_line, + } + } +} + +pub struct PointIterator<T> { + pub cur: Point<T>, + last_col: Column, + last_line: T, +} + +impl<T> Iterator for PointIterator<T> +where + T: Copy + Default + SubAssign<usize> + PartialEq, +{ + type Item = Point<T>; + + fn next(&mut self) -> Option<Self::Item> { + match self.cur { + Point { line, col } if line == Default::default() && col == self.last_col => None, + Point { col, .. } if col == self.last_col => { + self.cur.line -= 1; + self.cur.col = Column(0); + Some(self.cur) + }, + _ => { + self.cur.col += Column(1); + Some(self.cur) + } + } + } +} + +impl<T> BidirectionalIterator for PointIterator<T> +where + T: Copy + Default + AddAssign<usize> + SubAssign<usize> + PartialEq, +{ + fn prev(&mut self) -> Option<Self::Item> { + match self.cur { + Point { line, col: Column(0) } if line == self.last_line => None, + Point { col: Column(0), .. } => { + self.cur.line += 1; + self.cur.col = self.last_col; + Some(self.cur) + }, + _ => { + self.cur.col -= Column(1); + Some(self.cur) + } + } + } +} + /// A line /// /// Newtype to avoid passing values incorrectly @@ -312,28 +375,28 @@ macro_rules! ops { } } } - impl ops::AddAssign<$ty> for $ty { + impl AddAssign<$ty> for $ty { #[inline] fn add_assign(&mut self, rhs: $ty) { self.0 += rhs.0 } } - impl ops::SubAssign<$ty> for $ty { + impl SubAssign<$ty> for $ty { #[inline] fn sub_assign(&mut self, rhs: $ty) { self.0 -= rhs.0 } } - impl ops::AddAssign<usize> for $ty { + impl AddAssign<usize> for $ty { #[inline] fn add_assign(&mut self, rhs: usize) { self.0 += rhs } } - impl ops::SubAssign<usize> for $ty { + impl SubAssign<usize> for $ty { #[inline] fn sub_assign(&mut self, rhs: usize) { self.0 -= rhs @@ -347,7 +410,7 @@ macro_rules! ops { } } - impl ops::Add<usize> for $ty { + impl Add<usize> for $ty { type Output = $ty; #[inline] @@ -356,7 +419,7 @@ macro_rules! ops { } } - impl ops::Sub<usize> for $ty { + impl Sub<usize> for $ty { type Output = $ty; #[inline] diff --git a/src/input.rs b/src/input.rs index 32e4dcbd..8e72c8b7 100644 --- a/src/input.rs +++ b/src/input.rs @@ -21,20 +21,26 @@ use std::borrow::Cow; use std::mem; use std::time::Instant; +use std::iter::once; use copypasta::{Clipboard, Load, Buffer as ClipboardBuffer}; -use glutin::{ElementState, MouseButton, TouchPhase, MouseScrollDelta, ModifiersState, KeyboardInput}; +use glutin::{ + ElementState, KeyboardInput, ModifiersState, MouseButton, MouseCursor, MouseScrollDelta, + TouchPhase, +}; use crate::config::{self, Key}; use crate::grid::Scroll; -use crate::event::{ClickState, Mouse}; +use crate::event::{ClickState, Mouse, UrlHoverSaveState}; use crate::index::{Line, Column, Side, Point}; use crate::term::{Term, SizeInfo, Search}; use crate::term::mode::TermMode; +use crate::term::cell::Flags; use crate::util::fmt::Red; use crate::util::start_daemon; use crate::message_bar::{self, Message}; use crate::ansi::{Handler, ClearMode}; +use crate::url::Url; pub const FONT_SIZE_STEP: f32 = 0.5; @@ -385,19 +391,23 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { let motion_mode = TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG; let report_mode = TermMode::MOUSE_REPORT_CLICK | motion_mode; - // Don't launch URLs if mouse has moved - if prev_line != self.ctx.mouse().line + let mouse_moved = prev_line != self.ctx.mouse().line || prev_col != self.ctx.mouse().column - || prev_side != cell_side - { + || prev_side != cell_side; + + // Don't launch URLs if mouse has moved + if mouse_moved { self.ctx.mouse_mut().block_url_launcher = true; } - // Ignore motions over the message bar - if self.message_at_point(Some(point)).is_some() { + // Only report motions when cell changed and mouse is not over the message bar + if self.message_at_point(Some(point)).is_some() || !mouse_moved { return; } + // Underline URLs and change cursor on hover + self.update_url_highlight(point, modifiers); + if self.ctx.mouse().left_button_state == ElementState::Pressed && (modifiers.shift || !self.ctx.terminal().mode().intersects(report_mode)) { @@ -409,8 +419,6 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { cell_side, ); } else if self.ctx.terminal().mode().intersects(motion_mode) - // Only report motion when changing cells - && (prev_line != self.ctx.mouse().line || prev_col != self.ctx.mouse().column) && size_info.contains_point(x, y, false) { if self.ctx.mouse().left_button_state == ElementState::Pressed { @@ -425,6 +433,84 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { } } + /// Underline URLs and change the mouse cursor when URL hover state changes. + fn update_url_highlight(&mut self, point: Point, modifiers: ModifiersState) { + let mouse_mode = + TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG | TermMode::MOUSE_REPORT_CLICK; + + // Only show URLs as launchable when all required modifiers are pressed + let url = if self.mouse_config.url.modifiers.relaxed_eq(modifiers) + && (!self.ctx.terminal().mode().contains(TermMode::ALT_SCREEN) || modifiers.shift) + { + self.ctx.terminal().url_search(point.into()) + } else { + None + }; + + if let Some(Url { text, origin }) = url { + let mouse_cursor = if self.ctx.terminal().mode().intersects(mouse_mode) { + MouseCursor::Default + } else { + MouseCursor::Text + }; + + let cols = self.ctx.size_info().cols().0; + let last_line = self.ctx.size_info().lines().0 - 1; + + // Calculate the URL's start position + let col = (cols + point.col.0 - origin % cols) % cols; + let line = last_line - point.line.0 + (origin + cols - point.col.0 - 1) / cols; + let start = Point::new(line, Column(col)); + + // Update URLs only on change, so they don't all get marked as underlined + if self.ctx.mouse().url_hover_save.as_ref().map(|hs| hs.start) == Some(start) { + return; + } + + // Since the URL changed without reset, we need to clear the previous underline + if let Some(hover_save) = self.ctx.mouse_mut().url_hover_save.take() { + self.reset_underline(&hover_save); + } + + // Underline all cells and store their current underline state + let mut underlined = Vec::with_capacity(text.len()); + let iter = once(start).chain(start.iter(Column(cols - 1), last_line)); + for point in iter.take(text.len()) { + let cell = &mut self.ctx.terminal_mut().grid_mut()[point.line][point.col]; + underlined.push(cell.flags.contains(Flags::UNDERLINE)); + cell.flags.insert(Flags::UNDERLINE); + } + + // Save the higlight state for restoring it again + self.ctx.mouse_mut().url_hover_save = Some(UrlHoverSaveState { + mouse_cursor, + underlined, + start, + }); + + self.ctx.terminal_mut().set_mouse_cursor(MouseCursor::Hand); + self.ctx.terminal_mut().dirty = true; + } else if let Some(hover_save) = self.ctx.mouse_mut().url_hover_save.take() { + self.ctx.terminal_mut().set_mouse_cursor(hover_save.mouse_cursor); + self.ctx.terminal_mut().dirty = true; + self.reset_underline(&hover_save); + } + } + + /// Reset the underline state after unhovering a URL. + fn reset_underline(&mut self, hover_save: &UrlHoverSaveState) { + let last_col = self.ctx.size_info().cols() - 1; + let last_line = self.ctx.size_info().lines().0 - 1; + + let mut iter = once(hover_save.start).chain(hover_save.start.iter(last_col, last_line)); + for underlined in &hover_save.underlined { + if let (Some(point), false) = (iter.next(), underlined) { + let cell = &mut self.ctx.terminal_mut().grid_mut()[point.line][point.col]; + cell.flags.remove(Flags::UNDERLINE); + } + } + } + fn get_mouse_side(&self) -> Side { let size_info = self.ctx.size_info(); let x = self.ctx.mouse().x; @@ -600,7 +686,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { return None; } - let text = self.ctx.terminal().url_search(point.into())?; + let text = self.ctx.terminal().url_search(point.into())?.text; let launcher = self.mouse_config.url.launcher.as_ref()?; let mut args = launcher.args().to_vec(); @@ -52,13 +52,6 @@ mod url; pub use crate::grid::Grid; pub use crate::term::Term; -/// Facade around [winit's `MouseCursor`](glutin::MouseCursor) -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub enum MouseCursor { - Arrow, - Text, -} - pub mod gl { #![allow(clippy::all)] include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); diff --git a/src/selection.rs b/src/selection.rs index e609b294..8009b805 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -433,6 +433,7 @@ impl Span { mod test { use crate::index::{Line, Column, Side, Point}; use super::{Selection, Span, SpanType}; + use crate::url::Url; struct Dimensions(Point); impl super::Dimensions for Dimensions { @@ -453,7 +454,7 @@ mod test { impl super::Search for Dimensions { fn semantic_search_left(&self, point: Point<usize>) -> Point<usize> { point } fn semantic_search_right(&self, point: Point<usize>) -> Point<usize> { point } - fn url_search(&self, _: Point<usize>) -> Option<String> { None } + fn url_search(&self, _: Point<usize>) -> Option<Url> { None } } /// Test case of single cell selection diff --git a/src/term/mod.rs b/src/term/mod.rs index fd31cd5e..09f45721 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -20,6 +20,7 @@ use std::time::{Duration, Instant}; use arraydeque::ArrayDeque; use unicode_width::UnicodeWidthChar; +use glutin::MouseCursor; use font::{self, Size}; use crate::ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset, CursorStyle}; @@ -30,10 +31,9 @@ use crate::grid::{ use crate::index::{self, Point, Column, Line, IndexRange, Contains, Linear}; use crate::selection::{self, Selection, Locations}; use crate::config::{Config, VisualBellAnimation}; -use crate::MouseCursor; use copypasta::{Clipboard, Load, Store}; use crate::input::FONT_SIZE_STEP; -use crate::url::UrlParser; +use crate::url::{Url, UrlParser}; use crate::message_bar::MessageBuffer; use crate::term::color::Rgb; use crate::term::cell::{LineLength, Cell}; @@ -54,7 +54,7 @@ pub trait Search { /// 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<String>; + fn url_search(&self, _: Point<usize>) -> Option<Url>; } impl Search for Term { @@ -70,11 +70,11 @@ impl Search for Term { break; } - if iter.cur.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) { + if iter.cur().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.cur(); } point @@ -92,9 +92,9 @@ impl Search for Term { break; } - point = iter.cur; + point = iter.cur(); - if iter.cur.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) { + if point.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) { break; // cut off if on new line or hit escape char } } @@ -102,7 +102,7 @@ impl Search for Term { point } - fn url_search(&self, mut point: Point<usize>) -> Option<String> { + fn url_search(&self, mut point: Point<usize>) -> Option<Url> { // Switch first line from top to bottom point.line = self.grid.num_lines().0 - point.line - 1; @@ -1143,8 +1143,7 @@ impl Term { &self.grid } - // Mutable access for swapping out the grid during tests - #[cfg(test)] + /// Mutable access to the raw grid data structure pub fn grid_mut(&mut self) -> &mut Grid<Cell> { &mut self.grid } @@ -2034,15 +2033,15 @@ impl ansi::Handler for Term { ansi::Mode::CursorKeys => self.mode.insert(mode::TermMode::APP_CURSOR), ansi::Mode::ReportMouseClicks => { self.mode.insert(mode::TermMode::MOUSE_REPORT_CLICK); - self.set_mouse_cursor(MouseCursor::Arrow); + self.set_mouse_cursor(MouseCursor::Default); }, ansi::Mode::ReportCellMouseMotion => { self.mode.insert(mode::TermMode::MOUSE_DRAG); - self.set_mouse_cursor(MouseCursor::Arrow); + self.set_mouse_cursor(MouseCursor::Default); }, ansi::Mode::ReportAllMouseMotion => { self.mode.insert(mode::TermMode::MOUSE_MOTION); - self.set_mouse_cursor(MouseCursor::Arrow); + self.set_mouse_cursor(MouseCursor::Default); }, ansi::Mode::ReportFocusInOut => self.mode.insert(mode::TermMode::FOCUS_IN_OUT), ansi::Mode::BracketedPaste => self.mode.insert(mode::TermMode::BRACKETED_PASTE), @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use url::Url; +use url; // See https://tools.ietf.org/html/rfc3987#page-13 const URL_SEPARATOR_CHARS: [char; 10] = ['<', '>', '"', ' ', '{', '}', '|', '\\', '^', '`']; @@ -21,21 +21,35 @@ const URL_SCHEMES: [&str; 8] = [ "http", "https", "mailto", "news", "file", "git", "ssh", "ftp", ]; -// Parser for streaming inside-out detection of URLs. +/// URL text and origin of the original click position. +#[derive(Debug, PartialEq)] +pub struct Url { + pub text: String, + pub origin: 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, c: char) -> bool { - self.advance(c, 0) + if self.advance(c, 0) { + true + } else { + self.origin += 1; + false + } } /// Advance the parser one character to the right. @@ -44,7 +58,7 @@ impl UrlParser { } /// Returns the URL if the parser has found any. - pub fn url(mut self) -> Option<String> { + 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("://") { @@ -57,6 +71,7 @@ impl UrlParser { match c { 'a'...'z' | 'A'...'Z' => (), _ => { + self.origin = self.origin.saturating_sub(byte_index + 1); self.state = self.state.split_off(byte_index + c.len_utf8()); break; } @@ -97,10 +112,13 @@ impl UrlParser { } // Check if string is valid url - match Url::parse(&self.state) { + match url::Url::parse(&self.state) { Ok(url) => { - if URL_SCHEMES.contains(&url.scheme()) { - Some(self.state) + if URL_SCHEMES.contains(&url.scheme()) && self.origin > 0 { + Some(Url { + text: self.state, + origin: self.origin - 1, + }) } else { None } @@ -155,12 +173,10 @@ mod tests { term } - fn url_test(input: &str, expected: &str, click_index: usize) { + fn url_test(input: &str, expected: &str) { let term = url_create_term(input); - - let url = term.url_search(Point::new(0, Column(click_index))); - - assert_eq!(url, Some(expected.into())); + let url = term.url_search(Point::new(0, Column(15))); + assert_eq!(url.map(|u| u.text), Some(expected.into())); } #[test] @@ -168,72 +184,87 @@ mod tests { 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)); } #[test] fn url_matching_chars() { - url_test("(https://example.org/test(ing))", "https://example.org/test(ing)", 5); - url_test("https://example.org/test(ing)", "https://example.org/test(ing)", 5); - url_test("((https://example.org))", "https://example.org", 5); - url_test(")https://example.org(", "https://example.org", 5); - url_test("https://example.org)", "https://example.org", 5); - url_test("https://example.org(", "https://example.org", 5); - url_test("(https://one.org/)(https://two.org/)", "https://one.org", 5); - - url_test("https://[2001:db8:a0b:12f0::1]:80", "https://[2001:db8:a0b:12f0::1]:80", 5); - url_test("([(https://example.org/test(ing))])", "https://example.org/test(ing)", 5); - url_test("https://example.org/]()", "https://example.org", 5); - url_test("[https://example.org]", "https://example.org", 5); - - url_test("'https://example.org/test'ing'''", "https://example.org/test'ing'", 5); - url_test("https://example.org/test'ing'", "https://example.org/test'ing'", 5); - url_test("'https://example.org'", "https://example.org", 5); - url_test("'https://example.org", "https://example.org", 5); - url_test("https://example.org'", "https://example.org", 5); + 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"); } #[test] fn url_detect_end() { - url_test("https://example.org/test\u{00}ing", "https://example.org/test", 5); - url_test("https://example.org/test\u{1F}ing", "https://example.org/test", 5); - url_test("https://example.org/test\u{7F}ing", "https://example.org/test", 5); - url_test("https://example.org/test\u{9F}ing", "https://example.org/test", 5); - url_test("https://example.org/test\ting", "https://example.org/test", 5); - url_test("https://example.org/test ing", "https://example.org/test", 5); + 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", 5); - url_test("https://example.org.,;:)'!/?", "https://example.org", 5); - url_test("https://example.org'.", "https://example.org", 5); + 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"); } #[test] fn url_remove_start_chars() { - url_test("complicated:https://example.org", "https://example.org", 15); - url_test("test.https://example.org", "https://example.org", 10); - url_test(",https://example.org", "https://example.org", 5); - url_test("\u{2502}https://example.org", "https://example.org", 5); + 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", 5); - url_test("https://example.org/\u{2008A}", "https://example.org/\u{2008A}", 5); - url_test("https://example.org/\u{f17c}", "https://example.org/\u{f17c}", 5); - url_test("https://üñîçøðé.com/ä", "https://üñîçøðé.com/ä", 5); + 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/ä"); } #[test] fn url_schemes() { - url_test("mailto://example.org", "mailto://example.org", 5); - url_test("https://example.org", "https://example.org", 5); - url_test("http://example.org", "http://example.org", 5); - url_test("news://example.org", "news://example.org", 5); - url_test("file://example.org", "file://example.org", 5); - url_test("git://example.org", "git://example.org", 5); - url_test("ssh://example.org", "ssh://example.org", 5); - url_test("ftp://example.org", "ftp://example.org", 5); + 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"); } } diff --git a/src/window.rs b/src/window.rs index 741ca38e..2aaa760b 100644 --- a/src/window.rs +++ b/src/window.rs @@ -21,14 +21,12 @@ use glutin::Icon; use image::ImageFormat; use glutin::{ self, ContextBuilder, ControlFlow, Event, EventsLoop, - MouseCursor as GlutinMouseCursor, WindowBuilder, - ContextTrait, + MouseCursor, WindowBuilder, ContextTrait }; use glutin::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; use crate::cli::Options; use crate::config::{Decorations, WindowConfig}; -use crate::MouseCursor; #[cfg(windows)] static WINDOW_ICON: &'static [u8] = include_bytes!("../assets/windows/alacritty.ico"); @@ -142,7 +140,7 @@ impl Window { window.show(); // Text cursor - window.set_cursor(GlutinMouseCursor::Text); + window.set_cursor(MouseCursor::Text); // Make the context current so OpenGL operations can run unsafe { @@ -237,10 +235,7 @@ impl Window { #[inline] pub fn set_mouse_cursor(&self, cursor: MouseCursor) { - self.window.set_cursor(match cursor { - MouseCursor::Arrow => GlutinMouseCursor::Default, - MouseCursor::Text => GlutinMouseCursor::Text, - }); + self.window.set_cursor(cursor); } /// Set mouse cursor visible |