Skip to content

Commit c6664a8

Browse files
authored
ide: add columns to document symbols (#778)
1 parent 98994e3 commit c6664a8

File tree

2 files changed

+210
-68
lines changed

2 files changed

+210
-68
lines changed

crates/squawk_ide/src/document_symbols.rs

Lines changed: 177 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,24 @@ use squawk_syntax::ast::{self, AstNode};
44
use crate::binder;
55
use crate::resolve::{resolve_function_info, resolve_table_info};
66

7+
#[derive(Debug)]
78
pub enum DocumentSymbolKind {
89
Table,
910
Function,
11+
Column,
1012
}
1113

14+
#[derive(Debug)]
1215
pub struct DocumentSymbol {
1316
pub name: String,
1417
pub detail: Option<String>,
1518
pub kind: DocumentSymbolKind,
16-
pub range: TextRange,
17-
pub selection_range: TextRange,
19+
/// Range used for determining when cursor is inside the symbol for showing
20+
/// in the UI
21+
pub full_range: TextRange,
22+
/// Range selected when symbol is selected
23+
pub focus_range: TextRange,
24+
pub children: Vec<DocumentSymbol>,
1825
}
1926

2027
pub fn document_symbols(file: &ast::SourceFile) -> Vec<DocumentSymbol> {
@@ -51,15 +58,27 @@ fn create_table_symbol(
5158
let (schema, table_name) = resolve_table_info(binder, &path)?;
5259
let name = format!("{}.{}", schema.0, table_name);
5360

54-
let range = create_table.syntax().text_range();
55-
let selection_range = name_node.syntax().text_range();
61+
let full_range = create_table.syntax().text_range();
62+
let focus_range = name_node.syntax().text_range();
63+
64+
let mut children = vec![];
65+
if let Some(table_arg_list) = create_table.table_arg_list() {
66+
for arg in table_arg_list.args() {
67+
if let ast::TableArg::Column(column) = arg
68+
&& let Some(column_symbol) = create_column_symbol(column)
69+
{
70+
children.push(column_symbol);
71+
}
72+
}
73+
}
5674

5775
Some(DocumentSymbol {
5876
name,
5977
detail: None,
6078
kind: DocumentSymbolKind::Table,
61-
range,
62-
selection_range,
79+
full_range,
80+
focus_range,
81+
children,
6382
})
6483
}
6584

@@ -74,22 +93,44 @@ fn create_function_symbol(
7493
let (schema, function_name) = resolve_function_info(binder, &path)?;
7594
let name = format!("{}.{}", schema.0, function_name);
7695

77-
let range = create_function.syntax().text_range();
78-
let selection_range = name_node.syntax().text_range();
96+
let full_range = create_function.syntax().text_range();
97+
let focus_range = name_node.syntax().text_range();
7998

8099
Some(DocumentSymbol {
81100
name,
82101
detail: None,
83102
kind: DocumentSymbolKind::Function,
84-
range,
85-
selection_range,
103+
full_range,
104+
focus_range,
105+
children: vec![],
106+
})
107+
}
108+
109+
fn create_column_symbol(column: ast::Column) -> Option<DocumentSymbol> {
110+
let name_node = column.name()?;
111+
let name = name_node.syntax().text().to_string();
112+
113+
let detail = column.ty().map(|t| t.syntax().text().to_string());
114+
115+
let full_range = column.syntax().text_range();
116+
let focus_range = name_node.syntax().text_range();
117+
118+
Some(DocumentSymbol {
119+
name,
120+
detail,
121+
kind: DocumentSymbolKind::Column,
122+
full_range,
123+
focus_range,
124+
children: vec![],
86125
})
87126
}
88127

89128
#[cfg(test)]
90129
mod tests {
91130
use super::*;
92-
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
131+
use annotate_snippets::{
132+
AnnotationKind, Group, Level, Renderer, Snippet, renderer::DecorStyle,
133+
};
93134
use insta::assert_snapshot;
94135

95136
fn symbols_not_found(sql: &str) {
@@ -109,44 +150,110 @@ mod tests {
109150
panic!("No symbols found. If this is expected, use `symbols_not_found` instead.")
110151
}
111152

112-
let mut groups = vec![];
153+
let mut output = vec![];
113154
for symbol in symbols {
114-
let kind = match symbol.kind {
115-
DocumentSymbolKind::Table => "table",
116-
DocumentSymbolKind::Function => "function",
117-
};
118-
let title = format!("{}: {}", kind, symbol.name);
119-
let group = Level::INFO.primary_title(title).element(
120-
Snippet::source(sql)
121-
.fold(true)
155+
let group = symbol_to_group(&symbol, sql);
156+
output.push(group);
157+
}
158+
Renderer::plain()
159+
.decor_style(DecorStyle::Unicode)
160+
.render(&output)
161+
.to_string()
162+
}
163+
164+
fn symbol_to_group<'a>(symbol: &DocumentSymbol, sql: &'a str) -> Group<'a> {
165+
let kind = match symbol.kind {
166+
DocumentSymbolKind::Table => "table",
167+
DocumentSymbolKind::Function => "function",
168+
DocumentSymbolKind::Column => "column",
169+
};
170+
171+
let title = if let Some(detail) = &symbol.detail {
172+
format!("{}: {} {}", kind, symbol.name, detail)
173+
} else {
174+
format!("{}: {}", kind, symbol.name)
175+
};
176+
177+
let snippet = Snippet::source(sql)
178+
.fold(true)
179+
.annotation(
180+
AnnotationKind::Primary
181+
.span(symbol.focus_range.into())
182+
.label("focus range"),
183+
)
184+
.annotation(
185+
AnnotationKind::Context
186+
.span(symbol.full_range.into())
187+
.label("full range"),
188+
);
189+
190+
let mut group = Level::INFO.primary_title(title.clone()).element(snippet);
191+
192+
if !symbol.children.is_empty() {
193+
let child_labels: Vec<String> = symbol
194+
.children
195+
.iter()
196+
.map(|child| {
197+
let kind = match child.kind {
198+
DocumentSymbolKind::Column => "column",
199+
_ => unreachable!("only columns can be children"),
200+
};
201+
let detail = &child.detail.as_ref().unwrap();
202+
format!("{}: {} {}", kind, child.name, detail)
203+
})
204+
.collect();
205+
206+
let mut children_snippet = Snippet::source(sql).fold(true);
207+
208+
for (i, child) in symbol.children.iter().enumerate() {
209+
children_snippet = children_snippet
122210
.annotation(
123-
AnnotationKind::Primary
124-
.span(symbol.selection_range.into())
125-
.label("name"),
211+
AnnotationKind::Context
212+
.span(child.full_range.into())
213+
.label(format!("full range for `{}`", child_labels[i].clone())),
126214
)
127215
.annotation(
128-
AnnotationKind::Context
129-
.span(symbol.range.into())
130-
.label("select range"),
131-
),
132-
);
133-
groups.push(group);
216+
AnnotationKind::Primary
217+
.span(child.focus_range.into())
218+
.label("focus range"),
219+
);
220+
}
221+
222+
group = group.element(children_snippet);
134223
}
135224

136-
let renderer = Renderer::plain().decor_style(DecorStyle::Unicode);
137-
renderer.render(&groups).to_string()
225+
group
138226
}
139227

