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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(?P<name>\\w+)\\s*\\}\\}").unwrap();
|
||||
}
|
||||
fn find_match_by_trigger(&self, trigger: &str) -> Option<Match> {
|
||||
let config = self.config_manager.active_config();
|
||||
|
||||
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) {
|
||||
if let Some(m) = config.matches.iter().find(|m|
|
||||
m.triggers.iter().any(|t| t == trigger)
|
||||
) {
|
||||
Some(m.clone())
|
||||
}else{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_match(&self, m: &Match, trailing_separator: Option<char>, trigger_offset: usize, skip_delete: bool) {
|
||||
let config = self.config_manager.active_config();
|
||||
|
||||
if !config.enable_active {
|
||||
|
@ -163,7 +161,9 @@ impl<
|
|||
m.triggers[trigger_offset].chars().count() as i32 + 1 // Count also the separator
|
||||
};
|
||||
|
||||
if !skip_delete {
|
||||
self.keyboard_manager.delete_string(&config, char_count);
|
||||
}
|
||||
|
||||
let mut previous_clipboard_content: Option<String> = None;
|
||||
|
||||
|
@ -287,6 +287,24 @@ impl<
|
|||
// Re-allow espanso to interpret actions
|
||||
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) {
|
||||
let message = if status {
|
||||
|
@ -432,6 +450,17 @@ impl<
|
|||
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
|
||||
NotifyRequest(String),
|
||||
|
||||
// Trigger an expansion from IPC
|
||||
Trigger(String),
|
||||
}
|
||||
|
||||
// Receivers
|
||||
|
|
57
src/main.rs
57
src/main.rs
|
@ -69,6 +69,7 @@ mod render;
|
|||
mod sysdaemon;
|
||||
mod system;
|
||||
mod ui;
|
||||
mod cli;
|
||||
mod utils;
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
@ -158,6 +159,32 @@ fn main() {
|
|||
.subcommand(SubCommand::with_name("default")
|
||||
.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
|
||||
.subcommand(SubCommand::with_name("package")
|
||||
.about("Espanso package manager commands")
|
||||
|
@ -274,6 +301,11 @@ fn main() {
|
|||
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("install") {
|
||||
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) {
|
||||
// Determine which is the file to edit
|
||||
let config = matches.value_of("config").unwrap_or("default");
|
||||
|
|
|
@ -67,6 +67,9 @@ impl IPCCommand {
|
|||
"notify" => Some(Event::System(SystemEvent::NotifyRequest(
|
||||
self.payload.clone(),
|
||||
))),
|
||||
"trigger" => Some(Event::System(SystemEvent::Trigger(
|
||||
self.payload.clone(),
|
||||
))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -101,6 +104,10 @@ impl IPCCommand {
|
|||
id: "notify".to_owned(),
|
||||
payload: message,
|
||||
}),
|
||||
Event::System(SystemEvent::Trigger(trigger)) => Some(IPCCommand {
|
||||
id: "trigger".to_owned(),
|
||||
payload: trigger,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -125,6 +132,13 @@ impl IPCCommand {
|
|||
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>) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user