🖥️

ターミナル環境の見直し 2022年末

2022/12/29に公開

前回の大改修から5, 6年は経過し、その間ツギハギメンテを繰り返していてだいぶ負債だらけになってきたので一掃する。

結果

こんな感じになった。

端末を良い感じにすると操作効率向上だけでなく、常に端末で作業したくなるし意味もなく毎日カラースキームを変えたり意味もなくpopupを起動したり意味もなく端末を眺めてそれだけでニヤニヤできるぞ。

構成・コンセプト

  • 以前までは見た目を派手にせずプレーンにしていたが、今回はこってり味にする。
  • あまり頑張りすぎない(大事)
    • 頑張りすぎる(深いところに手を出す)と、plugin公式の改善などですぐにメンテが必要になるので。
  • 環境/見直し対象はMac OS X, Alacritty, tmux, zsh, neovim。
    • neovimの見直しについてはまた別途。本稿ではそれ以外について記述。
    • シェルは10年以上zshを使っていて、慣れすぎていて変えられない。

ターミナルエミュレータ

  • 少し前にiterm2に動作の重さを感じAlacritty + tmuxにしていた。
  • AlacrittyはRust製のGPUを使う非常に高速なターミナルエミュレータ。tabを開いて複数セッションを管理できるターミナルマルチプレクサ機能がなく、設定もGUIではなくファイルベースで行い、シンプルになっている。そこがいい。

terminalの他の候補

  • 同じくRust製でGPU-acceleratedの高速ターミナルエミュレータ、weztermも試してみた。
  • こちらを参考にさせていただきました。 https://zenn.dev/yutakatay/articles/wezterm-intro
    • ターミナルマルチプレクサ機能があったり、ドキュメントの豊富さが素晴らしい。こちらも良さそう。

アイコンフォント(Nerdfont)の導入

  • ターミナルやvimのファイラーで、開発者向けアイコンをディレクトリやファイルに付けられるフォントにする。
  • Nerdfontをinstall。
    • https://github.com/ryanoasis/nerd-fonts
    • Mac OS Xであれば、brew caskでinstallできる。
    • ソースとなるフォントに応じて、種類は色々。好きなものを選ぶ。ここではHack Nerd Fontを使う。
brew tap homebrew/cask-fonts
brew install --cask font-hack-nerd-font
  • install後は、ひとまずターミナルエミュレータの設定でフォントを変える。
    • Alacrittyでは下記のようにする。sizeはお好みで。
font:
  size: 16
  normal:
    family: 'Hack Nerd Font'
    style: Regular
  bold:
    family: 'Hack Nerd Font'
    style: Bold
  italic:
    family: 'Hack Nerd Font'
    style: Italic
  bold_italic:
    family: 'Hack Nerd Font'
    style: Bold Italic

True color 対応

curl -s https://gist.githubusercontent.com/lifepillar/09a44b8cf0f9397465614e622979107f/raw/24-bit-color.sh | bash
  • Alacrittyの設定
env:
  TERM: xterm-256color
  • tmuxの設定(tmux.conf)
# tmuxを256色表示できるようにする(True Color対応)
# このdefault terminalは、shellで設定しているtmuxでの起動時のTERMと一致させる
set -g default-terminal "tmux-256color"
# terminal overridesには、「tmuxの外」でのTERM値を指定する。tmux外ではxterm-256colorなので、それを使って設定。
set -sa terminal-overrides ',xterm-256color:RGB'
  • zshrc
    tmux起動時のみ、TERM変数にセット。
# tmuxで起動された場合にTERMをtmux.confで設定しているdefault terminalにする。
# そうでなければ、xterm-256color
if [[ -n ${TMUX-} ]];then
    export TERM=tmux-256color
fi
  • terminfoのtest。tmux上で下記のように打ってエラーになるかどうかを確認。
export TERM=tmux-256color
reset
  • もしエラーになる場合は、下記のコマンドを打ってterminfoを生成しておく。(下記はOS Xでの設定)
brew install ncurses
/usr/local/opt/ncurses/bin/infocmp tmux-256color > ~/tmux-256color.info
tic -xe tmux-256color tmux-256color.info

Alacrittyのカラースキーム

アイコン付きのlsコマンド

  • lsdコマンドを使うと、lsコマンドの結果にアイコンが付く。
brew install lsd
  • お好みでaliasにlsの代わりとしてセットしておく (.zshrc)
