diff --git a/Cargo.lock b/Cargo.lock index b609af8..4b711da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -220,6 +220,12 @@ dependencies = [ "libdbus-sys", ] +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + [[package]] name = "dirs" version = "1.0.5" @@ -251,6 +257,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "downcast" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb454f0228b18c7f4c3b0ebbee346ed9c52e7443b0999cd543ff3571205701d" + [[package]] name = "dtoa" version = "0.4.7" @@ -334,6 +346,7 @@ dependencies = [ "glob", "lazy_static", "log", + "mockall", "ordered-float", "regex", "serde", @@ -455,6 +468,21 @@ dependencies = [ "widestring", ] +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fragile" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69a039c3498dc930fe810151a34ba0c1c70b02b8625035592e74432f678591f2" + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -684,6 +712,33 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mockall" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d614ad23f9bb59119b8b5670a85c7ba92c5e9adf4385c81ea00c51c8be33d5" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd4234635bca06fc96c7368d038061e0aae1b00a764dc817e900dc974e3deea" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote 1.0.9", + "syn 1.0.60", +] + [[package]] name = "named_pipe" version = "0.4.1" @@ -699,6 +754,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "notify-rust" version = "4.2.2" @@ -829,6 +890,35 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "predicates" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df" +dependencies = [ + "difference", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" + +[[package]] +name = "predicates-tree" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2" +dependencies = [ + "predicates-core", + "treeline", +] + [[package]] name = "proc-macro2" version = "1.0.24" @@ -1288,6 +1378,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "treeline" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" + [[package]] name = "unicase" version = "2.6.0" diff --git a/espanso-config/Cargo.toml b/espanso-config/Cargo.toml index 2e230f8..a4e8dbd 100644 --- a/espanso-config/Cargo.toml +++ b/espanso-config/Cargo.toml @@ -20,4 +20,5 @@ ordered-float = "2.0" [dev-dependencies] tempdir = "0.3.7" -tempfile = "3.2.0" \ No newline at end of file +tempfile = "3.2.0" +mockall = "0.9.1" \ No newline at end of file diff --git a/espanso-config/src/config/default.rs b/espanso-config/src/config/default.rs new file mode 100644 index 0000000..a023f5d --- /dev/null +++ b/espanso-config/src/config/default.rs @@ -0,0 +1,22 @@ +/* + * 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 . + */ + +pub(crate) const DEFAULT_CLIPBOARD_THRESHOLD: usize = 100; + +pub(crate) const DEFAULT_PRE_PASTE_DELAY: usize = 100; \ No newline at end of file diff --git a/espanso-config/src/config/mod.rs b/espanso-config/src/config/mod.rs index 4968f48..d68d9c8 100644 --- a/espanso-config/src/config/mod.rs +++ b/espanso-config/src/config/mod.rs @@ -25,16 +25,33 @@ mod parse; mod path; mod resolve; mod util; +pub(crate) mod default; pub(crate) mod store; +#[cfg(test)] +use mockall::{automock, predicate::*}; +#[cfg_attr(test, automock)] pub trait Config: Send { fn id(&self) -> i32; fn label(&self) -> &str; fn match_paths(&self) -> &[String]; fn backend(&self) -> Backend; - fn clipboard_threshold(&self) -> usize; - fn is_match(&self, app: &AppProperties) -> bool; + // Number of chars after which a match is injected with the clipboard + // backend instead of the default one. This is done for efficiency + // reasons, as injecting a long match through separate events becomes + // slow for long strings. + fn clipboard_threshold(&self) -> usize; + + // Delay (in ms) that espanso should wait to trigger the paste shortcut + // after copying the content in the clipboard. This is needed because + // if we trigger a "paste" shortcut before the content is actually + // copied in the clipboard, the operation will fail. + fn pre_paste_delay(&self) -> usize; + + // TODO: add other delay options (start by the ones needed in clipboard injector) + + fn is_match<'a>(&self, app: &AppProperties<'a>) -> bool; } pub trait ConfigStore: Send { diff --git a/espanso-config/src/config/parse/mod.rs b/espanso-config/src/config/parse/mod.rs index 38bd6f9..f3fae0d 100644 --- a/espanso-config/src/config/parse/mod.rs +++ b/espanso-config/src/config/parse/mod.rs @@ -30,6 +30,8 @@ pub(crate) struct ParsedConfig { pub backend: Option, pub clipboard_threshold: Option, + pub pre_paste_delay: Option, + // Includes pub includes: Option>, pub excludes: Option>, diff --git a/espanso-config/src/config/parse/yaml.rs b/espanso-config/src/config/parse/yaml.rs index 6021b9a..a229ba7 100644 --- a/espanso-config/src/config/parse/yaml.rs +++ b/espanso-config/src/config/parse/yaml.rs @@ -36,6 +36,9 @@ pub(crate) struct YAMLConfig { #[serde(default)] pub clipboard_threshold: Option, + #[serde(default)] + pub pre_paste_delay: Option, + #[serde(default)] pub includes: Option>, @@ -86,6 +89,9 @@ impl TryFrom for ParsedConfig { label: yaml_config.label, backend: yaml_config.backend, clipboard_threshold: yaml_config.clipboard_threshold, + + pre_paste_delay: yaml_config.pre_paste_delay, + use_standard_includes: yaml_config.use_standard_includes, includes: yaml_config.includes, extra_includes: yaml_config.extra_includes, @@ -112,6 +118,7 @@ mod tests { label: "test" backend: clipboard clipboard_threshold: 200 + pre_paste_delay: 300 use_standard_includes: true includes: ["test1"] @@ -136,6 +143,8 @@ mod tests { backend: Some("clipboard".to_string()), clipboard_threshold: Some(200), + pre_paste_delay: Some(300), + use_standard_includes: Some(true), includes: Some(vec!["test1".to_string()]), extra_includes: Some(vec!["test2".to_string()]), diff --git a/espanso-config/src/config/resolve.rs b/espanso-config/src/config/resolve.rs index e4df76b..72abb92 100644 --- a/espanso-config/src/config/resolve.rs +++ b/espanso-config/src/config/resolve.rs @@ -17,7 +17,13 @@ * along with espanso. If not, see . */ -use super::{AppProperties, Backend, Config, parse::ParsedConfig, path::calculate_paths, util::os_matches}; +use super::{ + default::{DEFAULT_CLIPBOARD_THRESHOLD, DEFAULT_PRE_PASTE_DELAY}, + parse::ParsedConfig, + path::calculate_paths, + util::os_matches, + AppProperties, Backend, Config, +}; use crate::{counter::next_id, merge}; use anyhow::Result; use log::error; @@ -118,7 +124,13 @@ impl Config for ResolvedConfig { } fn backend(&self) -> Backend { - match self.parsed.backend.as_deref().map(|b| b.to_lowercase()).as_deref() { + match self + .parsed + .backend + .as_deref() + .map(|b| b.to_lowercase()) + .as_deref() + { Some("clipboard") => Backend::Clipboard, Some("inject") => Backend::Inject, Some("auto") => Backend::Auto, @@ -130,7 +142,17 @@ impl Config for ResolvedConfig { } fn clipboard_threshold(&self) -> usize { - self.parsed.clipboard_threshold.unwrap_or(100) + self + .parsed + .clipboard_threshold + .unwrap_or(DEFAULT_CLIPBOARD_THRESHOLD) + } + + fn pre_paste_delay(&self) -> usize { + self + .parsed + .pre_paste_delay + .unwrap_or(DEFAULT_PRE_PASTE_DELAY) } } @@ -190,6 +212,7 @@ impl ResolvedConfig { label, backend, clipboard_threshold, + pre_paste_delay, includes, excludes, extra_includes, diff --git a/espanso-config/src/config/store.rs b/espanso-config/src/config/store.rs index 57b9885..e48222c 100644 --- a/espanso-config/src/config/store.rs +++ b/espanso-config/src/config/store.rs @@ -126,52 +126,22 @@ impl DefaultConfigStore { #[cfg(test)] mod tests { use super::*; + use crate::config::MockConfig; - struct MockConfig { - label: String, - is_match: bool, - } - - impl MockConfig { - pub fn new(label: &str, is_match: bool) -> Self { - Self { - label: label.to_string(), - is_match, - } - } - } - - impl Config for MockConfig { - fn id(&self) -> i32 { - 0 - } - - fn label(&self) -> &str { - &self.label - } - - fn match_paths(&self) -> &[String] { - unimplemented!() - } - - fn is_match(&self, _: &crate::config::AppProperties) -> bool { - self.is_match - } - - fn backend(&self) -> crate::config::Backend { - unimplemented!() - } - - fn clipboard_threshold(&self) -> usize { - unimplemented!() - } + pub fn new_mock(label: &'static str, is_match: bool) -> MockConfig { + let label = label.to_owned(); + let mut mock = MockConfig::new(); + mock.expect_id().return_const(0); + mock.expect_label().return_const(label); + mock.expect_is_match().return_const(is_match); + mock } #[test] fn config_store_selects_correctly() { - let default = MockConfig::new("default", false); - let custom1 = MockConfig::new("custom1", false); - let custom2 = MockConfig::new("custom2", true); + let default = new_mock("default", false); + let custom1 = new_mock("custom1", false); + let custom2 = new_mock("custom2", true); let store = DefaultConfigStore { default: Box::new(default), @@ -193,9 +163,9 @@ mod tests { #[test] fn config_store_active_fallback_to_default_if_no_match() { - let default = MockConfig::new("default", false); - let custom1 = MockConfig::new("custom1", false); - let custom2 = MockConfig::new("custom2", false); + let default = new_mock("default", false); + let custom1 = new_mock("custom1", false); + let custom2 = new_mock("custom2", false); let store = DefaultConfigStore { default: Box::new(default), diff --git a/espanso-config/src/legacy/mod.rs b/espanso-config/src/legacy/mod.rs index bea0251..d050bbc 100644 --- a/espanso-config/src/legacy/mod.rs +++ b/espanso-config/src/legacy/mod.rs @@ -22,7 +22,10 @@ use regex::Regex; use std::{collections::HashMap, path::Path}; use self::config::LegacyConfig; -use crate::matches::{MatchEffect, group::loader::yaml::parse::{YAMLMatch, YAMLVariable}}; +use crate::matches::{ + group::loader::yaml::parse::{YAMLMatch, YAMLVariable}, + MatchEffect, +}; use crate::{config::store::DefaultConfigStore, counter::StructId}; use crate::{ config::Config, @@ -139,10 +142,7 @@ fn deduplicate_matches( } // TODO: test case of matches with inner variables -fn deduplicate_vars( - vars: &mut [Variable], - var_map: &mut HashMap, -) { +fn deduplicate_vars(vars: &mut [Variable], var_map: &mut HashMap) { for v in vars.iter_mut() { let mut v_without_id = v.clone(); v_without_id.id = 0; @@ -254,7 +254,11 @@ impl Config for LegacyInteropConfig { } fn clipboard_threshold(&self) -> usize { - 100 + crate::config::default::DEFAULT_CLIPBOARD_THRESHOLD + } + + fn pre_paste_delay(&self) -> usize { + crate::config::default::DEFAULT_PRE_PASTE_DELAY } } @@ -472,8 +476,21 @@ mod tests { exec: None, }); - for (i, m) in match_store.query(default_config.match_paths()).matches.into_iter().enumerate() { - assert_eq!(m.id, match_store.query(active_config.match_paths()).matches.get(i).unwrap().id); + for (i, m) in match_store + .query(default_config.match_paths()) + .matches + .into_iter() + .enumerate() + { + assert_eq!( + m.id, + match_store + .query(active_config.match_paths()) + .matches + .get(i) + .unwrap() + .id + ); } assert_eq!(