ちなみに

火曜日の空は僕を押しつぶした。

ひさしぶりにzshに戻りました

仕事用のマシンをM1 MacBook Proに交換してもらったので、開発環境を整え直しました。

2年ほど fish を使ってきたのだけれど、普段は良いのだけれど、ちょっと自動化したくなったときに、やはりPOSIX準拠じゃないシェルはなかなか難しかった。macOSの標準も zsh になったことだし、久しぶりに戻ってみることにした。

導入

現代なので XDG Base Directory Specification に乗っかっておくことにする。 Arch LinuxWiki がよくまとまっていて助かるのでこれを参考にして進めた。

zshの場合は ZDOTDIR を指定するといいのだけれど、これをどこで指定するのかという問題がある。zshの起動時に最初に読み込まれるユーザー設定は ~/.zshenv なのだけれど、ここに ZDOTDIR を書くということは .zshenv だけホームディレクトリに残ってしまう。これはちょっと気持ち悪い。

あんまり嬉しくはないのだけれど、諦めて /etc/zshenv に書くことにします。

ZDOTDIR=$HOME/.config/zsh

これで zsh のユーザー毎の設定ファイルは ~/.config/zsh から読まれることになります。(仕事用の macOS だし自分しかユーザーはいないしまあいいでしょ)

次に仕様に沿った環境変数~/.config/zsh/.zshenv 設定していきます。 特にひねることはないので推奨のディレクトリにしておきます。 (GOPATH~/localghq のルートディレクトリを ~/local/src にしているので ~/.local はちょっと気持ち悪くはあります。)

export XDG_CONFIG_HOME="$HOME"/.config
export XDG_CACHE_HOME="$HOME"/.cache
export XDG_DATA_HOME="$HOME"/.local/share
export XDG_STATE_HOME="$HOME"/.local/state

はい、これで基本的な準備は整いました。

zsh は最新のものを使いたいので Homebrew でインストールしておきます。

$ brew install zsh

Zim

zsh をそのまま使うなんてことは出来ないので、設定をごりごり書いていきたい訳ですが、最近はもう自分で設定を書くのは面倒になっています。 もうちょっと若いときは無限に設定ファイルを触っていられたのですが、こうやってどんどん新しいことを学べなくなっていくのです。

設定なしに良い感じになって、かつ Oh My Zsh の遅さに辟易としていたのでとにかく速いのを探しました。

いくつか見て回って Zim が良さそうだったのでこれを導入します。

導入は最近よく見る curl を叩くやつ。

$ curl -fsSL https://raw.githubusercontent.com/zimfw/install/master/install.zsh | zsh

.zshrc にいくつか設定が書き込まれます。よく見ると設定が読み込まれたときにさらに本体のダウンロードを行うようです。

なんとこれだけでまあまあ良い感じに動きます。そして起動が速い!!!

asciiship という SpaceshipStarship を ASCII 文字だけで表現したテーマがデフォルトになっているのですが、もうこれでいいや感があったのでそのままにしています。どんどんこだわりが失われていっている。

他にもテーマがあったり、公式のモジュールもあるので ドキュメント を参照してください。

その他の設定

履歴ファイルの設定だけ .zshenv に書いても効いてくれなかったので以下のように .zshrc に書きました。 zim が上書きしている感じなのですが、設定済なら無視するように書かれているように見えるので多分何かを間違っているだけなはず。しかし調べる元気がない。

export HISTFILE="$XDG_DATA_HOME"/zsh/history

あとは cdr とか AUTO_CD 周りの設定を少し書いた。 AUTO_CD 自体は Zim が有効にしてくれている。

# cdrを有効にして設定する
autoload -Uz chpwd_recent_dirs cdr add-zsh-hook
add-zsh-hook chpwd chpwd_recent_dirs
zstyle ':completion:*:*:cdr:*:*' menu selection
zstyle ':chpwd:*' recent-dirs-max 100
zstyle ':chpwd:*' recent-dirs-default true
zstyle ':chpwd:*' recent-dirs-insert true
zstyle ':chpwd:*' recent-dirs-file "$XDG_DATA_HOME"/zsh/chpwd-recent-dirs

# AUTO_CDの対象に ~ と上位ディレクトリを加える
cdpath=(~ ..)

u というリポジトリのルートディレクトリに移動する関数を書いておく。 これはたしか ujihisa さんの設定から持ってきたのをずっと使っている気がする。 今回は補完関数も書いたけど、そこまで便利じゃないしバグがある。

