lattice_lsp/
diagnostics.rs1use std::sync::Arc;
29
30use lsp_types::{Diagnostic, PublishDiagnosticsParams, Uri};
31use tokio::sync::broadcast;
32
33pub const DIAGNOSTICS_CHANNEL_CAPACITY: usize = 256;
40
41#[derive(Debug, Clone)]
48pub struct DiagnosticEvent {
49 pub server_id: Arc<str>,
53 pub uri: Uri,
55 pub version: Option<i32>,
61 pub diagnostics: Arc<[Diagnostic]>,
65}
66
67impl DiagnosticEvent {
68 pub fn from_lsp(server_id: Arc<str>, params: PublishDiagnosticsParams) -> Self {
70 Self {
71 server_id,
72 uri: params.uri,
73 version: params.version,
74 diagnostics: Arc::from(params.diagnostics.into_boxed_slice()),
75 }
76 }
77
78 pub fn is_clear(&self) -> bool {
82 self.diagnostics.is_empty()
83 }
84}
85
86#[derive(Clone)]
89pub struct DiagnosticsBus {
90 tx: broadcast::Sender<DiagnosticEvent>,
91}
92
93impl DiagnosticsBus {
94 pub fn new() -> Self {
95 let (tx, _rx) = broadcast::channel(DIAGNOSTICS_CHANNEL_CAPACITY);
96 Self { tx }
97 }
98
99 pub fn subscribe(&self) -> broadcast::Receiver<DiagnosticEvent> {
103 self.tx.subscribe()
104 }
105
106 pub fn publish(&self, ev: DiagnosticEvent) {
111 let _ = self.tx.send(ev);
112 }
113
114 pub fn receiver_count(&self) -> usize {
117 self.tx.receiver_count()
118 }
119}
120
121impl Default for DiagnosticsBus {
122 fn default() -> Self {
123 Self::new()
124 }
125}
126
127impl std::fmt::Debug for DiagnosticsBus {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 f.debug_struct("DiagnosticsBus")
130 .field("subscribers", &self.receiver_count())
131 .finish()
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use lsp_types::{Diagnostic, DiagnosticSeverity, Position as LspPosition, Range as LspRange};
139 use std::str::FromStr;
140
141 fn sample_diagnostic() -> Diagnostic {
142 Diagnostic {
143 range: LspRange {
144 start: LspPosition {
145 line: 0,
146 character: 0,
147 },
148 end: LspPosition {
149 line: 0,
150 character: 1,
151 },
152 },
153 severity: Some(DiagnosticSeverity::ERROR),
154 code: Some(lsp_types::NumberOrString::String("E0308".into())),
155 code_description: None,
156 source: Some("rustc".into()),
157 message: "type mismatch".into(),
158 related_information: None,
159 tags: None,
160 data: None,
161 }
162 }
163
164 #[test]
165 fn from_lsp_preserves_uri_version_and_diagnostics() {
166 let uri = Uri::from_str("file:///x.rs").unwrap();
167 let params = PublishDiagnosticsParams {
168 uri: uri.clone(),
169 diagnostics: vec![sample_diagnostic()],
170 version: Some(7),
171 };
172 let ev = DiagnosticEvent::from_lsp(Arc::from("rust"), params);
173 assert_eq!(ev.uri, uri);
174 assert_eq!(ev.version, Some(7));
175 assert_eq!(ev.diagnostics.len(), 1);
176 assert!(!ev.is_clear());
177 }
178
179 #[test]
180 fn empty_diagnostics_is_clear_event() {
181 let uri = Uri::from_str("file:///y.rs").unwrap();
182 let params = PublishDiagnosticsParams {
183 uri,
184 diagnostics: Vec::new(),
185 version: Some(2),
186 };
187 let ev = DiagnosticEvent::from_lsp(Arc::from("rust"), params);
188 assert!(ev.is_clear());
189 }
190
191 #[tokio::test]
192 async fn bus_fans_out_to_multiple_subscribers() {
193 let bus = DiagnosticsBus::new();
194 let mut a = bus.subscribe();
195 let mut b = bus.subscribe();
196 assert_eq!(bus.receiver_count(), 2);
197 let uri = Uri::from_str("file:///z.rs").unwrap();
198 bus.publish(DiagnosticEvent {
199 server_id: Arc::from("rust"),
200 uri: uri.clone(),
201 version: Some(1),
202 diagnostics: Arc::from(Vec::<Diagnostic>::new().into_boxed_slice()),
203 });
204 let got_a = a.recv().await.unwrap();
205 let got_b = b.recv().await.unwrap();
206 assert_eq!(got_a.uri, uri);
207 assert_eq!(got_b.uri, uri);
208 }
209
210 #[tokio::test]
211 async fn bus_publish_with_no_subscribers_is_silent() {
212 let bus = DiagnosticsBus::new();
213 let uri = Uri::from_str("file:///void.rs").unwrap();
214 bus.publish(DiagnosticEvent {
215 server_id: Arc::from("rust"),
216 uri,
217 version: None,
218 diagnostics: Arc::from(Vec::<Diagnostic>::new().into_boxed_slice()),
219 });
220 }
222
223 #[tokio::test]
224 async fn late_subscriber_does_not_see_older_events() {
225 let bus = DiagnosticsBus::new();
226 let uri = Uri::from_str("file:///prior.rs").unwrap();
227 bus.publish(DiagnosticEvent {
228 server_id: Arc::from("rust"),
229 uri,
230 version: None,
231 diagnostics: Arc::from(Vec::<Diagnostic>::new().into_boxed_slice()),
232 });
233 let mut rx = bus.subscribe();
235 let r = tokio::time::timeout(std::time::Duration::from_millis(50), rx.recv()).await;
237 assert!(r.is_err(), "subscriber should not see prior event");
238 }
239}