neon 3.0.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -5
- package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
- package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
- package/dist/_chunks/credentials-MYdHdKah.js +188 -0
- package/dist/_chunks/env-NbA61JR3.js +585 -0
- package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
- package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
- package/dist/_chunks/profiles-Ir29rqns.js +217 -0
- package/dist/_chunks/psql-DWH-kc69.js +2169 -0
- package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
- package/dist/analytics.js +163 -207
- package/dist/api.js +815 -758
- package/dist/auth.js +121 -141
- package/dist/auth_context.js +39 -53
- package/dist/cli.js +4 -7
- package/dist/commands/api.js +220 -250
- package/dist/commands/api_keys.js +251 -314
- package/dist/commands/auth.js +283 -328
- package/dist/commands/bootstrap.js +372 -437
- package/dist/commands/branches.js +304 -455
- package/dist/commands/bucket.js +374 -514
- package/dist/commands/checkout.js +213 -298
- package/dist/commands/config.js +573 -690
- package/dist/commands/connection_string.js +137 -165
- package/dist/commands/data_api.js +238 -260
- package/dist/commands/databases.js +67 -76
- package/dist/commands/deploy.js +31 -25
- package/dist/commands/dev.js +639 -719
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +255 -305
- package/dist/commands/functions.js +275 -355
- package/dist/commands/index.js +70 -65
- package/dist/commands/init.js +84 -119
- package/dist/commands/inspect.js +55 -55
- package/dist/commands/ip_allow.js +88 -120
- package/dist/commands/link.js +874 -1019
- package/dist/commands/logs.js +291 -0
- package/dist/commands/neon_auth.js +725 -933
- package/dist/commands/operations.js +34 -25
- package/dist/commands/orgs.js +28 -18
- package/dist/commands/profile.js +615 -846
- package/dist/commands/projects.js +313 -373
- package/dist/commands/psql.js +60 -58
- package/dist/commands/roles.js +55 -58
- package/dist/commands/schema_diff.js +87 -131
- package/dist/commands/set_context.js +34 -26
- package/dist/commands/snapshots.js +288 -413
- package/dist/commands/status.js +41 -37
- package/dist/commands/user.js +21 -10
- package/dist/commands/vpc_endpoints.js +85 -113
- package/dist/config.js +7 -6
- package/dist/config_format.js +50 -66
- package/dist/config_template.js +128 -157
- package/dist/context.js +183 -235
- package/dist/current_branch_fast_path.js +40 -49
- package/dist/dev/env.js +2 -446
- package/dist/dev/functions.js +54 -68
- package/dist/dev/inputs.js +46 -58
- package/dist/dev/runtime.js +135 -164
- package/dist/dev/websocket.js +766 -959
- package/dist/env.js +27 -33
- package/dist/env_file.js +118 -132
- package/dist/env_services.js +2 -51
- package/dist/errors.js +57 -68
- package/dist/functions_api.js +45 -43
- package/dist/help.js +189 -140
- package/dist/index.js +182 -257
- package/dist/init/agents.js +137 -118
- package/dist/init/auth.js +58 -68
- package/dist/init/bootstrap.js +325 -396
- package/dist/init/build_config.js +4 -2
- package/dist/init/detect_agent.js +56 -101
- package/dist/init/editors.js +35 -52
- package/dist/init/enrich_output.js +51 -66
- package/dist/init/extension.js +134 -171
- package/dist/init/inspect.js +179 -266
- package/dist/init/interactive.js +510 -622
- package/dist/init/neonctl.js +117 -168
- package/dist/init/orchestrate.js +157 -173
- package/dist/init/phases/auth.js +188 -202
- package/dist/init/phases/cleanup.js +23 -23
- package/dist/init/phases/db.js +251 -277
- package/dist/init/phases/getting_started.js +213 -223
- package/dist/init/phases/mcp.js +174 -224
- package/dist/init/phases/migrations.js +247 -248
- package/dist/init/phases/neon_auth.js +114 -133
- package/dist/init/phases/setup.js +546 -703
- package/dist/init/phases/skills.js +75 -86
- package/dist/init/phases/status.js +72 -67
- package/dist/init/resolve_context.js +102 -99
- package/dist/init/route_command.js +91 -98
- package/dist/init/skills.js +174 -218
- package/dist/init/vsix.js +77 -99
- package/dist/log.js +17 -16
- package/dist/neon_services.js +104 -129
- package/dist/parameters.gen.js +481 -471
- package/dist/pkg.js +17 -19
- package/dist/profile_keys.js +44 -47
- package/dist/psql/cli.js +44 -47
- package/dist/psql/command/cmd_cond.js +231 -406
- package/dist/psql/command/cmd_connect.js +557 -764
- package/dist/psql/command/cmd_copy.js +728 -984
- package/dist/psql/command/cmd_describe.js +1499 -1688
- package/dist/psql/command/cmd_format.js +733 -905
- package/dist/psql/command/cmd_io.js +2 -2193
- package/dist/psql/command/cmd_lo.js +297 -359
- package/dist/psql/command/cmd_meta.js +727 -878
- package/dist/psql/command/cmd_misc.js +138 -172
- package/dist/psql/command/cmd_pipeline.js +2 -1148
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +260 -325
- package/dist/psql/command/inputQueue.js +35 -33
- package/dist/psql/command/shared.js +49 -63
- package/dist/psql/complete/filenames.js +90 -133
- package/dist/psql/complete/index.js +59 -97
- package/dist/psql/complete/matcher.js +236 -300
- package/dist/psql/complete/psqlVars.js +218 -223
- package/dist/psql/complete/queries.js +159 -177
- package/dist/psql/complete/rules.js +1493 -2299
- package/dist/psql/core/common.js +2 -1253
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1303
- package/dist/psql/core/prompt.js +391 -408
- package/dist/psql/core/settings.js +429 -644
- package/dist/psql/core/sqlHelp.js +480 -554
- package/dist/psql/core/startup.js +2 -846
- package/dist/psql/core/syncVars.js +67 -110
- package/dist/psql/core/variables.js +156 -278
- package/dist/psql/describe/formatters.js +884 -1285
- package/dist/psql/describe/processNamePattern.js +173 -260
- package/dist/psql/describe/queries.js +1368 -2403
- package/dist/psql/describe/versionGate.js +32 -41
- package/dist/psql/index.js +2 -2030
- package/dist/psql/io/history.js +232 -271
- package/dist/psql/io/input.js +103 -108
- package/dist/psql/io/lineEditor/buffer.js +238 -319
- package/dist/psql/io/lineEditor/complete.js +135 -213
- package/dist/psql/io/lineEditor/filename.js +139 -148
- package/dist/psql/io/lineEditor/index.js +653 -870
- package/dist/psql/io/lineEditor/keymap.js +544 -702
- package/dist/psql/io/lineEditor/vt100.js +294 -341
- package/dist/psql/io/pgpass.js +158 -187
- package/dist/psql/io/pgservice.js +146 -183
- package/dist/psql/io/psqlrc.js +328 -403
- package/dist/psql/print/aligned.js +1020 -1683
- package/dist/psql/print/asciidoc.js +180 -214
- package/dist/psql/print/crosstab.js +281 -442
- package/dist/psql/print/csv.js +48 -70
- package/dist/psql/print/html.js +195 -226
- package/dist/psql/print/json.js +75 -88
- package/dist/psql/print/latex.js +291 -364
- package/dist/psql/print/pager.js +171 -242
- package/dist/psql/print/troff.js +194 -226
- package/dist/psql/print/unaligned.js +69 -95
- package/dist/psql/print/units.js +167 -169
- package/dist/psql/scanner/slash.js +428 -483
- package/dist/psql/scanner/sql.js +445 -889
- package/dist/psql/scanner/stringutils.js +309 -379
- package/dist/psql/types/index.js +8 -7
- package/dist/psql/types/scanner.js +25 -22
- package/dist/psql/wire/connection.js +2042 -2803
- package/dist/psql/wire/copy.js +84 -100
- package/dist/psql/wire/notify.js +39 -59
- package/dist/psql/wire/pipeline.js +305 -518
- package/dist/psql/wire/protocol.js +349 -417
- package/dist/psql/wire/sasl.js +180 -265
- package/dist/psql/wire/tls.js +400 -561
- package/dist/storage_api.js +115 -129
- package/dist/test_utils/fixtures.js +94 -113
- package/dist/test_utils/oauth_server.js +10 -7
- package/dist/test_utils/project_dir.js +33 -0
- package/dist/utils/ai_gateway_notice.js +131 -162
- package/dist/utils/api_enums.js +21 -28
- package/dist/utils/auth.js +10 -4
- package/dist/utils/branch_notice.js +20 -19
- package/dist/utils/branch_picker.js +83 -89
- package/dist/utils/cli_name.js +15 -12
- package/dist/utils/compute_units.js +20 -27
- package/dist/utils/config_diff.js +127 -158
- package/dist/utils/enrichers.js +95 -148
- package/dist/utils/esbuild.js +130 -189
- package/dist/utils/flags.js +35 -47
- package/dist/utils/formats.js +8 -15
- package/dist/utils/git_diff.js +69 -80
- package/dist/utils/inspect_db.js +101 -143
- package/dist/utils/inspect_queries.js +179 -142
- package/dist/utils/middlewares.js +39 -45
- package/dist/utils/openapi.js +87 -99
- package/dist/utils/package_manager.js +312 -110
- package/dist/utils/point_in_time.js +49 -53
- package/dist/utils/psql.js +89 -106
- package/dist/utils/service_picker.js +55 -58
- package/dist/utils/string.js +5 -5
- package/dist/utils/ui.js +38 -55
- package/dist/utils/write_sync.js +26 -35
- package/dist/utils/zip.js +4 -3
- package/dist/writer.js +67 -87
- package/package.json +11 -6
- package/dist/_shared/auth_selection.js +0 -86
- package/dist/_shared/credentials.js +0 -209
- package/dist/_shared/env-core/env.js +0 -558
- package/dist/_shared/env-core/reuse-secrets.js +0 -223
- package/dist/_shared/paths.js +0 -148
- package/dist/_shared/profiles.js +0 -276
- package/dist/_shared/secure_file.js +0 -43
|
@@ -1,893 +1,676 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public-facing line editor.
|
|
3
|
-
*
|
|
4
|
-
* Glues the streaming VT100 decoder, the keymap, the line buffer, and
|
|
5
|
-
* the completion engine into a `readLine(prompt)` Promise-returning API.
|
|
6
|
-
*
|
|
7
|
-
* Architecture
|
|
8
|
-
* ------------
|
|
9
|
-
*
|
|
10
|
-
* stdin (raw mode)
|
|
11
|
-
* │ bytes
|
|
12
|
-
* ▼
|
|
13
|
-
* Vt100Decoder ─── KeyEvent[]
|
|
14
|
-
* │
|
|
15
|
-
* ▼
|
|
16
|
-
* dispatch(state, ev) ─── EditorAction
|
|
17
|
-
* │
|
|
18
|
-
* ▼
|
|
19
|
-
* render(prompt, state, stdout) (CSI writes)
|
|
20
|
-
* │
|
|
21
|
-
* └── on submit: resolve readLine() with state.buffer.text
|
|
22
|
-
* └── on ^C: reject with SignalError
|
|
23
|
-
* └── on ^D: resolve with EOF symbol
|
|
24
|
-
*
|
|
25
|
-
* Rendering strategy
|
|
26
|
-
* ------------------
|
|
27
|
-
*
|
|
28
|
-
* Naive but robust: track the number of rows last drawn, on every
|
|
29
|
-
* redraw move the cursor up to the prompt's anchor row and rewrite the
|
|
30
|
-
* whole `prompt + buffer.text` block. This avoids per-keystroke diffing
|
|
31
|
-
* bugs at the cost of a few extra bytes per keystroke. For the
|
|
32
|
-
* ~80-char lines users actually type interactively this is invisible.
|
|
33
|
-
*
|
|
34
|
-
* Wrapping uses the inlined `displayWidth` (port of WP-09's table) so
|
|
35
|
-
* East-Asian wide characters and zero-width combining marks render
|
|
36
|
-
* correctly. We never call `stdout.write` with embedded `\n`; line
|
|
37
|
-
* breaks come from explicit `\r\n` only on submit or when we need to
|
|
38
|
-
* display multi-row output (paste-mode newlines, candidate listings).
|
|
39
|
-
*/
|
|
40
1
|
import { LineBuffer } from "./buffer.js";
|
|
41
|
-
import { CompletionState, formatCandidates
|
|
42
|
-
import { dispatch, makeState
|
|
43
|
-
import {
|
|
2
|
+
import { CompletionState, formatCandidates } from "./complete.js";
|
|
3
|
+
import { dispatch, makeState } from "./keymap.js";
|
|
4
|
+
import { Vt100Decoder, csiClearScreen, csiDown, csiEraseToEol, csiRight, csiUp, disableBracketedPaste, enableBracketedPaste } from "./vt100.js";
|
|
5
|
+
//#region src/psql/io/lineEditor/index.ts
|
|
6
|
+
/**
|
|
7
|
+
* Public-facing line editor.
|
|
8
|
+
*
|
|
9
|
+
* Glues the streaming VT100 decoder, the keymap, the line buffer, and
|
|
10
|
+
* the completion engine into a `readLine(prompt)` Promise-returning API.
|
|
11
|
+
*
|
|
12
|
+
* Architecture
|
|
13
|
+
* ------------
|
|
14
|
+
*
|
|
15
|
+
* stdin (raw mode)
|
|
16
|
+
* │ bytes
|
|
17
|
+
* ▼
|
|
18
|
+
* Vt100Decoder ─── KeyEvent[]
|
|
19
|
+
* │
|
|
20
|
+
* ▼
|
|
21
|
+
* dispatch(state, ev) ─── EditorAction
|
|
22
|
+
* │
|
|
23
|
+
* ▼
|
|
24
|
+
* render(prompt, state, stdout) (CSI writes)
|
|
25
|
+
* │
|
|
26
|
+
* └── on submit: resolve readLine() with state.buffer.text
|
|
27
|
+
* └── on ^C: reject with SignalError
|
|
28
|
+
* └── on ^D: resolve with EOF symbol
|
|
29
|
+
*
|
|
30
|
+
* Rendering strategy
|
|
31
|
+
* ------------------
|
|
32
|
+
*
|
|
33
|
+
* Naive but robust: track the number of rows last drawn, on every
|
|
34
|
+
* redraw move the cursor up to the prompt's anchor row and rewrite the
|
|
35
|
+
* whole `prompt + buffer.text` block. This avoids per-keystroke diffing
|
|
36
|
+
* bugs at the cost of a few extra bytes per keystroke. For the
|
|
37
|
+
* ~80-char lines users actually type interactively this is invisible.
|
|
38
|
+
*
|
|
39
|
+
* Wrapping uses the inlined `displayWidth` (port of WP-09's table) so
|
|
40
|
+
* East-Asian wide characters and zero-width combining marks render
|
|
41
|
+
* correctly. We never call `stdout.write` with embedded `\n`; line
|
|
42
|
+
* breaks come from explicit `\r\n` only on submit or when we need to
|
|
43
|
+
* display multi-row output (paste-mode newlines, candidate listings).
|
|
44
|
+
*/
|
|
44
45
|
/** Thrown when ^C cancels the current line. */
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
46
|
+
var SignalError = class extends Error {
|
|
47
|
+
constructor() {
|
|
48
|
+
super("SIGINT");
|
|
49
|
+
this.name = "SignalError";
|
|
50
|
+
this.signal = "SIGINT";
|
|
51
|
+
}
|
|
52
|
+
};
|
|
52
53
|
/**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
* Sentinel for "Ctrl-D on an empty line". Compared with `===` by callers
|
|
55
|
+
* so we don't accidentally match a literal string.
|
|
56
|
+
*/
|
|
56
57
|
const EOF_SYMBOL = Symbol("LineEditor.EOF");
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
a.rowsDrawn = 0;
|
|
547
|
-
a.cursorRow = 0;
|
|
548
|
-
a.cursorCol = 0;
|
|
549
|
-
a.listingRowsDrawn = blockRows;
|
|
550
|
-
this.render(true);
|
|
551
|
-
}
|
|
552
|
-
// -------------------------------------------------------------------------
|
|
553
|
-
// Reverse-incremental-search
|
|
554
|
-
// -------------------------------------------------------------------------
|
|
555
|
-
async handleSearchKey(ev) {
|
|
556
|
-
if (this.active?.search == null)
|
|
557
|
-
return;
|
|
558
|
-
const s = this.active.search;
|
|
559
|
-
// ^G or Escape cancels and restores the saved line.
|
|
560
|
-
if ((ev.key === "char" && ev.ctrl && ev.char === "g") ||
|
|
561
|
-
ev.key === "escape") {
|
|
562
|
-
this.state.buffer.setText(s.savedBuffer);
|
|
563
|
-
this.active.search = null;
|
|
564
|
-
this.render();
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
// Enter accepts the current match (whatever's in the buffer).
|
|
568
|
-
if (ev.key === "enter") {
|
|
569
|
-
this.active.search = null;
|
|
570
|
-
await this.applyAction({ kind: "submit" }, ev);
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
// ^C bubbles to cancel.
|
|
574
|
-
if (ev.key === "char" && ev.ctrl && ev.char === "c") {
|
|
575
|
-
await this.applyAction({ kind: "cancel" }, ev);
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
// ^R again: search further back.
|
|
579
|
-
if (ev.key === "char" && ev.ctrl && ev.char === "r") {
|
|
580
|
-
this.searchStep(-1);
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
// Backspace: shrink the pattern.
|
|
584
|
-
if (ev.key === "backspace") {
|
|
585
|
-
s.pattern = s.pattern.slice(0, -1);
|
|
586
|
-
s.matchIndex = null;
|
|
587
|
-
this.searchStep(0);
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
// Printable char: extend the pattern.
|
|
591
|
-
if (ev.key === "char" &&
|
|
592
|
-
!ev.ctrl &&
|
|
593
|
-
!ev.meta &&
|
|
594
|
-
ev.char !== undefined) {
|
|
595
|
-
s.pattern += ev.char;
|
|
596
|
-
s.matchIndex = null;
|
|
597
|
-
this.searchStep(0);
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
// Anything else (arrows, etc) accepts the match and processes the key.
|
|
601
|
-
this.active.search = null;
|
|
602
|
-
await this.handleEvent(ev);
|
|
603
|
-
}
|
|
604
|
-
/** Walk history backward looking for the current pattern. */
|
|
605
|
-
searchStep(delta) {
|
|
606
|
-
if (this.active?.search == null)
|
|
607
|
-
return;
|
|
608
|
-
const s = this.active.search;
|
|
609
|
-
const hist = this.state.history;
|
|
610
|
-
const startFrom = s.matchIndex === null ? hist.length - 1 : s.matchIndex + delta;
|
|
611
|
-
for (let i = startFrom; i >= 0 && i < hist.length; i--) {
|
|
612
|
-
if (s.pattern === "" || hist[i].includes(s.pattern)) {
|
|
613
|
-
s.matchIndex = i;
|
|
614
|
-
this.state.buffer.setText(hist[i]);
|
|
615
|
-
this.render();
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
// No match: ring bell, keep current buffer.
|
|
620
|
-
this.stdout.write(BEL);
|
|
621
|
-
this.render();
|
|
622
|
-
}
|
|
623
|
-
// -------------------------------------------------------------------------
|
|
624
|
-
// Rendering
|
|
625
|
-
// -------------------------------------------------------------------------
|
|
626
|
-
/**
|
|
627
|
-
* Repaint the prompt + buffer. If `full` is true, skip the cursor-up
|
|
628
|
-
* optimisation (we don't know the previous geometry).
|
|
629
|
-
*/
|
|
630
|
-
render(full = false) {
|
|
631
|
-
if (this.active === null)
|
|
632
|
-
return;
|
|
633
|
-
const a = this.active;
|
|
634
|
-
// Move the cursor back to row 0 of the previously drawn block.
|
|
635
|
-
if (!full && a.rowsDrawn > 0) {
|
|
636
|
-
// Move up to the anchor row.
|
|
637
|
-
const up = a.cursorRow;
|
|
638
|
-
this.stdout.write(CR + (up > 0 ? csiUp(up) : ""));
|
|
639
|
-
}
|
|
640
|
-
else {
|
|
641
|
-
this.stdout.write(CR);
|
|
642
|
-
}
|
|
643
|
-
// Compose the output: prompt + buffer text, with line wrapping computed
|
|
644
|
-
// virtually so we know where the cursor ends up.
|
|
645
|
-
const width = Math.max(1, this.termWidth());
|
|
646
|
-
// Pick the prompt + rendered text for the three render flavours:
|
|
647
|
-
// - vi ex-mode (`:`-prompt): `: <exBuffer>`, cursor at the end.
|
|
648
|
-
// - reverse-i-search: the `(reverse-i-search)\`pat':` preamble,
|
|
649
|
-
// with the matched pattern highlighted.
|
|
650
|
-
// - default editing: the caller-supplied prompt + buffer text.
|
|
651
|
-
const inExMode = this.state.mode === "ex";
|
|
652
|
-
const promptStr = inExMode
|
|
653
|
-
? ":"
|
|
654
|
-
: a.search === null
|
|
655
|
-
? a.prompt
|
|
656
|
-
: `(reverse-i-search)\`${a.search.pattern}': `;
|
|
657
|
-
const rawText = inExMode
|
|
658
|
-
? this.state.exBuffer
|
|
659
|
-
: this.state.buffer.text.replace(/\n/g, "⏎"); // newline glyph
|
|
660
|
-
// For search rendering we highlight the matched pattern (case-insensitive)
|
|
661
|
-
// inside the matched entry; otherwise the rendered text equals the raw text.
|
|
662
|
-
const renderText = !inExMode && a.search !== null && a.search.pattern.length > 0
|
|
663
|
-
? highlightMatch(rawText, a.search.pattern)
|
|
664
|
-
: rawText;
|
|
665
|
-
const promptWidth = displayWidth(promptStr);
|
|
666
|
-
// Cursor positioning uses the raw text length, NOT the rendered text
|
|
667
|
-
// length, because ANSI escapes have zero display width but non-zero
|
|
668
|
-
// string length. In ex mode the cursor always sits at the end of the
|
|
669
|
-
// ex buffer.
|
|
670
|
-
const beforeText = inExMode
|
|
671
|
-
? rawText
|
|
672
|
-
: rawText.slice(0, this.codePointsBeforeCursor().length);
|
|
673
|
-
const allText = renderText;
|
|
674
|
-
// Compute physical row/col for cursor.
|
|
675
|
-
const { row: cursorRow, col: cursorCol } = positionAfter(promptWidth, beforeText, width);
|
|
676
|
-
// Compute final row/col for the full block (so we know how many rows).
|
|
677
|
-
// Strip ANSI escape sequences from the geometry calculation since they
|
|
678
|
-
// have zero display width but non-zero string length.
|
|
679
|
-
const { row: lastRow } = positionAfter(promptWidth, stripAnsi(allText), width);
|
|
680
|
-
const rowsDrawn = lastRow + 1;
|
|
681
|
-
// Write the line. We erase each row first so leftover chars from a longer
|
|
682
|
-
// previous render are scrubbed.
|
|
683
|
-
this.stdout.write(csiEraseToEol());
|
|
684
|
-
this.stdout.write(promptStr);
|
|
685
|
-
this.stdout.write(allText);
|
|
686
|
-
// After writing, if the previous render had more rows than this one,
|
|
687
|
-
// erase the leftover rows.
|
|
688
|
-
if (a.rowsDrawn > rowsDrawn) {
|
|
689
|
-
const extra = a.rowsDrawn - rowsDrawn;
|
|
690
|
-
for (let i = 0; i < extra; i++) {
|
|
691
|
-
this.stdout.write(LF + csiEraseToEol());
|
|
692
|
-
}
|
|
693
|
-
// Move back up to where we are.
|
|
694
|
-
this.stdout.write(csiUp(extra));
|
|
695
|
-
}
|
|
696
|
-
// Reposition cursor.
|
|
697
|
-
// After writing `allText`, cursor is at (lastRow, lastCol). We want
|
|
698
|
-
// (cursorRow, cursorCol).
|
|
699
|
-
const rowDelta = cursorRow - lastRow;
|
|
700
|
-
if (rowDelta < 0)
|
|
701
|
-
this.stdout.write(csiUp(-rowDelta));
|
|
702
|
-
if (rowDelta > 0)
|
|
703
|
-
this.stdout.write(csiDown(rowDelta));
|
|
704
|
-
this.stdout.write(CR);
|
|
705
|
-
if (cursorCol > 0)
|
|
706
|
-
this.stdout.write(csiRight(cursorCol));
|
|
707
|
-
a.rowsDrawn = rowsDrawn;
|
|
708
|
-
a.cursorRow = cursorRow;
|
|
709
|
-
a.cursorCol = cursorCol;
|
|
710
|
-
// Silence unused.
|
|
711
|
-
void csiLeft;
|
|
712
|
-
}
|
|
713
|
-
moveCursorToEnd() {
|
|
714
|
-
if (this.active === null)
|
|
715
|
-
return;
|
|
716
|
-
const a = this.active;
|
|
717
|
-
// Step down to the final row of the current render.
|
|
718
|
-
const down = a.rowsDrawn - 1 - a.cursorRow;
|
|
719
|
-
if (down > 0)
|
|
720
|
-
this.stdout.write(csiDown(down));
|
|
721
|
-
this.stdout.write(CR);
|
|
722
|
-
a.cursorRow = a.rowsDrawn - 1;
|
|
723
|
-
a.cursorCol = 0;
|
|
724
|
-
}
|
|
725
|
-
codePointsBeforeCursor() {
|
|
726
|
-
return { length: this.state.buffer.cursor };
|
|
727
|
-
}
|
|
728
|
-
termWidth() {
|
|
729
|
-
const s = this.stdout;
|
|
730
|
-
if (typeof s.columns === "number" && s.columns > 0)
|
|
731
|
-
return s.columns;
|
|
732
|
-
return 80;
|
|
733
|
-
}
|
|
734
|
-
}
|
|
58
|
+
var LineEditor = class {
|
|
59
|
+
constructor(opts = {}) {
|
|
60
|
+
this.EOF = EOF_SYMBOL;
|
|
61
|
+
this.pendingMode = null;
|
|
62
|
+
this.completion = new CompletionState();
|
|
63
|
+
this.eventQueue = [];
|
|
64
|
+
this.processing = false;
|
|
65
|
+
this.active = null;
|
|
66
|
+
this.dataListener = null;
|
|
67
|
+
this.wasRaw = false;
|
|
68
|
+
this.exitListener = null;
|
|
69
|
+
this.stdin = opts.stdin ?? process.stdin;
|
|
70
|
+
this.stdout = opts.stdout ?? process.stdout;
|
|
71
|
+
this.bracketedPaste = opts.bracketedPaste ?? true;
|
|
72
|
+
this.completer = opts.completer;
|
|
73
|
+
this.mode = opts.mode ?? "emacs";
|
|
74
|
+
this.state = makeState(opts.history ?? [], this.mode === "vi" ? "insert" : "emacs");
|
|
75
|
+
this.decoder = new Vt100Decoder({
|
|
76
|
+
escTimeoutMs: opts.escTimeoutMs ?? 50,
|
|
77
|
+
onTimeoutEvent: (ev) => {
|
|
78
|
+
this.handleDecoderTimeout(ev);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** Read one line. Resolves on Enter; rejects on Ctrl-C. */
|
|
83
|
+
readLine(prompt) {
|
|
84
|
+
if (this.active !== null) return Promise.reject(/* @__PURE__ */ new Error("LineEditor.readLine called re-entrantly"));
|
|
85
|
+
if (this.pendingMode !== null) {
|
|
86
|
+
this.mode = this.pendingMode;
|
|
87
|
+
this.pendingMode = null;
|
|
88
|
+
}
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
this.state.buffer = new LineBuffer();
|
|
91
|
+
this.state.historyIndex = -1;
|
|
92
|
+
this.state.liveSnapshot = null;
|
|
93
|
+
if (this.mode === "vi") {
|
|
94
|
+
this.state.mode = "insert";
|
|
95
|
+
this.state.viPending = null;
|
|
96
|
+
} else {
|
|
97
|
+
this.state.mode = "emacs";
|
|
98
|
+
this.state.viPending = null;
|
|
99
|
+
this.state.exBuffer = "";
|
|
100
|
+
}
|
|
101
|
+
this.completion.reset();
|
|
102
|
+
this.active = {
|
|
103
|
+
prompt,
|
|
104
|
+
resolve,
|
|
105
|
+
reject,
|
|
106
|
+
rowsDrawn: 0,
|
|
107
|
+
cursorRow: 0,
|
|
108
|
+
cursorCol: 0,
|
|
109
|
+
search: null,
|
|
110
|
+
listingRowsDrawn: 0
|
|
111
|
+
};
|
|
112
|
+
try {
|
|
113
|
+
this.enterRaw();
|
|
114
|
+
} catch (err) {
|
|
115
|
+
this.active = null;
|
|
116
|
+
reject(err);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
this.render();
|
|
120
|
+
if (this.eventQueue.length > 0) this.drainQueue();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/** Force redraw (call from SIGWINCH handler). */
|
|
124
|
+
redraw() {
|
|
125
|
+
if (this.active !== null) this.render(true);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Inject an out-of-band line into the terminal while a prompt is being
|
|
129
|
+
* edited. Used by callers that produce async output (NOTIFY messages,
|
|
130
|
+
* notices, etc.) that would otherwise clobber the prompt rendering.
|
|
131
|
+
*
|
|
132
|
+
* Behaviour:
|
|
133
|
+
* - No active readLine: pass-through to stdout.
|
|
134
|
+
* - Active readLine: move cursor to end-of-block, write a fresh newline
|
|
135
|
+
* so the injected text starts on its own row, write the text, then
|
|
136
|
+
* redraw the prompt + buffer below it (re-attaching the cursor).
|
|
137
|
+
*
|
|
138
|
+
* The injected text should NOT have a trailing newline — we add one as
|
|
139
|
+
* part of the move-to-end + LF dance. If the caller's payload already
|
|
140
|
+
* ends with `\n`, we strip it once.
|
|
141
|
+
*/
|
|
142
|
+
interject(text) {
|
|
143
|
+
if (this.active === null) {
|
|
144
|
+
this.stdout.write(text);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const a = this.active;
|
|
148
|
+
this.moveCursorToEnd();
|
|
149
|
+
this.stdout.write("\n");
|
|
150
|
+
const body = text.endsWith("\n") ? text.slice(0, -1) : text;
|
|
151
|
+
this.stdout.write(body);
|
|
152
|
+
this.stdout.write("\n");
|
|
153
|
+
a.rowsDrawn = 0;
|
|
154
|
+
a.cursorRow = 0;
|
|
155
|
+
a.cursorCol = 0;
|
|
156
|
+
a.listingRowsDrawn = 0;
|
|
157
|
+
this.render(true);
|
|
158
|
+
}
|
|
159
|
+
/** Cleanup raw mode and restore TTY. Idempotent. */
|
|
160
|
+
close() {
|
|
161
|
+
this.exitRaw();
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Request a switch between vi and emacs editing modes. The change takes
|
|
165
|
+
* effect at the NEXT `readLine()` boundary, not retroactively on the line
|
|
166
|
+
* currently being edited — matching upstream readline's `set editing-mode`
|
|
167
|
+
* semantics. Calling this with the currently-active mode is a no-op.
|
|
168
|
+
*/
|
|
169
|
+
setMode(mode) {
|
|
170
|
+
this.pendingMode = mode;
|
|
171
|
+
}
|
|
172
|
+
/** Read the editor's currently effective mode. Exposed for tests. */
|
|
173
|
+
getMode() {
|
|
174
|
+
return this.mode;
|
|
175
|
+
}
|
|
176
|
+
/** Push a line into the in-memory history. */
|
|
177
|
+
pushHistory(line) {
|
|
178
|
+
if (line.length === 0) return;
|
|
179
|
+
if (this.state.history[this.state.history.length - 1] === line) return;
|
|
180
|
+
this.state.history.push(line);
|
|
181
|
+
}
|
|
182
|
+
/** Replace the in-memory history list. */
|
|
183
|
+
setHistory(lines) {
|
|
184
|
+
this.state.history = lines.slice();
|
|
185
|
+
this.state.historyIndex = -1;
|
|
186
|
+
this.state.liveSnapshot = null;
|
|
187
|
+
}
|
|
188
|
+
enterRaw() {
|
|
189
|
+
const s = this.stdin;
|
|
190
|
+
if (isTtyReadStream(s)) {
|
|
191
|
+
this.wasRaw = Boolean(s.isRaw);
|
|
192
|
+
s.setRawMode(true);
|
|
193
|
+
}
|
|
194
|
+
s.resume();
|
|
195
|
+
this.decoder.reset();
|
|
196
|
+
if (this.bracketedPaste) this.stdout.write(enableBracketedPaste());
|
|
197
|
+
this.dataListener = (chunk) => {
|
|
198
|
+
this.handleChunk(chunk);
|
|
199
|
+
};
|
|
200
|
+
s.on("data", this.dataListener);
|
|
201
|
+
if (!this.exitListener) {
|
|
202
|
+
this.exitListener = () => {
|
|
203
|
+
this.exitRaw();
|
|
204
|
+
};
|
|
205
|
+
process.once("exit", this.exitListener);
|
|
206
|
+
process.once("SIGTERM", this.exitListener);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
exitRaw() {
|
|
210
|
+
const s = this.stdin;
|
|
211
|
+
if (this.dataListener !== null) {
|
|
212
|
+
s.off("data", this.dataListener);
|
|
213
|
+
this.dataListener = null;
|
|
214
|
+
}
|
|
215
|
+
if (isTtyReadStream(s) && !this.wasRaw) try {
|
|
216
|
+
s.setRawMode(false);
|
|
217
|
+
} catch {}
|
|
218
|
+
if (this.bracketedPaste) try {
|
|
219
|
+
this.stdout.write(disableBracketedPaste());
|
|
220
|
+
} catch {}
|
|
221
|
+
if (this.exitListener !== null) {
|
|
222
|
+
process.off("exit", this.exitListener);
|
|
223
|
+
process.off("SIGTERM", this.exitListener);
|
|
224
|
+
this.exitListener = null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
handleChunk(chunk) {
|
|
228
|
+
if (this.active === null) return;
|
|
229
|
+
const events = this.decoder.push(new Uint8Array(chunk));
|
|
230
|
+
for (const ev of events) this.eventQueue.push(ev);
|
|
231
|
+
this.drainQueue();
|
|
232
|
+
}
|
|
233
|
+
/** Called when the decoder's bare-Esc timer fires with a buffered event. */
|
|
234
|
+
handleDecoderTimeout(ev) {
|
|
235
|
+
if (this.active === null) return;
|
|
236
|
+
this.eventQueue.push(ev);
|
|
237
|
+
this.drainQueue();
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Serially drain the event queue. Each event may kick off async work
|
|
241
|
+
* (notably Tab completion); we await it before processing the next
|
|
242
|
+
* event so keystrokes don't race ahead of pending completion results.
|
|
243
|
+
*/
|
|
244
|
+
async drainQueue() {
|
|
245
|
+
if (this.processing) return;
|
|
246
|
+
this.processing = true;
|
|
247
|
+
try {
|
|
248
|
+
while (this.eventQueue.length > 0 && this.active !== null) {
|
|
249
|
+
const ev = this.eventQueue.shift();
|
|
250
|
+
if (ev === void 0) break;
|
|
251
|
+
await this.handleEvent(ev);
|
|
252
|
+
}
|
|
253
|
+
} finally {
|
|
254
|
+
this.processing = false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async handleEvent(ev) {
|
|
258
|
+
if (this.active === null) return;
|
|
259
|
+
if (this.active.search !== null) {
|
|
260
|
+
await this.handleSearchKey(ev);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const action = dispatch(this.state, ev);
|
|
264
|
+
await this.applyAction(action, ev);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Reset the completion engine and forget any listing geometry: the next Tab
|
|
268
|
+
* will request a fresh result, and the next `list` / `cycled` will emit a
|
|
269
|
+
* new block on a fresh row instead of trying to overwrite a stale listing
|
|
270
|
+
* whose coordinates are no longer valid.
|
|
271
|
+
*/
|
|
272
|
+
resetCompletion() {
|
|
273
|
+
this.completion.reset();
|
|
274
|
+
if (this.active !== null) this.active.listingRowsDrawn = 0;
|
|
275
|
+
}
|
|
276
|
+
async applyAction(action, ev) {
|
|
277
|
+
if (this.active === null) return;
|
|
278
|
+
const a = this.active;
|
|
279
|
+
switch (action.kind) {
|
|
280
|
+
case "noop": return;
|
|
281
|
+
case "redraw":
|
|
282
|
+
this.resetCompletion();
|
|
283
|
+
this.render();
|
|
284
|
+
return;
|
|
285
|
+
case "bell":
|
|
286
|
+
this.stdout.write("\x07");
|
|
287
|
+
return;
|
|
288
|
+
case "submit": {
|
|
289
|
+
this.resetCompletion();
|
|
290
|
+
const text = this.state.buffer.text;
|
|
291
|
+
this.moveCursorToEnd();
|
|
292
|
+
this.stdout.write("\n");
|
|
293
|
+
const resolve = a.resolve;
|
|
294
|
+
this.active = null;
|
|
295
|
+
this.exitRaw();
|
|
296
|
+
resolve(text);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
case "cancel": {
|
|
300
|
+
this.resetCompletion();
|
|
301
|
+
this.moveCursorToEnd();
|
|
302
|
+
this.stdout.write("\n");
|
|
303
|
+
const reject = a.reject;
|
|
304
|
+
this.active = null;
|
|
305
|
+
this.exitRaw();
|
|
306
|
+
reject(new SignalError());
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
case "eof": {
|
|
310
|
+
this.resetCompletion();
|
|
311
|
+
this.moveCursorToEnd();
|
|
312
|
+
this.stdout.write("\n");
|
|
313
|
+
const resolve = a.resolve;
|
|
314
|
+
this.active = null;
|
|
315
|
+
this.exitRaw();
|
|
316
|
+
resolve(EOF_SYMBOL);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
case "complete":
|
|
320
|
+
if (!this.completer) {
|
|
321
|
+
this.stdout.write("\x07");
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
await this.runCompletion();
|
|
325
|
+
return;
|
|
326
|
+
case "clear-screen":
|
|
327
|
+
this.resetCompletion();
|
|
328
|
+
this.stdout.write(csiClearScreen());
|
|
329
|
+
a.rowsDrawn = 0;
|
|
330
|
+
a.cursorRow = 0;
|
|
331
|
+
a.cursorCol = 0;
|
|
332
|
+
this.render(true);
|
|
333
|
+
return;
|
|
334
|
+
case "search-start":
|
|
335
|
+
this.resetCompletion();
|
|
336
|
+
a.search = {
|
|
337
|
+
pattern: "",
|
|
338
|
+
matchIndex: null,
|
|
339
|
+
savedBuffer: this.state.buffer.text
|
|
340
|
+
};
|
|
341
|
+
this.render();
|
|
342
|
+
return;
|
|
343
|
+
case "paste-start":
|
|
344
|
+
case "paste-end": return;
|
|
345
|
+
case "ex-update":
|
|
346
|
+
this.resetCompletion();
|
|
347
|
+
this.render();
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
async runCompletion() {
|
|
352
|
+
if (this.completer === void 0 || this.active === null) return;
|
|
353
|
+
const step = await this.completion.apply(this.state.buffer, this.completer);
|
|
354
|
+
if (this.active === null) return;
|
|
355
|
+
switch (step.kind) {
|
|
356
|
+
case "bell":
|
|
357
|
+
this.stdout.write("\x07");
|
|
358
|
+
return;
|
|
359
|
+
case "inserted":
|
|
360
|
+
this.render();
|
|
361
|
+
return;
|
|
362
|
+
case "cycled": {
|
|
363
|
+
const cands = this.completion.getCandidates();
|
|
364
|
+
const cycleIndex = this.completion.getCycleIndex();
|
|
365
|
+
if (cands.length > 0) {
|
|
366
|
+
const w = this.termWidth();
|
|
367
|
+
const block = formatCandidates(cands, w, cycleIndex);
|
|
368
|
+
const blockRows = block.split("\n").length;
|
|
369
|
+
if (this.active.listingRowsDrawn > 0) this.rewriteListingInPlace(block, blockRows);
|
|
370
|
+
else this.emitListingBelow(block, blockRows);
|
|
371
|
+
} else this.render();
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
case "list": {
|
|
375
|
+
const w = this.termWidth();
|
|
376
|
+
const block = formatCandidates(step.candidates, w);
|
|
377
|
+
const blockRows = block.split("\n").length;
|
|
378
|
+
this.emitListingBelow(block, blockRows);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Print `block` on fresh rows below the prompt block, then redraw the
|
|
385
|
+
* prompt + buffer below it. Remembers how many rows the listing occupies
|
|
386
|
+
* in `listingRowsDrawn` so a subsequent cycle can rewrite it in place.
|
|
387
|
+
*/
|
|
388
|
+
emitListingBelow(block, blockRows) {
|
|
389
|
+
if (this.active === null) return;
|
|
390
|
+
this.moveCursorToEnd();
|
|
391
|
+
this.stdout.write("\n");
|
|
392
|
+
this.stdout.write(block + "\n");
|
|
393
|
+
this.active.rowsDrawn = 0;
|
|
394
|
+
this.active.cursorRow = 0;
|
|
395
|
+
this.active.cursorCol = 0;
|
|
396
|
+
this.active.listingRowsDrawn = blockRows;
|
|
397
|
+
this.render(true);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Rewrite the candidate listing in place. Pre-condition: a listing of
|
|
401
|
+
* `this.active.listingRowsDrawn` rows is currently drawn just above the
|
|
402
|
+
* prompt block.
|
|
403
|
+
*
|
|
404
|
+
* 1) Step up to the FIRST row of the listing (past the prompt block).
|
|
405
|
+
* 2) Erase + reprint each listing row.
|
|
406
|
+
* 3) Move back down past any trailing erase, then redraw prompt + buffer.
|
|
407
|
+
*
|
|
408
|
+
* If `block` has a different row count from the old listing the difference
|
|
409
|
+
* is absorbed by clearing extra rows (shrinking) or by accepting some
|
|
410
|
+
* scroll (growing — rare in practice because the candidate list is fixed
|
|
411
|
+
* for the duration of a cycle).
|
|
412
|
+
*/
|
|
413
|
+
rewriteListingInPlace(block, blockRows) {
|
|
414
|
+
if (this.active === null) return;
|
|
415
|
+
const a = this.active;
|
|
416
|
+
const oldRows = a.listingRowsDrawn;
|
|
417
|
+
this.stdout.write("\r");
|
|
418
|
+
if (a.cursorRow > 0) this.stdout.write(csiUp(a.cursorRow));
|
|
419
|
+
this.stdout.write(csiUp(oldRows));
|
|
420
|
+
const newLines = block.split("\n");
|
|
421
|
+
for (let i = 0; i < newLines.length; i++) {
|
|
422
|
+
this.stdout.write(csiEraseToEol());
|
|
423
|
+
this.stdout.write(newLines[i]);
|
|
424
|
+
if (i < newLines.length - 1) this.stdout.write("\n\r");
|
|
425
|
+
}
|
|
426
|
+
if (blockRows < oldRows) {
|
|
427
|
+
const extra = oldRows - blockRows;
|
|
428
|
+
for (let i = 0; i < extra; i++) this.stdout.write("\n\r" + csiEraseToEol());
|
|
429
|
+
this.stdout.write(csiUp(extra));
|
|
430
|
+
}
|
|
431
|
+
this.stdout.write("\n\r");
|
|
432
|
+
a.rowsDrawn = 0;
|
|
433
|
+
a.cursorRow = 0;
|
|
434
|
+
a.cursorCol = 0;
|
|
435
|
+
a.listingRowsDrawn = blockRows;
|
|
436
|
+
this.render(true);
|
|
437
|
+
}
|
|
438
|
+
async handleSearchKey(ev) {
|
|
439
|
+
if (this.active?.search == null) return;
|
|
440
|
+
const s = this.active.search;
|
|
441
|
+
if (ev.key === "char" && ev.ctrl && ev.char === "g" || ev.key === "escape") {
|
|
442
|
+
this.state.buffer.setText(s.savedBuffer);
|
|
443
|
+
this.active.search = null;
|
|
444
|
+
this.render();
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (ev.key === "enter") {
|
|
448
|
+
this.active.search = null;
|
|
449
|
+
await this.applyAction({ kind: "submit" }, ev);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (ev.key === "char" && ev.ctrl && ev.char === "c") {
|
|
453
|
+
await this.applyAction({ kind: "cancel" }, ev);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (ev.key === "char" && ev.ctrl && ev.char === "r") {
|
|
457
|
+
this.searchStep(-1);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (ev.key === "backspace") {
|
|
461
|
+
s.pattern = s.pattern.slice(0, -1);
|
|
462
|
+
s.matchIndex = null;
|
|
463
|
+
this.searchStep(0);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (ev.key === "char" && !ev.ctrl && !ev.meta && ev.char !== void 0) {
|
|
467
|
+
s.pattern += ev.char;
|
|
468
|
+
s.matchIndex = null;
|
|
469
|
+
this.searchStep(0);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
this.active.search = null;
|
|
473
|
+
await this.handleEvent(ev);
|
|
474
|
+
}
|
|
475
|
+
/** Walk history backward looking for the current pattern. */
|
|
476
|
+
searchStep(delta) {
|
|
477
|
+
if (this.active?.search == null) return;
|
|
478
|
+
const s = this.active.search;
|
|
479
|
+
const hist = this.state.history;
|
|
480
|
+
const startFrom = s.matchIndex === null ? hist.length - 1 : s.matchIndex + delta;
|
|
481
|
+
for (let i = startFrom; i >= 0 && i < hist.length; i--) if (s.pattern === "" || hist[i].includes(s.pattern)) {
|
|
482
|
+
s.matchIndex = i;
|
|
483
|
+
this.state.buffer.setText(hist[i]);
|
|
484
|
+
this.render();
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
this.stdout.write("\x07");
|
|
488
|
+
this.render();
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Repaint the prompt + buffer. If `full` is true, skip the cursor-up
|
|
492
|
+
* optimisation (we don't know the previous geometry).
|
|
493
|
+
*/
|
|
494
|
+
render(full = false) {
|
|
495
|
+
if (this.active === null) return;
|
|
496
|
+
const a = this.active;
|
|
497
|
+
if (!full && a.rowsDrawn > 0) {
|
|
498
|
+
const up = a.cursorRow;
|
|
499
|
+
this.stdout.write("\r" + (up > 0 ? csiUp(up) : ""));
|
|
500
|
+
} else this.stdout.write("\r");
|
|
501
|
+
const width = Math.max(1, this.termWidth());
|
|
502
|
+
const inExMode = this.state.mode === "ex";
|
|
503
|
+
const promptStr = inExMode ? ":" : a.search === null ? a.prompt : `(reverse-i-search)\`${a.search.pattern}': `;
|
|
504
|
+
const rawText = inExMode ? this.state.exBuffer : this.state.buffer.text.replace(/\n/g, "⏎");
|
|
505
|
+
const renderText = !inExMode && a.search !== null && a.search.pattern.length > 0 ? highlightMatch(rawText, a.search.pattern) : rawText;
|
|
506
|
+
const promptWidth = displayWidth(promptStr);
|
|
507
|
+
const beforeText = inExMode ? rawText : rawText.slice(0, this.codePointsBeforeCursor().length);
|
|
508
|
+
const allText = renderText;
|
|
509
|
+
const { row: cursorRow, col: cursorCol } = positionAfter(promptWidth, beforeText, width);
|
|
510
|
+
const { row: lastRow } = positionAfter(promptWidth, stripAnsi(allText), width);
|
|
511
|
+
const rowsDrawn = lastRow + 1;
|
|
512
|
+
this.stdout.write(csiEraseToEol());
|
|
513
|
+
this.stdout.write(promptStr);
|
|
514
|
+
this.stdout.write(allText);
|
|
515
|
+
if (a.rowsDrawn > rowsDrawn) {
|
|
516
|
+
const extra = a.rowsDrawn - rowsDrawn;
|
|
517
|
+
for (let i = 0; i < extra; i++) this.stdout.write("\n" + csiEraseToEol());
|
|
518
|
+
this.stdout.write(csiUp(extra));
|
|
519
|
+
}
|
|
520
|
+
const rowDelta = cursorRow - lastRow;
|
|
521
|
+
if (rowDelta < 0) this.stdout.write(csiUp(-rowDelta));
|
|
522
|
+
if (rowDelta > 0) this.stdout.write(csiDown(rowDelta));
|
|
523
|
+
this.stdout.write("\r");
|
|
524
|
+
if (cursorCol > 0) this.stdout.write(csiRight(cursorCol));
|
|
525
|
+
a.rowsDrawn = rowsDrawn;
|
|
526
|
+
a.cursorRow = cursorRow;
|
|
527
|
+
a.cursorCol = cursorCol;
|
|
528
|
+
}
|
|
529
|
+
moveCursorToEnd() {
|
|
530
|
+
if (this.active === null) return;
|
|
531
|
+
const a = this.active;
|
|
532
|
+
const down = a.rowsDrawn - 1 - a.cursorRow;
|
|
533
|
+
if (down > 0) this.stdout.write(csiDown(down));
|
|
534
|
+
this.stdout.write("\r");
|
|
535
|
+
a.cursorRow = a.rowsDrawn - 1;
|
|
536
|
+
a.cursorCol = 0;
|
|
537
|
+
}
|
|
538
|
+
codePointsBeforeCursor() {
|
|
539
|
+
return { length: this.state.buffer.cursor };
|
|
540
|
+
}
|
|
541
|
+
termWidth() {
|
|
542
|
+
const s = this.stdout;
|
|
543
|
+
if (typeof s.columns === "number" && s.columns > 0) return s.columns;
|
|
544
|
+
return 80;
|
|
545
|
+
}
|
|
546
|
+
};
|
|
735
547
|
const isTtyReadStream = (s) => typeof s.setRawMode === "function";
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
// ---------------------------------------------------------------------------
|
|
739
|
-
const SGR_REVERSE = "\x1b[7m";
|
|
740
|
-
const SGR_NO_REVERSE = "\x1b[27m";
|
|
548
|
+
const SGR_REVERSE = "\x1B[7m";
|
|
549
|
+
const SGR_NO_REVERSE = "\x1B[27m";
|
|
741
550
|
/**
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
return text;
|
|
757
|
-
return (text.slice(0, idx) +
|
|
758
|
-
SGR_REVERSE +
|
|
759
|
-
text.slice(idx, idx + pattern.length) +
|
|
760
|
-
SGR_NO_REVERSE +
|
|
761
|
-
text.slice(idx + pattern.length));
|
|
551
|
+
* Wrap the first case-insensitive occurrence of `pattern` inside `text`
|
|
552
|
+
* with the reverse-video SGR pair. Returns `text` unchanged when the
|
|
553
|
+
* pattern is empty or not found.
|
|
554
|
+
*
|
|
555
|
+
* Kept as a pure helper for unit-testing (the renderer calls it during
|
|
556
|
+
* search mode, but `renderSearchLine` is the unit-testable surface).
|
|
557
|
+
*/
|
|
558
|
+
const highlightMatch = (text, pattern) => {
|
|
559
|
+
if (pattern.length === 0) return text;
|
|
560
|
+
const lcText = text.toLowerCase();
|
|
561
|
+
const lcPat = pattern.toLowerCase();
|
|
562
|
+
const idx = lcText.indexOf(lcPat);
|
|
563
|
+
if (idx < 0) return text;
|
|
564
|
+
return text.slice(0, idx) + SGR_REVERSE + text.slice(idx, idx + pattern.length) + SGR_NO_REVERSE + text.slice(idx + pattern.length);
|
|
762
565
|
};
|
|
763
566
|
/**
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
return prefix + highlightMatch(entry, pattern);
|
|
567
|
+
* Render the search prompt + matched entry as a single string, with the
|
|
568
|
+
* matched pattern highlighted via reverse video. Exposed for unit tests.
|
|
569
|
+
* The real interactive renderer applies the same logic inline.
|
|
570
|
+
*/
|
|
571
|
+
const renderSearchLine = (pattern, entry) => {
|
|
572
|
+
return `(reverse-i-search)\`${pattern}': ` + highlightMatch(entry, pattern);
|
|
771
573
|
};
|
|
772
574
|
/** Strip ANSI CSI escape sequences from `text` for display-width math. */
|
|
773
|
-
const stripAnsi = (text) =>
|
|
774
|
-
// Targets the SGR forms we emit (e.g. \x1b[7m, \x1b[27m). Kept narrow on
|
|
775
|
-
// purpose so it doesn't accidentally eat legitimate `[` characters.
|
|
776
|
-
text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
777
|
-
// ---------------------------------------------------------------------------
|
|
778
|
-
// Display-width helpers (inlined copy of WP-09's tables; kept minimal).
|
|
779
|
-
// ---------------------------------------------------------------------------
|
|
575
|
+
const stripAnsi = (text) => text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
780
576
|
const WIDE_RANGES = [
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
577
|
+
[4352, 4447],
|
|
578
|
+
[9001, 9002],
|
|
579
|
+
[11904, 12350],
|
|
580
|
+
[12353, 13311],
|
|
581
|
+
[13312, 19903],
|
|
582
|
+
[19968, 40959],
|
|
583
|
+
[40960, 42191],
|
|
584
|
+
[44032, 55203],
|
|
585
|
+
[63744, 64255],
|
|
586
|
+
[65040, 65049],
|
|
587
|
+
[65072, 65135],
|
|
588
|
+
[65280, 65376],
|
|
589
|
+
[65504, 65510],
|
|
590
|
+
[127744, 128591],
|
|
591
|
+
[129280, 129535],
|
|
592
|
+
[131072, 196605],
|
|
593
|
+
[196608, 262141]
|
|
798
594
|
];
|
|
799
595
|
const ZERO_RANGES = [
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
596
|
+
[768, 879],
|
|
597
|
+
[1155, 1161],
|
|
598
|
+
[1425, 1469],
|
|
599
|
+
[1471, 1471],
|
|
600
|
+
[1473, 1474],
|
|
601
|
+
[1476, 1477],
|
|
602
|
+
[1479, 1479],
|
|
603
|
+
[1552, 1562],
|
|
604
|
+
[1611, 1631],
|
|
605
|
+
[1648, 1648],
|
|
606
|
+
[1750, 1756],
|
|
607
|
+
[1759, 1764],
|
|
608
|
+
[1767, 1768],
|
|
609
|
+
[1770, 1773],
|
|
610
|
+
[1809, 1809],
|
|
611
|
+
[1840, 1866],
|
|
612
|
+
[8203, 8207],
|
|
613
|
+
[8234, 8238],
|
|
614
|
+
[8288, 8303],
|
|
615
|
+
[8400, 8432],
|
|
616
|
+
[65024, 65039],
|
|
617
|
+
[65056, 65071],
|
|
618
|
+
[65279, 65279],
|
|
619
|
+
[917760, 917999]
|
|
824
620
|
];
|
|
825
621
|
const inRange = (cp, ranges) => {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
return true;
|
|
837
|
-
}
|
|
838
|
-
return false;
|
|
622
|
+
let lo = 0;
|
|
623
|
+
let hi = ranges.length - 1;
|
|
624
|
+
while (lo <= hi) {
|
|
625
|
+
const mid = lo + hi >> 1;
|
|
626
|
+
const entry = ranges[mid];
|
|
627
|
+
if (cp < entry[0]) hi = mid - 1;
|
|
628
|
+
else if (cp > entry[1]) lo = mid + 1;
|
|
629
|
+
else return true;
|
|
630
|
+
}
|
|
631
|
+
return false;
|
|
839
632
|
};
|
|
840
633
|
const codePointWidth = (cp) => {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
return 0;
|
|
847
|
-
if (inRange(cp, WIDE_RANGES))
|
|
848
|
-
return 2;
|
|
849
|
-
return 1;
|
|
634
|
+
if (cp === 0) return 0;
|
|
635
|
+
if (cp < 32 || cp >= 127 && cp < 160) return 0;
|
|
636
|
+
if (inRange(cp, ZERO_RANGES)) return 0;
|
|
637
|
+
if (inRange(cp, WIDE_RANGES)) return 2;
|
|
638
|
+
return 1;
|
|
850
639
|
};
|
|
851
640
|
const displayWidth = (text) => {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
return w;
|
|
641
|
+
let w = 0;
|
|
642
|
+
for (const ch of text) w += codePointWidth(ch.codePointAt(0) ?? 0);
|
|
643
|
+
return w;
|
|
856
644
|
};
|
|
857
645
|
/**
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
646
|
+
* Compute the final (row, col) position after writing `text` to a
|
|
647
|
+
* terminal whose cursor starts at column `startCol` and is `width`
|
|
648
|
+
* columns wide. Wrapping happens by display column count, not code points.
|
|
649
|
+
*/
|
|
862
650
|
const positionAfter = (startCol, text, width) => {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
// Stay on this row; next char triggers the wrap.
|
|
887
|
-
// (Real terminals differ here; the conservative choice that matches
|
|
888
|
-
// most xterm derivatives is to NOT advance row until the next glyph.)
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
return { row, col };
|
|
651
|
+
let row = 0;
|
|
652
|
+
let col = startCol % width;
|
|
653
|
+
if (col === 0 && startCol > 0) row += Math.floor(startCol / width);
|
|
654
|
+
else row += Math.floor(startCol / width);
|
|
655
|
+
for (const ch of text) {
|
|
656
|
+
if (ch === "\n") {
|
|
657
|
+
row += 1;
|
|
658
|
+
col = 0;
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
const w = codePointWidth(ch.codePointAt(0) ?? 0);
|
|
662
|
+
if (col + w > width) {
|
|
663
|
+
row += 1;
|
|
664
|
+
col = w;
|
|
665
|
+
} else {
|
|
666
|
+
col += w;
|
|
667
|
+
if (col === width) {}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return {
|
|
671
|
+
row,
|
|
672
|
+
col
|
|
673
|
+
};
|
|
893
674
|
};
|
|
675
|
+
//#endregion
|
|
676
|
+
export { LineEditor, SignalError, highlightMatch, renderSearchLine };
|