diff --git a/espanso/src/cli/worker/builtin/debug.rs b/espanso/src/cli/worker/builtin/debug.rs
new file mode 100644
index 0000000..eecf128
--- /dev/null
+++ b/espanso/src/cli/worker/builtin/debug.rs
@@ -0,0 +1,38 @@
+/*
+ * 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::{cli::worker::builtin::generate_next_builtin_id, engine::event::{EventType, effect::TextInjectRequest}};
+
+use super::BuiltInMatch;
+
+pub fn create_match_show_active_config_info() -> BuiltInMatch {
+ BuiltInMatch {
+ id: generate_next_builtin_id(),
+ label: "Display active config information",
+ triggers: vec!["#acfg#".to_string()],
+ action: |context| {
+ println!("active config: {:?}", context.get_active_config().label());
+
+ EventType::TextInject(TextInjectRequest {
+ text: "test".to_string(),
+ force_mode: None,
+ })
+ },
+ }
+}
\ No newline at end of file
diff --git a/espanso/src/cli/worker/builtin/mod.rs b/espanso/src/cli/worker/builtin/mod.rs
new file mode 100644
index 0000000..05c7c5c
--- /dev/null
+++ b/espanso/src/cli/worker/builtin/mod.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::cell::Cell;
+
+use crate::engine::event::EventType;
+
+use super::context::Context;
+
+mod debug;
+
+const MIN_BUILTIN_MATCH_ID: i32 = 1_000_000_000;
+
+pub struct BuiltInMatch {
+ pub id: i32,
+ pub label: &'static str,
+ pub triggers: Vec,
+ pub action: fn(context: &dyn Context) -> EventType,
+}
+
+pub fn get_builtin_matches() -> Vec {
+ vec![
+ debug::create_match_show_active_config_info(),
+ ]
+}
+
+pub fn is_builtin_match(id: i32) -> bool {
+ id >= MIN_BUILTIN_MATCH_ID
+}
+
+thread_local! {
+ static CURRENT_BUILTIN_MATCH_ID: Cell = Cell::new(MIN_BUILTIN_MATCH_ID);
+}
+
+fn generate_next_builtin_id() -> i32 {
+ CURRENT_BUILTIN_MATCH_ID.with(|value| {
+ let current = value.get();
+ value.set(current + 1);
+ current
+ })
+}
diff --git a/espanso/src/cli/worker/config.rs b/espanso/src/cli/worker/config.rs
index d9625c2..e9ea90d 100644
--- a/espanso/src/cli/worker/config.rs
+++ b/espanso/src/cli/worker/config.rs
@@ -26,6 +26,8 @@ use espanso_config::{
use espanso_info::{AppInfo, AppInfoProvider};
use std::iter::FromIterator;
+use super::builtin::is_builtin_match;
+
pub struct ConfigManager<'a> {
config_store: &'a dyn ConfigStore,
match_store: &'a dyn MatchStore,
@@ -57,7 +59,7 @@ impl<'a> ConfigManager<'a> {
(config.clone(), self.match_store.query(&match_paths))
}
- pub fn default(&self) -> Arc{
+ pub fn default(&self) -> Arc {
self.config_store.default()
}
}
@@ -76,12 +78,22 @@ impl<'a> crate::engine::process::MatchFilter for ConfigManager<'a> {
let ids_set: HashSet = HashSet::from_iter(matches_ids.iter().copied());
let (_, match_set) = self.active_context();
- match_set
+ let active_user_defined_matches: Vec = match_set
.matches
.iter()
.filter(|m| ids_set.contains(&m.id))
.map(|m| m.id)
- .collect()
+ .collect();
+
+ let builtin_matches: Vec = matches_ids
+ .into_iter()
+ .filter(|id| is_builtin_match(**id))
+ .map(|id| *id)
+ .collect();
+
+ let mut output = active_user_defined_matches;
+ output.extend(builtin_matches);
+ output
}
}
diff --git a/espanso/src/cli/worker/context/default.rs b/espanso/src/cli/worker/context/default.rs
new file mode 100644
index 0000000..7e09be9
--- /dev/null
+++ b/espanso/src/cli/worker/context/default.rs
@@ -0,0 +1,43 @@
+/*
+ * 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 super::*;
+use crate::cli::worker::config::ConfigManager;
+
+pub struct DefaultContext<'a> {
+ config_manager: &'a ConfigManager<'a>,
+}
+
+impl<'a> DefaultContext<'a> {
+ pub fn new(config_manager: &'a ConfigManager<'a>) -> Self {
+ Self { config_manager }
+ }
+}
+
+impl<'a> Context for DefaultContext<'a> {}
+
+impl<'a> ConfigContext for DefaultContext<'a> {
+ fn get_default_config(&self) -> Arc {
+ self.config_manager.default()
+ }
+
+ fn get_active_config(&self) -> Arc {
+ self.config_manager.active()
+ }
+}
diff --git a/espanso/src/cli/worker/context/mod.rs b/espanso/src/cli/worker/context/mod.rs
new file mode 100644
index 0000000..41eefde
--- /dev/null
+++ b/espanso/src/cli/worker/context/mod.rs
@@ -0,0 +1,32 @@
+/*
+ * 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::sync::Arc;
+
+use espanso_config::config::Config;
+
+mod default;
+pub use default::DefaultContext;
+
+pub trait Context: ConfigContext {}
+
+pub trait ConfigContext {
+ fn get_default_config(&self) -> Arc;
+ fn get_active_config(&self) -> Arc;
+}
\ No newline at end of file
diff --git a/espanso/src/cli/worker/engine/mod.rs b/espanso/src/cli/worker/engine/mod.rs
index c9a4305..f4c9a4a 100644
--- a/espanso/src/cli/worker/engine/mod.rs
+++ b/espanso/src/cli/worker/engine/mod.rs
@@ -28,8 +28,7 @@ use espanso_path::Paths;
use espanso_ui::{event::UIEvent, UIRemote};
use log::{debug, error, info, warn};
-use crate::{cli::worker::{
- engine::{
+use crate::{cli::worker::{context::Context, engine::{
dispatch::executor::{
clipboard_injector::ClipboardInjectorAdapter, context_menu::ContextMenuHandlerAdapter,
event_injector::EventInjectorAdapter, icon::IconHandlerAdapter,
@@ -49,9 +48,7 @@ use crate::{cli::worker::{
RendererAdapter,
},
},
- },
- match_cache::MatchCache,
- }, engine::event::ExitMode, preferences::Preferences};
+ }, match_cache::{CombinedMatchCache, MatchCache}}, engine::event::ExitMode, preferences::Preferences};
use super::secure_input::SecureInputEvent;
@@ -82,13 +79,18 @@ pub fn initialize_and_spawn(
espanso_info::get_provider().expect("unable to initialize app info provider");
let config_manager =
super::config::ConfigManager::new(&*config_store, &*match_store, &*app_info_provider);
- let match_converter = MatchConverter::new(&*config_store, &*match_store);
let match_cache = MatchCache::load(&*config_store, &*match_store);
let modulo_manager = crate::gui::modulo::manager::ModuloManager::new();
let modulo_form_ui = crate::gui::modulo::form::ModuloFormUI::new(&modulo_manager);
let modulo_search_ui = crate::gui::modulo::search::ModuloSearchUI::new(&modulo_manager);
+ let context: Box = Box::new(super::context::DefaultContext::new(&config_manager));
+ let builtin_matches = super::builtin::get_builtin_matches();
+ let combined_match_cache = CombinedMatchCache::load(&match_cache, &builtin_matches);
+
+ let match_converter = MatchConverter::new(&*config_store, &*match_store, &builtin_matches);
+
let has_granted_capabilities = grant_linux_capabilities(use_evdev_backend);
// TODO: pass all the options
@@ -129,8 +131,8 @@ pub fn initialize_and_spawn(
super::engine::process::middleware::matcher::MatcherState,
>,
> = vec![&rolling_matcher, ®ex_matcher];
- let selector = MatchSelectorAdapter::new(&modulo_search_ui, &match_cache);
- let multiplexer = MultiplexAdapter::new(&match_cache);
+ let selector = MatchSelectorAdapter::new(&modulo_search_ui, &combined_match_cache);
+ let multiplexer = MultiplexAdapter::new(&combined_match_cache, &*context);
let injector = espanso_inject::get_injector(InjectorCreationOptions {
use_evdev: use_evdev_backend,
diff --git a/espanso/src/cli/worker/engine/process/middleware/match_select.rs b/espanso/src/cli/worker/engine/process/middleware/match_select.rs
index d39af6c..fd65be5 100644
--- a/espanso/src/cli/worker/engine/process/middleware/match_select.rs
+++ b/espanso/src/cli/worker/engine/process/middleware/match_select.rs
@@ -17,7 +17,6 @@
* along with espanso. If not, see .
*/
-use espanso_config::matches::{Match};
use log::error;
use crate::{
@@ -28,7 +27,13 @@ use crate::{
const MAX_LABEL_LEN: usize = 100;
pub trait MatchProvider<'a> {
- fn get_matches(&self, ids: &[i32]) -> Vec<&'a Match>;
+ fn get_matches(&self, ids: &[i32]) -> Vec>;
+}
+
+pub struct MatchSummary<'a> {
+ pub id: i32,
+ pub label: &'a str,
+ pub tag: Option<&'a str>,
}
pub struct MatchSelectorAdapter<'a> {
@@ -49,15 +54,14 @@ impl<'a> MatchSelector for MatchSelectorAdapter<'a> {
fn select(&self, matches_ids: &[i32]) -> Option {
let matches = self.match_provider.get_matches(&matches_ids);
let search_items: Vec = matches
- .iter()
+ .into_iter()
.map(|m| {
- let label = m.description();
- let clipped_label = &label[..std::cmp::min(label.len(), MAX_LABEL_LEN)];
+ let clipped_label = &m.label[..std::cmp::min(m.label.len(), MAX_LABEL_LEN)];
SearchItem {
id: m.id.to_string(),
label: clipped_label.to_string(),
- tag: m.cause_description().map(String::from),
+ tag: m.tag.map(String::from),
}
})
.collect();
diff --git a/espanso/src/cli/worker/engine/process/middleware/matcher/convert.rs b/espanso/src/cli/worker/engine/process/middleware/matcher/convert.rs
index 61039b2..24bad2b 100644
--- a/espanso/src/cli/worker/engine/process/middleware/matcher/convert.rs
+++ b/espanso/src/cli/worker/engine/process/middleware/matcher/convert.rs
@@ -30,16 +30,24 @@ use espanso_match::{
};
use std::iter::FromIterator;
+use crate::cli::worker::builtin::BuiltInMatch;
+
pub struct MatchConverter<'a> {
config_store: &'a dyn ConfigStore,
match_store: &'a dyn MatchStore,
+ builtin_matches: &'a [BuiltInMatch],
}
impl<'a> MatchConverter<'a> {
- pub fn new(config_store: &'a dyn ConfigStore, match_store: &'a dyn MatchStore) -> Self {
+ pub fn new(
+ config_store: &'a dyn ConfigStore,
+ match_store: &'a dyn MatchStore,
+ builtin_matches: &'a [BuiltInMatch],
+ ) -> Self {
Self {
config_store,
match_store,
+ builtin_matches,
}
}
@@ -48,6 +56,7 @@ impl<'a> MatchConverter<'a> {
let match_set = self.global_match_set();
let mut matches = Vec::new();
+ // First convert configuration (user-defined) matches
for m in match_set.matches {
if let MatchCause::Trigger(cause) = &m.cause {
for trigger in cause.triggers.iter() {
@@ -64,6 +73,17 @@ impl<'a> MatchConverter<'a> {
}
}
+ // Then convert built-in ones
+ for m in self.builtin_matches {
+ for trigger in m.triggers.iter() {
+ matches.push(RollingMatch::from_string(
+ m.id,
+ &trigger,
+ &StringMatchOptions::default()
+ ))
+ }
+ }
+
matches
}
@@ -74,10 +94,7 @@ impl<'a> MatchConverter<'a> {
for m in match_set.matches {
if let MatchCause::Regex(cause) = &m.cause {
- matches.push(RegexMatch::new(
- m.id,
- &cause.regex,
- ))
+ matches.push(RegexMatch::new(m.id, &cause.regex))
}
}
diff --git a/espanso/src/cli/worker/engine/process/middleware/multiplex.rs b/espanso/src/cli/worker/engine/process/middleware/multiplex.rs
index b0db6e2..2c73a8d 100644
--- a/espanso/src/cli/worker/engine/process/middleware/multiplex.rs
+++ b/espanso/src/cli/worker/engine/process/middleware/multiplex.rs
@@ -19,40 +19,59 @@
use espanso_config::matches::{Match, MatchEffect};
-use crate::engine::{event::{EventType, internal::DetectedMatch, internal::{ImageRequestedEvent, RenderingRequestedEvent, TextFormat}}, process::Multiplexer};
+use crate::{
+ cli::worker::{builtin::BuiltInMatch, context::Context},
+ engine::{
+ event::{
+ internal::DetectedMatch,
+ internal::{ImageRequestedEvent, RenderingRequestedEvent, TextFormat},
+ EventType,
+ },
+ process::Multiplexer,
+ },
+};
pub trait MatchProvider<'a> {
- fn get(&self, match_id: i32) -> Option<&'a Match>;
+ fn get(&self, match_id: i32) -> Option>;
+}
+
+pub enum MatchResult<'a> {
+ User(&'a Match),
+ Builtin(&'a BuiltInMatch),
}
pub struct MultiplexAdapter<'a> {
provider: &'a dyn MatchProvider<'a>,
+ context: &'a dyn Context,
}
impl<'a> MultiplexAdapter<'a> {
- pub fn new(provider: &'a dyn MatchProvider<'a>) -> Self {
- Self { provider }
+ pub fn new(provider: &'a dyn MatchProvider<'a>, context: &'a dyn Context) -> Self {
+ Self { provider, context }
}
}
impl<'a> Multiplexer for MultiplexAdapter<'a> {
fn convert(&self, detected_match: DetectedMatch) -> Option {
- let m = self.provider.get(detected_match.id)?;
-
- match &m.effect {
- MatchEffect::Text(effect) => Some(EventType::RenderingRequested(RenderingRequestedEvent {
- match_id: detected_match.id,
- trigger: detected_match.trigger,
- left_separator: detected_match.left_separator,
- right_separator: detected_match.right_separator,
- trigger_args: detected_match.args,
- format: convert_format(&effect.format),
- })),
- MatchEffect::Image(effect) => Some(EventType::ImageRequested(ImageRequestedEvent {
- match_id: detected_match.id,
- image_path: effect.path.clone(),
- })),
- MatchEffect::None => None,
+ match self.provider.get(detected_match.id)? {
+ MatchResult::User(m) => match &m.effect {
+ MatchEffect::Text(effect) => Some(EventType::RenderingRequested(RenderingRequestedEvent {
+ match_id: detected_match.id,
+ trigger: detected_match.trigger,
+ left_separator: detected_match.left_separator,
+ right_separator: detected_match.right_separator,
+ trigger_args: detected_match.args,
+ format: convert_format(&effect.format),
+ })),
+ MatchEffect::Image(effect) => Some(EventType::ImageRequested(ImageRequestedEvent {
+ match_id: detected_match.id,
+ image_path: effect.path.clone(),
+ })),
+ MatchEffect::None => None,
+ },
+ MatchResult::Builtin(m) => {
+ Some((m.action)(self.context))
+ }
}
}
}
@@ -60,7 +79,7 @@ impl<'a> Multiplexer for MultiplexAdapter<'a> {
fn convert_format(format: &espanso_config::matches::TextFormat) -> TextFormat {
match format {
espanso_config::matches::TextFormat::Plain => TextFormat::Plain,
- espanso_config::matches::TextFormat::Markdown => TextFormat::Markdown,
- espanso_config::matches::TextFormat::Html => TextFormat::Html,
+ espanso_config::matches::TextFormat::Markdown => TextFormat::Markdown,
+ espanso_config::matches::TextFormat::Html => TextFormat::Html,
}
}
diff --git a/espanso/src/cli/worker/match_cache.rs b/espanso/src/cli/worker/match_cache.rs
index 58d681d..4ce7d85 100644
--- a/espanso/src/cli/worker/match_cache.rs
+++ b/espanso/src/cli/worker/match_cache.rs
@@ -24,6 +24,8 @@ use espanso_config::{
matches::{store::MatchStore, Match, MatchEffect},
};
+use super::{builtin::BuiltInMatch, engine::process::middleware::match_select::MatchSummary};
+
pub struct MatchCache<'a> {
cache: HashMap,
}
@@ -43,12 +45,6 @@ impl<'a> MatchCache<'a> {
}
}
-impl<'a> super::engine::process::middleware::multiplex::MatchProvider<'a> for MatchCache<'a> {
- fn get(&self, match_id: i32) -> Option<&'a Match> {
- self.cache.get(&match_id).map(|m| *m)
- }
-}
-
impl<'a> super::engine::process::middleware::render::MatchProvider<'a> for MatchCache<'a> {
fn matches(&self) -> Vec<&'a Match> {
self.cache.iter().map(|(_, m)| *m).collect()
@@ -59,15 +55,6 @@ impl<'a> super::engine::process::middleware::render::MatchProvider<'a> for Match
}
}
-impl<'a> super::engine::process::middleware::match_select::MatchProvider<'a> for MatchCache<'a> {
- fn get_matches(&self, ids: &[i32]) -> Vec<&'a Match> {
- ids
- .iter()
- .flat_map(|id| self.cache.get(&id).map(|m| *m))
- .collect()
- }
-}
-
impl<'a> crate::engine::process::MatchInfoProvider for MatchCache<'a> {
fn get_force_mode(&self, match_id: i32) -> Option {
let m = self.cache.get(&match_id)?;
@@ -87,3 +74,79 @@ impl<'a> crate::engine::process::MatchInfoProvider for MatchCache<'a> {
None
}
}
+
+pub struct CombinedMatchCache<'a> {
+ user_match_cache: &'a MatchCache<'a>,
+ builtin_match_cache: HashMap,
+}
+
+pub enum MatchVariant<'a> {
+ User(&'a Match),
+ Builtin(&'a BuiltInMatch),
+}
+
+impl<'a> CombinedMatchCache<'a> {
+ pub fn load(user_match_cache: &'a MatchCache<'a>, builtin_matches: &'a [BuiltInMatch]) -> Self {
+ let mut builtin_match_cache = HashMap::new();
+
+ for m in builtin_matches {
+ builtin_match_cache.insert(m.id, m);
+ }
+
+ Self {
+ builtin_match_cache,
+ user_match_cache,
+ }
+ }
+
+ pub fn get(&self, match_id: i32) -> Option> {
+ if let Some(user_match) = self.user_match_cache.cache.get(&match_id).map(|m| *m) {
+ return Some(MatchVariant::User(user_match));
+ }
+
+ if let Some(builtin_match) = self.builtin_match_cache.get(&match_id).map(|m| *m) {
+ return Some(MatchVariant::Builtin(builtin_match));
+ }
+
+ None
+ }
+}
+
+impl<'a> super::engine::process::middleware::match_select::MatchProvider<'a>
+ for CombinedMatchCache<'a>
+{
+ fn get_matches(&self, ids: &[i32]) -> Vec> {
+ ids
+ .iter()
+ .filter_map(|id| self.get(*id))
+ .map(|m| match m {
+ MatchVariant::User(m) => MatchSummary {
+ id: m.id,
+ label: m.description(),
+ tag: m.cause_description(),
+ },
+ MatchVariant::Builtin(m) => MatchSummary {
+ id: m.id,
+ label: m.label,
+ tag: m.triggers.first().map(String::as_ref),
+ },
+ })
+ .collect()
+ }
+}
+
+impl<'a> super::engine::process::middleware::multiplex::MatchProvider<'a>
+ for CombinedMatchCache<'a>
+{
+ fn get(
+ &self,
+ match_id: i32,
+ ) -> Option> {
+ Some(match self.get(match_id)? {
+ MatchVariant::User(m) => super::engine::process::middleware::multiplex::MatchResult::User(m),
+ MatchVariant::Builtin(m) => {
+ super::engine::process::middleware::multiplex::MatchResult::Builtin(m)
+ }
+ })
+ }
+}
diff --git a/espanso/src/cli/worker/mod.rs b/espanso/src/cli/worker/mod.rs
index ce7f6aa..efee5d9 100644
--- a/espanso/src/cli/worker/mod.rs
+++ b/espanso/src/cli/worker/mod.rs
@@ -33,7 +33,9 @@ use self::ui::util::convert_icon_paths_to_tray_vec;
use super::{CliModule, CliModuleArgs};
+mod builtin;
mod config;
+mod context;
mod daemon_monitor;
mod engine;
mod ipc;