From 53ad7b729e8c3005ed0b52b8ca34d0702c21d4e2 Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Mon, 27 Apr 2026 12:45:04 +0000 Subject: [PATCH] Fix clipboard actions over ssh --- src/tui/utils/clipboard.ts | 85 +++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/src/tui/utils/clipboard.ts b/src/tui/utils/clipboard.ts index 677f4ef..53bf7fd 100644 --- a/src/tui/utils/clipboard.ts +++ b/src/tui/utils/clipboard.ts @@ -8,6 +8,36 @@ import { promisify } from "util"; const execAsync = promisify(exec); +// Define a list of clipboard methods with their platform and command. +// The platform is a function that returns true if the method is available on the current platform. +// The command is a function that returns a promise that resolves to the result of the command. +const clipboardMethods = { + pbCopy: { + platform: (platform: string) => platform === 'darwin', + command: async (text: string) => execAsync(`printf '%s' '${text}' | pbcopy`), + }, + xclip: { + platform: (platform: string) => platform === 'linux', + command: async (text: string) => execAsync(`printf '%s' '${text}' | xclip -selection clipboard`), + }, + xsel: { + platform: (platform: string) => platform === 'linux', + command: async (text: string) => execAsync(`printf '%s' '${text}' | xsel --clipboard --input`), + }, + ssh: { + platform: (platform: string) => platform === 'linux', + command: async (text: string) => process.stdout.write(`\x1b]52;c;${Buffer.from(text, 'utf-8').toString('base64')}\x07`), + }, + clip: { + platform: (platform: string) => platform === 'windows', + command: async (text: string) => execAsync(`echo|set /p="${text}" | clip`), + }, + clipboardy: { + platform: (platform: string) => platform === 'windows', + command: async (text: string) => clipboardy.writeSync(text), + }, +} + /** * Attempts to copy text to clipboard using multiple methods. * Tries native commands first (most reliable), then clipboardy as fallback. @@ -21,46 +51,25 @@ export async function copyToClipboard(text: string): Promise { // Escape the text for shell commands const escapedText = text.replace(/'/g, "'\\''"); - // Try native commands first - they're more reliable - try { - if (platform === "darwin") { - // macOS - use pbcopy directly - await execAsync(`printf '%s' '${escapedText}' | pbcopy`); - return; - } else if (platform === "linux") { - // Linux - try xclip, then xsel - try { - await execAsync( - `printf '%s' '${escapedText}' | xclip -selection clipboard`, - ); - return; - } catch { - try { - await execAsync( - `printf '%s' '${escapedText}' | xsel --clipboard --input`, - ); - return; - } catch { - // Fall through to clipboardy - } - } - } else if (platform === "win32") { - // Windows - use clip.exe - await execAsync(`echo|set /p="${text}" | clip`); - return; - } - } catch { - // Native command failed, try clipboardy - } + const availableMethods = Object.values(clipboardMethods).filter(method => method.platform(platform)); - // Fallback to clipboardy - try { - clipboardy.writeSync(text); - return; - } catch { - // clipboardy also failed + const errors: Error[] = []; + + for (const method of availableMethods) { + try { + if (method.platform(platform)) { + await method.command(escapedText); + } else { + continue; + } + return; + } catch(error) { + if (error instanceof Error) { + errors.push(error); + } + } } // All methods failed - throw new Error(`Clipboard not available. Install xclip or xsel on Linux.`); + throw new Error(`Clipboard not available. ${errors.map(error => error.message).join('\n')}`); }