neonctl 2.37.0 → 2.38.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 (160) hide show
  1. package/{LICENSE.md → LICENSE} +27 -3
  2. package/README.md +15 -700
  3. package/bin/cli.js +3 -0
  4. package/package.json +8 -75
  5. package/dist/analytics.js +0 -168
  6. package/dist/api.js +0 -720
  7. package/dist/auth.js +0 -125
  8. package/dist/callback.html +0 -51
  9. package/dist/cli.js +0 -9
  10. package/dist/commands/api.js +0 -278
  11. package/dist/commands/auth.js +0 -214
  12. package/dist/commands/bootstrap.js +0 -481
  13. package/dist/commands/branches.js +0 -488
  14. package/dist/commands/bucket.js +0 -549
  15. package/dist/commands/checkout.js +0 -321
  16. package/dist/commands/config.js +0 -626
  17. package/dist/commands/connection_string.js +0 -172
  18. package/dist/commands/data_api.js +0 -284
  19. package/dist/commands/databases.js +0 -82
  20. package/dist/commands/deploy.js +0 -26
  21. package/dist/commands/dev.js +0 -698
  22. package/dist/commands/diff.js +0 -222
  23. package/dist/commands/env.js +0 -189
  24. package/dist/commands/functions.js +0 -373
  25. package/dist/commands/index.js +0 -62
  26. package/dist/commands/init.js +0 -73
  27. package/dist/commands/inspect.js +0 -65
  28. package/dist/commands/ip_allow.js +0 -137
  29. package/dist/commands/link.js +0 -1121
  30. package/dist/commands/neon_auth.js +0 -1028
  31. package/dist/commands/operations.js +0 -28
  32. package/dist/commands/orgs.js +0 -24
  33. package/dist/commands/projects.js +0 -413
  34. package/dist/commands/psql.js +0 -62
  35. package/dist/commands/roles.js +0 -65
  36. package/dist/commands/schema_diff.js +0 -151
  37. package/dist/commands/set_context.js +0 -29
  38. package/dist/commands/snapshots.js +0 -455
  39. package/dist/commands/status.js +0 -40
  40. package/dist/commands/user.js +0 -15
  41. package/dist/commands/vpc_endpoints.js +0 -134
  42. package/dist/config.js +0 -11
  43. package/dist/config_format.js +0 -72
  44. package/dist/context.js +0 -231
  45. package/dist/current_branch_fast_path.js +0 -55
  46. package/dist/dev/env.js +0 -244
  47. package/dist/dev/functions.js +0 -70
  48. package/dist/dev/inputs.js +0 -63
  49. package/dist/dev/runtime.js +0 -146
  50. package/dist/env.js +0 -36
  51. package/dist/env_file.js +0 -159
  52. package/dist/errors.js +0 -80
  53. package/dist/functions_api.js +0 -48
  54. package/dist/help.js +0 -146
  55. package/dist/index.js +0 -240
  56. package/dist/log.js +0 -18
  57. package/dist/parameters.gen.js +0 -480
  58. package/dist/pkg.js +0 -25
  59. package/dist/psql/cli.js +0 -53
  60. package/dist/psql/command/cmd_cond.js +0 -437
  61. package/dist/psql/command/cmd_connect.js +0 -820
  62. package/dist/psql/command/cmd_copy.js +0 -1035
  63. package/dist/psql/command/cmd_describe.js +0 -1815
  64. package/dist/psql/command/cmd_format.js +0 -948
  65. package/dist/psql/command/cmd_io.js +0 -2193
  66. package/dist/psql/command/cmd_lo.js +0 -393
  67. package/dist/psql/command/cmd_meta.js +0 -969
  68. package/dist/psql/command/cmd_misc.js +0 -187
  69. package/dist/psql/command/cmd_pipeline.js +0 -1148
  70. package/dist/psql/command/cmd_restrict.js +0 -171
  71. package/dist/psql/command/cmd_show.js +0 -766
  72. package/dist/psql/command/dispatch.js +0 -343
  73. package/dist/psql/command/inputQueue.js +0 -42
  74. package/dist/psql/command/shared.js +0 -71
  75. package/dist/psql/complete/filenames.js +0 -139
  76. package/dist/psql/complete/index.js +0 -104
  77. package/dist/psql/complete/matcher.js +0 -315
  78. package/dist/psql/complete/psqlVars.js +0 -249
  79. package/dist/psql/complete/queries.js +0 -493
  80. package/dist/psql/complete/rules.js +0 -2424
  81. package/dist/psql/core/common.js +0 -1253
  82. package/dist/psql/core/help.js +0 -576
  83. package/dist/psql/core/mainloop.js +0 -1360
  84. package/dist/psql/core/prompt.js +0 -439
  85. package/dist/psql/core/settings.js +0 -686
  86. package/dist/psql/core/sqlHelp.js +0 -1066
  87. package/dist/psql/core/startup.js +0 -846
  88. package/dist/psql/core/syncVars.js +0 -116
  89. package/dist/psql/core/variables.js +0 -287
  90. package/dist/psql/describe/formatters.js +0 -1330
  91. package/dist/psql/describe/processNamePattern.js +0 -270
  92. package/dist/psql/describe/queries.js +0 -2452
  93. package/dist/psql/describe/versionGate.js +0 -44
  94. package/dist/psql/index.js +0 -2030
  95. package/dist/psql/io/history.js +0 -299
  96. package/dist/psql/io/input.js +0 -120
  97. package/dist/psql/io/lineEditor/buffer.js +0 -325
  98. package/dist/psql/io/lineEditor/complete.js +0 -227
  99. package/dist/psql/io/lineEditor/filename.js +0 -159
  100. package/dist/psql/io/lineEditor/index.js +0 -893
  101. package/dist/psql/io/lineEditor/keymap.js +0 -745
  102. package/dist/psql/io/lineEditor/vt100.js +0 -363
  103. package/dist/psql/io/pgpass.js +0 -202
  104. package/dist/psql/io/pgservice.js +0 -194
  105. package/dist/psql/io/psqlrc.js +0 -422
  106. package/dist/psql/print/aligned.js +0 -1748
  107. package/dist/psql/print/asciidoc.js +0 -230
  108. package/dist/psql/print/crosstab.js +0 -463
  109. package/dist/psql/print/csv.js +0 -76
  110. package/dist/psql/print/html.js +0 -240
  111. package/dist/psql/print/json.js +0 -96
  112. package/dist/psql/print/latex.js +0 -379
  113. package/dist/psql/print/pager.js +0 -267
  114. package/dist/psql/print/troff.js +0 -240
  115. package/dist/psql/print/unaligned.js +0 -99
  116. package/dist/psql/print/units.js +0 -188
  117. package/dist/psql/scanner/slash.js +0 -515
  118. package/dist/psql/scanner/sql.js +0 -914
  119. package/dist/psql/scanner/stringutils.js +0 -394
  120. package/dist/psql/types/backslash.js +0 -1
  121. package/dist/psql/types/connection.js +0 -1
  122. package/dist/psql/types/index.js +0 -7
  123. package/dist/psql/types/printer.js +0 -1
  124. package/dist/psql/types/repl.js +0 -1
  125. package/dist/psql/types/scanner.js +0 -24
  126. package/dist/psql/types/settings.js +0 -1
  127. package/dist/psql/types/variables.js +0 -1
  128. package/dist/psql/wire/connection.js +0 -2857
  129. package/dist/psql/wire/copy.js +0 -108
  130. package/dist/psql/wire/notify.js +0 -59
  131. package/dist/psql/wire/pipeline.js +0 -521
  132. package/dist/psql/wire/protocol.js +0 -466
  133. package/dist/psql/wire/sasl.js +0 -294
  134. package/dist/psql/wire/tls.js +0 -602
  135. package/dist/storage_api.js +0 -147
  136. package/dist/test_utils/fixtures.js +0 -121
  137. package/dist/test_utils/oauth_server.js +0 -9
  138. package/dist/types.js +0 -1
  139. package/dist/utils/ai_gateway_notice.js +0 -180
  140. package/dist/utils/api_enums.js +0 -33
  141. package/dist/utils/auth.js +0 -5
  142. package/dist/utils/branch_notice.js +0 -22
  143. package/dist/utils/branch_picker.js +0 -103
  144. package/dist/utils/compute_units.js +0 -28
  145. package/dist/utils/config_diff.js +0 -185
  146. package/dist/utils/enrichers.js +0 -161
  147. package/dist/utils/esbuild.js +0 -158
  148. package/dist/utils/formats.js +0 -18
  149. package/dist/utils/git_diff.js +0 -90
  150. package/dist/utils/inspect_db.js +0 -153
  151. package/dist/utils/inspect_queries.js +0 -372
  152. package/dist/utils/middlewares.js +0 -20
  153. package/dist/utils/openapi.js +0 -114
  154. package/dist/utils/package_manager.js +0 -68
  155. package/dist/utils/point_in_time.js +0 -56
  156. package/dist/utils/psql.js +0 -120
  157. package/dist/utils/string.js +0 -5
  158. package/dist/utils/ui.js +0 -59
  159. package/dist/utils/zip.js +0 -4
  160. package/dist/writer.js +0 -97
