提交 7b9f3516 编写于 作者: J Johannes Rieken

make ISaveParticipant async

上级 23fef6b1
......@@ -6,6 +6,7 @@
'use strict';
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
import {TPromise} from 'vs/base/common/winjs.base';
import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService';
import {IWorkbenchContribution} from 'vs/workbench/common/contributions';
import {ISaveParticipant, ITextFileEditorModel} from 'vs/workbench/parts/files/common/files';
......@@ -47,10 +48,11 @@ export class SaveParticipant implements ISaveParticipant, IWorkbenchContribution
this.trimTrailingWhitespace = configuration && configuration.files && configuration.files.trimTrailingWhitespace;
}
public participate(model: ITextFileEditorModel, env: { isAutoSaved: boolean }): void {
public participate(model: ITextFileEditorModel, env: { isAutoSaved: boolean }): TPromise<any> {
if (this.trimTrailingWhitespace) {
this.doTrimTrailingWhitespace(model.textEditorModel, env.isAutoSaved);
}
return TPromise.as(undefined);
}
private doTrimTrailingWhitespace(model: IModel, isAutoSaved: boolean): void {
......
......@@ -441,66 +441,81 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// A save participant can still change the model now and since we are so close to saving
// we do not want to trigger another auto save or similar, so we block this
// In addition we update our version right after in case it changed because of a model change
let saveParticipantPromise = TPromise.as(versionId);
if (TextFileEditorModel.saveParticipant) {
this.blockModelContentChange = true;
try {
TextFileEditorModel.saveParticipant.participate(this, { isAutoSaved });
} finally {
saveParticipantPromise = TPromise.as(undefined).then(() => {
this.blockModelContentChange = true;
return TextFileEditorModel.saveParticipant.participate(this, { isAutoSaved });
}).then(() => {
this.blockModelContentChange = false;
}
versionId = this.versionId;
return this.versionId;
}, err => {
this.blockModelContentChange = false;
return TPromise.wrapError(err);
});
}
// Clear error flag since we are trying to save again
this.inErrorMode = false;
this.mapPendingSaveToVersionId[versionId] = saveParticipantPromise.then(newVersionId => {
// Remember when this model was saved last
this.lastSaveAttemptTime = Date.now();
// Save to Disk
diag(`doSave(${versionId}) - before updateContent()`, this.resource, new Date());
this.mapPendingSaveToVersionId[versionId] = this.fileService.updateContent(this.versionOnDiskStat.resource, this.getValue(), {
overwriteReadonly: overwriteReadonly,
overwriteEncoding: overwriteEncoding,
mtime: this.versionOnDiskStat.mtime,
encoding: this.getEncoding(),
etag: this.versionOnDiskStat.etag
}).then((stat: IFileStat) => {
diag(`doSave(${versionId}) - after updateContent()`, this.resource, new Date());
// Remove from pending saves
// remove save participant promise and update versionId with
// its new value (if pre-save changes happened)
delete this.mapPendingSaveToVersionId[versionId];
versionId = newVersionId;
// Telemetry
this.telemetryService.publicLog('filePUT', { mimeType: stat.mime, ext: paths.extname(this.versionOnDiskStat.resource.fsPath) });
// Clear error flag since we are trying to save again
this.inErrorMode = false;
// Update dirty state unless model has changed meanwhile
if (versionId === this.versionId) {
diag(`doSave(${versionId}) - setting dirty to false because versionId did not change`, this.resource, new Date());
this.setDirty(false);
} else {
diag(`doSave(${versionId}) - not setting dirty to false because versionId did change meanwhile`, this.resource, new Date());
}
// Remember when this model was saved last
this.lastSaveAttemptTime = Date.now();
// Save to Disk
diag(`doSave(${versionId}) - before updateContent()`, this.resource, new Date());
this.mapPendingSaveToVersionId[versionId] = this.fileService.updateContent(this.versionOnDiskStat.resource, this.getValue(), {
overwriteReadonly: overwriteReadonly,
overwriteEncoding: overwriteEncoding,
mtime: this.versionOnDiskStat.mtime,
encoding: this.getEncoding(),
etag: this.versionOnDiskStat.etag
}).then((stat: IFileStat) => {
diag(`doSave(${versionId}) - after updateContent()`, this.resource, new Date());
// Remove from pending saves
delete this.mapPendingSaveToVersionId[versionId];
// Telemetry
this.telemetryService.publicLog('filePUT', { mimeType: stat.mime, ext: paths.extname(this.versionOnDiskStat.resource.fsPath) });
// Update dirty state unless model has changed meanwhile
if (versionId === this.versionId) {
diag(`doSave(${versionId}) - setting dirty to false because versionId did not change`, this.resource, new Date());
this.setDirty(false);
} else {
diag(`doSave(${versionId}) - not setting dirty to false because versionId did change meanwhile`, this.resource, new Date());
}
// Updated resolved stat with updated stat, and keep old for event
this.updateVersionOnDiskStat(stat);
// Updated resolved stat with updated stat, and keep old for event
this.updateVersionOnDiskStat(stat);
// Emit File Saved Event
this._onDidStateChange.fire(StateChange.SAVED);
}, (error) => {
diag(`doSave(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource, new Date());
// Emit File Saved Event
this._onDidStateChange.fire(StateChange.SAVED);
}, (error) => {
diag(`doSave(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource, new Date());
// Remove from pending saves
delete this.mapPendingSaveToVersionId[versionId];
// Remove from pending saves
delete this.mapPendingSaveToVersionId[versionId];
// Flag as error state
this.inErrorMode = true;
// Flag as error state
this.inErrorMode = true;
// Show to user
this.onSaveError(error);
// Show to user
this.onSaveError(error);
// Emit as event
this._onDidStateChange.fire(StateChange.SAVE_ERROR);
});
// Emit as event
this._onDidStateChange.fire(StateChange.SAVE_ERROR);
return this.mapPendingSaveToVersionId[versionId];
});
return this.mapPendingSaveToVersionId[versionId];
......
......@@ -112,7 +112,7 @@ export interface ISaveParticipant {
/**
* Participate in a save of a model. Allows to change the model before it is being saved to disk.
*/
participate(model: ITextFileEditorModel, env: { isAutoSaved: boolean }): void;
participate(model: ITextFileEditorModel, env: { isAutoSaved: boolean }): TPromise<any>;
}
/**
......
......@@ -294,6 +294,7 @@ suite('Files - TextFileEditorModel', () => {
model.textEditorModel.setValue('bar');
assert.ok(model.isDirty());
eventCounter++;
return undefined;
}
});
......@@ -309,4 +310,45 @@ suite('Files - TextFileEditorModel', () => {
});
});
});
test('Save Participant, async participant', function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource('/path/index_async.txt'), 'utf8');
TextFileEditorModel.setSaveParticipant({
participate: (model) => {
return TPromise.timeout(10);
}
});
return model.load().then(() => {
model.textEditorModel.setValue('foo');
const now = Date.now();
return model.save().then(() => {
assert.ok(Date.now() - now >= 10);
model.dispose();
});
});
});
test('Save Participant, bad participant', function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource('/path/index_async.txt'), 'utf8');
TextFileEditorModel.setSaveParticipant({
participate: (model) => {
return TPromise.wrapError('boom');
}
});
return model.load().then(() => {
model.textEditorModel.setValue('foo');
return model.save().then(() => {
assert.ok(false);
model.dispose();
}, err => {
assert.ok(err);
});
});
});
});
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册