mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
In a remote-project window, pasting a clipboard image into the terminal uploads it to the SSH host that owns the terminal (new term_store_image command over the existing remote RPC) and inserts the POSIX-quoted path after a successful upload, without a newline. Images go to a private per-user terminal-images directory (0700/0600, UUID names, no-follow descriptor-relative writes) and expire after 24 hours on the next upload. Failures notify and insert nothing; text paste and local terminals are unchanged. Images over 20 MiB are rejected in both the frontend and the host. Co-authored-by: dvictor357 <[email protected]>
104 lines
3.7 KiB
TypeScript
104 lines
3.7 KiB
TypeScript
import { pasteClipboardFiles, readClipboardText } from "@/platform/clipboard";
|
|
import { call, commands, type TerminalId } from "@/platform/ipc";
|
|
import { tx } from "@/platform/i18n";
|
|
import { notifications } from "@/workbench/contributions";
|
|
|
|
export interface RemotePasteTarget {
|
|
id: TerminalId;
|
|
supported: boolean;
|
|
current: () => boolean;
|
|
}
|
|
|
|
type Target = () => RemotePasteTarget | null;
|
|
|
|
const MAX_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
|
|
async function pasteImage(image: Blob, target: RemotePasteTarget, paste: (text: string) => void) {
|
|
try {
|
|
if (image.size > MAX_IMAGE_BYTES) throw new Error(tx`Image is too large to paste (limit ${20} MB).`);
|
|
const data = Array.from(new Uint8Array(await image.arrayBuffer()));
|
|
if (!target.current()) return;
|
|
if (!target.supported) throw new Error(tx("Remote server does not support image paste."));
|
|
const path = await call(commands.termStoreImage(target.id, data, image.type));
|
|
if (target.current()) paste(`'${path.replaceAll("'", "'\\''")}'`);
|
|
} catch (error) {
|
|
if (target.current())
|
|
notifications().notify({
|
|
level: "error",
|
|
title: tx("Cannot paste image"),
|
|
body: error instanceof Error ? error.message : String(error),
|
|
});
|
|
}
|
|
}
|
|
|
|
export function installTerminalPaste(host: HTMLElement, paste: (text: string) => void, getTarget?: Target): () => void {
|
|
let alive = true;
|
|
const onPaste = (event: ClipboardEvent) => {
|
|
const transfer = event.clipboardData;
|
|
const target = getTarget?.();
|
|
if (!event.defaultPrevented && target && transfer && !transfer.getData("text/plain")) {
|
|
const image = Array.from(transfer.files).find((file) => file.type.startsWith("image/"));
|
|
if (image) {
|
|
event.preventDefault();
|
|
event.stopImmediatePropagation();
|
|
void pasteImage(image, { ...target, current: () => alive && target.current() }, paste);
|
|
return;
|
|
}
|
|
}
|
|
pasteClipboardFiles(event, (text) => {
|
|
if (alive) paste(text);
|
|
});
|
|
};
|
|
host.addEventListener("paste", onPaste, true);
|
|
return () => {
|
|
alive = false;
|
|
host.removeEventListener("paste", onPaste, true);
|
|
};
|
|
}
|
|
|
|
export async function pasteTerminalClipboard(paste: (text: string) => void, getTarget?: Target) {
|
|
const target = getTarget?.();
|
|
try {
|
|
// Native paste preserves clipboard blobs where Clipboard.read() is unavailable.
|
|
if (target && document.queryCommandSupported?.("paste") && document.execCommand("paste")) return;
|
|
const images =
|
|
target && navigator.clipboard?.read
|
|
? navigator.clipboard.read().then(
|
|
(items) => ({ items }),
|
|
(error: unknown) => ({ error }),
|
|
)
|
|
: null;
|
|
const text = target ? ((await navigator.clipboard?.readText().catch(() => "")) ?? "") : await readClipboardText();
|
|
if (target && !target.current()) return;
|
|
if (text) {
|
|
paste(text);
|
|
return;
|
|
}
|
|
if (!target) return;
|
|
if (images) {
|
|
const result = await images;
|
|
if ("error" in result) throw result.error;
|
|
if (!target.current()) return;
|
|
for (const item of result.items) {
|
|
const mime = item.types.find((type) => type.startsWith("image/"));
|
|
if (mime) {
|
|
const image = await item.getType(mime);
|
|
if (target.current()) await pasteImage(image, target, paste);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
const paths = await readClipboardText();
|
|
if (!target.current()) return;
|
|
if (paths) paste(paths);
|
|
else if (!images) throw new Error(tx("Clipboard image reading is unavailable."));
|
|
} catch (error) {
|
|
if (target?.current())
|
|
notifications().notify({
|
|
level: "error",
|
|
title: tx("Cannot paste image"),
|
|
body: error instanceof Error ? error.message : String(error),
|
|
});
|
|
}
|
|
}
|