aboutsummaryrefslogtreecommitdiff
path: root/src/input.rs
diff options
context:
space:
mode:
authorChristian Duerr <chrisduerr@users.noreply.github.com>2018-10-22 19:39:26 +0000
committerGitHub <noreply@github.com>2018-10-22 19:39:26 +0000
commit8ee0d2b5b26faacb5f2663bae1fc056ac5ee26bf (patch)
tree093022ebdb474b891c8946ae42972dc23f73fefe /src/input.rs
parent4380d0864b1098909bdcfec132b866c34924517e (diff)
downloadalacritty-8ee0d2b5b26faacb5f2663bae1fc056ac5ee26bf.tar.gz
alacritty-8ee0d2b5b26faacb5f2663bae1fc056ac5ee26bf.zip
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. It is not always desired for URLs to open automatically when clicking on them. To resolve this a new `modifiers` field has been introduced to the config, which allows specifying which keyboard modifiers need to be held down to launch URLs in the specified launcher. Some tests have been added to make sure that the edge-cases of the URL parsing are protected against future regressions. To make testing easier the parsing method has been moved into the `SemanticSearch` trait. The name of the trait has also been changed to just `Search` and it has been moved to `src/term/mod.rs` to fit the additional functionality. This fixes #113.
Diffstat (limited to 'src/input.rs')
-rw-r--r--src/input.rs94
1 files changed, 71 insertions, 23 deletions
diff --git a/src/input.rs b/src/input.rs
index ed9aa7fc..49a9101e 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -63,6 +63,7 @@ pub trait ActionContext {
fn simple_selection(&mut self, point: Point, side: Side);
fn semantic_selection(&mut self, point: Point);
fn line_selection(&mut self, point: Point);
+ fn selection_is_empty(&self) -> bool;
fn mouse_mut(&mut self) -> &mut Mouse;
fn mouse(&self) -> &Mouse;
fn mouse_coords(&self) -> Option<Point>;
@@ -74,6 +75,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
@@ -326,16 +328,25 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
let size_info = self.ctx.size_info();
let point = size_info.pixels_to_coords(x, y);
+ let cell_side = self.get_mouse_side();
+ let prev_side = mem::replace(&mut self.ctx.mouse_mut().cell_side, cell_side);
let prev_line = mem::replace(&mut self.ctx.mouse_mut().line, point.line);
let prev_col = mem::replace(&mut self.ctx.mouse_mut().column, point.col);
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
+ || prev_col != self.ctx.mouse().column
+ || prev_side != cell_side
+ {
+ self.ctx.mouse_mut().block_url_launcher = true;
+ }
+
if self.ctx.mouse().left_button_state == ElementState::Pressed &&
( modifiers.shift || !self.ctx.terminal_mode().intersects(report_mode))
{
- let cell_side = self.get_mouse_side();
self.ctx.update_selection(Point {
line: point.line,
col: point.col
@@ -357,6 +368,26 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
}
}
+ fn get_mouse_side(&self) -> Side {
+ let size_info = self.ctx.size_info();
+ let x = self.ctx.mouse().x;
+
+ let cell_x = x.saturating_sub(size_info.padding_x as usize) % size_info.cell_width as usize;
+ let half_cell_width = (size_info.cell_width / 2.0) as usize;
+
+ let additional_padding = (size_info.width - size_info.padding_x * 2.) % size_info.cell_width;
+ let end_of_grid = size_info.width - size_info.padding_x - additional_padding;
+
+ if cell_x > half_cell_width
+ // Edge case when mouse leaves the window
+ || x as f32 >= end_of_grid
+ {
+ Side::Right
+ } else {
+ Side::Left
+ }
+ }
+
pub fn normal_mouse_report(&mut self, button: u8) {
let (line, column) = (self.ctx.mouse().line, self.ctx.mouse().column);
@@ -427,19 +458,24 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
self.ctx.mouse_mut().click_state = match self.ctx.mouse().click_state {
ClickState::Click if elapsed < self.mouse_config.double_click.threshold => {
+ self.ctx.mouse_mut().block_url_launcher = true;
self.on_mouse_double_click();
ClickState::DoubleClick
},
ClickState::DoubleClick if elapsed < self.mouse_config.triple_click.threshold => {
+ self.ctx.mouse_mut().block_url_launcher = true;
self.on_mouse_triple_click();
ClickState::TripleClick
},
_ => {
+ // Don't launch URLs if this click cleared the selection
+ self.ctx.mouse_mut().block_url_launcher = !self.ctx.selection_is_empty();
+
self.ctx.clear_selection();
// Start new empty selection
if let Some(point) = self.ctx.mouse_coords() {
- let side = self.get_mouse_side();
+ let side = self.ctx.mouse().cell_side;
self.ctx.simple_selection(point, side);
}
@@ -460,26 +496,6 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
};
}
- fn get_mouse_side(&self) -> Side {
- let size_info = self.ctx.size_info();
- let x = self.ctx.mouse().x;
-
- let cell_x = x.saturating_sub(size_info.padding_x as usize) % size_info.cell_width as usize;
- let half_cell_width = (size_info.cell_width / 2.0) as usize;
-
- let additional_padding = (size_info.width - size_info.padding_x * 2.) % size_info.cell_width;
- let end_of_grid = size_info.width - size_info.padding_x - additional_padding;
-
- if cell_x > half_cell_width
- // Edge case when mouse leaves the window
- || x as f32 >= end_of_grid
- {
- Side::Right
- } else {
- Side::Left
- }
- }
-
pub fn on_mouse_release(&mut self, button: MouseButton, modifiers: ModifiersState) {
let report_modes = TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION;
if !modifiers.shift && self.ctx.terminal_mode().intersects(report_modes)
@@ -492,6 +508,8 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
MouseButton::Other(_) => (),
};
return;
+ } else {
+ self.launch_url(modifiers);
}
if self.save_to_clipboard {
@@ -500,6 +518,27 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
self.ctx.copy_selection(ClipboardBuffer::Selection);
}
+ // Spawn URL launcher when clicking on URLs
+ fn launch_url(&self, modifiers: ModifiersState) -> Option<()> {
+ if modifiers != self.mouse_config.url.modifiers || self.ctx.mouse().block_url_launcher {
+ return None;
+ }
+
+ let point = self.ctx.mouse_coords()?;
+ let text = self.ctx.url(point.into())?;
+
+ let launcher = self.mouse_config.url.launcher.as_ref()?;
+ let mut args = launcher.args().to_vec();
+ args.push(text);
+
+ match Command::new(launcher.program()).args(&args).spawn() {
+ Ok(_) => debug!("Launched: {} {:?}", launcher.program(), args),
+ Err(_) => warn!("Unable to launch: {} {:?}", launcher.program(), args),
+ }
+
+ Some(())
+ }
+
pub fn on_mouse_wheel(&mut self, delta: MouseScrollDelta, phase: TouchPhase, modifiers: ModifiersState) {
match delta {
MouseScrollDelta::LineDelta(_columns, lines) => {
@@ -777,6 +816,10 @@ mod tests {
self.last_action = MultiClick::TripleClick;
}
+ fn selection_is_empty(&self) -> bool {
+ true
+ }
+
fn scroll(&mut self, scroll: Scroll) {
self.terminal.scroll_display(scroll);
}
@@ -795,6 +838,10 @@ mod tests {
self.mouse
}
+ fn url(&self, _: Point<usize>) -> Option<String> {
+ None
+ }
+
fn received_count(&mut self) -> &mut usize {
&mut self.received_count
}
@@ -863,11 +910,12 @@ mod tests {
threshold: Duration::from_millis(1000),
},
faux_scrollback_lines: None,
+ url: Default::default(),
},
scrolling_config: &config::Scrolling::default(),
key_bindings: &config.key_bindings()[..],
mouse_bindings: &config.mouse_bindings()[..],
- save_to_clipboard: config.selection().save_to_clipboard
+ save_to_clipboard: config.selection().save_to_clipboard,
};
if let Event::WindowEvent { event: WindowEvent::MouseInput { state, button, modifiers, .. }, .. } = $input {