Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/macos-image-paste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Allow Cmd+V to paste clipboard images on macOS.
20 changes: 13 additions & 7 deletions apps/kimi-code/src/tui/components/editor/custom-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down
23 changes: 23 additions & 0 deletions apps/kimi-code/test/tui/components/editor/custom-editor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down