Skip to content

Commit 4ccda40

Browse files
nice, comes out as expected
1 parent 7c279d5 commit 4ccda40

File tree

9 files changed

+172
-71
lines changed

9 files changed

+172
-71
lines changed

crates/pg_completions/src/builder.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
11
use crate::{item::CompletionItem, CompletionResult};
22

3-
pub struct CompletionBuilder {
4-
pub items: Vec<CompletionItem>,
3+
pub(crate) struct CompletionBuilder {
4+
items: Vec<CompletionItem>,
55
}
66

7-
pub struct CompletionConfig {}
7+
impl CompletionBuilder {
8+
pub fn new() -> Self {
9+
CompletionBuilder { items: vec![] }
10+
}
811

9-
impl From<&CompletionConfig> for CompletionBuilder {
10-
fn from(_config: &CompletionConfig) -> Self {
11-
Self { items: Vec::new() }
12+
pub fn add_item(&mut self, item: CompletionItem) {
13+
self.items.push(item)
1214
}
13-
}
1415

15-
impl CompletionBuilder {
1616
pub fn finish(mut self) -> CompletionResult {
1717
self.items.sort_by(|a, b| {
18-
b.preselect
19-
.cmp(&a.preselect)
20-
.then_with(|| b.score.cmp(&a.score))
21-
.then_with(|| a.data.label().cmp(b.data.label()))
18+
b.score()
19+
.cmp(&a.score())
20+
.then_with(|| a.label.cmp(&b.label))
2221
});
2322

24-
self.items.dedup_by(|a, b| a.data.label() == b.data.label());
23+
self.items.dedup_by(|a, b| a.label == b.label);
2524
self.items.truncate(crate::LIMIT);
25+
2626
let Self { items, .. } = self;
27+
2728
CompletionResult { items }
2829
}
2930
}

crates/pg_completions/src/complete.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use text_size::TextSize;
22

3-
use crate::{builder, context::CompletionContext, item::CompletionItem};
3+
use crate::{
4+
builder::CompletionBuilder, context::CompletionContext, item::CompletionItem, providers,
5+
};
46

57
pub const LIMIT: usize = 50;
68

@@ -17,9 +19,18 @@ pub struct CompletionResult {
1719
pub items: Vec<CompletionItem>,
1820
}
1921

20-
pub fn complete(params: &CompletionParams) -> CompletionResult {
21-
let ctx = CompletionContext::new(params);
22-
let mut builder = builder::CompletionBuilder::from(&builder::CompletionConfig {});
22+
pub fn complete(params: CompletionParams) -> CompletionResult {
23+
let ctx = CompletionContext::new(&params);
24+
let mut builder = CompletionBuilder::new();
25+
26+
if let Some(node) = ctx.ts_node {
27+
match node.kind() {
28+
"relation" => providers::complete_tables(&ctx, &mut builder),
29+
_ => {}
30+
}
31+
} else {
32+
// if query emtpy, autocomplete select keywords etc?
33+
}
2334

2435
builder.finish()
2536
}
@@ -55,7 +66,7 @@ mod tests {
5566
tree: Some(&tree),
5667
};
5768

58-
let result = complete(&p);
69+
let result = complete(p);
5970

6071
assert!(result.items.len() > 0);
6172
}
@@ -81,7 +92,7 @@ mod tests {
8192
tree: Some(&tree),
8293
};
8394

84-
let result = complete(&p);
95+
let result = complete(p);
8596

8697
assert!(result.items.len() > 0);
8798
}
@@ -120,7 +131,7 @@ mod tests {
120131
tree: Some(&tree),
121132
};
122133

123-
let result = complete(&p);
134+
let result = complete(p);
124135

125136
// TODO: actually assert that we get good autocompletion suggestions
126137
assert!(result.items.len() > 0);

crates/pg_completions/src/context.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,47 @@
11
use pg_schema_cache::SchemaCache;
2+
use text_size::TextSize;
23

34
use crate::CompletionParams;
45

5-
pub struct CompletionContext<'a> {
6+
pub(crate) struct CompletionContext<'a> {
67
pub ts_node: Option<tree_sitter::Node<'a>>,
78
pub tree: Option<&'a tree_sitter::Tree>,
89
pub text: &'a str,
910
pub schema_cache: &'a SchemaCache,
1011
pub original_token: Option<char>,
12+
pub position: TextSize,
1113
}
1214

1315
impl<'a> CompletionContext<'a> {
1416
pub fn new(params: &'a CompletionParams) -> Self {
17+
let ts_node = find_ts_node(&params);
18+
1519
Self {
16-
ts_node: find_ts_node(params),
20+
ts_node,
1721
tree: params.tree,
18-
text: params.text,
22+
text: &params.text,
1923
schema_cache: params.schema,
2024
original_token: find_original_token(params),
25+
position: params.position,
26+
}
27+
}
28+
29+
pub fn get_ts_node_content(&self, ts_node: tree_sitter::Node<'a>) -> Option<&'a str> {
30+
let source = self.text;
31+
match ts_node.utf8_text(source.as_bytes()) {
32+
Ok(content) => Some(content),
33+
Err(_) => None,
2134
}
2235
}
2336
}
2437

25-
fn find_original_token<'a>(params: &'a CompletionParams) -> Option<char> {
38+
fn find_original_token<'a>(params: &CompletionParams) -> Option<char> {
2639
let idx = usize::from(params.position);
2740
params.text.chars().nth(idx)
2841
}
2942

3043
fn find_ts_node<'a>(params: &'a CompletionParams) -> Option<tree_sitter::Node<'a>> {
31-
let tree = params.tree?;
44+
let tree = params.tree.as_ref()?;
3245

