1use 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 match parse(&self.content) {
39 Ok(ast) => {
40 let semantic_model = SemanticModel::analyze(&ast);
42
43 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 self.diagnostics = parse_errors
52 .iter()
53 .map(|err| {
54 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 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 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 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 for item in &ast.items {
130 match item {
131 crate::ast::DocumentItem::Table(table) => {
132 if offset >= table.name.span.start && offset <= table.name.span.end {
134 return Some(format_table_hover(table));
135 }
136
137 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 if offset >= column.col_type_span.start && offset <= column.col_type_span.end {
146 if let Some(enum_def) = find_enum_by_name(ast, &column.col_type) {
148 return Some(format_enum_hover(enum_def));
149 }
150 return Some(format!("### Type: `{}`", column.col_type));
152 }
153 }
154
155 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 if offset >= enum_def.name.span.start && offset <= enum_def.name.span.end {
168 return Some(format_enum_hover(enum_def));
169 }
170
171 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 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 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 if offset >= ref_def.from_table.span.start && offset <= ref_def.from_table.span.end {
193 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 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 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
218fn 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
230fn 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
242fn 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
266fn format_table_hover(table: &crate::ast::Table) -> String {
268 let mut hover = String::new();
269
270 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 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 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 hover.push_str("```dbml\n");
326 hover.push_str(&dbml_code);
327 hover.push_str("\n```\n");
328
329 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
339fn 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 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 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 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 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 let mut dbml_code = format!("Table {} {{\n", table.name.name);
414
415 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 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 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 hover.push_str("```dbml\n");
473 hover.push_str(&dbml_code);
474 hover.push_str("\n```\n");
475
476 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
486fn format_column_hover(column: &crate::ast::Column, table_name: &str) -> String {
488 let mut hover = String::new();
489
490 hover.push_str(&format!("### Column: `{}.{}`\n\n", table_name, column.name.name));
492
493 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 hover.push_str("```dbml\n");
533 hover.push_str(&dbml_code);
534 hover.push_str("\n```");
535
536 hover
537}
538
539fn format_enum_hover(enum_def: &crate::ast::Enum) -> String {
541 let mut hover = String::new();
542
543 hover.push_str(&format!("### Enum: `{}`\n\n", enum_def.name.name));
545
546 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 hover.push_str("```dbml\n");
561 hover.push_str(&dbml_code);
562 hover.push_str("\n```");
563
564 hover
565}
566
567fn 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
580fn format_index_hover(index: &crate::ast::Index, table_name: &str) -> String {
582 let mut hover = String::new();
583
584 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 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 let mut dbml_code = String::from("(");
606
607 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 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 hover.push_str("```dbml\n");
638 hover.push_str(&dbml_code);
639 hover.push_str("\n```\n\n");
640
641 hover.push_str(&format!("**Columns:** {}", column_strs.join(", ")));
643
644 hover
645}
646
647fn format_ref_hover(ref_def: &crate::ast::Ref) -> String {
649 let mut hover = String::new();
650
651 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 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 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 hover.push_str("```dbml\n");
724 hover.push_str(&dbml_code);
725 hover.push_str("\n```");
726
727 hover
728}
729
730fn format_project_hover(project: &crate::ast::Project) -> String {
732 let mut hover = String::new();
733
734 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 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 hover.push_str("```dbml\n");
765 hover.push_str(&dbml_code);
766 hover.push_str("\n```");
767
768 hover
769}
770
771fn 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}