Skip to main content

swls_core/feature/
diagnostics.rs

1use std::{collections::HashMap, ops::Range};
2
3use bevy_ecs::prelude::*;
4use futures::channel::mpsc;
5
6use crate::{
7    lsp_types::{Diagnostic, DiagnosticSeverity, TextDocumentItem, Url},
8    prelude::*,
9};
10
11#[derive(Resource)]
12pub struct DiagnosticPublisher {
13    tx: mpsc::UnboundedSender<DiagnosticItem>,
14    diagnostics: HashMap<crate::lsp_types::Url, Vec<(Diagnostic, &'static str)>>,
15}
16
17impl DiagnosticPublisher {
18    pub fn new() -> (Self, mpsc::UnboundedReceiver<DiagnosticItem>) {
19        let (tx, rx) = mpsc::unbounded();
20        (
21            Self {
22                tx,
23                diagnostics: HashMap::new(),
24            },
25            rx,
26        )
27    }
28
29    pub fn publish(
30        &mut self,
31        params: &TextDocumentItem,
32        diagnostics: Vec<Diagnostic>,
33        reason: &'static str,
34    ) -> Option<()> {
35        let items = self.diagnostics.entry(params.uri.clone()).or_default();
36        items.retain(|(_, r)| *r != reason);
37        items.extend(diagnostics.into_iter().map(|x| (x, reason)));
38        let diagnostics: Vec<_> = items.iter().map(|(x, _)| x).cloned().collect();
39        let uri = params.uri.clone();
40        let version = Some(params.version);
41        let item = DiagnosticItem {
42            diagnostics,
43            uri,
44            version,
45        };
46        self.tx.unbounded_send(item).ok()
47    }
48}
49
50#[derive(Debug)]
51pub struct SimpleDiagnostic {
52    pub range: Range<usize>,
53    pub msg: String,
54    pub severity: Option<DiagnosticSeverity>,
55}
56
57impl SimpleDiagnostic {
58    pub fn new(range: Range<usize>, msg: String) -> Self {
59        Self {
60            range,
61            msg,
62            severity: None,
63        }
64    }
65
66    pub fn new_severity(range: Range<usize>, msg: String, severity: DiagnosticSeverity) -> Self {
67        Self {
68            range,
69            msg,
70            severity: Some(severity),
71        }
72    }
73}
74
75#[derive(Clone)]
76pub struct DiagnosticSender {
77    tx: mpsc::UnboundedSender<Vec<SimpleDiagnostic>>,
78}
79
80#[derive(Debug)]
81pub struct DiagnosticItem {
82    pub diagnostics: Vec<Diagnostic>,
83    pub uri: Url,
84    pub version: Option<i32>,
85}
86
87impl DiagnosticSender {
88    pub fn push(&self, diagnostic: SimpleDiagnostic) -> Option<()> {
89        self.tx.unbounded_send(vec![diagnostic]).ok()
90    }
91
92    pub fn push_all(&self, diagnostics: Vec<SimpleDiagnostic>) -> Option<()> {
93        self.tx.unbounded_send(diagnostics).ok()
94    }
95}
96
97pub fn publish_diagnostics<L: Lang>(
98    query: Query<
99        (
100            &Errors<L::ElementError>,
101            &Wrapped<TextDocumentItem>,
102            &RopeC,
103            &crate::components::Label,
104        ),
105        (Changed<Errors<L::ElementError>>, With<Open>),
106    >,
107    mut client: ResMut<DiagnosticPublisher>,
108    config: Res<ServerConfig>,
109) where
110    L::ElementError: 'static + Clone,
111{
112    let disabled = config.config.local.is_disabled(Disabled::SyntaxDiagnostics);
113    for (element_errors, params, rope, label) in &query {
114        if disabled {
115            let _ = client.publish(&params.0, vec![], "syntax");
116            continue;
117        }
118        tracing::debug!("Publish diagnostics for {}", label.0);
119        let diagnostics: Vec<_> = element_errors
120            .0
121            .iter()
122            .cloned()
123            .map(|x| Into::<SimpleDiagnostic>::into(x))
124            .flat_map(|item| {
125                let (span, message) = (item.range, item.msg);
126                let start_position = offset_to_position(span.start, &rope.0)?;
127                let end_position = offset_to_position(span.end, &rope.0)?;
128                Some(Diagnostic {
129                    range: crate::lsp_types::Range::new(start_position, end_position),
130                    message,
131                    severity: item.severity,
132                    ..Default::default()
133                })
134            })
135            .collect();
136
137        let _ = client.publish(&params.0, diagnostics, "syntax");
138    }
139}