alias ls='lsd'

tmuxの設定

tmux popup window

  • tmux 3.2から使えるようになっている、floating window UIを使う
  • 一時的に作業して戻ってくるのが便利になる。
  • 新たにtmux sessionを立てたり、ワンショットのコマンドを直接打ったり、など色々できるのでkeybindしておくといい。下記は例。(tmux.conf)
# Option + down でpopup/floating windowを開く
bind -n M-down popup -xC -yC -w85% -h90% -d '#{pane_current_path}' -E 'tmux a -t flwin || tmux new -s flwin'

# ワンショットコマンドを打つ例。ここではtigコマンド。
bind Space popup -xC -yC -w85% -h90% -d '#{pane_current_path}' -E 'tig'
  • popupを起動するとこんな感じ。

tmux status line

  • status lineの見直し。plugin入れるとだいたいneovimのstatus lineの表示と似たようなものになるので、ここは自前で作る。
  • 必要な情報を出しつつも、できる限り1windowあたりのwidthを短くして、たくさんwindowを起動しても表示しきれるようにする。
  • 左statusline (tmux.conf)
# Session, Window, Pane番号を略称表記で。
# 各winodwは現在のdirnameを最大15文字までで切る、右statusはいらない、アクティブなwindowを強調表示。
# 頑張ればtmux.confの記法だけで書ける。(シェルのワンライナーを通す必要がない)
# ここでは削っているがhomeはNerdfontの家アイコンもセットしている
home_dir=${HOME}
set -g window-status-format '#I:#{?#{==:#{home_dir},#{pane_current_path}},home,#{b;=|15|..:pane_current_path}}'
set -g window-status-current-format '[#I:#{?#{==:#{home_dir},#{pane_current_path}},home,#{b;=|15|..:pane_current_path}}]'
  • 右status line。prefix押下時の状態, Copyモードなどの表示pluginを入れて #{prefix_highlight} として埋め込む。後はtmux version表示だけにしておく。(tmux.conf)
# prefix押下時の状態を知らせるplugin. copy, sync modeにも対応 => plugin管理にtpmを使用している。
set -g @plugin 'tmux-plugins/tmux-prefix-highlight'

# 右status lineの設定
set-option -g status-right-length 20
set -g status-right "#{prefix_highlight} tmux #{version} "

zsh prompt

  • いつも自前で組んでいたので、テーマpluginを入れずにいつも使っていた自前promptを拡張する。

    • 例えば AWS_PROFILE 変数がセットされていたらそれをpromptに出してあげる、というのはzsh hookを使えば書けるので、それを作る。
    • しかしこの手の機能が入ったplugin入れる人が多い世の中だし、そのほうが良いと自分も思う。フルスクラッチは需要なさそう。
    • 自分がよく使う特定の環境ごとに色を変えるとか、個人でカスタムし尽くせるのは便利なんだが。
  • 自前といってもgit statusについては下記のpluginを利用した。テーマといった包括的なpluginではなく、部品として使えるのは自分のニーズにマッチしていたゆえ。

    • https://github.com/romkatv/gitstatus
    • GITSTATUS_PROMPT 変数を使って自分の好きなprompt設定に組み込もう。 (.zshrcに追加)
    • 私はRight promptにアイコンと | で囲って表示している。 ( __GIT_BRANCH_ICON 変数にはNerdfontのアイコンが入っている。)
export RPROMPT='${GITSTATUS_PROMPT:+${__GIT_BRANCH_ICON}}${GITSTATUS_PROMPT:+|}${GITSTATUS_PROMPT}${GITSTATUS_PROMPT:+|} '

fzf連携

  • 直近のディレクトリ移動履歴からfilterして選択、直近のコマンド実行historyからfilterして選択、など、fuzzy finderでのselectionと実行について。

  • これまではzshのzaw pluginを使っていたが、長い間更新がないのでfzfに置き換える。

  • fzfではpreview機能が使えるので便利。

  • fzf自体のinstallがまだであれば上記のgithubのドキュメントを見てinstallしておく。

  • 実行するとこんな感じ。 tmux環境では前述のpopup windowをfzfのUIとして使える。

  • fzfの設定サンプルでも登場する通り、fd, batコマンドをinstallしておくと良い。OS XならbrewでOK。
brew install fd bat
  • 設定の完成版はこちら。それぞれ説明を後述する。(.zshrc)
