From bce62a759d99ec8cd8022491eeda58cd27adb9d6 Mon Sep 17 00:00:00 2001 From: Hai Feng Kao Date: Sat, 26 Oct 2019 12:29:07 +0800 Subject: [PATCH] Support `[ and `] commands to go to start / end of previously operated text (#4147) Fixes #2004 --- ROADMAP.md | 24 +++++++++++++----------- src/actions/commands/actions.ts | 32 ++++++++++++++++++++++++++++++++ src/history/historyTracker.ts | 22 +++++++++++++++++++++- test/mode/modeNormal.test.ts | 28 ++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 12 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index c0bff674..cd5ef95a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -132,25 +132,27 @@ Now follows an exhaustive list of every known Vim command that we could find. ## Marks and motions -| Status | Command | Description | -| ------------------ | ----------------------------------------------------------- | -------------------------------------------------- | -| :white_check_mark: | m{a-zA-Z} | mark current position with mark {a-zA-Z} | +| Status | Command | Description | +| ------------------ | ----------------------------------------------------------- | ------------------------------------------------------ | +| :white_check_mark: | m{a-zA-Z} | mark current position with mark {a-zA-Z} | | :white_check_mark: | `{a-z} | go to mark {a-z} within current file | | :white_check_mark: | `{A-Z} | go to mark {A-Z} in any file | | :white_check_mark: | `{0-9} | go to the position where Vim was previously exited | | :white_check_mark: | `` | go to the position before the last jump | | :arrow_down: | `" | go to the position when last editing this file | -| :arrow_down: | `[ | go to the start of the previously operated or put text | -| :arrow_down: | `] | go to the end of the previously operated or put text | +| :white_check_mark: | `[ | go to the start of the previously operated or put text | +| :white_check_mark: | '[ | go to the start of the previously operated or put text | +| :white_check_mark: | `] | go to the end of the previously operated or put text | +| :white_check_mark: | '] | go to the end of the previously operated or put text | | :arrow_down: | `< | go to the start of the (previous) Visual area | | :arrow_down: | `> | go to the end of the (previous) Visual area | | :white_check_mark: | `. | go to the position of the last change in this file | -| :white_check_mark: | '. | go to the position of the last change in this file | -| :arrow_down: | '{a-zA-Z0-9[]'"<>.} | same as `, but on the first non-blank in the line | -| :arrow_down: | :marks | print the active marks | -| :white_check_mark: | :1234: CTRL-O | go to Nth older position in jump list | -| :white_check_mark: | :1234: CTRL-I | go to Nth newer position in jump list | -| :arrow_down: | :ju[mps] | print the jump list | +| :white_check_mark: | '. | go to the position of the last change in this file | +| :arrow_down: | '{a-zA-Z0-9[]'"<>.} | same as `, but on the first non-blank in the line | +| :arrow_down: | :marks | print the active marks | +| :white_check_mark: | :1234: CTRL-O | go to Nth older position in jump list | +| :white_check_mark: | :1234: CTRL-I | go to Nth newer position in jump list | +| :arrow_down: | :ju[mps] | print the jump list | ## Various motions diff --git a/src/actions/commands/actions.ts b/src/actions/commands/actions.ts index c4ce7a67..f4770ea8 100644 --- a/src/actions/commands/actions.ts +++ b/src/actions/commands/actions.ts @@ -3008,6 +3008,38 @@ class CommandGoForwardInChangelist extends BaseCommand { } } +@RegisterAction +class CommandGoStartPrevOperatedText extends BaseCommand { + modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine, ModeName.VisualBlock]; + keys = [['`', '['], ["'", '[']]; + isJump = true; + + public async exec(position: Position, vimState: VimState): Promise { + const lastPos = vimState.historyTracker.getLastChangeStartPosition(); + if (lastPos !== undefined) { + vimState.cursorStopPosition = lastPos; + } + + return vimState; + } +} + +@RegisterAction +class CommandGoEndPrevOperatedText extends BaseCommand { + modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine, ModeName.VisualBlock]; + keys = [['`', ']'], ["'", ']']]; + isJump = true; + + public async exec(position: Position, vimState: VimState): Promise { + const lastPos = vimState.historyTracker.getLastChangeEndPosition(); + if (lastPos !== undefined) { + vimState.cursorStopPosition = lastPos; + } + + return vimState; + } +} + @RegisterAction class CommandGoLastChange extends BaseCommand { modes = [ModeName.Normal]; diff --git a/src/history/historyTracker.ts b/src/history/historyTracker.ts index 78c503cc..d61d4253 100644 --- a/src/history/historyTracker.ts +++ b/src/history/historyTracker.ts @@ -667,11 +667,18 @@ export class HistoryTracker { if (this.currentHistoryStepIndex === 0) { return undefined; } + const lastChangeIndex = this.historySteps[this.currentHistoryStepIndex].changes.length; if (lastChangeIndex === 0) { return undefined; } - return this.historySteps[this.currentHistoryStepIndex].changes[lastChangeIndex - 1].end(); + + const lastChange = this.historySteps[this.currentHistoryStepIndex].changes[lastChangeIndex - 1]; + if (lastChange.isAdd) { + return lastChange.end(); + } + + return lastChange.start; } getLastHistoryStartPosition(): Position[] | undefined { @@ -682,6 +689,19 @@ export class HistoryTracker { return this.historySteps[this.currentHistoryStepIndex].cursorStart; } + getLastChangeStartPosition(): Position | undefined { + if (this.currentHistoryStepIndex === 0) { + return undefined; + } + + const lastChangeIndex = this.historySteps[this.currentHistoryStepIndex].changes.length; + if (lastChangeIndex === 0) { + return undefined; + } + + return this.historySteps[this.currentHistoryStepIndex].changes[lastChangeIndex - 1].start; + } + setLastHistoryEndPosition(pos: Position[]) { this.historySteps[this.currentHistoryStepIndex].cursorEnd = pos; } diff --git a/test/mode/modeNormal.test.ts b/test/mode/modeNormal.test.ts index 3fc1bb47..1ac8b498 100644 --- a/test/mode/modeNormal.test.ts +++ b/test/mode/modeNormal.test.ts @@ -2071,6 +2071,34 @@ suite('Mode Normal', () => { endMode: ModeName.Normal, }); + newTest({ + title: '`] go to the end of the previously operated or put text', + start: ['hello|'], + keysPressed: 'a world`]', + end: ['hello worl|d'], + }); + + newTest({ + title: "'] go to the end of the previously operated or put text", + start: ['hello|'], + keysPressed: "a world']", + end: ['hello worl|d'], + }); + + newTest({ + title: '`[ go to the start of the previously operated or put text', + start: ['hello|'], + keysPressed: 'a world`[', + end: ['hello| world'], + }); + + newTest({ + title: "'[ go to the start of the previously operated or put text", + start: ['hello|'], + keysPressed: "a world'[", + end: ['hello| world'], + }); + suite('can handle gn', () => { test(`gn selects the next match text`, async () => { await modeHandler.handleMultipleKeyEvents('ifoo\nhello world\nhello\nhello'.split(''));