diff --git a/crates/ide-lsp/src/lib.rs b/crates/ide-lsp/src/lib.rs index 6390eb679..0f7246cda 100644 --- a/crates/ide-lsp/src/lib.rs +++ b/crates/ide-lsp/src/lib.rs @@ -1364,8 +1364,14 @@ impl LspManager { let mut first = Vec::new(); if method == "textDocument/implementation" { let (locs, complete) = self.java_source_implementations(snap, t, pos, cancel).await; - if complete && !locs.is_empty() { - return Ok(locs); + // A type declared in the sources has no implementations in the dependencies: + // none found there is the whole answer, without a type hierarchy over every jar. + if complete { + if !locs.is_empty() { + return Ok(locs); + } + out.value = Some(Vec::new()); + continue; } if let Some(partial) = partial && !locs.is_empty() @@ -1423,9 +1429,9 @@ impl LspManager { self.references_streaming(doc, pos, include_declaration, project_wide, true, cancel, None).await } - /// [`Self::references`]. Java, `project_wide`: without `libraries`, only the - /// workspace's sources are searched (`libraries_skipped`); with it, those found - /// there arrive on `partial` before the dependencies and the JDK are searched. + /// [`Self::references`]. Java: without `libraries`, only the workspace's sources are + /// searched (`libraries_skipped` when `project_wide`); with it and `project_wide`, those + /// found there arrive on `partial` before the dependencies and the JDK are searched. #[allow(clippy::too_many_arguments)] pub async fn references_streaming( &self, @@ -1578,7 +1584,10 @@ impl LspManager { }; scope::Widened { first: all, second: Some(found), incomplete: sources.incomplete } } else { - let first = query(false).await?; + // Bulk probes (usage counts, Safe Delete) ask about the sources' own symbols, + // which no dependency uses: searching every jar and the JDK only adds seconds + // per symbol and delays the searches the user asks for. + let first = query(!libraries).await?; let empty = first.is_empty(); scope::Widened::only(first, empty, &t.server) }; diff --git a/crates/ide-lsp/tests/mock_server.rs b/crates/ide-lsp/tests/mock_server.rs index 2eb71d768..f6275bb32 100644 --- a/crates/ide-lsp/tests/mock_server.rs +++ b/crates/ide-lsp/tests/mock_server.rs @@ -1836,10 +1836,11 @@ async fn java_implementations_come_from_sources_first() { .collect::() }; *s.responder.lock() = Some(Arc::new(move |_, method, params| match method { - // Line 0 is declared in the sources; line 2 in a dependency, which implements it on line 3. + // Lines 0 and 1 are declared in the sources; 2 and 3 in a dependency, which implements 2 on line 3. "workspace/executeCommand" => Some(match params["arguments"][1].as_u64() { Some(0) => json!({ "locations": at(&[1]), "complete": true }), Some(2) => json!({ "locations": at(&[1]), "complete": false }), + Some(3) => json!({ "locations": [], "complete": false }), _ => json!({ "locations": [], "complete": true }), }), "textDocument/implementation" => Some(at(&[3, 1])), @@ -1870,7 +1871,14 @@ async fn java_implementations_come_from_sources_first() { assert_eq!(lines(&found), vec![3, 1]); assert_eq!(s.requested("textDocument/implementation").len(), 2); - // None in the sources: the standard request, with the dependencies. + // Declared in the sources and implemented nowhere there: no search through the dependencies. + let found = h.mgr.goto(doc, Pos::new(1, 6), GotoKind::Implementation).await.unwrap(); + assert!(found.is_empty()); + let markers = h.mgr.implementations(doc, &[Pos::new(1, 6)], Default::default()).await.unwrap(); + assert!(markers[0].is_empty()); + assert_eq!(s.requested("textDocument/implementation").len(), 2); + + // None in the sources of a dependency's type: the standard request, with the dependencies. let partial = Mutex::new(Vec::new()); let found = h .mgr @@ -3100,9 +3108,17 @@ async fn java_usages_in_the_sources_arrive_before_the_dependencies() { let scopes: Vec<_> = s.requested("workspace/executeCommand")[3..].iter().map(|p| p["arguments"].get(4).cloned()).collect(); assert_eq!(scopes, [Some(json!("sources")), Some(json!("sources"))]); - // Scoped probes (code lenses) search everything at once. + // Scoped probes with the libraries search everything at once. let scoped = h.mgr.references(doc, Pos::new(0, 5), true, false, Default::default()).await.unwrap(); assert_eq!(scoped.locations.len(), 2); + // Usage counts and Safe Delete (no libraries): the sources alone, not widened. + let scoped = + h.mgr.references_streaming(doc, Pos::new(0, 5), true, false, false, Default::default(), None).await.unwrap(); + assert_eq!(scoped.locations.len(), 1); + assert!(scoped.locations.iter().all(|l| l.uri.is_none())); + let scopes: Vec<_> = + s.requested("workspace/executeCommand")[5..].iter().map(|p| p["arguments"].get(4).cloned()).collect(); + assert_eq!(scopes, [None, Some(json!("sources"))]); } #[tokio::test(flavor = "multi_thread")]