/* Prints line onto a window highlighting it according to attrs, which should * specify 0-9 color groups for every character in line. */ static void print_with_attrs(WINDOW *win, const char line[], const char attrs[], const cchar_t *default_attr) { cchar_t attr = *default_attr; while(*line != '\0') { if(*attrs == '0') { attr = *default_attr; } else if(*attrs != ' ') { const int color = (USER1_COLOR + (*attrs - '1')); col_attr_t col = cfg.cs.color[STATUS_LINE_COLOR]; cs_mix_colors(&col, &cfg.cs.color[color]); setcchar(&attr, L" ", col.attr, colmgr_get_pair(col.fg, col.bg), NULL); } const size_t len = utf8_chrw(line); char char_buf[len + 1]; copy_str(char_buf, sizeof(char_buf), line); wprinta(win, char_buf, &attr, 0); line += len; attrs += utf8_chrsw(char_buf); } }
/* Adds ellipsis to the string in buf not changing enlarging its length (at most * three first or last characters are replaced). */ static void add_ellipsis(AlignType align, char buf[]) { const size_t len = get_width_on_screen(buf); const size_t dot_count = MIN(len, MAX_ELLIPSIS_DOT_COUNT); if(align == AT_LEFT) { const size_t width_limit = len - dot_count; const size_t pos = utf8_strsnlen(buf, width_limit); memset(buf + pos, '.', dot_count); buf[pos + dot_count] = '\0'; } else { const char *new_beginning = buf; size_t skipped = 0; while(skipped < dot_count) { skipped += utf8_chrsw(new_beginning); new_beginning += utf8_chrw(new_beginning); } memmove(buf + dot_count, new_beginning, strlen(new_beginning) + 1); memset(buf, '.', dot_count); } }
/* Returns number of characters at the beginning of the str which form one * logical symbol. Takes UTF-8 encoding and terminal escape sequences into * account. */ static size_t get_char_width_esc(const char str[]) { return (*str == '\033') ? (size_t)(after_first(str, 'm') - str) : utf8_chrw(str); }
/* Corrects offset inside the line so that it points to the char after previous * character instead of the beginning of the current one. */ static size_t correct_offset(const char line[], const int offsets[], size_t offset) { assert(offset != 0U && "Offset has to be greater than zero."); const int prev_offset = offsets[offset - 1]; const size_t char_width = utf8_chrw(line + prev_offset); return prev_offset + char_width; }
/* Adds decorations like ellipsis to the output. Returns actual align type used * for the column (might not match col->info.align). */ static AlignType decorate_output(const column_t *col, char buf[], size_t max_line_width) { const size_t len = get_width_on_screen(buf); const size_t max_col_width = calculate_max_width(col, len, max_line_width); const int too_long = len > max_col_width; AlignType result; if(!too_long) { return (col->info.align == AT_RIGHT ? AT_RIGHT : AT_LEFT); } if(col->info.align == AT_LEFT || (col->info.align == AT_DYN && len <= max_col_width)) { const size_t truncate_pos = utf8_strsnlen(buf, max_col_width); buf[truncate_pos] = '\0'; result = AT_LEFT; } else { int extra_spaces; const size_t truncate_pos = utf8_strsnlen(buf, len - max_col_width); const char *new_beginning = buf + truncate_pos; extra_spaces = 0; while(get_width_on_screen(new_beginning) > max_col_width) { ++extra_spaces; new_beginning += utf8_chrw(new_beginning); } memmove(buf + extra_spaces, new_beginning, strlen(new_beginning) + 1); if(extra_spaces != 0) { memset(buf, ' ', extra_spaces); } assert(get_width_on_screen(buf) == max_col_width && "Column isn't filled."); result = AT_RIGHT; } if(col->info.cropping == CT_ELLIPSIS) { add_ellipsis(result, buf); } return result; }
/* Returns number of characters at the beginning of the str which form one * logical symbol. Takes UTF-8 encoding and terminal escape sequences into * account. */ static size_t get_char_width_esc(const char str[]) { if(*str != '\033') { return utf8_chrw(str); } else { const char *pos = strchr(str, 'm'); pos = (pos == NULL) ? (str + strlen(str)) : (pos + 1); return pos - str; } }
/* Converts the leading character of the str string to a printable string. Puts * number of screen character positions taken by the resulting string * representation of a character into *screen_width. Returns pointer to a * statically allocated buffer. */ TSTATIC const char * strchar2str(const char str[], int pos, size_t *screen_width) { static char buf[32]; const size_t char_width = utf8_chrw(str); if(char_width != 1 || (unsigned char)str[0] >= (unsigned char)' ') { memcpy(buf, str, char_width); buf[char_width] = '\0'; *screen_width = vifm_wcwidth(get_first_wchar(str)); } else if(str[0] == '\n') { buf[0] = '\0'; *screen_width = 0; } else if(str[0] == '\b') { strcpy(buf, ""); *screen_width = 0; } else if(str[0] == '\r') { strcpy(buf, "<cr>"); *screen_width = 4; } else if(str[0] == '\t') { const size_t space_count = cfg.tab_stop - pos%cfg.tab_stop; memset(buf, ' ', space_count); buf[space_count] = '\0'; *screen_width = space_count; } else if(str[0] == '\033') { char *dst = buf; while(*str != 'm' && *str != '\0' && (size_t)(dst - buf) < sizeof(buf) - 2) { *dst++ = *str++; } if(*str != 'm') { buf[0] = '\0'; } else { *dst++ = 'm'; *dst = '\0'; } *screen_width = 0; } else if((unsigned char)str[0] < (unsigned char)' ') { buf[0] = '^'; buf[1] = ('A' - 1) + str[0]; buf[2] = '\0'; *screen_width = 2; } else { buf[0] = str[0]; buf[1] = '\0'; *screen_width = 1; } return buf; }
/* 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); }
/* Forms new line with highlights of matcher of the re regular expression using * escape sequences that invert colors. Returns NULL when no match found or * memory allocation error occurred. */ static char * add_pattern_highlights(const char line[], size_t len, const char no_esc[], const int offsets[], const regex_t *re) { /* XXX: this might benefit from a rewrite, logic of when escape sequences are * copied is unclear (sometimes along with first matched character, * sometimes before the match). */ regmatch_t match; char *next; char *processed; int no_esc_pos = 0; int overhead = 0; int offset; if(regexec(re, no_esc, 1, &match, 0) != 0) { return NULL; } if((processed = malloc(len + 1)) == NULL) { return NULL; } /* Before the first match. */ if(match.rm_so != 0 && no_esc[match.rm_so] == '\0') { /* This is needed to handle possibility of immediate break from the loop * below. */ offset = correct_offset(line, offsets, match.rm_so); } else { offset = offsets[match.rm_so]; } strncpy(processed, line, offset); next = processed + offset; /* All matches. */ do { int so_offset; void *ptr; const int empty_match = (match.rm_so == match.rm_eo); match.rm_so += no_esc_pos; match.rm_eo += no_esc_pos; so_offset = offsets[match.rm_so]; if(empty_match) { if(no_esc[match.rm_eo] == '\0') { no_esc_pos = match.rm_eo; break; } } /* Between matches. */ if(no_esc_pos != 0) { const int corrected = correct_offset(line, offsets, no_esc_pos); strncpy(next, line + corrected, so_offset - corrected); } if(empty_match) { /* Copy single character after the match to advance forward. */ /* Position inside the line string. */ const int esc_pos = (no_esc_pos == 0) ? (size_t)(next - processed) : correct_offset(line, offsets, no_esc_pos); /* Number of characters to copy from the line string. */ const int len = (match.rm_so == 0) ? utf8_chrw(no_esc) : correct_offset(line, offsets, match.rm_so + 1) - esc_pos; strncpy(next, line + esc_pos, len); next += len; no_esc_pos += utf8_chrw(&no_esc[no_esc_pos]); } else { size_t new_overhead; size_t match_len; new_overhead = INV_OVERHEAD*count_substr_chars(no_esc, &match); len += new_overhead; if((ptr = realloc(processed, len + 1)) == NULL) { free(processed); return NULL; } processed = ptr; match_len = correct_offset(line, offsets, match.rm_eo) - so_offset; next = processed + so_offset + overhead; next = add_highlighted_substr(line + so_offset, match_len, next); no_esc_pos = match.rm_eo; overhead += new_overhead; } } while(regexec(re, no_esc + no_esc_pos, 1, &match, 0) == 0); /* Abort if there were no non-empty matches. */ if(overhead == 0) { free(processed); return 0; } /* After the last match. */ strcpy(next, line + (no_esc_pos == 0 ? (size_t)(next - processed) : correct_offset(line, offsets, no_esc_pos))); return processed; }
int flist_find_group(const FileView *view, int next) { /* TODO: refactor/simplify this function (flist_find_group()). */ const int correction = next ? -1 : 0; const int lb = correction; const int ub = view->list_rows + correction; const int inc = next ? +1 : -1; int pos = view->list_pos; dir_entry_t *pentry = &view->dir_entry[pos]; const char *ext = get_last_ext(pentry->name); size_t char_width = utf8_chrw(pentry->name); wchar_t ch = towupper(get_first_wchar(pentry->name)); const SortingKey sorting_key = flist_custom_active(view) && cv_compare(view->custom.type) ? SK_BY_ID : abs(view->sort[0]); const int is_dir = fentry_is_dir(pentry); const char *const type_str = get_type_str(pentry->type); regmatch_t pmatch = { .rm_so = 0, .rm_eo = 0 }; #ifndef _WIN32 char perms[16]; get_perm_string(perms, sizeof(perms), pentry->mode); #endif if(sorting_key == SK_BY_GROUPS) { pmatch = get_group_match(&view->primary_group, pentry->name); } while(pos > lb && pos < ub) { dir_entry_t *nentry; pos += inc; nentry = &view->dir_entry[pos]; switch(sorting_key) { case SK_BY_FILEEXT: if(fentry_is_dir(nentry)) { if(strncmp(pentry->name, nentry->name, char_width) != 0) { return pos; } } if(strcmp(get_last_ext(nentry->name), ext) != 0) { return pos; } break; case SK_BY_EXTENSION: if(strcmp(get_last_ext(nentry->name), ext) != 0) return pos; break; case SK_BY_GROUPS: { regmatch_t nmatch = get_group_match(&view->primary_group, nentry->name); if(pmatch.rm_eo - pmatch.rm_so != nmatch.rm_eo - nmatch.rm_so || (pmatch.rm_eo != pmatch.rm_so && strncmp(pentry->name + pmatch.rm_so, nentry->name + nmatch.rm_so, pmatch.rm_eo - pmatch.rm_so + 1U) != 0)) return pos; } break; case SK_BY_TARGET: if((nentry->type == FT_LINK) != (pentry->type == FT_LINK)) { /* One of the entries is not a link. */ return pos; } if(nentry->type == FT_LINK) { /* Both entries are symbolic links. */ char full_path[PATH_MAX]; char nlink[PATH_MAX], plink[PATH_MAX]; get_full_path_of(nentry, sizeof(full_path), full_path); if(get_link_target(full_path, nlink, sizeof(nlink)) != 0) { return pos; } get_full_path_of(pentry, sizeof(full_path), full_path); if(get_link_target(full_path, plink, sizeof(plink)) != 0) { return pos; } if(stroscmp(nlink, plink) != 0) { return pos; } } break; case SK_BY_NAME: if(strncmp(pentry->name, nentry->name, char_width) != 0) return pos; break; case SK_BY_INAME: if((wchar_t)towupper(get_first_wchar(nentry->name)) != ch) return pos; break; case SK_BY_SIZE: if(nentry->size != pentry->size) return pos; break; case SK_BY_NITEMS: if(entry_get_nitems(view, nentry) != entry_get_nitems(view, pentry)) return pos; break; case SK_BY_TIME_ACCESSED: if(nentry->atime != pentry->atime) return pos; break; case SK_BY_TIME_CHANGED: if(nentry->ctime != pentry->ctime) return pos; break; case SK_BY_TIME_MODIFIED: if(nentry->mtime != pentry->mtime) return pos; break; case SK_BY_DIR: if(is_dir != fentry_is_dir(nentry)) { return pos; } break; case SK_BY_TYPE: if(get_type_str(nentry->type) != type_str) { return pos; } break; #ifndef _WIN32 case SK_BY_GROUP_NAME: case SK_BY_GROUP_ID: if(nentry->gid != pentry->gid) return pos; break; case SK_BY_OWNER_NAME: case SK_BY_OWNER_ID: if(nentry->uid != pentry->uid) return pos; break; case SK_BY_MODE: if(nentry->mode != pentry->mode) return pos; break; case SK_BY_PERMISSIONS: { char nperms[16]; get_perm_string(nperms, sizeof(nperms), nentry->mode); if(strcmp(nperms, perms) != 0) { return pos; } break; } case SK_BY_NLINKS: if(nentry->nlinks != pentry->nlinks) { return pos; } break; #endif } /* Id sorting is builtin only and is defined outside SortingKey * enumeration. */ if((int)sorting_key == SK_BY_ID) { if(nentry->id != pentry->id) { return pos; } } } return pos; } /* Finds pointer to the beginning of the last extension of the file name. * Returns the pointer, which might point to the NUL byte if there are no * extensions. */ static const char * get_last_ext(const char name[]) { const char *const ext = strrchr(name, '.'); return (ext == NULL) ? (name + strlen(name)) : (ext + 1); } int flist_find_dir_group(const FileView *view, int next) { const int correction = next ? -1 : 0; const int lb = correction; const int ub = view->list_rows + correction; const int inc = next ? +1 : -1; int pos = curr_view->list_pos; dir_entry_t *pentry = &curr_view->dir_entry[pos]; const int is_dir = fentry_is_dir(pentry); while(pos > lb && pos < ub) { dir_entry_t *nentry; pos += inc; nentry = &curr_view->dir_entry[pos]; if(is_dir != fentry_is_dir(nentry)) { break; } } return pos; } int flist_first_sibling(const FileView *view) { const int parent = view->list_pos - view->dir_entry[view->list_pos].child_pos; return (parent == view->list_pos ? 0 : parent + 1); } int flist_last_sibling(const FileView *view) { int pos = view->list_pos - view->dir_entry[view->list_pos].child_pos; if(pos == view->list_pos) { /* For top-level entry, find the last top-level entry. */ pos = view->list_rows - 1; while(view->dir_entry[pos].child_pos != 0) { pos -= view->dir_entry[pos].child_pos; } } else { /* For non-top-level entry, go to last tree item and go up until our * child. */ const int parent = pos; pos = parent + view->dir_entry[parent].child_count; while(pos - view->dir_entry[pos].child_pos != parent) { pos -= view->dir_entry[pos].child_pos; } } return pos; }