3 * Progress bars for terminal programs
5 * (c) 2022 Mark Wooding
8 /*----- Licensing notice --------------------------------------------------*
10 * This library is free software: you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or (at your
13 * option) any later version.
15 * This library is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
18 * License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this library. If not, see <https://www.gnu.org/licenses/>.
26 /*----- Header files ------------------------------------------------------*/
36 #include <sys/ioctl.h>
38 #if defined(USE_TERMINFO)
41 #elif defined(USE_TERMCAP)
45 #include "multiprogress.h"
47 /*----- Progress state lifecycle ------------------------------------------*/
49 static FILE *dup_stream(int fd
)
50 /* Return a bidirectional `stdio' stream based on a duplicate of the
51 * file descriptor FD. (That way, we can safely `fclose' the copy
52 * without messing up the original descriptor, e.g., in the case
53 * where FD is standard output.)
59 newfd
= dup(fd
); if (newfd
< 0) return (0);
60 fp
= fdopen(newfd
, "r+"); if (!fp
) return (0);
64 int progress_init(struct progress_state
*progress
)
72 struct progress_ttyinfo
*tty
;
76 /* Clear the progress state. */
79 tty
->termbuf
= tty
->capbuf
= 0;
81 tty
->cap
.cr
= tty
->cap
.up
= tty
->cap
.ce
= tty
->cap
.cd
=
82 tty
->cap
.mr
= tty
->cap
.md
= tty
->cap
.me
=
83 tty
->cap
.af
= tty
->cap
.ab
= tty
->cap
.op
= 0;
85 progress
->items
= progress
->end_item
= 0;
86 progress
->nitems
= 0; progress
->last_lines
= 0;
87 progress
->tv_update
.tv_sec
= 0; progress
->tv_update
.tv_usec
= 0;
89 /* Determine a suitable terminal. Use a copy of stdout or stderr if they
90 * look like terminals; otherwise, try to open `/dev/tty'. If nothing
91 * works, then give up.
93 if (isatty(1)) tty
->fp
= dup_stream(1);
94 else if (isatty(2)) tty
->fp
= dup_stream(2);
95 else tty
->fp
= fopen("/dev/tty", "r+");
96 if (!tty
->fp
) return (-1);
98 /* Determine the terminal width or height. DIM is `defwd' or `defht'
99 * accordingly; VAR names the environment variable which might have the
100 * relevant dimension; GETCAP is an expression to retrieve the default
101 * dimension from the capability database or return -1 if it's not there,
102 * and DFLT is a fallback default in case the database comes up short.
104 * Note that we try to use `TIOCGWINSZ' to get the size, so this is all
105 * part of an overly elaborate plan B.
107 #define SETDIM(dim, var, getcap, dflt) do { \
108 t = getenv(var); if (t) { n = atoi(t); if (n) { tty->dim = n; break; } } \
109 n = getcap; if (n > 0) { tty->dim = n; break; } \
113 /* Determine the terminal capabilities and size. */
114 #if defined(USE_TERMINFO)
115 /* Use `terminfo'. This is better than `termcap', but not by much. In
116 * particular, there's still a global current terminal-capabilities record
117 * which we'll clobber.
120 /* Look up the terminal. */
121 if (setupterm(0, fileno(tty
->fp
), &err
) != OK
|| err
< 1) return (-1);
123 /* Basic cursor motion and erasure. */
124 tty
->cap
.cr
= tigetstr("cr");
125 tty
->cap
.nw
= tigetstr("nel");
126 tty
->cap
.up
= tigetstr("cuu1");
127 tty
->cap
.ce
= tigetstr("el");
128 tty
->cap
.cd
= tigetstr("ed");
130 if (tigetnum("xmc") < 1) {
131 /* No magic cookies, so check on the fancy highlighting. */
133 tty
->cap
.mr
= tigetstr("rev");
134 tty
->cap
.md
= tigetstr("bold");
135 tty
->cap
.me
= tigetstr("sgr0");
137 tty
->cap
.af
= tigetstr("setaf");
138 tty
->cap
.ab
= tigetstr("setab");
139 tty
->cap
.op
= tigetstr("op");
142 /* Find out whether erasure uses the current background colour. */
143 if (tigetflag("bce") > 0) tty
->cap
.f
|= TCF_BCE
;
145 /* Set the terminal size. */
146 SETDIM(defwd
, "COLUMNS", tigetnum("co"), 80);
147 SETDIM(defht
, "LINES", tigetnum("li"), 25);
149 #elif defined(USE_TERMCAP)
150 /* Use `termcap'. This is remarkably awful, really. We must guess at an
151 * upper bound on the size of a termcap string; memory is cheap now, so
152 * I've doubled the traditional size here. `tgetent' establishes a global
153 * current capability string, so we've clobbered that. And
156 /* Look up the terminal. */
157 tty
->termbuf
= malloc(4096); if (!tty
->termbuf
) return (-1);
158 tty
->capbuf
= malloc(4096); if (!tty
->capbuf
) return (-1);
159 term
= getenv("TERM"); if (!term
) return (-1);
160 if (tgetent(tty
->termbuf
, term
) < 1) return (-1);
161 capcur
= tty
->capbuf
;
163 /* Basic cursor motion and erasure. */
164 tty
->cap
.cr
= tgetstr("cr", &capcur
);
165 tty
->cap
.nw
= tgetstr("nw", &capcur
);
166 tty
->cap
.up
= tgetstr("up", &capcur
);
167 tty
->cap
.ce
= tgetstr("ce", &capcur
);
168 tty
->cap
.cd
= tgetstr("cd", &capcur
);
170 if (tgetnum("sg") < 1) {
171 /* No magic cookies, so check on the fancy highlighting. */
173 tty
->cap
.mr
= tgetstr("mr", &capcur
);
174 tty
->cap
.md
= tgetstr("md", &capcur
);
175 tty
->cap
.me
= tgetstr("me", &capcur
);
177 tty
->cap
.af
= tgetstr("AF", &capcur
);
178 tty
->cap
.ab
= tgetstr("AB", &capcur
);
179 tty
->cap
.op
= tgetstr("op", &capcur
);
182 /* Find out whether erasure uses the current background colour. */
183 if (tgetflag("ut") > 0) tty
->cap
.f
|= TCF_BCE
;
185 /* Set the pad character correctly. */
186 t
= tgetstr("pc", &capcur
); tty
->cap
.pc
= t ?
*t
: 0;
188 /* Set the terminal size. */
189 SETDIM(defwd
, "COLUMNS", tgetnum("co"), 80);
190 SETDIM(defht
, "LINES", tgetnum("li"), 25);
193 /* Nothing to do. Take a wild guess at the terminal size. */
195 SETDIM(defwd
, "COLUMNS", -1, 80);
196 SETDIM(defht
, "LINES", -1, 25);
202 /* Fill in default motion. These are frequently omitted from capability
205 if (!tty
->cap
.cr
) tty
->cap
.cr
= "\r";
206 if (!tty
->cap
.nw
) tty
->cap
.nw
= "\r\n";
208 /* If the terminal can't do the necessary motion and erasure then give up
211 if (!tty
->cap
.up
|| !tty
->cap
.ce
|| !tty
->cap
.cd
)
212 { fclose(tty
->fp
); tty
->fp
= 0; return (-1); }
214 /* If the terminal can't do all of the colour stuff we want, then clear
217 if (!tty
->cap
.af
|| !tty
->cap
.ab
|| !tty
->cap
.op
) tty
->cap
.op
= 0;
219 /* If the terminal can't return to normal, then clear bold and
222 if (!tty
->cap
.me
) tty
->cap
.mr
= tty
->cap
.md
= 0;
224 /* Turn on full buffering. We take responsibility for forcing output at
227 setvbuf(tty
->fp
, 0, _IOFBF
, BUFSIZ
);
233 void progress_free(struct progress_state
*progress
)
235 struct progress_ttyinfo
*tty
= &progress
->tty
;
237 if (tty
->fp
) { fclose(tty
->fp
); tty
->fp
= 0; }
238 free(tty
->termbuf
); tty
->termbuf
= 0;
239 free(tty
->capbuf
); tty
->capbuf
= 0;
242 /*----- Active item list maintenance --------------------------------------*/
244 int progress_additem(struct progress_state
*progress
,
245 struct progress_item
*item
)
247 if (item
->parent
) return (-1);
248 item
->prev
= progress
->end_item
; item
->next
= 0;
249 if (progress
->end_item
) progress
->end_item
->next
= item
;
250 else progress
->items
= item
;
251 progress
->end_item
= item
; item
->parent
= progress
;
257 int progress_removeitem(struct progress_state
*progress
,
258 struct progress_item
*item
)
260 if (!item
->parent
) return (-1);
261 if (item
->next
) item
->next
->prev
= item
->prev
;
262 else (progress
->end_item
) = item
->prev
;
263 if (item
->prev
) item
->prev
->next
= item
->next
;
264 else (progress
->items
) = item
->next
;
265 progress
->nitems
--; item
->parent
= 0;
270 /*----- Render state lifecycle --------------------------------------------*/
272 static void setup_render_state(struct progress_state
*progress
,
273 struct progress_render_state
*render
)
275 const struct progress_ttyinfo
*tty
= &progress
->tty
;
278 /* Clear everything. */
280 render
->linebuf
= 0; render
->linesz
= 0;
281 render
->tempbuf
= 0; render
->tempsz
= 0;
284 /* Save old `termcap' globals. We'll restore them in `free_render_state'.
286 render
->old_bc
= BC
; BC
= 0;
287 render
->old_up
= UP
; UP
= 0;
288 render
->old_pc
= PC
; PC
= tty
->cap
.pc
;
291 /* Determine the actual terminal size. Fall back on the default we
292 * established in `progress_init' if the kernel doesn't know.
294 if (!ioctl(fileno(tty
->fp
), TIOCGWINSZ
, &wsz
))
295 { render
->width
= wsz
.ws_col
; render
->height
= wsz
.ws_row
; }
297 { render
->width
= tty
->defwd
; render
->height
= tty
->defht
; }
299 /* We'll render progress bars with colour or standout if we can; otherwise,
300 * we'll just insert a `|' in the right place, but that takes up an extra
301 * column, so deduct one from the terminal's width to compensate.
303 if (render
->width
&& !tty
->cap
.op
&& !tty
->cap
.mr
) render
->width
--;
306 static void free_render_state(struct progress_render_state
*render
)
308 /* Send accumulated output to the terminal. */
309 fflush(render
->tty
->fp
);
311 /* Free the buffers. */
312 free(render
->linebuf
); render
->linebuf
= 0; render
->linesz
= 0;
313 free(render
->tempbuf
); render
->tempbuf
= 0; render
->tempsz
= 0;
316 /* Restore the `termcap' globals. */
323 /*----- Measuring string widths -------------------------------------------*/
325 #define CONV_MORE ((size_t)-2)
326 #define CONV_BAD ((size_t)-1)
329 mbstate_t ps
; /* conversion state */
330 const char *p
; size_t i
, sz
; /* input string, and cursor */
331 unsigned wd
; /* width accumulated so far */
334 static void init_measure(struct measure
*m
, const char *p
, size_t sz
)
335 /* Set up M to measure the SZ-byte string P. */
337 m
->p
= p
; m
->sz
= sz
; m
->i
= 0; m
->wd
= 0;
338 memset(&m
->ps
, 0, sizeof(m
->ps
));
341 static int advance_measure(struct measure
*m
)
342 /* Advance the measurement in M by one character. Return zero if the
343 * end of the string has been reached, or nonzero if there is more to
351 /* Determine the next character's code WCH, the length N of its encoding in P
352 * in bytes, and the character's width CHWD in columns.
354 n
= mbrtowc(&wch
, m
->p
+ m
->i
, m
->sz
- m
->i
, &m
->ps
);
355 if (!n
) { chwd
= 0; n
= m
->sz
- m
->i
; }
356 else if (n
== CONV_MORE
) { chwd
= 2; n
= m
->sz
- m
->i
; }
357 else if (n
== CONV_BAD
) { chwd
= 2; n
= 1; }
358 else chwd
= wcwidth(wch
);
360 /* Advance the state. */
361 m
->i
+= n
; m
->wd
+= chwd
;
363 /* Report whether there's more to come. */
364 return (m
->i
< m
->sz
);
367 static unsigned string_width(const char *p
, size_t sz
)
368 /* Return the width of the SZ-byte string P, in terminal columns. */
372 init_measure(&m
, p
, sz
);
373 while (advance_measure(&m
));
377 static size_t split_string(const char *p
, size_t sz
,
378 unsigned *wd_out
, unsigned maxwd
)
379 /* Return the size, in bytes, of the shortest prefix of the SZ-byte
380 * string P which is no less than MAXWD columns wide, or SZ if it's
381 * just too short. Store the actual width in *WD_OUT.
385 size_t i
; unsigned wd
;
388 init_measure(&m
, p
, sz
);
390 /* Advance until we're past the bound. */
392 if (!advance_measure(&m
)) { *wd_out
= m
.wd
; return (sz
); }
393 if (m
.wd
>= maxwd
) break;
396 /* Now /continue/ advancing past zero-width characters until we find
397 * something that wasn't zero-width. These might be combining accents or
398 * somesuch, and leaving them off would definitely be wrong.
402 more
= advance_measure(&m
);
403 if (m
.wd
> wd
) break;
409 *wd_out
= wd
; return (i
);
412 /*----- Output buffer handling --------------------------------------------*/
414 static int grow_linebuf(struct progress_render_state
*render
, size_t want
)
415 /* Extend the line buffer in RENDER so that it's at least WANT bytes
416 * long. Shuffle the accumulated left and right material in the
417 * buffer as necessary. Return 0 on success or -1 if this fails for
421 char *newbuf
; size_t newsz
;
423 /* Return if there's already enough space. */
424 if (want
<= render
->linesz
) return (0);
426 /* Work out how much space to allocate. The initial size is a rough guess
427 * based on the size of UTF-8 encoded characters, though it's not an upper
428 * bound because many characters have zero width. Double the buffer size
429 * if it's too small. Sneakily insert a terminating zero byte just in
432 if (!render
->linesz
) newsz
= 4*render
->width
+ 1;
433 else newsz
= render
->linesz
;
434 while (newsz
< want
) newsz
*= 2;
435 newbuf
= malloc(newsz
+ 1); if (!newbuf
) return (-1);
438 /* Copy the left and right strings into the new buffer. */
440 memcpy(newbuf
, render
->linebuf
, render
->leftsz
);
442 memcpy(newbuf
+ newsz
- render
->rightsz
,
443 render
->linebuf
+ render
->linesz
- render
->rightsz
,
446 /* Free the old buffer and remember the new one. */
447 free(render
->linebuf
); render
->linebuf
= newbuf
; render
->linesz
= newsz
;
453 static int grow_tempbuf(struct progress_render_state
*render
, size_t want
)
454 /* Extend the temporary buffer in RENDER so that it's at least WANT
455 * bytes long. Anything stored in the buffer will be lost. Return 0
456 * on success or -1 if this fails for any reason.
459 char *newbuf
; size_t newsz
;
461 /* Return if there's already enough space. */
462 if (want
<= render
->tempsz
) return (0);
464 /* Work out how much space to allocate. This is the same as `grow_linebuf'
467 if (!render
->tempsz
) newsz
= 4*render
->width
+ 1;
468 else newsz
= render
->tempsz
;
469 while (newsz
< want
) newsz
*= 2;
470 newbuf
= malloc(newsz
+ 1); if (!newbuf
) return (-1);
473 /* Free the old buffer and keep the new one. */
474 free(render
->tempbuf
); render
->tempbuf
= newbuf
; render
->tempsz
= newsz
;
480 enum { LEFT
, RIGHT
};
481 static int putstr(struct progress_render_state
*render
, unsigned side
,
482 const char *p
, size_t n
)
483 /* Add the N-byte string P to SIDE of the line buffer in RENDER.
484 * Return 0 on success or -1 if this fails for any reason.
487 unsigned newwd
= string_width(p
, n
);
490 if (newwd
>= render
->width
- render
->leftwd
- render
->rightwd
) return (-1);
491 want
= render
->leftsz
+ render
->rightsz
+ n
;
492 if (want
> render
->linesz
&& grow_linebuf(render
, want
)) return (-1);
495 memcpy(render
->linebuf
+ render
->leftsz
, p
, n
);
496 render
->leftsz
+= n
; render
->leftwd
+= newwd
;
499 memcpy(render
->linebuf
+ render
->linesz
- render
->rightsz
- n
, p
, n
);
500 render
->rightsz
+= n
; render
->rightwd
+= newwd
;
508 static int vputf(struct progress_render_state
*render
, unsigned side
,
509 const char *fmt
, va_list ap
)
510 /* Format a `printf'-style string FMT with arguments AP, and add it
511 * to SIDE of RENDER's line buffer.
517 if (!render
->tempsz
&& grow_tempbuf(render
, 2*strlen(fmt
))) return (-1);
520 rc
= vsnprintf(render
->tempbuf
, render
->tempsz
, fmt
, bp
);
522 if (rc
< 0) return (-1);
523 if (rc
<= render
->tempsz
) break;
524 if (grow_tempbuf(render
, 2*(rc
+ 1))) return (-1);
526 if (putstr(render
, side
, render
->tempbuf
, rc
)) return (-1);
530 int progress_vputleft(struct progress_render_state
*render
,
531 const char *fmt
, va_list ap
)
532 { return (vputf(render
, LEFT
, fmt
, ap
)); }
534 int progress_vputright(struct progress_render_state
*render
,
535 const char *fmt
, va_list ap
)
536 { return (vputf(render
, RIGHT
, fmt
, ap
)); }
538 int progress_putleft(struct progress_render_state
*render
,
539 const char *fmt
, ...)
544 va_start(ap
, fmt
); rc
= vputf(render
, LEFT
, fmt
, ap
); va_end(ap
);
548 int progress_putright(struct progress_render_state
*render
,
549 const char *fmt
, ...)
554 va_start(ap
, fmt
); rc
= vputf(render
, RIGHT
, fmt
, ap
); va_end(ap
);
558 /*----- Terminal output ---------------------------------------------------*/
560 #if defined(USE_TERMINFO)
562 static const struct progress_ttyinfo
*curtty
= 0;
563 static int putty(int ch
) { return (putc(ch
, curtty
->fp
)); }
564 void progress_put_sequence(const struct progress_ttyinfo
*tty
,
565 const char *p
, unsigned nlines
)
566 { if (p
) { curtty
= tty
; tputs(p
, nlines
, putty
); } }
567 void progress_set_fgcolour(const struct progress_ttyinfo
*tty
, int colour
)
568 { progress_put_sequence(tty
, tgoto(tty
->cap
.af
, -1, colour
), 1); }
569 void progress_set_bgcolour(const struct progress_ttyinfo
*tty
, int colour
)
570 { progress_put_sequence(tty
, tgoto(tty
->cap
.ab
, -1, colour
), 1); }
572 #elif defined(USE_TERMCAP)
574 static const struct progress_ttyinfo
*curtty
= 0;
575 static int putty(int ch
) { return (putc(ch
, curtty
->fp
)); }
576 void progress_put_sequence(const struct progress_ttyinfo
*tty
,
577 const char *p
, unsigned nlines
)
578 { if (p
) { curtty
= tty
; tputs(p
, nlines
, putty
); } }
579 void progress_set_fgcolour(const struct progress_ttyinfo
*tty
, int colour
)
580 { progress_put_sequence(tty
, tgoto(tty
->cap
.af
, -1, colour
), 1); }
581 void progress_set_bgcolour(const struct progress_ttyinfo
*tty
, int colour
)
582 { progress_put_sequence(tty
, tgoto(tty
->cap
.ab
, -1, colour
), 1); }
586 void progress_put_sequence(const struct progress_ttyinfo
*tty
,
587 const char *p
, unsigned nlines
) { ; }
588 void progress_set_fgcolour(const struct progress_ttyinfo
*tty
, int colour
)
590 void progress_set_bgcolour(const struct progress_ttyinfo
*tty
, int colour
)
595 /*----- Maintaining the progress display ----------------------------------*/
597 #define CLRF_ALL 1u /* clear everything */
598 static void clear_progress(struct progress_state
*progress
,
599 struct progress_render_state
*render
, unsigned f
)
600 /* Clear the current progress display maintained by PROGRESS,
601 * assisted by the RENDER state.
603 * If `CLRF_ALL' is set in F, then clear the entire display.
604 * Otherwise, clear the bottom few lines if there are now fewer
605 * progress items than there were last time we rendered the display,
606 * and leave the cursor at the start of the top line ready to
610 const struct progress_ttyinfo
*tty
= &progress
->tty
;
611 unsigned ndel
, nleave
;
614 progress_put_sequence(tty
, tty
->cap
.cr
, 1);
615 if (progress
->last_lines
) {
617 /* Decide how many lines to delete. Set `ndel' to the number of lines
618 * that will be entirely erased, and `nleave' to the number that we'll
622 { ndel
= progress
->last_lines
; nleave
= 0; }
624 if (progress
->nitems
>= progress
->last_lines
) ndel
= 0;
625 else ndel
= progress
->last_lines
- progress
->nitems
;
626 nleave
= progress
->last_lines
- ndel
;
629 /* Now actually do the clearing. Remember that the cursor is still on
633 for (i
= 1; i
< nleave
; i
++)
634 progress_put_sequence(tty
, tty
->cap
.up
, 1);
636 for (i
= 1; i
< ndel
; i
++)
637 progress_put_sequence(tty
, tty
->cap
.up
, 1);
638 progress_put_sequence(tty
, tty
->cap
.cd
, ndel
);
639 for (i
= 0; i
< nleave
; i
++)
640 progress_put_sequence(tty
, tty
->cap
.up
, 1);
644 /* Remember that we're now at the top of the display. */
645 progress
->last_lines
= 0;
648 int progress_clear(struct progress_state
*progress
)
650 struct progress_render_state render
;
652 if (!progress
->tty
.fp
) return (-1);
653 setup_render_state(progress
, &render
);
654 clear_progress(progress
, &render
, CLRF_ALL
);
655 free_render_state(&render
);
659 int progress_update(struct progress_state
*progress
)
661 struct progress_render_state render
;
662 const struct progress_ttyinfo
*tty
= &progress
->tty
;
663 struct progress_item
*item
;
667 if (!progress
->tty
.fp
) return (-1);
669 setup_render_state(progress
, &render
);
670 clear_progress(progress
, &render
, 0);
672 for (item
= progress
->items
; item
; item
= item
->next
) {
673 if (f
&f_any
) progress_put_sequence(tty
, tty
->cap
.nw
, 1);
674 render
.leftsz
= render
.rightsz
= 0;
675 render
.leftwd
= render
.rightwd
= 0;
676 item
->render(item
, &render
); progress
->last_lines
++; f
|= f_any
;
677 if (progress
->last_lines
> render
.height
) break;
679 if (f
&f_any
) progress_put_sequence(tty
, tty
->cap
.cr
, 1);
680 free_render_state(&render
);
684 /*----- Rendering progress bars -------------------------------------------*/
686 /* The basic problem here is to render text, formed of several pieces, to the
687 * terminal, placing some marker in the middle of it to indicate how much
688 * progress has been made. This marker might be a colour change, switching
689 * off reverse-video mode, or a `|' character.
701 /* State to track progress through the output of a progress bar, so
702 * that we insert the marker in the right place.
704 * This is a little state machine. We remember the current column
705 * position, the current state, and the column at which we'll next
709 const struct progress_render_state
*render
; /* render state */
710 unsigned pos
, nextpos
, state
; /* as described */
713 static void advance_bar_state(struct bar_state
*bar
)
714 /* If we've reached the column position for the next state change
715 * then arrange to do whatever it is we're meant to do, and update
716 * for the next change.
719 const struct progress_render_state
*render
= bar
->render
;
720 const struct progress_ttyinfo
*tty
= render
->tty
;
721 size_t here
= bar
->nextpos
;
723 while (bar
->nextpos
<= here
) {
724 switch (bar
->state
) {
725 case LEFT_COLOUR
: progress_set_bgcolour(tty
, 3); goto right
;
726 case LEFT_MONO
: progress_put_sequence(tty
, tty
->cap
.me
, 1); goto right
;
727 case LEFT_SIMPLE
: putc('|', tty
->fp
); goto right
;
728 right
: bar
->state
= RIGHT_ANY
; bar
->nextpos
= render
->width
; break;
729 case RIGHT_ANY
: bar
->state
= STOP
; bar
->nextpos
= UINT_MAX
; break;
734 /* Little utilities to output a chunk of text, or some spaces. */
735 static void put_str(FILE *fp
, const char *p
, size_t sz
)
736 { while (sz
--) putc(*p
++, fp
); }
737 static void put_spc(FILE *fp
, unsigned n
)
738 { while (n
--) putc(' ', fp
); }
740 static void put_barstr(struct bar_state
*bar
, const char *p
, size_t sz
)
741 /* Output the SZ-byte string P, driving the state machine BAR as we
749 /* Main loop. Determine how much space there is to the next state
750 * change, cut off that amount of space from the string, and advance.
753 n
= split_string(p
, sz
, &wd
, bar
->nextpos
- bar
->pos
);
754 if (n
== sz
&& wd
< bar
->nextpos
- bar
->pos
) break;
755 put_str(bar
->render
->tty
->fp
, p
, n
); bar
->pos
+= wd
;
756 advance_bar_state(bar
);
760 /* Write out the rest of the string, and update the position. We know that
761 * this won't reach the next transition.
763 put_str(bar
->render
->tty
->fp
, p
, sz
); bar
->pos
+= wd
;
766 static void put_barspc(struct bar_state
*bar
, unsigned n
)
767 /* Output N spaces, driving the state machine BAR as we go. */
772 step
= bar
->nextpos
- bar
->pos
;
774 put_spc(bar
->render
->tty
->fp
, step
); bar
->pos
+= step
;
775 advance_bar_state(bar
);
778 put_spc(bar
->render
->tty
->fp
, n
); bar
->pos
+= n
;
781 int progress_showbar(struct progress_render_state
*render
, double frac
)
783 const struct progress_ttyinfo
*tty
= render
->tty
;
784 struct bar_state bar
;
786 /* If there's no terminal, then there's nothing to do. */
787 if (!tty
->fp
) return (-1);
789 /* Set up the render state, with a transition where the bar should end. */
790 bar
.render
= render
; bar
.pos
= 0; bar
.nextpos
= frac
*render
->width
+ 0.5;
792 /* Set the initial state for the render. */
794 /* We have colour. The foreground will always be black. If we've made
795 * negligible progress then advance the state machine immediately, which
796 * will set a yellow background for the remainder of the line; otherwise
797 * set the background green for the start of the bar.
800 progress_set_fgcolour(tty
, 0); bar
.state
= LEFT_COLOUR
;
801 if (bar
.nextpos
) progress_set_bgcolour(tty
, 2);
802 else advance_bar_state(&bar
);
803 } else if (tty
->cap
.mr
) {
804 /* We have reverse-video. Write the progress bar in reverse and the rest
809 { bar
.state
= LEFT_MONO
; progress_put_sequence(tty
, tty
->cap
.mr
, 1); }
811 { bar
.state
= RIGHT
; bar
.nextpos
= render
->width
; }
813 /* Nothing fancy. We'll write `|' at the right place. */
814 bar
.state
= LEFT_SIMPLE
;
816 /* Write the left string, spaces to fill the gap, and the right string. */
817 put_barstr(&bar
, render
->linebuf
, render
->leftsz
);
818 put_barspc(&bar
, render
->width
- render
->leftwd
- render
->rightwd
);
820 render
->linebuf
+ render
->linesz
- render
->rightsz
,
823 /* Final output: turn off fancy highlighting, and colours. */
824 progress_put_sequence(tty
, tty
->cap
.me
, 1);
825 progress_put_sequence(tty
, tty
->cap
.op
, 1);
831 int progress_shownotice(struct progress_render_state
*render
, int bg
, int fg
)
833 const struct progress_ttyinfo
*tty
= render
->tty
;
835 /* If there's no terminal, then there's nothing to do. */
836 if (!tty
->fp
) return (-1);
838 /* Set the general background for the notice. */
840 /* We have colours, so set them. */
842 progress_set_fgcolour(tty
, fg
); progress_set_bgcolour(tty
, bg
);
843 } else if (tty
->cap
.mr
) {
844 /* We have reverse-video, so we might as well use that. */
846 progress_put_sequence(tty
, tty
->cap
.mr
, 1);
849 /* Set boldface. (If we have it.) */
850 progress_put_sequence(tty
, tty
->cap
.md
, 1);
852 /* Print the left string. If there's a right string, then print spaces and
853 * the right string; otherwise, try to optimize by erasing to the end of
854 * the line -- if that will erase in the background colour.
856 put_str(tty
->fp
, render
->linebuf
, render
->leftsz
);
857 if (!render
->rightsz
&& (tty
->cap
.f
&TCF_BCE
) && tty
->cap
.ce
)
858 progress_put_sequence(tty
, tty
->cap
.ce
, 1);
860 put_spc(tty
->fp
, render
->width
- render
->leftwd
- render
->rightwd
);
862 render
->linebuf
+ render
->linesz
- render
->rightsz
,
866 /* Put things back to normal. */
867 progress_put_sequence(tty
, tty
->cap
.me
, 1);
868 progress_put_sequence(tty
, tty
->cap
.op
, 1);
874 /*----- That's all, folks -------------------------------------------------*/