# ダブルアンダースコア `__` をprefixしている変数は、fzfが使用する変数ではなくこのスクリプト独自のユーザ変数。

# fzf install時に設定される行
[[ -f ~/.fzf.zsh ]] && source ~/.fzf.zsh

# tmux依存の設定
if [[ -n ${TMUX-} ]];then
    # DEFAULT_OPTSはtmuxの場合には--borderを除くので別にセットする。
    export FZF_DEFAULT_OPTS='--height 70% --layout=reverse'
    export FZF_TMUX_OPTS="-p 80%"
    __FZF_CMD="fzf-tmux"
    __FZF_CMD_OPTS=(
        -p
        80%
    )
else
    __FZF_CMD="fzf"
    __FZF_CMD_OPTS=()
    export FZF_DEFAULT_OPTS='--height 70% --layout=reverse --border'
fi

export FZF_DEFAULT_COMMAND='fd --type f --strip-cwd-prefix'

# fzfのsourceとして使うデフォルトのファイル探索のコマンド
__FZF_FD_FILES_CMD=(
    fd --type f
)
# fzfのsourceとして使うデフォルトのディレクトリ探索のコマンド
__FZF_FD_DIRS_CMD=(
    fd --type d
)
# 隠しファイル/ディレクトリ, link followを行うオプション
__FZF_FD_ALL_OPTS=(
    --hidden --follow --exclude ".git"
)
# fzfのsourceとして使うファイル全探索のコマンド
__FZF_FD_ALL_FILES_CMD=(
    ${__FZF_FD_FILES_CMD[@]}
    ${__FZF_FD_ALL_OPTS[@]}
)
# fzfのsourceとして使うディレクトリ全探索のコマンド
__FZF_FD_ALL_DIRS_CMD=(
    ${__FZF_FD_DIRS_CMD[@]}
    ${__FZF_FD_ALL_OPTS[@]}
)

# previewコマンド (ディレクトリ)
# dirのpreviewで、tree表示やicon表示とcolorを同時に扱うと表示が壊れる。 treeやiconが良ければcolorはneverで。
__FZF_DIR_PREVIEW_CMD=(
    lsd
    --icon=never
    --color=always
    --almost-all
    '{}'
    '|'
    head -n 100
)

# previewコマンド (ファイル)
__FZF_FILE_PREVIEW_CMD=(
    bat
    --style=numbers
    --color=always
    --line-range :100
    '{}'
)

# -------------------------------------------------
# cdrをfzf経由で選択して実行
# -------------------------------------------------
# こちらを参考に https://gist.github.com/siriusjack/0b0032f22c72ffc7e5ba217f80674ad2
function fzf-cdr () {
  local selected_dir=$(cdr -l | awk '{ print $2 }' | ${__FZF_CMD} ${__FZF_CMD_OPTS[@]} --prompt="cdr > " --query "$LBUFFER")
  if [ -n "$selected_dir" ]; then
    local BUFFER="cd ${selected_dir}"
    zle accept-line
  fi
}
zle -N fzf-cdr
# key bindはお好みで
bindkey "^O" fzf-cdr

# -------------------------------------------------
# command historyをfzf経由で選択
# 実行まではしないでカーソル位置をセットする。
# -------------------------------------------------
function select-history() {
  local BUFFER=$(history -n -r 1 | ${__FZF_CMD} ${__FZF_CMD_OPTS[@]} --prompt="cmd history > " --no-sort +m --query "$LBUFFER")
  local CURSOR=$#BUFFER
}
zle -N select-history
# key bindはお好みで
bindkey '^r' select-history

# -------------------------------------------------
# git branchをfzf経由で選択
# -------------------------------------------------
# PREVIEWでgit logを見る。--allの切り替えを可能に。
# こちらを参考に https://zenn.dev/yamo/articles/5c90852c9c64ab
function select-git-switch() {
  local target_br=$(
    git branch |
       ${__FZF_CMD} ${__FZF_CMD_OPTS[@]} --exit-0 --layout=reverse --info=hidden --no-multi \
       --prompt="git branch > " \
       --header=$'Press CTRL-A: --all, Ctrl-D: default\n\n' \
       --bind "ctrl-d:change-prompt(git branch >  )+reload(git branch)" \
       --bind "ctrl-a:change-prompt(git branch --all > )+reload(git branch --all | grep -v 'remotes/origin/HEAD')" \
       --preview-window="right,65%" \
       --preview="echo -e {} | tr -d ' *' | xargs git log --graph --decorate --abbrev-commit --format=format:'%C(blue)%h%C(reset) - %C(green)(%ar)%C(reset)%C(yellow)%d%C(reset)'$'\n'' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)' --color=always" | \
       head -n 1 | \
       perl -pe "s/\s//g; s/\*//g; s/remotes\/origin\///g"
  )
  if [ -n "$target_br" ]; then
    local BUFFER="git switch $target_br"
    zle accept-line
  fi
}
zle -N select-git-switch
# key bindはお好みで
bindkey "^g" select-git-switch

