1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163 |
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x16
x30
x16
x16
x16
x16
x16
x16
x16
x158
x158
x16
x16
x16
x16
x16
x16
x16
x16
x73
x73
x74
x73
x75
x75
x73
x86
x86
x73
x86
x159
x86
x86
x86
x86
x86
x86
x73
x79
x79
x73
x82
x82
x73
x116
x16
x48
x16 |
I
|
// Copyright 2018-2025 the Deno authors. MIT license.
import { handlePromptSelect } from "./_prompt_select.ts";
/** Options for {@linkcode promptMultipleSelect}. */
export interface PromptMultipleSelectOptions {
/** Clear the lines after the user's input. */
clear?: boolean;
/** The number of lines to be visible at once */
visibleLines?: number;
/** The string to indicate the selected item */
indicator?: string;
}
/**
* Value for {@linkcode promptMultipleSelect}.
* If an object, it must have a title and a value, else it can just be a string.
*
* @typeParam V The value of the underlying Entry, if any.
*/
export type PromptEntry<V = undefined> = V extends undefined ? string
: PromptEntryWithValue<V>;
/**
* A {@linkcode PromptEntry} with an underlying value.
*
* @typeParam V The value of the underlying Entry.
*/
export interface PromptEntryWithValue<V> {
/** The label for this entry. */
label: string;
/** The underlying value representing this entry. */
value: V;
}
const ETX = "\x03";
const ARROW_UP = "\u001B[A";
const ARROW_DOWN = "\u001B[B";
const CR = "\r";
const DELETE = "\u007F";
const CHECKED = "◉";
const UNCHECKED = "◯";
const input = Deno.stdin;
/**
* Shows the given message and waits for the user's input. Returns the user's selected value as string.
*
* Also supports filtering of the options by typing.
*
* @typeParam V The value of the underlying Entry, if any.
* @param message The prompt message to show to the user.
* @param values The values for the prompt.
* @param options The options for the prompt.
* @returns The selected values as an array of strings or `null` if stdin is not a TTY.
*
* @example Usage
* ```ts ignore
* import { promptMultipleSelect } from "@std/cli/unstable-prompt-multiple-select";
*
* const browsers = promptMultipleSelect(
* "Please select browsers:",
* ["safari", "chrome", "firefox"],
* { clear: true },
* );
* ```
*
* @example With title and value
* ```ts ignore
* import { promptMultipleSelect } from "@std/cli/unstable-prompt-multiple-select";
*
* const browsers = promptMultipleSelect(
* "Please select browsers:",
* [{
* label: "safari",
* value: 1,
* }, {
* label: "chrome",
* value: 2,
* }, {
* label: "firefox",
* value: 3,
* }],
* { clear: true },
* );
* ```
*
* @example With multiple options
* ```ts ignore
* import { promptMultipleSelect } from "@std/cli/unstable-prompt-multiple-select";
*
* const browsers = promptMultipleSelect(
* "Select your favorite numbers below 100:",
* [...Array(100).keys()].map(String),
* { clear: true, visibleLines: 5, indicator: "→" },
* );
* ```
*/
export function promptMultipleSelect<V = undefined>(
message: string,
values: PromptEntry<V>[],
options: PromptMultipleSelectOptions = {},
): PromptEntry<V>[] | null {
if (!input.isTerminal()) return null;
const selectedAbsoluteIndexes = new Set<number>();
handlePromptSelect(
message,
options.indicator ?? "❯",
values,
options.clear,
options.visibleLines,
(_active, absoluteIndex) => {
const checked = selectedAbsoluteIndexes.has(absoluteIndex);
return checked ? CHECKED : UNCHECKED;
},
(str, absoluteIndex, {
etx,
up,
down,
remove,
inputStr,
}) => {
switch (str) {
case ETX:
return etx();
case ARROW_UP:
up();
break;
case ARROW_DOWN:
down();
break;
case CR:
return true;
case " ": {
if (absoluteIndex !== undefined) {
if (selectedAbsoluteIndexes.has(absoluteIndex)) {
selectedAbsoluteIndexes.delete(absoluteIndex);
} else {
selectedAbsoluteIndexes.add(absoluteIndex);
}
}
break;
}
case DELETE:
remove();
break;
default:
inputStr();
break;
}
return false;
},
);
return [...selectedAbsoluteIndexes].map((it) => values[it]!);
}
|