diff options
-rw-r--r-- | CHANGELOG.md | 5 | ||||
-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 |
12 files changed, 314 insertions, 148 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index b3a5fbff..daf1fa83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - New configuration field `window.position` allows specifying the starting position - Added the ability to change the selection color - Text will reflow instead of truncating when resizing Alacritty +- Underline text and change cursor when hovering over URLs with required modifiers pressed + +### Changed + +- Clicking on non-alphabetical characters in front of URLs will no longer open them ### Fixed 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 |