pi-ask-user 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -12
- package/index.ts +398 -99
- package/package.json +2 -2
- package/single-select-layout.ts +6 -3
package/README.md
CHANGED
|
@@ -10,11 +10,13 @@ High-quality video: [ask-user-demo.mp4](./media/ask-user-demo.mp4)
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
-
|
|
13
|
+
- Searchable single-select option lists with wrapped titles and descriptions
|
|
14
|
+
- Responsive split-pane details preview on wide terminals with single-column fallback on narrow terminals
|
|
14
15
|
- Multi-select option lists
|
|
15
16
|
- Optional freeform responses
|
|
16
17
|
- Context display support
|
|
17
18
|
- Overlay mode — dialog floats over conversation, preserving context
|
|
19
|
+
- Pi-TUI-aligned keybinding and editor behavior
|
|
18
20
|
- Custom TUI rendering for tool calls and results
|
|
19
21
|
- System prompt integration via `promptSnippet` and `promptGuidelines`
|
|
20
22
|
- Optional timeout for auto-dismiss in both overlay and fallback input modes
|
|
@@ -94,14 +96,4 @@ interface AskToolDetails {
|
|
|
94
96
|
|
|
95
97
|
## Changelog
|
|
96
98
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
- Added `promptSnippet` and `promptGuidelines` for better LLM tool selection in the system prompt
|
|
100
|
-
- Added `renderCall` and `renderResult` for custom TUI rendering (compact tool call display, ✓/Cancelled result indicators)
|
|
101
|
-
- Added overlay mode — dialog now floats over the conversation instead of clearing the screen
|
|
102
|
-
- Added `timeout` parameter for auto-dismiss and cancellation support
|
|
103
|
-
- Added structured `details` (`AskToolDetails`) to all result paths for session state reconstruction and branching support
|
|
104
|
-
|
|
105
|
-
### 0.2.1
|
|
106
|
-
|
|
107
|
-
- Initial public release
|
|
99
|
+
See [CHANGELOG.md](./CHANGELOG.md).
|
package/index.ts
CHANGED
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
* Ask Tool Extension - Interactive question UI for pi-coding-agent
|
|
3
3
|
*
|
|
4
4
|
* Refactored to use built-in TUI primitives (Container/Text/Spacer/SelectList/Editor)
|
|
5
|
-
* and
|
|
5
|
+
* and a custom box border instead of manual ANSI box drawing.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { ExtensionAPI, Theme } from "@mariozechner/pi-coding-agent";
|
|
9
|
-
import {
|
|
9
|
+
import { getMarkdownTheme } from "@mariozechner/pi-coding-agent";
|
|
10
10
|
import { Type } from "@sinclair/typebox";
|
|
11
11
|
import {
|
|
12
12
|
Container,
|
|
13
13
|
type Component,
|
|
14
|
+
decodeKittyPrintable,
|
|
14
15
|
Editor,
|
|
15
16
|
type EditorTheme,
|
|
17
|
+
fuzzyFilter,
|
|
16
18
|
Key,
|
|
19
|
+
type Keybinding,
|
|
20
|
+
type KeybindingsManager,
|
|
17
21
|
Markdown,
|
|
18
22
|
type MarkdownTheme,
|
|
19
23
|
matchesKey,
|
|
@@ -25,10 +29,9 @@ import {
|
|
|
25
29
|
} from "@mariozechner/pi-tui";
|
|
26
30
|
import { renderSingleSelectRows } from "./single-select-layout";
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
+
import { createRequire } from "node:module";
|
|
33
|
+
const _require = createRequire(import.meta.url);
|
|
34
|
+
const ASK_USER_VERSION: string = (_require("./package.json") as { version: string }).version;
|
|
32
35
|
|
|
33
36
|
type AskOptionInput = QuestionOption | string;
|
|
34
37
|
|
|
@@ -50,6 +53,11 @@ interface AskToolDetails {
|
|
|
50
53
|
wasCustom?: boolean;
|
|
51
54
|
}
|
|
52
55
|
|
|
56
|
+
interface AskUIResult {
|
|
57
|
+
answer: string;
|
|
58
|
+
wasCustom: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
function normalizeOptions(options: AskOptionInput[]): QuestionOption[] {
|
|
54
62
|
return options
|
|
55
63
|
.map((option) => {
|
|
@@ -90,16 +98,90 @@ function createEditorTheme(theme: Theme): EditorTheme {
|
|
|
90
98
|
};
|
|
91
99
|
}
|
|
92
100
|
|
|
101
|
+
const BOX_BORDER_LEFT = "│ ";
|
|
102
|
+
const BOX_BORDER_RIGHT = " │";
|
|
103
|
+
const BOX_BORDER_OVERHEAD = BOX_BORDER_LEFT.length + BOX_BORDER_RIGHT.length;
|
|
104
|
+
|
|
105
|
+
class BoxBorderTop implements Component {
|
|
106
|
+
private color: (s: string) => string;
|
|
107
|
+
private title?: string;
|
|
108
|
+
private titleColor?: (s: string) => string;
|
|
109
|
+
constructor(color: (s: string) => string, title?: string, titleColor?: (s: string) => string) {
|
|
110
|
+
this.color = color;
|
|
111
|
+
this.title = title;
|
|
112
|
+
this.titleColor = titleColor;
|
|
113
|
+
}
|
|
114
|
+
invalidate(): void {}
|
|
115
|
+
render(width: number): string[] {
|
|
116
|
+
const inner = Math.max(0, width - 2);
|
|
117
|
+
if (!this.title || inner < this.title.length + 4) {
|
|
118
|
+
return [this.color(`╭${"─".repeat(inner)}╮`)];
|
|
119
|
+
}
|
|
120
|
+
const label = ` ${this.title} `;
|
|
121
|
+
const remaining = inner - 1 - label.length;
|
|
122
|
+
const titleStyle = this.titleColor ?? this.color;
|
|
123
|
+
return [
|
|
124
|
+
this.color("╭─") + titleStyle(label) + this.color("─".repeat(Math.max(0, remaining)) + "╮"),
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
class BoxBorderBottom implements Component {
|
|
130
|
+
private color: (s: string) => string;
|
|
131
|
+
private label?: string;
|
|
132
|
+
private labelColor?: (s: string) => string;
|
|
133
|
+
constructor(color: (s: string) => string, label?: string, labelColor?: (s: string) => string) {
|
|
134
|
+
this.color = color;
|
|
135
|
+
this.label = label;
|
|
136
|
+
this.labelColor = labelColor;
|
|
137
|
+
}
|
|
138
|
+
invalidate(): void {}
|
|
139
|
+
render(width: number): string[] {
|
|
140
|
+
const inner = Math.max(0, width - 2);
|
|
141
|
+
if (!this.label || inner < this.label.length + 4) {
|
|
142
|
+
return [this.color(`╰${"─".repeat(inner)}╯`)];
|
|
143
|
+
}
|
|
144
|
+
const tag = ` ${this.label} `;
|
|
145
|
+
const leftDashes = inner - tag.length - 1;
|
|
146
|
+
const style = this.labelColor ?? this.color;
|
|
147
|
+
return [
|
|
148
|
+
this.color("╰" + "─".repeat(Math.max(0, leftDashes))) + style(tag) + this.color("─╯"),
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function formatKeyList(keys: string[]): string {
|
|
154
|
+
return keys.join("/");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function keybindingHint(
|
|
158
|
+
theme: Theme,
|
|
159
|
+
keybindings: KeybindingsManager,
|
|
160
|
+
keybinding: Keybinding,
|
|
161
|
+
description: string,
|
|
162
|
+
): string {
|
|
163
|
+
return `${theme.fg("dim", formatKeyList(keybindings.getKeys(keybinding)))}${theme.fg("muted", ` ${description}`)}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function literalHint(theme: Theme, key: string, description: string): string {
|
|
167
|
+
return `${theme.fg("dim", key)}${theme.fg("muted", ` ${description}`)}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
93
170
|
type AskMode = "select" | "freeform";
|
|
94
171
|
|
|
95
172
|
const ASK_OVERLAY_MAX_HEIGHT_RATIO = 0.85;
|
|
96
173
|
const ASK_OVERLAY_WIDTH = "92%";
|
|
97
174
|
const ASK_OVERLAY_MIN_WIDTH = 40;
|
|
175
|
+
const SINGLE_SELECT_SPLIT_PANE_MIN_WIDTH = 84;
|
|
176
|
+
const SINGLE_SELECT_SPLIT_PANE_LEFT_MIN_WIDTH = 32;
|
|
177
|
+
const SINGLE_SELECT_SPLIT_PANE_RIGHT_MIN_WIDTH = 28;
|
|
178
|
+
const SINGLE_SELECT_SPLIT_PANE_SEPARATOR = " │ ";
|
|
98
179
|
|
|
99
180
|
class MultiSelectList implements Component {
|
|
100
181
|
private options: QuestionOption[];
|
|
101
182
|
private allowFreeform: boolean;
|
|
102
183
|
private theme: Theme;
|
|
184
|
+
private keybindings: KeybindingsManager;
|
|
103
185
|
private selectedIndex = 0;
|
|
104
186
|
private checked = new Set<number>();
|
|
105
187
|
private cachedWidth?: number;
|
|
@@ -109,10 +191,11 @@ class MultiSelectList implements Component {
|
|
|
109
191
|
public onSubmit?: (result: string) => void;
|
|
110
192
|
public onEnterFreeform?: () => void;
|
|
111
193
|
|
|
112
|
-
constructor(options: QuestionOption[], allowFreeform: boolean, theme: Theme) {
|
|
194
|
+
constructor(options: QuestionOption[], allowFreeform: boolean, theme: Theme, keybindings: KeybindingsManager) {
|
|
113
195
|
this.options = options;
|
|
114
196
|
this.allowFreeform = allowFreeform;
|
|
115
197
|
this.theme = theme;
|
|
198
|
+
this.keybindings = keybindings;
|
|
116
199
|
}
|
|
117
200
|
|
|
118
201
|
invalidate(): void {
|
|
@@ -135,7 +218,7 @@ class MultiSelectList implements Component {
|
|
|
135
218
|
}
|
|
136
219
|
|
|
137
220
|
handleInput(data: string): void {
|
|
138
|
-
if (
|
|
221
|
+
if (this.keybindings.matches(data, "tui.select.cancel")) {
|
|
139
222
|
this.onCancel?.();
|
|
140
223
|
return;
|
|
141
224
|
}
|
|
@@ -146,13 +229,13 @@ class MultiSelectList implements Component {
|
|
|
146
229
|
return;
|
|
147
230
|
}
|
|
148
231
|
|
|
149
|
-
if (
|
|
232
|
+
if (this.keybindings.matches(data, "tui.select.up") || matchesKey(data, Key.shift("tab"))) {
|
|
150
233
|
this.selectedIndex = this.selectedIndex === 0 ? count - 1 : this.selectedIndex - 1;
|
|
151
234
|
this.invalidate();
|
|
152
235
|
return;
|
|
153
236
|
}
|
|
154
237
|
|
|
155
|
-
if (
|
|
238
|
+
if (this.keybindings.matches(data, "tui.select.down") || matchesKey(data, Key.tab)) {
|
|
156
239
|
this.selectedIndex = this.selectedIndex === count - 1 ? 0 : this.selectedIndex + 1;
|
|
157
240
|
this.invalidate();
|
|
158
241
|
return;
|
|
@@ -180,7 +263,7 @@ class MultiSelectList implements Component {
|
|
|
180
263
|
return;
|
|
181
264
|
}
|
|
182
265
|
|
|
183
|
-
if (
|
|
266
|
+
if (this.keybindings.matches(data, "tui.select.confirm")) {
|
|
184
267
|
if (this.isFreeformRow(this.selectedIndex)) {
|
|
185
268
|
this.onEnterFreeform?.();
|
|
186
269
|
return;
|
|
@@ -269,7 +352,9 @@ class WrappedSingleSelectList implements Component {
|
|
|
269
352
|
private options: QuestionOption[];
|
|
270
353
|
private allowFreeform: boolean;
|
|
271
354
|
private theme: Theme;
|
|
355
|
+
private keybindings: KeybindingsManager;
|
|
272
356
|
private selectedIndex = 0;
|
|
357
|
+
private searchQuery = "";
|
|
273
358
|
private maxVisibleRows = 12;
|
|
274
359
|
private cachedWidth?: number;
|
|
275
360
|
private cachedLines?: string[];
|
|
@@ -278,10 +363,11 @@ class WrappedSingleSelectList implements Component {
|
|
|
278
363
|
public onSubmit?: (result: string) => void;
|
|
279
364
|
public onEnterFreeform?: () => void;
|
|
280
365
|
|
|
281
|
-
constructor(options: QuestionOption[], allowFreeform: boolean, theme: Theme) {
|
|
366
|
+
constructor(options: QuestionOption[], allowFreeform: boolean, theme: Theme, keybindings: KeybindingsManager) {
|
|
282
367
|
this.options = options;
|
|
283
368
|
this.allowFreeform = allowFreeform;
|
|
284
369
|
this.theme = theme;
|
|
370
|
+
this.keybindings = keybindings;
|
|
285
371
|
}
|
|
286
372
|
|
|
287
373
|
setMaxVisibleRows(rows: number): void {
|
|
@@ -297,57 +383,234 @@ class WrappedSingleSelectList implements Component {
|
|
|
297
383
|
this.cachedLines = undefined;
|
|
298
384
|
}
|
|
299
385
|
|
|
300
|
-
private
|
|
301
|
-
return this.options.
|
|
386
|
+
private getFilteredOptions(): QuestionOption[] {
|
|
387
|
+
return fuzzyFilter(this.options, this.searchQuery, (option) => `${option.title} ${option.description ?? ""}`);
|
|
302
388
|
}
|
|
303
389
|
|
|
304
|
-
private
|
|
305
|
-
return this.allowFreeform
|
|
390
|
+
private getItemCount(filteredOptions: QuestionOption[]): number {
|
|
391
|
+
return filteredOptions.length + (this.allowFreeform ? 1 : 0);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private isFreeformRow(index: number, filteredOptions: QuestionOption[]): boolean {
|
|
395
|
+
return this.allowFreeform && index === filteredOptions.length;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private setSearchQuery(query: string): void {
|
|
399
|
+
this.searchQuery = query;
|
|
400
|
+
this.selectedIndex = 0;
|
|
401
|
+
this.invalidate();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private popSearchCharacter(): void {
|
|
405
|
+
if (!this.searchQuery) return;
|
|
406
|
+
const characters = [...this.searchQuery];
|
|
407
|
+
characters.pop();
|
|
408
|
+
this.setSearchQuery(characters.join(""));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private getPrintableInput(data: string): string | null {
|
|
412
|
+
const kittyPrintable = decodeKittyPrintable(data);
|
|
413
|
+
if (kittyPrintable !== undefined) return kittyPrintable;
|
|
414
|
+
|
|
415
|
+
const characters = [...data];
|
|
416
|
+
if (characters.length !== 1) return null;
|
|
417
|
+
|
|
418
|
+
const [character] = characters;
|
|
419
|
+
if (!character) return null;
|
|
420
|
+
|
|
421
|
+
const code = character.charCodeAt(0);
|
|
422
|
+
if (code < 32 || code === 0x7f || (code >= 0x80 && code <= 0x9f)) {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return character;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
private styleListLine(line: string, width: number): string {
|
|
430
|
+
const trimmed = line.trim();
|
|
431
|
+
|
|
432
|
+
if (trimmed.startsWith("(")) {
|
|
433
|
+
return truncateToWidth(this.theme.fg("dim", line), width, "");
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (line.startsWith(" ")) {
|
|
437
|
+
return truncateToWidth(this.theme.fg("muted", line), width, "");
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (line.startsWith("→")) {
|
|
441
|
+
return truncateToWidth(this.theme.fg("accent", this.theme.bold(line)), width, "");
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return truncateToWidth(this.theme.fg("text", line), width, "");
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private getSplitPaneWidths(width: number): { left: number; right: number } | null {
|
|
448
|
+
if (width < SINGLE_SELECT_SPLIT_PANE_MIN_WIDTH) return null;
|
|
449
|
+
|
|
450
|
+
const availableWidth = width - SINGLE_SELECT_SPLIT_PANE_SEPARATOR.length;
|
|
451
|
+
if (availableWidth < SINGLE_SELECT_SPLIT_PANE_LEFT_MIN_WIDTH + SINGLE_SELECT_SPLIT_PANE_RIGHT_MIN_WIDTH) {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const preferredLeftWidth = Math.floor(availableWidth * 0.42);
|
|
456
|
+
const left = Math.max(
|
|
457
|
+
SINGLE_SELECT_SPLIT_PANE_LEFT_MIN_WIDTH,
|
|
458
|
+
Math.min(preferredLeftWidth, availableWidth - SINGLE_SELECT_SPLIT_PANE_RIGHT_MIN_WIDTH),
|
|
459
|
+
);
|
|
460
|
+
const right = availableWidth - left;
|
|
461
|
+
|
|
462
|
+
if (right < SINGLE_SELECT_SPLIT_PANE_RIGHT_MIN_WIDTH) return null;
|
|
463
|
+
return { left, right };
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
private buildListLines(width: number, filteredOptions: QuestionOption[], hideDescriptions = false): string[] {
|
|
467
|
+
const lines: string[] = [];
|
|
468
|
+
const count = this.getItemCount(filteredOptions);
|
|
469
|
+
const searchValue = this.searchQuery ? this.theme.fg("text", this.searchQuery) : this.theme.fg("dim", "type to filter");
|
|
470
|
+
lines.push(truncateToWidth(`${this.theme.fg("accent", "Filter:")} ${searchValue}`, width, ""));
|
|
471
|
+
|
|
472
|
+
if (this.searchQuery && filteredOptions.length === 0) {
|
|
473
|
+
lines.push(truncateToWidth(this.theme.fg("warning", "No matching options"), width, ""));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (count === 0) {
|
|
477
|
+
if (!this.searchQuery) {
|
|
478
|
+
lines.push(truncateToWidth(this.theme.fg("warning", "No options"), width, ""));
|
|
479
|
+
}
|
|
480
|
+
return lines.slice(0, this.maxVisibleRows);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const maxRows = Math.max(1, this.maxVisibleRows - lines.length);
|
|
484
|
+
const optionLines = renderSingleSelectRows({
|
|
485
|
+
options: filteredOptions,
|
|
486
|
+
selectedIndex: this.selectedIndex,
|
|
487
|
+
width,
|
|
488
|
+
allowFreeform: this.allowFreeform,
|
|
489
|
+
maxRows,
|
|
490
|
+
hideDescriptions,
|
|
491
|
+
}).map((line) => this.styleListLine(line, width));
|
|
492
|
+
|
|
493
|
+
lines.push(...optionLines);
|
|
494
|
+
return lines.slice(0, this.maxVisibleRows);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private buildPreviewLines(width: number, filteredOptions: QuestionOption[], maxLines: number): string[] {
|
|
498
|
+
if (maxLines <= 0) return [];
|
|
499
|
+
|
|
500
|
+
let mdTheme: MarkdownTheme | undefined;
|
|
501
|
+
try {
|
|
502
|
+
mdTheme = getMarkdownTheme();
|
|
503
|
+
} catch {}
|
|
504
|
+
|
|
505
|
+
// Build a markdown string for the preview content
|
|
506
|
+
let md = "";
|
|
507
|
+
|
|
508
|
+
if (this.isFreeformRow(this.selectedIndex, filteredOptions)) {
|
|
509
|
+
md += "## Custom response\n\n";
|
|
510
|
+
md += "Open the editor to write **any** answer.\n\n";
|
|
511
|
+
md += "*Use this when none of the listed options fit.*\n";
|
|
512
|
+
if (this.searchQuery) {
|
|
513
|
+
md += `\n> Current filter: \`${this.searchQuery}\`\n`;
|
|
514
|
+
}
|
|
515
|
+
} else {
|
|
516
|
+
const selected = filteredOptions[this.selectedIndex];
|
|
517
|
+
if (!selected) {
|
|
518
|
+
md += "*No option selected*\n";
|
|
519
|
+
} else {
|
|
520
|
+
md += `## ${selected.title}\n\n`;
|
|
521
|
+
if (selected.description?.trim()) {
|
|
522
|
+
md += `${selected.description}\n`;
|
|
523
|
+
} else {
|
|
524
|
+
md += "*No additional details provided for this option.*\n";
|
|
525
|
+
}
|
|
526
|
+
md += `\n---\n\nPress \`Enter\` to select this option.\n`;
|
|
527
|
+
if (this.searchQuery) {
|
|
528
|
+
md += `\n> Filter: \`${this.searchQuery}\`\n`;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Render via Markdown component if theme is available, otherwise fall back to plain text
|
|
534
|
+
let lines: string[];
|
|
535
|
+
if (mdTheme) {
|
|
536
|
+
const mdComponent = new Markdown(md.trim(), 0, 0, mdTheme);
|
|
537
|
+
lines = mdComponent.render(width);
|
|
538
|
+
} else {
|
|
539
|
+
lines = [];
|
|
540
|
+
for (const line of wrapTextWithAnsi(md.trim(), Math.max(10, width))) {
|
|
541
|
+
lines.push(truncateToWidth(line, width, ""));
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Trim trailing blanks
|
|
546
|
+
while (lines.length > 0 && lines[lines.length - 1]?.trim() === "") {
|
|
547
|
+
lines.pop();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (lines.length <= maxLines) return lines;
|
|
551
|
+
if (maxLines === 1) return [truncateToWidth(this.theme.fg("dim", "…"), width, "")];
|
|
552
|
+
|
|
553
|
+
const visibleLines = lines.slice(0, maxLines - 1);
|
|
554
|
+
visibleLines.push(truncateToWidth(this.theme.fg("dim", "…"), width, ""));
|
|
555
|
+
return visibleLines;
|
|
306
556
|
}
|
|
307
557
|
|
|
308
558
|
handleInput(data: string): void {
|
|
309
|
-
if (
|
|
310
|
-
this.
|
|
559
|
+
if (this.searchQuery && matchesKey(data, Key.escape)) {
|
|
560
|
+
this.setSearchQuery("");
|
|
311
561
|
return;
|
|
312
562
|
}
|
|
313
563
|
|
|
314
|
-
|
|
315
|
-
if (count === 0) {
|
|
564
|
+
if (this.keybindings.matches(data, "tui.select.cancel")) {
|
|
316
565
|
this.onCancel?.();
|
|
317
566
|
return;
|
|
318
567
|
}
|
|
319
568
|
|
|
320
|
-
|
|
569
|
+
const filteredOptions = this.getFilteredOptions();
|
|
570
|
+
const count = this.getItemCount(filteredOptions);
|
|
571
|
+
|
|
572
|
+
if ((this.keybindings.matches(data, "tui.select.up") || matchesKey(data, Key.shift("tab"))) && count > 0) {
|
|
321
573
|
this.selectedIndex = this.selectedIndex === 0 ? count - 1 : this.selectedIndex - 1;
|
|
322
574
|
this.invalidate();
|
|
323
575
|
return;
|
|
324
576
|
}
|
|
325
577
|
|
|
326
|
-
if (
|
|
578
|
+
if ((this.keybindings.matches(data, "tui.select.down") || matchesKey(data, Key.tab)) && count > 0) {
|
|
327
579
|
this.selectedIndex = this.selectedIndex === count - 1 ? 0 : this.selectedIndex + 1;
|
|
328
580
|
this.invalidate();
|
|
329
581
|
return;
|
|
330
582
|
}
|
|
331
583
|
|
|
332
584
|
const numMatch = data.match(/^[1-9]$/);
|
|
333
|
-
if (numMatch) {
|
|
585
|
+
if (numMatch && count > 0) {
|
|
334
586
|
const idx = Number.parseInt(numMatch[0], 10) - 1;
|
|
335
587
|
if (idx >= 0 && idx < count) {
|
|
336
588
|
this.selectedIndex = idx;
|
|
337
589
|
this.invalidate();
|
|
590
|
+
return;
|
|
338
591
|
}
|
|
339
|
-
return;
|
|
340
592
|
}
|
|
341
593
|
|
|
342
|
-
if (
|
|
343
|
-
if (this.isFreeformRow(this.selectedIndex)) {
|
|
594
|
+
if (this.keybindings.matches(data, "tui.select.confirm") && count > 0) {
|
|
595
|
+
if (this.isFreeformRow(this.selectedIndex, filteredOptions)) {
|
|
344
596
|
this.onEnterFreeform?.();
|
|
345
597
|
return;
|
|
346
598
|
}
|
|
347
599
|
|
|
348
|
-
const result =
|
|
600
|
+
const result = filteredOptions[this.selectedIndex]?.title;
|
|
349
601
|
if (result) this.onSubmit?.(result);
|
|
350
602
|
else this.onCancel?.();
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (this.keybindings.matches(data, "tui.editor.deleteCharBackward") || matchesKey(data, Key.backspace)) {
|
|
607
|
+
this.popSearchCharacter();
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const printableInput = this.getPrintableInput(data);
|
|
612
|
+
if (printableInput) {
|
|
613
|
+
this.setSearchQuery(this.searchQuery + printableInput);
|
|
351
614
|
}
|
|
352
615
|
}
|
|
353
616
|
|
|
@@ -356,37 +619,26 @@ class WrappedSingleSelectList implements Component {
|
|
|
356
619
|
return this.cachedLines;
|
|
357
620
|
}
|
|
358
621
|
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
this.cachedWidth = width;
|
|
363
|
-
return this.cachedLines;
|
|
364
|
-
}
|
|
622
|
+
const filteredOptions = this.getFilteredOptions();
|
|
623
|
+
const count = this.getItemCount(filteredOptions);
|
|
624
|
+
this.selectedIndex = count > 0 ? Math.max(0, Math.min(this.selectedIndex, count - 1)) : 0;
|
|
365
625
|
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
selectedIndex: this.selectedIndex,
|
|
369
|
-
width,
|
|
370
|
-
allowFreeform: this.allowFreeform,
|
|
371
|
-
maxRows: this.maxVisibleRows,
|
|
372
|
-
}).map((line) => {
|
|
373
|
-
const trimmed = line.trim();
|
|
374
|
-
let styled = line;
|
|
375
|
-
|
|
376
|
-
if (trimmed.startsWith("(")) {
|
|
377
|
-
styled = this.theme.fg("dim", line);
|
|
378
|
-
} else if (line.startsWith(" ")) {
|
|
379
|
-
styled = this.theme.fg("muted", line);
|
|
380
|
-
} else if (line.startsWith("→")) {
|
|
381
|
-
styled = this.theme.fg("accent", this.theme.bold(line));
|
|
382
|
-
} else if (trimmed.startsWith("Type something.")) {
|
|
383
|
-
styled = this.theme.fg("text", line);
|
|
384
|
-
} else {
|
|
385
|
-
styled = this.theme.fg("text", line);
|
|
386
|
-
}
|
|
626
|
+
const splitPane = this.getSplitPaneWidths(width);
|
|
627
|
+
let lines: string[];
|
|
387
628
|
|
|
388
|
-
|
|
389
|
-
|
|
629
|
+
if (!splitPane) {
|
|
630
|
+
lines = this.buildListLines(width, filteredOptions);
|
|
631
|
+
} else {
|
|
632
|
+
const listLines = this.buildListLines(splitPane.left, filteredOptions, true);
|
|
633
|
+
const previewLines = this.buildPreviewLines(splitPane.right, filteredOptions, this.maxVisibleRows);
|
|
634
|
+
const rowCount = Math.min(this.maxVisibleRows, Math.max(listLines.length, previewLines.length));
|
|
635
|
+
const separator = this.theme.fg("dim", SINGLE_SELECT_SPLIT_PANE_SEPARATOR);
|
|
636
|
+
lines = Array.from({ length: rowCount }, (_, index) => {
|
|
637
|
+
const left = truncateToWidth(listLines[index] ?? "", splitPane.left, "", true);
|
|
638
|
+
const right = truncateToWidth(previewLines[index] ?? "", splitPane.right, "");
|
|
639
|
+
return `${left}${separator}${right}`;
|
|
640
|
+
});
|
|
641
|
+
}
|
|
390
642
|
|
|
391
643
|
this.cachedWidth = width;
|
|
392
644
|
this.cachedLines = lines;
|
|
@@ -406,7 +658,8 @@ class AskComponent extends Container {
|
|
|
406
658
|
private allowFreeform: boolean;
|
|
407
659
|
private tui: TUI;
|
|
408
660
|
private theme: Theme;
|
|
409
|
-
private
|
|
661
|
+
private keybindings: KeybindingsManager;
|
|
662
|
+
private onDone: (result: AskUIResult | null) => void;
|
|
410
663
|
|
|
411
664
|
private mode: AskMode = "select";
|
|
412
665
|
|
|
@@ -442,7 +695,8 @@ class AskComponent extends Container {
|
|
|
442
695
|
allowFreeform: boolean,
|
|
443
696
|
tui: TUI,
|
|
444
697
|
theme: Theme,
|
|
445
|
-
|
|
698
|
+
keybindings: KeybindingsManager,
|
|
699
|
+
onDone: (result: AskUIResult | null) => void,
|
|
446
700
|
) {
|
|
447
701
|
super();
|
|
448
702
|
|
|
@@ -453,10 +707,15 @@ class AskComponent extends Container {
|
|
|
453
707
|
this.allowFreeform = allowFreeform;
|
|
454
708
|
this.tui = tui;
|
|
455
709
|
this.theme = theme;
|
|
710
|
+
this.keybindings = keybindings;
|
|
456
711
|
this.onDone = onDone;
|
|
457
712
|
|
|
458
713
|
// Layout skeleton
|
|
459
|
-
this.addChild(new
|
|
714
|
+
this.addChild(new BoxBorderTop(
|
|
715
|
+
(s: string) => theme.fg("accent", s),
|
|
716
|
+
"ask_user",
|
|
717
|
+
(s: string) => theme.fg("dim", theme.bold(s)),
|
|
718
|
+
));
|
|
460
719
|
this.addChild(new Spacer(1));
|
|
461
720
|
|
|
462
721
|
this.titleText = new Text("", 1, 0);
|
|
@@ -490,7 +749,11 @@ class AskComponent extends Container {
|
|
|
490
749
|
this.addChild(this.helpText);
|
|
491
750
|
|
|
492
751
|
this.addChild(new Spacer(1));
|
|
493
|
-
this.addChild(new
|
|
752
|
+
this.addChild(new BoxBorderBottom(
|
|
753
|
+
(s: string) => theme.fg("accent", s),
|
|
754
|
+
`v${ASK_USER_VERSION}`,
|
|
755
|
+
(s: string) => theme.fg("dim", s),
|
|
756
|
+
));
|
|
494
757
|
|
|
495
758
|
this.updateStaticText();
|
|
496
759
|
this.showSelectMode();
|
|
@@ -503,16 +766,31 @@ class AskComponent extends Container {
|
|
|
503
766
|
}
|
|
504
767
|
|
|
505
768
|
override render(width: number): string[] {
|
|
769
|
+
const innerWidth = Math.max(1, width - BOX_BORDER_OVERHEAD);
|
|
770
|
+
|
|
506
771
|
if (this.mode === "select" && !this.allowMultiple) {
|
|
507
772
|
const overlayMaxHeight = Math.max(12, Math.floor(this.tui.terminal.rows * ASK_OVERLAY_MAX_HEIGHT_RATIO));
|
|
508
|
-
const staticLines = this.countStaticLines(
|
|
773
|
+
const staticLines = this.countStaticLines(innerWidth);
|
|
509
774
|
const availableOptionRows = Math.max(4, overlayMaxHeight - staticLines);
|
|
510
775
|
this.ensureSingleSelectList().setMaxVisibleRows(availableOptionRows);
|
|
511
776
|
}
|
|
512
777
|
|
|
513
|
-
//
|
|
514
|
-
const
|
|
515
|
-
|
|
778
|
+
// Render children at the inner width (excluding side border characters)
|
|
779
|
+
const rawLines = super.render(innerWidth);
|
|
780
|
+
|
|
781
|
+
// First and last lines are the top/bottom box borders — pass through at full width.
|
|
782
|
+
// All inner lines get wrapped with side borders.
|
|
783
|
+
const borderColor = (s: string) => this.theme.fg("accent", s);
|
|
784
|
+
const titleColor = (s: string) => this.theme.fg("dim", this.theme.bold(s));
|
|
785
|
+
return rawLines.map((line, index) => {
|
|
786
|
+
if (index === 0 || index === rawLines.length - 1) {
|
|
787
|
+
// Box top/bottom borders already rendered at innerWidth — re-render at full width
|
|
788
|
+
if (index === 0) return new BoxBorderTop(borderColor, "ask_user", titleColor).render(width)[0];
|
|
789
|
+
return new BoxBorderBottom(borderColor, `v${ASK_USER_VERSION}`, (s: string) => this.theme.fg("dim", s)).render(width)[0];
|
|
790
|
+
}
|
|
791
|
+
const padded = truncateToWidth(line, innerWidth, "", true);
|
|
792
|
+
return `${borderColor(BOX_BORDER_LEFT)}${padded}${borderColor(BOX_BORDER_RIGHT)}`;
|
|
793
|
+
});
|
|
516
794
|
}
|
|
517
795
|
|
|
518
796
|
private countWrappedLines(text: string, width: number): number {
|
|
@@ -549,30 +827,45 @@ class AskComponent extends Container {
|
|
|
549
827
|
private updateHelpText(): void {
|
|
550
828
|
const theme = this.theme;
|
|
551
829
|
if (this.mode === "freeform") {
|
|
830
|
+
const alternateCancelKeys = this.keybindings
|
|
831
|
+
.getKeys("tui.select.cancel")
|
|
832
|
+
.filter((key) => key !== "escape" && key !== "esc");
|
|
552
833
|
const hints = [
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
]
|
|
834
|
+
keybindingHint(theme, this.keybindings, "tui.input.submit", "submit"),
|
|
835
|
+
keybindingHint(theme, this.keybindings, "tui.input.newLine", "newline"),
|
|
836
|
+
literalHint(theme, "esc", "back"),
|
|
837
|
+
alternateCancelKeys.length > 0 ? literalHint(theme, formatKeyList(alternateCancelKeys), "cancel") : null,
|
|
838
|
+
]
|
|
839
|
+
.filter((hint): hint is string => !!hint)
|
|
840
|
+
.join(" • ");
|
|
558
841
|
this.helpText.setText(theme.fg("dim", hints));
|
|
559
842
|
return;
|
|
560
843
|
}
|
|
561
844
|
|
|
562
845
|
if (this.allowMultiple) {
|
|
563
846
|
const hints = [
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
847
|
+
literalHint(theme, "↑↓", "navigate"),
|
|
848
|
+
literalHint(theme, "space", "toggle"),
|
|
849
|
+
keybindingHint(theme, this.keybindings, "tui.select.confirm", "submit"),
|
|
850
|
+
keybindingHint(theme, this.keybindings, "tui.select.cancel", "cancel"),
|
|
568
851
|
].join(" • ");
|
|
569
852
|
this.helpText.setText(theme.fg("dim", hints));
|
|
570
853
|
} else {
|
|
854
|
+
const alternateCancelKeys = this.keybindings
|
|
855
|
+
.getKeys("tui.select.cancel")
|
|
856
|
+
.filter((key) => key !== "escape" && key !== "esc");
|
|
571
857
|
const hints = [
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
858
|
+
literalHint(theme, "type", "filter"),
|
|
859
|
+
keybindingHint(theme, this.keybindings, "tui.editor.deleteCharBackward", "erase"),
|
|
860
|
+
literalHint(theme, "↑↓", "navigate"),
|
|
861
|
+
keybindingHint(theme, this.keybindings, "tui.select.confirm", "select"),
|
|
862
|
+
literalHint(theme, "esc", "clear/cancel"),
|
|
863
|
+
alternateCancelKeys.length > 0
|
|
864
|
+
? literalHint(theme, formatKeyList(alternateCancelKeys), "cancel")
|
|
865
|
+
: null,
|
|
866
|
+
]
|
|
867
|
+
.filter((hint): hint is string => !!hint)
|
|
868
|
+
.join(" • ");
|
|
576
869
|
this.helpText.setText(theme.fg("dim", hints));
|
|
577
870
|
}
|
|
578
871
|
}
|
|
@@ -580,8 +873,8 @@ class AskComponent extends Container {
|
|
|
580
873
|
private ensureSingleSelectList(): WrappedSingleSelectList {
|
|
581
874
|
if (this.singleSelectList) return this.singleSelectList;
|
|
582
875
|
|
|
583
|
-
const list = new WrappedSingleSelectList(this.options, this.allowFreeform, this.theme);
|
|
584
|
-
list.onSubmit = (result) => this.onDone(result);
|
|
876
|
+
const list = new WrappedSingleSelectList(this.options, this.allowFreeform, this.theme, this.keybindings);
|
|
877
|
+
list.onSubmit = (result) => this.onDone({ answer: result, wasCustom: false });
|
|
585
878
|
list.onCancel = () => this.onDone(null);
|
|
586
879
|
list.onEnterFreeform = () => this.showFreeformMode();
|
|
587
880
|
|
|
@@ -592,9 +885,9 @@ class AskComponent extends Container {
|
|
|
592
885
|
private ensureMultiSelectList(): MultiSelectList {
|
|
593
886
|
if (this.multiSelectList) return this.multiSelectList;
|
|
594
887
|
|
|
595
|
-
const list = new MultiSelectList(this.options, this.allowFreeform, this.theme);
|
|
888
|
+
const list = new MultiSelectList(this.options, this.allowFreeform, this.theme, this.keybindings);
|
|
596
889
|
list.onCancel = () => this.onDone(null);
|
|
597
|
-
list.onSubmit = (result) => this.onDone(result);
|
|
890
|
+
list.onSubmit = (result) => this.onDone({ answer: result, wasCustom: false });
|
|
598
891
|
list.onEnterFreeform = () => this.showFreeformMode();
|
|
599
892
|
|
|
600
893
|
this.multiSelectList = list;
|
|
@@ -603,11 +896,11 @@ class AskComponent extends Container {
|
|
|
603
896
|
|
|
604
897
|
private ensureEditor(): Editor {
|
|
605
898
|
if (this.editor) return this.editor;
|
|
606
|
-
const editor = new Editor(createEditorTheme(this.theme));
|
|
899
|
+
const editor = new Editor(this.tui, createEditorTheme(this.theme));
|
|
607
900
|
editor.disableSubmit = false;
|
|
608
901
|
editor.onSubmit = (text: string) => {
|
|
609
902
|
const trimmed = text.trim();
|
|
610
|
-
this.onDone(trimmed ? trimmed : null);
|
|
903
|
+
this.onDone(trimmed ? { answer: trimmed, wasCustom: true } : null);
|
|
611
904
|
};
|
|
612
905
|
this.editor = editor;
|
|
613
906
|
return editor;
|
|
@@ -651,18 +944,11 @@ class AskComponent extends Container {
|
|
|
651
944
|
return;
|
|
652
945
|
}
|
|
653
946
|
|
|
654
|
-
if (
|
|
947
|
+
if (this.keybindings.matches(data, "tui.select.cancel")) {
|
|
655
948
|
this.onDone(null);
|
|
656
949
|
return;
|
|
657
950
|
}
|
|
658
951
|
|
|
659
|
-
if (matchesKey(data, Key.ctrl("enter")) || matchesKey(data, "ctrl+enter")) {
|
|
660
|
-
const editor = this.ensureEditor();
|
|
661
|
-
const text = editor.getText().trim();
|
|
662
|
-
this.onDone(text ? text : null);
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
952
|
this.ensureEditor().handleInput(data);
|
|
667
953
|
this.tui.requestRender();
|
|
668
954
|
return;
|
|
@@ -685,11 +971,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
685
971
|
name: "ask_user",
|
|
686
972
|
label: "Ask User",
|
|
687
973
|
description:
|
|
688
|
-
"Ask the user a question with optional multiple-choice answers. Use this to gather information interactively. Before calling, gather context with tools (read/
|
|
974
|
+
"Ask the user a question with optional multiple-choice answers. Use this to gather information interactively. Before calling, gather context with tools (read/web/ref) and pass a short summary via the context field.",
|
|
689
975
|
promptSnippet:
|
|
690
976
|
"Ask the user a question with optional multiple-choice answers to gather information interactively",
|
|
691
977
|
promptGuidelines: [
|
|
692
|
-
"Before calling ask_user, gather context with tools (read/
|
|
978
|
+
"Before calling ask_user, gather context with tools (read/web/ref) and pass a short summary via the context field.",
|
|
693
979
|
"Use ask_user when the user's intent is ambiguous, when a decision requires explicit user input, or when multiple valid options exist.",
|
|
694
980
|
],
|
|
695
981
|
parameters: Type.Object({
|
|
@@ -783,10 +1069,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
783
1069
|
details: { question, context: normalizedContext, options, answer: null, cancelled: false },
|
|
784
1070
|
});
|
|
785
1071
|
|
|
786
|
-
let result:
|
|
1072
|
+
let result: AskUIResult | null;
|
|
787
1073
|
try {
|
|
788
|
-
result = await ctx.ui.custom<
|
|
789
|
-
(tui, theme,
|
|
1074
|
+
result = await ctx.ui.custom<AskUIResult | null>(
|
|
1075
|
+
(tui, theme, keybindings, done) => {
|
|
790
1076
|
// Wire AbortSignal so agent cancellation auto-dismisses the overlay
|
|
791
1077
|
if (signal) {
|
|
792
1078
|
const onAbort = () => done(null);
|
|
@@ -806,6 +1092,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
806
1092
|
allowFreeform,
|
|
807
1093
|
tui,
|
|
808
1094
|
theme,
|
|
1095
|
+
keybindings,
|
|
809
1096
|
done,
|
|
810
1097
|
);
|
|
811
1098
|
},
|
|
@@ -838,10 +1125,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
838
1125
|
};
|
|
839
1126
|
}
|
|
840
1127
|
|
|
841
|
-
pi.events.emit("ask:answered", {
|
|
1128
|
+
pi.events.emit("ask:answered", {
|
|
1129
|
+
question,
|
|
1130
|
+
context: normalizedContext,
|
|
1131
|
+
answer: result.answer,
|
|
1132
|
+
wasCustom: result.wasCustom,
|
|
1133
|
+
});
|
|
842
1134
|
return {
|
|
843
|
-
content: [{ type: "text", text: `User answered: ${result}` }],
|
|
844
|
-
details: {
|
|
1135
|
+
content: [{ type: "text", text: `User answered: ${result.answer}` }],
|
|
1136
|
+
details: {
|
|
1137
|
+
question,
|
|
1138
|
+
context: normalizedContext,
|
|
1139
|
+
options,
|
|
1140
|
+
answer: result.answer,
|
|
1141
|
+
cancelled: false,
|
|
1142
|
+
wasCustom: result.wasCustom,
|
|
1143
|
+
} as AskToolDetails,
|
|
845
1144
|
};
|
|
846
1145
|
},
|
|
847
1146
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-ask-user",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Interactive ask_user tool for pi-coding-agent with multi-select and freeform input
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "Interactive ask_user tool for pi-coding-agent with searchable split-pane selection UI, multi-select, and freeform input",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
package/single-select-layout.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface RenderSingleSelectRowsParams {
|
|
|
9
9
|
width: number;
|
|
10
10
|
allowFreeform: boolean;
|
|
11
11
|
maxRows?: number;
|
|
12
|
+
hideDescriptions?: boolean;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function wrapText(text: string, width: number): string[] {
|
|
@@ -42,12 +43,12 @@ function wrapText(text: string, width: number): string[] {
|
|
|
42
43
|
if (word.length <= width) {
|
|
43
44
|
current = word;
|
|
44
45
|
} else {
|
|
46
|
+
current = "";
|
|
45
47
|
for (let i = 0; i < word.length; i += width) {
|
|
46
48
|
const chunk = word.slice(i, i + width);
|
|
47
49
|
if (chunk.length === width || i + width < word.length) lines.push(chunk);
|
|
48
50
|
else current = chunk;
|
|
49
51
|
}
|
|
50
|
-
if (!current || current.length === width) current = "";
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
@@ -69,6 +70,7 @@ function buildItemBlocks(
|
|
|
69
70
|
width: number,
|
|
70
71
|
allowFreeform: boolean,
|
|
71
72
|
selectedIndex: number,
|
|
73
|
+
hideDescriptions = false,
|
|
72
74
|
): ItemBlock[] {
|
|
73
75
|
const normalizedWidth = Math.max(12, width);
|
|
74
76
|
const freeformLabel = "Type something. — Enter a custom response";
|
|
@@ -97,7 +99,7 @@ function buildItemBlocks(
|
|
|
97
99
|
lines.push(padLine(lineIndex === 0 ? numberPrefix : continuationPrefix, line));
|
|
98
100
|
});
|
|
99
101
|
|
|
100
|
-
if (item.option.description) {
|
|
102
|
+
if (item.option.description && !hideDescriptions) {
|
|
101
103
|
const descriptionPrefix = " ";
|
|
102
104
|
const descriptionLines = wrapText(
|
|
103
105
|
item.option.description,
|
|
@@ -122,9 +124,10 @@ export function renderSingleSelectRows({
|
|
|
122
124
|
width,
|
|
123
125
|
allowFreeform,
|
|
124
126
|
maxRows,
|
|
127
|
+
hideDescriptions,
|
|
125
128
|
}: RenderSingleSelectRowsParams): string[] {
|
|
126
129
|
const itemCount = options.length + (allowFreeform ? 1 : 0);
|
|
127
|
-
const blocks = buildItemBlocks(options, width, allowFreeform, selectedIndex);
|
|
130
|
+
const blocks = buildItemBlocks(options, width, allowFreeform, selectedIndex, hideDescriptions);
|
|
128
131
|
const allRows = flatten(blocks);
|
|
129
132
|
|
|
130
133
|
if (!Number.isFinite(maxRows) || !maxRows || maxRows <= 0 || allRows.length <= maxRows) {
|