diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 5090d690f30..47972c2053a 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -590,7 +590,7 @@ export class CloseAllEditorsAction extends Action { let saveOrRevertPromise: TPromise; if (confirm === ConfirmResult.DONT_SAVE) { - saveOrRevertPromise = this.textFileService.revertAll().then(() => true); + saveOrRevertPromise = this.textFileService.revertAll(null, { soft: true }).then(() => true); } else { saveOrRevertPromise = this.textFileService.saveAll(true).then(res => res.results.every(r => r.success)); } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index ceb4c5b632c..811cd656b2e 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -107,6 +107,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.toDispose.push(this.textFileService.onFilesAssociationChange(e => this.onFilesAssociationChange())); this.toDispose.push(this.onDidStateChange(e => { if (e === StateChange.REVERTED) { + // Cancel any content change event promises as they are no longer valid. this.contentChangeEventScheduler.cancel(); @@ -178,8 +179,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil /** * Discards any local changes and replaces the model with the contents of the version on disk. + * + * @param if the parameter soft is true, will not attempt to load the contents from disk. */ - public revert(): TPromise { + public revert(soft?: boolean): TPromise { if (!this.isResolved()) { return TPromise.as(null); } @@ -190,8 +193,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Unset flags const undo = this.setDirty(false); - // Reload - return this.load(true /* force */).then(() => { + let loadPromise: TPromise; + if (soft) { + loadPromise = TPromise.as(this); + } else { + loadPromise = this.load(true /* force */); + } + + return loadPromise.then(() => { // Emit file change event this._onDidStateChange.fire(StateChange.REVERTED); diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index 9cb5e6766b9..c65c4a8fcc1 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -14,7 +14,7 @@ import Event, { Emitter } from 'vs/base/common/event'; import platform = require('vs/base/common/platform'); import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { IRevertOptions, 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 { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -596,14 +596,14 @@ export abstract class TextFileService implements ITextFileService { return this.untitledEditorService.get(untitledResource).suggestFileName(); } - public revert(resource: URI, force?: boolean): TPromise { - return this.revertAll([resource], force).then(result => result.results.length === 1 && result.results[0].success); + public revert(resource: URI, options?: IRevertOptions): TPromise { + return this.revertAll([resource], options).then(result => result.results.length === 1 && result.results[0].success); } - public revertAll(resources?: URI[], force?: boolean): TPromise { + public revertAll(resources?: URI[], options?: IRevertOptions): TPromise { // Revert files first - return this.doRevertAllFiles(resources, force).then(operation => { + return this.doRevertAllFiles(resources, options).then(operation => { // Revert untitled const reverted = this.untitledEditorService.revertAll(resources); @@ -613,8 +613,8 @@ export abstract class TextFileService implements ITextFileService { }); } - private doRevertAllFiles(resources?: URI[], force?: boolean): TPromise { - const fileModels = force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); + private doRevertAllFiles(resources?: URI[], options?: IRevertOptions): TPromise { + const fileModels = options && options.force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); const mapResourceToResult: { [resource: string]: IResult } = Object.create(null); fileModels.forEach(m => { @@ -624,7 +624,7 @@ export abstract class TextFileService implements ITextFileService { }); return TPromise.join(fileModels.map(model => { - return model.revert().then(() => { + return model.revert(options && options.soft).then(() => { if (!model.isDirty()) { mapResourceToResult[model.getResource().toString()].success = true; } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index f529c80f11d..ede9aa3f8b5 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -223,7 +223,7 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport save(options?: IModelSaveOptions): TPromise; - revert(): TPromise; + revert(soft?: boolean): TPromise; setConflictResolutionMode(): void; @@ -245,6 +245,19 @@ export interface ISaveOptions { force: boolean; } +export interface IRevertOptions { + + /** + * Forces to load the contents from disk again even if the file is not dirty. + */ + force?: boolean; + + /** + * A soft revert will clear dirty state of a file but not attempt to load the contents from disk. + */ + soft?: boolean; +} + export interface ITextFileService extends IDisposable { _serviceBrand: any; onAutoSaveConfigurationChange: Event; @@ -311,10 +324,8 @@ export interface ITextFileService extends IDisposable { /** * Reverts all the provided resources and returns a promise with the operation result. - * - * @param force to force revert even when the file is not dirty */ - revertAll(resources?: URI[], force?: boolean): TPromise; + revertAll(resources?: URI[], options?: IRevertOptions): TPromise; /** * Brings up the confirm dialog to either save, don't save or cancel. diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index eb616b9a2f1..568497b0a34 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -136,7 +136,6 @@ suite('Files - TextFileEditorModel', () => { test('Revert', function (done) { let eventCounter = 0; - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); model.onDidStateChange(e => { @@ -162,6 +161,34 @@ suite('Files - TextFileEditorModel', () => { }, error => onError(error, done)); }); + test('Revert (soft)', function (done) { + let eventCounter = 0; + + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); + + model.onDidStateChange(e => { + if (e === StateChange.REVERTED) { + eventCounter++; + } + }); + + model.load().done(() => { + model.textEditorModel.setValue('foo'); + + assert.ok(model.isDirty()); + + return model.revert(true /* soft revert */).then(() => { + assert.ok(!model.isDirty()); + assert.equal(model.textEditorModel.getValue(), 'foo'); + assert.equal(eventCounter, 1); + + model.dispose(); + + done(); + }); + }, error => onError(error, done)); + }); + test('File not modified error is handled gracefully', function (done) { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');