Skip to content

Commit a1e34a9

Browse files
feat: basic hover (#463)
1 parent 6b0a9f8 commit a1e34a9

File tree

21 files changed

+439
-31
lines changed

21 files changed

+439
-31
lines changed

Cargo.lock

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ pgt_diagnostics_categories = { path = "./crates/pgt_diagnostics_categories", ver
7272
pgt_diagnostics_macros = { path = "./crates/pgt_diagnostics_macros", version = "0.0.0" }
7373
pgt_flags = { path = "./crates/pgt_flags", version = "0.0.0" }
7474
pgt_fs = { path = "./crates/pgt_fs", version = "0.0.0" }
75+
pgt_hover = { path = "./crates/pgt_hover", version = "0.0.0" }
7576
pgt_lexer = { path = "./crates/pgt_lexer", version = "0.0.0" }
7677
pgt_lexer_codegen = { path = "./crates/pgt_lexer_codegen", version = "0.0.0" }
7778
pgt_lsp = { path = "./crates/pgt_lsp", version = "0.0.0" }

crates/pgt_hover/Cargo.toml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[package]
2+
authors.workspace = true
3+
categories.workspace = true
4+
description = "<DESCRIPTION>"
5+
edition.workspace = true
6+
homepage.workspace = true
7+
keywords.workspace = true
8+
license.workspace = true
9+
name = "pgt_hover"
10+
repository.workspace = true
11+
version = "0.0.0"
12+
13+
14+
[dependencies]
15+
humansize = { version = "2.1.3" }
16+
pgt_query.workspace = true
17+
pgt_schema_cache.workspace = true
18+
pgt_text_size.workspace = true
19+
pgt_treesitter.workspace = true
20+
schemars = { workspace = true, optional = true }
21+
serde = { workspace = true, features = ["derive"] }
22+
serde_json = { workspace = true }
23+
sqlx.workspace = true
24+
tokio = { version = "1.41.1", features = ["full"] }
25+
tracing = { workspace = true }
26+
tree-sitter.workspace = true
27+
tree_sitter_sql.workspace = true
28+
29+
[dev-dependencies]
30+
pgt_test_utils.workspace = true
31+
32+
[lib]
33+
doctest = false
34+
35+
[features]
36+
schema = ["dep:schemars"]

crates/pgt_hover/src/hovered_node.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use pgt_text_size::TextSize;
2+
use pgt_treesitter::TreeSitterContextParams;
3+
4+
#[derive(Debug)]
5+
pub(crate) enum NodeIdentification {
6+
Name(String),
7+
SchemaAndName((String, String)),
8+
#[allow(unused)]
9+
SchemaAndTableAndName((String, String, String)),
10+
}
11+
12+
#[allow(unused)]
13+
#[derive(Debug)]
14+
pub(crate) enum HoveredNode {
15+
Schema(NodeIdentification),
16+
Table(NodeIdentification),
17+
Function(NodeIdentification),
18+
Column(NodeIdentification),
19+
Policy(NodeIdentification),
20+
Trigger(NodeIdentification),
21+
Role(NodeIdentification),
22+
}
23+
24+
impl HoveredNode {
25+
pub(crate) fn get(position: TextSize, text: &str, tree: &tree_sitter::Tree) -> Option<Self> {
26+
let ctx = pgt_treesitter::context::TreesitterContext::new(TreeSitterContextParams {
27+
position,
28+
text,
29+
tree,
30+
});
31+
32+
let node_content = ctx.get_node_under_cursor_content()?;
33+
34+
let under_node = ctx.node_under_cursor.as_ref()?;
35+
36+
match under_node.kind() {
37+
"identifier" if ctx.parent_matches_one_of_kind(&["object_reference", "relation"]) => {
38+
if let Some(schema) = ctx.schema_or_alias_name {
39+
Some(HoveredNode::Table(NodeIdentification::SchemaAndName((
40+
schema,
41+
node_content,
42+
))))
43+
} else {
44+
Some(HoveredNode::Table(NodeIdentification::Name(node_content)))
45+
}
46+
}
47+
_ => None,
48+
}
49+
}
50+
}

crates/pgt_hover/src/lib.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use pgt_schema_cache::SchemaCache;
2+
use pgt_text_size::TextSize;
3+
4+
use crate::{hovered_node::HoveredNode, to_markdown::ToHoverMarkdown};
5+
6+
mod hovered_node;
7+
mod to_markdown;
8+
9+
pub struct OnHoverParams<'a> {
10+
pub position: TextSize,
11+
pub schema_cache: &'a SchemaCache,
12+
pub stmt_sql: &'a str,
13+
pub ast: Option<&'a pgt_query::NodeEnum>,
14+
pub ts_tree: &'a tree_sitter::Tree,
15+
}
16+
17+
pub fn on_hover(params: OnHoverParams) -> Vec<String> {
18+
if let Some(hovered_node) = HoveredNode::get(params.position, params.stmt_sql, params.ts_tree) {
19+
match hovered_node {
20+
HoveredNode::Table(node_identification) => {
21+
let table = match node_identification {
22+
hovered_node::NodeIdentification::Name(n) => {
23+
params.schema_cache.find_table(n.as_str(), None)
24+
}
25+
hovered_node::NodeIdentification::SchemaAndName((s, n)) => {
26+
params.schema_cache.find_table(n.as_str(), Some(s.as_str()))
27+
}
28+
hovered_node::NodeIdentification::SchemaAndTableAndName(_) => None,
29+
};
30+
31+
table
32+
.map(|t| {
33+
let mut markdown = String::new();
34+
match t.to_hover_markdown(&mut markdown) {
35+
Ok(_) => vec![markdown],
36+
Err(_) => vec![],
37+
}
38+
})
39+
.unwrap_or(vec![])
40+
}
41+
42+
_ => todo!(),
43+
}
44+
} else {
45+
Default::default()
46+
}
47+
}

crates/pgt_hover/src/to_markdown.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use std::fmt::Write;
2+
3+
use humansize::DECIMAL;
4+
5+
pub(crate) trait ToHoverMarkdown {
6+
fn to_hover_markdown<W: Write>(&self, writer: &mut W) -> Result<(), std::fmt::Error>;
7+
}
8+
9+
impl ToHoverMarkdown for pgt_schema_cache::Table {
10+
fn to_hover_markdown<W: Write>(&self, writer: &mut W) -> Result<(), std::fmt::Error> {
11+
HeadlineWriter::for_table(writer, self)?;
12+
BodyWriter::for_table(writer, self)?;
13+
FooterWriter::for_table(writer, self)?;
14+
15+
Ok(())
16+
}
17+
}
18+
19+
struct HeadlineWriter;
20+
21+
impl HeadlineWriter {
22+
fn for_table<W: Write>(
23+
writer: &mut W,
24+
table: &pgt_schema_cache::Table,
25+
) -> Result<(), std::fmt::Error> {
26+
let table_kind = match table.table_kind {
27+
pgt_schema_cache::TableKind::View => " (View)",
28+
pgt_schema_cache::TableKind::MaterializedView => " (M.View)",
29+
pgt_schema_cache::TableKind::Partitioned => " (Partitioned)",
30+
pgt_schema_cache::TableKind::Ordinary => "",
31+
};
32+
33+
let locked_txt = if table.rls_enabled {
34+
" - 🔒 RLS enabled"
35+
} else {
36+
" - 🔓 RLS disabled"
37+
};
38+
39+
write!(
40+
writer,
41+
"### {}.{}{}{}",
42+
table.schema, table.name, table_kind, locked_txt
43+
)?;
44+
45+
markdown_newline(writer)?;
46+
47+
Ok(())
48+
}
49+
}
50+
51+
struct BodyWriter;
52+
53+
impl BodyWriter {
54+
fn for_table<W: Write>(
55+
writer: &mut W,
56+
table: &pgt_schema_cache::Table,
57+
) -> Result<(), std::fmt::Error> {
58+
if let Some(c) = table.comment.as_ref() {
59+
write!(writer, "{}", c)?;
60+
markdown_newline(writer)?;
61+
}
62+
63+
Ok(())
64+
}
65+
}
66+
67+
struct FooterWriter;
68+
69+
impl FooterWriter {
70+
fn for_table<W: Write>(
71+
writer: &mut W,
72+
table: &pgt_schema_cache::Table,
73+
) -> Result<(), std::fmt::Error> {
74+
write!(
75+
writer,
76+
"~{} rows, ~{} dead rows, {}",
77+
table.live_rows_estimate,
78+
table.dead_rows_estimate,
79+
humansize::format_size(table.bytes as u64, DECIMAL)
80+
)?;
81+
82+
Ok(())
83+
}
84+
}
85+
86+
fn markdown_newline<W: Write>(writer: &mut W) -> Result<(), std::fmt::Error> {
87+
write!(writer, " ")?;
88+
writeln!(writer)?;
89+
Ok(())
90+
}

crates/pgt_lsp/src/capabilities.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ use crate::handlers::code_actions::command_id;
33
use pgt_workspace::features::code_actions::CommandActionCategory;
44
use strum::IntoEnumIterator;
55
use tower_lsp::lsp_types::{
6-
ClientCapabilities, CompletionOptions, ExecuteCommandOptions, PositionEncodingKind,
7-
SaveOptions, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind,
8-
TextDocumentSyncOptions, TextDocumentSyncSaveOptions, WorkDoneProgressOptions,
6+
ClientCapabilities, CompletionOptions, ExecuteCommandOptions, HoverProviderCapability,
7+
PositionEncodingKind, SaveOptions, ServerCapabilities, TextDocumentSyncCapability,
8+
TextDocumentSyncKind, TextDocumentSyncOptions, TextDocumentSyncSaveOptions,
9+
WorkDoneProgressOptions,
910
};
1011

1112
/// The capabilities to send from server as part of [`InitializeResult`]
@@ -62,6 +63,7 @@ pub(crate) fn server_capabilities(capabilities: &ClientCapabilities) -> ServerCa
6263
true,
6364
)),
6465
rename_provider: None,
66+
hover_provider: Some(HoverProviderCapability::Simple(true)),
6567
..Default::default()
6668
}
6769
}

