From 768f92b0523a56176074e0b0d34da587abfe9533 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 18 Jul 2025 16:46:25 +0200 Subject: [PATCH 01/20] 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 02/20] 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 50d789de8b163bd4c5dc14c439484d0c722a82ff Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 19 Jul 2025 08:59:20 +0200 Subject: [PATCH 03/20] so far --- Cargo.lock | 8 +- Cargo.toml | 2 +- crates/pgt_completions/Cargo.toml | 2 +- .../pgt_completions/src/providers/columns.rs | 145 +++++++++++++----- crates/pgt_completions/src/test_helper.rs | 78 +--------- crates/pgt_test_utils/src/lib.rs | 78 ++++++++++ .../Cargo.toml | 4 +- .../src/context/base_parser.rs | 0 .../src/context/grant_parser.rs | 3 +- .../src/context/mod.rs | 6 +- .../src/context/policy_parser.rs | 3 +- .../src/context/revoke_parser.rs | 3 +- crates/pgt_treesitter/src/lib.rs | 2 + .../src/queries/insert_columns.rs | 6 +- .../src/queries/mod.rs} | 92 ++++++++++- .../src/queries/parameters.rs | 4 +- .../src/queries/relations.rs | 7 +- .../src/queries/select_columns.rs | 6 +- .../src/queries/table_aliases.rs | 4 +- .../src/queries/where_columns.rs | 4 +- .../pgt_treesitter_queries/src/queries/mod.rs | 86 ----------- crates/pgt_typecheck/Cargo.toml | 2 +- crates/pgt_typecheck/src/typed_identifier.rs | 2 +- 23 files changed, 310 insertions(+), 237 deletions(-) rename crates/{pgt_treesitter_queries => pgt_treesitter}/Cargo.toml (81%) rename crates/{pgt_completions => pgt_treesitter}/src/context/base_parser.rs (100%) rename crates/{pgt_completions => pgt_treesitter}/src/context/grant_parser.rs (99%) rename crates/{pgt_completions => pgt_treesitter}/src/context/mod.rs (99%) rename crates/{pgt_completions => pgt_treesitter}/src/context/policy_parser.rs (99%) rename crates/{pgt_completions => pgt_treesitter}/src/context/revoke_parser.rs (99%) create mode 100644 crates/pgt_treesitter/src/lib.rs rename crates/{pgt_treesitter_queries => pgt_treesitter}/src/queries/insert_columns.rs (97%) rename crates/{pgt_treesitter_queries/src/lib.rs => pgt_treesitter/src/queries/mod.rs} (72%) rename crates/{pgt_treesitter_queries => pgt_treesitter}/src/queries/parameters.rs (96%) rename crates/{pgt_treesitter_queries => pgt_treesitter}/src/queries/relations.rs (98%) rename crates/{pgt_treesitter_queries => pgt_treesitter}/src/queries/select_columns.rs (97%) rename crates/{pgt_treesitter_queries => pgt_treesitter}/src/queries/table_aliases.rs (97%) rename crates/{pgt_treesitter_queries => pgt_treesitter}/src/queries/where_columns.rs (97%) delete mode 100644 crates/pgt_treesitter_queries/src/queries/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 16b1de5e6..434f554b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2765,7 +2765,7 @@ dependencies = [ "pgt_schema_cache", "pgt_test_utils", "pgt_text_size", - "pgt_treesitter_queries", + "pgt_treesitter", "schemars", "serde", "serde_json", @@ -3047,10 +3047,12 @@ dependencies = [ ] [[package]] -name = "pgt_treesitter_queries" +name = "pgt_treesitter" version = "0.0.0" dependencies = [ "clap 4.5.23", + "pgt_schema_cache", + "pgt_text_size", "tree-sitter", "tree_sitter_sql", ] @@ -3074,7 +3076,7 @@ dependencies = [ "pgt_schema_cache", "pgt_test_utils", "pgt_text_size", - "pgt_treesitter_queries", + "pgt_treesitter", "sqlx", "tokio", "tree-sitter", diff --git a/Cargo.toml b/Cargo.toml index 15c6f02ff..2da8d4e12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,7 +82,7 @@ pgt_suppressions = { path = "./crates/pgt_suppressions", version = "0. pgt_text_edit = { path = "./crates/pgt_text_edit", version = "0.0.0" } pgt_text_size = { path = "./crates/pgt_text_size", version = "0.0.0" } pgt_tokenizer = { path = "./crates/pgt_tokenizer", version = "0.0.0" } -pgt_treesitter_queries = { path = "./crates/pgt_treesitter_queries", version = "0.0.0" } +pgt_treesitter = { path = "./crates/pgt_treesitter", version = "0.0.0" } pgt_typecheck = { path = "./crates/pgt_typecheck", version = "0.0.0" } pgt_workspace = { path = "./crates/pgt_workspace", version = "0.0.0" } diff --git a/crates/pgt_completions/Cargo.toml b/crates/pgt_completions/Cargo.toml index 916a00209..f05839c59 100644 --- a/crates/pgt_completions/Cargo.toml +++ b/crates/pgt_completions/Cargo.toml @@ -19,7 +19,7 @@ pgt_text_size.workspace = true fuzzy-matcher = "0.3.7" pgt_schema_cache.workspace = true -pgt_treesitter_queries.workspace = true +pgt_treesitter.workspace = true schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/crates/pgt_completions/src/providers/columns.rs b/crates/pgt_completions/src/providers/columns.rs index 04d0af656..e7bf49fbe 100644 --- a/crates/pgt_completions/src/providers/columns.rs +++ b/crates/pgt_completions/src/providers/columns.rs @@ -49,11 +49,13 @@ mod tests { use crate::{ CompletionItem, CompletionItemKind, complete, test_helper::{ - CURSOR_POS, CompletionAssertion, InputQuery, assert_complete_results, - assert_no_complete_results, get_test_deps, get_test_params, + CompletionAssertion, assert_complete_results, assert_no_complete_results, + get_test_deps, get_test_params, }, }; + use pgt_test_utils::QueryWithCursorPosition; + struct TestCase { query: String, message: &'static str, @@ -62,7 +64,7 @@ mod tests { } impl TestCase { - fn get_input_query(&self) -> InputQuery { + fn get_input_query(&self) -> QueryWithCursorPosition { let strs: Vec<&str> = self.query.split_whitespace().collect(); strs.join(" ").as_str().into() } @@ -94,7 +96,10 @@ mod tests { let queries: Vec = vec![ TestCase { message: "correctly prefers the columns of present tables", - query: format!(r#"select na{} from public.audio_books;"#, CURSOR_POS), + query: format!( + r#"select na{} from public.audio_books;"#, + QueryWithCursorPosition::cursor_marker() + ), label: "narrator", description: "public.audio_books", }, @@ -111,14 +116,17 @@ mod tests { join public.users u on u.id = subquery.id; "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ), label: "narrator_id", description: "private.audio_books", }, TestCase { message: "works without a schema", - query: format!(r#"select na{} from users;"#, CURSOR_POS), + query: format!( + r#"select na{} from users;"#, + QueryWithCursorPosition::cursor_marker() + ), label: "name", description: "public.users", }, @@ -165,7 +173,7 @@ mod tests { pool.execute(setup).await.unwrap(); let case = TestCase { - query: format!(r#"select n{};"#, CURSOR_POS), + query: format!(r#"select n{};"#, QueryWithCursorPosition::cursor_marker()), description: "", label: "", message: "", @@ -220,7 +228,10 @@ mod tests { let test_case = TestCase { message: "suggests user created tables first", - query: format!(r#"select {} from users"#, CURSOR_POS), + query: format!( + r#"select {} from users"#, + QueryWithCursorPosition::cursor_marker() + ), label: "", description: "", }; @@ -270,7 +281,10 @@ mod tests { let test_case = TestCase { message: "suggests user created tables first", - query: format!(r#"select * from private.{}"#, CURSOR_POS), + query: format!( + r#"select * from private.{}"#, + QueryWithCursorPosition::cursor_marker() + ), label: "", description: "", }; @@ -311,7 +325,11 @@ mod tests { pool.execute(setup).await.unwrap(); assert_complete_results( - format!(r#"select {} from users"#, CURSOR_POS).as_str(), + format!( + r#"select {} from users"#, + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![ CompletionAssertion::Label("address2".into()), CompletionAssertion::Label("email2".into()), @@ -324,7 +342,11 @@ mod tests { .await; assert_complete_results( - format!(r#"select {} from private.users"#, CURSOR_POS).as_str(), + format!( + r#"select {} from private.users"#, + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![ CompletionAssertion::Label("address1".into()), CompletionAssertion::Label("email1".into()), @@ -338,7 +360,11 @@ mod tests { // asserts fuzzy finding for "settings" assert_complete_results( - format!(r#"select sett{} from private.users"#, CURSOR_POS).as_str(), + format!( + r#"select sett{} from private.users"#, + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![CompletionAssertion::Label("user_settings".into())], None, &pool, @@ -372,7 +398,7 @@ mod tests { assert_complete_results( format!( "select u.id, p.{} from auth.users u join auth.posts p on u.id = p.user_id;", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ @@ -394,7 +420,7 @@ mod tests { assert_complete_results( format!( "select u.id, p.content from auth.users u join auth.posts p on u.id = p.{};", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ @@ -440,7 +466,7 @@ mod tests { assert_complete_results( format!( "select u.id, p.content from auth.users u join auth.{}", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ @@ -479,7 +505,7 @@ mod tests { assert_complete_results( format!( "select u.id, auth.posts.content from auth.users u join auth.posts on u.{}", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ @@ -496,7 +522,7 @@ mod tests { assert_complete_results( format!( "select u.id, p.content from auth.users u join auth.posts p on p.user_id = u.{}", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ @@ -536,7 +562,7 @@ mod tests { assert_complete_results( format!( "select {} from public.one o join public.two on o.id = t.id;", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ @@ -555,7 +581,7 @@ mod tests { assert_complete_results( format!( "select a, {} from public.one o join public.two on o.id = t.id;", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ @@ -577,7 +603,7 @@ mod tests { assert_complete_results( format!( "select o.id, a, b, c, d, e, {} from public.one o join public.two on o.id = t.id;", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ @@ -593,7 +619,7 @@ mod tests { assert_complete_results( format!( "select id, a, b, c, d, e, {} from public.one o join public.two on o.id = t.id;", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![CompletionAssertion::Label("z".to_string())], @@ -625,7 +651,11 @@ mod tests { // are lower in the alphabet assert_complete_results( - format!("insert into instruments ({})", CURSOR_POS).as_str(), + format!( + "insert into instruments ({})", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![ CompletionAssertion::Label("id".to_string()), CompletionAssertion::Label("name".to_string()), @@ -637,7 +667,11 @@ mod tests { .await; assert_complete_results( - format!("insert into instruments (id, {})", CURSOR_POS).as_str(), + format!( + "insert into instruments (id, {})", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![ CompletionAssertion::Label("name".to_string()), CompletionAssertion::Label("z".to_string()), @@ -648,7 +682,11 @@ mod tests { .await; assert_complete_results( - format!("insert into instruments (id, {}, name)", CURSOR_POS).as_str(), + format!( + "insert into instruments (id, {}, name)", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![CompletionAssertion::Label("z".to_string())], None, &pool, @@ -659,7 +697,7 @@ mod tests { assert_complete_results( format!( "insert into instruments (name, {}) values ('my_bass');", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ @@ -673,7 +711,11 @@ mod tests { // no completions in the values list! assert_no_complete_results( - format!("insert into instruments (id, name) values ({})", CURSOR_POS).as_str(), + format!( + "insert into instruments (id, name) values ({})", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), None, &pool, ) @@ -700,7 +742,11 @@ mod tests { pool.execute(setup).await.unwrap(); assert_complete_results( - format!("select name from instruments where {} ", CURSOR_POS).as_str(), + format!( + "select name from instruments where {} ", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![ CompletionAssertion::Label("created_at".into()), CompletionAssertion::Label("id".into()), @@ -715,7 +761,7 @@ mod tests { assert_complete_results( format!( "select name from instruments where z = 'something' and created_at > {}", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), // simply do not complete columns + schemas; functions etc. are ok @@ -732,7 +778,7 @@ mod tests { assert_complete_results( format!( "select name from instruments where id = 'something' and {}", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ @@ -749,7 +795,7 @@ mod tests { assert_complete_results( format!( "select name from instruments i join others o on i.z = o.a where i.{}", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ @@ -783,22 +829,37 @@ mod tests { pool.execute(setup).await.unwrap(); let queries = vec![ - format!("alter table instruments drop column {}", CURSOR_POS), + format!( + "alter table instruments drop column {}", + QueryWithCursorPosition::cursor_marker() + ), format!( "alter table instruments drop column if exists {}", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ), format!( "alter table instruments alter column {} set default", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() + ), + format!( + "alter table instruments alter {} set default", + QueryWithCursorPosition::cursor_marker() + ), + format!( + "alter table public.instruments alter column {}", + QueryWithCursorPosition::cursor_marker() + ), + format!( + "alter table instruments alter {}", + QueryWithCursorPosition::cursor_marker() + ), + format!( + "alter table instruments rename {} to new_col", + QueryWithCursorPosition::cursor_marker() ), - format!("alter table instruments alter {} set default", CURSOR_POS), - format!("alter table public.instruments alter column {}", CURSOR_POS), - format!("alter table instruments alter {}", CURSOR_POS), - format!("alter table instruments rename {} to new_col", CURSOR_POS), format!( "alter table public.instruments rename column {} to new_col", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ), ]; @@ -834,19 +895,19 @@ mod tests { let col_queries = vec![ format!( r#"create policy "my_pol" on public.instruments for select using ({})"#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ), format!( r#"create policy "my_pol" on public.instruments for insert with check ({})"#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ), format!( r#"create policy "my_pol" on public.instruments for update using (id = 1 and {})"#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ), format!( r#"create policy "my_pol" on public.instruments for insert with check (id = 1 and {})"#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ), ]; diff --git a/crates/pgt_completions/src/test_helper.rs b/crates/pgt_completions/src/test_helper.rs index 1bd5229ca..37d2174bf 100644 --- a/crates/pgt_completions/src/test_helper.rs +++ b/crates/pgt_completions/src/test_helper.rs @@ -1,40 +1,14 @@ use std::fmt::Display; use pgt_schema_cache::SchemaCache; +use pgt_test_utils::QueryWithCursorPosition; use sqlx::{Executor, PgPool}; use crate::{CompletionItem, CompletionItemKind, CompletionParams, complete}; -pub static CURSOR_POS: char = '€'; - -#[derive(Clone)] -pub struct InputQuery { - sql: String, - position: usize, -} - -impl From<&str> for InputQuery { - fn from(value: &str) -> Self { - let position = value - .find(CURSOR_POS) - .expect("Insert Cursor Position into your Query."); - - InputQuery { - sql: value.replace(CURSOR_POS, "").trim().to_string(), - position, - } - } -} - -impl Display for InputQuery { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.sql) - } -} - pub(crate) async fn get_test_deps( setup: Option<&str>, - input: InputQuery, + input: QueryWithCursorPosition, test_db: &PgPool, ) -> (tree_sitter::Tree, pgt_schema_cache::SchemaCache) { if let Some(setup) = setup { @@ -63,7 +37,7 @@ pub(crate) async fn get_test_deps( #[allow(dead_code)] pub(crate) async fn test_against_connection_string( conn_str: &str, - input: InputQuery, + input: QueryWithCursorPosition, ) -> (tree_sitter::Tree, pgt_schema_cache::SchemaCache) { let pool = sqlx::PgPool::connect(conn_str) .await @@ -83,14 +57,10 @@ pub(crate) async fn test_against_connection_string( (tree, schema_cache) } -pub(crate) fn get_text_and_position(q: InputQuery) -> (usize, String) { - (q.position, q.sql) -} - pub(crate) fn get_test_params<'a>( tree: &'a tree_sitter::Tree, schema_cache: &'a pgt_schema_cache::SchemaCache, - sql: InputQuery, + sql: QueryWithCursorPosition, ) -> CompletionParams<'a> { let (position, text) = get_text_and_position(sql); @@ -102,46 +72,6 @@ pub(crate) fn get_test_params<'a>( } } -#[cfg(test)] -mod tests { - use crate::test_helper::CURSOR_POS; - - use super::InputQuery; - - #[test] - fn input_query_should_extract_correct_position() { - struct TestCase { - query: String, - expected_pos: usize, - expected_sql_len: usize, - } - - let cases = vec![ - TestCase { - query: format!("select * from{}", CURSOR_POS), - expected_pos: 13, - expected_sql_len: 13, - }, - TestCase { - query: format!("{}select * from", CURSOR_POS), - expected_pos: 0, - expected_sql_len: 13, - }, - TestCase { - query: format!("select {} from", CURSOR_POS), - expected_pos: 7, - expected_sql_len: 12, - }, - ]; - - for case in cases { - let query = InputQuery::from(case.query.as_str()); - assert_eq!(query.position, case.expected_pos); - assert_eq!(query.sql.len(), case.expected_sql_len); - } - } -} - #[derive(Debug, PartialEq, Eq)] pub(crate) enum CompletionAssertion { Label(String), diff --git a/crates/pgt_test_utils/src/lib.rs b/crates/pgt_test_utils/src/lib.rs index e21c6ce4b..9a802a8e5 100644 --- a/crates/pgt_test_utils/src/lib.rs +++ b/crates/pgt_test_utils/src/lib.rs @@ -1 +1,79 @@ +use std::fmt::Display; + pub static MIGRATIONS: sqlx::migrate::Migrator = sqlx::migrate!("./testdb_migrations"); + +static CURSOR_POS: char = '€'; + +#[derive(Clone)] +pub struct QueryWithCursorPosition { + sql: String, + position: usize, +} + +impl QueryWithCursorPosition { + pub fn cursor_marker() -> char { + CURSOR_POS + } + + pub fn get_text_and_position(&self) -> (usize, String) { + (self.position, self.sql.clone()) + } +} + +impl From<&str> for QueryWithCursorPosition { + fn from(value: &str) -> Self { + let position = value + .find(CURSOR_POS) + .expect("Use `InputQuery::cursor_marker()` to insert cursor position into your Query."); + + QueryWithCursorPosition { + sql: value.replace(CURSOR_POS, "").trim().to_string(), + position, + } + } +} + +impl Display for QueryWithCursorPosition { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.sql) + } +} + +#[cfg(test)] +mod tests { + + use super::QueryWithCursorPosition; + + #[test] + fn input_query_should_extract_correct_position() { + struct TestCase { + query: String, + expected_pos: usize, + expected_sql_len: usize, + } + + let cases = vec![ + TestCase { + query: format!("select * from{}", QueryWithCursorPosition::cursor_marker()), + expected_pos: 13, + expected_sql_len: 13, + }, + TestCase { + query: format!("{}select * from", QueryWithCursorPosition::cursor_marker()), + expected_pos: 0, + expected_sql_len: 13, + }, + TestCase { + query: format!("select {} from", QueryWithCursorPosition::cursor_marker()), + expected_pos: 7, + expected_sql_len: 12, + }, + ]; + + for case in cases { + let query = QueryWithCursorPosition::from(case.query.as_str()); + assert_eq!(query.position, case.expected_pos); + assert_eq!(query.sql.len(), case.expected_sql_len); + } + } +} diff --git a/crates/pgt_treesitter_queries/Cargo.toml b/crates/pgt_treesitter/Cargo.toml similarity index 81% rename from crates/pgt_treesitter_queries/Cargo.toml rename to crates/pgt_treesitter/Cargo.toml index 5806861f5..c675e3fa1 100644 --- a/crates/pgt_treesitter_queries/Cargo.toml +++ b/crates/pgt_treesitter/Cargo.toml @@ -6,7 +6,7 @@ edition.workspace = true homepage.workspace = true keywords.workspace = true license.workspace = true -name = "pgt_treesitter_queries" +name = "pgt_treesitter" repository.workspace = true version = "0.0.0" @@ -15,6 +15,8 @@ version = "0.0.0" clap = { version = "4.5.23", features = ["derive"] } tree-sitter.workspace = true tree_sitter_sql.workspace = true +pgt_text_size.workspace = true +pgt_schema_cache.workspace = true [dev-dependencies] diff --git a/crates/pgt_completions/src/context/base_parser.rs b/crates/pgt_treesitter/src/context/base_parser.rs similarity index 100% rename from crates/pgt_completions/src/context/base_parser.rs rename to crates/pgt_treesitter/src/context/base_parser.rs diff --git a/crates/pgt_completions/src/context/grant_parser.rs b/crates/pgt_treesitter/src/context/grant_parser.rs similarity index 99% rename from crates/pgt_completions/src/context/grant_parser.rs rename to crates/pgt_treesitter/src/context/grant_parser.rs index 14ba882ae..db0450534 100644 --- a/crates/pgt_completions/src/context/grant_parser.rs +++ b/crates/pgt_treesitter/src/context/grant_parser.rs @@ -187,9 +187,10 @@ mod tests { use crate::{ context::base_parser::CompletionStatementParser, context::grant_parser::{GrantContext, GrantParser}, - test_helper::CURSOR_POS, }; + static CURSOR_POS: char = '€'; + fn with_pos(query: String) -> (usize, String) { let mut pos: Option = None; diff --git a/crates/pgt_completions/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs similarity index 99% rename from crates/pgt_completions/src/context/mod.rs rename to crates/pgt_treesitter/src/context/mod.rs index 01e563b0a..c2f6ff0d9 100644 --- a/crates/pgt_completions/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -7,12 +7,9 @@ mod grant_parser; mod policy_parser; mod revoke_parser; +use crate::queries::{self, QueryResult, TreeSitterQueriesExecutor}; use pgt_schema_cache::SchemaCache; use pgt_text_size::TextRange; -use pgt_treesitter_queries::{ - TreeSitterQueriesExecutor, - queries::{self, QueryResult}, -}; use crate::{ NodeText, @@ -178,6 +175,7 @@ pub(crate) struct CompletionContext<'a> { /// on u.id = i.user_id; /// ``` pub schema_or_alias_name: Option, + pub wrapping_clause_type: Option>, pub wrapping_node_kind: Option, diff --git a/crates/pgt_completions/src/context/policy_parser.rs b/crates/pgt_treesitter/src/context/policy_parser.rs similarity index 99% rename from crates/pgt_completions/src/context/policy_parser.rs rename to crates/pgt_treesitter/src/context/policy_parser.rs index bcc604990..4bf41db11 100644 --- a/crates/pgt_completions/src/context/policy_parser.rs +++ b/crates/pgt_treesitter/src/context/policy_parser.rs @@ -212,9 +212,10 @@ mod tests { use crate::{ context::base_parser::CompletionStatementParser, context::policy_parser::{PolicyContext, PolicyStmtKind}, - test_helper::CURSOR_POS, }; + static CURSOR_POS: char = '€'; + use super::PolicyParser; fn with_pos(query: String) -> (usize, String) { diff --git a/crates/pgt_completions/src/context/revoke_parser.rs b/crates/pgt_treesitter/src/context/revoke_parser.rs similarity index 99% rename from crates/pgt_completions/src/context/revoke_parser.rs rename to crates/pgt_treesitter/src/context/revoke_parser.rs index e0c43934c..d95de8f36 100644 --- a/crates/pgt_completions/src/context/revoke_parser.rs +++ b/crates/pgt_treesitter/src/context/revoke_parser.rs @@ -180,9 +180,10 @@ mod tests { use crate::{ context::base_parser::CompletionStatementParser, context::revoke_parser::{RevokeContext, RevokeParser}, - test_helper::CURSOR_POS, }; + static CURSOR_POS: char = '€'; + fn with_pos(query: String) -> (usize, String) { let mut pos: Option = None; diff --git a/crates/pgt_treesitter/src/lib.rs b/crates/pgt_treesitter/src/lib.rs new file mode 100644 index 000000000..483c539d7 --- /dev/null +++ b/crates/pgt_treesitter/src/lib.rs @@ -0,0 +1,2 @@ +pub mod context; +pub mod queries; diff --git a/crates/pgt_treesitter_queries/src/queries/insert_columns.rs b/crates/pgt_treesitter/src/queries/insert_columns.rs similarity index 97% rename from crates/pgt_treesitter_queries/src/queries/insert_columns.rs rename to crates/pgt_treesitter/src/queries/insert_columns.rs index 3e88d998f..94d67b690 100644 --- a/crates/pgt_treesitter_queries/src/queries/insert_columns.rs +++ b/crates/pgt_treesitter/src/queries/insert_columns.rs @@ -1,6 +1,6 @@ use std::sync::LazyLock; -use crate::{Query, QueryResult}; +use crate::queries::{Query, QueryResult}; use super::QueryTryFrom; @@ -51,7 +51,7 @@ impl<'a> QueryTryFrom<'a> for InsertColumnMatch<'a> { } impl<'a> Query<'a> for InsertColumnMatch<'a> { - fn execute(root_node: tree_sitter::Node<'a>, stmt: &'a str) -> Vec> { + fn execute(root_node: tree_sitter::Node<'a>, stmt: &'a str) -> Vec> { let mut cursor = tree_sitter::QueryCursor::new(); let matches = cursor.matches(&TS_QUERY, root_node, stmt.as_bytes()); @@ -73,7 +73,7 @@ impl<'a> Query<'a> for InsertColumnMatch<'a> { #[cfg(test)] mod tests { use super::InsertColumnMatch; - use crate::TreeSitterQueriesExecutor; + use crate::queries::TreeSitterQueriesExecutor; #[test] fn finds_all_insert_columns() { diff --git a/crates/pgt_treesitter_queries/src/lib.rs b/crates/pgt_treesitter/src/queries/mod.rs similarity index 72% rename from crates/pgt_treesitter_queries/src/lib.rs rename to crates/pgt_treesitter/src/queries/mod.rs index 4bf71e744..1d24f07a4 100644 --- a/crates/pgt_treesitter_queries/src/lib.rs +++ b/crates/pgt_treesitter/src/queries/mod.rs @@ -1,8 +1,91 @@ -pub mod queries; +mod insert_columns; +mod parameters; +mod relations; +mod select_columns; +mod table_aliases; +mod where_columns; use std::slice::Iter; -use queries::{Query, QueryResult}; +pub use insert_columns::*; +pub use parameters::*; +pub use relations::*; +pub use select_columns::*; +pub use table_aliases::*; +pub use where_columns::*; + +#[derive(Debug)] +pub enum QueryResult<'a> { + Relation(RelationMatch<'a>), + Parameter(ParameterMatch<'a>), + TableAliases(TableAliasMatch<'a>), + SelectClauseColumns(SelectColumnMatch<'a>), + InsertClauseColumns(InsertColumnMatch<'a>), + WhereClauseColumns(WhereColumnMatch<'a>), +} + +impl QueryResult<'_> { + pub fn within_range(&self, range: &tree_sitter::Range) -> bool { + match self { + QueryResult::Relation(rm) => { + let start = match rm.schema { + Some(s) => s.start_position(), + None => rm.table.start_position(), + }; + + let end = rm.table.end_position(); + + start >= range.start_point && end <= range.end_point + } + Self::Parameter(pm) => { + let node_range = pm.node.range(); + + node_range.start_point >= range.start_point + && node_range.end_point <= range.end_point + } + QueryResult::TableAliases(m) => { + let start = m.table.start_position(); + let end = m.alias.end_position(); + start >= range.start_point && end <= range.end_point + } + Self::SelectClauseColumns(cm) => { + let start = match cm.alias { + Some(n) => n.start_position(), + None => cm.column.start_position(), + }; + + let end = cm.column.end_position(); + + start >= range.start_point && end <= range.end_point + } + Self::WhereClauseColumns(cm) => { + let start = match cm.alias { + Some(n) => n.start_position(), + None => cm.column.start_position(), + }; + + let end = cm.column.end_position(); + + start >= range.start_point && end <= range.end_point + } + Self::InsertClauseColumns(cm) => { + let start = cm.column.start_position(); + let end = cm.column.end_position(); + start >= range.start_point && end <= range.end_point + } + } + } +} + +// This trait enforces that for any `Self` that implements `Query`, +// its &Self must implement TryFrom<&QueryResult> +pub(crate) trait QueryTryFrom<'a>: Sized { + type Ref: for<'any> TryFrom<&'a QueryResult<'a>, Error = String>; +} + +pub(crate) trait Query<'a>: QueryTryFrom<'a> { + fn execute(root_node: tree_sitter::Node<'a>, stmt: &'a str) -> Vec>; +} pub struct TreeSitterQueriesExecutor<'a> { root_node: tree_sitter::Node<'a>, @@ -68,9 +151,8 @@ impl<'a> Iterator for QueryResultIter<'a> { #[cfg(test)] mod tests { - use crate::{ - TreeSitterQueriesExecutor, - queries::{ParameterMatch, RelationMatch, TableAliasMatch}, + use crate::queries::{ + ParameterMatch, RelationMatch, TableAliasMatch, TreeSitterQueriesExecutor, }; #[test] diff --git a/crates/pgt_treesitter_queries/src/queries/parameters.rs b/crates/pgt_treesitter/src/queries/parameters.rs similarity index 96% rename from crates/pgt_treesitter_queries/src/queries/parameters.rs rename to crates/pgt_treesitter/src/queries/parameters.rs index 85ea9ad25..0b7f2e3df 100644 --- a/crates/pgt_treesitter_queries/src/queries/parameters.rs +++ b/crates/pgt_treesitter/src/queries/parameters.rs @@ -1,6 +1,6 @@ use std::sync::LazyLock; -use crate::{Query, QueryResult}; +use crate::queries::{Query, QueryResult}; use super::QueryTryFrom; @@ -59,7 +59,7 @@ impl<'a> QueryTryFrom<'a> for ParameterMatch<'a> { } impl<'a> Query<'a> for ParameterMatch<'a> { - fn execute(root_node: tree_sitter::Node<'a>, stmt: &'a str) -> Vec> { + fn execute(root_node: tree_sitter::Node<'a>, stmt: &'a str) -> Vec> { let mut cursor = tree_sitter::QueryCursor::new(); let matches = cursor.matches(&TS_QUERY, root_node, stmt.as_bytes()); diff --git a/crates/pgt_treesitter_queries/src/queries/relations.rs b/crates/pgt_treesitter/src/queries/relations.rs similarity index 98% rename from crates/pgt_treesitter_queries/src/queries/relations.rs rename to crates/pgt_treesitter/src/queries/relations.rs index 2d7e44317..cb6a6bea9 100644 --- a/crates/pgt_treesitter_queries/src/queries/relations.rs +++ b/crates/pgt_treesitter/src/queries/relations.rs @@ -1,6 +1,6 @@ use std::sync::LazyLock; -use crate::{Query, QueryResult}; +use crate::queries::{Query, QueryResult}; use super::QueryTryFrom; @@ -79,7 +79,7 @@ impl<'a> QueryTryFrom<'a> for RelationMatch<'a> { } impl<'a> Query<'a> for RelationMatch<'a> { - fn execute(root_node: tree_sitter::Node<'a>, stmt: &'a str) -> Vec> { + fn execute(root_node: tree_sitter::Node<'a>, stmt: &'a str) -> Vec> { let mut cursor = tree_sitter::QueryCursor::new(); let matches = cursor.matches(&TS_QUERY, root_node, stmt.as_bytes()); @@ -112,8 +112,9 @@ impl<'a> Query<'a> for RelationMatch<'a> { #[cfg(test)] mod tests { + use crate::queries::TreeSitterQueriesExecutor; + use super::RelationMatch; - use crate::TreeSitterQueriesExecutor; #[test] fn finds_table_without_schema() { diff --git a/crates/pgt_treesitter_queries/src/queries/select_columns.rs b/crates/pgt_treesitter/src/queries/select_columns.rs similarity index 97% rename from crates/pgt_treesitter_queries/src/queries/select_columns.rs rename to crates/pgt_treesitter/src/queries/select_columns.rs index 00b6977d0..f232abc38 100644 --- a/crates/pgt_treesitter_queries/src/queries/select_columns.rs +++ b/crates/pgt_treesitter/src/queries/select_columns.rs @@ -1,6 +1,6 @@ use std::sync::LazyLock; -use crate::{Query, QueryResult}; +use crate::queries::{Query, QueryResult}; use super::QueryTryFrom; @@ -63,7 +63,7 @@ impl<'a> QueryTryFrom<'a> for SelectColumnMatch<'a> { } impl<'a> Query<'a> for SelectColumnMatch<'a> { - fn execute(root_node: tree_sitter::Node<'a>, stmt: &'a str) -> Vec> { + fn execute(root_node: tree_sitter::Node<'a>, stmt: &'a str) -> Vec> { let mut cursor = tree_sitter::QueryCursor::new(); let matches = cursor.matches(&TS_QUERY, root_node, stmt.as_bytes()); @@ -96,7 +96,7 @@ impl<'a> Query<'a> for SelectColumnMatch<'a> { #[cfg(test)] mod tests { - use crate::TreeSitterQueriesExecutor; + use crate::queries::TreeSitterQueriesExecutor; use super::SelectColumnMatch; diff --git a/crates/pgt_treesitter_queries/src/queries/table_aliases.rs b/crates/pgt_treesitter/src/queries/table_aliases.rs similarity index 97% rename from crates/pgt_treesitter_queries/src/queries/table_aliases.rs rename to crates/pgt_treesitter/src/queries/table_aliases.rs index 4297a2186..70d4d52ef 100644 --- a/crates/pgt_treesitter_queries/src/queries/table_aliases.rs +++ b/crates/pgt_treesitter/src/queries/table_aliases.rs @@ -1,6 +1,6 @@ use std::sync::LazyLock; -use crate::{Query, QueryResult}; +use crate::queries::{Query, QueryResult}; use super::QueryTryFrom; @@ -69,7 +69,7 @@ impl<'a> QueryTryFrom<'a> for TableAliasMatch<'a> { } impl<'a> Query<'a> for TableAliasMatch<'a> { - fn execute(root_node: tree_sitter::Node<'a>, stmt: &'a str) -> Vec> { + fn execute(root_node: tree_sitter::Node<'a>, stmt: &'a str) -> Vec> { let mut cursor = tree_sitter::QueryCursor::new(); let matches = cursor.matches(&TS_QUERY, root_node, stmt.as_bytes()); diff --git a/crates/pgt_treesitter_queries/src/queries/where_columns.rs b/crates/pgt_treesitter/src/queries/where_columns.rs similarity index 97% rename from crates/pgt_treesitter_queries/src/queries/where_columns.rs rename to crates/pgt_treesitter/src/queries/where_columns.rs index 8e19590de..b683300b6 100644 --- a/crates/pgt_treesitter_queries/src/queries/where_columns.rs +++ b/crates/pgt_treesitter/src/queries/where_columns.rs @@ -1,6 +1,6 @@ use std::sync::LazyLock; -use crate::{Query, QueryResult}; +use crate::queries::{Query, QueryResult}; use super::QueryTryFrom; @@ -64,7 +64,7 @@ impl<'a> QueryTryFrom<'a> for WhereColumnMatch<'a> { } impl<'a> Query<'a> for WhereColumnMatch<'a> { - fn execute(root_node: tree_sitter::Node<'a>, stmt: &'a str) -> Vec> { + fn execute(root_node: tree_sitter::Node<'a>, stmt: &'a str) -> Vec> { let mut cursor = tree_sitter::QueryCursor::new(); let matches = cursor.matches(&TS_QUERY, root_node, stmt.as_bytes()); diff --git a/crates/pgt_treesitter_queries/src/queries/mod.rs b/crates/pgt_treesitter_queries/src/queries/mod.rs deleted file mode 100644 index b9f39aed8..000000000 --- a/crates/pgt_treesitter_queries/src/queries/mod.rs +++ /dev/null @@ -1,86 +0,0 @@ -mod insert_columns; -mod parameters; -mod relations; -mod select_columns; -mod table_aliases; -mod where_columns; - -pub use insert_columns::*; -pub use parameters::*; -pub use relations::*; -pub use select_columns::*; -pub use table_aliases::*; -pub use where_columns::*; - -#[derive(Debug)] -pub enum QueryResult<'a> { - Relation(RelationMatch<'a>), - Parameter(ParameterMatch<'a>), - TableAliases(TableAliasMatch<'a>), - SelectClauseColumns(SelectColumnMatch<'a>), - InsertClauseColumns(InsertColumnMatch<'a>), - WhereClauseColumns(WhereColumnMatch<'a>), -} - -impl QueryResult<'_> { - pub fn within_range(&self, range: &tree_sitter::Range) -> bool { - match self { - QueryResult::Relation(rm) => { - let start = match rm.schema { - Some(s) => s.start_position(), - None => rm.table.start_position(), - }; - - let end = rm.table.end_position(); - - start >= range.start_point && end <= range.end_point - } - Self::Parameter(pm) => { - let node_range = pm.node.range(); - - node_range.start_point >= range.start_point - && node_range.end_point <= range.end_point - } - QueryResult::TableAliases(m) => { - let start = m.table.start_position(); - let end = m.alias.end_position(); - start >= range.start_point && end <= range.end_point - } - Self::SelectClauseColumns(cm) => { - let start = match cm.alias { - Some(n) => n.start_position(), - None => cm.column.start_position(), - }; - - let end = cm.column.end_position(); - - start >= range.start_point && end <= range.end_point - } - Self::WhereClauseColumns(cm) => { - let start = match cm.alias { - Some(n) => n.start_position(), - None => cm.column.start_position(), - }; - - let end = cm.column.end_position(); - - start >= range.start_point && end <= range.end_point - } - Self::InsertClauseColumns(cm) => { - let start = cm.column.start_position(); - let end = cm.column.end_position(); - start >= range.start_point && end <= range.end_point - } - } - } -} - -// This trait enforces that for any `Self` that implements `Query`, -// its &Self must implement TryFrom<&QueryResult> -pub(crate) trait QueryTryFrom<'a>: Sized { - type Ref: for<'any> TryFrom<&'a QueryResult<'a>, Error = String>; -} - -pub(crate) trait Query<'a>: QueryTryFrom<'a> { - fn execute(root_node: tree_sitter::Node<'a>, stmt: &'a str) -> Vec>; -} diff --git a/crates/pgt_typecheck/Cargo.toml b/crates/pgt_typecheck/Cargo.toml index caacc6d17..34e6ef63f 100644 --- a/crates/pgt_typecheck/Cargo.toml +++ b/crates/pgt_typecheck/Cargo.toml @@ -17,7 +17,7 @@ pgt_diagnostics.workspace = true pgt_query_ext.workspace = true pgt_schema_cache.workspace = true pgt_text_size.workspace = true -pgt_treesitter_queries.workspace = true +pgt_treesitter.workspace = true sqlx.workspace = true tokio.workspace = true tree-sitter.workspace = true diff --git a/crates/pgt_typecheck/src/typed_identifier.rs b/crates/pgt_typecheck/src/typed_identifier.rs index 710b2fe98..1ee4095dc 100644 --- a/crates/pgt_typecheck/src/typed_identifier.rs +++ b/crates/pgt_typecheck/src/typed_identifier.rs @@ -1,5 +1,5 @@ use pgt_schema_cache::PostgresType; -use pgt_treesitter_queries::{TreeSitterQueriesExecutor, queries::ParameterMatch}; +use pgt_treesitter::queries::{ParameterMatch, TreeSitterQueriesExecutor}; /// A typed identifier is a parameter that has a type associated with it. /// It is used to replace parameters within the SQL string. From a80ba4ce69b762a4b2feba07e526756c2c2c6a85 Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 19 Jul 2025 08:59:36 +0200 Subject: [PATCH 04/20] ok --- crates/pgt_test_utils/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pgt_test_utils/src/lib.rs b/crates/pgt_test_utils/src/lib.rs index 9a802a8e5..2c17b198e 100644 --- a/crates/pgt_test_utils/src/lib.rs +++ b/crates/pgt_test_utils/src/lib.rs @@ -24,7 +24,7 @@ impl From<&str> for QueryWithCursorPosition { fn from(value: &str) -> Self { let position = value .find(CURSOR_POS) - .expect("Use `InputQuery::cursor_marker()` to insert cursor position into your Query."); + .expect("Use `QueryWithCursorPosition::cursor_marker()` to insert cursor position into your Query."); QueryWithCursorPosition { sql: value.replace(CURSOR_POS, "").trim().to_string(), From d50db86ff88859f0a2045597ee325fff1c31dc97 Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 19 Jul 2025 09:10:18 +0200 Subject: [PATCH 05/20] some more refatorings --- crates/pgt_completions/src/lib.rs | 1 - .../src/providers/functions.rs | 15 +- .../pgt_completions/src/providers/policies.rs | 7 +- crates/pgt_completions/src/providers/roles.rs | 60 +++++-- .../pgt_completions/src/providers/schemas.rs | 8 +- .../pgt_completions/src/providers/tables.rs | 134 +++++++++++--- .../pgt_completions/src/providers/triggers.rs | 169 ------------------ .../src/relevance/filtering.rs | 26 ++- crates/pgt_test_utils/src/lib.rs | 6 + crates/pgt_treesitter/Cargo.toml | 1 + .../src/context/grant_parser.rs | 26 +-- crates/pgt_treesitter/src/context/mod.rs | 132 +++++++++++--- .../src/context/policy_parser.rs | 38 ++-- .../src/context/revoke_parser.rs | 20 ++- .../pgt_workspace/src/features/completions.rs | 22 +-- 15 files changed, 358 insertions(+), 307 deletions(-) delete mode 100644 crates/pgt_completions/src/providers/triggers.rs diff --git a/crates/pgt_completions/src/lib.rs b/crates/pgt_completions/src/lib.rs index f8ca1a550..c4e592eef 100644 --- a/crates/pgt_completions/src/lib.rs +++ b/crates/pgt_completions/src/lib.rs @@ -1,6 +1,5 @@ mod builder; mod complete; -mod context; mod item; mod providers; mod relevance; diff --git a/crates/pgt_completions/src/providers/functions.rs b/crates/pgt_completions/src/providers/functions.rs index 615e4f951..919cd6ac9 100644 --- a/crates/pgt_completions/src/providers/functions.rs +++ b/crates/pgt_completions/src/providers/functions.rs @@ -70,11 +70,12 @@ mod tests { use crate::{ CompletionItem, CompletionItemKind, complete, test_helper::{ - CURSOR_POS, CompletionAssertion, assert_complete_results, get_test_deps, - get_test_params, + CompletionAssertion, assert_complete_results, get_test_deps, get_test_params, }, }; + use pgt_test_utils::QueryWithCursorPosition; + #[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] async fn completes_fn(pool: PgPool) { let setup = r#" @@ -89,7 +90,7 @@ mod tests { $$; "#; - let query = format!("select coo{}", CURSOR_POS); + let query = format!("select coo{}", QueryWithCursorPosition::cursor_marker()); let (tree, cache) = get_test_deps(Some(setup), query.as_str().into(), &pool).await; let params = get_test_params(&tree, &cache, query.as_str().into()); @@ -122,7 +123,7 @@ mod tests { $$; "#; - let query = format!(r#"select * from coo{}()"#, CURSOR_POS); + let query = format!(r#"select * from coo{}()"#, QueryWithCursorPosition::cursor_marker()); let (tree, cache) = get_test_deps(Some(setup), query.as_str().into(), &pool).await; let params = get_test_params(&tree, &cache, query.as_str().into()); @@ -156,7 +157,7 @@ mod tests { $$; "#; - let query = format!(r#"select coo{}"#, CURSOR_POS); + let query = format!(r#"select coo{}"#, QueryWithCursorPosition::cursor_marker()); let (tree, cache) = get_test_deps(Some(setup), query.as_str().into(), &pool).await; let params = get_test_params(&tree, &cache, query.as_str().into()); @@ -190,7 +191,7 @@ mod tests { $$; "#; - let query = format!(r#"select * from coo{}()"#, CURSOR_POS); + let query = format!(r#"select * from coo{}()"#, QueryWithCursorPosition::cursor_marker()); let (tree, cache) = get_test_deps(Some(setup), query.as_str().into(), &pool).await; let params = get_test_params(&tree, &cache, query.as_str().into()); @@ -259,7 +260,7 @@ mod tests { let query = format!( r#"create policy "my_pol" on public.instruments for insert with check (id = {})"#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ); assert_complete_results( diff --git a/crates/pgt_completions/src/providers/policies.rs b/crates/pgt_completions/src/providers/policies.rs index 216fcefaa..548eb0410 100644 --- a/crates/pgt_completions/src/providers/policies.rs +++ b/crates/pgt_completions/src/providers/policies.rs @@ -61,7 +61,8 @@ pub fn complete_policies<'a>(ctx: &CompletionContext<'a>, builder: &mut Completi mod tests { use sqlx::{Executor, PgPool}; - use crate::test_helper::{CURSOR_POS, CompletionAssertion, assert_complete_results}; + use crate::test_helper::{CompletionAssertion, assert_complete_results}; + use pgt_test_utils::QueryWithCursorPosition; #[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] async fn completes_within_quotation_marks(pool: PgPool) { @@ -89,7 +90,7 @@ mod tests { pool.execute(setup).await.unwrap(); assert_complete_results( - format!("alter policy \"{}\" on private.users;", CURSOR_POS).as_str(), + format!("alter policy \"{}\" on private.users;", QueryWithCursorPosition::cursor_marker()).as_str(), vec![ CompletionAssertion::Label("read for public users disallowed".into()), CompletionAssertion::Label("write for public users allowed".into()), @@ -100,7 +101,7 @@ mod tests { .await; assert_complete_results( - format!("alter policy \"w{}\" on private.users;", CURSOR_POS).as_str(), + format!("alter policy \"w{}\" on private.users;", QueryWithCursorPosition::cursor_marker()).as_str(), vec![CompletionAssertion::Label( "write for public users allowed".into(), )], diff --git a/crates/pgt_completions/src/providers/roles.rs b/crates/pgt_completions/src/providers/roles.rs index 01641543f..f86d18c6e 100644 --- a/crates/pgt_completions/src/providers/roles.rs +++ b/crates/pgt_completions/src/providers/roles.rs @@ -29,7 +29,9 @@ pub fn complete_roles<'a>(ctx: &CompletionContext<'a>, builder: &mut CompletionB mod tests { use sqlx::{Executor, PgPool}; - use crate::test_helper::{CURSOR_POS, CompletionAssertion, assert_complete_results}; + use crate::test_helper::{CompletionAssertion, assert_complete_results}; + + use pgt_test_utils::QueryWithCursorPosition; const SETUP: &str = r#" create table users ( @@ -42,7 +44,7 @@ mod tests { #[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] async fn works_in_drop_role(pool: PgPool) { assert_complete_results( - format!("drop role {}", CURSOR_POS).as_str(), + format!("drop role {}", QueryWithCursorPosition::cursor_marker()).as_str(), vec![ CompletionAssertion::LabelAndKind("owner".into(), crate::CompletionItemKind::Role), CompletionAssertion::LabelAndKind( @@ -63,7 +65,7 @@ mod tests { #[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] async fn works_in_alter_role(pool: PgPool) { assert_complete_results( - format!("alter role {}", CURSOR_POS).as_str(), + format!("alter role {}", QueryWithCursorPosition::cursor_marker()).as_str(), vec![ CompletionAssertion::LabelAndKind("owner".into(), crate::CompletionItemKind::Role), CompletionAssertion::LabelAndKind( @@ -86,7 +88,7 @@ mod tests { pool.execute(SETUP).await.unwrap(); assert_complete_results( - format!("set role {}", CURSOR_POS).as_str(), + format!("set role {}", QueryWithCursorPosition::cursor_marker()).as_str(), vec![ CompletionAssertion::LabelAndKind("owner".into(), crate::CompletionItemKind::Role), CompletionAssertion::LabelAndKind( @@ -104,7 +106,11 @@ mod tests { .await; assert_complete_results( - format!("set session authorization {}", CURSOR_POS).as_str(), + format!( + "set session authorization {}", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![ CompletionAssertion::LabelAndKind("owner".into(), crate::CompletionItemKind::Role), CompletionAssertion::LabelAndKind( @@ -133,7 +139,7 @@ mod tests { for all to {} using (true);"#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ @@ -157,7 +163,7 @@ mod tests { r#"create policy "my cool policy" on public.users for select to {}"#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ @@ -186,7 +192,7 @@ mod tests { r#"grant select on table public.users to {}"#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ @@ -211,7 +217,7 @@ mod tests { r#"grant select on table public.users to owner, {}"#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ @@ -232,7 +238,11 @@ mod tests { .await; assert_complete_results( - format!(r#"grant {} to owner"#, CURSOR_POS).as_str(), + format!( + r#"grant {} to owner"#, + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![ // recognizing already mentioned roles is not supported for now CompletionAssertion::LabelAndKind("owner".into(), crate::CompletionItemKind::Role), @@ -256,12 +266,30 @@ mod tests { pool.execute(SETUP).await.unwrap(); let queries = vec![ - format!("revoke {} from owner", CURSOR_POS), - format!("revoke admin option for {} from owner", CURSOR_POS), - format!("revoke owner from {}", CURSOR_POS), - format!("revoke all on schema public from {} granted by", CURSOR_POS), - format!("revoke all on schema public from owner, {}", CURSOR_POS), - format!("revoke all on table userse from owner, {}", CURSOR_POS), + format!( + "revoke {} from owner", + QueryWithCursorPosition::cursor_marker() + ), + format!( + "revoke admin option for {} from owner", + QueryWithCursorPosition::cursor_marker() + ), + format!( + "revoke owner from {}", + QueryWithCursorPosition::cursor_marker() + ), + format!( + "revoke all on schema public from {} granted by", + QueryWithCursorPosition::cursor_marker() + ), + format!( + "revoke all on schema public from owner, {}", + QueryWithCursorPosition::cursor_marker() + ), + format!( + "revoke all on table userse from owner, {}", + QueryWithCursorPosition::cursor_marker() + ), ]; for query in queries { diff --git a/crates/pgt_completions/src/providers/schemas.rs b/crates/pgt_completions/src/providers/schemas.rs index 561da0f85..11215df4e 100644 --- a/crates/pgt_completions/src/providers/schemas.rs +++ b/crates/pgt_completions/src/providers/schemas.rs @@ -31,9 +31,11 @@ mod tests { use crate::{ CompletionItemKind, - test_helper::{CURSOR_POS, CompletionAssertion, assert_complete_results}, + test_helper::{CompletionAssertion, assert_complete_results}, }; + use pgt_test_utils::QueryWithCursorPosition; + #[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] async fn autocompletes_schemas(pool: PgPool) { let setup = r#" @@ -50,7 +52,7 @@ mod tests { "#; assert_complete_results( - format!("select * from {}", CURSOR_POS).as_str(), + format!("select * from {}", QueryWithCursorPosition::cursor_marker()).as_str(), vec![ CompletionAssertion::LabelAndKind("public".to_string(), CompletionItemKind::Schema), CompletionAssertion::LabelAndKind("auth".to_string(), CompletionItemKind::Schema), @@ -97,7 +99,7 @@ mod tests { "#; assert_complete_results( - format!("select * from u{}", CURSOR_POS).as_str(), + format!("select * from u{}", QueryWithCursorPosition::cursor_marker()).as_str(), vec![ CompletionAssertion::LabelAndKind("users".into(), CompletionItemKind::Table), CompletionAssertion::LabelAndKind("ultimate".into(), CompletionItemKind::Schema), diff --git a/crates/pgt_completions/src/providers/tables.rs b/crates/pgt_completions/src/providers/tables.rs index 3fbee8f12..1844f2b60 100644 --- a/crates/pgt_completions/src/providers/tables.rs +++ b/crates/pgt_completions/src/providers/tables.rs @@ -47,11 +47,13 @@ mod tests { use crate::{ CompletionItem, CompletionItemKind, complete, test_helper::{ - CURSOR_POS, CompletionAssertion, assert_complete_results, assert_no_complete_results, + CompletionAssertion, assert_complete_results, assert_no_complete_results, get_test_deps, get_test_params, }, }; + use pgt_test_utils::QueryWithCursorPosition; + #[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] async fn autocompletes_simple_table(pool: PgPool) { let setup = r#" @@ -62,7 +64,10 @@ mod tests { ); "#; - let query = format!("select * from u{}", CURSOR_POS); + let query = format!( + "select * from u{}", + QueryWithCursorPosition::cursor_marker() + ); let (tree, cache) = get_test_deps(Some(setup), query.as_str().into(), &pool).await; let params = get_test_params(&tree, &cache, query.as_str().into()); @@ -98,9 +103,27 @@ mod tests { pool.execute(setup).await.unwrap(); let test_cases = vec![ - (format!("select * from u{}", CURSOR_POS), "users"), - (format!("select * from e{}", CURSOR_POS), "emails"), - (format!("select * from a{}", CURSOR_POS), "addresses"), + ( + format!( + "select * from u{}", + QueryWithCursorPosition::cursor_marker() + ), + "users", + ), + ( + format!( + "select * from e{}", + QueryWithCursorPosition::cursor_marker() + ), + "emails", + ), + ( + format!( + "select * from a{}", + QueryWithCursorPosition::cursor_marker() + ), + "addresses", + ), ]; for (query, expected_label) in test_cases { @@ -142,10 +165,25 @@ mod tests { pool.execute(setup).await.unwrap(); let test_cases = vec![ - (format!("select * from u{}", CURSOR_POS), "user_y"), // user_y is preferred alphanumerically - (format!("select * from private.u{}", CURSOR_POS), "user_z"), ( - format!("select * from customer_support.u{}", CURSOR_POS), + format!( + "select * from u{}", + QueryWithCursorPosition::cursor_marker() + ), + "user_y", + ), // user_y is preferred alphanumerically + ( + format!( + "select * from private.u{}", + QueryWithCursorPosition::cursor_marker() + ), + "user_z", + ), + ( + format!( + "select * from customer_support.u{}", + QueryWithCursorPosition::cursor_marker() + ), "user_y", ), ]; @@ -186,7 +224,10 @@ mod tests { $$; "#; - let query = format!(r#"select * from coo{}"#, CURSOR_POS); + let query = format!( + r#"select * from coo{}"#, + QueryWithCursorPosition::cursor_marker() + ); let (tree, cache) = get_test_deps(Some(setup), query.as_str().into(), &pool).await; let params = get_test_params(&tree, &cache, query.as_str().into()); @@ -213,7 +254,7 @@ mod tests { pool.execute(setup).await.unwrap(); assert_complete_results( - format!("update {}", CURSOR_POS).as_str(), + format!("update {}", QueryWithCursorPosition::cursor_marker()).as_str(), vec![CompletionAssertion::LabelAndKind( "public".into(), CompletionItemKind::Schema, @@ -224,7 +265,7 @@ mod tests { .await; assert_complete_results( - format!("update public.{}", CURSOR_POS).as_str(), + format!("update public.{}", QueryWithCursorPosition::cursor_marker()).as_str(), vec![CompletionAssertion::LabelAndKind( "coos".into(), CompletionItemKind::Table, @@ -235,14 +276,22 @@ mod tests { .await; assert_no_complete_results( - format!("update public.coos {}", CURSOR_POS).as_str(), + format!( + "update public.coos {}", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), None, &pool, ) .await; assert_complete_results( - format!("update coos set {}", CURSOR_POS).as_str(), + format!( + "update coos set {}", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![ CompletionAssertion::Label("id".into()), CompletionAssertion::Label("name".into()), @@ -253,7 +302,11 @@ mod tests { .await; assert_complete_results( - format!("update coos set name = 'cool' where {}", CURSOR_POS).as_str(), + format!( + "update coos set name = 'cool' where {}", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![ CompletionAssertion::Label("id".into()), CompletionAssertion::Label("name".into()), @@ -275,10 +328,15 @@ mod tests { pool.execute(setup).await.unwrap(); - assert_no_complete_results(format!("delete {}", CURSOR_POS).as_str(), None, &pool).await; + assert_no_complete_results( + format!("delete {}", QueryWithCursorPosition::cursor_marker()).as_str(), + None, + &pool, + ) + .await; assert_complete_results( - format!("delete from {}", CURSOR_POS).as_str(), + format!("delete from {}", QueryWithCursorPosition::cursor_marker()).as_str(), vec![ CompletionAssertion::LabelAndKind("public".into(), CompletionItemKind::Schema), CompletionAssertion::LabelAndKind("coos".into(), CompletionItemKind::Table), @@ -289,7 +347,11 @@ mod tests { .await; assert_complete_results( - format!("delete from public.{}", CURSOR_POS).as_str(), + format!( + "delete from public.{}", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![CompletionAssertion::Label("coos".into())], None, &pool, @@ -297,7 +359,11 @@ mod tests { .await; assert_complete_results( - format!("delete from public.coos where {}", CURSOR_POS).as_str(), + format!( + "delete from public.coos where {}", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![ CompletionAssertion::Label("id".into()), CompletionAssertion::Label("name".into()), @@ -329,7 +395,11 @@ mod tests { "#; assert_complete_results( - format!("select * from auth.users u join {}", CURSOR_POS).as_str(), + format!( + "select * from auth.users u join {}", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![ CompletionAssertion::LabelAndKind("public".into(), CompletionItemKind::Schema), CompletionAssertion::LabelAndKind("auth".into(), CompletionItemKind::Schema), @@ -365,7 +435,7 @@ mod tests { pool.execute(setup).await.unwrap(); assert_complete_results( - format!("alter table {}", CURSOR_POS).as_str(), + format!("alter table {}", QueryWithCursorPosition::cursor_marker()).as_str(), vec![ CompletionAssertion::LabelAndKind("public".into(), CompletionItemKind::Schema), CompletionAssertion::LabelAndKind("auth".into(), CompletionItemKind::Schema), @@ -378,7 +448,11 @@ mod tests { .await; assert_complete_results( - format!("alter table if exists {}", CURSOR_POS).as_str(), + format!( + "alter table if exists {}", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![ CompletionAssertion::LabelAndKind("public".into(), CompletionItemKind::Schema), CompletionAssertion::LabelAndKind("auth".into(), CompletionItemKind::Schema), @@ -391,7 +465,7 @@ mod tests { .await; assert_complete_results( - format!("drop table {}", CURSOR_POS).as_str(), + format!("drop table {}", QueryWithCursorPosition::cursor_marker()).as_str(), vec![ CompletionAssertion::LabelAndKind("public".into(), CompletionItemKind::Schema), CompletionAssertion::LabelAndKind("auth".into(), CompletionItemKind::Schema), @@ -404,7 +478,11 @@ mod tests { .await; assert_complete_results( - format!("drop table if exists {}", CURSOR_POS).as_str(), + format!( + "drop table if exists {}", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![ CompletionAssertion::LabelAndKind("public".into(), CompletionItemKind::Schema), CompletionAssertion::LabelAndKind("auth".into(), CompletionItemKind::Schema), @@ -432,7 +510,7 @@ mod tests { pool.execute(setup).await.unwrap(); assert_complete_results( - format!("insert into {}", CURSOR_POS).as_str(), + format!("insert into {}", QueryWithCursorPosition::cursor_marker()).as_str(), vec![ CompletionAssertion::LabelAndKind("public".into(), CompletionItemKind::Schema), CompletionAssertion::LabelAndKind("auth".into(), CompletionItemKind::Schema), @@ -444,7 +522,11 @@ mod tests { .await; assert_complete_results( - format!("insert into auth.{}", CURSOR_POS).as_str(), + format!( + "insert into auth.{}", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![CompletionAssertion::LabelAndKind( "users".into(), CompletionItemKind::Table, @@ -458,7 +540,7 @@ mod tests { assert_complete_results( format!( "insert into {} (name, email) values ('jules', 'a@b.com');", - CURSOR_POS + QueryWithCursorPosition::cursor_marker() ) .as_str(), vec![ diff --git a/crates/pgt_completions/src/providers/triggers.rs b/crates/pgt_completions/src/providers/triggers.rs deleted file mode 100644 index 6bc04debc..000000000 --- a/crates/pgt_completions/src/providers/triggers.rs +++ /dev/null @@ -1,169 +0,0 @@ -use crate::{ - CompletionItemKind, - builder::{CompletionBuilder, PossibleCompletionItem}, - context::CompletionContext, - relevance::{CompletionRelevanceData, filtering::CompletionFilter, scoring::CompletionScore}, -}; - -use super::helper::get_completion_text_with_schema_or_alias; - -pub fn complete_functions<'a>(ctx: &'a CompletionContext, builder: &mut CompletionBuilder<'a>) { - let available_functions = &ctx.schema_cache.functions; - - for func in available_functions { - let relevance = CompletionRelevanceData::Function(func); - - let item = PossibleCompletionItem { - label: func.name.clone(), - score: CompletionScore::from(relevance.clone()), - filter: CompletionFilter::from(relevance), - description: format!("Schema: {}", func.schema), - kind: CompletionItemKind::Function, - completion_text: get_completion_text_with_schema_or_alias( - ctx, - &func.name, - &func.schema, - ), - }; - - builder.add_item(item); - } -} - -#[cfg(test)] -mod tests { - use crate::{ - CompletionItem, CompletionItemKind, complete, - test_helper::{CURSOR_POS, get_test_deps, get_test_params}, - }; - - #[tokio::test] - async fn completes_fn() { - let setup = r#" - create or replace function cool() - returns trigger - language plpgsql - security invoker - as $$ - begin - raise exception 'dont matter'; - end; - $$; - "#; - - let query = format!("select coo{}", CURSOR_POS); - - let (tree, cache) = get_test_deps(setup, query.as_str().into()).await; - let params = get_test_params(&tree, &cache, query.as_str().into()); - let results = complete(params); - - let CompletionItem { label, .. } = results - .into_iter() - .next() - .expect("Should return at least one completion item"); - - assert_eq!(label, "cool"); - } - - #[tokio::test] - async fn prefers_fn_if_invocation() { - let setup = r#" - create table coos ( - id serial primary key, - name text - ); - - create or replace function cool() - returns trigger - language plpgsql - security invoker - as $$ - begin - raise exception 'dont matter'; - end; - $$; - "#; - - let query = format!(r#"select * from coo{}()"#, CURSOR_POS); - - let (tree, cache) = get_test_deps(setup, query.as_str().into()).await; - let params = get_test_params(&tree, &cache, query.as_str().into()); - let results = complete(params); - - let CompletionItem { label, kind, .. } = results - .into_iter() - .next() - .expect("Should return at least one completion item"); - - assert_eq!(label, "cool"); - assert_eq!(kind, CompletionItemKind::Function); - } - - #[tokio::test] - async fn prefers_fn_in_select_clause() { - let setup = r#" - create table coos ( - id serial primary key, - name text - ); - - create or replace function cool() - returns trigger - language plpgsql - security invoker - as $$ - begin - raise exception 'dont matter'; - end; - $$; - "#; - - let query = format!(r#"select coo{}"#, CURSOR_POS); - - let (tree, cache) = get_test_deps(setup, query.as_str().into()).await; - let params = get_test_params(&tree, &cache, query.as_str().into()); - let results = complete(params); - - let CompletionItem { label, kind, .. } = results - .into_iter() - .next() - .expect("Should return at least one completion item"); - - assert_eq!(label, "cool"); - assert_eq!(kind, CompletionItemKind::Function); - } - - #[tokio::test] - async fn prefers_function_in_from_clause_if_invocation() { - let setup = r#" - create table coos ( - id serial primary key, - name text - ); - - create or replace function cool() - returns trigger - language plpgsql - security invoker - as $$ - begin - raise exception 'dont matter'; - end; - $$; - "#; - - let query = format!(r#"select * from coo{}()"#, CURSOR_POS); - - let (tree, cache) = get_test_deps(setup, query.as_str().into()).await; - let params = get_test_params(&tree, &cache, query.as_str().into()); - let results = complete(params); - - let CompletionItem { label, kind, .. } = results - .into_iter() - .next() - .expect("Should return at least one completion item"); - - assert_eq!(label, "cool"); - assert_eq!(kind, CompletionItemKind::Function); - } -} diff --git a/crates/pgt_completions/src/relevance/filtering.rs b/crates/pgt_completions/src/relevance/filtering.rs index beea6ddb8..ae52b87f4 100644 --- a/crates/pgt_completions/src/relevance/filtering.rs +++ b/crates/pgt_completions/src/relevance/filtering.rs @@ -255,9 +255,11 @@ mod tests { use sqlx::{Executor, PgPool}; use crate::test_helper::{ - CURSOR_POS, CompletionAssertion, assert_complete_results, assert_no_complete_results, + CompletionAssertion, assert_complete_results, assert_no_complete_results, }; + use pgt_test_utils::QueryWithCursorPosition; + #[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] async fn completion_after_asterisk(pool: PgPool) { let setup = r#" @@ -270,11 +272,16 @@ mod tests { pool.execute(setup).await.unwrap(); - assert_no_complete_results(format!("select * {}", CURSOR_POS).as_str(), None, &pool).await; + assert_no_complete_results( + format!("select * {}", QueryWithCursorPosition::cursor_marker()).as_str(), + None, + &pool, + ) + .await; // if there s a COMMA after the asterisk, we're good assert_complete_results( - format!("select *, {}", CURSOR_POS).as_str(), + format!("select *, {}", QueryWithCursorPosition::cursor_marker()).as_str(), vec![ CompletionAssertion::Label("address".into()), CompletionAssertion::Label("email".into()), @@ -288,13 +295,20 @@ mod tests { #[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] async fn completion_after_create_table(pool: PgPool) { - assert_no_complete_results(format!("create table {}", CURSOR_POS).as_str(), None, &pool) - .await; + assert_no_complete_results( + format!("create table {}", QueryWithCursorPosition::cursor_marker()).as_str(), + None, + &pool, + ) + .await; } #[sqlx::test(migrator = "pgt_test_utils::MIGRATIONS")] async fn completion_in_column_definitions(pool: PgPool) { - let query = format!(r#"create table instruments ( {} )"#, CURSOR_POS); + let query = format!( + r#"create table instruments ( {} )"#, + QueryWithCursorPosition::cursor_marker() + ); assert_no_complete_results(query.as_str(), None, &pool).await; } } diff --git a/crates/pgt_test_utils/src/lib.rs b/crates/pgt_test_utils/src/lib.rs index 2c17b198e..11bb1aebe 100644 --- a/crates/pgt_test_utils/src/lib.rs +++ b/crates/pgt_test_utils/src/lib.rs @@ -20,6 +20,12 @@ impl QueryWithCursorPosition { } } +impl From for QueryWithCursorPosition { + fn from(value: String) -> Self { + value.as_str().into() + } +} + impl From<&str> for QueryWithCursorPosition { fn from(value: &str) -> Self { let position = value diff --git a/crates/pgt_treesitter/Cargo.toml b/crates/pgt_treesitter/Cargo.toml index c675e3fa1..d2f2a12f1 100644 --- a/crates/pgt_treesitter/Cargo.toml +++ b/crates/pgt_treesitter/Cargo.toml @@ -19,6 +19,7 @@ pgt_text_size.workspace = true pgt_schema_cache.workspace = true [dev-dependencies] +pgt_test_utils.workspace = true [lib] doctest = false diff --git a/crates/pgt_treesitter/src/context/grant_parser.rs b/crates/pgt_treesitter/src/context/grant_parser.rs index db0450534..c9aebc33b 100644 --- a/crates/pgt_treesitter/src/context/grant_parser.rs +++ b/crates/pgt_treesitter/src/context/grant_parser.rs @@ -189,13 +189,13 @@ mod tests { context::grant_parser::{GrantContext, GrantParser}, }; - static CURSOR_POS: char = '€'; + use pgt_test_utils::QueryWithCursorPosition; fn with_pos(query: String) -> (usize, String) { let mut pos: Option = None; for (p, c) in query.char_indices() { - if c == CURSOR_POS { + if c == QueryWithCursorPosition::cursor_marker() { pos = Some(p); break; } @@ -203,7 +203,9 @@ mod tests { ( pos.expect("Please add cursor position!"), - query.replace(CURSOR_POS, "REPLACED_TOKEN").to_string(), + query + .replace(QueryWithCursorPosition::cursor_marker(), "REPLACED_TOKEN") + .to_string(), ) } @@ -213,7 +215,7 @@ mod tests { r#" grant {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = GrantParser::get_context(query.as_str(), pos); @@ -236,7 +238,7 @@ mod tests { r#" grant select on {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = GrantParser::get_context(query.as_str(), pos); @@ -259,7 +261,7 @@ mod tests { r#" grant select on table {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = GrantParser::get_context(query.as_str(), pos); @@ -282,7 +284,7 @@ mod tests { r#" grant select on public.{} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = GrantParser::get_context(query.as_str(), pos); @@ -305,7 +307,7 @@ mod tests { r#" grant select on table public.{} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = GrantParser::get_context(query.as_str(), pos); @@ -328,7 +330,7 @@ mod tests { r#" grant select on public.users to {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = GrantParser::get_context(query.as_str(), pos); @@ -351,7 +353,7 @@ mod tests { r#" grant select on public.{} to test_role "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = GrantParser::get_context(query.as_str(), pos); @@ -374,7 +376,7 @@ mod tests { r#" grant select on "MySchema"."MyTable" to {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = GrantParser::get_context(query.as_str(), pos); @@ -397,7 +399,7 @@ mod tests { r#" grant select on public.users to alice, {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = GrantParser::get_context(query.as_str(), pos); diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index c2f6ff0d9..0d80a2843 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -818,9 +818,11 @@ mod tests { NodeText, context::{CompletionContext, WrappingClause}, sanitization::SanitizedCompletionParams, - test_helper::{CURSOR_POS, get_text_and_position}, + test_helper::get_text_and_position, }; + use pgt_test_utils::QueryWithCursorPosition; + use super::NodeUnderCursor; fn get_tree(input: &str) -> tree_sitter::Tree { @@ -836,45 +838,72 @@ mod tests { fn identifies_clauses() { let test_cases = vec![ ( - format!("Select {}* from users;", CURSOR_POS), + format!( + "Select {}* from users;", + QueryWithCursorPosition::cursor_marker() + ), WrappingClause::Select, ), ( - format!("Select * from u{};", CURSOR_POS), + format!( + "Select * from u{};", + QueryWithCursorPosition::cursor_marker() + ), WrappingClause::From, ), ( - format!("Select {}* from users where n = 1;", CURSOR_POS), + format!( + "Select {}* from users where n = 1;", + QueryWithCursorPosition::cursor_marker() + ), WrappingClause::Select, ), ( - format!("Select * from users where {}n = 1;", CURSOR_POS), + format!( + "Select * from users where {}n = 1;", + QueryWithCursorPosition::cursor_marker() + ), WrappingClause::Where, ), ( - format!("update users set u{} = 1 where n = 2;", CURSOR_POS), + format!( + "update users set u{} = 1 where n = 2;", + QueryWithCursorPosition::cursor_marker() + ), WrappingClause::Update, ), ( - format!("update users set u = 1 where n{} = 2;", CURSOR_POS), + format!( + "update users set u = 1 where n{} = 2;", + QueryWithCursorPosition::cursor_marker() + ), WrappingClause::Where, ), ( - format!("delete{} from users;", CURSOR_POS), + format!( + "delete{} from users;", + QueryWithCursorPosition::cursor_marker() + ), WrappingClause::Delete, ), ( - format!("delete from {}users;", CURSOR_POS), + format!( + "delete from {}users;", + QueryWithCursorPosition::cursor_marker() + ), WrappingClause::From, ), ( - format!("select name, age, location from public.u{}sers", CURSOR_POS), + format!( + "select name, age, location from public.u{}sers", + QueryWithCursorPosition::cursor_marker() + ), WrappingClause::From, ), ]; for (query, expected_clause) in test_cases { - let (position, text) = get_text_and_position(query.as_str().into()); + let (position, text) = QueryWithCursorPosition::from(query).get_text_and_position(); let tree = get_tree(text.as_str()); @@ -895,15 +924,33 @@ mod tests { fn identifies_schema() { let test_cases = vec![ ( - format!("Select * from private.u{}", CURSOR_POS), + format!( + "Select * from private.u{}", + QueryWithCursorPosition::cursor_marker() + ), Some("private"), ), ( - format!("Select * from private.u{}sers()", CURSOR_POS), + format!( + "Select * from private.u{}sers()", + QueryWithCursorPosition::cursor_marker() + ), Some("private"), ), - (format!("Select * from u{}sers", CURSOR_POS), None), - (format!("Select * from u{}sers()", CURSOR_POS), None), + ( + format!( + "Select * from u{}sers", + QueryWithCursorPosition::cursor_marker() + ), + None, + ), + ( + format!( + "Select * from u{}sers()", + QueryWithCursorPosition::cursor_marker() + ), + None, + ), ]; for (query, expected_schema) in test_cases { @@ -929,16 +976,40 @@ mod tests { #[test] fn identifies_invocation() { let test_cases = vec![ - (format!("Select * from u{}sers", CURSOR_POS), false), - (format!("Select * from u{}sers()", CURSOR_POS), true), - (format!("Select cool{};", CURSOR_POS), false), - (format!("Select cool{}();", CURSOR_POS), true), ( - format!("Select upp{}ercase as title from users;", CURSOR_POS), + format!( + "Select * from u{}sers", + QueryWithCursorPosition::cursor_marker() + ), + false, + ), + ( + format!( + "Select * from u{}sers()", + QueryWithCursorPosition::cursor_marker() + ), + true, + ), + ( + format!("Select cool{};", QueryWithCursorPosition::cursor_marker()), + false, + ), + ( + format!("Select cool{}();", QueryWithCursorPosition::cursor_marker()), + true, + ), + ( + format!( + "Select upp{}ercase as title from users;", + QueryWithCursorPosition::cursor_marker() + ), false, ), ( - format!("Select upp{}ercase(name) as title from users;", CURSOR_POS), + format!( + "Select upp{}ercase(name) as title from users;", + QueryWithCursorPosition::cursor_marker() + ), true, ), ]; @@ -963,8 +1034,14 @@ mod tests { #[test] fn does_not_fail_on_leading_whitespace() { let cases = vec![ - format!("{} select * from", CURSOR_POS), - format!(" {} select * from", CURSOR_POS), + format!( + "{} select * from", + QueryWithCursorPosition::cursor_marker() + ), + format!( + " {} select * from", + QueryWithCursorPosition::cursor_marker() + ), ]; for query in cases { @@ -1002,7 +1079,10 @@ mod tests { #[test] fn does_not_fail_on_trailing_whitespace() { - let query = format!("select * from {}", CURSOR_POS); + let query = format!( + "select * from {}", + QueryWithCursorPosition::cursor_marker() + ); let (position, text) = get_text_and_position(query.as_str().into()); @@ -1032,7 +1112,7 @@ mod tests { #[test] fn does_not_fail_with_empty_statements() { - let query = format!("{}", CURSOR_POS); + let query = format!("{}", QueryWithCursorPosition::cursor_marker()); let (position, text) = get_text_and_position(query.as_str().into()); @@ -1065,7 +1145,7 @@ mod tests { fn does_not_fail_on_incomplete_keywords() { // Instead of autocompleting "FROM", we'll assume that the user // is selecting a certain column name, such as `frozen_account`. - let query = format!("select * fro{}", CURSOR_POS); + let query = format!("select * fro{}", QueryWithCursorPosition::cursor_marker()); let (position, text) = get_text_and_position(query.as_str().into()); diff --git a/crates/pgt_treesitter/src/context/policy_parser.rs b/crates/pgt_treesitter/src/context/policy_parser.rs index 4bf41db11..776645163 100644 --- a/crates/pgt_treesitter/src/context/policy_parser.rs +++ b/crates/pgt_treesitter/src/context/policy_parser.rs @@ -214,7 +214,7 @@ mod tests { context::policy_parser::{PolicyContext, PolicyStmtKind}, }; - static CURSOR_POS: char = '€'; + use pgt_test_utils::QueryWithCursorPosition; use super::PolicyParser; @@ -222,7 +222,7 @@ mod tests { let mut pos: Option = None; for (p, c) in query.char_indices() { - if c == CURSOR_POS { + if c == QueryWithCursorPosition::cursor_marker() { pos = Some(p); break; } @@ -230,7 +230,9 @@ mod tests { ( pos.expect("Please add cursor position!"), - query.replace(CURSOR_POS, "REPLACED_TOKEN").to_string(), + query + .replace(QueryWithCursorPosition::cursor_marker(), "REPLACED_TOKEN") + .to_string(), ) } @@ -240,7 +242,7 @@ mod tests { r#" create policy {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = PolicyParser::get_context(query.as_str(), pos); @@ -266,7 +268,7 @@ mod tests { r#" create policy "my cool policy" {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = PolicyParser::get_context(query.as_str(), pos); @@ -292,7 +294,7 @@ mod tests { r#" create policy "my cool policy" on {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = PolicyParser::get_context(query.as_str(), pos); @@ -318,7 +320,7 @@ mod tests { r#" create policy "my cool policy" on auth.{} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = PolicyParser::get_context(query.as_str(), pos); @@ -345,7 +347,7 @@ mod tests { create policy "my cool policy" on auth.users as {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = PolicyParser::get_context(query.as_str(), pos); @@ -373,7 +375,7 @@ mod tests { as permissive {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = PolicyParser::get_context(query.as_str(), pos); @@ -401,7 +403,7 @@ mod tests { as permissive to {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = PolicyParser::get_context(query.as_str(), pos); @@ -433,7 +435,7 @@ mod tests { to all using (true); "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = PolicyParser::get_context(query.as_str(), pos); @@ -465,7 +467,7 @@ mod tests { to all using (true); "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = PolicyParser::get_context(query.as_str(), pos); @@ -494,7 +496,7 @@ mod tests { r#" drop policy {} on auth.users; "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = PolicyParser::get_context(query.as_str(), pos); @@ -521,7 +523,7 @@ mod tests { r#" drop policy "{}" on auth.users; "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = PolicyParser::get_context(query.as_str(), pos); @@ -550,7 +552,7 @@ mod tests { r#" drop policy "{} on auth.users; "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = PolicyParser::get_context(query.as_str(), pos); @@ -568,7 +570,7 @@ mod tests { to all using (id = {}) "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = PolicyParser::get_context(query.as_str(), pos); @@ -599,7 +601,7 @@ mod tests { to all using ({} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = PolicyParser::get_context(query.as_str(), pos); @@ -630,7 +632,7 @@ mod tests { to all with check ({} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = PolicyParser::get_context(query.as_str(), pos); diff --git a/crates/pgt_treesitter/src/context/revoke_parser.rs b/crates/pgt_treesitter/src/context/revoke_parser.rs index d95de8f36..4f5b09ec8 100644 --- a/crates/pgt_treesitter/src/context/revoke_parser.rs +++ b/crates/pgt_treesitter/src/context/revoke_parser.rs @@ -182,13 +182,13 @@ mod tests { context::revoke_parser::{RevokeContext, RevokeParser}, }; - static CURSOR_POS: char = '€'; + use pgt_test_utils::QueryWithCursorPosition; fn with_pos(query: String) -> (usize, String) { let mut pos: Option = None; for (p, c) in query.char_indices() { - if c == CURSOR_POS { + if c == QueryWithCursorPosition::cursor_marker() { pos = Some(p); break; } @@ -196,7 +196,9 @@ mod tests { ( pos.expect("Please add cursor position!"), - query.replace(CURSOR_POS, "REPLACED_TOKEN").to_string(), + query + .replace(QueryWithCursorPosition::cursor_marker(), "REPLACED_TOKEN") + .to_string(), ) } @@ -206,7 +208,7 @@ mod tests { r#" revoke {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = RevokeParser::get_context(query.as_str(), pos); @@ -229,7 +231,7 @@ mod tests { r#" revoke select on {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = RevokeParser::get_context(query.as_str(), pos); @@ -252,7 +254,7 @@ mod tests { r#" revoke select on public.{} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = RevokeParser::get_context(query.as_str(), pos); @@ -275,7 +277,7 @@ mod tests { r#" revoke select on public.users from {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = RevokeParser::get_context(query.as_str(), pos); @@ -298,7 +300,7 @@ mod tests { r#" revoke select on public.users from alice, {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = RevokeParser::get_context(query.as_str(), pos); @@ -321,7 +323,7 @@ mod tests { r#" revoke select on "MySchema"."MyTable" from {} "#, - CURSOR_POS + QueryWithCursorPosition::cursor_marker() )); let context = RevokeParser::get_context(query.as_str(), pos); diff --git a/crates/pgt_workspace/src/features/completions.rs b/crates/pgt_workspace/src/features/completions.rs index c6f05c6e2..a4b44a357 100644 --- a/crates/pgt_workspace/src/features/completions.rs +++ b/crates/pgt_workspace/src/features/completions.rs @@ -82,17 +82,17 @@ mod tests { use super::get_statement_for_completions; - static CURSOR_POSITION: &str = "€"; + use pgt_test_utils::QueryWithCursorPosition; fn get_doc_and_pos(sql: &str) -> (Document, TextSize) { let pos = sql - .find(CURSOR_POSITION) + .find(QueryWithCursorPosition::cursor_marker()) .expect("Please add cursor position to test sql"); let pos: u32 = pos.try_into().unwrap(); ( - Document::new(sql.replace(CURSOR_POSITION, ""), 5), + Document::new(sql.replace(QueryWithCursorPosition::cursor_marker(), ""), 5), TextSize::new(pos), ) } @@ -107,7 +107,7 @@ mod tests { select 1; "#, - CURSOR_POSITION + QueryWithCursorPosition::cursor_marker() ); let (doc, position) = get_doc_and_pos(sql.as_str()); @@ -120,7 +120,7 @@ mod tests { #[test] fn does_not_break_when_no_statements_exist() { - let sql = CURSOR_POSITION.to_string(); + let sql = QueryWithCursorPosition::cursor_marker().to_string(); let (doc, position) = get_doc_and_pos(sql.as_str()); @@ -129,7 +129,7 @@ mod tests { #[test] fn does_not_return_overlapping_statements_if_too_close() { - let sql = format!("select * from {}select 1;", CURSOR_POSITION); + let sql = format!("select * from {}select 1;", QueryWithCursorPosition::cursor_marker()); let (doc, position) = get_doc_and_pos(sql.as_str()); @@ -141,7 +141,7 @@ mod tests { #[test] fn is_fine_with_spaces() { - let sql = format!("select * from {} ;", CURSOR_POSITION); + let sql = format!("select * from {} ;", QueryWithCursorPosition::cursor_marker()); let (doc, position) = get_doc_and_pos(sql.as_str()); @@ -153,7 +153,7 @@ mod tests { #[test] fn considers_offset() { - let sql = format!("select * from {}", CURSOR_POSITION); + let sql = format!("select * from {}", QueryWithCursorPosition::cursor_marker()); let (doc, position) = get_doc_and_pos(sql.as_str()); @@ -174,7 +174,7 @@ mod tests { select {} from cool; $$; "#, - CURSOR_POSITION + QueryWithCursorPosition::cursor_marker() ); let sql = sql.trim(); @@ -189,7 +189,7 @@ mod tests { #[test] fn does_not_consider_too_far_offset() { - let sql = format!("select * from {}", CURSOR_POSITION); + let sql = format!("select * from {}", QueryWithCursorPosition::cursor_marker()); let (doc, position) = get_doc_and_pos(sql.as_str()); @@ -198,7 +198,7 @@ mod tests { #[test] fn does_not_consider_offset_if_statement_terminated_by_semi() { - let sql = format!("select * from users;{}", CURSOR_POSITION); + let sql = format!("select * from users;{}", QueryWithCursorPosition::cursor_marker()); let (doc, position) = get_doc_and_pos(sql.as_str()); From 70cccbab19f4e8a9f84ad56b0bd6df199e89dcbf Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 19 Jul 2025 09:10:25 +0200 Subject: [PATCH 06/20] ok --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 434f554b3..5cb3fe2d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3052,6 +3052,7 @@ version = "0.0.0" dependencies = [ "clap 4.5.23", "pgt_schema_cache", + "pgt_test_utils", "pgt_text_size", "tree-sitter", "tree_sitter_sql", From b8367c5748f57e528e64a6eea6cd1479e0ec115a Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 19 Jul 2025 09:20:02 +0200 Subject: [PATCH 07/20] almost there --- crates/pgt_treesitter/src/context/mod.rs | 87 +++++++++++++----------- 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index 0d80a2843..a17949351 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -9,7 +9,7 @@ mod revoke_parser; use crate::queries::{self, QueryResult, TreeSitterQueriesExecutor}; use pgt_schema_cache::SchemaCache; -use pgt_text_size::TextRange; +use pgt_text_size::{TextRange, TextSize}; use crate::{ NodeText, @@ -147,6 +147,13 @@ impl TryFrom for WrappingNode { } } +struct TreeSitterContextParams<'a> { + pub position: TextSize, + pub text: &'a str, + pub schema: &'a pgt_schema_cache::SchemaCache, + pub tree: &'a tree_sitter::Tree, +} + #[derive(Debug)] pub(crate) struct CompletionContext<'a> { pub node_under_cursor: Option>, @@ -189,10 +196,10 @@ pub(crate) struct CompletionContext<'a> { } impl<'a> CompletionContext<'a> { - pub fn new(params: &'a SanitizedCompletionParams) -> Self { + pub fn new(params: TreeSitterContextParams<'a>) -> Self { let mut ctx = Self { - tree: params.tree.as_ref(), - text: ¶ms.text, + tree: params.tree, + text: params.text, schema_cache: params.schema, position: usize::from(params.position), node_under_cursor: None, @@ -816,9 +823,7 @@ impl<'a> CompletionContext<'a> { mod tests { use crate::{ NodeText, - context::{CompletionContext, WrappingClause}, - sanitization::SanitizedCompletionParams, - test_helper::get_text_and_position, + context::{CompletionContext, TreeSitterContextParams, WrappingClause}, }; use pgt_test_utils::QueryWithCursorPosition; @@ -907,14 +912,14 @@ mod tests { let tree = get_tree(text.as_str()); - let params = SanitizedCompletionParams { + let params = TreeSitterContextParams { position: (position as u32).into(), - text, - tree: std::borrow::Cow::Owned(tree), + text: &text, + tree: &tree, schema: &pgt_schema_cache::SchemaCache::default(), }; - let ctx = CompletionContext::new(¶ms); + let ctx = CompletionContext::new(params); assert_eq!(ctx.wrapping_clause_type, Some(expected_clause)); } @@ -954,17 +959,17 @@ mod tests { ]; for (query, expected_schema) in test_cases { - let (position, text) = get_text_and_position(query.as_str().into()); + let (position, text) = QueryWithCursorPosition::from(query).get_text_and_position(); let tree = get_tree(text.as_str()); - let params = SanitizedCompletionParams { + let params = TreeSitterContextParams { position: (position as u32).into(), - text, - tree: std::borrow::Cow::Owned(tree), + text: &text, + tree: &tree, schema: &pgt_schema_cache::SchemaCache::default(), }; - let ctx = CompletionContext::new(¶ms); + let ctx = CompletionContext::new(params); assert_eq!( ctx.schema_or_alias_name, @@ -1015,17 +1020,17 @@ mod tests { ]; for (query, is_invocation) in test_cases { - let (position, text) = get_text_and_position(query.as_str().into()); + let (position, text) = QueryWithCursorPosition::from(query).get_text_and_position(); let tree = get_tree(text.as_str()); - let params = SanitizedCompletionParams { + let params = TreeSitterContextParams { position: (position as u32).into(), - text, - tree: std::borrow::Cow::Owned(tree), + text: text.as_str(), + tree: &tree, schema: &pgt_schema_cache::SchemaCache::default(), }; - let ctx = CompletionContext::new(¶ms); + let ctx = CompletionContext::new(params); assert_eq!(ctx.is_invocation, is_invocation); } @@ -1045,18 +1050,18 @@ mod tests { ]; for query in cases { - let (position, text) = get_text_and_position(query.as_str().into()); + let (position, text) = QueryWithCursorPosition::from(query).get_text_and_position(); let tree = get_tree(text.as_str()); - let params = SanitizedCompletionParams { + let params = TreeSitterContextParams { position: (position as u32).into(), - text, - tree: std::borrow::Cow::Owned(tree), + text: &text, + tree: &tree, schema: &pgt_schema_cache::SchemaCache::default(), }; - let ctx = CompletionContext::new(¶ms); + let ctx = CompletionContext::new(params); let node = ctx.node_under_cursor.as_ref().unwrap(); @@ -1084,18 +1089,18 @@ mod tests { QueryWithCursorPosition::cursor_marker() ); - let (position, text) = get_text_and_position(query.as_str().into()); + let (position, text) = QueryWithCursorPosition::from(query).get_text_and_position(); let tree = get_tree(text.as_str()); - let params = SanitizedCompletionParams { + let params = TreeSitterContextParams { position: (position as u32).into(), - text, - tree: std::borrow::Cow::Owned(tree), + text: &text, + tree: &tree, schema: &pgt_schema_cache::SchemaCache::default(), }; - let ctx = CompletionContext::new(¶ms); + let ctx = CompletionContext::new(params); let node = ctx.node_under_cursor.as_ref().unwrap(); @@ -1114,18 +1119,18 @@ mod tests { fn does_not_fail_with_empty_statements() { let query = format!("{}", QueryWithCursorPosition::cursor_marker()); - let (position, text) = get_text_and_position(query.as_str().into()); + let (position, text) = QueryWithCursorPosition::from(query).get_text_and_position(); let tree = get_tree(text.as_str()); - let params = SanitizedCompletionParams { + let params = TreeSitterContextParams { position: (position as u32).into(), - text, - tree: std::borrow::Cow::Owned(tree), + text: &text, + tree: &tree, schema: &pgt_schema_cache::SchemaCache::default(), }; - let ctx = CompletionContext::new(¶ms); + let ctx = CompletionContext::new(params); let node = ctx.node_under_cursor.as_ref().unwrap(); @@ -1147,18 +1152,18 @@ mod tests { // is selecting a certain column name, such as `frozen_account`. let query = format!("select * fro{}", QueryWithCursorPosition::cursor_marker()); - let (position, text) = get_text_and_position(query.as_str().into()); + let (position, text) = QueryWithCursorPosition::from(query).get_text_and_position(); let tree = get_tree(text.as_str()); - let params = SanitizedCompletionParams { + let params = TreeSitterContextParams { position: (position as u32).into(), - text, - tree: std::borrow::Cow::Owned(tree), + text: &text, + tree: &tree, schema: &pgt_schema_cache::SchemaCache::default(), }; - let ctx = CompletionContext::new(¶ms); + let ctx = CompletionContext::new(params); let node = ctx.node_under_cursor.as_ref().unwrap(); From 563cdcf45ed54a415ede8fe527d8f1552b189591 Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 19 Jul 2025 09:23:34 +0200 Subject: [PATCH 08/20] =?UTF-8?q?another=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/pgt_treesitter/src/context/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index a17949351..f7f1d0f1e 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -19,7 +19,6 @@ use crate::{ policy_parser::{PolicyParser, PolicyStmtKind}, revoke_parser::RevokeParser, }, - sanitization::SanitizedCompletionParams, }; #[derive(Debug, PartialEq, Eq, Hash, Clone)] From bfc24048060af47718778d78649862dde765be7e Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 19 Jul 2025 16:16:45 +0200 Subject: [PATCH 09/20] wowa wiwa --- crates/pgt_completions/Cargo.toml | 5 +- crates/pgt_completions/src/builder.rs | 3 +- crates/pgt_completions/src/complete.rs | 10 +- .../pgt_completions/src/providers/columns.rs | 3 +- .../src/providers/functions.rs | 12 +- .../pgt_completions/src/providers/helper.rs | 3 +- .../pgt_completions/src/providers/policies.rs | 14 ++- crates/pgt_completions/src/providers/roles.rs | 2 +- .../pgt_completions/src/providers/schemas.rs | 8 +- .../pgt_completions/src/providers/tables.rs | 3 +- .../src/relevance/filtering.rs | 2 +- .../pgt_completions/src/relevance/scoring.rs | 12 +- crates/pgt_completions/src/sanitization.rs | 8 +- crates/pgt_completions/src/test_helper.rs | 4 +- crates/pgt_treesitter/src/context/mod.rs | 111 ++++++------------ crates/pgt_treesitter/src/lib.rs | 3 + 16 files changed, 96 insertions(+), 107 deletions(-) diff --git a/crates/pgt_completions/Cargo.toml b/crates/pgt_completions/Cargo.toml index f05839c59..7558ce169 100644 --- a/crates/pgt_completions/Cargo.toml +++ b/crates/pgt_completions/Cargo.toml @@ -15,11 +15,10 @@ version = "0.0.0" async-std = "1.12.0" pgt_text_size.workspace = true - - -fuzzy-matcher = "0.3.7" pgt_schema_cache.workspace = true pgt_treesitter.workspace = true + +fuzzy-matcher = "0.3.7" schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/crates/pgt_completions/src/builder.rs b/crates/pgt_completions/src/builder.rs index 96576053f..5b7dc321e 100644 --- a/crates/pgt_completions/src/builder.rs +++ b/crates/pgt_completions/src/builder.rs @@ -1,10 +1,11 @@ use crate::{ CompletionItemKind, CompletionText, - context::CompletionContext, item::CompletionItem, relevance::{filtering::CompletionFilter, scoring::CompletionScore}, }; +use pgt_treesitter::CompletionContext; + pub(crate) struct PossibleCompletionItem<'a> { pub label: String, pub description: String, diff --git a/crates/pgt_completions/src/complete.rs b/crates/pgt_completions/src/complete.rs index bd5efd19d..ae3884cc3 100644 --- a/crates/pgt_completions/src/complete.rs +++ b/crates/pgt_completions/src/complete.rs @@ -1,8 +1,9 @@ use pgt_text_size::TextSize; +use pgt_treesitter::{TreeSitterContextParams, context::CompletionContext}; + use crate::{ builder::CompletionBuilder, - context::CompletionContext, item::CompletionItem, providers::{ complete_columns, complete_functions, complete_policies, complete_roles, complete_schemas, @@ -28,7 +29,12 @@ pub struct CompletionParams<'a> { pub fn complete(params: CompletionParams) -> Vec { let sanitized_params = SanitizedCompletionParams::from(params); - let ctx = CompletionContext::new(&sanitized_params); + let ctx = CompletionContext::new(TreeSitterContextParams { + position: sanitized_params.position, + schema: sanitized_params.schema, + text: &sanitized_params.text, + tree: &sanitized_params.tree, + }); let mut builder = CompletionBuilder::new(&ctx); diff --git a/crates/pgt_completions/src/providers/columns.rs b/crates/pgt_completions/src/providers/columns.rs index e7bf49fbe..8924794c5 100644 --- a/crates/pgt_completions/src/providers/columns.rs +++ b/crates/pgt_completions/src/providers/columns.rs @@ -1,7 +1,8 @@ +use pgt_treesitter::{CompletionContext, WrappingClause}; + use crate::{ CompletionItemKind, builder::{CompletionBuilder, PossibleCompletionItem}, - context::{CompletionContext, WrappingClause}, relevance::{CompletionRelevanceData, filtering::CompletionFilter, scoring::CompletionScore}, }; diff --git a/crates/pgt_completions/src/providers/functions.rs b/crates/pgt_completions/src/providers/functions.rs index 919cd6ac9..8829772ac 100644 --- a/crates/pgt_completions/src/providers/functions.rs +++ b/crates/pgt_completions/src/providers/functions.rs @@ -1,9 +1,9 @@ use pgt_schema_cache::Function; +use pgt_treesitter::CompletionContext; use crate::{ CompletionItemKind, CompletionText, builder::{CompletionBuilder, PossibleCompletionItem}, - context::CompletionContext, providers::helper::get_range_to_replace, relevance::{CompletionRelevanceData, filtering::CompletionFilter, scoring::CompletionScore}, }; @@ -123,7 +123,10 @@ mod tests { $$; "#; - let query = format!(r#"select * from coo{}()"#, QueryWithCursorPosition::cursor_marker()); + let query = format!( + r#"select * from coo{}()"#, + QueryWithCursorPosition::cursor_marker() + ); let (tree, cache) = get_test_deps(Some(setup), query.as_str().into(), &pool).await; let params = get_test_params(&tree, &cache, query.as_str().into()); @@ -191,7 +194,10 @@ mod tests { $$; "#; - let query = format!(r#"select * from coo{}()"#, QueryWithCursorPosition::cursor_marker()); + let query = format!( + r#"select * from coo{}()"#, + QueryWithCursorPosition::cursor_marker() + ); let (tree, cache) = get_test_deps(Some(setup), query.as_str().into(), &pool).await; let params = get_test_params(&tree, &cache, query.as_str().into()); diff --git a/crates/pgt_completions/src/providers/helper.rs b/crates/pgt_completions/src/providers/helper.rs index 811125bd1..6d71192f1 100644 --- a/crates/pgt_completions/src/providers/helper.rs +++ b/crates/pgt_completions/src/providers/helper.rs @@ -1,6 +1,7 @@ use pgt_text_size::{TextRange, TextSize}; +use pgt_treesitter::CompletionContext; -use crate::{CompletionText, context::CompletionContext, remove_sanitized_token}; +use crate::{CompletionText, remove_sanitized_token}; pub(crate) fn find_matching_alias_for_table( ctx: &CompletionContext, diff --git a/crates/pgt_completions/src/providers/policies.rs b/crates/pgt_completions/src/providers/policies.rs index 548eb0410..f0071c2ca 100644 --- a/crates/pgt_completions/src/providers/policies.rs +++ b/crates/pgt_completions/src/providers/policies.rs @@ -1,9 +1,9 @@ use pgt_text_size::{TextRange, TextSize}; +use pgt_treesitter::CompletionContext; use crate::{ CompletionItemKind, CompletionText, builder::{CompletionBuilder, PossibleCompletionItem}, - context::CompletionContext, relevance::{CompletionRelevanceData, filtering::CompletionFilter, scoring::CompletionScore}, }; @@ -90,7 +90,11 @@ mod tests { pool.execute(setup).await.unwrap(); assert_complete_results( - format!("alter policy \"{}\" on private.users;", QueryWithCursorPosition::cursor_marker()).as_str(), + format!( + "alter policy \"{}\" on private.users;", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![ CompletionAssertion::Label("read for public users disallowed".into()), CompletionAssertion::Label("write for public users allowed".into()), @@ -101,7 +105,11 @@ mod tests { .await; assert_complete_results( - format!("alter policy \"w{}\" on private.users;", QueryWithCursorPosition::cursor_marker()).as_str(), + format!( + "alter policy \"w{}\" on private.users;", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![CompletionAssertion::Label( "write for public users allowed".into(), )], diff --git a/crates/pgt_completions/src/providers/roles.rs b/crates/pgt_completions/src/providers/roles.rs index f86d18c6e..2a1fb41a6 100644 --- a/crates/pgt_completions/src/providers/roles.rs +++ b/crates/pgt_completions/src/providers/roles.rs @@ -1,9 +1,9 @@ use crate::{ CompletionItemKind, builder::{CompletionBuilder, PossibleCompletionItem}, - context::CompletionContext, relevance::{CompletionRelevanceData, filtering::CompletionFilter, scoring::CompletionScore}, }; +use pgt_treesitter::CompletionContext; pub fn complete_roles<'a>(ctx: &CompletionContext<'a>, builder: &mut CompletionBuilder<'a>) { let available_roles = &ctx.schema_cache.roles; diff --git a/crates/pgt_completions/src/providers/schemas.rs b/crates/pgt_completions/src/providers/schemas.rs index 11215df4e..641f1b32e 100644 --- a/crates/pgt_completions/src/providers/schemas.rs +++ b/crates/pgt_completions/src/providers/schemas.rs @@ -1,8 +1,8 @@ use crate::{ builder::{CompletionBuilder, PossibleCompletionItem}, - context::CompletionContext, relevance::{CompletionRelevanceData, filtering::CompletionFilter, scoring::CompletionScore}, }; +use pgt_treesitter::CompletionContext; pub fn complete_schemas<'a>(ctx: &'a CompletionContext, builder: &mut CompletionBuilder<'a>) { let available_schemas = &ctx.schema_cache.schemas; @@ -99,7 +99,11 @@ mod tests { "#; assert_complete_results( - format!("select * from u{}", QueryWithCursorPosition::cursor_marker()).as_str(), + format!( + "select * from u{}", + QueryWithCursorPosition::cursor_marker() + ) + .as_str(), vec![ CompletionAssertion::LabelAndKind("users".into(), CompletionItemKind::Table), CompletionAssertion::LabelAndKind("ultimate".into(), CompletionItemKind::Schema), diff --git a/crates/pgt_completions/src/providers/tables.rs b/crates/pgt_completions/src/providers/tables.rs index 1844f2b60..464dee021 100644 --- a/crates/pgt_completions/src/providers/tables.rs +++ b/crates/pgt_completions/src/providers/tables.rs @@ -1,6 +1,7 @@ +use pgt_treesitter::CompletionContext; + use crate::{ builder::{CompletionBuilder, PossibleCompletionItem}, - context::CompletionContext, item::CompletionItemKind, relevance::{CompletionRelevanceData, filtering::CompletionFilter, scoring::CompletionScore}, }; diff --git a/crates/pgt_completions/src/relevance/filtering.rs b/crates/pgt_completions/src/relevance/filtering.rs index ae52b87f4..4b24a45b1 100644 --- a/crates/pgt_completions/src/relevance/filtering.rs +++ b/crates/pgt_completions/src/relevance/filtering.rs @@ -1,6 +1,6 @@ use pgt_schema_cache::ProcKind; -use crate::context::{CompletionContext, NodeUnderCursor, WrappingClause, WrappingNode}; +use pgt_treesitter::context::{CompletionContext, NodeUnderCursor, WrappingClause, WrappingNode}; use super::CompletionRelevanceData; diff --git a/crates/pgt_completions/src/relevance/scoring.rs b/crates/pgt_completions/src/relevance/scoring.rs index a0b5efa53..11c78d884 100644 --- a/crates/pgt_completions/src/relevance/scoring.rs +++ b/crates/pgt_completions/src/relevance/scoring.rs @@ -1,6 +1,8 @@ use fuzzy_matcher::{FuzzyMatcher, skim::SkimMatcherV2}; -use crate::context::{CompletionContext, WrappingClause, WrappingNode}; +use pgt_treesitter::context::{CompletionContext, WrappingClause, WrappingNode}; + +use crate::sanitization; use super::CompletionRelevanceData; @@ -37,8 +39,8 @@ impl CompletionScore<'_> { fn check_matches_query_input(&mut self, ctx: &CompletionContext) { let content = match ctx.get_node_under_cursor_content() { - Some(c) => c.replace('"', ""), - None => return, + Some(c) if !sanitization::is_sanitized_token(c.as_str()) => c.replace('"', ""), + _ => return, }; let name = match self.data { @@ -142,7 +144,9 @@ impl CompletionScore<'_> { }; let has_mentioned_schema = ctx.schema_or_alias_name.is_some(); - let has_node_text = ctx.get_node_under_cursor_content().is_some(); + let has_node_text = ctx + .get_node_under_cursor_content() + .is_some_and(|txt| !sanitization::is_sanitized_token(txt.as_str())); self.score += match self.data { CompletionRelevanceData::Table(_) => match wrapping_node { diff --git a/crates/pgt_completions/src/sanitization.rs b/crates/pgt_completions/src/sanitization.rs index bf4d98160..155256c8a 100644 --- a/crates/pgt_completions/src/sanitization.rs +++ b/crates/pgt_completions/src/sanitization.rs @@ -23,6 +23,10 @@ pub(crate) fn remove_sanitized_token(it: &str) -> String { it.replace(SANITIZED_TOKEN, "") } +pub(crate) fn is_sanitized_token(txt: &str) -> bool { + txt == SANITIZED_TOKEN +} + #[derive(PartialEq, Eq, Debug)] pub(crate) enum NodeText { Replaced, @@ -118,10 +122,6 @@ where tree: Cow::Borrowed(params.tree), } } - - pub fn is_sanitized_token(txt: &str) -> bool { - txt == SANITIZED_TOKEN - } } /// Checks if the cursor is positioned inbetween two SQL nodes. diff --git a/crates/pgt_completions/src/test_helper.rs b/crates/pgt_completions/src/test_helper.rs index 37d2174bf..e6c347614 100644 --- a/crates/pgt_completions/src/test_helper.rs +++ b/crates/pgt_completions/src/test_helper.rs @@ -1,5 +1,3 @@ -use std::fmt::Display; - use pgt_schema_cache::SchemaCache; use pgt_test_utils::QueryWithCursorPosition; use sqlx::{Executor, PgPool}; @@ -62,7 +60,7 @@ pub(crate) fn get_test_params<'a>( schema_cache: &'a pgt_schema_cache::SchemaCache, sql: QueryWithCursorPosition, ) -> CompletionParams<'a> { - let (position, text) = get_text_and_position(sql); + let (position, text) = sql.get_text_and_position(); CompletionParams { position: (position as u32).into(), diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index f7f1d0f1e..d1ba616e6 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -11,14 +11,11 @@ use crate::queries::{self, QueryResult, TreeSitterQueriesExecutor}; use pgt_schema_cache::SchemaCache; use pgt_text_size::{TextRange, TextSize}; -use crate::{ - NodeText, - context::{ - base_parser::CompletionStatementParser, - grant_parser::GrantParser, - policy_parser::{PolicyParser, PolicyStmtKind}, - revoke_parser::RevokeParser, - }, +use crate::context::{ + base_parser::CompletionStatementParser, + grant_parser::GrantParser, + policy_parser::{PolicyParser, PolicyStmtKind}, + revoke_parser::RevokeParser, }; #[derive(Debug, PartialEq, Eq, Hash, Clone)] @@ -55,9 +52,9 @@ pub enum WrappingClause<'a> { } #[derive(PartialEq, Eq, Hash, Debug, Clone)] -pub(crate) struct MentionedColumn { - pub(crate) column: String, - pub(crate) alias: Option, +pub struct MentionedColumn { + pub column: String, + pub alias: Option, } /// We can map a few nodes, such as the "update" node, to actual SQL clauses. @@ -77,10 +74,10 @@ pub enum WrappingNode { } #[derive(Debug)] -pub(crate) enum NodeUnderCursor<'a> { +pub enum NodeUnderCursor<'a> { TsNode(tree_sitter::Node<'a>), CustomNode { - text: NodeText, + text: String, range: TextRange, kind: String, previous_node_kind: Option, @@ -146,7 +143,7 @@ impl TryFrom for WrappingNode { } } -struct TreeSitterContextParams<'a> { +pub struct TreeSitterContextParams<'a> { pub position: TextSize, pub text: &'a str, pub schema: &'a pgt_schema_cache::SchemaCache, @@ -154,7 +151,7 @@ struct TreeSitterContextParams<'a> { } #[derive(Debug)] -pub(crate) struct CompletionContext<'a> { +pub struct CompletionContext<'a> { pub node_under_cursor: Option>, pub tree: &'a tree_sitter::Tree, @@ -401,29 +398,18 @@ impl<'a> CompletionContext<'a> { } } - fn get_ts_node_content(&self, ts_node: &tree_sitter::Node<'a>) -> Option { + fn get_ts_node_content(&self, ts_node: &tree_sitter::Node<'a>) -> Option { let source = self.text; - ts_node.utf8_text(source.as_bytes()).ok().map(|txt| { - if SanitizedCompletionParams::is_sanitized_token(txt) { - NodeText::Replaced - } else { - NodeText::Original(txt.into()) - } - }) + ts_node + .utf8_text(source.as_bytes()) + .ok() + .map(|txt| txt.into()) } pub fn get_node_under_cursor_content(&self) -> Option { match self.node_under_cursor.as_ref()? { - NodeUnderCursor::TsNode(node) => { - self.get_ts_node_content(node).and_then(|nt| match nt { - NodeText::Replaced => None, - NodeText::Original(c) => Some(c.to_string()), - }) - } - NodeUnderCursor::CustomNode { text, .. } => match text { - NodeText::Replaced => None, - NodeText::Original(c) => Some(c.to_string()), - }, + NodeUnderCursor::TsNode(node) => self.get_ts_node_content(node), + NodeUnderCursor::CustomNode { text, .. } => Some(text.clone()), } } @@ -505,15 +491,10 @@ impl<'a> CompletionContext<'a> { match current_node_kind { "object_reference" | "field" => { let content = self.get_ts_node_content(¤t_node); - if let Some(node_txt) = content { - match node_txt { - NodeText::Original(txt) => { - let parts: Vec<&str> = txt.split('.').collect(); - if parts.len() == 2 { - self.schema_or_alias_name = Some(parts[0].to_string()); - } - } - NodeText::Replaced => {} + if let Some(txt) = content { + let parts: Vec<&str> = txt.split('.').collect(); + if parts.len() == 2 { + self.schema_or_alias_name = Some(parts[0].to_string()); } } } @@ -642,12 +623,7 @@ impl<'a> CompletionContext<'a> { break; } - if let Some(sibling_content) = - self.get_ts_node_content(&sib).and_then(|txt| match txt { - NodeText::Original(txt) => Some(txt), - NodeText::Replaced => None, - }) - { + if let Some(sibling_content) = self.get_ts_node_content(&sib) { if sibling_content == tokens[idx] { idx += 1; } @@ -678,9 +654,7 @@ impl<'a> CompletionContext<'a> { while let Some(sib) = first_sibling.next_sibling() { match sib.kind() { "object_reference" => { - if let Some(NodeText::Original(txt)) = - self.get_ts_node_content(&sib) - { + if let Some(txt) = self.get_ts_node_content(&sib) { let mut iter = txt.split('.').rev(); let table = iter.next().unwrap().to_string(); let schema = iter.next().map(|s| s.to_string()); @@ -694,9 +668,7 @@ impl<'a> CompletionContext<'a> { } "column" => { - if let Some(NodeText::Original(txt)) = - self.get_ts_node_content(&sib) - { + if let Some(txt) = self.get_ts_node_content(&sib) { let entry = MentionedColumn { column: txt, alias: None, @@ -721,7 +693,7 @@ impl<'a> CompletionContext<'a> { WrappingClause::AlterColumn => { while let Some(sib) = first_sibling.next_sibling() { if sib.kind() == "object_reference" { - if let Some(NodeText::Original(txt)) = self.get_ts_node_content(&sib) { + if let Some(txt) = self.get_ts_node_content(&sib) { let mut iter = txt.split('.').rev(); let table = iter.next().unwrap().to_string(); let schema = iter.next().map(|s| s.to_string()); @@ -781,7 +753,7 @@ impl<'a> CompletionContext<'a> { } } - pub(crate) fn parent_matches_one_of_kind(&self, kinds: &[&'static str]) -> bool { + pub fn parent_matches_one_of_kind(&self, kinds: &[&'static str]) -> bool { self.node_under_cursor .as_ref() .is_some_and(|under_cursor| match under_cursor { @@ -792,7 +764,7 @@ impl<'a> CompletionContext<'a> { NodeUnderCursor::CustomNode { .. } => false, }) } - pub(crate) fn before_cursor_matches_kind(&self, kinds: &[&'static str]) -> bool { + pub fn before_cursor_matches_kind(&self, kinds: &[&'static str]) -> bool { self.node_under_cursor.as_ref().is_some_and(|under_cursor| { match under_cursor { NodeUnderCursor::TsNode(node) => { @@ -820,10 +792,7 @@ impl<'a> CompletionContext<'a> { #[cfg(test)] mod tests { - use crate::{ - NodeText, - context::{CompletionContext, TreeSitterContextParams, WrappingClause}, - }; + use crate::context::{CompletionContext, TreeSitterContextParams, WrappingClause}; use pgt_test_utils::QueryWithCursorPosition; @@ -1066,10 +1035,7 @@ mod tests { match node { NodeUnderCursor::TsNode(node) => { - assert_eq!( - ctx.get_ts_node_content(node), - Some(NodeText::Original("select".into())) - ); + assert_eq!(ctx.get_ts_node_content(node), Some("select".into())); assert_eq!( ctx.wrapping_clause_type, @@ -1105,10 +1071,7 @@ mod tests { match node { NodeUnderCursor::TsNode(node) => { - assert_eq!( - ctx.get_ts_node_content(node), - Some(NodeText::Original("from".into())) - ); + assert_eq!(ctx.get_ts_node_content(node), Some("from".into())); } _ => unreachable!(), } @@ -1135,10 +1098,7 @@ mod tests { match node { NodeUnderCursor::TsNode(node) => { - assert_eq!( - ctx.get_ts_node_content(node), - Some(NodeText::Original("".into())) - ); + assert_eq!(ctx.get_ts_node_content(node), Some("".into())); assert_eq!(ctx.wrapping_clause_type, None); } _ => unreachable!(), @@ -1168,10 +1128,7 @@ mod tests { match node { NodeUnderCursor::TsNode(node) => { - assert_eq!( - ctx.get_ts_node_content(node), - Some(NodeText::Original("fro".into())) - ); + assert_eq!(ctx.get_ts_node_content(node), Some("fro".into())); assert_eq!(ctx.wrapping_clause_type, Some(WrappingClause::Select)); } _ => unreachable!(), diff --git a/crates/pgt_treesitter/src/lib.rs b/crates/pgt_treesitter/src/lib.rs index 483c539d7..6b19db53a 100644 --- a/crates/pgt_treesitter/src/lib.rs +++ b/crates/pgt_treesitter/src/lib.rs @@ -1,2 +1,5 @@ pub mod context; pub mod queries; + +pub use context::*; +pub use queries::*; From fee1ccf5de0276d738f0a6e7ddbdd920fdc7e065 Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 19 Jul 2025 16:18:33 +0200 Subject: [PATCH 10/20] ok --- Cargo.toml | 2 +- crates/pgt_completions/Cargo.toml | 22 +++++++++---------- crates/pgt_treesitter/Cargo.toml | 8 +++---- crates/pgt_treesitter/src/context/mod.rs | 12 +++++----- crates/pgt_typecheck/Cargo.toml | 20 ++++++++--------- .../pgt_workspace/src/features/completions.rs | 20 +++++++++++++---- 6 files changed, 48 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2da8d4e12..a5195d2da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,7 +82,7 @@ pgt_suppressions = { path = "./crates/pgt_suppressions", version = "0. pgt_text_edit = { path = "./crates/pgt_text_edit", version = "0.0.0" } pgt_text_size = { path = "./crates/pgt_text_size", version = "0.0.0" } pgt_tokenizer = { path = "./crates/pgt_tokenizer", version = "0.0.0" } -pgt_treesitter = { path = "./crates/pgt_treesitter", version = "0.0.0" } +pgt_treesitter = { path = "./crates/pgt_treesitter", version = "0.0.0" } pgt_typecheck = { path = "./crates/pgt_typecheck", version = "0.0.0" } pgt_workspace = { path = "./crates/pgt_workspace", version = "0.0.0" } diff --git a/crates/pgt_completions/Cargo.toml b/crates/pgt_completions/Cargo.toml index 7558ce169..0ebb8e56e 100644 --- a/crates/pgt_completions/Cargo.toml +++ b/crates/pgt_completions/Cargo.toml @@ -14,17 +14,17 @@ version = "0.0.0" [dependencies] async-std = "1.12.0" -pgt_text_size.workspace = true -pgt_schema_cache.workspace = true -pgt_treesitter.workspace = true - -fuzzy-matcher = "0.3.7" -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 +pgt_schema_cache.workspace = true +pgt_text_size.workspace = true +pgt_treesitter.workspace = true + +fuzzy-matcher = "0.3.7" +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 diff --git a/crates/pgt_treesitter/Cargo.toml b/crates/pgt_treesitter/Cargo.toml index d2f2a12f1..f2d8b46e1 100644 --- a/crates/pgt_treesitter/Cargo.toml +++ b/crates/pgt_treesitter/Cargo.toml @@ -12,11 +12,11 @@ version = "0.0.0" [dependencies] -clap = { version = "4.5.23", features = ["derive"] } -tree-sitter.workspace = true -tree_sitter_sql.workspace = true -pgt_text_size.workspace = true +clap = { version = "4.5.23", features = ["derive"] } pgt_schema_cache.workspace = true +pgt_text_size.workspace = true +tree-sitter.workspace = true +tree_sitter_sql.workspace = true [dev-dependencies] pgt_test_utils.workspace = true diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index d1ba616e6..4f6b1488d 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -212,11 +212,11 @@ impl<'a> CompletionContext<'a> { // policy handling is important to Supabase, but they are a PostgreSQL specific extension, // so the tree_sitter_sql language does not support it. // We infer the context manually. - if PolicyParser::looks_like_matching_stmt(¶ms.text) { + if PolicyParser::looks_like_matching_stmt(params.text) { ctx.gather_policy_context(); - } else if GrantParser::looks_like_matching_stmt(¶ms.text) { + } else if GrantParser::looks_like_matching_stmt(params.text) { ctx.gather_grant_context(); - } else if RevokeParser::looks_like_matching_stmt(¶ms.text) { + } else if RevokeParser::looks_like_matching_stmt(params.text) { ctx.gather_revoke_context(); } else { ctx.gather_tree_context(); @@ -230,7 +230,7 @@ impl<'a> CompletionContext<'a> { let revoke_context = RevokeParser::get_context(self.text, self.position); self.node_under_cursor = Some(NodeUnderCursor::CustomNode { - text: revoke_context.node_text.into(), + text: revoke_context.node_text, range: revoke_context.node_range, kind: revoke_context.node_kind.clone(), previous_node_kind: None, @@ -258,7 +258,7 @@ impl<'a> CompletionContext<'a> { let grant_context = GrantParser::get_context(self.text, self.position); self.node_under_cursor = Some(NodeUnderCursor::CustomNode { - text: grant_context.node_text.into(), + text: grant_context.node_text, range: grant_context.node_range, kind: grant_context.node_kind.clone(), previous_node_kind: None, @@ -286,7 +286,7 @@ impl<'a> CompletionContext<'a> { let policy_context = PolicyParser::get_context(self.text, self.position); self.node_under_cursor = Some(NodeUnderCursor::CustomNode { - text: policy_context.node_text.into(), + text: policy_context.node_text, range: policy_context.node_range, kind: policy_context.node_kind.clone(), previous_node_kind: Some(policy_context.previous_node_kind), diff --git a/crates/pgt_typecheck/Cargo.toml b/crates/pgt_typecheck/Cargo.toml index 34e6ef63f..175ecd596 100644 --- a/crates/pgt_typecheck/Cargo.toml +++ b/crates/pgt_typecheck/Cargo.toml @@ -12,16 +12,16 @@ version = "0.0.0" [dependencies] -pgt_console.workspace = true -pgt_diagnostics.workspace = true -pgt_query_ext.workspace = true -pgt_schema_cache.workspace = true -pgt_text_size.workspace = true -pgt_treesitter.workspace = true -sqlx.workspace = true -tokio.workspace = true -tree-sitter.workspace = true -tree_sitter_sql.workspace = true +pgt_console.workspace = true +pgt_diagnostics.workspace = true +pgt_query_ext.workspace = true +pgt_schema_cache.workspace = true +pgt_text_size.workspace = true +pgt_treesitter.workspace = true +sqlx.workspace = true +tokio.workspace = true +tree-sitter.workspace = true +tree_sitter_sql.workspace = true [dev-dependencies] insta.workspace = true diff --git a/crates/pgt_workspace/src/features/completions.rs b/crates/pgt_workspace/src/features/completions.rs index a4b44a357..a41dd06eb 100644 --- a/crates/pgt_workspace/src/features/completions.rs +++ b/crates/pgt_workspace/src/features/completions.rs @@ -129,7 +129,10 @@ mod tests { #[test] fn does_not_return_overlapping_statements_if_too_close() { - let sql = format!("select * from {}select 1;", QueryWithCursorPosition::cursor_marker()); + let sql = format!( + "select * from {}select 1;", + QueryWithCursorPosition::cursor_marker() + ); let (doc, position) = get_doc_and_pos(sql.as_str()); @@ -141,7 +144,10 @@ mod tests { #[test] fn is_fine_with_spaces() { - let sql = format!("select * from {} ;", QueryWithCursorPosition::cursor_marker()); + let sql = format!( + "select * from {} ;", + QueryWithCursorPosition::cursor_marker() + ); let (doc, position) = get_doc_and_pos(sql.as_str()); @@ -189,7 +195,10 @@ mod tests { #[test] fn does_not_consider_too_far_offset() { - let sql = format!("select * from {}", QueryWithCursorPosition::cursor_marker()); + let sql = format!( + "select * from {}", + QueryWithCursorPosition::cursor_marker() + ); let (doc, position) = get_doc_and_pos(sql.as_str()); @@ -198,7 +207,10 @@ mod tests { #[test] fn does_not_consider_offset_if_statement_terminated_by_semi() { - let sql = format!("select * from users;{}", QueryWithCursorPosition::cursor_marker()); + let sql = format!( + "select * from users;{}", + QueryWithCursorPosition::cursor_marker() + ); let (doc, position) = get_doc_and_pos(sql.as_str()); From 78b81520b555e0ccc65826303ded65140396fae4 Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 19 Jul 2025 16:21:28 +0200 Subject: [PATCH 11/20] renamed it --- crates/pgt_completions/src/builder.rs | 6 +++--- crates/pgt_completions/src/complete.rs | 4 ++-- .../pgt_completions/src/providers/columns.rs | 4 ++-- .../src/providers/functions.rs | 6 +++--- .../pgt_completions/src/providers/helper.rs | 8 ++++---- .../pgt_completions/src/providers/policies.rs | 4 ++-- crates/pgt_completions/src/providers/roles.rs | 4 ++-- .../pgt_completions/src/providers/schemas.rs | 4 ++-- .../pgt_completions/src/providers/tables.rs | 4 ++-- .../src/relevance/filtering.rs | 12 +++++------ .../pgt_completions/src/relevance/scoring.rs | 18 ++++++++--------- crates/pgt_treesitter/src/context/mod.rs | 20 +++++++++---------- 12 files changed, 47 insertions(+), 47 deletions(-) diff --git a/crates/pgt_completions/src/builder.rs b/crates/pgt_completions/src/builder.rs index 5b7dc321e..bf8eb66a6 100644 --- a/crates/pgt_completions/src/builder.rs +++ b/crates/pgt_completions/src/builder.rs @@ -4,7 +4,7 @@ use crate::{ relevance::{filtering::CompletionFilter, scoring::CompletionScore}, }; -use pgt_treesitter::CompletionContext; +use pgt_treesitter::TreesitterContext; pub(crate) struct PossibleCompletionItem<'a> { pub label: String, @@ -18,11 +18,11 @@ pub(crate) struct PossibleCompletionItem<'a> { pub(crate) struct CompletionBuilder<'a> { items: Vec>, - ctx: &'a CompletionContext<'a>, + ctx: &'a TreesitterContext<'a>, } impl<'a> CompletionBuilder<'a> { - pub fn new(ctx: &'a CompletionContext) -> Self { + pub fn new(ctx: &'a TreesitterContext) -> Self { CompletionBuilder { items: vec![], ctx } } diff --git a/crates/pgt_completions/src/complete.rs b/crates/pgt_completions/src/complete.rs index ae3884cc3..cab90fb52 100644 --- a/crates/pgt_completions/src/complete.rs +++ b/crates/pgt_completions/src/complete.rs @@ -1,6 +1,6 @@ use pgt_text_size::TextSize; -use pgt_treesitter::{TreeSitterContextParams, context::CompletionContext}; +use pgt_treesitter::{TreeSitterContextParams, context::TreesitterContext}; use crate::{ builder::CompletionBuilder, @@ -29,7 +29,7 @@ pub struct CompletionParams<'a> { pub fn complete(params: CompletionParams) -> Vec { let sanitized_params = SanitizedCompletionParams::from(params); - let ctx = CompletionContext::new(TreeSitterContextParams { + let ctx = TreesitterContext::new(TreeSitterContextParams { position: sanitized_params.position, schema: sanitized_params.schema, text: &sanitized_params.text, diff --git a/crates/pgt_completions/src/providers/columns.rs b/crates/pgt_completions/src/providers/columns.rs index 8924794c5..31d0f1c85 100644 --- a/crates/pgt_completions/src/providers/columns.rs +++ b/crates/pgt_completions/src/providers/columns.rs @@ -1,4 +1,4 @@ -use pgt_treesitter::{CompletionContext, WrappingClause}; +use pgt_treesitter::{TreesitterContext, WrappingClause}; use crate::{ CompletionItemKind, @@ -8,7 +8,7 @@ use crate::{ use super::helper::{find_matching_alias_for_table, get_completion_text_with_schema_or_alias}; -pub fn complete_columns<'a>(ctx: &CompletionContext<'a>, builder: &mut CompletionBuilder<'a>) { +pub fn complete_columns<'a>(ctx: &TreesitterContext<'a>, builder: &mut CompletionBuilder<'a>) { let available_columns = &ctx.schema_cache.columns; for col in available_columns { diff --git a/crates/pgt_completions/src/providers/functions.rs b/crates/pgt_completions/src/providers/functions.rs index 8829772ac..00240ead2 100644 --- a/crates/pgt_completions/src/providers/functions.rs +++ b/crates/pgt_completions/src/providers/functions.rs @@ -1,5 +1,5 @@ use pgt_schema_cache::Function; -use pgt_treesitter::CompletionContext; +use pgt_treesitter::TreesitterContext; use crate::{ CompletionItemKind, CompletionText, @@ -10,7 +10,7 @@ use crate::{ use super::helper::get_completion_text_with_schema_or_alias; -pub fn complete_functions<'a>(ctx: &'a CompletionContext, builder: &mut CompletionBuilder<'a>) { +pub fn complete_functions<'a>(ctx: &'a TreesitterContext, builder: &mut CompletionBuilder<'a>) { let available_functions = &ctx.schema_cache.functions; for func in available_functions { @@ -30,7 +30,7 @@ pub fn complete_functions<'a>(ctx: &'a CompletionContext, builder: &mut Completi } } -fn get_completion_text(ctx: &CompletionContext, func: &Function) -> CompletionText { +fn get_completion_text(ctx: &TreesitterContext, func: &Function) -> CompletionText { let range = get_range_to_replace(ctx); let mut text = get_completion_text_with_schema_or_alias(ctx, &func.name, &func.schema) .map(|ct| ct.text) diff --git a/crates/pgt_completions/src/providers/helper.rs b/crates/pgt_completions/src/providers/helper.rs index 6d71192f1..cd1046f12 100644 --- a/crates/pgt_completions/src/providers/helper.rs +++ b/crates/pgt_completions/src/providers/helper.rs @@ -1,10 +1,10 @@ use pgt_text_size::{TextRange, TextSize}; -use pgt_treesitter::CompletionContext; +use pgt_treesitter::TreesitterContext; use crate::{CompletionText, remove_sanitized_token}; pub(crate) fn find_matching_alias_for_table( - ctx: &CompletionContext, + ctx: &TreesitterContext, table_name: &str, ) -> Option { for (alias, table) in ctx.mentioned_table_aliases.iter() { @@ -15,7 +15,7 @@ pub(crate) fn find_matching_alias_for_table( None } -pub(crate) fn get_range_to_replace(ctx: &CompletionContext) -> TextRange { +pub(crate) fn get_range_to_replace(ctx: &TreesitterContext) -> TextRange { match ctx.node_under_cursor.as_ref() { Some(node) => { let content = ctx.get_node_under_cursor_content().unwrap_or("".into()); @@ -31,7 +31,7 @@ pub(crate) fn get_range_to_replace(ctx: &CompletionContext) -> TextRange { } pub(crate) fn get_completion_text_with_schema_or_alias( - ctx: &CompletionContext, + ctx: &TreesitterContext, item_name: &str, schema_or_alias_name: &str, ) -> Option { diff --git a/crates/pgt_completions/src/providers/policies.rs b/crates/pgt_completions/src/providers/policies.rs index f0071c2ca..6abdec787 100644 --- a/crates/pgt_completions/src/providers/policies.rs +++ b/crates/pgt_completions/src/providers/policies.rs @@ -1,5 +1,5 @@ use pgt_text_size::{TextRange, TextSize}; -use pgt_treesitter::CompletionContext; +use pgt_treesitter::TreesitterContext; use crate::{ CompletionItemKind, CompletionText, @@ -9,7 +9,7 @@ use crate::{ use super::helper::get_range_to_replace; -pub fn complete_policies<'a>(ctx: &CompletionContext<'a>, builder: &mut CompletionBuilder<'a>) { +pub fn complete_policies<'a>(ctx: &TreesitterContext<'a>, builder: &mut CompletionBuilder<'a>) { let available_policies = &ctx.schema_cache.policies; let surrounded_by_quotes = ctx diff --git a/crates/pgt_completions/src/providers/roles.rs b/crates/pgt_completions/src/providers/roles.rs index 2a1fb41a6..0e0770dc6 100644 --- a/crates/pgt_completions/src/providers/roles.rs +++ b/crates/pgt_completions/src/providers/roles.rs @@ -3,9 +3,9 @@ use crate::{ builder::{CompletionBuilder, PossibleCompletionItem}, relevance::{CompletionRelevanceData, filtering::CompletionFilter, scoring::CompletionScore}, }; -use pgt_treesitter::CompletionContext; +use pgt_treesitter::TreesitterContext; -pub fn complete_roles<'a>(ctx: &CompletionContext<'a>, builder: &mut CompletionBuilder<'a>) { +pub fn complete_roles<'a>(ctx: &TreesitterContext<'a>, builder: &mut CompletionBuilder<'a>) { let available_roles = &ctx.schema_cache.roles; for role in available_roles { diff --git a/crates/pgt_completions/src/providers/schemas.rs b/crates/pgt_completions/src/providers/schemas.rs index 641f1b32e..39e7d08e2 100644 --- a/crates/pgt_completions/src/providers/schemas.rs +++ b/crates/pgt_completions/src/providers/schemas.rs @@ -2,9 +2,9 @@ use crate::{ builder::{CompletionBuilder, PossibleCompletionItem}, relevance::{CompletionRelevanceData, filtering::CompletionFilter, scoring::CompletionScore}, }; -use pgt_treesitter::CompletionContext; +use pgt_treesitter::TreesitterContext; -pub fn complete_schemas<'a>(ctx: &'a CompletionContext, builder: &mut CompletionBuilder<'a>) { +pub fn complete_schemas<'a>(ctx: &'a TreesitterContext, builder: &mut CompletionBuilder<'a>) { let available_schemas = &ctx.schema_cache.schemas; for schema in available_schemas { diff --git a/crates/pgt_completions/src/providers/tables.rs b/crates/pgt_completions/src/providers/tables.rs index 464dee021..d32f1791e 100644 --- a/crates/pgt_completions/src/providers/tables.rs +++ b/crates/pgt_completions/src/providers/tables.rs @@ -1,4 +1,4 @@ -use pgt_treesitter::CompletionContext; +use pgt_treesitter::TreesitterContext; use crate::{ builder::{CompletionBuilder, PossibleCompletionItem}, @@ -8,7 +8,7 @@ use crate::{ use super::helper::get_completion_text_with_schema_or_alias; -pub fn complete_tables<'a>(ctx: &'a CompletionContext, builder: &mut CompletionBuilder<'a>) { +pub fn complete_tables<'a>(ctx: &'a TreesitterContext, builder: &mut CompletionBuilder<'a>) { let available_tables = &ctx.schema_cache.tables; for table in available_tables { diff --git a/crates/pgt_completions/src/relevance/filtering.rs b/crates/pgt_completions/src/relevance/filtering.rs index 4b24a45b1..18e3d7ce5 100644 --- a/crates/pgt_completions/src/relevance/filtering.rs +++ b/crates/pgt_completions/src/relevance/filtering.rs @@ -1,6 +1,6 @@ use pgt_schema_cache::ProcKind; -use pgt_treesitter::context::{CompletionContext, NodeUnderCursor, WrappingClause, WrappingNode}; +use pgt_treesitter::context::{NodeUnderCursor, TreesitterContext, WrappingClause, WrappingNode}; use super::CompletionRelevanceData; @@ -16,7 +16,7 @@ impl<'a> From> for CompletionFilter<'a> { } impl CompletionFilter<'_> { - pub fn is_relevant(&self, ctx: &CompletionContext) -> Option<()> { + pub fn is_relevant(&self, ctx: &TreesitterContext) -> Option<()> { self.completable_context(ctx)?; self.check_clause(ctx)?; self.check_invocation(ctx)?; @@ -25,7 +25,7 @@ impl CompletionFilter<'_> { Some(()) } - fn completable_context(&self, ctx: &CompletionContext) -> Option<()> { + fn completable_context(&self, ctx: &TreesitterContext) -> Option<()> { if ctx.wrapping_node_kind.is_none() && ctx.wrapping_clause_type.is_none() { return None; } @@ -70,7 +70,7 @@ impl CompletionFilter<'_> { Some(()) } - fn check_clause(&self, ctx: &CompletionContext) -> Option<()> { + fn check_clause(&self, ctx: &TreesitterContext) -> Option<()> { ctx.wrapping_clause_type .as_ref() .map(|clause| { @@ -208,7 +208,7 @@ impl CompletionFilter<'_> { .and_then(|is_ok| if is_ok { Some(()) } else { None }) } - fn check_invocation(&self, ctx: &CompletionContext) -> Option<()> { + fn check_invocation(&self, ctx: &TreesitterContext) -> Option<()> { if !ctx.is_invocation { return Some(()); } @@ -221,7 +221,7 @@ impl CompletionFilter<'_> { Some(()) } - fn check_mentioned_schema_or_alias(&self, ctx: &CompletionContext) -> Option<()> { + fn check_mentioned_schema_or_alias(&self, ctx: &TreesitterContext) -> Option<()> { if ctx.schema_or_alias_name.is_none() { return Some(()); } diff --git a/crates/pgt_completions/src/relevance/scoring.rs b/crates/pgt_completions/src/relevance/scoring.rs index 11c78d884..4bbf325f4 100644 --- a/crates/pgt_completions/src/relevance/scoring.rs +++ b/crates/pgt_completions/src/relevance/scoring.rs @@ -1,6 +1,6 @@ use fuzzy_matcher::{FuzzyMatcher, skim::SkimMatcherV2}; -use pgt_treesitter::context::{CompletionContext, WrappingClause, WrappingNode}; +use pgt_treesitter::context::{TreesitterContext, WrappingClause, WrappingNode}; use crate::sanitization; @@ -26,7 +26,7 @@ impl CompletionScore<'_> { self.score } - pub fn calc_score(&mut self, ctx: &CompletionContext) { + pub fn calc_score(&mut self, ctx: &TreesitterContext) { self.check_is_user_defined(); self.check_matches_schema(ctx); self.check_matches_query_input(ctx); @@ -37,7 +37,7 @@ impl CompletionScore<'_> { self.check_columns_in_stmt(ctx); } - fn check_matches_query_input(&mut self, ctx: &CompletionContext) { + fn check_matches_query_input(&mut self, ctx: &TreesitterContext) { let content = match ctx.get_node_under_cursor_content() { Some(c) if !sanitization::is_sanitized_token(c.as_str()) => c.replace('"', ""), _ => return, @@ -71,7 +71,7 @@ impl CompletionScore<'_> { } } - fn check_matching_clause_type(&mut self, ctx: &CompletionContext) { + fn check_matching_clause_type(&mut self, ctx: &TreesitterContext) { let clause_type = match ctx.wrapping_clause_type.as_ref() { None => return, Some(ct) => ct, @@ -137,7 +137,7 @@ impl CompletionScore<'_> { } } - fn check_matching_wrapping_node(&mut self, ctx: &CompletionContext) { + fn check_matching_wrapping_node(&mut self, ctx: &TreesitterContext) { let wrapping_node = match ctx.wrapping_node_kind.as_ref() { None => return, Some(wn) => wn, @@ -174,7 +174,7 @@ impl CompletionScore<'_> { } } - fn check_is_invocation(&mut self, ctx: &CompletionContext) { + fn check_is_invocation(&mut self, ctx: &TreesitterContext) { self.score += match self.data { CompletionRelevanceData::Function(_) if ctx.is_invocation => 30, CompletionRelevanceData::Function(_) if !ctx.is_invocation => -10, @@ -183,7 +183,7 @@ impl CompletionScore<'_> { }; } - fn check_matches_schema(&mut self, ctx: &CompletionContext) { + fn check_matches_schema(&mut self, ctx: &TreesitterContext) { let schema_name = match ctx.schema_or_alias_name.as_ref() { None => return, Some(n) => n, @@ -232,7 +232,7 @@ impl CompletionScore<'_> { } } - fn check_relations_in_stmt(&mut self, ctx: &CompletionContext) { + fn check_relations_in_stmt(&mut self, ctx: &TreesitterContext) { match self.data { CompletionRelevanceData::Table(_) | CompletionRelevanceData::Function(_) => return, _ => {} @@ -316,7 +316,7 @@ impl CompletionScore<'_> { } } - fn check_columns_in_stmt(&mut self, ctx: &CompletionContext) { + fn check_columns_in_stmt(&mut self, ctx: &TreesitterContext) { if let CompletionRelevanceData::Column(column) = self.data { /* * Columns can be mentioned in one of two ways: diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index 4f6b1488d..0f6c2161a 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -151,7 +151,7 @@ pub struct TreeSitterContextParams<'a> { } #[derive(Debug)] -pub struct CompletionContext<'a> { +pub struct TreesitterContext<'a> { pub node_under_cursor: Option>, pub tree: &'a tree_sitter::Tree, @@ -191,7 +191,7 @@ pub struct CompletionContext<'a> { pub mentioned_columns: HashMap>, HashSet>, } -impl<'a> CompletionContext<'a> { +impl<'a> TreesitterContext<'a> { pub fn new(params: TreeSitterContextParams<'a>) -> Self { let mut ctx = Self { tree: params.tree, @@ -792,7 +792,7 @@ impl<'a> CompletionContext<'a> { #[cfg(test)] mod tests { - use crate::context::{CompletionContext, TreeSitterContextParams, WrappingClause}; + use crate::context::{TreeSitterContextParams, TreesitterContext, WrappingClause}; use pgt_test_utils::QueryWithCursorPosition; @@ -887,7 +887,7 @@ mod tests { schema: &pgt_schema_cache::SchemaCache::default(), }; - let ctx = CompletionContext::new(params); + let ctx = TreesitterContext::new(params); assert_eq!(ctx.wrapping_clause_type, Some(expected_clause)); } @@ -937,7 +937,7 @@ mod tests { schema: &pgt_schema_cache::SchemaCache::default(), }; - let ctx = CompletionContext::new(params); + let ctx = TreesitterContext::new(params); assert_eq!( ctx.schema_or_alias_name, @@ -998,7 +998,7 @@ mod tests { schema: &pgt_schema_cache::SchemaCache::default(), }; - let ctx = CompletionContext::new(params); + let ctx = TreesitterContext::new(params); assert_eq!(ctx.is_invocation, is_invocation); } @@ -1029,7 +1029,7 @@ mod tests { schema: &pgt_schema_cache::SchemaCache::default(), }; - let ctx = CompletionContext::new(params); + let ctx = TreesitterContext::new(params); let node = ctx.node_under_cursor.as_ref().unwrap(); @@ -1065,7 +1065,7 @@ mod tests { schema: &pgt_schema_cache::SchemaCache::default(), }; - let ctx = CompletionContext::new(params); + let ctx = TreesitterContext::new(params); let node = ctx.node_under_cursor.as_ref().unwrap(); @@ -1092,7 +1092,7 @@ mod tests { schema: &pgt_schema_cache::SchemaCache::default(), }; - let ctx = CompletionContext::new(params); + let ctx = TreesitterContext::new(params); let node = ctx.node_under_cursor.as_ref().unwrap(); @@ -1122,7 +1122,7 @@ mod tests { schema: &pgt_schema_cache::SchemaCache::default(), }; - let ctx = CompletionContext::new(params); + let ctx = TreesitterContext::new(params); let node = ctx.node_under_cursor.as_ref().unwrap(); From 986eb296d6468db05f1967b01922253ef409b174 Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 19 Jul 2025 16:41:44 +0200 Subject: [PATCH 12/20] remove schema caceh --- crates/pgt_completions/src/complete.rs | 13 ++++++------- crates/pgt_completions/src/providers/columns.rs | 9 +++++++-- crates/pgt_completions/src/providers/functions.rs | 10 +++++++--- crates/pgt_completions/src/providers/policies.rs | 9 +++++++-- crates/pgt_completions/src/providers/roles.rs | 9 +++++++-- crates/pgt_completions/src/providers/schemas.rs | 9 +++++++-- crates/pgt_completions/src/providers/tables.rs | 9 +++++++-- crates/pgt_treesitter/src/context/mod.rs | 11 ----------- 8 files changed, 48 insertions(+), 31 deletions(-) diff --git a/crates/pgt_completions/src/complete.rs b/crates/pgt_completions/src/complete.rs index cab90fb52..e18589af0 100644 --- a/crates/pgt_completions/src/complete.rs +++ b/crates/pgt_completions/src/complete.rs @@ -31,19 +31,18 @@ pub fn complete(params: CompletionParams) -> Vec { let ctx = TreesitterContext::new(TreeSitterContextParams { position: sanitized_params.position, - schema: sanitized_params.schema, text: &sanitized_params.text, tree: &sanitized_params.tree, }); let mut builder = CompletionBuilder::new(&ctx); - complete_tables(&ctx, &mut builder); - complete_functions(&ctx, &mut builder); - complete_columns(&ctx, &mut builder); - complete_schemas(&ctx, &mut builder); - complete_policies(&ctx, &mut builder); - complete_roles(&ctx, &mut builder); + complete_tables(&ctx, sanitized_params.schema, &mut builder); + complete_functions(&ctx, sanitized_params.schema, &mut builder); + complete_columns(&ctx, sanitized_params.schema, &mut builder); + complete_schemas(&ctx, sanitized_params.schema, &mut builder); + complete_policies(&ctx, sanitized_params.schema, &mut builder); + complete_roles(&ctx, sanitized_params.schema, &mut builder); builder.finish() } diff --git a/crates/pgt_completions/src/providers/columns.rs b/crates/pgt_completions/src/providers/columns.rs index 31d0f1c85..ba3b24813 100644 --- a/crates/pgt_completions/src/providers/columns.rs +++ b/crates/pgt_completions/src/providers/columns.rs @@ -1,3 +1,4 @@ +use pgt_schema_cache::SchemaCache; use pgt_treesitter::{TreesitterContext, WrappingClause}; use crate::{ @@ -8,8 +9,12 @@ use crate::{ use super::helper::{find_matching_alias_for_table, get_completion_text_with_schema_or_alias}; -pub fn complete_columns<'a>(ctx: &TreesitterContext<'a>, builder: &mut CompletionBuilder<'a>) { - let available_columns = &ctx.schema_cache.columns; +pub fn complete_columns<'a>( + ctx: &TreesitterContext<'a>, + schema_cache: &'a SchemaCache, + builder: &mut CompletionBuilder<'a>, +) { + let available_columns = &schema_cache.columns; for col in available_columns { let relevance = CompletionRelevanceData::Column(col); diff --git a/crates/pgt_completions/src/providers/functions.rs b/crates/pgt_completions/src/providers/functions.rs index 00240ead2..b2ac2fae8 100644 --- a/crates/pgt_completions/src/providers/functions.rs +++ b/crates/pgt_completions/src/providers/functions.rs @@ -1,4 +1,4 @@ -use pgt_schema_cache::Function; +use pgt_schema_cache::{Function, SchemaCache}; use pgt_treesitter::TreesitterContext; use crate::{ @@ -10,8 +10,12 @@ use crate::{ use super::helper::get_completion_text_with_schema_or_alias; -pub fn complete_functions<'a>(ctx: &'a TreesitterContext, builder: &mut CompletionBuilder<'a>) { - let available_functions = &ctx.schema_cache.functions; +pub fn complete_functions<'a>( + ctx: &'a TreesitterContext, + schema_cache: &'a SchemaCache, + builder: &mut CompletionBuilder<'a>, +) { + let available_functions = &schema_cache.functions; for func in available_functions { let relevance = CompletionRelevanceData::Function(func); diff --git a/crates/pgt_completions/src/providers/policies.rs b/crates/pgt_completions/src/providers/policies.rs index 6abdec787..a5ffdb43e 100644 --- a/crates/pgt_completions/src/providers/policies.rs +++ b/crates/pgt_completions/src/providers/policies.rs @@ -1,3 +1,4 @@ +use pgt_schema_cache::SchemaCache; use pgt_text_size::{TextRange, TextSize}; use pgt_treesitter::TreesitterContext; @@ -9,8 +10,12 @@ use crate::{ use super::helper::get_range_to_replace; -pub fn complete_policies<'a>(ctx: &TreesitterContext<'a>, builder: &mut CompletionBuilder<'a>) { - let available_policies = &ctx.schema_cache.policies; +pub fn complete_policies<'a>( + ctx: &TreesitterContext<'a>, + schema_cache: &'a SchemaCache, + builder: &mut CompletionBuilder<'a>, +) { + let available_policies = &schema_cache.policies; let surrounded_by_quotes = ctx .get_node_under_cursor_content() diff --git a/crates/pgt_completions/src/providers/roles.rs b/crates/pgt_completions/src/providers/roles.rs index 0e0770dc6..b7664349c 100644 --- a/crates/pgt_completions/src/providers/roles.rs +++ b/crates/pgt_completions/src/providers/roles.rs @@ -3,10 +3,15 @@ use crate::{ builder::{CompletionBuilder, PossibleCompletionItem}, relevance::{CompletionRelevanceData, filtering::CompletionFilter, scoring::CompletionScore}, }; +use pgt_schema_cache::SchemaCache; use pgt_treesitter::TreesitterContext; -pub fn complete_roles<'a>(ctx: &TreesitterContext<'a>, builder: &mut CompletionBuilder<'a>) { - let available_roles = &ctx.schema_cache.roles; +pub fn complete_roles<'a>( + _ctx: &TreesitterContext<'a>, + schema_cache: &'a SchemaCache, + builder: &mut CompletionBuilder<'a>, +) { + let available_roles = &schema_cache.roles; for role in available_roles { let relevance = CompletionRelevanceData::Role(role); diff --git a/crates/pgt_completions/src/providers/schemas.rs b/crates/pgt_completions/src/providers/schemas.rs index 39e7d08e2..43c523875 100644 --- a/crates/pgt_completions/src/providers/schemas.rs +++ b/crates/pgt_completions/src/providers/schemas.rs @@ -2,10 +2,15 @@ use crate::{ builder::{CompletionBuilder, PossibleCompletionItem}, relevance::{CompletionRelevanceData, filtering::CompletionFilter, scoring::CompletionScore}, }; +use pgt_schema_cache::SchemaCache; use pgt_treesitter::TreesitterContext; -pub fn complete_schemas<'a>(ctx: &'a TreesitterContext, builder: &mut CompletionBuilder<'a>) { - let available_schemas = &ctx.schema_cache.schemas; +pub fn complete_schemas<'a>( + _ctx: &'a TreesitterContext, + schema_cache: &'a SchemaCache, + builder: &mut CompletionBuilder<'a>, +) { + let available_schemas = &schema_cache.schemas; for schema in available_schemas { let relevance = CompletionRelevanceData::Schema(schema); diff --git a/crates/pgt_completions/src/providers/tables.rs b/crates/pgt_completions/src/providers/tables.rs index d32f1791e..f78b697c9 100644 --- a/crates/pgt_completions/src/providers/tables.rs +++ b/crates/pgt_completions/src/providers/tables.rs @@ -1,3 +1,4 @@ +use pgt_schema_cache::SchemaCache; use pgt_treesitter::TreesitterContext; use crate::{ @@ -8,8 +9,12 @@ use crate::{ use super::helper::get_completion_text_with_schema_or_alias; -pub fn complete_tables<'a>(ctx: &'a TreesitterContext, builder: &mut CompletionBuilder<'a>) { - let available_tables = &ctx.schema_cache.tables; +pub fn complete_tables<'a>( + ctx: &'a TreesitterContext, + schema_cache: &'a SchemaCache, + builder: &mut CompletionBuilder<'a>, +) { + let available_tables = &schema_cache.tables; for table in available_tables { let relevance = CompletionRelevanceData::Table(table); diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index 0f6c2161a..9cfaadea1 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -8,7 +8,6 @@ mod policy_parser; mod revoke_parser; use crate::queries::{self, QueryResult, TreeSitterQueriesExecutor}; -use pgt_schema_cache::SchemaCache; use pgt_text_size::{TextRange, TextSize}; use crate::context::{ @@ -146,7 +145,6 @@ impl TryFrom for WrappingNode { pub struct TreeSitterContextParams<'a> { pub position: TextSize, pub text: &'a str, - pub schema: &'a pgt_schema_cache::SchemaCache, pub tree: &'a tree_sitter::Tree, } @@ -156,7 +154,6 @@ pub struct TreesitterContext<'a> { pub tree: &'a tree_sitter::Tree, pub text: &'a str, - pub schema_cache: &'a SchemaCache, pub position: usize, /// If the cursor is on a node that uses dot notation @@ -196,7 +193,6 @@ impl<'a> TreesitterContext<'a> { let mut ctx = Self { tree: params.tree, text: params.text, - schema_cache: params.schema, position: usize::from(params.position), node_under_cursor: None, schema_or_alias_name: None, @@ -884,7 +880,6 @@ mod tests { position: (position as u32).into(), text: &text, tree: &tree, - schema: &pgt_schema_cache::SchemaCache::default(), }; let ctx = TreesitterContext::new(params); @@ -934,7 +929,6 @@ mod tests { position: (position as u32).into(), text: &text, tree: &tree, - schema: &pgt_schema_cache::SchemaCache::default(), }; let ctx = TreesitterContext::new(params); @@ -995,7 +989,6 @@ mod tests { position: (position as u32).into(), text: text.as_str(), tree: &tree, - schema: &pgt_schema_cache::SchemaCache::default(), }; let ctx = TreesitterContext::new(params); @@ -1026,7 +1019,6 @@ mod tests { position: (position as u32).into(), text: &text, tree: &tree, - schema: &pgt_schema_cache::SchemaCache::default(), }; let ctx = TreesitterContext::new(params); @@ -1062,7 +1054,6 @@ mod tests { position: (position as u32).into(), text: &text, tree: &tree, - schema: &pgt_schema_cache::SchemaCache::default(), }; let ctx = TreesitterContext::new(params); @@ -1089,7 +1080,6 @@ mod tests { position: (position as u32).into(), text: &text, tree: &tree, - schema: &pgt_schema_cache::SchemaCache::default(), }; let ctx = TreesitterContext::new(params); @@ -1119,7 +1109,6 @@ mod tests { position: (position as u32).into(), text: &text, tree: &tree, - schema: &pgt_schema_cache::SchemaCache::default(), }; let ctx = TreesitterContext::new(params); From 97f42fcfa34a02edec57506465a9dcfa0d749ed4 Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 19 Jul 2025 17:10:09 +0200 Subject: [PATCH 13/20] 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 14/20] 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 15/20] 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 } From dbc3c7d9b5e4b59f79d74fafa00818666bb6225e Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 14 Aug 2025 13:12:55 +0200 Subject: [PATCH 16/20] fixed conflicts --- Cargo.lock | 2 +- crates/pgt_hover/Cargo.toml | 2 +- crates/pgt_hover/src/lib.rs | 2 +- crates/pgt_treesitter/Cargo.toml | 1 - crates/pgt_workspace/src/workspace/server/document.rs | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b69c57020..fe4998751 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2928,7 +2928,7 @@ name = "pgt_hover" version = "0.0.0" dependencies = [ "humansize", - "pgt_query_ext", + "pgt_query", "pgt_schema_cache", "pgt_test_utils", "pgt_text_size", diff --git a/crates/pgt_hover/Cargo.toml b/crates/pgt_hover/Cargo.toml index eab3f70cf..5b3a97140 100644 --- a/crates/pgt_hover/Cargo.toml +++ b/crates/pgt_hover/Cargo.toml @@ -13,7 +13,7 @@ version = "0.0.0" [dependencies] humansize = { version = "2.1.3" } -pgt_query_ext.workspace = true +pgt_query.workspace = true pgt_schema_cache.workspace = true pgt_text_size.workspace = true pgt_treesitter.workspace = true diff --git a/crates/pgt_hover/src/lib.rs b/crates/pgt_hover/src/lib.rs index 8a85d9804..c2f5c2136 100644 --- a/crates/pgt_hover/src/lib.rs +++ b/crates/pgt_hover/src/lib.rs @@ -10,7 +10,7 @@ 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 ast: Option<&'a pgt_query::NodeEnum>, pub ts_tree: &'a tree_sitter::Tree, } diff --git a/crates/pgt_treesitter/Cargo.toml b/crates/pgt_treesitter/Cargo.toml index b192eebab..d6107f941 100644 --- a/crates/pgt_treesitter/Cargo.toml +++ b/crates/pgt_treesitter/Cargo.toml @@ -11,7 +11,6 @@ repository.workspace = true version = "0.0.0" [lib] -proc-macro = true doctest = false [dependencies] diff --git a/crates/pgt_workspace/src/workspace/server/document.rs b/crates/pgt_workspace/src/workspace/server/document.rs index 5c72e974c..9a496dc1a 100644 --- a/crates/pgt_workspace/src/workspace/server/document.rs +++ b/crates/pgt_workspace/src/workspace/server/document.rs @@ -279,7 +279,7 @@ impl<'a> StatementMapper<'a> for WithCSTandASTMapper { StatementId, TextRange, Arc, - Option, + Option, ); fn map(&self, parser: &'a Document, id: StatementId, range: TextRange) -> Self::Output { From 606bd40ceaa5f17c574e87a6f685769843c8597f Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 14 Aug 2025 13:15:24 +0200 Subject: [PATCH 17/20] ok? --- packages/@postgrestools/backend-jsonrpc/src/workspace.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/@postgrestools/backend-jsonrpc/src/workspace.ts b/packages/@postgrestools/backend-jsonrpc/src/workspace.ts index 971f07ec9..60680b8ad 100644 --- a/packages/@postgrestools/backend-jsonrpc/src/workspace.ts +++ b/packages/@postgrestools/backend-jsonrpc/src/workspace.ts @@ -79,6 +79,7 @@ export type Category = | "flags/invalid" | "project" | "typecheck" + | "plpgsql_check" | "internalError/panic" | "syntax" | "dummy" From 07e56b1cc46293aca505b0c67bd522cd9c629342 Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 14 Aug 2025 13:17:30 +0200 Subject: [PATCH 18/20] remove libpg again --- libpg_query | 1 - 1 file changed, 1 deletion(-) delete mode 160000 libpg_query diff --git a/libpg_query b/libpg_query deleted file mode 160000 index 1c1a32ed2..000000000 --- a/libpg_query +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1c1a32ed2f4c7799830d50bf4cb159222aafec48 From 01e74763b8bb21b7b3e268aef862763720772815 Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 14 Aug 2025 13:19:26 +0200 Subject: [PATCH 19/20] no underscore --- crates/pgt_workspace/src/workspace/server.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pgt_workspace/src/workspace/server.rs b/crates/pgt_workspace/src/workspace/server.rs index 3e1abf06d..b7ff8a85f 100644 --- a/crates/pgt_workspace/src/workspace/server.rs +++ b/crates/pgt_workspace/src/workspace/server.rs @@ -681,14 +681,14 @@ impl Workspace for WorkspaceServer { tracing::debug!("No statement found."); Ok(CompletionsResult::default()) } - Some((_id, range, 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: _id.content().to_string(), + text: id.content().to_string(), }); Ok(CompletionsResult { items }) From ff58d26b2b5c24f54a5cc84351ec7dcd7b471aa8 Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 14 Aug 2025 13:21:22 +0200 Subject: [PATCH 20/20] thats not a warning --- crates/pgt_lsp/src/handlers/hover.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pgt_lsp/src/handlers/hover.rs b/crates/pgt_lsp/src/handlers/hover.rs index 4dd44ca6c..59d70ca74 100644 --- a/crates/pgt_lsp/src/handlers/hover.rs +++ b/crates/pgt_lsp/src/handlers/hover.rs @@ -16,7 +16,7 @@ pub(crate) fn on_hover( position: get_cursor_position(session, &url, position)?, }) { Ok(result) => { - tracing::warn!("Got a result. {:#?}", result); + tracing::debug!("Found hover items: {:#?}", result); Ok(lsp_types::HoverContents::Array( result