提交 5aeba8bf 编写于 作者: B Benjamin Pasero 提交者: GitHub

Merge pull request #16198 from Microsoft/ben/hot-exit-reload

introduce shutdown reason (fixes #15509)
...@@ -16,6 +16,13 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' ...@@ -16,6 +16,13 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService'); export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
export enum UnloadReason {
CLOSE,
QUIT,
RELOAD,
LOAD
}
export interface ILifecycleService { export interface ILifecycleService {
_serviceBrand: any; _serviceBrand: any;
...@@ -33,7 +40,7 @@ export interface ILifecycleService { ...@@ -33,7 +40,7 @@ export interface ILifecycleService {
ready(): void; ready(): void;
registerWindow(vscodeWindow: IVSCodeWindow): void; registerWindow(vscodeWindow: IVSCodeWindow): void;
unload(vscodeWindow: IVSCodeWindow): TPromise<boolean /* veto */>; unload(vscodeWindow: IVSCodeWindow, reason: UnloadReason): TPromise<boolean /* veto */>;
quit(fromUpdate?: boolean): TPromise<boolean /* veto */>; quit(fromUpdate?: boolean): TPromise<boolean /* veto */>;
} }
...@@ -126,7 +133,7 @@ export class LifecycleService implements ILifecycleService { ...@@ -126,7 +133,7 @@ export class LifecycleService implements ILifecycleService {
// Otherwise prevent unload and handle it from window // Otherwise prevent unload and handle it from window
e.preventDefault(); e.preventDefault();
this.unload(vscodeWindow).done(veto => { this.unload(vscodeWindow, UnloadReason.CLOSE).done(veto => {
if (!veto) { if (!veto) {
this.windowToCloseRequest[windowId] = true; this.windowToCloseRequest[windowId] = true;
vscodeWindow.win.close(); vscodeWindow.win.close();
...@@ -138,7 +145,7 @@ export class LifecycleService implements ILifecycleService { ...@@ -138,7 +145,7 @@ export class LifecycleService implements ILifecycleService {
}); });
} }
public unload(vscodeWindow: IVSCodeWindow): TPromise<boolean /* veto */> { public unload(vscodeWindow: IVSCodeWindow, reason: UnloadReason): TPromise<boolean /* veto */> {
// Always allow to unload a window that is not yet ready // Always allow to unload a window that is not yet ready
if (vscodeWindow.readyState !== ReadyState.READY) { if (vscodeWindow.readyState !== ReadyState.READY) {
...@@ -149,14 +156,14 @@ export class LifecycleService implements ILifecycleService { ...@@ -149,14 +156,14 @@ export class LifecycleService implements ILifecycleService {
return new TPromise<boolean>((c) => { return new TPromise<boolean>((c) => {
const oneTimeEventToken = this.oneTimeListenerTokenGenerator++; const oneTimeEventToken = this.oneTimeListenerTokenGenerator++;
const oneTimeOkEvent = 'vscode:ok' + oneTimeEventToken; const okChannel = `vscode:ok${oneTimeEventToken}`;
const oneTimeCancelEvent = 'vscode:cancel' + oneTimeEventToken; const cancelChannel = `vscode:cancel${oneTimeEventToken}`;
ipc.once(oneTimeOkEvent, () => { ipc.once(okChannel, () => {
c(false); // no veto c(false); // no veto
}); });
ipc.once(oneTimeCancelEvent, () => { ipc.once(cancelChannel, () => {
// Any cancellation also cancels a pending quit if present // Any cancellation also cancels a pending quit if present
if (this.pendingQuitPromiseComplete) { if (this.pendingQuitPromiseComplete) {
...@@ -168,7 +175,7 @@ export class LifecycleService implements ILifecycleService { ...@@ -168,7 +175,7 @@ export class LifecycleService implements ILifecycleService {
c(true); // veto 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 });
}); });
} }
......
...@@ -20,7 +20,7 @@ import { IStorageService } from 'vs/code/electron-main/storage'; ...@@ -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 { 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 { ipcMain as ipc, app, screen, BrowserWindow, dialog } from 'electron';
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/electron-main/paths'; 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 { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/code/electron-main/log'; import { ILogService } from 'vs/code/electron-main/log';
import { getPathLabel } from 'vs/base/common/labels'; import { getPathLabel } from 'vs/base/common/labels';
...@@ -273,7 +273,7 @@ export class WindowsManager implements IWindowsMainService { ...@@ -273,7 +273,7 @@ export class WindowsManager implements IWindowsMainService {
public reload(win: VSCodeWindow, cli?: ParsedArgs): void { public reload(win: VSCodeWindow, cli?: ParsedArgs): void {
// Only reload when the window has not vetoed this // 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) { if (!veto) {
win.reload(cli); win.reload(cli);
} }
...@@ -762,7 +762,7 @@ export class WindowsManager implements IWindowsMainService { ...@@ -762,7 +762,7 @@ export class WindowsManager implements IWindowsMainService {
} }
// Only load when the window has not vetoed this // 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) { if (!veto) {
// Load it // Load it
......
...@@ -20,7 +20,22 @@ export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleSe ...@@ -20,7 +20,22 @@ export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleSe
*/ */
export interface ShutdownEvent { export interface ShutdownEvent {
veto(value: boolean | TPromise<boolean>): void; veto(value: boolean | TPromise<boolean>): 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 { ...@@ -37,12 +52,6 @@ export interface ILifecycleService {
*/ */
willShutdown: boolean; 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 * Fired before shutdown happens. Allows listeners to veto against the
* shutdown. * shutdown.
...@@ -59,7 +68,6 @@ export interface ILifecycleService { ...@@ -59,7 +68,6 @@ export interface ILifecycleService {
export const NullLifecycleService: ILifecycleService = { export const NullLifecycleService: ILifecycleService = {
_serviceBrand: null, _serviceBrand: null,
willShutdown: false, willShutdown: false,
quitRequested: false,
onWillShutdown: () => ({ dispose() { } }), onWillShutdown: () => ({ dispose() { } }),
onShutdown: () => ({ dispose() { } }) onShutdown: () => ({ dispose() { } })
}; };
\ No newline at end of file
...@@ -708,7 +708,6 @@ export class TestLifecycleService implements ILifecycleService { ...@@ -708,7 +708,6 @@ export class TestLifecycleService implements ILifecycleService {
public _serviceBrand: any; public _serviceBrand: any;
public willShutdown: boolean; public willShutdown: boolean;
public quitRequested: boolean;
private _onWillShutdown = new Emitter<ShutdownEvent>(); private _onWillShutdown = new Emitter<ShutdownEvent>();
private _onShutdown = new Emitter<void>(); private _onShutdown = new Emitter<void>();
......
...@@ -10,6 +10,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' ...@@ -10,6 +10,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { ITextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textfiles';
import { IResolveContentOptions, IUpdateContentOptions } from 'vs/platform/files/common/files'; import { IResolveContentOptions, IUpdateContentOptions } from 'vs/platform/files/common/files';
import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
export const IBackupService = createDecorator<IBackupService>('backupService'); export const IBackupService = createDecorator<IBackupService>('backupService');
export const IBackupFileService = createDecorator<IBackupFileService>('backupFileService'); export const IBackupFileService = createDecorator<IBackupFileService>('backupFileService');
...@@ -29,7 +30,7 @@ export interface IBackupService { ...@@ -29,7 +30,7 @@ export interface IBackupService {
_serviceBrand: any; _serviceBrand: any;
isHotExitEnabled: boolean; isHotExitEnabled: boolean;
backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, quitRequested: boolean): TPromise<IBackupResult>; backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, reason: ShutdownReason): TPromise<IBackupResult>;
cleanupBackupsBeforeShutdown(): TPromise<void>; cleanupBackupsBeforeShutdown(): TPromise<void>;
} }
......
...@@ -17,6 +17,7 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un ...@@ -17,6 +17,7 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IWindowsService } from 'vs/platform/windows/common/windows';
import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
export class BackupService implements IBackupService { export class BackupService implements IBackupService {
...@@ -95,21 +96,46 @@ export class BackupService implements IBackupService { ...@@ -95,21 +96,46 @@ export class BackupService implements IBackupService {
return !this.environmentService.isExtensionDevelopment && this.configuredHotExit && !!this.contextService.getWorkspace(); return !this.environmentService.isExtensionDevelopment && this.configuredHotExit && !!this.contextService.getWorkspace();
} }
public backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, quitRequested: boolean): TPromise<IBackupResult> { public backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, reason: ShutdownReason): TPromise<IBackupResult> {
if (!this.isHotExitEnabled) { if (!this.isHotExitEnabled) {
return TPromise.as({ didBackup: false }); return TPromise.as({ didBackup: false });
} }
return this.windowsService.getWindowCount().then(windowCount => { return this.windowsService.getWindowCount().then(windowCount => {
// When quit is requested skip the confirm callback and attempt to backup all workspaces. // 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 // 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 // closed is the only VS Code window open, except for on Mac where hot exit is only
// ever activated when quit is requested. // 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 }); return TPromise.as({ didBackup: false });
} }
// Backup and hot exit // Backup
return this.backupAll(dirtyToBackup, textFileEditorModelManager).then(() => { return { didBackup: true }; }); // we did backup return this.backupAll(dirtyToBackup, textFileEditorModelManager).then(() => { return { didBackup: true }; }); // we did backup
}); });
} }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import { toErrorMessage } from 'vs/base/common/errorMessage'; 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 { IMessageService } from 'vs/platform/message/common/message';
import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService';
import { ipcRenderer as ipc } from 'electron'; import { ipcRenderer as ipc } from 'electron';
...@@ -21,7 +21,6 @@ export class LifecycleService implements ILifecycleService { ...@@ -21,7 +21,6 @@ export class LifecycleService implements ILifecycleService {
private _onShutdown = new Emitter<void>(); private _onShutdown = new Emitter<void>();
private _willShutdown: boolean; private _willShutdown: boolean;
private _quitRequested: boolean;
constructor( constructor(
@IMessageService private messageService: IMessageService, @IMessageService private messageService: IMessageService,
...@@ -34,10 +33,6 @@ export class LifecycleService implements ILifecycleService { ...@@ -34,10 +33,6 @@ export class LifecycleService implements ILifecycleService {
return this._willShutdown; return this._willShutdown;
} }
public get quitRequested(): boolean {
return this._quitRequested;
}
public get onWillShutdown(): Event<ShutdownEvent> { public get onWillShutdown(): Event<ShutdownEvent> {
return this._onWillShutdown.event; return this._onWillShutdown.event;
} }
...@@ -50,13 +45,11 @@ export class LifecycleService implements ILifecycleService { ...@@ -50,13 +45,11 @@ export class LifecycleService implements ILifecycleService {
const windowId = this.windowService.getWindowId(); const windowId = this.windowService.getWindowId();
// Main side indicates that window is about to unload, check for vetos // 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._willShutdown = true;
this._quitRequested = reply.quitRequested;
// trigger onWillShutdown events and veto collecting // trigger onWillShutdown events and veto collecting
this.onBeforeUnload(reply.quitRequested).done(veto => { this.onBeforeUnload(reply.reason).done(veto => {
this._quitRequested = false;
if (veto) { if (veto) {
this._willShutdown = false; // reset this flag since the shutdown has been vetoed! this._willShutdown = false; // reset this flag since the shutdown has been vetoed!
ipc.send(reply.cancelChannel, windowId); ipc.send(reply.cancelChannel, windowId);
...@@ -68,14 +61,14 @@ export class LifecycleService implements ILifecycleService { ...@@ -68,14 +61,14 @@ export class LifecycleService implements ILifecycleService {
}); });
} }
private onBeforeUnload(quitRequested: boolean): TPromise<boolean> { private onBeforeUnload(reason: ShutdownReason): TPromise<boolean> {
const vetos: (boolean | TPromise<boolean>)[] = []; const vetos: (boolean | TPromise<boolean>)[] = [];
this._onWillShutdown.fire({ this._onWillShutdown.fire({
veto(value) { veto(value) {
vetos.push(value); vetos.push(value);
}, },
quitRequested reason
}); });
if (vetos.length === 0) { if (vetos.length === 0) {
......
...@@ -14,7 +14,7 @@ import objects = require('vs/base/common/objects'); ...@@ -14,7 +14,7 @@ import objects = require('vs/base/common/objects');
import Event, { Emitter } from 'vs/base/common/event'; 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 { 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 { 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 { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IFileService, IResolveContentOptions, IFilesConfiguration, IFileOperationResult, FileOperationResult, AutoSaveConfiguration } from 'vs/platform/files/common/files'; import { IFileService, IResolveContentOptions, IFilesConfiguration, IFileOperationResult, FileOperationResult, AutoSaveConfiguration } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
...@@ -101,7 +101,7 @@ export abstract class TextFileService implements ITextFileService { ...@@ -101,7 +101,7 @@ export abstract class TextFileService implements ITextFileService {
private registerListeners(): void { private registerListeners(): void {
// Lifecycle // 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); this.lifecycleService.onShutdown(this.dispose, this);
// Configuration changes // Configuration changes
...@@ -113,7 +113,7 @@ export abstract class TextFileService implements ITextFileService { ...@@ -113,7 +113,7 @@ export abstract class TextFileService implements ITextFileService {
this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorFocusChanged())); this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorFocusChanged()));
} }
private beforeShutdown(quitRequested: boolean): boolean | TPromise<boolean> { private beforeShutdown(reason: ShutdownReason): boolean | TPromise<boolean> {
// Dirty files need treatment on shutdown // Dirty files need treatment on shutdown
const dirty = this.getDirty(); const dirty = this.getDirty();
...@@ -135,7 +135,7 @@ export abstract class TextFileService implements ITextFileService { ...@@ -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 hot exit is enabled, backup dirty files and allow to exit without confirmation
if (this.backupService.isHotExitEnabled) { 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) { if (result.didBackup) {
return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful) return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful)
} }
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import * as assert from 'assert'; 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 { workbenchInstantiationService, TestLifecycleService, TestTextFileService, onError, toResource } from 'vs/test/utils/servicesTestUtils';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
...@@ -28,7 +28,7 @@ class ServiceAccessor { ...@@ -28,7 +28,7 @@ class ServiceAccessor {
class ShutdownEventImpl implements ShutdownEvent { class ShutdownEventImpl implements ShutdownEvent {
public value: boolean | TPromise<boolean>; public value: boolean | TPromise<boolean>;
public quitRequested: boolean = false; public reason = ShutdownReason.CLOSE;
veto(value: boolean | TPromise<boolean>): void { veto(value: boolean | TPromise<boolean>): void {
this.value = value; this.value = value;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册