spawn-term 3.4.2 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/lib/TerminalBuffer.js +50 -203
- package/dist/cjs/lib/TerminalBuffer.js.map +1 -1
- package/dist/cjs/src/lib/TerminalBuffer.d.ts +15 -2
- package/dist/esm/lib/TerminalBuffer.js +37 -140
- package/dist/esm/lib/TerminalBuffer.js.map +1 -1
- package/dist/esm/src/lib/TerminalBuffer.d.ts +15 -2
- package/package.json +3 -3
|
@@ -8,7 +8,15 @@ Object.defineProperty(exports, "TerminalBuffer", {
|
|
|
8
8
|
return TerminalBuffer;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
-
var
|
|
11
|
+
var _terminalmodel = require("terminal-model");
|
|
12
|
+
function _array_like_to_array(arr, len) {
|
|
13
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
14
|
+
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
15
|
+
return arr2;
|
|
16
|
+
}
|
|
17
|
+
function _array_without_holes(arr) {
|
|
18
|
+
if (Array.isArray(arr)) return _array_like_to_array(arr);
|
|
19
|
+
}
|
|
12
20
|
function _class_call_check(instance, Constructor) {
|
|
13
21
|
if (!(instance instanceof Constructor)) {
|
|
14
22
|
throw new TypeError("Cannot call a class as a function");
|
|
@@ -28,152 +36,38 @@ function _create_class(Constructor, protoProps, staticProps) {
|
|
|
28
36
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
29
37
|
return Constructor;
|
|
30
38
|
}
|
|
31
|
-
function
|
|
32
|
-
if (
|
|
33
|
-
Object.defineProperty(obj, key, {
|
|
34
|
-
value: value,
|
|
35
|
-
enumerable: true,
|
|
36
|
-
configurable: true,
|
|
37
|
-
writable: true
|
|
38
|
-
});
|
|
39
|
-
} else {
|
|
40
|
-
obj[key] = value;
|
|
41
|
-
}
|
|
42
|
-
return obj;
|
|
43
|
-
}
|
|
44
|
-
function _getRequireWildcardCache(nodeInterop) {
|
|
45
|
-
if (typeof WeakMap !== "function") return null;
|
|
46
|
-
var cacheBabelInterop = new WeakMap();
|
|
47
|
-
var cacheNodeInterop = new WeakMap();
|
|
48
|
-
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
49
|
-
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
50
|
-
})(nodeInterop);
|
|
51
|
-
}
|
|
52
|
-
function _interop_require_wildcard(obj, nodeInterop) {
|
|
53
|
-
if (!nodeInterop && obj && obj.__esModule) {
|
|
54
|
-
return obj;
|
|
55
|
-
}
|
|
56
|
-
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
57
|
-
return {
|
|
58
|
-
default: obj
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
var cache = _getRequireWildcardCache(nodeInterop);
|
|
62
|
-
if (cache && cache.has(obj)) {
|
|
63
|
-
return cache.get(obj);
|
|
64
|
-
}
|
|
65
|
-
var newObj = {
|
|
66
|
-
__proto__: null
|
|
67
|
-
};
|
|
68
|
-
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
69
|
-
for(var key in obj){
|
|
70
|
-
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
71
|
-
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
72
|
-
if (desc && (desc.get || desc.set)) {
|
|
73
|
-
Object.defineProperty(newObj, key, desc);
|
|
74
|
-
} else {
|
|
75
|
-
newObj[key] = obj[key];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
newObj.default = obj;
|
|
80
|
-
if (cache) {
|
|
81
|
-
cache.set(obj, newObj);
|
|
82
|
-
}
|
|
83
|
-
return newObj;
|
|
39
|
+
function _iterable_to_array(iter) {
|
|
40
|
+
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
|
|
84
41
|
}
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
var source = arguments[i] != null ? arguments[i] : {};
|
|
88
|
-
var ownKeys = Object.keys(source);
|
|
89
|
-
if (typeof Object.getOwnPropertySymbols === "function") {
|
|
90
|
-
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
|
|
91
|
-
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
92
|
-
}));
|
|
93
|
-
}
|
|
94
|
-
ownKeys.forEach(function(key) {
|
|
95
|
-
_define_property(target, key, source[key]);
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
return target;
|
|
42
|
+
function _non_iterable_spread() {
|
|
43
|
+
throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
99
44
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
var Terminal = _headless.Terminal || ((_xterm_default = _headless.default) === null || _xterm_default === void 0 ? void 0 : _xterm_default.Terminal);
|
|
103
|
-
// ANSI color mode constants from xterm.js
|
|
104
|
-
var COLOR_MODE_DEFAULT = 0;
|
|
105
|
-
var COLOR_MODE_16 = 16777216; // 0x1000000 - 16 color palette (0-15)
|
|
106
|
-
var COLOR_MODE_256 = 33554432; // 0x2000000 - 256 color palette
|
|
107
|
-
var COLOR_MODE_RGB = 50331648; // 0x3000000 - 24-bit RGB
|
|
108
|
-
var DEFAULT_STYLE = {
|
|
109
|
-
fg: -1,
|
|
110
|
-
fgMode: COLOR_MODE_DEFAULT,
|
|
111
|
-
bg: -1,
|
|
112
|
-
bgMode: COLOR_MODE_DEFAULT,
|
|
113
|
-
bold: false,
|
|
114
|
-
dim: false,
|
|
115
|
-
italic: false,
|
|
116
|
-
underline: false,
|
|
117
|
-
inverse: false,
|
|
118
|
-
strikethrough: false
|
|
119
|
-
};
|
|
120
|
-
function styleEquals(a, b) {
|
|
121
|
-
return a.fg === b.fg && a.fgMode === b.fgMode && a.bg === b.bg && a.bgMode === b.bgMode && a.bold === b.bold && a.dim === b.dim && a.italic === b.italic && a.underline === b.underline && a.inverse === b.inverse && a.strikethrough === b.strikethrough;
|
|
45
|
+
function _to_consumable_array(arr) {
|
|
46
|
+
return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
|
|
122
47
|
}
|
|
123
|
-
function
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
if (
|
|
129
|
-
if (
|
|
130
|
-
if (style.inverse) codes.push(7);
|
|
131
|
-
if (style.strikethrough) codes.push(9);
|
|
132
|
-
// Foreground color
|
|
133
|
-
if (style.fgMode === COLOR_MODE_16) {
|
|
134
|
-
// 16-color palette: 0-7 are 30-37, 8-15 are 90-97
|
|
135
|
-
if (style.fg < 8) {
|
|
136
|
-
codes.push(30 + style.fg);
|
|
137
|
-
} else {
|
|
138
|
-
codes.push(90 + (style.fg - 8));
|
|
139
|
-
}
|
|
140
|
-
} else if (style.fgMode === COLOR_MODE_256) {
|
|
141
|
-
codes.push(38, 5, style.fg);
|
|
142
|
-
} else if (style.fgMode === COLOR_MODE_RGB) {
|
|
143
|
-
// RGB is encoded in the color value
|
|
144
|
-
var r = style.fg >> 16 & 0xff;
|
|
145
|
-
var g = style.fg >> 8 & 0xff;
|
|
146
|
-
var b = style.fg & 0xff;
|
|
147
|
-
codes.push(38, 2, r, g, b);
|
|
148
|
-
}
|
|
149
|
-
// Background color
|
|
150
|
-
if (style.bgMode === COLOR_MODE_16) {
|
|
151
|
-
if (style.bg < 8) {
|
|
152
|
-
codes.push(40 + style.bg);
|
|
153
|
-
} else {
|
|
154
|
-
codes.push(100 + (style.bg - 8));
|
|
155
|
-
}
|
|
156
|
-
} else if (style.bgMode === COLOR_MODE_256) {
|
|
157
|
-
codes.push(48, 5, style.bg);
|
|
158
|
-
} else if (style.bgMode === COLOR_MODE_RGB) {
|
|
159
|
-
var r1 = style.bg >> 16 & 0xff;
|
|
160
|
-
var g1 = style.bg >> 8 & 0xff;
|
|
161
|
-
var b1 = style.bg & 0xff;
|
|
162
|
-
codes.push(48, 2, r1, g1, b1);
|
|
163
|
-
}
|
|
164
|
-
if (codes.length === 0) return '';
|
|
165
|
-
return "\x1b[".concat(codes.join(';'), "m");
|
|
48
|
+
function _unsupported_iterable_to_array(o, minLen) {
|
|
49
|
+
if (!o) return;
|
|
50
|
+
if (typeof o === "string") return _array_like_to_array(o, minLen);
|
|
51
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
52
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
53
|
+
if (n === "Map" || n === "Set") return Array.from(n);
|
|
54
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
166
55
|
}
|
|
167
56
|
var TerminalBuffer = /*#__PURE__*/ function() {
|
|
168
57
|
"use strict";
|
|
169
|
-
function TerminalBuffer(
|
|
170
|
-
var
|
|
58
|
+
function TerminalBuffer(_cols) {
|
|
59
|
+
var _this = this;
|
|
60
|
+
var _scrollback = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 10000;
|
|
171
61
|
_class_call_check(this, TerminalBuffer);
|
|
172
|
-
this.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
62
|
+
this.allLines = [];
|
|
63
|
+
// terminal-model doesn't enforce column width during parsing
|
|
64
|
+
// It preserves all content as-is
|
|
65
|
+
this.terminal = new _terminalmodel.StreamingTerminal();
|
|
66
|
+
// Listen for completed lines (when \n is encountered)
|
|
67
|
+
this.terminal.setLineReadyCallback(function() {
|
|
68
|
+
var line = _this.terminal.renderLine();
|
|
69
|
+
_this.terminal.reset();
|
|
70
|
+
_this.allLines.push(line);
|
|
177
71
|
});
|
|
178
72
|
}
|
|
179
73
|
var _proto = TerminalBuffer.prototype;
|
|
@@ -181,79 +75,31 @@ var TerminalBuffer = /*#__PURE__*/ function() {
|
|
|
181
75
|
* Write raw data to the terminal buffer.
|
|
182
76
|
* The terminal interprets all ANSI sequences automatically.
|
|
183
77
|
*/ _proto.write = function write(data) {
|
|
184
|
-
|
|
185
|
-
this.terminal.write(str);
|
|
78
|
+
this.terminal.write(data);
|
|
186
79
|
};
|
|
187
80
|
/**
|
|
188
81
|
* Resize the terminal width.
|
|
189
|
-
|
|
190
|
-
|
|
82
|
+
* terminal-model doesn't use column constraints, so this is a no-op for compatibility.
|
|
83
|
+
*/ _proto.resize = function resize(_cols) {
|
|
84
|
+
// No-op - terminal-model doesn't enforce column width
|
|
191
85
|
};
|
|
192
86
|
/**
|
|
193
87
|
* Extract the rendered lines from the terminal buffer.
|
|
194
88
|
* This returns the actual visible content after all ANSI sequences
|
|
195
89
|
* have been processed, with color codes preserved.
|
|
90
|
+
*
|
|
91
|
+
* CRITICAL: Unlike the xterm implementation, we do NOT call trimStart(),
|
|
92
|
+
* which preserves legitimate indentation and blank lines.
|
|
196
93
|
*/ _proto.getLines = function getLines() {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
var result = '';
|
|
203
|
-
var currentStyle = _object_spread({}, DEFAULT_STYLE);
|
|
204
|
-
var _hasContent = false;
|
|
205
|
-
// First pass: find the last non-empty cell to know where content ends
|
|
206
|
-
var lastContentIndex = -1;
|
|
207
|
-
for(var j = bufferLine.length - 1; j >= 0; j--){
|
|
208
|
-
var cell = bufferLine.getCell(j);
|
|
209
|
-
if (cell && cell.getChars()) {
|
|
210
|
-
lastContentIndex = j;
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
// Second pass: build the line with ANSI codes
|
|
215
|
-
for(var j1 = 0; j1 <= lastContentIndex; j1++){
|
|
216
|
-
var cell1 = bufferLine.getCell(j1);
|
|
217
|
-
if (!cell1) continue;
|
|
218
|
-
var char = cell1.getChars();
|
|
219
|
-
var cellStyle = {
|
|
220
|
-
fg: cell1.getFgColor(),
|
|
221
|
-
fgMode: cell1.getFgColorMode(),
|
|
222
|
-
bg: cell1.getBgColor(),
|
|
223
|
-
bgMode: cell1.getBgColorMode(),
|
|
224
|
-
bold: cell1.isBold() !== 0,
|
|
225
|
-
dim: cell1.isDim() !== 0,
|
|
226
|
-
italic: cell1.isItalic() !== 0,
|
|
227
|
-
underline: cell1.isUnderline() !== 0,
|
|
228
|
-
inverse: cell1.isInverse() !== 0,
|
|
229
|
-
strikethrough: cell1.isStrikethrough() !== 0
|
|
230
|
-
};
|
|
231
|
-
// Check if style changed
|
|
232
|
-
if (!styleEquals(cellStyle, currentStyle)) {
|
|
233
|
-
// Reset if going back to default, otherwise emit new style
|
|
234
|
-
if (styleEquals(cellStyle, DEFAULT_STYLE)) {
|
|
235
|
-
result += '\x1b[0m';
|
|
236
|
-
} else {
|
|
237
|
-
// If we had styling before, reset first for clean transition
|
|
238
|
-
if (!styleEquals(currentStyle, DEFAULT_STYLE)) {
|
|
239
|
-
result += '\x1b[0m';
|
|
240
|
-
}
|
|
241
|
-
result += buildAnsiCode(cellStyle);
|
|
242
|
-
}
|
|
243
|
-
currentStyle = cellStyle;
|
|
244
|
-
}
|
|
245
|
-
result += char || ' ';
|
|
246
|
-
if (char) _hasContent = true;
|
|
247
|
-
}
|
|
248
|
-
// Reset at end of line if we had styling
|
|
249
|
-
if (!styleEquals(currentStyle, DEFAULT_STYLE)) {
|
|
250
|
-
result += '\x1b[0m';
|
|
251
|
-
}
|
|
252
|
-
// Trim leading whitespace - tools like ncu/npm use cursor positioning
|
|
253
|
-
// which creates lines with leading spaces when interpreted by xterm
|
|
254
|
-
lines.push(result.trimStart());
|
|
94
|
+
// Flush any pending content (incomplete line without \n)
|
|
95
|
+
if (this.terminal.hasContent()) {
|
|
96
|
+
var line = this.terminal.renderLine();
|
|
97
|
+
this.terminal.reset();
|
|
98
|
+
this.allLines.push(line);
|
|
255
99
|
}
|
|
256
|
-
//
|
|
100
|
+
// Return copy of all lines WITHOUT trimStart() - preserves whitespace
|
|
101
|
+
var lines = _to_consumable_array(this.allLines);
|
|
102
|
+
// Trim trailing empty lines only
|
|
257
103
|
while(lines.length > 0 && lines[lines.length - 1] === ''){
|
|
258
104
|
lines.pop();
|
|
259
105
|
}
|
|
@@ -263,6 +109,7 @@ var TerminalBuffer = /*#__PURE__*/ function() {
|
|
|
263
109
|
* Clean up terminal resources.
|
|
264
110
|
*/ _proto.dispose = function dispose() {
|
|
265
111
|
this.terminal.dispose();
|
|
112
|
+
this.allLines = [];
|
|
266
113
|
};
|
|
267
114
|
_create_class(TerminalBuffer, [
|
|
268
115
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/TerminalBuffer.ts"],"sourcesContent":["import * as xterm from '@xterm/headless';\n\n// Handle both ESM and CJS module formats\nconst Terminal = (xterm as { Terminal: typeof xterm.Terminal; default?: { Terminal: typeof xterm.Terminal } }).Terminal || (xterm as { default?: { Terminal: typeof xterm.Terminal } }).default?.Terminal;\n\n// ANSI color mode constants from xterm.js\nconst COLOR_MODE_DEFAULT = 0;\nconst COLOR_MODE_16 = 16777216; // 0x1000000 - 16 color palette (0-15)\nconst COLOR_MODE_256 = 33554432; // 0x2000000 - 256 color palette\nconst COLOR_MODE_RGB = 50331648; // 0x3000000 - 24-bit RGB\n\n/**\n * Wrapper around @xterm/headless Terminal that provides a virtual terminal buffer.\n * Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce\n * the actual rendered output rather than raw intermediate states.\n */\n// Cell attribute state for tracking changes\ninterface CellStyle {\n fg: number;\n fgMode: number;\n bg: number;\n bgMode: number;\n bold: boolean;\n dim: boolean;\n italic: boolean;\n underline: boolean;\n inverse: boolean;\n strikethrough: boolean;\n}\n\nconst DEFAULT_STYLE: CellStyle = {\n fg: -1,\n fgMode: COLOR_MODE_DEFAULT,\n bg: -1,\n bgMode: COLOR_MODE_DEFAULT,\n bold: false,\n dim: false,\n italic: false,\n underline: false,\n inverse: false,\n strikethrough: false,\n};\n\nfunction styleEquals(a: CellStyle, b: CellStyle): boolean {\n return a.fg === b.fg && a.fgMode === b.fgMode && a.bg === b.bg && a.bgMode === b.bgMode && a.bold === b.bold && a.dim === b.dim && a.italic === b.italic && a.underline === b.underline && a.inverse === b.inverse && a.strikethrough === b.strikethrough;\n}\n\nfunction buildAnsiCode(style: CellStyle): string {\n const codes: number[] = [];\n\n // Attributes\n if (style.bold) codes.push(1);\n if (style.dim) codes.push(2);\n if (style.italic) codes.push(3);\n if (style.underline) codes.push(4);\n if (style.inverse) codes.push(7);\n if (style.strikethrough) codes.push(9);\n\n // Foreground color\n if (style.fgMode === COLOR_MODE_16) {\n // 16-color palette: 0-7 are 30-37, 8-15 are 90-97\n if (style.fg < 8) {\n codes.push(30 + style.fg);\n } else {\n codes.push(90 + (style.fg - 8));\n }\n } else if (style.fgMode === COLOR_MODE_256) {\n codes.push(38, 5, style.fg);\n } else if (style.fgMode === COLOR_MODE_RGB) {\n // RGB is encoded in the color value\n const r = (style.fg >> 16) & 0xff;\n const g = (style.fg >> 8) & 0xff;\n const b = style.fg & 0xff;\n codes.push(38, 2, r, g, b);\n }\n\n // Background color\n if (style.bgMode === COLOR_MODE_16) {\n if (style.bg < 8) {\n codes.push(40 + style.bg);\n } else {\n codes.push(100 + (style.bg - 8));\n }\n } else if (style.bgMode === COLOR_MODE_256) {\n codes.push(48, 5, style.bg);\n } else if (style.bgMode === COLOR_MODE_RGB) {\n const r = (style.bg >> 16) & 0xff;\n const g = (style.bg >> 8) & 0xff;\n const b = style.bg & 0xff;\n codes.push(48, 2, r, g, b);\n }\n\n if (codes.length === 0) return '';\n return `\\x1b[${codes.join(';')}m`;\n}\n\nexport class TerminalBuffer {\n private terminal: InstanceType<typeof Terminal>;\n\n constructor(cols: number, scrollback = 10000) {\n this.terminal = new Terminal({\n cols,\n rows: 50, // Visible rows (doesn't matter much for headless)\n scrollback,\n allowProposedApi: true,\n });\n }\n\n /**\n * Write raw data to the terminal buffer.\n * The terminal interprets all ANSI sequences automatically.\n */\n write(data: string | Buffer): void {\n const str = typeof data === 'string' ? data : data.toString('utf8');\n this.terminal.write(str);\n }\n\n /**\n * Resize the terminal width.\n */\n resize(cols: number): void {\n this.terminal.resize(cols, this.terminal.rows);\n }\n\n /**\n * Extract the rendered lines from the terminal buffer.\n * This returns the actual visible content after all ANSI sequences\n * have been processed, with color codes preserved.\n */\n getLines(): string[] {\n const buffer = this.terminal.buffer.active;\n const lines: string[] = [];\n\n for (let i = 0; i < buffer.length; i++) {\n const bufferLine = buffer.getLine(i);\n if (!bufferLine) continue;\n\n let result = '';\n let currentStyle: CellStyle = { ...DEFAULT_STYLE };\n let _hasContent = false;\n\n // First pass: find the last non-empty cell to know where content ends\n let lastContentIndex = -1;\n for (let j = bufferLine.length - 1; j >= 0; j--) {\n const cell = bufferLine.getCell(j);\n if (cell && cell.getChars()) {\n lastContentIndex = j;\n break;\n }\n }\n\n // Second pass: build the line with ANSI codes\n for (let j = 0; j <= lastContentIndex; j++) {\n const cell = bufferLine.getCell(j);\n if (!cell) continue;\n\n const char = cell.getChars();\n const cellStyle: CellStyle = {\n fg: cell.getFgColor(),\n fgMode: cell.getFgColorMode(),\n bg: cell.getBgColor(),\n bgMode: cell.getBgColorMode(),\n bold: cell.isBold() !== 0,\n dim: cell.isDim() !== 0,\n italic: cell.isItalic() !== 0,\n underline: cell.isUnderline() !== 0,\n inverse: cell.isInverse() !== 0,\n strikethrough: cell.isStrikethrough() !== 0,\n };\n\n // Check if style changed\n if (!styleEquals(cellStyle, currentStyle)) {\n // Reset if going back to default, otherwise emit new style\n if (styleEquals(cellStyle, DEFAULT_STYLE)) {\n result += '\\x1b[0m';\n } else {\n // If we had styling before, reset first for clean transition\n if (!styleEquals(currentStyle, DEFAULT_STYLE)) {\n result += '\\x1b[0m';\n }\n result += buildAnsiCode(cellStyle);\n }\n currentStyle = cellStyle;\n }\n\n result += char || ' ';\n if (char) _hasContent = true;\n }\n\n // Reset at end of line if we had styling\n if (!styleEquals(currentStyle, DEFAULT_STYLE)) {\n result += '\\x1b[0m';\n }\n\n // Trim leading whitespace - tools like ncu/npm use cursor positioning\n // which creates lines with leading spaces when interpreted by xterm\n lines.push(result.trimStart());\n }\n\n // Trim trailing empty lines\n while (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n return lines;\n }\n\n /**\n * Get the number of rendered lines.\n */\n get lineCount(): number {\n return this.getLines().length;\n }\n\n /**\n * Clean up terminal resources.\n */\n dispose(): void {\n this.terminal.dispose();\n }\n}\n"],"names":["TerminalBuffer","Terminal","xterm","default","COLOR_MODE_DEFAULT","COLOR_MODE_16","COLOR_MODE_256","COLOR_MODE_RGB","DEFAULT_STYLE","fg","fgMode","bg","bgMode","bold","dim","italic","underline","inverse","strikethrough","styleEquals","a","b","buildAnsiCode","style","codes","push","r","g","length","join","cols","scrollback","terminal","rows","allowProposedApi","write","data","str","toString","resize","getLines","buffer","active","lines","i","bufferLine","getLine","result","currentStyle","_hasContent","lastContentIndex","j","cell","getCell","getChars","char","cellStyle","getFgColor","getFgColorMode","getBgColor","getBgColorMode","isBold","isDim","isItalic","isUnderline","isInverse","isStrikethrough","trimStart","pop","dispose","lineCount"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/TerminalBuffer.ts"],"sourcesContent":["import { StreamingTerminal } from 'terminal-model';\n\n/**\n * Wrapper around terminal-model's StreamingTerminal that provides a virtual terminal buffer.\n * Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce\n * the actual rendered output rather than raw intermediate states.\n *\n * This implementation preserves whitespace and blank lines by NOT calling trimStart(),\n * which was the bug in the previous xterm-based implementation.\n */\nexport class TerminalBuffer {\n private terminal: StreamingTerminal;\n private allLines: string[] = [];\n\n constructor(_cols: number, _scrollback = 10000) {\n // terminal-model doesn't enforce column width during parsing\n // It preserves all content as-is\n this.terminal = new StreamingTerminal();\n\n // Listen for completed lines (when \\n is encountered)\n this.terminal.setLineReadyCallback(() => {\n const line = this.terminal.renderLine();\n this.terminal.reset();\n this.allLines.push(line);\n });\n }\n\n /**\n * Write raw data to the terminal buffer.\n * The terminal interprets all ANSI sequences automatically.\n */\n write(data: string | Buffer): void {\n this.terminal.write(data);\n }\n\n /**\n * Resize the terminal width.\n * terminal-model doesn't use column constraints, so this is a no-op for compatibility.\n */\n resize(_cols: number): void {\n // No-op - terminal-model doesn't enforce column width\n }\n\n /**\n * Extract the rendered lines from the terminal buffer.\n * This returns the actual visible content after all ANSI sequences\n * have been processed, with color codes preserved.\n *\n * CRITICAL: Unlike the xterm implementation, we do NOT call trimStart(),\n * which preserves legitimate indentation and blank lines.\n */\n getLines(): string[] {\n // Flush any pending content (incomplete line without \\n)\n if (this.terminal.hasContent()) {\n const line = this.terminal.renderLine();\n this.terminal.reset();\n this.allLines.push(line);\n }\n\n // Return copy of all lines WITHOUT trimStart() - preserves whitespace\n const lines = [...this.allLines];\n\n // Trim trailing empty lines only\n while (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n return lines;\n }\n\n /**\n * Get the number of rendered lines.\n */\n get lineCount(): number {\n return this.getLines().length;\n }\n\n /**\n * Clean up terminal resources.\n */\n dispose(): void {\n this.terminal.dispose();\n this.allLines = [];\n }\n}\n"],"names":["TerminalBuffer","_cols","_scrollback","allLines","terminal","StreamingTerminal","setLineReadyCallback","line","renderLine","reset","push","write","data","resize","getLines","hasContent","lines","length","pop","dispose","lineCount"],"mappings":";;;;+BAUaA;;;eAAAA;;;6BAVqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAU3B,IAAA,AAAMA,+BAAN;;aAAMA,eAICC,KAAa;;YAAEC,cAAAA,iEAAc;gCAJ9BF;aAEHG,WAAqB,EAAE;QAG7B,6DAA6D;QAC7D,iCAAiC;QACjC,IAAI,CAACC,QAAQ,GAAG,IAAIC,gCAAiB;QAErC,sDAAsD;QACtD,IAAI,CAACD,QAAQ,CAACE,oBAAoB,CAAC;YACjC,IAAMC,OAAO,MAAKH,QAAQ,CAACI,UAAU;YACrC,MAAKJ,QAAQ,CAACK,KAAK;YACnB,MAAKN,QAAQ,CAACO,IAAI,CAACH;QACrB;;iBAdSP;IAiBX;;;GAGC,GACDW,OAAAA,KAEC,GAFDA,SAAAA,MAAMC,IAAqB;QACzB,IAAI,CAACR,QAAQ,CAACO,KAAK,CAACC;IACtB;IAEA;;;GAGC,GACDC,OAAAA,MAEC,GAFDA,SAAAA,OAAOZ,KAAa;IAClB,sDAAsD;IACxD;IAEA;;;;;;;GAOC,GACDa,OAAAA,QAiBC,GAjBDA,SAAAA;QACE,yDAAyD;QACzD,IAAI,IAAI,CAACV,QAAQ,CAACW,UAAU,IAAI;YAC9B,IAAMR,OAAO,IAAI,CAACH,QAAQ,CAACI,UAAU;YACrC,IAAI,CAACJ,QAAQ,CAACK,KAAK;YACnB,IAAI,CAACN,QAAQ,CAACO,IAAI,CAACH;QACrB;QAEA,sEAAsE;QACtE,IAAMS,QAAS,qBAAG,IAAI,CAACb,QAAQ;QAE/B,iCAAiC;QACjC,MAAOa,MAAMC,MAAM,GAAG,KAAKD,KAAK,CAACA,MAAMC,MAAM,GAAG,EAAE,KAAK,GAAI;YACzDD,MAAME,GAAG;QACX;QAEA,OAAOF;IACT;IASA;;GAEC,GACDG,OAAAA,OAGC,GAHDA,SAAAA;QACE,IAAI,CAACf,QAAQ,CAACe,OAAO;QACrB,IAAI,CAAChB,QAAQ,GAAG,EAAE;IACpB;kBAzEWH;;YA+DPoB,KAAAA;iBAAJ,AAHA;;GAEC,GACD;gBACE,OAAO,IAAI,CAACN,QAAQ,GAAGG,MAAM;YAC/B;;;WAjEWjB"}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrapper around terminal-model's StreamingTerminal that provides a virtual terminal buffer.
|
|
3
|
+
* Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce
|
|
4
|
+
* the actual rendered output rather than raw intermediate states.
|
|
5
|
+
*
|
|
6
|
+
* This implementation preserves whitespace and blank lines by NOT calling trimStart(),
|
|
7
|
+
* which was the bug in the previous xterm-based implementation.
|
|
8
|
+
*/
|
|
1
9
|
export declare class TerminalBuffer {
|
|
2
10
|
private terminal;
|
|
3
|
-
|
|
11
|
+
private allLines;
|
|
12
|
+
constructor(_cols: number, _scrollback?: number);
|
|
4
13
|
/**
|
|
5
14
|
* Write raw data to the terminal buffer.
|
|
6
15
|
* The terminal interprets all ANSI sequences automatically.
|
|
@@ -8,12 +17,16 @@ export declare class TerminalBuffer {
|
|
|
8
17
|
write(data: string | Buffer): void;
|
|
9
18
|
/**
|
|
10
19
|
* Resize the terminal width.
|
|
20
|
+
* terminal-model doesn't use column constraints, so this is a no-op for compatibility.
|
|
11
21
|
*/
|
|
12
|
-
resize(
|
|
22
|
+
resize(_cols: number): void;
|
|
13
23
|
/**
|
|
14
24
|
* Extract the rendered lines from the terminal buffer.
|
|
15
25
|
* This returns the actual visible content after all ANSI sequences
|
|
16
26
|
* have been processed, with color codes preserved.
|
|
27
|
+
*
|
|
28
|
+
* CRITICAL: Unlike the xterm implementation, we do NOT call trimStart(),
|
|
29
|
+
* which preserves legitimate indentation and blank lines.
|
|
17
30
|
*/
|
|
18
31
|
getLines(): string[];
|
|
19
32
|
/**
|
|
@@ -1,151 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const DEFAULT_STYLE = {
|
|
11
|
-
fg: -1,
|
|
12
|
-
fgMode: COLOR_MODE_DEFAULT,
|
|
13
|
-
bg: -1,
|
|
14
|
-
bgMode: COLOR_MODE_DEFAULT,
|
|
15
|
-
bold: false,
|
|
16
|
-
dim: false,
|
|
17
|
-
italic: false,
|
|
18
|
-
underline: false,
|
|
19
|
-
inverse: false,
|
|
20
|
-
strikethrough: false
|
|
21
|
-
};
|
|
22
|
-
function styleEquals(a, b) {
|
|
23
|
-
return a.fg === b.fg && a.fgMode === b.fgMode && a.bg === b.bg && a.bgMode === b.bgMode && a.bold === b.bold && a.dim === b.dim && a.italic === b.italic && a.underline === b.underline && a.inverse === b.inverse && a.strikethrough === b.strikethrough;
|
|
24
|
-
}
|
|
25
|
-
function buildAnsiCode(style) {
|
|
26
|
-
const codes = [];
|
|
27
|
-
// Attributes
|
|
28
|
-
if (style.bold) codes.push(1);
|
|
29
|
-
if (style.dim) codes.push(2);
|
|
30
|
-
if (style.italic) codes.push(3);
|
|
31
|
-
if (style.underline) codes.push(4);
|
|
32
|
-
if (style.inverse) codes.push(7);
|
|
33
|
-
if (style.strikethrough) codes.push(9);
|
|
34
|
-
// Foreground color
|
|
35
|
-
if (style.fgMode === COLOR_MODE_16) {
|
|
36
|
-
// 16-color palette: 0-7 are 30-37, 8-15 are 90-97
|
|
37
|
-
if (style.fg < 8) {
|
|
38
|
-
codes.push(30 + style.fg);
|
|
39
|
-
} else {
|
|
40
|
-
codes.push(90 + (style.fg - 8));
|
|
41
|
-
}
|
|
42
|
-
} else if (style.fgMode === COLOR_MODE_256) {
|
|
43
|
-
codes.push(38, 5, style.fg);
|
|
44
|
-
} else if (style.fgMode === COLOR_MODE_RGB) {
|
|
45
|
-
// RGB is encoded in the color value
|
|
46
|
-
const r = style.fg >> 16 & 0xff;
|
|
47
|
-
const g = style.fg >> 8 & 0xff;
|
|
48
|
-
const b = style.fg & 0xff;
|
|
49
|
-
codes.push(38, 2, r, g, b);
|
|
50
|
-
}
|
|
51
|
-
// Background color
|
|
52
|
-
if (style.bgMode === COLOR_MODE_16) {
|
|
53
|
-
if (style.bg < 8) {
|
|
54
|
-
codes.push(40 + style.bg);
|
|
55
|
-
} else {
|
|
56
|
-
codes.push(100 + (style.bg - 8));
|
|
57
|
-
}
|
|
58
|
-
} else if (style.bgMode === COLOR_MODE_256) {
|
|
59
|
-
codes.push(48, 5, style.bg);
|
|
60
|
-
} else if (style.bgMode === COLOR_MODE_RGB) {
|
|
61
|
-
const r = style.bg >> 16 & 0xff;
|
|
62
|
-
const g = style.bg >> 8 & 0xff;
|
|
63
|
-
const b = style.bg & 0xff;
|
|
64
|
-
codes.push(48, 2, r, g, b);
|
|
65
|
-
}
|
|
66
|
-
if (codes.length === 0) return '';
|
|
67
|
-
return `\x1b[${codes.join(';')}m`;
|
|
68
|
-
}
|
|
69
|
-
export class TerminalBuffer {
|
|
1
|
+
import { StreamingTerminal } from 'terminal-model';
|
|
2
|
+
/**
|
|
3
|
+
* Wrapper around terminal-model's StreamingTerminal that provides a virtual terminal buffer.
|
|
4
|
+
* Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce
|
|
5
|
+
* the actual rendered output rather than raw intermediate states.
|
|
6
|
+
*
|
|
7
|
+
* This implementation preserves whitespace and blank lines by NOT calling trimStart(),
|
|
8
|
+
* which was the bug in the previous xterm-based implementation.
|
|
9
|
+
*/ export class TerminalBuffer {
|
|
70
10
|
/**
|
|
71
11
|
* Write raw data to the terminal buffer.
|
|
72
12
|
* The terminal interprets all ANSI sequences automatically.
|
|
73
13
|
*/ write(data) {
|
|
74
|
-
|
|
75
|
-
this.terminal.write(str);
|
|
14
|
+
this.terminal.write(data);
|
|
76
15
|
}
|
|
77
16
|
/**
|
|
78
17
|
* Resize the terminal width.
|
|
79
|
-
|
|
80
|
-
|
|
18
|
+
* terminal-model doesn't use column constraints, so this is a no-op for compatibility.
|
|
19
|
+
*/ resize(_cols) {
|
|
20
|
+
// No-op - terminal-model doesn't enforce column width
|
|
81
21
|
}
|
|
82
22
|
/**
|
|
83
23
|
* Extract the rendered lines from the terminal buffer.
|
|
84
24
|
* This returns the actual visible content after all ANSI sequences
|
|
85
25
|
* have been processed, with color codes preserved.
|
|
26
|
+
*
|
|
27
|
+
* CRITICAL: Unlike the xterm implementation, we do NOT call trimStart(),
|
|
28
|
+
* which preserves legitimate indentation and blank lines.
|
|
86
29
|
*/ getLines() {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
let result = '';
|
|
93
|
-
let currentStyle = {
|
|
94
|
-
...DEFAULT_STYLE
|
|
95
|
-
};
|
|
96
|
-
let _hasContent = false;
|
|
97
|
-
// First pass: find the last non-empty cell to know where content ends
|
|
98
|
-
let lastContentIndex = -1;
|
|
99
|
-
for(let j = bufferLine.length - 1; j >= 0; j--){
|
|
100
|
-
const cell = bufferLine.getCell(j);
|
|
101
|
-
if (cell && cell.getChars()) {
|
|
102
|
-
lastContentIndex = j;
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// Second pass: build the line with ANSI codes
|
|
107
|
-
for(let j = 0; j <= lastContentIndex; j++){
|
|
108
|
-
const cell = bufferLine.getCell(j);
|
|
109
|
-
if (!cell) continue;
|
|
110
|
-
const char = cell.getChars();
|
|
111
|
-
const cellStyle = {
|
|
112
|
-
fg: cell.getFgColor(),
|
|
113
|
-
fgMode: cell.getFgColorMode(),
|
|
114
|
-
bg: cell.getBgColor(),
|
|
115
|
-
bgMode: cell.getBgColorMode(),
|
|
116
|
-
bold: cell.isBold() !== 0,
|
|
117
|
-
dim: cell.isDim() !== 0,
|
|
118
|
-
italic: cell.isItalic() !== 0,
|
|
119
|
-
underline: cell.isUnderline() !== 0,
|
|
120
|
-
inverse: cell.isInverse() !== 0,
|
|
121
|
-
strikethrough: cell.isStrikethrough() !== 0
|
|
122
|
-
};
|
|
123
|
-
// Check if style changed
|
|
124
|
-
if (!styleEquals(cellStyle, currentStyle)) {
|
|
125
|
-
// Reset if going back to default, otherwise emit new style
|
|
126
|
-
if (styleEquals(cellStyle, DEFAULT_STYLE)) {
|
|
127
|
-
result += '\x1b[0m';
|
|
128
|
-
} else {
|
|
129
|
-
// If we had styling before, reset first for clean transition
|
|
130
|
-
if (!styleEquals(currentStyle, DEFAULT_STYLE)) {
|
|
131
|
-
result += '\x1b[0m';
|
|
132
|
-
}
|
|
133
|
-
result += buildAnsiCode(cellStyle);
|
|
134
|
-
}
|
|
135
|
-
currentStyle = cellStyle;
|
|
136
|
-
}
|
|
137
|
-
result += char || ' ';
|
|
138
|
-
if (char) _hasContent = true;
|
|
139
|
-
}
|
|
140
|
-
// Reset at end of line if we had styling
|
|
141
|
-
if (!styleEquals(currentStyle, DEFAULT_STYLE)) {
|
|
142
|
-
result += '\x1b[0m';
|
|
143
|
-
}
|
|
144
|
-
// Trim leading whitespace - tools like ncu/npm use cursor positioning
|
|
145
|
-
// which creates lines with leading spaces when interpreted by xterm
|
|
146
|
-
lines.push(result.trimStart());
|
|
30
|
+
// Flush any pending content (incomplete line without \n)
|
|
31
|
+
if (this.terminal.hasContent()) {
|
|
32
|
+
const line = this.terminal.renderLine();
|
|
33
|
+
this.terminal.reset();
|
|
34
|
+
this.allLines.push(line);
|
|
147
35
|
}
|
|
148
|
-
//
|
|
36
|
+
// Return copy of all lines WITHOUT trimStart() - preserves whitespace
|
|
37
|
+
const lines = [
|
|
38
|
+
...this.allLines
|
|
39
|
+
];
|
|
40
|
+
// Trim trailing empty lines only
|
|
149
41
|
while(lines.length > 0 && lines[lines.length - 1] === ''){
|
|
150
42
|
lines.pop();
|
|
151
43
|
}
|
|
@@ -160,13 +52,18 @@ export class TerminalBuffer {
|
|
|
160
52
|
* Clean up terminal resources.
|
|
161
53
|
*/ dispose() {
|
|
162
54
|
this.terminal.dispose();
|
|
55
|
+
this.allLines = [];
|
|
163
56
|
}
|
|
164
|
-
constructor(
|
|
165
|
-
this.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
57
|
+
constructor(_cols, _scrollback = 10000){
|
|
58
|
+
this.allLines = [];
|
|
59
|
+
// terminal-model doesn't enforce column width during parsing
|
|
60
|
+
// It preserves all content as-is
|
|
61
|
+
this.terminal = new StreamingTerminal();
|
|
62
|
+
// Listen for completed lines (when \n is encountered)
|
|
63
|
+
this.terminal.setLineReadyCallback(()=>{
|
|
64
|
+
const line = this.terminal.renderLine();
|
|
65
|
+
this.terminal.reset();
|
|
66
|
+
this.allLines.push(line);
|
|
170
67
|
});
|
|
171
68
|
}
|
|
172
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/TerminalBuffer.ts"],"sourcesContent":["import * as xterm from '@xterm/headless';\n\n// Handle both ESM and CJS module formats\nconst Terminal = (xterm as { Terminal: typeof xterm.Terminal; default?: { Terminal: typeof xterm.Terminal } }).Terminal || (xterm as { default?: { Terminal: typeof xterm.Terminal } }).default?.Terminal;\n\n// ANSI color mode constants from xterm.js\nconst COLOR_MODE_DEFAULT = 0;\nconst COLOR_MODE_16 = 16777216; // 0x1000000 - 16 color palette (0-15)\nconst COLOR_MODE_256 = 33554432; // 0x2000000 - 256 color palette\nconst COLOR_MODE_RGB = 50331648; // 0x3000000 - 24-bit RGB\n\n/**\n * Wrapper around @xterm/headless Terminal that provides a virtual terminal buffer.\n * Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce\n * the actual rendered output rather than raw intermediate states.\n */\n// Cell attribute state for tracking changes\ninterface CellStyle {\n fg: number;\n fgMode: number;\n bg: number;\n bgMode: number;\n bold: boolean;\n dim: boolean;\n italic: boolean;\n underline: boolean;\n inverse: boolean;\n strikethrough: boolean;\n}\n\nconst DEFAULT_STYLE: CellStyle = {\n fg: -1,\n fgMode: COLOR_MODE_DEFAULT,\n bg: -1,\n bgMode: COLOR_MODE_DEFAULT,\n bold: false,\n dim: false,\n italic: false,\n underline: false,\n inverse: false,\n strikethrough: false,\n};\n\nfunction styleEquals(a: CellStyle, b: CellStyle): boolean {\n return a.fg === b.fg && a.fgMode === b.fgMode && a.bg === b.bg && a.bgMode === b.bgMode && a.bold === b.bold && a.dim === b.dim && a.italic === b.italic && a.underline === b.underline && a.inverse === b.inverse && a.strikethrough === b.strikethrough;\n}\n\nfunction buildAnsiCode(style: CellStyle): string {\n const codes: number[] = [];\n\n // Attributes\n if (style.bold) codes.push(1);\n if (style.dim) codes.push(2);\n if (style.italic) codes.push(3);\n if (style.underline) codes.push(4);\n if (style.inverse) codes.push(7);\n if (style.strikethrough) codes.push(9);\n\n // Foreground color\n if (style.fgMode === COLOR_MODE_16) {\n // 16-color palette: 0-7 are 30-37, 8-15 are 90-97\n if (style.fg < 8) {\n codes.push(30 + style.fg);\n } else {\n codes.push(90 + (style.fg - 8));\n }\n } else if (style.fgMode === COLOR_MODE_256) {\n codes.push(38, 5, style.fg);\n } else if (style.fgMode === COLOR_MODE_RGB) {\n // RGB is encoded in the color value\n const r = (style.fg >> 16) & 0xff;\n const g = (style.fg >> 8) & 0xff;\n const b = style.fg & 0xff;\n codes.push(38, 2, r, g, b);\n }\n\n // Background color\n if (style.bgMode === COLOR_MODE_16) {\n if (style.bg < 8) {\n codes.push(40 + style.bg);\n } else {\n codes.push(100 + (style.bg - 8));\n }\n } else if (style.bgMode === COLOR_MODE_256) {\n codes.push(48, 5, style.bg);\n } else if (style.bgMode === COLOR_MODE_RGB) {\n const r = (style.bg >> 16) & 0xff;\n const g = (style.bg >> 8) & 0xff;\n const b = style.bg & 0xff;\n codes.push(48, 2, r, g, b);\n }\n\n if (codes.length === 0) return '';\n return `\\x1b[${codes.join(';')}m`;\n}\n\nexport class TerminalBuffer {\n private terminal: InstanceType<typeof Terminal>;\n\n constructor(cols: number, scrollback = 10000) {\n this.terminal = new Terminal({\n cols,\n rows: 50, // Visible rows (doesn't matter much for headless)\n scrollback,\n allowProposedApi: true,\n });\n }\n\n /**\n * Write raw data to the terminal buffer.\n * The terminal interprets all ANSI sequences automatically.\n */\n write(data: string | Buffer): void {\n const str = typeof data === 'string' ? data : data.toString('utf8');\n this.terminal.write(str);\n }\n\n /**\n * Resize the terminal width.\n */\n resize(cols: number): void {\n this.terminal.resize(cols, this.terminal.rows);\n }\n\n /**\n * Extract the rendered lines from the terminal buffer.\n * This returns the actual visible content after all ANSI sequences\n * have been processed, with color codes preserved.\n */\n getLines(): string[] {\n const buffer = this.terminal.buffer.active;\n const lines: string[] = [];\n\n for (let i = 0; i < buffer.length; i++) {\n const bufferLine = buffer.getLine(i);\n if (!bufferLine) continue;\n\n let result = '';\n let currentStyle: CellStyle = { ...DEFAULT_STYLE };\n let _hasContent = false;\n\n // First pass: find the last non-empty cell to know where content ends\n let lastContentIndex = -1;\n for (let j = bufferLine.length - 1; j >= 0; j--) {\n const cell = bufferLine.getCell(j);\n if (cell && cell.getChars()) {\n lastContentIndex = j;\n break;\n }\n }\n\n // Second pass: build the line with ANSI codes\n for (let j = 0; j <= lastContentIndex; j++) {\n const cell = bufferLine.getCell(j);\n if (!cell) continue;\n\n const char = cell.getChars();\n const cellStyle: CellStyle = {\n fg: cell.getFgColor(),\n fgMode: cell.getFgColorMode(),\n bg: cell.getBgColor(),\n bgMode: cell.getBgColorMode(),\n bold: cell.isBold() !== 0,\n dim: cell.isDim() !== 0,\n italic: cell.isItalic() !== 0,\n underline: cell.isUnderline() !== 0,\n inverse: cell.isInverse() !== 0,\n strikethrough: cell.isStrikethrough() !== 0,\n };\n\n // Check if style changed\n if (!styleEquals(cellStyle, currentStyle)) {\n // Reset if going back to default, otherwise emit new style\n if (styleEquals(cellStyle, DEFAULT_STYLE)) {\n result += '\\x1b[0m';\n } else {\n // If we had styling before, reset first for clean transition\n if (!styleEquals(currentStyle, DEFAULT_STYLE)) {\n result += '\\x1b[0m';\n }\n result += buildAnsiCode(cellStyle);\n }\n currentStyle = cellStyle;\n }\n\n result += char || ' ';\n if (char) _hasContent = true;\n }\n\n // Reset at end of line if we had styling\n if (!styleEquals(currentStyle, DEFAULT_STYLE)) {\n result += '\\x1b[0m';\n }\n\n // Trim leading whitespace - tools like ncu/npm use cursor positioning\n // which creates lines with leading spaces when interpreted by xterm\n lines.push(result.trimStart());\n }\n\n // Trim trailing empty lines\n while (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n return lines;\n }\n\n /**\n * Get the number of rendered lines.\n */\n get lineCount(): number {\n return this.getLines().length;\n }\n\n /**\n * Clean up terminal resources.\n */\n dispose(): void {\n this.terminal.dispose();\n }\n}\n"],"names":["xterm","Terminal","default","COLOR_MODE_DEFAULT","COLOR_MODE_16","COLOR_MODE_256","COLOR_MODE_RGB","DEFAULT_STYLE","fg","fgMode","bg","bgMode","bold","dim","italic","underline","inverse","strikethrough","styleEquals","a","b","buildAnsiCode","style","codes","push","r","g","length","join","TerminalBuffer","write","data","str","toString","terminal","resize","cols","rows","getLines","buffer","active","lines","i","bufferLine","getLine","result","currentStyle","_hasContent","lastContentIndex","j","cell","getCell","getChars","char","cellStyle","getFgColor","getFgColorMode","getBgColor","getBgColorMode","isBold","isDim","isItalic","isUnderline","isInverse","isStrikethrough","trimStart","pop","lineCount","dispose","scrollback","allowProposedApi"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/TerminalBuffer.ts"],"sourcesContent":["import { StreamingTerminal } from 'terminal-model';\n\n/**\n * Wrapper around terminal-model's StreamingTerminal that provides a virtual terminal buffer.\n * Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce\n * the actual rendered output rather than raw intermediate states.\n *\n * This implementation preserves whitespace and blank lines by NOT calling trimStart(),\n * which was the bug in the previous xterm-based implementation.\n */\nexport class TerminalBuffer {\n private terminal: StreamingTerminal;\n private allLines: string[] = [];\n\n constructor(_cols: number, _scrollback = 10000) {\n // terminal-model doesn't enforce column width during parsing\n // It preserves all content as-is\n this.terminal = new StreamingTerminal();\n\n // Listen for completed lines (when \\n is encountered)\n this.terminal.setLineReadyCallback(() => {\n const line = this.terminal.renderLine();\n this.terminal.reset();\n this.allLines.push(line);\n });\n }\n\n /**\n * Write raw data to the terminal buffer.\n * The terminal interprets all ANSI sequences automatically.\n */\n write(data: string | Buffer): void {\n this.terminal.write(data);\n }\n\n /**\n * Resize the terminal width.\n * terminal-model doesn't use column constraints, so this is a no-op for compatibility.\n */\n resize(_cols: number): void {\n // No-op - terminal-model doesn't enforce column width\n }\n\n /**\n * Extract the rendered lines from the terminal buffer.\n * This returns the actual visible content after all ANSI sequences\n * have been processed, with color codes preserved.\n *\n * CRITICAL: Unlike the xterm implementation, we do NOT call trimStart(),\n * which preserves legitimate indentation and blank lines.\n */\n getLines(): string[] {\n // Flush any pending content (incomplete line without \\n)\n if (this.terminal.hasContent()) {\n const line = this.terminal.renderLine();\n this.terminal.reset();\n this.allLines.push(line);\n }\n\n // Return copy of all lines WITHOUT trimStart() - preserves whitespace\n const lines = [...this.allLines];\n\n // Trim trailing empty lines only\n while (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n return lines;\n }\n\n /**\n * Get the number of rendered lines.\n */\n get lineCount(): number {\n return this.getLines().length;\n }\n\n /**\n * Clean up terminal resources.\n */\n dispose(): void {\n this.terminal.dispose();\n this.allLines = [];\n }\n}\n"],"names":["StreamingTerminal","TerminalBuffer","write","data","terminal","resize","_cols","getLines","hasContent","line","renderLine","reset","allLines","push","lines","length","pop","lineCount","dispose","_scrollback","setLineReadyCallback"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,iBAAiB;AAEnD;;;;;;;CAOC,GACD,OAAO,MAAMC;IAiBX;;;GAGC,GACDC,MAAMC,IAAqB,EAAQ;QACjC,IAAI,CAACC,QAAQ,CAACF,KAAK,CAACC;IACtB;IAEA;;;GAGC,GACDE,OAAOC,KAAa,EAAQ;IAC1B,sDAAsD;IACxD;IAEA;;;;;;;GAOC,GACDC,WAAqB;QACnB,yDAAyD;QACzD,IAAI,IAAI,CAACH,QAAQ,CAACI,UAAU,IAAI;YAC9B,MAAMC,OAAO,IAAI,CAACL,QAAQ,CAACM,UAAU;YACrC,IAAI,CAACN,QAAQ,CAACO,KAAK;YACnB,IAAI,CAACC,QAAQ,CAACC,IAAI,CAACJ;QACrB;QAEA,sEAAsE;QACtE,MAAMK,QAAQ;eAAI,IAAI,CAACF,QAAQ;SAAC;QAEhC,iCAAiC;QACjC,MAAOE,MAAMC,MAAM,GAAG,KAAKD,KAAK,CAACA,MAAMC,MAAM,GAAG,EAAE,KAAK,GAAI;YACzDD,MAAME,GAAG;QACX;QAEA,OAAOF;IACT;IAEA;;GAEC,GACD,IAAIG,YAAoB;QACtB,OAAO,IAAI,CAACV,QAAQ,GAAGQ,MAAM;IAC/B;IAEA;;GAEC,GACDG,UAAgB;QACd,IAAI,CAACd,QAAQ,CAACc,OAAO;QACrB,IAAI,CAACN,QAAQ,GAAG,EAAE;IACpB;IArEA,YAAYN,KAAa,EAAEa,cAAc,KAAK,CAAE;aAFxCP,WAAqB,EAAE;QAG7B,6DAA6D;QAC7D,iCAAiC;QACjC,IAAI,CAACR,QAAQ,GAAG,IAAIJ;QAEpB,sDAAsD;QACtD,IAAI,CAACI,QAAQ,CAACgB,oBAAoB,CAAC;YACjC,MAAMX,OAAO,IAAI,CAACL,QAAQ,CAACM,UAAU;YACrC,IAAI,CAACN,QAAQ,CAACO,KAAK;YACnB,IAAI,CAACC,QAAQ,CAACC,IAAI,CAACJ;QACrB;IACF;AA2DF"}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrapper around terminal-model's StreamingTerminal that provides a virtual terminal buffer.
|
|
3
|
+
* Interprets ANSI escape sequences (cursor movement, line clearing, etc.) to produce
|
|
4
|
+
* the actual rendered output rather than raw intermediate states.
|
|
5
|
+
*
|
|
6
|
+
* This implementation preserves whitespace and blank lines by NOT calling trimStart(),
|
|
7
|
+
* which was the bug in the previous xterm-based implementation.
|
|
8
|
+
*/
|
|
1
9
|
export declare class TerminalBuffer {
|
|
2
10
|
private terminal;
|
|
3
|
-
|
|
11
|
+
private allLines;
|
|
12
|
+
constructor(_cols: number, _scrollback?: number);
|
|
4
13
|
/**
|
|
5
14
|
* Write raw data to the terminal buffer.
|
|
6
15
|
* The terminal interprets all ANSI sequences automatically.
|
|
@@ -8,12 +17,16 @@ export declare class TerminalBuffer {
|
|
|
8
17
|
write(data: string | Buffer): void;
|
|
9
18
|
/**
|
|
10
19
|
* Resize the terminal width.
|
|
20
|
+
* terminal-model doesn't use column constraints, so this is a no-op for compatibility.
|
|
11
21
|
*/
|
|
12
|
-
resize(
|
|
22
|
+
resize(_cols: number): void;
|
|
13
23
|
/**
|
|
14
24
|
* Extract the rendered lines from the terminal buffer.
|
|
15
25
|
* This returns the actual visible content after all ANSI sequences
|
|
16
26
|
* have been processed, with color codes preserved.
|
|
27
|
+
*
|
|
28
|
+
* CRITICAL: Unlike the xterm implementation, we do NOT call trimStart(),
|
|
29
|
+
* which preserves legitimate indentation and blank lines.
|
|
17
30
|
*/
|
|
18
31
|
getLines(): string[];
|
|
19
32
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spawn-term",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "Formats spawn with for terminal grouping",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"spawn",
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
"version": "tsds version"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@xterm/headless": "^6.0.0",
|
|
46
45
|
"cross-spawn-cb": "^3.0.0",
|
|
47
46
|
"install-module-linked": "^1.3.16",
|
|
48
47
|
"on-one": "^1.0.0",
|
|
49
|
-
"queue-cb": "^1.0.0"
|
|
48
|
+
"queue-cb": "^1.0.0",
|
|
49
|
+
"terminal-model": "^0.1.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/mocha": "*",
|