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