From 4af4a434a3b7417803c11d80d076c92fdafe4558 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Sat, 10 Apr 2021 12:05:32 +0200 Subject: [PATCH] feat(core): progress on the core pipeline --- Cargo.lock | 12 + espanso/Cargo.toml | 1 + espanso/src/cli/worker/config.rs | 56 +++-- .../cli/worker/{ => engine}/executor/mod.rs | 0 .../{ => engine}/executor/text_injector.rs | 0 espanso/src/cli/worker/engine/match_cache.rs | 58 +++++ .../worker/{ => engine}/matcher/convert.rs | 0 .../cli/worker/{ => engine}/matcher/mod.rs | 0 .../worker/{ => engine}/matcher/rolling.rs | 2 +- espanso/src/cli/worker/engine/mod.rs | 26 ++ espanso/src/cli/worker/engine/multiplex.rs | 57 +++++ espanso/src/cli/worker/engine/render.rs | 228 ++++++++++++++++++ .../cli/worker/{ => engine}/source/detect.rs | 0 .../src/cli/worker/{ => engine}/source/mod.rs | 0 espanso/src/cli/worker/{ => engine}/ui/mod.rs | 0 .../cli/worker/{ => engine}/ui/selector.rs | 0 espanso/src/cli/worker/mod.rs | 29 ++- .../engine/dispatch/executor/text_inject.rs | 2 +- espanso/src/engine/event/inject.rs | 4 +- espanso/src/engine/event/matches.rs | 2 +- espanso/src/engine/event/mod.rs | 7 +- espanso/src/engine/event/render.rs | 33 +++ espanso/src/engine/process/default.rs | 12 +- .../src/engine/process/middleware/matcher.rs | 2 +- espanso/src/engine/process/middleware/mod.rs | 2 + .../engine/process/middleware/multiplex.rs | 51 ++++ .../src/engine/process/middleware/render.rs | 63 +++++ espanso/src/engine/process/mod.rs | 31 ++- 28 files changed, 634 insertions(+), 44 deletions(-) rename espanso/src/cli/worker/{ => engine}/executor/mod.rs (100%) rename espanso/src/cli/worker/{ => engine}/executor/text_injector.rs (100%) create mode 100644 espanso/src/cli/worker/engine/match_cache.rs rename espanso/src/cli/worker/{ => engine}/matcher/convert.rs (100%) rename espanso/src/cli/worker/{ => engine}/matcher/mod.rs (100%) rename espanso/src/cli/worker/{ => engine}/matcher/rolling.rs (99%) create mode 100644 espanso/src/cli/worker/engine/mod.rs create mode 100644 espanso/src/cli/worker/engine/multiplex.rs create mode 100644 espanso/src/cli/worker/engine/render.rs rename espanso/src/cli/worker/{ => engine}/source/detect.rs (100%) rename espanso/src/cli/worker/{ => engine}/source/mod.rs (100%) rename espanso/src/cli/worker/{ => engine}/ui/mod.rs (100%) rename espanso/src/cli/worker/{ => engine}/ui/selector.rs (100%) create mode 100644 espanso/src/engine/event/render.rs create mode 100644 espanso/src/engine/process/middleware/multiplex.rs create mode 100644 espanso/src/engine/process/middleware/render.rs diff --git a/Cargo.lock b/Cargo.lock index 4725fb4..f98aeff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -296,6 +296,7 @@ dependencies = [ "espanso-inject", "espanso-match", "espanso-path", + "espanso-render", "espanso-ui", "lazy_static", "log", @@ -324,9 +325,11 @@ version = "0.1.0" dependencies = [ "anyhow", "dunce", + "enum-as-inner", "glob", "lazy_static", "log", + "ordered-float", "regex", "serde", "serde_yaml", @@ -666,6 +669,15 @@ dependencies = [ "objc", ] +[[package]] +name = "ordered-float" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "766f840da25490628d8e63e529cd21c014f6600c6b8517add12a6fa6167a6218" +dependencies = [ + "num-traits", +] + [[package]] name = "pkg-config" version = "0.3.19" diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml index 93513a5..26f1e60 100644 --- a/espanso/Cargo.toml +++ b/espanso/Cargo.toml @@ -21,6 +21,7 @@ espanso-config = { path = "../espanso-config" } espanso-match = { path = "../espanso-match" } espanso-clipboard = { path = "../espanso-clipboard" } espanso-info = { path = "../espanso-info" } +espanso-render = { path = "../espanso-render" } espanso-path = { path = "../espanso-path" } maplit = "1.0.2" simplelog = "0.9.0" diff --git a/espanso/src/cli/worker/config.rs b/espanso/src/cli/worker/config.rs index 84b85ca..d535359 100644 --- a/espanso/src/cli/worker/config.rs +++ b/espanso/src/cli/worker/config.rs @@ -17,7 +17,10 @@ * along with espanso. If not, see . */ -use std::collections::HashSet; +use std::{ + cell::RefCell, + collections::{HashMap, HashSet}, +}; use crate::engine::process::MatchFilter; use espanso_config::{ @@ -27,6 +30,8 @@ use espanso_config::{ use espanso_info::{AppInfo, AppInfoProvider}; use std::iter::FromIterator; +use super::engine::render::ConfigProvider; + pub struct ConfigManager<'a> { config_store: &'a dyn ConfigStore, match_store: &'a dyn MatchStore, @@ -46,29 +51,16 @@ impl<'a> ConfigManager<'a> { } } - fn active(&self) -> &'a dyn Config { + pub fn active(&self) -> &'a dyn Config { let current_app = self.app_info_provider.get_info(); let info = to_app_properties(¤t_app); self.config_store.active(&info) } - fn active_match_set(&self) -> MatchSet { - let match_paths = self.active().match_paths(); - self.match_store.query(&match_paths) - } -} - -impl<'a> MatchFilter for ConfigManager<'a> { - fn filter_active(&self, matches_ids: &[i32]) -> Vec { - let ids_set: HashSet = HashSet::from_iter(matches_ids.iter().copied()); - let match_set = self.active_match_set(); - - match_set - .matches - .iter() - .filter(|m| ids_set.contains(&m.id)) - .map(|m| m.id) - .collect() + pub fn active_context(&self) -> (&'a dyn Config, MatchSet) { + let config = self.active(); + let match_paths = config.match_paths(); + (config, self.match_store.query(&match_paths)) } } @@ -80,3 +72,29 @@ fn to_app_properties(info: &AppInfo) -> AppProperties { exec: info.exec.as_deref(), } } + +impl<'a> MatchFilter for ConfigManager<'a> { + fn filter_active(&self, matches_ids: &[i32]) -> Vec { + let ids_set: HashSet = HashSet::from_iter(matches_ids.iter().copied()); + let (_, match_set) = self.active_context(); + + match_set + .matches + .iter() + .filter(|m| ids_set.contains(&m.id)) + .map(|m| m.id) + .collect() + } +} + +impl<'a> ConfigProvider<'a> for ConfigManager<'a> { + fn configs(&self) -> Vec<(&'a dyn Config, MatchSet)> { + self.config_store.configs().into_iter().map(|config| { + (config, self.match_store.query(config.match_paths())) + }).collect() + } + + fn active(&self) -> (&'a dyn Config, MatchSet) { + self.active_context() + } +} diff --git a/espanso/src/cli/worker/executor/mod.rs b/espanso/src/cli/worker/engine/executor/mod.rs similarity index 100% rename from espanso/src/cli/worker/executor/mod.rs rename to espanso/src/cli/worker/engine/executor/mod.rs diff --git a/espanso/src/cli/worker/executor/text_injector.rs b/espanso/src/cli/worker/engine/executor/text_injector.rs similarity index 100% rename from espanso/src/cli/worker/executor/text_injector.rs rename to espanso/src/cli/worker/engine/executor/text_injector.rs diff --git a/espanso/src/cli/worker/engine/match_cache.rs b/espanso/src/cli/worker/engine/match_cache.rs new file mode 100644 index 0000000..a4f4ac2 --- /dev/null +++ b/espanso/src/cli/worker/engine/match_cache.rs @@ -0,0 +1,58 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use std::{collections::HashMap, iter::FromIterator}; + +use espanso_config::{ + config::ConfigStore, + matches::{store::MatchStore, Match}, +}; + +use super::{multiplex::MatchProvider, render::MatchIterator}; + +pub struct MatchCache<'a> { + cache: HashMap, +} + +impl<'a> MatchCache<'a> { + pub fn load(config_store: &'a dyn ConfigStore, match_store: &'a dyn MatchStore) -> Self { + let mut cache = HashMap::new(); + + let paths = config_store.get_all_match_paths(); + let global_set = match_store.query(&Vec::from_iter(paths.into_iter())); + + for m in global_set.matches { + cache.insert(m.id, m); + } + + Self { cache } + } +} + +impl<'a> MatchProvider<'a> for MatchCache<'a> { + fn get(&self, match_id: i32) -> Option<&'a Match> { + self.cache.get(&match_id).map(|m| *m) + } +} + +impl<'a> MatchIterator<'a> for MatchCache<'a> { + fn matches(&self) -> Vec<&'a Match> { + self.cache.iter().map(|(_, m)| *m).collect() + } +} diff --git a/espanso/src/cli/worker/matcher/convert.rs b/espanso/src/cli/worker/engine/matcher/convert.rs similarity index 100% rename from espanso/src/cli/worker/matcher/convert.rs rename to espanso/src/cli/worker/engine/matcher/convert.rs diff --git a/espanso/src/cli/worker/matcher/mod.rs b/espanso/src/cli/worker/engine/matcher/mod.rs similarity index 100% rename from espanso/src/cli/worker/matcher/mod.rs rename to espanso/src/cli/worker/engine/matcher/mod.rs diff --git a/espanso/src/cli/worker/matcher/rolling.rs b/espanso/src/cli/worker/engine/matcher/rolling.rs similarity index 99% rename from espanso/src/cli/worker/matcher/rolling.rs rename to espanso/src/cli/worker/engine/matcher/rolling.rs index 4719308..dfe1fc9 100644 --- a/espanso/src/cli/worker/matcher/rolling.rs +++ b/espanso/src/cli/worker/engine/matcher/rolling.rs @@ -81,7 +81,7 @@ impl From> for MatchResult { Self { id: result.id, trigger: result.trigger, - vars: result.vars, + args: result.vars, } } } diff --git a/espanso/src/cli/worker/engine/mod.rs b/espanso/src/cli/worker/engine/mod.rs new file mode 100644 index 0000000..9917235 --- /dev/null +++ b/espanso/src/cli/worker/engine/mod.rs @@ -0,0 +1,26 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +pub mod ui; +pub mod source; +pub mod render; +pub mod matcher; +pub mod executor; +pub mod multiplex; +pub mod match_cache; \ No newline at end of file diff --git a/espanso/src/cli/worker/engine/multiplex.rs b/espanso/src/cli/worker/engine/multiplex.rs new file mode 100644 index 0000000..5b61ad4 --- /dev/null +++ b/espanso/src/cli/worker/engine/multiplex.rs @@ -0,0 +1,57 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use std::collections::HashMap; + +use espanso_config::matches::{Match, MatchEffect}; + +use crate::engine::{ + event::{render::RenderingRequestedEvent, Event}, + process::Multiplexer, +}; + +pub trait MatchProvider<'a> { + fn get(&self, match_id: i32) -> Option<&'a Match>; +} + +pub struct MultiplexAdapter<'a> { + provider: &'a dyn MatchProvider<'a>, +} + +impl<'a> MultiplexAdapter<'a> { + pub fn new(provider: &'a dyn MatchProvider<'a>) -> Self { + Self { provider } + } +} + +impl<'a> Multiplexer for MultiplexAdapter<'a> { + fn convert(&self, match_id: i32, trigger: String, trigger_args: HashMap) -> Option { + let m = self.provider.get(match_id)?; + + match &m.effect { + MatchEffect::Text(_) => Some(Event::RenderingRequested(RenderingRequestedEvent { + match_id, + trigger, + trigger_args, + })), + // TODO: think about rich text and image + MatchEffect::None => None, + } + } +} diff --git a/espanso/src/cli/worker/engine/render.rs b/espanso/src/cli/worker/engine/render.rs new file mode 100644 index 0000000..12296f4 --- /dev/null +++ b/espanso/src/cli/worker/engine/render.rs @@ -0,0 +1,228 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use std::{cell::RefCell, collections::HashMap, convert::TryInto}; + +use espanso_config::{ + config::Config, + matches::{store::MatchSet, Match, MatchCause, MatchEffect}, +}; +use espanso_render::{CasingStyle, Context, RenderOptions, Template, Value, Variable}; + +use crate::{cli::worker::config::ConfigManager, engine::process::{Renderer, RendererError}}; + +pub trait MatchIterator<'a> { + fn matches(&self) -> Vec<&'a Match>; +} + +pub trait ConfigProvider<'a> { + fn configs(&self) -> Vec<(&'a dyn Config, MatchSet)>; + fn active(&self) -> (&'a dyn Config, MatchSet); +} + +pub struct RendererAdapter<'a> { + renderer: &'a dyn espanso_render::Renderer, + config_provider: &'a dyn ConfigProvider<'a>, + + template_map: HashMap>, + global_vars_map: HashMap, + + context_cache: RefCell>>, +} + +impl<'a> RendererAdapter<'a> { + pub fn new( + match_iterator: &'a dyn MatchIterator, + config_provider: &'a dyn ConfigProvider<'a>, + renderer: &'a dyn espanso_render::Renderer, + ) -> Self { + let template_map = generate_template_map(match_iterator); + let global_vars_map = generate_global_vars_map(config_provider); + + Self { + renderer, + config_provider, + template_map, + global_vars_map, + context_cache: RefCell::new(HashMap::new()), + } + } +} + +// TODO: test +fn generate_template_map(match_iterator: &dyn MatchIterator) -> HashMap> { + let mut template_map = HashMap::new(); + for m in match_iterator.matches() { + let entry = convert_to_template(m); + template_map.insert(m.id, entry); + } + template_map +} + +// TODO: test +fn generate_global_vars_map(config_provider: &dyn ConfigProvider) -> HashMap { + let mut global_vars_map = HashMap::new(); + + for (_, match_set) in config_provider.configs() { + for var in match_set.global_vars.iter() { + if !global_vars_map.contains_key(&var.id) { + global_vars_map.insert(var.id, convert_var((*var).clone())); + } + } + } + + global_vars_map +} + +// TODO: test +fn generate_context<'a>( + match_set: &MatchSet, + template_map: &'a HashMap>, + global_vars_map: &'a HashMap, +) -> Context<'a> { + let mut templates = Vec::new(); + let mut global_vars = Vec::new(); + + for m in match_set.matches.iter() { + if let Some(Some(template)) = template_map.get(&m.id) { + templates.push(template); + } + } + + for var in match_set.global_vars.iter() { + if let Some(var) = global_vars_map.get(&var.id) { + global_vars.push(var); + } + } + + Context { + templates, + global_vars + } +} + +// TODO: move conversion methods to new file? + +fn convert_to_template(m: &Match) -> Option