Support [ and ] commands to go to start / end of previously operated text (#4147)

Fixes #2004
This commit is contained in:
Hai Feng Kao 2019-10-26 12:29:07 +08:00 committed by Jason Fields
parent e7f1cd7f42
commit bce62a759d
4 changed files with 94 additions and 12 deletions

View File

@ -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

View File

@ -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<VimState> {
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<VimState> {
const lastPos = vimState.historyTracker.getLastChangeEndPosition();
if (lastPos !== undefined) {
vimState.cursorStopPosition = lastPos;
}
return vimState;
}
}
@RegisterAction
class CommandGoLastChange extends BaseCommand {
modes = [ModeName.Normal];

View File

@ -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;
}

View File

@ -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<Esc>`]',
end: ['hello worl|d'],
});
newTest({
title: "'] go to the end of the previously operated or put text",
start: ['hello|'],
keysPressed: "a world<Esc>']",
end: ['hello worl|d'],
});
newTest({
title: '`[ go to the start of the previously operated or put text',
start: ['hello|'],
keysPressed: 'a world<Esc>`[',
end: ['hello| world'],
});
newTest({
title: "'[ go to the start of the previously operated or put text",
start: ['hello|'],
keysPressed: "a world<Esc>'[",
end: ['hello| world'],
});
suite('can handle gn', () => {
test(`gn selects the next match text`, async () => {
await modeHandler.handleMultipleKeyEvents('ifoo\nhello world\nhello\nhello'.split(''));