読者です 読者をやめる 読者になる 読者になる

lightline.vim作りました - プラグインの直交性について

Vim

lightline.vimというVimプラグインを作りました。statuslineをなんかかっこよくしてくれるやつです。

https://github.com/itchyny/lightline.vim からインストールできます。

デフォルト (powerlineと同じ配色)
https://raw.githubusercontent.com/wiki/itchyny/lightline.vim/image/powerline/0.png

wombat
https://raw.githubusercontent.com/wiki/itchyny/lightline.vim/image/wombat/0.png

solarized
https://raw.githubusercontent.com/wiki/itchyny/lightline.vim/image/solarized_dark/0.png

landscape
https://raw.githubusercontent.com/wiki/itchyny/lightline.vim/image/landscape/0.png

どうしてこれを作ったのかということを話すには、vim-powerlineとの出会いまで遡らなくてはなりません。

vim-powerlineとの出会い

vim-powerlineとの出会いは約一年前になります。それ以前から気になってはいましたが、フォントにパッチを当てるのが面倒でためらっていました。しかし、重い腰を上げてインストールしてみました。

インストールしてすぐ感じたことは、配色が気に入らないことでした。normalモードが危険色(黄色)で、insertモードが寒色とは、一体どんなセンスを持っているのでしょうか。おかしすぎます。異常です。私はすぐに色を変更する方法を調べ、設定しました。

私の使っているカラースキームはコントラストが高めで、ステータスラインにも原色を使いました。私はこのステータスラインを非常に気に入り、一年過ごしたのです。

vim-powerlineがdeprecatedに

そのうち、vim-powerlineがdeprecatedになってしまいます。絶望しました。GitHubにあったwikiページもissueページも削除されました。vim-powerlineの作者は、zshやtmuxなどにも使用できる、より一般的な方法に傾倒していきました。作者との考えのずれを感じました。

vim-powerlineはdeprecatedになり、powerlineを使うことが推奨されました (注: vim-powerlinepowerlineは異なります)。しかし、私はすぐにpowerlineに移行することはありませんでした。一度は入れてみたのですが、powerline用のフォントを使ってもどうしても右側に隙間ができてしまったのです (注: 見た目を良くするためパッチを当てたフォントを用いるのですが、vim-powerlineとpowerlineでパッチのあて方が異なるのです)。私は諦めて、古きvim-powerlineに固執することにしました。

新星vim-airline

今年の6月の終わり、突如として新しいプラグインが現れました。vim-airlineです。

軽いこと、コアが小さい事、他のプラグインに対しても良きにしてくれることをプッシュしていました。

vim-airlineは瞬く間にvim-powerlineの後継という座を勝ち取りました。現在のGitHubでのスターの数は1323です。使用してる人はおよそ五倍と見積もると6000人くらいにもなります。

それでも私はvim-powerlineを離れることはありませんでした。

vim-airlineを導入する

この記事がきっかけでした。時代に乗り遅れる!そういった危機感を感じました。

私はこの記事を見て、vim-airlineに乗り換えます。8/20のことでした。

そしてすぐに、自分の気に入っている配色に変更します。

最初は気に入っていました。しかし、コードを追ううちに、だんだん腹立たしくなってきます。まず最初に気になったのが、色が左右で同じになってしまうという問題でした。なぜそういう設計にしたのか、意味不明でした。私はインストールしたその日のうちに、issueを立てました。

作者の答えは、こうでした。「左右で色を同じにすることで、テーマを書く労力が半減するんだ」一理あるとは思いましたが、vim-airlineの掲げているextensibilityとは程遠いものでした。

二つ目に気になったのが、ハードコーディングが多すぎることでした。vim-airlineは、左側の要素がsection_a、b、cそして右側がx、y、zとなっています。そしてreadonlyを表すマークは、常にsection_cに追加されるようになっていました。(ちなみに、pasteを表すマークも、section_aに追加されるようになっていました。)

絶望しました。一応PRは送りました。

しかし、もはやこのプラグインを使う気は薄れつつありました。

