Skip to content

Commit 138dbcf

Browse files
authored
ide: inlay hints for insert & add goto def for hints (#771)
1 parent 58126e5 commit 138dbcf

File tree

4 files changed

+202
-7
lines changed

4 files changed

+202
-7
lines changed

crates/squawk_ide/src/inlay_hints.rs

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::binder;
22
use crate::binder::Binder;
33
use crate::resolve;
4-
use rowan::TextSize;
4+
use crate::symbols::Name;
5+
use rowan::{TextRange, TextSize};
56
use squawk_syntax::ast::{self, AstNode};
67

78
/// `VSCode` has some theming options based on these types.
@@ -16,15 +17,18 @@ pub struct InlayHint {
1617
pub position: TextSize,
1718
pub label: String,
1819
pub kind: InlayHintKind,
20+
pub target: Option<TextRange>,
1921
}
2022

2123
pub fn inlay_hints(file: &ast::SourceFile) -> Vec<InlayHint> {
2224
let mut hints = vec![];
2325
let binder = binder::bind(file);
2426

2527
for node in file.syntax().descendants() {
26-
if let Some(call_expr) = ast::CallExpr::cast(node) {
28+
if let Some(call_expr) = ast::CallExpr::cast(node.clone()) {
2729
inlay_hint_call_expr(&mut hints, file, &binder, call_expr);
30+
} else if let Some(insert) = ast::Insert::cast(node) {
31+
inlay_hint_insert(&mut hints, file, &binder, insert);
2832
}
2933
}
3034

@@ -59,10 +63,12 @@ fn inlay_hint_call_expr(
5963
for (param, arg) in param_list.params().zip(arg_list.args()) {
6064
if let Some(param_name) = param.name() {
6165
let arg_start = arg.syntax().text_range().start();
66+
let target = Some(param_name.syntax().text_range());
6267
hints.push(InlayHint {
6368
position: arg_start,
6469
label: format!("{}: ", param_name.syntax().text()),
6570
kind: InlayHintKind::Parameter,
71+
target,
6672
});
6773
}
6874
}
@@ -71,6 +77,62 @@ fn inlay_hint_call_expr(
7177
Some(())
7278
}
7379

80+
fn inlay_hint_insert(
81+
hints: &mut Vec<InlayHint>,
82+
file: &ast::SourceFile,
83+
binder: &Binder,
84+
insert: ast::Insert,
85+
) -> Option<()> {
86+
let values = insert.values()?;
87+
let row_list = values.row_list()?;
88+
89+
let columns: Vec<(Name, Option<TextRange>)> = if let Some(column_list) = insert.column_list() {
90+
let table_arg_list = resolve::resolve_insert_table_columns(file, binder, &insert);
91+
92+
column_list
93+
.columns()
94+
.filter_map(|col| {
95+
let col_name = resolve::extract_column_name(&col)?;
96+
let target = table_arg_list
97+
.as_ref()
98+
.and_then(|list| resolve::find_column_in_table(list, &col_name));
99+
Some((col_name, target))
100+
})
101+
.collect()
102+
} else {
103+
let table_arg_list = resolve::resolve_insert_table_columns(file, binder, &insert)?;
104+
105+
table_arg_list
106+
.args()
107+
.filter_map(|arg| {
108+
if let ast::TableArg::Column(column) = arg
109+
&& let Some(name) = column.name()
110+
{
111+
let col_name = Name::new(name.syntax().text().to_string());
112+
let target = Some(name.syntax().text_range());
113+
Some((col_name, target))
114+
} else {
115+
None
116+
}
117+
})
118+
.collect()
119+
};
120+
121+
for row in row_list.rows() {
122+
for ((column_name, target), expr) in columns.iter().zip(row.exprs()) {
123+
let expr_start = expr.syntax().text_range().start();
124+
hints.push(InlayHint {
125+
position: expr_start,
126+
label: format!("{}: ", column_name),
127+
kind: InlayHintKind::Parameter,
128+
target: *target,
129+
});
130+
}
131+
}
132+
133+
Some(())
134+
}
135+
74136
#[cfg(test)]
75137
mod test {
76138
use crate::inlay_hints::inlay_hints;
@@ -216,4 +278,68 @@ select foo(1, 2);
216278
╰╴ ───
217279
");
218280
}
281+
282+
#[test]
283+
fn insert_with_column_list() {
284+
assert_snapshot!(check_inlay_hints("
285+
create table t (column_a int, column_b int, column_c text);
286+
insert into t (column_a, column_c) values (1, 'foo');
287+
"), @r"
288+
inlay hints:
289+
╭▸
290+
3 │ insert into t (column_a, column_c) values (column_a: 1, column_c: 'foo');
291+
╰╴ ────────── ──────────
292+
");
293+
}
294+
295+
#[test]
296+
fn insert_without_column_list() {
297+
assert_snapshot!(check_inlay_hints("
298+
create table t (column_a int, column_b int, column_c text);
299+
insert into t values (1, 2, 'foo');
300+
"), @r"
301+
inlay hints:
302+
╭▸
303+
3 │ insert into t values (column_a: 1, column_b: 2, column_c: 'foo');
304+
╰╴ ────────── ────────── ──────────
305+
");
306+
}
307+
308+
#[test]
309+
fn insert_multiple_rows() {
310+
assert_snapshot!(check_inlay_hints("
311+
create table t (x int, y int);
312+
insert into t values (1, 2), (3, 4);
313+
"), @r"
314+
inlay hints:
315+
╭▸
316+
3 │ insert into t values (x: 1, y: 2), (x: 3, y: 4);
317+
╰╴ ─── ─── ─── ───
318+
");
319+
}
320+
321+
#[test]
322+
fn insert_no_create_table() {
323+
assert_snapshot!(check_inlay_hints("
324+
insert into t (a, b) values (1, 2);
325+
"), @r"
326+
inlay hints:
327+
╭▸
328+
2 │ insert into t (a, b) values (a: 1, b: 2);
329+
╰╴ ─── ───
330+
");
331+
}
332+
333+
#[test]
334+
fn insert_more_values_than_columns() {
335+
assert_snapshot!(check_inlay_hints("
336+
create table t (a int, b int);
337+
insert into t values (1, 2, 3);
338+
"), @r"
339+
inlay hints:
340+
╭▸
341+
3 │ insert into t values (a: 1, b: 2, 3);
342+
╰╴ ─── ───
343+
");
344+
}
219345
}

crates/squawk_ide/src/resolve.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rowan::TextSize;
1+
use rowan::{TextRange, TextSize};
22
use squawk_syntax::{
33
SyntaxNodePtr,
44
ast::{self, AstNode},
@@ -318,6 +318,53 @@ fn extract_schema_name(path: &ast::Path) -> Option<Schema> {
318318
.map(|name_ref| Schema(Name::new(name_ref.syntax().text().to_string())))
319319
}
320320

321+
pub(crate) fn extract_column_name(col: &ast::Column) -> Option<Name> {
322+
let text = if let Some(name_ref) = col.name_ref() {
323+
name_ref.syntax().text().to_string()
324+
} else {
325+
let name = col.name()?;
326+
name.syntax().text().to_string()
327+
};
328+
Some(Name::new(text))
329+
}
330+
331+
pub(crate) fn find_column_in_table(
332+
table_arg_list: &ast::TableArgList,
333+
col_name: &Name,
334+
) -> Option<TextRange> {
335+
table_arg_list.args().find_map(|arg| {
336+
if let ast::TableArg::Column(column) = arg
337+
&& let Some(name) = column.name()
338+
&& Name::new(name.syntax().text().to_string()) == *col_name
339+
{
340+
Some(name.syntax().text_range())
341+
} else {
342+
None
343+
}
344+
})
345+
}
346+
347+
pub(crate) fn resolve_insert_table_columns(
348+
file: &ast::SourceFile,
349+
binder: &Binder,
350+
insert: &ast::Insert,
351+
) -> Option<ast::TableArgList> {
352+
let path = insert.path()?;
353+
let table_name = extract_table_name(&path)?;
354+
let schema = extract_schema_name(&path);
355+
let position = insert.syntax().text_range().start();
356+
357+
let table_ptr = resolve_table(binder, &table_name, &schema, position)?;
358+
let root = file.syntax();
359+
let table_name_node = table_ptr.to_node(root);
360+
361+
let create_table = table_name_node
362+
.ancestors()
363+
.find_map(ast::CreateTable::cast)?;
364+
365+
create_table.table_arg_list()
366+
}
367+
321368
pub(crate) fn resolve_table_info(binder: &Binder, path: &ast::Path) -> Option<(Schema, String)> {
322369
let table_name_str = extract_table_name_from_path(path)?;
323370
let schema = extract_schema_from_path(path);

crates/squawk_ide/src/symbols.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ impl Name {
2929
}
3030
}
3131

32+
impl fmt::Display for Name {
33+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34+
write!(f, "{}", self.0)
35+
}
36+
}
37+
3238
fn normalize_identifier(text: &str) -> SmolStr {
3339
if text.starts_with('"') && text.ends_with('"') && text.len() >= 2 {
3440
text[1..text.len() - 1].into()

crates/squawk_server/src/lib.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ use lsp_types::{
88
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
99
GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams,
1010
HoverProviderCapability, InitializeParams, InlayHint, InlayHintKind, InlayHintLabel,
11-
InlayHintParams, LanguageString, Location, MarkedString, OneOf, PublishDiagnosticsParams,
12-
ReferenceParams, SelectionRangeParams, SelectionRangeProviderCapability, ServerCapabilities,
13-
TextDocumentSyncCapability, TextDocumentSyncKind, Url, WorkDoneProgressOptions, WorkspaceEdit,
11+
InlayHintLabelPart, InlayHintParams, LanguageString, Location, MarkedString, OneOf,
12+
PublishDiagnosticsParams, ReferenceParams, SelectionRangeParams,
13+
SelectionRangeProviderCapability, ServerCapabilities, TextDocumentSyncCapability,
14+
TextDocumentSyncKind, Url, WorkDoneProgressOptions, WorkspaceEdit,
1415
notification::{
1516
DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, Notification as _,
1617
PublishDiagnostics,
@@ -257,9 +258,24 @@ fn handle_inlay_hints(
257258
squawk_ide::inlay_hints::InlayHintKind::Type => InlayHintKind::TYPE,
258259
squawk_ide::inlay_hints::InlayHintKind::Parameter => InlayHintKind::PARAMETER,
259260
};
261+
262+
let label = if let Some(target_range) = hint.target {
263+
InlayHintLabel::LabelParts(vec![InlayHintLabelPart {
264+
value: hint.label,
265+
location: Some(Location {
266+
uri: uri.clone(),
267+
range: lsp_utils::range(&line_index, target_range),
268+
}),
269+
tooltip: None,
270+
command: None,
271+
}])
272+
} else {
273+
InlayHintLabel::String(hint.label)
274+
};
275+
260276
InlayHint {
261277
position,
262-
label: InlayHintLabel::String(hint.label),
278+
label,
263279
kind: Some(kind),
264280
text_edits: None,
265281
tooltip: None,

0 commit comments

Comments
 (0)