diff --git a/packages/tui/src/component/dialog-project.tsx b/packages/tui/src/component/dialog-project.tsx index 71378a88729..5a468db3d4e 100644 --- a/packages/tui/src/component/dialog-project.tsx +++ b/packages/tui/src/component/dialog-project.tsx @@ -8,6 +8,9 @@ import { abbreviateHome } from "../runtime" import { useTuiPaths } from "../context/runtime" import { useLocation } from "../context/location" import { useToast } from "../ui/toast" +import { useTerminalDimensions } from "@opentui/solid" +import { truncateFilePath } from "../ui/file-path" +import { stringWidth } from "../util/string-width" export function DialogProject() { const dialog = useDialog() @@ -16,6 +19,7 @@ export function DialogProject() { const paths = useTuiPaths() const location = useLocation() const toast = useToast() + const dimensions = useTerminalDimensions() data.project.invalidate() void data.project.sync().catch(toast.error) @@ -36,12 +40,19 @@ export function DialogProject() { if (b.id === current()?.id) return 1 return 0 }) - .map((project) => ({ - title: project.name ?? path.basename(project.canonical), - description: abbreviateHome(project.canonical, paths.home), - value: project.canonical, - category: project.id === current()?.id ? "Current" : "Projects", - })) + .map((project) => { + const title = project.name ?? path.basename(project.canonical) + const description = abbreviateHome(project.canonical, paths.home) + // Dialog padding, the current marker, title padding, and the separating space use nine columns. + const width = Math.min(60, dimensions().width - 2) - 9 - stringWidth(title) + return { + title, + description: truncateFilePath(description, width), + searchText: description, + value: project.canonical, + category: project.id === current()?.id ? "Current" : "Projects", + } + }) }) return ( diff --git a/packages/tui/src/ui/file-path.tsx b/packages/tui/src/ui/file-path.tsx index c9539dd8282..09b38066707 100644 --- a/packages/tui/src/ui/file-path.tsx +++ b/packages/tui/src/ui/file-path.tsx @@ -62,9 +62,14 @@ export function truncateFilePath(value: string, maxWidth: number) { const separatorWidth = stringWidth(separator) let width = stringWidth(prefix + basename) for (let index = segments.length - 2; index >= 0; index--) { - const next = stringWidth(segments[index]!) + separatorWidth - if (width + next > maxWidth) break - selected.unshift(segments[index]!) + const segment = segments[index]! + const next = stringWidth(segment) + separatorWidth + if (width + next > maxWidth) { + const available = maxWidth - width - separatorWidth + if (available > 1) selected.unshift(takeStart(segment, available - 1) + "…") + break + } + selected.unshift(segment) width += next } return prefix + selected.join(separator) diff --git a/packages/tui/test/ui/file-path.test.ts b/packages/tui/test/ui/file-path.test.ts index 05d2dbe3e26..48d60cabaf9 100644 --- a/packages/tui/test/ui/file-path.test.ts +++ b/packages/tui/test/ui/file-path.test.ts @@ -14,6 +14,11 @@ describe("truncateFilePath", () => { expect(truncateFilePath(path, 19)).toBe("…/dialog-select.tsx") }) + test("uses remaining width for part of a long parent segment", () => { + const path = "/private/var/folders/run-17f048ec-dbb2-4b36-860c-98637bb51a8d/files" + expect(truncateFilePath(path, 40)).toBe("/…/run-17f048ec-dbb2-4b36-860c-98…/files") + }) + test("preserves the extension when the basename must shrink", () => { expect(truncateFilePath(path, 16)).toBe("…/dialog-se….tsx") expect(truncateFilePath("dialog-select.tsx", 12)).toBe("dialog-….tsx")