@psnext/slingcli 2.4.20260601-1 → 2.5.20260602-2
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/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler/package.json +4 -4
- package/node_modules/@aws-sdk/credential-provider-node/dist-cjs/index.js +12 -1
- package/node_modules/@aws-sdk/credential-provider-node/dist-es/runtime/memoize-chain.js +12 -1
- package/node_modules/@aws-sdk/credential-provider-node/package.json +1 -1
- package/node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler/package.json +4 -4
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +81 -18
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +14 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +197 -190
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +84 -21
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +24 -14
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +5 -9
- package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/google.js +5 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js +2 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.js +2 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +130 -62
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +18 -12
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js +4 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +5 -9
- package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/stream.js +14 -2
- package/node_modules/@earendil-works/pi-ai/dist/utils/abort-signals.js +34 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js +8 -7
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js +179 -79
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js +27 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/config.js +9 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.js +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +29 -5
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js +21 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/footer-data-provider.js +29 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js +65 -13
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/resolve-config-value.js +134 -11
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +12 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +167 -96
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js +14 -9
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.js +0 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js +7 -10
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js +5 -7
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +6 -7
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/render-utils.js +17 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js +5 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/index.js +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +69 -16
- package/node_modules/@earendil-works/pi-coding-agent/dist/migrations.js +118 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +2 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +61 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/print-mode.js +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +4 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/deprecation.js +13 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/json.js +7 -0
- package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +71 -56
- package/node_modules/@earendil-works/pi-coding-agent/package.json +7 -7
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +9 -53
- package/node_modules/@earendil-works/pi-tui/dist/components/input.js +6 -54
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +34 -7
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +172 -37
- package/node_modules/@earendil-works/pi-tui/dist/tui.js +166 -22
- package/node_modules/@earendil-works/pi-tui/dist/utils.js +8 -3
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.js +96 -0
- package/node_modules/@earendil-works/pi-tui/package.json +2 -2
- package/node_modules/@mariozechner/clipboard/Cargo.toml +3 -3
- package/node_modules/@mariozechner/clipboard/index.d.ts +34 -20
- package/node_modules/@mariozechner/clipboard/index.js +546 -257
- package/node_modules/@mariozechner/clipboard/package.json +14 -14
- package/node_modules/@mariozechner/clipboard/src/lib.rs +4 -9
- package/node_modules/@mariozechner/clipboard-linux-x64-gnu/clipboard.linux-x64-gnu.node +0 -0
- package/node_modules/@mariozechner/clipboard-linux-x64-gnu/package.json +2 -2
- package/node_modules/@smithy/core/dist-cjs/submodules/retry/index.browser.js +39 -22
- package/node_modules/@smithy/core/dist-cjs/submodules/retry/index.js +39 -22
- package/node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/configurations.js +19 -6
- package/node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/retryMiddleware.js +4 -5
- package/node_modules/@smithy/core/dist-es/submodules/retry/service-error-classification/constants.js +1 -1
- package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/ConfiguredRetryStrategy.js +4 -5
- package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/DefaultRetryToken.js +3 -0
- package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/StandardRetryStrategy.js +9 -5
- package/node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/configurations.d.ts +4 -1
- package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/ConfiguredRetryStrategy.d.ts +1 -2
- package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/DefaultRetryToken.d.ts +3 -0
- package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/StandardRetryStrategy.d.ts +1 -1
- package/node_modules/@smithy/core/package.json +2 -2
- package/node_modules/@smithy/credential-provider-imds/package.json +3 -3
- package/node_modules/@smithy/fetch-http-handler/package.json +4 -4
- package/node_modules/@smithy/signature-v4/package.json +3 -3
- package/node_modules/@smithy/types/dist-types/retry.d.ts +25 -0
- package/node_modules/@smithy/types/package.json +1 -1
- package/package.json +6 -6
- package/slingshot/index.js +231 -230
|
@@ -10,6 +10,23 @@ const TERMINAL_PROGRESS_KEEPALIVE_MS = 1000;
|
|
|
10
10
|
const TERMINAL_PROGRESS_ACTIVE_SEQUENCE = "\x1b]9;4;3\x07";
|
|
11
11
|
const TERMINAL_PROGRESS_CLEAR_SEQUENCE = "\x1b]9;4;0;\x07";
|
|
12
12
|
const APPLE_TERMINAL_SHIFT_ENTER_SEQUENCE = "\x1b[13;2u";
|
|
13
|
+
const DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS = 7;
|
|
14
|
+
const KITTY_KEYBOARD_PROTOCOL_FALLBACK_TIMEOUT_MS = 150;
|
|
15
|
+
const KEYBOARD_PROTOCOL_RESPONSE_FRAGMENT_TIMEOUT_MS = 150;
|
|
16
|
+
const KITTY_KEYBOARD_PROTOCOL_QUERY = `\x1b[>${DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS}u\x1b[?u\x1b[c`;
|
|
17
|
+
export function parseKeyboardProtocolNegotiationSequence(sequence) {
|
|
18
|
+
const kittyFlags = sequence.match(/^\x1b\[\?(\d+)u$/);
|
|
19
|
+
if (kittyFlags) {
|
|
20
|
+
return { type: "kitty-flags", flags: Number.parseInt(kittyFlags[1], 10) };
|
|
21
|
+
}
|
|
22
|
+
if (/^\x1b\[\?[\d;]*c$/.test(sequence)) {
|
|
23
|
+
return { type: "device-attributes" };
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
function isKeyboardProtocolNegotiationSequencePrefix(sequence, allowBareEscapePrefix) {
|
|
28
|
+
return (allowBareEscapePrefix && sequence === "\x1b") || sequence === "\x1b[" || /^\x1b\[\?[\d;]*$/.test(sequence);
|
|
29
|
+
}
|
|
13
30
|
export function isAppleTerminalSession() {
|
|
14
31
|
return process.platform === "darwin" && process.env.TERM_PROGRAM === "Apple_Terminal";
|
|
15
32
|
}
|
|
@@ -27,6 +44,12 @@ export class ProcessTerminal {
|
|
|
27
44
|
resizeHandler;
|
|
28
45
|
_kittyProtocolActive = false;
|
|
29
46
|
_modifyOtherKeysActive = false;
|
|
47
|
+
keyboardProtocolPushed = false;
|
|
48
|
+
keyboardProtocolNegotiationPending = false;
|
|
49
|
+
keyboardProtocolLateResponsePending = false;
|
|
50
|
+
keyboardProtocolNegotiationBuffer = "";
|
|
51
|
+
keyboardProtocolFallbackTimer;
|
|
52
|
+
keyboardProtocolBufferFlushTimer;
|
|
30
53
|
stdinBuffer;
|
|
31
54
|
stdinDataHandler;
|
|
32
55
|
progressInterval;
|
|
@@ -88,30 +111,28 @@ export class ProcessTerminal {
|
|
|
88
111
|
*/
|
|
89
112
|
setupStdinBuffer() {
|
|
90
113
|
this.stdinBuffer = new StdinBuffer({ timeout: 10 });
|
|
91
|
-
// Kitty protocol response pattern: \x1b[?<flags>u
|
|
92
|
-
const kittyResponsePattern = /^\x1b\[\?(\d+)u$/;
|
|
93
114
|
// Forward individual sequences to the input handler
|
|
94
115
|
this.stdinBuffer.on("data", (sequence) => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// Flag 1 = disambiguate escape codes
|
|
103
|
-
// Flag 2 = report event types (press/repeat/release)
|
|
104
|
-
// Flag 4 = report alternate keys (shifted key, base layout key)
|
|
105
|
-
// Base layout key enables shortcuts to work with non-Latin keyboard layouts
|
|
106
|
-
process.stdout.write("\x1b[>7u");
|
|
107
|
-
return; // Don't forward protocol response to TUI
|
|
116
|
+
if (this.keyboardProtocolNegotiationPending) {
|
|
117
|
+
const negotiationSequence = this.readKeyboardProtocolNegotiationSequence(sequence, true);
|
|
118
|
+
if (negotiationSequence === "pending") {
|
|
119
|
+
return; // Wait for the rest of a split negotiation response.
|
|
120
|
+
}
|
|
121
|
+
if (this.handleKeyboardProtocolNegotiationSequence(negotiationSequence)) {
|
|
122
|
+
return;
|
|
108
123
|
}
|
|
109
124
|
}
|
|
110
|
-
if (this.
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
125
|
+
if (this.keyboardProtocolLateResponsePending) {
|
|
126
|
+
const negotiationSequence = this.readKeyboardProtocolNegotiationSequence(sequence, false);
|
|
127
|
+
if (negotiationSequence === "pending") {
|
|
128
|
+
this.scheduleKeyboardProtocolNegotiationBufferFlush();
|
|
129
|
+
return; // Wait for the rest of a split late negotiation response.
|
|
130
|
+
}
|
|
131
|
+
if (this.handleKeyboardProtocolNegotiationSequence(negotiationSequence)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
114
134
|
}
|
|
135
|
+
this.forwardInputSequence(sequence);
|
|
115
136
|
});
|
|
116
137
|
// Re-wrap paste content with bracketed paste markers for existing editor handling
|
|
117
138
|
this.stdinBuffer.on("paste", (content) => {
|
|
@@ -125,29 +146,131 @@ export class ProcessTerminal {
|
|
|
125
146
|
};
|
|
126
147
|
}
|
|
127
148
|
/**
|
|
128
|
-
* Query terminal for Kitty keyboard protocol support and enable if available.
|
|
129
|
-
*
|
|
130
|
-
* Sends CSI ? u to query current flags. If terminal responds with CSI ? <flags> u,
|
|
131
|
-
* it supports the protocol and we enable it with CSI > 1 u.
|
|
149
|
+
* Query terminal for Kitty keyboard protocol support and enable it if available.
|
|
132
150
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
151
|
+
* Kitty's progressive enhancement detection requires requesting the desired
|
|
152
|
+
* flags before querying them. The trailing DA query is a sentinel supported by
|
|
153
|
+
* terminals that do not know Kitty keyboard protocol. A short timeout remains
|
|
154
|
+
* as a backup for terminals, PTYs, and SSH sessions that delay, split, or drop
|
|
155
|
+
* the DA response.
|
|
137
156
|
*
|
|
138
|
-
* The
|
|
139
|
-
*
|
|
157
|
+
* The requested flags are:
|
|
158
|
+
* - 1 = disambiguate escape codes
|
|
159
|
+
* - 2 = report event types (press/repeat/release)
|
|
160
|
+
* - 4 = report alternate keys (shifted key, base layout key)
|
|
140
161
|
*/
|
|
141
162
|
queryAndEnableKittyProtocol() {
|
|
142
163
|
this.setupStdinBuffer();
|
|
143
164
|
process.stdin.on("data", this.stdinDataHandler);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
165
|
+
this.keyboardProtocolPushed = true;
|
|
166
|
+
this.keyboardProtocolNegotiationPending = true;
|
|
167
|
+
this.keyboardProtocolLateResponsePending = false;
|
|
168
|
+
this.clearKeyboardProtocolNegotiationBuffer();
|
|
169
|
+
process.stdout.write(KITTY_KEYBOARD_PROTOCOL_QUERY);
|
|
170
|
+
this.keyboardProtocolFallbackTimer = setTimeout(() => {
|
|
171
|
+
this.keyboardProtocolFallbackTimer = undefined;
|
|
172
|
+
this.keyboardProtocolNegotiationPending = false;
|
|
173
|
+
this.keyboardProtocolLateResponsePending = true;
|
|
174
|
+
if (this.keyboardProtocolNegotiationBuffer === "\x1b") {
|
|
175
|
+
this.flushKeyboardProtocolNegotiationBufferAsInput();
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
this.scheduleKeyboardProtocolNegotiationBufferFlush();
|
|
179
|
+
}
|
|
180
|
+
this.enableModifyOtherKeys();
|
|
181
|
+
}, KITTY_KEYBOARD_PROTOCOL_FALLBACK_TIMEOUT_MS);
|
|
182
|
+
}
|
|
183
|
+
handleKeyboardProtocolNegotiationSequence(negotiationSequence) {
|
|
184
|
+
if (!negotiationSequence)
|
|
185
|
+
return false;
|
|
186
|
+
if (negotiationSequence.type === "kitty-flags") {
|
|
187
|
+
if (negotiationSequence.flags !== 0 && !this._kittyProtocolActive) {
|
|
188
|
+
this._kittyProtocolActive = true;
|
|
189
|
+
setKittyProtocolActive(true);
|
|
190
|
+
this.keyboardProtocolNegotiationPending = false;
|
|
191
|
+
this.keyboardProtocolLateResponsePending = true;
|
|
192
|
+
this.clearKeyboardProtocolNegotiationBuffer();
|
|
193
|
+
this.clearKeyboardProtocolFallbackTimer();
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
this.keyboardProtocolNegotiationPending = false;
|
|
198
|
+
this.keyboardProtocolLateResponsePending = true;
|
|
199
|
+
this.clearKeyboardProtocolNegotiationBuffer();
|
|
200
|
+
this.clearKeyboardProtocolFallbackTimer();
|
|
201
|
+
this.enableModifyOtherKeys();
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
readKeyboardProtocolNegotiationSequence(sequence, allowBareEscapePrefix) {
|
|
205
|
+
if (this.keyboardProtocolNegotiationBuffer) {
|
|
206
|
+
const bufferedSequence = this.keyboardProtocolNegotiationBuffer + sequence;
|
|
207
|
+
const negotiationSequence = parseKeyboardProtocolNegotiationSequence(bufferedSequence);
|
|
208
|
+
if (negotiationSequence) {
|
|
209
|
+
this.clearKeyboardProtocolNegotiationBuffer();
|
|
210
|
+
return negotiationSequence;
|
|
211
|
+
}
|
|
212
|
+
if (isKeyboardProtocolNegotiationSequencePrefix(bufferedSequence, allowBareEscapePrefix)) {
|
|
213
|
+
this.setKeyboardProtocolNegotiationBuffer(bufferedSequence);
|
|
214
|
+
return "pending";
|
|
149
215
|
}
|
|
150
|
-
|
|
216
|
+
this.flushKeyboardProtocolNegotiationBufferAsInput();
|
|
217
|
+
}
|
|
218
|
+
const negotiationSequence = parseKeyboardProtocolNegotiationSequence(sequence);
|
|
219
|
+
if (negotiationSequence)
|
|
220
|
+
return negotiationSequence;
|
|
221
|
+
if (isKeyboardProtocolNegotiationSequencePrefix(sequence, allowBareEscapePrefix)) {
|
|
222
|
+
this.setKeyboardProtocolNegotiationBuffer(sequence);
|
|
223
|
+
return "pending";
|
|
224
|
+
}
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
setKeyboardProtocolNegotiationBuffer(sequence) {
|
|
228
|
+
this.clearKeyboardProtocolNegotiationBufferFlushTimer();
|
|
229
|
+
this.keyboardProtocolNegotiationBuffer = sequence;
|
|
230
|
+
}
|
|
231
|
+
clearKeyboardProtocolNegotiationBuffer() {
|
|
232
|
+
this.clearKeyboardProtocolNegotiationBufferFlushTimer();
|
|
233
|
+
this.keyboardProtocolNegotiationBuffer = "";
|
|
234
|
+
}
|
|
235
|
+
flushKeyboardProtocolNegotiationBufferAsInput() {
|
|
236
|
+
if (!this.keyboardProtocolNegotiationBuffer)
|
|
237
|
+
return;
|
|
238
|
+
const sequence = this.keyboardProtocolNegotiationBuffer;
|
|
239
|
+
this.clearKeyboardProtocolNegotiationBuffer();
|
|
240
|
+
this.forwardInputSequence(sequence);
|
|
241
|
+
}
|
|
242
|
+
scheduleKeyboardProtocolNegotiationBufferFlush() {
|
|
243
|
+
if (!this.keyboardProtocolNegotiationBuffer || this.keyboardProtocolBufferFlushTimer)
|
|
244
|
+
return;
|
|
245
|
+
this.keyboardProtocolBufferFlushTimer = setTimeout(() => {
|
|
246
|
+
this.keyboardProtocolBufferFlushTimer = undefined;
|
|
247
|
+
this.flushKeyboardProtocolNegotiationBufferAsInput();
|
|
248
|
+
}, KEYBOARD_PROTOCOL_RESPONSE_FRAGMENT_TIMEOUT_MS);
|
|
249
|
+
}
|
|
250
|
+
clearKeyboardProtocolNegotiationBufferFlushTimer() {
|
|
251
|
+
if (!this.keyboardProtocolBufferFlushTimer)
|
|
252
|
+
return;
|
|
253
|
+
clearTimeout(this.keyboardProtocolBufferFlushTimer);
|
|
254
|
+
this.keyboardProtocolBufferFlushTimer = undefined;
|
|
255
|
+
}
|
|
256
|
+
forwardInputSequence(sequence) {
|
|
257
|
+
if (!this.inputHandler)
|
|
258
|
+
return;
|
|
259
|
+
const isAppleTerminal = sequence === "\r" && isAppleTerminalSession();
|
|
260
|
+
const input = normalizeAppleTerminalInput(sequence, isAppleTerminal, isAppleTerminal && isNativeModifierPressed("shift"));
|
|
261
|
+
this.inputHandler(input);
|
|
262
|
+
}
|
|
263
|
+
enableModifyOtherKeys() {
|
|
264
|
+
if (this._kittyProtocolActive || this._modifyOtherKeysActive)
|
|
265
|
+
return;
|
|
266
|
+
process.stdout.write("\x1b[>4;2m");
|
|
267
|
+
this._modifyOtherKeysActive = true;
|
|
268
|
+
}
|
|
269
|
+
clearKeyboardProtocolFallbackTimer() {
|
|
270
|
+
if (!this.keyboardProtocolFallbackTimer)
|
|
271
|
+
return;
|
|
272
|
+
clearTimeout(this.keyboardProtocolFallbackTimer);
|
|
273
|
+
this.keyboardProtocolFallbackTimer = undefined;
|
|
151
274
|
}
|
|
152
275
|
/**
|
|
153
276
|
* On Windows, add ENABLE_VIRTUAL_TERMINAL_INPUT (0x0200) to the stdin
|
|
@@ -188,13 +311,19 @@ export class ProcessTerminal {
|
|
|
188
311
|
}
|
|
189
312
|
}
|
|
190
313
|
async drainInput(maxMs = 1000, idleMs = 50) {
|
|
191
|
-
|
|
314
|
+
const shouldDisableKittyProtocol = this.keyboardProtocolPushed || this._kittyProtocolActive || this.keyboardProtocolNegotiationPending;
|
|
315
|
+
this.keyboardProtocolLateResponsePending = false;
|
|
316
|
+
this.clearKeyboardProtocolNegotiationBuffer();
|
|
317
|
+
this.clearKeyboardProtocolFallbackTimer();
|
|
318
|
+
if (shouldDisableKittyProtocol) {
|
|
192
319
|
// Disable Kitty keyboard protocol first so any late key releases
|
|
193
320
|
// do not generate new Kitty escape sequences.
|
|
194
321
|
process.stdout.write("\x1b[<u");
|
|
322
|
+
this.keyboardProtocolPushed = false;
|
|
195
323
|
this._kittyProtocolActive = false;
|
|
196
324
|
setKittyProtocolActive(false);
|
|
197
325
|
}
|
|
326
|
+
this.keyboardProtocolNegotiationPending = false;
|
|
198
327
|
if (this._modifyOtherKeysActive) {
|
|
199
328
|
process.stdout.write("\x1b[>4;0m");
|
|
200
329
|
this._modifyOtherKeysActive = false;
|
|
@@ -229,12 +358,18 @@ export class ProcessTerminal {
|
|
|
229
358
|
}
|
|
230
359
|
// Disable bracketed paste mode
|
|
231
360
|
process.stdout.write("\x1b[?2004l");
|
|
361
|
+
const shouldDisableKittyProtocol = this.keyboardProtocolPushed || this._kittyProtocolActive || this.keyboardProtocolNegotiationPending;
|
|
362
|
+
this.keyboardProtocolLateResponsePending = false;
|
|
363
|
+
this.clearKeyboardProtocolNegotiationBuffer();
|
|
364
|
+
this.clearKeyboardProtocolFallbackTimer();
|
|
232
365
|
// Disable Kitty keyboard protocol if not already done by drainInput()
|
|
233
|
-
if (
|
|
366
|
+
if (shouldDisableKittyProtocol) {
|
|
234
367
|
process.stdout.write("\x1b[<u");
|
|
368
|
+
this.keyboardProtocolPushed = false;
|
|
235
369
|
this._kittyProtocolActive = false;
|
|
236
370
|
setKittyProtocolActive(false);
|
|
237
371
|
}
|
|
372
|
+
this.keyboardProtocolNegotiationPending = false;
|
|
238
373
|
if (this._modifyOtherKeysActive) {
|
|
239
374
|
process.stdout.write("\x1b[>4;0m");
|
|
240
375
|
this._modifyOtherKeysActive = false;
|
|
@@ -118,6 +118,7 @@ export class TUI extends Container {
|
|
|
118
118
|
// Overlay stack for modal components rendered on top of base content
|
|
119
119
|
focusOrderCounter = 0;
|
|
120
120
|
overlayStack = [];
|
|
121
|
+
overlayFocusRestore = { status: "inactive" };
|
|
121
122
|
constructor(terminal, showHardwareCursor) {
|
|
122
123
|
super();
|
|
123
124
|
this.terminal = terminal;
|
|
@@ -152,16 +153,115 @@ export class TUI extends Container {
|
|
|
152
153
|
this.clearOnShrink = enabled;
|
|
153
154
|
}
|
|
154
155
|
setFocus(component) {
|
|
155
|
-
|
|
156
|
+
this.setFocusInternal({ component, overlayFocusRestore: "clear" });
|
|
157
|
+
}
|
|
158
|
+
setFocusInternal({ component, overlayFocusRestore, }) {
|
|
159
|
+
const previousFocus = this.focusedComponent;
|
|
160
|
+
let nextFocus = component;
|
|
161
|
+
const previousFocusedOverlay = previousFocus
|
|
162
|
+
? this.overlayStack.find((entry) => entry.component === previousFocus && this.isOverlayVisible(entry))
|
|
163
|
+
: undefined;
|
|
164
|
+
const nextFocusIsOverlay = nextFocus ? this.overlayStack.some((entry) => entry.component === nextFocus) : false;
|
|
165
|
+
const restoreState = this.getVisibleOverlayFocusRestore();
|
|
166
|
+
if (nextFocus && !nextFocusIsOverlay) {
|
|
167
|
+
if (restoreState.status === "blocked" && restoreState.blockedBy === previousFocus) {
|
|
168
|
+
if (restoreState.resume.status === "focus-target" || !this.isComponentMounted(restoreState.blockedBy)) {
|
|
169
|
+
nextFocus = this.resolveBlockedOverlayFocusResume(restoreState);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
this.overlayFocusRestore = {
|
|
173
|
+
status: "blocked",
|
|
174
|
+
overlay: restoreState.overlay,
|
|
175
|
+
blockedBy: nextFocus,
|
|
176
|
+
resume: restoreState.resume,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else if (previousFocusedOverlay &&
|
|
181
|
+
restoreState.status !== "inactive" &&
|
|
182
|
+
restoreState.overlay === previousFocusedOverlay &&
|
|
183
|
+
!this.isOverlayFocusAncestor(previousFocusedOverlay, nextFocus)) {
|
|
184
|
+
this.overlayFocusRestore = {
|
|
185
|
+
status: "blocked",
|
|
186
|
+
overlay: previousFocusedOverlay,
|
|
187
|
+
blockedBy: nextFocus,
|
|
188
|
+
resume: { status: "restore-overlay" },
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (nextFocus === null) {
|
|
193
|
+
if (restoreState.status === "blocked" && restoreState.blockedBy === previousFocus) {
|
|
194
|
+
nextFocus = this.resolveBlockedOverlayFocusResume(restoreState);
|
|
195
|
+
}
|
|
196
|
+
else if (overlayFocusRestore === "clear") {
|
|
197
|
+
this.clearOverlayFocusRestore();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
156
200
|
if (isFocusable(this.focusedComponent)) {
|
|
157
201
|
this.focusedComponent.focused = false;
|
|
158
202
|
}
|
|
159
|
-
this.focusedComponent =
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
203
|
+
this.focusedComponent = nextFocus;
|
|
204
|
+
if (isFocusable(nextFocus)) {
|
|
205
|
+
nextFocus.focused = true;
|
|
206
|
+
}
|
|
207
|
+
const focusedOverlay = nextFocus
|
|
208
|
+
? this.overlayStack.find((entry) => entry.component === nextFocus && this.isOverlayVisible(entry))
|
|
209
|
+
: undefined;
|
|
210
|
+
if (focusedOverlay) {
|
|
211
|
+
this.overlayFocusRestore = { status: "eligible", overlay: focusedOverlay };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
clearOverlayFocusRestore() {
|
|
215
|
+
this.overlayFocusRestore = { status: "inactive" };
|
|
216
|
+
}
|
|
217
|
+
clearOverlayFocusRestoreFor(overlay) {
|
|
218
|
+
if (this.overlayFocusRestore.status !== "inactive" && this.overlayFocusRestore.overlay === overlay) {
|
|
219
|
+
this.clearOverlayFocusRestore();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
resolveBlockedOverlayFocusResume(restoreState) {
|
|
223
|
+
if (restoreState.resume.status === "restore-overlay")
|
|
224
|
+
return restoreState.overlay.component;
|
|
225
|
+
this.clearOverlayFocusRestore();
|
|
226
|
+
return restoreState.resume.target;
|
|
227
|
+
}
|
|
228
|
+
getVisibleOverlayFocusRestore() {
|
|
229
|
+
const restoreState = this.overlayFocusRestore;
|
|
230
|
+
if (restoreState.status === "inactive")
|
|
231
|
+
return restoreState;
|
|
232
|
+
if (!this.overlayStack.includes(restoreState.overlay) || !this.isOverlayVisible(restoreState.overlay)) {
|
|
233
|
+
return { status: "inactive" };
|
|
234
|
+
}
|
|
235
|
+
return restoreState;
|
|
236
|
+
}
|
|
237
|
+
isOverlayFocusAncestor(entry, component) {
|
|
238
|
+
const visited = new Set();
|
|
239
|
+
let current = entry.preFocus;
|
|
240
|
+
while (current && !visited.has(current)) {
|
|
241
|
+
visited.add(current);
|
|
242
|
+
if (current === component)
|
|
243
|
+
return true;
|
|
244
|
+
current = this.overlayStack.find((overlay) => overlay.component === current)?.preFocus ?? null;
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
retargetOverlayPreFocus(removed) {
|
|
249
|
+
for (const overlay of this.overlayStack) {
|
|
250
|
+
if (overlay !== removed && overlay.preFocus === removed.component) {
|
|
251
|
+
overlay.preFocus = removed.preFocus;
|
|
252
|
+
}
|
|
163
253
|
}
|
|
164
254
|
}
|
|
255
|
+
isComponentMounted(component) {
|
|
256
|
+
return this.children.some((child) => this.containsComponent(child, component));
|
|
257
|
+
}
|
|
258
|
+
containsComponent(root, target) {
|
|
259
|
+
if (root === target)
|
|
260
|
+
return true;
|
|
261
|
+
if (!(root instanceof Container))
|
|
262
|
+
return false;
|
|
263
|
+
return root.children.some((child) => this.containsComponent(child, target));
|
|
264
|
+
}
|
|
165
265
|
/**
|
|
166
266
|
* Show an overlay component with configurable positioning and sizing.
|
|
167
267
|
* Returns a handle to control the overlay's visibility.
|
|
@@ -169,7 +269,7 @@ export class TUI extends Container {
|
|
|
169
269
|
showOverlay(component, options) {
|
|
170
270
|
const entry = {
|
|
171
271
|
component,
|
|
172
|
-
options,
|
|
272
|
+
...(options === undefined ? {} : { options }),
|
|
173
273
|
preFocus: this.focusedComponent,
|
|
174
274
|
hidden: false,
|
|
175
275
|
focusOrder: ++this.focusOrderCounter,
|
|
@@ -186,6 +286,8 @@ export class TUI extends Container {
|
|
|
186
286
|
hide: () => {
|
|
187
287
|
const index = this.overlayStack.indexOf(entry);
|
|
188
288
|
if (index !== -1) {
|
|
289
|
+
this.clearOverlayFocusRestoreFor(entry);
|
|
290
|
+
this.retargetOverlayPreFocus(entry);
|
|
189
291
|
this.overlayStack.splice(index, 1);
|
|
190
292
|
// Restore focus if this overlay had focus
|
|
191
293
|
if (this.focusedComponent === component) {
|
|
@@ -203,6 +305,7 @@ export class TUI extends Container {
|
|
|
203
305
|
entry.hidden = hidden;
|
|
204
306
|
// Update focus when hiding/showing
|
|
205
307
|
if (hidden) {
|
|
308
|
+
this.clearOverlayFocusRestoreFor(entry);
|
|
206
309
|
// If this overlay had focus, move focus to next visible or preFocus
|
|
207
310
|
if (this.focusedComponent === component) {
|
|
208
311
|
const topVisible = this.getTopmostVisibleOverlay();
|
|
@@ -222,17 +325,39 @@ export class TUI extends Container {
|
|
|
222
325
|
focus: () => {
|
|
223
326
|
if (!this.overlayStack.includes(entry) || !this.isOverlayVisible(entry))
|
|
224
327
|
return;
|
|
225
|
-
if (this.focusedComponent !== component) {
|
|
226
|
-
this.setFocus(component);
|
|
227
|
-
}
|
|
228
328
|
entry.focusOrder = ++this.focusOrderCounter;
|
|
329
|
+
this.setFocus(component);
|
|
229
330
|
this.requestRender();
|
|
230
331
|
},
|
|
231
|
-
unfocus: () => {
|
|
232
|
-
|
|
332
|
+
unfocus: (unfocusOptions) => {
|
|
333
|
+
const isFocused = this.focusedComponent === component;
|
|
334
|
+
const restoreState = this.overlayFocusRestore;
|
|
335
|
+
const hasPendingRestore = restoreState.status !== "inactive" && restoreState.overlay === entry;
|
|
336
|
+
if (!isFocused && !hasPendingRestore)
|
|
337
|
+
return;
|
|
338
|
+
if (restoreState.status === "blocked" &&
|
|
339
|
+
restoreState.overlay === entry &&
|
|
340
|
+
this.focusedComponent === restoreState.blockedBy) {
|
|
341
|
+
if (unfocusOptions) {
|
|
342
|
+
this.overlayFocusRestore = {
|
|
343
|
+
status: "blocked",
|
|
344
|
+
overlay: entry,
|
|
345
|
+
blockedBy: restoreState.blockedBy,
|
|
346
|
+
resume: { status: "focus-target", target: unfocusOptions.target },
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
this.clearOverlayFocusRestore();
|
|
351
|
+
}
|
|
352
|
+
this.requestRender();
|
|
233
353
|
return;
|
|
234
|
-
|
|
235
|
-
this.
|
|
354
|
+
}
|
|
355
|
+
this.clearOverlayFocusRestoreFor(entry);
|
|
356
|
+
if (isFocused || unfocusOptions) {
|
|
357
|
+
const topVisible = this.getTopmostVisibleOverlay();
|
|
358
|
+
const fallbackTarget = topVisible && topVisible !== entry ? topVisible.component : entry.preFocus;
|
|
359
|
+
this.setFocus(unfocusOptions ? unfocusOptions.target : fallbackTarget);
|
|
360
|
+
}
|
|
236
361
|
this.requestRender();
|
|
237
362
|
},
|
|
238
363
|
isFocused: () => this.focusedComponent === component,
|
|
@@ -240,9 +365,12 @@ export class TUI extends Container {
|
|
|
240
365
|
}
|
|
241
366
|
/** Hide the topmost overlay and restore previous focus. */
|
|
242
367
|
hideOverlay() {
|
|
243
|
-
const overlay = this.overlayStack.
|
|
368
|
+
const overlay = this.overlayStack[this.overlayStack.length - 1];
|
|
244
369
|
if (!overlay)
|
|
245
370
|
return;
|
|
371
|
+
this.clearOverlayFocusRestoreFor(overlay);
|
|
372
|
+
this.retargetOverlayPreFocus(overlay);
|
|
373
|
+
this.overlayStack.pop();
|
|
246
374
|
if (this.focusedComponent === overlay.component) {
|
|
247
375
|
// Find topmost visible overlay, or fall back to preFocus
|
|
248
376
|
const topVisible = this.getTopmostVisibleOverlay();
|
|
@@ -265,16 +393,17 @@ export class TUI extends Container {
|
|
|
265
393
|
}
|
|
266
394
|
return true;
|
|
267
395
|
}
|
|
268
|
-
/** Find the
|
|
396
|
+
/** Find the visual-frontmost visible capturing overlay, if any */
|
|
269
397
|
getTopmostVisibleOverlay() {
|
|
270
|
-
|
|
271
|
-
|
|
398
|
+
let topmost;
|
|
399
|
+
for (const overlay of this.overlayStack) {
|
|
400
|
+
if (overlay.options?.nonCapturing || !this.isOverlayVisible(overlay))
|
|
272
401
|
continue;
|
|
273
|
-
if (
|
|
274
|
-
|
|
402
|
+
if (!topmost || overlay.focusOrder > topmost.focusOrder) {
|
|
403
|
+
topmost = overlay;
|
|
275
404
|
}
|
|
276
405
|
}
|
|
277
|
-
return
|
|
406
|
+
return topmost;
|
|
278
407
|
}
|
|
279
408
|
invalidate() {
|
|
280
409
|
super.invalidate();
|
|
@@ -411,8 +540,23 @@ export class TUI extends Container {
|
|
|
411
540
|
this.setFocus(topVisible.component);
|
|
412
541
|
}
|
|
413
542
|
else {
|
|
414
|
-
|
|
415
|
-
|
|
543
|
+
this.setFocusInternal({ component: focusedOverlay.preFocus, overlayFocusRestore: "preserve" });
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const focusIsOverlay = this.overlayStack.some((o) => o.component === this.focusedComponent);
|
|
547
|
+
if (!focusIsOverlay) {
|
|
548
|
+
const restoreState = this.getVisibleOverlayFocusRestore();
|
|
549
|
+
if (restoreState.status === "eligible") {
|
|
550
|
+
this.setFocus(restoreState.overlay.component);
|
|
551
|
+
}
|
|
552
|
+
else if (restoreState.status === "blocked" && restoreState.blockedBy !== this.focusedComponent) {
|
|
553
|
+
if (restoreState.resume.status === "restore-overlay") {
|
|
554
|
+
this.setFocus(restoreState.overlay.component);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
this.clearOverlayFocusRestore();
|
|
558
|
+
this.setFocus(restoreState.resume.target);
|
|
559
|
+
}
|
|
416
560
|
}
|
|
417
561
|
}
|
|
418
562
|
// Pass input to focused component (including Ctrl+C)
|
|
@@ -604,7 +604,10 @@ export function wrapTextWithAnsi(text, width) {
|
|
|
604
604
|
for (const inputLine of inputLines) {
|
|
605
605
|
// Prepend active ANSI codes from previous lines (except for first line)
|
|
606
606
|
const prefix = result.length > 0 ? tracker.getActiveCodes() : "";
|
|
607
|
-
|
|
607
|
+
const wrappedLines = wrapSingleLine(prefix + inputLine, width);
|
|
608
|
+
for (const wrappedLine of wrappedLines) {
|
|
609
|
+
result.push(wrappedLine);
|
|
610
|
+
}
|
|
608
611
|
// Update tracker with codes from this line for next iteration
|
|
609
612
|
updateTrackerFromText(inputLine, tracker);
|
|
610
613
|
}
|
|
@@ -640,7 +643,9 @@ function wrapSingleLine(line, width) {
|
|
|
640
643
|
}
|
|
641
644
|
// Break long token - breakLongWord handles its own resets
|
|
642
645
|
const broken = breakLongWord(token, width, tracker);
|
|
643
|
-
|
|
646
|
+
for (let i = 0; i < broken.length - 1; i++) {
|
|
647
|
+
wrapped.push(broken[i]);
|
|
648
|
+
}
|
|
644
649
|
currentLine = broken[broken.length - 1];
|
|
645
650
|
currentVisibleLength = visibleWidth(currentLine);
|
|
646
651
|
continue;
|
|
@@ -679,7 +684,7 @@ function wrapSingleLine(line, width) {
|
|
|
679
684
|
// Trailing whitespace can cause lines to exceed the requested width
|
|
680
685
|
return wrapped.length > 0 ? wrapped.map((line) => line.trimEnd()) : [""];
|
|
681
686
|
}
|
|
682
|
-
const PUNCTUATION_REGEX = /[(){}[\]<>.,;:'"!?+\-=*/\\|&%^$#@~`]/;
|
|
687
|
+
export const PUNCTUATION_REGEX = /[(){}[\]<>.,;:'"!?+\-=*/\\|&%^$#@~`]/;
|
|
683
688
|
/**
|
|
684
689
|
* Check if a character is whitespace.
|
|
685
690
|
*/
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { getWordSegmenter, isWhitespaceChar, PUNCTUATION_REGEX } from "./utils.js";
|
|
2
|
+
const wordSegmenter = getWordSegmenter();
|
|
3
|
+
/**
|
|
4
|
+
* Find the cursor position after moving one word backward from `cursor` in `text`.
|
|
5
|
+
* Skips trailing whitespace, then stops at the next word/punctuation boundary.
|
|
6
|
+
*
|
|
7
|
+
* Pure function - does not mutate any state.
|
|
8
|
+
*/
|
|
9
|
+
export function findWordBackward(text, cursor, options) {
|
|
10
|
+
if (cursor <= 0)
|
|
11
|
+
return 0;
|
|
12
|
+
const textBeforeCursor = text.slice(0, cursor);
|
|
13
|
+
const segmentFn = options?.segment;
|
|
14
|
+
const isAtomic = options?.isAtomicSegment;
|
|
15
|
+
const segments = segmentFn ? [...segmentFn(textBeforeCursor)] : [...wordSegmenter.segment(textBeforeCursor)];
|
|
16
|
+
let newCursor = cursor;
|
|
17
|
+
// Skip trailing whitespace
|
|
18
|
+
while (segments.length > 0 &&
|
|
19
|
+
!isAtomic?.(segments[segments.length - 1]?.segment || "") &&
|
|
20
|
+
isWhitespaceChar(segments[segments.length - 1]?.segment || "")) {
|
|
21
|
+
newCursor -= segments.pop()?.segment.length || 0;
|
|
22
|
+
}
|
|
23
|
+
if (segments.length === 0)
|
|
24
|
+
return newCursor;
|
|
25
|
+
const last = segments[segments.length - 1];
|
|
26
|
+
if (isAtomic?.(last.segment)) {
|
|
27
|
+
// Skip one atomic segment.
|
|
28
|
+
newCursor -= last.segment.length;
|
|
29
|
+
}
|
|
30
|
+
else if (last.isWordLike) {
|
|
31
|
+
// Skip inside one word-like segment, preserving ASCII punctuation boundaries.
|
|
32
|
+
const segment = last.segment;
|
|
33
|
+
const matches = [...segment.matchAll(new RegExp(PUNCTUATION_REGEX, "g"))];
|
|
34
|
+
if (matches.length <= 0) {
|
|
35
|
+
newCursor -= segment.length;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const lastMatch = matches[matches.length - 1];
|
|
39
|
+
newCursor -= segment.length - (lastMatch.index + lastMatch[0].length);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// Skip non-word non-whitespace run (punctuation)
|
|
44
|
+
while (segments.length > 0 &&
|
|
45
|
+
!isAtomic?.(segments[segments.length - 1]?.segment || "") &&
|
|
46
|
+
!segments[segments.length - 1]?.isWordLike &&
|
|
47
|
+
!isWhitespaceChar(segments[segments.length - 1]?.segment || "")) {
|
|
48
|
+
newCursor -= segments.pop()?.segment.length || 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return newCursor;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Find the cursor position after moving one word forward from `cursor` in `text`.
|
|
55
|
+
* Skips leading whitespace, then stops at the next word/punctuation boundary.
|
|
56
|
+
*
|
|
57
|
+
* Pure function - does not mutate any state.
|
|
58
|
+
*/
|
|
59
|
+
export function findWordForward(text, cursor, options) {
|
|
60
|
+
if (cursor >= text.length)
|
|
61
|
+
return text.length;
|
|
62
|
+
const textAfterCursor = text.slice(cursor);
|
|
63
|
+
const segmentFn = options?.segment;
|
|
64
|
+
const isAtomic = options?.isAtomicSegment;
|
|
65
|
+
const segments = segmentFn ? segmentFn(textAfterCursor) : wordSegmenter.segment(textAfterCursor);
|
|
66
|
+
const iterator = segments[Symbol.iterator]();
|
|
67
|
+
let next = iterator.next();
|
|
68
|
+
let newCursor = cursor;
|
|
69
|
+
// Skip leading whitespace
|
|
70
|
+
while (!next.done && !isAtomic?.(next.value.segment) && isWhitespaceChar(next.value.segment)) {
|
|
71
|
+
newCursor += next.value.segment.length;
|
|
72
|
+
next = iterator.next();
|
|
73
|
+
}
|
|
74
|
+
if (next.done)
|
|
75
|
+
return newCursor;
|
|
76
|
+
if (isAtomic?.(next.value.segment)) {
|
|
77
|
+
// Skip one atomic segment.
|
|
78
|
+
newCursor += next.value.segment.length;
|
|
79
|
+
}
|
|
80
|
+
else if (next.value.isWordLike) {
|
|
81
|
+
// Skip inside one word-like segment, preserving ASCII punctuation boundaries.
|
|
82
|
+
newCursor += PUNCTUATION_REGEX.exec(next.value.segment)?.index ?? next.value.segment.length;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Skip non-word non-whitespace run (punctuation)
|
|
86
|
+
while (!next.done &&
|
|
87
|
+
!isAtomic?.(next.value.segment) &&
|
|
88
|
+
!next.value.isWordLike &&
|
|
89
|
+
!isWhitespaceChar(next.value.segment)) {
|
|
90
|
+
newCursor += next.value.segment.length;
|
|
91
|
+
next = iterator.next();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return newCursor;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=word-navigation.js.map
|