mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-24 06:33:01 -04:00
fix(tui): coalesce rapid selection copies
This commit is contained in:
@@ -571,15 +571,13 @@ function App(props: { pair?: DialogPairCredentials }) {
|
||||
offSelectionKeys()
|
||||
})
|
||||
|
||||
// Wire up console copy-to-clipboard via opentui's onCopySelection callback
|
||||
renderer.console.onCopySelection = async (text: string) => {
|
||||
// Windows clipboard mutations are serialized by OpenTUI. Coalesce rapid selections here so
|
||||
// they cannot fill its native operation queue and starve renderer polling.
|
||||
const copySelection = Selection.createSelectionCopy(clipboard, toast)
|
||||
renderer.console.onCopySelection = (text: string) => {
|
||||
if (!text || text.length === 0) return
|
||||
|
||||
await clipboard
|
||||
.write(text)
|
||||
.then(() => toast.show({ message: "Copied to clipboard", variant: "info" }))
|
||||
.catch(toast.error)
|
||||
|
||||
copySelection(text)
|
||||
renderer.clearSelection()
|
||||
}
|
||||
const terminalTitleEnabled = () => config.data.terminal?.title ?? true
|
||||
|
||||
@@ -23,6 +23,33 @@ type SelectionKeyEvent = {
|
||||
stopPropagation: () => void
|
||||
}
|
||||
|
||||
export function createSelectionCopy(clipboard: ClipboardService, toast: Toast) {
|
||||
let pending: string | undefined
|
||||
let running = false
|
||||
|
||||
const drain = async () => {
|
||||
while (pending !== undefined) {
|
||||
const text = pending
|
||||
pending = undefined
|
||||
const error = await clipboard.write(text).then(
|
||||
() => undefined,
|
||||
(error) => error,
|
||||
)
|
||||
if (pending !== undefined) continue
|
||||
if (error !== undefined) toast.error(error)
|
||||
else toast.show({ message: "Copied to clipboard", variant: "info" })
|
||||
}
|
||||
running = false
|
||||
}
|
||||
|
||||
return (text: string) => {
|
||||
pending = text
|
||||
if (running) return
|
||||
running = true
|
||||
void drain()
|
||||
}
|
||||
}
|
||||
|
||||
export function copy(renderer: Renderer, toast: Toast, clipboard: ClipboardService): boolean {
|
||||
const selection = renderer.getSelection()
|
||||
if (!selection) return false
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { createSelectionCopy } from "../src/util/selection"
|
||||
|
||||
test("keeps one clipboard write active and coalesces rapid selections", async () => {
|
||||
const writes: string[] = []
|
||||
const releases: (() => void)[] = []
|
||||
let active = 0
|
||||
let maximumActive = 0
|
||||
const clipboard = {
|
||||
async read() {
|
||||
return undefined
|
||||
},
|
||||
async write(text: string) {
|
||||
writes.push(text)
|
||||
active++
|
||||
maximumActive = Math.max(maximumActive, active)
|
||||
await new Promise<void>((resolve) => releases.push(resolve))
|
||||
active--
|
||||
},
|
||||
}
|
||||
const copied: string[] = []
|
||||
const failed: unknown[] = []
|
||||
const copy = createSelectionCopy(clipboard, {
|
||||
show: (input) => copied.push(input.message),
|
||||
error: (error) => failed.push(error),
|
||||
})
|
||||
|
||||
copy("first")
|
||||
for (let index = 0; index < 100; index++) copy(`selection-${index}`)
|
||||
|
||||
expect(writes).toEqual(["first"])
|
||||
expect(maximumActive).toBe(1)
|
||||
|
||||
releases.shift()?.()
|
||||
await Bun.sleep(0)
|
||||
|
||||
expect(writes).toEqual(["first", "selection-99"])
|
||||
expect(maximumActive).toBe(1)
|
||||
|
||||
releases.shift()?.()
|
||||
await Bun.sleep(0)
|
||||
|
||||
expect(copied).toEqual(["Copied to clipboard"])
|
||||
expect(failed).toEqual([])
|
||||
})
|
||||
Reference in New Issue
Block a user