diff --git a/src/vs/code/electron-main/lifecycle.ts b/src/vs/code/electron-main/lifecycle.ts index 0ac4e630112a83af56004cc63597e9374c4cf416..e33f148ea14a290114f321221792162de9cf3b8d 100644 --- a/src/vs/code/electron-main/lifecycle.ts +++ b/src/vs/code/electron-main/lifecycle.ts @@ -16,6 +16,13 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' export const ILifecycleService = createDecorator('lifecycleService'); +export enum UnloadReason { + CLOSE, + QUIT, + RELOAD, + LOAD +} + export interface ILifecycleService { _serviceBrand: any; @@ -33,7 +40,7 @@ export interface ILifecycleService { ready(): void; registerWindow(vscodeWindow: IVSCodeWindow): void; - unload(vscodeWindow: IVSCodeWindow): TPromise; + unload(vscodeWindow: IVSCodeWindow, reason: UnloadReason): TPromise; quit(fromUpdate?: boolean): TPromise; } @@ -126,7 +133,7 @@ export class LifecycleService implements ILifecycleService { // Otherwise prevent unload and handle it from window e.preventDefault(); - this.unload(vscodeWindow).done(veto => { + this.unload(vscodeWindow, UnloadReason.CLOSE).done(veto => { if (!veto) { this.windowToCloseRequest[windowId] = true; vscodeWindow.win.close(); @@ -138,7 +145,7 @@ export class LifecycleService implements ILifecycleService { }); } - public unload(vscodeWindow: IVSCodeWindow): TPromise { + public unload(vscodeWindow: IVSCodeWindow, reason: UnloadReason): TPromise { // Always allow to unload a window that is not yet ready if (vscodeWindow.readyState !== ReadyState.READY) { @@ -149,14 +156,14 @@ export class LifecycleService implements ILifecycleService { return new TPromise((c) => { const oneTimeEventToken = this.oneTimeListenerTokenGenerator++; - const oneTimeOkEvent = 'vscode:ok' + oneTimeEventToken; - const oneTimeCancelEvent = 'vscode:cancel' + oneTimeEventToken; + const okChannel = `vscode:ok${oneTimeEventToken}`; + const cancelChannel = `vscode:cancel${oneTimeEventToken}`; - ipc.once(oneTimeOkEvent, () => { + ipc.once(okChannel, () => { c(false); // no veto }); - ipc.once(oneTimeCancelEvent, () => { + ipc.once(cancelChannel, () => { // Any cancellation also cancels a pending quit if present if (this.pendingQuitPromiseComplete) { @@ -168,7 +175,7 @@ export class LifecycleService implements ILifecycleService { c(true); // veto }); - vscodeWindow.send('vscode:beforeUnload', { okChannel: oneTimeOkEvent, cancelChannel: oneTimeCancelEvent, quitRequested: this.quitRequested }); + vscodeWindow.send('vscode:beforeUnload', { okChannel, cancelChannel, reason: this.quitRequested ? UnloadReason.QUIT : reason }); }); } diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index cf37cc95c016fe4f56b8e6ec9c60a31b46bb8d1c..28b21b35e385287312c41fb9c789f9ea32c0ee47 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -20,7 +20,7 @@ import { IStorageService } from 'vs/code/electron-main/storage'; import { IPath, VSCodeWindow, IWindowConfiguration, IWindowState as ISingleWindowState, defaultWindowState, ReadyState } from 'vs/code/electron-main/window'; import { ipcMain as ipc, app, screen, BrowserWindow, dialog } from 'electron'; import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/electron-main/paths'; -import { ILifecycleService } from 'vs/code/electron-main/lifecycle'; +import { ILifecycleService, UnloadReason } from 'vs/code/electron-main/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/code/electron-main/log'; import { getPathLabel } from 'vs/base/common/labels'; @@ -273,7 +273,7 @@ export class WindowsManager implements IWindowsMainService { public reload(win: VSCodeWindow, cli?: ParsedArgs): void { // Only reload when the window has not vetoed this - this.lifecycleService.unload(win).done(veto => { + this.lifecycleService.unload(win, UnloadReason.RELOAD).done(veto => { if (!veto) { win.reload(cli); } @@ -762,7 +762,7 @@ export class WindowsManager implements IWindowsMainService { } // Only load when the window has not vetoed this - this.lifecycleService.unload(vscodeWindow).done(veto => { + this.lifecycleService.unload(vscodeWindow, UnloadReason.LOAD).done(veto => { if (!veto) { // Load it diff --git a/src/vs/platform/lifecycle/common/lifecycle.ts b/src/vs/platform/lifecycle/common/lifecycle.ts index 90a8cbc3712947883148f7c9e6ec5ca8fd9eb9f8..5906eca702fdbc9253f975a05954066334836909 100644 --- a/src/vs/platform/lifecycle/common/lifecycle.ts +++ b/src/vs/platform/lifecycle/common/lifecycle.ts @@ -20,7 +20,22 @@ export const ILifecycleService = createDecorator('lifecycleSe */ export interface ShutdownEvent { veto(value: boolean | TPromise): void; - quitRequested: boolean; + reason: ShutdownReason; +} + +export enum ShutdownReason { + + /** Window is closed */ + CLOSE, + + /** Application is quit */ + QUIT, + + /** Window is reloaded */ + RELOAD, + + /** Other configuration loaded into window */ + LOAD } /** @@ -37,12 +52,6 @@ export interface ILifecycleService { */ willShutdown: boolean; - /** - * A flag indications if the application is in the process of quitting all windows. This will be - * set before the onWillShutdown event is fired and reverted to false afterwards. - */ - quitRequested: boolean; - /** * Fired before shutdown happens. Allows listeners to veto against the * shutdown. @@ -59,7 +68,6 @@ export interface ILifecycleService { export const NullLifecycleService: ILifecycleService = { _serviceBrand: null, willShutdown: false, - quitRequested: false, onWillShutdown: () => ({ dispose() { } }), onShutdown: () => ({ dispose() { } }) }; \ No newline at end of file diff --git a/src/vs/test/utils/servicesTestUtils.ts b/src/vs/test/utils/servicesTestUtils.ts index 98c911d1c1af523ef7e75bdd8269a40afb376dd0..8ea79fb7ff1e173f2dbfed466a21b59869986780 100644 --- a/src/vs/test/utils/servicesTestUtils.ts +++ b/src/vs/test/utils/servicesTestUtils.ts @@ -708,7 +708,6 @@ export class TestLifecycleService implements ILifecycleService { public _serviceBrand: any; public willShutdown: boolean; - public quitRequested: boolean; private _onWillShutdown = new Emitter(); private _onShutdown = new Emitter(); diff --git a/src/vs/workbench/services/backup/common/backup.ts b/src/vs/workbench/services/backup/common/backup.ts index 061e2eec6f8a490b0dff94e5f74b7bb6ea83e0da..2fcda4055cc82fa3d7639fa1ab927f67e86cee61 100644 --- a/src/vs/workbench/services/backup/common/backup.ts +++ b/src/vs/workbench/services/backup/common/backup.ts @@ -10,6 +10,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { TPromise } from 'vs/base/common/winjs.base'; import { ITextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textfiles'; import { IResolveContentOptions, IUpdateContentOptions } from 'vs/platform/files/common/files'; +import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; export const IBackupService = createDecorator('backupService'); export const IBackupFileService = createDecorator('backupFileService'); @@ -29,7 +30,7 @@ export interface IBackupService { _serviceBrand: any; isHotExitEnabled: boolean; - backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, quitRequested: boolean): TPromise; + backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, reason: ShutdownReason): TPromise; cleanupBackupsBeforeShutdown(): TPromise; } diff --git a/src/vs/workbench/services/backup/node/backupService.ts b/src/vs/workbench/services/backup/node/backupService.ts index c9ef22c8c081168be80148f66696518305c3dd14..7060c7317e4728e82cb2104dcee967124212a772 100644 --- a/src/vs/workbench/services/backup/node/backupService.ts +++ b/src/vs/workbench/services/backup/node/backupService.ts @@ -17,6 +17,7 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un import { TPromise } from 'vs/base/common/winjs.base'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; export class BackupService implements IBackupService { @@ -95,21 +96,46 @@ export class BackupService implements IBackupService { return !this.environmentService.isExtensionDevelopment && this.configuredHotExit && !!this.contextService.getWorkspace(); } - public backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, quitRequested: boolean): TPromise { + public backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, reason: ShutdownReason): TPromise { if (!this.isHotExitEnabled) { return TPromise.as({ didBackup: false }); } return this.windowsService.getWindowCount().then(windowCount => { + // When quit is requested skip the confirm callback and attempt to backup all workspaces. // When quit is not requested the confirm callback should be shown when the window being // closed is the only VS Code window open, except for on Mac where hot exit is only // ever activated when quit is requested. - if (!quitRequested && (windowCount > 1 || platform.isMacintosh)) { + + let doBackup: boolean; + switch (reason) { + case ShutdownReason.CLOSE: + if (windowCount > 1 || platform.isMacintosh) { + doBackup = false; // do not backup if a window is closed that does not cause quitting of the application + } else { + doBackup = true; // backup if last window is closed on win/linux where the application quits right after + } + break; + + case ShutdownReason.QUIT: + doBackup = true; // backup because next start we restore all backups + break; + + case ShutdownReason.RELOAD: + doBackup = true; // backup because after window reload, backups restore + break; + + case ShutdownReason.LOAD: + doBackup = false; // do not backup because we are switching contexts + break; + } + + if (!doBackup) { return TPromise.as({ didBackup: false }); } - // Backup and hot exit + // Backup return this.backupAll(dirtyToBackup, textFileEditorModelManager).then(() => { return { didBackup: true }; }); // we did backup }); } diff --git a/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts index 12f4ff4639e00c0e3cc3d8f64fd4f27cf4efb612..1af9edb8d78129263d2cdccbd51aafba54a9080a 100644 --- a/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts @@ -7,7 +7,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import Severity from 'vs/base/common/severity'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { ILifecycleService, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; +import { ILifecycleService, ShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; import { IMessageService } from 'vs/platform/message/common/message'; import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; import { ipcRenderer as ipc } from 'electron'; @@ -21,7 +21,6 @@ export class LifecycleService implements ILifecycleService { private _onShutdown = new Emitter(); private _willShutdown: boolean; - private _quitRequested: boolean; constructor( @IMessageService private messageService: IMessageService, @@ -34,10 +33,6 @@ export class LifecycleService implements ILifecycleService { return this._willShutdown; } - public get quitRequested(): boolean { - return this._quitRequested; - } - public get onWillShutdown(): Event { return this._onWillShutdown.event; } @@ -50,13 +45,11 @@ export class LifecycleService implements ILifecycleService { const windowId = this.windowService.getWindowId(); // Main side indicates that window is about to unload, check for vetos - ipc.on('vscode:beforeUnload', (event, reply: { okChannel: string, cancelChannel: string, quitRequested: boolean }) => { + ipc.on('vscode:beforeUnload', (event, reply: { okChannel: string, cancelChannel: string, reason: ShutdownReason }) => { this._willShutdown = true; - this._quitRequested = reply.quitRequested; // trigger onWillShutdown events and veto collecting - this.onBeforeUnload(reply.quitRequested).done(veto => { - this._quitRequested = false; + this.onBeforeUnload(reply.reason).done(veto => { if (veto) { this._willShutdown = false; // reset this flag since the shutdown has been vetoed! ipc.send(reply.cancelChannel, windowId); @@ -68,14 +61,14 @@ export class LifecycleService implements ILifecycleService { }); } - private onBeforeUnload(quitRequested: boolean): TPromise { + private onBeforeUnload(reason: ShutdownReason): TPromise { const vetos: (boolean | TPromise)[] = []; this._onWillShutdown.fire({ veto(value) { vetos.push(value); }, - quitRequested + reason }); if (vetos.length === 0) { diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 60f543348cfac2bc6a190cf69d553de1557ef50a..dc024c56582dd62fa835387386564090fc68579f 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -14,7 +14,7 @@ import objects = require('vs/base/common/objects'); import Event, { Emitter } from 'vs/base/common/event'; import { IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { ConfirmResult } from 'vs/workbench/common/editor'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IFileService, IResolveContentOptions, IFilesConfiguration, IFileOperationResult, FileOperationResult, AutoSaveConfiguration } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -101,7 +101,7 @@ export abstract class TextFileService implements ITextFileService { private registerListeners(): void { // Lifecycle - this.lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown(event.quitRequested))); + this.lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown(event.reason))); this.lifecycleService.onShutdown(this.dispose, this); // Configuration changes @@ -113,7 +113,7 @@ export abstract class TextFileService implements ITextFileService { this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorFocusChanged())); } - private beforeShutdown(quitRequested: boolean): boolean | TPromise { + private beforeShutdown(reason: ShutdownReason): boolean | TPromise { // Dirty files need treatment on shutdown const dirty = this.getDirty(); @@ -135,7 +135,7 @@ export abstract class TextFileService implements ITextFileService { // If hot exit is enabled, backup dirty files and allow to exit without confirmation if (this.backupService.isHotExitEnabled) { - return this.backupService.backupBeforeShutdown(dirty, this.models, quitRequested).then(result => { + return this.backupService.backupBeforeShutdown(dirty, this.models, reason).then(result => { if (result.didBackup) { return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful) } diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index ffd03a2c1402cccf8a1fd4e45aafe2e1133496b6..f66e6c674ed1d61fd4e494321f2e236ea4ab5223 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -6,7 +6,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as assert from 'assert'; -import { ILifecycleService, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; +import { ILifecycleService, ShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, onError, toResource } from 'vs/test/utils/servicesTestUtils'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; @@ -28,7 +28,7 @@ class ServiceAccessor { class ShutdownEventImpl implements ShutdownEvent { public value: boolean | TPromise; - public quitRequested: boolean = false; + public reason = ShutdownReason.CLOSE; veto(value: boolean | TPromise): void { this.value = value;