diff --git a/Makefile b/Makefile index e215440..34654df 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ all: $(NAME) clean: rm -f $(NAME) -$(NAME): $(NAME).c bterm.h bb.h +$(NAME): $(NAME).c bterm.h bb.h columns.h $(CC) $(NAME).c $(CFLAGS) $(CWARN) $(G) $(O) -o $(NAME) install: $(NAME) diff --git a/bb.c b/bb.c index 327a787..8eec2ee 100644 --- a/bb.c +++ b/bb.c @@ -84,18 +84,6 @@ void cleanup(void) } } -/* - * Returns the color of a file listing, given its mode. - */ -const char* color_of(mode_t mode) -{ - if (S_ISDIR(mode)) return DIR_COLOR; - else if (S_ISLNK(mode)) return LINK_COLOR; - else if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) - return EXECUTABLE_COLOR; - else return NORMAL_COLOR; -} - /* * Used for sorting, this function compares files according to the sorting-related options, * like bb->sort @@ -213,7 +201,7 @@ void handle_next_key_binding(bb_t *bb) if (mouse_x != -1 && mouse_y != -1) { // Get bb column: for (int col = 0, x = 0; bb->columns[col]; col++, x++) { - x += columns[(int)bb->columns[col]].width; + x += (int)strlen(columns[(int)bb->columns[col]].name); if (x >= mouse_x) { bbmousecol[0] = bb->columns[col]; break; @@ -735,7 +723,7 @@ void run_bbcmd(bb_t *bb, const char *cmd) void render(bb_t *bb) { static int lastcursor = -1, lastscroll = -1; - char buf[64]; + char buf[PATH_MAX * 2]; if (!dirty) { // Use terminal scrolling: @@ -785,7 +773,7 @@ void render(bb_t *bb) move_cursor(tty_out, x, 1); fputs(indicator, tty_out); fputs(title, tty_out); - x += columns[(int)bb->columns[col]].width; + x += (int)strlen(title) + 1; } fputs(" \033[K\033[0m", tty_out); } @@ -819,97 +807,24 @@ void render(bb_t *bb) entry_t *entry = files[i]; if (i == bb->cursor) fputs(CURSOR_COLOR, tty_out); - int use_fullname = strcmp(bb->path, "") == 0; int x = 0; - for (int col = 0; bb->columns[col]; col++) { + for (int c = 0; bb->columns[c]; c++) { + column_t col = columns[(int)bb->columns[c]]; + memset(buf, 0, sizeof(buf)); + col.render(bb, entry, buf, strlen(col.name)+1); + fprintf(tty_out, "\033[%d;%dH\033[K", y+1, x+1); - if (col > 0) { + if (col.name == NULL) continue; + if (c > 0) { if (i == bb->cursor) fputs("┃", tty_out); else fputs("\033[37;2m┃\033[22m", tty_out); fputs(i == bb->cursor ? CURSOR_COLOR : "\033[0m", tty_out); x += 1; } move_cursor(tty_out, x, y); - switch (bb->columns[col]) { - case COL_SELECTED: - fputs(IS_SELECTED(entry) ? SELECTED_INDICATOR : NOT_SELECTED_INDICATOR, tty_out); - fputs(i == bb->cursor ? CURSOR_COLOR : "\033[0m", tty_out); - break; - - case COL_RANDOM: { - double k = (double)entry->shufflepos/(double)bb->nfiles; - int color = (int)(k*232 + (1.-k)*255); - fprintf(tty_out, "\033[48;5;%dm \033[0m%s", color, - i == bb->cursor ? CURSOR_COLOR : "\033[0m"); - break; - } - - case COL_SIZE: { - int j = 0; - const char* units = "BKMGTPEZY"; - double bytes = (double)entry->info.st_size; - while (bytes > 1024) { - bytes /= 1024; - j++; - } - fprintf(tty_out, " %6.*f%c ", j > 0 ? 1 : 0, bytes, units[j]); - break; - } - - case COL_MTIME: - strftime(buf, sizeof(buf), BB_TIME_FMT, localtime(&(entry->info.st_mtime))); - fputs(buf, tty_out); - break; - - case COL_CTIME: - strftime(buf, sizeof(buf), BB_TIME_FMT, localtime(&(entry->info.st_ctime))); - fputs(buf, tty_out); - break; - - case COL_ATIME: - strftime(buf, sizeof(buf), BB_TIME_FMT, localtime(&(entry->info.st_atime))); - fputs(buf, tty_out); - break; - - case COL_PERM: - fprintf(tty_out, " %03o", entry->info.st_mode & 0777); - break; - - case COL_NAME: { - char color[128]; - strcpy(color, color_of(entry->info.st_mode)); - if (i == bb->cursor) strcat(color, CURSOR_COLOR); - fputs(color, tty_out); - - char *name = use_fullname ? entry->fullname : entry->name; - if (entry->no_esc) fputs(name, tty_out); - else entry->no_esc |= !fputs_escaped(tty_out, name, color); - - if (E_ISDIR(entry)) fputs("/", tty_out); - - if (entry->linkname) { - if (i != bb->cursor) - fputs("\033[37m", tty_out); - fputs("\033[2m -> \033[3m", tty_out); - strcpy(color, color_of(entry->linkedmode)); - if (i == bb->cursor) strcat(color, CURSOR_COLOR); - strcat(color, "\033[3;2m"); - fputs(color, tty_out); - if (entry->link_no_esc) fputs(entry->linkname, tty_out); - else entry->link_no_esc |= !fputs_escaped(tty_out, entry->linkname, color); - - if (S_ISDIR(entry->linkedmode)) - fputs("/", tty_out); - - fputs("\033[22;23m", tty_out); - } - fputs(i == bb->cursor ? CURSOR_COLOR : "\033[0m", tty_out); - fputs("\033[K", tty_out); - break; - } - default: break; - } - x += columns[(int)bb->columns[col]].width; + fputs(buf, tty_out); + fputs("\033[K", tty_out); + x += (int)strlen(col.name) + 1; } fputs(" \033[K\033[0m", tty_out); // Reset color and attributes } diff --git a/bb.h b/bb.h index 5a9a86b..fccc6ee 100644 --- a/bb.h +++ b/bb.h @@ -97,11 +97,6 @@ typedef struct { char *description; } binding_t; -typedef struct { - int width; - const char *name; -} column_t; - typedef enum { COL_NONE = 0, COL_NAME = 'n', @@ -181,24 +176,11 @@ typedef struct proc_s { static binding_t bindings[MAX_BINDINGS]; -// Column widths and titles: -static const column_t columns[] = { - ['*'] = {2, "*"}, - ['a'] = {18, " Accessed"}, - ['c'] = {18, " Created"}, - ['m'] = {18, " Modified"}, - ['n'] = {40, "Name"}, - ['p'] = {5, "Permissions"}, - ['r'] = {2, "Random"}, - ['s'] = {9, " Size"}, -}; - // Functions void bb_browse(bb_t *bb, const char *initial_path); static void check_cmdfile(bb_t *bb); static void cleanup(void); static void cleanup_and_raise(int sig); -static const char* color_of(mode_t mode); #ifdef __APPLE__ static int compare_files(void *v, const void *v1, const void *v2); #else @@ -340,4 +322,6 @@ static const char *runstartup = " fi;\n" "done\n"; +#include "columns.h" + // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1 diff --git a/columns.h b/columns.h new file mode 100644 index 0000000..f8f685c --- /dev/null +++ b/columns.h @@ -0,0 +1,196 @@ +/* + * This file contains logic for drawing columns. + */ + +static void lpad(char *buf, int width) +{ + int len = strlen(buf); + if (len < width) { + int pad = width - len; + memmove(&buf[pad], buf, (size_t)len + 1); + while (pad > 0) buf[--pad] = ' '; + } +} + +/* + * Returns the color of a file listing, given its mode. + */ +static const char* color_of(mode_t mode) +{ + if (S_ISDIR(mode)) return DIR_COLOR; + else if (S_ISLNK(mode)) return LINK_COLOR; + else if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) + return EXECUTABLE_COLOR; + else return NORMAL_COLOR; +} + +static char* stpcpy_escaped(char *buf, const char *str, const char *color) +{ + static const char *escapes = " abtnvfr e"; + for (const char *c = str; *c; ++c) { + if (*c > 0 && *c <= '\x1b' && escapes[(int)*c] != ' ') { // "\n", etc. + buf += sprintf(buf, "\033[31m\\%c%s", escapes[(int)*c], color); + } else if (*c >= 0 && !(' ' <= *c && *c <= '~')) { // "\x02", etc. + buf += sprintf(buf, "\033[31m\\x%02X%s", *c, color); + } else { + *(buf++) = *c; + } + } + *(buf++) = '\0'; + return buf; +} + +static void timeago(char *buf, time_t t) +{ + const int SECOND = 1; + const int MINUTE = 60 * SECOND; + const int HOUR = 60 * MINUTE; + const int DAY = 24 * HOUR; + const int MONTH = 30 * DAY; + const int YEAR = 365 * DAY; + + time_t now = time(0); + double delta = difftime(now, t); + + if (delta < 1.5) + sprintf(buf, "a second"); + else if (delta < 1 * MINUTE) + sprintf(buf, "%d seconds", (int)delta); + else if (delta < 2 * MINUTE) + sprintf(buf, "a minute"); + else if (delta < 1 * HOUR) + sprintf(buf, "%d minutes", (int)delta/MINUTE); + else if (delta < 2 * HOUR) + sprintf(buf, "an hour"); + else if (delta < 1 * DAY) + sprintf(buf, "%d hours", (int)delta/HOUR); + else if (delta < 2 * DAY) + sprintf(buf, "yesterday"); + else if (delta < 1 * MONTH) + sprintf(buf, "%d days", (int)delta/DAY); + else if (delta < 2 * MONTH) + sprintf(buf, "a month"); + else if (delta < 1 * YEAR) + sprintf(buf, "%d months", (int)delta/MONTH); + else if (delta < 2 * YEAR) + sprintf(buf, "a year"); + else + sprintf(buf, "%d years", (int)delta/YEAR); +} + +void col_mreltime(bb_t *bb, entry_t *entry, char *buf, int width) { + (void)bb; + timeago(buf, entry->info.st_mtime); + lpad(buf, width); +} + +void col_areltime(bb_t *bb, entry_t *entry, char *buf, int width) { + (void)bb; + timeago(buf, entry->info.st_atime); + lpad(buf, width); +} + +void col_creltime(bb_t *bb, entry_t *entry, char *buf, int width) { + (void)bb; + timeago(buf, entry->info.st_ctime); + lpad(buf, width); +} + +void col_mtime(bb_t *bb, entry_t *entry, char *buf, int width) { + (void)bb; + strftime(buf, (size_t)width, BB_TIME_FMT, localtime(&(entry->info.st_mtime))); +} + +void col_atime(bb_t *bb, entry_t *entry, char *buf, int width) { + (void)bb; + strftime(buf, (size_t)width, BB_TIME_FMT, localtime(&(entry->info.st_atime))); +} + +void col_ctime(bb_t *bb, entry_t *entry, char *buf, int width) { + (void)bb; + strftime(buf, (size_t)width, BB_TIME_FMT, localtime(&(entry->info.st_ctime))); +} + +void col_selected(bb_t *bb, entry_t *entry, char *buf, int width) { + (void)bb; (void)width; + strcpy(buf, IS_SELECTED(entry) ? SELECTED_INDICATOR : NOT_SELECTED_INDICATOR); +} + +void col_perm(bb_t *bb, entry_t *entry, char *buf, int width) { + (void)bb; (void)width; + sprintf(buf, " %03o", entry->info.st_mode & 0777); +} + +void col_random(bb_t *bb, entry_t *entry, char *buf, int width) +{ + (void)width; + double k = (double)entry->shufflepos/(double)bb->nfiles; + int color = (int)(k*232 + (1.-k)*255); + sprintf(buf, "\033[48;5;%dm \033[0m%s", color, + entry == bb->files[bb->cursor] ? CURSOR_COLOR : "\033[0m"); +} + +void col_size(bb_t *bb, entry_t *entry, char *buf, int width) +{ + (void)bb; (void)width; + int j = 0; + const char* units = "BKMGTPEZY"; + double bytes = (double)entry->info.st_size; + while (bytes > 1024) { + bytes /= 1024; + j++; + } + sprintf(buf, " %6.*f%c ", j > 0 ? 1 : 0, bytes, units[j]); +} + +void col_name(bb_t *bb, entry_t *entry, char *buf, int width) +{ + (void)width; + char color[128]; + strcpy(color, color_of(entry->info.st_mode)); + if (entry == bb->files[bb->cursor]) strcpy(color, CURSOR_COLOR); + buf = stpcpy(buf, color); + + int use_fullname = strcmp(bb->path, "") == 0; + char *name = use_fullname ? entry->fullname : entry->name; + if (entry->no_esc) buf = stpcpy(buf, name); + else buf = stpcpy_escaped(buf, name, color); + + if (E_ISDIR(entry)) buf = stpcpy(buf, "/"); + + if (!entry->linkname) return; + + if (entry != bb->files[bb->cursor]) + buf = stpcpy(buf, "\033[37m"); + buf = stpcpy(buf, "\033[2m -> \033[3m"); + strcpy(color, color_of(entry->linkedmode)); + if (entry == bb->files[bb->cursor]) strcat(color, CURSOR_COLOR); + strcat(color, "\033[3;2m"); + buf = stpcpy(buf, color); + if (entry->link_no_esc) buf = stpcpy(buf, entry->linkname); + else buf = stpcpy_escaped(buf, entry->linkname, color); + + if (S_ISDIR(entry->linkedmode)) + buf = stpcpy(buf, "/"); + + buf = stpcpy(buf, "\033[22;23m"); +} + +typedef struct { + const char *name; + void (*render)(bb_t *, entry_t *, char *, int); +} column_t; + +static column_t columns[255] = { + ['*'] = {.name = "*", .render = col_selected}, + ['n'] = {.name = "Name ", .render = col_name}, + ['s'] = {.name = " Size", .render = col_size}, + ['p'] = {.name = "Perm", .render = col_perm}, + ['m'] = {.name = "Modified", .render = col_mreltime}, + ['M'] = {.name = " Modified ", .render = col_mtime}, + ['a'] = {.name = "Accessed", .render = col_areltime}, + ['A'] = {.name = " Accessed ", .render = col_atime}, + ['c'] = {.name = " Created", .render = col_creltime}, + ['C'] = {.name = " Created ", .render = col_ctime}, + ['r'] = {.name = "R", .render = col_random}, +};