qfhl.vim を作った

quickfix は便利ですが、それがバッファ内のどの位置を指しているのか、視覚的には分かりづらいです。

これを解決するための vim-hier というプラグインがあります。このプラグインは、quickfix の位置をハイライトで視覚的に表示してくれます。 実用には十分ではあるのですが、長らくメンテされておらず、またプラグインの構造やハイライトの仕組みが古いことが気になっていました。

一方、現在の Vim には text property という仕組みがあります。これを使えば、いい感じのものが作れそうな気がしました。

というわけで作ってみました。

github.com

(作ってみましたと軽く言ってますが、放置期間も含めると数年越しのリリースです…。)

qfhl.vim は、quickfix および location list の場所をバッファ上でハイライトします。

ハイライトには text property を使用しています。text property はバッファの一部に埋め込まれ、バッファ編集に合わせて位置が移動します。 これにより、ハイライト後にバッファを編集して行がずれたりした場合でも、ハイライトの位置も適切に移動します。

quickfix には行と桁と言う位置情報があり、それを元にハイライトを行っていますが、これだけだとどこまでハイライトをすればいいのかが判断できません。

これは不便なので、Vim 本体に PR を出しました。

github.com

quickfix が持つ位置情報に、オプショナルな終了位置の行と桁の情報を持たせられるようにする変更です。この PR は無事 v8.2.3019 で取り込まれました。

と言うわけで、もし quickfix が終了位置を持っていた場合は qfhl.vim も適切な範囲をハイライトします。 とは言うものの、quickfix を設定する多くの grep 系のコマンドやプラグインは、まだ終了位置を設定してくれないでしょう。終了位置がない場合は、仕方ないので行末までハイライトをします。

現時点で手軽に試すには :vimgrep コマンドが使えます。:vimgrep は組み込みコマンドだったので、上記の PR で終了位置を設定するように対応しています。

f:id:thinca:20210731135546p:plain
qfhl でハイライトを行った様子

最後に注意点ですが、Vim script の新しめの機能を使って書いているため、恐らく Neovim では動きません…。

以上、もし需要が合う方はお試しください。

dein.vim でのプラグインの更新チェックを爆速にする

先日、Vimプラグインマネージャである dein.vim に、プラグインの更新チェックを劇的に高速にする変更が取り込まれた。使うには設定が必要なので、その方法を紹介する。

dein.vim を最新版にする

言うまでもないが、まずは dein.vim を最新版にする。

:call dein#update('dein.vim')

GitHub の Personal Access Token を生成する

この機能を使うには、GitHub の Personal Access Token を用意する必要がある。

https://github.com/settings/tokens

GitHub にログインして上記のページにアクセスし、Generate new token を押す。token の用途がわかりやすいように名前を付け、Generate token を押す。追加の権限は必要ない。生成された token をなくさないようにメモし(ページ遷移すると二度と見れないので注意。メモし忘れた場合は再生成する)、vimrc に以下のように設定する。

let g:dein#install_github_api_token = 'your token'

your token のところに生成した token を書く。言うまでもないが、この token は人に知られてはまずいものなので、vimrc を公開しているのであれば別のファイルに分離するなどの工夫が必要だ。

更新方法を変更する

通常、プラグインの更新には dein#update() を使っていたと思うが、新しい方法では dein#check_update() の第1引数に TRUE な値を渡すことで行う。

call dein#check_update(v:true)

特定のプラグインだけ更新したい場合はプラグインの一覧を第2引数に渡す。

call dein#check_update(v:true, ['dein.vim'])

プラグインを100個単位で入れていたとしても、あっという間に更新のチェックが終わる。

仕組み

ここからはオマケで、高速化の仕組みについて。

Vimプラグインはそのほとんどが GitHub 上で公開されている。今までは、プラグインの新しいバージョンが公開されているかどうかを、リポジトリ 1 つずつチェックしていた。当然 1 件ずつ通信を行うため、プラグインを100個も200個も入れていると、とても時間がかかる。

