1use dashmap::DashMap;
3use tower_lsp::jsonrpc::Result;
4use tower_lsp::lsp_types::*;
5use tower_lsp::{Client, LanguageServer};
6
7use crate::state::DocumentState;
8
9pub struct Backend {
10 client: Client,
11 document_map: DashMap<Url, DocumentState>,
12}
13
14impl Backend {
15 pub fn new(client: Client) -> Self {
16 Self {
17 client,
18 document_map: DashMap::new(),
19 }
20 }
21
22 async fn on_change(&self, params: TextDocumentItem) {
23 let uri = params.uri;
24 let content = params.text;
25 let version = params.version;
26
27 let mut state = DocumentState::new(uri.clone(), content, version);
28 state.analyze();
29
30 let diagnostics = state.diagnostics.clone();
31 self.document_map.insert(uri.clone(), state);
32
33 self.client
34 .publish_diagnostics(uri, diagnostics, None)
35 .await;
36 }
37}
38
39#[tower_lsp::async_trait]
40impl LanguageServer for Backend {
41 async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
42 Ok(InitializeResult {
43 server_info: Some(ServerInfo {
44 name: "dbml-lsp".to_string(),
45 version: Some("0.1.0".to_string()),
46 }),
47 capabilities: ServerCapabilities {
48 text_document_sync: Some(TextDocumentSyncCapability::Kind(
49 TextDocumentSyncKind::FULL,
50 )),
51 definition_provider: Some(OneOf::Left(true)),
52 rename_provider: Some(OneOf::Left(true)),
53 hover_provider: Some(HoverProviderCapability::Simple(true)),
54 document_formatting_provider: Some(OneOf::Left(true)),
55 document_range_formatting_provider: Some(OneOf::Left(true)),
56 document_symbol_provider: Some(OneOf::Left(true)),
57 completion_provider: Some(CompletionOptions {
58 resolve_provider: Some(false),
59 trigger_characters: Some(vec![
60 ".".to_string(),
61 " ".to_string(),
62 "[".to_string(),
63 ":".to_string(),
64 "T".to_string(),
65 "e".to_string(),
66 "R".to_string(),
67 "P".to_string(),
68 ]),
69 work_done_progress_options: WorkDoneProgressOptions::default(),
70 all_commit_characters: None,
71 completion_item: Some(CompletionOptionsCompletionItem {
72 label_details_support: Some(true),
73 }),
74 }),
75 semantic_tokens_provider: Some(
76 SemanticTokensServerCapabilities::SemanticTokensOptions(
77 SemanticTokensOptions {
78 work_done_progress_options: WorkDoneProgressOptions::default(),
79 legend: SemanticTokensLegend {
80 token_types: vec![
81 SemanticTokenType::KEYWORD,
82 SemanticTokenType::CLASS,
83 SemanticTokenType::PROPERTY,
84 SemanticTokenType::ENUM,
85 SemanticTokenType::ENUM_MEMBER,
86 SemanticTokenType::TYPE,
87 SemanticTokenType::STRING,
88 SemanticTokenType::COMMENT,
89 SemanticTokenType::OPERATOR,
90 ],
91 token_modifiers: vec![],
92 },
93 range: Some(false),
94 full: Some(SemanticTokensFullOptions::Bool(true)),
95 },
96 ),
97 ),
98 ..ServerCapabilities::default()
99 },
100 })
101 }
102
103 async fn initialized(&self, _: InitializedParams) {
104 self.client
105 .log_message(MessageType::INFO, "DBML language server initialized")
106 .await;
107 }
108
109 async fn shutdown(&self) -> Result<()> {
110 Ok(())
111 }
112
113 async fn did_open(&self, params: DidOpenTextDocumentParams) {
114 self.on_change(TextDocumentItem {
115 uri: params.text_document.uri,
116 text: params.text_document.text,
117 version: params.text_document.version,
118 language_id: params.text_document.language_id,
119 })
120 .await;
121 }
122
123 async fn did_change(&self, params: DidChangeTextDocumentParams) {
124 let uri = params.text_document.uri;
125 let version = params.text_document.version;
126
127 if let Some(change) = params.content_changes.first() {
128 let mut state = self
129 .document_map
130 .get_mut(&uri)
131 .expect("Document should exist");
132
133 state.update_content(change.text.clone(), version);
134 state.analyze();
135
136 let diagnostics = state.diagnostics.clone();
137 drop(state);
138
139 self.client
140 .publish_diagnostics(uri, diagnostics, None)
141 .await;
142 }
143 }
144
145 async fn did_close(&self, params: DidCloseTextDocumentParams) {
146 self.document_map.remove(¶ms.text_document.uri);
147 }
148
149 async fn goto_definition(
150 &self,
151 params: GotoDefinitionParams,
152 ) -> Result<Option<GotoDefinitionResponse>> {
153 let uri = params.text_document_position_params.text_document.uri;
154 let position = params.text_document_position_params.position;
155
156 if let Some(state) = self.document_map.get(&uri) {
157 if let Some(semantic_model) = &state.semantic_model {
158 if let Some(ast) = &state.ast {
159 let offset = position_to_offset(&position, &state.content);
160
161 if let Some(symbol) = semantic_model.find_definition_at_position(offset, ast) {
163 let range = span_to_range(&symbol.span, &state.content);
164 return Ok(Some(GotoDefinitionResponse::Scalar(Location {
165 uri: uri.clone(),
166 range,
167 })));
168 }
169 }
170 }
171 }
172
173 Ok(None)
174 }
175
176 async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
177 let uri = params.text_document_position_params.text_document.uri;
178 let position = params.text_document_position_params.position;
179
180 if let Some(state) = self.document_map.get(&uri) {
181 if let Some(hover_content) = state.get_hover_info(position) {
182 return Ok(Some(Hover {
183 contents: HoverContents::Markup(MarkupContent {
184 kind: MarkupKind::Markdown,
185 value: hover_content,
186 }),
187 range: None,
188 }));
189 }
190 }
191
192 Ok(None)
193 }
194
195 async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
196 let uri = params.text_document_position.text_document.uri;
197 let position = params.text_document_position.position;
198 let new_name = params.new_name;
199
200 if let Some(state) = self.document_map.get(&uri) {
201 if let Some(semantic_model) = &state.semantic_model {
202 let offset = position_to_offset(&position, &state.content);
203
204 if let Some(symbol) = semantic_model.find_symbol_at_position(offset) {
205 let range = span_to_range(&symbol.span, &state.content);
206
207 let mut changes = std::collections::HashMap::new();
208 changes.insert(
209 uri.clone(),
210 vec![TextEdit {
211 range,
212 new_text: new_name,
213 }],
214 );
215
216 return Ok(Some(WorkspaceEdit {
217 changes: Some(changes),
218 document_changes: None,
219 change_annotations: None,
220 }));
221 }
222 }
223 }
224
225 Ok(None)
226 }
227
228 async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
229 let uri = params.text_document.uri;
230
231 if let Some(state) = self.document_map.get(&uri) {
232 if let Some(ast) = &state.ast {
233 let formatting_options = convert_lsp_formatting_options(¶ms.options);
235 let formatted = crate::formatter::format_document(ast, &formatting_options);
236
237 let start = Position::new(0, 0);
239 let end = offset_to_position(state.content.len(), &state.content);
240
241 return Ok(Some(vec![TextEdit {
242 range: Range::new(start, end),
243 new_text: formatted,
244 }]));
245 }
246 }
247
248 Ok(None)
249 }
250
251 async fn range_formatting(
252 &self,
253 params: DocumentRangeFormattingParams,
254 ) -> Result<Option<Vec<TextEdit>>> {
255 let uri = params.text_document.uri;
258
259 if let Some(state) = self.document_map.get(&uri) {
260 if let Some(ast) = &state.ast {
261 let formatting_options = convert_lsp_formatting_options(¶ms.options);
263 let formatted = crate::formatter::format_document(ast, &formatting_options);
264
265 let start = Position::new(0, 0);
267 let end = offset_to_position(state.content.len(), &state.content);
268
269 return Ok(Some(vec![TextEdit {
270 range: Range::new(start, end),
271 new_text: formatted,
272 }]));
273 }
274 }
275
276 Ok(None)
277 }
278
279 async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
280 let uri = params.text_document_position.text_document.uri;
281 let position = params.text_document_position.position;
282
283 self.client
284 .log_message(MessageType::INFO, format!("Completion requested at {}:{}", position.line, position.character))
285 .await;
286
287 if let Some(state) = self.document_map.get(&uri) {
288 let offset = position_to_offset(&position, &state.content);
289
290 let word_start = state.content[..offset]
292 .rfind(|c: char| c.is_whitespace() || c == '[' || c == '(' || c == ':' || c == '.' || c == ',')
293 .map(|pos| pos + 1)
294 .unwrap_or(0);
295 let current_word = state.content[word_start..offset].to_string();
296
297 let line_start = state.content[..offset]
299 .rfind('\n')
300 .map(|pos| pos + 1)
301 .unwrap_or(0);
302 let line_content = &state.content[line_start..offset];
303
304 self.client
305 .log_message(MessageType::INFO, format!("Line content: '{}', current word: '{}'", line_content, current_word))
306 .await;
307
308 let mut scored_completions: Vec<(CompletionItem, i32)> = Vec::new();
309
310 let context = analyze_completion_context(&state.content, offset, line_content, &state.ast);
312
313 self.client
314 .log_message(MessageType::INFO, format!("Completion context: {:?}, AST available: {}", context, state.ast.is_some()))
315 .await;
316
317
318 match context {
320 CompletionContext::TopLevel => {
321 if let Some(item) = create_scored_completion_item("Table", CompletionItemKind::CLASS, "Define a new table", ¤t_word) {
323 scored_completions.push(item);
324 }
325 if let Some(item) = create_scored_completion_item("enum", CompletionItemKind::ENUM, "Define an enum type", ¤t_word) {
326 scored_completions.push(item);
327 }
328 if let Some(item) = create_scored_completion_item("Ref", CompletionItemKind::REFERENCE, "Define a relationship", ¤t_word) {
329 scored_completions.push(item);
330 }
331 if let Some(item) = create_scored_completion_item("Project", CompletionItemKind::MODULE, "Define project settings", ¤t_word) {
332 scored_completions.push(item);
333 }
334 }
335 CompletionContext::TableBlock(table_name) => {
336 let data_types = vec![
338 ("integer", "Integer type"),
339 ("int", "Integer type (short)"),
340 ("varchar", "Variable character type"),
341 ("text", "Text type"),
342 ("timestamp", "Timestamp type"),
343 ("datetime", "Datetime type"),
344 ("date", "Date type"),
345 ("boolean", "Boolean type"),
346 ("bool", "Boolean type (short)"),
347 ("decimal", "Decimal type"),
348 ("float", "Float type"),
349 ("double", "Double type"),
350 ("json", "JSON type"),
351 ("jsonb", "Binary JSON type"),
352 ("uuid", "UUID type"),
353 ("bigint", "Big integer type"),
354 ("smallint", "Small integer type"),
355 ("char", "Character type"),
356 ("blob", "Binary large object"),
357 ("real", "Real number type"),
358 ];
359
360 for (dtype, doc) in data_types {
361 if let Some(item) = create_scored_completion_item(dtype, CompletionItemKind::TYPE_PARAMETER, doc, ¤t_word) {
362 scored_completions.push(item);
363 }
364 }
365
366 if let Some(ast) = &state.ast {
368 for item in &ast.items {
369 if let crate::ast::DocumentItem::Enum(enum_def) = item {
370 if let Some(mut scored_item) = create_scored_completion_item(
371 &enum_def.name.name,
372 CompletionItemKind::ENUM,
373 &format!("Enum with {} values", enum_def.members.len()),
374 ¤t_word,
375 ) {
376 scored_item.1 += 50;
377 scored_completions.push(scored_item);
378 }
379 }
380 }
381 }
382 }
383 CompletionContext::ColumnSettings => {
384 let settings = vec![
385 ("pk", "Primary key"),
386 ("not null", "Not null constraint"),
387 ("null", "Nullable"),
388 ("unique", "Unique constraint"),
389 ("increment", "Auto increment"),
390 ("default:", "Default value"),
391 ("note:", "Add a note"),
392 ("ref:", "Inline reference"),
393 ];
394
395 for (keyword, doc) in settings {
396 if let Some(item) = create_scored_completion_item(keyword, CompletionItemKind::KEYWORD, doc, ¤t_word) {
397 scored_completions.push(item);
398 }
399 }
400 }
401 CompletionContext::RefSettings => {
402 let settings = vec![
404 ("update:", "Update action"),
405 ("delete:", "Delete action"),
406 ];
407
408 for (keyword, doc) in settings {
409 if let Some(item) = create_scored_completion_item(keyword, CompletionItemKind::KEYWORD, doc, ¤t_word) {
410 scored_completions.push(item);
411 }
412 }
413 }
414 CompletionContext::RefAction => {
415 let actions = vec![
417 ("cascade", "CASCADE - automatically propagate changes"),
418 ("restrict", "RESTRICT - prevent the operation"),
419 ("no action", "NO ACTION - similar to restrict"),
420 ("set null", "SET NULL - set foreign key to null"),
421 ("set default", "SET DEFAULT - set foreign key to default value"),
422 ];
423
424 for (action, doc) in actions {
425 if let Some(item) = create_scored_completion_item(action, CompletionItemKind::VALUE, doc, ¤t_word) {
426 scored_completions.push(item);
427 }
428 }
429 }
430 CompletionContext::IndexesBlock(table_name) => {
431 if let Some(ast) = &state.ast {
433 if let Some(table) = find_table_by_name(&ast, &table_name) {
434 for table_item in &table.items {
435 if let crate::ast::TableItem::Column(col) = table_item {
436 if let Some(mut scored_item) = create_scored_completion_item(
437 &col.name.name,
438 CompletionItemKind::FIELD,
439 &format!("Column: {}", col.col_type),
440 ¤t_word,
441 ) {
442 scored_item.1 += 200;
443 scored_completions.push(scored_item);
444 }
445 }
446 }
447 }
448 } else {
449 let tables = extract_tables_from_content(&state.content);
451
452 for (tname, columns) in tables {
453 if tname == table_name {
454 for (col_name, col_type) in columns {
455 if let Some(mut scored_item) = create_scored_completion_item(
456 &col_name,
457 CompletionItemKind::FIELD,
458 &format!("Column: {}", col_type),
459 ¤t_word,
460 ) {
461 scored_item.1 += 200;
462 scored_completions.push(scored_item);
463 }
464 }
465 break;
466 }
467 }
468 }
469 }
470 CompletionContext::AfterDot(table_name) => {
471 if let Some(ast) = &state.ast {
473 if let Some(table) = find_table_by_name(&ast, &table_name) {
474 for table_item in &table.items {
475 if let crate::ast::TableItem::Column(col) = table_item {
476 if let Some(mut scored_item) = create_scored_completion_item(
477 &col.name.name,
478 CompletionItemKind::FIELD,
479 &format!("{}.{} ({})", table_name, col.name.name, col.col_type),
480 ¤t_word,
481 ) {
482 scored_item.1 += 300;
483 scored_completions.push(scored_item);
484 }
485 }
486 }
487 }
488 } else {
489 let tables = extract_tables_from_content(&state.content);
491
492 for (tname, columns) in tables {
493 if tname == table_name {
494 for (col_name, col_type) in columns {
495 if let Some(mut scored_item) = create_scored_completion_item(
496 &col_name,
497 CompletionItemKind::FIELD,
498 &format!("{}.{} ({})", table_name, col_name, col_type),
499 ¤t_word,
500 ) {
501 scored_item.1 += 300;
502 scored_completions.push(scored_item);
503 }
504 }
505 break;
506 }
507 }
508 }
509 }
510 CompletionContext::RefContext => {
511 if let Some(ast) = &state.ast {
513 for item in &ast.items {
514 if let crate::ast::DocumentItem::Table(table) = item {
515 if let Some(scored_item) = create_scored_completion_item(
516 &table.name.name,
517 CompletionItemKind::CLASS,
518 "Table reference",
519 ¤t_word,
520 ) {
521 scored_completions.push(scored_item);
522 }
523
524 for table_item in &table.items {
526 if let crate::ast::TableItem::Column(col) = table_item {
527 let full_ref = format!("{}.{}", table.name.name, col.name.name);
528 if let Some(scored_item) = create_scored_completion_item(
529 &full_ref,
530 CompletionItemKind::FIELD,
531 &format!("Reference to {}.{} ({})", table.name.name, col.name.name, col.col_type),
532 ¤t_word,
533 ) {
534 scored_completions.push(scored_item);
535 }
536 }
537 }
538 }
539 }
540 } else {
541 let tables = extract_tables_from_content(&state.content);
543 self.client
544 .log_message(MessageType::INFO, format!("Extracted {} tables from content", tables.len()))
545 .await;
546
547 for (table_name, columns) in tables {
548 if let Some(scored_item) = create_scored_completion_item(
549 &table_name,
550 CompletionItemKind::CLASS,
551 "Table reference",
552 ¤t_word,
553 ) {
554 scored_completions.push(scored_item);
555 }
556
557 for (col_name, col_type) in columns {
559 let full_ref = format!("{}.{}", table_name, col_name);
560 if let Some(scored_item) = create_scored_completion_item(
561 &full_ref,
562 CompletionItemKind::FIELD,
563 &format!("Reference to {}.{} ({})", table_name, col_name, col_type),
564 ¤t_word,
565 ) {
566 scored_completions.push(scored_item);
567 }
568 }
569 }
570 }
571
572 let operators = vec![
574 (">", "Many-to-one relationship"),
575 ("<", "One-to-many relationship"),
576 ("-", "One-to-one relationship"),
577 ("<>", "Many-to-many relationship"),
578 ];
579
580 for (op, doc) in operators {
581 if let Some(item) = create_scored_completion_item(op, CompletionItemKind::OPERATOR, doc, ¤t_word) {
582 scored_completions.push(item);
583 }
584 }
585 }
586 CompletionContext::DefaultValue(col_type) => {
587 let default_suggestions = match col_type.to_lowercase().as_str() {
589 "integer" | "int" | "bigint" | "smallint" => vec![
590 ("0", "Zero"),
591 ("1", "One"),
592 ("-1", "Negative one"),
593 ],
594 "boolean" | "bool" => vec![
595 ("true", "True"),
596 ("false", "False"),
597 ],
598 "varchar" | "text" | "char" => vec![
599 ("''", "Empty string"),
600 ("'default'", "Default text"),
601 ],
602 "timestamp" | "datetime" => vec![
603 ("`now()`", "Current timestamp"),
604 ("CURRENT_TIMESTAMP", "Current timestamp"),
605 ],
606 "date" => vec![
607 ("`now()`", "Current date"),
608 ("CURRENT_DATE", "Current date"),
609 ],
610 "uuid" => vec![
611 ("`gen_random_uuid()`", "Generate random UUID"),
612 ("`uuid_generate_v4()`", "Generate UUID v4"),
613 ],
614 "json" | "jsonb" => vec![
615 ("'{}'", "Empty JSON object"),
616 ("'[]'", "Empty JSON array"),
617 ],
618 _ => vec![],
619 };
620
621 for (default_val, doc) in default_suggestions {
622 if let Some(scored_item) = create_scored_completion_item(
623 default_val,
624 CompletionItemKind::VALUE,
625 doc,
626 ¤t_word,
627 ) {
628 scored_completions.push(scored_item);
629 }
630 }
631
632 if let Some(ast) = &state.ast {
634 for item in &ast.items {
635 if let crate::ast::DocumentItem::Enum(enum_def) = item {
636 if enum_def.name.name == col_type {
637 for member in &enum_def.members {
639 if let Some(mut scored_item) = create_scored_completion_item(
640 &format!("'{}'", member.name.name),
641 CompletionItemKind::ENUM_MEMBER,
642 &format!("Enum value from {}", enum_def.name.name),
643 ¤t_word,
644 ) {
645 scored_item.1 += 150;
646 scored_completions.push(scored_item);
647 }
648 }
649 break;
650 }
651 }
652 }
653 }
654 }
655 }
656
657 self.client
658 .log_message(MessageType::INFO, format!("Generated {} scored completions", scored_completions.len()))
659 .await;
660
661 if !scored_completions.is_empty() {
662 scored_completions.sort_by(|a, b| b.1.cmp(&a.1));
664
665 let completions: Vec<CompletionItem> = scored_completions
667 .into_iter()
668 .map(|(item, _score)| item)
669 .collect();
670
671 self.client
672 .log_message(MessageType::INFO, format!("Returning {} completions", completions.len()))
673 .await;
674
675 return Ok(Some(CompletionResponse::Array(completions)));
676 }
677 }
678
679 self.client
680 .log_message(MessageType::INFO, "No completions generated")
681 .await;
682
683 Ok(None)
684 }
685
686 async fn document_symbol(
687 &self,
688 params: DocumentSymbolParams,
689 ) -> Result<Option<DocumentSymbolResponse>> {
690 let uri = params.text_document.uri;
691
692 self.client
693 .log_message(MessageType::INFO, format!("Document symbol requested for: {}", uri))
694 .await;
695
696 if let Some(state) = self.document_map.get(&uri) {
697 if let Some(ast) = &state.ast {
698 let symbols = generate_document_symbols(ast, &state.content);
699
700 self.client
701 .log_message(MessageType::INFO, format!("Generated {} symbols", symbols.len()))
702 .await;
703
704 return Ok(Some(DocumentSymbolResponse::Nested(symbols)));
705 }
706 }
707
708 Ok(None)
709 }
710
711 async fn semantic_tokens_full(
712 &self,
713 params: SemanticTokensParams,
714 ) -> Result<Option<SemanticTokensResult>> {
715 let uri = params.text_document.uri;
716
717 if let Some(state) = self.document_map.get(&uri) {
718 if let Some(ast) = &state.ast {
719 let tokens = generate_semantic_tokens(ast, &state.content);
720 return Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
721 result_id: None,
722 data: tokens,
723 })));
724 }
725 }
726
727 Ok(None)
728 }
729}
730
731fn create_completion_item(label: &str, kind: CompletionItemKind, documentation: &str) -> CompletionItem {
732 CompletionItem {
733 label: label.to_string(),
734 kind: Some(kind),
735 detail: Some(documentation.to_string()),
736 documentation: Some(Documentation::String(documentation.to_string())),
737 insert_text: Some(label.to_string()),
738 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
739 ..Default::default()
740 }
741}
742
743fn fuzzy_match_score(pattern: &str, text: &str) -> Option<i32> {
745 if pattern.is_empty() {
746 return Some(0);
747 }
748
749 let pattern_lower = pattern.to_lowercase();
750 let text_lower = text.to_lowercase();
751
752 if text_lower == pattern_lower {
754 return Some(1000);
755 }
756
757 if text_lower.starts_with(&pattern_lower) {
759 return Some(900 - pattern.len() as i32);
760 }
761
762 if text_lower.contains(&pattern_lower) {
764 return Some(500);
765 }
766
767 let mut score = 100;
769 let mut text_idx = 0;
770 let text_chars: Vec<char> = text_lower.chars().collect();
771 let pattern_chars: Vec<char> = pattern_lower.chars().collect();
772
773 for p_char in pattern_chars.iter() {
774 let mut found = false;
775 while text_idx < text_chars.len() {
776 if text_chars[text_idx] == *p_char {
777 found = true;
778 text_idx += 1;
779 score -= 1; break;
781 }
782 text_idx += 1;
783 score -= 2; }
785
786 if !found {
787 return None; }
789 }
790
791 Some(score.max(1))
792}
793
794fn create_scored_completion_item(
795 label: &str,
796 kind: CompletionItemKind,
797 documentation: &str,
798 pattern: &str,
799) -> Option<(CompletionItem, i32)> {
800 let score = fuzzy_match_score(pattern, label)?;
801
802 let mut item = CompletionItem {
803 label: label.to_string(),
804 kind: Some(kind),
805 detail: Some(documentation.to_string()),
806 documentation: Some(Documentation::String(documentation.to_string())),
807 insert_text: Some(label.to_string()),
808 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
809 sort_text: Some(format!("{:04}", 10000 - score)), filter_text: Some(label.to_string()),
811 ..Default::default()
812 };
813
814 Some((item, score))
815}
816
817fn convert_lsp_formatting_options(
818 lsp_options: &FormattingOptions,
819) -> crate::formatter::FormattingOptions {
820 crate::formatter::FormattingOptions {
821 tab_size: lsp_options.tab_size,
822 insert_spaces: lsp_options.insert_spaces,
823 trim_trailing_whitespace: lsp_options
824 .properties
825 .get("trimTrailingWhitespace")
826 .and_then(|v| match v {
827 FormattingProperty::Bool(b) => Some(*b),
828 _ => None,
829 })
830 .unwrap_or(true),
831 insert_final_newline: lsp_options
832 .properties
833 .get("insertFinalNewline")
834 .and_then(|v| match v {
835 FormattingProperty::Bool(b) => Some(*b),
836 _ => None,
837 })
838 .unwrap_or(true),
839 trim_final_newlines: lsp_options
840 .properties
841 .get("trimFinalNewlines")
842 .and_then(|v| match v {
843 FormattingProperty::Bool(b) => Some(*b),
844 _ => None,
845 })
846 .unwrap_or(true),
847 }
848}
849
850fn position_to_offset(position: &Position, content: &str) -> usize {
851 let mut offset = 0;
852 let mut current_line = 0;
853 let mut current_col = 0;
854
855 for ch in content.chars() {
856 if current_line == position.line && current_col == position.character {
857 return offset;
858 }
859
860 if ch == '\n' {
861 current_line += 1;
862 current_col = 0;
863 } else {
864 current_col += 1;
865 }
866 offset += ch.len_utf8();
867 }
868
869 offset
870}
871
872fn span_to_range(span: &std::ops::Range<usize>, content: &str) -> Range {
873 let start_pos = offset_to_position(span.start, content);
874 let end_pos = offset_to_position(span.end, content);
875 Range::new(start_pos, end_pos)
876}
877
878fn offset_to_position(offset: usize, content: &str) -> Position {
879 let mut line = 0;
880 let mut col = 0;
881
882 for (i, ch) in content.chars().enumerate() {
883 if i >= offset {
884 break;
885 }
886 if ch == '\n' {
887 line += 1;
888 col = 0;
889 } else {
890 col += 1;
891 }
892 }
893
894 Position::new(line, col)
895}
896
897fn generate_semantic_tokens(ast: &crate::ast::Document, content: &str) -> Vec<SemanticToken> {
898 use crate::ast::*;
899
900 let mut tokens = Vec::new();
901 let mut prev_line = 0;
902 let mut prev_start = 0;
903
904 for item in &ast.items {
905 match item {
906 DocumentItem::Table(table) => {
907 add_token(
908 &mut tokens,
909 &table.name.span,
910 1,
911 content,
912 &mut prev_line,
913 &mut prev_start,
914 );
915
916 for item in &table.items {
917 if let TableItem::Column(col) = item {
918 add_token(
919 &mut tokens,
920 &col.name.span,
921 2,
922 content,
923 &mut prev_line,
924 &mut prev_start,
925 );
926 }
927 }
928 }
929 DocumentItem::Enum(enum_def) => {
930 add_token(
931 &mut tokens,
932 &enum_def.name.span,
933 3,
934 content,
935 &mut prev_line,
936 &mut prev_start,
937 );
938
939 for member in &enum_def.members {
940 add_token(
941 &mut tokens,
942 &member.name.span,
943 4,
944 content,
945 &mut prev_line,
946 &mut prev_start,
947 );
948 }
949 }
950 _ => {}
951 }
952 }
953
954 tokens
955}
956
957fn add_token(
958 tokens: &mut Vec<SemanticToken>,
959 span: &std::ops::Range<usize>,
960 token_type: u32,
961 content: &str,
962 prev_line: &mut u32,
963 prev_start: &mut u32,
964) {
965 let start_pos = offset_to_position(span.start, content);
966 let length = span.end - span.start;
967
968 let delta_line = start_pos.line - *prev_line;
969 let delta_start = if delta_line == 0 {
970 start_pos.character - *prev_start
971 } else {
972 start_pos.character
973 };
974
975 tokens.push(SemanticToken {
976 delta_line,
977 delta_start,
978 length: length as u32,
979 token_type,
980 token_modifiers_bitset: 0,
981 });
982
983 *prev_line = start_pos.line;
984 *prev_start = start_pos.character;
985}
986
987#[allow(deprecated)]
988fn generate_document_symbols(ast: &crate::ast::Document, content: &str) -> Vec<DocumentSymbol> {
989 use crate::ast::*;
990
991 let mut symbols = Vec::new();
992
993 for item in &ast.items {
994 match item {
995 DocumentItem::Table(table) => {
996 let mut children = Vec::new();
997
998 for table_item in &table.items {
1000 match table_item {
1001 TableItem::Column(column) => {
1002 children.push(DocumentSymbol {
1003 name: column.name.name.clone(),
1004 detail: Some(column.col_type.clone()),
1005 kind: SymbolKind::PROPERTY,
1006 tags: None,
1007 deprecated: None,
1008 range: span_to_range(&column.span, content),
1009 selection_range: span_to_range(&column.name.span, content),
1010 children: None,
1011 });
1012 }
1013 TableItem::Indexes(indexes_block) => {
1014 let mut index_children = Vec::new();
1015
1016 for (idx, index) in indexes_block.indexes.iter().enumerate() {
1017 let index_name = format!("index_{}", idx);
1018 let columns_str = index.columns.iter()
1019 .map(|col| match col {
1020 IndexColumn::Simple(ident) => ident.name.clone(),
1021 IndexColumn::Expression(expr) => format!("`{}`", expr),
1022 })
1023 .collect::<Vec<_>>()
1024 .join(", ");
1025
1026 index_children.push(DocumentSymbol {
1027 name: index_name,
1028 detail: Some(format!("({})", columns_str)),
1029 kind: SymbolKind::KEY,
1030 tags: None,
1031 deprecated: None,
1032 range: span_to_range(&index.span, content),
1033 selection_range: span_to_range(&index.span, content),
1034 children: None,
1035 });
1036 }
1037
1038 if !index_children.is_empty() {
1039 children.push(DocumentSymbol {
1040 name: "indexes".to_string(),
1041 detail: None,
1042 kind: SymbolKind::NAMESPACE,
1043 tags: None,
1044 deprecated: None,
1045 range: span_to_range(&indexes_block.span, content),
1046 selection_range: span_to_range(&indexes_block.span, content),
1047 children: Some(index_children),
1048 });
1049 }
1050 }
1051 _ => {}
1052 }
1053 }
1054
1055 let table_detail = if let Some(alias) = &table.alias {
1056 Some(format!("as {}", alias.name))
1057 } else {
1058 None
1059 };
1060
1061 symbols.push(DocumentSymbol {
1062 name: table.name.name.clone(),
1063 detail: table_detail,
1064 kind: SymbolKind::CLASS,
1065 tags: None,
1066 deprecated: None,
1067 range: span_to_range(&table.span, content),
1068 selection_range: span_to_range(&table.name.span, content),
1069 children: if children.is_empty() { None } else { Some(children) },
1070 });
1071 }
1072 DocumentItem::Enum(enum_def) => {
1073 let mut children = Vec::new();
1074
1075 for member in &enum_def.members {
1076 children.push(DocumentSymbol {
1077 name: member.name.name.clone(),
1078 detail: member.note.as_ref().map(|note| note.value.clone()),
1079 kind: SymbolKind::ENUM_MEMBER,
1080 tags: None,
1081 deprecated: None,
1082 range: span_to_range(&member.span, content),
1083 selection_range: span_to_range(&member.name.span, content),
1084 children: None,
1085 });
1086 }
1087
1088 symbols.push(DocumentSymbol {
1089 name: enum_def.name.name.clone(),
1090 detail: Some(format!("{} values", enum_def.members.len())),
1091 kind: SymbolKind::ENUM,
1092 tags: None,
1093 deprecated: None,
1094 range: span_to_range(&enum_def.span, content),
1095 selection_range: span_to_range(&enum_def.name.span, content),
1096 children: if children.is_empty() { None } else { Some(children) },
1097 });
1098 }
1099 DocumentItem::Ref(ref_def) => {
1100 let ref_name = if let Some(name) = &ref_def.name {
1101 name.name.clone()
1102 } else {
1103 format!("{}.{} → {}.{}",
1104 ref_def.from_table.name,
1105 ref_def.from_columns.iter()
1106 .map(|c| c.name.as_str())
1107 .collect::<Vec<_>>()
1108 .join(", "),
1109 ref_def.to_table.name,
1110 ref_def.to_columns.iter()
1111 .map(|c| c.name.as_str())
1112 .collect::<Vec<_>>()
1113 .join(", ")
1114 )
1115 };
1116
1117 let rel_type = match ref_def.relationship {
1118 RelationshipType::OneToOne => "1:1",
1119 RelationshipType::OneToMany => "1:N",
1120 RelationshipType::ManyToOne => "N:1",
1121 RelationshipType::ManyToMany => "N:N",
1122 };
1123
1124 symbols.push(DocumentSymbol {
1125 name: ref_name,
1126 detail: Some(rel_type.to_string()),
1127 kind: SymbolKind::INTERFACE,
1128 tags: None,
1129 deprecated: None,
1130 range: span_to_range(&ref_def.span, content),
1131 selection_range: span_to_range(&ref_def.span, content),
1132 children: None,
1133 });
1134 }
1135 DocumentItem::Project(project) => {
1136 let project_name = if let Some(name) = &project.name {
1137 name.name.clone()
1138 } else {
1139 "Project".to_string()
1140 };
1141
1142 symbols.push(DocumentSymbol {
1143 name: project_name,
1144 detail: Some("Project Settings".to_string()),
1145 kind: SymbolKind::MODULE,
1146 tags: None,
1147 deprecated: None,
1148 range: span_to_range(&project.span, content),
1149 selection_range: span_to_range(&project.span, content),
1150 children: None,
1151 });
1152 }
1153 }
1154 }
1155
1156 symbols
1157}
1158
1159fn is_in_table_block(content: &str, offset: usize) -> bool {
1161 let before = &content[..offset];
1162 let after = &content[offset..];
1163
1164 let mut depth = 0;
1166 let mut in_table = false;
1167
1168 for line in before.lines() {
1169 let trimmed = line.trim();
1170 if trimmed.starts_with("Table ") {
1171 in_table = true;
1172 } else if trimmed.starts_with("enum ") || trimmed.starts_with("Ref ") || trimmed.starts_with("Project ") {
1173 in_table = false;
1174 }
1175
1176 for ch in line.chars() {
1177 match ch {
1178 '{' => depth += 1,
1179 '}' => depth -= 1,
1180 _ => {}
1181 }
1182 }
1183 }
1184
1185 in_table && depth > 0
1186}
1187
1188fn is_in_enum_block(content: &str, offset: usize) -> bool {
1190 let before = &content[..offset];
1191
1192 let mut depth = 0;
1193 let mut in_enum = false;
1194
1195 for line in before.lines() {
1196 let trimmed = line.trim();
1197 if trimmed.starts_with("enum ") {
1198 in_enum = true;
1199 } else if trimmed.starts_with("Table ") || trimmed.starts_with("Ref ") || trimmed.starts_with("Project ") {
1200 in_enum = false;
1201 }
1202
1203 for ch in line.chars() {
1204 match ch {
1205 '{' => depth += 1,
1206 '}' => depth -= 1,
1207 _ => {}
1208 }
1209 }
1210 }
1211
1212 in_enum && depth > 0
1213}
1214
1215#[derive(Debug)]
1217enum CompletionContext {
1218 TopLevel,
1219 TableBlock(String), ColumnSettings, IndexesBlock(String), AfterDot(String), RefContext, DefaultValue(String), RefSettings, RefAction, }
1228
1229fn analyze_completion_context(
1231 content: &str,
1232 offset: usize,
1233 line_content: &str,
1234 ast: &Option<crate::ast::Document>,
1235) -> CompletionContext {
1236 if let Some(_dot_pos) = line_content.rfind('.') {
1238 if let Some(table_name) = extract_table_before_dot(line_content) {
1239 return CompletionContext::AfterDot(table_name);
1240 }
1241 }
1242
1243 if line_content.contains("update:") && !line_content.ends_with("update:") {
1245 let after_update = line_content.split("update:").last().unwrap_or("");
1246 if !after_update.contains(',') && !after_update.contains(']') && !after_update.contains('}') {
1247 return CompletionContext::RefAction;
1248 }
1249 }
1250 if line_content.contains("delete:") && !line_content.ends_with("delete:") {
1251 let after_delete = line_content.split("delete:").last().unwrap_or("");
1252 if !after_delete.contains(',') && !after_delete.contains(']') && !after_delete.contains('}') {
1253 return CompletionContext::RefAction;
1254 }
1255 }
1256
1257 let in_ref_block = is_in_ref_block(content, offset);
1259 let in_inline_ref = line_content.contains("ref:") && line_content.contains('[');
1260
1261 if (in_ref_block || in_inline_ref) && (line_content.contains(',') || line_content.ends_with(' ')) {
1262 let trimmed = line_content.trim();
1264 if !trimmed.ends_with("cascade") && !trimmed.ends_with("restrict") &&
1265 !trimmed.ends_with("null") && !trimmed.ends_with("action") && !trimmed.ends_with("default") {
1266 return CompletionContext::RefSettings;
1267 }
1268 }
1269
1270 if line_content.contains("default:") && line_content.contains('[') {
1272 if let Some(col_type) = extract_column_type_from_line(content, offset) {
1274 return CompletionContext::DefaultValue(col_type);
1275 }
1276 }
1277
1278 if line_content.contains('[') && !line_content.contains(']') {
1280 return CompletionContext::ColumnSettings;
1281 }
1282
1283 if line_content.contains("ref:") || line_content.contains("Ref") {
1285 return CompletionContext::RefContext;
1286 }
1287
1288 if let Some(table_name) = find_table_name_for_indexes_block(content, offset) {
1290 return CompletionContext::IndexesBlock(table_name);
1291 }
1292
1293 if let Some(table_name) = find_current_table_name(content, offset, ast) {
1295 if !line_content.contains('[') && !line_content.contains("indexes") {
1297 return CompletionContext::TableBlock(table_name);
1298 }
1299 }
1300
1301 CompletionContext::TopLevel
1303}
1304
1305fn extract_column_type_from_line(content: &str, offset: usize) -> Option<String> {
1307 let line_start = content[..offset].rfind('\n').map(|pos| pos + 1).unwrap_or(0);
1309 let line = &content[line_start..offset];
1310
1311 let parts: Vec<&str> = line.trim().split_whitespace().collect();
1313 if parts.len() >= 2 {
1314 Some(parts[1].to_string())
1315 } else {
1316 None
1317 }
1318}
1319
1320fn find_table_name_for_indexes_block(content: &str, offset: usize) -> Option<String> {
1322 let before = &content[..offset];
1323
1324 let mut depth = 0;
1325 let mut in_indexes = false;
1326 let mut table_name: Option<String> = None;
1327
1328 for line in before.lines() {
1329 let trimmed = line.trim();
1330
1331 if trimmed.starts_with("Table ") {
1333 let parts: Vec<&str> = trimmed.split_whitespace().collect();
1335 if parts.len() >= 2 {
1336 table_name = Some(parts[1].trim_end_matches('{').to_string());
1337 }
1338 }
1339
1340 if trimmed.starts_with("indexes") && trimmed.contains('{') {
1342 in_indexes = true;
1343 }
1344
1345 for ch in line.chars() {
1346 match ch {
1347 '{' => depth += 1,
1348 '}' => {
1349 depth -= 1;
1350 if depth == 1 {
1351 in_indexes = false;
1352 }
1353 }
1354 _ => {}
1355 }
1356 }
1357 }
1358
1359 if in_indexes && depth >= 2 {
1360 table_name
1361 } else {
1362 None
1363 }
1364}
1365
1366fn find_current_table_name(
1368 content: &str,
1369 offset: usize,
1370 ast: &Option<crate::ast::Document>,
1371) -> Option<String> {
1372 if let Some(ast) = ast {
1374 if let Some(table) = find_current_table(ast, content, offset) {
1375 return Some(table.name.name.clone());
1376 }
1377 }
1378
1379 let before = &content[..offset];
1381 let mut table_name: Option<String> = None;
1382 let mut depth = 0;
1383
1384 for line in before.lines() {
1385 let trimmed = line.trim();
1386
1387 if trimmed.starts_with("Table ") {
1388 let parts: Vec<&str> = trimmed.split_whitespace().collect();
1389 if parts.len() >= 2 {
1390 table_name = Some(parts[1].trim_end_matches('{').to_string());
1391 }
1392 } else if trimmed.starts_with("enum ") || trimmed.starts_with("Ref ") || trimmed.starts_with("Project ") {
1393 table_name = None;
1394 }
1395
1396 for ch in line.chars() {
1397 match ch {
1398 '{' => depth += 1,
1399 '}' => {
1400 depth -= 1;
1401 if depth == 0 {
1402 table_name = None;
1403 }
1404 }
1405 _ => {}
1406 }
1407 }
1408 }
1409
1410 if depth > 0 {
1411 table_name
1412 } else {
1413 None
1414 }
1415}
1416
1417fn find_table_by_name<'a>(ast: &'a crate::ast::Document, name: &str) -> Option<&'a crate::ast::Table> {
1419 for item in &ast.items {
1420 if let crate::ast::DocumentItem::Table(table) = item {
1421 if table.name.name == name {
1422 return Some(table);
1423 }
1424 }
1425 }
1426 None
1427}
1428
1429fn is_in_ref_block(content: &str, offset: usize) -> bool {
1431 let before = &content[..offset];
1432 let mut in_ref = false;
1433 let mut depth = 0;
1434
1435 for line in before.lines() {
1436 let trimmed = line.trim();
1437
1438 if trimmed.starts_with("Ref ") || trimmed.starts_with("Ref{") || trimmed.starts_with("Ref {") {
1440 in_ref = true;
1441 depth = 0;
1442 } else if trimmed.starts_with("Table ") || trimmed.starts_with("enum ") || trimmed.starts_with("Project ") {
1443 in_ref = false;
1444 }
1445
1446 for ch in line.chars() {
1448 match ch {
1449 '{' => depth += 1,
1450 '}' => {
1451 depth -= 1;
1452 if depth == 0 && in_ref {
1453 in_ref = false;
1454 }
1455 }
1456 _ => {}
1457 }
1458 }
1459 }
1460
1461 in_ref && depth > 0
1462}
1463
1464fn find_current_table<'a>(ast: &'a crate::ast::Document, _content: &str, offset: usize) -> Option<&'a crate::ast::Table> {
1466 use crate::ast::*;
1467
1468 for item in &ast.items {
1469 if let DocumentItem::Table(table) = item {
1470 if offset >= table.span.start && offset <= table.span.end {
1471 return Some(table);
1472 }
1473 }
1474 }
1475
1476 None
1477}
1478
1479fn extract_table_before_dot(line: &str) -> Option<String> {
1481 if let Some(dot_pos) = line.rfind('.') {
1483 let before_dot = &line[..dot_pos];
1485
1486 let word_start = before_dot
1488 .rfind(|c: char| c.is_whitespace() || c == '>' || c == '<' || c == '-' || c == ':' || c == '(' || c == ',')
1489 .map(|pos| pos + 1)
1490 .unwrap_or(0);
1491
1492 let table_name = before_dot[word_start..].trim().to_string();
1493
1494 if !table_name.is_empty() && table_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
1495 return Some(table_name);
1496 }
1497 }
1498
1499 None
1500}
1501
1502fn extract_tables_from_content(content: &str) -> Vec<(String, Vec<(String, String)>)> {
1505 let mut tables = Vec::new();
1506 let mut current_table: Option<(String, Vec<(String, String)>)> = None;
1507 let mut brace_depth = 0;
1508
1509 for line in content.lines() {
1510 let trimmed = line.trim();
1511
1512 if trimmed.starts_with("Table ") {
1514 if let Some(table) = current_table.take() {
1516 tables.push(table);
1517 }
1518
1519 let parts: Vec<&str> = trimmed.split_whitespace().collect();
1521 if parts.len() >= 2 {
1522 let table_name = parts[1].trim_end_matches('{').to_string();
1523 current_table = Some((table_name, Vec::new()));
1524 }
1525 }
1526
1527 for ch in trimmed.chars() {
1529 match ch {
1530 '{' => brace_depth += 1,
1531 '}' => {
1532 brace_depth -= 1;
1533 if brace_depth == 0 {
1534 if let Some(table) = current_table.take() {
1536 tables.push(table);
1537 }
1538 }
1539 }
1540 _ => {}
1541 }
1542 }
1543
1544 if let Some((_, ref mut columns)) = current_table {
1546 if brace_depth == 1 && !trimmed.starts_with("Table ") && !trimmed.starts_with("indexes") && !trimmed.is_empty() {
1547 let parts: Vec<&str> = trimmed.split_whitespace().collect();
1549 if parts.len() >= 2 {
1550 let col_name = parts[0].to_string();
1551 let col_type = parts[1].to_string();
1552
1553 if col_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
1555 columns.push((col_name, col_type));
1556 }
1557 }
1558 }
1559 }
1560 }
1561
1562 if let Some(table) = current_table {
1564 tables.push(table);
1565 }
1566
1567 tables
1568}
1569
1570