From 0eb58704a92740be31b2bf36a4c7792507b9d675 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Fri, 10 Jan 2020 23:29:21 +0100 Subject: [PATCH] First steps in passive match rendering --- src/config/mod.rs | 10 +++ src/main.rs | 5 +- src/render/default.rs | 182 +++++++++++++++++++++++++++++++++++++++++- src/render/mod.rs | 4 + 4 files changed, 195 insertions(+), 6 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index c849527..361751b 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -54,6 +54,8 @@ 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_preserve_clipboard() -> bool {false} +fn default_passive_match_regex() -> String{ "(?P:\\p{L}+)(/(?P.*)/)?".to_owned() } +fn default_passive_arg_regex() -> String{ "((\\\\/)|[^/])+".to_owned() } fn default_backspace_limit() -> i32 { 3 } fn default_exclude_default_matches() -> bool {false} fn default_matches() -> Vec { Vec::new() } @@ -102,6 +104,12 @@ pub struct Configs { #[serde(default = "default_preserve_clipboard")] pub preserve_clipboard: bool, + #[serde(default = "default_passive_match_regex")] + pub passive_match_regex: String, + + #[serde(default = "default_passive_arg_regex")] + pub passive_arg_regex: String, + #[serde(default)] pub paste_shortcut: PasteShortcut, @@ -150,6 +158,8 @@ impl Configs { 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_regex, default_passive_arg_regex()); result } diff --git a/src/main.rs b/src/main.rs index c0e8434..12c7561 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,7 +32,7 @@ use fs2::FileExt; use log::{info, warn, LevelFilter}; use simplelog::{CombinedLogger, SharedLogger, TerminalMode, TermLogger, WriteLogger}; -use crate::config::ConfigSet; +use crate::config::{ConfigSet, ConfigManager}; use crate::config::runtime::RuntimeConfigManager; use crate::engine::Engine; use crate::event::*; @@ -333,7 +333,8 @@ fn daemon_background(receive_channel: Receiver, config_set: ConfigSet) { let extensions = extension::get_extensions(); - let renderer = render::default::DefaultRenderer::new(extensions); + let renderer = render::default::DefaultRenderer::new(extensions, + config_manager.default_config().clone()); let engine = Engine::new(&keyboard_manager, &clipboard_manager, diff --git a/src/render/default.rs b/src/render/default.rs index d0d3f93..f1ef5f2 100644 --- a/src/render/default.rs +++ b/src/render/default.rs @@ -33,18 +33,36 @@ lazy_static! { pub struct DefaultRenderer { extension_map: HashMap>, + + // Regex used to identify matches (and arguments) in passive expansions + passive_match_regex: Regex, + + // Regex used to separate arguments in passive expansions + passive_arg_regex: Regex, } impl DefaultRenderer { - pub fn new(extensions: Vec>) -> DefaultRenderer { + pub fn new(extensions: Vec>, config: Configs) -> DefaultRenderer { // Register all the extensions let mut extension_map = HashMap::new(); for extension in extensions.into_iter() { extension_map.insert(extension.name(), extension); } + // Compile the regexes + let passive_match_regex = Regex::new(&config.passive_match_regex) + .unwrap_or_else(|e| { + panic!("Invalid passive match regex"); + }); + let passive_arg_regex = Regex::new(&config.passive_arg_regex) + .unwrap_or_else(|e| { + panic!("Invalid passive arg regex"); + }); + DefaultRenderer{ - extension_map + extension_map, + passive_match_regex, + passive_arg_regex } } @@ -69,7 +87,7 @@ impl super::Renderer for DefaultRenderer { match &m.content { // Text Match MatchContentType::Text(content) => { - let mut target_string = if content._has_vars { + let target_string = if content._has_vars { let mut output_map = HashMap::new(); for variable in content.vars.iter() { @@ -108,6 +126,7 @@ impl super::Renderer for DefaultRenderer { }, } }else{ // Normal extension variables + // TODO: pass the arguments to the extension let extension = self.extension_map.get(&variable.var_type); if let Some(extension) = extension { let ext_out = extension.calculate(&variable.params); @@ -123,6 +142,11 @@ impl super::Renderer for DefaultRenderer { } } + // TODO: replace the arguments + // the idea is that every param placeholder, such as $1$, is replaced with + // an ArgExtension when loading a match, which renders the argument as output + // this is only used as syntactic sugar + // Replace the variables let result = VAR_REGEX.replace_all(&content.replace, |caps: &Captures| { let var_name = caps.name("name").unwrap().as_str(); @@ -150,6 +174,156 @@ impl super::Renderer for DefaultRenderer { }, } } + + 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{ + "" + }; + + + // 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); + + // 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 = self.passive_arg_regex.split(match_args).into_iter().map( + |arg| arg.to_owned() + ).collect(); + + 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()) + } } -// TODO: tests \ No newline at end of file +// TESTS + +#[cfg(test)] +mod tests { + use super::*; + + fn get_renderer(config: Configs) -> DefaultRenderer { + DefaultRenderer::new(crate::extension::get_extensions(), config) + } + + fn get_config_for(s: &str) -> Configs { + let config : Configs = serde_yaml::from_str(s).unwrap(); + config + } + + fn verify_render(rendered: RenderResult, target: &str) { + match rendered { + RenderResult::Text(rendered) => { + println!("{}", rendered); + assert_eq!(rendered, target); + }, + _ => { + assert!(false) + } + } + } + + #[test] + fn test_render_passive_no_matches() { + let text = r###" + this text contains no matches + "###; + + let config = get_config_for(r###" + matches: + - trigger: test + replace: result + "###); + + let renderer = get_renderer(config.clone()); + + let rendered = renderer.render_passive(text, &config); + + verify_render(rendered, text); + } + + #[test] + fn test_render_passive_simple_match_no_args() { + let text = "this is a :test"; + + let config = get_config_for(r###" + matches: + - trigger: ':test' + replace: result + "###); + + let renderer = get_renderer(config.clone()); + + let rendered = renderer.render_passive(text, &config); + + verify_render(rendered, "this is a result"); + } + + #[test] + fn test_render_passive_multiple_match_no_args() { + let text = "this is a :test and then another :test"; + + let config = get_config_for(r###" + matches: + - trigger: ':test' + replace: result + "###); + + let renderer = get_renderer(config.clone()); + + let rendered = renderer.render_passive(text, &config); + + verify_render(rendered, "this is a result and then another result"); + } + + #[test] + fn test_render_passive_simple_match_multiline_no_args() { + let text = r###"this is a + :test + "###; + + let result= r###"this is a + result + "###; + + let config = get_config_for(r###" + matches: + - trigger: ':test' + replace: result + "###); + + let renderer = get_renderer(config.clone()); + + let rendered = renderer.render_passive(text, &config); + + verify_render(rendered, result); + } +} \ No newline at end of file diff --git a/src/render/mod.rs b/src/render/mod.rs index a676f7a..d83e982 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -24,7 +24,11 @@ use crate::config::Configs; pub(crate) mod default; pub trait Renderer { + // Render a match output fn render_match(&self, m: &Match, config: &Configs, args: Vec) -> RenderResult; + + // Render a passive expansion text + fn render_passive(&self, text: &str, config: &Configs) -> RenderResult; } pub enum RenderResult {