neonctl 2.27.1 → 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.
Files changed (136) hide show
  1. package/README.md +35 -3
  2. package/dist/analytics.js +52 -34
  3. package/dist/api.js +643 -13
  4. package/dist/auth.js +50 -44
  5. package/dist/cli.js +8 -1
  6. package/dist/commands/auth.js +64 -51
  7. package/dist/commands/bootstrap.js +115 -157
  8. package/dist/commands/branches.js +160 -150
  9. package/dist/commands/bucket.js +183 -146
  10. package/dist/commands/checkout.js +51 -51
  11. package/dist/commands/config.js +228 -82
  12. package/dist/commands/connection_string.js +62 -62
  13. package/dist/commands/data_api.js +100 -101
  14. package/dist/commands/databases.js +29 -26
  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 +101 -104
  19. package/dist/commands/index.js +27 -25
  20. package/dist/commands/init.js +23 -22
  21. package/dist/commands/ip_allow.js +29 -29
  22. package/dist/commands/link.js +232 -182
  23. package/dist/commands/neon_auth.js +385 -370
  24. package/dist/commands/operations.js +11 -11
  25. package/dist/commands/orgs.js +8 -8
  26. package/dist/commands/projects.js +103 -101
  27. package/dist/commands/psql.js +31 -31
  28. package/dist/commands/roles.js +27 -24
  29. package/dist/commands/schema_diff.js +25 -26
  30. package/dist/commands/set_context.js +17 -17
  31. package/dist/commands/status.js +40 -0
  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 +37 -14
  37. package/dist/current_branch_fast_path.js +55 -0
  38. package/dist/dev/env.js +33 -33
  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 +68 -5
  45. package/dist/functions_api.js +10 -10
  46. package/dist/help.js +15 -15
  47. package/dist/index.js +110 -107
  48. package/dist/log.js +2 -2
  49. package/dist/parameters.gen.js +14 -14
  50. package/dist/pkg.js +5 -5
  51. package/dist/psql/cli.js +4 -2
  52. package/dist/psql/command/cmd_cond.js +61 -61
  53. package/dist/psql/command/cmd_connect.js +159 -154
  54. package/dist/psql/command/cmd_copy.js +107 -97
  55. package/dist/psql/command/cmd_describe.js +368 -363
  56. package/dist/psql/command/cmd_format.js +276 -263
  57. package/dist/psql/command/cmd_io.js +269 -263
  58. package/dist/psql/command/cmd_lo.js +74 -66
  59. package/dist/psql/command/cmd_meta.js +148 -148
  60. package/dist/psql/command/cmd_misc.js +17 -17
  61. package/dist/psql/command/cmd_pipeline.js +142 -135
  62. package/dist/psql/command/cmd_restrict.js +25 -25
  63. package/dist/psql/command/cmd_show.js +183 -168
  64. package/dist/psql/command/dispatch.js +26 -26
  65. package/dist/psql/command/shared.js +14 -14
  66. package/dist/psql/complete/filenames.js +16 -16
  67. package/dist/psql/complete/index.js +4 -4
  68. package/dist/psql/complete/matcher.js +33 -32
  69. package/dist/psql/complete/psqlVars.js +173 -173
  70. package/dist/psql/complete/queries.js +5 -3
  71. package/dist/psql/complete/rules.js +900 -863
  72. package/dist/psql/core/common.js +136 -133
  73. package/dist/psql/core/help.js +343 -343
  74. package/dist/psql/core/mainloop.js +160 -153
  75. package/dist/psql/core/prompt.js +126 -123
  76. package/dist/psql/core/settings.js +111 -111
  77. package/dist/psql/core/sqlHelp.js +150 -150
  78. package/dist/psql/core/startup.js +211 -205
  79. package/dist/psql/core/syncVars.js +14 -14
  80. package/dist/psql/core/variables.js +24 -24
  81. package/dist/psql/describe/formatters.js +302 -289
  82. package/dist/psql/describe/processNamePattern.js +28 -28
  83. package/dist/psql/describe/queries.js +656 -651
  84. package/dist/psql/index.js +436 -411
  85. package/dist/psql/io/history.js +36 -36
  86. package/dist/psql/io/input.js +15 -15
  87. package/dist/psql/io/lineEditor/buffer.js +27 -25
  88. package/dist/psql/io/lineEditor/complete.js +15 -15
  89. package/dist/psql/io/lineEditor/filename.js +22 -22
  90. package/dist/psql/io/lineEditor/index.js +65 -62
  91. package/dist/psql/io/lineEditor/keymap.js +325 -318
  92. package/dist/psql/io/lineEditor/vt100.js +60 -60
  93. package/dist/psql/io/pgpass.js +18 -18
  94. package/dist/psql/io/pgservice.js +14 -14
  95. package/dist/psql/io/psqlrc.js +46 -46
  96. package/dist/psql/print/aligned.js +175 -166
  97. package/dist/psql/print/asciidoc.js +51 -51
  98. package/dist/psql/print/crosstab.js +34 -31
  99. package/dist/psql/print/csv.js +25 -22
  100. package/dist/psql/print/html.js +54 -54
  101. package/dist/psql/print/json.js +12 -12
  102. package/dist/psql/print/latex.js +118 -118
  103. package/dist/psql/print/pager.js +28 -26
  104. package/dist/psql/print/troff.js +48 -48
  105. package/dist/psql/print/unaligned.js +15 -14
  106. package/dist/psql/print/units.js +17 -17
  107. package/dist/psql/scanner/slash.js +48 -46
  108. package/dist/psql/scanner/sql.js +88 -84
  109. package/dist/psql/scanner/stringutils.js +21 -17
  110. package/dist/psql/types/index.js +7 -7
  111. package/dist/psql/types/scanner.js +8 -8
  112. package/dist/psql/wire/connection.js +341 -327
  113. package/dist/psql/wire/copy.js +7 -7
  114. package/dist/psql/wire/pipeline.js +26 -24
  115. package/dist/psql/wire/protocol.js +102 -102
  116. package/dist/psql/wire/sasl.js +62 -62
  117. package/dist/psql/wire/tls.js +79 -73
  118. package/dist/storage_api.js +22 -23
  119. package/dist/test_utils/fixtures.js +74 -41
  120. package/dist/test_utils/oauth_server.js +5 -5
  121. package/dist/utils/api_enums.js +33 -0
  122. package/dist/utils/branch_notice.js +5 -5
  123. package/dist/utils/branch_picker.js +26 -26
  124. package/dist/utils/compute_units.js +4 -4
  125. package/dist/utils/enrichers.js +28 -16
  126. package/dist/utils/esbuild.js +28 -28
  127. package/dist/utils/formats.js +1 -1
  128. package/dist/utils/middlewares.js +3 -3
  129. package/dist/utils/package_manager.js +68 -0
  130. package/dist/utils/point_in_time.js +12 -12
  131. package/dist/utils/psql.js +30 -30
  132. package/dist/utils/string.js +2 -2
  133. package/dist/utils/ui.js +9 -9
  134. package/dist/utils/zip.js +1 -1
  135. package/dist/writer.js +17 -17
  136. package/package.json +10 -12
