mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-21 02:24:09 -07:00
refactor(ui): unify keybinding infrastructure and support string initialization (#21776)
This commit is contained in:
committed by
GitHub
parent
b89944c3a3
commit
215f8f3f15
@@ -1,77 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { Key } from '../contexts/KeypressContext.js';
|
||||
|
||||
export type { Key };
|
||||
|
||||
/**
|
||||
* Translates a Key object into its corresponding ANSI escape sequence.
|
||||
* This is useful for sending control characters to a pseudo-terminal.
|
||||
*
|
||||
* @param key The Key object to translate.
|
||||
* @returns The ANSI escape sequence as a string, or null if no mapping exists.
|
||||
*/
|
||||
export function keyToAnsi(key: Key): string | null {
|
||||
if (key.ctrl) {
|
||||
// Ctrl + letter
|
||||
if (key.name >= 'a' && key.name <= 'z') {
|
||||
return String.fromCharCode(
|
||||
key.name.charCodeAt(0) - 'a'.charCodeAt(0) + 1,
|
||||
);
|
||||
}
|
||||
// Other Ctrl combinations might need specific handling
|
||||
switch (key.name) {
|
||||
case 'c':
|
||||
return '\x03'; // ETX (End of Text), commonly used for interrupt
|
||||
// Add other special ctrl cases if needed
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Arrow keys and other special keys
|
||||
switch (key.name) {
|
||||
case 'up':
|
||||
return '\x1b[A';
|
||||
case 'down':
|
||||
return '\x1b[B';
|
||||
case 'right':
|
||||
return '\x1b[C';
|
||||
case 'left':
|
||||
return '\x1b[D';
|
||||
case 'escape':
|
||||
return '\x1b';
|
||||
case 'tab':
|
||||
return '\t';
|
||||
case 'backspace':
|
||||
return '\x7f';
|
||||
case 'delete':
|
||||
return '\x1b[3~';
|
||||
case 'home':
|
||||
return '\x1b[H';
|
||||
case 'end':
|
||||
return '\x1b[F';
|
||||
case 'pageup':
|
||||
return '\x1b[5~';
|
||||
case 'pagedown':
|
||||
return '\x1b[6~';
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Enter/Return
|
||||
if (key.name === 'return') {
|
||||
return '\r';
|
||||
}
|
||||
|
||||
// If it's a simple character, return it.
|
||||
if (!key.ctrl && !key.cmd && key.sequence) {
|
||||
return key.sequence;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
getAdminErrorMessage,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { useKeypress } from './useKeypress.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { Command } from '../key/keyMatchers.js';
|
||||
import { useKeyMatchers } from './useKeyMatchers.js';
|
||||
import type { HistoryItemWithoutId } from '../types.js';
|
||||
import { MessageType } from '../types.js';
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import type { KeyMatchers } from '../keyMatchers.js';
|
||||
import { defaultKeyMatchers } from '../keyMatchers.js';
|
||||
import type { KeyMatchers } from '../key/keyMatchers.js';
|
||||
import { defaultKeyMatchers } from '../key/keyMatchers.js';
|
||||
|
||||
/**
|
||||
* Hook to retrieve the currently active key matchers.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { useReducer, useRef, useEffect, useCallback } from 'react';
|
||||
import { useKeypress, type Key } from './useKeypress.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { Command } from '../key/keyMatchers.js';
|
||||
import { debugLogger } from '@google/gemini-cli-core';
|
||||
import { useKeyMatchers } from './useKeyMatchers.js';
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ import {
|
||||
cleanupTerminalOnExit,
|
||||
terminalCapabilityManager,
|
||||
} from '../utils/terminalCapabilityManager.js';
|
||||
import { formatCommand } from '../utils/keybindingUtils.js';
|
||||
import { Command } from '../../config/keyBindings.js';
|
||||
import { formatCommand } from '../key/keybindingUtils.js';
|
||||
import { Command } from '../key/keyBindings.js';
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async () => {
|
||||
const actual = await vi.importActual('@google/gemini-cli-core');
|
||||
|
||||
@@ -20,8 +20,8 @@ import {
|
||||
terminalCapabilityManager,
|
||||
} from '../utils/terminalCapabilityManager.js';
|
||||
import { WARNING_PROMPT_DURATION_MS } from '../constants.js';
|
||||
import { formatCommand } from '../utils/keybindingUtils.js';
|
||||
import { Command } from '../../config/keyBindings.js';
|
||||
import { formatCommand } from '../key/keybindingUtils.js';
|
||||
import { Command } from '../key/keyBindings.js';
|
||||
|
||||
interface UseSuspendProps {
|
||||
handleWarning: (message: string) => void;
|
||||
|
||||
@@ -9,18 +9,12 @@ import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { useTabbedNavigation } from './useTabbedNavigation.js';
|
||||
import { useKeypress } from './useKeypress.js';
|
||||
import { useKeyMatchers } from './useKeyMatchers.js';
|
||||
import type { KeyMatchers } from '../keyMatchers.js';
|
||||
import type { Key, KeypressHandler } from '../contexts/KeypressContext.js';
|
||||
|
||||
vi.mock('./useKeypress.js', () => ({
|
||||
useKeypress: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./useKeyMatchers.js', () => ({
|
||||
useKeyMatchers: vi.fn(),
|
||||
}));
|
||||
|
||||
const createKey = (partial: Partial<Key>): Key => ({
|
||||
name: partial.name || '',
|
||||
sequence: partial.sequence || '',
|
||||
@@ -32,27 +26,10 @@ const createKey = (partial: Partial<Key>): Key => ({
|
||||
...partial,
|
||||
});
|
||||
|
||||
const mockKeyMatchers = {
|
||||
'cursor.left': vi.fn((key) => key.name === 'left'),
|
||||
'cursor.right': vi.fn((key) => key.name === 'right'),
|
||||
'dialog.next': vi.fn((key) => key.name === 'tab' && !key.shift),
|
||||
'dialog.previous': vi.fn((key) => key.name === 'tab' && key.shift),
|
||||
} as unknown as KeyMatchers;
|
||||
|
||||
vi.mock('../keyMatchers.js', () => ({
|
||||
Command: {
|
||||
MOVE_LEFT: 'cursor.left',
|
||||
MOVE_RIGHT: 'cursor.right',
|
||||
DIALOG_NEXT: 'dialog.next',
|
||||
DIALOG_PREV: 'dialog.previous',
|
||||
},
|
||||
}));
|
||||
|
||||
describe('useTabbedNavigation', () => {
|
||||
let capturedHandler: KeypressHandler;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(useKeyMatchers).mockReturnValue(mockKeyMatchers);
|
||||
vi.mocked(useKeypress).mockImplementation((handler) => {
|
||||
capturedHandler = handler;
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { useReducer, useCallback, useEffect, useRef } from 'react';
|
||||
import { useKeypress, type Key } from './useKeypress.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { Command } from '../key/keyMatchers.js';
|
||||
import { useKeyMatchers } from './useKeyMatchers.js';
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,7 @@ 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 { Command } from '../keyMatchers.js';
|
||||
import { Command } from '../key/keyMatchers.js';
|
||||
import { useKeyMatchers } from './useKeyMatchers.js';
|
||||
|
||||
export type VimMode = 'NORMAL' | 'INSERT';
|
||||
|
||||
Reference in New Issue
Block a user