mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-07 20:00:37 -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,143 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { formatKeyBinding, formatCommand } from './keybindingUtils.js';
|
||||
import { Command } from '../../config/keyBindings.js';
|
||||
import type { KeyBinding } from '../../config/keyBindings.js';
|
||||
|
||||
describe('keybindingUtils', () => {
|
||||
describe('formatKeyBinding', () => {
|
||||
const testCases: Array<{
|
||||
name: string;
|
||||
binding: KeyBinding;
|
||||
expected: {
|
||||
darwin: string;
|
||||
win32: string;
|
||||
linux: string;
|
||||
default: string;
|
||||
};
|
||||
}> = [
|
||||
{
|
||||
name: 'simple key',
|
||||
binding: { key: 'a' },
|
||||
expected: { darwin: 'A', win32: 'A', linux: 'A', default: 'A' },
|
||||
},
|
||||
{
|
||||
name: 'named key (return)',
|
||||
binding: { key: 'return' },
|
||||
expected: {
|
||||
darwin: 'Enter',
|
||||
win32: 'Enter',
|
||||
linux: 'Enter',
|
||||
default: 'Enter',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'named key (escape)',
|
||||
binding: { key: 'escape' },
|
||||
expected: { darwin: 'Esc', win32: 'Esc', linux: 'Esc', default: 'Esc' },
|
||||
},
|
||||
{
|
||||
name: 'ctrl modifier',
|
||||
binding: { key: 'c', ctrl: true },
|
||||
expected: {
|
||||
darwin: 'Ctrl+C',
|
||||
win32: 'Ctrl+C',
|
||||
linux: 'Ctrl+C',
|
||||
default: 'Ctrl+C',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'cmd modifier',
|
||||
binding: { key: 'z', cmd: true },
|
||||
expected: {
|
||||
darwin: 'Cmd+Z',
|
||||
win32: 'Win+Z',
|
||||
linux: 'Super+Z',
|
||||
default: 'Cmd/Win+Z',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'alt/option modifier',
|
||||
binding: { key: 'left', alt: true },
|
||||
expected: {
|
||||
darwin: 'Option+Left',
|
||||
win32: 'Alt+Left',
|
||||
linux: 'Alt+Left',
|
||||
default: 'Alt+Left',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'shift modifier',
|
||||
binding: { key: 'up', shift: true },
|
||||
expected: {
|
||||
darwin: 'Shift+Up',
|
||||
win32: 'Shift+Up',
|
||||
linux: 'Shift+Up',
|
||||
default: 'Shift+Up',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'multiple modifiers (ctrl+shift)',
|
||||
binding: { key: 'z', ctrl: true, shift: true },
|
||||
expected: {
|
||||
darwin: 'Ctrl+Shift+Z',
|
||||
win32: 'Ctrl+Shift+Z',
|
||||
linux: 'Ctrl+Shift+Z',
|
||||
default: 'Ctrl+Shift+Z',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'all modifiers',
|
||||
binding: { key: 'a', ctrl: true, alt: true, shift: true, cmd: true },
|
||||
expected: {
|
||||
darwin: 'Ctrl+Option+Shift+Cmd+A',
|
||||
win32: 'Ctrl+Alt+Shift+Win+A',
|
||||
linux: 'Ctrl+Alt+Shift+Super+A',
|
||||
default: 'Ctrl+Alt+Shift+Cmd/Win+A',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
testCases.forEach(({ name, binding, expected }) => {
|
||||
describe(`${name}`, () => {
|
||||
it('formats correctly for darwin', () => {
|
||||
expect(formatKeyBinding(binding, 'darwin')).toBe(expected.darwin);
|
||||
});
|
||||
it('formats correctly for win32', () => {
|
||||
expect(formatKeyBinding(binding, 'win32')).toBe(expected.win32);
|
||||
});
|
||||
it('formats correctly for linux', () => {
|
||||
expect(formatKeyBinding(binding, 'linux')).toBe(expected.linux);
|
||||
});
|
||||
it('formats correctly for default', () => {
|
||||
expect(formatKeyBinding(binding, 'default')).toBe(expected.default);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatCommand', () => {
|
||||
it('formats default commands (using default platform behavior)', () => {
|
||||
expect(formatCommand(Command.QUIT, undefined, 'default')).toBe('Ctrl+C');
|
||||
expect(formatCommand(Command.SUBMIT, undefined, 'default')).toBe('Enter');
|
||||
expect(
|
||||
formatCommand(Command.TOGGLE_BACKGROUND_SHELL, undefined, 'default'),
|
||||
).toBe('Ctrl+B');
|
||||
});
|
||||
|
||||
it('returns empty string for unknown commands', () => {
|
||||
expect(
|
||||
formatCommand(
|
||||
'unknown.command' as unknown as Command,
|
||||
undefined,
|
||||
'default',
|
||||
),
|
||||
).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,111 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import process from 'node:process';
|
||||
import {
|
||||
type Command,
|
||||
type KeyBinding,
|
||||
type KeyBindingConfig,
|
||||
defaultKeyBindings,
|
||||
} from '../../config/keyBindings.js';
|
||||
|
||||
/**
|
||||
* Maps internal key names to user-friendly display names.
|
||||
*/
|
||||
const KEY_NAME_MAP: Record<string, string> = {
|
||||
return: 'Enter',
|
||||
escape: 'Esc',
|
||||
backspace: 'Backspace',
|
||||
delete: 'Delete',
|
||||
up: 'Up',
|
||||
down: 'Down',
|
||||
left: 'Left',
|
||||
right: 'Right',
|
||||
pageup: 'Page Up',
|
||||
pagedown: 'Page Down',
|
||||
home: 'Home',
|
||||
end: 'End',
|
||||
tab: 'Tab',
|
||||
space: 'Space',
|
||||
'double escape': 'Double Esc',
|
||||
};
|
||||
|
||||
interface ModifierMap {
|
||||
ctrl: string;
|
||||
alt: string;
|
||||
shift: string;
|
||||
cmd: string;
|
||||
}
|
||||
|
||||
const MODIFIER_MAPS: Record<string, ModifierMap> = {
|
||||
darwin: {
|
||||
ctrl: 'Ctrl',
|
||||
alt: 'Option',
|
||||
shift: 'Shift',
|
||||
cmd: 'Cmd',
|
||||
},
|
||||
win32: {
|
||||
ctrl: 'Ctrl',
|
||||
alt: 'Alt',
|
||||
shift: 'Shift',
|
||||
cmd: 'Win',
|
||||
},
|
||||
linux: {
|
||||
ctrl: 'Ctrl',
|
||||
alt: 'Alt',
|
||||
shift: 'Shift',
|
||||
cmd: 'Super',
|
||||
},
|
||||
default: {
|
||||
ctrl: 'Ctrl',
|
||||
alt: 'Alt',
|
||||
shift: 'Shift',
|
||||
cmd: 'Cmd/Win',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats a single KeyBinding into a human-readable string (e.g., "Ctrl+C").
|
||||
*/
|
||||
export function formatKeyBinding(
|
||||
binding: KeyBinding,
|
||||
platform?: string,
|
||||
): string {
|
||||
const activePlatform =
|
||||
platform ??
|
||||
(process.env['FORCE_GENERIC_KEYBINDING_HINTS']
|
||||
? 'default'
|
||||
: process.platform);
|
||||
const modMap = MODIFIER_MAPS[activePlatform] || MODIFIER_MAPS['default'];
|
||||
const parts: string[] = [];
|
||||
|
||||
if (binding.ctrl) parts.push(modMap.ctrl);
|
||||
if (binding.alt) parts.push(modMap.alt);
|
||||
if (binding.shift) parts.push(modMap.shift);
|
||||
if (binding.cmd) parts.push(modMap.cmd);
|
||||
|
||||
const keyName = KEY_NAME_MAP[binding.key] || binding.key.toUpperCase();
|
||||
parts.push(keyName);
|
||||
|
||||
return parts.join('+');
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the primary keybinding for a command.
|
||||
*/
|
||||
export function formatCommand(
|
||||
command: Command,
|
||||
config: KeyBindingConfig = defaultKeyBindings,
|
||||
platform?: string,
|
||||
): string {
|
||||
const bindings = config[command];
|
||||
if (!bindings || bindings.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Use the first binding as the primary one for display
|
||||
return formatKeyBinding(bindings[0], platform);
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { Command } from '../key/keyMatchers.js';
|
||||
import type { Key } from '../hooks/useKeypress.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user