1use std::{collections::HashMap, fmt::Display, hash::Hash, ops::Range};
2
3use bevy_ecs::{prelude::*, schedule::ScheduleLabel};
4use chumsky::prelude::Simple;
5use futures::channel::mpsc;
6use lsp_types::{Diagnostic, DiagnosticSeverity, TextDocumentItem, Url};
7pub use systems::prefix::undefined_prefix;
9
10use crate::prelude::*;
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((undefined_prefix,));
17 world.add_schedule(diagnostics);
18}
19
20#[derive(Resource)]
21pub struct DiagnosticPublisher {
22 tx: mpsc::UnboundedSender<DiagnosticItem>,
23 diagnostics: HashMap<lsp_types::Url, Vec<(Diagnostic, &'static str)>>,
24}
25
26impl DiagnosticPublisher {
27 pub fn new() -> (Self, mpsc::UnboundedReceiver<DiagnosticItem>) {
28 let (tx, rx) = mpsc::unbounded();
29 (
30 Self {
31 tx,
32 diagnostics: HashMap::new(),
33 },
34 rx,
35 )
36 }
37
38 pub fn publish(
39 &mut self,
40 params: &TextDocumentItem,
41 diagnostics: Vec<Diagnostic>,
42 reason: &'static str,
43 ) -> Option<()> {
44 let items = self.diagnostics.entry(params.uri.clone()).or_default();
45 items.retain(|(_, r)| *r != reason);
46 items.extend(diagnostics.into_iter().map(|x| (x, reason)));
47 let diagnostics: Vec<_> = items.iter().map(|(x, _)| x).cloned().collect();
48 let uri = params.uri.clone();
49 let version = Some(params.version);
50 let item = DiagnosticItem {
51 diagnostics,
52 uri,
53 version,
54 };
55 self.tx.unbounded_send(item).ok()
56 }
57}
58
59#[derive(Debug)]
60pub struct SimpleDiagnostic {
61 pub range: Range<usize>,
62 pub msg: String,
63 pub severity: Option<DiagnosticSeverity>,
64}
65
66impl SimpleDiagnostic {
67 pub fn new(range: Range<usize>, msg: String) -> Self {
68 Self {
69 range,
70 msg,
71 severity: None,
72 }
73 }
74
75 pub fn new_severity(range: Range<usize>, msg: String, severity: DiagnosticSeverity) -> Self {
76 Self {
77 range,
78 msg,
79 severity: Some(severity),
80 }
81 }
82}
83
84impl<T: Display + Eq + Hash> From<Simple<T>> for SimpleDiagnostic {
85 fn from(e: Simple<T>) -> Self {
86 let msg = if let chumsky::error::SimpleReason::Custom(msg) = e.reason() {
87 msg.clone()
88 } else {
89 format!(
90 "{}{}, expected {}",
91 if e.found().is_some() {
92 "Unexpected token"
93 } else {
94 "Unexpected end of input"
95 },
96 if let Some(label) = e.label() {
97 format!(" while parsing {}", label)
98 } else {
99 String::new()
100 },
101 if e.expected().len() == 0 {
102 "something else".to_string()
103 } else {
104 e.expected()
105 .map(|expected| match expected {
106 Some(expected) => format!("'{}'", expected),
107 None => "end of input".to_string(),
108 })
109 .collect::<Vec<_>>()
110 .join(" or ")
111 },
112 )
113 };
114
115 SimpleDiagnostic::new(e.span(), msg)
116 }
117}
118
119impl<T: Display + Eq + Hash> From<(usize, Simple<T>)> for SimpleDiagnostic {
120 fn from(this: (usize, Simple<T>)) -> Self {
121 let (len, e) = this;
122 let msg = if let chumsky::error::SimpleReason::Custom(msg) = e.reason() {
123 msg.clone()
124 } else {
125 format!(
126 "{}{}, expected {}",
127 if e.found().is_some() {
128 "Unexpected token"
129 } else {
130 "Unexpected end of input"
131 },
132 if let Some(label) = e.label() {
133 format!(" while parsing {}", label)
134 } else {
135 String::new()
136 },
137 if e.expected().len() == 0 {
138 "something else".to_string()
139 } else {
140 e.expected()
141 .map(|expected| match expected {
142 Some(expected) => format!("'{}'", expected),
143 None => "end of input".to_string(),
144 })
145 .collect::<Vec<_>>()
146 .join(" or ")
147 },
148 )
149 };
150
151 let range = (len - e.span().end)..(len - e.span().start);
152 SimpleDiagnostic::new(range, msg)
153 }
154}
155
156#[derive(Clone)]
157pub struct DiagnosticSender {
158 tx: mpsc::UnboundedSender<Vec<SimpleDiagnostic>>,
159}
160
161#[derive(Debug)]
162pub struct DiagnosticItem {
163 pub diagnostics: Vec<Diagnostic>,
164 pub uri: Url,
165 pub version: Option<i32>,
166}
167impl DiagnosticSender {
168 pub fn push(&self, diagnostic: SimpleDiagnostic) -> Option<()> {
169 let out = self.tx.unbounded_send(vec![diagnostic]).ok();
170 out
171 }
172
173 pub fn push_all(&self, diagnostics: Vec<SimpleDiagnostic>) -> Option<()> {
174 self.tx.unbounded_send(diagnostics).ok()
175 }
176}
177
178pub fn publish_diagnostics<L: Lang>(
179 query: Query<
180 (
181 &Errors<L::TokenError>,
182 &Errors<L::ElementError>,
183 &Wrapped<TextDocumentItem>,
184 &RopeC,
185 &crate::components::Label,
186 ),
187 (
188 Or<(
189 Changed<Errors<L::TokenError>>,
190 Changed<Errors<L::ElementError>>,
191 )>,
192 With<Open>,
193 ),
194 >,
195 mut client: ResMut<DiagnosticPublisher>,
196) where
197 L::TokenError: 'static + Clone,
198 L::ElementError: 'static + Clone,
199{
200 for (token_errors, element_errors, params, rope, label) in &query {
201 tracing::info!("Publish diagnostics for {}", label.0);
202 use std::iter::Iterator as _;
203 let token_iter = token_errors
204 .0
205 .iter()
206 .cloned()
207 .map(|x| Into::<SimpleDiagnostic>::into(x));
208 let turtle_iter = element_errors
209 .0
210 .iter()
211 .cloned()
212 .map(|x| Into::<SimpleDiagnostic>::into(x));
213
214 let diagnostics: Vec<_> = Iterator::chain(token_iter, turtle_iter)
215 .flat_map(|item| {
216 let (span, message) = (item.range, item.msg);
217 let start_position = offset_to_position(span.start, &rope.0)?;
218 let end_position = offset_to_position(span.end, &rope.0)?;
219 Some(Diagnostic {
220 range: lsp_types::Range::new(start_position, end_position),
221 message,
222 severity: item.severity,
223 ..Default::default()
224 })
225 })
226 .collect();
227
228 let _ = client.publish(¶ms.0, diagnostics, "syntax");
229 }
230}