use lsp_max_protocol::{LawAxis, MaxDiagnostic};
use lsp_types_max::{Diagnostic, Uri as Url};
use parking_lot::RwLock;
use rustc_hash::FxHashMap;
use std::sync::Arc;
use crate::service::Client;
#[derive(Clone, Debug)]
pub struct DiagnosticSink {
client: Client,
last: Arc<RwLock<FxHashMap<Url, (u64, u64)>>>,
}
impl DiagnosticSink {
pub fn new(client: Client) -> Self {
Self {
client,
last: Arc::new(RwLock::new(FxHashMap::default())),
}
}
pub async fn publish(&self, uri: Url, diags: Vec<Diagnostic>) {
let hash = hash_diagnostics(&diags);
{
let last = self.last.read();
if last.get(&uri).map(|(_, fnv)| *fnv) == Some(hash) {
return;
}
}
self.last.write().insert(uri.clone(), (0u64, hash));
self.client.publish_diagnostics(uri, diags, None).await;
}
pub async fn publish_max(&self, uri: Url, diags: Vec<MaxDiagnostic>) {
let curr_bits = diagnostics_to_axis_bits(&diags);
let lsp_diags: Vec<Diagnostic> = diags.iter().map(|d| d.lsp.clone()).collect();
let curr_fnv = hash_diagnostics(&lsp_diags);
{
let last = self.last.read();
if let Some(&(prev_bits, prev_fnv)) = last.get(&uri) {
if prev_bits == curr_bits && prev_fnv == curr_fnv {
return;
}
}
}
self.last.write().insert(uri.clone(), (curr_bits, curr_fnv));
self.client.publish_diagnostics(uri, lsp_diags, None).await;
}
pub async fn clear(&self, uri: &Url) {
let was_present = self.last.write().remove(uri).is_some();
if was_present {
self.client
.publish_diagnostics(uri.clone(), vec![], None)
.await;
}
}
pub fn last_published(&self, uri: &Url) -> Option<u64> {
self.last.read().get(uri).map(|(_, fnv)| *fnv)
}
}
fn law_axis_bit(axis: &LawAxis) -> Option<u64> {
match axis {
LawAxis::Protocol => Some(1 << 0),
LawAxis::Type => Some(1 << 1),
LawAxis::Fixture => Some(1 << 2),
LawAxis::Documentation => Some(1 << 3),
LawAxis::Release => Some(1 << 4),
LawAxis::Hook => Some(1 << 5),
LawAxis::Repair => Some(1 << 6),
LawAxis::Receipt => Some(1 << 7),
LawAxis::Security => Some(1 << 8),
LawAxis::Autopoiesis => Some(1 << 9),
LawAxis::Domain => Some(1 << 10),
LawAxis::Custom(_) => None,
}
}
fn diagnostics_to_axis_bits(diags: &[MaxDiagnostic]) -> u64 {
diags
.iter()
.filter_map(|d| law_axis_bit(&d.law_axis))
.fold(0u64, |acc, bit| acc | bit)
}
fn hash_diagnostics(diags: &[Diagnostic]) -> u64 {
const OFFSET: u64 = 0xcbf29ce484222325;
const PRIME: u64 = 0x100000001b3;
let mut h = OFFSET;
h = (h ^ diags.len() as u64).wrapping_mul(PRIME);
for d in diags {
for b in d.message.bytes() {
h = (h ^ b as u64).wrapping_mul(PRIME);
}
h = (h ^ d
.severity
.map(|s| match s {
lsp_types_max::DiagnosticSeverity::ERROR => 1u64,
lsp_types_max::DiagnosticSeverity::WARNING => 2,
lsp_types_max::DiagnosticSeverity::INFORMATION => 3,
lsp_types_max::DiagnosticSeverity::HINT => 4,
_ => 0,
})
.unwrap_or(0xff))
.wrapping_mul(PRIME);
h = (h ^ d.range.start.line as u64).wrapping_mul(PRIME);
h = (h ^ d.range.start.character as u64).wrapping_mul(PRIME);
h = (h ^ d.range.end.line as u64).wrapping_mul(PRIME);
h = (h ^ d.range.end.character as u64).wrapping_mul(PRIME);
if let Some(src) = &d.source {
for b in src.bytes() {
h = (h ^ b as u64).wrapping_mul(PRIME);
}
}
h = h.wrapping_mul(PRIME);
}
h
}