chore(ask): reorganize docs and fix repo checks
This commit is contained in:
+39
-38
@@ -1,28 +1,30 @@
|
||||
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
||||
import { Editor, type EditorTheme } from "@mariozechner/pi-tui";
|
||||
import { createInitialState } from "../state/create.ts";
|
||||
import { toAskResult } from "../state/result.ts";
|
||||
import {
|
||||
applyNumberShortcut,
|
||||
cancelFlow,
|
||||
confirmCurrentSelection,
|
||||
createInitialState,
|
||||
enterInputMode,
|
||||
enterOptionNoteMode,
|
||||
enterQuestionNoteMode,
|
||||
getAnswer,
|
||||
getCurrentOption,
|
||||
getCurrentQuestion,
|
||||
getOptionNote,
|
||||
getQuestionNote,
|
||||
isEditingView,
|
||||
isSubmitTab,
|
||||
} from "../state/selectors.ts";
|
||||
import {
|
||||
applyNumberShortcut,
|
||||
cancelFlow,
|
||||
confirmCurrentSelection,
|
||||
enterInputMode,
|
||||
enterOptionNoteMode,
|
||||
enterQuestionNoteMode,
|
||||
moveOption,
|
||||
moveTab,
|
||||
saveCustomAnswer,
|
||||
saveNote,
|
||||
submitCustomAnswer,
|
||||
toAskResult,
|
||||
toggleCurrentMultiOption,
|
||||
} from "../state.ts";
|
||||
} from "../state/transitions.ts";
|
||||
import { isEditingView } from "../state/view.ts";
|
||||
import type { AskParams, AskResult, AskState } from "../types.ts";
|
||||
import type { AskInputCommand } from "./input.ts";
|
||||
import { getInputCommand } from "./input.ts";
|
||||
@@ -38,21 +40,21 @@ type Keybindings = CustomCallbackArgs[2];
|
||||
type Done = (result: AskResult) => void;
|
||||
|
||||
interface AskFlowController {
|
||||
state: AskState;
|
||||
cachedLines: string[] | undefined;
|
||||
suppressAutoInputForSelection: boolean;
|
||||
tui: Tui;
|
||||
theme: Theme;
|
||||
editor: Editor;
|
||||
done: Done;
|
||||
editor: Editor;
|
||||
state: AskState;
|
||||
suppressAutoInputForSelection: boolean;
|
||||
theme: Theme;
|
||||
tui: Tui;
|
||||
}
|
||||
|
||||
export async function runAskFlow(
|
||||
export function runAskFlow(
|
||||
ctx: ExtensionContext,
|
||||
params: AskParams,
|
||||
params: AskParams
|
||||
): Promise<AskResult> {
|
||||
return ctx.ui.custom<AskResult>((...args) =>
|
||||
createAskFlowController(args, params),
|
||||
createAskFlowController(args, params)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,16 +65,16 @@ function createAskFlowController(
|
||||
Keybindings,
|
||||
(result: AskResult) => void,
|
||||
],
|
||||
params: AskParams,
|
||||
params: AskParams
|
||||
) {
|
||||
const controller: AskFlowController = {
|
||||
state: createInitialState(params),
|
||||
cachedLines: undefined,
|
||||
suppressAutoInputForSelection: false,
|
||||
tui,
|
||||
theme,
|
||||
editor: createEditor(tui, theme),
|
||||
done,
|
||||
editor: createEditor(tui, theme),
|
||||
state: createInitialState(params),
|
||||
suppressAutoInputForSelection: false,
|
||||
theme,
|
||||
tui,
|
||||
};
|
||||
|
||||
controller.editor.onSubmit = (value) => submitEditor(controller, value);
|
||||
@@ -91,14 +93,14 @@ function createAskFlowController(
|
||||
|
||||
function renderController(
|
||||
controller: AskFlowController,
|
||||
width: number,
|
||||
width: number
|
||||
): string[] {
|
||||
if (!controller.cachedLines) {
|
||||
controller.cachedLines = renderAskScreen({
|
||||
editor: controller.editor,
|
||||
state: controller.state,
|
||||
theme: controller.theme,
|
||||
width,
|
||||
editor: controller.editor,
|
||||
});
|
||||
}
|
||||
return controller.cachedLines;
|
||||
@@ -116,7 +118,7 @@ function handleControllerInput(controller: AskFlowController, data: string) {
|
||||
function handleEditingCommand(
|
||||
controller: AskFlowController,
|
||||
command: AskInputCommand,
|
||||
data: string,
|
||||
data: string
|
||||
) {
|
||||
if (command.kind === "editMoveTab") {
|
||||
controller.state = saveEditorAndMoveTab(controller, command.delta);
|
||||
@@ -145,7 +147,7 @@ function handleEditingCommand(
|
||||
|
||||
function handleNavigationCommand(
|
||||
controller: AskFlowController,
|
||||
command: AskInputCommand,
|
||||
command: AskInputCommand
|
||||
) {
|
||||
switch (command.kind) {
|
||||
case "moveTab":
|
||||
@@ -174,7 +176,7 @@ function handleNavigationCommand(
|
||||
case "numberShortcut":
|
||||
commitNavigation(
|
||||
controller,
|
||||
applyNumberShortcut(controller.state, command.digit),
|
||||
applyNumberShortcut(controller.state, command.digit)
|
||||
);
|
||||
return;
|
||||
case "ignore":
|
||||
@@ -225,8 +227,7 @@ function openOptionNote(controller: AskFlowController) {
|
||||
const question = getCurrentQuestion(controller.state);
|
||||
const option = getCurrentOption(controller.state);
|
||||
if (
|
||||
!question ||
|
||||
!option ||
|
||||
!(question && option) ||
|
||||
option.isCustomOption ||
|
||||
isSubmitTab(controller.state)
|
||||
) {
|
||||
@@ -235,7 +236,7 @@ function openOptionNote(controller: AskFlowController) {
|
||||
controller.state = enterOptionNoteMode(
|
||||
controller.state,
|
||||
question.id,
|
||||
option.value,
|
||||
option.value
|
||||
);
|
||||
hydrateEditorForCurrentView(controller);
|
||||
refresh(controller);
|
||||
@@ -292,7 +293,7 @@ function syncInputModeWithSelection(controller: AskFlowController) {
|
||||
|
||||
const question = getCurrentQuestion(controller.state);
|
||||
const option = getCurrentOption(controller.state);
|
||||
if (!question || !option?.isCustomOption) {
|
||||
if (!(question && option?.isCustomOption)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -314,7 +315,7 @@ function saveEditorWithoutAdvancing(controller: AskFlowController): AskState {
|
||||
|
||||
function saveEditorAndMoveTab(
|
||||
controller: AskFlowController,
|
||||
delta: 1 | -1,
|
||||
delta: 1 | -1
|
||||
): AskState {
|
||||
const nextState = saveEditorWithoutAdvancing(controller);
|
||||
controller.suppressAutoInputForSelection = false;
|
||||
@@ -323,7 +324,7 @@ function saveEditorAndMoveTab(
|
||||
|
||||
function saveEditorAndMoveOption(
|
||||
controller: AskFlowController,
|
||||
delta: 1 | -1,
|
||||
delta: 1 | -1
|
||||
): AskState {
|
||||
const nextState = saveEditorWithoutAdvancing(controller);
|
||||
controller.suppressAutoInputForSelection = false;
|
||||
@@ -334,11 +335,11 @@ function createEditor(tui: Tui, theme: Theme) {
|
||||
const editorTheme: EditorTheme = {
|
||||
borderColor: (text) => theme.fg("accent", text),
|
||||
selectList: {
|
||||
description: (text) => theme.fg("muted", text),
|
||||
noMatch: (text) => theme.fg("warning", text),
|
||||
scrollInfo: (text) => theme.fg("dim", text),
|
||||
selectedPrefix: (text) => theme.fg("accent", text),
|
||||
selectedText: (text) => theme.fg("accent", text),
|
||||
description: (text) => theme.fg("muted", text),
|
||||
scrollInfo: (text) => theme.fg("dim", text),
|
||||
noMatch: (text) => theme.fg("warning", text),
|
||||
},
|
||||
};
|
||||
return new Editor(tui, editorTheme);
|
||||
|
||||
+4
-2
@@ -1,6 +1,8 @@
|
||||
import { Key, matchesKey } from "@mariozechner/pi-tui";
|
||||
import type { AskState } from "../types.ts";
|
||||
|
||||
const DIGIT_PATTERN = /^[1-9]$/;
|
||||
|
||||
export type AskInputCommand =
|
||||
| { kind: "moveTab"; delta: 1 | -1 }
|
||||
| { kind: "moveOption"; delta: 1 | -1 }
|
||||
@@ -18,7 +20,7 @@ export type AskInputCommand =
|
||||
|
||||
export function getInputCommand(
|
||||
state: AskState,
|
||||
data: string,
|
||||
data: string
|
||||
): AskInputCommand {
|
||||
if (state.view.kind === "input" || state.view.kind === "note") {
|
||||
return getEditingInputCommand(data);
|
||||
@@ -102,5 +104,5 @@ function isQuestionNoteShortcut(data: string): boolean {
|
||||
}
|
||||
|
||||
function parseDigit(data: string): number | null {
|
||||
return /^[1-9]$/.test(data) ? Number(data) : null;
|
||||
return DIGIT_PATTERN.test(data) ? Number(data) : null;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
getCurrentQuestion,
|
||||
isQuestionAnswered,
|
||||
isSubmitTab,
|
||||
} from "../state.ts";
|
||||
} from "../state/selectors.ts";
|
||||
import type { AskState } from "../types.ts";
|
||||
import { renderFooterText } from "./render-helpers.ts";
|
||||
import type { Theme } from "./render-types.ts";
|
||||
@@ -53,7 +53,7 @@ function renderTabs(state: AskState, theme: Theme, width: number): string {
|
||||
segments.push(
|
||||
active
|
||||
? theme.bg("selectedBg", theme.fg("text", text))
|
||||
: theme.fg(answered ? "success" : "muted", text),
|
||||
: theme.fg(answered ? "success" : "muted", text)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ function renderTabs(state: AskState, theme: Theme, width: number): string {
|
||||
segments.push(
|
||||
isSubmitTab(state)
|
||||
? theme.bg("selectedBg", theme.fg("text", submitText))
|
||||
: theme.fg("success", submitText),
|
||||
: theme.fg("success", submitText)
|
||||
);
|
||||
segments.push(theme.fg("dim", " →"));
|
||||
return truncateToWidth(` ${segments.join(" ")}`, width);
|
||||
@@ -80,6 +80,6 @@ function renderFooter(state: AskState, theme: Theme): string {
|
||||
const question = getCurrentQuestion(state);
|
||||
return theme.fg(
|
||||
"dim",
|
||||
renderFooterText(question?.type === "multi" ? "multi" : "default"),
|
||||
renderFooterText(question?.type === "multi" ? "multi" : "default")
|
||||
);
|
||||
}
|
||||
|
||||
+21
-20
@@ -14,7 +14,7 @@ export function pushWrappedText(
|
||||
theme: Theme,
|
||||
color: ThemeColor,
|
||||
prefix = "",
|
||||
continuationPrefix = prefix,
|
||||
continuationPrefix = prefix
|
||||
) {
|
||||
const availableWidth = Math.max(1, width - visibleWidth(prefix));
|
||||
const wrapped = wrapText(text, availableWidth);
|
||||
@@ -22,7 +22,7 @@ export function pushWrappedText(
|
||||
const line = wrapped[index];
|
||||
const currentPrefix = index === 0 ? prefix : continuationPrefix;
|
||||
lines.push(
|
||||
truncateToWidth(`${currentPrefix}${theme.fg(color, line)}`, width),
|
||||
truncateToWidth(`${currentPrefix}${theme.fg(color, line)}`, width)
|
||||
);
|
||||
}
|
||||
if (wrapped.length === 0) {
|
||||
@@ -34,7 +34,7 @@ export function renderInputLine(
|
||||
line: string,
|
||||
availableWidth: number,
|
||||
theme: Theme,
|
||||
color: ThemeColor = "text",
|
||||
color: ThemeColor = "text"
|
||||
): string {
|
||||
const innerWidth = Math.max(4, availableWidth - 2);
|
||||
const truncated = truncateToWidth(line, innerWidth);
|
||||
@@ -66,7 +66,8 @@ export function renderEditorBlock(args: {
|
||||
contentColor = "text",
|
||||
isEmpty = false,
|
||||
} = args;
|
||||
const innerLines = editorLines.length >= 2 ? editorLines.slice(1, -1) : editorLines;
|
||||
const innerLines =
|
||||
editorLines.length >= 2 ? editorLines.slice(1, -1) : editorLines;
|
||||
|
||||
if (isEmpty && placeholder) {
|
||||
lines.push(
|
||||
@@ -75,10 +76,10 @@ export function renderEditorBlock(args: {
|
||||
placeholder,
|
||||
availableWidth,
|
||||
theme,
|
||||
placeholderColor,
|
||||
placeholderColor
|
||||
)}`,
|
||||
width,
|
||||
),
|
||||
width
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -90,10 +91,10 @@ export function renderEditorBlock(args: {
|
||||
editorLine,
|
||||
availableWidth,
|
||||
theme,
|
||||
contentColor,
|
||||
contentColor
|
||||
)}`,
|
||||
width,
|
||||
),
|
||||
width
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -101,7 +102,7 @@ export function renderEditorBlock(args: {
|
||||
export function renderBox(
|
||||
content: Array<{ text: string; color: ThemeColor }>,
|
||||
width: number,
|
||||
theme: Theme,
|
||||
theme: Theme
|
||||
): string[] {
|
||||
const boxWidth = Math.max(UI_DIMENSIONS.boxMinWidth, width);
|
||||
const innerWidth = Math.max(4, boxWidth - 2);
|
||||
@@ -113,7 +114,7 @@ export function renderBox(
|
||||
const line = theme.fg(item.color, rawLine);
|
||||
const padding = " ".repeat(Math.max(0, innerWidth - visibleWidth(line)));
|
||||
lines.push(
|
||||
theme.fg("accent", "│") + line + padding + theme.fg("accent", "│"),
|
||||
theme.fg("accent", "│") + line + padding + theme.fg("accent", "│")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -126,7 +127,7 @@ export function renderPreviewPaneContent(
|
||||
| { label: string; description?: string; preview?: string }
|
||||
| undefined,
|
||||
theme: Theme,
|
||||
width: number,
|
||||
width: number
|
||||
): string[] {
|
||||
if (!selectedOption) {
|
||||
return renderBox([{ text: NO_PREVIEW_TEXT, color: "dim" }], width, theme);
|
||||
@@ -140,7 +141,7 @@ export function renderPreviewPaneContent(
|
||||
}
|
||||
content.push({ text: "", color: "dim" });
|
||||
for (const previewLine of (selectedOption.preview ?? NO_PREVIEW_TEXT).split(
|
||||
"\n",
|
||||
"\n"
|
||||
)) {
|
||||
content.push({
|
||||
text: previewLine,
|
||||
@@ -154,7 +155,7 @@ export function mergeColumns(
|
||||
left: string[],
|
||||
right: string[],
|
||||
leftWidth: number,
|
||||
width: number,
|
||||
width: number
|
||||
): string[] {
|
||||
const lines: string[] = [];
|
||||
const rowCount = Math.max(left.length, right.length);
|
||||
@@ -169,7 +170,7 @@ export function mergeColumns(
|
||||
|
||||
export function measurePreviewLeftWidth(
|
||||
options: Array<{ label: string; description?: string }>,
|
||||
width: number,
|
||||
width: number
|
||||
): number {
|
||||
let widest = 0;
|
||||
for (let index = 0; index < options.length; index++) {
|
||||
@@ -177,24 +178,24 @@ export function measurePreviewLeftWidth(
|
||||
widest = Math.max(
|
||||
widest,
|
||||
visibleWidth(`${index + 1}. ${option.label}`),
|
||||
option.description ? visibleWidth(option.description) : 0,
|
||||
option.description ? visibleWidth(option.description) : 0
|
||||
);
|
||||
}
|
||||
|
||||
const preferred = widest + 4;
|
||||
const maxWidth = Math.min(
|
||||
UI_DIMENSIONS.previewLeftMaxWidth,
|
||||
Math.floor(width * UI_DIMENSIONS.previewLeftRatio),
|
||||
Math.floor(width * UI_DIMENSIONS.previewLeftRatio)
|
||||
);
|
||||
return clamp(
|
||||
preferred,
|
||||
UI_DIMENSIONS.previewLeftMinWidth,
|
||||
Math.max(UI_DIMENSIONS.previewLeftMinWidth, maxWidth),
|
||||
Math.max(UI_DIMENSIONS.previewLeftMinWidth, maxWidth)
|
||||
);
|
||||
}
|
||||
|
||||
export function renderFooterText(
|
||||
mode: "input" | "note" | "submit" | "multi" | "default",
|
||||
mode: "input" | "note" | "submit" | "multi" | "default"
|
||||
) {
|
||||
switch (mode) {
|
||||
case "input":
|
||||
|
||||
+54
-38
@@ -1,13 +1,13 @@
|
||||
import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
||||
import { isOptionSelected } from "../state/answers.ts";
|
||||
import {
|
||||
getAnswer,
|
||||
getOptionNote,
|
||||
getQuestionNote,
|
||||
isInputOpenForQuestion,
|
||||
isOptionNoteOpen,
|
||||
isOptionSelected,
|
||||
isQuestionNoteOpen,
|
||||
} from "../state.ts";
|
||||
} from "../state/selectors.ts";
|
||||
import type { AskDisplayOption } from "../types.ts";
|
||||
import { UI_DIMENSIONS, UI_TEXT } from "./constants.ts";
|
||||
import {
|
||||
@@ -48,15 +48,15 @@ function renderQuestionNote(context: QuestionRenderContext) {
|
||||
theme,
|
||||
"accent",
|
||||
" ",
|
||||
" ",
|
||||
" "
|
||||
);
|
||||
renderEditorBlock({
|
||||
lines,
|
||||
editorLines: editor.render(
|
||||
Math.max(
|
||||
UI_DIMENSIONS.editorMinWidth,
|
||||
width - UI_DIMENSIONS.editorContentPadding,
|
||||
),
|
||||
width - UI_DIMENSIONS.editorContentPadding
|
||||
)
|
||||
),
|
||||
width,
|
||||
theme,
|
||||
@@ -80,7 +80,7 @@ function renderQuestionNote(context: QuestionRenderContext) {
|
||||
theme,
|
||||
"muted",
|
||||
" ",
|
||||
" ",
|
||||
" "
|
||||
);
|
||||
lines.push("");
|
||||
}
|
||||
@@ -95,7 +95,7 @@ function renderStandardQuestion(context: QuestionRenderContext) {
|
||||
function renderStandardOption(
|
||||
context: QuestionRenderContext,
|
||||
option: AskDisplayOption,
|
||||
index: number,
|
||||
index: number
|
||||
) {
|
||||
const { lines, state, question, theme, width } = context;
|
||||
const answer = getAnswer(state, question.id);
|
||||
@@ -106,7 +106,11 @@ function renderStandardOption(
|
||||
const pointer = selected ? "❯ " : " ";
|
||||
const prefix = getOptionPrefix(question.type, option, isAnsweredOption);
|
||||
const optionColor = getOptionColor(isAnsweredOption, selected);
|
||||
if (option.isCustomOption && selected && isInputOpenForQuestion(state, question.id)) {
|
||||
if (
|
||||
option.isCustomOption &&
|
||||
selected &&
|
||||
isInputOpenForQuestion(state, question.id)
|
||||
) {
|
||||
renderInteractiveCustomOption(context, index, pointer, prefix, optionColor);
|
||||
return;
|
||||
}
|
||||
@@ -118,7 +122,7 @@ function renderStandardOption(
|
||||
theme,
|
||||
optionColor,
|
||||
pointer,
|
||||
" ".repeat(visibleWidth(pointer)),
|
||||
" ".repeat(visibleWidth(pointer))
|
||||
);
|
||||
renderOptionMeta({
|
||||
...context,
|
||||
@@ -143,7 +147,7 @@ function renderPreviewQuestion(context: QuestionRenderContext) {
|
||||
theme,
|
||||
width,
|
||||
answer,
|
||||
selectedOption,
|
||||
selectedOption
|
||||
);
|
||||
} else {
|
||||
renderStackedPreviewLayout(
|
||||
@@ -153,7 +157,7 @@ function renderPreviewQuestion(context: QuestionRenderContext) {
|
||||
theme,
|
||||
width,
|
||||
answer,
|
||||
selectedOption,
|
||||
selectedOption
|
||||
);
|
||||
}
|
||||
|
||||
@@ -173,19 +177,19 @@ function renderWidePreviewLayout(
|
||||
theme: Theme,
|
||||
width: number,
|
||||
answer: ReturnType<typeof getAnswer>,
|
||||
selectedOption: AskDisplayOption | undefined,
|
||||
selectedOption: AskDisplayOption | undefined
|
||||
) {
|
||||
const leftWidth = measurePreviewLeftWidth(options, width);
|
||||
const rightWidth = Math.max(
|
||||
UI_DIMENSIONS.previewMinRightWidth,
|
||||
width - leftWidth - 2,
|
||||
width - leftWidth - 2
|
||||
);
|
||||
const leftPane = renderPreviewOptionList(
|
||||
state,
|
||||
options,
|
||||
theme,
|
||||
leftWidth,
|
||||
answer,
|
||||
answer
|
||||
);
|
||||
const rightPane = renderPreviewPaneContent(selectedOption, theme, rightWidth);
|
||||
for (const line of mergeColumns(leftPane, rightPane, leftWidth, width)) {
|
||||
@@ -200,7 +204,7 @@ function renderStackedPreviewLayout(
|
||||
theme: Theme,
|
||||
width: number,
|
||||
answer: ReturnType<typeof getAnswer>,
|
||||
selectedOption: AskDisplayOption | undefined,
|
||||
selectedOption: AskDisplayOption | undefined
|
||||
) {
|
||||
renderPreviewOptionList(state, options, theme, width, answer).forEach(add);
|
||||
add("");
|
||||
@@ -212,7 +216,7 @@ function renderPreviewOptionList(
|
||||
options: QuestionRenderContext["options"],
|
||||
theme: Theme,
|
||||
width: number,
|
||||
answer: ReturnType<typeof getAnswer>,
|
||||
answer: ReturnType<typeof getAnswer>
|
||||
): string[] {
|
||||
const lines: string[] = [];
|
||||
for (let index = 0; index < options.length; index++) {
|
||||
@@ -228,7 +232,7 @@ function renderPreviewOptionList(
|
||||
theme,
|
||||
getOptionColor(isAnsweredOption, selected),
|
||||
selected ? "❯ " : " ",
|
||||
" ",
|
||||
" "
|
||||
);
|
||||
if (option.description) {
|
||||
pushWrappedText(
|
||||
@@ -238,7 +242,7 @@ function renderPreviewOptionList(
|
||||
theme,
|
||||
"muted",
|
||||
" ",
|
||||
" ",
|
||||
" "
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -251,7 +255,7 @@ function renderOptionMeta(
|
||||
answer: ReturnType<typeof getAnswer>;
|
||||
option: AskDisplayOption;
|
||||
selected: boolean;
|
||||
},
|
||||
}
|
||||
) {
|
||||
const { lines, option, theme, width } = context;
|
||||
if (option.description) {
|
||||
@@ -262,7 +266,7 @@ function renderOptionMeta(
|
||||
theme,
|
||||
"muted",
|
||||
" ",
|
||||
" ",
|
||||
" "
|
||||
);
|
||||
}
|
||||
renderOptionEditorOrNote(context);
|
||||
@@ -312,7 +316,7 @@ function getOptionDetailRenderState(context: OptionDetailRenderContext) {
|
||||
}
|
||||
|
||||
function hasOptionDetailContent(
|
||||
renderState: ReturnType<typeof getOptionDetailRenderState>,
|
||||
renderState: ReturnType<typeof getOptionDetailRenderState>
|
||||
) {
|
||||
return !!(
|
||||
renderState.noteOpen ||
|
||||
@@ -324,15 +328,27 @@ function hasOptionDetailContent(
|
||||
|
||||
function renderOptionEditor(
|
||||
context: OptionDetailRenderContext,
|
||||
renderState: ReturnType<typeof getOptionDetailRenderState>,
|
||||
renderState: ReturnType<typeof getOptionDetailRenderState>
|
||||
): boolean {
|
||||
const { lines, editor, width, theme } = context;
|
||||
if (renderState.noteOpen) {
|
||||
renderIndentedEditor(lines, editor, width, theme, UI_TEXT.editorPlaceholderNote);
|
||||
renderIndentedEditor(
|
||||
lines,
|
||||
editor,
|
||||
width,
|
||||
theme,
|
||||
UI_TEXT.editorPlaceholderNote
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if (renderState.inputOpen) {
|
||||
renderIndentedEditor(lines, editor, width, theme, UI_TEXT.editorPlaceholderInput);
|
||||
renderIndentedEditor(
|
||||
lines,
|
||||
editor,
|
||||
width,
|
||||
theme,
|
||||
UI_TEXT.editorPlaceholderInput
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -340,10 +356,10 @@ function renderOptionEditor(
|
||||
|
||||
function renderOptionContent(
|
||||
context: OptionDetailRenderContext,
|
||||
renderState: ReturnType<typeof getOptionDetailRenderState>,
|
||||
renderState: ReturnType<typeof getOptionDetailRenderState>
|
||||
): boolean {
|
||||
const { lines, option, theme, width } = context;
|
||||
if (!option?.isCustomOption || !renderState.customText) {
|
||||
if (!(option?.isCustomOption && renderState.customText)) {
|
||||
return false;
|
||||
}
|
||||
pushWrappedText(
|
||||
@@ -353,14 +369,14 @@ function renderOptionContent(
|
||||
theme,
|
||||
"muted",
|
||||
" ",
|
||||
" ",
|
||||
" "
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderSavedOptionNote(
|
||||
context: OptionDetailRenderContext,
|
||||
optionNote: string | undefined,
|
||||
optionNote: string | undefined
|
||||
) {
|
||||
if (!optionNote) {
|
||||
return;
|
||||
@@ -373,7 +389,7 @@ function renderSavedOptionNote(
|
||||
theme,
|
||||
"muted",
|
||||
" ",
|
||||
" ",
|
||||
" "
|
||||
);
|
||||
}
|
||||
|
||||
@@ -382,15 +398,15 @@ function renderIndentedEditor(
|
||||
editor: QuestionRenderContext["editor"],
|
||||
width: number,
|
||||
theme: Theme,
|
||||
placeholder: string,
|
||||
placeholder: string
|
||||
) {
|
||||
renderEditorBlock({
|
||||
lines,
|
||||
editorLines: editor.render(
|
||||
Math.max(
|
||||
UI_DIMENSIONS.editorMinWidth,
|
||||
width - UI_DIMENSIONS.editorIndentedPadding,
|
||||
),
|
||||
width - UI_DIMENSIONS.editorIndentedPadding
|
||||
)
|
||||
),
|
||||
width,
|
||||
theme,
|
||||
@@ -404,7 +420,7 @@ function renderIndentedEditor(
|
||||
function getOptionPrefix(
|
||||
questionType: QuestionRenderContext["question"]["type"],
|
||||
option: AskDisplayOption,
|
||||
isAnsweredOption: boolean,
|
||||
isAnsweredOption: boolean
|
||||
): string {
|
||||
if (questionType !== "multi" || option.isCustomOption) {
|
||||
return "";
|
||||
@@ -417,13 +433,13 @@ function renderInteractiveCustomOption(
|
||||
index: number,
|
||||
pointer: string,
|
||||
prefix: string,
|
||||
optionColor: ReturnType<typeof getOptionColor>,
|
||||
optionColor: ReturnType<typeof getOptionColor>
|
||||
) {
|
||||
const { lines, editor, theme, width } = context;
|
||||
const label = `${index + 1}. ${prefix}`;
|
||||
const availableWidth = Math.max(
|
||||
1,
|
||||
width - visibleWidth(pointer) - visibleWidth(label),
|
||||
width - visibleWidth(pointer) - visibleWidth(label)
|
||||
);
|
||||
const text = editor.getText().replace(/\n/g, " ");
|
||||
const hasText = text.trim().length > 0;
|
||||
@@ -435,10 +451,10 @@ function renderInteractiveCustomOption(
|
||||
inputText,
|
||||
availableWidth,
|
||||
theme,
|
||||
inputColor,
|
||||
inputColor
|
||||
)}`,
|
||||
width,
|
||||
),
|
||||
width
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -1,6 +1,6 @@
|
||||
import { truncateToWidth } from "@mariozechner/pi-tui";
|
||||
import { SUBMIT_CHOICES } from "../constants.ts";
|
||||
import { toAskResult } from "../state.ts";
|
||||
import { toAskResult } from "../state/result.ts";
|
||||
import type { AskResult, AskState } from "../types.ts";
|
||||
import { UI_TEXT } from "./constants.ts";
|
||||
import { pushWrappedText } from "./render-helpers.ts";
|
||||
@@ -10,7 +10,7 @@ export function renderSubmitScreen(
|
||||
lines: string[],
|
||||
state: AskState,
|
||||
theme: Theme,
|
||||
width: number,
|
||||
width: number
|
||||
) {
|
||||
pushWrappedText(lines, UI_TEXT.reviewTitle, width, theme, "accent", " ", " ");
|
||||
lines.push("");
|
||||
@@ -22,7 +22,7 @@ export function renderSubmitScreen(
|
||||
question,
|
||||
submittedAnswers[question.id],
|
||||
theme,
|
||||
width,
|
||||
width
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export function renderSubmitScreen(
|
||||
theme,
|
||||
"success",
|
||||
" ",
|
||||
" ",
|
||||
" "
|
||||
);
|
||||
lines.push("");
|
||||
renderSubmitActions(lines, state, theme, width);
|
||||
@@ -45,12 +45,12 @@ function renderSubmittedQuestion(
|
||||
question: AskState["questions"][number],
|
||||
answer: AskResult["answers"][string] | undefined,
|
||||
theme: Theme,
|
||||
width: number,
|
||||
width: number
|
||||
) {
|
||||
pushWrappedText(lines, question.prompt, width, theme, "muted", " ● ", " ");
|
||||
if (!answer) {
|
||||
lines.push(
|
||||
truncateToWidth(` ${theme.fg("warning", UI_TEXT.unanswered)}`, width),
|
||||
truncateToWidth(` ${theme.fg("warning", UI_TEXT.unanswered)}`, width)
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -62,7 +62,7 @@ function renderSubmittedAnswer(
|
||||
lines: string[],
|
||||
answer: AskResult["answers"][string],
|
||||
theme: Theme,
|
||||
width: number,
|
||||
width: number
|
||||
) {
|
||||
const answerText = answer.customText ?? answer.labels.join(", ");
|
||||
if (answerText) {
|
||||
@@ -73,7 +73,7 @@ function renderSubmittedAnswer(
|
||||
theme,
|
||||
answer.customText ? "text" : "success",
|
||||
" ",
|
||||
" ",
|
||||
" "
|
||||
);
|
||||
}
|
||||
if (answer.note) {
|
||||
@@ -84,7 +84,7 @@ function renderSubmittedAnswer(
|
||||
theme,
|
||||
"muted",
|
||||
" ",
|
||||
" ",
|
||||
" "
|
||||
);
|
||||
}
|
||||
for (let index = 0; index < answer.values.length; index++) {
|
||||
@@ -101,7 +101,7 @@ function renderSubmittedAnswer(
|
||||
theme,
|
||||
"muted",
|
||||
" ",
|
||||
" ",
|
||||
" "
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,7 @@ function renderSubmitActions(
|
||||
lines: string[],
|
||||
state: AskState,
|
||||
theme: Theme,
|
||||
width: number,
|
||||
width: number
|
||||
) {
|
||||
for (let index = 0; index < SUBMIT_CHOICES.length; index++) {
|
||||
const selected = index === state.activeSubmitActionIndex;
|
||||
@@ -122,7 +122,7 @@ function renderSubmitActions(
|
||||
theme,
|
||||
selected ? "accent" : "text",
|
||||
prefix,
|
||||
prefix,
|
||||
prefix
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,30 +4,30 @@ import type {
|
||||
getAnswer,
|
||||
getCurrentQuestion,
|
||||
getRenderableOptions,
|
||||
} from "../state.ts";
|
||||
} from "../state/selectors.ts";
|
||||
import type { AskDisplayOption, AskState } from "../types.ts";
|
||||
|
||||
export type Theme = ExtensionContext["ui"]["theme"];
|
||||
|
||||
export interface QuestionRenderContext {
|
||||
editor: Editor;
|
||||
lines: string[];
|
||||
state: AskState;
|
||||
question: NonNullable<ReturnType<typeof getCurrentQuestion>>;
|
||||
options: ReturnType<typeof getRenderableOptions>;
|
||||
question: NonNullable<ReturnType<typeof getCurrentQuestion>>;
|
||||
state: AskState;
|
||||
theme: Theme;
|
||||
width: number;
|
||||
editor: Editor;
|
||||
}
|
||||
|
||||
export interface OptionDetailRenderContext {
|
||||
lines: string[];
|
||||
state: AskState;
|
||||
questionId: string;
|
||||
answer: ReturnType<typeof getAnswer>;
|
||||
editor: Editor;
|
||||
lines: string[];
|
||||
option: AskDisplayOption | undefined;
|
||||
questionId: string;
|
||||
selected?: boolean;
|
||||
state: AskState;
|
||||
theme: Theme;
|
||||
width: number;
|
||||
editor: Editor;
|
||||
selected?: boolean;
|
||||
withGap?: boolean;
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import {
|
||||
getCurrentQuestion,
|
||||
getRenderableOptions,
|
||||
isSubmitTab,
|
||||
} from "../state.ts";
|
||||
} from "../state/selectors.ts";
|
||||
import type { AskState } from "../types.ts";
|
||||
import { renderFrameFooter, renderFrameHeader } from "./render-frame.ts";
|
||||
import { renderQuestionScreen } from "./render-question.ts";
|
||||
|
||||
Reference in New Issue
Block a user