mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
import { isWindowsAbsolutePathLike } from './cross-platform-path'
|
|
import { IMAGE_FILE_EXTENSIONS } from './file-preview/image-mime-types'
|
|
import { shellEscapePath } from './shell/launch/shell-path-quoting'
|
|
import { isWslUncPath } from './wsl-paths'
|
|
|
|
type TerminalTargetShell = 'posix' | 'windows'
|
|
|
|
// Terminal TUIs recognize bracketed file-path pastes as image attachments, matching clipboard screenshots (#2842).
|
|
const IMAGE_DROP_EXTENSIONS = new Set(IMAGE_FILE_EXTENSIONS)
|
|
// Why: an ASCII space is unsafe because Codex shlex-splits a raw spaced path into several tokens.
|
|
const POSIX_RAW_IMAGE_DROP_UNSAFE_RE = /[ "'`$;&|<>(){}[\]*?!#\\]/
|
|
const WINDOWS_RAW_IMAGE_DROP_UNSAFE_RE = /[ "'`$;&|<>(){}[\]*?!#^%]/
|
|
|
|
/** Match image extensions case-insensitively within POSIX/Windows basenames; dots in parent directories do not count. */
|
|
export function isImageDropPath(path: string): boolean {
|
|
const lastDot = path.lastIndexOf('.')
|
|
if (lastDot === -1) {
|
|
return false
|
|
}
|
|
const lastSeparator = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
|
|
if (lastDot < lastSeparator) {
|
|
return false
|
|
}
|
|
return IMAGE_DROP_EXTENSIONS.has(path.slice(lastDot).toLowerCase())
|
|
}
|
|
|
|
export function isTerminalDropWindowsPathLike(path: string): boolean {
|
|
if (isWslUncPath(path)) {
|
|
return false
|
|
}
|
|
return isWindowsAbsolutePathLike(path) || path.includes('\\')
|
|
}
|
|
|
|
export function canPasteImageDropPathRaw(path: string, targetShell: TerminalTargetShell): boolean {
|
|
if (hasControlByte(path)) {
|
|
return false
|
|
}
|
|
const unsafeRe =
|
|
targetShell === 'windows' ? WINDOWS_RAW_IMAGE_DROP_UNSAFE_RE : POSIX_RAW_IMAGE_DROP_UNSAFE_RE
|
|
return !unsafeRe.test(path)
|
|
}
|
|
|
|
/** Bracketed-paste text, or null when paste framing would rewrite control bytes and alter the path. */
|
|
export function formatImageDropPasteText(
|
|
path: string,
|
|
targetShell: TerminalTargetShell
|
|
): string | null {
|
|
if (hasControlByte(path)) {
|
|
return null
|
|
}
|
|
if (canPasteImageDropPathRaw(path, targetShell)) {
|
|
return path
|
|
}
|
|
// Claude strips only one quote pair, so POSIX splice quoting leaks into paths; backslash escapes round-trip across Claude/Codex/shells.
|
|
return targetShell === 'windows'
|
|
? shellEscapePath(path, 'windows')
|
|
: backslashEscapePosixPath(path)
|
|
}
|
|
|
|
function backslashEscapePosixPath(path: string): string {
|
|
return path.replace(/[^a-zA-Z0-9_./@:\-\u0080-\uffff]/g, '\\$&')
|
|
}
|
|
|
|
function hasControlByte(path: string): boolean {
|
|
for (let i = 0; i < path.length; i += 1) {
|
|
const code = path.charCodeAt(i)
|
|
if (code < 0x20 || code === 0x7f) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|