The betty file manager (bfm) is a terminal-based file manager designed to utilize vim (or other text-based editor) and fzf. It was inspired by nnn but differs in ways that make it faster to use (and a bit more dangerous too).
Currently bfm can only be installed by cloning the repository, compiling, and manually installing.
git clone https://github.com/cskeeters/betty-file-manager.git --branch master --single-branch
cd betty-file-manager
go mod tidy
go build
cp bfm /usr/local/bin/
mkdir ~/.config/bfm
cp -rp plugins ~/.config/bfm
bfm starts with tab one of six active in the current directory. Pressing a number selects (and activates) a tab. If the tab pas not previously active, it will start and the same directory as the previous tab. When you close a tab, the previously selected tab will be re-selected.
Each tab remembers all of the directories which were previously displayed. I call this list the jump stack and it's a helpful way to jump back and forth between directories.
A file is added to the selection list by pressing s. The selection list can contain files from desperate folders. View the selection list with Ctrl+s.
bfm is designed to be used with fzf which is called in bash plugins. bfm comes with plugins that allow you to:
- Select a child file or directory and
cd
the current tab to it and move the cursor to the file. (Ctrl+/) - Select a directory from a list of commonly used paths and
cd
the current tab to it. (a) - Choose the desired directory after inputting string for autojump (Ctrl+j)
To have a provide a list of paths from which to select in fzf, create a text file ~/.paths
. Each line should have a full path, a tab character, then a description used for filtering that can contain spaces.
/home/chad/Downloads Downloads
/home/chad/Documents Documents
NOTE: This can be used from bash too.
autojump tracks directory usage. It allows the user to input a small string and guesses the indented directory. For example $ j bet
changes directory to ~/working/betty-file-manager
for me. From inside bfm, press J to input the parameter for j and the current tab will change to the directory returned by autojump.
NOTE: While bfm will use the editor provided in the EDITOR
environment variable, I discuss this feature assuming vim.
bfm utilizes vim for the user input when renaming a file, or bulk renaming all of the files/directories in the cwd. This allows developers already familiar with vim to utilize copy/paste, autocomplete, vertical paste, and other features in vim to facilitate headache-free renaming.
Save and quite to rename, :cq to cancel. (Based on return value)
bfm writes the cwd of the last active tab on close. You may wrap the command to bfm in a bash script or function that changes the directory of the calling shell after exit as follows.
b() {
bfm $*
LASTD="$HOME/.local/state/bfm.lastd"
if [ -f "$LASTD" ]; then
dir=$(cat "$LASTD")
cd "$dir"
fi
}
e will edit the file under the cursor with the text-editor specified in the environment variable EDITOR
. If EDITOR
is a terminal based editor, then bfm cannot be used until the editor closes. There are two ways around this problem.
- Use o instead of e. Configure the OS to open the file under the cursor in neovide or other GUI text editor.
- Open bfm in a tmux session. bfm will detect this and open a new tmux window for editing files when e is pressed.
Shell configuration for tmux:
b() {
tmux new-session -n BFM bfm $*
LASTD="$HOME/.local/state/bfm.lastd"
if [ -f "$LASTD" ]; then
dir=$(cat "$LASTD")
cd "$dir"
fi
}
Opening a shell will also be done in a separate tmux window if bfm is launched inside a tmux session
bfm does not confirm operations with the user before executing. X is like rm
, the file is gone. Utilize T to trash files, which can be undone.
bfm just calls trash, which can be a script or anything to perform the trash. Implementation options are trash-cli for linux (untested), or trash from osx-tools for MacOS.
? - help (shows shortcut keys)
q - quit
1 - Activate tab 1
2 - Activate tab 2
3 - Activate tab 3
4 - Activate tab 4
5 - Activate tab 5
6 - Activate tab 6
ctrl+s - View Selected Files
/ - Filter files (current tab only)
enter - Apply filter, back to COMMAND mode
escape - Cancel filter, back to COMMAND mode
ctrl+l - Clear filter (works in either mode)
ctrl+w - Backspace until space (delete word)
j/k - Next/Prev file
g/G - First/Last file
ctrl+d - Half page down
ctrl+u - Half page up
]/[ - Next/Prev selected file
h,-,bs - Parent directory
l/enter - Enter hovered directory
~ - Home directory
ctrl+o - Back in jumplist
ctrl+i - Next in jumplist
a - Select directory with FZF
J - autojump
ctrl+j - autojump with FZF
n - Sort by name
m - Sort by last modified
z - Sort by size (reverse)
s - Toggle select on file/directory
A - Select all files
d - Deselect All Files
e - Edit file (with EDITOR environment variable)
o - Open file (with open command/alias)
T - Trash file (with open command/alias)
- No confirmations on remove/trash
- Can view the selection list
- Operations will apply to selection if files are selected, otherwise, the file next to the cursor
- Does not support long-running plugins, but does support plugins
- Simpler file sorting
bfm has a simple plugin mechanism. It's hardly a plugin system because keys to trigger a plugin have to be manually added to the source code. This may change in the future.
BFM provides the plugin with two arguments:
- The path to the STATE_FILE
- The path to the CMD_FILE
The STATEFILE is a text file with the first line being the cwd of the current tab. The second line is the name of the file pointed to by the cursor.
The plugin may write commands to the CMD_FILE which BFM will execute once the plugin exits. BFM supports the following commands:
Command | Description |
---|---|
cd <path> |
changes directory to the path specified |
select <filename> |
moves the cursor to a file matching filename |
refresh |
refresh the current tab |
MacOS decided that folders that sync to Dropbox or OneDrive should live in ~/Library/CloudStorage/XXX. This makes the file paths unnecessary long. bfm supports simple string replacements for long paths to ensure that you can see the part of the path you need even when the window is small.
Create ~/.config/bfm/bfmrc
[[WdReplacement]]
real = "Library/CloudStorage/OneDrive-CompanyName"
repl = "=OneDrive="
[[WdReplacement]]
real = "Library/CloudStorage/OneDrive-SharedLibraries-CompanyName"
repl = "=OneDrive Shared="
bfm is written to be easily modified by developers. It's written in go and depends on:
Lipgloss is like css for the TUI. Styles are defined as variables and are then applied to pieces of text. Applying a style to a string returns a new string with the ANSI escape codes required to style the text.
TIP: Lipgloss provides an easy way to obtain the displayable width of a string that has been styled
width := lipgloss.Width(textWithASNI)
Bubbletea provides an event loop framework for TUI. Each time an event occurs (such as a keypress) an event is build and passed to Update
. Update
modifies a model that stores application state. View
is called to build a displayable screen for the user based on the current state in model.
term is mainly used to get the width and height of the terminal. When the window is resized, Update
is passed a tea.WindowSizeMsg
which requires the terminal width and height so that a screen customized to the current dimensions can be drawn by View
.
Bfm logs to ~/.local/log/bfm.log
.
File | Description |
---|---|
config.go | Loads toml configuration |
file_operations.go | User operations like Move, Copy, Delete, etc. |
fileutil.go | File related function helpers |
help.go | Generates help documentation |
main.go | Main program w/ Update (key processing) |
mathutil.go | Math related function helpers (min, max) |
model.go | BFM app state |
operations.go | View related operations like close tab |
order.go | File sorting |
plugin.go | Plugin system |
shell.go | Runs other programs like mv, cp, rm, vim, bash |
sliceutil.go | Slice related function helpers |
stringutil.go | String related function helpers |
style.go | Application styling (lipgloss) |
util.go | BFM app helpers |
view.go | Draw related code |