Add import template into tui. Fix tests that fail on macos. Fix some updates.
This commit is contained in:
121
src/tui/utils/format-dialog-message.ts
Normal file
121
src/tui/utils/format-dialog-message.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user