Draft the hint render data flow

Kirill Bulatov created

Change summary

crates/editor/src/display_map.rs                     | 21 +++++++
crates/editor/src/display_map/editor_addition_map.rs | 42 +++++++++++---
crates/editor/src/display_map/tab_map.rs             | 12 ++-
crates/editor/src/editor.rs                          | 25 +++++++-
crates/project/src/lsp_command.rs                    |  2 
crates/project/src/project.rs                        | 25 ++++++--
6 files changed, 103 insertions(+), 24 deletions(-)

Detailed changes

crates/editor/src/display_map.rs 🔗

@@ -30,6 +30,8 @@ pub use block_map::{
     BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
 };
 
+use self::editor_addition_map::InlayHintToRender;
+
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub enum FoldStatus {
     Folded,
@@ -286,6 +288,25 @@ impl DisplayMap {
             .update(cx, |map, cx| map.set_wrap_width(width, cx))
     }
 
+    pub fn set_inlay_hints(&self, new_hints: &[project::InlayHint], cx: &mut ModelContext<Self>) {
+        let multi_buffer = self.buffer.read(cx);
+        self.editor_addition_map.set_inlay_hints(
+            new_hints
+                .into_iter()
+                .filter_map(|hint| {
+                    let buffer = multi_buffer.buffer(hint.buffer_id)?.read(cx);
+                    let snapshot = buffer.snapshot();
+                    Some(InlayHintToRender {
+                        position: editor_addition_map::EditorAdditionPoint(
+                            text::ToPoint::to_point(&hint.position, &snapshot),
+                        ),
+                        text: hint.text().trim_end().into(),
+                    })
+                })
+                .collect(),
+        )
+    }
+
     fn tab_size(buffer: &ModelHandle<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 {
         let language = buffer
             .read(cx)

crates/editor/src/display_map/editor_addition_map.rs 🔗

@@ -10,17 +10,20 @@ use super::{
     TextHighlights,
 };
 use gpui::fonts::HighlightStyle;
-use language::{Chunk, Edit, Point, TextSummary};
+use language::{Chunk, Edit, Point, Rope, TextSummary};
+use parking_lot::Mutex;
+use project::InlayHint;
 use rand::Rng;
 use sum_tree::Bias;
 
-pub struct EditorAdditionMap;
+pub struct EditorAdditionMap(Mutex<EditorAdditionSnapshot>);
 
 #[derive(Clone)]
 pub struct EditorAdditionSnapshot {
     // TODO kb merge these two together
     pub suggestion_snapshot: SuggestionSnapshot,
     pub version: usize,
+    hints: Vec<InlayHintToRender>,
 }
 
 pub type EditorAdditionEdit = Edit<EditorAdditionOffset>;
@@ -63,6 +66,12 @@ pub struct EditorAdditionChunks<'a> {
     _z: &'a std::marker::PhantomData<()>,
 }
 
+#[derive(Clone)]
+pub struct InlayHintToRender {
+    pub(super) position: EditorAdditionPoint,
+    pub(super) text: Rope,
+}
+
 impl<'a> Iterator for EditorAdditionChunks<'a> {
     type Item = Chunk<'a>;
 
@@ -95,7 +104,12 @@ impl EditorAdditionPoint {
 
 impl EditorAdditionMap {
     pub fn new(suggestion_snapshot: SuggestionSnapshot) -> (Self, EditorAdditionSnapshot) {
-        todo!("TODO kb")
+        let snapshot = EditorAdditionSnapshot {
+            suggestion_snapshot: suggestion_snapshot.clone(),
+            version: 0,
+            hints: Vec::new(),
+        };
+        (Self(Mutex::new(snapshot.clone())), snapshot)
     }
 
     pub fn sync(
@@ -103,14 +117,24 @@ impl EditorAdditionMap {
         suggestion_snapshot: SuggestionSnapshot,
         suggestion_edits: Vec<SuggestionEdit>,
     ) -> (EditorAdditionSnapshot, Vec<EditorAdditionEdit>) {
-        todo!("TODO kb")
+        let mut snapshot = self.0.lock();
+
+        if snapshot.suggestion_snapshot.version != suggestion_snapshot.version {
+            snapshot.version += 1;
+        }
+
+        let editor_addition_edits = Vec::new();
+        {
+            todo!("TODO kb")
+        }
+
+        snapshot.suggestion_snapshot = suggestion_snapshot;
+
+        (snapshot.clone(), editor_addition_edits)
     }
 
-    pub fn randomly_mutate(
-        &self,
-        rng: &mut impl Rng,
-    ) -> (EditorAdditionSnapshot, Vec<EditorAdditionEdit>) {
-        todo!("TODO kb")
+    pub fn set_inlay_hints(&self, new_hints: Vec<InlayHintToRender>) {
+        self.0.lock().hints = new_hints;
     }
 }
 

crates/editor/src/display_map/tab_map.rs 🔗

@@ -761,11 +761,13 @@ mod tests {
         let (suggestion_map, _) = SuggestionMap::new(fold_snapshot);
         let (suggestion_snapshot, _) = suggestion_map.randomly_mutate(&mut rng);
         log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text());
-        let (editor_addition_map, _) = EditorAdditionMap::new(suggestion_snapshot.clone());
-        let (suggestion_snapshot, _) = editor_addition_map.randomly_mutate(&mut rng);
-        log::info!("EditorAdditionMap text: {:?}", suggestion_snapshot.text());
+        let (_, editor_addition_snapshot) = EditorAdditionMap::new(suggestion_snapshot.clone());
+        log::info!(
+            "EditorAdditionMap text: {:?}",
+            editor_addition_snapshot.text()
+        );
 
-        let (tab_map, _) = TabMap::new(suggestion_snapshot.clone(), tab_size);
+        let (tab_map, _) = TabMap::new(editor_addition_snapshot.clone(), tab_size);
         let tabs_snapshot = tab_map.set_max_expansion_column(32);
 
         let text = text::Rope::from(tabs_snapshot.text().as_str());
@@ -803,7 +805,7 @@ mod tests {
             );
 
             let mut actual_summary = tabs_snapshot.text_summary_for_range(start..end);
-            if tab_size.get() > 1 && suggestion_snapshot.text().contains('\t') {
+            if tab_size.get() > 1 && editor_addition_snapshot.text().contains('\t') {
                 actual_summary.longest_row = expected_summary.longest_row;
                 actual_summary.longest_row_chars = expected_summary.longest_row_chars;
             }

crates/editor/src/editor.rs 🔗

@@ -1169,11 +1169,19 @@ impl InlayHintState {
         Self::first_timestamp_newer(timestamp, &current_timestamp)
     }
 
-    fn update_if_newer(&self, new_hints: Vec<InlayHint>, new_timestamp: HashMap<usize, Global>) {
+    fn update_if_newer(
+        &self,
+        new_hints: Vec<InlayHint>,
+        new_timestamp: HashMap<usize, Global>,
+    ) -> bool {
         let mut guard = self.0.write();
         if Self::first_timestamp_newer(&new_timestamp, &guard.0) {
             guard.0 = new_timestamp;
             guard.1 = new_hints;
+
+            true
+        } else {
+            false
         }
     }
 
@@ -2688,7 +2696,7 @@ impl Editor {
 
         let inlay_hints_storage = Arc::clone(&self.inlay_hints);
         if inlay_hints_storage.is_newer(&new_timestamp) {
-            cx.spawn(|_, _| async move {
+            cx.spawn(|editor, mut cx| async move {
                 let mut new_hints = Vec::new();
                 for task_result in futures::future::join_all(hint_fetch_tasks).await {
                     match task_result {
@@ -2696,7 +2704,18 @@ impl Editor {
                         Err(e) => error!("Failed to update hints for buffer: {e:#}"),
                     }
                 }
-                inlay_hints_storage.update_if_newer(new_hints, new_timestamp);
+
+                // TODO kb another odd clone, can be avoid all this? hide hints behind a handle?
+                if inlay_hints_storage.update_if_newer(new_hints.clone(), new_timestamp) {
+                    editor
+                        .update(&mut cx, |editor, cx| {
+                            editor.display_map.update(cx, |display_map, cx| {
+                                display_map.set_inlay_hints(&new_hints, cx)
+                            });
+                        })
+                        .log_err()
+                        .unwrap_or(())
+                }
             })
             .detach();
         }

crates/project/src/lsp_command.rs 🔗

@@ -1834,6 +1834,7 @@ impl LspCommand for InlayHints {
                 .unwrap_or_default()
                 .into_iter()
                 .map(|lsp_hint| InlayHint {
+                    buffer_id: buffer.id() as u64,
                     position: origin_buffer.anchor_after(
                         origin_buffer
                             .clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left),
@@ -2006,6 +2007,7 @@ impl LspCommand for InlayHints {
         let mut hints = Vec::new();
         for message_hint in message.hints {
             let hint = InlayHint {
+                buffer_id: buffer.id() as u64,
                 position: message_hint
                     .position
                     .and_then(language::proto::deserialize_anchor)

crates/project/src/project.rs 🔗

@@ -29,6 +29,7 @@ use gpui::{
     AnyModelHandle, AppContext, AsyncAppContext, BorrowAppContext, Entity, ModelContext,
     ModelHandle, Task, WeakModelHandle,
 };
+use itertools::Itertools;
 use language::{
     language_settings::{language_settings, FormatOnSave, Formatter},
     point_to_lsp,
@@ -320,46 +321,56 @@ pub struct DiagnosticSummary {
     pub warning_count: usize,
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct Location {
     pub buffer: ModelHandle<Buffer>,
     pub range: Range<language::Anchor>,
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct InlayHint {
+    pub buffer_id: u64,
     pub position: Anchor,
     pub label: InlayHintLabel,
     pub kind: Option<String>,
     pub tooltip: Option<InlayHintTooltip>,
 }
 
-#[derive(Debug, Clone)]
+impl InlayHint {
+    pub fn text(&self) -> String {
+        match &self.label {
+            InlayHintLabel::String(s) => s.to_owned(),
+            InlayHintLabel::LabelParts(parts) => parts.iter().map(|part| &part.value).join(""),
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub enum InlayHintLabel {
     String(String),
     LabelParts(Vec<InlayHintLabelPart>),
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct InlayHintLabelPart {
     pub value: String,
     pub tooltip: Option<InlayHintLabelPartTooltip>,
     pub location: Option<Location>,
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub enum InlayHintTooltip {
     String(String),
     MarkupContent(MarkupContent),
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub enum InlayHintLabelPartTooltip {
     String(String),
     MarkupContent(MarkupContent),
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct MarkupContent {
     pub kind: String,
     pub value: String,