fix(cli): make alt+t expand thinking on macOS Option-compose terminals (#5872)

On macOS, terminals in the default "Option as compose character" mode
(iTerm2 "Normal", VS Code without macOptionIsMeta) turn Option+t into the
dagger glyph "†" (U+2020) with no modifier metadata, so the app cannot tell
Option was held — the "expand thinking" shortcut silently typed "†" into the
prompt instead of toggling. Terminals that speak the Kitty keyboard protocol
(e.g. Ghostty) report a real Alt+t event, which is why it already worked there.

Rewrite a lone "†" to a synthetic Alt+t in the keypress pipeline. This both
fires TOGGLE_THINKING_EXPANDED and, via the meta flag, stops the glyph from
being inserted into the input buffer, so it behaves exactly like Alt+t. Scoped
to darwin to avoid affecting legitimate "†" input on other platforms.

Generated with AI

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
ChiGao 2026-06-25 21:48:09 +08:00 committed by GitHub
parent a7b9c5d915
commit ecdf470bb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 115 additions and 0 deletions

View file

@ -122,6 +122,96 @@ describe('KeypressContext - Kitty Protocol', () => {
);
});
it('rewrites macOS composed Option+t glyph "†" to Alt+t', () => {
const originalPlatform = process.platform;
Object.defineProperty(process, 'platform', {
value: 'darwin',
configurable: true,
writable: true,
});
try {
const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), {
wrapper,
});
act(() => {
result.current.subscribe(keyHandler);
});
act(() => {
stdin.pressKey({
name: '',
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: '†',
});
});
expect(keyHandler).toHaveBeenCalledWith(
expect.objectContaining({
name: 't',
meta: true,
sequence: '†',
}),
);
} finally {
Object.defineProperty(process, 'platform', {
value: originalPlatform,
configurable: true,
writable: true,
});
}
});
it('leaves "†" untouched on non-macOS platforms', () => {
const originalPlatform = process.platform;
Object.defineProperty(process, 'platform', {
value: 'linux',
configurable: true,
writable: true,
});
try {
const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), {
wrapper,
});
act(() => {
result.current.subscribe(keyHandler);
});
act(() => {
stdin.pressKey({
name: '',
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: '†',
});
});
expect(keyHandler).toHaveBeenCalledWith(
expect.objectContaining({
name: '',
meta: false,
sequence: '†',
}),
);
} finally {
Object.defineProperty(process, 'platform', {
value: originalPlatform,
configurable: true,
writable: true,
});
}
});
it('should recognize regular enter key (keycode 13) in kitty protocol', async () => {
const keyHandler = vi.fn();

View file

@ -46,6 +46,15 @@ import { clipboardHasImage } from '../utils/clipboardUtils.js';
import { FOCUS_IN, FOCUS_OUT } from '../hooks/useFocus.js';
const ESC = '\u001B';
// On macOS, when the terminal's Option key is in its default "compose
// character" mode (iTerm2 "Normal", VS Code without macOptionIsMeta), Option+t
// is delivered to the app as the dagger glyph "†" (U+2020) with no modifier
// metadata — so there is no way to tell Option was held. Terminals that speak
// the Kitty keyboard protocol (e.g. Ghostty) instead report a real Alt+t event,
// which is why the shortcut already works there. We treat a lone "†" as Alt+t so
// the "expand thinking" shortcut works everywhere without requiring users to
// reconfigure their terminal. See handleKeypress for where this is applied.
const OPTION_T_COMPOSED_GLYPH = '†';
export const PASTE_MODE_PREFIX = `${ESC}[200~`;
export const PASTE_MODE_SUFFIX = `${ESC}[201~`;
export const DRAG_COMPLETION_TIMEOUT_MS = 100; // Broadcast full path after 100ms if no more input
@ -1048,6 +1057,22 @@ export function KeypressProvider({
if (key.name === 'return' && key.sequence === `${ESC}\r`) {
key.meta = true;
}
// macOS "Option as compose character" terminals turn Option+t into the
// bare glyph "†" (U+2020) with no modifier metadata. Rewrite it to a
// synthetic Alt+t so the "expand thinking" shortcut fires; the meta flag
// also stops the glyph from being inserted into the input buffer (the
// text buffer skips printable input when meta/ctrl is set), so it looks
// exactly like Alt was pressed.
if (
process.platform === 'darwin' &&
!isPaste &&
key.sequence === OPTION_T_COMPOSED_GLYPH
) {
key.name = 't';
key.meta = true;
}
broadcast({ ...key, paste: isPaste });
};