feat(core): implement image matches

This commit is contained in:
Federico Terzi 2021-05-01 19:41:22 +02:00
parent 7a8e39fdad
commit ddd62b225f
16 changed files with 277 additions and 30 deletions

View File

@ -17,10 +17,12 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use std::path::PathBuf;
use espanso_inject::{Injector, keys::Key};
use espanso_clipboard::Clipboard;
use crate::engine::{dispatch::TextInjector, dispatch::HtmlInjector};
use crate::engine::{dispatch::HtmlInjector, dispatch::{ImageInjector, TextInjector}};
pub struct ClipboardInjectorAdapter<'a> {
injector: &'a dyn Injector,
@ -71,3 +73,19 @@ impl <'a> HtmlInjector for ClipboardInjectorAdapter<'a> {
Ok(())
}
}
impl <'a> ImageInjector for ClipboardInjectorAdapter<'a> {
fn inject_image(&self, image_path: &str) -> anyhow::Result<()> {
let path = PathBuf::from(image_path);
if !path.is_file() {
return Err(std::io::Error::new(std::io::ErrorKind::NotFound, "image can't be found in the given path").into());
}
// TODO: handle clipboard restoration
self.clipboard.set_image(&path)?;
self.send_paste_combination()?;
Ok(())
}
}

View File

@ -22,12 +22,15 @@ use espanso_config::{config::ConfigStore, matches::store::MatchStore};
use espanso_path::Paths;
use ui::selector::MatchSelectorAdapter;
use crate::cli::worker::engine::path::PathProviderAdapter;
use super::ui::icon::IconPaths;
pub mod executor;
pub mod match_cache;
pub mod matcher;
pub mod multiplex;
pub mod path;
pub mod render;
pub mod source;
pub mod ui;
@ -99,6 +102,7 @@ pub fn initialize_and_spawn(
]);
let renderer_adapter =
super::engine::render::RendererAdapter::new(&match_cache, &config_manager, &renderer);
let path_provider = PathProviderAdapter::new(&paths);
let mut processor = crate::engine::process::default(
&matchers,
@ -109,6 +113,7 @@ pub fn initialize_and_spawn(
&match_cache,
&modifier_state_store,
&sequencer,
&path_provider,
);
let event_injector =
@ -125,6 +130,7 @@ pub fn initialize_and_spawn(
&config_manager,
&key_injector,
&clipboard_injector,
&clipboard_injector,
);
let mut engine = crate::engine::Engine::new(&funnel, &mut processor, &dispatcher);

View File

@ -19,14 +19,7 @@
use espanso_config::matches::{Match, MatchEffect};
use crate::engine::{
event::{
internal::DetectedMatch,
internal::{RenderingRequestedEvent, TextFormat},
EventType,
},
process::Multiplexer,
};
use crate::engine::{event::{EventType, internal::DetectedMatch, internal::{ImageRequestedEvent, RenderingRequestedEvent, TextFormat}}, process::Multiplexer};
pub trait MatchProvider<'a> {
fn get(&self, match_id: i32) -> Option<&'a Match>;
@ -55,7 +48,10 @@ impl<'a> Multiplexer for MultiplexAdapter<'a> {
trigger_args: detected_match.args,
format: convert_format(&effect.format),
})),
// TODO: think about image
MatchEffect::Image(effect) => Some(EventType::ImageRequested(ImageRequestedEvent {
match_id: detected_match.id,
image_path: effect.path.clone(),
})),
MatchEffect::None => None,
}
}

View File

