Rework inlay hint cache tests (#23156)

Kirill Bulatov created

Closes https://github.com/zed-industries/zed/issues/7928

* uncomments and fixes all inlay hint cache tests
* fixes a bug, where invalidated range did not store the new queried
ranges in the cache: this resulted in extra requests in editor that do
not fit into the screen
* comments a peculiarity with the `RefreshInlayHints` event: all editors
react to that when a new language server is inserted, even though
certain editors are not related to the new language server
* fixes handling of inlay hints for the same position: now the same
order is kept, as in the language server's response
(https://github.com/zed-industries/zed/issues/7928)
* queries for hints when on excerpt(s) expansion

Release Notes:

- Fixed inlay hints handling for the same position

Change summary

crates/collab/src/tests/editor_tests.rs    |  58 -
crates/editor/src/display_map/inlay_map.rs |   1 
crates/editor/src/editor.rs                |   1 
crates/editor/src/inlay_hint_cache.rs      | 700 ++++++++++-------------
4 files changed, 308 insertions(+), 452 deletions(-)

Detailed changes

crates/collab/src/tests/editor_tests.rs πŸ”—

@@ -1637,12 +1637,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
             extract_hint_labels(editor),
             "Host should get its first hints when opens an editor"
         );
-        let inlay_cache = editor.inlay_hint_cache();
-        assert_eq!(
-            inlay_cache.version(),
-            1,
-            "Host editor update the cache version after every cache/view change",
-        );
     });
     let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
     let editor_b = workspace_b
@@ -1661,12 +1655,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
             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.version(),
-            1,
-            "Guest editor update the cache version after every cache/view change"
-        );
     });
 
     let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
@@ -1682,16 +1670,12 @@ async fn test_mutual_editor_inlay_hint_cache_update(
             vec![after_client_edit.to_string()],
             extract_hint_labels(editor),
         );
-        let inlay_cache = editor.inlay_hint_cache();
-        assert_eq!(inlay_cache.version(), 2);
     });
     editor_b.update(cx_b, |editor, _| {
         assert_eq!(
             vec![after_client_edit.to_string()],
             extract_hint_labels(editor),
         );
-        let inlay_cache = editor.inlay_hint_cache();
-        assert_eq!(inlay_cache.version(), 2);
     });
 
     let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
@@ -1707,16 +1691,12 @@ async fn test_mutual_editor_inlay_hint_cache_update(
             vec![after_host_edit.to_string()],
             extract_hint_labels(editor),
         );
-        let inlay_cache = editor.inlay_hint_cache();
-        assert_eq!(inlay_cache.version(), 3);
     });
     editor_b.update(cx_b, |editor, _| {
         assert_eq!(
             vec![after_host_edit.to_string()],
             extract_hint_labels(editor),
         );
-        let inlay_cache = editor.inlay_hint_cache();
-        assert_eq!(inlay_cache.version(), 3);
     });
 
     let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
@@ -1732,12 +1712,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
             extract_hint_labels(editor),
             "Host should react to /refresh LSP request"
         );
-        let inlay_cache = editor.inlay_hint_cache();
-        assert_eq!(
-            inlay_cache.version(),
-            4,
-            "Host should accepted all edits and bump its cache version every time"
-        );
     });
     editor_b.update(cx_b, |editor, _| {
         assert_eq!(
@@ -1745,12 +1719,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
             extract_hint_labels(editor),
             "Guest should get a /refresh LSP request propagated by host"
         );
-        let inlay_cache = editor.inlay_hint_cache();
-        assert_eq!(
-            inlay_cache.version(),
-            4,
-            "Guest should accepted all edits and bump its cache version every time"
-        );
     });
 }
 
@@ -1906,12 +1874,6 @@ async fn test_inlay_hint_refresh_is_forwarded(
             extract_hint_labels(editor).is_empty(),
             "Host should get no hints due to them turned off"
         );
-        let inlay_cache = editor.inlay_hint_cache();
-        assert_eq!(
-            inlay_cache.version(),
-            0,
-            "Turned off hints should not generate version updates"
-        );
     });
 
     executor.run_until_parked();
@@ -1921,12 +1883,6 @@ async fn test_inlay_hint_refresh_is_forwarded(
             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.version(),
-            1,
-            "Should update cache version after first hints"
-        );
     });
 
     other_hints.fetch_or(true, atomic::Ordering::Release);
@@ -1938,13 +1894,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
     editor_a.update(cx_a, |editor, _| {
         assert!(
             extract_hint_labels(editor).is_empty(),
-            "Host should get nop hints due to them turned off, even after the /refresh"
-        );
-        let inlay_cache = editor.inlay_hint_cache();
-        assert_eq!(
-            inlay_cache.version(),
-            0,
-            "Turned off hints should not generate version updates, again"
+            "Host should get no hints due to them turned off, even after the /refresh"
         );
     });
 
@@ -1955,12 +1905,6 @@ async fn test_inlay_hint_refresh_is_forwarded(
             extract_hint_labels(editor),
             "Guest should get a /refresh LSP request propagated by host despite host hints are off"
         );
