スクリプトローカルな関数を手軽に呼び出す

この記事は Vim Advent Calendar 2011 の 28 日目の代打です。
急遽代打をやることになった*1けどネタが浮かばなかったので、適当に vimrc の中から便利かもしれない関数を晒すことにします。

スクリプトローカルな関数を呼び出したい

Vim script を書いてると、スクリプトローカルな関数をコマンドラインモードから直接呼び出して挙動を確認したくなること、ないですか?

まあそれなりにシチュエーションはあるかと思います。あることにします。
Vim script ではがんばればスクリプトローカルな関数を呼び出せますが、正直いちいちがんばりたくないです。

スクリプトローカルな関数を呼び出す関数

私の vimrc から抜粋した以下の関数を vimrc に書いておきます。

" Call a script local function.
" Usage:
" - S('local_func')
"   -> call s:local_func() in current file.
" - S('plugin/hoge.vim:local_func', 'string', 10)
"   -> call s:local_func('string', 10) in *plugin/hoge.vim.
" - S('plugin/hoge:local_func("string", 10)')
"   -> call s:local_func("string", 10) in *plugin/hoge(.vim)?.
function! S(f, ...)
  let [file, func] =a:f =~# ':' ?  split(a:f, ':') : [expand('%:p'), a:f]
  let fname = matchstr(func, '^\w*')

  " Get sourced scripts.
  redir =>slist
  silent scriptnames
  redir END

  let filepat = '\V' . substitute(file, '\\', '/', 'g') . '\v%(\.vim)?$'
  for s in split(slist, "\n")
    let p = matchlist(s, '^\s*\(\d\+\):\s*\(.*\)$')
    if empty(p)
      continue
    endif
    let [nr, sfile] = p[1 : 2]
    let sfile = fnamemodify(sfile, ':p:gs?\\?/?')
    if sfile =~# filepat &&
    \    exists(printf("*\<SNR>%d_%s", nr, fname))
      let cfunc = printf("\<SNR>%d_%s", nr, func)
      break
    endif
  endfor

  if !exists('nr')
    echoerr 'Not sourced: ' . file
    return
  elseif !exists('cfunc')
    let file = fnamemodify(file, ':p')
    echoerr printf(
    \    'File found, but function is not defined: %s: %s()', file, fname)
    return
  endif

  return 0 <= match(func, '^\w*\s*(.*)\s*$')
  \      ? eval(cfunc) : call(cfunc, a:000)
endfunction

追記: 一部外部の関数に依存していたのを修正
追記: scriptnames に silent し忘れてたのを修正

使い方

コメントにある通りですが、まず、

call S('local_func')

これで、現在編集しているバッファで定義されている s:local_func() を引数なしで呼び出します。
引数を与えたい場合は素直にこう。

call S('local_func', 1, 2, 3)

こういうことも可能です。

call S('local_func(1, 2, 3)')

ただし、この場合 eval() を使うため変数は渡せないので注意。
特定の名前のスクリプトのローカル関数を呼び出したい場合はこう。

call S('plugin/my_plugin.vim:local_func', 1, 2, 3)

この場合、"plugin/my_plugin.vim" で終わるスクリプトを探して、最初に見付かったもののローカル関数を呼び出します。
最後の .vim は省略可能です。

call S('plugin/my_plugin:local_func', 1, 2, 3)

以上

これでスクリプトローカル関数を呼び出し放題ですね!
私は実際にこれを使って vimrc 内で S('twitvim:post_twitter', message) みたいな感じで使ってます。


明日は @ さんです。

*1:と思ったらすでに kaoriya さんが代打してたけど気にしてはいけない