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