agy-powerline 1.3.0 → 1.5.0
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/CHANGELOG.md +55 -1
- package/README.fr.md +87 -32
- package/README.ja.md +87 -32
- package/README.md +89 -34
- package/README.zh-CN.md +87 -32
- package/llms.txt +127 -0
- package/package.json +8 -3
- package/src/cli/configure.js +70 -36
- package/src/cli/doctor.js +17 -1
- package/src/index.js +2 -1
- package/src/lib/catalog.js +21 -0
- package/src/lib/colors.js +17 -14
- package/src/lib/config.js +72 -20
- package/src/lib/fit.js +3 -0
- package/src/lib/formatters.js +107 -4
- package/src/lib/git.js +14 -5
- package/src/lib/layouts/lines.js +87 -22
- package/src/lib/layouts/pills.js +120 -33
- package/src/lib/session.js +49 -1
- package/src/lib/shared.js +2 -1
- package/src/lib/widget-options.js +108 -14
- package/src/lib/widget-style.js +112 -10
package/src/lib/git.js
CHANGED
|
@@ -13,6 +13,15 @@ const SYNC_CACHE_NAME = ".git-sync-cache.json";
|
|
|
13
13
|
const SYNC_CACHE_MAX = 20;
|
|
14
14
|
const COMMIT = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
15
15
|
|
|
16
|
+
// A git call that runs past its time is skipped until a later refresh. Tests
|
|
17
|
+
// give git more time: on a busy CI machine a plain call can take longer.
|
|
18
|
+
const timeouts = { sync: 150, detail: 300 };
|
|
19
|
+
function setGitTimeouts(next) {
|
|
20
|
+
const previous = { ...timeouts };
|
|
21
|
+
Object.assign(timeouts, next);
|
|
22
|
+
return previous;
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
// Only an absolute folder that exists can belong to the user's session. A
|
|
17
26
|
// relative path (a Windows path read on Linux, for one) resolves against
|
|
18
27
|
// wherever this process runs, and the upward walk would then report that
|
|
@@ -392,7 +401,7 @@ function getGitSync(gitDir, branch) {
|
|
|
392
401
|
["rev-list", "--left-right", "--count", `${branch}...${branch}@{upstream}`, "--"],
|
|
393
402
|
{
|
|
394
403
|
cwd: path.dirname(gitDir),
|
|
395
|
-
timeout:
|
|
404
|
+
timeout: timeouts.sync,
|
|
396
405
|
stdio: ["ignore", "pipe", "ignore"],
|
|
397
406
|
encoding: "utf8",
|
|
398
407
|
// Antigravity may run the statusline without a console; without this,
|
|
@@ -414,7 +423,7 @@ function getGitSync(gitDir, branch) {
|
|
|
414
423
|
// A timeout or missing git is worth knowing about when ahead/behind
|
|
415
424
|
// counts never show.
|
|
416
425
|
if (err.code === "ETIMEDOUT" || err.signal === "SIGTERM") {
|
|
417
|
-
logWarning("GIT_TIMEOUT", { timeoutMs:
|
|
426
|
+
logWarning("GIT_TIMEOUT", { timeoutMs: timeouts.sync });
|
|
418
427
|
} else if (err.code === "ENOENT") {
|
|
419
428
|
logWarning("GIT_NOT_FOUND");
|
|
420
429
|
}
|
|
@@ -437,7 +446,6 @@ const DETAIL_NEEDS = {
|
|
|
437
446
|
const STATUS_CACHE_NAME = ".git-status-cache.json";
|
|
438
447
|
const STATUS_CACHE_MAX = 20;
|
|
439
448
|
const STATUS_TTL_MS = 5000;
|
|
440
|
-
const DETAIL_TIMEOUT_MS = 300;
|
|
441
449
|
|
|
442
450
|
// No optional locks: the bar must never hold the index lock and make the
|
|
443
451
|
// user's own git commands fail. The -c flags and the submodule skip keep git
|
|
@@ -559,7 +567,7 @@ function runGitDetail(cwd, args) {
|
|
|
559
567
|
const { execFileSync } = require("child_process");
|
|
560
568
|
return execFileSync("git", args, {
|
|
561
569
|
cwd,
|
|
562
|
-
timeout:
|
|
570
|
+
timeout: timeouts.detail,
|
|
563
571
|
stdio: ["ignore", "pipe", "ignore"],
|
|
564
572
|
encoding: "utf8",
|
|
565
573
|
maxBuffer: 16 * 1024 * 1024,
|
|
@@ -567,7 +575,7 @@ function runGitDetail(cwd, args) {
|
|
|
567
575
|
});
|
|
568
576
|
} catch (err) {
|
|
569
577
|
if (err.code === "ETIMEDOUT" || err.signal === "SIGTERM") {
|
|
570
|
-
logWarning("GIT_TIMEOUT", { timeoutMs:
|
|
578
|
+
logWarning("GIT_TIMEOUT", { timeoutMs: timeouts.detail });
|
|
571
579
|
} else if (err.code === "ENOENT") {
|
|
572
580
|
logWarning("GIT_NOT_FOUND");
|
|
573
581
|
}
|
|
@@ -767,4 +775,5 @@ module.exports = {
|
|
|
767
775
|
getGitDetails,
|
|
768
776
|
resolveGitDetails,
|
|
769
777
|
clearGitDetailsCache,
|
|
778
|
+
setGitTimeouts,
|
|
770
779
|
};
|
package/src/lib/layouts/lines.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const {
|
|
4
4
|
rgb,
|
|
5
5
|
fg,
|
|
6
|
+
mix,
|
|
6
7
|
RESET,
|
|
7
8
|
DIM,
|
|
8
9
|
BOLD,
|
|
@@ -16,6 +17,7 @@ const {
|
|
|
16
17
|
basenameOf,
|
|
17
18
|
homePath,
|
|
18
19
|
formatResetText,
|
|
20
|
+
formatClockTime,
|
|
19
21
|
paceIndex,
|
|
20
22
|
paceGlyph,
|
|
21
23
|
formatGitSync,
|
|
@@ -24,7 +26,8 @@ const {
|
|
|
24
26
|
gitFileParts,
|
|
25
27
|
formatTasks,
|
|
26
28
|
formatMode,
|
|
27
|
-
|
|
29
|
+
limitWidth,
|
|
30
|
+
shortenPath,
|
|
28
31
|
formatTokens,
|
|
29
32
|
formatContextTokens,
|
|
30
33
|
shortModelName,
|
|
@@ -41,10 +44,12 @@ const {
|
|
|
41
44
|
resolveColor,
|
|
42
45
|
widgetColors,
|
|
43
46
|
widgetTag,
|
|
47
|
+
OS_ICONS,
|
|
44
48
|
isRaw,
|
|
45
49
|
isPaced,
|
|
46
50
|
METER_STYLES,
|
|
47
51
|
meterStyle,
|
|
52
|
+
hiddenByThreshold,
|
|
48
53
|
hasNoData,
|
|
49
54
|
RAM_CAUTION,
|
|
50
55
|
RAM_DANGER,
|
|
@@ -143,27 +148,41 @@ function renderDotMeter(
|
|
|
143
148
|
: colorForUsedPct(pct, palette, options));
|
|
144
149
|
|
|
145
150
|
const marker = paceIndex(options.elapsedPct, width);
|
|
146
|
-
const [full, blank] = METER_STYLES[options.style] || METER_STYLES.dots;
|
|
151
|
+
const [full, blank, pace] = METER_STYLES[options.style] || METER_STYLES.dots;
|
|
152
|
+
// A Classic meter is one color: the empty glyph's own texture (░) sets it
|
|
153
|
+
// apart, and the pace marker is a thin line.
|
|
154
|
+
const classic = options.classic === true;
|
|
155
|
+
const track = palette.track || rgb(60, 65, 75);
|
|
156
|
+
// ramp shades each filled cell by its place, from the theme's ok color to
|
|
157
|
+
// caution to danger, over the track; rings keeps the fill's color, faded.
|
|
158
|
+
const ramp = options.style === "ramp";
|
|
159
|
+
const trackColor = ramp
|
|
160
|
+
? track
|
|
161
|
+
: options.style === "rings"
|
|
162
|
+
? mix(color, track, 0.5)
|
|
163
|
+
: classic
|
|
164
|
+
? color
|
|
165
|
+
: track;
|
|
166
|
+
const fillColor = (i) => (ramp ? rampColor(width > 1 ? i / (width - 1) : 0, palette) : color);
|
|
167
|
+
const markerColor = palette.text || LINES.white;
|
|
147
168
|
|
|
148
|
-
if (marker < 0) {
|
|
169
|
+
if (marker < 0 && !ramp) {
|
|
149
170
|
if (ascii) {
|
|
150
171
|
return fg(color) + "#".repeat(filled) + DIM + "-".repeat(empty) + RESET;
|
|
151
172
|
}
|
|
152
|
-
const trackColor = palette.track || rgb(60, 65, 75);
|
|
153
173
|
return fg(color) + full.repeat(filled) + fg(trackColor) + blank.repeat(empty) + RESET;
|
|
154
174
|
}
|
|
155
175
|
|
|
156
176
|
// The marker replaces the dot where the quota window currently is.
|
|
157
|
-
const
|
|
158
|
-
const markerColor = palette.text || LINES.white;
|
|
177
|
+
const markGlyph = ascii ? paceGlyph(true) : classic ? "│" : pace || paceGlyph(false);
|
|
159
178
|
// ASCII empties are dim; NORMAL ends that without dropping a blink.
|
|
160
179
|
const undim = ascii ? NORMAL : "";
|
|
161
180
|
let out = "";
|
|
162
181
|
for (let i = 0; i < width; i++) {
|
|
163
182
|
if (i === marker) {
|
|
164
|
-
out += undim + fg(markerColor) +
|
|
183
|
+
out += undim + fg(markerColor) + markGlyph;
|
|
165
184
|
} else if (i < filled) {
|
|
166
|
-
out += undim + fg(
|
|
185
|
+
out += undim + fg(fillColor(i)) + (ascii ? "#" : full);
|
|
167
186
|
} else {
|
|
168
187
|
out += ascii ? DIM + "-" : fg(trackColor) + blank;
|
|
169
188
|
}
|
|
@@ -171,16 +190,27 @@ function renderDotMeter(
|
|
|
171
190
|
return out + RESET;
|
|
172
191
|
}
|
|
173
192
|
|
|
193
|
+
// A point along the meter, 0 to 1, as a color between ok, caution and danger.
|
|
194
|
+
function rampColor(at, palette) {
|
|
195
|
+
const ok = palette.quotaLow || LINES.green;
|
|
196
|
+
const caution = palette.quotaHigh || LINES.yellow;
|
|
197
|
+
const danger = palette.quotaCrit || LINES.red;
|
|
198
|
+
return at <= 0.5 ? mix(ok, caution, at * 2) : mix(caution, danger, (at - 0.5) * 2);
|
|
199
|
+
}
|
|
200
|
+
|
|
174
201
|
function buildLinesSegment(id, session, palette, emojis, ascii = false, widget = {}, config = {}) {
|
|
175
202
|
// Raw widgets keep only their value: no icon, no label.
|
|
176
203
|
const raw = isRaw(widget, config);
|
|
177
|
-
const tag = (icon) => widgetTag(id, { emojis, raw, icon });
|
|
178
|
-
// Meters: 25
|
|
204
|
+
const tag = (icon) => widgetTag(id, { emojis, raw, icon, widget, ascii });
|
|
205
|
+
// Meters: 25 cells, or 12 when the bar is fitted to a narrow terminal;
|
|
206
|
+
// Classic draws the same cells as blocks.
|
|
207
|
+
const classic = config.layout === "classic";
|
|
179
208
|
const dots = widget?.compact ? 12 : 25;
|
|
180
|
-
const style = meterStyle(widget, config, "lines");
|
|
209
|
+
const style = meterStyle(widget, config, classic ? "classic" : "lines");
|
|
181
210
|
const pace = (quota, thresh) => ({
|
|
182
211
|
...thresh,
|
|
183
212
|
style,
|
|
213
|
+
classic,
|
|
184
214
|
elapsedPct: isPaced(widget, config) ? quota.elapsedPct : null,
|
|
185
215
|
});
|
|
186
216
|
const over200k = session.exceeds200k ? " >200k" : "";
|
|
@@ -240,11 +270,14 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
240
270
|
return { thresh, blinkSeq, color };
|
|
241
271
|
}
|
|
242
272
|
|
|
243
|
-
if (hasNoData(id, session)) {
|
|
273
|
+
if (hasNoData(id, session) || hiddenByThreshold(id, widget, session)) {
|
|
244
274
|
return null;
|
|
245
275
|
}
|
|
246
276
|
|
|
247
277
|
switch (id) {
|
|
278
|
+
case "clock":
|
|
279
|
+
return fg(palette.text) + tag() + formatClockTime(new Date(), config.clock) + RESET;
|
|
280
|
+
|
|
248
281
|
case "model":
|
|
249
282
|
if (!session.model) {
|
|
250
283
|
return null;
|
|
@@ -252,7 +285,11 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
252
285
|
return (
|
|
253
286
|
fg(palette.model) +
|
|
254
287
|
tag() +
|
|
255
|
-
(
|
|
288
|
+
limitWidth(
|
|
289
|
+
widget?.short ? shortModelName(session.model, ascii) : session.model,
|
|
290
|
+
widget,
|
|
291
|
+
ascii
|
|
292
|
+
) +
|
|
256
293
|
RESET
|
|
257
294
|
);
|
|
258
295
|
|
|
@@ -328,6 +365,7 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
328
365
|
...thresh,
|
|
329
366
|
color,
|
|
330
367
|
style,
|
|
368
|
+
classic,
|
|
331
369
|
}) +
|
|
332
370
|
" " +
|
|
333
371
|
fg(color) +
|
|
@@ -362,7 +400,11 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
362
400
|
}
|
|
363
401
|
|
|
364
402
|
case "cwd": {
|
|
365
|
-
const dirname =
|
|
403
|
+
const dirname = limitWidth(
|
|
404
|
+
shortenPath(homePath(session.cwd), widget?.depth, ascii),
|
|
405
|
+
widget,
|
|
406
|
+
ascii
|
|
407
|
+
);
|
|
366
408
|
if (!dirname) {
|
|
367
409
|
return null;
|
|
368
410
|
}
|
|
@@ -370,13 +412,28 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
370
412
|
}
|
|
371
413
|
|
|
372
414
|
case "project": {
|
|
373
|
-
const proj = basenameOf(session.project);
|
|
415
|
+
const proj = limitWidth(basenameOf(session.project), widget, ascii);
|
|
374
416
|
if (!proj) {
|
|
375
417
|
return null;
|
|
376
418
|
}
|
|
377
419
|
return fg(palette.location) + tag() + proj + RESET;
|
|
378
420
|
}
|
|
379
421
|
|
|
422
|
+
case "os": {
|
|
423
|
+
if (!session.os) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
return fg(palette.location) + tag(OS_ICONS[session.os]) + session.os + RESET;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
case "host": {
|
|
430
|
+
if (!session.hostname) {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
const name = session.username ? `${session.username}@${session.hostname}` : session.hostname;
|
|
434
|
+
return fg(palette.location) + tag() + name + RESET;
|
|
435
|
+
}
|
|
436
|
+
|
|
380
437
|
case "git": {
|
|
381
438
|
if (!session.git || !session.git.branch) {
|
|
382
439
|
return null;
|
|
@@ -384,11 +441,12 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
384
441
|
const opStr = widget.operation === false ? "" : formatGitOperation(session.git.operation);
|
|
385
442
|
const syncStr = formatGitSync(session.git.ahead, session.git.behind, ascii);
|
|
386
443
|
const syncPart = syncStr ? ` ${syncStr}` : "";
|
|
444
|
+
const branch = limitWidth(session.git.branch, widget, ascii);
|
|
387
445
|
return (
|
|
388
446
|
fg(palette.git) +
|
|
389
447
|
tag() +
|
|
390
448
|
"(" +
|
|
391
|
-
|
|
449
|
+
branch +
|
|
392
450
|
(session.git.dirty ? `${fg(palette.gitDirty)}*` : "") +
|
|
393
451
|
(opStr ? `${fg(palette.gitDirty)} ${opStr}` : "") +
|
|
394
452
|
(syncPart ? `${fg(palette.git)}${syncPart}` : "") +
|
|
@@ -442,7 +500,9 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
442
500
|
if (!session.gitDetails?.worktree) {
|
|
443
501
|
return null;
|
|
444
502
|
}
|
|
445
|
-
return
|
|
503
|
+
return (
|
|
504
|
+
fg(palette.git) + tag() + limitWidth(session.gitDetails.worktree, widget, ascii) + RESET
|
|
505
|
+
);
|
|
446
506
|
|
|
447
507
|
case "plan":
|
|
448
508
|
if (!session.plan) {
|
|
@@ -479,7 +539,7 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
479
539
|
if (!session.title) {
|
|
480
540
|
return null;
|
|
481
541
|
}
|
|
482
|
-
return fg(palette.text) + tag() +
|
|
542
|
+
return fg(palette.text) + tag() + limitWidth(session.title, widget, ascii, TITLE_MAX) + RESET;
|
|
483
543
|
|
|
484
544
|
case "sandbox": {
|
|
485
545
|
if (!session.sandbox) {
|
|
@@ -576,11 +636,15 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
|
|
|
576
636
|
if (!widget?.text) {
|
|
577
637
|
return null;
|
|
578
638
|
}
|
|
579
|
-
return fg(palette.text) + tag() + widget.text + RESET;
|
|
639
|
+
return fg(palette.text) + tag() + limitWidth(widget.text, widget, ascii) + RESET;
|
|
580
640
|
}
|
|
581
641
|
|
|
582
642
|
case "exec": {
|
|
583
|
-
|
|
643
|
+
// Only real output is cut: the [cmd] preview and blocked markers stay whole.
|
|
644
|
+
const output =
|
|
645
|
+
session.execMode === "run"
|
|
646
|
+
? limitWidth(execWidgetOutput(widget, session), widget, ascii)
|
|
647
|
+
: execWidgetOutput(widget, session);
|
|
584
648
|
if (!output) {
|
|
585
649
|
return null;
|
|
586
650
|
}
|
|
@@ -707,7 +771,7 @@ function renderLinesLayout(session, emojis, ascii = false, colors, config) {
|
|
|
707
771
|
return RESET;
|
|
708
772
|
}
|
|
709
773
|
|
|
710
|
-
const pipe = ` ${DIM}${ascii ? "|" : "│"}${RESET} `;
|
|
774
|
+
const pipe = ` ${DIM}${ascii || config.layout === "classic" ? "|" : "│"}${RESET} `;
|
|
711
775
|
|
|
712
776
|
return (
|
|
713
777
|
rows
|
|
@@ -715,7 +779,8 @@ function renderLinesLayout(session, emojis, ascii = false, colors, config) {
|
|
|
715
779
|
const parts = groups.map((group) => group.join(pipe));
|
|
716
780
|
return joinFlex(parts, session.terminalWidth);
|
|
717
781
|
})
|
|
718
|
-
|
|
782
|
+
// rowGap: a line holding a space, as in Pills, so a host keeps it.
|
|
783
|
+
.join(config.rowGap === true ? "\n \n" : "\n") + RESET
|
|
719
784
|
);
|
|
720
785
|
}
|
|
721
786
|
|
package/src/lib/layouts/pills.js
CHANGED
|
@@ -20,6 +20,7 @@ const { pickGlyph, glyphChar } = require("../powerline");
|
|
|
20
20
|
const {
|
|
21
21
|
quotaColor,
|
|
22
22
|
contextColor,
|
|
23
|
+
formatClockTime,
|
|
23
24
|
resolveQuotaThresholds,
|
|
24
25
|
resolveContextThresholds,
|
|
25
26
|
shouldBlink,
|
|
@@ -34,7 +35,8 @@ const {
|
|
|
34
35
|
gitFileParts,
|
|
35
36
|
formatTasks,
|
|
36
37
|
formatMode,
|
|
37
|
-
|
|
38
|
+
limitWidth,
|
|
39
|
+
shortenPath,
|
|
38
40
|
homePath,
|
|
39
41
|
basenameOf,
|
|
40
42
|
label,
|
|
@@ -48,10 +50,12 @@ const {
|
|
|
48
50
|
resolveColor,
|
|
49
51
|
widgetColors,
|
|
50
52
|
widgetTag,
|
|
53
|
+
OS_ICONS,
|
|
51
54
|
isRaw,
|
|
52
55
|
isPaced,
|
|
53
56
|
METER_STYLES,
|
|
54
57
|
meterStyle,
|
|
58
|
+
hiddenByThreshold,
|
|
55
59
|
hasNoData,
|
|
56
60
|
RAM_CAUTION,
|
|
57
61
|
RAM_DANGER,
|
|
@@ -67,7 +71,12 @@ function fillMeter(pct, width, fillColor, emptyColor, segmentColor, pace = {}) {
|
|
|
67
71
|
const ratio = Math.max(0, Math.min(100, Number(pct))) / 100;
|
|
68
72
|
const used = Math.round(ratio * width);
|
|
69
73
|
const marker = paceIndex(pace.elapsedPct, width);
|
|
70
|
-
|
|
74
|
+
// pips, the default, needs a Unicode font: ASCII mode keeps the cells.
|
|
75
|
+
const pips = pace.style === "pips";
|
|
76
|
+
if (pips && !pace.ascii && getActiveDepth() !== "none") {
|
|
77
|
+
return pipMeter(used, marker, width, fillColor, emptyColor) + bg(segmentColor);
|
|
78
|
+
}
|
|
79
|
+
if (METER_STYLES[pace.style] && !(pips && pace.ascii)) {
|
|
71
80
|
return glyphMeter(used, marker, width, METER_STYLES[pace.style], pace);
|
|
72
81
|
}
|
|
73
82
|
// The fill is a background color; without color it is drawn with glyphs.
|
|
@@ -87,10 +96,30 @@ function fillMeter(pct, width, fillColor, emptyColor, segmentColor, pace = {}) {
|
|
|
87
96
|
return meter + bg(segmentColor);
|
|
88
97
|
}
|
|
89
98
|
|
|
99
|
+
// Small dots on the pill's own color: the filled ones in the light fill color,
|
|
100
|
+
// the rest in the dark track color, the same pair cells use. The pace marker
|
|
101
|
+
// is a larger dot in the other color, so nothing in the meter is a line.
|
|
102
|
+
function pipMeter(used, marker, width, fillColor, emptyColor) {
|
|
103
|
+
let meter = "";
|
|
104
|
+
let color = null;
|
|
105
|
+
|
|
106
|
+
for (let i = 0; i < width; i++) {
|
|
107
|
+
const filled = i < used;
|
|
108
|
+
const next = i === marker ? (filled ? emptyColor : fillColor) : filled ? fillColor : emptyColor;
|
|
109
|
+
if (next !== color) {
|
|
110
|
+
meter += fg(next);
|
|
111
|
+
color = next;
|
|
112
|
+
}
|
|
113
|
+
meter += i === marker ? METER_STYLES.pips[2] : METER_STYLES.pips[0];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return meter;
|
|
117
|
+
}
|
|
118
|
+
|
|
90
119
|
// A glyph style draws on the pill's own color, with no cells: filled glyphs in
|
|
91
120
|
// the pill's text color (readable on the pill, gradients included), empty
|
|
92
121
|
// ones in the same color dimmed. ASCII mode keeps # and -.
|
|
93
|
-
function glyphMeter(used, marker, width, [full, empty], { ascii, textColor }) {
|
|
122
|
+
function glyphMeter(used, marker, width, [full, empty, pace], { ascii, textColor }) {
|
|
94
123
|
let meter = fg(textColor);
|
|
95
124
|
let dim = false;
|
|
96
125
|
|
|
@@ -101,7 +130,7 @@ function glyphMeter(used, marker, width, [full, empty], { ascii, textColor }) {
|
|
|
101
130
|
dim = blank;
|
|
102
131
|
}
|
|
103
132
|
if (i === marker) {
|
|
104
|
-
meter += paceGlyph(ascii);
|
|
133
|
+
meter += (!ascii && pace) || paceGlyph(ascii);
|
|
105
134
|
} else if (ascii) {
|
|
106
135
|
meter += blank ? "-" : "#";
|
|
107
136
|
} else {
|
|
@@ -144,7 +173,7 @@ function pill(color, text, blink = false) {
|
|
|
144
173
|
function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, config = {}) {
|
|
145
174
|
// Raw widgets keep only their value: no icon, no label.
|
|
146
175
|
const raw = isRaw(widget, config);
|
|
147
|
-
const tag = (icon) => widgetTag(id, { emojis, raw, icon });
|
|
176
|
+
const tag = (icon) => widgetTag(id, { emojis, raw, icon, widget, ascii });
|
|
148
177
|
const lbl = (value, icon) => ` ${tag(icon)}${value} `;
|
|
149
178
|
// The pill frames a colored meter; plain-text meters (ASCII) get brackets.
|
|
150
179
|
const open = ascii ? "[" : "";
|
|
@@ -153,18 +182,27 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
153
182
|
const pace = (quota) => (isPaced(widget, config) ? quota.elapsedPct : null);
|
|
154
183
|
const over200k = session.exceeds200k ? " >200k" : "";
|
|
155
184
|
|
|
156
|
-
if (hasNoData(id, session)) {
|
|
185
|
+
if (hasNoData(id, session) || hiddenByThreshold(id, widget, session)) {
|
|
157
186
|
return null;
|
|
158
187
|
}
|
|
159
188
|
|
|
160
189
|
switch (id) {
|
|
190
|
+
case "clock":
|
|
191
|
+
return pill(colors.meta, lbl(formatClockTime(new Date(), config.clock)));
|
|
192
|
+
|
|
161
193
|
case "model":
|
|
162
194
|
if (!session.model) {
|
|
163
195
|
return null;
|
|
164
196
|
}
|
|
165
197
|
return pill(
|
|
166
198
|
colors.model,
|
|
167
|
-
lbl(
|
|
199
|
+
lbl(
|
|
200
|
+
limitWidth(
|
|
201
|
+
widget?.short ? shortModelName(session.model, ascii) : session.model,
|
|
202
|
+
widget,
|
|
203
|
+
ascii
|
|
204
|
+
)
|
|
205
|
+
)
|
|
168
206
|
);
|
|
169
207
|
|
|
170
208
|
case "quota": {
|
|
@@ -455,7 +493,11 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
455
493
|
}
|
|
456
494
|
|
|
457
495
|
case "cwd": {
|
|
458
|
-
const name =
|
|
496
|
+
const name = limitWidth(
|
|
497
|
+
shortenPath(homePath(session.cwd), widget?.depth, ascii),
|
|
498
|
+
widget,
|
|
499
|
+
ascii
|
|
500
|
+
);
|
|
459
501
|
if (!name) {
|
|
460
502
|
return null;
|
|
461
503
|
}
|
|
@@ -463,13 +505,28 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
463
505
|
}
|
|
464
506
|
|
|
465
507
|
case "project": {
|
|
466
|
-
const name = basenameOf(session.project);
|
|
508
|
+
const name = limitWidth(basenameOf(session.project), widget, ascii);
|
|
467
509
|
if (!name) {
|
|
468
510
|
return null;
|
|
469
511
|
}
|
|
470
512
|
return pill(colors.meta, lbl(name));
|
|
471
513
|
}
|
|
472
514
|
|
|
515
|
+
case "os": {
|
|
516
|
+
if (!session.os) {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
return pill(colors.meta, lbl(session.os, OS_ICONS[session.os]));
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
case "host": {
|
|
523
|
+
if (!session.hostname) {
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
const name = session.username ? `${session.username}@${session.hostname}` : session.hostname;
|
|
527
|
+
return pill(colors.meta, lbl(name));
|
|
528
|
+
}
|
|
529
|
+
|
|
473
530
|
case "git": {
|
|
474
531
|
// Antigravity can send vcs.type without a branch; an empty pill says nothing.
|
|
475
532
|
if (!session.git?.branch) {
|
|
@@ -480,9 +537,10 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
480
537
|
const opPart = opStr ? `|${opStr}` : "";
|
|
481
538
|
const syncStr = formatGitSync(session.git.ahead, session.git.behind, ascii);
|
|
482
539
|
const syncPart = syncStr ? ` ${syncStr}` : "";
|
|
540
|
+
const branch = limitWidth(session.git.branch, widget, ascii);
|
|
483
541
|
return pill(
|
|
484
542
|
session.git.dirty ? colors.gitDirty : colors.git,
|
|
485
|
-
lbl(`${mark}${
|
|
543
|
+
lbl(`${mark}${branch}${opPart}${syncPart}`)
|
|
486
544
|
);
|
|
487
545
|
}
|
|
488
546
|
|
|
@@ -520,7 +578,7 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
520
578
|
if (!session.gitDetails?.worktree) {
|
|
521
579
|
return null;
|
|
522
580
|
}
|
|
523
|
-
return pill(colors.git, lbl(session.gitDetails.worktree));
|
|
581
|
+
return pill(colors.git, lbl(limitWidth(session.gitDetails.worktree, widget, ascii)));
|
|
524
582
|
|
|
525
583
|
case "plan":
|
|
526
584
|
if (!session.plan) {
|
|
@@ -565,7 +623,7 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
565
623
|
if (!session.title) {
|
|
566
624
|
return null;
|
|
567
625
|
}
|
|
568
|
-
return pill(colors.meta, lbl(
|
|
626
|
+
return pill(colors.meta, lbl(limitWidth(session.title, widget, ascii, TITLE_MAX)));
|
|
569
627
|
|
|
570
628
|
case "ram": {
|
|
571
629
|
const pct = session.ramPct ?? 0;
|
|
@@ -654,11 +712,15 @@ function buildSegment(id, session, colors, emojis, ascii = false, widget = {}, c
|
|
|
654
712
|
if (!widget?.text) {
|
|
655
713
|
return null;
|
|
656
714
|
}
|
|
657
|
-
return pill(colors.meta, lbl(widget.text));
|
|
715
|
+
return pill(colors.meta, lbl(limitWidth(widget.text, widget, ascii)));
|
|
658
716
|
}
|
|
659
717
|
|
|
660
718
|
case "exec": {
|
|
661
|
-
|
|
719
|
+
// Only real output is cut: the [cmd] preview and blocked markers stay whole.
|
|
720
|
+
const output =
|
|
721
|
+
session.execMode === "run"
|
|
722
|
+
? limitWidth(execWidgetOutput(widget, session), widget, ascii)
|
|
723
|
+
: execWidgetOutput(widget, session);
|
|
662
724
|
if (!output) {
|
|
663
725
|
return null;
|
|
664
726
|
}
|
|
@@ -686,7 +748,6 @@ function buildRows(session, config, colors) {
|
|
|
686
748
|
// The widgets shown in the current row, so a gradient can be spread over
|
|
687
749
|
// exactly those once the row is complete.
|
|
688
750
|
let placed = [];
|
|
689
|
-
const splitEveryWidget = !config.powerline.enabled;
|
|
690
751
|
const gradient = gradientStops(config.gradient);
|
|
691
752
|
|
|
692
753
|
function styledSegment(widget, ownColors) {
|
|
@@ -767,13 +828,8 @@ function buildRows(session, config, colors) {
|
|
|
767
828
|
continue;
|
|
768
829
|
}
|
|
769
830
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
group.push(segment);
|
|
773
|
-
|
|
774
|
-
if (splitEveryWidget) {
|
|
775
|
-
groups.push(group);
|
|
776
|
-
}
|
|
831
|
+
placed.push({ widget, group: current, index: current.length });
|
|
832
|
+
current.push(segment);
|
|
777
833
|
}
|
|
778
834
|
|
|
779
835
|
flushRow();
|
|
@@ -794,20 +850,36 @@ function rightCap(color, terminal, glyph) {
|
|
|
794
850
|
return glyph ? fg(color) + capBackground(terminal) + glyph : "";
|
|
795
851
|
}
|
|
796
852
|
|
|
853
|
+
// Compared as the terminal will draw them: at 256 colors two different colors
|
|
854
|
+
// can land on the same code, and without color everything is the same.
|
|
797
855
|
function sameColor(a, b) {
|
|
798
|
-
return
|
|
856
|
+
return bg(a) === bg(b);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// Each solid Powerline glyph (U+E0B0 to U+E0BE, even code points) has a thin
|
|
860
|
+
// outline twin at the next code point. The round twins are drawn shorter than
|
|
861
|
+
// the cell in many Nerd Fonts, so round joins use a box-drawing line, which
|
|
862
|
+
// spans the full height.
|
|
863
|
+
function thinGlyph(glyph) {
|
|
864
|
+
const code = glyph.codePointAt(0);
|
|
865
|
+
if (glyph.length !== 1 || code < 0xe0b0 || code > 0xe0be || code % 2 !== 0) {
|
|
866
|
+
return glyph;
|
|
867
|
+
}
|
|
868
|
+
return code === 0xe0b4 || code === 0xe0b6 ? "│" : String.fromCodePoint(code + 1);
|
|
799
869
|
}
|
|
800
870
|
|
|
801
871
|
// A separator between two widgets of the same color would vanish, so it is
|
|
802
|
-
// drawn in the text color instead
|
|
872
|
+
// drawn as its thin outline in the text color instead, the way Powerline
|
|
873
|
+
// divides one color. ASCII joins are characters, not shapes, so they take the
|
|
874
|
+
// text color too, which reads on any pill. Inverted separators (meant for
|
|
803
875
|
// left-pointing glyphs) take the next widget's color on the previous one's.
|
|
804
|
-
function joinSegments(previous, next, glyph, invert, textColor) {
|
|
876
|
+
function joinSegments(previous, next, glyph, invert, textColor, ascii = false) {
|
|
805
877
|
if (!glyph) {
|
|
806
878
|
return "";
|
|
807
879
|
}
|
|
808
880
|
|
|
809
|
-
if (sameColor(previous.color, next.color)) {
|
|
810
|
-
return fg(next.textColor || textColor) + bg(next.color) + glyph;
|
|
881
|
+
if (ascii || sameColor(previous.color, next.color)) {
|
|
882
|
+
return fg(next.textColor || textColor) + bg(next.color) + thinGlyph(glyph);
|
|
811
883
|
}
|
|
812
884
|
|
|
813
885
|
return invert
|
|
@@ -846,13 +918,19 @@ function renderMeterSegment(segment, colors, ascii = false) {
|
|
|
846
918
|
|
|
847
919
|
// Caps cycle per pill and separators per join, counted across the whole
|
|
848
920
|
// statusline, so lists with several glyphs alternate from left to right.
|
|
921
|
+
// Glyphs are drawn without bold, which some fonts draw wider or shifted, and
|
|
922
|
+
// the pill ends on the terminal's background so the padding after it stays
|
|
923
|
+
// clear even without an end cap. Without color, solid caps would be blobs of
|
|
924
|
+
// text color, so only ASCII brackets are kept.
|
|
849
925
|
function renderGroup(group, colors, config, counters = { pill: 0, separator: 0 }) {
|
|
850
926
|
const { powerline } = config;
|
|
851
927
|
const ascii = config.ascii === true;
|
|
852
928
|
const terminal = colors.terminal;
|
|
853
929
|
const pillIndex = counters.pill++;
|
|
854
|
-
const
|
|
855
|
-
const
|
|
930
|
+
const caps = ascii || getActiveDepth() !== "none";
|
|
931
|
+
const cap = (list, slot) => (caps ? glyphChar(pickGlyph(list, pillIndex), slot, ascii) : "");
|
|
932
|
+
const startCap = cap(powerline.startCaps, "startCap");
|
|
933
|
+
const endCap = cap(powerline.endCaps, "endCap");
|
|
856
934
|
|
|
857
935
|
let out = leftCap(group[0].color, terminal, startCap);
|
|
858
936
|
|
|
@@ -873,13 +951,22 @@ function renderGroup(group, colors, config, counters = { pill: 0, separator: 0 }
|
|
|
873
951
|
const slot = counters.separator++;
|
|
874
952
|
const glyph = glyphChar(pickGlyph(powerline.separators, slot), "separator", ascii);
|
|
875
953
|
const invert = powerline.separatorInvert[slot % (powerline.separators.length || 1)];
|
|
876
|
-
out +=
|
|
954
|
+
out +=
|
|
955
|
+
NORMAL + joinSegments(segment, group[i + 1], glyph, invert === true, colors.text, ascii);
|
|
877
956
|
}
|
|
878
957
|
}
|
|
879
958
|
|
|
880
|
-
|
|
959
|
+
const last = group[group.length - 1].color;
|
|
960
|
+
return out + NORMAL + (endCap ? rightCap(last, terminal, endCap) : capBackground(terminal));
|
|
961
|
+
}
|
|
881
962
|
|
|
882
|
-
|
|
963
|
+
// With joins off every widget is a pill of its own, one space from the next;
|
|
964
|
+
// the Flex Separator still decides where the row opens up.
|
|
965
|
+
function renderPills(group, colors, config, counters) {
|
|
966
|
+
if (config.powerline.enabled) {
|
|
967
|
+
return renderGroup(group, colors, config, counters);
|
|
968
|
+
}
|
|
969
|
+
return group.map((segment) => renderGroup([segment], colors, config, counters)).join(" ");
|
|
883
970
|
}
|
|
884
971
|
|
|
885
972
|
function joinFlex(parts, terminalWidth) {
|
|
@@ -917,7 +1004,7 @@ function renderPillRows(rows, session, colors, config) {
|
|
|
917
1004
|
return rows
|
|
918
1005
|
.map((row) =>
|
|
919
1006
|
joinFlex(
|
|
920
|
-
row.map((group) =>
|
|
1007
|
+
row.map((group) => renderPills(group, colors, config, counters)),
|
|
921
1008
|
session.terminalWidth
|
|
922
1009
|
)
|
|
923
1010
|
)
|