fix(editor): release a decision to leave whose project change never came (#1178)

When the project change after an authorized leave never came, the editors' close decision stayed authorized: files failed with "The project is closing" and later leaves with "An editor close decision is already pending" until restart. An authorized decision with no project opening is now released when the window opens a file or is left again.

Fixes #1139
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-11-04 12:51:27 +08:00
1 джерело 65ca0390de
коміт 85c03d33af
2 files changed
+35

No files matched your search

+11
Переглянути файл
@@ -883,8 +883,18 @@ export class EditorController implements SlotHost {
return generation;
}
/**
* A decision to leave that was authorized, but whose project change or window close never
* came (the window stayed with its project): released, so the window can open files and
* be left again instead of refusing both until it is restarted.
*/
private releaseStaleClose() {
if (this.closeSession && this.closeCommitted && !useProject.getState().opening) this.closeSession.cancel();
}
private checkOpening(generation: { project: number; admission: number }) {
if (generation.project !== this.projectGeneration) throw new Error("The project changed while opening the editor");
this.releaseStaleClose();
if (generation.admission !== this.admissionGeneration || this.closeSession || this.closeRequests)
throw new Error("The project is closing");
}
@@ -1612,6 +1622,7 @@ export class EditorController implements SlotHost {
++this.admissionGeneration;
++this.closeRequests;
return this.enqueueRemoval(async () => {
this.releaseStaleClose();
if (this.closeSession) throw new Error("An editor close decision is already pending");
const generation = this.projectGeneration;
const documents = [...customDocuments(useTabs.getState(), useContributions.getState().editorKinds).values()];
+24
Переглянути файл
@@ -631,6 +631,30 @@ describe("custom editor document lifetime", () => {
expect(b.saved).toBe("edited");
});
it("releases an authorized decision to leave when the project never changed", async () => {
const doc = await open();
const session = await ctrl.prepareClose();
session.commit();
expect(doc.frozen).toBe(true);
// The window stayed with its project: files open again, and it can be left again.
await open("b");
expect(findTab(useTabs.getState(), input("b").key)).not.toBeNull();
expect(doc.frozen).toBe(false);
const again = await ctrl.prepareClose();
expect(() => session.commit()).toThrow();
again.cancel();
});
it("keeps an authorized decision while the project it leaves for is opening", async () => {
await open();
const session = await ctrl.prepareClose();
session.commit();
useProject.setState({ opening: true });
await expect(open("b")).rejects.toThrow("The project is closing");
await expect(ctrl.prepareClose()).rejects.toThrow("already pending");
useProject.setState({ opening: false });
});
it("rejects authorization from a cancelled project close session", async () => {
const doc = await open();
const session = await ctrl.prepareClose();