feat(examples): Wrap question extension text instead of truncating (#5708)
This commit is contained in:
@@ -5,7 +5,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||||
import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth } from "@earendil-works/pi-tui";
|
import {
|
||||||
|
Editor,
|
||||||
|
type EditorTheme,
|
||||||
|
Key,
|
||||||
|
matchesKey,
|
||||||
|
Text,
|
||||||
|
visibleWidth,
|
||||||
|
wrapTextWithAnsi,
|
||||||
|
} from "@earendil-works/pi-tui";
|
||||||
import { Type } from "typebox";
|
import { Type } from "typebox";
|
||||||
|
|
||||||
interface OptionWithDesc {
|
interface OptionWithDesc {
|
||||||
@@ -139,10 +147,27 @@ export default function question(pi: ExtensionAPI) {
|
|||||||
if (cachedLines) return cachedLines;
|
if (cachedLines) return cachedLines;
|
||||||
|
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
const add = (s: string) => lines.push(truncateToWidth(s, width));
|
const renderWidth = Math.max(1, width);
|
||||||
|
|
||||||
add(theme.fg("accent", "─".repeat(width)));
|
function addWrapped(text: string) {
|
||||||
add(theme.fg("text", ` ${params.question}`));
|
lines.push(...wrapTextWithAnsi(text, renderWidth));
|
||||||
|
}
|
||||||
|
|
||||||
|
function addWrappedWithPrefix(prefix: string, text: string) {
|
||||||
|
const prefixWidth = visibleWidth(prefix);
|
||||||
|
if (prefixWidth >= renderWidth) {
|
||||||
|
addWrapped(prefix + text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const wrapped = wrapTextWithAnsi(text, renderWidth - prefixWidth);
|
||||||
|
const continuationPrefix = " ".repeat(prefixWidth);
|
||||||
|
for (let i = 0; i < wrapped.length; i++) {
|
||||||
|
lines.push(`${i === 0 ? prefix : continuationPrefix}${wrapped[i]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(theme.fg("accent", "─".repeat(renderWidth)));
|
||||||
|
addWrappedWithPrefix(" ", theme.fg("text", params.question));
|
||||||
lines.push("");
|
lines.push("");
|
||||||
|
|
||||||
for (let i = 0; i < allOptions.length; i++) {
|
for (let i = 0; i < allOptions.length; i++) {
|
||||||
@@ -150,36 +175,32 @@ export default function question(pi: ExtensionAPI) {
|
|||||||
const selected = i === optionIndex;
|
const selected = i === optionIndex;
|
||||||
const isOther = opt.isOther === true;
|
const isOther = opt.isOther === true;
|
||||||
const prefix = selected ? theme.fg("accent", "> ") : " ";
|
const prefix = selected ? theme.fg("accent", "> ") : " ";
|
||||||
|
const label = `${i + 1}. ${opt.label}${isOther && editMode ? " ✎" : ""}`;
|
||||||
|
const color = selected || (isOther && editMode) ? "accent" : "text";
|
||||||
|
|
||||||
if (isOther && editMode) {
|
addWrappedWithPrefix(prefix, theme.fg(color, label));
|
||||||
add(prefix + theme.fg("accent", `${i + 1}. ${opt.label} ✎`));
|
|
||||||
} else if (selected) {
|
|
||||||
add(prefix + theme.fg("accent", `${i + 1}. ${opt.label}`));
|
|
||||||
} else {
|
|
||||||
add(` ${theme.fg("text", `${i + 1}. ${opt.label}`)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show description if present
|
// Show description if present
|
||||||
if (opt.description) {
|
if (opt.description) {
|
||||||
add(` ${theme.fg("muted", opt.description)}`);
|
addWrappedWithPrefix(" ", theme.fg("muted", opt.description));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editMode) {
|
if (editMode) {
|
||||||
lines.push("");
|
lines.push("");
|
||||||
add(theme.fg("muted", " Your answer:"));
|
addWrappedWithPrefix(" ", theme.fg("muted", "Your answer:"));
|
||||||
for (const line of editor.render(width - 2)) {
|
for (const line of editor.render(Math.max(1, renderWidth - 2))) {
|
||||||
add(` ${line}`);
|
lines.push(` ${line}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push("");
|
lines.push("");
|
||||||
if (editMode) {
|
if (editMode) {
|
||||||
add(theme.fg("dim", " Enter to submit • Esc to go back"));
|
addWrappedWithPrefix(" ", theme.fg("dim", "Enter to submit • Esc to go back"));
|
||||||
} else {
|
} else {
|
||||||
add(theme.fg("dim", " ↑↓ navigate • Enter to select • Esc to cancel"));
|
addWrappedWithPrefix(" ", theme.fg("dim", "↑↓ navigate • Enter to select • Esc to cancel"));
|
||||||
}
|
}
|
||||||
add(theme.fg("accent", "─".repeat(width)));
|
lines.push(theme.fg("accent", "─".repeat(renderWidth)));
|
||||||
|
|
||||||
cachedLines = lines;
|
cachedLines = lines;
|
||||||
return lines;
|
return lines;
|
||||||
|
|||||||
@@ -6,7 +6,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||||
import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth } from "@earendil-works/pi-tui";
|
import {
|
||||||
|
Editor,
|
||||||
|
type EditorTheme,
|
||||||
|
Key,
|
||||||
|
matchesKey,
|
||||||
|
Text,
|
||||||
|
visibleWidth,
|
||||||
|
wrapTextWithAnsi,
|
||||||
|
} from "@earendil-works/pi-tui";
|
||||||
import { Type } from "typebox";
|
import { Type } from "typebox";
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
@@ -259,13 +267,28 @@ export default function questionnaire(pi: ExtensionAPI) {
|
|||||||
if (cachedLines) return cachedLines;
|
if (cachedLines) return cachedLines;
|
||||||
|
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
|
const renderWidth = Math.max(1, width);
|
||||||
const q = currentQuestion();
|
const q = currentQuestion();
|
||||||
const opts = currentOptions();
|
const opts = currentOptions();
|
||||||
|
|
||||||
// Helper to add truncated line
|
function addWrapped(text: string) {
|
||||||
const add = (s: string) => lines.push(truncateToWidth(s, width));
|
lines.push(...wrapTextWithAnsi(text, renderWidth));
|
||||||
|
}
|
||||||
|
|
||||||
add(theme.fg("accent", "─".repeat(width)));
|
function addWrappedWithPrefix(prefix: string, text: string) {
|
||||||
|
const prefixWidth = visibleWidth(prefix);
|
||||||
|
if (prefixWidth >= renderWidth) {
|
||||||
|
addWrapped(prefix + text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const wrapped = wrapTextWithAnsi(text, renderWidth - prefixWidth);
|
||||||
|
const continuationPrefix = " ".repeat(prefixWidth);
|
||||||
|
for (let i = 0; i < wrapped.length; i++) {
|
||||||
|
lines.push(`${i === 0 ? prefix : continuationPrefix}${wrapped[i]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(theme.fg("accent", "─".repeat(renderWidth)));
|
||||||
|
|
||||||
// Tab bar (multi-question only)
|
// Tab bar (multi-question only)
|
||||||
if (isMulti) {
|
if (isMulti) {
|
||||||
@@ -287,7 +310,7 @@ export default function questionnaire(pi: ExtensionAPI) {
|
|||||||
? theme.bg("selectedBg", theme.fg("text", submitText))
|
? theme.bg("selectedBg", theme.fg("text", submitText))
|
||||||
: theme.fg(canSubmit ? "success" : "dim", submitText);
|
: theme.fg(canSubmit ? "success" : "dim", submitText);
|
||||||
tabs.push(`${submitStyled} →`);
|
tabs.push(`${submitStyled} →`);
|
||||||
add(` ${tabs.join("")}`);
|
addWrappedWithPrefix(" ", tabs.join(""));
|
||||||
lines.push("");
|
lines.push("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,54 +321,52 @@ export default function questionnaire(pi: ExtensionAPI) {
|
|||||||
const selected = i === optionIndex;
|
const selected = i === optionIndex;
|
||||||
const isOther = opt.isOther === true;
|
const isOther = opt.isOther === true;
|
||||||
const prefix = selected ? theme.fg("accent", "> ") : " ";
|
const prefix = selected ? theme.fg("accent", "> ") : " ";
|
||||||
const color = selected ? "accent" : "text";
|
const label = `${i + 1}. ${opt.label}${isOther && inputMode ? " ✎" : ""}`;
|
||||||
// Mark "Type something" differently when in input mode
|
const color = selected || (isOther && inputMode) ? "accent" : "text";
|
||||||
if (isOther && inputMode) {
|
|
||||||
add(prefix + theme.fg("accent", `${i + 1}. ${opt.label} ✎`));
|
addWrappedWithPrefix(prefix, theme.fg(color, label));
|
||||||
} else {
|
|
||||||
add(prefix + theme.fg(color, `${i + 1}. ${opt.label}`));
|
|
||||||
}
|
|
||||||
if (opt.description) {
|
if (opt.description) {
|
||||||
add(` ${theme.fg("muted", opt.description)}`);
|
addWrappedWithPrefix(" ", theme.fg("muted", opt.description));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
if (inputMode && q) {
|
if (inputMode && q) {
|
||||||
add(theme.fg("text", ` ${q.prompt}`));
|
addWrappedWithPrefix(" ", theme.fg("text", q.prompt));
|
||||||
lines.push("");
|
lines.push("");
|
||||||
// Show options for reference
|
// Show options for reference
|
||||||
renderOptions();
|
renderOptions();
|
||||||
lines.push("");
|
lines.push("");
|
||||||
add(theme.fg("muted", " Your answer:"));
|
addWrappedWithPrefix(" ", theme.fg("muted", "Your answer:"));
|
||||||
for (const line of editor.render(width - 2)) {
|
for (const line of editor.render(Math.max(1, renderWidth - 2))) {
|
||||||
add(` ${line}`);
|
lines.push(` ${line}`);
|
||||||
}
|
}
|
||||||
lines.push("");
|
lines.push("");
|
||||||
add(theme.fg("dim", " Enter to submit • Esc to cancel"));
|
addWrappedWithPrefix(" ", theme.fg("dim", "Enter to submit • Esc to cancel"));
|
||||||
} else if (currentTab === questions.length) {
|
} else if (currentTab === questions.length) {
|
||||||
add(theme.fg("accent", theme.bold(" Ready to submit")));
|
addWrappedWithPrefix(" ", theme.fg("accent", theme.bold("Ready to submit")));
|
||||||
lines.push("");
|
lines.push("");
|
||||||
for (const question of questions) {
|
for (const question of questions) {
|
||||||
const answer = answers.get(question.id);
|
const answer = answers.get(question.id);
|
||||||
if (answer) {
|
if (answer) {
|
||||||
const prefix = answer.wasCustom ? "(wrote) " : "";
|
const prefix = answer.wasCustom ? "(wrote) " : "";
|
||||||
add(`${theme.fg("muted", ` ${question.label}: `)}${theme.fg("text", prefix + answer.label)}`);
|
const summary = `${theme.fg("muted", `${question.label}: `)}${theme.fg("text", prefix + answer.label)}`;
|
||||||
|
addWrappedWithPrefix(" ", summary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lines.push("");
|
lines.push("");
|
||||||
if (allAnswered()) {
|
if (allAnswered()) {
|
||||||
add(theme.fg("success", " Press Enter to submit"));
|
addWrappedWithPrefix(" ", theme.fg("success", "Press Enter to submit"));
|
||||||
} else {
|
} else {
|
||||||
const missing = questions
|
const missing = questions
|
||||||
.filter((q) => !answers.has(q.id))
|
.filter((q) => !answers.has(q.id))
|
||||||
.map((q) => q.label)
|
.map((q) => q.label)
|
||||||
.join(", ");
|
.join(", ");
|
||||||
add(theme.fg("warning", ` Unanswered: ${missing}`));
|
addWrappedWithPrefix(" ", theme.fg("warning", `Unanswered: ${missing}`));
|
||||||
}
|
}
|
||||||
} else if (q) {
|
} else if (q) {
|
||||||
add(theme.fg("text", ` ${q.prompt}`));
|
addWrappedWithPrefix(" ", theme.fg("text", q.prompt));
|
||||||
lines.push("");
|
lines.push("");
|
||||||
renderOptions();
|
renderOptions();
|
||||||
}
|
}
|
||||||
@@ -353,11 +374,11 @@ export default function questionnaire(pi: ExtensionAPI) {
|
|||||||
lines.push("");
|
lines.push("");
|
||||||
if (!inputMode) {
|
if (!inputMode) {
|
||||||
const help = isMulti
|
const help = isMulti
|
||||||
? " Tab/←→ navigate • ↑↓ select • Enter confirm • Esc cancel"
|
? "Tab/←→ navigate • ↑↓ select • Enter confirm • Esc cancel"
|
||||||
: " ↑↓ navigate • Enter select • Esc cancel";
|
: "↑↓ navigate • Enter select • Esc cancel";
|
||||||
add(theme.fg("dim", help));
|
addWrappedWithPrefix(" ", theme.fg("dim", help));
|
||||||
}
|
}
|
||||||
add(theme.fg("accent", "─".repeat(width)));
|
lines.push(theme.fg("accent", "─".repeat(renderWidth)));
|
||||||
|
|
||||||
cachedLines = lines;
|
cachedLines = lines;
|
||||||
return lines;
|
return lines;
|
||||||
@@ -400,7 +421,7 @@ export default function questionnaire(pi: ExtensionAPI) {
|
|||||||
let text = theme.fg("toolTitle", theme.bold("questionnaire "));
|
let text = theme.fg("toolTitle", theme.bold("questionnaire "));
|
||||||
text += theme.fg("muted", `${count} question${count !== 1 ? "s" : ""}`);
|
text += theme.fg("muted", `${count} question${count !== 1 ? "s" : ""}`);
|
||||||
if (labels) {
|
if (labels) {
|
||||||
text += theme.fg("dim", ` (${truncateToWidth(labels, 40)})`);
|
text += theme.fg("dim", ` (${labels})`);
|
||||||
}
|
}
|
||||||
return new Text(text, 0, 0);
|
return new Text(text, 0, 0);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user