diff --git a/crates/editor/src/inlays/inlay_hints.rs b/crates/editor/src/inlays/inlay_hints.rs index 50d4ed8df9871902033bfb4b55d85eed4ace51f1..7aacd1c86e6ec9a2034493d26df6d2271d33724e 100644 --- a/crates/editor/src/inlays/inlay_hints.rs +++ b/crates/editor/src/inlays/inlay_hints.rs @@ -1,4 +1,5 @@ use std::{ + collections::hash_map, ops::{ControlFlow, Range}, time::Duration, }; @@ -778,6 +779,7 @@ impl Editor { } let excerpts = self.buffer.read(cx).excerpt_ids(); + let mut inserted_hint_text = HashMap::default(); let hints_to_insert = new_hints .into_iter() .filter_map(|(chunk_range, hints_result)| { @@ -804,8 +806,35 @@ impl Editor { } } }) - .flat_map(|hints| hints.into_values()) - .flatten() + .flat_map(|new_hints| { + let mut hints_deduplicated = Vec::new(); + + if new_hints.len() > 1 { + for (server_id, new_hints) in new_hints { + for (new_id, new_hint) in new_hints { + let hints_text_for_position = inserted_hint_text + .entry(new_hint.position) + .or_insert_with(HashMap::default); + let insert = + match hints_text_for_position.entry(new_hint.text().to_string()) { + hash_map::Entry::Occupied(o) => o.get() == &server_id, + hash_map::Entry::Vacant(v) => { + v.insert(server_id); + true + } + }; + + if insert { + hints_deduplicated.push((new_id, new_hint)); + } + } + } + } else { + hints_deduplicated.extend(new_hints.into_values().flatten()); + } + + hints_deduplicated + }) .filter_map(|(hint_id, lsp_hint)| { if inlay_hints.allowed_hint_kinds.contains(&lsp_hint.kind) && inlay_hints @@ -3732,6 +3761,7 @@ let c = 3;"# let mut fake_servers = language_registry.register_fake_lsp( "Rust", FakeLspAdapter { + name: "rust-analyzer", capabilities: lsp::ServerCapabilities { inlay_hint_provider: Some(lsp::OneOf::Left(true)), ..lsp::ServerCapabilities::default() @@ -3804,6 +3834,78 @@ let c = 3;"# }, ); + // Add another server that does send the same, duplicate hints back + let mut fake_servers_2 = language_registry.register_fake_lsp( + "Rust", + FakeLspAdapter { + name: "CrabLang-ls", + capabilities: lsp::ServerCapabilities { + inlay_hint_provider: Some(lsp::OneOf::Left(true)), + ..lsp::ServerCapabilities::default() + }, + initializer: Some(Box::new(move |fake_server| { + fake_server.set_request_handler::( + move |params, _| async move { + if params.text_document.uri + == lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap() + { + Ok(Some(vec![ + lsp::InlayHint { + position: lsp::Position::new(1, 9), + label: lsp::InlayHintLabel::String(": i32".to_owned()), + kind: Some(lsp::InlayHintKind::TYPE), + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }, + lsp::InlayHint { + position: lsp::Position::new(19, 9), + label: lsp::InlayHintLabel::String(": i33".to_owned()), + kind: Some(lsp::InlayHintKind::TYPE), + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }, + ])) + } else if params.text_document.uri + == lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap() + { + Ok(Some(vec![ + lsp::InlayHint { + position: lsp::Position::new(1, 10), + label: lsp::InlayHintLabel::String(": i34".to_owned()), + kind: Some(lsp::InlayHintKind::TYPE), + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }, + lsp::InlayHint { + position: lsp::Position::new(29, 10), + label: lsp::InlayHintLabel::String(": i35".to_owned()), + kind: Some(lsp::InlayHintKind::TYPE), + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }, + ])) + } else { + panic!("Unexpected file path {:?}", params.text_document.uri); + } + }, + ); + })), + ..FakeLspAdapter::default() + }, + ); + let (buffer_1, _handle_1) = project .update(cx, |project, cx| { project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx) @@ -3847,6 +3949,7 @@ let c = 3;"# }); let fake_server = fake_servers.next().await.unwrap(); + let _fake_server_2 = fake_servers_2.next().await.unwrap(); cx.executor().advance_clock(Duration::from_millis(100)); cx.executor().run_until_parked(); @@ -3854,12 +3957,17 @@ let c = 3;"# .update(cx, |editor, _window, cx| { assert_eq!( vec![ + ": i32".to_string(), ": i32".to_string(), ": i33".to_string(), + ": i33".to_string(), + ": i34".to_string(), ": i34".to_string(), ": i35".to_string(), + ": i35".to_string(), ], sorted_cached_hint_labels(editor, cx), + "We receive duplicate hints from 2 servers and cache them all" ); assert_eq!( vec![ @@ -3869,7 +3977,7 @@ let c = 3;"# ": i33".to_string(), ], visible_hint_labels(editor, cx), - "lib.rs is added before main.rs , so its excerpts should be visible first" + "lib.rs is added before main.rs , so its excerpts should be visible first; hints should be deduplicated per label" ); }) .unwrap(); @@ -3918,10 +4026,14 @@ let c = 3;"# .update(cx, |editor, _window, cx| { assert_eq!( vec![ + ": i32".to_string(), ": i32".to_string(), ": i33".to_string(), + ": i33".to_string(), + ": i34".to_string(), ": i34".to_string(), ": i35".to_string(), + ": i35".to_string(), ], sorted_cached_hint_labels(editor, cx), "No hint changes/duplicates should occur in the cache",