VimM#2を開催します。今回はテーマを「Vimプラグイン特集」とし、プラグインとは何か、便利なプラグインの紹介、プラグインの書き方について発表・解説を行う予定です。便利なプラグインの紹介についてはスピーカーを募集中です。それ以外については私が発表・解説する予定です。
今回のテーマはid:taku-oさんの「vimプラグインの作り方を知りたい需要があると、発表やってて感じた
」「どうやら便利なプラグインの話とか聞きたかった人もいたようだ
」という発言から決めてみました。ある程度の方向性を決めておいた方が、発表にもやることにもまとまりができて良いのではないかと思ったので、この形を提案してみました。
VimM#3以降を開催する際も何らかのテーマがあれば良いと思います。案としては、id:maedanaさんの意見にあるような、初級者から中級者へのステップアップ講座などが考えられますね。
Vimを使いこなす上で避けて通れないのがkey mappingです。Vimのデフォルトのキーバインドはそれ自体でもそれなりに優秀なのですが、キーの割り当て方が今一だったり、そもそもキーが割り当てられていない機能も多くあります。それをカスタマイズするための機能がkey mappingです。
Key mapping (あるいは単にmappingやmapとも呼ぶ)については:help map.txtを読めば一通りのことは分かるのですが、どういう風に使うべきかという観点からは詳しく説明されていませんし、掲載されているサンプルも実践という観点からは今一です。そこで、基本的なところから始めて、よくあるパターンや使い分けの仕方、そしてハマりがちな落とし穴について述べていきたいと思います。
Key mappingを定義するコマンドは多数ありますが、基本的な書式は以下の通りです:
map [...] {lhs} {rhs}<C-h>のように記述します(詳細: :help key-notation)。:help :map-arguments)。:mapの他に:vmapや:omap等のコマンドが存在します。各コマンドの違いはVimのどのモード(あるいはモードの組み合わせ)に対してkey mappingを定義するのかが異なります(詳細: :help map-overview)。基本的にコマンド名の頭文字が定義を行うモードを表します(例: :vmapはVisual mode用のkey mappingを定義する)。:map等のコマンドに対して:noremapというコマンドが存在します。両者の違いは、{rhs}に他のkey mappingsの{lhs}が含まれていた場合、それが再帰的に展開されるかどうかです(この再展開のことをremapと呼びます)。:noremap系のコマンドではremapが行われず、:map系のコマンドではremapが行われます。:help map.txtを参照してください。:help中のリンクへ移動noremap .j /|[^ |]\+|<Return>
vnoremap ir i[ onoremap ir i[
inoremap ,df <C-r>=strftime('%Y-%m-%dT%H:%M:%S')<Return> :help map-examples等では:map!を用いた例がありますが、Insert modeだけでなくCommand-line modeにまで割り当てる必要性があるケースはあまりありません。nnoremap <Space>s. :<C-u>source $VIMRC<Return>
:map系コマンドと:noremap系コマンドの使い分け基本的に:noremap系のコマンドだけを使います。どうしてもremapが必要なケースでのみ:map系のコマンドを使います。
というのも、{rhs}のどの部分がどうremapされるのかは、{lhs}が実際に入力されたときにどのようなkey mappingsが定義されているかに依存するため、key mappingの定義時にその挙動を事前に把握することが困難だからです。無闇に:map系のコマンドを使うと、後々になって意図しない副作用が発生する可能性があるため、:map系のコマンドはどうしても必要なケース以外は使うべきではありません。
そもそも:map系のコマンドが必要なケースというのは数えるほどしかありません。私の経験からは以下の2つしか考えられませんでした:
" <Esc>をプレフィックスキーとしてウィンドウの操作を行う nmap <Esc> <C-w>
私の場合、独自にウィンドウ関係のショートカットキーを<C-w>{x}の形式で追加して、<Esc>{x}と<C-w>{x}の両方で使用可能にしています(実際にはAlt-{x}やOption-{x}とタイプしており、これは端末エミュレーターによって<Esc>{x}に変換されて端末内に伝わります。なお、後者は念の為に残してあります)。このとき、:nnoremapを使うと追加キーバインドに<Esc>{x}でアクセスできなくなります。なので:nmapを使う必要があります。
蛇足: 厳密に言えば表題のようなキーシーケンスの定義はほぼ不可能です。例えば以下のような定義があった場合:
onoremap AAw aw onoremap Aw aw omap B A
入力dBwはdAwとして解釈されますが、入力dBBwはdABwとして解釈されます。後者の入力では、1回目のBはAに変換されますが、2回目のBでは既にOperator-pending modeでAという入力があるため、それと合わせてABという入力が行われたものと解釈されるためです。もしABが何かにmapされているならばremapが行われますが、ここではそのようなkey mappingは存在しないため、そのままのキーシーケンスで解釈されます。このようなケースの全てに対して自動的に対応させることは困難なため、表題のようなキーシーケンスの定義はほぼ不可能なのです。
後述
silent! noremap <unique> {lhs} {rhs}<unique>が指定されていると、{lhs}が既に何かにmapされている場合はkey mappingを定義しなくなります。ただその情報はエラーとして表示されるため、:silent!でそのエラーを無視します。maparg()等を使う方法もありますが、こちらの方が簡潔に記述できます。
noremap gs <Nop>
" 名前を付ける例 nnoremap <silent> <Plug>(fakeclip-y) \ :<C-u>set operatorfunc=fakeclip#yank<Return>g@ " 名前を利用してkey mappingを定義する例 nmap "+y <Plug>(fakeclip-y)
主にプラグインにおいて、キーバインドのカスタマイズをし易くするため、ある機能に対してインターフェースとなるような「名前」を付ける場合があります。{lhs}が<Plug>で始まるkey mappingがその名前です。
名前の付け方は自由ですが、慣習として、名前は<Plug>から始め、次にプラグイン名と機能名を含めることになっています。<Plug>は架空のキーなのでユーザーが直接入力することはできません。
このような名前を利用してkey mappingを定義する場合、remapが行われなければ名前の向こう側にある実際の機能が実行されません。よってこの場合はremapが必要となり、:map系のコマンドを使わなければなりません。
蛇足: 私の場合、「名前」<Plug>({plugin}-{feature})の形式に統一しています。括弧で括っているのは、<Plug>fooと<Plug>foobarのような曖昧なkey mappingを定義することを避けるためと、<Plug>PluginFeatureのような名前よりこちらの方が好みからです。
" 駄目な例 nnoremap qq :cc<Return> " 正しい例 nnoremap qq :<C-u>cc<Return>
countを指定した後で{lhs}を入力した場合、countに続いて{rhs}が入力されたことになります。:はcount付きだと:.,+Nが自動的に入力されます(Nはcountから1を引いた値)。同様に、Visual modeで:を入力した場合、:'<,'>が自動的に入力されます。
このような範囲指定を伴ってEx commandを実行したいケースはほとんどなく、エラーや意図しない挙動の原因となります。明確に範囲指定が必要なケース以外では、範囲指定の影響をキャンセルするために<C-u>を含める必要があります。
(その他、気が向いたら追加する予定。)