дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 11:15:40 +02:00
perf(java): count usage lenses in the project sources only (#2244)
* perf(java): count usage lenses in the project sources only Usage lenses asked JDT LS for a full references search per declaration, which also scans every dependency jar and the JDK. In a library class such as java.lang.String that kept JDT LS busy for tens of seconds per pass, and failed counts were asked again on every scroll. - Scoped Java searches without libraries pass the "sources" scope. - Lenses count declarations near the viewport (10 lines instead of 60). - Incomplete counts are remembered per document version and asked again once the server settles, instead of on every pass. * perf(java): turn off the language server's own reference lenses JDT LS shows a references lens above each declaration by default and the editor resolved them all at once, a references search per declaration on every open and edit. The editor's usage lenses already count usages. * style(editor): keep the usage lens's unrelated lines as they were
Цей коміт міститься в:
1 джерело
89b178f9aa
коміт
89c7ae7d4c
5 files changed
+47
-11
No files matched your search
@@ -592,8 +592,8 @@ pub async fn lsp_resolve_class_file(host: &Host, window: &str, path: std::path::
|
||||
|
||||
/// References of the symbol at `pos`. `project_wide` also searches project files
|
||||
/// outside the language server's analysis scope; bulk scans over many symbols
|
||||
/// leave it off. Java, `project_wide`: without `libraries`, the dependencies and the JDK
|
||||
/// are not searched; with it, those found in the sources arrive on `on_partial` before
|
||||
/// leave it off. Java: without `libraries`, the dependencies and the JDK are not
|
||||
/// searched; with it and `project_wide`, those found in the sources arrive on `on_partial` before
|
||||
/// the slower search for the rest, and the result has them all. Cancel with
|
||||
/// `cancel_request(request)`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
||||
@@ -1462,8 +1462,8 @@ 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
|
||||
/// [`Self::references`]. Java: without `libraries`, only the workspace's sources
|
||||
/// are searched (`libraries_skipped`); 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(
|
||||
@@ -1630,7 +1630,10 @@ impl LspManager {
|
||||
};
|
||||
scope::Widened { first: all, second: Some(found), incomplete: sources.incomplete }
|
||||
} else {
|
||||
let first = query(false).await?;
|
||||
// Scoped probes (usage counts, bulk scans) run for many symbols at once:
|
||||
// without `libraries`, the dependencies and the JDK are not searched.
|
||||
libraries_skipped = !libraries;
|
||||
let first = query(!libraries).await?;
|
||||
let empty = first.is_empty();
|
||||
scope::Widened::only(first, empty, &t.server)
|
||||
};
|
||||
|
||||
@@ -3752,6 +3752,32 @@ async fn classified_java_references_widen_without_losing_metadata() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Usage counts probe many symbols at once: without `libraries` they search the sources alone.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn scoped_java_usages_search_the_sources_unless_libraries_are_asked_for() {
|
||||
let mut caps = incremental_caps();
|
||||
caps["executeCommandProvider"] = json!({"commands": ["openidea.java.usages"]});
|
||||
let h = harness_for("java", "java", &[("jdtls", caps)], &[]);
|
||||
let s = h.launcher.state("jdtls");
|
||||
s.respond("workspace/executeCommand", json!([]));
|
||||
let (doc, _) = h.open("a.java", "int target;");
|
||||
h.wait_status("jdtls", ServerStatus::Ready).await;
|
||||
let seen = grep_candidates(&h);
|
||||
for libraries in [false, true] {
|
||||
let refs = h
|
||||
.mgr
|
||||
.references_streaming(doc, Pos::new(0, 5), false, false, libraries, Default::default(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(refs.libraries_skipped, !libraries);
|
||||
let requested = s.requested("workspace/executeCommand");
|
||||
let arguments = requested.last().unwrap()["arguments"].as_array().unwrap().clone();
|
||||
assert_eq!(arguments.get(4) == Some(&json!("sources")), !libraries);
|
||||
assert_eq!(arguments.len(), if libraries { 4 } else { 5 });
|
||||
}
|
||||
assert!(seen.lock().is_empty());
|
||||
}
|
||||
|
||||
/// A library method's usages in the sources are shown before the dependencies and the JDK
|
||||
/// are searched, which takes seconds; scope widening searches the sources alone again.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
|
||||
@@ -43,6 +43,9 @@ contentProvider = { preferred = "fernflowerContentProvider" }
|
||||
maven = { downloadSources = true, updateSnapshots = false }
|
||||
eclipse = { downloadSources = true }
|
||||
references = { includeDecompiledSources = true }
|
||||
# The editor's usage lenses count usages; the server's own lenses would search every
|
||||
# declaration of the file again, all at once (on by default in JDT LS).
|
||||
referencesCodeLens = { enabled = false }
|
||||
signatureHelp = { enabled = true }
|
||||
symbols = { includeGeneratedCode = true }
|
||||
configuration = { updateBuildConfiguration = "automatic" }
|
||||
|
||||
@@ -35,7 +35,7 @@ const LENS_KINDS = new Set<SymbolKind>([
|
||||
const MAX_PER_PASS = 40;
|
||||
const CONCURRENCY = 4;
|
||||
/** Lines above/below the viewport that are counted too, so scrolling feels instant. */
|
||||
const MARGIN_LINES = 60;
|
||||
const MARGIN_LINES = 10;
|
||||
const usagesBackoff = featureBackoff();
|
||||
|
||||
export interface LensTarget {
|
||||
@@ -114,7 +114,8 @@ class UsagesLensPlugin {
|
||||
private outline: { doc: Text; targets: LensTarget[] } | null = null;
|
||||
private generation = 0;
|
||||
private pending = new Set<RequestId>();
|
||||
private incomplete = false;
|
||||
/** Keys whose count came back incomplete (the server was indexing): not shown, asked again once it settles. */
|
||||
private incomplete = new Set<string>();
|
||||
private unsubscribe: () => void;
|
||||
private offReady: () => void;
|
||||
private offSettled: () => void;
|
||||
@@ -129,10 +130,13 @@ class UsagesLensPlugin {
|
||||
// Counts computed while the server was indexing may be incomplete.
|
||||
this.offReady = onLspRefresh("codeLens", () => {
|
||||
this.cache.clear();
|
||||
this.incomplete.clear();
|
||||
this.refresh();
|
||||
});
|
||||
this.offSettled = onServerSettled(() => {
|
||||
if (this.incomplete) this.refresh();
|
||||
if (!this.incomplete.size) return;
|
||||
this.incomplete.clear();
|
||||
this.refresh();
|
||||
});
|
||||
this.offBlame = onBlameInvalidated(() => this.refresh());
|
||||
}
|
||||
@@ -167,11 +171,11 @@ class UsagesLensPlugin {
|
||||
}
|
||||
const text = view.state.doc;
|
||||
const wanted = () => gen === this.generation && view.state.doc === text;
|
||||
this.incomplete = false;
|
||||
// Blame runs beside the reference requests; it never holds up the counts.
|
||||
const blame = authors ? blameFor(doc.path, text) : Promise.resolve(null);
|
||||
if (this.cacheDoc !== text) {
|
||||
this.cache = new Map();
|
||||
this.incomplete = new Set();
|
||||
this.cacheDoc = text;
|
||||
}
|
||||
try {
|
||||
@@ -187,7 +191,7 @@ class UsagesLensPlugin {
|
||||
.filter((t) => t.line >= firstLine && t.line <= lastLine)
|
||||
.slice(0, MAX_PER_PASS);
|
||||
const key = (t: LensTarget) => `${t.line}:${t.col}:${t.name}`;
|
||||
const todo = usages ? visible.filter((t) => !this.cache.has(key(t))) : [];
|
||||
const todo = usages ? visible.filter((t) => !this.cache.has(key(t)) && !this.incomplete.has(key(t))) : [];
|
||||
let unavailable = false;
|
||||
for (let i = 0; i < todo.length && !unavailable; i += CONCURRENCY) {
|
||||
await Promise.all(
|
||||
@@ -209,7 +213,7 @@ class UsagesLensPlugin {
|
||||
);
|
||||
}, wanted);
|
||||
if (wanted()) {
|
||||
if (incomplete) this.incomplete = true;
|
||||
if (incomplete) this.incomplete.add(key(t));
|
||||
else this.cache.set(key(t), refs.length);
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
Reference in new issue
Заблокувати користувача