diff --git a/espanso/src/cli/worker/config.rs b/espanso/src/cli/worker/config.rs
new file mode 100644
index 0000000..84b85ca
--- /dev/null
+++ b/espanso/src/cli/worker/config.rs
@@ -0,0 +1,82 @@
+/*
+ * 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::HashSet;
+
+use crate::engine::process::MatchFilter;
+use espanso_config::{
+ config::{AppProperties, Config, ConfigStore},
+ matches::store::{MatchSet, MatchStore},
+};
+use espanso_info::{AppInfo, AppInfoProvider};
+use std::iter::FromIterator;
+
+pub struct ConfigManager<'a> {
+ config_store: &'a dyn ConfigStore,
+ match_store: &'a dyn MatchStore,
+ app_info_provider: &'a dyn AppInfoProvider,
+}
+
+impl<'a> ConfigManager<'a> {
+ pub fn new(
+ config_store: &'a dyn ConfigStore,
+ match_store: &'a dyn MatchStore,
+ app_info_provider: &'a dyn AppInfoProvider,
+ ) -> Self {
+ Self {
+ config_store,
+ match_store,
+ app_info_provider,
+ }
+ }
+
+ 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()
+ }
+}
+
+// TODO: test
+fn to_app_properties(info: &AppInfo) -> AppProperties {
+ AppProperties {
+ title: info.title.as_deref(),
+ class: info.class.as_deref(),
+ exec: info.exec.as_deref(),
+ }
+}
diff --git a/espanso/src/cli/worker/matcher/convert.rs b/espanso/src/cli/worker/matcher/convert.rs
new file mode 100644
index 0000000..ffe52bf
--- /dev/null
+++ b/espanso/src/cli/worker/matcher/convert.rs
@@ -0,0 +1,72 @@
+/*
+ * 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 espanso_config::{
+ config::ConfigStore,
+ matches::{
+ store::{MatchSet, MatchStore},
+ MatchCause,
+ },
+};
+use espanso_match::rolling::{RollingMatch, StringMatchOptions};
+use std::iter::FromIterator;
+
+pub struct MatchConverter<'a> {
+ config_store: &'a dyn ConfigStore,
+ match_store: &'a dyn MatchStore,
+}
+
+impl<'a> MatchConverter<'a> {
+ pub fn new(config_store: &'a dyn ConfigStore, match_store: &'a dyn MatchStore) -> Self {
+ Self {
+ config_store,
+ match_store,
+ }
+ }
+
+ // TODO: test (might need to move the conversion logic into a separate function)
+ pub fn get_rolling_matches(&self) -> Vec> {
+ let match_set = self.global_match_set();
+ let mut matches = Vec::new();
+
+ for m in match_set.matches {
+ if let MatchCause::Trigger(cause) = &m.cause {
+ for trigger in cause.triggers.iter() {
+ matches.push(RollingMatch::from_string(
+ m.id,
+ &trigger,
+ &StringMatchOptions {
+ case_insensitive: cause.propagate_case,
+ preserve_case_markers: cause.propagate_case,
+ left_word: cause.left_word,
+ right_word: cause.right_word,
+ },
+ ))
+ }
+ }
+ }
+
+ matches
+ }
+
+ fn global_match_set(&self) -> MatchSet {
+ let paths = self.config_store.get_all_match_paths();
+ self.match_store.query(&Vec::from_iter(paths.into_iter()))
+ }
+}
diff --git a/espanso/src/cli/worker/matcher/mod.rs b/espanso/src/cli/worker/matcher/mod.rs
index 3e30275..ac109b6 100644
--- a/espanso/src/cli/worker/matcher/mod.rs
+++ b/espanso/src/cli/worker/matcher/mod.rs
@@ -22,6 +22,7 @@ use espanso_match::rolling::matcher::RollingMatcherState;
use enum_as_inner::EnumAsInner;
pub mod rolling;
+pub mod convert;
#[derive(Clone, EnumAsInner)]
pub enum MatcherState<'a> {
diff --git a/espanso/src/cli/worker/matcher/rolling.rs b/espanso/src/cli/worker/matcher/rolling.rs
index b11fe58..4719308 100644
--- a/espanso/src/cli/worker/matcher/rolling.rs
+++ b/espanso/src/cli/worker/matcher/rolling.rs
@@ -31,14 +31,8 @@ pub struct RollingMatcherAdapter {
}
impl RollingMatcherAdapter {
- pub fn new() -> Self {
- // TODO: pass actual matches
- let matches = vec![
- RollingMatch::from_string(1, "esp", &Default::default()),
- RollingMatch::from_string(2, "test", &Default::default()),
- ];
-
- let matcher = RollingMatcher::new(&matches, Default::default());
+ pub fn new(matches: &[RollingMatch]) -> Self {
+ let matcher = RollingMatcher::new(matches, Default::default());
Self { matcher }
}
diff --git a/espanso/src/cli/worker/mod.rs b/espanso/src/cli/worker/mod.rs
index 037deb2..b740cc6 100644
--- a/espanso/src/cli/worker/mod.rs
+++ b/espanso/src/cli/worker/mod.rs
@@ -19,10 +19,13 @@
use funnel::Source;
use process::Matcher;
+use ui::selector::MatchSelectorAdapter;
use crate::engine::{Engine, funnel, process, dispatch};
use super::{CliModule, CliModuleArgs};
+mod ui;
+mod config;
mod source;
mod matcher;
mod executor;
@@ -40,13 +43,21 @@ pub fn new() -> CliModule {
}
fn worker_main(args: CliModuleArgs) {
- let detect_source = source::detect::init_and_spawn().unwrap(); // TODO: handle error
+ let config_store = args.config_store.expect("missing config store in worker main");
+ let match_store = args.match_store.expect("missing match store in worker main");
+
+ 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 detect_source = 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();
+ let matcher = matcher::rolling::RollingMatcherAdapter::new(&match_converter.get_rolling_matches());
let matchers: Vec<&dyn Matcher> = vec![&matcher];
- let mut processor = process::default(&matchers);
+ let selector = MatchSelectorAdapter::new();
+ let mut processor = process::default(&matchers, &config_manager, &selector);
let text_injector = executor::text_injector::TextInjectorAdapter::new();
let dispatcher = dispatch::default(&text_injector);
diff --git a/espanso/src/cli/worker/ui/mod.rs b/espanso/src/cli/worker/ui/mod.rs
new file mode 100644
index 0000000..441c687
--- /dev/null
+++ b/espanso/src/cli/worker/ui/mod.rs
@@ -0,0 +1,20 @@
+/*
+ * 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 selector;
\ No newline at end of file
diff --git a/espanso/src/cli/worker/ui/selector.rs b/espanso/src/cli/worker/ui/selector.rs
new file mode 100644
index 0000000..d68995d
--- /dev/null
+++ b/espanso/src/cli/worker/ui/selector.rs
@@ -0,0 +1,37 @@
+/*
+ * 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 crate::engine::process::MatchSelector;
+
+pub struct MatchSelectorAdapter {
+ // TODO: pass Modulo search UI manager
+}
+
+impl MatchSelectorAdapter {
+ pub fn new() -> Self {
+ Self {}
+ }
+}
+
+impl MatchSelector for MatchSelectorAdapter {
+ fn select(&self, matches_ids: &[i32]) -> Option {
+ // TODO: replace with actual selection
+ Some(*matches_ids.first().unwrap())
+ }
+}
diff --git a/espanso/src/engine/event/inject.rs b/espanso/src/engine/event/inject.rs
index de2b254..683cf3f 100644
--- a/espanso/src/engine/event/inject.rs
+++ b/espanso/src/engine/event/inject.rs
@@ -19,6 +19,7 @@
#[derive(Debug)]
pub struct TextInjectEvent {
+ pub delete_count: i32,
pub text: String,
pub mode: TextInjectMode,
}
diff --git a/espanso/src/engine/event/matches_detected.rs b/espanso/src/engine/event/matches.rs
similarity index 85%
rename from espanso/src/engine/event/matches_detected.rs
rename to espanso/src/engine/event/matches.rs
index cb9e331..74f8a95 100644
--- a/espanso/src/engine/event/matches_detected.rs
+++ b/espanso/src/engine/event/matches.rs
@@ -21,12 +21,17 @@ use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub struct MatchesDetectedEvent {
- pub results: Vec,
+ pub matches: Vec,
}
#[derive(Debug, Clone, PartialEq)]
-pub struct MatchResult {
+pub struct DetectedMatch {
pub id: i32,
pub trigger: String,
pub vars: HashMap,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct MatchSelectedEvent {
+ pub chosen: DetectedMatch,
}
\ No newline at end of file
diff --git a/espanso/src/engine/event/mod.rs b/espanso/src/engine/event/mod.rs
index ecb1f86..c357e87 100644
--- a/espanso/src/engine/event/mod.rs
+++ b/espanso/src/engine/event/mod.rs
@@ -19,7 +19,7 @@
pub mod keyboard;
pub mod inject;
-pub mod matches_detected;
+pub mod matches;
#[derive(Debug)]
pub enum Event {
@@ -29,7 +29,8 @@ pub enum Event {
Keyboard(keyboard::KeyboardEvent),
// Internal
- MatchesDetected(matches_detected::MatchesDetectedEvent),
+ MatchesDetected(matches::MatchesDetectedEvent),
+ MatchSelected(matches::MatchSelectedEvent),
// Effects
TextInject(inject::TextInjectEvent),
diff --git a/espanso/src/engine/process/default.rs b/espanso/src/engine/process/default.rs
index 2eea714..17ba379 100644
--- a/espanso/src/engine/process/default.rs
+++ b/espanso/src/engine/process/default.rs
@@ -19,7 +19,7 @@
use log::trace;
-use super::{Event, Matcher, Middleware, Processor, middleware::matcher::MatchMiddleware};
+use super::{Event, MatchFilter, MatchSelector, Matcher, Middleware, Processor, middleware::match_select::MatchSelectMiddleware, middleware::matcher::MatchMiddleware};
use std::collections::VecDeque;
pub struct DefaultProcessor<'a> {
@@ -27,20 +27,25 @@ pub struct DefaultProcessor<'a> {
middleware: Vec>,
}
-impl <'a> DefaultProcessor<'a> {
- pub fn new(matchers: &'a [&'a dyn Matcher<'a, MatcherState>]) -> DefaultProcessor<'a> {
+impl<'a> DefaultProcessor<'a> {
+ pub fn new(
+ matchers: &'a [&'a dyn Matcher<'a, MatcherState>],
+ match_filter: &'a dyn MatchFilter,
+ match_selector: &'a dyn MatchSelector,
+ ) -> DefaultProcessor<'a> {
Self {
event_queue: VecDeque::new(),
middleware: vec![
Box::new(MatchMiddleware::new(matchers)),
- ]
+ Box::new(MatchSelectMiddleware::new(match_filter, match_selector)),
+ ],
}
}
fn process_one(&mut self) -> Option {
if let Some(event) = self.event_queue.pop_back() {
let mut current_event = event;
-
+
let mut current_queue = VecDeque::new();
let dispatch = |event: Event| {
trace!("dispatched event: {:?}", event);
@@ -51,7 +56,7 @@ impl <'a> DefaultProcessor<'a> {
trace!("middleware received event: {:?}", current_event);
current_event = middleware.next(current_event, &dispatch);
-
+
trace!("middleware produced event: {:?}", current_event);
}
@@ -66,7 +71,7 @@ impl <'a> DefaultProcessor<'a> {
}
}
-impl <'a> Processor for DefaultProcessor<'a> {
+impl<'a> Processor for DefaultProcessor<'a> {
fn process(&mut self, event: Event) -> Vec {
self.event_queue.push_front(event);
@@ -80,4 +85,4 @@ impl <'a> Processor for DefaultProcessor<'a> {
processed_events
}
-}
\ No newline at end of file
+}
diff --git a/espanso/src/engine/process/middleware/match_select.rs b/espanso/src/engine/process/middleware/match_select.rs
new file mode 100644
index 0000000..146ad0b
--- /dev/null
+++ b/espanso/src/engine/process/middleware/match_select.rs
@@ -0,0 +1,93 @@
+/*
+ * 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::{
+ matches::{MatchSelectedEvent},
+ Event,
+ },
+ process::{MatchFilter, MatchSelector},
+};
+
+pub struct MatchSelectMiddleware<'a> {
+ match_filter: &'a dyn MatchFilter,
+ match_selector: &'a dyn MatchSelector,
+}
+
+impl<'a> MatchSelectMiddleware<'a> {
+ pub fn new(match_filter: &'a dyn MatchFilter, match_selector: &'a dyn MatchSelector) -> Self {
+ Self {
+ match_filter,
+ match_selector,
+ }
+ }
+}
+
+impl<'a> Middleware for MatchSelectMiddleware<'a> {
+ fn next(&self, event: Event, _: &dyn FnMut(Event)) -> Event {
+ if let Event::MatchesDetected(m_event) = event {
+ let matches_ids: Vec = m_event.matches.iter().map(|m| m.id).collect();
+
+ // Find the matches that are actually valid in the current context
+ let valid_ids = self.match_filter.filter_active(&matches_ids);
+
+ return match valid_ids.len() {
+ 0 => Event::NOOP, // No valid matches, consume the event
+ 1 => {
+ // Only one match, no need to show a selection dialog
+ let m = m_event
+ .matches
+ .into_iter()
+ .find(|m| m.id == *valid_ids.first().unwrap());
+ if let Some(m) = m {
+ Event::MatchSelected(MatchSelectedEvent { chosen: m })
+ } else {
+ error!("MatchSelectMiddleware could not find the correspondent match");
+ Event::NOOP
+ }
+ }
+ _ => {
+ // Multiple matches, we need to ask the user which one to use
+ if let Some(selected_id) = self.match_selector.select(&valid_ids) {
+ let m = m_event
+ .matches
+ .into_iter()
+ .find(|m| m.id == selected_id);
+ if let Some(m) = m {
+ Event::MatchSelected(MatchSelectedEvent { chosen: m })
+ } else {
+ error!("MatchSelectMiddleware could not find the correspondent match");
+ Event::NOOP
+ }
+ } else {
+ debug!("MatchSelectMiddleware did not receive any match selection");
+ Event::NOOP
+ }
+ }
+ };
+ }
+
+ event
+ }
+}
+
+// TODO: test
\ No newline at end of file
diff --git a/espanso/src/engine/process/middleware/matcher.rs b/espanso/src/engine/process/middleware/matcher.rs
index c208b97..ab1c6ac 100644
--- a/espanso/src/engine/process/middleware/matcher.rs
+++ b/espanso/src/engine/process/middleware/matcher.rs
@@ -21,7 +21,7 @@ use log::trace;
use std::{cell::RefCell, collections::VecDeque};
use super::super::Middleware;
-use crate::engine::{event::{Event, keyboard::{Key, Status}, matches_detected::{MatchResult, MatchesDetectedEvent}}, process::{Matcher, MatcherEvent}};
+use crate::engine::{event::{Event, keyboard::{Key, Status}, matches::{DetectedMatch, MatchesDetectedEvent}}, process::{Matcher, MatcherEvent}};
const MAX_HISTORY: usize = 3; // TODO: get as parameter
@@ -85,12 +85,10 @@ impl<'a, State> Middleware for MatchMiddleware<'a, State> {
matcher_states.pop_front();
}
- println!("results: {:?}", all_results);
-
if !all_results.is_empty() {
return Event::MatchesDetected(MatchesDetectedEvent {
- results: all_results.into_iter().map(|result | {
- MatchResult {
+ matches: all_results.into_iter().map(|result | {
+ DetectedMatch {
id: result.id,
trigger: result.trigger,
vars: result.vars,
@@ -133,3 +131,5 @@ fn is_invalidating_key(key: &Key) -> bool {
_ => false,
}
}
+
+// TODO: test
\ No newline at end of file
diff --git a/espanso/src/engine/process/middleware/mod.rs b/espanso/src/engine/process/middleware/mod.rs
index 4b34c33..c6e0fcb 100644
--- a/espanso/src/engine/process/middleware/mod.rs
+++ b/espanso/src/engine/process/middleware/mod.rs
@@ -17,4 +17,5 @@
* along with espanso. If not, see .
*/
-pub mod matcher;
\ No newline at end of file
+pub mod matcher;
+pub mod match_select;
\ No newline at end of file
diff --git a/espanso/src/engine/process/mod.rs b/espanso/src/engine/process/mod.rs
index 73cb897..e16a501 100644
--- a/espanso/src/engine/process/mod.rs
+++ b/espanso/src/engine/process/mod.rs
@@ -17,13 +17,12 @@
* along with espanso. If not, see .
*/
-
use std::collections::HashMap;
-use super::{Event, event::keyboard::Key};
+use super::{event::keyboard::Key, Event};
-mod middleware;
mod default;
+mod middleware;
pub trait Middleware {
fn next(&self, event: Event, dispatch: &dyn FnMut(Event)) -> Event;
@@ -36,7 +35,11 @@ pub trait Processor {
// Dependency inversion entities
pub trait Matcher<'a, State> {
- fn process(&'a self, prev_state: Option<&State>, event: &MatcherEvent) -> (State, Vec);
+ fn process(
+ &'a self,
+ prev_state: Option<&State>,
+ event: &MatcherEvent,
+ ) -> (State, Vec);
}
#[derive(Debug)]
@@ -52,6 +55,18 @@ pub struct MatchResult {
pub vars: HashMap,
}
-pub fn default<'a, MatcherState>(matchers: &'a [&'a dyn Matcher<'a, MatcherState>]) -> impl Processor + 'a {
- default::DefaultProcessor::new(matchers)
-}
\ No newline at end of file
+pub trait MatchFilter {
+ fn filter_active(&self, matches_ids: &[i32]) -> Vec;
+}
+
+pub trait MatchSelector {
+ fn select(&self, matches_ids: &[i32]) -> Option;
+}
+
+pub fn default<'a, MatcherState>(
+ matchers: &'a [&'a dyn Matcher<'a, MatcherState>],
+ match_filter: &'a dyn MatchFilter,
+ match_selector: &'a dyn MatchSelector,
+) -> impl Processor + 'a {
+ default::DefaultProcessor::new(matchers, match_filter, match_selector)
+}
diff --git a/espanso/src/main.rs b/espanso/src/main.rs
index ce68796..d347453 100644
--- a/espanso/src/main.rs
+++ b/espanso/src/main.rs
@@ -201,8 +201,12 @@ fn main() {
let log_level = match matches.occurrences_of("v") {
0 => LevelFilter::Warn,
1 => LevelFilter::Info,
- 2 => LevelFilter::Debug,
- _ => LevelFilter::Trace,
+
+ // Trace mode is only available in debug mode for security reasons
+ #[cfg(debug_assertions)]
+ 3 => LevelFilter::Trace,
+
+ _ => LevelFilter::Debug,
};
let handler = CLI_HANDLERS