mirror of
https://github.com/Microsoft/vscode
synced 2024-08-28 05:19:39 +00:00
commit
45378eb8d8
|
@ -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()) {
|
||||
|
|
12
src/vs/monaco.d.ts
vendored
12
src/vs/monaco.d.ts
vendored
|
@ -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[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
22
src/vs/vscode.d.ts
vendored
22
src/vs/vscode.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, '');
|
||||
|
|
Loading…
Reference in a new issue