Query hints when editors gets open and visible

Kirill Bulatov created

Change summary

crates/collab/src/tests/integration_tests.rs | 163 ++++++++++++---------
crates/editor/src/editor.rs                  |   1 
crates/editor/src/element.rs                 |   2 
crates/editor/src/scroll.rs                  |  18 ++
4 files changed, 107 insertions(+), 77 deletions(-)

Detailed changes

crates/collab/src/tests/integration_tests.rs 🔗

@@ -7896,6 +7896,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
         .unwrap();
 
     let workspace_a = client_a.build_workspace(&project_a, cx_a);
+    cx_a.foreground().start_waiting();
+
     let editor_a = workspace_a
         .update(cx_a, |workspace, cx| {
             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
@@ -7904,58 +7906,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
         .unwrap()
         .downcast::<Editor>()
         .unwrap();
-    cx_a.foreground().run_until_parked();
-    editor_a.update(cx_a, |editor, _| {
-        assert!(
-            extract_hint_labels(editor).is_empty(),
-            "No inlays should be in the new cache"
-        );
-        let inlay_cache = editor.inlay_hint_cache();
-        assert_eq!(
-            inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
-            "Cache should use editor settings to get the allowed hint kinds"
-        );
-        assert_eq!(
-            inlay_cache.version, 0,
-            "New cache should have no version updates"
-        );
-    });
-    let workspace_b = client_b.build_workspace(&project_b, cx_b);
-    let editor_b = workspace_b
-        .update(cx_b, |workspace, cx| {
-            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
-        })
-        .await
-        .unwrap()
-        .downcast::<Editor>()
-        .unwrap();
 
-    cx_b.foreground().run_until_parked();
-    editor_b.update(cx_b, |editor, _| {
-        assert!(
-            extract_hint_labels(editor).is_empty(),
-            "No inlays should be in the new cache"
-        );
-        let inlay_cache = editor.inlay_hint_cache();
-        assert_eq!(
-            inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
-            "Cache should use editor settings to get the allowed hint kinds"
-        );
-        assert_eq!(
-            inlay_cache.version, 0,
-            "New cache should have no version updates"
-        );
-    });
-
-    cx_a.foreground().start_waiting();
-    let mut edits_made = 0;
     let fake_language_server = fake_language_servers.next().await.unwrap();
-    editor_b.update(cx_b, |editor, cx| {
-        editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
-        editor.handle_input(":", cx);
-        cx.focus(&editor_b);
-        edits_made += 1;
-    });
     let next_call_id = Arc::new(AtomicU32::new(0));
     fake_language_server
         .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
@@ -7992,37 +7944,77 @@ async fn test_mutual_editor_inlay_hint_cache_update(
     cx_a.foreground().finish_waiting();
     cx_a.foreground().run_until_parked();
 
-    fn extract_hint_labels(editor: &Editor) -> Vec<String> {
-        let mut labels = Vec::new();
-        for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
-            let excerpt_hints = excerpt_hints.read();
-            for (_, inlay) in excerpt_hints.hints.iter() {
-                match &inlay.label {
-                    project::InlayHintLabel::String(s) => labels.push(s.to_string()),
-                    _ => unreachable!(),
-                }
-            }
-        }
-        labels
-    }
-
+    let mut edits_made = 0;
+    edits_made += 1;
     editor_a.update(cx_a, |editor, _| {
         assert_eq!(
             vec!["0".to_string()],
             extract_hint_labels(editor),
-            "Host should get hints from the 1st edit and 1st LSP query"
+            "Host should get its first hints when opens an editor"
         );
         let inlay_cache = editor.inlay_hint_cache();
-        assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Inlay kinds settings never change during the test");
+        assert_eq!(
+            inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
+            "Cache should use editor settings to get the allowed hint kinds"
+        );
         assert_eq!(
             inlay_cache.version, edits_made,
-            "Each editor should track its own inlay cache history, which should be incremented after every cache/view change"
+            "Host editor should track its own inlay cache history, which should be incremented after every cache/view change"
         );
     });
+    let workspace_b = client_b.build_workspace(&project_b, cx_b);
+    let editor_b = workspace_b
+        .update(cx_b, |workspace, cx| {
+            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .unwrap();
+
+    cx_b.foreground().run_until_parked();
     editor_b.update(cx_b, |editor, _| {
         assert_eq!(
             vec!["0".to_string(), "1".to_string()],
             extract_hint_labels(editor),
+            "Client should get its first hints when opens an editor"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
+            "Cache should use editor settings to get the allowed hint kinds"
+        );
+        assert_eq!(
+            inlay_cache.version, edits_made,
+            "Client editor should track its own inlay cache history, which should be incremented after every cache/view change"
+        );
+    });
+
+    editor_b.update(cx_b, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
+        editor.handle_input(":", cx);
+        cx.focus(&editor_b);
+        edits_made += 1;
+    });
+    cx_a.foreground().run_until_parked();
+    cx_b.foreground().run_until_parked();
+    editor_a.update(cx_a, |editor, _| {
+        assert_eq!(
+            vec!["0".to_string(), "1".to_string(), "2".to_string()],
+            extract_hint_labels(editor),
+            "Host should get hints from the 1st edit and 1st LSP query"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
+            "Inlay kinds settings never change during the test"
+        );
+        assert_eq!(inlay_cache.version, edits_made);
+    });
+    editor_b.update(cx_b, |editor, _| {
+        assert_eq!(
+            vec!["0".to_string(), "1".to_string(), "2".to_string(), "3".to_string()],
+            extract_hint_labels(editor),
             "Guest should get hints the 1st edit and 2nd LSP query"
         );
         let inlay_cache = editor.inlay_hint_cache();
@@ -8043,7 +8035,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
     cx_b.foreground().run_until_parked();
     editor_a.update(cx_a, |editor, _| {
         assert_eq!(
-            vec!["0".to_string(), "1".to_string(), "2".to_string()],
+            vec!["0".to_string(), "1".to_string(), "2".to_string(), "3".to_string(), "4".to_string()],
             extract_hint_labels(editor),
             "Host should get hints from 3rd edit, 5th LSP query: \
 4th query was made by guest (but not applied) due to cache invalidation logic"
@@ -8061,7 +8053,9 @@ async fn test_mutual_editor_inlay_hint_cache_update(
                 "0".to_string(),
                 "1".to_string(),
                 "2".to_string(),
-                "3".to_string()
+                "3".to_string(),
+                "4".to_string(),
+                "5".to_string(),
             ],
             extract_hint_labels(editor),
             "Guest should get hints from 3rd edit, 6th LSP query"
@@ -8091,7 +8085,9 @@ async fn test_mutual_editor_inlay_hint_cache_update(
                 "1".to_string(),
                 "2".to_string(),
                 "3".to_string(),
-                "4".to_string()
+                "4".to_string(),
+                "5".to_string(),
+                "6".to_string(),
             ],
             extract_hint_labels(editor),
             "Host should react to /refresh LSP request and get new hints from 7th LSP query"
@@ -8108,7 +8104,16 @@ async fn test_mutual_editor_inlay_hint_cache_update(
     });
     editor_b.update(cx_b, |editor, _| {
         assert_eq!(
-            vec!["0".to_string(), "1".to_string(), "2".to_string(), "3".to_string(), "4".to_string(), "5".to_string()],
+            vec![
+                "0".to_string(),
+                "1".to_string(),
+                "2".to_string(),
+                "3".to_string(),
+                "4".to_string(),
+                "5".to_string(),
+                "6".to_string(),
+                "7".to_string(),
+            ],
             extract_hint_labels(editor),
             "Guest should get a /refresh LSP request propagated by host and get new hints from 8th LSP query"
         );
@@ -8120,7 +8125,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
         assert_eq!(
             inlay_cache.version,
             edits_made,
-            "Gues should accepted all edits and bump its cache version every time"
+            "Guest should accepted all edits and bump its cache version every time"
         );
     });
 }
@@ -8148,3 +8153,17 @@ fn room_participants(room: &ModelHandle<Room>, cx: &mut TestAppContext) -> RoomP
         RoomParticipants { remote, pending }
     })
 }
+
+fn extract_hint_labels(editor: &Editor) -> Vec<String> {
+    let mut labels = Vec::new();
+    for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
+        let excerpt_hints = excerpt_hints.read();
+        for (_, inlay) in excerpt_hints.hints.iter() {
+            match &inlay.label {
+                project::InlayHintLabel::String(s) => labels.push(s.to_string()),
+                _ => unreachable!(),
+            }
+        }
+    }
+    labels
+}

crates/editor/src/editor.rs 🔗

@@ -1393,7 +1393,6 @@ impl Editor {
         }
 
         this.report_editor_event("open", None, cx);
-        this.refresh_inlays(InlayRefreshReason::ExcerptEdited, cx);
         this
     }
 

crates/editor/src/element.rs 🔗

@@ -1921,7 +1921,7 @@ impl Element<Editor> for EditorElement {
         let em_advance = style.text.em_advance(cx.font_cache());
         let overscroll = vec2f(em_width, 0.);
         let snapshot = {
-            editor.set_visible_line_count(size.y() / line_height);
+            editor.set_visible_line_count(size.y() / line_height, cx);
 
             let editor_width = text_width - gutter_margin - overscroll.x() - em_width;
             let wrap_width = match editor.soft_wrap_mode(cx) {

crates/editor/src/scroll.rs 🔗

@@ -19,7 +19,8 @@ use crate::{
     display_map::{DisplaySnapshot, ToDisplayPoint},
     hover_popover::hide_hover,
     persistence::DB,
-    Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint,
+    Anchor, DisplayPoint, Editor, EditorMode, Event, InlayRefreshReason, MultiBufferSnapshot,
+    ToPoint,
 };
 
 use self::{
@@ -293,8 +294,19 @@ impl Editor {
         self.scroll_manager.visible_line_count
     }
 
-    pub(crate) fn set_visible_line_count(&mut self, lines: f32) {
+    pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext<Self>) {
+        let opened_first_time = self.scroll_manager.visible_line_count.is_none();
         self.scroll_manager.visible_line_count = Some(lines);
+        if opened_first_time {
+            cx.spawn(|editor, mut cx| async move {
+                editor
+                    .update(&mut cx, |editor, cx| {
+                        editor.refresh_inlays(InlayRefreshReason::NewLinesShown, cx)
+                    })
+                    .ok()
+            })
+            .detach()
+        }
     }
 
     pub fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext<Self>) {
@@ -322,7 +334,7 @@ impl Editor {
         );
 
         if !self.is_singleton(cx) {
-            self.refresh_inlays(crate::InlayRefreshReason::NewLinesShown, cx);
+            self.refresh_inlays(InlayRefreshReason::NewLinesShown, cx);
         }
     }