Skip to content

Commit ee9a759

Browse files
authored
feat: readd entry matching
1 parent c509c74 commit ee9a759

File tree

4 files changed

+138
-2
lines changed

4 files changed

+138
-2
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ categories = [ "os::unix-apis" ]
1111
keywords = [ "freedesktop", "desktop", "entry" ]
1212

1313
[dependencies]
14+
dirs = "5.0.1"
1415
gettext-rs = { version = "0.7", features = ["gettext-system"]}
1516
memchr = "2"
17+
textdistance = "1.0.2"
18+
strsim = "0.11.1"
1619
thiserror = "1"
1720
xdg = "2.4.0"
21+
log = "0.4.21"

src/decoder.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@ impl<'a> DesktopEntry<'a> {
7171
}
7272

7373
/// Return an owned [`DesktopEntry`]
74-
pub fn from_path<L>(path: PathBuf, locales_filter: Option<&[L]>) -> Result<DesktopEntry<'static>, DecodeError>
74+
pub fn from_path<L>(
75+
path: PathBuf,
76+
locales_filter: Option<&[L]>,
77+
) -> Result<DesktopEntry<'static>, DecodeError>
7578
where
7679
L: AsRef<str>,
7780
{
@@ -125,7 +128,11 @@ fn process_line<'buf, 'local_ref, 'res: 'local_ref + 'buf, F, L>(
125128
let locale = &key[start + 1..key.len() - 1];
126129

127130
match locales_filter {
128-
Some(locales_filter) if !locales_filter.iter().any(|l| l.as_ref() == locale) => return,
131+
Some(locales_filter)
132+
if !locales_filter.iter().any(|l| l.as_ref() == locale) =>
133+
{
134+
return
135+
}
129136
_ => (),
130137
}
131138

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
mod decoder;
55
mod iter;
66

7+
pub mod matching;
78
pub use decoder::DecodeError;
89

910
pub use self::iter::Iter;

src/matching.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright 2021 System76 <[email protected]>
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
use crate::DesktopEntry;
5+
6+
impl<'a> DesktopEntry<'a> {
7+
/// The returned value is between 0.0 and 1.0 (higher value means more similar).
8+
/// You can use the `additional_haystack_values` parameter to add relevant string that are not part of the desktop entry.
9+
pub fn match_query<Q, L>(
10+
&'a self,
11+
query: Q,
12+
locales: &'a [L],
13+
additional_haystack_values: &'a [&'a str],
14+
) -> f64
15+
where
16+
Q: AsRef<str>,
17+
L: AsRef<str>,
18+
{
19+
#[inline]
20+
fn add_value(v: &mut Vec<String>, value: &str, is_multiple: bool) {
21+
if is_multiple {
22+
value.split(';').for_each(|e| v.push(e.to_lowercase()));
23+
} else {
24+
v.push(value.to_lowercase());
25+
}
26+
}
27+
28+
// (field name, is separated by ";")
29+
let fields = [
30+
("Name", false),
31+
("GenericName", false),
32+
("Comment", false),
33+
("Categories", true),
34+
("Keywords", true),
35+
];
36+
37+
let mut normalized_values: Vec<String> = Vec::new();
38+
39+
normalized_values.extend(
40+
additional_haystack_values
41+
.iter()
42+
.map(|val| val.to_lowercase()),
43+
);
44+
45+
let desktop_entry_group = self.groups.get("Desktop Entry");
46+
47+
for field in fields {
48+
if let Some(group) = desktop_entry_group {
49+
if let Some((default_value, locale_map)) = group.get(field.0) {
50+
add_value(&mut normalized_values, default_value, field.1);
51+
52+
let mut at_least_one_locale = false;
53+
54+
for locale in locales {
55+
match locale_map.get(locale.as_ref()) {
56+
Some(value) => {
57+
add_value(&mut normalized_values, value, field.1);
58+
at_least_one_locale = true;
59+
}
60+
None => {
61+
if let Some(pos) = locale.as_ref().find('_') {
62+
if let Some(value) = locale_map.get(&locale.as_ref()[..pos]) {
63+
add_value(&mut normalized_values, value, field.1);
64+
at_least_one_locale = true;
65+
}
66+
}
67+
}
68+
}
69+
}
70+
71+
if !at_least_one_locale {
72+
if let Some(domain) = &self.ubuntu_gettext_domain {
73+
let gettext_value = crate::dgettext(domain, default_value);
74+
if !gettext_value.is_empty() {
75+
add_value(&mut normalized_values, &gettext_value, false);
76+
}
77+
}
78+
}
79+
}
80+
}
81+
}
82+
83+
let query = query.as_ref().to_lowercase();
84+
85+
let query_espaced = query.split_ascii_whitespace().collect::<Vec<_>>();
86+
87+
normalized_values
88+
.into_iter()
89+
.map(|de_field| {
90+
let jaro_score = strsim::jaro_winkler(&query, &de_field);
91+
92+
if query_espaced.iter().any(|query| de_field.contains(*query)) {
93+
// provide a bonus if the query is contained in the de field
94+
(jaro_score + 0.1).clamp(0.61, 1.)
95+
} else {
96+
jaro_score
97+
}
98+
})
99+
.max_by(|e1, e2| e1.total_cmp(e2))
100+
.unwrap_or(0.0)
101+
}
102+
}
103+
104+
/// Return the corresponding [`DesktopEntry`] that match the given appid.
105+
pub fn find_entry_from_appid<'a, I>(entries: I, appid: &str) -> Option<&'a DesktopEntry<'a>>
106+
where
107+
I: Iterator<Item = &'a DesktopEntry<'a>>,
108+
{
109+
let normalized_appid = appid.to_lowercase();
110+
111+
entries.into_iter().find(|e| {
112+
if e.appid.to_lowercase() == normalized_appid {
113+
return true;
114+
}
115+
116+
if let Some(field) = e.startup_wm_class() {
117+
if field.to_lowercase() == normalized_appid {
118+
return true;
119+
}
120+
}
121+
122+
false
123+
})
124+
}

0 commit comments

Comments
 (0)