diff options
author | Christian Duerr <contact@christianduerr.com> | 2021-03-01 19:50:39 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-01 19:50:39 +0000 |
commit | a954e076ca0b1ee9c1f272c2b119c67df3935fd4 (patch) | |
tree | f233f8622ac6ab33519bfcb70b480f23697198b1 /alacritty/src/config/ui_config.rs | |
parent | 772afc6a8aa9db6f89de4b23df27b571a40c9118 (diff) | |
download | r-alacritty-a954e076ca0b1ee9c1f272c2b119c67df3935fd4.tar.gz r-alacritty-a954e076ca0b1ee9c1f272c2b119c67df3935fd4.tar.bz2 r-alacritty-a954e076ca0b1ee9c1f272c2b119c67df3935fd4.zip |
Add regex terminal hints
This adds support for hints, which allow opening parts of the visual
buffer with external programs if they match a certain regex.
This is done using a visual overlay triggered on a specified key
binding, which then instructs the user which keys they need to press to
pass the text to the application.
In the future it should be possible to supply some built-in actions for
Copy/Pasting the action and using this to launch text when clicking on
it with the mouse. But the current implementation should already be
useful as-is.
Fixes #2792.
Fixes #2536.
Diffstat (limited to 'alacritty/src/config/ui_config.rs')
-rw-r--r-- | alacritty/src/config/ui_config.rs | 168 |
1 files changed, 166 insertions, 2 deletions
diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs index 2d7b5c98..6d06aa7d 100644 --- a/alacritty/src/config/ui_config.rs +++ b/alacritty/src/config/ui_config.rs @@ -1,13 +1,20 @@ +use std::cell::RefCell; use std::path::PathBuf; +use std::rc::Rc; use log::error; +use serde::de::Error as SerdeError; use serde::{Deserialize, Deserializer}; +use unicode_width::UnicodeWidthChar; use alacritty_config_derive::ConfigDeserialize; -use alacritty_terminal::config::{Percentage, LOG_TARGET_CONFIG}; +use alacritty_terminal::config::{Percentage, Program, LOG_TARGET_CONFIG}; +use alacritty_terminal::term::search::RegexSearch; use crate::config::bell::BellConfig; -use crate::config::bindings::{self, Binding, KeyBinding, MouseBinding}; +use crate::config::bindings::{ + self, Action, Binding, BindingMode, Key, KeyBinding, ModsWrapper, MouseBinding, +}; use crate::config::color::Colors; use crate::config::debug::Debug; use crate::config::font::Font; @@ -46,6 +53,9 @@ pub struct UiConfig { #[config(skip)] pub config_paths: Vec<PathBuf>, + /// Regex hints for interacting with terminal content. + pub hints: Hints, + /// Keybindings. key_bindings: KeyBindings, @@ -72,11 +82,27 @@ impl Default for UiConfig { bell: Default::default(), colors: Default::default(), draw_bold_text_with_bright_colors: Default::default(), + hints: Default::default(), } } } impl UiConfig { + /// Generate key bindings for all keyboard hints. + pub fn generate_hint_bindings(&mut self) { + for hint in self.hints.enabled.drain(..) { + let binding = KeyBinding { + trigger: hint.binding.key, + mods: hint.binding.mods.0, + mode: BindingMode::empty(), + notmode: BindingMode::empty(), + action: Action::Hint(hint), + }; + + self.key_bindings.0.push(binding); + } + } + #[inline] pub fn background_opacity(&self) -> f32 { self.background_opacity.as_f32() @@ -169,3 +195,141 @@ pub struct Delta<T: Default> { /// Vertical change. pub y: T, } + +/// Regex terminal hints. +#[derive(ConfigDeserialize, Default, Debug, PartialEq, Eq)] +pub struct Hints { + /// Characters for the hint labels. + alphabet: HintsAlphabet, + + /// All configured terminal hints. + enabled: Vec<Hint>, +} + +impl Hints { + /// Characters for the hint labels. + pub fn alphabet(&self) -> &str { + &self.alphabet.0 + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct HintsAlphabet(String); + +impl Default for HintsAlphabet { + fn default() -> Self { + Self(String::from("jfkdls;ahgurieowpq")) + } +} + +impl<'de> Deserialize<'de> for HintsAlphabet { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + + let mut character_count = 0; + for character in value.chars() { + if character.width() != Some(1) { + return Err(D::Error::custom("characters must be of width 1")); + } + character_count += 1; + } + + if character_count < 2 { + return Err(D::Error::custom("must include at last 2 characters")); + } + + Ok(Self(value)) + } +} + +/// Hint configuration. +#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Hint { + /// Command the text will be piped to. + pub command: Program, + + /// Regex for finding matches. + pub regex: LazyRegex, + + /// Binding required to search for this hint. + binding: HintBinding, +} + +/// Binding for triggering a keyboard hint. +#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)] +pub struct HintBinding { + pub key: Key, + pub mods: ModsWrapper, +} + +/// Lazy regex with interior mutability. +#[derive(Clone, Debug)] +pub struct LazyRegex(Rc<RefCell<LazyRegexVariant>>); + +impl LazyRegex { + /// Execute a function with the compiled regex DFAs as parameter. + pub fn with_compiled<T, F>(&self, f: F) -> T + where + F: Fn(&RegexSearch) -> T, + { + f(self.0.borrow_mut().compiled()) + } +} + +impl<'de> Deserialize<'de> for LazyRegex { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let regex = LazyRegexVariant::Pattern(String::deserialize(deserializer)?); + Ok(Self(Rc::new(RefCell::new(regex)))) + } +} + +/// Implement placeholder to allow derive upstream, since we never need it for this struct itself. +impl PartialEq for LazyRegex { + fn eq(&self, _other: &Self) -> bool { + false + } +} +impl Eq for LazyRegex {} + +/// Regex which is compiled on demand, to avoid expensive computations at startup. +#[derive(Clone, Debug)] +pub enum LazyRegexVariant { + Compiled(Box<RegexSearch>), + Pattern(String), +} + +impl LazyRegexVariant { + /// Get a reference to the compiled regex. + /// + /// If the regex is not already compiled, this will compile the DFAs and store them for future + /// access. + fn compiled(&mut self) -> &RegexSearch { + // Check if the regex has already been compiled. + let regex = match self { + Self::Compiled(regex_search) => return regex_search, + Self::Pattern(regex) => regex, + }; + + // Compile the regex. + let regex_search = match RegexSearch::new(®ex) { + Ok(regex_search) => regex_search, + Err(error) => { + error!("hint regex is invalid: {}", error); + RegexSearch::new("").unwrap() + }, + }; + *self = Self::Compiled(Box::new(regex_search)); + + // Return a reference to the compiled DFAs. + match self { + Self::Compiled(dfas) => dfas, + Self::Pattern(_) => unreachable!(), + } + } +} |