feat(cli): add quick clear input shortcuts in vim mode (#17470)

Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
This commit is contained in:
Harsha Nadimpalli
2026-01-26 09:36:42 -08:00
committed by GitHub
parent 4827333c48
commit b8319bee76
2 changed files with 178 additions and 9 deletions

View File

@@ -4,11 +4,12 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { useCallback, useReducer, useEffect } from 'react';
import { useCallback, useReducer, useEffect, useRef } from 'react';
import type { Key } from './useKeypress.js';
import type { TextBuffer } from '../components/shared/text-buffer.js';
import { useVimMode } from '../contexts/VimModeContext.js';
import { debugLogger } from '@google/gemini-cli-core';
import { keyMatchers, Command } from '../keyMatchers.js';
export type VimMode = 'NORMAL' | 'INSERT';
@@ -16,6 +17,7 @@ export type VimMode = 'NORMAL' | 'INSERT';
const DIGIT_MULTIPLIER = 10;
const DEFAULT_COUNT = 1;
const DIGIT_1_TO_9 = /^[1-9]$/;
const DOUBLE_ESCAPE_TIMEOUT_MS = 500; // Timeout for double-escape to clear input
// Command types
const CMD_TYPES = {
@@ -130,6 +132,9 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
const { vimEnabled, vimMode, setVimMode } = useVimMode();
const [state, dispatch] = useReducer(vimReducer, initialVimState);
// Track last escape timestamp for double-escape detection
const lastEscapeTimestampRef = useRef<number>(0);
// Sync vim mode from context to local state
useEffect(() => {
dispatch({ type: 'SET_MODE', mode: vimMode });
@@ -150,6 +155,19 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
[state.count],
);
// Returns true if two escapes occurred within DOUBLE_ESCAPE_TIMEOUT_MS.
const checkDoubleEscape = useCallback((): boolean => {
const now = Date.now();
const lastEscape = lastEscapeTimestampRef.current;
lastEscapeTimestampRef.current = now;
if (now - lastEscape <= DOUBLE_ESCAPE_TIMEOUT_MS) {
lastEscapeTimestampRef.current = 0;
return true;
}
return false;
}, []);
/** Executes common commands to eliminate duplication in dot (.) repeat command */
const executeCommand = useCallback(
(cmdType: string, count: number) => {
@@ -247,9 +265,9 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
*/
const handleInsertModeInput = useCallback(
(normalizedKey: Key): boolean => {
// Handle escape key immediately - switch to NORMAL mode on any escape
if (normalizedKey.name === 'escape') {
// Vim behavior: move cursor left when exiting insert mode (unless at beginning of line)
if (keyMatchers[Command.ESCAPE](normalizedKey)) {
// Record for double-escape detection (clearing happens in NORMAL mode)
checkDoubleEscape();
buffer.vimEscapeInsertMode();
dispatch({ type: 'ESCAPE_TO_NORMAL' });
updateMode('NORMAL');
@@ -298,7 +316,7 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
buffer.handleInput(normalizedKey);
return true; // Handled by vim
},
[buffer, dispatch, updateMode, onSubmit],
[buffer, dispatch, updateMode, onSubmit, checkDoubleEscape],
);
/**
@@ -401,6 +419,11 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
return false;
}
// Let InputPrompt handle Ctrl+C for clearing input (works in all modes)
if (keyMatchers[Command.CLEAR_INPUT](normalizedKey)) {
return false;
}
// Handle INSERT mode
if (state.mode === 'INSERT') {
return handleInsertModeInput(normalizedKey);
@@ -408,14 +431,21 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
// Handle NORMAL mode
if (state.mode === 'NORMAL') {
// If in NORMAL mode, allow escape to pass through to other handlers
// if there's no pending operation.
if (normalizedKey.name === 'escape') {
if (keyMatchers[Command.ESCAPE](normalizedKey)) {
if (state.pendingOperator) {
dispatch({ type: 'CLEAR_PENDING_STATES' });
lastEscapeTimestampRef.current = 0;
return true; // Handled by vim
}
return false; // Pass through to other handlers
// Check for double-escape to clear buffer
if (checkDoubleEscape()) {
buffer.setText('');
return true;
}
// First escape in NORMAL mode - pass through for UI feedback
return false;
}
// Handle count input (numbers 1-9, and 0 if count > 0)
@@ -776,6 +806,7 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
buffer,
executeCommand,
updateMode,
checkDoubleEscape,
],
);