summaryrefslogtreecommitdiff
path: root/mut/neovim/pack/plugins/start/blink.cmp/lua/blink/cmp/fuzzy/frecency.rs
blob: a6725983bc65b5ddade88183b1fda72bedc097f1 (plain)
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use crate::lsp_item::LspItem;
use heed::{types::*, EnvFlags};
use heed::{Database, Env, EnvOpenOptions};
use mlua::Result as LuaResult;
use serde::{Deserialize, Serialize};
use std::fs;
use std::time::{SystemTime, UNIX_EPOCH};

#[derive(Clone, Serialize, Deserialize)]
struct CompletionItemKey {
    label: String,
    kind: u32,
    source_id: String,
}

impl From<&LspItem> for CompletionItemKey {
    fn from(item: &LspItem) -> Self {
        Self {
            label: item.label.clone(),
            kind: item.kind,
            source_id: item.source_id.clone(),
        }
    }
}

#[derive(Debug)]
pub struct FrecencyTracker {
    env: Env,
    db: Database<SerdeBincode<CompletionItemKey>, SerdeBincode<Vec<u64>>>,
    access_thresholds: Vec<(f64, u64)>,
}

impl FrecencyTracker {
    pub fn new(db_path: &str, use_unsafe_no_lock: bool) -> LuaResult<Self> {
        fs::create_dir_all(db_path).map_err(|err| {
            mlua::Error::RuntimeError(
                "Failed to create frecency database directory: ".to_string() + &err.to_string(),
            )
        })?;
        let env = unsafe {
            let mut opts = EnvOpenOptions::new();
            if use_unsafe_no_lock {
                opts.flags(EnvFlags::NO_LOCK | EnvFlags::NO_SYNC | EnvFlags::NO_META_SYNC);
            }
            opts.open(db_path).map_err(|err| {
                mlua::Error::RuntimeError(
                    "Failed to open frecency database: ".to_string() + &err.to_string(),
                )
            })?
        };
        env.clear_stale_readers().map_err(|err| {
            mlua::Error::RuntimeError(
                "Failed to clear stale readers for frecency database: ".to_string()
                    + &err.to_string(),
            )
        })?;

        // we will open the default unnamed database
        let mut wtxn = env.write_txn().map_err(|err| {
            mlua::Error::RuntimeError(
                "Failed to open write transaction for frecency database: ".to_string()
                    + &err.to_string(),
            )
        })?;
        let db = env.create_database(&mut wtxn, None).map_err(|err| {
            mlua::Error::RuntimeError(
                "Failed to create frecency database: ".to_string() + &err.to_string(),
            )
        })?;

        let access_thresholds = [
            (1., 1000 * 60 * 2),             // 2 minutes
            (0.2, 1000 * 60 * 60),           // 1 hour
            (0.1, 1000 * 60 * 60 * 24),      // 1 day
            (0.05, 1000 * 60 * 60 * 24 * 7), // 1 week
        ]
        .to_vec();

        Ok(FrecencyTracker {
            env: env.clone(),
            db,
            access_thresholds,
        })
    }

    fn get_accesses(&self, item: &LspItem) -> LuaResult<Option<Vec<u64>>> {
        let rtxn = self.env.read_txn().map_err(|err| {
            mlua::Error::RuntimeError(
                "Failed to start read transaction for frecency database: ".to_string()
                    + &err.to_string(),
            )
        })?;
        self.db
            .get(&rtxn, &CompletionItemKey::from(item))
            .map_err(|err| {
                mlua::Error::RuntimeError(
                    "Failed to read from frecency database: ".to_string() + &err.to_string(),
                )
            })
    }

    fn get_now(&self) -> u64 {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs()
    }

    pub fn access(&mut self, item: &LspItem) -> LuaResult<()> {
        let mut wtxn = self.env.write_txn().map_err(|err| {
            mlua::Error::RuntimeError(
                "Failed to start write transaction for frecency database: ".to_string()
                    + &err.to_string(),
            )
        })?;

        let mut accesses = self.get_accesses(item)?.unwrap_or_default();
        accesses.push(self.get_now());

        self.db
            .put(&mut wtxn, &CompletionItemKey::from(item), &accesses)
            .map_err(|err| {
                mlua::Error::RuntimeError(
                    "Failed to write to frecency database: ".to_string() + &err.to_string(),
                )
            })?;

        wtxn.commit().map_err(|err| {
            mlua::Error::RuntimeError(
                "Failed to commit write transaction for frecency database: ".to_string()
                    + &err.to_string(),
            )
        })?;

        Ok(())
    }

    pub fn get_score(&self, item: &LspItem) -> i64 {
        let accesses = self.get_accesses(item).unwrap_or(None).unwrap_or_default();
        let now = self.get_now();
        let mut score = 0.0;
        'outer: for access in &accesses {
            let duration_since = now - access;
            for (rank, threshold_duration_since) in &self.access_thresholds {
                if duration_since < *threshold_duration_since {
                    score += rank;
                    continue 'outer;
                }
            }
        }
        score.min(4.) as i64
    }
}