diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 5a05edb5ccc1fac4c6f8e89b37fe0af3e170b814..d64c382f47edcc5b84b021b8d62976877a62044c 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -57,12 +57,16 @@ export function createTextBufferFactory(text: string): model.ITextBufferFactory return builder.finish(); } -export function createTextBufferFactoryFromStream(stream: IStringStream): TPromise { +export function createTextBufferFactoryFromStream(stream: IStringStream, filter?: (chunk: string) => string): TPromise { return new TPromise((c, e, p) => { let done = false; let builder = createTextBufferBuilder(); stream.on('data', (chunk) => { + if (filter) { + chunk = filter(chunk); + } + builder.acceptChunk(chunk); }); diff --git a/src/vs/workbench/browser/parts/editor/editorAreaDropHandler.ts b/src/vs/workbench/browser/parts/editor/editorAreaDropHandler.ts index 587ceeb912825346ea7b5466aac43f2130db0a4b..179201416641d3d812b2883c1f0aa331244f82cd 100644 --- a/src/vs/workbench/browser/parts/editor/editorAreaDropHandler.ts +++ b/src/vs/workbench/browser/parts/editor/editorAreaDropHandler.ts @@ -12,7 +12,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; import URI from 'vs/base/common/uri'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { BACKUP_FILE_RESOLVE_OPTIONS, IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { TPromise } from 'vs/base/common/winjs.base'; import { Schemas } from 'vs/base/common/network'; @@ -20,6 +20,8 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Position } from 'vs/platform/editor/common/editor'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { DefaultEndOfLine } from 'vs/editor/common/model'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; /** * Shared function across some editor components to handle drag & drop of external resources. E.g. of folders and workspace files @@ -37,6 +39,7 @@ export class EditorAreaDropHandler { @IEditorGroupService private groupService: IEditorGroupService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IConfigurationService private configurationService: IConfigurationService ) { } @@ -107,13 +110,22 @@ export class EditorAreaDropHandler { } // Resolve the contents of the dropped dirty resource from source - return this.textFileService.resolveTextContent(droppedDirtyEditor.backupResource, BACKUP_FILE_RESOLVE_OPTIONS).then(content => { + return this.backupFileService.resolveBackupContent(droppedDirtyEditor.backupResource).then(content => { // Set the contents of to the resource to the target - return this.backupFileService.backupResource(droppedDirtyEditor.resource, this.backupFileService.parseBackupContent(content.value)); + return this.backupFileService.backupResource(droppedDirtyEditor.resource, content.create(this.getDefaultEOL()).createSnapshot(true)); }).then(() => false, () => false /* ignore any error */); } + private getDefaultEOL(): DefaultEndOfLine { + const eol = this.configurationService.getValue('files.eol'); + if (eol === '\r\n') { + return DefaultEndOfLine.CRLF; + } + + return DefaultEndOfLine.LF; + } + private handleWorkspaceFileDrop(resources: (IDraggedResource | IDraggedEditor)[]): TPromise { const externalResources = resources.filter(d => d.isExternal).map(d => d.resource); diff --git a/src/vs/workbench/common/editor/untitledEditorModel.ts b/src/vs/workbench/common/editor/untitledEditorModel.ts index c7dcee066bd45e1d98ea64972082c052600a292c..ee33d7720af2b63c2ad81128ec6e57acdb512cf4 100644 --- a/src/vs/workbench/common/editor/untitledEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledEditorModel.ts @@ -16,9 +16,10 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IMode } from 'vs/editor/common/modes'; import Event, { Emitter } from 'vs/base/common/event'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { IBackupFileService, BACKUP_FILE_RESOLVE_OPTIONS } from 'vs/workbench/services/backup/common/backup'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { ITextBufferFactory } from 'vs/editor/common/model'; +import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; export class UntitledEditorModel extends BaseTextEditorModel implements IEncodingSupport { @@ -46,7 +47,6 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IBackupFileService private backupFileService: IBackupFileService, - @ITextFileService private textFileService: ITextFileService, @ITextResourceConfigurationService private configurationService: ITextResourceConfigurationService ) { super(modelService, modeService); @@ -163,18 +163,24 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin // Check for backups first return this.backupFileService.loadBackupResource(this.resource).then(backupResource => { if (backupResource) { - return this.textFileService.resolveTextContent(backupResource, BACKUP_FILE_RESOLVE_OPTIONS).then(rawTextContent => { - return this.backupFileService.parseBackupContent(rawTextContent.value); - }); + return this.backupFileService.resolveBackupContent(backupResource); } return null; - }).then(backupContent => { + }).then(backupTextBufferFactory => { + const hasBackup = !!backupTextBufferFactory; // untitled associated to file path are dirty right away as well as untitled with content - this.setDirty(this.hasAssociatedFilePath || !!backupContent); + this.setDirty(this.hasAssociatedFilePath || hasBackup); - return this.doLoad(backupContent || this.initialValue || '').then(model => { + let untitledContents: ITextBufferFactory; + if (backupTextBufferFactory) { + untitledContents = backupTextBufferFactory; + } else { + untitledContents = createTextBufferFactory(this.initialValue || ''); + } + + return this.doLoad(untitledContents).then(model => { // Encoding this.configuredEncoding = this.configurationService.getValue(this.resource, 'files.encoding'); @@ -189,7 +195,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin }); } - private doLoad(content: string): TPromise { + private doLoad(content: ITextBufferFactory): TPromise { // Create text editor model if not yet done if (!this.textEditorModel) { diff --git a/src/vs/workbench/services/backup/common/backup.ts b/src/vs/workbench/services/backup/common/backup.ts index 0b4d35f5d0cdbe6b507399ca0c819c44ecc077f1..d398e7bdde8d57fb78ed6d2e3b76bbb1567f7f60 100644 --- a/src/vs/workbench/services/backup/common/backup.ts +++ b/src/vs/workbench/services/backup/common/backup.ts @@ -65,13 +65,12 @@ export interface IBackupFileService { getWorkspaceFileBackups(): TPromise; /** - * Parses backup raw text content into the content, removing the metadata that is also stored - * in the file. + * Resolves the backup for the given resource. * - * @param textBufferFactory The ITextBufferFactory from a backup resource. - * @return The backup file's backed up content. + * @param value The contents from a backup resource as stream. + * @return The backup file's backed up content as text buffer factory. */ - parseBackupContent(textBufferFactory: ITextBufferFactory): string; + resolveBackupContent(backup: Uri): TPromise; /** * Discards the backup associated with a resource if it exists.. diff --git a/src/vs/workbench/services/backup/node/backupFileService.ts b/src/vs/workbench/services/backup/node/backupFileService.ts index 18c4e2a7b50734aa37d92b6206ff2dbc2df36d96..c0873744d49ce40134372df4056cb4885b034de5 100644 --- a/src/vs/workbench/services/backup/node/backupFileService.ts +++ b/src/vs/workbench/services/backup/node/backupFileService.ts @@ -10,12 +10,12 @@ import * as crypto from 'crypto'; import * as pfs from 'vs/base/node/pfs'; import Uri from 'vs/base/common/uri'; import { ResourceQueue } from 'vs/base/common/async'; -import { IBackupFileService, BACKUP_FILE_UPDATE_OPTIONS } from 'vs/workbench/services/backup/common/backup'; +import { IBackupFileService, BACKUP_FILE_UPDATE_OPTIONS, BACKUP_FILE_RESOLVE_OPTIONS } from 'vs/workbench/services/backup/common/backup'; import { IFileService, ITextSnapshot, IFileStat } from 'vs/platform/files/common/files'; import { TPromise } from 'vs/base/common/winjs.base'; import { readToMatchingString } from 'vs/base/node/stream'; -import { Range } from 'vs/editor/common/core/range'; -import { DefaultEndOfLine, ITextBufferFactory, EndOfLinePreference } from 'vs/editor/common/model'; +import { ITextBufferFactory } from 'vs/editor/common/model'; +import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; export interface IBackupFilesModel { resolve(backupRoot: string): TPromise; @@ -245,12 +245,27 @@ export class BackupFileService implements IBackupFileService { }); } - public parseBackupContent(textBufferFactory: ITextBufferFactory): string { - // The first line of a backup text file is the file name - const textBuffer = textBufferFactory.create(DefaultEndOfLine.LF); - const lineCount = textBuffer.getLineCount(); - const range = new Range(2, 1, lineCount, textBuffer.getLineLength(lineCount) + 1); - return textBuffer.getValueInRange(range, EndOfLinePreference.TextDefined); + public resolveBackupContent(backup: Uri): TPromise { + return this.fileService.resolveStreamContent(backup, BACKUP_FILE_RESOLVE_OPTIONS).then(content => { + + // Add a filter method to filter out everything until the meta marker + let metaFound = false; + const metaPreambleFilter = (chunk: string) => { + if (!metaFound && chunk) { + const metaIndex = chunk.indexOf(BackupFileService.META_MARKER); + if (metaIndex === -1) { + return ''; // meta not yet found, return empty string + } + + metaFound = true; + return chunk.substr(metaIndex + 1); // meta found, return everything after + } + + return chunk; + }; + + return createTextBufferFactoryFromStream(content.value, metaPreambleFilter); + }); } public toBackupResource(resource: Uri): Uri { diff --git a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts index ff58403e3819dcae6d11b98218b9a58c3670f8ed..e59a370b922fb4b82a734f2e86b24f29c2803d75 100644 --- a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts @@ -16,10 +16,12 @@ import pfs = require('vs/base/node/pfs'); import Uri from 'vs/base/common/uri'; import { BackupFileService, BackupFilesModel } from 'vs/workbench/services/backup/node/backupFileService'; import { FileService } from 'vs/workbench/services/files/node/fileService'; -import { createTextBufferFactory, TextModel } from 'vs/editor/common/model/textModel'; +import { TextModel } from 'vs/editor/common/model/textModel'; import { TestContextService, TestTextResourceConfigurationService, getRandomTestPath, TestLifecycleService } from 'vs/workbench/test/workbenchTestServices'; import { Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { DefaultEndOfLine } from 'vs/editor/common/model'; +import { snapshotToString } from 'vs/platform/files/common/files'; const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupfileservice'); const backupHome = path.join(parentDir, 'Backups'); @@ -268,10 +270,29 @@ suite('BackupFileService', () => { }); }); - test('parseBackupContent', () => { - test('should separate metadata from content', () => { - const textBufferFactory = createTextBufferFactory('metadata\ncontent'); - assert.equal(service.parseBackupContent(textBufferFactory), 'content'); + test('resolveBackupContent', () => { + test('should restore the original contents (untitled file)', () => { + const contents = 'test\nand more stuff'; + service.backupResource(untitledFile, contents).then(() => { + service.resolveBackupContent(service.toBackupResource(untitledFile)).then(factory => { + assert.equal(contents, snapshotToString(factory.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(true))); + }); + }); + }); + + test('should restore the original contents (text file)', () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'consectetur ', + 'adipiscing ßß elit', + ].join(''); + + service.backupResource(fooFile, contents).then(() => { + service.resolveBackupContent(service.toBackupResource(untitledFile)).then(factory => { + assert.equal(contents, snapshotToString(factory.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(true))); + }); + }); }); }); }); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 88197be4a82f5045e7ea5fbcf1fcbed63c2b4bf2..887cb059464bfaa7702dc160709db81c07984858 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -22,7 +22,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, IRawTextContent } from 'vs/workbench/services/textfile/common/textfiles'; import { EncodingMode } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; -import { IBackupFileService, BACKUP_FILE_RESOLVE_OPTIONS } from 'vs/workbench/services/backup/common/backup'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IFileService, IFileStat, FileOperationError, FileOperationResult, IContent, CONTENT_CHANGE_EVENT_BUFFER_DELAY, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMessageService, Severity } from 'vs/platform/message/common/message'; @@ -444,7 +444,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil diag('load() - created text editor model', this.resource, new Date()); this.createTextEditorModelPromise = this.doLoadBackup(backup).then(backupContent => { - const hasBackupContent = (typeof backupContent === 'string'); + const hasBackupContent = !!backupContent; return this.createTextEditorModel(hasBackupContent ? backupContent : value, resource).then(() => { this.createTextEditorModelPromise = null; @@ -488,14 +488,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.toDispose.push(this.textEditorModel.onDidChangeContent(() => this.onModelContentChanged())); } - private doLoadBackup(backup: URI): TPromise { + private doLoadBackup(backup: URI): TPromise { if (!backup) { return TPromise.as(null); } - return this.textFileService.resolveTextContent(backup, BACKUP_FILE_RESOLVE_OPTIONS).then(backup => { - return this.backupFileService.parseBackupContent(backup.value); - }, error => null /* ignore errors */); + return this.backupFileService.resolveBackupContent(backup).then(backupContent => backupContent, error => null /* ignore errors */); } protected getOrCreateMode(modeService: IModeService, preferredModeIds: string, firstLineText?: string): TPromise { diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index e5db572cc9115b28eb8c19e665b27c002f4ea153..d4d3fbd6c25ab487dd1b1533ec7598bfeefccf48 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -872,6 +872,10 @@ export class TestBackupFileService implements IBackupFileService { return textBuffer.getValueInRange(range, EndOfLinePreference.TextDefined); } + public resolveBackupContent(backup: URI): TPromise { + return TPromise.as(null); + } + public discardResourceBackup(resource: URI): TPromise { return TPromise.as(void 0); }