feat(cli): Add W, B, E Vim motions and operator support (#16209)

Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
This commit is contained in:
Adam DeMuri
2026-02-05 10:29:30 -07:00
committed by GitHub
parent 1cae5ab158
commit ee2c8eef19
6 changed files with 836 additions and 82 deletions

View File

@@ -24,9 +24,15 @@ const CMD_TYPES = {
DELETE_WORD_FORWARD: 'dw',
DELETE_WORD_BACKWARD: 'db',
DELETE_WORD_END: 'de',
DELETE_BIG_WORD_FORWARD: 'dW',
DELETE_BIG_WORD_BACKWARD: 'dB',
DELETE_BIG_WORD_END: 'dE',
CHANGE_WORD_FORWARD: 'cw',
CHANGE_WORD_BACKWARD: 'cb',
CHANGE_WORD_END: 'ce',
CHANGE_BIG_WORD_FORWARD: 'cW',
CHANGE_BIG_WORD_BACKWARD: 'cB',
CHANGE_BIG_WORD_END: 'cE',
DELETE_CHAR: 'x',
DELETE_LINE: 'dd',
CHANGE_LINE: 'cc',
@@ -187,6 +193,21 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
break;
}
case CMD_TYPES.DELETE_BIG_WORD_FORWARD: {
buffer.vimDeleteBigWordForward(count);
break;
}
case CMD_TYPES.DELETE_BIG_WORD_BACKWARD: {
buffer.vimDeleteBigWordBackward(count);
break;
}
case CMD_TYPES.DELETE_BIG_WORD_END: {
buffer.vimDeleteBigWordEnd(count);
break;
}
case CMD_TYPES.CHANGE_WORD_FORWARD: {
buffer.vimChangeWordForward(count);
updateMode('INSERT');
@@ -205,6 +226,24 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
break;
}
case CMD_TYPES.CHANGE_BIG_WORD_FORWARD: {
buffer.vimChangeBigWordForward(count);
updateMode('INSERT');
break;
}
case CMD_TYPES.CHANGE_BIG_WORD_BACKWARD: {
buffer.vimChangeBigWordBackward(count);
updateMode('INSERT');
break;
}
case CMD_TYPES.CHANGE_BIG_WORD_END: {
buffer.vimChangeBigWordEnd(count);
updateMode('INSERT');
break;
}
case CMD_TYPES.DELETE_CHAR: {
buffer.vimDeleteChar(count);
break;
@@ -371,7 +410,10 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
* @returns boolean indicating if command was handled
*/
const handleOperatorMotion = useCallback(
(operator: 'd' | 'c', motion: 'w' | 'b' | 'e'): boolean => {
(
operator: 'd' | 'c',
motion: 'w' | 'b' | 'e' | 'W' | 'B' | 'E',
): boolean => {
const count = getCurrentCount();
const commandMap = {
@@ -379,11 +421,17 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
w: CMD_TYPES.DELETE_WORD_FORWARD,
b: CMD_TYPES.DELETE_WORD_BACKWARD,
e: CMD_TYPES.DELETE_WORD_END,
W: CMD_TYPES.DELETE_BIG_WORD_FORWARD,
B: CMD_TYPES.DELETE_BIG_WORD_BACKWARD,
E: CMD_TYPES.DELETE_BIG_WORD_END,
},
c: {
w: CMD_TYPES.CHANGE_WORD_FORWARD,
b: CMD_TYPES.CHANGE_WORD_BACKWARD,
e: CMD_TYPES.CHANGE_WORD_END,
W: CMD_TYPES.CHANGE_BIG_WORD_FORWARD,
B: CMD_TYPES.CHANGE_BIG_WORD_BACKWARD,
E: CMD_TYPES.CHANGE_BIG_WORD_END,
},
};
@@ -524,6 +572,21 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
return true;
}
case 'W': {
// Check if this is part of a delete or change command (dW/cW)
if (state.pendingOperator === 'd') {
return handleOperatorMotion('d', 'W');
}
if (state.pendingOperator === 'c') {
return handleOperatorMotion('c', 'W');
}
// Normal big word movement
buffer.vimMoveBigWordForward(repeatCount);
dispatch({ type: 'CLEAR_COUNT' });
return true;
}
case 'b': {
// Check if this is part of a delete or change command (db/cb)
if (state.pendingOperator === 'd') {
@@ -539,6 +602,21 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
return true;
}
case 'B': {
// Check if this is part of a delete or change command (dB/cB)
if (state.pendingOperator === 'd') {
return handleOperatorMotion('d', 'B');
}
if (state.pendingOperator === 'c') {
return handleOperatorMotion('c', 'B');
}
// Normal backward big word movement
buffer.vimMoveBigWordBackward(repeatCount);
dispatch({ type: 'CLEAR_COUNT' });
return true;
}
case 'e': {
// Check if this is part of a delete or change command (de/ce)
if (state.pendingOperator === 'd') {
@@ -554,6 +632,21 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
return true;
}
case 'E': {
// Check if this is part of a delete or change command (dE/cE)
if (state.pendingOperator === 'd') {
return handleOperatorMotion('d', 'E');
}
if (state.pendingOperator === 'c') {
return handleOperatorMotion('c', 'E');
}
// Normal big word end movement
buffer.vimMoveBigWordEnd(repeatCount);
dispatch({ type: 'CLEAR_COUNT' });
return true;
}
case 'x': {
// Delete character under cursor
buffer.vimDeleteChar(repeatCount);