新しい方法では、GitHub の GraphQL API を使い、リポジトリを 100 件単位で更新があるかどうかをチェックする。このために Personal Access Token が必要になる。

GitHubREST API では 1 度のリクエストでアクセスできるリポジトリは 1 つだけになってしまうが、GraphQL API であれば 1 度の API リクエストで 100 件のプラグインのチェックができるため、更新されたプラグインがなければ非常に高速にチェックが終了する、というわけだ。

この手法については私が雑談レベルで提案していたものだが、今回 Shougo さんがものすごい速さで実装してくれた。ありがとうございます!

更にオマケ: 別の高速化の話

プラグインマネージャは更新処理の際、「今ローカルにインストールされているプラグインのバージョン(Git のコミット)はいくつか」を知る必要があるが、Windows では Linux などに比べると外部プロセスの起動が非常に遅く、リポジトリ毎に git コマンドを実行するだけでも数が多いとかなり時間がかかっていた。

別のプラグインマネージャである minpac ではこの問題を改善するために、「Git のコミットを得るために git コマンドを実行せずに直接 .git/ ディレクトリ内のファイルを読む」という手法を採用していたが、このたびこの方法が dein.vim にも取り込まれた

よって特に Windows ユーザーは以前に比べて更に高速になっている。

なお、こちらの変更は特に設定をしなくても恩恵を受けられる。

Vim で q を prefix キーにする

前置き

Vimプラグインをたくさんいれていると、それらを呼び出すためにキーマッピングを用意したくなることはよくある。

Vim はキーシーケンスに対してマッピングを割り当てられるので、何かのキーを prefix キーとして、そのキーに続けて何かしら機能を割り当てると言うテクニックがよく用いられる。

" スペースキーを prefix にする例

" スペースキー単体では何も起きないようにする
" これをしておかないと、うっかり <Space> + 割り当ててないキーを
" 押すと <Space> の元の機能が発動する
nnoremap <Space> <Nop>

" <Space>q でウィンドウを閉じる
nnoremap <silent> <Space>q :<C-u>quit<CR>

" ... 以下、<Space> + 何かにキーを割り当てられる

しかしこの方法をもってしても、割り当てたい機能はどんどん増えていくものである。prefix キーとして利用しやすいキーはいくつかあるが、それらが枯渇するほど機能を割り当てている人も世の中にはいるようだ。

q を prefix にすることで起きる問題

q は、マクロの記録を開始するコマンドである。q + レジスタのアルファベット1文字で、そのレジスタに対してマクロの記録を開始する。

しかし、26個もあるマクロを全て器用に使いこなしている人はおそらくまずいないと思う。私なんてだいたい1つしか使わない。どこに何が記録されているか、覚えられないからだ。

となると、残りの q + アルファベットは空いていることになる。q を prefix にすればこれらが使える。何ならアルファベット以外の文字も使えることになる。

nnoremap q <Nop>
nnoremap qa :<C-u>echo 'Awesome feature!'<CR>

こうすることで、qa は無事便利機能に割り当てることができる…が、こうしてしまうと、q<Nop> にすることで、マクロの開始が一切できなくなってしまう。

これはさすがにまずい。しかしこれは単に q<Nop> を割り当てなければ解決する。

nnoremap qa :<C-u>echo 'Awesome feature!'<CR>

マクロの記録も開始できる。これで何も問題はない…?

更に潜む問題

Vim のマクロの記録は q 単体で終了する。ところが、q で始まるキーマッピングがあると、押された q がマクロの終了であるのか、はたまたキーマッピングの呼び出しなのかを判断するため、Vim はしばらくの間キーマッピングの待受をする。

待受時間を待ってやり過ごすか、q で始まるキーマッピングではない何か別のキーのコマンドを実行すれば済むのだが、いかんせんやきもきしてしまう。やはり prefix 以外のキーマッピング待ちは発生させたくない。

解決策