@ -0,0 +1,40 @@
/*
* 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 espanso_path::Paths;
use crate::engine::process::PathProvider;
pub struct PathProviderAdapter<'a> {
paths: &'a Paths,
}
impl <'a> PathProviderAdapter<'a> {
pub fn new(paths: &'a Paths) -> Self {
Self {
paths,
}
}
}
impl <'a> PathProvider for PathProviderAdapter<'a> {
fn get_config_path(&self) -> &std::path::Path {
&self.paths.config
}
}

View File

@ -17,7 +17,7 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use super::Event;
use super::{Event, ImageInjector};
use super::{ModeProvider, Dispatcher, Executor, KeyInjector, TextInjector, HtmlInjector};
pub struct DefaultDispatcher<'a> {
@ -31,6 +31,7 @@ impl<'a> DefaultDispatcher<'a> {
mode_provider: &'a dyn ModeProvider,
key_injector: &'a dyn KeyInjector,
html_injector: &'a dyn HtmlInjector,
image_injector: &'a dyn ImageInjector,
) -> Self {
Self {
executors: vec![
@ -44,6 +45,9 @@ impl<'a> DefaultDispatcher<'a> {
)),
Box::new(super::executor::html_inject::HtmlInjectExecutor::new(
html_injector,
)),
Box::new(super::executor::image_inject::ImageInjectExecutor::new(
image_injector,
))
],
}

View File

@ -0,0 +1,56 @@
/*
* 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::super::{Event, Executor};
use crate::engine::event::EventType;
use anyhow::Result;
use log::error;
pub trait ImageInjector {
fn inject_image(&self, path: &str) -> Result<()>;
}
pub struct ImageInjectExecutor<'a> {
injector: &'a dyn ImageInjector,
}
impl<'a> ImageInjectExecutor<'a> {
pub fn new(injector: &'a dyn ImageInjector) -> Self {
Self { injector }
}
}
impl<'a> Executor for ImageInjectExecutor<'a> {
fn execute(&self, event: &Event) -> bool {
if let EventType::ImageInject(inject_event) = &event.etype {
if let Err(error) = self
.injector
.inject_image(&inject_event.image_path)
{
error!("image injector reported an error: {:?}", error);
}
return true;
}
false
}
}
// TODO: test

View File

@ -20,3 +20,4 @@
pub mod text_inject;
pub mod key_inject;
pub mod html_inject;
pub mod image_inject;

View File

@ -35,6 +35,7 @@ pub trait Dispatcher {
// Re-export dependency injection entities
pub use executor::html_inject::HtmlInjector;
pub use executor::text_inject::{Mode, ModeProvider, TextInjector};
pub use executor::image_inject::{ImageInjector};
// TODO: move into module
pub trait KeyInjector {
@ -47,6 +48,7 @@ pub fn default<'a>(
mode_provider: &'a dyn ModeProvider,
key_injector: &'a dyn KeyInjector,
html_injector: &'a dyn HtmlInjector,
image_injector: &'a dyn ImageInjector,
) -> impl Dispatcher + 'a {
default::DefaultDispatcher::new(
event_injector,
@ -54,5 +56,6 @@ pub fn default<'a>(
mode_provider,
key_injector,
html_injector,
image_injector,
)
}

View File

@ -56,3 +56,8 @@ pub enum TextInjectMode {
pub struct KeySequenceInjectRequest {
pub keys: Vec<Key>,
}
#[derive(Debug, Clone)]
pub struct ImageInjectRequest {
pub image_path: String,
}

View File

@ -60,6 +60,17 @@ pub enum TextFormat {
Html,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ImageRequestedEvent {
pub match_id: i32,
pub image_path: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ImageResolvedEvent {
pub image_path: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RenderedEvent {
pub match_id: i32,

View File

@ -59,7 +59,9 @@ pub enum EventType {
CauseCompensatedMatch(internal::CauseCompensatedMatchEvent),
RenderingRequested(internal::RenderingRequestedEvent),
ImageRequested(internal::ImageRequestedEvent),
Rendered(internal::RenderedEvent),
ImageResolved(internal::ImageResolvedEvent),
MatchInjected,
DiscardPrevious(internal::DiscardPreviousEvent),
@ -71,4 +73,5 @@ pub enum EventType {
TextInject(effect::TextInjectRequest),
MarkdownInject(effect::MarkdownInjectRequest),
HtmlInject(effect::HtmlInjectRequest),
ImageInject(effect::ImageInjectRequest),
}

View File

@ -19,13 +19,13 @@
use log::trace;
use super::{MatchFilter, MatchInfoProvider, MatchSelector, Matcher, Middleware, Multiplexer, Processor, Renderer, middleware::{
use super::{MatchFilter, MatchInfoProvider, MatchSelector, Matcher, Middleware, Multiplexer, PathProvider, Processor, Renderer, middleware::{
match_select::MatchSelectMiddleware, matcher::MatcherMiddleware, multiplex::MultiplexMiddleware,
render::RenderMiddleware, action::{ActionMiddleware, EventSequenceProvider}, cursor_hint::CursorHintMiddleware, cause::CauseCompensateMiddleware,
delay_modifiers::{DelayForModifierReleaseMiddleware, ModifierStatusProvider}, markdown::MarkdownMiddleware,
past_discard::PastEventsDiscardMiddleware,
}};
use crate::engine::event::{Event, EventType};
use crate::engine::{event::{Event, EventType}, process::middleware::image_resolve::ImageResolverMiddleware};
use std::collections::VecDeque;
pub struct DefaultProcessor<'a> {
@ -43,6 +43,7 @@ impl<'a> DefaultProcessor<'a> {
match_info_provider: &'a dyn MatchInfoProvider,
modifier_status_provider: &'a dyn ModifierStatusProvider,
event_sequence_provider: &'a dyn EventSequenceProvider,
path_provider: &'a dyn PathProvider,
) -> DefaultProcessor<'a> {
Self {
event_queue: VecDeque::new(),
@ -53,6 +54,7 @@ impl<'a> DefaultProcessor<'a> {
Box::new(CauseCompensateMiddleware::new()),
Box::new(MultiplexMiddleware::new(multiplexer)),
Box::new(RenderMiddleware::new(renderer)),
Box::new(ImageResolverMiddleware::new(path_provider)),
Box::new(CursorHintMiddleware::new()),
Box::new(ActionMiddleware::new(match_info_provider, event_sequence_provider)),
Box::new(MarkdownMiddleware::new()),

View File

@ -18,7 +18,15 @@
*/
use super::super::Middleware;
use crate::engine::event::{Event, EventType, effect::{HtmlInjectRequest, KeySequenceInjectRequest, MarkdownInjectRequest, TextInjectMode, TextInjectRequest}, input::Key, internal::{DiscardPreviousEvent, TextFormat}};
use crate::engine::event::{
effect::{
HtmlInjectRequest, ImageInjectRequest, KeySequenceInjectRequest, MarkdownInjectRequest,
TextInjectMode, TextInjectRequest,
},
input::Key,
internal::{DiscardPreviousEvent, TextFormat},
Event, EventType,
};
pub trait MatchInfoProvider {
fn get_force_mode(&self, match_id: i32) -> Option<TextInjectMode>;
@ -52,7 +60,7 @@ impl<'a> Middleware for ActionMiddleware<'a> {
fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event {
match &event.etype {
EventType::Rendered(m_event) => {
EventType::Rendered(_) | EventType::ImageResolved(_) => {
dispatch(Event::caused_by(event.source_id, EventType::MatchInjected));
dispatch(Event::caused_by(
event.source_id,
@ -61,7 +69,8 @@ impl<'a> Middleware for ActionMiddleware<'a> {
}),
));
Event::caused_by(
match &event.etype {
EventType::Rendered(m_event) => Event::caused_by(
event.source_id,
match m_event.format {
TextFormat::Plain => EventType::TextInject(TextInjectRequest {
@ -75,7 +84,15 @@ impl<'a> Middleware for ActionMiddleware<'a> {
markdown: m_event.body.clone(),
}),
},
)
),
EventType::ImageResolved(m_event) => Event::caused_by(
event.source_id,
EventType::ImageInject(ImageInjectRequest {
image_path: m_event.image_path.clone(),
}),
),
_ => unreachable!()
}
}
EventType::CursorHintCompensation(m_event) => {
dispatch(Event::caused_by(

View File

@ -0,0 +1,81 @@
/*
* 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::{
path::Path,
};
use log::{error};
use super::super::Middleware;
use crate::engine::event::{Event, EventType, internal::ImageResolvedEvent};
pub trait PathProvider {
fn get_config_path(&self) -> &Path;
}
pub struct ImageResolverMiddleware<'a> {
provider: &'a dyn PathProvider,
}
impl<'a> ImageResolverMiddleware<'a> {
pub fn new(provider: &'a dyn PathProvider) -> Self {
Self { provider }
}
}
impl<'a> Middleware for ImageResolverMiddleware<'a> {
fn name(&self) -> &'static str {
"image_resolve"
}
fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
if let EventType::ImageRequested(m_event) = &event.etype {
// On Windows, we have to replace the forward / with the backslash \ in the path
let path = if cfg!(target_os = "windows") {
m_event.image_path.replace("/", "\\")
} else {
m_event.image_path.to_owned()
};
let path = if path.contains("$CONFIG") {
let config_path = match self.provider.get_config_path().canonicalize() {
Ok(path) => path,
Err(err) => {
error!("unable to canonicalize the config path into the image resolver: {}", err);
self.provider.get_config_path().to_owned()
},
};
path.replace("$CONFIG", &config_path.to_string_lossy())
} else {
path
};
return Event::caused_by(event.source_id, EventType::ImageResolved(ImageResolvedEvent {
image_path: path,
}))
}
event
}
}
// TODO: test

View File

@ -21,6 +21,7 @@ pub mod action;
pub mod cause;
pub mod cursor_hint;
pub mod delay_modifiers;
pub mod image_resolve;
pub mod match_select;
pub mod matcher;
pub mod markdown;

View File

@ -94,6 +94,7 @@ pub enum RendererError {
pub use middleware::action::{MatchInfoProvider, EventSequenceProvider};
pub use middleware::delay_modifiers::ModifierStatusProvider;
pub use middleware::image_resolve::PathProvider;
pub fn default<'a, MatcherState>(
matchers: &'a [&'a dyn Matcher<'a, MatcherState>],
@ -104,6 +105,7 @@ pub fn default<'a, MatcherState>(
match_info_provider: &'a dyn MatchInfoProvider,
modifier_status_provider: &'a dyn ModifierStatusProvider,
event_sequence_provider: &'a dyn EventSequenceProvider,
path_provider: &'a dyn PathProvider,
) -> impl Processor + 'a {
default::DefaultProcessor::new(
matchers,
@ -114,5 +116,6 @@ pub fn default<'a, MatcherState>(
match_info_provider,
modifier_status_provider,
event_sequence_provider,
path_provider,
)
}