lsp_colors.rs

  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}