1
0
Fork 0
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:
Christian Duerr 2018-10-02 21:52:17 +02:00
parent e01317d885
commit 9d42de6c7c
No known key found for this signature in database
GPG key ID: 85CDAE3C164BA7B4
11 changed files with 148 additions and 12 deletions

View file

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

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

View file

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

View file

@ -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: ",│`|:\"' ()[]{}<>"

View file

@ -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: ",│`|:\"' ()[]{}<>"

View file

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

View file

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

View file

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

View file

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

View file

@ -51,6 +51,7 @@ extern crate vte;
extern crate xdg;
extern crate base64;
extern crate terminfo;
extern crate url;
#[macro_use]
pub mod macros;

View file

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