1use super::{analyze, span_contains};
4use crate::ast::Declaration;
5use crate::token::{Token, TokenKind};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum CompletionKind {
10 Keyword,
12 Type,
14 FieldAttribute,
16 ModelAttribute,
18 ModelName,
20 EnumName,
22 FieldName,
24}
25
26#[derive(Debug, Clone, PartialEq)]
28pub struct CompletionItem {
29 pub label: String,
31 pub insert_text: Option<String>,
33 pub is_snippet: bool,
35 pub kind: CompletionKind,
37 pub detail: Option<String>,
39}
40
41impl CompletionItem {
42 pub(super) fn new(
43 label: impl Into<String>,
44 kind: CompletionKind,
45 detail: impl Into<Option<String>>,
46 ) -> Self {
47 Self {
48 label: label.into(),
49 insert_text: None,
50 is_snippet: false,
51 kind,
52 detail: detail.into(),
53 }
54 }
55
56 pub(super) fn with_insert(
57 label: impl Into<String>,
58 insert_text: impl Into<String>,
59 kind: CompletionKind,
60 detail: impl Into<Option<String>>,
61 ) -> Self {
62 Self {
63 label: label.into(),
64 insert_text: Some(insert_text.into()),
65 is_snippet: false,
66 kind,
67 detail: detail.into(),
68 }
69 }
70
71 pub(super) fn with_snippet(
72 label: impl Into<String>,
73 snippet: impl Into<String>,
74 kind: CompletionKind,
75 detail: impl Into<Option<String>>,
76 ) -> Self {
77 Self {
78 label: label.into(),
79 insert_text: Some(snippet.into()),
80 is_snippet: true,
81 kind,
82 detail: detail.into(),
83 }
84 }
85}
86
87pub fn completion(source: &str, offset: usize) -> Vec<CompletionItem> {
98 let result = analyze(source);
99 let tokens = &result.tokens;
100
101 let provider: Option<String> = extract_provider_from_tokens(tokens);
103
104 if let Some(attr_name) = inside_attr_args_at(tokens, offset) {
105 let arg_index = attr_arg_index_at(tokens, offset).unwrap_or(0);
106 return attr_argument_completions(&attr_name, provider.as_deref(), arg_index);
107 }
108
109 let attr_ctx = attribute_context_at(tokens, offset);
110 if attr_ctx == AttributeContext::FieldAttr {
111 return field_attribute_completions();
112 }
113 if attr_ctx == AttributeContext::ModelAttr {
114 return model_attribute_completions();
115 }
116
117 if let Some(key) = config_value_context_at(tokens, offset) {
118 let block_kind = config_block_kind_at(tokens, offset);
119 let completions = config_value_completions(&key, block_kind);
120 if !completions.is_empty() {
121 return completions;
122 }
123 }
124
125 let ast = match &result.ast {
126 Some(a) => a,
127 None => {
128 return if is_inside_model_tokens(tokens, offset) {
131 scalar_type_completions(provider.as_deref())
132 } else {
133 top_level_completions()
134 };
135 }
136 };
137
138 let user_models: Vec<String> = ast
140 .declarations
141 .iter()
142 .filter_map(|d| {
143 if let Declaration::Model(m) = d {
144 Some(m.name.value.clone())
145 } else {
146 None
147 }
148 })
149 .collect();
150
151 let user_enums: Vec<String> = ast
152 .declarations
153 .iter()
154 .filter_map(|d| {
155 if let Declaration::Enum(e) = d {
156 Some(e.name.value.clone())
157 } else {
158 None
159 }
160 })
161 .collect();
162
163 let containing_decl = ast
165 .declarations
166 .iter()
167 .find(|d| span_contains(d.span(), offset));
168
169 match containing_decl {
170 None => {
171 if is_inside_model_tokens(tokens, offset) {
175 let mut items = scalar_type_completions(provider.as_deref());
176 for name in &user_models {
177 items.push(CompletionItem::new(
178 name.clone(),
179 CompletionKind::ModelName,
180 Some("Model reference".to_string()),
181 ));
182 }
183 for name in &user_enums {
184 items.push(CompletionItem::new(
185 name.clone(),
186 CompletionKind::EnumName,
187 Some("Enum reference".to_string()),
188 ));
189 }
190 items
191 } else {
192 top_level_completions()
193 }
194 }
195
196 Some(Declaration::Datasource(_)) | Some(Declaration::Generator(_)) => {
197 datasource_generator_completions()
198 }
199
200 Some(Declaration::Enum(_)) => {
201 Vec::new()
204 }
205
206 Some(Declaration::Model(_)) | Some(Declaration::Type(_)) => {
207 let mut items = scalar_type_completions(provider.as_deref());
208 for name in &user_models {
209 items.push(CompletionItem::new(
210 name.clone(),
211 CompletionKind::ModelName,
212 Some("Model reference".to_string()),
213 ));
214 }
215 for name in &user_enums {
216 items.push(CompletionItem::new(
217 name.clone(),
218 CompletionKind::EnumName,
219 Some("Enum reference".to_string()),
220 ));
221 }
222 items
223 }
224 }
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228enum ConfigBlockKind {
229 Datasource,
230 Generator,
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
234enum AttributeContext {
235 FieldAttr,
236 ModelAttr,
237 None,
238}
239
240fn inside_attr_args_at(tokens: &[Token], offset: usize) -> Option<String> {
245 let relevant: Vec<&Token> = tokens
247 .iter()
248 .filter(|t| t.span.end <= offset && !matches!(t.kind, TokenKind::Newline))
249 .collect();
250
251 let mut depth: i32 = 0;
252 for tok in relevant.iter().rev() {
253 match tok.kind {
254 TokenKind::RParen => depth += 1,
255 TokenKind::LParen => {
256 if depth == 0 {
257 let lparen_start = tok.span.start;
259 let before: Vec<&Token> = tokens
260 .iter()
261 .filter(|t| {
262 t.span.end <= lparen_start && !matches!(t.kind, TokenKind::Newline)
263 })
264 .collect();
265 if let Some(name_tok) = before.last() {
266 if let TokenKind::Ident(attr_name) = &name_tok.kind {
267 let attr_name = attr_name.clone();
268 let before_name: Vec<&Token> = tokens
270 .iter()
271 .filter(|t| {
272 t.span.end <= name_tok.span.start
273 && !matches!(t.kind, TokenKind::Newline)
274 })
275 .collect();
276 if let Some(at_tok) = before_name.last() {
277 if matches!(at_tok.kind, TokenKind::At | TokenKind::AtAt) {
278 return Some(attr_name);
279 }
280 }
281 }
282 }
283 return None;
284 }
285 depth -= 1;
286 }
287 _ => {}
288 }
289 }
290 None
291}
292
293fn config_value_context_at(tokens: &[Token], offset: usize) -> Option<String> {
296 let mut eq_pos: Option<usize> = None;
297 let mut key_pos: Option<usize> = None;
298
299 for (i, tok) in tokens.iter().enumerate() {
300 if tok.span.end > offset {
301 break;
302 }
303 if tok.kind == TokenKind::Newline {
304 eq_pos = None;
305 key_pos = None;
306 } else if tok.kind == TokenKind::Equal {
307 eq_pos = Some(i);
308 } else if let TokenKind::Ident(_) = tok.kind {
309 if eq_pos.is_none() {
310 key_pos = Some(i);
311 }
312 }
313 }
314
315 let eq_idx = eq_pos?;
316 let key_idx = key_pos?;
317
318 if eq_idx != key_idx + 1 {
319 return None;
320 }
321
322 if let TokenKind::Ident(key) = &tokens[key_idx].kind {
323 return Some(key.clone());
324 }
325 None
326}
327
328fn attribute_context_at(tokens: &[Token], offset: usize) -> AttributeContext {
330 let last = tokens
332 .iter()
333 .rfind(|t| t.span.end <= offset && !matches!(t.kind, TokenKind::Newline));
334
335 match last {
336 Some(t) if t.kind == TokenKind::AtAt => AttributeContext::ModelAttr,
337 Some(t) if t.kind == TokenKind::At => AttributeContext::FieldAttr,
338 Some(t) if matches!(t.kind, TokenKind::Ident(_)) => {
341 let before = tokens.iter().rfind(|tok| tok.span.end <= t.span.start);
342 match before {
343 Some(b) if b.kind == TokenKind::AtAt => AttributeContext::ModelAttr,
344 Some(b) if b.kind == TokenKind::At => AttributeContext::FieldAttr,
345 _ => AttributeContext::None,
346 }
347 }
348 _ => AttributeContext::None,
349 }
350}
351
352fn is_inside_model_tokens(tokens: &[Token], offset: usize) -> bool {
359 let relevant: Vec<&Token> = tokens.iter().filter(|t| t.span.end <= offset).collect();
361
362 let mut depth: i32 = 0;
363 for tok in relevant.iter().rev() {
364 match tok.kind {
365 TokenKind::RBrace => depth += 1,
366 TokenKind::LBrace => {
367 if depth == 0 {
368 let idx = tokens
371 .iter()
372 .position(|t| std::ptr::eq(t, *tok))
373 .unwrap_or(0);
374 let before: Vec<&Token> = tokens[..idx]
376 .iter()
377 .filter(|t| !matches!(t.kind, TokenKind::Newline))
378 .collect();
379 if let Some(name_tok) = before.last() {
380 if matches!(name_tok.kind, TokenKind::Ident(_)) {
381 let before_name: Vec<&Token> = tokens[..idx]
382 .iter()
383 .filter(|t| !matches!(t.kind, TokenKind::Newline))
384 .rev()
385 .skip(1)
386 .take(1)
387 .collect();
388 if let Some(kw) = before_name.first() {
389 return kw.kind == TokenKind::Model || kw.kind == TokenKind::Type;
390 }
391 }
392 }
393 return false;
394 }
395 depth -= 1;
396 }
397 _ => {}
398 }
399 }
400 false
401}
402
403fn extract_provider_from_tokens(tokens: &[Token]) -> Option<String> {
408 let n = tokens.len();
409 for i in 0..n {
410 if let TokenKind::Ident(ref kw) = tokens[i].kind {
412 if kw != "provider" {
413 continue;
414 }
415 } else {
416 continue;
417 }
418 let mut j = i + 1;
420 while j < n && matches!(tokens[j].kind, TokenKind::Newline) {
421 j += 1;
422 }
423 if j >= n || tokens[j].kind != TokenKind::Equal {
424 continue;
425 }
426 j += 1;
427 while j < n && matches!(tokens[j].kind, TokenKind::Newline) {
428 j += 1;
429 }
430 if j < n {
431 if let TokenKind::String(ref val) = tokens[j].kind {
432 let v = val.as_str();
433 if matches!(v, "postgresql" | "mysql" | "sqlite") {
434 return Some(v.to_string());
435 }
436 }
437 }
438 }
439 None
440}
441
442fn attr_arg_index_at(tokens: &[Token], offset: usize) -> Option<usize> {
449 let relevant: Vec<&Token> = tokens
450 .iter()
451 .filter(|t| t.span.end <= offset && !matches!(t.kind, TokenKind::Newline))
452 .collect();
453 let mut depth: i32 = 0;
454 let mut commas: usize = 0;
455 for tok in relevant.iter().rev() {
456 match tok.kind {
457 TokenKind::RParen => depth += 1,
458 TokenKind::LParen => {
459 if depth == 0 {
460 return Some(commas);
461 }
462 depth -= 1;
463 }
464 TokenKind::Comma if depth == 0 => commas += 1,
465 _ => {}
466 }
467 }
468 None
469}
470
471fn attr_argument_completions(
472 attr_name: &str,
473 provider: Option<&str>,
474 arg_index: usize,
475) -> Vec<CompletionItem> {
476 match attr_name {
477 "store" => vec![CompletionItem::new(
478 "json",
479 CompletionKind::FieldAttribute,
480 Some("Serialize array as JSON in the database".to_string()),
481 )],
482 "relation" => vec![
483 CompletionItem::new(
484 "fields: []",
485 CompletionKind::FieldName,
486 Some("Local FK field(s) on this model".to_string()),
487 ),
488 CompletionItem::new(
489 "references: []",
490 CompletionKind::FieldName,
491 Some("Referenced field(s) on the target model".to_string()),
492 ),
493 CompletionItem::new(
494 "name: \"\"",
495 CompletionKind::FieldName,
496 Some(
497 "Relation name (required when multiple relations to the same model)"
498 .to_string(),
499 ),
500 ),
501 CompletionItem::new(
502 "onDelete: Cascade",
503 CompletionKind::FieldName,
504 Some("Referential action on parent record delete".to_string()),
505 ),
506 CompletionItem::new(
507 "onUpdate: Cascade",
508 CompletionKind::FieldName,
509 Some("Referential action on parent record update".to_string()),
510 ),
511 ],
512 "default" => vec![
513 CompletionItem::new(
514 "autoincrement()",
515 CompletionKind::Keyword,
516 Some("Auto-incrementing integer sequence".to_string()),
517 ),
518 CompletionItem::new(
519 "now()",
520 CompletionKind::Keyword,
521 Some("Current timestamp at insert time".to_string()),
522 ),
523 CompletionItem::new(
524 "uuid()",
525 CompletionKind::Keyword,
526 Some("Randomly generated UUID".to_string()),
527 ),
528 CompletionItem::new(
529 "cuid()",
530 CompletionKind::Keyword,
531 Some("Randomly generated CUID".to_string()),
532 ),
533 CompletionItem::new(
534 "dbgenerated(\"expr\")",
535 CompletionKind::Keyword,
536 Some("Database-level default expression".to_string()),
537 ),
538 ],
539 "computed" => match arg_index {
540 0 => vec![CompletionItem::new(
543 "SQL expression",
544 CompletionKind::Keyword,
545 Some("e.g. price * quantity or first_name || ' ' || last_name".to_string()),
546 )],
547 _ => vec![
549 CompletionItem::new(
550 "Stored",
551 CompletionKind::Keyword,
552 Some("Computed on write, persisted on disk (all databases)".to_string()),
553 ),
554 CompletionItem::new(
555 "Virtual",
556 CompletionKind::Keyword,
557 Some("Computed on read, never stored (MySQL / SQLite only)".to_string()),
558 ),
559 ],
560 },
561 "index" => index_argument_completions(provider),
562 _ => vec![],
563 }
564}
565
566fn top_level_completions() -> Vec<CompletionItem> {
567 vec![
568 CompletionItem::new(
569 "model",
570 CompletionKind::Keyword,
571 Some("Define a data model".to_string()),
572 ),
573 CompletionItem::new(
574 "enum",
575 CompletionKind::Keyword,
576 Some("Define an enumeration".to_string()),
577 ),
578 CompletionItem::new(
579 "type",
580 CompletionKind::Keyword,
581 Some("Define a composite type".to_string()),
582 ),
583 CompletionItem::new(
584 "datasource",
585 CompletionKind::Keyword,
586 Some("Configure a data source".to_string()),
587 ),
588 CompletionItem::new(
589 "generator",
590 CompletionKind::Keyword,
591 Some("Configure code generation".to_string()),
592 ),
593 ]
594}
595
596fn index_argument_completions(provider: Option<&str>) -> Vec<CompletionItem> {
603 struct TypeEntry {
605 label: &'static str,
606 desc: &'static str,
607 providers: &'static [&'static str],
608 }
609 let type_entries = [
610 TypeEntry {
611 label: "type: BTree",
612 desc: "B-Tree index — default on all databases",
613 providers: &["postgresql", "mysql", "sqlite"],
614 },
615 TypeEntry {
616 label: "type: Hash",
617 desc: "Hash index — PostgreSQL and MySQL 8+",
618 providers: &["postgresql", "mysql"],
619 },
620 TypeEntry {
621 label: "type: Gin",
622 desc: "GIN index — PostgreSQL only (arrays, JSONB, full-text)",
623 providers: &["postgresql"],
624 },
625 TypeEntry {
626 label: "type: Gist",
627 desc: "GiST index — PostgreSQL only (geometry, range types)",
628 providers: &["postgresql"],
629 },
630 TypeEntry {
631 label: "type: Brin",
632 desc: "BRIN index — PostgreSQL only (ordered large tables)",
633 providers: &["postgresql"],
634 },
635 TypeEntry {
636 label: "type: FullText",
637 desc: "FULLTEXT index — MySQL only",
638 providers: &["mysql"],
639 },
640 ];
641
642 let mut items: Vec<CompletionItem> = type_entries
643 .iter()
644 .filter(|e| {
645 match provider {
648 Some(p) => e.providers.contains(&p),
649 None => true,
650 }
651 })
652 .map(|e| CompletionItem::new(e.label, CompletionKind::Keyword, Some(e.desc.to_string())))
653 .collect();
654
655 items.push(CompletionItem::new(
657 "name: \"\"",
658 CompletionKind::FieldName,
659 Some("Logical developer name for this index".to_string()),
660 ));
661 items.push(CompletionItem::new(
662 "map: \"\"",
663 CompletionKind::FieldName,
664 Some("Physical DDL index name (overrides auto-generated idx_… name)".to_string()),
665 ));
666
667 items
668}
669
670fn scalar_type_completions(provider: Option<&str>) -> Vec<CompletionItem> {
671 let pg = matches!(provider, Some("postgresql") | None);
672 let pg_or_mysql = matches!(provider, Some("postgresql") | Some("mysql") | None);
673
674 let mut items = vec![
675 CompletionItem::new(
676 "String",
677 CompletionKind::Type,
678 Some("UTF-8 text → VARCHAR / TEXT".to_string()),
679 ),
680 CompletionItem::new(
681 "Boolean",
682 CompletionKind::Type,
683 Some("true / false → BOOLEAN".to_string()),
684 ),
685 CompletionItem::new(
686 "Int",
687 CompletionKind::Type,
688 Some("32-bit integer → INTEGER".to_string()),
689 ),
690 CompletionItem::new(
691 "BigInt",
692 CompletionKind::Type,
693 Some("64-bit integer → BIGINT".to_string()),
694 ),
695 CompletionItem::new(
696 "Float",
697 CompletionKind::Type,
698 Some("64-bit float → DOUBLE PRECISION".to_string()),
699 ),
700 CompletionItem::new(
701 "Decimal",
702 CompletionKind::Type,
703 Some("Exact decimal → NUMERIC".to_string()),
704 ),
705 CompletionItem::new(
706 "DateTime",
707 CompletionKind::Type,
708 Some("Timestamp with time zone → TIMESTAMPTZ".to_string()),
709 ),
710 CompletionItem::new(
711 "Bytes",
712 CompletionKind::Type,
713 Some("Binary data → BYTEA".to_string()),
714 ),
715 CompletionItem::new(
716 "Json",
717 CompletionKind::Type,
718 Some("JSON document → JSONB".to_string()),
719 ),
720 CompletionItem::new(
721 "Uuid",
722 CompletionKind::Type,
723 Some("UUID → UUID".to_string()),
724 ),
725 ];
726
727 if pg {
728 items.push(CompletionItem::new(
729 "Jsonb",
730 CompletionKind::Type,
731 Some("JSONB document → JSONB (PostgreSQL only)".to_string()),
732 ));
733 items.push(CompletionItem::new(
734 "Xml",
735 CompletionKind::Type,
736 Some("XML document → XML (PostgreSQL only)".to_string()),
737 ));
738 }
739
740 if pg_or_mysql {
741 items.push(CompletionItem::with_snippet(
742 "Char(n)",
743 "Char(${1:n})",
744 CompletionKind::Type,
745 Some("Fixed-length string → CHAR(n) (PostgreSQL and MySQL)".to_string()),
746 ));
747 items.push(CompletionItem::with_snippet(
748 "VarChar(n)",
749 "VarChar(${1:n})",
750 CompletionKind::Type,
751 Some("Variable-length string → VARCHAR(n) (PostgreSQL and MySQL)".to_string()),
752 ));
753 }
754
755 items
756}
757
758fn field_attribute_completions() -> Vec<CompletionItem> {
759 vec![
760 CompletionItem::new(
761 "id",
762 CompletionKind::FieldAttribute,
763 Some("Mark as primary key".to_string()),
764 ),
765 CompletionItem::new(
766 "unique",
767 CompletionKind::FieldAttribute,
768 Some("Add a unique constraint".to_string()),
769 ),
770 CompletionItem::new(
771 "default()",
772 CompletionKind::FieldAttribute,
773 Some("Set a default value".to_string()),
774 ),
775 CompletionItem::new(
776 "relation()",
777 CompletionKind::FieldAttribute,
778 Some("Define a relation".to_string()),
779 ),
780 CompletionItem::new(
781 "map(\"\")",
782 CompletionKind::FieldAttribute,
783 Some("Override the column name".to_string()),
784 ),
785 CompletionItem::new(
786 "store(json)",
787 CompletionKind::FieldAttribute,
788 Some("Store as JSON column".to_string()),
789 ),
790 CompletionItem::new(
791 "updatedAt",
792 CompletionKind::FieldAttribute,
793 Some("Auto-set to current timestamp on every write".to_string()),
794 ),
795 CompletionItem::with_snippet(
796 "computed(…, Stored)",
797 "computed(${1:expr}, ${2|Stored,Virtual|})",
798 CompletionKind::FieldAttribute,
799 Some("Database-generated column (Stored or Virtual)".to_string()),
800 ),
801 CompletionItem::with_snippet(
802 "check(…)",
803 "check(${1:expr})",
804 CompletionKind::FieldAttribute,
805 Some("Add a CHECK constraint on this field".to_string()),
806 ),
807 ]
808}
809
810fn model_attribute_completions() -> Vec<CompletionItem> {
811 vec![
812 CompletionItem::new(
813 "id([])",
814 CompletionKind::ModelAttribute,
815 Some("Composite primary key".to_string()),
816 ),
817 CompletionItem::new(
818 "unique([])",
819 CompletionKind::ModelAttribute,
820 Some("Composite unique constraint".to_string()),
821 ),
822 CompletionItem::new(
823 "index([])",
824 CompletionKind::ModelAttribute,
825 Some(
826 "Add a database index — optionally with type: BTree|Hash|Gin|Gist|Brin|FullText"
827 .to_string(),
828 ),
829 ),
830 CompletionItem::new(
831 "map(\"\")",
832 CompletionKind::ModelAttribute,
833 Some("Override the table name".to_string()),
834 ),
835 CompletionItem::with_snippet(
836 "check(…)",
837 "check(${1:expr})",
838 CompletionKind::ModelAttribute,
839 Some("Add a table-level CHECK constraint".to_string()),
840 ),
841 ]
842}
843
844fn datasource_generator_completions() -> Vec<CompletionItem> {
845 vec![
846 CompletionItem::new(
847 "provider",
848 CompletionKind::FieldName,
849 Some("Database / generator provider".to_string()),
850 ),
851 CompletionItem::new(
852 "url",
853 CompletionKind::FieldName,
854 Some("Connection URL".to_string()),
855 ),
856 CompletionItem::new(
857 "output",
858 CompletionKind::FieldName,
859 Some("Output path for generated files".to_string()),
860 ),
861 CompletionItem::new(
862 "interface",
863 CompletionKind::FieldName,
864 Some("Client interface style: \"sync\" (default) or \"async\"".to_string()),
865 ),
866 CompletionItem::new(
867 "recursive_type_depth",
868 CompletionKind::FieldName,
869 Some(
870 "Depth of recursive include TypedDicts — Python client only (default: 5)"
871 .to_string(),
872 ),
873 ),
874 ]
875}
876
877fn config_block_kind_at(tokens: &[Token], offset: usize) -> Option<ConfigBlockKind> {
880 let relevant: Vec<&Token> = tokens.iter().filter(|t| t.span.end <= offset).collect();
881
882 let mut depth: i32 = 0;
883 for tok in relevant.iter().rev() {
884 match tok.kind {
885 TokenKind::RBrace => depth += 1,
886 TokenKind::LBrace => {
887 if depth == 0 {
888 let idx = tokens
889 .iter()
890 .position(|t| std::ptr::eq(t, *tok))
891 .unwrap_or(0);
892 let before: Vec<&Token> = tokens[..idx]
895 .iter()
896 .filter(|t| !matches!(t.kind, TokenKind::Newline))
897 .collect();
898 if before.len() >= 2 {
899 let kw_tok = &before[before.len() - 2];
900 return match kw_tok.kind {
901 TokenKind::Datasource => Some(ConfigBlockKind::Datasource),
902 TokenKind::Generator => Some(ConfigBlockKind::Generator),
903 _ => None,
904 };
905 }
906 return None;
907 }
908 depth -= 1;
909 }
910 _ => {}
911 }
912 }
913 None
914}
915
916fn config_value_completions(key: &str, block_kind: Option<ConfigBlockKind>) -> Vec<CompletionItem> {
917 match key {
918 "provider" => match block_kind {
919 Some(ConfigBlockKind::Datasource) => vec![
920 CompletionItem::with_insert(
921 "postgresql",
922 "\"postgresql\"",
923 CompletionKind::Keyword,
924 Some("PostgreSQL database".to_string()),
925 ),
926 CompletionItem::with_insert(
927 "mysql",
928 "\"mysql\"",
929 CompletionKind::Keyword,
930 Some("MySQL database".to_string()),
931 ),
932 CompletionItem::with_insert(
933 "sqlite",
934 "\"sqlite\"",
935 CompletionKind::Keyword,
936 Some("SQLite database".to_string()),
937 ),
938 ],
939 Some(ConfigBlockKind::Generator) => vec![
940 CompletionItem::with_insert(
941 "nautilus-client-rs",
942 "\"nautilus-client-rs\"",
943 CompletionKind::Keyword,
944 Some("Rust client generator".to_string()),
945 ),
946 CompletionItem::with_insert(
947 "nautilus-client-py",
948 "\"nautilus-client-py\"",
949 CompletionKind::Keyword,
950 Some("Python client generator".to_string()),
951 ),
952 CompletionItem::with_insert(
953 "nautilus-client-js",
954 "\"nautilus-client-js\"",
955 CompletionKind::Keyword,
956 Some("JavaScript/TypeScript client generator".to_string()),
957 ),
958 ],
959 None => vec![
960 CompletionItem::with_insert(
961 "postgresql",
962 "\"postgresql\"",
963 CompletionKind::Keyword,
964 Some("PostgreSQL database".to_string()),
965 ),
966 CompletionItem::with_insert(
967 "mysql",
968 "\"mysql\"",
969 CompletionKind::Keyword,
970 Some("MySQL database".to_string()),
971 ),
972 CompletionItem::with_insert(
973 "sqlite",
974 "\"sqlite\"",
975 CompletionKind::Keyword,
976 Some("SQLite database".to_string()),
977 ),
978 CompletionItem::with_insert(
979 "nautilus-client-rs",
980 "\"nautilus-client-rs\"",
981 CompletionKind::Keyword,
982 Some("Rust client generator".to_string()),
983 ),
984 CompletionItem::with_insert(
985 "nautilus-client-py",
986 "\"nautilus-client-py\"",
987 CompletionKind::Keyword,
988 Some("Python client generator".to_string()),
989 ),
990 CompletionItem::with_insert(
991 "nautilus-client-js",
992 "\"nautilus-client-js\"",
993 CompletionKind::Keyword,
994 Some("JavaScript/TypeScript client generator".to_string()),
995 ),
996 ],
997 },
998 "interface" => vec![
999 CompletionItem::with_insert(
1000 "sync",
1001 "\"sync\"",
1002 CompletionKind::Keyword,
1003 Some("Synchronous client interface (default)".to_string()),
1004 ),
1005 CompletionItem::with_insert(
1006 "async",
1007 "\"async\"",
1008 CompletionKind::Keyword,
1009 Some("Asynchronous client interface".to_string()),
1010 ),
1011 ],
1012 _ => Vec::new(),
1013 }
1014}