Skip to content

Commit

Permalink
Rough initial empty workspace hot exit impl
Browse files Browse the repository at this point in the history
Part of #13733
  • Loading branch information
Tyriar committed Dec 1, 2016
1 parent aa099fe commit 5517e6f
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 30 deletions.
9 changes: 9 additions & 0 deletions src/vs/code/electron-main/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface IWindowCreationOptions {
extensionDevelopmentPath?: string;
allowFullscreen?: boolean;
titleBarStyle?: 'native' | 'custom';
vscodeWindowId?: string;
}

export enum WindowMode {
Expand Down Expand Up @@ -70,6 +71,7 @@ export interface IPath {
export interface IWindowConfiguration extends ParsedArgs {
appRoot: string;
execPath: string;
vscodeWindowId: string;

userEnv: platform.IProcessEnvironment;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
32 changes: 27 additions & 5 deletions src/vs/code/electron-main/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/vs/platform/backup/common/backup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Uri from 'vs/base/common/uri';

export interface IBackupWorkspacesFormat {
folderWorkspaces: string[];
emptyWorkspaces: string[];
}

export const IBackupMainService = createDecorator<IBackupMainService>('backupService');
Expand All @@ -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;
}
67 changes: 58 additions & 9 deletions src/vs/platform/backup/electron-main/backupMainService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand All @@ -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;
Expand All @@ -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 {
Expand All @@ -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;
Expand All @@ -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);
});
}

Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/environment/common/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface IEnvironmentService {

execPath: string;
appRoot: string;
vscodeWindowId: string;

userHome: string;
userProductHome: string;
Expand Down
5 changes: 4 additions & 1 deletion src/vs/platform/environment/node/environmentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }

Expand Down Expand Up @@ -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; } {
Expand Down
2 changes: 1 addition & 1 deletion src/vs/test/utils/servicesTestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions src/vs/workbench/electron-browser/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const timers = (<any>window).MonacoEnvironment.timers;
export interface IWindowConfiguration extends ParsedArgs, IOpenFileRequest {
appRoot: string;
execPath: string;
vscodeWindowId: string;

userEnv: any; /* vs/code/electron-main/env/IProcessEnvironment*/

Expand All @@ -48,7 +49,7 @@ export interface IWindowConfiguration extends ParsedArgs, IOpenFileRequest {
}

export function startup(configuration: IWindowConfiguration): TPromise<void> {

console.log('vscodeWindowId: ' + configuration.vscodeWindowId);
// Ensure others can listen to zoom level changes
browser.setZoomFactor(webFrame.getZoomFactor());
browser.setZoomLevel(webFrame.getZoomLevel());
Expand Down Expand Up @@ -132,7 +133,7 @@ function getWorkspace(workspacePath: string): TPromise<IWorkspace> {

function openWorkbench(environment: IWindowConfiguration, workspace: IWorkspace, options: IOptions): TPromise<void> {
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);

Expand Down
5 changes: 4 additions & 1 deletion src/vs/workbench/services/backup/node/backupFileService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<IBackupFilesModel> {
Expand Down
12 changes: 2 additions & 10 deletions src/vs/workbench/services/backup/node/backupService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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<IBackupResult> {
Expand Down Expand Up @@ -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();
}

Expand Down

0 comments on commit 5517e6f

Please sign in to comment.