Properly handle settings toggle

Kirill Bulatov created

Change summary

crates/editor/src/editor.rs           |  42 ++++--
crates/editor/src/inlay_hint_cache.rs | 170 ++++++++++++++++++++++------
crates/editor/src/multi_buffer.rs     |  15 --
crates/editor/src/scroll.rs           |   2 
4 files changed, 164 insertions(+), 65 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -3,7 +3,7 @@ mod blink_manager;
 pub mod display_map;
 mod editor_settings;
 mod element;
-mod inlay_cache;
+mod inlay_hint_cache;
 
 mod git;
 mod highlight_matching_bracket;
@@ -54,7 +54,7 @@ use gpui::{
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
-use inlay_cache::{InlayCache, InlayHintQuery, InlayRefreshReason, InlaySplice};
+use inlay_hint_cache::{InlayHintCache, InlayHintQuery, InlayRefreshReason, InlaySplice};
 pub use items::MAX_TAB_TITLE_LEN;
 use itertools::Itertools;
 pub use language::{char_kind, CharKind};
@@ -540,7 +540,7 @@ pub struct Editor {
     gutter_hovered: bool,
     link_go_to_definition_state: LinkGoToDefinitionState,
     copilot_state: CopilotState,
-    inlay_cache: InlayCache,
+    inlay_cache: InlayHintCache,
     _subscriptions: Vec<Subscription>,
 }
 
@@ -1355,7 +1355,7 @@ impl Editor {
             hover_state: Default::default(),
             link_go_to_definition_state: Default::default(),
             copilot_state: Default::default(),
-            inlay_cache: InlayCache::new(settings::get::<EditorSettings>(cx).inlay_hints),
+            inlay_cache: InlayHintCache::new(settings::get::<EditorSettings>(cx).inlay_hints),
             gutter_hovered: false,
             _subscriptions: vec![
                 cx.observe(&buffer, Self::on_buffer_changed),
@@ -2604,23 +2604,37 @@ impl Editor {
         }
 
         let multi_buffer_handle = self.buffer().clone();
+        let multi_buffer_snapshot = multi_buffer_handle.read(cx).snapshot(cx);
         let currently_visible_ranges = self.excerpt_visible_offsets(&multi_buffer_handle, cx);
-        let currently_shown_inlays = self
-            .display_map
-            .read(cx)
-            .current_inlays()
-            .map(|inlay| (inlay.position, inlay.id))
-            .collect::<Vec<_>>();
+        let currently_shown_inlay_hints = self.display_map.read(cx).current_inlays().fold(
+            HashMap::<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>::default(),
+            |mut current_hints, inlay| {
+                if let Some(buffer_id) = inlay.position.buffer_id {
+                    let excerpt_hints = current_hints
+                        .entry(buffer_id)
+                        .or_default()
+                        .entry(inlay.position.excerpt_id)
+                        .or_default();
+                    match excerpt_hints.binary_search_by(|probe| {
+                        inlay.position.cmp(&probe.0, &multi_buffer_snapshot)
+                    }) {
+                        Ok(ix) | Err(ix) => {
+                            excerpt_hints.insert(ix, (inlay.position, inlay.id));
+                        }
+                    }
+                }
+                current_hints
+            },
+        );
         match reason {
             InlayRefreshReason::SettingsChange(new_settings) => {
                 if let Some(InlaySplice {
                     to_remove,
                     to_insert,
                 }) = self.inlay_cache.apply_settings(
-                    multi_buffer_handle,
                     new_settings,
                     currently_visible_ranges,
-                    currently_shown_inlays,
+                    currently_shown_inlay_hints,
                     cx,
                 ) {
                     self.splice_inlay_hints(to_remove, to_insert, cx);
@@ -2653,7 +2667,7 @@ impl Editor {
                                 editor.inlay_cache.append_inlays(
                                     multi_buffer_handle,
                                     std::iter::once(updated_range_query),
-                                    currently_shown_inlays,
+                                    currently_shown_inlay_hints,
                                     cx,
                                 )
                             })?
@@ -2683,7 +2697,7 @@ impl Editor {
                             editor.inlay_cache.replace_inlays(
                                 multi_buffer_handle,
                                 replacement_queries.into_iter(),
-                                currently_shown_inlays,
+                                currently_shown_inlay_hints,
                                 cx,
                             )
                         })?

crates/editor/src/inlay_cache.rs → crates/editor/src/inlay_hint_cache.rs 🔗

@@ -9,7 +9,7 @@ use gpui::{ModelHandle, Task, ViewContext};
 use language::Buffer;
 use project::{InlayHint, InlayHintKind};
 
-use collections::{HashMap, HashSet};
+use collections::{hash_map, HashMap, HashSet};
 
 #[derive(Debug, Copy, Clone)]
 pub enum InlayRefreshReason {
@@ -19,16 +19,25 @@ pub enum InlayRefreshReason {
 }
 
 #[derive(Debug, Clone, Default)]
-pub struct InlayCache {
+pub struct InlayHintCache {
     inlay_hints: HashMap<InlayId, InlayHint>,
-    inlays_in_buffers: HashMap<u64, BufferInlays>,
+    inlays_in_buffers: HashMap<u64, BufferInlays<(Anchor, InlayId)>>,
     allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
 }
 
 #[derive(Clone, Debug, Default)]
-struct BufferInlays {
+struct BufferInlays<I> {
     buffer_version: Global,
-    ordered_by_anchor_inlays: Vec<(Anchor, InlayId)>,
+    excerpt_inlays: HashMap<ExcerptId, Vec<I>>,
+}
+
+impl<I> BufferInlays<I> {
+    fn new(buffer_version: Global) -> Self {
+        Self {
+            buffer_version,
+            excerpt_inlays: HashMap::default(),
+        }
+    }
 }
 
 #[derive(Debug, Default)]
@@ -44,7 +53,7 @@ pub struct InlayHintQuery {
     pub excerpt_offset_query_range: Range<usize>,
 }
 
-impl InlayCache {
+impl InlayHintCache {
     pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self {
         Self {
             allowed_hint_kinds: allowed_inlay_hint_types(inlay_hint_settings),
@@ -55,10 +64,9 @@ impl InlayCache {
 
     pub fn apply_settings(
         &mut self,
-        multi_buffer: ModelHandle<MultiBuffer>,
         inlay_hint_settings: editor_settings::InlayHints,
         currently_visible_ranges: Vec<(ModelHandle<Buffer>, Range<usize>, ExcerptId)>,
-        currently_shown_inlays: Vec<(Anchor, InlayId)>,
+        mut currently_shown_inlay_hints: HashMap<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>,
         cx: &mut ViewContext<Editor>,
     ) -> Option<InlaySplice> {
         let new_allowed_hint_kinds = allowed_inlay_hint_types(inlay_hint_settings);
@@ -69,33 +77,88 @@ impl InlayCache {
             let mut to_remove = Vec::new();
             let mut to_insert = Vec::new();
 
-            let mut considered_inlay_ids = HashSet::default();
-            for (_, shown_inlay_id) in currently_shown_inlays {
-                if let Some(inlay_hint) = self.inlay_hints.get(&shown_inlay_id) {
-                    if !self.allowed_hint_kinds.contains(&inlay_hint.kind) {
-                        to_remove.push(shown_inlay_id);
+            let mut considered_hints =
+                HashMap::<u64, HashMap<ExcerptId, HashSet<InlayId>>>::default();
+            for (visible_buffer, _, visible_excerpt_id) in currently_visible_ranges {
+                let visible_buffer = visible_buffer.read(cx);
+                let visible_buffer_id = visible_buffer.remote_id();
+                match currently_shown_inlay_hints.entry(visible_buffer_id) {
+                    hash_map::Entry::Occupied(mut o) => {
+                        let shown_hints_per_excerpt = o.get_mut();
+                        for (_, shown_hint_id) in shown_hints_per_excerpt
+                            .remove(&visible_excerpt_id)
+                            .unwrap_or_default()
+                        {
+                            considered_hints
+                                .entry(visible_buffer_id)
+                                .or_default()
+                                .entry(visible_excerpt_id)
+                                .or_default()
+                                .insert(shown_hint_id);
+                            match self.inlay_hints.get(&shown_hint_id) {
+                                Some(shown_hint) => {
+                                    if !self.allowed_hint_kinds.contains(&shown_hint.kind) {
+                                        to_remove.push(shown_hint_id);
+                                    }
+                                }
+                                None => to_remove.push(shown_hint_id),
+                            }
+                        }
+                        if shown_hints_per_excerpt.is_empty() {
+                            o.remove();
+                        }
                     }
-                    considered_inlay_ids.insert(shown_inlay_id);
+                    hash_map::Entry::Vacant(_) => {}
                 }
             }
 
-            let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
-            for (inlay_id, inlay_hint) in &self.inlay_hints {
-                if self.allowed_hint_kinds.contains(&inlay_hint.kind)
-                    && !considered_inlay_ids.contains(inlay_id)
-                {
-                    if let Some(hint_to_readd) = currently_visible_ranges.iter()
-                        .filter(|(_, visible_range, _)| visible_range.contains(&inlay_hint.position.offset))
-                        .find_map(|(_, _, excerpt_id)| {
-                            let Some(anchor) = multi_buffer_snapshot
-                                .find_anchor_in_excerpt(*excerpt_id, inlay_hint.position) else { return None; };
-                            Some((Some(*inlay_id), anchor, inlay_hint.clone()))
-                        },
-                    ) {
-                        to_insert.push(hint_to_readd);
-                    }
-                }
-            }
+            let reenabled_hints = self
+                .inlays_in_buffers
+                .iter()
+                .filter_map(|(cached_buffer_id, cached_hints_per_excerpt)| {
+                    let considered_hints_in_excerpts = considered_hints.get(cached_buffer_id)?;
+                    let not_considered_cached_inlays = cached_hints_per_excerpt
+                        .excerpt_inlays
+                        .iter()
+                        .filter_map(|(cached_excerpt_id, cached_hints)| {
+                            let considered_excerpt_hints =
+                                considered_hints_in_excerpts.get(&cached_excerpt_id)?;
+                            let not_considered_cached_inlays = cached_hints
+                                .iter()
+                                .filter(|(_, cached_hint_id)| {
+                                    !considered_excerpt_hints.contains(cached_hint_id)
+                                })
+                                .copied();
+                            Some(not_considered_cached_inlays)
+                        })
+                        .flatten();
+                    Some(not_considered_cached_inlays)
+                })
+                .flatten()
+                .filter_map(|(cached_anchor, cached_inlay_id)| {
+                    Some((
+                        cached_anchor,
+                        cached_inlay_id,
+                        self.inlay_hints.get(&cached_inlay_id)?,
+                    ))
+                })
+                .filter(|(_, _, cached_inlay)| self.allowed_hint_kinds.contains(&cached_inlay.kind))
+                .map(|(cached_anchor, cached_inlay_id, reenabled_inlay)| {
+                    (
+                        Some(cached_inlay_id),
+                        cached_anchor,
+                        reenabled_inlay.clone(),
+                    )
+                });
+            to_insert.extend(reenabled_hints);
+
+            to_remove.extend(
+                currently_shown_inlay_hints
+                    .into_iter()
+                    .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt)
+                    .flat_map(|(_, excerpt_hints)| excerpt_hints)
+                    .map(|(_, hint_id)| hint_id),
+            );
 
             Some(InlaySplice {
                 to_remove,
@@ -114,13 +177,13 @@ impl InlayCache {
         &mut self,
         multi_buffer: ModelHandle<MultiBuffer>,
         ranges_to_add: impl Iterator<Item = InlayHintQuery>,
-        currently_shown_inlays: Vec<(Anchor, InlayId)>,
+        currently_shown_inlay_hints: HashMap<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>,
         cx: &mut ViewContext<Editor>,
     ) -> Task<anyhow::Result<InlaySplice>> {
         self.fetch_inlays(
             multi_buffer,
             ranges_to_add,
-            currently_shown_inlays,
+            currently_shown_inlay_hints,
             false,
             cx,
         )
@@ -130,21 +193,52 @@ impl InlayCache {
         &mut self,
         multi_buffer: ModelHandle<MultiBuffer>,
         new_ranges: impl Iterator<Item = InlayHintQuery>,
-        currently_shown_inlays: Vec<(Anchor, InlayId)>,
+        currently_shown_inlay_hints: HashMap<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>,
         cx: &mut ViewContext<Editor>,
     ) -> Task<anyhow::Result<InlaySplice>> {
-        self.fetch_inlays(multi_buffer, new_ranges, currently_shown_inlays, true, cx)
+        self.fetch_inlays(
+            multi_buffer,
+            new_ranges,
+            currently_shown_inlay_hints,
+            true,
+            cx,
+        )
     }
 
     fn fetch_inlays(
         &mut self,
         multi_buffer: ModelHandle<MultiBuffer>,
-        inlay_fetch_ranges: impl Iterator<Item = InlayHintQuery>,
-        currently_shown_inlays: Vec<(Anchor, InlayId)>,
+        inlay_queries: impl Iterator<Item = InlayHintQuery>,
+        mut currently_shown_inlay_hints: HashMap<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>,
         replace_old: bool,
         cx: &mut ViewContext<Editor>,
     ) -> Task<anyhow::Result<InlaySplice>> {
-        // TODO kb
+        let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
+        let inlay_queries_per_buffer = inlay_queries.fold(
+            HashMap::<u64, BufferInlays<InlayHintQuery>>::default(),
+            |mut queries, new_query| {
+                let mut buffer_queries = queries
+                    .entry(new_query.buffer_id)
+                    .or_insert_with(|| BufferInlays::new(new_query.buffer_version.clone()));
+                assert_eq!(buffer_queries.buffer_version, new_query.buffer_version);
+                let queries = buffer_queries
+                    .excerpt_inlays
+                    .entry(new_query.excerpt_id)
+                    .or_default();
+                // let z = multi_buffer_snapshot.anchor_in_excerpt(new_query.excerpt_id, text_anchor);
+                // .push(new_query);
+                // match queries
+                //     .binary_search_by(|probe| inlay.position.cmp(&probe.0, &multi_buffer_snapshot))
+                // {
+                //     Ok(ix) | Err(ix) => {
+                //         excerpt_hints.insert(ix, (inlay.position, inlay.id));
+                //     }
+                // }
+                // queries
+                todo!("TODO kb")
+            },
+        );
+
         todo!("TODO kb")
     }
 

crates/editor/src/multi_buffer.rs 🔗

@@ -2617,15 +2617,6 @@ impl MultiBufferSnapshot {
     }
 
     pub fn anchor_in_excerpt(&self, excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Anchor {
-        self.find_anchor_in_excerpt(excerpt_id, text_anchor)
-            .unwrap_or_else(|| panic!("excerpt not found"))
-    }
-
-    pub fn find_anchor_in_excerpt(
-        &self,
-        excerpt_id: ExcerptId,
-        text_anchor: text::Anchor,
-    ) -> Option<Anchor> {
         let locator = self.excerpt_locator_for_id(excerpt_id);
         let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
         cursor.seek(locator, Bias::Left, &());
@@ -2633,15 +2624,15 @@ impl MultiBufferSnapshot {
             if excerpt.id == excerpt_id {
                 let text_anchor = excerpt.clip_anchor(text_anchor);
                 drop(cursor);
-                return Some(Anchor {
+                return Anchor {
                     buffer_id: Some(excerpt.buffer_id),
                     excerpt_id,
                     text_anchor,
-                });
+                };
             }
         }
 
-        None
+        panic!("excerpt not found")
     }
 
     pub fn can_resolve(&self, anchor: &Anchor) -> bool {

crates/editor/src/scroll.rs 🔗

@@ -18,7 +18,7 @@ use workspace::WorkspaceId;
 use crate::{
     display_map::{DisplaySnapshot, ToDisplayPoint},
     hover_popover::hide_hover,
-    inlay_cache::InlayRefreshReason,
+    inlay_hint_cache::InlayRefreshReason,
     persistence::DB,
     Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint,
 };