@levu/snap 0.3.9 → 0.3.11
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/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.11] - 2026-02-28
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Added direct handling of bracketed paste payloads from raw input stream to preserve multiline content in Ghostty-like terminals.
|
|
12
|
+
- Kept compatibility with readline `line`-event paste flow while avoiding duplicate line ingestion when raw bracketed payload is available.
|
|
13
|
+
- Added regression coverage for bracketed paste payload capture without `line` events.
|
|
14
|
+
|
|
15
|
+
## [0.3.10] - 2026-02-28
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Seeded multiline prompt readline buffer from `initialValue`, so editing existing values supports character-by-character backspace/delete behavior.
|
|
19
|
+
- Improved multiline paste recovery when terminal submit flow collapses newline-separated URLs into a concatenated single line.
|
|
20
|
+
- Added regression coverage for concatenated submit recovery and initial-value editability.
|
|
21
|
+
|
|
8
22
|
## [0.3.9] - 2026-02-28
|
|
9
23
|
|
|
10
24
|
### Fixed
|
|
@@ -33,6 +33,9 @@ export const createMultilineTextPrompt = () => {
|
|
|
33
33
|
let expectingGhosttySequenceEcho = false;
|
|
34
34
|
let bracketPasteActive = false;
|
|
35
35
|
let bracketPasteProbe = '';
|
|
36
|
+
let bracketPasteBuffer = '';
|
|
37
|
+
let bracketPasteHasRawPayload = false;
|
|
38
|
+
let suppressLineEventsUntil = 0;
|
|
36
39
|
let pendingEnterSubmit = false;
|
|
37
40
|
let pendingEnterSubmitTimer;
|
|
38
41
|
let recentPasteBurstUntil = 0;
|
|
@@ -73,6 +76,10 @@ export const createMultilineTextPrompt = () => {
|
|
|
73
76
|
output.write(pc.dim(` Press Enter to submit; Shift+Enter for newline (Alt+Enter fallback)\n`));
|
|
74
77
|
const lines = value.split('\n');
|
|
75
78
|
let currentLine = lines.length > 0 ? lines.pop() : '';
|
|
79
|
+
rl.line = currentLine;
|
|
80
|
+
if (typeof rl.cursor === 'number') {
|
|
81
|
+
rl.cursor = currentLine.length;
|
|
82
|
+
}
|
|
76
83
|
const getLiveLine = () => {
|
|
77
84
|
const rlLine = typeof rl.line === 'string' ? rl.line : '';
|
|
78
85
|
if (rlLine.length > 0)
|
|
@@ -137,7 +144,10 @@ export const createMultilineTextPrompt = () => {
|
|
|
137
144
|
return '';
|
|
138
145
|
const normalizedPrimary = String(primary || '').trim();
|
|
139
146
|
const recoveredLastLine = recoveredLines[recoveredLines.length - 1] || '';
|
|
140
|
-
|
|
147
|
+
const recoveredJoined = recoveredLines.join('');
|
|
148
|
+
if (normalizedPrimary &&
|
|
149
|
+
recoveredLastLine !== normalizedPrimary &&
|
|
150
|
+
recoveredJoined !== normalizedPrimary)
|
|
141
151
|
return '';
|
|
142
152
|
return recoveredLines.join('\n');
|
|
143
153
|
};
|
|
@@ -168,6 +178,21 @@ export const createMultilineTextPrompt = () => {
|
|
|
168
178
|
const showPrompt = () => {
|
|
169
179
|
output.write(`\n${pc.dim('> ')}${getLiveLine()}`);
|
|
170
180
|
};
|
|
181
|
+
const applyPastedText = (rawPasted) => {
|
|
182
|
+
const normalized = String(rawPasted || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
183
|
+
if (!normalized)
|
|
184
|
+
return;
|
|
185
|
+
const merged = `${getLiveLine()}${normalized}`;
|
|
186
|
+
const pastedLines = merged.split('\n');
|
|
187
|
+
if (pastedLines.length > 1) {
|
|
188
|
+
lines.push(...pastedLines.slice(0, -1));
|
|
189
|
+
currentLine = pastedLines[pastedLines.length - 1];
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
currentLine = merged;
|
|
193
|
+
}
|
|
194
|
+
rl.line = currentLine;
|
|
195
|
+
};
|
|
171
196
|
const BRACKET_PASTE_START = '\u001b[200~';
|
|
172
197
|
const BRACKET_PASTE_END = '\u001b[201~';
|
|
173
198
|
const BRACKET_PASTE_PROBE_MAX = 96;
|
|
@@ -208,6 +233,38 @@ export const createMultilineTextPrompt = () => {
|
|
|
208
233
|
recentPasteBurstUntil = Math.max(recentPasteBurstUntil, Date.now() + PASTE_BURST_WINDOW_MS);
|
|
209
234
|
}
|
|
210
235
|
};
|
|
236
|
+
const ingestBracketedPasteChunk = (chunk) => {
|
|
237
|
+
if (!chunk)
|
|
238
|
+
return;
|
|
239
|
+
let rest = chunk;
|
|
240
|
+
while (rest.length > 0) {
|
|
241
|
+
if (!bracketPasteActive) {
|
|
242
|
+
const startIndex = rest.indexOf(BRACKET_PASTE_START);
|
|
243
|
+
if (startIndex < 0)
|
|
244
|
+
return;
|
|
245
|
+
bracketPasteActive = true;
|
|
246
|
+
rest = rest.slice(startIndex + BRACKET_PASTE_START.length);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const endIndex = rest.indexOf(BRACKET_PASTE_END);
|
|
250
|
+
if (endIndex < 0) {
|
|
251
|
+
if (rest.length > 0)
|
|
252
|
+
bracketPasteHasRawPayload = true;
|
|
253
|
+
bracketPasteBuffer += rest;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (endIndex > 0)
|
|
257
|
+
bracketPasteHasRawPayload = true;
|
|
258
|
+
bracketPasteBuffer += rest.slice(0, endIndex);
|
|
259
|
+
bracketPasteActive = false;
|
|
260
|
+
applyPastedText(bracketPasteBuffer);
|
|
261
|
+
bracketPasteBuffer = '';
|
|
262
|
+
bracketPasteHasRawPayload = false;
|
|
263
|
+
suppressLineEventsUntil = Math.max(suppressLineEventsUntil, Date.now() + 60);
|
|
264
|
+
showPrompt();
|
|
265
|
+
rest = rest.slice(endIndex + BRACKET_PASTE_END.length);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
211
268
|
showPrompt();
|
|
212
269
|
// Handle paste from clipboard
|
|
213
270
|
const handlePaste = async () => {
|
|
@@ -248,6 +305,8 @@ export const createMultilineTextPrompt = () => {
|
|
|
248
305
|
rl.on('line', (line) => {
|
|
249
306
|
if (cancelled)
|
|
250
307
|
return;
|
|
308
|
+
if ((bracketPasteActive && bracketPasteHasRawPayload) || Date.now() < suppressLineEventsUntil)
|
|
309
|
+
return;
|
|
251
310
|
const now = Date.now();
|
|
252
311
|
if (pendingEnterSubmit) {
|
|
253
312
|
const likelyPasteFlow = now < recentPasteBurstUntil;
|
|
@@ -315,6 +374,7 @@ export const createMultilineTextPrompt = () => {
|
|
|
315
374
|
if (cancelled)
|
|
316
375
|
return;
|
|
317
376
|
const text = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk ?? '');
|
|
377
|
+
ingestBracketedPasteChunk(text);
|
|
318
378
|
updateBracketPasteState(text);
|
|
319
379
|
notePossiblePasteBurst(text);
|
|
320
380
|
};
|
|
@@ -355,17 +415,7 @@ export const createMultilineTextPrompt = () => {
|
|
|
355
415
|
if (pasted) {
|
|
356
416
|
// Clear current line and show pasted content
|
|
357
417
|
output.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
|
|
358
|
-
|
|
359
|
-
if (pastedLines.length > 1) {
|
|
360
|
-
// Multiline paste
|
|
361
|
-
lines.push(...pastedLines.slice(0, -1));
|
|
362
|
-
currentLine = pastedLines[pastedLines.length - 1];
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
// Single line paste
|
|
366
|
-
currentLine += pasted;
|
|
367
|
-
}
|
|
368
|
-
rl.line = currentLine;
|
|
418
|
+
applyPastedText(pasted);
|
|
369
419
|
output.write(`${pc.dim('> ')}${currentLine}`);
|
|
370
420
|
}
|
|
371
421
|
}
|