Files
orca/src/shared/explicit-file-link-target.ts
T
Kelvin Amoaba f719d0fb10 fix(mobile): open terminal urls and file paths with desktop's link detection (#33964)
* fix(mobile): open wrapped terminal urls and paths with desktop's link detection

Desktop's terminal URL and file-link detectors move to src/shared and the phone's
WebView document calls them, replacing its single-row copies. File taps carry every
reading of the tapped text so the phone opens the longest one the host finds, as
desktop does.

* fix(mobile): keep the unc host of osc 8 file links for the host to resolve

* fix(mobile): resolve terminal file-tap readings a few at a time

A tap inside a run of path rows yields hundreds of hard-wrap guesses; firing them all at once overflowed the page bridge's pending-request cap and the real path was never checked.

* refactor(terminal): share the osc 8 link classifier and trim file-tap plumbing

Desktop and the phone now read OSC 8 targets through one shared classifier. Terminal file taps carry only their candidate list, deduped by path, and desktop checks OSC overlap only for candidates it can resolve.

* refactor(terminal): keep shared link files under the line cap

The xterm buffer subset gets its own module, and the text-only OSC resolver goes now that the router uses findExactTerminalFileLink.
2026-10-27 05:30:05 +00:00

79 lines
2.2 KiB
TypeScript

import { parseFileLinkLocation, type ParsedFileLinkLocation } from './file-link-location'
import {
joinAbsolutePath,
normalizeAbsolutePath,
resolveTildePath
} from './terminal-path-normalization'
export type ParsedExplicitFileLinkTarget = ParsedFileLinkLocation
export type ResolvedExplicitFileLinkTarget = Pick<
ParsedExplicitFileLinkTarget,
'line' | 'column'
> & {
absolutePath: string
}
type ParseExplicitFileLinkTargetOptions = {
allowRelativeDirectoryPath?: boolean
}
function canKeepTrailingSeparator(pathText: string): boolean {
// Bare roots are ambiguous link targets; absolute/tilde paths need a real segment to identify directories.
if (/^[\\/]+$/.test(pathText) || /^~[\\/]$/.test(pathText) || /^[A-Za-z]:[\\/]$/.test(pathText)) {
return false
}
return /^(?:~[\\/]|[\\/]|[A-Za-z]:[\\/])/.test(pathText)
}
export function parseExplicitFileLinkTarget(
value: string,
options: ParseExplicitFileLinkTargetOptions = {}
): ParsedExplicitFileLinkTarget | null {
const parsed = parseFileLinkLocation(value)
if (!parsed) {
return null
}
const { pathText, line, column } = parsed
const hasLineOrColumn = line !== null || column !== null
if (/^[\\/]\s/.test(pathText)) {
return null
}
if (/[\\/]$/.test(pathText)) {
const canKeepRelativeDirectory = options.allowRelativeDirectoryPath === true && !hasLineOrColumn
if (hasLineOrColumn || (!canKeepRelativeDirectory && !canKeepTrailingSeparator(pathText))) {
return null
}
}
return { pathText, line, column }
}
export function resolveExplicitFileLinkTargetPath(
pathText: string,
cwd: string,
homePath?: string | null
): string | null {
if (/^~[\\/]/.test(pathText)) {
return resolveTildePath(pathText, cwd, homePath)
}
return normalizeAbsolutePath(pathText)?.normalized ?? joinAbsolutePath(cwd, pathText)
}
export function resolveExplicitFileLinkTarget(
parsed: ParsedExplicitFileLinkTarget,
cwd: string,
homePath?: string | null
): ResolvedExplicitFileLinkTarget | null {
const absolutePath = resolveExplicitFileLinkTargetPath(parsed.pathText, cwd, homePath)
if (!absolutePath) {
return null
}
return {
absolutePath,
line: parsed.line,
column: parsed.column
}
}