diff --git a/packages/coding-agent/docs/keybindings.md b/packages/coding-agent/docs/keybindings.md index 34e4d68b4..bdb8d3701 100644 --- a/packages/coding-agent/docs/keybindings.md +++ b/packages/coding-agent/docs/keybindings.md @@ -126,7 +126,7 @@ In fullscreen mode, these actions control the transcript and take precedence ove | `app.exit` | `ctrl+d` | Exit (when editor empty) | | `app.suspend` | `ctrl+z` (None on Windows) | Suspend to background | | `app.editor.external` | `ctrl+g` | Open in external editor (`externalEditor`, `$VISUAL`, `$EDITOR`, Notepad on Windows, or `nano` elsewhere) | -| `app.clipboard.pasteImage` | `ctrl+v` (`alt+v` on Windows and WSL) | Paste files on macOS, images, or text from clipboard | +| `app.clipboard.pasteImage` | `ctrl+v` (`alt+v`, `ctrl+v` on Windows and WSL) | Paste files on macOS, images, or text from clipboard | On native Windows, `app.suspend` has no default because Windows terminals do not support Unix job control. If you assign it manually, Pi shows a status message instead of suspending. WSL uses the normal `ctrl+z` and `fg` behavior. diff --git a/packages/coding-agent/src/core/keybindings.ts b/packages/coding-agent/src/core/keybindings.ts index 75e540b37..f06a2e6f1 100644 --- a/packages/coding-agent/src/core/keybindings.ts +++ b/packages/coding-agent/src/core/keybindings.ts @@ -73,7 +73,7 @@ const APP_KEYBINDINGS = { description: "Restore queued messages", }, "app.clipboard.pasteImage": { - defaultKeys: windowsKeybindings ? "alt+v" : "ctrl+v", + defaultKeys: windowsKeybindings ? ["alt+v", "ctrl+v"] : "ctrl+v", description: "Paste files on macOS, images, or text from clipboard", }, "app.session.new": { defaultKeys: [], description: "Start a new session" }, diff --git a/packages/coding-agent/test/keybindings.test.ts b/packages/coding-agent/test/keybindings.test.ts index cb505eede..cdc089d72 100644 --- a/packages/coding-agent/test/keybindings.test.ts +++ b/packages/coding-agent/test/keybindings.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { KEYBINDINGS, useWindowsKeybindings } from "../src/core/keybindings.ts"; describe("Windows keybinding defaults", () => { @@ -20,11 +20,26 @@ describe("Windows keybinding defaults", () => { expect(useWindowsKeybindings("darwin", {})).toBe(false); }); + it("keeps ctrl+v as a paste key on WSL", async () => { + if (process.platform !== "linux") return; + vi.stubEnv("WSL_DISTRO_NAME", "Ubuntu"); + vi.resetModules(); + try { + const { KEYBINDINGS: wslKeybindings } = await import("../src/core/keybindings.ts"); + expect(wslKeybindings["app.clipboard.pasteImage"].defaultKeys).toEqual(["alt+v", "ctrl+v"]); + } finally { + vi.unstubAllEnvs(); + vi.resetModules(); + } + }); + it("applies the detected defaults consistently", () => { const windowsKeybindings = useWindowsKeybindings(); const nativeWindows = process.platform === "win32"; - expect(KEYBINDINGS["app.clipboard.pasteImage"].defaultKeys).toBe(windowsKeybindings ? "alt+v" : "ctrl+v"); + expect(KEYBINDINGS["app.clipboard.pasteImage"].defaultKeys).toEqual( + windowsKeybindings ? ["alt+v", "ctrl+v"] : "ctrl+v", + ); expect(KEYBINDINGS["tui.altScreen.search"].defaultKeys).toBe(windowsKeybindings ? "ctrl+f" : "ctrl+shift+f"); expect(KEYBINDINGS["app.message.followUp"].defaultKeys).toBe(windowsKeybindings ? "ctrl+q" : "alt+enter"); expect(KEYBINDINGS["app.model.cycleBackward"].defaultKeys).toBe(windowsKeybindings ? "alt+p" : "shift+ctrl+p");