neon 3.0.0 → 3.1.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.
- package/README.md +54 -0
- package/dist/_shared/auth_selection.js +76 -79
- package/dist/_shared/credentials.js +166 -187
- package/dist/_shared/env-core/env.js +354 -517
- package/dist/_shared/env-core/reuse-secrets.js +159 -203
- package/dist/_shared/paths.js +129 -126
- package/dist/_shared/profiles.js +192 -242
- package/dist/_shared/secure_file.js +36 -38
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/analytics.js +163 -207
- package/dist/api.js +815 -758
- package/dist/auth.js +121 -141
- package/dist/auth_context.js +39 -53
- package/dist/cli.js +4 -7
- package/dist/commands/api.js +220 -250
- package/dist/commands/api_keys.js +251 -314
- package/dist/commands/auth.js +283 -328
- package/dist/commands/bootstrap.js +372 -437
- package/dist/commands/branches.js +304 -455
- package/dist/commands/bucket.js +374 -514
- package/dist/commands/checkout.js +213 -298
- package/dist/commands/config.js +573 -690
- package/dist/commands/connection_string.js +137 -165
- package/dist/commands/data_api.js +238 -260
- package/dist/commands/databases.js +67 -76
- package/dist/commands/deploy.js +31 -25
- package/dist/commands/dev.js +639 -719
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +243 -303
- package/dist/commands/functions.js +275 -355
- package/dist/commands/index.js +70 -65
- package/dist/commands/init.js +84 -119
- package/dist/commands/inspect.js +55 -55
- package/dist/commands/ip_allow.js +88 -120
- package/dist/commands/link.js +874 -1019
- package/dist/commands/logs.js +291 -0
- package/dist/commands/neon_auth.js +725 -933
- package/dist/commands/operations.js +34 -25
- package/dist/commands/orgs.js +28 -18
- package/dist/commands/profile.js +614 -845
- package/dist/commands/projects.js +313 -373
- package/dist/commands/psql.js +60 -58
- package/dist/commands/roles.js +55 -58
- package/dist/commands/schema_diff.js +87 -131
- package/dist/commands/set_context.js +34 -26
- package/dist/commands/snapshots.js +288 -413
- package/dist/commands/status.js +41 -37
- package/dist/commands/user.js +21 -10
- package/dist/commands/vpc_endpoints.js +85 -113
- package/dist/config.js +7 -6
- package/dist/config_format.js +50 -66
- package/dist/config_template.js +128 -157
- package/dist/context.js +183 -235
- package/dist/current_branch_fast_path.js +40 -49
- package/dist/dev/env.js +313 -394
- package/dist/dev/functions.js +54 -68
- package/dist/dev/inputs.js +46 -58
- package/dist/dev/runtime.js +135 -164
- package/dist/dev/websocket.js +766 -959
- package/dist/env.js +27 -33
- package/dist/env_file.js +118 -132
- package/dist/env_services.js +36 -38
- package/dist/errors.js +57 -68
- package/dist/functions_api.js +45 -43
- package/dist/help.js +189 -140
- package/dist/index.js +182 -257
- package/dist/init/agents.js +137 -118
- package/dist/init/auth.js +58 -68
- package/dist/init/bootstrap.js +325 -396
- package/dist/init/build_config.js +4 -2
- package/dist/init/detect_agent.js +56 -101
- package/dist/init/editors.js +35 -52
- package/dist/init/enrich_output.js +51 -66
- package/dist/init/extension.js +134 -171
- package/dist/init/inspect.js +179 -266
- package/dist/init/interactive.js +510 -622
- package/dist/init/neonctl.js +117 -168
- package/dist/init/orchestrate.js +157 -173
- package/dist/init/phases/auth.js +188 -202
- package/dist/init/phases/cleanup.js +23 -23
- package/dist/init/phases/db.js +251 -277
- package/dist/init/phases/getting_started.js +213 -223
- package/dist/init/phases/mcp.js +174 -224
- package/dist/init/phases/migrations.js +247 -248
- package/dist/init/phases/neon_auth.js +114 -133
- package/dist/init/phases/setup.js +546 -703
- package/dist/init/phases/skills.js +75 -86
- package/dist/init/phases/status.js +72 -67
- package/dist/init/resolve_context.js +102 -99
- package/dist/init/route_command.js +91 -98
- package/dist/init/skills.js +174 -218
- package/dist/init/vsix.js +77 -99
- package/dist/log.js +17 -16
- package/dist/neon_services.js +104 -129
- package/dist/parameters.gen.js +481 -471
- package/dist/pkg.js +17 -19
- package/dist/profile_keys.js +44 -47
- package/dist/psql/cli.js +44 -47
- package/dist/psql/command/cmd_cond.js +231 -406
- package/dist/psql/command/cmd_connect.js +557 -764
- package/dist/psql/command/cmd_copy.js +727 -983
- package/dist/psql/command/cmd_describe.js +1499 -1688
- package/dist/psql/command/cmd_format.js +733 -905
- package/dist/psql/command/cmd_io.js +1293 -2082
- package/dist/psql/command/cmd_lo.js +297 -359
- package/dist/psql/command/cmd_meta.js +727 -878
- package/dist/psql/command/cmd_misc.js +138 -172
- package/dist/psql/command/cmd_pipeline.js +547 -1099
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +261 -325
- package/dist/psql/command/inputQueue.js +35 -33
- package/dist/psql/command/shared.js +49 -63
- package/dist/psql/complete/filenames.js +90 -133
- package/dist/psql/complete/index.js +59 -97
- package/dist/psql/complete/matcher.js +236 -300
- package/dist/psql/complete/psqlVars.js +218 -223
- package/dist/psql/complete/queries.js +159 -177
- package/dist/psql/complete/rules.js +1493 -2299
- package/dist/psql/core/common.js +762 -1180
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1302
- package/dist/psql/core/prompt.js +391 -408
- package/dist/psql/core/settings.js +429 -644
- package/dist/psql/core/sqlHelp.js +480 -554
- package/dist/psql/core/startup.js +626 -815
- package/dist/psql/core/syncVars.js +67 -110
- package/dist/psql/core/variables.js +156 -278
- package/dist/psql/describe/formatters.js +884 -1285
- package/dist/psql/describe/processNamePattern.js +173 -260
- package/dist/psql/describe/queries.js +1368 -2403
- package/dist/psql/describe/versionGate.js +32 -41
- package/dist/psql/index.js +1414 -1927
- package/dist/psql/io/history.js +232 -271
- package/dist/psql/io/input.js +103 -108
- package/dist/psql/io/lineEditor/buffer.js +238 -319
- package/dist/psql/io/lineEditor/complete.js +135 -213
- package/dist/psql/io/lineEditor/filename.js +139 -148
- package/dist/psql/io/lineEditor/index.js +653 -870
- package/dist/psql/io/lineEditor/keymap.js +544 -702
- package/dist/psql/io/lineEditor/vt100.js +294 -341
- package/dist/psql/io/pgpass.js +158 -187
- package/dist/psql/io/pgservice.js +146 -183
- package/dist/psql/io/psqlrc.js +328 -403
- package/dist/psql/print/aligned.js +1020 -1683
- package/dist/psql/print/asciidoc.js +180 -214
- package/dist/psql/print/crosstab.js +281 -442
- package/dist/psql/print/csv.js +48 -70
- package/dist/psql/print/html.js +195 -226
- package/dist/psql/print/json.js +75 -88
- package/dist/psql/print/latex.js +291 -364
- package/dist/psql/print/pager.js +171 -242
- package/dist/psql/print/troff.js +194 -226
- package/dist/psql/print/unaligned.js +69 -95
- package/dist/psql/print/units.js +167 -169
- package/dist/psql/scanner/slash.js +428 -483
- package/dist/psql/scanner/sql.js +445 -889
- package/dist/psql/scanner/stringutils.js +309 -379
- package/dist/psql/types/index.js +2 -7
- package/dist/psql/types/scanner.js +25 -22
- package/dist/psql/wire/connection.js +2042 -2803
- package/dist/psql/wire/copy.js +84 -100
- package/dist/psql/wire/notify.js +39 -59
- package/dist/psql/wire/pipeline.js +305 -518
- package/dist/psql/wire/protocol.js +349 -417
- package/dist/psql/wire/sasl.js +180 -265
- package/dist/psql/wire/tls.js +400 -561
- package/dist/storage_api.js +115 -129
- package/dist/test_utils/fixtures.js +94 -113
- package/dist/test_utils/oauth_server.js +10 -7
- package/dist/test_utils/project_dir.js +33 -0
- package/dist/utils/ai_gateway_notice.js +131 -162
- package/dist/utils/api_enums.js +21 -28
- package/dist/utils/auth.js +10 -4
- package/dist/utils/branch_notice.js +20 -19
- package/dist/utils/branch_picker.js +83 -89
- package/dist/utils/cli_name.js +15 -12
- package/dist/utils/compute_units.js +20 -27
- package/dist/utils/config_diff.js +127 -158
- package/dist/utils/enrichers.js +95 -148
- package/dist/utils/esbuild.js +130 -189
- package/dist/utils/flags.js +35 -47
- package/dist/utils/formats.js +8 -15
- package/dist/utils/git_diff.js +69 -80
- package/dist/utils/inspect_db.js +101 -143
- package/dist/utils/inspect_queries.js +179 -142
- package/dist/utils/middlewares.js +37 -44
- package/dist/utils/openapi.js +87 -99
- package/dist/utils/package_manager.js +312 -110
- package/dist/utils/point_in_time.js +49 -53
- package/dist/utils/psql.js +89 -106
- package/dist/utils/service_picker.js +55 -58
- package/dist/utils/string.js +5 -5
- package/dist/utils/ui.js +38 -55
- package/dist/utils/write_sync.js +26 -35
- package/dist/utils/zip.js +4 -3
- package/dist/writer.js +67 -87
- package/package.json +7 -5
|
@@ -1,745 +1,587 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Emacs keymap dispatch.
|
|
3
|
-
*
|
|
4
|
-
* Pure function: take a `KeyEvent` plus the current editor state, mutate
|
|
5
|
-
* the state, and return an `Action` describing what the renderer / outer
|
|
6
|
-
* loop should do (redraw, submit, signal cancel, request completion, …).
|
|
7
|
-
*
|
|
8
|
-
* The state lives in `EditorState`, which wraps a `LineBuffer` plus a
|
|
9
|
-
* little extra context (history navigation, last-tab timestamp, search
|
|
10
|
-
* mode). Keeping dispatch separate from rendering means we can write
|
|
11
|
-
* deterministic key-by-key tests without touching a TTY.
|
|
12
|
-
*/
|
|
13
1
|
import { LineBuffer } from "./buffer.js";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
2
|
+
//#region src/psql/io/lineEditor/keymap.ts
|
|
3
|
+
/**
|
|
4
|
+
* Emacs keymap dispatch.
|
|
5
|
+
*
|
|
6
|
+
* Pure function: take a `KeyEvent` plus the current editor state, mutate
|
|
7
|
+
* the state, and return an `Action` describing what the renderer / outer
|
|
8
|
+
* loop should do (redraw, submit, signal cancel, request completion, …).
|
|
9
|
+
*
|
|
10
|
+
* The state lives in `EditorState`, which wraps a `LineBuffer` plus a
|
|
11
|
+
* little extra context (history navigation, last-tab timestamp, search
|
|
12
|
+
* mode). Keeping dispatch separate from rendering means we can write
|
|
13
|
+
* deterministic key-by-key tests without touching a TTY.
|
|
14
|
+
*/
|
|
15
|
+
const makeState = (history = [], mode = "emacs") => ({
|
|
16
|
+
buffer: new LineBuffer(),
|
|
17
|
+
history: [...history],
|
|
18
|
+
historyIndex: -1,
|
|
19
|
+
liveSnapshot: null,
|
|
20
|
+
lastYank: null,
|
|
21
|
+
pasting: false,
|
|
22
|
+
mode,
|
|
23
|
+
viPending: null,
|
|
24
|
+
exBuffer: ""
|
|
24
25
|
});
|
|
25
26
|
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
else
|
|
95
|
-
state.buffer.moveLeft();
|
|
96
|
-
return { kind: "redraw" };
|
|
97
|
-
case "right":
|
|
98
|
-
if (ev.meta)
|
|
99
|
-
state.buffer.moveWordRight();
|
|
100
|
-
else
|
|
101
|
-
state.buffer.moveRight();
|
|
102
|
-
return { kind: "redraw" };
|
|
103
|
-
case "up":
|
|
104
|
-
return navigateHistory(state, -1);
|
|
105
|
-
case "down":
|
|
106
|
-
return navigateHistory(state, +1);
|
|
107
|
-
case "home":
|
|
108
|
-
state.buffer.moveHome();
|
|
109
|
-
return { kind: "redraw" };
|
|
110
|
-
case "end":
|
|
111
|
-
state.buffer.moveEnd();
|
|
112
|
-
return { kind: "redraw" };
|
|
113
|
-
case "escape":
|
|
114
|
-
// Bare Escape: ignore (Alt prefixes are decoded into meta:true).
|
|
115
|
-
return { kind: "noop" };
|
|
116
|
-
case "char":
|
|
117
|
-
return handleChar(state, ev);
|
|
118
|
-
case "pageup":
|
|
119
|
-
case "pagedown":
|
|
120
|
-
case "unknown":
|
|
121
|
-
return { kind: "bell" };
|
|
122
|
-
}
|
|
27
|
+
* Apply one key event. Returns the resulting action for the outer loop.
|
|
28
|
+
* All mutation happens in `state.buffer` (or the navigation fields).
|
|
29
|
+
*/
|
|
30
|
+
const dispatch = (state, ev) => {
|
|
31
|
+
if (state.pasting) {
|
|
32
|
+
if (ev.key === "paste-end") {
|
|
33
|
+
state.pasting = false;
|
|
34
|
+
return { kind: "paste-end" };
|
|
35
|
+
}
|
|
36
|
+
if (ev.key === "char" && ev.char !== void 0 && !ev.ctrl && !ev.meta) {
|
|
37
|
+
state.buffer.insert(ev.char);
|
|
38
|
+
return { kind: "redraw" };
|
|
39
|
+
}
|
|
40
|
+
if (ev.key === "enter") {
|
|
41
|
+
state.buffer.insert("\n");
|
|
42
|
+
return { kind: "redraw" };
|
|
43
|
+
}
|
|
44
|
+
if (ev.key === "tab") {
|
|
45
|
+
state.buffer.insert(" ");
|
|
46
|
+
return { kind: "redraw" };
|
|
47
|
+
}
|
|
48
|
+
return { kind: "noop" };
|
|
49
|
+
}
|
|
50
|
+
if (ev.key === "char" && ev.ctrl && ev.char === "c") {
|
|
51
|
+
state.viPending = null;
|
|
52
|
+
return { kind: "cancel" };
|
|
53
|
+
}
|
|
54
|
+
if (state.mode === "ex") return dispatchViEx(state, ev);
|
|
55
|
+
if (state.mode === "normal") return dispatchViNormal(state, ev);
|
|
56
|
+
if (state.mode === "insert") return dispatchViInsert(state, ev);
|
|
57
|
+
switch (ev.key) {
|
|
58
|
+
case "paste-start":
|
|
59
|
+
state.pasting = true;
|
|
60
|
+
return { kind: "paste-start" };
|
|
61
|
+
case "paste-end":
|
|
62
|
+
state.pasting = false;
|
|
63
|
+
return { kind: "paste-end" };
|
|
64
|
+
case "enter": return { kind: "submit" };
|
|
65
|
+
case "tab": return { kind: "complete" };
|
|
66
|
+
case "backspace":
|
|
67
|
+
if (ev.meta) state.buffer.killWordLeft();
|
|
68
|
+
else state.buffer.deleteLeft();
|
|
69
|
+
return { kind: "redraw" };
|
|
70
|
+
case "delete":
|
|
71
|
+
state.buffer.deleteRight();
|
|
72
|
+
return { kind: "redraw" };
|
|
73
|
+
case "left":
|
|
74
|
+
if (ev.meta) state.buffer.moveWordLeft();
|
|
75
|
+
else state.buffer.moveLeft();
|
|
76
|
+
return { kind: "redraw" };
|
|
77
|
+
case "right":
|
|
78
|
+
if (ev.meta) state.buffer.moveWordRight();
|
|
79
|
+
else state.buffer.moveRight();
|
|
80
|
+
return { kind: "redraw" };
|
|
81
|
+
case "up": return navigateHistory(state, -1);
|
|
82
|
+
case "down": return navigateHistory(state, 1);
|
|
83
|
+
case "home":
|
|
84
|
+
state.buffer.moveHome();
|
|
85
|
+
return { kind: "redraw" };
|
|
86
|
+
case "end":
|
|
87
|
+
state.buffer.moveEnd();
|
|
88
|
+
return { kind: "redraw" };
|
|
89
|
+
case "escape": return { kind: "noop" };
|
|
90
|
+
case "char": return handleChar(state, ev);
|
|
91
|
+
case "pageup":
|
|
92
|
+
case "pagedown":
|
|
93
|
+
case "unknown": return { kind: "bell" };
|
|
94
|
+
}
|
|
123
95
|
};
|
|
124
96
|
const handleChar = (state, ev) => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return { kind: "redraw" };
|
|
201
|
-
case "f":
|
|
202
|
-
case "F":
|
|
203
|
-
state.buffer.moveWordRight();
|
|
204
|
-
return { kind: "redraw" };
|
|
205
|
-
case "d":
|
|
206
|
-
case "D":
|
|
207
|
-
state.buffer.killWordRight();
|
|
208
|
-
return { kind: "redraw" };
|
|
209
|
-
case "y":
|
|
210
|
-
case "Y":
|
|
211
|
-
if (state.lastYank === null)
|
|
212
|
-
return { kind: "bell" };
|
|
213
|
-
{
|
|
214
|
-
const next = state.buffer.yankPop(state.lastYank);
|
|
215
|
-
if (next === undefined)
|
|
216
|
-
return { kind: "bell" };
|
|
217
|
-
state.lastYank = next;
|
|
218
|
-
}
|
|
219
|
-
return { kind: "redraw" };
|
|
220
|
-
default:
|
|
221
|
-
return { kind: "bell" };
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
// Plain printable.
|
|
225
|
-
state.buffer.insert(ch);
|
|
226
|
-
return { kind: "redraw" };
|
|
97
|
+
const ch = ev.char ?? "";
|
|
98
|
+
if (ch.length === 0) return { kind: "noop" };
|
|
99
|
+
if (ev.ctrl) switch (ch) {
|
|
100
|
+
case "a":
|
|
101
|
+
state.buffer.moveHome();
|
|
102
|
+
return { kind: "redraw" };
|
|
103
|
+
case "e":
|
|
104
|
+
state.buffer.moveEnd();
|
|
105
|
+
return { kind: "redraw" };
|
|
106
|
+
case "b":
|
|
107
|
+
state.buffer.moveLeft();
|
|
108
|
+
return { kind: "redraw" };
|
|
109
|
+
case "f":
|
|
110
|
+
state.buffer.moveRight();
|
|
111
|
+
return { kind: "redraw" };
|
|
112
|
+
case "p": return navigateHistory(state, -1);
|
|
113
|
+
case "n": return navigateHistory(state, 1);
|
|
114
|
+
case "k":
|
|
115
|
+
state.buffer.killToEnd();
|
|
116
|
+
return { kind: "redraw" };
|
|
117
|
+
case "u":
|
|
118
|
+
state.buffer.killToStart();
|
|
119
|
+
return { kind: "redraw" };
|
|
120
|
+
case "w":
|
|
121
|
+
state.buffer.killWordLeft();
|
|
122
|
+
return { kind: "redraw" };
|
|
123
|
+
case "y": {
|
|
124
|
+
const yanked = state.buffer.yank();
|
|
125
|
+
state.lastYank = yanked ?? null;
|
|
126
|
+
return yanked === void 0 ? { kind: "bell" } : { kind: "redraw" };
|
|
127
|
+
}
|
|
128
|
+
case "c": return { kind: "cancel" };
|
|
129
|
+
case "d":
|
|
130
|
+
if (state.buffer.length === 0) return { kind: "eof" };
|
|
131
|
+
state.buffer.deleteRight();
|
|
132
|
+
return { kind: "redraw" };
|
|
133
|
+
case "l": return { kind: "clear-screen" };
|
|
134
|
+
case "h":
|
|
135
|
+
state.buffer.deleteLeft();
|
|
136
|
+
return { kind: "redraw" };
|
|
137
|
+
case "r": return { kind: "search-start" };
|
|
138
|
+
case "t":
|
|
139
|
+
transpose(state.buffer);
|
|
140
|
+
return { kind: "redraw" };
|
|
141
|
+
case "_":
|
|
142
|
+
case "/": return state.buffer.undo() ? { kind: "redraw" } : { kind: "bell" };
|
|
143
|
+
case "g": return { kind: "bell" };
|
|
144
|
+
default: return { kind: "bell" };
|
|
145
|
+
}
|
|
146
|
+
if (ev.meta) switch (ch) {
|
|
147
|
+
case "b":
|
|
148
|
+
case "B":
|
|
149
|
+
state.buffer.moveWordLeft();
|
|
150
|
+
return { kind: "redraw" };
|
|
151
|
+
case "f":
|
|
152
|
+
case "F":
|
|
153
|
+
state.buffer.moveWordRight();
|
|
154
|
+
return { kind: "redraw" };
|
|
155
|
+
case "d":
|
|
156
|
+
case "D":
|
|
157
|
+
state.buffer.killWordRight();
|
|
158
|
+
return { kind: "redraw" };
|
|
159
|
+
case "y":
|
|
160
|
+
case "Y":
|
|
161
|
+
if (state.lastYank === null) return { kind: "bell" };
|
|
162
|
+
{
|
|
163
|
+
const next = state.buffer.yankPop(state.lastYank);
|
|
164
|
+
if (next === void 0) return { kind: "bell" };
|
|
165
|
+
state.lastYank = next;
|
|
166
|
+
}
|
|
167
|
+
return { kind: "redraw" };
|
|
168
|
+
default: return { kind: "bell" };
|
|
169
|
+
}
|
|
170
|
+
state.buffer.insert(ch);
|
|
171
|
+
return { kind: "redraw" };
|
|
227
172
|
};
|
|
228
173
|
/**
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
174
|
+
* Move history index by delta and load the corresponding entry. Saves
|
|
175
|
+
* the live line on the first upward step so the user can return to it.
|
|
176
|
+
*/
|
|
232
177
|
const navigateHistory = (state, delta) => {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
state.historyIndex = next;
|
|
255
|
-
state.buffer.setText(state.history[next]);
|
|
256
|
-
return { kind: "redraw" };
|
|
178
|
+
if (state.history.length === 0) return { kind: "bell" };
|
|
179
|
+
if (state.historyIndex === -1) {
|
|
180
|
+
if (delta < 0) {
|
|
181
|
+
state.liveSnapshot = state.buffer.text;
|
|
182
|
+
state.historyIndex = state.history.length - 1;
|
|
183
|
+
state.buffer.setText(state.history[state.historyIndex]);
|
|
184
|
+
return { kind: "redraw" };
|
|
185
|
+
}
|
|
186
|
+
return { kind: "bell" };
|
|
187
|
+
}
|
|
188
|
+
const next = state.historyIndex + delta;
|
|
189
|
+
if (next < 0) return { kind: "bell" };
|
|
190
|
+
if (next >= state.history.length) {
|
|
191
|
+
state.historyIndex = -1;
|
|
192
|
+
state.buffer.setText(state.liveSnapshot ?? "");
|
|
193
|
+
state.liveSnapshot = null;
|
|
194
|
+
return { kind: "redraw" };
|
|
195
|
+
}
|
|
196
|
+
state.historyIndex = next;
|
|
197
|
+
state.buffer.setText(state.history[next]);
|
|
198
|
+
return { kind: "redraw" };
|
|
257
199
|
};
|
|
258
|
-
// ---------------------------------------------------------------------------
|
|
259
|
-
// vi mode
|
|
260
|
-
// ---------------------------------------------------------------------------
|
|
261
200
|
/**
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
201
|
+
* Vi insert-mode dispatch. Behaves mostly like emacs for editing primitives —
|
|
202
|
+
* printable keys insert, backspace/delete/arrows work the same way — but Esc
|
|
203
|
+
* switches into normal mode. Ctrl keys are intentionally NOT vi commands here
|
|
204
|
+
* (^C is handled at the top of `dispatch`).
|
|
205
|
+
*/
|
|
267
206
|
const dispatchViInsert = (state, ev) => {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (ev.ctrl && ch === "d" && state.buffer.length === 0) {
|
|
315
|
-
return { kind: "eof" };
|
|
316
|
-
}
|
|
317
|
-
// Ignore other control combos in vi insert; just insert plain printables.
|
|
318
|
-
if (ev.ctrl || ev.meta)
|
|
319
|
-
return { kind: "noop" };
|
|
320
|
-
state.buffer.insert(ch);
|
|
321
|
-
return { kind: "redraw" };
|
|
322
|
-
}
|
|
323
|
-
case "pageup":
|
|
324
|
-
case "pagedown":
|
|
325
|
-
case "unknown":
|
|
326
|
-
return { kind: "bell" };
|
|
327
|
-
}
|
|
207
|
+
switch (ev.key) {
|
|
208
|
+
case "paste-start":
|
|
209
|
+
state.pasting = true;
|
|
210
|
+
return { kind: "paste-start" };
|
|
211
|
+
case "paste-end":
|
|
212
|
+
state.pasting = false;
|
|
213
|
+
return { kind: "paste-end" };
|
|
214
|
+
case "enter": return { kind: "submit" };
|
|
215
|
+
case "tab": return { kind: "complete" };
|
|
216
|
+
case "escape":
|
|
217
|
+
state.mode = "normal";
|
|
218
|
+
state.viPending = null;
|
|
219
|
+
if (state.buffer.cursor > 0) state.buffer.moveLeft();
|
|
220
|
+
return { kind: "redraw" };
|
|
221
|
+
case "backspace":
|
|
222
|
+
state.buffer.deleteLeft();
|
|
223
|
+
return { kind: "redraw" };
|
|
224
|
+
case "delete":
|
|
225
|
+
state.buffer.deleteRight();
|
|
226
|
+
return { kind: "redraw" };
|
|
227
|
+
case "left":
|
|
228
|
+
state.buffer.moveLeft();
|
|
229
|
+
return { kind: "redraw" };
|
|
230
|
+
case "right":
|
|
231
|
+
state.buffer.moveRight();
|
|
232
|
+
return { kind: "redraw" };
|
|
233
|
+
case "up": return navigateHistory(state, -1);
|
|
234
|
+
case "down": return navigateHistory(state, 1);
|
|
235
|
+
case "home":
|
|
236
|
+
state.buffer.moveHome();
|
|
237
|
+
return { kind: "redraw" };
|
|
238
|
+
case "end":
|
|
239
|
+
state.buffer.moveEnd();
|
|
240
|
+
return { kind: "redraw" };
|
|
241
|
+
case "char": {
|
|
242
|
+
const ch = ev.char ?? "";
|
|
243
|
+
if (ch.length === 0) return { kind: "noop" };
|
|
244
|
+
if (ev.ctrl && ch === "d" && state.buffer.length === 0) return { kind: "eof" };
|
|
245
|
+
if (ev.ctrl || ev.meta) return { kind: "noop" };
|
|
246
|
+
state.buffer.insert(ch);
|
|
247
|
+
return { kind: "redraw" };
|
|
248
|
+
}
|
|
249
|
+
case "pageup":
|
|
250
|
+
case "pagedown":
|
|
251
|
+
case "unknown": return { kind: "bell" };
|
|
252
|
+
}
|
|
328
253
|
};
|
|
329
254
|
/**
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
255
|
+
* Vi normal-mode dispatch. Implements the core motion + edit subset documented
|
|
256
|
+
* in the WP-24 plan: hjkl/0$^/bwe movement, x/X/dd/D/cc/cw/r/~ edits, i/a/I/A
|
|
257
|
+
* to switch back to insert. Multi-key sequences (dd, cw, r<char>) are tracked
|
|
258
|
+
* via `state.viPending`.
|
|
259
|
+
*/
|
|
335
260
|
const dispatchViNormal = (state, ev) => {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
return handleViNormalChar(state, ev);
|
|
375
|
-
case "paste-start":
|
|
376
|
-
state.pasting = true;
|
|
377
|
-
return { kind: "paste-start" };
|
|
378
|
-
case "paste-end":
|
|
379
|
-
state.pasting = false;
|
|
380
|
-
return { kind: "paste-end" };
|
|
381
|
-
case "pageup":
|
|
382
|
-
case "pagedown":
|
|
383
|
-
case "unknown":
|
|
384
|
-
return { kind: "bell" };
|
|
385
|
-
}
|
|
261
|
+
if (state.viPending !== null) return continueViPending(state, ev);
|
|
262
|
+
switch (ev.key) {
|
|
263
|
+
case "enter": return { kind: "submit" };
|
|
264
|
+
case "tab": return { kind: "bell" };
|
|
265
|
+
case "escape":
|
|
266
|
+
state.viPending = null;
|
|
267
|
+
return { kind: "noop" };
|
|
268
|
+
case "backspace":
|
|
269
|
+
state.buffer.moveLeft();
|
|
270
|
+
return { kind: "redraw" };
|
|
271
|
+
case "delete":
|
|
272
|
+
state.buffer.deleteRight();
|
|
273
|
+
return { kind: "redraw" };
|
|
274
|
+
case "left":
|
|
275
|
+
state.buffer.moveLeft();
|
|
276
|
+
return { kind: "redraw" };
|
|
277
|
+
case "right":
|
|
278
|
+
state.buffer.moveRight();
|
|
279
|
+
return { kind: "redraw" };
|
|
280
|
+
case "up": return navigateHistory(state, -1);
|
|
281
|
+
case "down": return navigateHistory(state, 1);
|
|
282
|
+
case "home":
|
|
283
|
+
state.buffer.moveHome();
|
|
284
|
+
return { kind: "redraw" };
|
|
285
|
+
case "end":
|
|
286
|
+
state.buffer.moveEnd();
|
|
287
|
+
return { kind: "redraw" };
|
|
288
|
+
case "char": return handleViNormalChar(state, ev);
|
|
289
|
+
case "paste-start":
|
|
290
|
+
state.pasting = true;
|
|
291
|
+
return { kind: "paste-start" };
|
|
292
|
+
case "paste-end":
|
|
293
|
+
state.pasting = false;
|
|
294
|
+
return { kind: "paste-end" };
|
|
295
|
+
case "pageup":
|
|
296
|
+
case "pagedown":
|
|
297
|
+
case "unknown": return { kind: "bell" };
|
|
298
|
+
}
|
|
386
299
|
};
|
|
387
300
|
const handleViNormalChar = (state, ev) => {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
state.viPending = "d";
|
|
465
|
-
return { kind: "noop" };
|
|
466
|
-
case "c":
|
|
467
|
-
state.viPending = "c";
|
|
468
|
-
return { kind: "noop" };
|
|
469
|
-
case "g":
|
|
470
|
-
// Stub: only 'gg' (go to first history) might be desirable; for now,
|
|
471
|
-
// just consume the prefix and bell on the follow-up.
|
|
472
|
-
state.viPending = "g";
|
|
473
|
-
return { kind: "noop" };
|
|
474
|
-
case ":":
|
|
475
|
-
// Enter ex-prompt mode. The renderer draws a `:` line; printable keys
|
|
476
|
-
// accumulate in `exBuffer`; Enter executes; Esc returns to normal.
|
|
477
|
-
state.mode = "ex";
|
|
478
|
-
state.exBuffer = "";
|
|
479
|
-
return { kind: "ex-update" };
|
|
480
|
-
default:
|
|
481
|
-
return { kind: "bell" };
|
|
482
|
-
}
|
|
301
|
+
const ch = ev.char ?? "";
|
|
302
|
+
if (ch.length === 0) return { kind: "noop" };
|
|
303
|
+
if (ev.ctrl && ch === "d" && state.buffer.length === 0) return { kind: "eof" };
|
|
304
|
+
if (ev.ctrl || ev.meta) return { kind: "bell" };
|
|
305
|
+
switch (ch) {
|
|
306
|
+
case "h":
|
|
307
|
+
state.buffer.moveLeft();
|
|
308
|
+
return { kind: "redraw" };
|
|
309
|
+
case "l":
|
|
310
|
+
state.buffer.moveRight();
|
|
311
|
+
return { kind: "redraw" };
|
|
312
|
+
case "b":
|
|
313
|
+
state.buffer.moveWordLeft();
|
|
314
|
+
return { kind: "redraw" };
|
|
315
|
+
case "w":
|
|
316
|
+
state.buffer.moveWordRight();
|
|
317
|
+
return { kind: "redraw" };
|
|
318
|
+
case "e":
|
|
319
|
+
viMoveEndOfWord(state.buffer);
|
|
320
|
+
return { kind: "redraw" };
|
|
321
|
+
case "0":
|
|
322
|
+
state.buffer.moveHome();
|
|
323
|
+
return { kind: "redraw" };
|
|
324
|
+
case "$":
|
|
325
|
+
state.buffer.moveEnd();
|
|
326
|
+
return { kind: "redraw" };
|
|
327
|
+
case "^":
|
|
328
|
+
viMoveFirstNonBlank(state.buffer);
|
|
329
|
+
return { kind: "redraw" };
|
|
330
|
+
case "j": return navigateHistory(state, 1);
|
|
331
|
+
case "k": return navigateHistory(state, -1);
|
|
332
|
+
case "i":
|
|
333
|
+
state.mode = "insert";
|
|
334
|
+
return { kind: "redraw" };
|
|
335
|
+
case "a":
|
|
336
|
+
if (state.buffer.cursor < state.buffer.length) state.buffer.moveRight();
|
|
337
|
+
state.mode = "insert";
|
|
338
|
+
return { kind: "redraw" };
|
|
339
|
+
case "I":
|
|
340
|
+
state.buffer.moveHome();
|
|
341
|
+
state.mode = "insert";
|
|
342
|
+
return { kind: "redraw" };
|
|
343
|
+
case "A":
|
|
344
|
+
state.buffer.moveEnd();
|
|
345
|
+
state.mode = "insert";
|
|
346
|
+
return { kind: "redraw" };
|
|
347
|
+
case "x":
|
|
348
|
+
state.buffer.deleteRight();
|
|
349
|
+
return { kind: "redraw" };
|
|
350
|
+
case "X":
|
|
351
|
+
state.buffer.deleteLeft();
|
|
352
|
+
return { kind: "redraw" };
|
|
353
|
+
case "D":
|
|
354
|
+
state.buffer.killToEnd();
|
|
355
|
+
return { kind: "redraw" };
|
|
356
|
+
case "~":
|
|
357
|
+
viToggleCaseAtCursor(state.buffer);
|
|
358
|
+
return { kind: "redraw" };
|
|
359
|
+
case "r":
|
|
360
|
+
state.viPending = "r";
|
|
361
|
+
return { kind: "noop" };
|
|
362
|
+
case "d":
|
|
363
|
+
state.viPending = "d";
|
|
364
|
+
return { kind: "noop" };
|
|
365
|
+
case "c":
|
|
366
|
+
state.viPending = "c";
|
|
367
|
+
return { kind: "noop" };
|
|
368
|
+
case "g":
|
|
369
|
+
state.viPending = "g";
|
|
370
|
+
return { kind: "noop" };
|
|
371
|
+
case ":":
|
|
372
|
+
state.mode = "ex";
|
|
373
|
+
state.exBuffer = "";
|
|
374
|
+
return { kind: "ex-update" };
|
|
375
|
+
default: return { kind: "bell" };
|
|
376
|
+
}
|
|
483
377
|
};
|
|
484
378
|
/**
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
379
|
+
* Vi ex-prompt dispatch (`:`-line). Mirrors a tiny slice of vim's ex grammar:
|
|
380
|
+
*
|
|
381
|
+
* - `q`, `quit`, `q!` — abort the current readLine (same as ^C).
|
|
382
|
+
* - `w` (no arg) — bell + ignore (we don't have a file to write to).
|
|
383
|
+
* - Esc — abort ex; return to normal mode.
|
|
384
|
+
* - Enter — execute the accumulated command.
|
|
385
|
+
*
|
|
386
|
+
* Printable characters extend `state.exBuffer`; Backspace shrinks it. Anything
|
|
387
|
+
* else bells. The renderer is responsible for drawing `: ` + exBuffer on its
|
|
388
|
+
* own row; we expose `ex-update` actions so it knows when to redraw.
|
|
389
|
+
*/
|
|
496
390
|
const dispatchViEx = (state, ev) => {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
case "home":
|
|
531
|
-
case "end":
|
|
532
|
-
case "pageup":
|
|
533
|
-
case "pagedown":
|
|
534
|
-
case "unknown":
|
|
535
|
-
return { kind: "bell" };
|
|
536
|
-
}
|
|
391
|
+
switch (ev.key) {
|
|
392
|
+
case "escape":
|
|
393
|
+
state.mode = "normal";
|
|
394
|
+
state.exBuffer = "";
|
|
395
|
+
return { kind: "redraw" };
|
|
396
|
+
case "enter": return executeExCommand(state);
|
|
397
|
+
case "backspace":
|
|
398
|
+
if (state.exBuffer.length === 0) {
|
|
399
|
+
state.mode = "normal";
|
|
400
|
+
return { kind: "redraw" };
|
|
401
|
+
}
|
|
402
|
+
state.exBuffer = state.exBuffer.slice(0, -1);
|
|
403
|
+
return { kind: "ex-update" };
|
|
404
|
+
case "char": {
|
|
405
|
+
const ch = ev.char ?? "";
|
|
406
|
+
if (ch.length === 0 || ev.ctrl || ev.meta) return { kind: "bell" };
|
|
407
|
+
state.exBuffer += ch;
|
|
408
|
+
return { kind: "ex-update" };
|
|
409
|
+
}
|
|
410
|
+
case "paste-start":
|
|
411
|
+
case "paste-end":
|
|
412
|
+
case "tab":
|
|
413
|
+
case "delete":
|
|
414
|
+
case "left":
|
|
415
|
+
case "right":
|
|
416
|
+
case "up":
|
|
417
|
+
case "down":
|
|
418
|
+
case "home":
|
|
419
|
+
case "end":
|
|
420
|
+
case "pageup":
|
|
421
|
+
case "pagedown":
|
|
422
|
+
case "unknown": return { kind: "bell" };
|
|
423
|
+
}
|
|
537
424
|
};
|
|
538
425
|
/**
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
426
|
+
* Interpret the accumulated ex buffer. Returns the action the driver should
|
|
427
|
+
* apply (cancel = abort current readLine; bell = unknown command; redraw =
|
|
428
|
+
* fall back to normal mode without side effects).
|
|
429
|
+
*/
|
|
543
430
|
const executeExCommand = (state) => {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
// We don't have a file to write to; bell and return to normal.
|
|
556
|
-
return { kind: "bell" };
|
|
557
|
-
case "":
|
|
558
|
-
// Bare `:` then Enter — just return to normal silently.
|
|
559
|
-
return { kind: "redraw" };
|
|
560
|
-
default:
|
|
561
|
-
return { kind: "bell" };
|
|
562
|
-
}
|
|
431
|
+
const cmd = state.exBuffer.trim();
|
|
432
|
+
state.exBuffer = "";
|
|
433
|
+
state.mode = "normal";
|
|
434
|
+
switch (cmd) {
|
|
435
|
+
case "q":
|
|
436
|
+
case "q!":
|
|
437
|
+
case "quit": return { kind: "cancel" };
|
|
438
|
+
case "w": return { kind: "bell" };
|
|
439
|
+
case "": return { kind: "redraw" };
|
|
440
|
+
default: return { kind: "bell" };
|
|
441
|
+
}
|
|
563
442
|
};
|
|
564
443
|
/**
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
444
|
+
* Resolve a pending vi operator using the next key event. `r<char>` replaces
|
|
445
|
+
* one char; `dd` / `cc` operate on the whole line; `dw` / `cw` operate on a
|
|
446
|
+
* word; anything else bells.
|
|
447
|
+
*/
|
|
569
448
|
const continueViPending = (state, ev) => {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
}
|
|
637
|
-
if (c === "$") {
|
|
638
|
-
state.buffer.killToEnd();
|
|
639
|
-
state.mode = "insert";
|
|
640
|
-
return { kind: "redraw" };
|
|
641
|
-
}
|
|
642
|
-
if (c === "0") {
|
|
643
|
-
state.buffer.killToStart();
|
|
644
|
-
state.mode = "insert";
|
|
645
|
-
return { kind: "redraw" };
|
|
646
|
-
}
|
|
647
|
-
return { kind: "bell" };
|
|
648
|
-
}
|
|
649
|
-
return { kind: "bell" };
|
|
449
|
+
const pending = state.viPending;
|
|
450
|
+
state.viPending = null;
|
|
451
|
+
if (ev.key === "escape") return { kind: "noop" };
|
|
452
|
+
if (pending === "r") {
|
|
453
|
+
if (ev.key !== "char" || ev.char === void 0 || ev.ctrl || ev.meta) return { kind: "bell" };
|
|
454
|
+
if (state.buffer.cursor >= state.buffer.length) return { kind: "bell" };
|
|
455
|
+
viReplaceCharAtCursor(state.buffer, ev.char);
|
|
456
|
+
return { kind: "redraw" };
|
|
457
|
+
}
|
|
458
|
+
if (pending === "g") return { kind: "bell" };
|
|
459
|
+
if (ev.key !== "char" || ev.char === void 0 || ev.ctrl || ev.meta) return { kind: "bell" };
|
|
460
|
+
const c = ev.char;
|
|
461
|
+
if (pending === "d") {
|
|
462
|
+
if (c === "d") {
|
|
463
|
+
state.buffer.moveHome();
|
|
464
|
+
state.buffer.killToEnd();
|
|
465
|
+
return { kind: "redraw" };
|
|
466
|
+
}
|
|
467
|
+
if (c === "w") {
|
|
468
|
+
state.buffer.killWordRight();
|
|
469
|
+
return { kind: "redraw" };
|
|
470
|
+
}
|
|
471
|
+
if (c === "b") {
|
|
472
|
+
state.buffer.killWordLeft();
|
|
473
|
+
return { kind: "redraw" };
|
|
474
|
+
}
|
|
475
|
+
if (c === "$") {
|
|
476
|
+
state.buffer.killToEnd();
|
|
477
|
+
return { kind: "redraw" };
|
|
478
|
+
}
|
|
479
|
+
if (c === "0") {
|
|
480
|
+
state.buffer.killToStart();
|
|
481
|
+
return { kind: "redraw" };
|
|
482
|
+
}
|
|
483
|
+
return { kind: "bell" };
|
|
484
|
+
}
|
|
485
|
+
if (pending === "c") {
|
|
486
|
+
if (c === "c") {
|
|
487
|
+
state.buffer.moveHome();
|
|
488
|
+
state.buffer.killToEnd();
|
|
489
|
+
state.mode = "insert";
|
|
490
|
+
return { kind: "redraw" };
|
|
491
|
+
}
|
|
492
|
+
if (c === "w") {
|
|
493
|
+
state.buffer.killWordRight();
|
|
494
|
+
state.mode = "insert";
|
|
495
|
+
return { kind: "redraw" };
|
|
496
|
+
}
|
|
497
|
+
if (c === "b") {
|
|
498
|
+
state.buffer.killWordLeft();
|
|
499
|
+
state.mode = "insert";
|
|
500
|
+
return { kind: "redraw" };
|
|
501
|
+
}
|
|
502
|
+
if (c === "$") {
|
|
503
|
+
state.buffer.killToEnd();
|
|
504
|
+
state.mode = "insert";
|
|
505
|
+
return { kind: "redraw" };
|
|
506
|
+
}
|
|
507
|
+
if (c === "0") {
|
|
508
|
+
state.buffer.killToStart();
|
|
509
|
+
state.mode = "insert";
|
|
510
|
+
return { kind: "redraw" };
|
|
511
|
+
}
|
|
512
|
+
return { kind: "bell" };
|
|
513
|
+
}
|
|
514
|
+
return { kind: "bell" };
|
|
650
515
|
};
|
|
651
516
|
/** `e` motion: jump to the end of the current word (or the next word). */
|
|
652
517
|
const viMoveEndOfWord = (buf) => {
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
i++;
|
|
661
|
-
// Step 2: if we landed inside a word, advance to its last char (one before
|
|
662
|
-
// the next non-word boundary).
|
|
663
|
-
while (i < cps.length - 1 && isWordChar(cps[i + 1]))
|
|
664
|
-
i++;
|
|
665
|
-
// Vi's `e` leaves the cursor ON the last char, not past it. Our buffer uses
|
|
666
|
-
// cursor "between code points", so put it after that last char.
|
|
667
|
-
if (i < cps.length)
|
|
668
|
-
i++;
|
|
669
|
-
buf.setText(text, i);
|
|
518
|
+
const text = buf.text;
|
|
519
|
+
const cps = Array.from(text);
|
|
520
|
+
let i = buf.cursor;
|
|
521
|
+
while (i < cps.length && !isWordChar(cps[i])) i++;
|
|
522
|
+
while (i < cps.length - 1 && isWordChar(cps[i + 1])) i++;
|
|
523
|
+
if (i < cps.length) i++;
|
|
524
|
+
buf.setText(text, i);
|
|
670
525
|
};
|
|
671
526
|
/** `^` motion: first non-blank char on the line. */
|
|
672
527
|
const viMoveFirstNonBlank = (buf) => {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
buf.setText(text, i);
|
|
528
|
+
const text = buf.text;
|
|
529
|
+
const cps = Array.from(text);
|
|
530
|
+
let i = 0;
|
|
531
|
+
while (i < cps.length && (cps[i] === " " || cps[i] === " ")) i++;
|
|
532
|
+
buf.setText(text, i);
|
|
679
533
|
};
|
|
680
534
|
/** `~` toggles the case of the character at the cursor; advances cursor. */
|
|
681
535
|
const viToggleCaseAtCursor = (buf) => {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
cps[i] = flipped;
|
|
696
|
-
buf.setText(cps.join(""), Math.min(i + 1, cps.length));
|
|
536
|
+
const text = buf.text;
|
|
537
|
+
const cps = Array.from(text);
|
|
538
|
+
const i = buf.cursor;
|
|
539
|
+
if (i >= cps.length) return;
|
|
540
|
+
const ch = cps[i];
|
|
541
|
+
const flipped = ch === ch.toLowerCase() ? ch.toUpperCase() : ch.toLowerCase();
|
|
542
|
+
if (flipped === ch) {
|
|
543
|
+
buf.setText(text, Math.min(i + 1, cps.length));
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
buf.pushUndo();
|
|
547
|
+
cps[i] = flipped;
|
|
548
|
+
buf.setText(cps.join(""), Math.min(i + 1, cps.length));
|
|
697
549
|
};
|
|
698
550
|
/** `r<char>` replaces the character at cursor and leaves cursor in place. */
|
|
699
551
|
const viReplaceCharAtCursor = (buf, ch) => {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
buf.setText(cps.join(""), i);
|
|
552
|
+
const text = buf.text;
|
|
553
|
+
const cps = Array.from(text);
|
|
554
|
+
const i = buf.cursor;
|
|
555
|
+
if (i >= cps.length) return;
|
|
556
|
+
buf.pushUndo();
|
|
557
|
+
cps[i] = ch;
|
|
558
|
+
buf.setText(cps.join(""), i);
|
|
708
559
|
};
|
|
709
|
-
// Re-export for vi helpers that need word classification (matches buffer.ts).
|
|
710
560
|
const isWordChar = (ch) => {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
return true; // a-z
|
|
720
|
-
if (cp === 0x5f)
|
|
721
|
-
return true; // _
|
|
722
|
-
if (cp > 0x7f)
|
|
723
|
-
return cp >= 0xa0;
|
|
724
|
-
return false;
|
|
561
|
+
if (ch.length === 0) return false;
|
|
562
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
563
|
+
if (cp >= 48 && cp <= 57) return true;
|
|
564
|
+
if (cp >= 65 && cp <= 90) return true;
|
|
565
|
+
if (cp >= 97 && cp <= 122) return true;
|
|
566
|
+
if (cp === 95) return true;
|
|
567
|
+
if (cp > 127) return cp >= 160;
|
|
568
|
+
return false;
|
|
725
569
|
};
|
|
726
570
|
/** ^T: swap the character before the cursor with the one before it. */
|
|
727
571
|
const transpose = (buf) => {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const tmp = cps[i - 1];
|
|
742
|
-
cps[i - 1] = cps[i];
|
|
743
|
-
cps[i] = tmp;
|
|
744
|
-
buf.setText(cps.join(""), Math.min(i + 1, len));
|
|
572
|
+
const len = buf.length;
|
|
573
|
+
if (len < 2) return;
|
|
574
|
+
const text = buf.text;
|
|
575
|
+
const cps = Array.from(text);
|
|
576
|
+
let i = buf.cursor;
|
|
577
|
+
if (i === 0) return;
|
|
578
|
+
if (i === len) i--;
|
|
579
|
+
if (i < 1) return;
|
|
580
|
+
buf.pushUndo();
|
|
581
|
+
const tmp = cps[i - 1];
|
|
582
|
+
cps[i - 1] = cps[i];
|
|
583
|
+
cps[i] = tmp;
|
|
584
|
+
buf.setText(cps.join(""), Math.min(i + 1, len));
|
|
745
585
|
};
|
|
586
|
+
//#endregion
|
|
587
|
+
export { dispatch, makeState };
|