refactor(core): restructure event pipeline to account for match cause compensation

This commit is contained in:
Federico Terzi 2021-04-17 14:59:42 +02:00
parent 799474a0fc
commit 976e653fc8
16 changed files with 224 additions and 67 deletions

View File

@ -41,7 +41,7 @@ impl<'a> MultiplexAdapter<'a> {
}
impl<'a> Multiplexer for MultiplexAdapter<'a> {
fn convert(&self, match_id: i32, trigger: String, trigger_args: HashMap<String, String>) -> Option<Event> {
fn convert(&self, match_id: i32, trigger: Option<String>, trigger_args: HashMap<String, String>) -> Option<Event> {
let m = self.provider.get(match_id)?;
match &m.effect {

View File

@ -0,0 +1,28 @@
/*
* 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/>.
*/
#[derive(Debug, Clone, PartialEq)]
pub struct TriggerCompensationEvent {
pub trigger: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CursorHintCompensationEvent {
pub cursor_hint_back_count: usize,
}

View File

@ -17,19 +17,19 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub enum Status {
Pressed,
Released,
}
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub enum Variant {
Left,
Right,
}
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub struct KeyboardEvent {
pub key: Key,
pub value: Option<String>,
@ -37,7 +37,7 @@ pub struct KeyboardEvent {
pub variant: Option<Variant>,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct KeySequenceInjectRequest {
pub keys: Vec<Key>,
}

View File

@ -27,11 +27,16 @@ pub struct MatchesDetectedEvent {
#[derive(Debug, Clone, PartialEq)]
pub struct DetectedMatch {
pub id: i32,
pub trigger: String,
pub trigger: Option<String>,
pub args: HashMap<String, String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MatchSelectedEvent {
pub chosen: DetectedMatch,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CauseCompensatedMatchEvent {
pub m: DetectedMatch,
}

View File

@ -21,23 +21,31 @@ pub mod keyboard;
pub mod text;
pub mod matches;
pub mod render;
pub mod effect;
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum Event {
NOOP,
ProcessingError(String),
ProcessingError(String), // TODO: create dedicated event
// Inputs
// TODO: Move to the input mode
Keyboard(keyboard::KeyboardEvent),
// Internal
// TODO: move to the "internal" mode (maybe, change name?)
MatchesDetected(matches::MatchesDetectedEvent),
MatchSelected(matches::MatchSelectedEvent),
CauseCompensatedMatch(matches::CauseCompensatedMatchEvent),
RenderingRequested(render::RenderingRequestedEvent),
Rendered(render::RenderedEvent),
// Effects
TriggerCompensation(effect::TriggerCompensationEvent),
CursorHintCompensation(effect::CursorHintCompensationEvent),
// TODO: move to the "effect" mod
KeySequenceInject(keyboard::KeySequenceInjectRequest),
TextInject(text::TextInjectRequest),
}

View File

@ -22,16 +22,12 @@ use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub struct RenderingRequestedEvent {
pub match_id: i32,
pub trigger: String,
pub trigger: Option<String>,
pub trigger_args: HashMap<String, String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RenderedEvent {
pub match_id: i32,
pub trigger: String,
pub body: String,
pub cursor_hint_back_count: Option<usize>,
}

View File

@ -17,13 +17,13 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct TextInjectRequest {
pub text: String,
pub force_mode: Option<TextInjectMode>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub enum TextInjectMode {
Keys,
Clipboard,

View File

@ -20,8 +20,8 @@
use log::trace;
use super::{Event, MatchFilter, MatchInfoProvider, MatchSelector, Matcher, Middleware, Multiplexer, Processor, Renderer, middleware::{
match_select::MatchSelectMiddleware, matcher::MatchMiddleware, multiplex::MultiplexMiddleware,
render::RenderMiddleware, action::ActionMiddleware,
match_select::MatchSelectMiddleware, matcher::MatcherMiddleware, multiplex::MultiplexMiddleware,
render::RenderMiddleware, action::ActionMiddleware, cursor_hint::CursorHintMiddleware, cause::CauseCompensateMiddleware,
}};
use std::collections::VecDeque;
@ -42,10 +42,12 @@ impl<'a> DefaultProcessor<'a> {
Self {
event_queue: VecDeque::new(),
middleware: vec![
Box::new(MatchMiddleware::new(matchers)),
Box::new(MatcherMiddleware::new(matchers)),
Box::new(MatchSelectMiddleware::new(match_filter, match_selector)),
Box::new(CauseCompensateMiddleware::new()),
Box::new(MultiplexMiddleware::new(multiplexer)),
Box::new(RenderMiddleware::new(renderer)),
Box::new(CursorHintMiddleware::new()),
Box::new(ActionMiddleware::new(match_info_provider)),
],
}

View File

@ -49,31 +49,27 @@ impl<'a> Middleware for ActionMiddleware<'a> {
}
fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event {
if let Event::Rendered(m_event) = &event {
dispatch(Event::TextInject(TextInjectRequest {
match &event {
Event::Rendered(m_event) => Event::TextInject(TextInjectRequest {
text: m_event.body.clone(),
force_mode: self.match_info_provider.get_force_mode(m_event.match_id),
}));
if let Some(cursor_hint_back_count) = m_event.cursor_hint_back_count {
dispatch(Event::KeySequenceInject(KeySequenceInjectRequest {
keys: (0..cursor_hint_back_count)
}),
Event::CursorHintCompensation(m_event) => {
Event::KeySequenceInject(KeySequenceInjectRequest {
keys: (0..m_event.cursor_hint_back_count)
.map(|_| Key::ArrowLeft)
.collect(),
}))
})
}
// This is executed before the dispatched event
return Event::KeySequenceInject(KeySequenceInjectRequest {
Event::TriggerCompensation(m_event) => Event::KeySequenceInject(KeySequenceInjectRequest {
keys: (0..m_event.trigger.chars().count())
.map(|_| Key::Backspace)
.collect(),
});
}),
_ => event,
}
// TODO: handle images
event
}
}

View File

@ -0,0 +1,66 @@
/*
* 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::Middleware;
use crate::engine::{
dispatch::Mode,
event::{
effect::TriggerCompensationEvent,
keyboard::{Key, KeySequenceInjectRequest},
matches::CauseCompensatedMatchEvent,
text::{TextInjectMode, TextInjectRequest},
Event,
},
};
pub struct CauseCompensateMiddleware {}
impl CauseCompensateMiddleware {
pub fn new() -> Self {
Self {}
}
}
impl Middleware for CauseCompensateMiddleware {
fn name(&self) -> &'static str {
"cause_compensate"
}
fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event {
if let Event::MatchSelected(m_event) = &event {
let compensated_event =
Event::CauseCompensatedMatch(CauseCompensatedMatchEvent { m: m_event.chosen.clone() });
if let Some(trigger) = &m_event.chosen.trigger {
dispatch(compensated_event);
// Before the event, place a trigger compensation
return Event::TriggerCompensation(TriggerCompensationEvent {
trigger: trigger.clone(),
});
} else {
return compensated_event;
}
}
event
}
}
// TODO: test

View File

@ -0,0 +1,77 @@
/*
* 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::Middleware;
use crate::engine::{dispatch::Mode, event::{Event, effect::CursorHintCompensationEvent, keyboard::{Key, KeySequenceInjectRequest}, render::RenderedEvent, text::{TextInjectMode, TextInjectRequest}}};
pub struct CursorHintMiddleware {}
impl CursorHintMiddleware {
pub fn new() -> Self {
Self {}
}
}
impl Middleware for CursorHintMiddleware {
fn name(&self) -> &'static str {
"cursor_hint"
}
fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event {
if let Event::Rendered(m_event) = event {
let (body, cursor_hint_back_count) = process_cursor_hint(m_event.body);
if let Some(cursor_hint_back_count) = cursor_hint_back_count {
dispatch(Event::CursorHintCompensation(CursorHintCompensationEvent {
cursor_hint_back_count,
}))
}
// Alter the rendered event to remove the cursor hint from the body
return Event::Rendered(RenderedEvent {
body,
..m_event
})
}
event
}
}
// TODO: test
fn process_cursor_hint(body: String) -> (String, Option<usize>) {
if let Some(index) = body.find("$|$") {
// Convert the byte index to a char index
let char_str = &body[0..index];
let char_index = char_str.chars().count();
let total_size = body.chars().count();
// Remove the $|$ placeholder
let body = body.replace("$|$", "");
// Calculate the amount of rewind moves needed (LEFT ARROW).
// Subtract also 3, equal to the number of chars of the placeholder "$|$"
let moves = total_size - char_index - 3;
(body, Some(moves))
} else {
(body, None)
}
}
// TODO: test

View File

@ -32,13 +32,13 @@ use crate::engine::{
const MAX_HISTORY: usize = 3; // TODO: get as parameter
pub struct MatchMiddleware<'a, State> {
pub struct MatcherMiddleware<'a, State> {
matchers: &'a [&'a dyn Matcher<'a, State>],
matcher_states: RefCell<VecDeque<Vec<State>>>,
}
impl<'a, State> MatchMiddleware<'a, State> {
impl<'a, State> MatcherMiddleware<'a, State> {
pub fn new(matchers: &'a [&'a dyn Matcher<'a, State>]) -> Self {
Self {
matchers,
@ -47,9 +47,9 @@ impl<'a, State> MatchMiddleware<'a, State> {
}
}
impl<'a, State> Middleware for MatchMiddleware<'a, State> {
impl<'a, State> Middleware for MatcherMiddleware<'a, State> {
fn name(&self) -> &'static str {
"match"
"matcher"
}
fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
@ -104,7 +104,7 @@ impl<'a, State> Middleware for MatchMiddleware<'a, State> {
.into_iter()
.map(|result| DetectedMatch {
id: result.id,
trigger: result.trigger,
trigger: Some(result.trigger),
args: result.args,
})
.collect(),

View File

@ -18,7 +18,9 @@
*/
pub mod action;
pub mod render;
pub mod cause;
pub mod cursor_hint;
pub mod match_select;
pub mod matcher;
pub mod multiplex;
pub mod match_select;
pub mod render;

View File

@ -38,8 +38,8 @@ impl<'a> Middleware for MultiplexMiddleware<'a> {
}
fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
if let Event::MatchSelected(m_event) = event {
return match self.multiplexer.convert(m_event.chosen.id, m_event.chosen.trigger, m_event.chosen.args) {
if let Event::CauseCompensatedMatch(m_event) = event {
return match self.multiplexer.convert(m_event.m.id, m_event.m.trigger, m_event.m.args) {
Some(event) => event,
None => {
error!("match multiplexing failed");

View File

@ -17,11 +17,12 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use log::{error};
use log::error;
use super::super::Middleware;
use crate::engine::{
event::{
effect::{CursorHintCompensationEvent, TriggerCompensationEvent},
render::RenderedEvent,
Event,
},
@ -47,13 +48,9 @@ impl<'a> Middleware for RenderMiddleware<'a> {
if let Event::RenderingRequested(m_event) = event {
match self.renderer.render(m_event.match_id, m_event.trigger_args) {
Ok(body) => {
let (body, cursor_hint_back_count) = process_cursor_hint(body);
return Event::Rendered(RenderedEvent {
match_id: m_event.match_id,
trigger: m_event.trigger,
body,
cursor_hint_back_count,
});
}
Err(err) => {
@ -73,23 +70,3 @@ impl<'a> Middleware for RenderMiddleware<'a> {
}
// TODO: test
fn process_cursor_hint(body: String) -> (String, Option<usize>) {
if let Some(index) = body.find("$|$") {
// Convert the byte index to a char index
let char_str = &body[0..index];
let char_index = char_str.chars().count();
let total_size = body.chars().count();
// Remove the $|$ placeholder
let body = body.replace("$|$", "");
// Calculate the amount of rewind moves needed (LEFT ARROW).
// Subtract also 3, equal to the number of chars of the placeholder "$|$"
let moves = total_size - char_index - 3;
(body, Some(moves))
} else {
(body, None)
}
}
// TODO: test

View File

@ -71,7 +71,7 @@ pub trait Multiplexer {
fn convert(
&self,
match_id: i32,
trigger: String,
trigger: Option<String>,
trigger_args: HashMap<String, String>,
) -> Option<Event>;
}