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",
"clap",
"crossbeam",
"enum-as-inner",
"espanso-clipboard",
"espanso-config",
"espanso-detect",

View File

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

View File

@ -29,4 +29,5 @@ anyhow = "1.0.38"
thiserror = "1.0.23"
clap = "2.33.3"
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/>.
*/
use crate::engine::{Engine, funnel};
use funnel::Source;
use process::Matcher;
use crate::engine::{Engine, funnel, process, dispatch};
use super::{CliModule, CliModuleArgs};
mod source;
mod matcher;
mod executor;
pub fn new() -> CliModule {
#[allow(clippy::needless_update)]
@ -35,10 +40,18 @@ pub fn new() -> CliModule {
}
fn worker_main(args: CliModuleArgs) {
let funnel = funnel::default(vec![
Box::new(),
]);
let detect_source = source::detect::init_and_spawn().unwrap(); // TODO: handle error
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/>.
*/
use anyhow::Result;
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>,
}
impl Source for DetectorSource {
fn register(&self, select: &Select) -> i32 {
todo!()
impl<'a> funnel::Source<'a> for DetectSource {
fn register(&'a self, select: &mut Select<'a>) -> usize {
select.recv(&self.receiver)
}
fn receive(&self, select: &SelectedOperation) -> Event {
todo!()
fn receive(&self, op: SelectedOperation) -> Event {
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!()
}
// TODO: pass options
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::Event;
pub struct DefaultDispatcher {
executors: Vec<Box<dyn Executor>>,
pub struct DefaultDispatcher<'a> {
executors: Vec<Box<dyn Executor + 'a>>,
}
impl DefaultDispatcher {
pub fn new(text_injector: impl TextInjector + 'static) -> Self {
impl <'a> DefaultDispatcher<'a> {
pub fn new(text_injector: &'a dyn TextInjector) -> Self {
Self {
executors: vec![
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) {
for executor in self.executors.iter() {
if executor.execute(&event) {

View File

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

View File

@ -35,7 +35,7 @@ pub trait TextInjector {
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(
text_injector,
)

View File

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

View File

@ -25,9 +25,9 @@ use super::Event;
mod default;
pub trait Source {
fn register(&self, select: &Select) -> i32;
fn receive(&self, select: &SelectedOperation) -> Event;
pub trait Source<'a> {
fn register<'b>(&'a self, select: &mut Select<'a>) -> usize;
fn receive<'b>(&'a self, op: SelectedOperation) -> Event;
}
pub trait Funnel {
@ -39,6 +39,6 @@ pub enum FunnelResult {
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)
}

View File

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

View File

@ -17,16 +17,18 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use log::trace;
use super::{Event, Matcher, Middleware, Processor, middleware::matcher::MatchMiddleware};
use std::collections::VecDeque;
pub struct DefaultProcessor {
pub struct DefaultProcessor<'a> {
event_queue: VecDeque<Event>,
middleware: Vec<Box<dyn Middleware>>,
middleware: Vec<Box<dyn Middleware + 'a>>,
}
impl DefaultProcessor {
pub fn new<MatcherState: 'static>(matchers: Vec<Box<dyn Matcher<MatcherState>>>) -> Self {
impl <'a> DefaultProcessor<'a> {
pub fn new<MatcherState>(matchers: &'a [&'a dyn Matcher<'a, MatcherState>]) -> DefaultProcessor<'a> {
Self {
event_queue: VecDeque::new(),
middleware: vec![
@ -41,13 +43,16 @@ impl DefaultProcessor {
let mut current_queue = VecDeque::new();
let dispatch = |event: Event| {
// TODO: add tracing information
trace!("dispatched event: {:?}", event);
current_queue.push_front(event);
};
for middleware in self.middleware.iter() {
// TODO: add tracing information
trace!("middleware received event: {:?}", current_event);
current_event = middleware.next(current_event, &dispatch);
trace!("middleware produced event: {:?}", current_event);
}
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> {
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
pub struct MatchMiddleware<State> {
matchers: Vec<Box<dyn Matcher<State>>>,
pub struct MatchMiddleware<'a, State> {
matchers: &'a [&'a dyn Matcher<'a, State>],
matcher_states: RefCell<VecDeque<Vec<State>>>,
}
impl<State> MatchMiddleware<State> {
pub fn new(matchers: Vec<Box<dyn Matcher<State>>>) -> Self {
impl<'a, State> MatchMiddleware<'a, State> {
pub fn new(matchers: &'a [&'a dyn Matcher<'a, State>]) -> Self {
Self {
matchers,
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 {
let mut matcher_states = self.matcher_states.borrow_mut();
let prev_states = if !matcher_states.is_empty() {
@ -85,6 +85,8 @@ impl<State> Middleware for MatchMiddleware<State> {
matcher_states.pop_front();
}
println!("results: {:?}", all_results);
if !all_results.is_empty() {
return Event::MatchesDetected(MatchesDetectedEvent {
results: all_results.into_iter().map(|result | {

View File

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

View File

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