feat(engine): implement undo feature
This commit is contained in:
parent
eab305d45f
commit
fb45f92b69
|
@ -89,3 +89,10 @@ pub struct SecureInputEnabledEvent {
|
||||||
pub app_name: String,
|
pub app_name: String,
|
||||||
pub app_path: String,
|
pub app_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct UndoEvent {
|
||||||
|
pub match_id: i32,
|
||||||
|
pub trigger: String,
|
||||||
|
pub replace: String,
|
||||||
|
}
|
|
@ -71,6 +71,7 @@ pub enum EventType {
|
||||||
ImageResolved(internal::ImageResolvedEvent),
|
ImageResolved(internal::ImageResolvedEvent),
|
||||||
MatchInjected,
|
MatchInjected,
|
||||||
DiscardPrevious(internal::DiscardPreviousEvent),
|
DiscardPrevious(internal::DiscardPreviousEvent),
|
||||||
|
Undo(internal::UndoEvent),
|
||||||
|
|
||||||
Disabled,
|
Disabled,
|
||||||
Enabled,
|
Enabled,
|
||||||
|
|
|
@ -19,8 +19,7 @@
|
||||||
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
use super::{
|
use super::{DisableOptions, MatchFilter, MatchInfoProvider, MatchProvider, MatchSelector, Matcher, MatcherMiddlewareConfigProvider, Middleware, Multiplexer, PathProvider, Processor, Renderer, UndoEnabledProvider, middleware::{
|
||||||
middleware::{
|
|
||||||
action::{ActionMiddleware, EventSequenceProvider},
|
action::{ActionMiddleware, EventSequenceProvider},
|
||||||
cause::CauseCompensateMiddleware,
|
cause::CauseCompensateMiddleware,
|
||||||
cursor_hint::CursorHintMiddleware,
|
cursor_hint::CursorHintMiddleware,
|
||||||
|
@ -31,18 +30,8 @@ use super::{
|
||||||
multiplex::MultiplexMiddleware,
|
multiplex::MultiplexMiddleware,
|
||||||
past_discard::PastEventsDiscardMiddleware,
|
past_discard::PastEventsDiscardMiddleware,
|
||||||
render::RenderMiddleware,
|
render::RenderMiddleware,
|
||||||
},
|
}};
|
||||||
DisableOptions, MatchFilter, MatchInfoProvider, MatchProvider, MatchSelector, Matcher,
|
use crate::{event::{Event, EventType}, process::middleware::{context_menu::ContextMenuMiddleware, disable::DisableMiddleware, exit::ExitMiddleware, hotkey::HotKeyMiddleware, icon_status::IconStatusMiddleware, image_resolve::ImageResolverMiddleware, search::SearchMiddleware, undo::UndoMiddleware}};
|
||||||
MatcherMiddlewareConfigProvider, Middleware, Multiplexer, PathProvider, Processor, Renderer,
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
event::{Event, EventType},
|
|
||||||
process::middleware::{
|
|
||||||
context_menu::ContextMenuMiddleware, disable::DisableMiddleware, exit::ExitMiddleware,
|
|
||||||
hotkey::HotKeyMiddleware, icon_status::IconStatusMiddleware,
|
|
||||||
image_resolve::ImageResolverMiddleware, search::SearchMiddleware,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
pub struct DefaultProcessor<'a> {
|
pub struct DefaultProcessor<'a> {
|
||||||
|
@ -65,6 +54,7 @@ impl<'a> DefaultProcessor<'a> {
|
||||||
disable_options: DisableOptions,
|
disable_options: DisableOptions,
|
||||||
matcher_options_provider: &'a dyn MatcherMiddlewareConfigProvider,
|
matcher_options_provider: &'a dyn MatcherMiddlewareConfigProvider,
|
||||||
match_provider: &'a dyn MatchProvider,
|
match_provider: &'a dyn MatchProvider,
|
||||||
|
undo_enabled_provider: &'a dyn UndoEnabledProvider,
|
||||||
) -> DefaultProcessor<'a> {
|
) -> DefaultProcessor<'a> {
|
||||||
Self {
|
Self {
|
||||||
event_queue: VecDeque::new(),
|
event_queue: VecDeque::new(),
|
||||||
|
@ -82,6 +72,7 @@ impl<'a> DefaultProcessor<'a> {
|
||||||
Box::new(ImageResolverMiddleware::new(path_provider)),
|
Box::new(ImageResolverMiddleware::new(path_provider)),
|
||||||
Box::new(CursorHintMiddleware::new()),
|
Box::new(CursorHintMiddleware::new()),
|
||||||
Box::new(ExitMiddleware::new()),
|
Box::new(ExitMiddleware::new()),
|
||||||
|
Box::new(UndoMiddleware::new(undo_enabled_provider)),
|
||||||
Box::new(ActionMiddleware::new(
|
Box::new(ActionMiddleware::new(
|
||||||
match_info_provider,
|
match_info_provider,
|
||||||
event_sequence_provider,
|
event_sequence_provider,
|
||||||
|
|
|
@ -126,10 +126,28 @@ impl<'a> Middleware for ActionMiddleware<'a> {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
EventType::Undo(m_event) => {
|
||||||
|
// We subtract one, because the backspace that triggered the undo feature
|
||||||
|
// already removed the last char
|
||||||
|
let backspace_count = m_event.replace.chars().count() - 1;
|
||||||
|
|
||||||
|
dispatch(Event::caused_by(
|
||||||
|
event.source_id,
|
||||||
|
EventType::TextInject(TextInjectRequest {
|
||||||
|
text: m_event.trigger.clone(),
|
||||||
|
force_mode: self.match_info_provider.get_force_mode(m_event.match_id),
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
|
||||||
|
Event::caused_by(
|
||||||
|
event.source_id,
|
||||||
|
EventType::KeySequenceInject(KeySequenceInjectRequest {
|
||||||
|
keys: (0..backspace_count).map(|_| Key::Backspace).collect(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
_ => event,
|
_ => event,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: handle images
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,3 +34,4 @@ pub mod multiplex;
|
||||||
pub mod past_discard;
|
pub mod past_discard;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
pub mod undo;
|
||||||
|
|
115
espanso-engine/src/process/middleware/undo.rs
Normal file
115
espanso-engine/src/process/middleware/undo.rs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* 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::cell::RefCell;
|
||||||
|
|
||||||
|
use super::super::Middleware;
|
||||||
|
use crate::event::{
|
||||||
|
input::{Key, Status},
|
||||||
|
internal::{TextFormat, UndoEvent},
|
||||||
|
Event, EventType,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait UndoEnabledProvider {
|
||||||
|
fn is_undo_enabled(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UndoMiddleware<'a> {
|
||||||
|
undo_enabled_provider: &'a dyn UndoEnabledProvider,
|
||||||
|
record: RefCell<Option<InjectionRecord>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> UndoMiddleware<'a> {
|
||||||
|
pub fn new(undo_enabled_provider: &'a dyn UndoEnabledProvider) -> Self {
|
||||||
|
Self {
|
||||||
|
undo_enabled_provider,
|
||||||
|
record: RefCell::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Middleware for UndoMiddleware<'a> {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"undo"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
|
||||||
|
let mut record = self.record.borrow_mut();
|
||||||
|
|
||||||
|
if let EventType::TriggerCompensation(m_event) = &event.etype {
|
||||||
|
*record = Some(InjectionRecord {
|
||||||
|
id: Some(event.source_id),
|
||||||
|
trigger: Some(m_event.trigger.clone()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
} else if let EventType::Rendered(m_event) = &event.etype {
|
||||||
|
if let TextFormat::Plain = m_event.format {
|
||||||
|
if let Some(record) = &mut *record {
|
||||||
|
if record.id == Some(event.source_id) {
|
||||||
|
record.injected_text = Some(m_event.body.clone());
|
||||||
|
record.match_id = Some(m_event.match_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let EventType::Keyboard(m_event) = &event.etype {
|
||||||
|
if m_event.status == Status::Pressed {
|
||||||
|
if m_event.key == Key::Backspace {
|
||||||
|
if let Some(record) = (*record).take() {
|
||||||
|
if let (Some(trigger), Some(injected_text), Some(match_id)) =
|
||||||
|
(record.trigger, record.injected_text, record.match_id)
|
||||||
|
{
|
||||||
|
if self.undo_enabled_provider.is_undo_enabled() {
|
||||||
|
return Event::caused_by(
|
||||||
|
event.source_id,
|
||||||
|
EventType::Undo(UndoEvent {
|
||||||
|
match_id,
|
||||||
|
trigger,
|
||||||
|
replace: injected_text,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*record = None;
|
||||||
|
}
|
||||||
|
} else if let EventType::Mouse(_) = &event.etype {
|
||||||
|
// Any mouse event invalidates the undo feature, as it could
|
||||||
|
// represent a change in application
|
||||||
|
*record = None;
|
||||||
|
} else if let EventType::CursorHintCompensation(_) = &event.etype {
|
||||||
|
// Cursor hints invalidate the undo feature, as it would be pretty
|
||||||
|
// complex to determine which delete operations should be performed.
|
||||||
|
// This might change in the future.
|
||||||
|
*record = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct InjectionRecord {
|
||||||
|
id: Option<u32>,
|
||||||
|
match_id: Option<i32>,
|
||||||
|
trigger: Option<String>,
|
||||||
|
injected_text: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: test
|
|
@ -44,6 +44,7 @@ pub use middleware::matcher::{
|
||||||
pub use middleware::multiplex::Multiplexer;
|
pub use middleware::multiplex::Multiplexer;
|
||||||
pub use middleware::render::{Renderer, RendererError};
|
pub use middleware::render::{Renderer, RendererError};
|
||||||
pub use middleware::search::MatchProvider;
|
pub use middleware::search::MatchProvider;
|
||||||
|
pub use middleware::undo::UndoEnabledProvider;
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn default<'a, MatcherState>(
|
pub fn default<'a, MatcherState>(
|
||||||
|
@ -59,6 +60,7 @@ pub fn default<'a, MatcherState>(
|
||||||
disable_options: DisableOptions,
|
disable_options: DisableOptions,
|
||||||
matcher_options_provider: &'a dyn MatcherMiddlewareConfigProvider,
|
matcher_options_provider: &'a dyn MatcherMiddlewareConfigProvider,
|
||||||
match_provider: &'a dyn MatchProvider,
|
match_provider: &'a dyn MatchProvider,
|
||||||
|
undo_enabled_provider: &'a dyn UndoEnabledProvider,
|
||||||
) -> impl Processor + 'a {
|
) -> impl Processor + 'a {
|
||||||
default::DefaultProcessor::new(
|
default::DefaultProcessor::new(
|
||||||
matchers,
|
matchers,
|
||||||
|
@ -73,5 +75,6 @@ pub fn default<'a, MatcherState>(
|
||||||
disable_options,
|
disable_options,
|
||||||
matcher_options_provider,
|
matcher_options_provider,
|
||||||
match_provider,
|
match_provider,
|
||||||
|
undo_enabled_provider,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user