Merge branch 'undo-backspace' into dev
This commit is contained in:
commit
f17898bfd7
|
@ -131,6 +131,9 @@ fn default_show_notifications() -> bool {
|
|||
fn default_auto_restart() -> bool {
|
||||
true
|
||||
}
|
||||
fn default_undo_backspace() -> bool {
|
||||
true
|
||||
}
|
||||
fn default_show_icon() -> bool {
|
||||
true
|
||||
}
|
||||
|
@ -149,6 +152,9 @@ fn default_global_vars() -> Vec<MatchVariable> {
|
|||
fn default_modulo_path() -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn default_mac_post_inject_delay() -> u64 {
|
||||
100
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Configs {
|
||||
|
@ -215,6 +221,9 @@ pub struct Configs {
|
|||
#[serde(default = "default_enable_active")]
|
||||
pub enable_active: bool,
|
||||
|
||||
#[serde(default = "default_undo_backspace")]
|
||||
pub undo_backspace: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub paste_shortcut: PasteShortcut,
|
||||
|
||||
|
@ -230,6 +239,9 @@ pub struct Configs {
|
|||
#[serde(default = "default_secure_input_watcher_interval")]
|
||||
pub secure_input_watcher_interval: i32,
|
||||
|
||||
#[serde(default = "default_mac_post_inject_delay")]
|
||||
pub mac_post_inject_delay: u64,
|
||||
|
||||
#[serde(default = "default_secure_input_notification")]
|
||||
pub secure_input_notification: bool,
|
||||
|
||||
|
|
138
src/engine.rs
138
src/engine.rs
|
@ -19,7 +19,7 @@
|
|||
|
||||
use crate::clipboard::ClipboardManager;
|
||||
use crate::config::BackendType;
|
||||
use crate::config::ConfigManager;
|
||||
use crate::config::{Configs, ConfigManager};
|
||||
use crate::event::{ActionEventReceiver, ActionType, SystemEvent, SystemEventReceiver};
|
||||
use crate::keyboard::KeyboardManager;
|
||||
use crate::matcher::{Match, MatchReceiver};
|
||||
|
@ -50,6 +50,8 @@ pub struct Engine<
|
|||
is_injecting: Arc<AtomicBool>,
|
||||
|
||||
enabled: RefCell<bool>,
|
||||
// Trigger string and injected text len pair
|
||||
last_expansion_data: RefCell<Option<(String, i32)>>,
|
||||
}
|
||||
|
||||
impl<
|
||||
|
@ -70,6 +72,7 @@ impl<
|
|||
is_injecting: Arc<AtomicBool>,
|
||||
) -> Engine<'a, S, C, M, U, R> {
|
||||
let enabled = RefCell::new(true);
|
||||
let last_expansion_data = RefCell::new(None);
|
||||
|
||||
Engine {
|
||||
keyboard_manager,
|
||||
|
@ -79,6 +82,7 @@ impl<
|
|||
renderer,
|
||||
is_injecting,
|
||||
enabled,
|
||||
last_expansion_data,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,17 +151,63 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
fn inject_text(&self, config: &Configs, target_string: &str, force_clipboard: bool) {
|
||||
let backend = if force_clipboard {
|
||||
&BackendType::Clipboard
|
||||
} else if config.backend == BackendType::Auto {
|
||||
if cfg!(target_os = "linux") {
|
||||
let all_ascii = target_string.chars().all(|c| c.is_ascii());
|
||||
if all_ascii {
|
||||
debug!(
|
||||
"All elements of the replacement are ascii, using Inject backend"
|
||||
);
|
||||
&BackendType::Inject
|
||||
} else {
|
||||
debug!("There are non-ascii characters, using Clipboard backend");
|
||||
&BackendType::Clipboard
|
||||
}
|
||||
} else {
|
||||
&BackendType::Inject
|
||||
}
|
||||
} else {
|
||||
&config.backend
|
||||
};
|
||||
|
||||
match backend {
|
||||
BackendType::Inject => {
|
||||
// To handle newlines, substitute each "\n" char with an Enter key press.
|
||||
let splits = target_string.split('\n');
|
||||
|
||||
for (i, split) in splits.enumerate() {
|
||||
if i > 0 {
|
||||
self.keyboard_manager.send_enter(&config);
|
||||
}
|
||||
|
||||
self.keyboard_manager.send_string(&config, split);
|
||||
}
|
||||
}
|
||||
BackendType::Clipboard => {
|
||||
self.clipboard_manager.set_clipboard(&target_string);
|
||||
self.keyboard_manager.trigger_paste(&config);
|
||||
}
|
||||
_ => {
|
||||
error!("Unsupported backend type evaluation.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_match(
|
||||
&self,
|
||||
m: &Match,
|
||||
trailing_separator: Option<char>,
|
||||
trigger_offset: usize,
|
||||
skip_delete: bool,
|
||||
) {
|
||||
) -> Option<(String, i32)> {
|
||||
let config = self.config_manager.active_config();
|
||||
|
||||
if !config.enable_active {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
|
||||
// Block espanso from reinterpreting its own actions
|
||||
|
@ -179,6 +229,8 @@ impl<
|
|||
.renderer
|
||||
.render_match(m, trigger_offset, config, vec![]);
|
||||
|
||||
let mut expansion_data: Option<(String, i32)> = None;
|
||||
|
||||
match rendered {
|
||||
RenderResult::Text(mut target_string) => {
|
||||
// If a trailing separator was counted in the match, add it back to the target string
|
||||
|
@ -213,53 +265,15 @@ impl<
|
|||
None
|
||||
};
|
||||
|
||||
let backend = if m.force_clipboard {
|
||||
&BackendType::Clipboard
|
||||
} else if config.backend == BackendType::Auto {
|
||||
if cfg!(target_os = "linux") {
|
||||
let all_ascii = target_string.chars().all(|c| c.is_ascii());
|
||||
if all_ascii {
|
||||
debug!(
|
||||
"All elements of the replacement are ascii, using Inject backend"
|
||||
);
|
||||
&BackendType::Inject
|
||||
} else {
|
||||
debug!("There are non-ascii characters, using Clipboard backend");
|
||||
&BackendType::Clipboard
|
||||
}
|
||||
} else {
|
||||
&BackendType::Inject
|
||||
}
|
||||
} else {
|
||||
&config.backend
|
||||
};
|
||||
|
||||
match backend {
|
||||
BackendType::Inject => {
|
||||
// To handle newlines, substitute each "\n" char with an Enter key press.
|
||||
let splits = target_string.split('\n');
|
||||
|
||||
for (i, split) in splits.enumerate() {
|
||||
if i > 0 {
|
||||
self.keyboard_manager.send_enter(&config);
|
||||
}
|
||||
|
||||
self.keyboard_manager.send_string(&config, split);
|
||||
}
|
||||
}
|
||||
BackendType::Clipboard => {
|
||||
// If the preserve_clipboard option is enabled, save the current
|
||||
// clipboard content to restore it later.
|
||||
previous_clipboard_content =
|
||||
self.return_content_if_preserve_clipboard_is_enabled();
|
||||
previous_clipboard_content = self.return_content_if_preserve_clipboard_is_enabled();
|
||||
|
||||
self.clipboard_manager.set_clipboard(&target_string);
|
||||
self.keyboard_manager.trigger_paste(&config);
|
||||
}
|
||||
_ => {
|
||||
error!("Unsupported backend type evaluation.");
|
||||
return;
|
||||
}
|
||||
self.inject_text(&config, &target_string, m.force_clipboard);
|
||||
|
||||
// Disallow undo backspace if cursor positioning is used
|
||||
if cursor_rewind.is_none() {
|
||||
expansion_data = Some((m.triggers[trigger_offset].clone(), target_string.chars().count() as i32));
|
||||
}
|
||||
|
||||
if let Some(moves) = cursor_rewind {
|
||||
|
@ -292,8 +306,17 @@ impl<
|
|||
.set_clipboard(&previous_clipboard_content);
|
||||
}
|
||||
|
||||
// On macOS, because the keyinjection is async, we need to wait a bit before
|
||||
// giving back the control. Otherwise, the injected actions will be handled back
|
||||
// by espanso itself.
|
||||
if cfg!(target_os = "macos") {
|
||||
std::thread::sleep(std::time::Duration::from_millis(config.mac_post_inject_delay));
|
||||
}
|
||||
|
||||
// Re-allow espanso to interpret actions
|
||||
self.is_injecting.store(false, Release);
|
||||
|
||||
expansion_data
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -311,7 +334,26 @@ impl<
|
|||
> MatchReceiver for Engine<'a, S, C, M, U, R>
|
||||
{
|
||||
fn on_match(&self, m: &Match, trailing_separator: Option<char>, trigger_offset: usize) {
|
||||
self.inject_match(m, trailing_separator, trigger_offset, false);
|
||||
let expansion_data = self.inject_match(m, trailing_separator, trigger_offset, false);
|
||||
let mut last_expansion_data = self.last_expansion_data.borrow_mut();
|
||||
(*last_expansion_data) = expansion_data;
|
||||
}
|
||||
|
||||
fn on_undo(&self) {
|
||||
let config = self.config_manager.active_config();
|
||||
|
||||
if !config.undo_backspace {
|
||||
return;
|
||||
}
|
||||
|
||||
let last_expansion_data = self.last_expansion_data.borrow();
|
||||
if let Some(ref last_expansion_data) = *last_expansion_data {
|
||||
let (trigger_string, injected_text_len) = last_expansion_data;
|
||||
// Delete the previously injected text, minus one character as it has been consumed by the backspace
|
||||
self.keyboard_manager.delete_string(&config, *injected_text_len - 1);
|
||||
// Restore previous text
|
||||
self.inject_text(&config, trigger_string, false);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_enable_update(&self, status: bool) {
|
||||
|
|
|
@ -320,6 +320,7 @@ pub trait MatchReceiver {
|
|||
fn on_match(&self, m: &Match, trailing_separator: Option<char>, trigger_offset: usize);
|
||||
fn on_enable_update(&self, status: bool);
|
||||
fn on_passive(&self);
|
||||
fn on_undo(&self);
|
||||
}
|
||||
|
||||
pub trait Matcher: KeyEventReceiver {
|
||||
|
|
|
@ -33,6 +33,7 @@ pub struct ScrollingMatcher<'a, R: MatchReceiver, M: ConfigManager<'a>> {
|
|||
passive_press_time: RefCell<SystemTime>,
|
||||
is_enabled: RefCell<bool>,
|
||||
was_previous_char_word_separator: RefCell<bool>,
|
||||
was_previous_char_a_match: RefCell<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -57,6 +58,7 @@ impl<'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> {
|
|||
passive_press_time,
|
||||
is_enabled: RefCell::new(true),
|
||||
was_previous_char_word_separator: RefCell::new(true),
|
||||
was_previous_char_a_match: RefCell::new(true),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,12 +106,8 @@ impl<'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMat
|
|||
.word_separators
|
||||
.contains(&c.chars().nth(0).unwrap_or_default());
|
||||
|
||||
// Workaround needed on macos to consider espanso replacement key presses as separators.
|
||||
if cfg!(target_os = "macos") {
|
||||
if c.len() > 1 {
|
||||
is_current_word_separator = true;
|
||||
}
|
||||
}
|
||||
let mut was_previous_char_a_match = self.was_previous_char_a_match.borrow_mut();
|
||||
(*was_previous_char_a_match) = false;
|
||||
|
||||
let mut was_previous_word_separator = self.was_previous_char_word_separator.borrow_mut();
|
||||
|
||||
|
@ -192,9 +190,7 @@ impl<'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMat
|
|||
if let Some(entry) = found_entry {
|
||||
let mtc = entry._match;
|
||||
|
||||
if let Some(last) = current_set_queue.back_mut() {
|
||||
last.clear();
|
||||
}
|
||||
current_set_queue.clear();
|
||||
|
||||
let trailing_separator = if !mtc.word {
|
||||
// If it's not a word match, it cannot have a trailing separator
|
||||
|
@ -216,12 +212,17 @@ impl<'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMat
|
|||
|
||||
self.receiver
|
||||
.on_match(mtc, trailing_separator, entry.trigger_offset);
|
||||
|
||||
|
||||
(*was_previous_char_a_match) = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_modifier(&self, m: KeyModifier) {
|
||||
let config = self.config_manager.default_config();
|
||||
|
||||
let mut was_previous_char_a_match = self.was_previous_char_a_match.borrow_mut();
|
||||
|
||||
// TODO: at the moment, activating the passive key triggers the toggle key
|
||||
// study a mechanism to avoid this problem
|
||||
|
||||
|
@ -253,7 +254,15 @@ impl<'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMat
|
|||
if m == BACKSPACE {
|
||||
let mut current_set_queue = self.current_set_queue.borrow_mut();
|
||||
current_set_queue.pop_back();
|
||||
|
||||
if (*was_previous_char_a_match) {
|
||||
current_set_queue.clear();
|
||||
self.receiver.on_undo();
|
||||
}
|
||||
}
|
||||
|
||||
// Disable the "backspace undo" feature
|
||||
(*was_previous_char_a_match) = false;
|
||||
|
||||
// Consider modifiers as separators to improve word matches reliability
|
||||
if m != LEFT_SHIFT && m != RIGHT_SHIFT && m != CAPS_LOCK {
|
||||
|
@ -269,6 +278,10 @@ impl<'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMat
|
|||
let mut was_previous_char_word_separator =
|
||||
self.was_previous_char_word_separator.borrow_mut();
|
||||
*was_previous_char_word_separator = true;
|
||||
|
||||
// Disable the "backspace undo" feature
|
||||
let mut was_previous_char_a_match = self.was_previous_char_a_match.borrow_mut();
|
||||
(*was_previous_char_a_match) = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user