fix(ask): used full dialog width for wrapped labels

This commit is contained in:
can1357
2026-08-16 01:51:38 +02:00
parent 565a21bfb4
commit 5cc9aa74bc
2 changed files with 19 additions and 10 deletions
@@ -317,14 +317,12 @@ function renderRowLabel(
const cursor = selected ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
const label = renderInlineMarkdown(rowItem.label, mdTheme, t => theme.fg(color, t));
const noteMarker = state.note && state.noteRowKey === rowItem.key ? theme.fg("success", " ✎ note") : "";
// Wrap the label onto continuation lines indented under the marker so the
// cursor stays visually anchored and the full label stays readable (the
// body ScrollView carries vertical overflow). The note marker is reserved
// on the first line; the label wraps to the remaining width. The row()
// chrome (borders + insets) consumes 4 columns, so the label must fit
// `width - 4` or the outer fit() re-truncates it with an ellipsis.
// `width` is already the inner content width consumed by row(); when a
// scrollbar is needed, renderRows() calls this again with one less column.
// Keep the cursor, option marker, first wrapped label line, and optional
// note marker within that budget so the outer fit() never truncates them.
const noteWidth = noteMarker ? visibleWidth(noteMarker) : 0;
const labelWidth = Math.max(1, width - 4 - visibleWidth(cursor) - visibleWidth(marker) - noteWidth);
const labelWidth = Math.max(1, width - visibleWidth(cursor) - visibleWidth(marker) - noteWidth);
const wrappedLabel = wrapTextWithAnsi(label, labelWidth);
const indent = padding(visibleWidth(cursor) + visibleWidth(marker));
const lines = [`${cursor}${marker}${wrappedLabel[0] ?? ""}${noteMarker}`];
@@ -1252,7 +1252,8 @@ describe("AskDialogComponent", () => {
it("wraps long option labels onto indented continuation lines instead of truncating", () => {
const onSubmit = vi.fn();
const longLabel = "This is a deliberately long option label ".repeat(4).trim();
const tail = "UNIQUE_TAIL_MARKER_8654";
const longLabel = `${"This is a deliberately long option label ".repeat(4)}${tail}`;
const questions: ExtensionAskDialogQuestion[] = [
{
id: "q1",
@@ -1268,8 +1269,8 @@ describe("AskDialogComponent", () => {
});
const output = render(component);
// The label tail must be present — no ellipsis truncation.
expect(output).toContain("deliberately long option label");
// The unique label tail must be present — no ellipsis truncation.
expect(output).toContain(tail);
expect(output).not.toContain("…");
// The first line carries the cursor glyph; continuation lines are
// indented under the marker so the cursor stays visually anchored.
@@ -1280,6 +1281,16 @@ describe("AskDialogComponent", () => {
expect(continuation).toMatch(/│ {3}/);
});
it("does not wrap an option label that fits the dialog content width", () => {
const component = new AskDialogComponent(
[{ id: "q1", question: "Pick one?", options: [{ label: "x".repeat(70) }] }],
{ onSubmit: vi.fn(), onCancel: vi.fn(), onPrompt: vi.fn() },
);
const output = render(component);
expect(output.split("\n").filter(line => line.includes("x"))).toHaveLength(1);
});
it("Other editor cancel returns to the option list without submitting", async () => {
const onPrompt = vi.fn().mockReturnValue(Promise.resolve(undefined));
const onSubmit = vi.fn();