agent-ecosystem/src/renderer/utils/pathDisplay.ts
iliya 52ef9fd0a8 feat: add FAQ section to README and improve cross-platform handling in code
- Introduced a comprehensive FAQ section in the README to address common user queries regarding app installation, code handling, agent communication, and project management.
- Enhanced cross-platform keyboard shortcut handling in the Electron app for better user experience on macOS and Windows/Linux.
- Updated signal handling in the standalone process to ensure proper shutdown behavior across platforms.
- Improved WSL user resolution logic to support default user retrieval for better compatibility.
- Enhanced notification handling to support cross-platform features and improve user feedback.
- Refactored SSH connection management to include additional key file types and improve authentication handling.
- Updated team management services to ensure consistent process termination across platforms.
- Improved project path handling in team provisioning to accommodate different operating systems.
- Enhanced editor components to utilize shared utility functions for path management, improving code maintainability.
2026-03-02 22:56:56 +02:00

130 lines
4.1 KiB
TypeScript

/**
* 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;
}