use sort mode everywhere

This commit is contained in:
Alexander Mohr 2025-05-04 13:24:16 +02:00
parent 99b893c468
commit 4c8a9771db
3 changed files with 78 additions and 36 deletions

View file

@ -43,6 +43,13 @@ pub enum WrapMode {
Inherit,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum SortOrder {
Default,
Alphabetical
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Mode {
/// searches `$PATH` for executables and allows them to be run by selecting them.
@ -125,6 +132,22 @@ impl FromStr for WrapMode {
}
}
impl FromStr for SortOrder {
type Err = ArgsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"alphabetical" => Ok(SortOrder::Alphabetical),
"default" => Ok(SortOrder::Default),
_ => Err(ArgsError::InvalidParameter(
format!("{s} is not a valid argument, see help for details").to_owned(),
)),
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone, Parser)]
#[clap(about = "Worf is a wofi clone written in rust, it aims to be a drop-in replacement")]
#[derive(Default)]
@ -238,8 +261,8 @@ pub struct Config {
#[clap(short = 'w', long = "columns")]
columns: Option<u32>,
#[clap(short = 'O', long = "sort-order")] // todo support this
sort_order: Option<String>,
#[clap(short = 'O', long = "sort-order")]
sort_order: Option<SortOrder>,
#[clap(short = 'Q', long = "search")]
search: Option<String>,
@ -498,6 +521,11 @@ impl Config {
pub fn no_actions(&self) -> bool {
self.no_actions.unwrap_or(false)
}
#[must_use]
pub fn sort_order(&self) -> SortOrder {
self.sort_order.clone().unwrap_or(SortOrder::Alphabetical)
}
}
fn default_false() -> bool {

View file

@ -25,7 +25,7 @@ use gtk4_layer_shell::{Edge, KeyboardMode, LayerShell};
use log;
use regex::Regex;
use crate::config::{Anchor, Config, MatchMethod, WrapMode};
use crate::config::{Anchor, Config, MatchMethod, SortOrder, WrapMode};
use crate::desktop::known_image_extension_regex_pattern;
use crate::{Error, config, desktop};
@ -1069,17 +1069,22 @@ fn percent_or_absolute(value: &str, base_value: i32) -> Option<i32> {
#[allow(clippy::cast_possible_wrap)]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_precision_loss)]
pub fn sort_menu_items_alphabetically_honor_initial_score<T: Clone>(items: &mut [MenuItem<T>]) {
let special_score = items.len() as f64;
let mut regular_score = 0.0;
items.sort_by(|l, r| r.label.cmp(&l.label));
pub fn apply_sort<T: Clone>(items: &mut [MenuItem<T>], order: &SortOrder) {
match order {
SortOrder::Default => {}
SortOrder::Alphabetical => {
let special_score = items.len() as f64;
let mut regular_score = 0.0;
items.sort_by(|l, r| r.label.cmp(&l.label));
for item in items.iter_mut() {
if item.initial_sort_score == 0.0 {
item.initial_sort_score += regular_score;
regular_score += 1.0;
} else {
item.initial_sort_score += special_score;
for item in items.iter_mut() {
if item.initial_sort_score == 0.0 {
item.initial_sort_score += regular_score;
regular_score += 1.0;
} else {
item.initial_sort_score += special_score;
}
}
}
}
}

View file

@ -1,4 +1,4 @@
use crate::config::{Config, expand_path};
use crate::config::{Config, expand_path, SortOrder};
use crate::desktop::{
copy_to_clipboard, create_file_if_not_exists, find_desktop_files, get_locale_variants,
is_executable, load_cache_file, lookup_name_with_locale, save_cache_file, spawn_fork,
@ -30,10 +30,11 @@ struct DRunProvider<T: Clone> {
cache: HashMap<String, i64>,
data: T,
no_actions: bool,
sort_order: SortOrder,
}
impl<T: Clone + Send + Sync> DRunProvider<T> {
fn new(menu_item_data: T, no_actions: bool) -> Self {
fn new(menu_item_data: T, no_actions: bool, sort_order: SortOrder) -> Self {
let (cache_path, d_run_cache) = load_d_run_cache();
DRunProvider {
items: None,
@ -41,6 +42,7 @@ impl<T: Clone + Send + Sync> DRunProvider<T> {
cache: d_run_cache,
data: menu_item_data,
no_actions,
sort_order,
}
}
@ -144,7 +146,7 @@ impl<T: Clone + Send + Sync> DRunProvider<T> {
start.elapsed().as_millis()
);
gui::sort_menu_items_alphabetically_honor_initial_score(&mut entries);
gui::apply_sort(&mut entries, &self.sort_order);
entries
}
}
@ -167,15 +169,17 @@ struct RunProvider {
items: Option<Vec<MenuItem<i32>>>,
cache_path: Option<PathBuf>,
cache: HashMap<String, i64>,
sort_order: SortOrder,
}
impl RunProvider {
fn new() -> Self {
fn new(sort_order: SortOrder) -> Self {
let (cache_path, d_run_cache) = load_run_cache();
RunProvider {
items: None,
cache_path,
cache: d_run_cache,
sort_order,
}
}
@ -226,7 +230,7 @@ impl RunProvider {
})
.collect();
gui::sort_menu_items_alphabetically_honor_initial_score(&mut entries);
gui::apply_sort(&mut entries, &self.sort_order);
entries
}
}
@ -248,13 +252,15 @@ impl<T: Clone + Send + Sync> ItemProvider<T> for DRunProvider<T> {
struct FileItemProvider<T: Clone> {
last_result: Option<Vec<MenuItem<T>>>,
menu_item_data: T,
sort_order: SortOrder,
}
impl<T: Clone> FileItemProvider<T> {
fn new(menu_item_data: T) -> Self {
fn new(menu_item_data: T, sort_order: SortOrder) -> Self {
FileItemProvider {
last_result: None,
menu_item_data,
sort_order,
}
}
@ -409,7 +415,7 @@ impl<T: Clone> ItemProvider<T> for FileItemProvider<T> {
});
}
gui::sort_menu_items_alphabetically_honor_initial_score(&mut items);
gui::apply_sort(&mut items, &self.sort_order);
self.last_result = Some(items.clone());
(true, items)
@ -426,9 +432,9 @@ struct SshProvider<T: Clone> {
}
impl<T: Clone> SshProvider<T> {
fn new(menu_item_data: T) -> Self {
fn new(menu_item_data: T, order: SortOrder) -> Self {
let re = Regex::new(r"(?m)^\s*Host\s+(.+)$").unwrap();
let items: Vec<_> = dirs::home_dir()
let mut items: Vec<_> = dirs::home_dir()
.map(|home| home.join(".ssh").join("config"))
.filter(|path| path.exists())
.map(|path| fs::read_to_string(&path).unwrap_or_default())
@ -456,6 +462,7 @@ impl<T: Clone> SshProvider<T> {
})
.collect();
gui::apply_sort(&mut items, &order);
Self { elements: items }
}
}
@ -540,7 +547,7 @@ struct EmojiProvider<T: Clone> {
}
impl<T: Clone> EmojiProvider<T> {
fn new(data: T) -> Self {
fn new(data: T, sort_order: SortOrder) -> Self {
let emoji = emoji::search::search_annotation_all("");
let mut menus = emoji
.into_iter()
@ -556,7 +563,7 @@ impl<T: Clone> EmojiProvider<T> {
)
})
.collect::<Vec<_>>();
gui::sort_menu_items_alphabetically_honor_initial_score(&mut menus);
gui::apply_sort(&mut menus, &sort_order);
Self {
elements: menus,
@ -581,18 +588,20 @@ struct DMenuProvider {
}
impl DMenuProvider {
fn new() -> Result<DMenuProvider, Error> {
fn new(sort_order: SortOrder) -> Result<DMenuProvider, Error> {
let mut input = String::new();
io::stdin()
.read_to_string(&mut input)
.map_err(|_| Error::StdInReadFail)?;
let items: Vec<MenuItem<String>> = input
let mut items: Vec<MenuItem<String>> = input
.lines()
.map(String::from)
.map(|s| MenuItem::new(s.clone(), None, None, vec![], None, 0.0, None))
.collect();
gui::apply_sort(&mut items, &sort_order);
Ok(Self { items })
}
}
@ -630,11 +639,11 @@ struct AutoItemProvider {
impl AutoItemProvider {
fn new(config: &Config) -> Self {
AutoItemProvider {
drun: DRunProvider::new(AutoRunType::DRun, config.no_actions()),
file: FileItemProvider::new(AutoRunType::File),
drun: DRunProvider::new(AutoRunType::DRun, config.no_actions(), config.sort_order()),
file: FileItemProvider::new(AutoRunType::File, config.sort_order()),
math: MathProvider::new(AutoRunType::Math),
ssh: SshProvider::new(AutoRunType::Ssh),
emoji: EmojiProvider::new(AutoRunType::Emoji),
ssh: SshProvider::new(AutoRunType::Ssh, config.sort_order()),
emoji: EmojiProvider::new(AutoRunType::Emoji, config.sort_order()),
last_mode: None,
}
}
@ -698,7 +707,7 @@ impl ItemProvider<AutoRunType> for AutoItemProvider {
///
/// Will return `Err` if it was not able to spawn the process
pub fn d_run(config: &Config) -> Result<(), Error> {
let provider = DRunProvider::new(0, config.no_actions());
let provider = DRunProvider::new(0, config.no_actions(), config.sort_order());
let cache_path = provider.cache_path.clone();
let mut cache = provider.cache.clone();
@ -719,7 +728,7 @@ pub fn d_run(config: &Config) -> Result<(), Error> {
///
/// Will return `Err` if it was not able to spawn the process
pub fn run(config: &Config) -> Result<(), Error> {
let provider = RunProvider::new();
let provider = RunProvider::new(config.sort_order());
let cache_path = provider.cache_path.clone();
let mut cache = provider.cache.clone();
@ -812,7 +821,7 @@ pub fn auto(config: &Config) -> Result<(), Error> {
/// # Panics
/// In case an internal regex does not parse anymore, this should never happen
pub fn file(config: &Config) -> Result<(), Error> {
let provider = FileItemProvider::new(0);
let provider = FileItemProvider::new(0, config.sort_order());
// todo ues a arc instead of cloning the config
let selection_result = gui::show(
@ -856,7 +865,7 @@ fn ssh_launch<T: Clone>(menu_item: &MenuItem<T>, config: &Config) -> Result<(),
/// * if it was not able to spawn the process
/// * if it didn't find a terminal
pub fn ssh(config: &Config) -> Result<(), Error> {
let provider = SshProvider::new(0);
let provider = SshProvider::new(0,config.sort_order() );
let selection_result = gui::show(config.clone(), provider, true, None);
if let Ok(mi) = selection_result {
ssh_launch(&mi, config)?;
@ -887,7 +896,7 @@ pub fn math(config: &Config) {
///
/// Forwards errors from the gui. See `gui::show` for details.
pub fn emoji(config: &Config) -> Result<(), Error> {
let provider = EmojiProvider::new(0);
let provider = EmojiProvider::new(0, config.sort_order());
let selection_result = gui::show(config.clone(), provider, true, None)?;
match selection_result.action {
None => Err(Error::MissingAction),
@ -900,7 +909,7 @@ pub fn emoji(config: &Config) -> Result<(), Error> {
///
/// Forwards errors from the gui. See `gui::show` for details.
pub fn dmenu(config: &Config) -> Result<(), Error> {
let provider = DMenuProvider::new()?;
let provider = DMenuProvider::new(config.sort_order())?;
let selection_result = gui::show(config.clone(), provider, true, None);
match selection_result {