mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
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
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<GlobalSettings, 'terminalLinkActionPopoverEnabled'>
|
||||
settings: Pick<
|
||||
GlobalSettings,
|
||||
| 'terminalLinkActionPopoverEnabled'
|
||||
| 'terminalLinkClickBehavior'
|
||||
| 'terminalUrlMiddleClickBehavior'
|
||||
>
|
||||
isMac: boolean
|
||||
updateSettings: (updates: Partial<GlobalSettings>) => 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 (
|
||||
<SearchableSetting
|
||||
@@ -30,18 +82,48 @@ export function BrowserTerminalLinkActionsSetting({
|
||||
description={description}
|
||||
keywords={getTerminalLinkActionSearchKeywords({ isMac })}
|
||||
>
|
||||
<div className="ml-4 border-l border-border pl-4">
|
||||
<SettingsSwitchRow
|
||||
label={title}
|
||||
description={description}
|
||||
checked={settings.terminalLinkActionPopoverEnabled !== false}
|
||||
onChange={() =>
|
||||
updateSettings({
|
||||
terminalLinkActionPopoverEnabled: settings.terminalLinkActionPopoverEnabled === false
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<section className="space-y-3">
|
||||
<SettingsSubsectionHeader title={title} description={description} />
|
||||
<div className="rounded-lg border border-border/60 bg-muted/10 px-4">
|
||||
<div className="divide-y divide-border/40">
|
||||
<SettingsRow
|
||||
label={plainClickLabel}
|
||||
description={plainClickDescription}
|
||||
alignTop
|
||||
control={
|
||||
<SettingsSegmentedControl<TerminalLinkClickBehavior>
|
||||
value={behavior}
|
||||
onChange={(value) => updateSettings({ terminalLinkClickBehavior: value })}
|
||||
ariaLabel={plainClickAriaLabel}
|
||||
size="sm"
|
||||
options={[
|
||||
{ value: 'actions', label: actionsLabel },
|
||||
{ value: 'open', label: openUrlLabel },
|
||||
{ value: 'none', label: leaveToTerminalLabel }
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingsRow
|
||||
label={middleClickLabel}
|
||||
description={middleClickDescription}
|
||||
control={
|
||||
<SettingsSegmentedControl<TerminalLinkClickBehavior>
|
||||
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 }
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</SearchableSetting>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
@@ -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<GlobalSettings, 'terminalLinkClickBehavior' | 'terminalLinkActionPopoverEnabled'>
|
||||
| null
|
||||
| undefined
|
||||
): TerminalLinkClickBehavior {
|
||||
if (settings?.terminalLinkClickBehavior === 'open') {
|
||||
return 'open'
|
||||
}
|
||||
if (settings?.terminalLinkClickBehavior === 'none') {
|
||||
return 'none'
|
||||
}
|
||||
return settings?.terminalLinkActionPopoverEnabled === false ? 'none' : 'actions'
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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<string, boolean>()
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -130,6 +130,8 @@ export function buildDefaultSettings(args: {
|
||||
openLinksInAppPreferencePrompted: false,
|
||||
openLinksInAppModifierInverts: false,
|
||||
terminalLinkActionPopoverEnabled: true,
|
||||
terminalLinkClickBehavior: 'actions',
|
||||
terminalUrlMiddleClickBehavior: 'open',
|
||||
openAgentTabsInChatByDefault: false,
|
||||
experimentalNativeChat: false,
|
||||
experimentalStructuredNativeChat: false,
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user