ユーザ定義コマンドのスコープ

問題: 以下のVim scriptを実行すると何が表示されるか。

command! -nargs=+ MyEcho echo <args>
let g:var = 'global'
function s:f()
  let l:var = 'local'
  MyEcho var
endfunction
call s:f()

答え: "local"
そう。ユーザ定義コマンドは関数内で実行された場合そのローカル変数にアクセスできる。
では、以下の2つのVim scriptがあった場合、b.vim を実行すると何が表示されるか。

" a.vim
let s:var = 'A'
command! -nargs=+ MyEcho echo <args>
" b.vim
source a.vim
let s:var = 'B'
MyEcho s:var

答え: "A"
スクリプトローカルな変数に関しては、コマンドが定義されたスコープが参照される。
一見不可解な気もするが、通常はこれは便利な挙動だ。

function s:command()
  echo 'hello'
endfunction
command! Command call s:command()

これはどのスコープにいても、コマンドラインからでも正常に動作する。


で、大抵の場合は便利なんだけど、場合によっては制約になりうる。具体例としては prettyprint.vim がそうだ。
prettyprint.vim の :PP コマンドは渡された式の内容を表示するが、この制約によってスクリプトローカルな変数や関数を含む式は受け付けない。
これはどうしようもないので、helpに注意書きをすることにした。ここに書いた通り、この場合は代わりに PP() 関数を使えば表示できる。
この一見気持ち悪いスコープの問題は、単にVimスクリプトローカル変数も実行時のスコープを見るようにしてくれればいいとかそういうことではなくて、てかそれをやられると現状の多くのプラグインが詰むので、まあどうしようもないのかなぁって感じだ。
今回の例だとプラグインの用途自体が限定的だし、PP() と言う逃げ道もあったのでまだいいけど、どうしようもなく困ってしまった例ってのもある。その話はまた今度。