Skip to content

Commit 119c766

Browse files
committed
Added really basic favorites
1 parent 9d47acc commit 119c766

10 files changed

Lines changed: 549 additions & 297 deletions

File tree

Cargo.lock

Lines changed: 261 additions & 162 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ version = "0.1.0"
44
edition = "2024"
55

66
[dependencies]
7+
dirs = "6.0.0"
78
freedesktop-desktop-entry = { version = "0.7.13", default-features = false }
89
freedesktop-icons = "0.4.0"
9-
gpui = { git = "https://github.com/zed-industries/zed", default-features = false, features = ["wayland" ] }
10+
gpui = { git = "https://github.com/zed-industries/zed", default-features = false, features = ["wayland"] }
1011
nucleo-matcher = "0.3.1"
1112
rust-embed = "8.7.2"
1213
serde = { version = "1.0.219", features = ["derive"] }

src/config.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use std::env;
21
use std::path::PathBuf;
2+
use std::sync::LazyLock;
33

44
use gpui::{Global, Rgba, SharedString, rgb};
55
use serde::Deserialize;
@@ -46,16 +46,15 @@ impl Global for Config {}
4646

4747
impl Config {
4848
pub fn load() -> Self {
49-
match std::fs::read_to_string(Self::get_path()) {
49+
match std::fs::read_to_string(&*CONFIG_SAVE_PATH) {
5050
Ok(file) => toml::from_str(&file).expect("Failed to parse the config file"),
5151
Err(_) => Self::default(),
5252
}
5353
}
54-
55-
fn get_path() -> PathBuf {
56-
match env::var("XDG_CONFIG_DIR") {
57-
Ok(config) => PathBuf::from(config).join("waystart.toml"),
58-
Err(_) => env::home_dir().unwrap().join(".config/waystart.toml"),
59-
}
60-
}
6154
}
55+
56+
static CONFIG_SAVE_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
57+
dirs::data_dir()
58+
.expect("Failed to get config directory")
59+
.join("waystart.toml")
60+
});

src/entries/application.rs

Lines changed: 29 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
use std::collections::HashSet;
21
use std::env;
3-
use std::hash::{Hash, Hasher};
42
use std::path::PathBuf;
53
use std::process::{Command, Stdio};
64
use std::sync::LazyLock;
@@ -14,7 +12,7 @@ use crate::config::Config;
1412

1513
#[derive(Debug)]
1614
pub struct Application {
17-
pub id: String,
15+
pub id: SharedString,
1816
pub name: SharedString,
1917
pub description: Option<SharedString>,
2018
pub icon: Option<Resource>,
@@ -56,59 +54,54 @@ impl Application {
5654
}
5755
}
5856
}
59-
}
60-
61-
impl Hash for Application {
62-
fn hash<H: Hasher>(&self, state: &mut H) {
63-
self.id.hash(state);
64-
}
65-
}
66-
67-
impl PartialEq for Application {
68-
fn eq(&self, other: &Self) -> bool {
69-
self.id == other.id
70-
}
71-
}
72-
73-
impl Eq for Application {}
7457

75-
pub fn load_applications() -> Vec<Application> {
76-
let locales = get_languages_from_env();
58+
pub fn load() -> Vec<Self> {
59+
let locales = get_languages_from_env();
7760

78-
Iter::new(default_paths())
79-
.entries(Some(&locales))
80-
.filter_map(|entry| {
61+
let mut applications = Vec::new();
62+
for entry in Iter::new(default_paths()).entries(Some(&locales)) {
8163
if entry.no_display() || entry.hidden() {
82-
return None;
64+
continue;
8365
}
8466

85-
let name = SharedString::from(entry.name(&locales)?.into_owned());
67+
let id = SharedString::from(entry.id().to_string());
68+
if applications.iter().any(|a: &Application| a.id == id) {
69+
continue;
70+
}
71+
72+
let Ok(exec) = entry.parse_exec_with_uris(&[], &locales) else {
73+
continue;
74+
};
75+
let name = match entry.name(&locales) {
76+
Some(name) => SharedString::from(name.into_owned()),
77+
None => continue,
78+
};
8679
let description = entry
8780
.comment(&locales)
88-
.map(|c| SharedString::from(c.into_owned()));
81+
.map(|description| SharedString::from(description.into_owned()));
8982
let icon = entry
9083
.icon()
91-
.and_then(|i| lookup(i).with_cache().with_size(28).find())
92-
.map(|i| i.into());
84+
.and_then(|icon| lookup(icon).with_cache().with_size(28).find())
85+
.map(|path| Resource::Path(path.into()));
9386
let searchable = Utf32String::from(match description {
9487
Some(ref d) => name.to_string() + " " + d.as_str(),
9588
None => name.to_string(),
9689
});
9790

98-
Some(Application {
99-
id: entry.id().to_string(),
91+
applications.push(Application {
92+
id,
10093
name,
10194
description,
10295
icon,
10396
searchable,
104-
exec: entry.parse_exec_with_uris(&[], &locales).ok()?,
97+
exec,
10598
working_dir: entry.path().and_then(|entry| entry.parse().ok()),
10699
open_in_terminal: entry.terminal(),
107-
})
108-
})
109-
.collect::<HashSet<Application>>()
110-
.into_iter()
111-
.collect()
100+
});
101+
}
102+
103+
applications
104+
}
112105
}
113106

