Something went wrong. Try again.
opencode v2 mirror
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163export function stripFileProtocol(input: string) { if (!input.startsWith("file://")) return input return input.slice("file://".length)}
export function stripQueryAndHash(input: string) { const hashIndex = input.indexOf("#") const queryIndex = input.indexOf("?")
if (hashIndex !== -1 && queryIndex !== -1) { return input.slice(0, Math.min(hashIndex, queryIndex)) }
if (hashIndex !== -1) return input.slice(0, hashIndex) if (queryIndex !== -1) return input.slice(0, queryIndex) return input}
export function unquoteGitPath(input: string) { if (!input.startsWith('"')) return input if (!input.endsWith('"')) return input const body = input.slice(1, -1) const bytes: number[] = []
for (let i = 0; i < body.length; i++) { const char = body[i]! if (char !== "\\") { bytes.push(char.charCodeAt(0)) continue }
const next = body[i + 1] if (!next) { bytes.push("\\".charCodeAt(0)) continue }
if (next >= "0" && next <= "7") { const chunk = body.slice(i + 1, i + 4) const match = chunk.match(/^[0-7]{1,3}/) if (!match) { bytes.push(next.charCodeAt(0)) i++ continue } bytes.push(parseInt(match[0], 8)) i += match[0].length continue }
const escaped = next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? "\t" : next === "b" ? "\b" : next === "f" ? "\f" : next === "v" ? "\v" : next === "\\" || next === '"' ? next : undefined
bytes.push((escaped ?? next).charCodeAt(0)) i++ }
return new TextDecoder().decode(new Uint8Array(bytes))}
export function decodeFilePath(input: string) { try { return decodeURIComponent(input) } catch { return input }}
export function encodeFilePath(filepath: string): string { // Normalize Windows paths: convert backslashes to forward slashes let normalized = filepath.replace(/\\/g, "/")
// Handle Windows absolute paths (D:/path -> /D:/path for proper file:// URLs) if (/^[A-Za-z]:/.test(normalized)) { normalized = "/" + normalized }
// Encode each path segment (preserving forward slashes as path separators) // Keep the colon in Windows drive letters (`/C:/...`) so downstream file URL parsers // can reliably detect drives. return normalized .split("/") .map((segment, index) => { if (index === 1 && /^[A-Za-z]:$/.test(segment)) return segment return encodeURIComponent(segment) }) .join("/")}
export function createPathHelpers(scope: () => string) { const normalize = (input: string) => { const root = scope()
// file:///C:/dir becomes /C:/dir once the protocol is gone; restore the drive form. let path = unquoteGitPath(decodeFilePath(stripQueryAndHash(stripFileProtocol(input)))).replace( /^[/\\]([A-Za-z]:)/, "$1", )
// Separator-agnostic prefix stripping for Cygwin/native Windows compatibility // Only case-insensitive on Windows (drive letter or UNC paths) const windows = /^[A-Za-z]:/.test(root) || root.startsWith("\\\\") const canonRoot = windows ? root.replace(/\\/g, "/").toLowerCase() : root.replace(/\\/g, "/") const canonPath = windows ? path.replace(/\\/g, "/").toLowerCase() : path.replace(/\\/g, "/") if ( canonPath.startsWith(canonRoot) && (canonRoot.endsWith("/") || canonPath === canonRoot || canonPath[canonRoot.length] === "/") ) { // Slice from original path to preserve native separators, then drop the separator itself. path = path.slice(root.length).replace(/^[/\\]/, "") }
if (path.startsWith("./") || path.startsWith(".\\")) { path = path.slice(2) }
// An absolute path that is not under the root stays absolute; it is a file outside the workspace. return path }
/** Whether a normalized path points outside the workspace root. */ const absolute = (path: string) => /^[A-Za-z]:[/\\]/.test(path) || path.startsWith("/") || path.startsWith("\\\\")
const tab = (input: string) => { const path = normalize(input) return `file://${encodeFilePath(path)}` }
const pathFromTab = (tabValue: string) => { if (!tabValue.startsWith("file://")) return return normalize(tabValue) }
const normalizeDir = (input: string) => { const path = normalize(input) const root = scope() const windows = /^[A-Za-z]:/.test(root) || root.startsWith("\\\\") return (windows ? path.replace(/\\/g, "/") : path).replace(/\/+$/, "") }
return { normalize, absolute, tab, pathFromTab, normalizeDir, }}