Skip to content

Commit 8ae75b3

Browse files
feat: autocomplete schemas (#340)
1 parent c6001fe commit 8ae75b3

File tree

11 files changed

+111
-16
lines changed

11 files changed

+111
-16
lines changed

crates/pgt_completions/benches/sanitization.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ fn to_params<'a>(
2727
position: TextSize::new(pos),
2828
schema: &cache,
2929
text,
30-
tree: Some(tree),
30+
tree: tree,
3131
}
3232
}
3333

crates/pgt_completions/src/complete.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
builder::CompletionBuilder,
55
context::CompletionContext,
66
item::CompletionItem,
7-
providers::{complete_columns, complete_functions, complete_tables},
7+
providers::{complete_columns, complete_functions, complete_schemas, complete_tables},
88
sanitization::SanitizedCompletionParams,
99
};
1010

@@ -32,6 +32,7 @@ pub fn complete(params: CompletionParams) -> Vec<CompletionItem> {
3232
complete_tables(&ctx, &mut builder);
3333
complete_functions(&ctx, &mut builder);
3434
complete_columns(&ctx, &mut builder);
35+
complete_schemas(&ctx, &mut builder);
3536

3637
builder.finish()
3738
}

crates/pgt_completions/src/item.rs

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub enum CompletionItemKind {
77
Table,
88
Function,
99
Column,
10+
Schema,
1011
}
1112

1213
#[derive(Debug, Serialize, Deserialize)]
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
mod columns;
22
mod functions;
3+
mod schemas;
34
mod tables;
45

56
pub use columns::*;
67
pub use functions::*;
8+
pub use schemas::*;
79
pub use tables::*;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use crate::{
2+
CompletionItem, builder::CompletionBuilder, context::CompletionContext,
3+
relevance::CompletionRelevanceData,
4+
};
5+
6+
pub fn complete_schemas(ctx: &CompletionContext, builder: &mut CompletionBuilder) {
7+
let available_schemas = &ctx.schema_cache.schemas;
8+
9+
for schema in available_schemas {
10+
let relevance = CompletionRelevanceData::Schema(&schema);
11+
12+
let item = CompletionItem {
13+
label: schema.name.clone(),
14+
description: "Schema".into(),
15+
preselected: false,
16+
kind: crate::CompletionItemKind::Schema,
17+
score: relevance.get_score(ctx),
18+
};
19+
20+
builder.add_item(item);
21+
}
22+
}
23+
24+
#[cfg(test)]
25+
mod tests {
26+
27+
use crate::{
28+
CompletionItemKind, complete,
29+
test_helper::{CURSOR_POS, get_test_deps, get_test_params},
30+
};
31+
32+
#[tokio::test]
33+
async fn autocompletes_schemas() {
34+
let setup = r#"
35+
create schema private;
36+
create schema auth;
37+
create schema internal;
38+
39+
-- add a table to compete against schemas
40+
create table users (
41+
id serial primary key,
42+
name text,
43+
password text
44+
);
45+
"#;
46+
47+
let query = format!("select * from {}", CURSOR_POS);
48+
49+
let (tree, cache) = get_test_deps(setup, query.as_str().into()).await;
50+
let params = get_test_params(&tree, &cache, query.as_str().into());
51+
let items = complete(params);
52+
53+
assert!(!items.is_empty());
54+
55+
assert_eq!(
56+
items
57+
.into_iter()
58+
.take(5)
59+
.map(|i| (i.label, i.kind))
60+
.collect::<Vec<(String, CompletionItemKind)>>(),
61+
vec![
62+
("public".to_string(), CompletionItemKind::Schema),
63+
("auth".to_string(), CompletionItemKind::Schema),
64+
("internal".to_string(), CompletionItemKind::Schema),
65+
("private".to_string(), CompletionItemKind::Schema),
66+
("users".to_string(), CompletionItemKind::Table),
67+
]
68+
);
69+
}
70+
}

