diff --git a/src/renderer/src/components/terminal-pane/TerminalContextMenu.test.tsx b/src/renderer/src/components/terminal-pane/TerminalContextMenu.test.tsx index 934befa4a9..2977f398d8 100644 --- a/src/renderer/src/components/terminal-pane/TerminalContextMenu.test.tsx +++ b/src/renderer/src/components/terminal-pane/TerminalContextMenu.test.tsx @@ -70,6 +70,8 @@ function renderMenu(overrides: Record = {}): void { onAddQuickCommand: vi.fn(), onToggleExpand: vi.fn(), onSetTitle: vi.fn(), + onClearPaneTitle: vi.fn(), + canClearPaneTitle: false, onCopyTerminalId: vi.fn(), onCopyPaneId: vi.fn(), ...overrides diff --git a/src/renderer/src/components/terminal-pane/TerminalContextMenu.tsx b/src/renderer/src/components/terminal-pane/TerminalContextMenu.tsx index ddb953317f..ef56ba4e1a 100644 --- a/src/renderer/src/components/terminal-pane/TerminalContextMenu.tsx +++ b/src/renderer/src/components/terminal-pane/TerminalContextMenu.tsx @@ -67,6 +67,8 @@ type TerminalContextMenuProps = { onAddQuickCommand: () => void onToggleExpand: () => void onSetTitle: () => void + onClearPaneTitle: () => void + canClearPaneTitle: boolean onCopyTerminalId: () => void onCopyPaneId: () => void } @@ -100,6 +102,8 @@ export default function TerminalContextMenu({ onAddQuickCommand, onToggleExpand, onSetTitle, + onClearPaneTitle, + canClearPaneTitle, onCopyTerminalId, onCopyPaneId }: TerminalContextMenuProps): React.JSX.Element { @@ -111,6 +115,8 @@ export default function TerminalContextMenu({ splitDown: formatShortcutLabel('terminal.splitDown', keybindings), equalize: formatShortcutLabel('terminal.equalizePaneSizes', keybindings), expand: formatShortcutLabel('terminal.expandPane', keybindings), + setTitle: formatShortcutLabel('terminal.setTitle', keybindings), + clearPaneTitle: formatShortcutLabel('terminal.clearPaneTitle', keybindings), close: formatShortcutLabel('terminal.closePane', keybindings), nativeChat: nativeChatToggleShortcutLabel(isMacPlatform()) }), @@ -118,6 +124,8 @@ export default function TerminalContextMenu({ ) const hasQuickCommands = repoQuickCommands.length > 0 || globalQuickCommands.length > 0 const showEqualizeShortcut = shortcuts.equalize !== 'Unassigned' + const showSetTitleShortcut = shortcuts.setTitle !== 'Unassigned' + const showClearPaneTitleShortcut = shortcuts.clearPaneTitle !== 'Unassigned' const renderQuickCommandItem = (command: TerminalQuickCommand): React.JSX.Element => ( onQuickCommand(command)}> {isTerminalAgentQuickCommand(command) ? ( @@ -337,7 +345,22 @@ export default function TerminalContextMenu({ > {translate('auto.components.terminal.pane.TerminalContextMenu.39809d152f', 'Set Title…')} + {showSetTitleShortcut ? ( + {shortcuts.setTitle} + ) : null} + {canClearPaneTitle ? ( + + + {translate( + 'auto.components.terminal.pane.TerminalContextMenu.clearPaneTitle', + 'Clear Pane Title' + )} + {showClearPaneTitleShortcut ? ( + {shortcuts.clearPaneTitle} + ) : null} + + ) : null} {translate( diff --git a/src/renderer/src/components/terminal-pane/TerminalPane.tsx b/src/renderer/src/components/terminal-pane/TerminalPane.tsx index 691d932c18..cffd405136 100644 --- a/src/renderer/src/components/terminal-pane/TerminalPane.tsx +++ b/src/renderer/src/components/terminal-pane/TerminalPane.tsx @@ -458,6 +458,65 @@ export default function TerminalPane({ const renameFocusFrameRef = useRef(null) const renameEnableBlurFrameRef = useRef(null) const renameRefocusFrameRef = useRef(null) + /** + * Cancels deferred focus/blur work from inline title editing. + * Rename sessions schedule multiple frames because xterm and Radix can both + * move focus after the menu closes. + */ + const cancelPendingRenameFrames = useCallback(() => { + const frameRefs = [renameFocusFrameRef, renameEnableBlurFrameRef, renameRefocusFrameRef] + for (const frameRef of frameRefs) { + if (frameRef.current !== null) { + cancelAnimationFrame(frameRef.current) + frameRef.current = null + } + } + }, []) + + /** + * Invalidates the active inline title edit session before unmount or cancel. + * Session IDs keep stale animation-frame callbacks from committing old titles. + */ + const closeRenameSession = useCallback(() => { + renameSessionIdRef.current += 1 + renameBlurCommitEnabledRef.current = true + renameUserRequestedBlurCommitRef.current = false + cancelPendingRenameFrames() + }, [cancelPendingRenameFrames]) + + /** + * Owns the terminal container ref and closes rename work when that owner + * detaches, preventing delayed focus callbacks from targeting stale DOM. + */ + const setContainerRef = useCallback( + (node: HTMLDivElement | null): void => { + containerRef.current = node + if (node !== null) { + return + } + // Why: inline title rename focus/blur frames are owned by the terminal + // container; invalidate them when that DOM owner detaches. + closeRenameSession() + }, + [closeRenameSession] + ) + + /** + * Starts inline title editing from either the context menu or keyboard + * shortcut while resetting stale blur-submit state from prior sessions. + */ + const handleStartRename = useCallback( + (paneId: number) => { + cancelPendingRenameFrames() + renameSessionIdRef.current += 1 + renameBlurCommitEnabledRef.current = false + renameUserRequestedBlurCommitRef.current = false + renameSubmittedRef.current = false + setRenameValue(paneTitlesRef.current[paneId] ?? '') + setRenamingPaneId(paneId) + }, + [cancelPendingRenameFrames] + ) const onPtyErrorRef = useRef((_paneId: number, message: string) => { if (isTerminalSessionStateSaveFailure(message)) { setTerminalError(null) @@ -888,6 +947,49 @@ export default function TerminalPane({ [paneTransportsRef, persistLayoutSnapshot] ) + /** + * Removes a custom pane title from React state, the fresh persistence ref, + * and the leaf-id tombstone set so the next layout snapshot stays cleared. + */ + const removePaneTitle = useCallback( + (paneId: number) => { + setPaneTitles((prev) => { + if (!(paneId in prev)) { + return prev + } + const next = { ...prev } + delete next[paneId] + return next + }) + // Eagerly remove from the ref so persistLayoutSnapshot sees the change. + if (paneId in paneTitlesRef.current) { + const next = { ...paneTitlesRef.current } + delete next[paneId] + paneTitlesRef.current = next + } + const leafId = managerRef.current?.getPanes().find((pane) => pane.id === paneId)?.leafId + if (leafId) { + removedTitleLeafIdsRef.current.add(leafId) + } + persistLayoutSnapshot() + }, + [persistLayoutSnapshot] + ) + + /** + * Ignores clear-title shortcuts for panes already using their automatic + * title, keeping the command idempotent and avoiding unnecessary snapshots. + */ + const handleClearPaneTitleShortcut = useCallback( + (paneId: number) => { + if (!paneTitlesRef.current[paneId]) { + return + } + removePaneTitle(paneId) + }, + [removePaneTitle] + ) + useEffect(() => { if (!terminalTab) { return @@ -1479,6 +1581,8 @@ export default function TerminalPane({ onSearchSelectedText: handleSearchSelectedText, onRequestClosePane: handleRequestClosePane, onClearPaneScrollback: clearPaneScrollback, + onSetTitle: handleStartRename, + onClearPaneTitle: handleClearPaneTitleShortcut, searchOpenRef, searchStateRef, macOptionAsAltRef, @@ -2143,49 +2247,6 @@ export default function TerminalPane({ } }, [tabId, worktreeId, setTabLayout]) - const cancelPendingRenameFrames = useCallback(() => { - const frameRefs = [renameFocusFrameRef, renameEnableBlurFrameRef, renameRefocusFrameRef] - for (const frameRef of frameRefs) { - if (frameRef.current !== null) { - cancelAnimationFrame(frameRef.current) - frameRef.current = null - } - } - }, []) - - const closeRenameSession = useCallback(() => { - renameSessionIdRef.current += 1 - renameBlurCommitEnabledRef.current = true - renameUserRequestedBlurCommitRef.current = false - cancelPendingRenameFrames() - }, [cancelPendingRenameFrames]) - - const setContainerRef = useCallback( - (node: HTMLDivElement | null): void => { - containerRef.current = node - if (node !== null) { - return - } - // Why: inline title rename focus/blur frames are owned by the terminal - // container; invalidate them when that DOM owner detaches. - closeRenameSession() - }, - [closeRenameSession] - ) - - const handleStartRename = useCallback( - (paneId: number) => { - cancelPendingRenameFrames() - renameSessionIdRef.current += 1 - renameBlurCommitEnabledRef.current = false - renameUserRequestedBlurCommitRef.current = false - renameSubmittedRef.current = false - setRenameValue(paneTitlesRef.current[paneId] ?? '') - setRenamingPaneId(paneId) - }, - [cancelPendingRenameFrames] - ) - useEffect(() => { if (renamingPaneId === null) { return @@ -2212,31 +2273,6 @@ export default function TerminalPane({ } }, [renamingPaneId]) - const removePaneTitle = useCallback( - (paneId: number) => { - setPaneTitles((prev) => { - if (!(paneId in prev)) { - return prev - } - const next = { ...prev } - delete next[paneId] - return next - }) - // Eagerly remove from the ref so persistLayoutSnapshot sees the change. - if (paneId in paneTitlesRef.current) { - const next = { ...paneTitlesRef.current } - delete next[paneId] - paneTitlesRef.current = next - } - const leafId = managerRef.current?.getPanes().find((pane) => pane.id === paneId)?.leafId - if (leafId) { - removedTitleLeafIdsRef.current.add(leafId) - } - persistLayoutSnapshot() - }, - [persistLayoutSnapshot] - ) - const handleRenameSubmit = useCallback(() => { if (renamingPaneId === null || renameSubmittedRef.current) { return @@ -2360,6 +2396,7 @@ export default function TerminalPane({ onRequestClosePane: handleRequestClosePane, onClearPaneScrollback: clearPaneScrollback, onSetTitle: handleStartRename, + onClearPaneTitle: handleClearPaneTitleShortcut, onPasteError: setTerminalError, onAgentSessionForkReady: setAgentSessionFork, forceBracketedMultilineTextPaste, @@ -2638,6 +2675,8 @@ export default function TerminalPane({ const activePane = managerRef.current?.getActivePane() const managedPanes = managerRef.current?.getPanes() ?? [] + const menuPaneHasCustomTitle = + contextMenu.menuPaneId !== null && Boolean(paneTitles[contextMenu.menuPaneId]) const chatLeafStillMounted = chatLeafId ? managedPanes.some((pane) => pane.leafId === chatLeafId) : false @@ -2814,6 +2853,8 @@ export default function TerminalPane({ } onToggleExpand={contextMenu.onToggleExpand} onSetTitle={contextMenu.onSetTitle} + onClearPaneTitle={contextMenu.onClearPaneTitle} + canClearPaneTitle={menuPaneHasCustomTitle} onCopyTerminalId={() => void contextMenu.onCopyTerminalId()} onCopyPaneId={contextMenu.onCopyPaneId} /> diff --git a/src/renderer/src/components/terminal-pane/keyboard-handlers.ts b/src/renderer/src/components/terminal-pane/keyboard-handlers.ts index ebedf8bed8..f70a8a77a3 100644 --- a/src/renderer/src/components/terminal-pane/keyboard-handlers.ts +++ b/src/renderer/src/components/terminal-pane/keyboard-handlers.ts @@ -132,6 +132,8 @@ type KeyboardHandlersDeps = { onSearchSelectedText: (text: string) => void onRequestClosePane: (paneId: number) => void onClearPaneScrollback: (pane: ManagedPane) => void + onSetTitle: (paneId: number) => void + onClearPaneTitle: (paneId: number) => void searchOpenRef: React.RefObject searchStateRef: React.RefObject macOptionAsAltRef: React.RefObject @@ -139,6 +141,11 @@ type KeyboardHandlersDeps = { terminalShortcutPolicy?: TerminalShortcutPolicy } +/** + * Installs terminal-pane shortcuts on the tab keyboard scope. + * Uses the shared shortcut policy before forwarding unmatched input to xterm + * so configurable Orca actions remain consistent across local and SSH panes. + */ export function useTerminalKeyboardShortcuts({ tabId, isActive, @@ -157,6 +164,8 @@ export function useTerminalKeyboardShortcuts({ onSearchSelectedText, onRequestClosePane, onClearPaneScrollback, + onSetTitle, + onClearPaneTitle, searchOpenRef, searchStateRef, macOptionAsAltRef, @@ -389,6 +398,28 @@ export function useTerminalKeyboardShortcuts({ return } + if (action.type === 'setTitle') { + e.preventDefault() + e.stopImmediatePropagation() + const pane = manager.getActivePane() ?? manager.getPanes()[0] + if (!pane) { + return + } + onSetTitle(pane.id) + return + } + + if (action.type === 'clearPaneTitle') { + e.preventDefault() + e.stopImmediatePropagation() + const pane = manager.getActivePane() ?? manager.getPanes()[0] + if (!pane) { + return + } + onClearPaneTitle(pane.id) + return + } + // Cmd+W closes the active split pane (or the whole tab when only one // pane remains). Always intercepted here so the tab-level handler in // Terminal.tsx never closes the entire tab directly — that would kill @@ -458,6 +489,8 @@ export function useTerminalKeyboardShortcuts({ onSearchSelectedText, onRequestClosePane, onClearPaneScrollback, + onSetTitle, + onClearPaneTitle, searchOpenRef, searchStateRef, macOptionAsAltRef, diff --git a/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.test.ts b/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.test.ts index cb6f676eae..fd5c81c314 100644 --- a/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.test.ts +++ b/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.test.ts @@ -262,6 +262,42 @@ describe('resolveTerminalShortcutAction', () => { ).toEqual({ type: 'equalizePaneSizes' }) }) + it('resolves terminal title actions only when users assign them', () => { + expect( + resolveTerminalShortcutAction(event({ key: 't', code: 'KeyT', metaKey: true }), true) + ).toBeNull() + expect( + resolveTerminalShortcutAction( + event({ key: 't', code: 'KeyT', metaKey: true }), + true, + 'false', + 0, + false, + { 'terminal.setTitle': ['Mod+T'] } + ) + ).toEqual({ type: 'setTitle' }) + expect( + resolveTerminalShortcutAction( + event({ key: 't', code: 'KeyT', metaKey: true, altKey: true }), + true, + 'false', + 0, + false, + { 'terminal.clearPaneTitle': ['Mod+Alt+T'] } + ) + ).toEqual({ type: 'clearPaneTitle' }) + expect( + resolveTerminalShortcutAction( + event({ key: 't', code: 'KeyT', metaKey: true, altKey: true, repeat: true }), + true, + 'false', + 0, + false, + { 'terminal.clearPaneTitle': ['Mod+Alt+T'] } + ) + ).toBeNull() + }) + it('lets Ctrl+D pass through as EOF on non-Mac, requires Shift for split (#586)', () => { // Ctrl+D without Shift on Windows/Linux must NOT trigger split — it's EOF expect( diff --git a/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.ts b/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.ts index e0ac8a9cd4..d90b6e5ea6 100644 --- a/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.ts +++ b/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.ts @@ -35,11 +35,18 @@ export type TerminalShortcutAction = | { type: 'focusPane'; direction: 'next' | 'previous' } | { type: 'equalizePaneSizes' } | { type: 'toggleExpandActivePane' } + | { type: 'setTitle' } + | { type: 'clearPaneTitle' } | { type: 'closeActivePane' } | { type: 'splitActivePane'; direction: 'vertical' | 'horizontal' } | { type: 'scrollViewport'; position: 'top' | 'bottom' } | { type: 'sendInput'; data: string } +/** + * Resolves terminal keyboard events before xterm receives them. + * Keeps configurable Orca shortcuts and terminal byte fallbacks in one + * platform-aware policy so renderer handlers do not duplicate key checks. + */ export function resolveTerminalShortcutAction( event: TerminalShortcutEvent, isMac: boolean, @@ -78,6 +85,14 @@ export function resolveTerminalShortcutAction( return { type: 'toggleExpandActivePane' } } + if (keybindingMatchesAction('terminal.setTitle', event, platform, keybindings)) { + return { type: 'setTitle' } + } + + if (keybindingMatchesAction('terminal.clearPaneTitle', event, platform, keybindings)) { + return { type: 'clearPaneTitle' } + } + if (keybindingMatchesAction('terminal.closePane', event, platform, keybindings)) { return { type: 'closeActivePane' } } diff --git a/src/renderer/src/components/terminal-pane/use-terminal-pane-context-menu.ts b/src/renderer/src/components/terminal-pane/use-terminal-pane-context-menu.ts index 08451cabd6..942cc73436 100644 --- a/src/renderer/src/components/terminal-pane/use-terminal-pane-context-menu.ts +++ b/src/renderer/src/components/terminal-pane/use-terminal-pane-context-menu.ts @@ -67,6 +67,7 @@ type UseTerminalPaneContextMenuDeps = { onRequestClosePane: (paneId: number) => void onClearPaneScrollback: (pane: ManagedPane) => void onSetTitle: (paneId: number) => void + onClearPaneTitle: (paneId: number) => void onPasteError: (message: string) => void onAgentSessionForkReady: (fork: PreparedAgentSessionFork) => void forceBracketedMultilineTextPaste: boolean @@ -96,6 +97,7 @@ type TerminalMenuState = { onQuickCommand: (command: TerminalQuickCommand) => void onToggleExpand: () => void onSetTitle: () => void + onClearPaneTitle: () => void runForPane: (paneId: number, action: () => Result) => Result } @@ -112,6 +114,7 @@ export function useTerminalPaneContextMenu({ onRequestClosePane, onClearPaneScrollback, onSetTitle, + onClearPaneTitle, onPasteError, onAgentSessionForkReady, forceBracketedMultilineTextPaste, @@ -433,6 +436,7 @@ export function useTerminalPaneContextMenu({ } } + /** Routes title edits through the resolved menu pane instead of active pane. */ const handleSetTitle = (): void => { const pane = resolveMenuPane() if (pane) { @@ -440,6 +444,14 @@ export function useTerminalPaneContextMenu({ } } + /** Clears the title for the pane that opened the context menu. */ + const handleClearPaneTitle = (): void => { + const pane = resolveMenuPane() + if (pane) { + onClearPaneTitle(pane.id) + } + } + const runForPane = (paneId: number, action: () => Result): Result => { const previousPaneId = contextPaneIdRef.current contextPaneIdRef.current = paneId @@ -548,6 +560,7 @@ export function useTerminalPaneContextMenu({ onQuickCommand, onToggleExpand, onSetTitle: handleSetTitle, + onClearPaneTitle: handleClearPaneTitle, runForPane } } diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 70e1c83aae..259c557826 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -2403,6 +2403,7 @@ "copyTerminalId": "Copy Terminal ID", "2cf85a6a55": "Copy Pane ID", "39809d152f": "Set Title…", + "clearPaneTitle": "Clear Pane Title", "06c2b0f043": "Equalize Pane Sizes", "98bccf4fa2": "Split Terminal Down", "20e565d865": "Split Terminal Right", diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index b2d2f0a8bc..564337a274 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -2403,6 +2403,7 @@ "copyTerminalId": "Copiar ID de terminal", "2cf85a6a55": "Copiar ID del panel", "39809d152f": "Establecer título…", + "clearPaneTitle": "Borrar título del panel", "06c2b0f043": "Igualar tamaños de paneles", "98bccf4fa2": "Terminal dividido hacia abajo", "20e565d865": "Terminal dividido a la derecha", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index cf1892dc15..da7a5a2423 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -2403,6 +2403,7 @@ "copyTerminalId": "ターミナル ID をコピー", "2cf85a6a55": "ペインIDのコピー", "39809d152f": "タイトルを設定…", + "clearPaneTitle": "ペインタイトルをクリア", "06c2b0f043": "ペインのサイズを均等化する", "98bccf4fa2": "スプリット Terminal ダウン", "20e565d865": "分割 Terminal 右", diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index dbe0a22257..905b5752ea 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -2403,6 +2403,7 @@ "copyTerminalId": "터미널 ID 복사", "2cf85a6a55": "창 ID 복사", "39809d152f": "제목 설정…", + "clearPaneTitle": "창 제목 지우기", "06c2b0f043": "창 크기 균등화", "98bccf4fa2": "Terminal을 아래로 분할", "20e565d865": "Terminal 오른쪽 분할", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 4b04666e2a..e69ea57567 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -2403,6 +2403,7 @@ "copyTerminalId": "复制终端 ID", "2cf85a6a55": "复制窗格 ID", "39809d152f": "设置标题...", + "clearPaneTitle": "清除窗格标题", "06c2b0f043": "均衡窗格大小", "98bccf4fa2": "向下拆分终端", "20e565d865": "向右拆分终端", diff --git a/src/shared/keybindings.test.ts b/src/shared/keybindings.test.ts index 3260366391..85d972c3a5 100644 --- a/src/shared/keybindings.test.ts +++ b/src/shared/keybindings.test.ts @@ -231,6 +231,7 @@ describe('keybindings', () => { expect(getEffectiveKeybindingsForAction('workspace.rename', 'darwin')).toEqual(['Mod+Alt+R']) expect(getEffectiveKeybindingsForAction('workspace.rename', 'linux')).toEqual([]) expect(formatKeybindingList(['Mod+Alt+R'], 'darwin')).toBe('⌘⌥R') + expect(getKeybindingDefinition('tab.rename')?.searchKeywords).not.toContain('set title') expect( keybindingMatchesAction( 'tab.rename', @@ -420,6 +421,35 @@ describe('keybindings', () => { ).toBe(true) }) + it('names terminal title shortcuts after pane menu actions', () => { + const setTitle = getKeybindingDefinition('terminal.setTitle') + const clearTitle = getKeybindingDefinition('terminal.clearPaneTitle') + + expect(setTitle?.title).toBe('Set Title…') + expect(setTitle?.group).toBe('Terminal Panes') + expect(setTitle?.scope).toBe('terminal') + expect(setTitle?.searchKeywords).toContain('set title') + expect(getEffectiveKeybindingsForAction('terminal.setTitle', 'darwin')).toEqual([]) + expect(getEffectiveKeybindingsForAction('terminal.setTitle', 'linux')).toEqual([]) + expect(getEffectiveKeybindingsForAction('terminal.setTitle', 'win32')).toEqual([]) + + expect(clearTitle?.title).toBe('Clear Pane Title') + expect(clearTitle?.group).toBe('Terminal Panes') + expect(clearTitle?.scope).toBe('terminal') + expect(clearTitle?.searchKeywords).toContain('remove title') + expect(getEffectiveKeybindingsForAction('terminal.clearPaneTitle', 'darwin')).toEqual([]) + expect(getEffectiveKeybindingsForAction('terminal.clearPaneTitle', 'linux')).toEqual([]) + expect(getEffectiveKeybindingsForAction('terminal.clearPaneTitle', 'win32')).toEqual([]) + expect( + keybindingMatchesAction( + 'terminal.clearPaneTitle', + { key: 't', code: 'KeyT', control: false, meta: true, alt: true, shift: false }, + 'darwin', + { 'terminal.clearPaneTitle': ['Mod+Alt+T'] } + ) + ).toBe(true) + }) + it('keeps workspace delete unassigned until users customize it', () => { const binding = { key: 'Backspace', diff --git a/src/shared/keybindings.ts b/src/shared/keybindings.ts index 563f539763..7778a888e8 100644 --- a/src/shared/keybindings.ts +++ b/src/shared/keybindings.ts @@ -103,6 +103,8 @@ export type KeybindingActionId = | 'terminal.focusPreviousPane' | 'terminal.equalizePaneSizes' | 'terminal.expandPane' + | 'terminal.setTitle' + | 'terminal.clearPaneTitle' | 'terminal.closePane' | 'terminal.splitRight' | 'terminal.splitDown' @@ -941,6 +943,22 @@ export const KEYBINDING_DEFINITIONS: readonly KeybindingDefinition[] = [ searchKeywords: ['shortcut', 'pane', 'expand', 'collapse'], defaultBindings: platformBindings(['Mod+Shift+Enter']) }, + { + id: 'terminal.setTitle', + title: 'Set Title…', + group: 'Terminal Panes', + scope: 'terminal', + searchKeywords: ['shortcut', 'terminal', 'pane', 'set title', 'title', 'rename'], + defaultBindings: platformBindings([]) + }, + { + id: 'terminal.clearPaneTitle', + title: 'Clear Pane Title', + group: 'Terminal Panes', + scope: 'terminal', + searchKeywords: ['shortcut', 'terminal', 'pane', 'clear title', 'remove title', 'title'], + defaultBindings: platformBindings([]) + }, { id: 'terminal.closePane', title: 'Close active pane',