-
Notifications
You must be signed in to change notification settings - Fork 318
Tips
Most of the following tips assume that ifs
option is set to "\n"
and shell
option is set to a posix compatible shell. You may need to adjust these to your setup accordingly.
Tip
For shell scripting, you can use https://shellcheck.net/ to check for common errors.
- Use a shebang of
#!/bin/sh
for POSIXsh
scripts. - Use a shebang of
#!/bin/bash
forbash
scripts.
It is possible to assign a keybinding for editing and reloading the config file. This is convenient especially when you are making config changes as a new user:
cmd edit-config ${{
$EDITOR ~/.config/lf/lfrc
lf -remote "send $id source ~/.config/lf/lfrc"
}}
map C edit-config
It is possible to start lf
with the default settings by passing a dummy blank file for the -config
option, which will prevent the user config file lfrc
from being loaded:
lf -config /dev/null
If you want to have borders around the columns:
set drawbox
If you are used to file managers with single column directory views (e.g. midnight commander or far manager), there are a few settings you can use to get a similar feeling:
set nopreview
set ratios 1
set info size:time
If you also want to have dual panes, you need to utilize a terminal multiplexer or your window manager. For tmux
you may consider using a shell alias to automatically create dual panes:
alias mc='tmux split -h lf; lf'
You can configure the on-redraw
hook command to set the number of columns based on the current terminal width:
cmd on-redraw %{{
if [ $lf_width -le 80 ]; then
lf -remote "send $id set ratios 1:2"
elif [ $lf_width -le 160 ]; then
lf -remote "send $id set ratios 1:2:3"
else
lf -remote "send $id set ratios 1:2:3:5"
fi
}}
Although previews can be toggled using the preview
option, the number of columns displayed remains the same, which results in the directories being shifted to a different column. To prevent the directories from being shifted, the number of columns also adjusted when toggling the preview:
cmd toggle_preview %{{
if [ "$lf_preview" = "true" ]; then
lf -remote "send $id :set preview false; set ratios 1:5"
else
lf -remote "send $id :set preview true; set ratios 1:2:3"
fi
}}
map zp toggle_preview
Note that the order of commands can matter. For example, preview
cannot be enabled if ratios
contains only a single number, so if ratios
is currently set to 1
, then you must use set ratios 1:1; set preview true
instead of set preview true; set ratios 1:1
.
Since release r29, lf
defaults to using an underline for the cursor in the preview pane, in order to distinguish it from the active cursor.
This is controlled by the cursorpreviewfmt
option.
Here are a few examples for different values of the option:
underline (default) | pre-r29 behavior | grey cursor | no cursor |
---|---|---|---|
set cursorpreviewfmt "\033[4m" |
set cursorpreviewfmt "\033[7m" |
set cursorpreviewfmt "\033[7;90m" |
set cursorpreviewfmt "" |
![]() |
![]() |
![]() |
![]() |
This uses ANSI codes, see for example this reference.
Although there is no native support for moving up
/down
in the parent directory, this can be achieved by chaining commands:
map J :updir; down; open
map K :updir; up; open
There is a caveat where if the next entry in the parent directory is a file, then it will be selected and opened instead. However, the dironly
option can be used to prevent a file from being selected:
cmd move-parent &{{
dironly="setlocal '$(dirname "$PWD")' dironly"
lf -remote "send $id :updir; $dironly true; $1; $dironly false; open"
}}
map J move-parent down
map K move-parent up
By default lf
saves the names of files to be copied or moved on the server first so that you can for instance copy files in one client and paste them in another. If you don't need such use cases then you may consider remapping existing keys to work with selected files on the client instead of saving them on the server in advance:
map y %cp -ri -- $fs .
map d %mv -i -- $fs .
map p
Unfortunately POSIX cp
and mv
commands do not define an option to backup existing files, but if you have the GNU implementation, you can define a custom paste
command to use --backup
option with these commands:
cmd paste %{{
set -- $(cat ~/.local/share/lf/files)
mode="$1"
shift
case "$mode" in
copy) cp -r --backup=numbered -- "$@" .;;
move) mv --backup=numbered -- "$@" .;;
esac
rm ~/.local/share/lf/files
lf -remote "send clear"
}}
See man cp
or man mv
for more information.
You can define an asynchronous paste command to do file copying and moving asynchronously:
cmd paste &{{
set -- $(cat ~/.local/share/lf/files)
mode="$1"
shift
case "$mode" in
copy) cp -rn -- "$@" .;;
move) mv -n -- "$@" .;;
esac
rm ~/.local/share/lf/files
lf -remote "send clear"
}}
You can also define this command with a different name (e.g. cmd paste-async &{{ .. }}
) and then bind it to a different key (e.g. map P paste-async
) to use it selectively.
You can use an alternative file copying program that provides progress information such as rsync
and feed this information to lf
to display progress while coping files:
cmd paste &{{
set -- $(cat ~/.local/share/lf/files)
mode="$1"
shift
case "$mode" in
copy)
rsync -av --ignore-existing --progress -- "$@" . |
stdbuf -i0 -o0 -e0 tr '\r' '\n' |
while IFS= read -r line; do
lf -remote "send $id echo $line"
done
;;
move) mv -n -- "$@" .;;
esac
rm ~/.local/share/lf/files
lf -remote "send clear"
}}
Information is shown at the bottom of the screen every second but it is overwritten for each action that also use this part of the screen.
This snippet:
- Tries to use CoW (reflinks) on btrfs, zfs and xfs
- Falls back to lf's native paste (keeps progress %) if it can't
- Handles matching names in destination with
.~1~
like lf - Forwards cp errors to status line, if any
Tested and works with set shell bash
with set shellopts '-eu'
(and GNU coreutils).
(Read before using)
cmd paste_try_cow &{{
# # This was very helpful for debugging:
# log_file="$HOME/lf-reflink-log-$(date +'%Y-%m-%d_%H-%M-%S')"
# [ -f "$log_file" ] || touch "$log_file"
# exec 1>> $log_file 2>&1
# set -x
# In theory, this may fail,
# but I tested it on selection with 10k files - everything worked (bash)
set -- $(cat ~/.local/share/lf/files)
mode="$1"
shift
if [ $mode = 'copy' ]; then
# Reflink if all items of selection and the destination are on the
# same mount point and it is CoW fs.
# (to make sure reflink never fails in first place, so we don't have to
# clean up)
src_targets="$(df --output=target -- "$@" | sed '1d' | sort -u)"
if [ "$(df --output=target -- "$PWD" | tail -n 1)" = \
"$(echo "$src_targets" | tail -n 1)" ] && \
(( "$(echo "$src_targets" | wc -l)" == 1 )) && \
[[ "$(df --output=fstype -- "$PWD" | tail -n 1)" =~ ^(btrfs|xfs|zfs)$ ]]; then
echo 'selected copy and cp reflink paste'
start=$(date '+%s')
# Handle same names in dst
# TODO parallelism, idk - but exit/return/break won't stop the loop from subshell...
for i in "$@"; do
name="${i##*/}"
original="$name"
count=0
while [ -w "$PWD/$name" ]; do
count=$((count+1))
name="$original.~$count~"
done
set +e
cp_out="$(cp -rn --preserve=all --reflink=always -- "$i" "$PWD/$name" 2>&1)"
set -e
if [ ! -z "$cp_out" ]; then
lf -remote "send $id echoerr $cp_out"
exit 0
fi
done
finish=$(( $(date '+%s') - $start ))
t=''
if (( $finish > 2 )); then
t="${finish}s"
fi
# Or just skip a file when names are the same.
# (A LOT faster if you e.g. pasting selection of 10k files)
# cp -rn --reflink=always -- "$@" .
lf -remote "send clear"
green=$'\u001b[32m'
reset=$'\u001b[0m'
lf -remote "send $id echo ${green}reflinked!${reset} $t"
else
echo 'selected copy and lf native paste'
lf -remote "send $id paste"
lf -remote "send clear"
fi
elif [ $mode = 'move' ]; then
echo 'selected move and lf native paste'
lf -remote "send $id paste"
lf -remote "send clear"
fi
# # for debug
# set +x
lf -remote "send load"
}}
# name is different to avoid recursive calls
map p paste_try_cow
By default, the list of selected files is cleared after executing cut
followed by paste
, but the list is retained after copy
followed by paste
. This matches the behavior of a number of other file managers. To ensure the selected files are cleared after copy
/paste
, you can add the clear
command:
map p :paste; clear
This alternate workflow allows you to choose whether you want to cut or copy the files when pasting. The result is a two-step method where you select the files, then either move or copy the files to the destination.
cmd alt-paste &{{
if [ -n "$fs" ]; then
lf -remote "send $id $1"
fi
lf -remote "send $id paste"
}}
map P alt-paste cut
map p alt-paste copy
For multiple instances of lf
, it is necessary to keep the file selection in sync.
cmd load-select &{{
# skip if triggered via save-select from itself
if [ $# -eq 1 ] && [ "$1" = "$id" ]; then
exit 0
fi
lf -remote "send $id unselect"
if [ -s ~/.local/share/lf/select ]; then
files=$(tr '\n' '\0' < ~/.local/share/lf/select | xargs -0 printf ' %q')
lf -remote "send $id toggle $files"
fi
}}
cmd save-select &{{
printf "%s" "$fs" > ~/.local/share/lf/select
lf -remote "send load-select $id"
}}
# redefine existing maps to invoke save-select afterwards
map <space> :toggle; down; save-select
map u :unselect; save-select
map v :invert; save-select
# define wrapper command for glob-select since it needs to forward an argument
cmd globsel &{{
lf -remote "send $id :glob-select \"$1\"; save-select"
}}
cmd alt-paste &{{
if [ -n "$fs" ]; then
lf -remote "send $id :$1; save-select"
fi
lf -remote "send $id paste"
}}
map P alt-paste cut
map p alt-paste copy
# load selection on startup
load-select
Let's say you generally prefer your sorting to be
# "latest modified first"
set sortby time
set reverse
but for some selected directories (e.g. for movies / tv series) you would prefer natural sort.
Hack for r30 and lower
Lf doesn't remember options changed at runtime, but luckily we do have on-cd
and user_{key}
options.
Here is how the said example could be implemented using it:
cmd on-cd &{{
case "$PWD" in
/mnt/movies*)
lf -remote "send $id set user_prev_sortby $lf_sortby"
lf -remote "send $id set sortby natural"
lf -remote "send $id set noreverse"
lf -remote "send $id echomsg changed sort to natural"
;;
*)
# restore sorting on directory exit
if [[ "$lf_user_prev_sortby" != "" ]]; then
lf -remote "send $id set sortby $lf_user_prev_sortby"
lf -remote "send $id set reverse"
lf -remote "send $id echomsg restored sort to $lf_user_prev_sortby"
lf -remote "send $id set user_prev_sortby ''"
fi
;;
esac
}}
# run on startup too
on-cd
But this is much better served by the setlocal
option introduced in r31
e.g. to recreate the example above you would need to just add
setlocal /mnt/movies/ sortby natural
setlocal /mnt/movies/ noreverse
to your lfrc.
(Note that /
at the end of the path makes the option recursive, see lf -doc
for more)
You can use the underlying mkdir
command to create new directories.
It is possible to define a push mapping for such commands for easier typing as follows:
map a push %mkdir<space>
You can also create a custom command for this purpose
cmd mkdir %mkdir "$@"
map a push :mkdir<space>
This command creates a directory for each argument passed by lf.
For example, :mkdir foo 'bar baz'
creates two directories named foo
and bar baz
.
You can also join arguments with space characters to avoid the need to quote arguments as such:
cmd mkdir %IFS=" "; mkdir -- "$*"
map a push :mkdir<space>
This command creates a single directory with the given name.
For example, :mkdir foo bar
creates a single directory named foo bar
.
You can also consider passing -p
option to mkdir
command to be able to create nested directories (e.g. mkdir -p foo/bar
to create a directory named foo
and then a directory named bar
inside foo
).
If you want to select the new directory afterwards, you can call a remote select
command as such:
cmd mkdir %{{
IFS=" "
mkdir -p -- "$*"
lf -remote "send $id select \"$*\""
}}
The above snippets can't handle double quotes in directory names. A better way is to do everything in a programming language. Here is an example.
lf-mkdir
#!/usr/bin/env raku
my $dir = prompt "Directory Name: ";
mkdir $dir;
run "lf", "-remote",
"send {%*ENV<id>} select \"{$dir.match(/^<-[/]>+/).subst: '"', '\"', :g}\"";
lfrc
map Md %lf-mkdir
lf-mkfile
#!/usr/bin/env raku
my $file = prompt "Filename: ";
open($file, :r, :create).close;
run "lf", "-remote", "send {%*ENV<id>} select \"{$file.subst: '"', '\"', :g}\"";
lfrc
map Mf %lf-mkfile
You can't use environmental (shell) variables directly in mappings and internal commands.
You have to send variable to lf from shell scope using remote call. For example - when you want to map g G
to cd $GOPATH
you can use:
map gG $lf -remote "send $id cd $GOPATH"
However it is possible to wrap this in a utility command which uses eval
to expand the environment variables and then invoke lf -remote
to send the result back to lf
:
cmd eval &{{
cmd="send $id"
for arg; do
cmd="$cmd $(eval "printf '%q' \"$arg\"")"
done
lf -remote "$cmd"
}}
This makes it possible to use environment variables in settings:
eval set previewer "$XDG_CONFIG_HOME/lf/previewer"
eval set cleaner "$XDG_CONFIG_HOME/lf/cleaner"
Environment variables can also be used in commands:
eval cd "$HOME/Documents"
Command substitution works too:
eval echo "$(date)"
zsh
does not split words by default as described here, which makes it difficult to work with $fs
and $fx
variables, but a compatibility option named shwordsplit
(-y
or --sh-word-split
) is provided for this purpose.
You can set this option for all commands as such:
set shell zsh
set shellopts '-euy'
set ifs "\n"
set filesep "\n" # default already
You can define a command to rename multiple files at the same time using your text editor to change the names.
cmd bulk-rename ${{
old="$(mktemp)"
new="$(mktemp)"
if [ -n "$fs" ]; then
fs="$(basename -a $fs)"
else
fs="$(ls)"
fi
printf '%s\n' "$fs" >"$old"
printf '%s\n' "$fs" >"$new"
$EDITOR "$new"
[ "$(wc -l < "$new")" -ne "$(wc -l < "$old")" ] && exit
paste "$old" "$new" | while IFS= read -r names; do
src="$(printf '%s' "$names" | cut -f1)"
dst="$(printf '%s' "$names" | cut -f2)"
if [ "$src" = "$dst" ] || [ -e "$dst" ]; then
continue
fi
mv -- "$src" "$dst"
done
rm -- "$old" "$new"
lf -remote "send $id unselect"
}}
This command either works on selected files or non-hidden files in the current directory if you don't have any selection.
Another very compact possibility which renames the selected items only with vidir
is:
map <f-2> $printf '%s\n' "$fx" | vidir -
Or, if you want more features, such as support for cyclic renames (A -> B, B -> A,...), for creating missing folders, git mv
, and with safeguards against deleting / overwriting files, and without annoying numbers before filenames - consider using dmulholl/vimv.
(same name as another vimv, but this one is more feature rich and written in rust, don't forget to read its --help
).
cmd bulkrename ${{
vimv -- $(basename -a -- $fx)
lf -remote "send $id load"
lf -remote "send $id unselect"
}}
map R bulkrename
Another option is File::Name::Editor written in raku language.
cmd bulkrename ${{
clear
file-name-editor --confirm $(basename -a -- $fx)
lf -remote "send $id unselect"
}}
map R bulkrename
Yet another option is qmv:
cmd bulkrename ${{
clear
qmv -d $fx
lf -remote "send $id unselect"
}}
map R bulkrename
Since #1162, lf
by default places the cursor right before the extension when renaming a file. However you can add additional bindings for other renaming schemes with the following config:
# unmap the default rename keybinding
map r
map i rename
map I :rename; cmd-home
map A :rename; cmd-end
map c :rename; cmd-delete-home
map C :rename; cmd-end; cmd-delete-home
This will give you the following behavior for the following keybindings:
Keybinding | Description | Effect of renaming foo.txt
|
---|---|---|
i |
rename with the cursor placed at the extension (original behavior) |
rename: foo|.txt |
I |
rename with the cursor placed at the beginning |
rename: |foo.txt |
A |
rename with the cursor placed at the end |
rename: foo.txt| |
c |
rename with the portion before the extension deleted |
rename: |.txt |
C |
rename with the entire filename deleted |
rename: | |
You can define a command to follow links:
cmd follow_link %{{
lf -remote "send ${id} select '$(readlink $f)'"
}}
map gL follow_link
Here's a config snippet that adds a soft / hard symlinking command and mapping:
# y (select for copy) and P to paste soft-link
# d (select for cut) and P to paste hard-link
cmd link %{{
set -- $(cat ~/.local/share/lf/files)
mode="$1"
shift
if [ "$#" -lt 1 ]; then
lf -remote "send $id echo no files to link"
exit 0
fi
case "$mode" in
# symbolically copy mode is indicating a soft link
copy) ln -sr -t . -- "$@";;
# while a move mode is indicating a hard link
move) ln -t . -- "$@";;
esac
rm ~/.local/share/lf/files
lf -remote "send clear"
}}
map P :link
P
is used for both soft and hard linking. The "cut" mode of files donates a hard link is requested, while a "copy" mode donates a soft link is requested.
You might be missing the possibility to put the lf
job into the background with the default ctrl
and z
keys.
map <c-z> $ kill -STOP $PPID
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 -e 's/^/trash:\/\/\//')
gio trash --restore $ft
echo 'restored' $(basename -a $fx)
}}
A function similar to macOS Finder.app
If you use zsh
, make sure you set shellopts '-euy'
as described above for proper $fx
split.
map <a-n> newfold
cmd newfold ${{
set -f
printf "Directory name: "
read newd
mkdir -- "$newd"
mv -- $fx "$newd"
}}
Like rangers da
, dr
, and dt
. Implementing this for copying files is mostly the same, simply rename move
to copy
where applicable.
NOTE this can also be used to switch a file selection from move to copy or vice versa.
WARN $'\n'
is not POSIX compliant.
cmd cut-add %{{
newline=$'\n'
sed -i '1s/.*/move/' "$XDG_DATA_HOME/lf/files"
echo "$fx" >> "$XDG_DATA_HOME/lf/files"
lf -remote "send $id unselect${newline}send $id sync"
}}
cmd cut-remove %{{
newline=$'\n'
sed -i '1s/.*/move/' "$XDG_DATA_HOME/lf/files"
while read -r file; do
sed -i "\|$file|d" "$XDG_DATA_HOME/lf/files"
done <<< "$fx"
lf -remote "send $id unselect${newline}send $id sync"
}}
cmd cut-toggle %{{
newline=$'\n'
files=$(comm --output-delimiter="" -3 \
<(tail -n +2 "$XDG_DATA_HOME/lf/files" | sort) \
<(echo "$fx" | sort) | tr -d '\0')
printf "move\n$files" > "$XDG_DATA_HOME/lf/files"
lf -remote "send $id unselect${newline}send $id sync"
}}
the following commands each use xclip to copy files onto your clipboard. This depends on xclip so it probably won't work on windows. I suggest anyone who wants to use it cross platform create a script to automate platform dependent clipboard tools and then replace xclip
below with that script. These commands strip the trailing line break.
cmd yank-dirname $dirname -- "$f" | head -c-1 | xclip -i -selection clipboard
cmd yank-path $printf '%s' "$fx" | xclip -i -selection clipboard
cmd yank-basename $basename -a -- $fx | head -c-1 | xclip -i -selection clipboard
cmd yank-basename-without-extension ${{
echo "$fx" |
xargs -r -d '\n' basename -a |
awk -e '{
for (i=length($0); i > 0; i--) {
if (substr($0, i, 1) == ".") {
if (i == 1) print $0
else print substr($0, 0, i-1)
break
}
}
if (i == 0)
print $0
}' |
if [ -n "$fs" ]; then cat; else tr -d '\n'; fi |
xclip -i -selection clipboard
}}
Here's an alternative implementation of yank-basename-without-extension
cmd yank-basename-without-extension &basename -a -- $fx | rev | cut -d. -f2- | rev | head -c-1 | xclip -i -selection clipboard
Here's another implementation which treats any character after the first "." as part of the extension.
cmd yank-basename-without-extension &basename -a -- $fx | cut -d. -f1 | head -c-1 | xclip -i -selection clipboard
If you just want use Ctrl+c to copy the path to the current file into the clipboard (and the paste it with the terminal's Ctrl+Shift+v shortcut), use the following:
cmd copy_to_clipboard %{{
if type pbcopy > /dev/null 2>&1; then
printf '%s' "$f" | pbcopy
elif type xsel > /dev/null 2>&1; then
printf '%s' "$f" | xsel -ib
fi
}}
map <c-c> :copy_to_clipboard
Note that it uses pbcopy on OS X and xsel on Linux.
cmd open-with-gui &$@ $fx ## opens with a gui application outside lf client
cmd open-with-cli $$@ $fx ## opens with a cli application inside lf client
map O push :open-with-gui<space> ## input application
map o push :open-with-cli<space> ## input application
cmd run-escaped %{{
IFS=" "
cmd="$1"
shift
"$cmd" "$*"
}}
map \\ push :run-escaped<space>
Press .
to repeat the last command typed on the command line:
map . :read; cmd-history-prev; cmd-enter
returned url wil be appended to clipboard (linux) edit to pbcopy for osx
cmd share $curl -F"file=@$fx" https://0x0.st | xclip -selection c
In lf
the i
key is the default for previewing files. It is convenient to let i
also quit the less
pager. Hence you uses the same key to open preview and to close it, and here is how to do:
$ echo "i quit" > ~/.config/lf/less
$ lesskey -o ~/.config/lf/less.lesskey ~/.config/lf/less
In the lf
config activate the preview and set the preview script for less to use this cusom key binding.
set preview
set previewer "less -k ~/.config/less.lesskey"
cmd select-files &{{
get_files() {
if [ "$lf_hidden" = 'false' ]; then
find "$PWD" -mindepth 1 -maxdepth 1 -type f -not -name '.*' -print0
else
find "$PWD" -mindepth 1 -maxdepth 1 -type f -print0
fi |
xargs -0 printf ' %q'
}
lf -remote "send $id :unselect; toggle $(get_files)"
}}
cmd select-dirs &{{
get_dirs() {
if [ "$lf_hidden" = 'false' ]; then
find "$PWD" -mindepth 1 -maxdepth 1 -type d -not -name '.*' -print0
else
find "$PWD" -mindepth 1 -maxdepth 1 -type d -print0
fi |
xargs -0 printf ' %q'
}
lf -remote "send $id :unselect; toggle $(get_dirs)"
}}
This is useful if you e.g. want to use rofi to search for specific instance of lf.
Operating System Commands (OSC) offer an escape sequence for changing the title of the terminal. The following changes the title to current directory:
printf "\033]0; $PWD\007"
However, it's not a standard, so it doesn't work in some terminals. It is confirmed to be working in alacritty, st, kitty and rxvt-unicode. KDE's konsole uses slightly a different escape sequence:
printf "\033]30; $PWD\007"
We can use this in lf via special on-cd
command, which runs when directory is changed.
cmd on-cd &{{
# '&' commands run silently in background (which is what we want here),
# but are not connected to stdout.
# To make sure our escape sequence still reaches stdout we pipe it to /dev/tty
printf "\033]0; $PWD\007" > /dev/tty
}}
# also run at startup
on-cd
If you want to show ~
instead of /home/username
, change printf line to
printf "\033]0; ${PWD/#$HOME/\~}\007" > /dev/tty
Or to strictly match POSIX standard (compatible with more shells):
printf "\033]0; $(pwd | sed "s|$HOME|~|")\007"
It might also be useful to show a program name.
printf "\033]0; ${PWD/#$HOME/\~}\007" > /dev/tty
demo of the first version
(it actually changes instantly, i just pull updates only once per second)
lf
can be configured to send an OSC 7 terminal sequence whenever it changes directories, which advises the terminal on what the current directory is supposed to be:
# set pane_path when changing directory
cmd on-cd &{{
printf "\e]7;$PWD\e\\" > /dev/tty
}}
# unset pane_path when quitting
cmd on-quit &{{
printf "\e]7;\e\\" > /dev/tty
}}
# trigger on-cd upon startup
on-cd
This is useful when trying to open a new pane/tab from the current directory shown in lf
. In addition this also works with tmux
, which uses the OSC 7 terminal sequence to set the variable pane_path
. Below is a configuration which opens panes at the location of pane_path
, falling back to pane_current_path
if not set:
# use pane_path, falling back to pane_current_path
bind v split-window -h -c "#{?pane_path,#{pane_path},#{pane_current_path}}"
bind s split-window -v -c "#{?pane_path,#{pane_path},#{pane_current_path}}"
Add the following to your lfrc
to show a warning on startup if lf
is running as a nested instance:
&[ $LF_LEVEL -eq 1 ] || lf -remote "send $id echoerr \"Warning: You're in a nested lf instance!\""
Show nesting level in 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.
function 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.
First, set an environment variable called LF_BOOKMARK_PATH
to an empty folder which will contain your bookmarks, then add the following to your lfrc
.
cmd bookmark_jump ${{
res="$(cat $LF_BOOKMARK_PATH/$(ls $LF_BOOKMARK_PATH | fzf))"
lf -remote "send $id cd \"$res\""
}}
cmd bookmark_create ${{
read ans
echo $PWD > $LF_BOOKMARK_PATH/$ans
}}
The incfilter
option is normally used for providing a preview of filtered files for the filter
modal command. However this can also be used as a file selector (similar to fzf
) for opening files.
set incfilter
map f filter
cmap <enter> &{{
# select and open file
if [ "$lf_mode" = "filter" ]; then
lf -remote "send $id :cmd-enter; setfilter; open"
else
lf -remote "send $id cmd-enter"
fi
}}
cmap <a-n> &{{
# go to next file
if [ "$lf_mode" = "filter" ]; then
lf -remote "send $id down"
fi
}}
cmap <a-p> &{{
# go to previous file
if [ "$lf_mode" = "filter" ]; then
lf -remote "send $id up"
fi
}}
Although lf
doesn't directly support profiles/themes, it is possible to emulate this by placing the profile settings inside separate files and loading them using the source
command. The profiles can be placed in a dedicated directory such as ~/.config/lf/profiles
.
~/.config/lf/profiles/foo
:
set info size
set promptfmt "\033[32m%u\033[34m%d\033[0m%f"
set ratios 1:1:1
~/.config/lf/profiles/bar
:
set info time
set promptfmt "\033[36m%d\033[33m%f"
set ratios 1:1:2
lfrc
file:
cmd profile &lf -remote "send $id source ~/.config/lf/profiles/$1"
With this, it should be possible to switch profiles using profile foo
and profile bar
. You may want to additionally create a profile with the default settings and source it before switching profiles, so that settings from the previous profile are not retained.
lf
provides a function lfcd
to cd into lf
current directory on quit.
This implies you must execute either lf
or lfcd
and cannot change your mind about whether to cd to the current directory on quit after running one or the other.
Following allows to quit lf
normally using q
hotkey or cd to current directory on quit using Q
hotkey.
Create script lf.bash
in your ~/.config/lf
directory with following contents:
lf() {
export LF_CD_FILE=/var/tmp/.lfcd-$$
command lf $@
if [ -s "$LF_CD_FILE" ]; then
local DIR="$(realpath "$(cat "$LF_CD_FILE")")"
if [ "$DIR" != "$PWD" ]; then
echo "cd to $DIR"
cd "$DIR"
fi
rm "$LF_CD_FILE"
fi
unset LF_CD_FILE
}
Source this file in your .bashrc
:
source ~/.config/lf/lf.bash
Then add following to your lfrc
config file:
cmd quit-and-cd &{{
pwd > $LF_CD_FILE
lf -remote "send $id quit"
}}
map Q quit-and-cd
Then you just run lf
normally and quit with q
or Q
.
You might want to check your .lfrc
file into source control, or otherwise share it between machines with different versions of lf
installed. If you do this, you might run into a situation where you want to set options differently dependent on the version of lf
being used.
For example, set cursorpreviewfmt
is not supported on lf prior to version 29. You can work around this with some code in your .lfrc
like this:
cmd set_lf_vars ${{
if [ $(lf --version) -ge 29 ]; then
# See https://github.com/gokcehan/lf/issues/1258
lf -remote "send $id set cursorpreviewfmt \"\\033[7m\""
fi
}}
:set_lf_vars
This will create a custom command which checks the version of lf
, runs logic conditionally using remote commands, and then runs that command immediately.
Similarly to the tip above, you can also set configuration parameters based upon the operating system you are running on. As an example, this code snippet sets a mapping that yanks the current path differently, depending on whether you are using Linux or MacOS:
cmd set_os_mappings ${{
if [[ ${OSTYPE} == darwin* ]]; then
lf -remote "send $id map Y \$printf \"%s\" \"\$fx\" | pbcopy"
else
lf -remote "send $id map Y \$printf \"%s\" \"\$fx\" | xclip -selection clipboard"
fi
}}
:set_os_mappings
When using the command line, it is possible to use cmd-complete
to automatically complete the typed command (if unambiguous) just before running it. This allows for shorthands. For example, use :del
as a shorthand for :delete
, and :setf foo
as a shorthand for :setfilter foo
.
cmap <enter> &{{
if [ "$lf_mode" = "command" ]; then
lf -remote "send $id complete-cmd"
else
lf -remote "send $id cmd-enter"
fi
}}
cmd complete-cmd :{{
# position cursor at the end of the first word
cmd-home
cmd-word
# perform tab completion before running the command
cmd-complete
cmd-enter
}}