今回、この問題を解決するために以下のように設定してみた。

nnoremap <script> <expr> q reg_recording() is# '' ? '<SID>(q)' : 'q'

nnoremap <silent> <SID>(q) q
nnoremap <silent> <SID>(q)a :<C-u>echo 'Awesome feature!'<CR>
nnoremap <silent> <SID>(q)<Space> :<C-u>quit<CR>

q を押した際に、マクロの記録中であれば即座に q を起動してマクロの記録を終了させ、そうでなければ prefix としての q を呼び出す。prefix の設定がない場合はそのまま q として機能させることで、マクロの記録開始も行えるようにする。

こうすることで、q を prefix として機能させつつマクロの記録開始/終了に影響を与えないようにすることができる。私は <Space>q をよく q<Space>誤爆することがあったので、同じ機能を割り当ててみた。

今回はなんとなく <SID> を使ってみたが、q prefix のキーマッピングを複数のスクリプトで定義したい場合は <Plug> を使っても問題ないだろう。

nmap <expr> q reg_recording() is# '' ? '<Plug>(q)' : 'q'

nnoremap <silent> <Plug>(q) q
nnoremap <silent> <Plug>(q)a :<C-u>echo 'Awesome feature!'<CR>
nnoremap <silent> <Plug>(q)<Space> :<C-u>quit<CR>

この場合は q のキーマッピングnnoremap ではなく nmap を使うことに注意する必要がある。

残る問題

ここまでやっても一応まだ問題は残ってる。ここで定義した q で始まるキーマッピングは、マクロの記録中に呼び出すことはできない。 しかしこれはある意味当然であり、マクロ記録中に呼び出したい機能を q に割り当てないようにする他はない。

VimConf 2019 が開催されて1週間が過ぎました

今年も無事 VimConf を終えることができました。私は今年もスタッフの一人として参加しました。参加してくれた皆さん、発表してくれた皆さん、スタッフ、そのほか関係者の皆さん、ありがとうございました。おつかれさまでした。

1週間経ったこともあり、感想記事が続々上がってきています。また、(遅くなりましたが)参加した皆さんにアンケートのお願いも送信し、続々と回答が集まっています。

VimConf の運営スタッフは全てボランティアです。スタッフには金銭的な報酬は一切ありません。まあこれは別に珍しいことではなく、大抵のコミュニティドリブンのカンファレンスはどこもそうだと思います。

それでいてやることは山のようになります。みんなプライベートの時間を切り崩してなんとか対応しています。みんなできる範囲でやっているので、負担が偏ったりもします。たぶん私は他のメンバーに比べると負担は軽かった方。みんなありがとう。

話が逸れた。こういった状況の中で、多くの感想記事やアンケートでの意見を拝見していると、大変だったけどやってよかった、と思えます。今のところ発見できた感想記事は全て目を通しています。ありがとうございます。

イベントの盛り上がりと継続と言う意味で、フィードバックは非常に重要です。「もうみんな書いてるし…」「イベントから時間が経ってしまったから…」そんなことはありません。今からでも遅くないです。あなたの記事を見た誰かが、次回は参加したいと思うかもしれません。

…感想を書こうと思ったら感想にすらなっていないポエムになってしまった。 ともあれ、カンファレンスは、参加してくれる皆さんがいてくれてこそ成立しています。そして参加してくれた皆さんがまた参加したいと思ってくれたのであれば、運営スタッフの一人としてこれほど嬉しいことはありません。

ライブコーディングで作ったプラグインを整理して公開した

先日、ゴリラ.vim #9 に参加してライブコーディングをしてきた。

gorillavim.connpass.com

その際に作ったプラグインを整理して最低限の形した。整理する過程で色々変わったのでその話など。

公開したプラグインは以下。

github.com

どんなプラグインなのか

これは日本語における、ひらがな⇔カタカナの変換や全角⇔半角の変換をするプラグイン

