feat(core): first draft of builtin matches

This commit is contained in:
Federico Terzi 2021-07-31 21:19:50 +02:00
parent 90db84b92f
commit 48d05a3f32
11 changed files with 348 additions and 59 deletions

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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,
})
},
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String>,
pub action: fn(context: &dyn Context) -> EventType,
}
pub fn get_builtin_matches() -> Vec<BuiltInMatch> {
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<i32> = 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
})
}

View File

@ -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<dyn Config>{
pub fn default(&self) -> Arc<dyn Config> {
self.config_store.default()
}
}
@ -76,12 +78,22 @@ impl<'a> crate::engine::process::MatchFilter for ConfigManager<'a> {
let ids_set: HashSet<i32> = HashSet::from_iter(matches_ids.iter().copied());
let (_, match_set) = self.active_context();
match_set
let active_user_defined_matches: Vec<i32> = match_set
.matches
.iter()
.filter(|m| ids_set.contains(&m.id))
.map(|m| m.id)
.collect()
.collect();
let builtin_matches: Vec<i32> = 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
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<dyn Config> {
self.config_manager.default()
}
fn get_active_config(&self) -> Arc<dyn Config> {
self.config_manager.active()
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<dyn Config>;
fn get_active_config(&self) -> Arc<dyn Config>;
}

View File

@ -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<dyn Context> = 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, &regex_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,

View File

@ -17,7 +17,6 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
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<MatchSummary<'a>>;
}
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<i32> {
let matches = self.match_provider.get_matches(&matches_ids);
let search_items: Vec<SearchItem> = 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();

View File

@ -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))
}
}

View File

@ -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<MatchResult<'a>>;
}
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<EventType> {
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))
}
}
}
}

View File

@ -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<i32, &'a Match>,
}
@ -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<crate::engine::event::effect::TextInjectMode> {
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<i32, &'a BuiltInMatch>,
}
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<MatchVariant<'a>> {
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<MatchSummary<'a>> {
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<super::engine::process::middleware::multiplex::MatchResult<'a>> {
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)
}
})
}
}

View File

@ -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;