Skip to content

Commit

Permalink
Remove word in command line with <C-w> (#4031)
Browse files Browse the repository at this point in the history
* Remove word in command line with <C-w>

Fixes #4027

* <C-w> only remove word before the cursor

* Fix a typo

* Fix the broken tests

* Add unit tests for <C-w> in cmd line

* Add <C-w> support in search mode

* Add tests for <C-w> in search mode

* Change runsOnceForEveryCursor of <C-w> cmd to return false
  • Loading branch information
stevenguh authored and J-Fields committed Sep 22, 2019
1 parent 78a4f72 commit 885d6e9
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 14 deletions.
50 changes: 50 additions & 0 deletions src/actions/commands/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,30 @@ class CommandEscInSearchMode extends BaseCommand {
}
}

@RegisterAction
class CommandRemoveWordInSearchMode extends BaseCommand {
modes = [ModeName.SearchInProgressMode];
keys = ['<C-w>'];
runsOnceForEveryCursor() {
return false;
}

public async exec(position: Position, vimState: VimState): Promise<VimState> {
const searchState = globalState.searchState!;
const pos = vimState.statusBarCursorCharacterPos;
const searchString = searchState.searchString;
const characterAt = Position.getWordLeft(searchString, pos);
// Needs explicit check undefined because zero is falsy and zero is a valid character pos.
if (characterAt !== undefined) {
searchState.searchString = searchString
.substring(0, characterAt)
.concat(searchString.slice(pos));
vimState.statusBarCursorCharacterPos = pos - (pos - characterAt);
}

return vimState;
}
}
@RegisterAction
class CommandPasteInSearchMode extends BaseCommand {
modes = [ModeName.SearchInProgressMode];
Expand Down Expand Up @@ -2185,6 +2209,32 @@ class CommandEscInCommandline extends BaseCommand {
}
}

@RegisterAction
class CommandRemoveWordCommandline extends BaseCommand {
modes = [ModeName.CommandlineInProgress];
keys = ['<C-w>'];
runsOnceForEveryCursor() {
return false;
}

public async exec(position: Position, vimState: VimState): Promise<VimState> {
const key = this.keysPressed[0];
const pos = vimState.statusBarCursorCharacterPos;
const cmdText = vimState.currentCommandlineText;
const characterAt = Position.getWordLeft(cmdText, pos);
// Needs explicit check undefined because zero is falsy and zero is a valid character pos.
if (characterAt !== undefined) {
vimState.currentCommandlineText = cmdText
.substring(0, characterAt)
.concat(cmdText.slice(pos));
vimState.statusBarCursorCharacterPos = pos - (pos - characterAt);
}

commandLine.lastKeyPressed = key;
return vimState;
}
}

@RegisterAction
class CommandPasteInCommandline extends BaseCommand {
modes = [ModeName.CommandlineInProgress];
Expand Down
52 changes: 38 additions & 14 deletions src/common/motion/position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,22 @@ export class Position extends vscode.Position {
);
}

/**
* Get the position of the word counting from the position specified.
* @param text The string to search from.
* @param pos The position of text to search from.
* @param inclusive true if we consider the pos a valid result, false otherwise.
* @returns The character position of the word to the left relative to the text and the pos.
* undefined if there is no word to the left of the postion.
*/
public static getWordLeft(
text: string,
pos: number,
inclusive: boolean = false
): number | undefined {
return Position.getWordLeftWithRegex(text, pos, Position._nonWordCharRegex, inclusive);
}

/**
* Inclusive is true if we consider the current position a valid result, false otherwise.
*/
Expand Down Expand Up @@ -1030,7 +1046,7 @@ export class Position extends vscode.Position {
return regexp;
}

