diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index c80daf9cff..eb93a75ddc 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -28,7 +28,7 @@ import { MessageID, PartID } from "@/session/schema" import { createStore, produce, unwrap } from "solid-js/store" import { usePromptHistory, type PromptInfo } from "./history" import { computePromptTraits } from "./traits" -import { assign } from "./part" +import { assign, expandPastedTextPlaceholders } from "./part" import { usePromptStash } from "./stash" import { DialogStash } from "../dialog-stash" import { type AutocompleteRef, Autocomplete } from "./autocomplete" @@ -1544,6 +1544,9 @@ export function Prompt(props: PromptProps) { }} ref={(r: TextareaRenderable) => { input = r + Object.assign(r, { + getClipboardText: (text: string) => expandPastedTextPlaceholders(text, store.prompt.parts), + }) setInputTarget(r) if (promptPartTypeId === 0) { promptPartTypeId = input.extmarks.registerType("prompt-part") diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/part.ts b/packages/opencode/src/cli/cmd/tui/component/prompt/part.ts index 8cdcef6067..c5ab85bc1e 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/part.ts +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/part.ts @@ -14,3 +14,10 @@ export function assign(part: Item): Item & { id: PartID } { id: PartID.ascending(), } } + +export function expandPastedTextPlaceholders(text: string, parts: PromptInfo["parts"]) { + return parts.reduce((result, part) => { + if (part.type !== "text" || !part.source?.text) return result + return result.replace(part.source.text.value, part.text) + }, text) +} diff --git a/packages/opencode/src/cli/cmd/tui/util/selection.ts b/packages/opencode/src/cli/cmd/tui/util/selection.ts index bb2f658cc2..ef21819d90 100644 --- a/packages/opencode/src/cli/cmd/tui/util/selection.ts +++ b/packages/opencode/src/cli/cmd/tui/util/selection.ts @@ -7,6 +7,7 @@ type Toast = { type FocusableSelectionTarget = { hasSelection: () => boolean + getClipboardText?: (text: string) => string } type Renderer = { @@ -23,10 +24,17 @@ type SelectionKeyEvent = { } export function copy(renderer: Renderer, toast: Toast): boolean { - const text = renderer.getSelection()?.getSelectedText() + const selection = renderer.getSelection() + if (!selection) return false + + const text = selection.getSelectedText() if (!text) return false - Clipboard.copy(text) + const focus = renderer.currentFocusedRenderable + const clipboardText = + focus?.getClipboardText && selection.selectedRenderables.includes(focus) ? focus.getClipboardText(text) : text + + Clipboard.copy(clipboardText) .then(() => toast.show({ message: "Copied to clipboard", variant: "info" })) .catch(toast.error)