summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/components
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-27 20:18:59 -0600
committerAdam <[email protected]>2025-12-27 20:18:59 -0600
commit2e972b3fdc4b7f3ccb2224f693391a8f11572c3c (patch)
treef732164d047e402007a822afe4770c9a175fc6bd /packages/app/src/components
parentd70e9fb01e6c7f4d3fd4c9ce6d13251108d6a7f4 (diff)
downloadopencode-2e972b3fdc4b7f3ccb2224f693391a8f11572c3c.tar.gz
opencode-2e972b3fdc4b7f3ccb2224f693391a8f11572c3c.zip
fix(desktop): copy/paste in terminal
Diffstat (limited to 'packages/app/src/components')
-rw-r--r--packages/app/src/components/prompt-input.tsx1
-rw-r--r--packages/app/src/components/terminal.tsx45
2 files changed, 43 insertions, 3 deletions
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index 612001501..3c3225137 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -248,6 +248,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}
const handlePaste = async (event: ClipboardEvent) => {
+ if (!isFocused()) return
const clipboardData = event.clipboardData
if (!clipboardData) return
diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx
index abf03fc06..1d6bab2a4 100644
--- a/packages/app/src/components/terminal.tsx
+++ b/packages/app/src/components/terminal.tsx
@@ -23,6 +23,35 @@ export const Terminal = (props: TerminalProps) => {
let fitAddon: FitAddon
let handleResize: () => void
const prefersDark = usePrefersDark()
+ const focusTerminal = () => term?.focus()
+ const copySelection = () => {
+ if (!term || !term.hasSelection()) return false
+ const selection = term.getSelection()
+ if (!selection) return false
+ const clipboard = navigator.clipboard
+ if (clipboard?.writeText) {
+ clipboard.writeText(selection).catch(() => {})
+ return true
+ }
+ if (!document.body) return false
+ const textarea = document.createElement("textarea")
+ textarea.value = selection
+ textarea.setAttribute("readonly", "")
+ textarea.style.position = "fixed"
+ textarea.style.opacity = "0"
+ document.body.appendChild(textarea)
+ textarea.select()
+ const copied = document.execCommand("copy")
+ document.body.removeChild(textarea)
+ return copied
+ }
+ const handlePointerDown = () => {
+ const activeElement = document.activeElement
+ if (activeElement instanceof HTMLElement && activeElement !== container) {
+ activeElement.blur()
+ }
+ focusTerminal()
+ }
onMount(async () => {
ghostty = await Ghostty.load()
@@ -48,8 +77,17 @@ export const Terminal = (props: TerminalProps) => {
ghostty,
})
term.attachCustomKeyEventHandler((event) => {
+ const key = event.key.toLowerCase()
+ if (key === "c") {
+ const macCopy = event.metaKey && !event.ctrlKey && !event.altKey
+ const linuxCopy = event.ctrlKey && event.shiftKey && !event.metaKey
+ if ((macCopy || linuxCopy) && copySelection()) {
+ event.preventDefault()
+ return true
+ }
+ }
// allow for ctrl-` to toggle terminal in parent
- if (event.ctrlKey && event.key.toLowerCase() === "`") {
+ if (event.ctrlKey && key === "`") {
event.preventDefault()
return true
}
@@ -62,6 +100,8 @@ export const Terminal = (props: TerminalProps) => {
term.loadAddon(fitAddon)
term.open(container)
+ container.addEventListener("pointerdown", handlePointerDown)
+ focusTerminal()
if (local.pty.buffer) {
if (local.pty.rows && local.pty.cols) {
@@ -75,8 +115,6 @@ export const Terminal = (props: TerminalProps) => {
fitAddon.fit()
}
- container.focus()
-
fitAddon.observeResize()
handleResize = () => fitAddon.fit()
window.addEventListener("resize", handleResize)
@@ -134,6 +172,7 @@ export const Terminal = (props: TerminalProps) => {
if (handleResize) {
window.removeEventListener("resize", handleResize)
}
+ container.removeEventListener("pointerdown", handlePointerDown)
if (serializeAddon && props.onCleanup) {
const buffer = serializeAddon.serialize()
props.onCleanup({