private getAllPositions(line: string, regex: RegExp): number[] {
private static getAllPositions(line: string, regex: RegExp): number[] {
let positions: number[] = [];
let result = regex.exec(line);

Expand Down Expand Up @@ -1068,23 +1084,31 @@ export class Position extends vscode.Position {
return positions;
}

private static getWordLeftWithRegex(
text: string,
pos: number,
regex: RegExp,
forceFirst: boolean = false,
inclusive: boolean = false
): number | undefined {
const positions = Position.getAllPositions(text, regex);
return positions
.reverse()
.find(index => (index < pos && !inclusive) || (index <= pos && inclusive) || forceFirst);
}

/**
* Inclusive is true if we consider the current position a valid result, false otherwise.
*/
private getWordLeftWithRegex(regex: RegExp, inclusive: boolean = false): Position {
for (let currentLine = this.line; currentLine >= 0; currentLine--) {
let positions = this.getAllPositions(
const newCharacter = Position.getWordLeftWithRegex(
TextEditor.getLineAt(new vscode.Position(currentLine, 0)).text,
regex
this.character,
regex,
currentLine !== this.line,
inclusive
);
let newCharacter = positions
.reverse()
.find(
index =>
(index < this.character && !inclusive) ||
(index <= this.character && inclusive) ||
currentLine !== this.line
);

if (newCharacter !== undefined) {
return new Position(currentLine, newCharacter);
Expand All @@ -1099,7 +1123,7 @@ export class Position extends vscode.Position {
*/
private getWordRightWithRegex(regex: RegExp, inclusive: boolean = false): Position {
for (let currentLine = this.line; currentLine < TextEditor.getLineCount(); currentLine++) {
let positions = this.getAllPositions(
let positions = Position.getAllPositions(
TextEditor.getLineAt(new vscode.Position(currentLine, 0)).text,
regex
);
Expand Down Expand Up @@ -1226,7 +1250,7 @@ export class Position extends vscode.Position {
private getCurrentSentenceEndWithRegex(regex: RegExp, inclusive: boolean): Position {
let paragraphEnd = this.getCurrentParagraphEnd();
for (let currentLine = this.line; currentLine <= paragraphEnd.line; currentLine++) {
let allPositions = this.getAllPositions(
let allPositions = Position.getAllPositions(
TextEditor.getLineAt(new vscode.Position(currentLine, 0)).text,
regex
);
Expand All @@ -1252,7 +1276,7 @@ export class Position extends vscode.Position {
return paragraphEnd;
} else {
for (let currentLine = this.line; currentLine <= paragraphEnd.line; currentLine++) {
const nonWhitePositions = this.getAllPositions(
const nonWhitePositions = Position.getAllPositions(
TextEditor.getLineAt(new vscode.Position(currentLine, 0)).text,
/\S/g
);
Expand Down
96 changes: 96 additions & 0 deletions test/cmd_line/command.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import * as assert from 'assert';
import { getAndUpdateModeHandler } from '../../extension';
import { ModeHandler } from '../../src/mode/modeHandler';
import { StatusBar } from '../../src/statusBar';
import { cleanUpWorkspace, setupWorkspace } from '../testUtils';

suite('cmd_line/search command', () => {
let modeHandler: ModeHandler;

suiteSetup(async () => {
await setupWorkspace();
modeHandler = await getAndUpdateModeHandler();
});

suiteTeardown(cleanUpWorkspace);

test('command <C-w> can remove word in cmd line', async () => {
await modeHandler.handleMultipleKeyEvents([':', 'a', 'b', 'c', '-', '1', '2', '3', '<C-w>']);
let statusBar = StatusBar.Get().trim();
assert.equal(statusBar, ':abc-|', 'Failed to remove word');

await modeHandler.handleKeyEvent('<C-w>');
statusBar = StatusBar.Get().trim();
assert.equal(statusBar, ':abc|', 'Failed to remove word');

await modeHandler.handleKeyEvent('<C-w>');
statusBar = StatusBar.Get().trim();
assert.equal(statusBar, ':|', 'Failed to remove word');

await modeHandler.handleKeyEvent('<C-w>');
statusBar = StatusBar.Get().trim();
assert.equal(statusBar, ':|', 'Failed to remove word');

await modeHandler.handleKeyEvent('<Esc>');
});

test('command <C-w> can remove word in search mode', async () => {
await modeHandler.handleMultipleKeyEvents(['/', 'a', 'b', 'c', '-', '1', '2', '3', '<C-w>']);
let statusBar = StatusBar.Get().trim();
assert.equal(statusBar, '/abc-|', 'Failed to remove word');

await modeHandler.handleKeyEvent('<C-w>');
statusBar = StatusBar.Get().trim();
assert.equal(statusBar, '/abc|', 'Failed to remove word');

await modeHandler.handleKeyEvent('<C-w>');
statusBar = StatusBar.Get().trim();
assert.equal(statusBar, '/|', 'Failed to remove word');

await modeHandler.handleKeyEvent('<C-w>');
statusBar = StatusBar.Get().trim();
assert.equal(statusBar, '/|', 'Failed to remove word');

await modeHandler.handleKeyEvent('<Esc>');
});

test('command <C-w> can remove word in cmd line while retrain cmd on the right of the cursor', async () => {
await modeHandler.handleMultipleKeyEvents([
':',
'a',
'b',
'c',
' ',
'1',
'2',
'3',
'<left>',
'<left>',
'<left>',
'<C-w>',
]);
const statusBar = StatusBar.Get().trim();
assert.equal(statusBar, ':|123', 'Failed to retain the text on the right of the cursor');
await modeHandler.handleKeyEvent('<Esc>');
});

test('command <C-w> can remove word in search mode while retrain cmd on the right of the cursor', async () => {
await modeHandler.handleMultipleKeyEvents([
'/',
'a',
'b',
'c',
' ',
'1',
'2',
'3',
'<left>',
'<left>',
'<left>',
'<C-w>',
]);
const statusBar = StatusBar.Get().trim();
assert.equal(statusBar, '/|123', 'Failed to retain the text on the right of the cursor');
await modeHandler.handleKeyEvent('<Esc>');
});
});

0 comments on commit 885d6e9

Please sign in to comment.