@levu/snap 0.3.0 → 0.3.1
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,14 @@ 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.1] - 2026-02-26
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Multiline text prompt now submits on `Enter` in raw input mode, restoring submit behavior in macOS terminals like JetBrains and Ghostty.
|
|
12
|
+
- Added `Shift+Enter` (and `Alt+Enter` fallback) support to insert newline in multiline prompt mode.
|
|
13
|
+
- Restored terminal state reliably by disabling raw mode and detaching keypress listeners on prompt cleanup.
|
|
14
|
+
- Added regression tests covering `Enter` submit and `Shift+Enter` newline behavior for multiline prompts.
|
|
15
|
+
|
|
8
16
|
## [0.2.0] - 2025-02-24
|
|
9
17
|
|
|
10
18
|
### Added
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createInterface } from 'node:readline';
|
|
1
|
+
import { createInterface, emitKeypressEvents } from 'node:readline';
|
|
2
2
|
import * as pc from 'picocolors';
|
|
3
3
|
export const createMultilineTextPrompt = () => {
|
|
4
4
|
return async (opts) => {
|
|
@@ -25,7 +25,17 @@ export const createMultilineTextPrompt = () => {
|
|
|
25
25
|
});
|
|
26
26
|
let value = initialValue;
|
|
27
27
|
let cancelled = false;
|
|
28
|
+
let rawModeEnabled = false;
|
|
29
|
+
let keypressListener;
|
|
30
|
+
let ignoreNextLineEvent = false;
|
|
28
31
|
const cleanup = () => {
|
|
32
|
+
if (keypressListener) {
|
|
33
|
+
input.off('keypress', keypressListener);
|
|
34
|
+
}
|
|
35
|
+
if (rawModeEnabled && input.setRawMode) {
|
|
36
|
+
input.setRawMode(false);
|
|
37
|
+
rawModeEnabled = false;
|
|
38
|
+
}
|
|
29
39
|
rl.close();
|
|
30
40
|
};
|
|
31
41
|
const submit = (val) => {
|
|
@@ -43,9 +53,15 @@ export const createMultilineTextPrompt = () => {
|
|
|
43
53
|
if (allowPaste) {
|
|
44
54
|
output.write(pc.dim(` Paste support: Ctrl+V to paste (macOS/Linux: Cmd+Shift+V)\n`));
|
|
45
55
|
}
|
|
46
|
-
output.write(pc.dim(` Press Enter
|
|
56
|
+
output.write(pc.dim(` Press Enter to submit; Shift+Enter for newline (Alt+Enter fallback)\n`));
|
|
47
57
|
const lines = value.split('\n');
|
|
48
58
|
let currentLine = lines.length > 0 ? lines.pop() : '';
|
|
59
|
+
const getLiveLine = () => {
|
|
60
|
+
const rlLine = typeof rl.line === 'string' ? rl.line : '';
|
|
61
|
+
if (rlLine.length > 0)
|
|
62
|
+
return rlLine;
|
|
63
|
+
return currentLine;
|
|
64
|
+
};
|
|
49
65
|
const showPrompt = () => {
|
|
50
66
|
output.write(`\n${pc.dim('> ')}${currentLine}`);
|
|
51
67
|
};
|
|
@@ -89,10 +105,14 @@ export const createMultilineTextPrompt = () => {
|
|
|
89
105
|
rl.on('line', (line) => {
|
|
90
106
|
if (cancelled)
|
|
91
107
|
return;
|
|
108
|
+
if (ignoreNextLineEvent) {
|
|
109
|
+
ignoreNextLineEvent = false;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
92
112
|
const now = Date.now();
|
|
93
113
|
// Check for double Enter to submit
|
|
94
114
|
if (line === '' && now - lastEnterTime < DOUBLE_ENTER_TIMEOUT) {
|
|
95
|
-
submit(lines.join('\n')
|
|
115
|
+
submit(lines.concat(getLiveLine()).join('\n'));
|
|
96
116
|
return;
|
|
97
117
|
}
|
|
98
118
|
lastEnterTime = now;
|
|
@@ -124,9 +144,11 @@ export const createMultilineTextPrompt = () => {
|
|
|
124
144
|
}
|
|
125
145
|
// Handle paste via keyboard shortcut
|
|
126
146
|
if (allowPaste && input.setRawMode) {
|
|
147
|
+
emitKeypressEvents(input);
|
|
127
148
|
input.setRawMode(true);
|
|
149
|
+
rawModeEnabled = true;
|
|
128
150
|
input.resume();
|
|
129
|
-
|
|
151
|
+
keypressListener = async (str, key) => {
|
|
130
152
|
if (cancelled)
|
|
131
153
|
return;
|
|
132
154
|
// Detect Ctrl+V or Cmd+V for paste
|
|
@@ -145,17 +167,27 @@ export const createMultilineTextPrompt = () => {
|
|
|
145
167
|
// Single line paste
|
|
146
168
|
currentLine += pasted;
|
|
147
169
|
}
|
|
170
|
+
rl.line = currentLine;
|
|
148
171
|
output.write(`${pc.dim('> ')}${currentLine}`);
|
|
149
172
|
}
|
|
150
173
|
}
|
|
151
|
-
else if (key.
|
|
152
|
-
|
|
153
|
-
|
|
174
|
+
else if (key.name === 'enter' || key.name === 'return') {
|
|
175
|
+
ignoreNextLineEvent = true;
|
|
176
|
+
if (key.shift || key.alt) {
|
|
177
|
+
// Shift+Enter / Alt+Enter inserts a new line.
|
|
178
|
+
lines.push(getLiveLine());
|
|
179
|
+
currentLine = '';
|
|
180
|
+
rl.line = '';
|
|
181
|
+
output.write(`\n${pc.dim('> ')}`);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
submit(lines.concat(getLiveLine()).join('\n'));
|
|
154
185
|
}
|
|
155
186
|
else if (key.name === 'escape') {
|
|
156
187
|
doCancel();
|
|
157
188
|
}
|
|
158
|
-
}
|
|
189
|
+
};
|
|
190
|
+
input.on('keypress', keypressListener);
|
|
159
191
|
}
|
|
160
192
|
// Handle non-interactive terminal
|
|
161
193
|
if (!input.isTTY) {
|
|
@@ -56,9 +56,10 @@ When `paste` is enabled, users can paste from clipboard using:
|
|
|
56
56
|
- macOS/Linux: `Cmd+V` or `Ctrl+V`
|
|
57
57
|
- Windows: `Ctrl+V`
|
|
58
58
|
|
|
59
|
-
Multi-line paste is automatically supported.
|
|
60
|
-
- `
|
|
61
|
-
-
|
|
59
|
+
Multi-line paste is automatically supported. Keyboard behavior:
|
|
60
|
+
- `Enter` submits input
|
|
61
|
+
- `Shift+Enter` inserts a newline (when your terminal reports Shift modifiers)
|
|
62
|
+
- `Alt+Enter` inserts a newline fallback
|
|
62
63
|
|
|
63
64
|
### confirm
|
|
64
65
|
Yes/no confirmation prompt.
|