hacksh 0.17.0 → 0.18.0
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 +58 -5
- package/dist/{command-t16M3-AZ.d.ts → command-Czf4qXhe.d.ts} +23 -8
- package/dist/index.d.ts +2 -2
- package/dist/index.js +91 -18
- package/dist/index.js.map +1 -1
- package/dist/{pretty-Db7-kATM.js → pretty-BjzGnVLM.js} +97 -32
- package/dist/pretty-BjzGnVLM.js.map +1 -0
- package/dist/printer-api.d.ts +2 -2
- package/dist/printer-api.js +1 -1
- package/package.json +3 -1
- package/dist/pretty-Db7-kATM.js.map +0 -1
package/README.md
CHANGED
|
@@ -528,7 +528,7 @@ finishes. A printer failure rejects the invocation without changing a successful
|
|
|
528
528
|
|
|
529
529
|
### Interactive input
|
|
530
530
|
|
|
531
|
-
Every `run` context has a `prompt` with `select`, `confirm`, and `text` methods. Interactive input
|
|
531
|
+
Every `run` context has a `prompt` with `select`, `autocomplete`, `confirm`, and `text` methods. Interactive input
|
|
532
532
|
is separate from `logger`: prompts share the invocation's selected printer, serialized output queue,
|
|
533
533
|
terminal lock, output mode, and abort signal.
|
|
534
534
|
|
|
@@ -562,15 +562,57 @@ uses `initialValue` when supplied and otherwise means no. Text displays its plac
|
|
|
562
562
|
using it as input, supports editing, and repeats the prompt only when `validate` returns a nonempty
|
|
563
563
|
message; `""` means valid. Backspace removes one Unicode code point. Input pasted after a selection
|
|
564
564
|
or confirmation is retained for the next sequential prompt instead of being discarded.
|
|
565
|
-
The built-in prompt API
|
|
566
|
-
|
|
565
|
+
The built-in prompt API does not provide a multiselect prompt.
|
|
566
|
+
|
|
567
|
+
Use `autocomplete` for a searchable single choice:
|
|
568
|
+
|
|
569
|
+
```ts
|
|
570
|
+
const environment = await prompt.autocomplete({
|
|
571
|
+
message: "Environment",
|
|
572
|
+
placeholder: "Type to filter…",
|
|
573
|
+
options: [
|
|
574
|
+
{ value: "production", label: "Production", priority: 20 },
|
|
575
|
+
{ value: "staging", label: "Staging", hint: "safe default", priority: 10 },
|
|
576
|
+
],
|
|
577
|
+
sortBy: (item) => item.priority,
|
|
578
|
+
}); // "production" | "staging"; displays Staging before Production
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
Autocomplete filters labels (falling back to values) by case-insensitive substring,
|
|
582
|
+
preserving the configured option order rather than ranking matches. An empty query
|
|
583
|
+
shows all options, initially with none selected. Down enters selection at the first
|
|
584
|
+
match; Up/Down move without wrapping. Up at the first match or Escape returns to
|
|
585
|
+
editing. Typing or Backspace while selecting edits the query and returns to input.
|
|
586
|
+
Enter submits only a selected match; it does nothing while editing or with no matches.
|
|
587
|
+
Backspace removes one grapheme cluster. Escape while editing and Ctrl-C cancel.
|
|
588
|
+
The placeholder is display-only, reappears when the query becomes empty, and remains
|
|
589
|
+
until the terminal delivers committed input; OS IME composition is not observable.
|
|
590
|
+
The pretty printer anchors the real cursor at the input end, including wrapped input.
|
|
591
|
+
|
|
592
|
+
Both `select` and `autocomplete` accept `sortBy: (item) => string | number`. The
|
|
593
|
+
callback receives the complete inferred option item, including custom fields such
|
|
594
|
+
as `priority`; the prompt still returns only its `value`, preserving literal types.
|
|
595
|
+
Without `sortBy`, options retain their definition order. With it, keys are computed
|
|
596
|
+
once per option at prompt startup and sorted ascending, stably: equal keys retain
|
|
597
|
+
definition order, and the original array is never reordered. Autocomplete filtering
|
|
598
|
+
preserves this sorted order without recomputing keys. `initialValue` on select
|
|
599
|
+
chooses a value in the sorted list without changing that order.
|
|
600
|
+
|
|
601
|
+
Numeric keys use numeric order; strings use locale-independent UTF-16 lexical
|
|
602
|
+
order, not locale-aware collation or numeric-string ordering. All keys in one
|
|
603
|
+
prompt must have the same type. Invalid keys (including `NaN`) or mixed types
|
|
604
|
+
produce `HackshPromptError` with reason `invalid-options`; a thrown callback
|
|
605
|
+
produces `input-error` with the original error as `cause`.
|
|
606
|
+
Explicit option types take the whole item type, for example
|
|
607
|
+
`SelectOptions<{ value: "staging" | "production"; priority: number }>`.
|
|
567
608
|
|
|
568
609
|
Prompts are allowed only when the resolved mode is interactive: pretty output outside CI with TTY
|
|
569
610
|
stdin and stdout. JSON, CI, and non-TTY invocations throw `HackshPromptError` immediately even if a
|
|
570
611
|
default is available. Its `reason` and `prompt` fields identify why input failed and the prompt's
|
|
571
|
-
`kind`, `message`, and select `choices`. Reasons distinguish `non-interactive`, `cancelled`,
|
|
612
|
+
`kind`, `message`, and select/autocomplete `choices`. Reasons distinguish `non-interactive`, `cancelled`,
|
|
572
613
|
`aborted`, `eof`, `overlapping`, `invalid-options`, `input-error`, and `printer-error`. Ctrl-C and
|
|
573
|
-
Escape cancel
|
|
614
|
+
Escape cancel (except Escape from autocomplete selection, which returns to editing);
|
|
615
|
+
`runMain` reports cancellation and exits with status 130. A prompt that starts or is
|
|
574
616
|
waiting after the invocation's `signal` aborts fails with `aborted` and the signal's reason as its
|
|
575
617
|
`cause`. Only one prompt may be active in an invocation. Input listeners and raw terminal mode are
|
|
576
618
|
restored after every outcome.
|
|
@@ -878,6 +920,17 @@ await showUsage(main, {
|
|
|
878
920
|
| `vp run ready` | Run check, test, and build |
|
|
879
921
|
| `pnpm test:packed` | Test the tarball with Node 22.18, host Node, and Deno |
|
|
880
922
|
|
|
923
|
+
The autocomplete API has PTY tests in `src/autocomplete-pty.test.ts`.
|
|
924
|
+
Run them with `pnpm test run src/autocomplete-pty.test.ts`. They launch
|
|
925
|
+
`src/fixtures/autocomplete-pty-cli.ts`, a CLI using the public `prompt.autocomplete`
|
|
926
|
+
API, and interpret its output with a headless terminal, checking visible choices,
|
|
927
|
+
placeholder transitions, cursor coordinates, and submission/cancellation cleanup.
|
|
928
|
+
For manual terminal verification, run `node src/fixtures/autocomplete-pty-cli.ts`.
|
|
929
|
+
Expected screens use indented multiline strings; ANSI handling stays in
|
|
930
|
+
`src/test-support/terminal.ts`. OS IME composition windows are outside this suite.
|
|
931
|
+
The development-only `node-pty` dependency requires Python and a C/C++ build
|
|
932
|
+
toolchain (Xcode Command Line Tools on macOS).
|
|
933
|
+
|
|
881
934
|
The packed-consumer test must execute its Node consumer with exactly Node 22.18.0. Run pnpm itself
|
|
882
935
|
under that version, or supply an absolute executable path explicitly:
|
|
883
936
|
|
|
@@ -91,7 +91,7 @@ declare class HackshValidationError extends HackshUsageError {
|
|
|
91
91
|
readonly issues: ReadonlyArray<HackshIssue>;
|
|
92
92
|
constructor(command: AnyCommandDef, issues: ReadonlyArray<HackshIssue>);
|
|
93
93
|
}
|
|
94
|
-
type PromptKind = "select" | "confirm" | "text";
|
|
94
|
+
type PromptKind = "select" | "autocomplete" | "confirm" | "text";
|
|
95
95
|
type HackshPromptErrorReason = "non-interactive" | "cancelled" | "aborted" | "eof" | "overlapping" | "invalid-options" | "input-error" | "printer-error";
|
|
96
96
|
interface PromptErrorDetails {
|
|
97
97
|
kind: PromptKind;
|
|
@@ -277,7 +277,15 @@ interface TextPromptState extends PromptStateBase {
|
|
|
277
277
|
value: string;
|
|
278
278
|
placeholder?: string;
|
|
279
279
|
}
|
|
280
|
-
|
|
280
|
+
interface AutocompletePromptState extends PromptStateBase {
|
|
281
|
+
kind: "autocomplete";
|
|
282
|
+
query: string;
|
|
283
|
+
placeholder?: string;
|
|
284
|
+
phase: "input" | "select";
|
|
285
|
+
choices: readonly PromptChoiceState[];
|
|
286
|
+
value?: string;
|
|
287
|
+
}
|
|
288
|
+
type PromptState = SelectPromptState | ConfirmPromptState | TextPromptState | AutocompletePromptState;
|
|
281
289
|
interface PromptPrintEvent {
|
|
282
290
|
type: "prompt";
|
|
283
291
|
prompt: PromptState;
|
|
@@ -352,10 +360,16 @@ interface SelectOption<T extends string> {
|
|
|
352
360
|
label?: string;
|
|
353
361
|
hint?: string;
|
|
354
362
|
}
|
|
355
|
-
interface
|
|
363
|
+
interface ChoiceOptions<TItem extends SelectOption<string>> {
|
|
356
364
|
message: string;
|
|
357
|
-
options: readonly
|
|
358
|
-
|
|
365
|
+
options: readonly TItem[];
|
|
366
|
+
sortBy?: (item: NoInfer<TItem>) => string | number;
|
|
367
|
+
}
|
|
368
|
+
interface SelectOptions<TItem extends SelectOption<string>> extends ChoiceOptions<TItem> {
|
|
369
|
+
initialValue?: NoInfer<TItem["value"]>;
|
|
370
|
+
}
|
|
371
|
+
interface AutocompleteOptions<TItem extends SelectOption<string>> extends ChoiceOptions<TItem> {
|
|
372
|
+
placeholder?: string;
|
|
359
373
|
}
|
|
360
374
|
interface ConfirmOptions {
|
|
361
375
|
message: string;
|
|
@@ -367,7 +381,8 @@ interface TextOptions {
|
|
|
367
381
|
validate?(value: string): string | undefined;
|
|
368
382
|
}
|
|
369
383
|
interface Prompter {
|
|
370
|
-
select<const
|
|
384
|
+
select<const TItem extends SelectOption<string>>(options: SelectOptions<TItem>): Promise<TItem["value"]>;
|
|
385
|
+
autocomplete<const TItem extends SelectOption<string>>(options: AutocompleteOptions<TItem>): Promise<TItem["value"]>;
|
|
371
386
|
confirm(options: ConfirmOptions): Promise<boolean>;
|
|
372
387
|
text(options: TextOptions): Promise<string>;
|
|
373
388
|
}
|
|
@@ -416,5 +431,5 @@ interface CommandDef<S extends ArgsSchema = EmptyArgs, R = unknown, Children ext
|
|
|
416
431
|
type AnyCommandDef = CommandDef<ArgsSchema>;
|
|
417
432
|
declare function defineCommand<S extends ArgsSchema = EmptyArgs, R = never, Children extends Record<string, AnyCommandDef> = {}>(def: CommandDef<S, R, Children>): NoInfer<CommandDef<S, R, Children>>;
|
|
418
433
|
//#endregion
|
|
419
|
-
export {
|
|
420
|
-
//# sourceMappingURL=command-
|
|
434
|
+
export { Logger as $, PromptStatus as A, resolveOutputMode as B, OutputWriter as C, PromptChoiceState as D, Printer as E, TerminalOutput as F, UsageSummary as G, UsageArgumentSummary as H, TextPromptState as I, summarizeUsage as J, renderUsage as K, UsagePrintEvent as L, SelectPromptState as M, StreamColors as N, PromptPrintEvent as O, TerminalInput as P, LogRecord as Q, VersionPrintEvent as R, OutputPrintEvent as S, PrintStream as T, UsageCommandSummary as U, ShowUsageOptions as V, UsageOptionSummary as W, LogLevel as X, LogKind as Y, LogProperties as Z, JsonPrinterOptions as _, ExecOptions as _t, ArgsSchema as a, HackshIssue as at, OutputMode as b, ExecSkipped as bt, Prompter as c, HackshUsageError as ct, TextOptions as d, PromptKind as dt, HackshError as et, Output as f, ValidationIssueSummary as ft, ExecPrintEvent as g, ExecFailureReason as gt, ErrorPrintEvent as h, ExecFailure as ht, defineCommand as i, HackshExitError as it, ResolveOutputModeOptions as j, PromptState as k, SelectOption as l, HackshValidationError as lt, ConfirmPromptState as m, ExecCommand as mt, CommandContext as n, HackshErrorSummary as nt, AutocompleteOptions as o, HackshPromptError as ot, AutocompletePromptState as p, Exec as pt, showUsage as q, CommandDef as r, HackshExecError as rt, ConfirmOptions as s, HackshPromptErrorReason as st, AnyCommandDef as t, HackshErrorKind as tt, SelectOptions as u, PromptErrorDetails as ut, LogPrintEvent as v, ExecResult as vt, PrintEvent as w, OutputPreference as x, ExecSuccess as xt, OutputFormat as y, ExecReturn as yt, createJsonPrinter as z };
|
|
435
|
+
//# sourceMappingURL=command-Czf4qXhe.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { $ as Logger, E as Printer, F as TerminalOutput, J as summarizeUsage, K as renderUsage, P as TerminalInput, V as ShowUsageOptions, _t as ExecOptions, a as ArgsSchema, at as HackshIssue, bt as ExecSkipped, c as Prompter, ct as HackshUsageError, d as TextOptions, dt as PromptKind, et as HackshError, f as Output, gt as ExecFailureReason, ht as ExecFailure, i as defineCommand, it as HackshExitError, l as SelectOption, lt as HackshValidationError, mt as ExecCommand, n as CommandContext, o as AutocompleteOptions, ot as HackshPromptError, pt as Exec, q as showUsage, r as CommandDef, rt as HackshExecError, s as ConfirmOptions, st as HackshPromptErrorReason, t as AnyCommandDef, u as SelectOptions, ut as PromptErrorDetails, vt as ExecResult, x as OutputPreference, xt as ExecSuccess, yt as ExecReturn } from "./command-Czf4qXhe.js";
|
|
2
2
|
import { output } from "zod/v4/core";
|
|
3
3
|
//#region src/parser.d.ts
|
|
4
4
|
/**
|
|
@@ -39,5 +39,5 @@ interface RunMainOptions extends OutputOptions {
|
|
|
39
39
|
}
|
|
40
40
|
declare function runMain<S extends ArgsSchema>(command: CommandDef<S>, options?: RunMainOptions): Promise<void>;
|
|
41
41
|
//#endregion
|
|
42
|
-
export { type CommandContext, type CommandDef, type ConfirmOptions, type Exec, type ExecCommand, type ExecFailure, type ExecFailureReason, type ExecOptions, type ExecResult, type ExecReturn, type ExecSkipped, type ExecSuccess, HackshError, HackshExecError, HackshExitError, type HackshIssue, HackshPromptError, type HackshPromptErrorReason, HackshUsageError, HackshValidationError, type Logger, type Output, type OutputOptions, type PromptErrorDetails, type PromptKind, type Prompter, type RunCommandOptions, type RunMainOptions, type SelectOption, type SelectOptions, type ShowUsageOptions, type TextOptions, defineCommand, parseArgs, renderUsage, runCommand, runMain, showUsage, summarizeUsage };
|
|
42
|
+
export { type AutocompleteOptions, type CommandContext, type CommandDef, type ConfirmOptions, type Exec, type ExecCommand, type ExecFailure, type ExecFailureReason, type ExecOptions, type ExecResult, type ExecReturn, type ExecSkipped, type ExecSuccess, HackshError, HackshExecError, HackshExitError, type HackshIssue, HackshPromptError, type HackshPromptErrorReason, HackshUsageError, HackshValidationError, type Logger, type Output, type OutputOptions, type PromptErrorDetails, type PromptKind, type Prompter, type RunCommandOptions, type RunMainOptions, type SelectOption, type SelectOptions, type ShowUsageOptions, type TextOptions, defineCommand, parseArgs, renderUsage, runCommand, runMain, showUsage, summarizeUsage };
|
|
43
43
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as createExec, c as HackshExitError, d as HackshValidationError, f as summarizeError, i as resolveOutputMode, l as HackshPromptError, n as createJsonPrinter, o as HackshError, p as summarizeErrors, r as createPrintQueue, s as HackshExecError, t as createPrettyPrinter, u as HackshUsageError } from "./pretty-
|
|
1
|
+
import { a as createExec, c as HackshExitError, d as HackshValidationError, f as summarizeError, i as resolveOutputMode, l as HackshPromptError, n as createJsonPrinter, o as HackshError, p as summarizeErrors, r as createPrintQueue, s as HackshExecError, t as createPrettyPrinter, u as HackshUsageError } from "./pretty-BjzGnVLM.js";
|
|
2
2
|
import { globalRegistry, safeParseAsync } from "zod/v4/core";
|
|
3
3
|
import { parseArgs as parseArgs$1 } from "node:util";
|
|
4
4
|
import { readFile, realpath } from "node:fs/promises";
|
|
@@ -619,6 +619,44 @@ function promptError(reason, prompt, message, cause) {
|
|
|
619
619
|
function validateMessage(kind, message, choices) {
|
|
620
620
|
if (typeof message !== "string" || message.length === 0) throw promptError("invalid-options", details$1(kind, message, choices), `Invalid ${kind} prompt: message must be a non-empty string`);
|
|
621
621
|
}
|
|
622
|
+
function validateChoices(kind, options) {
|
|
623
|
+
const choices = Array.isArray(options?.options) ? options.options.map((option) => typeof option === "object" && option !== null && typeof option.value === "string" ? option.value : "[Invalid choice]") : [];
|
|
624
|
+
const prompt = details$1(kind, options?.message, choices);
|
|
625
|
+
validateMessage(kind, options?.message, choices);
|
|
626
|
+
if (!Array.isArray(options.options) || options.options.length === 0) throw promptError("invalid-options", prompt, `A ${kind} prompt requires at least one choice`);
|
|
627
|
+
const seen = /* @__PURE__ */ new Set();
|
|
628
|
+
for (const option of options.options) {
|
|
629
|
+
if (typeof option !== "object" || option === null || typeof option.value !== "string" || option.value.length === 0 || option.label !== void 0 && typeof option.label !== "string" || option.hint !== void 0 && typeof option.hint !== "string") throw promptError("invalid-options", prompt, `${kind === "select" ? "Select" : "Autocomplete"} choices must have valid string values`);
|
|
630
|
+
if (seen.has(option.value)) throw promptError("invalid-options", prompt, `Duplicate ${kind} choice: ${option.value}`);
|
|
631
|
+
seen.add(option.value);
|
|
632
|
+
}
|
|
633
|
+
return prompt;
|
|
634
|
+
}
|
|
635
|
+
function sortedChoices(options, prompt) {
|
|
636
|
+
const { sortBy } = options;
|
|
637
|
+
if (sortBy === void 0) return options.options;
|
|
638
|
+
if (typeof sortBy !== "function") throw promptError("invalid-options", prompt, "sortBy must be a function");
|
|
639
|
+
let keyType;
|
|
640
|
+
const keyed = options.options.map((item) => {
|
|
641
|
+
let key;
|
|
642
|
+
try {
|
|
643
|
+
key = sortBy(item);
|
|
644
|
+
} catch (cause) {
|
|
645
|
+
throw promptError("input-error", prompt, "Choice sort key could not be computed", cause);
|
|
646
|
+
}
|
|
647
|
+
if (typeof key !== "string" && (typeof key !== "number" || Number.isNaN(key))) throw promptError("invalid-options", prompt, "sortBy must return a string or a non-NaN number");
|
|
648
|
+
const type = typeof key;
|
|
649
|
+
if (keyType !== void 0 && keyType !== type) throw promptError("invalid-options", prompt, "sortBy keys must all have the same type");
|
|
650
|
+
keyType = type;
|
|
651
|
+
return {
|
|
652
|
+
item,
|
|
653
|
+
key
|
|
654
|
+
};
|
|
655
|
+
});
|
|
656
|
+
keyed.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
|
|
657
|
+
return keyed.map(({ item }) => item);
|
|
658
|
+
}
|
|
659
|
+
const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
622
660
|
function asInput(value, prompt) {
|
|
623
661
|
if (typeof value !== "object" || value === null || typeof Reflect.get(value, "on") !== "function" || typeof Reflect.get(value, "removeListener") !== "function" || typeof Reflect.get(value, "resume") !== "function" || typeof Reflect.get(value, "pause") !== "function") throw promptError("input-error", prompt, "Interactive input is not a readable stream");
|
|
624
662
|
return value;
|
|
@@ -647,7 +685,7 @@ var PromptController = class {
|
|
|
647
685
|
throw promptError("printer-error", prompt, "The prompt could not be displayed", cause);
|
|
648
686
|
}
|
|
649
687
|
}
|
|
650
|
-
async #run(prompt, initial, accept, update, submitted, closed) {
|
|
688
|
+
async #run(prompt, initial, accept, update, submitted, closed, cancelOnEscape = () => true) {
|
|
651
689
|
if (!this.#interactive) throw promptError("non-interactive", prompt, `Cannot request ${prompt.kind} input in non-interactive mode: ${prompt.message}`);
|
|
652
690
|
if (this.#signal.aborted) throw promptError("aborted", prompt, "Prompt aborted", this.#signal.reason);
|
|
653
691
|
const input = asInput(this.#inputValue, prompt);
|
|
@@ -689,7 +727,7 @@ var PromptController = class {
|
|
|
689
727
|
};
|
|
690
728
|
const handleKeypress = async (character, key = {}) => {
|
|
691
729
|
if (settled) return;
|
|
692
|
-
if (key.ctrl && key.name === "c" || key.name === "escape") {
|
|
730
|
+
if (key.ctrl && key.name === "c" || key.name === "escape" && cancelOnEscape()) {
|
|
693
731
|
fail("cancelled", "Prompt cancelled");
|
|
694
732
|
return;
|
|
695
733
|
}
|
|
@@ -797,36 +835,71 @@ var PromptController = class {
|
|
|
797
835
|
}
|
|
798
836
|
}
|
|
799
837
|
async select(options) {
|
|
800
|
-
const
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
if (!Array.isArray(options.options) || options.options.length === 0) throw promptError("invalid-options", prompt, "A select prompt requires at least one choice");
|
|
804
|
-
const seen = /* @__PURE__ */ new Set();
|
|
805
|
-
for (const option of options.options) {
|
|
806
|
-
if (typeof option !== "object" || option === null || typeof option.value !== "string" || option.value.length === 0 || option.label !== void 0 && typeof option.label !== "string" || option.hint !== void 0 && typeof option.hint !== "string") throw promptError("invalid-options", prompt, "Select choices must have valid string values");
|
|
807
|
-
if (seen.has(option.value)) throw promptError("invalid-options", prompt, `Duplicate select choice: ${option.value}`);
|
|
808
|
-
seen.add(option.value);
|
|
809
|
-
}
|
|
810
|
-
let selected = options.initialValue === void 0 ? 0 : choices.indexOf(options.initialValue);
|
|
838
|
+
const prompt = validateChoices("select", options);
|
|
839
|
+
const choices = sortedChoices(options, prompt);
|
|
840
|
+
let selected = options.initialValue === void 0 ? 0 : choices.findIndex(({ value }) => value === options.initialValue);
|
|
811
841
|
if (selected < 0) throw promptError("invalid-options", prompt, `Unknown initial select value: ${String(options.initialValue)}`);
|
|
812
842
|
const state = (status = "active") => ({
|
|
813
843
|
kind: "select",
|
|
814
844
|
message: options.message,
|
|
815
845
|
status,
|
|
816
|
-
value:
|
|
817
|
-
choices:
|
|
846
|
+
value: choices[selected].value,
|
|
847
|
+
choices: choices.map(({ value, label, hint }, index) => ({
|
|
818
848
|
value,
|
|
819
849
|
label: label ?? value,
|
|
820
850
|
...hint === void 0 ? {} : { hint },
|
|
821
851
|
selected: index === selected
|
|
822
852
|
}))
|
|
823
853
|
});
|
|
824
|
-
return await this.#run(prompt, state(), (_character, key) => key.name === "return" || key.name === "enter" ?
|
|
854
|
+
return await this.#run(prompt, state(), (_character, key) => key.name === "return" || key.name === "enter" ? choices[selected].value : void 0, (_character, key) => {
|
|
825
855
|
if (key.name !== "up" && key.name !== "down") return void 0;
|
|
826
|
-
selected = (selected + (key.name === "up" ? -1 : 1) +
|
|
856
|
+
selected = (selected + (key.name === "up" ? -1 : 1) + choices.length) % choices.length;
|
|
827
857
|
return state();
|
|
828
858
|
}, () => state("submitted"), () => state("closed"));
|
|
829
859
|
}
|
|
860
|
+
async autocomplete(options) {
|
|
861
|
+
const prompt = validateChoices("autocomplete", options);
|
|
862
|
+
if (options.placeholder !== void 0 && typeof options.placeholder !== "string") throw promptError("invalid-options", prompt, "Autocomplete placeholder must be a string");
|
|
863
|
+
const choices = sortedChoices(options, prompt);
|
|
864
|
+
let query = "";
|
|
865
|
+
let selected = -1;
|
|
866
|
+
let filtered = choices;
|
|
867
|
+
const state = (status = "active") => ({
|
|
868
|
+
kind: "autocomplete",
|
|
869
|
+
message: options.message,
|
|
870
|
+
status,
|
|
871
|
+
query,
|
|
872
|
+
phase: selected < 0 ? "input" : "select",
|
|
873
|
+
...options.placeholder === void 0 ? {} : { placeholder: options.placeholder },
|
|
874
|
+
...selected < 0 ? {} : { value: filtered[selected].value },
|
|
875
|
+
choices: filtered.map(({ value, label, hint }, index) => ({
|
|
876
|
+
value,
|
|
877
|
+
label: label ?? value,
|
|
878
|
+
...hint === void 0 ? {} : { hint },
|
|
879
|
+
selected: index === selected
|
|
880
|
+
}))
|
|
881
|
+
});
|
|
882
|
+
return await this.#run(prompt, state(), (_character, key) => selected >= 0 && (key.name === "return" || key.name === "enter") ? filtered[selected].value : void 0, (character, key) => {
|
|
883
|
+
if (key.name === "down") {
|
|
884
|
+
if (selected >= filtered.length - 1) return void 0;
|
|
885
|
+
selected += 1;
|
|
886
|
+
} else if (key.name === "up") {
|
|
887
|
+
if (selected < 0) return void 0;
|
|
888
|
+
selected -= 1;
|
|
889
|
+
} else if (key.name === "escape") selected = -1;
|
|
890
|
+
else {
|
|
891
|
+
if (key.name === "backspace") {
|
|
892
|
+
const last = graphemeSegmenter.segment(query).containing(query.length - 1);
|
|
893
|
+
query = last === void 0 ? "" : query.slice(0, last.index);
|
|
894
|
+
} else if (!key.ctrl && !key.meta && character.length > 0 && !/\p{Cc}/u.test(character)) query += character;
|
|
895
|
+
else return void 0;
|
|
896
|
+
selected = -1;
|
|
897
|
+
const search = query.toLowerCase();
|
|
898
|
+
filtered = search.length === 0 ? choices : choices.filter(({ value, label }) => (label ?? value).toLowerCase().includes(search));
|
|
899
|
+
}
|
|
900
|
+
return state();
|
|
901
|
+
}, () => state("submitted"), () => state("closed"), () => selected < 0);
|
|
902
|
+
}
|
|
830
903
|
async confirm(options) {
|
|
831
904
|
const prompt = details$1("confirm", options?.message);
|
|
832
905
|
validateMessage("confirm", options?.message);
|