/** * Formats multi-line dialog messages for readable terminal display. * * Ink's `wrap="wrap"` breaks long lines mid-word, which looks broken for * dot-separated template validation paths. We pre-split on newlines and break * long lines at `.` segment boundaries instead. */ /** * Hard-wraps text when a single segment still exceeds the maximum width. */ function hardWrapLine(line: string, maxWidth: number): string[] { if (line.length <= maxWidth) { return [line]; } const wrapped: string[] = []; let remaining = line; while (remaining.length > maxWidth) { wrapped.push(remaining.slice(0, maxWidth)); remaining = ` ${remaining.slice(maxWidth)}`; } if (remaining.length > 0) { wrapped.push(remaining); } return wrapped; } /** * Breaks a long line at dot-separated segments, indenting continuations. */ function breakLongLineAtDots(line: string, maxWidth: number): string[] { const segments: string[] = []; let segmentStart = 0; for (let index = 0; index < line.length; index += 1) { if (line[index] === "." && index > 0) { segments.push(line.slice(segmentStart, index + 1)); segmentStart = index + 1; } } if (segmentStart < line.length) { segments.push(line.slice(segmentStart)); } if (segments.length === 0) { return hardWrapLine(line, maxWidth); } const lines: string[] = []; let current = ""; for (const segment of segments) { const candidate = current + segment; if (candidate.length > maxWidth && current.length > 0) { lines.push(current); current = ` ${segment}`; continue; } if (candidate.length > maxWidth) { lines.push(...hardWrapLine(segment, maxWidth)); current = ""; continue; } current = candidate; } if (current.length > 0) { lines.push(current); } return lines; } /** * Splits a dialog message into display lines that fit the available width. */ export function formatDialogMessageLines( message: string, contentWidth: number, ): string[] { const output: string[] = []; for (const rawLine of message.split("\n")) { const line = rawLine.trimEnd(); if (line.length === 0) { continue; } if (line.length <= contentWidth) { output.push(line); continue; } output.push(...breakLongLineAtDots(line, contentWidth)); } return output; } /** * Computes dialog width from the terminal size. */ export function getMessageDialogWidth(terminalColumns: number): number { return Math.min(Math.max(terminalColumns - 4, 60), 100); } /** Inner text width after dialog border and horizontal padding. */ export function getMessageContentWidth(dialogWidth: number): number { return Math.max(dialogWidth - 6, 40); } /** Maximum number of body lines shown before truncating with a summary. */ export const MAX_MESSAGE_DIALOG_LINES = 24;