fix(tui): fit project paths in open menu (#39887)
This commit is contained in:
parent
7aaf4e7750
commit
7fd12c560c
5 changed files with 52 additions and 17 deletions
|
|
@ -2,8 +2,8 @@ import { useTerminalDimensions } from "@opentui/solid"
|
|||
import { TextAttributes } from "@opentui/core"
|
||||
import { createMemo, createResource, createSignal, onMount, Show } from "solid-js"
|
||||
import path from "path"
|
||||
import { DialogSelect, type DialogSelectOption } from "../ui/dialog-select"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { DialogSelect, dialogSelectContentWidth, type DialogSelectOption } from "../ui/dialog-select"
|
||||
import { dialogWidth, useDialog } from "../ui/dialog"
|
||||
import { useClient } from "../context/client"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
|
|
@ -159,7 +159,10 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
|||
if (b.location === b.root.directory) return 1
|
||||
return a.location.localeCompare(b.location)
|
||||
})
|
||||
const titleWidth = Math.max(1, Math.min(116, dimensions().width - 2) - 12)
|
||||
const titleWidth = Math.max(
|
||||
1,
|
||||
dialogSelectContentWidth(Math.min(dialogWidth("xlarge"), dimensions().width - 2)),
|
||||
)
|
||||
|
||||
return list.map((item) => {
|
||||
const title = abbreviateHome(item.location, paths.home)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import path from "path"
|
|||
import { createMemo, createResource, createSignal, onMount } from "solid-js"
|
||||
import type { SessionInfo } from "@opencode-ai/client"
|
||||
import { useTerminalDimensions } from "@opentui/solid"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { DialogSelect } from "../ui/dialog-select"
|
||||
import { dialogWidth, useDialog } from "../ui/dialog"
|
||||
import { DialogSelect, dialogSelectContentWidth } from "../ui/dialog-select"
|
||||
import { useRoute } from "../context/route"
|
||||
import { useData } from "../context/data"
|
||||
import { useClient } from "../context/client"
|
||||
|
|
@ -105,8 +105,8 @@ export function DialogOpen() {
|
|||
.map((project) => {
|
||||
const title = project.name ?? path.basename(project.canonical)
|
||||
const footer = abbreviateHome(project.canonical, paths.home)
|
||||
// Dialog padding, the gutter column, title padding, and the separating space use nine columns.
|
||||
const width = Math.min(60, dimensions().width - 2) - 9 - stringWidth(title)
|
||||
const width =
|
||||
dialogSelectContentWidth(Math.min(dialogWidth("large"), dimensions().width - 2)) - stringWidth(title)
|
||||
return {
|
||||
title,
|
||||
footer: truncateFilePath(footer, width),
|
||||
|
|
|
|||
|
|
@ -83,6 +83,11 @@ export interface DialogSelectOption<T = any> {
|
|||
onSelect?: (ctx: DialogContext) => void
|
||||
}
|
||||
|
||||
export function dialogSelectContentWidth(dialogWidth: number) {
|
||||
// Scroll padding, row padding, the gutter, title padding, and the separating gap.
|
||||
return dialogWidth - 12
|
||||
}
|
||||
|
||||
export type DialogSelectRef<T> = {
|
||||
filter: string
|
||||
filtered: DialogSelectOption<T>[]
|
||||
|
|
|
|||
|
|
@ -8,9 +8,17 @@ import { useToast } from "./toast"
|
|||
import { useClipboard } from "../context/clipboard"
|
||||
import { useConfig } from "../config"
|
||||
|
||||
export type DialogSize = "medium" | "large" | "xlarge"
|
||||
|
||||
export function dialogWidth(size: DialogSize) {
|
||||
if (size === "xlarge") return 116
|
||||
if (size === "large") return 88
|
||||
return 60
|
||||
}
|
||||
|
||||
export function Dialog(
|
||||
props: ParentProps<{
|
||||
size?: "medium" | "large" | "xlarge"
|
||||
size?: DialogSize
|
||||
centered?: boolean
|
||||
onClose: () => void
|
||||
}>,
|
||||
|
|
@ -20,12 +28,6 @@ export function Dialog(
|
|||
const renderer = useRenderer()
|
||||
|
||||
let dismiss = false
|
||||
const width = () => {
|
||||
if (props.size === "xlarge") return 116
|
||||
if (props.size === "large") return 88
|
||||
return 60
|
||||
}
|
||||
|
||||
return (
|
||||
<box
|
||||
onMouseDown={() => {
|
||||
|
|
@ -57,7 +59,7 @@ export function Dialog(
|
|||
dismiss = false
|
||||
e.stopPropagation()
|
||||
}}
|
||||
width={width()}
|
||||
width={dialogWidth(props.size ?? "medium")}
|
||||
maxWidth={dimensions().width - 2}
|
||||
backgroundColor={theme.background.default}
|
||||
paddingTop={1}
|
||||
|
|
@ -74,7 +76,7 @@ function init() {
|
|||
element: JSX.Element
|
||||
onClose?: () => void
|
||||
}[],
|
||||
size: "medium" as "medium" | "large" | "xlarge",
|
||||
size: "medium" as DialogSize,
|
||||
centered: false,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import { expect, test } from "bun:test"
|
|||
import { mkdir } from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
import { createSignal, onCleanup, onMount } from "solid-js"
|
||||
import type { DialogSelectOption } from "../../../src/ui/dialog-select"
|
||||
import { dialogWidth } from "../../../src/ui/dialog"
|
||||
import { dialogSelectContentWidth, type DialogSelectOption } from "../../../src/ui/dialog-select"
|
||||
import { truncateFilePath } from "../../../src/ui/file-path"
|
||||
import { stringWidth } from "../../../src/util/string-width"
|
||||
import { tmpdir } from "../../fixture/fixture"
|
||||
import { TestTuiContexts } from "../../fixture/tui-environment"
|
||||
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||
|
|
@ -147,6 +150,28 @@ async function mountSelect(root: string, initial: DialogSelectOption<string>[],
|
|||
return { app, moved, replaceOptions, selected }
|
||||
}
|
||||
|
||||
test("budgets option content for constrained and full-width large dialogs", () => {
|
||||
expect(dialogSelectContentWidth(Math.min(dialogWidth("large"), 62 - 2)) - 7).toBe(41)
|
||||
expect(dialogSelectContentWidth(Math.min(dialogWidth("large"), 100 - 2)) - 7).toBe(69)
|
||||
})
|
||||
|
||||
test("renders the complete truncated footer within the option row", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const title = "Project"
|
||||
const footer = truncateFilePath(
|
||||
"/tmp/opencode/projects/a-very-long-project-directory/distinctive-tail.tsx",
|
||||
dialogSelectContentWidth(dialogWidth("medium")) - stringWidth(title),
|
||||
)
|
||||
const select = await mountSelect(tmp.path, [{ title, footer, value: "project" }])
|
||||
|
||||
try {
|
||||
await select.app.waitForFrame((frame) => frame.includes(footer))
|
||||
expect(select.app.captureCharFrame()).toContain(footer)
|
||||
} finally {
|
||||
select.app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
test("renders actions with a current selection", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const app = await renderSelect(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue