Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10855,19 +10855,42 @@ private struct SidebarEmptyArea: View {
enum SidebarPathFormatter {
static let homeDirectoryPath: String = FileManager.default.homeDirectoryForCurrentUser.path

/// Maximum number of path segments shown before adding a leading ellipsis.
/// e.g. `~/a/b/c/d` → `…/b/c/d` when maxSegments == 3.
static let maxDisplaySegments: Int = 3
Comment on lines +10858 to +10860

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Doc-comment example contradicts the actual constant value

The example in the doc comment shows …/c/d which implies only 2 tail segments are kept (maxSegments == 2), but the constant below it is set to 3. With maxDisplaySegments = 3, the real output for ~/a/b/c/d is …/b/c/d (three segments), not …/c/d (two segments). The example was apparently written for a different threshold and then not updated when the constant was changed to 3.

Suggested change
/// Maximum number of path segments shown before adding a leading ellipsis.
/// e.g. `~/a/b/c/d` → `…/c/d` when maxSegments == 2.
static let maxDisplaySegments: Int = 3
/// Maximum number of path segments shown before adding a leading ellipsis.
/// e.g. `~/a/b/c/d` → `…/b/c/d` when maxDisplaySegments == 3.
static let maxDisplaySegments: Int = 3


static func shortenedPath(
_ path: String,
homeDirectoryPath: String = Self.homeDirectoryPath
) -> String {
let trimmed = path.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return path }

// Replace home directory prefix with ~
let tildeReplaced: String
if trimmed == homeDirectoryPath {
return "~"
}
if trimmed.hasPrefix(homeDirectoryPath + "/") {
return "~" + trimmed.dropFirst(homeDirectoryPath.count)
}
return trimmed
} else if trimmed.hasPrefix(homeDirectoryPath + "/") {
tildeReplaced = "~" + trimmed.dropFirst(homeDirectoryPath.count)
} else {
tildeReplaced = trimmed
}

// Apply smart truncation: keep only the last maxDisplaySegments segments
// so paths with a long common prefix remain distinguishable in the sidebar.
// e.g. `~/Desktop/YOKE/Claude Code/Projects/Athlete Merch/nilclub`
// → `…/Projects/Athlete Merch/nilclub`
let segments = tildeReplaced.split(separator: "/", omittingEmptySubsequences: false)
// For a tilde-replaced path like `~/a/b/c`, split on "/" yields
// ["~", "a", "b", "c"]. The tilde token is always a single leading segment.
// For an absolute path like `/tmp/a/b`, split yields ["", "tmp", "a", "b"].
// We only truncate when there are strictly more segments than allowed.
let effectiveMax = maxDisplaySegments + 1 // +1 for the leading "~" or "" token
guard segments.count > effectiveMax else {
return tildeReplaced
}
let tail = segments.suffix(maxDisplaySegments).joined(separator: "/")
return "…/" + tail
}
}

Expand Down
79 changes: 79 additions & 0 deletions cmuxTests/GhosttyConfigTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,85 @@ final class SidebarPathFormatterTests: XCTestCase {
"/tmp/cmux"
)
}

// MARK: - Smart truncation (issue #2061)

func testShortenedPathKeepsThreeSegmentsUnchanged() {
// ~/a/b/c — exactly maxDisplaySegments; no ellipsis added
XCTAssertEqual(
SidebarPathFormatter.shortenedPath(
"/Users/example/a/b/c",
homeDirectoryPath: "/Users/example"
),
"~/a/b/c"
)
}

func testShortenedPathTruncatesLongHomePath() {
// ~/Desktop/YOKE/Claude Code/Projects/Athlete Merch/nilclub → …/Projects/Athlete Merch/nilclub
XCTAssertEqual(
SidebarPathFormatter.shortenedPath(
"/Users/example/Desktop/YOKE/Claude Code/Projects/Athlete Merch/nilclub",
homeDirectoryPath: "/Users/example"
),
"…/Projects/Athlete Merch/nilclub"
)
}

func testShortenedPathTruncatesFourSegmentHomePath() {
// ~/a/b/c/d — one segment over limit → …/b/c/d
XCTAssertEqual(
SidebarPathFormatter.shortenedPath(
"/Users/example/a/b/c/d",
homeDirectoryPath: "/Users/example"
),
"…/b/c/d"
)
}

func testShortenedPathLeavesShortHomePathUnchanged() {
// ~/projects/cmux — only 2 segments; no ellipsis
XCTAssertEqual(
SidebarPathFormatter.shortenedPath(
"/Users/example/projects/cmux",
homeDirectoryPath: "/Users/example"
),
"~/projects/cmux"
)
}

func testShortenedPathTruncatesLongAbsolutePath() {
// /a/b/c/d/e — 5 segments (no home match) → …/c/d/e
XCTAssertEqual(
SidebarPathFormatter.shortenedPath(
"/a/b/c/d/e",
homeDirectoryPath: "/Users/example"
),
"…/c/d/e"
)
}

func testShortenedPathLeavesShortAbsolutePathUnchanged() {
// /tmp/a/b — exactly 3 segments; no truncation
XCTAssertEqual(
SidebarPathFormatter.shortenedPath(
"/tmp/a/b",
homeDirectoryPath: "/Users/example"
),
"/tmp/a/b"
)
}

func testShortenedPathTruncatesLongAbsolutePathBeyondThree() {
// /tmp/a/b/c — 4 segments; triggers truncation → …/a/b/c
XCTAssertEqual(
SidebarPathFormatter.shortenedPath(
"/tmp/a/b/c",
homeDirectoryPath: "/Users/example"
),
"…/a/b/c"
)
}
}

final class GhosttyConfigTests: XCTestCase {
Expand Down