@levu/snap 0.3.8 → 0.3.10
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.10] - 2026-02-28
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Seeded multiline prompt readline buffer from `initialValue`, so editing existing values supports character-by-character backspace/delete behavior.
|
|
12
|
+
- Improved multiline paste recovery when terminal submit flow collapses newline-separated URLs into a concatenated single line.
|
|
13
|
+
- Added regression coverage for concatenated submit recovery and initial-value editability.
|
|
14
|
+
|
|
15
|
+
## [0.3.9] - 2026-02-28
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Added multiline paste recovery for cases where readline submit flow only exposes the last pasted line.
|
|
19
|
+
- Recovery now uses recent raw paste chunks to reconstruct full multi-line content when the submit payload is unexpectedly single-line.
|
|
20
|
+
- Added regression coverage for the “last line only after multiline paste” scenario.
|
|
21
|
+
|
|
8
22
|
## [0.3.8] - 2026-02-26
|
|
9
23
|
|
|
10
24
|
### Fixed
|
|
@@ -36,6 +36,8 @@ export const createMultilineTextPrompt = () => {
|
|
|
36
36
|
let pendingEnterSubmit = false;
|
|
37
37
|
let pendingEnterSubmitTimer;
|
|
38
38
|
let recentPasteBurstUntil = 0;
|
|
39
|
+
let sawPasteLikeRawInput = false;
|
|
40
|
+
let recentPasteRawBuffer = '';
|
|
39
41
|
const cleanup = () => {
|
|
40
42
|
if (pendingEnterSubmitTimer) {
|
|
41
43
|
clearTimeout(pendingEnterSubmitTimer);
|
|
@@ -71,6 +73,10 @@ export const createMultilineTextPrompt = () => {
|
|
|
71
73
|
output.write(pc.dim(` Press Enter to submit; Shift+Enter for newline (Alt+Enter fallback)\n`));
|
|
72
74
|
const lines = value.split('\n');
|
|
73
75
|
let currentLine = lines.length > 0 ? lines.pop() : '';
|
|
76
|
+
rl.line = currentLine;
|
|
77
|
+
if (typeof rl.cursor === 'number') {
|
|
78
|
+
rl.cursor = currentLine.length;
|
|
79
|
+
}
|
|
74
80
|
const getLiveLine = () => {
|
|
75
81
|
const rlLine = typeof rl.line === 'string' ? rl.line : '';
|
|
76
82
|
if (rlLine.length > 0)
|
|
@@ -114,8 +120,38 @@ export const createMultilineTextPrompt = () => {
|
|
|
114
120
|
const normalizeGhosttyInlineTokens = (raw) => {
|
|
115
121
|
return raw.replace(/(?:\u001b\[13;2u|\[13;2u|\u001b\[27;2;13~|\[27;2;13~|13~|~13)/g, '\n');
|
|
116
122
|
};
|
|
123
|
+
const stripAnsiControls = (raw) => {
|
|
124
|
+
return String(raw || '')
|
|
125
|
+
.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g, '')
|
|
126
|
+
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '');
|
|
127
|
+
};
|
|
128
|
+
const recoverMultilineFromRawPaste = (primary) => {
|
|
129
|
+
if (!sawPasteLikeRawInput)
|
|
130
|
+
return '';
|
|
131
|
+
if (primary.includes('\n'))
|
|
132
|
+
return '';
|
|
133
|
+
if (!recentPasteRawBuffer)
|
|
134
|
+
return '';
|
|
135
|
+
const recoveredLines = stripAnsiControls(recentPasteRawBuffer)
|
|
136
|
+
.replace(/\r/g, '\n')
|
|
137
|
+
.split('\n')
|
|
138
|
+
.map((line) => line.trim())
|
|
139
|
+
.filter(Boolean);
|
|
140
|
+
if (recoveredLines.length < 2)
|
|
141
|
+
return '';
|
|
142
|
+
const normalizedPrimary = String(primary || '').trim();
|
|
143
|
+
const recoveredLastLine = recoveredLines[recoveredLines.length - 1] || '';
|
|
144
|
+
const recoveredJoined = recoveredLines.join('');
|
|
145
|
+
if (normalizedPrimary &&
|
|
146
|
+
recoveredLastLine !== normalizedPrimary &&
|
|
147
|
+
recoveredJoined !== normalizedPrimary)
|
|
148
|
+
return '';
|
|
149
|
+
return recoveredLines.join('\n');
|
|
150
|
+
};
|
|
117
151
|
const buildSubmitValue = () => {
|
|
118
|
-
|
|
152
|
+
const primary = normalizeGhosttyInlineTokens(lines.concat(getLiveLine()).join('\n'));
|
|
153
|
+
const recovered = recoverMultilineFromRawPaste(primary);
|
|
154
|
+
return recovered || primary;
|
|
119
155
|
};
|
|
120
156
|
const absorbLine = (line) => {
|
|
121
157
|
if (line.trim() === '') {
|
|
@@ -143,6 +179,7 @@ export const createMultilineTextPrompt = () => {
|
|
|
143
179
|
const BRACKET_PASTE_END = '\u001b[201~';
|
|
144
180
|
const BRACKET_PASTE_PROBE_MAX = 96;
|
|
145
181
|
const PASTE_BURST_WINDOW_MS = 70;
|
|
182
|
+
const RAW_PASTE_BUFFER_MAX = 8192;
|
|
146
183
|
const updateBracketPasteState = (chunk) => {
|
|
147
184
|
if (!chunk)
|
|
148
185
|
return;
|
|
@@ -173,6 +210,8 @@ export const createMultilineTextPrompt = () => {
|
|
|
173
210
|
// In raw mode, normal typing usually arrives as single-byte chunks.
|
|
174
211
|
// Multi-byte chunks and newlines are strong signals that input is a paste burst.
|
|
175
212
|
if (normalized.length > 1 || /[\r\n]/.test(normalized)) {
|
|
213
|
+
sawPasteLikeRawInput = true;
|
|
214
|
+
recentPasteRawBuffer = `${recentPasteRawBuffer}${normalized}`.slice(-RAW_PASTE_BUFFER_MAX);
|
|
176
215
|
recentPasteBurstUntil = Math.max(recentPasteBurstUntil, Date.now() + PASTE_BURST_WINDOW_MS);
|
|
177
216
|
}
|
|
178
217
|
};
|