mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
fix(lsp): retry incomplete counts and preserve newer hints
This commit is contained in:
@@ -130,6 +130,30 @@ describe("background usage counts", () => {
|
||||
expect(titles(view)).toEqual(["2 usages"]);
|
||||
});
|
||||
|
||||
it("retries incomplete counts when indexing ends without a code-lens refresh", async () => {
|
||||
ipc.lspReferences.mockResolvedValueOnce(refs(0, "indexing"));
|
||||
const view = editor();
|
||||
emitLocalIdeEvent({ type: "languageServer", server: 1, name: "rust-analyzer", status: "busy", message: null });
|
||||
await settle();
|
||||
expect(titles(view)).toEqual([]);
|
||||
await settle();
|
||||
emitLocalIdeEvent({ type: "languageServer", server: 1, name: "rust-analyzer", status: "ready", message: null });
|
||||
await settle();
|
||||
expect(ipc.lspReferences).toHaveBeenCalledTimes(2);
|
||||
expect(titles(view)).toEqual(["2 usages"]);
|
||||
});
|
||||
|
||||
it("does not repeat complete counts when the server finishes other work", async () => {
|
||||
const view = editor();
|
||||
emitLocalIdeEvent({ type: "languageServer", server: 1, name: "rust-analyzer", status: "busy", message: null });
|
||||
await settle();
|
||||
await settle();
|
||||
emitLocalIdeEvent({ type: "languageServer", server: 1, name: "rust-analyzer", status: "ready", message: null });
|
||||
await settle();
|
||||
expect(ipc.lspReferences).toHaveBeenCalledTimes(1);
|
||||
expect(titles(view)).toEqual(["2 usages"]);
|
||||
});
|
||||
|
||||
it("cancels all in-flight requests on destroy without starting the next batch", async () => {
|
||||
const pending = deferred<ReturnType<typeof refs>>();
|
||||
ipc.docOutline.mockResolvedValue(ok(Array.from({ length: 8 }, (_, i) => item(`f${i}`, "function", i))));
|
||||
|
||||
@@ -13,7 +13,7 @@ import { commands, nextRequestId, type OutlineItem, type RequestId, type SymbolK
|
||||
import { useProject } from "@/platform/project";
|
||||
import { debounce, docOf, isUnavailable, lspRequest } from "./lsp";
|
||||
import { posToOffset } from "./positions";
|
||||
import { featureBackoff, onLspRefresh } from "./availability";
|
||||
import { featureBackoff, onLspRefresh, onServerSettled } from "./availability";
|
||||
import { editorPrefs } from "@/platform/editorSettings";
|
||||
import { authorLabel, authorSummary, authorTooltip, blameFor, onBlameInvalidated } from "./authorLens";
|
||||
import { tx } from "@/platform/i18n";
|
||||
@@ -113,8 +113,10 @@ class UsagesLensPlugin {
|
||||
private outline: { doc: Text; targets: LensTarget[] } | null = null;
|
||||
private generation = 0;
|
||||
private pending = new Set<RequestId>();
|
||||
private incomplete = false;
|
||||
private unsubscribe: () => void;
|
||||
private offReady: () => void;
|
||||
private offSettled: () => void;
|
||||
private offBlame: () => void;
|
||||
|
||||
constructor(private view: EditorView) {
|
||||
@@ -127,6 +129,9 @@ class UsagesLensPlugin {
|
||||
this.cache.clear();
|
||||
this.refresh();
|
||||
});
|
||||
this.offSettled = onServerSettled(() => {
|
||||
if (this.incomplete) this.refresh();
|
||||
});
|
||||
this.offBlame = onBlameInvalidated(() => this.refresh());
|
||||
}
|
||||
|
||||
@@ -159,6 +164,7 @@ 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) {
|
||||
@@ -191,7 +197,10 @@ class UsagesLensPlugin {
|
||||
// Only an explicit Find Usages should widen the server's scope.
|
||||
return commands.lspReferences(request, doc.doc, { line: t.line, col: t.col }, false, false);
|
||||
}, wanted);
|
||||
if (wanted() && !incomplete) this.cache.set(key(t), refs.length);
|
||||
if (wanted()) {
|
||||
if (incomplete) this.incomplete = true;
|
||||
else this.cache.set(key(t), refs.length);
|
||||
}
|
||||
} catch (e) {
|
||||
if (wanted() && isUnavailable(e)) unavailable = true;
|
||||
} finally {
|
||||
@@ -230,6 +239,7 @@ class UsagesLensPlugin {
|
||||
this.schedule.cancel();
|
||||
this.unsubscribe();
|
||||
this.offReady();
|
||||
this.offSettled();
|
||||
this.offBlame();
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
@@ -40,6 +40,16 @@ describe("pending hints", () => {
|
||||
expect(hint()).toBe("New error");
|
||||
});
|
||||
|
||||
it("does not overwrite a newer hint while waiting to show progress", () => {
|
||||
const done = showPendingHint(view, "Waiting…");
|
||||
vi.advanceTimersByTime(100);
|
||||
showHint(view, "New error", { tone: "error" });
|
||||
vi.advanceTimersByTime(150);
|
||||
expect(hint()).toBe("New error");
|
||||
done();
|
||||
expect(hint()).toBe("New error");
|
||||
});
|
||||
|
||||
it.each(["edit", "move", "destroy"])("does not show stale progress after %s", (change) => {
|
||||
const done = showPendingHint(view, "Waiting…");
|
||||
if (change === "edit") view.dispatch({ changes: { from: 0, insert: "x" } });
|
||||
|
||||
@@ -75,9 +75,16 @@ export function showHint(
|
||||
/** Show progress only for slow requests; the disposer removes only its own hint. */
|
||||
export function showPendingHint(view: EditorView, content: string): () => void {
|
||||
const { doc, selection } = view.state;
|
||||
const previous = view.state.field(hintField, false);
|
||||
let shown: Hint | null = null;
|
||||
const timer = setTimeout(() => {
|
||||
if (!view.dom.isConnected || view.state.doc !== doc || !view.state.selection.eq(selection)) return;
|
||||
if (
|
||||
!view.dom.isConnected ||
|
||||
view.state.doc !== doc ||
|
||||
!view.state.selection.eq(selection) ||
|
||||
view.state.field(hintField, false) !== previous
|
||||
)
|
||||
return;
|
||||
showHint(view, content, { timeout: 30_000 });
|
||||
shown = view.state.field(hintField);
|
||||
}, 250);
|
||||
|
||||
Reference in New Issue
Block a user