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 */
12import type * as Cause from "./Cause.ts"
13import * as Context from "./Context.ts"
14import type * as Effect from "./Effect.ts"
15import type * as Option from "./Option.ts"
16import type { PlatformError } from "./PlatformError.ts"
17import * as Predicate from "./Predicate.ts"
18import type * as Queue from "./Queue.ts"
19import * as Schema from "./Schema.ts"
20import type * as Scope from "./Scope.ts"
21
22const 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 */
31export 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 */
64export 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 */
97export 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
108const 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 */
124export 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 */
153export 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 */
166export 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 */
186export const make = (
187 impl: Omit<Terminal, typeof TypeId>
188): Terminal => Terminal.of({ ...impl, [TypeId]: TypeId })