他にも気になったところは多々あります。例えば、非アクティブのウィンドウはsection_cだけになる、section_a、b、cといった設定方法、関数を挟み込むことによって拡張するとかいう汚いやり方、statuslineの構文で直接設定しなければならないことなどでした。もはやPRどころでは改善できないと感じたのです。

lightline.vimを作り始める

vim-airlineを導入し、一瞬気に入りましたが、その日の夜には作者の考えとの大きな乖離を感じていました。readonlyのマークがsection_cに追加されるようになっていてユーザーが変更できない。そんな設計が二か月もまかり通っていることに絶望しました。

私は早速、新しいプラグインを作り始めました。それは8/21、vim-airlineをインストールした次の日のことでした。名前はlightline.vimとしました。軽い、その精神はvim-airlineから引き継ぎました。

まずやったことは、vim-airlineの最初のコミットのファイルを落とすことでした。たった150行のVim scriptのファイルが一つあるだけでした。全てがハードコーディングでしたが、まぁ最初はこんなものかな、とも思いました (区切りの文字は設定できるようになっていました)。

lightline.vimを作る時、最初に考えたのは、設定方法でした。アンダースコアの変数で設定するのが良いのか。#を含む変数で設定するのが良いのか。関数を提供し、それを.vimrcの中で読んでもらって設定するのが良いのか。といったことです。色々吟味した結果、ただ一つのグローバル変数g:lightlineによって設定して貰う方法を取りました。quickrunを少し意識したと思います。.vimrcにプラグイン名を何度も打ち込まなくて良い。辞書なのでプラグインから使いやすいと思ったのです。

次に考えたのは、表示するものをどうやって設定できるようにするかということでした。section_a、b、cという方法は絶対に間違っていると思いました。これらは配列であるべきです。また、非アクティブ時に表示するものも設定できて当然だと思いました。lightline.vimでは、次の4つの変数によって表示するものを設定します。

例えば、デフォルトでは

let g:lightline.active.left = [ [ 'mode', 'paste' ], [ 'readonly', 'filename', 'modified' ] ]
let g:lightline.active.right = [ [ 'lineinfo' ], [ 'percent' ], [ 'fileformat', 'fileencoding', 'filetype' ] ]
let g:lightline.inactive.left = [ [ 'filename' ] ]
let g:lightline.inactive.right = [ [ 'lineinfo' ], [ 'percent' ] ]

となっています。これらによって、表示されるものが決定します。例えば、.vimrcと言うファイルを読み取り専用で編集しようとし、さらにset pasteした時は以下のようになります。

.vimrcのバッファーの左側の要素はg:lightline.active.left、右側の要素はg:lightline.active.rightとなっています。奇妙に思うかもしれないのは、右側はlineinfoから、つまり外側から設定されるということです。これは非直感的かもしれませんが、「外側」という点でleft、right共に共通し、こちらのほうが美しいと思ったのです。

また、アクティブなウィンドウが設定できるのと同様に、非アクティブのウィンドウも設定できるようになっています。こんなことは、当然のことなのです。

他にも、ユーザーが.vimrcで簡単に拡張できるような仕組みを作りました。設定方法についてはGitHubのページのConfiguration tutorialを見て下さい。

直交性

lightline.vimを作るときに意識したことは、プラグインの直交性です。簡単に言うと、全く趣旨の異なるプラグインは、直交します。似ているプラグインは、平行からは少しずれますが、大体平行です。如何なるプラグインもそれ自身と平行です。

似ているプラグインは、時に酷く干渉し、お互いに悪い影響を与えます。これら二つのプラグインを使用している時に上手く動かないことを避けるため、プラグイン作成者は相手のプラグインの用いている変数、関数などで相手を抑えこもうとします。そして、それはしばしばひどい泥沼であり、二つのプラグインは両方沈むか、相手を無視したほうが勝ちます。

互いに直交したプラグインは、殆ど干渉しません。しかし、交差する点が一つだけあります。ここで、もし不具合が生じたとします。どうすればよいでしょう。

プラグインAの作者になったと想像して下さい。殆ど上手く動くのですが、他のプラグインBと干渉します。二つの選択肢があります。

