Skip to content

Commit 5cc63f0

Browse files
committed
feat(fuzzy): lazy get lua properties
Significantly improver performance but much of the sorting and filtering logic should likely be moved to the lua side for better flexibility and performance
1 parent 7fea65c commit 5cc63f0

File tree

5 files changed

+138
-78
lines changed

5 files changed

+138
-78
lines changed

lua/blink/cmp/fuzzy/frecency.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::fuzzy::LspItem;
1+
use crate::lsp_item::LspItem;
22
use heed::types::*;
33
use heed::{Database, Env, EnvOpenOptions};
44
use mlua::Result as LuaResult;

lua/blink/cmp/fuzzy/fuzzy.rs

+20-64
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,15 @@
11
// TODO: refactor this heresy
22

33
use crate::frecency::FrecencyTracker;
4+
use crate::lsp_item::LspItem;
45
use mlua::prelude::*;
56
use mlua::FromLua;
67
use mlua::Lua;
7-
use serde::{Deserialize, Serialize};
8+
use std::cell::LazyCell;
89
use std::cmp::Reverse;
910
use std::collections::HashSet;
1011

11-
#[derive(Clone, Serialize, Deserialize, Debug)]
12-
pub struct LspItem {
13-
pub label: String,
14-
#[serde(rename = "sortText")]
15-
pub sort_text: Option<String>,
16-
#[serde(rename = "filterText")]
17-
pub filter_text: Option<String>,
18-
pub kind: u32,
19-
pub score_offset: Option<i32>,
20-
pub source_id: String,
21-
}
22-
23-
impl FromLua<'_> for LspItem {
24-
fn from_lua(value: LuaValue<'_>, _lua: &'_ Lua) -> LuaResult<Self> {
25-
if let Some(tab) = value.as_table() {
26-
let label: String = tab.get("label").unwrap_or_default();
27-
let sort_text: Option<String> = tab.get("sortText").ok();
28-
let filter_text: Option<String> = tab.get("filterText").ok();
29-
let kind: u32 = tab.get("kind").unwrap_or_default();
30-
let score_offset: Option<i32> = tab.get("score_offset").ok();
31-
let source_id: String = tab.get("source_id").unwrap_or_default();
32-
33-
Ok(LspItem {
34-
label,
35-
sort_text,
36-
filter_text,
37-
kind,
38-
score_offset,
39-
source_id,
40-
})
41-
} else {
42-
Err(mlua::Error::FromLuaConversionError {
43-
from: "LuaValue",
44-
to: "LspItem",
45-
message: None,
46-
})
47-
}
48-
}
49-
}
50-
51-
#[derive(Clone, Serialize, Deserialize)]
52-
pub struct MatchedLspItem {
53-
label: String,
54-
kind: u32,
55-
index: u32,
56-
score: i32,
57-
}
58-
59-
#[derive(Clone, Serialize, Deserialize, Hash)]
12+
#[derive(Clone, Hash)]
6013
pub struct FuzzyOptions {
6114
use_typo_resistance: bool,
6215
use_frecency: bool,
@@ -99,30 +52,33 @@ impl FromLua<'_> for FuzzyOptions {
9952

10053
pub fn fuzzy(
10154
needle: String,
102-
haystack: Vec<LspItem>,
55+
haystack_labels: Vec<String>,
56+
haystack: Vec<LuaTable>,
10357
frecency: &FrecencyTracker,
10458
opts: FuzzyOptions,
10559
) -> Vec<usize> {
60+
let haystack = haystack
61+
.into_iter()
62+
.map(|item| LazyCell::new(move || -> LspItem { item.into() }))
63+
.collect::<Vec<_>>();
64+
10665
let nearby_words: HashSet<String> = HashSet::from_iter(opts.nearby_words.unwrap_or_default());
10766

10867
// Fuzzy match with fzrs
109-
let haystack_labels = haystack
110-
.iter()
111-
.map(|s| {
112-
if let Some(filter_text) = &s.filter_text {
113-
filter_text.as_str()
114-
} else {
115-
s.label.as_str()
116-
}
117-
})
118-
.collect::<Vec<_>>();
11968
let options = frizbee::Options {
12069
prefilter: !opts.use_typo_resistance,
12170
min_score: opts.min_score,
12271
stable_sort: false,
12372
..Default::default()
12473
};
125-
let mut matches = frizbee::match_list(&needle, &haystack_labels, options);
74+
let mut matches = frizbee::match_list(
75+
&needle,
76+
&haystack_labels
77+
.iter()
78+
.map(|s| s.as_str())
79+
.collect::<Vec<_>>(),
80+
options,
81+
);
12682

12783
// Sort by scores
12884
let match_scores = matches
@@ -135,13 +91,13 @@ pub fn fuzzy(
13591
};
13692
let nearby_words_score = if opts.use_proximity {
13793
nearby_words
138-
.get(&haystack[mtch.index_in_haystack].label)
94+
.get(&haystack_labels[mtch.index_in_haystack])
13995
.map(|_| 2)
14096
.unwrap_or(0)
14197
} else {
14298
0
14399
};
144-
let score_offset = haystack[mtch.index_in_haystack].score_offset.unwrap_or(0);
100+
let score_offset = haystack[mtch.index_in_haystack].score_offset;
145101

146102
(mtch.score as i32) + frecency_score + nearby_words_score + score_offset
147103
})

