feat(core): implement propagate_case option

This commit is contained in:
Federico Terzi 2021-04-19 22:04:38 +02:00
parent 5a729f5810
commit cedb073f06
6 changed files with 119 additions and 25 deletions

View File

@ -19,11 +19,14 @@
use std::{collections::HashMap, iter::FromIterator}; 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 crate::engine::process::MatchInfoProvider;
use super::{multiplex::MatchProvider, render::MatchIterator}; use super::multiplex::MatchProvider;
pub struct MatchCache<'a> { pub struct MatchCache<'a> {
cache: HashMap<i32, &'a Match>, cache: HashMap<i32, &'a Match>,
@ -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> { fn matches(&self) -> Vec<&'a Match> {
self.cache.iter().map(|(_, m)| *m).collect() 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> { impl<'a> MatchInfoProvider for MatchCache<'a> {

View File

@ -53,7 +53,6 @@ impl<'a> MatchConverter<'a> {
&trigger, &trigger,
&StringMatchOptions { &StringMatchOptions {
case_insensitive: cause.propagate_case, case_insensitive: cause.propagate_case,
preserve_case_markers: cause.propagate_case,
left_word: cause.left_word, left_word: cause.left_word,
right_word: cause.right_word, right_word: cause.right_word,
}, },

View File

@ -21,14 +21,17 @@ use std::{cell::RefCell, collections::HashMap, convert::TryInto};
use espanso_config::{ use espanso_config::{
config::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 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 matches(&self) -> Vec<&'a Match>;
fn get(&self, id: i32) -> Option<&'a Match>;
} }
pub trait ConfigProvider<'a> { pub trait ConfigProvider<'a> {
@ -38,6 +41,7 @@ pub trait ConfigProvider<'a> {
pub struct RendererAdapter<'a> { pub struct RendererAdapter<'a> {
renderer: &'a dyn espanso_render::Renderer, renderer: &'a dyn espanso_render::Renderer,
match_provider: &'a dyn MatchProvider<'a>,
config_provider: &'a dyn ConfigProvider<'a>, config_provider: &'a dyn ConfigProvider<'a>,
template_map: HashMap<i32, Option<Template>>, template_map: HashMap<i32, Option<Template>>,
@ -48,16 +52,17 @@ pub struct RendererAdapter<'a> {
impl<'a> RendererAdapter<'a> { impl<'a> RendererAdapter<'a> {
pub fn new( pub fn new(
match_iterator: &'a dyn MatchIterator, match_provider: &'a dyn MatchProvider<'a>,
config_provider: &'a dyn ConfigProvider<'a>, config_provider: &'a dyn ConfigProvider<'a>,
renderer: &'a dyn espanso_render::Renderer, renderer: &'a dyn espanso_render::Renderer,
) -> Self { ) -> 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); let global_vars_map = generate_global_vars_map(config_provider);
Self { Self {
renderer, renderer,
config_provider, config_provider,
match_provider,
template_map, template_map,
global_vars_map, global_vars_map,
context_cache: RefCell::new(HashMap::new()), context_cache: RefCell::new(HashMap::new()),
@ -66,9 +71,9 @@ impl<'a> RendererAdapter<'a> {
} }
// TODO: test // TODO: test
fn generate_template_map(match_iterator: &dyn MatchIterator) -> HashMap<i32, Option<Template>> { fn generate_template_map(match_provider: &dyn MatchProvider) -> HashMap<i32, Option<Template>> {
let mut template_map = HashMap::new(); let mut template_map = HashMap::new();
for m in match_iterator.matches() { for m in match_provider.matches() {
let entry = convert_to_template(m); let entry = convert_to_template(m);
template_map.insert(m.id, entry); template_map.insert(m.id, entry);
} }
@ -113,7 +118,7 @@ fn generate_context<'a>(
Context { Context {
templates, 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> { impl<'a> Renderer<'a> for RendererAdapter<'a> {
fn render(&'a self, match_id: i32, trigger_vars: HashMap<String, String>) -> anyhow::Result<String> { fn render(
&'a self,
match_id: i32,
trigger: Option<&str>,
trigger_vars: HashMap<String, String>,
) -> anyhow::Result<String> {
if let Some(Some(template)) = self.template_map.get(&match_id) { if let Some(Some(template)) = self.template_map.get(&match_id) {
let (config, match_set) = self.config_provider.active(); let (config, match_set) = self.config_provider.active();
let mut context_cache = self.context_cache.borrow_mut(); 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 { 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 // If some trigger vars are specified, augment the template with them
@ -226,3 +244,58 @@ impl<'a> Renderer<'a> for RendererAdapter<'a> {
} }
} }
fn extract_uppercasing_style(m: &Match) -> Option<UpperCasingStyle> {
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<UpperCasingStyle>,
) -> 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
}
}

View File

@ -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(); let mut all_results = Vec::new();
if let Some(matcher_event) = convert_to_matcher_event(&event) { 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 { fn is_event_of_interest(event: &Event) -> bool {
match event { 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 // TODO: handle mouse
Event::MatchInjected => true, Event::MatchInjected => true,
_ => false, _ => false,

View File

@ -46,7 +46,7 @@ impl<'a> Middleware for RenderMiddleware<'a> {
fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
if let Event::RenderingRequested(m_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) => { Ok(body) => {
let body = if let Some(right_separator) = m_event.right_separator { let body = if let Some(right_separator) = m_event.right_separator {
format!("{}{}", body, right_separator) format!("{}{}", body, right_separator)

View File

@ -77,7 +77,7 @@ pub trait Multiplexer {
} }
pub trait Renderer<'a> { pub trait Renderer<'a> {
fn render(&'a self, match_id: i32, trigger_args: HashMap<String, String>) -> Result<String>; fn render(&'a self, match_id: i32, trigger: Option<&str>, trigger_args: HashMap<String, String>) -> Result<String>;
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]