koturnの日記

普通の人です.ブログ上のコードはコピペ自由です.

Vim scriptにおける関数のコピーと削除

実用的ではない小ネタを紹介しようと思う. それは,Vim Scriptにおける関数のコピーとリネームである.

Vimでは,function [関数名]というコマンドを実行することで,関数の定義を行番号付きで見ることができる. 従って,これをredirすれば関数定義を文字列として得られるというわけだ. そこから,邪魔な行番号を除去すればよい.

function! s:redir(cmd) abort
  let [verbose, verbosefile] = [&verbose, &verbosefile]
  set verbose=0 verbosefile=
  redir => str
  execute 'silent!' a:cmd
    redir END
  let [&verbose, &verbosefile] = [verbose, verbosefile]
  return str
endfunction

function! s:get_funcdef(funcname, ...) abort
  let list_state = &l:list
  setlocal nolist
  let is_bang = a:0 > 0 ? a:1 : 1
  let funcdef = s:redir('function ' . a:funcname)
  if is_bang
    let funcdef = substitute(funcdef, 'function', 'function!', '')
  endif
  let funcdef = join(map(split(funcdef, "\n"), 'substitute(v:val, "^\\%(\\d\\{3,}\\|[0-9 ]\\{3}\\)", "", "")'), "\n")
  let &l:list = list_state
  return funcdef
endfunction

" これで関数定義(の文字列)が得られる
let s:funcdef = s:get_funcdef('s:get_funcdef')
" 関数定義を表示
function s:funcdef
" 関数を消去しても...
delfunction s:get_funcdef
" 再び定義することが可能
execute s:funcdef

途中の正規表現は,行番号を消去し,元々定義された通りのインデントにするためのものだ.(別にインデントも消去しても良いかもしれない) また,setlocal nolistとオプションをセットしているのは,redirでlistcharsに設定した行末文字ごと取得していたためである.

さて,関数定義を文字列として取得できたのならば,関数のコピーやリネームはたやすい.

function! s:copy_function(funcname, newname) abort
  execute substitute(s:get_funcdef(a:funcname), 'function! \zs\(.\{-}\)\ze(', a:newname, '')
endfunction

function! s:rename_function(funcname, newname) abort
  call s:copy_function(a:funcname, a:newname)
  execute 'delfunction' a:funcname
endfunction

あとは,適当なコマンドを定義し,

command! -bar -nargs=+ -complete=function CopyFunction  call s:copy_function(<f-args>)
command! -bar -nargs=+ -complete=function RenameFunction  call s:rename_function(<f-args>)

以下のようにコマンドを実行すればよいだけだ.

CopyFunction s:copy_function A
RenameFunction s:rename_function B

関数定義内のコメントやインデント等を削除すれば,しょっぱいパフォーマンスの向上ができるかもしれない....