-
Notifications
You must be signed in to change notification settings - Fork 331
Integrations
Below are some shell tools that can be integrated into lf using regular or remote commands. Feel free to add more tools to this list as you like.
A new cd command that helps you navigate faster by learning your habits.
cmd z-jump ${{
lf -remote "send $id cd \"$(/path/to/z.lua -e "$@" | sed 's/\\/\\\\/g;s/"/\\"/g')\""
}}
map Z push :z-jump<space>-I<space>
map zb push :z-jump<space>-b<space>
map zz push :z-jump<space>
zoxide is a smarter cd
command that helps you jump to any directory in just a few keystrokes. Integrating zoxide with lf is simple:
# bash/any POSIX shell
cmd z %{{
result="$(zoxide query --exclude "$PWD" "$@" | sed 's/\\/\\\\/g;s/"/\\"/g')"
lf -remote "send $id cd \"$result\""
}}
cmd zi ${{
result="$(zoxide query -i | sed 's/\\/\\\\/g;s/"/\\"/g')"
lf -remote "send $id cd \"$result\""
}}
cmd on-cd &{{
zoxide add "$PWD"
}}
# Powershell > 7.4
set shellflag "-cwa"
cmd z ${{
[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("UTF-8")
$result = ((zoxide query --exclude $PWD $args[0]) -replace "/", "//")
lf -remote "send $env:id cd '$result'"
}}
cmd zi ${{
[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("UTF-8")
$result=((zoxide query -i) -replace "/", "//")
lf -remote "send $id cd '$result'"
}}
cmd on-cd &{{
zoxide add "$PWD"
}}
autojump can be used to jump to a directory in lf that contains a given string:
cmd aj %lf -remote "send $id cd \"$(autojump "$1" | sed 's/\\/\\\\/g;s/"/\\"/g')\""
map a push :aj<space>
Note that autojump relies on shell prompts to build and update its database, so it will only be updated when you run commands outside of lf or exit from lfcd
function.
fasd can be used to navigate directories:
cmd fasd_dir ${{
res="$(fasd -dl | grep -iv cache | fzf 2>/dev/tty)"
if [ -n "$res" ]; then
if [ -d "$res" ]; then
cmd="cd"
else
cmd="select"
fi
res="$(printf '%s' "$res" | sed 's/\\/\\\\/g;s/"/\\"/g')"
lf -remote "send $id $cmd \"$res\""
fi
}}
map go :fasd_dir
You can bind keys in lf to your usual fzf commands:
map f $$EDITOR $(fzf)
It is also possible to define commands with arguments to use with fzf:
cmd fzf $$EDITOR $(find . -name "$1" | fzf)
map f push :fzf<space>
If you want to jump to a file or directory in lf using fuzzy matching, you can utilize fzf for this purpose:
cmd fzf_jump ${{
res="$(find . -maxdepth 1 | fzf --reverse --header="Jump to location")"
if [ -n "$res" ]; then
if [ -d "$res" ]; then
cmd="cd"
else
cmd="select"
fi
res="$(printf '%s' "$res" | sed 's/\\/\\\\/g;s/"/\\"/g')"
lf -remote "send $id $cmd \"$res\""
fi
}}
map <c-f> :fzf_jump
Combining fzf with ripgrep, you can interactively search in the contents of files under the current directory and select a file from the results:
cmd fzf_search ${{
cmd="rg --column --line-number --no-heading --color=always --smart-case"
fzf --ansi --disabled --layout=reverse --header="Search in files" --delimiter=: \
--bind="start:reload([ -n {q} ] && $cmd -- {q} || true)" \
--bind="change:reload([ -n {q} ] && $cmd -- {q} || true)" \
--bind='enter:become(lf -remote "send $id select \"$(printf "%s" {1} | sed '\''s/\\/\\\\/g;s/"/\\"/g'\'')\"")' \
--preview='cat -- {1}' # Use your favorite previewer here (bat, source-highlight, etc.), for example:
#--preview-window='+{2}-/2' \
#--preview='bat --color=always --highlight-line={2} -- {1}'
# Alternatively you can even use the same previewer you've configured for lf
#--preview='~/.config/lf/cleaner; ~/.config/lf/previewer {1} "$FZF_PREVIEW_COLUMNS" "$FZF_PREVIEW_LINES" "$FZF_PREVIEW_LEFT" "$FZF_PREVIEW_TOP"'
}}
map gs :fzf_search
trash-cli can be used as command line interface for FreeDesktop.org Trash specification. trash-cli already provides separate commands for trash operations (i.e. trash-put
, trash-empty
, trash-list
, trash-restore
, trash-rm
) so you can simply map these commands to a key:
cmd trash %trash-put -- $fx
Note that trash-cli uses the same trashcan used by KDE, GNOME, and XFCE.
On systems that use GIO (Gnome Input/Output), such as Ubuntu, this will use the gio trash
command to move the currently selected items (files and dirs) to the trashcan. If
GIO is not available, it falls back to mv
.
cmd trash ${{
set -f
if gio trash 2>/dev/null; then
gio trash $fx
else
mkdir -p ~/.trash
mv -- $fx ~/.trash
fi
}}
Basic use of mv
to implement a trashcan located in the user's home dir, as in the
fallback option here, has some noteable disadvantages:
-
If the items being moved to trash are not on the same filesystem as the user's home dir, they are moved between filesystems with potentially lengthy copy+delete operations.
-
Items in trash take up storage in the filesystem holding the user's home dir instead of the filesystem they were initially in.
-
Since items with the same name will overwrite each other if they're moved to the same dir, items with common names easily become overwritten and unrecoverable.
-
The OS does not know that files moved to trash are probably less important to the user than the user's other files, so will not suggest them for deleting when running low on disk space and may included them in automated backups, etc.
-
The OS does not provide any functionality that helps finding and restoring accidentally deleted files to their original locations.
gio trash
, however, implements a trashcan without any of the disadvantages listed
above. Instead of moving items across filesystems, it creates trash dirs as required on
the filesystems where the items alread are. Items are stored with metadata containing
original names and locations, preventing overwrites. The OS suggests deleting items from
the trash when running low on space. Functionality for finding files that were moved to
trash and restoring them to their original locations is available.
Assuming gio trash
is in use the next command provides a way to restore selected files
when browsing the Trash
folder, located at ~/.local/share/Trash/files/
:
cmd trash-restore %{{
set -f
ft="$(basename -a -- $fx | sed 's|^|trash:https:///|')"
gio trash --restore $ft
printf 'restored'
printf ' %s' $(basename -a -- $fx)
}}
It is possible to write a handler to open lf
in response to "Open in folder" requests from GUI applications like browsers or text editors. Below is a sample C program:
#include <stdio.h>
#include <stdlib.h>
#include <dbus/dbus.h>
static void show_items(DBusMessage* message) {
// set TERMINAL to configure the terminal in which lf is opened
const char *term = getenv("TERMINAL");
DBusMessageIter iter;
dbus_message_iter_init(message, &iter);
DBusMessageIter array;
dbus_message_iter_recurse(&iter, &array);
while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) {
const char* item;
dbus_message_iter_get_basic(&array, &item);
item += 7; // remove 'file:https://' prefix
char* cmd;
asprintf(&cmd, "%s lf '%s' &", term, item);
system(cmd);
free(cmd);
dbus_message_iter_next(&array);
}
}
static DBusHandlerResult message_handler(DBusConnection* connection, DBusMessage* message, void* user_data) {
if (dbus_message_is_method_call(message, "org.freedesktop.FileManager1", "ShowItems")) {
DBusMessage* reply = dbus_message_new_method_return(message);
if (reply != NULL) {
show_items(message);
dbus_connection_send(connection, reply, NULL);
dbus_message_unref(reply);
} else {
fprintf(stderr, "Error creating reply message\n");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
}
return DBUS_HANDLER_RESULT_HANDLED;
}
int main() {
DBusConnection* connection = dbus_bus_get(DBUS_BUS_SESSION, NULL);
if (connection == NULL) {
fprintf(stderr, "Failed to connect to the D-Bus session bus\n");
return 1;
}
dbus_bus_request_name(connection, "org.freedesktop.FileManager1", DBUS_NAME_FLAG_REPLACE_EXISTING, NULL);
dbus_connection_add_filter(connection, message_handler, NULL, NULL);
while (dbus_connection_read_write_dispatch(connection, -1))
;
return 0;
}
Compile using the following command:
gcc -o file-handler file-handler.c $(pkg-config --cflags --libs dbus-1)
Then set the TERMINAL
environment variable to configure which terminal is used to open lf
. Flags can be added if required, e.g. use a value of alacritty -e
for Alacritty.
eza
can be used to provide the file information shown in the bottom left corner:
cmd on-select &{{
lf -remote "send $id set statfmt \"$(eza -ld --color=always "$f" | sed 's/\\/\\\\/g;s/"/\\"/g')\""
}}
A couple of useful Git commands that can be run directly from LF if you're in a git project.
cmd git_branch ${{
git branch | fzf | xargs git checkout
pwd_shell="$(pwd | sed 's/\\/\\\\/g;s/"/\\"/g')"
lf -remote "send $id updir; cd \"$pwd_shell\""
}}
map gb :git_branch
map gp $clear; git pull --rebase || true; echo "press ENTER"; read ENTER
map gs $clear; git status; echo "press ENTER"; read ENTER
map gl $clear; git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
An example on-cd command to show some git related information:
cmd on-cd &{{
# display git repository status in your prompt
source /usr/share/git/completion/git-prompt.sh
GIT_PS1_SHOWDIRTYSTATE=auto
GIT_PS1_SHOWSTASHSTATE=auto
GIT_PS1_SHOWUNTRACKEDFILES=auto
GIT_PS1_SHOWUPSTREAM=auto
GIT_PS1_COMPRESSSPARSESTATE=auto
git="$(__git_ps1 " [GIT BRANCH:> %s]")" || true
fmt="\033[32;1m%u@%h\033[0m:\033[34;1m%w\033[0m\033[33;1m$git\033[0m"
lf -remote "send $id set promptfmt \"$fmt\""
}}
Another example on-cd command to show some git, mercury and subversion repository information only on parent directory. This will clear prompt when outside of parent directory of a git repository.
cmd on-cd &{{
# display repository status in your prompt
if [ -d .git ] || [ -f .git ]; then
branch="$(git branch --show-current 2>/dev/null)" || true
remote="$(git config --get "branch.$branch.remote" 2>/dev/null)" || true
url="$(git remote get-url "$remote" 2>/dev/null)" || true
fmt="\033[32;1m%u@%h\033[0m:\033[34;1m%w\033[0m\033[33;1m [GIT BRANCH:> $branch >> $url]\033[0m"
elif [ -d .hg ]; then
hg="$(hg branch 2>/dev/null)" || true
fmt="\033[32;1m%u@%h\033[0m:\033[34;1m%w\033[0m\033[33;1m [HG BRANCH:> $hg]\033[0m"
elif [ -d .svn ]; then
svn="$(svn info 2>/dev/null | awk '/^URL: /{print $2}')" || true
fmt="\033[32;1m%u@%h\033[0m:\033[34;1m%w\033[0m\033[33;1m [SVN URL:> $svn]\033[0m"
else
fmt="\033[32;1m%u@%h\033[0m:\033[34;1m%d\033[0m\033[1m%f\033[0m"
fi
lf -remote "send $id set promptfmt \"$fmt\""
}}
You can use sshfs
to mount remote filesystems and then browse them in lf
.
To prevent operation not permitted
errors when attempting to move files across devices on the same mount point, pass the -o workaround=renamexdev
argument to sshfs
.
The bundled Vim script plugin etc/lf.vim
only works in terminal vim and provides only a basic :LF
command.
There are a number of vim plugins providing more features which can be used in gvim and neovim as well:
lfrc
syntax highlighting:
Here are some that aren't specific to lf:
- vim-floaterm (uses nvim's floating window)
- fm-nvim
- tfm.nvim
Yet another way to copy and move showing progress but using only cp, mv, and cp-p magic. This also shows the speed and the ETA.
cmd paste $cp-p --lf-paste "$id"
On Windows, you can use QuickLook with lf to preview files just like with other file managers. Simply add
map <space> $your/path/to/QuickLook.exe %f%
If using WSL on windows, you can convert file paths using wslpath -w
and execute them with powershell
map <space> ${{
QL_EXE='C:\PATH_TO\QuickLook.exe'
QL_FILE=$(wslpath -w $f)
powershell.exe "$QL_EXE $QL_FILE"
}}
then you can use space to preview any files. Notice that this mapping replaces the original function of space.
Run node
scripts in a directory
cmd node_script ${{
script="$(jq -r '.scripts | keys[]' <package.json | sort | fzf --height 20%)"
npm run "$script"
}}
The following script allows you to use lf as a means to search, preview, and play videos hosted on YouTube:
https://github.com/slavistan/lf-gadgets/tree/master/lf-yt
Keep in mind, it requires a YouTube API key.
Convert audio files to mp3s using lame:
# convert to mp3 files using lame
cmd mp3 ${{
set -f
outname="$(printf '%s' "$f" | cut -d. -f1)"
lame -V --preset standard $f "$outname.mp3"
}}
ffmpeg can be used to convert videos to .mp3, compress videos to save space, and more.
The following script converts (selected) files of type webm,mkv,mp4 to mp3, then moves them to the ~/Music folder.
If it's not webm,mkv,mp4, the selected file is ignored.
It also checks for videos without an audio layer (via ffprobe, so if you don't need it, remove that if check) and notifies the user upon completion via notify-send (also optional, can be removed alongside the 4 variables used)
If an argument passes into this command/function, it also deletes the original video files.
cmd stripvideolayer ${{
clear
set -f
# Variables for notify-send
converted_filenames=""
converted_files_count=0
videos_without_audio_streams=""
videos_without_audio_streams_count=0
for pickedFilepath in $fx; do
case $pickedFilepath in
*.mp4 | *.webm | *.mkv) ;;
*) echo "Skipping $pickedFilepath" && continue 1;;
esac
parsed_MP3="$(printf '%s' "$pickedFilepath" | sed 's/\(.mp4\|.webm\|.mkv\)/.mp3/' | sed 's|.*\/||')"
parsed_MP3="$HOME/Music/$parsed_MP3"
# Using ffprobe because videos without audiostream result in exit code 1 which stops this entire loop of many files
# Remove (alongside its 2 variables) if you don't record videos without audio (which are admittedly rare)
if [[ $(ffprobe -loglevel error -show_entries stream=codec_type -of csv=p=0 "$pickedFilepath") != *"audio"* ]]; then
((videos_without_audio_streams_count=videos_without_audio_streams_count+1))
videos_without_audio_streams="$videos_without_audio_streams"$'\n'"$pickedFilepath"
continue 1
fi
ffmpeg -i "$pickedFilepath" "$parsed_MP3"
((converted_files_count=converted_files_count+1))
converted_filenames="$converted_filenames"$'\n'"$pickedFilepath"
if [[ $# -eq 1 ]]; then
rm -f -- "$pickedFilepath"
fi
done
# Notify the results to the user
if [[ $converted_files_count -gt 0 ]]; then
converted_filenames=$(echo "$converted_filenames" | sed 's|.*\/||')
notify-send "Converted MP3 Files($converted_files_count):" "$converted_filenames"
fi
if [[ $videos_without_audio_streams_count -gt 0 ]]; then
videos_without_audio_streams=$(echo "$videos_without_audio_streams" | sed 's|.*\/||')
notify-send "Videos without audio stream($videos_without_audio_streams_count):" "$videos_without_audio_streams"
fi
# Uncomment the below line if you want to automatically unselect the original converted video files
#lf -remote "send $id unselect"
}}
map u stripvideolayer
map <a-u> stripvideolayer delete_after_encoding
The following script compresses the (selected) files of type webm,mkv,mp4
It is essentially a glorified ffmpeg -i input.video -vcodec libx265 -crf "$compressionRatio" output.mp4;
Recommended for videos downloaded off websites (especially .webm/.mkv) and for mass-selecting a 10GB+ video folder and just letting it run in the background for hours, saving many gigabytes. And whenever it's finished, it notifies the user.
The compression ratio is determined by the user input (30 being default, which even at crystal-clear videos is hard to see difference, but 30 is rarely worth it on mp4 videos)
cmd compressvideo ${{
clear
set -f
converted_filenames="" # notify-send variable
converted_files_count=0 # notify-send variable
echo "Compression Rate? (default: 31, maximum: 50)"
read compressionRate
# If not a number (e.g. empty), give default 31 value
if ! [[ $cr =~ ^[0-5][0-9]$ ]]; then
compressionRate="31"
fi
for pickedFilepath in $fx; do
# could instead use ffprobe but would get more complicated as the filetype suffix becomes unknown
case "$pickedFilepath" in
*.mp4)
tempFilepath="$(printf '%s' "$pickedFilepath" | sed 's|.mp4|(CONVERTING).mp4|')"
mv -f "$pickedFilepath" "$tempFilepath"
ffmpeg -i "$tempFilepath" -vcodec libx265 -crf "$compressionRate" "$pickedFilepath"
rm -f -- "$tempFilepath"
;;
*.webm | *.mkv)
newFilepath="$(printf '%s' "$pickedFilepath" | sed 's/\(.webm\|.mkv\)/.mp4/')"
ffmpeg -i "$pickedFilepath" -vcodec libx265 -crf "$compressionRate" "$newFilepath"
rm -f -- "$pickedFilepath"
;;
*) continue 1;;
esac
((converted_files_count=converted_files_count+1))
converted_filenames="$converted_filenames"$'\n'"$pickedFilepath"
done
# Notify the user of the results
if [[ $converted_files_count -gt 0 ]]; then
converted_filenames="$(printf '%s' "$converted_filenames" | sed 's|.*\/||')"
notify-send "Compressed Videos($converted_files_count):" "$converted_filenames"
fi
}}
In Windows, you can easily integrate 7zip with lf as an archive extractor.
In lfrc
:
cmd extract $%LOCALAPPDATA%/lf/extract.cmd %f%
map x extract
Create a file named extract.cmd
available to lf in your lf user settings:
@ECHO OFF
REM
REM LF Archive Extract script
REM
REM Use 7zip for extractor
REM
REM Extract archive contents into destination folder
REM with the same name as the archive file
REM
7z x %1 -o%~n1 -y 1> %LOCALAPPDATA%\lf\extract.log 2>&1
Atool works as a simple frontend for various compression tools and can be used as a simple extract solution for many formats:
cmd extract ${{
set -f
atool -x $fx
}}
Since file extraction tools are occasionally affected by vulnerabilities that may allow overwriting files anywhere in the filesystem, it may be a good idea to sandbox the extraction process using this script
Create an archive from selected files using atool:
# Let lf type for you; simply provide the archive's name and extension
map Z push :&apack<space>''<space>$fx<left><left><left><left><left>
Ouch (Obvious Unified Compression Helper) provides a modern alternative to atool. It's a CLI tool for compressing and decompressing for various formats. Unlike atool is is still actively developed and implements the different (de)compression formats in rust, providing a safer alternative.
cmd extract ${{
set -f
ouch decompress $fx
}}
In order to avoid displaying outdated file strucures after decrompressing archives, it is possible to add the reload command to the key mapping for extraction:
map a :extract; reload
archivemount
can be used to browse archives like directories.
To integrate archivemount
with lf
, define a mapping to invoke it on a selected archive file:
map am ${{
mntdir="$f.mnt"
mkdir -p -- "$mntdir"
archivemount "$f" "$mntdir"
lf -remote "send $id cd \"$(printf '%s' "$mntdir" | sed 's/\\/\\\\/g;s/"/\\"/g')\""
}}
To automatically unmount archives after exiting, it is necessary to invoke lf
as a wrapper script. This is because archives cannot be unmounted while the user is currently inside them, which means that unmounting has to be performed after lf
exits.
alias lf=lfwrapper
lfwrapper() {
command lf "$@"
# cleanup
awk '$1 == "archivemount" { print $2 }' /etc/mtab | while read -r mntdir; do
sanitized_input="$(printf "$mntdir")" # /etc/mtab uses octal representation of spaces (possible other symbols too), printf would convert octal representation, so that it can be used in the umount & rmdir commands.
umount "$sanitized_input"
rmdir "$sanitized_input"
done
}
It is possible to integrate with lfcd
as well. There is the possibility that the user might exit lf
while inside an archive, in which case the destination can be set to the first existing parent directory.
lfcd() {
dir="$(lf -print-last-dir "$@")"
while ! cd "$dir" 2>/dev/null; do
dir="$(dirname -- "$dir")"
done
}
This snippet adds a zsh key binding Alt-k
that opens lf in a tmux split.
Pressing a
in lf adds the selected file(s) to the zsh command line as relative paths, A
adds absolute paths. .
changes the zsh directory.
Add this to your .zshrc
:
_zlf() {
emulate -L zsh
local d=$(mktemp -d) || return 1
{
mkfifo -m 600 $d/fifo || return 1
tmux split -bf zsh -c "exec {ZLE_FIFO}>$d/fifo; export ZLE_FIFO; exec lf" || return 1
local fd
exec {fd}<$d/fifo
zle -Fw $fd _zlf_handler
} always {
rm -rf $d
}
}
zle -N _zlf
bindkey '\ek' _zlf
_zlf_handler() {
emulate -L zsh
local line
if ! read -r line <&$1; then
zle -F $1
exec {1}<&-
return 1
fi
eval $line
zle -R
}
zle -N _zlf_handler
If you don't use tmux, you can modify the command to open a terminal emulator window instead, but if it runs synchronously you need to add &!
at the end to fork and disown the process.
Finally, add this to lfrc
:
cmd zle-cd %printf 'cd %q && zle reset-prompt\n' "$PWD" >&$ZLE_FIFO
cmd zle-insert-relative %{{
for f in $fx; do
printf 'LBUFFER+="${LBUFFER:+ }${(q)$(realpath %q --relative-to=$PWD)}"\n' "$f" >&$ZLE_FIFO
done
}}
cmd zle-insert-absolute %{{
for f in $fx; do
printf 'LBUFFER+="${LBUFFER:+ }%q"\n' "$f" >&$ZLE_FIFO
done
}}
cmd zle-init :{{
map . zle-cd
map a zle-insert-relative
map A zle-insert-absolute
}}
&[[ -n "$ZLE_FIFO" ]] && lf -remote "send $id zle-init"
ZSH Select with LF plugin
An alternative to this available as a plugin is located here: https://github.com/chmouel/zsh-select-with-lf
The lfcd function for bash, that makes it so that when quitting lf, the working dir will be the one you were in in lf. Add this to your config.fish file:
https://github.com/gokcehan/lf/blob/master/etc/lfcd.fish
Starship is a cross-shell prompt displaying info, like the git branch or go version of the current working directory.
cmd on-cd &{{
fmt="$(STARSHIP_SHELL= starship prompt | sed 's/\\/\\\\/g;s/"/\\"/g')"
lf -remote "send $id set promptfmt \"$fmt\""
}}
powerlevel10k zsh prompt
To show the current nesting level ($LF_LEVEL
) in your prompt when using p10k, add the following to your p10k.zsh
config file:
- Add the following function anywhere in the config file. Replace
📂
with whichever icon you want to use and is supported by your terminal/font.
prompt_lf() {
p10k segment -f 208 -i 📂 -t "$LF_LEVEL" -c "$LF_LEVEL"
}
- Add
lf
to your prompt (POWERLEVEL9K_LEFT_PROMPT_ELEMENTS
orPOWERLEVEL9K_RIGHT_PROMPT_ELEMENTS
at the top of the file)
This prompt segment will only be shown when the shell is "nested" in an lf instance.