@@ -30,19 +30,19 @@
30
30
  * delegates to `settings.vars`. Modes that disable substitution (`no-vars`)
31
31
  * naturally fall through to the scanner's existing behaviour.
32
32
  */
33
- import { scanSlashArgs } from '../scanner/slash.js';
34
- import { cmdCd, cmdCopyright, cmdEcho, cmdEdit, cmdErrverbose, cmdGetenv, cmdHelpSQL, cmdPrompt, cmdQecho, cmdQuit, cmdReset, cmdS, cmdSet, cmdSetenv, cmdShell, cmdSlashHelp, cmdTiming, cmdUnset, cmdWarn, } from './cmd_meta.js';
35
- import { cmdA, cmdC, cmdEncoding, cmdF, cmdH, cmdPset, cmdT, cmdTitleAttr, cmdX, } from './cmd_format.js';
36
- import { registerIoCommands } from './cmd_io.js';
37
- import { registerConnectCommands } from './cmd_connect.js';
38
- import { registerCopyCommands } from './cmd_copy.js';
39
- import { registerDescribeCommands } from './cmd_describe.js';
40
- import { registerPipelineCommands } from './cmd_pipeline.js';
41
- import { registerMiscCommands } from './cmd_misc.js';
42
- import { registerLargeObjectCommands } from './cmd_lo.js';
43
- import { registerShowCommands } from './cmd_show.js';
44
- import { isCommandRestricted, registerRestrictCommands, wrapRestrictedCommands, } from './cmd_restrict.js';
45
- import { writeErr } from './shared.js';
33
+ import { scanSlashArgs } from "../scanner/slash.js";
34
+ import { registerConnectCommands } from "./cmd_connect.js";
35
+ import { registerCopyCommands } from "./cmd_copy.js";
36
+ import { registerDescribeCommands } from "./cmd_describe.js";
37
+ import { cmdA, cmdC, cmdEncoding, cmdF, cmdH, cmdPset, cmdT, cmdTitleAttr, cmdX, } from "./cmd_format.js";
38
+ import { registerIoCommands } from "./cmd_io.js";
39
+ import { registerLargeObjectCommands } from "./cmd_lo.js";
40
+ import { cmdCd, cmdCopyright, cmdEcho, cmdEdit, cmdErrverbose, cmdGetenv, cmdHelpSQL, cmdPrompt, cmdQecho, cmdQuit, cmdReset, cmdS, cmdSet, cmdSetenv, cmdShell, cmdSlashHelp, cmdTiming, cmdUnset, cmdWarn, } from "./cmd_meta.js";
41
+ import { registerMiscCommands } from "./cmd_misc.js";
42
+ import { registerPipelineCommands } from "./cmd_pipeline.js";
43
+ import { isCommandRestricted, registerRestrictCommands, wrapRestrictedCommands, } from "./cmd_restrict.js";
44
+ import { registerShowCommands } from "./cmd_show.js";
45
+ import { writeErr } from "./shared.js";
46
46
  /**
47
47
  * Concrete `BackslashRegistry`: a primary-name → spec map plus a parallel
48
48
  * alias → primary-name map so lookups stay O(1).
@@ -103,7 +103,7 @@ export const makeContext = (opts) => {
103
103
  let cursor = 0;
104
104
  const rawArgs = opts.rawArgs;
105
105
  const varLookup = (name) => opts.settings.vars.get(name);
106
- const nextArg = (mode = 'normal') => {
106
+ const nextArg = (mode = "normal") => {
107
107
  // Find the next non-whitespace byte from the cursor; we use it both to
108
108
  // know whether anything remains and as the basis for span tracking.
109
109
  let i = cursor;
@@ -111,7 +111,7 @@ export const makeContext = (opts) => {
111
111
  i++;
112
112
  if (i >= rawArgs.length)
113
113
  return null;
114
- if (mode === 'whole-line') {
114
+ if (mode === "whole-line") {
115
115
  const tail = rawArgs.slice(i);
116
116
  cursor = rawArgs.length;
117
117
  return tail;
@@ -162,7 +162,7 @@ export const makeContext = (opts) => {
162
162
  * past the original `:name` form regardless of expansion size.
163
163
  */
