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> { 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)?; let m = self.provider.get(match_id)?;
match &m.effect { 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/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub enum Status { pub enum Status {
Pressed, Pressed,
Released, Released,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub enum Variant { pub enum Variant {
Left, Left,
Right, Right,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub struct KeyboardEvent { pub struct KeyboardEvent {
pub key: Key, pub key: Key,
pub value: Option<String>, pub value: Option<String>,
@ -37,7 +37,7 @@ pub struct KeyboardEvent {
pub variant: Option<Variant>, pub variant: Option<Variant>,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct KeySequenceInjectRequest { pub struct KeySequenceInjectRequest {
pub keys: Vec<Key>, pub keys: Vec<Key>,
} }

View File

@ -27,7 +27,7 @@ pub struct MatchesDetectedEvent {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct DetectedMatch { pub struct DetectedMatch {
pub id: i32, pub id: i32,
pub trigger: String, pub trigger: Option<String>,
pub args: HashMap<String, String>, pub args: HashMap<String, String>,
} }
@ -35,3 +35,8 @@ pub struct DetectedMatch {
pub struct MatchSelectedEvent { pub struct MatchSelectedEvent {
pub chosen: DetectedMatch, 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 text;
pub mod matches; pub mod matches;
pub mod render; pub mod render;
pub mod effect;
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum Event { pub enum Event {
NOOP, NOOP,
ProcessingError(String), ProcessingError(String), // TODO: create dedicated event
// Inputs // Inputs
// TODO: Move to the input mode
Keyboard(keyboard::KeyboardEvent), Keyboard(keyboard::KeyboardEvent),
// Internal // Internal
// TODO: move to the "internal" mode (maybe, change name?)
MatchesDetected(matches::MatchesDetectedEvent), MatchesDetected(matches::MatchesDetectedEvent),
MatchSelected(matches::MatchSelectedEvent), MatchSelected(matches::MatchSelectedEvent),
CauseCompensatedMatch(matches::CauseCompensatedMatchEvent),
RenderingRequested(render::RenderingRequestedEvent), RenderingRequested(render::RenderingRequestedEvent),
Rendered(render::RenderedEvent), Rendered(render::RenderedEvent),
// Effects // Effects
TriggerCompensation(effect::TriggerCompensationEvent),
CursorHintCompensation(effect::CursorHintCompensationEvent),
// TODO: move to the "effect" mod
KeySequenceInject(keyboard::KeySequenceInjectRequest), KeySequenceInject(keyboard::KeySequenceInjectRequest),
TextInject(text::TextInjectRequest), TextInject(text::TextInjectRequest),
} }

View File

@ -22,16 +22,12 @@ use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct RenderingRequestedEvent { pub struct RenderingRequestedEvent {
pub match_id: i32, pub match_id: i32,
pub trigger: String, pub trigger: Option<String>,
pub trigger_args: HashMap<String, String>, pub trigger_args: HashMap<String, String>,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct RenderedEvent { pub struct RenderedEvent {
pub match_id: i32, pub match_id: i32,
pub trigger: String,
pub body: 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/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct TextInjectRequest { pub struct TextInjectRequest {
pub text: String, pub text: String,
pub force_mode: Option<TextInjectMode>, pub force_mode: Option<TextInjectMode>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub enum TextInjectMode { pub enum TextInjectMode {
Keys, Keys,
Clipboard, Clipboard,

View File

@ -20,8 +20,8 @@
use log::trace; use log::trace;
use super::{Event, MatchFilter, MatchInfoProvider, MatchSelector, Matcher, Middleware, Multiplexer, Processor, Renderer, middleware::{ use super::{Event, MatchFilter, MatchInfoProvider, MatchSelector, Matcher, Middleware, Multiplexer, Processor, Renderer, middleware::{
match_select::MatchSelectMiddleware, matcher::MatchMiddleware, multiplex::MultiplexMiddleware, match_select::MatchSelectMiddleware, matcher::MatcherMiddleware, multiplex::MultiplexMiddleware,
render::RenderMiddleware, action::ActionMiddleware, render::RenderMiddleware, action::ActionMiddleware, cursor_hint::CursorHintMiddleware, cause::CauseCompensateMiddleware,
}}; }};
use std::collections::VecDeque; use std::collections::VecDeque;
@ -42,10 +42,12 @@ impl<'a> DefaultProcessor<'a> {
Self { Self {
event_queue: VecDeque::new(), event_queue: VecDeque::new(),
middleware: vec![ middleware: vec![
Box::new(MatchMiddleware::new(matchers)), Box::new(MatcherMiddleware::new(matchers)),
Box::new(MatchSelectMiddleware::new(match_filter, match_selector)), Box::new(MatchSelectMiddleware::new(match_filter, match_selector)),
Box::new(CauseCompensateMiddleware::new()),
Box::new(MultiplexMiddleware::new(multiplexer)), Box::new(MultiplexMiddleware::new(multiplexer)),
Box::new(RenderMiddleware::new(renderer)), Box::new(RenderMiddleware::new(renderer)),
Box::new(CursorHintMiddleware::new()),
Box::new(ActionMiddleware::new(match_info_provider)), 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 { fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event {
if let Event::Rendered(m_event) = &event { match &event {
dispatch(Event::TextInject(TextInjectRequest { Event::Rendered(m_event) => Event::TextInject(TextInjectRequest {
text: m_event.body.clone(), text: m_event.body.clone(),
force_mode: self.match_info_provider.get_force_mode(m_event.match_id), force_mode: self.match_info_provider.get_force_mode(m_event.match_id),
})); }),
Event::CursorHintCompensation(m_event) => {
if let Some(cursor_hint_back_count) = m_event.cursor_hint_back_count { Event::KeySequenceInject(KeySequenceInjectRequest {
dispatch(Event::KeySequenceInject(KeySequenceInjectRequest { keys: (0..m_event.cursor_hint_back_count)
keys: (0..cursor_hint_back_count)
.map(|_| Key::ArrowLeft) .map(|_| Key::ArrowLeft)
.collect(), .collect(),
})) })
} }
Event::TriggerCompensation(m_event) => Event::KeySequenceInject(KeySequenceInjectRequest {
// This is executed before the dispatched event
return Event::KeySequenceInject(KeySequenceInjectRequest {
keys: (0..m_event.trigger.chars().count()) keys: (0..m_event.trigger.chars().count())
.map(|_| Key::Backspace) .map(|_| Key::Backspace)
.collect(), .collect(),
}); }),
_ => event,
} }
// TODO: handle images // 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 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>], matchers: &'a [&'a dyn Matcher<'a, State>],
matcher_states: RefCell<VecDeque<Vec<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 { pub fn new(matchers: &'a [&'a dyn Matcher<'a, State>]) -> Self {
Self { Self {
matchers, 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 { fn name(&self) -> &'static str {
"match" "matcher"
} }
fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
@ -104,7 +104,7 @@ impl<'a, State> Middleware for MatchMiddleware<'a, State> {
.into_iter() .into_iter()
.map(|result| DetectedMatch { .map(|result| DetectedMatch {
id: result.id, id: result.id,
trigger: result.trigger, trigger: Some(result.trigger),
args: result.args, args: result.args,
}) })
.collect(), .collect(),

View File

@ -18,7 +18,9 @@
*/ */
pub mod action; pub mod action;
pub mod render; pub mod cause;
pub mod cursor_hint;
pub mod match_select;
pub mod matcher; pub mod matcher;
pub mod multiplex; 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 { fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
if let Event::MatchSelected(m_event) = event { if let Event::CauseCompensatedMatch(m_event) = event {
return match self.multiplexer.convert(m_event.chosen.id, m_event.chosen.trigger, m_event.chosen.args) { return match self.multiplexer.convert(m_event.m.id, m_event.m.trigger, m_event.m.args) {
Some(event) => event, Some(event) => event,
None => { None => {
error!("match multiplexing failed"); error!("match multiplexing failed");

View File

@ -17,11 +17,12 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use log::{error}; use log::error;
use super::super::Middleware; use super::super::Middleware;
use crate::engine::{ use crate::engine::{
event::{ event::{
effect::{CursorHintCompensationEvent, TriggerCompensationEvent},
render::RenderedEvent, render::RenderedEvent,
Event, Event,
}, },
@ -47,13 +48,9 @@ impl<'a> Middleware for RenderMiddleware<'a> {
if let Event::RenderingRequested(m_event) = event { if let Event::RenderingRequested(m_event) = event {
match self.renderer.render(m_event.match_id, m_event.trigger_args) { match self.renderer.render(m_event.match_id, m_event.trigger_args) {
Ok(body) => { Ok(body) => {
let (body, cursor_hint_back_count) = process_cursor_hint(body);
return Event::Rendered(RenderedEvent { return Event::Rendered(RenderedEvent {
match_id: m_event.match_id, match_id: m_event.match_id,
trigger: m_event.trigger,
body, body,
cursor_hint_back_count,
}); });
} }
Err(err) => { Err(err) => {
@ -73,23 +70,3 @@ impl<'a> Middleware for RenderMiddleware<'a> {
} }
// TODO: test // 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( fn convert(
&self, &self,
match_id: i32, match_id: i32,
trigger: String, trigger: Option<String>,
trigger_args: HashMap<String, String>, trigger_args: HashMap<String, String>,
) -> Option<Event>; ) -> Option<Event>;
} }