lua/blink/cmp/fuzzy/init.lua

+8-5
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,15 @@ function fuzzy.filter_items(needle, haystack)
3535
local cursor_row = vim.api.nvim_win_get_cursor(0)[1]
3636
local start_row = math.max(0, cursor_row - 30)
3737
local end_row = math.min(cursor_row + 30, vim.api.nvim_buf_line_count(0))
38-
local nearby_words =
39-
fuzzy.rust.get_words(table.concat(vim.api.nvim_buf_get_lines(0, start_row, end_row, false), '\n'))
38+
local nearby_text = table.concat(vim.api.nvim_buf_get_lines(0, start_row, end_row, false), '\n')
39+
local nearby_words = #nearby_text < 10000 and fuzzy.rust.get_words(nearby_text) or {}
4040

4141
-- perform fuzzy search
42-
local filtered_items = {}
43-
local matched_indices = fuzzy.rust.fuzzy(needle, haystack, {
42+
local haystack_filter_text = {}
43+
for _, item in ipairs(haystack) do
44+
table.insert(haystack_filter_text, item.label)
45+
end
46+
local matched_indices = fuzzy.rust.fuzzy(needle, haystack_filter_text, haystack, {
4447
-- each matching char is worth 4 points and it receives a bonus for capitalization, delimiter and prefix
4548
-- so this should generally be good
4649
-- TODO: make this configurable
@@ -53,10 +56,10 @@ function fuzzy.filter_items(needle, haystack)
5356
nearby_words = nearby_words,
5457
})
5558

59+
local filtered_items = {}
5660
for _, idx in ipairs(matched_indices) do
5761
table.insert(filtered_items, haystack[idx + 1])
5862
end
59-
6063
return filtered_items
6164
end
6265

lua/blink/cmp/fuzzy/lib.rs

+19-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::frecency::FrecencyTracker;
2-
use crate::fuzzy::{FuzzyOptions, LspItem};
2+
use crate::fuzzy::FuzzyOptions;
3+
use crate::lsp_item::LspItem;
34
use lazy_static::lazy_static;
45
use mlua::prelude::*;
56
use regex::Regex;
@@ -8,6 +9,7 @@ use std::sync::RwLock;
89

910
mod frecency;
1011
mod fuzzy;
12+
mod lsp_item;
1113

1214
lazy_static! {
1315
static ref REGEX: Regex = Regex::new(r"[A-Za-z][A-Za-z0-9_\\-]{2,32}").unwrap();
@@ -52,7 +54,12 @@ pub fn access(_: &Lua, item: LspItem) -> LuaResult<bool> {
5254

5355
pub fn fuzzy(
5456
_lua: &Lua,
55-
(needle, haystack, opts): (String, Vec<LspItem>, FuzzyOptions),
57+
(needle, haystack_filter_text, haystack, opts): (
58+
String,
59+
Vec<String>,
60+
Vec<LuaTable>,
61+
FuzzyOptions,
62+
),
5663
) -> LuaResult<Vec<u32>> {
5764
let mut frecency_handle = FRECENCY.write().map_err(|_| {
5865
mlua::Error::RuntimeError("Failed to acquire lock for frecency".to_string())
@@ -61,10 +68,12 @@ pub fn fuzzy(
6168
mlua::Error::RuntimeError("Attempted to use frencecy before initialization".to_string())
6269
})?;
6370

64-
Ok(fuzzy::fuzzy(needle, haystack, frecency, opts)
65-
.into_iter()
66-
.map(|i| i as u32)
67-
.collect())
71+
Ok(
72+
fuzzy::fuzzy(needle, haystack_filter_text, haystack, frecency, opts)
73+
.into_iter()
74+
.map(|i| i as u32)
75+
.collect(),
76+
)
6877
}
6978

7079
pub fn get_words(_: &Lua, text: String) -> LuaResult<Vec<String>> {
@@ -76,13 +85,15 @@ pub fn get_words(_: &Lua, text: String) -> LuaResult<Vec<String>> {
7685
.collect())
7786
}
7887

79-
#[mlua::lua_module]
88+
// NOTE: skip_memory_check greatly improves performance
89+
// https://github.com/mlua-rs/mlua/issues/318
90+
#[mlua::lua_module(skip_memory_check)]
8091
fn blink_cmp_fuzzy(lua: &Lua) -> LuaResult<LuaTable> {
8192
let exports = lua.create_table()?;
8293
exports.set("fuzzy", lua.create_function(fuzzy)?)?;
8394
exports.set("get_words", lua.create_function(get_words)?)?;
8495
exports.set("init_db", lua.create_function(init_db)?)?;
8596
exports.set("destroy_db", lua.create_function(destroy_db)?)?;
86-
exports.set("access", lua.create_function(access)?)?;
97+
// exports.set("access", lua.create_function(access)?)?;
8798
Ok(exports)
8899
}

lua/blink/cmp/fuzzy/lsp_item.rs

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use mlua::prelude::*;
2+
3+
#[derive(Debug)]
4+
pub struct LspItem {
5+
pub label: String,
6+
pub kind: u32,
7+
pub score_offset: i32,
8+
pub source_id: String,
9+
}
10+
11+
impl From<LuaTable<'_>> for LspItem {
12+
fn from(tab: LuaTable) -> Self {
13+
let label: String = tab.get("label").unwrap_or_default();
14+
let kind: u32 = tab.get("kind").unwrap_or_default();
15+
let score_offset: i32 = tab.get("score_offset").unwrap_or(0);
16+
let source_id: String = tab.get("source_id").unwrap_or_default();
17+
18+
LspItem {
19+
label,
20+
kind,
21+
score_offset,
22+
source_id,
23+
}
24+
}
25+
}
26+
27+
// #[derive(Debug)]
28+
// pub struct LspItem<'lua> {
29+
// table: LuaTable<'lua>,
30+
// pub label: OnceCell<String>,
31+
// pub sort_text: OnceCell<Option<String>>,
32+
// pub filter_text: OnceCell<String>,
33+
// pub kind: OnceCell<u32>,
34+
// pub score_offset: OnceCell<i32>,
35+
// pub source_id: OnceCell<String>,
36+
// }
37+
//
38+
// impl LspItem<'_> {
39+
// pub fn label(&self) -> String {
40+
// self.label
41+
// .get_or_init(|| self.table.get::<_, String>("label").unwrap_or_default())
42+
// .to_owned()
43+
// }
44+
// pub fn sort_text(&self) -> Option<String> {
45+
// self.sort_text
46+
// .get_or_init(|| self.table.get("sortText").ok())
47+
// .to_owned()
48+
// }
49+
// pub fn filter_text(&self) -> String {
50+
// self.filter_text
51+
// .get_or_init(|| self.table.get("filterText").unwrap_or(self.label()))
52+
// .to_owned()
53+
// }
54+
// pub fn kind(&self) -> u32 {
55+
// self.kind
56+
// .get_or_init(|| self.table.get("kind").unwrap_or_default())
57+
// .to_owned()
58+
// }
59+
// pub fn score_offset(&self) -> i32 {
60+
// self.score_offset
61+
// .get_or_init(|| self.table.get("score_offset").unwrap_or(0))
62+
// .to_owned()
63+
// }
64+
// pub fn source_id(&self) -> String {
65+
// self.source_id
66+
// .get_or_init(|| self.table.get("source_id").unwrap_or_default())
67+
// .to_owned()
68+
// }
69+
// }
70+
//
71+
// impl<'lua> FromLua<'lua> for LspItem<'lua> {
72+
// fn from_lua(value: LuaValue<'lua>, _lua: &'lua Lua) -> LuaResult<Self> {
73+
// match value {
74+
// LuaValue::Table(tab) => Ok(LspItem {
75+
// table: tab,
76+
// label: OnceCell::new(),
77+
// sort_text: OnceCell::new(),
78+
// filter_text: OnceCell::new(),
79+
// kind: OnceCell::new(),
80+
// score_offset: OnceCell::new(),
81+
// source_id: OnceCell::new(),
82+
// }),
83+
// _ => Err(mlua::Error::FromLuaConversionError {
84+
// from: "LuaValue",
85+
// to: "LspItem",
86+
// message: None,
87+
// }),
88+
// }
89+
// }
90+
// }

0 commit comments

Comments
 (0)