VimM#2開催のお知らせ

2008-07-29T01:02:31 / vim, event, vimm / comment

VimM#2を開催します。今回はテーマを「Vimプラグイン特集」とし、プラグインとは何か、便利なプラグインの紹介、プラグインの書き方について発表・解説を行う予定です。便利なプラグインの紹介についてはスピーカーを募集中です。それ以外については私が発表・解説する予定です。

今回のテーマはid:taku-oさんの「vimプラグインの作り方を知りたい需要があると、発表やってて感じた」「どうやら便利なプラグインの話とか聞きたかった人もいたようだ」という発言から決めてみました。ある程度の方向性を決めておいた方が、発表にもやることにもまとまりができて良いのではないかと思ったので、この形を提案してみました。

VimM#3以降を開催する際も何らかのテーマがあれば良いと思います。案としては、id:maedanaさんの意見にあるような、初級者から中級者へのステップアップ講座などが考えられますね。

Vim: Key mappingを極める

2008-07-20T15:03:35 / vim / comment

Vimを使いこなす上で避けて通れないのがkey mappingです。Vimのデフォルトのキーバインドはそれ自体でもそれなりに優秀なのですが、キーの割り当て方が今一だったり、そもそもキーが割り当てられていない機能も多くあります。それをカスタマイズするための機能がkey mappingです。

Key mapping (あるいは単にmappingやmapとも呼ぶ)については:help map.txtを読めば一通りのことは分かるのですが、どういう風に使うべきかという観点からは詳しく説明されていませんし、掲載されているサンプルも実践という観点からは今一です。そこで、基本的なところから始めて、よくあるパターンや使い分けの仕方、そしてハマりがちな落とし穴について述べていきたいと思います。

基本

Key mappingを定義するコマンドは多数ありますが、基本的な書式は以下の通りです:

map [...] {lhs}  {rhs}

機能の種類とそれを割り当てるべきモード

カーソル移動

割り当てるべきモード
Normal mode, Visual mode, Operator-pending mode
例: :help中のリンクへ移動
noremap .j  /|[^ |]\+|<Return>
解説
カーソル移動なのでNormal modeに割り当てるのは当然ですが、カーソル移動 = {motion}なので、Visual modeでの範囲選択やoperatorの範囲指定でも使用可能であるべきです。
蛇足: ここでの「カーソル移動」はNormal modeをベースとした通常の編集作業におけるカーソル移動を意味します。Insert modeおよびCommand-line modeにおけるカーソル移動の場合は好みによるところが大きいので何とも言えません。私の場合、Insert mode中にカーソル移動をすることはほとんどないので、定義するとしたらCommand-line modeのみになります。

範囲選択

割り当てるべきモード
Visual mode, Operator-pending mode
例: Text object i[の同義語的キーシーケンスを追加(i[は押し難い)
vnoremap ir  i[
onoremap ir  i[
解説
Text objectのような、カーソル移動ではないが特定の範囲を選択する機能の場合、統一性の観点からVisual modeとOperator-pending modeの両方で使用可能であるべきです。

テキスト入力補助

割り当てるべきモード
Insert mode (基本的にこれだけ), Command-line mode (必要に応じて)
例: 現在の日付を挿入
inoremap ,df  <C-r>=strftime('%Y-%m-%dT%H:%M:%S')<Return> 
解説
:help map-examples等では:map!を用いた例がありますが、Insert modeだけでなくCommand-line modeにまで割り当てる必要性があるケースはあまりありません。

何らかの機能へのショートカット

割り当てるべきモード
Normal mode (基本的にこれだけ)
例: vimrcをリロードする
nnoremap <Space>s.  :<C-u>source $VIMRC<Return>
解説
割り当てる機能によりますが、Normal mode以外のモードに割り当てる場合は、各種副作用を適切にキャンセルする必要があるため、Normal modeに限定しておいた方が無難です。

: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

入力dBwdAwとして解釈されますが、入力dBBwdABwとして解釈されます。後者の入力では、1回目のBAに変換されますが、2回目のBでは既にOperator-pending modeでAという入力があるため、それと合わせてABという入力が行われたものと解釈されるためです。もしABが何かにmapされているならばremapが行われますが、ここではそのようなkey mappingは存在しないため、そのままのキーシーケンスで解釈されます。このようなケースの全てに対して自動的に対応させることは困難なため、表題のようなキーシーケンスの定義はほぼ不可能なのです。

あるキーシーケンスに対してインターフェースとなる名前を付け、それを利用してkey mappingを定義する

後述

よくあるパターン

{lhs}が既に何かにmapされているならばmapしない

silent! noremap <unique> {lhs}  {rhs}

<unique>が指定されていると、{lhs}が既に何かにmapされている場合はkey mappingを定義しなくなります。ただその情報はエラーとして表示されるため、:silent!でそのエラーを無視します。maparg()等を使う方法もありますが、こちらの方が簡潔に記述できます。

{lhs}を無効にする

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のような名前よりこちらの方が好みからです。

落とし穴

Ex commandを割り当てようとしてやりがちなミス

" 駄目な例
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>を含める必要があります。

(その他、気が向いたら追加する予定。)