1 /* vifm
2 * Copyright (C) 2014 xaizek.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 */
18
19 #include "statusline.h"
20 #include "private/statusline.h"
21
22 #include <curses.h> /* mvwin() werase() */
23
24 #include <assert.h> /* assert() */
25 #include <ctype.h> /* isdigit() */
26 #include <stddef.h> /* NULL size_t */
27 #include <stdlib.h> /* RAND_MAX free() rand() */
28 #include <string.h> /* strcat() strdup() strlen() */
29 #include <time.h> /* time() */
30 #include <unistd.h>
31
32 #include "../cfg/config.h"
33 #include "../compat/pthread.h"
34 #include "../compat/reallocarray.h"
35 #include "../engine/mode.h"
36 #include "../engine/parsing.h"
37 #include "../engine/var.h"
38 #include "../modes/modes.h"
39 #include "../utils/fs.h"
40 #include "../utils/log.h"
41 #include "../utils/macros.h"
42 #include "../utils/path.h"
43 #include "../utils/str.h"
44 #include "../utils/string_array.h"
45 #include "../utils/test_helpers.h"
46 #include "../utils/utf8.h"
47 #include "../utils/utils.h"
48 #include "../background.h"
49 #include "../filelist.h"
50 #include "color_manager.h"
51 #include "color_scheme.h"
52 #include "colored_line.h"
53 #include "ui.h"
54
55 static void update_stat_window_old(view_t *view, int lazy_redraw);
56 static void refresh_window(WINDOW *win, int lazily);
57 TSTATIC cline_t expand_status_line_macros(view_t *view, const char format[]);
58 static cline_t parse_view_macros(view_t *view, const char **format,
59 const char macros[], int opt);
60 static int expand_num(char buf[], size_t buf_len, int val);
61 static const char * get_tip(void);
62 static void check_expanded_str(const char buf[], int skip, int *nexpansions);
63 static pthread_spinlock_t * get_job_bar_changed_lock(void);
64 static void init_job_bar_changed_lock(void);
65 static int is_job_bar_visible(void);
66 static const char * format_job_bar(void);
67 static char ** take_job_descr_snapshot(void);
68
69 /* Number of background jobs. */
70 static size_t nbar_jobs;
71 /* Array of jobs. */
72 static bg_op_t **bar_jobs;
73 /* Whether list of jobs needs to be redrawn. */
74 static int job_bar_changed;
75 /* Protects accesses to job_bar_changed variable. */
76 static pthread_spinlock_t job_bar_changed_lock;
77
78 void
ui_stat_update(view_t * view,int lazy_redraw)79 ui_stat_update(view_t *view, int lazy_redraw)
80 {
81 if(!cfg.display_statusline || view != curr_view)
82 {
83 return;
84 }
85
86 /* Don't redraw anything until :restart command is finished. */
87 if(curr_stats.restart_in_progress)
88 {
89 return;
90 }
91
92 ui_stat_job_bar_check_for_updates();
93
94 if(cfg.status_line[0] == '\0')
95 {
96 update_stat_window_old(view, lazy_redraw);
97 return;
98 }
99
100 const int width = getmaxx(stdscr);
101
102 wresize(stat_win, 1, width);
103 ui_set_bg(stat_win, &cfg.cs.color[STATUS_LINE_COLOR],
104 cfg.cs.pair[STATUS_LINE_COLOR]);
105 werase(stat_win);
106 checked_wmove(stat_win, 0, 0);
107
108 cline_t result = expand_status_line_macros(view, cfg.status_line);
109 assert(strlen(result.attrs) == utf8_strsw(result.line) && "Broken attrs!");
110 result.line = break_in_two(result.line, width, "%=");
111 result.attrs = break_in_two(result.attrs, width, "=");
112 cline_print(&result, stat_win, &cfg.cs.color[STATUS_LINE_COLOR]);
113 cline_dispose(&result);
114
115 refresh_window(stat_win, lazy_redraw);
116 }
117
118 /* Formats status line in the "old way" (before introduction of 'statusline'
119 * option). */
120 static void
update_stat_window_old(view_t * view,int lazy_redraw)121 update_stat_window_old(view_t *view, int lazy_redraw)
122 {
123 const dir_entry_t *const curr = get_current_entry(view);
124 char name_buf[160*2 + 1];
125 char perm_buf[26];
126 char size_buf[64];
127 char id_buf[52];
128 int x;
129 int cur_x;
130 size_t print_width;
131 char *filename;
132
133 if(curr == NULL || fentry_is_fake(curr))
134 {
135 werase(stat_win);
136 refresh_window(stat_win, lazy_redraw);
137 return;
138 }
139
140 x = getmaxx(stdscr);
141 wresize(stat_win, 1, x);
142
143 ui_set_bg(stat_win, &cfg.cs.color[STATUS_LINE_COLOR],
144 cfg.cs.pair[STATUS_LINE_COLOR]);
145
146 filename = get_current_file_name(view);
147 print_width = utf8_strsnlen(filename, 20 + MAX(0, x - 83));
148 copy_str(name_buf, MIN(sizeof(name_buf), print_width + 1), filename);
149 friendly_size_notation(fentry_get_size(view, curr), sizeof(size_buf),
150 size_buf);
151
152 get_uid_string(curr, 0, sizeof(id_buf), id_buf);
153 if(id_buf[0] != '\0')
154 strcat(id_buf, ":");
155 get_gid_string(curr, 0, sizeof(id_buf) - strlen(id_buf),
156 id_buf + strlen(id_buf));
157 #ifndef _WIN32
158 get_perm_string(perm_buf, sizeof(perm_buf), curr->mode);
159 #else
160 copy_str(perm_buf, sizeof(perm_buf), attr_str_long(curr->attrs));
161 #endif
162
163 werase(stat_win);
164 cur_x = 2;
165 checked_wmove(stat_win, 0, cur_x);
166 wprint(stat_win, name_buf);
167 cur_x += 22;
168 if(x > 83)
169 cur_x += x - 83;
170 mvwaddstr(stat_win, 0, cur_x, size_buf);
171 cur_x += 12;
172 mvwaddstr(stat_win, 0, cur_x, perm_buf);
173 cur_x += 11;
174
175 snprintf(name_buf, sizeof(name_buf), "%d %s filtered", view->filtered,
176 (view->filtered == 1) ? "file" : "files");
177 if(view->filtered > 0)
178 mvwaddstr(stat_win, 0, x - (strlen(name_buf) + 2), name_buf);
179
180 if(cur_x + strlen(id_buf) + 1 > x - (strlen(name_buf) + 2))
181 break_at(id_buf, ':');
182 if(cur_x + strlen(id_buf) + 1 > x - (strlen(name_buf) + 2))
183 id_buf[0] = '\0';
184 mvwaddstr(stat_win, 0, cur_x, id_buf);
185
186 refresh_window(stat_win, lazy_redraw);
187 }
188
189 /* Refreshes given window, possibly lazily. */
190 static void
refresh_window(WINDOW * win,int lazily)191 refresh_window(WINDOW *win, int lazily)
192 {
193 if(lazily)
194 {
195 wnoutrefresh(stat_win);
196 }
197 else
198 {
199 ui_refresh_win(stat_win);
200 }
201 }
202
203 /* Expands view macros to be displayed on the status line according to the
204 * format string. Returns colored line. */
205 TSTATIC cline_t
expand_status_line_macros(view_t * view,const char format[])206 expand_status_line_macros(view_t *view, const char format[])
207 {
208 const dir_entry_t *const curr = get_current_entry(view);
209 if(curr == NULL || fentry_is_fake(curr))
210 {
211 /* Fake entries don't have valid information. */
212 return cline_make();
213 }
214
215 return parse_view_macros(view, &format, "tTfaAugsEdD-xlLSz%[]{*", 0);
216 }
217
218 /* Expands possibly limited set of view macros. Returns newly allocated string,
219 * which should be freed by the caller. */
220 char *
expand_view_macros(view_t * view,const char format[],const char macros[])221 expand_view_macros(view_t *view, const char format[], const char macros[])
222 {
223 cline_t result = parse_view_macros(view, &format, macros, 0);
224 free(result.attrs);
225 return result.line;
226 }
227
228 /* Expands macros in the *format string advancing the pointer as it goes. The
229 * opt represents conditional expression state, should be zero for non-recursive
230 * calls. Returns colored line. */
231 static cline_t
parse_view_macros(view_t * view,const char ** format,const char macros[],int opt)232 parse_view_macros(view_t *view, const char **format, const char macros[],
233 int opt)
234 {
235 const dir_entry_t *const curr = get_current_entry(view);
236 cline_t result = cline_make();
237 char c;
238 int nexpansions = 0;
239 int has_expander = 0;
240
241 if(curr == NULL)
242 {
243 return result;
244 }
245
246 while((c = **format) != '\0')
247 {
248 size_t width = 0;
249 int left_align = 0;
250 char buf[PATH_MAX + 1];
251 const char *const next = ++*format;
252 int skip, ok;
253
254 if(c != '%' ||
255 (!char_is_one_of(macros, *next) && !isdigit(*next) &&
256 (*next != '=' || has_expander)))
257 {
258 if(strappendch(&result.line, &result.line_len, c) != 0)
259 {
260 break;
261 }
262 continue;
263 }
264
265 if(*next == '=')
266 {
267 (void)cline_sync(&result, 0);
268
269 if(strappend(&result.line, &result.line_len, "%=") != 0 ||
270 strappendch(&result.attrs, &result.attrs_len, '=') != 0)
271 {
272 break;
273 }
274 ++*format;
275 has_expander = 1;
276 continue;
277 }
278
279 if(*next == '-')
280 {
281 left_align = 1;
282 ++*format;
283 }
284
285 while(isdigit(**format))
286 {
287 width = width*10 + *(*format)++ - '0';
288 }
289 c = *(*format)++;
290
291 skip = 0;
292 ok = 1;
293 buf[0] = '\0';
294 switch(c)
295 {
296 char path[PATH_MAX + 1];
297 char *escaped;
298
299 case 'a':
300 friendly_size_notation(get_free_space(curr_view->curr_dir), sizeof(buf),
301 buf);
302 break;
303 case 't':
304 case 'f':
305 if(c == 't')
306 {
307 format_entry_name(curr, NF_FULL, sizeof(path), path);
308 }
309 else
310 {
311 get_short_path_of(view, curr, NF_FULL, 0, sizeof(path), path);
312 }
313 escaped = escape_unreadable(path);
314 copy_str(buf, sizeof(buf), escaped);
315 free(escaped);
316 break;
317 case 'T':
318 if(curr->type == FT_LINK)
319 {
320 char full_path[PATH_MAX + 1];
321 get_full_path_of(curr, sizeof(full_path), full_path);
322 if(get_link_target(full_path, buf, sizeof(buf)) != 0)
323 {
324 copy_str(buf, sizeof(buf), "Failed to resolve link");
325 }
326 }
327 break;
328 case 'A':
329 #ifndef _WIN32
330 get_perm_string(buf, sizeof(buf), curr->mode);
331 #else
332 copy_str(buf, sizeof(buf), attr_str_long(curr->attrs));
333 #endif
334 break;
335 case 'u':
336 get_uid_string(curr, 0, sizeof(buf), buf);
337 break;
338 case 'g':
339 get_gid_string(curr, 0, sizeof(buf), buf);
340 break;
341 case 's':
342 friendly_size_notation(fentry_get_size(view, curr), sizeof(buf), buf);
343 break;
344 case 'E':
345 {
346 uint64_t size = 0U;
347
348 typedef int (*iter_f)(view_t *view, dir_entry_t **entry);
349 /* No current element for visual mode, since it can contain truly
350 * empty selection when cursor is on ../ directory. */
351 iter_f iter = vle_mode_is(VISUAL_MODE) ? &iter_selected_entries
352 : &iter_selection_or_current;
353
354 dir_entry_t *entry = NULL;
355 while(iter(view, &entry))
356 {
357 size += fentry_get_size(view, entry);
358 }
359
360 friendly_size_notation(size, sizeof(buf), buf);
361 }
362 break;
363 case 'd':
364 {
365 struct tm *tm_ptr = localtime(&curr->mtime);
366 strftime(buf, sizeof(buf), cfg.time_format, tm_ptr);
367 }
368 break;
369 case '-':
370 case 'x':
371 skip = expand_num(buf, sizeof(buf), view->filtered);
372 break;
373 case 'l':
374 skip = expand_num(buf, sizeof(buf), view->list_pos + 1);
375 break;
376 case 'L':
377 skip = expand_num(buf, sizeof(buf), view->list_rows + view->filtered);
378 break;
379 case 'S':
380 skip = expand_num(buf, sizeof(buf), view->list_rows);
381 break;
382 case '%':
383 copy_str(buf, sizeof(buf), "%");
384 break;
385 case 'z':
386 copy_str(buf, sizeof(buf), get_tip());
387 break;
388 case 'D':
389 if(curr_stats.number_of_windows == 1)
390 {
391 view_t *const other = (view == curr_view) ? other_view : curr_view;
392 copy_str(buf, sizeof(buf), replace_home_part(other->curr_dir));
393 }
394 break;
395 case '[':
396 {
397 cline_t opt = parse_view_macros(view, format, macros, 1);
398 copy_str(buf, sizeof(buf), opt.line);
399 free(opt.line);
400
401 cline_splice_attrs(&result, &opt);
402 break;
403 }
404 case ']':
405 if(opt)
406 {
407 if(nexpansions == 0)
408 {
409 cline_clear(&result);
410 }
411 cline_finish(&result);
412 return result;
413 }
414
415 LOG_INFO_MSG("Unmatched %%]");
416 ok = 0;
417 break;
418 case '{':
419 {
420 /* Try to find matching closing bracket
421 * TODO: implement the way to escape it, so that the expr may contain
422 * closing brackets */
423 const char *e = strchr(*format, '}');
424 char *expr = NULL, *resstr = NULL;
425 var_t res = var_false();
426 ParsingErrors parsing_error;
427
428 /* If there's no matching closing bracket, just add the opening one
429 * literally */
430 if(e == NULL)
431 {
432 ok = 0;
433 break;
434 }
435
436 /* Create a NULL-terminated copy of the given expr.
437 * TODO: we could temporarily use buf for that, to avoid extra
438 * allocation, but explicitly named variable reads better. */
439 expr = calloc(e - (*format) + 1 /* NUL-term */, 1);
440 memcpy(expr, *format, e - (*format));
441
442 /* Try to parse expr, and convert the res to string if succeed. */
443 parsing_error = parse(expr, 0, &res);
444 if(parsing_error == PE_NO_ERROR)
445 {
446 resstr = var_to_str(res);
447 }
448
449 if(resstr != NULL)
450 {
451 copy_str(buf, sizeof(buf), resstr);
452 }
453 else
454 {
455 copy_str(buf, sizeof(buf), "<Invalid expr>");
456 }
457
458 var_free(res);
459 free(resstr);
460 free(expr);
461
462 *format = e + 1 /* closing bracket */;
463 }
464 break;
465 case '*':
466 if(width > 9)
467 {
468 snprintf(buf, sizeof(buf), "%%%d*", (int)width);
469 width = 0;
470 break;
471 }
472 cline_set_attr(&result, '0' + width);
473 width = 0;
474 break;
475
476 default:
477 LOG_INFO_MSG("Unexpected %%-sequence: %%%c", c);
478 ok = 0;
479 break;
480 }
481
482 if(char_is_one_of("tTAugsEd", c) && fentry_is_fake(curr))
483 {
484 buf[0] = '\0';
485 }
486
487 if(!ok)
488 {
489 *format = next;
490 if(strappendch(&result.line, &result.line_len, '%') != 0)
491 {
492 break;
493 }
494 continue;
495 }
496
497 check_expanded_str(buf, skip, &nexpansions);
498 stralign(buf, width, ' ', left_align);
499
500 if(strappend(&result.line, &result.line_len, buf) != 0)
501 {
502 break;
503 }
504 }
505
506 /* Unmatched %[. */
507 if(opt)
508 {
509 (void)strprepend(&result.line, &result.line_len, "%[");
510 (void)strprepend(&result.attrs, &result.attrs_len, " ");
511 }
512
513 cline_finish(&result);
514 return result;
515 }
516
517 /* Prints number into the buffer. Returns non-zero if numeric value is
518 * "empty" (zero). */
519 static int
expand_num(char buf[],size_t buf_len,int val)520 expand_num(char buf[], size_t buf_len, int val)
521 {
522 snprintf(buf, buf_len, "%d", val);
523 return (val == 0);
524 }
525
526 /* Picks tip to be displayed. Returns pointer to the tip. */
527 static const char *
get_tip(void)528 get_tip(void)
529 {
530 enum { SECS_PER_MIN = 60 };
531
532 static time_t last_time;
533 static int need_to_shuffle = 1;
534 static unsigned int last_item = 0;
535 static const char *tips[] = {
536 "Space after :command name is sometimes optional: :chmod+x",
537 "[count]h moves several directories up, e.g. for `cd ../..`: 2h",
538 ":set options without completions are completed with current value",
539 ":help command completes matches by substring match",
540 "'classify' can be used to decorate names with chars/icons",
541 "You can rename files recursively via `:rename!`",
542 "t key toggles selection of current file",
543 ":sync command navigates to the same location on the other panel",
544 "Enter key leaves visual mode preserving selection",
545 "Enable 'runexec' option to run executable files via l/Enter",
546 "Various FUSE file-systems can be used to extend functionality",
547 "Trailing slash in :file[x]type, :filter and = matches directories only",
548 ":edit forwards arguments to editor, e.g.: :edit +set\\ filetype=sh script",
549 "Use :nmap/:vmap/etc. for short information on available mappings",
550 "c key in :file menu puts command into command-line to edit before running",
551 ":copen reopens the last list of files found by a :grep-like command",
552 "Up/down arrows on command-line load history entries with identical prefix",
553 };
554
555 if(need_to_shuffle)
556 {
557 /* Fisher-Yates ("Knuth") shuffle algorithm implementation that mildly
558 * considers potential biases (not important here, but why not). */
559 unsigned int i;
560 for(i = 0U; i < ARRAY_LEN(tips) - 1U; ++i)
561 {
562 const unsigned int j =
563 i + (rand()/(RAND_MAX + 1.0))*(ARRAY_LEN(tips) - i);
564 const char *const t = tips[i];
565 tips[i] = tips[j];
566 tips[j] = t;
567 }
568 need_to_shuffle = 0;
569 }
570
571 time_t now = time(NULL);
572 if(now > last_time + SECS_PER_MIN)
573 {
574 last_time = now;
575 last_item = (last_item + 1U)%ARRAY_LEN(tips);
576 }
577 return tips[last_item];
578 }
579
580 /* Examines expansion buffer to check whether expansion took place. Updates
581 * *nexpansions accordingly. */
582 static void
check_expanded_str(const char buf[],int skip,int * nexpansions)583 check_expanded_str(const char buf[], int skip, int *nexpansions)
584 {
585 if(buf[0] != '\0' && !skip)
586 {
587 ++*nexpansions;
588 }
589 }
590
591 int
ui_stat_reposition(int statusbar_height,int force_stat_win)592 ui_stat_reposition(int statusbar_height, int force_stat_win)
593 {
594 const int stat_line_height = (force_stat_win || cfg.display_statusline)
595 ? getmaxy(stat_win)
596 : 0;
597 const int job_bar_height = ui_stat_job_bar_height();
598 const int y = getmaxy(stdscr)
599 - statusbar_height
600 - stat_line_height
601 - job_bar_height;
602
603 mvwin(job_bar, y, 0);
604 if(job_bar_height != 0)
605 {
606 wresize(job_bar, job_bar_height, getmaxx(job_bar));
607 }
608
609 if(force_stat_win || cfg.display_statusline)
610 {
611 mvwin(stat_win, y + job_bar_height, 0);
612 return 1;
613 }
614 return 0;
615 }
616
617 void
ui_stat_refresh(void)618 ui_stat_refresh(void)
619 {
620 ui_stat_job_bar_check_for_updates();
621
622 ui_refresh_win(job_bar);
623 ui_refresh_win(stat_win);
624 }
625
626 int
ui_stat_job_bar_height(void)627 ui_stat_job_bar_height(void)
628 {
629 return (nbar_jobs != 0U) ? 1 : 0;
630 }
631
632 void
ui_stat_job_bar_add(bg_op_t * bg_op)633 ui_stat_job_bar_add(bg_op_t *bg_op)
634 {
635 const int prev_height = ui_stat_job_bar_height();
636
637 bg_op_t **p = reallocarray(bar_jobs, nbar_jobs + 1, sizeof(*bar_jobs));
638 if(p == NULL)
639 {
640 return;
641 }
642 bar_jobs = p;
643
644 bar_jobs[nbar_jobs] = bg_op;
645 ++nbar_jobs;
646
647 if(!is_job_bar_visible())
648 {
649 return;
650 }
651
652 ui_stat_job_bar_redraw();
653
654 if(ui_stat_job_bar_height() != prev_height)
655 {
656 stats_redraw_later();
657 }
658 }
659
660 void
ui_stat_job_bar_remove(bg_op_t * bg_op)661 ui_stat_job_bar_remove(bg_op_t *bg_op)
662 {
663 size_t i;
664 const int prev_height = ui_stat_job_bar_height();
665
666 for(i = 0U; i < nbar_jobs; ++i)
667 {
668 if(bar_jobs[i] == bg_op)
669 {
670 memmove(&bar_jobs[i], &bar_jobs[i + 1],
671 sizeof(*bar_jobs)*(nbar_jobs - 1 - i));
672 break;
673 }
674 }
675
676 --nbar_jobs;
677
678 if(ui_stat_job_bar_height() != 0)
679 {
680 ui_stat_job_bar_redraw();
681 }
682 else if(prev_height != 0)
683 {
684 stats_redraw_later();
685 }
686 }
687
688 void
ui_stat_job_bar_changed(bg_op_t * bg_op)689 ui_stat_job_bar_changed(bg_op_t *bg_op)
690 {
691 pthread_spinlock_t *const lock = get_job_bar_changed_lock();
692
693 pthread_spin_lock(lock);
694 job_bar_changed = 1;
695 pthread_spin_unlock(lock);
696 }
697
698 void
ui_stat_job_bar_check_for_updates(void)699 ui_stat_job_bar_check_for_updates(void)
700 {
701 static int prev_width;
702
703 pthread_spinlock_t *const lock = get_job_bar_changed_lock();
704 int job_bar_changed_value;
705
706 pthread_spin_lock(lock);
707 job_bar_changed_value = job_bar_changed;
708 job_bar_changed = 0;
709 pthread_spin_unlock(lock);
710
711 if(job_bar_changed_value || getmaxx(job_bar) != prev_width)
712 {
713 ui_stat_job_bar_redraw();
714 }
715
716 prev_width = getmaxx(job_bar);
717 }
718
719 /* Gets spinlock for the job_bar_changed variable in thread-safe way. Returns
720 * the lock. */
721 static pthread_spinlock_t *
get_job_bar_changed_lock(void)722 get_job_bar_changed_lock(void)
723 {
724 static pthread_once_t once = PTHREAD_ONCE_INIT;
725 pthread_once(&once, &init_job_bar_changed_lock);
726 return &job_bar_changed_lock;
727 }
728
729 /* Performs spinlock initialization at most once. */
730 static void
init_job_bar_changed_lock(void)731 init_job_bar_changed_lock(void)
732 {
733 int ret = pthread_spin_init(&job_bar_changed_lock, PTHREAD_PROCESS_PRIVATE);
734 assert(ret == 0 && "Failed to initialize spinlock!");
735 (void)ret;
736 }
737
738 /* Checks whether job bar is visible. Returns non-zero if so, and zero
739 * otherwise. */
740 static int
is_job_bar_visible(void)741 is_job_bar_visible(void)
742 {
743 /* Pretend that bar isn't visible in tests. */
744 return curr_stats.load_stage >= 2
745 && ui_stat_job_bar_height() != 0 && !is_in_menu_like_mode();
746 }
747
748 void
ui_stat_job_bar_redraw(void)749 ui_stat_job_bar_redraw(void)
750 {
751 if(!is_job_bar_visible())
752 {
753 return;
754 }
755
756 werase(job_bar);
757 checked_wmove(job_bar, 0, 0);
758 wprint(job_bar, format_job_bar());
759
760 wnoutrefresh(job_bar);
761 /* Update status_bar after job_bar just to ensure that it owns the cursor.
762 * Don't know a cleaner way of doing this. */
763 wnoutrefresh(status_bar);
764 doupdate();
765 }
766
767 /* Formats contents of the job bar. Returns pointer to statically allocated
768 * storage. */
769 static const char *
format_job_bar(void)770 format_job_bar(void)
771 {
772 enum { MAX_UTF_CHAR_LEN = 4 };
773
774 static char bar_text[512*MAX_UTF_CHAR_LEN + 1];
775
776 size_t i;
777 size_t text_width;
778 size_t width_used;
779 size_t max_width;
780 char **descrs;
781
782 descrs = take_job_descr_snapshot();
783
784 bar_text[0] = '\0';
785 text_width = 0U;
786
787 /* The check of stage is for tests. */
788 max_width = (curr_stats.load_stage < 2) ? 80 : getmaxx(job_bar);
789 width_used = 0U;
790 for(i = 0U; i < nbar_jobs; ++i)
791 {
792 const int progress = bar_jobs[i]->progress;
793 const unsigned int reserved = (progress == -1) ? 0U : 5U;
794 char item_text[max_width*MAX_UTF_CHAR_LEN + 1U];
795
796 const size_t width = (i == nbar_jobs - 1U)
797 ? (max_width - width_used)
798 : (max_width/nbar_jobs);
799
800 char *const ellipsed = left_ellipsis(descrs[i], width - 2U - reserved,
801 curr_stats.ellipsis);
802
803 if(progress == -1)
804 {
805 snprintf(item_text, sizeof(item_text), "[%s]", ellipsed);
806 }
807 else
808 {
809 snprintf(item_text, sizeof(item_text), "[%s %3d%%]", ellipsed, progress);
810 }
811
812 free(ellipsed);
813
814 (void)sstrappend(bar_text, &text_width, sizeof(bar_text), item_text);
815
816 width_used += width;
817 }
818
819 free_string_array(descrs, nbar_jobs);
820
821 return bar_text;
822 }
823
824 /* Makes snapshot of current job descriptions. Returns array of length
825 * nbar_jobs which should be freed via free_string_array(). */
826 static char **
take_job_descr_snapshot(void)827 take_job_descr_snapshot(void)
828 {
829 size_t i;
830 char **descrs;
831
832 descrs = reallocarray(NULL, nbar_jobs, sizeof(*descrs));
833 for(i = 0U; i < nbar_jobs; ++i)
834 {
835 const char *descr;
836
837 bg_op_lock(bar_jobs[i]);
838 descr = bar_jobs[i]->descr;
839 descrs[i] = strdup((descr == NULL) ? "UNKNOWN" : descr);
840 bg_op_unlock(bar_jobs[i]);
841 }
842
843 return descrs;
844 }
845
846 void
ui_stat_draw_popup_line(WINDOW * win,const char item[],const char descr[],size_t max_width)847 ui_stat_draw_popup_line(WINDOW *win, const char item[], const char descr[],
848 size_t max_width)
849 {
850 char *left, *right, *line;
851 const size_t text_width = utf8_strsw(item);
852 const size_t win_width = getmaxx(win);
853 const int align_columns = (max_width <= win_width/4);
854 const char *const fmt = align_columns ? "%-*s %-*s" : "%-*s %*s";
855 size_t width_left;
856 size_t item_width;
857
858 if(text_width >= win_width)
859 {
860 char *const whole = right_ellipsis(item, win_width, curr_stats.ellipsis);
861 wprint(win, whole);
862 free(whole);
863 return;
864 }
865
866 left = right_ellipsis(item, win_width - 3, curr_stats.ellipsis);
867
868 item_width = align_columns ? max_width : utf8_strsw(left);
869 width_left = win_width - 2 - MAX(item_width, utf8_strsw(left));
870
871 right = right_ellipsis(descr, width_left, curr_stats.ellipsis);
872
873 line = format_str(fmt, (int)item_width, left, (int)width_left, right);
874 free(left);
875 free(right);
876
877 wprint(win, line);
878
879 free(line);
880 }
881
882 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
883 /* vim: set cinoptions+=t0 filetype=c : */
884