Skip to content

Commit a1490a0

Browse files
it stopped working, even though I improved it
1 parent ebfa576 commit a1490a0

File tree

6 files changed

+181
-46
lines changed

6 files changed

+181
-46
lines changed

crates/pgt_completions/src/providers/columns.rs

Lines changed: 106 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
use pgt_schema_cache::SchemaCache;
2-
use pgt_treesitter::{TreesitterContext, WrappingClause};
1+
use pgt_schema_cache::{Column, SchemaCache};
2+
use pgt_treesitter::TreesitterContext;
33

44
use crate::{
5-
CompletionItemKind,
5+
CompletionItemKind, CompletionText,
66
builder::{CompletionBuilder, PossibleCompletionItem},
7+
providers::helper::{get_range_to_replace, with_closed_quote},
78
relevance::{CompletionRelevanceData, filtering::CompletionFilter, scoring::CompletionScore},
89
};
910

10-
use super::helper::{find_matching_alias_for_table, get_completion_text_with_schema_or_alias};
11+
use super::helper::{find_matching_alias_for_table, with_schema_or_alias};
1112

1213
pub fn complete_columns<'a>(
1314
ctx: &TreesitterContext<'a>,
@@ -19,33 +20,34 @@ pub fn complete_columns<'a>(
1920
for col in available_columns {
2021
let relevance = CompletionRelevanceData::Column(col);
2122

22-
let mut item = PossibleCompletionItem {
23+
let item = PossibleCompletionItem {
2324
label: col.name.clone(),
2425
score: CompletionScore::from(relevance.clone()),
2526
filter: CompletionFilter::from(relevance),
2627
description: format!("{}.{}", col.schema_name, col.table_name),
2728
kind: CompletionItemKind::Column,
28-
completion_text: None,
29+
completion_text: Some(get_completion_text(ctx, col)),
2930
detail: col.type_name.as_ref().map(|t| t.to_string()),
3031
};
3132

32-
// autocomplete with the alias in a join clause if we find one
33-
if matches!(
34-
ctx.wrapping_clause_type,
35-
Some(WrappingClause::Join { .. })
36-
| Some(WrappingClause::Where)
37-
| Some(WrappingClause::Select)
38-
) {
39-
item.completion_text = find_matching_alias_for_table(ctx, col.table_name.as_str())
40-
.and_then(|alias| {
41-
get_completion_text_with_schema_or_alias(ctx, col.name.as_str(), alias.as_str())
42-
});
43-
}
44-
4533
builder.add_item(item);
4634
}
4735
}
4836

37+
fn get_completion_text(ctx: &TreesitterContext, col: &Column) -> CompletionText {
38+
let range = get_range_to_replace(ctx);
39+
let col_name = with_closed_quote(ctx, col.name.as_str());
40+
let alias = find_matching_alias_for_table(ctx, col.table_name.as_str());
41+
let with_schema_or_alias =
42+
with_schema_or_alias(ctx, col_name.as_str(), alias.as_ref().map(|s| s.as_str()));
43+
44+
CompletionText {
45+
is_snippet: false,
46+
range,
47+
text: with_schema_or_alias,
48+
}
49+
}
50+
4951
#[cfg(test)]
5052
mod tests {
5153
use std::vec;
@@ -932,4 +934,89 @@ mod tests {
932934
.await;
933935
}
934936
}
937+
938+
#[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")]
939+
async fn completes_quoted_columns(pool: PgPool) {
940+
let setup = r#"
941+
create schema if not exists auth;
942+
drop table if exists auth.quote_test_users;
943+
944+
create table auth.quote_test_users (
945+
id serial primary key,
946+
email text unique not null,
947+
name text not null,
948+
"quoted_column" text
949+
);
950+
"#;
951+
952+
pool.execute(setup).await.unwrap();
953+
954+
// test completion inside quoted column name
955+
assert_complete_results(
956+
format!(
957+
r#"select "em{}" from "auth"."quote_test_users""#,
958+
QueryWithCursorPosition::cursor_marker()
959+
)
960+
.as_str(),
961+
vec![CompletionAssertion::LabelAndDesc(
962+
"email".to_string(),
963+
"auth.quote_test_users".to_string(),
964+
)],
965+
None,
966+
&pool,
967+
)
968+
.await;
969+
970+
// test completion for already quoted column
971+
assert_complete_results(
972+
format!(
973+
r#"select "quoted_col{}" from "auth"."quote_test_users""#,
974+
QueryWithCursorPosition::cursor_marker()
975+
)
976+
.as_str(),
977+
vec![CompletionAssertion::LabelAndDesc(
978+
"quoted_column".to_string(),
979+
"auth.quote_test_users".to_string(),
980+
)],
981+
None,
982+
&pool,
983+
)
984+
.await;
985+
986+
// test completion with empty quotes
987+
assert_complete_results(
988+
format!(
989+
r#"select "{}" from "auth"."quote_test_users""#,
990+
QueryWithCursorPosition::cursor_marker()
991+
)
992+
.as_str(),
993+
vec![
994+
CompletionAssertion::Label("email".to_string()),
995+
CompletionAssertion::Label("id".to_string()),
996+
CompletionAssertion::Label("name".to_string()),
997+
CompletionAssertion::Label("quoted_column".to_string()),
998+
],
999+
None,
1000+
&pool,
1001+
)
1002+
.await;
1003+
1004+
// test completion with partially opened quote
1005+
assert_complete_results(
1006+
format!(
1007+
r#"select "{} from "auth"."quote_test_users""#,
1008+
QueryWithCursorPosition::cursor_marker()
1009+
)
1010+
.as_str(),
1011+
vec![
1012+
CompletionAssertion::Label("email".to_string()),
1013+
CompletionAssertion::Label("id".to_string()),
1014+
CompletionAssertion::Label("name".to_string()),
1015+
CompletionAssertion::Label("quoted_column".to_string()),
1016+
],
1017+
None,
1018+
&pool,
1019+
)
1020+
.await;
1021+
}
9351022
}

