Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
344 changes: 329 additions & 15 deletions crates/squawk_ide/src/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,38 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option<String> {
let token = token_from_offset(file, offset)?;
let parent = token.parent()?;

let name_ref = ast::NameRef::cast(parent)?;
let binder = binder::bind(file);

if let Some(name_ref) = ast::NameRef::cast(parent.clone()) {
if is_column_ref(&name_ref) {
return hover_column(file, &name_ref, &binder);
}

if !is_column_ref(&name_ref) {
return None;
if is_table_ref(&name_ref) {
return hover_table(file, &name_ref, &binder);
}
}

if let Some(name) = ast::Name::cast(parent) {
if let Some(column) = name.syntax().parent().and_then(ast::Column::cast)
&& let Some(create_table) = column.syntax().ancestors().find_map(ast::CreateTable::cast)
{
return hover_column_definition(&create_table, &column, &binder);
}

if let Some(create_table) = name.syntax().ancestors().find_map(ast::CreateTable::cast) {
return format_create_table(&create_table, &binder);
}
}

None
}

fn hover_column(
file: &ast::SourceFile,
name_ref: &ast::NameRef,
binder: &binder::Binder,
) -> Option<String> {
let column_name = name_ref.syntax().text().to_string();

let create_index = name_ref
Expand All @@ -24,11 +50,9 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option<String> {
let relation_name = create_index.relation_name()?;
let path = relation_name.path()?;

let binder = binder::bind(file);

let (schema, table_name) = resolve::resolve_table_info(&binder, &path)?;
let (schema, table_name) = resolve::resolve_table_info(binder, &path)?;

let column_ptr = resolve::resolve_name_ref(&binder, &name_ref)?;
let column_ptr = resolve::resolve_name_ref(binder, name_ref)?;

let root = file.syntax();
let column_name_node = column_ptr.to_node(root);
Expand All @@ -43,14 +67,128 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option<String> {
))
}

fn hover_column_definition(
create_table: &ast::CreateTable,
column: &ast::Column,
binder: &binder::Binder,
) -> Option<String> {
let column_name = column.name()?.syntax().text().to_string();
let ty = column.ty()?;
let path = create_table.path()?;
let table_name = path.segment()?.name()?.syntax().text().to_string();

let schema = if let Some(qualifier) = path.qualifier() {
qualifier.syntax().text().to_string()
} else if let Some(schema) = table_schema(create_table, binder) {
schema
} else {
return Some(format!(
"{}.{} {}",
table_name,
column_name,
ty.syntax().text()
));
};

Some(format!(
"{}.{}.{} {}",
schema,
table_name,
column_name,
ty.syntax().text()
))
}

fn hover_table(
file: &ast::SourceFile,
name_ref: &ast::NameRef,
binder: &binder::Binder,
) -> Option<String> {
let table_ptr = resolve::resolve_name_ref(binder, name_ref)?;

let root = file.syntax();
let table_name_node = table_ptr.to_node(root);

let create_table = table_name_node
.ancestors()
.find_map(ast::CreateTable::cast)?;

format_create_table(&create_table, binder)
}

// Insert inferred schema into the create table hover info
fn format_create_table(create_table: &ast::CreateTable, binder: &binder::Binder) -> Option<String> {
let path = create_table.path()?;
let mut text = create_table.syntax().text().to_string();

if path.qualifier().is_some() {
return Some(text);
}

let Some(schema) = table_schema(create_table, binder) else {
return Some(text);
};

let Some(offset) = table_name_offset(create_table, &path) else {
return Some(text);
};

text.insert_str(offset, &format!("{}.", schema));
Some(text)
}

fn table_schema(create_table: &ast::CreateTable, binder: &binder::Binder) -> Option<String> {
let is_temp = create_table.temp_token().is_some() || create_table.temporary_token().is_some();
if is_temp {
return Some("pg_temp".to_string());
}

let position = create_table.syntax().text_range().start();
let search_path = binder.search_path_at(position);
search_path.first().map(|s| s.to_string())
}

fn table_name_offset(create_table: &ast::CreateTable, path: &ast::Path) -> Option<usize> {
let segment = path.segment()?;
let name = segment.name()?;
let name_start = name.syntax().text_range().start();
let create_table_start = create_table.syntax().text_range().start();
Some((name_start - create_table_start).into())
}

fn is_column_ref(name_ref: &ast::NameRef) -> bool {
let mut in_partition_item = false;

for ancestor in name_ref.syntax().ancestors() {
if ast::PartitionItem::can_cast(ancestor.kind()) {
return true;
in_partition_item = true;
}
if ast::CreateIndex::can_cast(ancestor.kind()) {
return in_partition_item;
}
}
false
}

fn is_table_ref(name_ref: &ast::NameRef) -> bool {
let mut in_partition_item = false;

for ancestor in name_ref.syntax().ancestors() {
if ast::DropTable::can_cast(ancestor.kind()) {
return true;
}
if ast::Table::can_cast(ancestor.kind()) {
return true;
}
if ast::DropIndex::can_cast(ancestor.kind()) {
return false;
}
if ast::PartitionItem::can_cast(ancestor.kind()) {
in_partition_item = true;
}
if ast::CreateIndex::can_cast(ancestor.kind()) {
return !in_partition_item;
}
}
false
}
Expand Down Expand Up @@ -239,13 +377,16 @@ create index idx_email on public.users(email$0);
}