-        let inlay_cache = editor.inlay_hint_cache();
-        assert_eq!(
-            inlay_cache.version(),
-            2,
-            "Guest should accepted all edits and bump its cache version every time"
-        );
     });
 }
 

crates/editor/src/display_map/inlay_map.rs πŸ”—

@@ -569,6 +569,7 @@ impl InlayMap {
                 probe
                     .position
                     .cmp(&inlay_to_insert.position, &snapshot.buffer)
+                    .then(std::cmp::Ordering::Less)
             }) {
                 Ok(ix) | Err(ix) => {
                     self.inlays.insert(ix, inlay_to_insert);

crates/editor/src/editor.rs πŸ”—

@@ -12441,6 +12441,7 @@ impl Editor {
                 cx.emit(EditorEvent::ExcerptsEdited { ids: ids.clone() })
             }
             multi_buffer::Event::ExcerptsExpanded { ids } => {
+                self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
                 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
             }
             multi_buffer::Event::Reparsed(buffer_id) => {

crates/editor/src/inlay_hint_cache.rs πŸ”—

@@ -1,5 +1,5 @@
 /// Stores and updates all data received from LSP <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_inlayHint">textDocument/inlayHint</a> requests.
-/// Has nothing to do with other inlays, e.g. copilot suggestions β€”Β those are stored elsewhere.
+/// Has nothing to do with other inlays, e.g. copilot suggestions β€” those are stored elsewhere.
 /// On every update, cache may query for more inlay hints and update inlays on the screen.
 ///
 /// Inlays stored on screen are in [`crate::display_map::inlay_map`] and this cache is the only way to update any inlay hint data in the visible hints in the inlay map.
@@ -38,7 +38,7 @@ pub struct InlayHintCache {
     pub(super) enabled: bool,
     enabled_in_settings: bool,
     update_tasks: HashMap<ExcerptId, TasksForRanges>,
-    refresh_task: Option<Task<()>>,
+    refresh_task: Task<()>,
     invalidate_debounce: Option<Duration>,
     append_debounce: Option<Duration>,
     lsp_request_limiter: Arc<Semaphore>,
@@ -116,13 +116,9 @@ impl InvalidationStrategy {
 
 impl TasksForRanges {
     fn new(query_ranges: QueryRanges, task: Task<()>) -> Self {
-        let mut sorted_ranges = Vec::new();
-        sorted_ranges.extend(query_ranges.before_visible);
-        sorted_ranges.extend(query_ranges.visible);
-        sorted_ranges.extend(query_ranges.after_visible);
         Self {
             tasks: vec![task],
-            sorted_ranges,
+            sorted_ranges: query_ranges.into_sorted_query_ranges(),
         }
     }
 
@@ -135,7 +131,7 @@ impl TasksForRanges {
     ) {
         let query_ranges = if invalidate.should_invalidate() {
             self.tasks.clear();
-            self.sorted_ranges.clear();
+            self.sorted_ranges = query_ranges.clone().into_sorted_query_ranges();
             query_ranges
         } else {
             let mut non_cached_query_ranges = query_ranges;
@@ -272,7 +268,7 @@ impl InlayHintCache {
             enabled_in_settings: inlay_hint_settings.enabled,
             hints: HashMap::default(),
             update_tasks: HashMap::default(),
-            refresh_task: None,
+            refresh_task: Task::ready(()),
             invalidate_debounce: debounce_value(inlay_hint_settings.edit_debounce_ms),
             append_debounce: debounce_value(inlay_hint_settings.scroll_debounce_ms),
             version: 0,
@@ -384,7 +380,7 @@ impl InlayHintCache {
         } else {
             self.append_debounce
         };
-        self.refresh_task = Some(cx.spawn(|editor, mut cx| async move {
+        self.refresh_task = cx.spawn(|editor, mut cx| async move {
             if let Some(debounce_duration) = debounce_duration {
                 cx.background_executor().timer(debounce_duration).await;
             }
@@ -401,7 +397,7 @@ impl InlayHintCache {
                     )
                 })
                 .ok();
-        }));
+        });
 
         if invalidated_hints.is_empty() {
             None
@@ -580,10 +576,6 @@ impl InlayHintCache {
         hints
     }
 
-    pub fn version(&self) -> usize {
-        self.version
-    }
-
     /// Queries a certain hint from the cache for extra data via the LSP <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#inlayHint_resolve">resolve</a> request.
     pub(super) fn spawn_hint_resolve(
         &self,
@@ -731,6 +723,16 @@ impl QueryRanges {
     fn is_empty(&self) -> bool {
         self.before_visible.is_empty() && self.visible.is_empty() && self.after_visible.is_empty()
     }
+
+    fn into_sorted_query_ranges(self) -> Vec<Range<text::Anchor>> {
+        let mut sorted_ranges = Vec::with_capacity(
+            self.before_visible.len() + self.visible.len() + self.after_visible.len(),
+        );
+        sorted_ranges.extend(self.before_visible);
+        sorted_ranges.extend(self.visible);
+        sorted_ranges.extend(self.after_visible);
+        sorted_ranges
+    }
 }
 
 fn determine_query_ranges(
@@ -940,7 +942,7 @@ fn fetch_and_update_hints(
                         None => true,
                     };
                     if query_not_around_visible_range {
-                        // log::trace!("Fetching inlay hints for range {fetch_range_to_log:?} got throttled and fell off the current visible range, skipping.");
+                        log::trace!("Fetching inlay hints for range {fetch_range_to_log:?} got throttled and fell off the current visible range, skipping.");
                         if let Some(task_ranges) = editor
                             .inlay_hint_cache
                             .update_tasks
@@ -1008,12 +1010,12 @@ fn fetch_and_update_hints(
             })
             .await;
         if let Some(new_update) = new_update {
-            // log::debug!(
-            //     "Applying update for range {fetch_range_to_log:?}: remove from editor: {}, remove from cache: {}, add to cache: {}",
-            //     new_update.remove_from_visible.len(),
-            //     new_update.remove_from_cache.len(),
-            //     new_update.add_to_cache.len()
-            // );
+            log::debug!(
+                "Applying update for range {fetch_range_to_log:?}: remove from editor: {}, remove from cache: {}, add to cache: {}",
+                new_update.remove_from_visible.len(),
+                new_update.remove_from_cache.len(),
+                new_update.add_to_cache.len()
+            );
             log::trace!("New update: {new_update:?}");
             editor
                 .update(&mut cx, |editor, cx| {
@@ -1181,48 +1183,39 @@ fn apply_hint_update(
                     .cmp(&new_hint.position, &buffer_snapshot)
             }) {
             Ok(i) => {
-                let mut insert_position = Some(i);
-                for id in &cached_excerpt_hints.ordered_hints[i..] {
-                    let cached_hint = &cached_excerpt_hints.hints_by_id[id];
-                    if new_hint
-                        .position
-                        .cmp(&cached_hint.position, &buffer_snapshot)
-                        .is_gt()
-                    {
-                        break;
-                    }
-                    if cached_hint.text() == new_hint.text() {
-                        insert_position = None;
-                        break;
-                    }
-                }
-                insert_position
+                // When a hint is added to the same position where existing ones are present,
+                // do not deduplicate it: we split hint queries into non-overlapping ranges
+                // and each hint batch returned by the server should already contain unique hints.
+                i + cached_excerpt_hints.ordered_hints[i..].len() + 1
             }
-            Err(i) => Some(i),
+            Err(i) => i,
         };
 
-        if let Some(insert_position) = insert_position {
-            let new_inlay_id = post_inc(&mut editor.next_inlay_id);
-            if editor
-                .inlay_hint_cache
-                .allowed_hint_kinds
-                .contains(&new_hint.kind)
+        let new_inlay_id = post_inc(&mut editor.next_inlay_id);
+        if editor
+            .inlay_hint_cache
+            .allowed_hint_kinds
+            .contains(&new_hint.kind)
+        {
+            if let Some(new_hint_position) =
+                multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position)
             {
-                if let Some(new_hint_position) =
-                    multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position)
-                {
-                    splice
-                        .to_insert
-                        .push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
-                }
+                splice
+                    .to_insert
+                    .push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
             }
-            let new_id = InlayId::Hint(new_inlay_id);
-            cached_excerpt_hints.hints_by_id.insert(new_id, new_hint);
+        }
+        let new_id = InlayId::Hint(new_inlay_id);
+        cached_excerpt_hints.hints_by_id.insert(new_id, new_hint);
+        if cached_excerpt_hints.ordered_hints.len() <= insert_position {
+            cached_excerpt_hints.ordered_hints.push(new_id);
+        } else {
             cached_excerpt_hints
                 .ordered_hints
                 .insert(insert_position, new_id);
-            cached_inlays_changed = true;
         }
+
+        cached_inlays_changed = true;
     }
     cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
     drop(cached_excerpt_hints);
@@ -1266,15 +1259,19 @@ fn apply_hint_update(
 #[cfg(test)]
 pub mod tests {
     use crate::editor_tests::update_test_language_settings;
+    use crate::scroll::ScrollAmount;
     use crate::{scroll::Autoscroll, test::editor_lsp_test_context::rust_lang, ExcerptRange};
     use futures::StreamExt;
     use gpui::{Context, SemanticVersion, TestAppContext, WindowHandle};
+    use itertools::Itertools as _;
     use language::{language_settings::AllLanguageSettingsContent, Capability, FakeLspAdapter};
+    use language::{Language, LanguageConfig, LanguageMatcher};
     use lsp::FakeLanguageServer;
+    use parking_lot::Mutex;
     use project::{FakeFs, Project};
     use serde_json::json;
     use settings::SettingsStore;
-    use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
+    use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
     use text::Point;
 
     use super::*;
@@ -1293,50 +1290,35 @@ pub mod tests {
                 show_background: false,
             })
         });
-
         let (_, editor, fake_server) = prepare_test_objects(cx, |fake_server, file_with_hints| {
             let lsp_request_count = Arc::new(AtomicU32::new(0));
             fake_server.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
                 let task_lsp_request_count = Arc::clone(&lsp_request_count);
                 async move {
+                    let i = task_lsp_request_count.fetch_add(1, Ordering::Release) + 1;
                     assert_eq!(
                         params.text_document.uri,
                         lsp::Url::from_file_path(file_with_hints).unwrap(),
                     );
-                    let current_call_id =
-                        Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
-                    let mut new_hints = Vec::with_capacity(2 * current_call_id as usize);
-                    for _ in 0..2 {
-                        let mut i = current_call_id;
-                        loop {
-                            new_hints.push(lsp::InlayHint {
-                                position: lsp::Position::new(0, i),
-                                label: lsp::InlayHintLabel::String(i.to_string()),
-                                kind: None,
-                                text_edits: None,
-                                tooltip: None,
-                                padding_left: None,
-                                padding_right: None,
-                                data: None,
-                            });
-                            if i == 0 {
-                                break;
-                            }
-                            i -= 1;
-                        }
-                    }
-
-                    Ok(Some(new_hints))
+                    Ok(Some(vec![lsp::InlayHint {
+                        position: lsp::Position::new(0, i),
+                        label: lsp::InlayHintLabel::String(i.to_string()),
+                        kind: None,
+                        text_edits: None,
+                        tooltip: None,
+                        padding_left: None,
+                        padding_right: None,
+                        data: None,
+                    }]))
                 }
             });
         })
         .await;
         cx.executor().run_until_parked();
 
-        let mut edits_made = 1;
         editor
             .update(cx, |editor, cx| {
-                let expected_hints = vec!["0".to_string()];
+                let expected_hints = vec!["1".to_string()];
                 assert_eq!(
                     expected_hints,
                     cached_hint_labels(editor),
@@ -1348,10 +1330,6 @@ pub mod tests {
                     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,
-                    "The editor update the cache version after every cache/view change"
-                );
             })
             .unwrap();
 
@@ -1359,13 +1337,12 @@ pub mod tests {
             .update(cx, |editor, cx| {
                 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
                 editor.handle_input("some change", cx);
-                edits_made += 1;
             })
             .unwrap();
         cx.executor().run_until_parked();
         editor
             .update(cx, |editor, cx| {
-                let expected_hints = vec!["0".to_string(), "1".to_string()];
+                let expected_hints = vec!["2".to_string()];
                 assert_eq!(
                     expected_hints,
                     cached_hint_labels(editor),
@@ -1377,10 +1354,6 @@ pub mod tests {
                     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,
-                    "The editor update the cache version after every cache/view change"
-                );
             })
             .unwrap();
 
@@ -1388,11 +1361,10 @@ pub mod tests {
             .request::<lsp::request::InlayHintRefreshRequest>(())
             .await
             .expect("inlay refresh request failed");
-        edits_made += 1;
         cx.executor().run_until_parked();
         editor
             .update(cx, |editor, cx| {
-                let expected_hints = vec!["0".to_string(), "1".to_string(), "2".to_string()];
+                let expected_hints = vec!["3".to_string()];
                 assert_eq!(
                     expected_hints,
                     cached_hint_labels(editor),
@@ -1404,10 +1376,6 @@ pub mod tests {
                     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,
-                    "The editor update the cache version after every cache/view change"
-                );
             })
             .unwrap();
     }
@@ -1453,7 +1421,6 @@ pub mod tests {
         .await;
         cx.executor().run_until_parked();
 
-        let mut edits_made = 1;
         editor
             .update(cx, |editor, cx| {
                 let expected_hints = vec!["0".to_string()];
@@ -1463,11 +1430,6 @@ pub mod tests {
                     "Should get its first hints when opening the editor"
                 );
                 assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-                assert_eq!(
-                    editor.inlay_hint_cache().version,
-                    edits_made,
-                    "The editor update the cache version after every cache/view change"
-                );
             })
             .unwrap();
 
@@ -1496,11 +1458,6 @@ pub mod tests {
                     "Should not update hints while the work task is running"
                 );
                 assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-                assert_eq!(
-                    editor.inlay_hint_cache().version,
-                    edits_made,
-                    "Should not update the cache while the work task is running"
-                );
             })
             .unwrap();
 
@@ -1512,7 +1469,6 @@ pub mod tests {
         });
         cx.executor().run_until_parked();
 
-        edits_made += 1;
         editor
             .update(cx, |editor, cx| {
                 let expected_hints = vec!["1".to_string()];
@@ -1522,246 +1478,223 @@ pub mod tests {
                     "New hints should be queried after the work task is done"
                 );
                 assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+            })
+            .unwrap();
+    }
+
+    #[gpui::test]
+    async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) {
+        init_test(cx, |settings| {
+            settings.defaults.inlay_hints = Some(InlayHintSettings {
+                enabled: true,
+                edit_debounce_ms: 0,
+                scroll_debounce_ms: 0,
+                show_type_hints: true,
+                show_parameter_hints: true,
+                show_other_hints: true,
+                show_background: false,
+            })
+        });
+
+        let fs = FakeFs::new(cx.background_executor.clone());
+        fs.insert_tree(
+            "/a",
+            json!({
+                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
+                "other.md": "Test md file with some text",
+            }),
+        )
+        .await;
+
+        let project = Project::test(fs, ["/a".as_ref()], cx).await;
+
+        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
+        let mut rs_fake_servers = None;
+        let mut md_fake_servers = None;
+        for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
+            language_registry.add(Arc::new(Language::new(
+                LanguageConfig {
+                    name: name.into(),
+                    matcher: LanguageMatcher {
+                        path_suffixes: vec![path_suffix.to_string()],
+                        ..Default::default()
+                    },
+                    ..Default::default()
+                },
+                Some(tree_sitter_rust::LANGUAGE.into()),
+            )));
+            let fake_servers = language_registry.register_fake_lsp(
+                name,
+                FakeLspAdapter {
+                    name,
+                    capabilities: lsp::ServerCapabilities {
+                        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+                        ..Default::default()
+                    },
+                    initializer: Some(Box::new({
+                        move |fake_server| {
+                            let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
+                            let md_lsp_request_count = Arc::new(AtomicU32::new(0));
+                            fake_server.handle_request::<lsp::request::InlayHintRequest, _, _>(
+                                move |params, _| {
+                                    let i = match name {
+                                        "Rust" => {
+                                            assert_eq!(
+                                                params.text_document.uri,
+                                                lsp::Url::from_file_path("/a/main.rs").unwrap(),
+                                            );
+                                            rs_lsp_request_count.fetch_add(1, Ordering::Release) + 1
+                                        }
+                                        "Markdown" => {
+                                            assert_eq!(
+                                                params.text_document.uri,
+                                                lsp::Url::from_file_path("/a/other.md").unwrap(),
+                                            );
+                                            md_lsp_request_count.fetch_add(1, Ordering::Release) + 1
+                                        }
+                                        unexpected => panic!("Unexpected language: {unexpected}"),
+                                    };
+
+                                    async move {
+                                        let query_start = params.range.start;
+                                        Ok(Some(vec![lsp::InlayHint {
+                                            position: query_start,
+                                            label: lsp::InlayHintLabel::String(i.to_string()),
+                                            kind: None,
+                                            text_edits: None,
+                                            tooltip: None,
+                                            padding_left: None,
+                                            padding_right: None,
+                                            data: None,
+                                        }]))
+                                    }
+                                },
+                            );
+                        }
+                    })),
+                    ..Default::default()
+                },
+            );
+            match name {
+                "Rust" => rs_fake_servers = Some(fake_servers),
+                "Markdown" => md_fake_servers = Some(fake_servers),
+                _ => unreachable!(),
+            }
+        }
+
+        let rs_buffer = project
+            .update(cx, |project, cx| {
+                project.open_local_buffer("/a/main.rs", cx)
+            })
+            .await
+            .unwrap();
+        let rs_editor =
+            cx.add_window(|cx| Editor::for_buffer(rs_buffer, Some(project.clone()), cx));
+        cx.executor().run_until_parked();
+
+        let _rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
+        cx.executor().run_until_parked();
+        rs_editor
+            .update(cx, |editor, cx| {
+                let expected_hints = vec!["1".to_string()];
                 assert_eq!(
-                    editor.inlay_hint_cache().version,
-                    edits_made,
-                    "Cache version should update once after the work task is done"
+                    expected_hints,
+                    cached_hint_labels(editor),
+                    "Should get its first hints when opening the editor"
                 );
+                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+            })
+            .unwrap();
+
+        cx.executor().run_until_parked();
+        let md_buffer = project
+            .update(cx, |project, cx| {
+                project.open_local_buffer("/a/other.md", cx)
+            })
+            .await
+            .unwrap();
+        let md_editor = cx.add_window(|cx| Editor::for_buffer(md_buffer, Some(project), cx));
+        cx.executor().run_until_parked();
+
+        let _md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
+        cx.executor().run_until_parked();
+        md_editor
+            .update(cx, |editor, cx| {
+                let expected_hints = vec!["1".to_string()];
+                assert_eq!(
+                    expected_hints,
+                    cached_hint_labels(editor),
+                    "Markdown editor should have a separate version, repeating Rust editor rules"
+                );
+                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+            })
+            .unwrap();
+
+        rs_editor
+            .update(cx, |editor, cx| {
+                editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+                editor.handle_input("some rs change", cx);
+            })
+            .unwrap();
+        cx.executor().run_until_parked();
+        rs_editor
+            .update(cx, |editor, cx| {
+                // TODO: Here, we do not get "2", because inserting another language server will trigger `RefreshInlayHints` event from the `LspStore`
+                // A project is listened in every editor, so each of them will react to this event.
+                //
+                // We do not have language server IDs for remote projects, so cannot easily say on the editor level,
+                // whether we should ignore a particular `RefreshInlayHints` event.
+                let expected_hints = vec!["3".to_string()];
+                assert_eq!(
+                    expected_hints,
+                    cached_hint_labels(editor),
+                    "Rust inlay cache should change after the edit"
+                );
+                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+            })
+            .unwrap();
+        md_editor
+            .update(cx, |editor, cx| {
+                let expected_hints = vec!["1".to_string()];
+                assert_eq!(
+                    expected_hints,
+                    cached_hint_labels(editor),
+                    "Markdown editor should not be affected by Rust editor changes"
+                );
+                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
             })
             .unwrap();
-    }
 
-    // #[gpui::test]
-    // async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) {
-    //     init_test(cx, |settings| {
-    //         settings.defaults.inlay_hints = Some(InlayHintSettings {
-    //             enabled: true,
-    //             edit_debounce_ms: 0,
-    //             scroll_debounce_ms: 0,
-    //             show_type_hints: true,
-    //             show_parameter_hints: true,
-    //             show_other_hints: true,
-    //             show_background: false,
-    //         })
-    //     });
-
-    //     let fs = FakeFs::new(cx.background_executor.clone());
-    //     fs.insert_tree(
-    //         "/a",
-    //         json!({
-    //             "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
-    //             "other.md": "Test md file with some text",
-    //         }),
-    //     )
-    //     .await;
-
-    //     let project = Project::test(fs, ["/a".as_ref()], cx).await;
-
-    //     let language_registry = project.read_with(cx, |project, _| project.languages().clone());
-    //     let mut rs_fake_servers = None;
-    //     let mut md_fake_servers = None;
-    //     for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
-    //         language_registry.add(Arc::new(Language::new(
-    //             LanguageConfig {
-    //                 name: name.into(),
-    //                 matcher: LanguageMatcher {
-    //                     path_suffixes: vec![path_suffix.to_string()],
-    //                     ..Default::default()
-    //                 },
-    //                 ..Default::default()
-    //             },
-    //             Some(tree_sitter_rust::LANGUAGE.into()),
-    //         )));
-    //         let fake_servers = language_registry.register_fake_lsp(
-    //             name,
-    //             FakeLspAdapter {
-    //                 name,
-    //                 capabilities: lsp::ServerCapabilities {
-    //                     inlay_hint_provider: Some(lsp::OneOf::Left(true)),
-    //                     ..Default::default()
-    //                 },
-    //                 ..Default::default()
-    //             },
-    //         );
-    //         match name {
-    //             "Rust" => rs_fake_servers = Some(fake_servers),
-    //             "Markdown" => md_fake_servers = Some(fake_servers),
-    //             _ => unreachable!(),
-    //         }
-    //     }
-
-    //     let rs_buffer = project
-    //         .update(cx, |project, cx| {
-    //             project.open_local_buffer("/a/main.rs", cx)
-    //         })
-    //         .await
-    //         .unwrap();
-    //     let rs_editor =
-    //         cx.add_window(|cx| Editor::for_buffer(rs_buffer, Some(project.clone()), cx));
-
-    //     cx.executor().run_until_parked();
-    //     cx.executor().start_waiting();
-    //     let rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
-    //     let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
-    //     rs_fake_server
-    //         .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-    //             let task_lsp_request_count = Arc::clone(&rs_lsp_request_count);
-    //             async move {
-    //                 assert_eq!(
-    //                     params.text_document.uri,
-    //                     lsp::Url::from_file_path("/a/main.rs").unwrap(),
-    //                 );
-    //                 let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
-    //                 Ok(Some(vec![lsp::InlayHint {
-    //                     position: lsp::Position::new(0, i),
-    //                     label: lsp::InlayHintLabel::String(i.to_string()),
-    //                     kind: None,
-    //                     text_edits: None,
-    //                     tooltip: None,
-    //                     padding_left: None,
-    //                     padding_right: None,
-    //                     data: None,
-    //                 }]))
-    //             }
-    //         })
-    //         .next()
-    //         .await;
-    //     cx.executor().run_until_parked();
-    //     rs_editor
-    //         .update(cx, |editor, cx| {
-    //             let expected_hints = vec!["0".to_string()];
-    //             assert_eq!(
-    //                 expected_hints,
-    //                 cached_hint_labels(editor),
-    //                 "Should get its first hints when opening the editor"
-    //             );
-    //             assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-    //             assert_eq!(
-    //                 editor.inlay_hint_cache().version,
-    //                 1,
-    //                 "Rust editor update the cache version after every cache/view change"
-    //             );
-    //         })
-    //         .unwrap();
-
-    //     cx.executor().run_until_parked();
-    //     let md_buffer = project
-    //         .update(cx, |project, cx| {
-    //             project.open_local_buffer("/a/other.md", cx)
-    //         })
-    //         .await
-    //         .unwrap();
-    //     let md_editor = cx.add_window(|cx| Editor::for_buffer(md_buffer, Some(project), cx));
-
-    //     cx.executor().run_until_parked();
-    //     cx.executor().start_waiting();
-    //     let md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
-    //     let md_lsp_request_count = Arc::new(AtomicU32::new(0));
-    //     md_fake_server
-    //         .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-    //             let task_lsp_request_count = Arc::clone(&md_lsp_request_count);
-    //             async move {
-    //                 assert_eq!(
-    //                     params.text_document.uri,
-    //                     lsp::Url::from_file_path("/a/other.md").unwrap(),
-    //                 );
-    //                 let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
-    //                 Ok(Some(vec![lsp::InlayHint {
-    //                     position: lsp::Position::new(0, i),
-    //                     label: lsp::InlayHintLabel::String(i.to_string()),
-    //                     kind: None,
-    //                     text_edits: None,
-    //                     tooltip: None,
-    //                     padding_left: None,
-    //                     padding_right: None,
-    //                     data: None,
-    //                 }]))
-    //             }
-    //         })
-    //         .next()
-    //         .await;
-    //     cx.executor().run_until_parked();
-    //     md_editor
-    //         .update(cx, |editor, cx| {
-    //             let expected_hints = vec!["0".to_string()];
-    //             assert_eq!(
-    //                 expected_hints,
-    //                 cached_hint_labels(editor),
-    //                 "Markdown editor should have a separate version, repeating Rust editor rules"
-    //             );
-    //             assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-    //             assert_eq!(editor.inlay_hint_cache().version, 1);
-    //         })
-    //         .unwrap();
-
-    //     rs_editor
-    //         .update(cx, |editor, cx| {
-    //             editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
-    //             editor.handle_input("some rs change", cx);
-    //         })
-    //         .unwrap();
-    //     cx.executor().run_until_parked();
-    //     rs_editor
-    //         .update(cx, |editor, cx| {
-    //             let expected_hints = vec!["1".to_string()];
-    //             assert_eq!(
-    //                 expected_hints,
-    //                 cached_hint_labels(editor),
-    //                 "Rust inlay cache should change after the edit"
-    //             );
-    //             assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-    //             assert_eq!(
-    //                 editor.inlay_hint_cache().version,
-    //                 2,
-    //                 "Every time hint cache changes, cache version should be incremented"
-    //             );
-    //         })
-    //         .unwrap();
-    //     md_editor
-    //         .update(cx, |editor, cx| {
-    //             let expected_hints = vec!["0".to_string()];
-    //             assert_eq!(
-    //                 expected_hints,
-    //                 cached_hint_labels(editor),
-    //                 "Markdown editor should not be affected by Rust editor changes"
-    //             );
-    //             assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-    //             assert_eq!(editor.inlay_hint_cache().version, 1);
-    //         })
-    //         .unwrap();
-
-    //     md_editor
-    //         .update(cx, |editor, cx| {
-    //             editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
-    //             editor.handle_input("some md change", cx);
-    //         })
-    //         .unwrap();
-    //     cx.executor().run_until_parked();
-    //     md_editor
-    //         .update(cx, |editor, cx| {
-    //             let expected_hints = vec!["1".to_string()];
-    //             assert_eq!(
-    //                 expected_hints,
-    //                 cached_hint_labels(editor),
-    //                 "Rust editor should not be affected by Markdown editor changes"
-    //             );
-    //             assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-    //             assert_eq!(editor.inlay_hint_cache().version, 2);
-    //         })
-    //         .unwrap();
-    //     rs_editor
-    //         .update(cx, |editor, cx| {
-    //             let expected_hints = vec!["1".to_string()];
-    //             assert_eq!(
-    //                 expected_hints,
-    //                 cached_hint_labels(editor),
-    //                 "Markdown editor should also change independently"
-    //             );
-    //             assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-    //             assert_eq!(editor.inlay_hint_cache().version, 2);
-    //         })
-    //         .unwrap();
-    // }
+        md_editor
+            .update(cx, |editor, cx| {
+                editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+                editor.handle_input("some md change", cx);
+            })
+            .unwrap();
+        cx.executor().run_until_parked();
+        md_editor
+            .update(cx, |editor, cx| {
+                let expected_hints = vec!["2".to_string()];
+                assert_eq!(
+                    expected_hints,
+                    cached_hint_labels(editor),
+                    "Rust editor should not be affected by Markdown editor changes"
+                );
+                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+            })
+            .unwrap();
+        rs_editor
+            .update(cx, |editor, cx| {
+                let expected_hints = vec!["3".to_string()];
+                assert_eq!(
+                    expected_hints,
+                    cached_hint_labels(editor),
+                    "Markdown editor should also change independently"
+                );
+                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+            })
+            .unwrap();
+    }
 
     #[gpui::test]
     async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
@@ -1778,14 +1711,14 @@ pub mod tests {
             })
         });
 
-        let lsp_request_count = Arc::new(AtomicU32::new(0));
+        let lsp_request_count = Arc::new(AtomicUsize::new(0));
         let (_, editor, fake_server) = prepare_test_objects(cx, {
             let lsp_request_count = lsp_request_count.clone();
             move |fake_server, file_with_hints| {
                 let lsp_request_count = lsp_request_count.clone();
                 fake_server.handle_request::<lsp::request::InlayHintRequest, _, _>(
                     move |params, _| {
-                        lsp_request_count.fetch_add(1, Ordering::SeqCst);
+                        lsp_request_count.fetch_add(1, Ordering::Release);
                         async move {
                             assert_eq!(
                                 params.text_document.uri,
@@ -1833,7 +1766,6 @@ pub mod tests {
         .await;
         cx.executor().run_until_parked();
 
-        let mut edits_made = 1;
         editor
             .update(cx, |editor, cx| {
                 assert_eq!(
@@ -1843,15 +1775,15 @@ pub mod tests {
                 );
                 assert_eq!(
                     vec![
-                        "other hint".to_string(),
-                        "parameter hint".to_string(),
                         "type hint".to_string(),
+                        "parameter hint".to_string(),
+                        "other hint".to_string(),
                     ],
                     cached_hint_labels(editor),
                     "Should get its first hints when opening the editor"
                 );
                 assert_eq!(
-                    vec!["other hint".to_string(), "type hint".to_string()],
+                    vec!["type hint".to_string(), "other hint".to_string()],
                     visible_hint_labels(editor, cx)
                 );
                 let inlay_cache = editor.inlay_hint_cache();
@@ -1859,10 +1791,6 @@ pub mod tests {
                     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,
-                    "The editor update the cache version after every cache/view change"
-                );
             })
             .unwrap();
 
@@ -1880,22 +1808,17 @@ pub mod tests {
                 );
                 assert_eq!(
                     vec![
-                        "other hint".to_string(),
-                        "parameter hint".to_string(),
                         "type hint".to_string(),
+                        "parameter hint".to_string(),
+                        "other hint".to_string(),
                     ],
                     cached_hint_labels(editor),
                     "Cached hints should not change due to allowed hint kinds settings update"
                 );
                 assert_eq!(
-                    vec!["other hint".to_string(), "type hint".to_string()],
+                    vec!["type hint".to_string(), "other hint".to_string()],
                     visible_hint_labels(editor, cx)
                 );
-                assert_eq!(
-                    editor.inlay_hint_cache().version,
-                    edits_made,
-                    "Should not update cache version due to new loaded hints being the same"
-                );
             })
             .unwrap();
 
@@ -1911,15 +1834,15 @@ pub mod tests {
             ),
             (
                 HashSet::from_iter([None, Some(InlayHintKind::Type)]),
-                vec!["other hint".to_string(), "type hint".to_string()],
+                vec!["type hint".to_string(), "other hint".to_string()],
             ),
             (
                 HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
-                vec!["other hint".to_string(), "parameter hint".to_string()],
+                vec!["parameter hint".to_string(), "other hint".to_string()],
             ),
             (
                 HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
-                vec!["parameter hint".to_string(), "type hint".to_string()],
+                vec!["type hint".to_string(), "parameter hint".to_string()],
             ),
             (
                 HashSet::from_iter([
@@ -1928,13 +1851,12 @@ pub mod tests {
                     Some(InlayHintKind::Parameter),
                 ]),
                 vec![
-                    "other hint".to_string(),
-                    "parameter hint".to_string(),
                     "type hint".to_string(),
+                    "parameter hint".to_string(),
+                    "other hint".to_string(),
                 ],
             ),
         ] {
-            edits_made += 1;
             update_test_language_settings(cx, |settings| {
                 settings.defaults.inlay_hints = Some(InlayHintSettings {
                     enabled: true,
@@ -1956,9 +1878,9 @@ pub mod tests {
                 );
                 assert_eq!(
                     vec![
-                        "other hint".to_string(),
-                        "parameter hint".to_string(),
                         "type hint".to_string(),
+                        "parameter hint".to_string(),
+                        "other hint".to_string(),
                     ],
                     cached_hint_labels(editor),
                     "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
@@ -1973,14 +1895,9 @@ pub mod tests {
                     inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
                     "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
                 );
-                assert_eq!(
-                    inlay_cache.version, edits_made,
-                    "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change"
-                );
             }).unwrap();
         }
 
-        edits_made += 1;
         let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
         update_test_language_settings(cx, |settings| {
             settings.defaults.inlay_hints = Some(InlayHintSettings {
@@ -2015,10 +1932,6 @@ pub mod tests {
                     inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
                     "Should update its allowed hint kinds even when hints got disabled"
                 );
-                assert_eq!(
-                    inlay_cache.version, edits_made,
-                    "The editor should update the cache version after hints got disabled"
-                );
             })
             .unwrap();
 
@@ -2027,22 +1940,19 @@ pub mod tests {
             .await
             .expect("inlay refresh request failed");
         cx.executor().run_until_parked();
-        editor.update(cx, |editor, cx| {
-            assert_eq!(
-                lsp_request_count.load(Ordering::Relaxed),
-                2,
-                "Should not load new hints when they got disabled"
-            );
-            assert!(cached_hint_labels(editor).is_empty());
-            assert!(visible_hint_labels(editor, cx).is_empty());
-            assert_eq!(
-                editor.inlay_hint_cache().version, edits_made,
-                "The editor should not update the cache version after /refresh query without updates"
-            );
-        }).unwrap();
+        editor
+            .update(cx, |editor, cx| {
+                assert_eq!(
+                    lsp_request_count.load(Ordering::Relaxed),
+                    2,
+                    "Should not load new hints when they got disabled"
+                );
+                assert!(cached_hint_labels(editor).is_empty());
+                assert!(visible_hint_labels(editor, cx).is_empty());
+            })
+            .unwrap();
 
         let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
-        edits_made += 1;
         update_test_language_settings(cx, |settings| {
             settings.defaults.inlay_hints = Some(InlayHintSettings {
                 enabled: true,