極稀に欲しくなる操作で、サクラエディタなどには標準で付いている。Vim でもやりたかったので作ってみることにした。ネタとしても小さめでライブコーディングでもなんとかなるかなという目論見。

試験的なプラグインということもあって、無駄に最新バージョンの Vim を要求する。scriptversion 4 が通らない Vim では動かない。

プラグイン

変換するという想定でライブコーディング時には convja.vim という名前で作り始めた。

しかしあとであれこれ考えて、文字種の判定や抽出もできると便利かも(やるとは言っていない)と思い、汎用的な処理ができるように jautil.vim という名前にしてみた。無駄に汎用化してしまうのは私の悪い癖である。わかってはいる…。

インターフェース

ライブコーディング時では、以下のようなインターフェースを想定していた。

echo convja#convert('123アイウ', {'target': 'hankaku', 'type': 'kana'})
# => '123アイウ'

しかし作っているうちに紆余曲折があり、もっとシンプルに指定できるようにしたいなぁという気持ちになったので以下のような形式に落ち着いた。

echo jautil#convert('123アイウ', 'hankaku:kana')
# => '123アイウ'

簡易とはいえ特殊な文法を知る必要があるが、まあ辞書の形式を知るのと大差ないだろう。

拡張

第2引数の target に配列を渡せるようにした。これにより、まず半角カナを全角カナにし、その後全角カナをひらがなにすることで全角/半角両方のカナをひらがなに直す、ということが1度の呼び出しでできる。

echo jautil#convert('アイウエオカきくけこ', ['hankaku:kana', 'katakana'])
# => 'あいうえおかきくけこ'

また、第1引数に配列を渡すことで第1引数と第2引数を入れ替えられるようにした。これにより、partial を使って特定の変換をする関数を用意することができるようになる。

let H2Z = function('jautil#convert', [['hankaku']])
echo H2Z('abc')
" => 'abc'

今後の展望

あれこれこうできたらいいなと思っていることはあるが、優先度は激低なのでやるかは不明。ライブラリだし vital module 化したいよなー。

もっとちゃんと使いたいぜって人がいたら Issue を立てたりしてください。やるかどうかはわからないですが…。

ゴリラ.vim #1 に行ってきた

ゴリラ.vim #1 に行ってきた。

この規模の Vim イベントで定員オーバーは初めて見たかもしれない。しかもまだ実績のない第1回でこれなのだから、ゴリラさんの求心力の高さが伺える。

会場は渋谷ヒカリエにある株式会社ディー・エヌ・エーさん。セキュリティゲートの受付で思わず「うほうほ」と挨拶しそうになったけど、なんとか堪えました。危なかった。

発表は初心者向けから上級者向けまでいい感じにバラけており、後になるほど高度になっていく感じで発表順序も完璧だった。満足度が高い。

dice_zu さんが私の作った showtime.vim を使ってくれてて嬉しかった。もうちょっとちゃんとメンテしないと…。

懇親会では寿司とカツサンドをつまみながらおしゃべり。学生さんが多かった印象。ちょいちょい移動しつつ話してたけども、更にもうちょっとあちこち移動して話をすれば良かったと少し後悔。

この感じのイベントを今後は毎月のペースでやる予定とのことで、今後が楽しみです。

帰り際のゴリラさんとの会話

ゴリラさん「thinca さん、次回は発表してください!」

マンボウ「そうですね。検討してみます」

ゴリラさん「検討じゃなくて、してください!」

マンボウ「あ、は、ハイ…」

ということで次回は何か発表することになった。進撃のゴリラには敵わなかったよ…。何を話そうかなぁ。

VimConf 2018 で Bram Moolenaar さんと会った話

2018年11月24日に VimConf 2018 が開催されてから、早いものでもう 2 週間経った。

2 週間経ってしまった…。2 週間…早い…。

このままだと私の VimConf が終わらないので、正直何を書けばいいのかわからないのだけど、なんか書こうと思う。

VimConf 2018 前日

