diff --git a/extensions/vscode-api-tests/src/workspace.test.ts b/extensions/vscode-api-tests/src/workspace.test.ts index bf06b4ea399..d45c6bd5610 100644 --- a/extensions/vscode-api-tests/src/workspace.test.ts +++ b/extensions/vscode-api-tests/src/workspace.test.ts @@ -179,21 +179,66 @@ suite('workspace-namespace', () => { return Promise.all([a, b, c]); }); - test('eol, change via editor', () => { - return createRandomFile('foo\nbar\nbar').then(file => { - return workspace.openTextDocument(file).then(doc => { - assert.equal(doc.eol, EndOfLine.LF); - return window.showTextDocument(doc).then(editor => { - return editor.edit(builder => builder.setEndOfLine(EndOfLine.CRLF)); + // test('eol, change via editor', () => { + // return createRandomFile('foo\nbar\nbar').then(file => { + // return workspace.openTextDocument(file).then(doc => { + // assert.equal(doc.eol, EndOfLine.LF); + // return window.showTextDocument(doc).then(editor => { + // return editor.edit(builder => builder.setEndOfLine(EndOfLine.CRLF)); - }).then(value => { - assert.ok(value); - assert.ok(doc.isDirty); - assert.equal(doc.eol, EndOfLine.CRLF); - }); - }); - }); - }); + // }).then(value => { + // assert.ok(value); + // assert.ok(doc.isDirty); + // assert.equal(doc.eol, EndOfLine.CRLF); + // }); + // }); + // }); + // }); + + // test('eol, change via applyEdit', () => { + // return createRandomFile('foo\nbar\nbar').then(file => { + // return workspace.openTextDocument(file).then(doc => { + // assert.equal(doc.eol, EndOfLine.LF); + + // const edit = new WorkspaceEdit(); + // edit.set(file, [TextEdit.setEndOfLine(EndOfLine.CRLF)]); + // return workspace.applyEdit(edit).then(value => { + // assert.ok(value); + // assert.ok(doc.isDirty); + // assert.equal(doc.eol, EndOfLine.CRLF); + // }); + // }); + // }); + // }); + + // test('eol, change via onWillSave', () => { + + // let called = false; + // let sub = workspace.onWillSaveTextDocument(e => { + // called = true; + // e.waitUntil(Promise.resolve([TextEdit.setEndOfLine(EndOfLine.LF)])); + // }); + + // return createRandomFile('foo\r\nbar\r\nbar').then(file => { + // return workspace.openTextDocument(file).then(doc => { + // assert.equal(doc.eol, EndOfLine.CRLF); + // const edit = new WorkspaceEdit(); + // edit.set(file, [TextEdit.insert(new Position(0, 0), '-changes-')]); + + // return workspace.applyEdit(edit).then(success => { + // assert.ok(success); + // return doc.save(); + + // }).then(success => { + // assert.ok(success); + // assert.ok(called); + // assert.ok(!doc.isDirty); + // assert.equal(doc.eol, EndOfLine.LF); + // sub.dispose(); + // }); + // }); + // }); + // }); test('events: onDidOpenTextDocument, onDidChangeTextDocument, onDidSaveTextDocument', () => { return createRandomFile().then(file => { diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index cd329259d7c..57fa81ff233 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -516,6 +516,12 @@ export interface DocumentSymbolProvider { provideDocumentSymbols(model: editorCommon.IReadOnlyModel, token: CancellationToken): SymbolInformation[] | Thenable; } +export interface TextEdit { + text: string; + range: editorCommon.IRange; + eol: editorCommon.EndOfLineSequence; +} + /** * Interface used to format a model */ diff --git a/src/vs/editor/common/services/bulkEdit.ts b/src/vs/editor/common/services/bulkEdit.ts index 5292fac6f08..8bdb31db9ec 100644 --- a/src/vs/editor/common/services/bulkEdit.ts +++ b/src/vs/editor/common/services/bulkEdit.ts @@ -18,21 +18,11 @@ import { Selection } from 'vs/editor/common/core/selection'; import { IIdentifiedSingleEditOperation, IModel, IRange, ISelection, EndOfLineSequence, ICommonCodeEditor } from 'vs/editor/common/editorCommon'; import { IProgressRunner } from 'vs/platform/progress/common/progress'; -export interface IResourceTextEdit { +export interface IResourceEdit { resource: URI; range?: IRange; newText: string; -} - -export interface IResourceEOLEdit { - resource: URI; - eol: EndOfLineSequence; -} - -export type IResourceEdit = IResourceTextEdit | IResourceEOLEdit; - -function isIEndOfLineSequenceEdit(thing: any): thing is IResourceEOLEdit { - return thing && URI.isUri((thing).resource) && typeof (thing).eol === 'number'; + newEol?: EndOfLineSequence; } interface IRecording { @@ -94,19 +84,19 @@ class EditTask implements IDisposable { } public addEdit(edit: IResourceEdit): void { - if (isIEndOfLineSequenceEdit(edit)) { - // store new EOL-sequence, last wins - this._newEol = edit.eol; + // create edit operation + let range: IRange; + if (!edit.range) { + range = this._model.getFullModelRange(); } else { - // create edit operation - let range: IRange; - if (!edit.range) { - range = this._model.getFullModelRange(); - } else { - range = edit.range; - } - this._edits.push(EditOperation.replaceMove(Range.lift(range), edit.newText)); + range = edit.range; + } + this._edits.push(EditOperation.replaceMove(Range.lift(range), edit.newText)); + + // honor eol-change + if (typeof edit.newEol === 'number') { + this._newEol = edit.newEol; } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index be3a31eff5d..0b080a79a74 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4759,6 +4759,12 @@ declare module monaco.languages { provideDocumentSymbols(model: editor.IReadOnlyModel, token: CancellationToken): SymbolInformation[] | Thenable; } + export interface TextEdit { + text: string; + range: IRange; + eol: editor.EndOfLineSequence; + } + /** * Interface used to format a model */ diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 7837feb3d52..50e986c7520 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2031,6 +2031,14 @@ declare module 'vscode' { */ static delete(range: Range): TextEdit; + /** + * Utility to create an eol-edit. + * + * @param eol An eol-sequence + * @return A new text edit object. + */ + static setEndOfLine(eol: EndOfLine): TextEdit; + /** * The range this edit applies to. */ @@ -2041,6 +2049,11 @@ declare module 'vscode' { */ newText: string; + /** + * The eol-sequence used in the document. + */ + newEol: EndOfLine; + /** * Create a new TextEdit. * diff --git a/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts index 9a1b17e02f2..8ee95f6ad0a 100644 --- a/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts @@ -12,7 +12,7 @@ import { illegalState } from 'vs/base/common/errors'; import { TPromise } from 'vs/base/common/winjs.base'; import { MainThreadWorkspaceShape, ExtHostDocumentSaveParticipantShape } from 'vs/workbench/api/node/extHost.protocol'; import { TextEdit } from 'vs/workbench/api/node/extHostTypes'; -import { fromRange, TextDocumentSaveReason } from 'vs/workbench/api/node/extHostTypeConverters'; +import { fromRange, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/node/extHostTypeConverters'; import { IResourceEdit } from 'vs/editor/common/services/bulkEdit'; import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; @@ -138,11 +138,12 @@ export class ExtHostDocumentSaveParticipant extends ExtHostDocumentSaveParticipa for (const value of values) { if (Array.isArray(value) && (value).every(e => e instanceof TextEdit)) { - for (const { newText, range } of value) { + for (const { newText, newEol, range } of value) { edits.push({ resource: document.uri, - range: fromRange(range), - newText + range: range && fromRange(range), + newText, + newEol: EndOfLine.from(newEol) }); } } diff --git a/src/vs/workbench/api/node/extHostTypeConverters.ts b/src/vs/workbench/api/node/extHostTypeConverters.ts index 3f90544e02d..1991c66a2d2 100644 --- a/src/vs/workbench/api/node/extHostTypeConverters.ts +++ b/src/vs/workbench/api/node/extHostTypeConverters.ts @@ -366,12 +366,15 @@ export namespace TextDocumentSaveReason { } -export function fromEOL(eol: vscode.EndOfLine): EndOfLineSequence { - if (eol === types.EndOfLine.CRLF) { - return EndOfLineSequence.CRLF; - } else if (eol === types.EndOfLine.LF) { - return EndOfLineSequence.LF; +export namespace EndOfLine { + + export function from(eol: vscode.EndOfLine): EndOfLineSequence { + if (eol === types.EndOfLine.CRLF) { + return EndOfLineSequence.CRLF; + } else if (eol === types.EndOfLine.LF) { + return EndOfLineSequence.LF; + } + return undefined; } - return undefined; } diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index 7a7d14e7f50..0c5b8147fec 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -429,16 +429,22 @@ export class TextEdit { return TextEdit.replace(range, ''); } - protected _range: Range; + static setEndOfLine(eol: EndOfLine): TextEdit { + let ret = new TextEdit(undefined, undefined); + ret.newEol = eol; + return ret; + } + protected _range: Range; protected _newText: string; + protected _newEol: EndOfLine; get range(): Range { return this._range; } set range(value: Range) { - if (!value) { + if (value && !Range.isRange(value)) { throw illegalArgument('range'); } this._range = value; @@ -448,10 +454,24 @@ export class TextEdit { return this._newText || ''; } - set newText(value) { + set newText(value: string) { + if (value && typeof value !== 'string') { + throw illegalArgument('newText'); + } this._newText = value; } + get newEol(): EndOfLine { + return this._newEol; + } + + set newEol(value: EndOfLine) { + if (value && typeof value !== 'number') { + throw illegalArgument('newEol'); + } + this._newEol = value; + } + constructor(range: Range, newText: string) { this.range = range; this.newText = newText; @@ -460,7 +480,8 @@ export class TextEdit { toJSON(): any { return { range: this.range, - newText: this.newText + newText: this.newText, + newEol: this._newEol }; } } @@ -1186,4 +1207,4 @@ export class ShellTask extends BaseTask { } this._options = value; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index b63e432e583..38024197279 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -8,9 +8,9 @@ import URI from 'vs/base/common/uri'; import { normalize } from 'vs/base/common/paths'; import { relative } from 'path'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; -import { IResourceTextEdit } from 'vs/editor/common/services/bulkEdit'; +import { IResourceEdit } from 'vs/editor/common/services/bulkEdit'; import { TPromise } from 'vs/base/common/winjs.base'; -import { fromRange } from 'vs/workbench/api/node/extHostTypeConverters'; +import { fromRange, EndOfLine } from 'vs/workbench/api/node/extHostTypeConverters'; import { MainContext, MainThreadWorkspaceShape } from './extHost.protocol'; import * as vscode from 'vscode'; @@ -70,7 +70,7 @@ export class ExtHostWorkspace { appyEdit(edit: vscode.WorkspaceEdit): TPromise { - let resourceEdits: IResourceTextEdit[] = []; + let resourceEdits: IResourceEdit[] = []; let entries = edit.entries(); for (let entry of entries) { @@ -80,7 +80,8 @@ export class ExtHostWorkspace { resourceEdits.push({ resource: uri, newText: edit.newText, - range: fromRange(edit.range) + newEol: EndOfLine.from(edit.newEol), + range: edit.range && fromRange(edit.range) }); } } diff --git a/src/vs/workbench/parts/search/browser/replaceService.ts b/src/vs/workbench/parts/search/browser/replaceService.ts index 81f067126eb..4fb0b6372c3 100644 --- a/src/vs/workbench/parts/search/browser/replaceService.ts +++ b/src/vs/workbench/parts/search/browser/replaceService.ts @@ -15,7 +15,7 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { Match, FileMatch, FileMatchOrMatch, ISearchWorkbenchService } from 'vs/workbench/parts/search/common/searchModel'; -import { BulkEdit, IResourceTextEdit, createBulkEdit } from 'vs/editor/common/services/bulkEdit'; +import { BulkEdit, IResourceEdit, createBulkEdit } from 'vs/editor/common/services/bulkEdit'; import { IProgressRunner } from 'vs/platform/progress/common/progress'; import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -182,9 +182,9 @@ export class ReplaceService implements IReplaceService { }); } - private createEdit(match: Match, text: string, resource: URI = null): IResourceTextEdit { + private createEdit(match: Match, text: string, resource: URI = null): IResourceEdit { let fileMatch: FileMatch = match.parent(); - let resourceEdit: IResourceTextEdit = { + let resourceEdit: IResourceEdit = { resource: resource !== null ? resource : fileMatch.resource(), range: match.range(), newText: text diff --git a/src/vs/workbench/test/node/api/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/test/node/api/extHostDocumentSaveParticipant.test.ts index 57599fb66fb..9f51444c3a7 100644 --- a/src/vs/workbench/test/node/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/node/api/extHostDocumentSaveParticipant.test.ts @@ -9,11 +9,11 @@ import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors'; -import { TextDocumentSaveReason, TextEdit, Position } from 'vs/workbench/api/node/extHostTypes'; +import { TextDocumentSaveReason, TextEdit, Position, EndOfLine } from 'vs/workbench/api/node/extHostTypes'; import { MainThreadWorkspaceShape } from 'vs/workbench/api/node/extHost.protocol'; import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/node/extHostDocumentSaveParticipant'; import { OneGetThreadService } from './testThreadService'; -import { IResourceTextEdit } from 'vs/editor/common/services/bulkEdit'; +import { IResourceEdit } from 'vs/editor/common/services/bulkEdit'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import * as vscode from 'vscode'; @@ -252,7 +252,7 @@ suite('ExtHostDocumentSaveParticipant', () => { test('event delivery, pushEdits sync', () => { - let edits: IResourceTextEdit[]; + let edits: IResourceEdit[]; const participant = new ExtHostDocumentSaveParticipant(documents, new class extends MainThreadWorkspaceShape { $applyWorkspaceEdit(_edits) { edits = _edits; @@ -262,18 +262,19 @@ suite('ExtHostDocumentSaveParticipant', () => { let sub = participant.onWillSaveTextDocumentEvent(function (e) { e.waitUntil(TPromise.as([TextEdit.insert(new Position(0, 0), 'bar')])); + e.waitUntil(TPromise.as([TextEdit.setEndOfLine(EndOfLine.CRLF)])); }); return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(() => { sub.dispose(); - assert.equal(edits.length, 1); + assert.equal(edits.length, 2); }); }); test('event delivery, concurrent change', () => { - let edits: IResourceTextEdit[]; + let edits: IResourceEdit[]; const participant = new ExtHostDocumentSaveParticipant(documents, new class extends MainThreadWorkspaceShape { $applyWorkspaceEdit(_edits) { edits = _edits; @@ -306,7 +307,7 @@ suite('ExtHostDocumentSaveParticipant', () => { test('event delivery, two listeners -> two document states', () => { const participant = new ExtHostDocumentSaveParticipant(documents, new class extends MainThreadWorkspaceShape { - $applyWorkspaceEdit(_edits: IResourceTextEdit[]) { + $applyWorkspaceEdit(_edits: IResourceEdit[]) { for (const { resource, newText, range } of _edits) { documents.$acceptModelChanged(resource.toString(), [{ diff --git a/src/vs/workbench/test/node/api/extHostTypes.test.ts b/src/vs/workbench/test/node/api/extHostTypes.test.ts index b65d6bddaf3..0487d049d9f 100644 --- a/src/vs/workbench/test/node/api/extHostTypes.test.ts +++ b/src/vs/workbench/test/node/api/extHostTypes.test.ts @@ -67,7 +67,7 @@ suite('ExtHostTypes', function () { assert.throws(() => (pos as any).character = -1); assert.throws(() => (pos as any).line = 12); - let {line, character} = pos.toJSON(); + let { line, character } = pos.toJSON(); assert.equal(line, 0); assert.equal(character, 0); }); @@ -319,9 +319,6 @@ suite('ExtHostTypes', function () { test('TextEdit', function () { - assert.throws(() => new types.TextEdit(null, 'far')); - assert.throws(() => new types.TextEdit(undefined, 'far')); - let range = new types.Range(1, 1, 2, 11); let edit = new types.TextEdit(range, undefined); assert.equal(edit.newText, '');