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.
Files changed (135) hide show
  1. package/README.md +71 -71
  2. package/dist/analytics.js +35 -33
  3. package/dist/api.js +34 -34
  4. package/dist/auth.js +50 -44
  5. package/dist/cli.js +2 -2
  6. package/dist/commands/auth.js +58 -52
  7. package/dist/commands/bootstrap.js +115 -157
  8. package/dist/commands/branches.js +154 -147
  9. package/dist/commands/bucket.js +124 -118
  10. package/dist/commands/checkout.js +49 -49
  11. package/dist/commands/config.js +212 -88
  12. package/dist/commands/connection_string.js +62 -62
  13. package/dist/commands/data_api.js +96 -96
  14. package/dist/commands/databases.js +23 -23
  15. package/dist/commands/deploy.js +12 -12
  16. package/dist/commands/dev.js +114 -114
  17. package/dist/commands/env.js +43 -43
  18. package/dist/commands/functions.js +97 -98
  19. package/dist/commands/index.js +26 -26
  20. package/dist/commands/init.js +23 -22
  21. package/dist/commands/ip_allow.js +29 -29
  22. package/dist/commands/link.js +223 -166
  23. package/dist/commands/neon_auth.js +381 -363
  24. package/dist/commands/operations.js +11 -11
  25. package/dist/commands/orgs.js +8 -8
  26. package/dist/commands/projects.js +101 -99
  27. package/dist/commands/psql.js +31 -31
  28. package/dist/commands/roles.js +21 -21
  29. package/dist/commands/schema_diff.js +23 -23
  30. package/dist/commands/set_context.js +17 -17
  31. package/dist/commands/status.js +17 -17
  32. package/dist/commands/user.js +5 -5
  33. package/dist/commands/vpc_endpoints.js +50 -50
  34. package/dist/config.js +7 -7
  35. package/dist/config_format.js +5 -5
  36. package/dist/context.js +23 -16
  37. package/dist/current_branch_fast_path.js +6 -6
  38. package/dist/dev/env.js +34 -34
  39. package/dist/dev/functions.js +4 -4
  40. package/dist/dev/inputs.js +6 -6
  41. package/dist/dev/runtime.js +25 -25
  42. package/dist/env.js +14 -14
  43. package/dist/env_file.js +13 -13
  44. package/dist/errors.js +19 -19
  45. package/dist/functions_api.js +10 -10
  46. package/dist/help.js +15 -15
  47. package/dist/index.js +94 -92
  48. package/dist/log.js +2 -2
  49. package/dist/pkg.js +5 -5
  50. package/dist/psql/cli.js +4 -2
  51. package/dist/psql/command/cmd_cond.js +61 -61
  52. package/dist/psql/command/cmd_connect.js +159 -154
  53. package/dist/psql/command/cmd_copy.js +107 -97
  54. package/dist/psql/command/cmd_describe.js +368 -363
  55. package/dist/psql/command/cmd_format.js +276 -263
  56. package/dist/psql/command/cmd_io.js +269 -263
  57. package/dist/psql/command/cmd_lo.js +74 -66
  58. package/dist/psql/command/cmd_meta.js +148 -148
  59. package/dist/psql/command/cmd_misc.js +17 -17
  60. package/dist/psql/command/cmd_pipeline.js +142 -135
  61. package/dist/psql/command/cmd_restrict.js +25 -25
  62. package/dist/psql/command/cmd_show.js +183 -168
  63. package/dist/psql/command/dispatch.js +26 -26
  64. package/dist/psql/command/shared.js +14 -14
  65. package/dist/psql/complete/filenames.js +16 -16
  66. package/dist/psql/complete/index.js +4 -4
  67. package/dist/psql/complete/matcher.js +33 -32
  68. package/dist/psql/complete/psqlVars.js +173 -173
  69. package/dist/psql/complete/queries.js +5 -3
  70. package/dist/psql/complete/rules.js +900 -863
  71. package/dist/psql/core/common.js +136 -133
  72. package/dist/psql/core/help.js +343 -343
  73. package/dist/psql/core/mainloop.js +160 -153
  74. package/dist/psql/core/prompt.js +126 -123
  75. package/dist/psql/core/settings.js +111 -111
  76. package/dist/psql/core/sqlHelp.js +150 -150
  77. package/dist/psql/core/startup.js +211 -205
  78. package/dist/psql/core/syncVars.js +14 -14
  79. package/dist/psql/core/variables.js +24 -24
  80. package/dist/psql/describe/formatters.js +302 -289
  81. package/dist/psql/describe/processNamePattern.js +28 -28
  82. package/dist/psql/describe/queries.js +656 -651
  83. package/dist/psql/index.js +436 -411
  84. package/dist/psql/io/history.js +36 -36
  85. package/dist/psql/io/input.js +15 -15
  86. package/dist/psql/io/lineEditor/buffer.js +27 -25
  87. package/dist/psql/io/lineEditor/complete.js +15 -15
  88. package/dist/psql/io/lineEditor/filename.js +22 -22
  89. package/dist/psql/io/lineEditor/index.js +65 -62
  90. package/dist/psql/io/lineEditor/keymap.js +325 -318
  91. package/dist/psql/io/lineEditor/vt100.js +60 -60
  92. package/dist/psql/io/pgpass.js +18 -18
  93. package/dist/psql/io/pgservice.js +14 -14
  94. package/dist/psql/io/psqlrc.js +46 -46
  95. package/dist/psql/print/aligned.js +175 -166
  96. package/dist/psql/print/asciidoc.js +51 -51
  97. package/dist/psql/print/crosstab.js +34 -31
  98. package/dist/psql/print/csv.js +25 -22
  99. package/dist/psql/print/html.js +54 -54
  100. package/dist/psql/print/json.js +12 -12
  101. package/dist/psql/print/latex.js +118 -118
  102. package/dist/psql/print/pager.js +28 -26
  103. package/dist/psql/print/troff.js +48 -48
  104. package/dist/psql/print/unaligned.js +15 -14
  105. package/dist/psql/print/units.js +17 -17
  106. package/dist/psql/scanner/slash.js +48 -46
  107. package/dist/psql/scanner/sql.js +88 -84
  108. package/dist/psql/scanner/stringutils.js +21 -17
  109. package/dist/psql/types/index.js +7 -7
  110. package/dist/psql/types/scanner.js +8 -8
  111. package/dist/psql/wire/connection.js +341 -327
  112. package/dist/psql/wire/copy.js +7 -7
  113. package/dist/psql/wire/pipeline.js +26 -24
  114. package/dist/psql/wire/protocol.js +102 -102
  115. package/dist/psql/wire/sasl.js +62 -62
  116. package/dist/psql/wire/tls.js +79 -73
  117. package/dist/storage_api.js +15 -15
  118. package/dist/test_utils/fixtures.js +34 -31
  119. package/dist/test_utils/oauth_server.js +5 -5
  120. package/dist/utils/api_enums.js +13 -13
  121. package/dist/utils/branch_notice.js +5 -5
  122. package/dist/utils/branch_picker.js +26 -26
  123. package/dist/utils/compute_units.js +4 -4
  124. package/dist/utils/enrichers.js +20 -15
  125. package/dist/utils/esbuild.js +28 -28
  126. package/dist/utils/formats.js +1 -1
  127. package/dist/utils/middlewares.js +3 -3
  128. package/dist/utils/package_manager.js +68 -0
  129. package/dist/utils/point_in_time.js +12 -12
  130. package/dist/utils/psql.js +30 -30
  131. package/dist/utils/string.js +2 -2
  132. package/dist/utils/ui.js +9 -9
  133. package/dist/utils/zip.js +1 -1
  134. package/dist/writer.js +17 -17
  135. package/package.json +6 -7
