recomposable 1.1.5 → 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 +7 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/renderer.js +129 -116
- 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/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,22 +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
|
-
//
|
|
469
|
-
|
|
470
|
-
row =
|
|
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}`;
|
|
471
480
|
}
|
|
472
481
|
buf.push(row);
|
|
473
482
|
break;
|
|
@@ -483,6 +492,7 @@ function renderListView(state) {
|
|
|
483
492
|
return buf.join(exports.CLEAR_EOL + '\n');
|
|
484
493
|
}
|
|
485
494
|
function truncateLine(str, maxWidth) {
|
|
495
|
+
const { reset } = (0, theme_1.getActivePalette)();
|
|
486
496
|
let visPos = 0;
|
|
487
497
|
let rawPos = 0;
|
|
488
498
|
while (rawPos < str.length) {
|
|
@@ -499,7 +509,7 @@ function truncateLine(str, maxWidth) {
|
|
|
499
509
|
}
|
|
500
510
|
}
|
|
501
511
|
if (visPos >= maxWidth) {
|
|
502
|
-
return str.substring(0, rawPos) +
|
|
512
|
+
return str.substring(0, rawPos) + reset;
|
|
503
513
|
}
|
|
504
514
|
visPos++;
|
|
505
515
|
rawPos++;
|
|
@@ -507,6 +517,7 @@ function truncateLine(str, maxWidth) {
|
|
|
507
517
|
return str;
|
|
508
518
|
}
|
|
509
519
|
function highlightSearchInLine(line, query, baseColor) {
|
|
520
|
+
const { reverse, yellow, reset } = (0, theme_1.getActivePalette)();
|
|
510
521
|
if (!query)
|
|
511
522
|
return line;
|
|
512
523
|
const lowerLine = line.toLowerCase();
|
|
@@ -521,7 +532,7 @@ function highlightSearchInLine(line, query, baseColor) {
|
|
|
521
532
|
break;
|
|
522
533
|
}
|
|
523
534
|
result += line.substring(pos, idx);
|
|
524
|
-
result += `${
|
|
535
|
+
result += `${reverse}${yellow}${line.substring(idx, idx + query.length)}${reset}${restore}`;
|
|
525
536
|
pos = idx + query.length;
|
|
526
537
|
}
|
|
527
538
|
return result;
|
|
@@ -536,10 +547,11 @@ function wrapPlainLine(line, width) {
|
|
|
536
547
|
return result;
|
|
537
548
|
}
|
|
538
549
|
function renderLogView(state) {
|
|
550
|
+
const { reset, bold, dim, green, yellow, red } = (0, theme_1.getActivePalette)();
|
|
539
551
|
const columns = process.stdout.columns ?? 80;
|
|
540
552
|
const rows = process.stdout.rows ?? 24;
|
|
541
553
|
const buf = [];
|
|
542
|
-
for (const line of
|
|
554
|
+
for (const line of renderLogo()) {
|
|
543
555
|
buf.push(line);
|
|
544
556
|
}
|
|
545
557
|
buf.push(separatorLine(columns));
|
|
@@ -553,36 +565,36 @@ function renderLogView(state) {
|
|
|
553
565
|
const buildInfo = state.bottomLogLines.get(state.logBuildKey);
|
|
554
566
|
const isBuilding = state.rebuilding.has(state.logBuildKey) || state.cascading.has(state.logBuildKey);
|
|
555
567
|
if (buildInfo && buildInfo.action === 'build_failed') {
|
|
556
|
-
statusLine = ` ${
|
|
568
|
+
statusLine = ` ${red}build failed ${bold}${serviceName}${reset}`;
|
|
557
569
|
}
|
|
558
570
|
else if (isBuilding) {
|
|
559
|
-
statusLine = ` ${
|
|
571
|
+
statusLine = ` ${yellow}rebuilding ${bold}${serviceName}${reset}`;
|
|
560
572
|
}
|
|
561
573
|
else {
|
|
562
|
-
statusLine = ` ${
|
|
574
|
+
statusLine = ` ${green}build logs ${bold}${serviceName}${reset}`;
|
|
563
575
|
}
|
|
564
576
|
}
|
|
565
577
|
else {
|
|
566
|
-
statusLine = ` ${
|
|
578
|
+
statusLine = ` ${green}full logs ${bold}${serviceName}${reset}`;
|
|
567
579
|
}
|
|
568
580
|
let scrollStatus;
|
|
569
581
|
if (state.logAutoScroll) {
|
|
570
|
-
scrollStatus = `${
|
|
582
|
+
scrollStatus = `${green}live${reset}`;
|
|
571
583
|
}
|
|
572
584
|
else {
|
|
573
585
|
const currentLine = Math.max(1, totalLines - state.logScrollOffset);
|
|
574
586
|
const pct = totalLines > 0 ? Math.round((currentLine / totalLines) * 100) : 100;
|
|
575
|
-
scrollStatus = `${
|
|
587
|
+
scrollStatus = `${yellow}paused ${dim}line ${currentLine} / ${totalLines} (${pct}%)${reset}`;
|
|
576
588
|
}
|
|
577
589
|
statusLine += ` ${scrollStatus}`;
|
|
578
590
|
if (state.logSearchPending || state.logHistoryLoading) {
|
|
579
|
-
statusLine += ` ${
|
|
591
|
+
statusLine += ` ${yellow}loading history...${reset}`;
|
|
580
592
|
}
|
|
581
593
|
else if (state.logSearchQuery && state.logSearchMatches.length > 0) {
|
|
582
|
-
statusLine += ` ${
|
|
594
|
+
statusLine += ` ${dim}match ${state.logSearchMatchIdx + 1}/${state.logSearchMatches.length}${reset}`;
|
|
583
595
|
}
|
|
584
596
|
else if (state.logSearchQuery && state.logSearchMatches.length === 0) {
|
|
585
|
-
statusLine += ` ${
|
|
597
|
+
statusLine += ` ${red}no matches${reset}`;
|
|
586
598
|
}
|
|
587
599
|
buf.push(statusLine);
|
|
588
600
|
const bottomReserved = state.logSearchActive ? 1 : 0;
|
|
@@ -596,7 +608,7 @@ function renderLogView(state) {
|
|
|
596
608
|
endLine = Math.max(Math.min(availableRows, totalLines), totalLines - state.logScrollOffset);
|
|
597
609
|
}
|
|
598
610
|
if (totalLines === 0) {
|
|
599
|
-
buf.push(` ${
|
|
611
|
+
buf.push(` ${dim}loading...${reset}`);
|
|
600
612
|
}
|
|
601
613
|
const searchQuery = state.logSearchQuery || '';
|
|
602
614
|
const matchSet = searchQuery ? new Set(state.logSearchMatches) : null;
|
|
@@ -614,7 +626,7 @@ function renderLogView(state) {
|
|
|
614
626
|
segment = highlightSearchInLine(segment, searchQuery, lineColor || undefined);
|
|
615
627
|
}
|
|
616
628
|
if (lineColor) {
|
|
617
|
-
segment = `${lineColor}${segment}${
|
|
629
|
+
segment = `${lineColor}${segment}${reset}`;
|
|
618
630
|
}
|
|
619
631
|
displayLines.push(segment);
|
|
620
632
|
}
|
|
@@ -630,23 +642,24 @@ function renderLogView(state) {
|
|
|
630
642
|
buf.push('');
|
|
631
643
|
}
|
|
632
644
|
if (state.logSearchActive) {
|
|
633
|
-
buf.push(`${
|
|
645
|
+
buf.push(`${bold}/${reset}${state.logSearchQuery}${bold}_${reset}`);
|
|
634
646
|
}
|
|
635
647
|
return buf.join(exports.CLEAR_EOL + '\n');
|
|
636
648
|
}
|
|
637
649
|
function renderExecView(state) {
|
|
650
|
+
const { reset, bold, dim, green, yellow, cyan } = (0, theme_1.getActivePalette)();
|
|
638
651
|
const columns = process.stdout.columns ?? 80;
|
|
639
652
|
const rows = process.stdout.rows ?? 24;
|
|
640
653
|
const buf = [];
|
|
641
|
-
for (const line of
|
|
654
|
+
for (const line of renderLogo()) {
|
|
642
655
|
buf.push(line);
|
|
643
656
|
}
|
|
644
657
|
buf.push(separatorLine(columns));
|
|
645
658
|
buf.push(` ${renderLegend({ execMode: true })}`);
|
|
646
659
|
const serviceName = state.execService || '???';
|
|
647
|
-
const runningIndicator = state.execChild ? `${
|
|
648
|
-
const cwdInfo = state.execCwd ? ` ${
|
|
649
|
-
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}`);
|
|
650
663
|
const headerHeight = buf.length;
|
|
651
664
|
// Reserve 1 line for the prompt at the bottom
|
|
652
665
|
const availableRows = Math.max(1, rows - headerHeight - 1);
|
|
@@ -662,7 +675,7 @@ function renderExecView(state) {
|
|
|
662
675
|
buf.push('');
|
|
663
676
|
}
|
|
664
677
|
// Command prompt
|
|
665
|
-
buf.push(`${
|
|
678
|
+
buf.push(`${green}$ ${reset}${state.execInput}${bold}_${reset}`);
|
|
666
679
|
return buf.join(exports.CLEAR_EOL + '\n');
|
|
667
680
|
}
|
|
668
681
|
//# sourceMappingURL=renderer.js.map
|