1use crate::Diagnostic;
2use collections::HashMap;
3use lsp::LanguageServerId;
4use std::{
5 cmp::{Ordering, Reverse},
6 iter,
7 ops::Range,
8};
9use sum_tree::{self, Bias, SumTree};
10use text::{Anchor, FromAnchor, PointUtf16, ToOffset};
11
12/// A set of diagnostics associated with a given buffer, provided
13/// by a single language server.
14///
15/// The diagnostics are stored in a [`SumTree`], which allows this struct
16/// to be cheaply copied, and allows for efficient retrieval of the
17/// diagnostics that intersect a given range of the buffer.
18#[derive(Clone, Debug, Default)]
19pub struct DiagnosticSet {
20 diagnostics: SumTree<DiagnosticEntry<Anchor>>,
21}
22
23/// A single diagnostic in a set. Generic over its range type, because
24/// the diagnostics are stored internally as [`Anchor`]s, but can be
25/// resolved to different coordinates types like [`usize`] byte offsets or
26/// [`Point`](gpui::Point)s.
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct DiagnosticEntry<T> {
29 /// The range of the buffer where the diagnostic applies.
30 pub range: Range<T>,
31 /// The information about the diagnostic.
32 pub diagnostic: Diagnostic,
33}
34
35/// A group of related diagnostics, ordered by their start position
36/// in the buffer.
37#[derive(Debug)]
38pub struct DiagnosticGroup<T> {
39 /// The diagnostics.
40 pub entries: Vec<DiagnosticEntry<T>>,
41 /// The index into `entries` where the primary diagnostic is stored.
42 pub primary_ix: usize,
43}
44
45#[derive(Clone, Debug)]
46pub struct Summary {
47 start: Anchor,
48 end: Anchor,
49 min_start: Anchor,
50 max_end: Anchor,
51 count: usize,
52}
53
54impl<T> DiagnosticEntry<T> {
55 /// Returns a raw LSP diagnostic ssed to provide diagnostic context to LSP
56 /// codeAction request
57 pub fn to_lsp_diagnostic_stub(&self) -> lsp::Diagnostic {
58 let code = self
59 .diagnostic
60 .code
61 .clone()
62 .map(lsp::NumberOrString::String);
63
64 lsp::Diagnostic {
65 code,
66 severity: Some(self.diagnostic.severity),
67 ..Default::default()
68 }
69 }
70}
71
72impl DiagnosticSet {
73 /// Constructs a [DiagnosticSet] from a sequence of entries, ordered by
74 /// their position in the buffer.
75 pub fn from_sorted_entries<I>(iter: I, buffer: &text::BufferSnapshot) -> Self
76 where
77 I: IntoIterator<Item = DiagnosticEntry<Anchor>>,
78 {
79 Self {
80 diagnostics: SumTree::from_iter(iter, buffer),
81 }
82 }
83
84 /// Constructs a [DiagnosticSet] from a sequence of entries in an arbitrary order.
85 pub fn new<I>(iter: I, buffer: &text::BufferSnapshot) -> Self
86 where
87 I: IntoIterator<Item = DiagnosticEntry<PointUtf16>>,
88 {
89 let mut entries = iter.into_iter().collect::<Vec<_>>();
90 entries.sort_unstable_by_key(|entry| (entry.range.start, Reverse(entry.range.end)));
91 Self {
92 diagnostics: SumTree::from_iter(
93 entries.into_iter().map(|entry| DiagnosticEntry {
94 range: buffer.anchor_before(entry.range.start)
95 ..buffer.anchor_before(entry.range.end),
96 diagnostic: entry.diagnostic,
97 }),
98 buffer,
99 ),
100 }
101 }
102
103 /// Returns the number of diagnostics in the set.
104 pub fn len(&self) -> usize {
105 self.diagnostics.summary().count
106 }
107
108 /// Returns an iterator over the diagnostic entries in the set.
109 pub fn iter(&self) -> impl Iterator<Item = &DiagnosticEntry<Anchor>> {
110 self.diagnostics.iter()
111 }
112
113 /// Returns an iterator over the diagnostic entries that intersect the
114 /// given range of the buffer.
115 pub fn range<'a, T, O>(
116 &'a self,
117 range: Range<T>,
118 buffer: &'a text::BufferSnapshot,
119 inclusive: bool,
120 reversed: bool,
121 ) -> impl 'a + Iterator<Item = DiagnosticEntry<O>>
122 where
123 T: 'a + ToOffset,
124 O: FromAnchor,
125 {
126 let end_bias = if inclusive { Bias::Right } else { Bias::Left };
127 let range = buffer.anchor_before(range.start)..buffer.anchor_at(range.end, end_bias);
128 let mut cursor = self.diagnostics.filter::<_, ()>({
129 move |summary: &Summary| {
130 let start_cmp = range.start.cmp(&summary.max_end, buffer);
131 let end_cmp = range.end.cmp(&summary.min_start, buffer);
132 if inclusive {
133 start_cmp <= Ordering::Equal && end_cmp >= Ordering::Equal
134 } else {
135 start_cmp == Ordering::Less && end_cmp == Ordering::Greater
136 }
137 }
138 });
139
140 if reversed {
141 cursor.prev(buffer);
142 } else {
143 cursor.next(buffer);
144 }
145 iter::from_fn({
146 move || {
147 if let Some(diagnostic) = cursor.item() {
148 if reversed {
149 cursor.prev(buffer);
150 } else {
151 cursor.next(buffer);
152 }
153 Some(diagnostic.resolve(buffer))
154 } else {
155 None
156 }
157 }
158 })
159 }
160
161 /// Adds all of this set's diagnostic groups to the given output vector.
162 pub fn groups(
163 &self,
164 language_server_id: LanguageServerId,
165 output: &mut Vec<(LanguageServerId, DiagnosticGroup<Anchor>)>,
166 buffer: &text::BufferSnapshot,
167 ) {
168 let mut groups = HashMap::default();
169 for entry in self.diagnostics.iter() {
170 groups
171 .entry(entry.diagnostic.group_id)
172 .or_insert(Vec::new())
173 .push(entry.clone());
174 }
175
176 let start_ix = output.len();
177 output.extend(groups.into_values().filter_map(|mut entries| {
178 entries.sort_unstable_by(|a, b| a.range.start.cmp(&b.range.start, buffer));
179 entries
180 .iter()
181 .position(|entry| entry.diagnostic.is_primary)
182 .map(|primary_ix| {
183 (
184 language_server_id,
185 DiagnosticGroup {
186 entries,
187 primary_ix,
188 },
189 )
190 })
191 }));
192 output[start_ix..].sort_unstable_by(|(id_a, group_a), (id_b, group_b)| {
193 group_a.entries[group_a.primary_ix]
194 .range
195 .start
196 .cmp(&group_b.entries[group_b.primary_ix].range.start, buffer)
197 .then_with(|| id_a.cmp(id_b))
198 });
199 }
200
201 /// Returns all of the diagnostics in a particular diagnostic group,
202 /// in order of their position in the buffer.
203 pub fn group<'a, O: FromAnchor>(
204 &'a self,
205 group_id: usize,
206 buffer: &'a text::BufferSnapshot,
207 ) -> impl 'a + Iterator<Item = DiagnosticEntry<O>> {
208 self.iter()
209 .filter(move |entry| entry.diagnostic.group_id == group_id)
210 .map(|entry| entry.resolve(buffer))
211 }
212}
213
214impl sum_tree::Item for DiagnosticEntry<Anchor> {
215 type Summary = Summary;
216
217 fn summary(&self) -> Self::Summary {
218 Summary {
219 start: self.range.start,
220 end: self.range.end,
221 min_start: self.range.start,
222 max_end: self.range.end,
223 count: 1,
224 }
225 }
226}
227
228impl DiagnosticEntry<Anchor> {
229 /// Converts the [DiagnosticEntry] to a different buffer coordinate type.
230 pub fn resolve<O: FromAnchor>(&self, buffer: &text::BufferSnapshot) -> DiagnosticEntry<O> {
231 DiagnosticEntry {
232 range: O::from_anchor(&self.range.start, buffer)
233 ..O::from_anchor(&self.range.end, buffer),
234 diagnostic: self.diagnostic.clone(),
235 }
236 }
237}
238
239impl Default for Summary {
240 fn default() -> Self {
241 Self {
242 start: Anchor::MIN,
243 end: Anchor::MAX,
244 min_start: Anchor::MAX,
245 max_end: Anchor::MIN,
246 count: 0,
247 }
248 }
249}
250
251impl sum_tree::Summary for Summary {
252 type Context = text::BufferSnapshot;
253
254 fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
255 if other.min_start.cmp(&self.min_start, buffer).is_lt() {
256 self.min_start = other.min_start;
257 }
258 if other.max_end.cmp(&self.max_end, buffer).is_gt() {
259 self.max_end = other.max_end;
260 }
261 self.start = other.start;
262 self.end = other.end;
263 self.count += other.count;
264 }
265}