mepcli 0.4.0 → 0.5.5
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 +46 -4
- package/dist/core.d.ts +10 -3
- package/dist/core.js +28 -24
- package/dist/prompts/autocomplete.d.ts +22 -0
- package/dist/prompts/autocomplete.js +175 -0
- package/dist/prompts/checkbox.js +0 -4
- package/dist/prompts/editor.d.ts +14 -0
- package/dist/prompts/editor.js +207 -0
- package/dist/prompts/file.js +0 -6
- package/dist/prompts/keypress.d.ts +7 -0
- package/dist/prompts/keypress.js +57 -0
- package/dist/prompts/sort.d.ts +14 -0
- package/dist/prompts/sort.js +160 -0
- package/dist/prompts/table.d.ts +14 -0
- package/dist/prompts/table.js +106 -0
- package/dist/prompts/text.js +5 -24
- package/dist/prompts/tree.d.ts +20 -0
- package/dist/prompts/tree.js +223 -0
- package/dist/spinner.d.ts +33 -0
- package/dist/spinner.js +89 -0
- package/dist/types.d.ts +39 -0
- package/dist/utils.js +5 -1
- package/example.ts +113 -22
- package/package.json +13 -3
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KeypressPrompt = void 0;
|
|
4
|
+
const ansi_1 = require("../ansi");
|
|
5
|
+
const base_1 = require("../base");
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
class KeypressPrompt extends base_1.Prompt {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
super(options);
|
|
10
|
+
}
|
|
11
|
+
render(firstRender) {
|
|
12
|
+
let output = `${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}`;
|
|
13
|
+
if (this.options.keys) {
|
|
14
|
+
const hint = this.options.keys.map(k => {
|
|
15
|
+
if (k === '\r' || k === '\n' || k === 'enter')
|
|
16
|
+
return 'enter';
|
|
17
|
+
if (k === ' ' || k === 'space')
|
|
18
|
+
return 'space';
|
|
19
|
+
return k;
|
|
20
|
+
}).join('/');
|
|
21
|
+
// Only show hint if it's short enough to be helpful, or always?
|
|
22
|
+
// Let's always show it if provided, or maybe just dimmed.
|
|
23
|
+
output += ` ${theme_1.theme.muted}(${hint})${ansi_1.ANSI.RESET}`;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
output += ` ${theme_1.theme.muted}(Press any key)${ansi_1.ANSI.RESET}`;
|
|
27
|
+
}
|
|
28
|
+
this.renderFrame(output);
|
|
29
|
+
}
|
|
30
|
+
handleInput(char, key) {
|
|
31
|
+
let keyName = char;
|
|
32
|
+
if (char === '\r' || char === '\n')
|
|
33
|
+
keyName = 'enter';
|
|
34
|
+
else if (char === ' ')
|
|
35
|
+
keyName = 'space';
|
|
36
|
+
else if (char === '\u001b')
|
|
37
|
+
keyName = 'escape';
|
|
38
|
+
else if (char === '\t')
|
|
39
|
+
keyName = 'tab';
|
|
40
|
+
// Handle backspace
|
|
41
|
+
else if (char === '\x7f' || char === '\b')
|
|
42
|
+
keyName = 'backspace';
|
|
43
|
+
// Check against whitelist
|
|
44
|
+
if (this.options.keys) {
|
|
45
|
+
const allowed = this.options.keys.map(k => k.toLowerCase());
|
|
46
|
+
// Check normalized name or exact char
|
|
47
|
+
if (!allowed.includes(keyName) && !allowed.includes(char)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (this.options.showInvisible) {
|
|
52
|
+
this.print(` ${theme_1.theme.success}${keyName}${ansi_1.ANSI.RESET}`);
|
|
53
|
+
}
|
|
54
|
+
this.submit(keyName);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.KeypressPrompt = KeypressPrompt;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { SortOptions, MouseEvent } from '../types';
|
|
3
|
+
export declare class SortPrompt extends Prompt<string[], SortOptions> {
|
|
4
|
+
private items;
|
|
5
|
+
private selectedIndex;
|
|
6
|
+
private grabbedIndex;
|
|
7
|
+
private scrollTop;
|
|
8
|
+
private readonly pageSize;
|
|
9
|
+
constructor(options: SortOptions);
|
|
10
|
+
protected render(firstRender: boolean): void;
|
|
11
|
+
protected handleInput(char: string): void;
|
|
12
|
+
private swap;
|
|
13
|
+
protected handleMouse(event: MouseEvent): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SortPrompt = 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
|
+
class SortPrompt extends base_1.Prompt {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super(options);
|
|
11
|
+
this.selectedIndex = 0;
|
|
12
|
+
this.grabbedIndex = null;
|
|
13
|
+
this.scrollTop = 0;
|
|
14
|
+
this.pageSize = 10;
|
|
15
|
+
this.items = [...options.items];
|
|
16
|
+
}
|
|
17
|
+
render(firstRender) {
|
|
18
|
+
// Adjust Scroll Top
|
|
19
|
+
if (this.selectedIndex < this.scrollTop) {
|
|
20
|
+
this.scrollTop = this.selectedIndex;
|
|
21
|
+
}
|
|
22
|
+
else if (this.selectedIndex >= this.scrollTop + this.pageSize) {
|
|
23
|
+
this.scrollTop = this.selectedIndex - this.pageSize + 1;
|
|
24
|
+
}
|
|
25
|
+
// Ensure valid scroll (handle list shrinking?) - list doesn't shrink here but good practice
|
|
26
|
+
this.scrollTop = Math.max(0, Math.min(this.scrollTop, this.items.length - this.pageSize));
|
|
27
|
+
if (this.scrollTop < 0)
|
|
28
|
+
this.scrollTop = 0;
|
|
29
|
+
let output = '';
|
|
30
|
+
// Header
|
|
31
|
+
output += `${theme_1.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${theme_1.theme.muted}(Press <space> to grab, arrows to move, <enter> to confirm)${ansi_1.ANSI.RESET}\n`;
|
|
32
|
+
const visibleItems = this.items.slice(this.scrollTop, this.scrollTop + this.pageSize);
|
|
33
|
+
visibleItems.forEach((item, index) => {
|
|
34
|
+
const actualIndex = this.scrollTop + index;
|
|
35
|
+
if (index > 0)
|
|
36
|
+
output += '\n';
|
|
37
|
+
const isSelected = actualIndex === this.selectedIndex;
|
|
38
|
+
const isGrabbed = actualIndex === this.grabbedIndex;
|
|
39
|
+
// Pointer
|
|
40
|
+
let prefix = ' ';
|
|
41
|
+
if (isSelected) {
|
|
42
|
+
if (isGrabbed) {
|
|
43
|
+
prefix = `${theme_1.theme.main}${symbols_1.symbols.pointer}${symbols_1.symbols.pointer} `; // Double pointer for grabbed?
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
prefix = `${theme_1.theme.main}${symbols_1.symbols.pointer} `;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (isGrabbed) {
|
|
50
|
+
// Should not happen if we move grabbed item with cursor
|
|
51
|
+
// But if logic differs, maybe.
|
|
52
|
+
}
|
|
53
|
+
// Item Content
|
|
54
|
+
let content = item;
|
|
55
|
+
if (isGrabbed) {
|
|
56
|
+
content = `${ansi_1.ANSI.BOLD}${theme_1.theme.main}${content}${ansi_1.ANSI.RESET}`;
|
|
57
|
+
}
|
|
58
|
+
else if (isSelected) {
|
|
59
|
+
content = `${theme_1.theme.main}${content}${ansi_1.ANSI.RESET}`;
|
|
60
|
+
}
|
|
61
|
+
// Index indicator? Maybe not needed, minimalist.
|
|
62
|
+
output += `${prefix}${content}`;
|
|
63
|
+
});
|
|
64
|
+
this.renderFrame(output);
|
|
65
|
+
}
|
|
66
|
+
handleInput(char) {
|
|
67
|
+
// Enter
|
|
68
|
+
if (char === '\r' || char === '\n') {
|
|
69
|
+
this.submit(this.items);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Space (Grab/Drop)
|
|
73
|
+
if (char === ' ') {
|
|
74
|
+
if (this.grabbedIndex === null) {
|
|
75
|
+
this.grabbedIndex = this.selectedIndex;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
this.grabbedIndex = null;
|
|
79
|
+
}
|
|
80
|
+
this.render(false);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Up
|
|
84
|
+
if (this.isUp(char)) {
|
|
85
|
+
if (this.grabbedIndex !== null) {
|
|
86
|
+
// Move item up
|
|
87
|
+
if (this.selectedIndex > 0) {
|
|
88
|
+
const newIndex = this.selectedIndex - 1;
|
|
89
|
+
this.swap(this.selectedIndex, newIndex);
|
|
90
|
+
this.selectedIndex = newIndex;
|
|
91
|
+
this.grabbedIndex = newIndex;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// Move cursor up
|
|
96
|
+
this.selectedIndex = this.selectedIndex > 0 ? this.selectedIndex - 1 : this.items.length - 1;
|
|
97
|
+
}
|
|
98
|
+
this.render(false);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Down
|
|
102
|
+
if (this.isDown(char)) {
|
|
103
|
+
if (this.grabbedIndex !== null) {
|
|
104
|
+
// Move item down
|
|
105
|
+
if (this.selectedIndex < this.items.length - 1) {
|
|
106
|
+
const newIndex = this.selectedIndex + 1;
|
|
107
|
+
this.swap(this.selectedIndex, newIndex);
|
|
108
|
+
this.selectedIndex = newIndex;
|
|
109
|
+
this.grabbedIndex = newIndex;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// Move cursor down
|
|
114
|
+
this.selectedIndex = this.selectedIndex < this.items.length - 1 ? this.selectedIndex + 1 : 0;
|
|
115
|
+
}
|
|
116
|
+
this.render(false);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
swap(i, j) {
|
|
121
|
+
[this.items[i], this.items[j]] = [this.items[j], this.items[i]];
|
|
122
|
+
}
|
|
123
|
+
// Mouse support?
|
|
124
|
+
// Drag and drop is hard with just clicks/scroll.
|
|
125
|
+
// Maybe click to grab, scroll to move?
|
|
126
|
+
handleMouse(event) {
|
|
127
|
+
// Simple scroll support for navigation
|
|
128
|
+
if (event.action === 'scroll') {
|
|
129
|
+
if (event.scroll === 'up') {
|
|
130
|
+
if (this.grabbedIndex !== null) {
|
|
131
|
+
if (this.selectedIndex > 0) {
|
|
132
|
+
const newIndex = this.selectedIndex - 1;
|
|
133
|
+
this.swap(this.selectedIndex, newIndex);
|
|
134
|
+
this.selectedIndex = newIndex;
|
|
135
|
+
this.grabbedIndex = newIndex;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
this.selectedIndex = this.selectedIndex > 0 ? this.selectedIndex - 1 : this.items.length - 1;
|
|
140
|
+
}
|
|
141
|
+
this.render(false);
|
|
142
|
+
}
|
|
143
|
+
else if (event.scroll === 'down') {
|
|
144
|
+
if (this.grabbedIndex !== null) {
|
|
145
|
+
if (this.selectedIndex < this.items.length - 1) {
|
|
146
|
+
const newIndex = this.selectedIndex + 1;
|
|
147
|
+
this.swap(this.selectedIndex, newIndex);
|
|
148
|
+
this.selectedIndex = newIndex;
|
|
149
|
+
this.grabbedIndex = newIndex;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
this.selectedIndex = this.selectedIndex < this.items.length - 1 ? this.selectedIndex + 1 : 0;
|
|
154
|
+
}
|
|
155
|
+
this.render(false);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
exports.SortPrompt = SortPrompt;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { TableOptions, MouseEvent } from '../types';
|
|
3
|
+
export declare class TablePrompt<V> extends Prompt<V, TableOptions<V>> {
|
|
4
|
+
private selectedIndex;
|
|
5
|
+
private scrollTop;
|
|
6
|
+
private readonly pageSize;
|
|
7
|
+
private colWidths;
|
|
8
|
+
constructor(options: TableOptions<V>);
|
|
9
|
+
private calculateColWidths;
|
|
10
|
+
protected render(firstRender: boolean): void;
|
|
11
|
+
private pad;
|
|
12
|
+
protected handleInput(char: string): void;
|
|
13
|
+
protected handleMouse(event: MouseEvent): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TablePrompt = 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 TablePrompt extends base_1.Prompt {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
super(options);
|
|
12
|
+
this.selectedIndex = 0;
|
|
13
|
+
this.scrollTop = 0;
|
|
14
|
+
this.colWidths = [];
|
|
15
|
+
this.pageSize = options.rows || 7;
|
|
16
|
+
this.calculateColWidths();
|
|
17
|
+
}
|
|
18
|
+
calculateColWidths() {
|
|
19
|
+
const { columns, data } = this.options;
|
|
20
|
+
this.colWidths = columns.map(c => (0, utils_1.stringWidth)(c));
|
|
21
|
+
data.forEach(row => {
|
|
22
|
+
row.row.forEach((cell, idx) => {
|
|
23
|
+
if (idx < this.colWidths.length) {
|
|
24
|
+
this.colWidths[idx] = Math.max(this.colWidths[idx], (0, utils_1.stringWidth)(cell));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
// Add padding
|
|
29
|
+
this.colWidths = this.colWidths.map(w => w + 2);
|
|
30
|
+
}
|
|
31
|
+
render(firstRender) {
|
|
32
|
+
// Scroll Logic
|
|
33
|
+
if (this.selectedIndex < this.scrollTop) {
|
|
34
|
+
this.scrollTop = this.selectedIndex;
|
|
35
|
+
}
|
|
36
|
+
else if (this.selectedIndex >= this.scrollTop + this.pageSize) {
|
|
37
|
+
this.scrollTop = this.selectedIndex - this.pageSize + 1;
|
|
38
|
+
}
|
|
39
|
+
const maxScroll = Math.max(0, this.options.data.length - this.pageSize);
|
|
40
|
+
this.scrollTop = Math.min(this.scrollTop, maxScroll);
|
|
41
|
+
let output = '';
|
|
42
|
+
// Title
|
|
43
|
+
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`;
|
|
44
|
+
// Table Header
|
|
45
|
+
let headerStr = ' '; // Indent for pointer space
|
|
46
|
+
this.options.columns.forEach((col, i) => {
|
|
47
|
+
headerStr += this.pad(col, this.colWidths[i]);
|
|
48
|
+
});
|
|
49
|
+
output += `${ansi_1.ANSI.BOLD}${headerStr}${ansi_1.ANSI.RESET}\n`;
|
|
50
|
+
// Table Body
|
|
51
|
+
const visibleRows = this.options.data.slice(this.scrollTop, this.scrollTop + this.pageSize);
|
|
52
|
+
visibleRows.forEach((item, index) => {
|
|
53
|
+
const actualIndex = this.scrollTop + index;
|
|
54
|
+
if (index > 0)
|
|
55
|
+
output += '\n';
|
|
56
|
+
const isSelected = actualIndex === this.selectedIndex;
|
|
57
|
+
const pointer = isSelected ? `${theme_1.theme.main}${symbols_1.symbols.pointer}${ansi_1.ANSI.RESET} ` : ' ';
|
|
58
|
+
let rowStr = '';
|
|
59
|
+
item.row.forEach((cell, colIdx) => {
|
|
60
|
+
const width = this.colWidths[colIdx];
|
|
61
|
+
let cellStr = this.pad(cell, width);
|
|
62
|
+
if (isSelected) {
|
|
63
|
+
cellStr = `${theme_1.theme.main}${cellStr}${ansi_1.ANSI.RESET}`;
|
|
64
|
+
}
|
|
65
|
+
rowStr += cellStr;
|
|
66
|
+
});
|
|
67
|
+
output += `${pointer}${rowStr}`;
|
|
68
|
+
});
|
|
69
|
+
this.renderFrame(output);
|
|
70
|
+
}
|
|
71
|
+
pad(str, width) {
|
|
72
|
+
const len = (0, utils_1.stringWidth)(str);
|
|
73
|
+
if (len >= width)
|
|
74
|
+
return str;
|
|
75
|
+
return str + ' '.repeat(width - len);
|
|
76
|
+
}
|
|
77
|
+
handleInput(char) {
|
|
78
|
+
if (char === '\r' || char === '\n') {
|
|
79
|
+
this.submit(this.options.data[this.selectedIndex].value);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (this.isUp(char)) {
|
|
83
|
+
this.selectedIndex = this.selectedIndex > 0 ? this.selectedIndex - 1 : this.options.data.length - 1;
|
|
84
|
+
this.render(false);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (this.isDown(char)) {
|
|
88
|
+
this.selectedIndex = this.selectedIndex < this.options.data.length - 1 ? this.selectedIndex + 1 : 0;
|
|
89
|
+
this.render(false);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
handleMouse(event) {
|
|
94
|
+
if (event.action === 'scroll') {
|
|
95
|
+
if (event.scroll === 'up') {
|
|
96
|
+
this.selectedIndex = this.selectedIndex > 0 ? this.selectedIndex - 1 : this.options.data.length - 1;
|
|
97
|
+
this.render(false);
|
|
98
|
+
}
|
|
99
|
+
else if (event.scroll === 'down') {
|
|
100
|
+
this.selectedIndex = this.selectedIndex < this.options.data.length - 1 ? this.selectedIndex + 1 : 0;
|
|
101
|
+
this.render(false);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.TablePrompt = TablePrompt;
|
package/dist/prompts/text.js
CHANGED
|
@@ -52,22 +52,22 @@ class TextPrompt extends base_1.Prompt {
|
|
|
52
52
|
// This is tricky because segments might contain '\n'.
|
|
53
53
|
// safeSplit treats '\n' as a segment.
|
|
54
54
|
let cursorLineIndex = 0;
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
const cursorSegmentIndexOnLine = 0;
|
|
56
|
+
const currentSegmentIndex = 0;
|
|
57
57
|
for (let i = 0; i < lines.length; i++) {
|
|
58
58
|
// How many segments in this line?
|
|
59
59
|
// We can't just use lines[i].length because that's chars.
|
|
60
60
|
// We need to split the line again or iterate segments.
|
|
61
61
|
// Iterating segments is safer.
|
|
62
62
|
// Let's assume we iterate global segments until we hit a newline segment
|
|
63
|
-
|
|
63
|
+
const lineSegmentsCount = 0;
|
|
64
64
|
// Since rawValue.split('\n') consumes the newlines, we need to account for them.
|
|
65
65
|
// Alternative: iterate this.segments
|
|
66
66
|
// Find where the cursor falls.
|
|
67
67
|
}
|
|
68
68
|
// Let's iterate segments to find cursor position (row, col)
|
|
69
69
|
cursorLineIndex = 0;
|
|
70
|
-
|
|
70
|
+
const colIndex = 0; // Visual column or char index?
|
|
71
71
|
// If we want visual cursor position, we need visual width of segments.
|
|
72
72
|
let visualColIndex = 0;
|
|
73
73
|
for (let i = 0; i < this.cursor; i++) {
|
|
@@ -90,7 +90,7 @@ class TextPrompt extends base_1.Prompt {
|
|
|
90
90
|
// Now prepare lines for display (scrolling/truncation)
|
|
91
91
|
// We need to reconstruct lines from segments to apply styling/truncation logic per line.
|
|
92
92
|
let currentLineSegments = [];
|
|
93
|
-
|
|
93
|
+
const processedLines = []; // Array of segment arrays
|
|
94
94
|
for (const seg of this.segments) {
|
|
95
95
|
if (seg === '\n') {
|
|
96
96
|
processedLines.push(currentLineSegments);
|
|
@@ -118,25 +118,6 @@ class TextPrompt extends base_1.Prompt {
|
|
|
118
118
|
// But cursorRelativeCol is global? No, we reset it on newline.
|
|
119
119
|
// So cursorRelativeCol above was correct for the current line.
|
|
120
120
|
if (isCursorLine) {
|
|
121
|
-
// Check if we need to scroll
|
|
122
|
-
// We need visual width of the line up to cursor.
|
|
123
|
-
// cursorRelativeCol holds that.
|
|
124
|
-
// If visual position > maxContentLen, we scroll.
|
|
125
|
-
// This logic is similar to before but needs to use widths.
|
|
126
|
-
// For simplicity, let's stick to the previous slice logic but apply it to SEGMENTS if possible.
|
|
127
|
-
// But slicing segments for display is safer.
|
|
128
|
-
// Let's implement simple tail truncation for now to keep it robust.
|
|
129
|
-
// Ideally we scroll, but scrolling with variable width chars is complex.
|
|
130
|
-
// "Good Enough": if it fits, show it. If not, truncate end.
|
|
131
|
-
// If cursor is beyond end, scroll (slice from left).
|
|
132
|
-
// Simplified: just show visibleLine truncated by base class renderFrame?
|
|
133
|
-
// But renderFrame truncates blindly. We want the cursor visible.
|
|
134
|
-
// Let's leave scrolling out for this specific "Backspace" fix task unless it's critical.
|
|
135
|
-
// The user asked for "Backspace Emoji fix".
|
|
136
|
-
// The scrolling logic is secondary but important.
|
|
137
|
-
// I will preserve the existing simple scrolling logic but using segments?
|
|
138
|
-
// No, let's just use the string for display and let renderFrame truncate.
|
|
139
|
-
// Fix: Ensure we don't crash or show garbage.
|
|
140
121
|
}
|
|
141
122
|
displayValueLines.push(theme_1.theme.main + visibleLine + ansi_1.ANSI.RESET);
|
|
142
123
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { TreeOptions, MouseEvent } from '../types';
|
|
3
|
+
export declare class TreePrompt<V> extends Prompt<V, TreeOptions<V>> {
|
|
4
|
+
private cursor;
|
|
5
|
+
private expandedNodes;
|
|
6
|
+
private flatList;
|
|
7
|
+
private scrollTop;
|
|
8
|
+
private readonly pageSize;
|
|
9
|
+
private readonly ICON_CLOSED;
|
|
10
|
+
private readonly ICON_OPEN;
|
|
11
|
+
private readonly ICON_LEAF;
|
|
12
|
+
constructor(options: TreeOptions<V>);
|
|
13
|
+
private expandPathTo;
|
|
14
|
+
private initializeExpanded;
|
|
15
|
+
private recalculateFlatList;
|
|
16
|
+
private traverse;
|
|
17
|
+
protected render(firstRender: boolean): void;
|
|
18
|
+
protected handleInput(char: string, key: Buffer): void;
|
|
19
|
+
protected handleMouse(event: MouseEvent): void;
|
|
20
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TreePrompt = 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
|
+
class TreePrompt extends base_1.Prompt {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super(options);
|
|
11
|
+
this.cursor = 0;
|
|
12
|
+
this.expandedNodes = new Set();
|
|
13
|
+
this.flatList = [];
|
|
14
|
+
this.scrollTop = 0;
|
|
15
|
+
this.pageSize = 15;
|
|
16
|
+
// Icons
|
|
17
|
+
this.ICON_CLOSED = symbols_1.symbols.pointer === '>' ? '+' : '▸';
|
|
18
|
+
this.ICON_OPEN = symbols_1.symbols.pointer === '>' ? '-' : '▾';
|
|
19
|
+
this.ICON_LEAF = symbols_1.symbols.pointer === '>' ? ' ' : ' '; // No specific icon for leaf, just indentation
|
|
20
|
+
this.initializeExpanded(this.options.data);
|
|
21
|
+
// Handle initial value
|
|
22
|
+
if (this.options.initial !== undefined) {
|
|
23
|
+
this.expandPathTo(this.options.initial);
|
|
24
|
+
}
|
|
25
|
+
this.recalculateFlatList();
|
|
26
|
+
if (this.options.initial !== undefined) {
|
|
27
|
+
const index = this.flatList.findIndex(item => item.node.value === this.options.initial);
|
|
28
|
+
if (index !== -1) {
|
|
29
|
+
this.cursor = index;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
expandPathTo(value) {
|
|
34
|
+
const find = (nodes) => {
|
|
35
|
+
for (const node of nodes) {
|
|
36
|
+
if (node.value === value)
|
|
37
|
+
return true;
|
|
38
|
+
if (node.children) {
|
|
39
|
+
if (find(node.children)) {
|
|
40
|
+
this.expandedNodes.add(node);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
};
|
|
47
|
+
find(this.options.data);
|
|
48
|
+
}
|
|
49
|
+
initializeExpanded(nodes) {
|
|
50
|
+
for (const node of nodes) {
|
|
51
|
+
if (node.expanded) {
|
|
52
|
+
this.expandedNodes.add(node);
|
|
53
|
+
}
|
|
54
|
+
if (node.children) {
|
|
55
|
+
this.initializeExpanded(node.children);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
recalculateFlatList() {
|
|
60
|
+
this.flatList = [];
|
|
61
|
+
this.traverse(this.options.data, 0, null);
|
|
62
|
+
// Adjust cursor if it went out of bounds (e.g. collapsing a folder above cursor)
|
|
63
|
+
if (this.cursor >= this.flatList.length) {
|
|
64
|
+
this.cursor = Math.max(0, this.flatList.length - 1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
traverse(nodes, depth, parent) {
|
|
68
|
+
for (const node of nodes) {
|
|
69
|
+
this.flatList.push({
|
|
70
|
+
node,
|
|
71
|
+
depth,
|
|
72
|
+
parent
|
|
73
|
+
});
|
|
74
|
+
if (node.children && node.children.length > 0 && this.expandedNodes.has(node)) {
|
|
75
|
+
this.traverse(node.children, depth + 1, node);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
render(firstRender) {
|
|
80
|
+
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`;
|
|
81
|
+
if (this.flatList.length === 0) {
|
|
82
|
+
output += ` ${theme_1.theme.muted}No data${ansi_1.ANSI.RESET}`;
|
|
83
|
+
this.renderFrame(output);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Adjust Scroll
|
|
87
|
+
if (this.cursor < this.scrollTop) {
|
|
88
|
+
this.scrollTop = this.cursor;
|
|
89
|
+
}
|
|
90
|
+
else if (this.cursor >= this.scrollTop + this.pageSize) {
|
|
91
|
+
this.scrollTop = this.cursor - this.pageSize + 1;
|
|
92
|
+
}
|
|
93
|
+
const visible = this.flatList.slice(this.scrollTop, this.scrollTop + this.pageSize);
|
|
94
|
+
visible.forEach((item, index) => {
|
|
95
|
+
const actualIndex = this.scrollTop + index;
|
|
96
|
+
const isSelected = actualIndex === this.cursor;
|
|
97
|
+
// Indentation
|
|
98
|
+
const indentSize = this.options.indent || 2;
|
|
99
|
+
const indentation = ' '.repeat(item.depth * indentSize);
|
|
100
|
+
// Pointer
|
|
101
|
+
let linePrefix = '';
|
|
102
|
+
if (isSelected) {
|
|
103
|
+
linePrefix = `${theme_1.theme.main}${symbols_1.symbols.pointer} `;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
linePrefix = ' ';
|
|
107
|
+
}
|
|
108
|
+
// Folder Icon
|
|
109
|
+
let icon = ' '; // Default 2 spaces for alignment
|
|
110
|
+
const hasChildren = item.node.children && item.node.children.length > 0;
|
|
111
|
+
if (hasChildren) {
|
|
112
|
+
if (this.expandedNodes.has(item.node)) {
|
|
113
|
+
icon = `${this.ICON_OPEN} `;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
icon = `${this.ICON_CLOSED} `;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Title
|
|
120
|
+
let title = item.node.title;
|
|
121
|
+
if (item.node.disabled) {
|
|
122
|
+
title = `${theme_1.theme.muted}${title} (disabled)${ansi_1.ANSI.RESET}`;
|
|
123
|
+
}
|
|
124
|
+
// Compose line
|
|
125
|
+
let line = `${indentation}${icon}${title}`;
|
|
126
|
+
if (isSelected) {
|
|
127
|
+
line = `${theme_1.theme.main}${line}${ansi_1.ANSI.RESET}`;
|
|
128
|
+
}
|
|
129
|
+
output += linePrefix + line;
|
|
130
|
+
if (index < visible.length - 1)
|
|
131
|
+
output += '\n';
|
|
132
|
+
});
|
|
133
|
+
this.renderFrame(output);
|
|
134
|
+
}
|
|
135
|
+
handleInput(char, key) {
|
|
136
|
+
if (this.flatList.length === 0)
|
|
137
|
+
return;
|
|
138
|
+
// Navigation
|
|
139
|
+
if (this.isUp(char)) {
|
|
140
|
+
this.cursor = (this.cursor - 1 + this.flatList.length) % this.flatList.length;
|
|
141
|
+
this.render(false);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (this.isDown(char)) {
|
|
145
|
+
this.cursor = (this.cursor + 1) % this.flatList.length;
|
|
146
|
+
this.render(false);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const currentItem = this.flatList[this.cursor];
|
|
150
|
+
const node = currentItem.node;
|
|
151
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
152
|
+
// Right: Expand or Go Down
|
|
153
|
+
if (this.isRight(char)) {
|
|
154
|
+
if (hasChildren) {
|
|
155
|
+
if (!this.expandedNodes.has(node)) {
|
|
156
|
+
this.expandedNodes.add(node);
|
|
157
|
+
this.recalculateFlatList();
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// Go to first child (next item in flat list)
|
|
161
|
+
if (this.cursor + 1 < this.flatList.length) {
|
|
162
|
+
this.cursor++;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
this.render(false);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Left: Collapse or Go Up (Parent)
|
|
170
|
+
if (this.isLeft(char)) {
|
|
171
|
+
if (hasChildren && this.expandedNodes.has(node)) {
|
|
172
|
+
this.expandedNodes.delete(node);
|
|
173
|
+
this.recalculateFlatList();
|
|
174
|
+
this.render(false);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// Go to parent
|
|
179
|
+
if (currentItem.parent) {
|
|
180
|
+
const parentIndex = this.flatList.findIndex(x => x.node === currentItem.parent);
|
|
181
|
+
if (parentIndex !== -1) {
|
|
182
|
+
this.cursor = parentIndex;
|
|
183
|
+
this.render(false);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Toggle (Space)
|
|
190
|
+
if (char === ' ') {
|
|
191
|
+
if (hasChildren) {
|
|
192
|
+
if (this.expandedNodes.has(node)) {
|
|
193
|
+
this.expandedNodes.delete(node);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
this.expandedNodes.add(node);
|
|
197
|
+
}
|
|
198
|
+
this.recalculateFlatList();
|
|
199
|
+
this.render(false);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// Submit (Enter)
|
|
204
|
+
if (char === '\r' || char === '\n') {
|
|
205
|
+
if (!node.disabled) {
|
|
206
|
+
this.submit(node.value);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
handleMouse(event) {
|
|
211
|
+
if (event.action === 'scroll') {
|
|
212
|
+
if (event.scroll === 'up') {
|
|
213
|
+
this.cursor = (this.cursor - 1 + this.flatList.length) % this.flatList.length;
|
|
214
|
+
this.render(false);
|
|
215
|
+
}
|
|
216
|
+
else if (event.scroll === 'down') {
|
|
217
|
+
this.cursor = (this.cursor + 1) % this.flatList.length;
|
|
218
|
+
this.render(false);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
exports.TreePrompt = TreePrompt;
|