spawn-term 3.5.3 → 3.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/CompactProcessLine.js +20 -6
- package/dist/cjs/components/CompactProcessLine.js.map +1 -1
- package/dist/cjs/lib/clipText.js +1 -1
- package/dist/cjs/lib/clipText.js.map +1 -1
- package/dist/esm/components/CompactProcessLine.js +20 -6
- package/dist/esm/components/CompactProcessLine.js.map +1 -1
- package/dist/esm/lib/clipText.js +1 -1
- package/dist/esm/lib/clipText.js.map +1 -1
- 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) {
|
|
@@ -68,7 +84,7 @@ var _default = /*#__PURE__*/ (0, _react.memo)(function CompactProcessLine(param)
|
|
|
68
84
|
var item = param.item, _param_isSelected = param.isSelected, isSelected = _param_isSelected === void 0 ? false : _param_isSelected;
|
|
69
85
|
var store = (0, _StoreContextts.useStore)();
|
|
70
86
|
var stdout = (0, _ink.useStdout)().stdout;
|
|
71
|
-
var terminalWidth = (stdout === null || stdout === void 0 ? void 0 : stdout.columns) ||
|
|
87
|
+
var terminalWidth = (stdout === null || stdout === void 0 ? void 0 : stdout.columns) || 120;
|
|
72
88
|
var group = item.group, title = item.title, state = item.state, lines = item.lines;
|
|
73
89
|
var selectionIndicator = isSelected ? _figurests.default.pointer : ' ';
|
|
74
90
|
// Display name: prefer group, fall back to title
|
|
@@ -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 ||
|
|
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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"}
|
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"}
|
|
@@ -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) {
|
|
@@ -22,7 +38,7 @@ function getErrorCount(lines) {
|
|
|
22
38
|
export default /*#__PURE__*/ memo(function CompactProcessLine({ item, isSelected = false }) {
|
|
23
39
|
const store = useStore();
|
|
24
40
|
const { stdout } = useStdout();
|
|
25
|
-
const terminalWidth = (stdout === null || stdout === void 0 ? void 0 : stdout.columns) ||
|
|
41
|
+
const terminalWidth = (stdout === null || stdout === void 0 ? void 0 : stdout.columns) || 120;
|
|
26
42
|
const { group, title, state, lines } = item;
|
|
27
43
|
const selectionIndicator = isSelected ? figures.pointer : ' ';
|
|
28
44
|
// Display name: prefer group, fall back to title
|
|
@@ -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 ||
|
|
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":"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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"}
|