recomposable 1.1.4 → 1.1.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/README.md +8 -2
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/renderer.js +129 -114
- package/dist/lib/renderer.js.map +1 -1
- package/dist/lib/theme.d.ts +47 -0
- package/dist/lib/theme.js +184 -0
- package/dist/lib/theme.js.map +1 -0
- package/dist/lib/types.d.ts +1 -0
- package/package.json +1 -1
- package/screenshots/demo.gif +0 -0
- package/screenshots/worktree.gif +0 -0
package/dist/lib/renderer.js
CHANGED
|
@@ -18,25 +18,8 @@ exports.wrapPlainLine = wrapPlainLine;
|
|
|
18
18
|
exports.renderLogView = renderLogView;
|
|
19
19
|
exports.renderExecView = renderExecView;
|
|
20
20
|
const state_1 = require("./state");
|
|
21
|
+
const theme_1 = require("./theme");
|
|
21
22
|
const ESC = '\x1b[';
|
|
22
|
-
const RESET = `${ESC}0m`;
|
|
23
|
-
const BOLD = `${ESC}1m`;
|
|
24
|
-
const DIM = `${ESC}2m`;
|
|
25
|
-
const REVERSE = `${ESC}7m`;
|
|
26
|
-
const FG_GREEN = `${ESC}32m`;
|
|
27
|
-
const FG_YELLOW = `${ESC}33m`;
|
|
28
|
-
const FG_RED = `${ESC}31m`;
|
|
29
|
-
const FG_GRAY = `${ESC}90m`;
|
|
30
|
-
const FG_CYAN = `${ESC}36m`;
|
|
31
|
-
const FG_WHITE = `${ESC}37m`;
|
|
32
|
-
const ITALIC = `${ESC}3m`;
|
|
33
|
-
const BG_HIGHLIGHT = `${ESC}48;5;237m`;
|
|
34
|
-
const LOGO = [
|
|
35
|
-
` ${ITALIC}${BOLD}${FG_CYAN}\u250C\u2500\u2510\u250C\u2500\u2510\u250C\u2500\u2510\u250C\u2500\u2510\u250C\u252C\u2510\u250C\u2500\u2510\u250C\u2500\u2510\u250C\u2500\u2510\u250C\u2500\u2510\u250C\u2510 \u252C \u250C\u2500\u2510${RESET}`,
|
|
36
|
-
` ${ITALIC}${BOLD}${FG_CYAN}\u251C\u252C\u2518\u251C\u2524 \u2502 \u2502 \u2502\u2502\u2502\u2502\u251C\u2500\u2518\u2502 \u2502\u2514\u2500\u2510\u251C\u2500\u2524\u251C\u2534\u2510\u2502 \u251C\u2524${RESET}`,
|
|
37
|
-
` ${ITALIC}${BOLD}${FG_CYAN}\u2534\u2514\u2500\u2514\u2500\u2518\u2514\u2500\u2518\u2514\u2500\u2518\u2534 \u2534\u2534 \u2514\u2500\u2518\u2514\u2500\u2518\u2534 \u2534\u2514\u2500\u2518\u2534\u2500\u2518\u2514\u2500\u2518${RESET}`,
|
|
38
|
-
` ${DIM}docker compose manager${RESET}`,
|
|
39
|
-
];
|
|
40
23
|
function visLen(str) {
|
|
41
24
|
return str.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
42
25
|
}
|
|
@@ -50,13 +33,17 @@ function padVisibleStart(str, width) {
|
|
|
50
33
|
}
|
|
51
34
|
exports.CLEAR_EOL = `${ESC}K`;
|
|
52
35
|
exports.CLEAR_EOS = `${ESC}J`;
|
|
53
|
-
|
|
36
|
+
function patternColors() {
|
|
37
|
+
const p = (0, theme_1.getActivePalette)();
|
|
38
|
+
return [p.yellow, p.red, p.cyan, p.magenta];
|
|
39
|
+
}
|
|
54
40
|
function logLineColor(line, patterns) {
|
|
41
|
+
const colors = patternColors();
|
|
55
42
|
let color = null;
|
|
56
43
|
for (let pi = 0; pi < patterns.length; pi++) {
|
|
57
44
|
const group = Array.isArray(patterns[pi]) ? patterns[pi] : [patterns[pi]];
|
|
58
45
|
if (group.some(p => line.includes(p))) {
|
|
59
|
-
color =
|
|
46
|
+
color = colors[pi % colors.length];
|
|
60
47
|
}
|
|
61
48
|
}
|
|
62
49
|
return color;
|
|
@@ -64,6 +51,7 @@ function logLineColor(line, patterns) {
|
|
|
64
51
|
// Cached separator line — recomputed only when terminal width changes
|
|
65
52
|
let cachedSepColumns = 0;
|
|
66
53
|
let cachedSepLine = '';
|
|
54
|
+
let cachedSepGray = '';
|
|
67
55
|
function patternLabel(pattern) {
|
|
68
56
|
return pattern.replace(/^[\[\(\{<]/, '').replace(/[\]\)\}>]$/, '');
|
|
69
57
|
}
|
|
@@ -75,30 +63,33 @@ function parseTimestamp(ts) {
|
|
|
75
63
|
return isNaN(d.getTime()) ? null : d;
|
|
76
64
|
}
|
|
77
65
|
function relativeTime(ts) {
|
|
66
|
+
const { gray, dim, reset } = (0, theme_1.getActivePalette)();
|
|
78
67
|
const date = parseTimestamp(ts);
|
|
79
68
|
if (!date)
|
|
80
|
-
return `${
|
|
69
|
+
return `${gray}-${reset}`;
|
|
81
70
|
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
82
71
|
if (seconds < 0)
|
|
83
|
-
return `${
|
|
72
|
+
return `${gray}-${reset}`;
|
|
84
73
|
if (seconds < 60)
|
|
85
|
-
return `${
|
|
74
|
+
return `${dim}${seconds}s ago${reset}`;
|
|
86
75
|
const minutes = Math.floor(seconds / 60);
|
|
87
76
|
if (minutes < 60)
|
|
88
|
-
return `${
|
|
77
|
+
return `${dim}${minutes}m ago${reset}`;
|
|
89
78
|
const hours = Math.floor(minutes / 60);
|
|
90
79
|
if (hours < 24)
|
|
91
|
-
return `${
|
|
80
|
+
return `${dim}${hours}h ago${reset}`;
|
|
92
81
|
const days = Math.floor(hours / 24);
|
|
93
|
-
return `${
|
|
82
|
+
return `${dim}${days}d ago${reset}`;
|
|
94
83
|
}
|
|
95
84
|
function clearScreen() {
|
|
96
85
|
return `${ESC}H${ESC}?25l`;
|
|
97
86
|
}
|
|
98
87
|
function separatorLine(columns) {
|
|
99
|
-
|
|
88
|
+
const { gray, reset } = (0, theme_1.getActivePalette)();
|
|
89
|
+
if (columns !== cachedSepColumns || gray !== cachedSepGray) {
|
|
100
90
|
cachedSepColumns = columns;
|
|
101
|
-
|
|
91
|
+
cachedSepGray = gray;
|
|
92
|
+
cachedSepLine = ` ${gray}${'\u2500'.repeat(Math.max(0, columns - 2))}${reset}`;
|
|
102
93
|
}
|
|
103
94
|
return cachedSepLine;
|
|
104
95
|
}
|
|
@@ -106,31 +97,33 @@ function showCursor() {
|
|
|
106
97
|
return `${ESC}?25h`;
|
|
107
98
|
}
|
|
108
99
|
function statusIcon(status, isRebuilding, isRestarting, isStopping, isStarting) {
|
|
100
|
+
const { yellow, gray, red, green, reset } = (0, theme_1.getActivePalette)();
|
|
109
101
|
if (isRebuilding || isRestarting || isStopping || isStarting)
|
|
110
|
-
return `${
|
|
102
|
+
return `${yellow}\u25CF${reset}`;
|
|
111
103
|
if (!status)
|
|
112
|
-
return `${
|
|
104
|
+
return `${gray}\u25CB${reset}`;
|
|
113
105
|
const { state, health } = status;
|
|
114
106
|
if (state === 'running') {
|
|
115
107
|
if (health === 'unhealthy')
|
|
116
|
-
return `${
|
|
117
|
-
return `${
|
|
108
|
+
return `${red}\u25CF${reset}`;
|
|
109
|
+
return `${green}\u25CF${reset}`;
|
|
118
110
|
}
|
|
119
111
|
if (state === 'restarting')
|
|
120
|
-
return `${
|
|
121
|
-
return `${
|
|
112
|
+
return `${yellow}\u25CF${reset}`;
|
|
113
|
+
return `${gray}\u25CB${reset}`;
|
|
122
114
|
}
|
|
123
115
|
function statusText(status, isRebuilding, isRestarting, isStopping, isStarting) {
|
|
116
|
+
const { yellow, gray, red, green, dim, reset } = (0, theme_1.getActivePalette)();
|
|
124
117
|
if (isStopping)
|
|
125
|
-
return `${
|
|
118
|
+
return `${yellow}STOPPING...${reset}`;
|
|
126
119
|
if (isStarting)
|
|
127
|
-
return `${
|
|
120
|
+
return `${yellow}STARTING...${reset}`;
|
|
128
121
|
if (isRestarting)
|
|
129
|
-
return `${
|
|
122
|
+
return `${yellow}RESTARTING...${reset}`;
|
|
130
123
|
if (isRebuilding)
|
|
131
|
-
return `${
|
|
124
|
+
return `${yellow}REBUILDING...${reset}`;
|
|
132
125
|
if (!status)
|
|
133
|
-
return `${
|
|
126
|
+
return `${gray}stopped${reset}`;
|
|
134
127
|
const { state, health } = status;
|
|
135
128
|
let text = state;
|
|
136
129
|
if (health && health !== 'none' && health !== '') {
|
|
@@ -138,14 +131,14 @@ function statusText(status, isRebuilding, isRestarting, isStopping, isStarting)
|
|
|
138
131
|
}
|
|
139
132
|
if (state === 'running') {
|
|
140
133
|
if (health === 'unhealthy')
|
|
141
|
-
return `${
|
|
142
|
-
return `${
|
|
134
|
+
return `${red}${text}${reset}`;
|
|
135
|
+
return `${green}${text}${reset}`;
|
|
143
136
|
}
|
|
144
137
|
if (state === 'exited')
|
|
145
|
-
return `${
|
|
138
|
+
return `${gray}${text}${reset}`;
|
|
146
139
|
if (state === 'restarting')
|
|
147
|
-
return `${
|
|
148
|
-
return `${
|
|
140
|
+
return `${yellow}${text}${reset}`;
|
|
141
|
+
return `${dim}${text}${reset}`;
|
|
149
142
|
}
|
|
150
143
|
function formatMem(bytes) {
|
|
151
144
|
if (bytes <= 0)
|
|
@@ -156,12 +149,23 @@ function formatMem(bytes) {
|
|
|
156
149
|
return `${Math.round(bytes / (1024 * 1024))}M`;
|
|
157
150
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}G`;
|
|
158
151
|
}
|
|
152
|
+
function renderLogo() {
|
|
153
|
+
const { italic, bold, dim, reset, logo } = (0, theme_1.getActivePalette)();
|
|
154
|
+
const color = logo || '';
|
|
155
|
+
return [
|
|
156
|
+
` ${color}${italic}${bold}\u250C\u2500\u2510\u250C\u2500\u2510\u250C\u2500\u2510\u250C\u2500\u2510\u250C\u252C\u2510\u250C\u2500\u2510\u250C\u2500\u2510\u250C\u2500\u2510\u250C\u2500\u2510\u250C\u2510 \u252C \u250C\u2500\u2510${reset}`,
|
|
157
|
+
` ${color}${italic}${bold}\u251C\u252C\u2518\u251C\u2524 \u2502 \u2502 \u2502\u2502\u2502\u2502\u251C\u2500\u2518\u2502 \u2502\u2514\u2500\u2510\u251C\u2500\u2524\u251C\u2534\u2510\u2502 \u251C\u2524${reset}`,
|
|
158
|
+
` ${color}${italic}${bold}\u2534\u2514\u2500\u2514\u2500\u2518\u2514\u2500\u2518\u2514\u2500\u2518\u2534 \u2534\u2534 \u2514\u2500\u2518\u2514\u2500\u2518\u2534 \u2534\u2514\u2500\u2518\u2534\u2500\u2518\u2514\u2500\u2518${reset}`,
|
|
159
|
+
` ${dim}docker compose manager${reset}`,
|
|
160
|
+
];
|
|
161
|
+
}
|
|
159
162
|
function renderLegend(opts = {}) {
|
|
163
|
+
const { reverse, dim, reset } = (0, theme_1.getActivePalette)();
|
|
160
164
|
const { logPanelActive = false, logsScrollMode = false, noCacheActive = false, noDepsActive = false, watchActive = false, execMode = false, execInline = false, worktreePickerActive = false } = opts;
|
|
161
165
|
const item = (text, active) => {
|
|
162
166
|
if (active)
|
|
163
|
-
return `${
|
|
164
|
-
return `${
|
|
167
|
+
return `${reverse} ${text} ${reset}`;
|
|
168
|
+
return `${dim}${text}${reset}`;
|
|
165
169
|
};
|
|
166
170
|
if (worktreePickerActive) {
|
|
167
171
|
return [
|
|
@@ -217,12 +221,14 @@ function renderLegend(opts = {}) {
|
|
|
217
221
|
].join(' ');
|
|
218
222
|
}
|
|
219
223
|
function renderListView(state) {
|
|
224
|
+
const { reset, bold, dim, reverse, green, yellow, red, gray, cyan, magenta } = (0, theme_1.getActivePalette)();
|
|
220
225
|
const columns = process.stdout.columns ?? 80;
|
|
221
226
|
const rows = process.stdout.rows ?? 24;
|
|
222
227
|
const patterns = state.config.logScanPatterns || [];
|
|
228
|
+
const pColors = patternColors();
|
|
223
229
|
const sep = separatorLine(columns);
|
|
224
230
|
const buf = [];
|
|
225
|
-
for (const line of
|
|
231
|
+
for (const line of renderLogo()) {
|
|
226
232
|
buf.push(line);
|
|
227
233
|
}
|
|
228
234
|
const watchActive = state.watching.size > 0;
|
|
@@ -234,44 +240,44 @@ function renderListView(state) {
|
|
|
234
240
|
buf.push(sep);
|
|
235
241
|
buf.push(` ${help}`);
|
|
236
242
|
// Single column header row (not repeated per group)
|
|
237
|
-
let colHeader = `${
|
|
243
|
+
let colHeader = `${dim} ${'SERVICE'.padEnd(24)} `;
|
|
238
244
|
colHeader += `${'STATUS'.padEnd(22)} ${'BUILT'.padEnd(12)} ${'RESTARTED'.padEnd(12)}`;
|
|
239
245
|
for (const p of patterns)
|
|
240
246
|
colHeader += patternLabel(Array.isArray(p) ? p[0] : p).padStart(5) + ' ';
|
|
241
247
|
colHeader += ` ${'CPU/MEM'.padStart(16)} ${'PORTS'.padEnd(14)}`;
|
|
242
248
|
if (state.showWorktreeColumn)
|
|
243
249
|
colHeader += ` ${'WORKTREE'.padEnd(15)}`;
|
|
244
|
-
buf.push(colHeader +
|
|
250
|
+
buf.push(colHeader + reset);
|
|
245
251
|
const headerHeight = buf.length;
|
|
246
252
|
const bottomBuf = [];
|
|
247
253
|
if (state.worktreePickerActive) {
|
|
248
254
|
const selEntry = state.flatList[state.cursor];
|
|
249
255
|
if (selEntry) {
|
|
250
256
|
bottomBuf.push(sep);
|
|
251
|
-
bottomBuf.push(` ${
|
|
252
|
-
bottomBuf.push(` ${
|
|
257
|
+
bottomBuf.push(` ${cyan}switch worktree ${bold}${selEntry.service}${reset}`);
|
|
258
|
+
bottomBuf.push(` ${dim}j/k navigate Enter confirm Esc cancel${reset}`);
|
|
253
259
|
for (let wi = 0; wi < state.worktreePickerEntries.length; wi++) {
|
|
254
260
|
const wt = state.worktreePickerEntries[wi];
|
|
255
261
|
const isSelected = wi === state.worktreePickerCursor;
|
|
256
|
-
const prefix = isSelected ? `${
|
|
257
|
-
const suffix = isSelected ? `${
|
|
262
|
+
const prefix = isSelected ? `${reverse}` : '';
|
|
263
|
+
const suffix = isSelected ? `${reset}` : '';
|
|
258
264
|
const currentTag = (state.worktreePickerCurrentPath && state.worktreePickerCurrentPath === wt.path)
|
|
259
|
-
? ` ${
|
|
260
|
-
bottomBuf.push(` ${prefix} ${wt.branch} ${
|
|
265
|
+
? ` ${dim}(current)${reset}` : '';
|
|
266
|
+
bottomBuf.push(` ${prefix} ${wt.branch} ${dim}${wt.path}${reset}${currentTag}${suffix}`);
|
|
261
267
|
}
|
|
262
268
|
}
|
|
263
269
|
}
|
|
264
270
|
else if (state.execActive && state.execService) {
|
|
265
271
|
bottomBuf.push(sep);
|
|
266
|
-
const runningIndicator = state.execChild ? `${
|
|
267
|
-
const cwdInfo = state.execCwd ? ` ${
|
|
268
|
-
bottomBuf.push(` ${
|
|
272
|
+
const runningIndicator = state.execChild ? `${yellow}running${reset}` : `${green}ready${reset}`;
|
|
273
|
+
const cwdInfo = state.execCwd ? ` ${dim}${state.execCwd}${reset}` : '';
|
|
274
|
+
bottomBuf.push(` ${cyan}exec ${bold}${state.execService}${reset} ${runningIndicator}${cwdInfo}`);
|
|
269
275
|
const maxOutputLines = Math.max(1, (state.config.bottomLogCount || 10) - 1);
|
|
270
276
|
const outputStart = Math.max(0, state.execOutputLines.length - maxOutputLines);
|
|
271
277
|
for (let i = outputStart; i < state.execOutputLines.length; i++) {
|
|
272
278
|
bottomBuf.push(truncateLine(` ${state.execOutputLines[i]}`, columns));
|
|
273
279
|
}
|
|
274
|
-
bottomBuf.push(`${
|
|
280
|
+
bottomBuf.push(`${green}$ ${reset}${state.execInput}${bold}_${reset}`);
|
|
275
281
|
}
|
|
276
282
|
else if (state.showBottomLogs) {
|
|
277
283
|
const selEntry = state.flatList[state.cursor];
|
|
@@ -281,23 +287,23 @@ function renderListView(state) {
|
|
|
281
287
|
const cascade = state.cascading.get(sk);
|
|
282
288
|
if (cascade) {
|
|
283
289
|
bottomBuf.push(sep);
|
|
284
|
-
bottomBuf.push(` ${
|
|
290
|
+
bottomBuf.push(` ${yellow}cascading ${bold}${selEntry.service}${reset}`);
|
|
285
291
|
for (let si = 0; si < cascade.steps.length; si++) {
|
|
286
292
|
const step = cascade.steps[si];
|
|
287
293
|
let marker;
|
|
288
294
|
switch (step.status) {
|
|
289
295
|
case 'completed':
|
|
290
|
-
marker = `${
|
|
296
|
+
marker = `${green}[done]${reset}`;
|
|
291
297
|
break;
|
|
292
298
|
case 'in_progress':
|
|
293
|
-
marker = `${
|
|
299
|
+
marker = `${yellow}[>>> ]${reset}`;
|
|
294
300
|
break;
|
|
295
301
|
case 'failed':
|
|
296
|
-
marker = `${
|
|
302
|
+
marker = `${red}[FAIL]${reset}`;
|
|
297
303
|
break;
|
|
298
|
-
default: marker = `${
|
|
304
|
+
default: marker = `${dim}[ ]${reset}`;
|
|
299
305
|
}
|
|
300
|
-
bottomBuf.push(` ${marker} ${step.action} ${
|
|
306
|
+
bottomBuf.push(` ${marker} ${step.action} ${bold}${step.service}${reset}`);
|
|
301
307
|
}
|
|
302
308
|
}
|
|
303
309
|
const info = state.bottomLogLines.get(sk);
|
|
@@ -306,33 +312,33 @@ function renderListView(state) {
|
|
|
306
312
|
bottomBuf.push(sep);
|
|
307
313
|
}
|
|
308
314
|
const isFailed = info.action === 'build_failed' || info.action === 'restart_failed' || info.action === 'stop_failed' || info.action === 'start_failed' || info.action === 'switch_failed';
|
|
309
|
-
const actionColor = isFailed ?
|
|
310
|
-
: info.action === 'rebuilding' || info.action === 'restarting' || info.action === 'stopping' || info.action === 'starting' || info.action === 'cascading' || info.action === 'switching' ?
|
|
311
|
-
: info.action === 'watching' ?
|
|
315
|
+
const actionColor = isFailed ? red
|
|
316
|
+
: info.action === 'rebuilding' || info.action === 'restarting' || info.action === 'stopping' || info.action === 'starting' || info.action === 'cascading' || info.action === 'switching' ? yellow
|
|
317
|
+
: info.action === 'watching' ? cyan : green;
|
|
312
318
|
const actionLabel = isFailed ? info.action.replace('_', ' ').toUpperCase() : info.action;
|
|
313
|
-
let headerLine = ` ${actionColor}${actionLabel} ${
|
|
319
|
+
let headerLine = ` ${actionColor}${actionLabel} ${bold}${info.service}${reset}`;
|
|
314
320
|
const bq = state.bottomSearchQuery || '';
|
|
315
321
|
if (bq && !state.bottomSearchActive) {
|
|
316
322
|
if (state.bottomSearchLoading) {
|
|
317
|
-
headerLine += ` ${
|
|
323
|
+
headerLine += ` ${yellow}searching "${bq}"...${reset}`;
|
|
318
324
|
}
|
|
319
325
|
else {
|
|
320
326
|
const totalMatches = state.bottomSearchTotalMatches;
|
|
321
327
|
headerLine += totalMatches > 0
|
|
322
|
-
? ` ${
|
|
323
|
-
: ` ${
|
|
328
|
+
? ` ${dim}search: "${bq}" (${totalMatches} match${totalMatches !== 1 ? 'es' : ''} in full log)${reset}`
|
|
329
|
+
: ` ${red}search: "${bq}" (no matches)${reset}`;
|
|
324
330
|
}
|
|
325
331
|
}
|
|
326
332
|
bottomBuf.push(headerLine);
|
|
327
333
|
if (info.lines.length === 0 && info.action === 'logs') {
|
|
328
|
-
bottomBuf.push(` ${
|
|
334
|
+
bottomBuf.push(` ${dim}loading...${reset}`);
|
|
329
335
|
}
|
|
330
336
|
const searchQuery = bq && !state.bottomSearchActive ? bq : '';
|
|
331
337
|
const maxBottomLines = state.config.bottomLogCount || 10;
|
|
332
338
|
const visibleLines = info.lines.slice(-maxBottomLines);
|
|
333
339
|
for (const line of visibleLines) {
|
|
334
340
|
let coloredLine = line.substring(0, columns - 4);
|
|
335
|
-
const lineColor = logLineColor(coloredLine, patterns) ||
|
|
341
|
+
const lineColor = logLineColor(coloredLine, patterns) || gray;
|
|
336
342
|
if (searchQuery) {
|
|
337
343
|
const lowerLine = coloredLine.toLowerCase();
|
|
338
344
|
const lowerQ = searchQuery.toLowerCase();
|
|
@@ -346,16 +352,16 @@ function renderListView(state) {
|
|
|
346
352
|
break;
|
|
347
353
|
}
|
|
348
354
|
result += coloredLine.substring(pos, idx);
|
|
349
|
-
result += `${
|
|
355
|
+
result += `${reverse}${yellow}${coloredLine.substring(idx, idx + searchQuery.length)}${reset}${lineColor}`;
|
|
350
356
|
pos = idx + searchQuery.length;
|
|
351
357
|
}
|
|
352
358
|
coloredLine = result;
|
|
353
359
|
}
|
|
354
360
|
}
|
|
355
|
-
bottomBuf.push(` ${lineColor}${coloredLine}${
|
|
361
|
+
bottomBuf.push(` ${lineColor}${coloredLine}${reset}`);
|
|
356
362
|
}
|
|
357
363
|
if (state.bottomSearchActive) {
|
|
358
|
-
bottomBuf.push(`${
|
|
364
|
+
bottomBuf.push(`${bold}/${reset}${state.bottomSearchQuery}${bold}_${reset}`);
|
|
359
365
|
}
|
|
360
366
|
}
|
|
361
367
|
}
|
|
@@ -394,8 +400,8 @@ function renderListView(state) {
|
|
|
394
400
|
break;
|
|
395
401
|
case 'header': {
|
|
396
402
|
const group = state.groups[stub.groupIdx];
|
|
397
|
-
const label = ` ${
|
|
398
|
-
buf.push(group.error ? `${label} ${
|
|
403
|
+
const label = ` ${bold}${group.label}${reset}`;
|
|
404
|
+
buf.push(group.error ? `${label} ${red}(${group.error})${reset}` : label);
|
|
399
405
|
break;
|
|
400
406
|
}
|
|
401
407
|
case 'service': {
|
|
@@ -411,7 +417,7 @@ function renderListView(state) {
|
|
|
411
417
|
const isCascading = state.cascading.has(sk);
|
|
412
418
|
const icon = statusIcon(st, rebuilding || isCascading, restarting, stopping, starting);
|
|
413
419
|
const stext = statusText(st, rebuilding || isCascading, restarting, stopping, starting);
|
|
414
|
-
const watchIndicator = isWatching ? `${
|
|
420
|
+
const watchIndicator = isWatching ? `${cyan}W${reset}` : ' ';
|
|
415
421
|
const wtBranch = st ? st.worktree : null;
|
|
416
422
|
const name = entry.service.padEnd(24);
|
|
417
423
|
const statusPadded = padVisible(stext, 22);
|
|
@@ -424,25 +430,25 @@ function renderListView(state) {
|
|
|
424
430
|
const cpuDanger = state.config.cpuDangerThreshold || 100;
|
|
425
431
|
const memWarn = (state.config.memWarnThreshold || 512) * 1024 * 1024;
|
|
426
432
|
const memDanger = (state.config.memDangerThreshold || 1024) * 1024 * 1024;
|
|
427
|
-
let color =
|
|
433
|
+
let color = dim;
|
|
428
434
|
if (cpu > cpuDanger || mem > memDanger)
|
|
429
|
-
color =
|
|
435
|
+
color = red;
|
|
430
436
|
else if (cpu > cpuWarn || mem > memWarn)
|
|
431
|
-
color =
|
|
437
|
+
color = yellow;
|
|
432
438
|
const cpuText = cpu.toFixed(1) + '%';
|
|
433
439
|
const memText = formatMem(mem);
|
|
434
|
-
cpuMemStr = padVisible(`${color}${cpuText} / ${memText}${
|
|
440
|
+
cpuMemStr = padVisible(`${color}${cpuText} / ${memText}${reset}`, 16);
|
|
435
441
|
}
|
|
436
442
|
else {
|
|
437
|
-
cpuMemStr = padVisible(`${
|
|
443
|
+
cpuMemStr = padVisible(`${dim}-${reset}`, 16);
|
|
438
444
|
}
|
|
439
445
|
let portsStr;
|
|
440
446
|
if (st && st.ports && st.ports.length > 0) {
|
|
441
447
|
const portsText = st.ports.map(p => p.published).join(' ');
|
|
442
|
-
portsStr = padVisible(`${
|
|
448
|
+
portsStr = padVisible(`${dim}${portsText}${reset}`, 14);
|
|
443
449
|
}
|
|
444
450
|
else {
|
|
445
|
-
portsStr = padVisible(`${
|
|
451
|
+
portsStr = padVisible(`${dim}-${reset}`, 14);
|
|
446
452
|
}
|
|
447
453
|
const built = padVisible(relativeTime(st ? st.createdAt : null), 12);
|
|
448
454
|
const restarted = padVisible(relativeTime(st ? st.startedAt : null), 12);
|
|
@@ -452,20 +458,25 @@ function renderListView(state) {
|
|
|
452
458
|
for (let pi = 0; pi < patterns.length; pi++) {
|
|
453
459
|
const key = Array.isArray(patterns[pi]) ? patterns[pi][0] : patterns[pi];
|
|
454
460
|
const count = logCounts ? (logCounts.get(key) || 0) : 0;
|
|
455
|
-
const color = count > 0 ?
|
|
456
|
-
const countText = count > 0 ? `${color}${count}${
|
|
461
|
+
const color = count > 0 ? pColors[pi % pColors.length] : dim;
|
|
462
|
+
const countText = count > 0 ? `${color}${count}${reset}` : `${color}-${reset}`;
|
|
457
463
|
countsStr += padVisibleStart(countText, 5) + ' ';
|
|
458
464
|
}
|
|
459
465
|
let worktreeCol = '';
|
|
460
466
|
if (state.showWorktreeColumn) {
|
|
461
467
|
const wtLabel = (0, state_1.worktreeLabel)(st ? st.worktree : null);
|
|
462
|
-
const wtColor = (wtBranch && wtBranch !== 'main') ?
|
|
463
|
-
worktreeCol = ` ${wtColor}${wtLabel.padEnd(15)}${
|
|
468
|
+
const wtColor = (wtBranch && wtBranch !== 'main') ? yellow : dim;
|
|
469
|
+
worktreeCol = ` ${wtColor}${wtLabel.padEnd(15)}${reset}`;
|
|
464
470
|
}
|
|
465
|
-
let row = ` ${watchIndicator}${icon} ${
|
|
471
|
+
let row = ` ${watchIndicator}${icon} ${bold}${name}${reset} ${statusPadded} ${built} ${restarted}${countsStr} ${cpuMemStr} ${portsStr}${worktreeCol}`;
|
|
466
472
|
if (isSelected) {
|
|
467
|
-
|
|
468
|
-
|
|
473
|
+
const { highlightBg } = (0, theme_1.getActivePalette)();
|
|
474
|
+
// Use explicit bg color for highlight bar so colored text stays readable;
|
|
475
|
+
// strip dim/gray so text pops on the highlight bg; make fg colors bold
|
|
476
|
+
row = row.replace(/\x1b\[2m/g, '').replace(/\x1b\[90m/g, '');
|
|
477
|
+
row = row.replace(/\x1b\[1;3([1-6])m/g, '\x1b[1;3$1m');
|
|
478
|
+
row = row.replace(/\x1b\[3([1-6])m/g, '\x1b[1;3$1m');
|
|
479
|
+
row = `${highlightBg}${bold}${row.replace(/\x1b\[0m/g, `${reset}${highlightBg}${bold}`)}${' '.repeat(Math.max(0, columns - visLen(row)))}${reset}`;
|
|
469
480
|
}
|
|
470
481
|
buf.push(row);
|
|
471
482
|
break;
|
|
@@ -481,6 +492,7 @@ function renderListView(state) {
|
|
|
481
492
|
return buf.join(exports.CLEAR_EOL + '\n');
|
|
482
493
|
}
|
|
483
494
|
function truncateLine(str, maxWidth) {
|
|
495
|
+
const { reset } = (0, theme_1.getActivePalette)();
|
|
484
496
|
let visPos = 0;
|
|
485
497
|
let rawPos = 0;
|
|
486
498
|
while (rawPos < str.length) {
|
|
@@ -497,7 +509,7 @@ function truncateLine(str, maxWidth) {
|
|
|
497
509
|
}
|
|
498
510
|
}
|
|
499
511
|
if (visPos >= maxWidth) {
|
|
500
|
-
return str.substring(0, rawPos) +
|
|
512
|
+
return str.substring(0, rawPos) + reset;
|
|
501
513
|
}
|
|
502
514
|
visPos++;
|
|
503
515
|
rawPos++;
|
|
@@ -505,6 +517,7 @@ function truncateLine(str, maxWidth) {
|
|
|
505
517
|
return str;
|
|
506
518
|
}
|
|
507
519
|
function highlightSearchInLine(line, query, baseColor) {
|
|
520
|
+
const { reverse, yellow, reset } = (0, theme_1.getActivePalette)();
|
|
508
521
|
if (!query)
|
|
509
522
|
return line;
|
|
510
523
|
const lowerLine = line.toLowerCase();
|
|
@@ -519,7 +532,7 @@ function highlightSearchInLine(line, query, baseColor) {
|
|
|
519
532
|
break;
|
|
520
533
|
}
|
|
521
534
|
result += line.substring(pos, idx);
|
|
522
|
-
result += `${
|
|
535
|
+
result += `${reverse}${yellow}${line.substring(idx, idx + query.length)}${reset}${restore}`;
|
|
523
536
|
pos = idx + query.length;
|
|
524
537
|
}
|
|
525
538
|
return result;
|
|
@@ -534,10 +547,11 @@ function wrapPlainLine(line, width) {
|
|
|
534
547
|
return result;
|
|
535
548
|
}
|
|
536
549
|
function renderLogView(state) {
|
|
550
|
+
const { reset, bold, dim, green, yellow, red } = (0, theme_1.getActivePalette)();
|
|
537
551
|
const columns = process.stdout.columns ?? 80;
|
|
538
552
|
const rows = process.stdout.rows ?? 24;
|
|
539
553
|
const buf = [];
|
|
540
|
-
for (const line of
|
|
554
|
+
for (const line of renderLogo()) {
|
|
541
555
|
buf.push(line);
|
|
542
556
|
}
|
|
543
557
|
buf.push(separatorLine(columns));
|
|
@@ -551,36 +565,36 @@ function renderLogView(state) {
|
|
|
551
565
|
const buildInfo = state.bottomLogLines.get(state.logBuildKey);
|
|
552
566
|
const isBuilding = state.rebuilding.has(state.logBuildKey) || state.cascading.has(state.logBuildKey);
|
|
553
567
|
if (buildInfo && buildInfo.action === 'build_failed') {
|
|
554
|
-
statusLine = ` ${
|
|
568
|
+
statusLine = ` ${red}build failed ${bold}${serviceName}${reset}`;
|
|
555
569
|
}
|
|
556
570
|
else if (isBuilding) {
|
|
557
|
-
statusLine = ` ${
|
|
571
|
+
statusLine = ` ${yellow}rebuilding ${bold}${serviceName}${reset}`;
|
|
558
572
|
}
|
|
559
573
|
else {
|
|
560
|
-
statusLine = ` ${
|
|
574
|
+
statusLine = ` ${green}build logs ${bold}${serviceName}${reset}`;
|
|
561
575
|
}
|
|
562
576
|
}
|
|
563
577
|
else {
|
|
564
|
-
statusLine = ` ${
|
|
578
|
+
statusLine = ` ${green}full logs ${bold}${serviceName}${reset}`;
|
|
565
579
|
}
|
|
566
580
|
let scrollStatus;
|
|
567
581
|
if (state.logAutoScroll) {
|
|
568
|
-
scrollStatus = `${
|
|
582
|
+
scrollStatus = `${green}live${reset}`;
|
|
569
583
|
}
|
|
570
584
|
else {
|
|
571
585
|
const currentLine = Math.max(1, totalLines - state.logScrollOffset);
|
|
572
586
|
const pct = totalLines > 0 ? Math.round((currentLine / totalLines) * 100) : 100;
|
|
573
|
-
scrollStatus = `${
|
|
587
|
+
scrollStatus = `${yellow}paused ${dim}line ${currentLine} / ${totalLines} (${pct}%)${reset}`;
|
|
574
588
|
}
|
|
575
589
|
statusLine += ` ${scrollStatus}`;
|
|
576
590
|
if (state.logSearchPending || state.logHistoryLoading) {
|
|
577
|
-
statusLine += ` ${
|
|
591
|
+
statusLine += ` ${yellow}loading history...${reset}`;
|
|
578
592
|
}
|
|
579
593
|
else if (state.logSearchQuery && state.logSearchMatches.length > 0) {
|
|
580
|
-
statusLine += ` ${
|
|
594
|
+
statusLine += ` ${dim}match ${state.logSearchMatchIdx + 1}/${state.logSearchMatches.length}${reset}`;
|
|
581
595
|
}
|
|
582
596
|
else if (state.logSearchQuery && state.logSearchMatches.length === 0) {
|
|
583
|
-
statusLine += ` ${
|
|
597
|
+
statusLine += ` ${red}no matches${reset}`;
|
|
584
598
|
}
|
|
585
599
|
buf.push(statusLine);
|
|
586
600
|
const bottomReserved = state.logSearchActive ? 1 : 0;
|
|
@@ -594,7 +608,7 @@ function renderLogView(state) {
|
|
|
594
608
|
endLine = Math.max(Math.min(availableRows, totalLines), totalLines - state.logScrollOffset);
|
|
595
609
|
}
|
|
596
610
|
if (totalLines === 0) {
|
|
597
|
-
buf.push(` ${
|
|
611
|
+
buf.push(` ${dim}loading...${reset}`);
|
|
598
612
|
}
|
|
599
613
|
const searchQuery = state.logSearchQuery || '';
|
|
600
614
|
const matchSet = searchQuery ? new Set(state.logSearchMatches) : null;
|
|
@@ -612,7 +626,7 @@ function renderLogView(state) {
|
|
|
612
626
|
segment = highlightSearchInLine(segment, searchQuery, lineColor || undefined);
|
|
613
627
|
}
|
|
614
628
|
if (lineColor) {
|
|
615
|
-
segment = `${lineColor}${segment}${
|
|
629
|
+
segment = `${lineColor}${segment}${reset}`;
|
|
616
630
|
}
|
|
617
631
|
displayLines.push(segment);
|
|
618
632
|
}
|
|
@@ -628,23 +642,24 @@ function renderLogView(state) {
|
|
|
628
642
|
buf.push('');
|
|
629
643
|
}
|
|
630
644
|
if (state.logSearchActive) {
|
|
631
|
-
buf.push(`${
|
|
645
|
+
buf.push(`${bold}/${reset}${state.logSearchQuery}${bold}_${reset}`);
|
|
632
646
|
}
|
|
633
647
|
return buf.join(exports.CLEAR_EOL + '\n');
|
|
634
648
|
}
|
|
635
649
|
function renderExecView(state) {
|
|
650
|
+
const { reset, bold, dim, green, yellow, cyan } = (0, theme_1.getActivePalette)();
|
|
636
651
|
const columns = process.stdout.columns ?? 80;
|
|
637
652
|
const rows = process.stdout.rows ?? 24;
|
|
638
653
|
const buf = [];
|
|
639
|
-
for (const line of
|
|
654
|
+
for (const line of renderLogo()) {
|
|
640
655
|
buf.push(line);
|
|
641
656
|
}
|
|
642
657
|
buf.push(separatorLine(columns));
|
|
643
658
|
buf.push(` ${renderLegend({ execMode: true })}`);
|
|
644
659
|
const serviceName = state.execService || '???';
|
|
645
|
-
const runningIndicator = state.execChild ? `${
|
|
646
|
-
const cwdInfo = state.execCwd ? ` ${
|
|
647
|
-
buf.push(` ${
|
|
660
|
+
const runningIndicator = state.execChild ? `${yellow}running${reset}` : `${green}ready${reset}`;
|
|
661
|
+
const cwdInfo = state.execCwd ? ` ${dim}${state.execCwd}${reset}` : '';
|
|
662
|
+
buf.push(` ${cyan}exec ${bold}${serviceName}${reset} ${runningIndicator}${cwdInfo}`);
|
|
648
663
|
const headerHeight = buf.length;
|
|
649
664
|
// Reserve 1 line for the prompt at the bottom
|
|
650
665
|
const availableRows = Math.max(1, rows - headerHeight - 1);
|
|
@@ -660,7 +675,7 @@ function renderExecView(state) {
|
|
|
660
675
|
buf.push('');
|
|
661
676
|
}
|
|
662
677
|
// Command prompt
|
|
663
|
-
buf.push(`${
|
|
678
|
+
buf.push(`${green}$ ${reset}${state.execInput}${bold}_${reset}`);
|
|
664
679
|
return buf.join(exports.CLEAR_EOL + '\n');
|
|
665
680
|
}
|
|
666
681
|
//# sourceMappingURL=renderer.js.map
|