From 40aa7e489936d2cb66ca60621ac7dcbd7d4ba47d Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Thu, 12 Nov 2026 01:33:25 +0800 Subject: [PATCH] fix(java): resolve navigation and usages against the text just typed (#2759) Right after typing in a Java file, Show Usages answered for the wrong symbol and Go to Declaration could do nothing. Usages, Find Usages and hierarchy now wait for the editor's edits to sync before sending the caret position, and the java-usages commands (usages, implementations, hierarchy, configuration) wait for jdtls to reconcile queued document changes before resolving a position (bundle 1.13.0, rebuilt reproducibly from source). File-system events (e.g. the build writing target/classes) now only drop cached navigation answers instead of discarding an in-flight Go to Declaration. --- crates/ide-sdk/src/java.rs | 2 +- java/java-usages/MANIFEST.MF | 2 +- java/java-usages/README.md | 2 +- java/java-usages/openidea-java-usages.jar | Bin 160512 -> 161726 bytes .../src/ai/openidea/Documents.java | 21 +++++++ .../configuration/ConfigurationCommand.java | 2 + .../openidea/hierarchy/HierarchyCommand.java | 2 + .../ImplementationsCommand.java | 2 + .../src/ai/openidea/usages/UsagesCommand.java | 2 + .../lsp/configurationNavigation.test.ts | 54 +++++++++++------- src/editor/lsp/configurationNavigation.ts | 11 ++-- src/features/code-insight/hierarchy.ts | 3 +- src/features/code-insight/ipc.ts | 7 ++- .../code-insight/usageResults.test.ts | 33 ++++++++++- src/features/code-insight/usages.ts | 2 + 15 files changed, 113 insertions(+), 32 deletions(-) create mode 100644 java/java-usages/src/ai/openidea/Documents.java diff --git a/crates/ide-sdk/src/java.rs b/crates/ide-sdk/src/java.rs index d7008cf5e..8e3cccdca 100644 --- a/crates/ide-sdk/src/java.rs +++ b/crates/ide-sdk/src/java.rs @@ -1541,7 +1541,7 @@ pub fn usages_bundle(data_dir: &Path) -> Option { install_bundle( data_dir, "java-usages", - "1.12.0", + "1.13.0", include_bytes!("../../../java/java-usages/openidea-java-usages.jar"), ) } diff --git a/java/java-usages/MANIFEST.MF b/java/java-usages/MANIFEST.MF index dffa21313..2418c45c4 100644 --- a/java/java-usages/MANIFEST.MF +++ b/java/java-usages/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: OpenIDEA Java usages Bundle-SymbolicName: ai.openidea.usages;singleton:=true -Bundle-Version: 1.12.0 +Bundle-Version: 1.13.0 Bundle-RequiredExecutionEnvironment: JavaSE-21 Require-Bundle: org.eclipse.jdt.ls.core,org.eclipse.jdt.core, org.eclipse.jdt.core.manipulation,org.eclipse.core.runtime, diff --git a/java/java-usages/README.md b/java/java-usages/README.md index 39773681d..b074a12c3 100644 --- a/java/java-usages/README.md +++ b/java/java-usages/README.md @@ -88,7 +88,7 @@ is not a claim of full IntelliJ feature parity. ## Rebuild and delivery The checked-in jar is embedded by ide-sdk `include_bytes!`, installed as -`java-usages-1.12.0.jar`, and added alongside existing initialization bundles. +`java-usages-1.13.0.jar`, and added alongside existing initialization bundles. Users need no compiler, Maven or Python. Manifest/install version must change when replacing the bundle because OSGi caches bundles. diff --git a/java/java-usages/openidea-java-usages.jar b/java/java-usages/openidea-java-usages.jar index abfa38710246ca4ec2f463cd81931d062548143c..63ca14d948e66cacd2547d681c3593b7617b8b02 100644 GIT binary patch delta 36274 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 UEGjmfROG8#NcK->@K&h*0T`+);{X5v delta 35054 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 XeqGfSeo>8~Tq;ocX-fU&W<%`%r&Id3 diff --git a/java/java-usages/src/ai/openidea/Documents.java b/java/java-usages/src/ai/openidea/Documents.java new file mode 100644 index 000000000..1662cfc52 --- /dev/null +++ b/java/java-usages/src/ai/openidea/Documents.java @@ -0,0 +1,21 @@ +package ai.openidea; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.jdt.ls.core.internal.JobHelpers; +import org.eclipse.jdt.ls.core.internal.handlers.BaseDocumentLifeCycleHandler; + +/** What a command that reads an editor's position needs before it resolves the position. */ +public final class Documents { + private Documents() {} + + /** + * Waits for the queued document changes to be reconciled, as JDT LS's own position requests + * do: `workspace/executeCommand` does not, so a position sent right after an edit would + * be resolved against the element structure from before it. + */ + public static void awaitChanges(IProgressMonitor monitor) { + JobHelpers.waitForJobs(BaseDocumentLifeCycleHandler.DOCUMENT_LIFE_CYCLE_JOBS, monitor); + if (monitor.isCanceled()) throw new OperationCanceledException(); + } +} diff --git a/java/java-usages/src/ai/openidea/configuration/ConfigurationCommand.java b/java/java-usages/src/ai/openidea/configuration/ConfigurationCommand.java index 3a16024d4..f4640d60b 100644 --- a/java/java-usages/src/ai/openidea/configuration/ConfigurationCommand.java +++ b/java/java-usages/src/ai/openidea/configuration/ConfigurationCommand.java @@ -1,5 +1,6 @@ package ai.openidea.configuration; +import ai.openidea.Documents; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; @@ -96,6 +97,7 @@ public final class ConfigurationCommand implements IDelegateCommandHandler { @Override public Object executeCommand(String command, List args, IProgressMonitor monitor) throws Exception { if (command.equals("openidea.java.configuration.warmup")) return ConfigurationWarmup.request(args); + Documents.awaitChanges(monitor); String uri = (String) args.get(0); String key = args.get(1) instanceof String s ? s : null; int line = ((Number) args.get(2)).intValue(), column = ((Number) args.get(3)).intValue(); diff --git a/java/java-usages/src/ai/openidea/hierarchy/HierarchyCommand.java b/java/java-usages/src/ai/openidea/hierarchy/HierarchyCommand.java index b2504dd75..b6b8bcf24 100644 --- a/java/java-usages/src/ai/openidea/hierarchy/HierarchyCommand.java +++ b/java/java-usages/src/ai/openidea/hierarchy/HierarchyCommand.java @@ -1,5 +1,6 @@ package ai.openidea.hierarchy; +import ai.openidea.Documents; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -44,6 +45,7 @@ public final class HierarchyCommand implements IDelegateCommandHandler { @Override public Object executeCommand(String command, List arguments, IProgressMonitor monitor) throws Exception { + Documents.awaitChanges(monitor); String uri = (String) arguments.get(0); int line = ((Number) arguments.get(1)).intValue(); int column = ((Number) arguments.get(2)).intValue(); diff --git a/java/java-usages/src/ai/openidea/implementations/ImplementationsCommand.java b/java/java-usages/src/ai/openidea/implementations/ImplementationsCommand.java index 1dbedf115..821af09c2 100644 --- a/java/java-usages/src/ai/openidea/implementations/ImplementationsCommand.java +++ b/java/java-usages/src/ai/openidea/implementations/ImplementationsCommand.java @@ -1,5 +1,6 @@ package ai.openidea.implementations; +import ai.openidea.Documents; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -27,6 +28,7 @@ public final class ImplementationsCommand implements IDelegateCommandHandler { @Override public Object executeCommand(String command, List arguments, IProgressMonitor monitor) throws Exception { + Documents.awaitChanges(monitor); List locations = new ArrayList<>(); boolean complete = find(arguments, locations, monitor); return Map.of("locations", locations, "complete", complete); diff --git a/java/java-usages/src/ai/openidea/usages/UsagesCommand.java b/java/java-usages/src/ai/openidea/usages/UsagesCommand.java index 57e61de80..b1b05bc8f 100644 --- a/java/java-usages/src/ai/openidea/usages/UsagesCommand.java +++ b/java/java-usages/src/ai/openidea/usages/UsagesCommand.java @@ -1,5 +1,6 @@ package ai.openidea.usages; +import ai.openidea.Documents; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -29,6 +30,7 @@ import org.eclipse.lsp4j.*; public final class UsagesCommand implements IDelegateCommandHandler { @Override public Object executeCommand(String command, List arguments, IProgressMonitor monitor) throws Exception { + Documents.awaitChanges(monitor); String uri = (String) arguments.get(0); int line = ((Number) arguments.get(1)).intValue(); int column = ((Number) arguments.get(2)).intValue(); diff --git a/src/editor/lsp/configurationNavigation.test.ts b/src/editor/lsp/configurationNavigation.test.ts index 0e2f422cf..55b37ce87 100644 --- a/src/editor/lsp/configurationNavigation.test.ts +++ b/src/editor/lsp/configurationNavigation.test.ts @@ -200,27 +200,39 @@ describe("configuration navigation result reuse", () => { expect(lspGoto).toHaveBeenCalledTimes(2); }); - it.each(["settled", "refresh"])("does not swallow an in-flight click on its own server %s event", async (event) => { - let resolve!: (r: Awaited>) => void; - lspGoto.mockImplementationOnce( - () => - new Promise((r) => { - resolve = r; - }), - ); - const view = editor(); - const pending = requestNavigation(view, 10, "definition"); - await vi.waitFor(() => expect(lspGoto).toHaveBeenCalledOnce()); - if (event === "settled") { - emitLocalIdeEvent({ type: "languageServer", server: 1, name: "jdtls", status: "busy", message: null }); - vi.setSystemTime(Date.now() + 1600); - emitLocalIdeEvent({ type: "languageServer", server: 1, name: "jdtls", status: "ready", message: null }); - } else emitLocalIdeEvent({ type: "lspRefresh", server: 1, kind: "codeLens" }); - resolve({ status: "ok", data: [target] }); - expect(await pending).toEqual([target]); - await requestNavigation(view, 10, "definition"); - expect(lspGoto).toHaveBeenCalledTimes(2); - }); + it.each(["settled", "refresh", "fsChanged", "excludedDirsChanged"])( + "does not swallow an in-flight click on a %s event", + async (event) => { + let resolve!: (r: Awaited>) => void; + lspGoto.mockImplementationOnce( + () => + new Promise((r) => { + resolve = r; + }), + ); + const view = editor(); + const pending = requestNavigation(view, 10, "definition"); + await vi.waitFor(() => expect(lspGoto).toHaveBeenCalledOnce()); + if (event === "settled") { + emitLocalIdeEvent({ type: "languageServer", server: 1, name: "jdtls", status: "busy", message: null }); + vi.setSystemTime(Date.now() + 1600); + emitLocalIdeEvent({ type: "languageServer", server: 1, name: "jdtls", status: "ready", message: null }); + } else if (event === "refresh") emitLocalIdeEvent({ type: "lspRefresh", server: 1, kind: "codeLens" }); + // The build writing its output right after an edit. + else if (event === "fsChanged") + emitLocalIdeEvent({ + type: "fsChanged", + created: ["/p/target/classes/Config.class"], + modified: [], + removed: [], + }); + else emitLocalIdeEvent({ type: "excludedDirsChanged", dirs: ["/p/target"] }); + resolve({ status: "ok", data: [target] }); + expect(await pending).toEqual([target]); + await requestNavigation(view, 10, "definition"); + expect(lspGoto).toHaveBeenCalledTimes(2); + }, + ); it("does not retain errors or missing targets during import", async () => { const view = editor(); diff --git a/src/editor/lsp/configurationNavigation.ts b/src/editor/lsp/configurationNavigation.ts index 34a6760b5..c7d795d46 100644 --- a/src/editor/lsp/configurationNavigation.ts +++ b/src/editor/lsp/configurationNavigation.ts @@ -67,9 +67,10 @@ export function navigationWord(view: EditorView, offset: number): { from: number return from < to ? { from: line.from + from, to: line.from + to } : null; } -/** Indexing/refresh can be progress from this very lookup. Do not cancel an - * explicit click when its own server task finishes; simply don't retain that - * answer. Source edits/restarts still invalidate the request generation. */ +/** Indexing/refresh can be progress from this very lookup, and file changes the + * build writing its output after an edit. Do not cancel an explicit click for + * them; simply don't retain that answer. Source edits/restarts still invalidate + * the request generation. */ function refreshConfigurationNavigation() { for (const [key, entry] of results) { entry.retain = false; @@ -147,8 +148,8 @@ export function requestNavigation(view: EditorView, offset: number, kind: GotoKi export function installConfigurationNavigationEvents(): () => void { const clear = invalidateConfigurationNavigation; const off = [ - onIdeEvent("fsChanged", clear), - onIdeEvent("excludedDirsChanged", clear), + onIdeEvent("fsChanged", refreshConfigurationNavigation), + onIdeEvent("excludedDirsChanged", refreshConfigurationNavigation), onIdeEvent("docPatched", clear), onIdeEvent("docLanguage", clear), onIdeEvent("workspaceEditApplied", clear), diff --git a/src/features/code-insight/hierarchy.ts b/src/features/code-insight/hierarchy.ts index b01cf301a..35b19afb5 100644 --- a/src/features/code-insight/hierarchy.ts +++ b/src/features/code-insight/hierarchy.ts @@ -5,7 +5,7 @@ import { incompleteNote } from "@/editor/lsp/lsp"; import { IpcError, call, commands, nextRequestId } from "@/platform/ipc"; import { toolWindowService } from "@/workbench/contributions"; import { basename } from "@/platform/project"; -import { caretContext, hint, lspRequest, reportError, withDocument, type CaretContext } from "./ipc"; +import { caretContext, caretSynced, hint, lspRequest, reportError, withDocument, type CaretContext } from "./ipc"; import { tx } from "@/platform/i18n"; export type HierarchyMode = "call" | "type"; @@ -295,6 +295,7 @@ export async function showHierarchy(mode: HierarchyMode) { root: { name: c.word ?? basename(c.path), path: c.path, line: c.line, col: c.col }, }); toolWindowService().show("hierarchy"); + await caretSynced(c); if ((await loadRoot(true)) !== "noTarget") return; // Nothing at the caret: retry at the enclosing method / class. const root = await enclosingRoot(c, mode); diff --git a/src/features/code-insight/ipc.ts b/src/features/code-insight/ipc.ts index 96718452d..faa629e37 100644 --- a/src/features/code-insight/ipc.ts +++ b/src/features/code-insight/ipc.ts @@ -1,6 +1,6 @@ /** IPC helpers shared by the code-insight views. */ import type { EditorView } from "@codemirror/view"; -import { activeEditor, editorsFor } from "@/editor/api"; +import { activeEditor, editorsFor, whenSynced } from "@/editor/api"; import { isVirtualUri, virtualDocument } from "@/platform/virtualDocs"; import { executeAction } from "@/platform/actions"; import { IpcError, call, commands, isCancelled, isTauri, unavailableText } from "@/platform/ipc"; @@ -71,6 +71,11 @@ export function caretContext(): CaretContext | null { }; } +/** Language servers answer from the backend's copy: the caret's position is only valid there once the editor's edits reached it. */ +export function caretSynced(c: CaretContext): Promise { + return c.view ? whenSynced(c.view) : Promise.resolve(); +} + function wordAt(view: EditorView, offset: number): string | null { const w = view.state.wordAt(offset); return w ? view.state.sliceDoc(w.from, w.to) : null; diff --git a/src/features/code-insight/usageResults.test.ts b/src/features/code-insight/usageResults.test.ts index 60d501ae8..f7c75b9ad 100644 --- a/src/features/code-insight/usageResults.test.ts +++ b/src/features/code-insight/usageResults.test.ts @@ -5,6 +5,8 @@ vi.mock("@/workbench/contributions", async (orig) => ({ ...(await orig()), toolWindowService: () => ({ show }), })); +/** The editor at the caret (`null`: a search from outside an editor). */ +let caretView: import("@codemirror/view").EditorView | null = null; const referenceResponse = vi.fn(); const lspReferences = vi.fn(async (..._args: unknown[]) => ({ status: "ok", data: null })); vi.mock("@/platform/ipc", async (orig) => { @@ -13,7 +15,7 @@ vi.mock("@/platform/ipc", async (orig) => { }); vi.mock("./ipc", async (orig) => ({ ...(await orig()), - caretContext: () => ({ doc: 1, path: "/p/a.rs", line: 0, col: 0, word: "used", view: null }), + caretContext: () => ({ doc: 1, path: "/p/a.rs", line: 0, col: 0, word: "used", view: caretView }), lspRequest: (request: () => unknown) => { request(); return referenceResponse(); @@ -109,6 +111,35 @@ describe("findUsages", () => { resetUsages(); }); + it("asks the server only once the editor's edits reached it", async () => { + const { EditorState } = await import("@codemirror/state"); + const { EditorView } = await import("@codemirror/view"); + const { documentSynced } = await import("@/editor/api"); + let release!: () => void; + const synced = new Promise((resolve) => { + release = resolve; + }); + caretView = new EditorView({ + state: EditorState.create({ doc: "used();", extensions: documentSynced.of(() => synced) }), + }); + referenceResponse.mockResolvedValueOnce({ locations: [], incomplete: null }); + lspReferences.mockClear(); + const pending = findUsages(); + try { + await new Promise((r) => setTimeout(r, 10)); + expect(lspReferences).not.toHaveBeenCalled(); + release(); + await pending; + expect(lspReferences).toHaveBeenCalledTimes(1); + } finally { + release(); + await pending; + caretView.destroy(); + caretView = null; + resetUsages(); + } + }); + it("waits for the contributed index barrier, then preserves partial and final usage events", async () => { let release!: () => void; const sync = vi.fn( diff --git a/src/features/code-insight/usages.ts b/src/features/code-insight/usages.ts index 3f323521b..5da12694c 100644 --- a/src/features/code-insight/usages.ts +++ b/src/features/code-insight/usages.ts @@ -10,6 +10,7 @@ import { shortcutLabel } from "@/platform/keymap"; import { synchronizeDocumentIndexes, toolWindowService } from "@/workbench/contributions"; import { caretContext, + caretSynced, caretPoint, hint, lspRequest, @@ -228,6 +229,7 @@ export async function fetchUsages( let classified: ClassifiedReference[] = []; let note: string | null = null; let librariesSkipped = false; + await caretSynced(c); await synchronizeDocumentIndexes(c.path); if (c.view && c.view.state.doc !== before) throw new IpcError({ kind: "cancelled", message: "", data: null }); let settled = false;