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 update_on_edit: bool,
126 for_server_id: Option<LanguageServerId>,
127 buffer_id: Option<BufferId>,
128 _: &Window,
129 cx: &mut Context<Self>,
130 ) {
131 if !self.mode().is_full() {
132 return;
133 }
134 let Some(project) = self.project.clone() else {
135 return;
136 };
137 if self
138 .colors
139 .as_ref()
140 .is_none_or(|colors| colors.render_mode == DocumentColorsRenderMode::None)
141 {
142 return;
143 }
144
145 let all_colors_task = project.read(cx).lsp_store().update(cx, |lsp_store, cx| {
146 self.buffer()
147 .update(cx, |multi_buffer, cx| {
148 multi_buffer
149 .all_buffers()
150 .into_iter()
151 .filter(|editor_buffer| {
152 buffer_id.is_none_or(|buffer_id| {
153 buffer_id == editor_buffer.read(cx).remote_id()
154 })
155 })
156 .collect::<Vec<_>>()
157 })
158 .into_iter()
159 .filter_map(|buffer| {
160 let buffer_id = buffer.read(cx).remote_id();
161 let colors_task =
162 lsp_store.document_colors(update_on_edit, for_server_id, buffer, cx)?;
163 Some(async move { (buffer_id, colors_task.await) })
164 })
165 .collect::<Vec<_>>()
166 });
167 cx.spawn(async move |editor, cx| {
168 let all_colors = join_all(all_colors_task).await;
169 let Ok((multi_buffer_snapshot, editor_excerpts)) = editor.update(cx, |editor, cx| {
170 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
171 let editor_excerpts = multi_buffer_snapshot.excerpts().fold(
172 HashMap::default(),
173 |mut acc, (excerpt_id, buffer_snapshot, excerpt_range)| {
174 let excerpt_data = acc
175 .entry(buffer_snapshot.remote_id())
176 .or_insert_with(Vec::new);
177 let excerpt_point_range =
178 excerpt_range.context.to_point_utf16(&buffer_snapshot);
179 excerpt_data.push((
180 excerpt_id,
181 buffer_snapshot.clone(),
182 excerpt_point_range,
183 ));
184 acc
185 },
186 );
187 (multi_buffer_snapshot, editor_excerpts)
188 }) else {
189 return;
190 };
191
192 let mut new_editor_colors = Vec::<(Range<Anchor>, DocumentColor)>::new();
193 for (buffer_id, colors) in all_colors {
194 let Some(excerpts) = editor_excerpts.get(&buffer_id) else {
195 continue;
196 };
197 match colors {
198 Ok(colors) => {
199 for color in colors {
200 let color_start = point_from_lsp(color.lsp_range.start);
201 let color_end = point_from_lsp(color.lsp_range.end);
202
203 for (excerpt_id, buffer_snapshot, excerpt_range) in excerpts {
204 if !excerpt_range.contains(&color_start.0)
205 || !excerpt_range.contains(&color_end.0)
206 {
207 continue;
208 }
209 let Some(color_start_anchor) = multi_buffer_snapshot
210 .anchor_in_excerpt(
211 *excerpt_id,
212 buffer_snapshot.anchor_before(
213 buffer_snapshot
214 .clip_point_utf16(color_start, Bias::Left),
215 ),
216 )
217 else {
218 continue;
219 };
220 let Some(color_end_anchor) = multi_buffer_snapshot
221 .anchor_in_excerpt(
222 *excerpt_id,
223 buffer_snapshot.anchor_after(
224 buffer_snapshot
225 .clip_point_utf16(color_end, Bias::Right),
226 ),
227 )
228 else {
229 continue;
230 };
231
232 let (Ok(i) | Err(i)) =
233 new_editor_colors.binary_search_by(|(probe, _)| {
234 probe
235 .start
236 .cmp(&color_start_anchor, &multi_buffer_snapshot)
237 .then_with(|| {
238 probe
239 .end
240 .cmp(&color_end_anchor, &multi_buffer_snapshot)
241 })
242 });
243 new_editor_colors
244 .insert(i, (color_start_anchor..color_end_anchor, color));
245 break;
246 }
247 }
248 }
249 Err(e) => log::error!("Failed to retrieve document colors: {e}"),
250 }
251 }
252
253 editor
254 .update(cx, |editor, cx| {
255 let mut colors_splice = InlaySplice::default();
256 let mut new_color_inlays = Vec::with_capacity(new_editor_colors.len());
257 let Some(colors) = &mut editor.colors else {
258 return;
259 };
260 let mut existing_colors = colors.colors.iter().peekable();
261 for (new_range, new_color) in new_editor_colors {
262 let rgba_color = Rgba {
263 r: new_color.color.red,
264 g: new_color.color.green,
265 b: new_color.color.blue,
266 a: new_color.color.alpha,
267 };
268
269 loop {
270 match existing_colors.peek() {
271 Some((existing_range, existing_color, existing_inlay_id)) => {
272 match existing_range
273 .start
274 .cmp(&new_range.start, &multi_buffer_snapshot)
275 .then_with(|| {
276 existing_range
277 .end
278 .cmp(&new_range.end, &multi_buffer_snapshot)
279 }) {
280 cmp::Ordering::Less => {
281 colors_splice.to_remove.push(*existing_inlay_id);
282 existing_colors.next();
283 continue;
284 }
285 cmp::Ordering::Equal => {
286 if existing_color == &new_color {
287 new_color_inlays.push((
288 new_range,
289 new_color,
290 *existing_inlay_id,
291 ));
292 } else {
293 colors_splice.to_remove.push(*existing_inlay_id);
294
295 let inlay = Inlay::color(
296 post_inc(&mut editor.next_color_inlay_id),
297 new_range.start,
298 rgba_color,
299 );
300 let inlay_id = inlay.id;
301 colors_splice.to_insert.push(inlay);
302 new_color_inlays
303 .push((new_range, new_color, inlay_id));
304 }
305 existing_colors.next();
306 break;
307 }
308 cmp::Ordering::Greater => {
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.push((new_range, new_color, inlay_id));
317 break;
318 }
319 }
320 }
321 None => {
322 let inlay = Inlay::color(
323 post_inc(&mut editor.next_color_inlay_id),
324 new_range.start,
325 rgba_color,
326 );
327 let inlay_id = inlay.id;
328 colors_splice.to_insert.push(inlay);
329 new_color_inlays.push((new_range, new_color, inlay_id));
330 break;
331 }
332 }
333 }
334 }
335 if existing_colors.peek().is_some() {
336 colors_splice
337 .to_remove
338 .extend(existing_colors.map(|(_, _, id)| *id));
339 }
340
341 let mut updated = colors.set_colors(new_color_inlays);
342 if colors.render_mode == DocumentColorsRenderMode::Inlay
343 && (!colors_splice.to_insert.is_empty()
344 || !colors_splice.to_remove.is_empty())
345 {
346 editor.splice_inlays(&colors_splice.to_remove, colors_splice.to_insert, cx);
347 updated = true;
348 }
349
350 if updated {
351 cx.notify();
352 }
353 })
354 .ok();
355 })
356 .detach();
357 }
358}