Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(hover): hover on functions + add hover integration tests #485

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
juleswritescode merged 7 commits into feat/hover-col from feat/hover-fn
Sep 1, 2025
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions Cargo.lock
View file Open in desktop

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/pgt_completions/src/relevance/filtering.rs
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ impl CompletionFilter<'_> {
.is_none_or(|n| n != &WrappingNode::List)
&& (ctx.before_cursor_matches_kind(&["keyword_into"])
|| (ctx.before_cursor_matches_kind(&["."])
&& ctx.parent_matches_one_of_kind(&["object_reference"])))
&& ctx.matches_ancestor_history(&["object_reference"])))
}

WrappingClause::DropTable | WrappingClause::AlterTable => ctx
Expand Down Expand Up @@ -136,7 +136,7 @@ impl CompletionFilter<'_> {
WrappingClause::Where => {
ctx.before_cursor_matches_kind(&["keyword_and", "keyword_where"])
|| (ctx.before_cursor_matches_kind(&["."])
&& ctx.parent_matches_one_of_kind(&["field"]))
&& ctx.matches_ancestor_history(&["field"]))
}

WrappingClause::PolicyCheck => {
Expand Down
1 change: 1 addition & 0 deletions crates/pgt_hover/Cargo.toml
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ tree-sitter.workspace = true
tree_sitter_sql.workspace = true

[dev-dependencies]
insta = { version = "1.42.1" }
pgt_test_utils.workspace = true

[lib]
Expand Down
30 changes: 29 additions & 1 deletion crates/pgt_hover/src/contextual_priority.rs
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use pgt_schema_cache::{Column, Table};
use pgt_schema_cache::{Column, Function, Table};
use pgt_treesitter::context::TreesitterContext;

pub(crate) trait ContextualPriority {
Expand Down Expand Up @@ -74,6 +74,34 @@ impl ContextualPriority for Table {
}
}

impl ContextualPriority for Function {
fn relevance_score(&self, _ctx: &TreesitterContext) -> f32 {
let mut score = 0.0;

// built-in functions get higher priority
if self.language == "internal" {
score += 100.0;
}

// public schema functions get base priority
if self.schema == "public" {
score += 50.0;
} else {
score += 25.0;
}

// aggregate and window functions are commonly used
match self.kind {
pgt_schema_cache::ProcKind::Aggregate => score += 20.0,
pgt_schema_cache::ProcKind::Window => score += 15.0,
pgt_schema_cache::ProcKind::Function => score += 10.0,
pgt_schema_cache::ProcKind::Procedure => score += 5.0,
}

score
}
}

/// Will first sort the items by a score and then filter out items with a score gap algorithm.
///
/// `[200, 180, 150, 140]` => all items are returned
Expand Down
11 changes: 11 additions & 0 deletions crates/pgt_hover/src/hovered_item.rs
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{contextual_priority::ContextualPriority, to_markdown::ToHoverMarkdow
pub(crate) enum HoverItem<'a> {
Table(&'a pgt_schema_cache::Table),
Column(&'a pgt_schema_cache::Column),
Function(&'a pgt_schema_cache::Function),
}

impl<'a> From<&'a pgt_schema_cache::Table> for HoverItem<'a> {
Expand All @@ -19,11 +20,18 @@ impl<'a> From<&'a pgt_schema_cache::Column> for HoverItem<'a> {
}
}

impl<'a> From<&'a pgt_schema_cache::Function> for HoverItem<'a> {
fn from(value: &'a pgt_schema_cache::Function) -> Self {
HoverItem::Function(value)
}
}

impl ContextualPriority for HoverItem<'_> {
fn relevance_score(&self, ctx: &pgt_treesitter::TreesitterContext) -> f32 {
match self {
HoverItem::Table(table) => table.relevance_score(ctx),
HoverItem::Column(column) => column.relevance_score(ctx),
HoverItem::Function(function) => function.relevance_score(ctx),
}
}
}
Expand All @@ -33,20 +41,23 @@ impl ToHoverMarkdown for HoverItem<'_> {
match self {
HoverItem::Table(table) => ToHoverMarkdown::hover_headline(*table, writer),
HoverItem::Column(column) => ToHoverMarkdown::hover_headline(*column, writer),
HoverItem::Function(function) => ToHoverMarkdown::hover_headline(*function, writer),
}
}

fn hover_body<W: std::fmt::Write>(&self, writer: &mut W) -> Result<bool, std::fmt::Error> {
match self {
HoverItem::Table(table) => ToHoverMarkdown::hover_body(*table, writer),
HoverItem::Column(column) => ToHoverMarkdown::hover_body(*column, writer),
HoverItem::Function(function) => ToHoverMarkdown::hover_body(*function, writer),
}
}

fn hover_footer<W: std::fmt::Write>(&self, writer: &mut W) -> Result<bool, std::fmt::Error> {
match self {
HoverItem::Table(table) => ToHoverMarkdown::hover_footer(*table, writer),
HoverItem::Column(column) => ToHoverMarkdown::hover_footer(*column, writer),
HoverItem::Function(function) => ToHoverMarkdown::hover_footer(*function, writer),
}
}
}
16 changes: 14 additions & 2 deletions crates/pgt_hover/src/hovered_node.rs
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ impl HoveredNode {
let under_node = ctx.node_under_cursor.as_ref()?;

match under_node.kind() {
"identifier" if ctx.parent_matches_one_of_kind(&["object_reference", "relation"]) => {
"identifier" if ctx.matches_ancestor_history(&["relation", "object_reference"]) => {
if let Some(schema) = ctx.schema_or_alias_name.as_ref() {
Some(HoveredNode::Table(NodeIdentification::SchemaAndName((
schema.clone(),
Expand All @@ -35,7 +35,7 @@ impl HoveredNode {
Some(HoveredNode::Table(NodeIdentification::Name(node_content)))
}
}
"identifier" if ctx.parent_matches_one_of_kind(&["field"]) => {
"identifier" if ctx.matches_ancestor_history(&["field"]) => {
if let Some(table_or_alias) = ctx.schema_or_alias_name.as_ref() {
Some(HoveredNode::Column(NodeIdentification::SchemaAndName((
table_or_alias.clone(),
Expand All @@ -45,6 +45,18 @@ impl HoveredNode {
Some(HoveredNode::Column(NodeIdentification::Name(node_content)))
}
}
"identifier" if ctx.matches_ancestor_history(&["invocation", "object_reference"]) => {
if let Some(schema) = ctx.schema_or_alias_name.as_ref() {
Some(HoveredNode::Function(NodeIdentification::SchemaAndName((
schema.clone(),
node_content,
))))
} else {
Some(HoveredNode::Function(NodeIdentification::Name(
node_content,
)))
}
}
_ => None,
}
}
Expand Down
27 changes: 26 additions & 1 deletion crates/pgt_hover/src/lib.rs
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,16 @@ pub fn on_hover(params: OnHoverParams) -> Vec<String> {
.collect(),

hovered_node::NodeIdentification::SchemaAndName((table_or_alias, column_name)) => {
// resolve alias to actual table name if needed
let actual_table = ctx
.mentioned_table_aliases
.get(table_or_alias.as_str())
.map(|s| s.as_str())
.unwrap_or(table_or_alias.as_str());

params
.schema_cache
.find_cols(&column_name, Some(&table_or_alias), None)
.find_cols(&column_name, Some(actual_table), None)
.into_iter()
.map(HoverItem::from)
.collect()
Expand All @@ -67,6 +74,24 @@ pub fn on_hover(params: OnHoverParams) -> Vec<String> {
hovered_node::NodeIdentification::SchemaAndTableAndName(_) => vec![],
},

HoveredNode::Function(node_identification) => match node_identification {
hovered_node::NodeIdentification::Name(function_name) => params
.schema_cache
.find_functions(&function_name, None)
.into_iter()
.map(HoverItem::from)
.collect(),

hovered_node::NodeIdentification::SchemaAndName((schema, function_name)) => params
.schema_cache
.find_functions(&function_name, Some(&schema))
.into_iter()
.map(HoverItem::from)
.collect(),

hovered_node::NodeIdentification::SchemaAndTableAndName(_) => vec![],
},

_ => todo!(),
};

Expand Down
116 changes: 107 additions & 9 deletions crates/pgt_hover/src/to_markdown.rs
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,39 @@ pub(crate) fn format_hover_markdown<T: ToHoverMarkdown>(
item.hover_headline(&mut markdown)?;
markdown_newline(&mut markdown)?;

if item.hover_body(&mut markdown)? {
markdown_newline(&mut markdown)?;
}
write!(markdown, "#### ")?;
item.hover_body(&mut markdown)?;
markdown_newline(&mut markdown)?;

write!(markdown, "--- ")?;
markdown_newline(&mut markdown)?;
item.hover_footer(&mut markdown)?;

Ok(markdown)
}

impl ToHoverMarkdown for pgt_schema_cache::Table {
fn hover_headline<W: Write>(&self, writer: &mut W) -> Result<(), std::fmt::Error> {
write!(writer, "`{}.{}`", self.schema, self.name)?;

let table_kind = match self.table_kind {
pgt_schema_cache::TableKind::View => " (View)",
pgt_schema_cache::TableKind::MaterializedView => " (M.View)",
pgt_schema_cache::TableKind::Partitioned => " (Partitioned)",
pgt_schema_cache::TableKind::Ordinary => "",
};

write!(writer, "{}", table_kind)?;

let locked_txt = if self.rls_enabled {
" - 🔒 RLS enabled"
} else {
" - 🔓 RLS disabled"
};

write!(
writer,
"{}.{}{}{}",
self.schema, self.name, table_kind, locked_txt
)
write!(writer, "{}", locked_txt)?;

Ok(())
}

fn hover_body<W: Write>(&self, writer: &mut W) -> Result<bool, std::fmt::Error> {
Expand Down Expand Up @@ -73,7 +77,7 @@ impl ToHoverMarkdown for pgt_schema_cache::Column {
fn hover_headline<W: Write>(&self, writer: &mut W) -> Result<(), std::fmt::Error> {
write!(
writer,
"{}.{}.{}",
"`{}.{}.{}`",
self.schema_name, self.table_name, self.name
)
}
Expand Down Expand Up @@ -118,6 +122,100 @@ impl ToHoverMarkdown for pgt_schema_cache::Column {
}
}

impl ToHoverMarkdown for pgt_schema_cache::Function {
fn hover_headline<W: Write>(&self, writer: &mut W) -> Result<(), std::fmt::Error> {
write!(writer, "`{}.{}", self.schema, self.name)?;

if let Some(args) = &self.argument_types {
write!(writer, "({})", args)?;
} else {
write!(writer, "()")?;
}

write!(
writer,
" → {}`",
self.return_type.as_ref().unwrap_or(&"void".to_string())
)?;

Ok(())
}

fn hover_body<W: Write>(&self, writer: &mut W) -> Result<bool, std::fmt::Error> {
let kind_text = match self.kind {
pgt_schema_cache::ProcKind::Function => "Function",
pgt_schema_cache::ProcKind::Procedure => "Procedure",
pgt_schema_cache::ProcKind::Aggregate => "Aggregate",
pgt_schema_cache::ProcKind::Window => "Window",
};

write!(writer, "{}", kind_text)?;

let behavior_text = match self.behavior {
pgt_schema_cache::Behavior::Immutable => " - Immutable",
pgt_schema_cache::Behavior::Stable => " - Stable",
pgt_schema_cache::Behavior::Volatile => "",
};

write!(writer, "{}", behavior_text)?;

if self.security_definer {
write!(writer, " - Security DEFINER")?;
} else {
write!(writer, " - Security INVOKER")?;
}

Ok(true)
}

fn hover_footer<W: Write>(&self, writer: &mut W) -> Result<bool, std::fmt::Error> {
if let Some(def) = self.definition.as_ref() {
/*
* We don't want to show 250 lines of functions to the user.
* If we have more than 30 lines, we'll only show the signature.
*/
if def.lines().count() > 30 {
let without_boilerplate: String = def
.split_ascii_whitespace()
.skip_while(|elem| {
["create", "or", "replace", "function"]
.contains(&elem.to_ascii_lowercase().as_str())
})
.collect::<Vec<&str>>()
.join(" ");

for char in without_boilerplate.chars() {
match char {
'(' => {
write!(writer, "(\n ")?;
}

')' => {
write!(writer, "\n)\n")?;
break;
}

',' => {
// one space already present
write!(writer, ",\n ")?;
}

_ => {
write!(writer, "{}", char)?;
}
}
}
} else {
write!(writer, "```\n{}\n```", def)?;
}

Ok(true)
} else {
Ok(false)
}
}
}

fn markdown_newline<W: Write>(writer: &mut W) -> Result<(), std::fmt::Error> {
write!(writer, " ")?;
writeln!(writer)?;
Expand Down
Loading

AltStyle によって変換されたページ (->オリジナル) /