From cd81725d701cc29202c67fc5e9cb0fa27e86ff2d Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 18 Sep 2026 17:31:06 -0700 Subject: [PATCH] feat(terminal): configure URL click and middle-click behavior (#21438) * feat(terminal): configure URL click behavior * fix(i18n): include terminal link setting title * fix(i18n): localize terminal click controls * fix(settings): update terminal URL click title --- .../native-chat-web-link-actions.ts | 8 +- .../use-native-chat-link-actions.ts | 10 +- .../BrowserTerminalLinkActionsSetting.tsx | 116 +++++++++++++++--- .../settings/browser-search.test.ts | 7 +- .../src/components/settings/browser-search.ts | 13 +- .../terminal-link-action-request.ts | 22 +++- .../terminal-link-action-routing.test.ts | 52 ++++++++ .../terminal-pane/terminal-link-activation.ts | 19 ++- .../terminal-link-click-behavior.test.ts | 20 +++ .../terminal-link-click-behavior.ts | 19 +++ .../terminal-link-pty-mouse-suppression.ts | 8 +- .../terminal-pane-mount-preparation.ts | 11 +- .../terminal-url-link-hit-testing.ts | 4 +- src/renderer/src/i18n/locales/en.json | 14 ++- src/shared/default-global-settings.ts | 2 + src/shared/global-settings-types.ts | 4 + 16 files changed, 291 insertions(+), 38 deletions(-) create mode 100644 src/renderer/src/components/terminal-pane/terminal-link-click-behavior.test.ts create mode 100644 src/renderer/src/components/terminal-pane/terminal-link-click-behavior.ts diff --git a/src/renderer/src/components/native-chat/native-chat-web-link-actions.ts b/src/renderer/src/components/native-chat/native-chat-web-link-actions.ts index 54e3f408b32..ffe0659d922 100644 --- a/src/renderer/src/components/native-chat/native-chat-web-link-actions.ts +++ b/src/renderer/src/components/native-chat/native-chat-web-link-actions.ts @@ -4,6 +4,7 @@ import { isTerminalLinkActionActivation, isTerminalLinkDirectActivation } from '@/components/terminal-pane/terminal-link-activation' +import type { TerminalLinkClickBehavior } from '@/components/terminal-pane/terminal-link-click-behavior' import { buildHttpLinkActions, openRoutedHttpLink, @@ -18,6 +19,7 @@ export type NativeChatWebLinkDeps = { destinations: HttpLinkActionDestinations /** Off: a plain click opens the routed destination outright, as it did before actions existed. */ actionsEnabled: boolean + plainClickBehavior?: TerminalLinkClickBehavior restoreFocus: () => void request: (request: LinkActionRequest) => void } @@ -59,11 +61,15 @@ export function handleNativeChatWebLink( return false } - event.preventDefault() if (!deps.actionsEnabled) { + if (deps.plainClickBehavior === 'none') { + return false + } + event.preventDefault() open(deps.destinations.primary) return true } + event.preventDefault() const keyboardAnchor = event.detail === 0 ? event.currentTarget?.getBoundingClientRect() : null deps.request({ anchorX: keyboardAnchor?.left ?? event.clientX, diff --git a/src/renderer/src/components/native-chat/use-native-chat-link-actions.ts b/src/renderer/src/components/native-chat/use-native-chat-link-actions.ts index cf0b5e9f5ee..2afdd379cd6 100644 --- a/src/renderer/src/components/native-chat/use-native-chat-link-actions.ts +++ b/src/renderer/src/components/native-chat/use-native-chat-link-actions.ts @@ -13,6 +13,7 @@ import { resolveNativeChatHttpLinkSourceOwner } from './native-chat-http-link-source-owner' import { handleNativeChatWebLink } from './native-chat-web-link-actions' +import { terminalLinkClickBehaviorFor } from '@/components/terminal-pane/terminal-link-click-behavior' import { useNativeChatFileLinkClick } from './use-native-chat-file-link-click' export type NativeChatLinkActions = { @@ -61,6 +62,12 @@ export function useNativeChatLinkActions( // Read at click time: settings and workspace ownership must not re-render the transcript. const state = useAppStore.getState() const sourceOwner = resolveNativeChatHttpLinkSourceOwner(state, context.worktreeId) + const plainClickBehavior = + state.settings?.terminalLinkClickBehavior === undefined + ? state.settings?.terminalLinkActionPopoverEnabled === false + ? 'open' + : 'actions' + : terminalLinkClickBehaviorFor(state.settings) const anchor = event.currentTarget handleNativeChatWebLink(event, route.url, { worktreeId: context.worktreeId, @@ -70,7 +77,8 @@ export function useNativeChatLinkActions( sourceOwner, canNativeChatOpenOwnedBrowser(state, context.worktreeId, sourceOwner) ), - actionsEnabled: state.settings?.terminalLinkActionPopoverEnabled !== false, + actionsEnabled: plainClickBehavior === 'actions', + plainClickBehavior, restoreFocus: () => (anchor.isConnected ? anchor : rootRef.current)?.focus({ preventScroll: true }), request: setLinkActionRequest diff --git a/src/renderer/src/components/settings/BrowserTerminalLinkActionsSetting.tsx b/src/renderer/src/components/settings/BrowserTerminalLinkActionsSetting.tsx index 3335c456ebd..b3e0c62879b 100644 --- a/src/renderer/src/components/settings/BrowserTerminalLinkActionsSetting.tsx +++ b/src/renderer/src/components/settings/BrowserTerminalLinkActionsSetting.tsx @@ -2,12 +2,24 @@ import type { GlobalSettings } from '../../../../shared/global-settings-types' import { translate } from '@/i18n/i18n' import { BROWSER_TERMINAL_LINK_ACTIONS_SETTINGS_TARGET_ID } from '@/lib/settings-navigation-types' import { SearchableSetting } from './SearchableSetting' -import { SettingsSwitchRow } from './SettingsFormControls' -import { getTerminalLinkActionsDescription } from './browser-link-routing-copy' +import { + SettingsRow, + SettingsSegmentedControl, + SettingsSubsectionHeader +} from './SettingsFormControls' import { getTerminalLinkActionSearchKeywords } from './browser-search' +import { + terminalLinkClickBehaviorFor, + type TerminalLinkClickBehavior +} from '../terminal-pane/terminal-link-click-behavior' type BrowserTerminalLinkActionsSettingProps = { - settings: Pick + settings: Pick< + GlobalSettings, + | 'terminalLinkActionPopoverEnabled' + | 'terminalLinkClickBehavior' + | 'terminalUrlMiddleClickBehavior' + > isMac: boolean updateSettings: (updates: Partial) => void } @@ -19,9 +31,49 @@ export function BrowserTerminalLinkActionsSetting({ }: BrowserTerminalLinkActionsSettingProps): React.JSX.Element { const title = translate( 'auto.components.settings.BrowserTerminalLinkActionsSetting.title', - 'Show link actions' + 'Terminal URL clicks' + ) + const description = translate( + 'auto.components.settings.BrowserTerminalLinkActionsSetting.descriptionV2', + 'Control clicks on detected URLs printed in terminal panes and chat transcripts.' + ) + const behavior = terminalLinkClickBehaviorFor(settings) + const plainClickLabel = translate( + 'auto.components.settings.BrowserTerminalLinkActionsSetting.plainClickLabel', + 'Plain click' + ) + const plainClickDescription = translate( + 'auto.components.settings.BrowserTerminalLinkActionsSetting.plainClickDescription', + 'Choose whether a left-click shows actions, opens the URL, or leaves it to the terminal. Cmd/Ctrl-click always opens directly.' + ) + const plainClickAriaLabel = translate( + 'auto.components.settings.BrowserTerminalLinkActionsSetting.plainClickAriaLabel', + 'Plain click URL behavior' + ) + const middleClickLabel = translate( + 'auto.components.settings.BrowserTerminalLinkActionsSetting.middleClickLabel', + 'Middle click' + ) + const middleClickDescription = translate( + 'auto.components.settings.BrowserTerminalLinkActionsSetting.middleClickDescription', + 'Choose what a mouse-wheel click does on a detected terminal URL.' + ) + const middleClickAriaLabel = translate( + 'auto.components.settings.BrowserTerminalLinkActionsSetting.middleClickAriaLabel', + 'Middle click' + ) + const actionsLabel = translate( + 'auto.components.settings.BrowserTerminalLinkActionsSetting.actionsLabel', + 'Actions' + ) + const openUrlLabel = translate( + 'auto.components.settings.BrowserTerminalLinkActionsSetting.openUrlLabel', + 'Open URL' + ) + const leaveToTerminalLabel = translate( + 'auto.components.settings.BrowserTerminalLinkActionsSetting.leaveToTerminalLabel', + 'Leave to terminal' ) - const description = getTerminalLinkActionsDescription({ isMac }) return ( -
- - updateSettings({ - terminalLinkActionPopoverEnabled: settings.terminalLinkActionPopoverEnabled === false - }) - } - /> -
+
+ +
+
+ + value={behavior} + onChange={(value) => updateSettings({ terminalLinkClickBehavior: value })} + ariaLabel={plainClickAriaLabel} + size="sm" + options={[ + { value: 'actions', label: actionsLabel }, + { value: 'open', label: openUrlLabel }, + { value: 'none', label: leaveToTerminalLabel } + ]} + /> + } + /> + + value={settings.terminalUrlMiddleClickBehavior ?? 'open'} + onChange={(value) => updateSettings({ terminalUrlMiddleClickBehavior: value })} + ariaLabel={middleClickAriaLabel} + size="sm" + options={[ + { value: 'actions', label: actionsLabel }, + { value: 'open', label: openUrlLabel }, + { value: 'none', label: leaveToTerminalLabel } + ]} + /> + } + /> +
+
+
) } diff --git a/src/renderer/src/components/settings/browser-search.test.ts b/src/renderer/src/components/settings/browser-search.test.ts index 9d1df9717ad..07321b120b9 100644 --- a/src/renderer/src/components/settings/browser-search.test.ts +++ b/src/renderer/src/components/settings/browser-search.test.ts @@ -61,10 +61,9 @@ describe('browser settings search copy', () => { expect(linkRoutingEntry?.keywords).not.toContain('cmd') const terminalActionsEntry = getBrowserPaneSearchEntries({ isMac: false }).find( - (entry) => entry.title === 'Show link actions' + (entry) => entry.title === 'Terminal URL clicks' ) - expect(terminalActionsEntry?.description).toContain('Ctrl-click') - expect(terminalActionsEntry?.description).not.toContain('Cmd/Ctrl') + expect(terminalActionsEntry?.description).toContain('detected URLs') expect(terminalActionsEntry?.keywords).toEqual( getTerminalLinkActionSearchKeywords({ isMac: false }) ) @@ -102,7 +101,7 @@ describe('browser link routing modifier copy', () => { 'Default Zoom', 'Link Routing', 'Hold Shift to open in Orca', - 'Show link actions', + 'Terminal URL clicks', 'Localhost Worktree Labels', 'Session & Cookies', 'Remote server workspaces', diff --git a/src/renderer/src/components/settings/browser-search.ts b/src/renderer/src/components/settings/browser-search.ts index 17c7e11a771..71114696b77 100644 --- a/src/renderer/src/components/settings/browser-search.ts +++ b/src/renderer/src/components/settings/browser-search.ts @@ -4,7 +4,6 @@ import { translateSearchKeyword } from './settings-search-keywords' import { getBrowserUserAgentSearchEntry } from './browser-user-agent-search' import { getBrowserLinkRoutingDescription, - getTerminalLinkActionsDescription, getLinkRoutingModifierDescription, getLinkRoutingModifierTitle } from './browser-link-routing-copy' @@ -59,6 +58,11 @@ export function getTerminalLinkActionSearchKeywords(platform: BrowserShortcutPla 'auto.components.settings.browser.search.terminalLinkActions.disable', 'disable' ), + 'link click behavior', + 'open directly', + 'modifier-click only', + 'middle click', + 'mouse 3', platform.isMac ? 'cmd' : 'ctrl' ] } @@ -187,9 +191,12 @@ export function getBrowserPaneSearchEntries( { title: translate( 'auto.components.settings.BrowserTerminalLinkActionsSetting.title', - 'Show link actions' + 'Terminal URL clicks' + ), + description: translate( + 'auto.components.settings.BrowserTerminalLinkActionsSetting.descriptionV2', + 'Control clicks on detected URLs printed in terminal panes and chat transcripts.' ), - description: getTerminalLinkActionsDescription(platform), keywords: getTerminalLinkActionSearchKeywords(platform) }, { diff --git a/src/renderer/src/components/terminal-pane/terminal-link-action-request.ts b/src/renderer/src/components/terminal-pane/terminal-link-action-request.ts index 833d05ffa4d..1ddedfe9e5a 100644 --- a/src/renderer/src/components/terminal-pane/terminal-link-action-request.ts +++ b/src/renderer/src/components/terminal-pane/terminal-link-action-request.ts @@ -1,5 +1,9 @@ import type { TerminalLinkPointerGesture } from './terminal-link-pointer-gesture' -import { isTerminalLinkActionActivation } from './terminal-link-activation' +import { + isTerminalLinkActionActivation, + isTerminalMiddleClickActivation +} from './terminal-link-activation' +import type { TerminalLinkClickBehavior } from './terminal-link-click-behavior' import { closeLinkActionRequest, type LinkAction, @@ -21,6 +25,8 @@ export type TerminalLinkActionContext = { claimPtyMouse: () => boolean request: TerminalLinkActionRequester focusTerminal: () => void + plainClickBehavior?: TerminalLinkClickBehavior + middleClickBehavior?: TerminalLinkClickBehavior } export function closeTerminalLinkActionRequest( @@ -43,7 +49,10 @@ export function requestTerminalLinkAction( if ( !event || !context || - !isTerminalLinkActionActivation(event) || + !( + isTerminalLinkActionActivation(event) || + (isTerminalMiddleClickActivation(event) && context.middleClickBehavior !== 'none') + ) || !context.pointerGesture.canRequestAction(event) ) { return false @@ -53,6 +62,15 @@ export function requestTerminalLinkAction( return false } event.preventDefault() + const middleClick = isTerminalMiddleClickActivation(event) + if ((middleClick ? context.middleClickBehavior : context.plainClickBehavior) === 'open') { + context.focusTerminal() + details.primary?.run() + return true + } + if (middleClick && context.middleClickBehavior !== 'actions') { + return false + } context.request({ ...details, paneId: context.paneId, diff --git a/src/renderer/src/components/terminal-pane/terminal-link-action-routing.test.ts b/src/renderer/src/components/terminal-pane/terminal-link-action-routing.test.ts index e344ac82a0e..ca1168eb7b6 100644 --- a/src/renderer/src/components/terminal-pane/terminal-link-action-routing.test.ts +++ b/src/renderer/src/components/terminal-pane/terminal-link-action-routing.test.ts @@ -30,6 +30,12 @@ function plainEvent(): MouseEvent { } as unknown as MouseEvent } +function middleEvent(): MouseEvent { + const event = plainEvent() + Object.defineProperty(event, 'button', { value: 1 }) + return event +} + function actionContext(request = vi.fn()): TerminalLinkActionContext { return { paneId: 7, @@ -85,6 +91,52 @@ describe('terminal link action routing', () => { ) }) + it('opens the primary destination directly when plain-click mode is enabled', () => { + const request = vi.fn() + const run = vi.fn() + const context = actionContext(request) + context.plainClickBehavior = 'open' + + expect( + requestTerminalLinkAction(plainEvent(), context, { + destination: 'https://example.com', + kind: 'url', + primary: { label: 'Open', run } + }) + ).toBe(true) + expect(run).toHaveBeenCalledOnce() + expect(request).not.toHaveBeenCalled() + }) + + it('opens a URL on middle click when configured', () => { + const run = vi.fn() + const context = actionContext() + context.middleClickBehavior = 'open' + expect( + requestTerminalLinkAction(middleEvent(), context, { + destination: 'https://example.com', + kind: 'url', + primary: { label: 'Open', run } + }) + ).toBe(true) + expect(run).toHaveBeenCalledOnce() + }) + + it('keeps middle click available when plain clicks stay with the terminal', () => { + const run = vi.fn() + const context = actionContext() + context.plainClickBehavior = 'none' + context.middleClickBehavior = 'open' + expect( + requestTerminalLinkAction(middleEvent(), context, { + destination: 'https://example.com', + kind: 'url', + primary: { label: 'Open', run } + }) + ).toBe(true) + expect(run).toHaveBeenCalledOnce() + }) + it('leaves PTY mouse ownership with an ineligible pointer gesture', () => { const context = actionContext() context.pointerGesture.canRequestAction = () => false diff --git a/src/renderer/src/components/terminal-pane/terminal-link-activation.ts b/src/renderer/src/components/terminal-pane/terminal-link-activation.ts index aae05daa875..70eb08ff595 100644 --- a/src/renderer/src/components/terminal-pane/terminal-link-activation.ts +++ b/src/renderer/src/components/terminal-pane/terminal-link-activation.ts @@ -29,6 +29,23 @@ export function isTerminalLinkActionActivation(event: TerminalLinkMouseEvent | u ) } +export function isTerminalMiddleClickActivation( + event: TerminalLinkMouseEvent | undefined +): boolean { + return Boolean( + event && + event.button === 1 && + !event.altKey && + !event.shiftKey && + !event.metaKey && + !event.ctrlKey + ) +} + export function isTerminalOwnedLinkGesture(event: TerminalLinkMouseEvent | undefined): boolean { - return isTerminalLinkDirectActivation(event) || isTerminalLinkActionActivation(event) + return ( + isTerminalLinkDirectActivation(event) || + isTerminalLinkActionActivation(event) || + isTerminalMiddleClickActivation(event) + ) } diff --git a/src/renderer/src/components/terminal-pane/terminal-link-click-behavior.test.ts b/src/renderer/src/components/terminal-pane/terminal-link-click-behavior.test.ts new file mode 100644 index 00000000000..2ba4a4a24d2 --- /dev/null +++ b/src/renderer/src/components/terminal-pane/terminal-link-click-behavior.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest' +import { terminalLinkClickBehaviorFor } from './terminal-link-click-behavior' + +describe('terminalLinkClickBehaviorFor', () => { + it('defaults to actions and preserves legacy profiles', () => { + expect(terminalLinkClickBehaviorFor(undefined)).toBe('actions') + expect(terminalLinkClickBehaviorFor({ terminalLinkActionPopoverEnabled: true })).toBe('actions') + expect(terminalLinkClickBehaviorFor({ terminalLinkActionPopoverEnabled: false })).toBe('none') + }) + + it('prefers the explicit behavior for new profiles', () => { + expect( + terminalLinkClickBehaviorFor({ + terminalLinkActionPopoverEnabled: false, + terminalLinkClickBehavior: 'open' + }) + ).toBe('open') + expect(terminalLinkClickBehaviorFor({ terminalLinkClickBehavior: 'none' })).toBe('none') + }) +}) diff --git a/src/renderer/src/components/terminal-pane/terminal-link-click-behavior.ts b/src/renderer/src/components/terminal-pane/terminal-link-click-behavior.ts new file mode 100644 index 00000000000..4683ac2771a --- /dev/null +++ b/src/renderer/src/components/terminal-pane/terminal-link-click-behavior.ts @@ -0,0 +1,19 @@ +import type { GlobalSettings } from '../../../../shared/global-settings-types' + +export type TerminalLinkClickBehavior = 'actions' | 'open' | 'none' + +/** Resolves the new preference while keeping older profiles behaviorally identical. */ +export function terminalLinkClickBehaviorFor( + settings: + | Pick + | null + | undefined +): TerminalLinkClickBehavior { + if (settings?.terminalLinkClickBehavior === 'open') { + return 'open' + } + if (settings?.terminalLinkClickBehavior === 'none') { + return 'none' + } + return settings?.terminalLinkActionPopoverEnabled === false ? 'none' : 'actions' +} diff --git a/src/renderer/src/components/terminal-pane/terminal-link-pty-mouse-suppression.ts b/src/renderer/src/components/terminal-pane/terminal-link-pty-mouse-suppression.ts index 6dccb30340c..f5a4537ece3 100644 --- a/src/renderer/src/components/terminal-pane/terminal-link-pty-mouse-suppression.ts +++ b/src/renderer/src/components/terminal-pane/terminal-link-pty-mouse-suppression.ts @@ -1,7 +1,8 @@ import type { IDisposable, Terminal } from '@xterm/xterm' import { isTerminalLinkActionActivation, - isTerminalLinkDirectActivation + isTerminalLinkDirectActivation, + isTerminalMiddleClickActivation } from './terminal-link-activation' import { isXtermMouseReport } from './terminal-pointer-input-sequences' @@ -80,7 +81,10 @@ export function installTerminalLinkPtyMouseSuppression( captureCurrentMouseEvent() return } - if (!isTerminalLinkDirectActivation(event) || !shouldSuppressMouseEvent(event)) { + if ( + (!isTerminalLinkDirectActivation(event) && !isTerminalMiddleClickActivation(event)) || + !shouldSuppressMouseEvent(event) + ) { return } restore() diff --git a/src/renderer/src/components/terminal-pane/terminal-pane-mount-preparation.ts b/src/renderer/src/components/terminal-pane/terminal-pane-mount-preparation.ts index 56c9825212d..c6fe626871b 100644 --- a/src/renderer/src/components/terminal-pane/terminal-pane-mount-preparation.ts +++ b/src/renderer/src/components/terminal-pane/terminal-pane-mount-preparation.ts @@ -23,6 +23,7 @@ import { fitAndFocusPanes, fitPanes } from './pane-helpers' import { registerRuntimeTerminalTab } from '@/runtime/sync-runtime-graph' import { normalizeTerminalLayoutSnapshot } from './layout-serialization' import { applyTerminalAppearance } from './terminal-appearance' +import { terminalLinkClickBehaviorFor } from './terminal-link-click-behavior' import { createTerminalPanePtyDeps } from './terminal-pane-pty-deps' import type { DeferredSplitPaneHandoffHandle } from './deferred-split-pane-handoff' import { @@ -130,7 +131,9 @@ export function prepareTerminalPaneMount( canOpenOwnedBrowserForPane(paneId) ) const getLinkActionContext = (paneId: number): TerminalLinkActionContext | null => { - if (deps.settingsRef.current?.terminalLinkActionPopoverEnabled === false) { + const plainClickBehavior = terminalLinkClickBehaviorFor(deps.settingsRef.current) + const middleClickBehavior = deps.settingsRef.current?.terminalUrlMiddleClickBehavior ?? 'open' + if (plainClickBehavior === 'none' && middleClickBehavior === 'none') { return null } const pane = deps.managerRef.current?.getPanes().find((candidate) => candidate.id === paneId) @@ -145,7 +148,9 @@ export function prepareTerminalPaneMount( pointerGesture, claimPtyMouse: suppression.claimAction, request: deps.requestTerminalLinkAction, - focusTerminal: () => pane.terminal.focus() + focusTerminal: () => pane.terminal.focus(), + plainClickBehavior, + middleClickBehavior } } const pathExistsCache = new Map() @@ -243,7 +248,7 @@ export function prepareTerminalPaneMount( getHttpLinkSourceOwnerForPane(paneId), canOpenOwnedBrowserForPane(paneId) ), - showActions: deps.settingsRef.current?.terminalLinkActionPopoverEnabled !== false + showActions: terminalLinkClickBehaviorFor(deps.settingsRef.current) === 'actions' }) return { container, diff --git a/src/renderer/src/components/terminal-pane/terminal-url-link-hit-testing.ts b/src/renderer/src/components/terminal-pane/terminal-url-link-hit-testing.ts index 0139df29e16..2fa35ee80ac 100644 --- a/src/renderer/src/components/terminal-pane/terminal-url-link-hit-testing.ts +++ b/src/renderer/src/components/terminal-pane/terminal-url-link-hit-testing.ts @@ -59,7 +59,7 @@ export type TerminalHttpLinkActionDestinations = HttpLinkActionDestinations export type TerminalLinkRoutingPreferenceRequester = HttpLinkRoutingPreferenceRequester function isDesktopHttpLinkFallbackActivation(event: MouseEvent): boolean { - if (event.defaultPrevented || event.button !== 0) { + if (event.defaultPrevented || (event.button !== 0 && event.button !== 1)) { return false } // Why: Shift-only, Alt, and non-primary clicks remain available to the terminal or child TUI. @@ -111,7 +111,7 @@ export function findHttpLinkAtTerminalMouseEvent( terminal: Terminal, event: MouseEvent ): string | null { - if (event.button !== 0 || !isTerminalOwnedLinkGesture(event)) { + if ((event.button !== 0 && event.button !== 1) || !isTerminalOwnedLinkGesture(event)) { return null } const position = getTerminalBufferPositionForMouseEvent(terminal, event) diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index d2364f78977..d9802a25472 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -11329,8 +11329,18 @@ "descriptionOrca": "Links open in your system browser. When enabled, {{chord}}+click opens one in Orca's built-in browser instead." }, "BrowserTerminalLinkActionsSetting": { - "title": "Show link actions", - "description": "Show available actions when you click a link in the terminal or a chat transcript. Turn this off to require {{modifier}}-click in the terminal." + "title": "Terminal URL clicks", + "description": "Show available actions when you click a link in the terminal or a chat transcript. Turn this off to require {{modifier}}-click in the terminal.", + "descriptionV2": "Control clicks on detected URLs printed in terminal panes and chat transcripts.", + "plainClickLabel": "Plain click", + "plainClickDescription": "Choose whether a left-click shows actions, opens the URL, or leaves it to the terminal. Cmd/Ctrl-click always opens directly.", + "plainClickAriaLabel": "Plain click URL behavior", + "middleClickLabel": "Middle click", + "middleClickDescription": "Choose what a mouse-wheel click does on a detected terminal URL.", + "middleClickAriaLabel": "Middle click", + "actionsLabel": "Actions", + "openUrlLabel": "Open URL", + "leaveToTerminalLabel": "Leave to terminal" }, "PluginConsentDialog": { "workerTrust": "Background worker — runs its own process", diff --git a/src/shared/default-global-settings.ts b/src/shared/default-global-settings.ts index 9339013d6d4..7c532c3bfa0 100644 --- a/src/shared/default-global-settings.ts +++ b/src/shared/default-global-settings.ts @@ -130,6 +130,8 @@ export function buildDefaultSettings(args: { openLinksInAppPreferencePrompted: false, openLinksInAppModifierInverts: false, terminalLinkActionPopoverEnabled: true, + terminalLinkClickBehavior: 'actions', + terminalUrlMiddleClickBehavior: 'open', openAgentTabsInChatByDefault: false, experimentalNativeChat: false, experimentalStructuredNativeChat: false, diff --git a/src/shared/global-settings-types.ts b/src/shared/global-settings-types.ts index 1c31c1ccedd..48ac9122dfd 100644 --- a/src/shared/global-settings-types.ts +++ b/src/shared/global-settings-types.ts @@ -218,6 +218,10 @@ export type GlobalSettings = { openLinksInAppModifierInverts?: boolean /** Show link actions on plain click in the terminal and chat; off restores modifier-click-only terminal links. */ terminalLinkActionPopoverEnabled?: boolean + /** Plain-click behavior for terminal links; optional for profiles saved before this setting existed. */ + terminalLinkClickBehavior?: 'actions' | 'open' | 'none' + /** Middle mouse URL behavior; defaults to opening the primary routed destination. */ + terminalUrlMiddleClickBehavior?: 'open' | 'actions' | 'none' /** Opt-in: open new coding-agent tabs in native chat instead of the raw terminal; optional for legacy settings. */ openAgentTabsInChatByDefault?: boolean /** Experimental native chat surface for Claude/Codex sessions; off by default. */