読者です 読者をやめる 読者になる 読者になる

Re: vimでスクリプト内関数を書き換える

vim

そう簡単に行かないのが Vim の恐ろしいところ。

Big Sky :: vimでスクリプト内関数を書き換える

とんでもない落とし穴があります。
書き換えた関数でスクリプトローカルな変数にアクセスしようとすると、関数の ID に関係なく関数を定義したファイルのスクリプトローカルな変数を見てしまいます。

a.vim

let s:value1 = 'a.vim:value1'
let s:value2 = 'a.vim:value2'

function! s:get_value()
  return s:value1
endfunction

" A wrapper to call from other scope.
function! GetValue()
  return s:get_value()
endfunction

b.vim

source hookfunc.vim
source a.vim

let s:value1 = 'b.vim:value1'
let s:value2 = 'b.vim:value2'

function! s:get_value()
  return s:value2
endfunction

echo GetValue()
" => a.vim:value1

call HookFunc(GetFunc(fnamemodify("a.vim", ":p"), "get_value"), GetFunc(expand("%:p"), "get_value"))

echo GetValue()
" => b.vim:value2  (not "a.vim:value2")

これではスクリプトローカルな変数を参照する関数は外から書き換えられない。非常に残念。
もっと簡単に書き換えようと以下のような物まで書いてたのに(書いてる途中でこの事実に気付いた)。

" hookfunc.vim
command! -nargs=+ Hookfunc execute s:hookfunc(<q-args>)

function! s:get_script_id(fname)
  let snlist = ''
  redir => snlist
  silent! scriptnames
  redir END

  for line in split(tolower(substitute(snlist, '\\', '/', 'g')), "\n")
    if line =~? '\V' . a:fname . '\$'
      return matchstr(line, '^\s*\zs\d\+')
    endif
  endfor
endfunction

function! s:hookfunc(args)
  let [file, func] = split(a:args, ':')
  return 'function! <SNR>' . s:get_script_id(file) . '_' . func
endfunction


" ------------------------------------------------------------------


function! s:hoge(text)
  echo "Hello, " . a:text
endfunction

call s:hoge('world.')
" => Hello, world.

Hookfunc hookfunc.vim:hoge(text)
  echo "Goodbye, " . a:text
endfunction


call s:hoge('world.')
" => Goodbye, world.

Vim の深淵は深すぎて覗き込もうとすると思わぬ怪我をするので気を付けましょう。