Skip to content

Commit 4e146cc

Browse files
committed
fix(tui): handle empty paste fallback
1 parent 9384197 commit 4e146cc

File tree

3 files changed

+47
-3
lines changed

3 files changed

+47
-3
lines changed

packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { DialogAlert } from "../../ui/dialog-alert"
3030
import { useToast } from "../../ui/toast"
3131
import { useKV } from "../../context/kv"
3232
import { useTextareaKeybindings } from "../textarea-keybindings"
33+
import { resolvePastedContent } from "./paste"
3334

3435
export type PromptProps = {
3536
sessionID?: string
@@ -861,13 +862,15 @@ export function Prompt(props: PromptProps) {
861862
// Normalize line endings at the boundary
862863
// Windows ConPTY/Terminal often sends CR-only newlines in bracketed paste
863864
// Replace CRLF first, then any remaining CR
864-
const normalizedText = event.text.replace(/\r\n/g, "\n").replace(/\r/g, "\n")
865-
const pastedContent = normalizedText.trim()
866-
if (!pastedContent) {
865+
const resolvedContent = await resolvePastedContent(event.text, Clipboard.read)
866+
867+
if (!resolvedContent) {
867868
command.trigger("prompt.paste")
868869
return
869870
}
870871

872+
const pastedContent = resolvedContent
873+
871874
// trim ' from the beginning and end of the pasted content. just
872875
// ' and nothing else
873876
const filepath = pastedContent.replace(/^'+|'+$/g, "").replace(/\\ /g, " ")
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Clipboard } from "../../util/clipboard"
2+
3+
export async function resolvePastedContent(
4+
eventText: string,
5+
readClipboard: () => Promise<Clipboard.Content | undefined>,
6+
): Promise<string | undefined> {
7+
// Normalize and use paste payload when terminals supply text directly
8+
const normalized = eventText.replace(/\r\n/g, "\n").replace(/\r/g, "\n")
9+
const text = normalized.trim()
10+
if (text) return text
11+
12+
// Finder copy often yields empty paste events; fallback to clipboard text
13+
const clipboardContent = await readClipboard()
14+
const clipboardText = clipboardContent?.mime.startsWith("text/") ? clipboardContent.data : undefined
15+
if (!clipboardText) return
16+
return clipboardText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim()
17+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { describe, expect, mock, test } from "bun:test"
2+
import { resolvePastedContent } from "../../../src/cli/cmd/tui/component/prompt/paste"
3+
4+
describe("resolvePastedContent", () => {
5+
test("returns normalized event text when present", async () => {
6+
const readClipboard = mock(async () => undefined)
7+
const result = await resolvePastedContent(" Hello\r\nWorld ", readClipboard)
8+
expect(result).toBe("Hello\nWorld")
9+
expect(readClipboard.mock.calls.length).toBe(0)
10+
})
11+
12+
test("falls back to clipboard text when event text is empty", async () => {
13+
const readClipboard = mock(async () => ({ data: " clipboard text ", mime: "text/plain" }))
14+
const result = await resolvePastedContent("", readClipboard)
15+
expect(result).toBe("clipboard text")
16+
expect(readClipboard.mock.calls.length).toBe(1)
17+
})
18+
19+
test("returns undefined when clipboard lacks text content", async () => {
20+
const readClipboard = mock(async () => ({ data: "ignored", mime: "image/png" }))
21+
const result = await resolvePastedContent("", readClipboard)
22+
expect(result).toBeUndefined()
23+
})
24+
})

0 commit comments

Comments
 (0)