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.
- package/README.md +2 -2
- package/dist/analytics.js +35 -33
- package/dist/api.js +34 -34
- package/dist/auth.js +50 -44
- package/dist/cli.js +2 -2
- package/dist/commands/auth.js +58 -52
- package/dist/commands/bootstrap.js +115 -157
- package/dist/commands/branches.js +154 -147
- package/dist/commands/bucket.js +124 -118
- package/dist/commands/checkout.js +49 -49
- package/dist/commands/config.js +212 -88
- package/dist/commands/connection_string.js +62 -62
- package/dist/commands/data_api.js +96 -96
- package/dist/commands/databases.js +23 -23
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/dev.js +114 -114
- package/dist/commands/env.js +43 -43
- package/dist/commands/functions.js +97 -98
- package/dist/commands/index.js +26 -26
- package/dist/commands/init.js +23 -22
- package/dist/commands/ip_allow.js +29 -29
- package/dist/commands/link.js +223 -166
- package/dist/commands/neon_auth.js +381 -363
- package/dist/commands/operations.js +11 -11
- package/dist/commands/orgs.js +8 -8
- package/dist/commands/projects.js +101 -99
- package/dist/commands/psql.js +31 -31
- package/dist/commands/roles.js +21 -21
- package/dist/commands/schema_diff.js +23 -23
- package/dist/commands/set_context.js +17 -17
- package/dist/commands/status.js +17 -17
- package/dist/commands/user.js +5 -5
- package/dist/commands/vpc_endpoints.js +50 -50
- package/dist/config.js +7 -7
- package/dist/config_format.js +5 -5
- package/dist/context.js +23 -16
- package/dist/current_branch_fast_path.js +6 -6
- package/dist/dev/env.js +33 -33
- package/dist/dev/functions.js +4 -4
- package/dist/dev/inputs.js +6 -6
- package/dist/dev/runtime.js +25 -25
- package/dist/env.js +14 -14
- package/dist/env_file.js +13 -13
- package/dist/errors.js +19 -19
- package/dist/functions_api.js +10 -10
- package/dist/help.js +15 -15
- package/dist/index.js +94 -92
- package/dist/log.js +2 -2
- package/dist/pkg.js +5 -5
- package/dist/psql/cli.js +4 -2
- package/dist/psql/command/cmd_cond.js +61 -61
- package/dist/psql/command/cmd_connect.js +159 -154
- package/dist/psql/command/cmd_copy.js +107 -97
- package/dist/psql/command/cmd_describe.js +368 -363
- package/dist/psql/command/cmd_format.js +276 -263
- package/dist/psql/command/cmd_io.js +269 -263
- package/dist/psql/command/cmd_lo.js +74 -66
- package/dist/psql/command/cmd_meta.js +148 -148
- package/dist/psql/command/cmd_misc.js +17 -17
- package/dist/psql/command/cmd_pipeline.js +142 -135
- package/dist/psql/command/cmd_restrict.js +25 -25
- package/dist/psql/command/cmd_show.js +183 -168
- package/dist/psql/command/dispatch.js +26 -26
- package/dist/psql/command/shared.js +14 -14
- package/dist/psql/complete/filenames.js +16 -16
- package/dist/psql/complete/index.js +4 -4
- package/dist/psql/complete/matcher.js +33 -32
- package/dist/psql/complete/psqlVars.js +173 -173
- package/dist/psql/complete/queries.js +5 -3
- package/dist/psql/complete/rules.js +900 -863
- package/dist/psql/core/common.js +136 -133
- package/dist/psql/core/help.js +343 -343
- package/dist/psql/core/mainloop.js +160 -153
- package/dist/psql/core/prompt.js +126 -123
- package/dist/psql/core/settings.js +111 -111
- package/dist/psql/core/sqlHelp.js +150 -150
- package/dist/psql/core/startup.js +211 -205
- package/dist/psql/core/syncVars.js +14 -14
- package/dist/psql/core/variables.js +24 -24
- package/dist/psql/describe/formatters.js +302 -289
- package/dist/psql/describe/processNamePattern.js +28 -28
- package/dist/psql/describe/queries.js +656 -651
- package/dist/psql/index.js +436 -411
- package/dist/psql/io/history.js +36 -36
- package/dist/psql/io/input.js +15 -15
- package/dist/psql/io/lineEditor/buffer.js +27 -25
- package/dist/psql/io/lineEditor/complete.js +15 -15
- package/dist/psql/io/lineEditor/filename.js +22 -22
- package/dist/psql/io/lineEditor/index.js +65 -62
- package/dist/psql/io/lineEditor/keymap.js +325 -318
- package/dist/psql/io/lineEditor/vt100.js +60 -60
- package/dist/psql/io/pgpass.js +18 -18
- package/dist/psql/io/pgservice.js +14 -14
- package/dist/psql/io/psqlrc.js +46 -46
- package/dist/psql/print/aligned.js +175 -166
- package/dist/psql/print/asciidoc.js +51 -51
- package/dist/psql/print/crosstab.js +34 -31
- package/dist/psql/print/csv.js +25 -22
- package/dist/psql/print/html.js +54 -54
- package/dist/psql/print/json.js +12 -12
- package/dist/psql/print/latex.js +118 -118
- package/dist/psql/print/pager.js +28 -26
- package/dist/psql/print/troff.js +48 -48
- package/dist/psql/print/unaligned.js +15 -14
- package/dist/psql/print/units.js +17 -17
- package/dist/psql/scanner/slash.js +48 -46
- package/dist/psql/scanner/sql.js +88 -84
- package/dist/psql/scanner/stringutils.js +21 -17
- package/dist/psql/types/index.js +7 -7
- package/dist/psql/types/scanner.js +8 -8
- package/dist/psql/wire/connection.js +341 -327
- package/dist/psql/wire/copy.js +7 -7
- package/dist/psql/wire/pipeline.js +26 -24
- package/dist/psql/wire/protocol.js +102 -102
- package/dist/psql/wire/sasl.js +62 -62
- package/dist/psql/wire/tls.js +79 -73
- package/dist/storage_api.js +15 -15
- package/dist/test_utils/fixtures.js +34 -31
- package/dist/test_utils/oauth_server.js +5 -5
- package/dist/utils/api_enums.js +13 -13
- package/dist/utils/branch_notice.js +5 -5
- package/dist/utils/branch_picker.js +26 -26
- package/dist/utils/compute_units.js +4 -4
- package/dist/utils/enrichers.js +20 -15
- package/dist/utils/esbuild.js +28 -28
- package/dist/utils/formats.js +1 -1
- package/dist/utils/middlewares.js +3 -3
- package/dist/utils/package_manager.js +68 -0
- package/dist/utils/point_in_time.js +12 -12
- package/dist/utils/psql.js +30 -30
- package/dist/utils/string.js +2 -2
- package/dist/utils/ui.js +9 -9
- package/dist/utils/zip.js +1 -1
- package/dist/writer.js +17 -17
- 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
|
|
14
|
-
export const makeState = (history = [], mode =
|
|
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 ===
|
|
33
|
+
if (ev.key === "paste-end") {
|
|
34
34
|
state.pasting = false;
|
|
35
|
-
return { kind:
|
|
35
|
+
return { kind: "paste-end" };
|
|
36
36
|
}
|
|
37
|
-
if (ev.key ===
|
|
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:
|
|
42
|
+
return { kind: "redraw" };
|
|
40
43
|
}
|
|
41
|
-
if (ev.key ===
|
|
42
|
-
state.buffer.insert(
|
|
43
|
-
return { kind:
|
|
44
|
+
if (ev.key === "enter") {
|
|
45
|
+
state.buffer.insert("\n");
|
|
46
|
+
return { kind: "redraw" };
|
|
44
47
|
}
|
|
45
|
-
if (ev.key ===
|
|
46
|
-
state.buffer.insert(
|
|
47
|
-
return { kind:
|
|
48
|
+
if (ev.key === "tab") {
|
|
49
|
+
state.buffer.insert("\t");
|
|
50
|
+
return { kind: "redraw" };
|
|
48
51
|
}
|
|
49
|
-
return { kind:
|
|
52
|
+
return { kind: "noop" };
|
|
50
53
|
}
|
|
51
54
|
// ^C always cancels regardless of mode.
|
|
52
|
-
if (ev.key ===
|
|
55
|
+
if (ev.key === "char" && ev.ctrl && ev.char === "c") {
|
|
53
56
|
state.viPending = null;
|
|
54
|
-
return { kind:
|
|
57
|
+
return { kind: "cancel" };
|
|
55
58
|
}
|
|
56
59
|
// Route to vi dispatch when in vi modes.
|
|
57
|
-
if (state.mode ===
|
|
60
|
+
if (state.mode === "ex") {
|
|
58
61
|
return dispatchViEx(state, ev);
|
|
59
62
|
}
|
|
60
|
-
if (state.mode ===
|
|
63
|
+
if (state.mode === "normal") {
|
|
61
64
|
return dispatchViNormal(state, ev);
|
|
62
65
|
}
|
|
63
|
-
if (state.mode ===
|
|
66
|
+
if (state.mode === "insert") {
|
|
64
67
|
return dispatchViInsert(state, ev);
|
|
65
68
|
}
|
|
66
69
|
switch (ev.key) {
|
|
67
|
-
case
|
|
70
|
+
case "paste-start":
|
|
68
71
|
state.pasting = true;
|
|
69
|
-
return { kind:
|
|
70
|
-
case
|
|
72
|
+
return { kind: "paste-start" };
|
|
73
|
+
case "paste-end":
|
|
71
74
|
state.pasting = false;
|
|
72
|
-
return { kind:
|
|
73
|
-
case
|
|
74
|
-
return { kind:
|
|
75
|
-
case
|
|
76
|
-
return { kind:
|
|
77
|
-
case
|
|
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:
|
|
85
|
-
case
|
|
87
|
+
return { kind: "redraw" };
|
|
88
|
+
case "delete":
|
|
86
89
|
state.buffer.deleteRight();
|
|
87
|
-
return { kind:
|
|
88
|
-
case
|
|
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:
|
|
94
|
-
case
|
|
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:
|
|
100
|
-
case
|
|
102
|
+
return { kind: "redraw" };
|
|
103
|
+
case "up":
|
|
101
104
|
return navigateHistory(state, -1);
|
|
102
|
-
case
|
|
105
|
+
case "down":
|
|
103
106
|
return navigateHistory(state, +1);
|
|
104
|
-
case
|
|
107
|
+
case "home":
|
|
105
108
|
state.buffer.moveHome();
|
|
106
|
-
return { kind:
|
|
107
|
-
case
|
|
109
|
+
return { kind: "redraw" };
|
|
110
|
+
case "end":
|
|
108
111
|
state.buffer.moveEnd();
|
|
109
|
-
return { kind:
|
|
110
|
-
case
|
|
112
|
+
return { kind: "redraw" };
|
|
113
|
+
case "escape":
|
|
111
114
|
// Bare Escape: ignore (Alt prefixes are decoded into meta:true).
|
|
112
|
-
return { kind:
|
|
113
|
-
case
|
|
115
|
+
return { kind: "noop" };
|
|
116
|
+
case "char":
|
|
114
117
|
return handleChar(state, ev);
|
|
115
|
-
case
|
|
116
|
-
case
|
|
117
|
-
case
|
|
118
|
-
return { kind:
|
|
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:
|
|
127
|
+
return { kind: "noop" };
|
|
125
128
|
if (ev.ctrl) {
|
|
126
129
|
switch (ch) {
|
|
127
|
-
case
|
|
130
|
+
case "a":
|
|
128
131
|
state.buffer.moveHome();
|
|
129
|
-
return { kind:
|
|
130
|
-
case
|
|
132
|
+
return { kind: "redraw" };
|
|
133
|
+
case "e":
|
|
131
134
|
state.buffer.moveEnd();
|
|
132
|
-
return { kind:
|
|
133
|
-
case
|
|
135
|
+
return { kind: "redraw" };
|
|
136
|
+
case "b":
|
|
134
137
|
state.buffer.moveLeft();
|
|
135
|
-
return { kind:
|
|
136
|
-
case
|
|
138
|
+
return { kind: "redraw" };
|
|
139
|
+
case "f":
|
|
137
140
|
state.buffer.moveRight();
|
|
138
|
-
return { kind:
|
|
139
|
-
case
|
|
141
|
+
return { kind: "redraw" };
|
|
142
|
+
case "p":
|
|
140
143
|
return navigateHistory(state, -1);
|
|
141
|
-
case
|
|
144
|
+
case "n":
|
|
142
145
|
return navigateHistory(state, +1);
|
|
143
|
-
case
|
|
146
|
+
case "k":
|
|
144
147
|
state.buffer.killToEnd();
|
|
145
|
-
return { kind:
|
|
146
|
-
case
|
|
148
|
+
return { kind: "redraw" };
|
|
149
|
+
case "u":
|
|
147
150
|
state.buffer.killToStart();
|
|
148
|
-
return { kind:
|
|
149
|
-
case
|
|
151
|
+
return { kind: "redraw" };
|
|
152
|
+
case "w":
|
|
150
153
|
state.buffer.killWordLeft();
|
|
151
|
-
return { kind:
|
|
152
|
-
case
|
|
154
|
+
return { kind: "redraw" };
|
|
155
|
+
case "y": {
|
|
153
156
|
const yanked = state.buffer.yank();
|
|
154
157
|
state.lastYank = yanked ?? null;
|
|
155
|
-
return yanked === undefined
|
|
158
|
+
return yanked === undefined
|
|
159
|
+
? { kind: "bell" }
|
|
160
|
+
: { kind: "redraw" };
|
|
156
161
|
}
|
|
157
|
-
case
|
|
158
|
-
return { kind:
|
|
159
|
-
case
|
|
162
|
+
case "c":
|
|
163
|
+
return { kind: "cancel" };
|
|
164
|
+
case "d":
|
|
160
165
|
if (state.buffer.length === 0)
|
|
161
|
-
return { kind:
|
|
166
|
+
return { kind: "eof" };
|
|
162
167
|
state.buffer.deleteRight();
|
|
163
|
-
return { kind:
|
|
164
|
-
case
|
|
165
|
-
return { kind:
|
|
166
|
-
case
|
|
168
|
+
return { kind: "redraw" };
|
|
169
|
+
case "l":
|
|
170
|
+
return { kind: "clear-screen" };
|
|
171
|
+
case "h":
|
|
167
172
|
state.buffer.deleteLeft();
|
|
168
|
-
return { kind:
|
|
169
|
-
case
|
|
170
|
-
return { kind:
|
|
171
|
-
case
|
|
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:
|
|
181
|
+
return { kind: "redraw" };
|
|
177
182
|
}
|
|
178
|
-
case
|
|
179
|
-
case
|
|
180
|
-
return state.buffer.undo()
|
|
181
|
-
|
|
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:
|
|
190
|
+
return { kind: "bell" };
|
|
184
191
|
default:
|
|
185
|
-
return { kind:
|
|
192
|
+
return { kind: "bell" };
|
|
186
193
|
}
|
|
187
194
|
}
|
|
188
195
|
if (ev.meta) {
|
|
189
196
|
switch (ch) {
|
|
190
|
-
case
|
|
191
|
-
case
|
|
197
|
+
case "b":
|
|
198
|
+
case "B":
|
|
192
199
|
state.buffer.moveWordLeft();
|
|
193
|
-
return { kind:
|
|
194
|
-
case
|
|
195
|
-
case
|
|
200
|
+
return { kind: "redraw" };
|
|
201
|
+
case "f":
|
|
202
|
+
case "F":
|
|
196
203
|
state.buffer.moveWordRight();
|
|
197
|
-
return { kind:
|
|
198
|
-
case
|
|
199
|
-
case
|
|
204
|
+
return { kind: "redraw" };
|
|
205
|
+
case "d":
|
|
206
|
+
case "D":
|
|
200
207
|
state.buffer.killWordRight();
|
|
201
|
-
return { kind:
|
|
202
|
-
case
|
|
203
|
-
case
|
|
208
|
+
return { kind: "redraw" };
|
|
209
|
+
case "y":
|
|
210
|
+
case "Y":
|
|
204
211
|
if (state.lastYank === null)
|
|
205
|
-
return { kind:
|
|
212
|
+
return { kind: "bell" };
|
|
206
213
|
{
|
|
207
214
|
const next = state.buffer.yankPop(state.lastYank);
|
|
208
215
|
if (next === undefined)
|
|
209
|
-
return { kind:
|
|
216
|
+
return { kind: "bell" };
|
|
210
217
|
state.lastYank = next;
|
|
211
218
|
}
|
|
212
|
-
return { kind:
|
|
219
|
+
return { kind: "redraw" };
|
|
213
220
|
default:
|
|
214
|
-
return { kind:
|
|
221
|
+
return { kind: "bell" };
|
|
215
222
|
}
|
|
216
223
|
}
|
|
217
224
|
// Plain printable.
|
|
218
225
|
state.buffer.insert(ch);
|
|
219
|
-
return { kind:
|
|
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:
|
|
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:
|
|
240
|
+
return { kind: "redraw" };
|
|
234
241
|
}
|
|
235
|
-
return { kind:
|
|
242
|
+
return { kind: "bell" };
|
|
236
243
|
}
|
|
237
244
|
const next = state.historyIndex + delta;
|
|
238
245
|
if (next < 0)
|
|
239
|
-
return { kind:
|
|
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:
|
|
252
|
+
return { kind: "redraw" };
|
|
246
253
|
}
|
|
247
254
|
state.historyIndex = next;
|
|
248
255
|
state.buffer.setText(state.history[next]);
|
|
249
|
-
return { kind:
|
|
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
|
|
269
|
+
case "paste-start":
|
|
263
270
|
state.pasting = true;
|
|
264
|
-
return { kind:
|
|
265
|
-
case
|
|
271
|
+
return { kind: "paste-start" };
|
|
272
|
+
case "paste-end":
|
|
266
273
|
state.pasting = false;
|
|
267
|
-
return { kind:
|
|
268
|
-
case
|
|
269
|
-
return { kind:
|
|
270
|
-
case
|
|
271
|
-
return { kind:
|
|
272
|
-
case
|
|
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 =
|
|
282
|
+
state.mode = "normal";
|
|
276
283
|
state.viPending = null;
|
|
277
284
|
if (state.buffer.cursor > 0)
|
|
278
285
|
state.buffer.moveLeft();
|
|
279
|
-
return { kind:
|
|
280
|
-
case
|
|
286
|
+
return { kind: "redraw" };
|
|
287
|
+
case "backspace":
|
|
281
288
|
state.buffer.deleteLeft();
|
|
282
|
-
return { kind:
|
|
283
|
-
case
|
|
289
|
+
return { kind: "redraw" };
|
|
290
|
+
case "delete":
|
|
284
291
|
state.buffer.deleteRight();
|
|
285
|
-
return { kind:
|
|
286
|
-
case
|
|
292
|
+
return { kind: "redraw" };
|
|
293
|
+
case "left":
|
|
287
294
|
state.buffer.moveLeft();
|
|
288
|
-
return { kind:
|
|
289
|
-
case
|
|
295
|
+
return { kind: "redraw" };
|
|
296
|
+
case "right":
|
|
290
297
|
state.buffer.moveRight();
|
|
291
|
-
return { kind:
|
|
292
|
-
case
|
|
298
|
+
return { kind: "redraw" };
|
|
299
|
+
case "up":
|
|
293
300
|
return navigateHistory(state, -1);
|
|
294
|
-
case
|
|
301
|
+
case "down":
|
|
295
302
|
return navigateHistory(state, +1);
|
|
296
|
-
case
|
|
303
|
+
case "home":
|
|
297
304
|
state.buffer.moveHome();
|
|
298
|
-
return { kind:
|
|
299
|
-
case
|
|
305
|
+
return { kind: "redraw" };
|
|
306
|
+
case "end":
|
|
300
307
|
state.buffer.moveEnd();
|
|
301
|
-
return { kind:
|
|
302
|
-
case
|
|
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:
|
|
312
|
+
return { kind: "noop" };
|
|
306
313
|
// ^D on empty buffer still acts as EOF in either vi mode.
|
|
307
|
-
if (ev.ctrl && ch ===
|
|
308
|
-
return { kind:
|
|
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:
|
|
319
|
+
return { kind: "noop" };
|
|
313
320
|
state.buffer.insert(ch);
|
|
314
|
-
return { kind:
|
|
321
|
+
return { kind: "redraw" };
|
|
315
322
|
}
|
|
316
|
-
case
|
|
317
|
-
case
|
|
318
|
-
case
|
|
319
|
-
return { kind:
|
|
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
|
|
335
|
-
return { kind:
|
|
336
|
-
case
|
|
341
|
+
case "enter":
|
|
342
|
+
return { kind: "submit" };
|
|
343
|
+
case "tab":
|
|
337
344
|
// No completion in normal mode (matches vim/readline-vi).
|
|
338
|
-
return { kind:
|
|
339
|
-
case
|
|
345
|
+
return { kind: "bell" };
|
|
346
|
+
case "escape":
|
|
340
347
|
// Already normal; clear any half-formed operator.
|
|
341
348
|
state.viPending = null;
|
|
342
|
-
return { kind:
|
|
343
|
-
case
|
|
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:
|
|
347
|
-
case
|
|
353
|
+
return { kind: "redraw" };
|
|
354
|
+
case "delete":
|
|
348
355
|
state.buffer.deleteRight();
|
|
349
|
-
return { kind:
|
|
350
|
-
case
|
|
356
|
+
return { kind: "redraw" };
|
|
357
|
+
case "left":
|
|
351
358
|
state.buffer.moveLeft();
|
|
352
|
-
return { kind:
|
|
353
|
-
case
|
|
359
|
+
return { kind: "redraw" };
|
|
360
|
+
case "right":
|
|
354
361
|
state.buffer.moveRight();
|
|
355
|
-
return { kind:
|
|
356
|
-
case
|
|
362
|
+
return { kind: "redraw" };
|
|
363
|
+
case "up":
|
|
357
364
|
return navigateHistory(state, -1);
|
|
358
|
-
case
|
|
365
|
+
case "down":
|
|
359
366
|
return navigateHistory(state, +1);
|
|
360
|
-
case
|
|
367
|
+
case "home":
|
|
361
368
|
state.buffer.moveHome();
|
|
362
|
-
return { kind:
|
|
363
|
-
case
|
|
369
|
+
return { kind: "redraw" };
|
|
370
|
+
case "end":
|
|
364
371
|
state.buffer.moveEnd();
|
|
365
|
-
return { kind:
|
|
366
|
-
case
|
|
372
|
+
return { kind: "redraw" };
|
|
373
|
+
case "char":
|
|
367
374
|
return handleViNormalChar(state, ev);
|
|
368
|
-
case
|
|
375
|
+
case "paste-start":
|
|
369
376
|
state.pasting = true;
|
|
370
|
-
return { kind:
|
|
371
|
-
case
|
|
377
|
+
return { kind: "paste-start" };
|
|
378
|
+
case "paste-end":
|
|
372
379
|
state.pasting = false;
|
|
373
|
-
return { kind:
|
|
374
|
-
case
|
|
375
|
-
case
|
|
376
|
-
case
|
|
377
|
-
return { kind:
|
|
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:
|
|
390
|
+
return { kind: "noop" };
|
|
384
391
|
// ^D on empty buffer is EOF in vi normal mode too.
|
|
385
|
-
if (ev.ctrl && ch ===
|
|
386
|
-
return { kind:
|
|
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:
|
|
397
|
+
return { kind: "bell" };
|
|
391
398
|
switch (ch) {
|
|
392
399
|
// Movement
|
|
393
|
-
case
|
|
400
|
+
case "h":
|
|
394
401
|
state.buffer.moveLeft();
|
|
395
|
-
return { kind:
|
|
396
|
-
case
|
|
402
|
+
return { kind: "redraw" };
|
|
403
|
+
case "l":
|
|
397
404
|
state.buffer.moveRight();
|
|
398
|
-
return { kind:
|
|
399
|
-
case
|
|
405
|
+
return { kind: "redraw" };
|
|
406
|
+
case "b":
|
|
400
407
|
state.buffer.moveWordLeft();
|
|
401
|
-
return { kind:
|
|
402
|
-
case
|
|
408
|
+
return { kind: "redraw" };
|
|
409
|
+
case "w":
|
|
403
410
|
state.buffer.moveWordRight();
|
|
404
|
-
return { kind:
|
|
405
|
-
case
|
|
411
|
+
return { kind: "redraw" };
|
|
412
|
+
case "e":
|
|
406
413
|
viMoveEndOfWord(state.buffer);
|
|
407
|
-
return { kind:
|
|
408
|
-
case
|
|
414
|
+
return { kind: "redraw" };
|
|
415
|
+
case "0":
|
|
409
416
|
state.buffer.moveHome();
|
|
410
|
-
return { kind:
|
|
411
|
-
case
|
|
417
|
+
return { kind: "redraw" };
|
|
418
|
+
case "$":
|
|
412
419
|
state.buffer.moveEnd();
|
|
413
|
-
return { kind:
|
|
414
|
-
case
|
|
420
|
+
return { kind: "redraw" };
|
|
421
|
+
case "^":
|
|
415
422
|
viMoveFirstNonBlank(state.buffer);
|
|
416
|
-
return { kind:
|
|
423
|
+
return { kind: "redraw" };
|
|
417
424
|
// History (vi-style j/k).
|
|
418
|
-
case
|
|
425
|
+
case "j":
|
|
419
426
|
return navigateHistory(state, +1);
|
|
420
|
-
case
|
|
427
|
+
case "k":
|
|
421
428
|
return navigateHistory(state, -1);
|
|
422
429
|
// Mode switches.
|
|
423
|
-
case
|
|
424
|
-
state.mode =
|
|
425
|
-
return { kind:
|
|
426
|
-
case
|
|
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 =
|
|
430
|
-
return { kind:
|
|
431
|
-
case
|
|
436
|
+
state.mode = "insert";
|
|
437
|
+
return { kind: "redraw" };
|
|
438
|
+
case "I":
|
|
432
439
|
state.buffer.moveHome();
|
|
433
|
-
state.mode =
|
|
434
|
-
return { kind:
|
|
435
|
-
case
|
|
440
|
+
state.mode = "insert";
|
|
441
|
+
return { kind: "redraw" };
|
|
442
|
+
case "A":
|
|
436
443
|
state.buffer.moveEnd();
|
|
437
|
-
state.mode =
|
|
438
|
-
return { kind:
|
|
444
|
+
state.mode = "insert";
|
|
445
|
+
return { kind: "redraw" };
|
|
439
446
|
// Edits.
|
|
440
|
-
case
|
|
447
|
+
case "x":
|
|
441
448
|
state.buffer.deleteRight();
|
|
442
|
-
return { kind:
|
|
443
|
-
case
|
|
449
|
+
return { kind: "redraw" };
|
|
450
|
+
case "X":
|
|
444
451
|
state.buffer.deleteLeft();
|
|
445
|
-
return { kind:
|
|
446
|
-
case
|
|
452
|
+
return { kind: "redraw" };
|
|
453
|
+
case "D":
|
|
447
454
|
state.buffer.killToEnd();
|
|
448
|
-
return { kind:
|
|
449
|
-
case
|
|
455
|
+
return { kind: "redraw" };
|
|
456
|
+
case "~":
|
|
450
457
|
viToggleCaseAtCursor(state.buffer);
|
|
451
|
-
return { kind:
|
|
458
|
+
return { kind: "redraw" };
|
|
452
459
|
// Multi-key operators: wait for next char.
|
|
453
|
-
case
|
|
454
|
-
state.viPending =
|
|
455
|
-
return { kind:
|
|
456
|
-
case
|
|
457
|
-
state.viPending =
|
|
458
|
-
return { kind:
|
|
459
|
-
case
|
|
460
|
-
state.viPending =
|
|
461
|
-
return { kind:
|
|
462
|
-
case
|
|
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 =
|
|
466
|
-
return { kind:
|
|
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 =
|
|
471
|
-
state.exBuffer =
|
|
472
|
-
return { kind:
|
|
477
|
+
state.mode = "ex";
|
|
478
|
+
state.exBuffer = "";
|
|
479
|
+
return { kind: "ex-update" };
|
|
473
480
|
default:
|
|
474
|
-
return { kind:
|
|
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
|
|
498
|
+
case "escape":
|
|
492
499
|
// Abort ex; back to normal without executing anything.
|
|
493
|
-
state.mode =
|
|
494
|
-
state.exBuffer =
|
|
495
|
-
return { kind:
|
|
496
|
-
case
|
|
500
|
+
state.mode = "normal";
|
|
501
|
+
state.exBuffer = "";
|
|
502
|
+
return { kind: "redraw" };
|
|
503
|
+
case "enter":
|
|
497
504
|
return executeExCommand(state);
|
|
498
|
-
case
|
|
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 =
|
|
503
|
-
return { kind:
|
|
509
|
+
state.mode = "normal";
|
|
510
|
+
return { kind: "redraw" };
|
|
504
511
|
}
|
|
505
512
|
state.exBuffer = state.exBuffer.slice(0, -1);
|
|
506
|
-
return { kind:
|
|
507
|
-
case
|
|
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:
|
|
518
|
+
return { kind: "bell" };
|
|
512
519
|
state.exBuffer += ch;
|
|
513
|
-
return { kind:
|
|
520
|
+
return { kind: "ex-update" };
|
|
514
521
|
}
|
|
515
|
-
case
|
|
516
|
-
case
|
|
517
|
-
case
|
|
518
|
-
case
|
|
519
|
-
case
|
|
520
|
-
case
|
|
521
|
-
case
|
|
522
|
-
case
|
|
523
|
-
case
|
|
524
|
-
case
|
|
525
|
-
case
|
|
526
|
-
case
|
|
527
|
-
case
|
|
528
|
-
return { kind:
|
|
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 =
|
|
546
|
+
state.exBuffer = "";
|
|
547
|
+
state.mode = "normal";
|
|
541
548
|
switch (cmd) {
|
|
542
|
-
case
|
|
543
|
-
case
|
|
544
|
-
case
|
|
549
|
+
case "q":
|
|
550
|
+
case "q!":
|
|
551
|
+
case "quit":
|
|
545
552
|
// Abort the readLine. Same outcome as ^C — driver throws SignalError.
|
|
546
|
-
return { kind:
|
|
547
|
-
case
|
|
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:
|
|
550
|
-
case
|
|
556
|
+
return { kind: "bell" };
|
|
557
|
+
case "":
|
|
551
558
|
// Bare `:` then Enter — just return to normal silently.
|
|
552
|
-
return { kind:
|
|
559
|
+
return { kind: "redraw" };
|
|
553
560
|
default:
|
|
554
|
-
return { kind:
|
|
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 ===
|
|
567
|
-
return { kind:
|
|
568
|
-
if (pending ===
|
|
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 !==
|
|
571
|
-
return { kind:
|
|
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:
|
|
581
|
+
return { kind: "bell" };
|
|
575
582
|
viReplaceCharAtCursor(state.buffer, ev.char);
|
|
576
|
-
return { kind:
|
|
583
|
+
return { kind: "redraw" };
|
|
577
584
|
}
|
|
578
|
-
if (pending ===
|
|
585
|
+
if (pending === "g") {
|
|
579
586
|
// Only 'gg' is recognised; we don't actually implement first-history yet.
|
|
580
|
-
return { kind:
|
|
587
|
+
return { kind: "bell" };
|
|
581
588
|
}
|
|
582
589
|
// dd / cc / dw / cw all key off ev.char.
|
|
583
|
-
if (ev.key !==
|
|
584
|
-
return { kind:
|
|
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 ===
|
|
588
|
-
if (c ===
|
|
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:
|
|
599
|
+
return { kind: "redraw" };
|
|
593
600
|
}
|
|
594
|
-
if (c ===
|
|
601
|
+
if (c === "w") {
|
|
595
602
|
state.buffer.killWordRight();
|
|
596
|
-
return { kind:
|
|
603
|
+
return { kind: "redraw" };
|
|
597
604
|
}
|
|
598
|
-
if (c ===
|
|
605
|
+
if (c === "b") {
|
|
599
606
|
state.buffer.killWordLeft();
|
|
600
|
-
return { kind:
|
|
607
|
+
return { kind: "redraw" };
|
|
601
608
|
}
|
|
602
|
-
if (c ===
|
|
609
|
+
if (c === "$") {
|
|
603
610
|
state.buffer.killToEnd();
|
|
604
|
-
return { kind:
|
|
611
|
+
return { kind: "redraw" };
|
|
605
612
|
}
|
|
606
|
-
if (c ===
|
|
613
|
+
if (c === "0") {
|
|
607
614
|
state.buffer.killToStart();
|
|
608
|
-
return { kind:
|
|
615
|
+
return { kind: "redraw" };
|
|
609
616
|
}
|
|
610
|
-
return { kind:
|
|
617
|
+
return { kind: "bell" };
|
|
611
618
|
}
|
|
612
|
-
if (pending ===
|
|
613
|
-
if (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 =
|
|
618
|
-
return { kind:
|
|
624
|
+
state.mode = "insert";
|
|
625
|
+
return { kind: "redraw" };
|
|
619
626
|
}
|
|
620
|
-
if (c ===
|
|
627
|
+
if (c === "w") {
|
|
621
628
|
state.buffer.killWordRight();
|
|
622
|
-
state.mode =
|
|
623
|
-
return { kind:
|
|
629
|
+
state.mode = "insert";
|
|
630
|
+
return { kind: "redraw" };
|
|
624
631
|
}
|
|
625
|
-
if (c ===
|
|
632
|
+
if (c === "b") {
|
|
626
633
|
state.buffer.killWordLeft();
|
|
627
|
-
state.mode =
|
|
628
|
-
return { kind:
|
|
634
|
+
state.mode = "insert";
|
|
635
|
+
return { kind: "redraw" };
|
|
629
636
|
}
|
|
630
|
-
if (c ===
|
|
637
|
+
if (c === "$") {
|
|
631
638
|
state.buffer.killToEnd();
|
|
632
|
-
state.mode =
|
|
633
|
-
return { kind:
|
|
639
|
+
state.mode = "insert";
|
|
640
|
+
return { kind: "redraw" };
|
|
634
641
|
}
|
|
635
|
-
if (c ===
|
|
642
|
+
if (c === "0") {
|
|
636
643
|
state.buffer.killToStart();
|
|
637
|
-
state.mode =
|
|
638
|
-
return { kind:
|
|
644
|
+
state.mode = "insert";
|
|
645
|
+
return { kind: "redraw" };
|
|
639
646
|
}
|
|
640
|
-
return { kind:
|
|
647
|
+
return { kind: "bell" };
|
|
641
648
|
}
|
|
642
|
-
return { kind:
|
|
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] ===
|
|
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(
|
|
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(
|
|
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(
|
|
744
|
+
buf.setText(cps.join(""), Math.min(i + 1, len));
|
|
738
745
|
};
|