Просмотр исходного кода

fix(desktop): copy/paste in terminal

Adam 1 месяц назад
Родитель
Сommit
2e972b3fdc
2 измененных файлов с 43 добавлено и 3 удалено
  1. 1 0
      packages/app/src/components/prompt-input.tsx
  2. 42 3
      packages/app/src/components/terminal.tsx

+ 1 - 0
packages/app/src/components/prompt-input.tsx

@@ -248,6 +248,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
   }
   }
 
 
   const handlePaste = async (event: ClipboardEvent) => {
   const handlePaste = async (event: ClipboardEvent) => {
+    if (!isFocused()) return
     const clipboardData = event.clipboardData
     const clipboardData = event.clipboardData
     if (!clipboardData) return
     if (!clipboardData) return
 
 

+ 42 - 3
packages/app/src/components/terminal.tsx

@@ -23,6 +23,35 @@ export const Terminal = (props: TerminalProps) => {
   let fitAddon: FitAddon
   let fitAddon: FitAddon
   let handleResize: () => void
   let handleResize: () => void
   const prefersDark = usePrefersDark()
   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 () => {
   onMount(async () => {
     ghostty = await Ghostty.load()
     ghostty = await Ghostty.load()
@@ -48,8 +77,17 @@ export const Terminal = (props: TerminalProps) => {
       ghostty,
       ghostty,
     })
     })
     term.attachCustomKeyEventHandler((event) => {
     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
       // allow for ctrl-` to toggle terminal in parent
-      if (event.ctrlKey && event.key.toLowerCase() === "`") {
+      if (event.ctrlKey && key === "`") {
         event.preventDefault()
         event.preventDefault()
         return true
         return true
       }
       }
@@ -62,6 +100,8 @@ export const Terminal = (props: TerminalProps) => {
     term.loadAddon(fitAddon)
     term.loadAddon(fitAddon)
 
 
     term.open(container)
     term.open(container)
+    container.addEventListener("pointerdown", handlePointerDown)
+    focusTerminal()
 
 
     if (local.pty.buffer) {
     if (local.pty.buffer) {
       if (local.pty.rows && local.pty.cols) {
       if (local.pty.rows && local.pty.cols) {
@@ -75,8 +115,6 @@ export const Terminal = (props: TerminalProps) => {
       fitAddon.fit()
       fitAddon.fit()
     }
     }
 
 
-    container.focus()
-
     fitAddon.observeResize()
     fitAddon.observeResize()
     handleResize = () => fitAddon.fit()
     handleResize = () => fitAddon.fit()
     window.addEventListener("resize", handleResize)
     window.addEventListener("resize", handleResize)
@@ -134,6 +172,7 @@ export const Terminal = (props: TerminalProps) => {
     if (handleResize) {
     if (handleResize) {
       window.removeEventListener("resize", handleResize)
       window.removeEventListener("resize", handleResize)
     }
     }
+    container.removeEventListener("pointerdown", handlePointerDown)
     if (serializeAddon && props.onCleanup) {
     if (serializeAddon && props.onCleanup) {
       const buffer = serializeAddon.serialize()
       const buffer = serializeAddon.serialize()
       props.onCleanup({
       props.onCleanup({