mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
Add option to open URLs on click
This adds the option to automatically launch URLs with a specified program when clicking on them. The config option `mouse.url_launcher` has been added to specify which program should be used to open the URL. The URL is always passed as the last parameter to the specified command. This fixes #113.
This commit is contained in:
parent
e01317d885
commit
9d42de6c7c
11 changed files with 148 additions and 12 deletions
|
@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Add `save_to_clipboard` configuration option for copying selected text to the system clipboard
|
||||
- New terminfo entry, `alacritty-direct`, that advertises 24-bit color support
|
||||
- Add support for CSI sequences Cursor Next Line (`\e[nE`) and Cursor Previous Line (`\e[nF`)
|
||||
- When `mouse.url_launcher` is set, clicking on URLs will now open them with the specified program
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
44
Cargo.lock
generated
44
Cargo.lock
generated
|
@ -37,6 +37,7 @@ dependencies = [
|
|||
"static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"terminfo 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vte 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"x11-dl 2.18.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -479,6 +480,16 @@ dependencies = [
|
|||
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.6.1"
|
||||
|
@ -610,6 +621,11 @@ dependencies = [
|
|||
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.1.0"
|
||||
|
@ -1236,6 +1252,19 @@ name = "ucd-util"
|
|||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.5"
|
||||
|
@ -1254,6 +1283,16 @@ dependencies = [
|
|||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8-ranges"
|
||||
version = "1.0.1"
|
||||
|
@ -1511,6 +1550,7 @@ dependencies = [
|
|||
"checksum gleam 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0d41e7ac812597988fdae31c9baec3c6d35cadb8ad9ab88a9bf9c0f119ed66c2"
|
||||
"checksum glutin 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "42fb2de780307bd2bedbe013bc585659a683e7c6307d0baa878aec3da9250fc1"
|
||||
"checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e"
|
||||
"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
|
||||
"checksum inotify 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40b54539f3910d6f84fbf9a643efd6e3aa6e4f001426c0329576128255994718"
|
||||
"checksum inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0"
|
||||
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
|
||||
|
@ -1528,6 +1568,7 @@ dependencies = [
|
|||
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
|
||||
"checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f"
|
||||
"checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
|
||||
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
||||
"checksum memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4b3629fe9fdbff6daa6c33b90f7c08355c1aca05a3d01fa8063b822fcf185f3b"
|
||||
"checksum memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2ffa2c986de11a9df78620c01eeaaf27d94d3ff02bf81bfcca953102dd0c6ff"
|
||||
"checksum mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "71646331f2619b1026cc302f87a2b8b648d5c6dd6937846a16cc8ce0f347f432"
|
||||
|
@ -1600,9 +1641,12 @@ dependencies = [
|
|||
"checksum tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8d6cc2de7725863c86ac71b0b9068476fec50834f055a243558ef1655bbd34cb"
|
||||
"checksum tokio-reactor 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4bfbaf9f260635649ec26b6fb4aded03887295ffcd999f6e43fd2c4758f758ea"
|
||||
"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"
|
||||
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
|
||||
"checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25"
|
||||
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
||||
"checksum url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2a321979c09843d272956e73700d12c4e7d3d92b2ee112b31548aef0d4efc5a6"
|
||||
"checksum utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd70f467df6810094968e2fce0ee1bd0e87157aceb026a8c083bcf5e25b9efe4"
|
||||
"checksum utf8parse 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a15ea87f3194a3a454c78d79082b4f5e85f6956ddb6cb86bbfbe4892aa3c0323"
|
||||
"checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d"
|
||||
|
|
|
@ -40,6 +40,7 @@ env_logger = "0.5"
|
|||
base64 = "0.9.0"
|
||||
static_assertions = "0.2.5"
|
||||
terminfo = "0.6.1"
|
||||
url = "1.7.1"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os="dragonfly", target_os="openbsd"))'.dependencies]
|
||||
x11-dl = "2"
|
||||
|
|
|
@ -259,6 +259,15 @@ mouse:
|
|||
double_click: { threshold: 300 }
|
||||
triple_click: { threshold: 300 }
|
||||
|
||||
# URL Launcher
|
||||
#
|
||||
# This program is executed when clicking on a text which is recognized as a URL.
|
||||
# The URL is always added to the command as the last parameter.
|
||||
#url_launcher:
|
||||
# program: /usr/bin/firefox
|
||||
# args:
|
||||
# - --new-tab
|
||||
|
||||
selection:
|
||||
semantic_escape_chars: ",│`|:\"' ()[]{}<>"
|
||||
|
||||
|
|
|
@ -257,6 +257,15 @@ mouse:
|
|||
double_click: { threshold: 300 }
|
||||
triple_click: { threshold: 300 }
|
||||
|
||||
# URL Launcher
|
||||
#
|
||||
# This program is executed when clicking on a text which is recognized as a URL.
|
||||
# The URL is always added to the command as the last parameter.
|
||||
#url_launcher:
|
||||
# program: /usr/bin/firefox
|
||||
# args:
|
||||
# - --new-tab
|
||||
|
||||
selection:
|
||||
semantic_escape_chars: ",│`|:\"' ()[]{}<>"
|
||||
|
||||
|
|
|
@ -88,6 +88,10 @@ pub struct Mouse {
|
|||
#[serde(default, deserialize_with = "failure_default")]
|
||||
pub triple_click: ClickHandler,
|
||||
|
||||
// Program for opening links
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
pub url_launcher: Option<CommandWrapper>,
|
||||
|
||||
// TODO: DEPRECATED
|
||||
#[serde(default)]
|
||||
pub faux_scrollback_lines: Option<usize>,
|
||||
|
@ -102,6 +106,7 @@ impl Default for Mouse {
|
|||
triple_click: ClickHandler {
|
||||
threshold: Duration::from_millis(300),
|
||||
},
|
||||
url_launcher: None,
|
||||
faux_scrollback_lines: None,
|
||||
}
|
||||
}
|
||||
|
@ -733,9 +738,9 @@ impl<'a> de::Deserialize<'a> for ActionWrapper {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
enum CommandWrapper {
|
||||
pub enum CommandWrapper {
|
||||
Just(String),
|
||||
WithArgs {
|
||||
program: String,
|
||||
|
@ -744,6 +749,22 @@ enum CommandWrapper {
|
|||
},
|
||||
}
|
||||
|
||||
impl CommandWrapper {
|
||||
pub fn program(&self) -> &String {
|
||||
match *self {
|
||||
CommandWrapper::Just(ref program) => program,
|
||||
CommandWrapper::WithArgs { ref program, .. } => program,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn args(&self) -> &[String] {
|
||||
match *self {
|
||||
CommandWrapper::Just(_) => &[],
|
||||
CommandWrapper::WithArgs { ref args, .. } => &args,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use ::term::{mode, TermMode};
|
||||
|
||||
struct ModeWrapper {
|
||||
|
|
36
src/event.rs
36
src/event.rs
|
@ -4,11 +4,13 @@ use std::fs::File;
|
|||
use std::io::Write;
|
||||
use std::sync::mpsc;
|
||||
use std::time::{Instant};
|
||||
use std::cmp::min;
|
||||
|
||||
use serde_json as json;
|
||||
use parking_lot::MutexGuard;
|
||||
use glutin::{self, ModifiersState, Event, ElementState};
|
||||
use copypasta::{Clipboard, Load, Store, Buffer as ClipboardBuffer};
|
||||
use url::Url;
|
||||
|
||||
use ansi::{Handler, ClearMode};
|
||||
use grid::Scroll;
|
||||
|
@ -19,7 +21,7 @@ use index::{Line, Column, Side, Point};
|
|||
use input::{self, MouseBinding, KeyBinding};
|
||||
use selection::Selection;
|
||||
use sync::FairMutex;
|
||||
use term::{Term, SizeInfo, TermMode};
|
||||
use term::{Term, SizeInfo, TermMode, Cell};
|
||||
use util::limit;
|
||||
use util::fmt::Red;
|
||||
use window::Window;
|
||||
|
@ -104,6 +106,36 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> {
|
|||
self.terminal.dirty = true;
|
||||
}
|
||||
|
||||
fn url(&self, mut point: Point<usize>) -> Option<String> {
|
||||
let grid = self.terminal.grid();
|
||||
point.line = grid.num_lines().0 - point.line - 1;
|
||||
|
||||
// Limit the starting point to the last line in the history
|
||||
point.line = min(point.line, grid.len() - 1);
|
||||
|
||||
// Create forwards and backwards iterators
|
||||
let iterf = grid.iter_from(point);
|
||||
point.col += 1;
|
||||
let iterb = grid.iter_from(point);
|
||||
|
||||
// Put all characters until separators into a String
|
||||
let url_char = |cell: &&Cell| {
|
||||
cell.c != ' ' && cell.c != '\'' && cell.c != '"'
|
||||
};
|
||||
|
||||
let mut buf = String::new();
|
||||
|
||||
iterb.rev().take_while(url_char).for_each(|cell| buf.push(cell.c));
|
||||
buf = buf.chars().rev().collect();
|
||||
iterf.take_while(url_char).for_each(|cell| buf.push(cell.c));
|
||||
|
||||
// Check if string is valid url
|
||||
match Url::parse(&buf) {
|
||||
Ok(_) => Some(buf),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn line_selection(&mut self, point: Point) {
|
||||
let point = self.terminal.visible_to_buffer(point);
|
||||
*self.terminal.selection_mut() = Some(Selection::lines(point));
|
||||
|
@ -196,6 +228,7 @@ pub struct Mouse {
|
|||
pub column: Column,
|
||||
pub cell_side: Side,
|
||||
pub lines_scrolled: f32,
|
||||
pub last_press_pos: (usize, usize),
|
||||
}
|
||||
|
||||
impl Default for Mouse {
|
||||
|
@ -213,6 +246,7 @@ impl Default for Mouse {
|
|||
column: Column(0),
|
||||
cell_side: Side::Left,
|
||||
lines_scrolled: 0.0,
|
||||
last_press_pos: (0, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,11 +31,6 @@ use self::storage::Storage;
|
|||
|
||||
const MIN_INIT_SIZE: usize = 1_000;
|
||||
|
||||
/// Bidirection iterator
|
||||
pub trait BidirectionalIterator: Iterator {
|
||||
fn prev(&mut self) -> Option<Self::Item>;
|
||||
}
|
||||
|
||||
/// An item in the grid along with its Line and Column.
|
||||
pub struct Indexed<T> {
|
||||
pub inner: T,
|
||||
|
@ -474,8 +469,8 @@ impl<'a, T> Iterator for GridIterator<'a, T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T> BidirectionalIterator for GridIterator<'a, T> {
|
||||
fn prev(&mut self) -> Option<Self::Item> {
|
||||
impl<'a, T> DoubleEndedIterator for GridIterator<'a, T> {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
let num_cols = self.grid.num_cols();
|
||||
|
||||
match self.cur {
|
||||
|
|
21
src/input.rs
21
src/input.rs
|
@ -73,6 +73,7 @@ pub trait ActionContext {
|
|||
fn scroll(&mut self, scroll: Scroll);
|
||||
fn clear_history(&mut self);
|
||||
fn hide_window(&mut self);
|
||||
fn url(&self, _: Point<usize>) -> Option<String>;
|
||||
}
|
||||
|
||||
/// Describes a state and action to take in that state
|
||||
|
@ -415,6 +416,8 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
|
|||
let elapsed = self.ctx.mouse().last_click_timestamp.elapsed();
|
||||
self.ctx.mouse_mut().last_click_timestamp = now;
|
||||
|
||||
self.ctx.mouse_mut().last_press_pos = (self.ctx.mouse().x, self.ctx.mouse().y);
|
||||
|
||||
self.ctx.mouse_mut().click_state = match self.ctx.mouse().click_state {
|
||||
ClickState::Click if elapsed < self.mouse_config.double_click.threshold => {
|
||||
self.on_mouse_double_click();
|
||||
|
@ -482,6 +485,24 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
|
|||
MouseButton::Other(_) => (),
|
||||
};
|
||||
return;
|
||||
} else {
|
||||
// Spawn URL launcher when clicking on URLs
|
||||
let moved = self.ctx.mouse().last_press_pos != (self.ctx.mouse().x, self.ctx.mouse().y);
|
||||
let ref url_launcher = self.mouse_config.url_launcher;
|
||||
match (self.ctx.mouse_coords(), url_launcher, moved) {
|
||||
(Some(point), Some(launcher), false) => {
|
||||
if let Some(text) = self.ctx.url(Point::new(point.line.0, point.col)) {
|
||||
let mut args = launcher.args().to_vec();
|
||||
args.push(text);
|
||||
debug!("Launching: {} {:?}", launcher.program(), args);
|
||||
Command::new(launcher.program())
|
||||
.args(&args)
|
||||
.spawn()
|
||||
.expect("url launcher error");
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
if self.save_to_clipboard {
|
||||
|
|
|
@ -51,6 +51,7 @@ extern crate vte;
|
|||
extern crate xdg;
|
||||
extern crate base64;
|
||||
extern crate terminfo;
|
||||
extern crate url;
|
||||
|
||||
#[macro_use]
|
||||
pub mod macros;
|
||||
|
|
|
@ -23,7 +23,7 @@ use unicode_width::UnicodeWidthChar;
|
|||
|
||||
use font::{self, Size};
|
||||
use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset, CursorStyle};
|
||||
use grid::{BidirectionalIterator, Grid, Indexed, IndexRegion, DisplayIter, Scroll, ViewportPosition};
|
||||
use grid::{Grid, Indexed, IndexRegion, DisplayIter, Scroll, ViewportPosition};
|
||||
use index::{self, Point, Column, Line, IndexRange, Contains, RangeInclusive, Linear};
|
||||
use selection::{self, Selection, Locations};
|
||||
use config::{Config, VisualBellAnimation};
|
||||
|
@ -44,7 +44,7 @@ impl selection::SemanticSearch for Term {
|
|||
let mut iter = self.grid.iter_from(point);
|
||||
let last_col = self.grid.num_cols() - Column(1);
|
||||
|
||||
while let Some(cell) = iter.prev() {
|
||||
while let Some(cell) = iter.next_back() {
|
||||
if self.semantic_escape_chars.contains(cell.c) {
|
||||
break;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue