Vimスクリプトを書いてみよう

単純なVimスクリプトを徐々にできていく様子を見ることで、その書き方を体験&習得してみよう。

目的

  • 機能を持ったVimスクリプト(プラグイン)を徐々に作る様子を見て、書き方を学習してみよう
  • 作るのはよくあるタイプのプラグイン
    • Javaの単体テスト用クラスファイルを開く(作る)コマンドを作るよ
  • 「ね、簡単でしょう(by ボブ)」と言ってみたい

まずは雛形

ひな形としてこんなスクリプトを作ります。

command! -nargs=0 UtestAppend call <SID>UtestAppend()

function! s:UtestAppend()
  echo 'HERE'
endfunction " s:UtestAppend()
  1. UtestAppendというコマンドを追加
  2. UtestAppendコマンドいが実行されたら ‘HERE’ と表示する

ほら、すごく簡単ですね。

Javaだけに作用させる

以下はdiff形式で変更点だけ。

@@ -1,5 +1,17 @@
 command! -nargs=0 UtestAppend call <SID>UtestAppend()
 
 function! s:UtestAppend()
-  echo 'HERE'
+  let target = s:GetTargetName()
+  if strlen(target) <= 0
+    echomsg 'Not test target file: ' . expand('%')
+    return 0
+  endif
 endfunction " s:UtestAppend()
+
+function! s:GetTargetName()
+  if expand('%:e') ==# 'java'
+    return expand('%')
+  else
+    return ''
+  endif
+endfunction " s:GetTargetName()

バッファがJavaの時だけファイル名を返す関数GetTargetName()を作り、それ以外の時はエラーメッセージを表示して終了するようにしました。

テスト用クラスのファイルを開く

次にテスト用のクラスファイルを決定しバッファとして開いてみます。

@@ -6,6 +6,13 @@
     echomsg 'Not test target file: ' . expand('%')
     return 0
   endif
+
+  let testfile = s:GetTestFileName(target)
+  if strlen(testfile) <= 0 || target ==# testfile
+    echomsg 'Cannot generate test file name: ' . target
+  endif
+
+  call s:OpenFile(testfile)
 endfunction " s:UtestAppend()
 
 function! s:GetTargetName()
@@ -15,3 +22,13 @@
     return ''
   endif
 endfunction " s:GetTargetName()
+
+function! s:GetTestFileName(target)
+  let testfile = a:target
+  let testfile = substitute(testfile, '\.java$', 'Test&', '')
+  return testfile
+endfunction " s:GetTestFileName()
+
+function! s:OpenFile(filepath)
+  execute 'split ' . a:filepath
+endfunction " s:OpenFile()
  1. GetTestFileName関数を作成: クラスのファイル名からテスト用クラスのファイル名を生成する
  2. OpenFile関数を作成: 指定したファイルを開く

テスト用のクラスの名前は私の好みでまずは *Test.java という形式にだけ対応させました。つまりテスト対象クラスがFooBar.javaならばテスト用クラスはFooBarTest.javaとなります。これはあとで拡張します。

これで基本機能は完成です。思ったよりもずっと簡単でしょう。

ディレクトリに対応

最近のJavaはmavenの標準ディレクトリ構成に従っていることも多いので、それに対応してみましょう。

@@ -25,10 +25,16 @@
 
 function! s:GetTestFileName(target)
   let testfile = a:target
+  let testfile = substitute(testfile, '\<src/main/java/', 'src/test/java/', '')
   let testfile = substitute(testfile, '\.java$', 'Test&', '')
   return testfile
 endfunction " s:GetTestFileName()
 
 function! s:OpenFile(filepath)
   execute 'split ' . a:filepath
+
+  let dir = expand('%:h')
+  if !isdirectory(dir)
+    call mkdir(dir, 'p')
+  endif
 endfunction " s:OpenFile()

テスト対象クラスが src/main/java 下にあったらテスト用クラスを src/test/java 下に作るようにし、必要に応じてディレクトリを掘るようにしました。

これは本当に簡単ですね。

Test*.javaにも対応

私の好みで *Test.java だけに対応しましたが、既に Test*.java があるのならそちらを優先して開くようにしましょう。

@@ -16,7 +16,11 @@
 endfunction " s:UtestAppend()
 
 function! s:GetTargetName()
+  let curr = expand('%')
   if expand('%:e') ==# 'java'
+    if curr =~# '\<src/test/java' || curr =~# 'Test.java$' || curr =~# 'Test\k\+.java$'
+      return ''
+    endif
     return expand('%')
   else
     return ''
@@ -26,8 +30,13 @@
 function! s:GetTestFileName(target)
   let testfile = a:target
   let testfile = substitute(testfile, '\<src/main/java/', 'src/test/java/', '')
-  let testfile = substitute(testfile, '\.java$', 'Test&', '')
-  return testfile
+  let testfile1 = substitute(testfile, '\.java$', 'Test&', '')
+  let testfile2 = substitute(testfile, '\k\+.java$', 'Test&', '')
+  if getfsize(testfile2) >= 0
+    return testfile2
+  else
+    return testfile1
+  endif
 endfunction " s:GetTestFileName()
 
 function! s:OpenFile(filepath)

ついでにテスト用クラスのテストは作らないようにしてみました。

最後に全体

最後に完成したファイルの全体を見てみましょう。

command! -nargs=0 UtestAppend call <SID>UtestAppend()

function! s:UtestAppend()
  let target = s:GetTargetName()
  if strlen(target) <= 0
    echomsg 'Not test target file: ' . expand('%')
    return 0
  endif

  let testfile = s:GetTestFileName(target)
  if strlen(testfile) <= 0 || target ==# testfile
    echomsg 'Cannot generate test file name: ' . target
  endif

  call s:OpenFile(testfile)
endfunction " s:UtestAppend()

function! s:GetTargetName()
  let curr = expand('%')
  if expand('%:e') ==# 'java'
    if curr =~# '\<src/test/java' || curr =~# 'Test.java$' || curr =~# 'Test\k\+.java$'
      return ''
    endif
    return expand('%')
  else
    return ''
  endif
endfunction " s:GetTargetName()

function! s:GetTestFileName(target)
  let testfile = a:target
  let testfile = substitute(testfile, '\<src/main/java/', 'src/test/java/', '')
  let testfile1 = substitute(testfile, '\.java$', 'Test&', '')
  let testfile2 = substitute(testfile, '\k\+.java$', 'Test&', '')
  if getfsize(testfile2) >= 0
    return testfile2
  else
    return testfile1
  endif
endfunction " s:GetTestFileName()

function! s:OpenFile(filepath)
  execute 'split ' . a:filepath

  let dir = expand('%:h')
  if !isdirectory(dir)
    call mkdir(dir, 'p')
  endif
endfunction " s:OpenFile()

プラグインとして仕立てる、エラーハンドリングする、などなどの余地はかなりありますが、だいたい動くという意味ではこんな感じで良いのです。

「ね、簡単でしょう?」

(満面のドヤ顔で)

間違い訂正

2012/02/20 09:40

最後のファイル全体がなんかコピペ時にイロイロおかしくなってたみたいです。訂正しました。

2012/02/20 23:15

  • diffが小さくなるようにしました
  • <, >, " といった文字が正しく表示されていないのを訂正しました