mepcli 0.5.0 → 0.6.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 +182 -6
- package/dist/ansi.d.ts +1 -0
- package/dist/ansi.js +1 -0
- package/dist/base.d.ts +1 -1
- package/dist/base.js +1 -10
- package/dist/core.d.ts +26 -1
- package/dist/core.js +72 -0
- package/dist/highlight.d.ts +1 -0
- package/dist/highlight.js +40 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/input.js +26 -14
- package/dist/prompts/autocomplete.d.ts +1 -1
- package/dist/prompts/autocomplete.js +2 -7
- package/dist/prompts/calendar.d.ts +20 -0
- package/dist/prompts/calendar.js +329 -0
- package/dist/prompts/checkbox.d.ts +1 -1
- package/dist/prompts/checkbox.js +38 -8
- package/dist/prompts/code.d.ts +17 -0
- package/dist/prompts/code.js +210 -0
- package/dist/prompts/color.d.ts +14 -0
- package/dist/prompts/color.js +147 -0
- package/dist/prompts/confirm.d.ts +1 -1
- package/dist/prompts/confirm.js +1 -1
- package/dist/prompts/cron.d.ts +13 -0
- package/dist/prompts/cron.js +176 -0
- package/dist/prompts/date.d.ts +1 -1
- package/dist/prompts/date.js +15 -5
- package/dist/prompts/editor.d.ts +14 -0
- package/dist/prompts/editor.js +207 -0
- package/dist/prompts/file.d.ts +7 -0
- package/dist/prompts/file.js +56 -60
- package/dist/prompts/form.d.ts +17 -0
- package/dist/prompts/form.js +225 -0
- package/dist/prompts/grid.d.ts +14 -0
- package/dist/prompts/grid.js +178 -0
- package/dist/prompts/keypress.d.ts +7 -0
- package/dist/prompts/keypress.js +57 -0
- package/dist/prompts/list.d.ts +1 -1
- package/dist/prompts/list.js +42 -22
- package/dist/prompts/multi-select.d.ts +1 -1
- package/dist/prompts/multi-select.js +39 -4
- package/dist/prompts/number.d.ts +1 -1
- package/dist/prompts/number.js +2 -2
- package/dist/prompts/range.d.ts +9 -0
- package/dist/prompts/range.js +140 -0
- package/dist/prompts/rating.d.ts +1 -1
- package/dist/prompts/rating.js +1 -1
- package/dist/prompts/select.d.ts +1 -1
- package/dist/prompts/select.js +1 -1
- package/dist/prompts/slider.d.ts +1 -1
- package/dist/prompts/slider.js +1 -1
- package/dist/prompts/snippet.d.ts +18 -0
- package/dist/prompts/snippet.js +203 -0
- package/dist/prompts/sort.d.ts +1 -1
- package/dist/prompts/sort.js +1 -4
- package/dist/prompts/spam.d.ts +17 -0
- package/dist/prompts/spam.js +62 -0
- package/dist/prompts/table.d.ts +1 -1
- package/dist/prompts/table.js +1 -1
- package/dist/prompts/text.d.ts +1 -0
- package/dist/prompts/text.js +14 -32
- package/dist/prompts/toggle.d.ts +1 -1
- package/dist/prompts/toggle.js +1 -1
- package/dist/prompts/transfer.d.ts +18 -0
- package/dist/prompts/transfer.js +203 -0
- package/dist/prompts/tree-select.d.ts +32 -0
- package/dist/prompts/tree-select.js +277 -0
- package/dist/prompts/tree.d.ts +20 -0
- package/dist/prompts/tree.js +231 -0
- package/dist/prompts/wait.d.ts +18 -0
- package/dist/prompts/wait.js +62 -0
- package/dist/types.d.ts +105 -0
- package/dist/utils.js +6 -2
- package/example.ts +213 -27
- package/package.json +14 -4
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CalendarPrompt = void 0;
|
|
4
|
+
const ansi_1 = require("../ansi");
|
|
5
|
+
const base_1 = require("../base");
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
class CalendarPrompt extends base_1.Prompt {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
super(options);
|
|
10
|
+
this.selection = null;
|
|
11
|
+
this.selectingRange = false; // Internal state for range selection
|
|
12
|
+
// Normalize initial value
|
|
13
|
+
if (Array.isArray(options.initial)) {
|
|
14
|
+
this.selection = [new Date(options.initial[0]), new Date(options.initial[1])];
|
|
15
|
+
this.cursor = new Date(options.initial[1]); // Cursor at end of range
|
|
16
|
+
}
|
|
17
|
+
else if (options.initial) {
|
|
18
|
+
this.selection = new Date(options.initial);
|
|
19
|
+
this.cursor = new Date(options.initial);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
this.cursor = new Date();
|
|
23
|
+
// If range mode but no initial, selection remains null until user picks
|
|
24
|
+
if (this.options.mode !== 'range') {
|
|
25
|
+
this.selection = new Date();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Clone cursor to viewDate to track month view independently
|
|
29
|
+
this.viewDate = new Date(this.cursor);
|
|
30
|
+
this.viewDate.setDate(1);
|
|
31
|
+
}
|
|
32
|
+
getDaysInMonth(year, month) {
|
|
33
|
+
return new Date(year, month + 1, 0).getDate();
|
|
34
|
+
}
|
|
35
|
+
getDayOfWeek(year, month, day) {
|
|
36
|
+
return new Date(year, month, day).getDay();
|
|
37
|
+
}
|
|
38
|
+
generateMonthGrid(year, month) {
|
|
39
|
+
const days = [];
|
|
40
|
+
const firstDayOfMonth = new Date(year, month, 1);
|
|
41
|
+
const startDayOfWeek = firstDayOfMonth.getDay(); // 0 (Sun) to 6 (Sat)
|
|
42
|
+
const weekStart = this.options.weekStart || 0;
|
|
43
|
+
// Calculate days from previous month to fill the first row
|
|
44
|
+
// If startDayOfWeek is 2 (Tue) and weekStart is 0 (Sun), we need 2 days from prev month.
|
|
45
|
+
// If startDayOfWeek is 0 (Sun) and weekStart is 1 (Mon), we need 6 days from prev month.
|
|
46
|
+
const daysFromPrevMonth = (startDayOfWeek - weekStart + 7) % 7;
|
|
47
|
+
if (daysFromPrevMonth === 0 && startDayOfWeek !== weekStart) {
|
|
48
|
+
// Logic check: if starts on same day, 0.
|
|
49
|
+
}
|
|
50
|
+
const prevMonthDate = new Date(year, month, 0); // Last day of prev month
|
|
51
|
+
const prevMonthDaysCount = prevMonthDate.getDate();
|
|
52
|
+
// Add previous month days
|
|
53
|
+
for (let i = daysFromPrevMonth - 1; i >= 0; i--) {
|
|
54
|
+
days.push({
|
|
55
|
+
date: new Date(year, month - 1, prevMonthDaysCount - i),
|
|
56
|
+
inMonth: false
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Add current month days
|
|
60
|
+
const daysInMonth = this.getDaysInMonth(year, month);
|
|
61
|
+
for (let i = 1; i <= daysInMonth; i++) {
|
|
62
|
+
days.push({
|
|
63
|
+
date: new Date(year, month, i),
|
|
64
|
+
inMonth: true
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Add next month days to fill 42 cells (6 rows * 7 cols)
|
|
68
|
+
let nextMonthDay = 1;
|
|
69
|
+
while (days.length < 42) {
|
|
70
|
+
days.push({
|
|
71
|
+
date: new Date(year, month + 1, nextMonthDay++),
|
|
72
|
+
inMonth: false
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return days;
|
|
76
|
+
}
|
|
77
|
+
isSameDay(d1, d2) {
|
|
78
|
+
return d1.getFullYear() === d2.getFullYear() &&
|
|
79
|
+
d1.getMonth() === d2.getMonth() &&
|
|
80
|
+
d1.getDate() === d2.getDate();
|
|
81
|
+
}
|
|
82
|
+
isSelected(d) {
|
|
83
|
+
if (!this.selection)
|
|
84
|
+
return false;
|
|
85
|
+
if (this.options.mode === 'range') {
|
|
86
|
+
if (Array.isArray(this.selection)) {
|
|
87
|
+
const [start, end] = this.selection;
|
|
88
|
+
// If the range is complete, highlight everything in between
|
|
89
|
+
// Sort to ensure valid range check even if start > end (though we should normalize)
|
|
90
|
+
const s = start < end ? start : end;
|
|
91
|
+
const e = start < end ? end : start;
|
|
92
|
+
// Set times to midnight for comparison
|
|
93
|
+
const dTime = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
|
94
|
+
const sTime = new Date(s.getFullYear(), s.getMonth(), s.getDate()).getTime();
|
|
95
|
+
const eTime = new Date(e.getFullYear(), e.getMonth(), e.getDate()).getTime();
|
|
96
|
+
return dTime >= sTime && dTime <= eTime;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// Selecting range, but only first point picked?
|
|
100
|
+
// Usually when picking range, first click sets start, moving cursor highlights?
|
|
101
|
+
// For now, if selection is single date in range mode, just highlight it
|
|
102
|
+
return this.isSameDay(d, this.selection);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
return this.isSameDay(d, this.selection);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Helper to check if selection is 'in progress' (only one end picked) for visual feedback
|
|
110
|
+
isRangeStart(d) {
|
|
111
|
+
if (this.options.mode !== 'range' || !this.selection)
|
|
112
|
+
return false;
|
|
113
|
+
if (Array.isArray(this.selection)) {
|
|
114
|
+
return this.isSameDay(d, this.selection[0]) || this.isSameDay(d, this.selection[1]);
|
|
115
|
+
}
|
|
116
|
+
return this.isSameDay(d, this.selection);
|
|
117
|
+
}
|
|
118
|
+
render(_firstRender) {
|
|
119
|
+
const monthNames = ["January", "February", "March", "April", "May", "June",
|
|
120
|
+
"July", "August", "September", "October", "November", "December"
|
|
121
|
+
];
|
|
122
|
+
const year = this.viewDate.getFullYear();
|
|
123
|
+
const month = this.viewDate.getMonth();
|
|
124
|
+
const header = `${ansi_1.ANSI.BOLD}${monthNames[month]} ${year}${ansi_1.ANSI.RESET}`;
|
|
125
|
+
// Centered header roughly
|
|
126
|
+
// 20 is approx width of calendar (3 chars * 7 cols - 1 space = 20)
|
|
127
|
+
// Actually grid width: 7 columns. Each cell is usually "DD ". Last col "DD".
|
|
128
|
+
// Let's say cell width is 3 chars (2 digits + 1 space). Total 21 chars.
|
|
129
|
+
const weekDays = this.options.weekStart === 1
|
|
130
|
+
? ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
|
|
131
|
+
: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
132
|
+
const grid = this.generateMonthGrid(year, month);
|
|
133
|
+
let output = `${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}\n`;
|
|
134
|
+
// Controls hint
|
|
135
|
+
output += `${theme_1.theme.muted}< ${header} >${ansi_1.ANSI.RESET}\n`;
|
|
136
|
+
// Weekday Header
|
|
137
|
+
output += weekDays.map(d => `${theme_1.theme.muted}${d}${ansi_1.ANSI.RESET}`).join(' ') + '\n';
|
|
138
|
+
// Grid
|
|
139
|
+
let rowLine = '';
|
|
140
|
+
for (let i = 0; i < grid.length; i++) {
|
|
141
|
+
const cell = grid[i];
|
|
142
|
+
const dateStr = cell.date.getDate().toString().padStart(2, ' ');
|
|
143
|
+
let style = '';
|
|
144
|
+
const isCursor = this.isSameDay(cell.date, this.cursor);
|
|
145
|
+
const isSel = this.isSelected(cell.date);
|
|
146
|
+
const isToday = this.isSameDay(cell.date, new Date());
|
|
147
|
+
// Base style
|
|
148
|
+
if (!cell.inMonth) {
|
|
149
|
+
style = theme_1.theme.muted;
|
|
150
|
+
}
|
|
151
|
+
if (isSel) {
|
|
152
|
+
// If it is selected, use main color
|
|
153
|
+
style = theme_1.theme.main;
|
|
154
|
+
}
|
|
155
|
+
if (isToday && !isSel) {
|
|
156
|
+
style += ansi_1.ANSI.UNDERLINE;
|
|
157
|
+
}
|
|
158
|
+
if (isCursor) {
|
|
159
|
+
style += ansi_1.ANSI.REVERSE; // Invert colors for cursor
|
|
160
|
+
}
|
|
161
|
+
// Apply
|
|
162
|
+
// Reset must be applied after each cell to prevent bleeding
|
|
163
|
+
rowLine += `${style}${dateStr}${ansi_1.ANSI.RESET}`;
|
|
164
|
+
if ((i + 1) % 7 === 0) {
|
|
165
|
+
output += rowLine + '\n';
|
|
166
|
+
rowLine = '';
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
rowLine += ' ';
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Helper text
|
|
173
|
+
const help = this.options.mode === 'range'
|
|
174
|
+
? 'Enter: Select start/end'
|
|
175
|
+
: 'Enter: Select';
|
|
176
|
+
output += `${theme_1.theme.muted}${help}${ansi_1.ANSI.RESET}`;
|
|
177
|
+
this.renderFrame(output);
|
|
178
|
+
}
|
|
179
|
+
syncViewDate() {
|
|
180
|
+
if (this.cursor.getMonth() !== this.viewDate.getMonth() || this.cursor.getFullYear() !== this.viewDate.getFullYear()) {
|
|
181
|
+
this.viewDate = new Date(this.cursor);
|
|
182
|
+
this.viewDate.setDate(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// When the user navigates the visible month independently (mouse scroll or
|
|
186
|
+
// '<'/'>'), keep the cursor in the newly viewed month so subsequent
|
|
187
|
+
// cursor movements don't snap the view back to the old month.
|
|
188
|
+
alignCursorToViewDate() {
|
|
189
|
+
const day = this.cursor.getDate();
|
|
190
|
+
const year = this.viewDate.getFullYear();
|
|
191
|
+
const month = this.viewDate.getMonth();
|
|
192
|
+
const daysInTarget = this.getDaysInMonth(year, month);
|
|
193
|
+
const newDay = Math.min(day, daysInTarget);
|
|
194
|
+
this.cursor = new Date(year, month, newDay);
|
|
195
|
+
}
|
|
196
|
+
handleInput(char, _key) {
|
|
197
|
+
const isUp = this.isUp(char);
|
|
198
|
+
const isDown = this.isDown(char);
|
|
199
|
+
const isLeft = this.isLeft(char);
|
|
200
|
+
const isRight = this.isRight(char);
|
|
201
|
+
// Navigation
|
|
202
|
+
if (isUp || isDown || isLeft || isRight) {
|
|
203
|
+
const d = new Date(this.cursor);
|
|
204
|
+
if (isUp)
|
|
205
|
+
d.setDate(d.getDate() - 7);
|
|
206
|
+
if (isDown)
|
|
207
|
+
d.setDate(d.getDate() + 7);
|
|
208
|
+
if (isLeft)
|
|
209
|
+
d.setDate(d.getDate() - 1);
|
|
210
|
+
if (isRight)
|
|
211
|
+
d.setDate(d.getDate() + 1);
|
|
212
|
+
this.cursor = d;
|
|
213
|
+
this.syncViewDate();
|
|
214
|
+
this.render(false);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// PageUp (Month - 1)
|
|
218
|
+
if (char === '\x1b[5~') {
|
|
219
|
+
this.cursor.setMonth(this.cursor.getMonth() - 1);
|
|
220
|
+
this.syncViewDate();
|
|
221
|
+
this.render(false);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// PageDown (Month + 1)
|
|
225
|
+
if (char === '\x1b[6~') {
|
|
226
|
+
this.cursor.setMonth(this.cursor.getMonth() + 1);
|
|
227
|
+
this.syncViewDate();
|
|
228
|
+
this.render(false);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
// Ctrl+Up (Year - 1)
|
|
232
|
+
if (char === '\x1b[1;5A') {
|
|
233
|
+
this.cursor.setFullYear(this.cursor.getFullYear() - 1);
|
|
234
|
+
this.syncViewDate();
|
|
235
|
+
this.render(false);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
// Ctrl+Down (Year + 1)
|
|
239
|
+
if (char === '\x1b[1;5B') {
|
|
240
|
+
this.cursor.setFullYear(this.cursor.getFullYear() + 1);
|
|
241
|
+
this.syncViewDate();
|
|
242
|
+
this.render(false);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
// Home (First Day of Month)
|
|
246
|
+
if (char === '\x1b[H' || char === '\x1b[1~') {
|
|
247
|
+
this.cursor.setDate(1);
|
|
248
|
+
this.render(false);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// End (Last Day of Month)
|
|
252
|
+
if (char === '\x1b[F' || char === '\x1b[4~') {
|
|
253
|
+
this.cursor.setMonth(this.cursor.getMonth() + 1);
|
|
254
|
+
this.cursor.setDate(0);
|
|
255
|
+
this.render(false);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
// t (Today)
|
|
259
|
+
if (char === 't') {
|
|
260
|
+
this.cursor = new Date();
|
|
261
|
+
this.syncViewDate();
|
|
262
|
+
this.render(false);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
// Month Navigation with < and > (shift+, shift+.)
|
|
266
|
+
if (char === '<' || char === ',') {
|
|
267
|
+
this.viewDate.setMonth(this.viewDate.getMonth() - 1);
|
|
268
|
+
this.alignCursorToViewDate();
|
|
269
|
+
this.render(false);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (char === '>' || char === '.') {
|
|
273
|
+
this.viewDate.setMonth(this.viewDate.getMonth() + 1);
|
|
274
|
+
this.alignCursorToViewDate();
|
|
275
|
+
this.render(false);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
// Selection
|
|
279
|
+
if (char === '\r' || char === '\n' || char === ' ') {
|
|
280
|
+
if (this.options.mode === 'range') {
|
|
281
|
+
if (!this.selectingRange) {
|
|
282
|
+
// Start new range selection
|
|
283
|
+
this.selection = this.cursor; // First point (single date temporary)
|
|
284
|
+
this.selectingRange = true;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
// Finish range selection
|
|
288
|
+
const start = this.selection;
|
|
289
|
+
const end = this.cursor;
|
|
290
|
+
// Order them
|
|
291
|
+
if (start > end) {
|
|
292
|
+
this.selection = [end, start];
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
this.selection = [start, end];
|
|
296
|
+
}
|
|
297
|
+
this.selectingRange = false;
|
|
298
|
+
this.submit(this.selection);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
// Single mode
|
|
304
|
+
this.selection = this.cursor;
|
|
305
|
+
this.submit(this.selection);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
this.render(false);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
handleMouse(event) {
|
|
313
|
+
if (event.action === 'scroll') {
|
|
314
|
+
const direction = event.scroll === 'up' ? -1 : 1;
|
|
315
|
+
if (event.ctrl) {
|
|
316
|
+
// Ctrl+Scroll: Day (Cursor)
|
|
317
|
+
this.cursor.setDate(this.cursor.getDate() + direction);
|
|
318
|
+
this.syncViewDate();
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
// Normal Scroll: Month
|
|
322
|
+
this.viewDate.setMonth(this.viewDate.getMonth() + direction);
|
|
323
|
+
this.alignCursorToViewDate();
|
|
324
|
+
}
|
|
325
|
+
this.render(false);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
exports.CalendarPrompt = CalendarPrompt;
|
|
@@ -7,7 +7,7 @@ export declare class CheckboxPrompt<V> extends Prompt<any[], CheckboxOptions<V>>
|
|
|
7
7
|
private scrollTop;
|
|
8
8
|
private readonly pageSize;
|
|
9
9
|
constructor(options: CheckboxOptions<V>);
|
|
10
|
-
protected render(
|
|
10
|
+
protected render(_firstRender: boolean): void;
|
|
11
11
|
protected handleInput(char: string): void;
|
|
12
12
|
protected handleMouse(event: MouseEvent): void;
|
|
13
13
|
}
|
package/dist/prompts/checkbox.js
CHANGED
|
@@ -11,12 +11,12 @@ class CheckboxPrompt extends base_1.Prompt {
|
|
|
11
11
|
super(options);
|
|
12
12
|
this.selectedIndex = 0;
|
|
13
13
|
this.errorMsg = '';
|
|
14
|
-
// Pagination state
|
|
14
|
+
// Pagination state
|
|
15
15
|
this.scrollTop = 0;
|
|
16
16
|
this.pageSize = 10;
|
|
17
17
|
this.checkedState = options.choices.map(c => !!c.selected);
|
|
18
18
|
}
|
|
19
|
-
render(
|
|
19
|
+
render(_firstRender) {
|
|
20
20
|
// Adjust Scroll Top
|
|
21
21
|
if (this.selectedIndex < this.scrollTop) {
|
|
22
22
|
this.scrollTop = this.selectedIndex;
|
|
@@ -31,7 +31,7 @@ class CheckboxPrompt extends base_1.Prompt {
|
|
|
31
31
|
let output = '';
|
|
32
32
|
// Header
|
|
33
33
|
const icon = this.errorMsg ? `${theme_1.theme.error}${symbols_1.symbols.cross}` : `${theme_1.theme.success}?`;
|
|
34
|
-
output += `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${theme_1.theme.muted}(
|
|
34
|
+
output += `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${theme_1.theme.muted}(Space: toggle, a: all, x: none, Enter: submit)${ansi_1.ANSI.RESET}`;
|
|
35
35
|
// List
|
|
36
36
|
const choices = this.options.choices;
|
|
37
37
|
const visibleChoices = choices.slice(this.scrollTop, this.scrollTop + this.pageSize);
|
|
@@ -50,7 +50,7 @@ class CheckboxPrompt extends base_1.Prompt {
|
|
|
50
50
|
});
|
|
51
51
|
// Indication of more items
|
|
52
52
|
if (choices.length > this.pageSize) {
|
|
53
|
-
const progress = ` ${this.scrollTop + 1}-${Math.min(this.scrollTop + this.pageSize, choices.length)} of ${choices.length}`;
|
|
53
|
+
// const progress = ` ${this.scrollTop + 1}-${Math.min(this.scrollTop + this.pageSize, choices.length)} of ${choices.length}`;
|
|
54
54
|
}
|
|
55
55
|
if (this.errorMsg) {
|
|
56
56
|
output += `\n${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`;
|
|
@@ -72,10 +72,6 @@ class CheckboxPrompt extends base_1.Prompt {
|
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
74
|
this.cleanup();
|
|
75
|
-
// renderFrame cleans up lines, but doesn't print the final state "persisted" if we want to show the result?
|
|
76
|
-
// Usually we clear the prompt or show a summary.
|
|
77
|
-
// MepCLI seems to submit and let the caller decide or just print newline.
|
|
78
|
-
// Base `submit` prints newline.
|
|
79
75
|
const results = this.options.choices
|
|
80
76
|
.filter((_, i) => this.checkedState[i])
|
|
81
77
|
.map(c => c.value);
|
|
@@ -83,6 +79,40 @@ class CheckboxPrompt extends base_1.Prompt {
|
|
|
83
79
|
this._resolve(results);
|
|
84
80
|
return;
|
|
85
81
|
}
|
|
82
|
+
// --- Batch Shortcuts ---
|
|
83
|
+
// 'a': Select All
|
|
84
|
+
if (char === 'a') {
|
|
85
|
+
if (this.options.max && this.options.choices.length > this.options.max) {
|
|
86
|
+
this.errorMsg = `Cannot select all: Max limit is ${this.options.max}`;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
this.checkedState.fill(true);
|
|
90
|
+
this.errorMsg = '';
|
|
91
|
+
}
|
|
92
|
+
this.render(false);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// 'x' or 'n': Select None
|
|
96
|
+
if (char === 'x' || char === 'n') {
|
|
97
|
+
this.checkedState.fill(false);
|
|
98
|
+
this.errorMsg = '';
|
|
99
|
+
this.render(false);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// 'i': Invert Selection
|
|
103
|
+
if (char === 'i') {
|
|
104
|
+
const potentialCount = this.checkedState.filter(s => !s).length;
|
|
105
|
+
if (this.options.max && potentialCount > this.options.max) {
|
|
106
|
+
this.errorMsg = `Cannot invert: Result exceeds max limit ${this.options.max}`;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
this.checkedState = this.checkedState.map(s => !s);
|
|
110
|
+
this.errorMsg = '';
|
|
111
|
+
}
|
|
112
|
+
this.render(false);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Space Toggle
|
|
86
116
|
if (char === ' ') {
|
|
87
117
|
const currentChecked = this.checkedState[this.selectedIndex];
|
|
88
118
|
const selectedCount = this.checkedState.filter(Boolean).length;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { CodeOptions, MouseEvent } from '../types';
|
|
3
|
+
export declare class CodePrompt extends Prompt<string, CodeOptions> {
|
|
4
|
+
private tokens;
|
|
5
|
+
private variableTokens;
|
|
6
|
+
private values;
|
|
7
|
+
private activeVarIndex;
|
|
8
|
+
private cursor;
|
|
9
|
+
private lastLinesUp;
|
|
10
|
+
constructor(options: CodeOptions);
|
|
11
|
+
private parseTemplate;
|
|
12
|
+
protected render(firstRender: boolean): void;
|
|
13
|
+
protected handleInput(char: string, _key: Buffer): void;
|
|
14
|
+
protected handleMouse(event: MouseEvent): void;
|
|
15
|
+
private moveFocus;
|
|
16
|
+
private submitCode;
|
|
17
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CodePrompt = void 0;
|
|
4
|
+
const ansi_1 = require("../ansi");
|
|
5
|
+
const base_1 = require("../base");
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
const highlight_1 = require("../highlight");
|
|
8
|
+
class CodePrompt extends base_1.Prompt {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super(options);
|
|
11
|
+
this.tokens = [];
|
|
12
|
+
this.variableTokens = [];
|
|
13
|
+
this.values = {};
|
|
14
|
+
this.activeVarIndex = 0;
|
|
15
|
+
this.cursor = 0;
|
|
16
|
+
this.lastLinesUp = 0;
|
|
17
|
+
this.parseTemplate();
|
|
18
|
+
// Init values
|
|
19
|
+
this.variableTokens.forEach(idx => {
|
|
20
|
+
const name = this.tokens[idx].value;
|
|
21
|
+
this.values[name] = '';
|
|
22
|
+
});
|
|
23
|
+
// Init cursor at end of first var
|
|
24
|
+
if (this.variableTokens.length > 0) {
|
|
25
|
+
this.cursor = 0; // Start empty
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
parseTemplate() {
|
|
29
|
+
const regex = /\$\{([a-zA-Z0-9_]+)\}/g;
|
|
30
|
+
let lastIndex = 0;
|
|
31
|
+
let match;
|
|
32
|
+
while ((match = regex.exec(this.options.template)) !== null) {
|
|
33
|
+
if (match.index > lastIndex) {
|
|
34
|
+
this.tokens.push({ type: 'static', value: this.options.template.substring(lastIndex, match.index) });
|
|
35
|
+
}
|
|
36
|
+
this.tokens.push({ type: 'variable', value: match[1] });
|
|
37
|
+
this.variableTokens.push(this.tokens.length - 1);
|
|
38
|
+
lastIndex = regex.lastIndex;
|
|
39
|
+
}
|
|
40
|
+
if (lastIndex < this.options.template.length) {
|
|
41
|
+
this.tokens.push({ type: 'static', value: this.options.template.substring(lastIndex) });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
render(firstRender) {
|
|
45
|
+
// Reset cursor from previous render relative position
|
|
46
|
+
if (!firstRender && this.lastLinesUp > 0) {
|
|
47
|
+
this.print(`\x1b[${this.lastLinesUp}B`);
|
|
48
|
+
}
|
|
49
|
+
this.lastLinesUp = 0;
|
|
50
|
+
// 1. Construct Raw String for Highlighting
|
|
51
|
+
const ACTIVE_PLACEHOLDER = '___ACTIVE___';
|
|
52
|
+
let rawWithPlaceholder = '';
|
|
53
|
+
this.tokens.forEach((token, idx) => {
|
|
54
|
+
if (token.type === 'static') {
|
|
55
|
+
rawWithPlaceholder += token.value;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
if (this.variableTokens[this.activeVarIndex] === idx) {
|
|
59
|
+
rawWithPlaceholder += ACTIVE_PLACEHOLDER;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
rawWithPlaceholder += this.values[token.value] || '';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
// 2. Highlight
|
|
67
|
+
let highlighted = '';
|
|
68
|
+
const shouldHighlight = this.options.highlight !== false; // Default true
|
|
69
|
+
if (shouldHighlight) {
|
|
70
|
+
highlighted = (0, highlight_1.highlightJson)(rawWithPlaceholder);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
highlighted = rawWithPlaceholder;
|
|
74
|
+
}
|
|
75
|
+
// 3. Replace Placeholder with Styled Active Value
|
|
76
|
+
const activeVarName = this.tokens[this.variableTokens[this.activeVarIndex]].value;
|
|
77
|
+
const activeVal = this.values[activeVarName] || '';
|
|
78
|
+
// Use Main color + Underline. RESET restores default.
|
|
79
|
+
const styledActive = `${theme_1.theme.main}${ansi_1.ANSI.UNDERLINE}${activeVal}${ansi_1.ANSI.RESET}`;
|
|
80
|
+
highlighted = highlighted.replace(ACTIVE_PLACEHOLDER, styledActive);
|
|
81
|
+
// 4. Output
|
|
82
|
+
const prefix = `${theme_1.theme.success}? ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}\n`;
|
|
83
|
+
const suffix = `\n${theme_1.theme.muted}(Tab to next, Enter to submit)${ansi_1.ANSI.RESET}`;
|
|
84
|
+
const fullOutput = prefix + highlighted + suffix;
|
|
85
|
+
this.renderFrame(fullOutput);
|
|
86
|
+
// 5. Cursor Calculation
|
|
87
|
+
// Calculate (row, col) relative to start of snippet
|
|
88
|
+
let textBeforeCursor = '';
|
|
89
|
+
for (let i = 0; i < this.tokens.length; i++) {
|
|
90
|
+
const token = this.tokens[i];
|
|
91
|
+
if (token.type === 'static') {
|
|
92
|
+
textBeforeCursor += token.value;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
if (this.variableTokens[this.activeVarIndex] === i) {
|
|
96
|
+
textBeforeCursor += activeVal.substring(0, this.cursor);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
textBeforeCursor += this.values[token.value] || '';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const rowsBefore = textBeforeCursor.split('\n');
|
|
105
|
+
const cursorRow = rowsBefore.length - 1;
|
|
106
|
+
const cursorCol = rowsBefore[rowsBefore.length - 1].length;
|
|
107
|
+
// Calculate total lines in snippet
|
|
108
|
+
let fullRaw = '';
|
|
109
|
+
this.tokens.forEach(token => {
|
|
110
|
+
fullRaw += (token.type === 'static' ? token.value : (this.values[token.value] || ''));
|
|
111
|
+
});
|
|
112
|
+
const totalSnippetLines = fullRaw.split('\n').length;
|
|
113
|
+
// Calculate linesUp from the bottom of snippet
|
|
114
|
+
// Suffix is 1 line.
|
|
115
|
+
// CursorRow is 0-based index from top of snippet.
|
|
116
|
+
// If cursorRow is at bottom (totalSnippetLines-1), linesUp = 1 (Suffix).
|
|
117
|
+
// If cursorRow is at top (0), linesUp = 1 + (totalSnippetLines - 1).
|
|
118
|
+
const linesUp = 1 + (totalSnippetLines - 1 - cursorRow);
|
|
119
|
+
this.print(ansi_1.ANSI.SHOW_CURSOR);
|
|
120
|
+
if (linesUp > 0) {
|
|
121
|
+
this.print(`\x1b[${linesUp}A`);
|
|
122
|
+
this.lastLinesUp = linesUp;
|
|
123
|
+
}
|
|
124
|
+
this.print(ansi_1.ANSI.CURSOR_LEFT);
|
|
125
|
+
if (cursorCol > 0) {
|
|
126
|
+
this.print(`\x1b[${cursorCol}C`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
handleInput(char, _key) {
|
|
130
|
+
// Nav
|
|
131
|
+
if (char === '\u001b[Z') { // Shift Tab
|
|
132
|
+
this.moveFocus(-1);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (char === '\t') {
|
|
136
|
+
this.moveFocus(1);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Enter
|
|
140
|
+
if (char === '\r' || char === '\n') {
|
|
141
|
+
this.submitCode();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const activeTokenIdx = this.variableTokens[this.activeVarIndex];
|
|
145
|
+
const varName = this.tokens[activeTokenIdx].value;
|
|
146
|
+
const val = this.values[varName] || '';
|
|
147
|
+
// Editing
|
|
148
|
+
if (char === '\u0008' || char === '\x7f') { // Backspace
|
|
149
|
+
if (this.cursor > 0) {
|
|
150
|
+
const pre = val.slice(0, this.cursor - 1);
|
|
151
|
+
const post = val.slice(this.cursor);
|
|
152
|
+
this.values[varName] = pre + post;
|
|
153
|
+
this.cursor--;
|
|
154
|
+
this.render(false);
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (this.isLeft(char)) {
|
|
159
|
+
if (this.cursor > 0)
|
|
160
|
+
this.cursor--;
|
|
161
|
+
this.render(false);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (this.isRight(char)) {
|
|
165
|
+
if (this.cursor < val.length)
|
|
166
|
+
this.cursor++;
|
|
167
|
+
this.render(false);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (!/^[\x00-\x1F]/.test(char) && !char.startsWith('\x1b')) {
|
|
171
|
+
const pre = val.slice(0, this.cursor);
|
|
172
|
+
const post = val.slice(this.cursor);
|
|
173
|
+
this.values[varName] = pre + char + post;
|
|
174
|
+
this.cursor += char.length;
|
|
175
|
+
this.render(false);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
handleMouse(event) {
|
|
179
|
+
if (event.action === 'scroll') {
|
|
180
|
+
if (event.scroll === 'up') {
|
|
181
|
+
this.moveFocus(-1);
|
|
182
|
+
}
|
|
183
|
+
else if (event.scroll === 'down') {
|
|
184
|
+
this.moveFocus(1);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
moveFocus(direction) {
|
|
189
|
+
const nextIndex = this.activeVarIndex + direction;
|
|
190
|
+
if (nextIndex >= 0 && nextIndex < this.variableTokens.length) {
|
|
191
|
+
this.activeVarIndex = nextIndex;
|
|
192
|
+
const varName = this.tokens[this.variableTokens[this.activeVarIndex]].value;
|
|
193
|
+
this.cursor = (this.values[varName] || '').length; // Move cursor to end
|
|
194
|
+
this.render(false);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
submitCode() {
|
|
198
|
+
let result = '';
|
|
199
|
+
this.tokens.forEach(token => {
|
|
200
|
+
if (token.type === 'static') {
|
|
201
|
+
result += token.value;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
result += this.values[token.value] || '';
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
this.submit(result);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
exports.CodePrompt = CodePrompt;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { ColorOptions, MouseEvent } from '../types';
|
|
3
|
+
export declare class ColorPrompt extends Prompt<string, ColorOptions> {
|
|
4
|
+
private rgb;
|
|
5
|
+
private activeChannel;
|
|
6
|
+
private inputBuffer;
|
|
7
|
+
constructor(options: ColorOptions);
|
|
8
|
+
private parseHex;
|
|
9
|
+
private rgbToHex;
|
|
10
|
+
private getBgColorCode;
|
|
11
|
+
protected render(_firstRender: boolean): void;
|
|
12
|
+
protected handleInput(char: string, _key: Buffer): void;
|
|
13
|
+
protected handleMouse(event: MouseEvent): void;
|
|
14
|
+
}
|