feat(core): implement rich text matches

This commit is contained in:
Federico Terzi 2021-04-29 22:37:44 +02:00
parent 038798a2d6
commit 2f53752e97
18 changed files with 472 additions and 27 deletions

251
Cargo.lock generated
View File

@ -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"

View File

@ -35,3 +35,5 @@ enum-as-inner = "0.3.3"
dirs = "3.0.1"
serde = { version = "1.0.123", features = ["derive"] }
serde_json = "1.0.62"
markdown = "0.3.0"
html2text = "0.2.1"

View File

@ -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(())
}

View File

@ -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);

View File

@ -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,
}
}

View File

@ -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<Box<dyn Executor + 'a>>,
@ -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,
))
],
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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

View File

@ -19,3 +19,4 @@
pub mod text_inject;
pub mod key_inject;
pub mod html_inject;

View File

@ -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,
)
}

View File

@ -36,6 +36,16 @@ pub struct TextInjectRequest {
pub force_mode: Option<TextInjectMode>,
}
#[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,

View File

@ -50,12 +50,21 @@ pub struct RenderingRequestedEvent {
pub left_separator: Option<String>,
pub right_separator: Option<String>,
pub trigger_args: HashMap<String, String>,
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)]

View File

@ -69,4 +69,6 @@ pub enum EventType {
KeySequenceInject(effect::KeySequenceInjectRequest),
TextInject(effect::TextInjectRequest),
MarkdownInject(effect::MarkdownInjectRequest),
HtmlInject(effect::HtmlInjectRequest),
}

View File

@ -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)),
],
}

View File

@ -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<TextInjectMode>;
@ -68,10 +63,18 @@ impl<'a> Middleware for ActionMiddleware<'a> {
Event::caused_by(
event.source_id,
EventType::TextInject(TextInjectRequest {
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) => {

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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("<p>") {
html = html.trim_start_matches("<p>");
}
if html.ends_with("</p>") {
html = html.trim_end_matches("</p>");
}
return Event::caused_by(event.source_id, EventType::HtmlInject(HtmlInjectRequest {
html: html.to_owned(),
}))
}
event
}
}
// TODO: test

View File

@ -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;

View File

@ -62,6 +62,7 @@ impl<'a> Middleware for RenderMiddleware<'a> {
EventType::Rendered(RenderedEvent {
match_id: m_event.match_id,
body,
format: m_event.format,
}),
);
}

View File

@ -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![