neonctl 2.28.0 → 2.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/analytics.js +35 -33
- package/dist/api.js +34 -34
- package/dist/auth.js +50 -44
- package/dist/cli.js +2 -2
- package/dist/commands/auth.js +58 -52
- package/dist/commands/bootstrap.js +115 -157
- package/dist/commands/branches.js +154 -147
- package/dist/commands/bucket.js +124 -118
- package/dist/commands/checkout.js +49 -49
- package/dist/commands/config.js +212 -88
- package/dist/commands/connection_string.js +62 -62
- package/dist/commands/data_api.js +96 -96
- package/dist/commands/databases.js +23 -23
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/dev.js +114 -114
- package/dist/commands/env.js +43 -43
- package/dist/commands/functions.js +97 -98
- package/dist/commands/index.js +26 -26
- package/dist/commands/init.js +23 -22
- package/dist/commands/ip_allow.js +29 -29
- package/dist/commands/link.js +223 -166
- package/dist/commands/neon_auth.js +381 -363
- package/dist/commands/operations.js +11 -11
- package/dist/commands/orgs.js +8 -8
- package/dist/commands/projects.js +101 -99
- package/dist/commands/psql.js +31 -31
- package/dist/commands/roles.js +21 -21
- package/dist/commands/schema_diff.js +23 -23
- package/dist/commands/set_context.js +17 -17
- package/dist/commands/status.js +17 -17
- package/dist/commands/user.js +5 -5
- package/dist/commands/vpc_endpoints.js +50 -50
- package/dist/config.js +7 -7
- package/dist/config_format.js +5 -5
- package/dist/context.js +23 -16
- package/dist/current_branch_fast_path.js +6 -6
- package/dist/dev/env.js +33 -33
- package/dist/dev/functions.js +4 -4
- package/dist/dev/inputs.js +6 -6
- package/dist/dev/runtime.js +25 -25
- package/dist/env.js +14 -14
- package/dist/env_file.js +13 -13
- package/dist/errors.js +19 -19
- package/dist/functions_api.js +10 -10
- package/dist/help.js +15 -15
- package/dist/index.js +94 -92
- package/dist/log.js +2 -2
- package/dist/pkg.js +5 -5
- package/dist/psql/cli.js +4 -2
- package/dist/psql/command/cmd_cond.js +61 -61
- package/dist/psql/command/cmd_connect.js +159 -154
- package/dist/psql/command/cmd_copy.js +107 -97
- package/dist/psql/command/cmd_describe.js +368 -363
- package/dist/psql/command/cmd_format.js +276 -263
- package/dist/psql/command/cmd_io.js +269 -263
- package/dist/psql/command/cmd_lo.js +74 -66
- package/dist/psql/command/cmd_meta.js +148 -148
- package/dist/psql/command/cmd_misc.js +17 -17
- package/dist/psql/command/cmd_pipeline.js +142 -135
- package/dist/psql/command/cmd_restrict.js +25 -25
- package/dist/psql/command/cmd_show.js +183 -168
- package/dist/psql/command/dispatch.js +26 -26
- package/dist/psql/command/shared.js +14 -14
- package/dist/psql/complete/filenames.js +16 -16
- package/dist/psql/complete/index.js +4 -4
- package/dist/psql/complete/matcher.js +33 -32
- package/dist/psql/complete/psqlVars.js +173 -173
- package/dist/psql/complete/queries.js +5 -3
- package/dist/psql/complete/rules.js +900 -863
- package/dist/psql/core/common.js +136 -133
- package/dist/psql/core/help.js +343 -343
- package/dist/psql/core/mainloop.js +160 -153
- package/dist/psql/core/prompt.js +126 -123
- package/dist/psql/core/settings.js +111 -111
- package/dist/psql/core/sqlHelp.js +150 -150
- package/dist/psql/core/startup.js +211 -205
- package/dist/psql/core/syncVars.js +14 -14
- package/dist/psql/core/variables.js +24 -24
- package/dist/psql/describe/formatters.js +302 -289
- package/dist/psql/describe/processNamePattern.js +28 -28
- package/dist/psql/describe/queries.js +656 -651
- package/dist/psql/index.js +436 -411
- package/dist/psql/io/history.js +36 -36
- package/dist/psql/io/input.js +15 -15
- package/dist/psql/io/lineEditor/buffer.js +27 -25
- package/dist/psql/io/lineEditor/complete.js +15 -15
- package/dist/psql/io/lineEditor/filename.js +22 -22
- package/dist/psql/io/lineEditor/index.js +65 -62
- package/dist/psql/io/lineEditor/keymap.js +325 -318
- package/dist/psql/io/lineEditor/vt100.js +60 -60
- package/dist/psql/io/pgpass.js +18 -18
- package/dist/psql/io/pgservice.js +14 -14
- package/dist/psql/io/psqlrc.js +46 -46
- package/dist/psql/print/aligned.js +175 -166
- package/dist/psql/print/asciidoc.js +51 -51
- package/dist/psql/print/crosstab.js +34 -31
- package/dist/psql/print/csv.js +25 -22
- package/dist/psql/print/html.js +54 -54
- package/dist/psql/print/json.js +12 -12
- package/dist/psql/print/latex.js +118 -118
- package/dist/psql/print/pager.js +28 -26
- package/dist/psql/print/troff.js +48 -48
- package/dist/psql/print/unaligned.js +15 -14
- package/dist/psql/print/units.js +17 -17
- package/dist/psql/scanner/slash.js +48 -46
- package/dist/psql/scanner/sql.js +88 -84
- package/dist/psql/scanner/stringutils.js +21 -17
- package/dist/psql/types/index.js +7 -7
- package/dist/psql/types/scanner.js +8 -8
- package/dist/psql/wire/connection.js +341 -327
- package/dist/psql/wire/copy.js +7 -7
- package/dist/psql/wire/pipeline.js +26 -24
- package/dist/psql/wire/protocol.js +102 -102
- package/dist/psql/wire/sasl.js +62 -62
- package/dist/psql/wire/tls.js +79 -73
- package/dist/storage_api.js +15 -15
- package/dist/test_utils/fixtures.js +34 -31
- package/dist/test_utils/oauth_server.js +5 -5
- package/dist/utils/api_enums.js +13 -13
- package/dist/utils/branch_notice.js +5 -5
- package/dist/utils/branch_picker.js +26 -26
- package/dist/utils/compute_units.js +4 -4
- package/dist/utils/enrichers.js +20 -15
- package/dist/utils/esbuild.js +28 -28
- package/dist/utils/formats.js +1 -1
- package/dist/utils/middlewares.js +3 -3
- package/dist/utils/package_manager.js +68 -0
- package/dist/utils/point_in_time.js +12 -12
- package/dist/utils/psql.js +30 -30
- package/dist/utils/string.js +2 -2
- package/dist/utils/ui.js +9 -9
- package/dist/utils/zip.js +1 -1
- package/dist/writer.js +17 -17
- package/package.json +6 -7
|
@@ -35,22 +35,22 @@ export const doubleQuote = (s) => `"${s.replace(/[\\"$`]/g, (m) => `\\${m}`)}"`;
|
|
|
35
35
|
export const detectQuoteStyle = (prefix) => {
|
|
36
36
|
// Walk left-to-right tracking the current quote state. A naive last-quote
|
|
37
37
|
// approach gets fooled by escaped quotes inside doublequotes.
|
|
38
|
-
let style =
|
|
38
|
+
let style = "none";
|
|
39
39
|
let i = 0;
|
|
40
40
|
while (i < prefix.length) {
|
|
41
41
|
const ch = prefix[i];
|
|
42
|
-
if (style ===
|
|
42
|
+
if (style === "none") {
|
|
43
43
|
if (ch === "'") {
|
|
44
|
-
style =
|
|
44
|
+
style = "single";
|
|
45
45
|
i++;
|
|
46
46
|
continue;
|
|
47
47
|
}
|
|
48
48
|
if (ch === '"') {
|
|
49
|
-
style =
|
|
49
|
+
style = "double";
|
|
50
50
|
i++;
|
|
51
51
|
continue;
|
|
52
52
|
}
|
|
53
|
-
if (ch ===
|
|
53
|
+
if (ch === "\\" && i + 1 < prefix.length) {
|
|
54
54
|
// Escaped char outside quotes: skip the escape.
|
|
55
55
|
i += 2;
|
|
56
56
|
continue;
|
|
@@ -58,19 +58,19 @@ export const detectQuoteStyle = (prefix) => {
|
|
|
58
58
|
i++;
|
|
59
59
|
continue;
|
|
60
60
|
}
|
|
61
|
-
if (style ===
|
|
61
|
+
if (style === "single") {
|
|
62
62
|
if (ch === "'")
|
|
63
|
-
style =
|
|
63
|
+
style = "none";
|
|
64
64
|
i++;
|
|
65
65
|
continue;
|
|
66
66
|
}
|
|
67
67
|
// double-quoted
|
|
68
68
|
if (ch === '"') {
|
|
69
|
-
style =
|
|
69
|
+
style = "none";
|
|
70
70
|
i++;
|
|
71
71
|
continue;
|
|
72
72
|
}
|
|
73
|
-
if (ch ===
|
|
73
|
+
if (ch === "\\" && i + 1 < prefix.length) {
|
|
74
74
|
i += 2;
|
|
75
75
|
continue;
|
|
76
76
|
}
|
|
@@ -91,12 +91,12 @@ export const detectQuoteStyle = (prefix) => {
|
|
|
91
91
|
*/
|
|
92
92
|
export const quoteForCompletion = (name, style) => {
|
|
93
93
|
switch (style) {
|
|
94
|
-
case
|
|
94
|
+
case "none":
|
|
95
95
|
return needsQuoting(name) ? singleQuote(name) : name;
|
|
96
|
-
case
|
|
96
|
+
case "single":
|
|
97
97
|
// Inside single quotes, only `'` is special.
|
|
98
98
|
return name.replace(/'/g, "'\\''");
|
|
99
|
-
case
|
|
99
|
+
case "double":
|
|
100
100
|
// Inside double quotes: \, ", $, ` need escaping.
|
|
101
101
|
return name.replace(/[\\"$`]/g, (m) => `\\${m}`);
|
|
102
102
|
}
|
|
@@ -106,23 +106,23 @@ export const quoteForCompletion = (name, style) => {
|
|
|
106
106
|
* the caller wants to feed the partial input to a filesystem lookup.
|
|
107
107
|
*/
|
|
108
108
|
export const unquote = (s) => {
|
|
109
|
-
let out =
|
|
110
|
-
let style =
|
|
109
|
+
let out = "";
|
|
110
|
+
let style = "none";
|
|
111
111
|
let i = 0;
|
|
112
112
|
while (i < s.length) {
|
|
113
113
|
const ch = s[i];
|
|
114
|
-
if (style ===
|
|
114
|
+
if (style === "none") {
|
|
115
115
|
if (ch === "'") {
|
|
116
|
-
style =
|
|
116
|
+
style = "single";
|
|
117
117
|
i++;
|
|
118
118
|
continue;
|
|
119
119
|
}
|
|
120
120
|
if (ch === '"') {
|
|
121
|
-
style =
|
|
121
|
+
style = "double";
|
|
122
122
|
i++;
|
|
123
123
|
continue;
|
|
124
124
|
}
|
|
125
|
-
if (ch ===
|
|
125
|
+
if (ch === "\\" && i + 1 < s.length) {
|
|
126
126
|
out += s[i + 1];
|
|
127
127
|
i += 2;
|
|
128
128
|
continue;
|
|
@@ -131,9 +131,9 @@ export const unquote = (s) => {
|
|
|
131
131
|
i++;
|
|
132
132
|
continue;
|
|
133
133
|
}
|
|
134
|
-
if (style ===
|
|
134
|
+
if (style === "single") {
|
|
135
135
|
if (ch === "'") {
|
|
136
|
-
style =
|
|
136
|
+
style = "none";
|
|
137
137
|
i++;
|
|
138
138
|
continue;
|
|
139
139
|
}
|
|
@@ -143,11 +143,11 @@ export const unquote = (s) => {
|
|
|
143
143
|
}
|
|
144
144
|
// double-quoted
|
|
145
145
|
if (ch === '"') {
|
|
146
|
-
style =
|
|
146
|
+
style = "none";
|
|
147
147
|
i++;
|
|
148
148
|
continue;
|
|
149
149
|
}
|
|
150
|
-
if (ch ===
|
|
150
|
+
if (ch === "\\" && i + 1 < s.length) {
|
|
151
151
|
out += s[i + 1];
|
|
152
152
|
i += 2;
|
|
153
153
|
continue;
|
|
@@ -37,23 +37,23 @@
|
|
|
37
37
|
* breaks come from explicit `\r\n` only on submit or when we need to
|
|
38
38
|
* display multi-row output (paste-mode newlines, candidate listings).
|
|
39
39
|
*/
|
|
40
|
-
import { LineBuffer } from
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import { BEL, CR,
|
|
40
|
+
import { LineBuffer } from "./buffer.js";
|
|
41
|
+
import { CompletionState, formatCandidates, } from "./complete.js";
|
|
42
|
+
import { dispatch, makeState, } from "./keymap.js";
|
|
43
|
+
import { BEL, CR, csiClearScreen, csiDown, csiEraseToEol, csiLeft, csiRight, csiUp, disableBracketedPaste, enableBracketedPaste, LF, Vt100Decoder, } from "./vt100.js";
|
|
44
44
|
/** Thrown when ^C cancels the current line. */
|
|
45
45
|
export class SignalError extends Error {
|
|
46
46
|
constructor() {
|
|
47
|
-
super(
|
|
48
|
-
this.name =
|
|
49
|
-
this.signal =
|
|
47
|
+
super("SIGINT");
|
|
48
|
+
this.name = "SignalError";
|
|
49
|
+
this.signal = "SIGINT";
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
53
|
* Sentinel for "Ctrl-D on an empty line". Compared with `===` by callers
|
|
54
54
|
* so we don't accidentally match a literal string.
|
|
55
55
|
*/
|
|
56
|
-
const EOF_SYMBOL = Symbol(
|
|
56
|
+
const EOF_SYMBOL = Symbol("LineEditor.EOF");
|
|
57
57
|
export class LineEditor {
|
|
58
58
|
constructor(opts = {}) {
|
|
59
59
|
this.EOF = EOF_SYMBOL;
|
|
@@ -79,8 +79,8 @@ export class LineEditor {
|
|
|
79
79
|
this.stdout = opts.stdout ?? process.stdout;
|
|
80
80
|
this.bracketedPaste = opts.bracketedPaste ?? true;
|
|
81
81
|
this.completer = opts.completer;
|
|
82
|
-
this.mode = opts.mode ??
|
|
83
|
-
this.state = makeState(opts.history ?? [], this.mode ===
|
|
82
|
+
this.mode = opts.mode ?? "emacs";
|
|
83
|
+
this.state = makeState(opts.history ?? [], this.mode === "vi" ? "insert" : "emacs");
|
|
84
84
|
this.decoder = new Vt100Decoder({
|
|
85
85
|
// LineEditor default: 50ms matches GNU readline's `keyseq-timeout`
|
|
86
86
|
// default — enough to disambiguate Alt-X across all modern terminals
|
|
@@ -95,7 +95,7 @@ export class LineEditor {
|
|
|
95
95
|
/** Read one line. Resolves on Enter; rejects on Ctrl-C. */
|
|
96
96
|
readLine(prompt) {
|
|
97
97
|
if (this.active !== null) {
|
|
98
|
-
return Promise.reject(new Error(
|
|
98
|
+
return Promise.reject(new Error("LineEditor.readLine called re-entrantly"));
|
|
99
99
|
}
|
|
100
100
|
// Apply any pending setMode() request at this readLine boundary so an
|
|
101
101
|
// in-flight line keeps its prior dispatch.
|
|
@@ -112,14 +112,14 @@ export class LineEditor {
|
|
|
112
112
|
// emacs → 'emacs'. This must happen on every readLine so a mid-session
|
|
113
113
|
// VI_MODE flip is observed (the state machine has its own per-line
|
|
114
114
|
// mode field that needs to be reset).
|
|
115
|
-
if (this.mode ===
|
|
116
|
-
this.state.mode =
|
|
115
|
+
if (this.mode === "vi") {
|
|
116
|
+
this.state.mode = "insert";
|
|
117
117
|
this.state.viPending = null;
|
|
118
118
|
}
|
|
119
119
|
else {
|
|
120
|
-
this.state.mode =
|
|
120
|
+
this.state.mode = "emacs";
|
|
121
121
|
this.state.viPending = null;
|
|
122
|
-
this.state.exBuffer =
|
|
122
|
+
this.state.exBuffer = "";
|
|
123
123
|
}
|
|
124
124
|
this.completion.reset();
|
|
125
125
|
this.active = {
|
|
@@ -179,7 +179,7 @@ export class LineEditor {
|
|
|
179
179
|
const a = this.active;
|
|
180
180
|
this.moveCursorToEnd();
|
|
181
181
|
this.stdout.write(LF);
|
|
182
|
-
const body = text.endsWith(
|
|
182
|
+
const body = text.endsWith("\n") ? text.slice(0, -1) : text;
|
|
183
183
|
this.stdout.write(body);
|
|
184
184
|
this.stdout.write(LF);
|
|
185
185
|
// Reset drawn-state so render() lays out a fresh block under the
|
|
@@ -239,19 +239,19 @@ export class LineEditor {
|
|
|
239
239
|
this.dataListener = (chunk) => {
|
|
240
240
|
this.handleChunk(chunk);
|
|
241
241
|
};
|
|
242
|
-
s.on(
|
|
242
|
+
s.on("data", this.dataListener);
|
|
243
243
|
if (!this.exitListener) {
|
|
244
244
|
this.exitListener = () => {
|
|
245
245
|
this.exitRaw();
|
|
246
246
|
};
|
|
247
|
-
process.once(
|
|
248
|
-
process.once(
|
|
247
|
+
process.once("exit", this.exitListener);
|
|
248
|
+
process.once("SIGTERM", this.exitListener);
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
exitRaw() {
|
|
252
252
|
const s = this.stdin;
|
|
253
253
|
if (this.dataListener !== null) {
|
|
254
|
-
s.off(
|
|
254
|
+
s.off("data", this.dataListener);
|
|
255
255
|
this.dataListener = null;
|
|
256
256
|
}
|
|
257
257
|
if (isTtyReadStream(s) && !this.wasRaw) {
|
|
@@ -271,8 +271,8 @@ export class LineEditor {
|
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
273
|
if (this.exitListener !== null) {
|
|
274
|
-
process.off(
|
|
275
|
-
process.off(
|
|
274
|
+
process.off("exit", this.exitListener);
|
|
275
|
+
process.off("SIGTERM", this.exitListener);
|
|
276
276
|
this.exitListener = null;
|
|
277
277
|
}
|
|
278
278
|
}
|
|
@@ -344,16 +344,16 @@ export class LineEditor {
|
|
|
344
344
|
return;
|
|
345
345
|
const a = this.active;
|
|
346
346
|
switch (action.kind) {
|
|
347
|
-
case
|
|
347
|
+
case "noop":
|
|
348
348
|
return;
|
|
349
|
-
case
|
|
349
|
+
case "redraw":
|
|
350
350
|
this.resetCompletion();
|
|
351
351
|
this.render();
|
|
352
352
|
return;
|
|
353
|
-
case
|
|
353
|
+
case "bell":
|
|
354
354
|
this.stdout.write(BEL);
|
|
355
355
|
return;
|
|
356
|
-
case
|
|
356
|
+
case "submit": {
|
|
357
357
|
this.resetCompletion();
|
|
358
358
|
const text = this.state.buffer.text;
|
|
359
359
|
// Move cursor past the end of the rendered block and emit a newline.
|
|
@@ -365,7 +365,7 @@ export class LineEditor {
|
|
|
365
365
|
resolve(text);
|
|
366
366
|
return;
|
|
367
367
|
}
|
|
368
|
-
case
|
|
368
|
+
case "cancel": {
|
|
369
369
|
// Upstream psql doesn't echo `^C` to the screen on Ctrl-C — it just
|
|
370
370
|
// breaks to the next prompt line silently. Match that behaviour.
|
|
371
371
|
this.resetCompletion();
|
|
@@ -377,7 +377,7 @@ export class LineEditor {
|
|
|
377
377
|
reject(new SignalError());
|
|
378
378
|
return;
|
|
379
379
|
}
|
|
380
|
-
case
|
|
380
|
+
case "eof": {
|
|
381
381
|
this.resetCompletion();
|
|
382
382
|
this.moveCursorToEnd();
|
|
383
383
|
this.stdout.write(LF);
|
|
@@ -387,7 +387,7 @@ export class LineEditor {
|
|
|
387
387
|
resolve(EOF_SYMBOL);
|
|
388
388
|
return;
|
|
389
389
|
}
|
|
390
|
-
case
|
|
390
|
+
case "complete": {
|
|
391
391
|
if (!this.completer) {
|
|
392
392
|
this.stdout.write(BEL);
|
|
393
393
|
return;
|
|
@@ -395,7 +395,7 @@ export class LineEditor {
|
|
|
395
395
|
await this.runCompletion();
|
|
396
396
|
return;
|
|
397
397
|
}
|
|
398
|
-
case
|
|
398
|
+
case "clear-screen":
|
|
399
399
|
this.resetCompletion();
|
|
400
400
|
this.stdout.write(csiClearScreen());
|
|
401
401
|
a.rowsDrawn = 0;
|
|
@@ -403,21 +403,21 @@ export class LineEditor {
|
|
|
403
403
|
a.cursorCol = 0;
|
|
404
404
|
this.render(true);
|
|
405
405
|
return;
|
|
406
|
-
case
|
|
406
|
+
case "search-start":
|
|
407
407
|
this.resetCompletion();
|
|
408
408
|
a.search = {
|
|
409
|
-
pattern:
|
|
409
|
+
pattern: "",
|
|
410
410
|
matchIndex: null,
|
|
411
411
|
savedBuffer: this.state.buffer.text,
|
|
412
412
|
};
|
|
413
413
|
this.render();
|
|
414
414
|
return;
|
|
415
|
-
case
|
|
416
|
-
case
|
|
415
|
+
case "paste-start":
|
|
416
|
+
case "paste-end":
|
|
417
417
|
// Bracketed paste markers are otherwise transparent.
|
|
418
418
|
void ev;
|
|
419
419
|
return;
|
|
420
|
-
case
|
|
420
|
+
case "ex-update":
|
|
421
421
|
// Vi `:`-ex prompt text changed (entered/typed/backspaced). The
|
|
422
422
|
// renderer reads `state.mode === 'ex'` and swaps in a `: <buf>` line.
|
|
423
423
|
this.resetCompletion();
|
|
@@ -435,13 +435,13 @@ export class LineEditor {
|
|
|
435
435
|
if (this.active === null)
|
|
436
436
|
return;
|
|
437
437
|
switch (step.kind) {
|
|
438
|
-
case
|
|
438
|
+
case "bell":
|
|
439
439
|
this.stdout.write(BEL);
|
|
440
440
|
return;
|
|
441
|
-
case
|
|
441
|
+
case "inserted":
|
|
442
442
|
this.render();
|
|
443
443
|
return;
|
|
444
|
-
case
|
|
444
|
+
case "cycled": {
|
|
445
445
|
// In-place rewrite of the candidate listing with the new highlight.
|
|
446
446
|
// If we already have a listing on screen (placed above the current
|
|
447
447
|
// prompt block by an earlier `list` or `cycled`), navigate up to its
|
|
@@ -453,7 +453,7 @@ export class LineEditor {
|
|
|
453
453
|
if (cands.length > 0) {
|
|
454
454
|
const w = this.termWidth();
|
|
455
455
|
const block = formatCandidates(cands, w, cycleIndex);
|
|
456
|
-
const blockRows = block.split(
|
|
456
|
+
const blockRows = block.split("\n").length;
|
|
457
457
|
if (this.active.listingRowsDrawn > 0) {
|
|
458
458
|
this.rewriteListingInPlace(block, blockRows);
|
|
459
459
|
}
|
|
@@ -466,11 +466,11 @@ export class LineEditor {
|
|
|
466
466
|
}
|
|
467
467
|
return;
|
|
468
468
|
}
|
|
469
|
-
case
|
|
469
|
+
case "list": {
|
|
470
470
|
// Print listing on a new row, then redraw the prompt+line below it.
|
|
471
471
|
const w = this.termWidth();
|
|
472
472
|
const block = formatCandidates(step.candidates, w);
|
|
473
|
-
const blockRows = block.split(
|
|
473
|
+
const blockRows = block.split("\n").length;
|
|
474
474
|
this.emitListingBelow(block, blockRows);
|
|
475
475
|
return;
|
|
476
476
|
}
|
|
@@ -523,7 +523,7 @@ export class LineEditor {
|
|
|
523
523
|
// 3. Erase each listing row and write the new block. We treat the listing
|
|
524
524
|
// as `blockRows` lines separated by LF; on the last line we DON'T emit
|
|
525
525
|
// a trailing LF (otherwise we'd push the prompt down by one row).
|
|
526
|
-
const newLines = block.split(
|
|
526
|
+
const newLines = block.split("\n");
|
|
527
527
|
for (let i = 0; i < newLines.length; i++) {
|
|
528
528
|
this.stdout.write(csiEraseToEol());
|
|
529
529
|
this.stdout.write(newLines[i]);
|
|
@@ -557,38 +557,41 @@ export class LineEditor {
|
|
|
557
557
|
return;
|
|
558
558
|
const s = this.active.search;
|
|
559
559
|
// ^G or Escape cancels and restores the saved line.
|
|
560
|
-
if ((ev.key ===
|
|
561
|
-
ev.key ===
|
|
560
|
+
if ((ev.key === "char" && ev.ctrl && ev.char === "g") ||
|
|
561
|
+
ev.key === "escape") {
|
|
562
562
|
this.state.buffer.setText(s.savedBuffer);
|
|
563
563
|
this.active.search = null;
|
|
564
564
|
this.render();
|
|
565
565
|
return;
|
|
566
566
|
}
|
|
567
567
|
// Enter accepts the current match (whatever's in the buffer).
|
|
568
|
-
if (ev.key ===
|
|
568
|
+
if (ev.key === "enter") {
|
|
569
569
|
this.active.search = null;
|
|
570
|
-
await this.applyAction({ kind:
|
|
570
|
+
await this.applyAction({ kind: "submit" }, ev);
|
|
571
571
|
return;
|
|
572
572
|
}
|
|
573
573
|
// ^C bubbles to cancel.
|
|
574
|
-
if (ev.key ===
|
|
575
|
-
await this.applyAction({ kind:
|
|
574
|
+
if (ev.key === "char" && ev.ctrl && ev.char === "c") {
|
|
575
|
+
await this.applyAction({ kind: "cancel" }, ev);
|
|
576
576
|
return;
|
|
577
577
|
}
|
|
578
578
|
// ^R again: search further back.
|
|
579
|
-
if (ev.key ===
|
|
579
|
+
if (ev.key === "char" && ev.ctrl && ev.char === "r") {
|
|
580
580
|
this.searchStep(-1);
|
|
581
581
|
return;
|
|
582
582
|
}
|
|
583
583
|
// Backspace: shrink the pattern.
|
|
584
|
-
if (ev.key ===
|
|
584
|
+
if (ev.key === "backspace") {
|
|
585
585
|
s.pattern = s.pattern.slice(0, -1);
|
|
586
586
|
s.matchIndex = null;
|
|
587
587
|
this.searchStep(0);
|
|
588
588
|
return;
|
|
589
589
|
}
|
|
590
590
|
// Printable char: extend the pattern.
|
|
591
|
-
if (ev.key ===
|
|
591
|
+
if (ev.key === "char" &&
|
|
592
|
+
!ev.ctrl &&
|
|
593
|
+
!ev.meta &&
|
|
594
|
+
ev.char !== undefined) {
|
|
592
595
|
s.pattern += ev.char;
|
|
593
596
|
s.matchIndex = null;
|
|
594
597
|
this.searchStep(0);
|
|
@@ -606,7 +609,7 @@ export class LineEditor {
|
|
|
606
609
|
const hist = this.state.history;
|
|
607
610
|
const startFrom = s.matchIndex === null ? hist.length - 1 : s.matchIndex + delta;
|
|
608
611
|
for (let i = startFrom; i >= 0 && i < hist.length; i--) {
|
|
609
|
-
if (s.pattern ===
|
|
612
|
+
if (s.pattern === "" || hist[i].includes(s.pattern)) {
|
|
610
613
|
s.matchIndex = i;
|
|
611
614
|
this.state.buffer.setText(hist[i]);
|
|
612
615
|
this.render();
|
|
@@ -632,7 +635,7 @@ export class LineEditor {
|
|
|
632
635
|
if (!full && a.rowsDrawn > 0) {
|
|
633
636
|
// Move up to the anchor row.
|
|
634
637
|
const up = a.cursorRow;
|
|
635
|
-
this.stdout.write(CR + (up > 0 ? csiUp(up) :
|
|
638
|
+
this.stdout.write(CR + (up > 0 ? csiUp(up) : ""));
|
|
636
639
|
}
|
|
637
640
|
else {
|
|
638
641
|
this.stdout.write(CR);
|
|
@@ -645,15 +648,15 @@ export class LineEditor {
|
|
|
645
648
|
// - reverse-i-search: the `(reverse-i-search)\`pat':` preamble,
|
|
646
649
|
// with the matched pattern highlighted.
|
|
647
650
|
// - default editing: the caller-supplied prompt + buffer text.
|
|
648
|
-
const inExMode = this.state.mode ===
|
|
651
|
+
const inExMode = this.state.mode === "ex";
|
|
649
652
|
const promptStr = inExMode
|
|
650
|
-
?
|
|
653
|
+
? ":"
|
|
651
654
|
: a.search === null
|
|
652
655
|
? a.prompt
|
|
653
656
|
: `(reverse-i-search)\`${a.search.pattern}': `;
|
|
654
657
|
const rawText = inExMode
|
|
655
658
|
? this.state.exBuffer
|
|
656
|
-
: this.state.buffer.text.replace(/\n/g,
|
|
659
|
+
: this.state.buffer.text.replace(/\n/g, "⏎"); // newline glyph
|
|
657
660
|
// For search rendering we highlight the matched pattern (case-insensitive)
|
|
658
661
|
// inside the matched entry; otherwise the rendered text equals the raw text.
|
|
659
662
|
const renderText = !inExMode && a.search !== null && a.search.pattern.length > 0
|
|
@@ -724,17 +727,17 @@ export class LineEditor {
|
|
|
724
727
|
}
|
|
725
728
|
termWidth() {
|
|
726
729
|
const s = this.stdout;
|
|
727
|
-
if (typeof s.columns ===
|
|
730
|
+
if (typeof s.columns === "number" && s.columns > 0)
|
|
728
731
|
return s.columns;
|
|
729
732
|
return 80;
|
|
730
733
|
}
|
|
731
734
|
}
|
|
732
|
-
const isTtyReadStream = (s) => typeof s.setRawMode ===
|
|
735
|
+
const isTtyReadStream = (s) => typeof s.setRawMode === "function";
|
|
733
736
|
// ---------------------------------------------------------------------------
|
|
734
737
|
// Search-line rendering / highlighting
|
|
735
738
|
// ---------------------------------------------------------------------------
|
|
736
|
-
const SGR_REVERSE =
|
|
737
|
-
const SGR_NO_REVERSE =
|
|
739
|
+
const SGR_REVERSE = "\x1b[7m";
|
|
740
|
+
const SGR_NO_REVERSE = "\x1b[27m";
|
|
738
741
|
/**
|
|
739
742
|
* Wrap the first case-insensitive occurrence of `pattern` inside `text`
|
|
740
743
|
* with the reverse-video SGR pair. Returns `text` unchanged when the
|
|
@@ -771,7 +774,7 @@ const stripAnsi = (text) =>
|
|
|
771
774
|
// Targets the SGR forms we emit (e.g. \x1b[7m, \x1b[27m). Kept narrow on
|
|
772
775
|
// purpose so it doesn't accidentally eat legitimate `[` characters.
|
|
773
776
|
// eslint-disable-next-line no-control-regex
|
|
774
|
-
text.replace(/\x1b\[[0-9;]*m/g,
|
|
777
|
+
text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
775
778
|
// ---------------------------------------------------------------------------
|
|
776
779
|
// Display-width helpers (inlined copy of WP-09's tables; kept minimal).
|
|
777
780
|
// ---------------------------------------------------------------------------
|
|
@@ -868,7 +871,7 @@ const positionAfter = (startCol, text, width) => {
|
|
|
868
871
|
row += Math.floor(startCol / width);
|
|
869
872
|
}
|
|
870
873
|
for (const ch of text) {
|
|
871
|
-
if (ch ===
|
|
874
|
+
if (ch === "\n") {
|
|
872
875
|
row += 1;
|
|
873
876
|
col = 0;
|
|
874
877
|
continue;
|