fix(editor): auto-save when the app is deactivated while focus is in a frame (#1636)

With focus in a plugin's web view, the window's blur fires when the frame takes focus, before any edit, and not again when the user switches applications, so edits made in the frame weren't auto-saved. The editor now also saves on the application-deactivated event.
This commit is contained in:
l0ng-ai
2026-11-06 06:33:12 +08:00
committed by GitHub
parent 83d2f67b4f
commit a6cf4442d8
3 changed files with 31 additions and 0 deletions
+11
View File
@@ -21,6 +21,7 @@ import {
type SyntaxHighlights,
type TextChange,
} from "@/platform/ipc";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { EditorState } from "@codemirror/state";
import { customRegions } from "./cm/regions";
@@ -60,6 +61,11 @@ export interface DocBackend {
onDirty(handler: (e: { doc: DocId; dirty: boolean }) => void): () => void;
/** Files changed on disk (used to refresh nothing but closed-file state). */
onFsChanged?(handler: (e: { removed: string[] }) => void): () => void;
/**
* Every OpenIDEA window lost focus to another application. Also seen while focus is in a frame
* (a plugin's web view), which hides the window's own `blur`.
*/
onApplicationDeactivated?(handler: () => void): () => void;
/**
* An open document's language changed (Settings › File Types, Override File
* Type), or its own mode (`DocumentInfo.uiMode`).
@@ -86,6 +92,11 @@ export const ipcBackend: DocBackend = {
onPatched: (h) => onIdeEvent("docPatched", (e) => h(e)),
onDirty: (h) => onIdeEvent("docDirty", (e) => h(e)),
onFsChanged: (h) => onIdeEvent("fsChanged", (e) => h(e)),
onApplicationDeactivated: (h) => {
if (!isTauri()) return () => {};
const listener = getCurrentWebviewWindow().listen("ide://application-deactivated", () => h());
return () => void listener.then((unlisten) => unlisten());
},
onLanguage: (h) => onIdeEvent("docLanguage", (e) => h(e)),
};
+2
View File
@@ -353,6 +353,8 @@ export class EditorController implements SlotHost {
void this.saveCustomDocuments(true).catch((e) => this.documentError(e));
}
};
if (this.manager.backend.onApplicationDeactivated)
this.disposers.push(this.manager.backend.onApplicationDeactivated(onWindowBlur));
const onPageHide = () => this.saveSession();
if (typeof window !== "undefined") {
if (this.manager.backend.syncFromDisk)
+18
View File
@@ -481,6 +481,24 @@ describe("custom editor document lifetime", () => {
expect(findTab(useTabs.getState(), input().key)).toBeNull();
});
it("autosaves when the application is deactivated while focus is in a frame", async () => {
ctrl.stop();
let deactivated = () => {};
ctrl = new EditorController({
...createMemoryBackend(),
onApplicationDeactivated: (handler) => {
deactivated = handler;
return () => {};
},
});
ctrl.start();
const doc = await open();
useProject.setState({ settings: { files: { autoSave: true } } as never });
// Focus inside a plugin's frame: the window's own blur has already fired, before the edit.
deactivated();
await vi.waitFor(() => expect(doc.saved).toBe("edited"));
});
it("does not prompt or blur-autosave a document until execution has drained", async () => {
const doc = await open();
useProject.setState({ settings: { files: { autoSave: true } } as never });