提交 078c3c18 编写于 作者: J Johannes Rieken

fix #9764

上级 0a0bfedb
......@@ -381,7 +381,7 @@ export class SnippetController {
public run(snippet: CodeSnippet, overwriteBefore: number, overwriteAfter: number, stripPrefix?: boolean): void {
this._runAndRestoreController(() => {
if (snippet.isInsertOnly/* || snippet.isSingleTabstopOnly*/) {
if (snippet.isInsertOnly || snippet.isSingleTabstopOnly) {
// Only inserts text, not placeholders, tabstops etc
// Only cursor endposition
this._runForAllSelections(snippet, overwriteBefore, overwriteAfter, stripPrefix);
......@@ -483,41 +483,77 @@ export class SnippetController {
const edits: editorCommon.IIdentifiedSingleEditOperation[] = [];
const selections = this._editor.getSelections();
let lineDelta = 0;
let columnDelta = 0;
const model = this._editor.getModel();
let totalDelta = 0;
const newSelections: { offset: number; i: number }[] = [];
// sort selections by start position but remember where
// each selection came from
const selectionEntries = selections
.map((selection, i) => ({ selection, i }))
.sort((a, b) => Range.compareRangesUsingStarts(a.selection, b.selection));
for (const {selection, i} of selectionEntries) {
let {adaptedSnippet, typeRange} = SnippetController._prepareSnippet(this._editor, selection, snippet, overwriteBefore, overwriteAfter, stripPrefix);
for (let i = 0; i < selections.length; i++) {
let {adaptedSnippet, typeRange} = SnippetController._prepareSnippet(this._editor, selections[i], snippet, overwriteBefore, overwriteAfter, stripPrefix);
SnippetController._addCommandForSnippet(this._editor.getModel(), adaptedSnippet, typeRange, edits);
if (i === 0 && snippet.isSingleTabstopOnly) {
const finalCursorPos = SnippetController._getSnippetCursorOnly(adaptedSnippet);
const editEnd = typeRange.getEndPosition();
editEnd.lineNumber += adaptedSnippet.lines.length - 1;
editEnd.column = adaptedSnippet.lines[adaptedSnippet.lines.length - 1].length + 1;
// compute new selection offset
// * get current offset
// * get length of snippet that we insert
// * get final cursor position of snippet that we insert (might not exist)
// * NEW selection offset is current + final cursor pos + inserts_until_here
let offset = model.getOffsetAt(typeRange.getStartPosition());
// inserts until here
offset += totalDelta;
// each snippet has a different length (because of whitespace changes)
let snippetLength = (adaptedSnippet.lines.length - 1) * model.getEOL().length;
for (const line of adaptedSnippet.lines) {
snippetLength += line.length;
}
// each snippet has a different cursor offset
const finalCursorPos = SnippetController._getSnippetCursorOnly(adaptedSnippet);
if (finalCursorPos) {
let finalCursorOffset: number;
if (finalCursorPos.lineNumber === typeRange.startLineNumber) {
finalCursorOffset = finalCursorPos.column - typeRange.startColumn;
} else {
finalCursorOffset = finalCursorPos.column - 1;
for (let i = 0, lineNumber = typeRange.startLineNumber; lineNumber < finalCursorPos.lineNumber; i++, lineNumber++) {
finalCursorOffset += adaptedSnippet.lines[i].length + model.getEOL().length;
}
}
offset += finalCursorOffset;
lineDelta = finalCursorPos.lineNumber - editEnd.lineNumber;
columnDelta = finalCursorPos.column - editEnd.column;
} else {
offset += snippetLength;
}
newSelections.push({ offset, i });
totalDelta += (snippetLength - model.getValueLengthInRange(typeRange));
}
if (edits.length === 0) {
return;
}
const cursorStateComputer: editorCommon.ICursorStateComputer = function (inverseEdits) {
return inverseEdits.map((edit, i) => {
let {endLineNumber, endColumn} = edit.range;
endLineNumber += lineDelta;
endColumn += columnDelta;
return new Selection(endLineNumber, endColumn, endLineNumber, endColumn);
});
const cursorStateComputer: editorCommon.ICursorStateComputer = function () {
// create new selections from the new selection offsets
// and restore the order we had at the beginning
const result: Selection[] = [];
for (const {offset, i} of newSelections) {
const pos = model.getPositionAt(offset);
result[i] = new Selection(pos.lineNumber, pos.column, pos.lineNumber, pos.column);
}
return result;
};
const model = this._editor.getModel();
model.pushStackElement();
this._editor.setSelections(model.pushEditOperations(selections, edits, cursorStateComputer));
model.pushStackElement();
......
......@@ -23,14 +23,19 @@ class TestSnippetController extends SnippetController {
suite('SnippetController', () => {
function snippetTest(cb:(editor:MockCodeEditor, cursor:Cursor, codeSnippet: CodeSnippet, snippetController:TestSnippetController)=>void): void {
withMockCodeEditor([
'function test() {',
'\tvar x = 3;',
'\tvar arr = [];',
'\t',
'}'
], {}, (editor, cursor) => {
function snippetTest(cb: (editor: MockCodeEditor, cursor: Cursor, codeSnippet: CodeSnippet, snippetController: TestSnippetController) => void, lines?: string[]): void {
if (!lines) {
lines = [
'function test() {',
'\tvar x = 3;',
'\tvar arr = [];',
'\t',
'}'
];
};
withMockCodeEditor(lines, {}, (editor, cursor) => {
editor.getModel().updateOptions({
insertSpaces: false
});
......@@ -222,64 +227,201 @@ suite('SnippetController', () => {
});
test('Final tabstop with multiple selections', () => {
// snippetTest((editor, cursor, codeSnippet, snippetController) => {
// editor.setSelections([
// new Selection(1, 1, 1, 1),
// new Selection(2, 1, 2, 1),
// ]);
// codeSnippet = CodeSnippet.fromInternal('foo{{}}');
// snippetController.run(codeSnippet, 0, 0, false);
// assert.equal(editor.getSelections().length, 2);
// const [first, second] = editor.getSelections();
// assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString());
// assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 4, endLineNumber: 2, endColumn: 4 }), second.toString());
// });
// snippetTest((editor, cursor, codeSnippet, snippetController) => {
// editor.setSelections([
// new Selection(1, 1, 1, 1),
// new Selection(2, 1, 2, 1),
// ]);
// codeSnippet = CodeSnippet.fromInternal('foo{{}}bar');
// snippetController.run(codeSnippet, 0, 0, false);
// assert.equal(editor.getSelections().length, 2);
// const [first, second] = editor.getSelections();
// assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString());
// assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 4, endLineNumber: 2, endColumn: 4 }), second.toString());
// });
// snippetTest((editor, cursor, codeSnippet, snippetController) => {
// editor.setSelections([
// new Selection(1, 1, 1, 1),
// new Selection(1, 5, 1, 5),
// ]);
// codeSnippet = CodeSnippet.fromInternal('foo{{}}bar');
// snippetController.run(codeSnippet, 0, 0, false);
// assert.equal(editor.getSelections().length, 2);
// const [first, second] = editor.getSelections();
// assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString());
// assert.ok(second.equalsRange({ startLineNumber: 1, startColumn: 14, endLineNumber: 1, endColumn: 14 }), second.toString());
// });
// snippetTest((editor, cursor, codeSnippet, snippetController) => {
// editor.setSelections([
// new Selection(1, 1, 1, 1),
// new Selection(1, 5, 1, 5),
// ]);
// codeSnippet = CodeSnippet.fromInternal('foo\n{{}}\nbar');
// snippetController.run(codeSnippet, 0, 0, false);
// assert.equal(editor.getSelections().length, 2);
// const [first, second] = editor.getSelections();
// assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), first.toString());
// assert.ok(second.equalsRange({ startLineNumber: 4, startColumn: 1, endLineNumber: 4, endColumn: 1 }), second.toString());
// });
snippetTest((editor, cursor, codeSnippet, snippetController) => {
editor.setSelections([
new Selection(1, 1, 1, 1),
new Selection(2, 1, 2, 1),
]);
codeSnippet = CodeSnippet.fromInternal('foo{{}}');
snippetController.run(codeSnippet, 0, 0, false);
assert.equal(editor.getSelections().length, 2);
const [first, second] = editor.getSelections();
assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 4, endLineNumber: 2, endColumn: 4 }), second.toString());
});
snippetTest((editor, cursor, codeSnippet, snippetController) => {
editor.setSelections([
new Selection(1, 1, 1, 1),
new Selection(2, 1, 2, 1),
]);
codeSnippet = CodeSnippet.fromInternal('foo{{}}bar');
snippetController.run(codeSnippet, 0, 0, false);
assert.equal(editor.getSelections().length, 2);
const [first, second] = editor.getSelections();
assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 4, endLineNumber: 2, endColumn: 4 }), second.toString());
});
snippetTest((editor, cursor, codeSnippet, snippetController) => {
editor.setSelections([
new Selection(1, 1, 1, 1),
new Selection(1, 5, 1, 5),
]);
codeSnippet = CodeSnippet.fromInternal('foo{{}}bar');
snippetController.run(codeSnippet, 0, 0, false);
assert.equal(editor.getSelections().length, 2);
const [first, second] = editor.getSelections();
assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 1, startColumn: 14, endLineNumber: 1, endColumn: 14 }), second.toString());
});
snippetTest((editor, cursor, codeSnippet, snippetController) => {
editor.setSelections([
new Selection(1, 1, 1, 1),
new Selection(1, 5, 1, 5),
]);
codeSnippet = CodeSnippet.fromInternal('foo\n{{}}\nbar');
snippetController.run(codeSnippet, 0, 0, false);
assert.equal(editor.getSelections().length, 2);
const [first, second] = editor.getSelections();
assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 4, startColumn: 1, endLineNumber: 4, endColumn: 1 }), second.toString());
});
snippetTest((editor, cursor, codeSnippet, snippetController) => {
editor.setSelections([
new Selection(1, 1, 1, 1),
new Selection(1, 5, 1, 5),
]);
codeSnippet = CodeSnippet.fromInternal('foo\n{{}}\nbar');
snippetController.run(codeSnippet, 0, 0, false);
assert.equal(editor.getSelections().length, 2);
const [first, second] = editor.getSelections();
assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 4, startColumn: 1, endLineNumber: 4, endColumn: 1 }), second.toString());
});
snippetTest((editor, cursor, codeSnippet, snippetController) => {
editor.setSelections([
new Selection(2, 7, 2, 7),
]);
codeSnippet = CodeSnippet.fromInternal('xo{{}}r');
snippetController.run(codeSnippet, 1, 0, false);
assert.equal(editor.getSelections().length, 1);
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 2, startColumn: 8, endColumn: 8, endLineNumber: 2 }));
});
});
test('Final tabstop, #11742 simple', () => {
snippetTest((editor, cursor, codeSnippet, controller) => {
editor.setSelection(new Selection(1, 19, 1, 19));
codeSnippet = CodeSnippet.fromTextmate('{{% url_**$1** %}}');
controller.run(codeSnippet, 2, 0, true);
assert.equal(editor.getSelections().length, 1);
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 1, startColumn: 27, endLineNumber: 1, endColumn: 27 }));
assert.equal(editor.getModel().getValue(), 'example example {{% url_**** %}}');
}, ['example example sc']);
snippetTest((editor, cursor, codeSnippet, controller) => {
editor.setSelection(new Selection(1, 3, 1, 3));
codeSnippet = CodeSnippet.fromTextmate([
'afterEach((done) => {',
'\t${1}test',
'});'
].join('\n'));
controller.run(codeSnippet, 2, 0, true);
assert.equal(editor.getSelections().length, 1);
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 2, startColumn: 2, endLineNumber: 2, endColumn: 2 }), editor.getSelection().toString());
assert.equal(editor.getModel().getValue(), 'afterEach((done) => {\n\ttest\n});');
}, ['af']);
snippetTest((editor, cursor, codeSnippet, controller) => {
editor.setSelection(new Selection(1, 3, 1, 3));
codeSnippet = CodeSnippet.fromTextmate([
'afterEach((done) => {',
'${1}\ttest',
'});'
].join('\n'));
controller.run(codeSnippet, 2, 0, true);
assert.equal(editor.getSelections().length, 1);
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), editor.getSelection().toString());
assert.equal(editor.getModel().getValue(), 'afterEach((done) => {\n\ttest\n});');
}, ['af']);
snippetTest((editor, cursor, codeSnippet, controller) => {
editor.setSelection(new Selection(1, 9, 1, 9));
codeSnippet = CodeSnippet.fromTextmate([
'aft${1}er'
].join('\n'));
controller.run(codeSnippet, 8, 0, true);
assert.equal(editor.getModel().getValue(), 'after');
assert.equal(editor.getSelections().length, 1);
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), editor.getSelection().toString());
}, ['afterone']);
});
test('Final tabstop, #11742 different indents', () => {
snippetTest((editor, cursor, codeSnippet, controller) => {
editor.setSelections([
new Selection(2, 4, 2, 4),
new Selection(1, 3, 1, 3)
]);
codeSnippet = CodeSnippet.fromTextmate([
'afterEach((done) => {',
'\t${1}test',
'});'
].join('\n'));
controller.run(codeSnippet, 2, 0, true);
assert.equal(editor.getSelections().length, 2);
const [first, second] = editor.getSelections();
assert.ok(first.equalsRange({ startLineNumber: 5, startColumn: 3, endLineNumber: 5, endColumn: 3 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 2, endLineNumber: 2, endColumn: 2 }), second.toString());
}, ['af', '\taf']);
});
test('Final tabstop, no tabstop', () => {
snippetTest((editor, cursor, codeSnippet, controller) => {
editor.setSelections([
new Selection(1, 3, 1, 3)
]);
codeSnippet = CodeSnippet.fromTextmate('afterEach');
controller.run(codeSnippet, 2, 0, true);
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 1, startColumn: 10, endLineNumber: 1, endColumn: 10 }));
}, ['af', '\taf']);
});
});
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册