概要
あなたのキーボードは光りますか?
もちろん光りますよね。その方がかっこいいからです。
ではあなたのコマンドプロンプトは光りますか?
もし光らないのであれば、この記事を読んで光らせてみませんか?
以下では、chameleonという七色に輝くコマンドプロンプトを開発するまでの道のりをまとめていきます。
対象読者
- コマンドプロンプトの自作に興味があって、手ごろな実装例を探している方
- コマンドプロンプトを輝かせたい方
開発までの道のり
最小構成
ボトムアップに作り始めましょう。コマンドプロンプトを設定するために必要なファイルは、prompt_chameleon_setupというファイルのみです(chameleon
の部分は任意の名前だから適宜読み替えてください)。実際はchameleon.zshのようなファイルを用意してシンボリックリンクを張ることが多いようです1。
prompt_chameleon_setup() {
typeset -g prompt_chameleon_color='#FF0000' # 赤!
PROMPT='%F{$prompt_chameleon_color}❯ %f' # プロンプトの文字や色の指定
prompt_opts=(percent subst) # これがないと上記のPROMPTが展開されず文字通り解釈される
}
prompt_chameleon_setup "$@"
シンボリックリンクも作成すると、このcommitの状態になります。このディレクトリで以下を実行すると、chameleonが有効化され赤い❯
のプロンプトが表示されます。ひとまず動きました。
fpath+=($(pwd))
autoload -U promptinit; promptinit
prompt chameleon
再描画
さて、色を徐々に変えるにはどうしたらよいでしょうか?コマンドを実行する度に色を変えるなら、precmdというhookを使うことができます。ただし今回はコマンド入力中も徐々に色を変えたいから不採用です。
TMOUTを利用するハックなら、この条件をクリアできます。ただ、更新間隔を1秒より短くできないという問題があり微妙です2。
最終的に今回の要件に合致したのは、zsh-asyncというライブラリでした。.zshrcに以下を追記すると、まだの場合にインストールしてくれます。
if ! [[ -d ~/.zsh/zsh-async ]]; then
git clone https://github.com/mafredri/zsh-async.git "$HOME/.zsh/zsh-async"
fi
source $HOME/.zsh/zsh-async/async.zsh
async_init
async_start_worker
async_register_callback
async_job
などが使えるようになるので、先ほどのスクリプトに追記をしましょう。これで0.5秒おきにプロンプトが再描画されるようになります。
prompt_chameleon_gradation() {
prompt_chameleon_color='#000000' # 黒!
}
prompt_chameleon_refresh() {
prompt_chameleon_gradation
zle reset-prompt # 再描画
async_job chameleon sleep $prompt_chameleon_interval # 0.5秒待つだけのjobを再度積む
}
prompt_chameleon_setup() {
typeset -g prompt_chameleon_color='#FF0000'
PROMPT='%F{$prompt_chameleon_color}❯ %f'
prompt_opts=(percent subst)
typeset -g prompt_chameleon_interval=0.5
async_init # 初期化
async_start_worker chameleon # cameleonというworkerを用意
async_register_callback chameleon prompt_chameleon_refresh # job実行の度にprompt_chameleon_refresh
async_job chameleon sleep $prompt_chameleon_interval # 0.5秒待つだけのjobを積む
}
prompt_chameleon_setup "$@"
ここまで作業するとこのcommitの状態になります。先ほどと同様の方法でcameleonを有効化すると一瞬赤いプロンプトが表示されてすぐに黒くなることが確認できると思います。
グラデーション
先ほどは赤いプロンプトを黒く変えるだけでしたが、本来の目的は七色に輝かせることでした。そのためのprompt_chameleon_gradationは以下です。先ほどの実装を置き換えてください。
2023/06/06 @ko1nksm さんのコメントを参考に修正
prompt_chameleon_gradation() {
local temp=$prompt_chameleon_color
local -A rgb
rgb=(
r 0x${temp:1:2}
g 0x${temp:3:2}
b 0x${temp:5:2}
)
local increment=30
if (( rgb[r] == 255 )) && (( rgb[g] < 255 )) && (( rgb[b] == 0 )); then
# red -> yellow
rgb[g]=$((rgb[g] + increment))
rgb[g]=$((rgb[g] < 255 ? rgb[g] : 255))
elif (( rgb[r] > 0 )) && (( rgb[g] == 255 )) && (( rgb[b] == 0 )); then
# yellow -> green
rgb[r]=$((rgb[r] - increment))
rgb[r]=$((rgb[r] < 0 ? 0 : rgb[r]))
elif (( rgb[r] == 0 )) && (( rgb[g] == 255 )) && (( rgb[b] < 255 )); then
# yellow -> aqua
rgb[b]=$((rgb[b] + increment))
rgb[b]=$((rgb[b] < 255 ? rgb[b] : 255))
elif (( rgb[r] == 0 )) && (( rgb[g] > 0 )) && (( rgb[b] == 255 )); then
# aqua -> blue
rgb[g]=$((rgb[g] - increment))
rgb[g]=$((rgb[g] < 0 ? 0 : rgb[g]))
elif (( rgb[r] < 255 )) && (( rgb[g] == 0 )) && (( rgb[b] == 255 )); then
# blue -> purple
rgb[r]=$((rgb[r] + increment))
rgb[r]=$((rgb[r] < 255 ? rgb[r] : 255))
elif (( rgb[r] == 255 )) && (( rgb[g] == 0 )) && (( rgb[b] > 0 )); then
# purple -> red
rgb[b]=$((rgb[b] - increment))
rgb[b]=$((rgb[b] < 0 ? 0 : rgb[b]))
fi
for key in ${(k)rgb}; do
printf -v "rgb[$key]" "%02x" $rgb[$key]
done
prompt_chameleon_color='#'${rgb[r]}${rgb[g]}${rgb[b]}
}
この状態のcommitがこれです。ようやく七色に輝きました。
仕上げ
あとは何かエラーが生じた場合にworkerを再起動する設定を足したり、zsh-asyncが未インストールの場合の対応を追記したら完成です。これについてはmainブランチの実装を見てください。
最後に
ちゃんと実用的にするならカレントディレクトリやGitの情報も表示するべきですが、今回は自由研究程度のつもりだったのでそこまではやりませんでした。とはいえせっかく実装したので、興味があればREADMEを見てインストールして遊んでみてください。
いつか時間があるときに、最強の輝くコマンドプロンプトを作ってみたいものですね。