Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions plugins/applications/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@ Config(
// The terminal used for running terminal based desktop entries, if left as `None` a static list of terminals is used
// to determine what terminal to use.
terminal: Some("alacritty"),
// The history size for the application history, set to 0 to disable history
history_size: 50,
)
```
67 changes: 67 additions & 0 deletions plugins/applications/src/history.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use std::collections::VecDeque;
use std::fs;
use std::env;

use crate::scrubber::DesktopEntry;


pub struct History(VecDeque<DesktopEntry>);

impl History {
pub fn new() -> Self {
Self(VecDeque::new())
}

pub fn load() -> Self {

let path = format!(
"{}/.cache/anyrun-applications-history",
env::var("HOME").expect("Unable to determine HOME directory")
);

if let Ok(content) = fs::read_to_string(&path) {
let history: VecDeque<DesktopEntry> = ron::from_str(&content)
.unwrap_or_else(|why| {
eprintln!("Error parsing history: {}", why);
VecDeque::new()
});
return Self(history);
}

Self::new()
}

pub fn write(&self) {

let path = format!(
"{}/.cache/anyrun-applications-history",
env::var("HOME").expect("Unable to determine HOME directory")
);

let content = ron::to_string(&self.0).unwrap_or_else(|why| {
eprintln!("Error serializing history: {}", why);
String::new()
});
if let Err(why) = fs::write(&path, content) {
eprintln!("Error writing history: {}", why);
}
}

pub fn add_entry(&mut self, entry: DesktopEntry) {
self.0.push_front(entry);
}

pub fn truncate(&mut self, max_entries: usize) {
self.0.truncate(max_entries);
}

pub fn get_entry_info(&self, entry: &DesktopEntry) -> Option<(usize, usize)> {
let index = self.0.iter().position(|x| x == entry)?;
let count = self.0.iter().filter(|x| *x == entry).count();
Some((index, count))
}
pub fn count(&self) -> usize {
self.0.len()
}
}

52 changes: 37 additions & 15 deletions plugins/applications/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub struct Config {
desktop_actions: bool,
max_entries: usize,
terminal: Option<String>,
history_size: usize,
}

impl Default for Config {
Expand All @@ -18,21 +19,24 @@ impl Default for Config {
desktop_actions: false,
max_entries: 5,
terminal: None,
history_size: 50,
}
}
}

pub struct State {
config: Config,
config: Config,
entries: Vec<(DesktopEntry, u64)>,
history: history::History,
}

mod scrubber;
mod history;

const SENSIBLE_TERMINALS: &[&str] = &["alacritty", "foot", "kitty", "wezterm", "wterm"];

#[handler]
pub fn handler(selection: Match, state: &State) -> HandleResult {
pub fn handler(selection: Match, state: &mut State) -> HandleResult {
let entry = state
.entries
.iter()
Expand Down Expand Up @@ -74,6 +78,11 @@ pub fn handler(selection: Match, state: &State) -> HandleResult {
eprintln!("Error running desktop entry: {}", why);
}

state.history.add_entry(entry.clone());
state.history.truncate(state.config.history_size);
state.history.write();


HandleResult::Close
}

Expand All @@ -95,11 +104,15 @@ pub fn init(config_dir: RString) -> State {
Vec::new()
});

State { config, entries }
let history = history::History::load();
println!("Loaded {} history entries", history.count());

State { config, entries, history }
}

#[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
Expand All @@ -116,9 +129,18 @@ pub fn get_matches(input: RString, state: &State) -> RVec<Match> {
.keywords
.iter()
.map(|keyword| matcher.fuzzy_match(keyword, &input).unwrap_or(0))
.sum::<i64>();
.sum::<i64>();

let mut score = (app_score * 25 + keyword_score) - entry.offset;
let history_score = state.history.get_entry_info(entry).map(|(index, count)| {
let recency_bias = i64::max(5-index as i64, 0);
(count as i64 + recency_bias) * 20
}).unwrap_or(0);

if app_score + keyword_score == 0 {
return None;
}

let mut score = (app_score * 25 + keyword_score + history_score) - entry.offset;

// prioritize actions
if entry.desc.is_some() {
Expand All @@ -135,17 +157,17 @@ pub fn get_matches(input: RString, state: &State) -> RVec<Match> {

entries.sort_by(|a, b| b.2.cmp(&a.2));

entries.truncate(state.config.max_entries);
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()
.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]
Expand Down
4 changes: 3 additions & 1 deletion plugins/applications/src/scrubber.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::{collections::HashMap, env, ffi::OsStr, fs, path::PathBuf};

use serde::{Deserialize, Serialize};

use crate::Config;

#[derive(Clone, Debug)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct DesktopEntry {
pub exec: String,
pub path: Option<PathBuf>,
Expand Down