A bit of refactoring, moving more rendering code into draw.c
This commit is contained in:
parent
5371a49ce0
commit
845f8e42c6
2
Makefile
2
Makefile
@ -8,7 +8,7 @@ CWARN=-Wall -Wpedantic -Wextra -Wno-unknown-pragmas -Wno-missing-field-initializ
|
||||
-Wno-padded -Wsign-conversion -Wno-missing-noreturn -Wno-cast-qual -Wtype-limits
|
||||
#CFLAGS += -fsanitize=address -fno-omit-frame-pointer
|
||||
|
||||
CFILES=columns.c bterm.c
|
||||
CFILES=draw.c bterm.c
|
||||
OBJFILES=$(CFILES:.c=.o)
|
||||
|
||||
all: $(NAME)
|
||||
|
251
bb.c
251
bb.c
@ -21,20 +21,18 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "bb.h"
|
||||
#include "columns.h"
|
||||
#include "draw.h"
|
||||
|
||||
// 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
|
||||
static int compare_files(const void *v1, const void *v2, void *v);
|
||||
#endif
|
||||
static int fputs_escaped(FILE *f, const char *str, const char *color);
|
||||
static void handle_next_key_binding(bb_t *bb);
|
||||
static void init_term(void);
|
||||
static int is_simple_bbcmd(const char *s);
|
||||
@ -45,7 +43,6 @@ static char* normalize_path(const char *root, const char *path, char *pbuf);
|
||||
static int populate_files(bb_t *bb, const char *path);
|
||||
static void print_bindings(int fd);
|
||||
static void run_bbcmd(bb_t *bb, const char *cmd);
|
||||
static void render(bb_t *bb);
|
||||
static void restore_term(const struct termios *term);
|
||||
static int run_script(bb_t *bb, const char *cmd);
|
||||
static void set_columns(bb_t *bb, const char *cols);
|
||||
@ -90,20 +87,6 @@ static const struct termios default_termios = {
|
||||
static const char *description_str = "bb - an itty bitty console TUI file browser\n";
|
||||
static const char *usage_str = "Usage: bb (-h/--help | -v/--version | -s | -d | -0 | +command)* [[--] directory]\n";
|
||||
|
||||
column_t columns[255] = {
|
||||
['*'] = {.name = "*", .render = col_selected},
|
||||
['n'] = {.name = "Name", .render = col_name, .stretchy = 1},
|
||||
['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 = "Random", .render = col_random},
|
||||
};
|
||||
|
||||
|
||||
// Variables used within this file to track global state
|
||||
static binding_t bindings[MAX_BINDINGS];
|
||||
@ -111,8 +94,7 @@ static struct termios orig_termios, bb_termios;
|
||||
static FILE *tty_out = NULL, *tty_in = NULL;
|
||||
static struct winsize winsize = {0};
|
||||
static char cmdfilename[PATH_MAX] = {0};
|
||||
static proc_t *running_procs = NULL;
|
||||
static int dirty = 1;
|
||||
static bb_t *current_bb = NULL;
|
||||
|
||||
/*
|
||||
* Use bb to browse the filesystem.
|
||||
@ -124,7 +106,7 @@ void bb_browse(bb_t *bb, const char *initial_path)
|
||||
run_script(bb, "bbstartup");
|
||||
check_cmdfile(bb);
|
||||
while (!bb->should_quit) {
|
||||
render(bb);
|
||||
render(tty_out, bb);
|
||||
handle_next_key_binding(bb);
|
||||
}
|
||||
run_script(bb, "bbshutdown");
|
||||
@ -157,8 +139,12 @@ static void cleanup_and_raise(int sig)
|
||||
{
|
||||
cleanup();
|
||||
int childsig = (sig == SIGTSTP || sig == SIGSTOP) ? sig : SIGHUP;
|
||||
for (proc_t *p = running_procs; p; p = p->running.next)
|
||||
kill(p->pid, childsig);
|
||||
if (current_bb) {
|
||||
for (proc_t *p = current_bb->running_procs; p; p = p->running.next) {
|
||||
kill(p->pid, childsig);
|
||||
LL_REMOVE(p, running);
|
||||
}
|
||||
}
|
||||
raise(sig);
|
||||
// This code will only ever be run if sig is SIGTSTP/SIGSTOP, otherwise, raise() won't return:
|
||||
init_term();
|
||||
@ -182,18 +168,6 @@ static void cleanup(void)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Used for sorting, this function compares files according to the sorting-related options,
|
||||
* like bb->sort
|
||||
@ -261,29 +235,6 @@ static int compare_files(const void *v1, const void *v2, void *v)
|
||||
#undef COMPARE_TIME
|
||||
}
|
||||
|
||||
/*
|
||||
* Print a string, but replacing bytes like '\n' with a red-colored "\n".
|
||||
* The color argument is what color to put back after the red.
|
||||
* Returns the number of bytes that were escaped.
|
||||
*/
|
||||
static int fputs_escaped(FILE *f, const char *str, const char *color)
|
||||
{
|
||||
static const char *escapes = " abtnvfr e";
|
||||
int escaped = 0;
|
||||
for (const char *c = str; *c; ++c) {
|
||||
if (*c > 0 && *c <= '\x1b' && escapes[(int)*c] != ' ') { // "\n", etc.
|
||||
fprintf(f, "\033[31m\\%c%s", escapes[(int)*c], color);
|
||||
++escaped;
|
||||
} else if (*c >= 0 && !(' ' <= *c && *c <= '~')) { // "\x02", etc.
|
||||
fprintf(f, "\033[31m\\x%02X%s", *c, color);
|
||||
++escaped;
|
||||
} else {
|
||||
fputc(*c, f);
|
||||
}
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
/*
|
||||
* Wait until the user has pressed a key with an associated key binding and run
|
||||
* that binding.
|
||||
@ -295,7 +246,7 @@ static void handle_next_key_binding(bb_t *bb)
|
||||
do {
|
||||
do {
|
||||
key = bgetkey(tty_in, &mouse_x, &mouse_y);
|
||||
if (key == -1 && dirty) return;
|
||||
if (key == -1 && bb->dirty) return;
|
||||
} while (key == -1);
|
||||
|
||||
binding = NULL;
|
||||
@ -309,10 +260,10 @@ static void handle_next_key_binding(bb_t *bb)
|
||||
|
||||
char bbmousecol[2] = {0, 0}, bbclicked[PATH_MAX];
|
||||
if (mouse_x != -1 && mouse_y != -1) {
|
||||
int *colwidths = get_column_widths(bb->columns, winsize.ws_col-1);
|
||||
// Get bb column:
|
||||
for (int col = 0, x = 0; bb->columns[col]; col++, x++) {
|
||||
if (!columns[(int)bb->columns[col]].name) continue;
|
||||
x += (int)strlen(columns[(int)bb->columns[col]].name);
|
||||
x += colwidths[col];
|
||||
if (x >= mouse_x) {
|
||||
bbmousecol[0] = bb->columns[col];
|
||||
break;
|
||||
@ -522,7 +473,7 @@ static int populate_files(bb_t *bb, const char *path)
|
||||
setenv("BBPREVPATH", bb->prev_path, 1);
|
||||
}
|
||||
|
||||
dirty = 1;
|
||||
bb->dirty = 1;
|
||||
strcpy(bb->path, pbuf);
|
||||
set_title(bb);
|
||||
|
||||
@ -710,10 +661,10 @@ static void run_bbcmd(bb_t *bb, const char *cmd)
|
||||
}
|
||||
} else if (matches_cmd(cmd, "fg:") || matches_cmd(cmd, "fg")) { // +fg:
|
||||
int nprocs = 0;
|
||||
for (proc_t *p = running_procs; p; p = p->running.next) ++nprocs;
|
||||
for (proc_t *p = bb->running_procs; p; p = p->running.next) ++nprocs;
|
||||
int fg = value ? nprocs - (int)strtol(value, NULL, 10) : 0;
|
||||
proc_t *child = NULL;
|
||||
for (proc_t *p = running_procs; p && !child; p = p->running.next) {
|
||||
for (proc_t *p = bb->running_procs; p && !child; p = p->running.next) {
|
||||
if (fg-- == 0) child = p;
|
||||
}
|
||||
if (!child) return;
|
||||
@ -728,7 +679,7 @@ static void run_bbcmd(bb_t *bb, const char *cmd)
|
||||
signal(SIGTTOU, SIG_DFL);
|
||||
init_term();
|
||||
set_title(bb);
|
||||
dirty = 1;
|
||||
bb->dirty = 1;
|
||||
} else if (matches_cmd(cmd, "glob:")) { // +glob:
|
||||
set_globs(bb, value[0] ? value : "*");
|
||||
populate_files(bb, bb->path);
|
||||
@ -826,163 +777,6 @@ static void run_bbcmd(bb_t *bb, const char *cmd)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Draw everything to the screen.
|
||||
* If `dirty` is false, then use terminal scrolling to move the file listing
|
||||
* around and only update the files that have changed.
|
||||
*/
|
||||
static void render(bb_t *bb)
|
||||
{
|
||||
static int lastcursor = -1, lastscroll = -1;
|
||||
|
||||
if (!dirty) {
|
||||
// Use terminal scrolling:
|
||||
if (lastscroll > bb->scroll) {
|
||||
fprintf(tty_out, "\033[3;%dr\033[%dT\033[1;%dr", winsize.ws_row-1, lastscroll - bb->scroll, winsize.ws_row);
|
||||
} else if (lastscroll < bb->scroll) {
|
||||
fprintf(tty_out, "\033[3;%dr\033[%dS\033[1;%dr", winsize.ws_row-1, bb->scroll - lastscroll, winsize.ws_row);
|
||||
}
|
||||
}
|
||||
|
||||
int colwidths[MAX_COLS] = {0};
|
||||
int space = winsize.ws_col - 1, nstretchy = 0;
|
||||
for (int c = 0; bb->columns[c]; c++) {
|
||||
column_t col = columns[(int)bb->columns[c]];
|
||||
if (!col.name) continue;
|
||||
if (col.stretchy) {
|
||||
++nstretchy;
|
||||
} else {
|
||||
colwidths[c] = strlen(col.name) + 1;
|
||||
space -= colwidths[c];
|
||||
}
|
||||
if (c > 0) --space;
|
||||
}
|
||||
for (int c = 0; bb->columns[c]; c++)
|
||||
if (columns[(int)bb->columns[c]].stretchy)
|
||||
colwidths[c] = space / nstretchy;
|
||||
|
||||
if (dirty) {
|
||||
// Path
|
||||
move_cursor(tty_out, 0, 0);
|
||||
const char *color = TITLE_COLOR;
|
||||
fputs(color, tty_out);
|
||||
|
||||
char *home = getenv("HOME");
|
||||
if (home && strncmp(bb->path, home, strlen(home)) == 0) {
|
||||
fputs("~", tty_out);
|
||||
fputs_escaped(tty_out, bb->path + strlen(home), color);
|
||||
} else {
|
||||
fputs_escaped(tty_out, bb->path, color);
|
||||
}
|
||||
fprintf(tty_out, "\033[0;2m[%s]", bb->globpats);
|
||||
fputs(" \033[K\033[0m", tty_out);
|
||||
|
||||
static const char *help = "Press '?' to see key bindings ";
|
||||
move_cursor(tty_out, MAX(0, winsize.ws_col - (int)strlen(help)), 0);
|
||||
fputs(help, tty_out);
|
||||
fputs("\033[K\033[0m", tty_out);
|
||||
|
||||
// Columns
|
||||
move_cursor(tty_out, 0, 1);
|
||||
fputs("\033[0;44;30m\033[K", tty_out);
|
||||
int x = 0;
|
||||
for (int c = 0; bb->columns[c]; c++) {
|
||||
column_t col = columns[(int)bb->columns[c]];
|
||||
if (!col.name) continue;
|
||||
const char *title = col.name;
|
||||
if (!title) title = "";
|
||||
move_cursor_col(tty_out, x);
|
||||
if (c > 0) {
|
||||
fputs("┃\033[K", tty_out);
|
||||
x += 1;
|
||||
}
|
||||
const char *indicator = " ";
|
||||
if (bb->columns[c] == bb->sort[1])
|
||||
indicator = bb->sort[0] == '-' ? RSORT_INDICATOR : SORT_INDICATOR;
|
||||
move_cursor_col(tty_out, x);
|
||||
fputs(indicator, tty_out);
|
||||
fputs(title, tty_out);
|
||||
x += colwidths[c];
|
||||
}
|
||||
fputs(" \033[K\033[0m", tty_out);
|
||||
}
|
||||
|
||||
if (bb->nfiles == 0) {
|
||||
move_cursor(tty_out, 0, 2);
|
||||
fputs("\033[37;2m ...no files here... \033[0m\033[J", tty_out);
|
||||
} else {
|
||||
entry_t **files = bb->files;
|
||||
for (int i = bb->scroll; i < bb->scroll + ONSCREEN && i < bb->nfiles; i++) {
|
||||
if (!(dirty || i == bb->cursor || i == lastcursor ||
|
||||
i < lastscroll || i >= lastscroll + ONSCREEN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entry_t *entry = files[i];
|
||||
const char *color = i == bb->cursor ?
|
||||
CURSOR_COLOR : color_of(entry->info.st_mode);
|
||||
fputs(color, tty_out);
|
||||
int x = 0, y = i - bb->scroll + 2;
|
||||
move_cursor(tty_out, x, y);
|
||||
for (int c = 0; bb->columns[c]; c++) {
|
||||
column_t col = columns[(int)bb->columns[c]];
|
||||
if (!col.name) continue;
|
||||
move_cursor_col(tty_out, x);
|
||||
if (c > 0) { // Separator |
|
||||
if (i == bb->cursor) fprintf(tty_out, "\033[2m┃\033[22m");
|
||||
else fprintf(tty_out, "\033[37;2m┃\033[22m%s", color);
|
||||
x += 1;
|
||||
}
|
||||
char buf[PATH_MAX * 2] = {0};
|
||||
col.render(entry, color, buf, colwidths[c]);
|
||||
fprintf(tty_out, "%s\033[K", buf);
|
||||
x += colwidths[c];
|
||||
}
|
||||
fputs("\033[0m", tty_out);
|
||||
}
|
||||
move_cursor(tty_out, 0, MIN(bb->nfiles - bb->scroll, ONSCREEN) + 2);
|
||||
fputs("\033[J", tty_out);
|
||||
}
|
||||
|
||||
// Scrollbar:
|
||||
if (bb->nfiles > ONSCREEN) {
|
||||
int height = (ONSCREEN*ONSCREEN + (bb->nfiles-1))/bb->nfiles;
|
||||
int start = 2 + (bb->scroll*ONSCREEN)/bb->nfiles;
|
||||
for (int i = 2; i < 2 + ONSCREEN; i++) {
|
||||
move_cursor(tty_out, winsize.ws_col-1, i);
|
||||
fprintf(tty_out, "%s\033[0m",
|
||||
(i >= start && i < start + height) ? SCROLLBAR_FG : SCROLLBAR_BG);
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom Line:
|
||||
move_cursor(tty_out, winsize.ws_col/2, winsize.ws_row - 1);
|
||||
fputs("\033[0m\033[K", tty_out);
|
||||
int x = winsize.ws_col;
|
||||
if (bb->selected) { // Number of selected files
|
||||
int n = 0;
|
||||
for (entry_t *s = bb->selected; s; s = s->selected.next) ++n;
|
||||
x -= 14;
|
||||
for (int k = n; k; k /= 10) x--;
|
||||
move_cursor(tty_out, MAX(0, x), winsize.ws_row - 1);
|
||||
fprintf(tty_out, "\033[41;30m %d Selected \033[0m", n);
|
||||
}
|
||||
int nprocs = 0;
|
||||
for (proc_t *p = running_procs; p; p = p->running.next) ++nprocs;
|
||||
if (nprocs > 0) { // Number of suspended processes
|
||||
x -= 13;
|
||||
for (int k = nprocs; k; k /= 10) x--;
|
||||
move_cursor(tty_out, MAX(0, x), winsize.ws_row - 1);
|
||||
fprintf(tty_out, "\033[44;30m %d Suspended \033[0m", nprocs);
|
||||
}
|
||||
move_cursor(tty_out, winsize.ws_col/2, winsize.ws_row - 1);
|
||||
|
||||
lastcursor = bb->cursor;
|
||||
lastscroll = bb->scroll;
|
||||
fflush(tty_out);
|
||||
dirty = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Close the /dev/tty terminals and restore some of the attributes.
|
||||
*/
|
||||
@ -1032,9 +826,9 @@ static int run_script(bb_t *bb, const char *cmd)
|
||||
err("Failed to fork");
|
||||
|
||||
(void)setpgid(proc->pid, proc->pid);
|
||||
LL_PREPEND(running_procs, proc, running);
|
||||
LL_PREPEND(bb->running_procs, proc, running);
|
||||
int status = wait_for_process(&proc);
|
||||
dirty = 1;
|
||||
bb->dirty = 1;
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -1095,7 +889,7 @@ static void set_interleave(bb_t *bb, int interleave)
|
||||
bb->interleave_dirs = interleave;
|
||||
if (interleave) setenv("BBINTERLEAVE", "interleave", 1);
|
||||
else unsetenv("BBINTERLEAVE");
|
||||
dirty = 1;
|
||||
bb->dirty = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1126,7 +920,7 @@ static void set_selected(bb_t *bb, entry_t *e, int selected)
|
||||
if (IS_SELECTED(e) == selected) return;
|
||||
|
||||
if (bb->nfiles > 0 && e != bb->files[bb->cursor])
|
||||
dirty = 1;
|
||||
bb->dirty = 1;
|
||||
|
||||
if (selected) {
|
||||
LL_PREPEND(bb->selected, e, selected);
|
||||
@ -1196,7 +990,7 @@ static void sort_files(bb_t *bb)
|
||||
#endif
|
||||
for (int i = 0; i < bb->nfiles; i++)
|
||||
bb->files[i]->index = i;
|
||||
dirty = 1;
|
||||
bb->dirty = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1219,9 +1013,7 @@ static char *trim(char *s)
|
||||
static void update_term_size(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
struct winsize oldsize = winsize;
|
||||
ioctl(STDIN_FILENO, TIOCGWINSZ, &winsize);
|
||||
dirty |= (oldsize.ws_col != winsize.ws_col || oldsize.ws_row != winsize.ws_row);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1395,6 +1187,7 @@ int main(int argc, char *argv[])
|
||||
.columns = "*smpn",
|
||||
.sort = "+n",
|
||||
};
|
||||
current_bb = &bb;
|
||||
set_globs(&bb, "*");
|
||||
init_term();
|
||||
bb_browse(&bb, full_initial_path);
|
||||
|
23
bb.h
23
bb.h
@ -10,7 +10,6 @@
|
||||
|
||||
#include "bterm.h"
|
||||
#include "entry.h"
|
||||
#include "columns.h"
|
||||
|
||||
// Macros:
|
||||
#define BB_VERSION "0.27.0"
|
||||
@ -27,8 +26,6 @@
|
||||
|
||||
// Configurable options:
|
||||
#define SCROLLOFF MIN(5, (winsize.ws_row-4)/2)
|
||||
#define SORT_INDICATOR "↓"
|
||||
#define RSORT_INDICATOR "↑"
|
||||
// Colors (using ANSI escape sequences):
|
||||
#define TITLE_COLOR "\033[37;1m"
|
||||
#define NORMAL_COLOR "\033[37m"
|
||||
@ -70,7 +67,7 @@
|
||||
fputs(" Press any key to continue...\033[0m ", tty_out); \
|
||||
fflush(tty_out); \
|
||||
while (bgetkey(tty_in, NULL, NULL) == -1) usleep(100); \
|
||||
dirty = 1; \
|
||||
bb->dirty = 1; \
|
||||
} while (0)
|
||||
|
||||
#define LL_PREPEND(head, node, name) do { \
|
||||
@ -96,6 +93,14 @@ typedef struct {
|
||||
char *description;
|
||||
} binding_t;
|
||||
|
||||
// For keeping track of child processes
|
||||
typedef struct proc_s {
|
||||
pid_t pid;
|
||||
struct {
|
||||
struct proc_s *next, **atme;
|
||||
} running;
|
||||
} proc_t;
|
||||
|
||||
typedef struct bb_s {
|
||||
entry_t *hash[HASH_SIZE];
|
||||
entry_t **files;
|
||||
@ -110,16 +115,10 @@ typedef struct bb_s {
|
||||
char columns[MAX_COLS+1];
|
||||
unsigned int interleave_dirs : 1;
|
||||
unsigned int should_quit : 1;
|
||||
unsigned int dirty : 1;
|
||||
proc_t *running_procs;
|
||||
} bb_t;
|
||||
|
||||
// For keeping track of child processes
|
||||
typedef struct proc_s {
|
||||
pid_t pid;
|
||||
struct {
|
||||
struct proc_s *next, **atme;
|
||||
} running;
|
||||
} proc_t;
|
||||
|
||||
// Hack to get TinyCC (TCC) compilation to work:
|
||||
// https://lists.nongnu.org/archive/html/tinycc-devel/2018-07/msg00000.html
|
||||
#ifdef __TINYC__
|
||||
|
161
columns.c
161
columns.c
@ -1,161 +0,0 @@
|
||||
/*
|
||||
* columns.c - This file contains logic for drawing columns.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "columns.h"
|
||||
#include "entry.h"
|
||||
|
||||
#define E_ISDIR(e) (S_ISDIR(S_ISLNK((e)->info.st_mode) ? (e)->linkedmode : (e)->info.st_mode))
|
||||
|
||||
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] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
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(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)color;
|
||||
timeago(buf, entry->info.st_mtime);
|
||||
lpad(buf, width);
|
||||
}
|
||||
|
||||
void col_areltime(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)color;
|
||||
timeago(buf, entry->info.st_atime);
|
||||
lpad(buf, width);
|
||||
}
|
||||
|
||||
void col_creltime(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)color;
|
||||
timeago(buf, entry->info.st_ctime);
|
||||
lpad(buf, width);
|
||||
}
|
||||
|
||||
void col_mtime(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)color;
|
||||
strftime(buf, (size_t)width, TIME_FMT, localtime(&(entry->info.st_mtime)));
|
||||
}
|
||||
|
||||
void col_atime(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)color;
|
||||
strftime(buf, (size_t)width, TIME_FMT, localtime(&(entry->info.st_atime)));
|
||||
}
|
||||
|
||||
void col_ctime(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)color;
|
||||
strftime(buf, (size_t)width, TIME_FMT, localtime(&(entry->info.st_ctime)));
|
||||
}
|
||||
|
||||
void col_selected(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)width;
|
||||
buf = stpcpy(buf, IS_SELECTED(entry) ? SELECTED_INDICATOR : NOT_SELECTED_INDICATOR);
|
||||
buf = stpcpy(buf, color);
|
||||
}
|
||||
|
||||
void col_perm(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)color; (void)width;
|
||||
sprintf(buf, " %03o", entry->info.st_mode & 0777);
|
||||
}
|
||||
|
||||
void col_random(entry_t *entry, const char *color, char *buf, int width)
|
||||
{
|
||||
(void)color;
|
||||
sprintf(buf, "%*d", width, entry->shufflepos);
|
||||
}
|
||||
|
||||
void col_size(entry_t *entry, const char *color, char *buf, int width)
|
||||
{
|
||||
(void)color; (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(entry_t *entry, const char *color, char *buf, int width)
|
||||
{
|
||||
(void)width;
|
||||
if (entry->no_esc) buf = stpcpy(buf, entry->name);
|
||||
else buf = stpcpy_escaped(buf, entry->name, color);
|
||||
|
||||
if (E_ISDIR(entry)) buf = stpcpy(buf, "/");
|
||||
|
||||
if (!entry->linkname) return;
|
||||
|
||||
buf = stpcpy(buf, "\033[2m -> \033[3m");
|
||||
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");
|
||||
}
|
395
draw.c
Normal file
395
draw.c
Normal file
@ -0,0 +1,395 @@
|
||||
/*
|
||||
* draw.c - This file contains logic for drawing columns.
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "bterm.h"
|
||||
#include "draw.h"
|
||||
#include "entry.h"
|
||||
|
||||
#define E_ISDIR(e) (S_ISDIR(S_ISLNK((e)->info.st_mode) ? (e)->linkedmode : (e)->info.st_mode))
|
||||
|
||||
column_t column_info[255] = {
|
||||
['*'] = {.name = "*", .render = col_selected},
|
||||
['n'] = {.name = "Name", .render = col_name, .stretchy = 1},
|
||||
['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 = "Random", .render = col_random},
|
||||
};
|
||||
|
||||
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] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Print a string, but replacing bytes like '\n' with a red-colored "\n".
|
||||
* The color argument is what color to put back after the red.
|
||||
* Returns the number of bytes that were escaped.
|
||||
*/
|
||||
static int fputs_escaped(FILE *f, const char *str, const char *color)
|
||||
{
|
||||
static const char *escapes = " abtnvfr e";
|
||||
int escaped = 0;
|
||||
for (const char *c = str; *c; ++c) {
|
||||
if (*c > 0 && *c <= '\x1b' && escapes[(int)*c] != ' ') { // "\n", etc.
|
||||
fprintf(f, "\033[31m\\%c%s", escapes[(int)*c], color);
|
||||
++escaped;
|
||||
} else if (*c >= 0 && !(' ' <= *c && *c <= '~')) { // "\x02", etc.
|
||||
fprintf(f, "\033[31m\\x%02X%s", *c, color);
|
||||
++escaped;
|
||||
} else {
|
||||
fputc(*c, f);
|
||||
}
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 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(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)color;
|
||||
timeago(buf, entry->info.st_mtime);
|
||||
lpad(buf, width);
|
||||
}
|
||||
|
||||
void col_areltime(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)color;
|
||||
timeago(buf, entry->info.st_atime);
|
||||
lpad(buf, width);
|
||||
}
|
||||
|
||||
void col_creltime(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)color;
|
||||
timeago(buf, entry->info.st_ctime);
|
||||
lpad(buf, width);
|
||||
}
|
||||
|
||||
void col_mtime(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)color;
|
||||
strftime(buf, (size_t)width, TIME_FMT, localtime(&(entry->info.st_mtime)));
|
||||
}
|
||||
|
||||
void col_atime(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)color;
|
||||
strftime(buf, (size_t)width, TIME_FMT, localtime(&(entry->info.st_atime)));
|
||||
}
|
||||
|
||||
void col_ctime(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)color;
|
||||
strftime(buf, (size_t)width, TIME_FMT, localtime(&(entry->info.st_ctime)));
|
||||
}
|
||||
|
||||
void col_selected(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)width;
|
||||
buf = stpcpy(buf, IS_SELECTED(entry) ? SELECTED_INDICATOR : NOT_SELECTED_INDICATOR);
|
||||
buf = stpcpy(buf, color);
|
||||
}
|
||||
|
||||
void col_perm(entry_t *entry, const char *color, char *buf, int width) {
|
||||
(void)color; (void)width;
|
||||
sprintf(buf, " %03o", entry->info.st_mode & 0777);
|
||||
}
|
||||
|
||||
void col_random(entry_t *entry, const char *color, char *buf, int width)
|
||||
{
|
||||
(void)color;
|
||||
sprintf(buf, "%*d", width, entry->shufflepos);
|
||||
}
|
||||
|
||||
void col_size(entry_t *entry, const char *color, char *buf, int width)
|
||||
{
|
||||
(void)color; (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(entry_t *entry, const char *color, char *buf, int width)
|
||||
{
|
||||
(void)width;
|
||||
if (entry->no_esc) buf = stpcpy(buf, entry->name);
|
||||
else buf = stpcpy_escaped(buf, entry->name, color);
|
||||
|
||||
if (E_ISDIR(entry)) buf = stpcpy(buf, "/");
|
||||
|
||||
if (!entry->linkname) return;
|
||||
|
||||
buf = stpcpy(buf, "\033[2m -> \033[3m");
|
||||
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");
|
||||
}
|
||||
|
||||
int *get_column_widths(char columns[], int width)
|
||||
{
|
||||
// TODO: maybe memoize
|
||||
static int colwidths[16] = {0};
|
||||
int space = width, nstretchy = 0;
|
||||
for (int c = 0; columns[c]; c++) {
|
||||
column_t col = column_info[(int)columns[c]];
|
||||
if (!col.name) continue;
|
||||
if (col.stretchy) {
|
||||
++nstretchy;
|
||||
} else {
|
||||
colwidths[c] = strlen(col.name) + 1;
|
||||
space -= colwidths[c];
|
||||
}
|
||||
if (c > 0) --space;
|
||||
}
|
||||
for (int c = 0; columns[c]; c++)
|
||||
if (column_info[(int)columns[c]].stretchy)
|
||||
colwidths[c] = space / nstretchy;
|
||||
return colwidths;
|
||||
}
|
||||
|
||||
void draw_column_labels(FILE *out, char columns[], char *sort, int width)
|
||||
{
|
||||
int *colwidths = get_column_widths(columns, width);
|
||||
fputs("\033[0;44;30m\033[K", out);
|
||||
int x = 0;
|
||||
for (int c = 0; columns[c]; c++) {
|
||||
column_t col = column_info[(int)columns[c]];
|
||||
if (!col.name) continue;
|
||||
const char *title = col.name;
|
||||
move_cursor_col(out, x);
|
||||
if (c > 0) {
|
||||
fputs("┃\033[K", out);
|
||||
x += 1;
|
||||
}
|
||||
const char *indicator = " ";
|
||||
if (columns[c] == sort[1])
|
||||
indicator = sort[0] == '-' ? RSORT_INDICATOR : SORT_INDICATOR;
|
||||
move_cursor_col(out, x);
|
||||
fputs(indicator, out);
|
||||
if (title) fputs(title, out);
|
||||
x += colwidths[c];
|
||||
}
|
||||
fputs(" \033[K\033[0m", out);
|
||||
}
|
||||
|
||||
void draw_row(FILE *out, char columns[], entry_t *entry, const char *color, int width)
|
||||
{
|
||||
int *colwidths = get_column_widths(columns, width);
|
||||
fputs(color, out);
|
||||
int x = 0;
|
||||
for (int c = 0; columns[c]; c++) {
|
||||
column_t col = column_info[(int)columns[c]];
|
||||
if (!col.name) continue;
|
||||
move_cursor_col(out, x);
|
||||
if (c > 0) { // Separator |
|
||||
fprintf(out, "\033[37;2m┃\033[22m%s", color);
|
||||
x += 1;
|
||||
}
|
||||
char buf[PATH_MAX * 2] = {0};
|
||||
col.render(entry, color, buf, colwidths[c]);
|
||||
fprintf(out, "%s\033[K", buf);
|
||||
x += colwidths[c];
|
||||
}
|
||||
fputs("\033[0m", out);
|
||||
}
|
||||
|
||||
/*
|
||||
* Draw everything to the screen.
|
||||
* If `dirty` is false, then use terminal scrolling to move the file listing
|
||||
* around and only update the files that have changed.
|
||||
*/
|
||||
void render(FILE *out, bb_t *bb)
|
||||
{
|
||||
static int lastcursor = -1, lastscroll = -1;
|
||||
static struct winsize oldsize = {0};
|
||||
|
||||
struct winsize winsize;
|
||||
ioctl(STDIN_FILENO, TIOCGWINSZ, &winsize);
|
||||
|
||||
bb->dirty |= (winsize.ws_row != oldsize.ws_row) || (winsize.ws_col != oldsize.ws_col);
|
||||
|
||||
if (!bb->dirty) {
|
||||
// Use terminal scrolling:
|
||||
if (lastscroll > bb->scroll) {
|
||||
fprintf(out, "\033[3;%dr\033[%dT\033[1;%dr", winsize.ws_row-1, lastscroll - bb->scroll, winsize.ws_row);
|
||||
} else if (lastscroll < bb->scroll) {
|
||||
fprintf(out, "\033[3;%dr\033[%dS\033[1;%dr", winsize.ws_row-1, bb->scroll - lastscroll, winsize.ws_row);
|
||||
}
|
||||
}
|
||||
|
||||
if (bb->dirty) {
|
||||
// Path
|
||||
move_cursor(out, 0, 0);
|
||||
const char *color = TITLE_COLOR;
|
||||
fputs(color, out);
|
||||
|
||||
char *home = getenv("HOME");
|
||||
if (home && strncmp(bb->path, home, strlen(home)) == 0) {
|
||||
fputs("~", out);
|
||||
fputs_escaped(out, bb->path + strlen(home), color);
|
||||
} else {
|
||||
fputs_escaped(out, bb->path, color);
|
||||
}
|
||||
fprintf(out, "\033[0;2m[%s]", bb->globpats);
|
||||
fputs(" \033[K\033[0m", out);
|
||||
|
||||
static const char *help = "Press '?' to see key bindings ";
|
||||
move_cursor(out, MAX(0, winsize.ws_col - (int)strlen(help)), 0);
|
||||
fputs(help, out);
|
||||
fputs("\033[K\033[0m", out);
|
||||
|
||||
// Columns
|
||||
move_cursor(out, 0, 1);
|
||||
fputs("\033[0;44;30m\033[K", out);
|
||||
draw_column_labels(out, bb->columns, bb->sort, winsize.ws_col-1);
|
||||
}
|
||||
|
||||
if (bb->nfiles == 0) {
|
||||
move_cursor(out, 0, 2);
|
||||
fputs("\033[37;2m ...no files here... \033[0m\033[J", out);
|
||||
} else {
|
||||
entry_t **files = bb->files;
|
||||
for (int i = bb->scroll; i < bb->scroll + ONSCREEN && i < bb->nfiles; i++) {
|
||||
if (!(bb->dirty || i == bb->cursor || i == lastcursor ||
|
||||
i < lastscroll || i >= lastscroll + ONSCREEN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entry_t *entry = files[i];
|
||||
const char *color = i == bb->cursor ?
|
||||
CURSOR_COLOR : color_of(entry->info.st_mode);
|
||||
int x = 0, y = i - bb->scroll + 2;
|
||||
move_cursor(out, x, y);
|
||||
draw_row(out, bb->columns, entry, color, winsize.ws_col-1);
|
||||
}
|
||||
move_cursor(out, 0, MIN(bb->nfiles - bb->scroll, ONSCREEN) + 2);
|
||||
fputs("\033[J", out);
|
||||
}
|
||||
|
||||
// Scrollbar:
|
||||
if (bb->nfiles > ONSCREEN) {
|
||||
int height = (ONSCREEN*ONSCREEN + (bb->nfiles-1))/bb->nfiles;
|
||||
int start = 2 + (bb->scroll*ONSCREEN)/bb->nfiles;
|
||||
for (int i = 2; i < 2 + ONSCREEN; i++) {
|
||||
move_cursor(out, winsize.ws_col-1, i);
|
||||
fprintf(out, "%s\033[0m",
|
||||
(i >= start && i < start + height) ? SCROLLBAR_FG : SCROLLBAR_BG);
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom Line:
|
||||
move_cursor(out, winsize.ws_col/2, winsize.ws_row - 1);
|
||||
fputs("\033[0m\033[K", out);
|
||||
int x = winsize.ws_col;
|
||||
if (bb->selected) { // Number of selected files
|
||||
int n = 0;
|
||||
for (entry_t *s = bb->selected; s; s = s->selected.next) ++n;
|
||||
x -= 14;
|
||||
for (int k = n; k; k /= 10) x--;
|
||||
move_cursor(out, MAX(0, x), winsize.ws_row - 1);
|
||||
fprintf(out, "\033[41;30m %d Selected \033[0m", n);
|
||||
}
|
||||
int nprocs = 0;
|
||||
for (proc_t *p = bb->running_procs; p; p = p->running.next) ++nprocs;
|
||||
if (nprocs > 0) { // Number of suspended processes
|
||||
x -= 13;
|
||||
for (int k = nprocs; k; k /= 10) x--;
|
||||
move_cursor(out, MAX(0, x), winsize.ws_row - 1);
|
||||
fprintf(out, "\033[44;30m %d Suspended \033[0m", nprocs);
|
||||
}
|
||||
move_cursor(out, winsize.ws_col/2, winsize.ws_row - 1);
|
||||
|
||||
lastcursor = bb->cursor;
|
||||
lastscroll = bb->scroll;
|
||||
fflush(out);
|
||||
bb->dirty = 0;
|
||||
}
|
||||
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
|
@ -1,15 +1,20 @@
|
||||
/*
|
||||
* columns.h - This file contains definitions for bb column-drawing code.
|
||||
* draw.h - This file contains definitions for bb column-drawing code.
|
||||
*/
|
||||
|
||||
#ifndef FILE_COLUMNS__H
|
||||
#define FILE_COLUMNS__H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "bb.h"
|
||||
#include "entry.h"
|
||||
|
||||
#define TIME_FMT " %T %D "
|
||||
#define SELECTED_INDICATOR " \033[31;7m \033[0m"
|
||||
#define NOT_SELECTED_INDICATOR " "
|
||||
#define SORT_INDICATOR "↓"
|
||||
#define RSORT_INDICATOR "↑"
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
@ -29,6 +34,11 @@ typedef enum {
|
||||
COL_SELECTED = '*',
|
||||
} column_e;
|
||||
|
||||
void draw_column_labels(FILE *out, char columns[], char *sort, int width);
|
||||
void draw_row(FILE *out, char columns[], entry_t *entry, const char *color, int width);
|
||||
int *get_column_widths(char columns[], int width);
|
||||
void render(FILE *out, bb_t *bb);
|
||||
|
||||
void col_mreltime(entry_t *entry, const char *color, char *buf, int width);
|
||||
void col_areltime(entry_t *entry, const char *color, char *buf, int width);
|
||||
void col_creltime(entry_t *entry, const char *color, char *buf, int width);
|
||||
@ -42,3 +52,4 @@ void col_size(entry_t *entry, const char *color, char *buf, int width);
|
||||
void col_name(entry_t *entry, const char *color, char *buf, int width);
|
||||
|
||||
#endif
|
||||
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
|
Loading…
Reference in New Issue
Block a user