mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-16 17:11:04 -07:00
undo (#18147)
This commit is contained in:
@@ -821,65 +821,72 @@ describe('KeypressContext', () => {
|
||||
// Terminals to test
|
||||
const terminals = ['iTerm2', 'Ghostty', 'MacTerminal', 'VSCodeTerminal'];
|
||||
|
||||
// Key mappings: letter -> [keycode, accented character]
|
||||
const keys: Record<string, [number, string]> = {
|
||||
b: [98, '\u222B'],
|
||||
f: [102, '\u0192'],
|
||||
m: [109, '\u00B5'],
|
||||
// Key mappings: letter -> [keycode, accented character, shift]
|
||||
const keys: Record<string, [number, string, boolean]> = {
|
||||
b: [98, '\u222B', false],
|
||||
f: [102, '\u0192', false],
|
||||
m: [109, '\u00B5', false],
|
||||
z: [122, '\u03A9', false],
|
||||
Z: [122, '\u00B8', true],
|
||||
};
|
||||
|
||||
it.each(
|
||||
terminals.flatMap((terminal) =>
|
||||
Object.entries(keys).map(([key, [keycode, accentedChar]]) => {
|
||||
if (terminal === 'Ghostty') {
|
||||
// Ghostty uses kitty protocol sequences
|
||||
return {
|
||||
terminal,
|
||||
key,
|
||||
chunk: `\x1b[${keycode};3u`,
|
||||
expected: {
|
||||
name: key,
|
||||
shift: false,
|
||||
alt: true,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
},
|
||||
};
|
||||
} else if (terminal === 'MacTerminal') {
|
||||
// Mac Terminal sends ESC + letter
|
||||
return {
|
||||
terminal,
|
||||
key,
|
||||
kitty: false,
|
||||
chunk: `\x1b${key}`,
|
||||
expected: {
|
||||
sequence: `\x1b${key}`,
|
||||
name: key,
|
||||
shift: false,
|
||||
alt: true,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// iTerm2 and VSCode send accented characters (å, ø, µ)
|
||||
// Note: µ (mu) is sent with alt:false on iTerm2/VSCode but
|
||||
// gets converted to m with alt:true
|
||||
return {
|
||||
terminal,
|
||||
key,
|
||||
chunk: accentedChar,
|
||||
expected: {
|
||||
name: key,
|
||||
shift: false,
|
||||
alt: true, // Always expect alt:true after conversion
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence: accentedChar,
|
||||
},
|
||||
};
|
||||
}
|
||||
}),
|
||||
Object.entries(keys).map(
|
||||
([key, [keycode, accentedChar, shiftValue]]) => {
|
||||
if (terminal === 'Ghostty') {
|
||||
// Ghostty uses kitty protocol sequences
|
||||
// Modifier 3 is Alt, 4 is Shift+Alt
|
||||
const modifier = shiftValue ? 4 : 3;
|
||||
return {
|
||||
terminal,
|
||||
key,
|
||||
chunk: `\x1b[${keycode};${modifier}u`,
|
||||
expected: {
|
||||
name: key.toLowerCase(),
|
||||
shift: shiftValue,
|
||||
alt: true,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
},
|
||||
};
|
||||
} else if (terminal === 'MacTerminal') {
|
||||
// Mac Terminal sends ESC + letter
|
||||
const chunk = shiftValue
|
||||
? `\x1b${key.toUpperCase()}`
|
||||
: `\x1b${key.toLowerCase()}`;
|
||||
return {
|
||||
terminal,
|
||||
key,
|
||||
kitty: false,
|
||||
chunk,
|
||||
expected: {
|
||||
sequence: chunk,
|
||||
name: key.toLowerCase(),
|
||||
shift: shiftValue,
|
||||
alt: true,
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// iTerm2 and VSCode send accented characters (å, ø, µ, Ω, ¸)
|
||||
return {
|
||||
terminal,
|
||||
key,
|
||||
chunk: accentedChar,
|
||||
expected: {
|
||||
name: key.toLowerCase(),
|
||||
shift: shiftValue,
|
||||
alt: true, // Always expect alt:true after conversion
|
||||
ctrl: false,
|
||||
cmd: false,
|
||||
sequence: accentedChar,
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)(
|
||||
'should handle Alt+$key in $terminal',
|
||||
@@ -1302,4 +1309,57 @@ describe('KeypressContext', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Greek support', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
lang: 'en_US.UTF-8',
|
||||
expected: { name: 'z', alt: true, insertable: false },
|
||||
desc: 'non-Greek locale (Option+z)',
|
||||
},
|
||||
{
|
||||
lang: 'el_GR.UTF-8',
|
||||
expected: { name: '', insertable: true },
|
||||
desc: 'Greek LANG',
|
||||
},
|
||||
{
|
||||
lcAll: 'el_GR.UTF-8',
|
||||
expected: { name: '', insertable: true },
|
||||
desc: 'Greek LC_ALL',
|
||||
},
|
||||
{
|
||||
lang: 'en_US.UTF-8',
|
||||
lcAll: 'el_GR.UTF-8',
|
||||
expected: { name: '', insertable: true },
|
||||
desc: 'LC_ALL overriding non-Greek LANG',
|
||||
},
|
||||
{
|
||||
lang: 'el_GR.UTF-8',
|
||||
char: '\u00B8',
|
||||
expected: { name: 'z', alt: true, shift: true },
|
||||
desc: 'Cedilla (\u00B8) in Greek locale (should be Option+Shift+z)',
|
||||
},
|
||||
])(
|
||||
'should handle $char correctly in $desc',
|
||||
async ({ lang, lcAll, char = '\u03A9', expected }) => {
|
||||
if (lang) vi.stubEnv('LANG', lang);
|
||||
if (lcAll) vi.stubEnv('LC_ALL', lcAll);
|
||||
|
||||
const { keyHandler } = setupKeypressTest();
|
||||
|
||||
act(() => stdin.write(char));
|
||||
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
...expected,
|
||||
sequence: char,
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -130,6 +130,8 @@ const MAC_ALT_KEY_CHARACTER_MAP: Record<string, string> = {
|
||||
'\u222B': 'b', // "∫" back one word
|
||||
'\u0192': 'f', // "ƒ" forward one word
|
||||
'\u00B5': 'm', // "µ" toggle markup view
|
||||
'\u03A9': 'z', // "Ω" Option+z
|
||||
'\u00B8': 'Z', // "¸" Option+Shift+z
|
||||
};
|
||||
|
||||
function nonKeyboardEventFilter(
|
||||
@@ -305,6 +307,10 @@ function createDataListener(keypressHandler: KeypressHandler) {
|
||||
function* emitKeys(
|
||||
keypressHandler: KeypressHandler,
|
||||
): Generator<void, void, string> {
|
||||
const lang = process.env['LANG'] || '';
|
||||
const lcAll = process.env['LC_ALL'] || '';
|
||||
const isGreek = lang.startsWith('el') || lcAll.startsWith('el');
|
||||
|
||||
while (true) {
|
||||
let ch = yield;
|
||||
let sequence = ch;
|
||||
@@ -574,8 +580,15 @@ function* emitKeys(
|
||||
} else if (MAC_ALT_KEY_CHARACTER_MAP[ch]) {
|
||||
// Note: we do this even if we are not on Mac, because mac users may
|
||||
// remotely connect to non-Mac systems.
|
||||
name = MAC_ALT_KEY_CHARACTER_MAP[ch];
|
||||
alt = true;
|
||||
// We skip this mapping for Greek users to avoid blocking the Omega character.
|
||||
if (isGreek && ch === '\u03A9') {
|
||||
insertable = true;
|
||||
} else {
|
||||
const mapped = MAC_ALT_KEY_CHARACTER_MAP[ch];
|
||||
name = mapped.toLowerCase();
|
||||
shift = mapped !== name;
|
||||
alt = true;
|
||||
}
|
||||
} else if (sequence === `${ESC}${ESC}`) {
|
||||
// Double escape
|
||||
name = 'escape';
|
||||
|
||||
Reference in New Issue
Block a user