mepcli 0.2.1 → 0.2.6

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.
@@ -0,0 +1,13 @@
1
+ import { EventEmitter } from 'events';
2
+ export declare class InputParser extends EventEmitter {
3
+ private buffer;
4
+ private timeout;
5
+ private state;
6
+ constructor();
7
+ /**
8
+ * Feed data into the parser.
9
+ */
10
+ feed(data: Buffer): void;
11
+ private processChar;
12
+ private emitKey;
13
+ }
package/dist/input.js ADDED
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ // src/input.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.InputParser = void 0;
5
+ const events_1 = require("events");
6
+ class InputParser extends events_1.EventEmitter {
7
+ constructor() {
8
+ super();
9
+ this.buffer = '';
10
+ this.timeout = null;
11
+ this.state = 'NORMAL';
12
+ }
13
+ /**
14
+ * Feed data into the parser.
15
+ */
16
+ feed(data) {
17
+ // Convert buffer to string.
18
+ // For partial multi-byte sequences at the chunk boundary,
19
+ // buffer.toString() might produce replacement chars.
20
+ // Ideally we should use StringDecoder, but since we are handling KeyPresses,
21
+ // and usually a keypress is complete, simple toString often works.
22
+ // However, the user mentioned fragmentation issues.
23
+ // But InputParser usually receives data from stdin.
24
+ // The core issue of fragmentation is splitting escape codes like \x1b [ A
25
+ const input = data.toString('utf-8');
26
+ for (let i = 0; i < input.length; i++) {
27
+ const char = input[i];
28
+ this.processChar(char);
29
+ }
30
+ }
31
+ processChar(char) {
32
+ if (this.state === 'NORMAL') {
33
+ if (char === '\x1b') {
34
+ this.state = 'ESC';
35
+ this.buffer = char;
36
+ // Start a short timeout to detect bare ESC
37
+ this.timeout = setTimeout(() => {
38
+ this.emitKey(this.buffer);
39
+ this.buffer = '';
40
+ this.state = 'NORMAL';
41
+ }, 20); // 20ms timeout
42
+ }
43
+ else {
44
+ this.emitKey(char);
45
+ }
46
+ }
47
+ else if (this.state === 'ESC') {
48
+ // Cancel timeout as we received more data
49
+ if (this.timeout)
50
+ clearTimeout(this.timeout);
51
+ this.timeout = null;
52
+ this.buffer += char;
53
+ if (char === '[') {
54
+ this.state = 'CSI';
55
+ }
56
+ else if (char === 'O') {
57
+ // SS3 sequence like \x1b O A (Application Cursor Keys)
58
+ // Treat as similar to CSI for collecting the next char
59
+ this.state = 'CSI';
60
+ }
61
+ else {
62
+ // Alt + Key or similar (\x1b + char)
63
+ this.emitKey(this.buffer);
64
+ this.buffer = '';
65
+ this.state = 'NORMAL';
66
+ }
67
+ }
68
+ else if (this.state === 'CSI') {
69
+ this.buffer += char;
70
+ // CSI sequences end with 0x40-0x7E
71
+ if (char >= '@' && char <= '~') {
72
+ this.emitKey(this.buffer);
73
+ this.buffer = '';
74
+ this.state = 'NORMAL';
75
+ }
76
+ // Otherwise, we keep buffering (params like 1;2)
77
+ }
78
+ }
79
+ emitKey(key) {
80
+ // Normalize Enter
81
+ if (key === '\r')
82
+ key = '\n';
83
+ // We emit both the raw sequence and a normalized representation if needed,
84
+ // but existing prompt logic handles raw strings like \x1b[A.
85
+ // So we just emit the reconstructed sequence.
86
+ this.emit('keypress', key, Buffer.from(key));
87
+ }
88
+ }
89
+ exports.InputParser = InputParser;
@@ -0,0 +1,12 @@
1
+ import { Prompt } from '../base';
2
+ import { CheckboxOptions } from '../types';
3
+ export declare class CheckboxPrompt extends Prompt<any[], CheckboxOptions> {
4
+ private selectedIndex;
5
+ private checkedState;
6
+ private errorMsg;
7
+ private scrollTop;
8
+ private readonly pageSize;
9
+ constructor(options: CheckboxOptions);
10
+ protected render(firstRender: boolean): void;
11
+ protected handleInput(char: string): void;
12
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CheckboxPrompt = void 0;
4
+ const ansi_1 = require("../ansi");
5
+ const base_1 = require("../base");
6
+ const theme_1 = require("../theme");
7
+ // --- Implementation: Checkbox Prompt ---
8
+ class CheckboxPrompt extends base_1.Prompt {
9
+ constructor(options) {
10
+ super(options);
11
+ this.selectedIndex = 0;
12
+ this.errorMsg = '';
13
+ // Pagination state (added for consistency and performance)
14
+ this.scrollTop = 0;
15
+ this.pageSize = 10;
16
+ this.checkedState = options.choices.map(c => !!c.selected);
17
+ }
18
+ render(firstRender) {
19
+ // Adjust Scroll Top
20
+ if (this.selectedIndex < this.scrollTop) {
21
+ this.scrollTop = this.selectedIndex;
22
+ }
23
+ else if (this.selectedIndex >= this.scrollTop + this.pageSize) {
24
+ this.scrollTop = this.selectedIndex - this.pageSize + 1;
25
+ }
26
+ // Ensure we don't scroll past bounds if list is small
27
+ if (this.options.choices.length <= this.pageSize) {
28
+ this.scrollTop = 0;
29
+ }
30
+ let output = '';
31
+ // Header
32
+ const icon = this.errorMsg ? `${theme_1.theme.error}✖` : `${theme_1.theme.success}?`;
33
+ output += `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${theme_1.theme.muted}(Press <space> to select, <enter> to confirm)${ansi_1.ANSI.RESET}`;
34
+ // List
35
+ const choices = this.options.choices;
36
+ const visibleChoices = choices.slice(this.scrollTop, this.scrollTop + this.pageSize);
37
+ visibleChoices.forEach((choice, index) => {
38
+ const actualIndex = this.scrollTop + index;
39
+ output += '\n'; // New line for each item
40
+ const cursor = actualIndex === this.selectedIndex ? `${theme_1.theme.main}❯${ansi_1.ANSI.RESET}` : ' ';
41
+ const isChecked = this.checkedState[actualIndex];
42
+ const checkbox = isChecked
43
+ ? `${theme_1.theme.success}◉${ansi_1.ANSI.RESET}`
44
+ : `${theme_1.theme.muted}◯${ansi_1.ANSI.RESET}`;
45
+ const title = actualIndex === this.selectedIndex
46
+ ? `${theme_1.theme.main}${choice.title}${ansi_1.ANSI.RESET}`
47
+ : choice.title;
48
+ output += `${cursor} ${checkbox} ${title}`;
49
+ });
50
+ // Indication of more items
51
+ if (choices.length > this.pageSize) {
52
+ const progress = ` ${this.scrollTop + 1}-${Math.min(this.scrollTop + this.pageSize, choices.length)} of ${choices.length}`;
53
+ // Maybe add this to the header or footer?
54
+ // Let's add it to footer or header. Adding to header is cleaner.
55
+ // But I already wrote header.
56
+ // Let's just append it at the bottom if I want, or ignore for now to keep UI minimal.
57
+ }
58
+ if (this.errorMsg) {
59
+ output += `\n${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`;
60
+ }
61
+ this.renderFrame(output);
62
+ }
63
+ handleInput(char) {
64
+ if (char === '\r' || char === '\n') {
65
+ const selectedCount = this.checkedState.filter(Boolean).length;
66
+ const { min = 0, max } = this.options;
67
+ if (selectedCount < min) {
68
+ this.errorMsg = `You must select at least ${min} options.`;
69
+ this.render(false);
70
+ return;
71
+ }
72
+ if (max && selectedCount > max) {
73
+ this.errorMsg = `You can only select up to ${max} options.`;
74
+ this.render(false);
75
+ return;
76
+ }
77
+ this.cleanup();
78
+ // renderFrame cleans up lines, but doesn't print the final state "persisted" if we want to show the result?
79
+ // Usually we clear the prompt or show a summary.
80
+ // MepCLI seems to submit and let the caller decide or just print newline.
81
+ // Base `submit` prints newline.
82
+ const results = this.options.choices
83
+ .filter((_, i) => this.checkedState[i])
84
+ .map(c => c.value);
85
+ if (this._resolve)
86
+ this._resolve(results);
87
+ return;
88
+ }
89
+ if (char === ' ') {
90
+ const currentChecked = this.checkedState[this.selectedIndex];
91
+ const selectedCount = this.checkedState.filter(Boolean).length;
92
+ const { max } = this.options;
93
+ if (!currentChecked && max && selectedCount >= max) {
94
+ this.errorMsg = `Max ${max} selections allowed.`;
95
+ }
96
+ else {
97
+ this.checkedState[this.selectedIndex] = !currentChecked;
98
+ this.errorMsg = '';
99
+ }
100
+ this.render(false);
101
+ }
102
+ if (this.isUp(char)) { // Up
103
+ this.selectedIndex = this.selectedIndex > 0 ? this.selectedIndex - 1 : this.options.choices.length - 1;
104
+ this.errorMsg = '';
105
+ this.render(false);
106
+ }
107
+ if (this.isDown(char)) { // Down
108
+ this.selectedIndex = this.selectedIndex < this.options.choices.length - 1 ? this.selectedIndex + 1 : 0;
109
+ this.errorMsg = '';
110
+ this.render(false);
111
+ }
112
+ }
113
+ }
114
+ exports.CheckboxPrompt = CheckboxPrompt;
@@ -0,0 +1,7 @@
1
+ import { Prompt } from '../base';
2
+ import { ConfirmOptions } from '../types';
3
+ export declare class ConfirmPrompt extends Prompt<boolean, ConfirmOptions> {
4
+ constructor(options: ConfirmOptions);
5
+ protected render(firstRender: boolean): void;
6
+ protected handleInput(char: string): void;
7
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConfirmPrompt = void 0;
4
+ const ansi_1 = require("../ansi");
5
+ const base_1 = require("../base");
6
+ const theme_1 = require("../theme");
7
+ // --- Implementation: Confirm Prompt ---
8
+ class ConfirmPrompt extends base_1.Prompt {
9
+ constructor(options) {
10
+ super(options);
11
+ this.value = options.initial ?? true;
12
+ }
13
+ render(firstRender) {
14
+ // Prepare content
15
+ const hint = this.value ? `${ansi_1.ANSI.BOLD}Yes${ansi_1.ANSI.RESET}/no` : `yes/${ansi_1.ANSI.BOLD}No${ansi_1.ANSI.RESET}`;
16
+ 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} ${theme_1.theme.muted}(${hint})${ansi_1.ANSI.RESET} `;
17
+ const text = this.value ? 'Yes' : 'No';
18
+ output += `${theme_1.theme.main}${text}${ansi_1.ANSI.RESET}`;
19
+ this.renderFrame(output);
20
+ }
21
+ handleInput(char) {
22
+ const c = char.toLowerCase();
23
+ if (c === '\r' || c === '\n') {
24
+ this.submit(this.value);
25
+ return;
26
+ }
27
+ if (c === 'y') {
28
+ this.value = true;
29
+ this.render(false);
30
+ }
31
+ if (c === 'n') {
32
+ this.value = false;
33
+ this.render(false);
34
+ }
35
+ // Allow left/right to toggle as well for better UX
36
+ if (this.isLeft(char) || this.isRight(char)) {
37
+ this.value = !this.value;
38
+ this.render(false);
39
+ }
40
+ }
41
+ }
42
+ exports.ConfirmPrompt = ConfirmPrompt;
@@ -0,0 +1,10 @@
1
+ import { Prompt } from '../base';
2
+ import { DateOptions } from '../types';
3
+ export declare class DatePrompt extends Prompt<Date, DateOptions> {
4
+ private selectedField;
5
+ private errorMsg;
6
+ private inputBuffer;
7
+ constructor(options: DateOptions);
8
+ protected render(firstRender: boolean): void;
9
+ protected handleInput(char: string): void;
10
+ }
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DatePrompt = void 0;
4
+ const ansi_1 = require("../ansi");
5
+ const base_1 = require("../base");
6
+ const theme_1 = require("../theme");
7
+ // --- Implementation: Date Prompt ---
8
+ class DatePrompt extends base_1.Prompt {
9
+ constructor(options) {
10
+ super(options);
11
+ this.selectedField = 0; // 0: Year, 1: Month, 2: Day, 3: Hour, 4: Minute
12
+ this.errorMsg = '';
13
+ this.inputBuffer = '';
14
+ this.value = options.initial || new Date();
15
+ }
16
+ render(firstRender) {
17
+ // Date formatting
18
+ const y = this.value.getFullYear();
19
+ const m = (this.value.getMonth() + 1).toString().padStart(2, '0');
20
+ const d = this.value.getDate().toString().padStart(2, '0');
21
+ const h = this.value.getHours().toString().padStart(2, '0');
22
+ const min = this.value.getMinutes().toString().padStart(2, '0');
23
+ const fields = [y, m, d, h, min];
24
+ const display = fields.map((val, i) => {
25
+ if (i === this.selectedField)
26
+ return `${theme_1.theme.main}${ansi_1.ANSI.UNDERLINE}${val}${ansi_1.ANSI.RESET}`;
27
+ return val;
28
+ });
29
+ const icon = this.errorMsg ? `${theme_1.theme.error}✖` : `${theme_1.theme.success}?`;
30
+ const dateStr = `${display[0]}-${display[1]}-${display[2]} ${display[3]}:${display[4]}`;
31
+ let output = `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${dateStr} ${theme_1.theme.muted}(Use arrows or type)${ansi_1.ANSI.RESET}`;
32
+ if (this.errorMsg) {
33
+ output += `\n${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`;
34
+ }
35
+ this.renderFrame(output);
36
+ }
37
+ handleInput(char) {
38
+ if (char === '\r' || char === '\n') {
39
+ this.submit(this.value);
40
+ return;
41
+ }
42
+ if (this.isLeft(char)) { // Left
43
+ this.selectedField = Math.max(0, this.selectedField - 1);
44
+ this.inputBuffer = ''; // Reset buffer on move
45
+ this.errorMsg = '';
46
+ this.render(false);
47
+ return;
48
+ }
49
+ if (this.isRight(char)) { // Right
50
+ this.selectedField = Math.min(4, this.selectedField + 1);
51
+ this.inputBuffer = ''; // Reset buffer on move
52
+ this.errorMsg = '';
53
+ this.render(false);
54
+ return;
55
+ }
56
+ // Support numeric input
57
+ if (/^\d$/.test(char)) {
58
+ const maxLen = this.selectedField === 0 ? 4 : 2;
59
+ let nextBuffer = this.inputBuffer + char;
60
+ if (nextBuffer.length > maxLen) {
61
+ nextBuffer = char;
62
+ }
63
+ const val = parseInt(nextBuffer, 10);
64
+ let valid = true;
65
+ if (this.selectedField === 1 && (val < 1 || val > 12))
66
+ valid = false; // Month
67
+ if (this.selectedField === 2 && (val < 1 || val > 31))
68
+ valid = false; // Day (rough check)
69
+ if (this.selectedField === 3 && (val > 23))
70
+ valid = false; // Hour
71
+ if (this.selectedField === 4 && (val > 59))
72
+ valid = false; // Minute
73
+ if (!valid) {
74
+ nextBuffer = char;
75
+ }
76
+ this.inputBuffer = nextBuffer;
77
+ const finalVal = parseInt(this.inputBuffer, 10);
78
+ const d = new Date(this.value);
79
+ if (this.selectedField === 0) {
80
+ d.setFullYear(finalVal);
81
+ }
82
+ else if (this.selectedField === 1)
83
+ d.setMonth(Math.max(0, Math.min(11, finalVal - 1)));
84
+ else if (this.selectedField === 2)
85
+ d.setDate(Math.max(1, Math.min(31, finalVal)));
86
+ else if (this.selectedField === 3)
87
+ d.setHours(Math.max(0, Math.min(23, finalVal)));
88
+ else if (this.selectedField === 4)
89
+ d.setMinutes(Math.max(0, Math.min(59, finalVal)));
90
+ this.value = d;
91
+ this.errorMsg = '';
92
+ this.render(false);
93
+ return;
94
+ }
95
+ // Support standard and application cursor keys
96
+ const isUp = this.isUp(char);
97
+ const isDown = this.isDown(char);
98
+ if (isUp || isDown) {
99
+ this.inputBuffer = ''; // Reset buffer on arrow move
100
+ const dir = isUp ? 1 : -1;
101
+ const d = new Date(this.value);
102
+ switch (this.selectedField) {
103
+ case 0:
104
+ d.setFullYear(d.getFullYear() + dir);
105
+ break;
106
+ case 1:
107
+ d.setMonth(d.getMonth() + dir);
108
+ break;
109
+ case 2:
110
+ d.setDate(d.getDate() + dir);
111
+ break;
112
+ case 3:
113
+ d.setHours(d.getHours() + dir);
114
+ break;
115
+ case 4:
116
+ d.setMinutes(d.getMinutes() + dir);
117
+ break;
118
+ }
119
+ let valid = true;
120
+ if (this.options.min && d < this.options.min) {
121
+ this.errorMsg = 'Date cannot be before minimum allowed.';
122
+ valid = false;
123
+ }
124
+ if (this.options.max && d > this.options.max) {
125
+ this.errorMsg = 'Date cannot be after maximum allowed.';
126
+ valid = false;
127
+ }
128
+ if (valid) {
129
+ this.value = d;
130
+ this.errorMsg = '';
131
+ }
132
+ this.render(false);
133
+ }
134
+ }
135
+ }
136
+ exports.DatePrompt = DatePrompt;
@@ -0,0 +1,13 @@
1
+ import { Prompt } from '../base';
2
+ import { FileOptions } from '../types';
3
+ export declare class FilePrompt extends Prompt<string, FileOptions> {
4
+ private input;
5
+ private cursor;
6
+ private suggestions;
7
+ private selectedSuggestion;
8
+ private errorMsg;
9
+ constructor(options: FileOptions);
10
+ private updateSuggestions;
11
+ protected render(firstRender: boolean): void;
12
+ protected handleInput(char: string): void;
13
+ }
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FilePrompt = void 0;
37
+ const ansi_1 = require("../ansi");
38
+ const base_1 = require("../base");
39
+ const theme_1 = require("../theme");
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ // --- Implementation: File Prompt ---
43
+ class FilePrompt extends base_1.Prompt {
44
+ constructor(options) {
45
+ super(options);
46
+ this.input = '';
47
+ this.cursor = 0;
48
+ this.suggestions = [];
49
+ this.selectedSuggestion = -1;
50
+ this.errorMsg = '';
51
+ this.input = options.basePath || '';
52
+ this.cursor = this.input.length;
53
+ }
54
+ updateSuggestions() {
55
+ try {
56
+ const dir = path.dirname(this.input) || '.';
57
+ const partial = path.basename(this.input);
58
+ if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
59
+ const files = fs.readdirSync(dir);
60
+ this.suggestions = files
61
+ .filter(f => f.startsWith(partial))
62
+ .filter(f => {
63
+ const fullPath = path.join(dir, f);
64
+ // Handle errors if file doesn't exist or permission denied
65
+ try {
66
+ const isDir = fs.statSync(fullPath).isDirectory();
67
+ if (this.options.onlyDirectories && !isDir)
68
+ return false;
69
+ if (this.options.extensions && !isDir) {
70
+ return this.options.extensions.some(ext => f.endsWith(ext));
71
+ }
72
+ return true;
73
+ }
74
+ catch (e) {
75
+ return false;
76
+ }
77
+ })
78
+ .map(f => {
79
+ const fullPath = path.join(dir, f);
80
+ try {
81
+ if (fs.statSync(fullPath).isDirectory())
82
+ return f + '/';
83
+ }
84
+ catch (e) { /* ignore */ }
85
+ return f;
86
+ });
87
+ }
88
+ else {
89
+ this.suggestions = [];
90
+ }
91
+ }
92
+ catch (e) {
93
+ this.suggestions = [];
94
+ }
95
+ this.selectedSuggestion = -1;
96
+ }
97
+ render(firstRender) {
98
+ // Construct string
99
+ const icon = this.errorMsg ? `${theme_1.theme.error}✖` : `${theme_1.theme.success}?`;
100
+ let output = `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${this.input}`;
101
+ // Suggestions
102
+ if (this.suggestions.length > 0) {
103
+ output += '\n'; // Separate input from suggestions
104
+ const maxShow = 5;
105
+ const displayed = this.suggestions.slice(0, maxShow);
106
+ displayed.forEach((s, i) => {
107
+ if (i > 0)
108
+ output += '\n';
109
+ if (i === this.selectedSuggestion) {
110
+ output += `${theme_1.theme.main}❯ ${s}${ansi_1.ANSI.RESET}`;
111
+ }
112
+ else {
113
+ output += ` ${s}`;
114
+ }
115
+ });
116
+ if (this.suggestions.length > maxShow) {
117
+ output += `\n ...and ${this.suggestions.length - maxShow} more`;
118
+ }
119
+ }
120
+ this.renderFrame(output);
121
+ this.print(ansi_1.ANSI.SHOW_CURSOR);
122
+ // Restore Cursor Logic
123
+ // We need to move up to the input line if we printed suggestions.
124
+ // The input line is always the first line (index 0).
125
+ // So we move up by (totalLines - 1).
126
+ const totalLines = this.lastRenderHeight; // renderFrame sets this
127
+ if (totalLines > 1) {
128
+ this.print(`\x1b[${totalLines - 1}A`);
129
+ }
130
+ // Move right
131
+ const prefix = `${icon} ${theme_1.theme.title}${this.options.message} `;
132
+ const prefixLen = this.stripAnsi(prefix).length;
133
+ // Cursor is usually at the end of input unless we add backspace support etc.
134
+ // The cursor property tracks it, but my handleInput simplified it.
135
+ // Let's rely on this.input.length for now since handleInput appends.
136
+ // Ah, handleInput logic below supports cursor pos theoretically but I only see appending?
137
+ // Actually handleInput doesn't support left/right in the original code, it supports down/up for suggestions.
138
+ // So cursor is always at end.
139
+ const targetCol = prefixLen + this.input.length;
140
+ this.print(ansi_1.ANSI.CURSOR_LEFT);
141
+ if (targetCol > 0)
142
+ this.print(`\x1b[${targetCol}C`);
143
+ }
144
+ handleInput(char) {
145
+ if (char === '\t') { // Tab
146
+ if (this.suggestions.length === 1) {
147
+ const dir = path.dirname(this.input);
148
+ this.input = path.join(dir === '.' ? '' : dir, this.suggestions[0]);
149
+ this.cursor = this.input.length;
150
+ this.suggestions = [];
151
+ this.render(false);
152
+ }
153
+ else if (this.suggestions.length > 1) {
154
+ // Cycle or show? For now cycle if selected
155
+ if (this.selectedSuggestion !== -1) {
156
+ const dir = path.dirname(this.input);
157
+ this.input = path.join(dir === '.' ? '' : dir, this.suggestions[this.selectedSuggestion]);
158
+ this.cursor = this.input.length;
159
+ this.suggestions = [];
160
+ this.render(false);
161
+ }
162
+ else {
163
+ // Just show suggestions (already done in render loop usually, but update logic ensures it)
164
+ this.updateSuggestions();
165
+ this.render(false);
166
+ }
167
+ }
168
+ else {
169
+ this.updateSuggestions();
170
+ this.render(false);
171
+ }
172
+ return;
173
+ }
174
+ if (char === '\r' || char === '\n') {
175
+ if (this.selectedSuggestion !== -1) {
176
+ const dir = path.dirname(this.input);
177
+ this.input = path.join(dir === '.' ? '' : dir, this.suggestions[this.selectedSuggestion]);
178
+ this.cursor = this.input.length;
179
+ this.suggestions = [];
180
+ this.selectedSuggestion = -1;
181
+ this.render(false);
182
+ }
183
+ else {
184
+ this.submit(this.input);
185
+ }
186
+ return;
187
+ }
188
+ if (this.isDown(char)) { // Down
189
+ if (this.suggestions.length > 0) {
190
+ this.selectedSuggestion = (this.selectedSuggestion + 1) % Math.min(this.suggestions.length, 5);
191
+ this.render(false);
192
+ }
193
+ return;
194
+ }
195
+ if (this.isUp(char)) { // Up
196
+ if (this.suggestions.length > 0) {
197
+ this.selectedSuggestion = (this.selectedSuggestion - 1 + Math.min(this.suggestions.length, 5)) % Math.min(this.suggestions.length, 5);
198
+ this.render(false);
199
+ }
200
+ return;
201
+ }
202
+ if (char === '\u0008' || char === '\x7f') { // Backspace
203
+ if (this.input.length > 0) {
204
+ this.input = this.input.slice(0, -1);
205
+ this.updateSuggestions();
206
+ this.render(false);
207
+ }
208
+ return;
209
+ }
210
+ if (!/^[\x00-\x1F]/.test(char) && !char.startsWith('\x1b')) {
211
+ this.input += char;
212
+ this.updateSuggestions();
213
+ this.render(false);
214
+ }
215
+ }
216
+ }
217
+ exports.FilePrompt = FilePrompt;
@@ -0,0 +1,9 @@
1
+ import { Prompt } from '../base';
2
+ import { ListOptions } from '../types';
3
+ export declare class ListPrompt extends Prompt<string[], ListOptions> {
4
+ private currentInput;
5
+ private errorMsg;
6
+ constructor(options: ListOptions);
7
+ protected render(firstRender: boolean): void;
8
+ protected handleInput(char: string): void;
9
+ }