.zshrcを晒す(2019年版)

.zshrcが200行を超えてきたので記事に残したいと思います。
どこかの誰かの役に立てれば幸いです。

実際のファイルはこちらから参照することができます。
[reireias/dotfiles/.zshrc](https://github.com/reireias/dotfiles/blob/master/.zshrc)

.zshrcのブロックごとに解説していきます。

## 全体
vimのfoldを使うために次のコメントでセグメントを囲い、折りたたみの範囲を指定しています。

```zsh
# Comment {{{
settings...
# }}}
```

## Import
このセグメントでは外部ファイルのインポートを記述しています。

```zsh
# INPORTS {{{
source ~/.bashrc
source ~/.zplug/init.zsh
# }}}
```

`~/.bashrc`はbash用の設定ファイルです。ほとんど設定されていませんが。
`~/.zplug/init.zsh`はzplugというプラグインのための設定になります。

## Plugin
このセグメントではzplugに関する設定を記述しています。

```zsh
# PLUGINS {{{
zplug "zsh-users/zsh-syntax-highlighting", defer:2
zplug "b4b4r07/zsh-vimode-visual", defer:3
zplug "zsh-users/zsh-completions"
zplug "b4b4r07/enhancd", use:init.sh
zplug "zsh-users/zsh-autosuggestions"
zplug load
setopt nonomatch
export ZSH_HIGHLIGHT_STYLES[path]='fg=081'
# }}}
```

以下利用しているpluginの解説です。

### zsh-syntax-highlighting
`zsh-users/zsh-syntax-highlighting`はコマンドライン自体の色付けを行うプラグインです。
以下のようなシンタックスハイライトが可能です。

- 存在するコマンドはグリーン
- 存在しないコマンドはレッド
- 存在するパスはシアン
- 存在しないパスはホワイト

![zsh_syntax.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214526/9c336881-59a3-a718-618e-d9296c37059d.png)


### zsh-completions
`zsh-users/zsh-completions`はzshの補完機能をさらに強化するプラグインです。
`docker`や`go`等のサブコマンドを持つコマンドに関しても柔軟に補完してくれます。
例として`go <tab>`と入力した場合の補完候補一覧は次の画像のようになります。
![zsh-completions.png](https://qiita-image-store.s3.amazonaws.com/0/214526/b4e81d83-d042-3c6f-dd46-e1572424a20a.png)

### enhancd
`b4b4r07/enhancd`はその名の通り`cd`コマンドを拡張するプラグインです。
`cd -`で直近に利用したディレクトリ一覧から選択して移動したりできます。
私はオプションで下記のように指定し、候補をpecoで絞り込むように設定しています。

```zsh
# enhancd
export ENHANCD_FILTER=peco
export ENHANCD_DISABLE_DOT=1
export ENHANCD_DISABLE_HOME=1
```

### zsh-autosuggestions
`zsh-users/zsh-autosuggestions`はヒストリーから現在入力中のコマンドを探し、補完を表示してくれるプラグインです。
前方一致の中から最新の履歴を表示してくれるので、簡単な補完はこのプラグインで、詳細に検索したい場合は後述の`peco`を使ったインクリメンタルサーチで検索しています。

補完候補は下記のように薄く表示されます。
![zsh_suggest.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214526/ebd32806-98b8-c998-9fc4-22e3777840d1.png)


## History
コマンド履歴に関する設定のセグメントです。
検索に関する設定は後述のpecoのセグメントで行っています。

```zsh
# HISTORY {{{
# history
HISTFILE=$HOME/.zsh-history
HISTSIZE=100000
SAVEHIST=1000000
```

`HISTFILE`はコマンド履歴の保存先ファイル、`HISTSIZE`はメモリに保持するコマンド履歴数、`SAVEHIST`はファイルに保存するコマンド履歴数になります。大きすぎると動作や検索が遅くなるそうです。

```zsh
# share .zshhistory
setopt inc_append_history
setopt share_history
```
`inc_append_history`はシェルの終了を待たずにファイルにコマンド履歴を保存します。
`share_history`は異なるタブ、ウィンドウ間でのコマンド履歴を共有します。
これらの設定により、tmux等の複数ターミナル環境間でコマンド履歴を共有することができます。

```zsh
# cdr
if [[ -n $(echo ${^fpath}/chpwd_recent_dirs(N)) && -n $(echo ${^fpath}/cdr(N)) ]]; then
    autoload -Uz chpwd_recent_dirs cdr add-zsh-hook
    add-zsh-hook chpwd chpwd_recent_dirs
    zstyle ':completion:*' recent-dirs-insert both
    zstyle ':chpwd:*' recent-dirs-default true
    zstyle ':chpwd:*' recent-dirs-max 1000
    zstyle ':chpwd:*' recent-dirs-file "$HOME/.cache/chpwd-recent-dirs"
fi
# }}}
```
cdrと呼ばれるディレクトリ移動の履歴保存の設定です。
検索については後述のpecoのセグメントで設定しています。

## Color
色の設定に関するセグメントです。
個人的には**補完と一二を争うほど重要**だと思ってます。

```zsh
# COLOR {{{
# LS_COLORS
eval `dircolors -b`
eval `dircolors ${HOME}/.dircolors`

# remove file mark
unsetopt list_types
```
dircolorsは`ls`コマンドや補完時のファイルに種類に応じて色付けを行う設定です。
デフォルトの設定とdotfilesで管理している設定を読み込ませています。

`list_types`はファイル種別の記号を補完候補の末尾に表示します。(`ls -F`での記号と同じ)
邪魔なので無効にしています。

```zsh
# color at completion
autoload colors
zstyle ':completion:*' verbose yes
zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}
```

タブでの補完候補にもシンタックスハイライトが反映される設定です。
これらの設定で`vi <tab>`と入力すると、以下のように候補が表示されます。
![zsh-completion.png](https://qiita-image-store.s3.amazonaws.com/0/214526/d7f560ba-dfd8-3b59-b111-d0b069927865.png)

```zsh
# less
export LESS='-R'

# man
export MANPAGER='less -R'
man() {
    env \
        LESS_TERMCAP_mb=$(printf "\e[1;33m") \
        LESS_TERMCAP_md=$(printf "\e[1;36m") \
        LESS_TERMCAP_me=$(printf "\e[0m") \
        LESS_TERMCAP_se=$(printf "\e[0m") \
        LESS_TERMCAP_so=$(printf "\e[1;44;33m") \
        LESS_TERMCAP_ue=$(printf "\e[0m") \
        LESS_TERMCAP_us=$(printf "\e[1;32m") \
        man "$@"
}
# }}}
```
最後は`less`と`man`の設定です。
`less`は`grep`や`jq`等で色付きの結果を渡すことが多いため、カラー情報を解釈する設定にしています。
`man`に関してもデフォルトの単色/太字混在は見辛いので、適当なシンタックスハイライト設定を施しています。もう少しイケてるシンタックスハイライトにしたいと考えています。

`man grep`での表示例

![man_grep.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214526/55a0dbed-fd88-805a-3036-a4fc143c813d.png)


## Prompt
続いてPromptの設定です。

```zsh
# PROMPT {{{
# prompt
PROMPT=" %{[38;5;154m%}%~%{[0m%}
 %{[38;5;81m%}%(!.#.$)%{[0m%} "
RPROMPT="%{[38;5;013m%}[%m]%{[0m%}"

# vcs_info
autoload -Uz vcs_info
setopt prompt_subst
zstyle ':vcs_info:git:*' check-for-changes true
zstyle ':vcs_info:git:*' stagedstr "%F{yellow}!"
zstyle ':vcs_info:git:*' unstagedstr "%F{red}+"
zstyle ':vcs_info:*' formats "%F{green}%c%u[ %b]%f"
zstyle ':vcs_info:*' actionformats '[%b|%a]'
precmd () { vcs_info }
RPROMPT='${vcs_info_msg_0_} '$RPROMPT
# }}}
```
文字化けしている部分は制御文字です。

`PROMPT`はターミナルの左側に表示される情報と色を設定します。
1行目には現在のディレクトリ、2行目には通常ユーザーの場合は`$`をrootの場合は`#`を表示します。
rootかどうかをひと目で判断できるので必須の設定です。

`RPROMPT`は右側を設定します。
仕事ではsshで様々なサーバーにログインすることが多いため、`%m`でホスト名を表示しています。

`vcs_info`の項目では、zshの機能を利用して、カレントディレクトリのgitのステータスやブランチ名を表示しています。
一部文字化けはnerd fontを利用しているためです。
gitのステータスに応じて下記のように`RPROMPT`の色が変わるように設定しています。

![zsh_vcs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214526/542340b7-fa85-1e49-20d8-0b354f6679c0.png)

## Alias
aliasに関する設定をまとめています。
日々増えていきます。
便利なものはコメントで紹介します。

```zsh
# ALIAS {{{
# general
alias ls='ls -h --color=always'
alias dir='ls --color=auto --format=vertical'
alias vdir='ls --color=auto --format=long'
alias ll='ls -l'        # 多用します
alias la='ls -A'        # 多用します
alias l='ls -CF'
alias du='du -sh *'     # ディレクトリごとにサブディレクトリ含む合計容量を出力します
alias open='gnome-open' # htmlファイルをopenすればブラウザで開くことができます
alias caddy='ruby /opt/caddy/caddy/caddy.rb'
alias terminal='gnome-terminal --hide-menubar'
alias tm='terminal'     # tm ./ で現在いるディレクトリでターミナルを複製できます
alias reload='source ~/.zshrc' # .zshrc編集中は多用してます
alias ssh='TERM=xterm ssh'

# cd
alias ..2='cd ../..'
alias ..3='cd ../../..'
alias ..4='cd ../../../..'

# vim
alias vi='nvim'
alias nvi='nvim'
alias vimdiff='nvim -d'

# dotfiles
alias dot='cd ~/dev/src/github.com/reireias/dotfiles'
alias zshconfig='nvim ~/.zshrc'
alias vimconfig='nvim ~/.vimrc'

# grep
alias g='grep --color=always'
alias grep='grep --color=always'
alias jgrep='grep --include="*.java"'
alias jsgrep='grep --include="*.js"'
alias fgrep='find ./ | grep'
alias ngrep='grep --exclude-dir={node_modules,.nuxt}'

# git
# gitはよく使うのでいろいろ設定しています。
# カラー設定込みでaliasに登録しているものもあります
alias g='git'
alias gb='git branch'
alias gl='git log --pretty=format:"%C(yellow)%h%Creset %C(magenta)%ci%Creset%n%C(cyan)%an <%ae>%Creset%n%B"'
alias glp='git log -p'
alias glg='git log --graph --pretty=format:"%C(yellow)%h%Creset %C(magenta)%ci%Creset%n%C(cyan)%an <%ae>%Creset%n%B"'
alias gco='git checkout'
alias gd='git diff'
alias gdh='git diff HEAD'
alias gds='git diff --stat'
alias gdt='git difftool'
alias gst='git status'
alias gp='git pull'

# docker
alias d='docker'
# デフォルトだと見切れるので、表示項目を変更しています。
alias dps='docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}"'
alias dc='docker-compose'
# }}}
```

aliasはこの記事にいろいろまとめているので、気になる方はどうぞ。
[世の中のエンジニアのalias設定](https://qiita.com/reireias/items/d906ab086c3bc4c22147)

## Key

キー設定に関するセグメントです。
HomeキーやEndキーを多用するのでいろいろと設定しています。
また、`Ctrl + ← | → `で単語単位のカーソル移動になっています。

```zsh
# KEY {{{
# key bind
typeset -A key

if [[ -n "${terminfo}" ]]; then
    key[Home]="${terminfo[khome]}"
    key[End]="${terminfo[kend]}"
    key[Insert]="${terminfo[kich1]}"
    key[Delete]="${terminfo[kdch1]}"
    key[Up]="${terminfo[kcuu1]}"
    key[Down]="${terminfo[kcud1]}"
    key[Left]="${terminfo[kcub1]}"
    key[Right]="${terminfo[kcuf1]}"
    key[PageUp]="${terminfo[kpp]}"
    key[PageDown]="${terminfo[knp]}"

    # setup key accordingly
    [[ -n "${key[Home]}"     ]]  && bindkey  "${key[Home]}"     beginning-of-line
    [[ -n "${key[End]}"      ]]  && bindkey  "${key[End]}"      end-of-line
    [[ -n "${key[Insert]}"   ]]  && bindkey  "${key[Insert]}"   overwrite-mode
    [[ -n "${key[Delete]}"   ]]  && bindkey  "${key[Delete]}"   delete-char
    [[ -n "${key[Up]}"       ]]  && bindkey  "${key[Up]}"       up-line-or-history
    [[ -n "${key[Down]}"     ]]  && bindkey  "${key[Down]}"     down-line-or-history
    [[ -n "${key[Left]}"     ]]  && bindkey  "${key[Left]}"     backward-char
    [[ -n "${key[Right]}"    ]]  && bindkey  "${key[Right]}"    forward-char
    [[ -n "${key[PageUp]}"   ]]  && bindkey  "${key[PageUp]}"   beginning-of-buffer-or-history
    [[ -n "${key[PageDown]}" ]]  && bindkey  "${key[PageDown]}" end-of-buffer-or-history
fi

# word
bindkey "\e[1;5C" forward-word
bindkey "\e[1;5D" backward-word

if (( ${+terminfo[smkx]} )) && (( ${+terminfo[rmkx]} )); then
    function zle-line-init () {
        printf '%s' "${terminfo[smkx]}"
    }
    function zle-line-finish () {
        printf '%s' "${terminfo[rmkx]}"
    }
    zle -N zle-line-init
    zle -N zle-line-finish
fi
# }}}
```

## peco
インタラクティブフィルタリングツール[peco](https://github.com/peco/peco)の設定に関するセグメントです。
pecoは非常に便利なツールですので、個人的にはこれ無しでは生きていけない身体になってしまいました。

```zsh
# PECO {{{
# peco
function peco-history-selection() {
    BUFFER=`history -n 1 | tac  | awk '!a[$0]++' | peco`
    CURSOR=$#BUFFER
    zle reset-prompt
}

zle -N peco-history-selection
bindkey '^R' peco-history-selection
```

コマンド履歴のインクリメンタルサーチを`Ctrl + R`に設定しています。
業務はほぼすべてLinuxで行っているので、履歴検索の使用頻度はzsh機能の中では最多だと思います。

```zsh
function peco-ghq-look () {
    local ghq_roots="$(git config --path --get-all ghq.root)"
    local selected_dir=$(ghq list --full-path | \
        xargs -I{} ls -dl --time-style=+%s {}/.git | sed 's/.*\([0-9]\{10\}\)/\1/' | sort -nr | \
        sed "s,.*\(${ghq_roots/$'\n'/\|}\)/,," | \
        sed 's/\/.git//' | \
        peco --prompt="cd-ghq >" --query "$LBUFFER")
    if [ -n "$selected_dir" ]; then
        BUFFER="cd $(ghq list --full-path | grep --color=never -E "/$selected_dir$")"
        zle accept-line
    fi
}

zle -N peco-ghq-look
bindkey '^G' peco-ghq-look
```

[ghq](https://github.com/motemen/ghq)というリポジトリ管理ツールのリポジトリ一覧を`Ctrl + G`に設定しています。
リポジトリ名でインクリメンタルサーチを行い、選択してEnterキーを押すとそのリポジトリのディレクトリに移動します。

```zsh
function peco-cdr () {
    local selected_dir="$(cdr -l | sed 's/^[0-9]\+ \+//' | peco --prompt="cdr >" --query "$LBUFFER")"
    if [ -n "$selected_dir" ]; then
        BUFFER="cd ${selected_dir}"
        zle accept-line
    fi
}
zle -N peco-cdr
bindkey '^E' peco-cdr
# }}}
```
前述したディレクトリ移動履歴のcdrに関するインクリメンタルサーチです。
`Ctrl + E`に割り当てています。
候補には最近移動したディレクトリが表示され、選択しEnterキーを入力するとそのディレクトリに移動します。

## Completion
補完の設定です。
この辺は各ツールの`README.md`を参考に書いています。

```zsh
# COMPLETION {{{
# pip
function _pip_completion {
  local words cword
  read -Ac words
  read -cn cword
  reply=( $( COMP_WORDS="$words[*]" \
             COMP_CWORD=$(( cword-1 )) \
             PIP_AUTO_COMPLETE=1 $words[1] ) )
}
compctl -K _pip_completion pip3

# npm
if type complete &>/dev/null; then
    _npm_completion () {
        local words cword
        if type _get_comp_words_by_ref &>/dev/null; then
            _get_comp_words_by_ref -n = -n @ -n : -w words -i cword
        else
            cword="$COMP_CWORD"
            words=("${COMP_WORDS[@]}")
        fi

        local si="$IFS"
        IFS=$'\n' COMPREPLY=($(COMP_CWORD="$cword" \
            COMP_LINE="$COMP_LINE" \
            COMP_POINT="$COMP_POINT" \
            npm completion -- "${words[@]}" \
            2>/dev/null)) || return $?
        IFS="$si"
        if type __ltrim_colon_completions &>/dev/null; then
            __ltrim_colon_completions "${words[cword]}"
        fi
    }
    complete -o default -F _npm_completion npm
elif type compdef &>/dev/null; then
    _npm_completion() {
        local si=$IFS
        compadd -- $(COMP_CWORD=$((CURRENT-1)) \
            COMP_LINE=$BUFFER \
            COMP_POINT=0 \
            npm completion -- "${words[@]}" \
            2>/dev/null)
        IFS=$si
    }
    compdef _npm_completion npm
elif type compctl &>/dev/null; then
    _npm_completion () {
        local cword line point words si
        read -Ac words
        read -cn cword
        let cword-=1
        read -l line
        read -ln point
        si="$IFS"
        IFS=$'\n' reply=($(COMP_CWORD="$cword" \
            COMP_LINE="$line" \
            COMP_POINT="$point" \
            npm completion -- "${words[@]}" \
            2>/dev/null)) || return $?
        IFS="$si"
    }
    compctl -K _npm_completion npm
fi
# }}}
```

## Other

その他の設定です。
golangやneovim用の環境変数等を定義しています、

`pr-open`関数は、現在いるディレクトリのブランチに対応するPRが存在すれば、それをブラウザで開いてくれる神自作関数です。
めっちゃ便利です。
`hub`コマンドに依存しています。

```zsh
# OTHER {{{
# pandoc
pandoc_git () {
    pandoc -s --self-contained -t html5 -c ~/.pandoc/github.css $@
}

# neovim
export XDG_CONFIG_HOME=~/.config

# golang
export GOROOT=/usr/lib/go
export GOPATH=$HOME/dev
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

# travis
[ -f /home/fx30079/.travis/travis.sh ] && source /home/fx30079/.travis/travis.sh

# hub
function pr-open {
    local url=$(hub pr list -h $(git symbolic-ref --short HEAD) -f "%U")
    if [ -z "$url" ]; then
        echo "The PR based this branch not found."
        return 1
    fi
    open $url
}
# }}}
```

## Local

環境に依存するような設定は`.zshrc_local`というファイルに記述し、gitでの管理外としています。
`.zshrc_local`が存在すれば読み込みます。

```zsh
# LOCAL {{{
# Load local setting
if [ -e ~/.zshrc_local ]; then
    source ~/.zshrc_local
fi
# }}}
```

## さいごに
よいzshライフを！