feat(core): implement image matches
This commit is contained in:
parent
7a8e39fdad
commit
ddd62b225f
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
40
espanso/src/cli/worker/engine/path.rs
Normal file
40
espanso/src/cli/worker/engine/path.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
))
|
||||
],
|
||||
}
|
||||
|
|
56
espanso/src/engine/dispatch/executor/image_inject.rs
Normal file
56
espanso/src/engine/dispatch/executor/image_inject.rs
Normal 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
|
|
@ -20,3 +20,4 @@
|
|||
pub mod text_inject;
|
||||
pub mod key_inject;
|
||||
pub mod html_inject;
|
||||
pub mod image_inject;
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -56,3 +56,8 @@ pub enum TextInjectMode {
|
|||
pub struct KeySequenceInjectRequest {
|
||||
pub keys: Vec<Key>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImageInjectRequest {
|
||||
pub image_path: String,
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
}
|
|
@ -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()),
|
||||
|
|
|
@ -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(
|
||||
|
|
81
espanso/src/engine/process/middleware/image_resolve.rs
Normal file
81
espanso/src/engine/process/middleware/image_resolve.rs
Normal 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
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user