forked from denoland/std
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathunstable_prompt_select.ts
91 lines (78 loc) · 2.51 KB
/
unstable_prompt_select.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
// Copyright 2018-2025 the Deno authors. MIT license.
/** Options for {@linkcode promptSelect}. */
export interface PromptSelectOptions {
/** Clear the lines after the user's input. */
clear?: boolean;
}
const ETX = "\x03";
const ARROW_UP = "\u001B[A";
const ARROW_DOWN = "\u001B[B";
const CR = "\r";
const INDICATOR = "❯";
const PADDING = " ".repeat(INDICATOR.length);
const input = Deno.stdin;
const output = Deno.stdout;
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const CLR_ALL = encoder.encode("\x1b[J"); // Clear all lines after cursor
const HIDE_CURSOR = encoder.encode("\x1b[?25l");
const SHOW_CURSOR = encoder.encode("\x1b[?25h");
/**
* Shows the given message and waits for the user's input. Returns the user's selected value as string.
*
* @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 string that was entered or `null` if stdin is not a TTY.
*
* @example Usage
* ```ts ignore
* import { promptSelect } from "@std/cli/prompt-select";
*
* const browser = promptSelect("Please select a browser:", ["safari", "chrome", "firefox"], { clear: true });
* ```
*/
export function promptSelect(
message: string,
values: string[],
{ clear }: PromptSelectOptions = {},
): string | null {
if (!input.isTerminal()) return null;
const length = values.length;
let selectedIndex = 0;
input.setRaw(true);
output.writeSync(HIDE_CURSOR);
const buffer = new Uint8Array(4);
loop:
while (true) {
output.writeSync(encoder.encode(`${message}\r\n`));
for (const [index, value] of values.entries()) {
const start = index === selectedIndex ? INDICATOR : PADDING;
output.writeSync(encoder.encode(`${start} ${value}\r\n`));
}
const n = input.readSync(buffer);
if (n === null || n === 0) break;
const string = decoder.decode(buffer.slice(0, n));
switch (string) {
case ETX:
output.writeSync(SHOW_CURSOR);
return Deno.exit(0);
case ARROW_UP:
selectedIndex = (selectedIndex - 1 + length) % length;
break;
case ARROW_DOWN:
selectedIndex = (selectedIndex + 1) % length;
break;
case CR:
break loop;
}
output.writeSync(encoder.encode(`\x1b[${length + 1}A`));
}
if (clear) {
output.writeSync(encoder.encode(`\x1b[${length + 1}A`));
output.writeSync(CLR_ALL);
}
output.writeSync(SHOW_CURSOR);
input.setRaw(false);
return values[selectedIndex] ?? null;
}