Skip to content

Commit 9d47acc

Browse files
committed
Added fuzzy find
1 parent dc7c18e commit 9d47acc

5 files changed

Lines changed: 65 additions & 24 deletions

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ edition = "2024"
77
freedesktop-desktop-entry = { version = "0.7.13", default-features = false }
88
freedesktop-icons = "0.4.0"
99
gpui = { git = "https://github.com/zed-industries/zed", default-features = false, features = ["wayland" ] }
10+
nucleo-matcher = "0.3.1"
1011
rust-embed = "8.7.2"
1112
serde = { version = "1.0.219", features = ["derive"] }
1213
smol = "2.0.2"

src/config.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::path::PathBuf;
44
use gpui::{Global, Rgba, SharedString, rgb};
55
use serde::Deserialize;
66

7-
#[derive(Deserialize)]
7+
#[derive(Default, Deserialize)]
88
#[serde(default)]
99
pub struct Config {
1010
pub terminal: Option<String>,
@@ -26,15 +26,6 @@ pub struct ThemeConfig {
2626
pub accent_foreground: Rgba,
2727
}
2828

29-
impl Default for Config {
30-
fn default() -> Self {
31-
Self {
32-
terminal: None,
33-
theme: ThemeConfig::default(),
34-
}
35-
}
36-
}
37-
3829
impl Default for ThemeConfig {
3930
fn default() -> Self {
4031
Self {

src/entries/application.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::sync::LazyLock;
88
use freedesktop_desktop_entry::{Iter, default_paths, get_languages_from_env};
99
use freedesktop_icons::lookup;
1010
use gpui::{Resource, SharedString};
11+
use nucleo_matcher::Utf32String;
1112

1213
use crate::config::Config;
1314

@@ -17,6 +18,7 @@ pub struct Application {
1718
pub name: SharedString,
1819
pub description: Option<SharedString>,
1920
pub icon: Option<Resource>,
21+
pub searchable: Utf32String,
2022
exec: Vec<String>,
2123
working_dir: Option<PathBuf>,
2224
open_in_terminal: bool,
@@ -80,14 +82,25 @@ pub fn load_applications() -> Vec<Application> {
8082
return None;
8183
}
8284

85+
let name = SharedString::from(entry.name(&locales)?.into_owned());
86+
let description = entry
87+
.comment(&locales)
88+
.map(|c| SharedString::from(c.into_owned()));
89+
let icon = entry
90+
.icon()
91+
.and_then(|i| lookup(i).with_cache().with_size(28).find())
92+
.map(|i| i.into());
93+
let searchable = Utf32String::from(match description {
94+
Some(ref d) => name.to_string() + " " + d.as_str(),
95+
None => name.to_string(),
96+
});
97+
8398
Some(Application {
8499
id: entry.id().to_string(),
85-
name: entry.name(&locales).map(|c| c.into_owned().into())?,
86-
description: entry.comment(&locales).map(|c| c.into_owned().into()),
87-
icon: entry
88-
.icon()
89-
.and_then(|i| lookup(i).with_cache().with_size(28).find())
90-
.map(|i| i.into()),
100+
name,
101+
description,
102+
icon,
103+
searchable,
91104
exec: entry.parse_exec_with_uris(&[], &locales).ok()?,
92105
working_dir: entry.path().and_then(|entry| entry.parse().ok()),
93106
open_in_terminal: entry.terminal(),
@@ -109,7 +122,7 @@ static TERMINAL: LazyLock<&str> = LazyLock::new(|| {
109122
"konsole",
110123
]
111124
.into_iter()
112-
.find(|term| paths.iter().any(|dir| dir.join(&term).is_file()))
125+
.find(|term| paths.iter().any(|dir| dir.join(term).is_file()))
113126
.expect(
114127
"Failed to find a terminal emulator in your PATH. Please use the config to specify one.",
115128
)

src/entries/mod.rs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
use std::cell::RefCell;
12
use std::cmp::Reverse;
23
use std::ops::Deref;
34
use std::rc::Rc;
45

56
use gpui::{App, Global, Resource, SharedString};
7+
use nucleo_matcher::pattern::{AtomKind, CaseMatching, Normalization, Pattern};
8+
use nucleo_matcher::{Matcher, Utf32Str};
69

710
use crate::config::Config;
811
use crate::entries::application::Application;
@@ -41,6 +44,12 @@ impl Entry {
4144
}
4245
}
4346

47+
pub fn searchable(&self) -> Utf32Str<'_> {
48+
match self {
49+
Entry::Application(entry) => entry.searchable.slice(..),
50+
}
51+
}
52+
4453
pub fn open(&self, cx: &mut App) -> bool {
4554
cx.global_mut::<SearchEntries>()
4655
.increment_frequency(self.id());
@@ -55,6 +64,7 @@ impl Entry {
5564
pub struct SearchEntries {
5665
entries: Vec<Entry>,
5766
frequencies: Frequencies,
67+
matcher: RefCell<Matcher>,
5868
}
5969

6070
impl SearchEntries {
@@ -68,6 +78,7 @@ impl SearchEntries {
6878
Self {
6979
entries,
7080
frequencies: Frequencies::load(),
81+
matcher: RefCell::new(Matcher::default()),
7182
}
7283
}
7384

@@ -82,18 +93,32 @@ impl SearchEntries {
8293
}
8394

8495
pub fn filtered(&self, search_term: &str) -> Vec<Entry> {
85-
if search_term.trim().is_empty() {
96+
let search_term = search_term.trim();
97+
if search_term.is_empty() {
8698
return self.entries.clone();
8799
}
88100

89-
self.entries
101+
let p = Pattern::new(
102+
search_term,
103+
CaseMatching::Ignore,
104+
Normalization::Smart,
105+
AtomKind::Fuzzy,
106+
);
107+
108+
let mut matcher = self.matcher.borrow_mut();
109+
let mut result: Vec<_> = self
110+
.entries
90111
.iter()
91-
.filter(|entry| {
92-
entry.name().to_lowercase().contains(search_term)
93-
|| entry
94-
.description()
95-
.is_some_and(|desc| desc.to_lowercase().contains(search_term))
112+
.filter_map(|item| {
113+
p.score(item.searchable(), &mut matcher)
114+
.map(|score| (item, score))
96115
})
116+
.collect();
117+
result.sort_by_key(|(_, score)| Reverse(*score));
118+
119+
result
120+
.into_iter()
121+
.map(|(entry, _)| entry)
97122
.cloned()
98123
.collect()
99124
}

0 commit comments

Comments
 (0)