dbml_language_server/
state.rs

1// src/state.rs
2use crate::ast::Document;
3use crate::analysis::semantic::SemanticModel;
4use lsp_types::{Diagnostic, Url};
5
6#[derive(Debug)]
7#[allow(dead_code)]
8pub struct DocumentState {
9    pub uri: Url,
10    pub content: String,
11    pub version: i32,
12    pub ast: Option<Document>,
13    pub semantic_model: Option<SemanticModel>,
14    pub diagnostics: Vec<Diagnostic>,
15}
16
17impl DocumentState {
18    pub fn new(uri: Url, content: String, version: i32) -> Self {
19        Self {
20            uri,
21            content,
22            version,
23            ast: None,
24            semantic_model: None,
25            diagnostics: Vec::new(),
26        }
27    }
28
29    pub fn update_content(&mut self, content: String, version: i32) {
30        self.content = content;
31        self.version = version;
32    }
33
34    pub fn analyze(&mut self) {
35        use crate::analysis::parser::parse;
36
37        // Parse the content
38        match parse(&self.content) {
39            Ok(ast) => {
40                // Perform semantic analysis
41                let semantic_model = SemanticModel::analyze(&ast);
42
43                // Convert semantic errors to diagnostics
44                self.diagnostics = semantic_model.to_diagnostics(&self.content);
45
46                self.ast = Some(ast);
47                self.semantic_model = Some(semantic_model);
48            }
49            Err(parse_errors) => {
50                // Convert parse errors to diagnostics
51                self.diagnostics = parse_errors
52                    .iter()
53                    .map(|err| {
54                        // Extract span from the parse error
55                        let (start, end, message) = match err {
56                            crate::analysis::parser::Error::LexError(simple_err) => {
57                                let span = simple_err.span();
58                                let message = format!("Lexer error: unexpected character");
59                                (span.start, span.end, message)
60                            }
61                            crate::analysis::parser::Error::ParseError(simple_err) => {
62                                let span = simple_err.span();
63                                let start = span.start;
64                                let end = span.end;
65                                
66                                // Build a user-friendly error message
67                                let message = match simple_err.reason() {
68                                    chumsky::error::SimpleReason::Unexpected => {
69                                        let found = simple_err.found()
70                                            .map(|t| format!("{:?}", t))
71                                            .unwrap_or_else(|| "end of file".to_string());
72                                        
73                                        let expected: Vec<String> = simple_err.expected()
74                                            .filter_map(|opt| {
75                                                opt.as_ref().map(|t| format!("{:?}", t))
76                                            })
77                                            .collect();
78                                        
79                                        if expected.is_empty() {
80                                            format!("Unexpected token: {}", found)
81                                        } else if expected.len() == 1 {
82                                            format!("Expected {}, but found {}", expected[0], found)
83                                        } else {
84                                            format!("Expected one of: {}, but found {}", 
85                                                expected.join(", "), found)
86                                        }
87                                    }
88                                    chumsky::error::SimpleReason::Unclosed { span, delimiter } => {
89                                        format!("Unclosed delimiter {:?} at position {}..{}", 
90                                            delimiter, span.start, span.end)
91                                    }
92                                    chumsky::error::SimpleReason::Custom(msg) => msg.clone(),
93                                };
94                                
95                                (start, end, message)
96                            }
97                        };
98
99                        // Convert byte offsets to LSP positions
100                        let start_pos = offset_to_position(start, &self.content);
101                        let end_pos = offset_to_position(end, &self.content);
102
103                        Diagnostic {
104                            range: lsp_types::Range::new(start_pos, end_pos),
105                            severity: Some(lsp_types::DiagnosticSeverity::ERROR),
106                            code: None,
107                            code_description: None,
108                            source: Some("dbml-lsp".to_string()),
109                            message,
110                            related_information: None,
111                            tags: None,
112                            data: None,
113                        }
114                    })
115                    .collect();
116
117                self.ast = None;
118                self.semantic_model = None;
119            }
120        }
121    }
122
123    /// Get hover information for the symbol at the given position
124    pub fn get_hover_info(&self, position: lsp_types::Position) -> Option<String> {
125        let ast = self.ast.as_ref()?;
126        let offset = position_to_offset(&position, &self.content);
127
128        // Find what symbol is at this position
129        for item in &ast.items {
130            match item {
131                crate::ast::DocumentItem::Table(table) => {
132                    // Check if hovering over table name
133                    if offset >= table.name.span.start && offset <= table.name.span.end {
134                        return Some(format_table_hover(table));
135                    }
136                    
137                    // Check if hovering over a column
138                    for table_item in &table.items {
139                        if let crate::ast::TableItem::Column(column) = table_item {
140                            if offset >= column.name.span.start && offset <= column.name.span.end {
141                                return Some(format_column_hover(column, &table.name.name));
142                            }
143                            
144                            // Check if hovering over column type (might be an enum)
145                            if offset >= column.col_type_span.start && offset <= column.col_type_span.end {
146                                // Try to find an enum with this name
147                                if let Some(enum_def) = find_enum_by_name(ast, &column.col_type) {
148                                    return Some(format_enum_hover(enum_def));
149                                }
150                                // Otherwise, just show the type
151                                return Some(format!("### Type: `{}`", column.col_type));
152                            }
153                        }
154                        
155                        // Check if hovering over indexes
156                        if let crate::ast::TableItem::Indexes(indexes_block) = table_item {
157                            for index in &indexes_block.indexes {
158                                if offset >= index.span.start && offset <= index.span.end {
159                                    return Some(format_index_hover(index, &table.name.name));
160                                }
161                            }
162                        }
163                    }
164                }
165                crate::ast::DocumentItem::Enum(enum_def) => {
166                    // Check if hovering over enum name
167                    if offset >= enum_def.name.span.start && offset <= enum_def.name.span.end {
168                        return Some(format_enum_hover(enum_def));
169                    }
170                    
171                    // Check if hovering over an enum member
172                    for member in &enum_def.members {
173                        if offset >= member.name.span.start && offset <= member.name.span.end {
174                            return Some(format_enum_member_hover(member, &enum_def.name.name));
175                        }
176                    }
177                }
178                crate::ast::DocumentItem::Project(project) => {
179                    // Check if hovering over project name
180                    if let Some(name) = &project.name {
181                        if offset >= name.span.start && offset <= name.span.end {
182                            return Some(format_project_hover(project));
183                        }
184                    }
185                    // Check if hovering anywhere in the project span
186                    if offset >= project.span.start && offset <= project.span.end {
187                        return Some(format_project_hover(project));
188                    }
189                }
190                crate::ast::DocumentItem::Ref(ref_def) => {
191                    // Check if hovering over a table name within the reference
192                    if offset >= ref_def.from_table.span.start && offset <= ref_def.from_table.span.end {
193                        // Find the table definition
194                        if let Some(table) = find_table_by_name(ast, &ref_def.from_table.name) {
195                            return Some(format_table_hover_with_ref_context(table, ref_def, true));
196                        }
197                    }
198                    
199                    if offset >= ref_def.to_table.span.start && offset <= ref_def.to_table.span.end {
200                        // Find the table definition
201                        if let Some(table) = find_table_by_name(ast, &ref_def.to_table.name) {
202                            return Some(format_table_hover_with_ref_context(table, ref_def, false));
203                        }
204                    }
205                    
206                    // Check if hovering anywhere else in the reference
207                    if offset >= ref_def.span.start && offset <= ref_def.span.end {
208                        return Some(format_ref_hover(ref_def));
209                    }
210                }
211            }
212        }
213
214        None
215    }
216}
217
218/// Find a table by name in the document
219fn find_table_by_name<'a>(ast: &'a crate::ast::Document, name: &str) -> Option<&'a crate::ast::Table> {
220    for item in &ast.items {
221        if let crate::ast::DocumentItem::Table(table) = item {
222            if table.name.name == name {
223                return Some(table);
224            }
225        }
226    }
227    None
228}
229
230/// Find an enum by name in the document
231fn find_enum_by_name<'a>(ast: &'a crate::ast::Document, name: &str) -> Option<&'a crate::ast::Enum> {
232    for item in &ast.items {
233        if let crate::ast::DocumentItem::Enum(enum_def) = item {
234            if enum_def.name.name == name {
235                return Some(enum_def);
236            }
237        }
238    }
239    None
240}
241
242/// Convert LSP Position to byte offset
243fn position_to_offset(position: &lsp_types::Position, content: &str) -> usize {
244    let mut line = 0;
245    let mut col = 0;
246    let mut offset = 0;
247
248    for ch in content.chars() {
249        if line == position.line && col == position.character {
250            break;
251        }
252
253        if ch == '\n' {
254            line += 1;
255            col = 0;
256        } else {
257            col += 1;
258        }
259        
260        offset += ch.len_utf8();
261    }
262
263    offset
264}
265
266/// Format hover content for a table
267fn format_table_hover(table: &crate::ast::Table) -> String {
268    let mut hover = String::new();
269    
270    // Header
271    hover.push_str(&format!("### Table: `{}`\n\n", table.name.name));
272    
273    if let Some(alias) = &table.alias {
274        hover.push_str(&format!("**Alias:** `{}`\n\n", alias.name));
275    }
276    
277    // Build DBML code block
278    let mut dbml_code = format!("Table {} {{\n", table.name.name);
279    
280    for item in &table.items {
281        if let crate::ast::TableItem::Column(column) = item {
282            dbml_code.push_str(&format!("  {} {}", column.name.name, column.col_type));
283            
284            // Add settings
285            let mut settings = Vec::new();
286            for setting in &column.settings {
287                match setting {
288                    crate::ast::ColumnSetting::PrimaryKey => settings.push("pk".to_string()),
289                    crate::ast::ColumnSetting::NotNull => settings.push("not null".to_string()),
290                    crate::ast::ColumnSetting::Null => settings.push("null".to_string()),
291                    crate::ast::ColumnSetting::Unique => settings.push("unique".to_string()),
292                    crate::ast::ColumnSetting::Increment => settings.push("increment".to_string()),
293                    crate::ast::ColumnSetting::Default(val) => {
294                        match val {
295                            crate::ast::DefaultValue::String(s) => settings.push(format!("default: '{}'", s)),
296                            crate::ast::DefaultValue::Number(n) => settings.push(format!("default: {}", n)),
297                            crate::ast::DefaultValue::Boolean(b) => settings.push(format!("default: {}", b)),
298                            crate::ast::DefaultValue::Expression(e) => settings.push(format!("default: `{}`", e)),
299                        }
300                    },
301                    crate::ast::ColumnSetting::Ref(ref_def) => {
302                        let rel_symbol = match ref_def.relationship {
303                            crate::ast::RelationshipType::OneToOne => "-",
304                            crate::ast::RelationshipType::OneToMany => "<",
305                            crate::ast::RelationshipType::ManyToOne => ">",
306                            crate::ast::RelationshipType::ManyToMany => "<>",
307                        };
308                        settings.push(format!("ref: {} {}.{}", rel_symbol, ref_def.target_table.name, ref_def.target_column.name));
309                    }
310                    crate::ast::ColumnSetting::Note(note) => settings.push(format!("note: '{}'", note.value)),
311                }
312            }
313            
314            if !settings.is_empty() {
315                dbml_code.push_str(&format!(" [{}]", settings.join(", ")));
316            }
317            
318            dbml_code.push('\n');
319        }
320    }
321    
322    dbml_code.push_str("}");
323    
324    // Add syntax-highlighted code block
325    hover.push_str("```dbml\n");
326    hover.push_str(&dbml_code);
327    hover.push_str("\n```\n");
328    
329    // Add note if present
330    for item in &table.items {
331        if let crate::ast::TableItem::Note(note) = item {
332            hover.push_str(&format!("\n**Note:** {}", note.value));
333        }
334    }
335    
336    hover
337}
338
339/// Format hover content for a table with reference context
340fn format_table_hover_with_ref_context(table: &crate::ast::Table, ref_def: &crate::ast::Ref, is_source: bool) -> String {
341    let mut hover = String::new();
342    
343    // Show reference context first
344    let rel_type = match ref_def.relationship {
345        crate::ast::RelationshipType::OneToOne => "One-to-One",
346        crate::ast::RelationshipType::OneToMany => "One-to-Many",
347        crate::ast::RelationshipType::ManyToOne => "Many-to-One",
348        crate::ast::RelationshipType::ManyToMany => "Many-to-Many",
349    };
350    
351    let role = if is_source { "Source" } else { "Target" };
352    hover.push_str(&format!("### {} Table in {} Relationship\n\n", role, rel_type));
353    
354    // Show the reference in a code block
355    let rel_symbol = match ref_def.relationship {
356        crate::ast::RelationshipType::OneToOne => "-",
357        crate::ast::RelationshipType::OneToMany => "<",
358        crate::ast::RelationshipType::ManyToOne => ">",
359        crate::ast::RelationshipType::ManyToMany => "<>",
360    };
361    
362    let mut ref_code = format!("Ref: {}.{} {} {}.{}",
363        ref_def.from_table.name,
364        ref_def.from_columns.iter().map(|c| c.name.as_str()).collect::<Vec<_>>().join(", "),
365        rel_symbol,
366        ref_def.to_table.name,
367        ref_def.to_columns.iter().map(|c| c.name.as_str()).collect::<Vec<_>>().join(", ")
368    );
369    
370    // Add referential actions if present
371    if ref_def.on_delete.is_some() || ref_def.on_update.is_some() {
372        let mut settings = Vec::new();
373        
374        if let Some(on_delete) = &ref_def.on_delete {
375            let action = match on_delete {
376                crate::ast::ReferentialAction::Cascade => "cascade",
377                crate::ast::ReferentialAction::Restrict => "restrict",
378                crate::ast::ReferentialAction::NoAction => "no action",
379                crate::ast::ReferentialAction::SetNull => "set null",
380                crate::ast::ReferentialAction::SetDefault => "set default",
381            };
382            settings.push(format!("delete: {}", action));
383        }
384        
385        if let Some(on_update) = &ref_def.on_update {
386            let action = match on_update {
387                crate::ast::ReferentialAction::Cascade => "cascade",
388                crate::ast::ReferentialAction::Restrict => "restrict",
389                crate::ast::ReferentialAction::NoAction => "no action",
390                crate::ast::ReferentialAction::SetNull => "set null",
391                crate::ast::ReferentialAction::SetDefault => "set default",
392            };
393            settings.push(format!("update: {}", action));
394        }
395        
396        ref_code.push_str(&format!(" [{}]", settings.join(", ")));
397    }
398    
399    hover.push_str("```dbml\n");
400    hover.push_str(&ref_code);
401    hover.push_str("\n```\n\n");
402    
403    hover.push_str("---\n\n");
404    
405    // Now show table details
406    hover.push_str(&format!("### Table: `{}`\n\n", table.name.name));
407    
408    if let Some(alias) = &table.alias {
409        hover.push_str(&format!("**Alias:** `{}`\n\n", alias.name));
410    }
411    
412    // Build DBML code block
413    let mut dbml_code = format!("Table {} {{\n", table.name.name);
414    
415    // Get columns involved in the relationship
416    let involved_columns: Vec<&str> = if is_source {
417        ref_def.from_columns.iter().map(|c| c.name.as_str()).collect()
418    } else {
419        ref_def.to_columns.iter().map(|c| c.name.as_str()).collect()
420    };
421    
422    for item in &table.items {
423        if let crate::ast::TableItem::Column(column) = item {
424            dbml_code.push_str(&format!("  {} {}", column.name.name, column.col_type));
425            
426            // Add settings
427            let mut settings = Vec::new();
428            for setting in &column.settings {
429                match setting {
430                    crate::ast::ColumnSetting::PrimaryKey => settings.push("pk".to_string()),
431                    crate::ast::ColumnSetting::NotNull => settings.push("not null".to_string()),
432                    crate::ast::ColumnSetting::Null => settings.push("null".to_string()),
433                    crate::ast::ColumnSetting::Unique => settings.push("unique".to_string()),
434                    crate::ast::ColumnSetting::Increment => settings.push("increment".to_string()),
435                    crate::ast::ColumnSetting::Default(val) => {
436                        match val {
437                            crate::ast::DefaultValue::String(s) => settings.push(format!("default: '{}'", s)),
438                            crate::ast::DefaultValue::Number(n) => settings.push(format!("default: {}", n)),
439                            crate::ast::DefaultValue::Boolean(b) => settings.push(format!("default: {}", b)),
440                            crate::ast::DefaultValue::Expression(e) => settings.push(format!("default: `{}`", e)),
441                        }
442                    },
443                    crate::ast::ColumnSetting::Ref(ref_def) => {
444                        let rel_symbol = match ref_def.relationship {
445                            crate::ast::RelationshipType::OneToOne => "-",
446                            crate::ast::RelationshipType::OneToMany => "<",
447                            crate::ast::RelationshipType::ManyToOne => ">",
448                            crate::ast::RelationshipType::ManyToMany => "<>",
449                        };
450                        settings.push(format!("ref: {} {}.{}", rel_symbol, ref_def.target_table.name, ref_def.target_column.name));
451                    }
452                    crate::ast::ColumnSetting::Note(note) => settings.push(format!("note: '{}'", note.value)),
453                }
454            }
455            
456            if !settings.is_empty() {
457                dbml_code.push_str(&format!(" [{}]", settings.join(", ")));
458            }
459            
460            // Mark relationship columns with a comment
461            if involved_columns.contains(&column.name.name.as_str()) {
462                dbml_code.push_str(" // Referenced column");
463            }
464            
465            dbml_code.push('\n');
466        }
467    }
468    
469    dbml_code.push_str("}");
470    
471    // Add syntax-highlighted code block
472    hover.push_str("```dbml\n");
473    hover.push_str(&dbml_code);
474    hover.push_str("\n```\n");
475    
476    // Add note if present
477    for item in &table.items {
478        if let crate::ast::TableItem::Note(note) = item {
479            hover.push_str(&format!("\n**Note:** {}", note.value));
480        }
481    }
482    
483    hover
484}
485
486/// Format hover content for a column
487fn format_column_hover(column: &crate::ast::Column, table_name: &str) -> String {
488    let mut hover = String::new();
489    
490    // Header
491    hover.push_str(&format!("### Column: `{}.{}`\n\n", table_name, column.name.name));
492    
493    // Build DBML code for the column
494    let mut dbml_code = format!("{} {}", column.name.name, column.col_type);
495    
496    let mut settings = Vec::new();
497    for setting in &column.settings {
498        match setting {
499            crate::ast::ColumnSetting::PrimaryKey => settings.push("pk".to_string()),
500            crate::ast::ColumnSetting::NotNull => settings.push("not null".to_string()),
501            crate::ast::ColumnSetting::Null => settings.push("null".to_string()),
502            crate::ast::ColumnSetting::Unique => settings.push("unique".to_string()),
503            crate::ast::ColumnSetting::Increment => settings.push("increment".to_string()),
504            crate::ast::ColumnSetting::Default(val) => {
505                match val {
506                    crate::ast::DefaultValue::String(s) => settings.push(format!("default: '{}'", s)),
507                    crate::ast::DefaultValue::Number(n) => settings.push(format!("default: {}", n)),
508                    crate::ast::DefaultValue::Boolean(b) => settings.push(format!("default: {}", b)),
509                    crate::ast::DefaultValue::Expression(e) => settings.push(format!("default: `{}`", e)),
510                }
511            }
512            crate::ast::ColumnSetting::Note(note) => {
513                settings.push(format!("note: '{}'", note.value));
514            }
515            crate::ast::ColumnSetting::Ref(ref_def) => {
516                let rel_symbol = match ref_def.relationship {
517                    crate::ast::RelationshipType::OneToOne => "-",
518                    crate::ast::RelationshipType::OneToMany => "<",
519                    crate::ast::RelationshipType::ManyToOne => ">",
520                    crate::ast::RelationshipType::ManyToMany => "<>",
521                };
522                settings.push(format!("ref: {} {}.{}", rel_symbol, ref_def.target_table.name, ref_def.target_column.name));
523            }
524        }
525    }
526    
527    if !settings.is_empty() {
528        dbml_code.push_str(&format!(" [{}]", settings.join(", ")));
529    }
530    
531    // Add syntax-highlighted code block
532    hover.push_str("```dbml\n");
533    hover.push_str(&dbml_code);
534    hover.push_str("\n```");
535    
536    hover
537}
538
539/// Format hover content for an enum
540fn format_enum_hover(enum_def: &crate::ast::Enum) -> String {
541    let mut hover = String::new();
542    
543    // Header
544    hover.push_str(&format!("### Enum: `{}`\n\n", enum_def.name.name));
545    
546    // Build DBML code block
547    let mut dbml_code = format!("enum {} {{\n", enum_def.name.name);
548    
549    for member in &enum_def.members {
550        dbml_code.push_str(&format!("  {}", member.name.name));
551        if let Some(note) = &member.note {
552            dbml_code.push_str(&format!(" [note: '{}']", note.value));
553        }
554        dbml_code.push('\n');
555    }
556    
557    dbml_code.push('}');
558    
559    // Add syntax-highlighted code block
560    hover.push_str("```dbml\n");
561    hover.push_str(&dbml_code);
562    hover.push_str("\n```");
563    
564    hover
565}
566
567/// Format hover content for an enum member
568fn format_enum_member_hover(member: &crate::ast::EnumMember, enum_name: &str) -> String {
569    let mut hover = String::new();
570    
571    hover.push_str(&format!("### Enum Value: `{}.{}`\n\n", enum_name, member.name.name));
572    
573    if let Some(note) = &member.note {
574        hover.push_str(&format!("**Note:** {}", note.value));
575    }
576    
577    hover
578}
579
580/// Format hover content for an index
581fn format_index_hover(index: &crate::ast::Index, table_name: &str) -> String {
582    let mut hover = String::new();
583    
584    // Determine index type from settings
585    let mut index_type = "Index";
586    let mut index_name = None;
587    
588    for setting in &index.settings {
589        match setting {
590            crate::ast::IndexSetting::PrimaryKey => index_type = "Primary Key Index",
591            crate::ast::IndexSetting::Unique => index_type = "Unique Index",
592            crate::ast::IndexSetting::Name(name) => index_name = Some(name.clone()),
593            _ => {}
594        }
595    }
596    
597    // Header
598    if let Some(name) = &index_name {
599        hover.push_str(&format!("### {}: `{}`\n\n", index_type, name));
600    } else {
601        hover.push_str(&format!("### {} on `{}`\n\n", index_type, table_name));
602    }
603    
604    // Build DBML code for the index
605    let mut dbml_code = String::from("(");
606    
607    // Add columns
608    let column_strs: Vec<String> = index.columns.iter().map(|col| {
609        match col {
610            crate::ast::IndexColumn::Simple(ident) => ident.name.clone(),
611            crate::ast::IndexColumn::Expression(expr) => format!("`{}`", expr),
612        }
613    }).collect();
614    
615    dbml_code.push_str(&column_strs.join(", "));
616    dbml_code.push(')');
617    
618    // Add settings
619    if !index.settings.is_empty() {
620        let mut settings_str = Vec::new();
621        
622        for setting in &index.settings {
623            match setting {
624                crate::ast::IndexSetting::PrimaryKey => settings_str.push("pk".to_string()),
625                crate::ast::IndexSetting::Unique => settings_str.push("unique".to_string()),
626                crate::ast::IndexSetting::Name(name) => settings_str.push(format!("name: '{}'", name)),
627                crate::ast::IndexSetting::Type(idx_type) => settings_str.push(format!("type: {}", idx_type)),
628            }
629        }
630        
631        if !settings_str.is_empty() {
632            dbml_code.push_str(&format!(" [{}]", settings_str.join(", ")));
633        }
634    }
635    
636    // Add syntax-highlighted code block
637    hover.push_str("```dbml\n");
638    hover.push_str(&dbml_code);
639    hover.push_str("\n```\n\n");
640    
641    // Add description
642    hover.push_str(&format!("**Columns:** {}", column_strs.join(", ")));
643    
644    hover
645}
646
647/// Format hover content for a reference
648fn format_ref_hover(ref_def: &crate::ast::Ref) -> String {
649    let mut hover = String::new();
650    
651    // Header with relationship type
652    let rel_type = match ref_def.relationship {
653        crate::ast::RelationshipType::OneToOne => "One-to-One",
654        crate::ast::RelationshipType::OneToMany => "One-to-Many",
655        crate::ast::RelationshipType::ManyToOne => "Many-to-One",
656        crate::ast::RelationshipType::ManyToMany => "Many-to-Many",
657    };
658    
659    hover.push_str(&format!("### {} Relationship\n\n", rel_type));
660    
661    if let Some(name) = &ref_def.name {
662        hover.push_str(&format!("**Name:** `{}`\n\n", name.name));
663    }
664    
665    // Build DBML code for the reference
666    let rel_symbol = match ref_def.relationship {
667        crate::ast::RelationshipType::OneToOne => "-",
668        crate::ast::RelationshipType::OneToMany => "<",
669        crate::ast::RelationshipType::ManyToOne => ">",
670        crate::ast::RelationshipType::ManyToMany => "<>",
671    };
672    
673    let mut dbml_code = String::from("Ref");
674    
675    if let Some(name) = &ref_def.name {
676        dbml_code.push_str(&format!(": {}", name.name));
677    }
678    
679    dbml_code.push_str(&format!(": {}.{} {} {}.{}",
680        ref_def.from_table.name,
681        ref_def.from_columns.iter()
682            .map(|c| c.name.as_str())
683            .collect::<Vec<_>>()
684            .join(", "),
685        rel_symbol,
686        ref_def.to_table.name,
687        ref_def.to_columns.iter()
688            .map(|c| c.name.as_str())
689            .collect::<Vec<_>>()
690            .join(", ")
691    ));
692    
693    // Add relationship settings if present
694    if ref_def.on_delete.is_some() || ref_def.on_update.is_some() {
695        let mut settings = Vec::new();
696        
697        if let Some(on_delete) = &ref_def.on_delete {
698            let action = match on_delete {
699                crate::ast::ReferentialAction::Cascade => "cascade",
700                crate::ast::ReferentialAction::Restrict => "restrict",
701                crate::ast::ReferentialAction::NoAction => "no action",
702                crate::ast::ReferentialAction::SetNull => "set null",
703                crate::ast::ReferentialAction::SetDefault => "set default",
704            };
705            settings.push(format!("delete: {}", action));
706        }
707        
708        if let Some(on_update) = &ref_def.on_update {
709            let action = match on_update {
710                crate::ast::ReferentialAction::Cascade => "cascade",
711                crate::ast::ReferentialAction::Restrict => "restrict",
712                crate::ast::ReferentialAction::NoAction => "no action",
713                crate::ast::ReferentialAction::SetNull => "set null",
714                crate::ast::ReferentialAction::SetDefault => "set default",
715            };
716            settings.push(format!("update: {}", action));
717        }
718        
719        dbml_code.push_str(&format!(" [{}]", settings.join(", ")));
720    }
721    
722    // Add syntax-highlighted code block
723    hover.push_str("```dbml\n");
724    hover.push_str(&dbml_code);
725    hover.push_str("\n```");
726    
727    hover
728}
729
730/// Format hover content for a project
731fn format_project_hover(project: &crate::ast::Project) -> String {
732    let mut hover = String::new();
733    
734    // Header
735    if let Some(name) = &project.name {
736        hover.push_str(&format!("### Project: `{}`\n\n", name.name));
737    } else {
738        hover.push_str("### Project\n\n");
739    }
740    
741    // Build DBML code block
742    let mut dbml_code = String::from("Project");
743    
744    if let Some(name) = &project.name {
745        dbml_code.push_str(&format!(" {}", name.name));
746    }
747    
748    dbml_code.push_str(" {\n");
749    
750    for setting in &project.settings {
751        match setting {
752            crate::ast::ProjectSetting::DatabaseType(db_type) => {
753                dbml_code.push_str(&format!("  database_type: '{}'\n", db_type));
754            }
755            crate::ast::ProjectSetting::Note(note) => {
756                dbml_code.push_str(&format!("  Note: '{}'\n", note.value));
757            }
758        }
759    }
760    
761    dbml_code.push('}');
762    
763    // Add syntax-highlighted code block
764    hover.push_str("```dbml\n");
765    hover.push_str(&dbml_code);
766    hover.push_str("\n```");
767    
768    hover
769}
770
771// Remove the now-unused format_referential_action function since we inline the text in the code
772
773/// Convert a byte offset to an LSP Position (line and character)
774fn offset_to_position(offset: usize, content: &str) -> lsp_types::Position {
775    let mut line = 0;
776    let mut col = 0;
777    let mut current_offset = 0;
778
779    for ch in content.chars() {
780        if current_offset >= offset {
781            break;
782        }
783
784        if ch == '\n' {
785            line += 1;
786            col = 0;
787        } else {
788            col += 1;
789        }
790        
791        current_offset += ch.len_utf8();
792    }
793
794    lsp_types::Position::new(line, col)
795}