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