Порівняти коміти

...
Автор SHA1 Повідомлення Дата
l0ng-ai fc21c50c06 fix(vcs): show the current line's inline blame at once when the file is blamed
Every caret move waited a fixed 300 ms before showing the new line's blame,
even when the document's blame was already loaded and showing it needs no
git call, so the annotation trailed the caret. A caret move within a
document whose blame is loaded (or loading) now refreshes at once; edits and
a document not blamed yet still wait, so typing doesn't run git per
keystroke.
2026-11-15 01:10:00 +08:00
2 змінених файлів з 20 додано та 5 видалено

No files matched your search

+6 -2
Переглянути файл
@@ -112,7 +112,7 @@ describe("inline blame formatting", () => {
});
describe("current line decoration", () => {
it("debounces caret moves and reuses blame for the same document version", async () => {
it("debounces caret moves until the document is blamed, then follows the caret at once", async () => {
const v = editor();
await vi.advanceTimersByTimeAsync(299);
expect(mocks.blame).not.toHaveBeenCalled();
@@ -121,9 +121,10 @@ describe("current line decoration", () => {
expect(mocks.blame).not.toHaveBeenCalled();
await vi.advanceTimersByTimeAsync(1);
expect(hint()?.textContent).toContain("Bob Jones");
// The blame is at hand: the caret's new line shows without the delay.
v.dispatch({ selection: { anchor: 0 } });
expect(hint()).toBeNull();
await tick();
await vi.advanceTimersByTimeAsync(0);
expect(hint()?.textContent).toContain("Alice Smith");
expect(mocks.blame).toHaveBeenCalledTimes(1);
expect(mocks.file).toHaveBeenCalledTimes(1);
@@ -207,6 +208,9 @@ describe("current line decoration", () => {
mocks.blame.mockResolvedValue({ status: "ok", data: [uncommitted, bob, uncommitted] });
v.dispatch({ changes: { from: 0, insert: "x" } });
expect(hint()).toBeNull();
// An edit still waits, so typing doesn't run git per keystroke.
await vi.advanceTimersByTimeAsync(299);
expect(mocks.blame).toHaveBeenCalledTimes(1);
await tick();
expect(mocks.blame).toHaveBeenLastCalledWith(ctx.path, "xone\ntwo\nthree", null);
expect(hint()?.textContent).toBe("You, now · Uncommitted changes");
+14 -3
Переглянути файл
@@ -66,6 +66,9 @@ export function inlineBlameSegments(line: BlameLine, now = Date.now()): BlameSeg
}
/** Only the current document version is retained, including its in-flight request. */
/** How long a caret move or an edit waits before the file is blamed. */
const BLAME_DELAY_MS = 300;
class BlameCache {
private doc: Text | undefined;
private pending: Promise<BlameLine[]> | undefined;
@@ -75,6 +78,11 @@ class BlameCache {
this.pending = undefined;
}
/** Whether `doc`'s blame is loaded or loading: showing another of its lines runs no git. */
has(doc: Text): boolean {
return doc === this.doc && !!this.pending;
}
get(doc: Text, load: () => Promise<BlameLine[]>): Promise<BlameLine[]> {
if (doc !== this.doc || !this.pending) {
this.doc = doc;
@@ -265,14 +273,17 @@ export function inlineBlame(ctx: EditorDocContext): Extension {
if (update.docChanged) this.cache.clear();
if (update.docChanged || update.selectionSet) {
this.line = undefined;
this.schedule();
// The caret moved within a document whose blame is at hand: follow it at once. Edits
// (and a document not blamed yet) wait, so typing doesn't run git per keystroke.
this.schedule(this.cache.has(update.state.doc) ? 0 : BLAME_DELAY_MS);
}
}
private schedule() {
private schedule(delay = BLAME_DELAY_MS) {
clearTimeout(this.timer);
++this.generation;
this.timer = setTimeout(() => void this.refresh(), 300);
if (delay === 0) void this.refresh();
else this.timer = setTimeout(() => void this.refresh(), delay);
}
private async refresh() {