diff --git a/.changeset/macos-image-paste.md b/.changeset/macos-image-paste.md new file mode 100644 index 000000000..2ebadf9f8 --- /dev/null +++ b/.changeset/macos-image-paste.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Allow Cmd+V to paste clipboard images on macOS. diff --git a/apps/kimi-code/src/tui/components/editor/custom-editor.ts b/apps/kimi-code/src/tui/components/editor/custom-editor.ts index ded2af648..53fec4ab7 100644 --- a/apps/kimi-code/src/tui/components/editor/custom-editor.ts +++ b/apps/kimi-code/src/tui/components/editor/custom-editor.ts @@ -111,6 +111,12 @@ function getNewlineInput(data: string): string | undefined { return undefined; } +function isPasteImageShortcut(data: string): boolean { + if (process.platform === 'win32') return matchesKey(data, Key.alt('v')); + if (matchesKey(data, Key.ctrl('v'))) return true; + return process.platform === 'darwin' && matchesKey(data, Key.super('v')); +} + export class CustomEditor extends Editor { public onEscape?: () => void; public onCtrlD?: () => void; @@ -132,8 +138,8 @@ export class CustomEditor extends Editor { public connectedAbove = false; public borderHighlighted = false; /** - * Called when the user triggers "paste image" (Ctrl-V on Unix, - * Alt-V on Windows — Ctrl-V is terminal-reserved there). Return + * Called when the user triggers "paste image" (Ctrl-V on non-Windows, + * plus Cmd-V on macOS and Alt-V on Windows). Return * `true` to consume the key (image was read and handled); return * `false` to let the key fall through to the normal paste path. * The callback may be async; pi-tui awaits it before dispatching @@ -273,11 +279,11 @@ export class CustomEditor extends Editor { // Paste image binding — platform-aware: // Windows terminals reserve Ctrl-V for their own paste handling // (e.g. Windows Terminal's Ctrl+V shortcut), so we listen for - // Alt-V there. Everywhere else Ctrl-V pastes. When the host - // reports no image available, we fall through to pi-tui's - // normal paste path so text from the clipboard still works. - const pasteKey = process.platform === 'win32' ? 'alt+v' : Key.ctrl('v'); - if (matchesKey(normalized, pasteKey)) { + // Alt-V there. Non-Windows keeps Ctrl-V, and macOS also accepts + // Cmd-V (Kitty "super"). + // When the host reports no image available, we fall through to + // pi-tui's normal paste path so text from the clipboard still works. + if (isPasteImageShortcut(normalized)) { if (this.expandPasteMarkerAtCursor()) { return; } diff --git a/apps/kimi-code/test/tui/components/editor/custom-editor.test.ts b/apps/kimi-code/test/tui/components/editor/custom-editor.test.ts index fa30293cc..30c7ebb22 100644 --- a/apps/kimi-code/test/tui/components/editor/custom-editor.test.ts +++ b/apps/kimi-code/test/tui/components/editor/custom-editor.test.ts @@ -255,6 +255,29 @@ describe('CustomEditor paste marker expansion', () => { }); }); +describe('CustomEditor image paste shortcuts', () => { + it('handles macOS Cmd+V as an image paste shortcut', async () => { + const originalPlatform = process.platform; + Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true }); + try { + const editor = makeEditor(); + const onPasteImage = vi.fn(async () => true); + const onTextPaste = vi.fn(); + editor.onPasteImage = onPasteImage; + editor.onTextPaste = onTextPaste; + + editor.handleInput('\u001B[118;9u'); + await Promise.resolve(); + + expect(onPasteImage).toHaveBeenCalledOnce(); + expect(onTextPaste).not.toHaveBeenCalled(); + expect(editor.getText()).toBe(''); + } finally { + Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true }); + } + }); +}); + describe('CustomEditor shortcut telemetry hooks', () => { it('reports newline shortcuts, including Ctrl-J, before delegating to the base editor', () => { const editor = makeEditor();