diff --git a/extensions/vscode-api-tests/src/workspace.test.ts b/extensions/vscode-api-tests/src/workspace.test.ts index 0e8a23e8f3925adaa36eabe262762cb0886d1d85..d45c6bd56108bed4f92a73c4bf737f045318b425 100644 --- a/extensions/vscode-api-tests/src/workspace.test.ts +++ b/extensions/vscode-api-tests/src/workspace.test.ts @@ -6,7 +6,7 @@ 'use strict'; import * as assert from 'assert'; -import { workspace, TextDocument, window, Position, Uri, EventEmitter, WorkspaceEdit, Disposable } from 'vscode'; +import { workspace, TextDocument, window, Position, Uri, EventEmitter, WorkspaceEdit, Disposable, EndOfLine } from 'vscode'; import { createRandomFile, deleteFile, cleanUp, pathEquals } from './utils'; import { join, basename } from 'path'; import * as fs from 'fs'; @@ -160,6 +160,86 @@ suite('workspace-namespace', () => { }); }); + test('eol, read', () => { + const a = createRandomFile('foo\nbar\nbar').then(file => { + return workspace.openTextDocument(file).then(doc => { + assert.equal(doc.eol, EndOfLine.LF); + }); + }); + const b = createRandomFile('foo\nbar\nbar\r\nbaz').then(file => { + return workspace.openTextDocument(file).then(doc => { + assert.equal(doc.eol, EndOfLine.LF); + }); + }); + const c = createRandomFile('foo\r\nbar\r\nbar').then(file => { + return workspace.openTextDocument(file).then(doc => { + assert.equal(doc.eol, EndOfLine.CRLF); + }); + }); + 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)); + + // }).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 => { let disposables: Disposable[] = []; diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index cd329259d7c3de77c9f8d05cdb0971dca150067c..ae8c1133243a237f3d53402062ca41fc7b5736de 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 { + range: editorCommon.IRange; + text: string; + eol?: editorCommon.EndOfLineSequence; +} + /** * Interface used to format a model */ @@ -537,7 +543,7 @@ export interface DocumentFormattingEditProvider { /** * Provide formatting edits for a whole document. */ - provideDocumentFormattingEdits(model: editorCommon.IReadOnlyModel, options: FormattingOptions, token: CancellationToken): editorCommon.ISingleEditOperation[] | Thenable; + provideDocumentFormattingEdits(model: editorCommon.IReadOnlyModel, options: FormattingOptions, token: CancellationToken): TextEdit[] | Thenable; } /** * The document formatting provider interface defines the contract between extensions and @@ -551,7 +557,7 @@ export interface DocumentRangeFormattingEditProvider { * or larger range. Often this is done by adjusting the start and end * of the range to full syntax nodes. */ - provideDocumentRangeFormattingEdits(model: editorCommon.IReadOnlyModel, range: Range, options: FormattingOptions, token: CancellationToken): editorCommon.ISingleEditOperation[] | Thenable; + provideDocumentRangeFormattingEdits(model: editorCommon.IReadOnlyModel, range: Range, options: FormattingOptions, token: CancellationToken): TextEdit[] | Thenable; } /** * The document formatting provider interface defines the contract between extensions and @@ -566,7 +572,7 @@ export interface OnTypeFormattingEditProvider { * what range the position to expand to, like find the matching `{` * when `}` has been entered. */ - provideOnTypeFormattingEdits(model: editorCommon.IReadOnlyModel, position: Position, ch: string, options: FormattingOptions, token: CancellationToken): editorCommon.ISingleEditOperation[] | Thenable; + provideOnTypeFormattingEdits(model: editorCommon.IReadOnlyModel, position: Position, ch: string, options: FormattingOptions, token: CancellationToken): TextEdit[] | Thenable; } /** diff --git a/src/vs/editor/common/services/bulkEdit.ts b/src/vs/editor/common/services/bulkEdit.ts index 1bd22951d33512b3626e44775000437fb7dc437e..8bdb31db9ecf0c29402124b37bbf10c926dc3e6a 100644 --- a/src/vs/editor/common/services/bulkEdit.ts +++ b/src/vs/editor/common/services/bulkEdit.ts @@ -15,13 +15,14 @@ import { IFileService, IFileChange } from 'vs/platform/files/common/files'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import { IIdentifiedSingleEditOperation, IModel, IRange, ISelection, ICommonCodeEditor } from 'vs/editor/common/editorCommon'; +import { IIdentifiedSingleEditOperation, IModel, IRange, ISelection, EndOfLineSequence, ICommonCodeEditor } from 'vs/editor/common/editorCommon'; import { IProgressRunner } from 'vs/platform/progress/common/progress'; export interface IResourceEdit { resource: URI; range?: IRange; newText: string; + newEol?: EndOfLineSequence; } interface IRecording { @@ -74,6 +75,7 @@ class EditTask implements IDisposable { private get _model(): IModel { return this._modelReference.object.textEditorModel; } private _modelReference: IReference; private _edits: IIdentifiedSingleEditOperation[]; + private _newEol: EndOfLineSequence; constructor(modelReference: IReference) { this._endCursorSelection = null; @@ -82,6 +84,8 @@ class EditTask implements IDisposable { } public addEdit(edit: IResourceEdit): void { + + // create edit operation let range: IRange; if (!edit.range) { range = this._model.getFullModelRange(); @@ -89,16 +93,22 @@ class EditTask implements IDisposable { 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; + } } public apply(): void { - if (this._edits.length === 0) { - return; + if (this._edits.length > 0) { + this._edits.sort(EditTask._editCompare); + this._initialSelections = this._getInitialSelections(); + this._model.pushEditOperations(this._initialSelections, this._edits, (edits) => this._getEndCursorSelections(edits)); + } + if (this._newEol !== undefined) { + this._model.setEOL(this._newEol); } - this._edits.sort(EditTask._editCompare); - - this._initialSelections = this._getInitialSelections(); - this._model.pushEditOperations(this._initialSelections, this._edits, (edits) => this._getEndCursorSelections(edits)); } protected _getInitialSelections(): Selection[] { diff --git a/src/vs/editor/contrib/format/browser/formatActions.ts b/src/vs/editor/contrib/format/browser/formatActions.ts index a6b7778af9e69f6b3181424459da187afefa1433..a476cd1891d36a427caece914e9b846fb1300c57 100644 --- a/src/vs/editor/contrib/format/browser/formatActions.ts +++ b/src/vs/editor/contrib/format/browser/formatActions.ts @@ -157,7 +157,7 @@ class FormatOnType implements editorCommon.IEditorContribution { return; } - this.editor.executeCommand(this.getId(), new EditOperationsCommand(edits, this.editor.getSelection())); + EditOperationsCommand.execute(this.editor, edits); alertFormattingEdits(edits); }, (err) => { @@ -241,8 +241,7 @@ class FormatOnPaste implements editorCommon.IEditorContribution { if (!state.validate(this.editor) || isFalsyOrEmpty(edits)) { return; } - const command = new EditOperationsCommand(edits, this.editor.getSelection()); - this.editor.executeCommand(this.getId(), command); + EditOperationsCommand.execute(this.editor, edits); alertFormattingEdits(edits); }); } @@ -276,8 +275,8 @@ export abstract class AbstractFormatAction extends EditorAction { if (!state.validate(editor) || isFalsyOrEmpty(edits)) { return; } - const command = new EditOperationsCommand(edits, editor.getSelection()); - editor.executeCommand(this.id, command); + + EditOperationsCommand.execute(editor, edits); alertFormattingEdits(edits); editor.focus(); }); diff --git a/src/vs/editor/contrib/format/common/format.ts b/src/vs/editor/contrib/format/common/format.ts index e2e1fb84ec3c2baf614b83cd9bb1719ecbfdcf9f..0f8bb56771973064e9a6e440150b97d39e6dc348 100644 --- a/src/vs/editor/contrib/format/common/format.ts +++ b/src/vs/editor/contrib/format/common/format.ts @@ -10,14 +10,14 @@ import URI from 'vs/base/common/uri'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { TPromise } from 'vs/base/common/winjs.base'; import { Range } from 'vs/editor/common/core/range'; -import { IReadOnlyModel, ISingleEditOperation } from 'vs/editor/common/editorCommon'; +import { IReadOnlyModel } from 'vs/editor/common/editorCommon'; import { CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions'; -import { DocumentFormattingEditProviderRegistry, DocumentRangeFormattingEditProviderRegistry, OnTypeFormattingEditProviderRegistry, FormattingOptions } from 'vs/editor/common/modes'; +import { DocumentFormattingEditProviderRegistry, DocumentRangeFormattingEditProviderRegistry, OnTypeFormattingEditProviderRegistry, FormattingOptions, TextEdit } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { asWinJsPromise, sequence } from 'vs/base/common/async'; import { Position } from 'vs/editor/common/core/position'; -export function getDocumentRangeFormattingEdits(model: IReadOnlyModel, range: Range, options: FormattingOptions): TPromise { +export function getDocumentRangeFormattingEdits(model: IReadOnlyModel, range: Range, options: FormattingOptions): TPromise { const providers = DocumentRangeFormattingEditProviderRegistry.ordered(model); @@ -25,7 +25,7 @@ export function getDocumentRangeFormattingEdits(model: IReadOnlyModel, range: Ra return TPromise.as(undefined); } - let result: ISingleEditOperation[]; + let result: TextEdit[]; return sequence(providers.map(provider => { if (isFalsyOrEmpty(result)) { return () => { @@ -38,7 +38,7 @@ export function getDocumentRangeFormattingEdits(model: IReadOnlyModel, range: Ra })).then(() => result); } -export function getDocumentFormattingEdits(model: IReadOnlyModel, options: FormattingOptions): TPromise { +export function getDocumentFormattingEdits(model: IReadOnlyModel, options: FormattingOptions): TPromise { const providers = DocumentFormattingEditProviderRegistry.ordered(model); // try range formatters when no document formatter is registered @@ -46,7 +46,7 @@ export function getDocumentFormattingEdits(model: IReadOnlyModel, options: Forma return getDocumentRangeFormattingEdits(model, model.getFullModelRange(), options); } - let result: ISingleEditOperation[]; + let result: TextEdit[]; return sequence(providers.map(provider => { if (isFalsyOrEmpty(result)) { return () => { @@ -59,7 +59,7 @@ export function getDocumentFormattingEdits(model: IReadOnlyModel, options: Forma })).then(() => result); } -export function getOnTypeFormattingEdits(model: IReadOnlyModel, position: Position, ch: string, options: FormattingOptions): TPromise { +export function getOnTypeFormattingEdits(model: IReadOnlyModel, position: Position, ch: string, options: FormattingOptions): TPromise { const [support] = OnTypeFormattingEditProviderRegistry.ordered(model); if (!support) { return TPromise.as(undefined); @@ -74,7 +74,7 @@ export function getOnTypeFormattingEdits(model: IReadOnlyModel, position: Positi } CommonEditorRegistry.registerLanguageCommand('_executeFormatRangeProvider', function (accessor, args) { - const {resource, range, options} = args; + const { resource, range, options } = args; if (!(resource instanceof URI) || !Range.isIRange(range)) { throw illegalArgument(); } @@ -86,7 +86,7 @@ CommonEditorRegistry.registerLanguageCommand('_executeFormatRangeProvider', func }); CommonEditorRegistry.registerLanguageCommand('_executeFormatDocumentProvider', function (accessor, args) { - const {resource, options} = args; + const { resource, options } = args; if (!(resource instanceof URI)) { throw illegalArgument('resource'); } @@ -99,7 +99,7 @@ CommonEditorRegistry.registerLanguageCommand('_executeFormatDocumentProvider', f }); CommonEditorRegistry.registerDefaultLanguageCommand('_executeFormatOnTypeProvider', function (model, position, args) { - const {ch, options } = args; + const { ch, options } = args; if (typeof ch !== 'string') { throw illegalArgument('ch'); } diff --git a/src/vs/editor/contrib/format/common/formatCommand.ts b/src/vs/editor/contrib/format/common/formatCommand.ts index bfb76e1ec47b250ed5c3d4280d5754d9d05573ed..48130bb6be7978efcd0ffafa5b4db47e0b1110d1 100644 --- a/src/vs/editor/contrib/format/common/formatCommand.ts +++ b/src/vs/editor/contrib/format/common/formatCommand.ts @@ -6,26 +6,42 @@ import * as strings from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; +import { TextEdit } from 'vs/editor/common/modes'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { Selection } from 'vs/editor/common/core/selection'; export class EditOperationsCommand implements editorCommon.ICommand { - private _edits: editorCommon.ISingleEditOperation[]; + static execute(editor: editorCommon.ICommonCodeEditor, edits: TextEdit[]) { + const cmd = new EditOperationsCommand(edits, editor.getSelection()); + editor.executeCommand('formatEditsCommand', cmd); + + if (typeof cmd._newEol === 'number') { + editor.getModel().setEOL(cmd._newEol); + } + } + + private _edits: TextEdit[]; + private _newEol: editorCommon.EndOfLineSequence; + private _initialSelection: Selection; private _selectionId: string; - constructor(edits: editorCommon.ISingleEditOperation[], initialSelection: Selection) { + constructor(edits: TextEdit[], initialSelection: Selection) { this._edits = edits; this._initialSelection = initialSelection; } public getEditOperations(model: editorCommon.ITokenizedModel, builder: editorCommon.IEditOperationBuilder): void { - this._edits + + for (let edit of this._edits) { // We know that this edit.range comes from the mirror model, so it should only contain \n and no \r's - .map((edit) => EditOperationsCommand.trimEdit(edit, model)) - .filter((edit) => edit !== null) // produced above in case the edit.text is identical to the existing text - .forEach((edit) => builder.addEditOperation(Range.lift(edit.range), edit.text)); + let trimEdit = EditOperationsCommand.trimEdit(edit, model); + if (trimEdit !== null) { // produced above in case the edit.text is identical to the existing text + builder.addEditOperation(Range.lift(edit.range), edit.text); + } + this._newEol = edit.eol; + } var selectionIsSet = false; if (Array.isArray(this._edits) && this._edits.length === 1 && this._initialSelection.isEmpty()) { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 74e0df4ffd01e490040ecd3bf51fed1b0d259a6d..0771928b6118ed52c10fa87cb18393cbac8ea17e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4741,6 +4741,12 @@ declare module monaco.languages { provideDocumentSymbols(model: editor.IReadOnlyModel, token: CancellationToken): SymbolInformation[] | Thenable; } + export interface TextEdit { + range: IRange; + text: string; + eol?: editor.EndOfLineSequence; + } + /** * Interface used to format a model */ @@ -4763,7 +4769,7 @@ declare module monaco.languages { /** * Provide formatting edits for a whole document. */ - provideDocumentFormattingEdits(model: editor.IReadOnlyModel, options: FormattingOptions, token: CancellationToken): editor.ISingleEditOperation[] | Thenable; + provideDocumentFormattingEdits(model: editor.IReadOnlyModel, options: FormattingOptions, token: CancellationToken): TextEdit[] | Thenable; } /** @@ -4778,7 +4784,7 @@ declare module monaco.languages { * or larger range. Often this is done by adjusting the start and end * of the range to full syntax nodes. */ - provideDocumentRangeFormattingEdits(model: editor.IReadOnlyModel, range: Range, options: FormattingOptions, token: CancellationToken): editor.ISingleEditOperation[] | Thenable; + provideDocumentRangeFormattingEdits(model: editor.IReadOnlyModel, range: Range, options: FormattingOptions, token: CancellationToken): TextEdit[] | Thenable; } /** @@ -4794,7 +4800,7 @@ declare module monaco.languages { * what range the position to expand to, like find the matching `{` * when `}` has been entered. */ - provideOnTypeFormattingEdits(model: editor.IReadOnlyModel, position: Position, ch: string, options: FormattingOptions, token: CancellationToken): editor.ISingleEditOperation[] | Thenable; + provideOnTypeFormattingEdits(model: editor.IReadOnlyModel, position: Position, ch: string, options: FormattingOptions, token: CancellationToken): TextEdit[] | Thenable; } /** diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index a1c0eab3e2ce6894d589c32df4df2507bebfaae4..692348f1154d0593131e38e856233e6abc24bec1 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -131,6 +131,12 @@ declare module 'vscode' { */ save(): Thenable; + /** + * The [end of line](#EndOfLine) sequence that is predominately + * used in this document. + */ + readonly eol: EndOfLine; + /** * The number of lines in this document. */ @@ -2031,6 +2037,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 +2055,14 @@ declare module 'vscode' { */ newText: string; + /** + * The eol-sequence used in the document. + * + * *Note* that the eol-sequence will be applied to the + * whole document. + */ + newEol: EndOfLine; + /** * Create a new TextEdit. * diff --git a/src/vs/workbench/api/node/extHostDocumentData.ts b/src/vs/workbench/api/node/extHostDocumentData.ts index c8777b54fed86d8c317832c18ebe4cf0a4086457..cdfb255d6e51076bc6571941a878ffe484aead0c 100644 --- a/src/vs/workbench/api/node/extHostDocumentData.ts +++ b/src/vs/workbench/api/node/extHostDocumentData.ts @@ -8,7 +8,7 @@ import { ok } from 'vs/base/common/assert'; import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings'; import { MirrorModel2 } from 'vs/editor/common/model/mirrorModel2'; import URI from 'vs/base/common/uri'; -import { Range, Position } from 'vs/workbench/api/node/extHostTypes'; +import { Range, Position, EndOfLine } from 'vs/workbench/api/node/extHostTypes'; import * as vscode from 'vscode'; import { getWordAtText, ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper'; import { MainThreadDocumentsShape } from './extHost.protocol'; @@ -76,6 +76,7 @@ export class ExtHostDocumentData extends MirrorModel2 { get isDirty() { return data._isDirty; }, save() { return data._save(); }, getText(range?) { return range ? data._getTextInRange(range) : data.getText(); }, + get eol() { return data._eol === '\n' ? EndOfLine.LF : EndOfLine.CRLF; }, get lineCount() { return data._lines.length; }, lineAt(lineOrPos) { return data._lineAt(lineOrPos); }, offsetAt(pos) { return data._offsetAt(pos); }, diff --git a/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts index 038b7d42ea861633a5ecf7a5160426241840bbca..8ee95f6ad0a31421ace9eb5ee3578310051ecd97 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'; @@ -101,10 +101,10 @@ export class ExtHostDocumentSaveParticipant extends ExtHostDocumentSaveParticipa private _deliverEventAsync(listener: Function, thisArg: any, stubEvent: vscode.TextDocumentWillSaveEvent): TPromise { - const promises: TPromise[] = []; + const promises: TPromise[] = []; - const {document, reason} = stubEvent; - const {version} = document; + const { document, reason } = stubEvent; + const { version } = document; const event = Object.freeze({ document, @@ -127,21 +127,23 @@ export class ExtHostDocumentSaveParticipant extends ExtHostDocumentSaveParticipa // freeze promises after event call Object.freeze(promises); - return new TPromise((resolve, reject) => { + return new TPromise((resolve, reject) => { // join on all listener promises, reject after timeout const handle = setTimeout(() => reject(new Error('timeout')), this._thresholds.timeout); return always(TPromise.join(promises), () => clearTimeout(handle)).then(resolve, reject); }).then(values => { - const edits: IResourceEdit[] = []; + let edits: IResourceEdit[] = []; + 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 791e2cdbdb0d8bb3e6bce6849d6c184af1b962d8..aa189c755b1eb524af927475cc3fa199d7c7e112 100644 --- a/src/vs/workbench/api/node/extHostTypeConverters.ts +++ b/src/vs/workbench/api/node/extHostTypeConverters.ts @@ -8,7 +8,7 @@ import Severity from 'vs/base/common/severity'; import * as modes from 'vs/editor/common/modes'; import * as types from './extHostTypes'; import { Position as EditorPosition } from 'vs/platform/editor/common/editor'; -import { IPosition, ISelection, IRange, IDecorationOptions, ISingleEditOperation } from 'vs/editor/common/editorCommon'; +import { IPosition, ISelection, IRange, IDecorationOptions, EndOfLineSequence } from 'vs/editor/common/editorCommon'; import * as vscode from 'vscode'; import URI from 'vs/base/common/uri'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; @@ -46,6 +46,9 @@ export function fromSelection(selection: SelectionLike): ISelection { } export function fromRange(range: RangeLike): IRange { + if (!range) { + return undefined; + } let { start, end } = range; return { startLineNumber: start.line + 1, @@ -56,6 +59,9 @@ export function fromRange(range: RangeLike): IRange { } export function toRange(range: IRange): types.Range { + if (!range) { + return undefined; + } let { startLineNumber, startColumn, endLineNumber, endColumn } = range; return new types.Range(startLineNumber - 1, startColumn - 1, endLineNumber - 1, endColumn - 1); } @@ -153,14 +159,17 @@ export function fromRangeOrRangeWithMessage(ranges: vscode.Range[] | vscode.Deco export const TextEdit = { - from(edit: vscode.TextEdit): ISingleEditOperation { - return { + from(edit: vscode.TextEdit): modes.TextEdit { + return { text: edit.newText, + eol: EndOfLine.from(edit.newEol), range: fromRange(edit.range) }; }, - to(edit: ISingleEditOperation): vscode.TextEdit { - return new types.TextEdit(toRange(edit.range), edit.text); + to(edit: modes.TextEdit): vscode.TextEdit { + let result = new types.TextEdit(toRange(edit.range), edit.text); + result.newEol = EndOfLine.to(edit.eol); + return result; } }; @@ -364,3 +373,26 @@ export namespace TextDocumentSaveReason { } } } + + +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; + } + + export function to(eol: EndOfLineSequence): vscode.EndOfLine { + if (eol === EndOfLineSequence.CRLF) { + return types.EndOfLine.CRLF; + } else if (eol === EndOfLineSequence.LF) { + return types.EndOfLine.LF; + } + return undefined; + } +} + diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index a9c7b1d30e8a9e9a46642d6ef05378a157572c82..0c5b8147fec8045332e6215dcce54a557e674e69 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -399,6 +399,11 @@ export class Selection extends Range { } } +export enum EndOfLine { + LF = 1, + CRLF = 2 +} + export class TextEdit { static isTextEdit(thing: any): thing is TextEdit { @@ -424,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; @@ -443,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; @@ -455,7 +480,8 @@ export class TextEdit { toJSON(): any { return { range: this.range, - newText: this.newText + newText: this.newText, + newEol: this._newEol }; } } @@ -905,11 +931,6 @@ export enum StatusBarAlignment { Right = 2 } -export enum EndOfLine { - LF = 1, - CRLF = 2 -} - export enum TextEditorLineNumbersStyle { Off = 0, On = 1, @@ -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 d7d52f02c37d1b2adc344ad2ea25aee37b62a5f0..38024197279d407468d21823cdda0885fd8ffc81 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -10,7 +10,7 @@ import { relative } from 'path'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; 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'; @@ -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 d9e04d6684a2c3e32966e25ec55604a1af735df6..4fb0b6372c3f2c6cd4fd19481c3d0ad328197ccb 100644 --- a/src/vs/workbench/parts/search/browser/replaceService.ts +++ b/src/vs/workbench/parts/search/browser/replaceService.ts @@ -191,4 +191,4 @@ export class ReplaceService implements IReplaceService { }; return resourceEdit; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/test/node/api/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/test/node/api/extHostDocumentSaveParticipant.test.ts index 81aef369d8414d3ec46b44ad6680c17b11dc914b..9f51444c3a7b8a4dee039f023c82ab663ce5d812 100644 --- a/src/vs/workbench/test/node/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/node/api/extHostDocumentSaveParticipant.test.ts @@ -9,7 +9,7 @@ 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'; @@ -262,12 +262,13 @@ 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); }); }); diff --git a/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts index dba5742402405806f3a579b46fb4de3631a3d162..77bfe3bd520c768090f8df6ae00de27cadb5536c 100644 --- a/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts @@ -927,16 +927,20 @@ suite('ExtHostLanguageFeatures', function () { test('Format Doc, data conversion', function () { disposables.push(extHost.registerDocumentFormattingEditProvider(defaultSelector, { provideDocumentFormattingEdits(): any { - return [new types.TextEdit(new types.Range(0, 0, 0, 0), 'testing')]; + return [new types.TextEdit(new types.Range(0, 0, 0, 0), 'testing'), types.TextEdit.setEndOfLine(types.EndOfLine.LF)]; } })); return threadService.sync().then(() => { return getDocumentFormattingEdits(model, { insertSpaces: true, tabSize: 4 }).then(value => { - assert.equal(value.length, 1); - let [first] = value; + assert.equal(value.length, 2); + let [first, second] = value; assert.equal(first.text, 'testing'); assert.deepEqual(first.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }); + + assert.equal(second.eol, EditorCommon.EndOfLineSequence.LF); + assert.equal(second.text, ''); + assert.equal(second.range, undefined); }); }); }); diff --git a/src/vs/workbench/test/node/api/extHostTypes.test.ts b/src/vs/workbench/test/node/api/extHostTypes.test.ts index b65d6bddaf35cbf42906302467f5c5b1f8c2fcf9..0487d049d9f0d5b767705ded1eda8d5a25309f99 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, '');