swls_core/feature/
diagnostics.rs1use 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(¶ms.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(¶ms.0, diagnostics, "syntax");
138 }
139}