From 768f92b0523a56176074e0b0d34da587abfe9533 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 18 Jul 2025 16:46:25 +0200 Subject: [PATCH 1/5] intermeidate --- Cargo.lock | 19 ++++++++ Cargo.toml | 1 + crates/pgt_hover/Cargo.toml | 35 +++++++++++++++ crates/pgt_hover/src/hovered_node.rs | 25 +++++++++++ crates/pgt_hover/src/lib.rs | 19 ++++++++ crates/pgt_hover/src/to_markdown.rs | 3 ++ crates/pgt_lsp/src/handlers.rs | 1 + crates/pgt_lsp/src/handlers/hover.rs | 36 ++++++++++++++++ crates/pgt_lsp/src/server.rs | 9 ++++ crates/pgt_workspace/Cargo.toml | 1 + .../pgt_workspace/src/features/completions.rs | 8 ++-- crates/pgt_workspace/src/features/mod.rs | 1 + crates/pgt_workspace/src/features/on_hover.rs | 25 +++++++++++ crates/pgt_workspace/src/workspace.rs | 3 ++ crates/pgt_workspace/src/workspace/client.rs | 7 +++ crates/pgt_workspace/src/workspace/server.rs | 43 +++++++++++++++++-- .../src/workspace/server/document.rs | 31 ++++++++++--- 17 files changed, 255 insertions(+), 12 deletions(-) create mode 100644 crates/pgt_hover/Cargo.toml create mode 100644 crates/pgt_hover/src/hovered_node.rs create mode 100644 crates/pgt_hover/src/lib.rs create mode 100644 crates/pgt_hover/src/to_markdown.rs create mode 100644 crates/pgt_lsp/src/handlers/hover.rs create mode 100644 crates/pgt_workspace/src/features/on_hover.rs diff --git a/Cargo.lock b/Cargo.lock index 16b1de5e6..80dd4df96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2874,6 +2874,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "pgt_hover" +version = "0.0.0" +dependencies = [ + "pgt_query_ext", + "pgt_schema_cache", + "pgt_test_utils", + "pgt_text_size", + "schemars", + "serde", + "serde_json", + "sqlx", + "tokio", + "tracing", + "tree-sitter", + "tree_sitter_sql", +] + [[package]] name = "pgt_lexer" version = "0.0.0" @@ -3100,6 +3118,7 @@ dependencies = [ "pgt_console", "pgt_diagnostics", "pgt_fs", + "pgt_hover", "pgt_lexer", "pgt_query_ext", "pgt_schema_cache", diff --git a/Cargo.toml b/Cargo.toml index 15c6f02ff..edf9a0e9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,7 @@ pgt_diagnostics_categories = { path = "./crates/pgt_diagnostics_categories", ver pgt_diagnostics_macros = { path = "./crates/pgt_diagnostics_macros", version = "0.0.0" } pgt_flags = { path = "./crates/pgt_flags", version = "0.0.0" } pgt_fs = { path = "./crates/pgt_fs", version = "0.0.0" } +pgt_hover = { path = "./crates/pgt_hover", version = "0.0.0" } pgt_lexer = { path = "./crates/pgt_lexer", version = "0.0.0" } pgt_lexer_codegen = { path = "./crates/pgt_lexer_codegen", version = "0.0.0" } pgt_lsp = { path = "./crates/pgt_lsp", version = "0.0.0" } diff --git a/crates/pgt_hover/Cargo.toml b/crates/pgt_hover/Cargo.toml new file mode 100644 index 000000000..699bbc98f --- /dev/null +++ b/crates/pgt_hover/Cargo.toml @@ -0,0 +1,35 @@ +[package] +authors.workspace = true +categories.workspace = true +description = "" +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "pgt_hover" +repository.workspace = true +version = "0.0.0" + + +[dependencies] +pgt_text_size.workspace = true +pgt_schema_cache.workspace = true +pgt_query_ext.workspace = true +schemars = { workspace = true, optional = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +tracing = { workspace = true } +tree-sitter.workspace = true +tree_sitter_sql.workspace = true +sqlx.workspace = true +tokio = { version = "1.41.1", features = ["full"] } + +[dev-dependencies] +pgt_test_utils.workspace = true + +[lib] +doctest = false + +[features] +schema = ["dep:schemars"] + diff --git a/crates/pgt_hover/src/hovered_node.rs b/crates/pgt_hover/src/hovered_node.rs new file mode 100644 index 000000000..9dc0c2502 --- /dev/null +++ b/crates/pgt_hover/src/hovered_node.rs @@ -0,0 +1,25 @@ +use pgt_text_size::TextSize; + +use crate::OnHoverParams; + +pub(crate) enum NodeIdentification { + Name(String), + SchemaAndName((String, String)), + SchemaAndTableAndName((String, String, String)), +} + +pub(crate) enum HoveredNode { + Schema(NodeIdentification), + Table(NodeIdentification), + Function(NodeIdentification), + Column(NodeIdentification), + Policy(NodeIdentification), + Trigger(NodeIdentification), + Role(NodeIdentification), +} + +impl HoveredNode { + pub(crate) fn get(position: TextSize, cst: &tree_sitter::Tree) -> Self { + todo!() + } +} diff --git a/crates/pgt_hover/src/lib.rs b/crates/pgt_hover/src/lib.rs new file mode 100644 index 000000000..2beb7ac90 --- /dev/null +++ b/crates/pgt_hover/src/lib.rs @@ -0,0 +1,19 @@ +use pgt_schema_cache::SchemaCache; +use pgt_text_size::TextSize; + +mod hovered_node; +mod to_markdown; + +pub struct OnHoverParams<'a> { + pub position: TextSize, + pub schema_cache: &'a SchemaCache, + pub ast: Option<&'a pgt_query_ext::NodeEnum>, + pub ts_tree: &'a tree_sitter::Tree, +} + +pub fn on_hover(_params: OnHoverParams) -> Vec { + // needs to find the right element(s) in the schema_cache + // then, we should map the schema_cache items into markdown strings. + + vec![] +} diff --git a/crates/pgt_hover/src/to_markdown.rs b/crates/pgt_hover/src/to_markdown.rs new file mode 100644 index 000000000..b47aaaf86 --- /dev/null +++ b/crates/pgt_hover/src/to_markdown.rs @@ -0,0 +1,3 @@ +pub(crate) trait ToHoverMarkdown { + fn to_hover_markdown(&self) -> String; +} diff --git a/crates/pgt_lsp/src/handlers.rs b/crates/pgt_lsp/src/handlers.rs index 103bef2f7..113e3fcc1 100644 --- a/crates/pgt_lsp/src/handlers.rs +++ b/crates/pgt_lsp/src/handlers.rs @@ -1,3 +1,4 @@ pub(crate) mod code_actions; pub(crate) mod completions; +pub(crate) mod hover; pub(crate) mod text_document; diff --git a/crates/pgt_lsp/src/handlers/hover.rs b/crates/pgt_lsp/src/handlers/hover.rs new file mode 100644 index 000000000..637df2c48 --- /dev/null +++ b/crates/pgt_lsp/src/handlers/hover.rs @@ -0,0 +1,36 @@ +use pgt_console::Markup; +use pgt_diagnostics::adapters; +use pgt_workspace::{WorkspaceError, features::on_hover::OnHoverParams}; +use tower_lsp::lsp_types::{self, MarkedString, MarkupContent}; + +use crate::{adapters::get_cursor_position, diagnostics::LspError, session::Session}; + +pub(crate) fn on_hover( + session: &Session, + params: lsp_types::HoverParams, +) -> Result { + let url = params.text_document_position_params.text_document.uri; + let position = params.text_document_position_params.position; + let path = session.file_path(&url)?; + + match session.workspace.on_hover(OnHoverParams { + path, + position: get_cursor_position(session, &url, position), + }) { + Ok(result) => lsp_types::HoverContents::Array( + result + .into_iter() + .map(|markdown| MarkedString::from_markdown(markdown)), + ), + + Err(e) => match e { + WorkspaceError::DatabaseConnectionError(_) => { + Ok(lsp_types::HoverContents::Markup(MarkupContent { + kind: lsp_types::MarkupKind::PlainText, + value: "Cannot connect to database.".into(), + })); + } + _ => Err(e.into()), + }, + } +} diff --git a/crates/pgt_lsp/src/server.rs b/crates/pgt_lsp/src/server.rs index 6420c5113..1a5c401e4 100644 --- a/crates/pgt_lsp/src/server.rs +++ b/crates/pgt_lsp/src/server.rs @@ -13,6 +13,7 @@ use rustc_hash::FxHashMap; use serde_json::json; use std::panic::RefUnwindSafe; use std::path::PathBuf; +use std::result; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; use tokio::io::{AsyncRead, AsyncWrite}; @@ -265,6 +266,14 @@ impl LanguageServer for LSPServer { } } + #[tracing::instrument(level = "trace", skip_all)] + async fn hover(&self, params: HoverParams) -> LspResult> { + match handlers::hover::on_hover(session, params) { + Ok(result) => LspResult::Ok(Some(result)), + Err(e) => LspResult::Err(into_lsp_error(e)), + } + } + #[tracing::instrument(level = "trace", skip_all)] async fn completion(&self, params: CompletionParams) -> LspResult> { match handlers::completions::get_completions(&self.session, params) { diff --git a/crates/pgt_workspace/Cargo.toml b/crates/pgt_workspace/Cargo.toml index 3ef4936b6..ee0b1d181 100644 --- a/crates/pgt_workspace/Cargo.toml +++ b/crates/pgt_workspace/Cargo.toml @@ -25,6 +25,7 @@ pgt_configuration = { workspace = true } pgt_console = { workspace = true } pgt_diagnostics = { workspace = true } pgt_fs = { workspace = true, features = ["serde"] } +pgt_hover = {workspace=true} pgt_lexer = { workspace = true } pgt_query_ext = { workspace = true } pgt_schema_cache = { workspace = true } diff --git a/crates/pgt_workspace/src/features/completions.rs b/crates/pgt_workspace/src/features/completions.rs index c6f05c6e2..20edd6b74 100644 --- a/crates/pgt_workspace/src/features/completions.rs +++ b/crates/pgt_workspace/src/features/completions.rs @@ -4,7 +4,7 @@ use pgt_completions::CompletionItem; use pgt_fs::PgTPath; use pgt_text_size::{TextRange, TextSize}; -use crate::workspace::{Document, GetCompletionsFilter, GetCompletionsMapper, StatementId}; +use crate::workspace::{Document, GetCompletionsFilter, StatementId, WithCSTMapper}; #[derive(Debug, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] @@ -32,7 +32,7 @@ impl IntoIterator for CompletionsResult { pub(crate) fn get_statement_for_completions( doc: &Document, position: TextSize, -) -> Option<(StatementId, TextRange, String, Arc)> { +) -> Option<(StatementId, TextRange, Arc)> { let count = doc.count(); // no arms no cookies if count == 0 { @@ -40,7 +40,7 @@ pub(crate) fn get_statement_for_completions( } let mut eligible_statements = doc.iter_with_filter( - GetCompletionsMapper, + WithCSTMapper, GetCompletionsFilter { cursor_position: position, }, @@ -49,7 +49,7 @@ pub(crate) fn get_statement_for_completions( if count == 1 { eligible_statements.next() } else { - let mut prev_stmt: Option<(StatementId, TextRange, String, Arc)> = None; + let mut prev_stmt: Option<(StatementId, TextRange, Arc)> = None; for current_stmt in eligible_statements { /* diff --git a/crates/pgt_workspace/src/features/mod.rs b/crates/pgt_workspace/src/features/mod.rs index 31013f36a..7455f0bef 100644 --- a/crates/pgt_workspace/src/features/mod.rs +++ b/crates/pgt_workspace/src/features/mod.rs @@ -1,3 +1,4 @@ pub mod code_actions; pub mod completions; pub mod diagnostics; +pub mod on_hover; diff --git a/crates/pgt_workspace/src/features/on_hover.rs b/crates/pgt_workspace/src/features/on_hover.rs new file mode 100644 index 000000000..88be84096 --- /dev/null +++ b/crates/pgt_workspace/src/features/on_hover.rs @@ -0,0 +1,25 @@ +use biome_rowan::TextSize; +use pgt_fs::PgTPath; + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +pub struct OnHoverParams { + pub path: PgTPath, + pub position: TextSize, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize, Default)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +pub struct OnHoverResult { + /// Can contain multiple blocks of markdown + /// if the hovered-on item is ambiguous. + pub(crate) markdown_blocks: Vec, +} + +impl IntoIterator for OnHoverResult { + type Item = String; + type IntoIter = as IntoIterator>::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.markdown_blocks.into_iter() + } +} diff --git a/crates/pgt_workspace/src/workspace.rs b/crates/pgt_workspace/src/workspace.rs index 9206b39dc..0747c081a 100644 --- a/crates/pgt_workspace/src/workspace.rs +++ b/crates/pgt_workspace/src/workspace.rs @@ -17,6 +17,7 @@ use crate::{ }, completions::{CompletionsResult, GetCompletionsParams}, diagnostics::{PullDiagnosticsParams, PullDiagnosticsResult}, + on_hover::{OnHoverParams, OnHoverResult}, }, }; @@ -113,6 +114,8 @@ pub trait Workspace: Send + Sync + RefUnwindSafe { params: GetCompletionsParams, ) -> Result; + fn on_hover(&self, params: OnHoverParams) -> Result; + /// Register a possible workspace project folder. Returns the key of said project. Use this key when you want to switch to different projects. fn register_project_folder( &self, diff --git a/crates/pgt_workspace/src/workspace/client.rs b/crates/pgt_workspace/src/workspace/client.rs index 2bd215133..05e964f6a 100644 --- a/crates/pgt_workspace/src/workspace/client.rs +++ b/crates/pgt_workspace/src/workspace/client.rs @@ -161,4 +161,11 @@ where ) -> Result { self.request("pgt/get_completions", params) } + + fn on_hover( + &self, + params: crate::features::on_hover::OnHoverParams, + ) -> Result { + self.request("pgt/on_hover", params) + } } diff --git a/crates/pgt_workspace/src/workspace/server.rs b/crates/pgt_workspace/src/workspace/server.rs index e6456afc7..3cc3dc013 100644 --- a/crates/pgt_workspace/src/workspace/server.rs +++ b/crates/pgt_workspace/src/workspace/server.rs @@ -35,9 +35,10 @@ use crate::{ }, completions::{CompletionsResult, GetCompletionsParams, get_statement_for_completions}, diagnostics::{PullDiagnosticsParams, PullDiagnosticsResult}, + on_hover::{self, OnHoverParams, OnHoverResult}, }, settings::{WorkspaceSettings, WorkspaceSettingsHandle, WorkspaceSettingsHandleMut}, - workspace::AnalyserDiagnosticsMapper, + workspace::{AnalyserDiagnosticsMapper, WithCSTMapper, WithCSTandASTMapper}, }; use super::{ @@ -634,20 +635,56 @@ impl Workspace for WorkspaceServer { tracing::debug!("No statement found."); Ok(CompletionsResult::default()) } - Some((_id, range, content, cst)) => { + Some((_id, range, cst)) => { let position = params.position - range.start(); let items = pgt_completions::complete(pgt_completions::CompletionParams { position, schema: schema_cache.as_ref(), tree: &cst, - text: content, + text: _id.content().to_string(), }); Ok(CompletionsResult { items }) } } } + + fn on_hover(&self, params: OnHoverParams) -> Result { + let documents = self.documents.read().unwrap(); + let doc = documents + .get(¶ms.path) + .ok_or(WorkspaceError::not_found())?; + + let pool = self.get_current_connection(); + if pool.is_none() { + tracing::debug!("No database connection available. Skipping completions."); + return Ok(OnHoverResult::default()); + } + let pool = pool.unwrap(); + + let schema_cache = self.schema_cache.load(pool)?; + + match doc + .iter_with_filter( + WithCSTandASTMapper, + CursorPositionFilter::new(params.position), + ) + .next() + { + Some((stmt_id, range, content, ts_tree, maybe_ast)) => { + let markdown_blocks = pgt_hover::on_hover(pgt_hover::OnHoverParams { + ts_tree: &ts_tree, + schema_cache: &schema_cache, + ast: maybe_ast, + position, + }); + + OnHoverResult { markdown_blocks } + } + None => Ok(OnHoverResult::default()), + } + } } /// Returns `true` if `path` is a directory or diff --git a/crates/pgt_workspace/src/workspace/server/document.rs b/crates/pgt_workspace/src/workspace/server/document.rs index c9f880eca..95d0bc782 100644 --- a/crates/pgt_workspace/src/workspace/server/document.rs +++ b/crates/pgt_workspace/src/workspace/server/document.rs @@ -268,14 +268,35 @@ impl<'a> StatementMapper<'a> for AnalyserDiagnosticsMapper { ) } } +pub struct WithCSTMapper; +impl<'a> StatementMapper<'a> for WithCSTMapper { + type Output = (StatementId, TextRange, Arc); -pub struct GetCompletionsMapper; -impl<'a> StatementMapper<'a> for GetCompletionsMapper { - type Output = (StatementId, TextRange, String, Arc); + fn map(&self, parser: &'a Document, id: StatementId, range: TextRange) -> Self::Output { + let tree = parser.cst_db.get_or_cache_tree(&id); + (id.clone(), range, tree) + } +} + +pub struct WithCSTandASTMapper; +impl<'a> StatementMapper<'a> for WithCSTandASTMapper { + type Output = ( + StatementId, + TextRange, + Arc, + Option, + ); fn map(&self, parser: &'a Document, id: StatementId, range: TextRange) -> Self::Output { let tree = parser.cst_db.get_or_cache_tree(&id); - (id.clone(), range, id.content().to_string(), tree) + let ast_result = parser.ast_db.get_or_cache_ast(&id); + + let ast_option = match &*ast_result { + Ok(node) => Some(node.clone()), + Err(_) => None, + }; + + (id.clone(), range, tree, ast_option) } } @@ -555,7 +576,7 @@ $$ LANGUAGE plpgsql;"; let input = "SELECT * FROM users;"; let d = Document::new(input.to_string(), 1); - let results = d.iter(GetCompletionsMapper).collect::>(); + let results = d.iter(WithCSTMapper).collect::>(); assert_eq!(results.len(), 1); let (_id, _range, content, tree) = &results[0]; From 4d9d35c693786e295ba1266605064a06d541e641 Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 19 Jul 2025 08:33:06 +0200 Subject: [PATCH 2/5] add some formatting --- Cargo.lock | 10 ++++ crates/pgt_hover/Cargo.toml | 1 + crates/pgt_hover/src/hovered_node.rs | 4 +- crates/pgt_hover/src/to_markdown.rs | 83 +++++++++++++++++++++++++++- 4 files changed, 94 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80dd4df96..8e000e5a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1834,6 +1834,15 @@ version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "icu_collections" version = "1.5.0" @@ -2878,6 +2887,7 @@ dependencies = [ name = "pgt_hover" version = "0.0.0" dependencies = [ + "humansize", "pgt_query_ext", "pgt_schema_cache", "pgt_test_utils", diff --git a/crates/pgt_hover/Cargo.toml b/crates/pgt_hover/Cargo.toml index 699bbc98f..ca09eb326 100644 --- a/crates/pgt_hover/Cargo.toml +++ b/crates/pgt_hover/Cargo.toml @@ -23,6 +23,7 @@ tree-sitter.workspace = true tree_sitter_sql.workspace = true sqlx.workspace = true tokio = { version = "1.41.1", features = ["full"] } +humansize ={ version = "2.1.3" } [dev-dependencies] pgt_test_utils.workspace = true diff --git a/crates/pgt_hover/src/hovered_node.rs b/crates/pgt_hover/src/hovered_node.rs index 9dc0c2502..12d47e7f3 100644 --- a/crates/pgt_hover/src/hovered_node.rs +++ b/crates/pgt_hover/src/hovered_node.rs @@ -19,7 +19,5 @@ pub(crate) enum HoveredNode { } impl HoveredNode { - pub(crate) fn get(position: TextSize, cst: &tree_sitter::Tree) -> Self { - todo!() - } + pub(crate) fn get(position: TextSize, cst: &tree_sitter::Tree) -> Self {} } diff --git a/crates/pgt_hover/src/to_markdown.rs b/crates/pgt_hover/src/to_markdown.rs index b47aaaf86..b59d9a946 100644 --- a/crates/pgt_hover/src/to_markdown.rs +++ b/crates/pgt_hover/src/to_markdown.rs @@ -1,3 +1,84 @@ +use std::fmt::Write; + +use humansize::DECIMAL; + pub(crate) trait ToHoverMarkdown { - fn to_hover_markdown(&self) -> String; + fn to_hover_markdown(&self, writer: &mut W) -> Result<(), std::fmt::Error>; +} + +impl ToHoverMarkdown for pgt_schema_cache::Table { + fn to_hover_markdown(&self, writer: &mut W) -> Result<(), std::fmt::Error> { + HeadlineWriter::for_table(writer, &self)?; + BodyWriter::for_table(writer, &self)?; + FooterWriter::for_table(writer, &self)?; + + Ok(()) + } +} + +struct HeadlineWriter; + +impl HeadlineWriter { + fn for_table( + writer: &mut W, + table: &pgt_schema_cache::Table, + ) -> Result<(), std::fmt::Error> { + let table_kind = match table.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 => "", + }; + + let locked_txt = if table.rls_enabled { + " - 🔒 RLS enabled" + } else { + " - 🔓 RLS disabled" + }; + + write!( + writer, + "### {}.{}{}{}", + table.schema, table.name, table_kind, locked_txt + )?; + + writeln!(writer)?; + + Ok(()) + } +} + +struct BodyWriter; + +impl BodyWriter { + fn for_table( + writer: &mut W, + table: &pgt_schema_cache::Table, + ) -> Result<(), std::fmt::Error> { + if let Some(c) = table.comment.as_ref() { + write!(writer, "{}", c)?; + writeln!(writer)?; + } + + Ok(()) + } +} + +struct FooterWriter; + +impl FooterWriter { + fn for_table( + writer: &mut W, + table: &pgt_schema_cache::Table, + ) -> Result<(), std::fmt::Error> { + write!( + writer, + "~{} rows, ~{} dead rows, {}", + table.live_rows_estimate, + table.dead_rows_estimate, + humansize::format_size(table.bytes as u64, DECIMAL) + )?; + + Ok(()) + } } From 97f42fcfa34a02edec57506465a9dcfa0d749ed4 Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 19 Jul 2025 17:10:09 +0200 Subject: [PATCH 3/5] ok? --- Cargo.lock | 1 + crates/pgt_hover/Cargo.toml | 1 + crates/pgt_hover/src/hovered_node.rs | 28 +++++++++++++++++++--- crates/pgt_hover/src/lib.rs | 35 ++++++++++++++++++++++++++-- 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e59d3560..0efd4988f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2892,6 +2892,7 @@ dependencies = [ "pgt_schema_cache", "pgt_test_utils", "pgt_text_size", + "pgt_treesitter", "schemars", "serde", "serde_json", diff --git a/crates/pgt_hover/Cargo.toml b/crates/pgt_hover/Cargo.toml index ca09eb326..ec8a53312 100644 --- a/crates/pgt_hover/Cargo.toml +++ b/crates/pgt_hover/Cargo.toml @@ -14,6 +14,7 @@ version = "0.0.0" [dependencies] pgt_text_size.workspace = true pgt_schema_cache.workspace = true +pgt_treesitter.workspace = true pgt_query_ext.workspace = true schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } diff --git a/crates/pgt_hover/src/hovered_node.rs b/crates/pgt_hover/src/hovered_node.rs index 12d47e7f3..9433beb7a 100644 --- a/crates/pgt_hover/src/hovered_node.rs +++ b/crates/pgt_hover/src/hovered_node.rs @@ -1,6 +1,5 @@ use pgt_text_size::TextSize; - -use crate::OnHoverParams; +use pgt_treesitter::TreeSitterContextParams; pub(crate) enum NodeIdentification { Name(String), @@ -19,5 +18,28 @@ pub(crate) enum HoveredNode { } impl HoveredNode { - pub(crate) fn get(position: TextSize, cst: &tree_sitter::Tree) -> Self {} + pub(crate) fn get(position: TextSize, text: &str, tree: &tree_sitter::Tree) -> Option { + let ctx = pgt_treesitter::context::TreesitterContext::new(TreeSitterContextParams { + position, + text, + tree, + }); + + let node_content = ctx.get_node_under_cursor_content()?; + let under_node = ctx.node_under_cursor?; + + match under_node.kind() { + "relation" => { + if let Some(schema) = ctx.schema_or_alias_name { + Some(HoveredNode::Table(NodeIdentification::SchemaAndName(( + schema, + node_content, + )))) + } else { + Some(HoveredNode::Table(NodeIdentification::Name(node_content))) + } + } + _ => None, + } + } } diff --git a/crates/pgt_hover/src/lib.rs b/crates/pgt_hover/src/lib.rs index 2beb7ac90..58a2ec1c4 100644 --- a/crates/pgt_hover/src/lib.rs +++ b/crates/pgt_hover/src/lib.rs @@ -1,19 +1,50 @@ use pgt_schema_cache::SchemaCache; use pgt_text_size::TextSize; +use crate::{hovered_node::HoveredNode, to_markdown::ToHoverMarkdown}; + mod hovered_node; mod to_markdown; pub struct OnHoverParams<'a> { pub position: TextSize, pub schema_cache: &'a SchemaCache, + pub stmt_sql: &'a str, pub ast: Option<&'a pgt_query_ext::NodeEnum>, pub ts_tree: &'a tree_sitter::Tree, } -pub fn on_hover(_params: OnHoverParams) -> Vec { +pub fn on_hover(params: OnHoverParams) -> Vec { // needs to find the right element(s) in the schema_cache // then, we should map the schema_cache items into markdown strings. - vec![] + if let Some(hovered_node) = HoveredNode::get(params.position, params.stmt_sql, params.ts_tree) { + match hovered_node { + HoveredNode::Table(node_identification) => { + let table = match node_identification { + hovered_node::NodeIdentification::Name(n) => { + params.schema_cache.find_table(n.as_str(), None) + } + hovered_node::NodeIdentification::SchemaAndName((s, n)) => { + params.schema_cache.find_table(n.as_str(), Some(s.as_str())) + } + hovered_node::NodeIdentification::SchemaAndTableAndName(_) => None, + }; + + table + .map(|t| { + let mut markdown = String::new(); + match t.to_hover_markdown(&mut markdown) { + Ok(_) => vec![markdown], + Err(_) => vec![], + } + }) + .unwrap_or(vec![]) + } + + _ => todo!(), + } + } else { + Default::default() + } } From a8c705101bfa8e37ec63ceb7c44b2755242d58fb Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 19 Jul 2025 17:53:56 +0200 Subject: [PATCH 4/5] wowa wiwa --- crates/pgt_hover/src/hovered_node.rs | 9 +++++-- crates/pgt_hover/src/lib.rs | 3 --- crates/pgt_hover/src/to_markdown.rs | 10 +++++-- crates/pgt_lsp/src/capabilities.rs | 8 +++--- crates/pgt_lsp/src/handlers/hover.rs | 26 ++++++++++++------- crates/pgt_lsp/src/server.rs | 10 ++++--- crates/pgt_schema_cache/src/schema_cache.rs | 6 ++--- .../pgt_workspace/src/features/completions.rs | 16 ++++++------ crates/pgt_workspace/src/features/on_hover.rs | 4 +-- crates/pgt_workspace/src/workspace/server.rs | 15 ++++++----- .../src/workspace/server/document.rs | 4 +-- 11 files changed, 66 insertions(+), 45 deletions(-) diff --git a/crates/pgt_hover/src/hovered_node.rs b/crates/pgt_hover/src/hovered_node.rs index 9433beb7a..2f1905e70 100644 --- a/crates/pgt_hover/src/hovered_node.rs +++ b/crates/pgt_hover/src/hovered_node.rs @@ -1,12 +1,16 @@ use pgt_text_size::TextSize; use pgt_treesitter::TreeSitterContextParams; +#[derive(Debug)] pub(crate) enum NodeIdentification { Name(String), SchemaAndName((String, String)), + #[allow(unused)] SchemaAndTableAndName((String, String, String)), } +#[allow(unused)] +#[derive(Debug)] pub(crate) enum HoveredNode { Schema(NodeIdentification), Table(NodeIdentification), @@ -26,10 +30,11 @@ impl HoveredNode { }); let node_content = ctx.get_node_under_cursor_content()?; - let under_node = ctx.node_under_cursor?; + + let under_node = ctx.node_under_cursor.as_ref()?; match under_node.kind() { - "relation" => { + "identifier" if ctx.parent_matches_one_of_kind(&["object_reference", "relation"]) => { if let Some(schema) = ctx.schema_or_alias_name { Some(HoveredNode::Table(NodeIdentification::SchemaAndName(( schema, diff --git a/crates/pgt_hover/src/lib.rs b/crates/pgt_hover/src/lib.rs index 58a2ec1c4..8a85d9804 100644 --- a/crates/pgt_hover/src/lib.rs +++ b/crates/pgt_hover/src/lib.rs @@ -15,9 +15,6 @@ pub struct OnHoverParams<'a> { } pub fn on_hover(params: OnHoverParams) -> Vec { - // needs to find the right element(s) in the schema_cache - // then, we should map the schema_cache items into markdown strings. - if let Some(hovered_node) = HoveredNode::get(params.position, params.stmt_sql, params.ts_tree) { match hovered_node { HoveredNode::Table(node_identification) => { diff --git a/crates/pgt_hover/src/to_markdown.rs b/crates/pgt_hover/src/to_markdown.rs index b59d9a946..04b919470 100644 --- a/crates/pgt_hover/src/to_markdown.rs +++ b/crates/pgt_hover/src/to_markdown.rs @@ -42,7 +42,7 @@ impl HeadlineWriter { table.schema, table.name, table_kind, locked_txt )?; - writeln!(writer)?; + markdown_newline(writer)?; Ok(()) } @@ -57,7 +57,7 @@ impl BodyWriter { ) -> Result<(), std::fmt::Error> { if let Some(c) = table.comment.as_ref() { write!(writer, "{}", c)?; - writeln!(writer)?; + markdown_newline(writer)?; } Ok(()) @@ -82,3 +82,9 @@ impl FooterWriter { Ok(()) } } + +fn markdown_newline(writer: &mut W) -> Result<(), std::fmt::Error> { + write!(writer, " ")?; + writeln!(writer)?; + Ok(()) +} diff --git a/crates/pgt_lsp/src/capabilities.rs b/crates/pgt_lsp/src/capabilities.rs index 3b473eb73..8c8ff6d92 100644 --- a/crates/pgt_lsp/src/capabilities.rs +++ b/crates/pgt_lsp/src/capabilities.rs @@ -3,9 +3,10 @@ use crate::handlers::code_actions::command_id; use pgt_workspace::features::code_actions::CommandActionCategory; use strum::IntoEnumIterator; use tower_lsp::lsp_types::{ - ClientCapabilities, CompletionOptions, ExecuteCommandOptions, PositionEncodingKind, - SaveOptions, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, - TextDocumentSyncOptions, TextDocumentSyncSaveOptions, WorkDoneProgressOptions, + ClientCapabilities, CompletionOptions, ExecuteCommandOptions, HoverProviderCapability, + PositionEncodingKind, SaveOptions, ServerCapabilities, TextDocumentSyncCapability, + TextDocumentSyncKind, TextDocumentSyncOptions, TextDocumentSyncSaveOptions, + WorkDoneProgressOptions, }; /// The capabilities to send from server as part of [`InitializeResult`] @@ -62,6 +63,7 @@ pub(crate) fn server_capabilities(capabilities: &ClientCapabilities) -> ServerCa true, )), rename_provider: None, + hover_provider: Some(HoverProviderCapability::Simple(true)), ..Default::default() } } diff --git a/crates/pgt_lsp/src/handlers/hover.rs b/crates/pgt_lsp/src/handlers/hover.rs index 637df2c48..05cdba4d3 100644 --- a/crates/pgt_lsp/src/handlers/hover.rs +++ b/crates/pgt_lsp/src/handlers/hover.rs @@ -1,5 +1,3 @@ -use pgt_console::Markup; -use pgt_diagnostics::adapters; use pgt_workspace::{WorkspaceError, features::on_hover::OnHoverParams}; use tower_lsp::lsp_types::{self, MarkedString, MarkupContent}; @@ -15,22 +13,30 @@ pub(crate) fn on_hover( match session.workspace.on_hover(OnHoverParams { path, - position: get_cursor_position(session, &url, position), + position: get_cursor_position(session, &url, position)?, }) { - Ok(result) => lsp_types::HoverContents::Array( - result - .into_iter() - .map(|markdown| MarkedString::from_markdown(markdown)), - ), + Ok(result) => { + tracing::warn!("Got a result. {:#?}", result); + + Ok(lsp_types::HoverContents::Array( + result + .into_iter() + .map(|markdown| MarkedString::from_markdown(markdown)) + .collect(), + )) + } Err(e) => match e { WorkspaceError::DatabaseConnectionError(_) => { Ok(lsp_types::HoverContents::Markup(MarkupContent { kind: lsp_types::MarkupKind::PlainText, value: "Cannot connect to database.".into(), - })); + })) + } + _ => { + tracing::error!("Received an error: {:#?}", e); + Err(e.into()) } - _ => Err(e.into()), }, } } diff --git a/crates/pgt_lsp/src/server.rs b/crates/pgt_lsp/src/server.rs index 1a5c401e4..76d9bd9a6 100644 --- a/crates/pgt_lsp/src/server.rs +++ b/crates/pgt_lsp/src/server.rs @@ -13,7 +13,6 @@ use rustc_hash::FxHashMap; use serde_json::json; use std::panic::RefUnwindSafe; use std::path::PathBuf; -use std::result; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; use tokio::io::{AsyncRead, AsyncWrite}; @@ -267,9 +266,12 @@ impl LanguageServer for LSPServer { } #[tracing::instrument(level = "trace", skip_all)] - async fn hover(&self, params: HoverParams) -> LspResult> { - match handlers::hover::on_hover(session, params) { - Ok(result) => LspResult::Ok(Some(result)), + async fn hover(&self, params: HoverParams) -> LspResult> { + match handlers::hover::on_hover(&self.session, params) { + Ok(result) => LspResult::Ok(Some(Hover { + contents: result, + range: None, + })), Err(e) => LspResult::Err(into_lsp_error(e)), } } diff --git a/crates/pgt_schema_cache/src/schema_cache.rs b/crates/pgt_schema_cache/src/schema_cache.rs index 8fb9683b1..b8bc78d43 100644 --- a/crates/pgt_schema_cache/src/schema_cache.rs +++ b/crates/pgt_schema_cache/src/schema_cache.rs @@ -60,13 +60,13 @@ impl SchemaCache { pub fn find_table(&self, name: &str, schema: Option<&str>) -> Option<&Table> { self.tables .iter() - .find(|t| t.name == name && schema.is_none() || Some(t.schema.as_str()) == schema) + .find(|t| t.name == name && schema.is_none_or(|s| s == t.schema.as_str())) } pub fn find_type(&self, name: &str, schema: Option<&str>) -> Option<&PostgresType> { self.types .iter() - .find(|t| t.name == name && schema.is_none() || Some(t.schema.as_str()) == schema) + .find(|t| t.name == name && schema.is_none_or(|s| s == t.schema.as_str())) } pub fn find_col(&self, name: &str, table: &str, schema: Option<&str>) -> Option<&Column> { @@ -80,7 +80,7 @@ impl SchemaCache { pub fn find_types(&self, name: &str, schema: Option<&str>) -> Vec<&PostgresType> { self.types .iter() - .filter(|t| t.name == name && schema.is_none() || Some(t.schema.as_str()) == schema) + .filter(|t| t.name == name && schema.is_none_or(|s| s == t.schema.as_str())) .collect() } } diff --git a/crates/pgt_workspace/src/features/completions.rs b/crates/pgt_workspace/src/features/completions.rs index d8e0e8606..5944f14cf 100644 --- a/crates/pgt_workspace/src/features/completions.rs +++ b/crates/pgt_workspace/src/features/completions.rs @@ -112,10 +112,10 @@ mod tests { let (doc, position) = get_doc_and_pos(sql.as_str()); - let (_, _, text, _) = + let (stmt, _, _) = get_statement_for_completions(&doc, position).expect("Expected Statement"); - assert_eq!(text, "update users set email = 'myemail@com';") + assert_eq!(stmt.content(), "update users set email = 'myemail@com';") } #[test] @@ -151,10 +151,10 @@ mod tests { let (doc, position) = get_doc_and_pos(sql.as_str()); - let (_, _, text, _) = + let (stmt, _, _) = get_statement_for_completions(&doc, position).expect("Expected Statement"); - assert_eq!(text, "select * from ;") + assert_eq!(stmt.content(), "select * from ;") } #[test] @@ -163,10 +163,10 @@ mod tests { let (doc, position) = get_doc_and_pos(sql.as_str()); - let (_, _, text, _) = + let (stmt, _, _) = get_statement_for_completions(&doc, position).expect("Expected Statement"); - assert_eq!(text, "select * from") + assert_eq!(stmt.content(), "select * from") } #[test] @@ -187,10 +187,10 @@ mod tests { let (doc, position) = get_doc_and_pos(sql); - let (_, _, text, _) = + let (stmt, _, _) = get_statement_for_completions(&doc, position).expect("Expected Statement"); - assert_eq!(text.trim(), "select from cool;") + assert_eq!(stmt.content().trim(), "select from cool;") } #[test] diff --git a/crates/pgt_workspace/src/features/on_hover.rs b/crates/pgt_workspace/src/features/on_hover.rs index 88be84096..3e3fcd49e 100644 --- a/crates/pgt_workspace/src/features/on_hover.rs +++ b/crates/pgt_workspace/src/features/on_hover.rs @@ -1,5 +1,5 @@ -use biome_rowan::TextSize; use pgt_fs::PgTPath; +use pgt_text_size::TextSize; #[derive(Debug, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] @@ -18,7 +18,7 @@ pub struct OnHoverResult { impl IntoIterator for OnHoverResult { type Item = String; - type IntoIter = as IntoIterator>::IntoIter; + type IntoIter = as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.markdown_blocks.into_iter() } diff --git a/crates/pgt_workspace/src/workspace/server.rs b/crates/pgt_workspace/src/workspace/server.rs index 3cc3dc013..a6a64e698 100644 --- a/crates/pgt_workspace/src/workspace/server.rs +++ b/crates/pgt_workspace/src/workspace/server.rs @@ -35,10 +35,10 @@ use crate::{ }, completions::{CompletionsResult, GetCompletionsParams, get_statement_for_completions}, diagnostics::{PullDiagnosticsParams, PullDiagnosticsResult}, - on_hover::{self, OnHoverParams, OnHoverResult}, + on_hover::{OnHoverParams, OnHoverResult}, }, settings::{WorkspaceSettings, WorkspaceSettingsHandle, WorkspaceSettingsHandleMut}, - workspace::{AnalyserDiagnosticsMapper, WithCSTMapper, WithCSTandASTMapper}, + workspace::{AnalyserDiagnosticsMapper, WithCSTandASTMapper}, }; use super::{ @@ -672,15 +672,18 @@ impl Workspace for WorkspaceServer { ) .next() { - Some((stmt_id, range, content, ts_tree, maybe_ast)) => { + Some((stmt_id, range, ts_tree, maybe_ast)) => { + let position_in_stmt = params.position + range.start(); + let markdown_blocks = pgt_hover::on_hover(pgt_hover::OnHoverParams { ts_tree: &ts_tree, schema_cache: &schema_cache, - ast: maybe_ast, - position, + ast: maybe_ast.as_ref(), + position: position_in_stmt, + stmt_sql: stmt_id.content(), }); - OnHoverResult { markdown_blocks } + Ok(OnHoverResult { markdown_blocks }) } None => Ok(OnHoverResult::default()), } diff --git a/crates/pgt_workspace/src/workspace/server/document.rs b/crates/pgt_workspace/src/workspace/server/document.rs index 95d0bc782..b2e97934a 100644 --- a/crates/pgt_workspace/src/workspace/server/document.rs +++ b/crates/pgt_workspace/src/workspace/server/document.rs @@ -579,8 +579,8 @@ $$ LANGUAGE plpgsql;"; let results = d.iter(WithCSTMapper).collect::>(); assert_eq!(results.len(), 1); - let (_id, _range, content, tree) = &results[0]; - assert_eq!(content, "SELECT * FROM users;"); + let (id, _, tree) = &results[0]; + assert_eq!(id.content(), "SELECT * FROM users;"); assert_eq!(tree.root_node().kind(), "program"); } From a065011820089ebd369519b9978ee3eb2299e7ad Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 19 Jul 2025 17:56:07 +0200 Subject: [PATCH 5/5] just ready --- crates/pgt_hover/Cargo.toml | 27 +++++++++++++-------------- crates/pgt_hover/src/to_markdown.rs | 6 +++--- crates/pgt_lsp/src/handlers/hover.rs | 2 +- crates/pgt_workspace/Cargo.toml | 2 +- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/crates/pgt_hover/Cargo.toml b/crates/pgt_hover/Cargo.toml index ec8a53312..eab3f70cf 100644 --- a/crates/pgt_hover/Cargo.toml +++ b/crates/pgt_hover/Cargo.toml @@ -12,19 +12,19 @@ version = "0.0.0" [dependencies] -pgt_text_size.workspace = true -pgt_schema_cache.workspace = true -pgt_treesitter.workspace = true -pgt_query_ext.workspace = true -schemars = { workspace = true, optional = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -tracing = { workspace = true } -tree-sitter.workspace = true -tree_sitter_sql.workspace = true -sqlx.workspace = true -tokio = { version = "1.41.1", features = ["full"] } -humansize ={ version = "2.1.3" } +humansize = { version = "2.1.3" } +pgt_query_ext.workspace = true +pgt_schema_cache.workspace = true +pgt_text_size.workspace = true +pgt_treesitter.workspace = true +schemars = { workspace = true, optional = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +sqlx.workspace = true +tokio = { version = "1.41.1", features = ["full"] } +tracing = { workspace = true } +tree-sitter.workspace = true +tree_sitter_sql.workspace = true [dev-dependencies] pgt_test_utils.workspace = true @@ -34,4 +34,3 @@ doctest = false [features] schema = ["dep:schemars"] - diff --git a/crates/pgt_hover/src/to_markdown.rs b/crates/pgt_hover/src/to_markdown.rs index 04b919470..7ea661600 100644 --- a/crates/pgt_hover/src/to_markdown.rs +++ b/crates/pgt_hover/src/to_markdown.rs @@ -8,9 +8,9 @@ pub(crate) trait ToHoverMarkdown { impl ToHoverMarkdown for pgt_schema_cache::Table { fn to_hover_markdown(&self, writer: &mut W) -> Result<(), std::fmt::Error> { - HeadlineWriter::for_table(writer, &self)?; - BodyWriter::for_table(writer, &self)?; - FooterWriter::for_table(writer, &self)?; + HeadlineWriter::for_table(writer, self)?; + BodyWriter::for_table(writer, self)?; + FooterWriter::for_table(writer, self)?; Ok(()) } diff --git a/crates/pgt_lsp/src/handlers/hover.rs b/crates/pgt_lsp/src/handlers/hover.rs index 05cdba4d3..4dd44ca6c 100644 --- a/crates/pgt_lsp/src/handlers/hover.rs +++ b/crates/pgt_lsp/src/handlers/hover.rs @@ -21,7 +21,7 @@ pub(crate) fn on_hover( Ok(lsp_types::HoverContents::Array( result .into_iter() - .map(|markdown| MarkedString::from_markdown(markdown)) + .map(MarkedString::from_markdown) .collect(), )) } diff --git a/crates/pgt_workspace/Cargo.toml b/crates/pgt_workspace/Cargo.toml index ee0b1d181..1498fb080 100644 --- a/crates/pgt_workspace/Cargo.toml +++ b/crates/pgt_workspace/Cargo.toml @@ -25,7 +25,7 @@ pgt_configuration = { workspace = true } pgt_console = { workspace = true } pgt_diagnostics = { workspace = true } pgt_fs = { workspace = true, features = ["serde"] } -pgt_hover = {workspace=true} +pgt_hover = { workspace = true } pgt_lexer = { workspace = true } pgt_query_ext = { workspace = true } pgt_schema_cache = { workspace = true }