2019-05-21 20:06:53 -07:00
/*
2019-06-10 20:37:34 -07:00
BB Configuration , Startup Commands , and Key Bindings
2019-05-25 21:47:30 -07:00
2019-06-10 20:37:34 -07:00
User customization goes in config . h , which is created by running ` make `
2019-05-25 21:47:30 -07:00
( config . def . h is for keeping the defaults around , just in case )
2019-06-10 20:37:34 -07:00
This file contains :
- Global options , like which colors are used
- Column formatting ( width and title )
- Startup commands
- User key bindings
2019-05-25 21:47:30 -07:00
2019-06-10 20:37:34 -07:00
For startup commands and key bindings , the following values are provided as
environment variables :
2019-05-25 21:47:30 -07:00
2019-06-10 20:37:34 -07:00
$ @ ( the list of arguments ) : the full paths of the selected files , or if
no files are selected , the full path of the file under the cursor
$ BBCURSOR : the full path of the file under the cursor
$ BBSELECTED : " 1 " if any files are selected , otherwise " "
2019-06-15 14:14:05 -07:00
$ BBDOTFILES : " 1 " if files beginning with " . " are visible in bb , otherwise " "
2019-05-25 21:47:30 -07:00
$ BB_DEPTH : the number of ` bb ` instances deep ( in case you want to run a
shell and have that shell print something special in the prompt )
$ BBCMD : a file to which ` bb ` commands can be written ( used internally )
In order to modify bb ' s internal state , you can call ` bb + cmd ` , where " cmd "
is one of the following commands ( or a unique prefix of one ) :
2019-05-30 00:33:51 -07:00
. : [ 01 ] Whether to show " . " in each directory
. . : [ 01 ] Whether to show " .. " in each directory
2019-05-27 19:51:04 -07:00
cd : < path > Navigate to < path >
columns : < columns > Change which columns are visible , and in what order
2019-06-15 14:14:05 -07:00
deselect : < filename > Deselect < filename >
2019-05-30 00:33:51 -07:00
dotfiles : [ 01 ] Whether dotfiles are visible
2019-05-27 19:51:04 -07:00
goto : < filename > Move the cursor to < filename > ( changing directory if needed )
2019-05-31 13:26:23 -07:00
interleave : [ 01 ] Whether or not directories should be interleaved with files in the display
2019-05-27 19:51:04 -07:00
move : < num * > Move the cursor a numeric amount
quit Quit bb
refresh Refresh the file listing
scroll : < num * > Scroll the view a numeric amount
2019-06-15 14:14:05 -07:00
select : < filename > Select < filename >
sort : ( [ + - ] method ) + Set sorting method ( + : normal , - : reverse ) , additional methods act as tiebreaker
2019-05-27 19:51:04 -07:00
spread : < num * > Spread the selection state at the cursor
toggle : < filename > Toggle the selection status of < filename >
2019-06-10 20:37:34 -07:00
Note : for numeric - based commands ( like scroll ) , the number can be either
2019-05-25 21:47:30 -07:00
an absolute value or a relative value ( starting with ' + ' or ' - ' ) , and / or
a percent ( ending with ' % ' ) . Scrolling and moving , ' % ' means percent of
screen height , and ' % n ' means percent of number of files ( e . g . + 50 % means
half a screen height down , and 100 % n means the last file )
2019-06-10 20:37:34 -07:00
Internally , bb will write the commands ( NUL terminated ) to $ BBCMD , if
$ BBCMD is set , and read the file when file browsing resumes . These commands
can also be passed to bb at startup , and will run immediately .
E . g . ` bb ' + col : n ' ' + sort : + r ' . ` will launch ` bb ` only showing the name column , randomly sorted .
As a shorthand and performance optimization , commands that don ' t rely on any
shell variables or scripting can be written as " +move:+1 " instead of " bb '+move:+1' " ,
which is a bit faster because internally it avoids writing to and reading from
the $ BBCMD file .
2019-05-21 20:06:53 -07:00
*/
2019-05-28 21:36:42 -07:00
# include "bterm.h"
2019-05-22 00:25:25 -07:00
2019-06-10 20:37:34 -07:00
// Constants:
# define MAX_REBINDINGS 8
// Types:
typedef struct {
int keys [ MAX_REBINDINGS + 1 ] ;
2019-06-12 18:55:55 -07:00
const char * script ;
2019-06-10 20:37:34 -07:00
const char * description ;
} binding_t ;
typedef struct {
int width ;
const char * name ;
} column_t ;
2019-05-26 16:34:48 -07:00
// Configurable options:
2019-06-10 20:37:34 -07:00
# define SCROLLOFF MIN(5, (termheight-4) / 2)
2019-05-27 15:53:07 -07:00
# define CMDFILE_FORMAT " / tmp / bb.XXXXXX"
# define SORT_INDICATOR "↓"
# define RSORT_INDICATOR "↑"
2019-05-29 19:32:52 -07:00
# define SELECTED_INDICATOR " \033[31;7m \033[0m"
2019-05-29 00:56:49 -07:00
# define NOT_SELECTED_INDICATOR " "
2019-06-10 20:37:34 -07:00
// Colors (using ANSI escape sequences):
2019-05-31 13:23:01 -07:00
# define TITLE_COLOR "\033[37;1m"
2019-05-28 21:36:42 -07:00
# define NORMAL_COLOR "\033[37m"
2019-05-28 21:46:16 -07:00
# define CURSOR_COLOR "\033[43;30;1m"
2019-05-28 21:36:42 -07:00
# define LINK_COLOR "\033[35m"
# define DIR_COLOR "\033[34m"
# define EXECUTABLE_COLOR "\033[31m"
2019-05-26 16:34:48 -07:00
2019-06-10 20:37:34 -07:00
// Some handy macros for common shell script behaviors:
2019-06-15 16:17:43 -07:00
# define PAUSE " read -n1 -p '\033[2mPress any key to continue...\033[0m\033[?25l' > / dev / tty < / dev / tty"
2019-05-21 20:06:53 -07:00
2019-06-10 20:37:34 -07:00
// Bold text:
# define B(s) "\033[1m" s "\033[22m"
// Macros for getting user input:
2019-06-10 22:26:13 -07:00
# ifdef USE_ASK
# define ASKECHO(prompt, initial) "ask --prompt=\"" prompt "\" --query=\"" initial "\""
2019-06-06 18:10:14 -07:00
# define ASK(var, prompt, initial) var "=\"$(" ASKECHO(prompt, initial) ")\""
2019-06-06 15:33:20 -07:00
# else
2019-09-15 18:02:36 -07:00
# define ASK(var, prompt, initial) "read -p \"" B(prompt) "\" " var " < / dev / tty > / dev / tty"
# define ASKECHO(prompt, initial) "read -p \"" B(prompt) "\" REPLY < / dev / tty > / dev / tty && echo \"$REPLY\""
2019-06-06 18:10:14 -07:00
# endif
2019-06-10 22:26:13 -07:00
// Macros for picking from a list of options:
2019-06-06 18:10:14 -07:00
# ifndef PICK
2019-06-11 17:38:24 -07:00
# define PICK(prompt, initial) " { awk '{print length, $1}' | sort -n | cut -d' ' -f2- | "\
2019-07-12 16:19:31 -07:00
" grep -i -m1 \" $( " ASKECHO ( prompt , initial ) " | sed 's;.;[^/&]*[&];g') \" ; } "
2019-06-06 15:33:20 -07:00
# endif
2019-06-10 20:37:34 -07:00
// Display a spinning indicator if command takes longer than 10ms:
# ifndef SPIN
# define SPIN(cmd) "{ " cmd "; } & " \
" pid=$!; " \
" spinner='- \\ |/'; " \
" sleep 0.01; " \
" while kill -0 $pid 2>/dev/null; do " \
" printf '%c \\ 033[D' \" $spinner \" >/dev/tty; " \
2019-06-12 15:11:29 -07:00
" spinner= \" $(echo $spinner | sed 's/ \\ (. \\ ) \\ (.* \\ )/ \\ 2 \\ 1/') \" ; " \
2019-06-10 20:37:34 -07:00
" sleep 0.1; " \
" done "
# endif
2019-05-25 20:56:38 -07:00
2019-09-15 18:02:36 -07:00
# ifndef CONFIRM
# define CONFIRM(action, files) " { { echo \""B(action)"\"; printf '%s\\n' \""files"\"; } | more; " \
ASK ( " REPLY " , " Is that okay? [y/N] " , " " ) " ; test \" $REPLY \" = 'y'; } "
# endif
2019-05-31 17:59:58 -07:00
2019-05-25 21:47:30 -07:00
// These commands will run at startup (before command-line arguments)
2019-05-25 20:56:38 -07:00
extern const char * startupcmds [ ] ;
2019-05-31 17:59:58 -07:00
extern const column_t columns [ 128 ] ;
2019-06-10 20:37:34 -07:00
extern binding_t bindings [ ] ;
2019-05-31 14:56:25 -07:00
2019-06-10 20:37:34 -07:00
// Column widths and titles:
2019-05-31 17:59:58 -07:00
const column_t columns [ 128 ] = {
[ ' * ' ] = { 2 , " * " } ,
[ ' a ' ] = { 21 , " Accessed " } ,
[ ' c ' ] = { 21 , " Created " } ,
[ ' m ' ] = { 21 , " Modified " } ,
[ ' n ' ] = { 40 , " Name " } ,
[ ' p ' ] = { 5 , " Permissions " } ,
[ ' r ' ] = { 2 , " Random " } ,
[ ' s ' ] = { 9 , " Size " } ,
2019-05-31 14:56:25 -07:00
} ;
2019-06-10 20:37:34 -07:00
// This is a list of commands that runs when `bb` launches:
const char * startupcmds [ ] = {
// Set some default marks:
2019-09-12 18:41:15 -07:00
" mkdir -p ~/.config/bb/marks " ,
2019-09-15 18:02:36 -07:00
" ln -sT ~/.config/bb/marks ~/.config/bb/marks/marks 2>/dev/null " ,
" ln -sT ~ ~/.config/bb/marks/home 2>/dev/null " ,
" ln -sT / ~/.config/bb/marks/root 2>/dev/null " ,
" ln -sT ~/.config ~/.config/bb/marks/config 2>/dev/null " ,
" ln -sT ~/.local ~/.config/bb/marks/local 2>/dev/null " ,
2019-06-10 20:37:34 -07:00
// Default column and sorting options:
" +sort:+n " , " +col:*smpn " , " +.. " ,
NULL , // NULL-terminated array
} ;
/******************************************************************************
* These are all the key bindings for bb .
* The format is : { { keys , . . . } , " <script> " , " <description> " }
*
* Please note that these are sh scripts , not bash scripts , so bash - isms
* won ' t work unless you make your script use ` bash - c " <your bash script> " `
*
* If your editor is vim ( and not neovim ) , you can replace ` $ EDITOR ` below with
* ` vim - c ' set t_ti = t_te = ' " $@ " ` to prevent momentarily seeing the shell
* after editing .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-05-25 20:56:38 -07:00
binding_t bindings [ ] = {
2019-07-12 16:19:31 -07:00
{ { ' ? ' , KEY_F1 } , " +help " , B ( " Help " ) " menu " } ,
2019-05-31 21:49:42 -07:00
{ { ' q ' , ' Q ' } , " +quit " , B ( " Quit " ) } ,
{ { ' j ' , KEY_ARROW_DOWN } , " +move:+1 " , B ( " Next " ) " file " } ,
{ { ' k ' , KEY_ARROW_UP } , " +move:-1 " , B ( " Previous " ) " file " } ,
{ { ' h ' , KEY_ARROW_LEFT } , " +cd:.. " , B ( " Parent " ) " directory " } ,
{ { ' l ' , KEY_ARROW_RIGHT } , " test -d \" $BBCURSOR \" && bb \" +cd:$BBCURSOR \" " , B ( " Enter " ) " a directory " } ,
2019-05-25 20:56:38 -07:00
{ { ' \r ' , KEY_MOUSE_DOUBLE_LEFT } ,
2019-06-06 16:45:31 -07:00
" if test -d \" $BBCURSOR \" ; then bb \" +cd:$BBCURSOR \" ; "
2019-06-30 01:24:47 -07:00
# ifdef __APPLE__
2019-06-12 16:57:07 -07:00
" elif file -bI \" $BBCURSOR \" | grep -q '^ \\ (text/ \\ |inode/empty \\ )'; then $EDITOR \" $BBCURSOR \" ; "
2019-06-06 16:45:31 -07:00
" else open \" $BBCURSOR \" ; fi " ,
2019-05-23 00:57:25 -07:00
# else
2019-06-12 16:57:07 -07:00
" elif file -bi \" $BBCURSOR \" | grep -q '^ \\ (text/ \\ |inode/empty \\ )'; then $EDITOR \" $BBCURSOR \" ; "
2019-06-06 16:45:31 -07:00
" else xdg-open \" $BBCURSOR \" ; fi " ,
2019-05-23 00:57:25 -07:00
# endif
2019-06-10 20:37:34 -07:00
B ( " Open " ) " file/directory " } ,
2019-05-31 21:49:42 -07:00
{ { ' ' , ' v ' , ' V ' } , " +toggle " , B ( " Toggle " ) " selection " } ,
2019-06-15 14:14:05 -07:00
{ { KEY_ESC } , " bb +deselect: \" $@ \" " , B ( " Clear " ) " selection " } ,
2019-06-10 20:37:34 -07:00
{ { ' e ' } , " $EDITOR \" $@ \" || " PAUSE , B ( " Edit " ) " file in $EDITOR " } ,
2019-06-15 14:14:05 -07:00
{ { KEY_CTRL_F } , " bb \" +goto:$(if test $BBDOTFILES; then find -mindepth 1; else find -mindepth 1 ! -path '*/.*'; fi "
" | " PICK ( " Find: " , " " ) " ) \" " , B ( " Search " ) " for file " } ,
{ { ' / ' } , " bb \" +goto:$(if test $BBDOTFILES; then find -mindepth 1 -maxdepth 1; else find -mindepth 1 -maxdepth 1 ! -path '*/.*'; fi "
" | " PICK ( " Pick: " , " " ) " ) \" " , B ( " Pick " ) " file " } ,
2019-09-15 18:02:36 -07:00
{ { ' d ' , KEY_DELETE } , CONFIRM ( " The following files will be deleted: " , " $@ " ) " && rm -rf \" $@ \" && bb +refresh && bb +deselect: \" $@ \" " ,
B ( " Delete " ) " files " } ,
{ { ' M ' } , CONFIRM ( " The following files will be moved here: " , " $@ " ) " && " SPIN ( " mv -i \" $@ \" . && bb +refresh && bb +deselect: \" $@ \" && for f; do bb \" +sel:$(basename \" $f \" ) \" ; done " ) " || " PAUSE ,
2019-05-31 21:49:42 -07:00
B ( " Move " ) " files to current directory " } ,
2019-09-15 18:02:36 -07:00
{ { ' c ' } , CONFIRM ( " The following files will be copied here: " , " $@ " )
" && for f; do if test \" ./$(basename \" $f \" ) \" -ef \" $f \" ; then "
SPIN ( " cp -ri \" $f \" \" $(basename \" $f \" ).copy \" " ) " ; "
" else " SPIN ( " cp -ri \" $f \" . " ) " ; fi; done; bb +refresh " ,
B ( " Copy " ) " the selected files here " } ,
2019-06-09 16:15:17 -07:00
{ { ' n ' } , ASK ( " name " , " New file: " , " " ) " && touch \" $name \" && bb \" +goto:$name \" +r || " PAUSE , B ( " New file " ) } ,
{ { ' N ' } , ASK ( " name " , " New dir: " , " " ) " && mkdir \" $name \" && bb \" +goto:$name \" +r || " PAUSE , B ( " New directory " ) } ,
2019-06-12 15:11:29 -07:00
{ { KEY_CTRL_G } , " bb \" +cd:$( " ASKECHO ( " Go to directory: " , " " ) " ) \" " , B ( " Go to " ) " directory " } ,
2019-09-15 18:02:36 -07:00
{ { KEY_CTRL_S } , ASK ( " savename " , " Save selection as: " , " " ) " && printf '%s \\ 0' \" $@ \" > ~/.config/bb/ \" $savename \" " ,
B ( " Save " ) " the selection " } ,
{ { KEY_CTRL_O } , " loadpath= \" $(find ~/.config/bb -maxdepth 1 -type f | " PICK ( " Load selection: " , " " ) " ) \" "
" && test -e \" $loadpath \" && bb +deselect:'*' "
" && while IFS= read -r -d $' \\ 0'; do bb +select: \" $REPLY \" ; done < \" $loadpath \" " ,
B ( " Open " ) " a selection " } ,
2019-06-18 21:16:32 -07:00
{ { ' | ' } , ASK ( " cmd " , " | " , " " ) " && printf '%s \\ n' \" $@ \" | sh -c \" $cmd \" ; " PAUSE " ; bb +r " ,
2019-05-31 21:49:42 -07:00
B ( " Pipe " ) " selected files to a command " } ,
2019-06-18 21:16:32 -07:00
{ { ' : ' } , " sh -c \" $( " ASKECHO ( " : " , " " ) " ) \" -- \" $@ \" ; " PAUSE " ; bb +refresh " ,
2019-05-31 21:49:42 -07:00
B ( " Run " ) " a command " } ,
2019-07-12 16:19:31 -07:00
{ { ' > ' } , " tput rmcup >/dev/tty; $SHELL; bb +r " , " Open a " B ( " shell " ) } ,
2019-09-15 18:02:36 -07:00
{ { ' \' ' } , " mark= \" $(ls ~/.config/bb/marks | " PICK ( " Jump to: " , " " ) " ) \" "
" && bb +cd: \" $(readlink -f ~/.config/bb/marks/ \" $mark \" ) \" " ,
2019-09-12 18:41:15 -07:00
B ( " Jump " ) " to a directory " } ,
{ { ' - ' } , " test $BBPREVPATH && bb +cd: \" $BBPREVPATH \" " , " Go to " B ( " previous " ) " directory " } ,
2019-09-15 18:02:36 -07:00
{ { ' ; ' } , " bb +cd:'<selection>' " , " Show " B ( " selected files " ) } ,
{ { ' 0 ' } , " bb +cd: \" $BBINITIALPATH \" " , " Go to " B ( " initial directory " ) } ,
2019-09-12 18:41:15 -07:00
{ { ' m ' } , " ln -s \" $PWD \" ~/.config/bb/marks/ \" $( " ASKECHO ( " Mark: " , " " ) " ) \" " , B ( " Mark " ) " this directory " } ,
2019-05-31 21:49:42 -07:00
{ { ' r ' } ,
2019-06-09 16:15:17 -07:00
" bb +refresh; "
2019-06-06 16:45:31 -07:00
" for f; do "
2019-06-12 18:55:55 -07:00
" if r= \" $(dirname \" $f \" )/$( " ASKECHO ( " Rename: " , " $(basename \" $f \" ) " ) " ) \" ; then "
2019-06-09 16:15:17 -07:00
" if test \" $r \" != \" $f \" && mv -i \" $f \" \" $r \" ; then "
" test $BBSELECTED && bb \" +deselect:$f \" \" +select:$r \" ; "
" fi; "
" else break; "
" fi; "
2019-06-06 18:10:14 -07:00
" done " , B ( " Rename " ) " files " } ,
2019-05-31 21:49:42 -07:00
{ { ' R ' } ,
2019-06-09 16:15:17 -07:00
" if " ASK ( " patt " , " Rename pattern: " , " s/ " ) " ; then true; else exit; fi; "
" if sed -E \" $patt \" </dev/null; then true; else " PAUSE " ; exit; fi; "
" bb +refresh; "
2019-06-06 16:45:31 -07:00
" for f; do "
2019-06-12 15:11:29 -07:00
" renamed= \" $(dirname \" $f \" )/$(basename \" $f \" | sed -E \" $patt \" ) \" ; "
" if test \" $f \" != \" $renamed \" && mv -i \" $f \" \" $renamed \" && test $BBSELECTED; then "
2019-06-12 16:57:07 -07:00
" bb \" +deselect:$f \" \" +select:$renamed \" ; "
2019-06-12 15:11:29 -07:00
" fi; "
2019-06-12 16:57:07 -07:00
" done " , B ( " Regex rename " ) " files " } ,
{ { ' S ' } ,
2019-07-12 16:19:31 -07:00
ASK ( " patt " , " Select pattern: " , " " ) " && bb +sel: \" $patt \" " ,
2019-06-12 16:57:07 -07:00
B ( " Select " ) " file(s) by pattern " } ,
2019-05-31 21:49:42 -07:00
{ { ' J ' } , " +spread:+1 " , B ( " Spread " ) " selection down " } ,
{ { ' K ' } , " +spread:-1 " , B ( " Spread " ) " selection up " } ,
2019-06-12 15:11:29 -07:00
{ { ' b ' } , " bb \" +$( " ASKECHO ( " bb + " , " " ) " ) \" " , " Run a " B ( " bb command " ) } ,
2019-05-31 21:49:42 -07:00
{ { ' s ' } ,
2019-09-07 21:51:59 -07:00
" read -n1 -p \" " B ( " Sort (n)ame (s)ize (m)odification (c)reation (a)ccess (r)andom (p)ermissions: " ) " \" sort "
" && bb \" +sort:+$sort \" +refresh " ,
2019-05-31 21:49:42 -07:00
B ( " Sort " ) " by... " } ,
2019-06-12 15:11:29 -07:00
{ { ' # ' } , " bb \" +col:$( " ASKECHO ( " Set columns: " , " " ) " ) \" " , " Set " B ( " columns " ) } ,
2019-06-10 22:26:13 -07:00
{ { ' . ' } , " +dotfiles " , " Toggle " B ( " dotfiles " ) } ,
2019-05-31 21:49:42 -07:00
{ { ' g ' , KEY_HOME } , " +move:0 " , " Go to " B ( " first " ) " file " } ,
{ { ' G ' , KEY_END } , " +move:100%n " , " Go to " B ( " last " ) " file " } ,
{ { KEY_F5 , KEY_CTRL_R } , " +refresh " , B ( " Refresh " ) } ,
2019-07-12 16:19:31 -07:00
{ { KEY_CTRL_A } , " if test $BBDOTFILES; then find -mindepth 1 -maxdepth 1 -print0; else find -mindepth 1 -maxdepth 1 ! -path '*/.*' -print0; fi | bb +sel: " ,
2019-06-15 14:14:05 -07:00
B ( " Select all " ) " files in current directory " } ,
2019-05-31 21:49:42 -07:00
{ { KEY_PGDN } , " +scroll:+100% " , B ( " Page down " ) } ,
{ { KEY_PGUP } , " +scroll:-100% " , B ( " Page up " ) } ,
{ { KEY_CTRL_D } , " +scroll:+50% " , B ( " Half page down " ) } ,
{ { KEY_CTRL_U } , " +scroll:-50% " , B ( " Half page up " ) } ,
{ { KEY_MOUSE_WHEEL_DOWN } , " +scroll:+3 " , B ( " Scroll down " ) } ,
{ { KEY_MOUSE_WHEEL_UP } , " +scroll:-3 " , B ( " Scroll up " ) } ,
2019-06-15 14:13:35 -07:00
{ { - 1 } }
// Array must be -1-terminated
2019-05-21 20:06:53 -07:00
} ;
2019-05-28 21:36:42 -07:00
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1