dbml_language_server/
server.rs

1// src/server.rs
2use 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(&params.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                    // Use the new find_definition_at_position method
162                    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                // Convert LSP FormattingOptions to our FormattingOptions
234                let formatting_options = convert_lsp_formatting_options(&params.options);
235                let formatted = crate::formatter::format_document(ast, &formatting_options);
236
237                // Create a text edit that replaces the entire document
238                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        // For range formatting, we'll format the entire document
256        // A more sophisticated implementation could format only the selected range
257        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                // Convert LSP FormattingOptions to our FormattingOptions
262                let formatting_options = convert_lsp_formatting_options(&params.options);
263                let formatted = crate::formatter::format_document(ast, &formatting_options);
264
265                // Create a text edit that replaces the entire document
266                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            // Get the current word being typed
291            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            // Get the line content up to the cursor
298            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            // Analyze the semantic context at this position
311            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            // Generate completions based on context
319            match context {
320                CompletionContext::TopLevel => {
321                    // Top-level keywords
322                    if let Some(item) = create_scored_completion_item("Table", CompletionItemKind::CLASS, "Define a new table", &current_word) {
323                        scored_completions.push(item);
324                    }
325                    if let Some(item) = create_scored_completion_item("enum", CompletionItemKind::ENUM, "Define an enum type", &current_word) {
326                        scored_completions.push(item);
327                    }
328                    if let Some(item) = create_scored_completion_item("Ref", CompletionItemKind::REFERENCE, "Define a relationship", &current_word) {
329                        scored_completions.push(item);
330                    }
331                    if let Some(item) = create_scored_completion_item("Project", CompletionItemKind::MODULE, "Define project settings", &current_word) {
332                        scored_completions.push(item);
333                    }
334                }
335                CompletionContext::TableBlock(table_name) => {
336                    // Data types
337                    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, &current_word) {
362                            scored_completions.push(item);
363                        }
364                    }
365                    
366                    // Add enum types with higher priority
367                    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                                    &current_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, &current_word) {
397                            scored_completions.push(item);
398                        }
399                    }
400                }
401                CompletionContext::RefSettings => {
402                    // Reference settings like update: and delete:
403                    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, &current_word) {
410                            scored_completions.push(item);
411                        }
412                    }
413                }
414                CompletionContext::RefAction => {
415                    // Actions for update: and delete:
416                    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, &current_word) {
426                            scored_completions.push(item);
427                        }
428                    }
429                }
430                CompletionContext::IndexesBlock(table_name) => {
431                    // Suggest columns from the current table
432                    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                                        &current_word,
441                                    ) {
442                                        scored_item.1 += 200;
443                                        scored_completions.push(scored_item);
444                                    }
445                                }
446                            }
447                        }
448                    } else {
449                        // No AST - extract from content
450                        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                                        &current_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                    // Suggest columns from specific table
472                    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                                        &current_word,
481                                    ) {
482                                        scored_item.1 += 300;
483                                        scored_completions.push(scored_item);
484                                    }
485                                }
486                            }
487                        }
488                    } else {
489                        // No AST - extract from content
490                        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                                        &current_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                    // Table names and table.column combinations
512                    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                                    &current_word,
520                                ) {
521                                    scored_completions.push(scored_item);
522                                }
523                                
524                                // Also suggest table.column combinations
525                                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                                            &current_word,
533                                        ) {
534                                            scored_completions.push(scored_item);
535                                        }
536                                    }
537                                }
538                            }
539                        }
540                    } else {
541                        // No AST available - parse tables from content directly
542                        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                                &current_word,
553                            ) {
554                                scored_completions.push(scored_item);
555                            }
556                            
557                            // Also suggest table.column combinations
558                            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                                    &current_word,
565                                ) {
566                                    scored_completions.push(scored_item);
567                                }
568                            }
569                        }
570                    }
571                    
572                    // Relationship operators
573                    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, &current_word) {
582                            scored_completions.push(item);
583                        }
584                    }
585                }
586                CompletionContext::DefaultValue(col_type) => {
587                    // Type-specific default values
588                    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                            &current_word,
627                        ) {
628                            scored_completions.push(scored_item);
629                        }
630                    }
631                    
632                    // Check if column type is an enum
633                    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                                    // Suggest enum values
638                                    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                                            &current_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                // Sort by score (highest first)
663                scored_completions.sort_by(|a, b| b.1.cmp(&a.1));
664                
665                // Extract just the completion items
666                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
743// Fuzzy matching score - higher is better
744fn 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    // Exact match gets highest score
753    if text_lower == pattern_lower {
754        return Some(1000);
755    }
756    
757    // Starts with gets high score
758    if text_lower.starts_with(&pattern_lower) {
759        return Some(900 - pattern.len() as i32);
760    }
761    
762    // Contains gets medium score
763    if text_lower.contains(&pattern_lower) {
764        return Some(500);
765    }
766    
767    // Fuzzy match - all characters must appear in order
768    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; // Penalty for gaps
780                break;
781            }
782            text_idx += 1;
783            score -= 2; // Larger penalty for skipped characters
784        }
785        
786        if !found {
787            return None; // Pattern doesn't match
788        }
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)), // Lower sort_text appears first
810        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                // Add columns as children
999                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
1159// Helper function to check if cursor is inside a table block
1160fn is_in_table_block(content: &str, offset: usize) -> bool {
1161    let before = &content[..offset];
1162    let after = &content[offset..];
1163    
1164    // Count opening and closing braces before cursor
1165    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
1188// Helper function to check if cursor is inside an enum block
1189fn 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// Completion context types
1216#[derive(Debug)]
1217enum CompletionContext {
1218    TopLevel,
1219    TableBlock(String),              // Inside a table block (table name)
1220    ColumnSettings,                   // Inside column settings [...]
1221    IndexesBlock(String),             // Inside indexes { } (table name)
1222    AfterDot(String),                 // After table_name. (table name)
1223    RefContext,                       // In a Ref or ref: context
1224    DefaultValue(String),             // After default: (column type)
1225    RefSettings,                      // Inside Ref { } or after ref:, for update:/delete:
1226    RefAction,                        // After update: or delete:, for actions
1227}
1228
1229// Analyze the completion context at the given position
1230fn analyze_completion_context(
1231    content: &str,
1232    offset: usize,
1233    line_content: &str,
1234    ast: &Option<crate::ast::Document>,
1235) -> CompletionContext {
1236    // Check for dot notation first (highest priority)
1237    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    // Check for reference action context (after update: or delete:)
1244    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    // Check for reference settings context (inside Ref { } or after ref: with comma)
1258    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        // Check if we're not already after update: or delete:
1263        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    // Check for default value context
1271    if line_content.contains("default:") && line_content.contains('[') {
1272        // Try to extract column type
1273        if let Some(col_type) = extract_column_type_from_line(content, offset) {
1274            return CompletionContext::DefaultValue(col_type);
1275        }
1276    }
1277    
1278    // Check if inside column settings bracket
1279    if line_content.contains('[') && !line_content.contains(']') {
1280        return CompletionContext::ColumnSettings;
1281    }
1282    
1283    // Check for ref context
1284    if line_content.contains("ref:") || line_content.contains("Ref") {
1285        return CompletionContext::RefContext;
1286    }
1287    
1288    // Check if inside indexes block
1289    if let Some(table_name) = find_table_name_for_indexes_block(content, offset) {
1290        return CompletionContext::IndexesBlock(table_name);
1291    }
1292    
1293    // Check if inside a table block
1294    if let Some(table_name) = find_current_table_name(content, offset, ast) {
1295        // Not in settings, not in indexes - must be defining columns
1296        if !line_content.contains('[') && !line_content.contains("indexes") {
1297            return CompletionContext::TableBlock(table_name);
1298        }
1299    }
1300    
1301    // Default to top-level
1302    CompletionContext::TopLevel
1303}
1304
1305// Extract column type from the current line
1306fn extract_column_type_from_line(content: &str, offset: usize) -> Option<String> {
1307    // Find the line start
1308    let line_start = content[..offset].rfind('\n').map(|pos| pos + 1).unwrap_or(0);
1309    let line = &content[line_start..offset];
1310    
1311    // Parse: column_name TYPE [settings]
1312    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
1320// Find table name for indexes block context
1321fn 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        // Track table name
1332        if trimmed.starts_with("Table ") {
1333            // Extract table name
1334            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        // Check if we're entering an indexes block
1341        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
1366// Find current table name from AST or content
1367fn find_current_table_name(
1368    content: &str,
1369    offset: usize,
1370    ast: &Option<crate::ast::Document>,
1371) -> Option<String> {
1372    // First try using AST
1373    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    // Fallback: parse from content
1380    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
1417// Helper to find table by name in AST
1418fn 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
1429// Check if we're inside a Ref block
1430fn 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        // Check if starting a Ref block
1439        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        // Track brace depth
1447        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
1464// Helper function to find the current table the cursor is in
1465fn 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
1479// Helper function to extract table name before dot for table.column completion
1480fn extract_table_before_dot(line: &str) -> Option<String> {
1481    // Find the last dot
1482    if let Some(dot_pos) = line.rfind('.') {
1483        // Get text before the dot
1484        let before_dot = &line[..dot_pos];
1485        
1486        // Extract the identifier immediately before the dot
1487        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
1502// Extract tables and columns from content using simple text parsing
1503// This is a fallback when AST parsing fails (e.g., incomplete syntax)
1504fn 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        // Detect table definition
1513        if trimmed.starts_with("Table ") {
1514            // Save previous table if any
1515            if let Some(table) = current_table.take() {
1516                tables.push(table);
1517            }
1518            
1519            // Extract table name
1520            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        // Track braces
1528        for ch in trimmed.chars() {
1529            match ch {
1530                '{' => brace_depth += 1,
1531                '}' => {
1532                    brace_depth -= 1;
1533                    if brace_depth == 0 {
1534                        // End of table
1535                        if let Some(table) = current_table.take() {
1536                            tables.push(table);
1537                        }
1538                    }
1539                }
1540                _ => {}
1541            }
1542        }
1543        
1544        // Extract column if we're in a table
1545        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                // Try to parse as column: name type [settings]
1548                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                    // Only add if it looks like a valid identifier
1554                    if col_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
1555                        columns.push((col_name, col_type));
1556                    }
1557                }
1558            }
1559        }
1560    }
1561    
1562    // Don't forget the last table
1563    if let Some(table) = current_table {
1564        tables.push(table);
1565    }
1566    
1567    tables
1568}
1569
1570// Old helper functions (kept for backward compatibility where needed)
1571