use std::collections::{HashMap, HashSet};
use std::mem;
use std::sync::{Arc, RwLock};
use itertools::{Either, Itertools};
use lsp_types::{Diagnostic, Url};
#[derive(Clone)]
pub struct ProjectDiagnostics {
file_diagnostics: Arc<RwLock<HashMap<Url, SelfAndOriginatingFilesDiagnostics>>>,
}
type SelfAndOriginatingFilesDiagnostics = HashMap<Url, Vec<Diagnostic>>;
impl ProjectDiagnostics {
pub fn new() -> Self {
Self { file_diagnostics: Default::default() }
}
#[tracing::instrument(skip_all)]
pub fn update(
&self,
root_on_disk_file_url: Url,
new_diags: SelfAndOriginatingFilesDiagnostics,
) -> HashMap<Url, Vec<Diagnostic>> {
let old_diags = self
.file_diagnostics
.read()
.expect("file diagnostics are poisoned, bailing out")
.get(&root_on_disk_file_url)
.cloned()
.unwrap_or_default();
if new_diags == old_diags {
return HashMap::new();
}
self.file_diagnostics
.write()
.expect("file diagnostics are poisoned, bailing out")
.insert(root_on_disk_file_url.clone(), new_diags.clone());
let mut diags_to_send = HashMap::new();
for location_file_url in old_diags.keys() {
if !new_diags.contains_key(location_file_url) {
diags_to_send.insert(location_file_url.clone(), Vec::new());
}
}
for (location_file_url, new_diags_for_url) in new_diags {
if old_diags.get(&location_file_url) != Some(&new_diags_for_url) {
diags_to_send.insert(location_file_url, new_diags_for_url);
}
}
diags_to_send
}
pub fn clear_old(&self, processed_files_to_retain: &HashSet<Url>) -> Vec<Url> {
let mut file_diagnostics =
self.file_diagnostics.write().expect("file diagnostics are poisoned, bailing out");
let (clean, removed) = mem::take(&mut *file_diagnostics).into_iter().partition_map(
|(processed_file_url, diags)| {
if processed_files_to_retain.contains(&processed_file_url) {
Either::Left((processed_file_url, diags))
} else {
Either::Right(processed_file_url)
}
},
);
*file_diagnostics = clean;
removed
}
}