Developer's Blog

Vim の snipMate.vim プラグインを改造して、1キーで trigger の展開と補完をできるようにする

こんにちは。Sleipnir 3 開発担当兼 Sleipnir Mobile for Android 開発担当の西田です。

普段は Visual Studio と Eclipse で開発しているヘタレ Vimmer ですが、
Ruby や Python でちょっとしたスクリプトを書いたりするときは Vim を使ってます。
というわけで今回は、そういうときに役立つ Vim の小ネタを紹介します。

さて、皆さんは snipMate.vim というプラグインをご存知でしょうか。

簡単に紹介しますと、よく使うコード片 (snippet) を、それに関連付けられた短い単語 (trigger) を入力するだけで挿入することができるプラグインです。

例えば C 言語 のソースを編集中に

for<tab>

と入力すると(<tab> は タブキー)、

for(int i = 0; i < count; ++i) {

}

と展開されます。ここでは「for」が trigger で、展開された for 文 が snippet になります。

まぁこれだけなら別に大したこともないのですが、snipMate.vim のすごいところは、展開後に さらに <tab> を押すことで、「int」、「i」、「count」を順に選択することができるので、必要な部分だけを簡単に書き換えることができます。もちろん書き換えずにスキップすることもできます。さらに嬉しいことに、ひとつの i を変更すると連動して残り 2 つの i も同様に変更されます!DRY ですね。最後にブレースの中にカーソルが移動するので、流れるようにコードを書き続けることができます。

他にも snippet にスクリプトを埋め込んで、展開時にそれを評価して挿入するといったこともできます。例えば、

date<tab>

と入力すると

2011-07-05

と展開されるのですが、この日付はスクリプトで今日の値が得られるようになっています。

このプラグインはインストールするだけでもれなく C, C++, java, Javascript, Objective-C, Ruby, Python, Perl, HTML その他いろいろなファイルタイプ用の snippet がたっぷり付いてくるのですぐに使い始められます。もちろん、自分で snippet を追加・削除・編集することもできます。

といった具合にもう手放せない感じの snipMate.vim ですが、若干不満に思うこともあります。それは、

  • <tab> は他のことにも使うキーなので誤操作しやすい。もっとマイナーなキーで展開するようにしたい。
  • snippet が増えてくると、trigger がわからなくなる。

です。というわけで次のように改造します。

  • <tab> ではなく Ctrl-L で展開する。
  • Ctrl-L を押したとき、マッチする trigger が存在すれば snippet を展開し、そうでなければ trigger の補完候補一覧を表示する。

こうすることで、誤操作を気にすることがなくなり、また trigger を忘れてもとりあえず Ctrl-L を押すことで trigger の一覧を表示することができるはず!

それでは、github にある最新版からちゃちゃっと hack した結果の diff はこちらです:

diff -r 6be3f9c76b17 -r f6e6c3ef4130 after/plugin/snipMate.vim
--- a/after/plugin/snipMate.vim	Tue Jul 05 15:44:20 2011 +0900
+++ b/after/plugin/snipMate.vim	Tue Jul 05 15:46:08 2011 +0900
@@ -10,11 +10,11 @@
 "
 " You can safely adjust these mappings to your preferences (as explained in
 " :help snipMate-remap).
-ino <silent> <tab> <c-r>=TriggerSnippet()<cr>
-snor <silent> <tab> <esc>i<right><c-r>=TriggerSnippet()<cr>
-ino <silent> <s-tab> <c-r>=BackwardsSnippet()<cr>
-snor <silent> <s-tab> <esc>i<right><c-r>=BackwardsSnippet()<cr>
-ino <silent> <c-r><tab> <c-r>=ShowAvailableSnips()<cr>
+ino <silent> <C-l> <c-r>=TriggerSnippet()<cr>
+snor <silent> <C-l> <esc>i<right><c-r>=TriggerSnippet()<cr>
+ino <silent> <C-\><C-l> <c-r>=BackwardsSnippet()<cr>
+snor <silent> <C-\><C-l> <esc>i<right><c-r>=BackwardsSnippet()<cr>
+ino <silent> <c-r><C-l> <c-r>=ShowAvailableSnips()<cr>
 
 " The default mappings for these are annoying & sometimes break snipMate.
 " You can change them back if you want, I've put them here for convenience.
diff -r 6be3f9c76b17 -r f6e6c3ef4130 autoload/snipMate.vim
--- a/autoload/snipMate.vim	Tue Jul 05 15:44:20 2011 +0900
+++ b/autoload/snipMate.vim	Tue Jul 05 15:46:08 2011 +0900
@@ -198,7 +198,7 @@
 	if s:curPos == s:snipLen
 		let sMode = s:endCol == g:snipPos[s:curPos-1][1]+g:snipPos[s:curPos-1][2]
 		call s:RemoveSnippet()
-		return sMode ? "\<tab>" : TriggerSnippet()
+		return sMode ? "\<C-l>" : TriggerSnippet()
 	endif
 
 	call cursor(g:snipPos[s:curPos][0], g:snipPos[s:curPos][1])
diff -r 6be3f9c76b17 -r f6e6c3ef4130 plugin/snipMate.vim
--- a/plugin/snipMate.vim	Tue Jul 05 15:44:20 2011 +0900
+++ b/plugin/snipMate.vim	Tue Jul 05 15:46:08 2011 +0900
@@ -147,20 +147,9 @@
 endf
 
 fun! TriggerSnippet()
-	if exists('g:SuperTabMappingForward')
-		if g:SuperTabMappingForward == "<tab>"
-			let SuperTabKey = "\<c-n>"
-		elseif g:SuperTabMappingBackward == "<tab>"
-			let SuperTabKey = "\<c-p>"
-		endif
-	endif
-
 	if pumvisible() " Update snippet if completion is used, or deal with supertab
-		if exists('SuperTabKey')
-			call feedkeys(SuperTabKey) | return ''
-		endif
 		call feedkeys("\<esc>a", 'n') " Close completion menu
-		call feedkeys("\<tab>") | return ''
+		call feedkeys("\<C-l>") | return ''
 	endif
 
 	if exists('g:snipPos') | return snipMate#jumpTabStop(0) | endif
@@ -177,28 +166,14 @@
 		endif
 	endfor
 
-	if exists('SuperTabKey')
-		call feedkeys(SuperTabKey)
-		return ''
-	endif
-	return "\<tab>"
+	call ShowAvailableSnips()
+	return "\<C-p>\<Down>"
+	"return "\<C-l>"
 endf
 
 fun! BackwardsSnippet()
 	if exists('g:snipPos') | return snipMate#jumpTabStop(1) | endif
-
-	if exists('g:SuperTabMappingForward')
-		if g:SuperTabMappingBackward == "<s-tab>"
-			let SuperTabKey = "\<c-p>"
-		elseif g:SuperTabMappingForward == "<s-tab>"
-			let SuperTabKey = "\<c-n>"
-		endif
-	endif
-	if exists('SuperTabKey')
-		call feedkeys(SuperTabKey)
-		return ''
-	endif
-	return "\<s-tab>"
+	return "\<C-\>\<C-l>"
 endf
 
 " Check if word under cursor is snippet trigger; if it isn't, try checking if

trigger の補完一覧のイメージです:

vim-snipmate-triggers

それでは、よい Vim life を。 Visual Studio で開発している Sleipnir 3 と Eclipse で開発している Sleipnir Mobile for Android (もうすぐα版公開!) もよろしくお願いいたします。 \(^o^)/

Copyright © 2019 Fenrir Inc. All rights reserved.