feat: show hover tooltip for OSC 8 and file path links (#246)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Neil
2026-03-31 19:07:14 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 6f77e8b2c6
commit 9fa7aec27a
4 changed files with 37 additions and 5 deletions
@@ -66,7 +66,12 @@ export function openDetectedFilePath(
})()
}
export function createFilePathLinkProvider(paneId: number, deps: LinkHandlerDeps): ILinkProvider {
export function createFilePathLinkProvider(
paneId: number,
deps: LinkHandlerDeps,
linkTooltip: HTMLElement,
openLinkHint: string
): ILinkProvider {
const { startupCwd, managerRef, pathExistsCache, worktreeId, worktreePath } = deps
return {
provideLinks: (bufferLineNumber, callback) => {
@@ -109,11 +114,21 @@ export function createFilePathLinkProvider(paneId: number, deps: LinkHandlerDeps
end: { x: parsed.endIndex + 1, y: bufferLineNumber }
},
text: parsed.displayText,
activate: () => {
activate: (event) => {
if (!isTerminalLinkActivation(event)) {
return
}
openDetectedFilePath(resolved.absolutePath, resolved.line, resolved.column, {
worktreeId,
worktreePath
})
},
hover: () => {
linkTooltip.textContent = `${resolved.absolutePath} (${openLinkHint})`
linkTooltip.style.display = ''
},
leave: () => {
linkTooltip.style.display = 'none'
}
}
})
@@ -125,7 +140,9 @@ export function createFilePathLinkProvider(paneId: number, deps: LinkHandlerDeps
}
}
export function isTerminalLinkActivation(event: Pick<MouseEvent, 'metaKey' | 'ctrlKey'> | undefined): boolean {
export function isTerminalLinkActivation(
event: Pick<MouseEvent, 'metaKey' | 'ctrlKey'> | undefined
): boolean {
const isMac = navigator.userAgent.includes('Mac')
return isMac ? Boolean(event?.metaKey) : Boolean(event?.ctrlKey)
}
@@ -159,15 +159,28 @@ export function useTerminalPaneLifecycle({
markWorktreeUnread
}
const isMac = navigator.userAgent.includes('Mac')
const openLinkHint = isMac ? '⌘+click to open' : 'Ctrl+click to open'
const manager = new PaneManager(container, {
onPaneCreated: (pane) => {
const linkProviderDisposable = pane.terminal.registerLinkProvider(
createFilePathLinkProvider(pane.id, linkDeps)
createFilePathLinkProvider(pane.id, linkDeps, pane.linkTooltip, openLinkHint)
)
linkProviderDisposablesRef.current.set(pane.id, linkProviderDisposable)
pane.terminal.options.linkHandler = {
allowNonHttpProtocols: true,
activate: (event, text) => handleOscLink(text, event as MouseEvent | undefined)
activate: (event, text) => handleOscLink(text, event as MouseEvent | undefined),
// Show bottom-left tooltip on hover for OSC 8 hyperlinks (e.g.
// GitHub owner/repo#issue references emitted by CLI tools) — same
// behaviour as the WebLinksAddon provides for plain-text URLs.
hover: (_event, text) => {
pane.linkTooltip.textContent = `${text} (${openLinkHint})`
pane.linkTooltip.style.display = ''
},
leave: () => {
pane.linkTooltip.style.display = 'none'
}
}
applyAppearance(manager)
connectPanePty(pane, manager, ptyDeps)
@@ -33,6 +33,7 @@ export type ManagedPane = {
id: number
terminal: Terminal
container: HTMLElement // the .pane element
linkTooltip: HTMLElement
fitAddon: FitAddon
searchAddon: SearchAddon
serializeAddon: SerializeAddon
@@ -317,6 +317,7 @@ export class PaneManager {
id: pane.id,
terminal: pane.terminal,
container: pane.container,
linkTooltip: pane.linkTooltip,
fitAddon: pane.fitAddon,
searchAddon: pane.searchAddon,
serializeAddon: pane.serializeAddon