Skip to content

Commit dd70fa6

Browse files
authored
Merge pull request #155 from juleswritescode/feat/completions-fn
feat(completions): autocomplete for function types
2 parents 0df473b + aeb5dff commit dd70fa6

File tree

15 files changed

+596
-258
lines changed

15 files changed

+596
-258
lines changed

crates/pg_completions/src/builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ impl CompletionBuilder {
2828
.enumerate()
2929
.map(|(idx, mut item)| {
3030
if idx == 0 {
31-
item.preselected = Some(should_preselect_first_item);
31+
item.preselected = should_preselect_first_item;
3232
}
3333
item
3434
})

crates/pg_completions/src/complete.rs

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

33
use crate::{
4-
builder::CompletionBuilder, context::CompletionContext, item::CompletionItem,
5-
providers::complete_tables,
4+
builder::CompletionBuilder,
5+
context::CompletionContext,
6+
item::CompletionItem,
7+
providers::{complete_functions, complete_tables},
68
};
79

810
pub const LIMIT: usize = 50;
@@ -11,7 +13,7 @@ pub const LIMIT: usize = 50;
1113
pub struct CompletionParams<'a> {
1214
pub position: TextSize,
1315
pub schema: &'a pg_schema_cache::SchemaCache,
14-
pub text: &'a str,
16+
pub text: String,
1517
pub tree: Option<&'a tree_sitter::Tree>,
1618
}
1719

@@ -34,192 +36,7 @@ pub fn complete(params: CompletionParams) -> CompletionResult {
3436
let mut builder = CompletionBuilder::new();
3537

3638
complete_tables(&ctx, &mut builder);
39+
complete_functions(&ctx, &mut builder);
3740

3841
builder.finish()
3942
}
40-
41-
#[cfg(test)]
42-
mod tests {
43-
use pg_schema_cache::SchemaCache;
44-
use pg_test_utils::test_database::*;
45-
46-
use sqlx::Executor;
47-
48-
use crate::{complete, CompletionParams};
49-
50-
#[tokio::test]
51-
async fn autocompletes_simple_table() {
52-
let test_db = get_new_test_db().await;
53-
54-
let setup = r#"
55-
create table users (
56-
id serial primary key,
57-
name text,
58-
password text
59-
);
60-
"#;
61-
62-
test_db
63-
.execute(setup)
64-
.await
65-
.expect("Failed to execute setup query");
66-
67-
let input = "select * from u";
68-
69-
let mut parser = tree_sitter::Parser::new();
70-
parser
71-
.set_language(tree_sitter_sql::language())
72-
.expect("Error loading sql language");
73-
74-
let tree = parser.parse(input, None).unwrap();
75-
let schema_cache = SchemaCache::load(&test_db)
76-
.await
77-
.expect("Couldn't load Schema Cache");
78-
79-
let p = CompletionParams {
80-
position: ((input.len() - 1) as u32).into(),
81-
schema: &schema_cache,
82-
text: input,
83-
tree: Some(&tree),
84-
};
85-
86-
let result = complete(p);
87-
88-
assert!(!result.items.is_empty());
89-
90-
let best_match = &result.items[0];
91-
92-
assert_eq!(
93-
best_match.label, "users",
94-
"Does not return the expected table to autocomplete: {}",
95-
best_match.label
96-
)
97-
}
98-
99-
#[tokio::test]
100-
async fn autocompletes_table_alphanumerically() {
101-
let test_db = get_new_test_db().await;
102-
103-
let setup = r#"
104-
create table addresses (
105-
id serial primary key
106-
);
107-
108-
create table users (
109-
id serial primary key
110-
);
111-
112-
create table emails (
113-
id serial primary key
114-
);
115-
"#;
116-
117-
test_db
118-
.execute(setup)
119-
.await
120-
.expect("Failed to execute setup query");
121-
122-
let schema_cache = SchemaCache::load(&test_db)
123-
.await
124-
.expect("Couldn't load Schema Cache");
125-
126-
let mut parser = tree_sitter::Parser::new();
127-
parser
128-
.set_language(tree_sitter_sql::language())
129-
.expect("Error loading sql language");
130-
131-
let test_cases = vec![
132-
("select * from us", "users"),
133-
("select * from em", "emails"),
134-
("select * from ", "addresses"),
135-
];
136-
137-
for (input, expected_label) in test_cases {
138-
let tree = parser.parse(input, None).unwrap();
139-
140-
let p = CompletionParams {
141-
position: ((input.len() - 1) as u32).into(),
142-
schema: &schema_cache,
143-
text: input,
144-
tree: Some(&tree),
145-
};
146-
147-
let result = complete(p);
148-
149-
assert!(!result.items.is_empty());
150-
151-
let best_match = &result.items[0];
152-
153-
assert_eq!(
154-
best_match.label, expected_label,
155-
"Does not return the expected table to autocomplete: {}",
156-
best_match.label
157-
)
158-
}
159-
}
160-
161-
#[tokio::test]
162-
async fn autocompletes_table_with_schema() {
163-
let test_db = get_new_test_db().await;
164-
165-
let setup = r#"
166-
create schema customer_support;
167-
create schema private;
168-
169-
create table private.user_z (
170-
id serial primary key,
171-
name text,
172-
password text
173-
);
174-
175-
create table customer_support.user_y (
176-
id serial primary key,
177-
request text,
178-
send_at timestamp with time zone
179-
);
180-
"#;
181-
182-
test_db
183-
.execute(setup)
184-
.await
185-
.expect("Failed to execute setup query");
186-
187-
let schema_cache = SchemaCache::load(&test_db)
188-
.await
189-
.expect("Couldn't load SchemaCache");
190-
191-
let mut parser = tree_sitter::Parser::new();
192-
parser
193-
.set_language(tree_sitter_sql::language())
194-
.expect("Error loading sql language");
195-
196-
let test_cases = vec![
197-
("select * from u", "user_y"), // user_y is preferred alphanumerically
198-
("select * from private.u", "user_z"),
199-
("select * from customer_support.u", "user_y"),
200-
];
201-
202-
for (input, expected_label) in test_cases {
203-
let tree = parser.parse(input, None).unwrap();
204-
205-
let p = CompletionParams {
206-
position: ((input.len() - 1) as u32).into(),
207-
schema: &schema_cache,
208-
text: input,
209-
tree: Some(&tree),
210-
};
211-
212-
let result = complete(p);
213-
214-
assert!(!result.items.is_empty());
215-
216-
let best_match = &result.items[0];
217-
218-
assert_eq!(
219-
best_match.label, expected_label,
220-
"Does not return the expected table to autocomplete: {}",
221-
best_match.label
222-
)
223-
}
224-
}
225-
}

