diff --git a/build.rs b/build.rs index b2b1f65..ca8571f 100644 --- a/build.rs +++ b/build.rs @@ -25,7 +25,7 @@ fn get_config() -> PathBuf { */ #[cfg(target_os = "windows")] -fn print_config() { +fn print_config() { println!("cargo:rustc-link-lib=static=winbridge"); println!("cargo:rustc-link-lib=dylib=user32"); } @@ -47,10 +47,9 @@ fn print_config() { println!("cargo:rustc-link-lib=framework=IOKit"); } -fn main() -{ +fn main() { let dst = get_config(); println!("cargo:rustc-link-search=native={}", dst.display()); print_config(); -} \ No newline at end of file +} diff --git a/src/bridge/linux.rs b/src/bridge/linux.rs index 5c0ec4c..ef04d27 100644 --- a/src/bridge/linux.rs +++ b/src/bridge/linux.rs @@ -17,11 +17,11 @@ * along with espanso. If not, see . */ -use std::os::raw::{c_void, c_char}; +use std::os::raw::{c_char, c_void}; #[allow(improper_ctypes)] -#[link(name="linuxbridge", kind="static")] -extern { +#[link(name = "linuxbridge", kind = "static")] +extern "C" { pub fn check_x11() -> i32; pub fn initialize(s: *const c_void) -> i32; pub fn eventloop(); @@ -34,8 +34,9 @@ extern { pub fn is_current_window_special() -> i32; // Keyboard - pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u8, - i32, i32, i32)); + pub fn register_keypress_callback( + cb: extern "C" fn(_self: *mut c_void, *const u8, i32, i32, i32), + ); pub fn send_string(string: *const c_char); pub fn delete_string(count: i32); @@ -52,4 +53,4 @@ extern { pub fn fast_delete_string(count: i32, delay: i32); pub fn fast_left_arrow(count: i32); pub fn fast_send_enter(); -} \ No newline at end of file +} diff --git a/src/bridge/macos.rs b/src/bridge/macos.rs index 1cfb204..cb87675 100644 --- a/src/bridge/macos.rs +++ b/src/bridge/macos.rs @@ -17,7 +17,7 @@ * along with espanso. If not, see . */ -use std::os::raw::{c_void, c_char}; +use std::os::raw::{c_char, c_void}; #[repr(C)] pub struct MacMenuItem { @@ -27,8 +27,8 @@ pub struct MacMenuItem { } #[allow(improper_ctypes)] -#[link(name="macbridge", kind="static")] -extern { +#[link(name = "macbridge", kind = "static")] +extern "C" { pub fn initialize(s: *const c_void, icon_path: *const c_char, show_icon: i32); pub fn eventloop(); pub fn headless_eventloop(); @@ -39,8 +39,8 @@ extern { pub fn open_settings_panel(); pub fn get_active_app_bundle(buffer: *mut c_char, size: i32) -> i32; pub fn get_active_app_identifier(buffer: *mut c_char, size: i32) -> i32; - pub fn get_secure_input_process(pid:*mut i64) -> i32; - pub fn get_path_from_pid(pid:i64, buffer: *mut c_char, size: i32) -> i32; + pub fn get_secure_input_process(pid: *mut i64) -> i32; + pub fn get_path_from_pid(pid: i64, buffer: *mut c_char, size: i32) -> i32; // Clipboard pub fn get_clipboard(buffer: *mut c_char, size: i32) -> i32; @@ -48,13 +48,14 @@ extern { pub fn set_clipboard_image(path: *const c_char) -> i32; // 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 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 - pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u8, - i32, i32, i32)); + pub fn register_keypress_callback( + cb: extern "C" fn(_self: *mut c_void, *const u8, i32, i32, i32), + ); pub fn send_string(string: *const c_char); pub fn send_vkey(vk: i32); @@ -62,4 +63,4 @@ extern { pub fn delete_string(count: i32); pub fn trigger_paste(); pub fn trigger_copy(); -} \ No newline at end of file +} diff --git a/src/bridge/mod.rs b/src/bridge/mod.rs index 3c6b412..3446e17 100644 --- a/src/bridge/mod.rs +++ b/src/bridge/mod.rs @@ -24,4 +24,4 @@ pub(crate) mod windows; pub(crate) mod linux; #[cfg(target_os = "macos")] -pub(crate) mod macos; \ No newline at end of file +pub(crate) mod macos; diff --git a/src/bridge/windows.rs b/src/bridge/windows.rs index 70c71a4..2f75254 100644 --- a/src/bridge/windows.rs +++ b/src/bridge/windows.rs @@ -17,7 +17,7 @@ * along with espanso. If not, see . */ -use std::os::raw::{c_void}; +use std::os::raw::c_void; #[repr(C)] pub struct WindowsMenuItem { @@ -27,10 +27,15 @@ pub struct WindowsMenuItem { } #[allow(improper_ctypes)] -#[link(name="winbridge", kind="static")] -extern { +#[link(name = "winbridge", kind = "static")] +extern "C" { pub fn start_daemon_process() -> i32; - pub fn initialize(s: *const c_void, ico_path: *const u16, bmp_path: *const u16, show_icon: i32) -> i32; + pub fn initialize( + s: *const c_void, + ico_path: *const u16, + bmp_path: *const u16, + show_icon: i32, + ) -> i32; // SYSTEM pub fn get_active_window_name(buffer: *mut u16, size: i32) -> i32; @@ -40,8 +45,8 @@ extern { pub fn show_notification(message: *const u16) -> i32; pub fn close_notification(); 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_context_menu_click_callback(cb: extern fn(_self: *mut c_void, id: i32)); + pub fn register_icon_click_callback(cb: extern "C" fn(_self: *mut c_void)); + pub fn register_context_menu_click_callback(cb: extern "C" fn(_self: *mut c_void, id: i32)); pub fn cleanup_ui(); // CLIPBOARD @@ -50,8 +55,9 @@ extern { pub fn set_clipboard_image(path: *const u16) -> i32; // KEYBOARD - pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u16, - i32, i32, i32, i32, i32)); + pub fn register_keypress_callback( + cb: extern "C" fn(_self: *mut c_void, *const u16, i32, i32, i32, i32, i32), + ); pub fn eventloop(); pub fn send_string(string: *const u16); @@ -64,4 +70,4 @@ extern { // PROCESSES pub fn start_process(cmd: *const u16) -> i32; -} \ No newline at end of file +} diff --git a/src/check.rs b/src/check.rs index daed087..414efbc 100644 --- a/src/check.rs +++ b/src/check.rs @@ -27,20 +27,18 @@ pub fn check_preconditions() -> bool { let mut result = true; // Make sure notify-send is installed - let status = Command::new("notify-send") - .arg("-v") - .output(); + let status = Command::new("notify-send").arg("-v").output(); if status.is_err() { println!("Error: 'notify-send' command is needed for espanso to work correctly, please install it."); result = false; } // Make sure xclip is installed - let status = Command::new("xclip") - .arg("-version") - .output(); + let status = Command::new("xclip").arg("-version").output(); 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; } @@ -70,4 +68,4 @@ pub fn check_preconditions() -> bool { pub fn check_preconditions() -> bool { // Nothing needed on windows true -} \ No newline at end of file +} diff --git a/src/clipboard/linux.rs b/src/clipboard/linux.rs index b3e4e4d..24dda8c 100644 --- a/src/clipboard/linux.rs +++ b/src/clipboard/linux.rs @@ -17,18 +17,16 @@ * along with espanso. If not, see . */ -use std::process::{Command, Stdio}; -use std::io::{Write}; -use log::{error}; +use log::error; +use std::io::Write; use std::path::Path; +use std::process::{Command, Stdio}; pub struct LinuxClipboardManager {} impl super::ClipboardManager for LinuxClipboardManager { - fn get_clipboard(&self) -> Option { - let res = Command::new("xclip") - .args(&["-o", "-sel", "clip"]) - .output(); + fn get_clipboard(&self) -> Option { + let res = Command::new("xclip").args(&["-o", "-sel", "clip"]).output(); if let Ok(output) = res { if output.status.success() { @@ -71,14 +69,14 @@ impl super::ClipboardManager for LinuxClipboardManager { Some(ext) => { let ext = ext.to_string_lossy().to_lowercase(); match ext.as_ref() { - "png" => {"image/png"}, - "jpg" | "jpeg" => {"image/jpeg"}, - "gif" => {"image/gif"}, - "svg" => {"image/svg"}, - _ => {"image/png"}, + "png" => "image/png", + "jpg" | "jpeg" => "image/jpeg", + "gif" => "image/gif", + "svg" => "image/svg", + _ => "image/png", } - }, - None => {"image/png"}, + } + None => "image/png", }; let image_path = image_path.to_string_lossy().into_owned(); @@ -95,6 +93,6 @@ impl super::ClipboardManager for LinuxClipboardManager { impl LinuxClipboardManager { pub fn new() -> LinuxClipboardManager { - LinuxClipboardManager{} + LinuxClipboardManager {} } -} \ No newline at end of file +} diff --git a/src/clipboard/macos.rs b/src/clipboard/macos.rs index 3777d21..8299179 100644 --- a/src/clipboard/macos.rs +++ b/src/clipboard/macos.rs @@ -17,20 +17,18 @@ * along with espanso. If not, see . */ -use std::os::raw::c_char; use crate::bridge::macos::*; -use std::ffi::{CStr, CString}; -use std::path::Path; 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 { - fn get_clipboard(&self) -> Option { + fn get_clipboard(&self) -> Option { unsafe { - let mut buffer : [c_char; 2000] = [0; 2000]; + let mut buffer: [c_char; 2000] = [0; 2000]; let res = get_clipboard(buffer.as_mut_ptr(), buffer.len() as i32); if res > 0 { @@ -71,6 +69,6 @@ impl super::ClipboardManager for MacClipboardManager { impl MacClipboardManager { pub fn new() -> MacClipboardManager { - MacClipboardManager{} + MacClipboardManager {} } -} \ No newline at end of file +} diff --git a/src/clipboard/mod.rs b/src/clipboard/mod.rs index b04d5c2..41e43de 100644 --- a/src/clipboard/mod.rs +++ b/src/clipboard/mod.rs @@ -50,4 +50,4 @@ pub fn get_manager() -> impl ClipboardManager { #[cfg(target_os = "macos")] pub fn get_manager() -> impl ClipboardManager { macos::MacClipboardManager::new() -} \ No newline at end of file +} diff --git a/src/clipboard/windows.rs b/src/clipboard/windows.rs index 63fa070..b2e2e1d 100644 --- a/src/clipboard/windows.rs +++ b/src/clipboard/windows.rs @@ -17,24 +17,22 @@ * along with espanso. If not, see . */ -use widestring::U16CString; -use crate::bridge::windows::{set_clipboard, get_clipboard, set_clipboard_image}; +use crate::bridge::windows::{get_clipboard, set_clipboard, set_clipboard_image}; use std::path::Path; +use widestring::U16CString; -pub struct WindowsClipboardManager { - -} +pub struct WindowsClipboardManager {} impl WindowsClipboardManager { pub fn new() -> WindowsClipboardManager { - WindowsClipboardManager{} + WindowsClipboardManager {} } } impl super::ClipboardManager for WindowsClipboardManager { - fn get_clipboard(&self) -> Option { + fn get_clipboard(&self) -> Option { unsafe { - let mut buffer : [u16; 2000] = [0; 2000]; + let mut buffer: [u16; 2000] = [0; 2000]; let res = get_clipboard(buffer.as_mut_ptr(), buffer.len() as i32); if res > 0 { @@ -62,4 +60,4 @@ impl super::ClipboardManager for WindowsClipboardManager { set_clipboard_image(payload_c.as_ptr()); } } -} \ No newline at end of file +} diff --git a/src/config/mod.rs b/src/config/mod.rs index eaa94a9..a2e6d7e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -19,63 +19,133 @@ extern crate dirs; -use std::path::{Path, PathBuf}; -use std::{fs}; -use crate::matcher::{Match, MatchVariable}; -use std::fs::{File, create_dir_all}; -use std::io::Read; -use serde::{Serialize, Deserialize}; use crate::event::KeyModifier; use crate::keyboard::PasteShortcut; -use std::collections::{HashSet, HashMap}; -use log::{error}; -use std::fmt; +use crate::matcher::{Match, MatchVariable}; +use log::error; +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, HashSet}; use std::error::Error; +use std::fmt; +use std::fs; +use std::fs::{create_dir_all, File}; +use std::io::Read; +use std::path::{Path, PathBuf}; use walkdir::WalkDir; pub(crate) mod runtime; -const DEFAULT_CONFIG_FILE_CONTENT : &str = include_str!("../res/config.yml"); +const DEFAULT_CONFIG_FILE_CONTENT: &str = include_str!("../res/config.yml"); -pub const DEFAULT_CONFIG_FILE_NAME : &str = "default.yml"; +pub const DEFAULT_CONFIG_FILE_NAME: &str = "default.yml"; pub const USER_CONFIGS_FOLDER_NAME: &str = "user"; // Default values for primitives -fn default_name() -> String{ "default".to_owned() } -fn default_parent() -> String{ "self".to_owned() } -fn default_filter_title() -> String{ "".to_owned() } -fn default_filter_class() -> String{ "".to_owned() } -fn default_filter_exec() -> String{ "".to_owned() } -fn default_log_level() -> i32 { 0 } -fn default_conflict_check() -> bool{ false } -fn default_ipc_server_port() -> i32 { 34982 } -fn default_worker_ipc_server_port() -> i32 { 34983 } -fn default_use_system_agent() -> bool { true } -fn default_config_caching_interval() -> i32 { 800 } -fn default_word_separators() -> Vec { vec![' ', ',', '.', '?', '!', '\r', '\n', 22u8 as char] } -fn default_toggle_interval() -> u32 { 230 } -fn default_toggle_key() -> KeyModifier { KeyModifier::ALT } -fn default_preserve_clipboard() -> bool {true} -fn default_passive_match_regex() -> String{ "(?P:\\p{L}+)(/(?P.*)/)?".to_owned() } -fn default_passive_arg_delimiter() -> char { '/' } -fn default_passive_arg_escape() -> char { '\\' } -fn default_passive_key() -> KeyModifier { KeyModifier::OFF } -fn default_enable_passive() -> bool { false } -fn default_enable_active() -> bool { true } -fn default_backspace_limit() -> i32 { 3 } -fn default_backspace_delay() -> i32 { 0 } -fn default_inject_delay() -> i32 { 0 } -fn default_restore_clipboard_delay() -> i32 { 300 } -fn default_exclude_default_entries() -> bool {false} -fn default_secure_input_watcher_enabled() -> bool {true} -fn default_secure_input_notification() -> bool {true} -fn default_show_notifications() -> bool {true} -fn default_auto_restart() -> bool {false} -fn default_show_icon() -> bool {true} -fn default_fast_inject() -> bool {true} -fn default_secure_input_watcher_interval() -> i32 {5000} -fn default_matches() -> Vec { Vec::new() } -fn default_global_vars() -> Vec { Vec::new() } +fn default_name() -> String { + "default".to_owned() +} +fn default_parent() -> String { + "self".to_owned() +} +fn default_filter_title() -> String { + "".to_owned() +} +fn default_filter_class() -> String { + "".to_owned() +} +fn default_filter_exec() -> String { + "".to_owned() +} +fn default_log_level() -> i32 { + 0 +} +fn default_conflict_check() -> bool { + false +} +fn default_ipc_server_port() -> i32 { + 34982 +} +fn default_worker_ipc_server_port() -> i32 { + 34983 +} +fn default_use_system_agent() -> bool { + true +} +fn default_config_caching_interval() -> i32 { + 800 +} +fn default_word_separators() -> Vec { + vec![' ', ',', '.', '?', '!', '\r', '\n', 22u8 as char] +} +fn default_toggle_interval() -> u32 { + 230 +} +fn default_toggle_key() -> KeyModifier { + KeyModifier::ALT +} +fn default_preserve_clipboard() -> bool { + true +} +fn default_passive_match_regex() -> String { + "(?P:\\p{L}+)(/(?P.*)/)?".to_owned() +} +fn default_passive_arg_delimiter() -> char { + '/' +} +fn default_passive_arg_escape() -> char { + '\\' +} +fn default_passive_key() -> KeyModifier { + KeyModifier::OFF +} +fn default_enable_passive() -> bool { + false +} +fn default_enable_active() -> bool { + true +} +fn default_backspace_limit() -> i32 { + 3 +} +fn default_backspace_delay() -> i32 { + 0 +} +fn default_inject_delay() -> i32 { + 0 +} +fn default_restore_clipboard_delay() -> i32 { + 300 +} +fn default_exclude_default_entries() -> bool { + false +} +fn default_secure_input_watcher_enabled() -> bool { + true +} +fn default_secure_input_notification() -> bool { + true +} +fn default_show_notifications() -> bool { + true +} +fn default_auto_restart() -> bool { + false +} +fn default_show_icon() -> bool { + true +} +fn default_fast_inject() -> bool { + true +} +fn default_secure_input_watcher_interval() -> i32 { + 5000 +} +fn default_matches() -> Vec { + Vec::new() +} +fn default_global_vars() -> Vec { + Vec::new() +} #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Configs { @@ -113,7 +183,7 @@ pub struct Configs { pub config_caching_interval: i32, #[serde(default = "default_word_separators")] - pub word_separators: Vec, // TODO: add parsing test + pub word_separators: Vec, // TODO: add parsing test #[serde(default = "default_toggle_key")] pub toggle_key: KeyModifier, @@ -188,8 +258,7 @@ pub struct Configs { pub matches: Vec, #[serde(default = "default_global_vars")] - pub global_vars: Vec - + pub global_vars: Vec, } // Macro used to validate config fields @@ -216,7 +285,11 @@ impl Configs { fn validate_user_defined_config(&self) -> bool { let mut result = true; - validate_field!(result, self.config_caching_interval, default_config_caching_interval()); + validate_field!( + result, + self.config_caching_interval, + default_config_caching_interval() + ); validate_field!(result, self.log_level, default_log_level()); validate_field!(result, self.conflict_check, default_conflict_check()); validate_field!(result, self.toggle_key, default_toggle_key()); @@ -224,16 +297,52 @@ impl Configs { validate_field!(result, self.backspace_limit, default_backspace_limit()); validate_field!(result, self.ipc_server_port, default_ipc_server_port()); validate_field!(result, self.use_system_agent, default_use_system_agent()); - validate_field!(result, self.preserve_clipboard, default_preserve_clipboard()); - validate_field!(result, self.passive_match_regex, default_passive_match_regex()); - validate_field!(result, self.passive_arg_delimiter, default_passive_arg_delimiter()); - validate_field!(result, self.passive_arg_escape, default_passive_arg_escape()); + validate_field!( + result, + self.preserve_clipboard, + default_preserve_clipboard() + ); + validate_field!( + result, + self.passive_match_regex, + default_passive_match_regex() + ); + validate_field!( + result, + self.passive_arg_delimiter, + default_passive_arg_delimiter() + ); + validate_field!( + result, + self.passive_arg_escape, + default_passive_arg_escape() + ); validate_field!(result, self.passive_key, default_passive_key()); - validate_field!(result, self.restore_clipboard_delay, default_restore_clipboard_delay()); - validate_field!(result, self.secure_input_watcher_enabled, default_secure_input_watcher_enabled()); - validate_field!(result, self.secure_input_watcher_interval, default_secure_input_watcher_interval()); - validate_field!(result, self.secure_input_notification, default_secure_input_notification()); - validate_field!(result, self.show_notifications, default_show_notifications()); + validate_field!( + result, + self.restore_clipboard_delay, + default_restore_clipboard_delay() + ); + validate_field!( + result, + self.secure_input_watcher_enabled, + default_secure_input_watcher_enabled() + ); + validate_field!( + result, + self.secure_input_watcher_interval, + default_secure_input_watcher_interval() + ); + validate_field!( + result, + self.secure_input_notification, + default_secure_input_notification() + ); + validate_field!( + result, + self.show_notifications, + default_show_notifications() + ); validate_field!(result, self.show_icon, default_show_icon()); result @@ -256,7 +365,7 @@ pub enum BackendType { // when an injection is possible (only ascii characters in the replacement), and falling // back to the Clipboard backend otherwise. // Should only be used on Linux systems. - Auto + Auto, } impl Default for BackendType { // The default backend varies based on the operating system. @@ -285,18 +394,16 @@ impl Configs { let res = file.read_to_string(&mut contents); if res.is_err() { - return Err(ConfigLoadError::UnableToReadFile) + return Err(ConfigLoadError::UnableToReadFile); } let config_res = serde_yaml::from_str(&contents); match config_res { Ok(config) => Ok(config), - Err(e) => { - Err(ConfigLoadError::InvalidYAML(path.to_owned(), e.to_string())) - } + Err(e) => Err(ConfigLoadError::InvalidYAML(path.to_owned(), e.to_string())), } - }else{ + } else { eprintln!("Error: Cannot load file {:?}", path); Err(ConfigLoadError::FileNotFound) } @@ -309,9 +416,16 @@ impl Configs { merged_matches.iter().for_each(|m| { match_trigger_set.extend(m.triggers.clone()); }); - let parent_matches : Vec = self.matches.iter().filter(|&m| { - !m.triggers.iter().any(|trigger| match_trigger_set.contains(trigger)) - }).cloned().collect(); + let parent_matches: Vec = self + .matches + .iter() + .filter(|&m| { + !m.triggers + .iter() + .any(|trigger| match_trigger_set.contains(trigger)) + }) + .cloned() + .collect(); merged_matches.extend(parent_matches); self.matches = merged_matches; @@ -322,9 +436,12 @@ impl Configs { merged_global_vars.iter().for_each(|m| { vars_name_set.insert(m.name.clone()); }); - let parent_vars : Vec = self.global_vars.iter().filter(|&m| { - !vars_name_set.contains(&m.name) - }).cloned().collect(); + let parent_vars: Vec = self + .global_vars + .iter() + .filter(|&m| !vars_name_set.contains(&m.name)) + .cloned() + .collect(); merged_global_vars.extend(parent_vars); self.global_vars = merged_global_vars; @@ -336,9 +453,16 @@ impl Configs { self.matches.iter().for_each(|m| { match_trigger_set.extend(m.triggers.clone()); }); - let default_matches : Vec = default.matches.iter().filter(|&m| { - !m.triggers.iter().any(|trigger| match_trigger_set.contains(trigger)) - }).cloned().collect(); + let default_matches: Vec = default + .matches + .iter() + .filter(|&m| { + !m.triggers + .iter() + .any(|trigger| match_trigger_set.contains(trigger)) + }) + .cloned() + .collect(); self.matches.extend(default_matches); @@ -347,12 +471,14 @@ impl Configs { self.global_vars.iter().for_each(|m| { vars_name_set.insert(m.name.clone()); }); - let default_vars : Vec = default.global_vars.iter().filter(|&m| { - !vars_name_set.contains(&m.name) - }).cloned().collect(); + let default_vars: Vec = default + .global_vars + .iter() + .filter(|&m| !vars_name_set.contains(&m.name)) + .cloned() + .collect(); self.global_vars.extend(default_vars); - } } @@ -365,7 +491,7 @@ pub struct ConfigSet { impl ConfigSet { pub fn load(config_dir: &Path, package_dir: &Path) -> Result { if !config_dir.is_dir() { - return Err(ConfigLoadError::InvalidConfigDirectory) + return Err(ConfigLoadError::InvalidConfigDirectory); } // Load default configuration @@ -404,12 +530,24 @@ impl ConfigSet { let path = entry.path(); // Skip non-yaml config files - if path.extension().unwrap_or_default().to_str().unwrap_or_default() != "yml" { + if path + .extension() + .unwrap_or_default() + .to_str() + .unwrap_or_default() + != "yml" + { continue; } // Skip hidden files - if path.file_name().unwrap_or_default().to_str().unwrap_or_default().starts_with(".") { + if path + .file_name() + .unwrap_or_default() + .to_str() + .unwrap_or_default() + .starts_with(".") + { continue; } @@ -417,7 +555,7 @@ impl ConfigSet { // Make sure the config does not contain reserved fields if !config.validate_user_defined_config() { - return Err(ConfigLoadError::InvalidParameter(path.to_owned())) + return Err(ConfigLoadError::InvalidParameter(path.to_owned())); } // No name specified, defaulting to the path name @@ -431,14 +569,19 @@ impl ConfigSet { name_set.insert(config.name.clone()); - if config.parent == "self" { // No parent, root config + if config.parent == "self" { + // No parent, root config root_configs.push(config); - }else{ // Children config + } else { + // Children config let children_vec = children_map.entry(config.parent.clone()).or_default(); children_vec.push(config); } - }else{ - eprintln!("Warning: Unable to read config file: {}", entry.unwrap_err()) + } else { + eprintln!( + "Warning: Unable to read config file: {}", + entry.unwrap_err() + ) } } @@ -450,7 +593,7 @@ impl ConfigSet { } // Separate default from specific - let default= configs.get(0).unwrap().clone(); + let default = configs.get(0).unwrap().clone(); let mut specific = (&configs[1..]).to_vec().clone(); // Add default entries to specific configs when needed @@ -466,14 +609,13 @@ impl ConfigSet { let has_conflicts = Self::has_conflicts(&default, &specific); if has_conflicts { eprintln!("Warning: some triggers had conflicts and may not behave as intended"); - eprintln!("To turn off this check, add \"conflict_check: false\" in the configuration"); + eprintln!( + "To turn off this check, add \"conflict_check: false\" in the configuration" + ); } } - Ok(ConfigSet { - default, - specific - }) + Ok(ConfigSet { default, specific }) } fn reduce_configs(target: Configs, children_map: &HashMap>) -> Configs { @@ -484,7 +626,7 @@ impl ConfigSet { target.merge_config(children); } target - }else{ + } else { target } } @@ -500,7 +642,7 @@ impl ConfigSet { if !default_file.exists() { let result = fs::write(&default_file, DEFAULT_CONFIG_FILE_CONTENT); if result.is_err() { - return Err(ConfigLoadError::UnableToCreateDefaultConfig) + return Err(ConfigLoadError::UnableToCreateDefaultConfig); } } @@ -510,34 +652,34 @@ impl ConfigSet { if !user_config_dir.exists() { let res = create_dir_all(user_config_dir.as_path()); if res.is_err() { - return Err(ConfigLoadError::UnableToCreateDefaultConfig) + return Err(ConfigLoadError::UnableToCreateDefaultConfig); } } - // Packages let package_dir = crate::context::get_package_dir(); let res = create_dir_all(package_dir.as_path()); if res.is_err() { - return Err(ConfigLoadError::UnableToCreateDefaultConfig) // TODO: change error type + return Err(ConfigLoadError::UnableToCreateDefaultConfig); // TODO: change error type } return ConfigSet::load(config_dir.as_path(), package_dir.as_path()); } fn has_conflicts(default: &Configs, specific: &Vec) -> bool { - let mut sorted_triggers : Vec = default.matches.iter().flat_map(|t| { - t.triggers.clone() - }).collect(); + let mut sorted_triggers: Vec = default + .matches + .iter() + .flat_map(|t| t.triggers.clone()) + .collect(); sorted_triggers.sort(); let mut has_conflicts = Self::list_has_conflicts(&sorted_triggers); for s in specific.iter() { - let mut specific_triggers : Vec = s.matches.iter().flat_map(|t| { - t.triggers.clone() - }).collect(); + let mut specific_triggers: Vec = + s.matches.iter().flat_map(|t| t.triggers.clone()).collect(); specific_triggers.sort(); has_conflicts |= Self::list_has_conflicts(&specific_triggers); } @@ -547,7 +689,7 @@ impl ConfigSet { fn list_has_conflicts(sorted_list: &Vec) -> bool { if sorted_list.len() <= 1 { - return false + return false; } let mut has_conflicts = false; @@ -556,7 +698,10 @@ impl ConfigSet { let previous = &sorted_list[i]; if item.starts_with(previous) { has_conflicts = true; - eprintln!("Warning: trigger '{}' is conflicting with '{}' and may not behave as intended", item, previous); + eprintln!( + "Warning: trigger '{}' is conflicting with '{}' and may not behave as intended", + item, previous + ); } } @@ -610,17 +755,16 @@ impl Error for ConfigLoadError { } } - - #[cfg(test)] mod tests { use super::*; + use crate::matcher::MatchContentType; use std::io::Write; use tempfile::{NamedTempFile, TempDir}; - use crate::matcher::{MatchContentType}; - const TEST_WORKING_CONFIG_FILE : &str = include_str!("../res/test/working_config.yml"); - const TEST_CONFIG_FILE_WITH_BAD_YAML : &str = include_str!("../res/test/config_with_bad_yaml.yml"); + const TEST_WORKING_CONFIG_FILE: &str = include_str!("../res/test/working_config.yml"); + const TEST_CONFIG_FILE_WITH_BAD_YAML: &str = + include_str!("../res/test/config_with_bad_yaml.yml"); // Test Configs @@ -646,16 +790,17 @@ mod tests { let broken_config_file = create_tmp_file(TEST_CONFIG_FILE_WITH_BAD_YAML); let config = Configs::load_config(broken_config_file.path()); match config { - Ok(_) => {assert!(false)}, + Ok(_) => assert!(false), Err(e) => { match e { - ConfigLoadError::InvalidYAML(p, _) => assert_eq!(p, broken_config_file.path().to_owned()), + ConfigLoadError::InvalidYAML(p, _) => { + assert_eq!(p, broken_config_file.path().to_owned()) + } _ => assert!(false), } assert!(true); - }, + } } - } #[test] @@ -674,59 +819,69 @@ mod tests { #[test] fn test_user_defined_config_does_not_have_reserved_fields() { - let working_config_file = create_tmp_file(r###" + let working_config_file = create_tmp_file( + r###" backend: Clipboard - "###); + "###, + ); let config = Configs::load_config(working_config_file.path()); assert_eq!(config.unwrap().validate_user_defined_config(), true); } #[test] fn test_user_defined_config_has_reserved_fields_config_caching_interval() { - let working_config_file = create_tmp_file(r###" + let working_config_file = create_tmp_file( + r###" # This should not happen in an app-specific config config_caching_interval: 100 - "###); + "###, + ); let config = Configs::load_config(working_config_file.path()); assert_eq!(config.unwrap().validate_user_defined_config(), false); } #[test] fn test_user_defined_config_has_reserved_fields_toggle_key() { - let working_config_file = create_tmp_file(r###" + let working_config_file = create_tmp_file( + r###" # This should not happen in an app-specific config toggle_key: CTRL - "###); + "###, + ); let config = Configs::load_config(working_config_file.path()); assert_eq!(config.unwrap().validate_user_defined_config(), false); } #[test] fn test_user_defined_config_has_reserved_fields_toggle_interval() { - let working_config_file = create_tmp_file(r###" + let working_config_file = create_tmp_file( + r###" # This should not happen in an app-specific config toggle_interval: 1000 - "###); + "###, + ); let config = Configs::load_config(working_config_file.path()); assert_eq!(config.unwrap().validate_user_defined_config(), false); } #[test] fn test_user_defined_config_has_reserved_fields_backspace_limit() { - let working_config_file = create_tmp_file(r###" + let working_config_file = create_tmp_file( + r###" # This should not happen in an app-specific config backspace_limit: 10 - "###); + "###, + ); let config = Configs::load_config(working_config_file.path()); assert_eq!(config.unwrap().validate_user_defined_config(), false); } @@ -744,7 +899,9 @@ mod tests { create_temp_espanso_directories_with_default_content(DEFAULT_CONFIG_FILE_CONTENT) } - pub fn create_temp_espanso_directories_with_default_content(default_content: &str) -> (TempDir, TempDir) { + pub fn create_temp_espanso_directories_with_default_content( + default_content: &str, + ) -> (TempDir, TempDir) { let data_dir = TempDir::new().expect("unable to create data directory"); let package_dir = TempDir::new().expect("unable to create package directory"); @@ -771,7 +928,12 @@ mod tests { create_temp_file_in_dir(&user_config_dir, name, content) } - pub fn create_package_file(package_data_dir: &Path, package_name: &str, filename: &str, content: &str) -> PathBuf { + pub fn create_package_file( + package_data_dir: &Path, + package_name: &str, + filename: &str, + content: &str, + ) -> PathBuf { let package_dir = package_data_dir.join(package_name); if !package_dir.exists() { create_dir_all(&package_dir).unwrap(); @@ -792,7 +954,10 @@ mod tests { fn test_config_set_load_fail_bad_directory() { let config_set = ConfigSet::load(Path::new("invalid/path"), Path::new("invalid/path")); assert_eq!(config_set.is_err(), true); - assert_eq!(config_set.unwrap_err(), ConfigLoadError::InvalidConfigDirectory); + assert_eq!( + config_set.unwrap_err(), + ConfigLoadError::InvalidConfigDirectory + ); } #[test] @@ -807,21 +972,20 @@ mod tests { #[test] fn test_config_set_invalid_yaml_syntax() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( - TEST_CONFIG_FILE_WITH_BAD_YAML - ); + let (data_dir, package_dir) = + create_temp_espanso_directories_with_default_content(TEST_CONFIG_FILE_WITH_BAD_YAML); let default_path = data_dir.path().join(DEFAULT_CONFIG_FILE_NAME); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); match config_set { - Ok(_) => {assert!(false)}, + Ok(_) => assert!(false), Err(e) => { match e { ConfigLoadError::InvalidYAML(p, _) => assert_eq!(p, default_path), _ => assert!(false), } assert!(true); - }, + } } } @@ -829,117 +993,179 @@ mod tests { fn test_config_set_specific_file_with_reserved_fields() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###" + let user_defined_path = create_user_config_file( + data_dir.path(), + "specific.yml", + r###" config_caching_interval: 10000 - "###); + "###, + ); let user_defined_path_copy = user_defined_path.clone(); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); assert!(config_set.is_err()); - assert_eq!(config_set.unwrap_err(), ConfigLoadError::InvalidParameter(user_defined_path_copy)) + assert_eq!( + config_set.unwrap_err(), + ConfigLoadError::InvalidParameter(user_defined_path_copy) + ) } #[test] fn test_config_set_specific_file_missing_name_auto_generated() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###" + let user_defined_path = create_user_config_file( + data_dir.path(), + "specific.yml", + r###" backend: Clipboard - "###); + "###, + ); let user_defined_path_copy = user_defined_path.clone(); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); assert!(config_set.is_ok()); - assert_eq!(config_set.unwrap().specific[0].name, user_defined_path_copy.to_str().unwrap_or_default()) + assert_eq!( + config_set.unwrap().specific[0].name, + user_defined_path_copy.to_str().unwrap_or_default() + ) } #[test] fn test_config_set_specific_file_duplicate_name() { let (data_dir, package_dir) = create_temp_espanso_directories(); - create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file( + data_dir.path(), + "specific.yml", + r###" name: specific1 - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific2.yml", r###" + create_user_config_file( + data_dir.path(), + "specific2.yml", + r###" name: specific1 - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); assert!(config_set.is_err()); - assert!(variant_eq(&config_set.unwrap_err(), &ConfigLoadError::NameDuplicate(PathBuf::new()))) + assert!(variant_eq( + &config_set.unwrap_err(), + &ConfigLoadError::NameDuplicate(PathBuf::new()) + )) } #[test] fn test_user_defined_config_set_merge_with_parent_matches() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" matches: - trigger: ":lol" replace: "LOL" - trigger: ":yess" replace: "Bob" - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific1.yml", r###" + create_user_config_file( + data_dir.path(), + "specific1.yml", + r###" name: specific1 matches: - trigger: "hello" replace: "newstring" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.default.matches.len(), 2); assert_eq!(config_set.specific[0].matches.len(), 3); - assert!(config_set.specific[0].matches.iter().find(|x| x.triggers[0] == "hello").is_some()); - assert!(config_set.specific[0].matches.iter().find(|x| x.triggers[0] == ":lol").is_some()); - assert!(config_set.specific[0].matches.iter().find(|x| x.triggers[0] == ":yess").is_some()); + assert!(config_set.specific[0] + .matches + .iter() + .find(|x| x.triggers[0] == "hello") + .is_some()); + assert!(config_set.specific[0] + .matches + .iter() + .find(|x| x.triggers[0] == ":lol") + .is_some()); + assert!(config_set.specific[0] + .matches + .iter() + .find(|x| x.triggers[0] == ":yess") + .is_some()); } #[test] fn test_user_defined_config_set_merge_with_parent_matches_child_priority() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" matches: - trigger: ":lol" replace: "LOL" - trigger: ":yess" replace: "Bob" - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific2.yml", r###" + create_user_config_file( + data_dir.path(), + "specific2.yml", + r###" name: specific1 matches: - trigger: ":lol" replace: "newstring" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.default.matches.len(), 2); assert_eq!(config_set.specific[0].matches.len(), 2); - assert!(config_set.specific[0].matches.iter().find(|x| { - if let MatchContentType::Text(content) = &x.content { - x.triggers[0] == ":lol" && content.replace == "newstring" - }else{ - false - } - }).is_some()); - assert!(config_set.specific[0].matches.iter().find(|x| x.triggers[0] == ":yess").is_some()); + assert!(config_set.specific[0] + .matches + .iter() + .find(|x| { + if let MatchContentType::Text(content) = &x.content { + x.triggers[0] == ":lol" && content.replace == "newstring" + } else { + false + } + }) + .is_some()); + assert!(config_set.specific[0] + .matches + .iter() + .find(|x| x.triggers[0] == ":yess") + .is_some()); } #[test] fn test_user_defined_config_set_exclude_merge_with_parent_matches() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" matches: - trigger: ":lol" replace: "LOL" - trigger: ":yess" replace: "Bob" - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific2.yml", r###" + create_user_config_file( + data_dir.path(), + "specific2.yml", + r###" name: specific1 exclude_default_entries: true @@ -947,19 +1173,24 @@ mod tests { matches: - trigger: "hello" replace: "newstring" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.default.matches.len(), 2); assert_eq!(config_set.specific[0].matches.len(), 1); - assert!(config_set.specific[0].matches.iter().find(|x| { - if let MatchContentType::Text(content) = &x.content { - x.triggers[0] == "hello" && content.replace == "newstring" - }else{ - false - } - }).is_some()); + assert!(config_set.specific[0] + .matches + .iter() + .find(|x| { + if let MatchContentType::Text(content) = &x.content { + x.triggers[0] == "hello" && content.replace == "newstring" + } else { + false + } + }) + .is_some()); } #[test] @@ -971,10 +1202,13 @@ mod tests { replace: "LOL" - trigger: ":yess" replace: "Bob" - "### + "###, ); - create_user_config_file(data_dir.path(), "specific.zzz", r###" + create_user_config_file( + data_dir.path(), + "specific.zzz", + r###" name: specific1 exclude_default_entries: true @@ -982,7 +1216,8 @@ mod tests { matches: - trigger: "hello" replace: "newstring" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.specific.len(), 0); @@ -997,10 +1232,13 @@ mod tests { replace: "LOL" - trigger: ":yess" replace: "Bob" - "### + "###, ); - create_user_config_file(data_dir.path(), ".specific.yml", r###" + create_user_config_file( + data_dir.path(), + ".specific.yml", + r###" name: specific1 exclude_default_entries: true @@ -1008,7 +1246,8 @@ mod tests { matches: - trigger: "hello" replace: "newstring" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.specific.len(), 0); @@ -1018,13 +1257,21 @@ mod tests { fn test_config_set_no_parent_configs_works_correctly() { let (data_dir, package_dir) = create_temp_espanso_directories(); - create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file( + data_dir.path(), + "specific.yml", + r###" name: specific1 - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific2.yml", r###" + create_user_config_file( + data_dir.path(), + "specific2.yml", + r###" name: specific2 - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.specific.len(), 2); @@ -1032,97 +1279,156 @@ mod tests { #[test] fn test_config_set_default_parent_works_correctly() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" matches: - trigger: hasta replace: Hasta la vista - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file( + data_dir.path(), + "specific.yml", + r###" parent: default matches: - trigger: "hello" replace: "world" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.specific.len(), 0); assert_eq!(config_set.default.matches.len(), 2); - assert!(config_set.default.matches.iter().any(|m| m.triggers[0] == "hasta")); - assert!(config_set.default.matches.iter().any(|m| m.triggers[0] == "hello")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.triggers[0] == "hasta")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.triggers[0] == "hello")); } #[test] fn test_config_set_no_parent_should_not_merge() { - let (data_dir, package_dir)= create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" matches: - trigger: hasta replace: Hasta la vista - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file( + data_dir.path(), + "specific.yml", + r###" matches: - trigger: "hello" replace: "world" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.specific.len(), 1); assert_eq!(config_set.default.matches.len(), 1); - assert!(config_set.default.matches.iter().any(|m| m.triggers[0] == "hasta")); - assert!(!config_set.default.matches.iter().any(|m| m.triggers[0] == "hello")); - assert!(config_set.specific[0].matches.iter().any(|m| m.triggers[0] == "hello")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.triggers[0] == "hasta")); + assert!(!config_set + .default + .matches + .iter() + .any(|m| m.triggers[0] == "hello")); + assert!(config_set.specific[0] + .matches + .iter() + .any(|m| m.triggers[0] == "hello")); } #[test] fn test_config_set_default_nested_parent_works_correctly() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" matches: - trigger: hasta replace: Hasta la vista - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file( + data_dir.path(), + "specific.yml", + r###" name: custom1 parent: default matches: - trigger: "hello" replace: "world" - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific2.yml", r###" + create_user_config_file( + data_dir.path(), + "specific2.yml", + r###" parent: custom1 matches: - trigger: "super" replace: "mario" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.specific.len(), 0); assert_eq!(config_set.default.matches.len(), 3); - assert!(config_set.default.matches.iter().any(|m| m.triggers[0] == "hasta")); - assert!(config_set.default.matches.iter().any(|m| m.triggers[0] == "hello")); - assert!(config_set.default.matches.iter().any(|m| m.triggers[0] == "super")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.triggers[0] == "hasta")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.triggers[0] == "hello")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.triggers[0] == "super")); } #[test] fn test_config_set_parent_merge_children_priority_should_be_higher() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" matches: - trigger: hasta replace: Hasta la vista - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file( + data_dir.path(), + "specific.yml", + r###" parent: default matches: - trigger: "hasta" replace: "world" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.specific.len(), 0); @@ -1130,7 +1436,7 @@ mod tests { assert!(config_set.default.matches.iter().any(|m| { if let MatchContentType::Text(content) = &m.content { m.triggers[0] == "hasta" && content.replace == "world" - }else{ + } else { false } })); @@ -1138,117 +1444,181 @@ mod tests { #[test] fn test_config_set_package_configs_default_merge() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" matches: - trigger: hasta replace: Hasta la vista - "###); + "###, + ); - create_package_file(package_dir.path(), "package1", "package.yml", r###" + create_package_file( + package_dir.path(), + "package1", + "package.yml", + r###" parent: default matches: - trigger: "harry" replace: "potter" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.specific.len(), 0); assert_eq!(config_set.default.matches.len(), 2); - assert!(config_set.default.matches.iter().any(|m| m.triggers[0] == "hasta")); - assert!(config_set.default.matches.iter().any(|m| m.triggers[0] == "harry")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.triggers[0] == "hasta")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.triggers[0] == "harry")); } #[test] fn test_config_set_package_configs_without_merge() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" matches: - trigger: hasta replace: Hasta la vista - "###); + "###, + ); - create_package_file(package_dir.path(), "package1", "package.yml", r###" + create_package_file( + package_dir.path(), + "package1", + "package.yml", + r###" matches: - trigger: "harry" replace: "potter" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.specific.len(), 1); assert_eq!(config_set.default.matches.len(), 1); - assert!(config_set.default.matches.iter().any(|m| m.triggers[0] == "hasta")); - assert!(config_set.specific[0].matches.iter().any(|m| m.triggers[0] == "harry")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.triggers[0] == "hasta")); + assert!(config_set.specific[0] + .matches + .iter() + .any(|m| m.triggers[0] == "harry")); } #[test] fn test_config_set_package_configs_multiple_files() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" matches: - trigger: hasta replace: Hasta la vista - "###); + "###, + ); - create_package_file(package_dir.path(), "package1", "package.yml", r###" + create_package_file( + package_dir.path(), + "package1", + "package.yml", + r###" name: package1 matches: - trigger: "harry" replace: "potter" - "###); + "###, + ); - create_package_file(package_dir.path(), "package1", "addon.yml", r###" + create_package_file( + package_dir.path(), + "package1", + "addon.yml", + r###" parent: package1 matches: - trigger: "ron" replace: "weasley" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.specific.len(), 1); assert_eq!(config_set.default.matches.len(), 1); - assert!(config_set.default.matches.iter().any(|m| m.triggers[0] == "hasta")); - assert!(config_set.specific[0].matches.iter().any(|m| m.triggers[0] == "harry")); - assert!(config_set.specific[0].matches.iter().any(|m| m.triggers[0] == "ron")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.triggers[0] == "hasta")); + assert!(config_set.specific[0] + .matches + .iter() + .any(|m| m.triggers[0] == "harry")); + assert!(config_set.specific[0] + .matches + .iter() + .any(|m| m.triggers[0] == "ron")); } #[test] fn test_list_has_conflict_no_conflict() { - assert_eq!(ConfigSet::list_has_conflicts(&vec!(":ab".to_owned(), ":bc".to_owned())), false); + assert_eq!( + ConfigSet::list_has_conflicts(&vec!(":ab".to_owned(), ":bc".to_owned())), + false + ); } #[test] fn test_list_has_conflict_conflict() { - let mut list = vec!("ac".to_owned(), "ab".to_owned(), "abc".to_owned()); + let mut list = vec!["ac".to_owned(), "ab".to_owned(), "abc".to_owned()]; list.sort(); assert_eq!(ConfigSet::list_has_conflicts(&list), true); } #[test] fn test_has_conflict_no_conflict() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" matches: - trigger: ac replace: Hasta la vista - trigger: bc replace: Jon - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file( + data_dir.path(), + "specific.yml", + r###" name: specific1 matches: - trigger: "hello" replace: "world" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); - assert_eq!(ConfigSet::has_conflicts(&config_set.default, &config_set.specific), false); + assert_eq!( + ConfigSet::has_conflicts(&config_set.default, &config_set.specific), + false + ); } #[test] fn test_has_conflict_conflict_in_default() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" matches: - trigger: ac replace: Hasta la vista @@ -1256,134 +1626,195 @@ mod tests { replace: Jon - trigger: acb replace: Error - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file( + data_dir.path(), + "specific.yml", + r###" name: specific1 matches: - trigger: "hello" replace: "world" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); - assert_eq!(ConfigSet::has_conflicts(&config_set.default, &config_set.specific), true); + assert_eq!( + ConfigSet::has_conflicts(&config_set.default, &config_set.specific), + true + ); } #[test] fn test_has_conflict_conflict_in_specific_and_default() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" matches: - trigger: ac replace: Hasta la vista - trigger: bc replace: Jon - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file( + data_dir.path(), + "specific.yml", + r###" name: specific1 matches: - trigger: "bcd" replace: "Conflict" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); - assert_eq!(ConfigSet::has_conflicts(&config_set.default, &config_set.specific), true); + assert_eq!( + ConfigSet::has_conflicts(&config_set.default, &config_set.specific), + true + ); } #[test] fn test_has_conflict_no_conflict_in_specific_and_specific() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" matches: - trigger: ac replace: Hasta la vista - trigger: bc replace: Jon - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file( + data_dir.path(), + "specific.yml", + r###" name: specific1 matches: - trigger: "bad" replace: "Conflict" - "###); - create_user_config_file(data_dir.path(), "specific2.yml", r###" + "###, + ); + create_user_config_file( + data_dir.path(), + "specific2.yml", + r###" name: specific2 matches: - trigger: "badass" replace: "Conflict" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); - assert_eq!(ConfigSet::has_conflicts(&config_set.default, &config_set.specific), false); + assert_eq!( + ConfigSet::has_conflicts(&config_set.default, &config_set.specific), + false + ); } #[test] fn test_config_set_specific_inherits_default_global_vars() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" global_vars: - name: testvar type: date params: format: "%m" - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file( + data_dir.path(), + "specific.yml", + r###" global_vars: - name: specificvar type: date params: format: "%m" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.specific.len(), 1); assert_eq!(config_set.default.global_vars.len(), 1); assert_eq!(config_set.specific[0].global_vars.len(), 2); - assert!(config_set.specific[0].global_vars.iter().any(|m| m.name == "testvar")); - assert!(config_set.specific[0].global_vars.iter().any(|m| m.name == "specificvar")); + assert!(config_set.specific[0] + .global_vars + .iter() + .any(|m| m.name == "testvar")); + assert!(config_set.specific[0] + .global_vars + .iter() + .any(|m| m.name == "specificvar")); } #[test] fn test_config_set_default_get_variables_from_specific() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" global_vars: - name: testvar type: date params: format: "%m" - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file( + data_dir.path(), + "specific.yml", + r###" parent: default global_vars: - name: specificvar type: date params: format: "%m" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.specific.len(), 0); assert_eq!(config_set.default.global_vars.len(), 2); - assert!(config_set.default.global_vars.iter().any(|m| m.name == "testvar")); - assert!(config_set.default.global_vars.iter().any(|m| m.name == "specificvar")); + assert!(config_set + .default + .global_vars + .iter() + .any(|m| m.name == "testvar")); + assert!(config_set + .default + .global_vars + .iter() + .any(|m| m.name == "specificvar")); } #[test] fn test_config_set_specific_dont_inherits_default_global_vars_when_exclude_is_on() { - let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###" + let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( + r###" global_vars: - name: testvar type: date params: format: "%m" - "###); + "###, + ); - create_user_config_file(data_dir.path(), "specific.yml", r###" + create_user_config_file( + data_dir.path(), + "specific.yml", + r###" exclude_default_entries: true global_vars: @@ -1391,12 +1822,16 @@ mod tests { type: date params: format: "%m" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap(); assert_eq!(config_set.specific.len(), 1); assert_eq!(config_set.default.global_vars.len(), 1); assert_eq!(config_set.specific[0].global_vars.len(), 1); - assert!(config_set.specific[0].global_vars.iter().any(|m| m.name == "specificvar")); + assert!(config_set.specific[0] + .global_vars + .iter() + .any(|m| m.name == "specificvar")); } -} \ No newline at end of file +} diff --git a/src/config/runtime.rs b/src/config/runtime.rs index 6a07fac..577897c 100644 --- a/src/config/runtime.rs +++ b/src/config/runtime.rs @@ -17,13 +17,13 @@ * along with espanso. If not, see . */ -use regex::Regex; +use super::{ConfigSet, Configs}; +use crate::matcher::Match; use crate::system::SystemManager; +use log::{debug, warn}; +use regex::Regex; use std::cell::RefCell; use std::time::SystemTime; -use log::{debug, warn}; -use super::{Configs, ConfigSet}; -use crate::matcher::Match; pub struct RuntimeConfigManager<'a, S: SystemManager> { set: ConfigSet, @@ -37,10 +37,10 @@ pub struct RuntimeConfigManager<'a, S: SystemManager> { // Cache last_config_update: RefCell, - last_config: RefCell> + last_config: RefCell>, } -impl <'a, S: SystemManager> RuntimeConfigManager<'a, S> { +impl<'a, S: SystemManager> RuntimeConfigManager<'a, S> { pub fn new<'b>(set: ConfigSet, system_manager: S) -> RuntimeConfigManager<'b, S> { // Compile all the regexps let title_regexps = set.specific.iter().map( @@ -101,7 +101,7 @@ impl <'a, S: SystemManager> RuntimeConfigManager<'a, S> { exec_regexps, system_manager, 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() { if let Some(regex) = regex { if regex.is_match(&title) { - debug!("Matched 'filter_title' for '{}' config, using custom settings.", - self.set.specific[i].name); + debug!( + "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() { if let Some(regex) = regex { if regex.is_match(&executable) { - debug!("Matched 'filter_exec' for '{}' config, using custom settings.", - self.set.specific[i].name); + debug!( + "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() { if let Some(regex) = regex { if regex.is_match(&class) { - debug!("Matched 'filter_class' for '{}' config, using custom settings.", - self.set.specific[i].name); + debug!( + "Matched 'filter_class' for '{}' config, using custom settings.", + self.set.specific[i].name + ); - return &self.set.specific[i] + return &self.set.specific[i]; } } } @@ -167,7 +173,7 @@ impl <'a, S: SystemManager> RuntimeConfigManager<'a, S> { } } -impl <'a, S: SystemManager> super::ConfigManager<'a> for RuntimeConfigManager<'a, S> { +impl<'a, S: SystemManager> super::ConfigManager<'a> for RuntimeConfigManager<'a, S> { fn active_config(&'a self) -> &'a Configs { let mut last_config_update = self.last_config_update.borrow_mut(); if let Ok(elapsed) = (*last_config_update).elapsed() { @@ -204,8 +210,8 @@ impl <'a, S: SystemManager> super::ConfigManager<'a> for RuntimeConfigManager<'a #[cfg(test)] mod tests { use super::*; - use crate::config::ConfigManager; use crate::config::tests::{create_temp_espanso_directories, create_user_config_file}; + use crate::config::ConfigManager; struct DummySystemManager { title: RefCell, @@ -225,10 +231,10 @@ mod tests { } impl DummySystemManager { pub fn new_custom(title: &str, class: &str, exec: &str) -> DummySystemManager { - DummySystemManager{ + DummySystemManager { title: RefCell::new(title.to_owned()), class: RefCell::new(class.to_owned()), - exec: RefCell::new(exec.to_owned()) + exec: RefCell::new(exec.to_owned()), } } @@ -247,21 +253,33 @@ mod tests { fn test_runtime_constructor_regex_load_correctly() { let (data_dir, package_dir) = create_temp_espanso_directories(); - create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: myname1 filter_exec: "Title" - "###); + "###, + ); - create_user_config_file(&data_dir.path(), "specific2.yml", r###" + create_user_config_file( + &data_dir.path(), + "specific2.yml", + r###" name: myname2 filter_title: "Yeah" filter_class: "Car" - "###); + "###, + ); - create_user_config_file(&data_dir.path(), "specific3.yml", r###" + create_user_config_file( + &data_dir.path(), + "specific3.yml", + r###" name: myname3 filter_title: "Nice" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); assert!(config_set.is_ok()); @@ -270,12 +288,24 @@ mod tests { let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager); - let sp1index = config_manager.set.specific - .iter().position(|x| x.name == "myname1").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(); + let sp1index = config_manager + .set + .specific + .iter() + .position(|x| x.name == "myname1") + .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.title_regexps.len(), 3); @@ -298,21 +328,33 @@ mod tests { fn test_runtime_constructor_malformed_regexes_are_ignored() { let (data_dir, package_dir) = create_temp_espanso_directories(); - create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: myname1 filter_exec: "[`-_]" - "###); + "###, + ); - create_user_config_file(&data_dir.path(), "specific2.yml", r###" + create_user_config_file( + &data_dir.path(), + "specific2.yml", + r###" name: myname2 filter_title: "[`-_]" filter_class: "Car" - "###); + "###, + ); - create_user_config_file(&data_dir.path(), "specific3.yml", r###" + create_user_config_file( + &data_dir.path(), + "specific3.yml", + r###" name: myname3 filter_title: "Nice" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); assert!(config_set.is_ok()); @@ -321,12 +363,24 @@ mod tests { let config_manager = RuntimeConfigManager::new(config_set.unwrap(), dummy_system_manager); - let sp1index = config_manager.set.specific - .iter().position(|x| x.name == "myname1").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(); + let sp1index = config_manager + .set + .specific + .iter() + .position(|x| x.name == "myname1") + .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.title_regexps.len(), 3); @@ -349,15 +403,20 @@ mod tests { fn test_runtime_calculate_active_config_specific_title_match() { let (data_dir, package_dir) = create_temp_espanso_directories(); - create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: chrome filter_title: "Chrome" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); 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); @@ -368,15 +427,20 @@ mod tests { fn test_runtime_calculate_active_config_specific_class_match() { let (data_dir, package_dir) = create_temp_espanso_directories(); - create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: chrome filter_class: "Chrome" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); 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); @@ -387,15 +451,20 @@ mod tests { fn test_runtime_calculate_active_config_specific_exec_match() { let (data_dir, package_dir) = create_temp_espanso_directories(); - create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: chrome filter_exec: "chrome.exe" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); 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); @@ -406,16 +475,21 @@ mod tests { fn test_runtime_calculate_active_config_specific_multi_filter_match() { let (data_dir, package_dir) = create_temp_espanso_directories(); - create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: chrome filter_class: Browser filter_exec: "firefox.exe" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); 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); @@ -426,15 +500,20 @@ mod tests { fn test_runtime_calculate_active_config_no_match() { let (data_dir, package_dir) = create_temp_espanso_directories(); - create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: firefox filter_title: "Firefox" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); 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); @@ -445,25 +524,32 @@ mod tests { fn test_runtime_active_config_cache() { let (data_dir, package_dir) = create_temp_espanso_directories(); - create_user_config_file(&data_dir.path(), "specific.yml", r###" + create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: firefox filter_title: "Firefox" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); 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); assert_eq!(config_manager.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 assert_eq!(config_manager.calculate_active_config().name, "firefox"); assert_eq!(config_manager.active_config().name, "default"); } -} \ No newline at end of file +} diff --git a/src/context/linux.rs b/src/context/linux.rs index 7089f78..6a4ddb1 100644 --- a/src/context/linux.rs +++ b/src/context/linux.rs @@ -17,18 +17,18 @@ * along with espanso. If not, see . */ -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 std::process::exit; +use crate::config::Configs; +use crate::event::KeyModifier::*; +use crate::event::*; use log::{debug, error}; use std::ffi::CStr; +use std::os::raw::{c_char, c_void}; +use std::process::exit; use std::sync::atomic::AtomicBool; -use std::sync::Arc; use std::sync::atomic::Ordering::Acquire; -use crate::config::Configs; +use std::sync::mpsc::Sender; +use std::sync::Arc; #[repr(C)] pub struct LinuxContext { @@ -37,11 +37,13 @@ pub struct LinuxContext { } impl LinuxContext { - pub fn new(_: Configs, send_channel: Sender, is_injecting: Arc) -> Box { + pub fn new( + _: Configs, + send_channel: Sender, + is_injecting: Arc, + ) -> Box { // Check if the X11 context is available - let x11_available = unsafe { - check_x11() - }; + let x11_available = unsafe { check_x11() }; if x11_available < 0 { error!("Error, can't connect to X11 context"); @@ -50,7 +52,7 @@ impl LinuxContext { let context = Box::new(LinuxContext { send_channel, - is_injecting + is_injecting, }); unsafe { @@ -79,14 +81,21 @@ impl super::Context for LinuxContext { impl Drop for LinuxContext { fn drop(&mut self) { - unsafe { cleanup(); } + unsafe { + cleanup(); + } } } // Native bridge code -extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, _len: i32, - event_type: i32, key_code: i32) { +extern "C" fn keypress_callback( + _self: *mut c_void, + raw_buffer: *const u8, + _len: i32, + event_type: i32, + key_code: i32, +) { unsafe { let _self = _self as *mut LinuxContext; @@ -98,7 +107,8 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, _len: i32 return; } - if event_type == 0 { // Char event + if event_type == 0 { + // Char event // Convert the received buffer to a string let c_str = CStr::from_ptr(raw_buffer as *const c_char); let char_str = c_str.to_str(); @@ -108,12 +118,13 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, _len: i32 Ok(char_str) => { let event = Event::Key(KeyEvent::Char(char_str.to_owned())); (*_self).send_channel.send(event).unwrap(); - }, + } Err(e) => { - debug!("Unable to receive char: {}",e); - }, + debug!("Unable to receive char: {}", e); + } } - }else if event_type == 1 { // Modifier event + } else if event_type == 1 { + // Modifier event let modifier: Option = match key_code { 133 => Some(LEFT_META), @@ -132,13 +143,15 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, _len: i32 if let Some(modifier) = modifier { let event = Event::Key(KeyEvent::Modifier(modifier)); (*_self).send_channel.send(event).unwrap(); - }else{ // Not one of the default modifiers, send an "other" event + } else { + // Not one of the default modifiers, send an "other" event let event = Event::Key(KeyEvent::Other); (*_self).send_channel.send(event).unwrap(); } - }else{ // Other type of event + } else { + // Other type of event let event = Event::Key(KeyEvent::Other); (*_self).send_channel.send(event).unwrap(); } } -} \ No newline at end of file +} diff --git a/src/context/macos.rs b/src/context/macos.rs index 48641a1..a5c9b54 100644 --- a/src/context/macos.rs +++ b/src/context/macos.rs @@ -17,23 +17,23 @@ * along with espanso. If not, see . */ -use std::sync::mpsc::Sender; -use std::os::raw::{c_void, c_char}; use crate::bridge::macos::*; -use crate::event::{Event, KeyEvent, KeyModifier, ActionType, SystemEvent}; +use crate::config::Configs; use crate::event::KeyModifier::*; -use std::ffi::{CString, CStr}; -use std::{fs, thread}; -use log::{info, error, debug}; +use crate::event::{ActionType, Event, KeyEvent, KeyModifier, SystemEvent}; +use crate::system::macos::MacSystemManager; +use log::{debug, error, info}; +use std::cell::RefCell; +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_void}; use std::process::exit; use std::sync::atomic::AtomicBool; -use std::sync::Arc; use std::sync::atomic::Ordering::Acquire; -use crate::config::Configs; -use std::cell::RefCell; -use crate::system::macos::MacSystemManager; +use std::sync::mpsc::Sender; +use std::sync::Arc; +use std::{fs, thread}; -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 send_channel: Sender, @@ -43,14 +43,20 @@ pub struct MacContext { } impl MacContext { - pub fn new(config: Configs, send_channel: Sender, is_injecting: Arc) -> Box { + pub fn new( + config: Configs, + send_channel: Sender, + is_injecting: Arc, + ) -> Box { // Check accessibility unsafe { let res = prompt_accessibility(); if res == 0 { 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/"); exit(1); } @@ -69,9 +75,12 @@ impl MacContext { if status_icon_target.exists() { info!("Status icon already initialized, skipping."); - }else { + } else { 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 + ); }); } @@ -82,12 +91,9 @@ impl MacContext { register_icon_click_callback(icon_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 show_icon = if config.show_icon { - 1 - }else{ - 0 - }; + let status_icon_path = + CString::new(status_icon_target.to_str().unwrap_or_default()).unwrap_or_default(); + let show_icon = if config.show_icon { 1 } else { 0 }; initialize(context_ptr, status_icon_path.as_ptr(), show_icon); } @@ -155,8 +161,13 @@ impl super::Context for MacContext { // Native bridge code -extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, - event_type: i32, key_code: i32) { +extern "C" fn keypress_callback( + _self: *mut c_void, + raw_buffer: *const u8, + len: i32, + event_type: i32, + key_code: i32, +) { unsafe { let _self = _self as *mut MacContext; @@ -168,7 +179,8 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, return; } - if event_type == 0 { // Char event + if event_type == 0 { + // Char event // Convert the received buffer to a string let c_str = CStr::from_ptr(raw_buffer as (*const c_char)); let char_str = c_str.to_str(); @@ -178,12 +190,13 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, Ok(char_str) => { let event = Event::Key(KeyEvent::Char(char_str.to_owned())); (*_self).send_channel.send(event).unwrap(); - }, + } Err(e) => { - error!("Unable to receive char: {}",e); - }, + error!("Unable to receive char: {}", e); + } } - }else if event_type == 1 { // Modifier event + } else if event_type == 1 { + // Modifier event let modifier: Option = match key_code { 0x37 => Some(LEFT_META), 0x36 => Some(RIGHT_META), @@ -201,18 +214,20 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, if let Some(modifier) = modifier { let event = Event::Key(KeyEvent::Modifier(modifier)); (*_self).send_channel.send(event).unwrap(); - }else{ // Not one of the default modifiers, send an "other" event + } else { + // Not one of the default modifiers, send an "other" event let event = Event::Key(KeyEvent::Other); (*_self).send_channel.send(event).unwrap(); } - }else{ // Other type of event + } else { + // Other type of event let event = Event::Key(KeyEvent::Other); (*_self).send_channel.send(event).unwrap(); } } } -extern fn icon_click_callback(_self: *mut c_void) { +extern "C" fn icon_click_callback(_self: *mut c_void) { unsafe { let _self = _self as *mut MacContext; @@ -221,7 +236,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 { let _self = _self as *mut MacContext; diff --git a/src/context/mod.rs b/src/context/mod.rs index 0508cde..50805fb 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -26,13 +26,13 @@ mod linux; #[cfg(target_os = "macos")] pub(crate) mod macos; -use std::sync::mpsc::Sender; -use crate::event::Event; -use std::path::PathBuf; -use std::fs::create_dir_all; -use std::sync::{Once, Arc}; -use std::sync::atomic::AtomicBool; use crate::config::Configs; +use crate::event::Event; +use std::fs::create_dir_all; +use std::path::PathBuf; +use std::sync::atomic::AtomicBool; +use std::sync::mpsc::Sender; +use std::sync::{Arc, Once}; pub trait Context { fn eventloop(&self); @@ -40,25 +40,37 @@ pub trait Context { // MAC IMPLEMENTATION #[cfg(target_os = "macos")] -pub fn new(config: Configs, send_channel: Sender, is_injecting: Arc) -> Box { +pub fn new( + config: Configs, + send_channel: Sender, + is_injecting: Arc, +) -> Box { macos::MacContext::new(config, send_channel, is_injecting) } // LINUX IMPLEMENTATION #[cfg(target_os = "linux")] -pub fn new(config: Configs, send_channel: Sender, is_injecting: Arc) -> Box { +pub fn new( + config: Configs, + send_channel: Sender, + is_injecting: Arc, +) -> Box { linux::LinuxContext::new(config, send_channel, is_injecting) } // WINDOWS IMPLEMENTATION #[cfg(target_os = "windows")] -pub fn new(config: Configs, send_channel: Sender, is_injecting: Arc) -> Box { +pub fn new( + config: Configs, + send_channel: Sender, + is_injecting: Arc, +) -> Box { windows::WindowsContext::new(config, send_channel, is_injecting) } // espanso directories -static WARING_INIT : Once = Once::new(); +static WARING_INIT: Once = Once::new(); pub fn get_data_dir() -> PathBuf { let data_dir = dirs::data_local_dir().expect("Can't obtain data_local_dir(), terminating."); @@ -75,7 +87,10 @@ pub fn get_config_dir() -> PathBuf { if let Some(parent) = exe_dir { let config_dir = parent.join(".espanso"); 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; } } @@ -110,7 +125,7 @@ pub fn get_config_dir() -> PathBuf { espanso_dir } -const PACKAGES_FOLDER_NAME : &str = "packages"; +const PACKAGES_FOLDER_NAME: &str = "packages"; pub fn get_package_dir() -> PathBuf { // Deprecated $HOME/.espanso/packages directory compatibility check @@ -125,4 +140,4 @@ pub fn get_package_dir() -> PathBuf { let package_dir = data_dir.join(PACKAGES_FOLDER_NAME); create_dir_all(&package_dir).expect("Error creating espanso packages directory"); package_dir -} \ No newline at end of file +} diff --git a/src/context/windows.rs b/src/context/windows.rs index 9465faf..ec92b57 100644 --- a/src/context/windows.rs +++ b/src/context/windows.rs @@ -17,21 +17,21 @@ * along with espanso. If not, see . */ -use std::sync::mpsc::Sender; use crate::bridge::windows::*; -use crate::event::{Event, KeyEvent, KeyModifier, ActionType}; -use crate::event::KeyModifier::*; -use std::ffi::c_void; -use std::{fs}; -use widestring::{U16CString, U16CStr}; -use log::{info, error, debug}; -use std::sync::atomic::AtomicBool; -use std::sync::Arc; -use std::sync::atomic::Ordering::Acquire; use crate::config::Configs; +use crate::event::KeyModifier::*; +use crate::event::{ActionType, Event, KeyEvent, KeyModifier}; +use log::{debug, error, info}; +use std::ffi::c_void; +use std::fs; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering::Acquire; +use std::sync::mpsc::Sender; +use std::sync::Arc; +use widestring::{U16CStr, U16CString}; -const BMP_BINARY : &[u8] = include_bytes!("../res/win/espanso.bmp"); -const ICO_BINARY : &[u8] = include_bytes!("../res/win/espanso.ico"); +const BMP_BINARY: &[u8] = include_bytes!("../res/win/espanso.bmp"); +const ICO_BINARY: &[u8] = include_bytes!("../res/win/espanso.ico"); pub struct WindowsContext { send_channel: Sender, @@ -39,31 +39,42 @@ pub struct WindowsContext { } impl WindowsContext { - pub fn new(config: Configs, send_channel: Sender, is_injecting: Arc) -> Box { + pub fn new( + config: Configs, + send_channel: Sender, + is_injecting: Arc, + ) -> Box { // Initialize image resources 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"); if espanso_bmp_image.exists() { info!("BMP already initialized, skipping."); - }else { - fs::write(&espanso_bmp_image, BMP_BINARY) - .expect("Unable to write windows bmp file"); + } else { + fs::write(&espanso_bmp_image, BMP_BINARY).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"); if espanso_ico_image.exists() { info!("ICO already initialized, skipping."); - }else { - fs::write(&espanso_ico_image, ICO_BINARY) - .expect("Unable to write windows ico file"); + } else { + fs::write(&espanso_ico_image, ICO_BINARY).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(); @@ -71,7 +82,7 @@ impl WindowsContext { let send_channel = send_channel; - let context = Box::new(WindowsContext{ + let context = Box::new(WindowsContext { send_channel, is_injecting, }); @@ -87,14 +98,15 @@ impl WindowsContext { let ico_file_c = U16CString::from_str(ico_icon).unwrap(); let bmp_file_c = U16CString::from_str(bmp_icon).unwrap(); - let show_icon = if config.show_icon { - 1 - }else{ - 0 - }; + let show_icon = if config.show_icon { 1 } else { 0 }; // Initialize the windows - let res = initialize(context_ptr, ico_file_c.as_ptr(), bmp_file_c.as_ptr(), show_icon); + let res = initialize( + context_ptr, + ico_file_c.as_ptr(), + bmp_file_c.as_ptr(), + show_icon, + ); if res != 1 { panic!("Can't initialize Windows context") } @@ -114,8 +126,15 @@ impl super::Context for WindowsContext { // Native bridge code -extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32, - event_type: i32, key_code: i32, variant: i32, is_key_down: i32) { +extern "C" fn keypress_callback( + _self: *mut c_void, + raw_buffer: *const u16, + len: i32, + event_type: i32, + key_code: i32, + variant: i32, + is_key_down: i32, +) { unsafe { let _self = _self as *mut WindowsContext; @@ -127,8 +146,10 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32 return; } - if event_type == 0 { // Char event - if is_key_down != 0 { // KEY DOWN EVENT + if event_type == 0 { + // Char event + if is_key_down != 0 { + // KEY DOWN EVENT // Convert the received buffer to a string let buffer = std::slice::from_raw_parts(raw_buffer, len as usize); let c_string = U16CStr::from_slice_with_nul(buffer); @@ -141,17 +162,19 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32 Ok(string) => { let event = Event::Key(KeyEvent::Char(string)); (*_self).send_channel.send(event).unwrap(); - }, + } Err(e) => { error!("Unable to receive char: {}", e); - }, + } } } else { error!("unable to decode widechar"); } } - }else if event_type == 1 { // Modifier event - if is_key_down == 1 { // Keyup event + } else if event_type == 1 { + // Modifier event + if is_key_down == 1 { + // Keyup event let modifier: Option = match (key_code, variant) { (0x5B, _) => Some(LEFT_META), (0x5C, _) => Some(RIGHT_META), @@ -169,12 +192,13 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32 if let Some(modifier) = modifier { let event = Event::Key(KeyEvent::Modifier(modifier)); (*_self).send_channel.send(event).unwrap(); - }else{ // Not one of the default modifiers, send an "other" event + } else { + // Not one of the default modifiers, send an "other" event let event = Event::Key(KeyEvent::Other); (*_self).send_channel.send(event).unwrap(); } } - }else{ + } else { // Other type of event let event = Event::Key(KeyEvent::Other); (*_self).send_channel.send(event).unwrap(); @@ -182,7 +206,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 { let _self = _self as *mut WindowsContext; @@ -191,12 +215,11 @@ 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 { let _self = _self as *mut WindowsContext; let event = Event::Action(ActionType::from(id)); (*_self).send_channel.send(event).unwrap(); } -} \ No newline at end of file +} diff --git a/src/edit.rs b/src/edit.rs index 25d6377..fc1e802 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -20,11 +20,17 @@ use std::path::Path; #[cfg(target_os = "linux")] -fn default_editor() -> String{ "/bin/nano".to_owned() } +fn default_editor() -> String { + "/bin/nano".to_owned() +} #[cfg(target_os = "macos")] -fn default_editor() -> String{ "/usr/bin/nano".to_owned() } +fn default_editor() -> String { + "/usr/bin/nano".to_owned() +} #[cfg(target_os = "windows")] -fn default_editor() -> String{ "C:\\Windows\\System32\\notepad.exe".to_owned() } +fn default_editor() -> String { + "C:\\Windows\\System32\\notepad.exe".to_owned() +} pub fn open_editor(file_path: &Path) -> bool { use std::process::Command; @@ -34,20 +40,18 @@ pub fn open_editor(file_path: &Path) -> bool { let visual_var = std::env::var_os("VISUAL"); // Prioritize the editors specified by the environment variable, use the default one - let editor : String = if let Some(editor_var) = editor_var { + let editor: String = if let Some(editor_var) = editor_var { editor_var.to_string_lossy().to_string() - }else if let Some(visual_var) = visual_var { + } else if let Some(visual_var) = visual_var { visual_var.to_string_lossy().to_string() - }else{ + } else { default_editor() }; // Start the editor and wait for its termination let status = if cfg!(target_os = "windows") { - Command::new(&editor) - .arg(file_path) - .spawn() - }else{ + Command::new(&editor).arg(file_path).spawn() + } else { // On Unix, spawn the editor using the shell so that it can // accept parameters. See issue #245 Command::new("/bin/bash") @@ -56,18 +60,17 @@ pub fn open_editor(file_path: &Path) -> bool { .spawn() }; - if let Ok(mut child) = status { // Wait for the user to edit the configuration let result = child.wait(); if let Ok(exit_status) = result { exit_status.success() - }else{ + } else { false } - }else{ + } else { println!("Error: could not start editor at: {}", &editor); false } -} \ No newline at end of file +} diff --git a/src/engine.rs b/src/engine.rs index 50a41f0..f349847 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -17,25 +17,31 @@ * along with espanso. If not, see . */ -use crate::matcher::{Match, MatchReceiver}; -use crate::keyboard::KeyboardManager; -use crate::config::ConfigManager; -use crate::config::BackendType; use crate::clipboard::ClipboardManager; -use log::{info, warn, debug, error}; -use crate::ui::{UIManager, MenuItem, MenuItemType}; -use crate::event::{ActionEventReceiver, ActionType, SystemEventReceiver, SystemEvent}; -use crate::render::{Renderer, RenderResult}; +use crate::config::BackendType; +use crate::config::ConfigManager; +use crate::event::{ActionEventReceiver, ActionType, SystemEvent, SystemEventReceiver}; +use crate::keyboard::KeyboardManager; +use crate::matcher::{Match, MatchReceiver}; +use crate::protocol::{send_command_or_warn, IPCCommand, Service}; +use crate::render::{RenderResult, Renderer}; +use crate::ui::{MenuItem, MenuItemType, UIManager}; +use log::{debug, error, info, warn}; +use regex::Regex; use std::cell::RefCell; use std::process::exit; -use regex::{Regex}; -use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering::Release; -use crate::protocol::{Service, IPCCommand, send_command_or_warn}; +use std::sync::Arc; -pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, - U: UIManager, R: Renderer> { +pub struct Engine< + 'a, + S: KeyboardManager, + C: ClipboardManager, + M: ConfigManager<'a>, + U: UIManager, + R: Renderer, +> { keyboard_manager: &'a S, clipboard_manager: &'a C, config_manager: &'a M, @@ -46,14 +52,27 @@ pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager< enabled: RefCell, } -impl <'a, S: KeyboardManager, C: ClipboardManager, 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, is_injecting: Arc) -> Engine<'a, S, C, M, U, R> { +impl< + 'a, + S: KeyboardManager, + C: ClipboardManager, + 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, + is_injecting: Arc, + ) -> Engine<'a, S, C, M, U, R> { let enabled = RefCell::new(true); - Engine{keyboard_manager, + Engine { + keyboard_manager, clipboard_manager, config_manager, ui_manager, @@ -67,36 +86,32 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa let mut menu = Vec::new(); let enabled = self.enabled.borrow(); - let toggle_text = if *enabled { - "Disable" - }else{ - "Enable" - }.to_owned(); - menu.push(MenuItem{ + let toggle_text = if *enabled { "Disable" } else { "Enable" }.to_owned(); + menu.push(MenuItem { item_type: MenuItemType::Button, item_name: toggle_text, item_id: ActionType::Toggle as i32, }); - menu.push(MenuItem{ + menu.push(MenuItem { item_type: MenuItemType::Separator, item_name: "".to_owned(), item_id: 998, }); - menu.push(MenuItem{ + menu.push(MenuItem { item_type: MenuItemType::Button, item_name: "Reload configs".to_owned(), item_id: ActionType::RestartWorker as i32, }); - menu.push(MenuItem{ + menu.push(MenuItem { item_type: MenuItemType::Separator, item_name: "".to_owned(), item_id: 999, }); - menu.push(MenuItem{ + menu.push(MenuItem { item_type: MenuItemType::Button, item_name: "Exit espanso".to_owned(), item_id: ActionType::Exit as i32, @@ -110,10 +125,10 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa // clipboard content in order to restore it later. if self.config_manager.default_config().preserve_clipboard { match self.clipboard_manager.get_clipboard() { - Some(clipboard) => {Some(clipboard)}, - None => {None}, + Some(clipboard) => Some(clipboard), + None => None, } - }else { + } else { None } } @@ -123,9 +138,15 @@ lazy_static! { static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P\\w+)\\s*\\}\\}").unwrap(); } -impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager, R: Renderer> - MatchReceiver for Engine<'a, S, C, M, U, R>{ - +impl< + '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, trigger_offset: usize) { let config = self.config_manager.active_config(); @@ -138,23 +159,26 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa let char_count = if trailing_separator.is_none() { m.triggers[trigger_offset].chars().count() as i32 - }else{ + } else { m.triggers[trigger_offset].chars().count() as i32 + 1 // Count also the separator }; self.keyboard_manager.delete_string(&config, char_count); - let mut previous_clipboard_content : Option = None; + let mut previous_clipboard_content: Option = None; - let rendered = self.renderer.render_match(m, trigger_offset, config, vec![]); + let rendered = self + .renderer + .render_match(m, trigger_offset, config, vec![]); match rendered { RenderResult::Text(mut 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 trailing_separator == '\r' { // If the trailing separator is a carriage return, - target_string.push('\n'); // convert it to new line - }else{ + if trailing_separator == '\r' { + // If the trailing separator is a carriage return, + target_string.push('\n'); // convert it to new line + } else { target_string.push(trailing_separator); } } @@ -177,26 +201,28 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa // Subtract also 3, equal to the number of chars of the placeholder "$|$" let moves = (total_size - char_index - 3) as i32; Some(moves) - }else{ + } else { None }; let backend = if m.force_clipboard { &BackendType::Clipboard - }else if config.backend == BackendType::Auto { + } else if config.backend == BackendType::Auto { if cfg!(target_os = "linux") { let all_ascii = target_string.chars().all(|c| c.is_ascii()); if all_ascii { - debug!("All elements of the replacement are ascii, using Inject backend"); + debug!( + "All elements of the replacement are ascii, using Inject backend" + ); &BackendType::Inject - }else{ + } else { debug!("There are non-ascii characters, using Clipboard backend"); &BackendType::Clipboard } - }else{ + } else { &BackendType::Inject } - }else{ + } else { &config.backend }; @@ -212,15 +238,16 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa self.keyboard_manager.send_string(&config, split); } - }, + } BackendType::Clipboard => { // If the preserve_clipboard option is enabled, save the current // clipboard content to restore it later. - previous_clipboard_content = self.return_content_if_preserve_clipboard_is_enabled(); + previous_clipboard_content = + self.return_content_if_preserve_clipboard_is_enabled(); self.clipboard_manager.set_clipboard(&target_string); self.keyboard_manager.trigger_paste(&config); - }, + } _ => { error!("Unsupported backend type evaluation."); return; @@ -231,7 +258,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa // Simulate left arrow key presses to bring the cursor into the desired position self.keyboard_manager.move_cursor_left(&config, moves); } - }, + } RenderResult::Image(image_path) => { // If the preserve_clipboard option is enabled, save the current // clipboard content to restore it later. @@ -239,19 +266,22 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa self.clipboard_manager.set_clipboard_image(&image_path); self.keyboard_manager.trigger_paste(&config); - }, + } RenderResult::Error => { error!("Could not render match: {}", m.triggers[trigger_offset]); - }, + } } // Restore previous clipboard content if let Some(previous_clipboard_content) = previous_clipboard_content { // Sometimes an expansion gets overwritten before pasting by the previous content // 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); } // Re-allow espanso to interpret actions @@ -261,7 +291,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa fn on_enable_update(&self, status: bool) { let message = if status { "espanso enabled" - }else{ + } else { "espanso disabled" }; @@ -293,13 +323,13 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa let previous_clipboard = self.clipboard_manager.get_clipboard(); // Sleep for a while, giving time to effectively copy the text - std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding + std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding // Trigger a copy shortcut to transfer the content of the selection to the clipboard self.keyboard_manager.trigger_copy(&config); // Sleep for a while, giving time to effectively copy the text - std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding + std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding // Then get the text from the clipboard and render the match output let clipboard = self.clipboard_manager.get_clipboard(); @@ -308,7 +338,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa // Don't expand empty clipboards, as usually they are the result of an empty passive selection if clipboard.trim().is_empty() { info!("Avoiding passive expansion, as the user didn't select anything"); - }else{ + } else { if let Some(previous_content) = previous_clipboard { // Because of issue #213, we need to make sure the user selected something. if clipboard == previous_content { @@ -316,8 +346,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa } else { info!("Passive mode activated"); - let rendered = self.renderer.render_passive(&clipboard, - &config); + let rendered = self.renderer.render_passive(&clipboard, &config); match rendered { RenderResult::Text(payload) => { @@ -326,10 +355,8 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding self.keyboard_manager.trigger_paste(&config); - }, - _ => { - warn!("Cannot expand passive match") - }, + } + _ => warn!("Cannot expand passive match"), } } } @@ -341,34 +368,50 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa } } -impl <'a, S: KeyboardManager, C: ClipboardManager, - M: ConfigManager<'a>, U: UIManager, R: Renderer> ActionEventReceiver for Engine<'a, S, C, M, U, R>{ - +impl< + '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) { let config = self.config_manager.default_config(); match e { ActionType::IconClick => { self.ui_manager.show_menu(self.build_menu()); - }, + } ActionType::ExitWorker => { info!("terminating worker process"); self.ui_manager.cleanup(); exit(0); - }, + } ActionType::Exit => { send_command_or_warn(Service::Daemon, config.clone(), IPCCommand::exit()); - }, + } ActionType::RestartWorker => { - send_command_or_warn(Service::Daemon, config.clone(), IPCCommand::restart_worker()); - }, + send_command_or_warn( + Service::Daemon, + config.clone(), + IPCCommand::restart_worker(), + ); + } _ => {} } } } -impl <'a, S: KeyboardManager, C: ClipboardManager, - M: ConfigManager<'a>, U: UIManager, R: Renderer> SystemEventReceiver for Engine<'a, S, C, M, U, R>{ - +impl< + 'a, + S: KeyboardManager, + C: ClipboardManager, + M: ConfigManager<'a>, + U: UIManager, + R: Renderer, + > SystemEventReceiver for Engine<'a, S, C, M, U, R> +{ fn on_system_event(&self, e: SystemEvent) { match e { // MacOS specific @@ -379,16 +422,16 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, if config.secure_input_notification && config.show_notifications { self.ui_manager.notify_delay(&format!("{} has activated SecureInput. Espanso won't work until you disable it.", app_name), 5000); } - }, + } SystemEvent::SecureInputDisabled => { info!("SecureInput has been disabled."); - }, + } SystemEvent::NotifyRequest(message) => { let config = self.config_manager.default_config(); if config.show_notifications { self.ui_manager.notify(&message); } - }, + } } } -} \ No newline at end of file +} diff --git a/src/event/manager.rs b/src/event/manager.rs index db920c1..5bafec4 100644 --- a/src/event/manager.rs +++ b/src/event/manager.rs @@ -17,7 +17,7 @@ * along with espanso. If not, see . */ -use crate::event::{KeyEventReceiver, ActionEventReceiver, Event, SystemEventReceiver}; +use crate::event::{ActionEventReceiver, Event, KeyEventReceiver, SystemEventReceiver}; use std::sync::mpsc::Receiver; pub trait EventManager { @@ -32,37 +32,44 @@ pub struct DefaultEventManager<'a> { } impl<'a> DefaultEventManager<'a> { - pub fn new(receive_channel: Receiver, key_receivers: Vec<&'a dyn KeyEventReceiver>, - action_receivers: Vec<&'a dyn ActionEventReceiver>, - system_receivers: Vec<&'a dyn SystemEventReceiver>) -> DefaultEventManager<'a> { + pub fn new( + receive_channel: Receiver, + key_receivers: Vec<&'a dyn KeyEventReceiver>, + action_receivers: Vec<&'a dyn ActionEventReceiver>, + system_receivers: Vec<&'a dyn SystemEventReceiver>, + ) -> DefaultEventManager<'a> { DefaultEventManager { receive_channel, key_receivers, action_receivers, - system_receivers + system_receivers, } } } -impl <'a> EventManager for DefaultEventManager<'a> { +impl<'a> EventManager for DefaultEventManager<'a> { fn eventloop(&self) { loop { match self.receive_channel.recv() { - Ok(event) => { - match event { - Event::Key(key_event) => { - self.key_receivers.iter().for_each(move |&receiver| receiver.on_key_event(key_event.clone())); - }, - Event::Action(action_event) => { - self.action_receivers.iter().for_each(|&receiver| receiver.on_action_event(action_event.clone())); - } - Event::System(system_event) => { - self.system_receivers.iter().for_each(move |&receiver| receiver.on_system_event(system_event.clone())); - } + Ok(event) => match event { + Event::Key(key_event) => { + self.key_receivers + .iter() + .for_each(move |&receiver| receiver.on_key_event(key_event.clone())); + } + Event::Action(action_event) => { + self.action_receivers + .iter() + .for_each(|&receiver| receiver.on_action_event(action_event.clone())); + } + Event::System(system_event) => { + self.system_receivers.iter().for_each(move |&receiver| { + receiver.on_system_event(system_event.clone()) + }); } }, Err(e) => panic!("Broken event channel {}", e), } } } -} \ No newline at end of file +} diff --git a/src/event/mod.rs b/src/event/mod.rs index dcecb82..600c5b6 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -19,7 +19,7 @@ pub(crate) mod manager; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; #[allow(dead_code)] #[derive(Debug, Clone)] @@ -60,7 +60,7 @@ impl From for ActionType { pub enum KeyEvent { Char(String), Modifier(KeyModifier), - Other + Other, } #[allow(non_camel_case_types)] @@ -99,44 +99,24 @@ impl KeyModifier { match config { KeyModifier::CTRL => { current == &LEFT_CTRL || current == &RIGHT_CTRL || current == &CTRL - }, + } KeyModifier::SHIFT => { current == &LEFT_SHIFT || current == &RIGHT_SHIFT || current == &SHIFT - }, - KeyModifier::ALT => { - current == &LEFT_ALT || current == &RIGHT_ALT || current == &ALT - }, + } + KeyModifier::ALT => current == &LEFT_ALT || current == &RIGHT_ALT || current == &ALT, KeyModifier::META => { current == &LEFT_META || current == &RIGHT_META || current == &META - }, - KeyModifier::BACKSPACE => { - current == &BACKSPACE - }, - KeyModifier::LEFT_CTRL => { - current == &LEFT_CTRL - }, - KeyModifier::RIGHT_CTRL => { - current == &RIGHT_CTRL - }, - KeyModifier::LEFT_ALT => { - current == &LEFT_ALT - }, - KeyModifier::RIGHT_ALT => { - current == &RIGHT_ALT - }, - KeyModifier::LEFT_META => { - current == &LEFT_META - }, - KeyModifier::RIGHT_META => { - current == &RIGHT_META - }, - KeyModifier::LEFT_SHIFT => { - current == &LEFT_SHIFT - }, - KeyModifier::RIGHT_SHIFT => { - current == &RIGHT_SHIFT - }, - _ => {false}, + } + KeyModifier::BACKSPACE => current == &BACKSPACE, + KeyModifier::LEFT_CTRL => current == &LEFT_CTRL, + KeyModifier::RIGHT_CTRL => current == &RIGHT_CTRL, + KeyModifier::LEFT_ALT => current == &LEFT_ALT, + KeyModifier::RIGHT_ALT => current == &RIGHT_ALT, + KeyModifier::LEFT_META => current == &LEFT_META, + KeyModifier::RIGHT_META => current == &RIGHT_META, + KeyModifier::LEFT_SHIFT => current == &LEFT_SHIFT, + KeyModifier::RIGHT_SHIFT => current == &RIGHT_SHIFT, + _ => false, } } } @@ -145,11 +125,11 @@ impl KeyModifier { #[derive(Debug, Clone)] pub enum SystemEvent { // MacOS specific - SecureInputEnabled(String, String), // AppName, App Path + SecureInputEnabled(String, String), // AppName, App Path SecureInputDisabled, // Notification - NotifyRequest(String) + NotifyRequest(String), } // Receivers @@ -170,8 +150,8 @@ pub trait SystemEventReceiver { #[cfg(test)] mod tests { - use super::*; use super::KeyModifier::*; + use super::*; #[test] fn test_shallow_equals_ctrl() { @@ -225,4 +205,4 @@ mod tests { assert!(!KeyModifier::shallow_equals(&OFF, &META)); assert!(!KeyModifier::shallow_equals(&OFF, &SHIFT)); } -} \ No newline at end of file +} diff --git a/src/extension/clipboard.rs b/src/extension/clipboard.rs index c8442cd..60846ca 100644 --- a/src/extension/clipboard.rs +++ b/src/extension/clipboard.rs @@ -17,8 +17,8 @@ * along with espanso. If not, see . */ -use serde_yaml::{Mapping}; use crate::clipboard::ClipboardManager; +use serde_yaml::Mapping; pub struct ClipboardExtension { clipboard_manager: Box, @@ -26,9 +26,7 @@ pub struct ClipboardExtension { impl ClipboardExtension { pub fn new(clipboard_manager: Box) -> ClipboardExtension { - ClipboardExtension{ - clipboard_manager - } + ClipboardExtension { clipboard_manager } } } @@ -40,4 +38,4 @@ impl super::Extension for ClipboardExtension { fn calculate(&self, _: &Mapping, _: &Vec) -> Option { self.clipboard_manager.get_clipboard() } -} \ No newline at end of file +} diff --git a/src/extension/date.rs b/src/extension/date.rs index 431b4e5..ac15295 100644 --- a/src/extension/date.rs +++ b/src/extension/date.rs @@ -17,14 +17,14 @@ * along with espanso. If not, see . */ -use serde_yaml::{Mapping, Value}; use chrono::{DateTime, Local}; +use serde_yaml::{Mapping, Value}; pub struct DateExtension {} impl DateExtension { pub fn new() -> DateExtension { - DateExtension{} + DateExtension {} } } @@ -40,10 +40,10 @@ impl super::Extension for DateExtension { let date = if let Some(format) = format { now.format(format.as_str().unwrap()).to_string() - }else{ + } else { now.to_rfc2822() }; Some(date) } -} \ No newline at end of file +} diff --git a/src/extension/dummy.rs b/src/extension/dummy.rs index c9932e8..6bbb1dd 100644 --- a/src/extension/dummy.rs +++ b/src/extension/dummy.rs @@ -23,7 +23,7 @@ pub struct DummyExtension {} impl DummyExtension { pub fn new() -> DummyExtension { - DummyExtension{} + DummyExtension {} } } @@ -37,8 +37,8 @@ impl super::Extension for DummyExtension { if let Some(echo) = echo { Some(echo.as_str().unwrap_or_default().to_owned()) - }else{ + } else { None } } -} \ No newline at end of file +} diff --git a/src/extension/mod.rs b/src/extension/mod.rs index 9db489a..9a363c5 100644 --- a/src/extension/mod.rs +++ b/src/extension/mod.rs @@ -17,22 +17,22 @@ * along with espanso. If not, see . */ -use serde_yaml::Mapping; use crate::clipboard::ClipboardManager; +use serde_yaml::Mapping; -mod date; -mod shell; -mod script; -mod random; mod clipboard; +mod date; pub mod dummy; +mod random; +mod script; +mod shell; pub trait Extension { fn name(&self) -> String; fn calculate(&self, params: &Mapping, args: &Vec) -> Option; } -pub fn get_extensions(clipboard_manager: Box) -> Vec>{ +pub fn get_extensions(clipboard_manager: Box) -> Vec> { vec![ Box::new(date::DateExtension::new()), Box::new(shell::ShellExtension::new()), @@ -41,4 +41,4 @@ pub fn get_extensions(clipboard_manager: Box) -> Vec. */ -use serde_yaml::{Mapping, Value}; +use log::{error, warn}; use rand::seq::SliceRandom; -use log::{warn, error}; +use serde_yaml::{Mapping, Value}; pub struct RandomExtension {} impl RandomExtension { pub fn new() -> RandomExtension { - RandomExtension{} + RandomExtension {} } } @@ -38,13 +38,14 @@ impl super::Extension for RandomExtension { let choices = params.get(&Value::from("choices")); if choices.is_none() { warn!("No 'choices' parameter specified for random variable"); - return None + return None; } let choices = choices.unwrap().as_sequence(); if let Some(choices) = choices { - let str_choices = choices.iter().map(|arg| { - arg.as_str().unwrap_or_default().to_string() - }).collect::>(); + let str_choices = choices + .iter() + .map(|arg| arg.as_str().unwrap_or_default().to_string()) + .collect::>(); // Select a random choice between the possibilities let choice = str_choices.choose(&mut rand::thread_rng()); @@ -54,14 +55,13 @@ impl super::Extension for RandomExtension { // Render arguments let output = crate::render::utils::render_args(output, args); - return Some(output) - }, + return Some(output); + } None => { error!("Could not select a random choice."); - return None - }, + return None; + } } - } error!("choices array have an invalid format '{:?}'", choices); @@ -77,11 +77,7 @@ mod tests { #[test] fn test_random_basic() { let mut params = Mapping::new(); - let choices = vec!( - "first", - "second", - "third", - ); + let choices = vec!["first", "second", "third"]; params.insert(Value::from("choices"), Value::from(choices.clone())); let extension = RandomExtension::new(); @@ -97,11 +93,7 @@ mod tests { #[test] fn test_random_with_args() { let mut params = Mapping::new(); - let choices = vec!( - "first $0$", - "second $0$", - "$0$ third", - ); + let choices = vec!["first $0$", "second $0$", "$0$ third"]; params.insert(Value::from("choices"), Value::from(choices.clone())); let extension = RandomExtension::new(); @@ -111,12 +103,8 @@ mod tests { let output = output.unwrap(); - let rendered_choices = vec!( - "first test", - "second test", - "test third", - ); + let rendered_choices = vec!["first test", "second test", "test third"]; assert!(rendered_choices.iter().any(|x| x == &output)); } -} \ No newline at end of file +} diff --git a/src/extension/script.rs b/src/extension/script.rs index ac233bc..875abe6 100644 --- a/src/extension/script.rs +++ b/src/extension/script.rs @@ -17,16 +17,16 @@ * along with espanso. If not, see . */ +use log::{error, warn}; use serde_yaml::{Mapping, Value}; -use std::process::Command; -use log::{warn, error}; use std::path::PathBuf; +use std::process::Command; pub struct ScriptExtension {} impl ScriptExtension { pub fn new() -> ScriptExtension { - ScriptExtension{} + ScriptExtension {} } } @@ -39,17 +39,21 @@ impl super::Extension for ScriptExtension { let args = params.get(&Value::from("args")); if args.is_none() { warn!("No 'args' parameter specified for script variable"); - return None + return None; } let args = args.unwrap().as_sequence(); if let Some(args) = args { - let mut str_args = args.iter().map(|arg| { - arg.as_str().unwrap_or_default().to_string() - }).collect::>(); + let mut str_args = args + .iter() + .map(|arg| arg.as_str().unwrap_or_default().to_string()) + .collect::>(); // The user has to enable argument concatenation explicitly - let inject_args = params.get(&Value::from("inject_args")) - .unwrap_or(&Value::from(false)).as_bool().unwrap_or(false); + let inject_args = params + .get(&Value::from("inject_args")) + .unwrap_or(&Value::from(false)) + .as_bool() + .unwrap_or(false); if inject_args { str_args.extend(user_args.clone()); } @@ -71,17 +75,12 @@ impl super::Extension for ScriptExtension { } }); - let output = if str_args.len() > 1 { - Command::new(&str_args[0]) - .args(&str_args[1..]) - .output() - }else{ - Command::new(&str_args[0]) - .output() + Command::new(&str_args[0]).args(&str_args[1..]).output() + } else { + Command::new(&str_args[0]).output() }; - match output { Ok(output) => { let output_str = String::from_utf8_lossy(output.stdout.as_slice()); @@ -94,12 +93,12 @@ impl super::Extension for ScriptExtension { warn!("Script command reported error: \n{}", error_str); } - return Some(output_str.into_owned()) - }, + return Some(output_str.into_owned()); + } Err(e) => { error!("Could not execute script '{:?}', error: {}", args, e); - return None - }, + return None; + } } } @@ -117,7 +116,10 @@ mod tests { #[cfg(not(target_os = "windows"))] fn test_script_basic() { 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 output = extension.calculate(¶ms, &vec![]); @@ -130,7 +132,10 @@ mod tests { #[cfg(not(target_os = "windows"))] fn test_script_inject_args_off() { 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 output = extension.calculate(¶ms, &vec!["jon".to_owned()]); @@ -143,7 +148,10 @@ mod tests { #[cfg(not(target_os = "windows"))] fn test_script_inject_args_on() { 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)); let extension = ScriptExtension::new(); @@ -152,4 +160,4 @@ mod tests { assert!(output.is_some()); assert_eq!(output.unwrap(), "hello world jon\n"); } -} \ No newline at end of file +} diff --git a/src/extension/shell.rs b/src/extension/shell.rs index ab27fe5..d769a0a 100644 --- a/src/extension/shell.rs +++ b/src/extension/shell.rs @@ -17,15 +17,15 @@ * along with espanso. If not, see . */ +use log::{error, warn}; +use regex::{Captures, Regex}; use serde_yaml::{Mapping, Value}; use std::process::{Command, Output}; -use log::{warn, error}; -use regex::{Regex, Captures}; lazy_static! { static ref POS_ARG_REGEX: Regex = if cfg!(target_os = "windows") { Regex::new("%(?P\\d+)").unwrap() - }else{ + } else { Regex::new("\\$(?P\\d+)").unwrap() }; } @@ -39,33 +39,15 @@ pub enum Shell { } impl Shell { - fn execute_cmd(&self, cmd: &str) -> std::io::Result{ + fn execute_cmd(&self, cmd: &str) -> std::io::Result { match self { - Shell::Cmd => { - Command::new("cmd") - .args(&["/C", &cmd]) - .output() - }, - Shell::Powershell => { - Command::new("powershell") - .args(&["-Command", &cmd]) - .output() - }, - Shell::WSL => { - Command::new("wsl") - .args(&["bash", "-c", &cmd]) - .output() - }, - Shell::Bash => { - Command::new("bash") - .args(&["-c", &cmd]) - .output() - }, - Shell::Sh => { - Command::new("sh") - .args(&["-c", &cmd]) - .output() - }, + Shell::Cmd => Command::new("cmd").args(&["/C", &cmd]).output(), + Shell::Powershell => Command::new("powershell") + .args(&["-Command", &cmd]) + .output(), + Shell::WSL => Command::new("wsl").args(&["bash", "-c", &cmd]).output(), + Shell::Bash => Command::new("bash").args(&["-c", &cmd]).output(), + Shell::Sh => Command::new("sh").args(&["-c", &cmd]).output(), } } @@ -76,7 +58,7 @@ impl Shell { "wsl" => Some(Shell::WSL), "bash" => Some(Shell::Bash), "sh" => Some(Shell::Sh), - _ => None + _ => None, } } } @@ -85,11 +67,11 @@ impl Default for Shell { fn default() -> Shell { if cfg!(target_os = "windows") { Shell::Powershell - }else if cfg!(target_os = "macos") { + } else if cfg!(target_os = "macos") { Shell::Sh - }else if cfg!(target_os = "linux") { + } else if cfg!(target_os = "linux") { Shell::Bash - }else{ + } else { panic!("invalid target os for shell") } } @@ -99,7 +81,7 @@ pub struct ShellExtension {} impl ShellExtension { pub fn new() -> ShellExtension { - ShellExtension{} + ShellExtension {} } } @@ -112,20 +94,22 @@ impl super::Extension for ShellExtension { let cmd = params.get(&Value::from("cmd")); if cmd.is_none() { warn!("No 'cmd' parameter specified for shell variable"); - return None + return None; } let cmd = cmd.unwrap().as_str().unwrap(); // Render positional parameters in args - let cmd = POS_ARG_REGEX.replace_all(&cmd, |caps: &Captures| { - let position_str = caps.name("pos").unwrap().as_str(); - let position = position_str.parse::().unwrap_or(-1); - if position >= 0 && position < args.len() as i32 { - args[position as usize].to_owned() - }else{ - "".to_owned() - } - }).to_string(); + let cmd = POS_ARG_REGEX + .replace_all(&cmd, |caps: &Captures| { + let position_str = caps.name("pos").unwrap().as_str(); + let position = position_str.parse::().unwrap_or(-1); + if position >= 0 && position < args.len() as i32 { + args[position as usize].to_owned() + } else { + "".to_owned() + } + }) + .to_string(); let shell_param = params.get(&Value::from("shell")); let shell = if let Some(shell_param) = shell_param { @@ -138,7 +122,7 @@ impl super::Extension for ShellExtension { } shell.unwrap() - }else{ + } else { Shell::default() }; @@ -169,11 +153,11 @@ impl super::Extension for ShellExtension { } Some(output_str) - }, + } Err(e) => { error!("Could not execute cmd '{}', error: {}", cmd, e); None - }, + } } } } @@ -195,7 +179,7 @@ mod tests { if cfg!(target_os = "windows") { assert_eq!(output.unwrap(), "hello world\r\n"); - }else{ + } else { assert_eq!(output.unwrap(), "hello world\n"); } } @@ -216,7 +200,10 @@ mod tests { #[test] fn test_shell_trimmed_2() { let mut params = Mapping::new(); - 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)); @@ -239,7 +226,7 @@ mod tests { assert!(output.is_some()); if cfg!(target_os = "windows") { assert_eq!(output.unwrap(), "hello world\r\n"); - }else{ + } else { assert_eq!(output.unwrap(), "hello world\n"); } } @@ -285,4 +272,4 @@ mod tests { assert_eq!(output.unwrap(), "hello\r\n"); } -} \ No newline at end of file +} diff --git a/src/keyboard/linux.rs b/src/keyboard/linux.rs index 622c1e1..dd05f67 100644 --- a/src/keyboard/linux.rs +++ b/src/keyboard/linux.rs @@ -17,14 +17,13 @@ * along with espanso. If not, see . */ -use std::ffi::CString; -use crate::bridge::linux::*; use super::PasteShortcut; -use log::error; +use crate::bridge::linux::*; use crate::config::Configs; +use log::error; +use std::ffi::CString; -pub struct LinuxKeyboardManager { -} +pub struct LinuxKeyboardManager {} impl super::KeyboardManager for LinuxKeyboardManager { fn send_string(&self, active_config: &Configs, s: &str) { @@ -33,11 +32,11 @@ impl super::KeyboardManager for LinuxKeyboardManager { Ok(cstr) => unsafe { if active_config.fast_inject { fast_send_string(cstr.as_ptr(), active_config.inject_delay); - }else{ + } else { send_string(cstr.as_ptr()); } - } - Err(e) => panic!(e.to_string()) + }, + Err(e) => panic!(e.to_string()), } } @@ -45,7 +44,7 @@ impl super::KeyboardManager for LinuxKeyboardManager { unsafe { if active_config.fast_inject { fast_send_enter(); - }else{ + } else { send_enter(); } } @@ -94,7 +93,7 @@ impl super::KeyboardManager for LinuxKeyboardManager { unsafe { if active_config.fast_inject { fast_delete_string(count, active_config.backspace_delay); - }else{ + } else { delete_string(count) } } @@ -104,7 +103,7 @@ impl super::KeyboardManager for LinuxKeyboardManager { unsafe { if active_config.fast_inject { fast_left_arrow(count); - }else{ + } else { left_arrow(count); } } @@ -115,4 +114,4 @@ impl super::KeyboardManager for LinuxKeyboardManager { trigger_copy(); } } -} \ No newline at end of file +} diff --git a/src/keyboard/macos.rs b/src/keyboard/macos.rs index 03442fb..a5ea03e 100644 --- a/src/keyboard/macos.rs +++ b/src/keyboard/macos.rs @@ -17,21 +17,22 @@ * along with espanso. If not, see . */ -use std::ffi::CString; -use crate::bridge::macos::*; use super::PasteShortcut; -use log::error; +use crate::bridge::macos::*; use crate::config::Configs; +use log::error; +use std::ffi::CString; -pub struct MacKeyboardManager { -} +pub struct MacKeyboardManager {} impl super::KeyboardManager for MacKeyboardManager { fn send_string(&self, _: &Configs, s: &str) { let res = CString::new(s); match res { - Ok(cstr) => unsafe { send_string(cstr.as_ptr()); } - Err(e) => panic!(e.to_string()) + Ok(cstr) => unsafe { + send_string(cstr.as_ptr()); + }, + Err(e) => panic!(e.to_string()), } } @@ -64,7 +65,7 @@ impl super::KeyboardManager for MacKeyboardManager { } fn delete_string(&self, _: &Configs, count: i32) { - unsafe {delete_string(count)} + unsafe { delete_string(count) } } fn move_cursor_left(&self, _: &Configs, count: i32) { @@ -73,4 +74,4 @@ impl super::KeyboardManager for MacKeyboardManager { send_multi_vkey(0x7B, count); } } -} \ No newline at end of file +} diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index 304b201..add9499 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -17,8 +17,8 @@ * along with espanso. If not, see . */ -use serde::{Serialize, Deserialize}; use crate::config::Configs; +use serde::{Deserialize, Serialize}; #[cfg(target_os = "windows")] mod windows; @@ -40,15 +40,15 @@ pub trait KeyboardManager { #[derive(Debug, Serialize, Deserialize, Clone)] pub enum PasteShortcut { - Default, // Default one for the current system - CtrlV, // Classic Ctrl+V shortcut - CtrlShiftV, // Could be used to paste without formatting in many applications - ShiftInsert, // Often used in Linux systems - CtrlAltV, // Used in some Linux terminals (urxvt) - MetaV, // Corresponding to Win+V on Windows and Linux, CMD+V on macOS + Default, // Default one for the current system + CtrlV, // Classic Ctrl+V shortcut + CtrlShiftV, // Could be used to paste without formatting in many applications + ShiftInsert, // Often used in Linux systems + CtrlAltV, // Used in some Linux terminals (urxvt) + MetaV, // Corresponding to Win+V on Windows and Linux, CMD+V on macOS } -impl Default for PasteShortcut{ +impl Default for PasteShortcut { fn default() -> Self { PasteShortcut::Default } @@ -57,17 +57,17 @@ impl Default for PasteShortcut{ // WINDOWS IMPLEMENTATION #[cfg(target_os = "windows")] pub fn get_manager() -> impl KeyboardManager { - windows::WindowsKeyboardManager{} + windows::WindowsKeyboardManager {} } // LINUX IMPLEMENTATION #[cfg(target_os = "linux")] pub fn get_manager() -> impl KeyboardManager { - linux::LinuxKeyboardManager{} + linux::LinuxKeyboardManager {} } // MAC IMPLEMENTATION #[cfg(target_os = "macos")] pub fn get_manager() -> impl KeyboardManager { - macos::MacKeyboardManager{} -} \ No newline at end of file + macos::MacKeyboardManager {} +} diff --git a/src/keyboard/windows.rs b/src/keyboard/windows.rs index e21f05d..fce6718 100644 --- a/src/keyboard/windows.rs +++ b/src/keyboard/windows.rs @@ -17,27 +17,23 @@ * along with espanso. If not, see . */ -use widestring::{U16CString}; -use crate::bridge::windows::*; use super::PasteShortcut; -use log::error; +use crate::bridge::windows::*; use crate::config::Configs; +use log::error; +use widestring::U16CString; -pub struct WindowsKeyboardManager { -} +pub struct WindowsKeyboardManager {} impl super::KeyboardManager for WindowsKeyboardManager { fn send_string(&self, _: &Configs, s: &str) { let res = U16CString::from_str(s); match res { - Ok(s) => { - unsafe { - send_string(s.as_ptr()); - } - } - Err(e) => println!("Error while sending string: {}", e.to_string()) + Ok(s) => unsafe { + send_string(s.as_ptr()); + }, + Err(e) => println!("Error while sending string: {}", e.to_string()), } - } fn send_enter(&self, _: &Configs) { @@ -63,9 +59,7 @@ impl super::KeyboardManager for WindowsKeyboardManager { } fn delete_string(&self, config: &Configs, count: i32) { - unsafe { - delete_string(count, config.backspace_delay) - } + unsafe { delete_string(count, config.backspace_delay) } } fn move_cursor_left(&self, _: &Configs, count: i32) { @@ -80,4 +74,4 @@ impl super::KeyboardManager for WindowsKeyboardManager { trigger_copy(); } } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 3831ba9..c727040 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,56 +20,56 @@ #[macro_use] extern crate lazy_static; +use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher}; +use regex::Regex; use std::fs::{File, OpenOptions}; use std::io::{BufRead, BufReader}; use std::process::exit; -use std::sync::{Arc, mpsc}; +use std::process::{Command, Stdio}; use std::sync::atomic::AtomicBool; -use std::sync::mpsc::{Receiver, Sender, RecvError}; +use std::sync::mpsc::channel; +use std::sync::mpsc::{Receiver, RecvError, Sender}; +use std::sync::{mpsc, Arc}; use std::thread; use std::time::Duration; -use std::process::{Command, Stdio}; -use notify::{RecommendedWatcher, Watcher, RecursiveMode, DebouncedEvent}; -use std::sync::mpsc::channel; -use regex::Regex; -use clap::{App, Arg, ArgMatches, SubCommand, AppSettings}; +use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; use fs2::FileExt; -use log::{info, LevelFilter, warn, error}; -use simplelog::{CombinedLogger, SharedLogger, TerminalMode, TermLogger, WriteLogger}; +use log::{error, info, warn, LevelFilter}; +use simplelog::{CombinedLogger, SharedLogger, TermLogger, TerminalMode, WriteLogger}; -use crate::config::{ConfigManager, ConfigSet, Configs}; use crate::config::runtime::RuntimeConfigManager; +use crate::config::{ConfigManager, ConfigSet, Configs}; use crate::engine::Engine; -use crate::event::*; use crate::event::manager::{DefaultEventManager, EventManager}; +use crate::event::*; use crate::matcher::scrolling::ScrollingMatcher; -use crate::package::{InstallResult, PackageManager, RemoveResult, UpdateResult}; use crate::package::default::DefaultPackageManager; use crate::package::zip::ZipPackageResolver; +use crate::package::{InstallResult, PackageManager, RemoveResult, UpdateResult}; use crate::protocol::*; use crate::system::SystemManager; use crate::ui::UIManager; -mod ui; -mod edit; -mod event; -mod check; -mod utils; mod bridge; -mod engine; -mod config; -mod render; -mod system; -mod context; -mod matcher; -mod process; -mod package; -mod keyboard; -mod protocol; +mod check; mod clipboard; +mod config; +mod context; +mod edit; +mod engine; +mod event; mod extension; +mod keyboard; +mod matcher; +mod package; +mod process; +mod protocol; +mod render; mod sysdaemon; +mod system; +mod ui; +mod utils; const VERSION: &str = env!("CARGO_PKG_VERSION"); const LOG_FILE: &str = "espanso.log"; @@ -77,24 +77,25 @@ const LOG_FILE: &str = "espanso.log"; fn main() { let install_subcommand = SubCommand::with_name("install") .about("Install a package. Equivalent to 'espanso package install'") - .arg(Arg::with_name("external") - .short("e") - .long("external") - .required(false) - .takes_value(false) - .help("Allow installing packages from non-verified repositories.")) - .arg(Arg::with_name("package_name") - .help("Package name")) - .arg(Arg::with_name("repository_url") - .help("(Optional) Link to GitHub repository") - .required(false) - .default_value("hub") + .arg( + Arg::with_name("external") + .short("e") + .long("external") + .required(false) + .takes_value(false) + .help("Allow installing packages from non-verified repositories."), + ) + .arg(Arg::with_name("package_name").help("Package name")) + .arg( + Arg::with_name("repository_url") + .help("(Optional) Link to GitHub repository") + .required(false) + .default_value("hub"), ); let uninstall_subcommand = SubCommand::with_name("uninstall") .about("Remove an installed package. Equivalent to 'espanso package uninstall'") - .arg(Arg::with_name("package_name") - .help("Package name")); + .arg(Arg::with_name("package_name").help("Package name")); let mut clap_instance = App::new("espanso") .version(VERSION) @@ -191,7 +192,6 @@ fn main() { return; } - let log_level = matches.occurrences_of("v") as i32; // Load the configuration @@ -299,7 +299,9 @@ fn main() { } // Defaults help print - clap_instance.print_long_help().expect("Unable to print help"); + clap_instance + .print_long_help() + .expect("Unable to print help"); println!(); } @@ -314,8 +316,8 @@ fn init_logger(config_set: &ConfigSet, reset: bool) { let mut log_outputs: Vec> = Vec::new(); // Initialize terminal output - let terminal_out = TermLogger::new(log_level, - simplelog::Config::default(), TerminalMode::Mixed); + let terminal_out = + TermLogger::new(log_level, simplelog::Config::default(), TerminalMode::Mixed); if let Some(terminal_out) = terminal_out { log_outputs.push(terminal_out); } @@ -338,9 +340,7 @@ fn init_logger(config_set: &ConfigSet, reset: bool) { let file_out = WriteLogger::new(LevelFilter::Info, simplelog::Config::default(), log_file); log_outputs.push(file_out); - CombinedLogger::init( - log_outputs - ).expect("Error opening log destination"); + CombinedLogger::init(log_outputs).expect("Error opening log destination"); // Activate logging for panics log_panics::init(); @@ -360,26 +360,42 @@ fn daemon_main(config_set: ConfigSet) { init_logger(&config_set, true); info!("espanso version {}", VERSION); - info!("using config path: {}", context::get_config_dir().to_string_lossy()); - info!("using package path: {}", context::get_package_dir().to_string_lossy()); + info!( + "using config path: {}", + context::get_config_dir().to_string_lossy() + ); + info!( + "using package path: {}", + context::get_package_dir().to_string_lossy() + ); let (send_channel, receive_channel) = mpsc::channel(); - let ipc_server = protocol::get_ipc_server(Service::Daemon, config_set.default.clone(), send_channel.clone()); + let ipc_server = protocol::get_ipc_server( + Service::Daemon, + config_set.default.clone(), + send_channel.clone(), + ); ipc_server.start(); info!("spawning worker process..."); let espanso_path = std::env::current_exe().expect("unable to obtain espanso path location"); - crate::process::spawn_process(&espanso_path.to_string_lossy().to_string(), &vec!("worker".to_owned())); + crate::process::spawn_process( + &espanso_path.to_string_lossy().to_string(), + &vec!["worker".to_owned()], + ); std::thread::sleep(Duration::from_millis(200)); if config_set.default.auto_restart { let send_channel_clone = send_channel.clone(); - thread::Builder::new().name("watcher_background".to_string()).spawn(move || { - watcher_background(send_channel_clone); - }).expect("Unable to spawn watcher background thread"); + thread::Builder::new() + .name("watcher_background".to_string()) + .spawn(move || { + watcher_background(send_channel_clone); + }) + .expect("Unable to spawn watcher background thread"); } loop { @@ -388,33 +404,48 @@ fn daemon_main(config_set: ConfigSet) { match event { Event::Action(ActionType::RestartWorker) => { // Terminate the worker process - send_command_or_warn(Service::Worker, config_set.default.clone(), IPCCommand::exit_worker()); + send_command_or_warn( + Service::Worker, + config_set.default.clone(), + IPCCommand::exit_worker(), + ); std::thread::sleep(Duration::from_millis(500)); // Restart the worker process - crate::process::spawn_process(&espanso_path.to_string_lossy().to_string(), &vec!("worker".to_owned(), "--reload".to_owned())); - }, + crate::process::spawn_process( + &espanso_path.to_string_lossy().to_string(), + &vec!["worker".to_owned(), "--reload".to_owned()], + ); + } Event::Action(ActionType::Exit) => { - send_command_or_warn(Service::Worker, config_set.default.clone(), IPCCommand::exit_worker()); + send_command_or_warn( + Service::Worker, + config_set.default.clone(), + IPCCommand::exit_worker(), + ); std::thread::sleep(Duration::from_millis(200)); info!("terminating espanso."); std::process::exit(0); - }, + } _ => { // Forward the command to the worker let command = IPCCommand::from(event); if let Some(command) = command { - send_command_or_warn(Service::Worker, config_set.default.clone(), command); + send_command_or_warn( + Service::Worker, + config_set.default.clone(), + command, + ); } } } - }, + } Err(e) => { warn!("error while reading event in daemon process: {}", e); - }, + } } } } @@ -423,13 +454,18 @@ fn watcher_background(sender: Sender) { // Create a channel to receive the events. let (tx, rx) = channel(); - let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(1)).expect("unable to create file watcher"); + let mut watcher: RecommendedWatcher = + Watcher::new(tx, Duration::from_secs(1)).expect("unable to create file watcher"); let config_path = crate::context::get_config_dir(); - watcher.watch(&config_path, RecursiveMode::Recursive).expect("unable to start watcher"); - - info!("watching for changes in path: {}", config_path.to_string_lossy()); + watcher + .watch(&config_path, RecursiveMode::Recursive) + .expect("unable to start watcher"); + info!( + "watching for changes in path: {}", + config_path.to_string_lossy() + ); loop { let should_reload = match rx.recv() { @@ -443,15 +479,16 @@ fn watcher_background(sender: Sender) { }; if let Some(path) = path { - if path.extension().unwrap_or_default() == "yml" { // Only load yml files + if path.extension().unwrap_or_default() == "yml" { + // Only load yml files true - }else{ + } else { false } - }else{ + } else { false } - }, + } Err(e) => { warn!("error while watching files: {:?}", e); false @@ -469,11 +506,12 @@ fn watcher_background(sender: Sender) { sender.send(event).unwrap_or_else(|e| { warn!("unable to communicate with daemon thread: {}", e); }) - }, + } Err(error) => { error!("Unable to reload configuration due to an error: {}", error); let event = Event::System(SystemEvent::NotifyRequest( - "Unable to reload config due to an error, see the logs for more details.".to_owned() + "Unable to reload config due to an error, see the logs for more details." + .to_owned(), )); sender.send(event).unwrap_or_else(|e| { warn!("unable to communicate with daemon thread: {}", e); @@ -492,7 +530,7 @@ fn worker_main(config_set: ConfigSet, matches: &ArgMatches) { let is_reloading: bool = if matches.is_present("reload") { true - }else{ + } else { false }; @@ -502,21 +540,34 @@ fn worker_main(config_set: ConfigSet, matches: &ArgMatches) { // we could reinterpret the characters we are injecting let is_injecting = Arc::new(std::sync::atomic::AtomicBool::new(false)); - let context = context::new(config_set.default.clone(), send_channel.clone(), is_injecting.clone()); + let context = context::new( + config_set.default.clone(), + send_channel.clone(), + is_injecting.clone(), + ); let config_set_copy = config_set.clone(); - thread::Builder::new().name("daemon_background".to_string()).spawn(move || { - worker_background(receive_channel, config_set_copy, is_injecting, is_reloading); - }).expect("Unable to spawn daemon background thread"); + thread::Builder::new() + .name("daemon_background".to_string()) + .spawn(move || { + worker_background(receive_channel, config_set_copy, is_injecting, is_reloading); + }) + .expect("Unable to spawn daemon background thread"); - let ipc_server = protocol::get_ipc_server(Service::Worker, config_set.default, send_channel.clone()); + let ipc_server = + protocol::get_ipc_server(Service::Worker, config_set.default, send_channel.clone()); ipc_server.start(); context.eventloop(); } /// Background thread worker for the daemon -fn worker_background(receive_channel: Receiver, config_set: ConfigSet, is_injecting: Arc, is_reloading: bool) { +fn worker_background( + receive_channel: Receiver, + config_set: ConfigSet, + is_injecting: Arc, + is_reloading: bool, +) { let system_manager = system::get_manager(); let config_manager = RuntimeConfigManager::new(config_set, system_manager); @@ -524,7 +575,7 @@ fn worker_background(receive_channel: Receiver, config_set: ConfigSet, is if config_manager.default_config().show_notifications { if !is_reloading { ui_manager.notify("espanso is running!"); - }else{ + } else { ui_manager.notify("Reloaded config!"); } } @@ -535,24 +586,25 @@ fn worker_background(receive_channel: Receiver, config_set: ConfigSet, is let extensions = extension::get_extensions(Box::new(clipboard::get_manager())); - let renderer = render::default::DefaultRenderer::new(extensions, - config_manager.default_config().clone()); + let renderer = + render::default::DefaultRenderer::new(extensions, config_manager.default_config().clone()); - let engine = Engine::new(&keyboard_manager, - &clipboard_manager, - &config_manager, - &ui_manager, - &renderer, - is_injecting, + let engine = Engine::new( + &keyboard_manager, + &clipboard_manager, + &config_manager, + &ui_manager, + &renderer, + is_injecting, ); let matcher = ScrollingMatcher::new(&config_manager, &engine); let event_manager = DefaultEventManager::new( receive_channel, - vec!(&matcher), - vec!(&engine, &matcher), - vec!(&engine), + vec![&matcher], + vec![&engine, &matcher], + vec![&engine], ); info!("worker is running!"); @@ -597,21 +649,21 @@ fn start_daemon(config_set: ConfigSet) { if let Ok(status) = res { if status.success() { println!("Daemon started correctly!") - }else{ + } else { eprintln!("Error starting launchd daemon with status: {}", status); } - }else{ + } else { eprintln!("Error starting launchd daemon: {}", res.unwrap_err()); } - }else{ + } else { fork_daemon(config_set); } } #[cfg(target_os = "linux")] fn start_daemon(config_set: ConfigSet) { - use std::process::{Command, Stdio}; use crate::sysdaemon::{verify, VerifyResult}; + use std::process::{Command, Stdio}; // Check if Systemd is available in the system let status = Command::new("systemctl") @@ -622,11 +674,7 @@ fn start_daemon(config_set: ConfigSet) { // If Systemd is not available in the system, espanso should default to unmanaged mode // See issue https://github.com/federico-terzi/espanso/issues/139 - let force_unmanaged = if let Err(_) = status { - true - } else { - false - }; + let force_unmanaged = if let Err(_) = status { true } else { false }; if config_set.default.use_system_agent && !force_unmanaged { // Make sure espanso is currently registered in systemd @@ -634,12 +682,12 @@ fn start_daemon(config_set: ConfigSet) { match res { VerifyResult::EnabledAndValid => { // Do nothing, everything is ok! - }, + } VerifyResult::EnabledButInvalidPath => { eprintln!("Updating espanso service file with new path..."); unregister_main(config_set.clone()); register_main(config_set); - }, + } VerifyResult::NotEnabled => { use dialoguer::Confirmation; if Confirmation::new() @@ -656,7 +704,7 @@ fn start_daemon(config_set: ConfigSet) { std::process::exit(4); } - }, + } } // Start the espanso service @@ -667,13 +715,13 @@ fn start_daemon(config_set: ConfigSet) { if let Ok(status) = res { if status.success() { println!("Daemon started correctly!") - }else{ + } else { eprintln!("Error starting systemd daemon with status: {}", status); } - }else{ + } else { eprintln!("Error starting systemd daemon: {}", res.unwrap_err()); } - }else{ + } else { if force_unmanaged { eprintln!("Systemd is not available in this system, switching to unmanaged mode."); } @@ -690,7 +738,8 @@ fn fork_daemon(config_set: ConfigSet) { println!("Unable to fork."); exit(4); } - if pid > 0 { // Parent process exit + if pid > 0 { + // Parent process exit println!("daemon started!"); exit(0); } @@ -723,12 +772,11 @@ fn status_main() { println!("espanso is not running"); release_lock(lock_file); - }else{ + } else { println!("espanso is running"); } } - /// Stop subcommand, used to stop the daemon. fn stop_main(config_set: ConfigSet) { // Try to acquire lock file @@ -748,8 +796,12 @@ fn restart_main(config_set: ConfigSet) { let lock_file = acquire_lock(); if lock_file.is_none() { // Terminate the current espanso daemon - send_command_or_warn(Service::Daemon, config_set.default.clone(), IPCCommand::exit()); - }else{ + send_command_or_warn( + Service::Daemon, + config_set.default.clone(), + IPCCommand::exit(), + ); + } else { release_lock(lock_file.unwrap()); } @@ -768,14 +820,20 @@ fn detect_main() { println!("Listening for changes, now focus the window you want to analyze."); println!("You can terminate with CTRL+C\n"); - let mut last_title : String = "".to_owned(); - let mut last_class : String = "".to_owned(); - let mut last_exec : String = "".to_owned(); + let mut last_title: String = "".to_owned(); + let mut last_class: String = "".to_owned(); + let mut last_exec: String = "".to_owned(); loop { - let curr_title = system_manager.get_current_window_title().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(); + let curr_title = system_manager + .get_current_window_title() + .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 if curr_title != last_title || curr_class != last_class || curr_exec != last_exec { @@ -800,23 +858,31 @@ fn detect_main() { #[cfg(target_os = "macos")] fn detect_main() { thread::spawn(|| { - use std::io::Write; use std::io::stdout; + use std::io::Write; let system_manager = system::get_manager(); 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"); - let mut last_title : String = "".to_owned(); - let mut last_class : String = "".to_owned(); - let mut last_exec : String = "".to_owned(); + let mut last_title: String = "".to_owned(); + let mut last_class: String = "".to_owned(); + let mut last_exec: String = "".to_owned(); loop { - let curr_title = system_manager.get_current_window_title().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(); + let curr_title = system_manager + .get_current_window_title() + .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 if curr_title != last_title || curr_class != last_class || curr_exec != last_exec { @@ -844,22 +910,22 @@ fn detect_main() { fn cmd_main(config_set: ConfigSet, matches: &ArgMatches) { let command = if matches.subcommand_matches("exit").is_some() { Some(IPCCommand::exit()) - }else if matches.subcommand_matches("toggle").is_some() { + } else if matches.subcommand_matches("toggle").is_some() { Some(IPCCommand { id: String::from("toggle"), payload: String::from(""), }) - }else if matches.subcommand_matches("enable").is_some() { + } else if matches.subcommand_matches("enable").is_some() { Some(IPCCommand { id: String::from("enable"), payload: String::from(""), }) - }else if matches.subcommand_matches("disable").is_some() { + } else if matches.subcommand_matches("disable").is_some() { Some(IPCCommand { id: String::from("disable"), payload: String::from(""), }) - }else{ + } else { None }; @@ -889,7 +955,7 @@ fn log_main() { } exit(0); - }else{ + } else { println!("Error reading log file"); exit(1); } @@ -911,44 +977,43 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) { let repository = matches.value_of("repository_url").unwrap_or("hub"); - let package_resolver= Box::new(ZipPackageResolver::new()); + let package_resolver = Box::new(ZipPackageResolver::new()); let allow_external: bool = if matches.is_present("external") { println!("Allowing external repositories"); true - }else{ + } else { false }; let mut package_manager = DefaultPackageManager::new_default(Some(package_resolver)); - let res = if repository == "hub" { // Installation from the Hub + let res = if repository == "hub" { + // Installation from the Hub if package_manager.is_index_outdated() { println!("Updating package index..."); let res = package_manager.update_index(false); match res { - Ok(update_result) => { - match update_result { - UpdateResult::NotOutdated => { - eprintln!("Index was already up to date"); - }, - UpdateResult::Updated => { - println!("Index updated!"); - }, + Ok(update_result) => match update_result { + UpdateResult::NotOutdated => { + eprintln!("Index was already up to date"); + } + UpdateResult::Updated => { + println!("Index updated!"); } }, Err(e) => { eprintln!("{}", e); exit(2); - }, + } } - }else{ + } else { println!("Using cached package index, run 'espanso package refresh' to update it.") } package_manager.install_package(package_name, allow_external) - }else{ + } else { // Make sure the repo is a valid github url lazy_static! { static ref GITHUB_REGEX: Regex = Regex::new(r#"https://github\.com/\S*/\S*"#).unwrap(); @@ -962,61 +1027,61 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) { if !allow_external { Ok(InstallResult::BlockedExternalPackage(repository.to_owned())) - }else{ + } else { package_manager.install_package_from_repo(package_name, repository) } }; match res { - Ok(install_result) => { - match install_result { - InstallResult::NotFoundInIndex => { - eprintln!("Package not found"); - }, - InstallResult::NotFoundInRepo => { - eprintln!("Package not found in repository, are you sure the folder exist in the repo?"); - }, - InstallResult::UnableToParsePackageInfo => { - eprintln!("Unable to parse Package info from README.md"); - }, - InstallResult::MissingPackageVersion => { - eprintln!("Missing package version"); - }, - InstallResult::AlreadyInstalled => { - eprintln!("{} already installed!", package_name); - }, - InstallResult::BlockedExternalPackage(repo_url) => { - eprintln!("Warning: the requested package is hosted on an external repository:"); - eprintln!(); - eprintln!("{}", repo_url); - eprintln!(); - eprintln!("and its contents may not have been verified by espanso."); - eprintln!(); - eprintln!("For your security, espanso blocks packages that are not verified."); - eprintln!("If you want to install the package anyway, you can force espanso"); - eprintln!("to install it with the following command, but please do it only"); - eprintln!("if you trust the source or you verified the contents of the package"); - eprintln!("by checking out the repository listed above."); - eprintln!(); + Ok(install_result) => match install_result { + InstallResult::NotFoundInIndex => { + eprintln!("Package not found"); + } + InstallResult::NotFoundInRepo => { + eprintln!( + "Package not found in repository, are you sure the folder exist in the repo?" + ); + } + InstallResult::UnableToParsePackageInfo => { + eprintln!("Unable to parse Package info from README.md"); + } + InstallResult::MissingPackageVersion => { + eprintln!("Missing package version"); + } + InstallResult::AlreadyInstalled => { + eprintln!("{} already installed!", package_name); + } + InstallResult::BlockedExternalPackage(repo_url) => { + eprintln!("Warning: the requested package is hosted on an external repository:"); + eprintln!(); + eprintln!("{}", repo_url); + eprintln!(); + eprintln!("and its contents may not have been verified by espanso."); + eprintln!(); + eprintln!("For your security, espanso blocks packages that are not verified."); + eprintln!("If you want to install the package anyway, you can force espanso"); + eprintln!("to install it with the following command, but please do it only"); + eprintln!("if you trust the source or you verified the contents of the package"); + eprintln!("by checking out the repository listed above."); + eprintln!(); - if repository == "hub" { - eprintln!("espanso install {} --external", package_name); - }else{ - eprintln!("espanso install {} {} --external", package_name, repository); - } - eprintln!(); + if repository == "hub" { + eprintln!("espanso install {} --external", package_name); + } else { + eprintln!("espanso install {} {} --external", package_name, repository); } - InstallResult::Installed => { - println!("{} successfully installed!", package_name); - println!(); - println!("You need to restart espanso for changes to take effect, using:"); - println!(" espanso restart"); - }, + eprintln!(); + } + InstallResult::Installed => { + println!("{} successfully installed!", package_name); + println!(); + println!("You need to restart espanso for changes to take effect, using:"); + println!(" espanso restart"); } }, Err(e) => { eprintln!("{}", e); - }, + } } } @@ -1031,22 +1096,20 @@ fn remove_package_main(_config_set: ConfigSet, matches: &ArgMatches) { let res = package_manager.remove_package(package_name); match res { - Ok(remove_result) => { - match remove_result { - RemoveResult::NotFound => { - eprintln!("{} package was not installed.", package_name); - }, - RemoveResult::Removed => { - println!("{} successfully removed!", package_name); - println!(); - println!("You need to restart espanso for changes to take effect, using:"); - println!(" espanso restart"); - }, + Ok(remove_result) => match remove_result { + RemoveResult::NotFound => { + eprintln!("{} package was not installed.", package_name); + } + RemoveResult::Removed => { + println!("{} successfully removed!", package_name); + println!(); + println!("You need to restart espanso for changes to take effect, using:"); + println!(" espanso restart"); } }, Err(e) => { eprintln!("{}", e); - }, + } } } @@ -1056,20 +1119,18 @@ fn update_index_main(_config_set: ConfigSet) { let res = package_manager.update_index(true); match res { - Ok(update_result) => { - match update_result { - UpdateResult::NotOutdated => { - eprintln!("Index was already up to date"); - }, - UpdateResult::Updated => { - println!("Index updated!"); - }, + Ok(update_result) => match update_result { + UpdateResult::NotOutdated => { + eprintln!("Index was already up to date"); + } + UpdateResult::Updated => { + println!("Index updated!"); } }, Err(e) => { eprintln!("{}", e); exit(2); - }, + } } } @@ -1082,7 +1143,7 @@ fn list_package_main(_config_set: ConfigSet, matches: &ArgMatches) { for package in list.iter() { println!("{:?}", package); } - }else{ + } else { for package in list.iter() { println!("{} - {}", package.name, package.version); } @@ -1096,14 +1157,14 @@ fn path_main(_config_set: ConfigSet, matches: &ArgMatches) { if matches.subcommand_matches("config").is_some() { println!("{}", config.to_string_lossy()); - }else if matches.subcommand_matches("packages").is_some() { + } else if matches.subcommand_matches("packages").is_some() { println!("{}", packages.to_string_lossy()); - }else if matches.subcommand_matches("data").is_some() { + } else if matches.subcommand_matches("data").is_some() { println!("{}", data.to_string_lossy()); - }else if matches.subcommand_matches("default").is_some() { + } else if matches.subcommand_matches("default").is_some() { let default_file = config.join(crate::config::DEFAULT_CONFIG_FILE_NAME); println!("{}", default_file.to_string_lossy()); - }else{ + } else { println!("Config: {}", config.to_string_lossy()); println!("Packages: {}", packages.to_string_lossy()); println!("Data: {}", data.to_string_lossy()); @@ -1117,11 +1178,11 @@ fn edit_main(matches: &ArgMatches) { let config_dir = crate::context::get_config_dir(); let config_path = match config { - "default" => { - config_dir.join(crate::config::DEFAULT_CONFIG_FILE_NAME) - }, - name => { // Otherwise, search in the user/ config folder - config_dir.join(crate::config::USER_CONFIGS_FOLDER_NAME) + "default" => config_dir.join(crate::config::DEFAULT_CONFIG_FILE_NAME), + name => { + // Otherwise, search in the user/ config folder + config_dir + .join(crate::config::USER_CONFIGS_FOLDER_NAME) .join(name.to_owned() + ".yml") } }; @@ -1130,39 +1191,44 @@ fn edit_main(matches: &ArgMatches) { // Based on the fact that the file already exists or not, we should detect in different // ways if a reload is needed - let should_reload =if config_path.exists() { + let should_reload = if config_path.exists() { // Get the last modified date, so that we can detect if the user actually edits the file // before reloading let metadata = std::fs::metadata(&config_path).expect("cannot gather file metadata"); - let last_modified = metadata.modified().expect("cannot read file last modified date"); + let last_modified = metadata + .modified() + .expect("cannot read file last modified date"); let result = crate::edit::open_editor(&config_path); if result { - let new_metadata = std::fs::metadata(&config_path).expect("cannot gather file metadata"); - let new_last_modified = new_metadata.modified().expect("cannot read file last modified date"); + let new_metadata = + std::fs::metadata(&config_path).expect("cannot gather file metadata"); + let new_last_modified = new_metadata + .modified() + .expect("cannot read file last modified date"); if last_modified != new_last_modified { println!("File has been modified, reloading configuration"); true - }else{ + } else { println!("File has not been modified, avoiding reload"); false } - }else{ + } else { false } - }else{ + } else { let result = crate::edit::open_editor(&config_path); if result { // If the file has been created, we should reload the espanso config if config_path.exists() { println!("A new file has been created, reloading configuration"); true - }else{ + } else { println!("No file has been created, avoiding reload"); false } - }else{ + } else { false } }; @@ -1170,11 +1236,11 @@ fn edit_main(matches: &ArgMatches) { let no_restart: bool = if matches.is_present("norestart") { println!("Avoiding automatic restart"); true - }else{ + } else { false }; - if should_reload && !no_restart{ + if should_reload && !no_restart { // Load the configuration let config_set = ConfigSet::load_default().unwrap_or_else(|e| { eprintln!("{}", e); @@ -1203,7 +1269,7 @@ fn acquire_custom_lock(name: &str) -> Option { let res = file.try_lock_exclusive(); if res.is_ok() { - return Some(file) + return Some(file); } None @@ -1221,4 +1287,4 @@ fn precheck_guard() { println!("Pre-check was not successful, espanso could not be started."); exit(5); } -} \ No newline at end of file +} diff --git a/src/matcher/mod.rs b/src/matcher/mod.rs index 84f3dbc..6914f97 100644 --- a/src/matcher/mod.rs +++ b/src/matcher/mod.rs @@ -17,13 +17,13 @@ * along with espanso. If not, see . */ -use serde::{Serialize, Deserialize, Deserializer}; -use crate::event::{KeyEvent, KeyModifier}; use crate::event::KeyEventReceiver; -use serde_yaml::Mapping; +use crate::event::{KeyEvent, KeyModifier}; use regex::Regex; -use std::path::PathBuf; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_yaml::Mapping; use std::fs; +use std::path::PathBuf; pub(crate) mod scrolling; @@ -61,16 +61,17 @@ pub struct ImageContent { pub path: PathBuf, } -impl <'de> serde::Deserialize<'de> for Match { - fn deserialize(deserializer: D) -> Result where - D: Deserializer<'de> { - +impl<'de> serde::Deserialize<'de> for Match { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { let auto_match = AutoMatch::deserialize(deserializer)?; Ok(Match::from(&auto_match)) } } -impl<'a> From<&'a AutoMatch> for Match{ +impl<'a> From<&'a AutoMatch> for Match { fn from(other: &'a AutoMatch) -> Self { lazy_static! { static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(\\w+)\\s*\\}\\}").unwrap(); @@ -78,9 +79,9 @@ impl<'a> From<&'a AutoMatch> for Match{ let mut triggers = if !other.triggers.is_empty() { other.triggers.clone() - }else if !other.trigger.is_empty() { - vec!(other.trigger.clone()) - }else{ + } else if !other.trigger.is_empty() { + vec![other.trigger.clone()] + } else { panic!("Match does not have any trigger defined: {:?}", other) }; @@ -89,44 +90,48 @@ impl<'a> From<&'a AutoMatch> for Match{ // "hello", "Hello", "HELLO" if other.propagate_case { // List with first letter capitalized - let first_capitalized : Vec = triggers.iter().map(|trigger| { - let capitalized = trigger.clone(); - let mut v: Vec = capitalized.chars().collect(); + let first_capitalized: Vec = triggers + .iter() + .map(|trigger| { + let capitalized = trigger.clone(); + let mut v: Vec = capitalized.chars().collect(); - // Capitalize the first alphabetic letter - // See issue #244 - let first_alphabetic = v.iter().position(|c| { - c.is_alphabetic() - }).unwrap_or(0); + // Capitalize the first alphabetic letter + // See issue #244 + let first_alphabetic = v.iter().position(|c| c.is_alphabetic()).unwrap_or(0); - v[first_alphabetic] = v[first_alphabetic].to_uppercase().nth(0).unwrap(); - v.into_iter().collect() - }).collect(); + v[first_alphabetic] = v[first_alphabetic].to_uppercase().nth(0).unwrap(); + v.into_iter().collect() + }) + .collect(); - let all_capitalized : Vec = triggers.iter().map(|trigger| { - trigger.to_uppercase() - }).collect(); + let all_capitalized: Vec = triggers + .iter() + .map(|trigger| trigger.to_uppercase()) + .collect(); triggers.extend(first_capitalized); triggers.extend(all_capitalized); } - let trigger_sequences = triggers.iter().map(|trigger| { - // Calculate the trigger sequence - let mut trigger_sequence = Vec::new(); - let trigger_chars : Vec = trigger.chars().collect(); - trigger_sequence.extend(trigger_chars.into_iter().map(|c| { - TriggerEntry::Char(c) - })); - if other.word { // If it's a word match, end with a word separator - trigger_sequence.push(TriggerEntry::WordSeparator); - } + let trigger_sequences = triggers + .iter() + .map(|trigger| { + // Calculate the trigger sequence + let mut trigger_sequence = Vec::new(); + let trigger_chars: Vec = trigger.chars().collect(); + trigger_sequence.extend(trigger_chars.into_iter().map(|c| TriggerEntry::Char(c))); + if other.word { + // If it's a word match, end with a word separator + trigger_sequence.push(TriggerEntry::WordSeparator); + } - trigger_sequence - }).collect(); + trigger_sequence + }) + .collect(); - - 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(); // Check if the match contains variables @@ -139,11 +144,12 @@ impl<'a> From<&'a AutoMatch> for Match{ }; 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 let new_path = if cfg!(target_os = "windows") { image_path.replace("/", "\\") - }else{ + } else { image_path.to_owned() }; @@ -153,20 +159,20 @@ impl<'a> From<&'a AutoMatch> for Match{ let config_path = fs::canonicalize(&config_dir); let config_path = if let Ok(config_path) = config_path { config_path.to_string_lossy().into_owned() - }else{ + } else { "".to_owned() }; new_path.replace("$CONFIG", &config_path) - }else{ + } else { new_path.to_owned() }; let content = ImageContent { - path: PathBuf::from(new_path) + path: PathBuf::from(new_path), }; MatchContentType::Image(content) - }else { + } else { eprintln!("ERROR: no action specified for match {}, please specify either 'replace' or 'image_path'", other.trigger); std::process::exit(2); }; @@ -214,15 +220,33 @@ struct AutoMatch { pub force_clipboard: bool, } -fn default_trigger() -> String {"".to_owned()} -fn default_triggers() -> Vec {Vec::new()} -fn default_vars() -> Vec {Vec::new()} -fn default_word() -> bool {false} -fn default_passive_only() -> bool {false} -fn default_replace() -> Option {None} -fn default_image_path() -> Option {None} -fn default_propagate_case() -> bool {false} -fn default_force_clipboard() -> bool {false} +fn default_trigger() -> String { + "".to_owned() +} +fn default_triggers() -> Vec { + Vec::new() +} +fn default_vars() -> Vec { + Vec::new() +} +fn default_word() -> bool { + false +} +fn default_passive_only() -> bool { + false +} +fn default_replace() -> Option { + None +} +fn default_image_path() -> Option { + None +} +fn default_propagate_case() -> bool { + false +} +fn default_force_clipboard() -> bool { + false +} #[derive(Debug, Serialize, Deserialize, Clone)] pub struct MatchVariable { @@ -235,12 +259,14 @@ pub struct MatchVariable { pub params: Mapping, } -fn default_params() -> Mapping {Mapping::new()} +fn default_params() -> Mapping { + Mapping::new() +} #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub enum TriggerEntry { Char(char), - WordSeparator + WordSeparator, } pub trait MatchReceiver { @@ -249,29 +275,28 @@ pub trait MatchReceiver { fn on_passive(&self); } -pub trait Matcher : KeyEventReceiver { +pub trait Matcher: KeyEventReceiver { fn handle_char(&self, c: &str); fn handle_modifier(&self, m: KeyModifier); fn handle_other(&self); } -impl KeyEventReceiver for M { +impl KeyEventReceiver for M { fn on_key_event(&self, e: KeyEvent) { match e { KeyEvent::Char(c) => { self.handle_char(&c); - }, + } KeyEvent::Modifier(m) => { self.handle_modifier(m); - }, + } KeyEvent::Other => { self.handle_other(); - }, + } } } } - // TESTS #[cfg(test)] @@ -285,15 +310,15 @@ mod tests { replace: "There are no variables" "###; - let _match : Match = serde_yaml::from_str(match_str).unwrap(); + let _match: Match = serde_yaml::from_str(match_str).unwrap(); match _match.content { MatchContentType::Text(content) => { assert_eq!(content._has_vars, false); - }, + } _ => { assert!(false); - }, + } } } @@ -304,15 +329,15 @@ mod tests { replace: "There are {{one}} and {{two}} variables" "###; - let _match : Match = serde_yaml::from_str(match_str).unwrap(); + let _match: Match = serde_yaml::from_str(match_str).unwrap(); match _match.content { MatchContentType::Text(content) => { assert_eq!(content._has_vars, true); - }, + } _ => { assert!(false); - }, + } } } @@ -323,15 +348,15 @@ mod tests { replace: "There is {{ one }} variable" "###; - let _match : Match = serde_yaml::from_str(match_str).unwrap(); + let _match: Match = serde_yaml::from_str(match_str).unwrap(); match _match.content { MatchContentType::Text(content) => { assert_eq!(content._has_vars, true); - }, + } _ => { assert!(false); - }, + } } } @@ -342,7 +367,7 @@ mod tests { replace: "This is a test" "###; - let _match : Match = serde_yaml::from_str(match_str).unwrap(); + let _match: Match = serde_yaml::from_str(match_str).unwrap(); assert_eq!(_match._trigger_sequences[0][0], TriggerEntry::Char('t')); assert_eq!(_match._trigger_sequences[0][1], TriggerEntry::Char('e')); @@ -358,7 +383,7 @@ mod tests { word: true "###; - let _match : Match = serde_yaml::from_str(match_str).unwrap(); + let _match: Match = serde_yaml::from_str(match_str).unwrap(); assert_eq!(_match._trigger_sequences[0][0], TriggerEntry::Char('t')); assert_eq!(_match._trigger_sequences[0][1], TriggerEntry::Char('e')); @@ -374,15 +399,15 @@ mod tests { image_path: "/path/to/file" "###; - let _match : Match = serde_yaml::from_str(match_str).unwrap(); + let _match: Match = serde_yaml::from_str(match_str).unwrap(); match _match.content { MatchContentType::Image(content) => { assert_eq!(content.path, PathBuf::from("/path/to/file")); - }, + } _ => { assert!(false); - }, + } } } @@ -393,7 +418,7 @@ mod tests { replace: "This is a test" "###; - let _match : Match = serde_yaml::from_str(match_str).unwrap(); + let _match: Match = serde_yaml::from_str(match_str).unwrap(); assert_eq!(_match.triggers, vec![":test"]) } @@ -407,7 +432,7 @@ mod tests { replace: "This is a test" "###; - let _match : Match = serde_yaml::from_str(match_str).unwrap(); + let _match: Match = serde_yaml::from_str(match_str).unwrap(); assert_eq!(_match.triggers, vec![":test1", ":test2"]) } @@ -419,7 +444,7 @@ mod tests { replace: "This is a test" "###; - let _match : Match = serde_yaml::from_str(match_str).unwrap(); + let _match: Match = serde_yaml::from_str(match_str).unwrap(); assert_eq!(_match.triggers, vec![":test1", ":test2"]) } @@ -432,7 +457,7 @@ mod tests { propagate_case: true "###; - let _match : Match = serde_yaml::from_str(match_str).unwrap(); + let _match: Match = serde_yaml::from_str(match_str).unwrap(); assert_eq!(_match.triggers, vec!["hello", "Hello", "HELLO"]) } @@ -445,9 +470,12 @@ mod tests { propagate_case: true "###; - let _match : Match = serde_yaml::from_str(match_str).unwrap(); + let _match: Match = serde_yaml::from_str(match_str).unwrap(); - assert_eq!(_match.triggers, vec!["hello", "hi", "Hello", "Hi", "HELLO", "HI"]) + assert_eq!( + _match.triggers, + vec!["hello", "hi", "Hello", "Hi", "HELLO", "HI"] + ) } #[test] @@ -459,7 +487,7 @@ mod tests { propagate_case: true "###; - let _match : Match = serde_yaml::from_str(match_str).unwrap(); + let _match: Match = serde_yaml::from_str(match_str).unwrap(); assert_eq!(_match._trigger_sequences[0][0], TriggerEntry::Char('t')); assert_eq!(_match._trigger_sequences[0][1], TriggerEntry::Char('e')); @@ -487,7 +515,7 @@ mod tests { replace: "" "###; - let _match : Match = serde_yaml::from_str(match_str).unwrap(); + let _match: Match = serde_yaml::from_str(match_str).unwrap(); } #[test] @@ -498,7 +526,7 @@ mod tests { propagate_case: true "###; - let _match : Match = serde_yaml::from_str(match_str).unwrap(); + let _match: Match = serde_yaml::from_str(match_str).unwrap(); assert_eq!(_match.triggers, vec![":hello", ":Hello", ":HELLO"]) } @@ -511,8 +539,8 @@ mod tests { propagate_case: true "###; - let _match : Match = serde_yaml::from_str(match_str).unwrap(); + let _match: Match = serde_yaml::from_str(match_str).unwrap(); assert_eq!(_match.triggers, vec![":..", ":..", ":.."]) } -} \ No newline at end of file +} diff --git a/src/matcher/scrolling.rs b/src/matcher/scrolling.rs index 1916b9c..0dd38a4 100644 --- a/src/matcher/scrolling.rs +++ b/src/matcher/scrolling.rs @@ -17,13 +17,13 @@ * along with espanso. If not, see . */ -use crate::matcher::{Match, MatchReceiver, TriggerEntry}; -use std::cell::{RefCell}; -use crate::event::{KeyModifier, ActionEventReceiver, ActionType}; use crate::config::ConfigManager; -use crate::event::KeyModifier::{BACKSPACE, LEFT_SHIFT, RIGHT_SHIFT, CAPS_LOCK}; -use std::time::SystemTime; +use crate::event::KeyModifier::{BACKSPACE, CAPS_LOCK, LEFT_SHIFT, RIGHT_SHIFT}; +use crate::event::{ActionEventReceiver, ActionType, KeyModifier}; +use crate::matcher::{Match, MatchReceiver, TriggerEntry}; +use std::cell::RefCell; use std::collections::VecDeque; +use std::time::SystemTime; pub struct ScrollingMatcher<'a, R: MatchReceiver, M: ConfigManager<'a>> { config_manager: &'a M, @@ -40,16 +40,16 @@ struct MatchEntry<'a> { start: usize, count: usize, trigger_offset: usize, // The index of the trigger in the Match that matched - _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> { pub fn new(config_manager: &'a M, receiver: &'a R) -> ScrollingMatcher<'a, R, M> { let current_set_queue = RefCell::new(VecDeque::new()); let toggle_press_time = RefCell::new(SystemTime::now()); let passive_press_time = RefCell::new(SystemTime::now()); - ScrollingMatcher{ + ScrollingMatcher { config_manager, receiver, current_set_queue, @@ -74,19 +74,21 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> { self.receiver.on_enable_update(*is_enabled); } - fn is_matching(mtc: &Match, current_char: &str, start: usize, trigger_offset: usize, is_current_word_separator: bool) -> bool { + fn is_matching( + mtc: &Match, + current_char: &str, + start: usize, + trigger_offset: usize, + is_current_word_separator: bool, + ) -> bool { match mtc._trigger_sequences[trigger_offset][start] { - TriggerEntry::Char(c) => { - current_char.starts_with(c) - }, - TriggerEntry::WordSeparator => { - is_current_word_separator - }, + TriggerEntry::Char(c) => current_char.starts_with(c), + TriggerEntry::WordSeparator => is_current_word_separator, } } } -impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMatcher<'a, R, M> { +impl<'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMatcher<'a, R, M> { fn handle_char(&self, c: &str) { // if not enabled, avoid any processing if !*(self.is_enabled.borrow()) { @@ -98,9 +100,9 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa let active_config = self.config_manager.active_config(); // Check if the current char is a word separator - let mut is_current_word_separator = active_config.word_separators.contains( - &c.chars().nth(0).unwrap_or_default() - ); + let mut is_current_word_separator = active_config + .word_separators + .contains(&c.chars().nth(0).unwrap_or_default()); // Workaround needed on macos to consider espanso replacement key presses as separators. if cfg!(target_os = "macos") { @@ -118,22 +120,23 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa for m in active_config.matches.iter() { // only active-enabled matches are considered if m.passive_only { - continue + continue; } for trigger_offset in 0..m._trigger_sequences.len() { - let mut result = Self::is_matching(m, c, 0, trigger_offset, is_current_word_separator); + let mut result = + Self::is_matching(m, c, 0, trigger_offset, is_current_word_separator); if m.word { result = result && *was_previous_word_separator } if result { - new_matches.push(MatchEntry{ + new_matches.push(MatchEntry { start: 1, count: m._trigger_sequences[trigger_offset].len(), trigger_offset, - _match: &m + _match: &m, }); } } @@ -142,22 +145,29 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa let combined_matches: Vec = match current_set_queue.back_mut() { Some(last_matches) => { - let mut updated: Vec = last_matches.iter() + let mut updated: Vec = last_matches + .iter() .filter(|&x| { - Self::is_matching(x._match, c, x.start, x.trigger_offset, is_current_word_separator) + Self::is_matching( + x._match, + c, + x.start, + x.trigger_offset, + is_current_word_separator, + ) }) - .map(|x | MatchEntry{ - start: x.start+1, + .map(|x| MatchEntry { + start: x.start + 1, count: x.count, trigger_offset: x.trigger_offset, - _match: &x._match + _match: &x._match, }) .collect(); updated.extend(new_matches); updated - }, - None => {new_matches}, + } + None => new_matches, }; let mut found_entry = None; @@ -171,7 +181,9 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa 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(); } @@ -189,20 +201,21 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa None } else if !is_current_word_separator { None - }else{ + } else { let as_char = c.chars().nth(0); match as_char { Some(c) => { Some(c) // Current char is the trailing separator - }, - None => {None}, + } + None => None, } }; // Force espanso to consider the last char as a separator *was_previous_word_separator = true; - self.receiver.on_match(mtc, trailing_separator, entry.trigger_offset); + self.receiver + .on_match(mtc, trailing_separator, entry.trigger_offset); } } @@ -212,22 +225,28 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa // TODO: at the moment, activating the passive key triggers the toggle key // study a mechanism to avoid this problem - if KeyModifier::shallow_equals(&m, &config.toggle_key) { - check_interval(&self.toggle_press_time, - u128::from(config.toggle_interval), || { - self.toggle(); + if KeyModifier::shallow_equals(&m, &config.toggle_key) { + check_interval( + &self.toggle_press_time, + u128::from(config.toggle_interval), + || { + self.toggle(); - let is_enabled = self.is_enabled.borrow(); + let is_enabled = self.is_enabled.borrow(); - if !*is_enabled { - self.current_set_queue.borrow_mut().clear(); - } - }); - }else if KeyModifier::shallow_equals(&m, &config.passive_key) { - check_interval(&self.passive_press_time, - u128::from(config.toggle_interval), || { - self.receiver.on_passive(); - }); + if !*is_enabled { + self.current_set_queue.borrow_mut().clear(); + } + }, + ); + } else if KeyModifier::shallow_equals(&m, &config.passive_key) { + check_interval( + &self.passive_press_time, + u128::from(config.toggle_interval), + || { + self.receiver.on_passive(); + }, + ); } // Backspace handling, basically "rewinding history" @@ -238,7 +257,8 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa // Consider modifiers as separators to improve word matches reliability if m != LEFT_SHIFT && m != RIGHT_SHIFT && m != CAPS_LOCK { - let mut was_previous_char_word_separator = self.was_previous_char_word_separator.borrow_mut(); + let mut was_previous_char_word_separator = + self.was_previous_char_word_separator.borrow_mut(); *was_previous_char_word_separator = true; } } @@ -246,29 +266,35 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa fn handle_other(&self) { // When receiving "other" type of events, we mark them as valid separators. // This dramatically improves the reliability of word matches - let mut was_previous_char_word_separator = self.was_previous_char_word_separator.borrow_mut(); + let mut was_previous_char_word_separator = + self.was_previous_char_word_separator.borrow_mut(); *was_previous_char_word_separator = true; } } -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) { match e { ActionType::Toggle => { self.toggle(); - }, + } ActionType::Enable => { self.set_enabled(true); - }, + } ActionType::Disable => { self.set_enabled(false); - }, + } _ => {} } } } -fn check_interval(state_var: &RefCell, interval: u128, elapsed_callback: F) where F:Fn() { +fn check_interval(state_var: &RefCell, interval: u128, elapsed_callback: F) +where + F: Fn(), +{ let mut press_time = state_var.borrow_mut(); if let Ok(elapsed) = press_time.elapsed() { if elapsed.as_millis() < interval { @@ -277,4 +303,4 @@ fn check_interval(state_var: &RefCell, interval: u128, elapsed_ca } (*press_time) = SystemTime::now(); -} \ No newline at end of file +} diff --git a/src/package/default.rs b/src/package/default.rs index ccf8d32..e2cc3c9 100644 --- a/src/package/default.rs +++ b/src/package/default.rs @@ -17,20 +17,22 @@ * along with espanso. If not, see . */ -use std::path::{PathBuf, Path}; -use crate::package::{PackageIndex, UpdateResult, Package, InstallResult, RemoveResult, PackageResolver}; -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::InstallResult::{NotFoundInIndex, AlreadyInstalled, BlockedExternalPackage}; -use std::fs; -use regex::Regex; +use crate::package::InstallResult::{AlreadyInstalled, BlockedExternalPackage, NotFoundInIndex}; use crate::package::RemoveResult::Removed; +use crate::package::UpdateResult::{NotOutdated, Updated}; +use crate::package::{ + InstallResult, Package, PackageIndex, PackageResolver, RemoveResult, UpdateResult, +}; +use regex::Regex; 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}; -const DEFAULT_PACKAGE_INDEX_FILE : &str = "package_index.json"; +const DEFAULT_PACKAGE_INDEX_FILE: &str = "package_index.json"; pub struct DefaultPackageManager { package_dir: PathBuf, @@ -42,18 +44,24 @@ pub struct DefaultPackageManager { } impl DefaultPackageManager { - pub fn new(package_dir: PathBuf, data_dir: PathBuf, package_resolver: Option>) -> DefaultPackageManager { + pub fn new( + package_dir: PathBuf, + data_dir: PathBuf, + package_resolver: Option>, + ) -> DefaultPackageManager { let local_index = Self::load_local_index(&data_dir); - DefaultPackageManager{ + DefaultPackageManager { package_dir, data_dir, package_resolver, - local_index + local_index, } } - pub fn new_default(package_resolver: Option>) -> DefaultPackageManager { + pub fn new_default( + package_resolver: Option>, + ) -> DefaultPackageManager { DefaultPackageManager::new( crate::context::get_package_dir(), crate::context::get_data_dir(), @@ -72,7 +80,7 @@ impl DefaultPackageManager { let local_index = serde_json::from_reader(reader); if let Ok(local_index) = local_index { - return local_index + return local_index; } } @@ -81,19 +89,21 @@ impl DefaultPackageManager { fn request_index() -> Result> { 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)); let mut res = request.send()?; let body = res.text()?; - let index : PackageIndex = serde_json::from_str(&body)?; + let index: PackageIndex = serde_json::from_str(&body)?; Ok(index) } fn parse_package_from_readme(readme_path: &Path) -> Option { 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 @@ -101,7 +111,7 @@ impl DefaultPackageManager { if let Ok(file) = file { let reader = BufReader::new(file); - let mut fields :HashMap = HashMap::new(); + let mut fields: HashMap = HashMap::new(); let mut started = false; @@ -109,35 +119,38 @@ impl DefaultPackageManager { let line = line.unwrap(); if line.contains("---") { if started { - break - }else{ + break; + } else { started = true; } - }else if started { + } else if started { let caps = FIELD_REGEX.captures(&line); if let Some(caps) = caps { let property = caps.get(1); let value = caps.get(2); if property.is_some() && value.is_some() { - fields.insert(property.unwrap().as_str().to_owned(), - value.unwrap().as_str().to_owned()); + fields.insert( + property.unwrap().as_str().to_owned(), + value.unwrap().as_str().to_owned(), + ); } } } } - if !fields.contains_key("package_name") || - !fields.contains_key("package_title") || - !fields.contains_key("package_version") || - !fields.contains_key("package_repo") || - !fields.contains_key("package_desc") || - !fields.contains_key("package_author") { - return None + if !fields.contains_key("package_name") + || !fields.contains_key("package_title") + || !fields.contains_key("package_version") + || !fields.contains_key("package_repo") + || !fields.contains_key("package_desc") + || !fields.contains_key("package_author") + { + return None; } let original_repo = if fields.contains_key("package_original_repo") { fields.get("package_original_repo").unwrap().clone() - }else{ + } else { fields.get("package_repo").unwrap().clone() }; @@ -147,7 +160,7 @@ impl DefaultPackageManager { "false" => false, _ => false, } - }else{ + } else { false }; @@ -159,18 +172,18 @@ impl DefaultPackageManager { desc: fields.get("package_desc").unwrap().clone(), author: fields.get("package_author").unwrap().clone(), is_core, - original_repo + original_repo, }; Some(package) - }else{ + } else { None } } fn local_index_timestamp(&self) -> u64 { if let Some(local_index) = &self.local_index { - return local_index.last_update + return local_index.last_update; } 0 @@ -198,7 +211,8 @@ impl DefaultPackageManager { fn cache_local_index(&self) { 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); std::fs::write(local_index_file, serialized).expect("Unable to cache local index"); } @@ -207,13 +221,15 @@ impl DefaultPackageManager { impl super::PackageManager for DefaultPackageManager { 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 local_index_timestamp = self.local_index_timestamp(); // Local index is outdated if older than a day - local_index_timestamp + 60*60*24 < current_timestamp + local_index_timestamp + 60 * 60 * 24 < current_timestamp } fn update_index(&mut self, force: bool) -> Result> { @@ -225,48 +241,60 @@ impl super::PackageManager for DefaultPackageManager { self.cache_local_index(); Ok(Updated) - }else{ + } else { Ok(NotOutdated) } } fn get_package(&self, name: &str) -> Option { if let Some(local_index) = &self.local_index { - let result = local_index.packages.iter().find(|package| { - package.name == name - }); + let result = local_index + .packages + .iter() + .find(|package| package.name == name); if let Some(package) = result { - return Some(package.clone()) + return Some(package.clone()); } } None } - fn install_package(&self, name: &str, allow_external: bool) -> Result> { + fn install_package( + &self, + name: &str, + allow_external: bool, + ) -> Result> { let package = self.get_package(name); match package { Some(package) => { if package.is_core || allow_external { self.install_package_from_repo(name, &package.repo) - }else{ + } else { Ok(BlockedExternalPackage(package.original_repo)) } - }, - None => { - Ok(NotFoundInIndex) - }, + } + None => Ok(NotFoundInIndex), } } - fn install_package_from_repo(&self, name: &str, repo_url: &str) -> Result> { + fn install_package_from_repo( + &self, + name: &str, + repo_url: &str, + ) -> Result> { // Check if package is already installed 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); } - let temp_dir = self.package_resolver.as_ref().unwrap().clone_repo_to_temp(repo_url)?; + let temp_dir = self + .package_resolver + .as_ref() + .unwrap() + .clone_repo_to_temp(repo_url)?; let temp_package_dir = temp_dir.path().join(name); if !temp_package_dir.exists() { @@ -329,15 +357,16 @@ impl super::PackageManager for DefaultPackageManager { #[cfg(test)] mod tests { use super::*; - use tempfile::{TempDir, NamedTempFile}; - use std::path::Path; + use crate::package::zip::ZipPackageResolver; + use crate::package::InstallResult::*; use crate::package::PackageManager; use std::fs::{create_dir, create_dir_all}; - use crate::package::InstallResult::*; - use crate::package::zip::ZipPackageResolver; + use std::path::Path; + use tempfile::{NamedTempFile, TempDir}; - 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 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 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"); @@ -347,7 +376,10 @@ mod tests { package_manager: DefaultPackageManager, } - fn create_temp_package_manager(setup: F) -> TempPackageManager where F: Fn(&Path, &Path) -> (){ + fn create_temp_package_manager(setup: F) -> TempPackageManager + where + F: Fn(&Path, &Path) -> (), + { let package_dir = TempDir::new().expect("unable to create temp directory"); let data_dir = TempDir::new().expect("unable to create temp directory"); @@ -362,7 +394,7 @@ mod tests { TempPackageManager { package_dir, data_dir, - package_manager + package_manager, } } @@ -389,26 +421,38 @@ mod tests { fn test_up_to_date_index_should_not_be_updated() { let mut temp = create_temp_package_manager(|_, data_dir| { 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 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).unwrap(); }); - assert_eq!(temp.package_manager.update_index(false).unwrap(), UpdateResult::NotOutdated); + assert_eq!( + temp.package_manager.update_index(false).unwrap(), + UpdateResult::NotOutdated + ); } #[test] fn test_up_to_date_index_with_force_should_be_updated() { let mut temp = create_temp_package_manager(|_, data_dir| { 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 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).unwrap(); }); - assert_eq!(temp.package_manager.update_index(true).unwrap(), UpdateResult::Updated); + assert_eq!( + temp.package_manager.update_index(true).unwrap(), + UpdateResult::Updated + ); } #[test] @@ -418,15 +462,25 @@ mod tests { std::fs::write(index_file, OUTDATED_INDEX_CONTENT).unwrap(); }); - assert_eq!(temp.package_manager.update_index(false).unwrap(), UpdateResult::Updated); + assert_eq!( + temp.package_manager.update_index(false).unwrap(), + UpdateResult::Updated + ); } #[test] fn test_update_index_should_create_file() { let mut temp = create_temp_package_manager(|_, _| {}); - assert_eq!(temp.package_manager.update_index(false).unwrap(), UpdateResult::Updated); - assert!(temp.data_dir.path().join(DEFAULT_PACKAGE_INDEX_FILE).exists()) + assert_eq!( + temp.package_manager.update_index(false).unwrap(), + UpdateResult::Updated + ); + assert!(temp + .data_dir + .path() + .join(DEFAULT_PACKAGE_INDEX_FILE) + .exists()) } #[test] @@ -436,7 +490,13 @@ mod tests { std::fs::write(index_file, GET_PACKAGE_INDEX).unwrap(); }); - 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] @@ -470,7 +530,12 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("doesnotexist", false).unwrap(), NotFoundInIndex); + assert_eq!( + temp.package_manager + .install_package("doesnotexist", false) + .unwrap(), + NotFoundInIndex + ); } #[test] @@ -481,7 +546,12 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("italian-accents", false).unwrap(), AlreadyInstalled); + assert_eq!( + temp.package_manager + .install_package("italian-accents", false) + .unwrap(), + AlreadyInstalled + ); } #[test] @@ -491,10 +561,23 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("dummy-package", false).unwrap(), Installed); + assert_eq!( + temp.package_manager + .install_package("dummy-package", false) + .unwrap(), + Installed + ); assert!(temp.package_dir.path().join("dummy-package").exists()); - assert!(temp.package_dir.path().join("dummy-package/README.md").exists()); - assert!(temp.package_dir.path().join("dummy-package/package.yml").exists()); + assert!(temp + .package_dir + .path() + .join("dummy-package/README.md") + .exists()); + assert!(temp + .package_dir + .path() + .join("dummy-package/package.yml") + .exists()); } #[test] @@ -504,7 +587,12 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("not-existing", false).unwrap(), NotFoundInRepo); + assert_eq!( + temp.package_manager + .install_package("not-existing", false) + .unwrap(), + NotFoundInRepo + ); } #[test] @@ -514,7 +602,12 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("dummy-package2", false).unwrap(), MissingPackageVersion); + assert_eq!( + temp.package_manager + .install_package("dummy-package2", false) + .unwrap(), + MissingPackageVersion + ); } #[test] @@ -524,7 +617,12 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("dummy-package3", false).unwrap(), UnableToParsePackageInfo); + assert_eq!( + temp.package_manager + .install_package("dummy-package3", false) + .unwrap(), + UnableToParsePackageInfo + ); } #[test] @@ -534,7 +632,12 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("dummy-package4", false).unwrap(), UnableToParsePackageInfo); + assert_eq!( + temp.package_manager + .install_package("dummy-package4", false) + .unwrap(), + UnableToParsePackageInfo + ); } #[test] @@ -544,10 +647,23 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX).unwrap(); }); - assert_eq!(temp.package_manager.install_package("dummy-package", false).unwrap(), Installed); + assert_eq!( + temp.package_manager + .install_package("dummy-package", false) + .unwrap(), + Installed + ); assert!(temp.package_dir.path().join("dummy-package").exists()); - assert!(temp.package_dir.path().join("dummy-package/README.md").exists()); - assert!(temp.package_dir.path().join("dummy-package/package.yml").exists()); + assert!(temp + .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(); assert_eq!(list.len(), 1); @@ -564,25 +680,51 @@ mod tests { }); assert!(temp.package_dir.path().join("dummy-package").exists()); - assert!(temp.package_dir.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/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/README.md").exists()); - assert!(!temp.package_dir.path().join("dummy-package/package.yml").exists()); + assert!(!temp + .package_dir + .path() + .join("dummy-package/README.md") + .exists()); + assert!(!temp + .package_dir + .path() + .join("dummy-package/package.yml") + .exists()); } #[test] fn test_remove_package_not_found() { let 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] fn test_parse_package_from_readme() { let file = NamedTempFile::new().unwrap(); - fs::write(file.path(), r###" + fs::write( + file.path(), + r###" --- package_name: "italian-accents" package_title: "Italian Accents" @@ -592,7 +734,9 @@ mod tests { package_repo: "https://github.com/federico-terzi/espanso-hub-core" is_core: true --- - "###).unwrap(); + "###, + ) + .unwrap(); let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap(); @@ -613,7 +757,9 @@ mod tests { #[test] fn test_parse_package_from_readme_with_bad_metadata() { let file = NamedTempFile::new().unwrap(); - fs::write(file.path(), r###" + fs::write( + file.path(), + r###" --- package_name: italian-accents package_title: "Italian Accents" @@ -624,7 +770,9 @@ mod tests { is_core: true --- Readme text - "###).unwrap(); + "###, + ) + .unwrap(); let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap(); @@ -641,4 +789,4 @@ mod tests { assert_eq!(package, target_package); } -} \ No newline at end of file +} diff --git a/src/package/mod.rs b/src/package/mod.rs index 8942c96..1d99210 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -17,10 +17,10 @@ * along with espanso. If not, see . */ -pub(crate) mod zip; pub(crate) mod default; +pub(crate) mod zip; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use std::error::Error; use tempfile::TempDir; @@ -30,8 +30,16 @@ pub trait PackageManager { fn get_package(&self, name: &str) -> Option; - fn install_package(&self, name: &str, allow_external: bool) -> Result>; - fn install_package_from_repo(&self, name: &str, repo_url: &str) -> Result>; + fn install_package( + &self, + name: &str, + allow_external: bool, + ) -> Result>; + fn install_package_from_repo( + &self, + name: &str, + repo_url: &str, + ) -> Result>; fn remove_package(&self, name: &str) -> Result>; @@ -57,18 +65,21 @@ pub struct Package { pub original_repo: String, } -fn default_is_core() -> bool {false} -fn default_original_repo() -> String {"".to_owned()} +fn default_is_core() -> bool { + false +} +fn default_original_repo() -> String { + "".to_owned() +} #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct PackageIndex { #[serde(rename = "lastUpdate")] pub last_update: u64, - pub packages: Vec + pub packages: Vec, } - #[derive(Clone, Debug, PartialEq)] pub enum UpdateResult { NotOutdated, @@ -83,11 +94,11 @@ pub enum InstallResult { MissingPackageVersion, AlreadyInstalled, Installed, - BlockedExternalPackage(String) + BlockedExternalPackage(String), } #[derive(Clone, Debug, PartialEq)] pub enum RemoveResult { NotFound, - Removed -} \ No newline at end of file + Removed, +} diff --git a/src/package/zip.rs b/src/package/zip.rs index e12b26d..52fcf9d 100644 --- a/src/package/zip.rs +++ b/src/package/zip.rs @@ -1,14 +1,14 @@ -use tempfile::TempDir; -use std::error::Error; -use std::io::{Cursor, copy}; -use std::{fs, io}; use log::debug; +use std::error::Error; +use std::io::{copy, Cursor}; +use std::{fs, io}; +use tempfile::TempDir; pub struct ZipPackageResolver; impl ZipPackageResolver { pub fn new() -> ZipPackageResolver { - return ZipPackageResolver{}; + return ZipPackageResolver {}; } } @@ -54,10 +54,19 @@ impl super::PackageResolver for ZipPackageResolver { } 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(); } 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 !p.exists() { fs::create_dir_all(&p).unwrap(); @@ -74,13 +83,15 @@ impl super::PackageResolver for ZipPackageResolver { #[cfg(test)] mod tests { - use super::*; use super::super::PackageResolver; + use super::*; #[test] fn test_clone_temp_repository() { let resolver = ZipPackageResolver::new(); - let cloned_dir = resolver.clone_repo_to_temp("https://github.com/federico-terzi/espanso-hub-core").unwrap(); + let cloned_dir = resolver + .clone_repo_to_temp("https://github.com/federico-terzi/espanso-hub-core") + .unwrap(); assert!(cloned_dir.path().join("LICENSE").exists()); } -} \ No newline at end of file +} diff --git a/src/process.rs b/src/process.rs index 544bbd4..fdf7930 100644 --- a/src/process.rs +++ b/src/process.rs @@ -22,9 +22,7 @@ use widestring::WideCString; #[cfg(target_os = "windows")] pub fn spawn_process(cmd: &str, args: &Vec) { - let quoted_args: Vec = args.iter().map(|arg| { - format!("\"{}\"", arg) - }).collect(); + let quoted_args: Vec = args.iter().map(|arg| format!("\"{}\"", arg)).collect(); let quoted_args = quoted_args.join(" "); let final_cmd = format!("\"{}\" {}", cmd, quoted_args); unsafe { @@ -34,7 +32,7 @@ pub fn spawn_process(cmd: &str, args: &Vec) { if res < 0 { warn!("unable to start process: {}", final_cmd); } - }else{ + } else { warn!("unable to convert process string into wide format") } } diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 60dc784..dae371b 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -17,14 +17,14 @@ * along with espanso. If not, see . */ -use serde::{Deserialize, Serialize}; -use std::sync::mpsc::Sender; -use crate::event::{Event, SystemEvent}; -use crate::event::ActionType; -use std::io::{BufReader, Read, Write}; -use std::error::Error; -use log::error; use crate::config::Configs; +use crate::event::ActionType; +use crate::event::{Event, SystemEvent}; +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")] mod windows; @@ -47,7 +47,6 @@ pub fn send_command_or_warn(service: Service, configs: Configs, command: IPCComm } } - #[derive(Serialize, Deserialize, Debug)] pub struct IPCCommand { pub id: String, @@ -59,41 +58,50 @@ pub struct IPCCommand { impl IPCCommand { fn to_event(&self) -> Option { match self.id.as_ref() { - "exit" => { - Some(Event::Action(ActionType::Exit)) - }, - "wexit" => { - Some(Event::Action(ActionType::ExitWorker)) - }, - "toggle" => { - Some(Event::Action(ActionType::Toggle)) - }, - "enable" => { - Some(Event::Action(ActionType::Enable)) - }, - "disable" => { - Some(Event::Action(ActionType::Disable)) - }, - "restartworker" => { - Some(Event::Action(ActionType::RestartWorker)) - }, - "notify" => { - Some(Event::System(SystemEvent::NotifyRequest(self.payload.clone()))) - }, - _ => None + "exit" => Some(Event::Action(ActionType::Exit)), + "wexit" => Some(Event::Action(ActionType::ExitWorker)), + "toggle" => Some(Event::Action(ActionType::Toggle)), + "enable" => Some(Event::Action(ActionType::Enable)), + "disable" => Some(Event::Action(ActionType::Disable)), + "restartworker" => Some(Event::Action(ActionType::RestartWorker)), + "notify" => Some(Event::System(SystemEvent::NotifyRequest( + self.payload.clone(), + ))), + _ => None, } } pub fn from(event: Event) -> Option { match event { - Event::Action(ActionType::Exit) => Some(IPCCommand{id: "exit".to_owned(), payload: "".to_owned()}), - Event::Action(ActionType::ExitWorker) => Some(IPCCommand{id: "wexit".to_owned(), payload: "".to_owned()}), - Event::Action(ActionType::Toggle) => Some(IPCCommand{id: "toggle".to_owned(), payload: "".to_owned()}), - Event::Action(ActionType::Enable) => Some(IPCCommand{id: "enable".to_owned(), payload: "".to_owned()}), - Event::Action(ActionType::Disable) => Some(IPCCommand{id: "disable".to_owned(), payload: "".to_owned()}), - Event::Action(ActionType::RestartWorker) => Some(IPCCommand{id: "restartworker".to_owned(), payload: "".to_owned()}), - Event::System(SystemEvent::NotifyRequest(message)) => Some(IPCCommand{id: "notify".to_owned(), payload: message}), - _ => None + Event::Action(ActionType::Exit) => Some(IPCCommand { + id: "exit".to_owned(), + payload: "".to_owned(), + }), + Event::Action(ActionType::ExitWorker) => Some(IPCCommand { + id: "wexit".to_owned(), + payload: "".to_owned(), + }), + Event::Action(ActionType::Toggle) => Some(IPCCommand { + id: "toggle".to_owned(), + payload: "".to_owned(), + }), + Event::Action(ActionType::Enable) => Some(IPCCommand { + id: "enable".to_owned(), + payload: "".to_owned(), + }), + Event::Action(ActionType::Disable) => Some(IPCCommand { + id: "disable".to_owned(), + payload: "".to_owned(), + }), + Event::Action(ActionType::RestartWorker) => Some(IPCCommand { + id: "restartworker".to_owned(), + payload: "".to_owned(), + }), + Event::System(SystemEvent::NotifyRequest(message)) => Some(IPCCommand { + id: "notify".to_owned(), + payload: message, + }), + _ => None, } } @@ -122,22 +130,23 @@ impl IPCCommand { fn process_event(event_channel: &Sender, stream: Result) { match stream { Ok(stream) => { - let mut json_str= String::new(); + let mut json_str = String::new(); let mut buf_reader = BufReader::new(stream); let res = buf_reader.read_to_string(&mut json_str); if res.is_ok() { - let command : Result = serde_json::from_str(&json_str); + let command: Result = + serde_json::from_str(&json_str); match command { Ok(command) => { let event = command.to_event(); if let Some(event) = event { event_channel.send(event).expect("Broken event channel"); } - }, + } Err(e) => { error!("Error deserializing JSON command: {}", e); - }, + } } } } @@ -147,7 +156,10 @@ fn process_event(event_channel: &Sender, stream: Resul } } -fn send_command(command: IPCCommand, stream: Result) -> Result<(), String>{ +fn send_command( + command: IPCCommand, + stream: Result, +) -> Result<(), String> { match stream { Ok(mut stream) => { let json_str = serde_json::to_string(&command); @@ -155,12 +167,10 @@ fn send_command(command: IPCCommand, stream: Result) - stream.write_all(json_str.as_bytes()).unwrap_or_else(|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()) @@ -173,7 +183,11 @@ pub enum Service { // UNIX IMPLEMENTATION #[cfg(not(target_os = "windows"))] -pub fn get_ipc_server(service: Service, _: Configs, event_channel: Sender) -> impl IPCServer { +pub fn get_ipc_server( + service: Service, + _: Configs, + event_channel: Sender, +) -> impl IPCServer { unix::UnixIPCServer::new(service, event_channel) } @@ -184,11 +198,15 @@ pub fn get_ipc_client(service: Service, _: Configs) -> impl IPCClient { // WINDOWS IMPLEMENTATION #[cfg(target_os = "windows")] -pub fn get_ipc_server(service: Service, config: Configs, event_channel: Sender) -> impl IPCServer { +pub fn get_ipc_server( + service: Service, + config: Configs, + event_channel: Sender, +) -> impl IPCServer { windows::WindowsIPCServer::new(service, config, event_channel) } #[cfg(target_os = "windows")] pub fn get_ipc_client(service: Service, config: Configs) -> impl IPCClient { windows::WindowsIPCClient::new(service, config) -} \ No newline at end of file +} diff --git a/src/protocol/unix.rs b/src/protocol/unix.rs index 6146bb9..98313da 100644 --- a/src/protocol/unix.rs +++ b/src/protocol/unix.rs @@ -17,18 +17,18 @@ * along with espanso. If not, see . */ -use std::os::unix::net::{UnixStream,UnixListener}; -use log::{info, warn}; -use std::sync::mpsc::Sender; use super::IPCCommand; +use log::{info, warn}; +use std::os::unix::net::{UnixListener, UnixStream}; +use std::sync::mpsc::Sender; +use super::Service; use crate::context; use crate::event::*; use crate::protocol::{process_event, send_command}; -use super::Service; -const DAEMON_UNIX_SOCKET_NAME : &str = "espanso.sock"; -const WORKER_UNIX_SOCKET_NAME : &str = "worker.sock"; +const DAEMON_UNIX_SOCKET_NAME: &str = "espanso.sock"; +const WORKER_UNIX_SOCKET_NAME: &str = "worker.sock"; pub struct UnixIPCServer { service: Service, @@ -39,15 +39,15 @@ impl UnixIPCServer { pub fn new(service: Service, event_channel: Sender) -> UnixIPCServer { UnixIPCServer { service, - event_channel + event_channel, } } } -fn get_unix_name(service: &Service) -> String{ +fn get_unix_name(service: &Service) -> String { match service { - Service::Daemon => {DAEMON_UNIX_SOCKET_NAME.to_owned()}, - Service::Worker => {WORKER_UNIX_SOCKET_NAME.to_owned()}, + Service::Daemon => DAEMON_UNIX_SOCKET_NAME.to_owned(), + Service::Worker => WORKER_UNIX_SOCKET_NAME.to_owned(), } } @@ -55,21 +55,28 @@ impl super::IPCServer for UnixIPCServer { fn start(&self) { let event_channel = self.event_channel.clone(); let socket_name = get_unix_name(&self.service); - std::thread::Builder::new().name("ipc_server".to_string()).spawn(move || { - let espanso_dir = context::get_data_dir(); - let unix_socket = espanso_dir.join(socket_name); + std::thread::Builder::new() + .name("ipc_server".to_string()) + .spawn(move || { + let espanso_dir = context::get_data_dir(); + let unix_socket = espanso_dir.join(socket_name); - std::fs::remove_file(unix_socket.clone()).unwrap_or_else(|e| { - warn!("Unable to delete Unix socket: {}", e); - }); - let listener = UnixListener::bind(unix_socket.clone()).expect("Can't bind to Unix Socket"); + std::fs::remove_file(unix_socket.clone()).unwrap_or_else(|e| { + warn!("Unable to delete Unix socket: {}", e); + }); + 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() { - process_event(&event_channel, stream); - } - }).expect("Unable to spawn IPC server thread"); + for stream in listener.incoming() { + process_event(&event_channel, stream); + } + }) + .expect("Unable to spawn IPC server thread"); } } @@ -79,7 +86,7 @@ pub struct UnixIPCClient { impl UnixIPCClient { pub fn new(service: Service) -> UnixIPCClient { - UnixIPCClient{service} + UnixIPCClient { service } } } @@ -94,4 +101,4 @@ impl super::IPCClient for UnixIPCClient { send_command(command, stream) } -} \ No newline at end of file +} diff --git a/src/protocol/windows.rs b/src/protocol/windows.rs index a995564..67b2125 100644 --- a/src/protocol/windows.rs +++ b/src/protocol/windows.rs @@ -17,14 +17,14 @@ * along with espanso. If not, see . */ -use log::{info}; -use std::sync::mpsc::Sender; -use std::net::{TcpListener, TcpStream}; use super::IPCCommand; +use log::info; +use std::net::{TcpListener, TcpStream}; +use std::sync::mpsc::Sender; +use crate::config::Configs; use crate::event::*; use crate::protocol::{process_event, send_command, Service}; -use crate::config::{Configs}; pub struct WindowsIPCServer { service: Service, @@ -34,15 +34,23 @@ pub struct WindowsIPCServer { fn to_port(config: &Configs, service: &Service) -> u16 { let port = match service { - Service::Daemon => {config.ipc_server_port}, - Service::Worker => {config.worker_ipc_server_port}, + Service::Daemon => config.ipc_server_port, + Service::Worker => config.worker_ipc_server_port, }; port as u16 } impl WindowsIPCServer { - pub fn new(service: Service, config: Configs, event_channel: Sender) -> WindowsIPCServer { - WindowsIPCServer {service, config, event_channel} + pub fn new( + service: Service, + config: Configs, + event_channel: Sender, + ) -> WindowsIPCServer { + WindowsIPCServer { + service, + config, + event_channel, + } } } @@ -50,17 +58,22 @@ impl super::IPCServer for WindowsIPCServer { fn start(&self) { let event_channel = self.event_channel.clone(); let server_port = to_port(&self.config, &self.service); - std::thread::Builder::new().name("ipc_server".to_string()).spawn(move || { - let listener = TcpListener::bind( - format!("127.0.0.1:{}", server_port) - ).expect("Error binding to IPC server port"); + std::thread::Builder::new() + .name("ipc_server".to_string()) + .spawn(move || { + 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() { - process_event(&event_channel, stream); - } - }).expect("Unable to spawn IPC server thread"); + for stream in listener.incoming() { + process_event(&event_channel, stream); + } + }) + .expect("Unable to spawn IPC server thread"); } } @@ -71,17 +84,15 @@ pub struct WindowsIPCClient { impl WindowsIPCClient { pub fn new(service: Service, config: Configs) -> WindowsIPCClient { - WindowsIPCClient{service, config} + WindowsIPCClient { service, config } } } impl super::IPCClient for WindowsIPCClient { fn send_command(&self, command: IPCCommand) -> Result<(), String> { let port = to_port(&self.config, &self.service); - let stream = TcpStream::connect( - ("127.0.0.1", port) - ); + let stream = TcpStream::connect(("127.0.0.1", port)); send_command(command, stream) } -} \ No newline at end of file +} diff --git a/src/render/default.rs b/src/render/default.rs index b95d88d..6c9fcbc 100644 --- a/src/render/default.rs +++ b/src/render/default.rs @@ -17,18 +17,18 @@ * along with espanso. If not, see . */ -use serde_yaml::{Value}; -use std::collections::{HashMap, HashSet}; -use regex::{Regex, Captures}; -use log::{warn, error}; use super::*; -use crate::matcher::{Match, MatchContentType}; use crate::config::Configs; use crate::extension::Extension; +use crate::matcher::{Match, MatchContentType}; +use log::{error, warn}; +use regex::{Captures, Regex}; +use serde_yaml::Value; +use std::collections::{HashMap, HashSet}; lazy_static! { static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P\\w+)\\s*\\}\\}").unwrap(); - static ref UNKNOWN_VARIABLE : String = "".to_string(); + static ref UNKNOWN_VARIABLE: String = "".to_string(); } pub struct DefaultRenderer { @@ -47,12 +47,11 @@ impl DefaultRenderer { } // Compile the regexes - let passive_match_regex = Regex::new(&config.passive_match_regex) - .unwrap_or_else(|e| { - panic!("Invalid passive match regex: {:?}", e); - }); + let passive_match_regex = Regex::new(&config.passive_match_regex).unwrap_or_else(|e| { + panic!("Invalid passive match regex: {:?}", e); + }); - DefaultRenderer{ + DefaultRenderer { extension_map, passive_match_regex, } @@ -69,7 +68,6 @@ impl DefaultRenderer { break; } } - } result @@ -77,7 +75,13 @@ impl DefaultRenderer { } impl super::Renderer for DefaultRenderer { - fn render_match(&self, m: &Match, trigger_offset: usize, config: &Configs, args: Vec) -> RenderResult { + fn render_match( + &self, + m: &Match, + trigger_offset: usize, + config: &Configs, + args: Vec, + ) -> RenderResult { // Manage the different types of matches match &m.content { // Text Match @@ -88,7 +92,7 @@ impl super::Renderer for DefaultRenderer { for caps in VAR_REGEX.captures_iter(&content.replace) { let var_name = caps.name("name").unwrap().as_str(); target_vars.insert(var_name.to_owned()); - }; + } let target_string = if target_vars.len() > 0 { let mut output_map = HashMap::new(); @@ -106,24 +110,32 @@ impl super::Renderer for DefaultRenderer { // Extract the match trigger from the variable params let trigger = variable.params.get(&Value::from("trigger")); if trigger.is_none() { - warn!("Missing param 'trigger' in match variable: {}", variable.name); + warn!( + "Missing param 'trigger' in match variable: {}", + variable.name + ); continue; } let trigger = trigger.unwrap(); // 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() { - warn!("Could not find inner match with trigger: '{}'", trigger.as_str().unwrap_or("undefined")); - continue + warn!( + "Could not find inner match with trigger: '{}'", + trigger.as_str().unwrap_or("undefined") + ); + continue; } let (inner_match, trigger_offset) = inner_match.unwrap(); // Render the inner match // TODO: inner arguments - let result = self.render_match(&inner_match, trigger_offset, config, vec![]); + let result = + self.render_match(&inner_match, trigger_offset, config, vec![]); // Inner matches are only supported for text-expansions, warn the user otherwise match result { @@ -134,18 +146,25 @@ impl super::Renderer for DefaultRenderer { 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); if let Some(extension) = extension { let ext_out = extension.calculate(&variable.params, &args); if let Some(output) = ext_out { output_map.insert(variable.name.clone(), output); - }else{ + } else { 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{ - error!("No extension found for variable type: {}", variable.var_type); + } else { + error!( + "No extension found for variable type: {}", + variable.var_type + ); } } } @@ -158,14 +177,14 @@ impl super::Renderer for DefaultRenderer { }); result.to_string() - }else{ // No variables, simple text substitution + } else { + // No variables, simple text substitution content.replace.clone() }; // Unescape any brackets (needed to be able to insert double brackets in replacement // text, without triggering the variable system). See issue #187 - let target_string = target_string.replace("\\{", "{") - .replace("\\}", "}"); + let target_string = target_string.replace("\\{", "{").replace("\\}", "}"); // Render any argument that may be present let target_string = utils::render_args(&target_string, &args); @@ -177,27 +196,26 @@ impl super::Renderer for DefaultRenderer { // The check should be carried out from the position of the first // alphabetic letter // See issue #244 - let first_alphabetic = trigger.chars().position(|c| { - c.is_alphabetic() - }).unwrap_or(0); + let first_alphabetic = + trigger.chars().position(|c| c.is_alphabetic()).unwrap_or(0); let first_char = trigger.chars().nth(first_alphabetic); - let second_char = trigger.chars().nth(first_alphabetic + 1); + let second_char = trigger.chars().nth(first_alphabetic + 1); let mode: i32 = if let Some(first_char) = first_char { if first_char.is_uppercase() { if let Some(second_char) = second_char { if second_char.is_uppercase() { 2 // Full CAPITALIZATION - }else{ + } else { 1 // Only first letter capitalized: Capitalization } - }else{ - 2 // Single char, defaults to full CAPITALIZATION + } else { + 2 // Single char, defaults to full CAPITALIZATION } - }else{ - 0 // Lowercase, no action + } else { + 0 // Lowercase, no action } - }else{ + } else { 0 }; @@ -207,78 +225,79 @@ impl super::Renderer for DefaultRenderer { let mut v: Vec = target_string.chars().collect(); v[0] = v[0].to_uppercase().nth(0).unwrap(); v.into_iter().collect() - }, - 2 => { // Full capitalization + } + 2 => { + // Full capitalization target_string.to_uppercase() - }, - _ => { // Noop + } + _ => { + // Noop target_string } } - }else{ + } else { target_string }; RenderResult::Text(target_string) - }, + } // Image Match MatchContentType::Image(content) => { // Make sure the image exist beforehand if content.path.exists() { RenderResult::Image(content.path.clone()) - }else{ + } else { error!("Image not found in path: {:?}", content.path); RenderResult::Error } - }, + } } } fn render_passive(&self, text: &str, config: &Configs) -> RenderResult { // Render the matches - let result = self.passive_match_regex.replace_all(&text, |caps: &Captures| { - let match_name = if let Some(name) = caps.name("name") { - name.as_str() - }else{ - "" - }; + let result = self + .passive_match_regex + .replace_all(&text, |caps: &Captures| { + let match_name = if let Some(name) = caps.name("name") { + name.as_str() + } else { + "" + }; + // Get the original matching string, useful to return the match untouched + let original_match = caps.get(0).unwrap().as_str(); - // Get the original matching string, useful to return the match untouched - let original_match = caps.get(0).unwrap().as_str(); + // Find the corresponding match + let m = DefaultRenderer::find_match(config, match_name); - // Find the corresponding match - let m = DefaultRenderer::find_match(config, match_name); - - // If no match is found, leave the match without modifications - if m.is_none() { - return original_match.to_owned(); - } - - // Compute the args by separating them - let match_args = if let Some(args) = caps.name("args") { - args.as_str() - }else{ - "" - }; - let args : Vec = utils::split_args(match_args, - config.passive_arg_delimiter, - config.passive_arg_escape); - - let (m, trigger_offset) = m.unwrap(); - // Render the actual match - let result = self.render_match(&m, trigger_offset, &config, args); - - match result { - RenderResult::Text(out) => { - out - }, - _ => { - original_match.to_owned() + // If no match is found, leave the match without modifications + if m.is_none() { + return original_match.to_owned(); } - } - }); + + // Compute the args by separating them + let match_args = if let Some(args) = caps.name("args") { + args.as_str() + } else { + "" + }; + let args: Vec = utils::split_args( + match_args, + config.passive_arg_delimiter, + config.passive_arg_escape, + ); + + let (m, trigger_offset) = m.unwrap(); + // Render the actual match + let result = self.render_match(&m, trigger_offset, &config, args); + + match result { + RenderResult::Text(out) => out, + _ => original_match.to_owned(), + } + }); RenderResult::Text(result.into_owned()) } @@ -291,11 +310,14 @@ mod tests { use super::*; fn get_renderer(config: Configs) -> DefaultRenderer { - DefaultRenderer::new(vec![Box::new(crate::extension::dummy::DummyExtension::new())], config) + DefaultRenderer::new( + vec![Box::new(crate::extension::dummy::DummyExtension::new())], + config, + ) } fn get_config_for(s: &str) -> Configs { - let config : Configs = serde_yaml::from_str(s).unwrap(); + let config: Configs = serde_yaml::from_str(s).unwrap(); config } @@ -303,10 +325,8 @@ mod tests { match rendered { RenderResult::Text(rendered) => { assert_eq!(rendered, target); - }, - _ => { - assert!(false) } + _ => assert!(false), } } @@ -316,11 +336,13 @@ mod tests { this text contains no matches "###; - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - trigger: test replace: result - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -333,11 +355,13 @@ mod tests { fn test_render_passive_simple_match_no_args() { let text = "this is a :test"; - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - trigger: ':test' replace: result - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -350,11 +374,13 @@ mod tests { fn test_render_passive_multiple_match_no_args() { let text = "this is a :test and then another :test"; - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - trigger: ':test' replace: result - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -369,15 +395,17 @@ mod tests { :test "###; - let result= r###"this is a + let result = r###"this is a result "###; - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - trigger: ':test' replace: result - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -390,7 +418,8 @@ mod tests { fn test_render_passive_nested_matches_no_args() { let text = ":greet"; - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - trigger: ':greet' replace: "hi {{name}}" @@ -402,7 +431,8 @@ mod tests { - trigger: ':name' replace: john - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -415,11 +445,13 @@ mod tests { fn test_render_passive_simple_match_with_args() { let text = ":greet/Jon/"; - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - trigger: ':greet' replace: "Hi $0$" - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -432,11 +464,13 @@ mod tests { fn test_render_passive_simple_match_with_multiple_args() { let text = ":greet/Jon/Snow/"; - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - trigger: ':greet' replace: "Hi $0$, there is $1$ outside" - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -449,11 +483,13 @@ mod tests { fn test_render_passive_simple_match_with_escaped_args() { let text = ":greet/Jon/10\\/12/"; - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - trigger: ':greet' replace: "Hi $0$, today is $1$" - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -466,11 +502,13 @@ mod tests { fn test_render_passive_simple_match_with_args_not_closed() { let text = ":greet/Jon/Snow"; - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - trigger: ':greet' replace: "Hi $0$" - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -483,7 +521,8 @@ mod tests { fn test_render_passive_local_var() { let text = "this is :test"; - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - trigger: ':test' replace: "my {{output}}" @@ -492,7 +531,8 @@ mod tests { type: dummy params: echo: "result" - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -505,7 +545,8 @@ mod tests { fn test_render_passive_global_var() { let text = "this is :test"; - let config = get_config_for(r###" + let config = get_config_for( + r###" global_vars: - name: output type: dummy @@ -515,7 +556,8 @@ mod tests { - trigger: ':test' replace: "my {{output}}" - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -528,7 +570,8 @@ mod tests { fn test_render_passive_global_var_is_overridden_by_local() { let text = "this is :test"; - let config = get_config_for(r###" + let config = get_config_for( + r###" global_vars: - name: output type: dummy @@ -543,7 +586,8 @@ mod tests { params: echo: "local" - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -556,11 +600,13 @@ mod tests { fn test_render_match_with_unknown_variable_does_not_crash() { let text = "this is :test"; - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - trigger: ':test' replace: "my {{unknown}}" - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -573,11 +619,13 @@ mod tests { fn test_render_escaped_double_brackets_should_not_consider_them_variable() { let text = "this is :test"; - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - trigger: ':test' replace: "my \\{\\{unknown\\}\\}" - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -590,11 +638,13 @@ mod tests { fn test_render_passive_simple_match_multi_trigger_no_args() { let text = "this is a :yolo and :test"; - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - triggers: [':test', ':yolo'] replace: result - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -607,11 +657,13 @@ mod tests { fn test_render_passive_simple_match_multi_trigger_with_args() { let text = ":yolo/Jon/"; - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - triggers: [':greet', ':yolo'] replace: "Hi $0$" - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -622,18 +674,20 @@ mod tests { #[test] fn test_render_match_case_propagation_no_case() { - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - trigger: 'test' replace: result propagate_case: true - "###); + "###, + ); let renderer = get_renderer(config.clone()); let m = config.matches[0].clone(); - let trigger_offset = m.triggers.iter().position(|x| x== "test").unwrap(); + let trigger_offset = m.triggers.iter().position(|x| x == "test").unwrap(); let rendered = renderer.render_match(&m, trigger_offset, &config, vec![]); @@ -642,18 +696,20 @@ mod tests { #[test] fn test_render_match_case_propagation_first_capital() { - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - trigger: 'test' replace: result propagate_case: true - "###); + "###, + ); let renderer = get_renderer(config.clone()); let m = config.matches[0].clone(); - let trigger_offset = m.triggers.iter().position(|x| x== "Test").unwrap(); + let trigger_offset = m.triggers.iter().position(|x| x == "Test").unwrap(); let rendered = renderer.render_match(&m, trigger_offset, &config, vec![]); @@ -662,21 +718,23 @@ mod tests { #[test] fn test_render_match_case_propagation_all_capital() { - let config = get_config_for(r###" + let config = get_config_for( + r###" matches: - trigger: 'test' replace: result propagate_case: true - "###); + "###, + ); let renderer = get_renderer(config.clone()); let m = config.matches[0].clone(); - let trigger_offset = m.triggers.iter().position(|x| x== "TEST").unwrap(); + let trigger_offset = m.triggers.iter().position(|x| x == "TEST").unwrap(); let rendered = renderer.render_match(&m, trigger_offset, &config, vec![]); verify_render(rendered, "RESULT"); } -} \ No newline at end of file +} diff --git a/src/render/mod.rs b/src/render/mod.rs index b7a3946..264fc06 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -17,16 +17,22 @@ * along with espanso. If not, see . */ -use std::path::PathBuf; -use crate::matcher::{Match}; use crate::config::Configs; +use crate::matcher::Match; +use std::path::PathBuf; pub(crate) mod default; pub(crate) mod utils; pub trait Renderer { // Render a match output - fn render_match(&self, m: &Match, trigger_offset: usize, config: &Configs, args: Vec) -> RenderResult; + fn render_match( + &self, + m: &Match, + trigger_offset: usize, + config: &Configs, + args: Vec, + ) -> RenderResult; // Render a passive expansion text fn render_passive(&self, text: &str, config: &Configs) -> RenderResult; @@ -35,5 +41,5 @@ pub trait Renderer { pub enum RenderResult { Text(String), Image(PathBuf), - Error -} \ No newline at end of file + Error, +} diff --git a/src/render/utils.rs b/src/render/utils.rs index fe486b6..cfee8d7 100644 --- a/src/render/utils.rs +++ b/src/render/utils.rs @@ -17,7 +17,7 @@ * along with espanso. If not, see . */ -use regex::{Regex, Captures}; +use regex::{Captures, Regex}; lazy_static! { static ref ARG_REGEX: Regex = Regex::new("\\$(?P\\d+)\\$").unwrap(); @@ -25,12 +25,12 @@ lazy_static! { pub fn render_args(text: &str, args: &Vec) -> String { let result = ARG_REGEX.replace_all(text, |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::().unwrap_or(-1); if position >= 0 && position < args.len() as i32 { args[position as usize].to_owned() - }else{ + } else { "".to_owned() } }); @@ -43,24 +43,24 @@ pub fn split_args(text: &str, delimiter: char, escape: char) -> Vec { // Make sure the text is not empty if text.is_empty() { - return output + return output; } let mut last = String::from(""); - let mut previous : char = char::from(0); + let mut previous: char = char::from(0); text.chars().into_iter().for_each(|c| { if c == delimiter { if previous != escape { output.push(last.clone()); last = String::from(""); - }else{ + } else { last.push(c); } - }else if c == escape { + } else if c == escape { if previous == escape { last.push(c); } - }else{ + } else { last.push(c); } previous = c; @@ -80,25 +80,28 @@ mod tests { #[test] 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") } #[test] 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") } #[test] fn test_render_args_one_multiple_args() { - 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") + 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" + ) } #[test] 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 ") } @@ -124,7 +127,7 @@ mod tests { #[test] fn test_split_args_empty() { - let empty_vec : Vec = vec![]; + let empty_vec: Vec = vec![]; assert_eq!(split_args("", '/', '\\'), empty_vec) } -} \ No newline at end of file +} diff --git a/src/sysdaemon.rs b/src/sysdaemon.rs index 706af9d..4d4c055 100644 --- a/src/sysdaemon.rs +++ b/src/sysdaemon.rs @@ -20,14 +20,14 @@ // This functions are used to register/unregister espanso from the system daemon manager. use crate::config::ConfigSet; -use crate::sysdaemon::VerifyResult::{EnabledAndValid, NotEnabled, EnabledButInvalidPath}; +use crate::sysdaemon::VerifyResult::{EnabledAndValid, EnabledButInvalidPath, NotEnabled}; // INSTALLATION #[cfg(target_os = "macos")] -const MAC_PLIST_CONTENT : &str = include_str!("res/mac/com.federicoterzi.espanso.plist"); +const MAC_PLIST_CONTENT: &str = include_str!("res/mac/com.federicoterzi.espanso.plist"); #[cfg(target_os = "macos")] -const MAC_PLIST_FILENAME : &str = "com.federicoterzi.espanso.plist"; +const MAC_PLIST_FILENAME: &str = "com.federicoterzi.espanso.plist"; #[cfg(target_os = "macos")] pub fn register(_config_set: ConfigSet) { @@ -45,13 +45,21 @@ pub fn register(_config_set: ConfigSet) { let plist_file = agents_dir.join(MAC_PLIST_FILENAME); 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"); - 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) - .replace("{{{espanso_path}}}", espanso_path.to_str().unwrap_or_default()); + let plist_content = String::from(MAC_PLIST_CONTENT).replace( + "{{{espanso_path}}}", + espanso_path.to_str().unwrap_or_default(), + ); std::fs::write(plist_file.clone(), plist_content).expect("Unable to write plist file"); @@ -61,8 +69,8 @@ pub fn register(_config_set: ConfigSet) { println!("Reloading entry..."); let res = Command::new("launchctl") - .args(&["unload", "-w", plist_file.to_str().unwrap_or_default()]) - .output(); + .args(&["unload", "-w", plist_file.to_str().unwrap_or_default()]) + .output(); let res = Command::new("launchctl") .args(&["load", "-w", plist_file.to_str().unwrap_or_default()]) @@ -72,7 +80,7 @@ pub fn register(_config_set: ConfigSet) { if status.success() { println!("Entry loaded correctly!") } - }else{ + } else { println!("Error loading new entry"); } } @@ -95,7 +103,7 @@ pub fn unregister(_config_set: ConfigSet) { std::fs::remove_file(&plist_file).expect("Could not remove espanso entry"); println!("Entry removed correctly!") - }else{ + } else { println!("espanso is not installed"); } } @@ -103,14 +111,14 @@ pub fn unregister(_config_set: ConfigSet) { // LINUX #[cfg(target_os = "linux")] -const LINUX_SERVICE_CONTENT : &str = include_str!("res/linux/systemd.service"); +const LINUX_SERVICE_CONTENT: &str = include_str!("res/linux/systemd.service"); #[cfg(target_os = "linux")] -const LINUX_SERVICE_FILENAME : &str = "espanso.service"; +const LINUX_SERVICE_FILENAME: &str = "espanso.service"; #[cfg(target_os = "linux")] pub fn register(_: ConfigSet) { use std::fs::create_dir_all; - use std::process::{Command}; + use std::process::Command; // Check if espanso service is already registered let res = Command::new("systemctl") @@ -126,7 +134,7 @@ pub fn register(_: ConfigSet) { eprintln!(" espanso unregister"); std::process::exit(5); } - }else{ + } else { if output == "disabled" { use dialoguer::Confirmation; if !Confirmation::new() @@ -154,15 +162,24 @@ pub fn register(_: ConfigSet) { let service_file = user_dir.join(LINUX_SERVICE_FILENAME); 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"); - 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) - .replace("{{{espanso_path}}}", espanso_path.to_str().unwrap_or_default()); + let service_content = String::from(LINUX_SERVICE_CONTENT).replace( + "{{{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!") } @@ -177,7 +194,7 @@ pub fn register(_: ConfigSet) { if status.success() { println!("Service registered correctly!") } - }else{ + } else { println!("Error loading espanso service"); } } @@ -191,7 +208,7 @@ pub enum VerifyResult { #[cfg(target_os = "linux")] pub fn verify() -> VerifyResult { use regex::Regex; - use std::process::{Command}; + use std::process::Command; // Check if espanso service is already registered let res = Command::new("systemctl") @@ -201,7 +218,7 @@ pub fn verify() -> VerifyResult { let output = String::from_utf8_lossy(res.stdout.as_slice()); let output = output.trim(); if !res.status.success() || output != "enabled" { - return NotEnabled + return NotEnabled; } } @@ -219,10 +236,11 @@ pub fn verify() -> VerifyResult { if res.status.success() { let caps = EXEC_PATH_REGEX.captures(output).unwrap(); let path = caps.get(1).map_or("", |m| m.as_str()); - 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"); if espanso_path.to_string_lossy() != path { - return EnabledButInvalidPath + return EnabledButInvalidPath; } } } @@ -232,12 +250,13 @@ pub fn verify() -> VerifyResult { #[cfg(target_os = "linux")] pub fn unregister(_: ConfigSet) { - use std::process::{Command}; + use std::process::Command; // Disable the service first Command::new("systemctl") .args(&["--user", "disable", "espanso"]) - .status().expect("Unable to invoke systemctl"); + .status() + .expect("Unable to invoke systemctl"); // Then delete the espanso.service entry let config_dir = dirs::config_dir().expect("Could not get configuration directory"); @@ -251,13 +270,16 @@ pub fn unregister(_: ConfigSet) { Ok(_) => { println!("Deleted entry at {}", service_file.to_string_lossy()); println!("Service unregistered successfully!"); - }, + } Err(e) => { - println!("Error, could not delete service entry at {} with error {}", - service_file.to_string_lossy(), e); - }, + println!( + "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"); } } @@ -272,4 +294,4 @@ pub fn register(_config_set: ConfigSet) { #[cfg(target_os = "windows")] pub fn unregister(_config_set: ConfigSet) { println!("Windows does not support automatic system daemon integration.") -} \ No newline at end of file +} diff --git a/src/system/linux.rs b/src/system/linux.rs index df05ac3..5f2c8dc 100644 --- a/src/system/linux.rs +++ b/src/system/linux.rs @@ -19,7 +19,9 @@ 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; pub struct LinuxSystemManager {} @@ -27,7 +29,7 @@ pub struct LinuxSystemManager {} impl super::SystemManager for LinuxSystemManager { fn get_current_window_title(&self) -> Option { unsafe { - let mut buffer : [c_char; 100] = [0; 100]; + let mut buffer: [c_char; 100] = [0; 100]; let res = get_active_window_name(buffer.as_mut_ptr(), buffer.len() as i32); if res > 0 { @@ -39,13 +41,13 @@ impl super::SystemManager for LinuxSystemManager { } } } - + None } fn get_current_window_class(&self) -> Option { unsafe { - let mut buffer : [c_char; 100] = [0; 100]; + let mut buffer: [c_char; 100] = [0; 100]; let res = get_active_window_class(buffer.as_mut_ptr(), buffer.len() as i32); if res > 0 { @@ -63,7 +65,7 @@ impl super::SystemManager for LinuxSystemManager { fn get_current_window_executable(&self) -> Option { unsafe { - let mut buffer : [c_char; 100] = [0; 100]; + let mut buffer: [c_char; 100] = [0; 100]; let res = get_active_window_executable(buffer.as_mut_ptr(), buffer.len() as i32); if res > 0 { @@ -82,6 +84,6 @@ impl super::SystemManager for LinuxSystemManager { impl LinuxSystemManager { pub fn new() -> LinuxSystemManager { - LinuxSystemManager{} + LinuxSystemManager {} } -} \ No newline at end of file +} diff --git a/src/system/macos.rs b/src/system/macos.rs index d1b401e..c3ccf43 100644 --- a/src/system/macos.rs +++ b/src/system/macos.rs @@ -19,12 +19,12 @@ use std::os::raw::c_char; +use crate::bridge::macos::{ + get_active_app_bundle, get_active_app_identifier, get_path_from_pid, get_secure_input_process, +}; use std::ffi::CStr; -use crate::bridge::macos::{get_active_app_bundle, get_active_app_identifier, get_secure_input_process, get_path_from_pid}; -pub struct MacSystemManager { - -} +pub struct MacSystemManager {} impl super::SystemManager for MacSystemManager { fn get_current_window_title(&self) -> Option { @@ -33,7 +33,7 @@ impl super::SystemManager for MacSystemManager { fn get_current_window_class(&self) -> Option { unsafe { - let mut buffer : [c_char; 250] = [0; 250]; + let mut buffer: [c_char; 250] = [0; 250]; let res = get_active_app_identifier(buffer.as_mut_ptr(), buffer.len() as i32); if res > 0 { @@ -51,7 +51,7 @@ impl super::SystemManager for MacSystemManager { fn get_current_window_executable(&self) -> Option { unsafe { - let mut buffer : [c_char; 250] = [0; 250]; + let mut buffer: [c_char; 250] = [0; 250]; let res = get_active_app_bundle(buffer.as_mut_ptr(), buffer.len() as i32); if res > 0 { @@ -70,9 +70,7 @@ impl super::SystemManager for MacSystemManager { impl MacSystemManager { pub fn new() -> MacSystemManager { - MacSystemManager{ - - } + MacSystemManager {} } /// Check whether an application is currently holding the Secure Input. @@ -82,9 +80,9 @@ impl MacSystemManager { let mut pid: i64 = -1; let res = get_secure_input_process(&mut pid as *mut i64); - if res > 0{ + if res > 0 { Some(pid) - }else{ + } else { None } } @@ -105,7 +103,7 @@ impl MacSystemManager { if let Some(pid) = pid { // Size of the buffer is ruled by the PROC_PIDPATHINFO_MAXSIZE constant. // the underlying proc_pidpath REQUIRES a buffer of that dimension, otherwise it fail silently. - let mut buffer : [c_char; 4096] = [0; 4096]; + let mut buffer: [c_char; 4096] = [0; 4096]; let res = get_path_from_pid(pid, buffer.as_mut_ptr(), buffer.len() as i32); if res > 0 { @@ -117,23 +115,23 @@ impl MacSystemManager { let caps = APP_REGEX.captures(&process); let app_name = if let Some(caps) = caps { caps.get(1).map_or("", |m| m.as_str()).to_owned() - }else{ + } else { process.to_owned() }; Some((app_name, process)) - }else{ + } else { None } - }else{ + } else { None } - }else{ + } else { None } - }else{ + } else { None } } } -} \ No newline at end of file +} diff --git a/src/system/mod.rs b/src/system/mod.rs index a0bd25a..479ef86 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -48,4 +48,4 @@ pub fn get_manager() -> impl SystemManager { #[cfg(target_os = "macos")] pub fn get_manager() -> impl SystemManager { macos::MacSystemManager::new() -} \ No newline at end of file +} diff --git a/src/system/windows.rs b/src/system/windows.rs index 01e9cfd..eb0d030 100644 --- a/src/system/windows.rs +++ b/src/system/windows.rs @@ -17,23 +17,21 @@ * along with espanso. If not, see . */ -use widestring::U16CString; use crate::bridge::windows::*; +use widestring::U16CString; -pub struct WindowsSystemManager { - -} +pub struct WindowsSystemManager {} impl WindowsSystemManager { pub fn new() -> WindowsSystemManager { - WindowsSystemManager{} + WindowsSystemManager {} } } impl super::SystemManager for WindowsSystemManager { fn get_current_window_title(&self) -> Option { unsafe { - let mut buffer : [u16; 100] = [0; 100]; + let mut buffer: [u16; 100] = [0; 100]; let res = get_active_window_name(buffer.as_mut_ptr(), buffer.len() as i32); if res > 0 { @@ -53,7 +51,7 @@ impl super::SystemManager for WindowsSystemManager { fn get_current_window_executable(&self) -> Option { unsafe { - let mut buffer : [u16; 250] = [0; 250]; + let mut buffer: [u16; 250] = [0; 250]; let res = get_active_window_executable(buffer.as_mut_ptr(), buffer.len() as i32); if res > 0 { @@ -66,4 +64,4 @@ impl super::SystemManager for WindowsSystemManager { None } -} \ No newline at end of file +} diff --git a/src/ui/linux.rs b/src/ui/linux.rs index 814e259..d62a46a 100644 --- a/src/ui/linux.rs +++ b/src/ui/linux.rs @@ -17,12 +17,12 @@ * along with espanso. If not, see . */ -use std::process::Command; use super::MenuItem; use log::{error, info}; 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"); pub struct LinuxUIManager { icon_path: PathBuf, @@ -35,8 +35,14 @@ impl super::UIManager for LinuxUIManager { fn notify_delay(&self, message: &str, duration: i32) { let res = Command::new("notify-send") - .args(&["-i", self.icon_path.to_str().unwrap_or_default(), - "-t", &duration.to_string(), "espanso", message]) + .args(&[ + "-i", + self.icon_path.to_str().unwrap_or_default(), + "-t", + &duration.to_string(), + "espanso", + message, + ]) .output(); if let Err(e) = res { @@ -59,12 +65,13 @@ impl LinuxUIManager { let data_dir = crate::context::get_data_dir(); let icon_path = data_dir.join("icon.png"); 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"); } - LinuxUIManager{ - icon_path - } + LinuxUIManager { icon_path } } -} \ No newline at end of file +} diff --git a/src/ui/macos.rs b/src/ui/macos.rs index ebad9d0..8784f70 100644 --- a/src/ui/macos.rs +++ b/src/ui/macos.rs @@ -17,21 +17,21 @@ * along with espanso. If not, see . */ -use std::{fs, io}; -use std::io::{Cursor}; +use crate::bridge::macos::{show_context_menu, MacMenuItem}; +use crate::context; +use crate::ui::{MenuItem, MenuItemType}; +use log::{debug, info, warn}; 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::process::Command; -use crate::ui::{MenuItem, MenuItemType}; -use crate::bridge::macos::{MacMenuItem, show_context_menu}; -use std::os::raw::c_char; -use crate::context; +use std::{fs, io}; -const NOTIFY_HELPER_BINARY : &'static [u8] = include_bytes!("../res/mac/EspansoNotifyHelper.zip"); +const NOTIFY_HELPER_BINARY: &'static [u8] = include_bytes!("../res/mac/EspansoNotifyHelper.zip"); pub struct MacUIManager { - notify_helper_path: PathBuf + notify_helper_path: PathBuf, } impl super::UIManager for MacUIManager { @@ -60,14 +60,14 @@ impl super::UIManager for MacUIManager { for item in menu.iter() { let text = CString::new(item.item_name.clone()).unwrap_or_default(); - let mut str_buff : [c_char; 100] = [0; 100]; + let mut str_buff: [c_char; 100] = [0; 100]; unsafe { std::ptr::copy(text.as_ptr(), str_buff.as_mut_ptr(), item.item_name.len()); } let menu_type = match item.item_type { - MenuItemType::Button => {1}, - MenuItemType::Separator => {2}, + MenuItemType::Button => 1, + MenuItemType::Separator => 2, }; let raw_item = MacMenuItem { @@ -79,7 +79,9 @@ impl super::UIManager for MacUIManager { 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) { @@ -91,21 +93,22 @@ impl MacUIManager { pub fn new() -> MacUIManager { let notify_helper_path = MacUIManager::initialize_notify_helper(); - MacUIManager{ - notify_helper_path - } + MacUIManager { notify_helper_path } } fn initialize_notify_helper() -> PathBuf { 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"); if espanso_target.exists() { info!("EspansoNotifyHelper already initialized, skipping."); - }else{ + } else { // Extract zip file let reader = Cursor::new(NOTIFY_HELPER_BINARY); @@ -123,10 +126,19 @@ impl MacUIManager { } 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(); } 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 !p.exists() { fs::create_dir_all(&p).unwrap(); @@ -146,4 +158,4 @@ impl MacUIManager { espanso_target } -} \ No newline at end of file +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 81798b7..1400b19 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -60,4 +60,4 @@ pub fn get_uimanager() -> impl UIManager { #[cfg(target_os = "windows")] pub fn get_uimanager() -> impl UIManager { windows::WindowsUIManager::new() -} \ No newline at end of file +} diff --git a/src/ui/windows.rs b/src/ui/windows.rs index f6df686..e0bbc99 100644 --- a/src/ui/windows.rs +++ b/src/ui/windows.rs @@ -17,16 +17,18 @@ * along with espanso. If not, see . */ -use crate::bridge::windows::{show_notification, close_notification, WindowsMenuItem, show_context_menu, cleanup_ui}; -use widestring::U16CString; -use std::{thread, time}; -use log::{debug}; -use std::sync::Mutex; -use std::sync::Arc; +use crate::bridge::windows::{ + cleanup_ui, close_notification, show_context_menu, show_notification, WindowsMenuItem, +}; 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 { - id: Arc> + id: Arc>, } impl super::UIManager for WindowsUIManager { @@ -45,29 +47,30 @@ impl super::UIManager for WindowsUIManager { // Setup a timeout to close the notification let id = Arc::clone(&self.id); - let _ = thread::Builder::new().name("notification_thread".to_string()).spawn(move || { - for _ in 1..10 { - let duration = time::Duration::from_millis(step as u64); - thread::sleep(duration); + let _ = thread::Builder::new() + .name("notification_thread".to_string()) + .spawn(move || { + for _ in 1..10 { + let duration = time::Duration::from_millis(step as u64); + thread::sleep(duration); - let new_id = id.lock().unwrap(); - if *new_id != current_id { - debug!("Cancelling notification close event with id {}", current_id); - return; + let new_id = id.lock().unwrap(); + if *new_id != current_id { + debug!("Cancelling notification close event with id {}", current_id); + return; + } } - } - unsafe { - close_notification(); - } - }); + unsafe { + close_notification(); + } + }); // Create and show a window notification unsafe { let message = U16CString::from_str(message).unwrap(); show_notification(message.as_ptr()); } - } fn show_menu(&self, menu: Vec) { @@ -75,14 +78,14 @@ impl super::UIManager for WindowsUIManager { for item in menu.iter() { let text = U16CString::from_str(item.item_name.clone()).unwrap_or_default(); - let mut str_buff : [u16; 100] = [0; 100]; + let mut str_buff: [u16; 100] = [0; 100]; unsafe { std::ptr::copy(text.as_ptr(), str_buff.as_mut_ptr(), text.len()); } let menu_type = match item.item_type { - MenuItemType::Button => {1}, - MenuItemType::Separator => {2}, + MenuItemType::Button => 1, + MenuItemType::Separator => 2, }; let raw_item = WindowsMenuItem { @@ -94,7 +97,9 @@ impl super::UIManager for WindowsUIManager { 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) { @@ -108,10 +113,8 @@ impl WindowsUIManager { pub fn new() -> WindowsUIManager { let id = Arc::new(Mutex::new(0)); - let manager = WindowsUIManager { - id - }; + let manager = WindowsUIManager { id }; manager } -} \ No newline at end of file +} diff --git a/src/utils.rs b/src/utils.rs index 259dc80..e619c8c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -17,9 +17,9 @@ * along with espanso. If not, see . */ -use std::path::Path; use std::error::Error; use std::fs::create_dir; +use std::path::Path; pub fn copy_dir(source_dir: &Path, dest_dir: &Path) -> Result<(), Box> { for entry in std::fs::read_dir(source_dir)? { @@ -30,8 +30,9 @@ pub fn copy_dir(source_dir: &Path, dest_dir: &Path) -> Result<(), Box let target_dir = dest_dir.join(name); create_dir(&target_dir)?; copy_dir(&entry, &target_dir)?; - }else if entry.is_file() { - let target_entry = dest_dir.join(entry.file_name().expect("Error obtaining the filename")); + } else if entry.is_file() { + let target_entry = + dest_dir.join(entry.file_name().expect("Error obtaining the filename")); std::fs::copy(entry, target_entry)?; } } @@ -42,8 +43,8 @@ pub fn copy_dir(source_dir: &Path, dest_dir: &Path) -> Result<(), Box #[cfg(test)] mod tests { use super::*; - use tempfile::TempDir; use std::fs::create_dir; + use tempfile::TempDir; #[test] 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/nested").exists()); - assert!(dest_tmp_dir.path().join("source/nested/nestedfile.txt").exists()); + assert!(dest_tmp_dir + .path() + .join("source/nested/nestedfile.txt") + .exists()); } - -} \ No newline at end of file +}