zed でも Emacs を使いたい

# はじめに

zsh と Emacs にどっぷりなので両者を連携させるネタを。
zsh には zed というエディタがあり、メモリ上にロードした関数を編集できたりします。zed の実態はシェル関数で内部的には vared というビルトインコマンドが使われています (いつの間にかシンタックスハイライトされるようになってた)。しかし Emacs 使いとしてはやはり使い慣れた Emacs で編集したいものです。zsh のヘビーユーザであれば関数をいじることも多いと思うので、そのやり方を紹介します。

# 設定
ニッチ過ぎて plugin 化していないので地道に設定します。「zed で」とかタイトルで言ってますが、zed 自体を再定義するのはちょっとお行儀が悪い気がするので新しい関数を作成します。`function-editor`, `variable-editor`, `pipe-editor` という関数を作成し、fpath の通ったディレクトリに放り込みます。

```zsh:function-editor
emulate -L zsh

autoload +X -Uz variable-editor

if (( $# == 0 )); then
    print -u 2 -- "${0:t}: No argument"; return 1
fi

local func="$(functions -- $1)"

case "$func" in
    *'# undefined'*)    func="$(autoload +X $1; functions -- $1)";;
    '')                 func="${(q-)1} () {"$'\n'"}";;
esac

variable-editor func || return $?

if [[ -n "$func" ]]; then
    eval function "$func"
else
    unfunction $1
fi
```

```zsh:variable-editor
emulate -L zsh

autoload +X -Uz pipe-editor

local opts

zparseopts -D -E -a opts -- A a || return $?

[[ "$1" == "--" ]] && shift

if (( $# == 0 )); then
    print -u 2 -- "${0:t}: No argument"; return 1
fi

{
    function __${0:t}-edit-associative-array(){
        __${0:t}-edit-array $1 "assoc"
    }

    function __${0:t}-edit-array(){
        if [[ "$2" == "assoc" ]]; then
            local before="${(PFkv)1}"
            local typeset_opts=( -gA )
        else
            local before="${(PF)1}"
            local typeset_opts=( -ga )
        fi

        local after="$(pipe-editor <<< $before)"

        [[ "$before" == "$after" ]] && return 1

        (( ${(P)+1} )) || typeset $typeset_opts -- $1
        eval "${1}=( ${(fqq)after} )"
    }

    function __${0:t}-edit-scalar(){
        local before="${(P)1}"
        local after="$(pipe-editor <<< $before)"

        [[ "$before" == "$after" ]] && return 1

        (( ${(P)+1} )) || typeset -g -- $1
        eval "${1}=${(qq)after}"
    }

    if (( ${opts[(I)-A]} )); then
        __${0:t}-edit-associative-array $1
    elif (( ${opts[(I)-a]} )); then
        __${0:t}-edit-array $1
    else
        __${0:t}-edit-scalar $1
    fi
} always {
    unfunction -m "__${0:t}-*" 2> /dev/null
}
```

```zsh:pipe-editor
emulate -L zsh

function {
    ${(z)EDITOR:-emacs} $@ > /dev/null
    cat -- ${@[-1]}
} $@ -- =(<&0)
```

ついでに補完関数も作成します。

```zsh:_function-editor
#compdef function-editor
_functions "$@"
```

```zsh:_variable-editor
#compdef variable-editor

local context state state_descr line
local -A opt_args

# オプションの補完は case-sensitive
_arguments -M 'm:{A-Za-z}={A-Za-z}' -s : \
    '(-a)-A[edit associative array]' \
    '(-A)-a[edit array parameter]' \
    '1: :->vars' \
    && return 0

case "$state" in
    vars)
        local pattern='scalar*~*readonly*'

        if [[ -n "${(k)opt_args[-A]}" ]]; then
            pattern='association*~*readonly*'
        elif [[ -n "${(k)opt_args[-a]}" ]]; then
            pattern='array*~*readonly*'
        fi

        _parameters -g $pattern && return 0
        ;;
esac

return 1
```

variable-editor はコマンドラインに入力済のオプションにより補完候補の出し分け (スカラ変数、配列、連想配列) をしてます。
zsh 特有の parameter expansion flags とか array subscript flags とか初心者殺し感がすごいですがここでは説明しません。気になる人は `man zshexpn`, `man zshparam` とかしてください (コマンドラインだと補完が効く!!)。
そして `.zshrc` に

```zsh:.zshrc
export EDITOR="/path/to/emacsclient"
autoload -Uz {function,variable,pipe}-editor
alias fed='function-editor' ved='variable-editor'
```

とかしておけば `fed 関数名` を実行したときに Emacs で編集できるようになります。また `ved 変数名` で変数の値を編集できたりもします。Emacs 以外を使いたい人は `EDITOR` を変えてどうぞ。

# おまけ

編集した関数を元に戻すための設定もしておきます。`reload` 自体はオートロード関数にはしないでおきます (自分自身を reload しないようにすればいいんだろうけど)。

```zsh:.zshrc
function reload(){
    if (( $# == 0 )); then
        print -u 2 -- "${0:t}: No argument"; return 1
    fi

    local -U funcs loadable_funcs

    funcs=( ${(k)functions[(I)(${(j:|:)~@})]} )
    loadable_funcs=( ${^fpath}/(${(~j:|:)funcs})(N-.:t) )

    (( ${#funcs}          )) && unfunction $funcs 2> /dev/null
    (( ${#loadable_funcs} )) && autoload +X $loadable_funcs
}
alias reload='noglob reload' # コマンドラインでパターンをクォートしなくてもよくする
compdef _functions reload
```

`reload 関数名` で編集した関数を元に戻すことができます。関数名の部分は `hoge*` のようなパターンも指定できるので複数関数をまとめて reload できます。

# おわりに

コマンドラインで関数作成するのは手軽ですが、編集機能が貧弱でつらみがあります。この設定をしておけば手軽さはそのままに Emacs の編集機能をすべて使えるため、新しい関数を作成するときなど試行錯誤が捗ります。