一番目は簡単に見えます (そしてしばしば容易に実装できます) が、要は「泣き寝入り」です。上手く動くからいいや、ではありません。その方法は「仕方なし」といった方法です。もちろん良いのは二番目です。プラグインAと同様の動作をするプラグインは、Bと干渉するはずです。そうなったらまた困る人が出るのです。より一般的で、ベターな方法を提案できるならば、それが良いに決まっています。もしそういう方法がないのならば、プラグインAとBは直交から少しずれており、何かしら似たようなことをやろうとしているということです。

よく分からないと思うので、例を挙げます。

私は以前、thumbnail.vimというプラグインを作りました。

このプラグインと干渉したのが、neocomplcacheでした。thumbnail.vimには、バッファーを絞り込む機能があります。しかし、インサートモードに入ったら、neocomplcacheが補完しようとしたのです。絞り込みに、補完は不要です。およそunite.vimと同じようなことをしようしていると理解していたので、unite.vimのコードを見てみました。すると、unite.vimneocomplcacheのコマンドを使用しているではありませんか!

そうです。unite.vimneocomplcacheの交差する点で干渉することに困ったShougoさんは、neocomplcacheのコマンドをunite.vimに書くことで解決したのです。それは明らかに間違った方法だと確信しました。completefuncというバッファーローカルな変数によって回避できることだったからです。私はすぐにShougoさんにTwitterでmentionし、数日して変更されたと記憶しています。その変更により、unite.vimからはneocomplcacheのコマンドは消えました。私のthumbnail.vimneocomplcacheのコマンドを使用することなく、二つのプラグインが干渉することが回避されました。

プラグインが交差する点にあるのはバグばかりではありません。二つのプラグインの便利な組み合わせ技もあります。例を挙げます。

vimshellでGitHubリポジトリにpushした時に、自動的にpushした旨をTweetVimでツイートしたいと思ったとします。なるほど、便利そうな機能です (私はTweetVimを使ったことがないので、こういうことを実現できるかどうかは知りません)。しかし、それってvimshellで実装すべきことなのでしょうか。或いはTweetVimで実装すべきことなのでしょうか。

こんなことは、どちらのプラグイン開発者も実装すべきではないことです。なぜなら、これらは全く異なるプラグインだからです。プラグインの趣旨が違います。ベクトルが違うのです。これらのプラグインは、ほぼ直交しているのです。その交差する点にある、ユーザーがやりたいことと言うのは、プラグインの作者が提供することではないのです。

もちろん例外もあります。例えば、二つのプラグインの作者が同じ人である場合、或いは処理があまりに複雑になる場合、といった具合です。前者はまだ良いでしょう。APIの変更にも追従できるからです。しかし、後者は新しい「二つのプラグインの交差点としてのプラグイン」として提供されるべきです。或いは、二つのプラグインの提供するAPIが悪いかです。

lightline.vimは、他のプラグインに対しては何もしません。例えばunite.vimではunite#get_status_string()という、statuslineに表示するのに適した便利な関数があります。しかし、lightline.vimで自動的にファイルタイプuniteに対してunite#get_status_string()を設定する、といったことはやりません。それは、lightline.vimとunite.vimが交差する点、「lightline.vim使用時のuniteバッファーのステータスライン」と言う交差点であり、ユーザーが二つのプラグインAPIを組み合わせて設定すべきことだからです。vimshell.vimに対しても、lightline.vimは何もしません。現在のパスが表示されたら確かに便利だろうなぁと、lightline.vimを作った私も思うのに、です。便利ならば提供すればいいといったものではありません。実は、当初はvim-fugitiveというプラグインの関数を用いて、GitHubのブランチを表示する機能をつけていました。vim-powerlineやpowerline、vim-airlineでは標準で付属する、最も基本的で便利な機能だとは理解していますが、私はこれもやはりユーザーが設定すべきだと考え、切り捨てました。


プラグインの交差点は、ユーザーが設定すべきことです。そう出来るように、両プラグインの作者はAPIを考えるべきです。

プラグインの交差点で干渉した場合、より一般的な方法を探るべきです。そういう方法を、干渉する相手と共に考えるべきです。


プラグインの直交性は重要です。

それは私の美学でもあるのです。