164
164
  const consumedSpan = (tail, mode, varLookup) => {
165
- if (mode === 'whole-line')
165
+ if (mode === "whole-line")
166
166
  return tail.length;
167
167
  let i = 0;
168
168
  // Skip leading whitespace inside the tail (already trimmed by caller, but
@@ -170,16 +170,16 @@ const consumedSpan = (tail, mode, varLookup) => {
170
170
  while (i < tail.length && /[\s]/.test(tail[i]))
171
171
  i++;
172
172
  // filepipe special: a leading `|` slurps to EOL.
173
- if (mode === 'filepipe' && tail[i] === '|')
173
+ if (mode === "filepipe" && tail[i] === "|")
174
174
  return tail.length;
175
175
  while (i < tail.length) {
176
176
  const c = tail[i];
177
- if (/[\s]/.test(c) || c === '\\')
177
+ if (/[\s]/.test(c) || c === "\\")
178
178
  break;
179
179
  if (c === "'") {
180
180
  i++;
181
181
  while (i < tail.length) {
182
- if (tail[i] === '\\' && i + 1 < tail.length) {
182
+ if (tail[i] === "\\" && i + 1 < tail.length) {
183
183
  i += 2;
184
184
  continue;
185
185
  }
@@ -203,15 +203,15 @@ const consumedSpan = (tail, mode, varLookup) => {
203
203
  i++;
204
204
  continue;
205
205
  }
206
- if (c === '`') {
206
+ if (c === "`") {
207
207
  i++;
208
- while (i < tail.length && tail[i] !== '`')
208
+ while (i < tail.length && tail[i] !== "`")
209
209
  i++;
210
210
  if (i < tail.length)
211
211
  i++;
212
212
  continue;
213
213
  }
214
- if (c === ':' && mode !== 'no-vars') {
214
+ if (c === ":" && mode !== "no-vars") {
215
215
  // :"name" / :'name' / :name — advance past the source form. We don't
216
216
  // actually call varLookup here; we just measure the lexical span.
217
217
  void varLookup;
@@ -248,14 +248,14 @@ const consumedSpan = (tail, mode, varLookup) => {
248
248
  export const dispatchBackslash = async (registry, cmdName, ctx) => {
249
249
  const spec = registry.lookup(cmdName);
250
250
  if (!spec)
251
- return { status: 'error' };
251
+ return { status: "error" };
252
252
  // PG 18: refuse shell/filesystem-touching commands while restricted.
253
253
  // We check against the resolved *primary* name so aliases like
254
254
  // `\write` → `w` are caught.
255
255
  if (isCommandRestricted(ctx.settings, spec.name)) {
256
256
  writeErr(`\\${cmdName}: command is not allowed in restricted mode; ` +
257
257
  `use \\unrestrict to leave restricted mode\n`);
258
- return { status: 'error' };
258
+ return { status: "error" };
259
259
  }
260
260
  return spec.run(ctx);
261
261
  };
@@ -310,8 +310,8 @@ export const defaultRegistry = () => {
310
310
  // shim. (In an active branch this makes `\html` a silent no-op rather than
311
311
  // upstream's "invalid command", but no test exercises that path.)
312
312
  r.register({
313
- name: 'html',
314
- run: () => Promise.resolve({ status: 'ok' }),
313
+ name: "html",
314
+ run: () => Promise.resolve({ status: "ok" }),
315
315
  });
316
316
  // Format.
317
317
  r.register(cmdA);
@@ -31,23 +31,23 @@ export const parseBool = (raw) => {
31
31
  return null;
32
32
  const lower = raw.toLowerCase();
33
33
  const startsWith = (target) => lower.length <= target.length && target.startsWith(lower);
34
- if (startsWith('true'))
34
+ if (startsWith("true"))
35
35
  return true;
36
- if (startsWith('false'))
36
+ if (startsWith("false"))
37
37
  return false;
38
- if (startsWith('yes'))
38
+ if (startsWith("yes"))
39
39
  return true;
40
- if (startsWith('no'))
40
+ if (startsWith("no"))
41
41
  return false;
42
42
  if (lower.length >= 2) {
43
- if ('on'.startsWith(lower))
43
+ if ("on".startsWith(lower))
44
44
  return true;
45
- if ('off'.startsWith(lower))
45
+ if ("off".startsWith(lower))
46
46
  return false;
47
47
  }
48
- if (raw === '1')
48
+ if (raw === "1")
49
49
  return true;
50
- if (raw === '0')
50
+ if (raw === "0")
51
51
  return false;
52
52
  return null;
53
53
  };
@@ -60,12 +60,12 @@ export const parseTriple = (raw) => {
60
60
  // bool prefixes were inverted (review: minor divergences).
61
61
  const b = parseBool(raw);
62
62
  if (b === true)
63
- return 'on';
63
+ return "on";
64
64
  if (b === false)
65
- return 'off';
66
- if ('auto'.startsWith(lower))
67
- return 'auto';
68
- if ('toggle'.startsWith(lower))
69
- return 'toggle';
65
+ return "off";
66
+ if ("auto".startsWith(lower))
67
+ return "auto";
68
+ if ("toggle".startsWith(lower))
69
+ return "toggle";
70
70
  return null;
71
71
  };
@@ -19,8 +19,8 @@
19
19
  * fires; partial multi-candidate prefixes leave the closing quote off so
20
20
  * the user can keep typing.
21
21
  */
22
- import { readdirSync, statSync } from 'node:fs';
23
- import { join, basename, dirname } from 'node:path';
22
+ import { readdirSync, statSync } from "node:fs";
23
+ import { basename, dirname, join } from "node:path";
24
24
  /**
25
25
  * Enumerate filesystem entries matching the partial path the user typed.
26
26
  *
@@ -45,14 +45,14 @@ export const completeFilenames = (currentWord, quoteCtx, cwd = process.cwd()) =>
45
45
  }
46
46
  // Split into dir + basename prefix. A trailing `/` means "enumerate this
47
47
  // dir" and basename prefix is empty.
48
- const lastSlash = raw.lastIndexOf('/');
49
- const dirPart = lastSlash === -1 ? '' : raw.slice(0, lastSlash + 1);
48
+ const lastSlash = raw.lastIndexOf("/");
49
+ const dirPart = lastSlash === -1 ? "" : raw.slice(0, lastSlash + 1);
50
50
  const basePrefix = lastSlash === -1 ? raw : raw.slice(lastSlash + 1);
51
51
  // Resolve the directory to scan. Empty `dirPart` → cwd; otherwise it's
52
52
  // taken relative to cwd (or absolute if starts with `/`).
53
- const scanDir = dirPart === ''
53
+ const scanDir = dirPart === ""
54
54
  ? cwd
55
- : dirPart.startsWith('/')
55
+ : dirPart.startsWith("/")
56
56
  ? dirPart
57
57
  : join(cwd, dirPart);
58
58
  let entries;
@@ -80,10 +80,10 @@ export const completeFilenames = (currentWord, quoteCtx, cwd = process.cwd()) =>
80
80
  catch {
81
81
  // Broken symlink etc. — treat as regular file.
82
82
  }
83
- const full = dirPart + entry + (isDir ? '/' : '');
83
+ const full = dirPart + entry + (isDir ? "/" : "");
84
84
  candidates.push(full);
85
85
  }
86
- if (quoteCtx === 'none') {
86
+ if (quoteCtx === "none") {
87
87
  // Bare paths. Preserve any opening single quote the user already typed
88
88
  // (rare for the no-quote contexts, but harmless to mirror).
89
89
  if (hadOpeningSingleQuote) {
@@ -95,7 +95,7 @@ export const completeFilenames = (currentWord, quoteCtx, cwd = process.cwd()) =>
95
95
  // line editor's `shouldAppendSpace` checks quote balance — unique
96
96
  // candidates close the quote (so `'...'` balances → trailing space
97
97
  // fires), multi-candidate common prefixes leave the closing quote off.
98
- if (candidates.length === 1 && !candidates[0].endsWith('/')) {
98
+ if (candidates.length === 1 && !candidates[0].endsWith("/")) {
99
99
  // Unique file (not directory): close the quote so the trailing space
100
100
  // fires. Opening quote: re-add if user typed it, else add ourselves.
101
101
  return ["'" + candidates[0] + "'"];
@@ -118,18 +118,18 @@ export const isCopyFromOrTo = (prevWords) => {
118
118
  // Walk from the end backward: the immediate prev word must be FROM or TO,
119
119
  // and somewhere earlier must be COPY (case-insensitive).
120
120
  const last = prevWords[prevWords.length - 1].toUpperCase();
121
- if (last !== 'FROM' && last !== 'TO')
121
+ if (last !== "FROM" && last !== "TO")
122
122
  return false;
123
123
  for (let i = prevWords.length - 2; i >= 0; i--) {
124
- if (prevWords[i].toUpperCase() === 'COPY')
124
+ if (prevWords[i].toUpperCase() === "COPY")
125
125
  return true;
126
126
  // If we walk past the start of statement (e.g. another keyword like
127
127
  // SELECT) we abort — only the SQL `COPY` form should match.
128
- if (prevWords[i].toUpperCase() === 'SELECT' ||
129
- prevWords[i].toUpperCase() === 'INSERT' ||
130
- prevWords[i].toUpperCase() === 'UPDATE' ||
131
- prevWords[i].toUpperCase() === 'DELETE' ||
132
- prevWords[i].toUpperCase() === 'WITH') {
128
+ if (prevWords[i].toUpperCase() === "SELECT" ||
129
+ prevWords[i].toUpperCase() === "INSERT" ||
130
+ prevWords[i].toUpperCase() === "UPDATE" ||
131
+ prevWords[i].toUpperCase() === "DELETE" ||
132
+ prevWords[i].toUpperCase() === "WITH") {
133
133
  return false;
134
134
  }
135
135
  }
@@ -20,8 +20,8 @@
20
20
  * the cursor was sitting on (`currentWord`), which we already compute in
21
21
  * `splitForCompletion` during tokenization.
22
22
  */
23
- import { splitForCompletion } from './matcher.js';
24
- import { findCompletions } from './rules.js';
23
+ import { splitForCompletion } from "./matcher.js";
24
+ import { findCompletions } from "./rules.js";
25
25
  /**
26
26
  * Build a completer bound to the given settings. The settings reference is
27
27
  * captured by closure; we never snapshot, so changes to `settings.db` (via
@@ -32,7 +32,7 @@ export const psqlCompleter = (ctx) => {
32
32
  const { prevWords, currentWord, replaceLength } = splitForCompletion(input, cursor);
33
33
  const ruleCtx = {
34
34
  settings: ctx.settings,
35
- queryBuf: ctx.getQueryBuf?.() ?? '',
35
+ queryBuf: ctx.getQueryBuf?.() ?? "",
36
36
  };
37
37
  const { candidates } = await findCompletions(prevWords, currentWord, ruleCtx);
38
38
  // De-duplicate while preserving order.
@@ -97,7 +97,7 @@ const longestCommonPrefix = (candidates, fallback) => {
97
97
  out.push(first);
98
98
  }
99
99
  // Common prefix should not be shorter than what's already typed.
100
- const candidatePrefix = out.join('');
100
+ const candidatePrefix = out.join("");
101
101
  if (candidatePrefix.length >= fallback.length)
102
102
  return candidatePrefix;
103
103
  return fallback;
@@ -25,7 +25,7 @@
25
25
  * needs to do the same word-split-then-match dance.
26
26
  */
27
27
  export const MatchAny = null;
28
- export const MatchAnyExcept = (pattern) => '!' + pattern;
28
+ export const MatchAnyExcept = (pattern) => "!" + pattern;
29
29
  const cimatch = (s1, s2, n, caseSensitive) => {
30
30
  if (s1.length < n || s2.length < n)
31
31
  return false;
@@ -43,7 +43,7 @@ const cimatch = (s1, s2, n, caseSensitive) => {
43
43
  export const wordMatches = (pattern, word, caseSensitive = false) => {
44
44
  if (pattern === null)
45
45
  return true;
46
- if (pattern.startsWith('!')) {
46
+ if (pattern.startsWith("!")) {
47
47
  return !wordMatches(pattern.slice(1), word, caseSensitive);
48
48
  }
49
49
  const wordlen = word.length;
@@ -51,8 +51,8 @@ export const wordMatches = (pattern, word, caseSensitive = false) => {
51
51
  for (;;) {
52
52
  let starIdx = -1;
53
53
  let i = 0;
54
- while (i < cursor.length && cursor[i] !== '|') {
55
- if (cursor[i] === '*')
54
+ while (i < cursor.length && cursor[i] !== "|") {
55
+ if (cursor[i] === "*")
56
56
  starIdx = i;
57
57
  i++;
58
58
  }
@@ -66,7 +66,8 @@ export const wordMatches = (pattern, word, caseSensitive = false) => {
66
66
  }
67
67
  }
68
68
  else {
69
- if (wordlen === i && cimatch(word, cursor, wordlen, caseSensitive)) {
69
+ if (wordlen === i &&
70
+ cimatch(word, cursor, wordlen, caseSensitive)) {
70
71
  return true;
71
72
  }
72
73
  }
@@ -147,12 +148,12 @@ export const tokenize = (input) => {
147
148
  while (i < input.length) {
148
149
  const ch = input[i];
149
150
  // Whitespace.
150
- if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
151
+ if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r") {
151
152
  i++;
152
153
  continue;
153
154
  }
154
155
  // Backslash command. Capture the entire backslash word as one token.
155
- if (ch === '\\') {
156
+ if (ch === "\\") {
156
157
  const start = i;
157
158
  i++;
158
159
  // Single-char commands like `\!` and `\?` are valid; otherwise read
@@ -168,7 +169,7 @@ export const tokenize = (input) => {
168
169
  // that key on `prevWords.length === 1` still fire. The
169
170
  // `S` (system-objects) suffix is already a letter, so it's consumed
170
171
  // above; only `+` needs explicit handling here.
171
- while (i < input.length && input[i] === '+')
172
+ while (i < input.length && input[i] === "+")
172
173
  i++;
173
174
  }
174
175
  out.push({
@@ -208,7 +209,7 @@ export const tokenize = (input) => {
208
209
  const start = i;
209
210
  i++;
210
211
  while (i < input.length) {
211
- if (input[i] === '\\' && i + 1 < input.length) {
212
+ if (input[i] === "\\" && i + 1 < input.length) {
212
213
  i += 2;
213
214
  continue;
214
215
  }
@@ -231,12 +232,12 @@ export const tokenize = (input) => {
231
232
  continue;
232
233
  }
233
234
  // Punctuation that splits words.
234
- if (ch === ',' ||
235
- ch === ';' ||
236
- ch === '(' ||
237
- ch === ')' ||
238
- ch === '[' ||
239
- ch === ']') {
235
+ if (ch === "," ||
236
+ ch === ";" ||
237
+ ch === "(" ||
238
+ ch === ")" ||
239
+ ch === "[" ||
240
+ ch === "]") {
240
241
  out.push({ text: ch, raw: ch, start: i, end: i + 1 });
241
242
  i++;
242
243
  continue;
@@ -246,19 +247,19 @@ export const tokenize = (input) => {
246
247
  const start = i;
247
248
  while (i < input.length) {
248
249
  const c = input[i];
249
- if (c === ' ' ||
250
- c === '\t' ||
251
- c === '\n' ||
252
- c === '\r' ||
253
- c === ',' ||
254
- c === ';' ||
255
- c === '(' ||
256
- c === ')' ||
257
- c === '[' ||
258
- c === ']' ||
250
+ if (c === " " ||
251
+ c === "\t" ||
252
+ c === "\n" ||
253
+ c === "\r" ||
254
+ c === "," ||
255
+ c === ";" ||
256
+ c === "(" ||
257
+ c === ")" ||
258
+ c === "[" ||
259
+ c === "]" ||
259
260
  c === '"' ||
260
261
  c === "'" ||
261
- c === '\\') {
262
+ c === "\\") {
262
263
  break;
263
264
  }
264
265
  i++;
@@ -289,16 +290,16 @@ export const splitForCompletion = (input, cursor) => {
289
290
  const head = input.slice(0, cursor);
290
291
  const tokens = tokenize(head);
291
292
  if (tokens.length === 0) {
292
- return { prevWords: [], currentWord: '', replaceLength: 0 };
293
+ return { prevWords: [], currentWord: "", replaceLength: 0 };
293
294
  }
294
295
  const last = tokens[tokens.length - 1];
295
296
  // The cursor is sitting inside the last token if (a) it ends exactly at
296
297
  // cursor AND (b) the last char before cursor isn't whitespace.
297
298
  const charBefore = head[head.length - 1];
298
- const inWhitespace = charBefore === ' ' ||
299
- charBefore === '\t' ||
300
- charBefore === '\n' ||
301
- charBefore === '\r';
299
+ const inWhitespace = charBefore === " " ||
300
+ charBefore === "\t" ||
301
+ charBefore === "\n" ||
302
+ charBefore === "\r";
302
303
  if (last.end === head.length && !inWhitespace) {
303
304
  return {
304
305
  prevWords: tokens.slice(0, -1).map((t) => t.text),
@@ -308,7 +309,7 @@ export const splitForCompletion = (input, cursor) => {
308
309
  }
309
310
  return {
310
311
  prevWords: tokens.map((t) => t.text),
311
- currentWord: '',
312
+ currentWord: "",
312
313
  replaceLength: 0,
313
314
  };
314
315
  };