114107
static TERMINAL: LazyLock<&str> = LazyLock::new(|| {

src/entries/favorites.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use std::collections::HashSet;
2+
use std::ops::{Deref, DerefMut};
3+
use std::path::PathBuf;
4+
use std::sync::LazyLock;
5+
6+
use gpui::SharedString;
7+
use serde::{Deserialize, Serialize};
8+
9+
#[derive(Serialize, Deserialize)]
10+
pub(super) struct Favorites {
11+
favorites: HashSet<SharedString>,
12+
}
13+
14+
impl Favorites {
15+
pub fn load() -> Self {
16+
match std::fs::read_to_string(&*FAVORITES_SAVE_PATH) {
17+
Ok(file) => toml::from_str(&file).expect("Failed to parse favorites"),
18+
Err(_) => Self {
19+
favorites: HashSet::new(),
20+
},
21+
}
22+
}
23+
24+
pub async fn save(&self) {
25+
let content = toml::to_string(&self).expect("Failed to serialize favorites");
26+
if let Err(err) = smol::fs::write(&*FAVORITES_SAVE_PATH, content).await {
27+
eprintln!(
28+
"Failed to save favorites at {}: {}",
29+
FAVORITES_SAVE_PATH.to_string_lossy(),
30+
err
31+
);
32+
}
33+
}
34+
}
35+
36+
impl Deref for Favorites {
37+
type Target = HashSet<SharedString>;
38+
39+
fn deref(&self) -> &Self::Target {
40+
&self.favorites
41+
}
42+
}
43+
44+
impl DerefMut for Favorites {
45+
fn deref_mut(&mut self) -> &mut Self::Target {
46+
&mut self.favorites
47+
}
48+
}
49+
50+
static FAVORITES_SAVE_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
51+
dirs::data_dir()
52+
.expect("Failed to get data directory")
53+
.join("waystart.toml")
54+
});

src/entries/frequency.rs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,37 @@
11
use std::cmp::Ordering;
22
use std::collections::HashMap;
3-
use std::env;
43
use std::ops::{Deref, DerefMut};
54
use std::path::PathBuf;
5+
use std::sync::LazyLock;
66
use std::time::SystemTime;
77

8+
use gpui::SharedString;
89
use serde::{Deserialize, Serialize};
910

10-
pub(super) struct Frequencies(HashMap<String, EntryFrequency>);
11+
pub(super) struct Frequencies(HashMap<SharedString, EntryFrequency>);
1112

