はじめに
唐突ですが今回が最終回です。今までも好き放題書いてきましたが、最終回はさらに趣味に走ってIdeaVIMプラグインだけの話をします。
IdeaVIMとは、名前から想像できるようにvimの機能を再現するプラグインです。それなりに再現度が高いこともあり、地味に人気のあるプラグインです。筆者はかれこれ10年以上、このプラグインを愛用しています。
IdeaVIMの歴史
プラグインの中でも古参のほうで、2003年にはすでに存在していました。もともとは一人の有志の方が作ったプラグインでしたが、作者のRick Maddyさんが「Java関係の仕事から離れそう」という事で、後継者を募ったところJetBrains社のOleg Shpynovさんが名乗り出ました。登場時から人気もあり、JetBrains社の人(いわゆる中の人)がコミッタになった事もあって、いつの間にかJetBrains公式プラグインとなりました。ただ、公式プラグインといってもAndroid StudioやIntelliJがvimモードを考慮した仕組みになっている訳ではありません。相変わらず無理を通してvimモードを再現しています。
JetBrains公式プラグインといってもオープンソースです。パッチ提供やバグ報告はいつでも歓迎です。
現在のコミッタは同じくJetBrains社のAndrey Vlasovskikhさんです。とても活発な方で、Twitterで「IdeaVIM~」とかつぶやくと、それが日本語だろうがお構いなしにメンションを打ってきます。どうやらロシア圏の方のようで普段はロシア語でつぶやいているのですが、その時は英語で問いかけてくるので、ビックリしないで相手をしてあげてください。それと一応、IdeaVIM専用のTwitterアカウントもあります(中の人は Andrey さんです)。
10年以上の歴史をもつプラグインということもあり、vimのエンジン部分はvim6をベースに作られています。世間的にvimが流行りだしたのはvim7からだと思うので、vim7ユーザにとっては若干物足りない実装になっています。それでもText-Objectsなど、ある程度vim7の機能も取り込んでいます。
IdeaVIMは結構まじめにvimっぽい動作をするプラグインですが、どうやっても「がんばって真似てる」域を脱することはありません。後でIdeaVIMの仕組みを紹介しますが、元々vimモードを考慮して作っていないモードレスエディタを無理矢理vim化しただけでも大したものなので、これを許容できるかどうかがIdeaVIMを使えるかどうかのポイントになると思います。
vimのような歴史あるエディタの愛用者は、それぞれ独自のこだわりを持っています。それ故、vim風、vim互換モードという謳い文句に期待しては落胆を繰り返していると思います。それなりにvimキーバインドの呪いにかかっている筆者が10年近くIdeaVIMを使い続けて得た教訓は「寛容」です。vim以外のエディタは何をどうしようとvimにはなれません。そこを呑み込んだ上でIdeaVIMに接しましょう。
平たく言うと「IdeaVIMはvimではなくて、vimっぽい操作ができるIdeaVIMという新しいエディタ」なのです。
IdeaVIMのインストールと諸設定
プラグインマネージャ(「Preferences / Plugins」)から「IdeaVIM」を選びインストールしてください。インストールが完了すると再起動を促すので、指示に従ってAndroid Studioを再起動します。再起動すると次のようなダイアログが表示されるので、ベースにしたいキーマップを選択します。
これでIdeaVIMが有効になります。見た目には変化が分かりませんが、Toolsメニューの"Vim Emulator"がONになっていることと、キーマップ設定(「Preferences / Keymap」)が"Vim"になっていることでIdeaVIMが有効になっていることがわかります。そうそう、カーソルがIビームからブロックカーソルに変わるので、これでもIdeaVIMが有効になっていることが分かります。
このあたりの仕組みについては後で解説するので、まずはIdeaVIMでどんなことができるのか紹介します。
IdeaVIMでできること・できないこと
いわゆる4つのモード(Normalモード、Insertモード、Visualモード、コマンドモード)を備えています。Vimそのものには敵わないにしろ、極力Vimでできることを再現しています。一応、IdeaVIM公式のコマンド一覧があるのですが、困ったことにこれが全てというワケでは無いです。ただ、ここに載っているコマンドはすべて機能するので参考にはなります。
- h,j,k,l など基本的なカーソル移動
- 基本的な操作はVimと遜色ありません。IdeaVIMのもっとも特徴的な機能が、単語区切りでのカーソル移動(Word Motions)が非ASCII文字にも対応していることです。筆者の知る限りでは、Vim本体以外でこの機能を実装しているVimプラグインはIdeaVIMだけです。
-
- Vimとの細かい違いは、IdeaVIMは論理改行間のカーソルの上下移動も
j, k
で移動します。つまりIdeaVIMのj, k
は実際のところはgj, gk
扱いということです。
- Ctrl-[による編集モードへの復帰
- Vimに慣れた人の中にはNormalモードへの復帰にESCキーではなく、Ctrl-[ にクセをつけてしまった人たちが居ます。IdeaVIMも Ctrl-[ でNormalモードへの復帰が可能なのですが、JISキーボードの場合、それが Ctrl+@ に割り当てられています。
- JISキーボードでも Ctrl-[ に割り当て直すことは可能です。キーマップに関しては、あとでまとめて説明します。
- Visualモード
- Android Studioの範囲選択と連動しています。選択範囲は"Cut, Copy"でクリップボードに保存することも、IdeaVIMのYankバッファに保存することもできます。Exコマンドの
[range]
にも利用できます。
- また、Android Studioの"Column Selection Mode"とは別に専用の矩形選択モード(Ctrl-V)を持ちます。
- Text-Objectsのサポート
- もともとIdeaVIMはvim6ベースですが、いくつかText-Objectsをサポートしています。VisualモードやNormalモードの演算子未解決(Operator Pending)モード時の範囲指示に使用できます。
- サポートしているText-Objectsをリスト1に示します。残念ながら「
at, it
(a tag block, inner tag block)」はサポートしていません。一応チケットはあがっていますが、長いこと放置されたままです。
- また
iskeyword
オプションを設定することもできません。
- キーボードマクロのサポート
q
コマンドによるマクロの記録。@
コマンドによる実行をサポートしています。マクロの記録は実際にタイプしたキーだけを記録します。Insertモード時にAndroid Studioのコード補完に頼った場合、補完候補を絞り込むまでにタイプしたキーだけがマクロに記録されます。
- ジャンプコマンドのサポート
- 主立ったジャンプコマンドをサポートしています。ブロック間の移動(
%, [{, [(, ]), ]}
)やメソッド間の移動([m, [M, ]m, ]M
)も可能です。
- メソッド間の移動に関しては、Android Studioの"Next/Previous Method"より使いやすいです。
- /, ? のインクリメンタルサーチ
- インクリメンタルサーチはできません。普通の検索のみです(キーワードを入力してEnterキーを押す)。
- *, #でカーソル位置のワードを検索
- できます。
- 検索語のハイライト表示
- 初期設定を変えてなければ
:hls
が有効になっています。/,?
や *, #
で検索語がハイライト表示されている場合、:nohl
でそのハイライトを消すことができます。
- Exコマンド
- 全てではありませんがある程度のExコマンドをサポートしています。IdeaVIMが提供しているExコマンドはリスト2 の通りです。ほとんどはvim由来のものばかりですが、一部にIdeaVIM固有のコマンドがあります。いくつかおもしろいコマンドをピックアップして紹介します。
- :class, :find, :symbol : 頭から順に「Navigateメニュー」の "Class...", "File...", "Symbol..." に対応しています。それなりに便利なのですが、SHIFTキー2回押しの"Search Everywhere"ができてからはありがたみが減ったかも知れません。
- :promptfind, :promptrepl : それぞれ「Edit → Findメニュー」の "Find..." と "Replace..." に対応しています。
/
, ?
がインクリメンタルサーチに対応していないので :pro
のほうが使い勝手がよいです。:promptrepl
はコマンドを覚えられないので、ほとんど使った事がありません。
- :quit, :wq, :exit : このコマンドを実行してもAndroid Studioは終了しません。エディタのタブが閉じるだけですので、ご安心ください。
- :w, :wq : イベントが起こるたびにオートセーブするAndroid Studioにはあまり意味の無いコマンドですが、一応 特定のファイルだけセーブすることができます。
- :help : ヘルプです。引数に与えたキーワードを vimdoc.sourceforge.net から検索します(Webブラウザが起動します)。
- :dumpline : IdeaVIMのデバッグコマンドです。Android StudioのログにDebugレベルで出力するので、普通は何が起きたかわかりませんし、気にしなくてもよいです。
- ウィンドウ分割
- できません。Android Studioがもともと備えているウィンドウ関連のコマンドに、Vim風なショートカットキーを割り当てることで疑似的に再現することは可能です。それでもVimに比べれば、Android Studioが提供しているウィンドウ分割系のコマンド些細なものばかりなので気休め程度でしかありません。
- mapコマンドによるキーアサインの変更
- できません。Android Studioがもともと持つ「Preferences / Keymap」でIdeaVIMのキーアサインを 奪い返す ことは可能です。このあたりの仕組みについては後述します。
- 当然、
map
コマンドの要望は高いのですが未だ実装されずにいます。実装の困難さも想像できるので、そう簡単には解決しないでしょう。
- VIM-288 Support key mapping commands from .vimrc
- vimscript
- できません。私の知る限りにおいて、これを実装しているIDE用vimプラグインは皆無です。さすがに、これは無茶振りだろうと皆思うのか、チケットすらあがってません。
- 筆者は若干(?)IdeaVIMびいきな事もあってかvimscriptをIDEのvimプラグインに求めるのは、そもそもの方向性が間違っているとさえ思ってます。
- 宣言に移動(Ctrl+])と元居た場所に戻る(Ctrl+T)、Javadocの参照(K)
- できます。「宣言に移動(
Ctrl+]
)」については、ESC(Ctrl-[
)と同じく、JISキーボードの場合は Ctrl+[
に割り当てられています。また gd
や gD
でも「宣言に移動」できます。
- カーソル位置のJavadocを参照する "Quick Documentation" は、一見するとメニューバーからはショートカットキーの割り当てが解除されるだけなのですが、ちゃっかり
K
コマンドに割り当てられています。まあ、vimらしいキーアサインですね。
- タブ移動(gt/gT)
- できます。個人的には、Exコマンドの
:n, :N
のほうがタイプ数が少ないので好みです。
- 折り畳み(folding)
zM, zR
(すべて閉じる、すべて開く)、zc, zo
(閉じる、開く)の4つのみサポートしています。
IdeaVIMのオプションと .ideavimrc
IdeaVIMもvim同様 :set
コマンドによるオプション設定が可能です。設定可能なオプションとその初期値は表1の通りです。オプションの意味はvimのものと同じで、省略記法も同じものが使えます。たとえば「hlsearch
」は「hls
」でもOKです。
表1 IdeaVIMで有効なオプションの一覧
オプション名 | 省略記法 | 初期値 |
digraph | dg | nodigraph |
gdefault | gd | nogdefault |
history | hi | 20 |
hlsearch | hls | nohlsearch |
ignorecase | ic | noignorecase |
matchpairs | mps | (:),{:},[:] |
more | more | more |
nrformats | nf | octal,hex |
scroll | scr | 0 |
scrolljump | sj | 1 |
scrolloff | so | 0 |
selection | sel | inclusive |
showmode | smd | noshowmode |
sidescroll | ss | 0 |
sidescrolloff | siso | 0 |
smartcase | scs | nosmartcase |
undolevels | ul | 1000 |
visualbell | vb | novisualbell |
wrapscan | ws | wrapscan |
設定可能なすべてのオプションを参照する方法や、現在の設定値の確認方法もvimと同じく「:set all
」や「:set
」で確認できます。
ここまで同じだと、アレもそうか?と予想できるように初期設定をRCファイルに記述しておくことも可能です。RCファイルは <HOME>
ディレクトリに格納したファイルを上から順番に探していき、見つかったファイルのみを読み込みます(リスト3)。
ここで困った事は、vimのRCファイル(.vimrc
と_vimrc
)も読み込む対象にしていることです。仮にIdeaVIMがvimのRCファイルを読み込んでも、評価できるのは表1のオプション行のみなので、その他のvim向けの記述がIdeaVIMに悪影響を及ぼすことはありません。唯一の困り事は、そのオプションの解釈がvimとIdeaVIMと若干異なるものがあると言うことです。
IdeaVIMの利用者は高確率でvimも使っているでしょう。IdeaVIMのRCファイルの仕組みを知らないため、意図せずに.vimrc
や_vimrc
をIdeaVIMが読み込んでいるのです。vimもIdeaVIMも同じRCファイルを共有することは、あまり気持ちの良いことではないので 空でも良いので IdeaVIMのRCファイル(.ideavimrc
か_ideavim
)を用意しておくことをオススメします。
IdeaVIMの仕組み
前述したとおり、IdeaVIMは「なんちゃってvim」レベルで、vimと全く同じ感覚で使おうとすると違和感を感じる部分が多々あります。この微妙な違いがストレスの元になるので、コアなvimユーザであるほど許しがたく感じます。
気休めではありますが、IdeaVIMの仕組みをある程度理解しておくと、違和感にも納得できる部分がでてきます。一介の利用者が内部の仕組みを意識して、回避策を労するというのもおかしな話ですが、仕組みを知っていれば修正方法もわかるかも知れませんし、そうしたらパッチを送ることもできるでしょう。せっかくのオープンソースなので、不満があるなら直してしまいましょう。それはそれで本末転倒な気もしますが、これがvimユーザの性(さが)なのだと信じています。
能書きが長くなりましたが本題に入ります。IdeaVIMの基本的な仕組みは『Android Studioのエディタに対するキー入力を横取りして、vimっぽく振る舞う』事です。図7のように、
- Android Studioのエディタ上に薄いIdeaVIMの層があり、
- NormalモードやVisualモードの時はIdeaVIMがキー入力をすべて捌き、
- Insertモードの時のみ、IdeaVIMの層に穴を空けて直接Android Studioにキー入力を渡す
……というイメージで動いています。そのため、冗談のようなホントの話なのですが、vimの機能をほぼ丸ごと実装しています。これはIdeaVIMに限った事では無く、Vrapper、jViなどの他のvim風プラグインも似たような実装になっています(これを知ったとき、世の中のvim使いの執念を垣間見た気がしました)。
IdeaVIM導入後に任意のキーマップからVimキーマップを作成するのは、エディタへのキー入力をIdeaVIMが捌くための仕組み作りのひとつです。ここで重要なのはキーマップ名が「Vim」であることではなく、ショートカットキーの塊をIdeaVIMに割り当てることです。具体例を見た方がわかりやすいのですが、IdeaVIMを有効にすると「Preferences / Keymap」の「Plug-ins / IdeaVIM / Keys」に多数のショートカットキーが割り当てられます。
この「Keys」を起点にして、IdeaVIMは多種多様なショートカットキーをvim風に解釈してAndroid Studioのエディタに反映しているのです。IdeaVIMのインストール直後(または"Reconfigure Vim Keymap"実行時)に「Vim Keymap setting」ダイアログで「Vim」キーマップを作成しているのは、「あるキーマップに、IdeaVIM用の設定―――「Keys」にショートカットキーを割り当てる」ためです。
このような仕込みをしているため「Vim」キーマップはそれなりの意味を持ちます。一応、IdeaVIMはキーマップが「Vim」以外になっていると警告を発します。
ただ、本当に意味があるのはキーマップが「Vim」であることではなく、「Keys」に多数のショートカットキーが割り当てられている事なので、厳密に言えばキーマップが「Vim」以外でもIdeaVIMは機能します。これ、後のキーカスタマイズに重要になるので覚えておいてください。
IdeaVIMの有効範囲
IdeaVIMによるvi/vimキーバインドはAndroid Studioのエディタ部分に対してのみ有効です。それ以外、例えばツールウィンドウやナビゲーションバーではvimキーバンドは使えません。
ただ、落とし穴というか引っかけ的なトラップがあります。IdeaVIMが作用するのはAndroid Studioのエディタ部分です。もっと細かく言うと、Android Studioのエディタコンポーネントに対して作用します。このエディタコンポーネントがクセ者で、主にエディタ部分に使われているのですが「ぱっと見、テキストフィールドぽい部分も実はエディタコンポーネントだった」なんて事がよくあります。
IdeaVIMは、エディタ部分以外でエディタコンポーネントを使っている場合、初期状態をInsertモードにしているため、その事実に気付くことは稀です。一番分かりやすいのは「Commit Changes」ダイアログの「Commit Message」のテキストエリアでしょう。このエリアがまさにエディタコンポーネントで初期状態がInsertモードの典型です。
「Commit Message」エリアもIdeaVIMの影響下にあるため、ESCキーでNormalモードに移行することができます。意味があるかどうかは別としてExコマンドも実行できます。
このような部分が他にもいくつかあります(どこでエディタコンポーネントを使っているかは、Android StudioやベースのIntelliJの都合で変わります)。ときおり入力フィールドにフォーカスが当たっているのにキー入力が一切受け付けない状況になった場合は、実はそこがIdeaVIMの影響下で知らないうちにNormalモードになっているだけかも知れませんよ(a
やi
を押すと入力できるようになったりします)。
現状のIdeaVIMでできるキーカスタマイズ
IdeaVIMにmap
コマンドは提供されていませんが、まったくキーカスタマイズができないかというとそんなことはありません。IdeaVIM内のキーアサインは変更できませんが、IdeaVIMに奪われたキーバインド(ショートカットキー)をAndroid Studioに奪い返すことはできます。
たとえば、筆者はWindows版のgVimを普段使いしているのですが、クリップボード操作に Ctrl-X, C, V
を使いたいため mswin.vim が欠かせません。IdeaVIMでも同様にしたい場合、そのショートカットキーをAndroid Studioの本来の機能に再定義するのが手っ取り早いです。
IdeaVIMから奪い返したショートカットキーは、IdeaVIMの影響を受けなくなるのでNormalモード、Insertモードに関わりなく機能します。IdeaVIMはモードによってショートカットキーに複数の機能を割り当てているので、どのショートカットキーを奪い返すかは厳選してください。また、なんでも感でも奪い返せるわけでは無いので、何度か試行錯誤する必要があります(この時点ですでに魔改造なのです)。
筆者がIdeaVIMから奪い返したショートカットキーは、だいたい表2の通りです。
表2 IdeaVIMから奪い返して再定義したショートカットキーの例
ショートカットキー | 再定義したコマンド |
Ctrl-[ | "Escape" |
Ctrl-A | "Select All" |
Ctrl-X | "Cut" |
Ctrl-C | "Copy" |
Ctrl-V | "Paste" |
Ctrl-Z | "Undo" |
Ctrl-T | "Back" |
Ctrl-] | "Declaration" |
Ctrl-W | "Select Word at Caret" |
そう滅多にやらない操作だと思いますが "Reconfigure Vim Keymap" を実行すると「Vim」キーマップが初期化されるため、ショートカットキーを奪い返す操作は「Vim」キーマップに対してではなく、「Vim」キーマップを別のキーマップにコピーしてから行う事をオススメします。
IdeaVIMの魔改造のすすめ
ショートカットキーをAndroid Studioに奪い返すだけでは満足できない人の最後の手段がIdeaVIMの魔改造です。極論ですが、IdeaVIM自身がオープンソースなのでその気になればナンデモできます。何を隠そう筆者もIdeaVIMの魔改造に手を染めたひとりです。
意外というか当然というか、IdeaVIMをforkして自分用のIdeaVIMを作っている人はそれなりに居るようで、IdeaVIMユーザの中では決して珍しい事ではありません。
実際、ショートカットキーを変更するだけならば、それほど大変ではないです。IdeaVIMのキー設定は RegisterActionsクラス に集約されており、ここの parser.registerAction()
でコマンドとショートカットキーの割り当てを行っています。
リスト4がRegisterActions
クラスの抜粋になります。これはカーソルを下に移動する機能の割り当てなのですが、"VimMotionDown"
というコマンドにj, ↓(VK_DOWN), Ctrl-J, Ctrl+N
の4つのショートカットキーを割り当てています。
parser.registerAction()
の第二引数(リスト4の例だと "VimMotionDown"
)がAndroid Studioに登録しているコマンド名になります。コマンド名が "Vim~"
ではじまるものはIdeaVIMが新規に登録したコマンドで、それ以外はAndroid Studioが元々持っているコマンドになります。
さらに加熱すると独自のExコマンドを開発することもできます。Exコマンドは com.maddyhome.idea.vim.ex.handerパッケージに格納されており、CommandParserクラスを介してIdeaVIMに登録します。ソースコードを覗いてみるとわかりますが、そんなに難しいことはやっていません。
参考までに、筆者が調べていたIdeaVIMの内部情報を提示しておきます。これで味を占めて、IdeaVIM本家にパッチを送っていただけると大変ありがたいです。
さいごに
Androidにほとんど触れず、ひたすらAndroid Studioの操作ばかりしてきた本連載も今回でおしまいです。「10ヵ月も連載してれば、Ver1.0も出るだろう」とタカを括っていましたが、ようやっと半分のVer0.5.0を過ぎたところです。このペースで開発していたら、ベースにしているIntelliJ IDEAの次のバージョン(ver14)が出てしまいそうなのですが、Android Studioはどこで向かおうとしているのでしょうね。
Android Studioが登場してから、ずっとGoogleの開発ペースに振り回されている感はありますが、Android Studioの元になったIntelliJやビルドシステムに使っているGradleは、それ単体で見れば良いツールなのは間違いありません。きっとGoogleもより良いAndroid開発環境を整備しようと試行錯誤しているのだと思います。
「正式リリースはまだか」とはやる気持ちもありますが、未だ「EARLY ACCESS PREVIEW」が取れずにいるので、もうしばらくはGoogleと一緒に人柱の気分を味わう心の余裕が要りそうです。