140228
#[test]
141229
fn create_table() {
142-
assert_snapshot!(symbols("create table users (id int);"), @r"
230+
assert_snapshot!(symbols("
231+
create table users (
232+
id int,
233+
email citext
234+
);"), @r"
143235
info: table: public.users
144236
╭▸
145-
1 │ create table users (id int);
146-
│ ┬────────────┯━━━━─────────
147-
│ │ │
148-
│ │ name
149-
╰╴select range
237+
2 │ create table users (
238+
│ │ ━━━━━ focus range
239+
│ ┌─┘
240+
│ │
241+
3 │ │ id int,
242+
4 │ │ email citext
243+
5 │ │ );
244+
│ └─┘ full range
245+
246+
247+
3 │ id int,
248+
│ ┯━────
249+
│ │
250+
│ full range for `column: id int`
251+
│ focus range
252+
4 │ email citext
253+
│ ┯━━━━───────
254+
│ │
255+
│ full range for `column: email citext`
256+
╰╴ focus range
150257
");
151258
}
152259

@@ -160,8 +267,8 @@ mod tests {
160267
1 │ create function hello() returns void as $$ select 1; $$ language sql;
161268
│ ┬───────────────┯━━━━───────────────────────────────────────────────
162269
│ │ │
163-
│ │ name
164-
╰╴select range
270+
│ │ focus range
271+
╰╴full range
165272
"
166273
);
167274
}
@@ -178,23 +285,37 @@ create function get_user(user_id int) returns void as $$ select 1; $$ language s
178285
2 │ create table users (id int);
179286
│ ┬────────────┯━━━━─────────
180287
│ │ │
181-
│ │ name
182-
│ select range
288+
│ │ focus range
289+
│ full range
290+
291+
292+
2 │ create table users (id int);
293+
│ ┯━────
294+
│ │
295+
│ full range for `column: id int`
296+
│ focus range
183297
╰╴
184298
info: table: public.posts
185299
╭▸
186300
3 │ create table posts (id int);
187301
│ ┬────────────┯━━━━─────────
188302
│ │ │
189-
│ │ name
190-
╰╴select range
303+
│ │ focus range
304+
│ full range
305+
306+
307+
3 │ create table posts (id int);
308+
│ ┯━────
309+
│ │
310+
│ full range for `column: id int`
311+
╰╴ focus range
191312
info: function: public.get_user
192313
╭▸
193314
4 │ create function get_user(user_id int) returns void as $$ select 1; $$ language sql;
194315
│ ┬───────────────┯━━━━━━━──────────────────────────────────────────────────────────
195316
│ │ │
196-
│ │ name
197-
╰╴select range
317+
│ │ focus range
318+
╰╴full range
198319
");
199320
}
200321

@@ -209,16 +330,23 @@ create function my_schema.hello() returns void as $$ select 1; $$ language sql;
209330
2 │ create table public.users (id int);
210331
│ ┬───────────────────┯━━━━─────────
211332
│ │ │
212-
│ │ name
213-
│ select range
333+
│ │ focus range
334+
│ full range
335+
336+
337+
2 │ create table public.users (id int);
338+
│ ┯━────
339+
│ │
340+
│ full range for `column: id int`
341+
│ focus range
214342
╰╴
215343
info: function: my_schema.hello
216344
╭▸
217345
3 │ create function my_schema.hello() returns void as $$ select 1; $$ language sql;
218346
│ ┬─────────────────────────┯━━━━───────────────────────────────────────────────
219347
│ │ │
220-
│ │ name
221-
╰╴select range
348+
│ │ focus range
349+
╰╴full range
222350
");
223351
}
224352

