Skip to content

Commit 4ef2c75

Browse files
fix(completions): use fuzzy matching for user input (#393)
This makes it such that we rank higher the `portfolio_settings` column if we type "sett" – before, it didn't get any boost.
1 parent 6469ce3 commit 4ef2c75

File tree

4 files changed

+58
-48
lines changed

4 files changed

+58
-48
lines changed

Cargo.lock

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/pgt_completions/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ async-std = "1.12.0"
1717
pgt_text_size.workspace = true
1818

1919

20+
fuzzy-matcher = "0.3.7"
2021
pgt_schema_cache.workspace = true
2122
pgt_treesitter_queries.workspace = true
2223
schemars = { workspace = true, optional = true }

crates/pgt_completions/src/providers/columns.rs

+33-43
Original file line numberDiff line numberDiff line change
@@ -273,60 +273,50 @@ mod tests {
273273
id1 serial primary key,
274274
name1 text,
275275
address1 text,
276-
email1 text
276+
email1 text,
277+
user_settings jsonb
277278
);
278279
279280
create table public.users (
280281
id2 serial primary key,
281282
name2 text,
282283
address2 text,
283-
email2 text
284+
email2 text,
285+
settings jsonb
284286
);
285287
"#;
286288

287-
{
288-
let test_case = TestCase {
289-
message: "",
290-
query: format!(r#"select {} from users"#, CURSOR_POS),
291-
label: "suggests from table",
292-
description: "",
293-
};
294-
295-
let (tree, cache) = get_test_deps(setup, test_case.get_input_query()).await;
296-
let params = get_test_params(&tree, &cache, test_case.get_input_query());
297-
let results = complete(params);
298-
299-
assert_eq!(
300-
results
301-
.into_iter()
302-
.take(4)
303-
.map(|item| item.label)
304-
.collect::<Vec<String>>(),
305-
vec!["address2", "email2", "id2", "name2"]
306-
);
307-
}
308-
309-
{
310-
let test_case = TestCase {
311-
message: "",
312-
query: format!(r#"select {} from private.users"#, CURSOR_POS),
313-
label: "suggests from table",
314-
description: "",
315-
};
289+
assert_complete_results(
290+
format!(r#"select {} from users"#, CURSOR_POS).as_str(),
291+
vec![
292+
CompletionAssertion::Label("address2".into()),
293+
CompletionAssertion::Label("email2".into()),
294+
CompletionAssertion::Label("id2".into()),
295+
CompletionAssertion::Label("name2".into()),
296+
],
297+
setup,
298+
)
299+
.await;
316300

317-
let (tree, cache) = get_test_deps(setup, test_case.get_input_query()).await;
318-
let params = get_test_params(&tree, &cache, test_case.get_input_query());
319-
let results = complete(params);
301+
assert_complete_results(
302+
format!(r#"select {} from private.users"#, CURSOR_POS).as_str(),
303+
vec![
304+
CompletionAssertion::Label("address1".into()),
305+
CompletionAssertion::Label("email1".into()),
306+
CompletionAssertion::Label("id1".into()),
307+
CompletionAssertion::Label("name1".into()),
308+
],
309+
setup,
310+
)
311+
.await;
320312

321-
assert_eq!(
322-
results
323-
.into_iter()
324-
.take(4)
325-
.map(|item| item.label)
326-
.collect::<Vec<String>>(),
327-
vec!["address1", "email1", "id1", "name1"]
328-
);
329-
}
313+
// asserts fuzzy finding for "settings"
314+
assert_complete_results(
315+
format!(r#"select sett{} from private.users"#, CURSOR_POS).as_str(),
316+
vec![CompletionAssertion::Label("user_settings".into())],
317+
setup,
318+
)
319+
.await;
330320
}
331321

332322
#[tokio::test]

crates/pgt_completions/src/relevance/scoring.rs

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use fuzzy_matcher::{FuzzyMatcher, skim::SkimMatcherV2};
2+
13
use crate::context::{CompletionContext, WrappingClause, WrappingNode};
24

35
use super::CompletionRelevanceData;
@@ -45,14 +47,21 @@ impl CompletionScore<'_> {
4547
CompletionRelevanceData::Schema(s) => s.name.as_str(),
4648
};
4749

48-
if name.starts_with(content.as_str()) {
49-
let len: i32 = content
50-
.len()
50+
let fz_matcher = SkimMatcherV2::default();
51+
52+
if let Some(score) = fz_matcher.fuzzy_match(name, content.as_str()) {
53+
let scorei32: i32 = score
5154
.try_into()
5255
.expect("The length of the input exceeds i32 capacity");
5356

54-
self.score += len * 10;
55-
};
57+
// the scoring value isn't linear.
58+
// here are a couple of samples:
59+
// - item: bytea_string_agg_transfn, input: n, score: 15
60+
// - item: numeric_uplus, input: n, score: 31
61+
// - item: settings, input: sett, score: 91
62+
// - item: user_settings, input: sett, score: 82
63+
self.score += scorei32 / 2;
64+
}
5665
}
5766

5867
fn check_matching_clause_type(&mut self, ctx: &CompletionContext) {

0 commit comments

Comments
 (0)