mirror of
https://github.com/VSCodeVim/Vim.git
synced 2024-11-20 11:22:16 +03:00
Support multi-cursor in test harness
Now you can just put multiple cursors into the 'start' or 'end' of a test, a few examples are in multicursor.test.ts. I've also done some general refactor of testSimplifier.ts to make it more straightforward. ALSO, a slight refactor in VimState (making isMultiCursor a function of cursors.length) Fixes #4582
This commit is contained in:
parent
c915239068
commit
d80c8c8550
@ -511,7 +511,7 @@ class CommandEsc extends BaseCommand {
|
||||
}
|
||||
|
||||
if (vimState.currentMode === Mode.Normal && vimState.isMultiCursor) {
|
||||
vimState.isMultiCursor = false;
|
||||
vimState.collapseCursors();
|
||||
}
|
||||
|
||||
if (vimState.currentMode === Mode.EasyMotionMode) {
|
||||
@ -533,10 +533,6 @@ class CommandEsc extends BaseCommand {
|
||||
|
||||
await vimState.setCurrentMode(Mode.Normal);
|
||||
|
||||
if (!vimState.isMultiCursor) {
|
||||
vimState.cursors = [vimState.cursors[0]];
|
||||
}
|
||||
|
||||
return vimState;
|
||||
}
|
||||
}
|
||||
@ -2649,7 +2645,6 @@ class CommandInsertNewLineAbove extends BaseCommand {
|
||||
}
|
||||
vimState.cursors = vimState.cursors.reverse();
|
||||
vimState.isFakeMultiCursor = true;
|
||||
vimState.isMultiCursor = true;
|
||||
return vimState;
|
||||
}
|
||||
}
|
||||
@ -2689,7 +2684,6 @@ class CommandInsertNewLineBefore extends BaseCommand {
|
||||
}
|
||||
vimState.cursors = vimState.cursors.reverse();
|
||||
vimState.isFakeMultiCursor = true;
|
||||
vimState.isMultiCursor = true;
|
||||
return vimState;
|
||||
}
|
||||
}
|
||||
@ -3594,7 +3588,6 @@ class ActionGoToInsertVisualBlockMode extends BaseCommand {
|
||||
|
||||
public async exec(position: Position, vimState: VimState): Promise<VimState> {
|
||||
await vimState.setCurrentMode(Mode.Insert);
|
||||
vimState.isMultiCursor = true;
|
||||
vimState.isFakeMultiCursor = true;
|
||||
|
||||
for (const { line, start } of TextEditor.iterateLinesInBlock(vimState)) {
|
||||
@ -3626,7 +3619,6 @@ class ActionChangeInVisualBlockMode extends BaseCommand {
|
||||
}
|
||||
|
||||
await vimState.setCurrentMode(Mode.Insert);
|
||||
vimState.isMultiCursor = true;
|
||||
vimState.isFakeMultiCursor = true;
|
||||
|
||||
for (const { start } of TextEditor.iterateLinesInBlock(vimState)) {
|
||||
@ -3656,7 +3648,6 @@ class ActionChangeToEOLInVisualBlockMode extends BaseCommand {
|
||||
}
|
||||
|
||||
await vimState.setCurrentMode(Mode.Insert);
|
||||
vimState.isMultiCursor = true;
|
||||
vimState.isFakeMultiCursor = true;
|
||||
|
||||
for (const { end } of TextEditor.iterateLinesInBlock(vimState)) {
|
||||
@ -3681,7 +3672,6 @@ abstract class ActionGoToInsertVisualLineModeCommand extends BaseCommand {
|
||||
|
||||
public async exec(position: Position, vimState: VimState): Promise<VimState> {
|
||||
await vimState.setCurrentMode(Mode.Insert);
|
||||
vimState.isMultiCursor = true;
|
||||
vimState.isFakeMultiCursor = true;
|
||||
|
||||
vimState.cursors = [];
|
||||
@ -3778,7 +3768,6 @@ class ActionGoToInsertVisualBlockModeAppend extends BaseCommand {
|
||||
|
||||
public async exec(position: Position, vimState: VimState): Promise<VimState> {
|
||||
await vimState.setCurrentMode(Mode.Insert);
|
||||
vimState.isMultiCursor = true;
|
||||
vimState.isFakeMultiCursor = true;
|
||||
|
||||
for (const { line, end } of TextEditor.iterateLinesInBlock(vimState)) {
|
||||
|
@ -102,8 +102,7 @@ class CommandEscInsertMode extends BaseCommand {
|
||||
}
|
||||
|
||||
if (vimState.isFakeMultiCursor) {
|
||||
vimState.cursors = [vimState.cursors[0]];
|
||||
vimState.isMultiCursor = false;
|
||||
vimState.collapseCursors();
|
||||
vimState.isFakeMultiCursor = false;
|
||||
}
|
||||
return vimState;
|
||||
|
@ -162,10 +162,6 @@ export class ModeHandler implements vscode.Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.selections.length === 1) {
|
||||
this.vimState.isMultiCursor = false;
|
||||
}
|
||||
|
||||
if (isStatusBarMode(this.vimState.currentMode)) {
|
||||
return;
|
||||
}
|
||||
|
@ -50,12 +50,6 @@ export class VimState implements vscode.Disposable {
|
||||
*/
|
||||
public lastKeyPressedTimestamp = 0;
|
||||
|
||||
/**
|
||||
* Are multiple cursors currently present?
|
||||
*/
|
||||
// TODO: why isn't this a function?
|
||||
public isMultiCursor = false;
|
||||
|
||||
/**
|
||||
* Is the multicursor something like visual block "multicursor", where
|
||||
* natively in vim there would only be one cursor whose changes were applied
|
||||
@ -174,7 +168,6 @@ export class VimState implements vscode.Disposable {
|
||||
}
|
||||
|
||||
this._cursors = Array.from(map.values());
|
||||
this.isMultiCursor = this._cursors.length > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -188,6 +181,16 @@ export class VimState implements vscode.Disposable {
|
||||
this._cursorsInitialState = Object.assign([], value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Are multiple cursors currently present?
|
||||
*/
|
||||
public get isMultiCursor(): boolean {
|
||||
return this.cursors.length > 1;
|
||||
}
|
||||
public collapseCursors(): void {
|
||||
this.cursors = [this.cursors[0]];
|
||||
}
|
||||
|
||||
public isRecordingMacro: boolean = false;
|
||||
public isReplayingMacro: boolean = false;
|
||||
|
||||
|
@ -139,6 +139,10 @@ export class TextEditor {
|
||||
return vscode.window.activeTextEditor!.selection;
|
||||
}
|
||||
|
||||
static getSelections(): vscode.Range[] {
|
||||
return vscode.window.activeTextEditor!.selections;
|
||||
}
|
||||
|
||||
static getText(selection?: vscode.Range): string {
|
||||
return vscode.window.activeTextEditor!.document.getText(selection);
|
||||
}
|
||||
|
@ -1940,8 +1940,8 @@ suite('Mode Normal', () => {
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'can ctrl-a on an octal ',
|
||||
start: ['07|'],
|
||||
title: 'can ctrl-a on an octal',
|
||||
start: ['0|7'],
|
||||
keysPressed: '<C-a>',
|
||||
end: ['01|0'],
|
||||
});
|
||||
@ -2309,10 +2309,10 @@ suite('Mode Normal', () => {
|
||||
title: "Can 'D'elete the characters under multiple cursors until the end of the line",
|
||||
start: [
|
||||
'test aaa test aaa test aaa test |aaa test',
|
||||
'test aaa test aaa test aaa test aaa test',
|
||||
'test aaa test aaa test aaa test |aaa test',
|
||||
],
|
||||
keysPressed: '<C-alt+down>D<Esc>',
|
||||
end: ['test aaa test aaa test aaa test| ', 'test aaa test aaa test aaa test '],
|
||||
keysPressed: 'D',
|
||||
end: ['test aaa test aaa test aaa test| ', 'test aaa test aaa test aaa test| '],
|
||||
});
|
||||
|
||||
newTest({
|
||||
|
@ -1338,21 +1338,24 @@ suite('Mode Visual', () => {
|
||||
title: 'multiline insert from bottom up selection',
|
||||
start: ['111', '222', '333', '4|44', '555'],
|
||||
keysPressed: 'vkkI_',
|
||||
end: ['111', '2_|22', '_333', '_444', '555'],
|
||||
end: ['111', '2_|22', '_|333', '_|444', '555'],
|
||||
endMode: Mode.Insert,
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'multiline insert from top down selection',
|
||||
start: ['111', '2|22', '333', '444', '555'],
|
||||
keysPressed: 'vjjI_',
|
||||
end: ['111', '2_|22', '_333', '_444', '555'],
|
||||
end: ['111', '2_|22', '_|333', '_|444', '555'],
|
||||
endMode: Mode.Insert,
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'skips blank lines',
|
||||
start: ['111', '2|22', ' ', '444', '555'],
|
||||
keysPressed: 'vjjI_',
|
||||
end: ['111', '2_|22', ' ', '_444', '555'],
|
||||
end: ['111', '2_|22', ' ', '_|444', '555'],
|
||||
endMode: Mode.Insert,
|
||||
});
|
||||
});
|
||||
|
||||
@ -1361,21 +1364,24 @@ suite('Mode Visual', () => {
|
||||
title: 'multiline append from bottom up selection',
|
||||
start: ['111', '222', '333', '4|44', '555'],
|
||||
keysPressed: 'vkkA_',
|
||||
end: ['111', '222_|', '333_', '44_4', '555'],
|
||||
end: ['111', '222_|', '333_|', '44_|4', '555'],
|
||||
endMode: Mode.Insert,
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'multiline append from top down selection',
|
||||
start: ['111', '2|22', '333', '444', '555'],
|
||||
keysPressed: 'vjjA_',
|
||||
end: ['111', '222_|', '333_', '44_4', '555'],
|
||||
end: ['111', '222_|', '333_|', '44_|4', '555'],
|
||||
endMode: Mode.Insert,
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'skips blank lines',
|
||||
start: ['111', '2|22', ' ', '444', '555'],
|
||||
keysPressed: 'vjjA_',
|
||||
end: ['111', '222_|', ' ', '44_4', '555'],
|
||||
end: ['111', '222_|', ' ', '44_|4', '555'],
|
||||
endMode: Mode.Insert,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -33,63 +33,64 @@ suite('Mode Visual Block', () => {
|
||||
title: 'Can handle A forward select',
|
||||
start: ['|test', 'test'],
|
||||
keysPressed: 'l<C-v>ljA123',
|
||||
end: ['tes123|t', 'tes123t'],
|
||||
end: ['tes123|t', 'tes123|t'],
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'Can handle A backwards select',
|
||||
start: ['tes|t', 'test'],
|
||||
keysPressed: 'h<C-v>hjA123',
|
||||
end: ['tes123|t', 'tes123t'],
|
||||
end: ['tes123|t', 'tes123|t'],
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'Can handle I forward select',
|
||||
start: ['|test', 'test'],
|
||||
keysPressed: 'l<C-v>ljI123',
|
||||
end: ['t123|est', 't123est'],
|
||||
end: ['t123|est', 't123|est'],
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'Can handle I backwards select',
|
||||
start: ['tes|t', 'test'],
|
||||
keysPressed: 'h<C-v>hjI123',
|
||||
end: ['t123|est', 't123est'],
|
||||
end: ['t123|est', 't123|est'],
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'Can handle I with empty lines on first character (inserts on empty line)',
|
||||
start: ['|test', '', 'test'],
|
||||
keysPressed: '<C-v>lljjI123',
|
||||
end: ['123|test', '123', '123test'],
|
||||
end: ['123|test', '123|', '123|test'],
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'Can handle I with empty lines on non-first character (does not insert on empty line)',
|
||||
start: ['t|est', '', 'test'],
|
||||
keysPressed: '<C-v>lljjI123',
|
||||
end: ['t123|est', '', 't123est'],
|
||||
end: ['t123|est', '', 't123|est'],
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'Can handle c forward select',
|
||||
start: ['|test', 'test'],
|
||||
keysPressed: 'l<C-v>ljc123',
|
||||
end: ['t123|t', 't123t'],
|
||||
end: ['t123|t', 't123|t'],
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'Can handle c backwards select',
|
||||
start: ['tes|t', 'test'],
|
||||
keysPressed: 'h<C-v>hjc123',
|
||||
end: ['t123|t', 't123t'],
|
||||
end: ['t123|t', 't123|t'],
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'Can handle C',
|
||||
start: ['tes|t', 'test'],
|
||||
keysPressed: 'h<C-v>hjC123',
|
||||
end: ['t123|', 't123'],
|
||||
end: ['t123|', 't123|'],
|
||||
endMode: Mode.Insert,
|
||||
});
|
||||
|
||||
newTest({
|
||||
@ -144,7 +145,7 @@ suite('Mode Visual Block', () => {
|
||||
title: 'Properly add to end of lines j then $',
|
||||
start: ['|Dog', 'Angry', 'Dog', 'Angry', 'Dog'],
|
||||
keysPressed: '<C-v>4j$Aaa',
|
||||
end: ['Dogaa|', 'Angryaa', 'Dogaa', 'Angryaa', 'Dogaa'],
|
||||
end: ['Dogaa|', 'Angryaa|', 'Dogaa|', 'Angryaa|', 'Dogaa|'],
|
||||
});
|
||||
|
||||
newTest({
|
||||
|
@ -441,21 +441,24 @@ suite('Mode Visual Line', () => {
|
||||
title: 'multiline insert from bottom up selection',
|
||||
start: ['111', '222', '333', '4|44', '555'],
|
||||
keysPressed: 'VkkI_',
|
||||
end: ['111', '_|222', '_333', '_444', '555'],
|
||||
end: ['111', '_|222', '_|333', '_|444', '555'],
|
||||
endMode: Mode.Insert,
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'multiline insert from top down selection',
|
||||
start: ['111', '2|22', '333', '444', '555'],
|
||||
keysPressed: 'VjjI_',
|
||||
end: ['111', '_|222', '_333', '_444', '555'],
|
||||
end: ['111', '_|222', '_|333', '_|444', '555'],
|
||||
endMode: Mode.Insert,
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'skips blank lines',
|
||||
start: ['111', '2|22', ' ', '444', '555'],
|
||||
keysPressed: 'VjjI_',
|
||||
end: ['111', '_|222', ' ', '_444', '555'],
|
||||
end: ['111', '_|222', ' ', '_|444', '555'],
|
||||
endMode: Mode.Insert,
|
||||
});
|
||||
});
|
||||
|
||||
@ -464,21 +467,24 @@ suite('Mode Visual Line', () => {
|
||||
title: 'multiline append from bottom up selection',
|
||||
start: ['111', '222', '333', '4|44', '555'],
|
||||
keysPressed: 'VkkA_',
|
||||
end: ['111', '222_|', '333_', '444_', '555'],
|
||||
end: ['111', '222_|', '333_|', '444_|', '555'],
|
||||
endMode: Mode.Insert,
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'multiline append from top down selection',
|
||||
start: ['111', '2|22', '333', '444', '555'],
|
||||
keysPressed: 'VjjA_',
|
||||
end: ['111', '222_|', '333_', '444_', '555'],
|
||||
end: ['111', '222_|', '333_|', '444_|', '555'],
|
||||
endMode: Mode.Insert,
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'skips blank lines',
|
||||
start: ['111', '2|22', ' ', '444', '555'],
|
||||
keysPressed: 'VjjA_',
|
||||
end: ['111', '222_|', ' ', '444_', '555'],
|
||||
end: ['111', '222_|', ' ', '444_|', '555'],
|
||||
endMode: Mode.Insert,
|
||||
});
|
||||
|
||||
newTest({
|
||||
|
@ -2,9 +2,12 @@ import * as assert from 'assert';
|
||||
import { getAndUpdateModeHandler } from '../extension';
|
||||
import { ModeHandler } from '../src/mode/modeHandler';
|
||||
import { assertEqualLines, cleanUpWorkspace, setupWorkspace } from './testUtils';
|
||||
import { getTestingFunctions } from './testSimplifier';
|
||||
import { Mode } from '../src/mode/mode';
|
||||
|
||||
suite('Multicursor', () => {
|
||||
let modeHandler: ModeHandler;
|
||||
const { newTest, newTestOnly, newTestSkip } = getTestingFunctions();
|
||||
|
||||
setup(async () => {
|
||||
await setupWorkspace();
|
||||
@ -24,7 +27,7 @@ suite('Multicursor', () => {
|
||||
await modeHandler.handleMultipleKeyEvents(['<C-alt+down>']);
|
||||
}
|
||||
|
||||
assert.strictEqual(modeHandler.vimState.cursors.length, 2, 'Cursor succesfully created.');
|
||||
assert.strictEqual(modeHandler.vimState.cursors.length, 2, 'Cursor successfully created.');
|
||||
await modeHandler.handleMultipleKeyEvents(['c', 'w', '3', '3', '<Esc>']);
|
||||
assertEqualLines(['33', '33']);
|
||||
});
|
||||
@ -40,7 +43,7 @@ suite('Multicursor', () => {
|
||||
await modeHandler.handleMultipleKeyEvents(['<C-alt+up>', '<C-alt+up>']);
|
||||
}
|
||||
|
||||
assert.strictEqual(modeHandler.vimState.cursors.length, 3, 'Cursor succesfully created.');
|
||||
assert.strictEqual(modeHandler.vimState.cursors.length, 3, 'Cursor successfully created.');
|
||||
await modeHandler.handleMultipleKeyEvents(['c', 'w', '4', '4', '<Esc>']);
|
||||
assertEqualLines(['44', '44', '44']);
|
||||
});
|
||||
@ -122,4 +125,61 @@ suite('Multicursor', () => {
|
||||
assertEqualLines(['<div></div> asd', '<div></div>']);
|
||||
assert.strictEqual(modeHandler.vimState.cursors.length, 2);
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'dd works with multi-cursor',
|
||||
start: ['on|e', 'two', 't|hree', 'four'],
|
||||
keysPressed: 'dd',
|
||||
end: ['|two', '|four'],
|
||||
endMode: Mode.Normal,
|
||||
});
|
||||
|
||||
// TODO - this actually doesn't work, but it should
|
||||
// newTest({
|
||||
// title: 'dab works with multi-cursor',
|
||||
// start: ['(on|e)', 'two', '(thre|e', 'four)'],
|
||||
// keysPressed: 'dd',
|
||||
// end: ['|', 'two', '|'],
|
||||
// endMode: Mode.Normal,
|
||||
// });
|
||||
|
||||
newTest({
|
||||
title: 'Vd works with multi-cursor',
|
||||
start: ['on|e', 'two', 't|hree', 'four'],
|
||||
keysPressed: 'dd',
|
||||
end: ['|two', '|four'],
|
||||
endMode: Mode.Normal,
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: '<C-v>d works with multi-cursor',
|
||||
start: ['|one', 'two', 't|hree', 'four'],
|
||||
keysPressed: '<C-v>jld',
|
||||
end: ['|e', 'o', 't|ee', 'fr'],
|
||||
endMode: Mode.Normal,
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'cw works with multi-cursor',
|
||||
start: ['one |two three |four five'],
|
||||
keysPressed: 'cw',
|
||||
end: ['one | three | five'],
|
||||
endMode: Mode.Insert,
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: '<count>f<char> works with multi-cursor',
|
||||
start: ['|apple. banana! cucumber!', '|date! eggplant! fig.'],
|
||||
keysPressed: '2f!',
|
||||
end: ['apple. banana! cucumber|!', 'date! eggplant|! fig.'],
|
||||
endMode: Mode.Normal,
|
||||
});
|
||||
|
||||
newTest({
|
||||
title: 'o works with multi-cursor',
|
||||
start: ['li|ne1', 'l|ine2'],
|
||||
keysPressed: 'o',
|
||||
end: ['line1', '|', 'line2', '|'],
|
||||
endMode: Mode.Insert,
|
||||
});
|
||||
});
|
||||
|
@ -9,13 +9,15 @@ import { ModeHandler } from '../src/mode/modeHandler';
|
||||
import { TextEditor } from '../src/textEditor';
|
||||
import { assertEqualLines } from './testUtils';
|
||||
import { globalState } from '../src/state/globalState';
|
||||
import { Range } from '../src/common/motion/range';
|
||||
import { RecordedState } from '../src/state/recordedState';
|
||||
|
||||
export function getTestingFunctions() {
|
||||
const getNiceStack = (stack: string | undefined): string => {
|
||||
return stack ? stack.split('\n').splice(2, 1).join('\n') : 'no stack available :(';
|
||||
};
|
||||
|
||||
const newTest = (testObj: ITestObject): void => {
|
||||
const newTest = (testObj: ITestParams): void => {
|
||||
const stack = new Error().stack;
|
||||
const niceStack = getNiceStack(stack);
|
||||
|
||||
@ -32,7 +34,7 @@ export function getTestingFunctions() {
|
||||
);
|
||||
};
|
||||
|
||||
const newTestOnly = (testObj: ITestObject): void => {
|
||||
const newTestOnly = (testObj: ITestParams): void => {
|
||||
console.log('!!! Running single test !!!');
|
||||
const stack = new Error().stack;
|
||||
const niceStack = getNiceStack(stack);
|
||||
@ -50,7 +52,7 @@ export function getTestingFunctions() {
|
||||
);
|
||||
};
|
||||
|
||||
const newTestSkip = (testObj: ITestObject): void => {
|
||||
const newTestSkip = (testObj: ITestParams): void => {
|
||||
const stack = new Error().stack;
|
||||
const niceStack = getNiceStack(stack);
|
||||
|
||||
@ -74,113 +76,35 @@ export function getTestingFunctions() {
|
||||
};
|
||||
}
|
||||
|
||||
interface ITestObject {
|
||||
// TODO: add start mode, start/end registers, end status bar
|
||||
interface ITestParams {
|
||||
/** What behavior does this test enforce? */
|
||||
title: string;
|
||||
/** Lines in the document at the test's start */
|
||||
start: string[];
|
||||
/** Simulated user input; control characters like <Esc> will be parsed */
|
||||
keysPressed: string;
|
||||
/** Expected lines in the document when the test ends */
|
||||
end: string[];
|
||||
/** Expected mode when the test ends */
|
||||
endMode?: Mode;
|
||||
/** Expected jumps */
|
||||
jumps?: string[];
|
||||
}
|
||||
|
||||
class TestObjectHelper {
|
||||
/**
|
||||
* Position that the test says that the cursor starts at.
|
||||
*/
|
||||
startPosition = new Position(0, 0);
|
||||
|
||||
/**
|
||||
* Position that the test says that the cursor ends at.
|
||||
*/
|
||||
endPosition = new Position(0, 0);
|
||||
|
||||
private _isValid = false;
|
||||
private _testObject: ITestObject;
|
||||
|
||||
constructor(_testObject: ITestObject) {
|
||||
this._testObject = _testObject;
|
||||
|
||||
this._parse(_testObject);
|
||||
}
|
||||
|
||||
public get isValid(): boolean {
|
||||
return this._isValid;
|
||||
}
|
||||
|
||||
private _setStartCursorPosition(lines: string[]): boolean {
|
||||
const result = this._getCursorPosition(lines);
|
||||
this.startPosition = result.position;
|
||||
return result.success;
|
||||
}
|
||||
|
||||
private _setEndCursorPosition(lines: string[]): boolean {
|
||||
const result = this._getCursorPosition(lines);
|
||||
this.endPosition = result.position;
|
||||
return result.success;
|
||||
}
|
||||
|
||||
private _getCursorPosition(lines: string[]): { success: boolean; position: Position } {
|
||||
const ret = { success: false, position: new Position(0, 0) };
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const columnIdx = lines[i].indexOf('|');
|
||||
if (columnIdx >= 0) {
|
||||
ret.position = new Position(i, columnIdx);
|
||||
ret.success = true;
|
||||
function parseCursors(lines: string[]): Position[] {
|
||||
let cursors = [] as Position[];
|
||||
for (let line = 0; line < lines.length; line++) {
|
||||
let cursorsOnLine = 0;
|
||||
for (let column = 0; column < lines[line].length; column++) {
|
||||
if (lines[line][column] === '|') {
|
||||
cursors.push(new Position(line, column - cursorsOnLine));
|
||||
cursorsOnLine++;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private _parse(t: ITestObject): void {
|
||||
this._isValid = true;
|
||||
if (!this._setStartCursorPosition(t.start)) {
|
||||
this._isValid = false;
|
||||
}
|
||||
if (!this._setEndCursorPosition(t.end)) {
|
||||
this._isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
public asVimInputText(): string[] {
|
||||
const ret = 'i' + this._testObject.start.join('\n').replace('|', '');
|
||||
return ret.split('');
|
||||
}
|
||||
|
||||
public asVimOutputText(): string[] {
|
||||
const ret = this._testObject.end.slice(0);
|
||||
ret[this.endPosition.line] = ret[this.endPosition.line].replace('|', '');
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sequence of Vim movement characters 'hjkl' as a string array
|
||||
* which will move the cursor to the start position given in the test.
|
||||
*/
|
||||
public getKeyPressesToMoveToStartPosition(): string[] {
|
||||
let ret = '';
|
||||
const linesToMove = this.startPosition.line;
|
||||
|
||||
const cursorPosAfterEsc =
|
||||
this._testObject.start[this._testObject.start.length - 1].replace('|', '').length - 1;
|
||||
const numCharsInCursorStartLine =
|
||||
this._testObject.start[this.startPosition.line].replace('|', '').length - 1;
|
||||
const charactersToMove = this.startPosition.character;
|
||||
|
||||
if (linesToMove > 0) {
|
||||
ret += Array(linesToMove + 1).join('j');
|
||||
} else if (linesToMove < 0) {
|
||||
ret += Array(Math.abs(linesToMove) + 1).join('k');
|
||||
}
|
||||
|
||||
if (charactersToMove > 0) {
|
||||
ret += Array(charactersToMove + 1).join('l');
|
||||
} else if (charactersToMove < 0) {
|
||||
ret += Array(Math.abs(charactersToMove) + 1).join('h');
|
||||
}
|
||||
|
||||
return ret.split('');
|
||||
}
|
||||
return cursors;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -230,80 +154,98 @@ function tokenizeKeySequence(sequence: string): string[] {
|
||||
return result;
|
||||
}
|
||||
|
||||
async function testIt(modeHandler: ModeHandler, testObj: ITestObject): Promise<void> {
|
||||
async function testIt(modeHandler: ModeHandler, testParams: ITestParams): Promise<void> {
|
||||
modeHandler.vimState.editor = vscode.window.activeTextEditor!;
|
||||
|
||||
const helper = new TestObjectHelper(testObj);
|
||||
const jumpTracker = globalState.jumpTracker;
|
||||
// Find the cursors in the start/end strings
|
||||
const cursorStartPositions = parseCursors(testParams.start);
|
||||
const expectedCursorPositions = parseCursors(testParams.end);
|
||||
assert(cursorStartPositions.length > 0, "Missing '|' in test object's start.");
|
||||
assert(expectedCursorPositions.length > 0, "Missing '|' in test object's end.");
|
||||
|
||||
// Don't try this at home, kids.
|
||||
(modeHandler as any).vimState.cursorPosition = new Position(0, 0);
|
||||
|
||||
await modeHandler.handleKeyEvent('<Esc>');
|
||||
// Take the cursor characters out of the start/end strings
|
||||
testParams.start = testParams.start.map((line) => line.replace('|', ''));
|
||||
testParams.end = testParams.end.map((line) => line.replace('|', ''));
|
||||
|
||||
// Insert all the text as a single action.
|
||||
await modeHandler.vimState.editor.edit((builder) => {
|
||||
builder.insert(new Position(0, 0), testObj.start.join('\n').replace('|', ''));
|
||||
builder.insert(new Position(0, 0), testParams.start.join('\n'));
|
||||
});
|
||||
|
||||
await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g']);
|
||||
|
||||
// Since we bypassed VSCodeVim to add text,
|
||||
// we need to tell the history tracker that we added it.
|
||||
modeHandler.vimState.historyTracker.addChange();
|
||||
modeHandler.vimState.historyTracker.finishCurrentStep();
|
||||
|
||||
// move cursor to start position using 'hjkl'
|
||||
await modeHandler.handleMultipleKeyEvents(helper.getKeyPressesToMoveToStartPosition());
|
||||
modeHandler.handleMultipleKeyEvents(['<Esc>', '<Esc>']);
|
||||
|
||||
// Move cursors to starting positions
|
||||
modeHandler.vimState.recordedState = new RecordedState();
|
||||
modeHandler.vimState.cursors = cursorStartPositions.map((pos) => new Range(pos, pos));
|
||||
modeHandler.updateView(modeHandler.vimState);
|
||||
|
||||
Globals.mockModeHandler = modeHandler;
|
||||
|
||||
let keysPressed = testObj.keysPressed;
|
||||
let keysPressed = testParams.keysPressed;
|
||||
if (process.platform === 'win32') {
|
||||
keysPressed = keysPressed.replace(/\\n/g, '\\r\\n');
|
||||
}
|
||||
|
||||
const jumpTracker = globalState.jumpTracker;
|
||||
jumpTracker.clearJumps();
|
||||
|
||||
// Assumes key presses are single characters for now
|
||||
await modeHandler.handleMultipleKeyEvents(tokenizeKeySequence(keysPressed));
|
||||
|
||||
// Check valid test object input
|
||||
assert(helper.isValid, "Missing '|' in test object.");
|
||||
|
||||
// Check given end output is correct
|
||||
const lines = helper.asVimOutputText();
|
||||
assertEqualLines(lines);
|
||||
assertEqualLines(testParams.end);
|
||||
|
||||
// Check final cursor position
|
||||
const actualPosition = Position.FromVSCodePosition(TextEditor.getSelection().start);
|
||||
const expectedPosition = helper.endPosition;
|
||||
assert.strictEqual(actualPosition.line, expectedPosition.line, 'Cursor LINE position is wrong.');
|
||||
assert.strictEqual(
|
||||
actualPosition.character,
|
||||
expectedPosition.character,
|
||||
'Cursor CHARACTER position is wrong.'
|
||||
// Check final positions of all cursors
|
||||
const actualCursorPositions = TextEditor.getSelections().map((sel) =>
|
||||
Position.FromVSCodePosition(sel.start)
|
||||
);
|
||||
assert.strictEqual(
|
||||
actualCursorPositions.length,
|
||||
expectedCursorPositions.length,
|
||||
'Wrong number of cursors'
|
||||
);
|
||||
for (let i = 0; i < actualCursorPositions.length; i++) {
|
||||
const actual = actualCursorPositions[i];
|
||||
const expected = expectedCursorPositions[i];
|
||||
assert.deepStrictEqual(
|
||||
{
|
||||
line: actual.line,
|
||||
character: actual.character,
|
||||
},
|
||||
{
|
||||
line: expected.line,
|
||||
character: expected.character,
|
||||
},
|
||||
`Cursor #${i + 1}'s position is wrong.`
|
||||
);
|
||||
}
|
||||
|
||||
// endMode: check end mode is correct if given
|
||||
if (testObj.endMode !== undefined) {
|
||||
if (testParams.endMode !== undefined) {
|
||||
const actualMode = Mode[modeHandler.currentMode].toUpperCase();
|
||||
const expectedMode = Mode[testObj.endMode].toUpperCase();
|
||||
const expectedMode = Mode[testParams.endMode].toUpperCase();
|
||||
assert.strictEqual(actualMode, expectedMode, "Didn't enter correct mode.");
|
||||
}
|
||||
|
||||
// jumps: check jumps are correct if given
|
||||
if (testObj.jumps !== undefined) {
|
||||
if (testParams.jumps !== undefined) {
|
||||
assert.deepEqual(
|
||||
jumpTracker.jumps.map((j) => lines[j.position.line] || '<MISSING>'),
|
||||
testObj.jumps.map((t) => t.replace('|', '')),
|
||||
jumpTracker.jumps.map((j) => testParams.end[j.position.line] || '<MISSING>'),
|
||||
testParams.jumps.map((t) => t.replace('|', '')),
|
||||
'Incorrect jumps found'
|
||||
);
|
||||
|
||||
const stripBar = (text: string | undefined) => (text ? text.replace('|', '') : text);
|
||||
const actualJumpPosition =
|
||||
(jumpTracker.currentJump && lines[jumpTracker.currentJump.position.line]) || '<FRONT>';
|
||||
const expectedJumpPosition = stripBar(testObj.jumps.find((t) => t.includes('|'))) || '<FRONT>';
|
||||
(jumpTracker.currentJump && testParams.end[jumpTracker.currentJump.position.line]) ||
|
||||
'<FRONT>';
|
||||
const expectedJumpPosition =
|
||||
stripBar(testParams.jumps.find((t) => t.includes('|'))) || '<FRONT>';
|
||||
|
||||
assert.deepEqual(
|
||||
actualJumpPosition.toString(),
|
||||
@ -313,4 +255,4 @@ async function testIt(modeHandler: ModeHandler, testObj: ITestObject): Promise<v
|
||||
}
|
||||
}
|
||||
|
||||
export { ITestObject, testIt };
|
||||
export { ITestParams as ITestObject, testIt };
|
||||
|
@ -76,6 +76,9 @@ export async function WaitForEditorsToClose(numExpectedEditors: number = 0): Pro
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the contents of the active editor matches what's given
|
||||
*/
|
||||
export function assertEqualLines(expectedLines: string[]) {
|
||||
for (let i = 0; i < expectedLines.length; i++) {
|
||||
const expected = expectedLines[i];
|
||||
@ -83,7 +86,7 @@ export function assertEqualLines(expectedLines: string[]) {
|
||||
assert.strictEqual(
|
||||
actual,
|
||||
expected,
|
||||
`Content does not match; Expected=${expected}. Actual=${actual}.`
|
||||
`Content of line ${i + 1} does not match; Expected=${expected}. Actual=${actual}.`
|
||||
);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user