Add terminal pane ID copy action (#3172)
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
This commit is contained in:
parent
c8fd13120d
commit
66efd938e6
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()}`
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue