spawn-term 3.5.4 → 3.5.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.
- package/dist/cjs/components/CompactProcessLine.js +19 -5
- package/dist/cjs/components/CompactProcessLine.js.map +1 -1
- package/dist/cjs/components/ExpandedOutput.js +9 -1
- package/dist/cjs/components/ExpandedOutput.js.map +1 -1
- package/dist/cjs/lib/AnsiText.js +214 -0
- package/dist/cjs/lib/AnsiText.js.map +1 -0
- package/dist/cjs/lib/clipText.js +1 -1
- package/dist/cjs/lib/clipText.js.map +1 -1
- package/dist/cjs/src/lib/AnsiText.d.ts +9 -0
- package/dist/esm/components/CompactProcessLine.js +19 -5
- package/dist/esm/components/CompactProcessLine.js.map +1 -1
- package/dist/esm/components/ExpandedOutput.js +4 -1
- package/dist/esm/components/ExpandedOutput.js.map +1 -1
- package/dist/esm/lib/AnsiText.js +121 -0
- package/dist/esm/lib/AnsiText.js.map +1 -0
- package/dist/esm/lib/clipText.js +1 -1
- package/dist/esm/lib/clipText.js.map +1 -1
- package/dist/esm/src/lib/AnsiText.d.ts +9 -0
- package/package.json +1 -1
|
@@ -12,6 +12,7 @@ var _jsxruntime = require("react/jsx-runtime");
|
|
|
12
12
|
var _ink = require("ink");
|
|
13
13
|
var _react = require("react");
|
|
14
14
|
var _constantsts = require("../constants.js");
|
|
15
|
+
var _ansiRegexts = /*#__PURE__*/ _interop_require_default(require("../lib/ansiRegex.js"));
|
|
15
16
|
var _clipTextts = require("../lib/clipText.js");
|
|
16
17
|
var _figurests = /*#__PURE__*/ _interop_require_default(require("../lib/figures.js"));
|
|
17
18
|
var _formatts = require("../lib/format.js");
|
|
@@ -51,6 +52,21 @@ function _object_spread(target) {
|
|
|
51
52
|
}
|
|
52
53
|
return target;
|
|
53
54
|
}
|
|
55
|
+
var ansi = (0, _ansiRegexts.default)();
|
|
56
|
+
/**
|
|
57
|
+
* Strip ANSI escape codes from a string.
|
|
58
|
+
*/ function stripAnsi(str) {
|
|
59
|
+
return str.replace(ansi, '');
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Simple truncation for plain text (no ANSI codes).
|
|
63
|
+
* Adds ellipsis if truncated.
|
|
64
|
+
*/ function truncate(str, maxWidth) {
|
|
65
|
+
if (maxWidth <= 0) return '';
|
|
66
|
+
if (maxWidth === 1) return '…';
|
|
67
|
+
if (str.length <= maxWidth) return str;
|
|
68
|
+
return "".concat(str.slice(0, maxWidth - 1), "…");
|
|
69
|
+
}
|
|
54
70
|
function getLastOutputLine(lines) {
|
|
55
71
|
for(var i = lines.length - 1; i >= 0; i--){
|
|
56
72
|
if (lines[i].text.length > 0) {
|
|
@@ -86,12 +102,13 @@ var _default = /*#__PURE__*/ (0, _react.memo)(function CompactProcessLine(param)
|
|
|
86
102
|
var statusText = (0, _react.useMemo)(function() {
|
|
87
103
|
if (state === 'running') {
|
|
88
104
|
var lastLine = getLastOutputLine(lines);
|
|
89
|
-
|
|
105
|
+
var stripped = lastLine ? stripAnsi(lastLine) : '';
|
|
106
|
+
return stripped ? truncate(stripped, statusWidth) : '';
|
|
90
107
|
}
|
|
91
108
|
if (state === 'error') {
|
|
92
109
|
var errorCount = getErrorCount(lines);
|
|
93
110
|
var text = errorCount > 0 ? "".concat(errorCount, " error").concat(errorCount > 1 ? 's' : '') : 'failed';
|
|
94
|
-
return (
|
|
111
|
+
return truncate(text, statusWidth);
|
|
95
112
|
}
|
|
96
113
|
return ''; // success - no status text
|
|
97
114
|
}, [
|
|
@@ -118,8 +135,6 @@ var _default = /*#__PURE__*/ (0, _react.memo)(function CompactProcessLine(param)
|
|
|
118
135
|
}, [
|
|
119
136
|
state
|
|
120
137
|
]);
|
|
121
|
-
// Status text color
|
|
122
|
-
var statusColor = state === 'error' ? 'red' : 'gray';
|
|
123
138
|
return /*#__PURE__*/ (0, _jsxruntime.jsxs)(_ink.Box, {
|
|
124
139
|
width: terminalWidth,
|
|
125
140
|
children: [
|
|
@@ -141,7 +156,6 @@ var _default = /*#__PURE__*/ (0, _react.memo)(function CompactProcessLine(param)
|
|
|
141
156
|
statusWidth > 0 && statusText && /*#__PURE__*/ (0, _jsxruntime.jsx)(_ink.Box, {
|
|
142
157
|
width: statusWidth + gap,
|
|
143
158
|
children: /*#__PURE__*/ (0, _jsxruntime.jsxs)(_ink.Text, {
|
|
144
|
-
color: statusColor,
|
|
145
159
|
children: [
|
|
146
160
|
" ",
|
|
147
161
|
statusText
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/CompactProcessLine.tsx"],"sourcesContent":["import { Box, Text, useStdout } from 'ink';\nimport { memo, useMemo } from 'react';\nimport { SPINNER } from '../constants.ts';\nimport { clipText } from '../lib/clipText.ts';\nimport figures from '../lib/figures.ts';\nimport { calculateColumnWidth } from '../lib/format.ts';\nimport { useStore } from '../state/StoreContext.ts';\nimport type { ChildProcess, Line } from '../types.ts';\nimport { LineType } from '../types.ts';\nimport Spinner from './Spinner.ts';\n\ntype Props = {\n item: ChildProcess;\n isSelected?: boolean;\n};\n\nfunction getLastOutputLine(lines: Line[]): string {\n for (let i = lines.length - 1; i >= 0; i--) {\n if (lines[i].text.length > 0) {\n return lines[i].text;\n }\n }\n return '';\n}\n\nfunction getErrorCount(lines: Line[]): number {\n return lines.filter((line) => line.type === LineType.stderr).length;\n}\n\nexport default memo(function CompactProcessLine({ item, isSelected = false }: Props) {\n const store = useStore();\n const { stdout } = useStdout();\n const terminalWidth = stdout?.columns || 120;\n\n const { group, title, state, lines } = item;\n const selectionIndicator = isSelected ? figures.pointer : ' ';\n\n // Display name: prefer group, fall back to title\n const displayName = group || title;\n\n // Calculate widths - use dynamic column width based on longest name\n const selectionWidth = 1; // selection indicator\n const iconWidth = 2; // icon + space\n const maxGroupLength = store.getMaxGroupLength();\n const nameColumnWidth = calculateColumnWidth('max', terminalWidth, maxGroupLength);\n const gap = 1; // space between name and status\n const statusWidth = Math.max(0, terminalWidth - selectionWidth - iconWidth - nameColumnWidth - gap);\n\n // Clip name to column width and pad\n const clippedName = clipText(displayName, nameColumnWidth).padEnd(nameColumnWidth);\n\n // Status text based on state - clip to available width\n const statusText = useMemo(() => {\n if (state === 'running') {\n const lastLine = getLastOutputLine(lines);\n
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/CompactProcessLine.tsx"],"sourcesContent":["import { Box, Text, useStdout } from 'ink';\nimport { memo, useMemo } from 'react';\nimport { SPINNER } from '../constants.ts';\nimport ansiRegex from '../lib/ansiRegex.ts';\nimport { clipText } from '../lib/clipText.ts';\nimport figures from '../lib/figures.ts';\nimport { calculateColumnWidth } from '../lib/format.ts';\nimport { useStore } from '../state/StoreContext.ts';\nimport type { ChildProcess, Line } from '../types.ts';\nimport { LineType } from '../types.ts';\nimport Spinner from './Spinner.ts';\n\nconst ansi = ansiRegex();\n\n/**\n * Strip ANSI escape codes from a string.\n */\nfunction stripAnsi(str: string): string {\n return str.replace(ansi, '');\n}\n\n/**\n * Simple truncation for plain text (no ANSI codes).\n * Adds ellipsis if truncated.\n */\nfunction truncate(str: string, maxWidth: number): string {\n if (maxWidth <= 0) return '';\n if (maxWidth === 1) return '…';\n if (str.length <= maxWidth) return str;\n return `${str.slice(0, maxWidth - 1)}…`;\n}\n\ntype Props = {\n item: ChildProcess;\n isSelected?: boolean;\n};\n\nfunction getLastOutputLine(lines: Line[]): string {\n for (let i = lines.length - 1; i >= 0; i--) {\n if (lines[i].text.length > 0) {\n return lines[i].text;\n }\n }\n return '';\n}\n\nfunction getErrorCount(lines: Line[]): number {\n return lines.filter((line) => line.type === LineType.stderr).length;\n}\n\nexport default memo(function CompactProcessLine({ item, isSelected = false }: Props) {\n const store = useStore();\n const { stdout } = useStdout();\n const terminalWidth = stdout?.columns || 120;\n\n const { group, title, state, lines } = item;\n const selectionIndicator = isSelected ? figures.pointer : ' ';\n\n // Display name: prefer group, fall back to title\n const displayName = group || title;\n\n // Calculate widths - use dynamic column width based on longest name\n const selectionWidth = 1; // selection indicator\n const iconWidth = 2; // icon + space\n const maxGroupLength = store.getMaxGroupLength();\n const nameColumnWidth = calculateColumnWidth('max', terminalWidth, maxGroupLength);\n const gap = 1; // space between name and status\n const statusWidth = Math.max(0, terminalWidth - selectionWidth - iconWidth - nameColumnWidth - gap);\n\n // Clip name to column width and pad\n const clippedName = clipText(displayName, nameColumnWidth).padEnd(nameColumnWidth);\n\n // Status text based on state - clip to available width\n const statusText = useMemo(() => {\n if (state === 'running') {\n const lastLine = getLastOutputLine(lines);\n const stripped = lastLine ? stripAnsi(lastLine) : '';\n return stripped ? truncate(stripped, statusWidth) : '';\n }\n if (state === 'error') {\n const errorCount = getErrorCount(lines);\n const text = errorCount > 0 ? `${errorCount} error${errorCount > 1 ? 's' : ''}` : 'failed';\n return truncate(text, statusWidth);\n }\n return ''; // success - no status text\n }, [state, lines, statusWidth]);\n\n // Icon based on state\n const icon = useMemo(() => {\n switch (state) {\n case 'running':\n return <Spinner {...SPINNER} />;\n case 'success':\n return <Text color=\"green\">{figures.tick}</Text>;\n case 'error':\n return <Text color=\"red\">{figures.cross}</Text>;\n }\n }, [state]);\n\n return (\n <Box width={terminalWidth}>\n <Text color={isSelected ? 'cyan' : undefined}>{selectionIndicator}</Text>\n <Box width={iconWidth}>{icon}</Box>\n <Box width={nameColumnWidth}>\n <Text inverse={isSelected}>{clippedName}</Text>\n </Box>\n {statusWidth > 0 && statusText && (\n <Box width={statusWidth + gap}>\n <Text> {statusText}</Text>\n </Box>\n )}\n </Box>\n );\n});\n"],"names":["ansi","ansiRegex","stripAnsi","str","replace","truncate","maxWidth","length","slice","getLastOutputLine","lines","i","text","getErrorCount","filter","line","type","LineType","stderr","memo","CompactProcessLine","item","isSelected","store","useStore","stdout","useStdout","terminalWidth","columns","group","title","state","selectionIndicator","figures","pointer","displayName","selectionWidth","iconWidth","maxGroupLength","getMaxGroupLength","nameColumnWidth","calculateColumnWidth","gap","statusWidth","Math","max","clippedName","clipText","padEnd","statusText","useMemo","lastLine","stripped","errorCount","icon","Spinner","SPINNER","Text","color","tick","cross","Box","width","undefined","inverse"],"mappings":"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"}
|
|
@@ -12,6 +12,12 @@ var _jsxruntime = require("react/jsx-runtime");
|
|
|
12
12
|
var _ink = require("ink");
|
|
13
13
|
var _react = require("react");
|
|
14
14
|
var _constantsts = require("../constants.js");
|
|
15
|
+
var _AnsiTextts = /*#__PURE__*/ _interop_require_default(require("../lib/AnsiText.js"));
|
|
16
|
+
function _interop_require_default(obj) {
|
|
17
|
+
return obj && obj.__esModule ? obj : {
|
|
18
|
+
default: obj
|
|
19
|
+
};
|
|
20
|
+
}
|
|
15
21
|
var isMac = process.platform === 'darwin';
|
|
16
22
|
var _default = /*#__PURE__*/ (0, _react.memo)(function ExpandedOutput(param) {
|
|
17
23
|
var lines = param.lines, scrollOffset = param.scrollOffset, _param_maxVisible = param.maxVisible, maxVisible = _param_maxVisible === void 0 ? _constantsts.EXPANDED_MAX_VISIBLE_LINES : _param_maxVisible;
|
|
@@ -36,7 +42,9 @@ var _default = /*#__PURE__*/ (0, _react.memo)(function ExpandedOutput(param) {
|
|
|
36
42
|
/*#__PURE__*/ (0, _jsxruntime.jsxs)(_ink.Text, {
|
|
37
43
|
children: [
|
|
38
44
|
"│ ",
|
|
39
|
-
|
|
45
|
+
/*#__PURE__*/ (0, _jsxruntime.jsx)(_AnsiTextts.default, {
|
|
46
|
+
children: line.text || ' '
|
|
47
|
+
})
|
|
40
48
|
]
|
|
41
49
|
}, scrollOffset + i));
|
|
42
50
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/ExpandedOutput.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo } from 'react';\nimport { EXPANDED_MAX_VISIBLE_LINES } from '../constants.ts';\nimport type { Line } from '../types.ts';\n\nconst isMac = process.platform === 'darwin';\n\ntype Props = {\n lines: Line[];\n scrollOffset: number;\n maxVisible?: number;\n};\n\nexport default memo(function ExpandedOutput({ lines, scrollOffset, maxVisible = EXPANDED_MAX_VISIBLE_LINES }: Props) {\n const visibleLines = lines.slice(scrollOffset, scrollOffset + maxVisible);\n const hasMore = lines.length > scrollOffset + maxVisible;\n const remaining = lines.length - scrollOffset - maxVisible;\n\n if (lines.length === 0) {\n return (\n <Box paddingLeft={2}>\n <Text dimColor>│ (no output)</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" paddingLeft={2}>\n {visibleLines.map((line, i) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: Lines have no unique ID, index is stable for this scrolling view\n <Text key={scrollOffset + i}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/ExpandedOutput.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo } from 'react';\nimport { EXPANDED_MAX_VISIBLE_LINES } from '../constants.ts';\nimport AnsiText from '../lib/AnsiText.ts';\nimport type { Line } from '../types.ts';\n\nconst isMac = process.platform === 'darwin';\n\ntype Props = {\n lines: Line[];\n scrollOffset: number;\n maxVisible?: number;\n};\n\nexport default memo(function ExpandedOutput({ lines, scrollOffset, maxVisible = EXPANDED_MAX_VISIBLE_LINES }: Props) {\n const visibleLines = lines.slice(scrollOffset, scrollOffset + maxVisible);\n const hasMore = lines.length > scrollOffset + maxVisible;\n const remaining = lines.length - scrollOffset - maxVisible;\n\n if (lines.length === 0) {\n return (\n <Box paddingLeft={2}>\n <Text dimColor>│ (no output)</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" paddingLeft={2}>\n {visibleLines.map((line, i) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: Lines have no unique ID, index is stable for this scrolling view\n <Text key={scrollOffset + i}>\n │ <AnsiText>{line.text || ' '}</AnsiText>\n </Text>\n ))}\n {hasMore ? (\n <Text dimColor>\n │ [+{remaining} more, Tab/⇧Tab page, {isMac ? '⌥↑/↓' : 'g/G'} top/bottom, ↵ close]\n </Text>\n ) : (\n <Text dimColor>│ [↵ close]</Text>\n )}\n </Box>\n );\n});\n"],"names":["isMac","process","platform","memo","ExpandedOutput","lines","scrollOffset","maxVisible","EXPANDED_MAX_VISIBLE_LINES","visibleLines","slice","hasMore","length","remaining","Box","paddingLeft","Text","dimColor","flexDirection","map","line","i","AnsiText","text"],"mappings":";;;;+BAcA;;;eAAA;;;;mBAd0B;qBACL;2BACsB;iEACtB;;;;;;AAGrB,IAAMA,QAAQC,QAAQC,QAAQ,KAAK;IAQnC,yBAAeC,IAAAA,WAAI,EAAC,SAASC,eAAe,KAAuE;QAArEC,QAAF,MAAEA,OAAOC,eAAT,MAASA,kCAAT,MAAuBC,YAAAA,4CAAaC,uCAA0B;IACxG,IAAMC,eAAeJ,MAAMK,KAAK,CAACJ,cAAcA,eAAeC;IAC9D,IAAMI,UAAUN,MAAMO,MAAM,GAAGN,eAAeC;IAC9C,IAAMM,YAAYR,MAAMO,MAAM,GAAGN,eAAeC;IAEhD,IAAIF,MAAMO,MAAM,KAAK,GAAG;QACtB,qBACE,qBAACE,QAAG;YAACC,aAAa;sBAChB,cAAA,qBAACC,SAAI;gBAACC,QAAQ;0BAAC;;;IAGrB;IAEA,qBACE,sBAACH,QAAG;QAACI,eAAc;QAASH,aAAa;;YACtCN,aAAaU,GAAG,CAAC,SAACC,MAAMC;uBACvB,iHAAiH;8BACjH,sBAACL,SAAI;;wBAAwB;sCACzB,qBAACM,mBAAQ;sCAAEF,KAAKG,IAAI,IAAI;;;mBADjBjB,eAAee;;YAI3BV,wBACC,sBAACK,SAAI;gBAACC,QAAQ;;oBAAC;oBACRJ;oBAAU;oBAAuBb,QAAQ,SAAS;oBAAM;;+BAG/D,qBAACgB,SAAI;gBAACC,QAAQ;0BAAC;;;;AAIvB"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, /**
|
|
6
|
+
* Component that renders text with ANSI escape codes as styled Ink Text components.
|
|
7
|
+
* Parses ANSI color and style codes and applies them as Ink props.
|
|
8
|
+
*/ "default", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
get: function() {
|
|
11
|
+
return _default;
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
var _jsxruntime = require("react/jsx-runtime");
|
|
15
|
+
var _ink = require("ink");
|
|
16
|
+
var _react = require("react");
|
|
17
|
+
function _array_like_to_array(arr, len) {
|
|
18
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
19
|
+
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
20
|
+
return arr2;
|
|
21
|
+
}
|
|
22
|
+
function _array_with_holes(arr) {
|
|
23
|
+
if (Array.isArray(arr)) return arr;
|
|
24
|
+
}
|
|
25
|
+
function _define_property(obj, key, value) {
|
|
26
|
+
if (key in obj) {
|
|
27
|
+
Object.defineProperty(obj, key, {
|
|
28
|
+
value: value,
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: true,
|
|
31
|
+
writable: true
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
obj[key] = value;
|
|
35
|
+
}
|
|
36
|
+
return obj;
|
|
37
|
+
}
|
|
38
|
+
function _iterable_to_array_limit(arr, i) {
|
|
39
|
+
var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
|
|
40
|
+
if (_i == null) return;
|
|
41
|
+
var _arr = [];
|
|
42
|
+
var _n = true;
|
|
43
|
+
var _d = false;
|
|
44
|
+
var _s, _e;
|
|
45
|
+
try {
|
|
46
|
+
for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
|
|
47
|
+
_arr.push(_s.value);
|
|
48
|
+
if (i && _arr.length === i) break;
|
|
49
|
+
}
|
|
50
|
+
} catch (err) {
|
|
51
|
+
_d = true;
|
|
52
|
+
_e = err;
|
|
53
|
+
} finally{
|
|
54
|
+
try {
|
|
55
|
+
if (!_n && _i["return"] != null) _i["return"]();
|
|
56
|
+
} finally{
|
|
57
|
+
if (_d) throw _e;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return _arr;
|
|
61
|
+
}
|
|
62
|
+
function _non_iterable_rest() {
|
|
63
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
64
|
+
}
|
|
65
|
+
function _object_spread(target) {
|
|
66
|
+
for(var i = 1; i < arguments.length; i++){
|
|
67
|
+
var source = arguments[i] != null ? arguments[i] : {};
|
|
68
|
+
var ownKeys = Object.keys(source);
|
|
69
|
+
if (typeof Object.getOwnPropertySymbols === "function") {
|
|
70
|
+
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
|
|
71
|
+
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
ownKeys.forEach(function(key) {
|
|
75
|
+
_define_property(target, key, source[key]);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return target;
|
|
79
|
+
}
|
|
80
|
+
function _sliced_to_array(arr, i) {
|
|
81
|
+
return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
|
|
82
|
+
}
|
|
83
|
+
function _unsupported_iterable_to_array(o, minLen) {
|
|
84
|
+
if (!o) return;
|
|
85
|
+
if (typeof o === "string") return _array_like_to_array(o, minLen);
|
|
86
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
87
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
88
|
+
if (n === "Map" || n === "Set") return Array.from(n);
|
|
89
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
90
|
+
}
|
|
91
|
+
// ANSI color codes to Ink color mapping
|
|
92
|
+
// Based on standard ANSI SGR (Select Graphic Rendition) parameters
|
|
93
|
+
var ANSI_COLORS = {
|
|
94
|
+
'\x1b[30m': 'black',
|
|
95
|
+
'\x1b[31m': 'red',
|
|
96
|
+
'\x1b[32m': 'green',
|
|
97
|
+
'\x1b[33m': 'yellow',
|
|
98
|
+
'\x1b[34m': 'blue',
|
|
99
|
+
'\x1b[35m': 'magenta',
|
|
100
|
+
'\x1b[36m': 'cyan',
|
|
101
|
+
'\x1b[37m': 'white',
|
|
102
|
+
// Bright colors
|
|
103
|
+
'\x1b[90m': 'gray',
|
|
104
|
+
'\x1b[91m': 'redBright',
|
|
105
|
+
'\x1b[92m': 'greenBright',
|
|
106
|
+
'\x1b[93m': 'yellowBright',
|
|
107
|
+
'\x1b[94m': 'blueBright',
|
|
108
|
+
'\x1b[95m': 'magentaBright',
|
|
109
|
+
'\x1b[96m': 'cyanBright',
|
|
110
|
+
'\x1b[97m': 'whiteBright'
|
|
111
|
+
};
|
|
112
|
+
// ANSI style codes
|
|
113
|
+
var ANSI_STYLES = {
|
|
114
|
+
'\x1b[1m': 'bold',
|
|
115
|
+
'\x1b[2m': 'dim',
|
|
116
|
+
'\x1b[3m': 'italic',
|
|
117
|
+
'\x1b[4m': 'underline',
|
|
118
|
+
'\x1b[9m': 'strikethrough',
|
|
119
|
+
'\x1b[7m': 'inverse'
|
|
120
|
+
};
|
|
121
|
+
// Reset codes
|
|
122
|
+
var RESET_CODES = [
|
|
123
|
+
'\x1b[0m',
|
|
124
|
+
'\x1b[39m',
|
|
125
|
+
'\x1b[49m'
|
|
126
|
+
];
|
|
127
|
+
/**
|
|
128
|
+
* Parse a string with ANSI codes into segments with styling information.
|
|
129
|
+
*/ function parseAnsiString(input) {
|
|
130
|
+
var segments = [];
|
|
131
|
+
var currentSegment = {
|
|
132
|
+
text: ''
|
|
133
|
+
};
|
|
134
|
+
var i = 0;
|
|
135
|
+
while(i < input.length){
|
|
136
|
+
// Check for ANSI escape sequence
|
|
137
|
+
if (input[i] === '\x1b' && input[i + 1] === '[') {
|
|
138
|
+
// Find the end of the escape sequence (ends with a letter)
|
|
139
|
+
var j = i + 2;
|
|
140
|
+
while(j < input.length && !/[a-zA-Z]/.test(input[j])){
|
|
141
|
+
j++;
|
|
142
|
+
}
|
|
143
|
+
j++; // Include the letter
|
|
144
|
+
var code = input.slice(i, j);
|
|
145
|
+
// If we have accumulated text, save it
|
|
146
|
+
if (currentSegment.text.length > 0) {
|
|
147
|
+
segments.push(currentSegment);
|
|
148
|
+
currentSegment = _object_spread({
|
|
149
|
+
text: ''
|
|
150
|
+
}, Object.fromEntries(Object.entries(currentSegment).filter(function(param) {
|
|
151
|
+
var _param = _sliced_to_array(param, 1), key = _param[0];
|
|
152
|
+
return key !== 'text';
|
|
153
|
+
}).filter(function(param) {
|
|
154
|
+
var _param = _sliced_to_array(param, 2), _ = _param[0], value = _param[1];
|
|
155
|
+
return value !== undefined;
|
|
156
|
+
})));
|
|
157
|
+
}
|
|
158
|
+
// Apply the style
|
|
159
|
+
if (RESET_CODES.includes(code)) {
|
|
160
|
+
// Reset all styles
|
|
161
|
+
currentSegment = {
|
|
162
|
+
text: ''
|
|
163
|
+
};
|
|
164
|
+
} else if (ANSI_COLORS[code]) {
|
|
165
|
+
currentSegment.color = ANSI_COLORS[code];
|
|
166
|
+
} else if (ANSI_STYLES[code]) {
|
|
167
|
+
var style = ANSI_STYLES[code];
|
|
168
|
+
currentSegment[style] = true;
|
|
169
|
+
}
|
|
170
|
+
i = j;
|
|
171
|
+
} else {
|
|
172
|
+
// Regular character
|
|
173
|
+
currentSegment.text += input[i];
|
|
174
|
+
i++;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Add the last segment if it has text
|
|
178
|
+
if (currentSegment.text.length > 0) {
|
|
179
|
+
segments.push(currentSegment);
|
|
180
|
+
}
|
|
181
|
+
return segments;
|
|
182
|
+
}
|
|
183
|
+
var _default = /*#__PURE__*/ (0, _react.memo)(function AnsiText(param) {
|
|
184
|
+
var children = param.children;
|
|
185
|
+
// If the input is empty or has no ANSI codes, just render it directly
|
|
186
|
+
if (!children || !children.includes('\x1b')) {
|
|
187
|
+
return /*#__PURE__*/ (0, _jsxruntime.jsx)(_react.Fragment, {
|
|
188
|
+
children: children
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
var segments = parseAnsiString(children);
|
|
192
|
+
// If parsing resulted in a single unstyled segment, render it directly
|
|
193
|
+
if (segments.length === 1 && !segments[0].color && !segments[0].bold && !segments[0].dim && !segments[0].italic && !segments[0].underline && !segments[0].strikethrough && !segments[0].inverse) {
|
|
194
|
+
return /*#__PURE__*/ (0, _jsxruntime.jsx)(_react.Fragment, {
|
|
195
|
+
children: segments[0].text
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return /*#__PURE__*/ (0, _jsxruntime.jsx)(_react.Fragment, {
|
|
199
|
+
children: segments.map(function(segment, index) {
|
|
200
|
+
return(// biome-ignore lint/suspicious/noArrayIndexKey: Segments have no unique ID, index is stable for parsed content
|
|
201
|
+
/*#__PURE__*/ (0, _jsxruntime.jsx)(_ink.Text, {
|
|
202
|
+
color: segment.color,
|
|
203
|
+
bold: segment.bold,
|
|
204
|
+
dimColor: segment.dim,
|
|
205
|
+
italic: segment.italic,
|
|
206
|
+
underline: segment.underline,
|
|
207
|
+
strikethrough: segment.strikethrough,
|
|
208
|
+
inverse: segment.inverse,
|
|
209
|
+
children: segment.text
|
|
210
|
+
}, index));
|
|
211
|
+
})
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
/* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/AnsiText.tsx"],"sourcesContent":["import { Text } from 'ink';\nimport { Fragment, memo } from 'react';\n\n// ANSI color codes to Ink color mapping\n// Based on standard ANSI SGR (Select Graphic Rendition) parameters\nconst ANSI_COLORS: Record<string, string> = {\n '\\x1b[30m': 'black',\n '\\x1b[31m': 'red',\n '\\x1b[32m': 'green',\n '\\x1b[33m': 'yellow',\n '\\x1b[34m': 'blue',\n '\\x1b[35m': 'magenta',\n '\\x1b[36m': 'cyan',\n '\\x1b[37m': 'white',\n // Bright colors\n '\\x1b[90m': 'gray',\n '\\x1b[91m': 'redBright',\n '\\x1b[92m': 'greenBright',\n '\\x1b[93m': 'yellowBright',\n '\\x1b[94m': 'blueBright',\n '\\x1b[95m': 'magentaBright',\n '\\x1b[96m': 'cyanBright',\n '\\x1b[97m': 'whiteBright',\n};\n\n// ANSI style codes\nconst ANSI_STYLES: Record<string, string> = {\n '\\x1b[1m': 'bold',\n '\\x1b[2m': 'dim',\n '\\x1b[3m': 'italic',\n '\\x1b[4m': 'underline',\n '\\x1b[9m': 'strikethrough',\n '\\x1b[7m': 'inverse',\n};\n\n// Reset codes\nconst RESET_CODES = ['\\x1b[0m', '\\x1b[39m', '\\x1b[49m'];\n\ntype TextSegment = {\n text: string;\n color?: string;\n bold?: boolean;\n dim?: boolean;\n italic?: boolean;\n underline?: boolean;\n strikethrough?: boolean;\n inverse?: boolean;\n};\n\n/**\n * Parse a string with ANSI codes into segments with styling information.\n */\nfunction parseAnsiString(input: string): TextSegment[] {\n const segments: TextSegment[] = [];\n let currentSegment: TextSegment = { text: '' };\n let i = 0;\n\n while (i < input.length) {\n // Check for ANSI escape sequence\n if (input[i] === '\\x1b' && input[i + 1] === '[') {\n // Find the end of the escape sequence (ends with a letter)\n let j = i + 2;\n while (j < input.length && !/[a-zA-Z]/.test(input[j])) {\n j++;\n }\n j++; // Include the letter\n\n const code = input.slice(i, j);\n\n // If we have accumulated text, save it\n if (currentSegment.text.length > 0) {\n segments.push(currentSegment);\n currentSegment = {\n text: '',\n ...Object.fromEntries(\n Object.entries(currentSegment)\n .filter(([key]) => key !== 'text')\n .filter(([_, value]) => value !== undefined)\n ),\n } as TextSegment;\n }\n\n // Apply the style\n if (RESET_CODES.includes(code)) {\n // Reset all styles\n currentSegment = { text: '' };\n } else if (ANSI_COLORS[code]) {\n currentSegment.color = ANSI_COLORS[code];\n } else if (ANSI_STYLES[code]) {\n const style = ANSI_STYLES[code] as keyof Omit<TextSegment, 'text' | 'color'>;\n currentSegment[style] = true;\n }\n\n i = j;\n } else {\n // Regular character\n currentSegment.text += input[i];\n i++;\n }\n }\n\n // Add the last segment if it has text\n if (currentSegment.text.length > 0) {\n segments.push(currentSegment);\n }\n\n return segments;\n}\n\ntype Props = {\n children: string;\n};\n\n/**\n * Component that renders text with ANSI escape codes as styled Ink Text components.\n * Parses ANSI color and style codes and applies them as Ink props.\n */\nexport default memo(function AnsiText({ children }: Props) {\n // If the input is empty or has no ANSI codes, just render it directly\n if (!children || !children.includes('\\x1b')) {\n return <Fragment>{children}</Fragment>;\n }\n\n const segments = parseAnsiString(children);\n\n // If parsing resulted in a single unstyled segment, render it directly\n if (segments.length === 1 && !segments[0].color && !segments[0].bold && !segments[0].dim && !segments[0].italic && !segments[0].underline && !segments[0].strikethrough && !segments[0].inverse) {\n return <Fragment>{segments[0].text}</Fragment>;\n }\n\n return (\n <Fragment>\n {segments.map((segment, index) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: Segments have no unique ID, index is stable for parsed content\n <Text key={index} color={segment.color} bold={segment.bold} dimColor={segment.dim} italic={segment.italic} underline={segment.underline} strikethrough={segment.strikethrough} inverse={segment.inverse}>\n {segment.text}\n </Text>\n ))}\n </Fragment>\n );\n});\n"],"names":["ANSI_COLORS","ANSI_STYLES","RESET_CODES","parseAnsiString","input","segments","currentSegment","text","i","length","j","test","code","slice","push","Object","fromEntries","entries","filter","key","_","value","undefined","includes","color","style","memo","AnsiText","children","Fragment","bold","dim","italic","underline","strikethrough","inverse","map","segment","index","Text","dimColor"],"mappings":"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"}
|
package/dist/cjs/lib/clipText.js
CHANGED
|
@@ -51,6 +51,6 @@ function clipText(str, maxWidth) {
|
|
|
51
51
|
i++;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
return "".concat(result, "…");
|
|
54
|
+
return "".concat(result, "\x1b[0m…");
|
|
55
55
|
}
|
|
56
56
|
/* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/clipText.ts"],"sourcesContent":["import ansiRegex from './ansiRegex.ts';\n\nconst regex = ansiRegex();\n\n/**\n * Get the visible length of a string, ignoring ANSI escape codes.\n */\nexport function visibleLength(str: string): number {\n return str.replace(regex, '').length;\n}\n\n/**\n * Clip text to a maximum visible width, accounting for ANSI escape codes.\n * Adds ellipsis (…) if truncated.\n */\nexport function clipText(str: string, maxWidth: number): string {\n if (maxWidth <= 0) return '';\n if (maxWidth === 1) return '…';\n\n const stripped = str.replace(regex, '');\n if (stripped.length <= maxWidth) return str;\n\n // Need to truncate - walk through and preserve ANSI codes\n let visibleCount = 0;\n let result = '';\n let i = 0;\n\n while (i < str.length && visibleCount < maxWidth - 1) {\n // Check for ANSI escape sequence (ESC [ params m)\n const remaining = str.slice(i);\n // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape requires control char\n const match = remaining.match(/^(\\u001B\\[[0-9;]*m)/);\n\n if (match) {\n // Include the ANSI code in output but don't count toward visible length\n result += match[1];\n i += match[1].length;\n } else {\n // Regular character\n result += str[i];\n visibleCount++;\n i++;\n }\n }\n\n return `${result}…`;\n}\n"],"names":["clipText","visibleLength","regex","ansiRegex","str","replace","length","maxWidth","stripped","visibleCount","result","i","remaining","slice","match"],"mappings":";;;;;;;;;;;QAegBA;eAAAA;;QARAC;eAAAA;;;kEAPM;;;;;;AAEtB,IAAMC,QAAQC,IAAAA,oBAAS;AAKhB,SAASF,cAAcG,GAAW;IACvC,OAAOA,IAAIC,OAAO,CAACH,OAAO,IAAII,MAAM;AACtC;AAMO,SAASN,SAASI,GAAW,EAAEG,QAAgB;IACpD,IAAIA,YAAY,GAAG,OAAO;IAC1B,IAAIA,aAAa,GAAG,OAAO;IAE3B,IAAMC,WAAWJ,IAAIC,OAAO,CAACH,OAAO;IACpC,IAAIM,SAASF,MAAM,IAAIC,UAAU,OAAOH;IAExC,0DAA0D;IAC1D,IAAIK,eAAe;IACnB,IAAIC,SAAS;IACb,IAAIC,IAAI;IAER,MAAOA,IAAIP,IAAIE,MAAM,IAAIG,eAAeF,WAAW,EAAG;QACpD,kDAAkD;QAClD,IAAMK,YAAYR,IAAIS,KAAK,CAACF;QAC5B,6FAA6F;QAC7F,IAAMG,QAAQF,UAAUE,KAAK,CAAC;QAE9B,IAAIA,OAAO;YACT,wEAAwE;YACxEJ,UAAUI,KAAK,CAAC,EAAE;YAClBH,KAAKG,KAAK,CAAC,EAAE,CAACR,MAAM;QACtB,OAAO;YACL,oBAAoB;YACpBI,UAAUN,GAAG,CAACO,EAAE;YAChBF;YACAE;QACF;IACF;IAEA,OAAO,AAAC,GAAS,OAAPD,QAAO;AACnB"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/clipText.ts"],"sourcesContent":["import ansiRegex from './ansiRegex.ts';\n\nconst regex = ansiRegex();\n\n/**\n * Get the visible length of a string, ignoring ANSI escape codes.\n */\nexport function visibleLength(str: string): number {\n return str.replace(regex, '').length;\n}\n\n/**\n * Clip text to a maximum visible width, accounting for ANSI escape codes.\n * Adds ellipsis (…) if truncated.\n */\nexport function clipText(str: string, maxWidth: number): string {\n if (maxWidth <= 0) return '';\n if (maxWidth === 1) return '…';\n\n const stripped = str.replace(regex, '');\n if (stripped.length <= maxWidth) return str;\n\n // Need to truncate - walk through and preserve ANSI codes\n let visibleCount = 0;\n let result = '';\n let i = 0;\n\n while (i < str.length && visibleCount < maxWidth - 1) {\n // Check for ANSI escape sequence (ESC [ params m)\n const remaining = str.slice(i);\n // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape requires control char\n const match = remaining.match(/^(\\u001B\\[[0-9;]*m)/);\n\n if (match) {\n // Include the ANSI code in output but don't count toward visible length\n result += match[1];\n i += match[1].length;\n } else {\n // Regular character\n result += str[i];\n visibleCount++;\n i++;\n }\n }\n\n return `${result}\\x1b[0m…`;\n}\n"],"names":["clipText","visibleLength","regex","ansiRegex","str","replace","length","maxWidth","stripped","visibleCount","result","i","remaining","slice","match"],"mappings":";;;;;;;;;;;QAegBA;eAAAA;;QARAC;eAAAA;;;kEAPM;;;;;;AAEtB,IAAMC,QAAQC,IAAAA,oBAAS;AAKhB,SAASF,cAAcG,GAAW;IACvC,OAAOA,IAAIC,OAAO,CAACH,OAAO,IAAII,MAAM;AACtC;AAMO,SAASN,SAASI,GAAW,EAAEG,QAAgB;IACpD,IAAIA,YAAY,GAAG,OAAO;IAC1B,IAAIA,aAAa,GAAG,OAAO;IAE3B,IAAMC,WAAWJ,IAAIC,OAAO,CAACH,OAAO;IACpC,IAAIM,SAASF,MAAM,IAAIC,UAAU,OAAOH;IAExC,0DAA0D;IAC1D,IAAIK,eAAe;IACnB,IAAIC,SAAS;IACb,IAAIC,IAAI;IAER,MAAOA,IAAIP,IAAIE,MAAM,IAAIG,eAAeF,WAAW,EAAG;QACpD,kDAAkD;QAClD,IAAMK,YAAYR,IAAIS,KAAK,CAACF;QAC5B,6FAA6F;QAC7F,IAAMG,QAAQF,UAAUE,KAAK,CAAC;QAE9B,IAAIA,OAAO;YACT,wEAAwE;YACxEJ,UAAUI,KAAK,CAAC,EAAE;YAClBH,KAAKG,KAAK,CAAC,EAAE,CAACR,MAAM;QACtB,OAAO;YACL,oBAAoB;YACpBI,UAAUN,GAAG,CAACO,EAAE;YAChBF;YACAE;QACF;IACF;IAEA,OAAO,AAAC,GAAS,OAAPD,QAAO;AACnB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
children: string;
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Component that renders text with ANSI escape codes as styled Ink Text components.
|
|
6
|
+
* Parses ANSI color and style codes and applies them as Ink props.
|
|
7
|
+
*/
|
|
8
|
+
declare const _default: import("react").NamedExoticComponent<Props>;
|
|
9
|
+
export default _default;
|
|
@@ -2,12 +2,28 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Box, Text, useStdout } from 'ink';
|
|
3
3
|
import { memo, useMemo } from 'react';
|
|
4
4
|
import { SPINNER } from '../constants.js';
|
|
5
|
+
import ansiRegex from '../lib/ansiRegex.js';
|
|
5
6
|
import { clipText } from '../lib/clipText.js';
|
|
6
7
|
import figures from '../lib/figures.js';
|
|
7
8
|
import { calculateColumnWidth } from '../lib/format.js';
|
|
8
9
|
import { useStore } from '../state/StoreContext.js';
|
|
9
10
|
import { LineType } from '../types.js';
|
|
10
11
|
import Spinner from './Spinner.js';
|
|
12
|
+
const ansi = ansiRegex();
|
|
13
|
+
/**
|
|
14
|
+
* Strip ANSI escape codes from a string.
|
|
15
|
+
*/ function stripAnsi(str) {
|
|
16
|
+
return str.replace(ansi, '');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Simple truncation for plain text (no ANSI codes).
|
|
20
|
+
* Adds ellipsis if truncated.
|
|
21
|
+
*/ function truncate(str, maxWidth) {
|
|
22
|
+
if (maxWidth <= 0) return '';
|
|
23
|
+
if (maxWidth === 1) return '…';
|
|
24
|
+
if (str.length <= maxWidth) return str;
|
|
25
|
+
return `${str.slice(0, maxWidth - 1)}…`;
|
|
26
|
+
}
|
|
11
27
|
function getLastOutputLine(lines) {
|
|
12
28
|
for(let i = lines.length - 1; i >= 0; i--){
|
|
13
29
|
if (lines[i].text.length > 0) {
|
|
@@ -40,12 +56,13 @@ export default /*#__PURE__*/ memo(function CompactProcessLine({ item, isSelected
|
|
|
40
56
|
const statusText = useMemo(()=>{
|
|
41
57
|
if (state === 'running') {
|
|
42
58
|
const lastLine = getLastOutputLine(lines);
|
|
43
|
-
|
|
59
|
+
const stripped = lastLine ? stripAnsi(lastLine) : '';
|
|
60
|
+
return stripped ? truncate(stripped, statusWidth) : '';
|
|
44
61
|
}
|
|
45
62
|
if (state === 'error') {
|
|
46
63
|
const errorCount = getErrorCount(lines);
|
|
47
64
|
const text = errorCount > 0 ? `${errorCount} error${errorCount > 1 ? 's' : ''}` : 'failed';
|
|
48
|
-
return
|
|
65
|
+
return truncate(text, statusWidth);
|
|
49
66
|
}
|
|
50
67
|
return ''; // success - no status text
|
|
51
68
|
}, [
|
|
@@ -74,8 +91,6 @@ export default /*#__PURE__*/ memo(function CompactProcessLine({ item, isSelected
|
|
|
74
91
|
}, [
|
|
75
92
|
state
|
|
76
93
|
]);
|
|
77
|
-
// Status text color
|
|
78
|
-
const statusColor = state === 'error' ? 'red' : 'gray';
|
|
79
94
|
return /*#__PURE__*/ _jsxs(Box, {
|
|
80
95
|
width: terminalWidth,
|
|
81
96
|
children: [
|
|
@@ -97,7 +112,6 @@ export default /*#__PURE__*/ memo(function CompactProcessLine({ item, isSelected
|
|
|
97
112
|
statusWidth > 0 && statusText && /*#__PURE__*/ _jsx(Box, {
|
|
98
113
|
width: statusWidth + gap,
|
|
99
114
|
children: /*#__PURE__*/ _jsxs(Text, {
|
|
100
|
-
color: statusColor,
|
|
101
115
|
children: [
|
|
102
116
|
" ",
|
|
103
117
|
statusText
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/CompactProcessLine.tsx"],"sourcesContent":["import { Box, Text, useStdout } from 'ink';\nimport { memo, useMemo } from 'react';\nimport { SPINNER } from '../constants.ts';\nimport { clipText } from '../lib/clipText.ts';\nimport figures from '../lib/figures.ts';\nimport { calculateColumnWidth } from '../lib/format.ts';\nimport { useStore } from '../state/StoreContext.ts';\nimport type { ChildProcess, Line } from '../types.ts';\nimport { LineType } from '../types.ts';\nimport Spinner from './Spinner.ts';\n\ntype Props = {\n item: ChildProcess;\n isSelected?: boolean;\n};\n\nfunction getLastOutputLine(lines: Line[]): string {\n for (let i = lines.length - 1; i >= 0; i--) {\n if (lines[i].text.length > 0) {\n return lines[i].text;\n }\n }\n return '';\n}\n\nfunction getErrorCount(lines: Line[]): number {\n return lines.filter((line) => line.type === LineType.stderr).length;\n}\n\nexport default memo(function CompactProcessLine({ item, isSelected = false }: Props) {\n const store = useStore();\n const { stdout } = useStdout();\n const terminalWidth = stdout?.columns || 120;\n\n const { group, title, state, lines } = item;\n const selectionIndicator = isSelected ? figures.pointer : ' ';\n\n // Display name: prefer group, fall back to title\n const displayName = group || title;\n\n // Calculate widths - use dynamic column width based on longest name\n const selectionWidth = 1; // selection indicator\n const iconWidth = 2; // icon + space\n const maxGroupLength = store.getMaxGroupLength();\n const nameColumnWidth = calculateColumnWidth('max', terminalWidth, maxGroupLength);\n const gap = 1; // space between name and status\n const statusWidth = Math.max(0, terminalWidth - selectionWidth - iconWidth - nameColumnWidth - gap);\n\n // Clip name to column width and pad\n const clippedName = clipText(displayName, nameColumnWidth).padEnd(nameColumnWidth);\n\n // Status text based on state - clip to available width\n const statusText = useMemo(() => {\n if (state === 'running') {\n const lastLine = getLastOutputLine(lines);\n
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/CompactProcessLine.tsx"],"sourcesContent":["import { Box, Text, useStdout } from 'ink';\nimport { memo, useMemo } from 'react';\nimport { SPINNER } from '../constants.ts';\nimport ansiRegex from '../lib/ansiRegex.ts';\nimport { clipText } from '../lib/clipText.ts';\nimport figures from '../lib/figures.ts';\nimport { calculateColumnWidth } from '../lib/format.ts';\nimport { useStore } from '../state/StoreContext.ts';\nimport type { ChildProcess, Line } from '../types.ts';\nimport { LineType } from '../types.ts';\nimport Spinner from './Spinner.ts';\n\nconst ansi = ansiRegex();\n\n/**\n * Strip ANSI escape codes from a string.\n */\nfunction stripAnsi(str: string): string {\n return str.replace(ansi, '');\n}\n\n/**\n * Simple truncation for plain text (no ANSI codes).\n * Adds ellipsis if truncated.\n */\nfunction truncate(str: string, maxWidth: number): string {\n if (maxWidth <= 0) return '';\n if (maxWidth === 1) return '…';\n if (str.length <= maxWidth) return str;\n return `${str.slice(0, maxWidth - 1)}…`;\n}\n\ntype Props = {\n item: ChildProcess;\n isSelected?: boolean;\n};\n\nfunction getLastOutputLine(lines: Line[]): string {\n for (let i = lines.length - 1; i >= 0; i--) {\n if (lines[i].text.length > 0) {\n return lines[i].text;\n }\n }\n return '';\n}\n\nfunction getErrorCount(lines: Line[]): number {\n return lines.filter((line) => line.type === LineType.stderr).length;\n}\n\nexport default memo(function CompactProcessLine({ item, isSelected = false }: Props) {\n const store = useStore();\n const { stdout } = useStdout();\n const terminalWidth = stdout?.columns || 120;\n\n const { group, title, state, lines } = item;\n const selectionIndicator = isSelected ? figures.pointer : ' ';\n\n // Display name: prefer group, fall back to title\n const displayName = group || title;\n\n // Calculate widths - use dynamic column width based on longest name\n const selectionWidth = 1; // selection indicator\n const iconWidth = 2; // icon + space\n const maxGroupLength = store.getMaxGroupLength();\n const nameColumnWidth = calculateColumnWidth('max', terminalWidth, maxGroupLength);\n const gap = 1; // space between name and status\n const statusWidth = Math.max(0, terminalWidth - selectionWidth - iconWidth - nameColumnWidth - gap);\n\n // Clip name to column width and pad\n const clippedName = clipText(displayName, nameColumnWidth).padEnd(nameColumnWidth);\n\n // Status text based on state - clip to available width\n const statusText = useMemo(() => {\n if (state === 'running') {\n const lastLine = getLastOutputLine(lines);\n const stripped = lastLine ? stripAnsi(lastLine) : '';\n return stripped ? truncate(stripped, statusWidth) : '';\n }\n if (state === 'error') {\n const errorCount = getErrorCount(lines);\n const text = errorCount > 0 ? `${errorCount} error${errorCount > 1 ? 's' : ''}` : 'failed';\n return truncate(text, statusWidth);\n }\n return ''; // success - no status text\n }, [state, lines, statusWidth]);\n\n // Icon based on state\n const icon = useMemo(() => {\n switch (state) {\n case 'running':\n return <Spinner {...SPINNER} />;\n case 'success':\n return <Text color=\"green\">{figures.tick}</Text>;\n case 'error':\n return <Text color=\"red\">{figures.cross}</Text>;\n }\n }, [state]);\n\n return (\n <Box width={terminalWidth}>\n <Text color={isSelected ? 'cyan' : undefined}>{selectionIndicator}</Text>\n <Box width={iconWidth}>{icon}</Box>\n <Box width={nameColumnWidth}>\n <Text inverse={isSelected}>{clippedName}</Text>\n </Box>\n {statusWidth > 0 && statusText && (\n <Box width={statusWidth + gap}>\n <Text> {statusText}</Text>\n </Box>\n )}\n </Box>\n );\n});\n"],"names":["Box","Text","useStdout","memo","useMemo","SPINNER","ansiRegex","clipText","figures","calculateColumnWidth","useStore","LineType","Spinner","ansi","stripAnsi","str","replace","truncate","maxWidth","length","slice","getLastOutputLine","lines","i","text","getErrorCount","filter","line","type","stderr","CompactProcessLine","item","isSelected","store","stdout","terminalWidth","columns","group","title","state","selectionIndicator","pointer","displayName","selectionWidth","iconWidth","maxGroupLength","getMaxGroupLength","nameColumnWidth","gap","statusWidth","Math","max","clippedName","padEnd","statusText","lastLine","stripped","errorCount","icon","color","tick","cross","width","undefined","inverse"],"mappings":";AAAA,SAASA,GAAG,EAAEC,IAAI,EAAEC,SAAS,QAAQ,MAAM;AAC3C,SAASC,IAAI,EAAEC,OAAO,QAAQ,QAAQ;AACtC,SAASC,OAAO,QAAQ,kBAAkB;AAC1C,OAAOC,eAAe,sBAAsB;AAC5C,SAASC,QAAQ,QAAQ,qBAAqB;AAC9C,OAAOC,aAAa,oBAAoB;AACxC,SAASC,oBAAoB,QAAQ,mBAAmB;AACxD,SAASC,QAAQ,QAAQ,2BAA2B;AAEpD,SAASC,QAAQ,QAAQ,cAAc;AACvC,OAAOC,aAAa,eAAe;AAEnC,MAAMC,OAAOP;AAEb;;CAEC,GACD,SAASQ,UAAUC,GAAW;IAC5B,OAAOA,IAAIC,OAAO,CAACH,MAAM;AAC3B;AAEA;;;CAGC,GACD,SAASI,SAASF,GAAW,EAAEG,QAAgB;IAC7C,IAAIA,YAAY,GAAG,OAAO;IAC1B,IAAIA,aAAa,GAAG,OAAO;IAC3B,IAAIH,IAAII,MAAM,IAAID,UAAU,OAAOH;IACnC,OAAO,GAAGA,IAAIK,KAAK,CAAC,GAAGF,WAAW,GAAG,CAAC,CAAC;AACzC;AAOA,SAASG,kBAAkBC,KAAa;IACtC,IAAK,IAAIC,IAAID,MAAMH,MAAM,GAAG,GAAGI,KAAK,GAAGA,IAAK;QAC1C,IAAID,KAAK,CAACC,EAAE,CAACC,IAAI,CAACL,MAAM,GAAG,GAAG;YAC5B,OAAOG,KAAK,CAACC,EAAE,CAACC,IAAI;QACtB;IACF;IACA,OAAO;AACT;AAEA,SAASC,cAAcH,KAAa;IAClC,OAAOA,MAAMI,MAAM,CAAC,CAACC,OAASA,KAAKC,IAAI,KAAKjB,SAASkB,MAAM,EAAEV,MAAM;AACrE;AAEA,6BAAehB,KAAK,SAAS2B,mBAAmB,EAAEC,IAAI,EAAEC,aAAa,KAAK,EAAS;IACjF,MAAMC,QAAQvB;IACd,MAAM,EAAEwB,MAAM,EAAE,GAAGhC;IACnB,MAAMiC,gBAAgBD,CAAAA,mBAAAA,6BAAAA,OAAQE,OAAO,KAAI;IAEzC,MAAM,EAAEC,KAAK,EAAEC,KAAK,EAAEC,KAAK,EAAEjB,KAAK,EAAE,GAAGS;IACvC,MAAMS,qBAAqBR,aAAaxB,QAAQiC,OAAO,GAAG;IAE1D,iDAAiD;IACjD,MAAMC,cAAcL,SAASC;IAE7B,oEAAoE;IACpE,MAAMK,iBAAiB,GAAG,sBAAsB;IAChD,MAAMC,YAAY,GAAG,eAAe;IACpC,MAAMC,iBAAiBZ,MAAMa,iBAAiB;IAC9C,MAAMC,kBAAkBtC,qBAAqB,OAAO0B,eAAeU;IACnE,MAAMG,MAAM,GAAG,gCAAgC;IAC/C,MAAMC,cAAcC,KAAKC,GAAG,CAAC,GAAGhB,gBAAgBQ,iBAAiBC,YAAYG,kBAAkBC;IAE/F,oCAAoC;IACpC,MAAMI,cAAc7C,SAASmC,aAAaK,iBAAiBM,MAAM,CAACN;IAElE,uDAAuD;IACvD,MAAMO,aAAalD,QAAQ;QACzB,IAAImC,UAAU,WAAW;YACvB,MAAMgB,WAAWlC,kBAAkBC;YACnC,MAAMkC,WAAWD,WAAWzC,UAAUyC,YAAY;YAClD,OAAOC,WAAWvC,SAASuC,UAAUP,eAAe;QACtD;QACA,IAAIV,UAAU,SAAS;YACrB,MAAMkB,aAAahC,cAAcH;YACjC,MAAME,OAAOiC,aAAa,IAAI,GAAGA,WAAW,MAAM,EAAEA,aAAa,IAAI,MAAM,IAAI,GAAG;YAClF,OAAOxC,SAASO,MAAMyB;QACxB;QACA,OAAO,IAAI,2BAA2B;IACxC,GAAG;QAACV;QAAOjB;QAAO2B;KAAY;IAE9B,sBAAsB;IACtB,MAAMS,OAAOtD,QAAQ;QACnB,OAAQmC;YACN,KAAK;gBACH,qBAAO,KAAC3B;oBAAS,GAAGP,OAAO;;YAC7B,KAAK;gBACH,qBAAO,KAACJ;oBAAK0D,OAAM;8BAASnD,QAAQoD,IAAI;;YAC1C,KAAK;gBACH,qBAAO,KAAC3D;oBAAK0D,OAAM;8BAAOnD,QAAQqD,KAAK;;QAC3C;IACF,GAAG;QAACtB;KAAM;IAEV,qBACE,MAACvC;QAAI8D,OAAO3B;;0BACV,KAAClC;gBAAK0D,OAAO3B,aAAa,SAAS+B;0BAAYvB;;0BAC/C,KAACxC;gBAAI8D,OAAOlB;0BAAYc;;0BACxB,KAAC1D;gBAAI8D,OAAOf;0BACV,cAAA,KAAC9C;oBAAK+D,SAAShC;8BAAaoB;;;YAE7BH,cAAc,KAAKK,4BAClB,KAACtD;gBAAI8D,OAAOb,cAAcD;0BACxB,cAAA,MAAC/C;;wBAAK;wBAAEqD;;;;;;AAKlB,GAAG"}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { memo } from 'react';
|
|
4
4
|
import { EXPANDED_MAX_VISIBLE_LINES } from '../constants.js';
|
|
5
|
+
import AnsiText from '../lib/AnsiText.js';
|
|
5
6
|
const isMac = process.platform === 'darwin';
|
|
6
7
|
export default /*#__PURE__*/ memo(function ExpandedOutput({ lines, scrollOffset, maxVisible = EXPANDED_MAX_VISIBLE_LINES }) {
|
|
7
8
|
const visibleLines = lines.slice(scrollOffset, scrollOffset + maxVisible);
|
|
@@ -24,7 +25,9 @@ export default /*#__PURE__*/ memo(function ExpandedOutput({ lines, scrollOffset,
|
|
|
24
25
|
/*#__PURE__*/ _jsxs(Text, {
|
|
25
26
|
children: [
|
|
26
27
|
"│ ",
|
|
27
|
-
|
|
28
|
+
/*#__PURE__*/ _jsx(AnsiText, {
|
|
29
|
+
children: line.text || ' '
|
|
30
|
+
})
|
|
28
31
|
]
|
|
29
32
|
}, scrollOffset + i)),
|
|
30
33
|
hasMore ? /*#__PURE__*/ _jsxs(Text, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/ExpandedOutput.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo } from 'react';\nimport { EXPANDED_MAX_VISIBLE_LINES } from '../constants.ts';\nimport type { Line } from '../types.ts';\n\nconst isMac = process.platform === 'darwin';\n\ntype Props = {\n lines: Line[];\n scrollOffset: number;\n maxVisible?: number;\n};\n\nexport default memo(function ExpandedOutput({ lines, scrollOffset, maxVisible = EXPANDED_MAX_VISIBLE_LINES }: Props) {\n const visibleLines = lines.slice(scrollOffset, scrollOffset + maxVisible);\n const hasMore = lines.length > scrollOffset + maxVisible;\n const remaining = lines.length - scrollOffset - maxVisible;\n\n if (lines.length === 0) {\n return (\n <Box paddingLeft={2}>\n <Text dimColor>│ (no output)</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" paddingLeft={2}>\n {visibleLines.map((line, i) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: Lines have no unique ID, index is stable for this scrolling view\n <Text key={scrollOffset + i}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/ExpandedOutput.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo } from 'react';\nimport { EXPANDED_MAX_VISIBLE_LINES } from '../constants.ts';\nimport AnsiText from '../lib/AnsiText.ts';\nimport type { Line } from '../types.ts';\n\nconst isMac = process.platform === 'darwin';\n\ntype Props = {\n lines: Line[];\n scrollOffset: number;\n maxVisible?: number;\n};\n\nexport default memo(function ExpandedOutput({ lines, scrollOffset, maxVisible = EXPANDED_MAX_VISIBLE_LINES }: Props) {\n const visibleLines = lines.slice(scrollOffset, scrollOffset + maxVisible);\n const hasMore = lines.length > scrollOffset + maxVisible;\n const remaining = lines.length - scrollOffset - maxVisible;\n\n if (lines.length === 0) {\n return (\n <Box paddingLeft={2}>\n <Text dimColor>│ (no output)</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" paddingLeft={2}>\n {visibleLines.map((line, i) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: Lines have no unique ID, index is stable for this scrolling view\n <Text key={scrollOffset + i}>\n │ <AnsiText>{line.text || ' '}</AnsiText>\n </Text>\n ))}\n {hasMore ? (\n <Text dimColor>\n │ [+{remaining} more, Tab/⇧Tab page, {isMac ? '⌥↑/↓' : 'g/G'} top/bottom, ↵ close]\n </Text>\n ) : (\n <Text dimColor>│ [↵ close]</Text>\n )}\n </Box>\n );\n});\n"],"names":["Box","Text","memo","EXPANDED_MAX_VISIBLE_LINES","AnsiText","isMac","process","platform","ExpandedOutput","lines","scrollOffset","maxVisible","visibleLines","slice","hasMore","length","remaining","paddingLeft","dimColor","flexDirection","map","line","i","text"],"mappings":";AAAA,SAASA,GAAG,EAAEC,IAAI,QAAQ,MAAM;AAChC,SAASC,IAAI,QAAQ,QAAQ;AAC7B,SAASC,0BAA0B,QAAQ,kBAAkB;AAC7D,OAAOC,cAAc,qBAAqB;AAG1C,MAAMC,QAAQC,QAAQC,QAAQ,KAAK;AAQnC,6BAAeL,KAAK,SAASM,eAAe,EAAEC,KAAK,EAAEC,YAAY,EAAEC,aAAaR,0BAA0B,EAAS;IACjH,MAAMS,eAAeH,MAAMI,KAAK,CAACH,cAAcA,eAAeC;IAC9D,MAAMG,UAAUL,MAAMM,MAAM,GAAGL,eAAeC;IAC9C,MAAMK,YAAYP,MAAMM,MAAM,GAAGL,eAAeC;IAEhD,IAAIF,MAAMM,MAAM,KAAK,GAAG;QACtB,qBACE,KAACf;YAAIiB,aAAa;sBAChB,cAAA,KAAChB;gBAAKiB,QAAQ;0BAAC;;;IAGrB;IAEA,qBACE,MAAClB;QAAImB,eAAc;QAASF,aAAa;;YACtCL,aAAaQ,GAAG,CAAC,CAACC,MAAMC,IACvB,iHAAiH;8BACjH,MAACrB;;wBAA4B;sCACzB,KAACG;sCAAUiB,KAAKE,IAAI,IAAI;;;mBADjBb,eAAeY;YAI3BR,wBACC,MAACb;gBAAKiB,QAAQ;;oBAAC;oBACRF;oBAAU;oBAAuBX,QAAQ,SAAS;oBAAM;;+BAG/D,KAACJ;gBAAKiB,QAAQ;0BAAC;;;;AAIvB,GAAG"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from 'ink';
|
|
3
|
+
import { Fragment, memo } from 'react';
|
|
4
|
+
// ANSI color codes to Ink color mapping
|
|
5
|
+
// Based on standard ANSI SGR (Select Graphic Rendition) parameters
|
|
6
|
+
const ANSI_COLORS = {
|
|
7
|
+
'\x1b[30m': 'black',
|
|
8
|
+
'\x1b[31m': 'red',
|
|
9
|
+
'\x1b[32m': 'green',
|
|
10
|
+
'\x1b[33m': 'yellow',
|
|
11
|
+
'\x1b[34m': 'blue',
|
|
12
|
+
'\x1b[35m': 'magenta',
|
|
13
|
+
'\x1b[36m': 'cyan',
|
|
14
|
+
'\x1b[37m': 'white',
|
|
15
|
+
// Bright colors
|
|
16
|
+
'\x1b[90m': 'gray',
|
|
17
|
+
'\x1b[91m': 'redBright',
|
|
18
|
+
'\x1b[92m': 'greenBright',
|
|
19
|
+
'\x1b[93m': 'yellowBright',
|
|
20
|
+
'\x1b[94m': 'blueBright',
|
|
21
|
+
'\x1b[95m': 'magentaBright',
|
|
22
|
+
'\x1b[96m': 'cyanBright',
|
|
23
|
+
'\x1b[97m': 'whiteBright'
|
|
24
|
+
};
|
|
25
|
+
// ANSI style codes
|
|
26
|
+
const ANSI_STYLES = {
|
|
27
|
+
'\x1b[1m': 'bold',
|
|
28
|
+
'\x1b[2m': 'dim',
|
|
29
|
+
'\x1b[3m': 'italic',
|
|
30
|
+
'\x1b[4m': 'underline',
|
|
31
|
+
'\x1b[9m': 'strikethrough',
|
|
32
|
+
'\x1b[7m': 'inverse'
|
|
33
|
+
};
|
|
34
|
+
// Reset codes
|
|
35
|
+
const RESET_CODES = [
|
|
36
|
+
'\x1b[0m',
|
|
37
|
+
'\x1b[39m',
|
|
38
|
+
'\x1b[49m'
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* Parse a string with ANSI codes into segments with styling information.
|
|
42
|
+
*/ function parseAnsiString(input) {
|
|
43
|
+
const segments = [];
|
|
44
|
+
let currentSegment = {
|
|
45
|
+
text: ''
|
|
46
|
+
};
|
|
47
|
+
let i = 0;
|
|
48
|
+
while(i < input.length){
|
|
49
|
+
// Check for ANSI escape sequence
|
|
50
|
+
if (input[i] === '\x1b' && input[i + 1] === '[') {
|
|
51
|
+
// Find the end of the escape sequence (ends with a letter)
|
|
52
|
+
let j = i + 2;
|
|
53
|
+
while(j < input.length && !/[a-zA-Z]/.test(input[j])){
|
|
54
|
+
j++;
|
|
55
|
+
}
|
|
56
|
+
j++; // Include the letter
|
|
57
|
+
const code = input.slice(i, j);
|
|
58
|
+
// If we have accumulated text, save it
|
|
59
|
+
if (currentSegment.text.length > 0) {
|
|
60
|
+
segments.push(currentSegment);
|
|
61
|
+
currentSegment = {
|
|
62
|
+
text: '',
|
|
63
|
+
...Object.fromEntries(Object.entries(currentSegment).filter(([key])=>key !== 'text').filter(([_, value])=>value !== undefined))
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Apply the style
|
|
67
|
+
if (RESET_CODES.includes(code)) {
|
|
68
|
+
// Reset all styles
|
|
69
|
+
currentSegment = {
|
|
70
|
+
text: ''
|
|
71
|
+
};
|
|
72
|
+
} else if (ANSI_COLORS[code]) {
|
|
73
|
+
currentSegment.color = ANSI_COLORS[code];
|
|
74
|
+
} else if (ANSI_STYLES[code]) {
|
|
75
|
+
const style = ANSI_STYLES[code];
|
|
76
|
+
currentSegment[style] = true;
|
|
77
|
+
}
|
|
78
|
+
i = j;
|
|
79
|
+
} else {
|
|
80
|
+
// Regular character
|
|
81
|
+
currentSegment.text += input[i];
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Add the last segment if it has text
|
|
86
|
+
if (currentSegment.text.length > 0) {
|
|
87
|
+
segments.push(currentSegment);
|
|
88
|
+
}
|
|
89
|
+
return segments;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Component that renders text with ANSI escape codes as styled Ink Text components.
|
|
93
|
+
* Parses ANSI color and style codes and applies them as Ink props.
|
|
94
|
+
*/ export default /*#__PURE__*/ memo(function AnsiText({ children }) {
|
|
95
|
+
// If the input is empty or has no ANSI codes, just render it directly
|
|
96
|
+
if (!children || !children.includes('\x1b')) {
|
|
97
|
+
return /*#__PURE__*/ _jsx(Fragment, {
|
|
98
|
+
children: children
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const segments = parseAnsiString(children);
|
|
102
|
+
// If parsing resulted in a single unstyled segment, render it directly
|
|
103
|
+
if (segments.length === 1 && !segments[0].color && !segments[0].bold && !segments[0].dim && !segments[0].italic && !segments[0].underline && !segments[0].strikethrough && !segments[0].inverse) {
|
|
104
|
+
return /*#__PURE__*/ _jsx(Fragment, {
|
|
105
|
+
children: segments[0].text
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return /*#__PURE__*/ _jsx(Fragment, {
|
|
109
|
+
children: segments.map((segment, index)=>// biome-ignore lint/suspicious/noArrayIndexKey: Segments have no unique ID, index is stable for parsed content
|
|
110
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
111
|
+
color: segment.color,
|
|
112
|
+
bold: segment.bold,
|
|
113
|
+
dimColor: segment.dim,
|
|
114
|
+
italic: segment.italic,
|
|
115
|
+
underline: segment.underline,
|
|
116
|
+
strikethrough: segment.strikethrough,
|
|
117
|
+
inverse: segment.inverse,
|
|
118
|
+
children: segment.text
|
|
119
|
+
}, index))
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/AnsiText.tsx"],"sourcesContent":["import { Text } from 'ink';\nimport { Fragment, memo } from 'react';\n\n// ANSI color codes to Ink color mapping\n// Based on standard ANSI SGR (Select Graphic Rendition) parameters\nconst ANSI_COLORS: Record<string, string> = {\n '\\x1b[30m': 'black',\n '\\x1b[31m': 'red',\n '\\x1b[32m': 'green',\n '\\x1b[33m': 'yellow',\n '\\x1b[34m': 'blue',\n '\\x1b[35m': 'magenta',\n '\\x1b[36m': 'cyan',\n '\\x1b[37m': 'white',\n // Bright colors\n '\\x1b[90m': 'gray',\n '\\x1b[91m': 'redBright',\n '\\x1b[92m': 'greenBright',\n '\\x1b[93m': 'yellowBright',\n '\\x1b[94m': 'blueBright',\n '\\x1b[95m': 'magentaBright',\n '\\x1b[96m': 'cyanBright',\n '\\x1b[97m': 'whiteBright',\n};\n\n// ANSI style codes\nconst ANSI_STYLES: Record<string, string> = {\n '\\x1b[1m': 'bold',\n '\\x1b[2m': 'dim',\n '\\x1b[3m': 'italic',\n '\\x1b[4m': 'underline',\n '\\x1b[9m': 'strikethrough',\n '\\x1b[7m': 'inverse',\n};\n\n// Reset codes\nconst RESET_CODES = ['\\x1b[0m', '\\x1b[39m', '\\x1b[49m'];\n\ntype TextSegment = {\n text: string;\n color?: string;\n bold?: boolean;\n dim?: boolean;\n italic?: boolean;\n underline?: boolean;\n strikethrough?: boolean;\n inverse?: boolean;\n};\n\n/**\n * Parse a string with ANSI codes into segments with styling information.\n */\nfunction parseAnsiString(input: string): TextSegment[] {\n const segments: TextSegment[] = [];\n let currentSegment: TextSegment = { text: '' };\n let i = 0;\n\n while (i < input.length) {\n // Check for ANSI escape sequence\n if (input[i] === '\\x1b' && input[i + 1] === '[') {\n // Find the end of the escape sequence (ends with a letter)\n let j = i + 2;\n while (j < input.length && !/[a-zA-Z]/.test(input[j])) {\n j++;\n }\n j++; // Include the letter\n\n const code = input.slice(i, j);\n\n // If we have accumulated text, save it\n if (currentSegment.text.length > 0) {\n segments.push(currentSegment);\n currentSegment = {\n text: '',\n ...Object.fromEntries(\n Object.entries(currentSegment)\n .filter(([key]) => key !== 'text')\n .filter(([_, value]) => value !== undefined)\n ),\n } as TextSegment;\n }\n\n // Apply the style\n if (RESET_CODES.includes(code)) {\n // Reset all styles\n currentSegment = { text: '' };\n } else if (ANSI_COLORS[code]) {\n currentSegment.color = ANSI_COLORS[code];\n } else if (ANSI_STYLES[code]) {\n const style = ANSI_STYLES[code] as keyof Omit<TextSegment, 'text' | 'color'>;\n currentSegment[style] = true;\n }\n\n i = j;\n } else {\n // Regular character\n currentSegment.text += input[i];\n i++;\n }\n }\n\n // Add the last segment if it has text\n if (currentSegment.text.length > 0) {\n segments.push(currentSegment);\n }\n\n return segments;\n}\n\ntype Props = {\n children: string;\n};\n\n/**\n * Component that renders text with ANSI escape codes as styled Ink Text components.\n * Parses ANSI color and style codes and applies them as Ink props.\n */\nexport default memo(function AnsiText({ children }: Props) {\n // If the input is empty or has no ANSI codes, just render it directly\n if (!children || !children.includes('\\x1b')) {\n return <Fragment>{children}</Fragment>;\n }\n\n const segments = parseAnsiString(children);\n\n // If parsing resulted in a single unstyled segment, render it directly\n if (segments.length === 1 && !segments[0].color && !segments[0].bold && !segments[0].dim && !segments[0].italic && !segments[0].underline && !segments[0].strikethrough && !segments[0].inverse) {\n return <Fragment>{segments[0].text}</Fragment>;\n }\n\n return (\n <Fragment>\n {segments.map((segment, index) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: Segments have no unique ID, index is stable for parsed content\n <Text key={index} color={segment.color} bold={segment.bold} dimColor={segment.dim} italic={segment.italic} underline={segment.underline} strikethrough={segment.strikethrough} inverse={segment.inverse}>\n {segment.text}\n </Text>\n ))}\n </Fragment>\n );\n});\n"],"names":["Text","Fragment","memo","ANSI_COLORS","ANSI_STYLES","RESET_CODES","parseAnsiString","input","segments","currentSegment","text","i","length","j","test","code","slice","push","Object","fromEntries","entries","filter","key","_","value","undefined","includes","color","style","AnsiText","children","bold","dim","italic","underline","strikethrough","inverse","map","segment","index","dimColor"],"mappings":"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"}
|
package/dist/esm/lib/clipText.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/clipText.ts"],"sourcesContent":["import ansiRegex from './ansiRegex.ts';\n\nconst regex = ansiRegex();\n\n/**\n * Get the visible length of a string, ignoring ANSI escape codes.\n */\nexport function visibleLength(str: string): number {\n return str.replace(regex, '').length;\n}\n\n/**\n * Clip text to a maximum visible width, accounting for ANSI escape codes.\n * Adds ellipsis (…) if truncated.\n */\nexport function clipText(str: string, maxWidth: number): string {\n if (maxWidth <= 0) return '';\n if (maxWidth === 1) return '…';\n\n const stripped = str.replace(regex, '');\n if (stripped.length <= maxWidth) return str;\n\n // Need to truncate - walk through and preserve ANSI codes\n let visibleCount = 0;\n let result = '';\n let i = 0;\n\n while (i < str.length && visibleCount < maxWidth - 1) {\n // Check for ANSI escape sequence (ESC [ params m)\n const remaining = str.slice(i);\n // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape requires control char\n const match = remaining.match(/^(\\u001B\\[[0-9;]*m)/);\n\n if (match) {\n // Include the ANSI code in output but don't count toward visible length\n result += match[1];\n i += match[1].length;\n } else {\n // Regular character\n result += str[i];\n visibleCount++;\n i++;\n }\n }\n\n return `${result}…`;\n}\n"],"names":["ansiRegex","regex","visibleLength","str","replace","length","clipText","maxWidth","stripped","visibleCount","result","i","remaining","slice","match"],"mappings":"AAAA,OAAOA,eAAe,iBAAiB;AAEvC,MAAMC,QAAQD;AAEd;;CAEC,GACD,OAAO,SAASE,cAAcC,GAAW;IACvC,OAAOA,IAAIC,OAAO,CAACH,OAAO,IAAII,MAAM;AACtC;AAEA;;;CAGC,GACD,OAAO,SAASC,SAASH,GAAW,EAAEI,QAAgB;IACpD,IAAIA,YAAY,GAAG,OAAO;IAC1B,IAAIA,aAAa,GAAG,OAAO;IAE3B,MAAMC,WAAWL,IAAIC,OAAO,CAACH,OAAO;IACpC,IAAIO,SAASH,MAAM,IAAIE,UAAU,OAAOJ;IAExC,0DAA0D;IAC1D,IAAIM,eAAe;IACnB,IAAIC,SAAS;IACb,IAAIC,IAAI;IAER,MAAOA,IAAIR,IAAIE,MAAM,IAAII,eAAeF,WAAW,EAAG;QACpD,kDAAkD;QAClD,MAAMK,YAAYT,IAAIU,KAAK,CAACF;QAC5B,6FAA6F;QAC7F,MAAMG,QAAQF,UAAUE,KAAK,CAAC;QAE9B,IAAIA,OAAO;YACT,wEAAwE;YACxEJ,UAAUI,KAAK,CAAC,EAAE;YAClBH,KAAKG,KAAK,CAAC,EAAE,CAACT,MAAM;QACtB,OAAO;YACL,oBAAoB;YACpBK,UAAUP,GAAG,CAACQ,EAAE;YAChBF;YACAE;QACF;IACF;IAEA,OAAO,GAAGD,OAAO,
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/clipText.ts"],"sourcesContent":["import ansiRegex from './ansiRegex.ts';\n\nconst regex = ansiRegex();\n\n/**\n * Get the visible length of a string, ignoring ANSI escape codes.\n */\nexport function visibleLength(str: string): number {\n return str.replace(regex, '').length;\n}\n\n/**\n * Clip text to a maximum visible width, accounting for ANSI escape codes.\n * Adds ellipsis (…) if truncated.\n */\nexport function clipText(str: string, maxWidth: number): string {\n if (maxWidth <= 0) return '';\n if (maxWidth === 1) return '…';\n\n const stripped = str.replace(regex, '');\n if (stripped.length <= maxWidth) return str;\n\n // Need to truncate - walk through and preserve ANSI codes\n let visibleCount = 0;\n let result = '';\n let i = 0;\n\n while (i < str.length && visibleCount < maxWidth - 1) {\n // Check for ANSI escape sequence (ESC [ params m)\n const remaining = str.slice(i);\n // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape requires control char\n const match = remaining.match(/^(\\u001B\\[[0-9;]*m)/);\n\n if (match) {\n // Include the ANSI code in output but don't count toward visible length\n result += match[1];\n i += match[1].length;\n } else {\n // Regular character\n result += str[i];\n visibleCount++;\n i++;\n }\n }\n\n return `${result}\\x1b[0m…`;\n}\n"],"names":["ansiRegex","regex","visibleLength","str","replace","length","clipText","maxWidth","stripped","visibleCount","result","i","remaining","slice","match"],"mappings":"AAAA,OAAOA,eAAe,iBAAiB;AAEvC,MAAMC,QAAQD;AAEd;;CAEC,GACD,OAAO,SAASE,cAAcC,GAAW;IACvC,OAAOA,IAAIC,OAAO,CAACH,OAAO,IAAII,MAAM;AACtC;AAEA;;;CAGC,GACD,OAAO,SAASC,SAASH,GAAW,EAAEI,QAAgB;IACpD,IAAIA,YAAY,GAAG,OAAO;IAC1B,IAAIA,aAAa,GAAG,OAAO;IAE3B,MAAMC,WAAWL,IAAIC,OAAO,CAACH,OAAO;IACpC,IAAIO,SAASH,MAAM,IAAIE,UAAU,OAAOJ;IAExC,0DAA0D;IAC1D,IAAIM,eAAe;IACnB,IAAIC,SAAS;IACb,IAAIC,IAAI;IAER,MAAOA,IAAIR,IAAIE,MAAM,IAAII,eAAeF,WAAW,EAAG;QACpD,kDAAkD;QAClD,MAAMK,YAAYT,IAAIU,KAAK,CAACF;QAC5B,6FAA6F;QAC7F,MAAMG,QAAQF,UAAUE,KAAK,CAAC;QAE9B,IAAIA,OAAO;YACT,wEAAwE;YACxEJ,UAAUI,KAAK,CAAC,EAAE;YAClBH,KAAKG,KAAK,CAAC,EAAE,CAACT,MAAM;QACtB,OAAO;YACL,oBAAoB;YACpBK,UAAUP,GAAG,CAACQ,EAAE;YAChBF;YACAE;QACF;IACF;IAEA,OAAO,GAAGD,OAAO,QAAQ,CAAC;AAC5B"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
children: string;
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Component that renders text with ANSI escape codes as styled Ink Text components.
|
|
6
|
+
* Parses ANSI color and style codes and applies them as Ink props.
|
|
7
|
+
*/
|
|
8
|
+
declare const _default: import("react").NamedExoticComponent<Props>;
|
|
9
|
+
export default _default;
|