Properly update inlay hints when settings are changed

Kirill Bulatov created

Change summary

crates/editor/src/display_map.rs |   2 
crates/editor/src/editor.rs      | 236 ++++++++++----------------------
crates/editor/src/inlay_cache.rs | 248 +++++++++++++++++++++++++++++++--
crates/project/src/project.rs    |  18 +-
4 files changed, 315 insertions(+), 189 deletions(-)

Detailed changes

crates/editor/src/display_map.rs 🔗

@@ -303,7 +303,7 @@ impl DisplayMap {
             .update(cx, |map, cx| map.sync(snapshot, edits, cx));
         self.block_map.read(snapshot, edits);
 
-        let new_inlays = to_insert
+        let new_inlays: Vec<(InlayId, InlayProperties<String>)> = to_insert
             .into_iter()
             .map(|(inlay_id, hint_anchor, hint)| {
                 let mut text = hint.text();

crates/editor/src/editor.rs 🔗

@@ -1,4 +1,5 @@
 mod blink_manager;
+
 pub mod display_map;
 mod editor_settings;
 mod element;
@@ -26,8 +27,8 @@ use aho_corasick::AhoCorasick;
 use anyhow::{anyhow, Context, Result};
 use blink_manager::BlinkManager;
 use client::{ClickhouseEvent, TelemetrySettings};
-use clock::{Global, ReplicaId};
-use collections::{hash_map, BTreeMap, Bound, HashMap, HashSet, VecDeque};
+use clock::ReplicaId;
+use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
 use copilot::Copilot;
 pub use display_map::DisplayPoint;
 use display_map::*;
@@ -53,7 +54,7 @@ use gpui::{
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
-use inlay_cache::{InlayCache, InlaysUpdate, OrderedByAnchorOffset};
+use inlay_cache::{InlayCache, InlayRefreshReason, InlaysUpdate, QueryInlaysRange};
 pub use items::MAX_TAB_TITLE_LEN;
 use itertools::Itertools;
 pub use language::{char_kind, CharKind};
@@ -73,10 +74,7 @@ pub use multi_buffer::{
 };
 use multi_buffer::{MultiBufferChunks, ToOffsetUtf16};
 use ordered_float::OrderedFloat;
-use project::{
-    FormatTrigger, InlayHint, InlayHintKind, Location, LocationLink, Project, ProjectPath,
-    ProjectTransaction,
-};
+use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction};
 use scroll::{
     autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
 };
@@ -85,7 +83,6 @@ use serde::{Deserialize, Serialize};
 use settings::SettingsStore;
 use smallvec::SmallVec;
 use snippet::Snippet;
-use std::path::PathBuf;
 use std::{
     any::TypeId,
     borrow::Cow,
@@ -1291,14 +1288,16 @@ impl Editor {
             (mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
 
         let mut project_subscriptions = Vec::new();
-        if mode == EditorMode::Full && buffer.read(cx).is_singleton() {
+        if mode == EditorMode::Full {
             if let Some(project) = project.as_ref() {
-                project_subscriptions.push(cx.observe(project, |_, _, cx| {
-                    cx.emit(Event::TitleChanged);
-                }));
+                if buffer.read(cx).is_singleton() {
+                    project_subscriptions.push(cx.observe(project, |_, _, cx| {
+                        cx.emit(Event::TitleChanged);
+                    }));
+                }
                 project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| {
                     if let project::Event::RefreshInlays = event {
-                        editor.refresh_inlays(cx);
+                        editor.refresh_inlays(InlayRefreshReason::Regular, cx);
                     };
                 }));
             }
@@ -1353,8 +1352,8 @@ impl Editor {
             hover_state: Default::default(),
             link_go_to_definition_state: Default::default(),
             copilot_state: Default::default(),
-            // TODO kb has to live between editors
-            inlay_cache: InlayCache::default(),
+            // TODO kb has to live between editor reopens
+            inlay_cache: InlayCache::new(settings::get::<EditorSettings>(cx).inlay_hints),
             gutter_hovered: false,
             _subscriptions: vec![
                 cx.observe(&buffer, Self::on_buffer_changed),
@@ -1379,7 +1378,7 @@ impl Editor {
         }
 
         this.report_editor_event("open", None, cx);
-        this.refresh_inlays(cx);
+        this.refresh_inlays(InlayRefreshReason::Regular, cx);
         this
     }
 
@@ -2591,13 +2590,12 @@ impl Editor {
         }
     }
 
-    fn refresh_inlays(&mut self, cx: &mut ViewContext<Self>) {
+    fn refresh_inlays(&mut self, reason: InlayRefreshReason, cx: &mut ViewContext<Self>) {
         if self.mode != EditorMode::Full {
             return;
         }
 
-        let inlay_hint_settings = settings::get::<EditorSettings>(cx).inlay_hints;
-        if !inlay_hint_settings.enabled {
+        if !settings::get::<EditorSettings>(cx).inlay_hints.enabled {
             let to_remove = self.inlay_cache.clear();
             self.display_map.update(cx, |display_map, cx| {
                 display_map.splice_inlays(to_remove, Vec::new(), cx);
@@ -2605,151 +2603,63 @@ impl Editor {
             return;
         }
 
-        struct InlayRequestKey {
-            buffer_path: PathBuf,
-            buffer_version: Global,
-            excerpt_id: ExcerptId,
-        }
-
-        let multi_buffer = self.buffer();
-        let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
-        let inlay_fetch_tasks = multi_buffer_snapshot
-            .excerpts()
-            .filter_map(|(excerpt_id, buffer_snapshot, excerpt_range)| {
-                let buffer_path = buffer_snapshot.resolve_file_path(cx, true)?;
-                let buffer_id = buffer_snapshot.remote_id();
-                let buffer_version = buffer_snapshot.version().clone();
-                let buffer_handle = multi_buffer.read(cx).buffer(buffer_id);
-                let inlays_up_to_date =
-                    self.inlay_cache
-                        .inlays_up_to_date(&buffer_path, &buffer_version, excerpt_id);
-                let key = InlayRequestKey {
-                    buffer_path,
-                    buffer_version,
-                    excerpt_id,
-                };
-
-                // TODO kb split this into 2 different steps:
-                // 1. cache population
-                // 2. cache querying + hint filters on top (needs to store previous filter settings)
-                let task = cx.spawn(|editor, mut cx| async move {
-                    if inlays_up_to_date {
-                        anyhow::Ok((key, None))
-                    } else {
-                        let Some(buffer_handle) = buffer_handle else { return Ok((key, Some(Vec::new()))) };
-                        let max_buffer_offset = cx.read(|cx| buffer_handle.read(cx).len());
-                        let excerpt_range = excerpt_range.context;
-                        let query_start = excerpt_range.start.offset;
-                        let query_end = excerpt_range.end.offset.min(max_buffer_offset);
-                        let task = editor
-                            .update(&mut cx, |editor, cx| {
-                                editor.project.as_ref().map(|project| {
-                                    project.update(cx, |project, cx| {
-                                        project.query_inlay_hints_for_buffer(
-                                            buffer_handle,
-                                            query_start..query_end,
-                                            cx,
-                                        )
-                                    })
-                                })
-                            })
-                            .context("inlays fecth task spawn")?;
-
-                        Ok((key, match task {
-                            Some(task) => {
-                                match task.await.context("inlays for buffer task")? {
-                                    Some(mut new_inlays) => {
-                                        let mut allowed_inlay_hint_types = Vec::new();
-                                        if inlay_hint_settings.show_type_hints {
-                                            allowed_inlay_hint_types.push(Some(InlayHintKind::Type));
-                                        }
-                                        if inlay_hint_settings.show_parameter_hints {
-                                            allowed_inlay_hint_types.push(Some(InlayHintKind::Parameter));
-                                        }
-                                        if inlay_hint_settings.show_other_hints {
-                                            allowed_inlay_hint_types.push(None);
-                                        }
-                                        new_inlays.retain(|inlay| {
-                                            let inlay_offset = inlay.position.offset;
-                                            allowed_inlay_hint_types.contains(&inlay.kind)
-                                                && query_start <= inlay_offset && inlay_offset <= query_end
-                                        });
-                                        Some(new_inlays)
-                                    },
-                                    None => None,
-                                }
-
-                            },
-                            None => Some(Vec::new()),
-                        }))
-                    }
-                });
-
-                Some(task)
-            })
-            .collect::<Vec<_>>();
-
-        cx.spawn(|editor, mut cx| async move {
-            let mut inlay_updates: HashMap<
-                PathBuf,
-                (
-                    Global,
-                    HashMap<ExcerptId, Option<OrderedByAnchorOffset<InlayHint>>>,
-                ),
-            > = HashMap::default();
-            let multi_buffer_snapshot =
-                editor.read_with(&cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))?;
-
-            for task_result in futures::future::join_all(inlay_fetch_tasks).await {
-                match task_result {
-                    Ok((request_key, response_inlays)) => {
-                        let inlays_per_excerpt = HashMap::from_iter([(
-                            request_key.excerpt_id,
-                            response_inlays.map(|excerpt_inlays| {
-                                excerpt_inlays.into_iter().fold(
-                                    OrderedByAnchorOffset::default(),
-                                    |mut ordered_inlays, inlay| {
-                                        let anchor = multi_buffer_snapshot.anchor_in_excerpt(
-                                            request_key.excerpt_id,
-                                            inlay.position,
-                                        );
-                                        ordered_inlays.add(anchor, inlay);
-                                        ordered_inlays
-                                    },
-                                )
-                            }),
-                        )]);
-                        match inlay_updates.entry(request_key.buffer_path) {
-                            hash_map::Entry::Occupied(mut o) => {
-                                o.get_mut().1.extend(inlays_per_excerpt);
-                            }
-                            hash_map::Entry::Vacant(v) => {
-                                v.insert((request_key.buffer_version, inlays_per_excerpt));
-                            }
-                        }
-                    }
-                    Err(e) => error!("Failed to update inlays for buffer: {e:#}"),
-                }
-            }
-
-            if !inlay_updates.is_empty() {
+        match reason {
+            InlayRefreshReason::Settings(new_settings) => {
                 let InlaysUpdate {
                     to_remove,
                     to_insert,
-                } = editor.update(&mut cx, |editor, _| {
-                    dbg!(editor.inlay_cache.update_inlays(inlay_updates))
-                })?;
-
-                editor.update(&mut cx, |editor, cx| {
-                    editor.display_map.update(cx, |display_map, cx| {
-                        display_map.splice_inlays(to_remove, to_insert, cx);
-                    });
-                })?;
+                } = self.inlay_cache.apply_settings(new_settings);
+                self.display_map.update(cx, |display_map, cx| {
+                    display_map.splice_inlays(to_remove, to_insert, cx);
+                });
             }
+            InlayRefreshReason::Regular => {
+                let buffer_handle = self.buffer().clone();
+                let inlay_fetch_ranges = buffer_handle
+                    .read(cx)
+                    .snapshot(cx)
+                    .excerpts()
+                    .filter_map(|(excerpt_id, buffer_snapshot, excerpt_range)| {
+                        let buffer_path = buffer_snapshot.resolve_file_path(cx, true)?;
+                        let buffer_id = buffer_snapshot.remote_id();
+                        let buffer_version = buffer_snapshot.version().clone();
+                        let max_buffer_offset = buffer_snapshot.len();
+                        let excerpt_range = excerpt_range.context;
+                        Some(QueryInlaysRange {
+                            buffer_path,
+                            buffer_id,
+                            buffer_version,
+                            excerpt_id,
+                            excerpt_offset_range: excerpt_range.start.offset
+                                ..excerpt_range.end.offset.min(max_buffer_offset),
+                        })
+                    })
+                    .collect::<Vec<_>>();
 
-            anyhow::Ok(())
-        })
-        .detach_and_log_err(cx);
+                cx.spawn(|editor, mut cx| async move {
+                    let InlaysUpdate {
+                        to_remove,
+                        to_insert,
+                    } = editor
+                        .update(&mut cx, |editor, cx| {
+                            editor.inlay_cache.fetch_inlays(
+                                buffer_handle,
+                                inlay_fetch_ranges.into_iter(),
+                                cx,
+                            )
+                        })?
+                        .await
+                        .context("inlay cache hint fetch")?;
+
+                    editor.update(&mut cx, |editor, cx| {
+                        editor.display_map.update(cx, |display_map, cx| {
+                            display_map.splice_inlays(to_remove, to_insert, cx);
+                        });
+                    })
+                })
+                .detach_and_log_err(cx);
+            }
+        }
     }
 
     fn trigger_on_type_formatting(
@@ -5687,6 +5597,7 @@ impl Editor {
                 }
             }
 
+            // TODO: Handle selections that cross excerpts
             // TODO: Handle selections that cross excerpts
             for selection in &mut selections {
                 let start_column = snapshot.indent_size_for_line(selection.start.row).len;
@@ -7332,7 +7243,7 @@ impl Editor {
         };
 
         if refresh_inlay_hints {
-            self.refresh_inlays(cx);
+            self.refresh_inlays(InlayRefreshReason::Regular, cx);
         }
     }
 
@@ -7342,7 +7253,10 @@ impl Editor {
 
     fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
         self.refresh_copilot_suggestions(true, cx);
-        self.refresh_inlays(cx);
+        self.refresh_inlays(
+            InlayRefreshReason::Settings(settings::get::<EditorSettings>(cx).inlay_hints),
+            cx,
+        );
     }
 
     pub fn set_searchable(&mut self, searchable: bool) {

crates/editor/src/inlay_cache.rs 🔗

@@ -1,18 +1,29 @@
 use std::{
     cmp,
+    ops::Range,
     path::{Path, PathBuf},
 };
 
-use crate::{Anchor, ExcerptId};
+use crate::{editor_settings, Anchor, Editor, ExcerptId, MultiBuffer};
+use anyhow::Context;
 use clock::{Global, Local};
-use project::InlayHint;
+use gpui::{ModelHandle, Task, ViewContext};
+use log::error;
+use project::{InlayHint, InlayHintKind};
 use util::post_inc;
 
-use collections::{BTreeMap, HashMap};
+use collections::{hash_map, BTreeMap, HashMap, HashSet};
 
-#[derive(Clone, Debug, Default)]
+#[derive(Debug, Copy, Clone)]
+pub enum InlayRefreshReason {
+    Settings(editor_settings::InlayHints),
+    Regular,
+}
+
+#[derive(Debug, Clone, Default)]
 pub struct InlayCache {
     inlays_per_buffer: HashMap<PathBuf, BufferInlays>,
+    allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
     next_inlay_id: usize,
 }
 
@@ -37,6 +48,10 @@ impl<T> OrderedByAnchorOffset<T> {
     fn into_ordered_elements(self) -> impl Iterator<Item = (Anchor, T)> {
         self.0.into_values()
     }
+
+    fn ordered_elements(&self) -> impl Iterator<Item = &(Anchor, T)> {
+        self.0.values()
+    }
 }
 
 impl<T> Default for OrderedByAnchorOffset<T> {
@@ -54,14 +69,150 @@ struct BufferInlays {
 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct InlayId(pub usize);
 
-#[derive(Debug)]
+#[derive(Debug, Default)]
 pub struct InlaysUpdate {
     pub to_remove: Vec<InlayId>,
     pub to_insert: Vec<(InlayId, Anchor, InlayHint)>,
 }
+impl InlaysUpdate {
+    fn merge(&mut self, other: Self) {
+        let mut new_to_remove = other.to_remove.iter().copied().collect::<HashSet<_>>();
+        self.to_insert
+            .retain(|(inlay_id, _, _)| !new_to_remove.remove(&inlay_id));
+        self.to_remove.extend(new_to_remove);
+        self.to_insert
+            .extend(other.to_insert.into_iter().filter(|(inlay_id, _, _)| {
+                !self
+                    .to_remove
+                    .iter()
+                    .any(|removed_inlay_id| removed_inlay_id == inlay_id)
+            }));
+    }
+}
+
+pub struct QueryInlaysRange {
+    pub buffer_id: u64,
+    pub buffer_path: PathBuf,
+    pub buffer_version: Global,
+    pub excerpt_id: ExcerptId,
+    pub excerpt_offset_range: Range<usize>,
+}
 
 impl InlayCache {
-    pub fn inlays_up_to_date(
+    pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self {
+        Self {
+            inlays_per_buffer: HashMap::default(),
+            allowed_hint_kinds: allowed_inlay_hint_types(inlay_hint_settings),
+            next_inlay_id: 0,
+        }
+    }
+
+    pub fn fetch_inlays(
+        &mut self,
+        multi_buffer: ModelHandle<MultiBuffer>,
+        inlay_fetch_ranges: impl Iterator<Item = QueryInlaysRange>,
+        cx: &mut ViewContext<Editor>,
+    ) -> Task<anyhow::Result<InlaysUpdate>> {
+        let mut inlay_fetch_tasks = Vec::new();
+        for inlay_fetch_range in inlay_fetch_ranges {
+            let inlays_up_to_date = self.inlays_up_to_date(
+                &inlay_fetch_range.buffer_path,
+                &inlay_fetch_range.buffer_version,
+                inlay_fetch_range.excerpt_id,
+            );
+            let task_multi_buffer = multi_buffer.clone();
+            let task = cx.spawn(|editor, mut cx| async move {
+                if inlays_up_to_date {
+                    anyhow::Ok((inlay_fetch_range, None))
+                } else {
+                    let Some(buffer_handle) = cx.read(|cx| task_multi_buffer.read(cx).buffer(inlay_fetch_range.buffer_id))
+                        else { return Ok((inlay_fetch_range, Some(Vec::new()))) };
+                    let task = editor
+                        .update(&mut cx, |editor, cx| {
+                            let max_buffer_offset = buffer_handle.read(cx).len();
+                            let excerpt_offset_range = &inlay_fetch_range.excerpt_offset_range;
+                            editor.project.as_ref().map(|project| {
+                                project.update(cx, |project, cx| {
+                                    project.query_inlay_hints_for_buffer(
+                                        buffer_handle,
+                                        excerpt_offset_range.start..excerpt_offset_range.end.min(max_buffer_offset),
+                                        cx,
+                                    )
+                                })
+                            })
+                        })
+                        .context("inlays fecth task spawn")?;
+
+                    Ok((inlay_fetch_range, match task {
+                        Some(task) => task.await.context("inlays for buffer task")?,
+                        None => Some(Vec::new()),
+                    }))
+                }
+            });
+            inlay_fetch_tasks.push(task);
+        }
+
+        let final_task = cx.spawn(|editor, mut cx| async move {
+            let mut inlay_updates: HashMap<
+                PathBuf,
+                (
+                    Global,
+                    HashMap<ExcerptId, Option<(Range<usize>, OrderedByAnchorOffset<InlayHint>)>>,
+                ),
+            > = HashMap::default();
+            let multi_buffer_snapshot =
+                editor.read_with(&cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))?;
+
+            for task_result in futures::future::join_all(inlay_fetch_tasks).await {
+                match task_result {
+                    Ok((request_key, response_inlays)) => {
+                        let inlays_per_excerpt = HashMap::from_iter([(
+                            request_key.excerpt_id,
+                            response_inlays
+                                .map(|excerpt_inlays| {
+                                    excerpt_inlays.into_iter().fold(
+                                        OrderedByAnchorOffset::default(),
+                                        |mut ordered_inlays, inlay| {
+                                            let anchor = multi_buffer_snapshot.anchor_in_excerpt(
+                                                request_key.excerpt_id,
+                                                inlay.position,
+                                            );
+                                            ordered_inlays.add(anchor, inlay);
+                                            ordered_inlays
+                                        },
+                                    )
+                                })
+                                .map(|inlays| (request_key.excerpt_offset_range, inlays)),
+                        )]);
+                        match inlay_updates.entry(request_key.buffer_path) {
+                            hash_map::Entry::Occupied(mut o) => {
+                                o.get_mut().1.extend(inlays_per_excerpt);
+                            }
+                            hash_map::Entry::Vacant(v) => {
+                                v.insert((request_key.buffer_version, inlays_per_excerpt));
+                            }
+                        }
+                    }
+                    Err(e) => error!("Failed to update inlays for buffer: {e:#}"),
+                }
+            }
+
+            let updates = if !inlay_updates.is_empty() {
+                let inlays_update = editor.update(&mut cx, |editor, _| {
+                    editor.inlay_cache.apply_fetch_inlays(inlay_updates)
+                })?;
+                inlays_update
+            } else {
+                InlaysUpdate::default()
+            };
+
+            anyhow::Ok(updates)
+        });
+
+        final_task
+    }
+
+    fn inlays_up_to_date(
         &self,
         buffer_path: &Path,
         buffer_version: &Global,
@@ -69,17 +220,17 @@ impl InlayCache {
     ) -> bool {
         let Some(buffer_inlays) = self.inlays_per_buffer.get(buffer_path) else { return false };
         let buffer_up_to_date = buffer_version == &buffer_inlays.buffer_version
-            || buffer_inlays.buffer_version.changed_since(buffer_version);
+            || buffer_inlays.buffer_version.changed_since(&buffer_version);
         buffer_up_to_date && buffer_inlays.inlays_per_excerpts.contains_key(&excerpt_id)
     }
 
-    pub fn update_inlays(
+    fn apply_fetch_inlays(
         &mut self,
-        inlay_updates: HashMap<
+        fetched_inlays: HashMap<
             PathBuf,
             (
                 Global,
-                HashMap<ExcerptId, Option<OrderedByAnchorOffset<InlayHint>>>,
+                HashMap<ExcerptId, Option<(Range<usize>, OrderedByAnchorOffset<InlayHint>)>>,
             ),
         >,
     ) -> InlaysUpdate {
@@ -87,10 +238,17 @@ impl InlayCache {
         let mut to_remove = Vec::new();
         let mut to_insert = Vec::new();
 
-        for (buffer_path, (buffer_version, new_buffer_inlays)) in inlay_updates {
+        for (buffer_path, (buffer_version, new_buffer_inlays)) in fetched_inlays {
             match old_inlays.remove(&buffer_path) {
                 Some(mut old_buffer_inlays) => {
                     for (excerpt_id, new_excerpt_inlays) in new_buffer_inlays {
+                        let (_, mut new_excerpt_inlays) = match new_excerpt_inlays {
+                            Some((excerpt_offset_range, new_inlays)) => (
+                                excerpt_offset_range,
+                                new_inlays.into_ordered_elements().fuse().peekable(),
+                            ),
+                            None => continue,
+                        };
                         if self.inlays_up_to_date(&buffer_path, &buffer_version, excerpt_id) {
                             continue;
                         }
@@ -99,12 +257,7 @@ impl InlayCache {
                             .inlays_per_buffer
                             .get_mut(&buffer_path)
                             .expect("element expected: `old_inlays.remove` returned `Some`");
-                        let mut new_excerpt_inlays = match new_excerpt_inlays {
-                            Some(new_inlays) => {
-                                new_inlays.into_ordered_elements().fuse().peekable()
-                            }
-                            None => continue,
-                        };
+
                         if old_buffer_inlays
                             .inlays_per_excerpts
                             .remove(&excerpt_id)
@@ -192,7 +345,7 @@ impl InlayCache {
                         OrderedByAnchorOffset<(InlayId, InlayHint)>,
                     > = HashMap::default();
                     for (new_excerpt_id, new_ordered_inlays) in new_buffer_inlays {
-                        if let Some(new_ordered_inlays) = new_ordered_inlays {
+                        if let Some((_, new_ordered_inlays)) = new_ordered_inlays {
                             for (new_anchor, new_inlay) in
                                 new_ordered_inlays.into_ordered_elements()
                             {
@@ -230,6 +383,49 @@ impl InlayCache {
         }
     }
 
+    pub fn apply_settings(
+        &mut self,
+        inlay_hint_settings: editor_settings::InlayHints,
+    ) -> InlaysUpdate {
+        let new_allowed_inlay_hint_types = allowed_inlay_hint_types(inlay_hint_settings);
+
+        let new_allowed_hint_kinds = new_allowed_inlay_hint_types
+            .difference(&self.allowed_hint_kinds)
+            .copied()
+            .collect::<HashSet<_>>();
+        let removed_hint_kinds = self
+            .allowed_hint_kinds
+            .difference(&new_allowed_inlay_hint_types)
+            .collect::<HashSet<_>>();
+        let mut to_remove = Vec::new();
+        let mut to_insert = Vec::new();
+        for (anchor, (inlay_id, inlay_hint)) in self
+            .inlays_per_buffer
+            .iter()
+            .map(|(_, buffer_inlays)| {
+                buffer_inlays
+                    .inlays_per_excerpts
+                    .iter()
+                    .map(|(_, excerpt_inlays)| excerpt_inlays.ordered_elements())
+                    .flatten()
+            })
+            .flatten()
+        {
+            if removed_hint_kinds.contains(&inlay_hint.kind) {
+                to_remove.push(*inlay_id);
+            } else if new_allowed_hint_kinds.contains(&inlay_hint.kind) {
+                to_insert.push((*inlay_id, *anchor, inlay_hint.to_owned()));
+            }
+        }
+
+        self.allowed_hint_kinds = new_allowed_hint_kinds;
+
+        InlaysUpdate {
+            to_remove,
+            to_insert,
+        }
+    }
+
     pub fn clear(&mut self) -> Vec<InlayId> {
         self.inlays_per_buffer
             .drain()
@@ -248,3 +444,19 @@ impl InlayCache {
             .collect()
     }
 }
+
+fn allowed_inlay_hint_types(
+    inlay_hint_settings: editor_settings::InlayHints,
+) -> HashSet<Option<InlayHintKind>> {
+    let mut new_allowed_inlay_hint_types = HashSet::default();
+    if inlay_hint_settings.show_type_hints {
+        new_allowed_inlay_hint_types.insert(Some(InlayHintKind::Type));
+    }
+    if inlay_hint_settings.show_parameter_hints {
+        new_allowed_inlay_hint_types.insert(Some(InlayHintKind::Parameter));
+    }
+    if inlay_hint_settings.show_other_hints {
+        new_allowed_inlay_hint_types.insert(None);
+    }
+    new_allowed_inlay_hint_types
+}

crates/project/src/project.rs 🔗

@@ -321,13 +321,13 @@ pub struct DiagnosticSummary {
     pub warning_count: usize,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Location {
     pub buffer: ModelHandle<Buffer>,
     pub range: Range<language::Anchor>,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct InlayHint {
     pub buffer_id: u64,
     pub position: Anchor,
@@ -338,7 +338,7 @@ pub struct InlayHint {
     pub tooltip: Option<InlayHintTooltip>,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub enum InlayHintKind {
     Type,
     Parameter,
@@ -370,32 +370,32 @@ impl InlayHint {
     }
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum InlayHintLabel {
     String(String),
     LabelParts(Vec<InlayHintLabelPart>),
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct InlayHintLabelPart {
     pub value: String,
     pub tooltip: Option<InlayHintLabelPartTooltip>,
     pub location: Option<Location>,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum InlayHintTooltip {
     String(String),
     MarkupContent(MarkupContent),
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum InlayHintLabelPartTooltip {
     String(String),
     MarkupContent(MarkupContent),
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct MarkupContent {
     pub kind: String,
     pub value: String,
@@ -4975,7 +4975,7 @@ impl Project {
                     lsp_request,
                     response,
                     project,
-                    buffer_handle,
+                    buffer_handle.clone(),
                     cx,
                 )
                 .await;