書いておかないとあとで分からなくなるのでメモ。
序
zsh 便利ですよね。vcs_info
も便利ですよね。
vcs_info
は名前の通り VCS の情報を出してくれる君です。
zshのターミナルにリポジトリの情報を表示してみる · けんごのお屋敷
本題
最近 zsh のプロンプトを以下のように変更しました。
旧
新
ポイントは左にあった vcs_info
が右に移動していることです。
RPROMPT 問題
vcs_info
を右に移動するには vcs_info
を単純に RPROMPT
に移動させれば良いのでは?と思いますが、 RPROMPT
はコマンド入力と同じ場所に表示されるため、パスやコマンドが長いと消えてしまいます。
なら上に表示すれば良いのではと思って PROMPT
と RPOMPT
を二行にしてみると、今度は表示されなくなってしまいます。
RPOMPT
は PROMPT
が複数行に渡っても最終行と同じ行に表示され、改行ができません。
一行上の右端に表示するには、 precmd
内でプロンプト復帰前に printf
コマンドを用いて表示する必要がありました。
printf 問題
色がない場合、 printf
で右端に表示するには以下のように行なえば良いです。
# プロンプト復帰前に呼ばれる precmd() { # $prompt_right -> 右端に表示する文字列 # $prompt_left -> 左端に表示する文字列 printf "%s%*s" "$prompt_left" "$((${COLUMNS} - ${#prompt_left}))" "$prompt_right" }
%*s
( *
で幅を指定する) で長さに "$((${COLUMNS}-${#prompt_left}))"
(全体の横の長さから $prompt_left
の長さを引いたもの) を指定することによって、右寄せを実現しています。
しかし、これを色付きで行うと崩れてしまいます。
なぜなら、ANSI エスケープシーケンスを含めた時、 $prompt_right
等にエスケープシーケンスが入って文字の長さが長くなり、 printf
の幅空けや ${#prompt_left}
による文字長の取得が狂うためです…!!!!😇
この時、ちゃんとシーケンスの長さを考慮してあげるとうまく表示できるようになります。
# 長さは適当に 10 にしている printf "%s%*s" "$prompt_left" "$((${COLUMNS} - ${#prompt_left} + 10" "$prompt_right"
vcs_info
の action 問題
vcs_info
には actionformats
という機能があり、コンフリクト解決やリベース時などにその action
を表示することが出来ます。
自分は、acrion
の中身は目立つように赤色にしています。なので action
があると、シーケンスの文字数が増えて再び描画が崩れてしまいます。
action
があるかどうかは precmd
内部では分かりません。どうしようかなと調べていると、 vcs_info
にはフック関数を設定できることが分かりました。
これを使って、フック関数の中で action
があるかどうかを見れば、あとは変数にセットすることで precmd
内でも分かります。
最終的に以下の様になりました。
# tput でエスケープシーケンスを出力する tput_normal=$(tput sgr0) tput_yellow=$(tput setaf 3) tput_red=$(tput setaf 1) tput_cyan=$(tput setaf 6) # vcs_info の設定 zstyle ':vcs_info:*' enable git hg zstyle ':vcs_info:*:*' formats "[${tput_yellow}%r: %b${tput_normal}]" zstyle ':vcs_info:*:*' actionformats "[${tput_yellow}%r: %b${tput_normal} (${tput_red}%a${tput_normal})]" # hook 関数の設定 # メッセージが設定される直前 zstyle ':vcs_info:*+set-message:*' hooks vcs_info_hook # VCS が存在しないとき zstyle ':vcs_info:*+no-vcs:*' hooks no_vcs_hook # VCS がないときのエスケープシーケンス長 novcs_color_len=$((${#tput_cyan} + ${#tput_normal})) # VCS があるが action がないとき noaction_color_len=$((${novcs_color_len} + ${#tput_yellow} + ${#tput_normal})) # VCS があり、action もあるとき action_color_len=$((${noaction_color_len} + ${#tput_red} + ${#tput_normal})) # $prompt_color_len にシーケンス長を設定する # VCS があるときの hook function +vi-vcs_info_hook() { # action があるかどうかは hook_com[action] を見れば良い if [ -z ${hook_com[action]} ]; then prompt_color_len=$noaction_color_len else prompt_color_len=$action_color_len fi } # VCS がないときの hook function +vi-no_vcs_hook() { prompt_color_len=$novcs_color_len } precmd() { vcs_info # 左寄せする文字列 prompt_left="${tput_cyan}${USER}@${HOST}${tput_normal}" # 各情報を表示 printf "\n%s%*s\n" "$prompt_left" \ "$((${COLUMNS}-${#prompt_left}+${prompt_color_len}-1))" \ "${vcs_info_msg_0_}" } # このプロンプトは printf 後に表示される (パスの部分) PROMPT='%~ %(!. !root! #.>) '
途中で -1 しているのは、右寄せの最後に一文字空けるためです (wrap っぽく判別されるのを防ぐ)。
これで大体いい感じになりました。
リサイズ問題
ウィンドウのサイズを変更した時、 RPROMPT
では良い感じに右寄せ文字が移動してくれるのですが precmd
で printf
した文字列は再描画されないので残念ながらこのままではズレてしまいます。
ここで、 TRAPWINCH
を用います。
TRAPWINCH
はサイズが変更された時に呼ばれる関数です。しかし、単純にここで printf
すると、プロンプトの下に追加されてしまうので思った通りになりません。
そこで、 printf
していたのを PROMPT
に変数として組み込み、TRAPWINCH
内で zle reset-prompt
することによって PROMPT
ごと再描画するようにします。
# $prompt_header を生成する function generate_promopt_header() { prompt_header=$(printf "\n%s%*s" "$prompt_left" \ "$((${COLUMNS}-${#prompt_left}+${prompt_color_len}-1))" \ "${vcs_info_msg_0_}") } precmd() { vcs_info prompt_left="${tput_cyan}${USER}@${HOST}${tput_normal}" # printf をやめて $prompt_header を生成するのみとする generate_promopt_header } TRAPWINCH() { # 長さが変わったので $prompt_header を再生成 generate_promopt_header # プロンプト再描画 zle reset-prompt } # $prompt_header を改行前に含める PROMPT='${prompt_header} %~ %(!. !root! #.>) '
こうして、幅が変わっても無事に再描画されるようになりました。
残る問題
実はまだ問題が残っていて、ブランチ名等が日本語等のときにカラム幅が合わなくなり、はみ出してしまいます。
wc -c
等を使ってもバイト数だから上手くいかないし、どうしたものか…。
まあ日本語で git ブランチや git 用のディレクトリ作ることはそうそう無さそうなので今回は放置。
全体的な zshrc
はこちらです。(大したことやってない)