Add option to sort by most frequently used

This commit is contained in:
loserMcloser 2022-04-10 22:14:00 -06:00
parent bf216d7e45
commit 3714e5e0be
5 changed files with 60 additions and 41 deletions

View File

@ -41,10 +41,13 @@ exclude = [] # list of regexes for excluded app ids (name of the .desktop file)
# use "" to disable launching commands
command_prefix = ":"
frequent_first = true # sort matches of equal quality by most frequently used
recent_first = true # sort matches of equal quality by most recently used
# when both frequent_first and recent_first are set,
# sorting is by frequency first, and recency is used to break ties in frequency
# term_command = "alacritty -e {}" # command for applications run in terminal (default uses "$TERMINAL -e")
# specify name overrides (id is the name of the desktop file)
[name_overrides]
# id = "name\rextra"
# id = "name\rextra"

View File

@ -27,7 +27,7 @@ use gio::AppInfo;
use glib::shell_unquote;
use crate::locale::string_collate;
use super::{consts::*, Config, Field, History};
use super::{consts::*, Config, Field, HistoryData};
use regex::RegexSet;
#[derive(Eq)]
@ -38,7 +38,7 @@ pub struct AppEntry {
pub info: AppInfo,
pub label: Label,
pub score: i64,
pub last_used: u64,
pub history: HistoryData
}
impl AppEntry {
@ -73,7 +73,7 @@ impl AppEntry {
add_attrs(&attr_list, &config.markup_default, 0, self.display_string.len() as u32);
if let Some((lo, hi)) = self.extra_range {
add_attrs(&attr_list, &config.markup_extra, lo, hi);
add_attrs(&attr_list, &config.markup_extra, lo, hi);
}
self.label.set_attributes(Some(&attr_list));
}
@ -81,15 +81,18 @@ impl AppEntry {
impl PartialEq for AppEntry {
fn eq(&self, other: &Self) -> bool {
self.score.eq(&other.score) && self.last_used.eq(&other.last_used)
self.score.eq(&other.score) && self.history.eq(&other.history)
}
}
impl Ord for AppEntry {
fn cmp(&self, other: &Self) -> Ordering {
match self.score.cmp(&other.score) {
Ordering::Equal => match self.last_used.cmp(&other.last_used) {
Ordering::Equal => string_collate(&self.display_string, &other.display_string),
Ordering::Equal => match self.history.usage_count.cmp(&other.history.usage_count) {
Ordering::Equal => match self.history.last_used.cmp(&other.history.last_used) {
Ordering::Equal => string_collate(&self.display_string, &other.display_string),
ord => ord.reverse()
}
ord => ord.reverse()
}
ord => ord.reverse()
@ -129,7 +132,8 @@ fn add_attrs(list: &AttrList, attrs: &Vec<Attribute>, start: u32, end: u32) {
}
}
pub fn load_entries(config: &Config, history: &History) -> HashMap<ListBoxRow, AppEntry> {
pub fn load_entries(config: &Config, history: &HashMap<String, HistoryData>)
-> HashMap<ListBoxRow, AppEntry> {
let mut entries = HashMap::new();
let icon_theme = IconTheme::default().unwrap();
let apps = gio::AppInfo::all();
@ -151,14 +155,14 @@ pub fn load_entries(config: &Config, history: &History) -> HashMap<ListBoxRow, A
continue
}
let (display_string, extra_range) = if let Some(name)
let (display_string, extra_range) = if let Some(name)
= get_app_field(&app, Field::Id).and_then(|id| config.name_overrides.get(&id)) {
let i = name.find('\r');
(name.replace('\r', " "), i.map(|i| (i as u32 +1, name.len() as u32)))
} else {
let extra = config.extra_field.get(0).and_then(|f| get_app_field(&app, *f));
match extra {
Some(e) if (!config.hide_extra_if_contained || !name.to_lowercase().contains(&e.to_lowercase())) => (format!("{} {}", name, e),
Some(e) if (!config.hide_extra_if_contained || !name.to_lowercase().contains(&e.to_lowercase())) => (format!("{} {}", name, e),
Some((name.len() as u32 + 1, name.len() as u32 + 1 + e.len() as u32))),
_ => (name, None)
}
@ -202,9 +206,9 @@ pub fn load_entries(config: &Config, history: &History) -> HashMap<ListBoxRow, A
row.add(&hbox);
row.style_context().add_class(APP_ROW_CLASS);
let last_used = if config.recent_first {
history.last_used.get(&id).copied().unwrap_or_default()
} else { 0 };
let history_data = history.get(&id).copied().unwrap_or_default();
let last_used = if config.recent_first { history_data.last_used } else { 0 };
let usage_count = if config.frequent_first { history_data.usage_count } else { 0 };
let app_entry = AppEntry {
display_string,
@ -213,7 +217,7 @@ pub fn load_entries(config: &Config, history: &History) -> HashMap<ListBoxRow, A
info: app,
label,
score: 100,
last_used,
history: HistoryData { last_used, usage_count }
};
app_entry.set_markup(config);
entries.insert(row, app_entry);

View File

@ -50,6 +50,7 @@ make_config!(Config {
markup_highlight: Vec<Attribute> = (parse_attributes("foreground=\"red\" underline=\"double\"").unwrap()) "markup_highlight" [deserialize_with = "deserialize_markup"],
markup_extra: Vec<Attribute> = (parse_attributes("font_style=\"italic\" font_size=\"smaller\"").unwrap()) "markup_extra" [deserialize_with = "deserialize_markup"],
exclusive: bool = (true) "exclusive",
frequent_first: bool = (true) "frequent_first",
recent_first: bool = (true) "recent_first",
icon_size: i32 = (64) "icon_size",
lines: i32 = (2) "lines",

View File

@ -5,31 +5,42 @@ use std::time::{SystemTime, UNIX_EPOCH};
use super::util::get_history_file;
use std::fs::File;
#[derive(Deserialize, Serialize)]
pub struct History {
pub last_used: HashMap<String, u64>
#[derive(Copy, Clone, Default, Eq, Deserialize, Serialize)]
pub struct HistoryData {
pub last_used : u64,
pub usage_count : u32
}
impl History {
pub fn load() -> History {
match get_history_file(false) {
Some(file) => {
let config_str = std::fs::read_to_string(file).expect("Cannot read history file");
toml::from_str(&config_str).expect("Cannot parse config: {}")
},
_ => History { last_used: HashMap::new() }
}
impl PartialEq for HistoryData {
fn eq(&self, other: &Self) -> bool {
self.last_used.eq(&other.last_used) && self.usage_count.eq(&other.usage_count)
}
}
pub fn load_history() -> HashMap<String, HistoryData> {
match get_history_file(false) {
Some(file) => {
let config_str = std::fs::read_to_string(file).expect("Cannot read history file");
toml::from_str(&config_str).expect("Cannot parse config: {}")
},
_ => HashMap::new()
}
}
pub fn save_history(history: &HashMap<String, HistoryData>) {
let file = get_history_file(true).expect("Cannot create history file or cache directory");
let mut file = File::create(file).expect("Cannot open history file for writing");
let s = toml::to_string(history).unwrap();
file.write_all(s.as_bytes()).expect("Cannot write to history file");
}
pub fn update_history(history: &mut HashMap<String, HistoryData>, id: &str) {
let epoch = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards");
let usage_count;
match history.get(&id.to_string()) {
Some(history_match) => { usage_count = history_match.usage_count + 1 },
None => { usage_count = 1 }
}
pub fn save(&self) {
let file = get_history_file(true).expect("Cannot create history file or cache directory");
let mut file = File::create(file).expect("Cannot open history file for writing");
let s = toml::to_string(self).unwrap();
file.write_all(s.as_bytes()).expect("Cannot write to history file");
}
pub fn update(&mut self, id: &str) {
let epoch = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards");
self.last_used.insert(id.to_string(), epoch.as_secs());
}
}
history.insert( id.to_string(), HistoryData{ last_used: epoch.as_secs(), usage_count } );
}

View File

@ -85,7 +85,7 @@ fn app_startup(application: &gtk::Application) {
let listbox = gtk::ListBoxBuilder::new().name(LISTBOX_NAME).build();
scroll.add(&listbox);
let history = Rc::new(RefCell::new(History::load()));
let history = Rc::new(RefCell::new(load_history()));
let entries = Rc::new(RefCell::new(load_entries(&config, &history.borrow())));
for (row, _) in &entries.borrow() as &HashMap<ListBoxRow, AppEntry> {
@ -155,8 +155,8 @@ fn app_startup(application: &gtk::Application) {
launch_app(&e.info, term_command.as_deref());
let mut history = history.borrow_mut();
history.update(e.info.id().unwrap().as_str());
history.save();
update_history(&mut history, e.info.id().unwrap().as_str());
save_history(&history);
window.close();
}