ブログ
Vimスクリプトを書いてみよう
目的
- 機能を持ったVimスクリプト(プラグイン)を徐々に作る様子を見て、書き方を学習してみよう
- 作るのはよくあるタイプのプラグイン
- Javaの単体テスト用クラスファイルを開く(作る)コマンドを作るよ
- 「ね、簡単でしょう(by ボブ)」と言ってみたい
まずは雛形
ひな形としてこんなスクリプトを作ります。
command! -nargs=0 UtestAppend call <SID>UtestAppend() function! s:UtestAppend() echo 'HERE' endfunction " s:UtestAppend()
- UtestAppendというコマンドを追加
- 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()
- GetTestFileName関数を作成: クラスのファイル名からテスト用クラスのファイル名を生成する
- 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
"バッファがJavaの時だけファイル名を返す関数GetTargetName()を作り" GetTargetName()がエラー時の処理だけでファイル名返してない気がします。 --- tyru
最後のファイル全体がなんかコピペ時にイロイロおかしくなってたみたいです。訂正しました。
2012/02/20 23:15
- diffが小さくなるようにしました
- <, >, " といった文字が正しく表示されていないのを訂正しました
VimだけでXML Reformat
まっつんさんの記事のパクリです。
たとえばこういう XML があったとして…
<status><created_at>Mon Feb 06 21:07:52 +0000 2012</created_at><id>166629198054690816</id><text>Post-Bowl Twitter analysis http://t.co/OYYSRSew http://t.co/M0AtLQVd</text><source>web</source><truncated>false</truncated><favorited>false</favorited><in_reply_to_status_id></in_reply_to_status_id><in_reply_to_user_id></in_reply_to_user_id><in_reply_to_screen_name></in_reply_to_screen_name><retweet_count>454</retweet_count><retweeted>false</retweeted><user><id>783214</id><name>Twitter</name><screen_name>twitter</screen_name><location>San Francisco, CA</location><description>Always wondering what's happening. </description><profile_image_url>http://a0.twimg.com/profile_images/1124040897/at-twitter_normal.png</profile_image_url><profile_image_url_https>https://si0.twimg.com/profile_images/1124040897/at-twitter_normal.png</profile_image_url_https><url>http://blog.twitter.com/</url><protected>false</protected><followers_count>7625563</followers_count><profile_background_color>ACDED6</profile_background_color><profile_text_color>333333</profile_text_color><profile_link_color>038543</profile_link_color><profile_sidebar_fill_color>F6F6F6</profile_sidebar_fill_color><profile_sidebar_border_color>EEEEEE</profile_sidebar_border_color><friends_count>822</friends_count><created_at>Tue Feb 20 14:35:54 +0000 2007</created_at><favourites_count>16</favourites_count><utc_offset>-28800</utc_offset><time_zone>Pacific Time (US & Canada)</time_zone><profile_background_image_url>http://a1.twimg.com/profile_background_images/378245879/Twitter_1544x2000.png</profile_background_image_url><profile_background_image_url_https>https://si0.twimg.com/profile_background_images/378245879/Twitter_1544x2000.png</profile_background_image_url_https><profile_background_tile>true</profile_background_tile><profile_use_background_image>true</profile_use_background_image><notifications>false</notifications><geo_enabled>true</geo_enabled><verified>true</verified><following>true</following><statuses_count>1266</statuses_count><lang>en</lang><contributors_enabled>true</contributors_enabled><follow_request_sent>false</follow_request_sent><listed_count>68708</listed_count><show_all_inline_media>true</show_all_inline_media><default_profile>false</default_profile><default_profile_image>false</default_profile_image><is_translator>false</is_translator></user><geo/><coordinates/><place/><possibly_sensitive>false</possibly_sensitive><contributors><user_id>7694352</user_id></contributors></status>
Vim のバッファにコピペで貼りつけた上で次のようにコマンドを叩くと…
:set ft=xml :%s/></>^M</g :%s/>\(\S\)/>^M\1/g :%s/\(\S\)</\1^M</g gg=G
こんなんなります。^Mは<CTRL-V><ENTER>で入力しましょう。めんどくさければマクロ化や関数化しても良いですが、まぁその場で組み立てれば十分です。
<status>
<created_at>
Mon Feb 06 21:07:52 +0000 2012
</created_at>
<id>
166629198054690816
</id>
<text>
Post-Bowl Twitter analysis http://t.co/OYYSRSew http://t.co/M0AtLQVd
</text>
<source>
web
</source>
<truncated>
false
</truncated>
<favorited>
false
</favorited>
<in_reply_to_status_id>
</in_reply_to_status_id>
<in_reply_to_user_id>
</in_reply_to_user_id>
<in_reply_to_screen_name>
</in_reply_to_screen_name>
<retweet_count>
454
</retweet_count>
<retweeted>
false
</retweeted>
<user>
<id>
783214
</id>
<name>
Twitter
</name>
<screen_name>
twitter
</screen_name>
<location>
San Francisco, CA
</location>
<description>
Always wondering what's happening. </description>
<profile_image_url>
http://a0.twimg.com/profile_images/1124040897/at-twitter_normal.png
</profile_image_url>
<profile_image_url_https>
https://si0.twimg.com/profile_images/1124040897/at-twitter_normal.png
</profile_image_url_https>
<url>
http://blog.twitter.com/
</url>
<protected>
false
</protected>
<followers_count>
7625563
</followers_count>
<profile_background_color>
ACDED6
</profile_background_color>
<profile_text_color>
333333
</profile_text_color>
<profile_link_color>
038543
</profile_link_color>
<profile_sidebar_fill_color>
F6F6F6
</profile_sidebar_fill_color>
<profile_sidebar_border_color>
EEEEEE
</profile_sidebar_border_color>
<friends_count>
822
</friends_count>
<created_at>
Tue Feb 20 14:35:54 +0000 2007
</created_at>
<favourites_count>
16
</favourites_count>
<utc_offset>
-28800
</utc_offset>
<time_zone>
Pacific Time (US & Canada)
</time_zone>
<profile_background_image_url>
http://a1.twimg.com/profile_background_images/378245879/Twitter_1544x2000.png
</profile_background_image_url>
<profile_background_image_url_https>
https://si0.twimg.com/profile_background_images/378245879/Twitter_1544x2000.png
</profile_background_image_url_https>
<profile_background_tile>
true
</profile_background_tile>
<profile_use_background_image>
true
</profile_use_background_image>
<notifications>
false
</notifications>
<geo_enabled>
true
</geo_enabled>
<verified>
true
</verified>
<following>
true
</following>
<statuses_count>
1266
</statuses_count>
<lang>
en
</lang>
<contributors_enabled>
true
</contributors_enabled>
<follow_request_sent>
false
</follow_request_sent>
<listed_count>
68708
</listed_count>
<show_all_inline_media>
true
</show_all_inline_media>
<default_profile>
false
</default_profile>
<default_profile_image>
false
</default_profile_image>
<is_translator>
false
</is_translator>
</user>
<geo/>
<coordinates/>
<place/>
<possibly_sensitive>
false
</possibly_sensitive>
<contributors>
<user_id>
7694352
</user_id>
</contributors>
</status>
手元の環境にPythonが入ってなくても安心です。そうスパルタンVimならね。
スパルタンVimのPDF公開
C81での頒布からそろそろ三週間になろうとしていますので、スパルタンVimのPDFを公開します。以下からダウンロードしてください。
http://files.kaoriya.net/goto/c81pdf
このPDFは表紙とそれ以外を別々のPDFとして出力し、MERGE PDFというWebサービスを使って1つのPDFにしました。最近はほんとうに便利になりましたね。
2012/01/20追記
スパルタンVimに対してtwitterでいただいた誤りの指摘をまとめました。 http://togetter.com/li/244330 その他に見つけた場合はtwitterならば @kaoriya にお願いします。その他の手段は…まかせます。私の目に入りそうなところに書いてください。
