| 1 | /** |
| 2 | * Service definition for interactive terminal capabilities. Programs can query |
| 3 | * terminal dimensions, read a line of input, receive low-level key events, and |
| 4 | * display text without depending directly on a specific platform implementation. |
| 5 | * |
| 6 | * This module defines the `Terminal` service, input event shapes, key metadata, |
| 7 | * the `QuitError` used when a user cancels input, a guard for that error, and a |
| 8 | * constructor for custom terminal service implementations. |
| 9 | * |
| 10 | * @since 4.0.0 |
| 11 | */ |
| 12 | import type * as Cause from "./Cause.ts" |
| 13 | import * as Context from "./Context.ts" |
| 14 | import type * as Effect from "./Effect.ts" |
| 15 | import type * as Option from "./Option.ts" |
| 16 | import type { PlatformError } from "./PlatformError.ts" |
| 17 | import * as Predicate from "./Predicate.ts" |
| 18 | import type * as Queue from "./Queue.ts" |
| 19 | import * as Schema from "./Schema.ts" |
| 20 | import type * as Scope from "./Scope.ts" |
| 21 | |
| 22 | const TypeId = "~effect/Terminal" |
| 23 | |
| 24 | /** |
| 25 | * A `Terminal` represents a command-line interface which can read input from a |
| 26 | * user and display messages to a user. |
| 27 | * |
| 28 | * @category services |
| 29 | * @since 4.0.0 |
| 30 | */ |
| 31 | export interface Terminal { |
| 32 | readonly [TypeId]: typeof TypeId |
| 33 | |
| 34 | /** |
| 35 | * The number of columns available on the platform's terminal interface. |
| 36 | */ |
| 37 | readonly columns: Effect.Effect<number> |
| 38 | /** |
| 39 | * The number of rows available on the platform's terminal interface. |
| 40 | */ |
| 41 | |
| 42 | readonly rows: Effect.Effect<number> |
| 43 | /** |
| 44 | * Reads input events from the default standard input. |
| 45 | */ |
| 46 | readonly readInput: Effect.Effect<Queue.Dequeue<UserInput, Cause.Done>, never, Scope.Scope> |
| 47 | /** |
| 48 | * Reads a single line from the default standard input. |
| 49 | */ |
| 50 | readonly readLine: Effect.Effect<string, QuitError> |
| 51 | /** |
| 52 | * Displays text to the default standard output. |
| 53 | */ |
| 54 | readonly display: (text: string) => Effect.Effect<void, PlatformError> |
| 55 | } |
| 56 | |
| 57 | /** |
| 58 | * Keyboard key metadata for terminal input, including the key name and |
| 59 | * modifier state. |
| 60 | * |
| 61 | * @category models |
| 62 | * @since 4.0.0 |
| 63 | */ |
| 64 | export interface Key { |
| 65 | /** |
| 66 | * The name of the key being pressed. |
| 67 | */ |
| 68 | readonly name: string |
| 69 | /** |
| 70 | * If set to `true`, then the user is also holding down the `Ctrl` key. |
| 71 | */ |
| 72 | readonly ctrl: boolean |
| 73 | /** |
| 74 | * If set to `true`, then the user is also holding down the `Meta` key. |
| 75 | */ |
| 76 | readonly meta: boolean |
| 77 | /** |
| 78 | * If set to `true`, then the user is also holding down the `Shift` key. |
| 79 | */ |
| 80 | readonly shift: boolean |
| 81 | } |
| 82 | |
| 83 | /** |
| 84 | * A terminal input event containing an optional raw character and the parsed |
| 85 | * key that was pressed. |
| 86 | * |
| 87 | * **When to use** |
| 88 | * |
| 89 | * Use when consuming low-level terminal input events from `Terminal.readInput` |
| 90 | * and you need both raw character input and parsed key metadata. |
| 91 | * |
| 92 | * @see {@link Key} for the parsed key metadata stored on each input event |
| 93 | * |
| 94 | * @category models |
| 95 | * @since 4.0.0 |
| 96 | */ |
| 97 | export interface UserInput { |
| 98 | /** |
| 99 | * The character read from the user (if any). |
| 100 | */ |
| 101 | readonly input: Option.Option<string> |
| 102 | /** |
| 103 | * The key that the user pressed. |
| 104 | */ |
| 105 | readonly key: Key |
| 106 | } |
| 107 | |
| 108 | const QuitErrorTypeId = "~effect/Terminal/QuitError" |
| 109 | |
| 110 | /** |
| 111 | * Represents an error that occurs when a user attempts to |
| 112 | * quit out of a `Terminal` prompt for input (usually by entering `ctrl`+`c`). |
| 113 | * |
| 114 | * **When to use** |
| 115 | * |
| 116 | * Use when implementing terminal input or prompts that need to signal |
| 117 | * user-requested cancellation through the typed error channel. |
| 118 | * |
| 119 | * @see {@link isQuitError} for checking unknown errors when handling terminal cancellation |
| 120 | * |
| 121 | * @category errors |
| 122 | * @since 4.0.0 |
| 123 | */ |
| 124 | export class QuitError extends Schema.Error<QuitError>("QuitError")({ |
| 125 | _tag: Schema.tag("QuitError") |
| 126 | }) { |
| 127 | /** |
| 128 | * Marks this value as a terminal quit error for runtime guards. |
| 129 | * |
| 130 | * @since 4.0.0 |
| 131 | */ |
| 132 | readonly [QuitErrorTypeId] = QuitErrorTypeId |
| 133 | } |
| 134 | |
| 135 | /** |
| 136 | * Returns `true` if the provided value is a `Terminal.QuitError`. |
| 137 | * |
| 138 | * **When to use** |
| 139 | * |
| 140 | * Use to narrow unknown failures to `QuitError` when handling terminal input |
| 141 | * cancellation. |
| 142 | * |
| 143 | * **Details** |
| 144 | * |
| 145 | * Returns `true` when the value carries the `QuitError` runtime marker and |
| 146 | * narrows it to `QuitError`. |
| 147 | * |
| 148 | * @see {@link QuitError} for the error value produced when terminal input is quit |
| 149 | * |
| 150 | * @category guards |
| 151 | * @since 4.0.0 |
| 152 | */ |
| 153 | export const isQuitError = (u: unknown): u is QuitError => Predicate.hasProperty(u, QuitErrorTypeId) |
| 154 | |
| 155 | /** |
| 156 | * Service tag for command-line input and output services. |
| 157 | * |
| 158 | * **When to use** |
| 159 | * |
| 160 | * Use to access or provide platform terminal capabilities such as reading |
| 161 | * input, writing output, and inspecting terminal dimensions. |
| 162 | * |
| 163 | * @category services |
| 164 | * @since 4.0.0 |
| 165 | */ |
| 166 | export const Terminal: Context.Service<Terminal, Terminal> = Context.Service("effect/Terminal") |
| 167 | |
| 168 | /** |
| 169 | * Creates a `Terminal` service implementation. |
| 170 | * |
| 171 | * **When to use** |
| 172 | * |
| 173 | * Use to construct a custom `Terminal` service implementation from concrete |
| 174 | * terminal capabilities when writing a platform adapter, test implementation, |
| 175 | * or custom runtime service. |
| 176 | * |
| 177 | * **Details** |
| 178 | * |
| 179 | * The implementation object supplies `columns`, `rows`, `readInput`, |
| 180 | * `readLine`, and `display`; `make` attaches the `Terminal` service marker so |
| 181 | * the result can be provided through the `Terminal` context service. |
| 182 | * |
| 183 | * @category constructors |
| 184 | * @since 4.0.0 |
| 185 | */ |
| 186 | export const make = ( |
| 187 | impl: Omit<Terminal, typeof TypeId> |
| 188 | ): Terminal => Terminal.of({ ...impl, [TypeId]: TypeId }) |