feat: pip/rich-style download progress bar (━ filled green, remaining red)

Replace the ASCII '|####|' tqdm bar with a pip/rich bar: heavy-horizontal ━
cells, a ╸ partial cell at the edge, the completed portion in green and the
remaining portion in red, plus 'size/size rate/s eta'. Frames clear the line
with ESC[2K so nothing is left over on narrow terminals.
这个提交包含在:
JGZYES
2026-09-05 21:59:53 +08:00
父节点 a9760f973e
当前提交 500d518d22
+29 -20
查看文件
@@ -148,37 +148,46 @@ static void hms(double sec, char *out, size_t n) {
* 93%|##############--------| 36.1MB/38.8MB [01:30<00:05, 560.7KB/s] */ * 93%|##############--------| 36.1MB/38.8MB [01:30<00:05, 560.7KB/s] */
static void render_progress(unsigned long long fetched, unsigned long long total, static void render_progress(unsigned long long fetched, unsigned long long total,
double elapsed, double speed, int done) { double elapsed, double speed, int done) {
int w = 28; int w = 40; /* bar width in columns */
char line[240]; char line[320]; int li = 0;
if (total == 0) { /* unknown total */ /* erase-line + CR so each frame overwrites cleanly (no leftover, narrow-safe) */
{\
const char *cl = "\033[2K\r";
size_t l = strlen(cl); memcpy(line+li, cl, l); li += (int)l;\
}
if (total == 0) { /* unknown total */
char f[32], sp[32], et[32]; char f[32], sp[32], et[32];
hs(fetched, f, sizeof(f)); hs(fetched, f, sizeof(f));
hs(speed > 0 ? speed : 0, sp, sizeof(sp)); hs(speed > 0 ? speed : 0, sp, sizeof(sp));
hms(elapsed, et, sizeof(et)); hms(elapsed, et, sizeof(et));
snprintf(line, sizeof(line), "\r %s [%s, %s/s]", f, et, sp); int n = snprintf(line+li, sizeof(line)-(size_t)li, " %s [%s, %s/s]", f, et, sp);
if (n > 0) li += n;
} else { } else {
double pct = (double)fetched / (double)total * 100.0; double frac = (double)fetched / (double)total;
int fill = (int)(pct / 100.0 * w); int full = (int)(frac * (double)w); if (full > w) full = w;
char bar[32]; int part = (full < w) && (frac * (double)w - (double)full) > 0.0001;
for (int i = 0; i < w; i++) bar[i] = (i < fill) ? '#' : ' '; char f[32], t[32], sp[32], eta[32];
bar[w] = '\0';
char f[32], t[32], sp[32], e[32], eta[32];
hs(fetched, f, sizeof(f)); hs(fetched, f, sizeof(f));
hs(total, t, sizeof(t)); hs(total, t, sizeof(t));
hs(speed > 0 ? speed : 0, sp, sizeof(sp)); hs(speed > 0 ? speed : 0, sp, sizeof(sp));
hms(elapsed, e, sizeof(e));
double rem = speed > 0 ? ((double)total - (double)fetched) / speed : 0; double rem = speed > 0 ? ((double)total - (double)fetched) / speed : 0;
hms(rem, eta, sizeof(eta)); hms(rem, eta, sizeof(eta));
snprintf(line, sizeof(line), "\r%3.0f%%|%s| %s/%s [%s<%s, %s/s]", /* pip/rich style: filled bar GREEN, partial cell ╸, remaining RED */
pct >= 100.0 ? 100.0 : pct, bar, f, t, e, eta, sp); const char *grn = "\033[32m", *red = "\033[31m", *rst = "\033[0m";
const char *BAR = "\xe2\x94\x81"; /* ━ */
const char *PART = "\xe2\x95\xb8"; /* ╸ */
#define APP(s) do { size_t l = strlen(s); memcpy(line+li, s, l); li += (int)l; } while (0)
APP(grn); for (int i = 0; i < full; i++) APP(BAR); if (part) APP(PART);
APP(rst); APP(red);
int used = full + (part ? 1 : 0);
for (int i = used; i < w; i++) APP(BAR);
APP(rst);
#undef APP
int n = snprintf(line+li, sizeof(line)-(size_t)li, " %s/%s %s/s eta %s", f, t, sp, eta);
if (n > 0) li += n;
} }
/* pad to a fixed width that ALWAYS fits in a normal terminal (~72 cols) so line[li] = '\0';
* the frame overwrites in place; ≥200 spaces would wrap on narrow consoles fputs(line, stderr);
* and that is what makes it look like the bar is spamming many lines. */
int L = (int)strlen(line);
while (L < 72) line[L++] = ' ';
line[L] = '\0';
fprintf(stderr, "%s", line);
if (done) fputc('\n', stderr); if (done) fputc('\n', stderr);
fflush(stderr); fflush(stderr);
} }