zsh で自分で作っている補完関数を簡単に再読み込みする

## 追記
ここで紹介した機能を持つ独立したプラグインを作成しました。

[zsh で補完関数を作るときに便利な再読み込みするプラグインを作った - Qiita](http://qiita.com/mollifier/items/062be21d0d48f724d311)

本格的に使うならこちらをおすすめします。

## はじめに
自分で zsh の補完関数とかを作る時に便利な関数を作ったので紹介する。

## これは何?

zsh の補完関数を作ったとき、当然それを呼び出して確認してってする。でもその準備がけっこうめんどくさい。

まず、その補完関数を fpath の通ったところに置かないといけない。
例えばカレントディレクトリの中に \_my-command という補完関数を作っているとして

```zsh
% ls
_my-command
```

fpath を設定する必要がある。

```zsh
% fpath=($PWD $fpath)
```

その上これだけではダメで、fpath を変えた後は `compinit` を改めて呼ばないといけない。

```zsh
% compinit
```

これでとりあえず補完関数が動くようになる。

で、何回も修正して確認して、というのを繰り返すことになるんだけど、実は補完ファイルは単に中身を編集しただけではすぐには反映されない。

中身を書き直したら、

```zsh
% unfunction _my-command
% autoload -Uz +X _my-command
```

こんな感じで再読み込みしないといけない。めんどくさい。

なので、こういう初期化と再読み込みを簡単にする zsh の関数を作った。これを `.zshrc` に書いておけばOK。

```zsh:.zshrc
# helper function to autoload
# Example 1 : zload ~/work/function/_f
# Example 2 : zload *
function zload {
    if [[ "${#}" -le 0 ]]; then
        echo "Usage: $0 PATH..."
        echo 'Load specified files as an autoloading function'
        return 1
    fi

    local file function_path function_name
    for file in "$@"; do
        if [[ -z "$file" ]]; then
            continue
        fi

        function_path="${file:h}"
        function_name="${file:t}"

        if (( $+functions[$function_name] )) ; then
            # "function_name" is defined
            unfunction "$function_name"
        fi
        FPATH="$function_path" autoload -Uz +X "$function_name"

        if [[ "$function_name" == _* ]]; then
            # "function_name" is a completion script

            # fpath requires absolute path
            # convert relative path to absolute path with :a modifier
            fpath=("${function_path:a}" $fpath) compinit
        fi
    done
}
```

## 使い方

`zload` 関数に読み込みたいファイルを引数で指定して使う。

```zsh
% zload ~/work/function/_f
```

引数で指定するファイルの形式は zsh の補完ファイル、autoload して使うプラグインのどちらでもOK。どっちでも読み込める。

読み込むファイルは複数指定できる。

```zsh
% zload *
```

### 例

例えば my-command という autoload して使う zsh のプラグインを作っているとする。あと、 \_my-command という名前でそいつの補完ファイルも作ってるとしよう。

```zsh
% ls
my-command _my-command
# カレントディレクトリの中に自作プラグインの本体と補完ファイルがある
```

こんな感じで読み込んで使う。

```zsh
% my-command
zsh: command not found: my-command
# 最初は当然呼び出せない

# zload を使うと...
% zload my-command _my-command
% my-command
...
# autoload したので my-command が呼び出せる

% my-command [TAB]
... 
# 補完ファイルも読み込めてる
```

編集したときはもう一度 zload し直せば反映される。

```zsh
% vim my-command _my-command
# 何か編集

# 再読み込み
% zload my-command _my-command
```

## さらに便利にする

編集した後の再読み込みも面倒と思う人もいるかもしれない。僕もそう思う。そんなときはこれも追加で `.zshrc` に書いておくといい。

```zsh:.zshrc
# helper function to reload autoloading functions which are already defined
# Example : zreload /path/to/file
function zreload() {
    if [[ "${#}" -le 0 ]]; then
        echo "Usage: $0 FILE..."
        echo 'Reload specified files as an autoloading function'
        return 1
    fi

    local file function_path function_name
    for file in "$@"; do
        if [[ -z "$file" ]]; then
            continue
        fi

        function_path="${file:h}"
        function_name="${file:t}"

        if (( $+functions[$function_name] )) ; then
            # "function_name" is defined
            unfunction "$function_name"
            FPATH="$function_path" autoload -Uz +X "$function_name"
        fi
    done
}

# この行は .zshrc の他の箇所で書いている場合は改めて書かなくて良い
autoload -Uz add-zsh-hook

function _auto_reload_hook {
    # reload functions in current directory automatically
    [[ -n "$AUTO_RELOAD_TARGET_DIRECTORY" ]] \
    && [[ "$AUTO_RELOAD_TARGET_DIRECTORY" == "$PWD" ]] \
    && zreload $PWD/*(N.,@)
}
add-zsh-hook preexec _auto_reload_hook

function enable_auto_reload {
    AUTO_RELOAD_TARGET_DIRECTORY=$PWD
}

function disable_auto_reload {
    AUTO_RELOAD_TARGET_DIRECTORY=""
}
```

これで1回 zload した後は自動的に関数を再読み込みするようになる。

ただし毎回勝手に再読込するのはちょっとやり過ぎと思ったので、有効/無効を切り替えできるようにしてる(初期状態では無効)。


```zsh
# まずは読み込みたいファイルが置いてある場所に移動する
% cd path/to/dir

# 最初の zload は手動で呼び出す
% zload my-command _my-command

# 自動ロードを有効にする
% enable_auto_reload

% vim _my-command
# ... 編集する
# ... 勝手にリロードされる

# 自動ロードを無効にする
% disable_auto_reload
```

自動ロードしたい場所に移動してから `enable_auto_reload` を呼び出すと、それ以降そのディレクトリにあるファイルが勝手に再読み込みされるようになる。元に戻したい場合は `disable_auto_reload` を呼び出す。

これで関数を書くのがスムーズになると思うので試してみてください!