fix(coding-agent): fall back to text clipboard paste
This commit is contained in:
@@ -110,7 +110,7 @@ export const KEYBINDINGS = {
|
||||
},
|
||||
"app.clipboard.pasteImage": {
|
||||
defaultKeys: process.platform === "win32" ? "alt+v" : "ctrl+v",
|
||||
description: "Paste image from clipboard",
|
||||
description: "Paste image from clipboard (text fallback)",
|
||||
},
|
||||
"app.session.new": { defaultKeys: [], description: "Start a new session" },
|
||||
"app.session.tree": { defaultKeys: [], description: "Open session tree" },
|
||||
|
||||
@@ -33,7 +33,7 @@ export class CustomEditor extends Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for paste image keybinding
|
||||
// Check for clipboard paste keybinding
|
||||
if (this.keybindings.matches(data, "app.clipboard.pasteImage")) {
|
||||
this.onPasteImage?.();
|
||||
return;
|
||||
|
||||
@@ -95,7 +95,7 @@ import { isInstallTelemetryEnabled } from "../../core/telemetry.ts";
|
||||
import type { TruncationResult } from "../../core/tools/truncate.ts";
|
||||
import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.ts";
|
||||
import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelog } from "../../utils/changelog.ts";
|
||||
import { copyToClipboard } from "../../utils/clipboard.ts";
|
||||
import { copyToClipboard, readClipboardText } from "../../utils/clipboard.ts";
|
||||
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.ts";
|
||||
import { parseGitUrl } from "../../utils/git.ts";
|
||||
import { getCwdRelativePath } from "../../utils/paths.ts";
|
||||
@@ -770,7 +770,7 @@ export class InteractiveMode {
|
||||
rawKeyHint("!!", "to run bash (no context)"),
|
||||
hint("app.message.followUp", "to queue follow-up"),
|
||||
hint("app.message.dequeue", "to edit all queued messages"),
|
||||
hint("app.clipboard.pasteImage", "to paste image"),
|
||||
hint("app.clipboard.pasteImage", "to paste image (with text fallback)"),
|
||||
rawKeyHint("drop files", "to attach"),
|
||||
].join("\n");
|
||||
const compactInstructions = [
|
||||
@@ -2599,29 +2599,33 @@ export class InteractiveMode {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle clipboard image paste (triggered on Ctrl+V)
|
||||
// Handle clipboard paste (triggered on Ctrl+V). Images are attached by path;
|
||||
// otherwise, paste plain text from the system clipboard.
|
||||
this.defaultEditor.onPasteImage = () => {
|
||||
this.handleClipboardImagePaste();
|
||||
void this.handleClipboardPaste();
|
||||
};
|
||||
}
|
||||
|
||||
private async handleClipboardImagePaste(): Promise<void> {
|
||||
private async handleClipboardPaste(): Promise<void> {
|
||||
try {
|
||||
const image = await readClipboardImage();
|
||||
if (!image) {
|
||||
if (image) {
|
||||
const tmpDir = os.tmpdir();
|
||||
const ext = extensionForImageMimeType(image.mimeType) ?? "png";
|
||||
const fileName = `pi-clipboard-${crypto.randomUUID()}.${ext}`;
|
||||
const filePath = path.join(tmpDir, fileName);
|
||||
fs.writeFileSync(filePath, Buffer.from(image.bytes));
|
||||
|
||||
this.editor.insertTextAtCursor?.(filePath);
|
||||
this.ui.requestRender();
|
||||
return;
|
||||
}
|
||||
|
||||
// Write to temp file
|
||||
const tmpDir = os.tmpdir();
|
||||
const ext = extensionForImageMimeType(image.mimeType) ?? "png";
|
||||
const fileName = `pi-clipboard-${crypto.randomUUID()}.${ext}`;
|
||||
const filePath = path.join(tmpDir, fileName);
|
||||
fs.writeFileSync(filePath, Buffer.from(image.bytes));
|
||||
|
||||
// Insert file path directly
|
||||
this.editor.insertTextAtCursor?.(filePath);
|
||||
this.ui.requestRender();
|
||||
const text = await readClipboardText();
|
||||
if (text) {
|
||||
this.editor.insertTextAtCursor?.(text);
|
||||
this.ui.requestRender();
|
||||
}
|
||||
} catch {
|
||||
// Silently ignore clipboard errors (may not have permission, etc.)
|
||||
}
|
||||
@@ -5795,7 +5799,7 @@ export class InteractiveMode {
|
||||
| \`${copyMessage}\` | Copy last assistant message |
|
||||
| \`${followUp}\` | Queue follow-up message |
|
||||
| \`${dequeue}\` | Restore queued messages |
|
||||
| \`${pasteImage}\` | Paste image from clipboard |
|
||||
| \`${pasteImage}\` | Paste image or text from clipboard |
|
||||
| \`/\` | Slash commands |
|
||||
| \`!\` | Run bash command |
|
||||
| \`!!\` | Run bash command (excluded from context) |
|
||||
|
||||
@@ -3,6 +3,7 @@ import { dirname, join } from "path";
|
||||
import { pathToFileURL } from "url";
|
||||
|
||||
export type ClipboardModule = {
|
||||
getText: () => Promise<string>;
|
||||
setText: (text: string) => Promise<void>;
|
||||
hasImage: () => boolean;
|
||||
getImageBinary: () => Promise<Array<number>>;
|
||||
|
||||
@@ -32,6 +32,20 @@ function emitOsc52(text: string): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Read plain text from the system clipboard, if native clipboard access is available. */
|
||||
export async function readClipboardText(): Promise<string | null> {
|
||||
if (!clipboard) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const text = await clipboard.getText();
|
||||
return text || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function copyToClipboard(text: string): Promise<void> {
|
||||
let copied = false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user