crates/squawk_server/src/lib.rs

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -316,27 +316,41 @@ fn handle_document_symbol(
316316

317317
let symbols = document_symbols(&file);
318318

319+
fn convert_symbol(
320+
sym: squawk_ide::document_symbols::DocumentSymbol,
321+
line_index: &LineIndex,
322+
) -> DocumentSymbol {
323+
let range = lsp_utils::range(line_index, sym.full_range);
324+
let selection_range = lsp_utils::range(line_index, sym.focus_range);
325+
326+
let children = sym
327+
.children
328+
.into_iter()
329+
.map(|child| convert_symbol(child, line_index))
330+
.collect::<Vec<_>>();
331+
332+
let children = (!children.is_empty()).then_some(children);
333+
334+
DocumentSymbol {
335+
name: sym.name,
336+
detail: sym.detail,
337+
kind: match sym.kind {
338+
DocumentSymbolKind::Table => SymbolKind::STRUCT,
339+
DocumentSymbolKind::Function => SymbolKind::FUNCTION,
340+
DocumentSymbolKind::Column => SymbolKind::FIELD,
341+
},
342+
tags: None,
343+
range,
344+
selection_range,
345+
children,
346+
#[allow(deprecated)]
347+
deprecated: None,
348+
}
349+
}
350+
319351
let lsp_symbols: Vec<DocumentSymbol> = symbols
320352
.into_iter()
321-
.map(|sym| {
322-
let range = lsp_utils::range(&line_index, sym.range);
323-
let selection_range = lsp_utils::range(&line_index, sym.selection_range);
324-
325-
DocumentSymbol {
326-
name: sym.name,
327-
detail: sym.detail,
328-
kind: match sym.kind {
329-
DocumentSymbolKind::Table => SymbolKind::STRUCT,
330-
DocumentSymbolKind::Function => SymbolKind::FUNCTION,
331-
},
332-
tags: None,
333-
range,
334-
selection_range,
335-
children: None,
336-
#[allow(deprecated)]
337-
deprecated: None,
338-
}
339-
})
353+
.map(|sym| convert_symbol(sym, &line_index))
340354
.collect();
341355

342356
let resp = Response {

0 commit comments

Comments
 (0)