diff --git a/src/extension/script.rs b/src/extension/script.rs index 2b1791d..e62caea 100644 --- a/src/extension/script.rs +++ b/src/extension/script.rs @@ -211,7 +211,7 @@ mod tests { let mut params = Mapping::new(); params.insert( Value::from("args"), - Value::from(vec!["echo", "$ESPANSO_VAR1 $ESPANSO_FORM1_NAME"]), + Value::from(vec!["bash", "-c", "echo $ESPANSO_VAR1 $ESPANSO_FORM1_NAME"]), ); let mut vars: HashMap = HashMap::new(); @@ -221,9 +221,9 @@ mod tests { vars.insert("var1".to_owned(), ExtensionResult::Single("hello".to_owned())); let extension = ScriptExtension::new(); - let output = extension.calculate(¶ms, &vec![]); + let output = extension.calculate(¶ms, &vec![], &vars); assert!(output.is_some()); - assert_eq!(output.unwrap(), "hello Jon"); + assert_eq!(output.unwrap(), ExtensionResult::Single("hello John".to_owned())); } } diff --git a/src/matcher/mod.rs b/src/matcher/mod.rs index 27e2336..571b773 100644 --- a/src/matcher/mod.rs +++ b/src/matcher/mod.rs @@ -1,5 +1,5 @@ /* - * This file is part of espanso. + * This file is part of espans{ name: (), var_type: (), params: ()} * * Copyright (C) 2019 Federico Terzi * @@ -19,9 +19,10 @@ use crate::event::KeyEventReceiver; use crate::event::{KeyEvent, KeyModifier}; -use regex::Regex; +use regex::{Captures, Regex}; use serde::{Deserialize, Deserializer, Serialize}; -use serde_yaml::Mapping; +use serde_yaml::{Mapping, Value}; +use std::collections::HashMap; use std::fs; use std::path::PathBuf; @@ -47,7 +48,7 @@ pub enum MatchContentType { Image(ImageContent), } -#[derive(Debug, Serialize, Clone)] +#[derive(Debug, Serialize, Clone, PartialEq)] pub struct TextContent { pub replace: String, pub vars: Vec, @@ -143,6 +144,40 @@ impl<'a> From<&'a AutoMatch> for Match { _has_vars: has_vars, }; + MatchContentType::Text(content) + } else if let Some(form) = &other.form { // Form shorthand + // Replace all the form fields with actual variables + let new_replace = VAR_REGEX.replace_all(&form, |caps: &Captures| { + let var_name = caps.get(1).unwrap().as_str(); + format!("{{{{form1.{}}}}}", var_name) + }); + let new_replace = new_replace.to_string(); + + // Convert the form data to valid variables + let mut params = Mapping::new(); + if let Some(fields) = &other.form_fields { + let mut mapping_fields = Mapping::new(); + fields.iter().for_each(|(key, value)| { + mapping_fields.insert(Value::from(key.to_owned()), Value::from(value.clone())); + }); + params.insert(Value::from("fields"), Value::from(mapping_fields)); + } + params.insert(Value::from("layout"), Value::from(form.to_owned())); + + let vars = vec![ + MatchVariable { + name: "form1".to_owned(), + var_type: "form".to_owned(), + params, + } + ]; + + let content = TextContent { + replace: new_replace, + vars, + _has_vars: true, + }; + MatchContentType::Text(content) } else if let Some(image_path) = &other.image_path { // Image match @@ -173,7 +208,7 @@ impl<'a> From<&'a AutoMatch> for Match { MatchContentType::Image(content) } else { - eprintln!("ERROR: no action specified for match {}, please specify either 'replace' or 'image_path'", other.trigger); + eprintln!("ERROR: no action specified for match {}, please specify either 'replace', 'image_path' or 'form'", other.trigger); std::process::exit(2); }; @@ -204,6 +239,12 @@ struct AutoMatch { #[serde(default = "default_image_path")] pub image_path: Option, + #[serde(default = "default_form")] + pub form: Option, + + #[serde(default = "default_form_fields")] + pub form_fields: Option>, + #[serde(default = "default_vars")] pub vars: Vec, @@ -238,6 +279,12 @@ fn default_passive_only() -> bool { fn default_replace() -> Option { None } +fn default_form() -> Option { + None +} +fn default_form_fields() -> Option> { + None +} fn default_image_path() -> Option { None } @@ -248,7 +295,7 @@ fn default_force_clipboard() -> bool { false } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct MatchVariable { pub name: String, @@ -543,4 +590,68 @@ mod tests { assert_eq!(_match.triggers, vec![":..", ":..", ":.."]) } + + #[test] + fn test_match_form_translated_correctly() { + let match_str = r###" + trigger: ":test" + form: "Hey {{name}}, how are you? {{greet}}" + "###; + + let _match: Match = serde_yaml::from_str(match_str).unwrap(); + match _match.content { + MatchContentType::Text(content) => { + let mut mapping = Mapping::new(); + mapping.insert(Value::from("layout"), Value::from("Hey {{name}}, how are you? {{greet}}")); + assert_eq!(content, TextContent { + replace: "Hey {{form1.name}}, how are you? {{form1.greet}}".to_owned(), + _has_vars: true, + vars: vec![ + MatchVariable { + name: "form1".to_owned(), + var_type: "form".to_owned(), + params: mapping, + } + ] + }); + }, + _ => panic!("wrong content") + } + } + + #[test] + fn test_match_form_with_fields_translated_correctly() { + let match_str = r###" + trigger: ":test" + form: "Hey {{name}}, how are you? {{greet}}" + form_fields: + name: + multiline: true + "###; + + let _match: Match = serde_yaml::from_str(match_str).unwrap(); + match _match.content { + MatchContentType::Text(content) => { + let mut name_mapping = Mapping::new(); + name_mapping.insert(Value::from("multiline"), Value::Bool(true)); + let mut submapping = Mapping::new(); + submapping.insert(Value::from("name"), Value::from(name_mapping)); + let mut mapping = Mapping::new(); + mapping.insert(Value::from("fields"), Value::from(submapping)); + mapping.insert(Value::from("layout"), Value::from("Hey {{name}}, how are you? {{greet}}")); + assert_eq!(content, TextContent { + replace: "Hey {{form1.name}}, how are you? {{form1.greet}}".to_owned(), + _has_vars: true, + vars: vec![ + MatchVariable { + name: "form1".to_owned(), + var_type: "form".to_owned(), + params: mapping, + } + ] + }); + }, + _ => panic!("wrong content") + } + } } diff --git a/src/ui/modulo/mod.rs b/src/ui/modulo/mod.rs index 93c523c..83b1b48 100644 --- a/src/ui/modulo/mod.rs +++ b/src/ui/modulo/mod.rs @@ -1,6 +1,6 @@ use crate::config::Configs; use std::process::{Command, Child, Output}; -use log::{error}; +use log::{error, info}; use std::io::{Error, Write}; pub mod form; @@ -42,6 +42,10 @@ impl ModuloManager { } } + if let Some(ref modulo_path) = modulo_path { + info!("Using modulo at {:?}", modulo_path); + } + Self { modulo_path, }