diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 9fcd36b1b6a19..6395150a01d3e 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -32,6 +32,7 @@ export interface IWindowCreationOptions { extensionDevelopmentPath?: string; allowFullscreen?: boolean; titleBarStyle?: 'native' | 'custom'; + vscodeWindowId?: string; } export enum WindowMode { @@ -70,6 +71,7 @@ export interface IPath { export interface IWindowConfiguration extends ParsedArgs { appRoot: string; execPath: string; + vscodeWindowId: string; userEnv: platform.IProcessEnvironment; @@ -137,6 +139,7 @@ export class VSCodeWindow implements IVSCodeWindow { private _lastFocusTime: number; private _readyState: ReadyState; private _extensionDevelopmentPath: string; + private _vscodeWindowId: string; private windowState: IWindowState; private currentWindowMode: WindowMode; @@ -196,6 +199,8 @@ export class VSCodeWindow implements IVSCodeWindow { } } + this._vscodeWindowId = config.vscodeWindowId ? config.vscodeWindowId : Date.now().toString(); + // Create the browser window. this._win = new BrowserWindow(options); this._id = this._win.id; @@ -260,6 +265,10 @@ export class VSCodeWindow implements IVSCodeWindow { return this._win; } + public get vscodeWindowId(): string { + return this._vscodeWindowId; + } + public focus(): void { if (!this._win) { return; diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 28b21b35e3852..9e4987a5c2c43 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -335,6 +335,7 @@ export class WindowsManager implements IWindowsMainService { let filesToOpen: IPath[] = []; let filesToDiff: IPath[] = []; let emptyToOpen = iPathsToOpen.filter(iPath => !iPath.workspacePath && !iPath.filePath); + let emptyToRestore = (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath) ? this.backupService.getEmptyWorkspaceBackupWindowIds() : []; let filesToCreate = iPathsToOpen.filter(iPath => !!iPath.filePath && iPath.createFilePath); // Diff mode needs special care @@ -385,7 +386,7 @@ export class WindowsManager implements IWindowsMainService { // Otherwise open instance with files else { - const configuration = this.toConfiguration(openConfig, null, filesToOpen, filesToCreate, filesToDiff); + const configuration = this.toConfiguration(openConfig, null, null, filesToOpen, filesToCreate, filesToDiff); const browserWindow = this.openInBrowserWindow(configuration, true /* new window */); usedWindows.push(browserWindow); @@ -427,7 +428,7 @@ export class WindowsManager implements IWindowsMainService { return; // ignore folders that are already open } - const configuration = this.toConfiguration(openConfig, folderToOpen, filesToOpen, filesToCreate, filesToDiff); + const configuration = this.toConfiguration(openConfig, null, folderToOpen, filesToOpen, filesToCreate, filesToDiff); const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse); usedWindows.push(browserWindow); @@ -441,7 +442,20 @@ export class WindowsManager implements IWindowsMainService { } // Handle empty - if (emptyToOpen.length > 0) { + if (emptyToRestore.length > 0) { + // TODO: There's an extra empty workspace opening when restoring an empty workspace (sometimes) + emptyToRestore.forEach(vscodeWindowId => { + const configuration = this.toConfiguration(openConfig); + configuration.vscodeWindowId = vscodeWindowId; + const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse); + usedWindows.push(browserWindow); + + openInNewWindow = true; // any other folders to open must open in new window then + }); + } + // TODO: Is this handling correct? + // Only open empty if no empty workspaces were restored + else if (emptyToOpen.length > 0) { emptyToOpen.forEach(() => { const configuration = this.toConfiguration(openConfig); const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse); @@ -614,12 +628,13 @@ export class WindowsManager implements IWindowsMainService { this.open({ cli: openConfig.cli, forceNewWindow: true, forceEmpty: openConfig.cli._.length === 0 }); } - private toConfiguration(config: IOpenConfiguration, workspacePath?: string, filesToOpen?: IPath[], filesToCreate?: IPath[], filesToDiff?: IPath[]): IWindowConfiguration { + private toConfiguration(config: IOpenConfiguration, vscodeWindowId?: string, workspacePath?: string, filesToOpen?: IPath[], filesToCreate?: IPath[], filesToDiff?: IPath[]): IWindowConfiguration { const configuration: IWindowConfiguration = mixin({}, config.cli); // inherit all properties from CLI configuration.appRoot = this.environmentService.appRoot; configuration.execPath = process.execPath; configuration.userEnv = this.getWindowUserEnv(config); configuration.isInitialStartup = config.initialStartup; + configuration.vscodeWindowId = vscodeWindowId; configuration.workspacePath = workspacePath; configuration.filesToOpen = filesToOpen; configuration.filesToCreate = filesToCreate; @@ -729,9 +744,16 @@ export class WindowsManager implements IWindowsMainService { state: this.getNewWindowState(configuration), extensionDevelopmentPath: configuration.extensionDevelopmentPath, allowFullscreen: this.lifecycleService.wasUpdated || (windowConfig && windowConfig.restoreFullscreen), - titleBarStyle: windowConfig ? windowConfig.titleBarStyle : void 0 + titleBarStyle: windowConfig ? windowConfig.titleBarStyle : void 0, + vscodeWindowId: configuration.vscodeWindowId }); + configuration.vscodeWindowId = vscodeWindow.vscodeWindowId; + if (!configuration.extensionDevelopmentPath) { + // TODO: Cover closing a folder/existing window case + this.backupService.pushEmptyWorkspaceBackupWindowIdSync(configuration.vscodeWindowId); + } + WindowsManager.WINDOWS.push(vscodeWindow); // Window Events diff --git a/src/vs/platform/backup/common/backup.ts b/src/vs/platform/backup/common/backup.ts index 3da562e5e67f1..05e676e4e4f24 100644 --- a/src/vs/platform/backup/common/backup.ts +++ b/src/vs/platform/backup/common/backup.ts @@ -8,6 +8,7 @@ import Uri from 'vs/base/common/uri'; export interface IBackupWorkspacesFormat { folderWorkspaces: string[]; + emptyWorkspaces: string[]; } export const IBackupMainService = createDecorator('backupService'); @@ -22,10 +23,16 @@ export interface IBackupMainService { */ getWorkspaceBackupPaths(): string[]; + // TODO: Doc + getEmptyWorkspaceBackupWindowIds(): string[]; + /** * Pushes workspace backup paths to be tracked for restoration. * * @param workspaces The workspaces to add. */ pushWorkspaceBackupPathsSync(workspaces: Uri[]): void; + + // TODO: Doc + pushEmptyWorkspaceBackupWindowIdSync(vscodeWindowId: string): void; } diff --git a/src/vs/platform/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts index 0234c2a8725e9..e117551572b4c 100644 --- a/src/vs/platform/backup/electron-main/backupMainService.ts +++ b/src/vs/platform/backup/electron-main/backupMainService.ts @@ -33,6 +33,10 @@ export class BackupMainService implements IBackupMainService { return this.backups.folderWorkspaces; } + public getEmptyWorkspaceBackupWindowIds(): string[] { + return this.backups.emptyWorkspaces; + } + public pushWorkspaceBackupPathsSync(workspaces: Uri[]): void { let needsSaving = false; workspaces.forEach(workspace => { @@ -47,6 +51,16 @@ export class BackupMainService implements IBackupMainService { } } + // TODO: Think of a less terrible name + // TODO: Test + // TODO: Merge with pushWorkspaceBackupPathsSync? + public pushEmptyWorkspaceBackupWindowIdSync(vscodeWindowId: string): void { + if (this.backups.emptyWorkspaces.indexOf(vscodeWindowId) === -1) { + this.backups.emptyWorkspaces.push(vscodeWindowId); + this.saveSync(); + } + } + protected removeWorkspaceBackupPathSync(workspace: Uri): void { if (!this.backups.folderWorkspaces) { return; @@ -59,6 +73,20 @@ export class BackupMainService implements IBackupMainService { this.saveSync(); } + // TODO: Test + // TODO: Merge with removeWorkspaceBackupPathSync? + private removeEmptyWorkspaceBackupWindowIdSync(vscodeWindowId: string): void { + if (!this.backups.emptyWorkspaces) { + return; + } + const index = this.backups.emptyWorkspaces.indexOf(vscodeWindowId); + if (index === -1) { + return; + } + this.backups.emptyWorkspaces.splice(index, 1); + this.saveSync(); + } + protected loadSync(): void { let backups: IBackupWorkspacesFormat; try { @@ -71,12 +99,20 @@ export class BackupMainService implements IBackupMainService { if (backups.folderWorkspaces) { const fws = backups.folderWorkspaces; if (!Array.isArray(fws) || fws.some(f => typeof f !== 'string')) { - backups = Object.create(null); + backups.folderWorkspaces = []; } + } else { + backups.folderWorkspaces = []; } - if (!backups.folderWorkspaces) { - backups.folderWorkspaces = []; + // Ensure emptyWorkspaces is a string[] + if (backups.emptyWorkspaces) { + const fws = backups.emptyWorkspaces; + if (!Array.isArray(fws) || fws.some(f => typeof f !== 'string')) { + backups.emptyWorkspaces = []; + } + } else { + backups.emptyWorkspaces = []; } this.backups = backups; @@ -86,20 +122,29 @@ export class BackupMainService implements IBackupMainService { } private validateBackupWorkspaces(backups: IBackupWorkspacesFormat): void { - const staleBackupWorkspaces: { workspacePath: string; backupPath: string; }[] = []; + // TODO: Tidy up, improve names, reduce duplication + const staleBackupWorkspaces: { workspaceIdentifier: string; backupPath: string; }[] = []; - const backupWorkspaces = backups.folderWorkspaces; - backupWorkspaces.forEach(workspacePath => { + backups.folderWorkspaces.forEach(workspacePath => { const backupPath = this.toBackupPath(workspacePath); if (!this.hasBackupsSync(backupPath)) { - staleBackupWorkspaces.push({ workspacePath, backupPath }); + staleBackupWorkspaces.push({ workspaceIdentifier: workspacePath, backupPath }); + } + }); + console.log('checking empty: ' + backups.emptyWorkspaces); + backups.emptyWorkspaces.forEach(vscodeWindowId => { + const backupPath = this.toEmptyWorkspaceBackupPath(vscodeWindowId); + console.log('backupPath: ' + backupPath); + if (!this.hasBackupsSync(backupPath)) { + staleBackupWorkspaces.push({ workspaceIdentifier: vscodeWindowId, backupPath }); } }); staleBackupWorkspaces.forEach(staleBackupWorkspace => { - const {backupPath, workspacePath} = staleBackupWorkspace; + const {backupPath, workspaceIdentifier} = staleBackupWorkspace; extfs.delSync(backupPath); - this.removeWorkspaceBackupPathSync(Uri.file(workspacePath)); + this.removeWorkspaceBackupPathSync(Uri.file(workspaceIdentifier)); + this.removeEmptyWorkspaceBackupWindowIdSync(workspaceIdentifier); }); } @@ -139,4 +184,8 @@ export class BackupMainService implements IBackupMainService { return path.join(this.backupHome, workspaceHash); } + + protected toEmptyWorkspaceBackupPath(vscodeWindowId: string): string { + return path.join(this.backupHome, vscodeWindowId); + } } diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index 2bcd927d2f081..810d6dd7a0d13 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -120,7 +120,7 @@ suite('BackupMainService', () => { }); test('removeWorkspaceBackupPath should fail gracefully when removing a path that doesn\'t exist', done => { - const workspacesJson: IBackupWorkspacesFormat = { folderWorkspaces: [fooFile.fsPath] }; + const workspacesJson: IBackupWorkspacesFormat = { folderWorkspaces: [fooFile.fsPath], emptyWorkspaces: [] }; pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)).then(() => { service.removeWorkspaceBackupPathSync(barFile); pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => { diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 320f6ff9bd0e9..eb2b88a291997 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -42,6 +42,7 @@ export interface IEnvironmentService { execPath: string; appRoot: string; + vscodeWindowId: string; userHome: string; userProductHome: string; diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index dbef908429011..c6b54638cec81 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -61,6 +61,9 @@ export class EnvironmentService implements IEnvironmentService { get execPath(): string { return this._execPath; } + @memoize + get vscodeWindowId(): string { return this._vscodeWindowId; } + @memoize get userHome(): string { return os.homedir(); } @@ -114,7 +117,7 @@ export class EnvironmentService implements IEnvironmentService { @memoize get sharedIPCHandle(): string { return `${getIPCHandlePrefix()}-${pkg.version}-shared${getIPCHandleSuffix()}`; } - constructor(private _args: ParsedArgs, private _execPath: string) { } + constructor(private _args: ParsedArgs, private _execPath: string, private _vscodeWindowId: string) { } } export function parseExtensionHostPort(args: ParsedArgs, isBuild: boolean): { port: number; break: boolean; } { diff --git a/src/vs/test/utils/servicesTestUtils.ts b/src/vs/test/utils/servicesTestUtils.ts index 57006f5f7afea..f0a3880fdb229 100644 --- a/src/vs/test/utils/servicesTestUtils.ts +++ b/src/vs/test/utils/servicesTestUtils.ts @@ -59,7 +59,7 @@ export function createFileInput(instantiationService: IInstantiationService, res return instantiationService.createInstance(FileEditorInput, resource, void 0); } -export const TestEnvironmentService = new EnvironmentService(parseArgs(process.argv), process.execPath); +export const TestEnvironmentService = new EnvironmentService(parseArgs(process.argv), process.execPath, ''); export class TestContextService implements IWorkspaceContextService { public _serviceBrand: any; diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 6efb494a1f664..23e55d22c465f 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -38,6 +38,7 @@ const timers = (window).MonacoEnvironment.timers; export interface IWindowConfiguration extends ParsedArgs, IOpenFileRequest { appRoot: string; execPath: string; + vscodeWindowId: string; userEnv: any; /* vs/code/electron-main/env/IProcessEnvironment*/ @@ -48,7 +49,7 @@ export interface IWindowConfiguration extends ParsedArgs, IOpenFileRequest { } export function startup(configuration: IWindowConfiguration): TPromise { - + console.log('vscodeWindowId: ' + configuration.vscodeWindowId); // Ensure others can listen to zoom level changes browser.setZoomFactor(webFrame.getZoomFactor()); browser.setZoomLevel(webFrame.getZoomLevel()); @@ -132,7 +133,7 @@ function getWorkspace(workspacePath: string): TPromise { function openWorkbench(environment: IWindowConfiguration, workspace: IWorkspace, options: IOptions): TPromise { const eventService = new EventService(); - const environmentService = new EnvironmentService(environment, environment.execPath); + const environmentService = new EnvironmentService(environment, environment.execPath, environment.vscodeWindowId); const contextService = new WorkspaceContextService(workspace); const configurationService = new WorkspaceConfigurationService(contextService, eventService, environmentService); diff --git a/src/vs/workbench/services/backup/node/backupFileService.ts b/src/vs/workbench/services/backup/node/backupFileService.ts index 3357fcf43e07c..ab093b5a939ca 100644 --- a/src/vs/workbench/services/backup/node/backupFileService.ts +++ b/src/vs/workbench/services/backup/node/backupFileService.ts @@ -102,13 +102,16 @@ export class BackupFileService implements IBackupFileService { if (this.currentWorkspace) { this.backupWorkspacePath = path.join(this.backupHome, this.hashPath(this.currentWorkspace)); + } else { + this.backupWorkspacePath = path.join(this.backupHome, this.environmentService.vscodeWindowId); } this.ready = this.init(); } private get backupEnabled(): boolean { - return this.currentWorkspace && !this.environmentService.isExtensionDevelopment; // Hot exit is disabled for empty workspaces and when doing extension development + // Hot exit is disabled when doing extension development + return !this.environmentService.isExtensionDevelopment; } private init(): TPromise { diff --git a/src/vs/workbench/services/backup/node/backupService.ts b/src/vs/workbench/services/backup/node/backupService.ts index cde1799b3c55e..39f4130f8a077 100644 --- a/src/vs/workbench/services/backup/node/backupService.ts +++ b/src/vs/workbench/services/backup/node/backupService.ts @@ -49,8 +49,7 @@ export class BackupService implements IBackupService { } private onConfigurationChange(configuration: IFilesConfiguration): void { - // Hot exit is disabled for empty workspaces - this.configuredHotExit = this.contextService.getWorkspace() && configuration && configuration.files && configuration.files.hotExit; + this.configuredHotExit = configuration && configuration.files && configuration.files.hotExit; } /** @@ -91,9 +90,7 @@ export class BackupService implements IBackupService { } public get isHotExitEnabled(): boolean { - // If hot exit is enabled then save the dirty files in the workspace and then exit - // Hot exit is currently disabled for empty workspaces (#13733). - return !this.environmentService.isExtensionDevelopment && this.configuredHotExit && !!this.contextService.getWorkspace(); + return !this.environmentService.isExtensionDevelopment && this.configuredHotExit; } public backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, reason: ShutdownReason): TPromise { @@ -141,11 +138,6 @@ export class BackupService implements IBackupService { return TPromise.as(void 0); } - const workspace = this.contextService.getWorkspace(); - if (!workspace) { - return TPromise.as(void 0); // no backups to cleanup - } - return this.backupFileService.discardAllWorkspaceBackups(); }