提交 2d15717a 编写于 作者: J Johannes Rieken

remove old command and use executeEdits everywhere, #47107

上级 33d551ce
......@@ -14,7 +14,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { registerEditorAction, ServicesAccessor, EditorAction, registerEditorContribution, IActionOptions } from 'vs/editor/browser/editorExtensions';
import { OnTypeFormattingEditProviderRegistry, DocumentRangeFormattingEditProviderRegistry } from 'vs/editor/common/modes';
import { getOnTypeFormattingEdits, getDocumentFormattingEdits, getDocumentRangeFormattingEdits, NoProviderError } from 'vs/editor/contrib/format/format';
import { EditOperationsCommand } from 'vs/editor/contrib/format/formatCommand';
import { FormattingEdit } from 'vs/editor/contrib/format/formattingEdit';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
......@@ -161,7 +161,7 @@ class FormatOnType implements editorCommon.IEditorContribution {
return;
}
EditOperationsCommand.executeAsCommand(this.editor, edits);
FormattingEdit.execute(this.editor, edits);
alertFormattingEdits(edits);
}, (err) => {
......@@ -244,7 +244,7 @@ class FormatOnPaste implements editorCommon.IEditorContribution {
if (!state.validate(this.editor) || isFalsyOrEmpty(edits)) {
return;
}
EditOperationsCommand.execute(this.editor, edits);
FormattingEdit.execute(this.editor, edits);
alertFormattingEdits(edits);
});
}
......@@ -280,7 +280,7 @@ export abstract class AbstractFormatAction extends EditorAction {
return;
}
EditOperationsCommand.execute(editor, edits);
FormattingEdit.execute(editor, edits);
alertFormattingEdits(edits);
editor.focus();
}, err => {
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
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';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ITextModel, EndOfLineSequence, ISingleEditOperation } from 'vs/editor/common/model';
import { EditOperation } from 'vs/editor/common/core/editOperation';
export class EditOperationsCommand implements editorCommon.ICommand {
static _handleEolEdits(editor: ICodeEditor, edits: TextEdit[]): ISingleEditOperation[] {
let newEol: EndOfLineSequence = undefined;
let singleEdits: ISingleEditOperation[] = [];
for (let edit of edits) {
if (typeof edit.eol === 'number') {
newEol = edit.eol;
}
if (edit.range && typeof edit.text === 'string') {
singleEdits.push(edit);
}
}
if (typeof newEol === 'number') {
editor.getModel().setEOL(newEol);
}
return singleEdits;
}
static executeAsCommand(editor: ICodeEditor, _edits: TextEdit[]) {
let edits = this._handleEolEdits(editor, _edits);
const cmd = new EditOperationsCommand(edits, editor.getSelection());
editor.pushUndoStop();
editor.executeCommand('formatEditsCommand', cmd);
editor.pushUndoStop();
}
static isFullModelReplaceEdit(editor: ICodeEditor, edit: ISingleEditOperation): boolean {
const model = editor.getModel();
const editRange = model.validateRange(edit.range);
const fullModelRange = model.getFullModelRange();
return fullModelRange.equalsRange(editRange);
}
static execute(editor: ICodeEditor, _edits: TextEdit[]) {
let edits = this._handleEolEdits(editor, _edits);
editor.pushUndoStop();
if (edits.length === 1 && EditOperationsCommand.isFullModelReplaceEdit(editor, edits[0])) {
// We use replace semantics and hope that markers stay put...
editor.executeEdits('formatEditsCommand', edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
} else {
editor.executeEdits('formatEditsCommand', edits.map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text)));
}
editor.pushUndoStop();
}
private _edits: ISingleEditOperation[];
private _initialSelection: Selection;
private _selectionId: string;
constructor(edits: ISingleEditOperation[], initialSelection: Selection) {
this._initialSelection = initialSelection;
this._edits = edits;
}
public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void {
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
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);
}
}
let selectionIsSet = false;
if (Array.isArray(this._edits) && this._edits.length === 1 && this._initialSelection.isEmpty()) {
if (this._edits[0].range.startColumn === this._initialSelection.endColumn &&
this._edits[0].range.startLineNumber === this._initialSelection.endLineNumber) {
selectionIsSet = true;
this._selectionId = builder.trackSelection(this._initialSelection, true);
} else if (this._edits[0].range.endColumn === this._initialSelection.startColumn &&
this._edits[0].range.endLineNumber === this._initialSelection.startLineNumber) {
selectionIsSet = true;
this._selectionId = builder.trackSelection(this._initialSelection, false);
}
}
if (!selectionIsSet) {
this._selectionId = builder.trackSelection(this._initialSelection);
}
}
public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection {
return helper.getTrackedSelection(this._selectionId);
}
static fixLineTerminators(edit: ISingleEditOperation, model: ITextModel): void {
edit.text = edit.text.replace(/\r\n|\r|\n/g, model.getEOL());
}
/**
* This is used to minimize the edits by removing changes that appear on the edges of the range which are identical
* to the current text.
*
* The reason this was introduced is to allow better selection tracking of the current cursor and solve
* bug #15108. There the cursor was jumping since the tracked selection was in the middle of the range edit
* and was lost.
*/
static trimEdit(edit: ISingleEditOperation, model: ITextModel): ISingleEditOperation {
this.fixLineTerminators(edit, model);
return this._trimEdit(model.validateRange(edit.range), edit.text, edit.forceMoveMarkers, model);
}
static _trimEdit(editRange: Range, editText: string, editForceMoveMarkers: boolean, model: ITextModel): ISingleEditOperation {
let currentText = model.getValueInRange(editRange);
// Find the equal characters in the front
let commonPrefixLength = strings.commonPrefixLength(editText, currentText);
// If the two strings are identical, return no edit (no-op)
if (commonPrefixLength === currentText.length && commonPrefixLength === editText.length) {
return null;
}
if (commonPrefixLength > 0) {
// Apply front trimming
let newStartPosition = model.modifyPosition(editRange.getStartPosition(), commonPrefixLength);
editRange = new Range(newStartPosition.lineNumber, newStartPosition.column, editRange.endLineNumber, editRange.endColumn);
editText = editText.substring(commonPrefixLength);
currentText = currentText.substr(commonPrefixLength);
}
// Find the equal characters in the rear
let commonSuffixLength = strings.commonSuffixLength(editText, currentText);
if (commonSuffixLength > 0) {
// Apply rear trimming
let newEndPosition = model.modifyPosition(editRange.getEndPosition(), -commonSuffixLength);
editRange = new Range(editRange.startLineNumber, editRange.startColumn, newEndPosition.lineNumber, newEndPosition.column);
editText = editText.substring(0, editText.length - commonSuffixLength);
currentText = currentText.substring(0, currentText.length - commonSuffixLength);
}
return {
text: editText,
range: editRange,
forceMoveMarkers: editForceMoveMarkers
};
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range } from 'vs/editor/common/core/range';
import { EndOfLineSequence, ISingleEditOperation } from 'vs/editor/common/model';
import { TextEdit } from 'vs/editor/common/modes';
export class FormattingEdit {
private static _handleEolEdits(editor: ICodeEditor, edits: TextEdit[]): ISingleEditOperation[] {
let newEol: EndOfLineSequence = undefined;
let singleEdits: ISingleEditOperation[] = [];
for (let edit of edits) {
if (typeof edit.eol === 'number') {
newEol = edit.eol;
}
if (edit.range && typeof edit.text === 'string') {
singleEdits.push(edit);
}
}
if (typeof newEol === 'number') {
editor.getModel().setEOL(newEol);
}
return singleEdits;
}
private static _isFullModelReplaceEdit(editor: ICodeEditor, edit: ISingleEditOperation): boolean {
const model = editor.getModel();
const editRange = model.validateRange(edit.range);
const fullModelRange = model.getFullModelRange();
return fullModelRange.equalsRange(editRange);
}
static execute(editor: ICodeEditor, _edits: TextEdit[]) {
let edits = FormattingEdit._handleEolEdits(editor, _edits);
editor.pushUndoStop();
if (edits.length === 1 && FormattingEdit._isFullModelReplaceEdit(editor, edits[0])) {
// We use replace semantics and hope that markers stay put...
editor.executeEdits('formatEditsCommand', edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
} else {
editor.executeEdits('formatEditsCommand', edits.map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text)));
}
editor.pushUndoStop();
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { ISingleEditOperation } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel';
import { EditOperationsCommand } from 'vs/editor/contrib/format/formatCommand';
import { testCommand } from 'vs/editor/test/browser/testCommand';
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, text: string[]): ISingleEditOperation {
return {
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
text: text.join('\n'),
forceMoveMarkers: false
};
}
suite('FormatCommand.trimEdit', () => {
function testTrimEdit(lines: string[], edit: ISingleEditOperation, expected: ISingleEditOperation): void {
let model = TextModel.createFromString(lines.join('\n'));
let actual = EditOperationsCommand.trimEdit(edit, model);
assert.deepEqual(actual, expected);
model.dispose();
}
test('single-line no-op', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1, 1, 1, 10, [
'some text'
]),
null
);
});
test('multi-line no-op 1', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1, 1, 2, 16, [
'some text',
'some other text'
]),
null
);
});
test('multi-line no-op 2', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1, 1, 2, 1, [
'some text',
''
]),
null
);
});
test('simple prefix, no suffix', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1, 1, 1, 10, [
'some interesting thing'
]),
editOp(1, 6, 1, 10, [
'interesting thing'
])
);
});
test('whole line prefix, no suffix', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1, 1, 1, 10, [
'some text',
'interesting thing'
]),
editOp(1, 10, 1, 10, [
'',
'interesting thing'
])
);
});
test('multi-line prefix, no suffix', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1, 1, 2, 16, [
'some text',
'some other interesting thing'
]),
editOp(2, 12, 2, 16, [
'interesting thing'
])
);
});
test('no prefix, simple suffix', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1, 1, 1, 10, [
'interesting text'
]),
editOp(1, 1, 1, 5, [
'interesting'
])
);
});
test('no prefix, whole line suffix', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1, 1, 1, 10, [
'interesting thing',
'some text'
]),
editOp(1, 1, 1, 1, [
'interesting thing',
''
])
);
});
test('no prefix, multi-line suffix', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1, 1, 2, 16, [
'interesting thing text',
'some other text'
]),
editOp(1, 1, 1, 5, [
'interesting thing'
])
);
});
test('no overlapping prefix & suffix', () => {
testTrimEdit(
[
'some cool text'
],
editOp(1, 1, 1, 15, [
'some interesting text'
]),
editOp(1, 6, 1, 10, [
'interesting'
])
);
});
test('overlapping prefix & suffix 1', () => {
testTrimEdit(
[
'some cool text'
],
editOp(1, 1, 1, 15, [
'some cool cool text'
]),
editOp(1, 11, 1, 11, [
'cool '
])
);
});
test('overlapping prefix & suffix 2', () => {
testTrimEdit(
[
'some cool cool text'
],
editOp(1, 1, 1, 29, [
'some cool text'
]),
editOp(1, 11, 1, 16, [
''
])
);
});
});
suite('FormatCommand', () => {
function testFormatCommand(lines: string[], selection: Selection, edits: ISingleEditOperation[], expectedLines: string[], expectedSelection: Selection): void {
testCommand(lines, null, selection, (sel) => new EditOperationsCommand(edits, sel), expectedLines, expectedSelection);
}
test('no-op', () => {
testFormatCommand(
[
'some text',
'some other text'
],
new Selection(2, 1, 2, 5),
[
editOp(1, 1, 2, 16, [
'some text',
'some other text'
])
],
[
'some text',
'some other text'
],
new Selection(2, 1, 2, 5)
);
});
test('trim beginning', () => {
testFormatCommand(
[
'some text',
'some other text'
],
new Selection(2, 1, 2, 5),
[
editOp(1, 1, 2, 16, [
'some text',
'some new other text'
])
],
[
'some text',
'some new other text'
],
new Selection(2, 1, 2, 5)
);
});
test('issue #144', () => {
testFormatCommand(
[
'package caddy',
'',
'func main() {',
'\tfmt.Println("Hello World! :)")',
'}',
''
],
new Selection(1, 1, 1, 1),
[
editOp(1, 1, 6, 1, [
'package caddy',
'',
'import "fmt"',
'',
'func main() {',
'\tfmt.Println("Hello World! :)")',
'}',
''
])
],
[
'package caddy',
'',
'import "fmt"',
'',
'func main() {',
'\tfmt.Println("Hello World! :)")',
'}',
''
],
new Selection(1, 1, 1, 1)
);
});
test('issue #23765', () => {
testFormatCommand(
[
' let a;'
],
new Selection(1, 1, 1, 1),
[
editOp(1, 1, 1, 2, [
''
])
],
[
'let a;'
],
new Selection(1, 1, 1, 1)
);
});
test('issue #44870', () => {
const initialText = [
'[',
' {},{',
' }',
']',
];
withTestCodeEditor(initialText, {}, (editor) => {
editor.setSelection(new Selection(2, 8, 2, 8));
EditOperationsCommand.execute(editor, [
editOp(2, 8, 2, 8, ['', ' ']),
editOp(2, 9, 3, 5, ['']),
]);
assert.equal(editor.getValue(), [
'[',
' {},',
' {}',
']',
].join('\n'));
assert.deepEqual(editor.getSelection(), new Selection(3, 5, 3, 5));
});
});
test('issue #47382: full model replace moves cursor to end of file', () => {
const initialText = [
'just some',
'Text',
'...more text'
];
withTestCodeEditor(initialText, {}, (editor) => {
editor.setSelection(new Selection(2, 1, 2, 1));
EditOperationsCommand.execute(editor, [{
range: new Range(1, 1, 3, 13),
text: [
'just some',
'\tText',
'...more text'
].join('\n')
}]);
assert.equal(editor.getValue(), [
'just some',
'\tText',
'...more text'
].join('\n'));
assert.deepEqual(editor.getSelection(), new Selection(2, 1, 2, 1));
});
});
});
......@@ -16,7 +16,7 @@ import { Selection } from 'vs/editor/common/core/selection';
import { Position } from 'vs/editor/common/core/position';
import { trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand';
import { getDocumentFormattingEdits, NoProviderError } from 'vs/editor/contrib/format/format';
import { EditOperationsCommand } from 'vs/editor/contrib/format/formatCommand';
import { FormattingEdit } from 'vs/editor/contrib/format/formattingEdit';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../node/extHost.protocol';
......@@ -239,7 +239,7 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant {
}
private _editsWithEditor(editor: ICodeEditor, edits: ISingleEditOperation[]): void {
EditOperationsCommand.execute(editor, edits);
FormattingEdit.execute(editor, edits);
}
private _editWithModel(model: ITextModel, edits: ISingleEditOperation[]): void {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册