crates/pgt_completions/src/providers/functions.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ use pgt_treesitter::TreesitterContext;
44
use crate::{
55
CompletionItemKind, CompletionText,
66
builder::{CompletionBuilder, PossibleCompletionItem},
7-
providers::helper::get_range_to_replace,
7+
providers::helper::{get_range_to_replace, with_closed_quote},
88
relevance::{CompletionRelevanceData, filtering::CompletionFilter, scoring::CompletionScore},
99
};
1010

11-
use super::helper::get_completion_text_with_schema_or_alias;
11+
use super::helper::with_schema_or_alias;
1212

1313
pub fn complete_functions<'a>(
1414
ctx: &'a TreesitterContext,
@@ -36,9 +36,8 @@ pub fn complete_functions<'a>(
3636

3737
fn get_completion_text(ctx: &TreesitterContext, func: &Function) -> CompletionText {
3838
let range = get_range_to_replace(ctx);
39-
let mut text = get_completion_text_with_schema_or_alias(ctx, &func.name, &func.schema)
40-
.map(|ct| ct.text)
41-
.unwrap_or(func.name.to_string());
39+
let closed_quote = with_closed_quote(ctx, &func.name);
40+
let mut text = with_schema_or_alias(ctx, closed_quote.as_str(), Some(func.schema.as_str()));
4241

4342
if ctx.is_invocation {
4443
CompletionText {
Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use pgt_text_size::{TextRange, TextSize};
22
use pgt_treesitter::TreesitterContext;
33

4-
use crate::{CompletionText, remove_sanitized_token};
4+
use crate::{is_sanitized_token_with_quote, remove_sanitized_token};
55

66
pub(crate) fn find_matching_alias_for_table(
77
ctx: &TreesitterContext,
@@ -19,33 +19,45 @@ pub(crate) fn get_range_to_replace(ctx: &TreesitterContext) -> TextRange {
1919
match ctx.node_under_cursor.as_ref() {
2020
Some(node) => {
2121
let content = ctx.get_node_under_cursor_content().unwrap_or("".into());
22-
let length = remove_sanitized_token(content.as_str()).len();
22+
let content = content.as_str();
23+
24+
let length = remove_sanitized_token(content).len();
2325

2426
let start = node.start_byte();
25-
let end = start + length;
27+
let mut end = start + length;
28+
29+
if is_sanitized_token_with_quote(content) {
30+
end += 1;
31+
}
2632

2733
TextRange::new(start.try_into().unwrap(), end.try_into().unwrap())
2834
}
2935
None => TextRange::empty(TextSize::new(0)),
3036
}
3137
}
3238

33-
pub(crate) fn get_completion_text_with_schema_or_alias(
39+
pub(crate) fn with_schema_or_alias(
3440
ctx: &TreesitterContext,
3541
item_name: &str,
36-
schema_or_alias_name: &str,
37-
) -> Option<CompletionText> {
42+
schema_or_alias_name: Option<&str>,
43+
) -> String {
3844
let is_already_prefixed_with_schema_name = ctx.schema_or_alias_name.is_some();
3945

40-
if schema_or_alias_name == "public" || is_already_prefixed_with_schema_name {
41-
None
46+
if schema_or_alias_name.is_none_or(|s| s == "public") || is_already_prefixed_with_schema_name {
47+
item_name.to_string()
4248
} else {
43-
let range = get_range_to_replace(ctx);
49+
format!("{}.{}", schema_or_alias_name.unwrap(), item_name).to_string()
50+
}
51+
}
4452

45-
Some(CompletionText {
46-
text: format!("{}.{}", schema_or_alias_name, item_name),
47-
range,
48-
is_snippet: false,
49-
})
53+
pub(crate) fn with_closed_quote(ctx: &TreesitterContext, item_name: &str) -> String {
54+
let mut with_closed = String::from(item_name);
55+
56+
if let Some(content) = ctx.get_node_under_cursor_content() {
57+
if is_sanitized_token_with_quote(content.as_str()) {
58+
with_closed.push('"');
59+
}
5060
}
61+
62+
with_closed
5163
}

crates/pgt_completions/src/providers/tables.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
use pgt_schema_cache::SchemaCache;
1+
use pgt_schema_cache::{SchemaCache, Table};
22
use pgt_treesitter::TreesitterContext;
33

44
use crate::{
5+
CompletionText,
56
builder::{CompletionBuilder, PossibleCompletionItem},
67
item::CompletionItemKind,
8+
providers::helper::{get_range_to_replace, with_closed_quote},
79
relevance::{CompletionRelevanceData, filtering::CompletionFilter, scoring::CompletionScore},
810
};
911

10-
use super::helper::get_completion_text_with_schema_or_alias;
12+
use super::helper::with_schema_or_alias;
1113

1214
pub fn complete_tables<'a>(
1315
ctx: &'a TreesitterContext,
@@ -34,17 +36,25 @@ pub fn complete_tables<'a>(
3436
description: table.schema.to_string(),
3537
kind: CompletionItemKind::Table,
3638
detail,
37-
completion_text: get_completion_text_with_schema_or_alias(
38-
ctx,
39-
&table.name,
40-
&table.schema,
41-
),
39+
completion_text: Some(get_completion_text(ctx, table)),
4240
};
4341

4442
builder.add_item(item);
4543
}
4644
}
4745

46+
fn get_completion_text(ctx: &TreesitterContext, table: &Table) -> CompletionText {
47+
let range = get_range_to_replace(ctx);
48+
let closed_quote = with_closed_quote(ctx, &table.name);
49+
let text = with_schema_or_alias(ctx, closed_quote.as_str(), Some(table.schema.as_str()));
50+
51+
CompletionText {
52+
text,
53+
range,
54+
is_snippet: false,
55+
}
56+
}
57+
4858
#[cfg(test)]
4959
mod tests {
5060

crates/pgt_completions/src/relevance/filtering.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,35 @@ impl CompletionFilter<'_> {
3939
if current_node_kind.starts_with("keyword_")
4040
|| current_node_kind == "="
4141
|| current_node_kind == ","
42-
|| current_node_kind == "literal"
4342
|| current_node_kind == "ERROR"
4443
{
4544
return None;
4645
}
4746

47+
// "literal" nodes can be identfiers wrapped in quotes:
48+
// `select "email" from auth.users;`
49+
// Here, "email" is a literal node.
50+
if current_node_kind == "literal" {
51+
match self.data {
52+
CompletionRelevanceData::Column(_) => match ctx.wrapping_clause_type.as_ref() {
53+
Some(WrappingClause::Select)
54+
| Some(WrappingClause::Where)
55+
| Some(WrappingClause::Join { .. })
56+
| Some(WrappingClause::Update)
57+
| Some(WrappingClause::Delete)
58+
| Some(WrappingClause::Insert)
59+
| Some(WrappingClause::DropColumn)
60+
| Some(WrappingClause::AlterColumn)
61+
| Some(WrappingClause::RenameColumn)
62+
| Some(WrappingClause::PolicyCheck) => {
63+
// the literal is probably a column
64+
}
65+
_ => return None,
66+
},
67+
_ => return None,
68+
}
69+
}
70+
4871
// No autocompletions if there are two identifiers without a separator.
4972
if ctx.node_under_cursor.as_ref().is_some_and(|n| match n {
5073
NodeUnderCursor::TsNode(node) => node.prev_sibling().is_some_and(|p| {

crates/pgt_completions/src/sanitization.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ pub(crate) fn is_sanitized_token(txt: &str) -> bool {
2929
txt == SANITIZED_TOKEN || txt == SANITIZED_TOKEN_WITH_QUOTE
3030
}
3131

32+
pub(crate) fn is_sanitized_token_with_quote(txt: &str) -> bool {
33+
txt == SANITIZED_TOKEN_WITH_QUOTE
34+
}
35+
3236
#[derive(PartialEq, Eq, Debug)]
3337
pub(crate) enum NodeText {
3438
Replaced,

0 commit comments

Comments
 (0)