Skip to content

feat: basic hover #463

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: refactor/extract-ts-context
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
36 changes: 36 additions & 0 deletions crates/pgt_hover/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[package]
authors.workspace = true
categories.workspace = true
description = "<DESCRIPTION>"
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
name = "pgt_hover"
repository.workspace = true
version = "0.0.0"


[dependencies]
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

[lib]
doctest = false

[features]
schema = ["dep:schemars"]
50 changes: 50 additions & 0 deletions crates/pgt_hover/src/hovered_node.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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),
Function(NodeIdentification),
Column(NodeIdentification),
Policy(NodeIdentification),
Trigger(NodeIdentification),
Role(NodeIdentification),
}

impl HoveredNode {
pub(crate) fn get(position: TextSize, text: &str, tree: &tree_sitter::Tree) -> Option<Self> {
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.as_ref()?;

match under_node.kind() {
"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,
node_content,
))))
} else {
Some(HoveredNode::Table(NodeIdentification::Name(node_content)))
}
}
_ => None,
}
}
}
47 changes: 47 additions & 0 deletions crates/pgt_hover/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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<String> {
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()
}
}
90 changes: 90 additions & 0 deletions crates/pgt_hover/src/to_markdown.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use std::fmt::Write;

use humansize::DECIMAL;

pub(crate) trait ToHoverMarkdown {
fn to_hover_markdown<W: Write>(&self, writer: &mut W) -> Result<(), std::fmt::Error>;
}

impl ToHoverMarkdown for pgt_schema_cache::Table {
fn to_hover_markdown<W: Write>(&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<W: Write>(
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
)?;

markdown_newline(writer)?;

Ok(())
}
}

struct BodyWriter;

impl BodyWriter {
fn for_table<W: Write>(
writer: &mut W,
table: &pgt_schema_cache::Table,
) -> Result<(), std::fmt::Error> {
if let Some(c) = table.comment.as_ref() {
write!(writer, "{}", c)?;
markdown_newline(writer)?;
}

Ok(())
}
}

struct FooterWriter;

impl FooterWriter {
fn for_table<W: Write>(
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(())
}
}

fn markdown_newline<W: Write>(writer: &mut W) -> Result<(), std::fmt::Error> {
write!(writer, " ")?;
writeln!(writer)?;
Ok(())
}
8 changes: 5 additions & 3 deletions crates/pgt_lsp/src/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`]
Expand Down Expand Up @@ -62,6 +63,7 @@ pub(crate) fn server_capabilities(capabilities: &ClientCapabilities) -> ServerCa
true,
)),
rename_provider: None,
hover_provider: Some(HoverProviderCapability::Simple(true)),
..Default::default()
}
}
1 change: 1 addition & 0 deletions crates/pgt_lsp/src/handlers.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub(crate) mod code_actions;
pub(crate) mod completions;
pub(crate) mod hover;
pub(crate) mod text_document;
42 changes: 42 additions & 0 deletions crates/pgt_lsp/src/handlers/hover.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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<lsp_types::HoverContents, LspError> {
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) => {
tracing::warn!("Got a result. {:#?}", result);

Ok(lsp_types::HoverContents::Array(
result
.into_iter()
.map(MarkedString::from_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())
}
},
}
}
11 changes: 11 additions & 0 deletions crates/pgt_lsp/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,17 @@ impl LanguageServer for LSPServer {
}
}

#[tracing::instrument(level = "trace", skip_all)]
async fn hover(&self, params: HoverParams) -> LspResult<Option<Hover>> {
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)),
}
}

#[tracing::instrument(level = "trace", skip_all)]
async fn completion(&self, params: CompletionParams) -> LspResult<Option<CompletionResponse>> {
match handlers::completions::get_completions(&self.session, params) {
Expand Down
6 changes: 3 additions & 3 deletions crates/pgt_schema_cache/src/schema_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -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()
}
}
Expand Down
Loading