neonctl 2.28.0 → 2.29.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 +71 -71
- 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 +34 -34
- 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
package/dist/psql/io/history.js
CHANGED
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
* bash/readline behaviour where the user's literal input is what gets
|
|
34
34
|
* deduped, not the escaped on-disk form.
|
|
35
35
|
*/
|
|
36
|
-
import {
|
|
37
|
-
import
|
|
38
|
-
import * as
|
|
39
|
-
import
|
|
36
|
+
import { randomUUID } from "node:crypto";
|
|
37
|
+
import { promises as fs } from "node:fs";
|
|
38
|
+
import * as os from "node:os";
|
|
39
|
+
import * as path from "node:path";
|
|
40
40
|
/** psql's compiled-in default for HISTSIZE (see `src/bin/psql/settings.h`). */
|
|
41
41
|
const DEFAULT_HISTSIZE = 500;
|
|
42
42
|
/**
|
|
@@ -78,7 +78,7 @@ export const clearHistory = () => {
|
|
|
78
78
|
inMemoryHistory = [];
|
|
79
79
|
};
|
|
80
80
|
/** Encode a single in-memory entry to the on-disk libreadline form. */
|
|
81
|
-
const encodeEntry = (entry) => entry.replace(/\\/g,
|
|
81
|
+
const encodeEntry = (entry) => entry.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
82
82
|
/**
|
|
83
83
|
* Decode a single on-disk libreadline line back to its in-memory form.
|
|
84
84
|
*
|
|
@@ -87,23 +87,23 @@ const encodeEntry = (entry) => entry.replace(/\\/g, '\\\\').replace(/\n/g, '\\n'
|
|
|
87
87
|
* a stray backslash at the end of the file does not eat the next entry.
|
|
88
88
|
*/
|
|
89
89
|
const decodeEntry = (line) => {
|
|
90
|
-
let out =
|
|
90
|
+
let out = "";
|
|
91
91
|
for (let i = 0; i < line.length; i++) {
|
|
92
92
|
const c = line.charCodeAt(i);
|
|
93
93
|
if (c === 0x5c /* '\\' */ && i + 1 < line.length) {
|
|
94
94
|
const next = line[i + 1];
|
|
95
|
-
if (next ===
|
|
96
|
-
out +=
|
|
95
|
+
if (next === "n") {
|
|
96
|
+
out += "\n";
|
|
97
97
|
i++;
|
|
98
98
|
continue;
|
|
99
99
|
}
|
|
100
|
-
if (next ===
|
|
101
|
-
out +=
|
|
100
|
+
if (next === "r") {
|
|
101
|
+
out += "\r";
|
|
102
102
|
i++;
|
|
103
103
|
continue;
|
|
104
104
|
}
|
|
105
|
-
if (next ===
|
|
106
|
-
out +=
|
|
105
|
+
if (next === "\\") {
|
|
106
|
+
out += "\\";
|
|
107
107
|
i++;
|
|
108
108
|
continue;
|
|
109
109
|
}
|
|
@@ -119,8 +119,8 @@ const decodeEntry = (line) => {
|
|
|
119
119
|
* Mirrors `pg_send_history()`'s filter in `input.c`.
|
|
120
120
|
*/
|
|
121
121
|
const shouldIgnore = (entry, prev, histcontrol) => {
|
|
122
|
-
const ignoreSpace = histcontrol ===
|
|
123
|
-
const ignoreDups = histcontrol ===
|
|
122
|
+
const ignoreSpace = histcontrol === "ignorespace" || histcontrol === "ignoreboth";
|
|
123
|
+
const ignoreDups = histcontrol === "ignoredups" || histcontrol === "ignoreboth";
|
|
124
124
|
if (ignoreSpace && entry.length > 0 && /^\s/.test(entry))
|
|
125
125
|
return true;
|
|
126
126
|
if (ignoreDups && prev !== undefined && prev === entry)
|
|
@@ -140,13 +140,13 @@ const shouldIgnore = (entry, prev, histcontrol) => {
|
|
|
140
140
|
export const loadHistory = async (filePath) => {
|
|
141
141
|
let raw;
|
|
142
142
|
try {
|
|
143
|
-
raw = await fs.readFile(filePath,
|
|
143
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
144
144
|
}
|
|
145
145
|
catch (err) {
|
|
146
146
|
if (err &&
|
|
147
|
-
typeof err ===
|
|
148
|
-
|
|
149
|
-
err.code ===
|
|
147
|
+
typeof err === "object" &&
|
|
148
|
+
"code" in err &&
|
|
149
|
+
err.code === "ENOENT") {
|
|
150
150
|
return [];
|
|
151
151
|
}
|
|
152
152
|
throw err;
|
|
@@ -155,13 +155,13 @@ export const loadHistory = async (filePath) => {
|
|
|
155
155
|
// doesn't produce a phantom empty entry. Don't strip more than one — a
|
|
156
156
|
// blank line in the middle of the file represents an entry that was
|
|
157
157
|
// literally the empty string (rare but possible), and we round-trip it.
|
|
158
|
-
if (raw.endsWith(
|
|
158
|
+
if (raw.endsWith("\n"))
|
|
159
159
|
raw = raw.slice(0, -1);
|
|
160
160
|
if (raw.length === 0)
|
|
161
161
|
return [];
|
|
162
162
|
const entries = [];
|
|
163
|
-
for (const line of raw.split(
|
|
164
|
-
if (line.startsWith(
|
|
163
|
+
for (const line of raw.split("\n")) {
|
|
164
|
+
if (line.startsWith("#"))
|
|
165
165
|
continue;
|
|
166
166
|
entries.push(decodeEntry(line));
|
|
167
167
|
}
|
|
@@ -183,12 +183,12 @@ export const loadHistory = async (filePath) => {
|
|
|
183
183
|
* when ignoredups is in effect — typical history files are well
|
|
184
184
|
* under 1 MiB.
|
|
185
185
|
*/
|
|
186
|
-
export const appendHistory = async (filePath, entry, histcontrol =
|
|
187
|
-
if (histcontrol ===
|
|
188
|
-
histcontrol ===
|
|
189
|
-
histcontrol ===
|
|
186
|
+
export const appendHistory = async (filePath, entry, histcontrol = "none") => {
|
|
187
|
+
if (histcontrol === "ignoredups" ||
|
|
188
|
+
histcontrol === "ignoreboth" ||
|
|
189
|
+
histcontrol === "ignorespace") {
|
|
190
190
|
let prev;
|
|
191
|
-
if (histcontrol ===
|
|
191
|
+
if (histcontrol === "ignoredups" || histcontrol === "ignoreboth") {
|
|
192
192
|
const existing = await loadHistory(filePath);
|
|
193
193
|
prev = existing[existing.length - 1];
|
|
194
194
|
}
|
|
@@ -200,7 +200,7 @@ export const appendHistory = async (filePath, entry, histcontrol = 'none') => {
|
|
|
200
200
|
// lock-step under HISTCONTROL. Recording happens AFTER the ignore check
|
|
201
201
|
// so an ignored line is absent from both.
|
|
202
202
|
recordHistory(entry);
|
|
203
|
-
await fs.appendFile(filePath, encodeEntry(entry) +
|
|
203
|
+
await fs.appendFile(filePath, encodeEntry(entry) + "\n", "utf8");
|
|
204
204
|
};
|
|
205
205
|
/**
|
|
206
206
|
* Trim the history file to its last `maxLines` entries.
|
|
@@ -221,9 +221,9 @@ export const truncateHistory = async (filePath, maxLines) => {
|
|
|
221
221
|
}
|
|
222
222
|
catch (err) {
|
|
223
223
|
if (err &&
|
|
224
|
-
typeof err ===
|
|
225
|
-
|
|
226
|
-
err.code ===
|
|
224
|
+
typeof err === "object" &&
|
|
225
|
+
"code" in err &&
|
|
226
|
+
err.code === "ENOENT") {
|
|
227
227
|
return;
|
|
228
228
|
}
|
|
229
229
|
throw err;
|
|
@@ -234,11 +234,11 @@ export const truncateHistory = async (filePath, maxLines) => {
|
|
|
234
234
|
if (entries.length <= maxLines)
|
|
235
235
|
return;
|
|
236
236
|
const kept = entries.slice(entries.length - maxLines);
|
|
237
|
-
const body = kept.map(encodeEntry).join(
|
|
237
|
+
const body = kept.map(encodeEntry).join("\n") + "\n";
|
|
238
238
|
const dir = path.dirname(filePath);
|
|
239
239
|
const base = path.basename(filePath);
|
|
240
240
|
const tmpPath = path.join(dir, `.${base}.${randomUUID()}.tmp`);
|
|
241
|
-
await fs.writeFile(tmpPath, body, { encoding:
|
|
241
|
+
await fs.writeFile(tmpPath, body, { encoding: "utf8", mode: 0o600 });
|
|
242
242
|
try {
|
|
243
243
|
await fs.rename(tmpPath, filePath);
|
|
244
244
|
}
|
|
@@ -269,16 +269,16 @@ export const defaultHistoryPath = (env = process.env) => {
|
|
|
269
269
|
const explicit = env.PSQL_HISTORY;
|
|
270
270
|
if (explicit !== undefined && explicit.length > 0)
|
|
271
271
|
return explicit;
|
|
272
|
-
if (process.platform ===
|
|
272
|
+
if (process.platform === "win32") {
|
|
273
273
|
const appdata = env.APPDATA;
|
|
274
274
|
if (appdata !== undefined && appdata.length > 0) {
|
|
275
|
-
return path.join(appdata,
|
|
275
|
+
return path.join(appdata, "postgresql", "psql_history");
|
|
276
276
|
}
|
|
277
277
|
// Fall through to homedir() if APPDATA isn't set; matches psql's
|
|
278
278
|
// graceful degradation on a minimally-configured Windows session.
|
|
279
279
|
}
|
|
280
280
|
const home = env.HOME ?? os.homedir();
|
|
281
|
-
return path.join(home,
|
|
281
|
+
return path.join(home, ".psql_history");
|
|
282
282
|
};
|
|
283
283
|
/**
|
|
284
284
|
* Resolve the effective HISTSIZE: the `HISTSIZE` env var (if a
|
|
@@ -290,7 +290,7 @@ export const defaultHistoryPath = (env = process.env) => {
|
|
|
290
290
|
*/
|
|
291
291
|
export const resolveHistSize = (env = process.env) => {
|
|
292
292
|
const raw = env.HISTSIZE;
|
|
293
|
-
if (raw === undefined || raw ===
|
|
293
|
+
if (raw === undefined || raw === "")
|
|
294
294
|
return DEFAULT_HISTSIZE;
|
|
295
295
|
const n = Number(raw);
|
|
296
296
|
if (!Number.isInteger(n) || n < 0)
|
package/dist/psql/io/input.js
CHANGED
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
* unit tests; production callers use {@link readLine} with the process
|
|
18
18
|
* defaults.
|
|
19
19
|
*/
|
|
20
|
-
import { createInterface } from
|
|
20
|
+
import { createInterface } from "node:readline";
|
|
21
21
|
/** True when `stream` is a TTY whose echo we can suppress via raw mode. */
|
|
22
22
|
const isRawCapableTty = (stream) => {
|
|
23
23
|
const s = stream;
|
|
24
|
-
return Boolean(s.isTTY) && typeof s.setRawMode ===
|
|
24
|
+
return Boolean(s.isTTY) && typeof s.setRawMode === "function";
|
|
25
25
|
};
|
|
26
26
|
/**
|
|
27
27
|
* Read one line of input, optionally suppressing echo.
|
|
@@ -61,11 +61,11 @@ const readEchoLine = (prompt, input, output) => {
|
|
|
61
61
|
// Resolve on the first complete line. Closing without one (EOF) yields ''.
|
|
62
62
|
// We don't rely on `line` firing before `close`: whichever lands first
|
|
63
63
|
// wins, and a buffered final line is delivered as a `line` event.
|
|
64
|
-
rl.on(
|
|
64
|
+
rl.on("line", (l) => {
|
|
65
65
|
settle(l);
|
|
66
66
|
});
|
|
67
|
-
rl.on(
|
|
68
|
-
settle(
|
|
67
|
+
rl.on("close", () => {
|
|
68
|
+
settle("");
|
|
69
69
|
});
|
|
70
70
|
});
|
|
71
71
|
};
|
|
@@ -81,33 +81,33 @@ const readNoEchoTty = (prompt, input, output) => {
|
|
|
81
81
|
output.write(prompt);
|
|
82
82
|
input.setRawMode(true);
|
|
83
83
|
input.resume();
|
|
84
|
-
input.setEncoding(
|
|
85
|
-
let buf =
|
|
84
|
+
input.setEncoding("utf8");
|
|
85
|
+
let buf = "";
|
|
86
86
|
const finish = (result) => {
|
|
87
87
|
input.setRawMode(false);
|
|
88
88
|
input.pause();
|
|
89
|
-
input.removeListener(
|
|
89
|
+
input.removeListener("data", onData);
|
|
90
90
|
// Terminate the (un-echoed) line the user couldn't see themselves type.
|
|
91
|
-
output.write(
|
|
91
|
+
output.write("\n");
|
|
92
92
|
resolve(result);
|
|
93
93
|
};
|
|
94
94
|
const onData = (chunk) => {
|
|
95
95
|
for (const ch of chunk) {
|
|
96
|
-
if (ch ===
|
|
96
|
+
if (ch === "\n" || ch === "\r") {
|
|
97
97
|
finish(buf);
|
|
98
98
|
return;
|
|
99
99
|
}
|
|
100
|
-
if (ch ===
|
|
100
|
+
if (ch === "") {
|
|
101
101
|
// Ctrl-C: cancel, return nothing.
|
|
102
|
-
finish(
|
|
102
|
+
finish("");
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
|
-
if (ch ===
|
|
105
|
+
if (ch === "") {
|
|
106
106
|
// Ctrl-D (EOF): return what we have.
|
|
107
107
|
finish(buf);
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
|
-
if (ch ===
|
|
110
|
+
if (ch === "" || ch === "\b") {
|
|
111
111
|
// DEL / Backspace: drop the last character.
|
|
112
112
|
buf = buf.slice(0, -1);
|
|
113
113
|
continue;
|
|
@@ -115,6 +115,6 @@ const readNoEchoTty = (prompt, input, output) => {
|
|
|
115
115
|
buf += ch;
|
|
116
116
|
}
|
|
117
117
|
};
|
|
118
|
-
input.on(
|
|
118
|
+
input.on("data", onData);
|
|
119
119
|
});
|
|
120
120
|
};
|
|
@@ -31,7 +31,7 @@ const toCodePoints = (s) => Array.from(s);
|
|
|
31
31
|
* Pure line buffer plus kill ring plus undo. Used by the keymap layer.
|
|
32
32
|
*/
|
|
33
33
|
export class LineBuffer {
|
|
34
|
-
constructor(initial =
|
|
34
|
+
constructor(initial = "", cursor) {
|
|
35
35
|
/** One code point per element. */
|
|
36
36
|
this.chars = [];
|
|
37
37
|
/** Code-point index in `[0, chars.length]`. */
|
|
@@ -41,7 +41,7 @@ export class LineBuffer {
|
|
|
41
41
|
/** Index into killRing for the next yank-pop (^Y / Alt-Y). */
|
|
42
42
|
this.yankIndex = -1;
|
|
43
43
|
/** Direction of the previous kill so consecutive kills concatenate. */
|
|
44
|
-
this.lastKill =
|
|
44
|
+
this.lastKill = "none";
|
|
45
45
|
/** Snapshots for ^_ / undo. */
|
|
46
46
|
this.undoStack = [];
|
|
47
47
|
this.chars = toCodePoints(initial);
|
|
@@ -52,7 +52,7 @@ export class LineBuffer {
|
|
|
52
52
|
// Accessors
|
|
53
53
|
// -------------------------------------------------------------------------
|
|
54
54
|
get text() {
|
|
55
|
-
return this.chars.join(
|
|
55
|
+
return this.chars.join("");
|
|
56
56
|
}
|
|
57
57
|
get cursor() {
|
|
58
58
|
return this._cursor;
|
|
@@ -66,7 +66,7 @@ export class LineBuffer {
|
|
|
66
66
|
restore(snap) {
|
|
67
67
|
this.chars = toCodePoints(snap.text);
|
|
68
68
|
this._cursor = this.clampCursor(snap.cursor);
|
|
69
|
-
this.lastKill =
|
|
69
|
+
this.lastKill = "none";
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
72
|
* Replace the entire buffer in one go. Used when navigating history or
|
|
@@ -77,7 +77,7 @@ export class LineBuffer {
|
|
|
77
77
|
this.chars = toCodePoints(text);
|
|
78
78
|
this._cursor =
|
|
79
79
|
cursor === undefined ? this.chars.length : this.clampCursor(cursor);
|
|
80
|
-
this.lastKill =
|
|
80
|
+
this.lastKill = "none";
|
|
81
81
|
}
|
|
82
82
|
// -------------------------------------------------------------------------
|
|
83
83
|
// Cursor movement (all in code points, never bytes / UTF-16 units)
|
|
@@ -85,20 +85,20 @@ export class LineBuffer {
|
|
|
85
85
|
moveLeft() {
|
|
86
86
|
if (this._cursor > 0)
|
|
87
87
|
this._cursor--;
|
|
88
|
-
this.lastKill =
|
|
88
|
+
this.lastKill = "none";
|
|
89
89
|
}
|
|
90
90
|
moveRight() {
|
|
91
91
|
if (this._cursor < this.chars.length)
|
|
92
92
|
this._cursor++;
|
|
93
|
-
this.lastKill =
|
|
93
|
+
this.lastKill = "none";
|
|
94
94
|
}
|
|
95
95
|
moveHome() {
|
|
96
96
|
this._cursor = 0;
|
|
97
|
-
this.lastKill =
|
|
97
|
+
this.lastKill = "none";
|
|
98
98
|
}
|
|
99
99
|
moveEnd() {
|
|
100
100
|
this._cursor = this.chars.length;
|
|
101
|
-
this.lastKill =
|
|
101
|
+
this.lastKill = "none";
|
|
102
102
|
}
|
|
103
103
|
/**
|
|
104
104
|
* Move left over one word. Word ≙ run of alphanumerics. Skip the
|
|
@@ -113,7 +113,7 @@ export class LineBuffer {
|
|
|
113
113
|
while (i > 0 && isWordChar(this.chars[i - 1]))
|
|
114
114
|
i--;
|
|
115
115
|
this._cursor = i;
|
|
116
|
-
this.lastKill =
|
|
116
|
+
this.lastKill = "none";
|
|
117
117
|
}
|
|
118
118
|
moveWordRight() {
|
|
119
119
|
let i = this._cursor;
|
|
@@ -122,7 +122,7 @@ export class LineBuffer {
|
|
|
122
122
|
while (i < this.chars.length && isWordChar(this.chars[i]))
|
|
123
123
|
i++;
|
|
124
124
|
this._cursor = i;
|
|
125
|
-
this.lastKill =
|
|
125
|
+
this.lastKill = "none";
|
|
126
126
|
}
|
|
127
127
|
// -------------------------------------------------------------------------
|
|
128
128
|
// Insertion / deletion
|
|
@@ -135,7 +135,7 @@ export class LineBuffer {
|
|
|
135
135
|
const cps = toCodePoints(s);
|
|
136
136
|
this.chars.splice(this._cursor, 0, ...cps);
|
|
137
137
|
this._cursor += cps.length;
|
|
138
|
-
this.lastKill =
|
|
138
|
+
this.lastKill = "none";
|
|
139
139
|
}
|
|
140
140
|
/** Backspace: delete the code point to the left of cursor. */
|
|
141
141
|
deleteLeft() {
|
|
@@ -144,7 +144,7 @@ export class LineBuffer {
|
|
|
144
144
|
this.pushUndo();
|
|
145
145
|
this.chars.splice(this._cursor - 1, 1);
|
|
146
146
|
this._cursor--;
|
|
147
|
-
this.lastKill =
|
|
147
|
+
this.lastKill = "none";
|
|
148
148
|
}
|
|
149
149
|
/** Delete the code point to the right of cursor. */
|
|
150
150
|
deleteRight() {
|
|
@@ -152,7 +152,7 @@ export class LineBuffer {
|
|
|
152
152
|
return;
|
|
153
153
|
this.pushUndo();
|
|
154
154
|
this.chars.splice(this._cursor, 1);
|
|
155
|
-
this.lastKill =
|
|
155
|
+
this.lastKill = "none";
|
|
156
156
|
}
|
|
157
157
|
// -------------------------------------------------------------------------
|
|
158
158
|
// Kill ring operations
|
|
@@ -162,19 +162,19 @@ export class LineBuffer {
|
|
|
162
162
|
if (this._cursor === this.chars.length)
|
|
163
163
|
return;
|
|
164
164
|
this.pushUndo();
|
|
165
|
-
const killed = this.chars.slice(this._cursor).join(
|
|
165
|
+
const killed = this.chars.slice(this._cursor).join("");
|
|
166
166
|
this.chars.length = this._cursor;
|
|
167
|
-
this.recordKill(killed,
|
|
167
|
+
this.recordKill(killed, "forward");
|
|
168
168
|
}
|
|
169
169
|
/** ^U: kill from start-of-line to cursor. */
|
|
170
170
|
killToStart() {
|
|
171
171
|
if (this._cursor === 0)
|
|
172
172
|
return;
|
|
173
173
|
this.pushUndo();
|
|
174
|
-
const killed = this.chars.slice(0, this._cursor).join(
|
|
174
|
+
const killed = this.chars.slice(0, this._cursor).join("");
|
|
175
175
|
this.chars.splice(0, this._cursor);
|
|
176
176
|
this._cursor = 0;
|
|
177
|
-
this.recordKill(killed,
|
|
177
|
+
this.recordKill(killed, "backward");
|
|
178
178
|
}
|
|
179
179
|
/** ^W: kill the word (backward) to the left of cursor. */
|
|
180
180
|
killWordLeft() {
|
|
@@ -188,10 +188,10 @@ export class LineBuffer {
|
|
|
188
188
|
if (i === this._cursor)
|
|
189
189
|
return;
|
|
190
190
|
this.pushUndo();
|
|
191
|
-
const killed = this.chars.slice(i, this._cursor).join(
|
|
191
|
+
const killed = this.chars.slice(i, this._cursor).join("");
|
|
192
192
|
this.chars.splice(i, this._cursor - i);
|
|
193
193
|
this._cursor = i;
|
|
194
|
-
this.recordKill(killed,
|
|
194
|
+
this.recordKill(killed, "backward");
|
|
195
195
|
}
|
|
196
196
|
/** M-d: kill the word (forward) starting at cursor. */
|
|
197
197
|
killWordRight() {
|
|
@@ -205,9 +205,9 @@ export class LineBuffer {
|
|
|
205
205
|
if (i === this._cursor)
|
|
206
206
|
return;
|
|
207
207
|
this.pushUndo();
|
|
208
|
-
const killed = this.chars.slice(this._cursor, i).join(
|
|
208
|
+
const killed = this.chars.slice(this._cursor, i).join("");
|
|
209
209
|
this.chars.splice(this._cursor, i - this._cursor);
|
|
210
|
-
this.recordKill(killed,
|
|
210
|
+
this.recordKill(killed, "forward");
|
|
211
211
|
}
|
|
212
212
|
/** ^Y: yank most recent kill at cursor. No-op if ring is empty. */
|
|
213
213
|
yank() {
|
|
@@ -240,7 +240,7 @@ export class LineBuffer {
|
|
|
240
240
|
}
|
|
241
241
|
/** Reset the kill-merge tracker. Called when a non-kill action runs. */
|
|
242
242
|
resetKillTracking() {
|
|
243
|
-
this.lastKill =
|
|
243
|
+
this.lastKill = "none";
|
|
244
244
|
}
|
|
245
245
|
/** Test helper / introspection. */
|
|
246
246
|
getKillRing() {
|
|
@@ -263,7 +263,7 @@ export class LineBuffer {
|
|
|
263
263
|
return false;
|
|
264
264
|
this.chars = toCodePoints(snap.text);
|
|
265
265
|
this._cursor = this.clampCursor(snap.cursor);
|
|
266
|
-
this.lastKill =
|
|
266
|
+
this.lastKill = "none";
|
|
267
267
|
return true;
|
|
268
268
|
}
|
|
269
269
|
// -------------------------------------------------------------------------
|
|
@@ -283,7 +283,9 @@ export class LineBuffer {
|
|
|
283
283
|
// Merge with the previous kill so consecutive ^K / ^W feel like one
|
|
284
284
|
// logical kill in the yank ring.
|
|
285
285
|
this.killRing[0] =
|
|
286
|
-
dir ===
|
|
286
|
+
dir === "forward"
|
|
287
|
+
? this.killRing[0] + text
|
|
288
|
+
: text + this.killRing[0];
|
|
287
289
|
}
|
|
288
290
|
else {
|
|
289
291
|
this.killRing.unshift(text);
|
|
@@ -68,7 +68,7 @@ export class CompletionState {
|
|
|
68
68
|
this.cycleIndex = -1;
|
|
69
69
|
this.cycleLen = 0;
|
|
70
70
|
if (res.candidates.length === 0)
|
|
71
|
-
return { kind:
|
|
71
|
+
return { kind: "bell" };
|
|
72
72
|
if (res.candidates.length === 1) {
|
|
73
73
|
// Mirror upstream readline: a unique completion gets a trailing space
|
|
74
74
|
// (rl_completion_append_character defaults to ' ') unless the result
|
|
@@ -76,10 +76,10 @@ export class CompletionState {
|
|
|
76
76
|
// is expected to continue typing through) or the candidate itself
|
|
77
77
|
// already ends in a punctuator that shouldn't be followed by a space.
|
|
78
78
|
const cand = res.candidates[0];
|
|
79
|
-
const text = shouldAppendSpace(cand, res) ? cand +
|
|
79
|
+
const text = shouldAppendSpace(cand, res) ? cand + " " : cand;
|
|
80
80
|
replaceBeforeCursor(buffer, res.replaceLength, text);
|
|
81
81
|
this.reset();
|
|
82
|
-
return { kind:
|
|
82
|
+
return { kind: "inserted" };
|
|
83
83
|
}
|
|
84
84
|
// Multiple candidates: insert the common prefix when it differs from
|
|
85
85
|
// what's currently in the buffer at that position. The diff can be
|
|
@@ -94,13 +94,13 @@ export class CompletionState {
|
|
|
94
94
|
// so the cycle path knows how many code points to overwrite when it
|
|
95
95
|
// swaps in the next candidate.
|
|
96
96
|
this.cycleLen = countCodePoints(res.commonPrefix);
|
|
97
|
-
return { kind:
|
|
97
|
+
return { kind: "inserted" };
|
|
98
98
|
}
|
|
99
99
|
// We already have a result, and the second Tab arrived in time.
|
|
100
100
|
this.tabCount++;
|
|
101
101
|
if (this.tabCount === 2 && elapsed <= DOUBLE_TAP_MS) {
|
|
102
102
|
// List the candidates.
|
|
103
|
-
return { kind:
|
|
103
|
+
return { kind: "list", candidates: this.result.candidates.slice() };
|
|
104
104
|
}
|
|
105
105
|
// Third or later: cycle.
|
|
106
106
|
const cands = this.result.candidates;
|
|
@@ -108,7 +108,7 @@ export class CompletionState {
|
|
|
108
108
|
const cand = cands[this.cycleIndex];
|
|
109
109
|
replaceBeforeCursor(buffer, this.cycleLen, cand);
|
|
110
110
|
this.cycleLen = countCodePoints(cand);
|
|
111
|
-
return { kind:
|
|
111
|
+
return { kind: "cycled", candidate: cand };
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
/**
|
|
@@ -146,9 +146,9 @@ const shouldAppendSpace = (candidate, result) => {
|
|
|
146
146
|
if (candidate.length === 0)
|
|
147
147
|
return false;
|
|
148
148
|
const last = candidate[candidate.length - 1];
|
|
149
|
-
if (last ===
|
|
149
|
+
if (last === " " || last === "\t" || last === "\n")
|
|
150
150
|
return false;
|
|
151
|
-
if (last ===
|
|
151
|
+
if (last === "." || last === "/" || last === "(")
|
|
152
152
|
return false;
|
|
153
153
|
if (last === '"') {
|
|
154
154
|
// Even count means quotes are balanced (e.g. `"mixedName"`) — completed
|
|
@@ -174,8 +174,8 @@ const countChar = (s, ch) => {
|
|
|
174
174
|
// Listing helper: format candidates in column layout for display.
|
|
175
175
|
// ---------------------------------------------------------------------------
|
|
176
176
|
/** ANSI reverse-video escape (SGR 7) used to mark the active candidate. */
|
|
177
|
-
const SGR_REVERSE =
|
|
178
|
-
const SGR_NO_REVERSE =
|
|
177
|
+
const SGR_REVERSE = "\x1b[7m";
|
|
178
|
+
const SGR_NO_REVERSE = "\x1b[27m";
|
|
179
179
|
/**
|
|
180
180
|
* Lay out candidates into a multi-column block. Returns the formatted
|
|
181
181
|
* string (without a trailing newline — caller decides). Columns are sized
|
|
@@ -190,7 +190,7 @@ const SGR_NO_REVERSE = '\x1b[27m';
|
|
|
190
190
|
*/
|
|
191
191
|
export const formatCandidates = (candidates, termWidth, highlightIndex) => {
|
|
192
192
|
if (candidates.length === 0)
|
|
193
|
-
return
|
|
193
|
+
return "";
|
|
194
194
|
const maxLen = candidates.reduce((m, c) => Math.max(m, c.length), 0);
|
|
195
195
|
const colWidth = maxLen + 2;
|
|
196
196
|
const cols = Math.max(1, Math.floor(termWidth / colWidth));
|
|
@@ -208,20 +208,20 @@ export const formatCandidates = (candidates, termWidth, highlightIndex) => {
|
|
|
208
208
|
if (idx >= candidates.length)
|
|
209
209
|
break;
|
|
210
210
|
const cand = candidates[idx];
|
|
211
|
-
const padded = cand +
|
|
211
|
+
const padded = cand + " ".repeat(colWidth - cand.length);
|
|
212
212
|
if (idx === hl) {
|
|
213
213
|
// Wrap only the candidate text, not the gutter spaces, so adjacent
|
|
214
214
|
// columns don't get a colored stripe between them.
|
|
215
215
|
parts.push(SGR_REVERSE +
|
|
216
216
|
cand +
|
|
217
217
|
SGR_NO_REVERSE +
|
|
218
|
-
|
|
218
|
+
" ".repeat(colWidth - cand.length));
|
|
219
219
|
}
|
|
220
220
|
else {
|
|
221
221
|
parts.push(padded);
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
|
-
lines.push(parts.join(
|
|
224
|
+
lines.push(parts.join("").trimEnd());
|
|
225
225
|
}
|
|
226
|
-
return lines.join(
|
|
226
|
+
return lines.join("\n");
|
|
227
227
|
};
|