void ui_stat_draw_popup_line(WINDOW *win, const char item[], const char descr[], size_t max_width) { char *left, *right, *line; const size_t text_width = utf8_strsw(item); const size_t win_width = getmaxx(win); const int align_columns = (max_width <= win_width/4); const char *const fmt = align_columns ? "%-*s %-*s" : "%-*s %*s"; size_t width_left; size_t item_width; if(text_width >= win_width) { char *const whole = right_ellipsis(item, win_width, curr_stats.ellipsis); wprint(win, whole); free(whole); return; } left = right_ellipsis(item, win_width - 3, curr_stats.ellipsis); item_width = align_columns ? max_width : utf8_strsw(left); width_left = win_width - 2 - MAX(item_width, utf8_strsw(left)); right = right_ellipsis(descr, width_left, curr_stats.ellipsis); line = format_str(fmt, (int)item_width, left, (int)width_left, right); free(left); free(right); wprint(win, line); free(line); }
/* Truncate the msg to the width by placing ellipsis in the middle and put the * result to the buffer. */ static void truncate_with_ellipsis(const char msg[], size_t width, char buffer[]) { const size_t screen_len = utf8_strsw(msg); const size_t screen_left_len = (width - 3)/2; const size_t screen_right_len = (width - 3) - screen_left_len; const size_t left = utf8_nstrsnlen(msg, screen_left_len); const size_t right = utf8_nstrsnlen(msg, screen_len - screen_right_len); strncpy(buffer, msg, left); strcpy(buffer + left, "..."); strcpy(buffer + left + 3, msg + right); assert(utf8_strsw(buffer) == width); }
/* Draws suggestion box and all its items (from completion list). */ static void draw_suggestion_box(void) { /* TODO: consider possibility of multicolumn display. */ const vle_compl_t *const items = vle_compl_get_items(); int height; WINDOW *const win = prepare_suggestion_box(&height); size_t max_title_width; int i; max_title_width = 0U; for(i = 0; i < height; ++i) { const size_t width = utf8_strsw(items[i].text); if(width > max_title_width) { max_title_width = width; } } for(i = 0; i < height; ++i) { checked_wmove(win, i, 0); ui_stat_draw_popup_line(win, items[i].text, items[i].descr, max_title_width); } ui_refresh_win(win); }
/* Draws box and title of the menu. */ static void draw_menu_frame(const menu_state_t *m) { const size_t title_len = getmaxx(menu_win) - 2*4; const char *const suffix = menu_and_view_are_in_sync(m->d, m->view) ? "" : replace_home_part(m->d->cwd); const char *const at = (suffix[0] == '\0' ? "" : " @ "); char *const title = format_str("%s%s%s", m->d->title, at, suffix); if(utf8_strsw(title) > title_len) { const size_t len = utf8_nstrsnlen(title, title_len - 3); strcpy(title + len, "..."); } box(menu_win, 0, 0); wattron(menu_win, A_BOLD); checked_wmove(menu_win, 0, 3); wprint(menu_win, " "); wprint(menu_win, title); wprint(menu_win, " "); wattroff(menu_win, A_BOLD); free(title); }
/* Draws search match highlight on the element. */ static void draw_search_match(char str[], int start, int end, int line, int width, int attrs) { const int len = strlen(str); if(end <= 0) { /* Match is completely at the left. */ checked_wmove(menu_win, line, 2); wprinta(menu_win, "<<<", attrs ^ A_REVERSE); } else if(start >= len) { /* Match is completely at the right. */ checked_wmove(menu_win, line, width - 3); wprinta(menu_win, ">>>", attrs ^ A_REVERSE); } else { /* Match is at least partially visible. */ char c; int match_start; if(start < 0) { start = 0; } if(end < len) { str[end] = '\0'; } /* Calculate number of screen characters before the match. */ c = str[start]; str[start] = '\0'; match_start = utf8_strsw(str); str[start] = c; checked_wmove(menu_win, line, 2 + match_start); wprinta(menu_win, str + start, attrs ^ (A_REVERSE | A_UNDERLINE)); } }
void ui_stat_update(view_t *view, int lazy_redraw) { if(!cfg.display_statusline || view != curr_view) { return; } /* Don't redraw anything until :restart command is finished. */ if(curr_stats.restart_in_progress) { return; } ui_stat_job_bar_check_for_updates(); if(cfg.status_line[0] == '\0') { update_stat_window_old(view, lazy_redraw); return; } const int width = getmaxx(stdscr); wresize(stat_win, 1, width); ui_set_bg(stat_win, &cfg.cs.color[STATUS_LINE_COLOR], cfg.cs.pair[STATUS_LINE_COLOR]); werase(stat_win); checked_wmove(stat_win, 0, 0); cchar_t default_attr; setcchar(&default_attr, L" ", cfg.cs.color[STATUS_LINE_COLOR].attr, cfg.cs.pair[STATUS_LINE_COLOR], NULL); LineWithAttrs result = expand_status_line_macros(view, cfg.status_line); assert(strlen(result.attrs) == utf8_strsw(result.line) && "Broken attrs!"); result.line = break_in_two(result.line, width, "%="); result.attrs = break_in_two(result.attrs, width, "="); print_with_attrs(stat_win, result.line, result.attrs, &default_attr); free(result.line); free(result.attrs); refresh_window(stat_win, lazy_redraw); }
static void status_bar_message_i(const char message[], int error) { /* TODO: Refactor this function status_bar_message_i() */ static char *msg; static int err; int len; const char *p, *q; int lines; int status_bar_lines; size_t screen_length; const char *out_msg; char truncated_msg[2048]; if(curr_stats.load_stage == 0) { return; } if(message != NULL) { if(replace_string(&msg, message)) { return; } err = error; save_status_bar_msg(msg); } if(msg == NULL || vle_mode_is(CMDLINE_MODE)) { return; } p = msg; q = msg - 1; status_bar_lines = 0; len = getmaxx(stdscr); while((q = strchr(q + 1, '\n')) != NULL) { status_bar_lines += DIV_ROUND_UP(q - p, len ); if(q == p) { ++status_bar_lines; } p = q + 1; } if(*p == '\0') { ++status_bar_lines; } screen_length = utf8_strsw(p); status_bar_lines += DIV_ROUND_UP(screen_length, len); if(status_bar_lines == 0) { status_bar_lines = 1; } lines = status_bar_lines; if(status_bar_lines > 1 || screen_length > (size_t)getmaxx(status_bar)) { ++lines; } out_msg = msg; if(lines > 1) { if(cfg.trunc_normal_sb_msgs && !err && curr_stats.allow_sb_msg_truncation) { truncate_with_ellipsis(msg, getmaxx(stdscr) - FIELDS_WIDTH(), truncated_msg); out_msg = truncated_msg; lines = 1; } else { const int extra = DIV_ROUND_UP(ARRAY_LEN(PRESS_ENTER_MSG) - 1, len) - 1; lines += extra; } } if(lines > getmaxy(stdscr)) { lines = getmaxy(stdscr); } (void)ui_stat_reposition(lines); mvwin(status_bar, getmaxy(stdscr) - lines, 0); if(lines == 1) { wresize(status_bar, lines, getmaxx(stdscr) - FIELDS_WIDTH()); } else { wresize(status_bar, lines, getmaxx(stdscr)); } checked_wmove(status_bar, 0, 0); if(err) { col_attr_t col = cfg.cs.color[CMD_LINE_COLOR]; mix_colors(&col, &cfg.cs.color[ERROR_MSG_COLOR]); wattron(status_bar, COLOR_PAIR(colmgr_get_pair(col.fg, col.bg)) | col.attr); } else { int attr = cfg.cs.color[CMD_LINE_COLOR].attr; wattron(status_bar, COLOR_PAIR(cfg.cs.pair[CMD_LINE_COLOR]) | attr); } werase(status_bar); wprint(status_bar, out_msg); multiline_status_bar = lines > 1; if(multiline_status_bar) { checked_wmove(status_bar, lines - DIV_ROUND_UP(ARRAY_LEN(PRESS_ENTER_MSG), len), 0); wclrtoeol(status_bar); if(lines < status_bar_lines) wprintw(status_bar, "%d of %d lines. ", lines, status_bar_lines); wprintw(status_bar, "%s", PRESS_ENTER_MSG); } wattrset(status_bar, 0); update_all_windows(); /* This is needed because update_all_windows() doesn't call doupdate() if * curr_stats.load_stage == 1. */ doupdate(); }
/* Draws single menu item at position specified by line argument. Non-zero * clear argument suppresses drawing current items in different color. */ static void draw_menu_item(menu_state_t *ms, int pos, int line, int clear) { menu_data_t *const m = ms->d; int i; int off; char *item_tail; const int width = (curr_stats.load_stage == 0) ? 100 : getmaxx(menu_win) - 2; /* Calculate color for the line. */ int attrs; col_attr_t col = cfg.cs.color[WIN_COLOR]; if(cfg.hl_search && ms->search_highlight && ms->matches != NULL && ms->matches[pos][0] >= 0) { cs_mix_colors(&col, &cfg.cs.color[SELECTED_COLOR]); } if(!clear && pos == m->pos) { cs_mix_colors(&col, &cfg.cs.color[CURR_LINE_COLOR]); } attrs = COLOR_PAIR(colmgr_get_pair(col.fg, col.bg)) | col.attr; /* Calculate offset of m->hor_pos's character in item text. */ off = 0; i = m->hor_pos; while(i-- > 0 && m->items[pos][off] != '\0') { off += utf8_chrw(m->items[pos] + off); } item_tail = strdup(m->items[pos] + off); replace_char(item_tail, '\t', ' '); wattron(menu_win, attrs); /* Clear the area. */ checked_wmove(menu_win, line, 1); if(curr_stats.load_stage != 0) { wprintw(menu_win, "%*s", width, ""); } /* Draw visible part of item. */ checked_wmove(menu_win, line, 2); if(utf8_strsw(item_tail) > (size_t)(width - 2)) { void *p; const size_t len = utf8_nstrsnlen(item_tail, width - 3 - 2 + 1); memset(item_tail + len, ' ', strlen(item_tail) - len); p = realloc(item_tail, len + 4); if(p != NULL) { item_tail = p; strcpy(item_tail + len - 1, "..."); } wprint(menu_win, item_tail); } else { const size_t len = utf8_nstrsnlen(item_tail, width - 2 + 1); item_tail[len] = '\0'; wprint(menu_win, item_tail); } wattroff(menu_win, attrs); if(ms->search_highlight && ms->matches != NULL && ms->matches[pos][0] >= 0) { draw_search_match(item_tail, ms->matches[pos][0] - m->hor_pos, ms->matches[pos][1] - m->hor_pos, line, width, attrs); } free(item_tail); }
/* Expands macros in the *format string advancing the pointer as it goes. The * opt represents conditional expression state, should be zero for non-recursive * calls. Returns newly allocated string, which should be freed by the * caller. */ static LineWithAttrs parse_view_macros(view_t *view, const char **format, const char macros[], int opt) { const dir_entry_t *const curr = get_current_entry(view); LineWithAttrs result = { .line = strdup(""), .attrs = strdup("") }; char c; int nexpansions = 0; int has_expander = 0; if(curr == NULL) { return result; } while((c = **format) != '\0') { size_t width = 0; int left_align = 0; char buf[PATH_MAX + 1]; const char *const next = ++*format; int skip, ok; if(c != '%' || (!char_is_one_of(macros, *next) && !isdigit(*next) && (*next != '=' || has_expander))) { if(strappendch(&result.line, &result.line_len, c) != 0) { break; } continue; } if(*next == '=') { (void)sync_attrs(&result, 0); if(strappend(&result.line, &result.line_len, "%=") != 0 || strappendch(&result.attrs, &result.attrs_len, '=') != 0) { break; } ++*format; has_expander = 1; continue; } if(*next == '-') { left_align = 1; ++*format; } while(isdigit(**format)) { width = width*10 + *(*format)++ - '0'; } c = *(*format)++; skip = 0; ok = 1; buf[0] = '\0'; switch(c) { case 'a': friendly_size_notation(get_free_space(curr_view->curr_dir), sizeof(buf), buf); break; case 't': format_entry_name(curr, NF_FULL, sizeof(buf), buf); break; case 'T': if(curr->type == FT_LINK) { char full_path[PATH_MAX + 1]; char link_path[PATH_MAX + 1]; //add by sim1 get_full_path_of(curr, sizeof(full_path), full_path); //mod by sim1 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< if(get_link_target(full_path, link_path, sizeof(link_path)) != 0) { copy_str(buf, sizeof(buf), "Failed to resolve link"); } else { snprintf(buf, sizeof(buf), " -> %s", link_path); } //mod by sim1 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> } break; case 'f': get_short_path_of(view, curr, NF_FULL, 0, sizeof(buf), buf); break; case 'A': #ifndef _WIN32 get_perm_string(buf, sizeof(buf), curr->mode); #else copy_str(buf, sizeof(buf), attr_str_long(curr->attrs)); #endif break; case 'u': get_uid_string(curr, 0, sizeof(buf), buf); break; case 'g': get_gid_string(curr, 0, sizeof(buf), buf); break; case 's': friendly_size_notation(fentry_get_size(view, curr), sizeof(buf), buf); break; //add by sim1 ************************************************ case 'r': { char path[PATH_MAX] = {0}; get_full_path_at(view, view->list_pos, sizeof(path), path); (void)get_rating_string(buf, sizeof(buf), path); } break; case 'n': { int nitems = !fentry_is_dir(curr) ? 0 : (int)fentry_get_nitems(view, curr); snprintf(buf, sizeof(buf), "%d", nitems); } break; //add by sim1 ************************************************ case 'E': { uint64_t size = 0U; typedef int (*iter_f)(view_t *view, dir_entry_t **entry); /* No current element for visual mode, since it can contain truly * empty selection when cursor is on ../ directory. */ iter_f iter = vle_mode_is(VISUAL_MODE) ? &iter_selected_entries : &iter_selection_or_current; dir_entry_t *entry = NULL; while(iter(view, &entry)) { size += fentry_get_size(view, entry); } friendly_size_notation(size, sizeof(buf), buf); } break; case 'd': { struct tm *tm_ptr = localtime(&curr->mtime); strftime(buf, sizeof(buf), cfg.time_format, tm_ptr); } break; case '-': case 'x': skip = expand_num(buf, sizeof(buf), view->filtered); break; case 'l': skip = expand_num(buf, sizeof(buf), view->list_pos + 1); break; case 'L': skip = expand_num(buf, sizeof(buf), view->list_rows + view->filtered); break; case 'S': skip = expand_num(buf, sizeof(buf), view->list_rows); break; case '%': copy_str(buf, sizeof(buf), "%"); break; case 'z': copy_str(buf, sizeof(buf), get_tip()); break; case 'D': if(curr_stats.number_of_windows == 1) { view_t *const other = (view == curr_view) ? other_view : curr_view; //mod by sim1 //copy_str(buf, sizeof(buf), replace_home_part(other->curr_dir)); snprintf(buf, sizeof(buf), " ‖ %s", replace_home_part(other->curr_dir)); } break; case '[': { LineWithAttrs opt = parse_view_macros(view, format, macros, 1); copy_str(buf, sizeof(buf), opt.line); free(opt.line); char *attrs = opt.attrs; if(sync_attrs(&result, 0) && opt.attrs_len > 0U) { if(*attrs != ' ') { result.attrs[result.attrs_len - 1U] = *attrs; } ++attrs; } strappend(&result.attrs, &result.attrs_len, attrs); free(opt.attrs); break; } case ']': if(opt) { if(nexpansions == 0) { replace_string(&result.line, ""); replace_string(&result.attrs, ""); result.line_len = 0U; result.attrs_len = 0U; } if(sync_attrs(&result, 0)) { result.attrs[--result.attrs_len] = '\0'; } return result; } LOG_INFO_MSG("Unmatched %%]"); ok = 0; break; case '{': { /* Try to find matching closing bracket * TODO: implement the way to escape it, so that the expr may contain * closing brackets */ const char *e = strchr(*format, '}'); char *expr = NULL, *resstr = NULL; var_t res = var_false(); ParsingErrors parsing_error; /* If there's no matching closing bracket, just add the opening one * literally */ if(e == NULL) { ok = 0; break; } /* Create a NULL-terminated copy of the given expr. * TODO: we could temporarily use buf for that, to avoid extra * allocation, but explicitly named variable reads better. */ expr = calloc(e - (*format) + 1 /* NUL-term */, 1); memcpy(expr, *format, e - (*format)); /* Try to parse expr, and convert the res to string if succeed. */ parsing_error = parse(expr, 0, &res); if(parsing_error == PE_NO_ERROR) { resstr = var_to_str(res); } if(resstr != NULL) { copy_str(buf, sizeof(buf), resstr); } else { copy_str(buf, sizeof(buf), "<Invalid expr>"); } var_free(res); free(resstr); free(expr); *format = e + 1 /* closing bracket */; } break; case '*': if(width > 9) { snprintf(buf, sizeof(buf), "%%%d*", (int)width); width = 0; break; } (void)sync_attrs(&result, 1); result.attrs[result.attrs_len - 1] = '0' + width; width = 0; break; default: LOG_INFO_MSG("Unexpected %%-sequence: %%%c", c); ok = 0; break; } if(char_is_one_of("tTAugsEd", c) && fentry_is_fake(curr)) { buf[0] = '\0'; } if(!ok) { *format = next; if(strappendch(&result.line, &result.line_len, '%') != 0) { break; } continue; } check_expanded_str(buf, skip, &nexpansions); stralign(buf, width, ' ', left_align); if(strappend(&result.line, &result.line_len, buf) != 0) { break; } } /* Unmatched %[. */ if(opt) { (void)strprepend(&result.line, &result.line_len, "%["); } if(sync_attrs(&result, 0)) { result.attrs[--result.attrs_len] = '\0'; } return result; } /* Makes sure that result->attrs has at least as many elements as result->line * contains characters + extra_width. Returns non-zero if result->attrs has * extra characters compared to result->line. */ static int sync_attrs(LineWithAttrs *result, int extra_width) { const size_t nchars = utf8_strsw(result->line) + extra_width; if(result->attrs_len < nchars) { char *const new_attrs = format_str("%s%*s", result->attrs, (int)(nchars - result->attrs_len), ""); free(result->attrs); result->attrs = new_attrs; result->attrs_len = nchars; } return (result->attrs_len > nchars); } /* Prints number into the buffer. Returns non-zero if numeric value is * "empty" (zero). */ static int expand_num(char buf[], size_t buf_len, int val) { snprintf(buf, buf_len, "%d", val); return (val == 0); }
/* Draws possibly centered formatted message with specified title and control * message on error_win. */ static void draw_msg(const char title[], const char msg[], const char ctrl_msg[], int centered, int recommended_width) { enum { margin = 1 }; int sw, sh; int w, h; int len; size_t ctrl_msg_n; size_t wctrl_msg; size_t i; curs_set(FALSE); getmaxyx(stdscr, sh, sw); ctrl_msg_n = MAX(measure_sub_lines(ctrl_msg, &wctrl_msg), 1U); h = sh - 2 - ctrl_msg_n + !cfg.display_statusline; /* The outermost condition is for VLA below (to calm static analyzers). */ w = MAX(2 + 2*margin, MIN(sw - 2, MAX(MAX(recommended_width, sw/3), (int)MAX(wctrl_msg, determine_width(msg)) + 4))); wresize(error_win, h, w); werase(error_win); len = strlen(msg); if(len <= w - 2 && strchr(msg, '\n') == NULL) { h = 5 + ctrl_msg_n; wresize(error_win, h, w); mvwin(error_win, (sh - h)/2, (sw - w)/2); checked_wmove(error_win, 2, (w - len)/2); wprint(error_win, msg); } else { int i; int cy = 2; i = 0; while(i < len) { int j; char buf[w - 2 - 2*margin + 1]; int cx; copy_str(buf, sizeof(buf), msg + i); for(j = 0; buf[j] != '\0'; j++) if(buf[j] == '\n') break; if(buf[j] != '\0') i++; buf[j] = '\0'; i += j; if(buf[0] == '\0') continue; h = cy + 4; wresize(error_win, h, w); mvwin(error_win, (sh - h)/2, (sw - w)/2); cx = centered ? (w - utf8_strsw(buf))/2 : (1 + margin); checked_wmove(error_win, cy++, cx); wprint(error_win, buf); } } box(error_win, 0, 0); if(title[0] != '\0') { mvwprintw(error_win, 0, (w - strlen(title) - 2)/2, " %s ", title); } /* Print control message line by line. */ for(i = ctrl_msg_n; i > 0U; --i) { const size_t len = strcspn(ctrl_msg, "\n"); mvwaddnstr(error_win, h - i - 1, MAX(0, (w - (int)len)/2), ctrl_msg, len); ctrl_msg = skip_char(ctrl_msg + len + 1U, '/'); } }
/* Draws possibly centered formatted message with specified title and control * message on error_win. */ static void draw_msg(const char title[], const char msg[], const char ctrl_msg[], int centered, int recommended_width) { enum { margin = 1 }; int sw, sh; int w, h; int max_h; int len; size_t ctrl_msg_n; size_t wctrl_msg; size_t i; int first_line_x = 1; const int first_line_y = 2; curs_set(0); getmaxyx(stdscr, sh, sw); ctrl_msg_n = MAX(measure_sub_lines(ctrl_msg, &wctrl_msg), 1U); max_h = sh - 2 - ctrl_msg_n + !cfg.display_statusline; h = max_h; /* The outermost condition is for VLA below (to calm static analyzers). */ w = MAX(2 + 2*margin, MIN(sw - 2, MAX(MAX(recommended_width, sw/3), (int)MAX(wctrl_msg, determine_width(msg)) + 4))); wresize(error_win, h, w); werase(error_win); len = strlen(msg); if(len <= w - 2 && strchr(msg, '\n') == NULL) { first_line_x = (w - len)/2; h = 5 + ctrl_msg_n; wresize(error_win, h, w); mvwin(error_win, (sh - h)/2, (sw - w)/2); checked_wmove(error_win, first_line_y, first_line_x); wprint(error_win, msg); } else { int i = 0; int cy = first_line_y; while(i < len) { int j; char buf[w - 2 - 2*margin + 1]; int cx; copy_str(buf, sizeof(buf), msg + i); for(j = 0; buf[j] != '\0'; j++) if(buf[j] == '\n') break; if(buf[j] != '\0') i++; buf[j] = '\0'; i += j; if(buf[0] == '\0') continue; if(cy >= max_h - (int)ctrl_msg_n - 3) { /* Skip trailing part of the message if it's too long, just print how * many lines we're omitting. */ size_t max_len; const int more_lines = 1U + measure_sub_lines(msg + i, &max_len); snprintf(buf, sizeof(buf), "<<%d more line%s not shown>>", more_lines, (more_lines == 1) ? "" : "s"); /* Make sure this is the last iteration of the loop. */ i = len; } h = 1 + cy + 1 + ctrl_msg_n + 1; wresize(error_win, h, w); mvwin(error_win, (sh - h)/2, (sw - w)/2); cx = centered ? (w - utf8_strsw(buf))/2 : (1 + margin); if(cy == first_line_y) { first_line_x = cx; } checked_wmove(error_win, cy++, cx); wprint(error_win, buf); } } box(error_win, 0, 0); if(title[0] != '\0') { mvwprintw(error_win, 0, (w - strlen(title) - 2)/2, " %s ", title); } /* Print control message line by line. */ for(i = ctrl_msg_n; i > 0U; --i) { const size_t len = strcspn(ctrl_msg, "\n"); mvwaddnstr(error_win, h - i - 1, MAX(0, (w - (int)len)/2), ctrl_msg, len); ctrl_msg = skip_char(ctrl_msg + len + 1U, '/'); } checked_wmove(error_win, first_line_y, first_line_x); }