freedesktop_desktop_entry/
matching.rs

1// Copyright 2021 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use crate::DesktopEntry;
5
6impl DesktopEntry {
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        &self,
11        query: Q,
12        locales: &[L],
13        additional_haystack_values: &[&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.group("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.0.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.
105pub fn find_entry_from_appid<'a, I>(entries: I, appid: &str) -> Option<&'a DesktopEntry>
106where
107    I: IntoIterator<Item = &'a DesktopEntry>,
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}