@@ -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 { promises as fs } from 'node:fs';
37
- import * as os from 'node:os';
38
- import * as path from 'node:path';
39
- import { randomUUID } from 'node:crypto';
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, '\\\\').replace(/\n/g, '\\n').replace(/\r/g, '\\r');
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 === 'n') {
96
- out += '\n';
95
+ if (next === "n") {
96
+ out += "\n";
97
97
  i++;
98
98
  continue;
99
99
  }
100
- if (next === 'r') {
101
- out += '\r';
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 === 'ignorespace' || histcontrol === 'ignoreboth';
123
- const ignoreDups = histcontrol === 'ignoredups' || histcontrol === 'ignoreboth';
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, 'utf8');
143
+ raw = await fs.readFile(filePath, "utf8");
144
144
  }
145
145
  catch (err) {
146
146
  if (err &&
147
- typeof err === 'object' &&
148
- 'code' in err &&
149
- err.code === 'ENOENT') {
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('\n'))
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('\n')) {
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 = 'none') => {
187
- if (histcontrol === 'ignoredups' ||
188
- histcontrol === 'ignoreboth' ||
189
- histcontrol === 'ignorespace') {
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 === 'ignoredups' || histcontrol === 'ignoreboth') {
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) + '\n', 'utf8');
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 === 'object' &&
225
- 'code' in err &&
226
- err.code === 'ENOENT') {
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('\n') + '\n';
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: 'utf8', mode: 0o600 });
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 === 'win32') {
272
+ if (process.platform === "win32") {
273
273
  const appdata = env.APPDATA;
274
274
  if (appdata !== undefined && appdata.length > 0) {
275
- return path.join(appdata, 'postgresql', 'psql_history');
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, '.psql_history');
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)
@@ -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 'node:readline';
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 === 'function';
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('line', (l) => {
64
+ rl.on("line", (l) => {
65
65
  settle(l);
66
66
  });
67
- rl.on('close', () => {
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('utf8');
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('data', onData);
89
+ input.removeListener("data", onData);
90
90
  // Terminate the (un-echoed) line the user couldn't see themselves type.
91
- output.write('\n');
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 === '\n' || ch === '\r') {
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 === '' || ch === '\b') {
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('data', onData);
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 = '', cursor) {
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 = 'none';
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 = 'none';
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 = 'none';
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 = 'none';
88
+ this.lastKill = "none";
89
89
  }
90
90
  moveRight() {
91
91
  if (this._cursor < this.chars.length)
92
92
  this._cursor++;
93
- this.lastKill = 'none';
93
+ this.lastKill = "none";
94
94
  }
95
95
  moveHome() {
96
96
  this._cursor = 0;
97
- this.lastKill = 'none';
97
+ this.lastKill = "none";
98
98
  }
99
99
  moveEnd() {
100
100
  this._cursor = this.chars.length;
101
- this.lastKill = 'none';
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 = 'none';
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 = 'none';
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 = 'none';
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 = 'none';
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 = 'none';
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, 'forward');
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, 'backward');
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, 'backward');
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, 'forward');
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 = 'none';
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 = 'none';
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 === 'forward' ? this.killRing[0] + text : text + this.killRing[0];
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: 'bell' };
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 + ' ' : cand;
79
+ const text = shouldAppendSpace(cand, res) ? cand + " " : cand;
80
80
  replaceBeforeCursor(buffer, res.replaceLength, text);
81
81
  this.reset();
82
- return { kind: 'inserted' };
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: 'inserted' };
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: 'list', candidates: this.result.candidates.slice() };
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: 'cycled', candidate: cand };
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 === ' ' || last === '\t' || last === '\n')
149
+ if (last === " " || last === "\t" || last === "\n")
150
150
  return false;
151
- if (last === '.' || last === '/' || 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 = '\x1b[7m';
178
- const SGR_NO_REVERSE = '\x1b[27m';
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 + ' '.repeat(colWidth - cand.length);
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
- ' '.repeat(colWidth - cand.length));
218
+ " ".repeat(colWidth - cand.length));
219
219
  }
220
220
  else {
221
221
  parts.push(padded);
222
222
  }
223
223
  }
224
- lines.push(parts.join('').trimEnd());
224
+ lines.push(parts.join("").trimEnd());
225
225
  }
226
- return lines.join('\n');
226
+ return lines.join("\n");
227
227
  };