diff options
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!(), + } + } +} |