Skip to content

Commit

Permalink
feat(cli/unstable): add promptMultipleSelect() (#6223)
Browse files Browse the repository at this point in the history
Co-authored-by: Yoshiya Hinosawa <stibium121@gmail.com>
  • Loading branch information
timreichen and kt3k authored Dec 12, 2024
1 parent 6828931 commit aa81c09
Show file tree
Hide file tree
Showing 4 changed files with 647 additions and 0 deletions.
1 change: 1 addition & 0 deletions _tools/check_docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const ENTRY_POINTS = [
"../cbor/mod.ts",
"../cli/mod.ts",
"../cli/unstable_spinner.ts",
"../cli/unstable_prompt_multiple_select.ts",
"../crypto/mod.ts",
"../collections/mod.ts",
"../csv/mod.ts",
Expand Down
1 change: 1 addition & 0 deletions cli/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"./parse-args": "./parse_args.ts",
"./prompt-secret": "./prompt_secret.ts",
"./unstable-prompt-select": "./unstable_prompt_select.ts",
"./unstable-prompt-multiple-select": "./unstable_prompt_multiple_select.ts",
"./unstable-spinner": "./unstable_spinner.ts",
"./unicode-width": "./unicode_width.ts"
}
Expand Down
97 changes: 97 additions & 0 deletions cli/unstable_prompt_multiple_select.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

/** Options for {@linkcode promptMultipleSelect}. */
export interface PromptMultipleSelectOptions {
/** 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 CHECKED = "◉";
const UNCHECKED = "◯";

const encoder = new TextEncoder();
const decoder = new TextDecoder();

const CLEAR_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 selected values as an array of strings.
*
* @example Usage
* ```ts ignore
* import { promptMultipleSelect } from "@std/cli/unstable-prompt-multiple-select";
*
* const browsers = promptMultipleSelect("Please select browsers:", ["safari", "chrome", "firefox"], { clear: true });
* ```
*/
export function promptMultipleSelect(
message: string,
values: string[],
options: PromptMultipleSelectOptions = {},
): string[] {
const { clear } = options;
const length = values.length;
let selectedIndex = 0;
const selectedIndexes = new Set<number>();

Deno.stdin.setRaw(true);
Deno.stdout.writeSync(HIDE_CURSOR);
const buffer = new Uint8Array(4);
loop:
while (true) {
Deno.stdout.writeSync(encoder.encode(`${message}\r\n`));
for (const [index, value] of values.entries()) {
const selected = index === selectedIndex;
const start = selected ? INDICATOR : PADDING;
const checked = selectedIndexes.has(index);
const state = checked ? CHECKED : UNCHECKED;
Deno.stdout.writeSync(encoder.encode(`${start} ${state} ${value}\r\n`));
}
const n = Deno.stdin.readSync(buffer);
if (n === null || n === 0) break;
const input = decoder.decode(buffer.slice(0, n));

switch (input) {
case ETX:
Deno.stdout.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;
case " ":
if (selectedIndexes.has(selectedIndex)) {
selectedIndexes.delete(selectedIndex);
} else {
selectedIndexes.add(selectedIndex);
}
break;
}
Deno.stdout.writeSync(encoder.encode(`\x1b[${length + 1}A`));
}
if (clear) {
Deno.stdout.writeSync(encoder.encode(`\x1b[${length + 1}A`));
Deno.stdout.writeSync(CLEAR_ALL);
}
Deno.stdout.writeSync(SHOW_CURSOR);
Deno.stdin.setRaw(false);
return [...selectedIndexes].map((it) => values[it] as string);
}
Loading

0 comments on commit aa81c09

Please sign in to comment.