Add support for tab-completion when selecting by rule
[alpine.git] / alpine / busy.c
blobaa6c8a385dfe9653dd5a5f0b15157b0bfe6ecf04
1 /* ========================================================================
2 * Copyright 2006-2007 University of Washington
3 * Copyright 2013-2022 Eduardo Chappa
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * ========================================================================
14 /*======================================================================
15 Implement busy_cue spinner
16 ====*/
19 #include <system.h>
20 #include <general.h>
22 #include "../c-client/c-client.h"
24 #include "../pith/conf.h"
25 #include "../pith/state.h"
26 #include "../pith/status.h"
27 #include "../pith/busy.h"
28 #include "../pith/debug.h"
29 #include "../pith/help.h"
31 #include "../pith/charconv/utf8.h"
33 #ifdef _WINDOWS
34 #include "../pico/osdep/mswin.h"
35 #endif
37 #include "status.h"
38 #include "after.h"
39 #include "busy.h"
42 static int dotcount;
43 static char busy_message[MAX_BM + 1];
44 static int busy_cue_pause;
45 static int busy_width;
46 static int final_message;
47 static int final_message_pri;
49 static percent_done_t percent_done_ptr;
51 #define MAX_SPINNER_WIDTH 32
52 #define MAX_SPINNER_ELEMENTS 64
54 static int spinner = 0;
55 static struct _spinner {
56 int width,
57 elements;
58 char *bars[MAX_SPINNER_ELEMENTS];
59 unsigned used_this_round:1;
60 } spinners[] = {
61 {4, 4, {"<|> ", "</> ", "<-> ", "<\\> "}},
62 {8, 34, {"~~~~~~~~", "~~~~~~~~", "/~~~~~~~", "/~~~~~~~", "_/~~~~~~",
63 "_/~~~~~~", "__/~~~~~", "__/~~~~~", "___/~~~~", "___/~~~~",
64 "\\___/~~~", "\\___/~~~", "~\\___/~~", "~\\___/~~", "~~\\___/~",
65 "~~\\___/~", "~~~\\___/", "~~~\\___/", "^~~~\\___", "^~~~\\___",
66 "~^~~~\\__", "~^~~~\\__", "~~^~~~\\_", "~~^~~~\\_", "~~~^~~~\\",
67 "~~~^~~~\\", "~~~~^~~~", "~~~~^~~~", "~~~~~^~~", "~~~~~^~~",
68 "~~~~~~^~", "~~~~~~^~", "~~~~~~~^", "~~~~~~~^"}},
69 {9, 14 , {"| WAIT |", "| WAIT |", "-|WAIT|-", "-|WAIT|-",
70 "--|AI|--", "--|AI|--", "---||---", "---||---",
71 "--|AI|--", "--|AI|--", "-|WAIT|-", "-|WAIT|-",
72 "| WAIT |", "| WAIT |"}},
73 {9, 24 , {"o | ", "o | ", " o | ", " o | ",
74 " o | ", " o | ", " o| ", " o| ",
75 " \\ ", " \\ ", " -o ", " -o ",
76 " / o ", " / o ", " | o ", " | o ",
77 " \\ o", " \\ o", " - ", " - ",
78 " / ", " / ", " | ", " | "}},
79 {8, 38, {"~~~~~~~~", "~~~~~~~~", "/~~~~~~~", "/~~~~~~~", "_/~~~~~~",
80 "_/~~~~~~", "__/~~~~~", "__/~~~~~", "___/~~~~", "___/~~~~",
81 "\\___/~~~", "\\___/~~~", "~\\___/~~", "~\\___/~~", "~~\\___/~",
82 "~~\\___/~", "~~~\\___/", "~~~\\___/", "~~~~\\___", "~~~~\\___",
83 "\\~~~~\\__", "\\~~~~\\__", "/\\~~~~\\_", "/\\~~~~\\_", "~/\\~~~~\\",
84 "~/\\~~~~\\", "~~/\\~~~~", "~~/\\~~~~", "~~~/\\~~~", "~~~/\\~~~",
85 "~~~~/\\~~", "~~~~/\\~~", "~~~~~/\\~", "~~~~~/\\~", "~~~~~~/\\",
86 "~~~~~~/\\", "~~~~~~~/", "~~~~~~~/"}},
87 {6, 10, {"> ", "<> ", "><> ", " ><> ", " ><> ",
88 " ><> ", " ><> ", " ><>", " ><", " >"}},
89 {6, 10, {" <", " <>", " <><", " <>< ", " <>< ",
90 " <>< ", " <>< ", "<>< ", ">< ", "< "}},
91 {6, 10, {"> <", "<> <>", "><> <><", " ><><>< ", " ><>< ",
92 " <><> ", " <><><> ", "<>< ><>", ">< ><", "< >"}},
93 {11, 4, {"--|-(o)-|--", "--/-(o)-\\--", "----(o)----", "--\\-(o)-/--"}},
94 {6, 7, {"\\____/", "_\\__/_", "__\\/__", "__/\\__",
95 "_/__\\_", "/____\\", "|____|"}},
96 {4, 4, {"<|> ", "<\\> ", "<-> ", "</> "}},
97 {4, 10,{"| ", " / ", " _ ", " \\ ", " | ", " | ", " \\ ",
98 " _ ", " / ", "| "}},
99 {4, 8, {"_ _ ", "\\ \\ ", " | |", " / /", " _ _", " / /", " | |", "\\ \\ "}},
100 {4, 8, {"_ ", "\\ ", " | ", " / ", " _ ", " \\ ", " | ", "/ "}},
101 {4, 8, {"_ ", "\\ ", " | ", " / ", " _ ", " / ", " | ", "\\ "}},
102 {4, 4, {" . ", " o ", " O ", " o "}},
103 {4, 5, {"....", " ...", ". ..", ".. .", "... "}},
104 {4, 5, {" ", ". ", " . ", " . ", " ."}},
105 {4, 4, {".oOo", "oOo.", "Oo.o", "o.oO"}},
106 {4, 11,{"____", "\\___", "|\\__", "||\\_", "|||\\", "||||", "/|||",
107 "_/||", "__/|", "___/", "____"}},
108 {7, 9, {". .", " . . ", " . . ",
109 " . ", " + ", " * ", " X ",
110 " # ", " "}},
111 {4, 4, {". O ", "o o ", "O . ", "o o "}},
112 {4, 26,{"| ", "/ ", "_ ", "\\ ", " | ", " / ", " _ ", " \\ ",
113 " | ", " / ", " _ ", " \\ ", " |", " |", " \\ ", " _ ", " / ",
114 " | ", " \\ ", " _ ", " / ", " | ", "\\ ", "_ ", "/ ", "| "}},
115 {4, 8, {"* ", "-* ", "--* ", " --*", " --", " -", " ", " "}},
116 {4, 2, {"\\/\\/", "/\\/\\"}},
117 {4, 4, {"\\|/|", "|\\|/", "/|\\|", "|/|\\"}}
123 * various pauses in 100th's of second
125 #define BUSY_PERIOD_PERCENT 25 /* percent done update */
126 #define BUSY_MSG_DONE 0 /* no more updates */
127 #define BUSY_MSG_RETRY 25 /* message line conflict */
128 #define BUSY_DELAY_PERCENT 33 /* pause before showing % */
129 #define BUSY_DELAY_SPINNER 100 /* second pause before spinner */
132 /* internal prototypes */
133 int do_busy_cue(void *);
134 void done_busy_cue(void *);
138 * Turn on a busy cue.
140 * msg -- the busy message to print in status line
141 * pc_f -- if non-null, call this function to get the percent done,
142 * (an integer between 0 and 100). If null, append dots.
143 * delay -- seconds to delay output of delay notification
145 * Returns: 0 If busy cue was already set up before we got here
146 * 1 If busy cue was not already set up.
148 * NOTE: busy_cue and cancel_busy_cue MUST be called symmetrically in the
149 * same lexical scope.
152 busy_cue(char *msg, percent_done_t pc_f, int delay)
154 AFTER_S *a = NULL, **ap;
156 dprint((9, "busy_cue(%s, %p, %d)\n", msg ? msg : "Busy", pc_f, delay));
158 if(!(ps_global && ps_global->ttyo)){
159 dprint((9, "busy_cue returns No (ttyo)"));
160 return(0);
164 * If we're already busy'ing, but a cue is invoked that
165 * supplies more useful info, use it...
167 if(after_active){
168 if(msg || pc_f)
169 stop_after(1); /* uninstall old handler */
170 else
171 return(0); /* nothing to add, return */
174 /* get ready to set up list of AFTER_S's */
175 ap = &a;
177 dotcount = 0;
178 percent_done_ptr = pc_f;
180 if(msg){
181 strncpy(busy_message, msg, sizeof(busy_message));
182 final_message = 1;
184 else{
185 strncpy(busy_message, "Busy", sizeof(busy_message));
186 final_message = 0;
189 busy_message[sizeof(busy_message)-1] = '\0';
190 busy_width = utf8_width(busy_message);
192 if(!delay){
193 char progress[MAX_SCREEN_COLS+1];
194 int space_left, slots_used;
196 final_message = 1;
197 space_left = (ps_global->ttyo ? ps_global->ttyo->screen_cols
198 : 80) - busy_width - 2; /* 2 is for [] */
199 slots_used = MAX(0, MIN(space_left-3, 10));
201 if(percent_done_ptr && slots_used >= 4){
202 snprintf(progress, sizeof(progress), "%s |%*s|", busy_message, slots_used, "");
203 progress[sizeof(progress)-1] = '\0';
205 else{
206 dotcount++;
207 snprintf(progress, sizeof(progress), "%s%*s", busy_message,
208 spinners[spinner].width + 1, "");
209 progress[sizeof(progress)-1] = '\0';
213 if(status_message_remaining()){
214 char buf[sizeof(progress) + 30];
215 char *append = " [not actually shown]";
217 strncpy(buf, progress, sizeof(buf)-1);
218 buf[sizeof(buf)-1] = '\0';
220 strncat(buf, append, sizeof(buf) - strlen(buf) - 1);
221 buf[sizeof(buf)-1] = '\0';
223 add_review_message(buf, -1);
225 else{
226 q_status_message(SM_ORDER, 0, 1, progress);
229 * We use display_message so that the initial message will
230 * be forced out only if there is not a previous message
231 * currently being displayed that hasn't been displayed for
232 * its min display time yet. In that case, we don't want
233 * to force out the initial message.
235 display_message('x');
238 fflush(stdout);
242 * Randomly select one of the animations, even taking care
243 * to run through all of them before starting over!
244 * The user won't actually see them all because most of them
245 * will never show up before they are canceled, but it
246 * should still help.
248 spinner = -1;
249 if(F_OFF(F_USE_BORING_SPINNER,ps_global) && ps_global->active_status_interval > 0){
250 int arrsize, eligible, pick_this_one, i, j;
252 arrsize = sizeof(spinners)/sizeof(struct _spinner);
254 /* how many of them are eligible to be used this round? */
255 for(eligible = i = 0; i < arrsize; i++)
256 if(!spinners[i].used_this_round)
257 eligible++;
259 if(eligible == 0) /* reset */
260 for(eligible = i = 0; i < arrsize; i++){
261 spinners[i].used_this_round = 0;
262 eligible++;
265 if(eligible > 0){ /* it will be */
266 pick_this_one = random() % eligible;
267 for(j = i = 0; i < arrsize && spinner < 0; i++)
268 if(!spinners[i].used_this_round){
269 if(j == pick_this_one)
270 spinner = i;
271 else
272 j++;
277 if(spinner < 0 || spinner > sizeof(spinners)/sizeof(struct _spinner) -1)
278 spinner = 0;
280 *ap = new_afterstruct();
281 (*ap)->delay = (pc_f) ? BUSY_DELAY_PERCENT : BUSY_DELAY_SPINNER;
282 (*ap)->f = do_busy_cue;
283 (*ap)->cf = done_busy_cue;
284 ap = &(*ap)->next;
286 start_after(a); /* launch cue handler */
288 #ifdef _WINDOWS
289 mswin_setcursor(MSWIN_CURSOR_BUSY);
290 #endif
292 return(1);
297 * If final_message was set when busy_cue was called:
298 * and message_pri = -1 -- no final message queued
299 * else final message queued with min equal to message_pri
301 void
302 cancel_busy_cue(int message_pri)
304 dprint((9, "cancel_busy_cue(%d)\n", message_pri));
306 final_message_pri = message_pri;
308 stop_after(0);
312 * suspend_busy_cue - continue previously installed busy_cue.
314 void
315 suspend_busy_cue(void)
317 dprint((9, "suspend_busy_cue\n"));
319 if(after_active)
320 busy_cue_pause = 1;
325 * resume_busy_cue - continue previously installed busy_cue.
327 void
328 resume_busy_cue(unsigned int pause)
330 dprint((9, "resume_busy_cue\n"));
332 if(after_active)
333 busy_cue_pause = 0;
338 * do_busy_cue - paint the busy cue and return how long caller
339 * should pause before calling us again
342 do_busy_cue(void *data)
344 int space_left, slots_used, period;
345 char dbuf[MAX_SCREEN_COLS+1];
347 /* Don't wipe out any displayed status message prematurely */
348 if(status_message_remaining() || busy_cue_pause)
349 return(BUSY_MSG_RETRY);
351 space_left = (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) -
352 busy_width - 2; /* 2 is for [] */
353 slots_used = MAX(0, MIN(space_left-3, 10));
355 if(percent_done_ptr && slots_used >= 4){
356 int completed, pd;
357 char *s;
359 pd = (*percent_done_ptr)();
360 pd = MIN(MAX(0, pd), 100);
362 completed = (pd * slots_used) / 100;
363 snprintf(dbuf, sizeof(dbuf), "%s |%s%s%*s|", busy_message,
364 completed > 1 ? repeat_char(completed-1, pd==100 ? ' ' : '-') : "",
365 (completed > 0 && pd != 100) ? ">" : "",
366 slots_used - completed, "");
367 dbuf[sizeof(dbuf)-1] = '\0';
369 if(slots_used == 10){
370 s = dbuf + strlen(dbuf) - 8;
371 if(pd < 10){
372 s++; s++;
373 *s++ = '0' + pd;
375 else if(pd < 100){
376 s++;
377 *s++ = '0' + pd / 10;
378 *s++ = '0' + pd % 10;
380 else{
381 *s++ = '1';
382 *s++ = '0';
383 *s++ = '0';
386 *s = '%';
389 period = BUSY_PERIOD_PERCENT;
391 else{
392 char b[MAX_SPINNER_WIDTH + 2];
393 int ind;
395 ind = (dotcount % spinners[spinner].elements);
397 spinners[spinner].used_this_round = 1;
398 if(space_left >= spinners[spinner].width + 1){
399 b[0] = SPACE;
400 strncpy(b+1,
401 (ps_global->active_status_interval > 0)
402 ? spinners[spinner].bars[ind] : "... ", sizeof(b)-1);
403 b[sizeof(b)-1] = '\0';
405 else if(space_left >= 2 && space_left < sizeof(b)){
406 b[0] = '.';
407 b[1] = '.';
408 b[2] = '.';
409 b[space_left] = '\0';
411 else
412 b[0] = '\0';
414 snprintf(dbuf, sizeof(dbuf), "%s%s", busy_message, b);
415 dbuf[sizeof(dbuf)-1] = '\0';
417 /* convert interval to delay in 100ths of second */
418 period = (ps_global->active_status_interval > 0)
419 ? (100 / MIN(10, ps_global->active_status_interval)) : BUSY_MSG_DONE;
422 status_message_write(dbuf, 1);
423 dotcount++;
424 fflush(stdout);
426 return(period);
430 void
431 done_busy_cue(void *data)
433 int space_left, slots_used;
435 if(final_message && final_message_pri >= 0){
436 char progress[MAX_SCREEN_COLS+1];
438 /* 2 is for [] */
439 space_left = (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - busy_width - 2;
440 slots_used = MAX(0, MIN(space_left-3, 10));
442 if(percent_done_ptr && slots_used >= 4){
443 int left, right;
445 right = (slots_used - 4)/2;
446 left = slots_used - 4 - right;
447 snprintf(progress, sizeof(progress), "%s |%*s100%%%*s|",
448 busy_message, left, "", right, "");
449 progress[sizeof(progress)-1] = '\0';
450 q_status_message(SM_ORDER,
451 final_message_pri>=2 ? MAX(final_message_pri,3) : 0,
452 final_message_pri+2, progress);
454 else{
455 int padding;
457 padding = MAX(0, MIN(space_left-5, spinners[spinner].width-4));
459 snprintf(progress, sizeof(progress), "%s %*sDONE", busy_message,
460 padding, "");
461 progress[sizeof(progress)-1] = '\0';
462 q_status_message(SM_ORDER,
463 final_message_pri>=2 ? MAX(final_message_pri,3) : 0,
464 final_message_pri+2, progress);
468 mark_status_dirty();