diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 01adf812f76991862a3cd4cad6d9a115c70f041f..b1973d71978bff4dd14a56bd592fa4c18f5d2430 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -1859,6 +1859,10 @@ export interface IModelContentChangedEvent2 { * The new text for the range. */ text: string; + /** + * The end-of-line character. + */ + eol: string; /** * The new version id the model has transitioned to. */ diff --git a/src/vs/editor/common/model/mirrorModel2.ts b/src/vs/editor/common/model/mirrorModel2.ts index ae70ac947abdf3cf84e51d60d54e468ac79ed43e..d9ffd7f121c9454ecc0b7d7d797dc29989a965d8 100644 --- a/src/vs/editor/common/model/mirrorModel2.ts +++ b/src/vs/editor/common/model/mirrorModel2.ts @@ -36,6 +36,18 @@ export class MirrorModel2 { } onEvents(events: IModelContentChangedEvent2[]): void { + let newEOL: string = null; + for (let i = 0, len = events.length; i < len; i++) { + let e = events[i]; + if (e.eol) { + newEOL = e.eol; + } + } + if (newEOL && newEOL !== this._eol) { + this._eol = newEOL; + this._lineStarts = null; + } + // Update my lines let lastVersionId = -1; for (let i = 0, len = events.length; i < len; i++) { diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index f74be8b2830dac4166e5a8a75b1f32384a38ffbc..8f627eaeb11a0818b8b0b03c03b78972e7a4c91b 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -124,6 +124,7 @@ export class TextModel extends OrderGuaranteeEventEmitter implements EditorCommo range: new Range(startLineNumber, startColumn, endLineNumber, endColumn), rangeLength: rangeLength, text: text, + eol: this._EOL, versionId: this.getVersionId(), isUndoing: isUndoing, isRedoing: isRedoing diff --git a/src/vs/editor/test/common/model/editableTextModel.test.ts b/src/vs/editor/test/common/model/editableTextModel.test.ts index ad3c7bd69d1ba28bbc13d471af4cea8ea460d504..af35fdc1632aa216c0c612763ca4bf7e44cde5d4 100644 --- a/src/vs/editor/test/common/model/editableTextModel.test.ts +++ b/src/vs/editor/test/common/model/editableTextModel.test.ts @@ -1284,6 +1284,35 @@ suite('EditorModel - EditableTextModel.applyEdits', () => { }); }); }); + + test('issue #1580: Changes in line endings are not correctly reflected in the extension host, leading to invalid offsets sent to external refactoring tools', () => { + let model = new EditableTextModel([], TextModel.toRawText('Hello\nWorld!'), null); + assert.equal(model.getEOL(), '\n'); + + let mirrorModel2 = new MirrorModel2(null, model.toRawText().lines, model.toRawText().EOL, model.getVersionId()); + let mirrorModel2PrevVersionId = model.getVersionId(); + + model.addListener(EditorCommon.EventType.ModelContentChanged2, (e:EditorCommon.IModelContentChangedEvent2) => { + let versionId = e.versionId; + if (versionId < mirrorModel2PrevVersionId) { + console.warn('Model version id did not advance between edits (2)'); + } + mirrorModel2PrevVersionId = versionId; + mirrorModel2.onEvents([e]); + }); + + let assertMirrorModels = () => { + model._assertLineNumbersOK(); + assert.equal(mirrorModel2.getText(), model.getValue(), 'mirror model 2 text OK'); + assert.equal(mirrorModel2.version, model.getVersionId(), 'mirror model 2 version OK'); + }; + + model.setEOL(EditorCommon.EndOfLineSequence.CRLF); + assertMirrorModels(); + + model.dispose(); + mirrorModel2.dispose(); + }); }); interface ILightWeightMarker { diff --git a/src/vs/workbench/test/common/api/extHostDocuments.test.ts b/src/vs/workbench/test/common/api/extHostDocuments.test.ts index 88ebddaab7b446b2414b40b33b6f93f808b39b79..12f54da0720aef1a073241fcaf1742fb1a222e0c 100644 --- a/src/vs/workbench/test/common/api/extHostDocuments.test.ts +++ b/src/vs/workbench/test/common/api/extHostDocuments.test.ts @@ -69,6 +69,7 @@ suite("PluginHostDocument", () => { data.onEvents([{ range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, text: '\t ', + eol: undefined, isRedoing: undefined, isUndoing: undefined, versionId: undefined, @@ -105,6 +106,7 @@ suite("PluginHostDocument", () => { data.onEvents([{ range: { startLineNumber: 1, startColumn: 3, endLineNumber: 1, endColumn: 6 }, text: '', + eol: undefined, isRedoing: undefined, isUndoing: undefined, versionId: undefined, @@ -121,6 +123,7 @@ suite("PluginHostDocument", () => { data.onEvents([{ range: { startLineNumber: 1, startColumn: 3, endLineNumber: 1, endColumn: 6 }, text: 'is could be', + eol: undefined, isRedoing: undefined, isUndoing: undefined, versionId: undefined, @@ -137,6 +140,7 @@ suite("PluginHostDocument", () => { data.onEvents([{ range: { startLineNumber: 1, startColumn: 3, endLineNumber: 1, endColumn: 6 }, text: 'is could be\na line with number', + eol: undefined, isRedoing: undefined, isUndoing: undefined, versionId: undefined, @@ -156,6 +160,7 @@ suite("PluginHostDocument", () => { data.onEvents([{ range: { startLineNumber: 1, startColumn: 3, endLineNumber: 2, endColumn: 6 }, text: '', + eol: undefined, isRedoing: undefined, isUndoing: undefined, versionId: undefined, @@ -222,10 +227,11 @@ suite("PluginHostDocument updates line mapping", () => { } } - function createChangeEvent(range:CodeEditorRange, text:string): EditorCommon.IModelContentChangedEvent2 { + function createChangeEvent(range:CodeEditorRange, text:string, eol?:string): EditorCommon.IModelContentChangedEvent2 { return { range: range, text: text, + eol: eol, isRedoing: undefined, isUndoing: undefined, versionId: undefined, @@ -329,4 +335,22 @@ suite("PluginHostDocument updates line mapping", () => { 'and finished with the fourth.', ], [createChangeEvent(new CodeEditorRange(1, 3, 4, 30), 'some new text\nthat\nspans multiple lines')]); }); + + test('after changing EOL to CRLF', () => { + testLineMappingAfterEvents([ + 'This is line one', + 'and this is line number two', + 'it is followed by #3', + 'and finished with the fourth.', + ], [createChangeEvent(new CodeEditorRange(1, 1, 1, 1), '', '\r\n')]); + }); + + test('after changing EOL to LF', () => { + testLineMappingAfterEvents([ + 'This is line one', + 'and this is line number two', + 'it is followed by #3', + 'and finished with the fourth.', + ], [createChangeEvent(new CodeEditorRange(1, 1, 1, 1), '', '\n')]); + }); }); \ No newline at end of file