From 2f53752e97a2b06255ad1d44ae99e34819778937 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Thu, 29 Apr 2021 22:37:44 +0200 Subject: [PATCH] feat(core): implement rich text matches --- Cargo.lock | 251 +++++++++++++++++- espanso/Cargo.toml | 4 +- .../engine/executor/clipboard_injector.rs | 29 +- espanso/src/cli/worker/engine/mod.rs | 1 + espanso/src/cli/worker/engine/multiplex.rs | 22 +- espanso/src/engine/dispatch/default.rs | 6 +- .../engine/dispatch/executor/html_inject.rs | 61 +++++ espanso/src/engine/dispatch/executor/mod.rs | 3 +- espanso/src/engine/dispatch/mod.rs | 12 +- espanso/src/engine/event/effect.rs | 10 + espanso/src/engine/event/internal.rs | 9 + espanso/src/engine/event/mod.rs | 2 + espanso/src/engine/process/default.rs | 3 +- .../src/engine/process/middleware/action.rs | 23 +- .../src/engine/process/middleware/markdown.rs | 60 +++++ espanso/src/engine/process/middleware/mod.rs | 1 + .../src/engine/process/middleware/render.rs | 1 + espanso/src/main.rs | 1 + 18 files changed, 472 insertions(+), 27 deletions(-) create mode 100644 espanso/src/engine/dispatch/executor/html_inject.rs create mode 100644 espanso/src/engine/process/middleware/markdown.rs diff --git a/Cargo.lock b/Cargo.lock index 4e1aef5..b609af8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -299,9 +299,11 @@ dependencies = [ "espanso-path", "espanso-render", "espanso-ui", + "html2text", "lazy_static", "log", "maplit", + "markdown", "serde", "serde_json", "simplelog", @@ -459,6 +461,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "futf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -505,6 +517,31 @@ dependencies = [ "libc", ] +[[package]] +name = "html2text" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26379dcb715e237b96102a12b505c553e2bffa74bae2e54658748d298660ef1" +dependencies = [ + "html5ever", + "markup5ever_rcdom", + "unicode-width", +] + +[[package]] +name = "html5ever" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote 1.0.9", + "syn 1.0.60", +] + [[package]] name = "itertools" version = "0.10.0" @@ -562,6 +599,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + [[package]] name = "mac-notification-sys" version = "0.3.0" @@ -589,6 +632,43 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +[[package]] +name = "markdown" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef3aab6a1d529b112695f72beec5ee80e729cb45af58663ec902c8fac764ecdd" +dependencies = [ + "lazy_static", + "pipeline", + "regex", +] + +[[package]] +name = "markup5ever" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" +dependencies = [ + "log", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f015da43bcd8d4f144559a3423f4591d69b8ce0652c905374da7205df336ae2b" +dependencies = [ + "html5ever", + "markup5ever", + "tendril", + "xml5ever", +] + [[package]] name = "memchr" version = "2.3.4" @@ -613,6 +693,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + [[package]] name = "notify-rust" version = "4.2.2" @@ -681,6 +767,50 @@ dependencies = [ "num-traits", ] +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared", + "rand 0.7.3", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pipeline" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15b6607fa632996eb8a17c9041cb6071cb75ac057abd45dece578723ea8c7c0" + [[package]] name = "pkg-config" version = "0.3.19" @@ -693,6 +823,12 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "proc-macro2" version = "1.0.24" @@ -730,6 +866,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", + "rand_pcg", +] + [[package]] name = "rand" version = "0.8.3" @@ -737,9 +887,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.0", "rand_core 0.6.2", - "rand_hc", + "rand_hc 0.3.0", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -767,6 +927,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + [[package]] name = "rand_core" version = "0.6.2" @@ -776,6 +945,15 @@ dependencies = [ "getrandom 0.2.2", ] +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rand_hc" version = "0.3.0" @@ -785,6 +963,15 @@ dependencies = [ "rand_core 0.6.2", ] +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -933,6 +1120,37 @@ dependencies = [ "termcolor", ] +[[package]] +name = "siphasher" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27" + +[[package]] +name = "string_cache" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a" +dependencies = [ + "lazy_static", + "new_debug_unreachable", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote 1.0.9", +] + [[package]] name = "strsim" version = "0.8.0" @@ -1010,6 +1228,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "tendril" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -1092,6 +1321,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "vec_map" version = "0.8.2" @@ -1204,6 +1439,18 @@ dependencies = [ "bitflags 0.9.1", ] +[[package]] +name = "xml5ever" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b1b52e6e8614d4a58b8e70cf51ec0cc21b256ad8206708bcff8139b5bbd6a59" +dependencies = [ + "log", + "mac", + "markup5ever", + "time", +] + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml index 02080d6..0134cb0 100644 --- a/espanso/Cargo.toml +++ b/espanso/Cargo.toml @@ -34,4 +34,6 @@ crossbeam = "0.8.0" enum-as-inner = "0.3.3" dirs = "3.0.1" serde = { version = "1.0.123", features = ["derive"] } -serde_json = "1.0.62" \ No newline at end of file +serde_json = "1.0.62" +markdown = "0.3.0" +html2text = "0.2.1" \ No newline at end of file diff --git a/espanso/src/cli/worker/engine/executor/clipboard_injector.rs b/espanso/src/cli/worker/engine/executor/clipboard_injector.rs index eee4cae..848ef0b 100644 --- a/espanso/src/cli/worker/engine/executor/clipboard_injector.rs +++ b/espanso/src/cli/worker/engine/executor/clipboard_injector.rs @@ -20,7 +20,7 @@ use espanso_inject::{Injector, keys::Key}; use espanso_clipboard::Clipboard; -use crate::engine::{dispatch::TextInjector}; +use crate::engine::{dispatch::TextInjector, dispatch::HtmlInjector}; pub struct ClipboardInjectorAdapter<'a> { injector: &'a dyn Injector, @@ -34,6 +34,16 @@ impl <'a> ClipboardInjectorAdapter<'a> { clipboard, } } + + fn send_paste_combination(&self) -> anyhow::Result<()> { + // TODO: handle delay duration + std::thread::sleep(std::time::Duration::from_millis(100)); + + // TODO: handle options + self.injector.send_key_combination(&[Key::Control, Key::V], Default::default())?; + + Ok(()) + } } impl <'a> TextInjector for ClipboardInjectorAdapter<'a> { @@ -45,11 +55,18 @@ impl <'a> TextInjector for ClipboardInjectorAdapter<'a> { // TODO: handle clipboard restoration self.clipboard.set_text(text)?; - // TODO: handle delay duration - std::thread::sleep(std::time::Duration::from_millis(100)); - - // TODO: handle options - self.injector.send_key_combination(&[Key::Control, Key::V], Default::default())?; + self.send_paste_combination()?; + + Ok(()) + } +} + +impl <'a> HtmlInjector for ClipboardInjectorAdapter<'a> { + fn inject_html(&self, html: &str, fallback_text: &str) -> anyhow::Result<()> { + // TODO: handle clipboard restoration + self.clipboard.set_html(html, Some(fallback_text))?; + + self.send_paste_combination()?; Ok(()) } diff --git a/espanso/src/cli/worker/engine/mod.rs b/espanso/src/cli/worker/engine/mod.rs index 0333fd6..04a31a1 100644 --- a/espanso/src/cli/worker/engine/mod.rs +++ b/espanso/src/cli/worker/engine/mod.rs @@ -124,6 +124,7 @@ pub fn initialize_and_spawn( &clipboard_injector, &config_manager, &key_injector, + &clipboard_injector, ); let mut engine = crate::engine::Engine::new(&funnel, &mut processor, &dispatcher); diff --git a/espanso/src/cli/worker/engine/multiplex.rs b/espanso/src/cli/worker/engine/multiplex.rs index 45b7059..bbcfb1d 100644 --- a/espanso/src/cli/worker/engine/multiplex.rs +++ b/espanso/src/cli/worker/engine/multiplex.rs @@ -19,7 +19,14 @@ use espanso_config::matches::{Match, MatchEffect}; -use crate::engine::{event::{EventType, internal::DetectedMatch, internal::RenderingRequestedEvent}, process::Multiplexer}; +use crate::engine::{ + event::{ + internal::DetectedMatch, + internal::{RenderingRequestedEvent, TextFormat}, + EventType, + }, + process::Multiplexer, +}; pub trait MatchProvider<'a> { fn get(&self, match_id: i32) -> Option<&'a Match>; @@ -40,15 +47,24 @@ impl<'a> Multiplexer for MultiplexAdapter<'a> { let m = self.provider.get(detected_match.id)?; match &m.effect { - MatchEffect::Text(_) => Some(EventType::RenderingRequested(RenderingRequestedEvent { + MatchEffect::Text(effect) => Some(EventType::RenderingRequested(RenderingRequestedEvent { match_id: detected_match.id, trigger: detected_match.trigger, left_separator: detected_match.left_separator, right_separator: detected_match.right_separator, trigger_args: detected_match.args, + format: convert_format(&effect.format), })), - // TODO: think about rich text and image + // TODO: think about image MatchEffect::None => None, } } } + +fn convert_format(format: &espanso_config::matches::TextFormat) -> TextFormat { + match format { + espanso_config::matches::TextFormat::Plain => TextFormat::Plain, + espanso_config::matches::TextFormat::Markdown => TextFormat::Markdown, + espanso_config::matches::TextFormat::Html => TextFormat::Html, + } +} diff --git a/espanso/src/engine/dispatch/default.rs b/espanso/src/engine/dispatch/default.rs index df80ff8..55d28a3 100644 --- a/espanso/src/engine/dispatch/default.rs +++ b/espanso/src/engine/dispatch/default.rs @@ -18,7 +18,7 @@ */ use super::Event; -use super::{ModeProvider, Dispatcher, Executor, KeyInjector, TextInjector}; +use super::{ModeProvider, Dispatcher, Executor, KeyInjector, TextInjector, HtmlInjector}; pub struct DefaultDispatcher<'a> { executors: Vec>, @@ -30,6 +30,7 @@ impl<'a> DefaultDispatcher<'a> { clipboard_injector: &'a dyn TextInjector, mode_provider: &'a dyn ModeProvider, key_injector: &'a dyn KeyInjector, + html_injector: &'a dyn HtmlInjector, ) -> Self { Self { executors: vec![ @@ -41,6 +42,9 @@ impl<'a> DefaultDispatcher<'a> { Box::new(super::executor::key_inject::KeyInjectExecutor::new( key_injector, )), + Box::new(super::executor::html_inject::HtmlInjectExecutor::new( + html_injector, + )) ], } } diff --git a/espanso/src/engine/dispatch/executor/html_inject.rs b/espanso/src/engine/dispatch/executor/html_inject.rs new file mode 100644 index 0000000..df72cf4 --- /dev/null +++ b/espanso/src/engine/dispatch/executor/html_inject.rs @@ -0,0 +1,61 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use super::super::{Event, Executor}; +use crate::engine::event::EventType; +use anyhow::Result; +use log::error; + +pub trait HtmlInjector { + fn inject_html(&self, html: &str, fallback: &str) -> Result<()>; +} + +pub struct HtmlInjectExecutor<'a> { + injector: &'a dyn HtmlInjector, +} + +impl<'a> HtmlInjectExecutor<'a> { + pub fn new(injector: &'a dyn HtmlInjector) -> Self { + Self { injector } + } +} + +impl<'a> Executor for HtmlInjectExecutor<'a> { + fn execute(&self, event: &Event) -> bool { + if let EventType::HtmlInject(inject_event) = &event.etype { + // Render the text fallback for those applications that don't support HTML clipboard + let decorator = html2text::render::text_renderer::TrivialDecorator::new(); + let fallback_text = + html2text::from_read_with_decorator(inject_event.html.as_bytes(), 1000000, decorator); + + if let Err(error) = self + .injector + .inject_html(&inject_event.html, &fallback_text) + { + error!("html injector reported an error: {:?}", error); + } + + return true; + } + + false + } +} + +// TODO: test diff --git a/espanso/src/engine/dispatch/executor/mod.rs b/espanso/src/engine/dispatch/executor/mod.rs index 9631006..09c73d0 100644 --- a/espanso/src/engine/dispatch/executor/mod.rs +++ b/espanso/src/engine/dispatch/executor/mod.rs @@ -18,4 +18,5 @@ */ pub mod text_inject; -pub mod key_inject; \ No newline at end of file +pub mod key_inject; +pub mod html_inject; \ No newline at end of file diff --git a/espanso/src/engine/dispatch/mod.rs b/espanso/src/engine/dispatch/mod.rs index 3bb7c8e..ca80f8c 100644 --- a/espanso/src/engine/dispatch/mod.rs +++ b/espanso/src/engine/dispatch/mod.rs @@ -33,7 +33,8 @@ pub trait Dispatcher { } // Re-export dependency injection entities -pub use executor::text_inject::{ModeProvider, Mode, TextInjector}; +pub use executor::html_inject::HtmlInjector; +pub use executor::text_inject::{Mode, ModeProvider, TextInjector}; // TODO: move into module pub trait KeyInjector { @@ -45,6 +46,13 @@ pub fn default<'a>( clipboard_injector: &'a dyn TextInjector, mode_provider: &'a dyn ModeProvider, key_injector: &'a dyn KeyInjector, + html_injector: &'a dyn HtmlInjector, ) -> impl Dispatcher + 'a { - default::DefaultDispatcher::new(event_injector, clipboard_injector, mode_provider, key_injector) + default::DefaultDispatcher::new( + event_injector, + clipboard_injector, + mode_provider, + key_injector, + html_injector, + ) } diff --git a/espanso/src/engine/event/effect.rs b/espanso/src/engine/event/effect.rs index f3f103a..d554b1e 100644 --- a/espanso/src/engine/event/effect.rs +++ b/espanso/src/engine/event/effect.rs @@ -36,6 +36,16 @@ pub struct TextInjectRequest { pub force_mode: Option, } +#[derive(Debug, Clone)] +pub struct MarkdownInjectRequest { + pub markdown: String, +} + +#[derive(Debug, Clone)] +pub struct HtmlInjectRequest { + pub html: String, +} + #[derive(Debug, PartialEq, Clone)] pub enum TextInjectMode { Keys, diff --git a/espanso/src/engine/event/internal.rs b/espanso/src/engine/event/internal.rs index bb5b673..6d9a510 100644 --- a/espanso/src/engine/event/internal.rs +++ b/espanso/src/engine/event/internal.rs @@ -50,12 +50,21 @@ pub struct RenderingRequestedEvent { pub left_separator: Option, pub right_separator: Option, pub trigger_args: HashMap, + pub format: TextFormat, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum TextFormat { + Plain, + Markdown, + Html, } #[derive(Debug, Clone, PartialEq)] pub struct RenderedEvent { pub match_id: i32, pub body: String, + pub format: TextFormat, } #[derive(Debug, Clone, PartialEq)] diff --git a/espanso/src/engine/event/mod.rs b/espanso/src/engine/event/mod.rs index bdb3e94..324fead 100644 --- a/espanso/src/engine/event/mod.rs +++ b/espanso/src/engine/event/mod.rs @@ -69,4 +69,6 @@ pub enum EventType { KeySequenceInject(effect::KeySequenceInjectRequest), TextInject(effect::TextInjectRequest), + MarkdownInject(effect::MarkdownInjectRequest), + HtmlInject(effect::HtmlInjectRequest), } \ No newline at end of file diff --git a/espanso/src/engine/process/default.rs b/espanso/src/engine/process/default.rs index 216ddaf..783e8eb 100644 --- a/espanso/src/engine/process/default.rs +++ b/espanso/src/engine/process/default.rs @@ -22,7 +22,7 @@ use log::trace; use super::{MatchFilter, MatchInfoProvider, MatchSelector, Matcher, Middleware, Multiplexer, Processor, Renderer, middleware::{ match_select::MatchSelectMiddleware, matcher::MatcherMiddleware, multiplex::MultiplexMiddleware, render::RenderMiddleware, action::{ActionMiddleware, EventSequenceProvider}, cursor_hint::CursorHintMiddleware, cause::CauseCompensateMiddleware, - delay_modifiers::{DelayForModifierReleaseMiddleware, ModifierStatusProvider}, + delay_modifiers::{DelayForModifierReleaseMiddleware, ModifierStatusProvider}, markdown::MarkdownMiddleware, past_discard::PastEventsDiscardMiddleware, }}; use crate::engine::event::{Event, EventType}; @@ -55,6 +55,7 @@ impl<'a> DefaultProcessor<'a> { Box::new(RenderMiddleware::new(renderer)), Box::new(CursorHintMiddleware::new()), Box::new(ActionMiddleware::new(match_info_provider, event_sequence_provider)), + Box::new(MarkdownMiddleware::new()), Box::new(DelayForModifierReleaseMiddleware::new(modifier_status_provider)), ], } diff --git a/espanso/src/engine/process/middleware/action.rs b/espanso/src/engine/process/middleware/action.rs index 41ae3e2..a581ac6 100644 --- a/espanso/src/engine/process/middleware/action.rs +++ b/espanso/src/engine/process/middleware/action.rs @@ -18,12 +18,7 @@ */ use super::super::Middleware; -use crate::engine::event::{ - effect::{KeySequenceInjectRequest, TextInjectMode, TextInjectRequest}, - input::Key, - internal::DiscardPreviousEvent, - Event, EventType, -}; +use crate::engine::event::{Event, EventType, effect::{HtmlInjectRequest, KeySequenceInjectRequest, MarkdownInjectRequest, TextInjectMode, TextInjectRequest}, input::Key, internal::{DiscardPreviousEvent, TextFormat}}; pub trait MatchInfoProvider { fn get_force_mode(&self, match_id: i32) -> Option; @@ -68,10 +63,18 @@ impl<'a> Middleware for ActionMiddleware<'a> { Event::caused_by( event.source_id, - EventType::TextInject(TextInjectRequest { - text: m_event.body.clone(), - force_mode: self.match_info_provider.get_force_mode(m_event.match_id), - }), + match m_event.format { + TextFormat::Plain => EventType::TextInject(TextInjectRequest { + text: m_event.body.clone(), + force_mode: self.match_info_provider.get_force_mode(m_event.match_id), + }), + TextFormat::Html => EventType::HtmlInject(HtmlInjectRequest { + html: m_event.body.clone(), + }), + TextFormat::Markdown => EventType::MarkdownInject(MarkdownInjectRequest { + markdown: m_event.body.clone(), + }), + }, ) } EventType::CursorHintCompensation(m_event) => { diff --git a/espanso/src/engine/process/middleware/markdown.rs b/espanso/src/engine/process/middleware/markdown.rs new file mode 100644 index 0000000..7ba0d8e --- /dev/null +++ b/espanso/src/engine/process/middleware/markdown.rs @@ -0,0 +1,60 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use super::super::Middleware; +use crate::engine::event::{Event, EventType, effect::{HtmlInjectRequest}}; + +// Convert markdown injection requests to HTML on the fly +pub struct MarkdownMiddleware {} + +impl MarkdownMiddleware { + pub fn new() -> Self { + Self {} + } +} + +impl Middleware for MarkdownMiddleware { + fn name(&self) -> &'static str { + "markdown" + } + + fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { + if let EventType::MarkdownInject(m_event) = &event.etype { + // Render the markdown into HTML + let html = markdown::to_html(&m_event.markdown); + let mut html = html.trim(); + + // Remove the surrounding paragraph + if html.starts_with("

") { + html = html.trim_start_matches("

"); + } + if html.ends_with("

") { + html = html.trim_end_matches("

"); + } + + return Event::caused_by(event.source_id, EventType::HtmlInject(HtmlInjectRequest { + html: html.to_owned(), + })) + } + + event + } +} + +// TODO: test diff --git a/espanso/src/engine/process/middleware/mod.rs b/espanso/src/engine/process/middleware/mod.rs index c183a3e..bb85599 100644 --- a/espanso/src/engine/process/middleware/mod.rs +++ b/espanso/src/engine/process/middleware/mod.rs @@ -23,6 +23,7 @@ pub mod cursor_hint; pub mod delay_modifiers; pub mod match_select; pub mod matcher; +pub mod markdown; pub mod multiplex; pub mod past_discard; pub mod render; diff --git a/espanso/src/engine/process/middleware/render.rs b/espanso/src/engine/process/middleware/render.rs index ffbc3a1..128e0b4 100644 --- a/espanso/src/engine/process/middleware/render.rs +++ b/espanso/src/engine/process/middleware/render.rs @@ -62,6 +62,7 @@ impl<'a> Middleware for RenderMiddleware<'a> { EventType::Rendered(RenderedEvent { match_id: m_event.match_id, body, + format: m_event.format, }), ); } diff --git a/espanso/src/main.rs b/espanso/src/main.rs index 1682255..5f87e36 100644 --- a/espanso/src/main.rs +++ b/espanso/src/main.rs @@ -226,6 +226,7 @@ fn main() { let config = ConfigBuilder::new() .set_time_to_local(true) .set_location_level(LevelFilter::Off) + .add_filter_ignore_str("html5ever") .build(); CombinedLogger::init(vec![