1use lsp_types::{
4 Diagnostic, DiagnosticSeverity, Hover, HoverContents, MarkupContent, MarkupKind, Position,
5 Range,
6};
7use std::collections::HashMap;
8use taplo::dom::node::IntegerValue;
9
10use crate::schema::{PropertySchema, SchemaProvider, TypeInfo};
11
12#[derive(Debug, Clone)]
16pub struct TomlDocument {
17 pub root: taplo::dom::Node,
19 pub env_vars: Vec<EnvVarReference>,
21 pub config_sections: HashMap<String, ConfigSection>,
23 pub content: String,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct EnvVarReference {
32 pub name: String,
34 pub default: Option<String>,
36 pub range: Range,
38}
39
40#[derive(Debug, Clone)]
44pub struct ConfigSection {
45 pub prefix: String,
47 pub properties: HashMap<String, ConfigProperty>,
49 pub range: Range,
51}
52
53#[derive(Debug, Clone)]
57pub struct ConfigProperty {
58 pub key: String,
60 pub value: ConfigValue,
62 pub range: Range,
64}
65
66#[derive(Debug, Clone, PartialEq)]
70pub enum ConfigValue {
71 String(String),
73 Integer(i64),
75 Float(f64),
77 Boolean(bool),
79 Array(Vec<ConfigValue>),
81 Table(HashMap<String, ConfigValue>),
83}
84
85pub struct TomlAnalyzer {
89 schema_provider: SchemaProvider,
91}
92
93impl TomlAnalyzer {
94 pub fn new(schema_provider: SchemaProvider) -> Self {
96 Self { schema_provider }
97 }
98
99 pub fn schema_provider(&self) -> &SchemaProvider {
101 &self.schema_provider
102 }
103
104 pub fn hover(&self, doc: &TomlDocument, position: Position) -> Option<Hover> {
122 if let Some(hover) = self.hover_env_var(doc, position) {
124 return Some(hover);
125 }
126
127 if let Some(hover) = self.hover_config_property(doc, position) {
129 return Some(hover);
130 }
131
132 None
133 }
134
135 fn hover_env_var(&self, doc: &TomlDocument, position: Position) -> Option<Hover> {
137 for env_var in &doc.env_vars {
139 if self.position_in_range(position, env_var.range) {
140 let mut hover_text = String::new();
141
142 hover_text.push_str("# 环境变量\n\n");
144
145 hover_text.push_str(&format!("**变量名**: `{}`\n\n", env_var.name));
147
148 if let Some(default) = &env_var.default {
150 hover_text.push_str(&format!("**默认值**: `{}`\n\n", default));
151 }
152
153 if let Ok(value) = std::env::var(&env_var.name) {
155 hover_text.push_str(&format!("**当前值**: `{}`\n\n", value));
156 } else {
157 hover_text.push_str("**当前值**: *未设置*\n\n");
158 }
159
160 hover_text.push_str("**说明**:\n\n");
162 hover_text.push_str("环境变量插值允许在配置文件中引用系统环境变量。\n\n");
163 hover_text.push_str("**格式**:\n");
164 hover_text.push_str("- `${VAR}` - 引用环境变量,如果未设置则报错\n");
165 hover_text.push_str("- `${VAR:default}` - 引用环境变量,如果未设置则使用默认值\n");
166
167 return Some(Hover {
168 contents: HoverContents::Markup(MarkupContent {
169 kind: MarkupKind::Markdown,
170 value: hover_text,
171 }),
172 range: Some(env_var.range),
173 });
174 }
175 }
176
177 None
178 }
179
180 fn hover_config_property(&self, doc: &TomlDocument, position: Position) -> Option<Hover> {
182 for (prefix, section) in &doc.config_sections {
184 for (key, property) in §ion.properties {
186 if self.position_in_range(position, property.range) {
187 if let Some(plugin_schema) = self.schema_provider.get_plugin_schema(prefix) {
189 if let Some(property_schema) = plugin_schema.properties.get(key) {
190 return Some(self.create_property_hover(
191 prefix,
192 key,
193 property,
194 property_schema,
195 ));
196 }
197 }
198
199 return Some(self.create_basic_property_hover(prefix, key, property));
201 }
202 }
203 }
204
205 None
206 }
207
208 fn create_property_hover(
210 &self,
211 prefix: &str,
212 key: &str,
213 property: &ConfigProperty,
214 schema: &PropertySchema,
215 ) -> Hover {
216 let mut hover_text = String::new();
217
218 hover_text.push_str(&format!("# 配置项: `{}.{}`\n\n", prefix, key));
220
221 if !schema.description.is_empty() {
223 hover_text.push_str(&format!("{}\n\n", schema.description));
224 }
225
226 hover_text.push_str(&format!(
228 "**类型**: {}\n\n",
229 self.type_info_to_string(&schema.type_info)
230 ));
231
232 hover_text.push_str(&format!(
234 "**当前值**: `{}`\n\n",
235 self.config_value_to_string(&property.value)
236 ));
237
238 if let Some(default) = &schema.default {
240 hover_text.push_str(&format!(
241 "**默认值**: `{}`\n\n",
242 self.value_to_string(default)
243 ));
244 }
245
246 if schema.required {
248 hover_text.push_str("**必需**: 是\n\n");
249 }
250
251 if let TypeInfo::String {
253 enum_values: Some(enum_vals),
254 ..
255 } = &schema.type_info
256 {
257 hover_text.push_str("**允许的值**:\n");
258 for val in enum_vals {
259 hover_text.push_str(&format!("- `{}`\n", val));
260 }
261 hover_text.push('\n');
262 }
263
264 match &schema.type_info {
266 TypeInfo::Integer { min, max } => {
267 if min.is_some() || max.is_some() {
268 hover_text.push_str("**值范围**:\n");
269 if let Some(min_val) = min {
270 hover_text.push_str(&format!("- 最小值: `{}`\n", min_val));
271 }
272 if let Some(max_val) = max {
273 hover_text.push_str(&format!("- 最大值: `{}`\n", max_val));
274 }
275 hover_text.push('\n');
276 }
277 }
278 TypeInfo::Float { min, max } => {
279 if min.is_some() || max.is_some() {
280 hover_text.push_str("**值范围**:\n");
281 if let Some(min_val) = min {
282 hover_text.push_str(&format!("- 最小值: `{}`\n", min_val));
283 }
284 if let Some(max_val) = max {
285 hover_text.push_str(&format!("- 最大值: `{}`\n", max_val));
286 }
287 hover_text.push('\n');
288 }
289 }
290 TypeInfo::String {
291 min_length,
292 max_length,
293 ..
294 } => {
295 if min_length.is_some() || max_length.is_some() {
296 hover_text.push_str("**长度限制**:\n");
297 if let Some(min_len) = min_length {
298 hover_text.push_str(&format!("- 最小长度: `{}`\n", min_len));
299 }
300 if let Some(max_len) = max_length {
301 hover_text.push_str(&format!("- 最大长度: `{}`\n", max_len));
302 }
303 hover_text.push('\n');
304 }
305 }
306 _ => {}
307 }
308
309 if let Some(example) = &schema.example {
311 hover_text.push_str("**示例**:\n\n");
312 hover_text.push_str("```toml\n");
313 hover_text.push_str(example);
314 hover_text.push_str("\n```\n\n");
315 }
316
317 if let Some(deprecated_msg) = &schema.deprecated {
319 hover_text.push_str(&format!("⚠️ **已废弃**: {}\n\n", deprecated_msg));
320 }
321
322 hover_text.push_str("---\n\n");
324 hover_text.push_str(&format!("*配置节*: `[{}]`\n", prefix));
325 hover_text.push_str("*配置文件*: `config/app.toml`\n");
326
327 Hover {
328 contents: HoverContents::Markup(MarkupContent {
329 kind: MarkupKind::Markdown,
330 value: hover_text,
331 }),
332 range: Some(property.range),
333 }
334 }
335
336 fn create_basic_property_hover(
338 &self,
339 prefix: &str,
340 key: &str,
341 property: &ConfigProperty,
342 ) -> Hover {
343 let mut hover_text = String::new();
344
345 hover_text.push_str(&format!("# 配置项: `{}.{}`\n\n", prefix, key));
347
348 hover_text.push_str(&format!(
350 "**当前值**: `{}`\n\n",
351 self.config_value_to_string(&property.value)
352 ));
353
354 hover_text.push_str(&format!(
356 "**类型**: {}\n\n",
357 self.config_value_type_name(&property.value)
358 ));
359
360 hover_text.push_str("⚠️ **警告**: 此配置项未在 Schema 中定义\n\n");
362
363 hover_text.push_str("---\n\n");
365 hover_text.push_str(&format!("*配置节*: `[{}]`\n", prefix));
366 hover_text.push_str("*配置文件*: `config/app.toml`\n");
367
368 Hover {
369 contents: HoverContents::Markup(MarkupContent {
370 kind: MarkupKind::Markdown,
371 value: hover_text,
372 }),
373 range: Some(property.range),
374 }
375 }
376
377 fn position_in_range(&self, position: Position, range: Range) -> bool {
379 if position.line < range.start.line || position.line > range.end.line {
381 return false;
382 }
383
384 if position.line == range.start.line && position.character < range.start.character {
386 return false;
387 }
388
389 if position.line == range.end.line && position.character > range.end.character {
390 return false;
391 }
392
393 true
394 }
395
396 fn config_value_to_string(&self, value: &ConfigValue) -> String {
398 match value {
399 ConfigValue::String(s) => format!("\"{}\"", s),
400 ConfigValue::Integer(i) => i.to_string(),
401 ConfigValue::Float(f) => f.to_string(),
402 ConfigValue::Boolean(b) => b.to_string(),
403 ConfigValue::Array(arr) => {
404 let items: Vec<String> =
405 arr.iter().map(|v| self.config_value_to_string(v)).collect();
406 format!("[{}]", items.join(", "))
407 }
408 ConfigValue::Table(table) => {
409 let items: Vec<String> = table
410 .iter()
411 .map(|(k, v)| format!("{} = {}", k, self.config_value_to_string(v)))
412 .collect();
413 format!("{{ {} }}", items.join(", "))
414 }
415 }
416 }
417
418 fn value_to_string(&self, value: &crate::schema::Value) -> String {
420 match value {
421 crate::schema::Value::String(s) => format!("\"{}\"", s),
422 crate::schema::Value::Integer(n) => n.to_string(),
423 crate::schema::Value::Float(f) => f.to_string(),
424 crate::schema::Value::Boolean(b) => b.to_string(),
425 crate::schema::Value::Array(arr) => {
426 let items: Vec<String> = arr.iter().map(|v| self.value_to_string(v)).collect();
427 format!("[{}]", items.join(", "))
428 }
429 crate::schema::Value::Table(obj) => {
430 let items: Vec<String> = obj
431 .iter()
432 .map(|(k, v)| format!("{} = {}", k, self.value_to_string(v)))
433 .collect();
434 format!("{{ {} }}", items.join(", "))
435 }
436 }
437 }
438
439 pub fn validate(&self, doc: &TomlDocument) -> Vec<Diagnostic> {
460 let mut diagnostics = Vec::new();
461
462 diagnostics.extend(self.validate_env_var_syntax(&doc.env_vars));
464
465 for (prefix, section) in &doc.config_sections {
467 if let Some(plugin_schema) = self.schema_provider.get_plugin_schema(prefix) {
469 diagnostics.extend(self.validate_section(section, &plugin_schema));
471
472 diagnostics.extend(self.validate_required_properties(section, &plugin_schema));
474 } else {
475 diagnostics.push(Diagnostic {
477 range: section.range,
478 severity: Some(DiagnosticSeverity::ERROR),
479 code: Some(lsp_types::NumberOrString::String(
480 "undefined-section".to_string(),
481 )),
482 message: format!("配置节 '{}' 未在 Schema 中定义", prefix),
483 source: Some("spring-lsp".to_string()),
484 ..Default::default()
485 });
486 }
487 }
488
489 diagnostics
490 }
491
492 fn validate_env_var_syntax(&self, env_vars: &[EnvVarReference]) -> Vec<Diagnostic> {
494 let mut diagnostics = Vec::new();
495
496 for env_var in env_vars {
497 if env_var.name.is_empty() {
499 diagnostics.push(Diagnostic {
500 range: env_var.range,
501 severity: Some(DiagnosticSeverity::ERROR),
502 code: Some(lsp_types::NumberOrString::String(
503 "empty-var-name".to_string(),
504 )),
505 message: "环境变量名不能为空".to_string(),
506 source: Some("spring-lsp".to_string()),
507 ..Default::default()
508 });
509 }
510
511 if !env_var
513 .name
514 .chars()
515 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_')
516 {
517 diagnostics.push(Diagnostic {
518 range: env_var.range,
519 severity: Some(DiagnosticSeverity::WARNING),
520 code: Some(lsp_types::NumberOrString::String(
521 "invalid-var-name".to_string(),
522 )),
523 message: format!(
524 "环境变量名 '{}' 不符合命名规范,建议使用大写字母、数字和下划线",
525 env_var.name
526 ),
527 source: Some("spring-lsp".to_string()),
528 ..Default::default()
529 });
530 }
531 }
532
533 diagnostics
534 }
535
536 fn validate_section(
538 &self,
539 section: &ConfigSection,
540 plugin_schema: &crate::schema::PluginSchema,
541 ) -> Vec<Diagnostic> {
542 let mut diagnostics = Vec::new();
543
544 for (key, property) in §ion.properties {
545 if let Some(property_schema) = plugin_schema.properties.get(key) {
546 if let Some(deprecated_msg) = &property_schema.deprecated {
548 diagnostics.push(Diagnostic {
549 range: property.range,
550 severity: Some(DiagnosticSeverity::WARNING),
551 code: Some(lsp_types::NumberOrString::String(
552 "deprecated-property".to_string(),
553 )),
554 message: format!("配置项 '{}' 已废弃: {}", key, deprecated_msg),
555 source: Some("spring-lsp".to_string()),
556 ..Default::default()
557 });
558 }
559
560 diagnostics.extend(self.validate_property_type(property, property_schema));
562
563 diagnostics.extend(self.validate_property_range(property, property_schema));
565 } else {
566 diagnostics.push(Diagnostic {
568 range: property.range,
569 severity: Some(DiagnosticSeverity::ERROR),
570 code: Some(lsp_types::NumberOrString::String(
571 "undefined-property".to_string(),
572 )),
573 message: format!("配置项 '{}' 未在 Schema 中定义", key),
574 source: Some("spring-lsp".to_string()),
575 ..Default::default()
576 });
577 }
578 }
579
580 diagnostics
581 }
582
583 fn validate_property_type(
585 &self,
586 property: &ConfigProperty,
587 schema: &PropertySchema,
588 ) -> Vec<Diagnostic> {
589 let mut diagnostics = Vec::new();
590
591 let type_matches = matches!(
592 (&property.value, &schema.type_info),
593 (ConfigValue::String(_), TypeInfo::String { .. })
594 | (ConfigValue::Integer(_), TypeInfo::Integer { .. })
595 | (ConfigValue::Float(_), TypeInfo::Float { .. })
596 | (ConfigValue::Boolean(_), TypeInfo::Boolean)
597 | (ConfigValue::Array(_), TypeInfo::Array { .. })
598 | (ConfigValue::Table(_), TypeInfo::Object { .. })
599 );
600
601 if !type_matches {
602 let expected_type = self.type_info_to_string(&schema.type_info);
603 let actual_type = self.config_value_type_name(&property.value);
604
605 diagnostics.push(Diagnostic {
606 range: property.range,
607 severity: Some(DiagnosticSeverity::ERROR),
608 code: Some(lsp_types::NumberOrString::String(
609 "type-mismatch".to_string(),
610 )),
611 message: format!(
612 "配置项 '{}' 的类型不匹配:期望 {},实际 {}",
613 property.key, expected_type, actual_type
614 ),
615 source: Some("spring-lsp".to_string()),
616 ..Default::default()
617 });
618 }
619
620 diagnostics
621 }
622
623 fn validate_property_range(
625 &self,
626 property: &ConfigProperty,
627 schema: &PropertySchema,
628 ) -> Vec<Diagnostic> {
629 let mut diagnostics = Vec::new();
630
631 match (&property.value, &schema.type_info) {
632 (
634 ConfigValue::String(s),
635 TypeInfo::String {
636 enum_values,
637 min_length,
638 max_length,
639 },
640 ) => {
641 if let Some(enum_vals) = enum_values {
643 if !enum_vals.contains(s) {
644 diagnostics.push(Diagnostic {
645 range: property.range,
646 severity: Some(DiagnosticSeverity::ERROR),
647 code: Some(lsp_types::NumberOrString::String(
648 "invalid-enum-value".to_string(),
649 )),
650 message: format!(
651 "配置项 '{}' 的值 '{}' 不在允许的枚举值中:{:?}",
652 property.key, s, enum_vals
653 ),
654 source: Some("spring-lsp".to_string()),
655 ..Default::default()
656 });
657 }
658 }
659
660 if let Some(min_len) = min_length {
662 if s.len() < *min_len {
663 diagnostics.push(Diagnostic {
664 range: property.range,
665 severity: Some(DiagnosticSeverity::ERROR),
666 code: Some(lsp_types::NumberOrString::String(
667 "string-too-short".to_string(),
668 )),
669 message: format!(
670 "配置项 '{}' 的值长度 {} 小于最小长度 {}",
671 property.key,
672 s.len(),
673 min_len
674 ),
675 source: Some("spring-lsp".to_string()),
676 ..Default::default()
677 });
678 }
679 }
680
681 if let Some(max_len) = max_length {
683 if s.len() > *max_len {
684 diagnostics.push(Diagnostic {
685 range: property.range,
686 severity: Some(DiagnosticSeverity::ERROR),
687 code: Some(lsp_types::NumberOrString::String(
688 "string-too-long".to_string(),
689 )),
690 message: format!(
691 "配置项 '{}' 的值长度 {} 超过最大长度 {}",
692 property.key,
693 s.len(),
694 max_len
695 ),
696 source: Some("spring-lsp".to_string()),
697 ..Default::default()
698 });
699 }
700 }
701 }
702
703 (ConfigValue::Integer(i), TypeInfo::Integer { min, max }) => {
705 if let Some(min_val) = min {
706 if *i < *min_val {
707 diagnostics.push(Diagnostic {
708 range: property.range,
709 severity: Some(DiagnosticSeverity::ERROR),
710 code: Some(lsp_types::NumberOrString::String(
711 "value-too-small".to_string(),
712 )),
713 message: format!(
714 "配置项 '{}' 的值 {} 小于最小值 {}",
715 property.key, i, min_val
716 ),
717 source: Some("spring-lsp".to_string()),
718 ..Default::default()
719 });
720 }
721 }
722
723 if let Some(max_val) = max {
724 if *i > *max_val {
725 diagnostics.push(Diagnostic {
726 range: property.range,
727 severity: Some(DiagnosticSeverity::ERROR),
728 code: Some(lsp_types::NumberOrString::String(
729 "value-too-large".to_string(),
730 )),
731 message: format!(
732 "配置项 '{}' 的值 {} 超过最大值 {}",
733 property.key, i, max_val
734 ),
735 source: Some("spring-lsp".to_string()),
736 ..Default::default()
737 });
738 }
739 }
740 }
741
742 (ConfigValue::Float(f), TypeInfo::Float { min, max }) => {
744 if let Some(min_val) = min {
745 if *f < *min_val {
746 diagnostics.push(Diagnostic {
747 range: property.range,
748 severity: Some(DiagnosticSeverity::ERROR),
749 code: Some(lsp_types::NumberOrString::String(
750 "value-too-small".to_string(),
751 )),
752 message: format!(
753 "配置项 '{}' 的值 {} 小于最小值 {}",
754 property.key, f, min_val
755 ),
756 source: Some("spring-lsp".to_string()),
757 ..Default::default()
758 });
759 }
760 }
761
762 if let Some(max_val) = max {
763 if *f > *max_val {
764 diagnostics.push(Diagnostic {
765 range: property.range,
766 severity: Some(DiagnosticSeverity::ERROR),
767 code: Some(lsp_types::NumberOrString::String(
768 "value-too-large".to_string(),
769 )),
770 message: format!(
771 "配置项 '{}' 的值 {} 超过最大值 {}",
772 property.key, f, max_val
773 ),
774 source: Some("spring-lsp".to_string()),
775 ..Default::default()
776 });
777 }
778 }
779 }
780
781 _ => {}
782 }
783
784 diagnostics
785 }
786
787 fn validate_required_properties(
789 &self,
790 section: &ConfigSection,
791 plugin_schema: &crate::schema::PluginSchema,
792 ) -> Vec<Diagnostic> {
793 let mut diagnostics = Vec::new();
794
795 for (key, property_schema) in &plugin_schema.properties {
796 if property_schema.required && !section.properties.contains_key(key) {
797 diagnostics.push(Diagnostic {
798 range: section.range,
799 severity: Some(DiagnosticSeverity::WARNING),
800 code: Some(lsp_types::NumberOrString::String(
801 "missing-required-property".to_string(),
802 )),
803 message: format!("缺少必需的配置项 '{}'", key),
804 source: Some("spring-lsp".to_string()),
805 ..Default::default()
806 });
807 }
808 }
809
810 diagnostics
811 }
812
813 fn type_info_to_string(&self, type_info: &TypeInfo) -> String {
815 match type_info {
816 TypeInfo::String { .. } => "字符串".to_string(),
817 TypeInfo::Integer { .. } => "整数".to_string(),
818 TypeInfo::Float { .. } => "浮点数".to_string(),
819 TypeInfo::Boolean => "布尔值".to_string(),
820 TypeInfo::Array { .. } => "数组".to_string(),
821 TypeInfo::Object { .. } => "对象".to_string(),
822 }
823 }
824
825 fn config_value_type_name(&self, value: &ConfigValue) -> String {
827 match value {
828 ConfigValue::String(_) => "字符串".to_string(),
829 ConfigValue::Integer(_) => "整数".to_string(),
830 ConfigValue::Float(_) => "浮点数".to_string(),
831 ConfigValue::Boolean(_) => "布尔值".to_string(),
832 ConfigValue::Array(_) => "数组".to_string(),
833 ConfigValue::Table(_) => "对象".to_string(),
834 }
835 }
836
837 pub fn parse(&self, content: &str) -> Result<TomlDocument, String> {
861 let parse_result = taplo::parser::parse(content);
863
864 if !parse_result.errors.is_empty() {
866 let error_messages: Vec<String> = parse_result
867 .errors
868 .iter()
869 .map(|e| format!("{:?}:{:?} - {}", e.range.start(), e.range.end(), e.message))
870 .collect();
871 return Err(format!("TOML 语法错误: {}", error_messages.join("; ")));
872 }
873
874 let root = parse_result.into_dom();
876
877 let env_vars = self.extract_env_vars(content);
879
880 let config_sections = self.extract_config_sections(&root, content);
882
883 Ok(TomlDocument {
884 root,
885 env_vars,
886 config_sections,
887 content: content.to_string(),
888 })
889 }
890
891 fn extract_env_vars(&self, content: &str) -> Vec<EnvVarReference> {
895 let mut env_vars = Vec::new();
896 let mut line = 0;
897 let mut line_start = 0;
898
899 for (byte_offset, _) in content.char_indices() {
900 if content[byte_offset..].starts_with('\n') {
902 line += 1;
903 line_start = byte_offset + 1;
904 }
905
906 if content[byte_offset..].starts_with("${") {
908 if let Some(end_offset) = content[byte_offset + 2..].find('}') {
910 let end_offset = byte_offset + 2 + end_offset;
911 let var_content = &content[byte_offset + 2..end_offset];
912
913 let (name, default) = if let Some(colon_pos) = var_content.find(':') {
915 let name = var_content[..colon_pos].to_string();
916 let default = Some(var_content[colon_pos + 1..].to_string());
917 (name, default)
918 } else {
919 (var_content.to_string(), None)
920 };
921
922 let start_char = byte_offset - line_start;
924 let end_char = end_offset + 1 - line_start;
925
926 env_vars.push(EnvVarReference {
927 name,
928 default,
929 range: Range {
930 start: lsp_types::Position {
931 line: line as u32,
932 character: start_char as u32,
933 },
934 end: lsp_types::Position {
935 line: line as u32,
936 character: end_char as u32,
937 },
938 },
939 });
940 }
941 }
942 }
943
944 env_vars
945 }
946
947 fn extract_config_sections(
951 &self,
952 root: &taplo::dom::Node,
953 content: &str,
954 ) -> HashMap<String, ConfigSection> {
955 let mut sections = HashMap::new();
956
957 if let Some(table) = root.as_table() {
959 let entries = table.entries();
960
961 let entries_arc = entries.get();
963 for (key, value) in entries_arc.iter() {
964 let prefix = key.value().to_string();
965
966 if value.as_table().is_some() {
968 let properties = self.extract_properties(value, content);
969 let range = self.node_to_range(value, content);
970
971 sections.insert(
972 prefix.clone(),
973 ConfigSection {
974 prefix,
975 properties,
976 range,
977 },
978 );
979 }
980 }
981 }
982
983 sections
984 }
985
986 fn extract_properties(
990 &self,
991 node: &taplo::dom::Node,
992 content: &str,
993 ) -> HashMap<String, ConfigProperty> {
994 let mut properties = HashMap::new();
995
996 if let Some(table) = node.as_table() {
998 let entries = table.entries();
999
1000 let entries_arc = entries.get();
1002 for (key, value) in entries_arc.iter() {
1003 let key_str = key.value().to_string();
1004 let config_value = self.node_to_config_value(value);
1005 let range = self.node_to_range(value, content);
1006
1007 properties.insert(
1008 key_str.clone(),
1009 ConfigProperty {
1010 key: key_str,
1011 value: config_value,
1012 range,
1013 },
1014 );
1015 }
1016 }
1017
1018 properties
1019 }
1020
1021 fn node_to_config_value(&self, node: &taplo::dom::Node) -> ConfigValue {
1023 match node {
1024 taplo::dom::Node::Bool(b) => ConfigValue::Boolean(b.value()),
1025 taplo::dom::Node::Str(s) => ConfigValue::String(s.value().to_string()),
1026 taplo::dom::Node::Integer(i) => {
1027 match i.value() {
1029 IntegerValue::Positive(v) => ConfigValue::Integer(v as i64),
1030 IntegerValue::Negative(v) => ConfigValue::Integer(v),
1031 }
1032 }
1033 taplo::dom::Node::Float(f) => ConfigValue::Float(f.value()),
1034 taplo::dom::Node::Array(arr) => {
1035 let items = arr.items();
1036 let mut values = Vec::new();
1037
1038 let items_arc = items.get();
1040 for item in items_arc.iter() {
1041 values.push(self.node_to_config_value(item));
1042 }
1043
1044 ConfigValue::Array(values)
1045 }
1046 taplo::dom::Node::Table(table) => {
1047 let entries = table.entries();
1048 let mut map = HashMap::new();
1049
1050 let entries_arc = entries.get();
1052 for (key, value) in entries_arc.iter() {
1053 let key_str = key.value().to_string();
1054 map.insert(key_str, self.node_to_config_value(value));
1055 }
1056
1057 ConfigValue::Table(map)
1058 }
1059 _ => ConfigValue::String(String::new()), }
1061 }
1062
1063 fn node_to_range(&self, node: &taplo::dom::Node, content: &str) -> Range {
1067 let mut text_ranges = node.text_ranges();
1069 if let Some(first_range) = text_ranges.next() {
1070 let start: usize = first_range.start().into();
1071 let end: usize = first_range.end().into();
1072
1073 let start_pos = self.byte_offset_to_position(content, start);
1075 let end_pos = self.byte_offset_to_position(content, end);
1076
1077 Range {
1078 start: start_pos,
1079 end: end_pos,
1080 }
1081 } else {
1082 Range {
1084 start: lsp_types::Position {
1085 line: 0,
1086 character: 0,
1087 },
1088 end: lsp_types::Position {
1089 line: 0,
1090 character: 0,
1091 },
1092 }
1093 }
1094 }
1095
1096 fn byte_offset_to_position(&self, content: &str, byte_offset: usize) -> lsp_types::Position {
1100 let mut line = 0;
1101 let mut character = 0;
1102 let mut current_offset = 0;
1103
1104 for ch in content.chars() {
1105 if current_offset >= byte_offset {
1106 break;
1107 }
1108
1109 if ch == '\n' {
1110 line += 1;
1111 character = 0;
1112 } else {
1113 character += 1;
1114 }
1115
1116 current_offset += ch.len_utf8();
1117 }
1118
1119 lsp_types::Position {
1120 line: line as u32,
1121 character: character as u32,
1122 }
1123 }
1124}