(path: string, maxLength: number)
| 106 | * Width-aware: uses stringWidth() for correct CJK/emoji measurement. |
| 107 | */ |
| 108 | export function truncatePath(path: string, maxLength: number): string { |
| 109 | if (stringWidth(path) <= maxLength) return path |
| 110 | |
| 111 | const separator = '/' |
| 112 | const ellipsis = '…' |
| 113 | const ellipsisWidth = 1 // '…' is always 1 column |
| 114 | const separatorWidth = 1 |
| 115 | |
| 116 | const parts = path.split(separator) |
| 117 | const first = parts[0] || '' |
| 118 | const last = parts[parts.length - 1] || '' |
| 119 | const firstWidth = stringWidth(first) |
| 120 | const lastWidth = stringWidth(last) |
| 121 | |
| 122 | // Only one part, so show as much of it as we can |
| 123 | if (parts.length === 1) { |
| 124 | return truncateToWidth(path, maxLength) |
| 125 | } |
| 126 | |
| 127 | // We don't have enough space to show the last part, so truncate it |
| 128 | // But since firstPart is empty (unix) we don't want the extra ellipsis |
| 129 | if (first === '' && ellipsisWidth + separatorWidth + lastWidth >= maxLength) { |
| 130 | return `${separator}${truncateToWidth(last, Math.max(1, maxLength - separatorWidth))}` |
| 131 | } |
| 132 | |
| 133 | // We have a first part so let's show the ellipsis and truncate last part |
| 134 | if ( |
| 135 | first !== '' && |
| 136 | ellipsisWidth * 2 + separatorWidth + lastWidth >= maxLength |
| 137 | ) { |
| 138 | return `${ellipsis}${separator}${truncateToWidth(last, Math.max(1, maxLength - ellipsisWidth - separatorWidth))}` |
| 139 | } |
| 140 | |
| 141 | // Truncate first and leave last |
| 142 | if (parts.length === 2) { |
| 143 | const availableForFirst = |
| 144 | maxLength - ellipsisWidth - separatorWidth - lastWidth |
| 145 | return `${truncateToWidthNoEllipsis(first, availableForFirst)}${ellipsis}${separator}${last}` |
| 146 | } |
| 147 | |
| 148 | // Now we start removing middle parts |
| 149 | |
| 150 | let available = |
| 151 | maxLength - firstWidth - lastWidth - ellipsisWidth - 2 * separatorWidth |
| 152 | |
| 153 | // Just the first and last are too long, so truncate first |
| 154 | if (available <= 0) { |
| 155 | const availableForFirst = Math.max( |
| 156 | 0, |
| 157 | maxLength - lastWidth - ellipsisWidth - 2 * separatorWidth, |
| 158 | ) |
| 159 | const truncatedFirst = truncateToWidthNoEllipsis(first, availableForFirst) |
| 160 | return `${truncatedFirst}${separator}${ellipsis}${separator}${last}` |
| 161 | } |
| 162 | |
| 163 | // Try to keep as many middle parts as possible |
| 164 | const middleParts = [] |
| 165 | for (let i = parts.length - 2; i > 0; i--) { |
no test coverage detected