mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-11 13:51:01 -05:00
Switch to rfind_url for URL detection
This switches to rfind_url for detecting URLs inside the grid. Instead of expanding at the cursor position, the complete terminal is searched from the bottom until the visible region is left with no active URL. Instead of having the field `cur` publicly accessibly on the `DisplayIterator`, there are the two methods `DisplayIterator::point` and `DisplayIterator::cell` for accessing the current element of the iterator now. This allows accessing the current element right after creating the iterator. Fixes #2629. Fixes #2627.
This commit is contained in:
parent
f51c7b067a
commit
9dddf649a1
9 changed files with 205 additions and 453 deletions
|
@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Override default bindings with subset terminal mode match
|
||||
- On Linux, respect fontconfig's `embeddedbitmap` configuration option
|
||||
- Selecting trailing tab with semantic expansion
|
||||
- URL parser incorrectly handling Markdown URLs and angled brackets
|
||||
|
||||
## 0.3.3
|
||||
|
||||
|
|
21
Cargo.lock
generated
21
Cargo.lock
generated
|
@ -65,6 +65,7 @@ dependencies = [
|
|||
"notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"objc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rfind_url 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -153,7 +154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.33"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -610,7 +611,7 @@ name = "failure"
|
|||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace 0.3.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"backtrace 0.3.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -874,7 +875,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "http_req"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1779,6 +1780,11 @@ dependencies = [
|
|||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfind_url"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rle-decode-fast"
|
||||
version = "1.0.1"
|
||||
|
@ -2448,7 +2454,7 @@ version = "0.19.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"android_glue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"backtrace 0.3.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"backtrace 0.3.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cocoa 0.18.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2472,7 +2478,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"embed-resource 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"http_req 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"http_req 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"named_pipe 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2577,7 +2583,7 @@ dependencies = [
|
|||
"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba"
|
||||
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
|
||||
"checksum autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "22130e92352b948e7e82a49cdb0aa94f2211761117f29e052dd397c1ac33542b"
|
||||
"checksum backtrace 0.3.33 (registry+https://github.com/rust-lang/crates.io-index)" = "88fb679bc9af8fa639198790a77f52d345fe13656c08b43afa9424c206b731c6"
|
||||
"checksum backtrace 0.3.34 (registry+https://github.com/rust-lang/crates.io-index)" = "b5164d292487f037ece34ec0de2fcede2faa162f085dd96d2385ab81b12765ba"
|
||||
"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b"
|
||||
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
|
||||
"checksum bindgen 0.33.2 (registry+https://github.com/rust-lang/crates.io-index)" = "603ed8d8392ace9581e834e26bd09799bf1e989a79bd1aedbb893e72962bdc6e"
|
||||
|
@ -2656,7 +2662,7 @@ dependencies = [
|
|||
"checksum glutin_gles2_sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "89996c30857ae1b4de4b5189abf1ea822a20a9fe9e1c93e5e7b862ff0bdd5cdf"
|
||||
"checksum glutin_glx_sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1290a5ca5e46fcfa7f66f949cc9d9194b2cb6f2ed61892c8c2b82343631dba57"
|
||||
"checksum glutin_wgl_sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f801bbc91efc22dd1c4818a47814fc72bf74d024510451b119381579bfa39021"
|
||||
"checksum http_req 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "42448f4b1a710db4889978e8c8145f6af60d729be549b1b71e036ecc96f9f26f"
|
||||
"checksum http_req 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3235907ba93aeeb84419957956ab7055f1cc4aacfabd4cd1f32f49addab3ec"
|
||||
"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
|
||||
"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
|
||||
"checksum image 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)" = "99198e595d012efccf12abf4abc08da2d97be0b0355a2b08d101347527476ba4"
|
||||
|
@ -2758,6 +2764,7 @@ dependencies = [
|
|||
"checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7"
|
||||
"checksum regex-syntax 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5485bf1523a9ed51c4964273f22f63f24e31632adb5dad134f488f86a3875c"
|
||||
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
|
||||
"checksum rfind_url 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d090798d14d8cc79d732ab0fc3c77ef3cd62c71d98e02b4f8c7076ad1c484973"
|
||||
"checksum rle-decode-fast 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac"
|
||||
"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af"
|
||||
"checksum rustc_tools_util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b725dadae9fabc488df69a287f5a99c5eaf5d10853842a8a3dfac52476f544ee"
|
||||
|
|
|
@ -32,6 +32,7 @@ terminfo = "0.6.1"
|
|||
url = "1.7.1"
|
||||
crossbeam-channel = "0.3.8"
|
||||
copypasta = { path = "../copypasta" }
|
||||
rfind_url = "0.4.0"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = "0.14.1"
|
||||
|
|
|
@ -120,7 +120,7 @@ pub enum Scroll {
|
|||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum ViewportPosition {
|
||||
enum ViewportPosition {
|
||||
Visible(Line),
|
||||
Above,
|
||||
Below,
|
||||
|
@ -141,11 +141,25 @@ impl<T: GridCell + Copy + Clone> Grid<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn visible_to_buffer(&self, point: Point) -> Point<usize> {
|
||||
Point { line: self.visible_line_to_buffer(point.line), col: point.col }
|
||||
pub fn buffer_to_visible(&self, point: impl Into<Point<usize>>) -> Point<usize> {
|
||||
let mut point = point.into();
|
||||
|
||||
match self.buffer_line_to_visible(point.line) {
|
||||
ViewportPosition::Visible(line) => point.line = line.0,
|
||||
ViewportPosition::Above => {
|
||||
point.col = Column(0);
|
||||
point.line = 0;
|
||||
},
|
||||
ViewportPosition::Below => {
|
||||
point.col = self.num_cols();
|
||||
point.line = self.num_lines().0 - 1;
|
||||
},
|
||||
}
|
||||
|
||||
pub fn buffer_line_to_visible(&self, line: usize) -> ViewportPosition {
|
||||
point
|
||||
}
|
||||
|
||||
fn buffer_line_to_visible(&self, line: usize) -> ViewportPosition {
|
||||
let offset = line.saturating_sub(self.display_offset);
|
||||
if line < self.display_offset {
|
||||
ViewportPosition::Below
|
||||
|
@ -156,7 +170,11 @@ impl<T: GridCell + Copy + Clone> Grid<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn visible_line_to_buffer(&self, line: Line) -> usize {
|
||||
pub fn visible_to_buffer(&self, point: Point) -> Point<usize> {
|
||||
Point { line: self.visible_line_to_buffer(point.line), col: point.col }
|
||||
}
|
||||
|
||||
fn visible_line_to_buffer(&self, line: Line) -> usize {
|
||||
self.line_to_offset(line) + self.display_offset
|
||||
}
|
||||
|
||||
|
@ -596,7 +614,17 @@ pub struct GridIterator<'a, T> {
|
|||
grid: &'a Grid<T>,
|
||||
|
||||
/// Current position of the iterator within the grid.
|
||||
pub cur: Point<usize>,
|
||||
cur: Point<usize>,
|
||||
}
|
||||
|
||||
impl<'a, T> GridIterator<'a, T> {
|
||||
pub fn point(&self) -> Point<usize> {
|
||||
self.cur
|
||||
}
|
||||
|
||||
pub fn cell(&self) -> &'a T {
|
||||
&self.grid[self.cur.line][self.cur.col]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Iterator for GridIterator<'a, T> {
|
||||
|
|
|
@ -109,8 +109,8 @@ fn test_iter() {
|
|||
|
||||
assert_eq!(None, iter.prev());
|
||||
assert_eq!(Some(&1), iter.next());
|
||||
assert_eq!(Column(1), iter.cur.col);
|
||||
assert_eq!(4, iter.cur.line);
|
||||
assert_eq!(Column(1), iter.point().col);
|
||||
assert_eq!(4, iter.point().line);
|
||||
|
||||
assert_eq!(Some(&2), iter.next());
|
||||
assert_eq!(Some(&3), iter.next());
|
||||
|
@ -118,12 +118,15 @@ fn test_iter() {
|
|||
|
||||
// test linewrapping
|
||||
assert_eq!(Some(&5), iter.next());
|
||||
assert_eq!(Column(0), iter.cur.col);
|
||||
assert_eq!(3, iter.cur.line);
|
||||
assert_eq!(Column(0), iter.point().col);
|
||||
assert_eq!(3, iter.point().line);
|
||||
|
||||
assert_eq!(Some(&4), iter.prev());
|
||||
assert_eq!(Column(4), iter.cur.col);
|
||||
assert_eq!(4, iter.cur.line);
|
||||
assert_eq!(Column(4), iter.point().col);
|
||||
assert_eq!(4, iter.point().line);
|
||||
|
||||
// Make sure iter.cell() returns the current iterator position
|
||||
assert_eq!(&4, iter.cell());
|
||||
|
||||
// test that iter ends at end of grid
|
||||
let mut final_iter = grid.iter_from(Point { line: 0, col: Column(4) });
|
||||
|
|
|
@ -59,6 +59,12 @@ impl From<Point<usize>> for Point<isize> {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Point<usize>> for Point<Line> {
|
||||
fn from(point: Point<usize>) -> Self {
|
||||
Point::new(Line(point.line), point.col)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Point<isize>> for Point<usize> {
|
||||
fn from(point: Point<isize>) -> Self {
|
||||
Point::new(point.line as usize, point.col)
|
||||
|
|
|
@ -18,27 +18,25 @@
|
|||
//! In order to figure that out, state about which modifier keys are pressed
|
||||
//! needs to be tracked. Additionally, we need a bit of a state machine to
|
||||
//! determine what to do when a non-modifier key is pressed.
|
||||
use crate::url::Url;
|
||||
use std::borrow::Cow;
|
||||
use std::mem;
|
||||
use std::ops::RangeInclusive;
|
||||
use std::time::Instant;
|
||||
|
||||
use glutin::{
|
||||
ElementState, KeyboardInput, ModifiersState, MouseButton, MouseCursor, MouseScrollDelta,
|
||||
TouchPhase,
|
||||
};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::ansi::{ClearMode, Handler};
|
||||
use crate::clipboard::ClipboardType;
|
||||
use crate::config::{self, Key};
|
||||
use crate::event::{ClickState, Mouse};
|
||||
use crate::grid::Scroll;
|
||||
use crate::index::{Column, Line, Linear, Point, Side};
|
||||
use crate::index::{Column, Line, Point, Side};
|
||||
use crate::message_bar::{self, Message};
|
||||
use crate::term::mode::TermMode;
|
||||
use crate::term::{Search, SizeInfo, Term};
|
||||
use crate::url::Url;
|
||||
use crate::term::{SizeInfo, Term};
|
||||
use crate::util::start_daemon;
|
||||
|
||||
pub const FONT_SIZE_STEP: f32 = 0.5;
|
||||
|
@ -392,15 +390,18 @@ enum MousePosition {
|
|||
|
||||
impl<'a, A: ActionContext + 'a> Processor<'a, A> {
|
||||
fn mouse_position(&mut self, point: Point) -> MousePosition {
|
||||
let buffer_point = self.ctx.terminal().visible_to_buffer(point);
|
||||
|
||||
// Check message bar before URL to ignore URLs in the message bar
|
||||
if let Some(message) = self.message_at_point(Some(point)) {
|
||||
if self.message_close_at_point(point, message) {
|
||||
MousePosition::MessageBarButton
|
||||
} else {
|
||||
MousePosition::MessageBar
|
||||
}
|
||||
// Check for url should be after check for message bar, since we're not looking into
|
||||
// message bar content.
|
||||
} else if let Some(url) = self.ctx.terminal().url_search(point.into()) {
|
||||
} else if let Some(url) =
|
||||
self.ctx.terminal().urls().drain(..).find(|url| url.contains(buffer_point))
|
||||
{
|
||||
MousePosition::Url(url)
|
||||
} else {
|
||||
MousePosition::Terminal
|
||||
|
@ -443,7 +444,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
|
|||
&& (!self.ctx.terminal().mode().intersects(mouse_mode) || modifiers.shift)
|
||||
&& self.mouse_config.url.launcher.is_some()
|
||||
{
|
||||
let url_bounds = self.url_bounds_at_point(url, point);
|
||||
let url_bounds = url.linear_bounds(self.ctx.terminal());
|
||||
self.ctx.terminal_mut().set_url_highlight(url_bounds);
|
||||
self.ctx.terminal_mut().set_mouse_cursor(MouseCursor::Hand);
|
||||
self.ctx.terminal_mut().dirty = true;
|
||||
|
@ -485,47 +486,6 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
|
|||
}
|
||||
}
|
||||
|
||||
fn url_bounds_at_point(&self, url: Url, point: Point) -> RangeInclusive<Linear> {
|
||||
let Url { origin, text } = url;
|
||||
let cols = self.ctx.size_info().cols().0;
|
||||
|
||||
// Calculate the URL's start position
|
||||
let lines_before = (origin + cols - point.col.0 - 1) / cols;
|
||||
let (start_col, start_line) = if lines_before > point.line.0 {
|
||||
(0, 0)
|
||||
} else {
|
||||
let start_col = (cols + point.col.0 - origin % cols) % cols;
|
||||
let start_line = point.line.0 - lines_before;
|
||||
(start_col, start_line)
|
||||
};
|
||||
|
||||
let start = Point::new(start_line, Column(start_col));
|
||||
|
||||
// Calculate the URL's highlight end position
|
||||
let len = text.width();
|
||||
let url_end_col_denormilized = point.col.0 + len - origin;
|
||||
|
||||
// This means that url ends at the last cell of the line
|
||||
let end_col = if url_end_col_denormilized % cols == 0 {
|
||||
cols - 1
|
||||
} else {
|
||||
url_end_col_denormilized % cols - 1
|
||||
};
|
||||
|
||||
let end_line = if end_col == cols - 1 {
|
||||
point.line.0 + (url_end_col_denormilized) / cols - 1
|
||||
} else {
|
||||
point.line.0 + (url_end_col_denormilized) / cols
|
||||
};
|
||||
|
||||
let end = Point::new(end_line, Column(end_col));
|
||||
|
||||
let start = Linear::from_point(Column(cols), start);
|
||||
let end = Linear::from_point(Column(cols), end);
|
||||
|
||||
RangeInclusive::new(start, end)
|
||||
}
|
||||
|
||||
fn get_mouse_side(&self) -> Side {
|
||||
let size_info = self.ctx.size_info();
|
||||
let x = self.ctx.mouse().x;
|
||||
|
@ -705,7 +665,9 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
|
|||
return None;
|
||||
}
|
||||
|
||||
let text = self.ctx.terminal().url_search(point.into())?.text;
|
||||
let point = self.ctx.terminal().visible_to_buffer(point);
|
||||
let url = self.ctx.terminal().urls().drain(..).find(|url| url.contains(point))?;
|
||||
let text = self.ctx.terminal().url_to_string(&url);
|
||||
|
||||
let launcher = self.mouse_config.url.launcher.as_ref()?;
|
||||
let mut args = launcher.args().to_vec();
|
||||
|
|
|
@ -20,17 +20,17 @@ use std::{io, mem, ptr};
|
|||
|
||||
use font::{self, Size};
|
||||
use glutin::MouseCursor;
|
||||
use rfind_url::{Parser, ParserState};
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use crate::ansi::{
|
||||
self, Attr, CharsetIndex, Color, CursorStyle, Handler, NamedColor, StandardCharset,
|
||||
self, Attr, CharsetIndex, Color, CursorStyle, Handler, NamedColor, StandardCharset, TermInfo,
|
||||
};
|
||||
use crate::clipboard::{Clipboard, ClipboardType};
|
||||
use crate::config::{Config, VisualBellAnimation};
|
||||
use crate::cursor::CursorKey;
|
||||
use crate::grid::{
|
||||
BidirectionalIterator, DisplayIter, Grid, GridCell, IndexRegion, Indexed, Scroll,
|
||||
ViewportPosition,
|
||||
};
|
||||
use crate::index::{self, Column, Contains, IndexRange, Line, Linear, Point};
|
||||
use crate::input::FONT_SIZE_STEP;
|
||||
|
@ -38,7 +38,7 @@ use crate::message_bar::MessageBuffer;
|
|||
use crate::selection::{self, Selection, SelectionRange, Span};
|
||||
use crate::term::cell::{Cell, Flags, LineLength};
|
||||
use crate::term::color::Rgb;
|
||||
use crate::url::{Url, UrlParser};
|
||||
use crate::url::Url;
|
||||
|
||||
#[cfg(windows)]
|
||||
use crate::tty;
|
||||
|
@ -58,8 +58,6 @@ pub trait Search {
|
|||
fn semantic_search_left(&self, _: Point<usize>) -> Point<usize>;
|
||||
/// Find the nearest semantic boundary _to the point_ of provided point.
|
||||
fn semantic_search_right(&self, _: Point<usize>) -> Point<usize>;
|
||||
/// Find the nearest URL boundary in both directions.
|
||||
fn url_search(&self, _: Point<usize>) -> Option<Url>;
|
||||
/// Find the nearest matching bracket.
|
||||
fn bracket_search(&self, _: Point<usize>) -> Option<Point<usize>>;
|
||||
}
|
||||
|
@ -77,11 +75,11 @@ impl Search for Term {
|
|||
break;
|
||||
}
|
||||
|
||||
if iter.cur.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) {
|
||||
if iter.point().col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) {
|
||||
break; // cut off if on new line or hit escape char
|
||||
}
|
||||
|
||||
point = iter.cur;
|
||||
point = iter.point();
|
||||
}
|
||||
|
||||
point
|
||||
|
@ -99,7 +97,7 @@ impl Search for Term {
|
|||
break;
|
||||
}
|
||||
|
||||
point = iter.cur;
|
||||
point = iter.point();
|
||||
|
||||
if point.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) {
|
||||
break; // cut off if on new line or hit escape char
|
||||
|
@ -109,40 +107,6 @@ impl Search for Term {
|
|||
point
|
||||
}
|
||||
|
||||
fn url_search(&self, mut point: Point<usize>) -> Option<Url> {
|
||||
let last_col = self.grid.num_cols() - 1;
|
||||
|
||||
// Switch first line from top to bottom
|
||||
point.line = self.grid.num_lines().0 - point.line - 1;
|
||||
|
||||
// Remove viewport scroll offset
|
||||
point.line += self.grid.display_offset();
|
||||
|
||||
// Create forwards and backwards iterators
|
||||
let mut iterf = self.grid.iter_from(point);
|
||||
point.col += 1;
|
||||
let mut iterb = self.grid.iter_from(point);
|
||||
|
||||
// Find URLs
|
||||
let mut url_parser = UrlParser::new();
|
||||
while let Some(cell) = iterb.prev() {
|
||||
if (iterb.cur.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE))
|
||||
|| url_parser.advance_left(cell)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(cell) = iterf.next() {
|
||||
if url_parser.advance_right(cell)
|
||||
|| (iterf.cur.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
url_parser.url()
|
||||
}
|
||||
|
||||
fn bracket_search(&self, point: Point<usize>) -> Option<Point<usize>> {
|
||||
let start_char = self.grid[point.line][point.col].c;
|
||||
|
||||
|
@ -175,7 +139,7 @@ impl Search for Term {
|
|||
|
||||
// Check if the bracket matches
|
||||
if c == end_char && skip_pairs == 0 {
|
||||
return Some(iter.cur);
|
||||
return Some(iter.point());
|
||||
} else if c == start_char {
|
||||
skip_pairs += 1;
|
||||
} else if c == end_char {
|
||||
|
@ -235,44 +199,27 @@ impl<'a> RenderableCellsIter<'a> {
|
|||
let cursor_offset = grid.line_to_offset(term.cursor.point.line);
|
||||
let inner = grid.display_iter();
|
||||
|
||||
let selection_range = selection.and_then(|span| {
|
||||
// Get on-screen lines of the selection's locations
|
||||
let start_line = grid.buffer_line_to_visible(span.start.line);
|
||||
let end_line = grid.buffer_line_to_visible(span.end.line);
|
||||
|
||||
// Limit block selection columns to within start/end points
|
||||
let (limit_start, limit_end) =
|
||||
if span.is_block { (span.start.col, span.end.col) } else { (Column(0), Column(0)) };
|
||||
|
||||
// Get start/end locations based on what part of selection is on screen
|
||||
let locations = match (start_line, end_line) {
|
||||
(ViewportPosition::Visible(start_line), ViewportPosition::Visible(end_line)) => {
|
||||
Some((start_line, span.start.col, end_line, span.end.col))
|
||||
},
|
||||
(ViewportPosition::Visible(start_line), ViewportPosition::Above) => {
|
||||
Some((start_line, span.start.col, Line(0), limit_end))
|
||||
},
|
||||
(ViewportPosition::Below, ViewportPosition::Visible(end_line)) => {
|
||||
Some((grid.num_lines(), limit_start, end_line, span.end.col))
|
||||
},
|
||||
(ViewportPosition::Below, ViewportPosition::Above) => {
|
||||
Some((grid.num_lines(), limit_start, Line(0), limit_end))
|
||||
},
|
||||
_ => None,
|
||||
let selection_range = selection.map(|span| {
|
||||
let (limit_start, limit_end) = if span.is_block {
|
||||
(span.end.col, span.start.col)
|
||||
} else {
|
||||
(Column(0), term.cols() - 1)
|
||||
};
|
||||
|
||||
locations.map(|(start_line, start_col, end_line, end_col)| {
|
||||
// start and end *lines* are swapped as we switch from buffer to
|
||||
// Line coordinates.
|
||||
let mut end = Point { line: start_line, col: start_col };
|
||||
let mut start = Point { line: end_line, col: end_col };
|
||||
// Get on-screen lines of the selection's locations
|
||||
let mut start = term.buffer_to_visible(span.start);
|
||||
let mut end = term.buffer_to_visible(span.end);
|
||||
|
||||
// Start and end lines are swapped as we switch from buffer to line coordinates
|
||||
if start > end {
|
||||
::std::mem::swap(&mut start, &mut end);
|
||||
mem::swap(&mut start, &mut end);
|
||||
}
|
||||
|
||||
SelectionRange::new(start, end, span.is_block)
|
||||
})
|
||||
// Trim start/end with partially visible block selection
|
||||
start.col = max(limit_start, start.col);
|
||||
end.col = min(limit_end, end.col);
|
||||
|
||||
SelectionRange::new(start.into(), end.into(), span.is_block)
|
||||
});
|
||||
|
||||
// Load cursor glyph
|
||||
|
@ -1108,10 +1055,14 @@ impl Term {
|
|||
Some(res)
|
||||
}
|
||||
|
||||
pub(crate) fn visible_to_buffer(&self, point: Point) -> Point<usize> {
|
||||
pub fn visible_to_buffer(&self, point: Point) -> Point<usize> {
|
||||
self.grid.visible_to_buffer(point)
|
||||
}
|
||||
|
||||
pub fn buffer_to_visible(&self, point: impl Into<Point<usize>>) -> Point<usize> {
|
||||
self.grid.buffer_to_visible(point)
|
||||
}
|
||||
|
||||
/// Convert the given pixel values to a grid coordinate
|
||||
///
|
||||
/// The mouse coordinates are expected to be relative to the top left. The
|
||||
|
@ -1368,9 +1319,79 @@ impl Term {
|
|||
pub fn clipboard(&mut self) -> &mut Clipboard {
|
||||
&mut self.clipboard
|
||||
}
|
||||
|
||||
pub fn urls(&self) -> Vec<Url> {
|
||||
let display_offset = self.grid.display_offset();
|
||||
let num_cols = self.grid.num_cols().0;
|
||||
let last_col = Column(num_cols - 1);
|
||||
let last_line = self.grid.num_lines() - 1;
|
||||
|
||||
let grid_end_point = Point::new(0, last_col);
|
||||
let mut iter = self.grid.iter_from(grid_end_point);
|
||||
|
||||
let mut parser = Parser::new();
|
||||
let mut extra_url_len = 0;
|
||||
let mut urls = Vec::new();
|
||||
|
||||
let mut c = Some(iter.cell());
|
||||
while let Some(cell) = c {
|
||||
let point = iter.point();
|
||||
c = iter.prev();
|
||||
|
||||
// Skip double-width cell but extend URL length
|
||||
if cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) {
|
||||
extra_url_len += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
impl ansi::TermInfo for Term {
|
||||
// Interrupt URLs on line break
|
||||
if point.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) {
|
||||
extra_url_len = 0;
|
||||
parser.reset();
|
||||
}
|
||||
|
||||
match parser.advance(cell.c) {
|
||||
ParserState::Url(length) => {
|
||||
urls.push(Url::new(point, length + extra_url_len, num_cols))
|
||||
},
|
||||
ParserState::NoUrl => {
|
||||
extra_url_len = 0;
|
||||
|
||||
// Stop searching for URLs once the viewport has been left without active URL
|
||||
if point.line > last_line.0 + display_offset {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
urls
|
||||
}
|
||||
|
||||
pub fn url_to_string(&self, url: &Url) -> String {
|
||||
let mut url_text = String::new();
|
||||
|
||||
let mut iter = self.grid.iter_from(url.start);
|
||||
|
||||
let mut c = Some(iter.cell());
|
||||
while let Some(cell) = c {
|
||||
if !cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) {
|
||||
url_text.push(cell.c);
|
||||
}
|
||||
|
||||
if iter.point() == url.end {
|
||||
break;
|
||||
}
|
||||
|
||||
c = iter.next();
|
||||
}
|
||||
|
||||
url_text
|
||||
}
|
||||
}
|
||||
|
||||
impl TermInfo for Term {
|
||||
#[inline]
|
||||
fn lines(&self) -> Line {
|
||||
self.grid.num_lines()
|
||||
|
|
|
@ -1,318 +1,41 @@
|
|||
// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
use crate::ansi::TermInfo;
|
||||
use crate::index::{Column, Linear, Point};
|
||||
use crate::term::Term;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use crate::term::cell::{Cell, Flags};
|
||||
|
||||
// See https://tools.ietf.org/html/rfc3987#page-13
|
||||
const URL_SEPARATOR_CHARS: [char; 10] = ['<', '>', '"', ' ', '{', '}', '|', '\\', '^', '`'];
|
||||
const URL_DENY_END_CHARS: [char; 7] = ['.', ',', ';', ':', '?', '!', '('];
|
||||
const URL_SCHEMES: [&str; 8] =
|
||||
["http://", "https://", "mailto:", "news:", "file://", "git://", "ssh://", "ftp://"];
|
||||
|
||||
/// URL text and origin of the original click position.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
|
||||
pub struct Url {
|
||||
pub text: String,
|
||||
pub origin: usize,
|
||||
pub start: Point<usize>,
|
||||
pub end: Point<usize>,
|
||||
}
|
||||
|
||||
/// Parser for streaming inside-out detection of URLs.
|
||||
pub struct UrlParser {
|
||||
state: String,
|
||||
origin: usize,
|
||||
impl Url {
|
||||
pub fn new(start: Point<usize>, length: usize, num_cols: usize) -> Self {
|
||||
let unwrapped_end_col = start.col.0 + length - 1;
|
||||
let end_col = unwrapped_end_col % num_cols;
|
||||
let end_line = start.line - unwrapped_end_col / num_cols;
|
||||
|
||||
Url { end: Point::new(end_line, Column(end_col)), start }
|
||||
}
|
||||
|
||||
impl UrlParser {
|
||||
pub fn new() -> Self {
|
||||
UrlParser { state: String::new(), origin: 0 }
|
||||
pub fn contains(&self, point: impl Into<Point<usize>>) -> bool {
|
||||
let point = point.into();
|
||||
point.line <= self.start.line
|
||||
&& point.line >= self.end.line
|
||||
&& (point.line != self.start.line || point.col >= self.start.col)
|
||||
&& (point.line != self.end.line || point.col <= self.end.col)
|
||||
}
|
||||
|
||||
/// Advance the parser one character to the left.
|
||||
pub fn advance_left(&mut self, cell: &Cell) -> bool {
|
||||
if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
|
||||
self.origin += 1;
|
||||
return false;
|
||||
}
|
||||
pub fn linear_bounds(&self, terminal: &Term) -> RangeInclusive<Linear> {
|
||||
let mut start = self.start;
|
||||
let mut end = self.end;
|
||||
|
||||
if self.advance(cell.c, 0) {
|
||||
true
|
||||
} else {
|
||||
self.origin += 1;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance the parser one character to the right.
|
||||
pub fn advance_right(&mut self, cell: &Cell) -> bool {
|
||||
if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.advance(cell.c, self.state.len())
|
||||
}
|
||||
|
||||
/// Returns the URL if the parser has found any.
|
||||
pub fn url(mut self) -> Option<Url> {
|
||||
// Remove non-alphabetical characters before the scheme
|
||||
// https://tools.ietf.org/html/rfc3986#section-3.1
|
||||
if let Some(index) = self.state.find("://") {
|
||||
let iter =
|
||||
self.state.char_indices().rev().skip_while(|(byte_index, _)| *byte_index >= index);
|
||||
for (byte_index, c) in iter {
|
||||
match c {
|
||||
'a'..='z' | 'A'..='Z' => (),
|
||||
_ => {
|
||||
self.origin =
|
||||
self.origin.saturating_sub(byte_index + c.width().unwrap_or(1));
|
||||
self.state = self.state.split_off(byte_index + c.len_utf8());
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove non-matching parenthesis and brackets
|
||||
let mut open_parens_count: isize = 0;
|
||||
let mut open_bracks_count: isize = 0;
|
||||
for (i, c) in self.state.char_indices() {
|
||||
match c {
|
||||
'(' => open_parens_count += 1,
|
||||
')' if open_parens_count > 0 => open_parens_count -= 1,
|
||||
'[' => open_bracks_count += 1,
|
||||
']' if open_bracks_count > 0 => open_bracks_count -= 1,
|
||||
')' | ']' => {
|
||||
self.state.truncate(i);
|
||||
break;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// Track number of quotes
|
||||
let mut num_quotes = self.state.chars().filter(|&c| c == '\'').count();
|
||||
|
||||
// Remove all characters which aren't allowed at the end of a URL
|
||||
while !self.state.is_empty()
|
||||
&& (URL_DENY_END_CHARS.contains(&self.state.chars().last().unwrap())
|
||||
|| (num_quotes % 2 != 0 && self.state.ends_with('\''))
|
||||
|| self.state.ends_with("''")
|
||||
|| self.state.ends_with("()"))
|
||||
{
|
||||
if self.state.pop().unwrap() == '\'' {
|
||||
num_quotes -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if string is valid url
|
||||
if self.origin > 0 && url::Url::parse(&self.state).is_ok() {
|
||||
for scheme in &URL_SCHEMES {
|
||||
if self.state.starts_with(scheme) {
|
||||
return Some(Url { origin: self.origin - 1, text: self.state });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn advance(&mut self, c: char, pos: usize) -> bool {
|
||||
if URL_SEPARATOR_CHARS.contains(&c)
|
||||
|| (c >= '\u{00}' && c <= '\u{1F}')
|
||||
|| (c >= '\u{7F}' && c <= '\u{9F}')
|
||||
{
|
||||
true
|
||||
} else {
|
||||
self.state.insert(pos, c);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::mem;
|
||||
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use crate::clipboard::Clipboard;
|
||||
use crate::grid::Grid;
|
||||
use crate::index::{Column, Line, Point};
|
||||
use crate::message_bar::MessageBuffer;
|
||||
use crate::term::cell::{Cell, Flags};
|
||||
use crate::term::{Search, SizeInfo, Term};
|
||||
|
||||
fn url_create_term(input: &str) -> Term {
|
||||
let size = SizeInfo {
|
||||
width: 21.0,
|
||||
height: 51.0,
|
||||
cell_width: 3.0,
|
||||
cell_height: 3.0,
|
||||
padding_x: 0.0,
|
||||
padding_y: 0.0,
|
||||
dpr: 1.0,
|
||||
};
|
||||
|
||||
let width = input.chars().map(|c| if c.width() == Some(2) { 2 } else { 1 }).sum();
|
||||
let mut term =
|
||||
Term::new(&Default::default(), size, MessageBuffer::new(), Clipboard::new_nop());
|
||||
let mut grid: Grid<Cell> = Grid::new(Line(1), Column(width), 0, Cell::default());
|
||||
|
||||
let mut i = 0;
|
||||
for c in input.chars() {
|
||||
grid[Line(0)][Column(i)].c = c;
|
||||
|
||||
if c.width() == Some(2) {
|
||||
grid[Line(0)][Column(i)].flags.insert(Flags::WIDE_CHAR);
|
||||
grid[Line(0)][Column(i + 1)].flags.insert(Flags::WIDE_CHAR_SPACER);
|
||||
grid[Line(0)][Column(i + 1)].c = ' ';
|
||||
i += 1;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
mem::swap(term.grid_mut(), &mut grid);
|
||||
|
||||
term
|
||||
}
|
||||
|
||||
fn url_test(input: &str, expected: &str) {
|
||||
let term = url_create_term(input);
|
||||
let url = term.url_search(Point::new(0, Column(15)));
|
||||
assert_eq!(url.map(|u| u.text), Some(expected.into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn url_skip_invalid() {
|
||||
let term = url_create_term("no url here");
|
||||
let url = term.url_search(Point::new(0, Column(4)));
|
||||
assert_eq!(url, None);
|
||||
|
||||
let term = url_create_term(" https://example.org");
|
||||
let url = term.url_search(Point::new(0, Column(0)));
|
||||
assert_eq!(url, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn url_origin() {
|
||||
let term = url_create_term(" test https://example.org ");
|
||||
let url = term.url_search(Point::new(0, Column(10)));
|
||||
assert_eq!(url.map(|u| u.origin), Some(4));
|
||||
|
||||
let term = url_create_term("https://example.org");
|
||||
let url = term.url_search(Point::new(0, Column(0)));
|
||||
assert_eq!(url.map(|u| u.origin), Some(0));
|
||||
|
||||
let term = url_create_term("https://全.org");
|
||||
let url = term.url_search(Point::new(0, Column(10)));
|
||||
assert_eq!(url.map(|u| u.origin), Some(10));
|
||||
|
||||
let term = url_create_term("https://全.org");
|
||||
let url = term.url_search(Point::new(0, Column(8)));
|
||||
assert_eq!(url.map(|u| u.origin), Some(8));
|
||||
|
||||
let term = url_create_term("https://全.org");
|
||||
let url = term.url_search(Point::new(0, Column(9)));
|
||||
assert_eq!(url.map(|u| u.origin), Some(9));
|
||||
|
||||
let term = url_create_term("test@https://example.org");
|
||||
let url = term.url_search(Point::new(0, Column(9)));
|
||||
assert_eq!(url.map(|u| u.origin), Some(4));
|
||||
|
||||
let term = url_create_term("test全https://example.org");
|
||||
let url = term.url_search(Point::new(0, Column(9)));
|
||||
assert_eq!(url.map(|u| u.origin), Some(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn url_matching_chars() {
|
||||
url_test("(https://example.org/test(ing))", "https://example.org/test(ing)");
|
||||
url_test("https://example.org/test(ing)", "https://example.org/test(ing)");
|
||||
url_test("((https://example.org))", "https://example.org");
|
||||
url_test(")https://example.org(", "https://example.org");
|
||||
url_test("https://example.org)", "https://example.org");
|
||||
url_test("https://example.org(", "https://example.org");
|
||||
url_test("(https://one.org/)(https://two.org/)", "https://one.org/");
|
||||
|
||||
url_test("https://[2001:db8:a0b:12f0::1]:80", "https://[2001:db8:a0b:12f0::1]:80");
|
||||
url_test("([(https://example.org/test(ing))])", "https://example.org/test(ing)");
|
||||
url_test("https://example.org/]()", "https://example.org/");
|
||||
url_test("[https://example.org]", "https://example.org");
|
||||
|
||||
url_test("'https://example.org/test'ing'''", "https://example.org/test'ing'");
|
||||
url_test("https://example.org/test'ing'", "https://example.org/test'ing'");
|
||||
url_test("'https://example.org'", "https://example.org");
|
||||
url_test("'https://example.org", "https://example.org");
|
||||
url_test("https://example.org'", "https://example.org");
|
||||
|
||||
url_test("(https://example.org/test全)", "https://example.org/test全");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn url_detect_end() {
|
||||
url_test("https://example.org/test\u{00}ing", "https://example.org/test");
|
||||
url_test("https://example.org/test\u{1F}ing", "https://example.org/test");
|
||||
url_test("https://example.org/test\u{7F}ing", "https://example.org/test");
|
||||
url_test("https://example.org/test\u{9F}ing", "https://example.org/test");
|
||||
url_test("https://example.org/test\ting", "https://example.org/test");
|
||||
url_test("https://example.org/test ing", "https://example.org/test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn url_remove_end_chars() {
|
||||
url_test("https://example.org/test?ing", "https://example.org/test?ing");
|
||||
url_test("https://example.org.,;:)'!/?", "https://example.org");
|
||||
url_test("https://example.org'.", "https://example.org");
|
||||
url_test("https://example.org/test/?;:", "https://example.org/test/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn url_remove_start_chars() {
|
||||
url_test("complicated:https://example.org", "https://example.org");
|
||||
url_test("test.https://example.org", "https://example.org");
|
||||
url_test(",https://example.org", "https://example.org");
|
||||
url_test("\u{2502}https://example.org", "https://example.org");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn url_unicode() {
|
||||
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");
|
||||
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");
|
||||
|
||||
assert_eq!(url_create_term("mailto.example.org").url_search(Point::default()), None);
|
||||
assert_eq!(url_create_term("https:example.org").url_search(Point::default()), None);
|
||||
assert_eq!(url_create_term("http:example.org").url_search(Point::default()), None);
|
||||
assert_eq!(url_create_term("news.example.org").url_search(Point::default()), None);
|
||||
assert_eq!(url_create_term("file:example.org").url_search(Point::default()), None);
|
||||
assert_eq!(url_create_term("git:example.org").url_search(Point::default()), None);
|
||||
assert_eq!(url_create_term("ssh:example.org").url_search(Point::default()), None);
|
||||
assert_eq!(url_create_term("ftp:example.org").url_search(Point::default()), None);
|
||||
start = terminal.buffer_to_visible(start);
|
||||
end = terminal.buffer_to_visible(end);
|
||||
|
||||
let start = Linear::from_point(terminal.cols(), start);
|
||||
let end = Linear::from_point(terminal.cols(), end);
|
||||
|
||||
RangeInclusive::new(start, end)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue