From 346ea736513d4f393657292288e86216e839e155 Mon Sep 17 00:00:00 2001 From: Julian Date: Sun, 12 Oct 2025 13:56:30 +0200 Subject: [PATCH 01/16] helper, update grammar --- crates/pgt_treesitter/src/context/mod.rs | 58 ++++++++++++++++++++++++ crates/pgt_treesitter_grammar/grammar.js | 26 +++++------ 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index 80755324a..e0505ddba 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -826,6 +826,42 @@ impl<'a> TreesitterContext<'a> { .unwrap_or(0) } + /// Returns true if the node under the cursor matches the field_name OR has a parent that matches the field_name. + pub fn node_under_cursor_is_within_field_name(&self, name: &str) -> bool { + self.node_under_cursor + .as_ref() + .map(|n| match n { + NodeUnderCursor::TsNode(node) => { + // It might seem weird that we have to check for the field_name from the parent, + // but TreeSitter wants it this way, since nodes often can only be named in + // the context of their parents. + let root_node = self.tree.root_node(); + let mut cursor = node.walk(); + let mut parent = node.parent(); + + while let Some(p) = parent { + if p == root_node { + break; + } + + if p.children_by_field_name(name, &mut cursor).any(|c| { + let r = c.range(); + // if the parent range contains the node range, the node is of the field_name. + r.start_byte <= node.start_byte() && r.end_byte >= node.end_byte() + }) { + return true; + } else { + parent = p.parent(); + } + } + + return false; + } + NodeUnderCursor::CustomNode { .. } => false, + }) + .unwrap_or(false) + } + pub fn get_mentioned_relations(&self, key: &Option) -> Option<&HashSet> { if let Some(key) = key.as_ref() { let sanitized_key = key.replace('"', ""); @@ -1214,4 +1250,26 @@ mod tests { _ => unreachable!(), } } + + #[test] + fn tryout_node_field() { + let query = format!( + r#"create table foo (id int not null, compfoo som{}e_type);"#, + QueryWithCursorPosition::cursor_marker() + ); + + let (position, text) = QueryWithCursorPosition::from(query).get_text_and_position(); + + let tree = get_tree(text.as_str()); + + let params = TreeSitterContextParams { + position: (position as u32).into(), + text: &text, + tree: &tree, + }; + + let ctx = TreesitterContext::new(params); + + assert!(ctx.node_under_cursor_is_within_field_name("custom_type")); + } } diff --git a/crates/pgt_treesitter_grammar/grammar.js b/crates/pgt_treesitter_grammar/grammar.js index 670c33442..15accb440 100644 --- a/crates/pgt_treesitter_grammar/grammar.js +++ b/crates/pgt_treesitter_grammar/grammar.js @@ -484,7 +484,7 @@ module.exports = grammar({ keyword_array: (_) => make_keyword("array"), // not included in _type since it's a constructor literal - _type: ($) => + type: ($) => prec.left( seq( choice( @@ -857,7 +857,7 @@ module.exports = grammar({ seq( optional($._argmode), optional($.identifier), - $._type, + $.type, optional(seq(choice($.keyword_default, "="), $.literal)) ), @@ -1234,8 +1234,8 @@ module.exports = grammar({ $.function_arguments, $.keyword_returns, choice( - $._type, - seq($.keyword_setof, $._type), + $.type, + seq($.keyword_setof, $.type), seq($.keyword_table, $.column_definitions), $.keyword_trigger ), @@ -1275,7 +1275,7 @@ module.exports = grammar({ function_declaration: ($) => seq( $.identifier, - $._type, + $.type, optional( seq( ":=", @@ -1523,7 +1523,7 @@ module.exports = grammar({ $.object_reference, repeat( choice( - seq($.keyword_as, $._type), + seq($.keyword_as, $.type), seq( $.keyword_increment, optional($.keyword_by), @@ -1781,7 +1781,7 @@ module.exports = grammar({ seq( optional(seq($.keyword_set, $.keyword_data)), $.keyword_type, - field("type", $._type) + field("type", $.type) ), seq( $.keyword_set, @@ -1975,7 +1975,7 @@ module.exports = grammar({ choice( repeat1( choice( - seq($.keyword_as, $._type), + seq($.keyword_as, $.type), seq($.keyword_increment, optional($.keyword_by), $.literal), seq( $.keyword_minvalue, @@ -2057,7 +2057,7 @@ module.exports = grammar({ ), seq( choice( - seq($.keyword_add, $.keyword_attribute, $.identifier, $._type), + seq($.keyword_add, $.keyword_attribute, $.identifier, $.type), seq( $.keyword_drop, $.keyword_attribute, @@ -2070,7 +2070,7 @@ module.exports = grammar({ $.identifier, optional(seq($.keyword_set, $.keyword_data)), $.keyword_type, - $._type + $.type ) ), optional(seq($.keyword_collate, $.identifier)), @@ -2624,7 +2624,7 @@ module.exports = grammar({ column_definition: ($) => seq( field("name", $._column), - field("type", $._type), + field("type", $.type), repeat($._column_constraint) ), @@ -2810,7 +2810,7 @@ module.exports = grammar({ field("name", $.identifier) ), - implicit_cast: ($) => seq($._expression, "::", $._type), + implicit_cast: ($) => seq($._expression, "::", $.type), // Postgres syntax for intervals interval: ($) => seq($.keyword_interval, $._literal_string), @@ -2819,7 +2819,7 @@ module.exports = grammar({ seq( field("name", $.keyword_cast), wrapped_in_parenthesis( - seq(field("parameter", $._expression), $.keyword_as, $._type) + seq(field("parameter", $._expression), $.keyword_as, $.type) ) ), From c3eb171afb59e00a2659219ed71b618b8f994c35 Mon Sep 17 00:00:00 2001 From: Julian Date: Sun, 12 Oct 2025 14:19:26 +0200 Subject: [PATCH 02/16] so far! --- crates/pgt_hover/src/hoverables/mod.rs | 14 ++++ .../pgt_hover/src/hoverables/postgres_type.rs | 78 +++++++++++++++++++ crates/pgt_hover/src/hovered_node.rs | 18 +++++ crates/pgt_hover/src/lib.rs | 18 +++++ 4 files changed, 128 insertions(+) create mode 100644 crates/pgt_hover/src/hoverables/postgres_type.rs diff --git a/crates/pgt_hover/src/hoverables/mod.rs b/crates/pgt_hover/src/hoverables/mod.rs index 675c1366a..f4cdff774 100644 --- a/crates/pgt_hover/src/hoverables/mod.rs +++ b/crates/pgt_hover/src/hoverables/mod.rs @@ -2,6 +2,7 @@ use crate::{contextual_priority::ContextualPriority, to_markdown::ToHoverMarkdow mod column; mod function; +mod postgres_type; mod role; mod schema; mod table; @@ -16,6 +17,7 @@ pub enum Hoverable<'a> { Function(&'a pgt_schema_cache::Function), Role(&'a pgt_schema_cache::Role), Schema(&'a pgt_schema_cache::Schema), + PostgresType(&'a pgt_schema_cache::PostgresType), } impl<'a> From<&'a pgt_schema_cache::Schema> for Hoverable<'a> { @@ -48,6 +50,12 @@ impl<'a> From<&'a pgt_schema_cache::Role> for Hoverable<'a> { } } +impl<'a> From<&'a pgt_schema_cache::PostgresType> for Hoverable<'a> { + fn from(value: &'a pgt_schema_cache::PostgresType) -> Self { + Hoverable::PostgresType(value) + } +} + impl ContextualPriority for Hoverable<'_> { fn relevance_score(&self, ctx: &pgt_treesitter::TreesitterContext) -> f32 { match self { @@ -56,6 +64,7 @@ impl ContextualPriority for Hoverable<'_> { Hoverable::Function(function) => function.relevance_score(ctx), Hoverable::Role(role) => role.relevance_score(ctx), Hoverable::Schema(schema) => schema.relevance_score(ctx), + Hoverable::PostgresType(type_) => type_.relevance_score(ctx), } } } @@ -68,6 +77,7 @@ impl ToHoverMarkdown for Hoverable<'_> { Hoverable::Function(function) => ToHoverMarkdown::hover_headline(*function, writer), Hoverable::Role(role) => ToHoverMarkdown::hover_headline(*role, writer), Hoverable::Schema(schema) => ToHoverMarkdown::hover_headline(*schema, writer), + Hoverable::PostgresType(type_) => ToHoverMarkdown::hover_headline(*type_, writer), } } @@ -78,6 +88,7 @@ impl ToHoverMarkdown for Hoverable<'_> { Hoverable::Function(function) => ToHoverMarkdown::hover_body(*function, writer), Hoverable::Role(role) => ToHoverMarkdown::hover_body(*role, writer), Hoverable::Schema(schema) => ToHoverMarkdown::hover_body(*schema, writer), + Hoverable::PostgresType(type_) => ToHoverMarkdown::hover_body(*type_, writer), } } @@ -88,6 +99,7 @@ impl ToHoverMarkdown for Hoverable<'_> { Hoverable::Function(function) => ToHoverMarkdown::hover_footer(*function, writer), Hoverable::Role(role) => ToHoverMarkdown::hover_footer(*role, writer), Hoverable::Schema(schema) => ToHoverMarkdown::hover_footer(*schema, writer), + Hoverable::PostgresType(type_) => ToHoverMarkdown::hover_footer(*type_, writer), } } @@ -98,6 +110,7 @@ impl ToHoverMarkdown for Hoverable<'_> { Hoverable::Function(function) => function.body_markdown_type(), Hoverable::Role(role) => role.body_markdown_type(), Hoverable::Schema(schema) => schema.body_markdown_type(), + Hoverable::PostgresType(type_) => type_.body_markdown_type(), } } @@ -108,6 +121,7 @@ impl ToHoverMarkdown for Hoverable<'_> { Hoverable::Function(function) => function.footer_markdown_type(), Hoverable::Role(role) => role.footer_markdown_type(), Hoverable::Schema(schema) => schema.footer_markdown_type(), + Hoverable::PostgresType(type_) => type_.footer_markdown_type(), } } } diff --git a/crates/pgt_hover/src/hoverables/postgres_type.rs b/crates/pgt_hover/src/hoverables/postgres_type.rs new file mode 100644 index 000000000..21cb33a26 --- /dev/null +++ b/crates/pgt_hover/src/hoverables/postgres_type.rs @@ -0,0 +1,78 @@ +use std::fmt::Write; + +use pgt_schema_cache::PostgresType; +use pgt_treesitter::TreesitterContext; + +use crate::{contextual_priority::ContextualPriority, to_markdown::ToHoverMarkdown}; + +impl ToHoverMarkdown for PostgresType { + fn hover_headline(&self, writer: &mut W) -> Result<(), std::fmt::Error> { + write!(writer, "`{}.{}`", self.schema, self.name)?; + Ok(()) + } + + fn hover_body(&self, writer: &mut W) -> Result { + if let Some(comment) = &self.comment { + write!(writer, "Comment: '{}'", comment)?; + writeln!(writer)?; + writeln!(writer)?; + } + + if !self.attributes.attrs.is_empty() { + write!(writer, "Attributes:")?; + writeln!(writer)?; + + for attribute in &self.attributes.attrs { + // TODO: look up other types with schema cache. + write!(writer, "- {}", attribute.name)?; + writeln!(writer)?; + } + + writeln!(writer)?; + } + + if !self.enums.values.is_empty() { + write!(writer, "Enum Permutations:")?; + writeln!(writer)?; + + for kind in &self.enums.values { + // TODO: look up other types with schema cache. + write!(writer, "- {}", kind)?; + writeln!(writer)?; + } + + writeln!(writer)?; + } + + Ok(true) + } + + fn hover_footer(&self, writer: &mut W) -> Result { + writeln!(writer)?; + Ok(true) + } +} + +impl ContextualPriority for PostgresType { + // there are no schemas with duplicate names. + fn relevance_score(&self, ctx: &TreesitterContext) -> f32 { + let mut score = 0.0; + + if ctx + .get_mentioned_relations(&Some(self.schema.clone())) + .is_some() + { + score += 100.0; + } + + if ctx.get_mentioned_relations(&None).is_some() && self.schema == "public" { + score += 100.0; + } + + if self.schema == "public" && score == 0.0 { + score += 10.0; + } + + score + } +} diff --git a/crates/pgt_hover/src/hovered_node.rs b/crates/pgt_hover/src/hovered_node.rs index c67cb8d06..76f144d21 100644 --- a/crates/pgt_hover/src/hovered_node.rs +++ b/crates/pgt_hover/src/hovered_node.rs @@ -18,6 +18,7 @@ pub(crate) enum HoveredNode { Policy(NodeIdentification), Trigger(NodeIdentification), Role(NodeIdentification), + PostgresType(NodeIdentification), } impl HoveredNode { @@ -74,6 +75,7 @@ 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(( @@ -86,10 +88,26 @@ impl HoveredNode { ))) } } + "identifier" if ctx.matches_one_of_ancestors(&["alter_role", "policy_to_role"]) => { Some(HoveredNode::Role(NodeIdentification::Name(node_content))) } + "identifier" + if ctx.matches_ancestor_history(&["type", "object_reference"]) + && ctx.node_under_cursor_is_within_field_name("custom_type") => + { + if let Some(schema) = ctx.schema_or_alias_name.as_ref() { + Some(HoveredNode::PostgresType( + NodeIdentification::SchemaAndName((schema.clone(), node_content)), + )) + } else { + Some(HoveredNode::PostgresType(NodeIdentification::Name( + node_content, + ))) + } + } + "revoke_role" | "grant_role" | "policy_role" => { Some(HoveredNode::Role(NodeIdentification::Name(node_content))) } diff --git a/crates/pgt_hover/src/lib.rs b/crates/pgt_hover/src/lib.rs index b690d7f05..379c615db 100644 --- a/crates/pgt_hover/src/lib.rs +++ b/crates/pgt_hover/src/lib.rs @@ -117,6 +117,24 @@ pub fn on_hover(params: OnHoverParams) -> Vec { _ => vec![], }, + HoveredNode::PostgresType(node_identification) => match node_identification { + hovered_node::NodeIdentification::Name(type_name) => params + .schema_cache + .find_type(&type_name, None) + .map(Hoverable::from) + .map(|s| vec![s]) + .unwrap_or_default(), + + hovered_node::NodeIdentification::SchemaAndName((schema, type_name)) => params + .schema_cache + .find_type(&type_name, Some(schema.as_str())) + .map(Hoverable::from) + .map(|s| vec![s]) + .unwrap_or_default(), + + _ => vec![], + }, + _ => todo!(), }; From 8fcf50e8888f40bf8d02dace4416a943e3349d2c Mon Sep 17 00:00:00 2001 From: Julian Date: Sun, 12 Oct 2025 14:31:43 +0200 Subject: [PATCH 03/16] use schema_cache --- crates/pgt_hover/src/hoverables/column.rs | 8 ++-- crates/pgt_hover/src/hoverables/function.rs | 8 ++-- crates/pgt_hover/src/hoverables/mod.rs | 44 ++++++++++--------- .../pgt_hover/src/hoverables/postgres_type.rs | 19 +++++--- crates/pgt_hover/src/hoverables/role.rs | 8 ++-- crates/pgt_hover/src/hoverables/schema.rs | 8 ++-- crates/pgt_hover/src/hoverables/table.rs | 8 ++-- crates/pgt_hover/src/lib.rs | 2 +- crates/pgt_hover/src/to_markdown.rs | 15 ++++--- crates/pgt_schema_cache/src/schema_cache.rs | 4 ++ crates/pgt_treesitter/src/context/mod.rs | 2 +- 11 files changed, 70 insertions(+), 56 deletions(-) diff --git a/crates/pgt_hover/src/hoverables/column.rs b/crates/pgt_hover/src/hoverables/column.rs index 913aae86e..54ebf130c 100644 --- a/crates/pgt_hover/src/hoverables/column.rs +++ b/crates/pgt_hover/src/hoverables/column.rs @@ -1,12 +1,12 @@ use std::fmt::Write; -use pgt_schema_cache::Column; +use pgt_schema_cache::{Column, SchemaCache}; use pgt_treesitter::TreesitterContext; use crate::{contextual_priority::ContextualPriority, to_markdown::ToHoverMarkdown}; impl ToHoverMarkdown for pgt_schema_cache::Column { - fn hover_headline(&self, writer: &mut W) -> Result<(), std::fmt::Error> { + fn hover_headline(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result<(), std::fmt::Error> { write!( writer, "`{}.{}.{}`", @@ -14,7 +14,7 @@ impl ToHoverMarkdown for pgt_schema_cache::Column { ) } - fn hover_body(&self, writer: &mut W) -> Result { + fn hover_body(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { if let Some(comment) = &self.comment { write!(writer, "Comment: '{}'", comment)?; writeln!(writer)?; @@ -46,7 +46,7 @@ impl ToHoverMarkdown for pgt_schema_cache::Column { Ok(true) } - fn hover_footer(&self, writer: &mut W) -> Result { + fn hover_footer(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { if let Some(default) = &self.default_expr { writeln!(writer)?; write!(writer, "Default: {}", default)?; diff --git a/crates/pgt_hover/src/hoverables/function.rs b/crates/pgt_hover/src/hoverables/function.rs index 54bfcc75d..03746870e 100644 --- a/crates/pgt_hover/src/hoverables/function.rs +++ b/crates/pgt_hover/src/hoverables/function.rs @@ -1,6 +1,6 @@ use std::fmt::Write; -use pgt_schema_cache::Function; +use pgt_schema_cache::{Function, SchemaCache}; use pgt_treesitter::TreesitterContext; use crate::{contextual_priority::ContextualPriority, to_markdown::ToHoverMarkdown}; @@ -10,7 +10,7 @@ impl ToHoverMarkdown for Function { "sql" } - fn hover_headline(&self, writer: &mut W) -> Result<(), std::fmt::Error> { + fn hover_headline(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result<(), std::fmt::Error> { write!(writer, "`{}.{}", self.schema, self.name)?; if let Some(args) = &self.argument_types { @@ -28,7 +28,7 @@ impl ToHoverMarkdown for Function { Ok(()) } - fn hover_body(&self, writer: &mut W) -> Result { + fn hover_body(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { let kind_text = match self.kind { pgt_schema_cache::ProcKind::Function => "Function", pgt_schema_cache::ProcKind::Procedure => "Procedure", @@ -55,7 +55,7 @@ impl ToHoverMarkdown for Function { Ok(true) } - fn hover_footer(&self, writer: &mut W) -> Result { + fn hover_footer(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { if let Some(def) = self.definition.as_ref() { /* * We don't want to show 250 lines of functions to the user. diff --git a/crates/pgt_hover/src/hoverables/mod.rs b/crates/pgt_hover/src/hoverables/mod.rs index f4cdff774..f6f634615 100644 --- a/crates/pgt_hover/src/hoverables/mod.rs +++ b/crates/pgt_hover/src/hoverables/mod.rs @@ -1,3 +1,5 @@ +use pgt_schema_cache::SchemaCache; + use crate::{contextual_priority::ContextualPriority, to_markdown::ToHoverMarkdown}; mod column; @@ -70,36 +72,36 @@ impl ContextualPriority for Hoverable<'_> { } impl ToHoverMarkdown for Hoverable<'_> { - fn hover_headline(&self, writer: &mut W) -> Result<(), std::fmt::Error> { + fn hover_headline(&self, writer: &mut W, schema_cache: &SchemaCache) -> Result<(), std::fmt::Error> { match self { - Hoverable::Table(table) => ToHoverMarkdown::hover_headline(*table, writer), - Hoverable::Column(column) => ToHoverMarkdown::hover_headline(*column, writer), - Hoverable::Function(function) => ToHoverMarkdown::hover_headline(*function, writer), - Hoverable::Role(role) => ToHoverMarkdown::hover_headline(*role, writer), - Hoverable::Schema(schema) => ToHoverMarkdown::hover_headline(*schema, writer), - Hoverable::PostgresType(type_) => ToHoverMarkdown::hover_headline(*type_, writer), + Hoverable::Table(table) => ToHoverMarkdown::hover_headline(*table, writer, schema_cache), + Hoverable::Column(column) => ToHoverMarkdown::hover_headline(*column, writer, schema_cache), + Hoverable::Function(function) => ToHoverMarkdown::hover_headline(*function, writer, schema_cache), + Hoverable::Role(role) => ToHoverMarkdown::hover_headline(*role, writer, schema_cache), + Hoverable::Schema(schema) => ToHoverMarkdown::hover_headline(*schema, writer, schema_cache), + Hoverable::PostgresType(type_) => ToHoverMarkdown::hover_headline(*type_, writer, schema_cache), } } - fn hover_body(&self, writer: &mut W) -> Result { + fn hover_body(&self, writer: &mut W, schema_cache: &SchemaCache) -> Result { match self { - Hoverable::Table(table) => ToHoverMarkdown::hover_body(*table, writer), - Hoverable::Column(column) => ToHoverMarkdown::hover_body(*column, writer), - Hoverable::Function(function) => ToHoverMarkdown::hover_body(*function, writer), - Hoverable::Role(role) => ToHoverMarkdown::hover_body(*role, writer), - Hoverable::Schema(schema) => ToHoverMarkdown::hover_body(*schema, writer), - Hoverable::PostgresType(type_) => ToHoverMarkdown::hover_body(*type_, writer), + Hoverable::Table(table) => ToHoverMarkdown::hover_body(*table, writer, schema_cache), + Hoverable::Column(column) => ToHoverMarkdown::hover_body(*column, writer, schema_cache), + Hoverable::Function(function) => ToHoverMarkdown::hover_body(*function, writer, schema_cache), + Hoverable::Role(role) => ToHoverMarkdown::hover_body(*role, writer, schema_cache), + Hoverable::Schema(schema) => ToHoverMarkdown::hover_body(*schema, writer, schema_cache), + Hoverable::PostgresType(type_) => ToHoverMarkdown::hover_body(*type_, writer, schema_cache), } } - fn hover_footer(&self, writer: &mut W) -> Result { + fn hover_footer(&self, writer: &mut W, schema_cache: &SchemaCache) -> Result { match self { - Hoverable::Table(table) => ToHoverMarkdown::hover_footer(*table, writer), - Hoverable::Column(column) => ToHoverMarkdown::hover_footer(*column, writer), - Hoverable::Function(function) => ToHoverMarkdown::hover_footer(*function, writer), - Hoverable::Role(role) => ToHoverMarkdown::hover_footer(*role, writer), - Hoverable::Schema(schema) => ToHoverMarkdown::hover_footer(*schema, writer), - Hoverable::PostgresType(type_) => ToHoverMarkdown::hover_footer(*type_, writer), + Hoverable::Table(table) => ToHoverMarkdown::hover_footer(*table, writer, schema_cache), + Hoverable::Column(column) => ToHoverMarkdown::hover_footer(*column, writer, schema_cache), + Hoverable::Function(function) => ToHoverMarkdown::hover_footer(*function, writer, schema_cache), + Hoverable::Role(role) => ToHoverMarkdown::hover_footer(*role, writer, schema_cache), + Hoverable::Schema(schema) => ToHoverMarkdown::hover_footer(*schema, writer, schema_cache), + Hoverable::PostgresType(type_) => ToHoverMarkdown::hover_footer(*type_, writer, schema_cache), } } diff --git a/crates/pgt_hover/src/hoverables/postgres_type.rs b/crates/pgt_hover/src/hoverables/postgres_type.rs index 21cb33a26..d26ccdfc9 100644 --- a/crates/pgt_hover/src/hoverables/postgres_type.rs +++ b/crates/pgt_hover/src/hoverables/postgres_type.rs @@ -1,17 +1,17 @@ use std::fmt::Write; -use pgt_schema_cache::PostgresType; +use pgt_schema_cache::{PostgresType, SchemaCache}; use pgt_treesitter::TreesitterContext; use crate::{contextual_priority::ContextualPriority, to_markdown::ToHoverMarkdown}; impl ToHoverMarkdown for PostgresType { - fn hover_headline(&self, writer: &mut W) -> Result<(), std::fmt::Error> { - write!(writer, "`{}.{}`", self.schema, self.name)?; + fn hover_headline(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result<(), std::fmt::Error> { + write!(writer, "`{}.{}` (Custom Type)", self.schema, self.name)?; Ok(()) } - fn hover_body(&self, writer: &mut W) -> Result { + fn hover_body(&self, writer: &mut W, schema_cache: &SchemaCache) -> Result { if let Some(comment) = &self.comment { write!(writer, "Comment: '{}'", comment)?; writeln!(writer)?; @@ -23,8 +23,14 @@ impl ToHoverMarkdown for PostgresType { writeln!(writer)?; for attribute in &self.attributes.attrs { - // TODO: look up other types with schema cache. write!(writer, "- {}", attribute.name)?; + + if let Some(type_info) = schema_cache.find_type_by_id(attribute.type_id) { + write!(writer, ": {}.{}", type_info.schema, type_info.name)?; + } else { + write!(writer, " (type_id: {})", attribute.type_id)?; + } + writeln!(writer)?; } @@ -36,7 +42,6 @@ impl ToHoverMarkdown for PostgresType { writeln!(writer)?; for kind in &self.enums.values { - // TODO: look up other types with schema cache. write!(writer, "- {}", kind)?; writeln!(writer)?; } @@ -47,7 +52,7 @@ impl ToHoverMarkdown for PostgresType { Ok(true) } - fn hover_footer(&self, writer: &mut W) -> Result { + fn hover_footer(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { writeln!(writer)?; Ok(true) } diff --git a/crates/pgt_hover/src/hoverables/role.rs b/crates/pgt_hover/src/hoverables/role.rs index 9908f08c3..89a8d7b66 100644 --- a/crates/pgt_hover/src/hoverables/role.rs +++ b/crates/pgt_hover/src/hoverables/role.rs @@ -1,18 +1,18 @@ use std::fmt::Write; -use pgt_schema_cache::Role; +use pgt_schema_cache::{Role, SchemaCache}; use pgt_treesitter::TreesitterContext; use crate::{contextual_priority::ContextualPriority, to_markdown::ToHoverMarkdown}; impl ToHoverMarkdown for pgt_schema_cache::Role { - fn hover_headline(&self, writer: &mut W) -> Result<(), std::fmt::Error> { + fn hover_headline(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result<(), std::fmt::Error> { write!(writer, "`{}`", self.name)?; Ok(()) } - fn hover_body(&self, writer: &mut W) -> Result { + fn hover_body(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { if let Some(comm) = self.comment.as_ref() { write!(writer, "Comment: '{}'", comm)?; writeln!(writer)?; @@ -81,7 +81,7 @@ impl ToHoverMarkdown for pgt_schema_cache::Role { Ok(true) } - fn hover_footer(&self, _writer: &mut W) -> Result { + fn hover_footer(&self, _writer: &mut W, _schema_cache: &SchemaCache) -> Result { Ok(false) } } diff --git a/crates/pgt_hover/src/hoverables/schema.rs b/crates/pgt_hover/src/hoverables/schema.rs index cb45a3c9b..5c0edbbe2 100644 --- a/crates/pgt_hover/src/hoverables/schema.rs +++ b/crates/pgt_hover/src/hoverables/schema.rs @@ -1,18 +1,18 @@ use std::fmt::Write; -use pgt_schema_cache::Schema; +use pgt_schema_cache::{Schema, SchemaCache}; use pgt_treesitter::TreesitterContext; use crate::{contextual_priority::ContextualPriority, to_markdown::ToHoverMarkdown}; impl ToHoverMarkdown for Schema { - fn hover_headline(&self, writer: &mut W) -> Result<(), std::fmt::Error> { + fn hover_headline(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result<(), std::fmt::Error> { write!(writer, "`{}` - owned by {}", self.name, self.owner)?; Ok(()) } - fn hover_body(&self, writer: &mut W) -> Result { + fn hover_body(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { if let Some(comment) = &self.comment { write!(writer, "Comment: '{}'", comment)?; writeln!(writer)?; @@ -46,7 +46,7 @@ impl ToHoverMarkdown for Schema { Ok(true) } - fn hover_footer(&self, writer: &mut W) -> Result { + fn hover_footer(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { writeln!(writer)?; write!( writer, diff --git a/crates/pgt_hover/src/hoverables/table.rs b/crates/pgt_hover/src/hoverables/table.rs index 4c457c28d..1e917df79 100644 --- a/crates/pgt_hover/src/hoverables/table.rs +++ b/crates/pgt_hover/src/hoverables/table.rs @@ -1,13 +1,13 @@ use std::fmt::Write; use humansize::DECIMAL; -use pgt_schema_cache::Table; +use pgt_schema_cache::{SchemaCache, Table}; use pgt_treesitter::TreesitterContext; use crate::{contextual_priority::ContextualPriority, to_markdown::ToHoverMarkdown}; impl ToHoverMarkdown for Table { - fn hover_headline(&self, writer: &mut W) -> Result<(), std::fmt::Error> { + fn hover_headline(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result<(), std::fmt::Error> { write!(writer, "`{}.{}`", self.schema, self.name)?; let table_kind = match self.table_kind { @@ -30,7 +30,7 @@ impl ToHoverMarkdown for Table { Ok(()) } - fn hover_body(&self, writer: &mut W) -> Result { + fn hover_body(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { if let Some(comment) = &self.comment { write!(writer, "Comment: '{}'", comment)?; writeln!(writer)?; @@ -40,7 +40,7 @@ impl ToHoverMarkdown for Table { } } - fn hover_footer(&self, writer: &mut W) -> Result { + fn hover_footer(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { writeln!(writer)?; write!( writer, diff --git a/crates/pgt_hover/src/lib.rs b/crates/pgt_hover/src/lib.rs index 379c615db..ff9aab023 100644 --- a/crates/pgt_hover/src/lib.rs +++ b/crates/pgt_hover/src/lib.rs @@ -140,7 +140,7 @@ pub fn on_hover(params: OnHoverParams) -> Vec { prioritize_by_context(items, &ctx) .into_iter() - .map(|item| format_hover_markdown(&item)) + .map(|item| format_hover_markdown(&item, params.schema_cache)) .filter_map(Result::ok) .collect() } else { diff --git a/crates/pgt_hover/src/to_markdown.rs b/crates/pgt_hover/src/to_markdown.rs index 1cd45c6b5..3dc6073fa 100644 --- a/crates/pgt_hover/src/to_markdown.rs +++ b/crates/pgt_hover/src/to_markdown.rs @@ -1,5 +1,7 @@ use std::fmt::Write; +use pgt_schema_cache::SchemaCache; + pub(crate) trait ToHoverMarkdown { fn body_markdown_type(&self) -> &'static str { "plain" @@ -9,25 +11,26 @@ pub(crate) trait ToHoverMarkdown { "plain" } - fn hover_headline(&self, writer: &mut W) -> Result<(), std::fmt::Error>; + fn hover_headline(&self, writer: &mut W, schema_cache: &SchemaCache) -> Result<(), std::fmt::Error>; - fn hover_body(&self, writer: &mut W) -> Result; // returns true if something was written + fn hover_body(&self, writer: &mut W, schema_cache: &SchemaCache) -> Result; // returns true if something was written - fn hover_footer(&self, writer: &mut W) -> Result; // returns true if something was written + fn hover_footer(&self, writer: &mut W, schema_cache: &SchemaCache) -> Result; // returns true if something was written } pub(crate) fn format_hover_markdown( item: &T, + schema_cache: &SchemaCache, ) -> Result { let mut markdown = String::new(); write!(markdown, "### ")?; - item.hover_headline(&mut markdown)?; + item.hover_headline(&mut markdown, schema_cache)?; markdown_newline(&mut markdown)?; write!(markdown, "```{}", item.body_markdown_type())?; markdown_newline(&mut markdown)?; - item.hover_body(&mut markdown)?; + item.hover_body(&mut markdown, schema_cache)?; markdown_newline(&mut markdown)?; write!(markdown, "```")?; @@ -37,7 +40,7 @@ pub(crate) fn format_hover_markdown( write!(markdown, "```{}", item.footer_markdown_type())?; markdown_newline(&mut markdown)?; - item.hover_footer(&mut markdown)?; + item.hover_footer(&mut markdown, schema_cache)?; markdown_newline(&mut markdown)?; write!(markdown, "```")?; diff --git a/crates/pgt_schema_cache/src/schema_cache.rs b/crates/pgt_schema_cache/src/schema_cache.rs index 227b49883..d90eeb952 100644 --- a/crates/pgt_schema_cache/src/schema_cache.rs +++ b/crates/pgt_schema_cache/src/schema_cache.rs @@ -98,6 +98,10 @@ impl SchemaCache { }) } + pub fn find_type_by_id(&self, id: i64) -> Option<&PostgresType> { + self.types.iter().find(|t| t.id == id) + } + pub fn find_cols(&self, name: &str, table: Option<&str>, schema: Option<&str>) -> Vec<&Column> { let sanitized_name = Self::sanitize_identifier(name); self.columns diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index e0505ddba..ece06d985 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -1252,7 +1252,7 @@ mod tests { } #[test] - fn tryout_node_field() { + fn verifies_node_has_field_name() { let query = format!( r#"create table foo (id int not null, compfoo som{}e_type);"#, QueryWithCursorPosition::cursor_marker() From 341a0d63ff676d3dbe46b14c8d4db51788b40ef0 Mon Sep 17 00:00:00 2001 From: Julian Date: Sun, 12 Oct 2025 14:37:30 +0200 Subject: [PATCH 04/16] ok --- .../pgt_hover/src/hoverables/postgres_type.rs | 26 ++++++++++++++++--- .../tests/hover_integration_tests.rs | 18 +++++++++++++ .../hover_custom_type_with_properties.snap | 24 +++++++++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 crates/pgt_hover/tests/snapshots/hover_custom_type_with_properties.snap diff --git a/crates/pgt_hover/src/hoverables/postgres_type.rs b/crates/pgt_hover/src/hoverables/postgres_type.rs index d26ccdfc9..e9dced294 100644 --- a/crates/pgt_hover/src/hoverables/postgres_type.rs +++ b/crates/pgt_hover/src/hoverables/postgres_type.rs @@ -6,12 +6,20 @@ use pgt_treesitter::TreesitterContext; use crate::{contextual_priority::ContextualPriority, to_markdown::ToHoverMarkdown}; impl ToHoverMarkdown for PostgresType { - fn hover_headline(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result<(), std::fmt::Error> { + fn hover_headline( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result<(), std::fmt::Error> { write!(writer, "`{}.{}` (Custom Type)", self.schema, self.name)?; Ok(()) } - fn hover_body(&self, writer: &mut W, schema_cache: &SchemaCache) -> Result { + fn hover_body( + &self, + writer: &mut W, + schema_cache: &SchemaCache, + ) -> Result { if let Some(comment) = &self.comment { write!(writer, "Comment: '{}'", comment)?; writeln!(writer)?; @@ -26,7 +34,13 @@ impl ToHoverMarkdown for PostgresType { write!(writer, "- {}", attribute.name)?; if let Some(type_info) = schema_cache.find_type_by_id(attribute.type_id) { - write!(writer, ": {}.{}", type_info.schema, type_info.name)?; + write!(writer, ": ")?; + + if type_info.schema != "pg_catalog" { + write!(writer, "{}.", type_info.schema)?; + } + + write!(writer, "{}", type_info.name)?; } else { write!(writer, " (type_id: {})", attribute.type_id)?; } @@ -52,7 +66,11 @@ impl ToHoverMarkdown for PostgresType { Ok(true) } - fn hover_footer(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { + fn hover_footer( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result { writeln!(writer)?; Ok(true) } diff --git a/crates/pgt_hover/tests/hover_integration_tests.rs b/crates/pgt_hover/tests/hover_integration_tests.rs index 58ffdc3e3..35e9da71e 100644 --- a/crates/pgt_hover/tests/hover_integration_tests.rs +++ b/crates/pgt_hover/tests/hover_integration_tests.rs @@ -518,3 +518,21 @@ async fn test_grant_table_hover(test_db: PgPool) { test_hover_at_cursor("grant_select", query, None, &test_db).await; } + +#[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] +async fn hover_on_types(test_db: PgPool) { + let setup = r#"create type compfoo as (f1 int, f2 text);"#; + + let query = format!( + "create function getfoo() returns setof comp{}foo as $$ select fooid, fooname from foo $$ language sql;", + QueryWithCursorPosition::cursor_marker() + ); + + test_hover_at_cursor( + "hover_custom_type_with_properties", + query, + Some(setup), + &test_db, + ) + .await; +} diff --git a/crates/pgt_hover/tests/snapshots/hover_custom_type_with_properties.snap b/crates/pgt_hover/tests/snapshots/hover_custom_type_with_properties.snap new file mode 100644 index 000000000..70c9b91f6 --- /dev/null +++ b/crates/pgt_hover/tests/snapshots/hover_custom_type_with_properties.snap @@ -0,0 +1,24 @@ +--- +source: crates/pgt_hover/tests/hover_integration_tests.rs +expression: snapshot +--- +# Input +```sql +create function getfoo() returns setof compfoo as $$ select fooid, fooname from foo $$ language sql; + ↑ hovered here +``` + +# Hover Results +### `public.compfoo` (Custom Type) +```plain +Attributes: +- f1: int4 +- f2: text + + +``` +--- +```plain + + +``` From 309c293f0898a79f3fdf546995426e16e971dd02 Mon Sep 17 00:00:00 2001 From: Julian Date: Sun, 12 Oct 2025 14:39:14 +0200 Subject: [PATCH 05/16] enums --- .../tests/hover_integration_tests.rs | 14 ++++++++++- .../snapshots/hover_custom_type_enum.snap | 24 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 crates/pgt_hover/tests/snapshots/hover_custom_type_enum.snap diff --git a/crates/pgt_hover/tests/hover_integration_tests.rs b/crates/pgt_hover/tests/hover_integration_tests.rs index 35e9da71e..7171284fa 100644 --- a/crates/pgt_hover/tests/hover_integration_tests.rs +++ b/crates/pgt_hover/tests/hover_integration_tests.rs @@ -520,7 +520,7 @@ async fn test_grant_table_hover(test_db: PgPool) { } #[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] -async fn hover_on_types(test_db: PgPool) { +async fn hover_on_composite_type(test_db: PgPool) { let setup = r#"create type compfoo as (f1 int, f2 text);"#; let query = format!( @@ -536,3 +536,15 @@ async fn hover_on_types(test_db: PgPool) { ) .await; } + +#[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] +async fn hover_on_enum_type(test_db: PgPool) { + let setup = r#"create type compfoo as ENUM ('yes', 'no');"#; + + let query = format!( + "create function getfoo() returns setof comp{}foo as $$ select fooid, fooname from foo $$ language sql;", + QueryWithCursorPosition::cursor_marker() + ); + + test_hover_at_cursor("hover_custom_type_enum", query, Some(setup), &test_db).await; +} diff --git a/crates/pgt_hover/tests/snapshots/hover_custom_type_enum.snap b/crates/pgt_hover/tests/snapshots/hover_custom_type_enum.snap new file mode 100644 index 000000000..6f9e9ffae --- /dev/null +++ b/crates/pgt_hover/tests/snapshots/hover_custom_type_enum.snap @@ -0,0 +1,24 @@ +--- +source: crates/pgt_hover/tests/hover_integration_tests.rs +expression: snapshot +--- +# Input +```sql +create function getfoo() returns setof compfoo as $$ select fooid, fooname from foo $$ language sql; + ↑ hovered here +``` + +# Hover Results +### `public.compfoo` (Custom Type) +```plain +Enum Permutations: +- yes +- no + + +``` +--- +```plain + + +``` From 01f0fb5c55f7ab6b8d77db5e0334953433fbce8c Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 17 Oct 2025 10:53:36 +0200 Subject: [PATCH 06/16] intermediary --- crates/pgt_treesitter/src/context/mod.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index ece06d985..c4d75cb77 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -1272,4 +1272,27 @@ mod tests { assert!(ctx.node_under_cursor_is_within_field_name("custom_type")); } + + #[test] + fn parses_custom_type_accesses_correctly() { + let query = format!( + r#"select (com{}pfoo).f1 from with_comp where (compfoo).f1 = 24;"#, + QueryWithCursorPosition::cursor_marker() + ); + + let (position, text) = QueryWithCursorPosition::from(query).get_text_and_position(); + + let tree = get_tree(text.as_str()); + + let params = TreeSitterContextParams { + position: (position as u32).into(), + text: &text, + tree: &tree, + }; + + let ctx = TreesitterContext::new(params); + + println!("{:#?}", ctx); + println!("{:#?}", ctx.get_node_under_cursor_content()) + } } From a1754e0607ffd3a5e00be17e5cd230e9cff1a0b6 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 17 Oct 2025 12:38:24 +0200 Subject: [PATCH 07/16] introduce new treesitter type --- crates/pgt_completions/src/relevance/filtering.rs | 2 +- crates/pgt_treesitter/src/queries/select_columns.rs | 6 ++++-- crates/pgt_treesitter/src/queries/where_columns.rs | 6 ++++-- crates/pgt_treesitter_grammar/grammar.js | 9 +++++---- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/crates/pgt_completions/src/relevance/filtering.rs b/crates/pgt_completions/src/relevance/filtering.rs index 6b263e889..c11390bd1 100644 --- a/crates/pgt_completions/src/relevance/filtering.rs +++ b/crates/pgt_completions/src/relevance/filtering.rs @@ -163,7 +163,7 @@ impl CompletionFilter<'_> { // only autocomplete left side of binary expression WrappingClause::Where => { ctx.before_cursor_matches_kind(&["keyword_and", "keyword_where"]) - || (ctx.before_cursor_matches_kind(&["."]) + || (ctx.before_cursor_matches_kind(&["field_qualifier"]) && ctx.matches_ancestor_history(&["field"])) } diff --git a/crates/pgt_treesitter/src/queries/select_columns.rs b/crates/pgt_treesitter/src/queries/select_columns.rs index ea3eb9bd1..d8fa1d16a 100644 --- a/crates/pgt_treesitter/src/queries/select_columns.rs +++ b/crates/pgt_treesitter/src/queries/select_columns.rs @@ -10,8 +10,10 @@ static TS_QUERY: LazyLock = LazyLock::new(|| { (select_expression (term (field - (object_reference)? @alias - "."? + (field_qualifier + (object_reference) @alias + "." + )? (identifier) @column ) ) diff --git a/crates/pgt_treesitter/src/queries/where_columns.rs b/crates/pgt_treesitter/src/queries/where_columns.rs index 27b466eac..b3371518c 100644 --- a/crates/pgt_treesitter/src/queries/where_columns.rs +++ b/crates/pgt_treesitter/src/queries/where_columns.rs @@ -12,8 +12,10 @@ static TS_QUERY: LazyLock = LazyLock::new(|| { (binary_expression (binary_expression (field - (object_reference)? @alias - "."? + (field_qualifier + (object_reference) @alias + "." + )? (identifier) @column ) ) diff --git a/crates/pgt_treesitter_grammar/grammar.js b/crates/pgt_treesitter_grammar/grammar.js index 15accb440..01eaef8e7 100644 --- a/crates/pgt_treesitter_grammar/grammar.js +++ b/crates/pgt_treesitter_grammar/grammar.js @@ -21,6 +21,7 @@ module.exports = grammar({ ], conflicts: ($) => [ + [$.all_fields, $.field_qualifier], [$.object_reference, $._qualified_field], [$.object_reference], [$.between_expression, $.binary_expression], @@ -2805,10 +2806,10 @@ module.exports = grammar({ field: ($) => field("name", $.identifier), _qualified_field: ($) => - seq( - optional(seq(optional_parenthesis($.object_reference), ".")), - field("name", $.identifier) - ), + seq(optional($.field_qualifier), field("name", $.identifier)), + + field_qualifier: ($) => + seq(prec.left(optional_parenthesis($.object_reference)), "."), implicit_cast: ($) => seq($._expression, "::", $.type), From 05555a197e0b5bebda10a7ca3c1153bd73c1747a Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 17 Oct 2025 13:29:29 +0200 Subject: [PATCH 08/16] ok --- .../pgt_completions/src/providers/columns.rs | 32 +++++++++---------- crates/pgt_hover/src/hovered_node.rs | 23 ++++++++++--- .../tests/hover_integration_tests.rs | 12 +++++++ .../hover_type_in_select_clause.snap | 24 ++++++++++++++ crates/pgt_schema_cache/src/schema_cache.rs | 2 ++ crates/pgt_treesitter/src/context/mod.rs | 7 +++- 6 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 crates/pgt_hover/tests/snapshots/hover_type_in_select_clause.snap diff --git a/crates/pgt_completions/src/providers/columns.rs b/crates/pgt_completions/src/providers/columns.rs index 1f404627c..22b18b25a 100644 --- a/crates/pgt_completions/src/providers/columns.rs +++ b/crates/pgt_completions/src/providers/columns.rs @@ -528,22 +528,22 @@ mod tests { ) .await; - assert_complete_results( - format!( - "select u.id, p.content from auth.users u join auth.posts p on p.user_id = u.{}", - QueryWithCursorPosition::cursor_marker() - ) - .as_str(), - vec![ - CompletionAssertion::KindNotExists(CompletionItemKind::Table), - CompletionAssertion::LabelAndKind("uid".to_string(), CompletionItemKind::Column), - CompletionAssertion::LabelAndKind("email".to_string(), CompletionItemKind::Column), - CompletionAssertion::LabelAndKind("name".to_string(), CompletionItemKind::Column), - ], - None, - &pool, - ) - .await; + // assert_complete_results( + // format!( + // "select u.id, p.content from auth.users u join auth.posts p on p.user_id = u.{}", + // QueryWithCursorPosition::cursor_marker() + // ) + // .as_str(), + // vec![ + // CompletionAssertion::KindNotExists(CompletionItemKind::Table), + // CompletionAssertion::LabelAndKind("uid".to_string(), CompletionItemKind::Column), + // CompletionAssertion::LabelAndKind("email".to_string(), CompletionItemKind::Column), + // CompletionAssertion::LabelAndKind("name".to_string(), CompletionItemKind::Column), + // ], + // None, + // &pool, + // ) + // .await; } #[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] diff --git a/crates/pgt_hover/src/hovered_node.rs b/crates/pgt_hover/src/hovered_node.rs index 76f144d21..56e842e38 100644 --- a/crates/pgt_hover/src/hovered_node.rs +++ b/crates/pgt_hover/src/hovered_node.rs @@ -94,16 +94,31 @@ impl HoveredNode { } "identifier" - if ctx.matches_ancestor_history(&["type", "object_reference"]) - && ctx.node_under_cursor_is_within_field_name("custom_type") => + if ( + // hover over custom type in create table, returns… + (ctx.matches_ancestor_history(&["type", "object_reference"]) + && ctx.node_under_cursor_is_within_field_name("custom_type")) + + // hover over type in select clause etc… + || (ctx + .matches_ancestor_history(&["field_qualifier", "object_reference"]) + && ctx.before_cursor_matches_kind(&["("]))) + + // make sure we're not checking against an alias + && ctx + .get_mentioned_table_for_alias( + node_content.replace('(', "").replace(')', "").as_str(), + ) + .is_none() => { + let sanitized = node_content.replace('(', "").replace(')', ""); if let Some(schema) = ctx.schema_or_alias_name.as_ref() { Some(HoveredNode::PostgresType( - NodeIdentification::SchemaAndName((schema.clone(), node_content)), + NodeIdentification::SchemaAndName((schema.clone(), sanitized)), )) } else { Some(HoveredNode::PostgresType(NodeIdentification::Name( - node_content, + sanitized, ))) } } diff --git a/crates/pgt_hover/tests/hover_integration_tests.rs b/crates/pgt_hover/tests/hover_integration_tests.rs index 7171284fa..002b540d3 100644 --- a/crates/pgt_hover/tests/hover_integration_tests.rs +++ b/crates/pgt_hover/tests/hover_integration_tests.rs @@ -548,3 +548,15 @@ async fn hover_on_enum_type(test_db: PgPool) { test_hover_at_cursor("hover_custom_type_enum", query, Some(setup), &test_db).await; } + +#[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] +async fn hover_type_in_select_clause(test_db: PgPool) { + let setup = r#"create type compfoo as (f1 int, f2 text);"#; + + let query = format!( + "select (co{}mpfoo).f1 from some_table s;", + QueryWithCursorPosition::cursor_marker() + ); + + test_hover_at_cursor("hover_type_in_select_clause", query, Some(setup), &test_db).await; +} diff --git a/crates/pgt_hover/tests/snapshots/hover_type_in_select_clause.snap b/crates/pgt_hover/tests/snapshots/hover_type_in_select_clause.snap new file mode 100644 index 000000000..9586dad4b --- /dev/null +++ b/crates/pgt_hover/tests/snapshots/hover_type_in_select_clause.snap @@ -0,0 +1,24 @@ +--- +source: crates/pgt_hover/tests/hover_integration_tests.rs +expression: snapshot +--- +# Input +```sql +select (compfoo).f1 from some_table s; + ↑ hovered here +``` + +# Hover Results +### `public.compfoo` (Custom Type) +```plain +Attributes: +- f1: int4 +- f2: text + + +``` +--- +```plain + + +``` diff --git a/crates/pgt_schema_cache/src/schema_cache.rs b/crates/pgt_schema_cache/src/schema_cache.rs index d90eeb952..8b9e125ff 100644 --- a/crates/pgt_schema_cache/src/schema_cache.rs +++ b/crates/pgt_schema_cache/src/schema_cache.rs @@ -89,6 +89,8 @@ impl SchemaCache { pub fn find_type(&self, name: &str, schema: Option<&str>) -> Option<&PostgresType> { let sanitized_name = Self::sanitize_identifier(name); + println!("findng type… {}", sanitized_name); + println!("findng schema… {:#?}", schema); self.types.iter().find(|t| { t.name == sanitized_name && schema diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index c4d75cb77..8f0874ad0 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -205,6 +205,8 @@ impl<'a> TreesitterContext<'a> { ctx.gather_info_from_ts_queries(); } + println!("{:#?}", ctx); + ctx } @@ -438,10 +440,13 @@ impl<'a> TreesitterContext<'a> { match current_node_kind { "object_reference" | "field" => { + let start = current_node.start_byte(); let content = self.get_ts_node_content(¤t_node); if let Some(txt) = content { let parts: Vec<&str> = txt.split('.').collect(); - if parts.len() == 2 { + // we do not want to set it if we're on the schema or alias node itself + let is_on_schema_node = start + parts[0].len() < self.position; + if parts.len() == 2 && !is_on_schema_node { self.schema_or_alias_name = Some(parts[0].to_string()); } } From e87eb381a912a86b8d2c0058a8d04115b08e1b25 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 17 Oct 2025 13:31:28 +0200 Subject: [PATCH 09/16] cool --- crates/pgt_treesitter/src/context/mod.rs | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index 8f0874ad0..d358c4d77 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -1277,27 +1277,4 @@ mod tests { assert!(ctx.node_under_cursor_is_within_field_name("custom_type")); } - - #[test] - fn parses_custom_type_accesses_correctly() { - let query = format!( - r#"select (com{}pfoo).f1 from with_comp where (compfoo).f1 = 24;"#, - QueryWithCursorPosition::cursor_marker() - ); - - let (position, text) = QueryWithCursorPosition::from(query).get_text_and_position(); - - let tree = get_tree(text.as_str()); - - let params = TreeSitterContextParams { - position: (position as u32).into(), - text: &text, - tree: &tree, - }; - - let ctx = TreesitterContext::new(params); - - println!("{:#?}", ctx); - println!("{:#?}", ctx.get_node_under_cursor_content()) - } } From 69045b1260a2d144478b212b2897f68f0cdddde0 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 17 Oct 2025 13:44:37 +0200 Subject: [PATCH 10/16] cool mate --- crates/pgt_schema_cache/src/schema_cache.rs | 2 -- crates/pgt_treesitter/src/context/mod.rs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/pgt_schema_cache/src/schema_cache.rs b/crates/pgt_schema_cache/src/schema_cache.rs index 31c5271f0..c4d57b81d 100644 --- a/crates/pgt_schema_cache/src/schema_cache.rs +++ b/crates/pgt_schema_cache/src/schema_cache.rs @@ -89,8 +89,6 @@ impl SchemaCache { pub fn find_type(&self, name: &str, schema: Option<&str>) -> Option<&PostgresType> { let sanitized_name = Self::sanitize_identifier(name); - println!("findng type… {}", sanitized_name); - println!("findng schema… {:#?}", schema); self.types.iter().find(|t| { t.name == sanitized_name && schema diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index 5b257b04b..761331898 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -445,7 +445,7 @@ impl<'a> TreesitterContext<'a> { if let Some(txt) = content { let parts: Vec<&str> = txt.split('.').collect(); // we do not want to set it if we're on the schema or alias node itself - let is_on_schema_node = start + parts[0].len() < self.position; + let is_on_schema_node = start + parts[0].len() >= self.position; if parts.len() == 2 && !is_on_schema_node { self.schema_or_alias_name = Some(parts[0].to_string()); } From 17b1739bdd6fec6dfa69737aed8f97ec099f657a Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 17 Oct 2025 13:45:42 +0200 Subject: [PATCH 11/16] want to do that --- .../pgt_completions/src/providers/columns.rs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/pgt_completions/src/providers/columns.rs b/crates/pgt_completions/src/providers/columns.rs index 22b18b25a..1f404627c 100644 --- a/crates/pgt_completions/src/providers/columns.rs +++ b/crates/pgt_completions/src/providers/columns.rs @@ -528,22 +528,22 @@ mod tests { ) .await; - // assert_complete_results( - // format!( - // "select u.id, p.content from auth.users u join auth.posts p on p.user_id = u.{}", - // QueryWithCursorPosition::cursor_marker() - // ) - // .as_str(), - // vec![ - // CompletionAssertion::KindNotExists(CompletionItemKind::Table), - // CompletionAssertion::LabelAndKind("uid".to_string(), CompletionItemKind::Column), - // CompletionAssertion::LabelAndKind("email".to_string(), CompletionItemKind::Column), - // CompletionAssertion::LabelAndKind("name".to_string(), CompletionItemKind::Column), - // ], - // None, - // &pool, - // ) - // .await; + assert_complete_results( + format!( + "select u.id, p.content from auth.users u join auth.posts p on p.user_id = u.{}", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), + vec![ + CompletionAssertion::KindNotExists(CompletionItemKind::Table), + CompletionAssertion::LabelAndKind("uid".to_string(), CompletionItemKind::Column), + CompletionAssertion::LabelAndKind("email".to_string(), CompletionItemKind::Column), + CompletionAssertion::LabelAndKind("name".to_string(), CompletionItemKind::Column), + ], + None, + &pool, + ) + .await; } #[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] From c014d67195a0286ad7c9c353da13fe8bbd42b63d Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 17 Oct 2025 13:49:24 +0200 Subject: [PATCH 12/16] okay! --- crates/pgt_hover/src/hovered_node.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pgt_hover/src/hovered_node.rs b/crates/pgt_hover/src/hovered_node.rs index e84276ec2..4b430c39e 100644 --- a/crates/pgt_hover/src/hovered_node.rs +++ b/crates/pgt_hover/src/hovered_node.rs @@ -99,11 +99,11 @@ impl HoveredNode { "identifier" if ( - // hover over custom type in create table, returns… + // hover over custom type in `create table` or `returns` (ctx.matches_ancestor_history(&["type", "object_reference"]) && ctx.node_under_cursor_is_within_field_name("custom_type")) - // hover over type in select clause etc… + // hover over type in `select` clause etc… || (ctx .matches_ancestor_history(&["field_qualifier", "object_reference"]) && ctx.before_cursor_matches_kind(&["("]))) From 60fde76905bc71d25b5760636b3fd5c8f7bbebb0 Mon Sep 17 00:00:00 2001 From: Julian Date: Sun, 19 Oct 2025 12:00:04 +0200 Subject: [PATCH 13/16] readied --- packages/@postgrestools/backend-jsonrpc/src/workspace.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/@postgrestools/backend-jsonrpc/src/workspace.ts b/packages/@postgrestools/backend-jsonrpc/src/workspace.ts index 24ea5d503..eb681cc41 100644 --- a/packages/@postgrestools/backend-jsonrpc/src/workspace.ts +++ b/packages/@postgrestools/backend-jsonrpc/src/workspace.ts @@ -122,7 +122,7 @@ export type DiagnosticTags = DiagnosticTag[]; /** * Serializable representation of a [Diagnostic](super::Diagnostic) advice -See the [Visitor] trait for additional documentation on all the supported advice types. +See the [Visitor] trait for additional documentation on all the supported advice types. */ export type Advice = | { log: [LogCategory, MarkupBuf] } @@ -227,7 +227,7 @@ export interface CompletionItem { /** * The text that the editor should fill in. If `None`, the `label` should be used. Tables, for example, might have different completion_texts: -label: "users", description: "Schema: auth", completion_text: "auth.users". +label: "users", description: "Schema: auth", completion_text: "auth.users". */ export interface CompletionText { is_snippet: boolean; @@ -411,7 +411,7 @@ export interface PartialVcsConfiguration { /** * The folder where we should check for VCS files. By default, we will use the same folder where `postgres-language-server.jsonc` was found. -If we can't find the configuration, it will attempt to use the current working directory. If no current working directory can't be found, we won't use the VCS integration, and a diagnostic will be emitted +If we can't find the configuration, it will attempt to use the current working directory. If no current working directory can't be found, we won't use the VCS integration, and a diagnostic will be emitted */ root?: string; /** From e8321771dd1de7f1083ccb2da2f572b376f5a0f9 Mon Sep 17 00:00:00 2001 From: Julian Date: Sun, 19 Oct 2025 12:02:18 +0200 Subject: [PATCH 14/16] just readied again --- crates/pgt_hover/src/hoverables/column.rs | 18 +++++- crates/pgt_hover/src/hoverables/function.rs | 18 +++++- crates/pgt_hover/src/hoverables/mod.rs | 62 ++++++++++++++++----- crates/pgt_hover/src/hoverables/role.rs | 18 +++++- crates/pgt_hover/src/hoverables/schema.rs | 18 +++++- crates/pgt_hover/src/hoverables/table.rs | 18 +++++- crates/pgt_hover/src/hovered_node.rs | 4 +- crates/pgt_hover/src/to_markdown.rs | 18 +++++- crates/pgt_treesitter/src/context/mod.rs | 2 +- 9 files changed, 141 insertions(+), 35 deletions(-) diff --git a/crates/pgt_hover/src/hoverables/column.rs b/crates/pgt_hover/src/hoverables/column.rs index 54ebf130c..43e407163 100644 --- a/crates/pgt_hover/src/hoverables/column.rs +++ b/crates/pgt_hover/src/hoverables/column.rs @@ -6,7 +6,11 @@ use pgt_treesitter::TreesitterContext; use crate::{contextual_priority::ContextualPriority, to_markdown::ToHoverMarkdown}; impl ToHoverMarkdown for pgt_schema_cache::Column { - fn hover_headline(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result<(), std::fmt::Error> { + fn hover_headline( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result<(), std::fmt::Error> { write!( writer, "`{}.{}.{}`", @@ -14,7 +18,11 @@ impl ToHoverMarkdown for pgt_schema_cache::Column { ) } - fn hover_body(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { + fn hover_body( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result { if let Some(comment) = &self.comment { write!(writer, "Comment: '{}'", comment)?; writeln!(writer)?; @@ -46,7 +54,11 @@ impl ToHoverMarkdown for pgt_schema_cache::Column { Ok(true) } - fn hover_footer(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { + fn hover_footer( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result { if let Some(default) = &self.default_expr { writeln!(writer)?; write!(writer, "Default: {}", default)?; diff --git a/crates/pgt_hover/src/hoverables/function.rs b/crates/pgt_hover/src/hoverables/function.rs index 03746870e..171c78f6e 100644 --- a/crates/pgt_hover/src/hoverables/function.rs +++ b/crates/pgt_hover/src/hoverables/function.rs @@ -10,7 +10,11 @@ impl ToHoverMarkdown for Function { "sql" } - fn hover_headline(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result<(), std::fmt::Error> { + fn hover_headline( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result<(), std::fmt::Error> { write!(writer, "`{}.{}", self.schema, self.name)?; if let Some(args) = &self.argument_types { @@ -28,7 +32,11 @@ impl ToHoverMarkdown for Function { Ok(()) } - fn hover_body(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { + fn hover_body( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result { let kind_text = match self.kind { pgt_schema_cache::ProcKind::Function => "Function", pgt_schema_cache::ProcKind::Procedure => "Procedure", @@ -55,7 +63,11 @@ impl ToHoverMarkdown for Function { Ok(true) } - fn hover_footer(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { + fn hover_footer( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result { if let Some(def) = self.definition.as_ref() { /* * We don't want to show 250 lines of functions to the user. diff --git a/crates/pgt_hover/src/hoverables/mod.rs b/crates/pgt_hover/src/hoverables/mod.rs index f6f634615..ad8bc19aa 100644 --- a/crates/pgt_hover/src/hoverables/mod.rs +++ b/crates/pgt_hover/src/hoverables/mod.rs @@ -72,36 +72,70 @@ impl ContextualPriority for Hoverable<'_> { } impl ToHoverMarkdown for Hoverable<'_> { - fn hover_headline(&self, writer: &mut W, schema_cache: &SchemaCache) -> Result<(), std::fmt::Error> { + fn hover_headline( + &self, + writer: &mut W, + schema_cache: &SchemaCache, + ) -> Result<(), std::fmt::Error> { match self { - Hoverable::Table(table) => ToHoverMarkdown::hover_headline(*table, writer, schema_cache), - Hoverable::Column(column) => ToHoverMarkdown::hover_headline(*column, writer, schema_cache), - Hoverable::Function(function) => ToHoverMarkdown::hover_headline(*function, writer, schema_cache), + Hoverable::Table(table) => { + ToHoverMarkdown::hover_headline(*table, writer, schema_cache) + } + Hoverable::Column(column) => { + ToHoverMarkdown::hover_headline(*column, writer, schema_cache) + } + Hoverable::Function(function) => { + ToHoverMarkdown::hover_headline(*function, writer, schema_cache) + } Hoverable::Role(role) => ToHoverMarkdown::hover_headline(*role, writer, schema_cache), - Hoverable::Schema(schema) => ToHoverMarkdown::hover_headline(*schema, writer, schema_cache), - Hoverable::PostgresType(type_) => ToHoverMarkdown::hover_headline(*type_, writer, schema_cache), + Hoverable::Schema(schema) => { + ToHoverMarkdown::hover_headline(*schema, writer, schema_cache) + } + Hoverable::PostgresType(type_) => { + ToHoverMarkdown::hover_headline(*type_, writer, schema_cache) + } } } - fn hover_body(&self, writer: &mut W, schema_cache: &SchemaCache) -> Result { + fn hover_body( + &self, + writer: &mut W, + schema_cache: &SchemaCache, + ) -> Result { match self { Hoverable::Table(table) => ToHoverMarkdown::hover_body(*table, writer, schema_cache), Hoverable::Column(column) => ToHoverMarkdown::hover_body(*column, writer, schema_cache), - Hoverable::Function(function) => ToHoverMarkdown::hover_body(*function, writer, schema_cache), + Hoverable::Function(function) => { + ToHoverMarkdown::hover_body(*function, writer, schema_cache) + } Hoverable::Role(role) => ToHoverMarkdown::hover_body(*role, writer, schema_cache), Hoverable::Schema(schema) => ToHoverMarkdown::hover_body(*schema, writer, schema_cache), - Hoverable::PostgresType(type_) => ToHoverMarkdown::hover_body(*type_, writer, schema_cache), + Hoverable::PostgresType(type_) => { + ToHoverMarkdown::hover_body(*type_, writer, schema_cache) + } } } - fn hover_footer(&self, writer: &mut W, schema_cache: &SchemaCache) -> Result { + fn hover_footer( + &self, + writer: &mut W, + schema_cache: &SchemaCache, + ) -> Result { match self { Hoverable::Table(table) => ToHoverMarkdown::hover_footer(*table, writer, schema_cache), - Hoverable::Column(column) => ToHoverMarkdown::hover_footer(*column, writer, schema_cache), - Hoverable::Function(function) => ToHoverMarkdown::hover_footer(*function, writer, schema_cache), + Hoverable::Column(column) => { + ToHoverMarkdown::hover_footer(*column, writer, schema_cache) + } + Hoverable::Function(function) => { + ToHoverMarkdown::hover_footer(*function, writer, schema_cache) + } Hoverable::Role(role) => ToHoverMarkdown::hover_footer(*role, writer, schema_cache), - Hoverable::Schema(schema) => ToHoverMarkdown::hover_footer(*schema, writer, schema_cache), - Hoverable::PostgresType(type_) => ToHoverMarkdown::hover_footer(*type_, writer, schema_cache), + Hoverable::Schema(schema) => { + ToHoverMarkdown::hover_footer(*schema, writer, schema_cache) + } + Hoverable::PostgresType(type_) => { + ToHoverMarkdown::hover_footer(*type_, writer, schema_cache) + } } } diff --git a/crates/pgt_hover/src/hoverables/role.rs b/crates/pgt_hover/src/hoverables/role.rs index 89a8d7b66..d6b440382 100644 --- a/crates/pgt_hover/src/hoverables/role.rs +++ b/crates/pgt_hover/src/hoverables/role.rs @@ -6,13 +6,21 @@ use pgt_treesitter::TreesitterContext; use crate::{contextual_priority::ContextualPriority, to_markdown::ToHoverMarkdown}; impl ToHoverMarkdown for pgt_schema_cache::Role { - fn hover_headline(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result<(), std::fmt::Error> { + fn hover_headline( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result<(), std::fmt::Error> { write!(writer, "`{}`", self.name)?; Ok(()) } - fn hover_body(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { + fn hover_body( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result { if let Some(comm) = self.comment.as_ref() { write!(writer, "Comment: '{}'", comm)?; writeln!(writer)?; @@ -81,7 +89,11 @@ impl ToHoverMarkdown for pgt_schema_cache::Role { Ok(true) } - fn hover_footer(&self, _writer: &mut W, _schema_cache: &SchemaCache) -> Result { + fn hover_footer( + &self, + _writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result { Ok(false) } } diff --git a/crates/pgt_hover/src/hoverables/schema.rs b/crates/pgt_hover/src/hoverables/schema.rs index 5c0edbbe2..90281371a 100644 --- a/crates/pgt_hover/src/hoverables/schema.rs +++ b/crates/pgt_hover/src/hoverables/schema.rs @@ -6,13 +6,21 @@ use pgt_treesitter::TreesitterContext; use crate::{contextual_priority::ContextualPriority, to_markdown::ToHoverMarkdown}; impl ToHoverMarkdown for Schema { - fn hover_headline(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result<(), std::fmt::Error> { + fn hover_headline( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result<(), std::fmt::Error> { write!(writer, "`{}` - owned by {}", self.name, self.owner)?; Ok(()) } - fn hover_body(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { + fn hover_body( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result { if let Some(comment) = &self.comment { write!(writer, "Comment: '{}'", comment)?; writeln!(writer)?; @@ -46,7 +54,11 @@ impl ToHoverMarkdown for Schema { Ok(true) } - fn hover_footer(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { + fn hover_footer( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result { writeln!(writer)?; write!( writer, diff --git a/crates/pgt_hover/src/hoverables/table.rs b/crates/pgt_hover/src/hoverables/table.rs index 1e917df79..a12132102 100644 --- a/crates/pgt_hover/src/hoverables/table.rs +++ b/crates/pgt_hover/src/hoverables/table.rs @@ -7,7 +7,11 @@ use pgt_treesitter::TreesitterContext; use crate::{contextual_priority::ContextualPriority, to_markdown::ToHoverMarkdown}; impl ToHoverMarkdown for Table { - fn hover_headline(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result<(), std::fmt::Error> { + fn hover_headline( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result<(), std::fmt::Error> { write!(writer, "`{}.{}`", self.schema, self.name)?; let table_kind = match self.table_kind { @@ -30,7 +34,11 @@ impl ToHoverMarkdown for Table { Ok(()) } - fn hover_body(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { + fn hover_body( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result { if let Some(comment) = &self.comment { write!(writer, "Comment: '{}'", comment)?; writeln!(writer)?; @@ -40,7 +48,11 @@ impl ToHoverMarkdown for Table { } } - fn hover_footer(&self, writer: &mut W, _schema_cache: &SchemaCache) -> Result { + fn hover_footer( + &self, + writer: &mut W, + _schema_cache: &SchemaCache, + ) -> Result { writeln!(writer)?; write!( writer, diff --git a/crates/pgt_hover/src/hovered_node.rs b/crates/pgt_hover/src/hovered_node.rs index 4b430c39e..b0d1cf0f3 100644 --- a/crates/pgt_hover/src/hovered_node.rs +++ b/crates/pgt_hover/src/hovered_node.rs @@ -111,11 +111,11 @@ impl HoveredNode { // make sure we're not checking against an alias && ctx .get_mentioned_table_for_alias( - node_content.replace('(', "").replace(')', "").as_str(), + node_content.replace(['(', ')'], "").as_str(), ) .is_none() => { - let sanitized = node_content.replace('(', "").replace(')', ""); + let sanitized = node_content.replace(['(', ')'], ""); if let Some(schema) = ctx.schema_or_alias_name.as_ref() { Some(HoveredNode::PostgresType( NodeIdentification::SchemaAndName((schema.clone(), sanitized)), diff --git a/crates/pgt_hover/src/to_markdown.rs b/crates/pgt_hover/src/to_markdown.rs index 3dc6073fa..ecc0f60f8 100644 --- a/crates/pgt_hover/src/to_markdown.rs +++ b/crates/pgt_hover/src/to_markdown.rs @@ -11,11 +11,23 @@ pub(crate) trait ToHoverMarkdown { "plain" } - fn hover_headline(&self, writer: &mut W, schema_cache: &SchemaCache) -> Result<(), std::fmt::Error>; + fn hover_headline( + &self, + writer: &mut W, + schema_cache: &SchemaCache, + ) -> Result<(), std::fmt::Error>; - fn hover_body(&self, writer: &mut W, schema_cache: &SchemaCache) -> Result; // returns true if something was written + fn hover_body( + &self, + writer: &mut W, + schema_cache: &SchemaCache, + ) -> Result; // returns true if something was written - fn hover_footer(&self, writer: &mut W, schema_cache: &SchemaCache) -> Result; // returns true if something was written + fn hover_footer( + &self, + writer: &mut W, + schema_cache: &SchemaCache, + ) -> Result; // returns true if something was written } pub(crate) fn format_hover_markdown( diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index 761331898..aeb94391d 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -862,7 +862,7 @@ impl<'a> TreesitterContext<'a> { } } - return false; + false } NodeUnderCursor::CustomNode { .. } => false, }) From 03c6b4b1948598382f0603641ab14d91ed014e5b Mon Sep 17 00:00:00 2001 From: Julian Date: Sun, 19 Oct 2025 12:04:39 +0200 Subject: [PATCH 15/16] remove log --- crates/pgt_treesitter/src/context/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index aeb94391d..9c404dadb 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -205,8 +205,6 @@ impl<'a> TreesitterContext<'a> { ctx.gather_info_from_ts_queries(); } - println!("{:#?}", ctx); - ctx } From 23daa18a03da3f9ffd799ae977a912d7627ac875 Mon Sep 17 00:00:00 2001 From: Julian Date: Sun, 19 Oct 2025 16:31:34 +0200 Subject: [PATCH 16/16] fix ts query --- crates/pgt_treesitter/src/queries/parameters.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/pgt_treesitter/src/queries/parameters.rs b/crates/pgt_treesitter/src/queries/parameters.rs index b64c73ae7..a137cc8de 100644 --- a/crates/pgt_treesitter/src/queries/parameters.rs +++ b/crates/pgt_treesitter/src/queries/parameters.rs @@ -10,10 +10,10 @@ static TS_QUERY: LazyLock = LazyLock::new(|| { static QUERY_STR: &str = r#" [ (field - (identifier)) @reference - (field - (object_reference) - "." (identifier)) @reference + (field_qualifier)? + (identifier) + ) @reference + (parameter) @parameter ] "#;