141 lines
3.8 KiB
Rust
141 lines
3.8 KiB
Rust
use abi_stable::std_types::{ROption, RString, RVec};
|
|
use anyrun_plugin::{anyrun_interface::HandleResult, *};
|
|
use fuzzy_matcher::FuzzyMatcher;
|
|
use scrubber::DesktopEntry;
|
|
use serde::Deserialize;
|
|
use std::{env, fs, process::Command};
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct Config {
|
|
desktop_actions: bool,
|
|
max_entries: usize,
|
|
terminal: Option<String>,
|
|
}
|
|
|
|
impl Default for Config {
|
|
fn default() -> Self {
|
|
Self {
|
|
desktop_actions: false,
|
|
max_entries: 5,
|
|
terminal: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct State {
|
|
config: Config,
|
|
entries: Vec<(DesktopEntry, u64)>,
|
|
}
|
|
|
|
mod scrubber;
|
|
|
|
const SENSIBLE_TERMINALS: &[&str] = &["alacritty", "foot", "kitty", "wezterm", "wterm"];
|
|
|
|
#[handler]
|
|
pub fn handler(selection: Match, state: &State) -> HandleResult {
|
|
let entry = state
|
|
.entries
|
|
.iter()
|
|
.find_map(|(entry, id)| {
|
|
if *id == selection.id.unwrap() {
|
|
Some(entry)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.unwrap();
|
|
|
|
if entry.term {
|
|
match &state.config.terminal {
|
|
Some(term) => {
|
|
if let Err(why) = Command::new(term).arg("-e").arg(&entry.exec).spawn() {
|
|
eprintln!("Error running desktop entry: {}", why);
|
|
}
|
|
}
|
|
None => {
|
|
for term in SENSIBLE_TERMINALS {
|
|
if Command::new(term)
|
|
.arg("-e")
|
|
.arg(&entry.exec)
|
|
.spawn()
|
|
.is_ok()
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if let Err(why) = Command::new("sh")
|
|
.arg("-c")
|
|
.arg(&entry.exec)
|
|
.current_dir(entry.path.as_ref().unwrap_or(&env::current_dir().unwrap()))
|
|
.spawn()
|
|
{
|
|
eprintln!("Error running desktop entry: {}", why);
|
|
}
|
|
|
|
HandleResult::Close
|
|
}
|
|
|
|
#[init]
|
|
pub fn init(config_dir: RString) -> State {
|
|
let config: Config = match fs::read_to_string(format!("{}/applications.ron", config_dir)) {
|
|
Ok(content) => ron::from_str(&content).unwrap_or_else(|why| {
|
|
eprintln!("Error parsing applications plugin config: {}", why);
|
|
Config::default()
|
|
}),
|
|
Err(why) => {
|
|
eprintln!("Error reading applications plugin config: {}", why);
|
|
Config::default()
|
|
}
|
|
};
|
|
|
|
let entries = scrubber::scrubber(&config).unwrap_or_else(|why| {
|
|
eprintln!("Failed to load desktop entries: {}", why);
|
|
Vec::new()
|
|
});
|
|
|
|
State { config, entries }
|
|
}
|
|
|
|
#[get_matches]
|
|
pub fn get_matches(input: RString, state: &State) -> RVec<Match> {
|
|
let matcher = fuzzy_matcher::skim::SkimMatcherV2::default().smart_case();
|
|
let mut entries = state
|
|
.entries
|
|
.iter()
|
|
.filter_map(|(entry, id)| {
|
|
let score = matcher.fuzzy_match(&entry.name, &input).unwrap_or(0)
|
|
+ matcher.fuzzy_match(&entry.exec, &input).unwrap_or(0);
|
|
|
|
if score > 0 {
|
|
Some((entry, *id, score))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
entries.sort_by(|a, b| b.2.cmp(&a.2));
|
|
|
|
entries.truncate(state.config.max_entries);
|
|
entries
|
|
.into_iter()
|
|
.map(|(entry, id, _)| Match {
|
|
title: entry.name.clone().into(),
|
|
description: entry.desc.clone().map(|desc| desc.into()).into(),
|
|
use_pango: false,
|
|
icon: ROption::RSome(entry.icon.clone().into()),
|
|
id: ROption::RSome(id),
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
#[info]
|
|
pub fn info() -> PluginInfo {
|
|
PluginInfo {
|
|
name: "Applications".into(),
|
|
icon: "application-x-executable".into(),
|
|
}
|
|
}
|