rez_lsp_server/server/
diagnostics.rs1use crate::core::Result;
4use crate::validation::{Severity as ValidationSeverity, ValidationEngine, ValidationResult};
5use std::collections::HashMap;
6use std::sync::Arc;
7use tokio::sync::RwLock;
8use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Position, Range, Url};
9
10pub struct DiagnosticsManager {
12 validation_engine: Arc<ValidationEngine>,
14 diagnostics: Arc<RwLock<HashMap<Url, Vec<Diagnostic>>>>,
16}
17
18impl DiagnosticsManager {
19 pub fn new() -> Result<Self> {
21 let validation_engine = Arc::new(ValidationEngine::new()?);
22 let diagnostics = Arc::new(RwLock::new(HashMap::new()));
23
24 Ok(Self {
25 validation_engine,
26 diagnostics,
27 })
28 }
29
30 pub async fn validate_file(&self, uri: &Url, content: &str) -> Result<Vec<Diagnostic>> {
32 let file_path = uri.path();
33
34 let validation_result = self.validation_engine.validate_file(content, file_path)?;
36
37 let diagnostics = self.convert_validation_result(&validation_result);
39
40 {
42 let mut diag_map = self.diagnostics.write().await;
43 diag_map.insert(uri.clone(), diagnostics.clone());
44 }
45
46 Ok(diagnostics)
47 }
48
49 pub async fn get_diagnostics(&self, uri: &Url) -> Vec<Diagnostic> {
51 let diag_map = self.diagnostics.read().await;
52 diag_map.get(uri).cloned().unwrap_or_default()
53 }
54
55 pub async fn clear_diagnostics(&self, uri: &Url) {
57 let mut diag_map = self.diagnostics.write().await;
58 diag_map.remove(uri);
59 }
60
61 pub async fn get_all_diagnostics(&self) -> HashMap<Url, Vec<Diagnostic>> {
63 let diag_map = self.diagnostics.read().await;
64 diag_map.clone()
65 }
66
67 fn convert_validation_result(&self, result: &ValidationResult) -> Vec<Diagnostic> {
69 result
70 .issues
71 .iter()
72 .map(|issue| {
73 let severity = match issue.severity {
74 ValidationSeverity::Critical => DiagnosticSeverity::ERROR,
75 ValidationSeverity::Error => DiagnosticSeverity::ERROR,
76 ValidationSeverity::Warning => DiagnosticSeverity::WARNING,
77 ValidationSeverity::Info => DiagnosticSeverity::INFORMATION,
78 };
79
80 let start_pos = Position {
81 line: issue.line - 1, character: issue.column - 1, };
84
85 let end_pos = Position {
86 line: issue.line - 1,
87 character: issue.column - 1 + issue.length,
88 };
89
90 let range = Range {
91 start: start_pos,
92 end: end_pos,
93 };
94
95 let mut diagnostic = Diagnostic {
96 range,
97 severity: Some(severity),
98 code: Some(NumberOrString::String(issue.code.clone())),
99 code_description: None,
100 source: Some("rez-lsp".to_string()),
101 message: issue.message.clone(),
102 related_information: None,
103 tags: None,
104 data: None,
105 };
106
107 if let Some(suggestion) = &issue.suggestion {
109 diagnostic.message =
110 format!("{}\nSuggestion: {}", diagnostic.message, suggestion);
111 }
112
113 diagnostic
114 })
115 .collect()
116 }
117
118 pub async fn get_validation_stats(&self) -> ValidationStats {
120 let diag_map = self.diagnostics.read().await;
121
122 let mut total_files = 0;
123 let mut files_with_errors = 0;
124 let mut files_with_warnings = 0;
125 let mut total_diagnostics = 0;
126 let mut total_errors = 0;
127 let mut total_warnings = 0;
128 let mut total_info = 0;
129
130 for diagnostics in diag_map.values() {
131 total_files += 1;
132 total_diagnostics += diagnostics.len();
133
134 let mut has_errors = false;
135 let mut has_warnings = false;
136
137 for diagnostic in diagnostics {
138 match diagnostic.severity {
139 Some(DiagnosticSeverity::ERROR) => {
140 total_errors += 1;
141 has_errors = true;
142 }
143 Some(DiagnosticSeverity::WARNING) => {
144 total_warnings += 1;
145 has_warnings = true;
146 }
147 Some(DiagnosticSeverity::INFORMATION) => {
148 total_info += 1;
149 }
150 _ => {}
151 }
152 }
153
154 if has_errors {
155 files_with_errors += 1;
156 } else if has_warnings {
157 files_with_warnings += 1;
158 }
159 }
160
161 ValidationStats {
162 total_files,
163 files_with_errors,
164 files_with_warnings,
165 total_diagnostics,
166 total_errors,
167 total_warnings,
168 total_info,
169 }
170 }
171}
172
173#[derive(Debug, Clone)]
175pub struct ValidationStats {
176 pub total_files: usize,
178 pub files_with_errors: usize,
180 pub files_with_warnings: usize,
182 pub total_diagnostics: usize,
184 pub total_errors: usize,
186 pub total_warnings: usize,
188 pub total_info: usize,
190}
191
192impl ValidationStats {
193 pub fn has_errors(&self) -> bool {
195 self.total_errors > 0
196 }
197
198 pub fn has_diagnostics(&self) -> bool {
200 self.total_diagnostics > 0
201 }
202
203 pub fn error_rate(&self) -> f64 {
205 if self.total_files > 0 {
206 (self.files_with_errors as f64 / self.total_files as f64) * 100.0
207 } else {
208 0.0
209 }
210 }
211}