# -------------------------------------------------
# fzf-cd-widget
# -------------------------------------------------
# previewではdir treeを表示
# OS XでALT-Cのkey bind (カレントディレクトリ以下のcd)を機能させる => opt-Cで起動 (もう少しsmartな方法はないのか...)
bindkey "ç" fzf-cd-widget
export FZF_ALT_C_OPTS="--prompt='dirs > ' --no-multi --preview '${__FZF_DIR_PREVIEW_CMD[*]}'
            --header=$'Press CTRL-R: --hidden --follow, Ctrl-D: default\n\n'
            --bind 'ctrl-d:change-prompt(dirs > )+reload(${__FZF_FD_DIRS_CMD[*]} .)'
            --bind 'ctrl-a:change-prompt(all dirs > )+reload(${__FZF_FD_ALL_DIRS_CMD[*]} .)'"
export FZF_ALT_C_COMMAND="${__FZF_FD_DIRS_CMD[*]}"

# Ctrl + tのwidget
export FZF_CTRL_T_OPTS="--prompt='files > ' --preview '${__FZF_FILE_PREVIEW_CMD[*]}'
            --header=$'Press CTRL-R: --hidden --follow, Ctrl-D: default\n\n'
            --bind 'ctrl-d:change-prompt(files > )+reload(${__FZF_FD_FILES_CMD[*]} .)'
            --bind 'ctrl-a:change-prompt(all files > )+reload(${__FZF_FD_ALL_FILES_CMD[*]} .)'"
export FZF_CTRL_T_COMMAND="${__FZF_FD_FILES_CMD[*]}"

# -------------------------------------------------
# **<TAB>の補完
# -------------------------------------------------
__FZF_COMP_TMP_DIR=/tmp/__fzf_comp
[[ ! -d ${__FZF_COMP_TMP_DIR} ]] && mkdir -p ${__FZF_COMP_TMP_DIR}
__CURRENT_SH_PID=$$
__FZF_COMPGEN_PATH_ARG_FILE=${__FZF_COMP_TMP_DIR}/compgen_path.${__CURRENT_SH_PID}
__FZF_COMPGEN_DIR_ARG_FILE=${__FZF_COMP_TMP_DIR}/compgen_dir.${__CURRENT_SH_PID}

# Use fd (https://github.com/sharkdp/fd) instead of the default find
# command for listing path candidates.
# - The first argument to the function ($1) is the base path to start traversal
# - See the source code (completion.{bash,zsh}) for the details.
_fzf_compgen_path() {
  # 下記のように変数に確保しておいて_fzf_comprun()で使いたいが、どうもサブシェルで起動されるのかここでの変数退避が後に反映されない。
  # __FZF_COMPGEN_PATH_ARG="$1"
  # よって、一時ファイルに一旦退避する
  echo "$1" > ${__FZF_COMPGEN_PATH_ARG_FILE}
  ${__FZF_FD_FILES_CMD[@]} . "$1"
}

# Use fd to generate the list for directory completion
_fzf_compgen_dir() {
  echo "$1" > ${__FZF_COMPGEN_DIR_ARG_FILE}
  ${__FZF_FD_DIRS_CMD[@]} . "$1"

}

