atuin_history/
sort.rs

1use atuin_client::history::History;
2
3type ScoredHistory = (f64, History);
4
5// Fuzzy search already comes sorted by minspan
6// This sorting should be applicable to all search modes, and solve the more "obvious" issues
7// first.
8// Later on, we can pass in context and do some boosts there too.
9pub fn sort(query: &str, input: Vec<History>) -> Vec<History> {
10    // This can totally be extended. We need to be _careful_ that it's not slow.
11    // We also need to balance sorting db-side with sorting here. SQLite can do a lot,
12    // but some things are just much easier/more doable in Rust.
13
14    let mut scored = input
15        .into_iter()
16        .map(|h| {
17            // If history is _prefixed_ with the query, score it more highly
18            let score = if h.command.starts_with(query) {
19                2.0
20            } else if h.command.contains(query) {
21                1.75
22            } else {
23                1.0
24            };
25
26            // calculate how long ago the history was, in seconds
27            let now = time::OffsetDateTime::now_utc().unix_timestamp();
28            let time = h.timestamp.unix_timestamp();
29            let diff = std::cmp::max(1, now - time); // no /0 please
30
31            // prefer newer history, but not hugely so as to offset the other scoring
32            // the numbers will get super small over time, but I don't want time to overpower other
33            // scoring
34            #[allow(clippy::cast_precision_loss)]
35            let time_score = 1.0 + (1.0 / diff as f64);
36            let score = score * time_score;
37
38            (score, h)
39        })
40        .collect::<Vec<ScoredHistory>>();
41
42    scored.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap().reverse());
43
44    // Remove the scores and return the history
45    scored.into_iter().map(|(_, h)| h).collect::<Vec<History>>()
46}