inlay_hint_cache.rs

  1use std::ops::Range;
  2
  3use crate::{
  4    editor_settings, scroll::ScrollAnchor, Anchor, Editor, ExcerptId, InlayId, MultiBuffer,
  5};
  6use anyhow::Context;
  7use clock::Global;
  8use gpui::{ModelHandle, Task, ViewContext};
  9use language::Buffer;
 10use log::error;
 11use project::{InlayHint, InlayHintKind};
 12
 13use collections::{hash_map, HashMap, HashSet};
 14use util::post_inc;
 15
 16#[derive(Debug, Copy, Clone)]
 17pub enum InlayRefreshReason {
 18    SettingsChange(editor_settings::InlayHints),
 19    Scroll(ScrollAnchor),
 20    VisibleExcerptsChange,
 21}
 22
 23#[derive(Debug, Default)]
 24pub struct InlayHintCache {
 25    inlay_hints: HashMap<InlayId, InlayHint>,
 26    hints_in_buffers: HashMap<u64, BufferHints<(Anchor, InlayId)>>,
 27    allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
 28}
 29
 30#[derive(Clone, Debug)]
 31struct BufferHints<I> {
 32    buffer_version: Global,
 33    hints_per_excerpt: HashMap<ExcerptId, ExcerptHints<I>>,
 34}
 35
 36#[derive(Clone, Debug)]
 37struct ExcerptHints<I> {
 38    cached_excerpt_offsets: Vec<Range<usize>>,
 39    hints: Vec<I>,
 40}
 41
 42impl<I> Default for ExcerptHints<I> {
 43    fn default() -> Self {
 44        Self {
 45            cached_excerpt_offsets: Vec::new(),
 46            hints: Vec::new(),
 47        }
 48    }
 49}
 50
 51impl<I> BufferHints<I> {
 52    fn new(buffer_version: Global) -> Self {
 53        Self {
 54            buffer_version,
 55            hints_per_excerpt: HashMap::default(),
 56        }
 57    }
 58}
 59
 60#[derive(Debug, Default)]
 61pub struct InlaySplice {
 62    pub to_remove: Vec<InlayId>,
 63    pub to_insert: Vec<(InlayId, Anchor, InlayHint)>,
 64}
 65
 66pub struct InlayHintQuery {
 67    pub buffer_id: u64,
 68    pub buffer_version: Global,
 69    pub excerpt_id: ExcerptId,
 70    pub excerpt_offset_query_range: Range<usize>,
 71}
 72
 73impl InlayHintCache {
 74    pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self {
 75        Self {
 76            allowed_hint_kinds: allowed_inlay_hint_types(inlay_hint_settings),
 77            hints_in_buffers: HashMap::default(),
 78            inlay_hints: HashMap::default(),
 79        }
 80    }
 81
 82    pub fn apply_settings(
 83        &mut self,
 84        inlay_hint_settings: editor_settings::InlayHints,
 85        currently_visible_ranges: Vec<(ModelHandle<Buffer>, Range<usize>, ExcerptId)>,
 86        mut currently_shown_inlay_hints: HashMap<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>,
 87        cx: &mut ViewContext<Editor>,
 88    ) -> Option<InlaySplice> {
 89        let new_allowed_hint_kinds = allowed_inlay_hint_types(inlay_hint_settings);
 90        if new_allowed_hint_kinds == self.allowed_hint_kinds {
 91            None
 92        } else {
 93            self.allowed_hint_kinds = new_allowed_hint_kinds;
 94            let mut to_remove = Vec::new();
 95            let mut to_insert = Vec::new();
 96
 97            let mut considered_hints =
 98                HashMap::<u64, HashMap<ExcerptId, HashSet<InlayId>>>::default();
 99            for (visible_buffer, _, visible_excerpt_id) in currently_visible_ranges {
100                let visible_buffer = visible_buffer.read(cx);
101                let visible_buffer_id = visible_buffer.remote_id();
102                match currently_shown_inlay_hints.entry(visible_buffer_id) {
103                    hash_map::Entry::Occupied(mut o) => {
104                        let shown_hints_per_excerpt = o.get_mut();
105                        for (_, shown_hint_id) in shown_hints_per_excerpt
106                            .remove(&visible_excerpt_id)
107                            .unwrap_or_default()
108                        {
109                            considered_hints
110                                .entry(visible_buffer_id)
111                                .or_default()
112                                .entry(visible_excerpt_id)
113                                .or_default()
114                                .insert(shown_hint_id);
115                            match self.inlay_hints.get(&shown_hint_id) {
116                                Some(shown_hint) => {
117                                    if !self.allowed_hint_kinds.contains(&shown_hint.kind) {
118                                        to_remove.push(shown_hint_id);
119                                    }
120                                }
121                                None => to_remove.push(shown_hint_id),
122                            }
123                        }
124                        if shown_hints_per_excerpt.is_empty() {
125                            o.remove();
126                        }
127                    }
128                    hash_map::Entry::Vacant(_) => {}
129                }
130            }
131
132            let reenabled_hints = self
133                .hints_in_buffers
134                .iter()
135                .filter_map(|(cached_buffer_id, cached_hints_per_excerpt)| {
136                    let considered_hints_in_excerpts = considered_hints.get(cached_buffer_id)?;
137                    let not_considered_cached_hints = cached_hints_per_excerpt
138                        .hints_per_excerpt
139                        .iter()
140                        .filter_map(|(cached_excerpt_id, cached_excerpt_hints)| {
141                            let considered_excerpt_hints =
142                                considered_hints_in_excerpts.get(&cached_excerpt_id)?;
143                            let not_considered_cached_hints = cached_excerpt_hints
144                                .hints
145                                .iter()
146                                .filter(|(_, cached_hint_id)| {
147                                    !considered_excerpt_hints.contains(cached_hint_id)
148                                })
149                                .copied();
150                            Some(not_considered_cached_hints)
151                        })
152                        .flatten();
153                    Some(not_considered_cached_hints)
154                })
155                .flatten()
156                .filter_map(|(cached_anchor, cached_inlay_id)| {
157                    Some((
158                        cached_anchor,
159                        cached_inlay_id,
160                        self.inlay_hints.get(&cached_inlay_id)?,
161                    ))
162                })
163                .filter(|(_, _, cached_inlay)| self.allowed_hint_kinds.contains(&cached_inlay.kind))
164                .map(|(cached_anchor, cached_inlay_id, reenabled_inlay)| {
165                    (cached_inlay_id, cached_anchor, reenabled_inlay.clone())
166                });
167            to_insert.extend(reenabled_hints);
168
169            to_remove.extend(
170                currently_shown_inlay_hints
171                    .into_iter()
172                    .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt)
173                    .flat_map(|(_, excerpt_hints)| excerpt_hints)
174                    .map(|(_, hint_id)| hint_id),
175            );
176
177            Some(InlaySplice {
178                to_remove,
179                to_insert,
180            })
181        }
182    }
183
184    pub fn clear(&mut self) -> Vec<InlayId> {
185        let ids_to_remove = self.inlay_hints.drain().map(|(id, _)| id).collect();
186        self.hints_in_buffers.clear();
187        ids_to_remove
188    }
189
190    pub fn append_hints(
191        &mut self,
192        multi_buffer: ModelHandle<MultiBuffer>,
193        ranges_to_add: impl Iterator<Item = InlayHintQuery>,
194        cx: &mut ViewContext<Editor>,
195    ) -> Task<anyhow::Result<InlaySplice>> {
196        let queries = ranges_to_add.filter_map(|additive_query| {
197            let Some(cached_buffer_hints) = self.hints_in_buffers.get(&additive_query.buffer_id)
198                else { return Some(vec![additive_query]) };
199            if cached_buffer_hints.buffer_version.changed_since(&additive_query.buffer_version) {
200                return None
201            }
202            let Some(excerpt_hints) = cached_buffer_hints.hints_per_excerpt.get(&additive_query.excerpt_id)
203                else { return Some(vec![additive_query]) };
204            let non_cached_ranges = missing_subranges(&excerpt_hints.cached_excerpt_offsets, &additive_query.excerpt_offset_query_range);
205            if non_cached_ranges.is_empty() {
206                None
207            } else {
208                Some(non_cached_ranges.into_iter().map(|non_cached_range| InlayHintQuery {
209                    buffer_id: additive_query.buffer_id,
210                    buffer_version: additive_query.buffer_version.clone(),
211                    excerpt_id: additive_query.excerpt_id,
212                    excerpt_offset_query_range: non_cached_range,
213                }).collect())
214            }
215        }).flatten();
216
217        let task_multi_buffer = multi_buffer.clone();
218        let fetch_queries_task = fetch_queries(multi_buffer, queries, cx);
219        cx.spawn(|editor, mut cx| async move {
220            let new_hints = fetch_queries_task.await?;
221            editor.update(&mut cx, |editor, cx| {
222                let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx);
223                let mut to_insert = Vec::new();
224                for (new_buffer_id, new_hints_per_buffer) in new_hints {
225                    let cached_buffer_hints = editor
226                        .inlay_hint_cache
227                        .hints_in_buffers
228                        .entry(new_buffer_id)
229                        .or_insert_with(|| {
230                            BufferHints::new(new_hints_per_buffer.buffer_version.clone())
231                        });
232                    if cached_buffer_hints
233                        .buffer_version
234                        .changed_since(&new_hints_per_buffer.buffer_version)
235                    {
236                        continue;
237                    }
238
239                    for (new_excerpt_id, new_excerpt_hints) in
240                        new_hints_per_buffer.hints_per_excerpt
241                    {
242                        let cached_excerpt_hints = cached_buffer_hints
243                            .hints_per_excerpt
244                            .entry(new_excerpt_id)
245                            .or_insert_with(|| ExcerptHints::default());
246                        for new_range in new_excerpt_hints.cached_excerpt_offsets {
247                            insert_and_merge_ranges(
248                                &mut cached_excerpt_hints.cached_excerpt_offsets,
249                                &new_range,
250                            )
251                        }
252                        for new_inlay_hint in new_excerpt_hints.hints {
253                            let hint_anchor = multi_buffer_snapshot
254                                .anchor_in_excerpt(new_excerpt_id, new_inlay_hint.position);
255                            let insert_ix =
256                                match cached_excerpt_hints.hints.binary_search_by(|probe| {
257                                    hint_anchor.cmp(&probe.0, &multi_buffer_snapshot)
258                                }) {
259                                    Ok(ix) | Err(ix) => ix,
260                                };
261
262                            let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id));
263                            cached_excerpt_hints
264                                .hints
265                                .insert(insert_ix, (hint_anchor, new_inlay_id));
266                            editor
267                                .inlay_hint_cache
268                                .inlay_hints
269                                .insert(new_inlay_id, new_inlay_hint.clone());
270                            if editor
271                                .inlay_hint_cache
272                                .allowed_hint_kinds
273                                .contains(&new_inlay_hint.kind)
274                            {
275                                to_insert.push((new_inlay_id, hint_anchor, new_inlay_hint));
276                            }
277                        }
278                    }
279                }
280
281                InlaySplice {
282                    to_remove: Vec::new(),
283                    to_insert,
284                }
285            })
286        })
287    }
288
289    pub fn replace_hints(
290        &mut self,
291        multi_buffer: ModelHandle<MultiBuffer>,
292        new_ranges: impl Iterator<Item = InlayHintQuery>,
293        currently_shown_inlay_hints: HashMap<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>,
294        cx: &mut ViewContext<Editor>,
295    ) -> Task<anyhow::Result<InlaySplice>> {
296        let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
297        // let inlay_queries_per_buffer = inlay_queries.fold(
298        //     HashMap::<u64, BufferInlays<InlayHintQuery>>::default(),
299        //     |mut queries, new_query| {
300        //         let mut buffer_queries = queries
301        //             .entry(new_query.buffer_id)
302        //             .or_insert_with(|| BufferInlays::new(new_query.buffer_version.clone()));
303        //         assert_eq!(buffer_queries.buffer_version, new_query.buffer_version);
304        //         let queries = buffer_queries
305        //             .excerpt_inlays
306        //             .entry(new_query.excerpt_id)
307        //             .or_default();
308        //         // let z = multi_buffer_snapshot.anchor_in_excerpt(new_query.excerpt_id, text_anchor);
309        //         // .push(new_query);
310        //         // match queries
311        //         //     .binary_search_by(|probe| inlay.position.cmp(&probe.0, &multi_buffer_snapshot))
312        //         // {
313        //         //     Ok(ix) | Err(ix) => {
314        //         //         excerpt_hints.insert(ix, (inlay.position, inlay.id));
315        //         //     }
316        //         // }
317        //         // queries
318        //         todo!("TODO kb")
319        //     },
320        // );
321
322        todo!("TODO kb")
323    }
324}
325
326fn allowed_inlay_hint_types(
327    inlay_hint_settings: editor_settings::InlayHints,
328) -> HashSet<Option<InlayHintKind>> {
329    let mut new_allowed_inlay_hint_types = HashSet::default();
330    if inlay_hint_settings.show_type_hints {
331        new_allowed_inlay_hint_types.insert(Some(InlayHintKind::Type));
332    }
333    if inlay_hint_settings.show_parameter_hints {
334        new_allowed_inlay_hint_types.insert(Some(InlayHintKind::Parameter));
335    }
336    if inlay_hint_settings.show_other_hints {
337        new_allowed_inlay_hint_types.insert(None);
338    }
339    new_allowed_inlay_hint_types
340}
341
342fn missing_subranges(cache: &[Range<usize>], input: &Range<usize>) -> Vec<Range<usize>> {
343    let mut missing = Vec::new();
344
345    // Find where the input range would fit in the cache
346    let index = match cache.binary_search_by_key(&input.start, |probe| probe.start) {
347        Ok(pos) | Err(pos) => pos,
348    };
349
350    // Check for a gap from the start of the input range to the first range in the cache
351    if index == 0 {
352        if input.start < cache[index].start {
353            missing.push(input.start..cache[index].start);
354        }
355    } else {
356        let prev_end = cache[index - 1].end;
357        if input.start < prev_end {
358            missing.push(input.start..prev_end);
359        }
360    }
361
362    // Iterate through the cache ranges starting from index
363    for i in index..cache.len() {
364        let start = if i > 0 { cache[i - 1].end } else { input.start };
365        let end = cache[i].start;
366
367        if start < end {
368            missing.push(start..end);
369        }
370    }
371
372    // Check for a gap from the last range in the cache to the end of the input range
373    if let Some(last_range) = cache.last() {
374        if last_range.end < input.end {
375            missing.push(last_range.end..input.end);
376        }
377    } else {
378        // If cache is empty, the entire input range is missing
379        missing.push(input.start..input.end);
380    }
381
382    missing
383}
384
385fn insert_and_merge_ranges(cache: &mut Vec<Range<usize>>, new_range: &Range<usize>) {
386    if cache.is_empty() {
387        cache.push(new_range.clone());
388        return;
389    }
390
391    // Find the index to insert the new range
392    let index = match cache.binary_search_by_key(&new_range.start, |probe| probe.start) {
393        Ok(pos) | Err(pos) => pos,
394    };
395
396    // Check if the new range overlaps with the previous range in the cache
397    if index > 0 && cache[index - 1].end >= new_range.start {
398        // Merge with the previous range
399        cache[index - 1].end = std::cmp::max(cache[index - 1].end, new_range.end);
400    } else {
401        // Insert the new range, as it doesn't overlap with the previous range
402        cache.insert(index, new_range.clone());
403    }
404
405    // Merge overlaps with subsequent ranges
406    let mut i = index;
407    while i + 1 < cache.len() && cache[i].end >= cache[i + 1].start {
408        cache[i].end = std::cmp::max(cache[i].end, cache[i + 1].end);
409        cache.remove(i + 1);
410        i += 1;
411    }
412}
413
414fn fetch_queries<'a, 'b>(
415    multi_buffer: ModelHandle<MultiBuffer>,
416    queries: impl Iterator<Item = InlayHintQuery>,
417    cx: &mut ViewContext<'a, 'b, Editor>,
418) -> Task<anyhow::Result<HashMap<u64, BufferHints<InlayHint>>>> {
419    let mut inlay_fetch_tasks = Vec::new();
420    for query in queries {
421        let task_multi_buffer = multi_buffer.clone();
422        let task = cx.spawn(|editor, mut cx| async move {
423            let Some(buffer_handle) = cx.read(|cx| task_multi_buffer.read(cx).buffer(query.buffer_id))
424                else { return anyhow::Ok((query, Some(Vec::new()))) };
425            let task = editor
426                .update(&mut cx, |editor, cx| {
427                    editor.project.as_ref().map(|project| {
428                        project.update(cx, |project, cx| {
429                            project.query_inlay_hints_for_buffer(
430                                buffer_handle,
431                                query.excerpt_offset_query_range.clone(),
432                                cx,
433                            )
434                        })
435                    })
436                })
437                .context("inlays fecth task spawn")?;
438            Ok((
439                query,
440                match task {
441                    Some(task) => task.await.context("inlays for buffer task")?,
442                    None => Some(Vec::new()),
443                },
444            ))
445        });
446
447        inlay_fetch_tasks.push(task);
448    }
449
450    cx.spawn(|editor, cx| async move {
451        let mut inlay_updates: HashMap<u64, BufferHints<InlayHint>> = HashMap::default();
452        for task_result in futures::future::join_all(inlay_fetch_tasks).await {
453            match task_result {
454                Ok((query, Some(response_hints))) => {
455                    let Some(buffer_snapshot) = editor.read_with(&cx, |editor, cx| {
456                        editor.buffer().read(cx).buffer(query.buffer_id).map(|buffer| buffer.read(cx).snapshot())
457                    })? else { continue; };
458                    let buffer_hints = inlay_updates
459                        .entry(query.buffer_id)
460                        .or_insert_with(|| BufferHints::new(query.buffer_version.clone()));
461                    if buffer_snapshot.version().changed_since(&buffer_hints.buffer_version) {
462                        continue;
463                    }
464                    let cached_excerpt_hints = buffer_hints
465                        .hints_per_excerpt
466                        .entry(query.excerpt_id)
467                        .or_default();
468                    insert_and_merge_ranges(&mut cached_excerpt_hints.cached_excerpt_offsets, &query.excerpt_offset_query_range);
469                    let excerpt_hints = &mut cached_excerpt_hints.hints;
470                    for inlay in response_hints {
471                        match excerpt_hints.binary_search_by(|probe| {
472                            inlay.position.cmp(&probe.position, &buffer_snapshot)
473                        }) {
474                            Ok(ix) | Err(ix) => excerpt_hints.insert(ix, inlay),
475                        }
476                    }
477                }
478                Ok((_, None)) => {}
479                Err(e) => error!("Failed to update inlays for buffer: {e:#}"),
480            }
481        }
482        Ok(inlay_updates)
483    })
484}