-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
index.ts
139 lines (121 loc) · 4.13 KB
/
index.ts
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
import {
createPrompt,
useState,
useRef,
useKeypress,
isUpKey,
isDownKey,
isSpaceKey,
isNumberKey,
isEnterKey,
Paginator,
} from '@inquirer/core';
import chalk from 'chalk';
import figures from 'figures';
import ansiEscapes from 'ansi-escapes';
export type Choice<Value> = {
name?: string;
value: Value;
disabled?: boolean;
};
type State<Value> = {
prefix?: string;
pageSize?: number;
instructions?: string | boolean;
message: string;
choices: ReadonlyArray<Choice<Value>>;
};
export default createPrompt(
<Value>(state: State<Value>, done: (value: Array<Value>) => void): string => {
const { prefix, pageSize = 7, instructions } = state;
const paginator = useRef(new Paginator()).current;
const [status, setStatus] = useState('pending');
const [choices, setChoices] = useState<Array<Choice<Value> & { checked?: boolean }>>([
...state.choices,
]);
const [cursorPosition, setCursorPosition] = useState(0);
const [showHelpTip, setShowHelpTip] = useState(false);
useKeypress((key) => {
let newCursorPosition = cursorPosition;
if (isEnterKey(key)) {
setStatus('done');
done(choices.filter((choice) => choice.checked).map((choice) => choice.value));
} else if (isUpKey(key) || isDownKey(key)) {
const offset = isUpKey(key) ? -1 : 1;
let selectedOption;
while (!selectedOption || selectedOption.disabled) {
newCursorPosition =
(newCursorPosition + offset + choices.length) % choices.length;
selectedOption = choices[newCursorPosition];
}
setCursorPosition(newCursorPosition);
} else if (isSpaceKey(key)) {
setShowHelpTip(false);
setChoices(
choices.map((choice, i) => {
if (i === cursorPosition) {
return { ...choice, checked: !choice.checked };
}
return choice;
})
);
} else if (key.name === 'a') {
const selectAll = Boolean(choices.find((choice) => !choice.checked));
setChoices(choices.map((choice) => ({ ...choice, checked: selectAll })));
} else if (key.name === 'i') {
setChoices(choices.map((choice) => ({ ...choice, checked: !choice.checked })));
} else if (isNumberKey(key)) {
// Adjust index to start at 1
const position = Number(key.name) - 1;
// Abort if the choice doesn't exists or if disabled
if (!choices[position] || choices[position]?.disabled) {
return;
}
setCursorPosition(position);
setChoices(
choices.map((choice, i) => {
if (i === position) {
return { ...choice, checked: !choice.checked };
}
return choice;
})
);
}
});
const message = chalk.bold(state.message);
if (status === 'done') {
const selection = choices
.filter((choice) => choice.checked)
.map(({ name, value }) => name || value);
return `${prefix} ${message} ${chalk.cyan(selection.join(', '))}`;
}
let helpTip = '';
if (showHelpTip && (instructions === undefined || instructions)) {
if (typeof instructions === 'string') {
helpTip = instructions;
} else {
const keys = [
`${chalk.cyan.bold('<space>')} to select`,
`${chalk.cyan.bold('<a>')} to toggle all`,
`${chalk.cyan.bold('<i>')} to invert selection`,
];
helpTip = ` (Press ${keys.join(', ')})`;
}
}
const allChoices = choices
.map(({ name, value, checked, disabled }, index) => {
const line = name || value;
if (disabled) {
return chalk.dim(` - ${line} (disabled)`);
}
const checkbox = checked ? chalk.green(figures.circleFilled) : figures.circle;
if (index === cursorPosition) {
return chalk.cyan(`${figures.pointer}${checkbox} ${line}`);
}
return ` ${checkbox} ${line}`;
})
.join('\n');
const windowedChoices = paginator.paginate(allChoices, cursorPosition, pageSize);
return `${prefix} ${message}${helpTip}\n${windowedChoices}${ansiEscapes.cursorHide}`;
}
);