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 {
+ if let MatchEffect::Text(text_effect) = &m.effect {
+ let ids = if let MatchCause::Trigger(cause) = &m.cause {
+ cause.triggers.clone()
+ } else {
+ Vec::new()
+ };
+
+ Some(Template {
+ ids,
+ body: text_effect.replace.clone(),
+ vars: convert_vars(text_effect.vars.clone()),
+ })
+ } else {
+ None
+ }
+}
+
+fn convert_vars(vars: Vec) -> Vec {
+ vars.into_iter().map(convert_var).collect()
+}
+
+fn convert_var(var: espanso_config::matches::Variable) -> espanso_render::Variable {
+ Variable {
+ name: var.name,
+ var_type: var.var_type,
+ params: convert_params(var.params),
+ }
+}
+
+fn convert_params(params: espanso_config::matches::Params) -> espanso_render::Params {
+ let mut new_params = espanso_render::Params::new();
+ for (key, value) in params {
+ new_params.insert(key, convert_value(value));
+ }
+ new_params
+}
+
+fn convert_value(value: espanso_config::matches::Value) -> espanso_render::Value {
+ match value {
+ espanso_config::matches::Value::Null => espanso_render::Value::Null,
+ espanso_config::matches::Value::Bool(v) => espanso_render::Value::Bool(v),
+ espanso_config::matches::Value::Number(n) => match n {
+ espanso_config::matches::Number::Integer(i) => {
+ espanso_render::Value::Number(espanso_render::Number::Integer(i))
+ }
+ espanso_config::matches::Number::Float(f) => {
+ espanso_render::Value::Number(espanso_render::Number::Float(f.into_inner()))
+ }
+ },
+ espanso_config::matches::Value::String(s) => espanso_render::Value::String(s),
+ espanso_config::matches::Value::Array(v) => {
+ espanso_render::Value::Array(v.into_iter().map(convert_value).collect())
+ }
+ espanso_config::matches::Value::Object(params) => {
+ espanso_render::Value::Object(convert_params(params))
+ }
+ }
+}
+
+impl<'a> Renderer<'a> for RendererAdapter<'a> {
+ fn render(&'a self, match_id: i32, 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));
+
+ // TODO: calculate the casing style instead of hardcoding it
+ let options = RenderOptions {
+ casing_style: CasingStyle::None,
+ };
+
+ // If some trigger vars are specified, augment the template with them
+ let augmented_template = if !trigger_vars.is_empty() {
+ let mut augmented = template.clone();
+ for (name, value) in trigger_vars {
+ 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,
+ })
+ }
+ Some(augmented)
+ } else {
+ None
+ };
+
+ let template = if let Some(augmented) = augmented_template.as_ref() {
+ augmented
+ } else {
+ template
+ };
+
+ 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()),
+ }
+ } else {
+ Err(RendererError::NotFound.into())
+ }
+ }
+}
+
diff --git a/espanso/src/cli/worker/source/detect.rs b/espanso/src/cli/worker/engine/source/detect.rs
similarity index 100%
rename from espanso/src/cli/worker/source/detect.rs
rename to espanso/src/cli/worker/engine/source/detect.rs
diff --git a/espanso/src/cli/worker/source/mod.rs b/espanso/src/cli/worker/engine/source/mod.rs
similarity index 100%
rename from espanso/src/cli/worker/source/mod.rs
rename to espanso/src/cli/worker/engine/source/mod.rs
diff --git a/espanso/src/cli/worker/ui/mod.rs b/espanso/src/cli/worker/engine/ui/mod.rs
similarity index 100%
rename from espanso/src/cli/worker/ui/mod.rs
rename to espanso/src/cli/worker/engine/ui/mod.rs
diff --git a/espanso/src/cli/worker/ui/selector.rs b/espanso/src/cli/worker/engine/ui/selector.rs
similarity index 100%
rename from espanso/src/cli/worker/ui/selector.rs
rename to espanso/src/cli/worker/engine/ui/selector.rs
diff --git a/espanso/src/cli/worker/mod.rs b/espanso/src/cli/worker/mod.rs
index b740cc6..2d15c1a 100644
--- a/espanso/src/cli/worker/mod.rs
+++ b/espanso/src/cli/worker/mod.rs
@@ -19,16 +19,13 @@
use funnel::Source;
use process::Matcher;
-use ui::selector::MatchSelectorAdapter;
+use engine::ui::selector::MatchSelectorAdapter;
use crate::engine::{Engine, funnel, process, dispatch};
use super::{CliModule, CliModuleArgs};
-mod ui;
+mod engine;
mod config;
-mod source;
-mod matcher;
-mod executor;
pub fn new() -> CliModule {
#[allow(clippy::needless_update)]
@@ -48,21 +45,27 @@ fn worker_main(args: CliModuleArgs) {
let app_info_provider = espanso_info::get_provider().expect("unable to initialize app info provider");
let config_manager = config::ConfigManager::new(&*config_store, &*match_store, &*app_info_provider);
- let match_converter = matcher::convert::MatchConverter::new(&*config_store, &*match_store);
+ let match_converter = engine::matcher::convert::MatchConverter::new(&*config_store, &*match_store);
+ let match_cache = engine::match_cache::MatchCache::load(&*config_store, &*match_store);
- let detect_source = source::detect::init_and_spawn().expect("failed to initialize detector module");
+ let detect_source = engine::source::detect::init_and_spawn().expect("failed to initialize detector module");
let sources: Vec<&dyn Source> = vec![&detect_source];
let funnel = funnel::default(&sources);
- let matcher = matcher::rolling::RollingMatcherAdapter::new(&match_converter.get_rolling_matches());
- let matchers: Vec<&dyn Matcher> = vec![&matcher];
+ let matcher = engine::matcher::rolling::RollingMatcherAdapter::new(&match_converter.get_rolling_matches());
+ let matchers: Vec<&dyn Matcher> = vec![&matcher];
let selector = MatchSelectorAdapter::new();
- let mut processor = process::default(&matchers, &config_manager, &selector);
+ let multiplexer = engine::multiplex::MultiplexAdapter::new(&match_cache);
- let text_injector = executor::text_injector::TextInjectorAdapter::new();
+ // TODO: add extensions
+ let renderer = espanso_render::create(Vec::new());
+ let renderer_adapter = engine::render::RendererAdapter::new(&match_cache, &config_manager, &renderer);
+
+ let mut processor = process::default(&matchers, &config_manager, &selector, &multiplexer, &renderer_adapter);
+
+ let text_injector = engine::executor::text_injector::TextInjectorAdapter::new();
let dispatcher = dispatch::default(&text_injector);
let mut engine = Engine::new(&funnel, &mut processor, &dispatcher);
engine.run();
-}
-
+}
\ No newline at end of file
diff --git a/espanso/src/engine/dispatch/executor/text_inject.rs b/espanso/src/engine/dispatch/executor/text_inject.rs
index 1966324..ca5da46 100644
--- a/espanso/src/engine/dispatch/executor/text_inject.rs
+++ b/espanso/src/engine/dispatch/executor/text_inject.rs
@@ -34,7 +34,7 @@ impl<'a> TextInjectExecutor<'a> {
impl<'a> Executor for TextInjectExecutor<'a> {
fn execute(&self, event: &Event) -> bool {
if let Event::TextInject(inject_event) = event {
- if inject_event.mode == TextInjectMode::Keys {
+ if let Some(TextInjectMode::Keys) = inject_event.force_mode {
if let Err(error) = self.injector.inject(&inject_event.text) {
error!("text injector reported an error: {:?}", error);
}
diff --git a/espanso/src/engine/event/inject.rs b/espanso/src/engine/event/inject.rs
index 683cf3f..1edc09a 100644
--- a/espanso/src/engine/event/inject.rs
+++ b/espanso/src/engine/event/inject.rs
@@ -18,10 +18,10 @@
*/
#[derive(Debug)]
-pub struct TextInjectEvent {
+pub struct TextInjectRequest {
pub delete_count: i32,
pub text: String,
- pub mode: TextInjectMode,
+ pub force_mode: Option,
}
#[derive(Debug, PartialEq)]
diff --git a/espanso/src/engine/event/matches.rs b/espanso/src/engine/event/matches.rs
index 74f8a95..6ca617a 100644
--- a/espanso/src/engine/event/matches.rs
+++ b/espanso/src/engine/event/matches.rs
@@ -28,7 +28,7 @@ pub struct MatchesDetectedEvent {
pub struct DetectedMatch {
pub id: i32,
pub trigger: String,
- pub vars: HashMap,
+ pub args: HashMap,
}
#[derive(Debug, Clone, PartialEq)]
diff --git a/espanso/src/engine/event/mod.rs b/espanso/src/engine/event/mod.rs
index c357e87..a55f080 100644
--- a/espanso/src/engine/event/mod.rs
+++ b/espanso/src/engine/event/mod.rs
@@ -20,10 +20,12 @@
pub mod keyboard;
pub mod inject;
pub mod matches;
+pub mod render;
#[derive(Debug)]
pub enum Event {
NOOP,
+ ProcessingError(String),
// Inputs
Keyboard(keyboard::KeyboardEvent),
@@ -32,6 +34,9 @@ pub enum Event {
MatchesDetected(matches::MatchesDetectedEvent),
MatchSelected(matches::MatchSelectedEvent),
+ RenderingRequested(render::RenderingRequestedEvent),
+ Rendered(render::RenderedEvent),
+
// Effects
- TextInject(inject::TextInjectEvent),
+ TextInject(inject::TextInjectRequest),
}
\ No newline at end of file
diff --git a/espanso/src/engine/event/render.rs b/espanso/src/engine/event/render.rs
new file mode 100644
index 0000000..2522774
--- /dev/null
+++ b/espanso/src/engine/event/render.rs
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct RenderingRequestedEvent {
+ pub match_id: i32,
+ pub trigger: String,
+ pub trigger_args: HashMap,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct RenderedEvent {
+ pub trigger: String,
+ pub body: String,
+}
diff --git a/espanso/src/engine/process/default.rs b/espanso/src/engine/process/default.rs
index 17ba379..988e965 100644
--- a/espanso/src/engine/process/default.rs
+++ b/espanso/src/engine/process/default.rs
@@ -19,7 +19,13 @@
use log::trace;
-use super::{Event, MatchFilter, MatchSelector, Matcher, Middleware, Processor, middleware::match_select::MatchSelectMiddleware, middleware::matcher::MatchMiddleware};
+use super::{
+ middleware::{
+ match_select::MatchSelectMiddleware, matcher::MatchMiddleware, multiplex::MultiplexMiddleware,
+ render::RenderMiddleware,
+ },
+ Event, MatchFilter, MatchSelector, Matcher, Middleware, Multiplexer, Processor, Renderer,
+};
use std::collections::VecDeque;
pub struct DefaultProcessor<'a> {
@@ -32,12 +38,16 @@ impl<'a> DefaultProcessor<'a> {
matchers: &'a [&'a dyn Matcher<'a, MatcherState>],
match_filter: &'a dyn MatchFilter,
match_selector: &'a dyn MatchSelector,
+ multiplexer: &'a dyn Multiplexer,
+ renderer: &'a dyn Renderer<'a>,
) -> DefaultProcessor<'a> {
Self {
event_queue: VecDeque::new(),
middleware: vec![
Box::new(MatchMiddleware::new(matchers)),
Box::new(MatchSelectMiddleware::new(match_filter, match_selector)),
+ Box::new(MultiplexMiddleware::new(multiplexer)),
+ Box::new(RenderMiddleware::new(renderer)),
],
}
}
diff --git a/espanso/src/engine/process/middleware/matcher.rs b/espanso/src/engine/process/middleware/matcher.rs
index ab1c6ac..f9a5dfd 100644
--- a/espanso/src/engine/process/middleware/matcher.rs
+++ b/espanso/src/engine/process/middleware/matcher.rs
@@ -91,7 +91,7 @@ impl<'a, State> Middleware for MatchMiddleware<'a, State> {
DetectedMatch {
id: result.id,
trigger: result.trigger,
- vars: result.vars,
+ args: result.args,
}
}).collect()
})
diff --git a/espanso/src/engine/process/middleware/mod.rs b/espanso/src/engine/process/middleware/mod.rs
index c6e0fcb..e449e1a 100644
--- a/espanso/src/engine/process/middleware/mod.rs
+++ b/espanso/src/engine/process/middleware/mod.rs
@@ -17,5 +17,7 @@
* along with espanso. If not, see .
*/
+pub mod render;
pub mod matcher;
+pub mod multiplex;
pub mod match_select;
\ No newline at end of file
diff --git a/espanso/src/engine/process/middleware/multiplex.rs b/espanso/src/engine/process/middleware/multiplex.rs
new file mode 100644
index 0000000..9e9235b
--- /dev/null
+++ b/espanso/src/engine/process/middleware/multiplex.rs
@@ -0,0 +1,51 @@
+/*
+ * 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 log::{debug, error};
+
+use super::super::Middleware;
+use crate::engine::{event::{Event, inject::{TextInjectRequest, TextInjectMode}, matches::MatchSelectedEvent}, process::{MatchFilter, MatchSelector, Multiplexer}};
+
+pub struct MultiplexMiddleware<'a> {
+ multiplexer: &'a dyn Multiplexer,
+}
+
+impl<'a> MultiplexMiddleware<'a> {
+ pub fn new(multiplexer: &'a dyn Multiplexer) -> Self {
+ Self { multiplexer }
+ }
+}
+
+impl<'a> Middleware for MultiplexMiddleware<'a> {
+ fn next(&self, event: Event, _: &dyn FnMut(Event)) -> Event {
+ if let Event::MatchSelected(m_event) = event {
+ return match self.multiplexer.convert(m_event.chosen.id, m_event.chosen.trigger, m_event.chosen.args) {
+ Some(event) => event,
+ None => {
+ error!("match multiplexing failed");
+ Event::NOOP
+ },
+ }
+ }
+
+ event
+ }
+}
+
+// TODO: test
diff --git a/espanso/src/engine/process/middleware/render.rs b/espanso/src/engine/process/middleware/render.rs
new file mode 100644
index 0000000..cb31040
--- /dev/null
+++ b/espanso/src/engine/process/middleware/render.rs
@@ -0,0 +1,63 @@
+/*
+ * 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 log::{debug, error};
+
+use super::super::Middleware;
+use crate::engine::{event::{Event, inject::{TextInjectRequest, TextInjectMode}, matches::MatchSelectedEvent, render::RenderedEvent}, process::{MatchFilter, MatchSelector, Renderer, RendererError}};
+
+pub struct RenderMiddleware<'a> {
+ renderer: &'a dyn Renderer<'a>,
+}
+
+impl<'a> RenderMiddleware<'a> {
+ pub fn new(renderer: &'a dyn Renderer<'a>) -> Self {
+ Self { renderer }
+ }
+}
+
+impl<'a> Middleware for RenderMiddleware<'a> {
+ fn next(&self, event: Event, _: &dyn FnMut(Event)) -> Event {
+ if let Event::RenderingRequested(m_event) = event {
+ match self.renderer.render(m_event.match_id, m_event.trigger_args) {
+ Ok(body) => {
+ return Event::Rendered(RenderedEvent {
+ trigger: m_event.trigger,
+ body,
+ })
+ }
+ Err(err) => {
+ match err.downcast_ref::() {
+ Some(RendererError::Aborted) => {
+ return Event::NOOP
+ }
+ _ => {
+ error!("error during rendering: {}", err);
+ return Event::ProcessingError("An error has occurred during rendering, please examine the logs or contact support.".to_string())
+ }
+ }
+ }
+ }
+ }
+
+ event
+ }
+}
+
+// TODO: test
diff --git a/espanso/src/engine/process/mod.rs b/espanso/src/engine/process/mod.rs
index e16a501..9258869 100644
--- a/espanso/src/engine/process/mod.rs
+++ b/espanso/src/engine/process/mod.rs
@@ -17,9 +17,10 @@
* along with espanso. If not, see .
*/
-use std::collections::HashMap;
-
use super::{event::keyboard::Key, Event};
+use anyhow::Result;
+use std::collections::HashMap;
+use thiserror::Error;
mod default;
mod middleware;
@@ -52,7 +53,7 @@ pub enum MatcherEvent {
pub struct MatchResult {
pub id: i32,
pub trigger: String,
- pub vars: HashMap,
+ pub args: HashMap,
}
pub trait MatchFilter {
@@ -63,10 +64,32 @@ pub trait MatchSelector {
fn select(&self, matches_ids: &[i32]) -> Option;
}
+pub trait Multiplexer {
+ fn convert(&self, match_id: i32, trigger: String, trigger_args: HashMap) -> Option;
+}
+
+pub trait Renderer<'a> {
+ fn render(&'a self, match_id: i32, trigger_args: HashMap) -> Result;
+}
+
+#[derive(Error, Debug)]
+pub enum RendererError {
+ #[error("rendering error")]
+ RenderingError(#[from] anyhow::Error),
+
+ #[error("match not found")]
+ NotFound,
+
+ #[error("aborted")]
+ Aborted,
+}
+
pub fn default<'a, MatcherState>(
matchers: &'a [&'a dyn Matcher<'a, MatcherState>],
match_filter: &'a dyn MatchFilter,
match_selector: &'a dyn MatchSelector,
+ multiplexer: &'a dyn Multiplexer,
+ renderer: &'a dyn Renderer<'a>,
) -> impl Processor + 'a {
- default::DefaultProcessor::new(matchers, match_filter, match_selector)
+ default::DefaultProcessor::new(matchers, match_filter, match_selector, multiplexer, renderer)
}