mepcli 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base.d.ts +4 -33
- package/dist/base.js +64 -88
- package/package.json +1 -1
package/dist/base.d.ts
CHANGED
|
@@ -2,8 +2,7 @@ import { MouseEvent } from './types';
|
|
|
2
2
|
import { detectCapabilities } from './utils';
|
|
3
3
|
/**
|
|
4
4
|
* Abstract base class for all prompts.
|
|
5
|
-
*
|
|
6
|
-
* to enforce DRY (Don't Repeat Yourself) principles.
|
|
5
|
+
* Implements a Robust Linear Scan Diffing Engine.
|
|
7
6
|
*/
|
|
8
7
|
export declare abstract class Prompt<T, O> {
|
|
9
8
|
protected options: O;
|
|
@@ -15,48 +14,20 @@ export declare abstract class Prompt<T, O> {
|
|
|
15
14
|
private _inputParser;
|
|
16
15
|
private _onKeyHandler?;
|
|
17
16
|
private _onDataHandler?;
|
|
18
|
-
protected lastRenderHeight: number;
|
|
19
17
|
protected lastRenderLines: string[];
|
|
18
|
+
protected lastRenderHeight: number;
|
|
20
19
|
protected capabilities: ReturnType<typeof detectCapabilities>;
|
|
21
20
|
constructor(options: O);
|
|
22
|
-
/**
|
|
23
|
-
* Renders the UI. Must be implemented by subclasses.
|
|
24
|
-
* @param firstRender Indicates if this is the initial render.
|
|
25
|
-
*/
|
|
26
21
|
protected abstract render(firstRender: boolean): void;
|
|
27
|
-
/**
|
|
28
|
-
* Handles specific key inputs. Must be implemented by subclasses.
|
|
29
|
-
* @param char The string representation of the key.
|
|
30
|
-
* @param key The raw buffer.
|
|
31
|
-
*/
|
|
32
22
|
protected abstract handleInput(char: string, key: Buffer): void;
|
|
33
|
-
/**
|
|
34
|
-
* Optional method to handle mouse events.
|
|
35
|
-
* Subclasses can override this to implement mouse interaction.
|
|
36
|
-
*/
|
|
37
23
|
protected handleMouse(_event: MouseEvent): void;
|
|
38
24
|
protected print(text: string): void;
|
|
39
|
-
/**
|
|
40
|
-
* Starts the prompt interaction.
|
|
41
|
-
* Sets up raw mode and listeners, returning a Promise.
|
|
42
|
-
*/
|
|
43
25
|
run(): Promise<T>;
|
|
44
|
-
/**
|
|
45
|
-
* Cleans up listeners and restores stdin state.
|
|
46
|
-
*/
|
|
47
26
|
protected cleanup(): void;
|
|
48
|
-
/**
|
|
49
|
-
* Submits the final value and resolves the promise.
|
|
50
|
-
*/
|
|
51
27
|
protected submit(result: T): void;
|
|
52
28
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*/
|
|
56
|
-
/**
|
|
57
|
-
* Renders the current frame by clearing the previous output and writing the new content.
|
|
58
|
-
* This approach ("Move Up -> Erase Down -> Print") is more robust against artifacts
|
|
59
|
-
* than line-by-line diffing, especially when the number of lines changes (e.g., filtering).
|
|
29
|
+
* Renders the frame using a linear scan diffing algorithm.
|
|
30
|
+
* Prevents flicker and handles height changes (expand/collapse) robustly.
|
|
60
31
|
*/
|
|
61
32
|
protected renderFrame(content: string): void;
|
|
62
33
|
protected stripAnsi(str: string): string;
|
package/dist/base.js
CHANGED
|
@@ -6,32 +6,22 @@ const input_1 = require("./input");
|
|
|
6
6
|
const utils_1 = require("./utils");
|
|
7
7
|
/**
|
|
8
8
|
* Abstract base class for all prompts.
|
|
9
|
-
*
|
|
10
|
-
* to enforce DRY (Don't Repeat Yourself) principles.
|
|
9
|
+
* Implements a Robust Linear Scan Diffing Engine.
|
|
11
10
|
*/
|
|
12
11
|
class Prompt {
|
|
13
12
|
constructor(options) {
|
|
14
|
-
// Smart Cursor State
|
|
15
|
-
this.lastRenderHeight = 0;
|
|
16
13
|
this.lastRenderLines = [];
|
|
14
|
+
this.lastRenderHeight = 0;
|
|
17
15
|
this.options = options;
|
|
18
16
|
this.stdin = process.stdin;
|
|
19
17
|
this.stdout = process.stdout;
|
|
20
18
|
this._inputParser = new input_1.InputParser();
|
|
21
19
|
this.capabilities = (0, utils_1.detectCapabilities)();
|
|
22
20
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Optional method to handle mouse events.
|
|
25
|
-
* Subclasses can override this to implement mouse interaction.
|
|
26
|
-
*/
|
|
27
21
|
handleMouse(_event) { }
|
|
28
22
|
print(text) {
|
|
29
23
|
this.stdout.write(text);
|
|
30
24
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Starts the prompt interaction.
|
|
33
|
-
* Sets up raw mode and listeners, returning a Promise.
|
|
34
|
-
*/
|
|
35
25
|
run() {
|
|
36
26
|
return new Promise((resolve, reject) => {
|
|
37
27
|
this._resolve = resolve;
|
|
@@ -41,24 +31,15 @@ class Prompt {
|
|
|
41
31
|
}
|
|
42
32
|
this.stdin.resume();
|
|
43
33
|
this.stdin.setEncoding('utf8');
|
|
44
|
-
// Enable Mouse Tracking if supported and requested
|
|
45
|
-
// Default to true if capabilities support it, unless explicitly disabled in options
|
|
46
34
|
const shouldEnableMouse = this.options.mouse !== false && this.capabilities.hasMouse;
|
|
47
35
|
if (shouldEnableMouse) {
|
|
48
36
|
this.print(ansi_1.ANSI.SET_ANY_EVENT_MOUSE + ansi_1.ANSI.SET_SGR_EXT_MODE_MOUSE);
|
|
49
37
|
}
|
|
50
|
-
// Initial render: Default to hidden cursor (good for menus)
|
|
51
|
-
// Subclasses like TextPrompt will explicitly show it if needed.
|
|
52
|
-
if (this.capabilities.isCI) {
|
|
53
|
-
// In CI, maybe don't hide cursor or do nothing?
|
|
54
|
-
// But for now follow standard flow.
|
|
55
|
-
}
|
|
56
38
|
this.print(ansi_1.ANSI.HIDE_CURSOR);
|
|
39
|
+
// Initial render
|
|
57
40
|
this.render(true);
|
|
58
|
-
// Setup Input Parser Listeners
|
|
59
41
|
this._onKeyHandler = (char, buffer) => {
|
|
60
|
-
|
|
61
|
-
if (char === '\u0003') {
|
|
42
|
+
if (char === '\u0003') { // Ctrl+C
|
|
62
43
|
this.cleanup();
|
|
63
44
|
this.print(ansi_1.ANSI.SHOW_CURSOR + '\n');
|
|
64
45
|
if (this._reject)
|
|
@@ -68,7 +49,6 @@ class Prompt {
|
|
|
68
49
|
this.handleInput(char, buffer);
|
|
69
50
|
};
|
|
70
51
|
this._inputParser.on('keypress', this._onKeyHandler);
|
|
71
|
-
// Listen to mouse events
|
|
72
52
|
this._inputParser.on('mouse', (event) => {
|
|
73
53
|
this.handleMouse(event);
|
|
74
54
|
});
|
|
@@ -78,9 +58,6 @@ class Prompt {
|
|
|
78
58
|
this.stdin.on('data', this._onDataHandler);
|
|
79
59
|
});
|
|
80
60
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Cleans up listeners and restores stdin state.
|
|
83
|
-
*/
|
|
84
61
|
cleanup() {
|
|
85
62
|
if (this._onDataHandler) {
|
|
86
63
|
this.stdin.removeListener('data', this._onDataHandler);
|
|
@@ -88,7 +65,6 @@ class Prompt {
|
|
|
88
65
|
if (this._onKeyHandler) {
|
|
89
66
|
this._inputParser.removeListener('keypress', this._onKeyHandler);
|
|
90
67
|
}
|
|
91
|
-
// Disable Mouse Tracking
|
|
92
68
|
this.print(ansi_1.ANSI.DISABLE_MOUSE);
|
|
93
69
|
if (typeof this.stdin.setRawMode === 'function') {
|
|
94
70
|
this.stdin.setRawMode(false);
|
|
@@ -96,51 +72,74 @@ class Prompt {
|
|
|
96
72
|
this.stdin.pause();
|
|
97
73
|
this.print(ansi_1.ANSI.SHOW_CURSOR);
|
|
98
74
|
}
|
|
99
|
-
/**
|
|
100
|
-
* Submits the final value and resolves the promise.
|
|
101
|
-
*/
|
|
102
75
|
submit(result) {
|
|
103
76
|
this.cleanup();
|
|
104
77
|
this.print('\n');
|
|
105
78
|
if (this._resolve)
|
|
106
79
|
this._resolve(result);
|
|
107
80
|
}
|
|
108
|
-
// --- Rendering Utilities ---
|
|
109
|
-
/**
|
|
110
|
-
* Render Method with Diffing (Virtual DOM for CLI).
|
|
111
|
-
* Calculates new lines, compares with old lines, and updates only changed parts.
|
|
112
|
-
*/
|
|
113
81
|
/**
|
|
114
|
-
* Renders the
|
|
115
|
-
*
|
|
116
|
-
* than line-by-line diffing, especially when the number of lines changes (e.g., filtering).
|
|
82
|
+
* Renders the frame using a linear scan diffing algorithm.
|
|
83
|
+
* Prevents flicker and handles height changes (expand/collapse) robustly.
|
|
117
84
|
*/
|
|
118
85
|
renderFrame(content) {
|
|
119
86
|
const width = this.stdout.columns || 80;
|
|
120
87
|
const rawLines = content.split('\n');
|
|
121
|
-
// Truncate
|
|
88
|
+
// Truncate lines to prevent wrapping artifacts
|
|
122
89
|
const newLines = rawLines.map(line => this.truncate(line, width));
|
|
123
|
-
// 1.
|
|
124
|
-
this.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
90
|
+
// 1. First Render Case
|
|
91
|
+
if (this.lastRenderLines.length === 0) {
|
|
92
|
+
this.print(newLines.join('\n'));
|
|
93
|
+
this.lastRenderLines = newLines;
|
|
94
|
+
this.lastRenderHeight = newLines.length;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
let outputBuffer = '';
|
|
98
|
+
// 2. Return Cursor to the Top of the Prompt
|
|
99
|
+
if (this.lastRenderHeight > 1) {
|
|
100
|
+
outputBuffer += `\x1b[${this.lastRenderHeight - 1}A`;
|
|
131
101
|
}
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
this.print(ansi_1.ANSI.ERASE_DOWN);
|
|
135
|
-
// 4. Print the new frame content
|
|
102
|
+
outputBuffer += '\r'; // Ensure column 0
|
|
103
|
+
// 3. Linear Scan & Update
|
|
136
104
|
for (let i = 0; i < newLines.length; i++) {
|
|
137
|
-
|
|
138
|
-
//
|
|
139
|
-
if (i
|
|
140
|
-
this.
|
|
105
|
+
const newLine = newLines[i];
|
|
106
|
+
// Logic for moving to the next line
|
|
107
|
+
if (i > 0) {
|
|
108
|
+
if (i < this.lastRenderLines.length) {
|
|
109
|
+
// Moving within the previously existing area.
|
|
110
|
+
// Use 'Down' (B) to avoid scrolling/shifting existing content.
|
|
111
|
+
outputBuffer += '\x1b[B\r';
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Moving into NEW area (Append).
|
|
115
|
+
// Must use '\n' to create the new line.
|
|
116
|
+
outputBuffer += '\n';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Printing logic
|
|
120
|
+
if (i < this.lastRenderLines.length) {
|
|
121
|
+
const oldLine = this.lastRenderLines[i];
|
|
122
|
+
if (newLine !== oldLine) {
|
|
123
|
+
// Overwrite existing line
|
|
124
|
+
outputBuffer += ansi_1.ANSI.ERASE_LINE + newLine;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// Print new line (we are already at start due to '\n' above)
|
|
129
|
+
outputBuffer += newLine;
|
|
141
130
|
}
|
|
142
131
|
}
|
|
143
|
-
//
|
|
132
|
+
// 4. Handle Shrinkage (Clear garbage below)
|
|
133
|
+
if (newLines.length < this.lastRenderLines.length) {
|
|
134
|
+
// Move down to the first obsolete line
|
|
135
|
+
outputBuffer += '\n';
|
|
136
|
+
// Clear everything below
|
|
137
|
+
outputBuffer += ansi_1.ANSI.ERASE_DOWN;
|
|
138
|
+
// Move back up to the last valid line to maintain cursor state consistency
|
|
139
|
+
outputBuffer += `\x1b[A`;
|
|
140
|
+
}
|
|
141
|
+
this.print(outputBuffer);
|
|
142
|
+
// Update State
|
|
144
143
|
this.lastRenderLines = newLines;
|
|
145
144
|
this.lastRenderHeight = newLines.length;
|
|
146
145
|
}
|
|
@@ -152,51 +151,28 @@ class Prompt {
|
|
|
152
151
|
if (visualWidth <= width) {
|
|
153
152
|
return str;
|
|
154
153
|
}
|
|
155
|
-
// Heuristic truncation using stringWidth
|
|
156
|
-
// We iterate and sum width until we hit limit - 3
|
|
157
154
|
let currentWidth = 0;
|
|
158
155
|
let cutIndex = 0;
|
|
159
156
|
let inAnsi = false;
|
|
160
157
|
for (let i = 0; i < str.length; i++) {
|
|
161
|
-
|
|
162
|
-
if (str[i] === '\x1b') {
|
|
158
|
+
if (str[i] === '\x1b')
|
|
163
159
|
inAnsi = true;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
inAnsi = false;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
// Re-implement basic width logic here for the cut index finding
|
|
172
|
-
let charWidth = 1;
|
|
173
|
-
let cp = code;
|
|
174
|
-
if (code >= 0xD800 && code <= 0xDBFF && i + 1 < str.length) {
|
|
175
|
-
const next = str.charCodeAt(i + 1);
|
|
176
|
-
if (next >= 0xDC00 && next <= 0xDFFF) {
|
|
177
|
-
cp = (code - 0xD800) * 0x400 + (next - 0xDC00) + 0x10000;
|
|
178
|
-
// i is incremented in main loop but we need to skip next char
|
|
179
|
-
// We'll handle i increment in the loop
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
if (cp >= 0x1100) { // Quick check for potentially wide
|
|
183
|
-
// It's acceptable to be slightly aggressive on wide chars for truncation
|
|
184
|
-
charWidth = 2;
|
|
185
|
-
}
|
|
160
|
+
if (!inAnsi) {
|
|
161
|
+
const code = str.charCodeAt(i);
|
|
162
|
+
const charWidth = code > 255 ? 2 : 1;
|
|
186
163
|
if (currentWidth + charWidth > width - 3) {
|
|
187
|
-
cutIndex = i;
|
|
188
164
|
break;
|
|
189
165
|
}
|
|
190
166
|
currentWidth += charWidth;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
if (str[i] === 'm' || (str[i] >= 'A' && str[i] <= 'Z'))
|
|
170
|
+
inAnsi = false;
|
|
194
171
|
}
|
|
195
172
|
cutIndex = i + 1;
|
|
196
173
|
}
|
|
197
174
|
return str.substring(0, cutIndex) + '...' + ansi_1.ANSI.RESET;
|
|
198
175
|
}
|
|
199
|
-
// Helper to check for arrow keys including application mode
|
|
200
176
|
isUp(char) {
|
|
201
177
|
return char === '\u001b[A' || char === '\u001bOA';
|
|
202
178
|
}
|