From cedb073f06c3d59f047dd98ecfc91709524c8434 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Mon, 19 Apr 2021 22:04:38 +0200 Subject: [PATCH] feat(core): implement propagate_case option --- espanso/src/cli/worker/engine/match_cache.rs | 13 ++- .../src/cli/worker/engine/matcher/convert.rs | 1 - espanso/src/cli/worker/engine/render.rs | 105 +++++++++++++++--- .../src/engine/process/middleware/matcher.rs | 21 +++- .../src/engine/process/middleware/render.rs | 2 +- espanso/src/engine/process/mod.rs | 2 +- 6 files changed, 119 insertions(+), 25 deletions(-) diff --git a/espanso/src/cli/worker/engine/match_cache.rs b/espanso/src/cli/worker/engine/match_cache.rs index 91f51be..f7a0e66 100644 --- a/espanso/src/cli/worker/engine/match_cache.rs +++ b/espanso/src/cli/worker/engine/match_cache.rs @@ -19,11 +19,14 @@ use std::{collections::HashMap, iter::FromIterator}; -use espanso_config::{config::ConfigStore, matches::{Match, MatchEffect, store::MatchStore}}; +use espanso_config::{ + config::ConfigStore, + matches::{store::MatchStore, Match, MatchEffect}, +}; use crate::engine::process::MatchInfoProvider; -use super::{multiplex::MatchProvider, render::MatchIterator}; +use super::multiplex::MatchProvider; pub struct MatchCache<'a> { cache: HashMap, @@ -50,10 +53,14 @@ impl<'a> MatchProvider<'a> for MatchCache<'a> { } } -impl<'a> MatchIterator<'a> for MatchCache<'a> { +impl<'a> super::render::MatchProvider<'a> for MatchCache<'a> { fn matches(&self) -> Vec<&'a Match> { self.cache.iter().map(|(_, m)| *m).collect() } + + fn get(&self, id: i32) -> Option<&'a Match> { + self.cache.get(&id).map(|m| *m) + } } impl<'a> MatchInfoProvider for MatchCache<'a> { diff --git a/espanso/src/cli/worker/engine/matcher/convert.rs b/espanso/src/cli/worker/engine/matcher/convert.rs index ffe52bf..3422ff2 100644 --- a/espanso/src/cli/worker/engine/matcher/convert.rs +++ b/espanso/src/cli/worker/engine/matcher/convert.rs @@ -53,7 +53,6 @@ impl<'a> MatchConverter<'a> { &trigger, &StringMatchOptions { case_insensitive: cause.propagate_case, - preserve_case_markers: cause.propagate_case, left_word: cause.left_word, right_word: cause.right_word, }, diff --git a/espanso/src/cli/worker/engine/render.rs b/espanso/src/cli/worker/engine/render.rs index 12296f4..7a3c763 100644 --- a/espanso/src/cli/worker/engine/render.rs +++ b/espanso/src/cli/worker/engine/render.rs @@ -21,14 +21,17 @@ use std::{cell::RefCell, collections::HashMap, convert::TryInto}; use espanso_config::{ config::Config, - matches::{store::MatchSet, Match, MatchCause, MatchEffect}, + matches::{store::MatchSet, Match, MatchCause, MatchEffect, UpperCasingStyle}, }; use espanso_render::{CasingStyle, Context, RenderOptions, Template, Value, Variable}; -use crate::{cli::worker::config::ConfigManager, engine::process::{Renderer, RendererError}}; +use crate::{ + engine::process::{Renderer, RendererError}, +}; -pub trait MatchIterator<'a> { +pub trait MatchProvider<'a> { fn matches(&self) -> Vec<&'a Match>; + fn get(&self, id: i32) -> Option<&'a Match>; } pub trait ConfigProvider<'a> { @@ -38,6 +41,7 @@ pub trait ConfigProvider<'a> { pub struct RendererAdapter<'a> { renderer: &'a dyn espanso_render::Renderer, + match_provider: &'a dyn MatchProvider<'a>, config_provider: &'a dyn ConfigProvider<'a>, template_map: HashMap>, @@ -48,16 +52,17 @@ pub struct RendererAdapter<'a> { impl<'a> RendererAdapter<'a> { pub fn new( - match_iterator: &'a dyn MatchIterator, + match_provider: &'a dyn MatchProvider<'a>, config_provider: &'a dyn ConfigProvider<'a>, renderer: &'a dyn espanso_render::Renderer, ) -> Self { - let template_map = generate_template_map(match_iterator); + let template_map = generate_template_map(match_provider); let global_vars_map = generate_global_vars_map(config_provider); Self { renderer, config_provider, + match_provider, template_map, global_vars_map, context_cache: RefCell::new(HashMap::new()), @@ -66,9 +71,9 @@ impl<'a> RendererAdapter<'a> { } // TODO: test -fn generate_template_map(match_iterator: &dyn MatchIterator) -> HashMap> { +fn generate_template_map(match_provider: &dyn MatchProvider) -> HashMap> { let mut template_map = HashMap::new(); - for m in match_iterator.matches() { + for m in match_provider.matches() { let entry = convert_to_template(m); template_map.insert(m.id, entry); } @@ -113,7 +118,7 @@ fn generate_context<'a>( Context { templates, - global_vars + global_vars, } } @@ -180,16 +185,29 @@ fn convert_value(value: espanso_config::matches::Value) -> espanso_render::Value } impl<'a> Renderer<'a> for RendererAdapter<'a> { - fn render(&'a self, match_id: i32, trigger_vars: HashMap) -> anyhow::Result { + fn render( + &'a self, + match_id: i32, + trigger: Option<&str>, + trigger_vars: HashMap, + ) -> anyhow::Result { if let Some(Some(template)) = self.template_map.get(&match_id) { let (config, match_set) = self.config_provider.active(); let mut context_cache = self.context_cache.borrow_mut(); - let context = context_cache.entry(config.id()).or_insert(generate_context(&match_set, &self.template_map, &self.global_vars_map)); + let context = context_cache + .entry(config.id()) + .or_insert_with(|| generate_context(&match_set, &self.template_map, &self.global_vars_map)); + + let raw_match = self.match_provider.get(match_id); + let preferred_uppercasing_style = raw_match.and_then(extract_uppercasing_style); - // TODO: calculate the casing style instead of hardcoding it let options = RenderOptions { - casing_style: CasingStyle::None, + casing_style: if let Some(trigger) = trigger { + calculate_casing_style(trigger, preferred_uppercasing_style) + } else { + CasingStyle::None + }, }; // If some trigger vars are specified, augment the template with them @@ -199,9 +217,9 @@ impl<'a> Renderer<'a> for RendererAdapter<'a> { let mut params = espanso_render::Params::new(); params.insert("echo".to_string(), Value::String(value)); augmented.vars.push(Variable { - name, - var_type: "echo".to_string(), - params, + name, + var_type: "echo".to_string(), + params, }) } Some(augmented) @@ -218,7 +236,7 @@ impl<'a> Renderer<'a> for RendererAdapter<'a> { match self.renderer.render(template, context, &options) { espanso_render::RenderResult::Success(body) => Ok(body), espanso_render::RenderResult::Aborted => Err(RendererError::Aborted.into()), - espanso_render::RenderResult::Error(err) => Err(RendererError::RenderingError(err).into()), + espanso_render::RenderResult::Error(err) => Err(RendererError::RenderingError(err).into()), } } else { Err(RendererError::NotFound.into()) @@ -226,3 +244,58 @@ impl<'a> Renderer<'a> for RendererAdapter<'a> { } } +fn extract_uppercasing_style(m: &Match) -> Option { + if let MatchCause::Trigger(cause) = &m.cause { + Some(cause.uppercase_style.clone()) + } else { + None + } +} + +// TODO: test +fn calculate_casing_style( + trigger: &str, + uppercasing_style: Option, +) -> CasingStyle { + let mut first_alphabetic = None; + let mut second_alphabetic = None; + + for c in trigger.chars() { + if c.is_alphabetic() { + if first_alphabetic.is_none() { + first_alphabetic = Some(c); + } else if second_alphabetic.is_none() { + second_alphabetic = Some(c); + } else { + break; + } + } + } + + if let Some(first) = first_alphabetic { + if let Some(second) = second_alphabetic { + if first.is_uppercase() { + if second.is_uppercase() { + CasingStyle::Uppercase + } else { + match uppercasing_style { + Some(UpperCasingStyle::CapitalizeWords) => CasingStyle::CapitalizeWords, + _ => CasingStyle::Capitalize, + } + } + } else { + CasingStyle::None + } + } else if first.is_uppercase() { + match uppercasing_style { + Some(UpperCasingStyle::Capitalize) => CasingStyle::Capitalize, + Some(UpperCasingStyle::CapitalizeWords) => CasingStyle::CapitalizeWords, + _ => CasingStyle::Uppercase, + } + } else { + CasingStyle::None + } + } else { + CasingStyle::None + } +} diff --git a/espanso/src/engine/process/middleware/matcher.rs b/espanso/src/engine/process/middleware/matcher.rs index aca712f..3c02020 100644 --- a/espanso/src/engine/process/middleware/matcher.rs +++ b/espanso/src/engine/process/middleware/matcher.rs @@ -78,8 +78,6 @@ impl<'a, State> Middleware for MatcherMiddleware<'a, State> { } } - // TODO: test if the matcher detects a word match when the states are cleared (probably not :( ) - let mut all_results = Vec::new(); if let Some(matcher_event) = convert_to_matcher_event(&event) { @@ -121,7 +119,24 @@ impl<'a, State> Middleware for MatcherMiddleware<'a, State> { fn is_event_of_interest(event: &Event) -> bool { match event { - Event::Keyboard(keyboard_event) if keyboard_event.status == Status::Pressed => true, + Event::Keyboard(keyboard_event) => { + if keyboard_event.status != Status::Pressed { + // Skip non-press events + false + } else { + match keyboard_event.key { + // Skip modifier keys + Key::Alt => false, + Key::Shift => false, + Key::CapsLock => false, + Key::Meta => false, + Key::NumLock => false, + Key::Control => false, + + _ => true + } + } + }, // TODO: handle mouse Event::MatchInjected => true, _ => false, diff --git a/espanso/src/engine/process/middleware/render.rs b/espanso/src/engine/process/middleware/render.rs index 1040ea4..c57d7e9 100644 --- a/espanso/src/engine/process/middleware/render.rs +++ b/espanso/src/engine/process/middleware/render.rs @@ -46,7 +46,7 @@ impl<'a> Middleware for RenderMiddleware<'a> { fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { if let Event::RenderingRequested(m_event) = event { - match self.renderer.render(m_event.match_id, m_event.trigger_args) { + match self.renderer.render(m_event.match_id, m_event.trigger.as_deref(), m_event.trigger_args) { Ok(body) => { let body = if let Some(right_separator) = m_event.right_separator { format!("{}{}", body, right_separator) diff --git a/espanso/src/engine/process/mod.rs b/espanso/src/engine/process/mod.rs index 0b91b2a..9da7e7b 100644 --- a/espanso/src/engine/process/mod.rs +++ b/espanso/src/engine/process/mod.rs @@ -77,7 +77,7 @@ pub trait Multiplexer { } pub trait Renderer<'a> { - fn render(&'a self, match_id: i32, trigger_args: HashMap) -> Result; + fn render(&'a self, match_id: i32, trigger: Option<&str>, trigger_args: HashMap) -> Result; } #[derive(Error, Debug)]