Implement ; and , (#674)

* Implement ; and ,

* Trying to fix travis ci agent (which does not handle clipboard tests well)

* fix tslint errors

* cleanup

* more cleanup

* even more cleanup

* and more cleanup
This commit is contained in:
amin roosta 2016-09-03 22:08:49 +04:30 committed by Grant Mathews
parent 660df9aa65
commit 69ff80c3f6
6 changed files with 131 additions and 4 deletions

View File

@ -54,8 +54,8 @@ Status | Command | Description
:white_check_mark: |:1234: F{char} | to the Nth occurrence of {char} to the left
:white_check_mark: |:1234: t{char} | till before the Nth occurrence of {char} to the right
:white_check_mark: |:1234: T{char} | till before the Nth occurrence of {char} to the left
|:1234: ; | repeat the last "f", "F", "t", or "T" N times
|:1234: , | repeat the last "f", "F", "t", or "T" N times in opposite direction
:white_check_mark: |:1234: ; | repeat the last "f", "F", "t", or "T" N times
:white_check_mark: |:1234: , | repeat the last "f", "F", "t", or "T" N times in opposite direction
## Up-down motions

View File

@ -156,6 +156,13 @@ export abstract class BaseMovement extends BaseAction {
canBePrefixedWithCount = false;
/**
* Whether we should change lastRepeatableMovement in VimState.
*/
public canBeRepeatedWithSemicolon(vimState: VimState, result: Position | IMovement) {
return false;
}
/**
* Whether we should change desiredColumn in VimState.
*/
@ -1992,6 +1999,10 @@ class MoveFindForward extends BaseMovement {
return result;
}
public canBeRepeatedWithSemicolon(vimState: VimState, result: Position | IMovement) {
return !vimState.recordedState.operator || !(isIMovement(result) && result.failed);
}
}
@RegisterAction
@ -2009,6 +2020,10 @@ class MoveFindBackward extends BaseMovement {
return result;
}
public canBeRepeatedWithSemicolon(vimState: VimState, result: Position | IMovement) {
return !vimState.recordedState.operator || !(isIMovement(result) && result.failed);
}
}
@ -2031,6 +2046,10 @@ class MoveTilForward extends BaseMovement {
return result;
}
public canBeRepeatedWithSemicolon(vimState: VimState, result: Position | IMovement) {
return !vimState.recordedState.operator || !(isIMovement(result) && result.failed);
}
}
@RegisterAction
@ -2048,6 +2067,59 @@ class MoveTilBackward extends BaseMovement {
return result;
}
public canBeRepeatedWithSemicolon(vimState: VimState, result: Position | IMovement) {
return !vimState.recordedState.operator || !(isIMovement(result) && result.failed);
}
}
@RegisterAction
class MoveRepeat extends BaseMovement {
keys = [";"];
public async execActionWithCount(position: Position, vimState: VimState, count: number): Promise<Position | IMovement> {
const movement = VimState.lastRepeatableMovement;
if (movement) {
const result = await movement.execActionWithCount(position, vimState, count);
/**
* For t<character> and T<character> commands vim executes ; as 2;
* This way the cursor will get to the next instance of <character>
*/
if (result instanceof Position && position.isEqual(result) && count <= 1) {
return await movement.execActionWithCount(position, vimState, 2);
}
return result;
}
return position;
}
}
@RegisterAction
class MoveRepeatReversed extends BaseMovement {
keys = [","];
static reverseMotionMapping : Map<Function, () => BaseMovement> = new Map([
[MoveFindForward, () => new MoveFindBackward()],
[MoveFindBackward, () => new MoveFindForward()],
[MoveTilForward, () => new MoveTilBackward()],
[MoveTilBackward, () => new MoveTilForward()]
]);
public async execActionWithCount(position: Position, vimState: VimState, count: number): Promise<Position | IMovement> {
const movement = VimState.lastRepeatableMovement;
if (movement) {
const reverse = MoveRepeatReversed.reverseMotionMapping.get(movement.constructor)();
reverse.keysPressed = [reverse.keys[0], movement.keysPressed[1]];
let result = await reverse.execActionWithCount(position, vimState, count);
// For t<character> and T<character> commands vim executes ; as 2;
if (result instanceof Position && position.isEqual(result) && count <= 1) {
result = await reverse.execActionWithCount(position, vimState, 2);
}
return result;
}
return position;
}
}
@RegisterAction

View File

@ -54,6 +54,8 @@ export class VimState {
public historyTracker: HistoryTracker;
public static lastRepeatableMovement : BaseMovement | undefined = undefined;
/**
* The keystroke sequence that made up our last complete action (that can be
* repeated with '.').
@ -850,6 +852,10 @@ export class ModeHandler implements vscode.Disposable {
}
}
if (movement.canBeRepeatedWithSemicolon(vimState, result)) {
VimState.lastRepeatableMovement = movement;
}
vimState.recordedState.count = 0;
let stop = vimState.cursorPosition;

View File

@ -23,7 +23,7 @@ Globals.isTesting = true;
testRunner.configure({
ui: 'tdd',
useColors: true,
timeout: 2500,
timeout: 4000,
});
module.exports = testRunner;

View File

@ -44,7 +44,7 @@ suite("register", () => {
newTest({
title: "Can use two registers together",
start: ['|one', "two"],
keysPressed: '"*yyjyy"*pp',
keysPressed: '"ayyj"byy"ap"bp',
end: ["one", "two", "one", "|two"],
});

View File

@ -0,0 +1,49 @@
"use strict";
import { ModeHandler } from "../../src/mode/modeHandler";
import { setupWorkspace, cleanUpWorkspace, assertEqualLines } from '../testUtils';
import { getTestingFunctions } from '../testSimplifier';
suite("register", () => {
let modeHandler: ModeHandler = new ModeHandler();
let {
newTest,
newTestOnly,
} = getTestingFunctions(modeHandler);
setup(async () => {
await setupWorkspace();
});
suiteTeardown(cleanUpWorkspace);
newTest({
title: "Can repeat f<character>",
start: ['|abc abc abc'],
keysPressed: 'fa;',
end: ['abc abc |abc'],
});
newTest({
title: "Can repeat reversed F<character>",
start: ['|abc abc abc'],
keysPressed: 'fa$,',
end: ['abc abc |abc'],
});
newTest({
title: "Can repeat t<character>",
start: ['|abc abc abc'],
keysPressed: 'tc;',
end: ['abc a|bc abc'],
});
newTest({
title: "Can repeat N times reversed t<character>",
start: ['|abc abc abc abc'],
keysPressed: 'tc$3,',
end: ['abc| abc abc abc'],
});
});