Display excerpt-ranged hints only

Kirill Bulatov created

Change summary

crates/editor/src/display_map.rs        |  25 +--
crates/editor/src/editor.rs             | 168 +++++++++++---------------
crates/editor/src/inlay_hint_storage.rs |  43 ++++++
3 files changed, 126 insertions(+), 110 deletions(-)

Detailed changes

crates/editor/src/display_map.rs 🔗

@@ -6,8 +6,8 @@ mod tab_map;
 mod wrap_map;
 
 use crate::{
-    display_map::inlay_map::InlayProperties, Anchor, AnchorRangeExt, InlayHintLocation,
-    MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
+    display_map::inlay_map::InlayProperties, Anchor, AnchorRangeExt, MultiBuffer,
+    MultiBufferSnapshot, ToOffset, ToPoint,
 };
 pub use block_map::{BlockMap, BlockPoint};
 use collections::{HashMap, HashSet};
@@ -287,7 +287,7 @@ impl DisplayMap {
 
     pub fn splice_inlays(
         &mut self,
-        new_hints: &HashMap<InlayHintLocation, Vec<project::InlayHint>>,
+        new_hints: Vec<(Anchor, project::InlayHint)>,
         cx: &mut ModelContext<Self>,
     ) {
         let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
@@ -302,18 +302,13 @@ impl DisplayMap {
             .update(cx, |map, cx| map.sync(snapshot, edits, cx));
         self.block_map.read(snapshot, edits);
 
-        let mut new_inlays = Vec::new();
-        for (&location, hints) in new_hints {
-            for hint in hints {
-                let hint_anchor =
-                    buffer_snapshot.anchor_in_excerpt(location.excerpt_id, hint.position);
-                new_inlays.push(InlayProperties {
-                    position: hint_anchor.bias_left(&buffer_snapshot),
-                    text: hint.text(),
-                });
-            }
-        }
-
+        let new_inlays = new_hints
+            .into_iter()
+            .map(|(hint_anchor, hint)| InlayProperties {
+                position: hint_anchor.bias_left(&buffer_snapshot),
+                text: hint.text(),
+            })
+            .collect();
         let (snapshot, edits, _) = self.inlay_map.splice(
             // TODO kb this is wrong, calc diffs in the editor instead.
             self.inlay_map.inlays.keys().copied().collect(),

crates/editor/src/editor.rs 🔗

@@ -2,6 +2,7 @@ mod blink_manager;
 pub mod display_map;
 mod editor_settings;
 mod element;
+mod inlay_hint_storage;
 
 mod git;
 mod highlight_matching_bracket;
@@ -25,7 +26,7 @@ use aho_corasick::AhoCorasick;
 use anyhow::{anyhow, Context, Result};
 use blink_manager::BlinkManager;
 use client::{ClickhouseEvent, TelemetrySettings};
-use clock::{Global, ReplicaId};
+use clock::ReplicaId;
 use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
 use copilot::Copilot;
 pub use display_map::DisplayPoint;
@@ -52,6 +53,7 @@ use gpui::{
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
+use inlay_hint_storage::InlayHintStorage;
 pub use items::MAX_TAB_TITLE_LEN;
 use itertools::Itertools;
 pub use language::{char_kind, CharKind};
@@ -71,7 +73,9 @@ pub use multi_buffer::{
 };
 use multi_buffer::{MultiBufferChunks, ToOffsetUtf16};
 use ordered_float::OrderedFloat;
-use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction};
+use project::{
+    FormatTrigger, InlayHint, Location, LocationLink, Project, ProjectPath, ProjectTransaction,
+};
 use scroll::{
     autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
 };
@@ -536,7 +540,7 @@ pub struct Editor {
     gutter_hovered: bool,
     link_go_to_definition_state: LinkGoToDefinitionState,
     copilot_state: CopilotState,
-    inlay_hint_versions: InlayHintVersions,
+    inlay_hint_storage: InlayHintStorage,
     _subscriptions: Vec<Subscription>,
 }
 
@@ -1153,37 +1157,6 @@ impl CopilotState {
     }
 }
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct InlayHintLocation {
-    pub buffer_id: u64,
-    pub excerpt_id: ExcerptId,
-}
-
-// TODO kb
-#[derive(Debug, Default, Clone)]
-struct InlayHintVersions {
-    last_buffer_versions_with_hints: HashMap<InlayHintLocation, Global>,
-}
-
-impl InlayHintVersions {
-    fn absent_or_newer(&self, location: &InlayHintLocation, new_version: &Global) -> bool {
-        self.last_buffer_versions_with_hints
-            .get(location)
-            .map(|last_version_with_hints| new_version.changed_since(&last_version_with_hints))
-            .unwrap_or(true)
-    }
-
-    fn insert(&mut self, location: InlayHintLocation, new_version: Global) -> bool {
-        if self.absent_or_newer(&location, &new_version) {
-            self.last_buffer_versions_with_hints
-                .insert(location, new_version);
-            true
-        } else {
-            false
-        }
-    }
-}
-
 #[derive(Debug)]
 struct ActiveDiagnosticGroup {
     primary_range: Range<Anchor>,
@@ -1324,7 +1297,7 @@ impl Editor {
                 project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| {
                     match event {
                         project::Event::ReloadInlayHints => {
-                            editor.try_update_inlay_hints(cx);
+                            editor.reload_inlay_hints(cx);
                         }
                         _ => {}
                     };
@@ -1382,7 +1355,7 @@ impl Editor {
             hover_state: Default::default(),
             link_go_to_definition_state: Default::default(),
             copilot_state: Default::default(),
-            inlay_hint_versions: InlayHintVersions::default(),
+            inlay_hint_storage: InlayHintStorage::default(),
             gutter_hovered: false,
             _subscriptions: vec![
                 cx.observe(&buffer, Self::on_buffer_changed),
@@ -2618,44 +2591,44 @@ impl Editor {
         }
     }
 
-    fn try_update_inlay_hints(&self, cx: &mut ViewContext<Self>) {
+    fn reload_inlay_hints(&self, cx: &mut ViewContext<Self>) {
         if self.mode != EditorMode::Full {
             return;
         }
 
-        let multi_buffer = self.buffer().read(cx);
-        let buffer_snapshot = multi_buffer.snapshot(cx);
-        let hint_fetch_tasks = buffer_snapshot
-            .excerpts()
-            .map(|(excerpt_id, excerpt_buffer_snapshot, _)| {
-                (excerpt_id, excerpt_buffer_snapshot.clone())
-            })
-            .map(|(excerpt_id, excerpt_buffer_snapshot)| {
+        let multi_buffer = self.buffer();
+        let hint_fetch_tasks = multi_buffer
+            .read(cx)
+            .all_buffers()
+            .into_iter()
+            .map(|buffer_handle| {
+                let buffer = buffer_handle.read(cx);
+                // TODO kb every time I reopen the same buffer, it's different.
+                // Find a way to understand it's the same buffer. Use paths?
+                dbg!(buffer_handle.id());
+                let buffer_id = dbg!(buffer.remote_id());
+                let buffer_len = buffer.len();
+
                 cx.spawn(|editor, mut cx| async move {
                     let task = editor
                         .update(&mut cx, |editor, cx| {
-                            editor.project.as_ref().and_then(|project| {
+                            editor.project.as_ref().map(|project| {
                                 project.update(cx, |project, cx| {
-                                    Some(
-                                        project.inlay_hints_for_buffer(
-                                            editor
-                                                .buffer()
-                                                .read(cx)
-                                                .buffer(excerpt_buffer_snapshot.remote_id())?,
-                                            0..excerpt_buffer_snapshot.len(),
-                                            cx,
-                                        ),
-                                    )
+                                    project.inlay_hints_for_buffer(buffer_handle, 0..buffer_len, cx)
                                 })
                             })
                         })
                         .context("inlay hints fecth task spawn")?;
 
                     anyhow::Ok((
-                        excerpt_id,
-                        excerpt_buffer_snapshot,
+                        buffer_id,
                         match task {
-                            Some(task) => task.await.context("inlay hints for buffer task")?,
+                            Some(task) => {
+                                let mut buffer_hints =
+                                    task.await.context("inlay hints for buffer task")?;
+                                buffer_hints.sort_unstable_by_key(|hint| hint.position.offset);
+                                buffer_hints
+                            }
                             None => Vec::new(),
                         },
                     ))
@@ -2664,52 +2637,57 @@ impl Editor {
             .collect::<Vec<_>>();
 
         cx.spawn(|editor, mut cx| async move {
-            let mut new_hints: HashMap<InlayHintLocation, Vec<project::InlayHint>> =
-                HashMap::default();
+            let mut hints_to_draw: Vec<(Anchor, InlayHint)> = Vec::new();
+            let (multi_buffer, multi_buffer_snapshot) = editor.read_with(&cx, |editor, cx| {
+                let multi_buffer = editor.buffer().clone();
+                let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
+                (multi_buffer, multi_buffer_snapshot)
+            })?;
+
             for task_result in futures::future::join_all(hint_fetch_tasks).await {
                 match task_result {
-                    Ok((excerpt_id, excerpt_buffer_snapshot, excerpt_hints)) => {
-                        let buffer_id = excerpt_buffer_snapshot.remote_id();
-                        let should_update_hints = editor
-                            .update(&mut cx, |editor, _| {
-                                // TODO kb wrong: need to query hints per buffer, not per excerpt
-                                // need to store the previous state and calculate the diff between them, and calculate anchors here too.
-                                editor.inlay_hint_versions.insert(
-                                    InlayHintLocation {
-                                        buffer_id,
-                                        excerpt_id,
-                                    },
-                                    excerpt_buffer_snapshot.version().clone(),
-                                )
-                            })
-                            .log_err()
-                            .unwrap_or(false);
-                        if should_update_hints {
-                            new_hints
-                                .entry(InlayHintLocation {
-                                    buffer_id,
-                                    excerpt_id,
+                    Ok((buffer_id, sorted_buffer_hints)) => {
+                        let Some(buffer_excerpts) = cx.read(|cx| {
+                            let multi_buffer = multi_buffer.read(cx);
+                            multi_buffer.buffer(buffer_id).map(|buffer| multi_buffer.excerpts_for_buffer(&buffer, cx))
+                        }) else { continue };
+                        for (excerpt_id, excerpt_range) in buffer_excerpts {
+                            let excerpt_hints = sorted_buffer_hints
+                                .iter()
+                                .cloned()
+                                .skip_while(|hint| {
+                                    hint.position.offset < excerpt_range.context.start.offset
+                                })
+                                .take_while(|hint| {
+                                    hint.position.offset <= excerpt_range.context.end.offset
                                 })
-                                .or_default()
-                                .extend(excerpt_hints);
+                                .collect::<Vec<_>>();
+
+                            if !excerpt_hints.is_empty() {
+                                hints_to_draw.extend(excerpt_hints.into_iter().map(|hint| {
+                                    let anchor = multi_buffer_snapshot
+                                        .anchor_in_excerpt(excerpt_id, hint.position);
+                                    (anchor, hint)
+                                }));
+                            }
                         }
                     }
                     Err(e) => error!("Failed to update hints for buffer: {e:#}"),
                 }
             }
 
-            if !new_hints.is_empty() {
-                editor
-                    .update(&mut cx, |editor, cx| {
-                        editor.display_map.update(cx, |display_map, cx| {
-                            display_map.splice_inlays(&new_hints, cx);
-                        });
-                    })
-                    .log_err()
-                    .unwrap_or(())
+            // TODO kb calculate diffs using the storage instead
+            if !hints_to_draw.is_empty() {
+                editor.update(&mut cx, |editor, cx| {
+                    editor.display_map.update(cx, |display_map, cx| {
+                        display_map.splice_inlays(hints_to_draw, cx);
+                    });
+                })?;
             }
+
+            anyhow::Ok(())
         })
-        .detach();
+        .detach_and_log_err(cx);
     }
 
     fn trigger_on_type_formatting(
@@ -7292,7 +7270,7 @@ impl Editor {
         };
 
         if update_inlay_hints {
-            self.try_update_inlay_hints(cx);
+            self.reload_inlay_hints(cx);
         }
     }
 

crates/editor/src/inlay_hint_storage.rs 🔗

@@ -0,0 +1,43 @@
+use crate::Anchor;
+use project::InlayHint;
+
+use collections::BTreeMap;
+
+#[derive(Debug, Default)]
+pub struct InlayHintStorage {
+    hints: BTreeMap<Anchor, InlayHint>,
+}
+
+impl InlayHintStorage {
+    // TODO kb calculate the diff instead
+    fn insert(&mut self) -> bool {
+        todo!("TODO kb")
+    }
+}
+
+// let buffer_version =
+// cx.read(|cx| buffer.read(cx).version().clone());
+
+// #[derive(Debug, Default, Clone)]
+// struct InlayHintVersions {
+//     last_buffer_versions_with_hints: HashMap<InlayHintLocation, Global>,
+// }
+
+// impl InlayHintVersions {
+//     fn absent_or_newer(&self, location: &InlayHintLocation, new_version: &Global) -> bool {
+//         self.last_buffer_versions_with_hints
+//             .get(location)
+//             .map(|last_version_with_hints| new_version.changed_since(&last_version_with_hints))
+//             .unwrap_or(true)
+//     }
+
+//     fn insert(&mut self, location: InlayHintLocation, new_version: Global) -> bool {
+//         if self.absent_or_newer(&location, &new_version) {
+//             self.last_buffer_versions_with_hints
+//                 .insert(location, new_version);
+//             true
+//         } else {
+//             false
+//         }
+//     }
+// }