Format using 'cargo fmt'

This commit is contained in:
Atul Bhosale 2020-02-13 23:34:25 +05:30
parent 4e649e73a1
commit 747be23d10
No known key found for this signature in database
GPG Key ID: 19A6A0B090883718
50 changed files with 2116 additions and 1326 deletions

View File

@ -46,8 +46,7 @@ fn print_config() {
println!("cargo:rustc-link-lib=framework=Cocoa"); println!("cargo:rustc-link-lib=framework=Cocoa");
} }
fn main() fn main() {
{
let dst = get_config(); let dst = get_config();
println!("cargo:rustc-link-search=native={}", dst.display()); println!("cargo:rustc-link-search=native={}", dst.display());

View File

@ -17,11 +17,11 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::os::raw::{c_void, c_char}; use std::os::raw::{c_char, c_void};
#[allow(improper_ctypes)] #[allow(improper_ctypes)]
#[link(name = "linuxbridge", kind = "static")] #[link(name = "linuxbridge", kind = "static")]
extern { extern "C" {
pub fn check_x11() -> i32; pub fn check_x11() -> i32;
pub fn initialize(s: *const c_void) -> i32; pub fn initialize(s: *const c_void) -> i32;
pub fn eventloop(); pub fn eventloop();
@ -34,8 +34,9 @@ extern {
pub fn is_current_window_special() -> i32; pub fn is_current_window_special() -> i32;
// Keyboard // Keyboard
pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u8, pub fn register_keypress_callback(
i32, i32, i32)); cb: extern "C" fn(_self: *mut c_void, *const u8, i32, i32, i32),
);
pub fn send_string(string: *const c_char); pub fn send_string(string: *const c_char);
pub fn delete_string(count: i32); pub fn delete_string(count: i32);

View File

@ -17,7 +17,7 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::os::raw::{c_void, c_char}; use std::os::raw::{c_char, c_void};
#[repr(C)] #[repr(C)]
pub struct MacMenuItem { pub struct MacMenuItem {
@ -28,7 +28,7 @@ pub struct MacMenuItem {
#[allow(improper_ctypes)] #[allow(improper_ctypes)]
#[link(name = "macbridge", kind = "static")] #[link(name = "macbridge", kind = "static")]
extern { extern "C" {
pub fn initialize(s: *const c_void, icon_path: *const c_char); pub fn initialize(s: *const c_void, icon_path: *const c_char);
pub fn eventloop(); pub fn eventloop();
pub fn headless_eventloop(); pub fn headless_eventloop();
@ -46,13 +46,14 @@ extern {
pub fn set_clipboard_image(path: *const c_char) -> i32; pub fn set_clipboard_image(path: *const c_char) -> i32;
// UI // UI
pub fn register_icon_click_callback(cb: extern fn(_self: *mut c_void)); pub fn register_icon_click_callback(cb: extern "C" fn(_self: *mut c_void));
pub fn show_context_menu(items: *const MacMenuItem, count: i32) -> i32; pub fn show_context_menu(items: *const MacMenuItem, count: i32) -> i32;
pub fn register_context_menu_click_callback(cb: extern fn(_self: *mut c_void, id: i32)); pub fn register_context_menu_click_callback(cb: extern "C" fn(_self: *mut c_void, id: i32));
// Keyboard // Keyboard
pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u8, pub fn register_keypress_callback(
i32, i32, i32)); cb: extern "C" fn(_self: *mut c_void, *const u8, i32, i32, i32),
);
pub fn send_string(string: *const c_char); pub fn send_string(string: *const c_char);
pub fn send_vkey(vk: i32); pub fn send_vkey(vk: i32);

View File

@ -17,7 +17,7 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::os::raw::{c_void}; use std::os::raw::c_void;
#[repr(C)] #[repr(C)]
pub struct WindowsMenuItem { pub struct WindowsMenuItem {
@ -28,7 +28,7 @@ pub struct WindowsMenuItem {
#[allow(improper_ctypes)] #[allow(improper_ctypes)]
#[link(name = "winbridge", kind = "static")] #[link(name = "winbridge", kind = "static")]
extern { extern "C" {
pub fn start_daemon_process() -> i32; pub fn start_daemon_process() -> i32;
pub fn initialize(s: *const c_void, ico_path: *const u16, bmp_path: *const u16) -> i32; pub fn initialize(s: *const c_void, ico_path: *const u16, bmp_path: *const u16) -> i32;
@ -40,8 +40,8 @@ extern {
pub fn show_notification(message: *const u16) -> i32; pub fn show_notification(message: *const u16) -> i32;
pub fn close_notification(); pub fn close_notification();
pub fn show_context_menu(items: *const WindowsMenuItem, count: i32) -> i32; pub fn show_context_menu(items: *const WindowsMenuItem, count: i32) -> i32;
pub fn register_icon_click_callback(cb: extern fn(_self: *mut c_void)); pub fn register_icon_click_callback(cb: extern "C" fn(_self: *mut c_void));
pub fn register_context_menu_click_callback(cb: extern fn(_self: *mut c_void, id: i32)); pub fn register_context_menu_click_callback(cb: extern "C" fn(_self: *mut c_void, id: i32));
pub fn cleanup_ui(); pub fn cleanup_ui();
// CLIPBOARD // CLIPBOARD
@ -50,8 +50,9 @@ extern {
pub fn set_clipboard_image(path: *const u16) -> i32; pub fn set_clipboard_image(path: *const u16) -> i32;
// KEYBOARD // KEYBOARD
pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u16, pub fn register_keypress_callback(
i32, i32, i32, i32)); cb: extern "C" fn(_self: *mut c_void, *const u16, i32, i32, i32, i32),
);
pub fn eventloop(); pub fn eventloop();
pub fn send_string(string: *const u16); pub fn send_string(string: *const u16);

View File

@ -27,20 +27,18 @@ pub fn check_dependencies() -> bool {
let mut result = true; let mut result = true;
// Make sure notify-send is installed // Make sure notify-send is installed
let status = Command::new("notify-send") let status = Command::new("notify-send").arg("-v").output();
.arg("-v")
.output();
if status.is_err() { if status.is_err() {
println!("Error: 'notify-send' command is needed for espanso to work correctly, please install it."); println!("Error: 'notify-send' command is needed for espanso to work correctly, please install it.");
result = false; result = false;
} }
// Make sure xclip is installed // Make sure xclip is installed
let status = Command::new("xclip") let status = Command::new("xclip").arg("-version").output();
.arg("-version")
.output();
if status.is_err() { if status.is_err() {
println!("Error: 'xclip' command is needed for espanso to work correctly, please install it."); println!(
"Error: 'xclip' command is needed for espanso to work correctly, please install it."
);
result = false; result = false;
} }

View File

@ -17,18 +17,16 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::process::{Command, Stdio};
use std::io::{Write};
use log::{error, warn}; use log::{error, warn};
use std::io::Write;
use std::path::Path; use std::path::Path;
use std::process::{Command, Stdio};
pub struct LinuxClipboardManager {} pub struct LinuxClipboardManager {}
impl super::ClipboardManager for LinuxClipboardManager { impl super::ClipboardManager for LinuxClipboardManager {
fn get_clipboard(&self) -> Option<String> { fn get_clipboard(&self) -> Option<String> {
let res = Command::new("xclip") let res = Command::new("xclip").args(&["-o", "-sel", "clip"]).output();
.args(&["-o", "-sel", "clip"])
.output();
if let Ok(output) = res { if let Ok(output) = res {
if output.status.success() { if output.status.success() {
@ -71,14 +69,14 @@ impl super::ClipboardManager for LinuxClipboardManager {
Some(ext) => { Some(ext) => {
let ext = ext.to_string_lossy().to_lowercase(); let ext = ext.to_string_lossy().to_lowercase();
match ext.as_ref() { match ext.as_ref() {
"png" => {"image/png"}, "png" => "image/png",
"jpg" | "jpeg" => {"image/jpeg"}, "jpg" | "jpeg" => "image/jpeg",
"gif" => {"image/gif"}, "gif" => "image/gif",
"svg" => {"image/svg"}, "svg" => "image/svg",
_ => {"image/png"}, _ => "image/png",
} }
}, }
None => {"image/png"}, None => "image/png",
}; };
let image_path = image_path.to_string_lossy().into_owned(); let image_path = image_path.to_string_lossy().into_owned();

View File

@ -17,15 +17,13 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::os::raw::c_char;
use crate::bridge::macos::*; use crate::bridge::macos::*;
use std::ffi::{CStr, CString};
use std::path::Path;
use log::{error, warn}; use log::{error, warn};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::path::Path;
pub struct MacClipboardManager { pub struct MacClipboardManager {}
}
impl super::ClipboardManager for MacClipboardManager { impl super::ClipboardManager for MacClipboardManager {
fn get_clipboard(&self) -> Option<String> { fn get_clipboard(&self) -> Option<String> {

View File

@ -17,13 +17,11 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use widestring::U16CString; use crate::bridge::windows::{get_clipboard, set_clipboard, set_clipboard_image};
use crate::bridge::windows::{set_clipboard, get_clipboard, set_clipboard_image};
use std::path::Path; use std::path::Path;
use widestring::U16CString;
pub struct WindowsClipboardManager { pub struct WindowsClipboardManager {}
}
impl WindowsClipboardManager { impl WindowsClipboardManager {
pub fn new() -> WindowsClipboardManager { pub fn new() -> WindowsClipboardManager {

File diff suppressed because it is too large Load Diff

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/>.
*/ */
use regex::Regex; use super::{ConfigSet, Configs};
use crate::matcher::Match;
use crate::system::SystemManager; use crate::system::SystemManager;
use log::{debug, warn};
use regex::Regex;
use std::cell::RefCell; use std::cell::RefCell;
use std::time::SystemTime; use std::time::SystemTime;
use log::{debug, warn};
use super::{Configs, ConfigSet};
use crate::matcher::Match;
pub struct RuntimeConfigManager<'a, S: SystemManager> { pub struct RuntimeConfigManager<'a, S: SystemManager> {
set: ConfigSet, set: ConfigSet,
@ -37,7 +37,7 @@ pub struct RuntimeConfigManager<'a, S: SystemManager> {
// Cache // Cache
last_config_update: RefCell<SystemTime>, last_config_update: RefCell<SystemTime>,
last_config: RefCell<Option<&'a Configs>> last_config: RefCell<Option<&'a Configs>>,
} }
impl<'a, S: SystemManager> RuntimeConfigManager<'a, S> { impl<'a, S: SystemManager> RuntimeConfigManager<'a, S> {
@ -101,7 +101,7 @@ impl <'a, S: SystemManager> RuntimeConfigManager<'a, S> {
exec_regexps, exec_regexps,
system_manager, system_manager,
last_config_update, last_config_update,
last_config last_config,
} }
} }
@ -118,10 +118,12 @@ impl <'a, S: SystemManager> RuntimeConfigManager<'a, S> {
for (i, regex) in self.title_regexps.iter().enumerate() { for (i, regex) in self.title_regexps.iter().enumerate() {
if let Some(regex) = regex { if let Some(regex) = regex {
if regex.is_match(&title) { if regex.is_match(&title) {
debug!("Matched 'filter_title' for '{}' config, using custom settings.", debug!(
self.set.specific[i].name); "Matched 'filter_title' for '{}' config, using custom settings.",
self.set.specific[i].name
);
return &self.set.specific[i] return &self.set.specific[i];
} }
} }
} }
@ -135,10 +137,12 @@ impl <'a, S: SystemManager> RuntimeConfigManager<'a, S> {
for (i, regex) in self.exec_regexps.iter().enumerate() { for (i, regex) in self.exec_regexps.iter().enumerate() {
if let Some(regex) = regex { if let Some(regex) = regex {
if regex.is_match(&executable) { if regex.is_match(&executable) {
debug!("Matched 'filter_exec' for '{}' config, using custom settings.", debug!(
self.set.specific[i].name); "Matched 'filter_exec' for '{}' config, using custom settings.",
self.set.specific[i].name
);
return &self.set.specific[i] return &self.set.specific[i];
} }
} }
} }
@ -152,10 +156,12 @@ impl <'a, S: SystemManager> RuntimeConfigManager<'a, S> {
for (i, regex) in self.class_regexps.iter().enumerate() { for (i, regex) in self.class_regexps.iter().enumerate() {
if let Some(regex) = regex { if let Some(regex) = regex {
if regex.is_match(&class) { if regex.is_match(&class) {
debug!("Matched 'filter_class' for '{}' config, using custom settings.", debug!(
self.set.specific[i].name); "Matched 'filter_class' for '{}' config, using custom settings.",
self.set.specific[i].name
);
return &self.set.specific[i] return &self.set.specific[i];
} }
} }
} }
@ -204,12 +210,14 @@ impl <'a, S: SystemManager> super::ConfigManager<'a> for RuntimeConfigManager<'a
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use tempfile::{NamedTempFile, TempDir}; use crate::config::tests::{
use crate::config::{DEFAULT_CONFIG_FILE_NAME, DEFAULT_CONFIG_FILE_CONTENT}; create_temp_espanso_directories, create_temp_file_in_dir, create_user_config_file,
};
use crate::config::ConfigManager;
use crate::config::{DEFAULT_CONFIG_FILE_CONTENT, DEFAULT_CONFIG_FILE_NAME};
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use crate::config::ConfigManager; use tempfile::{NamedTempFile, TempDir};
use crate::config::tests::{create_temp_espanso_directories, create_temp_file_in_dir, create_user_config_file};
struct DummySystemManager { struct DummySystemManager {
title: RefCell<String>, title: RefCell<String>,
@ -232,7 +240,7 @@ mod tests {
DummySystemManager { DummySystemManager {
title: RefCell::new(title.to_owned()), title: RefCell::new(title.to_owned()),
class: RefCell::new(class.to_owned()), class: RefCell::new(class.to_owned()),
exec: RefCell::new(exec.to_owned()) exec: RefCell::new(exec.to_owned()),
} }
} }
@ -251,21 +259,33 @@ mod tests {
fn test_runtime_constructor_regex_load_correctly() { fn test_runtime_constructor_regex_load_correctly() {
let (data_dir, package_dir) = create_temp_espanso_directories(); let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" let specific_path = create_user_config_file(
&data_dir.path(),
"specific.yml",
r###"
name: myname1 name: myname1
filter_exec: "Title" filter_exec: "Title"
"###); "###,
);
let specific_path2 = create_user_config_file(&data_dir.path(), "specific2.yml", r###" let specific_path2 = create_user_config_file(
&data_dir.path(),
"specific2.yml",
r###"
name: myname2 name: myname2
filter_title: "Yeah" filter_title: "Yeah"
filter_class: "Car" filter_class: "Car"
"###); "###,
);
let specific_path3 = create_user_config_file(&data_dir.path(), "specific3.yml", r###" let specific_path3 = create_user_config_file(
&data_dir.path(),
"specific3.yml",
r###"
name: myname3 name: myname3
filter_title: "Nice" filter_title: "Nice"
"###); "###,
);
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
assert!(config_set.is_ok()); assert!(config_set.is_ok());
@ -274,12 +294,24 @@ mod tests {
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager); let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
let sp1index = config_manager.set.specific let sp1index = config_manager
.iter().position(|x| x.name == "myname1").unwrap(); .set
let sp2index = config_manager.set.specific .specific
.iter().position(|x| x.name == "myname2").unwrap(); .iter()
let sp3index = config_manager.set.specific .position(|x| x.name == "myname1")
.iter().position(|x| x.name == "myname3").unwrap(); .unwrap();
let sp2index = config_manager
.set
.specific
.iter()
.position(|x| x.name == "myname2")
.unwrap();
let sp3index = config_manager
.set
.specific
.iter()
.position(|x| x.name == "myname3")
.unwrap();
assert_eq!(config_manager.exec_regexps.len(), 3); assert_eq!(config_manager.exec_regexps.len(), 3);
assert_eq!(config_manager.title_regexps.len(), 3); assert_eq!(config_manager.title_regexps.len(), 3);
@ -302,21 +334,33 @@ mod tests {
fn test_runtime_constructor_malformed_regexes_are_ignored() { fn test_runtime_constructor_malformed_regexes_are_ignored() {
let (data_dir, package_dir) = create_temp_espanso_directories(); let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" let specific_path = create_user_config_file(
&data_dir.path(),
"specific.yml",
r###"
name: myname1 name: myname1
filter_exec: "[`-_]" filter_exec: "[`-_]"
"###); "###,
);
let specific_path2 = create_user_config_file(&data_dir.path(), "specific2.yml", r###" let specific_path2 = create_user_config_file(
&data_dir.path(),
"specific2.yml",
r###"
name: myname2 name: myname2
filter_title: "[`-_]" filter_title: "[`-_]"
filter_class: "Car" filter_class: "Car"
"###); "###,
);
let specific_path3 = create_user_config_file(&data_dir.path(), "specific3.yml", r###" let specific_path3 = create_user_config_file(
&data_dir.path(),
"specific3.yml",
r###"
name: myname3 name: myname3
filter_title: "Nice" filter_title: "Nice"
"###); "###,
);
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
assert!(config_set.is_ok()); assert!(config_set.is_ok());
@ -325,12 +369,24 @@ mod tests {
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager); let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
let sp1index = config_manager.set.specific let sp1index = config_manager
.iter().position(|x| x.name == "myname1").unwrap(); .set
let sp2index = config_manager.set.specific .specific
.iter().position(|x| x.name == "myname2").unwrap(); .iter()
let sp3index = config_manager.set.specific .position(|x| x.name == "myname1")
.iter().position(|x| x.name == "myname3").unwrap(); .unwrap();
let sp2index = config_manager
.set
.specific
.iter()
.position(|x| x.name == "myname2")
.unwrap();
let sp3index = config_manager
.set
.specific
.iter()
.position(|x| x.name == "myname3")
.unwrap();
assert_eq!(config_manager.exec_regexps.len(), 3); assert_eq!(config_manager.exec_regexps.len(), 3);
assert_eq!(config_manager.title_regexps.len(), 3); assert_eq!(config_manager.title_regexps.len(), 3);
@ -353,15 +409,20 @@ mod tests {
fn test_runtime_calculate_active_config_specific_title_match() { fn test_runtime_calculate_active_config_specific_title_match() {
let (data_dir, package_dir) = create_temp_espanso_directories(); let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" let specific_path = create_user_config_file(
&data_dir.path(),
"specific.yml",
r###"
name: chrome name: chrome
filter_title: "Chrome" filter_title: "Chrome"
"###); "###,
);
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
assert!(config_set.is_ok()); assert!(config_set.is_ok());
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe"); let dummy_system_manager =
DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager); let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
@ -371,15 +432,20 @@ mod tests {
fn test_runtime_calculate_active_config_specific_class_match() { fn test_runtime_calculate_active_config_specific_class_match() {
let (data_dir, package_dir) = create_temp_espanso_directories(); let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" let specific_path = create_user_config_file(
&data_dir.path(),
"specific.yml",
r###"
name: chrome name: chrome
filter_class: "Chrome" filter_class: "Chrome"
"###); "###,
);
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
assert!(config_set.is_ok()); assert!(config_set.is_ok());
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe"); let dummy_system_manager =
DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager); let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
@ -389,15 +455,20 @@ mod tests {
fn test_runtime_calculate_active_config_specific_exec_match() { fn test_runtime_calculate_active_config_specific_exec_match() {
let (data_dir, package_dir) = create_temp_espanso_directories(); let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" let specific_path = create_user_config_file(
&data_dir.path(),
"specific.yml",
r###"
name: chrome name: chrome
filter_exec: "chrome.exe" filter_exec: "chrome.exe"
"###); "###,
);
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
assert!(config_set.is_ok()); assert!(config_set.is_ok());
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe"); let dummy_system_manager =
DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager); let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
@ -407,16 +478,21 @@ mod tests {
fn test_runtime_calculate_active_config_specific_multi_filter_match() { fn test_runtime_calculate_active_config_specific_multi_filter_match() {
let (data_dir, package_dir) = create_temp_espanso_directories(); let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" let specific_path = create_user_config_file(
&data_dir.path(),
"specific.yml",
r###"
name: chrome name: chrome
filter_class: Browser filter_class: Browser
filter_exec: "firefox.exe" filter_exec: "firefox.exe"
"###); "###,
);
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
assert!(config_set.is_ok()); assert!(config_set.is_ok());
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Browser", "C:\\Path\\chrome.exe"); let dummy_system_manager =
DummySystemManager::new_custom("Google Chrome", "Browser", "C:\\Path\\chrome.exe");
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager); let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
@ -427,15 +503,20 @@ mod tests {
fn test_runtime_calculate_active_config_no_match() { fn test_runtime_calculate_active_config_no_match() {
let (data_dir, package_dir) = create_temp_espanso_directories(); let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" let specific_path = create_user_config_file(
&data_dir.path(),
"specific.yml",
r###"
name: firefox name: firefox
filter_title: "Firefox" filter_title: "Firefox"
"###); "###,
);
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
assert!(config_set.is_ok()); assert!(config_set.is_ok());
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe"); let dummy_system_manager =
DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager); let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
@ -446,22 +527,29 @@ mod tests {
fn test_runtime_active_config_cache() { fn test_runtime_active_config_cache() {
let (data_dir, package_dir) = create_temp_espanso_directories(); let (data_dir, package_dir) = create_temp_espanso_directories();
let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" let specific_path = create_user_config_file(
&data_dir.path(),
"specific.yml",
r###"
name: firefox name: firefox
filter_title: "Firefox" filter_title: "Firefox"
"###); "###,
);
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); let config_set = ConfigSet::load(data_dir.path(), package_dir.path());
assert!(config_set.is_ok()); assert!(config_set.is_ok());
let dummy_system_manager = DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe"); let dummy_system_manager =
DummySystemManager::new_custom("Google Chrome", "Chrome", "C:\\Path\\chrome.exe");
let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager); let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager);
assert_eq!(config_manager.active_config().name, "default"); assert_eq!(config_manager.active_config().name, "default");
assert_eq!(config_manager.calculate_active_config().name, "default"); assert_eq!(config_manager.calculate_active_config().name, "default");
config_manager.system_manager.change("Firefox", "Browser", "C\\Path\\firefox.exe"); config_manager
.system_manager
.change("Firefox", "Browser", "C\\Path\\firefox.exe");
// Active config should have changed, but not cached one // Active config should have changed, but not cached one
assert_eq!(config_manager.calculate_active_config().name, "firefox"); assert_eq!(config_manager.calculate_active_config().name, "firefox");

View File

@ -17,36 +17,32 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::sync::mpsc::Sender;
use std::os::raw::{c_void, c_char};
use crate::event::*;
use crate::event::KeyModifier::*;
use crate::bridge::linux::*; use crate::bridge::linux::*;
use std::process::exit; use crate::event::KeyModifier::*;
use crate::event::*;
use log::{error, info}; use log::{error, info};
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::{c_char, c_void};
use std::process::exit;
use std::sync::mpsc::Sender;
use std::{thread, time}; use std::{thread, time};
#[repr(C)] #[repr(C)]
pub struct LinuxContext { pub struct LinuxContext {
pub send_channel: Sender<Event> pub send_channel: Sender<Event>,
} }
impl LinuxContext { impl LinuxContext {
pub fn new(send_channel: Sender<Event>) -> Box<LinuxContext> { pub fn new(send_channel: Sender<Event>) -> Box<LinuxContext> {
// Check if the X11 context is available // Check if the X11 context is available
let x11_available = unsafe { let x11_available = unsafe { check_x11() };
check_x11()
};
if x11_available < 0 { if x11_available < 0 {
error!("Error, can't connect to X11 context"); error!("Error, can't connect to X11 context");
std::process::exit(100); std::process::exit(100);
} }
let context = Box::new(LinuxContext { let context = Box::new(LinuxContext { send_channel });
send_channel,
});
unsafe { unsafe {
let context_ptr = &*context as *const LinuxContext as *const c_void; let context_ptr = &*context as *const LinuxContext as *const c_void;
@ -74,18 +70,26 @@ impl super::Context for LinuxContext {
impl Drop for LinuxContext { impl Drop for LinuxContext {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { cleanup(); } unsafe {
cleanup();
}
} }
} }
// Native bridge code // Native bridge code
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, extern "C" fn keypress_callback(
is_modifier: i32, key_code: i32) { _self: *mut c_void,
raw_buffer: *const u8,
len: i32,
is_modifier: i32,
key_code: i32,
) {
unsafe { unsafe {
let _self = _self as *mut LinuxContext; let _self = _self as *mut LinuxContext;
if is_modifier == 0 { // Char event if is_modifier == 0 {
// Char event
// Convert the received buffer to a string // Convert the received buffer to a string
let c_str = CStr::from_ptr(raw_buffer as (*const c_char)); let c_str = CStr::from_ptr(raw_buffer as (*const c_char));
let char_str = c_str.to_str(); let char_str = c_str.to_str();
@ -95,12 +99,13 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
Ok(char_str) => { Ok(char_str) => {
let event = Event::Key(KeyEvent::Char(char_str.to_owned())); let event = Event::Key(KeyEvent::Char(char_str.to_owned()));
(*_self).send_channel.send(event).unwrap(); (*_self).send_channel.send(event).unwrap();
}, }
Err(e) => { Err(e) => {
error!("Unable to receive char: {}", e); error!("Unable to receive char: {}", e);
},
} }
}else{ // Modifier event }
} else {
// Modifier event
let modifier: Option<KeyModifier> = match key_code { let modifier: Option<KeyModifier> = match key_code {
133 => Some(META), 133 => Some(META),
50 => Some(SHIFT), 50 => Some(SHIFT),

View File

@ -17,20 +17,20 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::sync::mpsc::Sender;
use std::os::raw::{c_void, c_char};
use crate::bridge::macos::*; use crate::bridge::macos::*;
use crate::event::{Event, KeyEvent, KeyModifier, ActionType};
use crate::event::KeyModifier::*; use crate::event::KeyModifier::*;
use std::ffi::{CString, CStr}; use crate::event::{ActionType, Event, KeyEvent, KeyModifier};
use log::{error, info};
use std::ffi::{CStr, CString};
use std::fs; use std::fs;
use log::{info, error}; use std::os::raw::{c_char, c_void};
use std::process::exit; use std::process::exit;
use std::sync::mpsc::Sender;
const STATUS_ICON_BINARY: &[u8] = include_bytes!("../res/mac/icon.png"); const STATUS_ICON_BINARY: &[u8] = include_bytes!("../res/mac/icon.png");
pub struct MacContext { pub struct MacContext {
pub send_channel: Sender<Event> pub send_channel: Sender<Event>,
} }
impl MacContext { impl MacContext {
@ -41,15 +41,15 @@ impl MacContext {
if res == 0 { if res == 0 {
error!("Accessibility must be enabled to make espanso work on MacOS."); error!("Accessibility must be enabled to make espanso work on MacOS.");
error!("Please allow espanso in the Security & Privacy panel, then restart espanso."); error!(
"Please allow espanso in the Security & Privacy panel, then restart espanso."
);
error!("For more information: https://espanso.org/install/mac/"); error!("For more information: https://espanso.org/install/mac/");
exit(1); exit(1);
} }
} }
let context = Box::new(MacContext { let context = Box::new(MacContext { send_channel });
send_channel
});
// Initialize the status icon path // Initialize the status icon path
let espanso_dir = super::get_data_dir(); let espanso_dir = super::get_data_dir();
@ -59,7 +59,10 @@ impl MacContext {
info!("Status icon already initialized, skipping."); info!("Status icon already initialized, skipping.");
} else { } else {
fs::write(&status_icon_target, STATUS_ICON_BINARY).unwrap_or_else(|e| { fs::write(&status_icon_target, STATUS_ICON_BINARY).unwrap_or_else(|e| {
error!("Error copying the Status Icon to the espanso data directory: {}", e); error!(
"Error copying the Status Icon to the espanso data directory: {}",
e
);
}); });
} }
@ -70,7 +73,8 @@ impl MacContext {
register_icon_click_callback(icon_click_callback); register_icon_click_callback(icon_click_callback);
register_context_menu_click_callback(context_menu_click_callback); register_context_menu_click_callback(context_menu_click_callback);
let status_icon_path = CString::new(status_icon_target.to_str().unwrap_or_default()).unwrap_or_default(); let status_icon_path =
CString::new(status_icon_target.to_str().unwrap_or_default()).unwrap_or_default();
initialize(context_ptr, status_icon_path.as_ptr()); initialize(context_ptr, status_icon_path.as_ptr());
} }
@ -88,12 +92,18 @@ impl super::Context for MacContext {
// Native bridge code // Native bridge code
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, extern "C" fn keypress_callback(
is_modifier: i32, key_code: i32) { _self: *mut c_void,
raw_buffer: *const u8,
len: i32,
is_modifier: i32,
key_code: i32,
) {
unsafe { unsafe {
let _self = _self as *mut MacContext; let _self = _self as *mut MacContext;
if is_modifier == 0 { // Char event if is_modifier == 0 {
// Char event
// Convert the received buffer to a string // Convert the received buffer to a string
let c_str = CStr::from_ptr(raw_buffer as (*const c_char)); let c_str = CStr::from_ptr(raw_buffer as (*const c_char));
let char_str = c_str.to_str(); let char_str = c_str.to_str();
@ -103,12 +113,13 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
Ok(char_str) => { Ok(char_str) => {
let event = Event::Key(KeyEvent::Char(char_str.to_owned())); let event = Event::Key(KeyEvent::Char(char_str.to_owned()));
(*_self).send_channel.send(event).unwrap(); (*_self).send_channel.send(event).unwrap();
}, }
Err(e) => { Err(e) => {
error!("Unable to receive char: {}", e); error!("Unable to receive char: {}", e);
},
} }
}else{ // Modifier event }
} else {
// Modifier event
let modifier: Option<KeyModifier> = match key_code { let modifier: Option<KeyModifier> = match key_code {
0x37 => Some(META), 0x37 => Some(META),
0x38 => Some(SHIFT), 0x38 => Some(SHIFT),
@ -126,7 +137,7 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
} }
} }
extern fn icon_click_callback(_self: *mut c_void) { extern "C" fn icon_click_callback(_self: *mut c_void) {
unsafe { unsafe {
let _self = _self as *mut MacContext; let _self = _self as *mut MacContext;
@ -135,7 +146,7 @@ extern fn icon_click_callback(_self: *mut c_void) {
} }
} }
extern fn context_menu_click_callback(_self: *mut c_void, id: i32) { extern "C" fn context_menu_click_callback(_self: *mut c_void, id: i32) {
unsafe { unsafe {
let _self = _self as *mut MacContext; let _self = _self as *mut MacContext;

View File

@ -26,10 +26,10 @@ mod linux;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub(crate) mod macos; pub(crate) mod macos;
use std::sync::mpsc::Sender;
use crate::event::Event; use crate::event::Event;
use std::path::PathBuf;
use std::fs::create_dir_all; use std::fs::create_dir_all;
use std::path::PathBuf;
use std::sync::mpsc::Sender;
use std::sync::Once; use std::sync::Once;
pub trait Context { pub trait Context {
@ -73,7 +73,10 @@ pub fn get_config_dir() -> PathBuf {
if let Some(parent) = exe_dir { if let Some(parent) = exe_dir {
let config_dir = parent.join(".espanso"); let config_dir = parent.join(".espanso");
if config_dir.exists() { if config_dir.exists() {
println!("PORTABLE MODE, using config folder: '{}'", config_dir.to_string_lossy()); println!(
"PORTABLE MODE, using config folder: '{}'",
config_dir.to_string_lossy()
);
return config_dir; return config_dir;
} }
} }

View File

@ -17,14 +17,14 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::sync::mpsc::Sender;
use crate::bridge::windows::*; use crate::bridge::windows::*;
use crate::event::{Event, KeyEvent, KeyModifier, ActionType};
use crate::event::KeyModifier::*; use crate::event::KeyModifier::*;
use crate::event::{ActionType, Event, KeyEvent, KeyModifier};
use log::{error, info};
use std::ffi::c_void; use std::ffi::c_void;
use std::{fs}; use std::fs;
use widestring::{U16CString, U16CStr}; use std::sync::mpsc::Sender;
use log::{info, error}; use widestring::{U16CStr, U16CString};
const BMP_BINARY: &[u8] = include_bytes!("../res/win/espanso.bmp"); const BMP_BINARY: &[u8] = include_bytes!("../res/win/espanso.bmp");
const ICO_BINARY: &[u8] = include_bytes!("../res/win/espanso.ico"); const ICO_BINARY: &[u8] = include_bytes!("../res/win/espanso.ico");
@ -39,26 +39,33 @@ impl WindowsContext {
let espanso_dir = super::get_data_dir(); let espanso_dir = super::get_data_dir();
info!("Initializing Espanso resources in {}", espanso_dir.as_path().display()); info!(
"Initializing Espanso resources in {}",
espanso_dir.as_path().display()
);
let espanso_bmp_image = espanso_dir.join("espansoicon.bmp"); let espanso_bmp_image = espanso_dir.join("espansoicon.bmp");
if espanso_bmp_image.exists() { if espanso_bmp_image.exists() {
info!("BMP already initialized, skipping."); info!("BMP already initialized, skipping.");
} else { } else {
fs::write(&espanso_bmp_image, BMP_BINARY) fs::write(&espanso_bmp_image, BMP_BINARY).expect("Unable to write windows bmp file");
.expect("Unable to write windows bmp file");
info!("Extracted bmp icon to: {}", espanso_bmp_image.to_str().unwrap_or("error")); info!(
"Extracted bmp icon to: {}",
espanso_bmp_image.to_str().unwrap_or("error")
);
} }
let espanso_ico_image = espanso_dir.join("espanso.ico"); let espanso_ico_image = espanso_dir.join("espanso.ico");
if espanso_ico_image.exists() { if espanso_ico_image.exists() {
info!("ICO already initialized, skipping."); info!("ICO already initialized, skipping.");
} else { } else {
fs::write(&espanso_ico_image, ICO_BINARY) fs::write(&espanso_ico_image, ICO_BINARY).expect("Unable to write windows ico file");
.expect("Unable to write windows ico file");
info!("Extracted 'ico' icon to: {}", espanso_ico_image.to_str().unwrap_or("error")); info!(
"Extracted 'ico' icon to: {}",
espanso_ico_image.to_str().unwrap_or("error")
);
} }
let bmp_icon = espanso_bmp_image.to_str().unwrap_or_default(); let bmp_icon = espanso_bmp_image.to_str().unwrap_or_default();
@ -66,9 +73,7 @@ impl WindowsContext {
let send_channel = send_channel; let send_channel = send_channel;
let context = Box::new(WindowsContext{ let context = Box::new(WindowsContext { send_channel });
send_channel,
});
unsafe { unsafe {
let context_ptr = &*context as *const WindowsContext as *const c_void; let context_ptr = &*context as *const WindowsContext as *const c_void;
@ -102,12 +107,20 @@ impl super::Context for WindowsContext {
// Native bridge code // Native bridge code
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32, extern "C" fn keypress_callback(
is_modifier: i32, key_code: i32, is_key_down: i32) { _self: *mut c_void,
raw_buffer: *const u16,
len: i32,
is_modifier: i32,
key_code: i32,
is_key_down: i32,
) {
unsafe { unsafe {
let _self = _self as *mut WindowsContext; let _self = _self as *mut WindowsContext;
if is_key_down != 0 { // KEY DOWN EVENT if is_key_down != 0 {
if is_modifier == 0 { // Char event // KEY DOWN EVENT
if is_modifier == 0 {
// Char event
// Convert the received buffer to a string // Convert the received buffer to a string
let buffer = std::slice::from_raw_parts(raw_buffer, len as usize); let buffer = std::slice::from_raw_parts(raw_buffer, len as usize);
let c_string = U16CStr::from_slice_with_nul(buffer); let c_string = U16CStr::from_slice_with_nul(buffer);
@ -120,17 +133,19 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32
Ok(string) => { Ok(string) => {
let event = Event::Key(KeyEvent::Char(string)); let event = Event::Key(KeyEvent::Char(string));
(*_self).send_channel.send(event).unwrap(); (*_self).send_channel.send(event).unwrap();
}, }
Err(e) => { Err(e) => {
error!("Unable to receive char: {}", e); error!("Unable to receive char: {}", e);
}, }
} }
} else { } else {
error!("unable to decode widechar"); error!("unable to decode widechar");
} }
} }
}else{ // KEY UP event } else {
if is_modifier != 0 { // Modifier event // KEY UP event
if is_modifier != 0 {
// Modifier event
let modifier: Option<KeyModifier> = match key_code { let modifier: Option<KeyModifier> = match key_code {
0x5B | 0x5C => Some(META), 0x5B | 0x5C => Some(META),
0x10 => Some(SHIFT), 0x10 => Some(SHIFT),
@ -149,7 +164,7 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32
} }
} }
extern fn icon_click_callback(_self: *mut c_void) { extern "C" fn icon_click_callback(_self: *mut c_void) {
unsafe { unsafe {
let _self = _self as *mut WindowsContext; let _self = _self as *mut WindowsContext;
@ -158,8 +173,7 @@ extern fn icon_click_callback(_self: *mut c_void) {
} }
} }
extern "C" fn context_menu_click_callback(_self: *mut c_void, id: i32) {
extern fn context_menu_click_callback(_self: *mut c_void, id: i32) {
unsafe { unsafe {
let _self = _self as *mut WindowsContext; let _self = _self as *mut WindowsContext;

View File

@ -17,25 +17,31 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use crate::matcher::{Match, MatchReceiver, MatchContentType};
use crate::keyboard::KeyboardManager;
use crate::config::ConfigManager;
use crate::config::BackendType;
use crate::clipboard::ClipboardManager; use crate::clipboard::ClipboardManager;
use log::{info, warn, error}; use crate::config::BackendType;
use crate::ui::{UIManager, MenuItem, MenuItemType}; use crate::config::ConfigManager;
use crate::event::{ActionEventReceiver, ActionType}; use crate::event::{ActionEventReceiver, ActionType};
use crate::extension::Extension; use crate::extension::Extension;
use crate::render::{Renderer, RenderResult}; use crate::keyboard::KeyboardManager;
use crate::matcher::{Match, MatchContentType, MatchReceiver};
use crate::render::{RenderResult, Renderer};
use crate::ui::{MenuItem, MenuItemType, UIManager};
use log::{error, info, warn};
use regex::{Captures, Regex};
use std::cell::RefCell; use std::cell::RefCell;
use std::process::exit;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use regex::{Regex, Captures}; use std::process::exit;
use std::time::SystemTime; use std::time::SystemTime;
pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, pub struct Engine<
U: UIManager, R: Renderer> { 'a,
S: KeyboardManager,
C: ClipboardManager,
M: ConfigManager<'a>,
U: UIManager,
R: Renderer,
> {
keyboard_manager: &'a S, keyboard_manager: &'a S,
clipboard_manager: &'a C, clipboard_manager: &'a C,
config_manager: &'a M, config_manager: &'a M,
@ -47,16 +53,28 @@ pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<
action_noop_interval: u128, action_noop_interval: u128,
} }
impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager, R: Renderer> impl<
Engine<'a, S, C, M, U, R> { 'a,
pub fn new(keyboard_manager: &'a S, clipboard_manager: &'a C, S: KeyboardManager,
config_manager: &'a M, ui_manager: &'a U, C: ClipboardManager,
renderer: &'a R) -> Engine<'a, S, C, M, U, R> { M: ConfigManager<'a>,
U: UIManager,
R: Renderer,
> Engine<'a, S, C, M, U, R>
{
pub fn new(
keyboard_manager: &'a S,
clipboard_manager: &'a C,
config_manager: &'a M,
ui_manager: &'a U,
renderer: &'a R,
) -> Engine<'a, S, C, M, U, R> {
let enabled = RefCell::new(true); let enabled = RefCell::new(true);
let last_action_time = RefCell::new(SystemTime::now()); let last_action_time = RefCell::new(SystemTime::now());
let action_noop_interval = config_manager.default_config().action_noop_interval; let action_noop_interval = config_manager.default_config().action_noop_interval;
Engine{keyboard_manager, Engine {
keyboard_manager,
clipboard_manager, clipboard_manager,
config_manager, config_manager,
ui_manager, ui_manager,
@ -71,11 +89,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
let mut menu = Vec::new(); let mut menu = Vec::new();
let enabled = self.enabled.borrow(); let enabled = self.enabled.borrow();
let toggle_text = if *enabled { let toggle_text = if *enabled { "Disable" } else { "Enable" }.to_owned();
"Disable"
}else{
"Enable"
}.to_owned();
menu.push(MenuItem { menu.push(MenuItem {
item_type: MenuItemType::Button, item_type: MenuItemType::Button,
item_name: toggle_text, item_name: toggle_text,
@ -102,8 +116,8 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
// clipboard content in order to restore it later. // clipboard content in order to restore it later.
if self.config_manager.default_config().preserve_clipboard { if self.config_manager.default_config().preserve_clipboard {
match self.clipboard_manager.get_clipboard() { match self.clipboard_manager.get_clipboard() {
Some(clipboard) => {Some(clipboard)}, Some(clipboard) => Some(clipboard),
None => {None}, None => None,
} }
} else { } else {
None None
@ -129,9 +143,15 @@ lazy_static! {
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P<name>\\w+)\\s*\\}\\}").unwrap(); static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P<name>\\w+)\\s*\\}\\}").unwrap();
} }
impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager, R: Renderer> impl<
MatchReceiver for Engine<'a, S, C, M, U, R>{ 'a,
S: KeyboardManager,
C: ClipboardManager,
M: ConfigManager<'a>,
U: UIManager,
R: Renderer,
> MatchReceiver for Engine<'a, S, C, M, U, R>
{
fn on_match(&self, m: &Match, trailing_separator: Option<char>) { fn on_match(&self, m: &Match, trailing_separator: Option<char>) {
let config = self.config_manager.active_config(); let config = self.config_manager.active_config();
@ -160,7 +180,8 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
RenderResult::Text(mut target_string) => { RenderResult::Text(mut target_string) => {
// If a trailing separator was counted in the match, add it back to the target string // If a trailing separator was counted in the match, add it back to the target string
if let Some(trailing_separator) = trailing_separator { if let Some(trailing_separator) = trailing_separator {
if trailing_separator == '\r' { // If the trailing separator is a carriage return, if trailing_separator == '\r' {
// If the trailing separator is a carriage return,
target_string.push('\n'); // convert it to new line target_string.push('\n'); // convert it to new line
} else { } else {
target_string.push(trailing_separator); target_string.push(trailing_separator);
@ -208,22 +229,23 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
self.keyboard_manager.send_string(split); self.keyboard_manager.send_string(split);
} }
} }
}, }
BackendType::Clipboard => { BackendType::Clipboard => {
// If the preserve_clipboard option is enabled, save the current // If the preserve_clipboard option is enabled, save the current
// clipboard content to restore it later. // 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.clipboard_manager.set_clipboard(&target_string);
self.keyboard_manager.trigger_paste(&config.paste_shortcut); self.keyboard_manager.trigger_paste(&config.paste_shortcut);
}, }
} }
if let Some(moves) = cursor_rewind { if let Some(moves) = cursor_rewind {
// Simulate left arrow key presses to bring the cursor into the desired position // Simulate left arrow key presses to bring the cursor into the desired position
self.keyboard_manager.move_cursor_left(moves); self.keyboard_manager.move_cursor_left(moves);
} }
}, }
RenderResult::Image(image_path) => { RenderResult::Image(image_path) => {
// If the preserve_clipboard option is enabled, save the current // If the preserve_clipboard option is enabled, save the current
// clipboard content to restore it later. // clipboard content to restore it later.
@ -231,19 +253,22 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
self.clipboard_manager.set_clipboard_image(&image_path); self.clipboard_manager.set_clipboard_image(&image_path);
self.keyboard_manager.trigger_paste(&config.paste_shortcut); self.keyboard_manager.trigger_paste(&config.paste_shortcut);
}, }
RenderResult::Error => { RenderResult::Error => {
error!("Could not render match: {}", m.trigger); error!("Could not render match: {}", m.trigger);
}, }
} }
// Restore previous clipboard content // Restore previous clipboard content
if let Some(previous_clipboard_content) = previous_clipboard_content { if let Some(previous_clipboard_content) = previous_clipboard_content {
// Sometimes an expansion gets overwritten before pasting by the previous content // Sometimes an expansion gets overwritten before pasting by the previous content
// A delay is needed to mitigate the problem // A delay is needed to mitigate the problem
std::thread::sleep(std::time::Duration::from_millis(config.restore_clipboard_delay as u64)); std::thread::sleep(std::time::Duration::from_millis(
config.restore_clipboard_delay as u64,
));
self.clipboard_manager.set_clipboard(&previous_clipboard_content); self.clipboard_manager
.set_clipboard(&previous_clipboard_content);
} }
} }
@ -291,8 +316,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
let clipboard = self.clipboard_manager.get_clipboard(); let clipboard = self.clipboard_manager.get_clipboard();
if let Some(clipboard) = clipboard { if let Some(clipboard) = clipboard {
let rendered = self.renderer.render_passive(&clipboard, let rendered = self.renderer.render_passive(&clipboard, &config);
&config);
match rendered { match rendered {
RenderResult::Text(payload) => { RenderResult::Text(payload) => {
@ -301,28 +325,32 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding
self.keyboard_manager.trigger_paste(&config.paste_shortcut); self.keyboard_manager.trigger_paste(&config.paste_shortcut);
}, }
_ => { _ => warn!("Cannot expand passive match"),
warn!("Cannot expand passive match")
},
} }
} }
} }
} }
impl <'a, S: KeyboardManager, C: ClipboardManager, impl<
M: ConfigManager<'a>, U: UIManager, R: Renderer> ActionEventReceiver for Engine<'a, S, C, M, U, R>{ 'a,
S: KeyboardManager,
C: ClipboardManager,
M: ConfigManager<'a>,
U: UIManager,
R: Renderer,
> ActionEventReceiver for Engine<'a, S, C, M, U, R>
{
fn on_action_event(&self, e: ActionType) { fn on_action_event(&self, e: ActionType) {
match e { match e {
ActionType::IconClick => { ActionType::IconClick => {
self.ui_manager.show_menu(self.build_menu()); self.ui_manager.show_menu(self.build_menu());
}, }
ActionType::Exit => { ActionType::Exit => {
info!("Terminating espanso."); info!("Terminating espanso.");
self.ui_manager.cleanup(); self.ui_manager.cleanup();
exit(0); exit(0);
}, }
_ => {} _ => {}
} }
} }

View File

@ -17,7 +17,7 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use crate::event::{KeyEventReceiver, ActionEventReceiver, Event}; use crate::event::{ActionEventReceiver, Event, KeyEventReceiver};
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
pub trait EventManager { pub trait EventManager {
@ -31,8 +31,11 @@ pub struct DefaultEventManager<'a> {
} }
impl<'a> DefaultEventManager<'a> { impl<'a> DefaultEventManager<'a> {
pub fn new(receive_channel: Receiver<Event>, key_receivers: Vec<&'a dyn KeyEventReceiver>, pub fn new(
action_receivers: Vec<&'a dyn ActionEventReceiver>) -> DefaultEventManager<'a> { receive_channel: Receiver<Event>,
key_receivers: Vec<&'a dyn KeyEventReceiver>,
action_receivers: Vec<&'a dyn ActionEventReceiver>,
) -> DefaultEventManager<'a> {
DefaultEventManager { DefaultEventManager {
receive_channel, receive_channel,
key_receivers, key_receivers,
@ -45,14 +48,16 @@ impl <'a> EventManager for DefaultEventManager<'a> {
fn eventloop(&self) { fn eventloop(&self) {
loop { loop {
match self.receive_channel.recv() { match self.receive_channel.recv() {
Ok(event) => { Ok(event) => match event {
match event {
Event::Key(key_event) => { Event::Key(key_event) => {
self.key_receivers.iter().for_each(move |&receiver| receiver.on_key_event(key_event.clone())); self.key_receivers
}, .iter()
Event::Action(action_event) => { .for_each(move |&receiver| receiver.on_key_event(key_event.clone()));
self.action_receivers.iter().for_each(|&receiver| receiver.on_action_event(action_event.clone()));
} }
Event::Action(action_event) => {
self.action_receivers
.iter()
.for_each(|&receiver| receiver.on_action_event(action_event.clone()));
} }
}, },
Err(e) => panic!("Broken event channel {}", e), Err(e) => panic!("Broken event channel {}", e),

View File

@ -19,12 +19,12 @@
pub(crate) mod manager; pub(crate) mod manager;
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Event { pub enum Event {
Action(ActionType), Action(ActionType),
Key(KeyEvent) Key(KeyEvent),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -53,7 +53,7 @@ impl From<i32> for ActionType {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum KeyEvent { pub enum KeyEvent {
Char(String), Char(String),
Modifier(KeyModifier) Modifier(KeyModifier),
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

View File

@ -17,8 +17,8 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use serde_yaml::{Mapping, Value};
use chrono::{DateTime, Local}; use chrono::{DateTime, Local};
use serde_yaml::{Mapping, Value};
pub struct DateExtension {} pub struct DateExtension {}

View File

@ -20,10 +20,10 @@
use serde_yaml::Mapping; use serde_yaml::Mapping;
mod date; mod date;
mod shell;
mod script;
mod random;
mod dummy; mod dummy;
mod random;
mod script;
mod shell;
pub trait Extension { pub trait Extension {
fn name(&self) -> String; fn name(&self) -> String;

View File

@ -17,9 +17,9 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use serde_yaml::{Mapping, Value}; use log::{error, warn};
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use log::{warn, error}; use serde_yaml::{Mapping, Value};
pub struct RandomExtension {} pub struct RandomExtension {}
@ -38,13 +38,14 @@ impl super::Extension for RandomExtension {
let choices = params.get(&Value::from("choices")); let choices = params.get(&Value::from("choices"));
if choices.is_none() { if choices.is_none() {
warn!("No 'choices' parameter specified for random variable"); warn!("No 'choices' parameter specified for random variable");
return None return None;
} }
let choices = choices.unwrap().as_sequence(); let choices = choices.unwrap().as_sequence();
if let Some(choices) = choices { if let Some(choices) = choices {
let str_choices = choices.iter().map(|arg| { let str_choices = choices
arg.as_str().unwrap_or_default().to_string() .iter()
}).collect::<Vec<String>>(); .map(|arg| arg.as_str().unwrap_or_default().to_string())
.collect::<Vec<String>>();
// Select a random choice between the possibilities // Select a random choice between the possibilities
let choice = str_choices.choose(&mut rand::thread_rng()); let choice = str_choices.choose(&mut rand::thread_rng());
@ -54,14 +55,13 @@ impl super::Extension for RandomExtension {
// Render arguments // Render arguments
let output = crate::render::utils::render_args(output, args); let output = crate::render::utils::render_args(output, args);
return Some(output) return Some(output);
}, }
None => { None => {
error!("Could not select a random choice."); error!("Could not select a random choice.");
return None return None;
}, }
} }
} }
error!("choices array have an invalid format '{:?}'", choices); error!("choices array have an invalid format '{:?}'", choices);
@ -77,11 +77,7 @@ mod tests {
#[test] #[test]
fn test_random_basic() { fn test_random_basic() {
let mut params = Mapping::new(); let mut params = Mapping::new();
let choices = vec!( let choices = vec!["first", "second", "third"];
"first",
"second",
"third",
);
params.insert(Value::from("choices"), Value::from(choices.clone())); params.insert(Value::from("choices"), Value::from(choices.clone()));
let extension = RandomExtension::new(); let extension = RandomExtension::new();
@ -97,11 +93,7 @@ mod tests {
#[test] #[test]
fn test_random_with_args() { fn test_random_with_args() {
let mut params = Mapping::new(); let mut params = Mapping::new();
let choices = vec!( let choices = vec!["first $0$", "second $0$", "$0$ third"];
"first $0$",
"second $0$",
"$0$ third",
);
params.insert(Value::from("choices"), Value::from(choices.clone())); params.insert(Value::from("choices"), Value::from(choices.clone()));
let extension = RandomExtension::new(); let extension = RandomExtension::new();
@ -111,11 +103,7 @@ mod tests {
let output = output.unwrap(); let output = output.unwrap();
let rendered_choices = vec!( let rendered_choices = vec!["first test", "second test", "test third"];
"first test",
"second test",
"test third",
);
assert!(rendered_choices.iter().any(|x| x == &output)); assert!(rendered_choices.iter().any(|x| x == &output));
} }

View File

@ -17,9 +17,9 @@
* 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, warn};
use serde_yaml::{Mapping, Value}; use serde_yaml::{Mapping, Value};
use std::process::Command; use std::process::Command;
use log::{warn, error};
pub struct ScriptExtension {} pub struct ScriptExtension {}
@ -38,28 +38,29 @@ impl super::Extension for ScriptExtension {
let args = params.get(&Value::from("args")); let args = params.get(&Value::from("args"));
if args.is_none() { if args.is_none() {
warn!("No 'args' parameter specified for script variable"); warn!("No 'args' parameter specified for script variable");
return None return None;
} }
let args = args.unwrap().as_sequence(); let args = args.unwrap().as_sequence();
if let Some(args) = args { if let Some(args) = args {
let mut str_args = args.iter().map(|arg| { let mut str_args = args
arg.as_str().unwrap_or_default().to_string() .iter()
}).collect::<Vec<String>>(); .map(|arg| arg.as_str().unwrap_or_default().to_string())
.collect::<Vec<String>>();
// The user has to enable argument concatenation explicitly // The user has to enable argument concatenation explicitly
let inject_args = params.get(&Value::from("inject_args")) let inject_args = params
.unwrap_or(&Value::from(false)).as_bool().unwrap_or(false); .get(&Value::from("inject_args"))
.unwrap_or(&Value::from(false))
.as_bool()
.unwrap_or(false);
if inject_args { if inject_args {
str_args.extend(user_args.clone()); str_args.extend(user_args.clone());
} }
let output = if str_args.len() > 1 { let output = if str_args.len() > 1 {
Command::new(&str_args[0]) Command::new(&str_args[0]).args(&str_args[1..]).output()
.args(&str_args[1..])
.output()
} else { } else {
Command::new(&str_args[0]) Command::new(&str_args[0]).output()
.output()
}; };
println!("{:?}", output); println!("{:?}", output);
@ -67,12 +68,12 @@ impl super::Extension for ScriptExtension {
Ok(output) => { Ok(output) => {
let output_str = String::from_utf8_lossy(output.stdout.as_slice()); let output_str = String::from_utf8_lossy(output.stdout.as_slice());
return Some(output_str.into_owned()) return Some(output_str.into_owned());
}, }
Err(e) => { Err(e) => {
error!("Could not execute script '{:?}', error: {}", args, e); error!("Could not execute script '{:?}', error: {}", args, e);
return None return None;
}, }
} }
} }
@ -90,7 +91,10 @@ mod tests {
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
fn test_script_basic() { fn test_script_basic() {
let mut params = Mapping::new(); let mut params = Mapping::new();
params.insert(Value::from("args"), Value::from(vec!["echo", "hello world"])); params.insert(
Value::from("args"),
Value::from(vec!["echo", "hello world"]),
);
let extension = ScriptExtension::new(); let extension = ScriptExtension::new();
let output = extension.calculate(&params, &vec![]); let output = extension.calculate(&params, &vec![]);
@ -103,7 +107,10 @@ mod tests {
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
fn test_script_inject_args_off() { fn test_script_inject_args_off() {
let mut params = Mapping::new(); let mut params = Mapping::new();
params.insert(Value::from("args"), Value::from(vec!["echo", "hello world"])); params.insert(
Value::from("args"),
Value::from(vec!["echo", "hello world"]),
);
let extension = ScriptExtension::new(); let extension = ScriptExtension::new();
let output = extension.calculate(&params, &vec!["jon".to_owned()]); let output = extension.calculate(&params, &vec!["jon".to_owned()]);
@ -116,7 +123,10 @@ mod tests {
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
fn test_script_inject_args_on() { fn test_script_inject_args_on() {
let mut params = Mapping::new(); let mut params = Mapping::new();
params.insert(Value::from("args"), Value::from(vec!["echo", "hello world"])); params.insert(
Value::from("args"),
Value::from(vec!["echo", "hello world"]),
);
params.insert(Value::from("inject_args"), Value::from(true)); params.insert(Value::from("inject_args"), Value::from(true));
let extension = ScriptExtension::new(); let extension = ScriptExtension::new();

View File

@ -17,10 +17,10 @@
* 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, warn};
use regex::{Captures, Regex};
use serde_yaml::{Mapping, Value}; use serde_yaml::{Mapping, Value};
use std::process::Command; use std::process::Command;
use log::{warn, error};
use regex::{Regex, Captures};
lazy_static! { lazy_static! {
static ref POS_ARG_REGEX: Regex = if cfg!(target_os = "windows") { static ref POS_ARG_REGEX: Regex = if cfg!(target_os = "windows") {
@ -47,12 +47,13 @@ impl super::Extension for ShellExtension {
let cmd = params.get(&Value::from("cmd")); let cmd = params.get(&Value::from("cmd"));
if cmd.is_none() { if cmd.is_none() {
warn!("No 'cmd' parameter specified for shell variable"); warn!("No 'cmd' parameter specified for shell variable");
return None return None;
} }
let cmd = cmd.unwrap().as_str().unwrap(); let cmd = cmd.unwrap().as_str().unwrap();
// Render positional parameters in args // Render positional parameters in args
let cmd = POS_ARG_REGEX.replace_all(&cmd, |caps: &Captures| { let cmd = POS_ARG_REGEX
.replace_all(&cmd, |caps: &Captures| {
let position_str = caps.name("pos").unwrap().as_str(); let position_str = caps.name("pos").unwrap().as_str();
let position = position_str.parse::<i32>().unwrap_or(-1); let position = position_str.parse::<i32>().unwrap_or(-1);
if position >= 0 && position < args.len() as i32 { if position >= 0 && position < args.len() as i32 {
@ -60,17 +61,13 @@ impl super::Extension for ShellExtension {
} else { } else {
"".to_owned() "".to_owned()
} }
}).to_string(); })
.to_string();
let output = if cfg!(target_os = "windows") { let output = if cfg!(target_os = "windows") {
Command::new("cmd") Command::new("cmd").args(&["/C", &cmd]).output()
.args(&["/C", &cmd])
.output()
} else { } else {
Command::new("sh") Command::new("sh").arg("-c").arg(&cmd).output()
.arg("-c")
.arg(&cmd)
.output()
}; };
match output { match output {
@ -90,11 +87,11 @@ impl super::Extension for ShellExtension {
} }
Some(output_str) Some(output_str)
}, }
Err(e) => { Err(e) => {
error!("Could not execute cmd '{}', error: {}", cmd, e); error!("Could not execute cmd '{}', error: {}", cmd, e);
None None
}, }
} }
} }
} }
@ -140,7 +137,10 @@ mod tests {
if cfg!(target_os = "windows") { if cfg!(target_os = "windows") {
params.insert(Value::from("cmd"), Value::from("echo hello world ")); params.insert(Value::from("cmd"), Value::from("echo hello world "));
} else { } else {
params.insert(Value::from("cmd"), Value::from("echo \" hello world \"")); params.insert(
Value::from("cmd"),
Value::from("echo \" hello world \""),
);
} }
params.insert(Value::from("trim"), Value::from(true)); params.insert(Value::from("trim"), Value::from(true));

View File

@ -17,20 +17,21 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::ffi::CString;
use crate::bridge::linux::*;
use super::PasteShortcut; use super::PasteShortcut;
use crate::bridge::linux::*;
use log::error; use log::error;
use std::ffi::CString;
pub struct LinuxKeyboardManager { pub struct LinuxKeyboardManager {}
}
impl super::KeyboardManager for LinuxKeyboardManager { impl super::KeyboardManager for LinuxKeyboardManager {
fn send_string(&self, s: &str) { fn send_string(&self, s: &str) {
let res = CString::new(s); let res = CString::new(s);
match res { match res {
Ok(cstr) => unsafe { send_string(cstr.as_ptr()); } Ok(cstr) => unsafe {
Err(e) => panic!(e.to_string()) send_string(cstr.as_ptr());
},
Err(e) => panic!(e.to_string()),
} }
} }

View File

@ -17,20 +17,21 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::ffi::CString;
use crate::bridge::macos::*;
use super::PasteShortcut; use super::PasteShortcut;
use crate::bridge::macos::*;
use log::error; use log::error;
use std::ffi::CString;
pub struct MacKeyboardManager { pub struct MacKeyboardManager {}
}
impl super::KeyboardManager for MacKeyboardManager { impl super::KeyboardManager for MacKeyboardManager {
fn send_string(&self, s: &str) { fn send_string(&self, s: &str) {
let res = CString::new(s); let res = CString::new(s);
match res { match res {
Ok(cstr) => unsafe { send_string(cstr.as_ptr()); } Ok(cstr) => unsafe {
Err(e) => panic!(e.to_string()) send_string(cstr.as_ptr());
},
Err(e) => panic!(e.to_string()),
} }
} }

View File

@ -17,7 +17,7 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use serde::{Serialize, Deserialize, Deserializer}; use serde::{Deserialize, Deserializer, Serialize};
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod windows; mod windows;

View File

@ -17,27 +17,23 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use widestring::{U16CString};
use crate::bridge::windows::*;
use super::PasteShortcut; use super::PasteShortcut;
use crate::bridge::windows::*;
use log::error; use log::error;
use widestring::U16CString;
pub struct WindowsKeyboardManager { pub struct WindowsKeyboardManager {}
}
impl super::KeyboardManager for WindowsKeyboardManager { impl super::KeyboardManager for WindowsKeyboardManager {
fn send_string(&self, s: &str) { fn send_string(&self, s: &str) {
let res = U16CString::from_str(s); let res = U16CString::from_str(s);
match res { match res {
Ok(s) => { Ok(s) => unsafe {
unsafe {
send_string(s.as_ptr()); send_string(s.as_ptr());
},
Err(e) => println!("Error while sending string: {}", e.to_string()),
} }
} }
Err(e) => println!("Error while sending string: {}", e.to_string())
}
}
fn send_enter(&self) { fn send_enter(&self) {
unsafe { unsafe {
@ -62,9 +58,7 @@ impl super::KeyboardManager for WindowsKeyboardManager {
} }
fn delete_string(&self, count: i32) { fn delete_string(&self, count: i32) {
unsafe { unsafe { delete_string(count) }
delete_string(count)
}
} }
fn move_cursor_left(&self, count: i32) { fn move_cursor_left(&self, count: i32) {

View File

@ -20,48 +20,48 @@
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
use std::thread;
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::process::exit; use std::process::exit;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
use std::thread;
use std::time::Duration; use std::time::Duration;
use clap::{App, Arg, SubCommand, ArgMatches}; use clap::{App, Arg, ArgMatches, SubCommand};
use fs2::FileExt; use fs2::FileExt;
use log::{info, warn, LevelFilter}; use log::{info, warn, LevelFilter};
use simplelog::{CombinedLogger, SharedLogger, TerminalMode, TermLogger, WriteLogger}; use simplelog::{CombinedLogger, SharedLogger, TermLogger, TerminalMode, WriteLogger};
use crate::config::{ConfigSet, ConfigManager};
use crate::config::runtime::RuntimeConfigManager; use crate::config::runtime::RuntimeConfigManager;
use crate::config::{ConfigManager, ConfigSet};
use crate::engine::Engine; use crate::engine::Engine;
use crate::event::*;
use crate::event::manager::{DefaultEventManager, EventManager}; use crate::event::manager::{DefaultEventManager, EventManager};
use crate::event::*;
use crate::matcher::scrolling::ScrollingMatcher; use crate::matcher::scrolling::ScrollingMatcher;
use crate::package::default::DefaultPackageManager;
use crate::package::{InstallResult, PackageManager, RemoveResult, UpdateResult};
use crate::protocol::*;
use crate::system::SystemManager; use crate::system::SystemManager;
use crate::ui::UIManager; use crate::ui::UIManager;
use crate::protocol::*; use std::io::{BufRead, BufReader};
use std::io::{BufReader, BufRead};
use crate::package::default::DefaultPackageManager;
use crate::package::{PackageManager, InstallResult, UpdateResult, RemoveResult};
mod ui;
mod event;
mod check;
mod utils;
mod bridge; mod bridge;
mod engine; mod check;
mod clipboard;
mod config; mod config;
mod render;
mod system;
mod context; mod context;
mod engine;
mod event;
mod extension;
mod keyboard;
mod matcher; mod matcher;
mod package; mod package;
mod keyboard;
mod protocol; mod protocol;
mod clipboard; mod render;
mod extension;
mod sysdaemon; mod sysdaemon;
mod system;
mod ui;
mod utils;
const VERSION: &str = env!("CARGO_PKG_VERSION"); const VERSION: &str = env!("CARGO_PKG_VERSION");
const LOG_FILE: &str = "espanso.log"; const LOG_FILE: &str = "espanso.log";
@ -69,13 +69,11 @@ const LOG_FILE: &str = "espanso.log";
fn main() { fn main() {
let install_subcommand = SubCommand::with_name("install") let install_subcommand = SubCommand::with_name("install")
.about("Install a package. Equivalent to 'espanso package install'") .about("Install a package. Equivalent to 'espanso package install'")
.arg(Arg::with_name("package_name") .arg(Arg::with_name("package_name").help("Package name"));
.help("Package name"));
let uninstall_subcommand = SubCommand::with_name("uninstall") let uninstall_subcommand = SubCommand::with_name("uninstall")
.about("Remove an installed package. Equivalent to 'espanso package uninstall'") .about("Remove an installed package. Equivalent to 'espanso package uninstall'")
.arg(Arg::with_name("package_name") .arg(Arg::with_name("package_name").help("Package name"));
.help("Package name"));
let mut clap_instance = App::new("espanso") let mut clap_instance = App::new("espanso")
.version(VERSION) .version(VERSION)
@ -248,7 +246,9 @@ fn main() {
} }
// Defaults help print // Defaults help print
clap_instance.print_long_help().expect("Unable to print help"); clap_instance
.print_long_help()
.expect("Unable to print help");
println!(); println!();
} }
@ -273,8 +273,8 @@ fn daemon_main(config_set: ConfigSet) {
let mut log_outputs: Vec<Box<dyn SharedLogger>> = Vec::new(); let mut log_outputs: Vec<Box<dyn SharedLogger>> = Vec::new();
// Initialize terminal output // Initialize terminal output
let terminal_out = TermLogger::new(log_level, let terminal_out =
simplelog::Config::default(), TerminalMode::Mixed); TermLogger::new(log_level, simplelog::Config::default(), TerminalMode::Mixed);
if let Some(terminal_out) = terminal_out { if let Some(terminal_out) = terminal_out {
log_outputs.push(terminal_out); log_outputs.push(terminal_out);
} }
@ -292,16 +292,20 @@ fn daemon_main(config_set: ConfigSet) {
let file_out = WriteLogger::new(LevelFilter::Info, simplelog::Config::default(), log_file); let file_out = WriteLogger::new(LevelFilter::Info, simplelog::Config::default(), log_file);
log_outputs.push(file_out); log_outputs.push(file_out);
CombinedLogger::init( CombinedLogger::init(log_outputs).expect("Error opening log destination");
log_outputs
).expect("Error opening log destination");
// Activate logging for panics // Activate logging for panics
log_panics::init(); log_panics::init();
info!("espanso version {}", VERSION); info!("espanso version {}", VERSION);
info!("using config path: {}", context::get_config_dir().to_string_lossy()); info!(
info!("using package path: {}", context::get_package_dir().to_string_lossy()); "using config path: {}",
context::get_config_dir().to_string_lossy()
);
info!(
"using package path: {}",
context::get_package_dir().to_string_lossy()
);
info!("starting daemon..."); info!("starting daemon...");
let (send_channel, receive_channel) = mpsc::channel(); let (send_channel, receive_channel) = mpsc::channel();
@ -309,9 +313,12 @@ fn daemon_main(config_set: ConfigSet) {
let context = context::new(send_channel.clone()); let context = context::new(send_channel.clone());
let config_set_copy = config_set.clone(); let config_set_copy = config_set.clone();
thread::Builder::new().name("daemon_background".to_string()).spawn(move || { thread::Builder::new()
.name("daemon_background".to_string())
.spawn(move || {
daemon_background(receive_channel, config_set_copy); daemon_background(receive_channel, config_set_copy);
}).expect("Unable to spawn daemon background thread"); })
.expect("Unable to spawn daemon background thread");
let ipc_server = protocol::get_ipc_server(config_set, send_channel.clone()); let ipc_server = protocol::get_ipc_server(config_set, send_channel.clone());
ipc_server.start(); ipc_server.start();
@ -333,10 +340,11 @@ fn daemon_background(receive_channel: Receiver<Event>, config_set: ConfigSet) {
let extensions = extension::get_extensions(); let extensions = extension::get_extensions();
let renderer = render::default::DefaultRenderer::new(extensions, let renderer =
config_manager.default_config().clone()); render::default::DefaultRenderer::new(extensions, config_manager.default_config().clone());
let engine = Engine::new(&keyboard_manager, let engine = Engine::new(
&keyboard_manager,
&clipboard_manager, &clipboard_manager,
&config_manager, &config_manager,
&ui_manager, &ui_manager,
@ -345,11 +353,8 @@ fn daemon_background(receive_channel: Receiver<Event>, config_set: ConfigSet) {
let matcher = ScrollingMatcher::new(&config_manager, &engine); let matcher = ScrollingMatcher::new(&config_manager, &engine);
let event_manager = DefaultEventManager::new( let event_manager =
receive_channel, DefaultEventManager::new(receive_channel, vec![&matcher], vec![&engine, &matcher]);
vec!(&matcher),
vec!(&engine, &matcher),
);
info!("espanso is running!"); info!("espanso is running!");
@ -477,7 +482,8 @@ fn fork_daemon(config_set: ConfigSet) {
println!("Unable to fork."); println!("Unable to fork.");
exit(4); exit(4);
} }
if pid > 0 { // Parent process exit if pid > 0 {
// Parent process exit
println!("daemon started!"); println!("daemon started!");
exit(0); exit(0);
} }
@ -515,7 +521,6 @@ fn status_main() {
} }
} }
/// Stop subcommand, used to stop the daemon. /// Stop subcommand, used to stop the daemon.
fn stop_main(config_set: ConfigSet) { fn stop_main(config_set: ConfigSet) {
// Try to acquire lock file // Try to acquire lock file
@ -526,10 +531,13 @@ fn stop_main(config_set: ConfigSet) {
exit(3); exit(3);
} }
let res = send_command(config_set, IPCCommand{ let res = send_command(
config_set,
IPCCommand {
id: "exit".to_owned(), id: "exit".to_owned(),
payload: "".to_owned(), payload: "".to_owned(),
}); },
);
if let Err(e) = res { if let Err(e) = res {
println!("{}", e); println!("{}", e);
@ -545,10 +553,14 @@ fn restart_main(config_set: ConfigSet) {
let lock_file = acquire_lock(); let lock_file = acquire_lock();
if lock_file.is_none() { if lock_file.is_none() {
// Terminate the current espanso daemon // Terminate the current espanso daemon
send_command(config_set.clone(), IPCCommand{ send_command(
config_set.clone(),
IPCCommand {
id: "exit".to_owned(), id: "exit".to_owned(),
payload: "".to_owned(), payload: "".to_owned(),
}).unwrap_or_else(|e| warn!("Unable to send IPC command to daemon: {}", e)); },
)
.unwrap_or_else(|e| warn!("Unable to send IPC command to daemon: {}", e));
} else { } else {
release_lock(lock_file.unwrap()); release_lock(lock_file.unwrap());
} }
@ -573,9 +585,15 @@ fn detect_main() {
let mut last_exec: String = "".to_owned(); let mut last_exec: String = "".to_owned();
loop { loop {
let curr_title = system_manager.get_current_window_title().unwrap_or_default(); let curr_title = system_manager
let curr_class = system_manager.get_current_window_class().unwrap_or_default(); .get_current_window_title()
let curr_exec = system_manager.get_current_window_executable().unwrap_or_default(); .unwrap_or_default();
let curr_class = system_manager
.get_current_window_class()
.unwrap_or_default();
let curr_exec = system_manager
.get_current_window_executable()
.unwrap_or_default();
// Check if a change occurred // Check if a change occurred
if curr_title != last_title || curr_class != last_class || curr_exec != last_exec { if curr_title != last_title || curr_class != last_class || curr_exec != last_exec {
@ -600,13 +618,15 @@ fn detect_main() {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
fn detect_main() { fn detect_main() {
thread::spawn(|| { thread::spawn(|| {
use std::io::Write;
use std::io::stdout; use std::io::stdout;
use std::io::Write;
let system_manager = system::get_manager(); let system_manager = system::get_manager();
println!("Listening for changes, now focus the window you want to analyze."); println!("Listening for changes, now focus the window you want to analyze.");
println!("Warning: stay on the window for a few seconds, as it may take a while to register."); println!(
"Warning: stay on the window for a few seconds, as it may take a while to register."
);
println!("You can terminate with CTRL+C\n"); println!("You can terminate with CTRL+C\n");
let mut last_title: String = "".to_owned(); let mut last_title: String = "".to_owned();
@ -614,9 +634,15 @@ fn detect_main() {
let mut last_exec: String = "".to_owned(); let mut last_exec: String = "".to_owned();
loop { loop {
let curr_title = system_manager.get_current_window_title().unwrap_or_default(); let curr_title = system_manager
let curr_class = system_manager.get_current_window_class().unwrap_or_default(); .get_current_window_title()
let curr_exec = system_manager.get_current_window_executable().unwrap_or_default(); .unwrap_or_default();
let curr_class = system_manager
.get_current_window_class()
.unwrap_or_default();
let curr_exec = system_manager
.get_current_window_executable()
.unwrap_or_default();
// Check if a change occurred // Check if a change occurred
if curr_title != last_title || curr_class != last_class || curr_exec != last_exec { if curr_title != last_title || curr_class != last_class || curr_exec != last_exec {
@ -730,20 +756,18 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) {
let res = package_manager.update_index(false); let res = package_manager.update_index(false);
match res { match res {
Ok(update_result) => { Ok(update_result) => match update_result {
match update_result {
UpdateResult::NotOutdated => { UpdateResult::NotOutdated => {
eprintln!("Index was already up to date"); eprintln!("Index was already up to date");
}, }
UpdateResult::Updated => { UpdateResult::Updated => {
println!("Index updated!"); println!("Index updated!");
},
} }
}, },
Err(e) => { Err(e) => {
eprintln!("{}", e); eprintln!("{}", e);
exit(2); exit(2);
}, }
} }
} else { } else {
println!("Using cached package index, run 'espanso package refresh' to update it.") println!("Using cached package index, run 'espanso package refresh' to update it.")
@ -756,30 +780,30 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) {
match install_result { match install_result {
InstallResult::NotFoundInIndex => { InstallResult::NotFoundInIndex => {
eprintln!("Package not found"); eprintln!("Package not found");
}, }
InstallResult::NotFoundInRepo => { InstallResult::NotFoundInRepo => {
eprintln!("Package not found in repository, are you sure the folder exist in the repo?"); eprintln!("Package not found in repository, are you sure the folder exist in the repo?");
}, }
InstallResult::UnableToParsePackageInfo => { InstallResult::UnableToParsePackageInfo => {
eprintln!("Unable to parse Package info from README.md"); eprintln!("Unable to parse Package info from README.md");
}, }
InstallResult::MissingPackageVersion => { InstallResult::MissingPackageVersion => {
eprintln!("Missing package version"); eprintln!("Missing package version");
}, }
InstallResult::AlreadyInstalled => { InstallResult::AlreadyInstalled => {
eprintln!("{} already installed!", package_name); eprintln!("{} already installed!", package_name);
}, }
InstallResult::Installed => { InstallResult::Installed => {
println!("{} successfully installed!", package_name); println!("{} successfully installed!", package_name);
println!(); println!();
println!("You need to restart espanso for changes to take effect, using:"); println!("You need to restart espanso for changes to take effect, using:");
println!(" espanso restart"); println!(" espanso restart");
},
} }
}, }
}
Err(e) => { Err(e) => {
eprintln!("{}", e); eprintln!("{}", e);
}, }
} }
} }
@ -794,22 +818,20 @@ fn remove_package_main(_config_set: ConfigSet, matches: &ArgMatches) {
let res = package_manager.remove_package(package_name); let res = package_manager.remove_package(package_name);
match res { match res {
Ok(remove_result) => { Ok(remove_result) => match remove_result {
match remove_result {
RemoveResult::NotFound => { RemoveResult::NotFound => {
eprintln!("{} package was not installed.", package_name); eprintln!("{} package was not installed.", package_name);
}, }
RemoveResult::Removed => { RemoveResult::Removed => {
println!("{} successfully removed!", package_name); println!("{} successfully removed!", package_name);
println!(); println!();
println!("You need to restart espanso for changes to take effect, using:"); println!("You need to restart espanso for changes to take effect, using:");
println!(" espanso restart"); println!(" espanso restart");
},
} }
}, },
Err(e) => { Err(e) => {
eprintln!("{}", e); eprintln!("{}", e);
}, }
} }
} }
@ -819,20 +841,18 @@ fn update_index_main(_config_set: ConfigSet) {
let res = package_manager.update_index(true); let res = package_manager.update_index(true);
match res { match res {
Ok(update_result) => { Ok(update_result) => match update_result {
match update_result {
UpdateResult::NotOutdated => { UpdateResult::NotOutdated => {
eprintln!("Index was already up to date"); eprintln!("Index was already up to date");
}, }
UpdateResult::Updated => { UpdateResult::Updated => {
println!("Index updated!"); println!("Index updated!");
},
} }
}, },
Err(e) => { Err(e) => {
eprintln!("{}", e); eprintln!("{}", e);
exit(2); exit(2);
}, }
} }
} }
@ -873,7 +893,6 @@ fn path_main(_config_set: ConfigSet, matches: &ArgMatches) {
} }
} }
fn acquire_lock() -> Option<File> { fn acquire_lock() -> Option<File> {
let espanso_dir = context::get_data_dir(); let espanso_dir = context::get_data_dir();
let lock_file_path = espanso_dir.join("espanso.lock"); let lock_file_path = espanso_dir.join("espanso.lock");
@ -887,7 +906,7 @@ fn acquire_lock() -> Option<File> {
let res = file.try_lock_exclusive(); let res = file.try_lock_exclusive();
if res.is_ok() { if res.is_ok() {
return Some(file) return Some(file);
} }
None None

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/>.
*/ */
use serde::{Serialize, Deserialize, Deserializer};
use crate::event::{KeyEvent, KeyModifier};
use crate::event::KeyEventReceiver; use crate::event::KeyEventReceiver;
use serde_yaml::Mapping; use crate::event::{KeyEvent, KeyModifier};
use regex::Regex; use regex::Regex;
use std::path::PathBuf; use serde::{Deserialize, Deserializer, Serialize};
use serde_yaml::Mapping;
use std::fs; use std::fs;
use std::path::PathBuf;
pub(crate) mod scrolling; pub(crate) mod scrolling;
@ -60,9 +60,10 @@ pub struct ImageContent {
} }
impl<'de> serde::Deserialize<'de> for Match { impl<'de> serde::Deserialize<'de> for Match {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
D: Deserializer<'de> { where
D: Deserializer<'de>,
{
let auto_match = AutoMatch::deserialize(deserializer)?; let auto_match = AutoMatch::deserialize(deserializer)?;
Ok(Match::from(&auto_match)) Ok(Match::from(&auto_match))
} }
@ -79,14 +80,14 @@ impl<'a> From<&'a AutoMatch> for Match{
// Calculate the trigger sequence // Calculate the trigger sequence
let mut trigger_sequence = Vec::new(); let mut trigger_sequence = Vec::new();
let trigger_chars: Vec<char> = other.trigger.chars().collect(); let trigger_chars: Vec<char> = other.trigger.chars().collect();
trigger_sequence.extend(trigger_chars.into_iter().map(|c| { trigger_sequence.extend(trigger_chars.into_iter().map(|c| TriggerEntry::Char(c)));
TriggerEntry::Char(c) if other.word {
})); // If it's a word match, end with a word separator
if other.word { // If it's a word match, end with a word separator
trigger_sequence.push(TriggerEntry::WordSeparator); trigger_sequence.push(TriggerEntry::WordSeparator);
} }
let content = if let Some(replace) = &other.replace { // Text match let content = if let Some(replace) = &other.replace {
// Text match
let new_replace = replace.clone(); let new_replace = replace.clone();
// Check if the match contains variables // Check if the match contains variables
@ -99,7 +100,8 @@ impl<'a> From<&'a AutoMatch> for Match{
}; };
MatchContentType::Text(content) MatchContentType::Text(content)
}else if let Some(image_path) = &other.image_path { // Image match } else if let Some(image_path) = &other.image_path {
// Image match
// On Windows, we have to replace the forward / with the backslash \ in the path // On Windows, we have to replace the forward / with the backslash \ in the path
let new_path = if cfg!(target_os = "windows") { let new_path = if cfg!(target_os = "windows") {
image_path.replace("/", "\\") image_path.replace("/", "\\")
@ -122,7 +124,7 @@ impl<'a> From<&'a AutoMatch> for Match{
}; };
let content = ImageContent { let content = ImageContent {
path: PathBuf::from(new_path) path: PathBuf::from(new_path),
}; };
MatchContentType::Image(content) MatchContentType::Image(content)
@ -162,11 +164,21 @@ struct AutoMatch {
pub passive_only: bool, pub passive_only: bool,
} }
fn default_vars() -> Vec<MatchVariable> {Vec::new()} fn default_vars() -> Vec<MatchVariable> {
fn default_word() -> bool {false} Vec::new()
fn default_passive_only() -> bool {false} }
fn default_replace() -> Option<String> {None} fn default_word() -> bool {
fn default_image_path() -> Option<String> {None} false
}
fn default_passive_only() -> bool {
false
}
fn default_replace() -> Option<String> {
None
}
fn default_image_path() -> Option<String> {
None
}
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct MatchVariable { pub struct MatchVariable {
@ -181,7 +193,7 @@ pub struct MatchVariable {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum TriggerEntry { pub enum TriggerEntry {
Char(char), Char(char),
WordSeparator WordSeparator,
} }
pub trait MatchReceiver { pub trait MatchReceiver {
@ -200,14 +212,13 @@ impl <M: Matcher> KeyEventReceiver for M {
match e { match e {
KeyEvent::Char(c) => { KeyEvent::Char(c) => {
self.handle_char(&c); self.handle_char(&c);
}, }
KeyEvent::Modifier(m) => { KeyEvent::Modifier(m) => {
self.handle_modifier(m); self.handle_modifier(m);
},
} }
} }
} }
}
// TESTS // TESTS
@ -227,10 +238,10 @@ mod tests {
match _match.content { match _match.content {
MatchContentType::Text(content) => { MatchContentType::Text(content) => {
assert_eq!(content._has_vars, false); assert_eq!(content._has_vars, false);
}, }
_ => { _ => {
assert!(false); assert!(false);
}, }
} }
} }
@ -246,10 +257,10 @@ mod tests {
match _match.content { match _match.content {
MatchContentType::Text(content) => { MatchContentType::Text(content) => {
assert_eq!(content._has_vars, true); assert_eq!(content._has_vars, true);
}, }
_ => { _ => {
assert!(false); assert!(false);
}, }
} }
} }
@ -265,10 +276,10 @@ mod tests {
match _match.content { match _match.content {
MatchContentType::Text(content) => { MatchContentType::Text(content) => {
assert_eq!(content._has_vars, true); assert_eq!(content._has_vars, true);
}, }
_ => { _ => {
assert!(false); assert!(false);
}, }
} }
} }
@ -316,10 +327,10 @@ mod tests {
match _match.content { match _match.content {
MatchContentType::Image(content) => { MatchContentType::Image(content) => {
assert_eq!(content.path, PathBuf::from("/path/to/file")); assert_eq!(content.path, PathBuf::from("/path/to/file"));
}, }
_ => { _ => {
assert!(false); assert!(false);
}, }
} }
} }
} }

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/>.
*/ */
use crate::matcher::{Match, MatchReceiver, TriggerEntry};
use std::cell::{RefCell, Ref};
use crate::event::{KeyModifier, ActionEventReceiver, ActionType};
use crate::config::ConfigManager; use crate::config::ConfigManager;
use crate::event::KeyModifier::BACKSPACE; use crate::event::KeyModifier::BACKSPACE;
use std::time::SystemTime; use crate::event::{ActionEventReceiver, ActionType, KeyModifier};
use crate::matcher::{Match, MatchReceiver, TriggerEntry};
use std::cell::{Ref, RefCell};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::time::SystemTime;
pub struct ScrollingMatcher<'a, R: MatchReceiver, M: ConfigManager<'a>> { pub struct ScrollingMatcher<'a, R: MatchReceiver, M: ConfigManager<'a>> {
config_manager: &'a M, config_manager: &'a M,
@ -39,7 +39,7 @@ pub struct ScrollingMatcher<'a, R: MatchReceiver, M: ConfigManager<'a>> {
struct MatchEntry<'a> { struct MatchEntry<'a> {
start: usize, start: usize,
count: usize, count: usize,
_match: &'a Match _match: &'a Match,
} }
impl<'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> { impl<'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> {
@ -73,14 +73,15 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> {
self.receiver.on_enable_update(*is_enabled); self.receiver.on_enable_update(*is_enabled);
} }
fn is_matching(mtc: &Match, current_char: &str, start: usize, is_current_word_separator: bool) -> bool { fn is_matching(
mtc: &Match,
current_char: &str,
start: usize,
is_current_word_separator: bool,
) -> bool {
match mtc._trigger_sequence[start] { match mtc._trigger_sequence[start] {
TriggerEntry::Char(c) => { TriggerEntry::Char(c) => current_char.starts_with(c),
current_char.starts_with(c) TriggerEntry::WordSeparator => is_current_word_separator,
},
TriggerEntry::WordSeparator => {
is_current_word_separator
},
} }
} }
} }
@ -97,9 +98,9 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
let active_config = self.config_manager.active_config(); let active_config = self.config_manager.active_config();
// Check if the current char is a word separator // Check if the current char is a word separator
let mut is_current_word_separator = active_config.word_separators.contains( let mut is_current_word_separator = active_config
&c.chars().nth(0).unwrap_or_default() .word_separators
); .contains(&c.chars().nth(0).unwrap_or_default());
// Workaround needed on macos to consider espanso replacement key presses as separators. // Workaround needed on macos to consider espanso replacement key presses as separators.
if cfg!(target_os = "macos") { if cfg!(target_os = "macos") {
@ -112,7 +113,9 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
let mut current_set_queue = self.current_set_queue.borrow_mut(); let mut current_set_queue = self.current_set_queue.borrow_mut();
let new_matches: Vec<MatchEntry> = active_config.matches.iter() let new_matches: Vec<MatchEntry> = active_config
.matches
.iter()
.filter(|&x| { .filter(|&x| {
// only active-enabled matches are considered // only active-enabled matches are considered
if x.passive_only { if x.passive_only {
@ -130,28 +133,27 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
.map(|x| MatchEntry { .map(|x| MatchEntry {
start: 1, start: 1,
count: x._trigger_sequence.len(), count: x._trigger_sequence.len(),
_match: &x _match: &x,
}) })
.collect(); .collect();
// TODO: use an associative structure to improve the efficiency of this first "new_matches" lookup. // TODO: use an associative structure to improve the efficiency of this first "new_matches" lookup.
let combined_matches: Vec<MatchEntry> = match current_set_queue.back_mut() { let combined_matches: Vec<MatchEntry> = match current_set_queue.back_mut() {
Some(last_matches) => { Some(last_matches) => {
let mut updated: Vec<MatchEntry> = last_matches.iter() let mut updated: Vec<MatchEntry> = last_matches
.filter(|&x| { .iter()
Self::is_matching(x._match, c, x.start, is_current_word_separator) .filter(|&x| Self::is_matching(x._match, c, x.start, is_current_word_separator))
})
.map(|x| MatchEntry { .map(|x| MatchEntry {
start: x.start + 1, start: x.start + 1,
count: x.count, count: x.count,
_match: &x._match _match: &x._match,
}) })
.collect(); .collect();
updated.extend(new_matches); updated.extend(new_matches);
updated updated
}, }
None => {new_matches}, None => new_matches,
}; };
let mut found_match = None; let mut found_match = None;
@ -165,7 +167,9 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
current_set_queue.push_back(combined_matches); current_set_queue.push_back(combined_matches);
if current_set_queue.len() as i32 > (self.config_manager.default_config().backspace_limit + 1) { if current_set_queue.len() as i32
> (self.config_manager.default_config().backspace_limit + 1)
{
current_set_queue.pop_front(); current_set_queue.pop_front();
} }
@ -186,8 +190,8 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
match as_char { match as_char {
Some(c) => { Some(c) => {
Some(c) // Current char is the trailing separator Some(c) // Current char is the trailing separator
}, }
None => {None}, None => None,
} }
}; };
@ -205,8 +209,10 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
// study a mechanism to avoid this problem // study a mechanism to avoid this problem
if m == config.toggle_key { if m == config.toggle_key {
check_interval(&self.toggle_press_time, check_interval(
u128::from(config.toggle_interval), || { &self.toggle_press_time,
u128::from(config.toggle_interval),
|| {
self.toggle(); self.toggle();
let is_enabled = self.is_enabled.borrow(); let is_enabled = self.is_enabled.borrow();
@ -214,12 +220,16 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
if !*is_enabled { if !*is_enabled {
self.current_set_queue.borrow_mut().clear(); self.current_set_queue.borrow_mut().clear();
} }
}); },
);
} else if m == config.passive_key { } else if m == config.passive_key {
check_interval(&self.passive_press_time, check_interval(
u128::from(config.toggle_interval), || { &self.passive_press_time,
u128::from(config.toggle_interval),
|| {
self.receiver.on_passive(); self.receiver.on_passive();
}); },
);
} }
// Backspace handling, basically "rewinding history" // Backspace handling, basically "rewinding history"
@ -230,24 +240,29 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
} }
} }
impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ActionEventReceiver for ScrollingMatcher<'a, R, M> { impl<'a, R: MatchReceiver, M: ConfigManager<'a>> ActionEventReceiver
for ScrollingMatcher<'a, R, M>
{
fn on_action_event(&self, e: ActionType) { fn on_action_event(&self, e: ActionType) {
match e { match e {
ActionType::Toggle => { ActionType::Toggle => {
self.toggle(); self.toggle();
}, }
ActionType::Enable => { ActionType::Enable => {
self.set_enabled(true); self.set_enabled(true);
}, }
ActionType::Disable => { ActionType::Disable => {
self.set_enabled(false); self.set_enabled(false);
}, }
_ => {} _ => {}
} }
} }
} }
fn check_interval<F>(state_var: &RefCell<SystemTime>, interval: u128, elapsed_callback: F) where F:Fn() { fn check_interval<F>(state_var: &RefCell<SystemTime>, interval: u128, elapsed_callback: F)
where
F: Fn(),
{
let mut press_time = state_var.borrow_mut(); let mut press_time = state_var.borrow_mut();
if let Ok(elapsed) = press_time.elapsed() { if let Ok(elapsed) = press_time.elapsed() {
if elapsed.as_millis() < interval { if elapsed.as_millis() < interval {

View File

@ -17,20 +17,20 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::path::{PathBuf, Path}; use crate::package::InstallResult::{AlreadyInstalled, NotFoundInIndex};
use crate::package::{PackageIndex, UpdateResult, Package, InstallResult, RemoveResult}; use crate::package::RemoveResult::Removed;
use std::error::Error;
use std::fs::{File, create_dir};
use std::io::{BufReader, BufRead};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::package::UpdateResult::{NotOutdated, Updated}; use crate::package::UpdateResult::{NotOutdated, Updated};
use crate::package::InstallResult::{NotFoundInIndex, AlreadyInstalled}; use crate::package::{InstallResult, Package, PackageIndex, RemoveResult, UpdateResult};
use std::fs;
use tempfile::TempDir;
use git2::Repository; use git2::Repository;
use regex::Regex; use regex::Regex;
use crate::package::RemoveResult::Removed;
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error;
use std::fs;
use std::fs::{create_dir, File};
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use tempfile::TempDir;
const DEFAULT_PACKAGE_INDEX_FILE: &str = "package_index.json"; const DEFAULT_PACKAGE_INDEX_FILE: &str = "package_index.json";
@ -48,14 +48,14 @@ impl DefaultPackageManager {
DefaultPackageManager { DefaultPackageManager {
package_dir, package_dir,
data_dir, data_dir,
local_index local_index,
} }
} }
pub fn new_default() -> DefaultPackageManager { pub fn new_default() -> DefaultPackageManager {
DefaultPackageManager::new( DefaultPackageManager::new(
crate::context::get_package_dir(), crate::context::get_package_dir(),
crate::context::get_data_dir() crate::context::get_data_dir(),
) )
} }
@ -70,7 +70,7 @@ impl DefaultPackageManager {
let local_index = serde_json::from_reader(reader); let local_index = serde_json::from_reader(reader);
if let Ok(local_index) = local_index { if let Ok(local_index) = local_index {
return local_index return local_index;
} }
} }
@ -79,7 +79,8 @@ impl DefaultPackageManager {
fn request_index() -> Result<super::PackageIndex, Box<dyn Error>> { fn request_index() -> Result<super::PackageIndex, Box<dyn Error>> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let request = client.get("https://hub.espanso.org/json/") let request = client
.get("https://hub.espanso.org/json/")
.header("User-Agent", format!("espanso/{}", crate::VERSION)); .header("User-Agent", format!("espanso/{}", crate::VERSION));
let mut res = request.send()?; let mut res = request.send()?;
@ -97,7 +98,8 @@ impl DefaultPackageManager {
fn parse_package_from_readme(readme_path: &Path) -> Option<Package> { fn parse_package_from_readme(readme_path: &Path) -> Option<Package> {
lazy_static! { lazy_static! {
static ref FIELD_REGEX: Regex = Regex::new(r###"^\s*(.*?)\s*:\s*"?(.*?)"?$"###).unwrap(); static ref FIELD_REGEX: Regex =
Regex::new(r###"^\s*(.*?)\s*:\s*"?(.*?)"?$"###).unwrap();
} }
// Read readme line by line // Read readme line by line
@ -113,7 +115,7 @@ impl DefaultPackageManager {
let line = line.unwrap(); let line = line.unwrap();
if line.contains("---") { if line.contains("---") {
if started { if started {
break break;
} else { } else {
started = true; started = true;
} }
@ -123,20 +125,23 @@ impl DefaultPackageManager {
let property = caps.get(1); let property = caps.get(1);
let value = caps.get(2); let value = caps.get(2);
if property.is_some() && value.is_some() { if property.is_some() && value.is_some() {
fields.insert(property.unwrap().as_str().to_owned(), fields.insert(
value.unwrap().as_str().to_owned()); property.unwrap().as_str().to_owned(),
value.unwrap().as_str().to_owned(),
);
} }
} }
} }
} }
if !fields.contains_key("package_name") || if !fields.contains_key("package_name")
!fields.contains_key("package_title") || || !fields.contains_key("package_title")
!fields.contains_key("package_version") || || !fields.contains_key("package_version")
!fields.contains_key("package_repo") || || !fields.contains_key("package_repo")
!fields.contains_key("package_desc") || || !fields.contains_key("package_desc")
!fields.contains_key("package_author") { || !fields.contains_key("package_author")
return None {
return None;
} }
let package = Package { let package = Package {
@ -145,7 +150,7 @@ impl DefaultPackageManager {
version: fields.get("package_version").unwrap().clone(), version: fields.get("package_version").unwrap().clone(),
repo: fields.get("package_repo").unwrap().clone(), repo: fields.get("package_repo").unwrap().clone(),
desc: fields.get("package_desc").unwrap().clone(), desc: fields.get("package_desc").unwrap().clone(),
author: fields.get("package_author").unwrap().clone() author: fields.get("package_author").unwrap().clone(),
}; };
Some(package) Some(package)
@ -156,7 +161,7 @@ impl DefaultPackageManager {
fn local_index_timestamp(&self) -> u64 { fn local_index_timestamp(&self) -> u64 {
if let Some(local_index) = &self.local_index { if let Some(local_index) = &self.local_index {
return local_index.last_update return local_index.last_update;
} }
0 0
@ -184,7 +189,8 @@ impl DefaultPackageManager {
fn cache_local_index(&self) { fn cache_local_index(&self) {
if let Some(local_index) = &self.local_index { if let Some(local_index) = &self.local_index {
let serialized = serde_json::to_string(local_index).expect("Unable to serialize local index"); let serialized =
serde_json::to_string(local_index).expect("Unable to serialize local index");
let local_index_file = self.data_dir.join(DEFAULT_PACKAGE_INDEX_FILE); let local_index_file = self.data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
std::fs::write(local_index_file, serialized).expect("Unable to cache local index"); std::fs::write(local_index_file, serialized).expect("Unable to cache local index");
} }
@ -193,7 +199,9 @@ impl DefaultPackageManager {
impl super::PackageManager for DefaultPackageManager { impl super::PackageManager for DefaultPackageManager {
fn is_index_outdated(&self) -> bool { fn is_index_outdated(&self) -> bool {
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards"); let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");
let current_timestamp = current_time.as_secs(); let current_timestamp = current_time.as_secs();
let local_index_timestamp = self.local_index_timestamp(); let local_index_timestamp = self.local_index_timestamp();
@ -218,11 +226,12 @@ impl super::PackageManager for DefaultPackageManager {
fn get_package(&self, name: &str) -> Option<Package> { fn get_package(&self, name: &str) -> Option<Package> {
if let Some(local_index) = &self.local_index { if let Some(local_index) = &self.local_index {
let result = local_index.packages.iter().find(|package| { let result = local_index
package.name == name .packages
}); .iter()
.find(|package| package.name == name);
if let Some(package) = result { if let Some(package) = result {
return Some(package.clone()) return Some(package.clone());
} }
} }
@ -232,19 +241,20 @@ impl super::PackageManager for DefaultPackageManager {
fn install_package(&self, name: &str) -> Result<InstallResult, Box<dyn Error>> { fn install_package(&self, name: &str) -> Result<InstallResult, Box<dyn Error>> {
let package = self.get_package(name); let package = self.get_package(name);
match package { match package {
Some(package) => { Some(package) => self.install_package_from_repo(name, &package.repo),
self.install_package_from_repo(name, &package.repo) None => Ok(NotFoundInIndex),
},
None => {
Ok(NotFoundInIndex)
},
} }
} }
fn install_package_from_repo(&self, name: &str, repo_url: &str) -> Result<InstallResult, Box<dyn Error>> { fn install_package_from_repo(
&self,
name: &str,
repo_url: &str,
) -> Result<InstallResult, Box<dyn Error>> {
// Check if package is already installed // Check if package is already installed
let packages = self.list_local_packages_names(); let packages = self.list_local_packages_names();
if packages.iter().any(|p| p == name) { // Package already installed if packages.iter().any(|p| p == name) {
// Package already installed
return Ok(AlreadyInstalled); return Ok(AlreadyInstalled);
} }
@ -311,15 +321,16 @@ impl super::PackageManager for DefaultPackageManager {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use tempfile::{TempDir, NamedTempFile}; use crate::package::InstallResult::*;
use std::path::Path;
use crate::package::PackageManager; use crate::package::PackageManager;
use std::fs::{create_dir, create_dir_all}; use std::fs::{create_dir, create_dir_all};
use crate::package::InstallResult::*;
use std::io::Write; use std::io::Write;
use std::path::Path;
use tempfile::{NamedTempFile, TempDir};
const OUTDATED_INDEX_CONTENT: &str = include_str!("../res/test/outdated_index.json"); const OUTDATED_INDEX_CONTENT: &str = include_str!("../res/test/outdated_index.json");
const INDEX_CONTENT_WITHOUT_UPDATE: &str = include_str!("../res/test/index_without_update.json"); const INDEX_CONTENT_WITHOUT_UPDATE: &str =
include_str!("../res/test/index_without_update.json");
const GET_PACKAGE_INDEX: &str = include_str!("../res/test/get_package_index.json"); const GET_PACKAGE_INDEX: &str = include_str!("../res/test/get_package_index.json");
const INSTALL_PACKAGE_INDEX: &str = include_str!("../res/test/install_package_index.json"); const INSTALL_PACKAGE_INDEX: &str = include_str!("../res/test/install_package_index.json");
@ -329,7 +340,10 @@ mod tests {
package_manager: DefaultPackageManager, package_manager: DefaultPackageManager,
} }
fn create_temp_package_manager<F>(setup: F) -> TempPackageManager where F: Fn(&Path, &Path) -> (){ fn create_temp_package_manager<F>(setup: F) -> TempPackageManager
where
F: Fn(&Path, &Path) -> (),
{
let package_dir = TempDir::new().expect("unable to create temp directory"); let package_dir = TempDir::new().expect("unable to create temp directory");
let data_dir = TempDir::new().expect("unable to create temp directory"); let data_dir = TempDir::new().expect("unable to create temp directory");
@ -337,13 +351,13 @@ mod tests {
let package_manager = DefaultPackageManager::new( let package_manager = DefaultPackageManager::new(
package_dir.path().clone().to_path_buf(), package_dir.path().clone().to_path_buf(),
data_dir.path().clone().to_path_buf() data_dir.path().clone().to_path_buf(),
); );
TempPackageManager { TempPackageManager {
package_dir, package_dir,
data_dir, data_dir,
package_manager package_manager,
} }
} }
@ -370,26 +384,38 @@ mod tests {
fn test_up_to_date_index_should_not_be_updated() { fn test_up_to_date_index_should_not_be_updated() {
let mut temp = create_temp_package_manager(|_, data_dir| { let mut temp = create_temp_package_manager(|_, data_dir| {
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE); let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards"); let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");
let current_timestamp = current_time.as_secs(); let current_timestamp = current_time.as_secs();
let new_contents = INDEX_CONTENT_WITHOUT_UPDATE.replace("XXXX", &format!("{}", current_timestamp)); let new_contents =
INDEX_CONTENT_WITHOUT_UPDATE.replace("XXXX", &format!("{}", current_timestamp));
std::fs::write(index_file, new_contents); std::fs::write(index_file, new_contents);
}); });
assert_eq!(temp.package_manager.update_index(false).unwrap(), UpdateResult::NotOutdated); assert_eq!(
temp.package_manager.update_index(false).unwrap(),
UpdateResult::NotOutdated
);
} }
#[test] #[test]
fn test_up_to_date_index_with_force_should_be_updated() { fn test_up_to_date_index_with_force_should_be_updated() {
let mut temp = create_temp_package_manager(|_, data_dir| { let mut temp = create_temp_package_manager(|_, data_dir| {
let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE); let index_file = data_dir.join(DEFAULT_PACKAGE_INDEX_FILE);
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards"); let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");
let current_timestamp = current_time.as_secs(); let current_timestamp = current_time.as_secs();
let new_contents = INDEX_CONTENT_WITHOUT_UPDATE.replace("XXXX", &format!("{}", current_timestamp)); let new_contents =
INDEX_CONTENT_WITHOUT_UPDATE.replace("XXXX", &format!("{}", current_timestamp));
std::fs::write(index_file, new_contents); std::fs::write(index_file, new_contents);
}); });
assert_eq!(temp.package_manager.update_index(true).unwrap(), UpdateResult::Updated); assert_eq!(
temp.package_manager.update_index(true).unwrap(),
UpdateResult::Updated
);
} }
#[test] #[test]
@ -399,15 +425,25 @@ mod tests {
std::fs::write(index_file, OUTDATED_INDEX_CONTENT); std::fs::write(index_file, OUTDATED_INDEX_CONTENT);
}); });
assert_eq!(temp.package_manager.update_index(false).unwrap(), UpdateResult::Updated); assert_eq!(
temp.package_manager.update_index(false).unwrap(),
UpdateResult::Updated
);
} }
#[test] #[test]
fn test_update_index_should_create_file() { fn test_update_index_should_create_file() {
let mut temp = create_temp_package_manager(|_, _| {}); let mut temp = create_temp_package_manager(|_, _| {});
assert_eq!(temp.package_manager.update_index(false).unwrap(), UpdateResult::Updated); assert_eq!(
assert!(temp.data_dir.path().join(DEFAULT_PACKAGE_INDEX_FILE).exists()) temp.package_manager.update_index(false).unwrap(),
UpdateResult::Updated
);
assert!(temp
.data_dir
.path()
.join(DEFAULT_PACKAGE_INDEX_FILE)
.exists())
} }
#[test] #[test]
@ -417,7 +453,13 @@ mod tests {
std::fs::write(index_file, GET_PACKAGE_INDEX); std::fs::write(index_file, GET_PACKAGE_INDEX);
}); });
assert_eq!(temp.package_manager.get_package("italian-accents").unwrap().title, "Italian Accents"); assert_eq!(
temp.package_manager
.get_package("italian-accents")
.unwrap()
.title,
"Italian Accents"
);
} }
#[test] #[test]
@ -451,7 +493,12 @@ mod tests {
std::fs::write(index_file, INSTALL_PACKAGE_INDEX); std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
}); });
assert_eq!(temp.package_manager.install_package("doesnotexist").unwrap(), NotFoundInIndex); assert_eq!(
temp.package_manager
.install_package("doesnotexist")
.unwrap(),
NotFoundInIndex
);
} }
#[test] #[test]
@ -462,12 +509,20 @@ mod tests {
std::fs::write(index_file, INSTALL_PACKAGE_INDEX); std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
}); });
assert_eq!(temp.package_manager.install_package("italian-accents").unwrap(), AlreadyInstalled); assert_eq!(
temp.package_manager
.install_package("italian-accents")
.unwrap(),
AlreadyInstalled
);
} }
#[test] #[test]
fn test_clone_temp_repository() { fn test_clone_temp_repository() {
let cloned_dir = DefaultPackageManager::clone_repo_to_temp("https://github.com/federico-terzi/espanso-hub-core").unwrap(); let cloned_dir = DefaultPackageManager::clone_repo_to_temp(
"https://github.com/federico-terzi/espanso-hub-core",
)
.unwrap();
assert!(cloned_dir.path().join("LICENSE").exists()); assert!(cloned_dir.path().join("LICENSE").exists());
} }
@ -478,10 +533,23 @@ mod tests {
std::fs::write(index_file, INSTALL_PACKAGE_INDEX); std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
}); });
assert_eq!(temp.package_manager.install_package("dummy-package").unwrap(), Installed); assert_eq!(
temp.package_manager
.install_package("dummy-package")
.unwrap(),
Installed
);
assert!(temp.package_dir.path().join("dummy-package").exists()); assert!(temp.package_dir.path().join("dummy-package").exists());
assert!(temp.package_dir.path().join("dummy-package/README.md").exists()); assert!(temp
assert!(temp.package_dir.path().join("dummy-package/package.yml").exists()); .package_dir
.path()
.join("dummy-package/README.md")
.exists());
assert!(temp
.package_dir
.path()
.join("dummy-package/package.yml")
.exists());
} }
#[test] #[test]
@ -491,7 +559,12 @@ mod tests {
std::fs::write(index_file, INSTALL_PACKAGE_INDEX); std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
}); });
assert_eq!(temp.package_manager.install_package("not-existing").unwrap(), NotFoundInRepo); assert_eq!(
temp.package_manager
.install_package("not-existing")
.unwrap(),
NotFoundInRepo
);
} }
#[test] #[test]
@ -501,7 +574,12 @@ mod tests {
std::fs::write(index_file, INSTALL_PACKAGE_INDEX); std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
}); });
assert_eq!(temp.package_manager.install_package("dummy-package2").unwrap(), MissingPackageVersion); assert_eq!(
temp.package_manager
.install_package("dummy-package2")
.unwrap(),
MissingPackageVersion
);
} }
#[test] #[test]
@ -511,7 +589,12 @@ mod tests {
std::fs::write(index_file, INSTALL_PACKAGE_INDEX); std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
}); });
assert_eq!(temp.package_manager.install_package("dummy-package3").unwrap(), UnableToParsePackageInfo); assert_eq!(
temp.package_manager
.install_package("dummy-package3")
.unwrap(),
UnableToParsePackageInfo
);
} }
#[test] #[test]
@ -521,7 +604,12 @@ mod tests {
std::fs::write(index_file, INSTALL_PACKAGE_INDEX); std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
}); });
assert_eq!(temp.package_manager.install_package("dummy-package4").unwrap(), UnableToParsePackageInfo); assert_eq!(
temp.package_manager
.install_package("dummy-package4")
.unwrap(),
UnableToParsePackageInfo
);
} }
#[test] #[test]
@ -531,10 +619,23 @@ mod tests {
std::fs::write(index_file, INSTALL_PACKAGE_INDEX); std::fs::write(index_file, INSTALL_PACKAGE_INDEX);
}); });
assert_eq!(temp.package_manager.install_package("dummy-package").unwrap(), Installed); assert_eq!(
temp.package_manager
.install_package("dummy-package")
.unwrap(),
Installed
);
assert!(temp.package_dir.path().join("dummy-package").exists()); assert!(temp.package_dir.path().join("dummy-package").exists());
assert!(temp.package_dir.path().join("dummy-package/README.md").exists()); assert!(temp
assert!(temp.package_dir.path().join("dummy-package/package.yml").exists()); .package_dir
.path()
.join("dummy-package/README.md")
.exists());
assert!(temp
.package_dir
.path()
.join("dummy-package/package.yml")
.exists());
let list = temp.package_manager.list_local_packages(); let list = temp.package_manager.list_local_packages();
assert_eq!(list.len(), 1); assert_eq!(list.len(), 1);
@ -551,25 +652,51 @@ mod tests {
}); });
assert!(temp.package_dir.path().join("dummy-package").exists()); assert!(temp.package_dir.path().join("dummy-package").exists());
assert!(temp.package_dir.path().join("dummy-package/README.md").exists()); assert!(temp
assert!(temp.package_dir.path().join("dummy-package/package.yml").exists()); .package_dir
assert_eq!(temp.package_manager.remove_package("dummy-package").unwrap(), RemoveResult::Removed); .path()
.join("dummy-package/README.md")
.exists());
assert!(temp
.package_dir
.path()
.join("dummy-package/package.yml")
.exists());
assert_eq!(
temp.package_manager
.remove_package("dummy-package")
.unwrap(),
RemoveResult::Removed
);
assert!(!temp.package_dir.path().join("dummy-package").exists()); assert!(!temp.package_dir.path().join("dummy-package").exists());
assert!(!temp.package_dir.path().join("dummy-package/README.md").exists()); assert!(!temp
assert!(!temp.package_dir.path().join("dummy-package/package.yml").exists()); .package_dir
.path()
.join("dummy-package/README.md")
.exists());
assert!(!temp
.package_dir
.path()
.join("dummy-package/package.yml")
.exists());
} }
#[test] #[test]
fn test_remove_package_not_found() { fn test_remove_package_not_found() {
let mut temp = create_temp_package_manager(|_, _| {}); let mut temp = create_temp_package_manager(|_, _| {});
assert_eq!(temp.package_manager.remove_package("not-existing").unwrap(), RemoveResult::NotFound); assert_eq!(
temp.package_manager.remove_package("not-existing").unwrap(),
RemoveResult::NotFound
);
} }
#[test] #[test]
fn test_parse_package_from_readme() { fn test_parse_package_from_readme() {
let file = NamedTempFile::new().unwrap(); let file = NamedTempFile::new().unwrap();
fs::write(file.path(), r###" fs::write(
file.path(),
r###"
--- ---
package_name: "italian-accents" package_name: "italian-accents"
package_title: "Italian Accents" package_title: "Italian Accents"
@ -578,7 +705,8 @@ mod tests {
package_author: "Federico Terzi" package_author: "Federico Terzi"
package_repo: "https://github.com/federico-terzi/espanso-hub-core" package_repo: "https://github.com/federico-terzi/espanso-hub-core"
--- ---
"###); "###,
);
let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap(); let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap();
@ -588,7 +716,7 @@ mod tests {
version: "0.1.0".to_string(), version: "0.1.0".to_string(),
repo: "https://github.com/federico-terzi/espanso-hub-core".to_string(), repo: "https://github.com/federico-terzi/espanso-hub-core".to_string(),
desc: "Include Italian accents substitutions to espanso.".to_string(), desc: "Include Italian accents substitutions to espanso.".to_string(),
author: "Federico Terzi".to_string() author: "Federico Terzi".to_string(),
}; };
assert_eq!(package, target_package); assert_eq!(package, target_package);
@ -597,7 +725,9 @@ mod tests {
#[test] #[test]
fn test_parse_package_from_readme_with_bad_metadata() { fn test_parse_package_from_readme_with_bad_metadata() {
let file = NamedTempFile::new().unwrap(); let file = NamedTempFile::new().unwrap();
fs::write(file.path(), r###" fs::write(
file.path(),
r###"
--- ---
package_name: italian-accents package_name: italian-accents
package_title: "Italian Accents" package_title: "Italian Accents"
@ -607,7 +737,8 @@ mod tests {
package_repo: "https://github.com/federico-terzi/espanso-hub-core" package_repo: "https://github.com/federico-terzi/espanso-hub-core"
--- ---
Readme text Readme text
"###); "###,
);
let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap(); let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap();
@ -617,7 +748,7 @@ mod tests {
version: "0.1.0".to_string(), version: "0.1.0".to_string(),
repo: "https://github.com/federico-terzi/espanso-hub-core".to_string(), repo: "https://github.com/federico-terzi/espanso-hub-core".to_string(),
desc: "Include Italian accents substitutions to espanso.".to_string(), desc: "Include Italian accents substitutions to espanso.".to_string(),
author: "Federico Terzi".to_string() author: "Federico Terzi".to_string(),
}; };
assert_eq!(package, target_package); assert_eq!(package, target_package);

View File

@ -18,7 +18,7 @@
*/ */
pub(crate) mod default; pub(crate) mod default;
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
use std::error::Error; use std::error::Error;
pub trait PackageManager { pub trait PackageManager {
@ -28,7 +28,11 @@ pub trait PackageManager {
fn get_package(&self, name: &str) -> Option<Package>; fn get_package(&self, name: &str) -> Option<Package>;
fn install_package(&self, name: &str) -> Result<InstallResult, Box<dyn Error>>; fn install_package(&self, name: &str) -> Result<InstallResult, Box<dyn Error>>;
fn install_package_from_repo(&self, name: &str, repo_url: &str) -> Result<InstallResult, Box<dyn Error>>; fn install_package_from_repo(
&self,
name: &str,
repo_url: &str,
) -> Result<InstallResult, Box<dyn Error>>;
fn remove_package(&self, name: &str) -> Result<RemoveResult, Box<dyn Error>>; fn remove_package(&self, name: &str) -> Result<RemoveResult, Box<dyn Error>>;
@ -42,7 +46,7 @@ pub struct Package {
pub version: String, pub version: String,
pub repo: String, pub repo: String,
pub desc: String, pub desc: String,
pub author: String pub author: String,
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@ -50,10 +54,9 @@ pub struct PackageIndex {
#[serde(rename = "lastUpdate")] #[serde(rename = "lastUpdate")]
pub last_update: u64, pub last_update: u64,
pub packages: Vec<Package> pub packages: Vec<Package>,
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum UpdateResult { pub enum UpdateResult {
NotOutdated, NotOutdated,
@ -67,11 +70,11 @@ pub enum InstallResult {
UnableToParsePackageInfo, UnableToParsePackageInfo,
MissingPackageVersion, MissingPackageVersion,
AlreadyInstalled, AlreadyInstalled,
Installed Installed,
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum RemoveResult { pub enum RemoveResult {
NotFound, NotFound,
Removed Removed,
} }

View File

@ -17,14 +17,14 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use serde::{Deserialize, Serialize};
use std::sync::mpsc::Sender;
use crate::event::Event;
use crate::event::ActionType;
use std::io::{BufReader, Read, Write};
use std::error::Error;
use log::error;
use crate::config::ConfigSet; use crate::config::ConfigSet;
use crate::event::ActionType;
use crate::event::Event;
use log::error;
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::io::{BufReader, Read, Write};
use std::sync::mpsc::Sender;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod windows; mod windows;
@ -51,19 +51,11 @@ pub struct IPCCommand {
impl IPCCommand { impl IPCCommand {
fn to_event(&self) -> Option<Event> { fn to_event(&self) -> Option<Event> {
match self.id.as_ref() { match self.id.as_ref() {
"exit" => { "exit" => Some(Event::Action(ActionType::Exit)),
Some(Event::Action(ActionType::Exit)) "toggle" => Some(Event::Action(ActionType::Toggle)),
}, "enable" => Some(Event::Action(ActionType::Enable)),
"toggle" => { "disable" => Some(Event::Action(ActionType::Disable)),
Some(Event::Action(ActionType::Toggle)) _ => None,
},
"enable" => {
Some(Event::Action(ActionType::Enable))
},
"disable" => {
Some(Event::Action(ActionType::Disable))
},
_ => None
} }
} }
} }
@ -76,17 +68,18 @@ fn process_event<R: Read, E: Error>(event_channel: &Sender<Event>, stream: Resul
let res = buf_reader.read_to_string(&mut json_str); let res = buf_reader.read_to_string(&mut json_str);
if res.is_ok() { if res.is_ok() {
let command : Result<IPCCommand, serde_json::Error> = serde_json::from_str(&json_str); let command: Result<IPCCommand, serde_json::Error> =
serde_json::from_str(&json_str);
match command { match command {
Ok(command) => { Ok(command) => {
let event = command.to_event(); let event = command.to_event();
if let Some(event) = event { if let Some(event) = event {
event_channel.send(event).expect("Broken event channel"); event_channel.send(event).expect("Broken event channel");
} }
}, }
Err(e) => { Err(e) => {
error!("Error deserializing JSON command: {}", e); error!("Error deserializing JSON command: {}", e);
}, }
} }
} }
} }
@ -96,7 +89,10 @@ fn process_event<R: Read, E: Error>(event_channel: &Sender<Event>, stream: Resul
} }
} }
fn send_command<W: Write, E: Error>(command: IPCCommand, stream: Result<W, E>) -> Result<(), String>{ fn send_command<W: Write, E: Error>(
command: IPCCommand,
stream: Result<W, E>,
) -> Result<(), String> {
match stream { match stream {
Ok(mut stream) => { Ok(mut stream) => {
let json_str = serde_json::to_string(&command); let json_str = serde_json::to_string(&command);
@ -104,12 +100,10 @@ fn send_command<W: Write, E: Error>(command: IPCCommand, stream: Result<W, E>) -
stream.write_all(json_str.as_bytes()).unwrap_or_else(|e| { stream.write_all(json_str.as_bytes()).unwrap_or_else(|e| {
println!("Can't write to IPC socket: {}", e); println!("Can't write to IPC socket: {}", e);
}); });
return Ok(()) return Ok(());
} }
},
Err(e) => {
return Err(format!("Can't connect to daemon: {}", e))
} }
Err(e) => return Err(format!("Can't connect to daemon: {}", e)),
} }
Err("Can't send command".to_owned()) Err("Can't send command".to_owned())

View File

@ -17,10 +17,10 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::os::unix::net::{UnixStream,UnixListener};
use log::{info, warn};
use std::sync::mpsc::Sender;
use super::IPCCommand; use super::IPCCommand;
use log::{info, warn};
use std::os::unix::net::{UnixListener, UnixStream};
use std::sync::mpsc::Sender;
use crate::context; use crate::context;
use crate::event::*; use crate::event::*;
@ -41,27 +41,32 @@ impl UnixIPCServer {
impl super::IPCServer for UnixIPCServer { impl super::IPCServer for UnixIPCServer {
fn start(&self) { fn start(&self) {
let event_channel = self.event_channel.clone(); let event_channel = self.event_channel.clone();
std::thread::Builder::new().name("ipc_server".to_string()).spawn(move || { std::thread::Builder::new()
.name("ipc_server".to_string())
.spawn(move || {
let espanso_dir = context::get_data_dir(); let espanso_dir = context::get_data_dir();
let unix_socket = espanso_dir.join(UNIX_SOCKET_NAME); let unix_socket = espanso_dir.join(UNIX_SOCKET_NAME);
std::fs::remove_file(unix_socket.clone()).unwrap_or_else(|e| { std::fs::remove_file(unix_socket.clone()).unwrap_or_else(|e| {
warn!("Unable to delete Unix socket: {}", e); warn!("Unable to delete Unix socket: {}", e);
}); });
let listener = UnixListener::bind(unix_socket.clone()).expect("Can't bind to Unix Socket"); let listener =
UnixListener::bind(unix_socket.clone()).expect("Can't bind to Unix Socket");
info!("Binded to IPC unix socket: {}", unix_socket.as_path().display()); info!(
"Binded to IPC unix socket: {}",
unix_socket.as_path().display()
);
for stream in listener.incoming() { for stream in listener.incoming() {
process_event(&event_channel, stream); process_event(&event_channel, stream);
} }
}).expect("Unable to spawn IPC server thread"); })
.expect("Unable to spawn IPC server thread");
} }
} }
pub struct UnixIPCClient { pub struct UnixIPCClient {}
}
impl UnixIPCClient { impl UnixIPCClient {
pub fn new() -> UnixIPCClient { pub fn new() -> UnixIPCClient {

View File

@ -17,14 +17,14 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use log::{info};
use std::sync::mpsc::Sender;
use std::net::{TcpListener, TcpStream};
use super::IPCCommand; use super::IPCCommand;
use log::info;
use std::net::{TcpListener, TcpStream};
use std::sync::mpsc::Sender;
use crate::config::ConfigSet;
use crate::event::*; use crate::event::*;
use crate::protocol::{process_event, send_command}; use crate::protocol::{process_event, send_command};
use crate::config::ConfigSet;
pub struct WindowsIPCServer { pub struct WindowsIPCServer {
config_set: ConfigSet, config_set: ConfigSet,
@ -33,7 +33,10 @@ pub struct WindowsIPCServer {
impl WindowsIPCServer { impl WindowsIPCServer {
pub fn new(config_set: ConfigSet, event_channel: Sender<Event>) -> WindowsIPCServer { pub fn new(config_set: ConfigSet, event_channel: Sender<Event>) -> WindowsIPCServer {
WindowsIPCServer {config_set, event_channel} WindowsIPCServer {
config_set,
event_channel,
}
} }
} }
@ -41,17 +44,22 @@ impl super::IPCServer for WindowsIPCServer {
fn start(&self) { fn start(&self) {
let event_channel = self.event_channel.clone(); let event_channel = self.event_channel.clone();
let server_port = self.config_set.default.ipc_server_port; let server_port = self.config_set.default.ipc_server_port;
std::thread::Builder::new().name("ipc_server".to_string()).spawn(move || { std::thread::Builder::new()
let listener = TcpListener::bind( .name("ipc_server".to_string())
format!("127.0.0.1:{}", server_port) .spawn(move || {
).expect("Error binding to IPC server port"); let listener = TcpListener::bind(format!("127.0.0.1:{}", server_port))
.expect("Error binding to IPC server port");
info!("Binded to IPC tcp socket: {}", listener.local_addr().unwrap().to_string()); info!(
"Binded to IPC tcp socket: {}",
listener.local_addr().unwrap().to_string()
);
for stream in listener.incoming() { for stream in listener.incoming() {
process_event(&event_channel, stream); process_event(&event_channel, stream);
} }
}).expect("Unable to spawn IPC server thread"); })
.expect("Unable to spawn IPC server thread");
} }
} }
@ -67,9 +75,8 @@ impl WindowsIPCClient {
impl super::IPCClient for WindowsIPCClient { impl super::IPCClient for WindowsIPCClient {
fn send_command(&self, command: IPCCommand) -> Result<(), String> { fn send_command(&self, command: IPCCommand) -> Result<(), String> {
let stream = TcpStream::connect( let stream =
("127.0.0.1", self.config_set.default.ipc_server_port as u16) TcpStream::connect(("127.0.0.1", self.config_set.default.ipc_server_port as u16));
);
send_command(command, stream) send_command(command, stream)
} }

View File

@ -17,15 +17,15 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use serde_yaml::{Mapping, Value};
use std::path::PathBuf;
use std::collections::HashMap;
use regex::{Regex, Captures};
use log::{warn, error};
use super::*; use super::*;
use crate::matcher::{Match, MatchContentType};
use crate::config::Configs; use crate::config::Configs;
use crate::extension::Extension; use crate::extension::Extension;
use crate::matcher::{Match, MatchContentType};
use log::{error, warn};
use regex::{Captures, Regex};
use serde_yaml::{Mapping, Value};
use std::collections::HashMap;
use std::path::PathBuf;
lazy_static! { lazy_static! {
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P<name>\\w+)\\s*\\}\\}").unwrap(); static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P<name>\\w+)\\s*\\}\\}").unwrap();
@ -47,8 +47,7 @@ impl DefaultRenderer {
} }
// Compile the regexes // Compile the regexes
let passive_match_regex = Regex::new(&config.passive_match_regex) let passive_match_regex = Regex::new(&config.passive_match_regex).unwrap_or_else(|e| {
.unwrap_or_else(|e| {
panic!("Invalid passive match regex"); panic!("Invalid passive match regex");
}); });
@ -90,17 +89,24 @@ impl super::Renderer for DefaultRenderer {
// Extract the match trigger from the variable params // Extract the match trigger from the variable params
let trigger = variable.params.get(&Value::from("trigger")); let trigger = variable.params.get(&Value::from("trigger"));
if trigger.is_none() { if trigger.is_none() {
warn!("Missing param 'trigger' in match variable: {}", variable.name); warn!(
"Missing param 'trigger' in match variable: {}",
variable.name
);
continue; continue;
} }
let trigger = trigger.unwrap(); let trigger = trigger.unwrap();
// Find the given match from the active configs // Find the given match from the active configs
let inner_match = DefaultRenderer::find_match(config, trigger.as_str().unwrap_or("")); let inner_match =
DefaultRenderer::find_match(config, trigger.as_str().unwrap_or(""));
if inner_match.is_none() { if inner_match.is_none() {
warn!("Could not find inner match with trigger: '{}'", trigger.as_str().unwrap_or("undefined")); warn!(
continue "Could not find inner match with trigger: '{}'",
trigger.as_str().unwrap_or("undefined")
);
continue;
} }
let inner_match = inner_match.unwrap(); let inner_match = inner_match.unwrap();
@ -118,7 +124,8 @@ impl super::Renderer for DefaultRenderer {
warn!("Inner matches must be of TEXT type. Mixing images is not supported yet.") warn!("Inner matches must be of TEXT type. Mixing images is not supported yet.")
}, },
} }
}else{ // Normal extension variables } else {
// Normal extension variables
let extension = self.extension_map.get(&variable.var_type); let extension = self.extension_map.get(&variable.var_type);
if let Some(extension) = extension { if let Some(extension) = extension {
let ext_out = extension.calculate(&variable.params, &args); let ext_out = extension.calculate(&variable.params, &args);
@ -126,10 +133,16 @@ impl super::Renderer for DefaultRenderer {
output_map.insert(variable.name.clone(), output); output_map.insert(variable.name.clone(), output);
} else { } else {
output_map.insert(variable.name.clone(), "".to_owned()); output_map.insert(variable.name.clone(), "".to_owned());
warn!("Could not generate output for variable: {}", variable.name); warn!(
"Could not generate output for variable: {}",
variable.name
);
} }
} else { } else {
error!("No extension found for variable type: {}", variable.var_type); error!(
"No extension found for variable type: {}",
variable.var_type
);
} }
} }
} }
@ -142,7 +155,8 @@ impl super::Renderer for DefaultRenderer {
}); });
result.to_string() result.to_string()
}else{ // No variables, simple text substitution } else {
// No variables, simple text substitution
content.replace.clone() content.replace.clone()
}; };
@ -150,7 +164,7 @@ impl super::Renderer for DefaultRenderer {
let target_string = utils::render_args(&target_string, &args); let target_string = utils::render_args(&target_string, &args);
RenderResult::Text(target_string) RenderResult::Text(target_string)
}, }
// Image Match // Image Match
MatchContentType::Image(content) => { MatchContentType::Image(content) => {
@ -161,20 +175,21 @@ impl super::Renderer for DefaultRenderer {
error!("Image not found in path: {:?}", content.path); error!("Image not found in path: {:?}", content.path);
RenderResult::Error RenderResult::Error
} }
}, }
} }
} }
fn render_passive(&self, text: &str, config: &Configs) -> RenderResult { fn render_passive(&self, text: &str, config: &Configs) -> RenderResult {
// Render the matches // Render the matches
let result = self.passive_match_regex.replace_all(&text, |caps: &Captures| { let result = self
.passive_match_regex
.replace_all(&text, |caps: &Captures| {
let match_name = if let Some(name) = caps.name("name") { let match_name = if let Some(name) = caps.name("name") {
name.as_str() name.as_str()
} else { } else {
"" ""
}; };
// Get the original matching string, useful to return the match untouched // Get the original matching string, useful to return the match untouched
let original_match = caps.get(0).unwrap().as_str(); let original_match = caps.get(0).unwrap().as_str();
@ -192,21 +207,19 @@ impl super::Renderer for DefaultRenderer {
} else { } else {
"" ""
}; };
let args : Vec<String> = utils::split_args(match_args, let args: Vec<String> = utils::split_args(
match_args,
config.passive_arg_delimiter, config.passive_arg_delimiter,
config.passive_arg_escape); config.passive_arg_escape,
);
let m = m.unwrap(); let m = m.unwrap();
// Render the actual match // Render the actual match
let result = self.render_match(&m, &config, args); let result = self.render_match(&m, &config, args);
match result { match result {
RenderResult::Text(out) => { RenderResult::Text(out) => out,
out _ => original_match.to_owned(),
},
_ => {
original_match.to_owned()
}
} }
}); });
@ -233,10 +246,8 @@ mod tests {
match rendered { match rendered {
RenderResult::Text(rendered) => { RenderResult::Text(rendered) => {
assert_eq!(rendered, target); assert_eq!(rendered, target);
},
_ => {
assert!(false)
} }
_ => assert!(false),
} }
} }
@ -246,11 +257,13 @@ mod tests {
this text contains no matches this text contains no matches
"###; "###;
let config = get_config_for(r###" let config = get_config_for(
r###"
matches: matches:
- trigger: test - trigger: test
replace: result replace: result
"###); "###,
);
let renderer = get_renderer(config.clone()); let renderer = get_renderer(config.clone());
@ -263,11 +276,13 @@ mod tests {
fn test_render_passive_simple_match_no_args() { fn test_render_passive_simple_match_no_args() {
let text = "this is a :test"; let text = "this is a :test";
let config = get_config_for(r###" let config = get_config_for(
r###"
matches: matches:
- trigger: ':test' - trigger: ':test'
replace: result replace: result
"###); "###,
);
let renderer = get_renderer(config.clone()); let renderer = get_renderer(config.clone());
@ -280,11 +295,13 @@ mod tests {
fn test_render_passive_multiple_match_no_args() { fn test_render_passive_multiple_match_no_args() {
let text = "this is a :test and then another :test"; let text = "this is a :test and then another :test";
let config = get_config_for(r###" let config = get_config_for(
r###"
matches: matches:
- trigger: ':test' - trigger: ':test'
replace: result replace: result
"###); "###,
);
let renderer = get_renderer(config.clone()); let renderer = get_renderer(config.clone());
@ -303,11 +320,13 @@ mod tests {
result result
"###; "###;
let config = get_config_for(r###" let config = get_config_for(
r###"
matches: matches:
- trigger: ':test' - trigger: ':test'
replace: result replace: result
"###); "###,
);
let renderer = get_renderer(config.clone()); let renderer = get_renderer(config.clone());
@ -320,7 +339,8 @@ mod tests {
fn test_render_passive_nested_matches_no_args() { fn test_render_passive_nested_matches_no_args() {
let text = ":greet"; let text = ":greet";
let config = get_config_for(r###" let config = get_config_for(
r###"
matches: matches:
- trigger: ':greet' - trigger: ':greet'
replace: "hi {{name}}" replace: "hi {{name}}"
@ -332,7 +352,8 @@ mod tests {
- trigger: ':name' - trigger: ':name'
replace: john replace: john
"###); "###,
);
let renderer = get_renderer(config.clone()); let renderer = get_renderer(config.clone());
@ -345,11 +366,13 @@ mod tests {
fn test_render_passive_simple_match_with_args() { fn test_render_passive_simple_match_with_args() {
let text = ":greet/Jon/"; let text = ":greet/Jon/";
let config = get_config_for(r###" let config = get_config_for(
r###"
matches: matches:
- trigger: ':greet' - trigger: ':greet'
replace: "Hi $0$" replace: "Hi $0$"
"###); "###,
);
let renderer = get_renderer(config.clone()); let renderer = get_renderer(config.clone());
@ -362,11 +385,13 @@ mod tests {
fn test_render_passive_simple_match_with_multiple_args() { fn test_render_passive_simple_match_with_multiple_args() {
let text = ":greet/Jon/Snow/"; let text = ":greet/Jon/Snow/";
let config = get_config_for(r###" let config = get_config_for(
r###"
matches: matches:
- trigger: ':greet' - trigger: ':greet'
replace: "Hi $0$, there is $1$ outside" replace: "Hi $0$, there is $1$ outside"
"###); "###,
);
let renderer = get_renderer(config.clone()); let renderer = get_renderer(config.clone());
@ -379,11 +404,13 @@ mod tests {
fn test_render_passive_simple_match_with_escaped_args() { fn test_render_passive_simple_match_with_escaped_args() {
let text = ":greet/Jon/10\\/12/"; let text = ":greet/Jon/10\\/12/";
let config = get_config_for(r###" let config = get_config_for(
r###"
matches: matches:
- trigger: ':greet' - trigger: ':greet'
replace: "Hi $0$, today is $1$" replace: "Hi $0$, today is $1$"
"###); "###,
);
let renderer = get_renderer(config.clone()); let renderer = get_renderer(config.clone());
@ -396,11 +423,13 @@ mod tests {
fn test_render_passive_simple_match_with_args_not_closed() { fn test_render_passive_simple_match_with_args_not_closed() {
let text = ":greet/Jon/Snow"; let text = ":greet/Jon/Snow";
let config = get_config_for(r###" let config = get_config_for(
r###"
matches: matches:
- trigger: ':greet' - trigger: ':greet'
replace: "Hi $0$" replace: "Hi $0$"
"###); "###,
);
let renderer = get_renderer(config.clone()); let renderer = get_renderer(config.clone());
@ -413,7 +442,8 @@ mod tests {
fn test_render_passive_local_var() { fn test_render_passive_local_var() {
let text = "this is :test"; let text = "this is :test";
let config = get_config_for(r###" let config = get_config_for(
r###"
matches: matches:
- trigger: ':test' - trigger: ':test'
replace: "my {{output}}" replace: "my {{output}}"
@ -422,7 +452,8 @@ mod tests {
type: dummy type: dummy
params: params:
echo: "result" echo: "result"
"###); "###,
);
let renderer = get_renderer(config.clone()); let renderer = get_renderer(config.clone());
@ -435,7 +466,8 @@ mod tests {
fn test_render_passive_global_var() { fn test_render_passive_global_var() {
let text = "this is :test"; let text = "this is :test";
let config = get_config_for(r###" let config = get_config_for(
r###"
global_vars: global_vars:
- name: output - name: output
type: dummy type: dummy
@ -445,7 +477,8 @@ mod tests {
- trigger: ':test' - trigger: ':test'
replace: "my {{output}}" replace: "my {{output}}"
"###); "###,
);
let renderer = get_renderer(config.clone()); let renderer = get_renderer(config.clone());
@ -458,7 +491,8 @@ mod tests {
fn test_render_passive_global_var_is_overridden_by_local() { fn test_render_passive_global_var_is_overridden_by_local() {
let text = "this is :test"; let text = "this is :test";
let config = get_config_for(r###" let config = get_config_for(
r###"
global_vars: global_vars:
- name: output - name: output
type: dummy type: dummy
@ -473,7 +507,8 @@ mod tests {
params: params:
echo: "local" echo: "local"
"###); "###,
);
let renderer = get_renderer(config.clone()); let renderer = get_renderer(config.clone());

View File

@ -17,9 +17,9 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::path::PathBuf;
use crate::matcher::{Match};
use crate::config::Configs; use crate::config::Configs;
use crate::matcher::Match;
use std::path::PathBuf;
pub(crate) mod default; pub(crate) mod default;
pub(crate) mod utils; pub(crate) mod utils;
@ -35,5 +35,5 @@ pub trait Renderer {
pub enum RenderResult { pub enum RenderResult {
Text(String), Text(String),
Image(PathBuf), Image(PathBuf),
Error Error,
} }

View File

@ -17,7 +17,7 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use regex::{Regex, Captures}; use regex::{Captures, Regex};
lazy_static! { lazy_static! {
static ref ARG_REGEX: Regex = Regex::new("\\$(?P<pos>\\d+)\\$").unwrap(); static ref ARG_REGEX: Regex = Regex::new("\\$(?P<pos>\\d+)\\$").unwrap();
@ -43,7 +43,7 @@ pub fn split_args(text: &str, delimiter: char, escape: char) -> Vec<String> {
// Make sure the text is not empty // Make sure the text is not empty
if text.is_empty() { if text.is_empty() {
return output return output;
} }
let mut last = String::from(""); let mut last = String::from("");
@ -80,25 +80,28 @@ mod tests {
#[test] #[test]
fn test_render_args_no_args() { fn test_render_args_no_args() {
let args = vec!("hello".to_owned()); let args = vec!["hello".to_owned()];
assert_eq!(render_args("no args", &args), "no args") assert_eq!(render_args("no args", &args), "no args")
} }
#[test] #[test]
fn test_render_args_one_arg() { fn test_render_args_one_arg() {
let args = vec!("jon".to_owned()); let args = vec!["jon".to_owned()];
assert_eq!(render_args("hello $0$", &args), "hello jon") assert_eq!(render_args("hello $0$", &args), "hello jon")
} }
#[test] #[test]
fn test_render_args_one_multiple_args() { fn test_render_args_one_multiple_args() {
let args = vec!("jon".to_owned(), "snow".to_owned()); let args = vec!["jon".to_owned(), "snow".to_owned()];
assert_eq!(render_args("hello $0$, the $1$ is white", &args), "hello jon, the snow is white") assert_eq!(
render_args("hello $0$, the $1$ is white", &args),
"hello jon, the snow is white"
)
} }
#[test] #[test]
fn test_render_args_out_of_range() { fn test_render_args_out_of_range() {
let args = vec!("jon".to_owned()); let args = vec!["jon".to_owned()];
assert_eq!(render_args("hello $10$", &args), "hello ") assert_eq!(render_args("hello $10$", &args), "hello ")
} }

View File

@ -44,13 +44,21 @@ pub fn register(_config_set: ConfigSet) {
let plist_file = agents_dir.join(MAC_PLIST_FILENAME); let plist_file = agents_dir.join(MAC_PLIST_FILENAME);
if !plist_file.exists() { if !plist_file.exists() {
println!("Creating LaunchAgents entry: {}", plist_file.to_str().unwrap_or_default()); println!(
"Creating LaunchAgents entry: {}",
plist_file.to_str().unwrap_or_default()
);
let espanso_path = std::env::current_exe().expect("Could not get espanso executable path"); let espanso_path = std::env::current_exe().expect("Could not get espanso executable path");
println!("Entry will point to: {}", espanso_path.to_str().unwrap_or_default()); println!(
"Entry will point to: {}",
espanso_path.to_str().unwrap_or_default()
);
let plist_content = String::from(MAC_PLIST_CONTENT) let plist_content = String::from(MAC_PLIST_CONTENT).replace(
.replace("{{{espanso_path}}}", espanso_path.to_str().unwrap_or_default()); "{{{espanso_path}}}",
espanso_path.to_str().unwrap_or_default(),
);
std::fs::write(plist_file.clone(), plist_content).expect("Unable to write plist file"); std::fs::write(plist_file.clone(), plist_content).expect("Unable to write plist file");
@ -153,15 +161,24 @@ pub fn register(config_set: ConfigSet) {
let service_file = user_dir.join(LINUX_SERVICE_FILENAME); let service_file = user_dir.join(LINUX_SERVICE_FILENAME);
if !service_file.exists() { if !service_file.exists() {
println!("Creating service entry: {}", service_file.to_str().unwrap_or_default()); println!(
"Creating service entry: {}",
service_file.to_str().unwrap_or_default()
);
let espanso_path = std::env::current_exe().expect("Could not get espanso executable path"); let espanso_path = std::env::current_exe().expect("Could not get espanso executable path");
println!("Entry will point to: {}", espanso_path.to_str().unwrap_or_default()); println!(
"Entry will point to: {}",
espanso_path.to_str().unwrap_or_default()
);
let service_content = String::from(LINUX_SERVICE_CONTENT) let service_content = String::from(LINUX_SERVICE_CONTENT).replace(
.replace("{{{espanso_path}}}", espanso_path.to_str().unwrap_or_default()); "{{{espanso_path}}}",
espanso_path.to_str().unwrap_or_default(),
);
std::fs::write(service_file.clone(), service_content).expect("Unable to write service file"); std::fs::write(service_file.clone(), service_content)
.expect("Unable to write service file");
println!("Service file created correctly!") println!("Service file created correctly!")
} }
@ -202,11 +219,14 @@ pub fn unregister(config_set: ConfigSet) {
Ok(_) => { Ok(_) => {
println!("Deleted entry at {}", service_file.to_string_lossy()); println!("Deleted entry at {}", service_file.to_string_lossy());
println!("Service unregistered successfully!"); println!("Service unregistered successfully!");
}, }
Err(e) => { Err(e) => {
println!("Error, could not delete service entry at {} with error {}", println!(
service_file.to_string_lossy(), e); "Error, could not delete service entry at {} with error {}",
}, service_file.to_string_lossy(),
e
);
}
} }
} else { } else {
eprintln!("Error, could not find espanso service file"); eprintln!("Error, could not find espanso service file");

View File

@ -19,7 +19,9 @@
use std::os::raw::c_char; use std::os::raw::c_char;
use crate::bridge::linux::{get_active_window_name, get_active_window_class, get_active_window_executable}; use crate::bridge::linux::{
get_active_window_class, get_active_window_executable, get_active_window_name,
};
use std::ffi::CStr; use std::ffi::CStr;
pub struct LinuxSystemManager {} pub struct LinuxSystemManager {}

View File

@ -19,12 +19,10 @@
use std::os::raw::c_char; use std::os::raw::c_char;
use std::ffi::CStr;
use crate::bridge::macos::{get_active_app_bundle, get_active_app_identifier}; use crate::bridge::macos::{get_active_app_bundle, get_active_app_identifier};
use std::ffi::CStr;
pub struct MacSystemManager { pub struct MacSystemManager {}
}
impl super::SystemManager for MacSystemManager { impl super::SystemManager for MacSystemManager {
fn get_current_window_title(&self) -> Option<String> { fn get_current_window_title(&self) -> Option<String> {
@ -70,8 +68,6 @@ impl super::SystemManager for MacSystemManager {
impl MacSystemManager { impl MacSystemManager {
pub fn new() -> MacSystemManager { pub fn new() -> MacSystemManager {
MacSystemManager{ MacSystemManager {}
}
} }
} }

View File

@ -17,12 +17,10 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use widestring::U16CString;
use crate::bridge::windows::*; use crate::bridge::windows::*;
use widestring::U16CString;
pub struct WindowsSystemManager { pub struct WindowsSystemManager {}
}
impl WindowsSystemManager { impl WindowsSystemManager {
pub fn new() -> WindowsSystemManager { pub fn new() -> WindowsSystemManager {

View File

@ -17,10 +17,10 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::process::Command;
use super::MenuItem; use super::MenuItem;
use log::{error, info}; use log::{error, info};
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command;
const LINUX_ICON_CONTENT: &[u8] = include_bytes!("../res/linux/icon.png"); const LINUX_ICON_CONTENT: &[u8] = include_bytes!("../res/linux/icon.png");
@ -31,8 +31,14 @@ pub struct LinuxUIManager {
impl super::UIManager for LinuxUIManager { impl super::UIManager for LinuxUIManager {
fn notify(&self, message: &str) { fn notify(&self, message: &str) {
let res = Command::new("notify-send") let res = Command::new("notify-send")
.args(&["-i", self.icon_path.to_str().unwrap_or_default(), .args(&[
"-t", "2000", "espanso", message]) "-i",
self.icon_path.to_str().unwrap_or_default(),
"-t",
"2000",
"espanso",
message,
])
.output(); .output();
if let Err(e) = res { if let Err(e) = res {
@ -55,12 +61,13 @@ impl LinuxUIManager {
let data_dir = crate::context::get_data_dir(); let data_dir = crate::context::get_data_dir();
let icon_path = data_dir.join("icon.png"); let icon_path = data_dir.join("icon.png");
if !icon_path.exists() { if !icon_path.exists() {
info!("Creating espanso icon in '{}'", icon_path.to_str().unwrap_or_default()); info!(
"Creating espanso icon in '{}'",
icon_path.to_str().unwrap_or_default()
);
std::fs::write(&icon_path, LINUX_ICON_CONTENT).expect("Unable to copy espanso icon"); std::fs::write(&icon_path, LINUX_ICON_CONTENT).expect("Unable to copy espanso icon");
} }
LinuxUIManager{ LinuxUIManager { icon_path }
icon_path
}
} }
} }

View File

@ -17,22 +17,22 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::{fs, io}; use crate::bridge::macos::{show_context_menu, MacMenuItem};
use std::io::{Cursor}; use crate::context;
use crate::ui::{MenuItem, MenuItemType};
use log::{debug, info, warn};
use std::ffi::CString; use std::ffi::CString;
use log::{info, warn, debug}; use std::io::Cursor;
use std::os::raw::c_char;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use crate::ui::{MenuItem, MenuItemType}; use std::{fs, io};
use crate::bridge::macos::{MacMenuItem, show_context_menu};
use std::os::raw::c_char;
use crate::context;
const NOTIFY_HELPER_BINARY: &'static [u8] = include_bytes!("../res/mac/EspansoNotifyHelper.zip"); const NOTIFY_HELPER_BINARY: &'static [u8] = include_bytes!("../res/mac/EspansoNotifyHelper.zip");
const DEFAULT_NOTIFICATION_DELAY: f64 = 1.5; const DEFAULT_NOTIFICATION_DELAY: f64 = 1.5;
pub struct MacUIManager { pub struct MacUIManager {
notify_helper_path: PathBuf notify_helper_path: PathBuf,
} }
impl super::UIManager for MacUIManager { impl super::UIManager for MacUIManager {
@ -61,8 +61,8 @@ impl super::UIManager for MacUIManager {
} }
let menu_type = match item.item_type { let menu_type = match item.item_type {
MenuItemType::Button => {1}, MenuItemType::Button => 1,
MenuItemType::Separator => {2}, MenuItemType::Separator => 2,
}; };
let raw_item = MacMenuItem { let raw_item = MacMenuItem {
@ -74,7 +74,9 @@ impl super::UIManager for MacUIManager {
raw_menu.push(raw_item); raw_menu.push(raw_item);
} }
unsafe { show_context_menu(raw_menu.as_ptr(), raw_menu.len() as i32); } unsafe {
show_context_menu(raw_menu.as_ptr(), raw_menu.len() as i32);
}
} }
fn cleanup(&self) { fn cleanup(&self) {
@ -86,15 +88,16 @@ impl MacUIManager {
pub fn new() -> MacUIManager { pub fn new() -> MacUIManager {
let notify_helper_path = MacUIManager::initialize_notify_helper(); let notify_helper_path = MacUIManager::initialize_notify_helper();
MacUIManager{ MacUIManager { notify_helper_path }
notify_helper_path
}
} }
fn initialize_notify_helper() -> PathBuf { fn initialize_notify_helper() -> PathBuf {
let espanso_dir = context::get_data_dir(); let espanso_dir = context::get_data_dir();
info!("Initializing EspansoNotifyHelper in {}", espanso_dir.as_path().display()); info!(
"Initializing EspansoNotifyHelper in {}",
espanso_dir.as_path().display()
);
let espanso_target = espanso_dir.join("EspansoNotifyHelper.app"); let espanso_target = espanso_dir.join("EspansoNotifyHelper.app");
@ -118,10 +121,19 @@ impl MacUIManager {
} }
if (&*file.name()).ends_with('/') { if (&*file.name()).ends_with('/') {
debug!("File {} extracted to \"{}\"", i, outpath.as_path().display()); debug!(
"File {} extracted to \"{}\"",
i,
outpath.as_path().display()
);
fs::create_dir_all(&outpath).unwrap(); fs::create_dir_all(&outpath).unwrap();
} else { } else {
debug!("File {} extracted to \"{}\" ({} bytes)", i, outpath.as_path().display(), file.size()); debug!(
"File {} extracted to \"{}\" ({} bytes)",
i,
outpath.as_path().display(),
file.size()
);
if let Some(p) = outpath.parent() { if let Some(p) = outpath.parent() {
if !p.exists() { if !p.exists() {
fs::create_dir_all(&p).unwrap(); fs::create_dir_all(&p).unwrap();

View File

@ -17,16 +17,18 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use crate::bridge::windows::{show_notification, close_notification, WindowsMenuItem, show_context_menu, cleanup_ui}; use crate::bridge::windows::{
use widestring::U16CString; cleanup_ui, close_notification, show_context_menu, show_notification, WindowsMenuItem,
use std::{thread, time}; };
use log::{debug};
use std::sync::Mutex;
use std::sync::Arc;
use crate::ui::{MenuItem, MenuItemType}; use crate::ui::{MenuItem, MenuItemType};
use log::debug;
use std::sync::Arc;
use std::sync::Mutex;
use std::{thread, time};
use widestring::U16CString;
pub struct WindowsUIManager { pub struct WindowsUIManager {
id: Arc<Mutex<i32>> id: Arc<Mutex<i32>>,
} }
impl super::UIManager for WindowsUIManager { impl super::UIManager for WindowsUIManager {
@ -39,7 +41,9 @@ impl super::UIManager for WindowsUIManager {
// Setup a timeout to close the notification // Setup a timeout to close the notification
let id = Arc::clone(&self.id); let id = Arc::clone(&self.id);
let _ = thread::Builder::new().name("notification_thread".to_string()).spawn(move || { let _ = thread::Builder::new()
.name("notification_thread".to_string())
.spawn(move || {
for _ in 1..10 { for _ in 1..10 {
let duration = time::Duration::from_millis(200); let duration = time::Duration::from_millis(200);
thread::sleep(duration); thread::sleep(duration);
@ -61,7 +65,6 @@ impl super::UIManager for WindowsUIManager {
let message = U16CString::from_str(message).unwrap(); let message = U16CString::from_str(message).unwrap();
show_notification(message.as_ptr()); show_notification(message.as_ptr());
} }
} }
fn show_menu(&self, menu: Vec<MenuItem>) { fn show_menu(&self, menu: Vec<MenuItem>) {
@ -75,8 +78,8 @@ impl super::UIManager for WindowsUIManager {
} }
let menu_type = match item.item_type { let menu_type = match item.item_type {
MenuItemType::Button => {1}, MenuItemType::Button => 1,
MenuItemType::Separator => {2}, MenuItemType::Separator => 2,
}; };
let raw_item = WindowsMenuItem { let raw_item = WindowsMenuItem {
@ -88,7 +91,9 @@ impl super::UIManager for WindowsUIManager {
raw_menu.push(raw_item); raw_menu.push(raw_item);
} }
unsafe { show_context_menu(raw_menu.as_ptr(), raw_menu.len() as i32); } unsafe {
show_context_menu(raw_menu.as_ptr(), raw_menu.len() as i32);
}
} }
fn cleanup(&self) { fn cleanup(&self) {
@ -102,9 +107,7 @@ impl WindowsUIManager {
pub fn new() -> WindowsUIManager { pub fn new() -> WindowsUIManager {
let id = Arc::new(Mutex::new(0)); let id = Arc::new(Mutex::new(0));
let manager = WindowsUIManager { let manager = WindowsUIManager { id };
id
};
manager manager
} }

View File

@ -17,9 +17,9 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::path::Path;
use std::error::Error; use std::error::Error;
use std::fs::create_dir; use std::fs::create_dir;
use std::path::Path;
pub fn copy_dir(source_dir: &Path, dest_dir: &Path) -> Result<(), Box<dyn Error>> { pub fn copy_dir(source_dir: &Path, dest_dir: &Path) -> Result<(), Box<dyn Error>> {
for entry in std::fs::read_dir(source_dir)? { for entry in std::fs::read_dir(source_dir)? {
@ -31,7 +31,8 @@ pub fn copy_dir(source_dir: &Path, dest_dir: &Path) -> Result<(), Box<dyn Error>
create_dir(&target_dir)?; create_dir(&target_dir)?;
copy_dir(&entry, &target_dir)?; copy_dir(&entry, &target_dir)?;
} else if entry.is_file() { } else if entry.is_file() {
let target_entry = dest_dir.join(entry.file_name().expect("Error obtaining the filename")); let target_entry =
dest_dir.join(entry.file_name().expect("Error obtaining the filename"));
std::fs::copy(entry, target_entry)?; std::fs::copy(entry, target_entry)?;
} }
} }
@ -42,8 +43,8 @@ pub fn copy_dir(source_dir: &Path, dest_dir: &Path) -> Result<(), Box<dyn Error>
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use tempfile::TempDir;
use std::fs::create_dir; use std::fs::create_dir;
use tempfile::TempDir;
#[test] #[test]
fn test_copy_dir_into() { fn test_copy_dir_into() {
@ -88,7 +89,9 @@ mod tests {
assert!(dest_tmp_dir.path().join("source/file2.txt").exists()); assert!(dest_tmp_dir.path().join("source/file2.txt").exists());
assert!(dest_tmp_dir.path().join("source/nested").exists()); assert!(dest_tmp_dir.path().join("source/nested").exists());
assert!(dest_tmp_dir.path().join("source/nested/nestedfile.txt").exists()); assert!(dest_tmp_dir
.path()
.join("source/nested/nestedfile.txt")
.exists());
} }
} }