crates/pgt_lsp/src/handlers.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub(crate) mod code_actions;
22
pub(crate) mod completions;
3+
pub(crate) mod hover;
34
pub(crate) mod text_document;

crates/pgt_lsp/src/handlers/hover.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use pgt_workspace::{WorkspaceError, features::on_hover::OnHoverParams};
2+
use tower_lsp::lsp_types::{self, MarkedString, MarkupContent};
3+
4+
use crate::{adapters::get_cursor_position, diagnostics::LspError, session::Session};
5+
6+
pub(crate) fn on_hover(
7+
session: &Session,
8+
params: lsp_types::HoverParams,
9+
) -> Result<lsp_types::HoverContents, LspError> {
10+
let url = params.text_document_position_params.text_document.uri;
11+
let position = params.text_document_position_params.position;
12+
let path = session.file_path(&url)?;
13+
14+
match session.workspace.on_hover(OnHoverParams {
15+
path,
16+
position: get_cursor_position(session, &url, position)?,
17+
}) {
18+
Ok(result) => {
19+
tracing::debug!("Found hover items: {:#?}", result);
20+
21+
Ok(lsp_types::HoverContents::Array(
22+
result
23+
.into_iter()
24+
.map(MarkedString::from_markdown)
25+
.collect(),
26+
))
27+
}
28+
29+
Err(e) => match e {
30+
WorkspaceError::DatabaseConnectionError(_) => {
31+
Ok(lsp_types::HoverContents::Markup(MarkupContent {
32+
kind: lsp_types::MarkupKind::PlainText,
33+
value: "Cannot connect to database.".into(),
34+
}))
35+
}
36+
_ => {
37+
tracing::error!("Received an error: {:#?}", e);
38+
Err(e.into())
39+
}
40+
},
41+
}
42+
}

crates/pgt_lsp/src/server.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,17 @@ impl LanguageServer for LSPServer {
265265
}
266266
}
267267

268+
#[tracing::instrument(level = "trace", skip_all)]
269+
async fn hover(&self, params: HoverParams) -> LspResult<Option<Hover>> {
270+
match handlers::hover::on_hover(&self.session, params) {
271+
Ok(result) => LspResult::Ok(Some(Hover {
272+
contents: result,
273+
range: None,
274+
})),
275+
Err(e) => LspResult::Err(into_lsp_error(e)),
276+
}
277+
}
278+
268279
#[tracing::instrument(level = "trace", skip_all)]
269280
async fn completion(&self, params: CompletionParams) -> LspResult<Option<CompletionResponse>> {
270281
match handlers::completions::get_completions(&self.session, params) {

0 commit comments

Comments
 (0)