# (EXPERIMENTAL) Advanced customization of fzf options via _fzf_comprun function
# - The first argument to the function is the name of the command.
# - You should make sure to pass the rest of the arguments to fzf.
_fzf_comprun() {
  local command=$1
  shift

  # コマンドごとにoptionを変える
  case "$command" in
    (cd)
        __FZF_COMPGEN_DIR_ARG=$(<${__FZF_COMPGEN_DIR_ARG_FILE})
        ${__FZF_CMD} "$@" ${__FZF_CMD_OPTS[@]} --no-multi --prompt='dirs > ' --preview "${__FZF_DIR_PREVIEW_CMD[*]}" \
            --header=$'Press CTRL-R: --hidden --follow, Ctrl-D: default\n\n' \
            --bind "ctrl-d:change-prompt(dirs > )+reload(${__FZF_FD_DIRS_CMD[*]} . ${__FZF_COMPGEN_DIR_ARG})" \
            --bind "ctrl-a:change-prompt(all dirs > )+reload(${__FZF_FD_ALL_DIRS_CMD[*]} . ${__FZF_COMPGEN_DIR_ARG})"
        rm ${__FZF_COMPGEN_DIR_ARG_FILE}
        ;;
    (cat|bat|nvim)
        __FZF_COMPGEN_PATH_ARG=$(<${__FZF_COMPGEN_PATH_ARG_FILE})
        ${__FZF_CMD} "$@" ${__FZF_CMD_OPTS[@]} --prompt='files > ' --preview "${__FZF_FILE_PREVIEW_CMD[*]}" \
            --header=$'Press CTRL-R: --hidden --follow, Ctrl-D: default\n\n' \
            --bind "ctrl-d:change-prompt(files > )+reload(${__FZF_FD_FILES_CMD[*]} . ${__FZF_COMPGEN_PATH_ARG})" \
            --bind "ctrl-a:change-prompt(all files > )+reload(${__FZF_FD_ALL_FILES_CMD[*]} . ${__FZF_COMPGEN_PATH_ARG})"
        rm ${__FZF_COMPGEN_PATH_ARG_FILE}
        ;;
    (*)
        ${__FZF_CMD} "$@" ${__FZF_CMD_OPTS[@]}
        ;;
  esac
}


fzf-tmux

  • fzfのUIをtmuxのpopup windowを使ったものにする。
  • tmuxからの起動時にはfzfコマンドの代わりに fzf-tmux -p 80% (大きさはお好みで)を使えばOK。

ディレクトリ移動(cdr)

command実行履歴

  • historyからfzfで選択して実行
  • keybindは好み :Ctrl-rで起動するようにしている。

git branch切り替え

  • 選択中のbranchに対してgit logをpreviewする。
  • デフォルトはremoteブランチを含まない。Ctrl-aでallブランチを対象にする。(Ctrl-dで戻す)
  • keybindは好み: Ctrl-gで起動するようにしている。

fzf Ctrl-T

  • fzfがデフォルトで用意してくれているwidget。
  • カレントディレクトリ以下のファイル・ディレクトリを選択sourceにしてfzfが起動する。
  • これをカスタムして、選択をファイルだけにする。
  • 選択sourceのfdコマンドには、デフォルトはhidden, follow linkをつけず、Ctrl-aでこのオプションを付ける。Ctrl-dで戻す。
  • previewはfzfのドキュメントの通りbatを使ってそのファイル内容をsyntax coloringして表示する。

fzf ALT-C

  • fzfがデフォルトで用意してくれているwidget。
  • カレントディレクトリ以下のディレクトリを選択sourceにしてfzfが起動し、選択結果にcdする。
  • 選択sourceのfdコマンドには、デフォルトはhidden, follow linkをつけず、Ctrl-aでこのオプションを付ける。Ctrl-dで戻す。
  • previewはちょうどlsdコマンドをinstallしていたので、これを使って選択中のディレクトリのlistを表示する。
    • なお、tree表示やアイコンとcolor optionを同時に使うと表示が崩れる。解決できず...

fzf completion連携

  • fzfでのcompletion機能をカスタムする。 デフォルトでは ** と打って<TAB>を押すとfzfが起動するもの。
  • source選択はカレントディレクトリからだけでなく、 ./work/hoge/** のようにpath指定するとそれを認識してsourceを選択してくれる。
  • これをfzfのドキュメントの方法でカスタムする。
  • cdの引数の補完ではdirのみ、cat, bat, 及びnvimの引数の補完の場合はファイルのみ。それぞれ前述と同様のpreviewをつけ、どちらも上述と同様にデフォルトはhidden, follow linkを行わず、Ctrl-a, Ctrl-dで切り替える。
    • このカスタムは一時ファイルを使っていて、頑張らないスタイルからするとちょっとやりすぎたかもしれない...

Discussion