1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
|
/*
BB Key Bindings
User-defined key bindings go in config.h, which is created by running `make`
(config.def.h is for keeping the defaults around, just in case)
The basic structure is:
<list of keys to bind>
<program to run>
<description> (for the help menu)
<flags> (whether to run in a full terminal window or silently, etc.)
When the scripts are run, the following values are provided as environment variables:
$@ (the list of arguments): the full paths of the selected files
$BBCURSOR: the full name of the file under the cursor
$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):
.:[01] Whether to show "." in each directory
..:[01] Whether to show ".." in each directory
cd:<path> Navigate to <path>
columns:<columns> Change which columns are visible, and in what order
deselect:<filename> Deselect <filename>
dotfiles:[01] Whether dotfiles are visible
goto:<filename> Move the cursor to <filename> (changing directory if needed)
interleave:[01] Whether or not directories should be interleaved with files in the display
jump:<key> Jump to the mark associated with <key>
mark:<key>[=<path>] Associate <key> with <path> (or current dir, if blank)
move:<num*> Move the cursor a numeric amount
quit Quit bb
refresh Refresh the file listing
scroll:<num*> Scroll the view a numeric amount
select:<filename> Select <filename>
sort:([+-]method)+ List of sortings (if equal on one, move to the next)
spread:<num*> Spread the selection state at the cursor
toggle:<filename> Toggle the selection status of <filename>
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.
*Note: for numeric-based commands (like scroll), the number can be either
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)
*/
#include "bterm.h"
// Configurable options:
#define KEY_DELAY 50
#define SCROLLOFF MIN(5, (termheight-4)/2)
#define CMDFILE_FORMAT "/tmp/bb.XXXXXX"
#define SORT_INDICATOR "↓"
#define RSORT_INDICATOR "↑"
#define SELECTED_INDICATOR " \033[31;7m \033[0m"
#define NOT_SELECTED_INDICATOR " "
#define TITLE_COLOR "\033[37;1m"
#define NORMAL_COLOR "\033[37m"
#define CURSOR_COLOR "\033[43;30;1m"
#define LINK_COLOR "\033[35m"
#define DIR_COLOR "\033[34m"
#define EXECUTABLE_COLOR "\033[31m"
#define PAUSE " read -n1 -p '\033[2mPress any key to continue...\033[0m\033[?25l'"
#ifndef FUZZY
#define FUZZY(prompt) "fzy --prompt=\"" prompt "\""
#endif
#ifndef ASK
#ifdef ASKECHO
#define ASK(var, prompt, ...) var "=\"$(" ASKECHO(prompt, ## __VA_ARGS__) ")\""
#else
#define ASK(var, prompt, ...) "read -p \"" prompt "\" " var
#endif
#endif
#ifndef ASKECHO
#define ASKECHO(prompt, ...) ASK("REPLY", prompt ## __VA_ARGS__) " && echo \"$REPLY\""
#endif
#define NORMAL_TERM (1<<0)
#define MAX_REBINDINGS 8
typedef struct {
int keys[MAX_REBINDINGS+1];
const char *command;
const char *description;
int flags;
} binding_t;
typedef struct {
int width;
const char *name;
} column_t;
// These commands will run at startup (before command-line arguments)
extern const char *startupcmds[];
extern const column_t columns[128];
const char *startupcmds[] = {
//////////////////////////////////////////////
// User-defined startup commands can go here
//////////////////////////////////////////////
// Set some default marks:
"+mark:0", "+mark:~=~", "+mark:h=~", "+mark:/=/", "+mark:c=~/.config",
"+mark:l=~/.local", "+mark:s=<selection>",
// Default column and sorting options:
"+sort:+n", "+col:*smpn", "+..",
NULL, // NULL-terminated array
};
// Column widths:
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"},
};
extern binding_t bindings[];
#define B(s) "\033[1m" s "\033[22m"
binding_t bindings[] = {
/*************************************************************************
* User-defined custom scripts can go here
* Format is: {{keys}, "script", "help text", flags}
*
* 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 script>"`
************************************************************************/
{{'?', KEY_F1}, "bb -b | $PAGER -r", B("Help")" menu", NORMAL_TERM},
{{'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"},
{{'\r', KEY_MOUSE_DOUBLE_LEFT},
#ifdef __APPLE__
"if test -d \"$BBCURSOR\"; then bb \"+cd:$BBCURSOR\"; "
"elif test -x \"$BBCURSOR\"; then \"$BBCURSOR\"; " PAUSE "; "
"elif file -bI \"$BBCURSOR\" | grep '^\\(text/\\|inode/empty\\)' >/dev/null; then $EDITOR \"$BBCURSOR\"; "
"else open \"$BBCURSOR\"; fi",
#else
"if test -d \"$BBCURSOR\"; then bb \"+cd:$BBCURSOR\"; "
"elif file -bi \"$BBCURSOR\" | grep '^\\(text/\\|inode/empty\\)' >/dev/null; then $EDITOR \"$BBCURSOR\"; "
"else xdg-open \"$BBCURSOR\"; fi",
#endif
B("Open")" file/directory", NORMAL_TERM},
{{' ','v','V'}, "+toggle", B("Toggle")" selection"},
{{KEY_ESC}, "+deselect:*", B("Clear")" selection"},
{{'e'}, "$EDITOR \"$@\"", B("Edit")" file in $EDITOR", NORMAL_TERM},
{{KEY_CTRL_F}, "bb \"+g:`find | "FUZZY("Find: ")"`\" +r", B("Fuzzy search")" for file"},
{{'/'}, "bb \"+g:`ls -a | " FUZZY("Pick: ") "`\" +r", B("Fuzzy pick")" file"},
{{'d', KEY_DELETE}, "rm -rfi \"$@\"; bb '+de:*' +r", B("Delete")" files"},
{{'D'}, "rm -rf \"$@\"; bb '+de:*' +r", B("Delete")" files (without confirmation)"},
{{'M'}, "mv -i \"$@\" .; bb '+de:*' +r; for f; do bb \"+sel:`pwd`/`basename \"$f\"`\"; done",
B("Move")" files to current directory"},
{{'c'}, "cp -i \"$@\" .; bb +r", B("Copy")" files to current directory"},
{{'C'}, "bb '+de:*'; for f; do cp \"$f\" \"$f.copy\" && bb \"+sel:$f.copy\"; done; bb +r", B("Clone")" files"},
{{'n'}, ASK("name", "New file: ")" && touch \"$name\"; bb +r \"+goto:$name\"", B("New file")},
{{'N'}, ASK("name", "New dir: ")" && mkdir \"$name\"; bb +r \"+goto:$name\"", B("New directory")},
{{KEY_CTRL_G}, "bb \"+cd:`" ASKECHO("Go to directory: ") "`\" +r", B("Go to")" directory"},
{{'|'}, ASK("cmd", "|") " && printf '%s\\n' \"$@\" | sh -c \"$cmd\"; " PAUSE "; bb +r",
B("Pipe")" selected files to a command"},
{{':'}, "sh -c \"`" ASKECHO(":") "`\" -- \"$@\"; " PAUSE "; bb +refresh",
B("Run")" a command"},
{{'>'}, "$SHELL", "Open a "B("shell"), NORMAL_TERM},
{{'m'}, "read -n1 -p 'Mark: ' m && bb \"+mark:$m;$PWD\"", "Set "B("mark")},
{{'\''}, "read -n1 -p 'Jump: ' j && bb \"+jump:$j\"", B("Jump")" to mark"},
{{'r'},
"bb '+deselect*' +refresh; "
"for f; do "
" if r=\"$(dirname \"$f\")/$("ASKECHO("Rename: ", "--initial=\"$(basename \"$f\")\"")")\" && "
" test \"$r\" != \"$f\" && mv -i \"$f\" \"$r\"; then "
" test $BBSELECTED && bb \"+select:$r\"; "
" elif test $BBSELECTED; then bb \"+select:$f\"; fi; "
"done", B("Rename")" files"};
{{'R'},
"if patt=\"`ask --initial='s/' 'Rename pattern: '`\"; then true; else bb +r; exit; fi; "
"if sed -E \"$patt\" </dev/null; then true; else " PAUSE "; bb +r; exit; fi; "
"bb '+deselect:*' +refresh; "
"for f; do "
" renamed=\"`dirname \"$f\"`/`basename \"$f\" | sed -E \"$patt\"`\"; "
" if test \"$f\" != \"$renamed\" && mv -i \"$f\" \"$renamed\"; then "
" test $BBSELECTED && bb \"+select:$renamed\"; "
" elif test $BBSELECTED; then bb \"+select:$f\"; fi "
"done", B("Regex rename")" files"},
{{'P'},
"patt=`ask 'Select pattern: '` && "
"for f in *; do echo \"$f\" | grep \"$patt\" >/dev/null 2>/dev/null && bb \"+sel:$f\"; done",
B("Regex select")" files"},
{{'J'}, "+spread:+1", B("Spread")" selection down"},
{{'K'}, "+spread:-1", B("Spread")" selection up"},
{{'b'}, "bb \"+`"ASKECHO("bb +")"`\"", "Run a "B("bb command")},
{{'s'},
("sort=\"$(ask -q 'Sort (n)ame (s)ize (m)odification (c)reation (a)ccess (r)andom "
"(p)ermissions: ' n s m c a r p)\""
"&& bb \"+sort:+$sort\""),
B("Sort")" by..."},
{{'#'}, "bb \"+col:`"ASKECHO("Set columns: ")"`\"", "Set "B("columns")},
{{'.'}, "bb +dotfiles", "Toggle "B("dotfiles")},
{{'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")},
{{KEY_CTRL_A}, "+select:*", B("Select all")" files in current directory"},
{{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")},
{{0}}, // Array must be 0-terminated
};
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
|