mepcli 0.6.1 → 1.0.0-beta.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/LICENSE +21 -21
- package/README.md +512 -326
- package/dist/ansi.d.ts +8 -0
- package/dist/ansi.js +8 -0
- package/dist/base.d.ts +21 -0
- package/dist/base.js +57 -0
- package/dist/core.d.ts +48 -1
- package/dist/core.js +156 -0
- package/dist/data/licenses.d.ts +2 -0
- package/dist/data/licenses.js +109 -0
- package/dist/highlight.js +58 -26
- package/dist/index.d.ts +36 -0
- package/dist/index.js +36 -0
- package/dist/input.js +0 -3
- package/dist/prompts/box.d.ts +21 -0
- package/dist/prompts/box.js +192 -0
- package/dist/prompts/breadcrumb.d.ts +22 -0
- package/dist/prompts/breadcrumb.js +302 -0
- package/dist/prompts/byte.d.ts +13 -0
- package/dist/prompts/byte.js +159 -0
- package/dist/prompts/calculator.d.ts +16 -0
- package/dist/prompts/calculator.js +213 -0
- package/dist/prompts/calendar.js +0 -5
- package/dist/prompts/code.d.ts +2 -0
- package/dist/prompts/code.js +104 -70
- package/dist/prompts/connection-string.d.ts +18 -0
- package/dist/prompts/connection-string.js +97 -0
- package/dist/prompts/curl.d.ts +39 -0
- package/dist/prompts/curl.js +285 -0
- package/dist/prompts/data-inspector.d.ts +22 -0
- package/dist/prompts/data-inspector.js +256 -0
- package/dist/prompts/dependency.d.ts +16 -0
- package/dist/prompts/dependency.js +265 -0
- package/dist/prompts/dial.d.ts +10 -0
- package/dist/prompts/dial.js +110 -0
- package/dist/prompts/diff.d.ts +10 -0
- package/dist/prompts/diff.js +101 -0
- package/dist/prompts/draw.d.ts +20 -0
- package/dist/prompts/draw.js +188 -0
- package/dist/prompts/editor.js +0 -4
- package/dist/prompts/emoji.d.ts +18 -0
- package/dist/prompts/emoji.js +228 -0
- package/dist/prompts/exec.d.ts +13 -0
- package/dist/prompts/exec.js +83 -0
- package/dist/prompts/fuzzy.d.ts +12 -0
- package/dist/prompts/fuzzy.js +136 -0
- package/dist/prompts/gauge.d.ts +21 -0
- package/dist/prompts/gauge.js +130 -0
- package/dist/prompts/heatmap.d.ts +13 -0
- package/dist/prompts/heatmap.js +141 -0
- package/dist/prompts/ip.d.ts +11 -0
- package/dist/prompts/ip.js +118 -0
- package/dist/prompts/kanban.d.ts +17 -0
- package/dist/prompts/kanban.js +228 -0
- package/dist/prompts/keypress.js +0 -2
- package/dist/prompts/license.d.ts +9 -0
- package/dist/prompts/license.js +105 -0
- package/dist/prompts/map.d.ts +15 -0
- package/dist/prompts/map.js +199 -0
- package/dist/prompts/match.d.ts +19 -0
- package/dist/prompts/match.js +275 -0
- package/dist/prompts/miller.d.ts +15 -0
- package/dist/prompts/miller.js +221 -0
- package/dist/prompts/multi-column-select.d.ts +10 -0
- package/dist/prompts/multi-column-select.js +166 -0
- package/dist/prompts/number.js +0 -2
- package/dist/prompts/otp.d.ts +10 -0
- package/dist/prompts/otp.js +91 -0
- package/dist/prompts/pattern.d.ts +22 -0
- package/dist/prompts/pattern.js +249 -0
- package/dist/prompts/quiz-select.d.ts +10 -0
- package/dist/prompts/quiz-select.js +104 -0
- package/dist/prompts/quiz-text.d.ts +11 -0
- package/dist/prompts/quiz-text.js +82 -0
- package/dist/prompts/regex.d.ts +13 -0
- package/dist/prompts/regex.js +131 -0
- package/dist/prompts/region.d.ts +11 -0
- package/dist/prompts/region.js +164 -0
- package/dist/prompts/schedule.d.ts +18 -0
- package/dist/prompts/schedule.js +221 -0
- package/dist/prompts/scroll.d.ts +13 -0
- package/dist/prompts/scroll.js +152 -0
- package/dist/prompts/seat.d.ts +17 -0
- package/dist/prompts/seat.js +165 -0
- package/dist/prompts/select-range.d.ts +8 -0
- package/dist/prompts/select-range.js +136 -0
- package/dist/prompts/select.d.ts +9 -9
- package/dist/prompts/semver.d.ts +6 -0
- package/dist/prompts/semver.js +32 -0
- package/dist/prompts/shortcut.d.ts +9 -0
- package/dist/prompts/shortcut.js +135 -0
- package/dist/prompts/slot.d.ts +16 -0
- package/dist/prompts/slot.js +107 -0
- package/dist/prompts/snippet.js +0 -3
- package/dist/prompts/sort-grid.d.ts +16 -0
- package/dist/prompts/sort-grid.js +146 -0
- package/dist/prompts/sort.js +0 -1
- package/dist/prompts/spreadsheet.d.ts +21 -0
- package/dist/prompts/spreadsheet.js +239 -0
- package/dist/prompts/text.d.ts +9 -7
- package/dist/prompts/text.js +52 -0
- package/dist/prompts/time.d.ts +12 -0
- package/dist/prompts/time.js +202 -0
- package/dist/prompts/tree-select.d.ts +0 -1
- package/dist/prompts/tree-select.js +1 -5
- package/dist/symbols.d.ts +12 -0
- package/dist/symbols.js +14 -2
- package/dist/theme.js +10 -1
- package/dist/types.d.ts +264 -1
- package/dist/utils.d.ts +53 -0
- package/dist/utils.js +252 -0
- package/package.json +51 -47
- package/example.ts +0 -390
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MatchPrompt = void 0;
|
|
4
|
+
const ansi_1 = require("../ansi");
|
|
5
|
+
const base_1 = require("../base");
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
const symbols_1 = require("../symbols");
|
|
8
|
+
const utils_1 = require("../utils");
|
|
9
|
+
class MatchPrompt extends base_1.Prompt {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
super(options);
|
|
12
|
+
// Links: Source ID -> Set of Target IDs
|
|
13
|
+
this.links = new Map();
|
|
14
|
+
this.cursorSource = 0;
|
|
15
|
+
this.cursorTarget = 0;
|
|
16
|
+
this.scrollTopSource = 0;
|
|
17
|
+
this.scrollTopTarget = 0;
|
|
18
|
+
this.activeSide = 'source';
|
|
19
|
+
this.pickedSourceIndex = null;
|
|
20
|
+
this.pageSize = 10;
|
|
21
|
+
this.source = this.normalize(options.source);
|
|
22
|
+
this.target = this.normalize(options.target);
|
|
23
|
+
}
|
|
24
|
+
normalize(items) {
|
|
25
|
+
return items.map(item => {
|
|
26
|
+
if (typeof item === 'string') {
|
|
27
|
+
return { id: item, label: item, value: item };
|
|
28
|
+
}
|
|
29
|
+
return item;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
render(_firstRender) {
|
|
33
|
+
const termWidth = process.stdout.columns || 80;
|
|
34
|
+
const colWidth = Math.floor((termWidth - 8) / 2); // -8 for middle spacing and borders
|
|
35
|
+
// Adjust Scroll Top
|
|
36
|
+
if (this.activeSide === 'source') {
|
|
37
|
+
if (this.cursorSource < this.scrollTopSource)
|
|
38
|
+
this.scrollTopSource = this.cursorSource;
|
|
39
|
+
if (this.cursorSource >= this.scrollTopSource + this.pageSize)
|
|
40
|
+
this.scrollTopSource = this.cursorSource - this.pageSize + 1;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
if (this.cursorTarget < this.scrollTopTarget)
|
|
44
|
+
this.scrollTopTarget = this.cursorTarget;
|
|
45
|
+
if (this.cursorTarget >= this.scrollTopTarget + this.pageSize)
|
|
46
|
+
this.scrollTopTarget = this.cursorTarget - this.pageSize + 1;
|
|
47
|
+
}
|
|
48
|
+
let output = `${theme_1.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}\n`;
|
|
49
|
+
// Headers
|
|
50
|
+
const sourceTitle = this.activeSide === 'source' ? `${theme_1.theme.main}Source${ansi_1.ANSI.RESET}` : 'Source';
|
|
51
|
+
const targetTitle = this.activeSide === 'target' ? `${theme_1.theme.main}Target${ansi_1.ANSI.RESET}` : 'Target';
|
|
52
|
+
output += ` ${sourceTitle}`.padEnd(colWidth + 2) + ' ' + ` ${targetTitle}\n`;
|
|
53
|
+
output += ` ${ansi_1.ANSI.DIM}${symbols_1.symbols.line.repeat(colWidth)}${ansi_1.ANSI.RESET} ${ansi_1.ANSI.DIM}${symbols_1.symbols.line.repeat(colWidth)}${ansi_1.ANSI.RESET}\n`;
|
|
54
|
+
// Render Rows
|
|
55
|
+
for (let i = 0; i < this.pageSize; i++) {
|
|
56
|
+
const idxSource = this.scrollTopSource + i;
|
|
57
|
+
const idxTarget = this.scrollTopTarget + i;
|
|
58
|
+
const itemSource = this.source[idxSource];
|
|
59
|
+
const itemTarget = this.target[idxTarget];
|
|
60
|
+
// Source Column
|
|
61
|
+
let sourceStr = '';
|
|
62
|
+
if (itemSource) {
|
|
63
|
+
const isSelected = this.activeSide === 'source' && idxSource === this.cursorSource;
|
|
64
|
+
const isPicked = this.pickedSourceIndex === idxSource;
|
|
65
|
+
const hasLinks = this.links.has(itemSource.id) && this.links.get(itemSource.id).size > 0;
|
|
66
|
+
let prefix = ' ';
|
|
67
|
+
if (isSelected)
|
|
68
|
+
prefix = `${theme_1.theme.main}${symbols_1.symbols.pointer} `;
|
|
69
|
+
if (isPicked)
|
|
70
|
+
prefix = `${theme_1.theme.success}${symbols_1.symbols.pointer} `;
|
|
71
|
+
let title = this.truncate(itemSource.label, colWidth - 4);
|
|
72
|
+
// Color logic
|
|
73
|
+
if (isPicked) {
|
|
74
|
+
title = `${theme_1.theme.success}${title}${ansi_1.ANSI.RESET}`;
|
|
75
|
+
}
|
|
76
|
+
else if (hasLinks) {
|
|
77
|
+
title = `${theme_1.theme.success}${title}${ansi_1.ANSI.RESET}`;
|
|
78
|
+
}
|
|
79
|
+
else if (isSelected) {
|
|
80
|
+
title = `${ansi_1.ANSI.FG_CYAN}${title}${ansi_1.ANSI.RESET}`;
|
|
81
|
+
}
|
|
82
|
+
// Indicator for links
|
|
83
|
+
const linkIndicator = hasLinks ? `${theme_1.theme.success}*${ansi_1.ANSI.RESET}` : ' ';
|
|
84
|
+
sourceStr = `${prefix}${title} ${linkIndicator}`;
|
|
85
|
+
// Highlight connected targets?
|
|
86
|
+
if (isSelected || isPicked) {
|
|
87
|
+
// This is handled in Target render
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
sourceStr = '';
|
|
92
|
+
}
|
|
93
|
+
// Target Column
|
|
94
|
+
let targetStr = '';
|
|
95
|
+
if (itemTarget) {
|
|
96
|
+
const isSelected = this.activeSide === 'target' && idxTarget === this.cursorTarget;
|
|
97
|
+
// Determine if this target is linked to the RELEVANT source
|
|
98
|
+
// Relevant source is: Picked Source OR (if none picked) Cursor Source
|
|
99
|
+
const relevantSourceIdx = this.pickedSourceIndex !== null ? this.pickedSourceIndex : this.cursorSource;
|
|
100
|
+
const relevantSource = this.source[relevantSourceIdx];
|
|
101
|
+
let isLinkedToRelevant = false;
|
|
102
|
+
let isLinkedToAny = false;
|
|
103
|
+
// Check links
|
|
104
|
+
for (const [sId, tIds] of Array.from(this.links.entries())) {
|
|
105
|
+
if (tIds.has(itemTarget.id)) {
|
|
106
|
+
isLinkedToAny = true;
|
|
107
|
+
if (relevantSource && sId === relevantSource.id) {
|
|
108
|
+
isLinkedToRelevant = true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
let prefix = ' ';
|
|
113
|
+
if (isSelected)
|
|
114
|
+
prefix = `${theme_1.theme.main}${symbols_1.symbols.pointer} `;
|
|
115
|
+
let title = this.truncate(itemTarget.label, colWidth - 4);
|
|
116
|
+
if (isLinkedToRelevant) {
|
|
117
|
+
title = `${theme_1.theme.success}${title}${ansi_1.ANSI.RESET}`;
|
|
118
|
+
}
|
|
119
|
+
else if (isLinkedToAny) {
|
|
120
|
+
// Linked to someone else
|
|
121
|
+
title = `${ansi_1.ANSI.DIM}${title}${ansi_1.ANSI.RESET}`;
|
|
122
|
+
}
|
|
123
|
+
else if (isSelected) {
|
|
124
|
+
title = `${ansi_1.ANSI.FG_CYAN}${title}${ansi_1.ANSI.RESET}`;
|
|
125
|
+
}
|
|
126
|
+
// Indicator
|
|
127
|
+
const linkIndicator = isLinkedToRelevant ? `${theme_1.theme.success}<=${ansi_1.ANSI.RESET}` : (isLinkedToAny ? `${ansi_1.ANSI.DIM}<=${ansi_1.ANSI.RESET}` : ' ');
|
|
128
|
+
targetStr = `${linkIndicator} ${prefix}${title}`;
|
|
129
|
+
}
|
|
130
|
+
// Pad Source
|
|
131
|
+
const sourceVisualLen = itemSource ? ((0, utils_1.stringWidth)(this.stripAnsi(sourceStr))) : 0;
|
|
132
|
+
const padding = ' '.repeat(Math.max(0, colWidth - sourceVisualLen + 2)); // +2 extra space
|
|
133
|
+
output += sourceStr + padding + ' ' + targetStr + '\n';
|
|
134
|
+
}
|
|
135
|
+
// Instructions
|
|
136
|
+
if (this.pickedSourceIndex !== null) {
|
|
137
|
+
output += `\n${theme_1.theme.success}Linking: ${this.source[this.pickedSourceIndex].label}${ansi_1.ANSI.RESET}`;
|
|
138
|
+
output += `\n${ansi_1.ANSI.DIM}Select Target to Link/Unlink. Esc to Cancel.${ansi_1.ANSI.RESET}`;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
output += `\n${ansi_1.ANSI.DIM}Space to Pick Source, Enter to Submit.${ansi_1.ANSI.RESET}`;
|
|
142
|
+
}
|
|
143
|
+
this.renderFrame(output);
|
|
144
|
+
}
|
|
145
|
+
handleInput(char) {
|
|
146
|
+
// Navigation
|
|
147
|
+
if (this.isUp(char)) {
|
|
148
|
+
if (this.activeSide === 'source') {
|
|
149
|
+
this.cursorSource = Math.max(0, this.cursorSource - 1);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
this.cursorTarget = Math.max(0, this.cursorTarget - 1);
|
|
153
|
+
}
|
|
154
|
+
this.render(false);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (this.isDown(char)) {
|
|
158
|
+
if (this.activeSide === 'source') {
|
|
159
|
+
this.cursorSource = Math.min(this.source.length - 1, this.cursorSource + 1);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
this.cursorTarget = Math.min(this.target.length - 1, this.cursorTarget + 1);
|
|
163
|
+
}
|
|
164
|
+
this.render(false);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// Space: Action
|
|
168
|
+
if (char === ' ') {
|
|
169
|
+
if (this.activeSide === 'source') {
|
|
170
|
+
// Pick Source
|
|
171
|
+
this.pickedSourceIndex = this.cursorSource;
|
|
172
|
+
this.activeSide = 'target';
|
|
173
|
+
// Try to find first linked target or stay at top?
|
|
174
|
+
// Stay at top or current pos is fine.
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
// Toggle Link
|
|
178
|
+
if (this.pickedSourceIndex !== null) {
|
|
179
|
+
const sId = this.source[this.pickedSourceIndex].id;
|
|
180
|
+
const tId = this.target[this.cursorTarget].id;
|
|
181
|
+
if (!this.links.has(sId)) {
|
|
182
|
+
this.links.set(sId, new Set());
|
|
183
|
+
}
|
|
184
|
+
const sourceLinks = this.links.get(sId);
|
|
185
|
+
if (sourceLinks.has(tId)) {
|
|
186
|
+
// Unlink
|
|
187
|
+
sourceLinks.delete(tId);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// Link
|
|
191
|
+
if (this.options.constraints?.oneToMany === false) {
|
|
192
|
+
for (const [otherSId, tIds] of Array.from(this.links.entries())) {
|
|
193
|
+
if (otherSId !== sId && tIds.has(tId)) {
|
|
194
|
+
tIds.delete(tId);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
sourceLinks.add(tId);
|
|
199
|
+
}
|
|
200
|
+
// Stay in Target side to allow picking more?
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
this.render(false);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// Enter
|
|
207
|
+
if (char === '\r' || char === '\n') {
|
|
208
|
+
// Check Required Constraint
|
|
209
|
+
if (this.options.constraints?.required) {
|
|
210
|
+
// Check if all sources have at least one link
|
|
211
|
+
const allLinked = this.source.every(s => {
|
|
212
|
+
return this.links.has(s.id) && this.links.get(s.id).size > 0;
|
|
213
|
+
});
|
|
214
|
+
if (!allLinked) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Submit Result: Record<SourceID, TargetValue[]>
|
|
219
|
+
const result = {};
|
|
220
|
+
for (const [sId, tIds] of Array.from(this.links.entries())) {
|
|
221
|
+
if (tIds.size > 0) {
|
|
222
|
+
// Map Target IDs back to Values
|
|
223
|
+
const values = Array.from(tIds).map(tid => {
|
|
224
|
+
return this.target.find(t => t.id === tid)?.value;
|
|
225
|
+
});
|
|
226
|
+
result[sId] = values;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
this.submit(result);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// Escape / Backspace to go back to Source
|
|
233
|
+
if (char === '\u001b' || char === '\u0008' || char === '\x7f' || char === 'h') { // h for vim left
|
|
234
|
+
if (this.activeSide === 'target') {
|
|
235
|
+
this.activeSide = 'source';
|
|
236
|
+
this.pickedSourceIndex = null;
|
|
237
|
+
this.render(false);
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// Tab
|
|
242
|
+
if (char === '\t') {
|
|
243
|
+
this.activeSide = this.activeSide === 'source' ? 'target' : 'source';
|
|
244
|
+
// If switching to target without picking, pickedIndex is null.
|
|
245
|
+
// Just viewing mode.
|
|
246
|
+
if (this.activeSide === 'source') {
|
|
247
|
+
this.pickedSourceIndex = null;
|
|
248
|
+
}
|
|
249
|
+
this.render(false);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
handleMouse(event) {
|
|
254
|
+
if (event.action === 'scroll') {
|
|
255
|
+
if (event.scroll === 'up') {
|
|
256
|
+
if (this.activeSide === 'source') {
|
|
257
|
+
this.cursorSource = Math.max(0, this.cursorSource - 1);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
this.cursorTarget = Math.max(0, this.cursorTarget - 1);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
if (this.activeSide === 'source') {
|
|
265
|
+
this.cursorSource = Math.min(this.source.length - 1, this.cursorSource + 1);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
this.cursorTarget = Math.min(this.target.length - 1, this.cursorTarget + 1);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
this.render(false);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
exports.MatchPrompt = MatchPrompt;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { MillerOptions, MouseEvent } from '../types';
|
|
3
|
+
export declare class MillerPrompt<V> extends Prompt<V[], MillerOptions<V>> {
|
|
4
|
+
private selections;
|
|
5
|
+
private activeCol;
|
|
6
|
+
private scrollTops;
|
|
7
|
+
private colWidth;
|
|
8
|
+
private visibleCols;
|
|
9
|
+
constructor(options: MillerOptions<V>);
|
|
10
|
+
private calculateLayout;
|
|
11
|
+
private getColumnData;
|
|
12
|
+
protected render(_firstRender: boolean): void;
|
|
13
|
+
protected handleInput(char: string): void;
|
|
14
|
+
protected handleMouse(event: MouseEvent): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MillerPrompt = void 0;
|
|
4
|
+
const base_1 = require("../base");
|
|
5
|
+
const theme_1 = require("../theme");
|
|
6
|
+
const ansi_1 = require("../ansi");
|
|
7
|
+
const symbols_1 = require("../symbols");
|
|
8
|
+
const utils_1 = require("../utils");
|
|
9
|
+
class MillerPrompt extends base_1.Prompt {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
super(options);
|
|
12
|
+
this.selections = [];
|
|
13
|
+
this.activeCol = 0;
|
|
14
|
+
this.scrollTops = []; // Scroll position for each column
|
|
15
|
+
// Layout
|
|
16
|
+
this.colWidth = 0;
|
|
17
|
+
this.visibleCols = 3;
|
|
18
|
+
this.selections = [0];
|
|
19
|
+
this.scrollTops = [0];
|
|
20
|
+
// TODO: Map initial values to selections if provided
|
|
21
|
+
this.calculateLayout();
|
|
22
|
+
}
|
|
23
|
+
calculateLayout() {
|
|
24
|
+
const termWidth = process.stdout.columns || 80;
|
|
25
|
+
// We aim for 3 columns? Or based on width?
|
|
26
|
+
// Minimum width per column ~ 20?
|
|
27
|
+
this.visibleCols = Math.floor(termWidth / 20);
|
|
28
|
+
if (this.visibleCols < 1)
|
|
29
|
+
this.visibleCols = 1;
|
|
30
|
+
if (this.visibleCols > 4)
|
|
31
|
+
this.visibleCols = 4; // Cap at 4
|
|
32
|
+
this.colWidth = Math.floor((termWidth - 4) / this.visibleCols); // -4 for margins
|
|
33
|
+
}
|
|
34
|
+
getColumnData(depth) {
|
|
35
|
+
let current = this.options.data;
|
|
36
|
+
for (let i = 0; i < depth; i++) {
|
|
37
|
+
const sel = this.selections[i];
|
|
38
|
+
if (current[sel] && current[sel].children) {
|
|
39
|
+
current = current[sel].children;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return current;
|
|
46
|
+
}
|
|
47
|
+
render(_firstRender) {
|
|
48
|
+
let output = '';
|
|
49
|
+
output += `${theme_1.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}\n`;
|
|
50
|
+
let startCol = 0;
|
|
51
|
+
if (this.activeCol >= this.visibleCols) {
|
|
52
|
+
startCol = this.activeCol - this.visibleCols + 1;
|
|
53
|
+
}
|
|
54
|
+
// Construct columns content
|
|
55
|
+
const columnsToRender = [];
|
|
56
|
+
for (let c = startCol; c < startCol + this.visibleCols; c++) {
|
|
57
|
+
const data = this.getColumnData(c);
|
|
58
|
+
if (!data)
|
|
59
|
+
break;
|
|
60
|
+
const rows = [];
|
|
61
|
+
const selectedIdx = this.selections[c] ?? -1;
|
|
62
|
+
const isFocusedCol = c === this.activeCol;
|
|
63
|
+
// Scroll handling
|
|
64
|
+
const pageSize = 10; // Fixed height for now
|
|
65
|
+
if (!this.scrollTops[c])
|
|
66
|
+
this.scrollTops[c] = 0;
|
|
67
|
+
if (selectedIdx !== -1) {
|
|
68
|
+
if (selectedIdx < this.scrollTops[c]) {
|
|
69
|
+
this.scrollTops[c] = selectedIdx;
|
|
70
|
+
}
|
|
71
|
+
else if (selectedIdx >= this.scrollTops[c] + pageSize) {
|
|
72
|
+
this.scrollTops[c] = selectedIdx - pageSize + 1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Render rows
|
|
76
|
+
const start = this.scrollTops[c];
|
|
77
|
+
const end = Math.min(data.length, start + pageSize);
|
|
78
|
+
for (let i = start; i < end; i++) {
|
|
79
|
+
const item = data[i];
|
|
80
|
+
const isSelected = i === selectedIdx;
|
|
81
|
+
const isFocused = isSelected && isFocusedCol;
|
|
82
|
+
let prefix = ' ';
|
|
83
|
+
if (isSelected) {
|
|
84
|
+
prefix = isFocused ? `${theme_1.theme.main}${symbols_1.symbols.pointer}${ansi_1.ANSI.RESET}` : `${theme_1.theme.muted}${symbols_1.symbols.pointer}${ansi_1.ANSI.RESET}`;
|
|
85
|
+
}
|
|
86
|
+
let title = item.title;
|
|
87
|
+
// Truncate
|
|
88
|
+
if ((0, utils_1.stringWidth)(title) > this.colWidth - 4) {
|
|
89
|
+
title = title.slice(0, this.colWidth - 5) + '…';
|
|
90
|
+
}
|
|
91
|
+
let line = `${prefix} ${isSelected && isFocused ? theme_1.theme.main : ''}${title}${ansi_1.ANSI.RESET}`;
|
|
92
|
+
// Add arrow if children exist
|
|
93
|
+
if (item.children && item.children.length > 0) {
|
|
94
|
+
const pad = this.colWidth - (0, utils_1.stringWidth)((0, utils_1.stripAnsi)(line)) - 2;
|
|
95
|
+
line += ' '.repeat(Math.max(0, pad)) + `${theme_1.theme.muted}>${ansi_1.ANSI.RESET}`;
|
|
96
|
+
}
|
|
97
|
+
rows.push(line);
|
|
98
|
+
}
|
|
99
|
+
// Pad column to pageSize
|
|
100
|
+
while (rows.length < pageSize) {
|
|
101
|
+
rows.push('');
|
|
102
|
+
}
|
|
103
|
+
columnsToRender.push(rows);
|
|
104
|
+
}
|
|
105
|
+
// Combine columns side by side
|
|
106
|
+
const rowCount = 10;
|
|
107
|
+
for (let r = 0; r < rowCount; r++) {
|
|
108
|
+
let line = '';
|
|
109
|
+
for (let c = 0; c < columnsToRender.length; c++) {
|
|
110
|
+
let cell = columnsToRender[c][r] || '';
|
|
111
|
+
// Pad cell
|
|
112
|
+
const len = (0, utils_1.stringWidth)((0, utils_1.stripAnsi)(cell));
|
|
113
|
+
const pad = this.colWidth - len;
|
|
114
|
+
cell += ' '.repeat(Math.max(0, pad));
|
|
115
|
+
line += cell;
|
|
116
|
+
// Separator
|
|
117
|
+
if (c < columnsToRender.length - 1) {
|
|
118
|
+
line += `${theme_1.theme.muted}│${ansi_1.ANSI.RESET} `;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
output += line + '\n';
|
|
122
|
+
}
|
|
123
|
+
// Breadcrumbs / Status
|
|
124
|
+
const pathTitles = [];
|
|
125
|
+
for (let i = 0; i <= this.activeCol; i++) {
|
|
126
|
+
const data = this.getColumnData(i);
|
|
127
|
+
if (data && typeof this.selections[i] === 'number') {
|
|
128
|
+
pathTitles.push(data[this.selections[i]].title);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
output += `${theme_1.theme.muted}Path: ${pathTitles.join(' / ')}${ansi_1.ANSI.RESET}`;
|
|
132
|
+
this.renderFrame(output);
|
|
133
|
+
}
|
|
134
|
+
handleInput(char) {
|
|
135
|
+
const currentData = this.getColumnData(this.activeCol);
|
|
136
|
+
if (!currentData)
|
|
137
|
+
return; // Should not happen
|
|
138
|
+
// Enter
|
|
139
|
+
if (char === '\r' || char === '\n') {
|
|
140
|
+
// Collect values
|
|
141
|
+
const values = [];
|
|
142
|
+
for (let i = 0; i <= this.activeCol; i++) {
|
|
143
|
+
const d = this.getColumnData(i);
|
|
144
|
+
if (d)
|
|
145
|
+
values.push(d[this.selections[i]].value);
|
|
146
|
+
}
|
|
147
|
+
this.submit(values);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (this.isUp(char)) {
|
|
151
|
+
if (this.selections[this.activeCol] > 0) {
|
|
152
|
+
this.selections[this.activeCol]--;
|
|
153
|
+
// Reset subsequent columns
|
|
154
|
+
this.selections = this.selections.slice(0, this.activeCol + 1);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
this.selections[this.activeCol] = currentData.length - 1;
|
|
158
|
+
this.selections = this.selections.slice(0, this.activeCol + 1);
|
|
159
|
+
}
|
|
160
|
+
this.render(false);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (this.isDown(char)) {
|
|
164
|
+
if (this.selections[this.activeCol] < currentData.length - 1) {
|
|
165
|
+
this.selections[this.activeCol]++;
|
|
166
|
+
this.selections = this.selections.slice(0, this.activeCol + 1);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
this.selections[this.activeCol] = 0;
|
|
170
|
+
this.selections = this.selections.slice(0, this.activeCol + 1);
|
|
171
|
+
}
|
|
172
|
+
this.render(false);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (this.isRight(char) || char === '\t') {
|
|
176
|
+
// Expand
|
|
177
|
+
const idx = this.selections[this.activeCol];
|
|
178
|
+
const item = currentData[idx];
|
|
179
|
+
if (item.children && item.children.length > 0) {
|
|
180
|
+
this.activeCol++;
|
|
181
|
+
this.selections[this.activeCol] = 0;
|
|
182
|
+
this.render(false);
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (this.isLeft(char) || char === '\x1b[Z') {
|
|
187
|
+
if (this.activeCol > 0) {
|
|
188
|
+
this.activeCol--;
|
|
189
|
+
this.render(false);
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
handleMouse(event) {
|
|
195
|
+
if (event.action === 'scroll') {
|
|
196
|
+
const currentData = this.getColumnData(this.activeCol);
|
|
197
|
+
if (!currentData || currentData.length === 0)
|
|
198
|
+
return;
|
|
199
|
+
if (event.scroll === 'up') {
|
|
200
|
+
if (this.selections[this.activeCol] > 0) {
|
|
201
|
+
this.selections[this.activeCol]--;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
this.selections[this.activeCol] = currentData.length - 1;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else if (event.scroll === 'down') {
|
|
208
|
+
if (this.selections[this.activeCol] < currentData.length - 1) {
|
|
209
|
+
this.selections[this.activeCol]++;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
this.selections[this.activeCol] = 0;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Reset subsequent columns
|
|
216
|
+
this.selections = this.selections.slice(0, this.activeCol + 1);
|
|
217
|
+
this.render(false);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
exports.MillerPrompt = MillerPrompt;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SelectPrompt } from './select';
|
|
2
|
+
import { MultiColumnSelectOptions } from '../types';
|
|
3
|
+
export declare class MultiColumnSelectPrompt<V> extends SelectPrompt<V, MultiColumnSelectOptions<V>> {
|
|
4
|
+
private cols;
|
|
5
|
+
private colWidth;
|
|
6
|
+
constructor(options: MultiColumnSelectOptions<V>);
|
|
7
|
+
private calculateLayout;
|
|
8
|
+
protected render(_firstRender: boolean): void;
|
|
9
|
+
protected handleInput(char: string): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MultiColumnSelectPrompt = void 0;
|
|
4
|
+
const select_1 = require("./select");
|
|
5
|
+
const theme_1 = require("../theme");
|
|
6
|
+
const ansi_1 = require("../ansi");
|
|
7
|
+
const utils_1 = require("../utils");
|
|
8
|
+
const symbols_1 = require("../symbols");
|
|
9
|
+
class MultiColumnSelectPrompt extends select_1.SelectPrompt {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
super(options);
|
|
12
|
+
this.cols = 1;
|
|
13
|
+
this.colWidth = 0;
|
|
14
|
+
this.calculateLayout();
|
|
15
|
+
}
|
|
16
|
+
calculateLayout() {
|
|
17
|
+
const termWidth = process.stdout.columns || 80;
|
|
18
|
+
const choices = this.options.choices.filter(c => !this.isSeparator(c));
|
|
19
|
+
if (choices.length === 0) {
|
|
20
|
+
this.cols = 1;
|
|
21
|
+
this.colWidth = termWidth;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// Calculate max item width
|
|
25
|
+
let maxLen = 0;
|
|
26
|
+
for (const c of choices) {
|
|
27
|
+
const len = (0, utils_1.stringWidth)(c.title);
|
|
28
|
+
if (len > maxLen)
|
|
29
|
+
maxLen = len;
|
|
30
|
+
}
|
|
31
|
+
// Add padding (pointer + space + item + space)
|
|
32
|
+
const itemWidth = maxLen + 4;
|
|
33
|
+
if (typeof this.options.cols === 'number') {
|
|
34
|
+
this.cols = this.options.cols;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// Auto
|
|
38
|
+
this.cols = Math.floor(termWidth / itemWidth);
|
|
39
|
+
if (this.cols < 1)
|
|
40
|
+
this.cols = 1;
|
|
41
|
+
}
|
|
42
|
+
// Final column width
|
|
43
|
+
this.colWidth = Math.floor(termWidth / this.cols);
|
|
44
|
+
}
|
|
45
|
+
render(_firstRender) {
|
|
46
|
+
let output = '';
|
|
47
|
+
const choices = this.getFilteredChoices();
|
|
48
|
+
// Header
|
|
49
|
+
const searchStr = this.searchBuffer ? ` ${theme_1.theme.muted}(Filter: ${this.searchBuffer})${ansi_1.ANSI.RESET}` : '';
|
|
50
|
+
output += `${theme_1.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}${searchStr}\n`;
|
|
51
|
+
if (choices.length === 0) {
|
|
52
|
+
output += ` ${theme_1.theme.muted}No results found${ansi_1.ANSI.RESET}`;
|
|
53
|
+
this.renderFrame(output);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// Grid Render
|
|
57
|
+
const totalRows = Math.ceil(choices.length / this.cols);
|
|
58
|
+
// Adjust Scroll
|
|
59
|
+
const currentRow = Math.floor(this.selectedIndex / this.cols);
|
|
60
|
+
if (currentRow < this.scrollTop) {
|
|
61
|
+
this.scrollTop = currentRow;
|
|
62
|
+
}
|
|
63
|
+
else if (currentRow >= this.scrollTop + this.pageSize) {
|
|
64
|
+
this.scrollTop = currentRow - this.pageSize + 1;
|
|
65
|
+
}
|
|
66
|
+
// Edge case: if list shrinks
|
|
67
|
+
if (this.scrollTop > totalRows - 1) {
|
|
68
|
+
this.scrollTop = Math.max(0, totalRows - this.pageSize);
|
|
69
|
+
}
|
|
70
|
+
const startRow = this.scrollTop;
|
|
71
|
+
const endRow = Math.min(totalRows, startRow + this.pageSize);
|
|
72
|
+
for (let r = startRow; r < endRow; r++) {
|
|
73
|
+
let rowStr = '';
|
|
74
|
+
for (let c = 0; c < this.cols; c++) {
|
|
75
|
+
const idx = r * this.cols + c;
|
|
76
|
+
if (idx >= choices.length)
|
|
77
|
+
break;
|
|
78
|
+
const choice = choices[idx];
|
|
79
|
+
// Truncate if needed
|
|
80
|
+
let title = choice.title || '';
|
|
81
|
+
if ((0, utils_1.stringWidth)(title) > this.colWidth - 3) {
|
|
82
|
+
title = title.slice(0, this.colWidth - 4) + '…';
|
|
83
|
+
}
|
|
84
|
+
let cellContent = '';
|
|
85
|
+
if (idx === this.selectedIndex) {
|
|
86
|
+
cellContent = `${theme_1.theme.main}${symbols_1.symbols.pointer} ${title}${ansi_1.ANSI.RESET}`;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
cellContent = ` ${title}`;
|
|
90
|
+
}
|
|
91
|
+
// Pad cell to colWidth
|
|
92
|
+
const currentWidth = (0, utils_1.stringWidth)(this.stripAnsi(cellContent));
|
|
93
|
+
const padding = Math.max(0, this.colWidth - currentWidth);
|
|
94
|
+
// Add cell to row
|
|
95
|
+
rowStr += cellContent + ' '.repeat(padding);
|
|
96
|
+
}
|
|
97
|
+
output += rowStr + '\n';
|
|
98
|
+
}
|
|
99
|
+
// Remove trailing newline
|
|
100
|
+
if (output.endsWith('\n'))
|
|
101
|
+
output = output.slice(0, -1);
|
|
102
|
+
this.renderFrame(output);
|
|
103
|
+
}
|
|
104
|
+
handleInput(char) {
|
|
105
|
+
const choices = this.getFilteredChoices();
|
|
106
|
+
if (char === '\r' || char === '\n') {
|
|
107
|
+
if (choices.length > 0) {
|
|
108
|
+
this.submit(choices[this.selectedIndex].value);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (this.isUp(char)) {
|
|
113
|
+
const newIndex = this.selectedIndex - this.cols;
|
|
114
|
+
if (newIndex >= 0) {
|
|
115
|
+
this.selectedIndex = newIndex;
|
|
116
|
+
}
|
|
117
|
+
this.render(false);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (this.isDown(char)) {
|
|
121
|
+
const newIndex = this.selectedIndex + this.cols;
|
|
122
|
+
if (newIndex < choices.length) {
|
|
123
|
+
this.selectedIndex = newIndex;
|
|
124
|
+
}
|
|
125
|
+
this.render(false);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (this.isLeft(char)) {
|
|
129
|
+
if (this.selectedIndex > 0) {
|
|
130
|
+
this.selectedIndex--;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
this.selectedIndex = choices.length - 1;
|
|
134
|
+
}
|
|
135
|
+
this.render(false);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (this.isRight(char)) {
|
|
139
|
+
if (this.selectedIndex < choices.length - 1) {
|
|
140
|
+
this.selectedIndex++;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
this.selectedIndex = 0;
|
|
144
|
+
}
|
|
145
|
+
this.render(false);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// Backspace
|
|
149
|
+
if (char === '\u0008' || char === '\x7f') {
|
|
150
|
+
if (this.searchBuffer.length > 0) {
|
|
151
|
+
this.searchBuffer = this.searchBuffer.slice(0, -1);
|
|
152
|
+
this.selectedIndex = 0;
|
|
153
|
+
this.calculateLayout();
|
|
154
|
+
this.render(false);
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Typing
|
|
159
|
+
if (char.length === 1 && !/^[\x00-\x1F]/.test(char) && !char.startsWith('\x1b')) {
|
|
160
|
+
this.searchBuffer += char;
|
|
161
|
+
this.selectedIndex = 0;
|
|
162
|
+
this.render(false);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
exports.MultiColumnSelectPrompt = MultiColumnSelectPrompt;
|