#[test]
fn hover_not_on_table_name() {
hover_not_found(
"
create table users(id int);
create index idx on users$0(id);
",
);
fn hover_on_table_name() {
assert_snapshot!(check_hover("
create table t(id int);
create index idx on t$0(id);
"), @r"
hover: create table public.t(id int)
╭▸
3 │ create index idx on t(id);
╰╴ ─ hover
");
}

#[test]
Expand All @@ -257,4 +398,177 @@ create index idx$0 on users(id);
",
);
}

#[test]
fn hover_table_in_create_index() {
assert_snapshot!(check_hover("
create table users(id int, email text);
create index idx_email on users$0(email);
"), @r"
hover: create table public.users(id int, email text)
╭▸
3 │ create index idx_email on users(email);
╰╴ ─ hover
");
}

#[test]
fn hover_table_with_schema() {
assert_snapshot!(check_hover("
create table public.users(id int, email text);
create index idx on public.users$0(id);
"), @r"
hover: create table public.users(id int, email text)
╭▸
3 │ create index idx on public.users(id);
╰╴ ─ hover
");
}

#[test]
fn hover_table_temp() {
assert_snapshot!(check_hover("
create temp table users(id int, email text);
create index idx on users$0(id);
"), @r"
hover: create temp table pg_temp.users(id int, email text)
╭▸
3 │ create index idx on users(id);
╰╴ ─ hover
");
}

#[test]
fn hover_table_multiline() {
assert_snapshot!(check_hover("
create table users(
id int,
email text,
name varchar(100)
);
create index idx on users$0(id);
"), @r"
hover: create table public.users(
id int,
email text,
name varchar(100)
)
╭▸
7 │ create index idx on users(id);
╰╴ ─ hover
");
}

#[test]
fn hover_table_with_search_path() {
assert_snapshot!(check_hover(r#"
set search_path to myschema;
create table users(id int, email text);
create index idx on users$0(id);
"#), @r"
hover: create table myschema.users(id int, email text)
╭▸
4 │ create index idx on users(id);
╰╴ ─ hover
");
}

#[test]
fn hover_table_search_path_at_definition() {
assert_snapshot!(check_hover(r#"
set search_path to myschema;
create table users(id int, email text);
set search_path to myschema, otherschema;
create index idx on users$0(id);
"#), @r"
hover: create table myschema.users(id int, email text)
╭▸
5 │ create index idx on users(id);
╰╴ ─ hover
");
}

#[test]
fn hover_on_create_table_definition() {
assert_snapshot!(check_hover("
create table t$0(x bigint);
"), @r"
hover: create table public.t(x bigint)
╭▸
2 │ create table t(x bigint);
╰╴ ─ hover
");
}

#[test]
fn hover_on_create_table_definition_with_schema() {
assert_snapshot!(check_hover("
create table myschema.users$0(id int);
"), @r"
hover: create table myschema.users(id int)
╭▸
2 │ create table myschema.users(id int);
╰╴ ─ hover
");
}

#[test]
fn hover_on_create_temp_table_definition() {
assert_snapshot!(check_hover("
create temp table t$0(x bigint);
"), @r"
hover: create temp table pg_temp.t(x bigint)
╭▸
2 │ create temp table t(x bigint);
╰╴ ─ hover
");
}

#[test]
fn hover_on_column_in_create_table() {
assert_snapshot!(check_hover("
create table t(id$0 int);
"), @r"
hover: public.t.id int
╭▸
2 │ create table t(id int);
╰╴ ─ hover
");
}

#[test]
fn hover_on_column_in_create_table_with_schema() {
assert_snapshot!(check_hover("
create table myschema.users(id$0 int, name text);
"), @r"
hover: myschema.users.id int
╭▸
2 │ create table myschema.users(id int, name text);
╰╴ ─ hover
");
}

#[test]
fn hover_on_column_in_temp_table() {
assert_snapshot!(check_hover("
create temp table t(x$0 bigint);
"), @r"
hover: pg_temp.t.x bigint
╭▸
2 │ create temp table t(x bigint);
╰╴ ─ hover
");
}

#[test]
fn hover_on_multiple_columns() {
assert_snapshot!(check_hover("
create table t(id int, email$0 text, name varchar(100));
"), @r"
hover: public.t.email text
╭▸
2 │ create table t(id int, email text, name varchar(100));
╰╴ ─ hover
");
}
}
Loading