Refactor to generalize Match and implemented macOS backend for image clipboard
This commit is contained in:
parent
a31a2e3c57
commit
5c875eeaed
|
@ -148,5 +148,11 @@ int32_t get_clipboard(char * buffer, int32_t size);
|
||||||
*/
|
*/
|
||||||
int32_t set_clipboard(char * text);
|
int32_t set_clipboard(char * text);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the clipboard image to the given file
|
||||||
|
*/
|
||||||
|
int32_t set_clipboard_image(char * path);
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
#endif //ESPANSO_BRIDGE_H
|
#endif //ESPANSO_BRIDGE_H
|
||||||
|
|
|
@ -230,6 +230,24 @@ int32_t set_clipboard(char * text) {
|
||||||
[pasteboard setString:nsText forType:NSPasteboardTypeString];
|
[pasteboard setString:nsText forType:NSPasteboardTypeString];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32_t set_clipboard_image(char *path) {
|
||||||
|
NSString *pathString = [NSString stringWithUTF8String:path];
|
||||||
|
NSImage *image = [[NSImage alloc] initWithContentsOfFile:pathString];
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
if (image != nil) {
|
||||||
|
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
|
||||||
|
[pasteboard clearContents];
|
||||||
|
NSArray *copiedObjects = [NSArray arrayWithObject:image];
|
||||||
|
[pasteboard writeObjects:copiedObjects];
|
||||||
|
result = 1;
|
||||||
|
}
|
||||||
|
[image release];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// CONTEXT MENU
|
// CONTEXT MENU
|
||||||
|
|
||||||
int32_t show_context_menu(MenuItem * items, int32_t count) {
|
int32_t show_context_menu(MenuItem * items, int32_t count) {
|
||||||
|
@ -273,4 +291,4 @@ int32_t prompt_accessibility() {
|
||||||
void open_settings_panel() {
|
void open_settings_panel() {
|
||||||
NSString *urlString = @"x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
|
NSString *urlString = @"x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
|
||||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
|
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
|
||||||
}
|
}
|
|
@ -43,6 +43,7 @@ extern {
|
||||||
// Clipboard
|
// Clipboard
|
||||||
pub fn get_clipboard(buffer: *mut c_char, size: i32) -> i32;
|
pub fn get_clipboard(buffer: *mut c_char, size: i32) -> i32;
|
||||||
pub fn set_clipboard(text: *const c_char) -> i32;
|
pub fn set_clipboard(text: *const c_char) -> i32;
|
||||||
|
pub fn set_clipboard_image(path: *const c_char) -> i32;
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
pub fn register_icon_click_callback(cb: extern fn(_self: *mut c_void));
|
pub fn register_icon_click_callback(cb: extern fn(_self: *mut c_void));
|
||||||
|
|
|
@ -18,8 +18,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::os::raw::c_char;
|
use std::os::raw::c_char;
|
||||||
use crate::bridge::macos::{get_clipboard, set_clipboard};
|
use crate::bridge::macos::*;
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::path::Path;
|
||||||
|
use log::{error, warn};
|
||||||
|
|
||||||
pub struct MacClipboardManager {
|
pub struct MacClipboardManager {
|
||||||
|
|
||||||
|
@ -52,6 +54,24 @@ impl super::ClipboardManager for MacClipboardManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_clipboard_image(&self, image_path: &Path) {
|
||||||
|
// Make sure the image exist beforehand
|
||||||
|
if !image_path.exists() {
|
||||||
|
error!("Image not found in path: {:?}", image_path);
|
||||||
|
}else{
|
||||||
|
let path_string = image_path.to_string_lossy().into_owned();
|
||||||
|
let res = CString::new(path_string);
|
||||||
|
if let Ok(path) = res {
|
||||||
|
unsafe {
|
||||||
|
let result = set_clipboard_image(path.as_ptr());
|
||||||
|
if result != 1 {
|
||||||
|
warn!("Couldn't set clipboard for image: {:?}", image_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MacClipboardManager {
|
impl MacClipboardManager {
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
mod windows;
|
mod windows;
|
||||||
|
|
||||||
|
@ -29,6 +31,7 @@ mod macos;
|
||||||
pub trait ClipboardManager {
|
pub trait ClipboardManager {
|
||||||
fn get_clipboard(&self) -> Option<String>;
|
fn get_clipboard(&self) -> Option<String>;
|
||||||
fn set_clipboard(&self, payload: &str);
|
fn set_clipboard(&self, payload: &str);
|
||||||
|
fn set_clipboard_image(&self, image_path: &Path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// LINUX IMPLEMENTATION
|
// LINUX IMPLEMENTATION
|
||||||
|
|
|
@ -426,6 +426,7 @@ mod tests {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use tempfile::{NamedTempFile, TempDir};
|
use tempfile::{NamedTempFile, TempDir};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
use crate::matcher::{TextContent, MatchContentType};
|
||||||
|
|
||||||
const TEST_WORKING_CONFIG_FILE : &str = include_str!("../res/test/working_config.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");
|
const TEST_CONFIG_FILE_WITH_BAD_YAML : &str = include_str!("../res/test/config_with_bad_yaml.yml");
|
||||||
|
@ -727,7 +728,13 @@ mod tests {
|
||||||
assert_eq!(config_set.default.matches.len(), 2);
|
assert_eq!(config_set.default.matches.len(), 2);
|
||||||
assert_eq!(config_set.specific[0].matches.len(), 2);
|
assert_eq!(config_set.specific[0].matches.len(), 2);
|
||||||
|
|
||||||
assert!(config_set.specific[0].matches.iter().find(|x| x.trigger == ":lol" && x.replace == "newstring").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());
|
assert!(config_set.specific[0].matches.iter().find(|x| x.trigger == ":yess").is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -755,7 +762,13 @@ mod tests {
|
||||||
assert_eq!(config_set.default.matches.len(), 2);
|
assert_eq!(config_set.default.matches.len(), 2);
|
||||||
assert_eq!(config_set.specific[0].matches.len(), 1);
|
assert_eq!(config_set.specific[0].matches.len(), 1);
|
||||||
|
|
||||||
assert!(config_set.specific[0].matches.iter().find(|x| x.trigger == "hello" && x.replace == "newstring").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]
|
#[test]
|
||||||
|
@ -897,7 +910,13 @@ mod tests {
|
||||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||||
assert_eq!(config_set.specific.len(), 0);
|
assert_eq!(config_set.specific.len(), 0);
|
||||||
assert_eq!(config_set.default.matches.len(), 1);
|
assert_eq!(config_set.default.matches.len(), 1);
|
||||||
assert!(config_set.default.matches.iter().any(|m| m.trigger == "hasta" && m.replace == "world"));
|
assert!(config_set.default.matches.iter().any(|m| {
|
||||||
|
if let MatchContentType::Text(content) = &m.content {
|
||||||
|
m.trigger == "hasta" && content.replace == "world"
|
||||||
|
}else{
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
182
src/engine.rs
182
src/engine.rs
|
@ -17,7 +17,7 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::matcher::{Match, MatchReceiver};
|
use crate::matcher::{Match, MatchReceiver, MatchContentType};
|
||||||
use crate::keyboard::KeyboardManager;
|
use crate::keyboard::KeyboardManager;
|
||||||
use crate::config::ConfigManager;
|
use crate::config::ConfigManager;
|
||||||
use crate::config::BackendType;
|
use crate::config::BackendType;
|
||||||
|
@ -29,6 +29,7 @@ use crate::extension::Extension;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
use regex::{Regex, Captures};
|
use regex::{Regex, Captures};
|
||||||
|
|
||||||
pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>,
|
pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>,
|
||||||
|
@ -118,97 +119,110 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
|
|
||||||
self.keyboard_manager.delete_string(char_count);
|
self.keyboard_manager.delete_string(char_count);
|
||||||
|
|
||||||
let mut target_string = if m._has_vars {
|
// Manage the different types of matches
|
||||||
let mut output_map = HashMap::new();
|
match &m.content {
|
||||||
|
// Text Match
|
||||||
|
MatchContentType::Text(content) => {
|
||||||
|
let mut target_string = if content._has_vars {
|
||||||
|
let mut output_map = HashMap::new();
|
||||||
|
|
||||||
for variable in m.vars.iter() {
|
for variable in content.vars.iter() {
|
||||||
let extension = self.extension_map.get(&variable.var_type);
|
let extension = self.extension_map.get(&variable.var_type);
|
||||||
if let Some(extension) = extension {
|
if let Some(extension) = extension {
|
||||||
let ext_out = extension.calculate(&variable.params);
|
let ext_out = extension.calculate(&variable.params);
|
||||||
if let Some(output) = ext_out {
|
if let Some(output) = ext_out {
|
||||||
output_map.insert(variable.name.clone(), output);
|
output_map.insert(variable.name.clone(), output);
|
||||||
}else{
|
}else{
|
||||||
output_map.insert(variable.name.clone(), "".to_owned());
|
output_map.insert(variable.name.clone(), "".to_owned());
|
||||||
warn!("Could not generate output for variable: {}", variable.name);
|
warn!("Could not generate output for variable: {}", variable.name);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
error!("No extension found for variable type: {}", variable.var_type);
|
error!("No extension found for variable type: {}", variable.var_type);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the variables
|
|
||||||
let result = VAR_REGEX.replace_all(&m.replace, |caps: &Captures| {
|
|
||||||
let var_name = caps.name("name").unwrap().as_str();
|
|
||||||
let output = output_map.get(var_name);
|
|
||||||
output.unwrap()
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_string()
|
|
||||||
}else{ // No variables, simple text substitution
|
|
||||||
m.replace.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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{
|
|
||||||
target_string.push(trailing_separator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert Windows style newlines into unix styles
|
|
||||||
target_string = target_string.replace("\r\n", "\n");
|
|
||||||
|
|
||||||
// Calculate cursor rewind moves if a Cursor Hint is present
|
|
||||||
let index = target_string.find("$|$");
|
|
||||||
let cursor_rewind = if let Some(index) = index {
|
|
||||||
// Convert the byte index to a char index
|
|
||||||
let char_str = &target_string[0..index];
|
|
||||||
let char_index = char_str.chars().count();
|
|
||||||
let total_size = target_string.chars().count();
|
|
||||||
|
|
||||||
// Remove the $|$ placeholder
|
|
||||||
target_string = target_string.replace("$|$", "");
|
|
||||||
|
|
||||||
// Calculate the amount of rewind moves needed (LEFT ARROW).
|
|
||||||
// Subtract also 3, equal to the number of chars of the placeholder "$|$"
|
|
||||||
let moves = (total_size - char_index - 3) as i32;
|
|
||||||
Some(moves)
|
|
||||||
}else{
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
match config.backend {
|
|
||||||
BackendType::Inject => {
|
|
||||||
// Send the expected string. On linux, newlines are managed automatically
|
|
||||||
// while on windows and macos, we need to emulate a Enter key press.
|
|
||||||
|
|
||||||
if cfg!(target_os = "linux") {
|
|
||||||
self.keyboard_manager.send_string(&target_string);
|
|
||||||
}else{
|
|
||||||
// To handle newlines, substitute each "\n" char with an Enter key press.
|
|
||||||
let splits = target_string.split('\n');
|
|
||||||
|
|
||||||
for (i, split) in splits.enumerate() {
|
|
||||||
if i > 0 {
|
|
||||||
self.keyboard_manager.send_enter();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.keyboard_manager.send_string(split);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace the variables
|
||||||
|
let result = VAR_REGEX.replace_all(&content.replace, |caps: &Captures| {
|
||||||
|
let var_name = caps.name("name").unwrap().as_str();
|
||||||
|
let output = output_map.get(var_name);
|
||||||
|
output.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
result.to_string()
|
||||||
|
}else{ // No variables, simple text substitution
|
||||||
|
content.replace.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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{
|
||||||
|
target_string.push(trailing_separator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Windows style newlines into unix styles
|
||||||
|
target_string = target_string.replace("\r\n", "\n");
|
||||||
|
|
||||||
|
// Calculate cursor rewind moves if a Cursor Hint is present
|
||||||
|
let index = target_string.find("$|$");
|
||||||
|
let cursor_rewind = if let Some(index) = index {
|
||||||
|
// Convert the byte index to a char index
|
||||||
|
let char_str = &target_string[0..index];
|
||||||
|
let char_index = char_str.chars().count();
|
||||||
|
let total_size = target_string.chars().count();
|
||||||
|
|
||||||
|
// Remove the $|$ placeholder
|
||||||
|
target_string = target_string.replace("$|$", "");
|
||||||
|
|
||||||
|
// Calculate the amount of rewind moves needed (LEFT ARROW).
|
||||||
|
// Subtract also 3, equal to the number of chars of the placeholder "$|$"
|
||||||
|
let moves = (total_size - char_index - 3) as i32;
|
||||||
|
Some(moves)
|
||||||
|
}else{
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
match config.backend {
|
||||||
|
BackendType::Inject => {
|
||||||
|
// Send the expected string. On linux, newlines are managed automatically
|
||||||
|
// while on windows and macos, we need to emulate a Enter key press.
|
||||||
|
|
||||||
|
if cfg!(target_os = "linux") {
|
||||||
|
self.keyboard_manager.send_string(&target_string);
|
||||||
|
}else{
|
||||||
|
// To handle newlines, substitute each "\n" char with an Enter key press.
|
||||||
|
let splits = target_string.split('\n');
|
||||||
|
|
||||||
|
for (i, split) in splits.enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
self.keyboard_manager.send_enter();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.keyboard_manager.send_string(split);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BackendType::Clipboard => {
|
||||||
|
self.clipboard_manager.set_clipboard(&target_string);
|
||||||
|
self.keyboard_manager.trigger_paste();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
BackendType::Clipboard => {
|
|
||||||
self.clipboard_manager.set_clipboard(&target_string);
|
// Image Match
|
||||||
|
MatchContentType::Image(content) => {
|
||||||
|
let image_path = PathBuf::from(&content.path);
|
||||||
|
self.clipboard_manager.set_clipboard_image(&image_path);
|
||||||
self.keyboard_manager.trigger_paste();
|
self.keyboard_manager.trigger_paste();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enable_update(&self, status: bool) {
|
fn on_enable_update(&self, status: bool) {
|
||||||
|
|
|
@ -28,18 +28,34 @@ pub(crate) mod scrolling;
|
||||||
#[derive(Debug, Serialize, Clone)]
|
#[derive(Debug, Serialize, Clone)]
|
||||||
pub struct Match {
|
pub struct Match {
|
||||||
pub trigger: String,
|
pub trigger: String,
|
||||||
pub replace: String,
|
pub content: MatchContentType,
|
||||||
pub vars: Vec<MatchVariable>,
|
|
||||||
pub word: bool,
|
pub word: bool,
|
||||||
|
|
||||||
#[serde(skip_serializing)]
|
|
||||||
pub _has_vars: bool,
|
|
||||||
|
|
||||||
// Automatically calculated from the trigger, used by the matcher to check for correspondences.
|
// Automatically calculated from the trigger, used by the matcher to check for correspondences.
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
pub _trigger_sequence: Vec<TriggerEntry>,
|
pub _trigger_sequence: Vec<TriggerEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Clone)]
|
||||||
|
pub enum MatchContentType {
|
||||||
|
Text(TextContent),
|
||||||
|
Image(ImageContent),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Clone)]
|
||||||
|
pub struct TextContent {
|
||||||
|
pub replace: String,
|
||||||
|
pub vars: Vec<MatchVariable>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub _has_vars: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Clone)]
|
||||||
|
pub struct ImageContent {
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl <'de> serde::Deserialize<'de> for Match {
|
impl <'de> serde::Deserialize<'de> for Match {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
|
||||||
D: Deserializer<'de> {
|
D: Deserializer<'de> {
|
||||||
|
@ -53,15 +69,10 @@ impl<'a> From<&'a AutoMatch> for Match{
|
||||||
fn from(other: &'a AutoMatch) -> Self {
|
fn from(other: &'a AutoMatch) -> Self {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(\\w+)\\s*\\}\\}").unwrap();
|
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(\\w+)\\s*\\}\\}").unwrap();
|
||||||
}
|
};
|
||||||
|
|
||||||
// TODO: may need to replace windows newline (\r\n) with newline only (\n)
|
// TODO: may need to replace windows newline (\r\n) with newline only (\n)
|
||||||
|
|
||||||
let new_replace = other.replace.clone();
|
|
||||||
|
|
||||||
// Check if the match contains variables
|
|
||||||
let has_vars = VAR_REGEX.is_match(&other.replace);
|
|
||||||
|
|
||||||
// Calculate the trigger sequence
|
// Calculate the trigger sequence
|
||||||
let mut trigger_sequence = Vec::new();
|
let mut trigger_sequence = Vec::new();
|
||||||
let trigger_chars : Vec<char> = other.trigger.chars().collect();
|
let trigger_chars : Vec<char> = other.trigger.chars().collect();
|
||||||
|
@ -72,12 +83,34 @@ impl<'a> From<&'a AutoMatch> for Match{
|
||||||
trigger_sequence.push(TriggerEntry::WordSeparator);
|
trigger_sequence.push(TriggerEntry::WordSeparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let content = if let Some(replace) = &other.replace { // Text match
|
||||||
|
let new_replace = replace.clone();
|
||||||
|
|
||||||
|
// Check if the match contains variables
|
||||||
|
let has_vars = VAR_REGEX.is_match(replace);
|
||||||
|
|
||||||
|
let content = TextContent {
|
||||||
|
replace: new_replace,
|
||||||
|
vars: other.vars.clone(),
|
||||||
|
_has_vars: has_vars,
|
||||||
|
};
|
||||||
|
|
||||||
|
MatchContentType::Text(content)
|
||||||
|
}else if let Some(image_path) = &other.image_path { // Image match
|
||||||
|
let content = ImageContent {
|
||||||
|
path: image_path.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
MatchContentType::Image(content)
|
||||||
|
}else {
|
||||||
|
eprintln!("ERROR: no action specified for match {}, please specify either 'replace' or 'image_path'", other.trigger);
|
||||||
|
std::process::exit(2);
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
trigger: other.trigger.clone(),
|
trigger: other.trigger.clone(),
|
||||||
replace: new_replace,
|
content,
|
||||||
vars: other.vars.clone(),
|
|
||||||
word: other.word.clone(),
|
word: other.word.clone(),
|
||||||
_has_vars: has_vars,
|
|
||||||
_trigger_sequence: trigger_sequence,
|
_trigger_sequence: trigger_sequence,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +120,12 @@ impl<'a> From<&'a AutoMatch> for Match{
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
struct AutoMatch {
|
struct AutoMatch {
|
||||||
pub trigger: String,
|
pub trigger: String,
|
||||||
pub replace: String,
|
|
||||||
|
#[serde(default = "default_replace")]
|
||||||
|
pub replace: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default = "default_image_path")]
|
||||||
|
pub image_path: Option<String>,
|
||||||
|
|
||||||
#[serde(default = "default_vars")]
|
#[serde(default = "default_vars")]
|
||||||
pub vars: Vec<MatchVariable>,
|
pub vars: Vec<MatchVariable>,
|
||||||
|
@ -98,6 +136,8 @@ struct AutoMatch {
|
||||||
|
|
||||||
fn default_vars() -> Vec<MatchVariable> {Vec::new()}
|
fn default_vars() -> Vec<MatchVariable> {Vec::new()}
|
||||||
fn default_word() -> bool {false}
|
fn default_word() -> bool {false}
|
||||||
|
fn default_replace() -> Option<String> {None}
|
||||||
|
fn default_image_path() -> Option<String> {None}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct MatchVariable {
|
pub struct MatchVariable {
|
||||||
|
@ -154,7 +194,14 @@ mod tests {
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
assert_eq!(_match._has_vars, false);
|
match _match.content {
|
||||||
|
MatchContentType::Text(content) => {
|
||||||
|
assert_eq!(content._has_vars, false);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
assert!(false);
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -166,7 +213,14 @@ mod tests {
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
assert_eq!(_match._has_vars, true);
|
match _match.content {
|
||||||
|
MatchContentType::Text(content) => {
|
||||||
|
assert_eq!(content._has_vars, true);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
assert!(false);
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -178,7 +232,14 @@ mod tests {
|
||||||
|
|
||||||
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
assert_eq!(_match._has_vars, true);
|
match _match.content {
|
||||||
|
MatchContentType::Text(content) => {
|
||||||
|
assert_eq!(content._has_vars, true);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
assert!(false);
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -212,4 +273,23 @@ mod tests {
|
||||||
assert_eq!(_match._trigger_sequence[3], TriggerEntry::Char('t'));
|
assert_eq!(_match._trigger_sequence[3], TriggerEntry::Char('t'));
|
||||||
assert_eq!(_match._trigger_sequence[4], TriggerEntry::WordSeparator);
|
assert_eq!(_match._trigger_sequence[4], TriggerEntry::WordSeparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_match_with_image_content() {
|
||||||
|
let match_str = r###"
|
||||||
|
trigger: "test"
|
||||||
|
image_path: "/path/to/file"
|
||||||
|
"###;
|
||||||
|
|
||||||
|
let _match : Match = serde_yaml::from_str(match_str).unwrap();
|
||||||
|
|
||||||
|
match _match.content {
|
||||||
|
MatchContentType::Image(content) => {
|
||||||
|
assert_eq!(content.path, "/path/to/file");
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
assert!(false);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user