crates/pg_completions/src/context.rs

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,46 @@ use pg_schema_cache::SchemaCache;
22

33
use crate::CompletionParams;
44

5+
#[derive(Debug, PartialEq, Eq)]
6+
pub enum ClauseType {
7+
Select,
8+
Where,
9+
From,
10+
Update,
11+
Delete,
12+
}
13+
14+
impl TryFrom<&str> for ClauseType {
15+
type Error = String;
16+
17+
fn try_from(value: &str) -> Result<Self, Self::Error> {
18+
match value {
19+
"select" => Ok(Self::Select),
20+
"where" => Ok(Self::Where),
21+
"from" => Ok(Self::From),
22+
"update" => Ok(Self::Update),
23+
"delete" => Ok(Self::Delete),
24+
_ => {
25+
let message = format!("Unimplemented ClauseType: {}", value);
26+
27+
// Err on tests, so we notice that we're lacking an implementation immediately.
28+
if cfg!(test) {
29+
panic!("{}", message);
30+
}
31+
32+
return Err(message);
33+
}
34+
}
35+
}
36+
}
37+
38+
impl TryFrom<String> for ClauseType {
39+
type Error = String;
40+
fn try_from(value: String) -> Result<ClauseType, Self::Error> {
41+
ClauseType::try_from(value.as_str())
42+
}
43+
}
44+
545
pub(crate) struct CompletionContext<'a> {
646
pub ts_node: Option<tree_sitter::Node<'a>>,
747
pub tree: Option<&'a tree_sitter::Tree>,
@@ -10,15 +50,15 @@ pub(crate) struct CompletionContext<'a> {
1050
pub position: usize,
1151

1252
pub schema_name: Option<String>,
13-
pub wrapping_clause_type: Option<String>,
53+
pub wrapping_clause_type: Option<ClauseType>,
1454
pub is_invocation: bool,
1555
}
1656

