diff --git a/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts index fcda092983ad012e1c2ecd0e0f4165dd3016743f..ada1b48de41d78b38214c2b8cfd8478483f9dcf6 100644 --- a/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts @@ -47,7 +47,7 @@ export class ExtHostDocumentSaveParticipant { }; } - $participateInSave(resource: URI): TPromise { + $participateInSave(resource: URI): TPromise { const entries = this._callbacks.entries(); return sequence(entries.map(([fn, thisArg]) => { @@ -106,8 +106,8 @@ export class ExtHostDocumentSaveParticipant { return this._workspace.$applyWorkspaceEdit(edits); } - // TODO@joh - fail? - console.warn('IGNORING changes because document has changed while computing changes'); + // TODO@joh bubble this to listener? + return new Error('ignoring change because of concurrent edits'); }, err => { // ignore error diff --git a/src/vs/workbench/test/node/api/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/test/node/api/extHostDocumentSaveParticipant.test.ts index a0ec404c9f60054e146e33b0e080c112a5560857..60f6d16f6885066b868cd3cefc4a12b27174b57f 100644 --- a/src/vs/workbench/test/node/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/node/api/extHostDocumentSaveParticipant.test.ts @@ -177,4 +177,82 @@ suite('ExtHostDocumentSaveParticipant', () => { assert.equal(edits.length, 1); }); }); + + test('event delivery, concurrent change', () => { + + let edits: IResourceEdit[]; + const participant = new ExtHostDocumentSaveParticipant(documents, new class extends MainThreadWorkspaceShape { + $applyWorkspaceEdit(_edits) { + edits = _edits; + return TPromise.as(true); + } + }); + + let sub = participant.onWillSaveTextDocumentEvent(function (e) { + + // concurrent change from somewhere + documents.$acceptModelChanged(resource.toString(), [{ + versionId: 2, + range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, + text: 'bar', + rangeLength: undefined, eol: undefined, isRedoing: undefined, isUndoing: undefined, + }]); + + e.waitUntil(TPromise.as([TextEdit.insert(new Position(0, 0), 'bar')])); + }); + + return participant.$participateInSave(resource).then(values => { + sub.dispose(); + + assert.equal(edits, undefined); + assert.ok((values[0]).message); + }); + + }); + + test('event delivery, two listeners -> two document states', () => { + + const participant = new ExtHostDocumentSaveParticipant(documents, new class extends MainThreadWorkspaceShape { + $applyWorkspaceEdit(_edits: IResourceEdit[]) { + + for (const {resource, newText, range} of _edits) { + documents.$acceptModelChanged(resource.toString(), [{ + range, + text: newText, + versionId: documents.getDocumentData(resource).version + 1, + rangeLength: undefined, eol: undefined, isRedoing: undefined, isUndoing: undefined, + }]); + } + return TPromise.as(true); + } + }); + + const document = documents.getDocumentData(resource).document; + + let sub1 = participant.onWillSaveTextDocumentEvent(function (e) { + // the document state we started with + assert.equal(document.version, 1); + assert.equal(document.getText(), 'foo'); + + e.waitUntil(TPromise.as([TextEdit.insert(new Position(0, 0), 'bar')])); + }); + + let sub2 = participant.onWillSaveTextDocumentEvent(function (e) { + // the document state AFTER the first listener kicked in + assert.equal(document.version, 2); + assert.equal(document.getText(), 'barfoo'); + + e.waitUntil(TPromise.as([TextEdit.insert(new Position(0, 0), 'bar')])); + }); + + return participant.$participateInSave(resource).then(values => { + sub1.dispose(); + sub2.dispose(); + + // the document state AFTER eventing is done + assert.equal(document.version, 3); + assert.equal(document.getText(), 'barbarfoo'); + }); + + }); });