Skip to content

Commit f7d6845

Browse files
committed
feat: clean up project sidebar display
- better padding - abbreviate long project paths
1 parent 8bed8c9 commit f7d6845

File tree

3 files changed

+78
-6
lines changed

3 files changed

+78
-6
lines changed

src/components/ProjectSidebar.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import type { ProjectConfig } from "../config";
55
import type { WorkspaceMetadata } from "../types/workspace";
66
import { usePersistedState } from "../hooks/usePersistedState";
77
import { matchesKeybind, formatKeybind, KEYBINDS } from "../utils/ui/keybinds";
8+
import { abbreviatePath } from "../utils/ui/pathAbbreviation";
9+
import { TooltipWrapper, Tooltip } from "./Tooltip";
810

911
// Styled Components
1012
const SidebarContainer = styled.div`
@@ -196,12 +198,10 @@ const RemoveBtn = styled.button`
196198

197199
const WorkspacesContainer = styled.div`
198200
background: #1a1a1a;
199-
border-left: 1px solid #2a2a2b;
200-
margin-left: 10px;
201201
`;
202202

203203
const WorkspaceHeader = styled.div`
204-
padding: 8px 12px;
204+
padding: 8px 12px 8px 22px;
205205
border-bottom: 1px solid #2a2a2b;
206206
`;
207207

@@ -234,7 +234,7 @@ const StatusIndicator = styled.div<{ active?: boolean }>`
234234
`;
235235

236236
const WorkspaceItem = styled.div<{ selected?: boolean }>`
237-
padding: 6px 12px 6px 24px;
237+
padding: 6px 12px 6px 28px;
238238
cursor: pointer;
239239
display: flex;
240240
align-items: center;
@@ -300,7 +300,7 @@ const WorkspaceNameInput = styled.input`
300300
const RenameErrorContainer = styled.div`
301301
position: absolute;
302302
top: 100%;
303-
left: 24px;
303+
left: 28px;
304304
right: 32px;
305305
margin-top: 4px;
306306
padding: 6px 8px;
@@ -458,7 +458,12 @@ const ProjectSidebar: React.FC<ProjectSidebarProps> = ({
458458
<ExpandIcon expanded={expandedProjects.has(projectPath)}></ExpandIcon>
459459
<ProjectInfo>
460460
<ProjectName>{getProjectName(projectPath)}</ProjectName>
461-
<ProjectPath>{projectPath}</ProjectPath>
461+
<TooltipWrapper inline>
462+
<ProjectPath>{abbreviatePath(projectPath)}</ProjectPath>
463+
<Tooltip className="tooltip" align="left">
464+
{projectPath}
465+
</Tooltip>
466+
</TooltipWrapper>
462467
</ProjectInfo>
463468
<RemoveBtn
464469
onClick={(e) => {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { abbreviatePath } from "./pathAbbreviation";
2+
3+
describe("abbreviatePath", () => {
4+
it("should abbreviate all directory components except the last one", () => {
5+
expect(abbreviatePath("/Users/ammar/Projects/coder/cmux")).toBe("/U/a/P/c/cmux");
6+
});
7+
8+
it("should handle paths without leading slash", () => {
9+
expect(abbreviatePath("Users/ammar/Projects/coder/cmux")).toBe("U/a/P/c/cmux");
10+
});
11+
12+
it("should handle single directory paths", () => {
13+
expect(abbreviatePath("/Users")).toBe("/Users");
14+
expect(abbreviatePath("Users")).toBe("Users");
15+
});
16+
17+
it("should handle root path", () => {
18+
expect(abbreviatePath("/")).toBe("/");
19+
});
20+
21+
it("should handle empty string", () => {
22+
expect(abbreviatePath("")).toBe("");
23+
});
24+
25+
it("should handle paths with multiple character directories", () => {
26+
expect(abbreviatePath("/home/username/Documents/project")).toBe("/h/u/D/project");
27+
});
28+
29+
it("should preserve the full last directory name", () => {
30+
expect(abbreviatePath("/Users/ammar/very-long-project-name")).toBe(
31+
"/U/a/very-long-project-name"
32+
);
33+
});
34+
});

src/utils/ui/pathAbbreviation.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Fish-style path abbreviation utility
3+
* Abbreviates all directory components except the last one to their first letter
4+
* Example: /Users/ammar/Projects/coder/cmux -> /U/a/P/c/cmux
5+
*/
6+
export function abbreviatePath(path: string): string {
7+
if (!path || typeof path !== "string") {
8+
return path;
9+
}
10+
11+
const parts = path.split("/");
12+
13+
// Handle root path or empty parts
14+
if (parts.length <= 1) {
15+
return path;
16+
}
17+
18+
// Abbreviate all parts except the last one
19+
const abbreviated = parts.map((part, index) => {
20+
// Keep the last part full
21+
if (index === parts.length - 1) {
22+
return part;
23+
}
24+
// Keep empty parts (like leading slash)
25+
if (part === "") {
26+
return part;
27+
}
28+
// Abbreviate to first character
29+
return part[0];
30+
});
31+
32+
return abbreviated.join("/");
33+
}

0 commit comments

Comments
 (0)