aboutsummaryrefslogtreecommitdiffstats
path: root/src/command/client/search/skim_impl.rs
blob: 416d0e46f86a0e17d7a05f4a665a708f51c1bff5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
use std::sync::Arc;

use atuin_client::settings::FilterMode;
use chrono::Utc;
use skim::{prelude::ExactOrFuzzyEngineFactory, MatchEngineFactory};
use tokio::task::yield_now;

use super::interactive::{HistoryWrapper, SearchState};

pub async fn fuzzy_search(
    state: &SearchState,
    all_history: &[Arc<HistoryWrapper>],
) -> Vec<Arc<HistoryWrapper>> {
    let mut set = Vec::with_capacity(200);
    let mut ranks = Vec::with_capacity(200);
    let engine = ExactOrFuzzyEngineFactory::builder().fuzzy_algorithm(skim::FuzzyAlgorithm::SkimV2);
    let query = state.input.as_str();
    let engine = engine.create_engine(query);
    let now = Utc::now();

    for (i, item) in all_history.iter().enumerate() {
        if i % 256 == 0 {
            yield_now().await;
        }
        match state.filter_mode {
            FilterMode::Global => {}
            FilterMode::Host if item.hostname == state.context.hostname => {}
            FilterMode::Session if item.session == state.context.session => {}
            FilterMode::Directory if item.cwd == state.context.cwd => {}
            _ => continue,
        }
        #[allow(clippy::cast_lossless, clippy::cast_precision_loss)]
        if let Some(res) = engine.match_item(item.clone()) {
            let [score, begin, _, _] = res.rank;

            let mut duration = ((now - item.timestamp).num_seconds() as f64).log2();
            if !duration.is_finite() || duration <= 1.0 {
                duration = 1.0;
            }
            let count = (item.count as f64 + 16.0).log2();
            let begin = (begin as f64 + 16.0).log2();

            // reduce longer durations, raise higher counts, raise matches close to the start
            let score = (score as f64) * count / duration / begin;

            'insert: {
                // algorithm:
                // 1. find either the position that this command ranks
                // 2. find the same command positioned better than our rank.
                for i in 0..set.len() {
                    // do we out score the corrent position?
                    if ranks[i] > score {
                        ranks.insert(i, score);
                        set.insert(i, item.clone());
                        let mut j = i + 1;
                        while j < set.len() {
                            // remove duplicates that have a worse score
                            if set[j].command == item.command {
                                ranks.remove(j);
                                set.remove(j);

                                // break this while loop because there won't be any other
                                // duplicates.
                                break;
                            }
                            j += 1;
                        }

                        // keep it limited
                        if ranks.len() > 200 {
                            ranks.pop();
                            set.pop();
                        }

                        break 'insert;
                    }
                    // don't continue if this command has a better score already
                    if set[i].command == item.command {
                        break 'insert;
                    }
                }

                if set.len() < 200 {
                    ranks.push(score);
                    set.push(item.clone());
                }
            }
        }
    }

    set
}