Add terminal pane ID copy action (#3172)

Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
This commit is contained in:
Neil 2026-05-30 12:48:19 -07:00 committed by GitHub
parent c8fd13120d
commit 66efd938e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 45 additions and 1 deletions

View File

@ -53,6 +53,7 @@ type TerminalContextMenuProps = {
onAddQuickCommand: () => void
onToggleExpand: () => void
onSetTitle: () => void
onCopyPaneId: () => void
}
export default function TerminalContextMenu({
@ -77,7 +78,8 @@ export default function TerminalContextMenu({
onQuickCommand,
onAddQuickCommand,
onToggleExpand,
onSetTitle
onSetTitle,
onCopyPaneId
}: TerminalContextMenuProps): React.JSX.Element {
const copyShortcut = useShortcutLabel('terminal.copySelection')
const pasteShortcut = useShortcutLabel('terminal.paste')
@ -239,6 +241,10 @@ export default function TerminalContextMenu({
<Pencil />
Set Title
</DropdownMenuItem>
<DropdownMenuItem onSelect={onCopyPaneId}>
<Copy />
Copy Pane ID
</DropdownMenuItem>
{canClosePane && (
<>
<DropdownMenuSeparator />

View File

@ -1540,6 +1540,7 @@ export default function TerminalPane({
managerRef,
paneTransportsRef,
paneCwdRef,
tabId,
worktreeId,
groupId: quickCommandGroupId,
fallbackCwd: cwd ?? '',
@ -1759,6 +1760,7 @@ export default function TerminalPane({
}
onToggleExpand={contextMenu.onToggleExpand}
onSetTitle={contextMenu.onSetTitle}
onCopyPaneId={contextMenu.onCopyPaneId}
/>
<TerminalQuickCommandDialog
open={quickCommandEditorOpen}

View File

@ -1,4 +1,5 @@
import { useEffect, useRef, useState } from 'react'
import { toast } from 'sonner'
import type { ManagedPane, PaneManager } from '@/lib/pane-manager/pane-manager'
import type { PtyTransport } from './pty-transport'
import { getConnectionId } from '@/lib/connection-context'
@ -9,6 +10,7 @@ import { sendTerminalQuickCommandToPane } from './terminal-quick-command-dispatc
import { splitWebRuntimeTerminal } from '@/runtime/web-runtime-session'
import { pasteTerminalText } from './terminal-bracketed-paste'
import { pasteTerminalClipboard } from './terminal-clipboard-paste'
import { makePaneKey } from '../../../../shared/stable-pane-id'
import { runQuickCommandInNewTab } from '@/lib/run-quick-command-in-new-tab'
const CLOSE_ALL_CONTEXT_MENUS_EVENT = 'orca-close-all-context-menus'
@ -17,6 +19,7 @@ type UseTerminalPaneContextMenuDeps = {
managerRef: React.RefObject<PaneManager | null>
paneTransportsRef: React.RefObject<Map<number, PtyTransport>>
paneCwdRef: React.RefObject<PaneCwdMap>
tabId: string
worktreeId: string
groupId: string | null
fallbackCwd: string
@ -36,6 +39,7 @@ type TerminalMenuState = {
menuPaneId: number | null
onContextMenuCapture: (event: React.MouseEvent<HTMLDivElement>) => void
onCopy: () => Promise<void>
onCopyPaneId: () => Promise<void>
onPaste: () => Promise<void>
onSplitRight: () => void
onSplitDown: () => void
@ -51,6 +55,7 @@ export function useTerminalPaneContextMenu({
managerRef,
paneTransportsRef,
paneCwdRef,
tabId,
worktreeId,
groupId,
fallbackCwd,
@ -107,6 +112,18 @@ export function useTerminalPaneContextMenu({
pane.terminal.focus()
}
const onCopyPaneId = async (): Promise<void> => {
const pane = resolveMenuPane()
if (!pane) {
return
}
// Why: orchestration targets use ORCA_PANE_KEY, which survives renderer
// remounts; the numeric PaneManager id is only a local runtime handle.
await window.api.ui.writeClipboardText(makePaneKey(tabId, pane.leafId))
toast.success('Pane ID copied')
pane.terminal.focus()
}
const onPaste = async (): Promise<void> => {
const pane = resolveMenuPane()
if (!pane) {
@ -267,6 +284,7 @@ export function useTerminalPaneContextMenu({
menuPaneId,
onContextMenuCapture,
onCopy,
onCopyPaneId,
onPaste,
onSplitRight,
onSplitDown,

View File

@ -224,6 +224,24 @@ test.describe('Terminal Panes', () => {
expect(activeLeafId).toMatch(UUID_RE)
})
test('terminal context menu copies the stable pane ID', async ({ orcaPage }) => {
const snapshot = await waitForPaneIdentitySnapshot(orcaPage, 1)
const leafId = snapshot.panes[0]?.leafId
if (!leafId) {
throw new Error('No terminal pane leaf id found')
}
const expectedPaneKey = `${snapshot.tabId}:${leafId}`
await openTerminalContextMenu(orcaPage)
await orcaPage.getByText('Copy Pane ID', { exact: true }).click()
await expect
.poll(() => orcaPage.evaluate(() => window.api.ui.readClipboardText()), { timeout: 3_000 })
.toBe(expectedPaneKey)
await expect(orcaPage.getByText('Pane ID copied', { exact: true })).toBeVisible()
expect(leafId).toMatch(UUID_RE)
})
test('first Set Title from terminal context menu stays open for typing', async ({ orcaPage }) => {
const title = `First menu title ${Date.now()}`