From 05601c886baf39740b8a487ac9e78f656f12acd3 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 31 May 2019 17:44:18 -0700 Subject: [PATCH] Added "" virtual directory, added '-' mark for "last non-virtual directory" and 's' for "selection virtual directory", cleaned up path normalization, added "N selected" visualization. --- bb.c | 227 +++++++++++++++++++++++++++++++-------------------- config.def.h | 91 +++++++++++---------- 2 files changed, 185 insertions(+), 133 deletions(-) diff --git a/bb.c b/bb.c index 194ff90..454215a 100644 --- a/bb.c +++ b/bb.c @@ -24,7 +24,7 @@ #include "config.h" #include "bterm.h" -#define BB_VERSION "0.11.3" +#define BB_VERSION "0.12.0" #ifndef PATH_MAX #define PATH_MAX 4096 @@ -142,6 +142,7 @@ static void set_scroll(bb_t *bb, int i); static entry_t* load_entry(bb_t *bb, const char *path); static void remove_entry(entry_t *e); static void sort_files(bb_t *bb); +static void normalize_path(const char *root, const char *path, char *pbuf); static int cd_to(bb_t *bb, const char *path); static void populate_files(bb_t *bb, const char *path); static bb_result_t execute_cmd(bb_t *bb, const char *cmd); @@ -380,6 +381,11 @@ void render(bb_t *bb) fputs_escaped(tty_out, bb->path, color); fputs(" \033[K\033[0m", tty_out); + static const char *help = "Press '?' to see key bindings "; + move_cursor(tty_out, MAX(0, termwidth - (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); @@ -442,6 +448,7 @@ 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++) { fprintf(tty_out, "\033[%d;%dH\033[K", y+1, x+1); @@ -500,8 +507,9 @@ void render(bb_t *bb) if (i == bb->cursor) strcat(color, CURSOR_COLOR); fputs(color, tty_out); - if (entry->no_esc) fputs(entry->name, tty_out); - else entry->no_esc |= !fputs_escaped(tty_out, entry->name, color); + 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); @@ -531,11 +539,15 @@ void render(bb_t *bb) fputs(" \033[K\033[0m", tty_out); // Reset color and attributes } - static const char *help = "Press '?' to see key bindings "; - move_cursor(tty_out, 0, termheight - 1); - fputs("\033[K", tty_out); - move_cursor(tty_out, MAX(0, termwidth - (int)strlen(help)), termheight - 1); - fputs(help, tty_out); + move_cursor(tty_out, MAX(0, termwidth - 14), termheight - 1); + if (bb->firstselected) { + int n = 0; + for (entry_t *s = bb->firstselected; s; s = s->selected.next) ++n; + fprintf(tty_out, "\033[41;30m% 4d Selected \033[0m", n); + } else { + fputs("\033[0m\033[K", tty_out); + } + lastcursor = bb->cursor; lastscroll = bb->scroll; fflush(tty_out); @@ -673,21 +685,20 @@ void set_cursor(bb_t *bb, int newcur) if (newcur > bb->nfiles - 1) newcur = bb->nfiles - 1; if (newcur < 0) newcur = 0; bb->cursor = newcur; - if (bb->nfiles <= termheight - 4) + if (bb->nfiles <= termheight - 4) { + bb->scroll = 0; return; + } if (newcur < bb->scroll + SCROLLOFF) bb->scroll = newcur - SCROLLOFF; else if (newcur > bb->scroll + (termheight-4) - SCROLLOFF) bb->scroll = newcur - (termheight-4) + SCROLLOFF; - if (bb->nfiles <= termheight - 4) { - bb->scroll = 0; - } else { - int max_scroll = bb->nfiles - (termheight-4) - 1; - if (max_scroll < 0) max_scroll = 0; - if (bb->scroll > max_scroll) bb->scroll = max_scroll; - if (bb->scroll < 0) bb->scroll = 0; - } + + int max_scroll = bb->nfiles - (termheight-4) - 1; + if (max_scroll < 0) max_scroll = 0; + if (bb->scroll > max_scroll) bb->scroll = max_scroll; + if (bb->scroll < 0) bb->scroll = 0; } /* @@ -799,43 +810,63 @@ void sort_files(bb_t *bb) bb->dirty = 1; } +/* + * Prepend `root` to relative paths, replace "~" with $HOME, remove ".", + * replace "/foo/baz/../" with "/foo/", and make sure there's a trailing + * slash. The normalized path is stored in `normalized`. + */ +void normalize_path(const char *root, const char *path, char *normalized) +{ + if (path[0] == '~' && (path[1] == '\0' || path[1] == '/')) { + char *home; + if (!(home = getenv("HOME"))) return; + strcpy(normalized, home); + ++path; + } else if (path[0] == '/') { + normalized[0] = '\0'; + } else { + strcpy(normalized, root); + } + strcat(normalized, path); + + if (normalized[strlen(normalized)-1] != '/') + strcat(normalized, "/"); + + char *src = normalized, *dest = normalized; + while (*src) { + if (strncmp(src, "/./", 3) == 0) { + src += 2; + } else if (strncmp(src, "/../", 4) == 0) { + src += 3; + while (dest > normalized && *(--dest) != '/') + ; + } + *(dest++) = *(src++); + } + *dest = '\0'; +} + int cd_to(bb_t *bb, const char *path) { char pbuf[PATH_MAX]; - if (path[0] == '~' && (path[1] == '\0' || path[1] == '/')) { - char *home; - if (!(home = getenv("HOME"))) - return BB_INVALID; - strcpy(pbuf, home); - strcat(pbuf, path+1); - } else if (path[0] == '/') { + if (strcmp(path, "") == 0) { strcpy(pbuf, path); + if (bb->marks['-']) free(bb->marks['-']); + bb->marks['-'] = memcheck(strdup(bb->path)); + } else if (strcmp(path, "..") == 0 && strcmp(bb->path, "") == 0) { + if (!bb->marks['-']) return -1; + strcpy(pbuf, bb->marks['-']); + if (chdir(pbuf)) return -1; } else { - strcpy(pbuf, bb->path); - strcat(pbuf, path); + normalize_path(bb->path, path, pbuf); + if (chdir(pbuf)) return -1; } - if (pbuf[strlen(pbuf)-1] != '/') - strcat(pbuf, "/"); - - while (1) { - char *p; - if ((p = strstr(pbuf, "/../"))) { - if (p == pbuf) return 0; - char *end = p + 3; - char *start = p - 1; - while (start > pbuf && *start != '/') --start; - memmove(start, end, strlen(end)+1); - continue; - } - if ((p = strstr(pbuf, "/./"))) { - memmove(p, p+2, strlen(p+2)+1); - continue; - } - break; + if (strcmp(bb->path, "") != 0) { + if (bb->marks['-']) free(bb->marks['-']); + bb->marks['-'] = memcheck(strdup(bb->path)); } - if (chdir(pbuf)) return -1; populate_files(bb, pbuf); return 0; } @@ -860,7 +891,8 @@ void populate_files(bb_t *bb, const char *path) bb->files = NULL; } - int old_scroll = bb->scroll; + int old_scroll = bb->scroll, old_cursor = bb->cursor; + int samedir = path && strcmp(bb->path, path) == 0; bb->nfiles = 0; bb->cursor = 0; bb->scroll = 0; @@ -868,46 +900,60 @@ void populate_files(bb_t *bb, const char *path) if (path == NULL || !path[0]) return; - DIR *dir = opendir(path); - if (!dir) - err("Couldn't open dir: %s", path); + size_t cap = 0; + if (strcmp(path, "") == 0) { + for (entry_t *e = bb->firstselected; e; e = e->selected.next) { + if ((size_t)bb->nfiles + 1 > cap) { + cap += 100; + bb->files = memcheck(realloc(bb->files, cap*sizeof(void*))); + } + e->index = bb->nfiles; + bb->files[bb->nfiles++] = e; + } + } else { + DIR *dir = opendir(path); + if (!dir) + err("Couldn't open dir: %s", path); + + if (path[strlen(path)-1] != '/') + err("No terminating slash on '%s'", path); + + char pathbuf[PATH_MAX]; + strcpy(pathbuf, path); + size_t pathbuflen = strlen(pathbuf); + for (struct dirent *dp; (dp = readdir(dir)) != NULL; ) { + if (dp->d_name[0] == '.') { + if (dp->d_name[1] == '.' && dp->d_name[2] == '\0') { + if (!bb->show_dotdot) continue; + } else if (dp->d_name[1] == '\0') { + if (!bb->show_dot) continue; + } else if (!bb->show_dotfiles) continue; + } + if ((size_t)bb->nfiles + 1 > cap) { + cap += 100; + bb->files = memcheck(realloc(bb->files, cap*sizeof(void*))); + } + strcpy(&pathbuf[pathbuflen], dp->d_name); + entry_t *entry = load_entry(bb, pathbuf); + if (!entry) err("Failed to load entry: '%s'", dp->d_name); + entry->index = bb->nfiles; + bb->files[bb->nfiles++] = entry; + } + closedir(dir); + } if (path != bb->path) strcpy(bb->path, path); - if (bb->path[strlen(bb->path)-1] != '/') - strcat(bb->path, "/"); - - size_t cap = 0; - char pathbuf[PATH_MAX]; - strcpy(pathbuf, bb->path); - size_t pathbuflen = strlen(pathbuf); - for (struct dirent *dp; (dp = readdir(dir)) != NULL; ) { - if (dp->d_name[0] == '.') { - if (dp->d_name[1] == '.' && dp->d_name[2] == '\0') { - if (!bb->show_dotdot) continue; - } else if (dp->d_name[1] == '\0') { - if (!bb->show_dot) continue; - } else if (!bb->show_dotfiles) continue; - } - if ((size_t)bb->nfiles + 1 > cap) { - cap += 100; - bb->files = memcheck(realloc(bb->files, cap*sizeof(void*))); - } - strcpy(&pathbuf[pathbuflen], dp->d_name); - entry_t *entry = load_entry(bb, pathbuf); - if (!entry) err("Failed to load entry: '%s'", dp->d_name); - entry->index = bb->nfiles; - bb->files[bb->nfiles++] = entry; - } - closedir(dir); - // TODO: this may have some weird aliasing issues, but eh, it's simple and effective for (int i = 0; i < bb->nfiles; i++) bb->files[i]->shufflepos = rand(); sort_files(bb); - set_scroll(bb, old_scroll); + if (samedir) { + set_cursor(bb, old_cursor); + set_scroll(bb, old_scroll); + } } /* @@ -948,7 +994,7 @@ bb_result_t execute_cmd(bb_t *bb, const char *cmd) switch (cmd[1]) { case 'e': { // +deselect: if (!value && !bb->nfiles) return BB_INVALID; - if (!value) value = bb->files[bb->cursor]->name; + if (!value) value = bb->files[bb->cursor]->fullname; if (strcmp(value, "*") == 0) { clear_selection(bb); return BB_OK; @@ -990,6 +1036,7 @@ bb_result_t execute_cmd(bb_t *bb, const char *cmd) } case 'j': { // +jump: if (!value) return BB_INVALID; + bb->dirty = 1; char key = value[0]; if (bb->marks[(int)key]) { value = bb->marks[(int)key]; @@ -1059,7 +1106,7 @@ bb_result_t execute_cmd(bb_t *bb, const char *cmd) case '\0': case 'e': // +select: if (!value && !bb->nfiles) return BB_INVALID; - if (!value) value = bb->files[bb->cursor]->name; + if (!value) value = bb->files[bb->cursor]->fullname; if (strcmp(value, "*") == 0) { for (int i = 0; i < bb->nfiles; i++) { if (strcmp(bb->files[i]->name, ".") @@ -1083,7 +1130,7 @@ bb_result_t execute_cmd(bb_t *bb, const char *cmd) } case 't': { // +toggle: if (!value && !bb->nfiles) return BB_INVALID; - if (!value) value = bb->files[bb->cursor]->name; + if (!value) value = bb->files[bb->cursor]->fullname; entry_t *e = load_entry(bb, value); if (e) toggle_entry(bb, e); return BB_OK; @@ -1102,6 +1149,7 @@ void bb_browse(bb_t *bb, const char *path) int lastwidth = termwidth, lastheight = termheight; int check_cmds = 1; + bb->marks['-'] = memcheck(strdup(path)); cd_to(bb, path); bb->scroll = 0; bb->cursor = 0; @@ -1215,7 +1263,6 @@ void bb_browse(bb_t *bb, const char *path) close_term(); raise(SIGTSTP); init_term(); - fputs(T_ON(T_ALT_SCREEN), tty_out); bb->dirty = 1; goto redraw; @@ -1275,8 +1322,10 @@ void bb_browse(bb_t *bb, const char *path) quit: populate_files(bb, NULL); - fputs(T_LEAVE_BBMODE, tty_out); - cleanup(); + if (tty_out) { + fputs(T_LEAVE_BBMODE, tty_out); + cleanup(); + } } /* @@ -1307,7 +1356,7 @@ void print_bindings(void) } *p = '\0'; printf("\033[1m\033[%dG%s\033[0m", width/2 - 1 - (int)strlen(buf), buf); - printf("\033[0m\033[%dG\033[34;1m%s\033[0m", width/2 + 1, bindings[i].description); + printf("\033[0m\033[%dG\033[34m%s\033[0m", width/2 + 1, bindings[i].description); printf("\033[0m\n"); } printf("\n"); @@ -1431,14 +1480,16 @@ int main(int argc, char *argv[]) signal(SIGPROF, cleanup_and_exit); signal(SIGSEGV, cleanup_and_exit); - char *real = realpath(initial_path, NULL); - if (!real || chdir(real)) err("Not a valid path: %s\n", initial_path); + char path[PATH_MAX], curdir[PATH_MAX]; + getcwd(curdir, PATH_MAX); + strcat(curdir, "/"); + normalize_path(curdir, initial_path, path); + if (chdir(path)) err("Not a valid path: %s\n", path); bb_t *bb = memcheck(calloc(1, sizeof(bb_t))); bb->columns[0] = COL_NAME; strcpy(bb->sort, "+n"); - bb_browse(bb, real); - free(real); + bb_browse(bb, path); if (bb->firstselected && print_selection) { for (entry_t *e = bb->firstselected; e; e = e->selected.next) { diff --git a/config.def.h b/config.def.h index fcf0838..6a26999 100644 --- a/config.def.h +++ b/config.def.h @@ -99,7 +99,7 @@ const char *startupcmds[] = { ////////////////////////////////////////////// // Set some default marks: "+mark:0", "+mark:~=~", "+mark:h=~", "+mark:/=/", "+mark:c=~/.config", - "+mark:l=~/.local", + "+mark:l=~/.local", "+mark:s=", // Default column and sorting options: "+sort:+n", "+col:*smpn", "+..", NULL, // NULL-terminated array @@ -122,20 +122,19 @@ const int colwidths[128] = { #pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" #endif extern binding_t bindings[]; +#define EM(s) "\033[33;4m" s "\033[0;34m" binding_t bindings[] = { ////////////////////////////////////////////////////////////////////////// // User-defined custom scripts can go here // Please note that these are sh scripts, not bash scripts, so bash-isms // won't work unless you make your script use `bash -c ""` ////////////////////////////////////////////////////////////////////////// - {{'?', KEY_F1}, "bb -b | $PAGER -r", "Show the help menu", NORMAL_TERM}, - {{'q', 'Q'}, "+quit", "Quit"}, - {{'k', KEY_ARROW_UP}, "+move:-1", "Move up"}, - {{'j', KEY_ARROW_DOWN}, "+move:+1", "Move down"}, - {{'h', KEY_ARROW_LEFT}, "+cd:..", "Go up a folder"}, - {{'l', KEY_ARROW_RIGHT}, "test -d \"$BBCURSOR\" && bb \"+cd:$BBCURSOR\"", "Enter a folder"}, - {{' ','v','V'}, "+toggle", "Toggle selection"}, - {{'e'}, "$EDITOR \"$@\"", "Edit file in $EDITOR", NORMAL_TERM}, + {{'?', KEY_F1}, "bb -b | $PAGER -r", EM("Help")" menu", NORMAL_TERM}, + {{'q', 'Q'}, "+quit", EM("Quit")}, + {{'j', KEY_ARROW_DOWN}, "+move:+1", EM("Next")" file"}, + {{'k', KEY_ARROW_UP}, "+move:-1", EM("Previous")" file"}, + {{'h', KEY_ARROW_LEFT}, "+cd:..", EM("Parent")" directory"}, + {{'l', KEY_ARROW_RIGHT}, "test -d \"$BBCURSOR\" && bb \"+cd:$BBCURSOR\"", EM("Enter")" a directory"}, {{'\r', KEY_MOUSE_DOUBLE_LEFT}, #ifdef __APPLE__ QUOTE( @@ -151,24 +150,27 @@ elif file -bi "$BBCURSOR" | grep '^\(text/\|inode/empty\)' >/dev/null; then $EDI else xdg-open "$BBCURSOR"; fi )/*ENDQUOTE*/, #endif - "Open file", NORMAL_TERM}, - {{'f'}, "bb \"+g:`fzf`\"", "Fuzzy search for file", NORMAL_TERM}, - {{'/'}, "bb \"+g:`ls -a|fzf`\"", "Fuzzy select file", NORMAL_TERM}, - {{'L'}, PIPE_SELECTION_TO "$PAGER", "List all selected files", NORMAL_TERM}, - {{'d', KEY_DELETE}, "rm -rfi \"$@\"; bb '+d:*' +r", "Delete files", AT_CURSOR}, - {{'D'}, "rm -rf \"$@\"; bb '+d:*' +r", "Delete files without confirmation"}, - {{'M'}, "mv -i \"$@\" .; bb '+d:*' +r", "Move files to current folder"}, - {{'c'}, "cp -i \"$@\" .; bb +r", "Copy files to current folder"}, - {{'C'}, "for f; do cp \"$f\" \"$f.copy\"; done; bb +r", "Clone files"}, - {{'n'}, "name=`bb '?New file: '` && touch \"$name\"; bb +r \"+goto:$name\"", "New file"}, - {{'N'}, "name=`bb '?New dir: '` && mkdir \"$name\"; bb +r \"+goto:$name\"", "New folder"}, + EM("Open")" file/directory", NORMAL_TERM}, + {{' ','v','V'}, "+toggle", EM("Toggle")" selection"}, + {{KEY_ESC}, "+deselect:*", EM("Clear")" selection"}, + {{'e'}, "$EDITOR \"$@\"", EM("Edit")" file in $EDITOR", NORMAL_TERM}, + {{KEY_CTRL_F}, "bb \"+g:`fzf`\"", EM("Fuzzy search")" for file", NORMAL_TERM}, + {{'/'}, "bb \"+g:`ls -a|fzf`\"", EM("Fuzzy select")" file", NORMAL_TERM}, + {{'d', KEY_DELETE}, "rm -rfi \"$@\"; bb '+de:*' +r", EM("Delete")" files", AT_CURSOR}, + {{'D'}, "rm -rf \"$@\"; bb '+de:*' +r", EM("Delete")" files (without confirmation)"}, + {{'M'}, "mv -i \"$@\" .; bb '+de:*' +r; for f; do bb \"+sel:`pwd`/`basename \"$f\"`\"; done", + EM("Move")" files to current directory"}, + {{'c'}, "cp -i \"$@\" .; bb +r", EM("Copy")" files to current directory"}, + {{'C'}, "bb '+de:*'; for f; do cp \"$f\" \"$f.copy\" && bb \"+sel:$f.copy\"; done; bb +r", EM("Clone")" files"}, + {{'n'}, "name=`bb '?New file: '` && touch \"$name\"; bb +r \"+goto:$name\"", EM("New file")}, + {{'N'}, "name=`bb '?New dir: '` && mkdir \"$name\"; bb +r \"+goto:$name\"", EM("New directory")}, {{'|'}, "cmd=`bb '?|'` && " PIPE_SELECTION_TO "sh -c \"$cmd\" && " PAUSE "; bb +r", - "Pipe selected files to a command"}, + EM("Pipe")" selected files to a command"}, {{':'}, "$SHELL -c \"`bb '?:'`\" -- \"$@\"; " PAUSE "; bb +refresh", - "Run a command"}, - {{'>'}, "$SHELL", "Open a shell", NORMAL_TERM}, - {{'m'}, "read -n1 -p 'Mark: ' m && bb \"+mark:$m;$PWD\"", "Set mark"}, - {{'\''}, "read -n1 -p 'Jump: ' j && bb \"+jump:$j\"", "Jump to mark"}, + EM("Run")" a command"}, + {{'>'}, "$SHELL", "Open a "EM("shell"), NORMAL_TERM}, + {{'m'}, "read -n1 -p 'Mark: ' m && bb \"+mark:$m;$PWD\"", "Set "EM("mark")}, + {{'\''}, "read -n1 -p 'Jump: ' j && bb \"+jump:$j\"", EM("Jump")" to mark"}, {{'r'}, QUOTE( bb '+deselect:*' +refresh; @@ -177,7 +179,7 @@ for f; do test "$f" != "$renamed" && mv -i "$f" "$renamed"; then test $BBSELECTED && bb "+select:$renamed"; elif test $BBSELECTED; then bb "+select:$f"; fi -done)/*ENDQUOTE*/, "Rename files", AT_CURSOR}, +done)/*ENDQUOTE*/, EM("Rename")" files", AT_CURSOR}, {{'R'}, QUOTE( if patt="`bb '?Rename pattern: ' 's/'`"; then true; else bb +r; exit; fi; @@ -188,32 +190,31 @@ for f; do if test "$f" != "$renamed" && mv -i "$f" "$renamed"; then test $BBSELECTED && bb "+select:$renamed"; elif test $BBSELECTED; then bb "+select:$f"; fi -done)/*ENDQUOTE*/, "Regex rename files", AT_CURSOR}, +done)/*ENDQUOTE*/, EM("Regex rename")" files", AT_CURSOR}, // TODO debug: {{'P'}, "patt=`bb '?Select pattern: '` && " "for f; do echo \"$f\" | grep \"$patt\" >/dev/null 2>/dev/null && bb \"+sel:$f\"; done", - "Regex select files"}, - {{'J'}, "+spread:+1", "Spread selection down"}, - {{'K'}, "+spread:-1", "Spread selection up"}, - {{'b'}, "bb \"+`bb '?bb +'`\"", "Run a bb command"}, + EM("Regex select")" files"}, + {{'J'}, "+spread:+1", EM("Spread")" selection down"}, + {{'K'}, "+spread:-1", EM("Spread")" selection up"}, + {{'b'}, "bb \"+`bb '?bb +'`\"", "Run a "EM("bb command")}, {{'s'}, "read -n1 -p 'Sort \033[1m(a)\033[22mlphabetic " "\033[1m(s)\033[22mize \033[1m(m)\033[22modification \033[1m(c)\033[22mcreation " "\033[1m(a)\033[22maccess \033[1m(r)\033[22mandom \033[1m(p)\033[22mermissions:\033[0m ' sort " - "&& bb \"+sort:+$sort\"", "Sort by..."}, - {{'#'}, "bb \"+col:`bb '?Set columns: '`\"", "Set columns"}, - {{'.'}, "bb +dotfiles", "Toggle dotfiles"}, - {{'g', KEY_HOME}, "+move:0", "Go to first file"}, - {{'G', KEY_END}, "+move:100%n", "Go to last file"}, - {{KEY_ESC}, "+deselect:*", "Clear selection"}, - {{KEY_F5, KEY_CTRL_R}, "+refresh", "Refresh"}, - {{KEY_CTRL_A}, "+select:*", "Select all files in current folder"}, - {{KEY_PGDN}, "+scroll:+100%", "Page down"}, - {{KEY_PGUP}, "+scroll:-100%", "Page up"}, - {{KEY_CTRL_D}, "+scroll:+50%", "Half page down"}, - {{KEY_CTRL_U}, "+scroll:-50%", "Half page up"}, - {{KEY_MOUSE_WHEEL_DOWN}, "+scroll:+3", "Scroll down"}, - {{KEY_MOUSE_WHEEL_UP}, "+scroll:-3", "Scroll up"}, + "&& bb \"+sort:+$sort\"", EM("Sort")" by..."}, + {{'#'}, "bb \"+col:`bb '?Set columns: '`\"", "Set "EM("columns")}, + {{'.'}, "bb +dotfiles", "Toggle "EM("dotfiles")}, + {{'g', KEY_HOME}, "+move:0", "Go to "EM("first")" file"}, + {{'G', KEY_END}, "+move:100%n", "Go to "EM("last")" file"}, + {{KEY_F5, KEY_CTRL_R}, "+refresh", EM("Refresh")}, + {{KEY_CTRL_A}, "+select:*", EM("Select all")" files in current directory"}, + {{KEY_PGDN}, "+scroll:+100%", EM("Page down")}, + {{KEY_PGUP}, "+scroll:-100%", EM("Page up")}, + {{KEY_CTRL_D}, "+scroll:+50%", EM("Half page down")}, + {{KEY_CTRL_U}, "+scroll:-50%", EM("Half page up")}, + {{KEY_MOUSE_WHEEL_DOWN}, "+scroll:+3", EM("Scroll down")}, + {{KEY_MOUSE_WHEEL_UP}, "+scroll:-3", EM("Scroll up")}, {{0}}, // Array must be 0-terminated }; #ifdef __APPLE__