changes
This commit is contained in:
@@ -76,3 +76,59 @@ export function allQuestionsAnswered(questions, answers) {
|
||||
export function nextTabAfterAnswer(currentTab, questionCount) {
|
||||
return currentTab < questionCount - 1 ? currentTab + 1 : questionCount;
|
||||
}
|
||||
|
||||
function takeWrappedSegment(text, maxWidth) {
|
||||
if (text.length <= maxWidth) {
|
||||
return { line: text, rest: "" };
|
||||
}
|
||||
|
||||
let breakpoint = -1;
|
||||
for (let index = 0; index < maxWidth; index += 1) {
|
||||
if (/\s/.test(text[index])) {
|
||||
breakpoint = index;
|
||||
}
|
||||
}
|
||||
|
||||
if (breakpoint > 0) {
|
||||
return {
|
||||
line: text.slice(0, breakpoint).trimEnd(),
|
||||
rest: text.slice(breakpoint + 1).trimStart(),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
line: text.slice(0, maxWidth),
|
||||
rest: text.slice(maxWidth),
|
||||
};
|
||||
}
|
||||
|
||||
export function wrapPrefixedText(text, width, firstPrefix = "", continuationPrefix = firstPrefix) {
|
||||
const source = String(text ?? "");
|
||||
if (source.length === 0) {
|
||||
return [firstPrefix];
|
||||
}
|
||||
|
||||
const lines = [];
|
||||
const blocks = source.split(/\r?\n/);
|
||||
let isFirstLine = true;
|
||||
|
||||
for (const block of blocks) {
|
||||
let remaining = block.trim();
|
||||
if (remaining.length === 0) {
|
||||
lines.push(isFirstLine ? firstPrefix : continuationPrefix);
|
||||
isFirstLine = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
while (remaining.length > 0) {
|
||||
const prefix = isFirstLine ? firstPrefix : continuationPrefix;
|
||||
const maxTextWidth = Math.max(1, width - prefix.length);
|
||||
const { line, rest } = takeWrappedSegment(remaining, maxTextWidth);
|
||||
lines.push(prefix + line);
|
||||
remaining = rest;
|
||||
isFirstLine = false;
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
nextTabAfterAnswer,
|
||||
normalizeQuestions,
|
||||
summarizeAnswers,
|
||||
wrapPrefixedText,
|
||||
} from "./question-core.mjs";
|
||||
|
||||
test("normalizeQuestions adds default labels and appends the Something else option", () => {
|
||||
@@ -157,3 +158,23 @@ test("nextTabAfterAnswer advances through questions and then to the submit tab",
|
||||
assert.equal(nextTabAfterAnswer(1, 3), 2);
|
||||
assert.equal(nextTabAfterAnswer(2, 3), 3);
|
||||
});
|
||||
|
||||
test("wrapPrefixedText wraps long prompts and keeps the prefix on continuation lines", () => {
|
||||
assert.deepEqual(wrapPrefixedText("Pick the best rollout strategy for this change", 18, " "), [
|
||||
" Pick the best",
|
||||
" rollout strategy",
|
||||
" for this change",
|
||||
]);
|
||||
});
|
||||
|
||||
test("wrapPrefixedText supports a different continuation prefix for wrapped option labels", () => {
|
||||
assert.deepEqual(wrapPrefixedText("Very long option label", 16, "> 1. ", " "), [
|
||||
"> 1. Very long",
|
||||
" option",
|
||||
" label",
|
||||
]);
|
||||
});
|
||||
|
||||
test("wrapPrefixedText breaks oversized words when there is no whitespace boundary", () => {
|
||||
assert.deepEqual(wrapPrefixedText("supercalifragilistic", 8), ["supercal", "ifragili", "stic"]);
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
nextTabAfterAnswer,
|
||||
normalizeQuestions,
|
||||
summarizeAnswers,
|
||||
wrapPrefixedText,
|
||||
} from "./question-core.mjs";
|
||||
|
||||
interface QuestionOption {
|
||||
@@ -220,6 +221,32 @@ async function runQuestionFlow(ctx: any, questions: Question[]): Promise<Questio
|
||||
const question = currentQuestion();
|
||||
const options = currentOptions();
|
||||
|
||||
function addWrapped(text: string, color: string, firstPrefix = "", continuationPrefix = firstPrefix) {
|
||||
for (const line of wrapPrefixedText(text, width, firstPrefix, continuationPrefix)) {
|
||||
add(theme.fg(color, line));
|
||||
}
|
||||
}
|
||||
|
||||
function addWrappedOption(option: QuestionOption, index: number, selected: boolean) {
|
||||
const firstPrefix = `${selected ? "> " : " "}${index + 1}. `;
|
||||
const continuationPrefix = " ".repeat(firstPrefix.length);
|
||||
addWrapped(option.label, selected ? "accent" : "text", firstPrefix, continuationPrefix);
|
||||
if (option.description) {
|
||||
addWrapped(option.description, "muted", " ");
|
||||
}
|
||||
}
|
||||
|
||||
function addWrappedReviewAnswer(questionLabel: string, value: string) {
|
||||
const firstPrefix = ` ${questionLabel}: `;
|
||||
const continuationPrefix = " ".repeat(firstPrefix.length);
|
||||
const wrapped = wrapPrefixedText(value, width, firstPrefix, continuationPrefix);
|
||||
for (let index = 0; index < wrapped.length; index += 1) {
|
||||
const prefix = index === 0 ? firstPrefix : continuationPrefix;
|
||||
const line = wrapped[index]!;
|
||||
add(theme.fg("muted", prefix) + theme.fg("text", line.slice(prefix.length)));
|
||||
}
|
||||
}
|
||||
|
||||
add(theme.fg("accent", "─".repeat(width)));
|
||||
|
||||
if (isMulti) {
|
||||
@@ -247,19 +274,14 @@ async function runQuestionFlow(ctx: any, questions: Question[]): Promise<Questio
|
||||
}
|
||||
|
||||
if (inputMode && question) {
|
||||
add(theme.fg("text", ` ${question.prompt}`));
|
||||
addWrapped(question.prompt, "text", " ");
|
||||
lines.push("");
|
||||
for (let index = 0; index < options.length; index += 1) {
|
||||
const option = options[index]!;
|
||||
const prefix = index === optionIndex ? theme.fg("accent", "> ") : " ";
|
||||
add(prefix + theme.fg(index === optionIndex ? "accent" : "text", `${index + 1}. ${option.label}`));
|
||||
if (option.description) {
|
||||
add(` ${theme.fg("muted", option.description)}`);
|
||||
}
|
||||
addWrappedOption(options[index]!, index, index === optionIndex);
|
||||
}
|
||||
lines.push("");
|
||||
add(theme.fg("muted", " Your answer:"));
|
||||
for (const line of editor.render(width - 2)) {
|
||||
for (const line of editor.render(Math.max(1, width - 2))) {
|
||||
add(` ${line}`);
|
||||
}
|
||||
} else if (isMulti && currentTab === questions.length) {
|
||||
@@ -269,7 +291,7 @@ async function runQuestionFlow(ctx: any, questions: Question[]): Promise<Questio
|
||||
const answer = answers.get(reviewQuestion.id);
|
||||
if (!answer) continue;
|
||||
const label = answer.wasCustom ? `(wrote) ${answer.label}` : `${answer.index}. ${answer.label}`;
|
||||
add(`${theme.fg("muted", ` ${reviewQuestion.label}: `)}${theme.fg("text", label)}`);
|
||||
addWrappedReviewAnswer(reviewQuestion.label, label);
|
||||
}
|
||||
lines.push("");
|
||||
if (allQuestionsAnswered(questions, answers)) {
|
||||
@@ -278,15 +300,10 @@ async function runQuestionFlow(ctx: any, questions: Question[]): Promise<Questio
|
||||
add(theme.fg("warning", " All questions must be answered before submit"));
|
||||
}
|
||||
} else if (question) {
|
||||
add(theme.fg("text", ` ${question.prompt}`));
|
||||
addWrapped(question.prompt, "text", " ");
|
||||
lines.push("");
|
||||
for (let index = 0; index < options.length; index += 1) {
|
||||
const option = options[index]!;
|
||||
const prefix = index === optionIndex ? theme.fg("accent", "> ") : " ";
|
||||
add(prefix + theme.fg(index === optionIndex ? "accent" : "text", `${index + 1}. ${option.label}`));
|
||||
if (option.description) {
|
||||
add(` ${theme.fg("muted", option.description)}`);
|
||||
}
|
||||
addWrappedOption(options[index]!, index, index === optionIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user