3346
let mut node = tree.root_node().named_descendant_for_byte_range(
3447
usize::from(params.position),

crates/pg_completions/src/item.rs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,41 @@
11
use text_size::TextRange;
22

3-
#[derive(Debug, PartialEq, Eq)]
4-
pub enum CompletionItemData {
5-
Table(pg_schema_cache::Table),
3+
use crate::relevance::CompletionRelevance;
4+
5+
#[derive(Debug)]
6+
pub enum CompletionItemData<'a> {
7+
Table(&'a pg_schema_cache::Table),
68
}
79

8-
impl CompletionItemData {
9-
pub fn label(&self) -> &str {
10+
impl<'a> CompletionItemData<'a> {
11+
pub fn label(&self) -> String {
1012
match self {
11-
CompletionItemData::Table(t) => t.name.as_str(),
13+
CompletionItemData::Table(t) => t.name.clone(),
1214
}
1315
}
1416
}
1517

16-
#[derive(Debug, PartialEq, Eq)]
18+
#[derive(Debug)]
1719
pub struct CompletionItem {
18-
pub score: i32,
1920
pub range: TextRange,
20-
pub preselect: bool,
21-
pub data: CompletionItemData,
21+
pub label: String,
22+
relevance: CompletionRelevance,
2223
}
2324

2425
impl CompletionItem {
25-
pub fn new_simple(score: i32, range: TextRange, data: CompletionItemData) -> Self {
26+
pub(crate) fn new(
27+
range: TextRange,
28+
data: CompletionItemData,
29+
relevance: CompletionRelevance,
30+
) -> Self {
2631
Self {
27-
score,
2832
range,
29-
preselect: false,
30-
data,
33+
label: data.label(),
34+
relevance,
3135
}
3236
}
37+
38+
pub(crate) fn score(&self) -> i32 {
39+
self.relevance.score()
40+
}
3341
}

crates/pg_completions/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod builder;
22
mod complete;
33
mod context;
44
mod item;
5+
mod providers;
56
mod relevance;
67

78
pub use complete::*;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mod tables;
2+
3+
pub use tables::*;
Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,38 @@
1+
use pg_schema_cache::Table;
12
use text_size::{TextRange, TextSize};
23

3-
use crate::{builder::CompletionBuilder, CompletionItem, CompletionItemData};
4-
5-
use super::CompletionProviderParams;
6-
7-
// todo unify this in a type resolver crate
8-
pub fn complete_tables<'a>(
9-
params: CompletionProviderParams<'a>,
10-
builder: &mut CompletionBuilder<'a>,
11-
) {
12-
if let Some(ts) = params.ts_node {
13-
let range = TextRange::new(
14-
TextSize::try_from(ts.start_byte()).unwrap(),
15-
TextSize::try_from(ts.end_byte()).unwrap(),
16-
);
17-
match ts.kind() {
18-
"relation" => {
19-
// todo better search
20-
params.schema.tables.iter().for_each(|table| {
21-
builder.items.push(CompletionItem::new_simple(
22-
1,
23-
range,
24-
CompletionItemData::Table(table),
25-
));
26-
});
27-
}
28-
_ => {}
29-
}
4+
use crate::{
5+
builder::CompletionBuilder,
6+
context::CompletionContext,
7+
item::{CompletionItem, CompletionItemData},
8+
relevance::CompletionRelevance,
9+
};
10+
11+
pub fn complete_tables(ctx: &CompletionContext, builder: &mut CompletionBuilder) {
12+
let available_tables = &ctx.schema_cache.tables;
13+
14+
let completion_items: Vec<CompletionItem> = available_tables
15+
.iter()
16+
.map(|table| to_completion_item(ctx, table))
17+
.collect();
18+
19+
for item in completion_items {
20+
builder.add_item(item);
3021
}
3122
}
23+
24+
fn to_completion_item(ctx: &CompletionContext, table: &Table) -> CompletionItem {
25+
let data = CompletionItemData::Table(table);
26+
27+
let start = ctx.position;
28+
let end = start + TextSize::from(table.name.len() as u32);
29+
let range = TextRange::new(start, end);
30+
31+
let mut relevance = CompletionRelevance::default();
32+
33+
relevance.set_is_catalog(&table.schema);
34+
relevance.set_matches_prefix(ctx, &table.name);
35+
relevance.set_matches_schema(ctx, &table.schema);
36+
37+
CompletionItem::new(range, data, relevance)
38+
}
Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,54 @@
1-
struct CompletionItemScore {}
1+
use crate::context::CompletionContext;
2+
3+
#[derive(Debug, Default)]
4+
pub(crate) struct CompletionRelevance {
5+
/// does the underlying data match the expected schema we can determine from the query?
6+
matches_schema: bool,
7+
8+
/// Is the underlying item from the pg_catalog schema?
9+
is_catalog: bool,
10+
11+
/// Do the characters the users typed match at least the first 3 characters
12+
/// of the underlying data's name?
13+
matches_prefix: usize,
14+
}
15+
16+
impl CompletionRelevance {
17+
pub fn score(&self) -> i32 {
18+
let mut score: i32 = 0;
19+
20+
if self.matches_schema {
21+
score += 5;
22+
} else if self.is_catalog {
23+
score -= 1;
24+
}
25+
26+
score += (self.matches_prefix * 5) as i32;
27+
28+
score
29+
}
30+
31+
pub fn set_matches_schema(&mut self, ctx: &CompletionContext, schema: &str) {
32+
let node = ctx.ts_node.unwrap();
33+
self.matches_schema = node
34+
.prev_named_sibling()
35+
.is_some_and(|n| ctx.get_ts_node_content(n).is_some_and(|c| c == schema));
36+
}
37+
38+
pub fn set_is_catalog(&mut self, schema: &str) {
39+
self.is_catalog = schema == "pg_catalog"
40+
}
41+
42+
pub fn set_matches_prefix(&mut self, ctx: &CompletionContext, name: &str) {
43+
let node = ctx.ts_node.unwrap();
44+
45+
let content = match ctx.get_ts_node_content(node) {
46+
Some(c) => c,
47+
None => return,
48+
};
49+
50+
if name.starts_with(content) {
51+
self.matches_prefix = content.len();
52+
};
53+
}
54+
}

crates/pg_lsp/src/session.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,17 +235,21 @@ impl Session {
235235

236236
let schema_cache = ide.schema_cache.read().expect("No Schema Cache");
237237

238-
let completion_items = pg_completions::complete(&CompletionParams {
238+
let completion_items = pg_completions::complete(CompletionParams {
239239
position: offset - range.start() - TextSize::from(1),
240-
text: stmt.text.as_str(),
241-
tree: ide.tree_sitter.tree(&stmt).as_ref().map(|x| x.as_ref()),
240+
text: &stmt.text,
241+
tree: ide
242+
.tree_sitter
243+
.tree(&stmt)
244+
.as_ref()
245+
.and_then(|t| Some(t.as_ref())),
242246
schema: &schema_cache,
243247
})
244248
.items
245249
.into_iter()
246250
.map(|i| CompletionItem {
247251
// TODO: add more data
248-
label: i.data.label().to_string(),
252+
label: i.label,
249253
label_details: None,
250254
kind: Some(CompletionItemKind::CLASS),
251255
detail: None,

0 commit comments

Comments
 (0)