1213
impl Frequencies {
1314
pub fn load() -> Self {
14-
Self(match std::fs::read_to_string(Self::get_path()) {
15+
Self(match std::fs::read_to_string(&*FREQUENCIES_SAVE_PATH) {
1516
Ok(file) => toml::from_str(&file).expect("Failed to parse frequency history"),
1617
Err(_) => HashMap::new(),
1718
})
1819
}
1920

2021
pub async fn save(&self) {
2122
let content = toml::to_string(&self.0).expect("Failed to serialize frequency history");
22-
let path = Self::get_path();
23-
if let Err(err) = smol::fs::write(&path, content).await {
23+
if let Err(err) = smol::fs::write(&*FREQUENCIES_SAVE_PATH, content).await {
2424
eprintln!(
2525
"Failed to save frequency history at {}: {}",
26-
path.to_string_lossy(),
26+
FREQUENCIES_SAVE_PATH.to_string_lossy(),
2727
err
2828
);
2929
}
3030
}
31-
32-
fn get_path() -> PathBuf {
33-
match env::var("XDG_CACHE_HOME") {
34-
Ok(config) => PathBuf::from(config).join("waystart.toml"),
35-
Err(_) => env::home_dir().unwrap().join(".cache/waystart.toml"),
36-
}
37-
}
3831
}
3932

4033
impl Deref for Frequencies {
41-
type Target = HashMap<String, EntryFrequency>;
34+
type Target = HashMap<SharedString, EntryFrequency>;
4235

4336
fn deref(&self) -> &Self::Target {
4437
&self.0
@@ -119,3 +112,9 @@ impl PartialEq for EntryFrequency {
119112
self.cmp(other).is_eq()
120113
}
121114
}
115+
116+
static FREQUENCIES_SAVE_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
117+
dirs::cache_dir()
118+
.expect("Failed to get cache directory")
119+
.join("waystart.toml")
120+
});

src/entries/mod.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ use nucleo_matcher::{Matcher, Utf32Str};
99

1010
use crate::config::Config;
1111
use crate::entries::application::Application;
12+
use crate::entries::favorites::Favorites;
1213
use crate::entries::frequency::{EntryFrequency, Frequencies};
1314

1415
mod application;
16+
mod favorites;
1517
mod frequency;
1618

1719
#[derive(Clone)]
@@ -20,7 +22,7 @@ pub enum Entry {
2022
}
2123

2224
impl Entry {
23-
pub fn id(&self) -> &str {
25+
pub fn id(&self) -> &SharedString {
2426
match self {
2527
Entry::Application(entry) => &entry.id,
2628
}
@@ -64,12 +66,13 @@ impl Entry {
6466
pub struct SearchEntries {
6567
entries: Vec<Entry>,
6668
frequencies: Frequencies,
69+
favorites: Favorites,
6770
matcher: RefCell<Matcher>,
6871
}
6972

7073
impl SearchEntries {
7174
pub fn load() -> Self {
72-
let entries = application::load_applications()
75+
let entries = Application::load()
7376
.into_iter()
7477
.map(Rc::new)
7578
.map(Entry::Application)
@@ -78,17 +81,24 @@ impl SearchEntries {
7881
Self {
7982
entries,
8083
frequencies: Frequencies::load(),
84+
favorites: Favorites::load(),
8185
matcher: RefCell::new(Matcher::default()),
8286
}
8387
}
8488

8589
pub async fn save(&self) {
8690
self.frequencies.save().await;
91+
self.favorites.save().await;
8792
}
8893

8994
pub fn sort_by_frequency(&mut self) {
9095
self.entries.sort_by_cached_key(|e| {
91-
Reverse(self.frequencies.get(e.id()).cloned().unwrap_or_default())
96+
Reverse(
97+
self.frequencies
98+
.get(e.id().as_str())
99+
.cloned()
100+
.unwrap_or_default(),
101+
)
92102
});
93103
}
94104

@@ -123,12 +133,24 @@ impl SearchEntries {
123133
.collect()
124134
}
125135

126-
pub fn increment_frequency(&mut self, entry_id: &str) {
136+
pub fn favorites(&self) -> Vec<Entry> {
137+
self.favorites
138+
.iter()
139+
.filter_map(|id| self.entries.iter().find(|entry| entry.id() == id))
140+
.cloned()
141+
.collect()
142+
}
143+
144+
pub fn add_favorite(&mut self, entry: &Entry) {
145+
self.favorites.insert(entry.id().clone());
146+
}
147+
148+
pub fn increment_frequency(&mut self, entry_id: &SharedString) {
127149
if let Some(frequency) = self.frequencies.get_mut(entry_id) {
128150
frequency.increment();
129151
} else {
130152
self.frequencies
131-
.insert(entry_id.to_string(), EntryFrequency::new());
153+
.insert(entry_id.clone(), EntryFrequency::new());
132154
}
133155
}
134156
}

0 commit comments

Comments
 (0)