agy-powerline 1.2.0 → 1.4.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.
@@ -1,6 +1,18 @@
1
1
  "use strict";
2
2
 
3
- const { rgb, fg, RESET, DIM, BOLD, NORMAL, BLINK } = require("../colors");
3
+ const {
4
+ rgb,
5
+ fg,
6
+ mix,
7
+ RESET,
8
+ DIM,
9
+ BOLD,
10
+ NORMAL,
11
+ BLINK,
12
+ gradientColors,
13
+ withContrast,
14
+ } = require("../colors");
15
+ const { gradientStops, terminalBackground } = require("../themes");
4
16
  const {
5
17
  basenameOf,
6
18
  homePath,
@@ -8,6 +20,9 @@ const {
8
20
  paceIndex,
9
21
  paceGlyph,
10
22
  formatGitSync,
23
+ formatGitOperation,
24
+ gitChangeParts,
25
+ gitFileParts,
11
26
  formatTasks,
12
27
  formatMode,
13
28
  clipText,
@@ -15,12 +30,13 @@ const {
15
30
  formatContextTokens,
16
31
  shortModelName,
17
32
  formatTokenSplit,
33
+ formatUsage,
18
34
  resolveQuotaThresholds,
19
35
  resolveContextThresholds,
20
36
  shouldBlink,
21
37
  } = require("../formatters");
22
38
  const { joinFlex } = require("./pills");
23
- const { execWidgetOutput } = require("../exec");
39
+ const { execWidgetOutput, restoreAfterReset } = require("../exec");
24
40
  const {
25
41
  NAMED_COLORS,
26
42
  resolveColor,
@@ -28,6 +44,8 @@ const {
28
44
  widgetTag,
29
45
  isRaw,
30
46
  isPaced,
47
+ METER_STYLES,
48
+ meterStyle,
31
49
  hasNoData,
32
50
  RAM_CAUTION,
33
51
  RAM_DANGER,
@@ -126,41 +144,69 @@ function renderDotMeter(
126
144
  : colorForUsedPct(pct, palette, options));
127
145
 
128
146
  const marker = paceIndex(options.elapsedPct, width);
147
+ const [full, blank, pace] = METER_STYLES[options.style] || METER_STYLES.dots;
148
+ // A Classic meter is one color: the empty glyph's own texture (░) sets it
149
+ // apart, and the pace marker is a thin line.
150
+ const classic = options.classic === true;
151
+ const track = palette.track || rgb(60, 65, 75);
152
+ // ramp shades each filled cell by its place, from the theme's ok color to
153
+ // caution to danger, over the track; rings keeps the fill's color, faded.
154
+ const ramp = options.style === "ramp";
155
+ const trackColor = ramp
156
+ ? track
157
+ : options.style === "rings"
158
+ ? mix(color, track, 0.5)
159
+ : classic
160
+ ? color
161
+ : track;
162
+ const fillColor = (i) => (ramp ? rampColor(width > 1 ? i / (width - 1) : 0, palette) : color);
163
+ const markerColor = palette.text || LINES.white;
129
164
 
130
- if (marker < 0) {
165
+ if (marker < 0 && !ramp) {
131
166
  if (ascii) {
132
167
  return fg(color) + "#".repeat(filled) + DIM + "-".repeat(empty) + RESET;
133
168
  }
134
- const trackColor = palette.track || rgb(60, 65, 75);
135
- return fg(color) + "●".repeat(filled) + fg(trackColor) + "●".repeat(empty) + RESET;
169
+ return fg(color) + full.repeat(filled) + fg(trackColor) + blank.repeat(empty) + RESET;
136
170
  }
137
171
 
138
172
  // The marker replaces the dot where the quota window currently is.
139
- const trackColor = palette.track || rgb(60, 65, 75);
140
- const markerColor = palette.text || LINES.white;
173
+ const markGlyph = ascii ? paceGlyph(true) : classic ? "│" : pace || paceGlyph(false);
141
174
  // ASCII empties are dim; NORMAL ends that without dropping a blink.
142
175
  const undim = ascii ? NORMAL : "";
143
176
  let out = "";
144
177
  for (let i = 0; i < width; i++) {
145
178
  if (i === marker) {
146
- out += undim + fg(markerColor) + paceGlyph(ascii);
179
+ out += undim + fg(markerColor) + markGlyph;
147
180
  } else if (i < filled) {
148
- out += undim + fg(color) + (ascii ? "#" : "●");
181
+ out += undim + fg(fillColor(i)) + (ascii ? "#" : full);
149
182
  } else {
150
- out += ascii ? DIM + "-" : fg(trackColor) + "●";
183
+ out += ascii ? DIM + "-" : fg(trackColor) + blank;
151
184
  }
152
185
  }
153
186
  return out + RESET;
154
187
  }
155
188
 
189
+ // A point along the meter, 0 to 1, as a color between ok, caution and danger.
190
+ function rampColor(at, palette) {
191
+ const ok = palette.quotaLow || LINES.green;
192
+ const caution = palette.quotaHigh || LINES.yellow;
193
+ const danger = palette.quotaCrit || LINES.red;
194
+ return at <= 0.5 ? mix(ok, caution, at * 2) : mix(caution, danger, (at - 0.5) * 2);
195
+ }
196
+
156
197
  function buildLinesSegment(id, session, palette, emojis, ascii = false, widget = {}, config = {}) {
157
198
  // Raw widgets keep only their value: no icon, no label.
158
199
  const raw = isRaw(widget, config);
159
200
  const tag = (icon) => widgetTag(id, { emojis, raw, icon });
160
- // Meters: 25 dots, or 12 when the bar is fitted to a narrow terminal.
201
+ // Meters: 25 cells, or 12 when the bar is fitted to a narrow terminal;
202
+ // Classic draws the same cells as blocks.
203
+ const classic = config.layout === "classic";
161
204
  const dots = widget?.compact ? 12 : 25;
205
+ const style = meterStyle(widget, config, classic ? "classic" : "lines");
162
206
  const pace = (quota, thresh) => ({
163
207
  ...thresh,
208
+ style,
209
+ classic,
164
210
  elapsedPct: isPaced(widget, config) ? quota.elapsedPct : null,
165
211
  });
166
212
  const over200k = session.exceeds200k ? " >200k" : "";
@@ -304,7 +350,12 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
304
350
  blinkSeq +
305
351
  fg(palette.text) +
306
352
  tag() +
307
- renderDotMeter(session.contextPct, dots, ascii, palette, false, { ...thresh, color }) +
353
+ renderDotMeter(session.contextPct, dots, ascii, palette, false, {
354
+ ...thresh,
355
+ color,
356
+ style,
357
+ classic,
358
+ }) +
308
359
  " " +
309
360
  fg(color) +
310
361
  `${formatContextTokens(session.usedTokens, session.contextSize)} ` +
@@ -357,6 +408,7 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
357
408
  if (!session.git || !session.git.branch) {
358
409
  return null;
359
410
  }
411
+ const opStr = widget.operation === false ? "" : formatGitOperation(session.git.operation);
360
412
  const syncStr = formatGitSync(session.git.ahead, session.git.behind, ascii);
361
413
  const syncPart = syncStr ? ` ${syncStr}` : "";
362
414
  return (
@@ -365,6 +417,7 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
365
417
  "(" +
366
418
  session.git.branch +
367
419
  (session.git.dirty ? `${fg(palette.gitDirty)}*` : "") +
420
+ (opStr ? `${fg(palette.gitDirty)} ${opStr}` : "") +
368
421
  (syncPart ? `${fg(palette.git)}${syncPart}` : "") +
369
422
  fg(palette.git) +
370
423
  ")" +
@@ -372,6 +425,52 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
372
425
  );
373
426
  }
374
427
 
428
+ case "gitChanges": {
429
+ const parts = gitChangeParts(session.gitDetails?.changes, ascii);
430
+ if (!parts.length) {
431
+ return null;
432
+ }
433
+ const shade = { added: palette.quotaLow, removed: palette.quotaCrit };
434
+ return (
435
+ fg(palette.git) +
436
+ tag() +
437
+ parts.map((part) => fg(shade[part.kind]) + part.text).join(" ") +
438
+ RESET
439
+ );
440
+ }
441
+
442
+ case "gitFiles": {
443
+ const parts = gitFileParts(session.gitDetails?.files, ascii);
444
+ if (!parts.length) {
445
+ return null;
446
+ }
447
+ const shade = (kind) => (kind === "conflicts" ? palette.quotaCrit : palette.git);
448
+ return (
449
+ fg(palette.git) +
450
+ tag() +
451
+ parts.map((part) => fg(shade(part.kind)) + part.text).join(" ") +
452
+ RESET
453
+ );
454
+ }
455
+
456
+ case "gitSha":
457
+ if (!session.gitDetails?.sha) {
458
+ return null;
459
+ }
460
+ return fg(palette.git) + tag() + session.gitDetails.sha + RESET;
461
+
462
+ case "gitStash":
463
+ if (!(session.gitDetails?.stash > 0)) {
464
+ return null;
465
+ }
466
+ return fg(palette.git) + tag() + session.gitDetails.stash + RESET;
467
+
468
+ case "gitWorktree":
469
+ if (!session.gitDetails?.worktree) {
470
+ return null;
471
+ }
472
+ return fg(palette.git) + tag() + session.gitDetails.worktree + RESET;
473
+
375
474
  case "plan":
376
475
  if (!session.plan) {
377
476
  return null;
@@ -474,6 +573,32 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
474
573
  RESET
475
574
  );
476
575
 
576
+ case "tokensToday":
577
+ case "tokensWeek": {
578
+ const usage = formatUsage(
579
+ session.usage?.[id === "tokensToday" ? "today" : "week"],
580
+ widget,
581
+ ascii
582
+ );
583
+ if (!usage) {
584
+ return null;
585
+ }
586
+ // A budget alerts like the context widgets: caution, then danger.
587
+ const thresh = resolveContextThresholds(widget, config);
588
+ const level =
589
+ usage.pct === null
590
+ ? "normal"
591
+ : usage.pct >= thresh.crit
592
+ ? "crit"
593
+ : usage.pct >= thresh.warn
594
+ ? "warn"
595
+ : "normal";
596
+ const color =
597
+ level === "crit" ? palette.quotaCrit : level === "warn" ? palette.quotaHigh : palette.text;
598
+ const blinkSeq = !ascii && shouldBlink(level, thresh.blink) ? BLINK : "";
599
+ return blinkSeq + fg(palette.text) + tag() + fg(color) + usage.text + RESET;
600
+ }
601
+
477
602
  case "customText": {
478
603
  if (!widget?.text) {
479
604
  return null;
@@ -491,7 +616,7 @@ function buildLinesSegment(id, session, palette, emojis, ascii = false, widget =
491
616
  // buildLinesRows already swapped the widget's color into the palette.
492
617
  const col = palette.location;
493
618
  const iconPart = emojis && icon && !raw ? `${icon} ` : "";
494
- return fg(col) + iconPart + named + output + RESET;
619
+ return fg(col) + iconPart + named + restoreAfterReset(output, fg(col)) + RESET;
495
620
  }
496
621
 
497
622
  default:
@@ -530,10 +655,22 @@ function buildLinesRows(session, config, palette, emojis, ascii = false, colors
530
655
  rowDefs.push(currentRowDef);
531
656
 
532
657
  const rows = [];
658
+ const gradient = colors ? gradientStops(config?.gradient) : null;
659
+ const background = terminalBackground(colors);
660
+
661
+ function renderItem(widget, widgetPal) {
662
+ const item = buildLinesSegment(widget.id, session, widgetPal, emojis, ascii, widget, config);
663
+ // Bold has to survive the resets inside the segment.
664
+ return item && widget.bold === true
665
+ ? BOLD + item.split(RESET).join(RESET + BOLD) + RESET
666
+ : item;
667
+ }
533
668
 
534
669
  for (const def of rowDefs) {
535
670
  const groups = [];
536
671
  let currentGroup = [];
672
+ // The widgets shown in this row, so a gradient spreads over exactly those.
673
+ const placed = [];
537
674
 
538
675
  function flushGroup() {
539
676
  if (currentGroup.length) {
@@ -552,26 +689,35 @@ function buildLinesRows(session, config, palette, emojis, ascii = false, colors
552
689
  continue;
553
690
  }
554
691
 
555
- const item = buildLinesSegment(
556
- widget.id,
557
- session,
558
- widgetPalette(widget, palette, colors),
559
- emojis,
560
- ascii,
561
- widget,
562
- config
563
- );
692
+ const item = renderItem(widget, widgetPalette(widget, palette, colors));
564
693
 
565
694
  if (item) {
566
- // Bold has to survive the resets inside the segment.
567
- currentGroup.push(
568
- widget.bold === true ? BOLD + item.split(RESET).join(RESET + BOLD) + RESET : item
569
- );
695
+ placed.push({ widget, group: currentGroup, index: currentGroup.length });
696
+ currentGroup.push(item);
570
697
  }
571
698
  }
572
699
 
573
700
  flushGroup();
574
701
 
702
+ // Each widget's text takes its step of the gradient, made readable on the
703
+ // terminal background; alert colors and a widget's own color stay.
704
+ if (gradient && placed.length) {
705
+ const shades = gradientColors(gradient, placed.length);
706
+ placed.forEach(({ widget, group, index }, i) => {
707
+ if (resolveColor(widget.color, colors)) {
708
+ return;
709
+ }
710
+ const shade = withContrast(shades[i], background);
711
+ const item = renderItem(
712
+ widget,
713
+ widgetPalette({ ...widget, color: shade }, palette, colors)
714
+ );
715
+ if (item) {
716
+ group[index] = item;
717
+ }
718
+ });
719
+ }
720
+
575
721
  if (groups.length) {
576
722
  rows.push(groups);
577
723
  }
@@ -588,7 +734,7 @@ function renderLinesLayout(session, emojis, ascii = false, colors, config) {
588
734
  return RESET;
589
735
  }
590
736
 
591
- const pipe = ` ${DIM}${ascii ? "|" : "│"}${RESET} `;
737
+ const pipe = ` ${DIM}${ascii || config.layout === "classic" ? "|" : "│"}${RESET} `;
592
738
 
593
739
  return (
594
740
  rows
@@ -596,7 +742,8 @@ function renderLinesLayout(session, emojis, ascii = false, colors, config) {
596
742
  const parts = groups.map((group) => group.join(pipe));
597
743
  return joinFlex(parts, session.terminalWidth);
598
744
  })
599
- .join("\n") + RESET
745
+ // rowGap: a line holding a space, as in Pills, so a host keeps it.
746
+ .join(config.rowGap === true ? "\n \n" : "\n") + RESET
600
747
  );
601
748
  }
602
749