aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md5
-rw-r--r--src/ansi.rs2
-rw-r--r--src/event.rs11
-rw-r--r--src/grid/mod.rs60
-rw-r--r--src/grid/tests.rs12
-rw-r--r--src/index.rs77
-rw-r--r--src/input.rs108
-rw-r--r--src/lib.rs7
-rw-r--r--src/selection.rs3
-rw-r--r--src/term/mod.rs25
-rw-r--r--src/url.rs141
-rw-r--r--src/window.rs11
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();
diff --git a/src/lib.rs b/src/lib.rs
index 8cf0f026..64d554ca 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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),
diff --git a/src/url.rs b/src/url.rs
index 836c36ba..a493dd37 100644
--- a/src/url.rs
+++ b/src/url.rs
@@ -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