Skip to content

Commit 339fb78

Browse files
authored
ide: add hover for create function (#767)
1 parent bc0c221 commit 339fb78

File tree

3 files changed

+160
-44
lines changed

3 files changed

+160
-44
lines changed

crates/squawk_ide/src/binder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ fn bind_create_function(b: &mut Binder, create_function: ast::CreateFunction) {
141141

142142
let name_ptr = path_to_ptr(&path);
143143

144-
let Some(schema) = b.current_search_path().first().cloned() else {
144+
let Some(schema) = schema_name(b, &path, false) else {
145145
return;
146146
};
147147

crates/squawk_ide/src/hover.rs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option<String> {
2222
if is_index_ref(&name_ref) {
2323
return hover_index(file, &name_ref, &binder);
2424
}
25+
26+
if is_function_ref(&name_ref) {
27+
return hover_function(file, &name_ref, &binder);
28+
}
2529
}
2630

2731
if let Some(name) = ast::Name::cast(parent) {
@@ -38,6 +42,14 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option<String> {
3842
if let Some(create_index) = name.syntax().ancestors().find_map(ast::CreateIndex::cast) {
3943
return format_create_index(&create_index, &binder);
4044
}
45+
46+
if let Some(create_function) = name
47+
.syntax()
48+
.ancestors()
49+
.find_map(ast::CreateFunction::cast)
50+
{
51+
return format_create_function(&create_function, &binder);
52+
}
4153
}
4254

4355
None
@@ -278,6 +290,68 @@ fn is_index_ref(name_ref: &ast::NameRef) -> bool {
278290
false
279291
}
280292

293+
fn is_function_ref(name_ref: &ast::NameRef) -> bool {
294+
for ancestor in name_ref.syntax().ancestors() {
295+
if ast::DropFunction::can_cast(ancestor.kind()) {
296+
return true;
297+
}
298+
}
299+
false
300+
}
301+
302+
fn hover_function(
303+
file: &ast::SourceFile,
304+
name_ref: &ast::NameRef,
305+
binder: &binder::Binder,
306+
) -> Option<String> {
307+
let function_ptr = resolve::resolve_name_ref(binder, name_ref)?;
308+
309+
let root = file.syntax();
310+
let function_name_node = function_ptr.to_node(root);
311+
312+
let create_function = function_name_node
313+
.ancestors()
314+
.find_map(ast::CreateFunction::cast)?;
315+
316+
format_create_function(&create_function, binder)
317+
}
318+
319+
fn format_create_function(
320+
create_function: &ast::CreateFunction,
321+
binder: &binder::Binder,
322+
) -> Option<String> {
323+
let path = create_function.path()?;
324+
let segment = path.segment()?;
325+
let name = segment.name()?;
326+
let function_name = name.syntax().text().to_string();
327+
328+
let schema = if let Some(qualifier) = path.qualifier() {
329+
qualifier.syntax().text().to_string()
330+
} else {
331+
function_schema(create_function, binder)?
332+
};
333+
334+
let param_list = create_function.param_list()?;
335+
let params = param_list.syntax().text().to_string();
336+
337+
let ret_type = create_function.ret_type()?;
338+
let return_type = ret_type.syntax().text().to_string();
339+
340+
Some(format!(
341+
"function {}.{}{} {}",
342+
schema, function_name, params, return_type
343+
))
344+
}
345+
346+
fn function_schema(
347+
create_function: &ast::CreateFunction,
348+
binder: &binder::Binder,
349+
) -> Option<String> {
350+
let position = create_function.syntax().text_range().start();
351+
let search_path = binder.search_path_at(position);
352+
search_path.first().map(|s| s.to_string())
353+
}
354+
281355
#[cfg(test)]
282356
mod test {
283357
use crate::hover::hover;
@@ -718,4 +792,68 @@ drop index idx_x$0;
718792
╰╴ ─ hover
719793
");
720794
}
795+
796+
#[test]
797+
fn hover_on_drop_function() {
798+
assert_snapshot!(check_hover("
799+
create function foo() returns int as $$ select 1 $$ language sql;
800+
drop function foo$0();
801+
"), @r"
802+
hover: function public.foo() returns int
803+
╭▸
804+
3 │ drop function foo();
805+
╰╴ ─ hover
806+
");
807+
}
808+
809+
#[test]
810+
fn hover_on_drop_function_with_schema() {
811+
assert_snapshot!(check_hover("
812+
create function myschema.foo() returns int as $$ select 1 $$ language sql;
813+
drop function myschema.foo$0();
814+
"), @r"
815+
hover: function myschema.foo() returns int
816+
╭▸
817+
3 │ drop function myschema.foo();
818+
╰╴ ─ hover
819+
");
820+
}
821+
822+
#[test]
823+
fn hover_on_create_function_definition() {
824+
assert_snapshot!(check_hover("
825+
create function foo$0() returns int as $$ select 1 $$ language sql;
826+
"), @r"
827+
hover: function public.foo() returns int
828+
╭▸
829+
2 │ create function foo() returns int as $$ select 1 $$ language sql;
830+
╰╴ ─ hover
831+
");
832+
}
833+
834+
#[test]
835+
fn hover_on_create_function_with_explicit_schema() {
836+
assert_snapshot!(check_hover("
837+
create function myschema.foo$0() returns int as $$ select 1 $$ language sql;
838+
"), @r"
839+
hover: function myschema.foo() returns int
840+
╭▸
841+
2 │ create function myschema.foo() returns int as $$ select 1 $$ language sql;
842+
╰╴ ─ hover
843+
");
844+
}
845+
846+
#[test]
847+
fn hover_on_drop_function_with_search_path() {
848+
assert_snapshot!(check_hover(r#"
849+
set search_path to myschema;
850+
create function foo() returns int as $$ select 1 $$ language sql;
851+
drop function foo$0();
852+
"#), @r"
853+
hover: function myschema.foo() returns int
854+
╭▸
855+
4 │ drop function foo();
856+
╰╴ ─ hover
857+
");
858+
}
721859
}

crates/squawk_ide/src/resolve.rs

Lines changed: 21 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -84,26 +84,7 @@ fn resolve_table(
8484
schema: &Option<Schema>,
8585
position: TextSize,
8686
) -> Option<SyntaxNodePtr> {
87-
let symbols = binder.scopes[binder.root_scope()].get(table_name)?;
88-
89-
if let Some(schema) = schema {
90-
let symbol_id = symbols.iter().copied().find(|id| {
91-
let symbol = &binder.symbols[*id];
92-
symbol.kind == SymbolKind::Table && &symbol.schema == schema
93-
})?;
94-
return Some(binder.symbols[symbol_id].ptr);
95-
} else {
96-
let search_path = binder.search_path_at(position);
97-
for search_schema in search_path {
98-
if let Some(symbol_id) = symbols.iter().copied().find(|id| {
99-
let symbol = &binder.symbols[*id];
100-
symbol.kind == SymbolKind::Table && &symbol.schema == search_schema
101-
}) {
102-
return Some(binder.symbols[symbol_id].ptr);
103-
}
104-
}
105-
}
106-
None
87+
resolve_for_kind(binder, table_name, schema, position, SymbolKind::Table)
10788
}
10889

10990
fn resolve_index(
@@ -112,20 +93,30 @@ fn resolve_index(
11293
schema: &Option<Schema>,
11394
position: TextSize,
11495
) -> Option<SyntaxNodePtr> {
115-
let symbols = binder.scopes[binder.root_scope()].get(index_name)?;
96+
resolve_for_kind(binder, index_name, schema, position, SymbolKind::Index)
97+
}
98+
99+
fn resolve_for_kind(
100+
binder: &Binder,
101+
name: &Name,
102+
schema: &Option<Schema>,
103+
position: TextSize,
104+
kind: SymbolKind,
105+
) -> Option<SyntaxNodePtr> {
106+
let symbols = binder.scopes[binder.root_scope()].get(name)?;
116107

117108
if let Some(schema) = schema {
118109
let symbol_id = symbols.iter().copied().find(|id| {
119110
let symbol = &binder.symbols[*id];
120-
symbol.kind == SymbolKind::Index && &symbol.schema == schema
111+
symbol.kind == kind && &symbol.schema == schema
121112
})?;
122113
return Some(binder.symbols[symbol_id].ptr);
123114
} else {
124115
let search_path = binder.search_path_at(position);
125116
for search_schema in search_path {
126117
if let Some(symbol_id) = symbols.iter().copied().find(|id| {
127118
let symbol = &binder.symbols[*id];
128-
symbol.kind == SymbolKind::Index && &symbol.schema == search_schema
119+
symbol.kind == kind && &symbol.schema == search_schema
129120
}) {
130121
return Some(binder.symbols[symbol_id].ptr);
131122
}
@@ -140,26 +131,13 @@ fn resolve_function(
140131
schema: &Option<Schema>,
141132
position: TextSize,
142133
) -> Option<SyntaxNodePtr> {
143-
let symbols = binder.scopes[binder.root_scope()].get(function_name)?;
144-
145-
if let Some(schema) = schema {
146-
let symbol_id = symbols.iter().copied().find(|id| {
147-
let symbol = &binder.symbols[*id];
148-
symbol.kind == SymbolKind::Function && &symbol.schema == schema
149-
})?;
150-
return Some(binder.symbols[symbol_id].ptr);
151-
} else {
152-
let search_path = binder.search_path_at(position);
153-
for search_schema in search_path {
154-
if let Some(symbol_id) = symbols.iter().copied().find(|id| {
155-
let symbol = &binder.symbols[*id];
156-
symbol.kind == SymbolKind::Function && &symbol.schema == search_schema
157-
}) {
158-
return Some(binder.symbols[symbol_id].ptr);
159-
}
160-
}
161-
}
162-
None
134+
resolve_for_kind(
135+
binder,
136+
function_name,
137+
schema,
138+
position,
139+
SymbolKind::Function,
140+
)
163141
}
164142

165143
fn resolve_create_index_column(binder: &Binder, name_ref: &ast::NameRef) -> Option<SyntaxNodePtr> {

0 commit comments

Comments
 (0)