дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
perf(java): prewarm configuration navigation and share results between hover and click (#947)
Configuration navigation from YAML/properties still waited for a cold index the first time and repeated the lookup between the ⌘-hover probe and the click. - Trusted projects with a Maven/Gradle/Eclipse build file start Java shortly after opening. Once JDT reports ServiceReady, the app calls the new openidea.java.configuration.warmup command: a low-priority background job builds the configuration index one project at a time (open YAML/properties modules first), waits for workspace builds, has a 30 s budget per project, and only fills free cache room. - Index builds take a cancellable per-project lock instead of one global lock, so a background build never delays another project, and a warmed snapshot is dropped if the Java model changed meanwhile. - In YAML/properties the editor underlines the whole key (hyphens and dots included), hover and Go to Definition share one bounded request per key, and the declaration chooser names the whole key. Bundle java-usages 1.6.0 is rebuilt reproducibly with build.py against JDT LS 1.63.0-202610191322 (SHA-256 33cfa1bb…). The real-JDT harness checks that the first lookup reuses the warmed index. Co-authored-by: qjlqyf <[email protected]>
Цей коміт міститься в:
1 джерело
8c381a07c4
коміт
c4b87ae09e
22 files changed
+1038
-37
No files matched your search
@@ -45,6 +45,7 @@ const fb = await import("./syntaxFallback");
|
||||
const { gotoTargets, navigationExtension, highlightUsages } = await import("./navigation");
|
||||
const { startRename, renameNamePart } = await import("./rename");
|
||||
const { lspDoc } = await import("./lsp");
|
||||
const { invalidateConfigurationNavigation } = await import("./configurationNavigation");
|
||||
const { useLspPopup, closePopup } = await import("./popupStore");
|
||||
|
||||
const r = (line: number, col: number, endCol: number): Range => ({ start: { line, col }, end: { line, col: endCol } });
|
||||
@@ -96,6 +97,7 @@ function editor(doc: string, language = "typescript", anchor = 0, path = "/p/a.t
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
invalidateConfigurationNavigation();
|
||||
for (const f of Object.values(cmd)) f.mockClear();
|
||||
openFile.mockClear();
|
||||
closePopup();
|
||||
@@ -603,6 +605,79 @@ describe("modifier-hover navigation recovery", () => {
|
||||
);
|
||||
}
|
||||
|
||||
it.each(["yaml", "properties"])(
|
||||
"%s hover underlines without a popup and clicking reuses the target",
|
||||
async (language) => {
|
||||
cmd.lspGoto.mockReturnValueOnce(ok([target]));
|
||||
const view = editor("port: 8080", language);
|
||||
move(view, 1);
|
||||
await vi.waitFor(() => expect(view.dom.querySelector(".cm-lsp-link")?.textContent).toBe("port"));
|
||||
expect(cmd.lspHover).not.toHaveBeenCalled();
|
||||
view.contentDOM.dispatchEvent(
|
||||
new MouseEvent("mousedown", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
metaKey: mac,
|
||||
ctrlKey: !mac,
|
||||
}),
|
||||
);
|
||||
await vi.waitFor(() => expect(openFile).toHaveBeenCalledOnce());
|
||||
expect(cmd.lspGoto).toHaveBeenCalledOnce();
|
||||
expect(view.dom.querySelector(".cm-lsp-nav-info")).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["yaml", "properties"])("%s underlines the entire hyphenated configuration name", async (language) => {
|
||||
cmd.lspGoto.mockReturnValueOnce(ok([target]));
|
||||
const view = editor("driver-class-name: example.Driver", language);
|
||||
for (const pos of [1, 6, 8, 12, 15]) {
|
||||
move(view, pos);
|
||||
await vi.waitFor(() => expect(view.dom.querySelector(".cm-lsp-link")?.textContent).toBe("driver-class-name"));
|
||||
}
|
||||
await gotoTargets(view, "definition", 14);
|
||||
expect(cmd.lspGoto).toHaveBeenCalledOnce();
|
||||
expect(openFile).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("invalidates the hover marker after dependencies change", async () => {
|
||||
cmd.lspGoto.mockReturnValueOnce(ok([target]));
|
||||
const view = editor("port: 8080", "yaml");
|
||||
move(view, 1);
|
||||
await vi.waitFor(() => expect(view.dom.querySelector(".cm-lsp-link")).not.toBeNull());
|
||||
invalidateConfigurationNavigation();
|
||||
cmd.lspGoto.mockReturnValueOnce(ok([]));
|
||||
move(view, 1);
|
||||
await vi.waitFor(() => expect(view.dom.querySelector(".cm-lsp-link")).toBeNull());
|
||||
expect(cmd.lspGoto).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("retries a stuck configuration probe without retaining the late response", async () => {
|
||||
let finish!: (value: Awaited<R<unknown>>) => void;
|
||||
cmd.lspGoto.mockReturnValueOnce(
|
||||
new Promise((resolve) => {
|
||||
finish = resolve;
|
||||
}),
|
||||
);
|
||||
const view = editor("port: 8080", "yaml");
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
move(view, 1);
|
||||
await vi.advanceTimersByTimeAsync(3001);
|
||||
cmd.lspGoto.mockReturnValueOnce(ok([target]));
|
||||
move(view, 1);
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
expect(cmd.lspGoto).toHaveBeenCalledTimes(2);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
await vi.waitFor(() => expect(view.dom.querySelector(".cm-lsp-link")).not.toBeNull());
|
||||
finish({ status: "ok", data: [] });
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
await gotoTargets(view, "definition", 1);
|
||||
expect(cmd.lspGoto).toHaveBeenCalledTimes(2);
|
||||
expect(openFile).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("shows the link without waiting for documentation", async () => {
|
||||
cmd.lspGoto.mockReturnValueOnce(ok([target]));
|
||||
cmd.lspHover.mockReturnValueOnce(new Promise(() => {}));
|
||||
|
||||
Reference in new issue
Заблокувати користувача