@effected/cli 0.10.0 → 0.11.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/Cancelled.js +44 -0
- package/CliAudience.js +178 -0
- package/CliColor.js +13 -19
- package/CliEnv.js +89 -0
- package/CliExit.js +1 -1
- package/CliFailure.js +253 -0
- package/CliInteractive.js +71 -0
- package/CliLinks.js +154 -0
- package/CliLog.js +294 -0
- package/CliLogger.js +34 -33
- package/CliMessage.js +83 -0
- package/CliPrompt.js +104 -0
- package/CliRuntime.js +103 -53
- package/CliTest.js +16 -0
- package/CliTheme.js +128 -0
- package/ConfigIssueRenderer.js +14 -33
- package/Doc.js +512 -0
- package/Fmt.js +133 -0
- package/GithubAnnotation.js +40 -0
- package/Glyphs.js +83 -0
- package/NotInteractive.js +42 -0
- package/README.md +129 -131
- package/Render.js +254 -0
- package/SchemaIssueRenderer.js +7 -10
- package/Status.js +163 -0
- package/TestTerminal.js +80 -0
- package/Token.js +69 -0
- package/index.d.ts +2923 -171
- package/index.js +19 -1
- package/internal/ansi.js +230 -0
- package/internal/autoFormat.js +34 -0
- package/internal/canPrompt.js +15 -0
- package/internal/counts.js +69 -0
- package/internal/diagnostics.js +32 -0
- package/internal/displayWidth.js +35 -0
- package/internal/failureTarget.js +156 -0
- package/internal/fallbackAnswer.js +18 -0
- package/internal/fileSink.js +62 -0
- package/internal/format.js +62 -7
- package/internal/layout.js +250 -0
- package/internal/linkScheme.js +30 -0
- package/internal/linkTarget.js +50 -0
- package/internal/logSafety.js +46 -0
- package/internal/renderAnsi.js +52 -0
- package/internal/renderDoc.js +319 -0
- package/internal/renderGithubLog.js +46 -0
- package/internal/renderMarkdown.js +367 -0
- package/internal/renderPlain.js +50 -0
- package/internal/scanAudience.js +106 -0
- package/internal/splitFrame.js +56 -0
- package/internal/wizardGate.js +18 -0
- package/package.json +35 -5
- package/testing.d.ts +88 -2
- package/testing.js +2 -1
- package/ui/CliUi.js +348 -0
- package/ui/CliUiLive.js +399 -0
- package/ui/Confirm.js +245 -0
- package/ui/DocView.js +74 -0
- package/ui/KeyHelp.js +62 -0
- package/ui/KeyTable.js +199 -0
- package/ui/MultiSelect.js +260 -0
- package/ui/Select.js +226 -0
- package/ui/Tabs.js +202 -0
- package/ui/TextInput.js +250 -0
- package/ui/Toggle.js +32 -0
- package/ui/UiKey.js +44 -0
- package/ui/UiProvider.js +60 -0
- package/ui/UiStreams.js +18 -0
- package/ui/UiTheme.js +119 -0
- package/ui/Viewport.js +204 -0
- package/ui/internal/ErrorBoundary.js +30 -0
- package/ui/internal/Holder.js +74 -0
- package/ui/internal/ScreenContext.js +52 -0
- package/ui/internal/UiProviders.js +21 -0
- package/ui/internal/ink.js +122 -0
- package/ui/internal/inkChalk.js +58 -0
- package/ui/internal/inkConsole.js +146 -0
- package/ui/internal/lineText.js +19 -0
- package/ui/internal/mountPermit.js +16 -0
- package/ui/internal/perfDrain.js +33 -0
- package/ui/internal/processStreams.js +19 -0
- package/ui/internal/renderOptions.js +13 -0
- package/ui/testing/CliUiTest.js +735 -0
- package/ui/testing/fakeStreams.js +76 -0
- package/ui/testing/terminalModel.js +59 -0
- package/ui-testing.d.ts +446 -0
- package/ui-testing.js +3 -0
- package/ui.d.ts +1648 -0
- package/ui.js +17 -0
package/ui.d.ts
ADDED
|
@@ -0,0 +1,1648 @@
|
|
|
1
|
+
import * as Cli from "@effected/cli";
|
|
2
|
+
import { Console, Context, Effect, Option, PubSub, Scope, Stream } from "effect";
|
|
3
|
+
import { Param } from "effect/cli";
|
|
4
|
+
import { ReactElement, ReactNode } from "react";
|
|
5
|
+
import { Key } from "ink";
|
|
6
|
+
import { ColorLevel } from "@effected/env";
|
|
7
|
+
//#region src/ui/CliUiLive.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* Options for `CliUi.live`.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
interface LiveOptions<E, S> {
|
|
14
|
+
/**
|
|
15
|
+
* The events to fold: a `PubSub` subscription, or a stream.
|
|
16
|
+
*
|
|
17
|
+
* A subscription (`PubSub.subscribe`, made before the first publish) is the surest: the view takes from it directly,
|
|
18
|
+
* so nothing published after the subscribe is missed, and `LiveHandle.close` folds every message still queued in it
|
|
19
|
+
* before the view ends. A publisher can also end it from its side, and the two ends differ:
|
|
20
|
+
*
|
|
21
|
+
* - `PubSub.end(pubsub, last)` keeps everything: the view folds what is buffered, then `last` once (core repeats a
|
|
22
|
+
* final message to every later take; the view takes it once), then ends. Make `last` an `isTerminal` event to
|
|
23
|
+
* commit the run with it; otherwise the run is committed as drawn.
|
|
24
|
+
* - `PubSub.shutdown` drops what the view has not taken yet. End with `close` (or `PubSub.end`) first.
|
|
25
|
+
*
|
|
26
|
+
* A stream: `live` makes its first pull before it returns, so one that subscribes on its first pull without forking
|
|
27
|
+
* (`Stream.fromPubSub`) is subscribed by then and sees an event published at once. One that forks its upstream
|
|
28
|
+
* (`Stream.merge`, `buffer`, a concurrent `flatMap`) subscribes later, and an event published before that is lost.
|
|
29
|
+
* `close` folds what the view has already pulled; what the stream holds and has not yielded is the stream's.
|
|
30
|
+
*/
|
|
31
|
+
readonly events: Stream.Stream<E> | PubSub.Subscription<E>;
|
|
32
|
+
/** The state before the first event. */
|
|
33
|
+
readonly initial: S;
|
|
34
|
+
/** Fold one event into the state. The kit never resets the state: a reducer that wants a fresh run resets it. */
|
|
35
|
+
readonly reduce: (state: S, event: E) => S;
|
|
36
|
+
/**
|
|
37
|
+
* Draw the state. `frame` is the wall clock in ticks of `tickMillis` (`floor(now / tickMillis)`, from `Clock`), so a
|
|
38
|
+
* spinner keeps turning across runs. Rendered inside the kit's providers: `useTheme`, `useGlyphs`, `Styled` and
|
|
39
|
+
* `useTerminalSize` work in it. When the final frame is printed as a string (not interactive, or a run that degraded
|
|
40
|
+
* before it painted), `useTerminalSize().rows` is `Infinity`, since that frame has no height to fit: a render must
|
|
41
|
+
* not allocate per row.
|
|
42
|
+
*/
|
|
43
|
+
readonly render: (state: S, frame: number) => ReactElement;
|
|
44
|
+
/** Whether an event starts a run: by default, the only event that begins one (see `begins`). */
|
|
45
|
+
readonly isStart: (event: E) => boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Whether an event begins a run while none is going, given the state before and after it is folded; `isStart` by
|
|
48
|
+
* default, so only a start begins one. Given, it replaces that default rather than adding to it, so keep `isStart`
|
|
49
|
+
* in it to begin on a start as well as on something else, such as a stream that a program joins mid-run:
|
|
50
|
+
* `(event, before, after) => isStart(event) || (before.phase === "idle" && after.phase !== "idle")`.
|
|
51
|
+
* An event that begins nothing while no run is going is folded and not drawn. A start while a run is going redraws
|
|
52
|
+
* that run in place, and this is not asked then; a start while a degraded run is going ends that run and begins a
|
|
53
|
+
* fresh one.
|
|
54
|
+
*/
|
|
55
|
+
readonly begins?: (event: E, before: S, after: S) => boolean;
|
|
56
|
+
/** Whether an event ends a run: its frame is committed to the terminal and the view unmounts until the next. */
|
|
57
|
+
readonly isTerminal: (event: E) => boolean;
|
|
58
|
+
/**
|
|
59
|
+
* `"owned"` (the default) for a view that owns its output, `"hosted"` for one drawn inside a host's (a test
|
|
60
|
+
* reporter). They differ only when the run is not interactive: owned writes each run's final frame once, as a
|
|
61
|
+
* string; hosted writes nothing, its host having its own output.
|
|
62
|
+
*/
|
|
63
|
+
readonly mode?: "owned" | "hosted";
|
|
64
|
+
/**
|
|
65
|
+
* The frame tick, in milliseconds; 80 by default. While a run is drawn the view redraws on every tick, so a spinner
|
|
66
|
+
* turns without events. Anything but a positive, finite number is a defect.
|
|
67
|
+
*/
|
|
68
|
+
readonly tickMillis?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Clear React's user-timing entries after every render: `true`, `false`, or `"auto"` (the default), which clears
|
|
71
|
+
* unless `NODE_ENV` is exactly `"production"`. React's development build records them on every render and never
|
|
72
|
+
* clears them. The clear is process-wide: it removes every `measure` entry, a program's own included; marks are left
|
|
73
|
+
* alone.
|
|
74
|
+
*/
|
|
75
|
+
readonly drainPerformance?: boolean | "auto";
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The handle of a live view.
|
|
79
|
+
*
|
|
80
|
+
* @public
|
|
81
|
+
*/
|
|
82
|
+
interface LiveHandle<S> {
|
|
83
|
+
/** The current state of the fold. */
|
|
84
|
+
readonly state: Effect.Effect<S>;
|
|
85
|
+
/**
|
|
86
|
+
* A `Console` whose every method writes above the frame while a run is mounted, and straight to the stream otherwise;
|
|
87
|
+
* none falls through to the program's own console. Output is split as Node's console splits it: `log`, `info`,
|
|
88
|
+
* `debug`, `dir`, `dirxml`, `table`, `count`, `timeLog`, `timeEnd` and a group's label to stdout, and `error`,
|
|
89
|
+
* `warn`, `trace` and a failed `assert` to stderr; a group indents what follows. An `Error` argument is written with
|
|
90
|
+
* its stack. `clear` does nothing, since erasing the screen would take the scrollback above the frame. Provide it
|
|
91
|
+
* around the work done while the view is mounted; a line written to the terminal any other way tears the frame.
|
|
92
|
+
*
|
|
93
|
+
* `Console.Console` is the seam: the kit's logger (`CliLogger`, `CliLog`) and `Effect.log*` write through whatever
|
|
94
|
+
* `Console` the fiber has, so they need no reference to the view. A host whose logging lives outside the view's
|
|
95
|
+
* owner provides the handle's console at the top of the program it runs, and every log line under it lands above
|
|
96
|
+
* the frame: `Effect.provideService(program, Console.Console, handle.logConsole)`. Output that never goes through
|
|
97
|
+
* Effect's `Console` (another library's own `process.stderr` writes) still tears the frame.
|
|
98
|
+
*/
|
|
99
|
+
readonly logConsole: Console.Console;
|
|
100
|
+
/**
|
|
101
|
+
* Completes once the events have ended (the stream ended, the subscription's `PubSub` was ended with `PubSub.end` or
|
|
102
|
+
* shut down, or `close` ended them) and the last run's frame is committed. Dies with what the view died of: a `reduce` that threw, or a
|
|
103
|
+
* stream that died.
|
|
104
|
+
*/
|
|
105
|
+
readonly done: Effect.Effect<void>;
|
|
106
|
+
/**
|
|
107
|
+
* End the view cleanly, then wait for `done`.
|
|
108
|
+
*
|
|
109
|
+
* @remarks
|
|
110
|
+
* It stops taking events, folds what the view holds and has not folded yet, and ends the run as the events ending
|
|
111
|
+
* does: an `isTerminal` event among them commits its run as usual, and a run with no terminal event is committed as
|
|
112
|
+
* drawn (owned and not interactive: its final frame is printed once). With a subscription, that includes every
|
|
113
|
+
* message still queued in it, so a run's tail published just before the close is never lost, as it would be to a
|
|
114
|
+
* `PubSub.shutdown`. With a stream, it is what the view has already pulled; elements the stream holds and has not
|
|
115
|
+
* yielded are the stream's own.
|
|
116
|
+
*
|
|
117
|
+
* Safe from the moment `live` returns, before Ink has loaded: a run whose mount is still under way is mounted and
|
|
118
|
+
* then ended, and a view with no run to end loads nothing.
|
|
119
|
+
*
|
|
120
|
+
* Idempotent: a second `close`, concurrent or later, waits for the same end and writes nothing more. After the events
|
|
121
|
+
* have ended it only waits for `done`. It dies as `done` does (a `reduce` that threw, a stream that died). Closing
|
|
122
|
+
* the caller's scope instead of calling `close` stops the view at once (nothing still queued is folded); closing it
|
|
123
|
+
* after `close` releases what is left. A `close` after the scope has closed completes at once, there being nothing
|
|
124
|
+
* left to end, where `done` is interrupted. A host ends its view with:
|
|
125
|
+
*
|
|
126
|
+
* ```ts
|
|
127
|
+
* handle.close.pipe(Effect.ensuring(Scope.close(scope, Exit.void)))
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
readonly close: Effect.Effect<void>;
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region src/ui/UiProvider.d.ts
|
|
134
|
+
/**
|
|
135
|
+
* What the kit's hooks read in a tree the kit did not mount: the theme, the glyph set, and optionally the size.
|
|
136
|
+
*
|
|
137
|
+
* @remarks
|
|
138
|
+
* Only `CliUi.context` mints one, because only it also loads the Ink and React the provider renders with. A value
|
|
139
|
+
* without the brand does not compile, which stops one being built by accident; the brand is a plain key, so a
|
|
140
|
+
* literal that spells it out compiles, and must never be written. Spread a minted value to change its fields
|
|
141
|
+
* (`{ ...value, size }`).
|
|
142
|
+
*
|
|
143
|
+
* @public
|
|
144
|
+
*/
|
|
145
|
+
interface UiContextValue {
|
|
146
|
+
/** Set by `CliUi.context` alone; never set it yourself. */
|
|
147
|
+
readonly "~@effected/cli/ui/UiContextValue": true;
|
|
148
|
+
/** The theme of the stream the tree draws on. */
|
|
149
|
+
readonly theme: Cli.StreamTheme;
|
|
150
|
+
/** The glyph set in use. */
|
|
151
|
+
readonly glyphs: Cli.GlyphSet;
|
|
152
|
+
/** The terminal size the tree is laid out at, which `useTerminalSize` reads in place of the stdout's. */
|
|
153
|
+
readonly size?: {
|
|
154
|
+
readonly columns: number;
|
|
155
|
+
readonly rows: number;
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Whether the GitHub Actions runner reads the output (`CliUi.context` sets it from `CurrentRuntimeEnv`, when one is
|
|
159
|
+
* provided): a `DocView` under it neutralizes any line its data would turn into a workflow command. `false` opts the
|
|
160
|
+
* tree out of that under GitHub Actions, the consumer's choice; a nested provider cannot clear it once a provider
|
|
161
|
+
* above it has set it.
|
|
162
|
+
*/
|
|
163
|
+
readonly neutralizeWorkflowCommands?: boolean;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Props of {@link UiProvider}.
|
|
167
|
+
*
|
|
168
|
+
* @public
|
|
169
|
+
*/
|
|
170
|
+
interface UiProviderProps {
|
|
171
|
+
/** The context, from `CliUi.context`. */
|
|
172
|
+
readonly value: UiContextValue;
|
|
173
|
+
/** The tree. */
|
|
174
|
+
readonly children?: ReactNode;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Provide the kit's context to an Ink tree the kit did not mount, so `useTheme`, `useGlyphs`, `Styled` and
|
|
178
|
+
* `useTerminalSize` work in it.
|
|
179
|
+
*
|
|
180
|
+
* @remarks
|
|
181
|
+
* Take the value from `CliUi.context`, which also loads Ink and React: the provider and the kit's hooks render with
|
|
182
|
+
* the modules the kit loaded. A screen mounted by `CliUi.run` already has this context.
|
|
183
|
+
*
|
|
184
|
+
* With `size`, `useTerminalSize` reads it instead of the stdout Ink draws on, less one column and one row as ever.
|
|
185
|
+
* Give it to Ink's `renderToString`, whose terminal hooks see the process's own stdout rather than the width it lays
|
|
186
|
+
* out at: `renderToString(tree, { columns })` with `size: { columns, rows }` keeps the kit's widgets cut to that
|
|
187
|
+
* width. A tree given a `size` no longer follows the terminal's resizes.
|
|
188
|
+
*
|
|
189
|
+
* Standalone, there is no screen to end: a kit widget's own quit key, such as `Select`'s `q`, does nothing, and an
|
|
190
|
+
* input handler a kit widget registers is called as is, so what it throws escapes as Ink leaves it. Nested inside a `CliUi.run` screen, it overrides only the theme, the glyphs and the size: the screen's
|
|
191
|
+
* cancel and its defect route pass through, so `q` still cancels and a throwing handler is still the screen's defect.
|
|
192
|
+
* Ink's colour level is the host's: `Styled` passes the theme's props, none at colour `none`.
|
|
193
|
+
*
|
|
194
|
+
* @param props - the context, and the tree
|
|
195
|
+
*
|
|
196
|
+
* @public
|
|
197
|
+
*/
|
|
198
|
+
export declare const UiProvider: (props: UiProviderProps) => ReactElement;
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/ui/CliUi.d.ts
|
|
201
|
+
/**
|
|
202
|
+
* How a screen ends: with a result, or cancelled for a reason.
|
|
203
|
+
*
|
|
204
|
+
* @remarks
|
|
205
|
+
* Only the first call counts. Resolving with an empty value (`[]`, `""`) is a result, not a cancel.
|
|
206
|
+
*
|
|
207
|
+
* @public
|
|
208
|
+
*/
|
|
209
|
+
interface ScreenControl<A> {
|
|
210
|
+
/** End the screen with `value`. */
|
|
211
|
+
readonly resolve: (value: A) => void;
|
|
212
|
+
/** End the screen as cancelled: `"escape"` for Esc or a widget's quit key, `"interrupt"` for Ctrl-C. */
|
|
213
|
+
readonly cancel: (reason: "escape" | "interrupt") => void;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* A screen: given its control, the React element to mount, or a promise of one.
|
|
217
|
+
*
|
|
218
|
+
* @remarks
|
|
219
|
+
* The kit's widgets sanitise the text they draw from data; a screen's own components (Ink's `Text`, `Styled`) draw
|
|
220
|
+
* what they are given, so text from data in them is the screen author's to pass through `Fmt.sanitize` first.
|
|
221
|
+
*
|
|
222
|
+
* @public
|
|
223
|
+
*/
|
|
224
|
+
type Screen<A> = (control: ScreenControl<A>) => ReactElement | Promise<ReactElement>;
|
|
225
|
+
/**
|
|
226
|
+
* Options for {@link CliUi.run}.
|
|
227
|
+
*
|
|
228
|
+
* @public
|
|
229
|
+
*/
|
|
230
|
+
interface CliUiRunOptions {
|
|
231
|
+
/**
|
|
232
|
+
* Erase the screen's last frame as it unmounts, however it ended; `false` by default, which leaves the last frame
|
|
233
|
+
* on the terminal, as a record of the answer.
|
|
234
|
+
*/
|
|
235
|
+
readonly clear?: boolean;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Options for {@link CliUi.prompt}.
|
|
239
|
+
*
|
|
240
|
+
* @public
|
|
241
|
+
*/
|
|
242
|
+
interface CliUiPromptOptions<A> {
|
|
243
|
+
/** The value to use when the run is not interactive. Without it a non-interactive run fails with `NotInteractive`. */
|
|
244
|
+
readonly otherwise?: A;
|
|
245
|
+
/** Erase the screen's last frame as it unmounts; `false` by default. See {@link CliUiRunOptions.clear}. */
|
|
246
|
+
readonly clear?: boolean;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Options for {@link CliUi.fallback}: `CliPrompt.fallback`'s, and whether the screen erases its last frame.
|
|
250
|
+
*
|
|
251
|
+
* @public
|
|
252
|
+
*/
|
|
253
|
+
type CliUiFallbackOptions<A> = Cli.CliPromptFallbackOptions<A> & {
|
|
254
|
+
/** Erase the screen's last frame as it unmounts; `false` by default. See {@link CliUiRunOptions.clear}. */
|
|
255
|
+
readonly clear?: boolean;
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* Interactive screens drawn with Ink, mounted as scoped resources.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* import { CliUi, Confirm } from "@effected/cli/ui"
|
|
263
|
+
* import { Effect } from "effect"
|
|
264
|
+
*
|
|
265
|
+
* // Answers `otherwise` when there is no person to ask, and fails with `Cancelled` when they back out.
|
|
266
|
+
* const ask = CliUi.prompt(Confirm.screen({ message: "Overwrite the config?" }), {
|
|
267
|
+
* otherwise: { confirmed: false, toggles: {} },
|
|
268
|
+
* })
|
|
269
|
+
* ```
|
|
270
|
+
*
|
|
271
|
+
* @public
|
|
272
|
+
*/
|
|
273
|
+
export declare class CliUi {
|
|
274
|
+
private constructor();
|
|
275
|
+
/**
|
|
276
|
+
* Mount `screen` and wait for it to resolve or cancel.
|
|
277
|
+
*
|
|
278
|
+
* @remarks
|
|
279
|
+
* When `CliInteractive` is false it fails with `NotInteractive` and mounts nothing; Ink and React are not even
|
|
280
|
+
* loaded. Otherwise it loads them, holds Ink's colour level at stdout's, and mounts the screen on
|
|
281
|
+
* `UiStreams` with Ink's own Ctrl-C exit off: Ctrl-C cancels with `"interrupt"` and Esc with `"escape"`.
|
|
282
|
+
*
|
|
283
|
+
* Mounting is one scoped resource. However the screen ends (resolved, cancelled, crashed, or the fiber
|
|
284
|
+
* interrupted), it is unmounted, raw mode and bracketed paste are off, the cursor is shown, and the colour level is
|
|
285
|
+
* restored. A
|
|
286
|
+
* component that throws is a defect, never a hang or a typed failure, and nothing of Ink's crash screen reaches
|
|
287
|
+
* stdout. So is a `useKeys` handler that throws; a handler a consumer registers with Ink's own `useInput` or
|
|
288
|
+
* `usePaste` is outside the kit, and what it throws escapes as Ink leaves it. A crash wins over an end in the same
|
|
289
|
+
* tick: a handler that cancels or resolves and then throws, or a component that throws before the screen has
|
|
290
|
+
* unmounted, is a defect, never the `Cancelled` or the value; a defect raised while the screen unmounts stays beside
|
|
291
|
+
* that crash in the cause rather than replacing it. An interrupt stays an interrupt, even when the tree reports a
|
|
292
|
+
* crash as it unmounts.
|
|
293
|
+
*
|
|
294
|
+
* A screen draws on stdout (`UiStreams`), and mounts only when `CliInteractive` is true: a human audience, a
|
|
295
|
+
* terminal on both stdin and stdout, and a `TERM` that is not `dumb`. `CliInteractive` reads `false` until a layer
|
|
296
|
+
* sets it (`CliRuntime.main`'s `env` does), so a program that never provides one always gets `NotInteractive`.
|
|
297
|
+
*
|
|
298
|
+
* Screens run one at a time, process-wide: Ink owns raw mode on the one terminal, so a second `run` waits until the
|
|
299
|
+
* first is released; so does a `run` while a {@link CliUi.live} view has a run drawn. A screen that itself awaits
|
|
300
|
+
* another `CliUi.run` therefore deadlocks, and nothing guards against it.
|
|
301
|
+
*
|
|
302
|
+
* Do not log while a screen is mounted. Ink redraws its frame by counting the lines it last wrote, and it is
|
|
303
|
+
* mounted with `patchConsole` off, so a line written to the terminal from elsewhere (an `Effect.log`, `CliLog`, a
|
|
304
|
+
* background fiber) lands inside the frame and tears it. Log before the screen mounts or after it resolves.
|
|
305
|
+
*
|
|
306
|
+
* With `clear` the last frame is erased as the screen unmounts, so a wizard of several screens leaves only what the
|
|
307
|
+
* program prints; without it the last frame stays, with the highlight where the answer was.
|
|
308
|
+
*
|
|
309
|
+
* @param screen - builds the element to mount from its {@link ScreenControl}
|
|
310
|
+
* @param options - whether to erase the last frame
|
|
311
|
+
*/
|
|
312
|
+
static readonly run: <A>(screen: Screen<A>, options?: CliUiRunOptions) => Effect.Effect<A, Cli.Cancelled | Cli.NotInteractive, Cli.CliTheme>;
|
|
313
|
+
/**
|
|
314
|
+
* The kit's context for an Ink tree the kit did not mount: stdout's theme and glyph set.
|
|
315
|
+
*
|
|
316
|
+
* @remarks
|
|
317
|
+
* Hand it to {@link UiProvider}. It loads Ink and React, as a screen's mount does, so the provider and the kit's
|
|
318
|
+
* hooks can render; a missing peer is a defect naming both. It is the only way to get a `UiContextValue`.
|
|
319
|
+
*
|
|
320
|
+
* Ink loads asynchronously, so this is an `Effect` that must run before the first render. A renderer that is itself
|
|
321
|
+
* synchronous (a test helper, a report-time `renderToString`) runs it once, with a top-level `await` at module
|
|
322
|
+
* scope, and then renders synchronously from the value as often as it likes:
|
|
323
|
+
*
|
|
324
|
+
* ```ts
|
|
325
|
+
* const value = await Effect.runPromise(CliUi.context.pipe(Effect.provide(themeLayer)))
|
|
326
|
+
*
|
|
327
|
+
* // later, synchronously:
|
|
328
|
+
* const text = renderToString(createElement(UiProvider, { value: { ...value, size: { columns, rows } } }, tree), {
|
|
329
|
+
* columns,
|
|
330
|
+
* })
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
static readonly context: Effect.Effect<UiContextValue, never, Cli.CliTheme>;
|
|
334
|
+
/**
|
|
335
|
+
* A live view over a stream: fold `events` into state, and draw it with Ink while a run is going, for the caller's
|
|
336
|
+
* scope.
|
|
337
|
+
*
|
|
338
|
+
* @remarks
|
|
339
|
+
* `events` is a `PubSub` subscription or a stream, folded in a fiber of the caller's scope. A subscription, made
|
|
340
|
+
* before the first publish, is the surest: nothing published after it is missed. `live` makes a stream's first pull
|
|
341
|
+
* before it returns, so one that subscribes on its first pull without forking (`Stream.fromPubSub`) is subscribed by
|
|
342
|
+
* then; one that forks its upstream (`Stream.merge`, `buffer`, a concurrent `flatMap`) subscribes later, and loses
|
|
343
|
+
* what is published before.
|
|
344
|
+
*
|
|
345
|
+
* `live` returns its handle at once, before Ink has loaded: it loads Ink when a run first mounts (or, when not
|
|
346
|
+
* interactive, when an owned run prints its final frame), and it waits on nothing asynchronous before returning, so
|
|
347
|
+
* a host outside Effect can take the handle with `Effect.runSync`. The handle works from the start: a `close`
|
|
348
|
+
* before any run has mounted folds what is queued and ends the view as the events ending would, waiting for a mount
|
|
349
|
+
* already under way, and one with no run to end loads nothing.
|
|
350
|
+
*
|
|
351
|
+
* End the view with `handle.close`: it stops taking events, folds what is still queued (a subscription's queued
|
|
352
|
+
* messages included), commits or prints the run as the events ending would, and waits for `done`. Then close the
|
|
353
|
+
* scope. A publisher may instead end a subscription with `PubSub.end(pubsub, last)`, which keeps everything: the view
|
|
354
|
+
* folds what is buffered and `last` once, then ends. A `PubSub.shutdown` drops what the view has not taken yet, and
|
|
355
|
+
* closing the scope by itself stops the fold at once: both lose a run's tail. Closing the scope unmounts whatever is drawn: the terminal is restored (the
|
|
356
|
+
* cursor shown, Ink's colour level put back) and nothing more is written. `done` completes when the events end.
|
|
357
|
+
*
|
|
358
|
+
* A run begins at an `isStart` event (or wherever `begins` says, given the state before and after the event) and ends
|
|
359
|
+
* at an `isTerminal` event; an event while no run is going that begins none is folded and not drawn, so what a program
|
|
360
|
+
* reports after a run ends never mounts a second copy of it. A run mounts the view; its end unmounts it, which leaves
|
|
361
|
+
* its last frame on the terminal, and the next run mounts afresh below it. A start while a run is drawn redraws in
|
|
362
|
+
* place: the frame is never cleared, so nothing above it is erased. The state is never reset by the kit: a reducer that wants a fresh run
|
|
363
|
+
* resets it on the start.
|
|
364
|
+
*
|
|
365
|
+
* The frame is at most the terminal's rows less one, re-read on every render and on a resize, so a tall frame never
|
|
366
|
+
* makes Ink wipe the scrollback; its width is Ink's own. The clamp
|
|
367
|
+
* lags one paint when the terminal gets shorter: Ink re-lays out and repaints the tree it already has on a resize,
|
|
368
|
+
* before React re-renders with the new row count, so a frame already at the old height can be drawn once taller
|
|
369
|
+
* than the terminal, which Ink answers by clearing the screen and its scrollback. Only a shrink in height while
|
|
370
|
+
* the frame is at its full height does it; a frame that keeps a few rows spare never meets it.
|
|
371
|
+
*
|
|
372
|
+
* While a run is drawn the view also redraws on a tick of `tickMillis` (80 by default), a schedule in the run's
|
|
373
|
+
* scope: interrupted with the run or the scope, it never outlives them. Its timer is not unref'd, so while a run is
|
|
374
|
+
* drawn it keeps the process alive: the run's terminal event, or the scope's close, is what lets the process exit.
|
|
375
|
+
* The frame index never steps back. Events that arrive at
|
|
376
|
+
* once, in one chunk or in several the view had not yet caught up with, are folded together and drawn once. The view
|
|
377
|
+
* takes events from `events` as fast as the stream yields them, so a stream that applies backpressure buffers in the
|
|
378
|
+
* view while it draws.
|
|
379
|
+
*
|
|
380
|
+
* A run whose drawing fails (a `render` that throws, or a mount that fails) degrades rather than ending the view:
|
|
381
|
+
* it is unmounted, leaving its last good frame on the terminal, then one warning is logged (`Effect.logWarning`),
|
|
382
|
+
* and the fold goes on. At its terminal event, a run with no frame left on the terminal (it never painted, or its
|
|
383
|
+
* last good frame threw too) writes its final frame once, as a string. The next run mounts afresh, and so does a
|
|
384
|
+
* start that comes while a degraded run is going: it ends that run as its terminal event would. A `reduce` that
|
|
385
|
+
* throws, or an `events` stream that dies, unmounts the run, then `done` dies with the error.
|
|
386
|
+
*
|
|
387
|
+
* When the run is not interactive, nothing is mounted and Ink is loaded only when a string is due. In the `owned`
|
|
388
|
+
* mode (the default) each run's final frame is written once to stdout, as a string laid out at stdout's width (80
|
|
389
|
+
* when it reports none) with no height to fit, at its terminal event or when the stream ends. It is escape-free at
|
|
390
|
+
* colour `none`, and for an agent audience (`Audience`, when provided) whatever the terminal could do. In the
|
|
391
|
+
* `hosted` mode nothing is written.
|
|
392
|
+
*
|
|
393
|
+
* No input is mounted: the view reads no keys and never enters raw mode, so Ctrl-C stays the platform's SIGINT,
|
|
394
|
+
* which interrupts the program and so closes the scope. Each run holds the process-wide mount permit from its
|
|
395
|
+
* mount to its end, so a `CliUi.run` during a run waits for the run to end, and one between runs mounts at once.
|
|
396
|
+
*
|
|
397
|
+
* While a run is drawn, write logs through `logConsole`, provided around the work the view reports on: its lines
|
|
398
|
+
* land above the frame. A line written to the terminal any other way tears the frame.
|
|
399
|
+
*
|
|
400
|
+
* The view draws on stdout (`UiStreams`), at stdout's colour level and glyphs, and mounts only when the run is
|
|
401
|
+
* interactive (`CliInteractive`).
|
|
402
|
+
*
|
|
403
|
+
* @param options - the events, the fold, the drawing, and what starts and ends a run
|
|
404
|
+
*/
|
|
405
|
+
static readonly live: <E, S>(options: LiveOptions<E, S>) => Effect.Effect<LiveHandle<S>, never, Scope.Scope | Cli.CliTheme>;
|
|
406
|
+
/**
|
|
407
|
+
* Run `screen` from a handler when the run is interactive; otherwise answer with `otherwise`, or fail with
|
|
408
|
+
* `NotInteractive` when there is none.
|
|
409
|
+
*
|
|
410
|
+
* @remarks
|
|
411
|
+
* `CliUi.run` with a default: not interactive, it returns `otherwise` and Ink and React are never loaded.
|
|
412
|
+
* Interactive, it mounts the screen, and a cancel is the typed `Cancelled` a handler can catch, which
|
|
413
|
+
* `CliRuntime.main` otherwise renders as one line with exit `130`. A missing Ink in an interactive run is a defect
|
|
414
|
+
* naming the peers, never a silent `otherwise`. Screens in sequence make a wizard: discover the defaults first,
|
|
415
|
+
* pass each as an `otherwise`, and a non-interactive run returns exactly them.
|
|
416
|
+
*
|
|
417
|
+
* As with `CliUi.run`, do not log while the screen is mounted: a line written to the terminal from elsewhere tears
|
|
418
|
+
* the frame.
|
|
419
|
+
*
|
|
420
|
+
* @param screen - the screen to show
|
|
421
|
+
* @param options - the non-interactive default, and whether to erase the last frame
|
|
422
|
+
*/
|
|
423
|
+
static readonly prompt: <A>(screen: Screen<A>, options?: CliUiPromptOptions<A>) => Effect.Effect<A, Cli.Cancelled | Cli.NotInteractive, Cli.CliTheme>;
|
|
424
|
+
/**
|
|
425
|
+
* A fallback for `Flag.withFallbackPrompt` or `Argument.withFallbackPrompt` that shows a screen when the run is
|
|
426
|
+
* interactive: `CliPrompt.fallback` for screens.
|
|
427
|
+
*
|
|
428
|
+
* @remarks
|
|
429
|
+
* Interactive, the screen mounts and its answer is the parameter's value. Not interactive, `otherwise` is used when
|
|
430
|
+
* given and Ink and React are never loaded; without it the parameter fails as missing, exactly as with no fallback,
|
|
431
|
+
* so core renders its own message and `CliRuntime.main` exits `64`. Name the parameter with `flag` (the name
|
|
432
|
+
* without dashes) or `argument` so that error can be built.
|
|
433
|
+
*
|
|
434
|
+
* The options are {@link @effected/cli!CliPromptFallbackOptions}, the same as `CliPrompt.fallback`'s, and `clear`
|
|
435
|
+
* as for {@link CliUi.run}.
|
|
436
|
+
*
|
|
437
|
+
* It runs during parsing, whose environment is core's alone, so it reads `CliTheme` if one is there: with
|
|
438
|
+
* `CliRuntime.main`'s `env` (`CliEnv.layer`), or provided around the program. With no theme it treats the run as
|
|
439
|
+
* not interactive, and when `CliInteractive` is on it says so once, at debug level. Interactivity is
|
|
440
|
+
* `CliInteractive`, which an audience flag can set before parsing under `CliAudience`.
|
|
441
|
+
*
|
|
442
|
+
* As with `CliPrompt.fallback`, the screen runs here rather than being handed to core, whose fallback runner
|
|
443
|
+
* turns a quit into the missing-parameter error, which would exit `64`. A cancel (Esc, Ctrl-C) is `Cancelled`,
|
|
444
|
+
* raised as a defect because core's parse step turns every typed failure into a usage error, so only
|
|
445
|
+
* `CliRuntime.main` (or `CliRuntime.reportFailures`) renders it, as one line with exit `130`. A missing Ink in an
|
|
446
|
+
* interactive run is a defect naming the peers, never a silent `otherwise`.
|
|
447
|
+
*
|
|
448
|
+
* After the screen has unmounted, core still runs the answered `Prompt.succeed` it is handed against the
|
|
449
|
+
* terminal, exactly as it does for `CliPrompt.fallback`: `Prompt.run` opens the terminal's input in a scope (on
|
|
450
|
+
* Node a readline over stdin, in raw mode) before looking at the prompt. That is harmless. The prompt is already
|
|
451
|
+
* answered, so nothing is read and no key is waited for; the scope closes at once, restoring the mode and closing
|
|
452
|
+
* the reader; and Ink has already let go of stdin, so the two never hold it together. Not interactive, the screen
|
|
453
|
+
* never mounts, and `CliPrompt.gateTerminal`, which `CliEnv.layer` installs, keeps that subscription off the real
|
|
454
|
+
* terminal altogether.
|
|
455
|
+
*
|
|
456
|
+
* @param screen - the screen to show
|
|
457
|
+
* @param options - the parameter it stands in for, the non-interactive default, and whether to erase the last frame
|
|
458
|
+
*/
|
|
459
|
+
static readonly fallback: <A>(screen: Screen<A>, options: CliUiFallbackOptions<A>) => Param.FallbackPrompt<A>;
|
|
460
|
+
/**
|
|
461
|
+
* A screen whose module is loaded only when it mounts, so importing the command that uses it loads neither the
|
|
462
|
+
* screen's own code nor React.
|
|
463
|
+
*
|
|
464
|
+
* @param load - imports the module whose default export is the screen
|
|
465
|
+
*/
|
|
466
|
+
static readonly lazy: <A>(load: () => Promise<{
|
|
467
|
+
readonly default: Screen<A>;
|
|
468
|
+
}>) => Screen<A>;
|
|
469
|
+
}
|
|
470
|
+
//#endregion
|
|
471
|
+
//#region src/ui/UiKey.d.ts
|
|
472
|
+
/**
|
|
473
|
+
* The named keys a screen understands and a test can press.
|
|
474
|
+
*
|
|
475
|
+
* @remarks
|
|
476
|
+
* Letters, digits and punctuation are not named: they arrive as typed text.
|
|
477
|
+
*
|
|
478
|
+
* @public
|
|
479
|
+
*/
|
|
480
|
+
type KeyName = "up" | "down" | "left" | "right" | "enter" | "space" | "tab" | "shift+tab" | "backspace" | "delete" | "escape" | "ctrl+c" | "home" | "end" | "pageup" | "pagedown";
|
|
481
|
+
/**
|
|
482
|
+
* A key as a screen sees it: a named key, or typed text.
|
|
483
|
+
*
|
|
484
|
+
* @remarks
|
|
485
|
+
* Space is always `Named("space")`, never `Char(" ")`, so a key table binds it once. A `Char` is what Ink delivers
|
|
486
|
+
* as one input, so a paste arrives as one `Char` holding the pasted text.
|
|
487
|
+
*
|
|
488
|
+
* @public
|
|
489
|
+
*/
|
|
490
|
+
export type UiKey = {
|
|
491
|
+
readonly _tag: "Named";
|
|
492
|
+
readonly name: KeyName;
|
|
493
|
+
} | {
|
|
494
|
+
readonly _tag: "Char";
|
|
495
|
+
readonly char: string;
|
|
496
|
+
};
|
|
497
|
+
/**
|
|
498
|
+
* Normalising Ink's input into {@link UiKey}s.
|
|
499
|
+
*
|
|
500
|
+
* @public
|
|
501
|
+
*/
|
|
502
|
+
export declare const UiKey: {
|
|
503
|
+
/**
|
|
504
|
+
* The key Ink reported to a `useInput` handler, or `undefined` for one the kit does not name: a Ctrl or Meta
|
|
505
|
+
* combination other than Ctrl-C, or an input holding a control character.
|
|
506
|
+
*/
|
|
507
|
+
readonly fromInk: (input: string, key: Key) => UiKey | undefined;
|
|
508
|
+
/** A named key. */
|
|
509
|
+
readonly named: (name: KeyName) => UiKey;
|
|
510
|
+
/** Typed text. */
|
|
511
|
+
readonly char: (char: string) => UiKey;
|
|
512
|
+
};
|
|
513
|
+
//#endregion
|
|
514
|
+
//#region src/ui/KeyTable.d.ts
|
|
515
|
+
/**
|
|
516
|
+
* One row of a {@link KeyTable}: the keys that trigger an action, and the help line that names it.
|
|
517
|
+
*
|
|
518
|
+
* @public
|
|
519
|
+
*/
|
|
520
|
+
interface Binding<Action> {
|
|
521
|
+
/** The keys, named or typed (`{ char: "q" }`); any of them triggers the action. */
|
|
522
|
+
readonly keys: ReadonlyArray<KeyName | {
|
|
523
|
+
readonly char: string;
|
|
524
|
+
}>;
|
|
525
|
+
/** What the keys do. */
|
|
526
|
+
readonly action: Action;
|
|
527
|
+
/** The words `KeyHelp` shows beside the keys. */
|
|
528
|
+
readonly help: string;
|
|
529
|
+
/** Bound but left out of the help line. */
|
|
530
|
+
readonly hidden?: boolean;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* One entry of a key table's help: the key labels and what they do.
|
|
534
|
+
*
|
|
535
|
+
* @public
|
|
536
|
+
*/
|
|
537
|
+
interface KeyHelpRow {
|
|
538
|
+
/** The keys, labelled for the glyph set and joined with `/`. */
|
|
539
|
+
readonly label: string;
|
|
540
|
+
/** What they do. */
|
|
541
|
+
readonly help: string;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Options for {@link useKeys}.
|
|
545
|
+
*
|
|
546
|
+
* @public
|
|
547
|
+
*/
|
|
548
|
+
interface UseKeysOptions {
|
|
549
|
+
/** Whether the keys are read; `true` by default. */
|
|
550
|
+
readonly isActive?: boolean;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* The keys a widget understands, as data: the one source both for dispatching input and for the help line, so the
|
|
554
|
+
* two cannot drift apart.
|
|
555
|
+
*
|
|
556
|
+
* @remarks
|
|
557
|
+
* Read it with {@link useKeys}, whose handler must step from current state, never render-closure state: several keys
|
|
558
|
+
* from one stdin read are dispatched before React re-renders.
|
|
559
|
+
*
|
|
560
|
+
* @public
|
|
561
|
+
*/
|
|
562
|
+
export declare class KeyTable<Action> {
|
|
563
|
+
/** The bindings, in priority order. */
|
|
564
|
+
readonly bindings: ReadonlyArray<Binding<Action>>;
|
|
565
|
+
private constructor();
|
|
566
|
+
/**
|
|
567
|
+
* A table from its bindings. When two bindings share a key, the first wins. A `{ char: " " }` key is stored as
|
|
568
|
+
* the named `"space"`, the only form in which Ink reports a space.
|
|
569
|
+
*
|
|
570
|
+
* @param bindings - the bindings, in priority order
|
|
571
|
+
*/
|
|
572
|
+
static readonly make: <Action_1>(bindings: ReadonlyArray<Binding<Action_1>>) => KeyTable<Action_1>;
|
|
573
|
+
/**
|
|
574
|
+
* The keys every screen has: Esc cancels with `"escape"` (help: cancel), and Ctrl-C cancels with `"interrupt"`,
|
|
575
|
+
* bound but hidden. `q` is never a root key: it belongs to a widget's own table, so a text input can type it.
|
|
576
|
+
*/
|
|
577
|
+
static readonly root: KeyTable<"escape" | "interrupt">;
|
|
578
|
+
/**
|
|
579
|
+
* The action of the first binding that holds `key`, or `None`.
|
|
580
|
+
*
|
|
581
|
+
* @param key - the key pressed
|
|
582
|
+
*/
|
|
583
|
+
readonly match: (key: UiKey) => Option.Option<Action>;
|
|
584
|
+
/**
|
|
585
|
+
* The help rows of every binding not hidden that can still fire, in order, labelled for `glyphs`: `↑/↓` under
|
|
586
|
+
* Unicode, `up/down` under ASCII.
|
|
587
|
+
*
|
|
588
|
+
* @remarks
|
|
589
|
+
* A key an earlier binding already holds (hidden or not) can never fire a later one, so a later binding is
|
|
590
|
+
* labelled with its remaining keys only, and left out when none remain.
|
|
591
|
+
*
|
|
592
|
+
* @param glyphs - the glyph set the labels are drawn with
|
|
593
|
+
*/
|
|
594
|
+
readonly help: (glyphs: Cli.GlyphSet) => ReadonlyArray<KeyHelpRow>;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Read the keys of `table` and dispatch the action each one matches; keys the table does not bind are ignored.
|
|
598
|
+
*
|
|
599
|
+
* @remarks
|
|
600
|
+
* One Ink `useInput` per call, and nothing else reads input.
|
|
601
|
+
*
|
|
602
|
+
* Text read in one go (`"yy"`, `"y\r"`) reaches Ink's `useInput` as one string; it is split here into a key per
|
|
603
|
+
* grapheme (a decomposed letter or a ZWJ emoji is one key), a line break (CR, LF or CR LF) as one enter, a tab as tab, a
|
|
604
|
+
* space as space, so `{ char: "y" }` matches each `y`. A `{ char }` binding matches in NFC, whichever form was typed.
|
|
605
|
+
* A bracketed paste never reaches it: the screen takes pastes on Ink's paste channel, so pasted text cannot press a
|
|
606
|
+
* widget's keys (a pasted `q` does not cancel); `TextInput` reads pastes as text.
|
|
607
|
+
*
|
|
608
|
+
* Several keys from one stdin read (a fast typist, a held arrow, a terminal that batches) are each dispatched
|
|
609
|
+
* before React re-renders, so `dispatch` must never step from state captured in the render that created it: the
|
|
610
|
+
* second key would see the first key's starting point and repeat its move. Step with a functional update
|
|
611
|
+
* (`setState((current) => step(current, action))`), a `useReducer` dispatch, or a ref the handler itself advances.
|
|
612
|
+
*
|
|
613
|
+
* Inside a screen mounted by `CliUi.run`, a `dispatch` that throws ends the screen as a defect carrying the error, as a
|
|
614
|
+
* component that throws in render does; it never escapes as an uncaught exception.
|
|
615
|
+
*
|
|
616
|
+
* @param table - the keys to read
|
|
617
|
+
* @param dispatch - receives each matched action
|
|
618
|
+
* @param options - whether the keys are read
|
|
619
|
+
*
|
|
620
|
+
* @public
|
|
621
|
+
*/
|
|
622
|
+
export declare const useKeys: <Action>(table: KeyTable<Action>, dispatch: (action: Action) => void, options?: UseKeysOptions) => void;
|
|
623
|
+
//#endregion
|
|
624
|
+
//#region src/ui/Confirm.d.ts
|
|
625
|
+
/**
|
|
626
|
+
* An extra on/off row a {@link Confirm} hosts beneath its yes/no answer.
|
|
627
|
+
*
|
|
628
|
+
* @public
|
|
629
|
+
*/
|
|
630
|
+
interface ConfirmToggle<K extends string> {
|
|
631
|
+
/** Names the toggle in the result. */
|
|
632
|
+
readonly key: K;
|
|
633
|
+
/** What the row shows. */
|
|
634
|
+
readonly label: string;
|
|
635
|
+
/** Whether it starts on. */
|
|
636
|
+
readonly value: boolean;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* What a {@link Confirm} resolves with: the answer, and every toggle by key.
|
|
640
|
+
*
|
|
641
|
+
* @public
|
|
642
|
+
*/
|
|
643
|
+
interface ConfirmResult<K extends string> {
|
|
644
|
+
/** Yes or no. */
|
|
645
|
+
readonly confirmed: boolean;
|
|
646
|
+
/**
|
|
647
|
+
* Each toggle's final value, by its key. Partial, because a toggle passed conditionally may be absent: read one
|
|
648
|
+
* with a fallback, `toggles.promote ?? false`.
|
|
649
|
+
*/
|
|
650
|
+
readonly toggles: Readonly<Partial<Record<K, boolean>>>;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Where a {@link Confirm} is.
|
|
654
|
+
*
|
|
655
|
+
* @public
|
|
656
|
+
*/
|
|
657
|
+
interface ConfirmState<K extends string> {
|
|
658
|
+
/** The answer. */
|
|
659
|
+
readonly confirmed: boolean;
|
|
660
|
+
/** The highlighted row: 0 is the yes/no row, each toggle follows. */
|
|
661
|
+
readonly row: number;
|
|
662
|
+
/** The toggles and their current values. */
|
|
663
|
+
readonly toggles: ReadonlyArray<ConfirmToggle<K>>;
|
|
664
|
+
/** Whether enter was pressed. */
|
|
665
|
+
readonly submitted: boolean;
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* What a key does in a {@link Confirm}.
|
|
669
|
+
*
|
|
670
|
+
* @public
|
|
671
|
+
*/
|
|
672
|
+
type ConfirmAction = "up" | "down" | "toggle" | "yes" | "no" | "flip" | "submit" | "cancel";
|
|
673
|
+
/**
|
|
674
|
+
* Options for {@link Confirm.init}.
|
|
675
|
+
*
|
|
676
|
+
* @public
|
|
677
|
+
*/
|
|
678
|
+
interface ConfirmInitOptions<K extends string> {
|
|
679
|
+
/** The starting answer; no (`false`) by default. */
|
|
680
|
+
readonly initial?: boolean;
|
|
681
|
+
/** Extra on/off rows, with unique keys. */
|
|
682
|
+
readonly toggles?: ReadonlyArray<ConfirmToggle<K>>;
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Options for {@link Confirm.screen}.
|
|
686
|
+
*
|
|
687
|
+
* @public
|
|
688
|
+
*/
|
|
689
|
+
interface ConfirmScreenOptions<K extends string> extends ConfirmInitOptions<K> {
|
|
690
|
+
/** The question. */
|
|
691
|
+
readonly message: string;
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Props of {@link Confirm.View}.
|
|
695
|
+
*
|
|
696
|
+
* @public
|
|
697
|
+
*/
|
|
698
|
+
interface ConfirmViewProps<K extends string> extends ConfirmScreenOptions<K> {
|
|
699
|
+
/** Receives the answer and the toggles when enter is pressed. */
|
|
700
|
+
readonly onSubmit: (result: ConfirmResult<K>) => void;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* A yes/no question, optionally with extra on/off rows beneath it: a pure reducer, its key table, a view and a
|
|
704
|
+
* ready-made screen.
|
|
705
|
+
*
|
|
706
|
+
* @remarks
|
|
707
|
+
* The rows are the yes/no row first, then each toggle. `y` and `n` set the answer and `←`/`→` flip it, from any
|
|
708
|
+
* row; `↑`/`↓` move between rows; space flips the highlighted toggle and does nothing on the yes/no row; enter
|
|
709
|
+
* submits; `q` cancels with `"escape"`. Toggle keys must be unique; `init` throws, and `screen` dies, on a repeat.
|
|
710
|
+
* With no toggles, the help line leaves out the row and toggle keys. Toggles that do not fit the terminal scroll
|
|
711
|
+
* in a window under the answer row, so the question, the answer and the help line stay on screen.
|
|
712
|
+
* The answer starts as no unless `initial` says otherwise, and `←`/`→` flip it whichever row is highlighted.
|
|
713
|
+
*
|
|
714
|
+
* A toggle passed conditionally is absent from the result when it was left out, so read it back with a fallback:
|
|
715
|
+
*
|
|
716
|
+
* ```ts
|
|
717
|
+
* import { CliUi, Confirm } from "@effected/cli/ui"
|
|
718
|
+
* import { Effect } from "effect"
|
|
719
|
+
*
|
|
720
|
+
* const publish = (drafts: number) =>
|
|
721
|
+
* Effect.gen(function* () {
|
|
722
|
+
* const { confirmed, toggles } = yield* CliUi.run(
|
|
723
|
+
* Confirm.screen({
|
|
724
|
+
* message: "Publish the release?",
|
|
725
|
+
* toggles: drafts > 0 ? [{ key: "promote", label: `promote ${drafts} drafts to stable`, value: true }] : [],
|
|
726
|
+
* }),
|
|
727
|
+
* )
|
|
728
|
+
* const promote = toggles.promote ?? false
|
|
729
|
+
* return { confirmed, promote }
|
|
730
|
+
* })
|
|
731
|
+
* ```
|
|
732
|
+
*
|
|
733
|
+
* @public
|
|
734
|
+
*/
|
|
735
|
+
export declare class Confirm {
|
|
736
|
+
private constructor();
|
|
737
|
+
/**
|
|
738
|
+
* A confirm with its answer (no by default) and toggles, on the yes/no row.
|
|
739
|
+
*
|
|
740
|
+
* @param options - the starting answer and the toggles
|
|
741
|
+
*/
|
|
742
|
+
static readonly init: <K extends string>(options?: ConfirmInitOptions<K>) => ConfirmState<K>;
|
|
743
|
+
/**
|
|
744
|
+
* Apply an action (see the class remarks for what each key does).
|
|
745
|
+
*
|
|
746
|
+
* @param state - where the confirm is
|
|
747
|
+
* @param action - the action
|
|
748
|
+
*/
|
|
749
|
+
static readonly step: <K extends string>(state: ConfirmState<K>, action: ConfirmAction) => ConfirmState<K>;
|
|
750
|
+
/**
|
|
751
|
+
* The answer and every toggle's value by key.
|
|
752
|
+
*
|
|
753
|
+
* @param state - where the confirm is
|
|
754
|
+
*/
|
|
755
|
+
static readonly result: <K extends string>(state: ConfirmState<K>) => ConfirmResult<K>;
|
|
756
|
+
/** The keys: y yes, n no, ←/→ flip, ↑/↓ row, space toggle, enter submit, q cancel (the view hides row and toggle from its help when there are no toggles). */
|
|
757
|
+
static readonly keys: KeyTable<ConfirmAction>;
|
|
758
|
+
/**
|
|
759
|
+
* Draw the confirm: the question, the answer row (`[Yes] No` or ` Yes [No]`, the chosen answer in brackets so it
|
|
760
|
+
* shows without colour, and in the accent token), a {@link Toggle} row per toggle, and the key help.
|
|
761
|
+
*
|
|
762
|
+
* @remarks
|
|
763
|
+
* Single-shot, like `Select.View`: the options are read once at mount.
|
|
764
|
+
*
|
|
765
|
+
* @param props - the question, the starting answer, the toggles and where the result goes
|
|
766
|
+
*/
|
|
767
|
+
static readonly View: <K extends string>(props: ConfirmViewProps<K>) => ReactElement;
|
|
768
|
+
/**
|
|
769
|
+
* A ready-made screen for `CliUi.run`: the confirm, resolving with the answer and the toggles.
|
|
770
|
+
*
|
|
771
|
+
* @param options - the question, the starting answer and the toggles
|
|
772
|
+
*/
|
|
773
|
+
static readonly screen: <K extends string>(options: ConfirmScreenOptions<K>) => Screen<ConfirmResult<K>>;
|
|
774
|
+
}
|
|
775
|
+
//#endregion
|
|
776
|
+
//#region src/ui/DocView.d.ts
|
|
777
|
+
/**
|
|
778
|
+
* Props of {@link DocView}.
|
|
779
|
+
*
|
|
780
|
+
* @public
|
|
781
|
+
*/
|
|
782
|
+
interface DocViewProps {
|
|
783
|
+
/** The document, or one block of it, which is drawn as a one-block document. */
|
|
784
|
+
readonly doc: Cli.Document | Cli.Block;
|
|
785
|
+
/**
|
|
786
|
+
* The render context. Omitted, it is built from the tree's theme (`useTheme`): its colour, its `paint` (token
|
|
787
|
+
* overrides included) and its glyphs, the width `useTerminalSize().columns`, a human audience, links off, the
|
|
788
|
+
* identity `displayPath`, and `neutralizeWorkflowCommands` when the tree is under the GitHub Actions runner. Given,
|
|
789
|
+
* it replaces that context entirely, neutralizing included (set `neutralizeWorkflowCommands` on it when the runner
|
|
790
|
+
* reads the output), and the view needs no provider.
|
|
791
|
+
*/
|
|
792
|
+
readonly ctx?: Cli.RenderContext;
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* The kit's document IR (`Doc`) drawn as Ink rows, laid out by the kit's own renderers, so a live view and a static
|
|
796
|
+
* report show a document the same way.
|
|
797
|
+
*
|
|
798
|
+
* @remarks
|
|
799
|
+
* The document is rendered with `Render.ansi` (`Render.plain` at colour `none`) at the width, and each line becomes
|
|
800
|
+
* one Ink `Text` row cut with `wrap: "truncate-end"`, so Ink never re-wraps what the renderer laid out. Everything the
|
|
801
|
+
* static renderers do holds: a collapsible is drawn open, an annotation is skipped, text from data is sanitised.
|
|
802
|
+
*
|
|
803
|
+
* Without a `ctx` the view takes its theme from the tree (a screen, a live view, or a `UiProvider`) and its width from
|
|
804
|
+
* `useTerminalSize`, so it follows a resize; for an agent the theme is colourless, so the view is escape-free. Links
|
|
805
|
+
* are off. The layout is memoised on the document's identity and the context: re-render with the same document, as a
|
|
806
|
+
* live view's tick does, and the renderer does not run again; build a new document only when it changes.
|
|
807
|
+
*
|
|
808
|
+
* @param props - the document, and optionally the render context
|
|
809
|
+
*
|
|
810
|
+
* @public
|
|
811
|
+
*/
|
|
812
|
+
export declare const DocView: (props: DocViewProps) => ReactElement;
|
|
813
|
+
//#endregion
|
|
814
|
+
//#region src/ui/KeyHelp.d.ts
|
|
815
|
+
/**
|
|
816
|
+
* Props of {@link KeyHelp}.
|
|
817
|
+
*
|
|
818
|
+
* @public
|
|
819
|
+
*/
|
|
820
|
+
interface KeyHelpProps {
|
|
821
|
+
/** The tables to describe, in order. */
|
|
822
|
+
readonly tables: ReadonlyArray<KeyTable<unknown>>;
|
|
823
|
+
/** Whether to end with the root keys (`esc cancel`); `true` by default. */
|
|
824
|
+
readonly root?: boolean;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* A one-line footer naming every visible binding of the given tables, then the root keys:
|
|
828
|
+
* `↑/↓ move · space toggle · enter continue · esc cancel`.
|
|
829
|
+
*
|
|
830
|
+
* @remarks
|
|
831
|
+
* Drawn from the same tables that dispatch the keys, so the help cannot name a key the screen ignores. Neighbouring
|
|
832
|
+
* rows with the same help share one entry (`↑/↓ move`). It stays one line, cut to the terminal width with the glyph
|
|
833
|
+
* set's ellipsis; when it must be cut, the widget's own keys give way and the root hint (`esc cancel`) stays whole at
|
|
834
|
+
* the end. Painted with the `muted` token; labels follow the screen's glyph set.
|
|
835
|
+
*
|
|
836
|
+
* @param props - the tables, and whether to append the root keys
|
|
837
|
+
*
|
|
838
|
+
* @public
|
|
839
|
+
*/
|
|
840
|
+
export declare const KeyHelp: (props: KeyHelpProps) => ReactElement;
|
|
841
|
+
//#endregion
|
|
842
|
+
//#region src/ui/Viewport.d.ts
|
|
843
|
+
/**
|
|
844
|
+
* Where a viewport is: the selected item, the first item in view, how many items fit, and how many there are.
|
|
845
|
+
*
|
|
846
|
+
* @remarks
|
|
847
|
+
* Every number counts items, never section headers. The reducer keeps `0 <= cursor < count` (or both 0 when there
|
|
848
|
+
* are no items) and `offset <= cursor < offset + height`, and never lets the window run past the last item.
|
|
849
|
+
*
|
|
850
|
+
* @public
|
|
851
|
+
*/
|
|
852
|
+
interface ViewportState {
|
|
853
|
+
/** The selected item. */
|
|
854
|
+
readonly cursor: number;
|
|
855
|
+
/** The first item in view. */
|
|
856
|
+
readonly offset: number;
|
|
857
|
+
/** How many items the window holds. */
|
|
858
|
+
readonly height: number;
|
|
859
|
+
/** How many items there are. */
|
|
860
|
+
readonly count: number;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* A row a viewport shows: a section header, or an item. Only items are selectable. An item's `key` is its React
|
|
864
|
+
* key in the view, so keys must be unique within one list: `Viewport.View` dies on a repeat.
|
|
865
|
+
*
|
|
866
|
+
* @public
|
|
867
|
+
*/
|
|
868
|
+
type ViewportRow = {
|
|
869
|
+
readonly _tag: "Header";
|
|
870
|
+
readonly label: string;
|
|
871
|
+
} | {
|
|
872
|
+
readonly _tag: "Item";
|
|
873
|
+
readonly key: string;
|
|
874
|
+
};
|
|
875
|
+
/**
|
|
876
|
+
* A move through a viewport.
|
|
877
|
+
*
|
|
878
|
+
* @public
|
|
879
|
+
*/
|
|
880
|
+
type ViewportMove = "up" | "down" | "home" | "end" | "pageup" | "pagedown";
|
|
881
|
+
/**
|
|
882
|
+
* Props of {@link Viewport.View}.
|
|
883
|
+
*
|
|
884
|
+
* @public
|
|
885
|
+
*/
|
|
886
|
+
interface ViewportViewProps {
|
|
887
|
+
/** The rows, headers and items, in order; the items are what `state` counts. */
|
|
888
|
+
readonly rows: ReadonlyArray<ViewportRow>;
|
|
889
|
+
/** Where the viewport is. */
|
|
890
|
+
readonly state: ViewportState;
|
|
891
|
+
/** Draws one row; `highlighted` is true for the selected item. Each row is clipped to one line. */
|
|
892
|
+
readonly renderRow: (row: ViewportRow, highlighted: boolean) => ReactElement;
|
|
893
|
+
/** Lines the rest of the screen uses (a title, a help line), taken off the terminal height; 0 by default. */
|
|
894
|
+
readonly reserved?: number;
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* A scrolling list: a pure reducer over a window of items, its key table, and a view that draws the window.
|
|
898
|
+
*
|
|
899
|
+
* @remarks
|
|
900
|
+
* The view never draws more lines than fit: its height is `min(state.height, terminal rows - 1 - reserved)`, and
|
|
901
|
+
* every row is clipped to one line of `columns - 1` cells, so a frame never fills the terminal and Ink never clears
|
|
902
|
+
* the screen and scrollback to redraw it. A section header stays visible: when the header of the first visible item
|
|
903
|
+
* has scrolled off, it is drawn again atop the slice.
|
|
904
|
+
*
|
|
905
|
+
* @public
|
|
906
|
+
*/
|
|
907
|
+
export declare class Viewport {
|
|
908
|
+
private constructor();
|
|
909
|
+
/**
|
|
910
|
+
* A viewport over `count` items, `height` of them in view, with `cursor` selected (clamped; 0 by default).
|
|
911
|
+
*
|
|
912
|
+
* @param count - how many items
|
|
913
|
+
* @param height - how many fit (at least 1)
|
|
914
|
+
* @param cursor - the item to select
|
|
915
|
+
*/
|
|
916
|
+
static readonly init: (count: number, height: number, cursor?: number) => ViewportState;
|
|
917
|
+
/**
|
|
918
|
+
* Move the cursor, clamped at both ends with no wrap, keeping it in view. A page is the window height.
|
|
919
|
+
*
|
|
920
|
+
* @param state - where the viewport is
|
|
921
|
+
* @param move - the move
|
|
922
|
+
*/
|
|
923
|
+
static readonly step: (state: ViewportState, move: ViewportMove) => ViewportState;
|
|
924
|
+
/**
|
|
925
|
+
* Change the window height, keeping the cursor where it is and in view.
|
|
926
|
+
*
|
|
927
|
+
* @param state - where the viewport is
|
|
928
|
+
* @param height - the new height (at least 1)
|
|
929
|
+
*/
|
|
930
|
+
static readonly resize: (state: ViewportState, height: number) => ViewportState;
|
|
931
|
+
/** The keys: ↑/↓ move, pgup/pgdn page, home top, end bottom. */
|
|
932
|
+
static readonly keys: KeyTable<ViewportMove>;
|
|
933
|
+
/**
|
|
934
|
+
* Draw the window: the visible rows, each clipped to one line. A repeated item key is a defect: the view throws, so
|
|
935
|
+
* the screen dies with the reason.
|
|
936
|
+
*
|
|
937
|
+
* @param props - the rows, the state, how to draw a row, and the lines reserved for the rest of the screen
|
|
938
|
+
*/
|
|
939
|
+
static readonly View: (props: ViewportViewProps) => ReactElement;
|
|
940
|
+
}
|
|
941
|
+
//#endregion
|
|
942
|
+
//#region src/ui/MultiSelect.d.ts
|
|
943
|
+
/**
|
|
944
|
+
* One item of a {@link MultiSelect} section.
|
|
945
|
+
*
|
|
946
|
+
* @public
|
|
947
|
+
*/
|
|
948
|
+
interface MultiSelectItem<A> {
|
|
949
|
+
/** Identifies the item within its section. */
|
|
950
|
+
readonly key: string;
|
|
951
|
+
/** What the row shows. */
|
|
952
|
+
readonly label: string;
|
|
953
|
+
/** What selecting it returns. */
|
|
954
|
+
readonly value: A;
|
|
955
|
+
/** A line shown, muted, beneath the list while this item is highlighted. */
|
|
956
|
+
readonly detail?: string;
|
|
957
|
+
/** Whether it starts selected. */
|
|
958
|
+
readonly selected?: boolean;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* A titled group of items; the title is a header the cursor never stops on.
|
|
962
|
+
*
|
|
963
|
+
* @public
|
|
964
|
+
*/
|
|
965
|
+
interface MultiSelectSection<A> {
|
|
966
|
+
/** The header. */
|
|
967
|
+
readonly title: string;
|
|
968
|
+
/** The items, in order. */
|
|
969
|
+
readonly items: ReadonlyArray<MultiSelectItem<A>>;
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Where a {@link MultiSelect} is: its sections, which items are selected, the viewport over the items, and whether
|
|
973
|
+
* it was submitted.
|
|
974
|
+
*
|
|
975
|
+
* @remarks
|
|
976
|
+
* Items are numbered across sections in order, section by section; `chosen` holds those numbers.
|
|
977
|
+
*
|
|
978
|
+
* @public
|
|
979
|
+
*/
|
|
980
|
+
interface MultiSelectState<A> {
|
|
981
|
+
/** The sections. */
|
|
982
|
+
readonly sections: ReadonlyArray<MultiSelectSection<A>>;
|
|
983
|
+
/** The selected items, by their number across all sections. */
|
|
984
|
+
readonly chosen: ReadonlySet<number>;
|
|
985
|
+
/** The highlighted item and the window over the list; it counts items only, never headers. */
|
|
986
|
+
readonly viewport: ViewportState;
|
|
987
|
+
/** Whether enter was pressed. */
|
|
988
|
+
readonly submitted: boolean;
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* What a key does in a {@link MultiSelect}.
|
|
992
|
+
*
|
|
993
|
+
* @public
|
|
994
|
+
*/
|
|
995
|
+
type MultiSelectAction = ViewportMove | "toggle" | "toggleSection" | "submit" | "cancel";
|
|
996
|
+
/**
|
|
997
|
+
* Options for {@link MultiSelect.init}.
|
|
998
|
+
*
|
|
999
|
+
* @public
|
|
1000
|
+
*/
|
|
1001
|
+
interface MultiSelectInitOptions {
|
|
1002
|
+
/** How many rows the list shows at most; the terminal height also limits it. 10 by default. */
|
|
1003
|
+
readonly height?: number;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Options for {@link MultiSelect.screen}.
|
|
1007
|
+
*
|
|
1008
|
+
* @public
|
|
1009
|
+
*/
|
|
1010
|
+
interface MultiSelectScreenOptions<A> {
|
|
1011
|
+
/** The question, shown above the list. */
|
|
1012
|
+
readonly message: string;
|
|
1013
|
+
/** The sections. */
|
|
1014
|
+
readonly sections: ReadonlyArray<MultiSelectSection<A>>;
|
|
1015
|
+
/** How many rows the list shows at most. */
|
|
1016
|
+
readonly height?: number;
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Props of {@link MultiSelect.View}.
|
|
1020
|
+
*
|
|
1021
|
+
* @public
|
|
1022
|
+
*/
|
|
1023
|
+
interface MultiSelectViewProps<A> extends MultiSelectScreenOptions<A> {
|
|
1024
|
+
/** Receives the selected values, in section then item order, when enter is pressed. */
|
|
1025
|
+
readonly onSubmit: (values: ReadonlyArray<A>) => void;
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Several choices from sectioned lists: a pure reducer, its key table, a view and a ready-made screen.
|
|
1029
|
+
*
|
|
1030
|
+
* Item keys must be unique across all sections; `init` throws, and `screen` dies, on a repeat.
|
|
1031
|
+
*
|
|
1032
|
+
* @remarks
|
|
1033
|
+
* The cursor moves over items only; section titles are headers drawn by the viewport, which keeps a scrolled-off
|
|
1034
|
+
* header visible. Submitting with nothing selected resolves an empty list, which is a result, not a cancel.
|
|
1035
|
+
*
|
|
1036
|
+
* @example
|
|
1037
|
+
* ```ts
|
|
1038
|
+
* import { CliUi, MultiSelect } from "@effected/cli/ui"
|
|
1039
|
+
* import { Effect } from "effect"
|
|
1040
|
+
*
|
|
1041
|
+
* const pickFeatures = Effect.gen(function* () {
|
|
1042
|
+
* const features = yield* CliUi.run(
|
|
1043
|
+
* MultiSelect.screen({
|
|
1044
|
+
* message: "Which features?",
|
|
1045
|
+
* sections: [
|
|
1046
|
+
* {
|
|
1047
|
+
* title: "Tooling",
|
|
1048
|
+
* items: [
|
|
1049
|
+
* { key: "lint", label: "Linting", value: "lint", selected: true },
|
|
1050
|
+
* { key: "test", label: "Tests", value: "test" },
|
|
1051
|
+
* ],
|
|
1052
|
+
* },
|
|
1053
|
+
* ],
|
|
1054
|
+
* }),
|
|
1055
|
+
* )
|
|
1056
|
+
* return features
|
|
1057
|
+
* })
|
|
1058
|
+
* ```
|
|
1059
|
+
*
|
|
1060
|
+
* @public
|
|
1061
|
+
*/
|
|
1062
|
+
export declare class MultiSelect {
|
|
1063
|
+
private constructor();
|
|
1064
|
+
/**
|
|
1065
|
+
* A multi-select over `sections`, each item starting as its own `selected` flag says, on the first item.
|
|
1066
|
+
*
|
|
1067
|
+
* @param sections - the sections
|
|
1068
|
+
* @param options - the list height
|
|
1069
|
+
*/
|
|
1070
|
+
static readonly init: <A>(sections: ReadonlyArray<MultiSelectSection<A>>, options?: MultiSelectInitOptions) => MultiSelectState<A>;
|
|
1071
|
+
/**
|
|
1072
|
+
* Apply an action: a viewport move over the items; `"toggle"` flips the highlighted item; `"toggleSection"`
|
|
1073
|
+
* selects every item of the highlighted item's section while any is unselected, and clears them all otherwise;
|
|
1074
|
+
* `"submit"` marks it submitted; `"cancel"` changes nothing here, because ending the screen is the view's job.
|
|
1075
|
+
*
|
|
1076
|
+
* @param state - where the multi-select is
|
|
1077
|
+
* @param action - the action
|
|
1078
|
+
*/
|
|
1079
|
+
static readonly step: <A>(state: MultiSelectState<A>, action: MultiSelectAction) => MultiSelectState<A>;
|
|
1080
|
+
/**
|
|
1081
|
+
* The selected values, in section order and then item order, however they were toggled.
|
|
1082
|
+
*
|
|
1083
|
+
* @param state - where the multi-select is
|
|
1084
|
+
*/
|
|
1085
|
+
static readonly selected: <A>(state: MultiSelectState<A>) => ReadonlyArray<A>;
|
|
1086
|
+
/** The keys: ↑/↓ move (page, home and end too), space toggle, a toggle section, enter continue, q cancel. */
|
|
1087
|
+
static readonly keys: KeyTable<MultiSelectAction>;
|
|
1088
|
+
/**
|
|
1089
|
+
* Draw the multi-select: the message, the sections (each item a check glyph, `◉`/`◯` or `[x]`/`[ ]` under ASCII,
|
|
1090
|
+
* then its label cut to the width; the highlighted one in the accent token with the arrow glyph), the highlighted
|
|
1091
|
+
* item's detail, and the key help. Enter calls `onSubmit` with the selected values; `q` cancels with `"escape"`.
|
|
1092
|
+
*
|
|
1093
|
+
* @remarks
|
|
1094
|
+
* Single-shot, like `Select.View`: the sections are read once at mount.
|
|
1095
|
+
*
|
|
1096
|
+
* @param props - the message, the sections, and where the selection goes
|
|
1097
|
+
*/
|
|
1098
|
+
static readonly View: <A>(props: MultiSelectViewProps<A>) => ReactElement;
|
|
1099
|
+
/**
|
|
1100
|
+
* A ready-made screen for `CliUi.run`: the multi-select, resolving with the selected values (`[]` when none are).
|
|
1101
|
+
*
|
|
1102
|
+
* @param options - the message, the sections and the list height
|
|
1103
|
+
*/
|
|
1104
|
+
static readonly screen: <A>(options: MultiSelectScreenOptions<A>) => Screen<ReadonlyArray<A>>;
|
|
1105
|
+
}
|
|
1106
|
+
//#endregion
|
|
1107
|
+
//#region src/ui/Select.d.ts
|
|
1108
|
+
/**
|
|
1109
|
+
* One choice of a {@link Select}.
|
|
1110
|
+
*
|
|
1111
|
+
* @public
|
|
1112
|
+
*/
|
|
1113
|
+
interface SelectChoice<A> {
|
|
1114
|
+
/** What the row shows. */
|
|
1115
|
+
readonly label: string;
|
|
1116
|
+
/** What choosing it returns. */
|
|
1117
|
+
readonly value: A;
|
|
1118
|
+
/** A line shown, muted, beneath the list while this choice is highlighted. */
|
|
1119
|
+
readonly detail?: string;
|
|
1120
|
+
/** Shown muted and never highlighted: moves skip it. */
|
|
1121
|
+
readonly disabled?: boolean;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Where a {@link Select} is: its choices, the viewport over them, and whether one was submitted.
|
|
1125
|
+
*
|
|
1126
|
+
* @public
|
|
1127
|
+
*/
|
|
1128
|
+
interface SelectState<A> {
|
|
1129
|
+
/** The choices. */
|
|
1130
|
+
readonly choices: ReadonlyArray<SelectChoice<A>>;
|
|
1131
|
+
/** The highlighted choice and the window over the list. */
|
|
1132
|
+
readonly viewport: ViewportState;
|
|
1133
|
+
/** Whether the highlighted choice was submitted. */
|
|
1134
|
+
readonly submitted: boolean;
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* What a key does in a {@link Select}: a viewport move, submit, or cancel.
|
|
1138
|
+
*
|
|
1139
|
+
* @public
|
|
1140
|
+
*/
|
|
1141
|
+
type SelectAction = ViewportMove | "submit" | "cancel";
|
|
1142
|
+
/**
|
|
1143
|
+
* Options for {@link Select.init}.
|
|
1144
|
+
*
|
|
1145
|
+
* @public
|
|
1146
|
+
*/
|
|
1147
|
+
interface SelectInitOptions {
|
|
1148
|
+
/**
|
|
1149
|
+
* The choice to start on: the first enabled one at or after it, or the nearest enabled one before it when none
|
|
1150
|
+
* follows. 0 by default.
|
|
1151
|
+
*/
|
|
1152
|
+
readonly initial?: number;
|
|
1153
|
+
/** How many rows the list shows at most; the terminal height also limits it. 10 by default. */
|
|
1154
|
+
readonly height?: number;
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Props of {@link Select.View}.
|
|
1158
|
+
*
|
|
1159
|
+
* @public
|
|
1160
|
+
*/
|
|
1161
|
+
interface SelectViewProps<A> {
|
|
1162
|
+
/** The question, shown above the list. */
|
|
1163
|
+
readonly message: string;
|
|
1164
|
+
/** The choices. */
|
|
1165
|
+
readonly choices: ReadonlyArray<SelectChoice<A>>;
|
|
1166
|
+
/** The choice to start on. */
|
|
1167
|
+
readonly initial?: number;
|
|
1168
|
+
/** How many rows the list shows at most. */
|
|
1169
|
+
readonly height?: number;
|
|
1170
|
+
/** Receives the value chosen with enter. */
|
|
1171
|
+
readonly onSubmit: (value: A) => void;
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Options for {@link Select.screen}.
|
|
1175
|
+
*
|
|
1176
|
+
* @public
|
|
1177
|
+
*/
|
|
1178
|
+
interface SelectScreenOptions<A> {
|
|
1179
|
+
/** The question, shown above the list. */
|
|
1180
|
+
readonly message: string;
|
|
1181
|
+
/** The choices. */
|
|
1182
|
+
readonly choices: ReadonlyArray<SelectChoice<A>>;
|
|
1183
|
+
/** The choice to start on. */
|
|
1184
|
+
readonly initial?: number;
|
|
1185
|
+
/** How many rows the list shows at most. */
|
|
1186
|
+
readonly height?: number;
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* A single choice from a list: a pure reducer, its key table, a view, and a ready-made screen.
|
|
1190
|
+
*
|
|
1191
|
+
* @remarks
|
|
1192
|
+
* A select with no enabled choice is a programming error: `init` throws, and `screen` dies, saying so.
|
|
1193
|
+
*
|
|
1194
|
+
* @example
|
|
1195
|
+
* ```ts
|
|
1196
|
+
* import { CliUi, Select } from "@effected/cli/ui"
|
|
1197
|
+
* import { Effect } from "effect"
|
|
1198
|
+
*
|
|
1199
|
+
* const pickTarget = Effect.gen(function* () {
|
|
1200
|
+
* const target = yield* CliUi.run(
|
|
1201
|
+
* Select.screen({
|
|
1202
|
+
* message: "Deploy to which environment?",
|
|
1203
|
+
* choices: [
|
|
1204
|
+
* { label: "staging", value: "staging" },
|
|
1205
|
+
* { label: "production", value: "production", detail: "Needs approval" },
|
|
1206
|
+
* ],
|
|
1207
|
+
* }),
|
|
1208
|
+
* )
|
|
1209
|
+
* return target
|
|
1210
|
+
* })
|
|
1211
|
+
* ```
|
|
1212
|
+
*
|
|
1213
|
+
* @public
|
|
1214
|
+
*/
|
|
1215
|
+
export declare class Select {
|
|
1216
|
+
private constructor();
|
|
1217
|
+
/**
|
|
1218
|
+
* A select over `choices`, on the first enabled choice at or after `initial` (the nearest enabled one before it
|
|
1219
|
+
* when none follows).
|
|
1220
|
+
*
|
|
1221
|
+
* @param choices - the choices
|
|
1222
|
+
* @param options - the starting choice and the list height
|
|
1223
|
+
*/
|
|
1224
|
+
static readonly init: <A>(choices: ReadonlyArray<SelectChoice<A>>, options?: SelectInitOptions) => SelectState<A>;
|
|
1225
|
+
/**
|
|
1226
|
+
* Apply an action: a move lands on the nearest enabled choice, never on a disabled one and never past either
|
|
1227
|
+
* end; `"submit"` marks the highlighted choice chosen (a disabled one never is); `"cancel"` changes nothing here,
|
|
1228
|
+
* because ending the screen is the view's job.
|
|
1229
|
+
*
|
|
1230
|
+
* @param state - where the select is
|
|
1231
|
+
* @param action - the action
|
|
1232
|
+
*/
|
|
1233
|
+
static readonly step: <A>(state: SelectState<A>, action: SelectAction) => SelectState<A>;
|
|
1234
|
+
/**
|
|
1235
|
+
* The submitted value, or `None` before a submit.
|
|
1236
|
+
*
|
|
1237
|
+
* @param state - where the select is
|
|
1238
|
+
*/
|
|
1239
|
+
static readonly chosen: <A>(state: SelectState<A>) => Option.Option<A>;
|
|
1240
|
+
/** The keys: the viewport's moves, enter choose, q cancel. */
|
|
1241
|
+
static readonly keys: KeyTable<SelectAction>;
|
|
1242
|
+
/**
|
|
1243
|
+
* Draw the select: the message, the list (the highlighted row in the accent token with the arrow glyph, disabled
|
|
1244
|
+
* rows muted, and at colour `none` ending in ` (disabled)` instead, every row cut to the width with the glyph set's
|
|
1245
|
+
* ellipsis), the highlighted choice's detail, and the
|
|
1246
|
+
* key help. Enter calls `onSubmit` with the value; `q` cancels the screen with `"escape"`.
|
|
1247
|
+
*
|
|
1248
|
+
* Single-shot: the choices and the starting choice are read once, when the view mounts, and later changes to them
|
|
1249
|
+
* are ignored; after a submit it stays as it is. Render a new view (a new screen) to ask again.
|
|
1250
|
+
*
|
|
1251
|
+
* @param props - the message, the choices, and where the chosen value goes
|
|
1252
|
+
*/
|
|
1253
|
+
static readonly View: <A>(props: SelectViewProps<A>) => ReactElement;
|
|
1254
|
+
/**
|
|
1255
|
+
* A ready-made screen for `CliUi.run`: the select, resolving with the chosen value.
|
|
1256
|
+
*
|
|
1257
|
+
* @param options - the message, the choices, the starting choice and the list height
|
|
1258
|
+
*/
|
|
1259
|
+
static readonly screen: <A>(options: SelectScreenOptions<A>) => Screen<A>;
|
|
1260
|
+
}
|
|
1261
|
+
//#endregion
|
|
1262
|
+
//#region src/ui/Tabs.d.ts
|
|
1263
|
+
/**
|
|
1264
|
+
* What a key does to a {@link Tabs} row: move to the previous or next tab (wrapping), or jump to one by index.
|
|
1265
|
+
*
|
|
1266
|
+
* @public
|
|
1267
|
+
*/
|
|
1268
|
+
type TabsAction = "prev" | "next" | {
|
|
1269
|
+
readonly jump: number;
|
|
1270
|
+
};
|
|
1271
|
+
/**
|
|
1272
|
+
* One tab.
|
|
1273
|
+
*
|
|
1274
|
+
* @public
|
|
1275
|
+
*/
|
|
1276
|
+
interface Tab<N extends string> {
|
|
1277
|
+
/** Identifies the tab; `onChange` and `value` speak in names. */
|
|
1278
|
+
readonly name: N;
|
|
1279
|
+
/** What the tab shows. */
|
|
1280
|
+
readonly label: string;
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Props of {@link Tabs.View}.
|
|
1284
|
+
*
|
|
1285
|
+
* @public
|
|
1286
|
+
*/
|
|
1287
|
+
interface TabsProps<N extends string> {
|
|
1288
|
+
/** The tabs, in order. */
|
|
1289
|
+
readonly tabs: ReadonlyArray<Tab<N>>;
|
|
1290
|
+
/** The active tab, when the caller controls it: keys then only ask, through `onChange`. */
|
|
1291
|
+
readonly value?: N;
|
|
1292
|
+
/** The starting tab when uncontrolled; the first tab by default. */
|
|
1293
|
+
readonly defaultValue?: N;
|
|
1294
|
+
/** Called with the starting tab once on mount, then with every tab a key asks for. */
|
|
1295
|
+
readonly onChange?: (name: N, index: number) => void;
|
|
1296
|
+
/**
|
|
1297
|
+
* Whether the tabs read keys; `true` by default. Unfocused, every tab is muted (the active one still underlined)
|
|
1298
|
+
* and keys are ignored.
|
|
1299
|
+
*/
|
|
1300
|
+
readonly isFocused?: boolean;
|
|
1301
|
+
/** Number each tab, `1. Label`; `false` by default. */
|
|
1302
|
+
readonly showIndex?: boolean;
|
|
1303
|
+
/** Between tabs in a row; ` │ ` by default (` | ` under ASCII glyphs). */
|
|
1304
|
+
readonly separator?: string;
|
|
1305
|
+
/** A row (`←`/`→`) or a column (`↑`/`↓`) of tabs; a row by default. */
|
|
1306
|
+
readonly direction?: "row" | "column";
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* A row (or column) of tabs, for a consumer's own screen.
|
|
1310
|
+
*
|
|
1311
|
+
* @remarks
|
|
1312
|
+
* Not a screen: render `Tabs.View` inside a consumer's own screen. Its keys come from one `useKeys`, so there is a
|
|
1313
|
+
* single input reader. Tab and Shift-Tab cycle whenever the tabs are focused, and plain digits jump, so a screen that
|
|
1314
|
+
* hosts Tabs beside another widget reading Tab, arrows or digits (a `TextInput`, say) must decide who has the keys:
|
|
1315
|
+
* pass `isFocused: false` to the Tabs while the other widget is being typed into. When unfocused, Tabs reads no key
|
|
1316
|
+
* and is drawn muted.
|
|
1317
|
+
*
|
|
1318
|
+
* @public
|
|
1319
|
+
*/
|
|
1320
|
+
export declare class Tabs {
|
|
1321
|
+
private constructor();
|
|
1322
|
+
/**
|
|
1323
|
+
* The tab an action lands on: `"prev"` and `"next"` wrap at both ends; `{ jump }` moves to that index and does
|
|
1324
|
+
* nothing when it is out of range.
|
|
1325
|
+
*
|
|
1326
|
+
* @param index - the active tab
|
|
1327
|
+
* @param count - how many tabs there are
|
|
1328
|
+
* @param action - the action
|
|
1329
|
+
*/
|
|
1330
|
+
static readonly step: (index: number, count: number, action: TabsAction) => number;
|
|
1331
|
+
/** The keys of a row: `←`/`→` and Tab/Shift-Tab switch, plain digits 1–9 jump, 0 is the tenth tab. */
|
|
1332
|
+
static readonly keys: KeyTable<TabsAction>;
|
|
1333
|
+
/** The keys of a column: `↑`/`↓` in place of `←`/`→`. */
|
|
1334
|
+
static readonly columnKeys: KeyTable<TabsAction>;
|
|
1335
|
+
/**
|
|
1336
|
+
* Draw the tabs: the active one in the accent token, bold and underlined; the others plain; every tab muted while
|
|
1337
|
+
* unfocused. At colour `"none"`, where all of that vanishes, the active tab is bracketed, `[Alpha]`, and the others
|
|
1338
|
+
* padded a space each side. A row wider than the terminal shows the tabs that fit around the active one, with the
|
|
1339
|
+
* glyph set's ellipsis at a cut edge, so it never wraps.
|
|
1340
|
+
*
|
|
1341
|
+
* @remarks
|
|
1342
|
+
* Controlled when `value` is given: a key calls `onChange` and the active tab moves only when `value` does.
|
|
1343
|
+
* Uncontrolled otherwise, starting at `defaultValue` or the first tab. Either way `onChange` fires once on mount
|
|
1344
|
+
* with the starting tab.
|
|
1345
|
+
*
|
|
1346
|
+
* @param props - the tabs and how they behave
|
|
1347
|
+
*/
|
|
1348
|
+
static readonly View: <N extends string>(props: TabsProps<N>) => ReactElement;
|
|
1349
|
+
}
|
|
1350
|
+
//#endregion
|
|
1351
|
+
//#region src/ui/TextInput.d.ts
|
|
1352
|
+
/**
|
|
1353
|
+
* Where a {@link TextInput} is: its value, the cursor within it, and whether enter was pressed.
|
|
1354
|
+
*
|
|
1355
|
+
* @public
|
|
1356
|
+
*/
|
|
1357
|
+
interface TextInputState {
|
|
1358
|
+
/** The text. */
|
|
1359
|
+
readonly value: string;
|
|
1360
|
+
/**
|
|
1361
|
+
* The insertion point, from 0 to the value's length, in UTF-16 code units, always on a code-point boundary: an
|
|
1362
|
+
* astral character (an emoji) is never split. Editing is by code point, not by grapheme, so a character built
|
|
1363
|
+
* from several code points (a flag, a family emoji) is still crossed one code point at a time.
|
|
1364
|
+
*/
|
|
1365
|
+
readonly cursor: number;
|
|
1366
|
+
/** Whether enter was pressed; the view submits only when the value also validates. */
|
|
1367
|
+
readonly submitted: boolean;
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Options for {@link TextInput.init}.
|
|
1371
|
+
*
|
|
1372
|
+
* @public
|
|
1373
|
+
*/
|
|
1374
|
+
interface TextInputInitOptions {
|
|
1375
|
+
/** The starting text; the cursor starts after it. */
|
|
1376
|
+
readonly initial?: string;
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Options for {@link TextInput.screen}.
|
|
1380
|
+
*
|
|
1381
|
+
* @public
|
|
1382
|
+
*/
|
|
1383
|
+
interface TextInputScreenOptions {
|
|
1384
|
+
/** The question, shown above the input. */
|
|
1385
|
+
readonly message: string;
|
|
1386
|
+
/** The starting text. */
|
|
1387
|
+
readonly initial?: string;
|
|
1388
|
+
/** Shown, muted, while the value is empty. */
|
|
1389
|
+
readonly placeholder?: string;
|
|
1390
|
+
/** Returns a message when the value cannot be submitted, or `undefined` when it can. */
|
|
1391
|
+
readonly validate?: (value: string) => string | undefined;
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Props of {@link TextInput.View}.
|
|
1395
|
+
*
|
|
1396
|
+
* @public
|
|
1397
|
+
*/
|
|
1398
|
+
interface TextInputViewProps extends TextInputScreenOptions {
|
|
1399
|
+
/** Receives the value when enter is pressed and it validates. */
|
|
1400
|
+
readonly onSubmit: (value: string) => void;
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* One line of text: a pure reducer, a view and a ready-made screen.
|
|
1404
|
+
*
|
|
1405
|
+
* @remarks
|
|
1406
|
+
* Every typed character is text, `q` included: the input binds no letter, so Esc and Ctrl-C are the screen's root
|
|
1407
|
+
* keys and still cancel with `"escape"` and `"interrupt"`.
|
|
1408
|
+
*
|
|
1409
|
+
* @example
|
|
1410
|
+
* ```ts
|
|
1411
|
+
* import { CliUi, TextInput } from "@effected/cli/ui"
|
|
1412
|
+
* import { Effect } from "effect"
|
|
1413
|
+
*
|
|
1414
|
+
* const askName = Effect.gen(function* () {
|
|
1415
|
+
* const name = yield* CliUi.run(
|
|
1416
|
+
* TextInput.screen({
|
|
1417
|
+
* message: "Package name?",
|
|
1418
|
+
* placeholder: "my-package",
|
|
1419
|
+
* validate: (value) => (value.trim() === "" ? "A name is required" : undefined),
|
|
1420
|
+
* }),
|
|
1421
|
+
* )
|
|
1422
|
+
* return name
|
|
1423
|
+
* })
|
|
1424
|
+
* ```
|
|
1425
|
+
*
|
|
1426
|
+
* @public
|
|
1427
|
+
*/
|
|
1428
|
+
export declare class TextInput {
|
|
1429
|
+
private constructor();
|
|
1430
|
+
/**
|
|
1431
|
+
* An input holding `initial`, the cursor after it.
|
|
1432
|
+
*
|
|
1433
|
+
* @param options - the starting text
|
|
1434
|
+
*/
|
|
1435
|
+
static readonly init: (options?: TextInputInitOptions) => TextInputState;
|
|
1436
|
+
/**
|
|
1437
|
+
* Apply a key: a typed character (any, `q` included) or space is inserted at the cursor; backspace and delete
|
|
1438
|
+
* remove around it; left, right, home and end move it, clamped to the text; enter marks it submitted. Every other
|
|
1439
|
+
* key changes nothing.
|
|
1440
|
+
*
|
|
1441
|
+
* @param state - where the input is
|
|
1442
|
+
* @param key - the key pressed
|
|
1443
|
+
*/
|
|
1444
|
+
static readonly step: (state: TextInputState, key: UiKey) => TextInputState;
|
|
1445
|
+
/**
|
|
1446
|
+
* Draw the input: the message, the value with the cursor shown as `▏` (`|` under ASCII glyphs, so it stays visible
|
|
1447
|
+
* without colour), the placeholder while empty, a validation message in the error token, and the key help. Enter
|
|
1448
|
+
* submits when `validate` passes; otherwise its message is shown until the next key other than enter, or a paste.
|
|
1449
|
+
*
|
|
1450
|
+
* @remarks
|
|
1451
|
+
* Text read in one go (a fast typist) is typed as it reads: printable runs are inserted whole, a return submits
|
|
1452
|
+
* what came before it (anything after it is dropped), a backspace byte deletes, a line feed becomes a space, and a
|
|
1453
|
+
* tab or other control character is dropped. A bracketed paste is inserted as text, its line breaks as spaces, and
|
|
1454
|
+
* never submits.
|
|
1455
|
+
*
|
|
1456
|
+
* @param props - the message, the starting text, the placeholder, the validator and where the value goes
|
|
1457
|
+
*/
|
|
1458
|
+
static readonly View: (props: TextInputViewProps) => ReactElement;
|
|
1459
|
+
/**
|
|
1460
|
+
* A ready-made screen for `CliUi.run`: the input, resolving with the submitted text.
|
|
1461
|
+
*
|
|
1462
|
+
* @param options - the message, the starting text, the placeholder and the validator
|
|
1463
|
+
*/
|
|
1464
|
+
static readonly screen: (options: TextInputScreenOptions) => Screen<string>;
|
|
1465
|
+
}
|
|
1466
|
+
//#endregion
|
|
1467
|
+
//#region src/ui/Toggle.d.ts
|
|
1468
|
+
/**
|
|
1469
|
+
* Props of {@link Toggle.View}.
|
|
1470
|
+
*
|
|
1471
|
+
* @public
|
|
1472
|
+
*/
|
|
1473
|
+
interface ToggleViewProps {
|
|
1474
|
+
/** What the toggle controls. */
|
|
1475
|
+
readonly label: string;
|
|
1476
|
+
/** Whether it is on. */
|
|
1477
|
+
readonly value: boolean;
|
|
1478
|
+
/** Whether the row is the highlighted one. */
|
|
1479
|
+
readonly highlighted: boolean;
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* An on/off row: a check glyph and a label.
|
|
1483
|
+
*
|
|
1484
|
+
* @public
|
|
1485
|
+
*/
|
|
1486
|
+
export declare class Toggle {
|
|
1487
|
+
private constructor();
|
|
1488
|
+
/**
|
|
1489
|
+
* Draw a toggle row: `◉` on or `◯` off (`[x]` and `[ ]` under ASCII glyphs), then the label, cut to the width with
|
|
1490
|
+
* the glyph set's ellipsis. A highlighted row starts with the arrow glyph and is painted with the accent token.
|
|
1491
|
+
*
|
|
1492
|
+
* @param props - the label, the value and whether the row is highlighted
|
|
1493
|
+
*/
|
|
1494
|
+
static readonly View: (props: ToggleViewProps) => ReactElement;
|
|
1495
|
+
}
|
|
1496
|
+
//#endregion
|
|
1497
|
+
//#region src/ui/UiStreams.d.ts
|
|
1498
|
+
/**
|
|
1499
|
+
* The streams a screen mounts on: Node streams, because Ink's stream contract is Node's.
|
|
1500
|
+
*
|
|
1501
|
+
* @remarks
|
|
1502
|
+
* `stdin` must offer `isTTY`, `setRawMode`, `ref` and `unref`, and emit `readable`; `stdout` and `stderr` offer
|
|
1503
|
+
* `columns`, `rows`, `isTTY` and `write`, and emit `resize`.
|
|
1504
|
+
*
|
|
1505
|
+
* The members are typed with Node's own stream types (`NodeJS.ReadStream`, `NodeJS.WriteStream`), as Ink's are, so a
|
|
1506
|
+
* TypeScript consumer of `./ui` needs `@types/node`, beside the optional peer `@types/react` for its React types.
|
|
1507
|
+
*
|
|
1508
|
+
* @public
|
|
1509
|
+
*/
|
|
1510
|
+
interface UiStreamsShape {
|
|
1511
|
+
/** The input a screen reads keys from. */
|
|
1512
|
+
readonly stdin: NodeJS.ReadStream;
|
|
1513
|
+
/** The output a screen draws on. */
|
|
1514
|
+
readonly stdout: NodeJS.WriteStream;
|
|
1515
|
+
/** The error output, which Ink also binds. */
|
|
1516
|
+
readonly stderr: NodeJS.WriteStream;
|
|
1517
|
+
}
|
|
1518
|
+
declare const UiStreams_base: Context.Reference<UiStreamsShape>;
|
|
1519
|
+
/**
|
|
1520
|
+
* The streams a screen mounts on, the process's own standard streams by default.
|
|
1521
|
+
*
|
|
1522
|
+
* @remarks
|
|
1523
|
+
* A `Context.Reference`, so it never appears in `R`: the default reads the process streams when first used, never
|
|
1524
|
+
* at import, and a test provides in-memory streams with `Effect.provideService(UiStreams, streams)`. `./ui` binds
|
|
1525
|
+
* Node's process streams.
|
|
1526
|
+
*
|
|
1527
|
+
* @public
|
|
1528
|
+
*/
|
|
1529
|
+
export declare class UiStreams extends UiStreams_base {}
|
|
1530
|
+
//#endregion
|
|
1531
|
+
//#region src/ui/UiTheme.d.ts
|
|
1532
|
+
/**
|
|
1533
|
+
* The styling props of an Ink `Text` that a {@link @effected/cli!Style} maps to.
|
|
1534
|
+
*
|
|
1535
|
+
* @public
|
|
1536
|
+
*/
|
|
1537
|
+
interface InkTextProps {
|
|
1538
|
+
/** The foreground: a colour name or a `#rrggbb` hex. */
|
|
1539
|
+
readonly color?: string;
|
|
1540
|
+
/** Bold. */
|
|
1541
|
+
readonly bold?: boolean;
|
|
1542
|
+
/** Dim. */
|
|
1543
|
+
readonly dimColor?: boolean;
|
|
1544
|
+
/** Italic. */
|
|
1545
|
+
readonly italic?: boolean;
|
|
1546
|
+
/** Underline. */
|
|
1547
|
+
readonly underline?: boolean;
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Props of {@link Styled}.
|
|
1551
|
+
*
|
|
1552
|
+
* @public
|
|
1553
|
+
*/
|
|
1554
|
+
interface StyledProps {
|
|
1555
|
+
/** A theme token, or an explicit style. */
|
|
1556
|
+
readonly token: Cli.TokenName | Cli.Style;
|
|
1557
|
+
/** The text. */
|
|
1558
|
+
readonly children?: ReactNode;
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* The usable size of the terminal.
|
|
1562
|
+
*
|
|
1563
|
+
* @public
|
|
1564
|
+
*/
|
|
1565
|
+
interface TerminalSize {
|
|
1566
|
+
/** The width, less one column, so a full-width line never wraps the cursor. */
|
|
1567
|
+
readonly columns: number;
|
|
1568
|
+
/** The height, less one row, so a full-height frame never scrolls. */
|
|
1569
|
+
readonly rows: number;
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* The Ink `Text` props for `style` at `color`.
|
|
1573
|
+
*
|
|
1574
|
+
* @remarks
|
|
1575
|
+
* At `"none"` it gives no styling props at all, not even bold or dim, so a frame is escape-free by construction;
|
|
1576
|
+
* Ink's colour level held at 0 is the backstop. A flag set to `false` adds no prop.
|
|
1577
|
+
*
|
|
1578
|
+
* Omit `color` in an Ink tree the kit did not mount, which has no colour level of its own to pass: every prop is
|
|
1579
|
+
* emitted, as at any level but `"none"`, and Ink's own chalk gates what reaches the terminal.
|
|
1580
|
+
*
|
|
1581
|
+
* @param style - the resolved style
|
|
1582
|
+
* @param color - the stream's colour level; omitted, every prop is emitted for Ink's chalk to gate
|
|
1583
|
+
*
|
|
1584
|
+
* @public
|
|
1585
|
+
*/
|
|
1586
|
+
export declare const inkProps: (style: Cli.Style, color?: ColorLevel) => InkTextProps;
|
|
1587
|
+
/**
|
|
1588
|
+
* The theme of the stream the mounted screen draws on.
|
|
1589
|
+
*
|
|
1590
|
+
* @remarks
|
|
1591
|
+
* A React hook: call it from a component rendered inside a `CliUi.run` screen or a `UiProvider`.
|
|
1592
|
+
*
|
|
1593
|
+
* @public
|
|
1594
|
+
*/
|
|
1595
|
+
export declare const useTheme: () => Cli.StreamTheme;
|
|
1596
|
+
/**
|
|
1597
|
+
* The glyph set of the mounted screen, so a component draws Unicode or ASCII glyphs to match the rest of the output.
|
|
1598
|
+
*
|
|
1599
|
+
* @remarks
|
|
1600
|
+
* A React hook: call it from a component rendered inside a `CliUi.run` screen or a `UiProvider`.
|
|
1601
|
+
*
|
|
1602
|
+
* @public
|
|
1603
|
+
*/
|
|
1604
|
+
export declare const useGlyphs: () => Cli.GlyphSet;
|
|
1605
|
+
/**
|
|
1606
|
+
* Text painted with a theme token or style, through the mounted screen's theme.
|
|
1607
|
+
*
|
|
1608
|
+
* @remarks
|
|
1609
|
+
* Its children are drawn as given. The kit's widgets sanitise every string they draw from data (escapes removed,
|
|
1610
|
+
* line breaks folded) before handing it here; text a consumer passes to `Styled`, or to Ink's own `Text`, is the
|
|
1611
|
+
* consumer's to sanitise, with `Fmt.sanitize`. Ink keeps the escape sequences it is handed, so text from data drawn
|
|
1612
|
+
* unsanitised can paint colour at colour `none` or plant a hyperlink, and a line break in it adds a row the screen's
|
|
1613
|
+
* height budget did not count.
|
|
1614
|
+
*
|
|
1615
|
+
* @param props - the token or style, and the text
|
|
1616
|
+
*
|
|
1617
|
+
* @public
|
|
1618
|
+
*/
|
|
1619
|
+
export declare const Styled: (props: StyledProps) => ReactElement;
|
|
1620
|
+
/**
|
|
1621
|
+
* The usable terminal size: the stdout Ink draws on, less one column and one row, re-read on every render and when
|
|
1622
|
+
* the terminal resizes; or, under a `UiProvider` given a `size`, that size less one column and one row.
|
|
1623
|
+
*
|
|
1624
|
+
* @remarks
|
|
1625
|
+
* A width or height the stream does not report, or reports as 0 (a pty that `script` opens says `0 0`), is unknown and
|
|
1626
|
+
* reads as 80 columns by 24 rows, so a screen never lays itself out at width 0. This is not Ink's own fallback, which
|
|
1627
|
+
* first asks the process's terminal (`terminal-size`: the tty, `COLUMNS`, `tput`) and only then uses 80x24; the kit
|
|
1628
|
+
* reads no `process` here. On a 0x0 pty with `COLUMNS=50`, Ink lays out at 50 while these rows are cut at 79.
|
|
1629
|
+
*
|
|
1630
|
+
* The override exists for Ink's `renderToString`, whose `useStdout` is the process's own stdout whatever width it
|
|
1631
|
+
* lays out at; without it, the kit's widgets would cut their rows to the wrong width there.
|
|
1632
|
+
*
|
|
1633
|
+
* Never feed `columns` into a `Box`'s `width`. On a resize Ink re-lays out the tree it already has and repaints
|
|
1634
|
+
* before React re-renders with the new size, so a width taken from this hook is one paint stale. After a shrink,
|
|
1635
|
+
* that stale, wider frame wraps in the narrower terminal and leaves a copy stranded above the live one. For a
|
|
1636
|
+
* one-column margin use `marginRight: 1`, which Ink recomputes within its own resize. Text cut to `columns` lags the
|
|
1637
|
+
* same paint, so give a long row Ink's `wrap: "truncate-end"` too: on a shrink Ink then clips it rather than letting
|
|
1638
|
+
* the terminal wrap it.
|
|
1639
|
+
*
|
|
1640
|
+
* A React hook: call it from a component rendered inside an Ink tree; it needs no screen, but reads a `UiProvider`'s
|
|
1641
|
+
* size when there is one.
|
|
1642
|
+
*
|
|
1643
|
+
* @public
|
|
1644
|
+
*/
|
|
1645
|
+
export declare const useTerminalSize: () => TerminalSize;
|
|
1646
|
+
//#endregion
|
|
1647
|
+
export type { Binding, CliUiFallbackOptions, CliUiPromptOptions, CliUiRunOptions, ConfirmAction, ConfirmInitOptions, ConfirmResult, ConfirmScreenOptions, ConfirmState, ConfirmToggle, ConfirmViewProps, DocViewProps, InkTextProps, KeyHelpProps, KeyHelpRow, KeyName, LiveHandle, LiveOptions, MultiSelectAction, MultiSelectInitOptions, MultiSelectItem, MultiSelectScreenOptions, MultiSelectSection, MultiSelectState, MultiSelectViewProps, Screen, ScreenControl, SelectAction, SelectChoice, SelectInitOptions, SelectScreenOptions, SelectState, SelectViewProps, StyledProps, Tab, TabsAction, TabsProps, TerminalSize, TextInputInitOptions, TextInputScreenOptions, TextInputState, TextInputViewProps, ToggleViewProps, UiContextValue, UiProviderProps, UiStreamsShape, UseKeysOptions, ViewportMove, ViewportRow, ViewportState, ViewportViewProps };
|
|
1648
|
+
//# sourceMappingURL=ui.d.ts.map
|