1use std::{cmp, ops::Range};
2
3use collections::HashMap;
4use futures::future::join_all;
5use gpui::{Hsla, Rgba};
6use language::point_from_lsp;
7use lsp::LanguageServerId;
8use multi_buffer::Anchor;
9use project::DocumentColor;
10use settings::Settings as _;
11use text::{Bias, BufferId, OffsetRangeExt as _};
12use ui::{App, Context, Window};
13use util::post_inc;
14
15use crate::{
16 DisplayPoint, Editor, EditorSettings, EditorSnapshot, InlayId, InlaySplice, RangeToAnchorExt,
17 display_map::Inlay, editor_settings::DocumentColorsRenderMode,
18};
19
20#[derive(Debug)]
21pub(super) struct LspColorData {
22 colors: Vec<(Range<Anchor>, DocumentColor, InlayId)>,
23 inlay_colors: HashMap<InlayId, usize>,
24 render_mode: DocumentColorsRenderMode,
25}
26
27impl LspColorData {
28 pub fn new(cx: &App) -> Self {
29 Self {
30 colors: Vec::new(),
31 inlay_colors: HashMap::default(),
32 render_mode: EditorSettings::get_global(cx).lsp_document_colors,
33 }
34 }
35
36 pub fn render_mode_updated(
37 &mut self,
38 new_render_mode: DocumentColorsRenderMode,
39 ) -> Option<InlaySplice> {
40 if self.render_mode == new_render_mode {
41 return None;
42 }
43 self.render_mode = new_render_mode;
44 match new_render_mode {
45 DocumentColorsRenderMode::Inlay => Some(InlaySplice {
46 to_remove: Vec::new(),
47 to_insert: self
48 .colors
49 .iter()
50 .map(|(range, color, id)| {
51 Inlay::color(
52 id.id(),
53 range.start,
54 Rgba {
55 r: color.color.red,
56 g: color.color.green,
57 b: color.color.blue,
58 a: color.color.alpha,
59 },
60 )
61 })
62 .collect(),
63 }),
64 DocumentColorsRenderMode::None => {
65 self.colors.clear();
66 Some(InlaySplice {
67 to_remove: self.inlay_colors.drain().map(|(id, _)| id).collect(),
68 to_insert: Vec::new(),
69 })
70 }
71 DocumentColorsRenderMode::Border | DocumentColorsRenderMode::Background => {
72 Some(InlaySplice {
73 to_remove: self.inlay_colors.drain().map(|(id, _)| id).collect(),
74 to_insert: Vec::new(),
75 })
76 }
77 }
78 }
79
80 fn set_colors(&mut self, colors: Vec<(Range<Anchor>, DocumentColor, InlayId)>) -> bool {
81 if self.colors == colors {
82 return false;
83 }
84
85 self.inlay_colors = colors
86 .iter()
87 .enumerate()
88 .map(|(i, (_, _, id))| (*id, i))
89 .collect();
90 self.colors = colors;
91 true
92 }
93
94 pub fn editor_display_highlights(
95 &self,
96 snapshot: &EditorSnapshot,
97 ) -> (DocumentColorsRenderMode, Vec<(Range<DisplayPoint>, Hsla)>) {
98 let render_mode = self.render_mode;
99 let highlights = if render_mode == DocumentColorsRenderMode::None
100 || render_mode == DocumentColorsRenderMode::Inlay
101 {
102 Vec::new()
103 } else {
104 self.colors
105 .iter()
106 .map(|(range, color, _)| {
107 let display_range = range.clone().to_display_points(snapshot);
108 let color = Hsla::from(Rgba {
109 r: color.color.red,
110 g: color.color.green,
111 b: color.color.blue,
112 a: color.color.alpha,
113 });
114 (display_range, color)
115 })
116 .collect()
117 };
118 (render_mode, highlights)
119 }
120}
121
122impl Editor {
123 pub(super) fn refresh_colors(
124 &mut self,
125 for_server_id: Option<LanguageServerId>,
126 buffer_id: Option<BufferId>,
127 _: &Window,
128 cx: &mut Context<Self>,
129 ) {
130 if !self.mode().is_full() {
131 return;
132 }
133 let Some(project) = self.project.clone() else {
134 return;
135 };
136 if self
137 .colors
138 .as_ref()
139 .is_none_or(|colors| colors.render_mode == DocumentColorsRenderMode::None)
140 {
141 return;
142 }
143
144 let all_colors_task = project.read(cx).lsp_store().update(cx, |lsp_store, cx| {
145 self.buffer()
146 .update(cx, |multi_buffer, cx| {
147 multi_buffer
148 .all_buffers()
149 .into_iter()
150 .filter(|editor_buffer| {
151 buffer_id.is_none_or(|buffer_id| {
152 buffer_id == editor_buffer.read(cx).remote_id()
153 })
154 })
155 .collect::<Vec<_>>()
156 })
157 .into_iter()
158 .filter_map(|buffer| {
159 let buffer_id = buffer.read(cx).remote_id();
160 let colors_task = lsp_store.document_colors(for_server_id, buffer, cx)?;
161 Some(async move { (buffer_id, colors_task.await) })
162 })
163 .collect::<Vec<_>>()
164 });
165 cx.spawn(async move |editor, cx| {
166 let all_colors = join_all(all_colors_task).await;
167 let Ok((multi_buffer_snapshot, editor_excerpts)) = editor.update(cx, |editor, cx| {
168 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
169 let editor_excerpts = multi_buffer_snapshot.excerpts().fold(
170 HashMap::default(),
171 |mut acc, (excerpt_id, buffer_snapshot, excerpt_range)| {
172 let excerpt_data = acc
173 .entry(buffer_snapshot.remote_id())
174 .or_insert_with(Vec::new);
175 let excerpt_point_range =
176 excerpt_range.context.to_point_utf16(&buffer_snapshot);
177 excerpt_data.push((
178 excerpt_id,
179 buffer_snapshot.clone(),
180 excerpt_point_range,
181 ));
182 acc
183 },
184 );
185 (multi_buffer_snapshot, editor_excerpts)
186 }) else {
187 return;
188 };
189
190 let mut new_editor_colors = Vec::<(Range<Anchor>, DocumentColor)>::new();
191 for (buffer_id, colors) in all_colors {
192 let Some(excerpts) = editor_excerpts.get(&buffer_id) else {
193 continue;
194 };
195 match colors {
196 Ok(colors) => {
197 for color in colors {
198 let color_start = point_from_lsp(color.lsp_range.start);
199 let color_end = point_from_lsp(color.lsp_range.end);
200
201 for (excerpt_id, buffer_snapshot, excerpt_range) in excerpts {
202 if !excerpt_range.contains(&color_start.0)
203 || !excerpt_range.contains(&color_end.0)
204 {
205 continue;
206 }
207 let Some(color_start_anchor) = multi_buffer_snapshot
208 .anchor_in_excerpt(
209 *excerpt_id,
210 buffer_snapshot.anchor_before(
211 buffer_snapshot
212 .clip_point_utf16(color_start, Bias::Left),
213 ),
214 )
215 else {
216 continue;
217 };
218 let Some(color_end_anchor) = multi_buffer_snapshot
219 .anchor_in_excerpt(
220 *excerpt_id,
221 buffer_snapshot.anchor_after(
222 buffer_snapshot
223 .clip_point_utf16(color_end, Bias::Right),
224 ),
225 )
226 else {
227 continue;
228 };
229
230 let (Ok(i) | Err(i)) =
231 new_editor_colors.binary_search_by(|(probe, _)| {
232 probe
233 .start
234 .cmp(&color_start_anchor, &multi_buffer_snapshot)
235 .then_with(|| {
236 probe
237 .end
238 .cmp(&color_end_anchor, &multi_buffer_snapshot)
239 })
240 });
241 new_editor_colors
242 .insert(i, (color_start_anchor..color_end_anchor, color));
243 break;
244 }
245 }
246 }
247 Err(e) => log::error!("Failed to retrieve document colors: {e}"),
248 }
249 }
250
251 editor
252 .update(cx, |editor, cx| {
253 let mut colors_splice = InlaySplice::default();
254 let mut new_color_inlays = Vec::with_capacity(new_editor_colors.len());
255 let Some(colors) = &mut editor.colors else {
256 return;
257 };
258 let mut existing_colors = colors.colors.iter().peekable();
259 for (new_range, new_color) in new_editor_colors {
260 let rgba_color = Rgba {
261 r: new_color.color.red,
262 g: new_color.color.green,
263 b: new_color.color.blue,
264 a: new_color.color.alpha,
265 };
266
267 loop {
268 match existing_colors.peek() {
269 Some((existing_range, existing_color, existing_inlay_id)) => {
270 match existing_range
271 .start
272 .cmp(&new_range.start, &multi_buffer_snapshot)
273 .then_with(|| {
274 existing_range
275 .end
276 .cmp(&new_range.end, &multi_buffer_snapshot)
277 }) {
278 cmp::Ordering::Less => {
279 colors_splice.to_remove.push(*existing_inlay_id);
280 existing_colors.next();
281 continue;
282 }
283 cmp::Ordering::Equal => {
284 if existing_color == &new_color {
285 new_color_inlays.push((
286 new_range,
287 new_color,
288 *existing_inlay_id,
289 ));
290 } else {
291 colors_splice.to_remove.push(*existing_inlay_id);
292
293 let inlay = Inlay::color(
294 post_inc(&mut editor.next_color_inlay_id),
295 new_range.start,
296 rgba_color,
297 );
298 let inlay_id = inlay.id;
299 colors_splice.to_insert.push(inlay);
300 new_color_inlays
301 .push((new_range, new_color, inlay_id));
302 }
303 existing_colors.next();
304 break;
305 }
306 cmp::Ordering::Greater => {
307 let inlay = Inlay::color(
308 post_inc(&mut editor.next_color_inlay_id),
309 new_range.start,
310 rgba_color,
311 );
312 let inlay_id = inlay.id;
313 colors_splice.to_insert.push(inlay);
314 new_color_inlays.push((new_range, new_color, inlay_id));
315 break;
316 }
317 }
318 }
319 None => {
320 let inlay = Inlay::color(
321 post_inc(&mut editor.next_color_inlay_id),
322 new_range.start,
323 rgba_color,
324 );
325 let inlay_id = inlay.id;
326 colors_splice.to_insert.push(inlay);
327 new_color_inlays.push((new_range, new_color, inlay_id));
328 break;
329 }
330 }
331 }
332 }
333 if existing_colors.peek().is_some() {
334 colors_splice
335 .to_remove
336 .extend(existing_colors.map(|(_, _, id)| *id));
337 }
338
339 let mut updated = colors.set_colors(new_color_inlays);
340 if colors.render_mode == DocumentColorsRenderMode::Inlay
341 && (!colors_splice.to_insert.is_empty()
342 || !colors_splice.to_remove.is_empty())
343 {
344 editor.splice_inlays(&colors_splice.to_remove, colors_splice.to_insert, cx);
345 updated = true;
346 }
347
348 if updated {
349 cx.notify();
350 }
351 })
352 .ok();
353 })
354 .detach();
355 }
356}