@@ -1,948 +0,0 @@
1
- /**
2
- * Formatting backslash commands.
3
- *
4
- * TypeScript port of the `exec_command_a/C/f/H/t/T/x/pset/encoding`
5
- * implementations in upstream PostgreSQL's `src/bin/psql/command.c` and
6
- * their backing `do_pset()` / `printPsetInfo()` helpers in the same file.
7
- *
8
- * All commands mutate `settings.popt.topt` (a {@link PrintTableOpts}) in
9
- * place. Several are thin wrappers over `\pset <option>` — for instance
10
- * `\a` is equivalent to `\pset format aligned|unaligned`. We keep the
11
- * separate exports so the registry can advertise them under their public
12
- * names without aliasing oddities.
13
- *
14
- * Encoding & connection coupling: `\encoding NAME` propagates the client
15
- * encoding to the live connection via {@link Connection.setClientEncoding}
16
- * (libpq's `PQsetClientEncoding`). Following upstream `do_encoding`, we
17
- * validate the name client-side first — an unrecognised name prints
18
- * `invalid encoding name "<name>"` and leaves the encoding unchanged,
19
- * never touching the connection.
20
- *
21
- * Error format: upstream uses `\\<cmd>: <message>` for diagnostics. We
22
- * mirror that exactly, writing to stderr and returning
23
- * `{ status: 'error' }`.
24
- */
25
- import { scanSlashArgs } from "../scanner/slash.js";
26
- import { parseBool, parseTriple, writeErr, writeOut } from "./shared.js";
27
- /** Recognised output-format names accepted by `\pset format`. */
28
- const OUTPUT_FORMATS = [
29
- "aligned",
30
- "unaligned",
31
- "wrapped",
32
- "html",
33
- "asciidoc",
34
- "latex",
35
- "latex-longtable",
36
- "troff-ms",
37
- "csv",
38
- "json",
39
- ];
40
- /** Convert OutputFormat to its human-readable display string. */
41
- const formatName = (f) => f;
42
- /**
43
- * Stringify a triple-state for status lines (`\x`, `\pset expanded`).
44
- * Matches upstream psql phrasing:
45
- * on → "Expanded display is on."
46
- * off → "Expanded display is off."
47
- * auto → "Expanded display is used automatically."
48
- */
49
- const tripleLabel = (value) => value === "auto" ? "used automatically" : value;
50
- /**
51
- * `\a` — toggle aligned/unaligned.
52
- *
53
- * Upstream `exec_command_a` flips the format via `do_pset("format", …)`
54
- * and, when not quiet, prints `Output format is <aligned|unaligned>.` —
55
- * the same `printPsetInfo("format")` line `\pset format` emits. We mirror
56
- * that here, gating the status write on QUIET like the sibling toggles.
57
- */
58
- export const cmdA = {
59
- name: "a",
60
- helpKey: "a",
61
- run: (ctx) => {
62
- const topt = ctx.settings.popt.topt;
63
- topt.format = topt.format === "aligned" ? "unaligned" : "aligned";
64
- if (!ctx.settings.quiet) {
65
- writeOut(`Output format is ${formatName(topt.format)}.\n`);
66
- }
67
- return Promise.resolve({ status: "ok" });
68
- },
69
- };
70
- /**
71
- * `\C [title]` — set or clear `topt.title`. No arg clears, any arg sets to
72
- * that string verbatim. Equivalent to `\pset title [value]`; upstream
73
- * `exec_command_C` dispatches via `do_pset("title", value, …)` so the
74
- * status line (`Title is "…".` / `Title is unset.`) is emitted by
75
- * `printPsetInfo`.
76
- */
77
- export const cmdC = {
78
- name: "C",
79
- helpKey: "C",
80
- run: (ctx) => {
81
- const arg = ctx.nextArg("normal");
82
- return Promise.resolve(applyPset(ctx.settings.popt.topt, "title", arg, ctx.cmdName, ctx.settings.quiet));
83
- },
84
- };
85
- /**
86
- * `\f [sep]` — set or show the unaligned field separator. With no arg, we
87
- * print the current value (upstream prints `Field separator is "%s".`).
88
- */
89
- export const cmdF = {
90
- name: "f",
91
- helpKey: "f",
92
- run: (ctx) => {
93
- const arg = ctx.nextArg("normal");
94
- if (arg === null) {
95
- writeOut(`Field separator is "${ctx.settings.popt.topt.fieldSep}".\n`);
96
- return Promise.resolve({ status: "ok" });
97
- }
98
- ctx.settings.popt.topt.fieldSep = arg;
99
- // Upstream confirms the change (quiet-gated), like the other \pset-style
100
- // setters (review: `\f SEP` is silent).
101
- if (!ctx.settings.quiet) {
102
- writeOut(`Field separator is "${arg}".\n`);
103
- }
104
- return Promise.resolve({ status: "ok" });
105
- },
106
- };
107
- /**
108
- * `\H` — toggle html on/off. If currently `html`, flip back to `aligned`;
109
- * otherwise flip to `html` (upstream remembers the prior format only
110
- * loosely — we always restore `aligned` to match the documented behaviour).
111
- */
112
- export const cmdH = {
113
- name: "H",
114
- helpKey: "H",
115
- run: (ctx) => {
116
- const topt = ctx.settings.popt.topt;
117
- topt.format = topt.format === "html" ? "aligned" : "html";
118
- return Promise.resolve({ status: "ok" });
119
- },
120
- };
121
- /**
122
- * `\t [on|off|toggle]` — tuples-only. No arg → toggle.
123
- *
124
- * Equivalent to `\pset tuples_only [value]`; upstream `exec_command_t`
125
- * dispatches via `do_pset("tuples_only", opt, …)`. The
126
- * `printPsetInfo("tuples_only")` confirmation line is only emitted when
127
- * `opt` is NULL (the toggle path) — when a value is supplied,
128
- * `do_pset` returns early via `ParseVariableBool` and skips
129
- * `printPsetInfo`, so the status line is suppressed.
130
- */
131
- export const cmdT = {
132
- name: "t",
133
- helpKey: "t",
134
- run: (ctx) => {
135
- const arg = ctx.nextArg("normal");
136
- return Promise.resolve(applyPset(ctx.settings.popt.topt, "tuples_only", arg, ctx.cmdName, ctx.settings.quiet));
137
- },
138
- };
139
- /**
140
- * `\T [attr]` — set HTML table attributes. No arg clears. Equivalent to
141
- * `\pset tableattr [value]`; upstream `exec_command_T` dispatches via
142
- * `do_pset("tableattr", value, …)` so the status line
143
- * (`Table attributes are "…".` / `Table attributes unset.`) is emitted
144
- * by `printPsetInfo`.
145
- */
146
- export const cmdTitleAttr = {
147
- name: "T",
148
- helpKey: "T",
149
- run: (ctx) => {
150
- const arg = ctx.nextArg("normal");
151
- return Promise.resolve(applyPset(ctx.settings.popt.topt, "tableattr", arg, ctx.cmdName, ctx.settings.quiet));
152
- },
153
- };
154
- /** `\x [on|off|auto|toggle]` — expanded output. No arg → toggle. */
155
- export const cmdX = {
156
- name: "x",
157
- helpKey: "x",
158
- run: (ctx) => {
159
- const arg = ctx.nextArg("normal");
160
- const topt = ctx.settings.popt.topt;
161
- let next;
162
- if (arg === null) {
163
- next = topt.expanded === "on" ? "off" : "on";
164
- }
165
- else {
166
- const parsed = parseTriple(arg);
167
- if (parsed === null) {
168
- writeErr(`\\${ctx.cmdName}: unrecognized value "${arg}": Boolean expected\n`);
169
- return Promise.resolve({ status: "error", errorWritten: true });
170
- }
171
- if (parsed === "toggle") {
172
- next = topt.expanded === "on" ? "off" : "on";
173
- }
174
- else {
175
- next = parsed;
176
- }
177
- }
178
- topt.expanded = next;
179
- writeOut(`Expanded display is ${tripleLabel(next)}.\n`);
180
- return Promise.resolve({ status: "ok" });
181
- },
182
- };
183
- /**
184
- * Canonical PostgreSQL encoding names, normalised the way upstream
185
- * `pg_char_to_encoding` (`src/common/encnames.c`) normalises before
186
- * comparing: lowercased with every `-` and `_` removed. The set covers the
187
- * full `pg_enc2name` table — both the backend-usable encodings and the
188
- * client-only ones (SJIS, BIG5, …) — because upstream `\encoding` validates
189
- * via `pg_char_to_encoding`, which accepts any recognised name and lets the
190
- * server reject genuinely unusable ones.
191
- */
192
- const NORMALISED_ENCODINGS = new Set([
193
- "sqlascii",
194
- "eucjp",
195
- "euccn",
196
- "euckr",
197
- "euctw",
198
- "eucjis2004",
199
- "utf8",
200
- "muleinternal",
201
- "latin1",
202
- "latin2",
203
- "latin3",
204
- "latin4",
205
- "latin5",
206
- "latin6",
207
- "latin7",
208
- "latin8",
209
- "latin9",
210
- "latin10",
211
- "win1256",
212
- "win1258",
213
- "win866",
214
- "win874",
215
- "koi8r",
216
- "win1251",
217
- "win1252",
218
- "iso88595",
219
- "iso88596",
220
- "iso88597",
221
- "iso88598",
222
- "win1250",
223
- "win1253",
224
- "win1254",
225
- "win1255",
226
- "win1257",
227
- "koi8u",
228
- "sjis",
229
- "big5",
230
- "gbk",
231
- "uhc",
232
- "gb18030",
233
- "johab",
234
- "shiftjis2004",
235
- // Aliases upstream's encoding_match_list accepts as recognised names.
236
- "unicode", // → UTF8
237
- "mskanji", // → SJIS
238
- "shiftjis", // → SJIS
239
- "windows949", // → UHC
240
- "windows950", // → BIG5
241
- "windows936", // → GBK
242
- "tcvn", // → WIN1258
243
- "tcvn5712", // → WIN1258
244
- "vscii", // → WIN1258
245
- "alt", // → WIN866
246
- "win", // → WIN1251
247
- "koi8", // → KOI8R
248
- "abc", // → WIN1258
249
- ]);
250
- /**
251
- * Mirror upstream `pg_char_to_encoding`'s name normalisation: drop every
252
- * `-` and `_`, lowercase, and look the result up in the canonical set.
253
- * Used to reproduce `\encoding`'s `invalid encoding name` guard without a
254
- * server round-trip.
255
- */
256
- const isValidEncodingName = (name) => NORMALISED_ENCODINGS.has(name.replace(/[-_]/g, "").toLowerCase());
257
- /**
258
- * `\encoding [name]` — show or set the client encoding.
259
- *
260
- * No arg: print the current `topt.encoding`. With an arg: validate the name
261
- * the way upstream `do_encoding` does (`pg_char_to_encoding` >= 0) — an
262
- * unrecognised name prints `\encoding: invalid encoding name "<name>"`,
263
- * leaves `topt.encoding` untouched, and never calls the connection.
264
- * Otherwise push it to the live connection via
265
- * {@link Connection.setClientEncoding} (libpq `PQsetClientEncoding`) and
266
- * mirror it into `topt.encoding` so prompts/printer see the new value.
267
- */
268
- export const cmdEncoding = {
269
- name: "encoding",
270
- helpKey: "encoding",
271
- run: async (ctx) => {
272
- const arg = ctx.nextArg("normal");
273
- if (arg === null) {
274
- writeOut(`${ctx.settings.popt.topt.encoding}\n`);
275
- return { status: "ok" };
276
- }
277
- if (!isValidEncodingName(arg)) {
278
- // Upstream `do_encoding` reports the rejected name verbatim and leaves
279
- // the current encoding in place.
280
- writeErr(`\\${ctx.cmdName}: invalid encoding name "${arg}"\n`);
281
- return { status: "error", errorWritten: true };
282
- }
283
- const { db } = ctx.settings;
284
- // `PgConnection` always implements setClientEncoding; the optional call
285
- // only short-circuits when there's no live connection (or a partial mock
286
- // in tests), in which case we still mirror the encoding into topt below.
287
- if (db?.setClientEncoding) {
288
- try {
289
- await db.setClientEncoding(arg);
290
- }
291
- catch (err) {
292
- // Server refused the SET (or the connection failed mid-flight);
293
- // surface the diagnostic and leave topt.encoding unchanged, matching
294
- // upstream's "leave the encoding as it was on failure" behaviour.
295
- const msg = err instanceof Error ? err.message : String(err);
296
- writeErr(`\\${ctx.cmdName}: ${msg}\n`);
297
- return { status: "error", errorWritten: true };
298
- }
299
- }
300
- ctx.settings.popt.topt.encoding = arg;
301
- return { status: "ok" };
302
- },
303
- };
304
- /**
305
- * The heart of `\pset`. Given a parsed `option` and optional `value`,
306
- * mutates `topt` in place and emits the upstream-style status line.
307
- *
308
- * Returns `{ status: 'error' }` and writes an error if the value is
309
- * unrecognised; `{ status: 'ok' }` otherwise.
310
- *
311
- * Wording reference: every status line is byte-matched against the
312
- * `printPsetInfo` table in upstream `src/bin/psql/command.c`. Notable
313
- * subtleties:
314
- *
315
- * - `tuples_only`, `footer`, and `numericlocale` are silenced when a
316
- * value is supplied — upstream `do_pset` returns directly out of
317
- * `ParseVariableBool`, never reaching `printPsetInfo`. The toggle
318
- * paths still print.
319
- * - `recordsep` renders the literal `\n` as the `<newline>` sentinel.
320
- * - `columns` reports `0` as `Target width is unset.`.
321
- * - `unicode_*_linestyle` uses the multi-word "line style" phrasing
322
- * even though the option name itself is a single token.
323
- * - `pager_min_lines` pluralizes via `ngettext` — singular at 1, plural
324
- * everywhere else (including 0).
325
- * - `csv_fieldsep` reports as `Field separator for CSV is "…".`.
326
- * - `xheader_width` quotes the named enum values (`"full"`/`"column"`/
327
- * `"page"`) and prints the numeric form unquoted.
328
- */
329
- export const applyPset = (topt, option, value, cmdName,
330
- // When `silent` is true, suppress the "X is now Y." status lines that
331
- // `\pset` emits on a successful set. Errors (invalid option / bad
332
- // value) still go to stderr. Used by `\g (option=value ...)` —
333
- // upstream applies the temporary overrides silently.
334
- silent = false) => {
335
- const writeOutMaybe = silent ? () => undefined : writeOut;
336
- const opt = option.toLowerCase();
337
- switch (opt) {
338
- case "format": {
339
- if (value === null) {
340
- writeOutMaybe(`Output format is ${formatName(topt.format)}.\n`);
341
- return { status: "ok" };
342
- }
343
- const v = value.toLowerCase();
344
- // Upstream `do_pset` accepts unambiguous prefix matches for the
345
- // format name via a hand-rolled cascade of `pg_strncasecmp` checks:
346
- //
347
- // 1. Special ambiguity guard for "aligned" vs "asciidoc" — any
348
- // input that is a prefix of BOTH is rejected with the
349
- // "ambiguous abbreviation" diagnostic (the only pair where
350
- // upstream cares).
351
- // 2. Otherwise pick the first OUTPUT_FORMATS entry that starts
352
- // with `v` (the order in OUTPUT_FORMATS — `latex` before
353
- // `latex-longtable` etc. — encodes which canonical name wins
354
- // when one is a prefix of another).
355
- const startsWithBoth = "aligned".startsWith(v) && "asciidoc".startsWith(v);
356
- if (startsWithBoth) {
357
- writeErr(`\\pset: ambiguous abbreviation "${value}" matches both "aligned" and "asciidoc"\n`);
358
- return { status: "error", errorWritten: true };
359
- }
360
- const match = OUTPUT_FORMATS.find((f) => f.startsWith(v));
361
- if (!match) {
362
- writeErr(`\\pset: allowed formats are aligned, asciidoc, csv, html, json, latex, latex-longtable, troff-ms, unaligned, wrapped\n`);
363
- return { status: "error", errorWritten: true };
364
- }
365
- topt.format = match;
366
- writeOutMaybe(`Output format is ${formatName(match)}.\n`);
367
- return { status: "ok" };
368
- }
369
- case "border": {
370
- if (value === null) {
371
- writeOutMaybe(`Border style is ${topt.border}.\n`);
372
- return { status: "ok" };
373
- }
374
- const n = parseInt(value, 10);
375
- if (!Number.isFinite(n) || n < 0 || n > 3) {
376
- writeErr(`\\pset: invalid border "${value}"\n`);
377
- return { status: "error", errorWritten: true };
378
- }
379
- topt.border = n;
380
- writeOutMaybe(`Border style is ${topt.border}.\n`);
381
- return { status: "ok" };
382
- }
383
- case "expanded":
384
- case "x": {
385
- if (value === null) {
386
- topt.expanded = topt.expanded === "on" ? "off" : "on";
387
- }
388
- else {
389
- const p = parseTriple(value);
390
- if (p === null) {
391
- writeErr(`\\pset: unrecognized value "${value}" for "expanded": Boolean expected\n`);
392
- return { status: "error", errorWritten: true };
393
- }
394
- topt.expanded =
395
- p === "toggle"
396
- ? topt.expanded === "on"
397
- ? "off"
398
- : "on"
399
- : p;
400
- }
401
- writeOutMaybe(`Expanded display is ${tripleLabel(topt.expanded)}.\n`);
402
- return { status: "ok" };
403
- }
404
- case "fieldsep": {
405
- if (value === null) {
406
- writeOutMaybe(`Field separator is "${topt.fieldSep}".\n`);
407
- return { status: "ok" };
408
- }
409
- topt.fieldSep = value;
410
- writeOutMaybe(`Field separator is "${topt.fieldSep}".\n`);
411
- return { status: "ok" };
412
- }
413
- case "fieldsep_zero": {
414
- // Upstream: any value (or none) forces fieldSep to the NUL byte.
415
- // The bulk-view's `fieldsep_zero` line is derived from fieldSep
416
- // (on iff fieldSep === '\0').
417
- topt.fieldSep = "\0";
418
- writeOutMaybe("Field separator is zero byte.\n");
419
- return { status: "ok" };
420
- }
421
- case "footer": {
422
- if (value !== null) {
423
- // Upstream `do_pset` returns directly from `ParseVariableBool`
424
- // for `footer`, bypassing the `printPsetInfo` call entirely
425
- // — `\pset footer on` is silent, while `\pset footer`
426
- // (toggle) still prints the new state.
427
- const b = parseBool(value);
428
- if (b === null) {
429
- writeErr(`\\pset: unrecognized value "${value}" for "footer": Boolean expected\n`);
430
- return { status: "error", errorWritten: true };
431
- }
432
- topt.defaultFooter = b;
433
- return { status: "ok" };
434
- }
435
- topt.defaultFooter = !topt.defaultFooter;
436
- writeOutMaybe(topt.defaultFooter
437
- ? "Default footer is on.\n"
438
- : "Default footer is off.\n");
439
- return { status: "ok" };
440
- }
441
- case "recordsep": {
442
- if (value !== null) {
443
- topt.recordSep = value;
444
- }
445
- // Upstream `printPsetInfo` has three branches: the separator-zero
446
- // path (handled by the dedicated `recordsep_zero` case), the
447
- // "<newline>" sentinel for the literal `\n` byte, and the quoted
448
- // verbatim form for everything else.
449
- if (topt.recordSep === "\n") {
450
- writeOutMaybe("Record separator is <newline>.\n");
451
- }
452
- else {
453
- writeOutMaybe(`Record separator is "${topt.recordSep}".\n`);
454
- }
455
- return { status: "ok" };
456
- }
457
- case "recordsep_zero": {
458
- topt.recordSep = "\0";
459
- writeOutMaybe("Record separator is zero byte.\n");
460
- return { status: "ok" };
461
- }
462
- case "tuples_only":
463
- case "t": {
464
- if (value !== null) {
465
- // Upstream `do_pset` returns directly from `ParseVariableBool`
466
- // for `tuples_only`, bypassing `printPsetInfo` — so
467
- // `\pset tuples_only on` (and the equivalent `\t on`) is
468
- // silent. The toggle path (no value) still prints.
469
- const b = parseBool(value);
470
- if (b === null) {
471
- writeErr(`\\pset: unrecognized value "${value}": Boolean expected\n`);
472
- return { status: "error", errorWritten: true };
473
- }
474
- topt.tuplesOnly = b;
475
- return { status: "ok" };
476
- }
477
- topt.tuplesOnly = !topt.tuplesOnly;
478
- writeOutMaybe(topt.tuplesOnly
479
- ? "Tuples only is on.\n"
480
- : "Tuples only is off.\n");
481
- return { status: "ok" };
482
- }
483
- case "title": {
484
- topt.title = value;
485
- if (value === null) {
486
- writeOutMaybe("Title is unset.\n");
487
- }
488
- else {
489
- writeOutMaybe(`Title is "${value}".\n`);
490
- }
491
- return { status: "ok" };
492
- }
493
- case "tableattr":
494
- case "t_a": {
495
- topt.tableAttr = value;
496
- if (value === null) {
497
- writeOutMaybe("Table attributes unset.\n");
498
- }
499
- else {
500
- writeOutMaybe(`Table attributes are "${value}".\n`);
501
- }
502
- return { status: "ok" };
503
- }
504
- case "pager": {
505
- if (value === null) {
506
- topt.pager = topt.pager === "off" ? "on" : "off";
507
- }
508
- else {
509
- const lower = value.toLowerCase();
510
- if (lower === "always") {
511
- topt.pager = "always";
512
- }
513
- else if (lower === "on" || lower === "off") {
514
- topt.pager = lower;
515
- }
516
- else {
517
- const b = parseBool(value);
518
- if (b === null) {
519
- writeErr(`\\pset: unrecognized value "${value}" for "pager"\n`);
520
- return { status: "error", errorWritten: true };
521
- }
522
- topt.pager = b ? "on" : "off";
523
- }
524
- }
525
- writeOutMaybe(topt.pager === "always"
526
- ? "Pager is always used.\n"
527
- : topt.pager === "on"
528
- ? "Pager is used for long output.\n"
529
- : "Pager usage is off.\n");
530
- return { status: "ok" };
531
- }
532
- case "pager_min_lines": {
533
- if (value !== null) {
534
- const n = parseInt(value, 10);
535
- if (!Number.isFinite(n) || n < 0) {
536
- writeErr(`\\pset: invalid pager_min_lines "${value}"\n`);
537
- return { status: "error", errorWritten: true };
538
- }
539
- topt.pagerMinLines = n;
540
- }
541
- // Upstream uses `ngettext` so singular ("line") fires only for
542
- // n == 1; 0 and 2+ render as "lines".
543
- const lines = topt.pagerMinLines;
544
- const unit = lines === 1 ? "line" : "lines";
545
- writeOutMaybe(`Pager won't be used for less than ${lines} ${unit}.\n`);
546
- return { status: "ok" };
547
- }
548
- case "null": {
549
- // Upstream guards the assignment with `if (value)`: the bare form
550
- // (`\pset null`, value === null) prints the current setting WITHOUT
551
- // resetting it. Only an explicit value (including the empty string,
552
- // `\pset null ''`) changes nullPrint.
553
- if (value !== null) {
554
- topt.nullPrint = value;
555
- }
556
- writeOutMaybe(`Null display is "${topt.nullPrint}".\n`);
557
- return { status: "ok" };
558
- }
559
- // `\pset display_true` / `\pset display_false` (PG 19+). Upstream guards
560
- // the assignment with `if (value)` — the bare form (no value) prints the
561
- // current setting WITHOUT resetting it, unlike `null`. The default when
562
- // unset is the literal 't' / 'f'.
563
- case "display_true": {
564
- if (value !== null) {
565
- topt.truePrint = value;
566
- }
567
- writeOutMaybe(`Boolean true display is "${topt.truePrint ?? "t"}".\n`);
568
- return { status: "ok" };
569
- }
570
- case "display_false": {
571
- if (value !== null) {
572
- topt.falsePrint = value;
573
- }
574
- writeOutMaybe(`Boolean false display is "${topt.falsePrint ?? "f"}".\n`);
575
- return { status: "ok" };
576
- }
577
- case "csv_fieldsep": {
578
- if (value !== null) {
579
- // Upstream `do_pset` splits the validation in two: length-based
580
- // ("must be a single one-byte character") fires for empty / multi-
581
- // char inputs *and* for the NUL byte (because in C the string is
582
- // NUL-terminated, so `'\0'` decodes to an empty C string). The
583
- // "cannot be a double quote, a newline, or a carriage return"
584
- // path is reserved for the three forbidden single-byte values.
585
- if (value.length !== 1 || value === "\0") {
586
- writeErr(`\\pset: csv_fieldsep must be a single one-byte character\n`);
587
- return { status: "error", errorWritten: true };
588
- }
589
- if (value === '"' || value === "\n" || value === "\r") {
590
- writeErr(`\\pset: csv_fieldsep cannot be a double quote, a newline, or a carriage return\n`);
591
- return { status: "error", errorWritten: true };
592
- }
593
- topt.csvFieldSep = value;
594
- }
595
- // Upstream wording: "Field separator for CSV is "%s".".
596
- writeOutMaybe(`Field separator for CSV is "${topt.csvFieldSep}".\n`);
597
- return { status: "ok" };
598
- }
599
- case "numericlocale": {
600
- if (value !== null) {
601
- // Upstream `do_pset` returns directly from `ParseVariableBool`
602
- // for `numericlocale`, bypassing `printPsetInfo`. The toggle
603
- // path (no value) still prints the new state.
604
- const p = parseTriple(value);
605
- if (p === null || p === "auto") {
606
- writeErr(`\\pset: unrecognized value "${value}" for "numericlocale": Boolean expected\n`);
607
- return { status: "error", errorWritten: true };
608
- }
609
- topt.numericLocale =
610
- p === "toggle" ? !topt.numericLocale : p === "on";
611
- return { status: "ok" };
612
- }
613
- topt.numericLocale = !topt.numericLocale;
614
- writeOutMaybe(topt.numericLocale
615
- ? "Locale-adjusted numeric output is on.\n"
616
- : "Locale-adjusted numeric output is off.\n");
617
- return { status: "ok" };
618
- }
619
- case "linestyle": {
620
- if (value === null) {
621
- writeOutMaybe(`Line style is ${topt.unicodeBorderLineStyle}.\n`);
622
- return { status: "ok" };
623
- }
624
- const lower = value.toLowerCase();
625
- // Preserve 'old-ascii' verbatim so the bulk-view (`\pset` with no
626
- // args) round-trips and the printer can pick the matching glyph
627
- // table. Upstream `do_pset("linestyle", "old-ascii", …)` flips
628
- // `popt.topt.line_style = &pg_asciiformat_old`; we carry the same
629
- // three-way distinction on `unicodeBorderLineStyle`.
630
- if (lower === "ascii" ||
631
- lower === "old-ascii" ||
632
- lower === "unicode") {
633
- const ls = lower;
634
- topt.unicodeBorderLineStyle = ls;
635
- topt.unicodeColumnLineStyle = ls;
636
- topt.unicodeHeaderLineStyle = ls;
637
- writeOutMaybe(`Line style is ${ls}.\n`);
638
- return { status: "ok" };
639
- }
640
- writeErr(`\\pset: allowed line styles are ascii, old-ascii, unicode\n`);
641
- return { status: "error", errorWritten: true };
642
- }
643
- case "columns": {
644
- if (value !== null) {
645
- const n = parseInt(value, 10);
646
- if (!Number.isFinite(n) || n < 0) {
647
- writeErr(`\\pset: invalid columns "${value}"\n`);
648
- return { status: "error", errorWritten: true };
649
- }
650
- topt.columns = n;
651
- }
652
- // Upstream `printPsetInfo` reports `0` as the special "unset"
653
- // sentinel — see `command.c:5433`.
654
- if (topt.columns === 0) {
655
- writeOutMaybe("Target width is unset.\n");
656
- }
657
- else {
658
- writeOutMaybe(`Target width is ${topt.columns}.\n`);
659
- }
660
- return { status: "ok" };
661
- }
662
- case "xheader_width": {
663
- if (value !== null) {
664
- const lower = value.toLowerCase();
665
- if (lower === "full" ||
666
- lower === "column" ||
667
- lower === "page") {
668
- topt.xheaderWidth = lower;
669
- }
670
- else {
671
- const n = parseInt(value, 10);
672
- if (!Number.isFinite(n) ||
673
- n <= 0 ||
674
- !/^[+]?\d+$/.test(value.trim())) {
675
- writeErr(`\\pset: allowed xheader_width values are "full" (default), "column", "page", or a number specifying the exact width\n`);
676
- return { status: "error", errorWritten: true };
677
- }
678
- topt.xheaderWidth = n;
679
- }
680
- }
681
- // Upstream `printPsetInfo` quotes the three named widths
682
- // ("full" / "column" / "page") but renders the numeric form
683
- // unquoted as `Expanded header width is 33.`.
684
- const current = topt.xheaderWidth ?? "full";
685
- if (typeof current === "number") {
686
- writeOutMaybe(`Expanded header width is ${current}.\n`);
687
- }
688
- else {
689
- writeOutMaybe(`Expanded header width is "${current}".\n`);
690
- }
691
- return { status: "ok" };
692
- }
693
- case "unicode_border_linestyle":
694
- case "unicode_column_linestyle":
695
- case "unicode_header_linestyle": {
696
- // Upstream `printPsetInfo` renders these as
697
- // `Unicode border line style is "single".` etc. — note the space
698
- // between "line" and "style" in the message (the option name
699
- // itself is one token, `linestyle`).
700
- const which = opt === "unicode_border_linestyle"
701
- ? "border"
702
- : opt === "unicode_column_linestyle"
703
- ? "column"
704
- : "header";
705
- if (value !== null) {
706
- const lower = value.toLowerCase();
707
- if (lower !== "single" && lower !== "double") {
708
- writeErr(`\\pset: ${opt} must be single or double\n`);
709
- return { status: "error", errorWritten: true };
710
- }
711
- const style = lower;
712
- if (opt === "unicode_border_linestyle") {
713
- topt.unicodeBorderStyle = style;
714
- }
715
- else if (opt === "unicode_column_linestyle") {
716
- topt.unicodeColumnStyle = style;
717
- }
718
- else {
719
- topt.unicodeHeaderStyle = style;
720
- }
721
- }
722
- const current = opt === "unicode_border_linestyle"
723
- ? (topt.unicodeBorderStyle ?? "single")
724
- : opt === "unicode_column_linestyle"
725
- ? (topt.unicodeColumnStyle ?? "single")
726
- : (topt.unicodeHeaderStyle ?? "single");
727
- writeOutMaybe(`Unicode ${which} line style is "${current}".\n`);
728
- return { status: "ok" };
729
- }
730
- default: {
731
- writeErr(`\\pset: unknown option "${option}"\n`);
732
- return { status: "error", errorWritten: true };
733
- }
734
- }
735
- };
736
- /**
737
- * Wrap a string value in single quotes, escaping embedded newlines and
738
- * single quotes. Mirrors upstream `pset_quoted_string` in
739
- * `src/bin/psql/command.c` — used by the bulk-view formatter so the
740
- * emitted line can be fed back into `\pset NAME VALUE`.
741
- */
742
- const psetQuotedString = (str) => {
743
- let out = "'";
744
- for (const ch of str) {
745
- if (ch === "\n")
746
- out += "\\n";
747
- else if (ch === "'")
748
- out += "\\'";
749
- else
750
- out += ch;
751
- }
752
- out += "'";
753
- return out;
754
- };
755
- /**
756
- * Render the numeric pager encoding upstream uses in `printPsetInfo`:
757
- * 0 = never, 1 = "if needed" (our `'on'`), 2 = always. We keep
758
- * `topt.pager` as the upstream-style triple ('off'|'on'|'always') for
759
- * `applyPset`'s state machine; this is only the bulk-view conversion.
760
- */
761
- const pagerNumeric = (pager) => pager === "off" ? 0 : pager === "on" ? 1 : 2;
762
- /**
763
- * Render `xheader_width` for the bulk view. Enum values print verbatim;
764
- * numeric values print as the integer.
765
- */
766
- const xheaderWidthDisplay = (w) => (typeof w === "number" ? String(w) : w);
767
- /**
768
- * Print the full current `\pset` state, one option per line, to stdout.
769
- * Used when `\pset` is invoked with no arguments. String-valued settings
770
- * are single-quoted (matching upstream `pset_value_string`); `tableattr`
771
- * and `title` are unquoted-empty when unset. The set, ordering, and
772
- * column-spacing mirror `printPsetInfo` in `src/bin/psql/command.c`.
773
- */
774
- const printAllPset = (topt) => {
775
- writeOut(`border ${topt.border}\n`);
776
- writeOut(`columns ${topt.columns}\n`);
777
- writeOut(`csv_fieldsep ${psetQuotedString(topt.csvFieldSep)}\n`);
778
- writeOut(`display_false ${psetQuotedString(topt.falsePrint ?? "f")}\n`);
779
- writeOut(`display_true ${psetQuotedString(topt.truePrint ?? "t")}\n`);
780
- writeOut(`expanded ${topt.expanded}\n`);
781
- writeOut(`fieldsep ${psetQuotedString(topt.fieldSep)}\n`);
782
- // fieldsep_zero / recordsep_zero are derived: upstream emits "on" iff
783
- // the corresponding separator is the NUL byte.
784
- writeOut(`fieldsep_zero ${topt.fieldSep === "\0" ? "on" : "off"}\n`);
785
- writeOut(`footer ${topt.defaultFooter ? "on" : "off"}\n`);
786
- writeOut(`format ${topt.format}\n`);
787
- writeOut(`linestyle ${topt.unicodeBorderLineStyle}\n`);
788
- writeOut(`null ${psetQuotedString(topt.nullPrint)}\n`);
789
- writeOut(`numericlocale ${topt.numericLocale ? "on" : "off"}\n`);
790
- // pager is emitted numerically (0/1/2) — upstream uses %d in printPsetInfo.
791
- writeOut(`pager ${pagerNumeric(topt.pager)}\n`);
792
- writeOut(`pager_min_lines ${topt.pagerMinLines}\n`);
793
- writeOut(`recordsep ${psetQuotedString(topt.recordSep)}\n`);
794
- writeOut(`recordsep_zero ${topt.recordSep === "\0" ? "on" : "off"}\n`);
795
- writeOut(`tableattr ${topt.tableAttr === null ? "" : psetQuotedString(topt.tableAttr)}\n`);
796
- writeOut(`title ${topt.title === null ? "" : psetQuotedString(topt.title)}\n`);
797
- writeOut(`tuples_only ${topt.tuplesOnly ? "on" : "off"}\n`);
798
- writeOut(`unicode_border_linestyle ${topt.unicodeBorderStyle ?? "single"}\n`);
799
- writeOut(`unicode_column_linestyle ${topt.unicodeColumnStyle ?? "single"}\n`);
800
- writeOut(`unicode_header_linestyle ${topt.unicodeHeaderStyle ?? "single"}\n`);
801
- writeOut(`xheader_width ${xheaderWidthDisplay(topt.xheaderWidth ?? "full")}\n`);
802
- };
803
- const lexRawArgs = (tail) => {
804
- const out = [];
805
- let i = 0;
806
- const isSpace = (c) => c === " " ||
807
- c === "\t" ||
808
- c === "\n" ||
809
- c === "\r" ||
810
- c === "\f" ||
811
- c === "\v";
812
- while (i < tail.length) {
813
- while (i < tail.length && isSpace(tail[i]))
814
- i++;
815
- if (i >= tail.length)
816
- break;
817
- if (tail[i] === "\\")
818
- break;
819
- let arg = "";
820
- while (i < tail.length && !isSpace(tail[i]) && tail[i] !== "\\") {
821
- const c = tail[i];
822
- if (c === "'") {
823
- // Single-quoted: keep delimiters in the warning text so the user
824
- // sees the literal token.
825
- let j = i + 1;
826
- let inner = "";
827
- while (j < tail.length && tail[j] !== "'") {
828
- if (tail[j] === "\\" && j + 1 < tail.length) {
829
- inner += tail[j] + tail[j + 1];
830
- j += 2;
831
- continue;
832
- }
833
- inner += tail[j];
834
- j++;
835
- }
836
- arg += `'${inner}'`;
837
- i = j < tail.length ? j + 1 : j;
838
- continue;
839
- }
840
- if (c === '"') {
841
- let j = i + 1;
842
- let inner = "";
843
- while (j < tail.length && tail[j] !== '"') {
844
- inner += tail[j];
845
- j++;
846
- }
847
- arg += `"${inner}"`;
848
- i = j < tail.length ? j + 1 : j;
849
- continue;
850
- }
851
- if (c === "`") {
852
- // Drop the backtick delimiters; don't run the command (OT_NO_EVAL).
853
- let j = i + 1;
854
- let inner = "";
855
- while (j < tail.length && tail[j] !== "`") {
856
- inner += tail[j];
857
- j++;
858
- }
859
- arg += inner;
860
- i = j < tail.length ? j + 1 : j;
861
- continue;
862
- }
863
- arg += c;
864
- i++;
865
- }
866
- out.push({ arg, endIdx: i });
867
- }
868
- return out;
869
- };
870
- /**
871
- * `\pset [option [value]]` — the master print-options setter.
872
- *
873
- * - No args: print all options.
874
- * - Option only: toggle (for booleans) or show current value.
875
- * - Option + value: set.
876
- * - Option + value + extra: set, then emit one `extra argument "X" ignored`
877
- * per extra arg (matches upstream `exec_command_pset`'s post-`do_pset`
878
- * call to `ignore_slash_options`).
879
- *
880
- * Implementation note: the surrounding REPL's `BackslashContext` (built
881
- * in `core/mainloop.ts::makeBackslashContext`) returns the full
882
- * `rawArgs` verbatim from `restOfLine()` — there's no cursor we can
883
- * read to find the unconsumed tail. We therefore lex `rawArgs` a second
884
- * time with {@link lexExtraArgs} to recover the *raw* (unexpanded)
885
- * token text, while still calling `ctx.nextArg('normal')` for
886
- * option/value so variable substitution and backtick execution behave
887
- * exactly like the rest of the slash-command layer. The drain pulls
888
- * the raw token text from index 2+ so the warning preserves the
889
- * upstream `OT_NO_EVAL` semantics — `:foo` stays as `:foo`, `` `cmd` ``
890
- * collapses to `cmd` without running.
891
- */
892
- export const cmdPset = {
893
- name: "pset",
894
- helpKey: "pset",
895
- run: (ctx) => {
896
- // Upstream `exec_command_pset` calls `psql_scan_slash_option` twice
897
- // (with `OT_NORMAL`, `evaluate=true`) for option + value, then loops
898
- // `psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false)` with
899
- // `evaluate=false` over the remainder to emit the "extra argument …
900
- // ignored" warnings. Our `BackslashContext.nextArg('normal')` route
901
- // pre-parses the entire `rawArgs` through `scanSlashArgs` on the
902
- // first call, which evaluates EVERY backtick — so an invocation
903
- // like `\pset fieldsep | \`nosuchcommand\` :foo` would spawn the
904
- // shell for `nosuchcommand` even though upstream never runs it.
905
- //
906
- // To match upstream's no-eval semantics we split the lex in two:
907
- // 1. Walk `rawArgs` no-eval with {@link lexRawArgs} to recover the
908
- // byte boundary that ends arg #2 (option + value).
909
- // 2. Re-parse the truncated prefix with `scanSlashArgs('normal')`
910
- // so option/value get their proper `:var` / `` ` ` `` expansion.
911
- // 3. Walk the tail with `lexRawArgs` no-eval and emit one
912
- // "extra argument … ignored" warning per token, using the raw
913
- // (unexpanded) text so `:foo` stays `:foo` and `` `cmd` ``
914
- // collapses to `cmd` without running.
915
- const rawEntries = lexRawArgs(ctx.rawArgs);
916
- if (rawEntries.length === 0) {
917
- printAllPset(ctx.settings.popt.topt);
918
- return Promise.resolve({ status: "ok" });
919
- }
920
- // Slice rawArgs up to the end of arg #2 (or arg #1 if only one was
921
- // provided) and feed THAT to the eval-mode scanner. Anything past
922
- // that boundary lives in the no-eval extras zone.
923
- const headEndIdx = rawEntries.length >= 2
924
- ? rawEntries[1].endIdx
925
- : rawEntries[0].endIdx;
926
- const headSlice = ctx.rawArgs.slice(0, headEndIdx);
927
- const varLookup = (name) => ctx.settings.vars.get(name);
928
- const headArgs = scanSlashArgs(headSlice, "normal", varLookup);
929
- const option = headArgs[0] ?? null;
930
- if (option === null) {
931
- printAllPset(ctx.settings.popt.topt);
932
- return Promise.resolve({ status: "ok" });
933
- }
934
- const value = headArgs[1] ?? null;
935
- // Under `--quiet` / `\set QUIET on`, upstream `exec_command_pset`
936
- // (and the printPsetInfo helper it delegates to) suppresses the
937
- // confirmation lines like `Null display is "…".` and `Tuples only
938
- // is on.`. Pass `silent=true` so applyPset skips the writes —
939
- // errors (invalid option / bad value) still go to stderr.
940
- const result = applyPset(ctx.settings.popt.topt, option, value, ctx.cmdName, ctx.settings.quiet);
941
- // Drain extras. Index 2+ in the no-eval lex are the tokens past
942
- // option+value; emit one warning per raw token.
943
- for (let i = 2; i < rawEntries.length; i++) {
944
- writeErr(`\\${ctx.cmdName}: extra argument "${rawEntries[i].arg}" ignored\n`);
945
- }
946
- return Promise.resolve(result);
947
- },
948
- };