diff --git a/src/editor/core/controller.ts b/src/editor/core/controller.ts index 723a27d5a..18ffcb05d 100644 --- a/src/editor/core/controller.ts +++ b/src/editor/core/controller.ts @@ -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()]; diff --git a/src/editor/core/customDocuments.test.ts b/src/editor/core/customDocuments.test.ts index b9f8a6993..cb56d13b7 100644 --- a/src/editor/core/customDocuments.test.ts +++ b/src/editor/core/customDocuments.test.ts @@ -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();