diff --git a/Cargo.lock b/Cargo.lock index 3977a60d..a95a0eb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1885,6 +1885,7 @@ dependencies = [ "line-index", "log", "rowan", + "smallvec", "smol_str", "squawk-linter", "squawk-syntax", diff --git a/Cargo.toml b/Cargo.toml index 0ae3deff..60ffb3e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ quote = "1.0.40" xshell = "0.2.7" proc-macro2 = "1.0.95" snapbox = { version = "0.6.0", features = ["diff", "term-svg", "cmd"] } +smallvec = "1.13.2" # local # we have to make the versions explicit otherwise `cargo publish` won't work diff --git a/crates/squawk_ide/Cargo.toml b/crates/squawk_ide/Cargo.toml index 52bc7950..8ee95cfe 100644 --- a/crates/squawk_ide/Cargo.toml +++ b/crates/squawk_ide/Cargo.toml @@ -20,6 +20,7 @@ annotate-snippets.workspace = true log.workspace = true smol_str.workspace = true la-arena.workspace = true +smallvec.workspace = true [dev-dependencies] insta.workspace = true diff --git a/crates/squawk_ide/src/binder.rs b/crates/squawk_ide/src/binder.rs index 2f132737..5caec747 100644 --- a/crates/squawk_ide/src/binder.rs +++ b/crates/squawk_ide/src/binder.rs @@ -92,6 +92,7 @@ fn bind_stmt(b: &mut Binder, stmt: ast::Stmt) { ast::Stmt::CreateTablespace(create_tablespace) => { bind_create_tablespace(b, create_tablespace) } + ast::Stmt::CreateDatabase(create_database) => bind_create_database(b, create_database), ast::Stmt::Set(set) => bind_set(b, set), _ => {} } @@ -384,6 +385,25 @@ fn bind_create_tablespace(b: &mut Binder, create_tablespace: ast::CreateTablespa b.scopes[root].insert(tablespace_name, tablespace_id); } +fn bind_create_database(b: &mut Binder, create_database: ast::CreateDatabase) { + let Some(name) = create_database.name() else { + return; + }; + + let database_name = Name::from_node(&name); + let name_ptr = SyntaxNodePtr::new(name.syntax()); + + let database_id = b.symbols.alloc(Symbol { + kind: SymbolKind::Database, + ptr: name_ptr, + schema: Schema::new("pg_database"), + params: None, + }); + + let root = b.root_scope(); + b.scopes[root].insert(database_name, database_id); +} + fn item_name(path: &ast::Path) -> Option { let segment = path.segment()?; diff --git a/crates/squawk_ide/src/classify.rs b/crates/squawk_ide/src/classify.rs index 96b426aa..a11f4334 100644 --- a/crates/squawk_ide/src/classify.rs +++ b/crates/squawk_ide/src/classify.rs @@ -9,7 +9,9 @@ pub(crate) enum NameRefClass { DropView, DropMaterializedView, DropSequence, + SequenceOwnedByColumn, Tablespace, + DropDatabase, ForeignKeyTable, ForeignKeyColumn, ForeignKeyLocalColumn, @@ -32,6 +34,7 @@ pub(crate) enum NameRefClass { CreateSchema, CreateIndex, CreateIndexColumn, + DefaultConstraintFunctionCall, SelectFunctionCall, SelectFromTable, SelectColumn, @@ -46,6 +49,7 @@ pub(crate) enum NameRefClass { UpdateWhereColumn, UpdateSetColumn, UpdateFromTable, + JoinUsingColumn, SchemaQualifier, TypeReference, } @@ -56,6 +60,7 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option let mut in_column_list = false; let mut in_where_clause = false; let mut in_from_clause = false; + let mut in_on_clause = false; let mut in_set_clause = false; let mut in_constraint_exclusion_list = false; let mut in_constraint_include_clause = false; @@ -83,11 +88,15 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option .is_some(); let mut in_from_clause = false; + let mut in_on_clause = false; for ancestor in parent.ancestors() { + if ast::OnClause::can_cast(ancestor.kind()) { + in_on_clause = true; + } if ast::FromClause::can_cast(ancestor.kind()) { in_from_clause = true; } - if ast::Select::can_cast(ancestor.kind()) && !in_from_clause { + if ast::Select::can_cast(ancestor.kind()) && (!in_from_clause || in_on_clause) { if is_function_call || is_schema_table_col { return Some(NameRefClass::SchemaQualifier); } else { @@ -119,15 +128,19 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option .is_some(); let mut in_from_clause = false; + let mut in_on_clause = false; let mut in_cast_expr = false; for ancestor in parent.ancestors() { + if ast::OnClause::can_cast(ancestor.kind()) { + in_on_clause = true; + } if ast::CastExpr::can_cast(ancestor.kind()) { in_cast_expr = true; } if ast::FromClause::can_cast(ancestor.kind()) { in_from_clause = true; } - if ast::Select::can_cast(ancestor.kind()) && !in_from_clause { + if ast::Select::can_cast(ancestor.kind()) && (!in_from_clause || in_on_clause) { if in_cast_expr { return Some(NameRefClass::TypeReference); } @@ -191,6 +204,15 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option if ast::DropSequence::can_cast(ancestor.kind()) { return Some(NameRefClass::DropSequence); } + if ast::DropDatabase::can_cast(ancestor.kind()) { + return Some(NameRefClass::DropDatabase); + } + if let Some(sequence_option) = ast::SequenceOption::cast(ancestor.clone()) + && sequence_option.owned_token().is_some() + && sequence_option.by_token().is_some() + { + return Some(NameRefClass::SequenceOwnedByColumn); + } if ast::DropTablespace::can_cast(ancestor.kind()) || ast::Tablespace::can_cast(ancestor.kind()) || ast::SetTablespace::can_cast(ancestor.kind()) @@ -296,6 +318,12 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option if ast::CallExpr::can_cast(ancestor.kind()) { in_call_expr = true; } + if ast::DefaultConstraint::can_cast(ancestor.kind()) && in_call_expr && !in_arg_list { + return Some(NameRefClass::DefaultConstraintFunctionCall); + } + if ast::OnClause::can_cast(ancestor.kind()) { + in_on_clause = true; + } if ast::FromClause::can_cast(ancestor.kind()) { in_from_clause = true; } @@ -303,7 +331,7 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option if in_call_expr && !in_arg_list { return Some(NameRefClass::SelectFunctionCall); } - if in_from_clause { + if in_from_clause && !in_on_clause { return Some(NameRefClass::SelectFromTable); } // Classify as SelectColumn for target list, WHERE, ORDER BY, GROUP BY, etc. @@ -331,6 +359,9 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option } return Some(NameRefClass::InsertTable); } + if ast::JoinUsingClause::can_cast(ancestor.kind()) && in_column_list { + return Some(NameRefClass::JoinUsingColumn); + } if ast::WhereClause::can_cast(ancestor.kind()) { in_where_clause = true; } @@ -371,6 +402,7 @@ pub(crate) enum NameClass { CreateIndex(ast::CreateIndex), CreateSequence(ast::CreateSequence), CreateTablespace(ast::CreateTablespace), + CreateDatabase(ast::CreateDatabase), CreateType(ast::CreateType), CreateFunction(ast::CreateFunction), CreateAggregate(ast::CreateAggregate), @@ -411,6 +443,9 @@ pub(crate) fn classify_name(name: &ast::Name) -> Option { if let Some(create_tablespace) = ast::CreateTablespace::cast(ancestor.clone()) { return Some(NameClass::CreateTablespace(create_tablespace)); } + if let Some(create_database) = ast::CreateDatabase::cast(ancestor.clone()) { + return Some(NameClass::CreateDatabase(create_database)); + } if let Some(create_type) = ast::CreateType::cast(ancestor.clone()) { return Some(NameClass::CreateType(create_type)); } diff --git a/crates/squawk_ide/src/find_references.rs b/crates/squawk_ide/src/find_references.rs index 18e719ce..5cd928a9 100644 --- a/crates/squawk_ide/src/find_references.rs +++ b/crates/squawk_ide/src/find_references.rs @@ -20,8 +20,8 @@ pub fn find_references(file: &ast::SourceFile, offset: TextSize) -> Vec { - if let Some(found) = resolve::resolve_name_ref(&binder, &name_ref) - && found == target + if let Some(found_refs) = resolve::resolve_name_ref(&binder, &name_ref) + && found_refs.contains(&target) { refs.push(name_ref.syntax().text_range()); } @@ -49,10 +49,12 @@ fn find_target(file: &ast::SourceFile, offset: TextSize, binder: &Binder) -> Opt return Some(SyntaxNodePtr::new(name.syntax())); } - if let Some(name_ref) = ast::NameRef::cast(parent.clone()) - && let Some(ptr) = resolve::resolve_name_ref(binder, &name_ref) - { - return Some(ptr); + if let Some(name_ref) = ast::NameRef::cast(parent.clone()) { + // TODO: I think we want to return a list of targets so we can support cases like: + // select * from t join u using (id); + // ^ find refs + return resolve::resolve_name_ref(binder, &name_ref) + .and_then(|ptrs| ptrs.into_iter().next()); } None diff --git a/crates/squawk_ide/src/goto_definition.rs b/crates/squawk_ide/src/goto_definition.rs index 605f1734..fa815f1e 100644 --- a/crates/squawk_ide/src/goto_definition.rs +++ b/crates/squawk_ide/src/goto_definition.rs @@ -2,14 +2,19 @@ use crate::binder; use crate::offsets::token_from_offset; use crate::resolve; use rowan::{TextRange, TextSize}; +use smallvec::{SmallVec, smallvec}; use squawk_syntax::{ SyntaxKind, ast::{self, AstNode}, }; -pub fn goto_definition(file: ast::SourceFile, offset: TextSize) -> Option { - let token = token_from_offset(&file, offset)?; - let parent = token.parent()?; +pub fn goto_definition(file: ast::SourceFile, offset: TextSize) -> SmallVec<[TextRange; 1]> { + let Some(token) = token_from_offset(&file, offset) else { + return smallvec![]; + }; + let Some(parent) = token.parent() else { + return smallvec![]; + }; // goto def on case exprs if (token.kind() == SyntaxKind::WHEN_KW && parent.kind() == SyntaxKind::WHEN_CLAUSE) @@ -20,7 +25,7 @@ pub fn goto_definition(file: ast::SourceFile, offset: TextSize) -> Option Option BEGIN/START TRANSACTION if ast::Commit::can_cast(parent.kind()) { if let Some(begin_range) = find_preceding_begin(&file, token.text_range().start()) { - return Some(begin_range); + return smallvec![begin_range]; } } // goto def on ROLLBACK -> BEGIN/START TRANSACTION if ast::Rollback::can_cast(parent.kind()) { if let Some(begin_range) = find_preceding_begin(&file, token.text_range().start()) { - return Some(begin_range); + return smallvec![begin_range]; } } @@ -43,23 +48,25 @@ pub fn goto_definition(file: ast::SourceFile, offset: TextSize) -> Option Option { @@ -113,22 +120,26 @@ mod test { let parse = ast::SourceFile::parse(&sql); assert_eq!(parse.errors(), vec![]); let file: ast::SourceFile = parse.tree(); - if let Some(result) = goto_definition(file, offset) { + let results = goto_definition(file, offset); + if !results.is_empty() { let offset: usize = offset.into(); - let group = Level::INFO.primary_title("definition").element( - Snippet::source(&sql) - .fold(true) - .annotation( - AnnotationKind::Context - .span(result.into()) - .label("2. destination"), - ) - .annotation( - AnnotationKind::Context - .span(offset..offset + 1) - .label("1. source"), - ), + let mut snippet = Snippet::source(&sql).fold(true); + + for (i, result) in results.iter().enumerate() { + snippet = snippet.annotation( + AnnotationKind::Context + .span((*result).into()) + .label(format!("{}. destination", i + 2)), + ); + } + + snippet = snippet.annotation( + AnnotationKind::Context + .span(offset..offset + 1) + .label("1. source"), ); + + let group = Level::INFO.primary_title("definition").element(snippet); let renderer = Renderer::plain().decor_style(DecorStyle::Unicode); return Some( renderer @@ -246,6 +257,22 @@ drop sequence s$0; "); } + #[test] + fn goto_create_sequence_owned_by() { + assert_snapshot!(goto(" +create table t(c serial); +create sequence s + owned by t.c$0; +"), @r" + ╭▸ + 2 │ create table t(c serial); + │ ─ 2. destination + 3 │ create sequence s + 4 │ owned by t.c; + ╰╴ ─ 1. source + "); + } + #[test] fn goto_drop_tablespace() { assert_snapshot!(goto(" @@ -274,6 +301,48 @@ create table t (a int) tablespace b$0ar; "); } + #[test] + fn goto_drop_database() { + assert_snapshot!(goto(" +create database mydb; +drop database my$0db; +"), @r" + ╭▸ + 2 │ create database mydb; + │ ──── 2. destination + 3 │ drop database mydb; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_drop_database_defined_after() { + assert_snapshot!(goto(" +drop database my$0db; +create database mydb; +"), @r" + ╭▸ + 2 │ drop database mydb; + │ ─ 1. source + 3 │ create database mydb; + ╰╴ ──── 2. destination + "); + } + + #[test] + fn goto_database_definition_returns_self() { + assert_snapshot!(goto(" +create database my$0db; +"), @r" + ╭▸ + 2 │ create database mydb; + │ ┬┬── + │ ││ + │ │1. source + ╰╴ 2. destination + "); + } + #[test] fn goto_drop_sequence_with_schema() { assert_snapshot!(goto(" @@ -1941,6 +2010,23 @@ select foo$0(1); "); } + #[test] + fn goto_default_constraint_function_call() { + assert_snapshot!(goto(" +create function f() returns int as 'select 1' language sql; +create table t( + a int default f$0() +); +"), @r" + ╭▸ + 2 │ create function f() returns int as 'select 1' language sql; + │ ─ 2. destination + 3 │ create table t( + 4 │ a int default f() + ╰╴ ─ 1. source + "); + } + #[test] fn goto_select_function_call_with_schema() { assert_snapshot!(goto(" @@ -3942,6 +4028,53 @@ select messages.message$0 from users left join messages on users.id = messages.u "); } + #[test] + fn goto_join_on_table_qualifier() { + assert_snapshot!(goto(" +create table t(a int); +create table u(a int); +select * from t join u on u$0.a = t.a; +"), @r" + ╭▸ + 3 │ create table u(a int); + │ ─ 2. destination + 4 │ select * from t join u on u.a = t.a; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_join_on_column() { + assert_snapshot!(goto(" +create table t(a int); +create table u(a int); +select * from t join u on u.a$0 = t.a; +"), @r" + ╭▸ + 3 │ create table u(a int); + │ ─ 2. destination + 4 │ select * from t join u on u.a = t.a; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_join_using_column() { + assert_snapshot!(goto(" +create table t(a int); +create table u(a int); +select * from t join u using (a$0); +"), @r" + ╭▸ + 2 │ create table t(a int); + │ ─ 2. destination + 3 │ create table u(a int); + │ ─ 3. destination + 4 │ select * from t join u using (a); + ╰╴ ─ 1. source + "); + } + #[test] fn goto_insert_select_cte_column() { assert_snapshot!(goto(" diff --git a/crates/squawk_ide/src/hover.rs b/crates/squawk_ide/src/hover.rs index a91938c7..f9fb9e5f 100644 --- a/crates/squawk_ide/src/hover.rs +++ b/crates/squawk_ide/src/hover.rs @@ -25,7 +25,8 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option { | NameRefClass::PrimaryKeyConstraintColumn | NameRefClass::NotNullConstraintColumn | NameRefClass::ExcludeConstraintColumn - | NameRefClass::PartitionByColumn => { + | NameRefClass::PartitionByColumn + | NameRefClass::JoinUsingColumn => { return hover_column(file, &name_ref, &binder); } NameRefClass::TypeReference | NameRefClass::DropType => { @@ -63,10 +64,13 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option { | NameRefClass::PartitionOfTable => { return hover_table(file, &name_ref, &binder); } - NameRefClass::ForeignKeyColumn | NameRefClass::ForeignKeyLocalColumn => { + NameRefClass::ForeignKeyColumn + | NameRefClass::ForeignKeyLocalColumn + | NameRefClass::SequenceOwnedByColumn => { return hover_column(file, &name_ref, &binder); } NameRefClass::DropSequence => return hover_sequence(file, &name_ref, &binder), + NameRefClass::DropDatabase => return hover_database(file, &name_ref, &binder), NameRefClass::Tablespace => return hover_tablespace(file, &name_ref, &binder), NameRefClass::DropIndex => return hover_index(file, &name_ref, &binder), NameRefClass::DropFunction => return hover_function(file, &name_ref, &binder), @@ -75,6 +79,9 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option { return hover_procedure(file, &name_ref, &binder); } NameRefClass::DropRoutine => return hover_routine(file, &name_ref, &binder), + NameRefClass::DefaultConstraintFunctionCall => { + return hover_function(file, &name_ref, &binder); + } NameRefClass::SelectFunctionCall => { // Try function first, but fall back to column if no function found // (handles function-call-style column access like `select a(t)`) @@ -111,6 +118,9 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option { NameClass::CreateTablespace(create_tablespace) => { return format_create_tablespace(&create_tablespace); } + NameClass::CreateDatabase(create_database) => { + return format_create_database(&create_database); + } NameClass::CreateType(create_type) => { return format_create_type(&create_type, &binder); } @@ -152,13 +162,31 @@ fn hover_column( name_ref: &ast::NameRef, binder: &binder::Binder, ) -> Option { - let column_ptr = resolve::resolve_name_ref(binder, name_ref)?; + let column_ptrs = resolve::resolve_name_ref(binder, name_ref)?; let root = file.syntax(); + let results: Vec = column_ptrs + .iter() + .filter_map(|column_ptr| format_hover_for_column_ptr(binder, root, column_ptr, name_ref)) + .collect(); + + if results.is_empty() { + return None; + } + + Some(results.join("\n")) +} + +fn format_hover_for_column_ptr( + binder: &binder::Binder, + root: &squawk_syntax::SyntaxNode, + column_ptr: &squawk_syntax::SyntaxNodePtr, + name_ref: &ast::NameRef, +) -> Option { let column_name_node = column_ptr.to_node(root); if let Some(with_table) = column_name_node.ancestors().find_map(ast::WithTable::cast) { - let cte_name = with_table.name()?.syntax().text().to_string(); + let cte_name = with_table.name()?; let column_name = if column_name_node .ancestors() .any(|a| ast::Values::can_cast(a.kind())) @@ -167,7 +195,11 @@ fn hover_column( } else { Name::from_string(column_name_node.text().to_string()) }; - return Some(format!("column {}.{}", cte_name, column_name)); + return Some(format!( + "column {}.{}", + cte_name.syntax().text(), + column_name + )); } // create view v(a) as select 1; @@ -181,7 +213,7 @@ fn hover_column( } let column = column_name_node.ancestors().find_map(ast::Column::cast)?; - let column_name = column.name()?.syntax().text().to_string(); + let column_name = column.name()?; let ty = column.ty()?; let create_table = column @@ -189,7 +221,7 @@ fn hover_column( .ancestors() .find_map(ast::CreateTable::cast)?; let path = create_table.path()?; - let table_name = path.segment()?.name()?.syntax().text().to_string(); + let table_name = path.segment()?.name()?; let schema = if let Some(qualifier) = path.qualifier() { qualifier.syntax().text().to_string() @@ -199,8 +231,8 @@ fn hover_column( Some(format_column( &schema, - &table_name, - &column_name, + &table_name.syntax().text().to_string(), + &column_name.syntax().text().to_string(), &ty.syntax().text(), )) } @@ -210,7 +242,9 @@ fn hover_composite_type_field( name_ref: &ast::NameRef, binder: &binder::Binder, ) -> Option { - let field_ptr = resolve::resolve_name_ref(binder, name_ref)?; + let field_ptr = resolve::resolve_name_ref(binder, name_ref)? + .into_iter() + .next()?; let root = file.syntax(); let field_name_node = field_ptr.to_node(root); @@ -269,7 +303,9 @@ fn hover_table( name_ref: &ast::NameRef, binder: &binder::Binder, ) -> Option { - let table_ptr = resolve::resolve_name_ref(binder, name_ref)?; + let table_ptr = resolve::resolve_name_ref(binder, name_ref)? + .into_iter() + .next()?; let root = file.syntax(); let table_name_node = table_ptr.to_node(root); @@ -297,7 +333,9 @@ fn hover_index( name_ref: &ast::NameRef, binder: &binder::Binder, ) -> Option { - let index_ptr = resolve::resolve_name_ref(binder, name_ref)?; + let index_ptr = resolve::resolve_name_ref(binder, name_ref)? + .into_iter() + .next()?; let root = file.syntax(); let index_name_node = index_ptr.to_node(root); @@ -314,7 +352,9 @@ fn hover_sequence( name_ref: &ast::NameRef, binder: &binder::Binder, ) -> Option { - let sequence_ptr = resolve::resolve_name_ref(binder, name_ref)?; + let sequence_ptr = resolve::resolve_name_ref(binder, name_ref)? + .into_iter() + .next()?; let root = file.syntax(); let sequence_name_node = sequence_ptr.to_node(root); @@ -331,18 +371,35 @@ fn hover_tablespace( name_ref: &ast::NameRef, binder: &binder::Binder, ) -> Option { - let tablespace_ptr = resolve::resolve_name_ref(binder, name_ref)?; + let tablespace_ptr = resolve::resolve_name_ref(binder, name_ref)? + .into_iter() + .next()?; let root = file.syntax(); let tablespace_name_node = tablespace_ptr.to_node(root); Some(format!("tablespace {}", tablespace_name_node.text())) } +fn hover_database( + file: &ast::SourceFile, + name_ref: &ast::NameRef, + binder: &binder::Binder, +) -> Option { + let database_ptr = resolve::resolve_name_ref(binder, name_ref)? + .into_iter() + .next()?; + let root = file.syntax(); + let database_name_node = database_ptr.to_node(root); + Some(format!("database {}", database_name_node.text())) +} + fn hover_type( file: &ast::SourceFile, name_ref: &ast::NameRef, binder: &binder::Binder, ) -> Option { - let type_ptr = resolve::resolve_name_ref(binder, name_ref)?; + let type_ptr = resolve::resolve_name_ref(binder, name_ref)? + .into_iter() + .next()?; let root = file.syntax(); let type_name_node = type_ptr.to_node(root); @@ -494,6 +551,11 @@ fn format_create_tablespace(create_tablespace: &ast::CreateTablespace) -> Option Some(format!("tablespace {}", name)) } +fn format_create_database(create_database: &ast::CreateDatabase) -> Option { + let name = create_database.name()?.syntax().text().to_string(); + Some(format!("database {}", name)) +} + fn index_schema(create_index: &ast::CreateIndex, binder: &binder::Binder) -> Option { let position = create_index.syntax().text_range().start(); let search_path = binder.search_path_at(position); @@ -543,7 +605,9 @@ fn hover_schema( name_ref: &ast::NameRef, binder: &binder::Binder, ) -> Option { - let schema_ptr = resolve::resolve_name_ref(binder, name_ref)?; + let schema_ptr = resolve::resolve_name_ref(binder, name_ref)? + .into_iter() + .next()?; let root = file.syntax(); let schema_name_node = schema_ptr.to_node(root); @@ -577,7 +641,9 @@ fn hover_function( name_ref: &ast::NameRef, binder: &binder::Binder, ) -> Option { - let function_ptr = resolve::resolve_name_ref(binder, name_ref)?; + let function_ptr = resolve::resolve_name_ref(binder, name_ref)? + .into_iter() + .next()?; let root = file.syntax(); let function_name_node = function_ptr.to_node(root); @@ -630,7 +696,9 @@ fn hover_aggregate( name_ref: &ast::NameRef, binder: &binder::Binder, ) -> Option { - let aggregate_ptr = resolve::resolve_name_ref(binder, name_ref)?; + let aggregate_ptr = resolve::resolve_name_ref(binder, name_ref)? + .into_iter() + .next()?; let root = file.syntax(); let aggregate_name_node = aggregate_ptr.to_node(root); @@ -677,7 +745,9 @@ fn hover_procedure( name_ref: &ast::NameRef, binder: &binder::Binder, ) -> Option { - let procedure_ptr = resolve::resolve_name_ref(binder, name_ref)?; + let procedure_ptr = resolve::resolve_name_ref(binder, name_ref)? + .into_iter() + .next()?; let root = file.syntax(); let procedure_name_node = procedure_ptr.to_node(root); @@ -724,7 +794,9 @@ fn hover_routine( name_ref: &ast::NameRef, binder: &binder::Binder, ) -> Option { - let routine_ptr = resolve::resolve_name_ref(binder, name_ref)?; + let routine_ptr = resolve::resolve_name_ref(binder, name_ref)? + .into_iter() + .next()?; let root = file.syntax(); let routine_name_node = routine_ptr.to_node(root); @@ -2788,4 +2860,19 @@ select ((((member))).name$0) from team; ╰╴ ─ hover "); } + + #[test] + fn hover_on_join_using_column() { + assert_snapshot!(check_hover(" +create table t(id int); +create table u(id int); +select * from t join u using (id$0); +"), @r" + hover: column public.t.id int + column public.u.id int + ╭▸ + 4 │ select * from t join u using (id); + ╰╴ ─ hover + "); + } } diff --git a/crates/squawk_ide/src/inlay_hints.rs b/crates/squawk_ide/src/inlay_hints.rs index 94eecf58..d533b51d 100644 --- a/crates/squawk_ide/src/inlay_hints.rs +++ b/crates/squawk_ide/src/inlay_hints.rs @@ -50,7 +50,9 @@ fn inlay_hint_call_expr( ast::FieldExpr::cast(expr.syntax().clone())?.field()? }; - let function_ptr = resolve::resolve_name_ref(binder, &name_ref)?; + let function_ptr = resolve::resolve_name_ref(binder, &name_ref)? + .into_iter() + .next()?; let root = file.syntax(); let function_name_node = function_ptr.to_node(root); diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index 06b06829..e854d1e7 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -1,4 +1,5 @@ use rowan::TextSize; +use smallvec::{SmallVec, smallvec}; use squawk_syntax::{ SyntaxNode, SyntaxNodePtr, ast::{self, AstNode}, @@ -10,7 +11,10 @@ use crate::column_name::ColumnName; pub(crate) use crate::symbols::Schema; use crate::symbols::{Name, SymbolKind}; -pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Option { +pub(crate) fn resolve_name_ref( + binder: &Binder, + name_ref: &ast::NameRef, +) -> Option> { let context = classify_name_ref(name_ref)?; match context { @@ -24,7 +28,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti let table_name = extract_table_name(&path)?; let schema = extract_schema_name(&path); let position = name_ref.syntax().text_range().start(); - resolve_table(binder, &table_name, &schema, position) + resolve_table(binder, &table_name, &schema, position).map(|ptr| smallvec![ptr]) } NameRefClass::SelectFromTable => { let table_name = Name::from_node(name_ref); @@ -41,23 +45,23 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti if schema.is_none() && let Some(cte_ptr) = resolve_cte_table(name_ref, &table_name) { - return Some(cte_ptr); + return Some(smallvec![cte_ptr]); } let position = name_ref.syntax().text_range().start(); if let Some(ptr) = resolve_table(binder, &table_name, &schema, position) { - return Some(ptr); + return Some(smallvec![ptr]); } - resolve_view(binder, &table_name, &schema, position) + resolve_view(binder, &table_name, &schema, position).map(|ptr| smallvec![ptr]) } NameRefClass::DropIndex => { let path = find_containing_path(name_ref)?; let index_name = extract_table_name(&path)?; let schema = extract_schema_name(&path); let position = name_ref.syntax().text_range().start(); - resolve_index(binder, &index_name, &schema, position) + resolve_index(binder, &index_name, &schema, position).map(|ptr| smallvec![ptr]) } NameRefClass::DropType | NameRefClass::TypeReference => { let (type_name, schema) = if let Some(parent) = name_ref.syntax().parent() @@ -82,25 +86,39 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti (type_name, schema) }; let position = name_ref.syntax().text_range().start(); - resolve_type(binder, &type_name, &schema, position) + resolve_type(binder, &type_name, &schema, position).map(|ptr| smallvec![ptr]) } NameRefClass::DropView | NameRefClass::DropMaterializedView => { let path = find_containing_path(name_ref)?; let view_name = extract_table_name(&path)?; let schema = extract_schema_name(&path); let position = name_ref.syntax().text_range().start(); - resolve_view(binder, &view_name, &schema, position) + resolve_view(binder, &view_name, &schema, position).map(|ptr| smallvec![ptr]) } NameRefClass::DropSequence => { let path = find_containing_path(name_ref)?; let sequence_name = extract_table_name(&path)?; let schema = extract_schema_name(&path); let position = name_ref.syntax().text_range().start(); - resolve_sequence(binder, &sequence_name, &schema, position) + resolve_sequence(binder, &sequence_name, &schema, position).map(|ptr| smallvec![ptr]) + } + NameRefClass::DropDatabase => { + let database_name = Name::from_node(name_ref); + resolve_database(binder, &database_name).map(|ptr| smallvec![ptr]) + } + NameRefClass::SequenceOwnedByColumn => { + let sequence_option = name_ref + .syntax() + .ancestors() + .find_map(ast::SequenceOption::cast)?; + let path = sequence_option.path()?; + let column_name = Name::from_node(name_ref); + let table_path = path.qualifier()?; + resolve_column_for_path(binder, &table_path, column_name).map(|ptr| smallvec![ptr]) } NameRefClass::Tablespace => { let tablespace_name = Name::from_node(name_ref); - resolve_tablespace(binder, &tablespace_name) + resolve_tablespace(binder, &tablespace_name).map(|ptr| smallvec![ptr]) } NameRefClass::ForeignKeyTable => { let foreign_key = name_ref @@ -111,7 +129,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti let table_name = extract_table_name(&path)?; let schema = extract_schema_name(&path); let position = name_ref.syntax().text_range().start(); - resolve_table(binder, &table_name, &schema, position) + resolve_table(binder, &table_name, &schema, position).map(|ptr| smallvec![ptr]) } NameRefClass::ForeignKeyColumn => { let foreign_key = name_ref @@ -120,7 +138,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti .find_map(ast::ForeignKeyConstraint::cast)?; let path = foreign_key.path()?; let column_name = Name::from_node(name_ref); - resolve_column_for_path(binder, &path, column_name) + resolve_column_for_path(binder, &path, column_name).map(|ptr| smallvec![ptr]) } NameRefClass::ForeignKeyLocalColumn => { let create_table = name_ref @@ -128,7 +146,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti .ancestors() .find_map(ast::CreateTable::cast)?; let column_name = Name::from_node(name_ref); - find_column_in_create_table(&create_table, &column_name) + find_column_in_create_table(&create_table, &column_name).map(|ptr| smallvec![ptr]) } NameRefClass::CheckConstraintColumn => { let create_table = name_ref @@ -136,7 +154,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti .ancestors() .find_map(ast::CreateTable::cast)?; let column_name = Name::from_node(name_ref); - find_column_in_create_table(&create_table, &column_name) + find_column_in_create_table(&create_table, &column_name).map(|ptr| smallvec![ptr]) } NameRefClass::GeneratedColumn => { let create_table = name_ref @@ -144,7 +162,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti .ancestors() .find_map(ast::CreateTable::cast)?; let column_name = Name::from_node(name_ref); - find_column_in_create_table(&create_table, &column_name) + find_column_in_create_table(&create_table, &column_name).map(|ptr| smallvec![ptr]) } NameRefClass::UniqueConstraintColumn => { let create_table = name_ref @@ -152,7 +170,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti .ancestors() .find_map(ast::CreateTable::cast)?; let column_name = Name::from_node(name_ref); - find_column_in_create_table(&create_table, &column_name) + find_column_in_create_table(&create_table, &column_name).map(|ptr| smallvec![ptr]) } NameRefClass::PrimaryKeyConstraintColumn => { let create_table = name_ref @@ -160,7 +178,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti .ancestors() .find_map(ast::CreateTable::cast)?; let column_name = Name::from_node(name_ref); - find_column_in_create_table(&create_table, &column_name) + find_column_in_create_table(&create_table, &column_name).map(|ptr| smallvec![ptr]) } NameRefClass::NotNullConstraintColumn => { let create_table = name_ref @@ -168,7 +186,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti .ancestors() .find_map(ast::CreateTable::cast)?; let column_name = Name::from_node(name_ref); - find_column_in_create_table(&create_table, &column_name) + find_column_in_create_table(&create_table, &column_name).map(|ptr| smallvec![ptr]) } NameRefClass::ExcludeConstraintColumn => { let create_table = name_ref @@ -176,7 +194,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti .ancestors() .find_map(ast::CreateTable::cast)?; let column_name = Name::from_node(name_ref); - find_column_in_create_table(&create_table, &column_name) + find_column_in_create_table(&create_table, &column_name).map(|ptr| smallvec![ptr]) } NameRefClass::PartitionByColumn => { let create_table = name_ref @@ -184,14 +202,14 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti .ancestors() .find_map(ast::CreateTable::cast)?; let column_name = Name::from_node(name_ref); - find_column_in_create_table(&create_table, &column_name) + find_column_in_create_table(&create_table, &column_name).map(|ptr| smallvec![ptr]) } NameRefClass::PartitionOfTable => { let path = find_containing_path(name_ref)?; let table_name = extract_table_name(&path)?; let schema = extract_schema_name(&path); let position = name_ref.syntax().text_range().start(); - resolve_table(binder, &table_name, &schema, position) + resolve_table(binder, &table_name, &schema, position).map(|ptr| smallvec![ptr]) } NameRefClass::LikeTable => { let like_clause = name_ref @@ -202,14 +220,14 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti let table_name = extract_table_name(&path)?; let schema = extract_schema_name(&path); let position = name_ref.syntax().text_range().start(); - resolve_table(binder, &table_name, &schema, position) + resolve_table(binder, &table_name, &schema, position).map(|ptr| smallvec![ptr]) } NameRefClass::InheritsTable => { let path = find_containing_path(name_ref)?; let table_name = extract_table_name(&path)?; let schema = extract_schema_name(&path); let position = name_ref.syntax().text_range().start(); - resolve_table(binder, &table_name, &schema, position) + resolve_table(binder, &table_name, &schema, position).map(|ptr| smallvec![ptr]) } NameRefClass::DropFunction => { let function_sig = name_ref @@ -222,6 +240,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti let params = extract_param_signature(&function_sig); let position = name_ref.syntax().text_range().start(); resolve_function(binder, &function_name, &schema, params.as_deref(), position) + .map(|ptr| smallvec![ptr]) } NameRefClass::DropAggregate => { let aggregate = name_ref @@ -240,6 +259,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti params.as_deref(), position, ) + .map(|ptr| smallvec![ptr]) } NameRefClass::DropProcedure => { let function_sig = name_ref @@ -258,6 +278,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti params.as_deref(), position, ) + .map(|ptr| smallvec![ptr]) } NameRefClass::DropRoutine => { let function_sig = name_ref @@ -273,16 +294,17 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti if let Some(ptr) = resolve_function(binder, &routine_name, &schema, params.as_deref(), position) { - return Some(ptr); + return Some(smallvec![ptr]); } if let Some(ptr) = resolve_aggregate(binder, &routine_name, &schema, params.as_deref(), position) { - return Some(ptr); + return Some(smallvec![ptr]); } resolve_procedure(binder, &routine_name, &schema, params.as_deref(), position) + .map(|ptr| smallvec![ptr]) } NameRefClass::CallProcedure => { let call = name_ref.syntax().ancestors().find_map(ast::Call::cast)?; @@ -291,10 +313,26 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti let schema = extract_schema_name(&path); let position = name_ref.syntax().text_range().start(); resolve_procedure(binder, &procedure_name, &schema, None, position) + .map(|ptr| smallvec![ptr]) } NameRefClass::DropSchema | NameRefClass::SchemaQualifier | NameRefClass::CreateSchema => { let schema_name = Name::from_node(name_ref); - resolve_schema(binder, &schema_name) + resolve_schema(binder, &schema_name).map(|ptr| smallvec![ptr]) + } + NameRefClass::DefaultConstraintFunctionCall => { + let schema = if let Some(parent_node) = name_ref.syntax().parent() + && let Some(field_expr) = ast::FieldExpr::cast(parent_node) + { + let base = field_expr.base()?; + let schema_name_ref = ast::NameRef::cast(base.syntax().clone())?; + Some(Schema(Name::from_node(&schema_name_ref))) + } else { + None + }; + let function_name = Name::from_node(name_ref); + let position = name_ref.syntax().text_range().start(); + resolve_function(binder, &function_name, &schema, None, position) + .map(|ptr| smallvec![ptr]) } NameRefClass::SelectFunctionCall => { let schema = if let Some(parent_node) = name_ref.syntax().parent() @@ -311,12 +349,12 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti // functions take precedence if let Some(ptr) = resolve_function(binder, &function_name, &schema, None, position) { - return Some(ptr); + return Some(smallvec![ptr]); } // aggregates take precedence over function-call-style column access if let Some(ptr) = resolve_aggregate(binder, &function_name, &schema, None, position) { - return Some(ptr); + return Some(smallvec![ptr]); } // if no function found, check if this is function-call-style column access @@ -327,23 +365,36 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti if schema.is_none() && let Some(ptr) = resolve_fn_call_column(binder, name_ref) { - return Some(ptr); + return Some(smallvec![ptr]); } None } - NameRefClass::CreateIndexColumn => resolve_create_index_column(binder, name_ref), - NameRefClass::SelectColumn => resolve_select_column(binder, name_ref), + NameRefClass::CreateIndexColumn => { + resolve_create_index_column(binder, name_ref).map(|ptr| smallvec![ptr]) + } + NameRefClass::SelectColumn => { + resolve_select_column(binder, name_ref).map(|ptr| smallvec![ptr]) + } NameRefClass::SelectQualifiedColumnTable => { - resolve_select_qualified_column_table(binder, name_ref) + resolve_select_qualified_column_table(binder, name_ref).map(|ptr| smallvec![ptr]) + } + NameRefClass::SelectQualifiedColumn => { + resolve_select_qualified_column(binder, name_ref).map(|ptr| smallvec![ptr]) + } + NameRefClass::CompositeTypeField => { + resolve_composite_type_field(binder, name_ref).map(|ptr| smallvec![ptr]) + } + NameRefClass::InsertColumn => { + resolve_insert_column(binder, name_ref).map(|ptr| smallvec![ptr]) + } + NameRefClass::DeleteWhereColumn => { + resolve_delete_where_column(binder, name_ref).map(|ptr| smallvec![ptr]) } - NameRefClass::SelectQualifiedColumn => resolve_select_qualified_column(binder, name_ref), - NameRefClass::CompositeTypeField => resolve_composite_type_field(binder, name_ref), - NameRefClass::InsertColumn => resolve_insert_column(binder, name_ref), - NameRefClass::DeleteWhereColumn => resolve_delete_where_column(binder, name_ref), NameRefClass::UpdateWhereColumn | NameRefClass::UpdateSetColumn => { - resolve_update_where_column(binder, name_ref) + resolve_update_where_column(binder, name_ref).map(|ptr| smallvec![ptr]) } + NameRefClass::JoinUsingColumn => resolve_join_using_columns(binder, name_ref), NameRefClass::UpdateFromTable => { let table_name = Name::from_node(name_ref); let schema = if let Some(parent) = name_ref.syntax().parent() @@ -359,16 +410,16 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti if schema.is_none() && let Some(cte_ptr) = resolve_cte_table(name_ref, &table_name) { - return Some(cte_ptr); + return Some(smallvec![cte_ptr]); } let position = name_ref.syntax().text_range().start(); if let Some(ptr) = resolve_table(binder, &table_name, &schema, position) { - return Some(ptr); + return Some(smallvec![ptr]); } - resolve_view(binder, &table_name, &schema, position) + resolve_view(binder, &table_name, &schema, position).map(|ptr| smallvec![ptr]) } } } @@ -433,6 +484,15 @@ fn resolve_tablespace(binder: &Binder, tablespace_name: &Name) -> Option Option { + let symbols = binder.scopes[binder.root_scope()].get(database_name)?; + let symbol_id = symbols.iter().copied().find(|id| { + let symbol = &binder.symbols[*id]; + symbol.kind == SymbolKind::Database + })?; + Some(binder.symbols[symbol_id].ptr) +} + fn resolve_for_kind( binder: &Binder, name: &Name, @@ -701,9 +761,7 @@ fn resolve_select_qualified_column( { ( Name::from_node(&table_field), - Some(Schema(Name::from_node( - &schema_name_ref - ))), + Some(Schema(Name::from_node(&schema_name_ref))), ) } else { return None; @@ -942,6 +1000,48 @@ fn resolve_delete_where_column(binder: &Binder, name_ref: &ast::NameRef) -> Opti resolve_column_for_path(binder, &path, column_name) } +fn resolve_join_using_columns( + binder: &Binder, + name_ref: &ast::NameRef, +) -> Option> { + let join_expr = name_ref + .syntax() + .ancestors() + .find_map(ast::JoinExpr::cast)?; + + let mut results: SmallVec<[SyntaxNodePtr; 1]> = SmallVec::new(); + + collect_from_join_expr(&join_expr, &mut results, &|from_item: &ast::FromItem| { + resolve_from_item_for_column(binder, from_item, name_ref) + }); + + (!results.is_empty()).then_some(results) +} + +fn collect_from_join_expr( + join_expr: &ast::JoinExpr, + results: &mut SmallVec<[SyntaxNodePtr; 1]>, + try_resolve: &F, +) where + F: Fn(&ast::FromItem) -> Option, +{ + if let Some(nested_join) = join_expr.join_expr() { + collect_from_join_expr(&nested_join, results, try_resolve); + } + if let Some(from_item) = join_expr.from_item() + && let Some(result) = try_resolve(&from_item) + { + results.push(result); + } + if let Some(join) = join_expr.join() + && let Some(from_item) = join.from_item() + { + if let Some(result) = try_resolve(&from_item) { + results.push(result); + } + } +} + fn resolve_update_where_column(binder: &Binder, name_ref: &ast::NameRef) -> Option { let column_name = Name::from_node(name_ref); diff --git a/crates/squawk_ide/src/symbols.rs b/crates/squawk_ide/src/symbols.rs index bbc5e93b..954069da 100644 --- a/crates/squawk_ide/src/symbols.rs +++ b/crates/squawk_ide/src/symbols.rs @@ -54,6 +54,7 @@ pub(crate) enum SymbolKind { View, Sequence, Tablespace, + Database, } #[derive(Clone, Debug)] diff --git a/crates/squawk_server/src/lib.rs b/crates/squawk_server/src/lib.rs index f10bfd53..42dad503 100644 --- a/crates/squawk_server/src/lib.rs +++ b/crates/squawk_server/src/lib.rs @@ -178,20 +178,37 @@ fn handle_goto_definition( let line_index = LineIndex::new(content); let offset = lsp_utils::offset(&line_index, position).unwrap(); - let range = goto_definition(file, offset); - - let result = match range { - Some(target_range) => { - debug_assert!( - !target_range.contains(offset), - "Our target destination range must not include the source range otherwise go to def won't work in vscode." - ); - GotoDefinitionResponse::Scalar(Location { - uri: uri.clone(), - range: lsp_utils::range(&line_index, target_range), - }) - } - None => GotoDefinitionResponse::Array(vec![]), + let ranges = goto_definition(file, offset); + + let result = if ranges.is_empty() { + GotoDefinitionResponse::Array(vec![]) + } else if ranges.len() == 1 { + // TODO: can we just always use the array response? + let target_range = ranges[0]; + debug_assert!( + !target_range.contains(offset), + "Our target destination range must not include the source range otherwise go to def won't work in vscode." + ); + GotoDefinitionResponse::Scalar(Location { + uri: uri.clone(), + range: lsp_utils::range(&line_index, target_range), + }) + } else { + GotoDefinitionResponse::Array( + ranges + .into_iter() + .map(|target_range| { + debug_assert!( + !target_range.contains(offset), + "Our target destination range must not include the source range otherwise go to def won't work in vscode." + ); + Location { + uri: uri.clone(), + range: lsp_utils::range(&line_index, target_range), + } + }) + .collect(), + ) }; let resp = Response { diff --git a/crates/squawk_wasm/src/lib.rs b/crates/squawk_wasm/src/lib.rs index 8e5be107..b7250633 100644 --- a/crates/squawk_wasm/src/lib.rs +++ b/crates/squawk_wasm/src/lib.rs @@ -193,23 +193,26 @@ pub fn goto_definition(content: String, line: u32, col: u32) -> Result = result + .into_iter() + .map(|range| { + let start = line_index.line_col(range.start()); + let end = line_index.line_col(range.end()); + let start_wide = line_index + .to_wide(line_index::WideEncoding::Utf16, start) + .unwrap(); + let end_wide = line_index + .to_wide(line_index::WideEncoding::Utf16, end) + .unwrap(); - LocationRange { - start_line: start_wide.line, - start_column: start_wide.col, - end_line: end_wide.line, - end_column: end_wide.col, - } - }); + LocationRange { + start_line: start_wide.line, + start_column: start_wide.col, + end_line: end_wide.line, + end_column: end_wide.col, + } + }) + .collect(); serde_wasm_bindgen::to_value(&response).map_err(into_error) } diff --git a/playground/src/providers.tsx b/playground/src/providers.tsx index 2a2fd92b..020e927a 100644 --- a/playground/src/providers.tsx +++ b/playground/src/providers.tsx @@ -155,28 +155,28 @@ export async function provideHover( export async function provideDefinition( model: monaco.editor.ITextModel, position: monaco.Position, -): Promise { +): Promise | null> { const content = model.getValue() if (!content) return null try { - const result = goto_definition( + const results = goto_definition( content, position.lineNumber - 1, position.column - 1, ) - if (!result) return null + if (!results) return null - return { + return results.map((results) => ({ uri: model.uri, range: { - startLineNumber: result.start_line + 1, - startColumn: result.start_column + 1, - endLineNumber: result.end_line + 1, - endColumn: result.end_column + 1, + startLineNumber: results.start_line + 1, + startColumn: results.start_column + 1, + endLineNumber: results.end_line + 1, + endColumn: results.end_column + 1, }, - } + })) } catch (e) { console.error("Error in provideDefinition:", e) return null diff --git a/playground/src/squawk.tsx b/playground/src/squawk.tsx index cfd4a1f3..3b1bb357 100644 --- a/playground/src/squawk.tsx +++ b/playground/src/squawk.tsx @@ -70,7 +70,7 @@ export function goto_definition( content: string, line: number, column: number, -): LocationRange | null { +): Array { return goto_definition_(content, line, column) }