@@ -4,17 +4,24 @@ use squawk_syntax::ast::{self, AstNode};
44use crate :: binder;
55use crate :: resolve:: { resolve_function_info, resolve_table_info} ;
66
7+ #[ derive( Debug ) ]
78pub enum DocumentSymbolKind {
89 Table ,
910 Function ,
11+ Column ,
1012}
1113
14+ #[ derive( Debug ) ]
1215pub 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
2027pub 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) ]
90129mod 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
0 commit comments