今年の VimConf は色んな意味で、忘れられないものになった。

2018 年 11 月 23 日。若干の発熱により、私は寝込んでいた。翌日には VimConf が控えていると言うのに、ここに来てまさかの風邪…。 幸いにも辛さはほぼなく、微熱が主な症状。一日中寝て過ごすことになったわけだけど、この間にも様々なことを考えた。

症状がひどくなったら VimConf は欠席せざるを得ない。私は当日にするべきタスクを持っていなかった*1ので、最悪私がいなくてもイベントの開催には支障がない。とは言え半年以上かけて準備をしてきたイベント。しかも Bram さんと会えるまたとない機会。これを逃がしたら次はないだろう。Vim に携わる者として、多少無理を押してでも行きたい…。しかし Bram さんに会えたとして、私は英語が大の苦手である。言いたいことはほとんど言えないだろう。Bram さんが何を言っているのかも、聞きとれる自信がない。正直、その点で言うと会うのが怖くすらある。そしてもちろん、症状が回復したとしても病み上がりの身。周りには迷惑をかけることになるだろう。だとしても…。

VimConf 2018 当日

そうこうして迎えた 2018 年 11 月 24 日。体調は完調とまでは行かないものの、出かけられそうなくらいにはなっていた。後悔はしたくない。普段より厚着をして、あまり好きではないマスクを着け、意を決して家を出た。

予定より少し遅れて会場に着くと、すでに開場待ちの人達がちらほら。KoRoN さんに呼ばれてスタッフルームに入る。「Bram さんもう来ているよ。mattn さんと話してる」

本当にこの時が来たのだな、と思った。その後、まだ開場前のホールで、Bram さんと会った。私は名前くらいしか言えなかった。そしてたぶん、私のことなんて Bram さんは知らないだろうと思う。スタッフジャケットを着ていたので、かろうじてスタッフだということは伝わった気がする。握手してくれた。めっちゃドキドキした。

ただでさえない文章力が崩壊してきた…。えーと、ともあれ私は Bram さんと対面することに成功した。でもやっぱり何も話せず、懇親会も含めてこの日は Bram さんとはこれっきりだった。

VimConf 2018 翌日

2018 年 11 月 25 日。VimConf 2018 も終わり興奮も冷めやらぬ中、VimConf の裏イベント、vimthon が開催された。VimConf スタッフと一部の Vim 開発者、そして Bram さんを迎えてわいわい Hackathon をするイベントだ。当日の様子をまとめた Togetter がある。

f:id:thinca:20181125093709j:plain
VimConf Hackathon

体調が悪化していたらキャンセルしようかとも思っていたが、幸いにもどちらかと言うと回復してきていたので、こちらも参加できた。

全員が揃ってしばらくしたところで、全員がそれぞれ自己紹介。中には簡単なものではなく、人によっては作ったもののデモなどをガッツリ、と言う感じの濃い自己紹介もあった。

座席の配置の関係で、最後は私の番。簡単に済ませることもできたのだけど…ここは少し勇気を出して、自分で作ったプラグインをいくつか紹介した。あまり多くの種類はデモできなかったけれど、どのプラグインを見せようかと選ぼうとした際に vimrc に書かれた私のプラグインのリストが画面に映り、これは全部私が作ったものだ、という話をした。

会の終わり際、Bram さんが帰る頃合いになった。別れを惜しみつつみんなそれぞれツーショット写真を取ったりした。その際に Bram さんに、「プラグインをたくさん作ってくれてありがとう」と言われた。多分。英語のヒアリングに一切自信がないけど、たぶん言われたと思う。本当に嬉しかった。

最高の体験

いつにも増して文章力のないただの日記になってしまった…。とにかく私は最高の体験をしました。

ここのところ Vim 活がまったく捗っておらず、いかんともしがたい状況が続いているので、Vim 活がんばろう。

おしまい。

*1:私に限らず、当日にするべきタスクを極力減らす方向でスタッフ全体で準備していた