diff --git a/src/vs/platform/backup/common/backup.ts b/src/vs/platform/backup/common/backup.ts index 975f50e2196b20fcd0d1d60b564c0f2543cd5bdd..1a86e74b51c1f4b4ba7c58d39c9fb1e36e0f4f77 100644 --- a/src/vs/platform/backup/common/backup.ts +++ b/src/vs/platform/backup/common/backup.ts @@ -5,7 +5,7 @@ 'use strict'; -import {createDecorator} from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import Uri from 'vs/base/common/uri'; export const IBackupService = createDecorator('backupService'); @@ -20,5 +20,6 @@ export interface IBackupService { registerBackupFile(resource: Uri): void; deregisterBackupFile(resource: Uri): void; getBackupFiles(workspace: string): string[]; + getBackupUntitledFiles(workspace: string): string[]; getBackupResource(resource: Uri): Uri; } diff --git a/src/vs/workbench/common/backup.ts b/src/vs/workbench/common/backup.ts index 9a4f3cc391fa66e28b7e5c8008d082acc115fb97..c619b810ce7969f83403b70353e299fe10799c0c 100644 --- a/src/vs/workbench/common/backup.ts +++ b/src/vs/workbench/common/backup.ts @@ -62,9 +62,22 @@ export class BackupService implements IBackupService { return this.fileContent.folderWorkspaces[workspace] || []; } + public getBackupUntitledFiles(workspace: string): string[] { + const workspaceHash = crypto.createHash('md5').update(this.workspaceResource.fsPath).digest('hex'); + const untitledDir = path.join(this.environmentService.userDataPath, 'Backups', workspaceHash, 'untitled'); + try { + const untitledFiles = fs.readdirSync(untitledDir).map(file => path.join(untitledDir, file)); + console.log('untitledFiles', untitledFiles); + return untitledFiles; + } catch (ex) { + console.log('untitled backups do not exist'); + return []; + } + } + public getBackupResource(resource: Uri): Uri { - let workspaceHash = crypto.createHash('md5').update(this.workspaceResource.fsPath).digest('hex'); + const workspaceHash = crypto.createHash('md5').update(this.workspaceResource.fsPath).digest('hex'); const backupName = crypto.createHash('md5').update(resource.fsPath).digest('hex'); const backupPath = path.join(this.environmentService.userDataPath, 'Backups', workspaceHash, resource.scheme, backupName); console.log('getBackupResource ' + Uri.file(backupPath)); diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledEditorInput.ts index 7761d5135a509bbf3fd55e9a2713659c5a6e54ab..f8a88eb1e91bf543a6d154a4913a45799cd31530 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledEditorInput.ts @@ -19,6 +19,9 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import Event, { Emitter } from 'vs/base/common/event'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +// TODO: This file cannot depend on native node modules +import fs = require('fs'); + /** * An editor input to be used for untitled text buffers. */ @@ -28,6 +31,7 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { public static SCHEMA: string = 'untitled'; private resource: URI; + private restoreResource: URI; private hasAssociatedFilePath: boolean; private modeId: string; private cachedModel: UntitledEditorModel; @@ -46,7 +50,7 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { @ITextFileService private textFileService: ITextFileService ) { super(); - + console.log('UntitledEditorInput constructor', resource, hasAssociatedFilePath, modeId); this.resource = resource; this.hasAssociatedFilePath = hasAssociatedFilePath; this.modeId = modeId; @@ -66,6 +70,10 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { return this.resource; } + public setRestoreResource(resource: URI): void { + this.restoreResource = resource; + } + public getName(): string { return this.hasAssociatedFilePath ? paths.basename(this.resource.fsPath) : this.resource.fsPath; } @@ -140,7 +148,11 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { } private createModel(): UntitledEditorModel { - const content = ''; + let content = ''; + if (this.restoreResource) { + // TODO: This loading should probably go through fileService, fs cannot be a dependency in common/ + content = fs.readFileSync(this.restoreResource.fsPath, 'utf8'); + } const model = this.instantiationService.createInstance(UntitledEditorModel, content, this.modeId, this.resource, this.hasAssociatedFilePath); // re-emit some events from the model diff --git a/src/vs/workbench/common/editor/untitledEditorModel.ts b/src/vs/workbench/common/editor/untitledEditorModel.ts index 9d1d45181f94d4d6ffbf86a14fc85e223268c33f..168e5a93ea2e5d164e5cbec09b5dec4a34799a44 100644 --- a/src/vs/workbench/common/editor/untitledEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledEditorModel.ts @@ -46,7 +46,7 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS super(value, modeId, resource, modeService, modelService); this.hasAssociatedFilePath = hasAssociatedFilePath; - this.dirty = hasAssociatedFilePath; // untitled associated to file path are dirty right away + this.dirty = hasAssociatedFilePath || value !== ''; // untitled associated to file path are dirty right away this._onDidChangeDirty = new Emitter(); this._onDidChangeEncoding = new Emitter(); diff --git a/src/vs/workbench/common/options.ts b/src/vs/workbench/common/options.ts index a805f1a6523849f927ca51f4c4e692ba47a24836..ce341d575d3613d246f569827e3e0d91474fca2e 100644 --- a/src/vs/workbench/common/options.ts +++ b/src/vs/workbench/common/options.ts @@ -24,4 +24,5 @@ export interface IOptions { filesToDiff?: IResourceInput[]; filesToRestore?: IResourceInput[]; + untitledFilesToRestore?: IResourceInput[]; } \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 745977b17ecb0a3bf574d0bd2dffd80af363e0f2..6d92c0003560851f29cc7adb061ddeeaa7c380da 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -176,8 +176,16 @@ export class Workbench implements IPartService { options.filesToRestore = this.backupService.getBackupFiles(workspace.resource.fsPath).map(filePath => { return { resource: Uri.file(filePath), options: { pinned: true } }; }); + options.untitledFilesToRestore = this.backupService.getBackupUntitledFiles(workspace.resource.fsPath).map(untitledFilePath => { + return { resource: Uri.from({ path: untitledFilePath, scheme: 'untitled' }), options: { pinned: true } }; + }); - this.hasFilesToCreateOpenOrDiff = (options.filesToCreate && options.filesToCreate.length > 0) || (options.filesToOpen && options.filesToOpen.length > 0) || (options.filesToDiff && options.filesToDiff.length > 0) || (options.filesToRestore.length > 0); + this.hasFilesToCreateOpenOrDiff = + (options.filesToCreate && options.filesToCreate.length > 0) || + (options.filesToOpen && options.filesToOpen.length > 0) || + (options.filesToDiff && options.filesToDiff.length > 0) || + (options.filesToRestore.length > 0) || + (options.untitledFilesToRestore.length > 0); this.toDispose = []; this.toShutdown = []; @@ -305,6 +313,7 @@ export class Workbench implements IPartService { const filesToCreate = wbopt.filesToCreate || []; const filesToOpen = wbopt.filesToOpen || []; const filesToRestore = wbopt.filesToRestore || []; + const untitledFilesToRestore = wbopt.untitledFilesToRestore || []; const filesToDiff = wbopt.filesToDiff; // Files to diff is exclusive @@ -323,9 +332,14 @@ export class Workbench implements IPartService { inputs.push(...filesToCreate.map(resourceInput => this.untitledEditorService.createOrGet(resourceInput.resource))); options.push(...filesToCreate.map(r => null)); // fill empty options for files to create because we dont have options there + // Files to restore + inputs.push(...untitledFilesToRestore.map(resourceInput => this.untitledEditorService.createOrGet(null, null, resourceInput.resource))); + options.push(...untitledFilesToRestore.map(r => null)); // fill empty options for files to create because we dont have options there + // Files to open let filesToOpenInputPromise = filesToOpen.map(resourceInput => this.editorService.createInput(resourceInput)); let filesToRestoreInputPromise = filesToRestore.map(resourceInput => this.editorService.createInput(resourceInput, true)); + return TPromise.join(filesToOpenInputPromise.concat(filesToRestoreInputPromise)).then((inputsToOpen) => { inputs.push(...inputsToOpen); options.push(...filesToOpen.concat(filesToRestore).map(resourceInput => TextEditorOptions.from(resourceInput))); diff --git a/src/vs/workbench/services/files/node/fileService.ts b/src/vs/workbench/services/files/node/fileService.ts index 2946d843707e9e8ac17f10b13cbe2da8f790d5ca..3cb3e807b17ad5f7f63c91fef3de158b9e77f5a9 100644 --- a/src/vs/workbench/services/files/node/fileService.ts +++ b/src/vs/workbench/services/files/node/fileService.ts @@ -459,10 +459,8 @@ export class FileService implements IFileService { public backupFile(resource: uri, content: string): TPromise { if (resource.scheme === 'file') { - // TODO: Persist hash -> file map on disk (json file?) - + this.backupService.registerBackupFile(resource); } - this.backupService.registerBackupFile(resource); const backupResource = this.getBackupPath(resource); console.log(`Backing up to ${backupResource.fsPath}`); return this.updateContent(backupResource, content); diff --git a/src/vs/workbench/services/untitled/common/untitledEditorService.ts b/src/vs/workbench/services/untitled/common/untitledEditorService.ts index e064aa9da5f56f49a0079655a76d73a36b6d0850..c533d90ef9664874a7891495875349e2e5e6b315 100644 --- a/src/vs/workbench/services/untitled/common/untitledEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledEditorService.ts @@ -6,6 +6,7 @@ import URI from 'vs/base/common/uri'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IBackupService } from 'vs/platform/backup/common/backup'; import arrays = require('vs/base/common/arrays'); import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import Event, { Emitter, once } from 'vs/base/common/event'; @@ -58,7 +59,7 @@ export interface IUntitledEditorService { * It is valid to pass in a file resource. In that case the path will be used as identifier. * The use case is to be able to create a new file with a specific path with VSCode. */ - createOrGet(resource?: URI, modeId?: string): UntitledEditorInput; + createOrGet(resource?: URI, modeId?: string, restoreResource?: URI): UntitledEditorInput; /** * A check to find out if a untitled resource has a file path associated or not. @@ -76,7 +77,10 @@ export class UntitledEditorService implements IUntitledEditorService { private _onDidChangeDirty: Emitter; private _onDidChangeEncoding: Emitter; - constructor( @IInstantiationService private instantiationService: IInstantiationService) { + constructor( + @IBackupService private backupService: IBackupService, + @IInstantiationService private instantiationService: IInstantiationService + ) { this._onDidChangeDirty = new Emitter(); this._onDidChangeEncoding = new Emitter(); } @@ -130,7 +134,7 @@ export class UntitledEditorService implements IUntitledEditorService { .map((i) => i.getResource()); } - public createOrGet(resource?: URI, modeId?: string): UntitledEditorInput { + public createOrGet(resource?: URI, modeId?: string, restoreResource?: URI): UntitledEditorInput { let hasAssociatedFilePath = false; if (resource) { hasAssociatedFilePath = (resource.scheme === 'file'); @@ -147,10 +151,10 @@ export class UntitledEditorService implements IUntitledEditorService { } // Create new otherwise - return this.doCreate(resource, hasAssociatedFilePath, modeId); + return this.doCreate(resource, hasAssociatedFilePath, modeId, restoreResource); } - private doCreate(resource?: URI, hasAssociatedFilePath?: boolean, modeId?: string): UntitledEditorInput { + private doCreate(resource?: URI, hasAssociatedFilePath?: boolean, modeId?: string, restoreResource?: URI): UntitledEditorInput { if (!resource) { // Create new taking a resource URI that is not already taken @@ -162,6 +166,9 @@ export class UntitledEditorService implements IUntitledEditorService { } const input = this.instantiationService.createInstance(UntitledEditorInput, resource, hasAssociatedFilePath, modeId); + if (restoreResource) { + input.setRestoreResource(restoreResource); + } const dirtyListener = input.onDidChangeDirty(() => { this._onDidChangeDirty.fire(resource);