mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-22 19:14:33 -07:00
feat(ui): add vim yank/paste (y/p/P) with unnamed register (#22026)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
@@ -63,6 +63,14 @@ const CMD_TYPES = {
|
||||
DELETE_TO_LAST_LINE: 'dG',
|
||||
CHANGE_TO_FIRST_LINE: 'cgg',
|
||||
CHANGE_TO_LAST_LINE: 'cG',
|
||||
YANK_LINE: 'yy',
|
||||
YANK_WORD_FORWARD: 'yw',
|
||||
YANK_BIG_WORD_FORWARD: 'yW',
|
||||
YANK_WORD_END: 'ye',
|
||||
YANK_BIG_WORD_END: 'yE',
|
||||
YANK_TO_EOL: 'y$',
|
||||
PASTE_AFTER: 'p',
|
||||
PASTE_BEFORE: 'P',
|
||||
} as const;
|
||||
|
||||
type PendingFindOp = {
|
||||
@@ -80,7 +88,7 @@ const createClearPendingState = () => ({
|
||||
type VimState = {
|
||||
mode: VimMode;
|
||||
count: number;
|
||||
pendingOperator: 'g' | 'd' | 'c' | 'dg' | 'cg' | null;
|
||||
pendingOperator: 'g' | 'd' | 'c' | 'y' | 'dg' | 'cg' | null;
|
||||
pendingFindOp: PendingFindOp | undefined;
|
||||
lastCommand: { type: string; count: number; char?: string } | null;
|
||||
lastFind: { op: 'f' | 'F' | 't' | 'T'; char: string } | undefined;
|
||||
@@ -93,7 +101,7 @@ type VimAction =
|
||||
| { type: 'CLEAR_COUNT' }
|
||||
| {
|
||||
type: 'SET_PENDING_OPERATOR';
|
||||
operator: 'g' | 'd' | 'c' | 'dg' | 'cg' | null;
|
||||
operator: 'g' | 'd' | 'c' | 'y' | 'dg' | 'cg' | null;
|
||||
}
|
||||
| { type: 'SET_PENDING_FIND_OP'; pendingFindOp: PendingFindOp | undefined }
|
||||
| {
|
||||
@@ -408,6 +416,46 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_TYPES.YANK_LINE: {
|
||||
buffer.vimYankLine(count);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_TYPES.YANK_WORD_FORWARD: {
|
||||
buffer.vimYankWordForward(count);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_TYPES.YANK_BIG_WORD_FORWARD: {
|
||||
buffer.vimYankBigWordForward(count);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_TYPES.YANK_WORD_END: {
|
||||
buffer.vimYankWordEnd(count);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_TYPES.YANK_BIG_WORD_END: {
|
||||
buffer.vimYankBigWordEnd(count);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_TYPES.YANK_TO_EOL: {
|
||||
buffer.vimYankToEndOfLine(count);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_TYPES.PASTE_AFTER: {
|
||||
buffer.vimPasteAfter(count);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_TYPES.PASTE_BEFORE: {
|
||||
buffer.vimPasteBefore(count);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -776,6 +824,17 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
|
||||
if (state.pendingOperator === 'c') {
|
||||
return handleOperatorMotion('c', 'w');
|
||||
}
|
||||
if (state.pendingOperator === 'y') {
|
||||
const count = getCurrentCount();
|
||||
executeCommand(CMD_TYPES.YANK_WORD_FORWARD, count);
|
||||
dispatch({
|
||||
type: 'SET_LAST_COMMAND',
|
||||
command: { type: CMD_TYPES.YANK_WORD_FORWARD, count },
|
||||
});
|
||||
dispatch({ type: 'CLEAR_COUNT' });
|
||||
dispatch({ type: 'SET_PENDING_OPERATOR', operator: null });
|
||||
return true;
|
||||
}
|
||||
|
||||
// Normal word movement
|
||||
buffer.vimMoveWordForward(repeatCount);
|
||||
@@ -791,6 +850,17 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
|
||||
if (state.pendingOperator === 'c') {
|
||||
return handleOperatorMotion('c', 'W');
|
||||
}
|
||||
if (state.pendingOperator === 'y') {
|
||||
const count = getCurrentCount();
|
||||
executeCommand(CMD_TYPES.YANK_BIG_WORD_FORWARD, count);
|
||||
dispatch({
|
||||
type: 'SET_LAST_COMMAND',
|
||||
command: { type: CMD_TYPES.YANK_BIG_WORD_FORWARD, count },
|
||||
});
|
||||
dispatch({ type: 'CLEAR_COUNT' });
|
||||
dispatch({ type: 'SET_PENDING_OPERATOR', operator: null });
|
||||
return true;
|
||||
}
|
||||
|
||||
// Normal big word movement
|
||||
buffer.vimMoveBigWordForward(repeatCount);
|
||||
@@ -836,6 +906,17 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
|
||||
if (state.pendingOperator === 'c') {
|
||||
return handleOperatorMotion('c', 'e');
|
||||
}
|
||||
if (state.pendingOperator === 'y') {
|
||||
const count = getCurrentCount();
|
||||
executeCommand(CMD_TYPES.YANK_WORD_END, count);
|
||||
dispatch({
|
||||
type: 'SET_LAST_COMMAND',
|
||||
command: { type: CMD_TYPES.YANK_WORD_END, count },
|
||||
});
|
||||
dispatch({ type: 'CLEAR_COUNT' });
|
||||
dispatch({ type: 'SET_PENDING_OPERATOR', operator: null });
|
||||
return true;
|
||||
}
|
||||
|
||||
// Normal word end movement
|
||||
buffer.vimMoveWordEnd(repeatCount);
|
||||
@@ -851,6 +932,17 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
|
||||
if (state.pendingOperator === 'c') {
|
||||
return handleOperatorMotion('c', 'E');
|
||||
}
|
||||
if (state.pendingOperator === 'y') {
|
||||
const count = getCurrentCount();
|
||||
executeCommand(CMD_TYPES.YANK_BIG_WORD_END, count);
|
||||
dispatch({
|
||||
type: 'SET_LAST_COMMAND',
|
||||
command: { type: CMD_TYPES.YANK_BIG_WORD_END, count },
|
||||
});
|
||||
dispatch({ type: 'CLEAR_COUNT' });
|
||||
dispatch({ type: 'SET_PENDING_OPERATOR', operator: null });
|
||||
return true;
|
||||
}
|
||||
|
||||
// Normal big word end movement
|
||||
buffer.vimMoveBigWordEnd(repeatCount);
|
||||
@@ -1027,6 +1119,17 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
|
||||
updateMode('INSERT');
|
||||
return true;
|
||||
}
|
||||
// Check if this is part of a yank command (y$)
|
||||
if (state.pendingOperator === 'y') {
|
||||
executeCommand(CMD_TYPES.YANK_TO_EOL, repeatCount);
|
||||
dispatch({
|
||||
type: 'SET_LAST_COMMAND',
|
||||
command: { type: CMD_TYPES.YANK_TO_EOL, count: repeatCount },
|
||||
});
|
||||
dispatch({ type: 'CLEAR_COUNT' });
|
||||
dispatch({ type: 'SET_PENDING_OPERATOR', operator: null });
|
||||
return true;
|
||||
}
|
||||
|
||||
// Move to end of line (with count, move down count-1 lines first)
|
||||
if (repeatCount > 1) {
|
||||
@@ -1220,6 +1323,59 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
case 'y': {
|
||||
if (state.pendingOperator === 'y') {
|
||||
// Second 'y' - yank N lines (yy command)
|
||||
const repeatCount = getCurrentCount();
|
||||
executeCommand(CMD_TYPES.YANK_LINE, repeatCount);
|
||||
dispatch({
|
||||
type: 'SET_LAST_COMMAND',
|
||||
command: { type: CMD_TYPES.YANK_LINE, count: repeatCount },
|
||||
});
|
||||
dispatch({ type: 'CLEAR_COUNT' });
|
||||
dispatch({ type: 'SET_PENDING_OPERATOR', operator: null });
|
||||
} else if (state.pendingOperator === null) {
|
||||
// First 'y' - wait for motion
|
||||
dispatch({ type: 'SET_PENDING_OPERATOR', operator: 'y' });
|
||||
} else {
|
||||
// Another operator is pending; clear it
|
||||
dispatch({ type: 'CLEAR_PENDING_STATES' });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case 'Y': {
|
||||
// Y yanks from cursor to end of line (equivalent to y$)
|
||||
const repeatCount = getCurrentCount();
|
||||
executeCommand(CMD_TYPES.YANK_TO_EOL, repeatCount);
|
||||
dispatch({
|
||||
type: 'SET_LAST_COMMAND',
|
||||
command: { type: CMD_TYPES.YANK_TO_EOL, count: repeatCount },
|
||||
});
|
||||
dispatch({ type: 'CLEAR_COUNT' });
|
||||
return true;
|
||||
}
|
||||
|
||||
case 'p': {
|
||||
executeCommand(CMD_TYPES.PASTE_AFTER, repeatCount);
|
||||
dispatch({
|
||||
type: 'SET_LAST_COMMAND',
|
||||
command: { type: CMD_TYPES.PASTE_AFTER, count: repeatCount },
|
||||
});
|
||||
dispatch({ type: 'CLEAR_COUNT' });
|
||||
return true;
|
||||
}
|
||||
|
||||
case 'P': {
|
||||
executeCommand(CMD_TYPES.PASTE_BEFORE, repeatCount);
|
||||
dispatch({
|
||||
type: 'SET_LAST_COMMAND',
|
||||
command: { type: CMD_TYPES.PASTE_BEFORE, count: repeatCount },
|
||||
});
|
||||
dispatch({ type: 'CLEAR_COUNT' });
|
||||
return true;
|
||||
}
|
||||
|
||||
case 'D': {
|
||||
// Delete from cursor to end of line (with count, delete to end of N lines)
|
||||
executeCommand(CMD_TYPES.DELETE_TO_EOL, repeatCount);
|
||||
|
||||
Reference in New Issue
Block a user