function u() {
  cd ./"$(git rev-parse --show-cdup)"
  if [ $# = 1 ]; then
    cd "$1"
  fi
}
_u() {
  _values $(fd --type d --base-directory $(git rev-parse --show-cdup))
}
compdef _u u

zsh-abbr

fishで一番良かったのが abbr だったと思うので、これを zsh でも再現させたい。 abbr は alias とは違って、省略形のコマンドを入力すると省略しない形に展開してくれるやつ。 g って入力してスペース打つと git に展開してくれる。 alias を覚えるのがつらくなってきたので展開してくれる方が元のコマンドに紐付けて覚えるので助かる。

zsh で同じ機能を実現するのに zsh-abbr が使えるのでこれを導入した。

zim にはプラグイン機構もあるのでこれを使って導入する。

$DOTDIR/.zimrc に以下の一行を加えて zimfw install というコマンドを打つだけ。

zmodule olets/zsh-abbr

これで導入が完了するので設定していく。abbr コマンドを使っても設定できるが $DOTDIR/abbreviations に書いておくと一括で指定出来る。 僕は以下のようにしている。

abbr b="bundle"
abbr be="bundle exec"
abbr d="docker"
abbr dc="docker compose"
abbr di="git diff"
abbr e="exit"
abbr g="git"
abbr gd="git diff --cached"
abbr ggrep="git grep"
abbr gl="git log"
abbr gr="git restore"
abbr gs="git switch"
abbr l="git log"
abbr p="git pull"
abbr pick="git cherry-pick"
abbr pop="git stash pop"
abbr s="git status -sb"
abbr st="git stash"

fzf

これまではコントリビュートする程度には peco を使ってきたのですが、気分を変えようと fzf に移行しました。 なんか勝手に重いイメージだったけれど、別にそんなことはなかった。 fzf は preview が出せるのが見た目が良くて楽しい。

導入はREADMEに従って Homebrew でインストールした。

$ brew install fzf
$ $(brew --prefix)/opt/fzf/install

補完の設定が生成されるので、それを .zprofile とかで読み込んでおく。

[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh

ここまですると Ctrl-rzsh の履歴検索、 Ctrl-t でカレントディレクトリ以下のファイル検索が fzf 経由で出来るようになる。

モダンなコマンド達と組み合わせるために fzf module を追加しておく。 モダンなコマンドというのは fdbat のことです。

zmodule fzf

$DOTDIR/.zimrc に追加して zimfw install する。

これでファイルの検索が爆速になるし、プレビューがカラフルで見やすくなる。

あとは良い感じのをいくつか定義しておく。

まずはデフォルトの設定を .zshenv に書いておく。peco のデフォルトに慣れているので --layout=reverse だけは外せない。あとはお好みで。

export FZF_DEFAULT_OPTS='--layout=reverse --border --exit-0'

最近はほとんどを VSCode の中で過ごしているので使う機会は限られているのだけれど、必要そうなのを書いておく。

ghqリポジトリを選んで移動するやつ。最低限これさえ出来ればあとはどうでもいい。

fzf-ghq() {
  local repo=$(ghq list | fzf --preview "ghq list --full-path --exact {} | xargs exa -h --long --icons --classify --git --no-permissions --no-user --no-filesize --git-ignore --sort modified --reverse --tree --level 2")
  if [ -n "$repo" ]; then
    repo=$(ghq list --full-path --exact $repo)
    BUFFER="cd ${repo}"
    zle accept-line
  fi
  zle clear-screen
}
zle -N fzf-ghq
bindkey '^[s' fzf-ghq

プルリクを検索しつつそのブランチに switch するやつ。 仕事では JIRA を使っていて、ブランチ名をチケットの番号にしているので目的のブランチを探すのが大変なことがある。 そんな時にはプルリクから検索して切り替えられると便利なので追加した。 gh が必要なのでインストールしておきましょう。 最近 id:shiba_yu36 さんが pecoで似たようなこと をされていた

fzf-pullreq() {
  local pullreq=$(CLICOLOR_FORCE=1 GH_FORCE_TTY=100% gh pr list | tail -n+4 | fzf --ansi --bind "change:reload:CLICOLOR_FORCE=1 GH_FORCE_TTY=100% gh pr list -S {q} | tail -n+4 || true" --disabled --preview "CLICOLOR_FORCE=1 GH_FORCE_TTY=100% gh pr view {1} | bat --color=always --style=grid --file-name a.md")
  if [ -n "$pullreq" ]; then
    pullreq=$(echo $pullreq | awk '{ print $1 }')
    BUFFER="gh pr checkout \"${pullreq}\""
    zle accept-line
  fi
  zle clear-screen
}
zle -N fzf-pullreq
bindkey '^[p' fzf-pullreq

Git のブランチを切り替えるやつ。特におもしろみはない。

# ブランチを選択して switch する
fzf-branch() {
  local format="\
%(color:yellow)%(refname:short)%(color:reset)|\
%(color:bold red)%(objectname:short)%(color:reset) \
%(color:bold green)(%(committerdate:relative))%(color:reset) \
%(color:bold blue)%(authorname)%(color:reset) \
%(color:yellow)%(upstream:track)%(color:reset)|\
%(contents:subject)"

  local branch=$(git for-each-ref --color=always --sort=-committerdate "refs/heads/" --format="$format" | column -ts "|" | fzf --preview "git log --color {1}")
  if [ -n "$branch" ]; then
    branch=$(echo $branch | awk '{ print $1 }')
    BUFFER="git switch ${branch}"
    zle accept-line
  fi
  zle clear-screen
}
zle -N fzf-branch
bindkey '^[b' fzf-branch

前半で設定した cdr のディレクトリを選択して移動できるやつ。

# cdrの履歴からディレクトリを移動する
fzf-cdr(){
    local dir=$(cdr -l | fzf --preview 'f(){ zsh -c "exa -h --long --icons --classify --git --no-permissions --no-user --no-filesize --git-ignore --sort modified --reverse --tree --level 2 $1" }; f {2}')
    if [ -n "$dir" ]; then
        dir=$(echo $dir | awk '{ print $1 }')
        BUFFER="cdr ${dir}"
        zle accept-line
    fi
    zle clear-screen
}
zle -N fzf-cdr
bindkey '^[r' fzf-cdr

しれっと exa を使っているのでこれもインストールしておきましょう。 ls などを exa に読み替えてくれるモジュールがあるのでこれも入れておく。いつも通り .zimrc に追加して zimfw install です。

zmodule exa

ls挙動 をちょっと弄りたかったので以下のようにしています。

alias ls='exa --classify --icons -h --reverse'

これで fzf を最低限は使えるようになりました。


この記事は Cornelius で書きました。