feat(core): improve basic core structure

This commit is contained in:
Federico Terzi 2021-04-03 13:55:21 +02:00
parent a4958ff352
commit c0de39fdd0
19 changed files with 459 additions and 70 deletions

1
Cargo.lock generated
View File

@ -288,6 +288,7 @@ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
"crossbeam", "crossbeam",
"enum-as-inner",
"espanso-clipboard", "espanso-clipboard",
"espanso-config", "espanso-config",
"espanso-detect", "espanso-detect",

View File

@ -28,9 +28,9 @@ mod util;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct MatchResult<Id> { pub struct MatchResult<Id> {
id: Id, pub id: Id,
trigger: String, pub trigger: String,
vars: HashMap<String, String>, pub vars: HashMap<String, String>,
} }
impl<Id: Default> Default for MatchResult<Id> { impl<Id: Default> Default for MatchResult<Id> {

View File

@ -29,4 +29,5 @@ anyhow = "1.0.38"
thiserror = "1.0.23" thiserror = "1.0.23"
clap = "2.33.3" clap = "2.33.3"
lazy_static = "1.4.0" lazy_static = "1.4.0"
crossbeam = "0.8.0" crossbeam = "0.8.0"
enum-as-inner = "0.3.3"

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
pub mod text_injector;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
use crate::engine::dispatch::TextInjector;
pub struct TextInjectorAdapter {}
impl TextInjectorAdapter {
pub fn new() -> Self {
Self {}
}
}
impl TextInjector for TextInjectorAdapter {
fn inject(&self, text: &str) -> anyhow::Result<()> {
// TODO: implement
println!("INJECT: {}", text);
Ok(())
}
}

View File

@ -0,0 +1,30 @@
use espanso_match::rolling::matcher::RollingMatcherState;
/*
* 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 enum_as_inner::EnumAsInner;
pub mod rolling;
#[derive(Clone, EnumAsInner)]
pub enum MatcherState<'a> {
Rolling(RollingMatcherState<'a, i32>),
// TODO: regex
}

View File

@ -0,0 +1,140 @@
/*
* 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_match::rolling::{matcher::RollingMatcher, RollingMatch};
use crate::engine::{
event::keyboard::Key,
process::{MatchResult, Matcher, MatcherEvent},
};
use super::MatcherState;
pub struct RollingMatcherAdapter {
matcher: RollingMatcher<i32>,
}
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());
Self { matcher }
}
}
impl <'a> Matcher<'a, MatcherState<'a>> for RollingMatcherAdapter {
fn process(
&'a self,
prev_state: Option<&MatcherState<'a>>,
event: &MatcherEvent,
) -> (MatcherState<'a>, Vec<MatchResult>) {
use espanso_match::Matcher;
let prev_state = prev_state.map(|state| {
if let Some(state) = state.as_rolling() {
state
} else {
panic!("invalid state type received in RollingMatcherAdapter")
}
});
let event = event.into();
let (state, results) = self.matcher.process(prev_state, event);
let enum_state = MatcherState::Rolling(state);
let results: Vec<MatchResult> = results.into_iter().map(|result| result.into()).collect();
(enum_state, results)
}
}
impl From<&MatcherEvent> for espanso_match::event::Event {
fn from(event: &MatcherEvent) -> Self {
match event {
MatcherEvent::Key { key, chars } => espanso_match::event::Event::Key {
key: key.clone().into(),
chars: chars.to_owned(),
},
MatcherEvent::VirtualSeparator => espanso_match::event::Event::VirtualSeparator,
}
}
}
impl From<espanso_match::MatchResult<i32>> for MatchResult {
fn from(result: espanso_match::MatchResult<i32>) -> Self {
Self {
id: result.id,
trigger: result.trigger,
vars: result.vars,
}
}
}
impl From<Key> for espanso_match::event::Key {
fn from(key: Key) -> Self {
match key {
Key::Alt => espanso_match::event::Key::Alt,
Key::CapsLock => espanso_match::event::Key::CapsLock,
Key::Control => espanso_match::event::Key::Control,
Key::Meta => espanso_match::event::Key::Meta,
Key::NumLock => espanso_match::event::Key::NumLock,
Key::Shift => espanso_match::event::Key::Shift,
Key::Enter => espanso_match::event::Key::Enter,
Key::Tab => espanso_match::event::Key::Tab,
Key::Space => espanso_match::event::Key::Space,
Key::ArrowDown => espanso_match::event::Key::ArrowDown,
Key::ArrowLeft => espanso_match::event::Key::ArrowLeft,
Key::ArrowRight => espanso_match::event::Key::ArrowRight,
Key::ArrowUp => espanso_match::event::Key::ArrowUp,
Key::End => espanso_match::event::Key::End,
Key::Home => espanso_match::event::Key::Home,
Key::PageDown => espanso_match::event::Key::PageDown,
Key::PageUp => espanso_match::event::Key::PageUp,
Key::Escape => espanso_match::event::Key::Escape,
Key::Backspace => espanso_match::event::Key::Backspace,
Key::F1 => espanso_match::event::Key::F1,
Key::F2 => espanso_match::event::Key::F2,
Key::F3 => espanso_match::event::Key::F3,
Key::F4 => espanso_match::event::Key::F4,
Key::F5 => espanso_match::event::Key::F5,
Key::F6 => espanso_match::event::Key::F6,
Key::F7 => espanso_match::event::Key::F7,
Key::F8 => espanso_match::event::Key::F8,
Key::F9 => espanso_match::event::Key::F9,
Key::F10 => espanso_match::event::Key::F10,
Key::F11 => espanso_match::event::Key::F11,
Key::F12 => espanso_match::event::Key::F12,
Key::F13 => espanso_match::event::Key::F13,
Key::F14 => espanso_match::event::Key::F14,
Key::F15 => espanso_match::event::Key::F15,
Key::F16 => espanso_match::event::Key::F16,
Key::F17 => espanso_match::event::Key::F17,
Key::F18 => espanso_match::event::Key::F18,
Key::F19 => espanso_match::event::Key::F19,
Key::F20 => espanso_match::event::Key::F20,
Key::Other(_) => espanso_match::event::Key::Other,
}
}
}

View File

@ -17,10 +17,15 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use crate::engine::{Engine, funnel}; use funnel::Source;
use process::Matcher;
use crate::engine::{Engine, funnel, process, dispatch};
use super::{CliModule, CliModuleArgs}; use super::{CliModule, CliModuleArgs};
mod source; mod source;
mod matcher;
mod executor;
pub fn new() -> CliModule { pub fn new() -> CliModule {
#[allow(clippy::needless_update)] #[allow(clippy::needless_update)]
@ -35,10 +40,18 @@ pub fn new() -> CliModule {
} }
fn worker_main(args: CliModuleArgs) { fn worker_main(args: CliModuleArgs) {
let funnel = funnel::default(vec![ let detect_source = source::detect::init_and_spawn().unwrap(); // TODO: handle error
Box::new(), let sources: Vec<&dyn Source> = vec![&detect_source];
]); let funnel = funnel::default(&sources);
let engine = Engine::new(funnel); let matcher = matcher::rolling::RollingMatcherAdapter::new();
let matchers: Vec<&dyn Matcher<matcher::MatcherState>> = vec![&matcher];
let mut processor = process::default(&matchers);
let text_injector = executor::text_injector::TextInjectorAdapter::new();
let dispatcher = dispatch::default(&text_injector);
let mut engine = Engine::new(&funnel, &mut processor, &dispatcher);
engine.run();
} }

View File

@ -17,25 +17,166 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use anyhow::Result;
use crossbeam::channel::{Receiver, Select, SelectedOperation}; use crossbeam::channel::{Receiver, Select, SelectedOperation};
use espanso_detect::event::InputEvent; use espanso_detect::{event::InputEvent, Source};
use log::{error, trace};
use crate::engine::{event::Event, funnel::Source}; use crate::engine::{
event::{
keyboard::{Key, KeyboardEvent, Status, Variant},
Event,
},
funnel, process,
};
use thiserror::Error;
pub struct DetectorSource { pub struct DetectSource {
receiver: Receiver<InputEvent>, receiver: Receiver<InputEvent>,
} }
impl Source for DetectorSource { impl<'a> funnel::Source<'a> for DetectSource {
fn register(&self, select: &Select) -> i32 { fn register(&'a self, select: &mut Select<'a>) -> usize {
todo!() select.recv(&self.receiver)
} }
fn receive(&self, select: &SelectedOperation) -> Event { fn receive(&self, op: SelectedOperation) -> Event {
todo!() let input_event = op
.recv(&self.receiver)
.expect("unable to select data from DetectSource receiver");
match input_event {
InputEvent::Keyboard(keyboard_event) => Event::Keyboard(KeyboardEvent {
key: keyboard_event.key.into(),
value: keyboard_event.value,
status: keyboard_event.status.into(),
variant: keyboard_event.variant.map(|variant| variant.into()),
}),
InputEvent::Mouse(_) => todo!(), // TODO
InputEvent::HotKey(_) => todo!(), // TODO
}
} }
} }
pub fn new() { // TODO: pass options
todo!() pub fn init_and_spawn() -> Result<DetectSource> {
} let (sender, receiver) = crossbeam::channel::unbounded();
let (init_tx, init_rx) = crossbeam::channel::unbounded();
if let Err(error) = std::thread::Builder::new()
.name("detect thread".to_string())
.spawn(
move || match espanso_detect::get_source(Default::default()) {
Ok(mut source) => {
if source.initialize().is_err() {
init_tx
.send(false)
.expect("unable to send to the init_tx channel");
} else {
init_tx
.send(true)
.expect("unable to send to the init_tx channel");
source.eventloop(Box::new(move |event| {
sender
.send(event)
.expect("unable to send to the source channel");
})).expect("detect eventloop crashed");
}
}
Err(error) => {
error!("cannot initialize event source: {:?}", error);
init_tx
.send(false)
.expect("unable to send to the init_tx channel");
}
},
)
{
error!("detection thread initialization failed: {:?}", error);
return Err(DetectSourceError::ThreadInitFailed.into());
}
// Wait for the initialization status
let has_initialized = init_rx
.recv()
.expect("unable to receive from the init_rx channel");
if !has_initialized {
return Err(DetectSourceError::InitFailed.into());
}
Ok(DetectSource { receiver })
}
#[derive(Error, Debug)]
pub enum DetectSourceError {
#[error("detection thread initialization failed")]
ThreadInitFailed,
#[error("detection source initialization failed")]
InitFailed,
}
impl From<espanso_detect::event::Key> for Key {
fn from(key: espanso_detect::event::Key) -> Self {
match key {
espanso_detect::event::Key::Alt => Key::Alt,
espanso_detect::event::Key::CapsLock => Key::CapsLock,
espanso_detect::event::Key::Control => Key::Control,
espanso_detect::event::Key::Meta => Key::Meta,
espanso_detect::event::Key::NumLock => Key::NumLock,
espanso_detect::event::Key::Shift => Key::Shift,
espanso_detect::event::Key::Enter => Key::Enter,
espanso_detect::event::Key::Tab => Key::Tab,
espanso_detect::event::Key::Space => Key::Space,
espanso_detect::event::Key::ArrowDown => Key::ArrowDown,
espanso_detect::event::Key::ArrowLeft => Key::ArrowLeft,
espanso_detect::event::Key::ArrowRight => Key::ArrowRight,
espanso_detect::event::Key::ArrowUp => Key::ArrowUp,
espanso_detect::event::Key::End => Key::End,
espanso_detect::event::Key::Home => Key::Home,
espanso_detect::event::Key::PageDown => Key::PageDown,
espanso_detect::event::Key::PageUp => Key::PageUp,
espanso_detect::event::Key::Escape => Key::Escape,
espanso_detect::event::Key::Backspace => Key::Backspace,
espanso_detect::event::Key::F1 => Key::F1,
espanso_detect::event::Key::F2 => Key::F2,
espanso_detect::event::Key::F3 => Key::F3,
espanso_detect::event::Key::F4 => Key::F4,
espanso_detect::event::Key::F5 => Key::F5,
espanso_detect::event::Key::F6 => Key::F6,
espanso_detect::event::Key::F7 => Key::F7,
espanso_detect::event::Key::F8 => Key::F8,
espanso_detect::event::Key::F9 => Key::F9,
espanso_detect::event::Key::F10 => Key::F10,
espanso_detect::event::Key::F11 => Key::F11,
espanso_detect::event::Key::F12 => Key::F12,
espanso_detect::event::Key::F13 => Key::F13,
espanso_detect::event::Key::F14 => Key::F14,
espanso_detect::event::Key::F15 => Key::F15,
espanso_detect::event::Key::F16 => Key::F16,
espanso_detect::event::Key::F17 => Key::F17,
espanso_detect::event::Key::F18 => Key::F18,
espanso_detect::event::Key::F19 => Key::F19,
espanso_detect::event::Key::F20 => Key::F20,
espanso_detect::event::Key::Other(code) => Key::Other(code),
}
}
}
impl From<espanso_detect::event::Variant> for Variant {
fn from(variant: espanso_detect::event::Variant) -> Self {
match variant {
espanso_detect::event::Variant::Left => Variant::Left,
espanso_detect::event::Variant::Right => Variant::Right,
}
}
}
impl From<espanso_detect::event::Status> for Status {
fn from(status: espanso_detect::event::Status) -> Self {
match status {
espanso_detect::event::Status::Pressed => Status::Pressed,
espanso_detect::event::Status::Released => Status::Released,
}
}
}

View File

@ -20,12 +20,12 @@
use super::{Dispatcher, Executor, TextInjector}; use super::{Dispatcher, Executor, TextInjector};
use super::Event; use super::Event;
pub struct DefaultDispatcher { pub struct DefaultDispatcher<'a> {
executors: Vec<Box<dyn Executor>>, executors: Vec<Box<dyn Executor + 'a>>,
} }
impl DefaultDispatcher { impl <'a> DefaultDispatcher<'a> {
pub fn new(text_injector: impl TextInjector + 'static) -> Self { pub fn new(text_injector: &'a dyn TextInjector) -> Self {
Self { Self {
executors: vec![ executors: vec![
Box::new(super::executor::text_inject::TextInjectExecutor::new(text_injector)), Box::new(super::executor::text_inject::TextInjectExecutor::new(text_injector)),
@ -34,7 +34,7 @@ impl DefaultDispatcher {
} }
} }
impl Dispatcher for DefaultDispatcher { impl <'a> Dispatcher for DefaultDispatcher<'a> {
fn dispatch(&self, event: Event) { fn dispatch(&self, event: Event) {
for executor in self.executors.iter() { for executor in self.executors.iter() {
if executor.execute(&event) { if executor.execute(&event) {

View File

@ -21,17 +21,17 @@ use super::super::{Event, Executor, TextInjector};
use crate::engine::event::inject::TextInjectMode; use crate::engine::event::inject::TextInjectMode;
use log::error; use log::error;
pub struct TextInjectExecutor<T: TextInjector> { pub struct TextInjectExecutor<'a> {
injector: T, injector: &'a dyn TextInjector,
} }
impl<T: TextInjector> TextInjectExecutor<T> { impl<'a> TextInjectExecutor<'a> {
pub fn new(injector: T) -> Self { pub fn new(injector: &'a dyn TextInjector) -> Self {
Self { injector } Self { injector }
} }
} }
impl<T: TextInjector> Executor for TextInjectExecutor<T> { impl<'a> Executor for TextInjectExecutor<'a> {
fn execute(&self, event: &Event) -> bool { fn execute(&self, event: &Event) -> bool {
if let Event::TextInject(inject_event) = event { if let Event::TextInject(inject_event) = event {
if inject_event.mode == TextInjectMode::Keys { if inject_event.mode == TextInjectMode::Keys {

View File

@ -35,7 +35,7 @@ pub trait TextInjector {
fn inject(&self, text: &str) -> Result<()>; fn inject(&self, text: &str) -> Result<()>;
} }
pub fn default(text_injector: impl TextInjector + 'static) -> impl Dispatcher { pub fn default<'a>(text_injector: &'a dyn TextInjector) -> impl Dispatcher + 'a {
default::DefaultDispatcher::new( default::DefaultDispatcher::new(
text_injector, text_injector,
) )

View File

@ -21,25 +21,25 @@ use crossbeam::channel::Select;
use super::{Funnel, FunnelResult, Source}; use super::{Funnel, FunnelResult, Source};
pub struct DefaultFunnel { pub struct DefaultFunnel<'a> {
sources: Vec<Box<dyn Source>>, sources: &'a [&'a dyn Source<'a>],
} }
impl DefaultFunnel { impl <'a> DefaultFunnel<'a> {
pub fn new(sources: Vec<Box<dyn Source>>) -> Self { pub fn new(sources: &'a [&'a dyn Source<'a>]) -> Self {
Self { Self {
sources sources
} }
} }
} }
impl Funnel for DefaultFunnel { impl <'a> Funnel for DefaultFunnel<'a> {
fn receive(&self) -> FunnelResult { fn receive(&self) -> FunnelResult {
let mut select = Select::new(); let mut select = Select::new();
// First register all the sources to the select operation // First register all the sources to the select operation
for source in self.sources.iter() { for source in self.sources.iter() {
source.register(&select); source.register(&mut select);
} }
// Wait for the first source (blocking operation) // Wait for the first source (blocking operation)
@ -50,7 +50,7 @@ impl Funnel for DefaultFunnel {
.expect("invalid source index returned by select operation"); .expect("invalid source index returned by select operation");
// Receive (and convert) the event // Receive (and convert) the event
let event = source.receive(&op); let event = source.receive(op);
FunnelResult::Event(event) FunnelResult::Event(event)
} }
} }

View File

@ -25,9 +25,9 @@ use super::Event;
mod default; mod default;
pub trait Source { pub trait Source<'a> {
fn register(&self, select: &Select) -> i32; fn register<'b>(&'a self, select: &mut Select<'a>) -> usize;
fn receive(&self, select: &SelectedOperation) -> Event; fn receive<'b>(&'a self, op: SelectedOperation) -> Event;
} }
pub trait Funnel { pub trait Funnel {
@ -39,6 +39,6 @@ pub enum FunnelResult {
EndOfStream, EndOfStream,
} }
pub fn default(sources: Vec<Box<dyn Source>>) -> impl Funnel { pub fn default<'a>(sources: &'a [&'a dyn Source<'a>]) -> impl Funnel + 'a {
DefaultFunnel::new(sources) DefaultFunnel::new(sources)
} }

View File

@ -18,7 +18,6 @@
*/ */
use log::{debug, trace}; use log::{debug, trace};
use std::collections::VecDeque;
use self::{ use self::{
dispatch::Dispatcher, dispatch::Dispatcher,
@ -32,14 +31,14 @@ pub mod event;
pub mod process; pub mod process;
pub mod funnel; pub mod funnel;
pub struct Engine<TFunnel: Funnel, TProcessor: Processor, TDispatcher: Dispatcher> { pub struct Engine<'a> {
funnel: TFunnel, funnel: &'a dyn Funnel,
processor: TProcessor, processor: &'a mut dyn Processor,
dispatcher: TDispatcher, dispatcher: &'a dyn Dispatcher,
} }
impl <TFunnel: Funnel, TProcessor: Processor, TDispatcher: Dispatcher> Engine<TFunnel, TProcessor, TDispatcher> { impl <'a> Engine<'a> {
pub fn new(funnel: TFunnel, processor: TProcessor, dispatcher: TDispatcher) -> Self { pub fn new(funnel: &'a dyn Funnel, processor: &'a mut dyn Processor, dispatcher: &'a dyn Dispatcher) -> Self {
Self { Self {
funnel, funnel,
processor, processor,
@ -51,8 +50,6 @@ impl <TFunnel: Funnel, TProcessor: Processor, TDispatcher: Dispatcher> Engine<TF
loop { loop {
match self.funnel.receive() { match self.funnel.receive() {
FunnelResult::Event(event) => { FunnelResult::Event(event) => {
trace!("received event from stream: {:?}", event);
let processed_events = self.processor.process(event); let processed_events = self.processor.process(event);
for event in processed_events { for event in processed_events {
self.dispatcher.dispatch(event); self.dispatcher.dispatch(event);

View File

@ -17,16 +17,18 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use log::trace;
use super::{Event, Matcher, Middleware, Processor, middleware::matcher::MatchMiddleware}; use super::{Event, Matcher, Middleware, Processor, middleware::matcher::MatchMiddleware};
use std::collections::VecDeque; use std::collections::VecDeque;
pub struct DefaultProcessor { pub struct DefaultProcessor<'a> {
event_queue: VecDeque<Event>, event_queue: VecDeque<Event>,
middleware: Vec<Box<dyn Middleware>>, middleware: Vec<Box<dyn Middleware + 'a>>,
} }
impl DefaultProcessor { impl <'a> DefaultProcessor<'a> {
pub fn new<MatcherState: 'static>(matchers: Vec<Box<dyn Matcher<MatcherState>>>) -> Self { pub fn new<MatcherState>(matchers: &'a [&'a dyn Matcher<'a, MatcherState>]) -> DefaultProcessor<'a> {
Self { Self {
event_queue: VecDeque::new(), event_queue: VecDeque::new(),
middleware: vec![ middleware: vec![
@ -41,13 +43,16 @@ impl DefaultProcessor {
let mut current_queue = VecDeque::new(); let mut current_queue = VecDeque::new();
let dispatch = |event: Event| { let dispatch = |event: Event| {
// TODO: add tracing information trace!("dispatched event: {:?}", event);
current_queue.push_front(event); current_queue.push_front(event);
}; };
for middleware in self.middleware.iter() { for middleware in self.middleware.iter() {
// TODO: add tracing information trace!("middleware received event: {:?}", current_event);
current_event = middleware.next(current_event, &dispatch); current_event = middleware.next(current_event, &dispatch);
trace!("middleware produced event: {:?}", current_event);
} }
while let Some(event) = current_queue.pop_back() { while let Some(event) = current_queue.pop_back() {
@ -61,7 +66,7 @@ impl DefaultProcessor {
} }
} }
impl Processor for DefaultProcessor { impl <'a> Processor for DefaultProcessor<'a> {
fn process(&mut self, event: Event) -> Vec<Event> { fn process(&mut self, event: Event) -> Vec<Event> {
self.event_queue.push_front(event); self.event_queue.push_front(event);

View File

@ -25,14 +25,14 @@ use crate::engine::{event::{Event, keyboard::{Key, Status}, matches_detected::{M
const MAX_HISTORY: usize = 3; // TODO: get as parameter const MAX_HISTORY: usize = 3; // TODO: get as parameter
pub struct MatchMiddleware<State> { pub struct MatchMiddleware<'a, State> {
matchers: Vec<Box<dyn Matcher<State>>>, matchers: &'a [&'a dyn Matcher<'a, State>],
matcher_states: RefCell<VecDeque<Vec<State>>>, matcher_states: RefCell<VecDeque<Vec<State>>>,
} }
impl<State> MatchMiddleware<State> { impl<'a, State> MatchMiddleware<'a, State> {
pub fn new(matchers: Vec<Box<dyn Matcher<State>>>) -> Self { pub fn new(matchers: &'a [&'a dyn Matcher<'a, State>]) -> Self {
Self { Self {
matchers, matchers,
matcher_states: RefCell::new(VecDeque::new()), matcher_states: RefCell::new(VecDeque::new()),
@ -40,7 +40,7 @@ impl<State> MatchMiddleware<State> {
} }
} }
impl<State> Middleware for MatchMiddleware<State> { impl<'a, State> Middleware for MatchMiddleware<'a, State> {
fn next(&self, event: Event, _: &dyn FnMut(Event)) -> Event { fn next(&self, event: Event, _: &dyn FnMut(Event)) -> Event {
let mut matcher_states = self.matcher_states.borrow_mut(); let mut matcher_states = self.matcher_states.borrow_mut();
let prev_states = if !matcher_states.is_empty() { let prev_states = if !matcher_states.is_empty() {
@ -85,6 +85,8 @@ impl<State> Middleware for MatchMiddleware<State> {
matcher_states.pop_front(); matcher_states.pop_front();
} }
println!("results: {:?}", all_results);
if !all_results.is_empty() { if !all_results.is_empty() {
return Event::MatchesDetected(MatchesDetectedEvent { return Event::MatchesDetected(MatchesDetectedEvent {
results: all_results.into_iter().map(|result | { results: all_results.into_iter().map(|result | {

View File

@ -35,10 +35,11 @@ pub trait Processor {
// Dependency inversion entities // Dependency inversion entities
pub trait Matcher<State> { pub trait Matcher<'a, State> {
fn process(&self, prev_state: Option<&State>, event: &MatcherEvent) -> (State, Vec<MatchResult>); fn process(&'a self, prev_state: Option<&State>, event: &MatcherEvent) -> (State, Vec<MatchResult>);
} }
#[derive(Debug)]
pub enum MatcherEvent { pub enum MatcherEvent {
Key { key: Key, chars: Option<String> }, Key { key: Key, chars: Option<String> },
VirtualSeparator, VirtualSeparator,
@ -46,11 +47,11 @@ pub enum MatcherEvent {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct MatchResult { pub struct MatchResult {
id: i32, pub id: i32,
trigger: String, pub trigger: String,
vars: HashMap<String, String>, pub vars: HashMap<String, String>,
} }
pub fn default<MatcherState: 'static>(matchers: Vec<Box<dyn Matcher<MatcherState>>>) -> impl Processor { pub fn default<'a, MatcherState>(matchers: &'a [&'a dyn Matcher<'a, MatcherState>]) -> impl Processor + 'a {
default::DefaultProcessor::new(matchers) default::DefaultProcessor::new(matchers)
} }

View File

@ -201,7 +201,8 @@ fn main() {
let log_level = match matches.occurrences_of("v") { let log_level = match matches.occurrences_of("v") {
0 => LevelFilter::Warn, 0 => LevelFilter::Warn,
1 => LevelFilter::Info, 1 => LevelFilter::Info,
_ => LevelFilter::Debug, 2 => LevelFilter::Debug,
_ => LevelFilter::Trace,
}; };
let handler = CLI_HANDLERS let handler = CLI_HANDLERS