Add CLI option to list matches and exec a trigger. Fix #263
This commit is contained in:
parent
6766d91af3
commit
968ef578c1
84
src/cli.rs
Normal file
84
src/cli.rs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* This file is part of espanso.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 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 serde::Serialize;
|
||||||
|
use crate::config::ConfigSet;
|
||||||
|
use crate::matcher::{Match, MatchContentType};
|
||||||
|
|
||||||
|
pub fn list_matches(config_set: ConfigSet, onlytriggers: bool) {
|
||||||
|
let matches = filter_matches(config_set);
|
||||||
|
|
||||||
|
for m in matches {
|
||||||
|
for trigger in m.triggers.iter() {
|
||||||
|
if onlytriggers {
|
||||||
|
println!("{}", trigger);
|
||||||
|
}else {
|
||||||
|
match m.content {
|
||||||
|
MatchContentType::Text(ref text) => {
|
||||||
|
println!("{} - {}", trigger, text.replace)
|
||||||
|
},
|
||||||
|
MatchContentType::Image(_) => {
|
||||||
|
// Skip image matches for now
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct JsonMatchEntry {
|
||||||
|
triggers: Vec<String>,
|
||||||
|
replace: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_matches_as_json(config_set: ConfigSet) {
|
||||||
|
let matches = filter_matches(config_set);
|
||||||
|
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
|
||||||
|
for m in matches {
|
||||||
|
match m.content {
|
||||||
|
MatchContentType::Text(ref text) => {
|
||||||
|
entries.push(JsonMatchEntry {
|
||||||
|
triggers: m.triggers,
|
||||||
|
replace: text.replace.clone(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
MatchContentType::Image(_) => {
|
||||||
|
// Skip image matches for now
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = serde_json::to_string(&entries);
|
||||||
|
|
||||||
|
println!("{}", output.unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_matches(config_set: ConfigSet) -> Vec<Match> {
|
||||||
|
let mut output = Vec::new();
|
||||||
|
output.extend(config_set.default.matches);
|
||||||
|
|
||||||
|
// TODO: consider specific matches by class, title or exe path
|
||||||
|
// for specific in config_set.specific {
|
||||||
|
// output.extend(specific.matches)
|
||||||
|
// }
|
||||||
|
output
|
||||||
|
}
|
|
@ -132,22 +132,20 @@ impl<
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_match_by_trigger(&self, trigger: &str) -> Option<Match> {
|
||||||
|
let config = self.config_manager.active_config();
|
||||||
|
|
||||||
|
if let Some(m) = config.matches.iter().find(|m|
|
||||||
|
m.triggers.iter().any(|t| t == trigger)
|
||||||
|
) {
|
||||||
|
Some(m.clone())
|
||||||
|
}else{
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
fn inject_match(&self, m: &Match, trailing_separator: Option<char>, trigger_offset: usize, skip_delete: bool) {
|
||||||
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P<name>\\w+)\\s*\\}\\}").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<
|
|
||||||
'a,
|
|
||||||
S: KeyboardManager,
|
|
||||||
C: ClipboardManager,
|
|
||||||
M: ConfigManager<'a>,
|
|
||||||
U: UIManager,
|
|
||||||
R: Renderer,
|
|
||||||
> MatchReceiver for Engine<'a, S, C, M, U, R>
|
|
||||||
{
|
|
||||||
fn on_match(&self, m: &Match, trailing_separator: Option<char>, trigger_offset: usize) {
|
|
||||||
let config = self.config_manager.active_config();
|
let config = self.config_manager.active_config();
|
||||||
|
|
||||||
if !config.enable_active {
|
if !config.enable_active {
|
||||||
|
@ -163,7 +161,9 @@ impl<
|
||||||
m.triggers[trigger_offset].chars().count() as i32 + 1 // Count also the separator
|
m.triggers[trigger_offset].chars().count() as i32 + 1 // Count also the separator
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !skip_delete {
|
||||||
self.keyboard_manager.delete_string(&config, char_count);
|
self.keyboard_manager.delete_string(&config, char_count);
|
||||||
|
}
|
||||||
|
|
||||||
let mut previous_clipboard_content: Option<String> = None;
|
let mut previous_clipboard_content: Option<String> = None;
|
||||||
|
|
||||||
|
@ -287,6 +287,24 @@ impl<
|
||||||
// Re-allow espanso to interpret actions
|
// Re-allow espanso to interpret actions
|
||||||
self.is_injecting.store(false, Release);
|
self.is_injecting.store(false, Release);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P<name>\\w+)\\s*\\}\\}").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
'a,
|
||||||
|
S: KeyboardManager,
|
||||||
|
C: ClipboardManager,
|
||||||
|
M: ConfigManager<'a>,
|
||||||
|
U: UIManager,
|
||||||
|
R: Renderer,
|
||||||
|
> MatchReceiver for Engine<'a, S, C, M, U, R>
|
||||||
|
{
|
||||||
|
fn on_match(&self, m: &Match, trailing_separator: Option<char>, trigger_offset: usize) {
|
||||||
|
self.inject_match(m, trailing_separator, trigger_offset, false);
|
||||||
|
}
|
||||||
|
|
||||||
fn on_enable_update(&self, status: bool) {
|
fn on_enable_update(&self, status: bool) {
|
||||||
let message = if status {
|
let message = if status {
|
||||||
|
@ -432,6 +450,17 @@ impl<
|
||||||
self.ui_manager.notify(&message);
|
self.ui_manager.notify(&message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SystemEvent::Trigger(trigger) => {
|
||||||
|
let m = self.find_match_by_trigger(&trigger);
|
||||||
|
match m {
|
||||||
|
Some(m) => {
|
||||||
|
self.inject_match(&m, None, 0, true);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
warn!("No match found with trigger: {}", trigger)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,6 +130,9 @@ pub enum SystemEvent {
|
||||||
|
|
||||||
// Notification
|
// Notification
|
||||||
NotifyRequest(String),
|
NotifyRequest(String),
|
||||||
|
|
||||||
|
// Trigger an expansion from IPC
|
||||||
|
Trigger(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receivers
|
// Receivers
|
||||||
|
|
57
src/main.rs
57
src/main.rs
|
@ -69,6 +69,7 @@ mod render;
|
||||||
mod sysdaemon;
|
mod sysdaemon;
|
||||||
mod system;
|
mod system;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
mod cli;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
@ -158,6 +159,32 @@ fn main() {
|
||||||
.subcommand(SubCommand::with_name("default")
|
.subcommand(SubCommand::with_name("default")
|
||||||
.about("Print the default configuration file path."))
|
.about("Print the default configuration file path."))
|
||||||
)
|
)
|
||||||
|
.subcommand(SubCommand::with_name("match")
|
||||||
|
.about("List and execute matches from the CLI")
|
||||||
|
.subcommand(SubCommand::with_name("list")
|
||||||
|
.about("Print all matches to standard output")
|
||||||
|
.arg(Arg::with_name("json")
|
||||||
|
.short("j")
|
||||||
|
.long("json")
|
||||||
|
.help("Return the matches as json")
|
||||||
|
.required(false)
|
||||||
|
.takes_value(false)
|
||||||
|
)
|
||||||
|
.arg(Arg::with_name("onlytriggers")
|
||||||
|
.short("t")
|
||||||
|
.long("onlytriggers")
|
||||||
|
.help("Print only triggers without replacement")
|
||||||
|
.required(false)
|
||||||
|
.takes_value(false)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.subcommand(SubCommand::with_name("exec")
|
||||||
|
.about("Triggers the expansion of the given match")
|
||||||
|
.arg(Arg::with_name("trigger")
|
||||||
|
.help("The trigger of the match to be expanded")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
// Package manager
|
// Package manager
|
||||||
.subcommand(SubCommand::with_name("package")
|
.subcommand(SubCommand::with_name("package")
|
||||||
.about("Espanso package manager commands")
|
.about("Espanso package manager commands")
|
||||||
|
@ -274,6 +301,11 @@ fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(matches) = matches.subcommand_matches("match") {
|
||||||
|
match_main(config_set, matches);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("package") {
|
if let Some(matches) = matches.subcommand_matches("package") {
|
||||||
if let Some(matches) = matches.subcommand_matches("install") {
|
if let Some(matches) = matches.subcommand_matches("install") {
|
||||||
install_main(config_set, matches);
|
install_main(config_set, matches);
|
||||||
|
@ -1229,6 +1261,31 @@ fn path_main(_config_set: ConfigSet, matches: &ArgMatches) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn match_main(config_set: ConfigSet, matches: &ArgMatches) {
|
||||||
|
if let Some(matches) = matches.subcommand_matches("list") {
|
||||||
|
let json = matches.is_present("json");
|
||||||
|
let onlytriggers = matches.is_present("onlytriggers");
|
||||||
|
|
||||||
|
if !json {
|
||||||
|
crate::cli::list_matches(config_set, onlytriggers);
|
||||||
|
}else{
|
||||||
|
crate::cli::list_matches_as_json(config_set);
|
||||||
|
}
|
||||||
|
}else if let Some(matches) = matches.subcommand_matches("exec") {
|
||||||
|
let trigger = matches.value_of("trigger").unwrap_or_else(|| {
|
||||||
|
eprintln!("missing trigger");
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
send_command_or_warn(
|
||||||
|
Service::Worker,
|
||||||
|
config_set.default.clone(),
|
||||||
|
IPCCommand::trigger(trigger),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn edit_main(matches: &ArgMatches) {
|
fn edit_main(matches: &ArgMatches) {
|
||||||
// Determine which is the file to edit
|
// Determine which is the file to edit
|
||||||
let config = matches.value_of("config").unwrap_or("default");
|
let config = matches.value_of("config").unwrap_or("default");
|
||||||
|
|
|
@ -67,6 +67,9 @@ impl IPCCommand {
|
||||||
"notify" => Some(Event::System(SystemEvent::NotifyRequest(
|
"notify" => Some(Event::System(SystemEvent::NotifyRequest(
|
||||||
self.payload.clone(),
|
self.payload.clone(),
|
||||||
))),
|
))),
|
||||||
|
"trigger" => Some(Event::System(SystemEvent::Trigger(
|
||||||
|
self.payload.clone(),
|
||||||
|
))),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,6 +104,10 @@ impl IPCCommand {
|
||||||
id: "notify".to_owned(),
|
id: "notify".to_owned(),
|
||||||
payload: message,
|
payload: message,
|
||||||
}),
|
}),
|
||||||
|
Event::System(SystemEvent::Trigger(trigger)) => Some(IPCCommand {
|
||||||
|
id: "trigger".to_owned(),
|
||||||
|
payload: trigger,
|
||||||
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,6 +132,13 @@ impl IPCCommand {
|
||||||
payload: "".to_owned(),
|
payload: "".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn trigger(trigger: &str) -> IPCCommand {
|
||||||
|
Self {
|
||||||
|
id: "trigger".to_owned(),
|
||||||
|
payload: trigger.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_event<R: Read, E: Error>(event_channel: &Sender<Event>, stream: Result<R, E>) {
|
fn process_event<R: Read, E: Error>(event_channel: &Sender<Event>, stream: Result<R, E>) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user