1757
impl<'a> CompletionContext<'a> {
1858
pub fn new(params: &'a CompletionParams) -> Self {
19-
let mut tree = Self {
59+
let mut ctx = Self {
2060
tree: params.tree,
21-
text: params.text,
61+
text: &params.text,
2262
schema_cache: params.schema,
2363
position: usize::from(params.position),
2464

@@ -28,9 +68,9 @@ impl<'a> CompletionContext<'a> {
2868
is_invocation: false,
2969
};
3070

31-
tree.gather_tree_context();
71+
ctx.gather_tree_context();
3272

33-
tree
73+
ctx
3474
}
3575

3676
pub fn get_ts_node_content(&self, ts_node: tree_sitter::Node<'a>) -> Option<&'a str> {
@@ -65,7 +105,7 @@ impl<'a> CompletionContext<'a> {
65105
let current_node_kind = current_node.kind();
66106

67107
match previous_node_kind {
68-
"statement" => self.wrapping_clause_type = Some(current_node_kind.to_string()),
108+
"statement" => self.wrapping_clause_type = current_node_kind.try_into().ok(),
69109
"invocation" => self.is_invocation = true,
70110

71111
_ => {}
@@ -84,7 +124,7 @@ impl<'a> CompletionContext<'a> {
84124

85125
// in Treesitter, the Where clause is nested inside other clauses
86126
"where" => {
87-
self.wrapping_clause_type = Some("where".to_string());
127+
self.wrapping_clause_type = "where".try_into().ok();
88128
}
89129

90130
_ => {}
@@ -102,7 +142,7 @@ impl<'a> CompletionContext<'a> {
102142

103143
#[cfg(test)]
104144
mod tests {
105-
use crate::context::CompletionContext;
145+
use crate::{context::CompletionContext, test_helper::CURSOR_POS};
106146

107147
fn get_tree(input: &str) -> tree_sitter::Tree {
108148
let mut parser = tree_sitter::Parser::new();
@@ -113,8 +153,6 @@ mod tests {
113153
parser.parse(input, None).expect("Unable to parse tree")
114154
}
115155

116-
static CURSOR_POS: &str = "XXX";
117-
118156
#[test]
119157
fn identifies_clauses() {
120158
let test_cases = vec![
@@ -151,14 +189,14 @@ mod tests {
151189
let tree = get_tree(text.as_str());
152190
let params = crate::CompletionParams {
153191
position: (position as u32).into(),
154-
text: text.as_str(),
192+
text: text,
155193
tree: Some(&tree),
156194
schema: &pg_schema_cache::SchemaCache::new(),
157195
};
158196

159197
let ctx = CompletionContext::new(&params);
160198

161-
assert_eq!(ctx.wrapping_clause_type, Some(expected_clause.to_string()));
199+
assert_eq!(ctx.wrapping_clause_type, expected_clause.try_into().ok());
162200
}
163201
}
164202

@@ -184,7 +222,7 @@ mod tests {
184222
let tree = get_tree(text.as_str());
185223
let params = crate::CompletionParams {
186224
position: (position as u32).into(),
187-
text: text.as_str(),
225+
text: text,
188226
tree: Some(&tree),
189227
schema: &pg_schema_cache::SchemaCache::new(),
190228
};
@@ -219,7 +257,7 @@ mod tests {
219257
let tree = get_tree(text.as_str());
220258
let params = crate::CompletionParams {
221259
position: (position as u32).into(),
222-
text: text.as_str(),
260+
text: text,
223261
tree: Some(&tree),
224262
schema: &pg_schema_cache::SchemaCache::new(),
225263
};

crates/pg_completions/src/item.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
#[derive(Debug)]
1+
#[derive(Debug, PartialEq, Eq)]
22
pub enum CompletionItemKind {
33
Table,
4+
Function,
45
}
56

67
#[derive(Debug)]
78
pub struct CompletionItem {
89
pub label: String,
910
pub(crate) score: i32,
1011
pub description: String,
11-
pub preselected: Option<bool>,
12+
pub preselected: bool,
1213
pub kind: CompletionItemKind,
1314
}

crates/pg_completions/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,8 @@ mod item;
55
mod providers;
66
mod relevance;
77

8+
#[cfg(test)]
9+
mod test_helper;
10+
811
pub use complete::*;
912
pub use item::*;

0 commit comments

Comments
 (0)