summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Duerr <contact@christianduerr.com>2021-04-03 23:52:44 +0000
committerGitHub <noreply@github.com>2021-04-03 23:52:44 +0000
commitcbcc12944006603131119b73c2ad72ebccf4562d (patch)
tree3f323703c8ce47d1bf89c2e13f2c129b3e750683
parent531e494cf9a90de91dbbb84cb6cfc3158b78f1f2 (diff)
downloadalacritty-cbcc12944006603131119b73c2ad72ebccf4562d.tar.gz
alacritty-cbcc12944006603131119b73c2ad72ebccf4562d.zip
Add copy/paste/select hint actions
This adds some built-in actions for handling hint selections without having to spawn external applications. The new actions are `Copy`, `Select` and `Paste`.
-rw-r--r--alacritty.yml10
-rw-r--r--alacritty/src/config/ui_config.rs30
-rw-r--r--alacritty/src/display/hint.rs40
-rw-r--r--alacritty/src/event.rs77
-rw-r--r--alacritty/src/input.rs36
-rw-r--r--alacritty_terminal/src/event.rs2
-rw-r--r--alacritty_terminal/src/event_loop.rs2
-rw-r--r--docs/features.md3
8 files changed, 135 insertions, 65 deletions
diff --git a/alacritty.yml b/alacritty.yml
index d049e665..3df946a9 100644
--- a/alacritty.yml
+++ b/alacritty.yml
@@ -478,9 +478,19 @@
# List with all available hints
#
+ # Each hint takes a `regex`, `binding` and either a `command` or an `action`.
+ #
# The fields `command`, `binding.key` and `binding.mods` accept the same
# values as they do in the `key_bindings` section.
#
+ # Values for `action`:
+ # - Copy
+ # Copy the hint's text to the clipboard.
+ # - Paste
+ # Paste the hint's text to the terminal or search.
+ # - Select
+ # Select the hint's text.
+ #
# Example
#
# enabled:
diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs
index 6d06aa7d..3cd2ad88 100644
--- a/alacritty/src/config/ui_config.rs
+++ b/alacritty/src/config/ui_config.rs
@@ -4,7 +4,7 @@ use std::rc::Rc;
use log::error;
use serde::de::Error as SerdeError;
-use serde::{Deserialize, Deserializer};
+use serde::{self, Deserialize, Deserializer};
use unicode_width::UnicodeWidthChar;
use alacritty_config_derive::ConfigDeserialize;
@@ -245,11 +245,35 @@ impl<'de> Deserialize<'de> for HintsAlphabet {
}
}
+/// Built-in actions for hint mode.
+#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)]
+pub enum HintInternalAction {
+ /// Copy the text to the clipboard.
+ Copy,
+ /// Write the text to the PTY/search.
+ Paste,
+ /// Select the text matching the hint.
+ Select,
+}
+
+/// Actions for hint bindings.
+#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
+pub enum HintAction {
+ /// Built-in hint action.
+ #[serde(rename = "action")]
+ Action(HintInternalAction),
+
+ /// Command the text will be piped to.
+ #[serde(rename = "command")]
+ Command(Program),
+}
+
/// Hint configuration.
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Hint {
- /// Command the text will be piped to.
- pub command: Program,
+ /// Action executed when this hint is triggered.
+ #[serde(flatten)]
+ pub action: HintAction,
/// Regex for finding matches.
pub regex: LazyRegex,
diff --git a/alacritty/src/display/hint.rs b/alacritty/src/display/hint.rs
index fe107139..2a5e9c65 100644
--- a/alacritty/src/display/hint.rs
+++ b/alacritty/src/display/hint.rs
@@ -1,7 +1,7 @@
+use alacritty_terminal::term::search::Match;
use alacritty_terminal::term::Term;
-use crate::config::ui_config::Hint;
-use crate::daemon::start_daemon;
+use crate::config::ui_config::{Hint, HintAction};
use crate::display::content::RegexMatches;
/// Percentage of characters in the hints alphabet used for the last character.
@@ -88,7 +88,7 @@ impl HintState {
}
/// Handle keyboard input during hint selection.
- pub fn keyboard_input<T>(&mut self, term: &Term<T>, c: char) {
+ pub fn keyboard_input<T>(&mut self, term: &Term<T>, c: char) -> Option<HintMatch> {
match c {
// Use backspace to remove the last character pressed.
'\x08' | '\x1f' => {
@@ -102,34 +102,25 @@ impl HintState {
// Update the visible matches.
self.update_matches(term);
- let hint = match self.hint.as_ref() {
- Some(hint) => hint,
- None => return,
- };
+ let hint = self.hint.as_ref()?;
// Find the last label starting with the input character.
let mut labels = self.labels.iter().enumerate().rev();
- let (index, label) = match labels.find(|(_, label)| !label.is_empty() && label[0] == c) {
- Some(last) => last,
- None => return,
- };
+ let (index, label) = labels.find(|(_, label)| !label.is_empty() && label[0] == c)?;
// Check if the selected label is fully matched.
if label.len() == 1 {
- // Get text for the hint's regex match.
- let hint_match = &self.matches[index];
- let text = term.bounds_to_string(*hint_match.start(), *hint_match.end());
-
- // Append text as last argument and launch command.
- let program = hint.command.program();
- let mut args = hint.command.args().to_vec();
- args.push(text);
- start_daemon(program, &args);
+ let bounds = self.matches[index].clone();
+ let action = hint.action.clone();
self.stop();
+
+ Some(HintMatch { action, bounds })
} else {
// Store character to preserve the selection.
self.keys.push(c);
+
+ None
}
}
@@ -152,6 +143,15 @@ impl HintState {
}
}
+/// Hint match which was selected by the user.
+pub struct HintMatch {
+ /// Action for handling the text.
+ pub action: HintAction,
+
+ /// Terminal range matching the hint.
+ pub bounds: Match,
+}
+
/// Generator for creating new hint labels.
struct HintLabels {
/// Full character set available.
diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs
index 94c40a39..341f398a 100644
--- a/alacritty/src/event.rs
+++ b/alacritty/src/event.rs
@@ -41,9 +41,10 @@ use alacritty_terminal::tty;
use crate::cli::Options as CLIOptions;
use crate::clipboard::Clipboard;
+use crate::config::ui_config::{HintAction, HintInternalAction};
use crate::config::{self, Config};
use crate::daemon::start_daemon;
-use crate::display::hint::HintState;
+use crate::display::hint::{HintMatch, HintState};
use crate::display::window::Window;
use crate::display::{Display, DisplayUpdate};
use crate::input::{self, ActionContext as _, FONT_SIZE_STEP};
@@ -178,7 +179,7 @@ pub struct ActionContext<'a, N, T> {
impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionContext<'a, N, T> {
#[inline]
- fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, val: B) {
+ fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&self, val: B) {
self.notifier.notify(val);
}
@@ -221,12 +222,17 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
*self.dirty = true;
}
+ // Copy text selection.
fn copy_selection(&mut self, ty: ClipboardType) {
- if let Some(selected) = self.terminal.selection_to_string() {
- if !selected.is_empty() {
- self.clipboard.store(ty, selected);
- }
+ let text = match self.terminal.selection_to_string().filter(|s| !s.is_empty()) {
+ Some(text) => text,
+ None => return,
+ };
+
+ if ty == ClipboardType::Selection && self.config.selection.save_to_clipboard {
+ self.clipboard.store(ClipboardType::Clipboard, text.clone());
}
+ self.clipboard.store(ty, text);
}
fn selection_is_empty(&self) -> bool {
@@ -258,11 +264,15 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
self.terminal.selection = Some(selection);
*self.dirty = true;
+
+ self.copy_selection(ClipboardType::Selection);
}
fn start_selection(&mut self, ty: SelectionType, point: Point, side: Side) {
self.terminal.selection = Some(Selection::new(ty, point, side));
*self.dirty = true;
+
+ self.copy_selection(ClipboardType::Selection);
}
fn toggle_selection(&mut self, ty: SelectionType, point: Point, side: Side) {
@@ -273,6 +283,8 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
Some(selection) if !selection.is_empty() => {
selection.ty = ty;
*self.dirty = true;
+
+ self.copy_selection(ClipboardType::Selection);
},
_ => self.start_selection(ty, point, side),
}
@@ -639,8 +651,59 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
/// Process a new character for keyboard hints.
fn hint_input(&mut self, c: char) {
- self.display.hint_state.keyboard_input(self.terminal, c);
+ let action = self.display.hint_state.keyboard_input(self.terminal, c);
*self.dirty = true;
+
+ let HintMatch { action, bounds } = match action {
+ Some(action) => action,
+ None => return,
+ };
+
+ match action {
+ // Launch an external program.
+ HintAction::Command(command) => {
+ let text = self.terminal.bounds_to_string(*bounds.start(), *bounds.end());
+ let mut args = command.args().to_vec();
+ args.push(text);
+ start_daemon(command.program(), &args);
+ },
+ // Copy the text to the clipboard.
+ HintAction::Action(HintInternalAction::Copy) => {
+ let text = self.terminal.bounds_to_string(*bounds.start(), *bounds.end());
+ self.clipboard.store(ClipboardType::Clipboard, text);
+ },
+ // Write the text to the PTY/search.
+ HintAction::Action(HintInternalAction::Paste) => {
+ let text = self.terminal.bounds_to_string(*bounds.start(), *bounds.end());
+ self.paste(&text);
+ },
+ // Select the text.
+ HintAction::Action(HintInternalAction::Select) => {
+ self.start_selection(SelectionType::Simple, *bounds.start(), Side::Left);
+ self.update_selection(*bounds.end(), Side::Right);
+ },
+ }
+ }
+
+ /// Paste a text into the terminal.
+ fn paste(&mut self, text: &str) {
+ if self.search_active() {
+ for c in text.chars() {
+ self.search_input(c);
+ }
+ } else if self.terminal().mode().contains(TermMode::BRACKETED_PASTE) {
+ self.write_to_pty(&b"\x1b[200~"[..]);
+ self.write_to_pty(text.replace("\x1b", "").into_bytes());
+ self.write_to_pty(&b"\x1b[201~"[..]);
+ } else {
+ // In non-bracketed (ie: normal) mode, terminal applications cannot distinguish
+ // pasted data from keystrokes.
+ // In theory, we should construct the keystrokes needed to produce the data we are
+ // pasting... since that's neither practical nor sensible (and probably an impossible
+ // task to solve in a general way), we'll just replace line breaks (windows and unix
+ // style) with a single carriage return (\r, which is what the Enter key produces).
+ self.write_to_pty(text.replace("\r\n", "\r").replace("\n", "\r").into_bytes());
+ }
}
/// Toggle the vi mode status.
diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs
index c5f41b6e..a66511cf 100644
--- a/alacritty/src/input.rs
+++ b/alacritty/src/input.rs
@@ -60,7 +60,7 @@ pub struct Processor<T: EventListener, A: ActionContext<T>> {
}
pub trait ActionContext<T: EventListener> {
- fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _data: B) {}
+ fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&self, _data: B) {}
fn mark_dirty(&mut self) {}
fn size_info(&self) -> SizeInfo;
fn copy_selection(&mut self, _ty: ClipboardType) {}
@@ -107,6 +107,7 @@ pub trait ActionContext<T: EventListener> {
fn toggle_vi_mode(&mut self) {}
fn hint_state(&mut self) -> &mut HintState;
fn hint_input(&mut self, _character: char) {}
+ fn paste(&mut self, _text: &str) {}
}
impl Action {
@@ -243,11 +244,11 @@ impl<T: EventListener> Execute<T> for Action {
Action::ClearSelection => ctx.clear_selection(),
Action::Paste => {
let text = ctx.clipboard_mut().load(ClipboardType::Clipboard);
- paste(ctx, &text);
+ ctx.paste(&text);
},
Action::PasteSelection => {
let text = ctx.clipboard_mut().load(ClipboardType::Selection);
- paste(ctx, &text);
+ ctx.paste(&text);
},
Action::ToggleFullscreen => ctx.window_mut().toggle_fullscreen(),
#[cfg(target_os = "macos")]
@@ -324,26 +325,6 @@ impl<T: EventListener> Execute<T> for Action {
}
}
-fn paste<T: EventListener, A: ActionContext<T>>(ctx: &mut A, contents: &str) {
- if ctx.search_active() {
- for c in contents.chars() {
- ctx.search_input(c);
- }
- } else if ctx.terminal().mode().contains(TermMode::BRACKETED_PASTE) {
- ctx.write_to_pty(&b"\x1b[200~"[..]);
- ctx.write_to_pty(contents.replace("\x1b", "").into_bytes());
- ctx.write_to_pty(&b"\x1b[201~"[..]);
- } else {
- // In non-bracketed (ie: normal) mode, terminal applications cannot distinguish
- // pasted data from keystrokes.
- // In theory, we should construct the keystrokes needed to produce the data we are
- // pasting... since that's neither practical nor sensible (and probably an impossible
- // task to solve in a general way), we'll just replace line breaks (windows and unix
- // style) with a single carriage return (\r, which is what the Enter key produces).
- ctx.write_to_pty(contents.replace("\r\n", "\r").replace("\n", "\r").into_bytes());
- }
-}
-
#[derive(Debug, Clone, PartialEq)]
pub enum MouseState {
Url(Url),
@@ -680,7 +661,6 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
}
self.ctx.scheduler_mut().unschedule(TimerId::SelectionScrolling);
- self.copy_selection();
}
pub fn mouse_wheel_input(&mut self, delta: MouseScrollDelta, phase: TouchPhase) {
@@ -965,14 +945,6 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
}
}
- /// Copy text selection.
- fn copy_selection(&mut self) {
- if self.ctx.config().selection.save_to_clipboard {
- self.ctx.copy_selection(ClipboardType::Clipboard);
- }
- self.ctx.copy_selection(ClipboardType::Selection);
- }
-
/// Trigger redraw when URL highlight changed.
#[inline]
fn update_url_state(&mut self, mouse_state: &MouseState) {
diff --git a/alacritty_terminal/src/event.rs b/alacritty_terminal/src/event.rs
index a1252570..70d16127 100644
--- a/alacritty_terminal/src/event.rs
+++ b/alacritty_terminal/src/event.rs
@@ -70,7 +70,7 @@ pub trait Notify {
/// Notify that an escape sequence should be written to the PTY.
///
/// TODO this needs to be able to error somehow.
- fn notify<B: Into<Cow<'static, [u8]>>>(&mut self, _: B);
+ fn notify<B: Into<Cow<'static, [u8]>>>(&self, _: B);
}
/// Types that are interested in when the display is resized.
diff --git a/alacritty_terminal/src/event_loop.rs b/alacritty_terminal/src/event_loop.rs
index 09c71668..c3224dfe 100644
--- a/alacritty_terminal/src/event_loop.rs
+++ b/alacritty_terminal/src/event_loop.rs
@@ -62,7 +62,7 @@ struct Writing {
pub struct Notifier(pub Sender<Msg>);
impl event::Notify for Notifier {
- fn notify<B>(&mut self, bytes: B)
+ fn notify<B>(&self, bytes: B)
where
B: Into<Cow<'static, [u8]>>,
{
diff --git a/docs/features.md b/docs/features.md
index 55f1d91a..3aa87aab 100644
--- a/docs/features.md
+++ b/docs/features.md
@@ -58,7 +58,8 @@ stays selected, allowing you to easily copy it.
Terminal hints allow easily interacting with visible text without having to
start vi mode. They consist of a regex that detects these text elements and then
-feeds them to an external application.
+either feeds them to an external application or triggers one of Alacritty's
+built-in actions.
Hints can be configured in the `hints` and `colors.hints` sections in the
Alacritty configuration file.