Gitのブランチ名をzshのプロンプトに表示させているのですが、別端末でブランチを切り替えたりすると、普通は現状が反映されません。心配だったらenterキーを押せば良いのですが、端末がたくさん開いてるときに限って間違ったブランチで作業してしまいそうになるので、これを防ぐために1分に1度プロンプトを更新するようにしました。ついでにプロンプトの時刻も更新されます。
関係ありそうな .zshrc の部分を切り出すと、こんな感じ。補完候補(menu select)表示中に更新が起こると候補が消えてしまうという課題がありますが…
zmodload zsh/datetime # $EPOCHSECONDS, strftime等を利用可能に #gitブランチ名表示 autoload -Uz vcs_info zstyle ':vcs_info:*' enable git zstyle ':vcs_info:git:*' formats '%c%u%b' zstyle ':vcs_info:git:*' actionformats '%c%u%b|%a' #git情報更新 update_vcs_info() { psvar=() LANG=en_US.UTF-8 vcs_info [[ -n "$vcs_info_msg_0_" ]] && psvar[1]="$vcs_info_msg_0_" } #プロンプトを毎分更新 reset_tmout() { TMOUT=$[60-EPOCHSECONDS%60] } precmd_functions=($precmd_functions update_vcs_info reset_tmout) redraw_tmout() { zle reset-prompt; reset_tmout } TRAPALRM() { update_vcs_info; redraw_tmout } #プロンプト unsetopt promptcr # 改行のない出力をプロンプトで上書きするのを防ぐ setopt PROMPT_SUBST PROMPT="%F{green}[%m-%T]%f%# " RPROMPT="%(?..%F{red}-%?-)%F{green}[%1(v|%F{yellow}%1v%F{green} |)%n:%~]%f" [[ -n "$SSH_CLIENT" ]] && PROMPT="%F{green}[%F{cyan}%B%m%b%F{green}-%T]%f%# "
追記
menuselectが消えてしまう件は、以下のように$WIDGET (直前に入力したキーのアクション) と$_lastcomp[insert] (最後に表示した補完の情報)の値を調べて、menu selectが表示されていると思われる場合にはresetを止めることで回避できそうです。なお該当する $WIDGET の値はキーバインドによって変わる可能性があります。自分の環境で何にすべきかは bindkey "^I" でTabキーに割り当てられているアクションを調べれば分かります。
ただし、古いzsh(5.1未満)でTRAPALRMの中で$WIDGETを評価しようとするとSEGVで死んでしまうバグがあるようで、そのような環境では諦めるしかありません。。
autoload -U is-at-least precmd_functions=($precmd_functions reset_tmout reset_lastcomp) reset_lastcomp() { _lastcomp=() } if is-at-least 5.1; then # avoid menuselect to be cleared by reset-prompt redraw_tmout() { [ "$WIDGET" = "expand-or-complete" ] && [[ "$_lastcomp[insert]" =~ "^automenu$|^menu:" ]] || zle reset-prompt reset_tmout } else # evaluating $WIDGET in TMOUT may crash :( redraw_tmout() { zle reset-prompt; reset_tmout } fi TRAPALRM() { check_gitinfo_update; redraw_tmout } ...
追記ここまで
なお変更の有無を表示する check-for-changes は巨大レポジトリだとあからさまに遅くなるので入れてません。
しかし、環境によって vcs_info が遅いことがあるので、ディレクトリやgit HEADが移動された時以外は実行しないようにし、さらにそのチェックも別のプロセスを立ち上げたりすることなくzshのプロセス内で処理が完結するようにした版が以下。
やってることは、precmdでコマンドを記録しておいて、gitが実行されたりディレクトリ移動が起きたらvcs_info、それ以外でも、各コマンド実行後と1分ごとに.git/HEADの更新日時を見て、前回のチェック時刻から更新が起きていたらvcs_info、という感じです。更新日時のチェックはextended_globの更新日時を元にマッチさせる機能を使っているので、外部コマンドの呼び出したりはしない(つまりforkしない)ため高速なはず。
zmodload zsh/datetime # $EPOCHSECONDS, strftime等を利用可能に autoload -U is-at-least #gitブランチ名表示 autoload -Uz vcs_info zstyle ':vcs_info:*' enable git zstyle ':vcs_info:git:*' formats '%c%u%b' zstyle ':vcs_info:git:*' actionformats '%c%u%b|%a' #プロンプトを毎分更新 reset_tmout() { TMOUT=$[60-EPOCHSECONDS%60] } precmd_functions=($precmd_functions reset_tmout reset_lastcomp) reset_lastcomp() { _lastcomp=() } if is-at-least 5.1; then # avoid menuselect to be cleared by reset-prompt redraw_tmout() { [ "$WIDGET" = "expand-or-complete" ] && [[ "$_lastcomp[insert]" =~ "^automenu$|^menu:" ]] || zle reset-prompt reset_tmout } else # evaluating $WIDGET in TMOUT may crash :( redraw_tmout() { zle reset-prompt; reset_tmout } fi TRAPALRM() { check_gitinfo_update; redraw_tmout } #プロンプト unsetopt promptcr # 改行のない出力をプロンプトで上書きするのを防ぐ setopt PROMPT_SUBST PROMPT="%F{green}[%m-%T]%f%# " RPROMPT="%(?..%F{red}-%?-)%F{green}[%1(v|%F{yellow}%1v%F{green} |)%n:%~]%f" [[ -n "$SSH_CLIENT" ]] && PROMPT="%F{green}[%F{cyan}%B%m%b%F{green}-%T]%f%# " #カレントディレクトリ/コマンド記録 local _cmd='' local _lastdir='' preexec_gitupdate() { _cmd="$1" _lastdir="$PWD" } preexec_functions=($preexec_functions preexec_gitupdate) #git情報更新 update_vcs_info() { psvar=() LANG=en_US.UTF-8 vcs_info [[ -n "$vcs_info_msg_0_" ]] && psvar[1]="$vcs_info_msg_0_" } #同一dir内でシェル外でgitのHEADが更新されていたら情報更新 check_gitinfo_update() { if [ -n "$_git_info_dir" -a -n "$_git_info_check_date" ]; then if [ -f "$_git_info_dir"/HEAD(ms-$((EPOCHSECONDS-$_git_info_check_date))) ]; then _git_info_check_date=$EPOCHSECONDS update_vcs_info fi 2>/dev/null fi } #カレントディレクトリ変更時/git関連コマンド実行時に情報更新 precmd_gitupdate() { local _r=$? local _git_used=0 case "${_cmd}" in git*|stg*) _git_used=1 esac if [ $_git_used = 1 -o "${_lastdir}" != "$PWD" ]; then local cwd="./" _git_info_dir= _git_info_check_date= while [ "$(echo $cwd(:a))" != / ]; do if [ -f .git/HEAD ]; then _git_info_dir="$PWD/.git" _git_info_check_date=$EPOCHSECONDS break fi cwd="../$cwd" done update_vcs_info else check_gitinfo_update fi return $_r } precmd_functions=($precmd_functions precmd_gitupdate)