perf - introduce and use "soft" revert when closing all editors

This commit is contained in:
Benjamin Pasero
2016-12-15 09:36:30 +01:00
parent fc0daf6d85
commit 0a073fa0dd
5 changed files with 64 additions and 17 deletions
@@ -590,7 +590,7 @@ export class CloseAllEditorsAction extends Action {
let saveOrRevertPromise: TPromise<boolean>;
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));
}
@@ -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<void> {
public revert(soft?: boolean): TPromise<void> {
if (!this.isResolved()) {
return TPromise.as<void>(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<EditorModel>;
if (soft) {
loadPromise = TPromise.as(this);
} else {
loadPromise = this.load(true /* force */);
}
return loadPromise.then(() => {
// Emit file change event
this._onDidStateChange.fire(StateChange.REVERTED);
@@ -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<boolean> {
return this.revertAll([resource], force).then(result => result.results.length === 1 && result.results[0].success);
public revert(resource: URI, options?: IRevertOptions): TPromise<boolean> {
return this.revertAll([resource], options).then(result => result.results.length === 1 && result.results[0].success);
}
public revertAll(resources?: URI[], force?: boolean): TPromise<ITextFileOperationResult> {
public revertAll(resources?: URI[], options?: IRevertOptions): TPromise<ITextFileOperationResult> {
// 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<ITextFileOperationResult> {
const fileModels = force ? this.getFileModels(resources) : this.getDirtyFileModels(resources);
private doRevertAllFiles(resources?: URI[], options?: IRevertOptions): TPromise<ITextFileOperationResult> {
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;
}
@@ -223,7 +223,7 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport
save(options?: IModelSaveOptions): TPromise<void>;
revert(): TPromise<void>;
revert(soft?: boolean): TPromise<void>;
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<IAutoSaveConfiguration>;
@@ -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<ITextFileOperationResult>;
revertAll(resources?: URI[], options?: IRevertOptions): TPromise<ITextFileOperationResult>;
/**
* Brings up the confirm dialog to either save, don't save or cancel.
@@ -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');