/** * Path display utilities for shortening file paths in tight UI spaces. * * Strategy: * 1. Strip project root to make relative * 2. Replace home directory with ~ * 3. Middle-truncate if still too long, preserving first and last segments * * Also provides resolveAbsolutePath() for clipboard copy (~ → real home, relative → absolute). */ import { splitPath } from '@shared/utils/platformPath'; /** * Shorten a file path for display in compact UI elements. * Full path should still be available via tooltip (title attribute). * * Examples: * - `/Users/name/.claude/projects/-Users-name-project/memory/MEMORY.md` → `~/.claude/…/memory/MEMORY.md` * - `/Users/name/project/.claude/rules/tailwind.md` (with projectRoot) → `.claude/rules/tailwind.md` * - `~/.claude/CLAUDE.md` → `~/.claude/CLAUDE.md` (already short) */ export function shortenDisplayPath(fullPath: string, projectRoot?: string, maxLength = 40): string { let p = fullPath; // 1. Make relative to project root if (projectRoot) { const root = projectRoot.replace(/[/\\]$/, ''); if (p.startsWith(root + '/') || p.startsWith(root + '\\')) { p = p.slice(root.length + 1); } } // 2. Replace home directory with ~ p = p .replace(/^\/Users\/[^/]+/, '~') .replace(/^\/home\/[^/]+/, '~') .replace(/^[A-Z]:\\Users\\[^\\]+/, '~'); // 3. If short enough, return as-is if (p.length <= maxLength) return p; // 4. Middle-truncate: keep first meaningful segments + … + last 2 segments const sep = p.includes('\\') ? '\\' : '/'; const segments = p.split(sep); // Determine where content starts (skip leading empty segment from absolute paths or ~) let startIdx = 0; if (segments[0] === '' || segments[0] === '~') startIdx = 1; // Need at least 4 content segments to truncate the middle if (segments.length - startIdx <= 3) return p; const head = segments.slice(0, startIdx + 1).join(sep); const tail = segments.slice(-2).join(sep); return `${head}${sep}\u2026${sep}${tail}`; } /** * Infer the user's home directory from a known absolute project path. * Works for macOS (/Users/x), Linux (/home/x), and Windows (C:\Users\x). */ function inferHomeDir(projectRoot: string): string | null { const match = /^(\/Users\/[^/]+)/.exec(projectRoot) ?? /^(\/home\/[^/]+)/.exec(projectRoot) ?? /^([A-Z]:\\Users\\[^\\]+)/.exec(projectRoot); return match?.[1] ?? null; } /** * Resolve a possibly-shortened path to its full absolute form for clipboard copy. * * - `~/...` → `/Users/username/...` (home dir inferred from projectRoot) * - `src/foo/bar` → `{projectRoot}/src/foo/bar` * - Already absolute → returned as-is */ /** * Truncate a project path to ~/relative/path format. * Works for macOS (/Users/...), Linux (/home/...) and Windows (C:\Users\...). */ export function formatProjectPath(path: string): string { const p = path.replace(/\\/g, '/'); if (p.startsWith('/Users/') || p.startsWith('/home/')) { const parts = splitPath(p); if (parts.length >= 2) { const rest = parts.slice(2).join('/'); return rest ? `~/${rest}` : '~'; } } if (isWindowsUserPath(path)) { const parts = splitPath(p); if (parts.length >= 3) { const rest = parts.slice(3).join('/'); return rest ? `~/${rest}` : '~'; } } return p; } function isWindowsUserPath(input: string): boolean { if (input.length < 10) return false; const drive = input.charCodeAt(0); const hasDriveLetter = ((drive >= 65 && drive <= 90) || (drive >= 97 && drive <= 122)) && input[1] === ':'; return hasDriveLetter && input.startsWith('\\Users\\', 2); } export function resolveAbsolutePath(filePath: string, projectRoot?: string): string { let p = filePath; // Resolve ~ using home dir inferred from projectRoot if (p.startsWith('~/') && projectRoot) { const homeDir = inferHomeDir(projectRoot); if (homeDir) { p = homeDir + p.slice(1); } } // Make relative paths absolute by prepending projectRoot if (projectRoot && !p.startsWith('/') && !p.startsWith('~') && !/^[A-Z]:[/\\]/.test(p)) { p = projectRoot.replace(/[/\\]$/, '') + '/' + p; } return p; }