crates/pgt_completions/src/relevance.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub(crate) enum CompletionRelevanceData<'a> {
55
Table(&'a pgt_schema_cache::Table),
66
Function(&'a pgt_schema_cache::Function),
77
Column(&'a pgt_schema_cache::Column),
8+
Schema(&'a pgt_schema_cache::Schema),
89
}
910

1011
impl CompletionRelevanceData<'_> {
@@ -58,6 +59,7 @@ impl CompletionRelevance<'_> {
5859
CompletionRelevanceData::Function(f) => f.name.as_str(),
5960
CompletionRelevanceData::Table(t) => t.name.as_str(),
6061
CompletionRelevanceData::Column(c) => c.name.as_str(),
62+
CompletionRelevanceData::Schema(s) => s.name.as_str(),
6163
};
6264

6365
if name.starts_with(content) {
@@ -97,6 +99,10 @@ impl CompletionRelevance<'_> {
9799
ClauseType::Where => 10,
98100
_ => -15,
99101
},
102+
CompletionRelevanceData::Schema(_) => match clause_type {
103+
ClauseType::From => 10,
104+
_ => -50,
105+
},
100106
}
101107
}
102108

@@ -129,6 +135,7 @@ impl CompletionRelevance<'_> {
129135
CompletionRelevanceData::Function(f) => f.schema.as_str(),
130136
CompletionRelevanceData::Table(t) => t.schema.as_str(),
131137
CompletionRelevanceData::Column(c) => c.schema_name.as_str(),
138+
CompletionRelevanceData::Schema(s) => s.name.as_str(),
132139
}
133140
}
134141

@@ -168,11 +175,7 @@ impl CompletionRelevance<'_> {
168175
}
169176

170177
fn check_is_user_defined(&mut self) {
171-
let schema = match self.data {
172-
CompletionRelevanceData::Column(c) => &c.schema_name,
173-
CompletionRelevanceData::Function(f) => &f.schema,
174-
CompletionRelevanceData::Table(t) => &t.schema,
175-
};
178+
let schema = self.get_schema_name().to_string();
176179

177180
let system_schemas = ["pg_catalog", "information_schema", "pg_toast"];
178181

crates/pgt_completions/src/sanitization.rs

+21-5
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,28 @@ where
4242
let cursor_pos: usize = params.position.into();
4343
let mut sql = String::new();
4444

45-
for (idx, c) in params.text.chars().enumerate() {
46-
if idx == cursor_pos {
47-
sql.push_str(SANITIZED_TOKEN);
48-
sql.push(' ');
45+
let mut sql_iter = params.text.chars();
46+
47+
for idx in 0..cursor_pos + 1 {
48+
match sql_iter.next() {
49+
Some(c) => {
50+
if idx == cursor_pos {
51+
sql.push_str(SANITIZED_TOKEN);
52+
sql.push(' ');
53+
}
54+
sql.push(c);
55+
}
56+
None => {
57+
// the cursor is outside the statement,
58+
// we want to push spaces until we arrive at the cursor position.
59+
// we'll then add the SANITIZED_TOKEN
60+
if idx == cursor_pos {
61+
sql.push_str(SANITIZED_TOKEN);
62+
} else {
63+
sql.push(' ');
64+
}
65+
}
4966
}
50-
sql.push(c);
5167
}
5268

5369
let mut parser = tree_sitter::Parser::new();

crates/pgt_completions/src/test_helper.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ impl From<&str> for InputQuery {
1818
.expect("Insert Cursor Position into your Query.");
1919

2020
InputQuery {
21-
sql: value.replace(CURSOR_POS, ""),
21+
sql: value.replace(CURSOR_POS, "").trim().to_string(),
2222
position,
2323
}
2424
}

crates/pgt_lsp/src/handlers/completions.rs

+1
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,6 @@ fn to_lsp_types_completion_item_kind(
5050
pgt_completions::CompletionItemKind::Function => lsp_types::CompletionItemKind::FUNCTION,
5151
pgt_completions::CompletionItemKind::Table => lsp_types::CompletionItemKind::CLASS,
5252
pgt_completions::CompletionItemKind::Column => lsp_types::CompletionItemKind::FIELD,
53+
pgt_completions::CompletionItemKind::Schema => lsp_types::CompletionItemKind::CLASS,
5354
}
5455
}

crates/pgt_schema_cache/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ mod versions;
1313
pub use columns::*;
1414
pub use functions::{Behavior, Function, FunctionArg, FunctionArgs};
1515
pub use schema_cache::SchemaCache;
16+
pub use schemas::Schema;
1617
pub use tables::{ReplicaIdentity, Table};

crates/pgt_schema_cache/src/schemas.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use crate::schema_cache::SchemaCacheItem;
44

55
#[derive(Debug, Clone, Default)]
66
pub struct Schema {
7-
id: i64,
8-
name: String,
9-
owner: String,
7+
pub id: i64,
8+
pub name: String,
9+
pub owner: String,
1010
}
1111

1212
impl SchemaCacheItem for Schema {

0 commit comments

Comments
 (0)