mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-13 23:01:09 -07:00
feat(cli): add quick clear input shortcuts in vim mode (#17470)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
This commit is contained in:
committed by
GitHub
parent
4827333c48
commit
b8319bee76
@@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user