1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2024-11-25 14:05:41 -05:00

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
This commit is contained in:
Christian Duerr 2019-03-19 19:14:17 +00:00 committed by GitHub
parent eb7a1ea803
commit a672f7d553
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 312 additions and 146 deletions

View file

@ -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

View file

@ -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

View file

@ -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,
}
}
}

View file

@ -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;

View file

@ -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

View file

@ -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]

View file

@ -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();

View file

@ -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"));

View file

@ -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

View file

@ -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),

View file

@ -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://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", 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://[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'", 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");
}
#[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");
}
}

View file

@ -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