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