diff --git a/README.md b/README.md
index a17e4a4..28166ea 100644
--- a/README.md
+++ b/README.md
@@ -1,26 +1,42 @@
-# fff (*Fucking Fast File-Manager*)
+# fff (*Fucking Fine File-Manager*)
-
-
-A simple file manager written in `bash`.
-
-
-
-
+### This fork is meant to make fff a more feature-rich file manager, but at cost of raw speed.
-- It's Fucking Fast ๐
-- Minimal (*only requires **bash** and coreutils*)
-- Smooth Scrolling (*using **vim** keybindings*)
-- Works on **Linux**, **BSD**, **macOS**, **Haiku** etc.
-- Supports `LS_COLORS`!
-- File Operations (*copy, paste, cut, **ranger style bulk rename**, etc*)
-- Instant as you type search
-- Tab completion for all commands!
-- Automatic CD on exit (*see [setup](#cd-on-exit)*)
-- Works as a **file picker** in `vim`/`neovim` ([**link**](https://github.com/dylanaraps/fff.vim))!
-- **Display images with w3m-img!**
-- Supports `$CDPATH`.
+
+### Changes to original
+
+- [Nerd Fonts devicons](https://www.nerdfonts.com/#home) support
+- Help page on `?`
+- `Open with` commands
+- `ctrl + d`/`ctrl + u` scrolling
+- view images using [sixel](https://github.com/saitoha/libsixel)
+- Git branch on status line
+- Recursive git signs for changed files
+- Display file modification date, time and size (resource-heavy)
+- Sort files by modification time or alphabetically
+- Working history of directories and picker for them
+- Changed marking behavior to nnn-like (mark with space, choose a command, and then execute it)
+- Changed keybindings to better suit more options
+- Optional config file for global configuration
+- Git branch in stats
+- Copy filename to clipboard with `y` and copy file with `c` (when marking)
+- Changed single file renaming behavior to allow using arrows and automatically display renamed file (and `ctrl + a` to go at the beginning of the filename).
+- Deleted clear option (clear marks by pressing `FFF_KEY_MARK_ALL`)
+- Mark and open with multiple files at time
+- Human-readable size in stats
+
+
+### Thanks
+
+A big part of code in there is from people who made PRs and posted issues to fff:
+
+- Roy Orbitson (help page)
+- Sidd Dino (devicons)
+- qwool (human-readable size)
+- Docbroke (sorting)
+- yiselieren (file details)
+- Isaac Elenbaas (config file, changing renaming behavior)
## Table of Contents
@@ -32,7 +48,6 @@ A simple file manager written in `bash`.
* [Manual](#manual)
* [CD on Exit](#cd-on-exit)
* [Bash and Zsh](#bash-and-zsh)
- * [Fish](#fish)
* [Usage](#usage)
* [Customization](#customization)
* [Customizing the keybindings.](#customizing-the-keybindings)
@@ -55,38 +70,22 @@ A simple file manager written in `bash`.
- Program handling (*non-text*).
- *Not needed on macos and Haiku.*
- *Customizable (if not using `xdg-open`): `$FFF_OPENER`.*
-
-**Dependencies for image display**
-
-- `w3m-img`
-- `xdotool` for X.
-- `fbset` for the framebuffer.
-
+- `Nerd Font` (*optional*)
+ - Icons
+- `xclip or any clipboard manager` (*optional*)
+ - clipboard
+- `libsixel` (*optional*)
+ - sixel support
## Installation
-### Distros
-
-- KISS Linux (based): `kiss b fff`
-- FreeBSD: `pkg install fff`
-- Haiku: `pkgman install fff`
-- macOS: `brew install fff`
-- Nix: `nix-env -iA fff`
-- Void Linux: `xbps-install -S fff`
-- Arch Linux: `pacman -S fff`
-
### Manual
1. Download `fff`.
- - Release: https://github.com/dylanaraps/fff/releases/latest
- - Git: `git clone https://github.com/dylanaraps/fff`
+ - Git: `git clone https://github.com/piotr-marendowski/fff`
2. Change working directory to `fff`.
- `cd fff`
3. Run `make install` inside the script directory to install the script.
- - **El Capitan**: `make PREFIX=/usr/local install`
- - **Haiku**: `make PREFIX="$(finddir B_USER_NONPACKAGED_DIRECTORY)" MANDIR='$(PREFIX)/documentation/man' DOCDIR='$(PREFIX)/documentation/fff' install`
- - **OpenIndiana**: `gmake install`
- - **MinGW/MSys**: `make -i install`
- **NOTE**: You may have to run this as root.
**NOTE:** `fff` can be uninstalled easily using `make uninstall`. This removes all of files from your system.
@@ -101,16 +100,6 @@ f() {
cd "$(cat "${XDG_CACHE_HOME:=${HOME}/.cache}/fff/.fff_d")"
}
```
-#### Fish
-```sh
-# Add this to you config.fish or equivalent.
-# Fish don't support recursive calls so use f function
-function f
- fff $argv
- set -q XDG_CACHE_HOME; or set XDG_CACHE_HOME $HOME/.cache
- cd (cat $XDG_CACHE_HOME/fff/.fff_d)
-end
-```
## Usage
@@ -120,9 +109,12 @@ k: scroll up
h: go to parent dir
l: go to child dir
-enter: go to child dir
+enter: go to child dir/open file
backspace: go to parent dir
+o: open file with
+O: open file with a GUI program detached from file manager
+
-: Go to previous dir.
g: go to top
@@ -134,11 +126,14 @@ G: go to bottom
/: search
t: go to trash
~: go to home
-e: refresh current dir
+z: refresh current dir
!: open shell in current dir
+i: display file details
+u: sort files
x: view file/dir attributes
-i: display image with w3m-img
+e: show history
+y: copy filename to clipboard
down: scroll down
up: scroll up
@@ -150,34 +145,53 @@ n: new dir
r: rename
X: toggle executable
-y: mark copy
-m: mark move
-d: mark trash (~/.local/share/fff/trash/)
-s: mark symbolic link
-b: mark bulk rename
-
-Y: mark all for copy
-M: mark all for move
-D: mark all for trash (~/.local/share/fff/trash/)
-S: mark all for symbolic link
-B: mark all for bulk rename
+space: mark file
+a: mark all files in directory
+c: copy
+m: move
+d: trash (move to FFF_TRASH)
+s: symbolic link
+b: bulk rename
p: execute paste/move/delete/bulk_rename
-c: clear file selections
[1-9]: favourites/bookmarks (see customization)
q: exit with 'cd' (if enabled).
Ctrl+C: exit without 'cd'.
+
+?: show help
```
## Customization
+`FFF_CONFIG` can be added to your `bashrc` (or other shell's configuration files). Everything put in `FFF_CONFIG` file will be sourced globally meaning that e.g. Neovim's terminal will have these settings.
+
+Personal note (can be unreproducible for your): I'm not sure why the only option (maybe there are others) not working in config file is `FFF_HIDDEN` which only works, when fff is run inside terminal manually.
+
```sh
# Show/Hide hidden files on open.
# (Off by default)
export FFF_HIDDEN=1
+# Show/Hide file icons on open
+# (Off by default)
+export FFF_FILE_ICON=1
+
+# Show/Hide git status signs (+) on open
+# (Off by default)
+export FFF_GIT_CHANGES=1
+
+# Default method to sort files on open
+# 0 - alphabetically
+# 1 - modification time
+# (0 by default)
+export FFF_SORT_METHOD=1
+
+# Show/Hide file details on open
+# (Off by default)
+export FFF_FILE_DETAILS=1
+
# Use LS_COLORS to color fff.
# (On by default if available)
# (Ignores FFF_COL1)
@@ -198,8 +212,16 @@ export FFF_COL4=1
# Status foreground color [0-9]
export FFF_COL5=0
+# Selection color
+# (inverted foreground by default)
+# ('48;2;R;G;B' values separated by ';', don't edit the '48;2;' part!).
+# In terminals that support truecolor, this will set the selection color
+# to grey, but on others selection will be only white bold text (if this
+# is set).
+export FFF_COL6="48;2;80;80;80"
+
# Text Editor
-export EDITOR="vim"
+export EDITOR="nvim"
# File Opener
export FFF_OPENER="xdg-open"
@@ -216,6 +238,11 @@ export FFF_CD_ON_EXIT=0
# If not using XDG, '${HOME}/.cache/fff/fff.d' is used.
export FFF_CD_FILE=~/.fff_d
+# Config File
+# Default: '${XDG_CONFIG_HOME/fff}'
+# If not using XDG, '${HOME}/.config/fff' is used.
+export FFF_CONFIG=~/.config/fff
+
# Trash Directory
# Default: '${XDG_DATA_HOME}/fff/trash'
# If not using XDG, '${HOME}/.local/share/fff/trash' is used.
@@ -239,9 +266,13 @@ export FFF_FAV7=
export FFF_FAV8=
export FFF_FAV9=
-# w3m-img offsets.
-export FFF_W3M_XOFFSET=0
-export FFF_W3M_YOFFSET=0
+# History file length.
+# (100 lines by default)
+# Every cd-on-exit (q) program deletes every line older than
+# FFF_HISTORY_LENGTH.
+# Example: history has 150 lines, quitting trims history file
+# to 100 most recent.
+export FFF_HISTORY_LENGTH=200
# File format.
# Customize the item string.
@@ -254,6 +285,19 @@ export FFF_FILE_FORMAT="%f"
# Format ('%f' is the current file): "str%fstr"
# Example (Add a ' >' before files): FFF_MARK_FORMAT="> %f"
export FFF_MARK_FORMAT=" %f*"
+
+# Clipboard program and arguments.
+# Default: xclip -selection c
+export FFF_KEY_CLIPBOARD="xclip -selection c"
+
+# Scroll steps.
+# (14 by default).
+export FFF_SCROLL_UP=14
+export FFF_SCROLL_DOWN=14
+
+# Sixel image program.
+# Default: img2sixel
+export FFF_SIXEL_CMD="img2sixel"
```
## Customizing the keybindings.
@@ -301,32 +345,34 @@ export FFF_KEY_TO_BOTTOM="G"
export FFF_KEY_GO_DIR=":"
export FFF_KEY_GO_HOME="~"
export FFF_KEY_GO_TRASH="t"
-export FFF_KEY_REFRESH="e"
+export FFF_KEY_REFRESH="z"
### File operations.
-
-export FFF_KEY_YANK="y"
+export FFF_KEY_MARK=" "
+export FFF_KEY_MARK_ALL="a"
+export FFF_KEY_COPY="c"
export FFF_KEY_MOVE="m"
export FFF_KEY_TRASH="d"
export FFF_KEY_LINK="s"
export FFF_KEY_BULK_RENAME="b"
-export FFF_KEY_YANK_ALL="Y"
-export FFF_KEY_MOVE_ALL="M"
-export FFF_KEY_TRASH_ALL="D"
-export FFF_KEY_LINK_ALL="S"
-export FFF_KEY_BULK_RENAME_ALL="B"
-
-export FFF_KEY_PASTE="p"
-export FFF_KEY_CLEAR="c"
+export FFF_KEY_EXECUTE="p"
export FFF_KEY_RENAME="r"
export FFF_KEY_MKDIR="n"
export FFF_KEY_MKFILE="f"
-export FFF_KEY_IMAGE="i" # display image with w3m-img
### Miscellaneous
+# Display file details.
+export FFF_KEY_DETAILS="i"
+
+# Display an image using sixel.
+export FFF_KEY_SIXEL="I"
+
+# Sort files.
+export FFF_KEY_SORT="u"
+
# Show file attributes.
export FFF_KEY_ATTRIBUTES="x"
@@ -335,6 +381,12 @@ export FFF_KEY_EXECUTABLE="X"
# Toggle hidden files.
export FFF_KEY_HIDDEN="."
+
+# Show history of directories.
+export FFF_KEY_HISTORY="e"
+
+# Yank filename to clipboard.
+export FFF_KEY_CLIPBOARD="y"
```
### Disabling keybindings.
@@ -394,11 +446,4 @@ read -srn 1 && key "$REPLY"
## Using `fff` in vim/neovim as a file picker
-See: [**`fff.vim`**](https://github.com/dylanaraps/fff.vim)
-
-
-## Why?
-
-ยฏ\\_(ใ)_/ยฏ
-
-dont touch my shrug
+See: [**`fff.vim`**](https://github.com/dylanaraps/fff.vim) or [**`fm-nvim`**](https://github.com/is0n/fm-nvim)
diff --git a/fff b/fff
index 16766e3..0555560 100755
--- a/fff
+++ b/fff
@@ -1,6 +1,4 @@
#!/usr/bin/env bash
-#
-# fff - fucking fast file-manager.
get_os() {
# Figure out the current operating system to set some specific variables.
@@ -15,8 +13,7 @@ get_os() {
haiku)
opener=open
- [[ -z $FFF_TRASH_CMD ]] &&
- FFF_TRASH_CMD=trash
+ [[ -z $FFF_TRASH_CMD ]] && FFF_TRASH_CMD=trash
[[ $FFF_TRASH_CMD == trash ]] && {
FFF_TRASH=$(finddir -v "$PWD" B_TRASH_DIRECTORY)
@@ -72,6 +69,10 @@ setup_options() {
# select options so the operation isn't repeated
# multiple times in the code.
+ # Source config file.
+ [[ -f "${FFF_CONFIG:=${XDG_CONFIG_HOME:=${HOME}/.config}/fff}" ]] &&
+ . "${FFF_CONFIG:=${XDG_CONFIG_HOME:=${HOME}/.config}/fff}"
+
# Format for normal files.
[[ $FFF_FILE_FORMAT == *%f* ]] && {
file_pre=${FFF_FILE_FORMAT/'%f'*}
@@ -82,14 +83,17 @@ setup_options() {
# Use affixes provided by the user or use defaults, if necessary.
if [[ $FFF_MARK_FORMAT == *%f* ]]; then
mark_pre=${FFF_MARK_FORMAT/'%f'*}
- mark_post=${FFF_MARK_FORMAT/*'%f'}
else
mark_pre=" "
- mark_post="*"
fi
# Find supported 'file' arguments.
file -I &>/dev/null || : "${file_flags:=biL}"
+
+ helping=0
+ sort=${FFF_SORT_METHOD:=0}
+ details=${FFF_FILE_DETAILS:=0}
+ history=0
}
get_term_size() {
@@ -98,7 +102,7 @@ get_term_size() {
read -r LINES COLUMNS < <(stty size)
# Max list items that fit in the scroll area.
- ((max_items=LINES-3))
+ ((max_items=LINES-2))
}
get_ls_colors() {
@@ -143,24 +147,355 @@ get_ls_colors() {
export "${ls_cols[@]}" &>/dev/null
}
-get_w3m_path() {
- # Find the path to the w3m-img library.
- w3m_paths=(/usr/{pkg/,}{local/,}{bin,lib,libexec,lib64,libexec64}/w3m/w3mi*)
- read -r w3m _ < <(type -p "$FFF_W3M_PATH" w3mimgdisplay "${w3m_paths[@]}")
-}
-
get_mime_type() {
# Get a file's mime_type.
mime_type=$(file "-${file_flags:-biL}" "$1" 2>/dev/null)
}
+get_icon() {
+ # $1 Absolute path to the file
+ # $2 name of the file/directory
+ # $3 the extracted extension from the file name
+
+ # Icons for directories
+ [[ -d "$1" ]] && {
+ case "$2" in
+ '.git' ) printf -- '๓ฐข'; return ;;
+ 'Desktop' ) printf -- '๓ฐ'; return ;;
+ 'Documents' ) printf -- '๏'; return ;;
+ 'Downloads' ) printf -- '๏'; return ;;
+ '.config' |\
+ 'Dotfiles' |\
+ 'dotfiles' |\
+ '.dotfiles' ) printf -- '๎'; return ;;
+ '.cache' ) printf -- '๏ฒ'; return ;;
+ 'Music' ) printf -- '๏ฅ'; return ;;
+ 'Pictures' ) printf -- '๎'; return ;;
+ 'Public' ) printf -- '๏ซ'; return ;;
+ 'Templates' ) printf -- '๏
'; return ;;
+ 'Videos' ) printf -- '๏'; return ;;
+ 'Games' ) printf -- '๓ฐ'; return ;;
+ 'home' ) printf -- '๏'; return ;;
+ 'mnt' ) printf -- '๓ฐถ'; return ;;
+ 'Dropbox' ) printf -- '๏
ซ'; return ;;
+
+ * ) printf -- '๎'; return ;;
+ esac
+ }
+ # Icons for files with no extension
+ [[ "$2" == "$3" ]] && {
+ case "$2" in
+ 'lsb-release' | 'arch-release' |\
+ 'gshadow' | 'group' |\
+ 'hosts' | 'hostname' |\
+ 'shadow' | 'sudoers' |\
+ 'passwd' | 'os-release' |\
+ '_gvimrc' | '_vimrc' |\
+ 'bspwmrc' |'cmakelists.txt'|\
+ 'config' | 'gradlew' |\
+ 'ini' | 'sxhkdrc' ) printf -- '๎'; return ;;
+
+ 'Makefile' | 'makefile' ) printf -- '๎ณ'; return ;;
+
+ 'authorized_keys' |\
+ 'known_hosts' |\
+ 'license' |\
+ 'LICENSE' ) printf -- '๎'; return ;;
+
+ 'gemfile' |\
+ 'Rakefile' |\
+ 'rakefile' ) printf -- '๎'; return ;;
+
+ 'a.out' |\
+ 'configure' ) printf -- '๎'; return ;;
+
+ 'dockerfile' |\
+ 'Dockerfile' ) printf -- '๓ฐกจ'; return ;;
+
+ 'dropbox' ) printf -- '๏
ซ'; return ;;
+ 'exact-match-case-sensitive-2' ) printf -- 'X'; return ;;
+ 'ledger' ) printf -- '๏
'; return ;;
+ 'node_modules' ) printf -- '๓ฐ'; return ;;
+ 'playlists' ) printf -- '๏'; return ;;
+ 'procfile' ) printf -- '๎'; return ;;
+ 'README' ) printf -- '๎'; return ;;
+ 'PKGBUILD' ) printf -- '๓ฐ'; return ;;
+ 'log' ) printf -- '๓ฑ'; return ;;
+ '*' ) printf -- '๎'; return ;;
+ esac
+ }
+ # Icon for files with the name starting with '.'
+ # without an extension
+ [[ "$2" == ".$3" ]] && {
+ case "$2" in
+ '.bash_aliases' |\
+ '.bash_history' |\
+ '.bash_logout' |\
+ '.bash_profile' |\
+ '.bashprofile' | '.project' |\
+ '.gitattributes' | '.bashrc' |\
+ '.gitconfig' | '.gitignore' |\
+ '.jack-settings' | '.DS_Store' |\
+ '.nvidia-settings-rc' | '.inputrc' |\
+ '.pam_environment' | '.dmrc' |\
+ '.recently-used' | '.profile' |\
+ '.selected_editor' | '.fasd' |\
+ '.Xdefaults' | '.Xauthority' |\
+ '.xinitrc' | '.xinputrc' |\
+ '.xprofile' | '.Xresources' |\
+ '.zsh_history' | '.zshrc' ) printf -- '๎'; return ;;
+
+ '.vim' |\
+ '.viminfo' |\
+ '.vimrc' ) printf -- '๎
'; return ;;
+
+ '.fehbg' ) printf -- '๎'; return ;;
+ '.gvimrc' ) printf -- '๎ซ'; return ;;
+ '.ncmpcpp' ) printf -- '๏'; return ;;
+
+ '*' ) printf -- '๎'; return ;;
+ esac
+ }
+ # Icon for files whose names have an extension
+ [[ "$2" == *"."* ]] && {
+ case "$2" in
+ 'locale.gen' |\
+ 'prefs.js' |\
+ 'cmakelists.txt' |\
+ 'Makefile.ac' |\
+ 'Makefile.in' |\
+ 'mimeapps.list' |\
+ 'user-dirs.dirs' ) printf -- '๎'; return ;;
+
+ 'README.markdown' |\
+ 'README.md' |\
+ 'README.rst' |\
+ 'README.txt' ) printf -- '๎'; return ;;
+
+ 'config.ac' |\
+ 'config.m4' |\
+ 'config.mk' ) printf -- '๎ค'; return ;;
+
+ 'gruntfile.coffee' |\
+ 'gruntfile.js' |\
+ 'gruntfile.ls' ) printf -- '๎'; return ;;
+
+ 'package-lock.json' |\
+ 'package.json' |\
+ 'webpack.config.js' ) printf -- '๎'; return ;;
+
+ 'gulpfile.coffee' |\
+ 'gulpfile.js' |\
+ 'gulpfile.ls' ) printf -- '๎'; return ;;
+
+ 'LICENSE.txt' |\
+ 'LICENSE.md' ) printf -- '๎'; return ;;
+
+
+ '.gitlab-ci.yml' ) printf -- '๏'; return ;;
+ 'config.ru' ) printf -- '๎'; return ;;
+ 'docker-compose.yml' ) printf -- '๏'; return ;;
+ 'exact-match-case-sensitive-1.txt' ) printf -- 'X'; return ;;
+ 'favicon.ico' ) printf -- '๎ฃ'; return ;;
+ 'mix.lock' ) printf -- '๎ญ'; return ;;
+ 'react.jsx' ) printf -- '๎ฅ'; return ;;
+ esac
+
+ case "$3" in
+ 'shada' |\
+ 'efi' | 'menu' |\
+ 'cfg' | 'EFI' |\
+ 'desktop' | 'name' |\
+ 'package-cache' | 'prefs' |\
+ 'reg' | 'gradle' |\
+ 'bat' | 'conf' |\
+ 'cvs' | 'tsv' |\
+ 'htaccess' | 'config' |\
+ 'htpasswd' | 'pro' |\
+ 'ini' | 'rc' |\
+ 'toml' | 'yaml' |\
+ 'x86_64' | 'x86' |\
+ 'yml' | 'properties' ) printf -- '๎'; return ;;
+
+ '7z' | 'apk' |\
+ 'bz2' | 'cab' |\
+ 'cpio'| 'deb' |\
+ 'gem' | 'gz' |\
+ 'gzip'| 'lha' |\
+ 'lzh' | 'lzma' |\
+ 'rar' | 'rpm' |\
+ 'tar' | 'tgz' |\
+ 'xbps'| 'xz' |\
+ 'zip' | 'zst' ) printf -- '๏'; return ;;
+
+ 'asp' | 'awk' |\
+ 'bash'| 'csh' |\
+ 'elf' |\
+ 'fish'| 'ksh' |\
+ 'ps1' | 'rom' |\
+ 'zsh' ) printf -- '๎'; return ;;
+
+ 'avi' | 'flv' |\
+ 'm4v' | 'mkv' |\
+ 'mov' | 'mp4' |\
+ 'mpeg'| 'mpg' |\
+ 'webm' ) printf -- '๏ฌ'; return ;;
+
+ 'bmp' | 'gif' |\
+ 'ico' | 'jpeg' |\
+ 'jpg' | 'png' |\
+ 'ppt' | 'pptx' |\
+ 'webp'| 'GIF' |\
+ 'xcf' | 'xbm' ) printf -- '๎'; return ;;
+
+ 'aup' | 'cue' |\
+ 'flac'| 'm4a' |\
+ 'mp3' | 'ogg' |\
+ 'wav' ) printf -- '๏'; return ;;
+
+ 'c' | 'c++' |\
+ 'cc' | 'cp' |\
+ 'cpp' | 'cxx' |\
+ 'h' | 'hpp' ) printf -- '๎'; return ;;
+
+ 'docx'| 'doc' |\
+ 'epub'| 'pdf' |\
+ 'rtf' | 'xls' |\
+ 'xlsx' ) printf -- '๏ญ'; return ;;
+
+ 'ejs' | 'haml' |\
+ 'htm' | 'html' |\
+ 'slim'| 'xhtml'|\
+ 'xml' ) printf -- '๎'; return ;;
+
+ 'a' | 'cmake' |\
+ 'jl' | 'o' |\
+ 'so' ) printf -- '๎ค'; return ;;
+
+ 'asm' | 'css' |\
+ 'less'| 's' |\
+ 'sh' | 'style' ) printf -- '๎'; return ;;
+
+ 'db' | 'dump' |\
+ 'img' |\
+ 'sql' | 'files' ) printf -- '๎'; return ;;
+
+ 'f#' | 'fs' |\
+ 'fsi' | 'fsx' |\
+ 'fsscript' ) printf -- '๎ง'; return ;;
+
+ 'markdown' |\
+ 'md' | 'mdx' |\
+ 'rmd' ) printf -- '๎'; return ;;
+
+ 'gemspec' |\
+ 'rake'| 'rb' ) printf -- '๎'; return ;;
+
+ 'dll' | 'exe' |\
+ 'msi' | 'bin' ) printf -- '๎'; return ;;
+
+ 'eex' | 'ex' |\
+ 'exs' | 'leex' ) printf -- '๎ญ'; return ;;
+
+ 'jar' | 'java' ) printf -- '๎'; return ;;
+ 'class' ) printf -- '๓ฐ'; return ;;
+
+ 'mustache' |\
+ 'hbs' ) printf -- '๎'; return ;;
+
+ 'json' |\
+ 'webmanifest' ) printf -- '๎'; return ;;
+
+ 'ttf' | 'otf' ) printf -- '๏ฑ'; return ;;
+
+ 'py' | 'pyc' |\
+ 'pyd' | 'pyo' ) printf -- '๎'; return ;;
+
+ 'cbr' | 'cbz' ) printf -- '๏ต'; return ;;
+ 'clj' | 'cljc' ) printf -- '๎จ'; return ;;
+ 'cljs'| 'edn' ) printf -- '๎ช'; return ;;
+ 'hrl' | 'erl' ) printf -- '๎ฑ'; return ;;
+ 'hh' | 'hxx' ) printf -- '๏ฝ'; return ;;
+ 'hs' | 'lhs' ) printf -- '๎'; return ;;
+ 'js' | 'mjs' ) printf -- '๎'; return ;;
+ 'jsx' | 'tsx' ) printf -- '๎บ'; return ;;
+ 'key' | 'pub' ) printf -- '๎'; return ;;
+ 'ml' | 'mli' ) printf -- 'ฮป'; return ;;
+ 'pl' | 'pm' ) printf -- '๎ฉ'; return ;;
+ 'vim' | 'vimrc' ) printf -- '๎
'; return ;;
+ 'psb' | 'psd' ) printf -- '๎ธ'; return ;;
+ 'rlib'| 'rs' ) printf -- '๎จ'; return ;;
+ 'sass'| 'scss' ) printf -- '๎'; return ;;
+ 'sln' | 'suo' ) printf -- '๎'; return ;;
+
+ 'coffee' ) printf -- '๎'; return ;;
+ 'ai' ) printf -- '๎ด'; return ;;
+ 'cs' ) printf -- '๏ '; return ;;
+ 'd' ) printf -- '๎ฏ'; return ;;
+ 'dart' ) printf -- '๎'; return ;;
+ 'diff' ) printf -- '๎จ'; return ;;
+ 'elm' ) printf -- '๎ฌ'; return ;;
+ 'fi' ) printf -- '|'; return ;;
+ 'go' ) printf -- '๎ง'; return ;;
+ 'log' ) printf -- '๓ฑ'; return ;;
+ 'lua' ) printf -- '๎ '; return ;;
+ 'nix' ) printf -- '๏'; return ;;
+ 'php' ) printf -- '๎'; return ;;
+ 'pp' ) printf -- '๏'; return ;;
+ 'r' ) printf -- '๎'; return ;;
+ 'rss' ) printf -- '๎'; return ;;
+ 'scala' ) printf -- '๎ท'; return ;;
+ 'styl' ) printf -- '๎'; return ;;
+ 'swift' ) printf -- '๎'; return ;;
+ 't' ) printf -- '๎ฉ'; return ;;
+ 'tex' ) printf -- '๎'; return ;;
+ 'ts' ) printf -- '๎จ'; return ;;
+ 'twig' ) printf -- '๎'; return ;;
+ 'vue' ) printf -- '๎ '; return ;;
+ 'xcplayground' ) printf -- '๎'; return ;;
+ 'xul' ) printf -- '๎
'; return ;;
+
+ 'lock' ) printf -- '๏ฃ'; return ;;
+ 'iso' ) printf -- '๓ฐป'; return ;;
+ 'uuid' ) printf -- '๏ '; return ;;
+ 'rej' ) printf -- '๎'; return ;;
+
+ '1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9') printf -- '๓ฐ'; return ;;
+ esac
+ }
+
+ printf -- '๎'; return
+}
+
status_line() {
# Status_line to print when files are marked for operation.
- local mark_ui="[${#marked_files[@]}] selected (${file_program[*]}) [p] ->"
+ local mark_ui=
+ [ ${#marked_files[@]} -gt 0 ] && {
+ mark_ui="[${#marked_files[@]}] selected "
+ [[ -n $file_program ]] &&
+ mark_ui+="(${file_program[*]}) "
+ }
+
+ # Display git branch and sorting.
+ git_branch=$(git branch 2> /dev/null | sed -n -e 's/^\* \(.*\)/[\1]/p')
+
+ # Display current sorting
+ [ "$sort" -eq 0 ] && \
+ sort_help="Sort: alpha" || \
+ sort_help="Sort: date"
# Escape the directory string.
# Remove all non-printable characters.
- PWD_escaped=${PWD//[^[:print:]]/^[}
+ if ((helping)) || ((history)); then
+ mark_ui=
+ PWD_escaped=" "
+ sort_help=
+ git_branch=
+ else
+ # Escape the directory string.
+ # Remove all non-printable characters.
+ PWD_escaped=${PWD//[^[:print:]]/^[}
+ fi
# '\e7': Save cursor position.
# This is more widely supported than '\e[s'.
@@ -175,14 +510,14 @@ status_line() {
# '\e8': Restore cursor position.
# This is more widely supported than '\e[u'.
printf '\e7\e[%sH\e[3%s;4%sm%*s\r%s %s%s\e[m\e[%sH\e[K\e8' \
- "$((LINES-1))" \
+ "$LINES" \
"${FFF_COL5:-0}" \
"${FFF_COL2:-1}" \
"$COLUMNS" "" \
"($((scroll+1))/$((list_total+1)))" \
- "${marked_files[*]:+${mark_ui}}" \
- "${1:-${PWD_escaped:-/}}" \
- "$LINES"
+ "$mark_ui" \
+ "${1:-${PWD_escaped:-/} $git_branch $sort_help}" \
+ "$((LINES-1))"
}
read_dir() {
@@ -200,22 +535,26 @@ read_dir() {
for item in "$PWD"/*; do
if [[ -d $item ]]; then
dirs+=("$item")
-
# Find the position of the child directory in the
# parent directory list.
[[ $item == "$OLDPWD" ]] &&
((previous_index=item_index))
+
((item_index++))
else
files+=("$item")
fi
done
+ [ "$sort" == 1 ] && {
+ dirs=($(stat -c '%Y=%n' "${dirs[@]}" | sort -nr | cut -d '=' -f2))
+ files=($(stat -c '%Y=%n' "${files[@]}" | sort -nr | cut -d '=' -f2))
+ }
+
list=("${dirs[@]}" "${files[@]}")
# Indicate that the directory is empty.
- [[ -z ${list[0]} ]] &&
- list[0]=empty
+ [[ -z ${list[0]} ]] && list[0]=empty
((list_total=${#list[@]}-1))
@@ -229,24 +568,35 @@ print_line() {
local file_ext=${file_name##*.}
local format
local suffix
+ local icon
+ local git
+
+ # History and help pages.
+ if ((helping)) || ((history)); then
+ file_name=${list[$1]}
+ [[ "$file_name" ]] && {
+ # Highlight the key(s)/numbers.
+ format+="\\e[${di:-1;3${FFF_COL1:-2}}m"
+ ((helping)) && local action="${file_name%: *}"
+ format+="$(cat -A <<<"$action" | head -c -2)\\e[${fi:-37}m "
+ file_name="${file_name##*: }"
+
+ # Check if item is selected.
+ (($1 == scroll)) && format+="\\e[1;${FFF_COL4:-1};${FFF_COL6:-7}m"
+ printf '\r%b\e[m\r' "${format}${file_name}"
+
+ return
+ }
# If the dir item doesn't exist, end here.
- if [[ -z ${list[$1]} ]]; then
+ elif [[ -z ${list[$1]} ]]; then
return
# Directory.
elif [[ -d ${list[$1]} ]]; then
- format+=\\e[${di:-1;3${FFF_COL1:-2}}m
+ format+=\\e[${di:-0;3${FFF_COL1:-4}}m
suffix+=/
- # Block special file.
- elif [[ -b ${list[$1]} ]]; then
- format+=\\e[${bd:-40;33;01}m
-
- # Character special file.
- elif [[ -c ${list[$1]} ]]; then
- format+=\\e[${cd:-40;33;01}m
-
# Executable file.
elif [[ -x ${list[$1]} ]]; then
format+=\\e[${ex:-01;32}m
@@ -259,14 +609,6 @@ print_line() {
elif [[ -h ${list[$1]} ]]; then
format+=\\e[${ln:-01;36}m
- # Fifo file.
- elif [[ -p ${list[$1]} ]]; then
- format+=\\e[${pi:-40;33}m
-
- # Socket file.
- elif [[ -S ${list[$1]} ]]; then
- format+=\\e[${so:-01;35}m
-
# Color files that end in a pattern as defined in LS_COLORS.
# 'BASH_REMATCH' is an array that stores each REGEX match.
elif [[ $FFF_LS_COLORS == 1 &&
@@ -291,21 +633,55 @@ print_line() {
# If the list item is under the cursor.
(($1 == scroll)) &&
- format+="\\e[1;3${FFF_COL4:-6};7m"
+ format+="\\e[1;${FFF_COL4:-1};${FFF_COL6:-7}m"
# If the list item is marked for operation.
- [[ ${marked_files[$1]} == "${list[$1]:-null}" ]] && {
+ [[ ${marked_files[$1]} == "${list[$1]:-null}" ]] &&
format+=\\e[3${FFF_COL3:-1}m${mark_pre}
- suffix+=${mark_post}
- }
# Escape the directory string.
# Remove all non-printable characters.
file_name=${file_name//[^[:print:]]/^[}
+ # Get icon.
+ (( ${FFF_FILE_ICON:=0} == 1 )) &&
+ icon="$(get_icon "${list[$1]}" "$file_name" "$file_ext") "
+
+ # Check if current dir is a git repo and has changes,
+ # then display git signs if it is.
+ if [ -n "$git_branch" ] && [ -n "$git_output" ]; then
+ (( ${FFF_GIT_CHANGES:=0} == 1 )) &&
+ [[ " ${changed[@]} " =~ " $file_name " ]] && git="+ " || git=" "
+ fi
+
+ # Display details (date, time and file size).
+ # If directory is empty then don't show details.
+ if (( details == 1 )) && [[ ${list[0]} != empty ]]; then
+ local line=' '
+
+ file_size=$("${FFF_STAT_CMD:-stat}" -c "%s" "${list[$1]}" 2> /dev/null | numfmt --to=iec)
+ date="$(date -r ${list[$1]} +"%Y-%m-%d %H:%M" 2> /dev/null)"
+
+ # If there is error, then just print space to align file names.
+ if [ "$?" -ne "0" ]; then
+ date=" "
+ line=' '
+ else
+ date=" ${date} "
+ file_size=" ${file_size}B "
+ fi
+ else
+ file_size=
+ date=
+ line=
+ fi
+
+ # ${file_size}${line:${#file_size}}
+ # https://stackoverflow.com/a/4411098
+ # This basically makes the file size and file name align.
printf '\r%b%s\e[m\r' \
- "${file_pre}${format}" \
- "${file_name}${suffix}${file_post}"
+ "${file_pre}${format}${date}${file_size}${line:${#file_size}} ${git}${icon}"\
+ "${file_name}${suffix}${file_post} "
}
draw_dir() {
@@ -361,111 +737,158 @@ draw_dir() {
((y=scroll_new_pos))
}
-draw_img() {
- # Draw an image file on the screen using w3m-img.
- # We can use the framebuffer; set win_info_cmd as appropriate.
- [[ $(tty) == /dev/tty[0-9]* && -w /dev/fb0 ]] &&
- win_info_cmd=fbset
-
- # X isn't running and we can't use the framebuffer, do nothing.
- [[ -z $DISPLAY && $win_info_cmd != fbset ]] &&
- return
-
- # File isn't an image file, do nothing.
- get_mime_type "${list[scroll]}"
- [[ $mime_type != image/* ]] &&
- return
-
- # w3m-img isn't installed, do nothing.
- type -p "$w3m" &>/dev/null || {
- cmd_line "error: Couldn't find 'w3m-img', is it installed?"
- return
- }
-
- # $win_info_cmd isn't installed, do nothing.
- type -p "${win_info_cmd:=xdotool}" &>/dev/null || {
- cmd_line "error: Couldn't find '$win_info_cmd', is it installed?"
- return
+redraw() {
+ changed=()
+ git_branch=$(git branch 2> /dev/null | sed -n -e 's/^\* \(.*\)/\1/p')
+ # Check if git branch name is not empty, then get changed files.
+ [ -n "$git_branch" ] && {
+ git_output=$(git status -s -unormal)
+ while read -r line; do
+ # Get elements.
+ element="${line#* }"
+ element="${line%%/*}"
+ changed+=("$element")
+ done <<< "$git_output"
}
- # Get terminal window size in pixels and set it to WIDTH and HEIGHT.
- if [[ $win_info_cmd == xdotool ]]; then
- IFS=$'\n' read -d "" -ra win_info \
- < <(xdotool getactivewindow getwindowgeometry --shell)
-
- declare "${win_info[@]}" &>/dev/null || {
- cmd_line "error: Failed to retrieve window size."
- return
+ # Redraw the current window.
+ # If 'full' is passed, re-fetch the directory list.
+ if [[ $1 == full ]]; then
+ ((helping)) || ((history)) && {
+ helping=0
+ history=0
+ find_previous=1
}
- else
- [[ $(fbset --show) =~ .*\"([0-9]+x[0-9]+)\".* ]]
- IFS=x read -r WIDTH HEIGHT <<< "${BASH_REMATCH[1]}"
+ read_dir
+ scroll=0
+ # If 'help' is passed, list help text.
+ elif [[ $1 == help ]]; then
+ helping=1
+ list_help
+ scroll=0
+ # If 'help' is passed, list help text.
+ elif [[ $1 == hist ]]; then
+ history=1
+ get_history
+ scroll=0
fi
- # Get the image size in pixels.
- read -r img_width img_height < <("$w3m" <<< "5;${list[scroll]}")
+ clear_screen
+ draw_dir
+ status_line
+}
- # Subtract the status_line area from the image size.
- ((HEIGHT=HEIGHT-HEIGHT*5/LINES))
+list_help() {
+ # Set window name.
+ printf '\e]2;fff: help\e'\\
+
+ # Turn off icons.
+ icon_value=${FFF_FILE_ICON:-0}
+ FFF_FILE_ICON=0
+
+ list=(
+ "${FFF_KEY_SCROLL_DOWN1:-j}: scroll down"
+ "${FFF_KEY_SCROLL_UP1:-k}: scroll up"
+ "${FFF_KEY_PARENT1:-h}: go to parent dir"
+ "${FFF_KEY_CHILD1:-l}: go to child dir"
+ ''
+ 'enter: go to child dir/open file'
+ 'backspace: go to parent dir'
+ ''
+ "${FFF_KEY_OPEN_WITH:=o}: open file with"
+ "${FFF_KEY_OPEN_WITH_DETACHED:=O}: open file with GUI program detached from file manager"
+ ''
+ "${FFF_KEY_PREVIOUS:--}: go to previous dir"
+ ''
+ "${FFF_KEY_TO_TOP:-g}: go to top"
+ "${FFF_KEY_TO_BOTTOM:-G}: go to bottom"
+ ''
+ "${FFF_KEY_GO_DIR:-:}: go to a directory by typing"
+ ''
+ "${FFF_KEY_HIDDEN:-.}: toggle hidden files"
+ "${FFF_KEY_SEARCH:-/}: search"
+ "${FFF_KEY_GO_TRASH:-t}: go to trash"
+ "${FFF_KEY_GO_HOME:-~}: go to home"
+ "${FFF_KEY_REFRESH:-z}: refresh current dir"
+ "${FFF_KEY_SHELL:-!}: open shell in current dir"
+ ''
+ "${FFF_KEY_DETAILS:-i}: display file details"
+ "${FFF_KEY_SIXEL:-I}: display an image using sixel"
+ "${FFF_KEY_SORT:-u}: sort files"
+ "${FFF_KEY_ATTRIBUTES:-x}: view file/dir attributes"
+ "${FFF_KEY_HISTORY:-e}: show history"
+ "${FFF_KEY_CLIPBOARD:-y}: yank filename to clipboard"
+ ''
+ 'down: scroll down'
+ 'up: scroll up'
+ 'left: go to parent dir'
+ 'right: go to child dir'
+ ''
+ "ctrl+d: scroll down for ${FFF_SCROLL_DOWN:=14} lines"
+ "ctrl+u: scroll up for ${FFF_SCROLL_UP:=14} lines"
+ ''
+ "${FFF_KEY_MKFILE:-f}: new file"
+ "${FFF_KEY_MKDIR:-n}: new dir"
+ "${FFF_KEY_RENAME:-r}: rename"
+ "${FFF_KEY_EXECUTABLE:-X}: toggle executable"
+ ''
+ "${FFF_KEY_MARK:-" "}: mark"
+ "${FFF_KEY_MARK_ALL:-a}: mark all"
+ "${FFF_KEY_COPY:-c}: mark copy"
+ "${FFF_KEY_MOVE:-m}: mark move"
+ "${FFF_KEY_TRASH:-d}: mark trash"
+ "${FFF_KEY_LINK:-s}: mark symbolic link"
+ "${FFF_KEY_BULK_RENAME:-b}: mark bulk rename"
+ ''
+ "${FFF_KEY_PASTE:-p}: execute paste/move/delete/bulk_rename"
+ ''
+ "q: exit with 'cd' (if enabled) or exit this help"
+ "ctrl+c: exit without 'cd'"
+ ''
+ "Trash directory: ${FFF_TRASH:-${XDG_DATA_HOME}/fff/trash}"
+ "${FFF_KEY_HELP:-?}: show this help"
+ ''
+ 'Bookmarks [1-9]: '
+ )
+
+ # Add bookmarks to list.
+ for i in {1..9}; do
+ var="FFF_FAV$i"
+ path=${!var}
+ if [[ -n $path ]]; then
+ list+=("$i: $path")
+ fi
+ done
- ((img_width > WIDTH)) && {
- ((img_height=img_height*WIDTH/img_width))
- ((img_width=WIDTH))
- }
+ ((list_total=${#list[@]}-1))
- ((img_height > HEIGHT)) && {
- ((img_width=img_width*HEIGHT/img_height))
- ((img_height=HEIGHT))
- }
+ # Save the original list in a second list as a backup.
+ cur_list=("${list[@]}")
+}
- clear_screen
- status_line "${list[scroll]}"
+get_history() {
+ # Set window name.
+ printf '\e]2;fff: history\e'\\
- # Add a small delay to fix issues in VTE terminals.
- ((BASH_VERSINFO[0] > 3)) &&
- read "${read_flags[@]}" -srn 1
-
- # Display the image.
- printf '0;1;%s;%s;%s;%s;;;;;%s\n3;\n4\n' \
- "${FFF_W3M_XOFFSET:-0}" \
- "${FFF_W3M_YOFFSET:-0}" \
- "$img_width" \
- "$img_height" \
- "${list[scroll]}" | "$w3m" &>/dev/null
-
- # Wait for user input.
- read -ern 1
-
- # Clear the image.
- printf '6;%s;%s;%s;%s\n3;' \
- "${FFF_W3M_XOFFSET:-0}" \
- "${FFF_W3M_YOFFSET:-0}" \
- "$WIDTH" \
- "$HEIGHT" | "$w3m" &>/dev/null
+ # Turn off icons.
+ icon_value=${FFF_FILE_ICON:-0}
+ FFF_FILE_ICON=0
- redraw
-}
+ # Add history to lines.
+ list=()
+ i=1
+ while read -r line; do
+ list+=("$i: $line")
+ i=$((i+1))
+ done < "${XDG_CACHE_HOME:=${HOME}/.cache}/fff/history"
-redraw() {
- # Redraw the current window.
- # If 'full' is passed, re-fetch the directory list.
- [[ $1 == full ]] && {
- read_dir
- scroll=0
- }
+ ((list_total=${#list[@]}-1))
- clear_screen
- draw_dir
- status_line
+ # Save the original list in a second list as a backup.
+ cur_list=("${list[@]}")
}
mark() {
- # Mark file for operation.
- # If an item is marked in a second directory,
- # clear the marked files.
- [[ $PWD != "$mark_dir" ]] &&
- marked_files=()
-
# Don't allow the user to mark the empty directory list item.
[[ ${list[0]} == empty && -z ${list[1]} ]] &&
return
@@ -473,19 +896,17 @@ mark() {
if [[ $1 == all ]]; then
if ((${#marked_files[@]} != ${#list[@]})); then
marked_files=("${list[@]}")
- mark_dir=$PWD
else
marked_files=()
+ file_program=()
fi
redraw
else
if [[ ${marked_files[$1]} == "${list[$1]}" ]]; then
- unset 'marked_files[scroll]'
-
+ unset 'marked_files[$1]'
else
marked_files[$1]="${list[$1]}"
- mark_dir=$PWD
fi
# Clear line before changing it.
@@ -493,32 +914,15 @@ mark() {
print_line "$1"
fi
- # Find the program to use.
- case "$2" in
- ${FFF_KEY_YANK:=y}|${FFF_KEY_YANK_ALL:=Y}) file_program=(cp -iR) ;;
- ${FFF_KEY_MOVE:=m}|${FFF_KEY_MOVE_ALL:=M}) file_program=(mv -i) ;;
- ${FFF_KEY_LINK:=s}|${FFF_KEY_LINK_ALL:=S}) file_program=(ln -s) ;;
-
- # These are 'fff' functions.
- ${FFF_KEY_TRASH:=d}|${FFF_KEY_TRASH_ALL:=D})
- file_program=(trash)
- ;;
-
- ${FFF_KEY_BULK_RENAME:=b}|${FFF_KEY_BULK_RENAME_ALL:=B})
- file_program=(bulk_rename)
- ;;
- esac
+ # If no programs are marked, clear the type of operation.
+ [[ -z ${marked_files[*]} ]] &&
+ file_program=()
status_line
}
trash() {
# Trash a file.
- cmd_line "trash [${#marked_files[@]}] items? [y/n]: " y n
-
- [[ $cmd_reply != y ]] &&
- return
-
if [[ $FFF_TRASH_CMD ]]; then
# Pass all but the last argument to the user's
# custom script. command is used to prevent this function
@@ -526,10 +930,10 @@ trash() {
command "$FFF_TRASH_CMD" "${@:1:$#-1}"
else
- cd "$FFF_TRASH" || cmd_line "error: Can't cd to trash directory."
+ cd "$FFF_TRASH" || cmd_line "\e[0;31mWarning!\e[0m Can't cd to trash directory."
if cp -alf "$@" &>/dev/null; then
- rm -r "${@:1:$#-1}"
+ rm -rf "${@:1:$#-1}"
else
mv -f "$@"
fi
@@ -554,7 +958,7 @@ bulk_rename() {
# If the user deleted a line, stop here.
((${#marked_files[@]} != ${#changed_files[@]})) && {
rm "$rename_file"
- cmd_line "error: Line mismatch in rename file. Doing nothing."
+ cmd_line "\e[0;31mWarning!\e[0m Line mismatch in rename file. Doing nothing."
return
}
@@ -584,14 +988,31 @@ bulk_rename() {
}
open() {
- # Open directories and files.
+ # Directories
if [[ -d $1/ ]]; then
search=
search_end_early=
- cd "${1:-/}" ||:
- redraw full
+ cd "${1:-/}" && {
+ # Add directory to history.
+ redraw full
+ echo -e "${PWD}\n$(cat "${XDG_CACHE_HOME:=${HOME}/.cache}/fff/history")" > \
+ "${XDG_CACHE_HOME:=${HOME}/.cache}/fff/history"
+ } || {
+ # Catch the error and redraw at current position.
+ clear_screen
+ draw_dir
+ cmd_line "\e[0;31mWarning!\e[0m Permission denied: ${1}"
+ }
+ # Files
elif [[ -f $1 ]]; then
+ # If 'fff' was opened as a file picker, save the opened
+ # file in a file called 'opened_file'.
+ ((file_picker == 1)) && {
+ printf '%s\n' "$1" > \
+ "${XDG_CACHE_HOME:=${HOME}/.cache}/fff/opened_file"
+ exit
+ }
# Figure out what kind of file we're working with.
get_mime_type "$1"
@@ -599,14 +1020,6 @@ open() {
# Everything else goes through 'xdg-open'/'open'.
case "$mime_type" in
text/*|*x-empty*|*json*)
- # If 'fff' was opened as a file picker, save the opened
- # file in a file called 'opened_file'.
- ((file_picker == 1)) && {
- printf '%s\n' "$1" > \
- "${XDG_CACHE_HOME:=${HOME}/.cache}/fff/opened_file"
- exit
- }
-
clear_screen
reset_terminal
"${VISUAL:-${EDITOR:-vi}}" "$1"
@@ -625,123 +1038,378 @@ open() {
fi
}
+open_with() {
+ # If 'fff' was opened as a file picker, save the opened
+ # file in a file called 'opened_file'.
+ ((file_picker == 1)) && {
+ printf '%s\n' $2 > \
+ "${XDG_CACHE_HOME:=${HOME}/.cache}/fff/opened_file"
+ exit
+ }
+
+ clear_screen
+ reset_terminal
+ # Warning: when opening file(s) with spaces in it it will
+ # recognize it as separate files!!!
+ $1 $2
+ setup_terminal
+ redraw
+}
+
cmd_line() {
# Write to the command_line (under status_line).
- cmd_reply=
+ cmd_reply=${list[scroll]##*/}
+ local status="${1} "
+ # Position of the cursor relative to the beginning of the reply.
+ local curpos=${#cmd_reply}
+ # Lenght of status.
+ local status_len=${#status}
+ # Current position of the cursor.
+ local pos=0
# '\e7': Save cursor position.
# '\e[?25h': Unhide the cursor.
# '\e[%sH': Move cursor to bottom (cmd_line).
printf '\e7\e[%sH\e[?25h' "$LINES"
+
+ # If nothing is passed then handle it as error,
+ # and run search/rename.
+ [ -n "$2" ] && {
+ # If search or go to or nothing was passed.
+ [[ "$2" != "rename" ]] && {
+ cmd_reply=
+ curpos=0
+ }
- # '\r\e[K': Redraw the read prompt on every keypress.
- # This is mimicking what happens normally.
- while IFS= read -rsn 1 -p $'\r\e[K'"${1}${cmd_reply}" read_reply; do
- case $read_reply in
- # Backspace.
- $'\177'|$'\b')
- cmd_reply=${cmd_reply%?}
+ while true; do
+ pos=$((status_len+curpos))
+ # '\r\e[K': Redraw everything.
+ # '\e[%s;'"$pos"'H': Go to $pos position at the bottom.
+ # '\e[5 q': Change cursor to vertical bar.
+ printf '\r\e[K'"${1}${cmd_reply}"
+ printf '\e[%s;'"$pos"'H' "$LINES"
+ printf '\e[5 q'
+ IFS= read -rsn 1 read_reply
+
+ case $read_reply in
+ # Backspace.
+ $'\177'|$'\b')
+ # Do not delete status.
+ [ $pos -le $status_len ] && curpos=0 || {
+ # Delete char at curpos.
+ cmd_reply="${cmd_reply:0:curpos-1}${cmd_reply:curpos}"
+ curpos=$((curpos-1))
+ }
+
+ # Clear tab-completion.
+ unset comp c
+ ;;
+
+ # Tab.
+ $'\t')
+ ((helping)) || ((history)) && return
+
+ comp_glob="$cmd_reply*"
+
+ # Pass the argument dirs to limit completion to directories.
+ [[ $2 == dirs ]] &&
+ comp_glob="$cmd_reply*/"
+
+ # Generate a completion list once.
+ [[ -z ${comp[0]} ]] &&
+ IFS=$'\n' read -d "" -ra comp < <(compgen -G "$comp_glob")
+
+ # On each tab press, cycle through the completion list.
+ [[ -n ${comp[c]} ]] && {
+ cmd_reply=${comp[c]}
+ ((c=c >= ${#comp[@]}-1 ? 0 : ++c))
+ }
+
+ curpos=${#cmd_reply}
+ ;;
+
+ # Ctrl + a go to the beginning of the reply.
+ $'\001')
+ curpos=0
+ ;;
+
+ # Escape / Custom 'no' value (used as a replacement for '-n 1').
+ $'\e'|${3:-null})
+ # Read escape sequence and check for arrow keys
+ read "${read_flags[@]}" -rsn 2 escape_chars
+ case $escape_chars in
+ # Left arrow.
+ '[D'|'OD')
+ curpos=$((curpos-1))
+ # Reset curpos if going out of bounds.
+ [ $pos -le $status_len ] && curpos=0
+ ;;
+ # Right arrow.
+ '[C'|'OC')
+ # Go to right only if previously went to the left.
+ [ $curpos -lt ${#cmd_reply} ] && curpos=$((curpos+1))
+ ;;
+
+ *)
+ cmd_reply=
+ break
+ ;;
+ esac
+ ;;
+
+ # Enter/Return.
+ "")
+ # If there's only one search result and its a directory,
+ # enter it on one enter keypress.
+ ! ((helping)) && ! ((history)) && \
+ [[ $2 == search && -d ${list[0]} ]] && \
+ ((list_total == 0)) && {
+ # '\e[?25l': Hide the cursor.
+ printf '\e[?25l'
+
+ open "${list[0]}"
+ search_end_early=1
+
+ # Unset tab completion variables since we're done.
+ unset comp c
+ return
+ }
+
+ break
+ ;;
+
+ # Custom 'yes' value (used as a replacement for '-n 1').
+ "${2:-null}")
+ cmd_reply=$read_reply
+ break
+ ;;
+
+ # Replace '~' with '$HOME'.
+ "~")
+ cmd_reply+=$HOME
+ curpos=${#cmd_reply}
+ ;;
+
+ # Anything else, add it to read reply.
+ *)
+ curpos=$((curpos+1))
+ # Add letter where is curpos.
+ cmd_reply="${cmd_reply:0:curpos-1}${read_reply}${cmd_reply:curpos-1}"
+
+ # Clear tab-completion.
+ unset comp c
+ ;;
+ esac
+
+ # Search on keypress if search passed as an argument.
+ [[ $2 == search ]] && {
+ # '\e[?25l': Hide the cursor.
+ printf '\e[?25l'
+
+ # Use a greedy glob to search.
+ if ((helping)) || ((history)); then
+ local item
+ list=()
+ for item in "${cur_list[@]}"; do
+ [[ "$item" == *"$cmd_reply"* ]] && list+=("$item")
+ done
+ else
+ list=("$PWD"/*"$cmd_reply"*)
+ fi
+ ((list_total=${#list[@]}-1))
- # Clear tab-completion.
- unset comp c
- ;;
+ # Draw the search results on screen.
+ scroll=0
+ redraw
- # Tab.
- $'\t')
- comp_glob="$cmd_reply*"
+ # '\e[%sH': Move cursor back to cmd_line.
+ # '\e[?25h': Unhide the cursor.
+ printf '\e[%sH\e[?25h' "$LINES"
+ }
+ done
+ } || {
+ while true; do
+ pos=$((status_len+curpos))
+ # '\r\e[K'"$1": Redraw everything.
+ # '\e[%s\e[?25l' "$LINES": Go to the last position at the bottom
+ # and hide the cursor.
+ # Cannot write ..%s.." "$1", because of no color in warning.
+ printf '\r\e[K'"$1"
+ printf '\e[%s\e[?25l' "$LINES"
+ IFS= read -rsn 1 read_reply
+ case $read_reply in
+ *)
+ # Clear tab-completion.
+ unset comp c
+ break
+ ;;
+ esac
+ done
+ }
- # Pass the argument dirs to limit completion to directories.
- [[ $2 == dirs ]] &&
- comp_glob="$cmd_reply*/"
+ # Unset tab completion variables since we're done.
+ unset comp c
- # Generate a completion list once.
- [[ -z ${comp[0]} ]] &&
- IFS=$'\n' read -d "" -ra comp < <(compgen -G "$comp_glob")
+ # '\e[2K': Clear the entire cmd_line on finish.
+ # '\e[?25l': Hide the cursor.
+ # '\e8': Restore cursor position.
+ printf '\e[2K\e[?25l\e8'
+ status_line
+}
- # On each tab press, cycle through the completion list.
- [[ -n ${comp[c]} ]] && {
- cmd_reply=${comp[c]}
- ((c=c >= ${#comp[@]}-1 ? 0 : ++c))
- }
- ;;
+key() {
+ # Handle special key presses.
+
+ # Handle ctrl + u scroll up.
+ [[ $1 == $'\x15' ]] && {
+ # Get the length to the first element and check if
+ # there is space for this jump.
+ #
+ # space_for_jump = length to the first element in the
+ # whole list
+ # scroll = current cursor position number in the list
+ # jump_len = if there is space = n jump, if not
+ # then is length to the first element
+ space_for_jump=${scroll}
+
+ if [[ ${space_for_jump} -gt ${FFF_SCROLL_UP:=14} ]]; then
+ jump_len=${FFF_SCROLL_UP:=14}
+ else
+ jump_len=${space_for_jump}
+ fi
- # Escape / Custom 'no' value (used as a replacement for '-n 1').
- $'\e'|${3:-null})
- read "${read_flags[@]}" -rsn 2
- cmd_reply=
- break
- ;;
+ # Check if the new position after jump is on the screen,
+ # then simply jump to it, otherwise go to the first
+ # visible/printed line and then go line-by-line printing it.
+ #
+ # new_pos_after_jump = scroll + jump number
+ # y = position of the scroll in the visible area
+ # visible_jump = how many already visible lines to jump
+ # invisible_jump = how many invisible lines to jump after it
+ ((new_pos_after_jump=$y-$jump_len))
+
+ if [[ $new_pos_after_jump -ge 0 ]]; then
+ ((visible_jump=$jump_len))
+ invisible_jump=0
+ else
+ ((visible_jump=$y))
+ ((invisible_jump=$jump_len-$y))
+ fi
- # Enter/Return.
- "")
- # If there's only one search result and its a directory,
- # enter it on one enter keypress.
- [[ $2 == search && -d ${list[0]} ]] && ((list_total == 0)) && {
- # '\e[?25l': Hide the cursor.
- printf '\e[?25l'
+ ((scroll=scroll-$visible_jump))
- open "${list[0]}"
- search_end_early=1
+ print_line "$((scroll+visible_jump))"
- # Unset tab completion variables since we're done.
- unset comp c
- return
- }
+ # Scroll n places up = print n * line/form feed.
+ for ((i=0; i<$visible_jump; i++)); do
+ if ((y < 2)); then
+ printf '\e[L'
+ else
+ printf '\e[A'
+ ((y--))
+ fi
+ done
- break
- ;;
+ # Scroll up and print new elements.
+ for ((i=0; i<$invisible_jump; i++)); do
+ while ((scroll > 0)); do
+ ((scroll--))
- # Custom 'yes' value (used as a replacement for '-n 1').
- ${2:-null})
- cmd_reply=$read_reply
- break
- ;;
+ print_line "$((scroll+1))"
- # Replace '~' with '$HOME'.
- "~")
- cmd_reply+=$HOME
- ;;
+ if ((y < 2)); then
+ printf '\e[L'
+ else
+ printf '\e[A'
+ ((y--))
+ fi
- # Anything else, add it to read reply.
- *)
- cmd_reply+=$read_reply
+ print_line "$scroll"
- # Clear tab-completion.
- unset comp c
- ;;
- esac
+ [[ "${list[scroll]}" ]] && break
+ done
+ done
- # Search on keypress if search passed as an argument.
- [[ $2 == search ]] && {
- # '\e[?25l': Hide the cursor.
- printf '\e[?25l'
+ print_line "$scroll"
+ status_line
+
+ }
- # Use a greedy glob to search.
- list=("$PWD"/*"$cmd_reply"*)
- ((list_total=${#list[@]}-1))
+ # Handle ctrl + d for scroll down.
+ [[ $1 == $'\x04' ]] && {
+ ((scroll != list_total)) && {
+ # Get the length to the last element and check if
+ # there is space for this jump.
+ #
+ # space_for_jump = length to the end of the whole list
+ # ${#list[@]} = number of all elements in the list
+ # scroll = current cursor position number
+ # jump_len = if there is space = n jump, if not
+ # then is length to the last element
+ ((space_for_jump=${#list[@]} - $scroll))
+
+ if [[ ${space_for_jump} -gt ${FFF_SCROLL_DOWN:=14} ]]; then
+ jump_len=${FFF_SCROLL_DOWN:=14}
+ else
+ ((jump_len=${space_for_jump}-1))
+ fi
- # Draw the search results on screen.
- scroll=0
- redraw
+ # Check if the new position after jump is on the screen,
+ # then simply jump to it, otherwise go to the last
+ # visible/printed line and then go line-by-line printing it.
+ #
+ # new_pos_after_jump = scroll + jump number
+ # max_items = length of the items that can fit in the screen
+ # y = position of the scroll in the visible area
+ # visible_jump = how many already visible lines to jump
+ # invisible_jump = how many invisible lines to jump after it
+ ((new_pos_after_jump=$y+$jump_len))
+
+ if [[ $new_pos_after_jump -le $max_items ]]; then
+ visible_jump=$jump_len
+ invisible_jump=0
+ else
+ ((visible_jump=${max_items}-$y))
+ ((invisible_jump=${new_pos_after_jump}-${max_items}))
+ fi
- # '\e[%sH': Move cursor back to cmd-line.
- # '\e[?25h': Unhide the cursor.
- printf '\e[%sH\e[?25h' "$LINES"
- }
- done
+ ((scroll=scroll+$visible_jump))
+ ((y < max_items)) && ((y=y+$visible_jump))
- # Unset tab completion variables since we're done.
- unset comp c
+ print_line "$((scroll-$visible_jump))"
- # '\e[2K': Clear the entire cmd_line on finish.
- # '\e[?25l': Hide the cursor.
- # '\e8': Restore cursor position.
- printf '\e[2K\e[?25l\e8'
-}
+ # Scroll n places down = print n * '\n'.
+ for ((i=0; i<$visible_jump; i++)); do
+ printf '\n'
+ done
+
+ # Scroll down and print new elements.
+ for ((i=0; i<$invisible_jump; i++)); do
+ while ((scroll < list_total)); do
+ ((scroll++))
+ ((y < max_items)) && ((y++))
+
+ print_line "$((scroll-1))"
+ printf '\n'
+ print_line "$scroll"
+
+ [[ "${list[scroll]}" ]] && break
+ done
+ done
+
+ print_line "$scroll"
+ status_line
+ }
+ }
-key() {
- # Handle special key presses.
[[ $1 == $'\e' ]] && {
+ # Exit marking with any special key.
+ [[ ${marked_files[*]} ]] && {
+ marked_files=()
+ file_program=()
+ redraw
+ }
+
read "${read_flags[@]}" -rsn 2
# Handle a normal escape key press.
@@ -756,10 +1424,17 @@ key() {
# 'C' is what bash sees when the right arrow is pressed
# ('\e[C' or '\eOC').
# '' is what bash sees when the enter/return key is pressed.
- ${FFF_KEY_CHILD1:=l}|\
- ${FFF_KEY_CHILD2:=$'\e[C'}|\
- ${FFF_KEY_CHILD3:=""}|\
- ${FFF_KEY_CHILD4:=$'\eOC'})
+ "${FFF_KEY_CHILD1:=l}" |\
+ "${FFF_KEY_CHILD2:=$'\e[C'}"|\
+ "${FFF_KEY_CHILD3:=""}" |\
+ "${FFF_KEY_CHILD4:=$'\eOC'}")
+ # If history then return to the previous state.
+ ((history)) && {
+ details=$saved_details
+ FFF_FILE_ICON="$icon_value"
+ list[scroll]=${list[scroll]#* }
+ }
+
open "${list[scroll]}"
;;
@@ -768,17 +1443,23 @@ key() {
# ('\e[D' or '\eOD').
# '\177' and '\b' are what bash sometimes sees when the backspace
# key is pressed.
- ${FFF_KEY_PARENT1:=h}|\
- ${FFF_KEY_PARENT2:=$'\e[D'}|\
- ${FFF_KEY_PARENT3:=$'\177'}|\
- ${FFF_KEY_PARENT4:=$'\b'}|\
- ${FFF_KEY_PARENT5:=$'\eOD'})
+ "${FFF_KEY_PARENT1:=h}" |\
+ "${FFF_KEY_PARENT2:=$'\e[D'}"|\
+ "${FFF_KEY_PARENT3:=$'\177'}"|\
+ "${FFF_KEY_PARENT4:=$'\b'}" |\
+ "${FFF_KEY_PARENT5:=$'\eOD'}")
+ # If history/help then return to the previous state.
+ if ((history)) || ((helping)); then
+ details=$saved_details
+ FFF_FILE_ICON="$icon_value"
+ open "$PWD"
+
# If a search was done, clear the results and open the current dir.
- if ((search == 1 && search_end_early != 1)); then
+ elif ((search == 1 && search_end_early != 1)); then
open "$PWD"
# If '$PWD' is '/', do nothing.
- elif [[ $PWD && $PWD != / ]]; then
+ elif [[ $PWD ]]; then
find_previous=1
open "${PWD%/*}"
fi
@@ -787,10 +1468,10 @@ key() {
# Scroll down.
# 'B' is what bash sees when the down arrow is pressed
# ('\e[B' or '\eOB').
- ${FFF_KEY_SCROLL_DOWN1:=j}|\
- ${FFF_KEY_SCROLL_DOWN2:=$'\e[B'}|\
- ${FFF_KEY_SCROLL_DOWN3:=$'\eOB'})
- ((scroll < list_total)) && {
+ "${FFF_KEY_SCROLL_DOWN1:=j}" |\
+ "${FFF_KEY_SCROLL_DOWN2:=$'\e[B'}"|\
+ "${FFF_KEY_SCROLL_DOWN3:=$'\eOB'}")
+ while ((scroll < list_total)); do
((scroll++))
((y < max_items)) && ((y++))
@@ -798,18 +1479,20 @@ key() {
printf '\n'
print_line "$scroll"
status_line
- }
+
+ [[ "${list[scroll]}" ]] && break
+ done
;;
# Scroll up.
# 'A' is what bash sees when the up arrow is pressed
# ('\e[A' or '\eOA').
- ${FFF_KEY_SCROLL_UP1:=k}|\
- ${FFF_KEY_SCROLL_UP2:=$'\e[A'}|\
- ${FFF_KEY_SCROLL_UP3:=$'\eOA'})
+ "${FFF_KEY_SCROLL_UP1:=k}" |\
+ "${FFF_KEY_SCROLL_UP2:=$'\e[A'}"|\
+ "${FFF_KEY_SCROLL_UP3:=$'\eOA'}")
# '\e[1L': Insert a line above the cursor.
# '\e[A': Move cursor up a line.
- ((scroll > 0)) && {
+ while ((scroll > 0)); do
((scroll--))
print_line "$((scroll+1))"
@@ -823,11 +1506,13 @@ key() {
print_line "$scroll"
status_line
- }
+
+ [[ "${list[scroll]}" ]] && break
+ done
;;
# Go to top.
- ${FFF_KEY_TO_TOP:=g})
+ "${FFF_KEY_TO_TOP:=g}")
((scroll != 0)) && {
scroll=0
redraw
@@ -835,7 +1520,7 @@ key() {
;;
# Go to bottom.
- ${FFF_KEY_TO_BOTTOM:=G})
+ "${FFF_KEY_TO_BOTTOM:=G}")
((scroll != list_total)) && {
((scroll=list_total))
redraw
@@ -843,7 +1528,9 @@ key() {
;;
# Show hidden files.
- ${FFF_KEY_HIDDEN:=.})
+ "${FFF_KEY_HIDDEN:=.}")
+ ((helping)) || ((history)) && return
+
# 'a=a>0?0:++a': Toggle between both values of 'shopt_flags'.
# This also works for '3' or more values with
# some modification.
@@ -853,22 +1540,22 @@ key() {
;;
# Search.
- ${FFF_KEY_SEARCH:=/})
+ "${FFF_KEY_SEARCH:=/}")
cmd_line "/" "search"
# If the search came up empty, redraw the current dir.
- if [[ -z ${list[*]} ]]; then
+ [[ -z ${list[*]} ]] && {
list=("${cur_list[@]}")
((list_total=${#list[@]}-1))
redraw
search=
- else
- search=1
- fi
+ } || search=1
+
+ status_line
;;
# Spawn a shell.
- ${FFF_KEY_SHELL:=!})
+ "${FFF_KEY_SHELL:=!}")
reset_terminal
# Make fff aware of how many times it is nested.
@@ -881,28 +1568,61 @@ key() {
;;
# Mark files for operation.
- ${FFF_KEY_YANK:=y}|\
- ${FFF_KEY_MOVE:=m}|\
- ${FFF_KEY_TRASH:=d}|\
- ${FFF_KEY_LINK:=s}|\
- ${FFF_KEY_BULK_RENAME:=b})
+ "${FFF_KEY_COPY:=c}" |\
+ "${FFF_KEY_MOVE:=m}" |\
+ "${FFF_KEY_TRASH:=d}" |\
+ "${FFF_KEY_LINK:=s}" |\
+ "${FFF_KEY_BULK_RENAME:=b}")
+ ((helping)) || ((history)) && return
+
+ [[ -z "${marked_files[*]}" ]] && return
+
+ previous_program="${file_program[0]}"
+
+ # Find the program to use.
+ case "$1" in
+ "${FFF_KEY_COPY:=c}") file_program=(cp -iR) ;;
+ "${FFF_KEY_MOVE:=m}") file_program=(mv -i) ;;
+ "${FFF_KEY_LINK:=s}") file_program=(ln -s) ;;
+
+ # These are 'fff' functions.
+ "${FFF_KEY_TRASH:=d}") file_program=(trash) ;;
+ "${FFF_KEY_BULK_RENAME:=b}") file_program=(bulk_rename) ;;
+ esac
+
+ # Toggle the file program if the same key was pressed.
+ [[ $previous_program == "${file_program[0]}" ]] &&
+ file_program=()
+
+ status_line
+ ;;
+
+ # Mark files for operation.
+ "${FFF_KEY_MARK:=" "}")
+ ((helping)) || ((history)) && return
+
mark "$scroll" "$1"
;;
# Mark all files for operation.
- ${FFF_KEY_YANK_ALL:=Y}|\
- ${FFF_KEY_MOVE_ALL:=M}|\
- ${FFF_KEY_TRASH_ALL:=D}|\
- ${FFF_KEY_LINK_ALL:=S}|\
- ${FFF_KEY_BULK_RENAME_ALL:=B})
+ "${FFF_KEY_MARK_ALL:=a}")
+ ((helping)) || ((history)) && return
+
mark all "$1"
;;
# Do the file operation.
- ${FFF_KEY_PASTE:=p})
+ "${FFF_KEY_EXECUTE:=p}")
+ ((helping)) || ((history)) && return
+
[[ ${marked_files[*]} ]] && {
[[ ! -w $PWD ]] && {
- cmd_line "warn: no write access to dir."
+ cmd_line "\e[0;31mWarning!\e[0m No write access to dir"
+ return
+ }
+
+ [[ -z ${file_program[0]} ]] && {
+ cmd_line "\e[0;31mWarning!\e[0m No operation selected [y,m,d,b,s]"
return
}
@@ -916,86 +1636,104 @@ key() {
stty -echo
marked_files=()
+ file_program=()
setup_terminal
redraw full
- }
- ;;
-
- # Clear all marked files.
- ${FFF_KEY_CLEAR:=c})
- [[ ${marked_files[*]} ]] && {
- marked_files=()
- redraw
+ } || {
+ [[ -z ${file_program[0]} ]] && {
+ cmd_line "\e[0;31mWarning!\e[0m 0 marked files"
+ return
+ }
}
;;
# Rename list item.
- ${FFF_KEY_RENAME:=r})
- [[ ! -e ${list[scroll]} ]] &&
- return
+ "${FFF_KEY_RENAME:=r}")
+ ((helping)) || ((history)) && return
- cmd_line "rename ${list[scroll]##*/}: "
+ [[ ! -e ${list[scroll]} ]] && return
+
+ cmd_line "Rename: " "rename"
[[ $cmd_reply ]] &&
if [[ -e $cmd_reply ]]; then
- cmd_line "warn: '$cmd_reply' already exists."
+ cmd_line "\e[0;31mWarning!\e[0m '$cmd_reply' already exists"
elif [[ -w ${list[scroll]} ]]; then
mv "${list[scroll]}" "${PWD}/${cmd_reply}"
redraw full
else
- cmd_line "warn: no write access to file."
+ cmd_line "\e[0;31mWarning!\e[0m No write access to file"
fi
;;
# Create a directory.
- ${FFF_KEY_MKDIR:=n})
- cmd_line "mkdir: " "dirs"
+ "${FFF_KEY_MKDIR:=n}")
+ ((helping)) || ((history)) && return
+
+ cmd_line "Create directory: " "dirs"
[[ $cmd_reply ]] &&
if [[ -e $cmd_reply ]]; then
- cmd_line "warn: '$cmd_reply' already exists."
+ cmd_line "\e[0;31mWarning!\e[0m '$cmd_reply' already exists"
elif [[ -w $PWD ]]; then
mkdir -p "${PWD}/${cmd_reply}"
redraw full
else
- cmd_line "warn: no write access to dir."
+ cmd_line "\e[0;31mWarning!\e[0m No write access to dir"
fi
;;
# Create a file.
- ${FFF_KEY_MKFILE:=f})
- cmd_line "mkfile: "
+ "${FFF_KEY_MKFILE:=f}")
+ ((helping)) || ((history)) && return
- [[ $cmd_reply ]] &&
+ cmd_line "Create file: " "file"
+
+ [[ $cmd_reply ]] && {
if [[ -e $cmd_reply ]]; then
- cmd_line "warn: '$cmd_reply' already exists."
+ cmd_line "\e[0;31mWarning!\e[0m '$cmd_reply' already exists"
elif [[ -w $PWD ]]; then
: > "$cmd_reply"
redraw full
else
- cmd_line "warn: no write access to dir."
+ cmd_line "\e[0;31mWarning!\e[0m No write access to dir"
fi
+ }
;;
# Show file attributes.
- ${FFF_KEY_ATTRIBUTES:=x})
+ "${FFF_KEY_ATTRIBUTES:=x}")
+ ((helping)) || ((history)) && return
+
[[ -e "${list[scroll]}" ]] && {
clear_screen
status_line "${list[scroll]}"
"${FFF_STAT_CMD:-stat}" "${list[scroll]}"
+
+ # Show human-readable size.
+ printf " Size (human-readable): "
+ size=$("${FFF_STAT_CMD:-stat}" -c "%s" "${list[scroll]}" | numfmt --to=iec)
+ printf "$size"
+ [[ $size =~ [0-9]$ ]] && printf "B\n" || printf "\n" # If size is in bytes.
+
+ # Display git branch.
+ [ -n "$git_branch" ] && printf " Git branch: $git_branch"
+
read -ern 1
redraw
}
;;
# Toggle executable flag.
- ${FFF_KEY_EXECUTABLE:=X})
+ "${FFF_KEY_EXECUTABLE:=X}")
+ ((helping)) || ((history)) && return
+
[[ -f ${list[scroll]} && -w ${list[scroll]} ]] && {
if [[ -x ${list[scroll]} ]]; then
chmod -x "${list[scroll]}"
@@ -1007,13 +1745,10 @@ key() {
}
;;
- # Show image in terminal.
- ${FFF_KEY_IMAGE:=i})
- draw_img
- ;;
-
# Go to dir.
- ${FFF_KEY_GO_DIR:=:})
+ "${FFF_KEY_GO_DIR:=:}")
+ ((helping)) || ((history)) && return
+
cmd_line "go to dir: " "dirs"
# Let 'cd' know about the current directory.
@@ -1024,44 +1759,189 @@ key() {
open "$PWD"
;;
+ # Open file with.
+ "${FFF_KEY_OPEN_WITH:=o}")
+ ((helping)) || ((history)) && return
+
+ cmd_line "open with: " "cmd"
+ [[ $cmd_reply ]] && {
+ if ! command -v $cmd_reply > /dev/null; then
+ redraw full
+ cmd_line "\e[0;31mWarning!\e[0m Command ${cmd_reply} not found"
+ return
+ fi
+
+ if [[ ${#marked_files[@]} -ne 0 ]]; then
+ open_with "${cmd_reply}" "${marked_files[*]}"
+
+ elif [[ -e "${list[scroll]}" ]]; then
+ open_with "${cmd_reply}" "${list[scroll]}"
+ fi
+ }
+ ;;
+
+ # Open file with to run background.
+ "${FFF_KEY_OPEN_WITH_DETACHED:=O}")
+ ((helping)) || ((history)) && return
+
+ cmd_line "open with (detached): " "cmd"
+ [[ $cmd_reply ]] && {
+ if ! type $cmd_reply > /dev/null; then
+ redraw full
+ cmd_line "\e[0;31mWarning!\e[0m Command ${cmd_reply} not found"
+ return
+ fi
+
+ if [[ ${#marked_files[@]} -ne 0 ]]; then
+ nohup "${cmd_reply}" "${marked_files[*]}" &>/dev/null &
+
+ elif [[ -e "${list[scroll]}" ]]; then
+ nohup "${cmd_reply}" "${list[scroll]}" &>/dev/null &
+ fi
+
+ disown
+ }
+ ;;
+
# Go to '$HOME'.
${FFF_KEY_GO_HOME:='~'})
open ~
;;
# Go to trash.
- ${FFF_KEY_GO_TRASH:=t})
+ "${FFF_KEY_GO_TRASH:=t}")
get_os
open "$FFF_TRASH"
;;
+ # Sort by name (0) time (1).
+ "${FFF_KEY_SORT:=u}")
+ ((helping)) || ((history)) && return
+
+ [ "$sort" == 0 ] && sort=1 || sort=0
+
+ open "$PWD"
+ ;;
+
+ # Show file details (on/off).
+ "${FFF_KEY_DETAILS:=i}")
+ ((helping)) || ((history)) && return
+
+ [ "$details" == 0 ] && details=1 || details=0
+
+ open "$PWD"
+ ;;
+
# Go to previous dir.
- ${FFF_KEY_PREVIOUS:=-})
+ "${FFF_KEY_PREVIOUS:=-}")
open "$OLDPWD"
;;
# Refresh current dir.
- ${FFF_KEY_REFRESH:=e})
+ "${FFF_KEY_REFRESH:=z}")
+ ((helping)) || ((history)) && {
+ list=("${cur_list[@]}")
+ redraw full
+ return
+ }
+
open "$PWD"
;;
+ # Yank filename to clipboard.
+ "${FFF_KEY_CLIPBOARD:-y}")
+ if ! command -v ${FFF_CLIPBOARD:=xclip -selection c} > /dev/null; then
+ redraw full
+ cmd_line "\e[0;31mWarning!\e[0m Cannot run '${FFF_CLIPBOARD:=xclip -selection c}'"
+ return
+ fi
+
+ [[ -e "${list[scroll]}" ]] && {
+ echo "${list[scroll]}" | ${FFF_CLIPBOARD:=xclip -selection c} 2>/dev/null
+
+ cmd_line "${list[scroll]} copied to clipboard"
+ }
+ ;;
+
# Directory favourites.
[1-9])
favourite="FFF_FAV${1}"
favourite="${!favourite}"
- [[ $favourite ]] &&
- open "$favourite"
+ [[ $favourite ]] && open "$favourite"
+ ;;
+
+ # Display help.
+ "${FFF_KEY_HELP:=?}")
+ ((history)) && return
+
+ ((helping)) && {
+ # Return to the previous state.
+ details=$saved_details
+ FFF_FILE_ICON="$icon_value"
+ redraw full
+ return
+ }
+
+ saved_details=$details
+ details=0
+ redraw help
+ ;;
+
+ # Display history.
+ "${FFF_KEY_HISTORY:=e}")
+ ((helping)) && return
+
+ ((history)) && {
+ # Return to the previous state.
+ details=$saved_details
+ FFF_FILE_ICON="$icon_value"
+ redraw full
+ return
+ }
+
+ saved_details=$details
+ details=0
+ redraw hist
+ ;;
+
+ # Display image using img2sixel.
+ "${FFF_KEY_SIXEL:=I}")
+ ((helping)) || ((history)) && return
+
+ if ! command -v img2sixel > /dev/null; then
+ redraw full
+ cmd_line "\e[0;31mWarning!\e[0m Command img2sixel not found"
+ return
+ fi
+
+ # https://stackoverflow.com/questions/25941394/how-does-bash-deal-with-nested-quotes
+ [[ -e "${list[scroll]}" ]] && {
+ clear_screen
+ "${FFF_SIXEL_CMD:-img2sixel}" "${list[scroll]}" && read -rsn1
+ setup_terminal
+ redraw
+ }
;;
# Quit and store current directory in a file for CD on exit.
# Don't allow user to redefine 'q' so a bad keybinding doesn't
# remove the option to quit.
- q)
+ "q")
+ ((helping)) || ((history)) && {
+ details=$saved_details
+ FFF_FILE_ICON="$icon_value" # Set FFF_FILE_ICON to previous state.
+ redraw full
+ return
+ }
+
+ # Delete everything in history after FFF_HISTORY_LENGTH line.
+ lines_num=${FFF_HISTORY_LENGTH:-100}
+ sed -i "${lines_num}q" "${XDG_CACHE_HOME:=${HOME}/.cache}/fff/history"
+
: "${FFF_CD_FILE:=${XDG_CACHE_HOME:=${HOME}/.cache}/fff/.fff_d}"
- [[ -w $FFF_CD_FILE ]] &&
- rm "$FFF_CD_FILE"
+ [[ -w $FFF_CD_FILE ]] && rm "$FFF_CD_FILE"
[[ ${FFF_CD_ON_EXIT:=1} == 1 ]] &&
printf '%s\n' "$PWD" > "$FFF_CD_FILE"
@@ -1128,7 +2008,6 @@ main() {
get_os
get_term_size
- get_w3m_path
setup_options
setup_terminal
redraw full
diff --git a/fff.1 b/fff.1
index 7880c30..c035faa 100644
--- a/fff.1
+++ b/fff.1
@@ -16,8 +16,10 @@ k: scroll up
h: go to parent dir
l: go to child dir
-enter: go to child dir
+enter: go to child dir/open file
backspace: go to parent dir
+o: open file with
+O: open file with GUI program detached from file manager
\-: Go to previous dir\.
@@ -30,11 +32,14 @@ G: go to bottom
/: search
t: go to trash
~: go to home
-e: refresh current dir
+z: refresh current dir
!: open shell in current dir
+i: display file details
+I: display an image using sixel
+u: sort files
x: view file/dir attributes
-i: display image with w3m-img
+e: show history
down: scroll down
up: scroll up
@@ -45,26 +50,24 @@ f: new file
n: new dir
r: rename
X: toggle executable
+y: copy filename to clipboard
-y: mark copy
-m: mark move
-d: mark trash (~/.local/share/fff/trash/)
-s: mark symbolic link
-b: mark bulk rename
+space: mark file
+a: mark all files in directory
+c: copy
+m: move
+d: trash (move to FFF_TRASH)
+s: symbolic link
+b: bulk rename
-Y: mark all for copy
-M: mark all for move
-D: mark all for trash (~/.local/share/fff/trash/)
-S: mark all for symbolic link
-B: mark all for bulk rename
-
-p: paste/move/delete/bulk_rename
-c: clear file selections
+p: execute paste/move/delete/bulk_rename
[1-9]: favourites/bookmarks (see customization)
q: exit with 'cd' (if enabled).
Ctrl+C: exit without 'cd'.
+
+?: show help
.
.fi
.
@@ -81,6 +84,24 @@ export FFF_LS_COLORS=1
# (On by default)
export FFF_HIDDEN=0
+# Show/Hide file icons on open
+# (Off by default)
+export FFF_FILE_ICON=1
+
+# Show/Hide git status signs (+) on open
+# (Off by default)
+export FFF_GIT_CHANGES=1
+
+# Default method to sort files on open
+# 0 - alphabetically
+# 1 - modification time
+# (0 by default)
+export FFF_SORT_METHOD=1
+
+# Show/Hide file details on open
+# (Off by default)
+export FFF_FILE_DETAILS=1
+
# Directory color [0\-9]
export FFF_COL1=2
@@ -96,6 +117,21 @@ export FFF_COL4=1
# Status foreground color [0\-9]
export FFF_COL5=0
+# Selection color
+# (inverted foreground by default)
+# ('48;2;R;G;B' values separated by ';', don't edit the '48;2;' part!).
+# In terminals that support truecolor, this will set the selection color
+# to grey, but on others selection will be only white bold text (if this
+# is set).
+export FFF_COL6="48;2;80;80;80"
+
+# Colored filenames
+# (0 by default)
+export FFF_COLORED_FILENAMES=1
+
+# Text Editor
+export EDITOR="nvim"
+
# Text Editor
export EDITOR="vim"
@@ -114,6 +150,11 @@ export FFF_CD_ON_EXIT=1
# If not using XDG, '${HOME}/.cache/fff/fff.d' is used.
export FFF_CD_FILE=~/.fff_d
+# Config File
+# Default: '${XDG_CONFIG_HOME/fff}'
+# If not using XDG, '${HOME}/.config/fff' is used.
+export FFF_CONFIG=~/.config/fff
+
# Trash Directory
# Default: '${XDG_DATA_HOME}/fff/trash'
# If not using XDG, '${XDG_DATA_HOME}/fff/trash' is used.
@@ -137,9 +178,13 @@ export FFF_FAV7=
export FFF_FAV8=
export FFF_FAV9=
-# w3m-img offsets.
-export FFF_W3M_XOFFSET=0
-export FFF_W3M_YOFFSET=0
+# History file length.
+# (100 lines by default)
+# Every cd-on-exit (q) program deletes every line older than
+# FFF_HISTORY_LENGTH.
+# Example: history has 150 lines, quitting trims history file
+# to 100 most recent.
+export FFF_HISTORY_LENGTH=200
# File format.
# Customize the item string.
@@ -152,6 +197,19 @@ export FFF_FILE_FORMAT="%f"
# Format ('%f' is the current file): "str%fstr"
# Example (Add a ' >' before files): FFF_MARK_FORMAT="> %f"
export FFF_MARK_FORMAT=" %f*"
+
+# Clipboard program and arguments.
+# Default: xclip -selection c
+export FFF_KEY_CLIPBOARD="xclip -selection c"
+
+# Scroll steps.
+# (14 by default).
+export FFF_SCROLL_UP=14
+export FFF_SCROLL_DOWN=14
+
+# Sixel image program.
+# Default: img2sixel
+export FFF_SIXEL_CMD="img2sixel"
.
.fi
.
@@ -159,7 +217,7 @@ export FFF_MARK_FORMAT=" %f*"
.
.nf
For more information see:
- https://github.com/dylanaraps/fff#customizing-the-keybindings
+ https://github.com/piotr-marendowski/fff#customizing-the-keybindings
### Moving around.
@@ -201,21 +259,15 @@ export FFF_KEY_GO_HOME="~"
export FFF_KEY_GO_TRASH="t"
### File operations.
-
-export FFF_KEY_YANK="y"
+export FFF_KEY_MARK=" "
+export FFF_KEY_MARK_ALL="v"
+export FFF_KEY_COPY="c"
export FFF_KEY_MOVE="m"
export FFF_KEY_TRASH="d"
export FFF_KEY_LINK="s"
export FFF_KEY_BULK_RENAME="b"
-export FFF_KEY_YANK_ALL="Y"
-export FFF_KEY_MOVE_ALL="M"
-export FFF_KEY_TRASH_ALL="D"
-export FFF_KEY_LINK_ALL="S"
-export FFF_KEY_BULK_RENAME_ALL="B"
-
-export FFF_KEY_PASTE="p"
-export FFF_KEY_CLEAR="c"
+export FFF_KEY_EXECUTE="p"
export FFF_KEY_RENAME="r"
export FFF_KEY_MKDIR="n"
@@ -223,6 +275,15 @@ export FFF_KEY_MKFILE="f"
### Miscellaneous
+# Display file details.
+export FFF_KEY_DETAILS="i"
+
+# Display an image using sixel.
+export FFF_KEY_SIXEL="I"
+
+# Sort files.
+export FFF_KEY_SORT="u"
+
# Show file attributes.
export FFF_KEY_ATTRIBUTES="x"
@@ -232,6 +293,11 @@ export FFF_KEY_EXECUTABLE="X"
# Toggle hidden files.
export FFF_KEY_HIDDEN="."
+# Show history of directories.
+export FFF_KEY_HISTORY="e"
+
+# Yank filename to clipboard.
+export FFF_KEY_CLIPBOARD="y"
.
.fi