lsp_colors.rs

  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}