以前こんなエントリを書いた。
petitviolet.hatenablog.com
が、最近はpecoからfzfに大体を移行している。
主な理由はfzfについているpreview機能が気に入ったからで、ファイルやGitをインタラクティブに選択する際にpreviewが出来ると助かることが多いため。
逆にpreview機能を必要としないhistory検索とかはpecoを使っているままだったりはする。
どんな感じになるのか
fzfの--preview
オプションを使って便利になる
事前準備とか
環境
macOS Mojave 10.14とZsh。
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.14
BuildVersion: 18A391
$ zsh --version
zsh 5.7 (x86_64-apple-darwin17.7.0)
$ fzf --version
0.18.0 (brew)
Zsh Line Editorについて
fzf関係なく、zshで関数に対するショートカットキーを設定したり入力中のバッファをいじるのにZLE(Zsh Line Editor)を使っている。
だいたい↓のあたり。
zle accept-line
zle clear-screen
zle -N my_function
bindkey '<key>' my_function
BUFFER='xxx'
LBUFFER+='yyy'
CURSOR=$#LBUFFER
詳しくはman zshzle
すると読める。
fzfのグローバル設定
FZF_DEFAULT_OPTS
は以下のようにしている。
export FZF_DEFAULT_OPTS="--no-sort --exact --cycle --multi --ansi --reverse --border --sync --bind=ctrl-t:toggle --bind=ctrl-k:kill-line --bind=?:toggle-preview --bind=down:preview-down --bind=up:preview-up"
まあこれはお好みで。
zshについてくるcdr
コマンドを使っていい感じにディレクトリ移動をする。
ls
に適当なオプションをつけて実行した結果をpreviewで表示している。
あるファイルを探しているけど、どこにあったっけ、と思いながらインタラクティブに選ぶことが出来て便利。
function select_cdr(){
local selected_dir=$(cdr -l | awk '{ print $2 }' | \
fzf --preview 'f() { sh -c "ls -hFGl $1" }; f {}')
if [ -n "$selected_dir" ]; then
BUFFER="cd ${selected_dir}"
zle accept-line
fi
zle clear-screen
}
zle -N select_cdr
bindkey '^@' select_cdr
treeコマンドからファイルを選択する
現在のディレクトリ配下からファイルを選択したいけどScalaとか書いてるとディレクトリ構造が深くてめんどくさい、という時にtreeで一覧表示しつつ、そのファイル名を選択できるようにした。
head
でファイルの先頭をpreviewしている。
ignoreしないと死んでしまうとは思うので、そこは適当に付け足したりする必要がありそう。
function tree_select() {
tree -N -a --charset=o -f -I '.git|.idea|resolution-cache|target/streams|node_modules' | \
fzf --preview 'f() {
set -- $(echo -- "$@" | grep -o "\./.*$");
if [ -d $1 ]; then
ls -lh $1
else
head -n 100 $1
fi
}; f {}' | \
sed -e "s/ ->.*\$//g" | \
tr -d '\||`| ' | \
tr '\n' ' ' | \
sed -e "s/--//g" | \
xargs echo
}
function tree_select_buffer(){
local SELECTED_FILE=$(tree_select)
if [ -n "$SELECTED_FILE" ]; then
LBUFFER+="$SELECTED_FILE"
CURSOR=$#LBUFFER
zle reset-prompt
fi
}
zle -N tree_select_buffer
bindkey "^t" tree_select_buffer
これを少し応用すると選択したファイルを直接vimで開く、みたいなことも可能。
function open_from_tree_vim(){
local selected_file=$(tree_select)
if [ -n "$selected_file" ]; then
BUFFER="vim $selected_file"
fi
zle accept-line
}
zle -N open_from_tree_vim
bindkey "^v^t" open_from_tree_vim
docker psからプロセスを選択する
docker ps
した結果からイメージを選択してstopしたりlogを見たりする時に使う。
function select_docker_process(){
LBUFFER+=$(docker ps -a --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Command}}\t{{.CreatedAt}}\t{{.Ports}}\t{{.Networks}}' | \
fzf --preview-window=down --preview 'f() {
set -- $(echo -- "$@")
if [[ $3 != "ID" ]]; then
docker logs --tail 300 $3
fi
}; f {}' | \
awk -F '\t' '{ print $2 }' | \
tr "\n" " ")
CURSOR=$#LBUFFER
zle reset-prompt
}
zle -N select_docker_process
bindkey "^g^d^h" select_docker_process
docker ps --format 'table ...'
が便利でよかった。
git statusで変更のあるファイルから選択する
git status
した結果からファイルを選択してgit add
したりする時に使う。
プレビューでgit diff
が表示されるようにしてある。
untrackedなファイルや既にgit add
したファイルとかについてもgit diff
で差分が見れるようにしたかった面倒な実装になってしまった。
function select_file_from_git_status() {
git status -u --short | \
fzf -m --ansi --reverse --preview 'f() {
local original=$@
set -- $(echo "$@");
if [ $(echo $original | grep -E "^M" | wc -l) -eq 1 ]; then # staged
git diff --color --cached $2
elif [ $(echo $original | grep -E "^\?\?" | wc -l) -eq 0 ]; then # unstaged
git diff --color $2
elif [ -d $2 ]; then # directory
ls -la $2
else
git diff --color --no-index /dev/null $2 # untracked
fi
}; f {}' |\
awk -F ' ' '{print $NF}' |
tr '\n' ' '
}
function insert_selected_git_files(){
LBUFFER+=$(select_file_from_git_status)
CURSOR=$#LBUFFER
zle reset-prompt
}
zle -N insert_selected_git_files
bindkey "^g^s" insert_selected_git_files
function select_git_add() {
local selected_file_to_add="$(select_file_from_git_status)"
if [ -n "$selected_file_to_add" ]; then
BUFFER="git add $(echo "$selected_file_to_add" | tr '\n' ' ')"
CURSOR=$#BUFFER
fi
zle accept-line
}
zle -N select_git_add
bindkey "^g^a" select_git_add
git branchとtagから選択する
git branch
とgit tag
の両方から、branch/tagを選択してcheckoutしたりpush/pullする時に使う。
主にgitのformatを設定するのが面倒くさかった。
function select_from_git_branch() {
local list=$(\
git branch --sort=refname --sort=-authordate --color --all \
--format='%(color:red)%(authordate:short)%(color:reset) %(objectname:short) %(color:green)%(refname:short)%(color:reset) %(if)%(HEAD)%(then)* %(else)%(end)'; \
git tag --color -l \
--format='%(color:red)%(creatordate:short)%(color:reset) %(objectname:short) %(color:yellow)%(align:width=45,position=left)%(refname:short)%(color:reset)%(end)')
echo $list | fzf --preview 'f() {
set -- $(echo -- "$@" | grep -o "[a-f0-9]\{7\}");
[ $# -eq 0 ] || git --no-pager log --oneline -100 --pretty=format:"%C(red)%ad%Creset %C(green)%h%Creset %C(blue)%<(15,trunc)%an%Creset: %s" --date=short --color $1;
}; f {}' |\
sed -e 's/\* //g' | \
awk '{print $3}' | \
sed -e "s;remotes/;;g" | \
perl -pe 's/\n/ /g'
}
function select_to_insert_branch() {
LBUFFER+=$(select_from_git_branch)
CURSOR=$#LBUFFER
zle reset-prompt
}
zle -N select_to_insert_branch
bindkey "^g^o" select_to_insert_branch
function select_git_checkout() {
local selected_file_to_checkout=`select_from_git_branch | sed -e "s;origin/;;g"`
if [ -n "$selected_file_to_checkout" ]; then
BUFFER="git checkout $(echo "$selected_file_to_checkout" | tr '\n' ' ')"
CURSOR=$#BUFFER
fi
zle accept-line
}
zle -N select_git_checkout
bindkey "^gco" select_git_checkout
まとめ