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 に割り当てないようにする他はない。

Vim Short Tips Advent Calendar を開催しました

全然ブログを更新しない thinca です。

さて、今年も Advent Calendar の季節がやってきました。あちこちで大量の知見が出回っていますね。

記事が増えるのはいいことなんですが、一方で私はこの流れにあるつらみも感じていました。

書くのがつらい

長編記事は書くのが大変です。いや、別に誰も長編記事を書いてくれ、とは言っていません。

しかし上がってくる記事はどれも力作。こんな流れの中、しょぼい記事は上げづらい、なんて感じたことはないでしょうか。

特に日本人は空気を読むように訓練されている節があるので、ハードルが高いと感じてしまう人もいそうな気がします。というか私は大作記事は大変なのでできれば書きたくないです…。

読むのがつらい

特にこの時期に大量の記事が出回ることになるので、楽しみな反面、読むのが大変です。

途中で挫折して読むのをやめてしまったカレンダー、ありませんか。

試験的な試み

そこで今回試しにやってみたのが、Vim Short Tips Advent Calendar です。

内容を、Twitter の 1 ツイートに収まるものに限定し、書く手間と読む手間を下げよう、という試みです。テーマが Vim なのは私が Vim 界隈で活動しているからですね。

内容も、高度なものである必要はありません。というか短いので高度なことは書けません。知ってるとちょっと便利な Tips がメインです。

実はこれ、11 月中にカレンダーがあちこちで立てられるなかぼんやり考えていたものなんですが、vim-jp コミュニティ内でコンセプトを説明して参加者を募ったのが 11/30 の 20 時頃。実際にカレンダーを立てたのはその1時間後の 21 時頃というギリギリでの開始でした。

にも関わらず、参加者はあっという間に集まり、最初数日分は即座に埋まり、その辺りを走っているうちに最後まで全てメンバーが埋まりました。コンセプトの狙い通り、参加のハードルを下げたのが功を奏したのだと思っています。

参加者や各 Tips を見てくれた人からも概ね好評だったように思います*1。こういうやり方もありだな、と感じました。

Tips 一覧

せっかくなので Tips 一覧を引用してみたいと思います。短いので全部並べてもサクッと読めますね。お気に入りの Tips を見付けたら、ぜひいいねや RT をしてあげてみてください。

1日目

2日目 3日目 4日目 5日目 6日目 7日目 8日目 9日目 10日目 11日目 12日目 13日目 14日目 15日目 16日目 17日目 18日目 19日目 20日 21日目 22日目 23日目 24日目 25日目

おまけ

*1:アンケートを取ったわけではないので私が感じた限りでは

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:私に限らず、当日にするべきタスクを極力減らす方向でスタッフ全体で準備していた

Travis で並列テストが終わってから deploy する

Travis では複数のジョブを並列で実行できる。複数の処理系のバージョンでテストを回したりするのはよく行われている。

また、Travis は後処理でデプロイも行える。専用の設定をする項目があり、一部の有名所のデプロイ先については雛形が用意されていてオプションの項目を埋めるだけだ。

私は今まで Travis から https://www.npmjs.com/ へ npm パッケージをデプロイする設定をしていたのだけど、普通に設定しているだけだと並列で実行されている全てのジョブがデプロイを実行するので、多重デプロイが発生していることに気付いた。

幸い www.npmjs.com は同じバージョンのデプロイをエラーとして弾いてくれていたのでデプロイ自体は問題なく行われていたが、いかんせん無駄だし、気持ち悪い。また、この設定だと一部のジョブでテストが失敗しても、どれか1つが成功してしまえばデプロイされてしまう。これは問題がある。

そこでなんとかする方法を調べた。

Build Stages

Travis には Build Stages というまさに今回の問題のための機能があり、これを使えば問題が解決することがわかった。

全てのジョブはいずれかのステージに紐付けられており、各ステージではステージ内の全てのジョブを並列に実行し、かつそれぞれステージはステージ内のジョブが全て成功してから次のステージに進む。

実は何もしなくても、通常の設定のジョブは全てデフォルトである test ステージに属していることになっている。

今まで行っていた設定は以下のような感じ(一部改変)。

language: node_js
node_js:
  - "node"
  - "6.0"
  - "6"
  - "7.0"
  - "7"

sudo: false

cache:
  directories:
    - node_modules

before_install:
  - npm install -g codecov

script:
  - npm run lint
  - npm run coverage  # テストしつつカバレッジを取る

after_success:
  - codecov

deploy:
  provider: npm
  skip_cleanup: true
  email: thinca+npm@gmail.com
  api_key:
    secure: {travis encrypt コマンドで設定した値}
  on:
    tags: true

これを以下のようにした。前半は全く同じで、最後だけ違う。

language: node_js
node_js:
  - "node"
  - "6.0"
  - "6"
  - "7.0"
  - "7"

sudo: false

cache:
  directories:
    - node_modules

before_install:
  - npm install -g codecov

script:
  - npm run lint
  - npm run coverage  # テストしつつカバレッジを取る

after_success:
  - codecov

jobs:
  include:
    - stage: npm release
      if: tag IS present
      node_js: "node"
      cache: {}
      before_install: skip
      install: skip
      script: skip
      after_success: true  # Don't skip: To trigger the deploy
      deploy:
        provider: npm
        email: thinca+npm@gmail.com
        api_key:
          secure: {travis encrypt コマンドで設定した値}
        on:
          tags: true

まず前提として、これは Git のタグを push した時にだけデプロイをする設定だ。if: tag IS present で、タグが存在する場合にのみこのジョブを実行する。このジョブは stage: npm release によって npm release ステージに属しており、これはデフォルトの test ステージの次に実行される。

jobs.include 以下の各ジョブは、トップレベルの設定を引き継ぐ。しかし、デプロイ時に再度テストを実行する意味はない。そこで各ステップを skip として飛ばすようにしている。この辺りは、テストの設定も jobs.include に書いてしまえば解決するのかもしれないけど、試していない。

そして次がハマったところで、どうも after_successskip してしまうと、デプロイ処理自体行われなくなってしまう模様。ドキュメントにも特に記載が見つけられず、かなり試行錯誤した。なのでここは skip せず、適当に何もせず成功するコマンド、今回の場合は true コマンドでトップレベルの設定を上書きした。

という感じで、無事Travis から npm パッケージのリリースに成功した。

これで今後は npm version patch 等でバージョンコミット&タグを作成したあと git push origin master --tags でタグを push するだけで npm パッケージがリリースできる。ラクチン。