Skip to main content

squawk_ide/
document_symbols.rs

1use rowan::TextRange;
2use squawk_syntax::ast::{self, AstNode};
3
4use crate::binder::{self, extract_string_literal};
5use crate::resolve::{
6    resolve_aggregate_info, resolve_function_info, resolve_procedure_info, resolve_sequence_info,
7    resolve_table_info, resolve_type_info, resolve_view_info,
8};
9
10#[derive(Debug)]
11pub enum DocumentSymbolKind {
12    Schema,
13    Table,
14    View,
15    MaterializedView,
16    Function,
17    Aggregate,
18    Procedure,
19    EventTrigger,
20    Role,
21    Policy,
22    Type,
23    Enum,
24    Index,
25    Domain,
26    Sequence,
27    Trigger,
28    Tablespace,
29    Database,
30    Server,
31    Extension,
32    Column,
33    Variant,
34    Cursor,
35    PreparedStatement,
36    Channel,
37}
38
39#[derive(Debug)]
40pub struct DocumentSymbol {
41    pub name: String,
42    pub detail: Option<String>,
43    pub kind: DocumentSymbolKind,
44    /// Range used for determining when cursor is inside the symbol for showing
45    /// in the UI
46    pub full_range: TextRange,
47    /// Range selected when symbol is selected
48    pub focus_range: TextRange,
49    pub children: Vec<DocumentSymbol>,
50}
51
52pub fn document_symbols(file: &ast::SourceFile) -> Vec<DocumentSymbol> {
53    let binder = binder::bind(file);
54    let mut symbols = vec![];
55
56    for stmt in file.stmts() {
57        match stmt {
58            ast::Stmt::CreateSchema(create_schema) => {
59                if let Some(symbol) = create_schema_symbol(create_schema) {
60                    symbols.push(symbol);
61                }
62            }
63            ast::Stmt::CreateTable(create_table) => {
64                if let Some(symbol) = create_table_symbol(&binder, create_table) {
65                    symbols.push(symbol);
66                }
67            }
68            ast::Stmt::CreateTableAs(create_table_as) => {
69                if let Some(symbol) = create_table_as_symbol(&binder, create_table_as) {
70                    symbols.push(symbol);
71                }
72            }
73            ast::Stmt::CreateForeignTable(create_foreign_table) => {
74                if let Some(symbol) = create_table_symbol(&binder, create_foreign_table) {
75                    symbols.push(symbol);
76                }
77            }
78            ast::Stmt::CreateFunction(create_function) => {
79                if let Some(symbol) = create_function_symbol(&binder, create_function) {
80                    symbols.push(symbol);
81                }
82            }
83            ast::Stmt::CreateAggregate(create_aggregate) => {
84                if let Some(symbol) = create_aggregate_symbol(&binder, create_aggregate) {
85                    symbols.push(symbol);
86                }
87            }
88            ast::Stmt::CreateProcedure(create_procedure) => {
89                if let Some(symbol) = create_procedure_symbol(&binder, create_procedure) {
90                    symbols.push(symbol);
91                }
92            }
93            ast::Stmt::CreateIndex(create_index) => {
94                if let Some(symbol) = create_index_symbol(create_index) {
95                    symbols.push(symbol);
96                }
97            }
98            ast::Stmt::CreateDomain(create_domain) => {
99                if let Some(symbol) = create_domain_symbol(&binder, create_domain) {
100                    symbols.push(symbol);
101                }
102            }
103            ast::Stmt::CreateSequence(create_sequence) => {
104                if let Some(symbol) = create_sequence_symbol(&binder, create_sequence) {
105                    symbols.push(symbol);
106                }
107            }
108            ast::Stmt::CreateTrigger(create_trigger) => {
109                if let Some(symbol) = create_trigger_symbol(create_trigger) {
110                    symbols.push(symbol);
111                }
112            }
113            ast::Stmt::CreateEventTrigger(create_event_trigger) => {
114                if let Some(symbol) = create_event_trigger_symbol(create_event_trigger) {
115                    symbols.push(symbol);
116                }
117            }
118            ast::Stmt::CreateTablespace(create_tablespace) => {
119                if let Some(symbol) = create_tablespace_symbol(create_tablespace) {
120                    symbols.push(symbol);
121                }
122            }
123            ast::Stmt::CreateDatabase(create_database) => {
124                if let Some(symbol) = create_database_symbol(create_database) {
125                    symbols.push(symbol);
126                }
127            }
128            ast::Stmt::CreateServer(create_server) => {
129                if let Some(symbol) = create_server_symbol(create_server) {
130                    symbols.push(symbol);
131                }
132            }
133            ast::Stmt::CreateExtension(create_extension) => {
134                if let Some(symbol) = create_extension_symbol(create_extension) {
135                    symbols.push(symbol);
136                }
137            }
138            ast::Stmt::CreateRole(create_role) => {
139                if let Some(symbol) = create_role_symbol(create_role) {
140                    symbols.push(symbol);
141                }
142            }
143            ast::Stmt::CreatePolicy(create_policy) => {
144                if let Some(symbol) = create_policy_symbol(create_policy) {
145                    symbols.push(symbol);
146                }
147            }
148            ast::Stmt::CreateType(create_type) => {
149                if let Some(symbol) = create_type_symbol(&binder, create_type) {
150                    symbols.push(symbol);
151                }
152            }
153            ast::Stmt::CreateView(create_view) => {
154                if let Some(symbol) = create_view_symbol(&binder, create_view) {
155                    symbols.push(symbol);
156                }
157            }
158            ast::Stmt::CreateMaterializedView(create_view) => {
159                if let Some(symbol) = create_materialized_view_symbol(&binder, create_view) {
160                    symbols.push(symbol);
161                }
162            }
163            ast::Stmt::Declare(declare) => {
164                if let Some(symbol) = create_declare_cursor_symbol(declare) {
165                    symbols.push(symbol);
166                }
167            }
168            ast::Stmt::Prepare(prepare) => {
169                if let Some(symbol) = create_prepare_symbol(prepare) {
170                    symbols.push(symbol);
171                }
172            }
173            ast::Stmt::Select(select) => {
174                symbols.extend(cte_table_symbols(select));
175            }
176            ast::Stmt::SelectInto(select_into) => {
177                symbols.extend(cte_table_symbols(select_into));
178            }
179            ast::Stmt::Insert(insert) => {
180                symbols.extend(cte_table_symbols(insert));
181            }
182            ast::Stmt::Update(update) => {
183                symbols.extend(cte_table_symbols(update));
184            }
185            ast::Stmt::Delete(delete) => {
186                symbols.extend(cte_table_symbols(delete));
187            }
188            ast::Stmt::Listen(listen) => {
189                if let Some(symbol) = create_listen_symbol(listen) {
190                    symbols.push(symbol);
191                }
192            }
193            ast::Stmt::Notify(notify) => {
194                if let Some(symbol) = create_notify_symbol(notify) {
195                    symbols.push(symbol);
196                }
197            }
198            ast::Stmt::Unlisten(unlisten) => {
199                if let Some(symbol) = create_unlisten_symbol(unlisten) {
200                    symbols.push(symbol);
201                }
202            }
203
204            _ => {}
205        }
206    }
207
208    symbols
209}
210
211fn cte_table_symbols(stmt: impl ast::HasWithClause) -> Vec<DocumentSymbol> {
212    let Some(with_clause) = stmt.with_clause() else {
213        return vec![];
214    };
215
216    with_clause
217        .with_tables()
218        .filter_map(create_cte_table_symbol)
219        .collect()
220}
221
222fn create_cte_table_symbol(with_table: ast::WithTable) -> Option<DocumentSymbol> {
223    let name_node = with_table.name()?;
224    let name = name_node.syntax().text().to_string();
225
226    let full_range = with_table.syntax().text_range();
227    let focus_range = name_node.syntax().text_range();
228
229    symbols_from_column_list(
230        with_table.column_list(),
231        name,
232        full_range,
233        focus_range,
234        DocumentSymbolKind::Table,
235    )
236}
237
238fn create_schema_symbol(create_schema: ast::CreateSchema) -> Option<DocumentSymbol> {
239    let (name, focus_range) = if let Some(name_node) = create_schema.name() {
240        (
241            name_node.syntax().text().to_string(),
242            name_node.syntax().text_range(),
243        )
244    } else if let Some(name) = create_schema.role().and_then(|r| r.name()) {
245        (name.syntax().text().to_string(), name.syntax().text_range())
246    } else {
247        return None;
248    };
249
250    let full_range = create_schema.syntax().text_range();
251
252    Some(DocumentSymbol {
253        name,
254        detail: None,
255        kind: DocumentSymbolKind::Schema,
256        full_range,
257        focus_range,
258        children: vec![],
259    })
260}
261
262fn create_table_symbol(
263    binder: &binder::Binder,
264    create_table: impl ast::HasCreateTable,
265) -> Option<DocumentSymbol> {
266    let path = create_table.path()?;
267    let segment = path.segment()?;
268    let name_node = segment.name()?;
269
270    let (schema, table_name) = resolve_table_info(binder, &path)?;
271    let name = format!("{}.{}", schema.0, table_name);
272
273    let full_range = create_table.syntax().text_range();
274    let focus_range = name_node.syntax().text_range();
275
276    let mut children = vec![];
277    if let Some(table_arg_list) = create_table.table_arg_list() {
278        for arg in table_arg_list.args() {
279            if let ast::TableArg::Column(column) = arg
280                && let Some(column_symbol) = create_column_symbol(column)
281            {
282                children.push(column_symbol);
283            }
284        }
285    }
286
287    Some(DocumentSymbol {
288        name,
289        detail: None,
290        kind: DocumentSymbolKind::Table,
291        full_range,
292        focus_range,
293        children,
294    })
295}
296
297fn create_table_as_symbol(
298    binder: &binder::Binder,
299    create_table_as: ast::CreateTableAs,
300) -> Option<DocumentSymbol> {
301    let path = create_table_as.path()?;
302    let segment = path.segment()?;
303    let name_node = if let Some(name) = segment.name() {
304        name.syntax().clone()
305    } else {
306        return None;
307    };
308
309    let (schema, table_name) = resolve_table_info(binder, &path)?;
310    let name = format!("{}.{}", schema.0, table_name);
311
312    let full_range = create_table_as.syntax().text_range();
313    let focus_range = name_node.text_range();
314
315    Some(DocumentSymbol {
316        name,
317        detail: None,
318        kind: DocumentSymbolKind::Table,
319        full_range,
320        focus_range,
321        // TODO: infer the column names, we need the same for views without
322        // explicit column lists
323        children: vec![],
324    })
325}
326
327fn create_view_symbol(
328    binder: &binder::Binder,
329    create_view: ast::CreateView,
330) -> Option<DocumentSymbol> {
331    let path = create_view.path()?;
332    let segment = path.segment()?;
333    let name_node = segment.name()?;
334
335    let (schema, view_name) = resolve_view_info(binder, &path)?;
336    let name = format!("{}.{}", schema.0, view_name);
337
338    let full_range = create_view.syntax().text_range();
339    let focus_range = name_node.syntax().text_range();
340
341    symbols_from_column_list(
342        create_view.column_list(),
343        name,
344        full_range,
345        focus_range,
346        DocumentSymbolKind::View,
347    )
348}
349
350fn symbols_from_column_list(
351    column_list: Option<ast::ColumnList>,
352    name: String,
353    full_range: TextRange,
354    focus_range: TextRange,
355    kind: DocumentSymbolKind,
356) -> Option<DocumentSymbol> {
357    let mut children = vec![];
358    if let Some(column_list) = column_list {
359        for column in column_list.columns() {
360            if let Some(column_symbol) = create_column_symbol(column) {
361                children.push(column_symbol);
362            }
363        }
364    }
365
366    Some(DocumentSymbol {
367        name,
368        detail: None,
369        kind,
370        full_range,
371        focus_range,
372        children,
373    })
374}
375
376// TODO: combine with create_view_symbol
377fn create_materialized_view_symbol(
378    binder: &binder::Binder,
379    create_view: ast::CreateMaterializedView,
380) -> Option<DocumentSymbol> {
381    let path = create_view.path()?;
382    let segment = path.segment()?;
383    let name_node = segment.name()?;
384
385    let (schema, view_name) = resolve_view_info(binder, &path)?;
386    let name = format!("{}.{}", schema.0, view_name);
387
388    let full_range = create_view.syntax().text_range();
389    let focus_range = name_node.syntax().text_range();
390
391    symbols_from_column_list(
392        create_view.column_list(),
393        name,
394        full_range,
395        focus_range,
396        DocumentSymbolKind::MaterializedView,
397    )
398}
399
400fn create_function_symbol(
401    binder: &binder::Binder,
402    create_function: ast::CreateFunction,
403) -> Option<DocumentSymbol> {
404    let path = create_function.path()?;
405    let segment = path.segment()?;
406    let name_node = segment.name()?;
407
408    let (schema, function_name) = resolve_function_info(binder, &path)?;
409    let name = format!("{}.{}", schema.0, function_name);
410
411    let full_range = create_function.syntax().text_range();
412    let focus_range = name_node.syntax().text_range();
413
414    Some(DocumentSymbol {
415        name,
416        detail: None,
417        kind: DocumentSymbolKind::Function,
418        full_range,
419        focus_range,
420        children: vec![],
421    })
422}
423
424fn create_aggregate_symbol(
425    binder: &binder::Binder,
426    create_aggregate: ast::CreateAggregate,
427) -> Option<DocumentSymbol> {
428    let path = create_aggregate.path()?;
429    let segment = path.segment()?;
430    let name_node = segment.name()?;
431
432    let (schema, aggregate_name) = resolve_aggregate_info(binder, &path)?;
433    let name = format!("{}.{}", schema.0, aggregate_name);
434
435    let full_range = create_aggregate.syntax().text_range();
436    let focus_range = name_node.syntax().text_range();
437
438    Some(DocumentSymbol {
439        name,
440        detail: None,
441        kind: DocumentSymbolKind::Aggregate,
442        full_range,
443        focus_range,
444        children: vec![],
445    })
446}
447
448fn create_procedure_symbol(
449    binder: &binder::Binder,
450    create_procedure: ast::CreateProcedure,
451) -> Option<DocumentSymbol> {
452    let path = create_procedure.path()?;
453    let segment = path.segment()?;
454    let name_node = segment.name()?;
455
456    let (schema, procedure_name) = resolve_procedure_info(binder, &path)?;
457    let name = format!("{}.{}", schema.0, procedure_name);
458
459    let full_range = create_procedure.syntax().text_range();
460    let focus_range = name_node.syntax().text_range();
461
462    Some(DocumentSymbol {
463        name,
464        detail: None,
465        kind: DocumentSymbolKind::Procedure,
466        full_range,
467        focus_range,
468        children: vec![],
469    })
470}
471
472fn create_index_symbol(create_index: ast::CreateIndex) -> Option<DocumentSymbol> {
473    let name_node = create_index.name()?;
474    let name = name_node.syntax().text().to_string();
475
476    let full_range = create_index.syntax().text_range();
477    let focus_range = name_node.syntax().text_range();
478
479    Some(DocumentSymbol {
480        name,
481        detail: None,
482        kind: DocumentSymbolKind::Index,
483        full_range,
484        focus_range,
485        children: vec![],
486    })
487}
488
489fn create_domain_symbol(
490    binder: &binder::Binder,
491    create_domain: ast::CreateDomain,
492) -> Option<DocumentSymbol> {
493    let path = create_domain.path()?;
494    let segment = path.segment()?;
495    let name_node = segment.name()?;
496
497    let (schema, domain_name) = resolve_type_info(binder, &path)?;
498    let name = format!("{}.{}", schema.0, domain_name);
499
500    let full_range = create_domain.syntax().text_range();
501    let focus_range = name_node.syntax().text_range();
502
503    Some(DocumentSymbol {
504        name,
505        detail: None,
506        kind: DocumentSymbolKind::Domain,
507        full_range,
508        focus_range,
509        children: vec![],
510    })
511}
512
513fn create_sequence_symbol(
514    binder: &binder::Binder,
515    create_sequence: ast::CreateSequence,
516) -> Option<DocumentSymbol> {
517    let path = create_sequence.path()?;
518    let segment = path.segment()?;
519    let name_node = segment.name()?;
520
521    let (schema, sequence_name) = resolve_sequence_info(binder, &path)?;
522    let name = format!("{}.{}", schema.0, sequence_name);
523
524    let full_range = create_sequence.syntax().text_range();
525    let focus_range = name_node.syntax().text_range();
526
527    Some(DocumentSymbol {
528        name,
529        detail: None,
530        kind: DocumentSymbolKind::Sequence,
531        full_range,
532        focus_range,
533        children: vec![],
534    })
535}
536
537fn create_trigger_symbol(create_trigger: ast::CreateTrigger) -> Option<DocumentSymbol> {
538    let name_node = create_trigger.name()?;
539    let name = name_node.syntax().text().to_string();
540
541    let full_range = create_trigger.syntax().text_range();
542    let focus_range = name_node.syntax().text_range();
543
544    Some(DocumentSymbol {
545        name,
546        detail: None,
547        kind: DocumentSymbolKind::Trigger,
548        full_range,
549        focus_range,
550        children: vec![],
551    })
552}
553
554fn create_event_trigger_symbol(
555    create_event_trigger: ast::CreateEventTrigger,
556) -> Option<DocumentSymbol> {
557    let name_node = create_event_trigger.name()?;
558    let name = name_node.syntax().text().to_string();
559
560    let full_range = create_event_trigger.syntax().text_range();
561    let focus_range = name_node.syntax().text_range();
562
563    Some(DocumentSymbol {
564        name,
565        detail: None,
566        kind: DocumentSymbolKind::EventTrigger,
567        full_range,
568        focus_range,
569        children: vec![],
570    })
571}
572
573fn create_tablespace_symbol(create_tablespace: ast::CreateTablespace) -> Option<DocumentSymbol> {
574    let name_node = create_tablespace.name()?;
575    let name = name_node.syntax().text().to_string();
576
577    let full_range = create_tablespace.syntax().text_range();
578    let focus_range = name_node.syntax().text_range();
579
580    Some(DocumentSymbol {
581        name,
582        detail: None,
583        kind: DocumentSymbolKind::Tablespace,
584        full_range,
585        focus_range,
586        children: vec![],
587    })
588}
589
590fn create_database_symbol(create_database: ast::CreateDatabase) -> Option<DocumentSymbol> {
591    let name_node = create_database.name()?;
592    let name = name_node.syntax().text().to_string();
593
594    let full_range = create_database.syntax().text_range();
595    let focus_range = name_node.syntax().text_range();
596
597    Some(DocumentSymbol {
598        name,
599        detail: None,
600        kind: DocumentSymbolKind::Database,
601        full_range,
602        focus_range,
603        children: vec![],
604    })
605}
606
607fn create_server_symbol(create_server: ast::CreateServer) -> Option<DocumentSymbol> {
608    let name_node = create_server.name()?;
609    let name = name_node.syntax().text().to_string();
610
611    let full_range = create_server.syntax().text_range();
612    let focus_range = name_node.syntax().text_range();
613
614    Some(DocumentSymbol {
615        name,
616        detail: None,
617        kind: DocumentSymbolKind::Server,
618        full_range,
619        focus_range,
620        children: vec![],
621    })
622}
623
624fn create_extension_symbol(create_extension: ast::CreateExtension) -> Option<DocumentSymbol> {
625    let name_node = create_extension.name()?;
626    let name = name_node.syntax().text().to_string();
627
628    let full_range = create_extension.syntax().text_range();
629    let focus_range = name_node.syntax().text_range();
630
631    Some(DocumentSymbol {
632        name,
633        detail: None,
634        kind: DocumentSymbolKind::Extension,
635        full_range,
636        focus_range,
637        children: vec![],
638    })
639}
640
641fn create_role_symbol(create_role: ast::CreateRole) -> Option<DocumentSymbol> {
642    let name_node = create_role.name()?;
643    let name = name_node.syntax().text().to_string();
644
645    let full_range = create_role.syntax().text_range();
646    let focus_range = name_node.syntax().text_range();
647
648    Some(DocumentSymbol {
649        name,
650        detail: None,
651        kind: DocumentSymbolKind::Role,
652        full_range,
653        focus_range,
654        children: vec![],
655    })
656}
657
658fn create_policy_symbol(create_policy: ast::CreatePolicy) -> Option<DocumentSymbol> {
659    let name_node = create_policy.name()?;
660    let name = name_node.syntax().text().to_string();
661
662    let full_range = create_policy.syntax().text_range();
663    let focus_range = name_node.syntax().text_range();
664
665    Some(DocumentSymbol {
666        name,
667        detail: None,
668        kind: DocumentSymbolKind::Policy,
669        full_range,
670        focus_range,
671        children: vec![],
672    })
673}
674
675fn create_type_symbol(
676    binder: &binder::Binder,
677    create_type: ast::CreateType,
678) -> Option<DocumentSymbol> {
679    let path = create_type.path()?;
680    let segment = path.segment()?;
681    let name_node = segment.name()?;
682
683    let (schema, type_name) = resolve_type_info(binder, &path)?;
684    let name = format!("{}.{}", schema.0, type_name);
685
686    let full_range = create_type.syntax().text_range();
687    let focus_range = name_node.syntax().text_range();
688
689    let mut children = vec![];
690    if let Some(variant_list) = create_type.variant_list() {
691        for variant in variant_list.variants() {
692            if let Some(variant_symbol) = create_variant_symbol(variant) {
693                children.push(variant_symbol);
694            }
695        }
696    } else if let Some(column_list) = create_type.column_list() {
697        for column in column_list.columns() {
698            if let Some(column_symbol) = create_column_symbol(column) {
699                children.push(column_symbol);
700            }
701        }
702    }
703
704    Some(DocumentSymbol {
705        name,
706        detail: None,
707        kind: if create_type.variant_list().is_some() {
708            DocumentSymbolKind::Enum
709        } else {
710            DocumentSymbolKind::Type
711        },
712        full_range,
713        focus_range,
714        children,
715    })
716}
717
718fn create_column_symbol(column: ast::Column) -> Option<DocumentSymbol> {
719    let name_node = column.name()?;
720    let name = name_node.syntax().text().to_string();
721
722    let detail = column.ty().map(|t| t.syntax().text().to_string());
723
724    let full_range = column.syntax().text_range();
725    let focus_range = name_node.syntax().text_range();
726
727    Some(DocumentSymbol {
728        name,
729        detail,
730        kind: DocumentSymbolKind::Column,
731        full_range,
732        focus_range,
733        children: vec![],
734    })
735}
736
737fn create_variant_symbol(variant: ast::Variant) -> Option<DocumentSymbol> {
738    let literal = variant.literal()?;
739    let name = extract_string_literal(&literal)?;
740
741    let full_range = variant.syntax().text_range();
742    let focus_range = literal.syntax().text_range();
743
744    Some(DocumentSymbol {
745        name,
746        detail: None,
747        kind: DocumentSymbolKind::Variant,
748        full_range,
749        focus_range,
750        children: vec![],
751    })
752}
753
754fn create_declare_cursor_symbol(declare: ast::Declare) -> Option<DocumentSymbol> {
755    let name_node = declare.name()?;
756    let name = name_node.syntax().text().to_string();
757
758    let full_range = declare.syntax().text_range();
759    let focus_range = name_node.syntax().text_range();
760
761    Some(DocumentSymbol {
762        name,
763        detail: None,
764        kind: DocumentSymbolKind::Cursor,
765        full_range,
766        focus_range,
767        children: vec![],
768    })
769}
770
771fn create_prepare_symbol(prepare: ast::Prepare) -> Option<DocumentSymbol> {
772    let name_node = prepare.name()?;
773    let name = name_node.syntax().text().to_string();
774
775    let full_range = prepare.syntax().text_range();
776    let focus_range = name_node.syntax().text_range();
777
778    Some(DocumentSymbol {
779        name,
780        detail: None,
781        kind: DocumentSymbolKind::PreparedStatement,
782        full_range,
783        focus_range,
784        children: vec![],
785    })
786}
787
788fn create_listen_symbol(listen: ast::Listen) -> Option<DocumentSymbol> {
789    let name_node = listen.name()?;
790    let name = name_node.syntax().text().to_string();
791
792    let full_range = listen.syntax().text_range();
793    let focus_range = name_node.syntax().text_range();
794
795    Some(DocumentSymbol {
796        name,
797        detail: Some("listen".to_string()),
798        kind: DocumentSymbolKind::Channel,
799        full_range,
800        focus_range,
801        children: vec![],
802    })
803}
804
805fn create_notify_symbol(notify: ast::Notify) -> Option<DocumentSymbol> {
806    let name_node = notify.name_ref()?;
807    let name = name_node.syntax().text().to_string();
808
809    let full_range = notify.syntax().text_range();
810    let focus_range = name_node.syntax().text_range();
811
812    Some(DocumentSymbol {
813        name,
814        detail: Some("notify".to_string()),
815        kind: DocumentSymbolKind::Channel,
816        full_range,
817        focus_range,
818        children: vec![],
819    })
820}
821
822fn create_unlisten_symbol(unlisten: ast::Unlisten) -> Option<DocumentSymbol> {
823    let name_node = unlisten.name_ref()?;
824    let name = name_node.syntax().text().to_string();
825
826    let full_range = unlisten.syntax().text_range();
827    let focus_range = name_node.syntax().text_range();
828
829    Some(DocumentSymbol {
830        name,
831        detail: Some("unlisten".to_string()),
832        kind: DocumentSymbolKind::Channel,
833        full_range,
834        focus_range,
835        children: vec![],
836    })
837}
838
839#[cfg(test)]
840mod tests {
841    use super::*;
842    use annotate_snippets::{
843        AnnotationKind, Group, Level, Renderer, Snippet, renderer::DecorStyle,
844    };
845    use insta::assert_snapshot;
846
847    fn symbols_not_found(sql: &str) {
848        let parse = ast::SourceFile::parse(sql);
849        let file = parse.tree();
850        let symbols = document_symbols(&file);
851        if !symbols.is_empty() {
852            panic!("Symbols found. If this is expected, use `symbols` instead.")
853        }
854    }
855
856    fn symbols(sql: &str) -> String {
857        let parse = ast::SourceFile::parse(sql);
858        let file = parse.tree();
859        let symbols = document_symbols(&file);
860        if symbols.is_empty() {
861            panic!("No symbols found. If this is expected, use `symbols_not_found` instead.")
862        }
863
864        let mut output = vec![];
865        for symbol in symbols {
866            let group = symbol_to_group(&symbol, sql);
867            output.push(group);
868        }
869        Renderer::plain()
870            .decor_style(DecorStyle::Unicode)
871            .render(&output)
872            .to_string()
873    }
874
875    fn symbol_to_group<'a>(symbol: &DocumentSymbol, sql: &'a str) -> Group<'a> {
876        let kind = match symbol.kind {
877            DocumentSymbolKind::Schema => "schema",
878            DocumentSymbolKind::Table => "table",
879            DocumentSymbolKind::View => "view",
880            DocumentSymbolKind::MaterializedView => "materialized view",
881            DocumentSymbolKind::Function => "function",
882            DocumentSymbolKind::Aggregate => "aggregate",
883            DocumentSymbolKind::Procedure => "procedure",
884            DocumentSymbolKind::EventTrigger => "event trigger",
885            DocumentSymbolKind::Role => "role",
886            DocumentSymbolKind::Policy => "policy",
887            DocumentSymbolKind::Type => "type",
888            DocumentSymbolKind::Enum => "enum",
889            DocumentSymbolKind::Index => "index",
890            DocumentSymbolKind::Domain => "domain",
891            DocumentSymbolKind::Sequence => "sequence",
892            DocumentSymbolKind::Trigger => "trigger",
893            DocumentSymbolKind::Tablespace => "tablespace",
894            DocumentSymbolKind::Database => "database",
895            DocumentSymbolKind::Server => "server",
896            DocumentSymbolKind::Extension => "extension",
897            DocumentSymbolKind::Column => "column",
898            DocumentSymbolKind::Variant => "variant",
899            DocumentSymbolKind::Cursor => "cursor",
900            DocumentSymbolKind::PreparedStatement => "prepared statement",
901            DocumentSymbolKind::Channel => "channel",
902        };
903
904        let title = if let Some(detail) = &symbol.detail {
905            format!("{}: {} {}", kind, symbol.name, detail)
906        } else {
907            format!("{}: {}", kind, symbol.name)
908        };
909
910        let snippet = Snippet::source(sql)
911            .fold(true)
912            .annotation(
913                AnnotationKind::Primary
914                    .span(symbol.focus_range.into())
915                    .label("focus range"),
916            )
917            .annotation(
918                AnnotationKind::Context
919                    .span(symbol.full_range.into())
920                    .label("full range"),
921            );
922
923        let mut group = Level::INFO.primary_title(title.clone()).element(snippet);
924
925        if !symbol.children.is_empty() {
926            let child_labels: Vec<String> = symbol
927                .children
928                .iter()
929                .map(|child| {
930                    let kind = match child.kind {
931                        DocumentSymbolKind::Column => "column",
932                        DocumentSymbolKind::Variant => "variant",
933                        _ => unreachable!("only columns and variants can be children"),
934                    };
935                    if let Some(detail) = &child.detail {
936                        format!("{}: {} {}", kind, child.name, detail)
937                    } else {
938                        format!("{}: {}", kind, child.name)
939                    }
940                })
941                .collect();
942
943            let mut children_snippet = Snippet::source(sql).fold(true);
944
945            for (i, child) in symbol.children.iter().enumerate() {
946                children_snippet = children_snippet
947                    .annotation(
948                        AnnotationKind::Context
949                            .span(child.full_range.into())
950                            .label(format!("full range for `{}`", child_labels[i].clone())),
951                    )
952                    .annotation(
953                        AnnotationKind::Primary
954                            .span(child.focus_range.into())
955                            .label("focus range"),
956                    );
957            }
958
959            group = group.element(children_snippet);
960        }
961
962        group
963    }
964
965    #[test]
966    fn create_table() {
967        assert_snapshot!(symbols("
968create table users (
969  id int,
970  email citext
971);"), @r"
972        info: table: public.users
973          ╭▸ 
974        2 │   create table users (
975          │   │            ━━━━━ focus range
976          │ ┌─┘
977          │ │
978        3 │ │   id int,
979        4 │ │   email citext
980        5 │ │ );
981          │ └─┘ full range
982983984        3 │     id int,
985          │     ┯━────
986          │     │
987          │     full range for `column: id int`
988          │     focus range
989        4 │     email citext
990          │     ┯━━━━───────
991          │     │
992          │     full range for `column: email citext`
993          ╰╴    focus range
994        ");
995    }
996
997    #[test]
998    fn create_table_as() {
999        assert_snapshot!(symbols("
1000create table t as select 1 a;
1001"), @r"
1002        info: table: public.t
1003          ╭▸ 
1004        2 │ create table t as select 1 a;
1005          │ ┬────────────┯──────────────
1006          │ │            │
1007          │ │            focus range
1008          ╰╴full range
1009        ");
1010    }
1011
1012    #[test]
1013    fn create_schema() {
1014        assert_snapshot!(symbols("
1015create schema foo;
1016"), @r"
1017        info: schema: foo
1018          ╭▸ 
1019        2 │ create schema foo;
1020          │ ┬─────────────┯━━
1021          │ │             │
1022          │ │             focus range
1023          ╰╴full range
1024        ");
1025    }
1026
1027    #[test]
1028    fn create_schema_authorization() {
1029        assert_snapshot!(symbols("
1030create schema authorization foo;
1031"), @r"
1032        info: schema: foo
1033          ╭▸ 
1034        2 │ create schema authorization foo;
1035          │ ┬───────────────────────────┯━━
1036          │ │                           │
1037          │ │                           focus range
1038          ╰╴full range
1039        ");
1040    }
1041
1042    #[test]
1043    fn listen_notify_unlisten() {
1044        assert_snapshot!(symbols("
1045listen updates;
1046notify updates;
1047unlisten updates;
1048unlisten *;
1049"), @r"
1050        info: channel: updates listen
1051          ╭▸ 
1052        2 │ listen updates;
1053          │ ┬──────┯━━━━━━
1054          │ │      │
1055          │ │      focus range
1056          │ full range
1057          ╰╴
1058        info: channel: updates notify
1059          ╭▸ 
1060        3 │ notify updates;
1061          │ ┬──────┯━━━━━━
1062          │ │      │
1063          │ │      focus range
1064          ╰╴full range
1065        info: channel: updates unlisten
1066          ╭▸ 
1067        4 │ unlisten updates;
1068          │ ┬────────┯━━━━━━
1069          │ │        │
1070          │ │        focus range
1071          ╰╴full range
1072        ");
1073    }
1074
1075    #[test]
1076    fn create_function() {
1077        assert_snapshot!(
1078            symbols("create function hello() returns void as $$ select 1; $$ language sql;"),
1079            @r"
1080        info: function: public.hello
1081          ╭▸ 
1082        1 │ create function hello() returns void as $$ select 1; $$ language sql;
1083          │ ┬───────────────┯━━━━───────────────────────────────────────────────
1084          │ │               │
1085          │ │               focus range
1086          ╰╴full range
1087        "
1088        );
1089    }
1090
1091    #[test]
1092    fn create_materialized_view() {
1093        assert_snapshot!(
1094            symbols("create materialized view reports as select 1;"),
1095            @r"
1096        info: materialized view: public.reports
1097          ╭▸ 
1098        1 │ create materialized view reports as select 1;
1099          │ ┬────────────────────────┯━━━━━━────────────
1100          │ │                        │
1101          │ │                        focus range
1102          ╰╴full range
1103        "
1104        );
1105    }
1106
1107    #[test]
1108    fn create_aggregate() {
1109        assert_snapshot!(
1110            symbols("create aggregate myavg(int) (sfunc = int4_avg_accum, stype = _int8);"),
1111            @r"
1112        info: aggregate: public.myavg
1113          ╭▸ 
1114        1 │ create aggregate myavg(int) (sfunc = int4_avg_accum, stype = _int8);
1115          │ ┬────────────────┯━━━━─────────────────────────────────────────────
1116          │ │                │
1117          │ │                focus range
1118          ╰╴full range
1119        "
1120        );
1121    }
1122
1123    #[test]
1124    fn create_procedure() {
1125        assert_snapshot!(
1126            symbols("create procedure hello() language sql as $$ select 1; $$;"),
1127            @r"
1128        info: procedure: public.hello
1129          ╭▸ 
1130        1 │ create procedure hello() language sql as $$ select 1; $$;
1131          │ ┬────────────────┯━━━━──────────────────────────────────
1132          │ │                │
1133          │ │                focus range
1134          ╰╴full range
1135        "
1136        );
1137    }
1138
1139    #[test]
1140    fn create_index() {
1141        assert_snapshot!(symbols("
1142create index idx_users_email on users (email);
1143"), @r"
1144        info: index: idx_users_email
1145          ╭▸ 
1146        2 │ create index idx_users_email on users (email);
1147          │ ┬────────────┯━━━━━━━━━━━━━━─────────────────
1148          │ │            │
1149          │ │            focus range
1150          ╰╴full range
1151        ");
1152    }
1153
1154    #[test]
1155    fn create_domain() {
1156        assert_snapshot!(
1157            symbols("create domain email_addr as text;"),
1158            @r"
1159        info: domain: public.email_addr
1160          ╭▸ 
1161        1 │ create domain email_addr as text;
1162          │ ┬─────────────┯━━━━━━━━━────────
1163          │ │             │
1164          │ │             focus range
1165          ╰╴full range
1166        "
1167        );
1168    }
1169
1170    #[test]
1171    fn create_sequence() {
1172        assert_snapshot!(
1173            symbols("create sequence user_id_seq;"),
1174            @r"
1175        info: sequence: public.user_id_seq
1176          ╭▸ 
1177        1 │ create sequence user_id_seq;
1178          │ ┬───────────────┯━━━━━━━━━━
1179          │ │               │
1180          │ │               focus range
1181          ╰╴full range
1182        "
1183        );
1184    }
1185
1186    #[test]
1187    fn create_trigger() {
1188        assert_snapshot!(symbols("
1189create trigger update_timestamp
1190  before update on users
1191  execute function update_modified_column();
1192"), @r"
1193        info: trigger: update_timestamp
1194          ╭▸ 
1195        2 │   create trigger update_timestamp
1196          │   │              ━━━━━━━━━━━━━━━━ focus range
1197          │ ┌─┘
1198          │ │
1199        3 │ │   before update on users
1200        4 │ │   execute function update_modified_column();
1201          ╰╴└───────────────────────────────────────────┘ full range
1202        ");
1203    }
1204
1205    #[test]
1206    fn create_event_trigger() {
1207        assert_snapshot!(
1208            symbols("create event trigger et on ddl_command_start execute function f();"),
1209            @r"
1210        info: event trigger: et
1211          ╭▸ 
1212        1 │ create event trigger et on ddl_command_start execute function f();
1213          │ ┬────────────────────┯━──────────────────────────────────────────
1214          │ │                    │
1215          │ │                    focus range
1216          ╰╴full range
1217        "
1218        );
1219    }
1220
1221    #[test]
1222    fn create_tablespace() {
1223        assert_snapshot!(symbols("
1224create tablespace dbspace location '/data/dbs';
1225"), @r"
1226        info: tablespace: dbspace
1227          ╭▸ 
1228        2 │ create tablespace dbspace location '/data/dbs';
1229          │ ┬─────────────────┯━━━━━━─────────────────────
1230          │ │                 │
1231          │ │                 focus range
1232          ╰╴full range
1233        ");
1234    }
1235
1236    #[test]
1237    fn create_database() {
1238        assert_snapshot!(
1239            symbols("create database mydb;"),
1240            @r"
1241        info: database: mydb
1242          ╭▸ 
1243        1 │ create database mydb;
1244          │ ┬───────────────┯━━━
1245          │ │               │
1246          │ │               focus range
1247          ╰╴full range
1248        "
1249        );
1250    }
1251
1252    #[test]
1253    fn create_server() {
1254        assert_snapshot!(symbols("
1255create server myserver foreign data wrapper postgres_fdw;
1256"), @r"
1257        info: server: myserver
1258          ╭▸ 
1259        2 │ create server myserver foreign data wrapper postgres_fdw;
1260          │ ┬─────────────┯━━━━━━━──────────────────────────────────
1261          │ │             │
1262          │ │             focus range
1263          ╰╴full range
1264        ");
1265    }
1266
1267    #[test]
1268    fn create_extension() {
1269        assert_snapshot!(
1270            symbols("create extension pgcrypto;"),
1271            @r"
1272        info: extension: pgcrypto
1273          ╭▸ 
1274        1 │ create extension pgcrypto;
1275          │ ┬────────────────┯━━━━━━━
1276          │ │                │
1277          │ │                focus range
1278          ╰╴full range
1279        "
1280        );
1281    }
1282
1283    #[test]
1284    fn create_role() {
1285        assert_snapshot!(symbols("
1286create role reader;
1287"), @r"
1288        info: role: reader
1289          ╭▸ 
1290        2 │ create role reader;
1291          │ ┬───────────┯━━━━━
1292          │ │           │
1293          │ │           focus range
1294          ╰╴full range
1295        ");
1296    }
1297
1298    #[test]
1299    fn create_policy() {
1300        assert_snapshot!(symbols("
1301create policy allow_read on t;
1302"), @r"
1303        info: policy: allow_read
1304          ╭▸ 
1305        2 │ create policy allow_read on t;
1306          │ ┬─────────────┯━━━━━━━━━─────
1307          │ │             │
1308          │ │             focus range
1309          ╰╴full range
1310        ");
1311    }
1312
1313    #[test]
1314    fn multiple_symbols() {
1315        assert_snapshot!(symbols("
1316create table users (id int);
1317create table posts (id int);
1318create function get_user(user_id int) returns void as $$ select 1; $$ language sql;
1319"), @r"
1320        info: table: public.users
1321          ╭▸ 
1322        2 │ create table users (id int);
1323          │ ┬────────────┯━━━━─────────
1324          │ │            │
1325          │ │            focus range
1326          │ full range
132713281329        2 │ create table users (id int);
1330          │                     ┯━────
1331          │                     │
1332          │                     full range for `column: id int`
1333          │                     focus range
1334          ╰╴
1335        info: table: public.posts
1336          ╭▸ 
1337        3 │ create table posts (id int);
1338          │ ┬────────────┯━━━━─────────
1339          │ │            │
1340          │ │            focus range
1341          │ full range
134213431344        3 │ create table posts (id int);
1345          │                     ┯━────
1346          │                     │
1347          │                     full range for `column: id int`
1348          ╰╴                    focus range
1349        info: function: public.get_user
1350          ╭▸ 
1351        4 │ create function get_user(user_id int) returns void as $$ select 1; $$ language sql;
1352          │ ┬───────────────┯━━━━━━━──────────────────────────────────────────────────────────
1353          │ │               │
1354          │ │               focus range
1355          ╰╴full range
1356        ");
1357    }
1358
1359    #[test]
1360    fn qualified_names() {
1361        assert_snapshot!(symbols("
1362create table public.users (id int);
1363create function my_schema.hello() returns void as $$ select 1; $$ language sql;
1364"), @r"
1365        info: table: public.users
1366          ╭▸ 
1367        2 │ create table public.users (id int);
1368          │ ┬───────────────────┯━━━━─────────
1369          │ │                   │
1370          │ │                   focus range
1371          │ full range
137213731374        2 │ create table public.users (id int);
1375          │                            ┯━────
1376          │                            │
1377          │                            full range for `column: id int`
1378          │                            focus range
1379          ╰╴
1380        info: function: my_schema.hello
1381          ╭▸ 
1382        3 │ create function my_schema.hello() returns void as $$ select 1; $$ language sql;
1383          │ ┬─────────────────────────┯━━━━───────────────────────────────────────────────
1384          │ │                         │
1385          │ │                         focus range
1386          ╰╴full range
1387        ");
1388    }
1389
1390    #[test]
1391    fn create_type() {
1392        assert_snapshot!(
1393            symbols("create type status as enum ('active', 'inactive');"),
1394            @r"
1395        info: enum: public.status
1396          ╭▸ 
1397        1 │ create type status as enum ('active', 'inactive');
1398          │ ┬───────────┯━━━━━───────────────────────────────
1399          │ │           │
1400          │ │           focus range
1401          │ full range
140214031404        1 │ create type status as enum ('active', 'inactive');
1405          │                             ┯━━━━━━━  ┯━━━━━━━━━
1406          │                             │         │
1407          │                             │         full range for `variant: inactive`
1408          │                             │         focus range
1409          │                             full range for `variant: active`
1410          ╰╴                            focus range
1411        "
1412        );
1413    }
1414
1415    #[test]
1416    fn create_type_composite() {
1417        assert_snapshot!(
1418            symbols("create type person as (name text, age int);"),
1419            @r"
1420        info: type: public.person
1421          ╭▸ 
1422        1 │ create type person as (name text, age int);
1423          │ ┬───────────┯━━━━━────────────────────────
1424          │ │           │
1425          │ │           focus range
1426          │ full range
142714281429        1 │ create type person as (name text, age int);
1430          │                        ┯━━━─────  ┯━━────
1431          │                        │          │
1432          │                        │          full range for `column: age int`
1433          │                        │          focus range
1434          │                        full range for `column: name text`
1435          ╰╴                       focus range
1436        "
1437        );
1438    }
1439
1440    #[test]
1441    fn create_type_composite_multiple_columns() {
1442        assert_snapshot!(
1443            symbols("create type address as (street text, city text, zip varchar(10));"),
1444            @r"
1445        info: type: public.address
1446          ╭▸ 
1447        1 │ create type address as (street text, city text, zip varchar(10));
1448          │ ┬───────────┯━━━━━━─────────────────────────────────────────────
1449          │ │           │
1450          │ │           focus range
1451          │ full range
145214531454        1 │ create type address as (street text, city text, zip varchar(10));
1455          │                         ┯━━━━━─────  ┯━━━─────  ┯━━────────────
1456          │                         │            │          │
1457          │                         │            │          full range for `column: zip varchar(10)`
1458          │                         │            │          focus range
1459          │                         │            full range for `column: city text`
1460          │                         │            focus range
1461          │                         full range for `column: street text`
1462          ╰╴                        focus range
1463        "
1464        );
1465    }
1466
1467    #[test]
1468    fn create_type_with_schema() {
1469        assert_snapshot!(
1470            symbols("create type myschema.status as enum ('active', 'inactive');"),
1471            @r"
1472        info: enum: myschema.status
1473          ╭▸ 
1474        1 │ create type myschema.status as enum ('active', 'inactive');
1475          │ ┬────────────────────┯━━━━━───────────────────────────────
1476          │ │                    │
1477          │ │                    focus range
1478          │ full range
147914801481        1 │ create type myschema.status as enum ('active', 'inactive');
1482          │                                      ┯━━━━━━━  ┯━━━━━━━━━
1483          │                                      │         │
1484          │                                      │         full range for `variant: inactive`
1485          │                                      │         focus range
1486          │                                      full range for `variant: active`
1487          ╰╴                                     focus range
1488        "
1489        );
1490    }
1491
1492    #[test]
1493    fn create_type_enum_multiple_variants() {
1494        assert_snapshot!(
1495            symbols("create type priority as enum ('low', 'medium', 'high', 'urgent');"),
1496            @r"
1497        info: enum: public.priority
1498          ╭▸ 
1499        1 │ create type priority as enum ('low', 'medium', 'high', 'urgent');
1500          │ ┬───────────┯━━━━━━━────────────────────────────────────────────
1501          │ │           │
1502          │ │           focus range
1503          │ full range
150415051506        1 │ create type priority as enum ('low', 'medium', 'high', 'urgent');
1507          │                               ┯━━━━  ┯━━━━━━━  ┯━━━━━  ┯━━━━━━━
1508          │                               │      │         │       │
1509          │                               │      │         │       full range for `variant: urgent`
1510          │                               │      │         │       focus range
1511          │                               │      │         full range for `variant: high`
1512          │                               │      │         focus range
1513          │                               │      full range for `variant: medium`
1514          │                               │      focus range
1515          │                               full range for `variant: low`
1516          ╰╴                              focus range
1517        "
1518        );
1519    }
1520
1521    #[test]
1522    fn declare_cursor() {
1523        assert_snapshot!(symbols("
1524declare c scroll cursor for select * from t;
1525"), @r"
1526        info: cursor: c
1527          ╭▸ 
1528        2 │ declare c scroll cursor for select * from t;
1529          │ ┬───────┯──────────────────────────────────
1530          │ │       │
1531          │ │       focus range
1532          ╰╴full range
1533        ");
1534    }
1535
1536    #[test]
1537    fn prepare_statement() {
1538        assert_snapshot!(symbols("
1539prepare stmt as select 1;
1540"), @r"
1541        info: prepared statement: stmt
1542          ╭▸ 
1543        2 │ prepare stmt as select 1;
1544          │ ┬───────┯━━━────────────
1545          │ │       │
1546          │ │       focus range
1547          ╰╴full range
1548        ");
1549    }
1550
1551    #[test]
1552    fn empty_file() {
1553        symbols_not_found("")
1554    }
1555
1556    #[test]
1557    fn non_create_statements() {
1558        symbols_not_found("select * from users;")
1559    }
1560
1561    #[test]
1562    fn cte_table() {
1563        assert_snapshot!(
1564            symbols("
1565with recent_users as (
1566  select id, email as user_email
1567  from users
1568)
1569select * from recent_users;
1570"),
1571            @r"
1572        info: table: recent_users
1573          ╭▸ 
1574        2 │   with recent_users as (
1575          │        │━━━━━━━━━━━
1576          │        │
1577          │ ┌──────focus range
1578          │ │
1579        3 │ │   select id, email as user_email
1580        4 │ │   from users
1581        5 │ │ )
1582          ╰╴└─┘ full range
1583        "
1584        );
1585    }
1586
1587    #[test]
1588    fn cte_table_with_column_list() {
1589        assert_snapshot!(
1590            symbols("
1591with t(a, b, c) as (
1592  select 1, 2, 3
1593)
1594select * from t;
1595"),
1596            @r"
1597        info: table: t
1598          ╭▸ 
1599        2 │   with t(a, b, c) as (
1600          │        ━ focus range
1601          │ ┌──────┘
1602          │ │
1603        3 │ │   select 1, 2, 3
1604        4 │ │ )
1605          │ └─┘ full range
160616071608        2 │   with t(a, b, c) as (
1609          │          ┯  ┯  ┯
1610          │          │  │  │
1611          │          │  │  full range for `column: c`
1612          │          │  │  focus range
1613          │          │  full range for `column: b`
1614          │          │  focus range
1615          │          full range for `column: a`
1616          ╰╴         focus range
1617        "
1618        );
1619    }
1620
1621    #[test]
1622    fn create_foreign_table() {
1623        assert_snapshot!(symbols("
1624create foreign table films (
1625  code char(5),
1626  title varchar(40)
1627) server film_server;
1628"), @r"
1629        info: table: public.films
1630          ╭▸ 
1631        2 │   create foreign table films (
1632          │   │                    ━━━━━ focus range
1633          │ ┌─┘
1634          │ │
1635        3 │ │   code char(5),
1636        4 │ │   title varchar(40)
1637        5 │ │ ) server film_server;
1638          │ └────────────────────┘ full range
163916401641        3 │     code char(5),
1642          │     ┯━━━────────
1643          │     │
1644          │     full range for `column: code char(5)`
1645          │     focus range
1646        4 │     title varchar(40)
1647          │     ┯━━━━────────────
1648          │     │
1649          │     full range for `column: title varchar(40)`
1650          ╰╴    focus range
1651        ");
1652    }
1653}