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        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}