はじめに
zsh と Emacs にどっぷりなので両者を連携させるネタを。
zsh には zed というエディタがあり、メモリ上にロードした関数を編集できたりします。zed の実態はシェル関数で内部的には vared というビルトインコマンドが使われています (いつの間にかシンタックスハイライトされるようになってた)。しかし Emacs 使いとしてはやはり使い慣れた Emacs で編集したいものです。zsh のヘビーユーザであれば関数をいじることも多いと思うので、そのやり方を紹介します。
設定
ニッチ過ぎて plugin 化していないので地道に設定します。「zed で」とかタイトルで言ってますが、zed 自体を再定義するのはちょっとお行儀が悪い気がするので新しい関数を作成します。function-editor
, variable-editor
, pipe-editor
という関数を作成し、fpath の通ったディレクトリに放り込みます。
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
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
}
emulate -L zsh
function {
${(z)EDITOR:-emacs} $@ > /dev/null
cat -- ${@[-1]}
} $@ -- =(<&0)
ついでに補完関数も作成します。
#compdef function-editor
_functions "$@"
#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
に
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 しないようにすればいいんだろうけど)。
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 の編集機能をすべて使えるため、新しい関数を作成するときなど試行錯誤が捗ります。