diff --git a/src/actions/actions.ts b/src/actions/actions.ts index c25e489c4e5..28b2fd25ebb 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -657,6 +657,63 @@ class MoveRight extends BaseMovement { } } +@RegisterAction +class MoveFindForward extends BaseMovement { + modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; + keys = ["f", ""]; + + public async execAction(position: Position, vimState: VimState): Promise { + const toFind = vimState.actionState.keysPressed[1]; + + vimState.cursorPosition = position.findForwards(toFind); + + return vimState; + } +} + +@RegisterAction +class MoveFindBackward extends BaseMovement { + modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; + keys = ["F", ""]; + + public async execAction(position: Position, vimState: VimState): Promise { + const toFind = vimState.actionState.keysPressed[1]; + + vimState.cursorPosition = position.findBackwards(toFind); + + return vimState; + } +} + + +@RegisterAction +class MoveTilForward extends BaseMovement { + modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; + keys = ["t", ""]; + + public async execAction(position: Position, vimState: VimState): Promise { + const toFind = vimState.actionState.keysPressed[1]; + + vimState.cursorPosition = position.tilForwards(toFind); + + return vimState; + } +} + +@RegisterAction +class MoveTilBackward extends BaseMovement { + modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; + keys = ["T", ""]; + + public async execAction(position: Position, vimState: VimState): Promise { + const toFind = vimState.actionState.keysPressed[1]; + + vimState.cursorPosition = position.tilBackwards(toFind); + + return vimState; + } +} + @RegisterAction class MoveLineEnd extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index 8ddd7465209..f2b194ad881 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -292,8 +292,6 @@ export class ModeHandler implements vscode.Disposable { } } else if (action === KeypressState.WaitingOnKeys) { return true; - } else { - actionState.keysPressed = []; } if (action) { @@ -417,6 +415,8 @@ export class ModeHandler implements vscode.Disposable { this._vimState.actionState = new ActionState(this._vimState); } + actionState.keysPressed = []; + return !!action; } diff --git a/src/motion/position.ts b/src/motion/position.ts index 32195e35684..9cbb1547133 100644 --- a/src/motion/position.ts +++ b/src/motion/position.ts @@ -410,4 +410,54 @@ export class Position extends vscode.Position { return new Position(TextEditor.getLineCount() - 1, 0).getLineEnd(); } + + + private findHelper(char: string, count: number, direction: number): Position { + // -1 = backwards, +1 = forwards + const line = TextEditor.getLineAt(this); + let index = this.character; + + while (count && index !== -1) { + if (direction > 0) { + index = line.text.indexOf(char, index + direction); + } else { + index = line.text.lastIndexOf(char, index + direction); + } + count--; + } + + if (index > -1) { + return new Position(this.line, index); + } + + return null; + } + + public tilForwards(char: string, count: number = 1): Position { + const position = this.findHelper(char, count, +1); + if (!position) { return this; } + + return new Position(this.line, position.character - 1); + } + + public tilBackwards(char: string, count: number = 1): Position { + const position = this.findHelper(char, count, -1); + if (!position) { return this; } + + return new Position(this.line, position.character + 1); + } + + public findForwards(char: string, count: number = 1): Position { + const position = this.findHelper(char, count, +1); + if (!position) { return this; } + + return new Position(this.line, position.character); + } + + public findBackwards(char: string, count: number = 1): Position { + const position = this.findHelper(char, count, -1); + if (!position) { return this; } + + return position; + } } \ No newline at end of file diff --git a/test/mode/modeNormal.test.ts b/test/mode/modeNormal.test.ts index 89c86fb4a16..3f7a62c87a5 100644 --- a/test/mode/modeNormal.test.ts +++ b/test/mode/modeNormal.test.ts @@ -366,4 +366,128 @@ suite("Mode Normal", () => { assertEqual(TextEditor.getSelection().start.character, 3, "caw is on wrong position"); await assert.equal(modeHandler.currentMode.name, ModeName.Insert, "didn't enter insert mode"); }); + + test("Can handle 'f'", async () => { + await modeHandler.handleMultipleKeyEvents( + 'itext text'.split('') + ); + + await modeHandler.handleMultipleKeyEvents([ + '', + '^', + 'f', 't' + ]); + + assertEqual(TextEditor.getSelection().start.character, 3, "f failed"); + }); + + test("Can handle 'f' twice", async () => { + await modeHandler.handleMultipleKeyEvents( + 'itext text'.split('') + ); + + await modeHandler.handleMultipleKeyEvents([ + '', + '^', + 'f', 't', + 'f', 't' + ]); + + assertEqual(TextEditor.getSelection().start.character, 5, "f failed"); + }); + + test("Can handle 'F'", async () => { + await modeHandler.handleMultipleKeyEvents( + 'itext text'.split('') + ); + + await modeHandler.handleMultipleKeyEvents([ + '', + '$', + 'F', 't' + ]); + + assertEqual(TextEditor.getSelection().start.character, 5, "F failed"); + }); + + test("Can handle 'F' twice", async () => { + await modeHandler.handleMultipleKeyEvents( + 'itext text'.split('') + ); + + await modeHandler.handleMultipleKeyEvents([ + '', + '$', + 'F', 't', + 'F', 't', + ]); + + assertEqual(TextEditor.getSelection().start.character, 3, "F failed"); + }); + + + + + test("Can handle 't'", async () => { + await modeHandler.handleMultipleKeyEvents( + 'itext text'.split('') + ); + + await modeHandler.handleMultipleKeyEvents([ + '', + '^', + 't', 't' + ]); + + assertEqual(TextEditor.getSelection().start.character, 2, "f failed"); + }); + + test("Can handle 't' twice", async () => { + await modeHandler.handleMultipleKeyEvents( + 'itext text'.split('') + ); + + await modeHandler.handleMultipleKeyEvents([ + '', + '^', + 't', 't', + 't', 't' + ]); + + // it does nothing the second time lawl + assertEqual(TextEditor.getSelection().start.character, 2, "f failed"); + }); + + test("Can handle 'T'", async () => { + await modeHandler.handleMultipleKeyEvents( + 'itext text'.split('') + ); + + await modeHandler.handleMultipleKeyEvents([ + '', + '$', + 'T', 't' + ]); + + assertEqual(TextEditor.getSelection().start.character, 6, "F failed"); + }); + + test("Can handle 'T' twice", async () => { + await modeHandler.handleMultipleKeyEvents( + 'itext text'.split('') + ); + + await modeHandler.handleMultipleKeyEvents([ + '', + '$', + 'T', 't', + 'T', 't', + ]); + + // it also does nothing the second time lawl lawl + assertEqual(TextEditor.getSelection().start.character, 6, "F failed"); + }); + + + });