From 747be23d10490199598bf8077b3e6c4bd8c529a7 Mon Sep 17 00:00:00 2001 From: Atul Bhosale Date: Thu, 13 Feb 2020 23:34:25 +0530 Subject: [PATCH] Format using 'cargo fmt' --- build.rs | 7 +- src/bridge/linux.rs | 13 +- src/bridge/macos.rs | 17 +- src/bridge/mod.rs | 2 +- src/bridge/windows.rs | 17 +- src/check.rs | 14 +- src/clipboard/linux.rs | 28 +- src/clipboard/macos.rs | 18 +- src/clipboard/mod.rs | 2 +- src/clipboard/windows.rs | 16 +- src/config/mod.rs | 902 ++++++++++++++++++++++++++++----------- src/config/runtime.rs | 222 +++++++--- src/context/linux.rs | 47 +- src/context/macos.rs | 57 ++- src/context/mod.rs | 15 +- src/context/windows.rs | 82 ++-- src/engine.rs | 150 ++++--- src/event/manager.rs | 31 +- src/event/mod.rs | 8 +- src/extension/date.rs | 8 +- src/extension/dummy.rs | 6 +- src/extension/mod.rs | 8 +- src/extension/random.rs | 44 +- src/extension/script.rs | 54 ++- src/extension/shell.rs | 56 +-- src/keyboard/linux.rs | 17 +- src/keyboard/macos.rs | 17 +- src/keyboard/mod.rs | 22 +- src/keyboard/windows.rs | 24 +- src/main.rs | 283 ++++++------ src/matcher/mod.rs | 105 +++-- src/matcher/scrolling.rs | 121 +++--- src/package/default.rs | 319 ++++++++++---- src/package/mod.rs | 19 +- src/protocol/mod.rs | 54 ++- src/protocol/unix.rs | 49 ++- src/protocol/windows.rs | 45 +- src/render/default.rs | 221 ++++++---- src/render/mod.rs | 8 +- src/render/utils.rs | 33 +- src/sysdaemon.rs | 70 +-- src/system/linux.rs | 16 +- src/system/macos.rs | 16 +- src/system/mod.rs | 2 +- src/system/windows.rs | 14 +- src/ui/linux.rs | 27 +- src/ui/macos.rs | 56 ++- src/ui/mod.rs | 2 +- src/ui/windows.rs | 61 +-- src/utils.rs | 17 +- 50 files changed, 2116 insertions(+), 1326 deletions(-) diff --git a/build.rs b/build.rs index a415c03..5d662a4 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"); } @@ -46,10 +46,9 @@ fn print_config() { println!("cargo:rustc-link-lib=framework=Cocoa"); } -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 967aa9d..0e1322b 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); @@ -45,4 +46,4 @@ extern { pub fn trigger_shift_ins_paste(); pub fn trigger_alt_shift_ins_paste(); pub fn trigger_copy(); -} \ No newline at end of file +} diff --git a/src/bridge/macos.rs b/src/bridge/macos.rs index c698d35..1dfa5be 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); pub fn eventloop(); pub fn headless_eventloop(); @@ -46,13 +46,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); @@ -60,4 +61,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 f7a4459..7272e7c 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,8 +27,8 @@ 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) -> i32; @@ -40,8 +40,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 +50,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)); + pub fn register_keypress_callback( + cb: extern "C" fn(_self: *mut c_void, *const u16, i32, i32, i32, i32), + ); pub fn eventloop(); pub fn send_string(string: *const u16); @@ -60,4 +61,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/check.rs b/src/check.rs index 146b170..2b5e56c 100644 --- a/src/check.rs +++ b/src/check.rs @@ -27,20 +27,18 @@ pub fn check_dependencies() -> 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; } @@ -57,4 +55,4 @@ pub fn check_dependencies() -> bool { pub fn check_dependencies() -> 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 8fb76e1..6954359 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, warn}; +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(); @@ -91,6 +89,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 9dec411..ab02c09 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -19,58 +19,110 @@ 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"; 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{ true } -fn default_ipc_server_port() -> i32 { 34982 } -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 {false} -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_action_noop_interval() -> u128 { 500 } -fn default_backspace_limit() -> i32 { 3 } -fn default_restore_clipboard_delay() -> i32 { 300 } -fn default_exclude_default_entries() -> bool {false} -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 { + true +} +fn default_ipc_server_port() -> i32 { + 34982 +} +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 { + false +} +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_action_noop_interval() -> u128 { + 500 +} +fn default_backspace_limit() -> i32 { + 3 +} +fn default_restore_clipboard_delay() -> i32 { + 300 +} +fn default_exclude_default_entries() -> bool { + false +} +fn default_matches() -> Vec { + Vec::new() +} +fn default_global_vars() -> Vec { + Vec::new() +} #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Configs { -#[serde(default = "default_name")] + #[serde(default = "default_name")] pub name: String, #[serde(default = "default_parent")] @@ -101,7 +153,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, @@ -152,8 +204,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 @@ -180,7 +231,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()); @@ -188,13 +243,37 @@ 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.action_noop_interval, default_action_noop_interval()); - validate_field!(result, self.restore_clipboard_delay, default_restore_clipboard_delay()); + validate_field!( + result, + self.action_noop_interval, + default_action_noop_interval() + ); + validate_field!( + result, + self.restore_clipboard_delay, + default_restore_clipboard_delay() + ); result } @@ -203,7 +282,7 @@ impl Configs { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum BackendType { Inject, - Clipboard + Clipboard, } impl Default for BackendType { // The default backend varies based on the operating system. @@ -232,18 +311,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 { Err(ConfigLoadError::FileNotFound) } } @@ -255,9 +332,12 @@ impl Configs { merged_matches.iter().for_each(|m| { match_trigger_set.insert(m.trigger.clone()); }); - let parent_matches : Vec = self.matches.iter().filter(|&m| { - !match_trigger_set.contains(&m.trigger) - }).cloned().collect(); + let parent_matches: Vec = self + .matches + .iter() + .filter(|&m| !match_trigger_set.contains(&m.trigger)) + .cloned() + .collect(); merged_matches.extend(parent_matches); self.matches = merged_matches; @@ -268,9 +348,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; @@ -282,9 +365,12 @@ impl Configs { self.matches.iter().for_each(|m| { match_trigger_set.insert(m.trigger.clone()); }); - let default_matches : Vec = default.matches.iter().filter(|&m| { - !match_trigger_set.contains(&m.trigger) - }).cloned().collect(); + let default_matches: Vec = default + .matches + .iter() + .filter(|&m| !match_trigger_set.contains(&m.trigger)) + .cloned() + .collect(); self.matches.extend(default_matches); @@ -293,12 +379,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); - } } @@ -311,7 +399,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 @@ -345,7 +433,13 @@ 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; } @@ -353,7 +447,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 @@ -367,14 +461,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() + ) } } @@ -386,7 +485,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 @@ -402,16 +501,15 @@ impl ConfigSet { for s in specific.iter() { let has_conflicts = Self::has_conflicts(&default, &specific); if has_conflicts { - eprintln!("Warning: some triggers had conflicts and may not behave as intended"); + 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"); } } } - Ok(ConfigSet { - default, - specific - }) + Ok(ConfigSet { default, specific }) } fn reduce_configs(target: Configs, children_map: &HashMap>) -> Configs { @@ -422,7 +520,7 @@ impl ConfigSet { target.merge_config(children); } target - }else{ + } else { target } } @@ -438,7 +536,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); } } @@ -448,34 +546,31 @@ 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().map(|t| { - t.trigger.clone() - }).collect(); + let mut sorted_triggers: Vec = + default.matches.iter().map(|t| t.trigger.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().map(|t| { - t.trigger.clone() - }).collect(); + let mut specific_triggers: Vec = + s.matches.iter().map(|t| t.trigger.clone()).collect(); specific_triggers.sort(); has_conflicts |= Self::list_has_conflicts(&specific_triggers); } @@ -485,7 +580,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; @@ -494,7 +589,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 + ); } } @@ -548,18 +646,17 @@ impl Error for ConfigLoadError { } } - - #[cfg(test)] mod tests { use super::*; + use crate::matcher::{MatchContentType, TextContent}; + use std::any::Any; use std::io::Write; use tempfile::{NamedTempFile, TempDir}; - use std::any::Any; - use crate::matcher::{TextContent, 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 @@ -585,16 +682,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] @@ -613,59 +711,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); } @@ -683,7 +791,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"); @@ -710,7 +820,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); @@ -731,7 +846,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] @@ -746,21 +864,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); - }, + } } } @@ -768,117 +885,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(); - 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###" name: specific1 - "###); + "###, + ); - let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###" + let user_defined_path2 = 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" - "###); + "###, + ); - let user_defined_path = create_user_config_file(data_dir.path(), "specific1.yml", r###" + let user_defined_path = 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.trigger == "hello").is_some()); - assert!(config_set.specific[0].matches.iter().find(|x| x.trigger == ":lol").is_some()); - assert!(config_set.specific[0].matches.iter().find(|x| x.trigger == ":yess").is_some()); + assert!(config_set.specific[0] + .matches + .iter() + .find(|x| x.trigger == "hello") + .is_some()); + assert!(config_set.specific[0] + .matches + .iter() + .find(|x| x.trigger == ":lol") + .is_some()); + assert!(config_set.specific[0] + .matches + .iter() + .find(|x| x.trigger == ":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" - "###); + "###, + ); - let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###" + let user_defined_path2 = 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.trigger == ":lol" && content.replace == "newstring" - }else{ - false - } - }).is_some()); - assert!(config_set.specific[0].matches.iter().find(|x| x.trigger == ":yess").is_some()); + assert!(config_set.specific[0] + .matches + .iter() + .find(|x| { + if let MatchContentType::Text(content) = &x.content { + x.trigger == ":lol" && content.replace == "newstring" + } else { + false + } + }) + .is_some()); + assert!(config_set.specific[0] + .matches + .iter() + .find(|x| x.trigger == ":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" - "###); + "###, + ); - let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###" + let user_defined_path2 = create_user_config_file( + data_dir.path(), + "specific2.yml", + r###" name: specific1 exclude_default_entries: true @@ -886,19 +1065,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.trigger == "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.trigger == "hello" && content.replace == "newstring" + } else { + false + } + }) + .is_some()); } #[test] @@ -910,10 +1094,13 @@ mod tests { replace: "LOL" - trigger: ":yess" replace: "Bob" - "### + "###, ); - let user_defined_path2 = create_user_config_file(data_dir.path(), "specific.zzz", r###" + let user_defined_path2 = create_user_config_file( + data_dir.path(), + "specific.zzz", + r###" name: specific1 exclude_default_entries: true @@ -921,7 +1108,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); @@ -931,13 +1119,21 @@ mod tests { fn test_config_set_no_parent_configs_works_correctly() { 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###" name: specific1 - "###); + "###, + ); - let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###" + let user_defined_path2 = 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); @@ -945,97 +1141,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 - "###); + "###, + ); - 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###" 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.trigger == "hasta")); - assert!(config_set.default.matches.iter().any(|m| m.trigger == "hello")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.trigger == "hasta")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.trigger == "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 - "###); + "###, + ); - 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###" 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.trigger == "hasta")); - assert!(!config_set.default.matches.iter().any(|m| m.trigger == "hello")); - assert!(config_set.specific[0].matches.iter().any(|m| m.trigger == "hello")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.trigger == "hasta")); + assert!(!config_set + .default + .matches + .iter() + .any(|m| m.trigger == "hello")); + assert!(config_set.specific[0] + .matches + .iter() + .any(|m| m.trigger == "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 - "###); + "###, + ); - 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###" name: custom1 parent: default matches: - trigger: "hello" replace: "world" - "###); + "###, + ); - let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###" + let user_defined_path2 = 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.trigger == "hasta")); - assert!(config_set.default.matches.iter().any(|m| m.trigger == "hello")); - assert!(config_set.default.matches.iter().any(|m| m.trigger == "super")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.trigger == "hasta")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.trigger == "hello")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.trigger == "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 - "###); + "###, + ); - 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###" 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); @@ -1043,7 +1298,7 @@ mod tests { assert!(config_set.default.matches.iter().any(|m| { if let MatchContentType::Text(content) = &m.content { m.trigger == "hasta" && content.replace == "world" - }else{ + } else { false } })); @@ -1051,117 +1306,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 - "###); + "###, + ); - let package_path = create_package_file(package_dir.path(), "package1", "package.yml", r###" + let package_path = 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.trigger == "hasta")); - assert!(config_set.default.matches.iter().any(|m| m.trigger == "harry")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.trigger == "hasta")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.trigger == "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 - "###); + "###, + ); - let package_path = create_package_file(package_dir.path(), "package1", "package.yml", r###" + let package_path = 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.trigger == "hasta")); - assert!(config_set.specific[0].matches.iter().any(|m| m.trigger == "harry")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.trigger == "hasta")); + assert!(config_set.specific[0] + .matches + .iter() + .any(|m| m.trigger == "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 - "###); + "###, + ); - let package_path = create_package_file(package_dir.path(), "package1", "package.yml", r###" + let package_path = create_package_file( + package_dir.path(), + "package1", + "package.yml", + r###" name: package1 matches: - trigger: "harry" replace: "potter" - "###); + "###, + ); - let package_path2 = create_package_file(package_dir.path(), "package1", "addon.yml", r###" + let package_path2 = 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.trigger == "hasta")); - assert!(config_set.specific[0].matches.iter().any(|m| m.trigger == "harry")); - assert!(config_set.specific[0].matches.iter().any(|m| m.trigger == "ron")); + assert!(config_set + .default + .matches + .iter() + .any(|m| m.trigger == "hasta")); + assert!(config_set.specific[0] + .matches + .iter() + .any(|m| m.trigger == "harry")); + assert!(config_set.specific[0] + .matches + .iter() + .any(|m| m.trigger == "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 - "###); + "###, + ); - 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###" 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 @@ -1169,134 +1488,195 @@ mod tests { replace: Jon - trigger: acb replace: Error - "###); + "###, + ); - 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###" 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 - "###); + "###, + ); - 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###" 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 - "###); + "###, + ); - 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###" name: specific1 matches: - trigger: "bad" replace: "Conflict" - "###); - let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###" + "###, + ); + let user_defined_path2 = 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" - "###); + "###, + ); - 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###" 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" - "###); + "###, + ); - 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###" 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" - "###); + "###, + ); - 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###" exclude_default_entries: true global_vars: @@ -1304,12 +1684,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 2e2aff0..d162700 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,12 +210,14 @@ impl <'a, S: SystemManager> super::ConfigManager<'a> for RuntimeConfigManager<'a #[cfg(test)] mod tests { use super::*; - use tempfile::{NamedTempFile, TempDir}; - use crate::config::{DEFAULT_CONFIG_FILE_NAME, DEFAULT_CONFIG_FILE_CONTENT}; + use crate::config::tests::{ + create_temp_espanso_directories, create_temp_file_in_dir, create_user_config_file, + }; + use crate::config::ConfigManager; + use crate::config::{DEFAULT_CONFIG_FILE_CONTENT, DEFAULT_CONFIG_FILE_NAME}; use std::fs; use std::path::PathBuf; - use crate::config::ConfigManager; - use crate::config::tests::{create_temp_espanso_directories, create_temp_file_in_dir, create_user_config_file}; + use tempfile::{NamedTempFile, TempDir}; struct DummySystemManager { title: RefCell, @@ -229,10 +237,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()), } } @@ -251,21 +259,33 @@ mod tests { fn test_runtime_constructor_regex_load_correctly() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + let specific_path = create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: myname1 filter_exec: "Title" - "###); + "###, + ); - let specific_path2 = create_user_config_file(&data_dir.path(), "specific2.yml", r###" + let specific_path2 = create_user_config_file( + &data_dir.path(), + "specific2.yml", + r###" name: myname2 filter_title: "Yeah" filter_class: "Car" - "###); + "###, + ); - let specific_path3 = create_user_config_file(&data_dir.path(), "specific3.yml", r###" + let specific_path3 = create_user_config_file( + &data_dir.path(), + "specific3.yml", + r###" name: myname3 filter_title: "Nice" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); assert!(config_set.is_ok()); @@ -274,12 +294,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); @@ -302,21 +334,33 @@ mod tests { fn test_runtime_constructor_malformed_regexes_are_ignored() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + let specific_path = create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: myname1 filter_exec: "[`-_]" - "###); + "###, + ); - let specific_path2 = create_user_config_file(&data_dir.path(), "specific2.yml", r###" + let specific_path2 = create_user_config_file( + &data_dir.path(), + "specific2.yml", + r###" name: myname2 filter_title: "[`-_]" filter_class: "Car" - "###); + "###, + ); - let specific_path3 = create_user_config_file(&data_dir.path(), "specific3.yml", r###" + let specific_path3 = create_user_config_file( + &data_dir.path(), + "specific3.yml", + r###" name: myname3 filter_title: "Nice" - "###); + "###, + ); let config_set = ConfigSet::load(data_dir.path(), package_dir.path()); assert!(config_set.is_ok()); @@ -325,12 +369,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); @@ -353,15 +409,20 @@ mod tests { fn test_runtime_calculate_active_config_specific_title_match() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + let specific_path = create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: chrome 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); @@ -371,15 +432,20 @@ mod tests { fn test_runtime_calculate_active_config_specific_class_match() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + let specific_path = create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: chrome 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); @@ -389,15 +455,20 @@ mod tests { fn test_runtime_calculate_active_config_specific_exec_match() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + let specific_path = create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: chrome 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); @@ -407,16 +478,21 @@ mod tests { fn test_runtime_calculate_active_config_specific_multi_filter_match() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + let specific_path = create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: chrome 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); @@ -427,15 +503,20 @@ mod tests { fn test_runtime_calculate_active_config_no_match() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + let specific_path = create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: firefox 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); @@ -446,25 +527,32 @@ mod tests { fn test_runtime_active_config_cache() { let (data_dir, package_dir) = create_temp_espanso_directories(); - let specific_path = create_user_config_file(&data_dir.path(), "specific.yml", r###" + let specific_path = create_user_config_file( + &data_dir.path(), + "specific.yml", + r###" name: firefox 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 2595d1b..6bb718a 100644 --- a/src/context/linux.rs +++ b/src/context/linux.rs @@ -17,36 +17,32 @@ * 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::event::KeyModifier::*; +use crate::event::*; use log::{error, info}; use std::ffi::CStr; +use std::os::raw::{c_char, c_void}; +use std::process::exit; +use std::sync::mpsc::Sender; use std::{thread, time}; #[repr(C)] pub struct LinuxContext { - pub send_channel: Sender + pub send_channel: Sender, } impl LinuxContext { pub fn new(send_channel: Sender) -> 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"); std::process::exit(100); } - let context = Box::new(LinuxContext { - send_channel, - }); + let context = Box::new(LinuxContext { send_channel }); unsafe { let context_ptr = &*context as *const LinuxContext as *const c_void; @@ -74,18 +70,26 @@ 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, - is_modifier: i32, key_code: i32) { +extern "C" fn keypress_callback( + _self: *mut c_void, + raw_buffer: *const u8, + len: i32, + is_modifier: i32, + key_code: i32, +) { unsafe { let _self = _self as *mut LinuxContext; - if is_modifier == 0 { // Char event + if is_modifier == 0 { + // Char event // Convert the received buffer to a string let c_str = CStr::from_ptr(raw_buffer as (*const c_char)); let char_str = c_str.to_str(); @@ -95,12 +99,13 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, Ok(char_str) => { 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{ // Modifier event + } else { + // Modifier event let modifier: Option = match key_code { 133 => Some(META), 50 => Some(SHIFT), @@ -116,4 +121,4 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, } } } -} \ No newline at end of file +} diff --git a/src/context/macos.rs b/src/context/macos.rs index 6c66ed3..5bafba0 100644 --- a/src/context/macos.rs +++ b/src/context/macos.rs @@ -17,20 +17,20 @@ * 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}; use crate::event::KeyModifier::*; -use std::ffi::{CString, CStr}; +use crate::event::{ActionType, Event, KeyEvent, KeyModifier}; +use log::{error, info}; +use std::ffi::{CStr, CString}; use std::fs; -use log::{info, error}; +use std::os::raw::{c_char, c_void}; use std::process::exit; +use std::sync::mpsc::Sender; -const STATUS_ICON_BINARY : &[u8] = include_bytes!("../res/mac/icon.png"); +const STATUS_ICON_BINARY: &[u8] = include_bytes!("../res/mac/icon.png"); pub struct MacContext { - pub send_channel: Sender + pub send_channel: Sender, } impl MacContext { @@ -41,15 +41,15 @@ impl MacContext { 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); } } - let context = Box::new(MacContext { - send_channel - }); + let context = Box::new(MacContext { send_channel }); // Initialize the status icon path let espanso_dir = super::get_data_dir(); @@ -57,9 +57,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 + ); }); } @@ -70,7 +73,8 @@ 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 status_icon_path = + CString::new(status_icon_target.to_str().unwrap_or_default()).unwrap_or_default(); initialize(context_ptr, status_icon_path.as_ptr()); } @@ -88,12 +92,18 @@ impl super::Context for MacContext { // Native bridge code -extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, - is_modifier: i32, key_code: i32) { +extern "C" fn keypress_callback( + _self: *mut c_void, + raw_buffer: *const u8, + len: i32, + is_modifier: i32, + key_code: i32, +) { unsafe { let _self = _self as *mut MacContext; - if is_modifier == 0 { // Char event + if is_modifier == 0 { + // Char event // Convert the received buffer to a string let c_str = CStr::from_ptr(raw_buffer as (*const c_char)); let char_str = c_str.to_str(); @@ -103,12 +113,13 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, Ok(char_str) => { 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{ // Modifier event + } else { + // Modifier event let modifier: Option = match key_code { 0x37 => Some(META), 0x38 => Some(SHIFT), @@ -126,7 +137,7 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, } } -extern fn icon_click_callback(_self: *mut c_void) { +extern "C" fn icon_click_callback(_self: *mut c_void) { unsafe { let _self = _self as *mut MacContext; @@ -135,7 +146,7 @@ extern fn icon_click_callback(_self: *mut c_void) { } } -extern fn context_menu_click_callback(_self: *mut c_void, id: i32) { +extern "C" fn context_menu_click_callback(_self: *mut c_void, id: i32) { unsafe { let _self = _self as *mut MacContext; diff --git a/src/context/mod.rs b/src/context/mod.rs index 782538f..d84530b 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -26,10 +26,10 @@ 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::path::PathBuf; +use std::sync::mpsc::Sender; use std::sync::Once; pub trait Context { @@ -56,7 +56,7 @@ pub fn new(send_channel: Sender) -> Box { // 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."); @@ -73,7 +73,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; } } @@ -108,7 +111,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 @@ -123,4 +126,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 c6ecf57..ab051d4 100644 --- a/src/context/windows.rs +++ b/src/context/windows.rs @@ -17,17 +17,17 @@ * 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 crate::event::{ActionType, Event, KeyEvent, KeyModifier}; +use log::{error, info}; use std::ffi::c_void; -use std::{fs}; -use widestring::{U16CString, U16CStr}; -use log::{info, error}; +use std::fs; +use std::sync::mpsc::Sender; +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,26 +39,33 @@ impl WindowsContext { 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(); @@ -66,9 +73,7 @@ impl WindowsContext { let send_channel = send_channel; - let context = Box::new(WindowsContext{ - send_channel, - }); + let context = Box::new(WindowsContext { send_channel }); unsafe { let context_ptr = &*context as *const WindowsContext as *const c_void; @@ -102,12 +107,20 @@ impl super::Context for WindowsContext { // Native bridge code -extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32, - is_modifier: i32, key_code: i32, is_key_down: i32) { +extern "C" fn keypress_callback( + _self: *mut c_void, + raw_buffer: *const u16, + len: i32, + is_modifier: i32, + key_code: i32, + is_key_down: i32, +) { unsafe { let _self = _self as *mut WindowsContext; - if is_key_down != 0 { // KEY DOWN EVENT - if is_modifier == 0 { // Char event + if is_key_down != 0 { + // KEY DOWN EVENT + if is_modifier == 0 { + // Char 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); @@ -120,23 +133,25 @@ 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); - }, + error!("Unable to receive char: {}", e); + } } - }else{ + } else { error!("unable to decode widechar"); } } - }else{ // KEY UP event - if is_modifier != 0 { // Modifier event + } else { + // KEY UP event + if is_modifier != 0 { + // Modifier event let modifier: Option = match key_code { 0x5B | 0x5C => Some(META), 0x10 => Some(SHIFT), 0x12 => Some(ALT), 0x11 => Some(CTRL), - 0x08 => Some(BACKSPACE), + 0x08 => Some(BACKSPACE), _ => None, }; @@ -149,7 +164,7 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32 } } -extern fn icon_click_callback(_self: *mut c_void) { +extern "C" fn icon_click_callback(_self: *mut c_void) { unsafe { let _self = _self as *mut WindowsContext; @@ -158,12 +173,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/engine.rs b/src/engine.rs index ce3faca..2436eb3 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -17,25 +17,31 @@ * along with espanso. If not, see . */ -use crate::matcher::{Match, MatchReceiver, MatchContentType}; -use crate::keyboard::KeyboardManager; -use crate::config::ConfigManager; -use crate::config::BackendType; use crate::clipboard::ClipboardManager; -use log::{info, warn, error}; -use crate::ui::{UIManager, MenuItem, MenuItemType}; +use crate::config::BackendType; +use crate::config::ConfigManager; use crate::event::{ActionEventReceiver, ActionType}; use crate::extension::Extension; -use crate::render::{Renderer, RenderResult}; +use crate::keyboard::KeyboardManager; +use crate::matcher::{Match, MatchContentType, MatchReceiver}; +use crate::render::{RenderResult, Renderer}; +use crate::ui::{MenuItem, MenuItemType, UIManager}; +use log::{error, info, warn}; +use regex::{Captures, Regex}; use std::cell::RefCell; -use std::process::exit; use std::collections::HashMap; use std::path::PathBuf; -use regex::{Regex, Captures}; +use std::process::exit; use std::time::SystemTime; -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, @@ -43,20 +49,32 @@ pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager< renderer: &'a R, enabled: RefCell, - last_action_time: RefCell, // Used to block espanso from re-interpreting it's own inputs + last_action_time: RefCell, // Used to block espanso from re-interpreting it's own inputs action_noop_interval: u128, } -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) -> 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, + ) -> Engine<'a, S, C, M, U, R> { let enabled = RefCell::new(true); let last_action_time = RefCell::new(SystemTime::now()); let action_noop_interval = config_manager.default_config().action_noop_interval; - Engine{keyboard_manager, + Engine { + keyboard_manager, clipboard_manager, config_manager, ui_manager, @@ -71,24 +89,20 @@ 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: 999, }); - menu.push(MenuItem{ + menu.push(MenuItem { item_type: MenuItemType::Button, item_name: "Exit".to_owned(), item_id: ActionType::Exit as i32, @@ -102,10 +116,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 } } @@ -129,9 +143,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) { let config = self.config_manager.active_config(); @@ -146,13 +166,13 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa let char_count = if trailing_separator.is_none() { m.trigger.chars().count() as i32 - }else{ + } else { m.trigger.chars().count() as i32 + 1 // Count also the separator }; self.keyboard_manager.delete_string(char_count); - let mut previous_clipboard_content : Option = None; + let mut previous_clipboard_content: Option = None; let rendered = self.renderer.render_match(m, config, vec![]); @@ -160,9 +180,10 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa 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); } } @@ -185,7 +206,7 @@ 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 }; @@ -196,7 +217,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa if cfg!(target_os = "linux") { self.keyboard_manager.send_string(&target_string); - }else{ + } else { // To handle newlines, substitute each "\n" char with an Enter key press. let splits = target_string.split('\n'); @@ -208,22 +229,23 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa self.keyboard_manager.send_string(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.paste_shortcut); - }, + } } if let Some(moves) = cursor_rewind { // Simulate left arrow key presses to bring the cursor into the desired position self.keyboard_manager.move_cursor_left(moves); } - }, + } RenderResult::Image(image_path) => { // If the preserve_clipboard option is enabled, save the current // clipboard content to restore it later. @@ -231,19 +253,22 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa self.clipboard_manager.set_clipboard_image(&image_path); self.keyboard_manager.trigger_paste(&config.paste_shortcut); - }, + } RenderResult::Error => { error!("Could not render match: {}", m.trigger); - }, + } } // 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); } } @@ -255,7 +280,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa let message = if status { "espanso enabled" - }else{ + } else { "espanso disabled" }; @@ -285,14 +310,13 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa self.keyboard_manager.trigger_copy(); // 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(); if let Some(clipboard) = clipboard { - let rendered = self.renderer.render_passive(&clipboard, - &config); + let rendered = self.renderer.render_passive(&clipboard, &config); match rendered { RenderResult::Text(payload) => { @@ -301,29 +325,33 @@ 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.paste_shortcut); - }, - _ => { - warn!("Cannot expand passive match") - }, + } + _ => warn!("Cannot expand passive match"), } } } } -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) { match e { ActionType::IconClick => { self.ui_manager.show_menu(self.build_menu()); - }, + } ActionType::Exit => { info!("Terminating espanso."); self.ui_manager.cleanup(); exit(0); - }, + } _ => {} } } -} \ No newline at end of file +} diff --git a/src/event/manager.rs b/src/event/manager.rs index b6cc0b1..8adf283 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}; +use crate::event::{ActionEventReceiver, Event, KeyEventReceiver}; use std::sync::mpsc::Receiver; pub trait EventManager { @@ -31,8 +31,11 @@ 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>) -> DefaultEventManager<'a> { + pub fn new( + receive_channel: Receiver, + key_receivers: Vec<&'a dyn KeyEventReceiver>, + action_receivers: Vec<&'a dyn ActionEventReceiver>, + ) -> DefaultEventManager<'a> { DefaultEventManager { receive_channel, key_receivers, @@ -41,22 +44,24 @@ impl<'a> DefaultEventManager<'a> { } } -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())); - } + 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())); } }, 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 dc47691..5dbd644 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -19,12 +19,12 @@ pub(crate) mod manager; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; #[derive(Debug, Clone)] pub enum Event { Action(ActionType), - Key(KeyEvent) + Key(KeyEvent), } #[derive(Debug, Clone)] @@ -53,7 +53,7 @@ impl From for ActionType { #[derive(Debug, Clone)] pub enum KeyEvent { Char(String), - Modifier(KeyModifier) + Modifier(KeyModifier), } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -74,4 +74,4 @@ pub trait KeyEventReceiver { pub trait ActionEventReceiver { fn on_action_event(&self, e: ActionType); -} \ 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 047060d..800a8b7 100644 --- a/src/extension/mod.rs +++ b/src/extension/mod.rs @@ -20,10 +20,10 @@ use serde_yaml::Mapping; mod date; -mod shell; -mod script; -mod random; mod dummy; +mod random; +mod script; +mod shell; pub trait Extension { fn name(&self) -> String; @@ -38,4 +38,4 @@ pub fn get_extensions() -> Vec> { Box::new(random::RandomExtension::new()), Box::new(dummy::DummyExtension::new()), ] -} \ No newline at end of file +} diff --git a/src/extension/random.rs b/src/extension/random.rs index 6923a6e..5d168c9 100644 --- a/src/extension/random.rs +++ b/src/extension/random.rs @@ -17,15 +17,15 @@ * along with espanso. If not, see . */ -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 16bd0ce..ac4ddad 100644 --- a/src/extension/script.rs +++ b/src/extension/script.rs @@ -17,15 +17,15 @@ * along with espanso. If not, see . */ +use log::{error, warn}; use serde_yaml::{Mapping, Value}; use std::process::Command; -use log::{warn, error}; pub struct ScriptExtension {} impl ScriptExtension { pub fn new() -> ScriptExtension { - ScriptExtension{} + ScriptExtension {} } } @@ -38,28 +38,29 @@ 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()); } 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() }; println!("{:?}", output); @@ -67,12 +68,12 @@ impl super::Extension for ScriptExtension { Ok(output) => { let output_str = String::from_utf8_lossy(output.stdout.as_slice()); - return Some(output_str.into_owned()) - }, + return Some(output_str.into_owned()); + } Err(e) => { error!("Could not execute script '{:?}', error: {}", args, e); - return None - }, + return None; + } } } @@ -90,7 +91,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![]); @@ -103,7 +107,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()]); @@ -116,7 +123,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(); @@ -125,4 +135,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 8ad9798..61d6879 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; -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() }; } @@ -34,7 +34,7 @@ pub struct ShellExtension {} impl ShellExtension { pub fn new() -> ShellExtension { - ShellExtension{} + ShellExtension {} } } @@ -47,30 +47,27 @@ 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 output = if cfg!(target_os = "windows") { - Command::new("cmd") - .args(&["/C", &cmd]) - .output() + Command::new("cmd").args(&["/C", &cmd]).output() } else { - Command::new("sh") - .arg("-c") - .arg(&cmd) - .output() + Command::new("sh").arg("-c").arg(&cmd).output() }; match output { @@ -90,11 +87,11 @@ impl super::Extension for ShellExtension { } Some(output_str) - }, + } Err(e) => { error!("Could not execute cmd '{}', error: {}", cmd, e); None - }, + } } } } @@ -116,7 +113,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"); } } @@ -139,8 +136,11 @@ mod tests { let mut params = Mapping::new(); if cfg!(target_os = "windows") { params.insert(Value::from("cmd"), Value::from("echo hello world ")); - }else{ - params.insert(Value::from("cmd"), Value::from("echo \" hello world \"")); + } else { + params.insert( + Value::from("cmd"), + Value::from("echo \" hello world \""), + ); } params.insert(Value::from("trim"), Value::from(true)); @@ -164,7 +164,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"); } } @@ -210,4 +210,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 78e0961..65f6deb 100644 --- a/src/keyboard/linux.rs +++ b/src/keyboard/linux.rs @@ -17,20 +17,21 @@ * along with espanso. If not, see . */ -use std::ffi::CString; -use crate::bridge::linux::*; use super::PasteShortcut; +use crate::bridge::linux::*; use log::error; +use std::ffi::CString; -pub struct LinuxKeyboardManager { -} +pub struct LinuxKeyboardManager {} impl super::KeyboardManager for LinuxKeyboardManager { fn send_string(&self, 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()), } } @@ -73,7 +74,7 @@ impl super::KeyboardManager for LinuxKeyboardManager { } fn delete_string(&self, count: i32) { - unsafe {delete_string(count)} + unsafe { delete_string(count) } } fn move_cursor_left(&self, count: i32) { @@ -87,4 +88,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 ccc4faf..a4fe248 100644 --- a/src/keyboard/macos.rs +++ b/src/keyboard/macos.rs @@ -17,20 +17,21 @@ * along with espanso. If not, see . */ -use std::ffi::CString; -use crate::bridge::macos::*; use super::PasteShortcut; +use crate::bridge::macos::*; use log::error; +use std::ffi::CString; -pub struct MacKeyboardManager { -} +pub struct MacKeyboardManager {} impl super::KeyboardManager for MacKeyboardManager { fn send_string(&self, 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()), } } @@ -63,7 +64,7 @@ impl super::KeyboardManager for MacKeyboardManager { } fn delete_string(&self, count: i32) { - unsafe {delete_string(count)} + unsafe { delete_string(count) } } fn move_cursor_left(&self, count: i32) { @@ -72,4 +73,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 66ebe77..e92dcae 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -17,7 +17,7 @@ * along with espanso. If not, see . */ -use serde::{Serialize, Deserialize, Deserializer}; +use serde::{Deserialize, Deserializer, Serialize}; #[cfg(target_os = "windows")] mod windows; @@ -39,14 +39,14 @@ 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 - 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 + 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 } @@ -55,17 +55,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 c7ee10a..b1436aa 100644 --- a/src/keyboard/windows.rs +++ b/src/keyboard/windows.rs @@ -17,26 +17,22 @@ * along with espanso. If not, see . */ -use widestring::{U16CString}; -use crate::bridge::windows::*; use super::PasteShortcut; +use crate::bridge::windows::*; use log::error; +use widestring::U16CString; -pub struct WindowsKeyboardManager { -} +pub struct WindowsKeyboardManager {} impl super::KeyboardManager for WindowsKeyboardManager { fn send_string(&self, 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) { @@ -62,9 +58,7 @@ impl super::KeyboardManager for WindowsKeyboardManager { } fn delete_string(&self, count: i32) { - unsafe { - delete_string(count) - } + unsafe { delete_string(count) } } fn move_cursor_left(&self, count: i32) { @@ -79,4 +73,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 12c7561..f6d9bb4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,48 +20,48 @@ #[macro_use] extern crate lazy_static; -use std::thread; use std::fs::{File, OpenOptions}; use std::process::exit; use std::sync::mpsc; use std::sync::mpsc::Receiver; +use std::thread; use std::time::Duration; -use clap::{App, Arg, SubCommand, ArgMatches}; +use clap::{App, Arg, ArgMatches, SubCommand}; use fs2::FileExt; use log::{info, warn, LevelFilter}; -use simplelog::{CombinedLogger, SharedLogger, TerminalMode, TermLogger, WriteLogger}; +use simplelog::{CombinedLogger, SharedLogger, TermLogger, TerminalMode, WriteLogger}; -use crate::config::{ConfigSet, ConfigManager}; use crate::config::runtime::RuntimeConfigManager; +use crate::config::{ConfigManager, ConfigSet}; use crate::engine::Engine; -use crate::event::*; use crate::event::manager::{DefaultEventManager, EventManager}; +use crate::event::*; use crate::matcher::scrolling::ScrollingMatcher; +use crate::package::default::DefaultPackageManager; +use crate::package::{InstallResult, PackageManager, RemoveResult, UpdateResult}; +use crate::protocol::*; use crate::system::SystemManager; use crate::ui::UIManager; -use crate::protocol::*; -use std::io::{BufReader, BufRead}; -use crate::package::default::DefaultPackageManager; -use crate::package::{PackageManager, InstallResult, UpdateResult, RemoveResult}; +use std::io::{BufRead, BufReader}; -mod ui; -mod event; -mod check; -mod utils; mod bridge; -mod engine; +mod check; +mod clipboard; mod config; -mod render; -mod system; mod context; +mod engine; +mod event; +mod extension; +mod keyboard; mod matcher; mod package; -mod keyboard; mod protocol; -mod clipboard; -mod extension; +mod render; mod sysdaemon; +mod system; +mod ui; +mod utils; const VERSION: &str = env!("CARGO_PKG_VERSION"); const LOG_FILE: &str = "espanso.log"; @@ -69,13 +69,11 @@ 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("package_name") - .help("Package name")); + .arg(Arg::with_name("package_name").help("Package name")); 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) @@ -248,7 +246,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!(); } @@ -273,8 +273,8 @@ fn daemon_main(config_set: ConfigSet) { 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); } @@ -292,16 +292,20 @@ fn daemon_main(config_set: ConfigSet) { 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(); 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() + ); info!("starting daemon..."); let (send_channel, receive_channel) = mpsc::channel(); @@ -309,9 +313,12 @@ fn daemon_main(config_set: ConfigSet) { let context = context::new(send_channel.clone()); let config_set_copy = config_set.clone(); - thread::Builder::new().name("daemon_background".to_string()).spawn(move || { - daemon_background(receive_channel, config_set_copy); - }).expect("Unable to spawn daemon background thread"); + thread::Builder::new() + .name("daemon_background".to_string()) + .spawn(move || { + daemon_background(receive_channel, config_set_copy); + }) + .expect("Unable to spawn daemon background thread"); let ipc_server = protocol::get_ipc_server(config_set, send_channel.clone()); ipc_server.start(); @@ -333,23 +340,21 @@ fn daemon_background(receive_channel: Receiver, config_set: ConfigSet) { let extensions = extension::get_extensions(); - 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, + let engine = Engine::new( + &keyboard_manager, + &clipboard_manager, + &config_manager, + &ui_manager, + &renderer, ); let matcher = ScrollingMatcher::new(&config_manager, &engine); - let event_manager = DefaultEventManager::new( - receive_channel, - vec!(&matcher), - vec!(&engine, &matcher), - ); + let event_manager = + DefaultEventManager::new(receive_channel, vec![&matcher], vec![&engine, &matcher]); info!("espanso is running!"); @@ -393,13 +398,13 @@ 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); } } @@ -454,13 +459,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."); } @@ -477,7 +482,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); } @@ -510,12 +516,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 @@ -526,15 +531,18 @@ fn stop_main(config_set: ConfigSet) { exit(3); } - let res = send_command(config_set, IPCCommand{ - id: "exit".to_owned(), - payload: "".to_owned(), - }); + let res = send_command( + config_set, + IPCCommand { + id: "exit".to_owned(), + payload: "".to_owned(), + }, + ); if let Err(e) = res { println!("{}", e); exit(1); - }else{ + } else { exit(0); } } @@ -545,11 +553,15 @@ fn restart_main(config_set: ConfigSet) { let lock_file = acquire_lock(); if lock_file.is_none() { // Terminate the current espanso daemon - send_command(config_set.clone(), IPCCommand{ - id: "exit".to_owned(), - payload: "".to_owned(), - }).unwrap_or_else(|e| warn!("Unable to send IPC command to daemon: {}", e)); - }else{ + send_command( + config_set.clone(), + IPCCommand { + id: "exit".to_owned(), + payload: "".to_owned(), + }, + ) + .unwrap_or_else(|e| warn!("Unable to send IPC command to daemon: {}", e)); + } else { release_lock(lock_file.unwrap()); } @@ -568,14 +580,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 { @@ -600,23 +618,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 { @@ -647,22 +673,22 @@ fn cmd_main(config_set: ConfigSet, matches: &ArgMatches) { id: String::from("exit"), payload: String::from(""), }) - }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 }; @@ -671,7 +697,7 @@ fn cmd_main(config_set: ConfigSet, matches: &ArgMatches) { if res.is_ok() { exit(0); - }else{ + } else { println!("{}", res.unwrap_err()); } } @@ -703,7 +729,7 @@ fn log_main() { } exit(0); - }else{ + } else { println!("Error reading log file"); exit(1); } @@ -730,22 +756,20 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) { 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.") } @@ -756,30 +780,30 @@ fn install_main(_config_set: ConfigSet, matches: &ArgMatches) { 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::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); - }, + } } } @@ -794,22 +818,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); - }, + } } } @@ -819,20 +841,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); - }, + } } } @@ -845,7 +865,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); } @@ -859,21 +879,20 @@ 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()); } } - fn acquire_lock() -> Option { let espanso_dir = context::get_data_dir(); let lock_file_path = espanso_dir.join("espanso.lock"); @@ -887,7 +906,7 @@ fn acquire_lock() -> Option { let res = file.try_lock_exclusive(); if res.is_ok() { - return Some(file) + return Some(file); } None @@ -905,4 +924,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 1c8916b..526b165 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; @@ -59,16 +59,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,15 +79,15 @@ impl<'a> From<&'a AutoMatch> for Match{ // Calculate the trigger sequence let mut trigger_sequence = Vec::new(); - let trigger_chars : Vec = other.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 + let trigger_chars: Vec = other.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 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 @@ -99,11 +100,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() }; @@ -113,20 +115,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); }; @@ -162,11 +164,21 @@ struct AutoMatch { pub passive_only: bool, } -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_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 +} #[derive(Debug, Serialize, Deserialize, Clone)] pub struct MatchVariable { @@ -181,7 +193,7 @@ pub struct MatchVariable { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub enum TriggerEntry { Char(char), - WordSeparator + WordSeparator, } pub trait MatchReceiver { @@ -190,25 +202,24 @@ 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); } -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); - }, + } } } } - // TESTS #[cfg(test)] @@ -222,15 +233,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); - }, + } } } @@ -241,15 +252,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); - }, + } } } @@ -260,15 +271,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); - }, + } } } @@ -279,7 +290,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_sequence[0], TriggerEntry::Char('t')); assert_eq!(_match._trigger_sequence[1], TriggerEntry::Char('e')); @@ -295,7 +306,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_sequence[0], TriggerEntry::Char('t')); assert_eq!(_match._trigger_sequence[1], TriggerEntry::Char('e')); @@ -311,15 +322,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); - }, + } } } -} \ No newline at end of file +} diff --git a/src/matcher/scrolling.rs b/src/matcher/scrolling.rs index ce1f951..9a1cef8 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, Ref}; -use crate::event::{KeyModifier, ActionEventReceiver, ActionType}; use crate::config::ConfigManager; use crate::event::KeyModifier::BACKSPACE; -use std::time::SystemTime; +use crate::event::{ActionEventReceiver, ActionType, KeyModifier}; +use crate::matcher::{Match, MatchReceiver, TriggerEntry}; +use std::cell::{Ref, RefCell}; use std::collections::VecDeque; +use std::time::SystemTime; pub struct ScrollingMatcher<'a, R: MatchReceiver, M: ConfigManager<'a>> { config_manager: &'a M, @@ -39,16 +39,16 @@ pub struct ScrollingMatcher<'a, R: MatchReceiver, M: ConfigManager<'a>> { struct MatchEntry<'a> { start: usize, count: usize, - _match: &'a Match + _match: &'a Match, } -impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> { +impl<'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> { 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, @@ -73,19 +73,20 @@ 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, is_current_word_separator: bool) -> bool { + fn is_matching( + mtc: &Match, + current_char: &str, + start: usize, + is_current_word_separator: bool, + ) -> bool { match mtc._trigger_sequence[start] { - 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()) { @@ -97,9 +98,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") { @@ -112,7 +113,9 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa let mut current_set_queue = self.current_set_queue.borrow_mut(); - let new_matches: Vec = active_config.matches.iter() + let new_matches: Vec = active_config + .matches + .iter() .filter(|&x| { // only active-enabled matches are considered if x.passive_only { @@ -127,31 +130,30 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa result }) - .map(|x | MatchEntry{ + .map(|x| MatchEntry { start: 1, count: x._trigger_sequence.len(), - _match: &x + _match: &x, }) .collect(); // TODO: use an associative structure to improve the efficiency of this first "new_matches" lookup. let combined_matches: Vec = match current_set_queue.back_mut() { Some(last_matches) => { - let mut updated: Vec = last_matches.iter() - .filter(|&x| { - Self::is_matching(x._match, c, x.start, is_current_word_separator) - }) - .map(|x | MatchEntry{ - start: x.start+1, + let mut updated: Vec = last_matches + .iter() + .filter(|&x| Self::is_matching(x._match, c, x.start, is_current_word_separator)) + .map(|x| MatchEntry { + start: x.start + 1, count: x.count, - _match: &x._match + _match: &x._match, }) .collect(); updated.extend(new_matches); updated - }, - None => {new_matches}, + } + None => new_matches, }; let mut found_match = None; @@ -165,7 +167,9 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa current_set_queue.push_back(combined_matches); - 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(); } @@ -181,13 +185,13 @@ 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, } }; @@ -205,21 +209,27 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa // study a mechanism to avoid this problem if m == config.toggle_key { - check_interval(&self.toggle_press_time, - u128::from(config.toggle_interval), || { - self.toggle(); + 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 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 m == config.passive_key { + check_interval( + &self.passive_press_time, + u128::from(config.toggle_interval), + || { + self.receiver.on_passive(); + }, + ); } // Backspace handling, basically "rewinding history" @@ -230,24 +240,29 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa } } -impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ActionEventReceiver for ScrollingMatcher<'a, R, M> { +impl<'a, R: MatchReceiver, M: ConfigManager<'a>> ActionEventReceiver + for ScrollingMatcher<'a, R, M> +{ fn on_action_event(&self, e: ActionType) { 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 { @@ -256,4 +271,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 dfbff4a..1e94591 100644 --- a/src/package/default.rs +++ b/src/package/default.rs @@ -17,22 +17,22 @@ * along with espanso. If not, see . */ -use std::path::{PathBuf, Path}; -use crate::package::{PackageIndex, UpdateResult, Package, InstallResult, RemoveResult}; -use std::error::Error; -use std::fs::{File, create_dir}; -use std::io::{BufReader, BufRead}; -use std::time::{SystemTime, UNIX_EPOCH}; +use crate::package::InstallResult::{AlreadyInstalled, NotFoundInIndex}; +use crate::package::RemoveResult::Removed; use crate::package::UpdateResult::{NotOutdated, Updated}; -use crate::package::InstallResult::{NotFoundInIndex, AlreadyInstalled}; -use std::fs; -use tempfile::TempDir; +use crate::package::{InstallResult, Package, PackageIndex, RemoveResult, UpdateResult}; use git2::Repository; use regex::Regex; -use crate::package::RemoveResult::Removed; use std::collections::HashMap; +use std::error::Error; +use std::fs; +use std::fs::{create_dir, File}; +use std::io::{BufRead, BufReader}; +use std::path::{Path, PathBuf}; +use std::time::{SystemTime, UNIX_EPOCH}; +use tempfile::TempDir; -const DEFAULT_PACKAGE_INDEX_FILE : &str = "package_index.json"; +const DEFAULT_PACKAGE_INDEX_FILE: &str = "package_index.json"; pub struct DefaultPackageManager { package_dir: PathBuf, @@ -45,17 +45,17 @@ impl DefaultPackageManager { pub fn new(package_dir: PathBuf, data_dir: PathBuf) -> DefaultPackageManager { let local_index = Self::load_local_index(&data_dir); - DefaultPackageManager{ + DefaultPackageManager { package_dir, data_dir, - local_index + local_index, } } pub fn new_default() -> DefaultPackageManager { DefaultPackageManager::new( crate::context::get_package_dir(), - crate::context::get_data_dir() + crate::context::get_data_dir(), ) } @@ -70,7 +70,7 @@ impl DefaultPackageManager { let local_index = serde_json::from_reader(reader); if let Ok(local_index) = local_index { - return local_index + return local_index; } } @@ -79,12 +79,13 @@ 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) } @@ -97,7 +98,8 @@ impl DefaultPackageManager { 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 @@ -105,7 +107,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; @@ -113,30 +115,33 @@ 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 package = Package { @@ -145,18 +150,18 @@ impl DefaultPackageManager { version: fields.get("package_version").unwrap().clone(), repo: fields.get("package_repo").unwrap().clone(), desc: fields.get("package_desc").unwrap().clone(), - author: fields.get("package_author").unwrap().clone() + author: fields.get("package_author").unwrap().clone(), }; Some(package) - }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 @@ -184,7 +189,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"); } @@ -193,13 +199,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> { @@ -211,18 +219,19 @@ 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()); } } @@ -232,19 +241,20 @@ impl super::PackageManager for DefaultPackageManager { fn install_package(&self, name: &str) -> Result> { let package = self.get_package(name); match package { - Some(package) => { - self.install_package_from_repo(name, &package.repo) - }, - None => { - Ok(NotFoundInIndex) - }, + Some(package) => self.install_package_from_repo(name, &package.repo), + 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); } @@ -311,15 +321,16 @@ impl super::PackageManager for DefaultPackageManager { #[cfg(test)] mod tests { use super::*; - use tempfile::{TempDir, NamedTempFile}; - use std::path::Path; + use crate::package::InstallResult::*; use crate::package::PackageManager; use std::fs::{create_dir, create_dir_all}; - use crate::package::InstallResult::*; use std::io::Write; + use std::path::Path; + use tempfile::{NamedTempFile, TempDir}; - const OUTDATED_INDEX_CONTENT : &str = include_str!("../res/test/outdated_index.json"); - const 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"); @@ -329,7 +340,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"); @@ -337,13 +351,13 @@ mod tests { let package_manager = DefaultPackageManager::new( package_dir.path().clone().to_path_buf(), - data_dir.path().clone().to_path_buf() + data_dir.path().clone().to_path_buf(), ); TempPackageManager { package_dir, data_dir, - package_manager + package_manager, } } @@ -370,26 +384,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); }); - 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); }); - assert_eq!(temp.package_manager.update_index(true).unwrap(), UpdateResult::Updated); + assert_eq!( + temp.package_manager.update_index(true).unwrap(), + UpdateResult::Updated + ); } #[test] @@ -399,15 +425,25 @@ mod tests { std::fs::write(index_file, OUTDATED_INDEX_CONTENT); }); - assert_eq!(temp.package_manager.update_index(false).unwrap(), UpdateResult::Updated); + assert_eq!( + temp.package_manager.update_index(false).unwrap(), + UpdateResult::Updated + ); } #[test] 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] @@ -417,7 +453,13 @@ mod tests { std::fs::write(index_file, GET_PACKAGE_INDEX); }); - assert_eq!(temp.package_manager.get_package("italian-accents").unwrap().title, "Italian Accents"); + assert_eq!( + temp.package_manager + .get_package("italian-accents") + .unwrap() + .title, + "Italian Accents" + ); } #[test] @@ -451,7 +493,12 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX); }); - assert_eq!(temp.package_manager.install_package("doesnotexist").unwrap(), NotFoundInIndex); + assert_eq!( + temp.package_manager + .install_package("doesnotexist") + .unwrap(), + NotFoundInIndex + ); } #[test] @@ -462,12 +509,20 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX); }); - assert_eq!(temp.package_manager.install_package("italian-accents").unwrap(), AlreadyInstalled); + assert_eq!( + temp.package_manager + .install_package("italian-accents") + .unwrap(), + AlreadyInstalled + ); } #[test] fn test_clone_temp_repository() { - let cloned_dir = DefaultPackageManager::clone_repo_to_temp("https://github.com/federico-terzi/espanso-hub-core").unwrap(); + let cloned_dir = DefaultPackageManager::clone_repo_to_temp( + "https://github.com/federico-terzi/espanso-hub-core", + ) + .unwrap(); assert!(cloned_dir.path().join("LICENSE").exists()); } @@ -478,10 +533,23 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX); }); - assert_eq!(temp.package_manager.install_package("dummy-package").unwrap(), Installed); + assert_eq!( + temp.package_manager + .install_package("dummy-package") + .unwrap(), + Installed + ); assert!(temp.package_dir.path().join("dummy-package").exists()); - assert!(temp.package_dir.path().join("dummy-package/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] @@ -491,7 +559,12 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX); }); - assert_eq!(temp.package_manager.install_package("not-existing").unwrap(), NotFoundInRepo); + assert_eq!( + temp.package_manager + .install_package("not-existing") + .unwrap(), + NotFoundInRepo + ); } #[test] @@ -501,7 +574,12 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX); }); - assert_eq!(temp.package_manager.install_package("dummy-package2").unwrap(), MissingPackageVersion); + assert_eq!( + temp.package_manager + .install_package("dummy-package2") + .unwrap(), + MissingPackageVersion + ); } #[test] @@ -511,7 +589,12 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX); }); - assert_eq!(temp.package_manager.install_package("dummy-package3").unwrap(), UnableToParsePackageInfo); + assert_eq!( + temp.package_manager + .install_package("dummy-package3") + .unwrap(), + UnableToParsePackageInfo + ); } #[test] @@ -521,7 +604,12 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX); }); - assert_eq!(temp.package_manager.install_package("dummy-package4").unwrap(), UnableToParsePackageInfo); + assert_eq!( + temp.package_manager + .install_package("dummy-package4") + .unwrap(), + UnableToParsePackageInfo + ); } #[test] @@ -531,10 +619,23 @@ mod tests { std::fs::write(index_file, INSTALL_PACKAGE_INDEX); }); - assert_eq!(temp.package_manager.install_package("dummy-package").unwrap(), Installed); + assert_eq!( + temp.package_manager + .install_package("dummy-package") + .unwrap(), + Installed + ); assert!(temp.package_dir.path().join("dummy-package").exists()); - assert!(temp.package_dir.path().join("dummy-package/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); @@ -551,25 +652,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 mut temp = create_temp_package_manager(|_, _| {}); - assert_eq!(temp.package_manager.remove_package("not-existing").unwrap(), RemoveResult::NotFound); + assert_eq!( + temp.package_manager.remove_package("not-existing").unwrap(), + RemoveResult::NotFound + ); } #[test] 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" @@ -578,7 +705,8 @@ mod tests { package_author: "Federico Terzi" package_repo: "https://github.com/federico-terzi/espanso-hub-core" --- - "###); + "###, + ); let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap(); @@ -588,7 +716,7 @@ mod tests { version: "0.1.0".to_string(), repo: "https://github.com/federico-terzi/espanso-hub-core".to_string(), desc: "Include Italian accents substitutions to espanso.".to_string(), - author: "Federico Terzi".to_string() + author: "Federico Terzi".to_string(), }; assert_eq!(package, target_package); @@ -597,7 +725,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" @@ -607,7 +737,8 @@ mod tests { package_repo: "https://github.com/federico-terzi/espanso-hub-core" --- Readme text - "###); + "###, + ); let package = DefaultPackageManager::parse_package_from_readme(file.path()).unwrap(); @@ -617,9 +748,9 @@ mod tests { version: "0.1.0".to_string(), repo: "https://github.com/federico-terzi/espanso-hub-core".to_string(), desc: "Include Italian accents substitutions to espanso.".to_string(), - author: "Federico Terzi".to_string() + author: "Federico Terzi".to_string(), }; assert_eq!(package, target_package); } -} \ No newline at end of file +} diff --git a/src/package/mod.rs b/src/package/mod.rs index 3ba505d..9c68fd4 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -18,7 +18,7 @@ */ pub(crate) mod default; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use std::error::Error; pub trait PackageManager { @@ -28,7 +28,11 @@ pub trait PackageManager { fn get_package(&self, name: &str) -> Option; fn install_package(&self, name: &str) -> Result>; - fn install_package_from_repo(&self, name: &str, repo_url: &str) -> Result>; + fn install_package_from_repo( + &self, + name: &str, + repo_url: &str, + ) -> Result>; fn remove_package(&self, name: &str) -> Result>; @@ -42,7 +46,7 @@ pub struct Package { pub version: String, pub repo: String, pub desc: String, - pub author: String + pub author: String, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] @@ -50,10 +54,9 @@ pub struct PackageIndex { #[serde(rename = "lastUpdate")] pub last_update: u64, - pub packages: Vec + pub packages: Vec, } - #[derive(Clone, Debug, PartialEq)] pub enum UpdateResult { NotOutdated, @@ -67,11 +70,11 @@ pub enum InstallResult { UnableToParsePackageInfo, MissingPackageVersion, AlreadyInstalled, - Installed + Installed, } #[derive(Clone, Debug, PartialEq)] pub enum RemoveResult { NotFound, - Removed -} \ No newline at end of file + Removed, +} diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 7818dce..68a3077 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; -use crate::event::ActionType; -use std::io::{BufReader, Read, Write}; -use std::error::Error; -use log::error; use crate::config::ConfigSet; +use crate::event::ActionType; +use crate::event::Event; +use log::error; +use serde::{Deserialize, Serialize}; +use std::error::Error; +use std::io::{BufReader, Read, Write}; +use std::sync::mpsc::Sender; #[cfg(target_os = "windows")] mod windows; @@ -51,19 +51,11 @@ pub struct IPCCommand { impl IPCCommand { fn to_event(&self) -> Option { match self.id.as_ref() { - "exit" => { - Some(Event::Action(ActionType::Exit)) - }, - "toggle" => { - Some(Event::Action(ActionType::Toggle)) - }, - "enable" => { - Some(Event::Action(ActionType::Enable)) - }, - "disable" => { - Some(Event::Action(ActionType::Disable)) - }, - _ => None + "exit" => Some(Event::Action(ActionType::Exit)), + "toggle" => Some(Event::Action(ActionType::Toggle)), + "enable" => Some(Event::Action(ActionType::Enable)), + "disable" => Some(Event::Action(ActionType::Disable)), + _ => None, } } } @@ -71,22 +63,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); - }, + } } } } @@ -96,7 +89,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); @@ -104,12 +100,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()) @@ -135,4 +129,4 @@ pub fn get_ipc_server(config_set: ConfigSet, event_channel: Sender) -> im #[cfg(target_os = "windows")] pub fn get_ipc_client(config_set: ConfigSet) -> impl IPCClient { windows::WindowsIPCClient::new(config_set) -} \ No newline at end of file +} diff --git a/src/protocol/unix.rs b/src/protocol/unix.rs index abdb08a..dcd51d5 100644 --- a/src/protocol/unix.rs +++ b/src/protocol/unix.rs @@ -17,16 +17,16 @@ * 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 crate::context; use crate::event::*; use crate::protocol::{process_event, send_command}; -const UNIX_SOCKET_NAME : &str = "espanso.sock"; +const UNIX_SOCKET_NAME: &str = "espanso.sock"; pub struct UnixIPCServer { event_channel: Sender, @@ -34,38 +34,43 @@ pub struct UnixIPCServer { impl UnixIPCServer { pub fn new(event_channel: Sender) -> UnixIPCServer { - UnixIPCServer {event_channel} + UnixIPCServer { event_channel } } } impl super::IPCServer for UnixIPCServer { fn start(&self) { let event_channel = self.event_channel.clone(); - std::thread::Builder::new().name("ipc_server".to_string()).spawn(move || { - let espanso_dir = context::get_data_dir(); - let unix_socket = espanso_dir.join(UNIX_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(UNIX_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"); } } -pub struct UnixIPCClient { - -} +pub struct UnixIPCClient {} impl UnixIPCClient { pub fn new() -> UnixIPCClient { - UnixIPCClient{} + UnixIPCClient {} } } @@ -79,4 +84,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 8cabd30..26ece0e 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::ConfigSet; use crate::event::*; use crate::protocol::{process_event, send_command}; -use crate::config::ConfigSet; pub struct WindowsIPCServer { config_set: ConfigSet, @@ -33,7 +33,10 @@ pub struct WindowsIPCServer { impl WindowsIPCServer { pub fn new(config_set: ConfigSet, event_channel: Sender) -> WindowsIPCServer { - WindowsIPCServer {config_set, event_channel} + WindowsIPCServer { + config_set, + event_channel, + } } } @@ -41,17 +44,22 @@ impl super::IPCServer for WindowsIPCServer { fn start(&self) { let event_channel = self.event_channel.clone(); let server_port = self.config_set.default.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"); + 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"); } } @@ -61,16 +69,15 @@ pub struct WindowsIPCClient { impl WindowsIPCClient { pub fn new(config_set: ConfigSet) -> WindowsIPCClient { - WindowsIPCClient{config_set} + WindowsIPCClient { config_set } } } impl super::IPCClient for WindowsIPCClient { fn send_command(&self, command: IPCCommand) -> Result<(), String> { - let stream = TcpStream::connect( - ("127.0.0.1", self.config_set.default.ipc_server_port as u16) - ); + let stream = + TcpStream::connect(("127.0.0.1", self.config_set.default.ipc_server_port as u16)); send_command(command, stream) } -} \ No newline at end of file +} diff --git a/src/render/default.rs b/src/render/default.rs index feafac6..f059d01 100644 --- a/src/render/default.rs +++ b/src/render/default.rs @@ -17,15 +17,15 @@ * along with espanso. If not, see . */ -use serde_yaml::{Mapping, Value}; -use std::path::PathBuf; -use std::collections::HashMap; -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::{Mapping, Value}; +use std::collections::HashMap; +use std::path::PathBuf; lazy_static! { static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P\\w+)\\s*\\}\\}").unwrap(); @@ -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"); - }); + let passive_match_regex = Regex::new(&config.passive_match_regex).unwrap_or_else(|e| { + panic!("Invalid passive match regex"); + }); - DefaultRenderer{ + DefaultRenderer { extension_map, passive_match_regex, } @@ -79,7 +78,7 @@ impl super::Renderer for DefaultRenderer { match &m.content { // Text Match MatchContentType::Text(content) => { - let target_string = if content._has_vars || !config.global_vars.is_empty(){ + let target_string = if content._has_vars || !config.global_vars.is_empty() { let mut output_map = HashMap::new(); // Cycle through both the local and global variables @@ -90,17 +89,24 @@ 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 = inner_match.unwrap(); @@ -118,18 +124,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 + ); } } } @@ -142,7 +155,8 @@ impl super::Renderer for DefaultRenderer { }); result.to_string() - }else{ // No variables, simple text substitution + } else { + // No variables, simple text substitution content.replace.clone() }; @@ -150,65 +164,64 @@ impl super::Renderer for DefaultRenderer { let target_string = utils::render_args(&target_string, &args); 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 = m.unwrap(); - // Render the actual match - let result = self.render_match(&m, &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 = m.unwrap(); + // Render the actual match + let result = self.render_match(&m, &config, args); + + match result { + RenderResult::Text(out) => out, + _ => original_match.to_owned(), + } + }); RenderResult::Text(result.into_owned()) } @@ -225,7 +238,7 @@ mod tests { } 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 } @@ -233,10 +246,8 @@ mod tests { match rendered { RenderResult::Text(rendered) => { assert_eq!(rendered, target); - }, - _ => { - assert!(false) } + _ => assert!(false), } } @@ -246,11 +257,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()); @@ -263,11 +276,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()); @@ -280,11 +295,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()); @@ -299,15 +316,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()); @@ -320,7 +339,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}}" @@ -332,7 +352,8 @@ mod tests { - trigger: ':name' replace: john - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -345,11 +366,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()); @@ -362,11 +385,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()); @@ -379,11 +404,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()); @@ -396,11 +423,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()); @@ -413,7 +442,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}}" @@ -422,7 +452,8 @@ mod tests { type: dummy params: echo: "result" - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -435,7 +466,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 @@ -445,7 +477,8 @@ mod tests { - trigger: ':test' replace: "my {{output}}" - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -458,7 +491,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 @@ -473,7 +507,8 @@ mod tests { params: echo: "local" - "###); + "###, + ); let renderer = get_renderer(config.clone()); @@ -481,4 +516,4 @@ mod tests { verify_render(rendered, "this is my local"); } -} \ No newline at end of file +} diff --git a/src/render/mod.rs b/src/render/mod.rs index 80bf645..ef1b4f3 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -17,9 +17,9 @@ * 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; @@ -35,5 +35,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 b13e0dc..f5ff029 100644 --- a/src/sysdaemon.rs +++ b/src/sysdaemon.rs @@ -24,9 +24,9 @@ use crate::config::ConfigSet; // 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) { @@ -44,13 +44,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"); @@ -60,8 +68,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()]) @@ -71,7 +79,7 @@ pub fn register(_config_set: ConfigSet) { if status.success() { println!("Entry loaded correctly!") } - }else{ + } else { println!("Error loading new entry"); } } @@ -94,7 +102,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"); } } @@ -102,9 +110,9 @@ 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(config_set: ConfigSet) { @@ -125,7 +133,7 @@ pub fn register(config_set: ConfigSet) { eprintln!(" espanso unregister"); std::process::exit(5); } - }else{ + } else { if output == "disabled" { use dialoguer::Confirmation; if !Confirmation::new() @@ -153,15 +161,24 @@ pub fn register(config_set: 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!") } @@ -176,7 +193,7 @@ pub fn register(config_set: ConfigSet) { if status.success() { println!("Service registered correctly!") } - }else{ + } else { println!("Error loading espanso service"); } } @@ -202,13 +219,16 @@ pub fn unregister(config_set: 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"); } } @@ -223,4 +243,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 fc867d1..421ed1b 100644 --- a/src/system/macos.rs +++ b/src/system/macos.rs @@ -19,12 +19,10 @@ use std::os::raw::c_char; -use std::ffi::CStr; use crate::bridge::macos::{get_active_app_bundle, get_active_app_identifier}; +use std::ffi::CStr; -pub struct MacSystemManager { - -} +pub struct MacSystemManager {} impl super::SystemManager for MacSystemManager { fn get_current_window_title(&self) -> Option { @@ -33,7 +31,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 +49,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,8 +68,6 @@ impl super::SystemManager for MacSystemManager { impl MacSystemManager { pub fn new() -> MacSystemManager { - MacSystemManager{ - - } + MacSystemManager {} } -} \ No newline at end of file +} diff --git a/src/system/mod.rs b/src/system/mod.rs index fadcf5a..a93c73b 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 f45d65a..24d87d8 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, @@ -31,9 +31,15 @@ pub struct LinuxUIManager { impl super::UIManager for LinuxUIManager { fn notify(&self, message: &str) { let res = Command::new("notify-send") - .args(&["-i", self.icon_path.to_str().unwrap_or_default(), - "-t", "2000", "espanso", message]) - .output(); + .args(&[ + "-i", + self.icon_path.to_str().unwrap_or_default(), + "-t", + "2000", + "espanso", + message, + ]) + .output(); if let Err(e) = res { error!("Could not send a notification, error: {}", e); @@ -55,12 +61,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 8bcf791..cbff04a 100644 --- a/src/ui/macos.rs +++ b/src/ui/macos.rs @@ -17,22 +17,22 @@ * 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 DEFAULT_NOTIFICATION_DELAY : f64 = 1.5; +const NOTIFY_HELPER_BINARY: &'static [u8] = include_bytes!("../res/mac/EspansoNotifyHelper.zip"); +const DEFAULT_NOTIFICATION_DELAY: f64 = 1.5; pub struct MacUIManager { - notify_helper_path: PathBuf + notify_helper_path: PathBuf, } impl super::UIManager for MacUIManager { @@ -55,14 +55,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 { @@ -74,7 +74,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) { @@ -86,21 +88,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); @@ -118,10 +121,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(); @@ -141,4 +153,4 @@ impl MacUIManager { espanso_target } -} \ No newline at end of file +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 1b096c6..2cf2495 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -59,4 +59,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 4024939..b4a780a 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 { @@ -39,29 +41,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(200); - thread::sleep(duration); + let _ = thread::Builder::new() + .name("notification_thread".to_string()) + .spawn(move || { + for _ in 1..10 { + let duration = time::Duration::from_millis(200); + 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) { @@ -69,14 +72,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 { @@ -88,7 +91,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) { @@ -102,10 +107,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 ffd9106..3355963 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 +}