1use std::{
2 cmp::Ordering,
3 ops::{Deref, Range},
4};
5
6use gpui::SharedString;
7use ropey::Rope;
8use sum_tree::{Bias, SeekTarget, SumTree};
9
10use crate::input::{Position, RopeExt as _};
11
12pub type DiagnosticRelatedInformation = lsp_types::DiagnosticRelatedInformation;
13pub(super) type CodeDescription = lsp_types::CodeDescription;
14pub type RelatedInformation = lsp_types::DiagnosticRelatedInformation;
15pub type DiagnosticTag = lsp_types::DiagnosticTag;
16
17#[derive(Debug, Eq, PartialEq, Clone, Default)]
18pub struct Diagnostic {
19 pub range: Range<Position>,
23
24 pub severity: DiagnosticSeverity,
27
28 pub code: Option<SharedString>,
30
31 pub code_description: Option<CodeDescription>,
32
33 pub source: Option<SharedString>,
36
37 pub message: SharedString,
39
40 pub related_information: Option<Vec<DiagnosticRelatedInformation>>,
43
44 pub tags: Option<Vec<DiagnosticTag>>,
46
47 pub data: Option<serde_json::Value>,
52}
53
54impl From<lsp_types::Diagnostic> for Diagnostic {
55 fn from(value: lsp_types::Diagnostic) -> Self {
56 Self {
57 range: value.range.start..value.range.end,
58 severity: value
59 .severity
60 .map(Into::into)
61 .unwrap_or(DiagnosticSeverity::Info),
62 code: value.code.map(|c| match c {
63 lsp_types::NumberOrString::Number(n) => SharedString::from(n.to_string()),
64 lsp_types::NumberOrString::String(s) => SharedString::from(s),
65 }),
66 code_description: value.code_description,
67 source: value.source.map(|s| s.into()),
68 message: value.message.into(),
69 related_information: value.related_information,
70 tags: value.tags,
71 data: value.data,
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
77pub enum DiagnosticSeverity {
78 #[default]
79 Hint,
80 Error,
81 Warning,
82 Info,
83}
84
85impl From<lsp_types::DiagnosticSeverity> for DiagnosticSeverity {
86 fn from(value: lsp_types::DiagnosticSeverity) -> Self {
87 match value {
88 lsp_types::DiagnosticSeverity::ERROR => Self::Error,
89 lsp_types::DiagnosticSeverity::WARNING => Self::Warning,
90 lsp_types::DiagnosticSeverity::INFORMATION => Self::Info,
91 lsp_types::DiagnosticSeverity::HINT => Self::Hint,
92 _ => Self::Info, }
94 }
95}
96
97impl Diagnostic {
98 pub fn new(range: Range<impl Into<Position>>, message: impl Into<SharedString>) -> Self {
99 Self {
100 range: range.start.into()..range.end.into(),
101 message: message.into(),
102 ..Default::default()
103 }
104 }
105
106 pub fn with_severity(mut self, severity: impl Into<DiagnosticSeverity>) -> Self {
107 self.severity = severity.into();
108 self
109 }
110
111 pub fn with_code(mut self, code: impl Into<SharedString>) -> Self {
112 self.code = Some(code.into());
113 self
114 }
115
116 pub fn with_source(mut self, source: impl Into<SharedString>) -> Self {
117 self.source = Some(source.into());
118 self
119 }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Default)]
123pub struct DiagnosticEntry {
124 pub range: Range<usize>,
126 pub diagnostic: Diagnostic,
127}
128
129impl Deref for DiagnosticEntry {
130 type Target = Diagnostic;
131
132 fn deref(&self) -> &Self::Target {
133 &self.diagnostic
134 }
135}
136
137#[derive(Debug, Default, Clone)]
138pub struct DiagnosticSummary {
139 count: usize,
140 start: usize,
141 end: usize,
142}
143
144impl sum_tree::Item for DiagnosticEntry {
145 type Summary = DiagnosticSummary;
146 fn summary(&self, _cx: &()) -> Self::Summary {
147 DiagnosticSummary {
148 count: 1,
149 start: self.range.start,
150 end: self.range.end,
151 }
152 }
153}
154
155impl sum_tree::Summary for DiagnosticSummary {
156 type Context<'a> = &'a ();
157 fn zero(_: Self::Context<'_>) -> Self {
158 DiagnosticSummary {
159 count: 0,
160 start: usize::MIN,
161 end: usize::MIN,
162 }
163 }
164
165 fn add_summary(&mut self, other: &Self, _: Self::Context<'_>) {
166 self.start = other.start;
167 self.end = other.end;
168 self.count += other.count;
169 }
170}
171
172impl SeekTarget<'_, DiagnosticSummary, DiagnosticSummary> for usize {
174 fn cmp(&self, other: &DiagnosticSummary, _: &()) -> Ordering {
175 if *self < other.start {
176 Ordering::Less
177 } else if *self > other.end {
178 Ordering::Greater
179 } else {
180 Ordering::Equal
181 }
182 }
183}
184
185#[derive(Debug, Clone)]
186pub struct DiagnosticSet {
187 text: Rope,
188 diagnostics: SumTree<DiagnosticEntry>,
189}
190
191impl DiagnosticSet {
192 pub fn new(text: &Rope) -> Self {
193 Self {
194 text: text.clone(),
195 diagnostics: SumTree::new(&()),
196 }
197 }
198
199 pub fn reset(&mut self, text: &Rope) {
200 self.text = text.clone();
201 self.clear();
202 }
203
204 pub fn push(&mut self, diagnostic: impl Into<Diagnostic>) {
205 let diagnostic = diagnostic.into();
206 let start = self.text.position_to_offset(&diagnostic.range.start);
207 let end = self.text.position_to_offset(&diagnostic.range.end);
208
209 self.diagnostics.push(
210 DiagnosticEntry {
211 range: start..end,
212 diagnostic,
213 },
214 &(),
215 );
216 }
217
218 pub fn extend<D, I>(&mut self, diagnostics: D)
219 where
220 D: IntoIterator<Item = I>,
221 I: Into<Diagnostic>,
222 {
223 for diagnostic in diagnostics {
224 self.push(diagnostic.into());
225 }
226 }
227
228 pub fn len(&self) -> usize {
229 self.diagnostics.summary().count
230 }
231
232 pub fn clear(&mut self) {
233 self.diagnostics = SumTree::new(&());
234 }
235
236 pub fn is_empty(&self) -> bool {
237 self.diagnostics.is_empty()
238 }
239
240 pub fn range(&self, range: Range<usize>) -> impl Iterator<Item = &DiagnosticEntry> {
241 let mut cursor = self.diagnostics.cursor::<DiagnosticSummary>(&());
242 cursor.seek(&range.start, Bias::Left);
243 std::iter::from_fn(move || {
244 if let Some(entry) = cursor.item() {
245 if entry.range.start < range.end {
246 cursor.next();
247 return Some(entry);
248 }
249 }
250 None
251 })
252 }
253
254 pub fn for_offset(&self, offset: usize) -> Option<&DiagnosticEntry> {
255 self.range(offset..offset + 1).next()
256 }
257
258 #[allow(unused)]
259 pub fn iter(&self) -> impl Iterator<Item = &DiagnosticEntry> {
260 self.diagnostics.iter()
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use crate::input::Position;
267
268 #[test]
269 fn test_diagnostic() {
270 use ropey::Rope;
271
272 use super::{Diagnostic, DiagnosticSet, DiagnosticSeverity};
273
274 let text = Rope::from("Hello, 你好warld!\nThis is a test.\nGoodbye, world!");
275 let mut diagnostics = DiagnosticSet::new(&text);
276
277 diagnostics.push(
278 Diagnostic::new(
279 Position::new(0, 7)..Position::new(0, 17),
280 "Spelling mistake",
281 )
282 .with_severity(DiagnosticSeverity::Warning),
283 );
284 diagnostics.push(
285 Diagnostic::new(Position::new(2, 9)..Position::new(2, 14), "Syntax error")
286 .with_severity(DiagnosticSeverity::Error),
287 );
288
289 assert_eq!(diagnostics.len(), 2);
290 let items = diagnostics.iter().collect::<Vec<_>>();
291
292 assert_eq!(items[0].message.as_str(), "Spelling mistake");
293 assert_eq!(items[0].range, 7..19);
294
295 assert_eq!(items[1].message.as_str(), "Syntax error");
296 assert_eq!(items[1].range, 45..50);
297
298 let items = diagnostics.range(6..48).collect::<Vec<_>>();
299 assert_eq!(items.len(), 2);
300
301 let item = diagnostics.for_offset(10).unwrap();
302 assert_eq!(item.message.as_str(), "Spelling mistake");
303
304 let item = diagnostics.for_offset(30);
305 assert!(item.is_none());
306
307 let item = diagnostics.for_offset(46).unwrap();
308 assert_eq!(item.message.as_str(), "Syntax error");
309
310 diagnostics.push(
311 Diagnostic::new(Position::new(1, 5)..Position::new(1, 7), "Info message")
312 .with_severity(DiagnosticSeverity::Info),
313 );
314 assert_eq!(diagnostics.len(), 3);
315
316 diagnostics.clear();
317 assert_eq!(diagnostics.len(), 0);
318 }
319}