neonctl 2.28.0 → 2.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +2 -2
  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 +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 +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
@@ -10,8 +10,8 @@
10
10
  * mode). Keeping dispatch separate from rendering means we can write
11
11
  * deterministic key-by-key tests without touching a TTY.
12
12
  */
13
- import { LineBuffer } from './buffer.js';
14
- export const makeState = (history = [], mode = 'emacs') => ({
13
+ import { LineBuffer } from "./buffer.js";
14
+ export const makeState = (history = [], mode = "emacs") => ({
15
15
  buffer: new LineBuffer(),
16
16
  history: [...history],
17
17
  historyIndex: -1,
@@ -20,7 +20,7 @@ export const makeState = (history = [], mode = 'emacs') => ({
20
20
  pasting: false,
21
21
  mode,
22
22
  viPending: null,
23
- exBuffer: '',
23
+ exBuffer: "",
24
24
  });
25
25
  /**
26
26
  * Apply one key event. Returns the resulting action for the outer loop.
@@ -30,193 +30,200 @@ export const dispatch = (state, ev) => {
30
30
  if (state.pasting) {
31
31
  // Inside a bracketed-paste block we treat every event as a literal char,
32
32
  // except the closing marker.
33
- if (ev.key === 'paste-end') {
33
+ if (ev.key === "paste-end") {
34
34
  state.pasting = false;
35
- return { kind: 'paste-end' };
35
+ return { kind: "paste-end" };
36
36
  }
37
- if (ev.key === 'char' && ev.char !== undefined && !ev.ctrl && !ev.meta) {
37
+ if (ev.key === "char" &&
38
+ ev.char !== undefined &&
39
+ !ev.ctrl &&
40
+ !ev.meta) {
38
41
  state.buffer.insert(ev.char);
39
- return { kind: 'redraw' };
42
+ return { kind: "redraw" };
40
43
  }
41
- if (ev.key === 'enter') {
42
- state.buffer.insert('\n');
43
- return { kind: 'redraw' };
44
+ if (ev.key === "enter") {
45
+ state.buffer.insert("\n");
46
+ return { kind: "redraw" };
44
47
  }
45
- if (ev.key === 'tab') {
46
- state.buffer.insert('\t');
47
- return { kind: 'redraw' };
48
+ if (ev.key === "tab") {
49
+ state.buffer.insert("\t");
50
+ return { kind: "redraw" };
48
51
  }
49
- return { kind: 'noop' };
52
+ return { kind: "noop" };
50
53
  }
51
54
  // ^C always cancels regardless of mode.
52
- if (ev.key === 'char' && ev.ctrl && ev.char === 'c') {
55
+ if (ev.key === "char" && ev.ctrl && ev.char === "c") {
53
56
  state.viPending = null;
54
- return { kind: 'cancel' };
57
+ return { kind: "cancel" };
55
58
  }
56
59
  // Route to vi dispatch when in vi modes.
57
- if (state.mode === 'ex') {
60
+ if (state.mode === "ex") {
58
61
  return dispatchViEx(state, ev);
59
62
  }
60
- if (state.mode === 'normal') {
63
+ if (state.mode === "normal") {
61
64
  return dispatchViNormal(state, ev);
62
65
  }
63
- if (state.mode === 'insert') {
66
+ if (state.mode === "insert") {
64
67
  return dispatchViInsert(state, ev);
65
68
  }
66
69
  switch (ev.key) {
67
- case 'paste-start':
70
+ case "paste-start":
68
71
  state.pasting = true;
69
- return { kind: 'paste-start' };
70
- case 'paste-end':
72
+ return { kind: "paste-start" };
73
+ case "paste-end":
71
74
  state.pasting = false;
72
- return { kind: 'paste-end' };
73
- case 'enter':
74
- return { kind: 'submit' };
75
- case 'tab':
76
- return { kind: 'complete' };
77
- case 'backspace':
75
+ return { kind: "paste-end" };
76
+ case "enter":
77
+ return { kind: "submit" };
78
+ case "tab":
79
+ return { kind: "complete" };
80
+ case "backspace":
78
81
  if (ev.meta) {
79
82
  state.buffer.killWordLeft();
80
83
  }
81
84
  else {
82
85
  state.buffer.deleteLeft();
83
86
  }
84
- return { kind: 'redraw' };
85
- case 'delete':
87
+ return { kind: "redraw" };
88
+ case "delete":
86
89
  state.buffer.deleteRight();
87
- return { kind: 'redraw' };
88
- case 'left':
90
+ return { kind: "redraw" };
91
+ case "left":
89
92
  if (ev.meta)
90
93
  state.buffer.moveWordLeft();
91
94
  else
92
95
  state.buffer.moveLeft();
93
- return { kind: 'redraw' };
94
- case 'right':
96
+ return { kind: "redraw" };
97
+ case "right":
95
98
  if (ev.meta)
96
99
  state.buffer.moveWordRight();
97
100
  else
98
101
  state.buffer.moveRight();
99
- return { kind: 'redraw' };
100
- case 'up':
102
+ return { kind: "redraw" };
103
+ case "up":
101
104
  return navigateHistory(state, -1);
102
- case 'down':
105
+ case "down":
103
106
  return navigateHistory(state, +1);
104
- case 'home':
107
+ case "home":
105
108
  state.buffer.moveHome();
106
- return { kind: 'redraw' };
107
- case 'end':
109
+ return { kind: "redraw" };
110
+ case "end":
108
111
  state.buffer.moveEnd();
109
- return { kind: 'redraw' };
110
- case 'escape':
112
+ return { kind: "redraw" };
113
+ case "escape":
111
114
  // Bare Escape: ignore (Alt prefixes are decoded into meta:true).
112
- return { kind: 'noop' };
113
- case 'char':
115
+ return { kind: "noop" };
116
+ case "char":
114
117
  return handleChar(state, ev);
115
- case 'pageup':
116
- case 'pagedown':
117
- case 'unknown':
118
- return { kind: 'bell' };
118
+ case "pageup":
119
+ case "pagedown":
120
+ case "unknown":
121
+ return { kind: "bell" };
119
122
  }
120
123
  };
121
124
  const handleChar = (state, ev) => {
122
- const ch = ev.char ?? '';
125
+ const ch = ev.char ?? "";
123
126
  if (ch.length === 0)
124
- return { kind: 'noop' };
127
+ return { kind: "noop" };
125
128
  if (ev.ctrl) {
126
129
  switch (ch) {
127
- case 'a':
130
+ case "a":
128
131
  state.buffer.moveHome();
129
- return { kind: 'redraw' };
130
- case 'e':
132
+ return { kind: "redraw" };
133
+ case "e":
131
134
  state.buffer.moveEnd();
132
- return { kind: 'redraw' };
133
- case 'b':
135
+ return { kind: "redraw" };
136
+ case "b":
134
137
  state.buffer.moveLeft();
135
- return { kind: 'redraw' };
136
- case 'f':
138
+ return { kind: "redraw" };
139
+ case "f":
137
140
  state.buffer.moveRight();
138
- return { kind: 'redraw' };
139
- case 'p':
141
+ return { kind: "redraw" };
142
+ case "p":
140
143
  return navigateHistory(state, -1);
141
- case 'n':
144
+ case "n":
142
145
  return navigateHistory(state, +1);
143
- case 'k':
146
+ case "k":
144
147
  state.buffer.killToEnd();
145
- return { kind: 'redraw' };
146
- case 'u':
148
+ return { kind: "redraw" };
149
+ case "u":
147
150
  state.buffer.killToStart();
148
- return { kind: 'redraw' };
149
- case 'w':
151
+ return { kind: "redraw" };
152
+ case "w":
150
153
  state.buffer.killWordLeft();
151
- return { kind: 'redraw' };
152
- case 'y': {
154
+ return { kind: "redraw" };
155
+ case "y": {
153
156
  const yanked = state.buffer.yank();
154
157
  state.lastYank = yanked ?? null;
155
- return yanked === undefined ? { kind: 'bell' } : { kind: 'redraw' };
158
+ return yanked === undefined
159
+ ? { kind: "bell" }
160
+ : { kind: "redraw" };
156
161
  }
157
- case 'c':
158
- return { kind: 'cancel' };
159
- case 'd':
162
+ case "c":
163
+ return { kind: "cancel" };
164
+ case "d":
160
165
  if (state.buffer.length === 0)
161
- return { kind: 'eof' };
166
+ return { kind: "eof" };
162
167
  state.buffer.deleteRight();
163
- return { kind: 'redraw' };
164
- case 'l':
165
- return { kind: 'clear-screen' };
166
- case 'h':
168
+ return { kind: "redraw" };
169
+ case "l":
170
+ return { kind: "clear-screen" };
171
+ case "h":
167
172
  state.buffer.deleteLeft();
168
- return { kind: 'redraw' };
169
- case 'r':
170
- return { kind: 'search-start' };
171
- case 't': {
173
+ return { kind: "redraw" };
174
+ case "r":
175
+ return { kind: "search-start" };
176
+ case "t": {
172
177
  // Transpose two chars before cursor. Edge cases per readline:
173
178
  // - at end-of-line, transpose the two chars before cursor
174
179
  // - at the very start with <2 chars, bell
175
180
  transpose(state.buffer);
176
- return { kind: 'redraw' };
181
+ return { kind: "redraw" };
177
182
  }
178
- case '_':
179
- case '/': // some terminals send ^/ as 0x1f
180
- return state.buffer.undo() ? { kind: 'redraw' } : { kind: 'bell' };
181
- case 'g':
183
+ case "_":
184
+ case "/": // some terminals send ^/ as 0x1f
185
+ return state.buffer.undo()
186
+ ? { kind: "redraw" }
187
+ : { kind: "bell" };
188
+ case "g":
182
189
  // ^G outside of search is a no-op bell.
183
- return { kind: 'bell' };
190
+ return { kind: "bell" };
184
191
  default:
185
- return { kind: 'bell' };
192
+ return { kind: "bell" };
186
193
  }
187
194
  }
188
195
  if (ev.meta) {
189
196
  switch (ch) {
190
- case 'b':
191
- case 'B':
197
+ case "b":
198
+ case "B":
192
199
  state.buffer.moveWordLeft();
193
- return { kind: 'redraw' };
194
- case 'f':
195
- case 'F':
200
+ return { kind: "redraw" };
201
+ case "f":
202
+ case "F":
196
203
  state.buffer.moveWordRight();
197
- return { kind: 'redraw' };
198
- case 'd':
199
- case 'D':
204
+ return { kind: "redraw" };
205
+ case "d":
206
+ case "D":
200
207
  state.buffer.killWordRight();
201
- return { kind: 'redraw' };
202
- case 'y':
203
- case 'Y':
208
+ return { kind: "redraw" };
209
+ case "y":
210
+ case "Y":
204
211
  if (state.lastYank === null)
205
- return { kind: 'bell' };
212
+ return { kind: "bell" };
206
213
  {
207
214
  const next = state.buffer.yankPop(state.lastYank);
208
215
  if (next === undefined)
209
- return { kind: 'bell' };
216
+ return { kind: "bell" };
210
217
  state.lastYank = next;
211
218
  }
212
- return { kind: 'redraw' };
219
+ return { kind: "redraw" };
213
220
  default:
214
- return { kind: 'bell' };
221
+ return { kind: "bell" };
215
222
  }
216
223
  }
217
224
  // Plain printable.
218
225
  state.buffer.insert(ch);
219
- return { kind: 'redraw' };
226
+ return { kind: "redraw" };
220
227
  };
221
228
  /**
222
229
  * Move history index by delta and load the corresponding entry. Saves
@@ -224,29 +231,29 @@ const handleChar = (state, ev) => {
224
231
  */
225
232
  const navigateHistory = (state, delta) => {
226
233
  if (state.history.length === 0)
227
- return { kind: 'bell' };
234
+ return { kind: "bell" };
228
235
  if (state.historyIndex === -1) {
229
236
  if (delta < 0) {
230
237
  state.liveSnapshot = state.buffer.text;
231
238
  state.historyIndex = state.history.length - 1;
232
239
  state.buffer.setText(state.history[state.historyIndex]);
233
- return { kind: 'redraw' };
240
+ return { kind: "redraw" };
234
241
  }
235
- return { kind: 'bell' };
242
+ return { kind: "bell" };
236
243
  }
237
244
  const next = state.historyIndex + delta;
238
245
  if (next < 0)
239
- return { kind: 'bell' };
246
+ return { kind: "bell" };
240
247
  if (next >= state.history.length) {
241
248
  // Stepped past newest: restore the live snapshot.
242
249
  state.historyIndex = -1;
243
- state.buffer.setText(state.liveSnapshot ?? '');
250
+ state.buffer.setText(state.liveSnapshot ?? "");
244
251
  state.liveSnapshot = null;
245
- return { kind: 'redraw' };
252
+ return { kind: "redraw" };
246
253
  }
247
254
  state.historyIndex = next;
248
255
  state.buffer.setText(state.history[next]);
249
- return { kind: 'redraw' };
256
+ return { kind: "redraw" };
250
257
  };
251
258
  // ---------------------------------------------------------------------------
252
259
  // vi mode
@@ -259,64 +266,64 @@ const navigateHistory = (state, delta) => {
259
266
  */
260
267
  const dispatchViInsert = (state, ev) => {
261
268
  switch (ev.key) {
262
- case 'paste-start':
269
+ case "paste-start":
263
270
  state.pasting = true;
264
- return { kind: 'paste-start' };
265
- case 'paste-end':
271
+ return { kind: "paste-start" };
272
+ case "paste-end":
266
273
  state.pasting = false;
267
- return { kind: 'paste-end' };
268
- case 'enter':
269
- return { kind: 'submit' };
270
- case 'tab':
271
- return { kind: 'complete' };
272
- case 'escape':
274
+ return { kind: "paste-end" };
275
+ case "enter":
276
+ return { kind: "submit" };
277
+ case "tab":
278
+ return { kind: "complete" };
279
+ case "escape":
273
280
  // Leave insert mode; vi convention: cursor steps left so it sits on the
274
281
  // last inserted char (unless we were already at column 0).
275
- state.mode = 'normal';
282
+ state.mode = "normal";
276
283
  state.viPending = null;
277
284
  if (state.buffer.cursor > 0)
278
285
  state.buffer.moveLeft();
279
- return { kind: 'redraw' };
280
- case 'backspace':
286
+ return { kind: "redraw" };
287
+ case "backspace":
281
288
  state.buffer.deleteLeft();
282
- return { kind: 'redraw' };
283
- case 'delete':
289
+ return { kind: "redraw" };
290
+ case "delete":
284
291
  state.buffer.deleteRight();
285
- return { kind: 'redraw' };
286
- case 'left':
292
+ return { kind: "redraw" };
293
+ case "left":
287
294
  state.buffer.moveLeft();
288
- return { kind: 'redraw' };
289
- case 'right':
295
+ return { kind: "redraw" };
296
+ case "right":
290
297
  state.buffer.moveRight();
291
- return { kind: 'redraw' };
292
- case 'up':
298
+ return { kind: "redraw" };
299
+ case "up":
293
300
  return navigateHistory(state, -1);
294
- case 'down':
301
+ case "down":
295
302
  return navigateHistory(state, +1);
296
- case 'home':
303
+ case "home":
297
304
  state.buffer.moveHome();
298
- return { kind: 'redraw' };
299
- case 'end':
305
+ return { kind: "redraw" };
306
+ case "end":
300
307
  state.buffer.moveEnd();
301
- return { kind: 'redraw' };
302
- case 'char': {
303
- const ch = ev.char ?? '';
308
+ return { kind: "redraw" };
309
+ case "char": {
310
+ const ch = ev.char ?? "";
304
311
  if (ch.length === 0)
305
- return { kind: 'noop' };
312
+ return { kind: "noop" };
306
313
  // ^D on empty buffer still acts as EOF in either vi mode.
307
- if (ev.ctrl && ch === 'd' && state.buffer.length === 0) {
308
- return { kind: 'eof' };
314
+ if (ev.ctrl && ch === "d" && state.buffer.length === 0) {
315
+ return { kind: "eof" };
309
316
  }
310
317
  // Ignore other control combos in vi insert; just insert plain printables.
311
318
  if (ev.ctrl || ev.meta)
312
- return { kind: 'noop' };
319
+ return { kind: "noop" };
313
320
  state.buffer.insert(ch);
314
- return { kind: 'redraw' };
321
+ return { kind: "redraw" };
315
322
  }
316
- case 'pageup':
317
- case 'pagedown':
318
- case 'unknown':
319
- return { kind: 'bell' };
323
+ case "pageup":
324
+ case "pagedown":
325
+ case "unknown":
326
+ return { kind: "bell" };
320
327
  }
321
328
  };
322
329
  /**
@@ -331,147 +338,147 @@ const dispatchViNormal = (state, ev) => {
331
338
  return continueViPending(state, ev);
332
339
  }
333
340
  switch (ev.key) {
334
- case 'enter':
335
- return { kind: 'submit' };
336
- case 'tab':
341
+ case "enter":
342
+ return { kind: "submit" };
343
+ case "tab":
337
344
  // No completion in normal mode (matches vim/readline-vi).
338
- return { kind: 'bell' };
339
- case 'escape':
345
+ return { kind: "bell" };
346
+ case "escape":
340
347
  // Already normal; clear any half-formed operator.
341
348
  state.viPending = null;
342
- return { kind: 'noop' };
343
- case 'backspace':
349
+ return { kind: "noop" };
350
+ case "backspace":
344
351
  // In normal mode bare backspace is "move left" per readline-vi.
345
352
  state.buffer.moveLeft();
346
- return { kind: 'redraw' };
347
- case 'delete':
353
+ return { kind: "redraw" };
354
+ case "delete":
348
355
  state.buffer.deleteRight();
349
- return { kind: 'redraw' };
350
- case 'left':
356
+ return { kind: "redraw" };
357
+ case "left":
351
358
  state.buffer.moveLeft();
352
- return { kind: 'redraw' };
353
- case 'right':
359
+ return { kind: "redraw" };
360
+ case "right":
354
361
  state.buffer.moveRight();
355
- return { kind: 'redraw' };
356
- case 'up':
362
+ return { kind: "redraw" };
363
+ case "up":
357
364
  return navigateHistory(state, -1);
358
- case 'down':
365
+ case "down":
359
366
  return navigateHistory(state, +1);
360
- case 'home':
367
+ case "home":
361
368
  state.buffer.moveHome();
362
- return { kind: 'redraw' };
363
- case 'end':
369
+ return { kind: "redraw" };
370
+ case "end":
364
371
  state.buffer.moveEnd();
365
- return { kind: 'redraw' };
366
- case 'char':
372
+ return { kind: "redraw" };
373
+ case "char":
367
374
  return handleViNormalChar(state, ev);
368
- case 'paste-start':
375
+ case "paste-start":
369
376
  state.pasting = true;
370
- return { kind: 'paste-start' };
371
- case 'paste-end':
377
+ return { kind: "paste-start" };
378
+ case "paste-end":
372
379
  state.pasting = false;
373
- return { kind: 'paste-end' };
374
- case 'pageup':
375
- case 'pagedown':
376
- case 'unknown':
377
- return { kind: 'bell' };
380
+ return { kind: "paste-end" };
381
+ case "pageup":
382
+ case "pagedown":
383
+ case "unknown":
384
+ return { kind: "bell" };
378
385
  }
379
386
  };
380
387
  const handleViNormalChar = (state, ev) => {
381
- const ch = ev.char ?? '';
388
+ const ch = ev.char ?? "";
382
389
  if (ch.length === 0)
383
- return { kind: 'noop' };
390
+ return { kind: "noop" };
384
391
  // ^D on empty buffer is EOF in vi normal mode too.
385
- if (ev.ctrl && ch === 'd' && state.buffer.length === 0) {
386
- return { kind: 'eof' };
392
+ if (ev.ctrl && ch === "d" && state.buffer.length === 0) {
393
+ return { kind: "eof" };
387
394
  }
388
395
  // Other ctrl/meta sequences: not bound in normal mode → bell.
389
396
  if (ev.ctrl || ev.meta)
390
- return { kind: 'bell' };
397
+ return { kind: "bell" };
391
398
  switch (ch) {
392
399
  // Movement
393
- case 'h':
400
+ case "h":
394
401
  state.buffer.moveLeft();
395
- return { kind: 'redraw' };
396
- case 'l':
402
+ return { kind: "redraw" };
403
+ case "l":
397
404
  state.buffer.moveRight();
398
- return { kind: 'redraw' };
399
- case 'b':
405
+ return { kind: "redraw" };
406
+ case "b":
400
407
  state.buffer.moveWordLeft();
401
- return { kind: 'redraw' };
402
- case 'w':
408
+ return { kind: "redraw" };
409
+ case "w":
403
410
  state.buffer.moveWordRight();
404
- return { kind: 'redraw' };
405
- case 'e':
411
+ return { kind: "redraw" };
412
+ case "e":
406
413
  viMoveEndOfWord(state.buffer);
407
- return { kind: 'redraw' };
408
- case '0':
414
+ return { kind: "redraw" };
415
+ case "0":
409
416
  state.buffer.moveHome();
410
- return { kind: 'redraw' };
411
- case '$':
417
+ return { kind: "redraw" };
418
+ case "$":
412
419
  state.buffer.moveEnd();
413
- return { kind: 'redraw' };
414
- case '^':
420
+ return { kind: "redraw" };
421
+ case "^":
415
422
  viMoveFirstNonBlank(state.buffer);
416
- return { kind: 'redraw' };
423
+ return { kind: "redraw" };
417
424
  // History (vi-style j/k).
418
- case 'j':
425
+ case "j":
419
426
  return navigateHistory(state, +1);
420
- case 'k':
427
+ case "k":
421
428
  return navigateHistory(state, -1);
422
429
  // Mode switches.
423
- case 'i':
424
- state.mode = 'insert';
425
- return { kind: 'redraw' };
426
- case 'a':
430
+ case "i":
431
+ state.mode = "insert";
432
+ return { kind: "redraw" };
433
+ case "a":
427
434
  if (state.buffer.cursor < state.buffer.length)
428
435
  state.buffer.moveRight();
429
- state.mode = 'insert';
430
- return { kind: 'redraw' };
431
- case 'I':
436
+ state.mode = "insert";
437
+ return { kind: "redraw" };
438
+ case "I":
432
439
  state.buffer.moveHome();
433
- state.mode = 'insert';
434
- return { kind: 'redraw' };
435
- case 'A':
440
+ state.mode = "insert";
441
+ return { kind: "redraw" };
442
+ case "A":
436
443
  state.buffer.moveEnd();
437
- state.mode = 'insert';
438
- return { kind: 'redraw' };
444
+ state.mode = "insert";
445
+ return { kind: "redraw" };
439
446
  // Edits.
440
- case 'x':
447
+ case "x":
441
448
  state.buffer.deleteRight();
442
- return { kind: 'redraw' };
443
- case 'X':
449
+ return { kind: "redraw" };
450
+ case "X":
444
451
  state.buffer.deleteLeft();
445
- return { kind: 'redraw' };
446
- case 'D':
452
+ return { kind: "redraw" };
453
+ case "D":
447
454
  state.buffer.killToEnd();
448
- return { kind: 'redraw' };
449
- case '~':
455
+ return { kind: "redraw" };
456
+ case "~":
450
457
  viToggleCaseAtCursor(state.buffer);
451
- return { kind: 'redraw' };
458
+ return { kind: "redraw" };
452
459
  // Multi-key operators: wait for next char.
453
- case 'r':
454
- state.viPending = 'r';
455
- return { kind: 'noop' };
456
- case 'd':
457
- state.viPending = 'd';
458
- return { kind: 'noop' };
459
- case 'c':
460
- state.viPending = 'c';
461
- return { kind: 'noop' };
462
- case 'g':
460
+ case "r":
461
+ state.viPending = "r";
462
+ return { kind: "noop" };
463
+ case "d":
464
+ state.viPending = "d";
465
+ return { kind: "noop" };
466
+ case "c":
467
+ state.viPending = "c";
468
+ return { kind: "noop" };
469
+ case "g":
463
470
  // Stub: only 'gg' (go to first history) might be desirable; for now,
464
471
  // just consume the prefix and bell on the follow-up.
465
- state.viPending = 'g';
466
- return { kind: 'noop' };
467
- case ':':
472
+ state.viPending = "g";
473
+ return { kind: "noop" };
474
+ case ":":
468
475
  // Enter ex-prompt mode. The renderer draws a `:` line; printable keys
469
476
  // accumulate in `exBuffer`; Enter executes; Esc returns to normal.
470
- state.mode = 'ex';
471
- state.exBuffer = '';
472
- return { kind: 'ex-update' };
477
+ state.mode = "ex";
478
+ state.exBuffer = "";
479
+ return { kind: "ex-update" };
473
480
  default:
474
- return { kind: 'bell' };
481
+ return { kind: "bell" };
475
482
  }
476
483
  };
477
484
  /**
@@ -488,44 +495,44 @@ const handleViNormalChar = (state, ev) => {
488
495
  */
489
496
  const dispatchViEx = (state, ev) => {
490
497
  switch (ev.key) {
491
- case 'escape':
498
+ case "escape":
492
499
  // Abort ex; back to normal without executing anything.
493
- state.mode = 'normal';
494
- state.exBuffer = '';
495
- return { kind: 'redraw' };
496
- case 'enter':
500
+ state.mode = "normal";
501
+ state.exBuffer = "";
502
+ return { kind: "redraw" };
503
+ case "enter":
497
504
  return executeExCommand(state);
498
- case 'backspace':
505
+ case "backspace":
499
506
  if (state.exBuffer.length === 0) {
500
507
  // Backspace through the implicit `:` returns to normal mode (matches
501
508
  // vim's "backspace at column 1 of ex line").
502
- state.mode = 'normal';
503
- return { kind: 'redraw' };
509
+ state.mode = "normal";
510
+ return { kind: "redraw" };
504
511
  }
505
512
  state.exBuffer = state.exBuffer.slice(0, -1);
506
- return { kind: 'ex-update' };
507
- case 'char': {
508
- const ch = ev.char ?? '';
513
+ return { kind: "ex-update" };
514
+ case "char": {
515
+ const ch = ev.char ?? "";
509
516
  // Ctrl/Meta combos aren't bound in ex.
510
517
  if (ch.length === 0 || ev.ctrl || ev.meta)
511
- return { kind: 'bell' };
518
+ return { kind: "bell" };
512
519
  state.exBuffer += ch;
513
- return { kind: 'ex-update' };
520
+ return { kind: "ex-update" };
514
521
  }
515
- case 'paste-start':
516
- case 'paste-end':
517
- case 'tab':
518
- case 'delete':
519
- case 'left':
520
- case 'right':
521
- case 'up':
522
- case 'down':
523
- case 'home':
524
- case 'end':
525
- case 'pageup':
526
- case 'pagedown':
527
- case 'unknown':
528
- return { kind: 'bell' };
522
+ case "paste-start":
523
+ case "paste-end":
524
+ case "tab":
525
+ case "delete":
526
+ case "left":
527
+ case "right":
528
+ case "up":
529
+ case "down":
530
+ case "home":
531
+ case "end":
532
+ case "pageup":
533
+ case "pagedown":
534
+ case "unknown":
535
+ return { kind: "bell" };
529
536
  }
530
537
  };
531
538
  /**
@@ -536,22 +543,22 @@ const dispatchViEx = (state, ev) => {
536
543
  const executeExCommand = (state) => {
537
544
  const cmd = state.exBuffer.trim();
538
545
  // Always leave ex mode after Enter, even on unknown commands.
539
- state.exBuffer = '';
540
- state.mode = 'normal';
546
+ state.exBuffer = "";
547
+ state.mode = "normal";
541
548
  switch (cmd) {
542
- case 'q':
543
- case 'q!':
544
- case 'quit':
549
+ case "q":
550
+ case "q!":
551
+ case "quit":
545
552
  // Abort the readLine. Same outcome as ^C — driver throws SignalError.
546
- return { kind: 'cancel' };
547
- case 'w':
553
+ return { kind: "cancel" };
554
+ case "w":
548
555
  // We don't have a file to write to; bell and return to normal.
549
- return { kind: 'bell' };
550
- case '':
556
+ return { kind: "bell" };
557
+ case "":
551
558
  // Bare `:` then Enter — just return to normal silently.
552
- return { kind: 'redraw' };
559
+ return { kind: "redraw" };
553
560
  default:
554
- return { kind: 'bell' };
561
+ return { kind: "bell" };
555
562
  }
556
563
  };
557
564
  /**
@@ -563,83 +570,83 @@ const continueViPending = (state, ev) => {
563
570
  const pending = state.viPending;
564
571
  state.viPending = null;
565
572
  // Escape cancels a pending operator without bell (matches vi convention).
566
- if (ev.key === 'escape')
567
- return { kind: 'noop' };
568
- if (pending === 'r') {
573
+ if (ev.key === "escape")
574
+ return { kind: "noop" };
575
+ if (pending === "r") {
569
576
  // r<char> replaces one char at cursor with <char>.
570
- if (ev.key !== 'char' || ev.char === undefined || ev.ctrl || ev.meta) {
571
- return { kind: 'bell' };
577
+ if (ev.key !== "char" || ev.char === undefined || ev.ctrl || ev.meta) {
578
+ return { kind: "bell" };
572
579
  }
573
580
  if (state.buffer.cursor >= state.buffer.length)
574
- return { kind: 'bell' };
581
+ return { kind: "bell" };
575
582
  viReplaceCharAtCursor(state.buffer, ev.char);
576
- return { kind: 'redraw' };
583
+ return { kind: "redraw" };
577
584
  }
578
- if (pending === 'g') {
585
+ if (pending === "g") {
579
586
  // Only 'gg' is recognised; we don't actually implement first-history yet.
580
- return { kind: 'bell' };
587
+ return { kind: "bell" };
581
588
  }
582
589
  // dd / cc / dw / cw all key off ev.char.
583
- if (ev.key !== 'char' || ev.char === undefined || ev.ctrl || ev.meta) {
584
- return { kind: 'bell' };
590
+ if (ev.key !== "char" || ev.char === undefined || ev.ctrl || ev.meta) {
591
+ return { kind: "bell" };
585
592
  }
586
593
  const c = ev.char;
587
- if (pending === 'd') {
588
- if (c === 'd') {
594
+ if (pending === "d") {
595
+ if (c === "d") {
589
596
  // dd: kill whole line.
590
597
  state.buffer.moveHome();
591
598
  state.buffer.killToEnd();
592
- return { kind: 'redraw' };
599
+ return { kind: "redraw" };
593
600
  }
594
- if (c === 'w') {
601
+ if (c === "w") {
595
602
  state.buffer.killWordRight();
596
- return { kind: 'redraw' };
603
+ return { kind: "redraw" };
597
604
  }
598
- if (c === 'b') {
605
+ if (c === "b") {
599
606
  state.buffer.killWordLeft();
600
- return { kind: 'redraw' };
607
+ return { kind: "redraw" };
601
608
  }
602
- if (c === '$') {
609
+ if (c === "$") {
603
610
  state.buffer.killToEnd();
604
- return { kind: 'redraw' };
611
+ return { kind: "redraw" };
605
612
  }
606
- if (c === '0') {
613
+ if (c === "0") {
607
614
  state.buffer.killToStart();
608
- return { kind: 'redraw' };
615
+ return { kind: "redraw" };
609
616
  }
610
- return { kind: 'bell' };
617
+ return { kind: "bell" };
611
618
  }
612
- if (pending === 'c') {
613
- if (c === 'c') {
619
+ if (pending === "c") {
620
+ if (c === "c") {
614
621
  // cc: kill whole line, enter insert.
615
622
  state.buffer.moveHome();
616
623
  state.buffer.killToEnd();
617
- state.mode = 'insert';
618
- return { kind: 'redraw' };
624
+ state.mode = "insert";
625
+ return { kind: "redraw" };
619
626
  }
620
- if (c === 'w') {
627
+ if (c === "w") {
621
628
  state.buffer.killWordRight();
622
- state.mode = 'insert';
623
- return { kind: 'redraw' };
629
+ state.mode = "insert";
630
+ return { kind: "redraw" };
624
631
  }
625
- if (c === 'b') {
632
+ if (c === "b") {
626
633
  state.buffer.killWordLeft();
627
- state.mode = 'insert';
628
- return { kind: 'redraw' };
634
+ state.mode = "insert";
635
+ return { kind: "redraw" };
629
636
  }
630
- if (c === '$') {
637
+ if (c === "$") {
631
638
  state.buffer.killToEnd();
632
- state.mode = 'insert';
633
- return { kind: 'redraw' };
639
+ state.mode = "insert";
640
+ return { kind: "redraw" };
634
641
  }
635
- if (c === '0') {
642
+ if (c === "0") {
636
643
  state.buffer.killToStart();
637
- state.mode = 'insert';
638
- return { kind: 'redraw' };
644
+ state.mode = "insert";
645
+ return { kind: "redraw" };
639
646
  }
640
- return { kind: 'bell' };
647
+ return { kind: "bell" };
641
648
  }
642
- return { kind: 'bell' };
649
+ return { kind: "bell" };
643
650
  };
644
651
  /** `e` motion: jump to the end of the current word (or the next word). */
645
652
  const viMoveEndOfWord = (buf) => {
@@ -666,7 +673,7 @@ const viMoveFirstNonBlank = (buf) => {
666
673
  const text = buf.text;
667
674
  const cps = Array.from(text);
668
675
  let i = 0;
669
- while (i < cps.length && (cps[i] === ' ' || cps[i] === '\t'))
676
+ while (i < cps.length && (cps[i] === " " || cps[i] === "\t"))
670
677
  i++;
671
678
  buf.setText(text, i);
672
679
  };
@@ -686,7 +693,7 @@ const viToggleCaseAtCursor = (buf) => {
686
693
  }
687
694
  buf.pushUndo();
688
695
  cps[i] = flipped;
689
- buf.setText(cps.join(''), Math.min(i + 1, cps.length));
696
+ buf.setText(cps.join(""), Math.min(i + 1, cps.length));
690
697
  };
691
698
  /** `r<char>` replaces the character at cursor and leaves cursor in place. */
692
699
  const viReplaceCharAtCursor = (buf, ch) => {
@@ -697,7 +704,7 @@ const viReplaceCharAtCursor = (buf, ch) => {
697
704
  return;
698
705
  buf.pushUndo();
699
706
  cps[i] = ch;
700
- buf.setText(cps.join(''), i);
707
+ buf.setText(cps.join(""), i);
701
708
  };
702
709
  // Re-export for vi helpers that need word classification (matches buffer.ts).
703
710
  const isWordChar = (ch) => {
@@ -734,5 +741,5 @@ const transpose = (buf) => {
734
741
  const tmp = cps[i - 1];
735
742
  cps[i - 1] = cps[i];
736
743
  cps[i] = tmp;
737
- buf.setText(cps.join(''), Math.min(i + 1, len));
744
+ buf.setText(cps.join(""), Math.min(i + 1, len));
738
745
  };