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
8 changes: 4 additions & 4 deletions packages/opencode/src/cli/cmd/tui/util/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import * as Process from "../../../../util/process"
const writeWithStdin = (cmd: string[], text: string): Promise<void> =>
Effect.runPromise(
AppProcess.Service.use((svc) => svc.run(ChildProcess.make(cmd[0]!, cmd.slice(1)), { stdin: text })).pipe(
Effect.flatMap(AppProcess.requireSuccess),
Effect.provide(AppProcess.defaultLayer),
Effect.catch(() => Effect.void),
Effect.asVoid,
),
).catch(() => undefined)
)

// Lazy load which and clipboardy to avoid expensive execa/which/isexe chain at startup
const getWhich = lazy(async () => {
Expand Down Expand Up @@ -130,7 +130,7 @@ const getCopyMethod = lazy(async () => {
console.log("clipboard: using osascript")
return async (text: string) => {
const escaped = text.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
await Process.run(["osascript", "-e", `set the clipboard to "${escaped}"`], { nothrow: true })
await Process.run(["osascript", "-e", `set the clipboard to "${escaped}"`])
}
}

Expand Down Expand Up @@ -168,7 +168,7 @@ const getCopyMethod = lazy(async () => {
console.log("clipboard: no native support")
return async (text: string) => {
const clipboardy = await getClipboardy()
await clipboardy.write(text).catch(() => {})
await clipboardy.write(text)
}
})

Expand Down
54 changes: 54 additions & 0 deletions packages/opencode/test/cli/cmd/tui/clipboard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, expect, test } from "bun:test"
import { chmod, mkdtemp, rm, writeFile } from "fs/promises"
import { tmpdir } from "os"
import path from "path"
import { pathToFileURL } from "url"

describe("TUI clipboard", () => {
test("rejects when the selected native clipboard command fails", async () => {
const directory = await mkdtemp(path.join(tmpdir(), "opencode-clipboard-"))

try {
const xclip = path.join(directory, "xclip")
await writeFile(xclip, "#!/bin/sh\nexit 7\n")
await chmod(xclip, 0o755)

const clipboard = path.resolve(import.meta.dir, "../../../../src/cli/cmd/tui/util/clipboard.ts")
const script = path.join(directory, "copy-fails.mjs")
await writeFile(
script,
`
import * as Clipboard from ${JSON.stringify(pathToFileURL(clipboard).href)}

try {
await Clipboard.copy("hello")
console.error("copy resolved")
process.exit(1)
} catch {
process.exit(0)
}
`.trimStart(),
)

const proc = Bun.spawn([process.execPath, script], {
env: {
...process.env,
PATH: `${directory}${path.delimiter}${process.env.PATH ?? ""}`,
WAYLAND_DISPLAY: "",
},
stdout: "pipe",
stderr: "pipe",
})
const [stdout, stderr, code] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
])

expect(`${stdout}${stderr}`).not.toContain("copy resolved")
expect(code).toBe(0)
} finally {
await rm(directory, { recursive: true, force: true })
}
})
})
Loading