提交 45378eb8 编写于 作者: J Johannes Rieken 提交者: GitHub

Merge pull request #22303 from Microsoft/joh/eol

TextDocument#eol
......@@ -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[] = [];
......
......@@ -516,6 +516,12 @@ export interface DocumentSymbolProvider {
provideDocumentSymbols(model: editorCommon.IReadOnlyModel, token: CancellationToken): SymbolInformation[] | Thenable<SymbolInformation[]>;
}
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<editorCommon.ISingleEditOperation[]>;
provideDocumentFormattingEdits(model: editorCommon.IReadOnlyModel, options: FormattingOptions, token: CancellationToken): TextEdit[] | Thenable<TextEdit[]>;
}
/**
* 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<editorCommon.ISingleEditOperation[]>;
provideDocumentRangeFormattingEdits(model: editorCommon.IReadOnlyModel, range: Range, options: FormattingOptions, token: CancellationToken): TextEdit[] | Thenable<TextEdit[]>;
}
/**
* 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<editorCommon.ISingleEditOperation[]>;
provideOnTypeFormattingEdits(model: editorCommon.IReadOnlyModel, position: Position, ch: string, options: FormattingOptions, token: CancellationToken): TextEdit[] | Thenable<TextEdit[]>;
}
/**
......
......@@ -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<ITextEditorModel>;
private _edits: IIdentifiedSingleEditOperation[];
private _newEol: EndOfLineSequence;
constructor(modelReference: IReference<ITextEditorModel>) {
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[] {
......
......@@ -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();
});
......
......@@ -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<ISingleEditOperation[]> {
export function getDocumentRangeFormattingEdits(model: IReadOnlyModel, range: Range, options: FormattingOptions): TPromise<TextEdit[]> {
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<ISingleEditOperation[]> {
export function getDocumentFormattingEdits(model: IReadOnlyModel, options: FormattingOptions): TPromise<TextEdit[]> {
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<ISingleEditOperation[]> {
export function getOnTypeFormattingEdits(model: IReadOnlyModel, position: Position, ch: string, options: FormattingOptions): TPromise<TextEdit[]> {
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');
}
......
......@@ -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()) {
......
......@@ -4741,6 +4741,12 @@ declare module monaco.languages {
provideDocumentSymbols(model: editor.IReadOnlyModel, token: CancellationToken): SymbolInformation[] | Thenable<SymbolInformation[]>;
}
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<editor.ISingleEditOperation[]>;
provideDocumentFormattingEdits(model: editor.IReadOnlyModel, options: FormattingOptions, token: CancellationToken): TextEdit[] | Thenable<TextEdit[]>;
}
/**
......@@ -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<editor.ISingleEditOperation[]>;
provideDocumentRangeFormattingEdits(model: editor.IReadOnlyModel, range: Range, options: FormattingOptions, token: CancellationToken): TextEdit[] | Thenable<TextEdit[]>;
}
/**
......@@ -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<editor.ISingleEditOperation[]>;
provideOnTypeFormattingEdits(model: editor.IReadOnlyModel, position: Position, ch: string, options: FormattingOptions, token: CancellationToken): TextEdit[] | Thenable<TextEdit[]>;
}
/**
......
......@@ -131,6 +131,12 @@ declare module 'vscode' {
*/
save(): Thenable<boolean>;
/**
* 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.
*
......
......@@ -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); },
......
......@@ -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<any> {
const promises: TPromise<any | vscode.TextEdit[]>[] = [];
const promises: TPromise<vscode.TextEdit[]>[] = [];
const {document, reason} = stubEvent;
const {version} = document;
const { document, reason } = stubEvent;
const { version } = document;
const event = Object.freeze(<vscode.TextDocumentWillSaveEvent>{
document,
......@@ -127,21 +127,23 @@ export class ExtHostDocumentSaveParticipant extends ExtHostDocumentSaveParticipa
// freeze promises after event call
Object.freeze(promises);
return new TPromise<any[]>((resolve, reject) => {
return new TPromise<vscode.TextEdit[][]>((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) && (<vscode.TextEdit[]>value).every(e => e instanceof TextEdit)) {
for (const {newText, range} of value) {
for (const { newText, newEol, range } of value) {
edits.push({
resource: <URI>document.uri,
range: fromRange(range),
newText
range: range && fromRange(range),
newText,
newEol: EndOfLine.from(newEol)
});
}
}
......
......@@ -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 <ISingleEditOperation>{
from(edit: vscode.TextEdit): modes.TextEdit {
return <modes.TextEdit>{
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;
}
}
......@@ -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
}
......@@ -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>uri,
newText: edit.newText,
range: fromRange(edit.range)
newEol: EndOfLine.from(edit.newEol),
range: edit.range && fromRange(edit.range)
});
}
}
......
......@@ -191,4 +191,4 @@ export class ReplaceService implements IReplaceService {
};
return resourceEdit;
}
}
\ No newline at end of file
}
......@@ -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);
});
});
......
......@@ -927,16 +927,20 @@ suite('ExtHostLanguageFeatures', function () {
test('Format Doc, data conversion', function () {
disposables.push(extHost.registerDocumentFormattingEditProvider(defaultSelector, <vscode.DocumentFormattingEditProvider>{
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);
});
});
});
......
......@@ -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, '');
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册