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

ElixirConf Japan 2017 に行ってきた

ElixirConf Japan 2017 に行ってきたよ。

最初に書いておくと、私は Elixir に関しては全く書いたことがなく、Erlang についても遥か昔にちょっとだけ触って、今は忘却の彼方という感じ。 さすがに行く前にチュートリアルくらいやっておくか、と思ってたのだけど、結局時間が作れなくてできずじまい…。

何しに行ったんだよ感がすごいけど、まあ色々と縁があったのでお邪魔しました。

感想を書くけど、こういうレベルなので適当なことや的外れなことを書いてると思う(いつものこと)。 (あと特に後半はバッテリー切れによりほとんどメモが取れてないので記憶がやばい。)

「Opening Keynote」 by José Valim

Elixir とはどういった言語なのか、その歴史と、未来の話、だと思う。たぶん。

ゆっくりしゃべってくれて聞きとりやすいと評判の英語だったけど、基本的なリスニング能力がそもそもないのでゆっくりだろうが聞きとれない程度の英語力でした。本当にありがとうございました。

Elixir も英語もわからないなりに聞いた感じ、型システムの導入を検討している点が興味深かった。型システムが入れば Elixir の売りの一つであるメンテナンス性も上がりそうな気がするし、型いいよね。

Phoenix で作るスケーラブルなリアルタイムゲームサーバー」 by hdtkkj

ミクシィのゲーム部門、XFLAG の方の発表。モンストの裏側の話。

Phoenix で作るにしてもやはり苦労ポイントは色々あるようで、なんかよくわからないけどすごそう(小並感)。

実際にサービスで動いているのかという質問に、実はまだ動いてないという回答は色々と察するものがあった。

「Elixir から始める関数型言語」 by tuvistavie

発表資料: http://tuvistavie.com/slides/elixir-fp-intro/

パターンマッチ、末尾再帰最適化、高階関数、イミュータビリティ、そしてイミュータブルな世界でどうやって状態を扱うのか。

最初に書いた通り、Elixir 入門以前みたいな感じなので、チュートリアル的な内容はとても助かった。私の場合、関数型を(ある程度)知っている視点で Elixir の機能を知ることができた。

= がパターンマッチなのはとてもおもしろい。ただ今は型システムがないようなので、型が入るともっと色々できるようになるのかなーと想像した。

「Elixir 導入提案」 by cooldaemon

gumi の CTO の cooldaemon さんが、 Erlang や Elixir とどのように出会い関わってきたか、みたいな話。いや、話の主題はそこではないけども。

SLA 99.9982% というキビしい条件が求められるところ、Erlang なら 99.9999999% が可能、と。Erlang まじすごい。

Erlang 導入したけど変更多い部分は Perl 強要されたり、Scala に浮気したけど Elixir に戻ってきたり、色々あって聞いてておもしろかった。特に Erlang 使いたいがために出世するというのがすごい。

というわけで gumi は今後 PythonErlang をやめて Elixir でリプレースしていくらしい。すばらしい。

「Elixir はリアルタイムWeb に強いというのは本当か?」 by mururu

発表資料: https://speakerdeck.com/mururu/elixirkariarutaimuwebniqiang-i-toiufalsehaben-dang-ka

難しくて完全に雰囲気で聞いてた…。プロセス可視化のやつが見た目すごそうな雰囲気出ててよかった。

Rubyist |> (^o^) |> Alchemist 〜Elixirの採用からサービス稼動までの記録〜」 by ohr486

発表資料: https://www.slideshare.net/ohr486/elixirconfjapan2017sessionohr486

どのようにしてチームに Elixir を導入するか。教育の手法などの話。

かなり具体的な実務的な内容で、これから Elixir を採用しようと思ったチームにとってすごく参考になりそうな話だった。

個人的にはそういった立場にないのでアレだけど、並列に動くように書けるか、とか、テストなんかの話もあったので個人で何かやる場合にも参考にできそう。

「ニコニコを支えるErlang/Elixir 〜 大規模運用して初めて見えたアレやコレ」 by kojingharang

発表資料: http://niconare.nicovideo.jp/watch/kn2397

ドワンゴの Elixir の導入規模がやばくて会場(TL)がどよめく。全体的にやってることが大規模かつ高レベルで、ただただすごいなーという感想だった。

なんか今発表資料見たら当日使われてなかった(収まらなかった?)ページが最後の方にあった。ツールちゃんと作ってるのもさすがだし見習いたい。

LT: 「Why did we decide to start using Elixir?」 by ma2ge

発表資料: https://speakerdeck.com/ma2gedev/why-we-decided-to-start-using-elixir

なぜ Elixir を使うのか、という問いに対して 3 つの理由を挙げている。

実際にはこれ、Elixir に限らずあらゆるものに対して適用できそうな理由なので、なぜ Elixir という問いの答えにはなってなさそうだけど、理由については共感できるものだし私も新しいことやって行きたいと思った。

LT: 「「Surge」 - Amazon DynamoDB for Elixir」 by hirocaster

発表資料: https://speakerdeck.com/hirocaster/surge-amazon-dynamodb-for-elixir

Surge の紹介。この辺りの基礎知識がなさすぎたので、キーワード聞いたら思い出す程度にとどめておこう。

LT: 「初心者のElixir |> Flow,GenStage |> Concurrent MapReduce」 by ovrmrv

発表資料: http://qiita.com/ovrmrw/items/14dc9907c2ec699f849c

Flow を使うと並列処理してくれてすごいらしい。すごい!

逆に言えば、いくら Elixir 使ってもちゃんと意識して書かないと並列にはならないってこと。当然だけど大事。

LT: 「AngulaとElixirの新しい関係」 by isyumi_net

発表資料: https://www.slideshare.net/ssuserd34c39/angulaelixir

Vim の設定が大変だった部分がどう大変だったのかが気になりすぎて他のことを覚えていない。ごめん。(単にフロントエンドよくわかってないというのもある)

LT: 「Elixirでbotkitを倒す」 by ne_sachirou

倒せなかったらしい…。

Botkit と Hedwig を比較した。Botkit は速いけど安全じゃない。Hedwig は、速くはないが安全。

LT: 「APIサーバーとしてのCowboy」 by hayabusa333

軽量サーバ Cowboy の紹介。そもそも Phoenix の内側で使われているのが Cowboy らしい。私なんかはそもそも Phoenix が Web フレームワークだということをこの日に知った感じなのでとてもアレ。

LT: 「Elixir client from OpenAPI(Swagger) definition」 by niku_name

発表資料: http://slide.rabbit-shocker.org/authors/niku/elixir-conf-japan-2017-04-01-lt/

Elixir 用の OpenAPI のアレ。Swagger はちょっと触ったことあるけど、あれいいですね。Elixir でも使えるというのはとてもよい。

LT特別枠(15分): Erlang and Elixir Factory SF Bay 2017 参加報告」 by jj1bdx

発表資料: https://speakerdeck.com/jj1bdx/erlang-and-elixir-factory-sf-bay-area-2017-can-jia-bao-gao

当然(?)ながら、私はこの Erlang and Elixir Factory というもの自体知らないわけなんだけど、いやはや会議日程がすごい。トレーニングやばい。

で、恐らく一番伝えたかったのは最後のスライド。「日本の皆さんもすごい成果出しているので発表しましょう!」

私も分野は違えど何かしてもあまり発表とかしたがらない人なので(あまり大したことはできてないけど)、割と他人事ではない気もしつつもうぐぐ…英語~。

「クロージング Keynote」 by Voluntas

発表資料: https://gist.github.com/voluntas/81ab2fe15372c9c67f3e0b12b3f534fa

ここまで、Erlang VM つよい!すごい!みたいな話が多かったけど、まあ当然いいことづくめってわけにも行かないわけで。欠点も知っておきつつ、長所が活きる場所で使う。そのために欠点の部分もちゃんとカバーされていたのはとてもよかった。

特に技術の dis って(今回のは別に dis ではないけど)、本当にその技術に精通している人だからこそできるものだと思う。最後にこの話が聞けてよかった。

まとめ的なやつ

ほぼ知識ゼロで参加したけど、全体を通して、Erlang VM はとにかく堅牢性が高い、落ちない、そしてとっつきづらい文法の Erlang にかわって、書きやすくメンテナンスしやすい言語として Elixir があるということがとても伝わってきた。

Erlang や Elixir が注目されている理由について今までピンと来ていなかったので、そこがわかっただけでもだいぶ収穫だった。

未経験で、場違いなんだろうなーとドキドキしながら参加して、実際場違い感はあったけど、思い切って参加してよかった。来年までにはチュートリアルやっておきます。

Meguro.vim #2 リベンジを開催した

Meguro.vim #2 リベンジなるイベントを開催したので、簡単にレポートっぽい何かを。

元々は 2/25 に Meguro.vim #2 が開催予定だったのだけど、私が風邪を引いてしまい開催できなかった…参加登録してくれた方には申し訳なかった…。というわけで、仕切り直して 3/25 に #2 リベンジという形でなんとか無事開催!

本編

今回はたまたま Osaka.vim #9 at Kobe が同じ日に開催されており、Lingr でお互い挨拶した結果、その場のノリでお互いの会場を 無駄に Google ハングアウトで繋ぐことに。こうしてサテライト会場っぽい感じになってとてもよかった。 どちらももくもく会だったので、もくもくしたりたまに発生する雑談の様子などをお互いに見ることができてとても良かった。別の機会でもできそう。もっとやって行きたい。

本編の内容では、duzzle.vim が空前の大ブーム。作られたのはかなり昔だけど、その完成度は高く、絶賛の声が多数。続編も望まれているので、みんなでプレイして続編を待ちましょう。

また、参加者は私を入れて 9 人と多くはなかったのに、事前に知らなかったけど実は(元)同じ会社の人が参加していた、みたいなのが 2 組も発生し、業界狭い事案だった。すごい。

晩ごはん

晩ごはん会は、出席率100%を達成!次回もこの調子で行きたい。あ、参加を強制するとかではなく。

おまけ: 試射会

今回は日程が Splatoon 2 の先行試射会と重なったので、思いきって試射会も一緒にやる形式にしてみた。実際に参加したのは、昼の部はほとんどおらず、ハードを持ってきたも私だけだったけど、夜はそこそこいて、@yysaki さんがハードを持ってきてくれたので大型のテレビとプロジェクタの2面で Splatoon 2 を遊ぶことができた。終了後も 1-2-Switch でかなり盛り上がってかなりよかった。

結果として何人かに布教することができたように思う。計画通り。

振り返り

実は終了後にアンケートも兼ねて任意参加で参加者みんなで KPT をやろうと思っていたのだけど、気付いたら月曜で今さらみんなに書いてくださいって言いづらくなってしまったので、反省。次回はちゃんと終わったあとまで考えて準備しないと。

あと、この記事自体書くのが遅すぎる。遅くても翌日には書きたいところ。この辺りも次回の課題。

大江戸Ruby会議 06 に行ってきた

元々行く予定は特になかったのだけど、ujihisa さんに誘われて前日に行くことを決定。枠に余裕があってよかった。

というわけで大江戸Ruby会議 06 に行ってきたよ。いつも通り雑にゆるい感想を。

Docker時代の分散RSpec環境の作り方 - @joker1007

資料: https://speakerdeck.com/joker1007/dockershi-dai-falsefen-san-rspechuan-jing-falsezuo-rifang

Docker で CI の時間を 1/3 にした話。実践的な内容で、すぐ試したくなる。

懇親会で気になっていた点を 2 点ほど聞いてみた。

カバレッジの集約をどうしているのか、という点は、カバレッジについても分割して統合する仕組みがすでにあるらしく、それでよしなにしているみたい。

もう 1 つ、どういう単位で分割をしているのか、という点については、今は適当にディレクトリ単位とかでやっているらしい。がんばれば過去の実績からいい感じで分割はできるはずだし実際そういうのもあるらしいのだけど、そこまでやらなくてもとりあえず成果が出たので今はそこまでやってないのだとか。実際そこまでやろうとするとコストも上がるだろうし、バランスってことでしょうね。参考になる。

My Open Source Journey - @JuanitoFatas

資料: https://speakerdeck.com/juanitofatas/my-open-source-journey

発表者の JuanitoFatas さんは台湾から最近日本に越してきたらしく、英語での発表。 せっかくなのでがんばって聞きとろうとしてみる(聞きとれるとは言っていない)。

内容は、たぶん、オープンソースとの関わり方、みたいな感じだったと思う。私自身オープンソースに関わる身として、関わる人もっと増えるといいなーと思った。ただ私はもっと英語なんとかしないとダメだな(しかしなんとかする気はあまりない)。

Text Editing in Ruby - @shugomaeda

資料: https://github.com/shugo/oedo06

Ruby で書かれたテキストエディタ Textbringer の話を、Textbringer 上で実装されたプレゼンツールで発表。

テキストエディタを作るのって、考慮しないといけないことが無限にあってとても大変そうというイメージがあって、実際に作っているのは本当にすごい(小並感)。

Ruby考古学II 1993-1997 - @keiju_ishitsuka

Ruby の名付け親、石塚さんの発表。当時の様子を知る人によって語られる貴重な Ruby の話。

初期の頃の Ruby は今と全然様相が違っていて、そこから今の形になっていったかと思うと感慨深い。

多相型、推論、Ruby - @soutaro

資料: https://speakerdeck.com/soutaro/duo-xiang-xing-tui-lun-ruby

今の Ruby に型を導入しようとした場合、どのようになるか、という考察。

あちらを立てるとこちらが立たず、という感じで、果たして Ruby の型は実際にはどうなっていくのか、興味深い。

esaRubyistと私 - @ken_c_lo

デザイナの方の発表。こういう方もコミュニティにいるんですね。懐の深さと広さを感じる。

デザイナとして Ruby エンジニアと関わっていった話で、発表者については私は全然存じ上げてなかったのだけど、発表から感謝の気持ちがとても伝わってきて、とてもいい話だった。

Rationalを(もうちょっと)最適化してみた - @tad

地道な最適化の話。毎回 Ruby の関数を呼んでいたところを、C 側で完結させることではやくしたらしい。

こういう地道な貢献がみんなの利便性を高めていると思うので、本当に有り難い。

フルタイムコミッター大戦

フルタイムコミッター4名による早押しクイズ大会。それぞれが問題を作り、残りのメンバーが早押しで答える形式。 早押しボタンに Nintendo Switch のジョイコンを使い、専用の Web アプリケーションで早押し制御する、すごい感じだった。

以下、雑なメモ。用語がマニアックなのと口頭だったのもあって、間違ってたらごめんなさい。出題者と回答者まではメモれなかった…。

  • Q1 RubyにはGCなどの設定を環境変数として渡すことができる。この数を答えよ
  • A1 14個

  • Q2 Ruby の BTS feature 10000 は何?

  • A2 format width and precision with symbol hash

  • Q3 Ruby2.4.0のParseにはいくつの非終端記号が定義されているか

  • A3 166個

  • Q4 yy.lex で読み込みが終了する条件は、ファイル終端、__END__ の他は何?

  • A5 C-z C-d NUL (想定回答)

  • Q5 make ruby make love make great make again 実際に使えないのはどれ?

  • A5 make again

  • Q6 Ruby 3 で導入を目指しているギルドでもっとも困難な問題は?

  • A6 名前付け

  • Q7 Git リポジトリ上で2番目に変更が多いファイルは?

  • A7 io.c

  • Q8 instance_variable_get はキャッシュヒット率が高いが、set_instance_variable のキャッシュヒット率は低い。なぜか?

  • A8 最初は必ず失敗するから

  • Q9 Forwardable で警告が出る件の修正Reject した理由は?

  • A9 respond_to_missing? を再定義していなかったから

  • Q10 Math クラスに一番最近追加されたメソッドは?

  • A10 cbrt

  • Q11 Ruby 2.4.0 のソースツリーの中に .rb ファイルはいくつある?

  • A11 2383 個

問題が濃すぎる…!実際正答が出ずにおまけで正解にしたりする問題が続出した。カオス。

高濃度雑談 - @ujm

資料: https://docs.google.com/presentation/d/1diEe7qYLjkHrjKoPNgyhlLItxV6ahnNgsfXdId0EmNI/edit

Ruby の話かと思いきや、ほぼほぼ Vim の話で、いつも通りすぎてさすがだなーという感じだった。

と思ったら今度は料理の話になって、残り1分コールの時点で残り半分くらいになって怒涛の追い上げで最後まで辿りつくすごい発表だった。すごかった。

Ruby 2.4 Internals - @ko1

Ruby 2.4 で行われた高速化手法についての解説。

Ruby 2.4 で Write Barrier 回りをすごくいい感じにしてとてもはやくなったらしい。すごい。

笹田さんはすごいことをやった。覚えた。

如何にして若き天才コミッタは生まれるのか - @rosylilly

Keynote の sorah さんとの思い出話。

天才ができるにはちゃんと才能を活かせる、育てられる環境が必要みたいな話で、sorah さんがどのような環境で育ってきたのかという話。

言ってしまえば概ね内輪話なのだけど、そこからちゃんと学びがあって、いい話だった。

Keynote - @sora_h

sorah さん自身による半生振り返り。珍しく自慢話します、とのことだったけど、実際自慢に足る内容だった。

全体的にすごかったけど、インターネットを始める話が完全にすごすぎて、なんかもうすごいという感想しか出てこなかった。この感想記事、全体的にすごいしか書いてない。

懇親会

懇親会の枠は、急遽参加を決めたこともあってすでに埋まっていたのだけど、当日枠があったのでなんとか参加できた。セーフ。

Ruby コミュニティのイベントに参加するのってもしかしたら実は多分初な気がして、普段インターネットで見かける Ruby 界隈のすごい人達と実際にお話しできてとても良かった。

個人的にはよく見かけて気になっていた @joker1007 さんとお話しできたのはとてもよかったし、他にも色々懐しい人なんかともお話しできたので本当に良かった。さっきから良かったしか書いてない。つまりまとめると良かった。

全体的な感想

この規模のイベントに参加したのは本当に久しぶりで、いい刺激になったように思う。

最近アウトプットが全然できていなくてとてもアレなので、ちゃんとやって行きたい。行きたい、なぁ…。

Yokohama.vim #9 に行ってきた

かなり今更感あるけど、Yokohama.vim #9 に行ってきたよ!

ここのところ全くブログが書けてないので、イベントの参加記事くらい書くか、という感じでめっちゃ遅いけど書きます。

第10回!

今回は #9 だけど、実は #0 からやっているのでついに第10回!めでたい。 いつものメンバーがいつつも、(たぶん)初参加の人とかもいたりして、新しい人が常に入ってくるのはコミュニティとして健全でよいですね。

基調講演

https://docs.google.com/presentation/d/1Kjt-GB9sAy0ViMMeF8–8KS7p2vAtd08u5MC_EiFHyg/edit#slide=id.pdocs.google.com

ujihisa さんの基調講演、のはずが、ujihisa さんから突然基調講演をやるよう無茶ぶりをされる。無理…。 今思えば、quickrun.vim の runner/job を見ながら job の使い方解説くらいはできた気がしたけど、とっさには思いつかなかった。瞬発力が足りない。

懇親会

他にも色々やった気がするけど、やはり間あけてはダメですね。記憶が…。 個人的には MacOS Sierra で使えなくなった Karabiner の代替として @tsukkee さんに教えてもらった Hammerspoon が収穫だった。設定が大変だけど、その分拡張性があって、これは気を付けないと無限に設定ファイルを書いてしまうタイプなので気を付けないといけない。

まとめ

最近は何やら開催日の決定に私の都合が反映されたりしていて、有り難いやら申し分けないやら。思えば今回は「何かできたらいいなぁ」と言いつつほぼ何もしなくて、あまり役割(?)を果たせてなかった感があるので、次回こそは。次も楽しみにしてます!

Vim 8.0 Advent Calendar

この記事は 2016 年 12 月に Qiita 上で行われた Vim 8.0 Advent Calendar を 1 つにまとめたものです。

目次


前書き

2016年9月、Vim の新しいメジャーバージョンである Vim 8.0 がリリースされました。 このアドベントカレンダーでは Vim 8.0 に含まれる新しい機能や変更などを紹介していきます。

注意:

  • 正確には Vim 7.4.0 以降に追加された機能になるので、リリースから時間が経っている機能もあります。
    • この Advent Calendar では便宜上、Vim 7.4 から Vim 8.0 の間に入った機能を Vim 8.0 の新機能として紹介します。ご了承ください。
  • 全ての新機能を紹介するものではありません。

Vim 8.0 Advent Calendar 1 日目 関数機能の強化

Vim 8.0 では Vim script の関数機能が強化されました。この記事では Partials とラムダを紹介します。

Partials

これまでの Vim script では function() 関数で関数参照(Funcref)を作成できました。これにより、関数を変数に入れ、直接呼び出すことができます。

let Foo = function('strftime')
echo Foo('%Y-%m-%d')
" => 2016-12-01
echo Foo('%Y-%m-%d', 1482634800)
" => 2016-12-25

これに加え、function() 関数に事前に引数を渡すことで、引数部分をバインドした関数参照を作れるようになりました。これを Partial と呼びます。

let Foo = function('strftime', ['%Y-%m-%d'])
echo Foo()
" => 2016-12-01
echo Foo(1482634800)
" => 2016-12-25

function() の第2引数以降に辞書を渡すことで、self をバインドすることも可能です。

function! Value() dict
  return self.value
endfunction

let dict = {'value': 10}
let Foo = function('Value', dict)

echo Foo()
" => 10

辞書から辞書関数の値を参照すると、自動的に辞書をバインドした関数参照が得られます。

let dict = {'value': 20}
function! dict.get_value() dict
  return self.value
endfunction

let Foo = dict.get_value

echo Foo()
" => 20

Partial から、バインドしている引数や辞書を得るには get() を使います。

let dict = {'value': 20}
function! AddN(n) dict
  return a:n + self.value
endfunction

let Add30 = function('AddN', [30], dict)
echo Add30()
" => 50
echo get(Add30, 'name')
" => AddN
echo string(get(Add30, 'func'))
" => function('AddN')
echo get(Add30, 'dict')
" => {'value': 20}
echo get(Add30, 'args')
" => [30]

ラムダ

これまでは関数を作るためには :function Ex コマンドを使って、複数行に渡ってコードを書く必要がありました。 そのため、sort() のような一部の関数を受け取る関数の実装が面倒でした。

function! MyCompare(i1, i2)
  return a:i1 - a:i2
endfunction
echo sort([3, 5, 4, 1, 2], 'MyCompare')
" => [1, 2, 3, 4, 5]

そこで新しくラムダ構文が追加されました。{args -> expr} という形式で、本文には式のみが書けます。

echo sort([3, 5, 4, 1, 2], { i1, i2 -> i1 - i2 })
" => [1, 2, 3, 4, 5]

また、map()filter() は今まで文字列で式を渡していましたが、関数参照を渡せるようになりました。 これらの関数は、配列の添字の {index} もしくは辞書のキー {key} と、各要素の値である {val} の 2 つを引数に取ります。間違いやすいので注意してください。

echo map(range(10), { index, val -> val * 2 })
" => [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

substitute() 関数も同様に {sub} に関数を渡せるようになりました。渡した関数は、引数としてマッチした対象を配列で受け取ります。配列の 0 番目はマッチした対象の全体、1 番目以降はパターン内のグループです。

echo substitute('char count sample', '\w\+', { m -> m[0] . '(' . len(m[0]) . ')' }, 'g')
" => char(4) count(5) sample(6)

クロージャ

関数内で更に関数を定義した際に、クロージャを作れるようになりました。これはどういうことかと言うと、ネストした関数の内側から、外側の関数のローカルスコープを参照できるということです。 クロージャを使うには :function Ex コマンドに closure フラグを渡します。

function! Counter()
  let c = 0
  function! s:count() closure
    let c += 1
    return c
  endfunction
  return funcref('s:count')
endfunction

let C1 = Counter()
let C2 = Counter()
echo C1()
" => 1
echo C2()
" => 1
echo C1()
" => 2
echo C2()
" => 2

関数のスコープに注意してください。関数内であろうとも、関数自体のスコープは関数ローカルにはなりません。グローバル関数を定義すれば、それはグローバル関数になります。恐らくこれは歴史的な理由によるものだと思われます。

もしくはラムダを使うことでもクロージャを作成できます。ラムダを使う場合、ラムダ内の式が静的にパースされて、外側の変数を参照していた場合にクロージャと判定されます。よって、例えば :executeeval() などで動的に参照されるだけの場合、クロージャにはならず外側の変数は参照できません。

function! OuterFunc(arg)
  let var = 0

  " これはクロージャになります。
  let closure = { -> var }

  " これはクロージャにはなりません。
  let not_closure = { -> eval('var') }

  " これは a:arg の参照によりクロージャになり、var も参照できます。
  let closure = { -> [a:arg, eval('var')][-1] }
endfunction

2種類の関数参照

関数参照は function() 関数で生成できますが、これによって生成される関数参照は名前参照になります。これはつまり、関数が同名で再定義された場合、参照先の関数も新しい関数になってしまうということです。 一方で先ほどのクロージャは何度も再定義されることが多く、問題になる場合があります。そこで、関数が上書きされても元の値を参照し続ける新しい参照を作るために、funcref() 関数が追加されました。 funcref() 関数の引数は function() 関数と全く同じで、Partial も作成可能です。違いは、funcref() 呼び出し時点での関数自体への参照が得られる点です。これにより参照先の関数が後で上書きされても、元の関数を参照し続けることができます。 注意点として、組み込み関数に対しては使えません。組み込み関数の関数参照を作る場合には function() を使う必要があります。

Vim 8.0 Advent Calendar 2 日目 チャンネル

Vim 8.0 では、外部リソースとのやりとりを行う機能としてチャンネルが追加されました。 本記事では、チャンネルの基本的な使い方として、ソケット通信を行う方法について簡単に説明します。 詳細については Vim 付属の help を参照してください。

チャンネルを使う

この例では、ローカルホストの HTTP サーバに対して ch_sendraw() 関数を使ってリクエストを送り、結果をハンドラで受け取って表示しています。

" リモートからのレスポンスがあった際に呼ばれるハンドラ関数を定義します
function! s:handle(ch, msg) abort
  " レスポンスを表示します。実際にはタイミング次第ではレスポンスが分割される可能性もあり得ます
  echo a:msg
  " ch_close() 関数でチャンネルを閉じることができます
  " リモートから切断された場合は自動的に閉じられます
  if ch_status(a:ch) !=# 'closed'
    call ch_close(a:ch)
  endif
  echo ch_status(s:ch)
  " => closed
endfunction

" チャンネルを開く際に渡すオプションを用意します
" ここではモードを "raw"、コールバックに先ほど定義したハンドラ関数の参照を指定しています
let s:options = {'mode': 'raw', 'callback': function('s:handle')}

" ch_open() でチャンネルを開きます
let s:ch = ch_open('localhost:80', s:options)
" ここで s:ch 変数は channel 型の値になります。channel 型は、チャンネル機能のために新しく追加された型です
echo ch_status(s:ch)
" => open
" 生の HTTP を叩きます
call ch_sendraw(s:ch, "GET / HTTP/1.0\r\n\r\n")

ここで、s:handle() 関数は非同期に呼び出されます。つまり、サーバからのレスポンスがあるまでの間、ユーザーは編集を続けることができます。 ただし、マルチスレッドではない点に注意してください。s:handle() が呼び出され、実行されている間はユーザーは Vim の操作ができません。重い処理はしないように注意が必要です。

チャンネルのモード

先ほどの例では raw モードでチャンネルを使用しました。チャンネルには他にも多くのメッセージの送信/受信の方法があります。

チャンネルで行う通信には 4 つのモードがあります。モードによって Vim はメッセージを解釈し、コールバックをメッセージ単位で呼び出してくれたり、メッセージのエンコード/デコードを行ってくれます。

  • JSON JSON 単位でメッセージをやりとりします。 メッセージには JSONVim のオブジェクトに変換したものが渡されます。

  • JS JSON に似ていますが、JavaScript のオブジェクトを使ってメッセージをやりとりします。 オブジェクトのキーの "" が省略されたり、配列に空の要素が許可されたりなどの違いがあります。

  • NL 行単位でメッセージをやりとりします。 メッセージには末尾の改行を取り除いたものが渡されます。

  • RAW Vim はメッセージを解釈しません。そのままのデータが使われます。

その他のメッセージの読み書きの方法

例では ch_sendraw() と、ハンドラを使った読み書きを行いました。他にもいくつか紹介します。

ch_sendexpr() ch_sendraw()

チャンネルに対してメッセージを送ります。 JSON モードや JS モードの際は ch_sendexpr() を使い、Vim のオブジェクトを渡すことでエンコードされた値が送られます。 NL モードや RAW モードの際は ch_sendraw() を使い、文字列を渡すことで生のデータを送れます。

ch_read() ch_readraw()

チャンネルのバッファにあるメッセージを読みます。読みとったメッセージはバッファから取り除かれます。また、ハンドラに処理されたメッセージもバッファからすでにないため、読めません。 JSON モードや JS モードの際は ch_read() を使い、デコードされた値が得られます。 NL モードや RAW モードの際は ch_readraw() を使い、生のデータを得られます。NL モードの場合は、改行単位でメッセージを読み取ります。

ch_evalexpr() ch_evalraw()

チャンネルに対してメッセージを送り、そのレスポンスを待って、レスポンスを返します。send と read を一度にやるものです。使い分けについても前述のものと同じです。

JSON/JS NL/RAW
送信 ch_sendexpr() ch_sendraw()
受信 ch_read() ch_readraw()
送受信 ch_evalexpr() ch_evalraw()

チャンネルには他にもまだまだ機能やオプションがあります。詳細は :help channel を参照してみてください。

Vim 8.0 Advent Calendar 3 日目 ジョブ

ジョブ機能を使うことで、外部プロセスを非同期で実行することができます。

ジョブを使う

この例では、ジョブを使って外部コマンド git grep -n word を実行し、結果を 1 行ずつ非同期で処理し、quickfix に追加しています。

function! s:handler(ch, msg) abort
  caddexpr a:msg
  cwindow
endfunction

call setqflist([])
let s:job = job_start(
\   ['git', 'grep', '-n', 'word'],
\   {'out_cb': function('s:handler')})

このように、ジョブを使うことで外部プロセスをバックグラウンドで実行し、チャンネルとコールバック関数を使うことで結果を非同期に処理できます。処理に時間のかかるソースコードのチェック処理などを裏で実行し、結果が出たら表示するといったことが可能になります。

ジョブのオプション

ジョブで指定できるオプションについて簡単に紹介します。ここで紹介しているものが全てではありません。詳細については help を参照してください。

let job_options = {}
モード

ジョブと接続される、標準入力、標準出力、標準エラー出力の各チャンネルのモードを指定します。デフォルトは nl で、改行を 1 つのメッセージとします。

" 標準入力のモードです。
let job_options.in_mode = 'nl'
" 標準出力のモードです。
let job_options.out_mode = 'nl'
" 標準エラー出力のモードです。
let job_options.err_mode = 'nl'
標準入出力の接続先

標準入出力についての細かい指定が行えます。

" 標準入力を使いません。
let job_options.in_io = 'null'

" 標準入力をチャンネルに接続します。デフォルトです。
let job_options.in_io = 'pipe'

" 標準入力をファイルから読み込みます。
let job_options.in_io = 'file'
" 標準入力をファイルから読み込む際のファイルへのパスです。
let job_options.in_name = '/path/file'

" 標準入力をバッファから読み込みます。
let job_options.in_io = 'buffer'
" 標準入力をバッファから読み取る際の読み取るバッファのバッファ番号です。
let job_options.in_buf = 1
" 標準入力をバッファから読み取る際のバッファの読み取り範囲の先頭行です。デフォルトは 1 です。
let job_options.in_top = 1
" 標準入力をバッファから読み取る際のバッファの読み取り範囲の最終行です。デフォルトはバッファの最終行です。
let job_options.in_bot = 9999  " 注意: 最終行にしたい場合はこの値は指定してはいけません。


" 標準出力を使いません。
let job_options.out_io = 'null'

" 標準出力をチャンネルに接続します。デフォルトです。
let job_options.out_io = 'pipe'

" 標準出力をファイルに出力します。
let job_options.out_io = 'file'
" 標準出力をファイルに出力する際のファイルへのパスです。
let job_options.out_name = '/path/file'

" 標準出力をバッファに出力します。
let job_options.out_io = 'buffer'
" 標準出力をバッファに出力する際のバッファ番号です。
" 指定がない場合や、存在しないバッファだった場合は、新しくバッファが作成されます。
let job_options.out_buf = 1
" 出力先がバッファの場合に 0 を指定すると、出力先バッファの 'modifiable' オプションをオフにします。
" 出力は行われますが、ユーザーはバッファを変更できなくなります。
let job_options.out_modifiable = 0
" 出力先がバッファの場合に 1 を指定すると、新しく作られたバッファの 1 行目にメッセージを出力します。
" メッセージは "Reading from channel output..." のようなものです。
let job_options.out_msg = 1

標準エラー出力は、標準出力のオプションのキーの outerr に変えたものが同じように用意されています。

コールバック

ジョブ側で何かが起きた際に呼び出される関数を指定します。指定しない場合は特に何も呼び出されません。

" 標準出力もしくは標準エラー出力から何か読み出せるようになった際に呼び出されます。
" 下記 2 つとは併用できません。
let job_options.callback = { ch, msg => [] }
" 標準出力から何か読み出せるようになった際に呼び出されます。
let job_options.out_cb = { ch, msg => [] }
" 標準エラー出力から何か読み出せるようになった際に呼び出されます。
let job_options.err_cb = { ch, msg => [] }
" チャンネルが閉じられた際に呼び出されます。
let job_options.close_cb = { ch => [] }
" ジョブ(外部プロセス)が終了した際に呼び出されます。
let job_options.exit_cb = { job, exit_status => [] }
その他
" ch_evalexpr() などでデータを読み取る際のタイムアウト時間(ミリ秒)です。
let job_options.timeout = 2000
" 標準出力を読み取る際のタイムアウト時間(ミリ秒)です。"timeout" を上書きします。
let job_options.out_timeout = 2000
" 標準エラー出力を読み取る際のタイムアウト時間(ミリ秒)です。"timeout" を上書きします。
let job_options.err_timeout = 2000

" Vim 終了時に、ジョブに対して job_stop() を呼び出します。
" デフォルトは 'term' で、Vim 終了時にジョブを停止します。
" 空文字列を指定すると、何もしません。
let job_options.stoponexit = 'term'

ジョブを制御する

ジョブを停止する

job_stop() 関数でジョブを終了させることができます。実際にはシグナルを送信したりします。 実際に何が起きるかは OS 依存です。

" ジョブを停止します。
" 第2引数には他に 'hup' 'quit' 'int' 'kill' や、シグナルの数値などが指定できます。
" 省略時は 'term' になります。
call job_stop(job, 'term')
ジョブの状態や情報を得る

job_status() 関数や job_info() 関数で、ジョブの状態や情報を取得できます。

echo job_status(job)
" => 'run' (ジョブが実行中の場合)
" => 'fail' (ジョブの開始に失敗した場合)
" => 'dead' (ジョブの実行が終了している場合)

" ジョブに紐付けられたチャンネルです。
let ch = job_getchannel(job)

" 様々な情報を辞書で取得します。
echo job_info(job)
" 'status'     job_status() の戻り値と同じです。
" 'channel'    job_getchannel() の戻り値と同じです。
" 'exitval'    終了コードです。'status' が 'dead' の場合のみ参照できます。
" 'exit_cb'    exit_cb オプションに指定された関数参照の値です。
" 'stoponexit' stoponexit オプションの値です。

Vim 8.0 Advent Calendar 4 日目 JSON サポート

チャンネルやジョブが追加されたのに合わせて、外部と JSON でのやり取りを行うことを想定して、JSON サポートが追加されました。

エンコード/デコードする

json_encode()json_decode() を使うことで、Vim の内部データと JSON 文字列を相互に変換できます。

let obj = {'users': [{'name': 'thinca', 'lang': 'vim'}]}
let json = json_encode(obj)
echo json
" => {"users":[{"lang":"vim","name":"thinca"}]}
echo json_decode(json)
" => {'users': [{'lang': 'vim', 'name': 'thinca'}]}

追加された値

Vim script には、true/false などの bool 値や、null などの値は存在しませんでした。 このままだと JSON と相互に変換するのに支障が出るため、新しくこれらを表す値が追加されました。

  • v:false
  • v:true
  • v:null
  • v:none

これらによって、bool 値や null を含む JSON も正しく相互変換されます。

let json = '{"is_vimmer":true,"has_free_time":false,"future":null}'
let obj = json_decode(json)
echo obj
" => {'future': v:null, 'is_vimmer': v:true, 'has_free_time': v:false}
echo json_encode(obj)
" => {"future":null,"is_vimmer":true,"has_free_time":false}

js_encode() js_decode()

チャンネルには JSON モードの他に JS モードがありました。これらに対応する js_encode() js_decode() があります。これらは JavaScript のオブジェクトのような形式を扱います。

let js = '{vimmers:["thinca",,],}'
let obj = js_decode(js)
echo obj
" => {'vimmers': ['thinca', v:none]}
echo js_encode(obj)
" => {vimmers:["thinca",,]}
  • オブジェクトのキーは必要がなければダブルクォートで囲われません。
  • 末尾カンマを許容します。
  • 配列内の空の要素(連続するカンマ)を許容し、この場合は v:none が使われます。

かなり特殊な値になるので、通常は JSON を使えばよいでしょう。

Vim 8.0 Advent Calendar 5 日目 タイマー

Vim 8.0 は新しくタイマー機能が追加されました。これにより、指定時間後に関数を呼び出すことができます。

タイマーを開始する

以下の例では 1 秒毎に関数を呼び出し、その度にカウントダウンを行い、最後に BOMB! と表示して終了します。

let dict = {'count': 10}
function! dict.countdown(timer) abort
  let self.count -= 1
  if self.count
    echo self.count
  else
    echo 'BOMB!'
    call timer_stop(a:timer)
  endif
endfunction

let timer = timer_start(1000, dict.countdown, {'repeat': -1})

タイマーが起動した後、タイマーによって関数が実行されている間以外は、ユーザーは編集を続けることができます。 例によって Vim はシングルスレッドですので、タイマーによって Vim script が実行されている間はユーザーは操作ができません。

関数の解説

timer_start({time}, {callback}, [, {options}])

タイマーを開始します。{time} ミリ秒後に {callback} 関数を呼び出します。 関数はタイマー ID を返します。この ID を使ってタイマーの操作ができます。また、{callback} 関数も引数にこの ID を受け取ります。 {options} には辞書でオプションを渡せます。今のところ有効なオプションは以下のものです。

  • "repeat" {callback} を繰り返し呼び出す回数を指定します。 正数を指定すると、{time} ミリ秒毎に指定した回数だけ {callback} が呼び出されます。 -1 を指定すると、制限なく呼び出され続けます。 指定しなかった場合は 1 回だけ呼び出されます。
timer_stop({timer})

指定したタイマーを停止します。{callback} 関数は呼び出されなくなります。

timer_pause({timer}, {paused})

タイマーを一時停止したり再開したりします。{paused} が TRUE の場合は一時停止、FALSE の場合は再開になります。

timer_info([{timer}])

タイマーの情報を返します。{timer} 引数を渡すと指定したタイマーの情報を、引数を省略した場合は全てのタイマーの情報を配列で返します。 情報は辞書で、ID や残り時間、呼び出される関数など一通りの情報が得られます。

timer_stopall()

タイマーは一歩間違えると暴発し、一切の操作ができなくなるような事態も起き得ます。timer_stopall() を呼び出すことで、全てのタイマーを停止することができます。

Vim 8.0 Advent Calendar 6 日目 パッケージ

Vim のパッケージ機能を使うことで、簡単なプラグインの管理を行うことができます。

パッケージとは

まず、パッケージ機能におけるパッケージとはどんなものかについて説明します。 1 つのパッケージは、複数のプラグインを含んでいます。また、プラグインはそれぞれ、Vim 起動時に読み込まれるか、あとから指定して読み込まれるかに分けられます。 パッケージは以下のようなディレクトリ構造になっています。

package/
|- start/
|  |- plugin1/
|  |- plugin2/
|  `- plugin3/
`- opt/
   |- plugin4/
   |- plugin5/
   `- plugin6/

start/ ディレクトリ以下にあるものが Vim 起動時に読み込まれるプラグインで、opt/ ディレクトリ以下にあるものがあとから指定して読み込まれるプラグインです。 plugin1 plugin2 などがプラグイン名になります。この名前は後から読み込む際に指定する名前になります。

パッケージを配置する

パッケージは、'packpath' オプションで指定されたディレクトリの中の pack/ ディレクトリ内から探されます。 'packpath' の初期値は 'runtimepath' の初期値と同じです。

具体例を挙げて見ていきます。 ~/.vim などが 'packpath' の 1 つとして登録されています。 この中の pack/ ディレクトリ内の、パッケージ名のディレクトリにパッケージを配置します。 つまり、パッケージ名を pack1pack2 とすると、以下のようになります。

~/.vim/
|- pack/
|  |- pack1/
|  |  |- start/
|  |  `- opt/
|  |- pack2/
|  |  |- start/
|  |  `- opt/
:  :

このようにパッケージ、およびその中のプラグインを配置しておくことで、Vim は起動時、vimrc ロード後のプラグイン読み込み前に、各パッケージの start/ ディレクトリ内のプラグイン'runtimepath' に自動的に追加し、その後プラグインを読み込みます。

オプショナルなプラグインのロード

opt/ 以下のプラグインは、:packadd Ex コマンドを使うことでロードできます。

:packadd plugin4

プラグイン名(=プラグインディレクトリ名)を指定することで、未ロードだった場合はロードされます。プラグイン'runtimepath' に追加され、プラグイン内の plugin/**/*.vim ファイルがロードされます。

また、vimrc 内からロードする場合は :packadd! を使います。! を付けると、'runtimepath' への追加のみが行われ、ロードはスキップされます。vimrc 内の場合、その後で別途ロード処理があるため、その場ではロードしないようにこちらを使用します。

パッケージの用途について

パッケージは Vim に標準で入った簡易プラグイン管理システムです。標準であることが強みでしょう。あまり多くのプラグインを使っていない人などは、この機能で十分な人もいるでしょう。 また、パッケージは見た限りだと、パッケージ単位でのプラグインの配布なども想定されていそうです。ディストリビューションなどでの配布や、関連したプラグインをまとめたものをパッケージにする用途が考えられます。

一方で、パッケージ機能はロード周りの世話はしてくれますが、昨今のプラグインマネージャプラグインが行ってくれるような、プラグイン自体のインストールや更新は行ってくれません。このレベルでプラグインを管理したい人は、やはりプラグインマネージャプラグインを利用するのが良いと思います。

Vim 8.0 Advent Calendar 7 日目 ウィンドウ ID

Windows ID を使うことで、特定のウィンドウの追跡が容易になります。

ウィンドウ ID がなかった時代

ウィンドウの指定はウィンドウ番号で行っていました。これはウィンドウの位置に対応して左上から順に振られます。

+-------------------------------+
|               |               |
|               |       2       |
|               |               |
|       1       |---------------|
|               |               |
|               |       3       |
|               |               |
+-------------------------------+

ウィンドウへ移動したり、ウィンドウに紐付けられた変数にアクセスする際は、このウィンドウ番号を使っていました。 しかし、このウィンドウ番号はウィンドウを移動すると変わってしまいます。例えば上の例で、ウィンドウ番号 1 の場所で <C-w>L を行うと、以下のようになります(括弧内は元のウィンドウ番号です)。

+-------------------------------+
|               |               |
|    (2->)1     |               |
|               |               |
|---------------|    (1->)3     |
|               |               |
|    (3->)2     |               |
|               |               |
+-------------------------------+

これだと困る、ということで埋まれたのがウィンドウ ID です。

ウィンドウ ID とは

ウィンドウ ID は全てのウィンドウに振られる ID です。 ウィンドウ番号と違い、ウィンドウを移動しても変わりません。

ウィンドウ ID の取得
" 現在アクティブなウィンドウのウィンドウ ID を取得します
let win_id = win_getid()
" 現在タブページからウィンドウ番号を指定してウィンドウ ID を取得します
let win_id = win_getid(winnr)
" タブページとウィンドウ番号を指定してウィンドウ ID を取得します
let win_id = win_getid(winnr, tabnr)

" バッファ名やバッファ番号から最初に見付かったバッファが
" 表示されているウィンドウのウィンドウ ID を取得します
let win_id = bufwinid(buf)

" 指定したバッファ番号のバッファを表示している
" ウィンドウのウィンドウ ID を配列で全て取得します
let win_ids = win_findbuf(bufnr)
ウィンドウ ID の使用
" 現在のタブページからウィンドウ ID のウィンドウを探してウィンドウ番号を返します
" 見付からなかった場合は 0 を返します
let winnr = win_id2win(win_id)
" ウィンドウ ID のウィンドウを探して、
" そのタブページ番号とウィンドウ番号を要素 2 の配列で返します
" 見付からなかった場合は [0, 0] を返します
let [tabnr, winnr] = win_id2tabwin(win_id)

" 指定のウィンドウ ID のウィンドウに移動します。成功したら TRUE を返します
let succeed = win_gotoid(win_id)

ウィンドウ番号とウィンドウ ID の併用

ウィンドウ ID は 1000 から振られます。これにより、1000 以上の場合はウィンドウ ID、1000 未満の場合はウィンドウ番号と仮定することで、既存の関数でウィンドウ番号を渡していた箇所でウィンドウ ID を渡せるようになっています。以下の関数で利用できます。

  • arglistid()
  • getcwd()
  • getloclist()
  • gettabwinvar()
  • haslocaldir()
  • setloclist()
  • settabwinvar()
  • winheight()
  • winwidth()

Vim 8.0 Advent Calendar 8 日目 defaults.vim

今回は新しく Vim に追加された defaults.vim という機構について解説します。

背景

Vim には今でも多くの新機能が追加され便利になっていっていますが、一方で互換性も重視しています。 中には、シンタックスハイライトなど明らかに便利であるにも関わらず、デフォルトでは有効になっていない機能があります。 新しく Vim を使い始めるユーザーにとって、互換性が理由で便利な機能がすぐに使えないことはあまり嬉しくはないでしょう。 そこで追加されたのが defaults.vim です。あくまで Vim 自体のデフォルト値は変えずに、ユーザーに便利な設定を提供します。

defaults.vim の読み込み

ユーザーの vimrc ファイルが存在しない場合、$VIMRUNTIME/defaults.vim ファイルは自動で読み込まれます。 読み込みたくない場合は、vimrc を読み込まない場合と同様に vim -u NONEvim -u NORC などを指定して Vim を起動します。 また、システム管理者が defaults.vim を読み込ませたくない場合、システムワイドの vimrc にて let g:skip_defaults_vim = 1 を行うことで defaults.vim の設定は適用されなくなります。

なお、この defaults.vim の追加によって、Vim は通常の起動でコンパチブルモードで起動することがなくなりました。このことは非互換の変更として help にも記載されています。

defaults.vim の内容

例えば以下のような設定があります。

  • Vi 互換モードをオフにする (set nocompatible)
  • filetype の検出やプラグインなどを有効にする
  • シンタックスハイライトを有効にする
  • 'nrformats' オプションの値から octal を取り除く
  • 間違えて <C-u> したときに undo できるようにする
  • :DiffOrig Ex コマンドの定義 (:help :DiffOrig)

詳細は実際にファイルの中身を見てみてください。:e $VIMRUNTIME/defaults.vim で開けます。

defaults.vim から始める vimrc

defaults.vim は最初に使い始める vimrc の叩き台としても機能します。 新しく自分用の vimrc を書き始めたいと思ったとき、defaults.vim の内容を継承したい場合は、defaults.vim ファイルを vimrc ファイルにコピーするか、もしくは vimrc の先頭に以下のように書きます。

source $VIMRUNTIME/defaults.vim

ここから、自分の気に入らない部分をちょっとずつ手を加えていくとよいでしょう。

逆に言うと、これらを行わずにユーザーの vimrc ファイルを作成すると、defaults.vim が読み込まれなくなることから、挙動が変わってしまうことに注意してください。

Vim 8.0 Advent Calendar 9 日目 2 進数のサポート

Vim 8.0 では 2 進数のサポートが強化されています。

2 進数の数値リテラル

0b もしくは 0B で始まる 2 進数リテラルが追加されました。

echo 0b1010 == 10
" => 1

<C-a> <C-x> の 2 進数サポート

'nrformats' オプションに指定できる値に bin が追加されました。 これはデフォルトで含まれているため、特に設定せずに利用可能です。 0b0B で始まる 2 進数の数値の上で <C-a><C-x> を実行すると、数値を増減できます。

0b1000
↓<C-x>
0b0111

printf() の 2 進数サポート

printf() 関数のフォーマットに %b が追加されました。 これを使うことで、数値を 2 進数の文字列に変換できます。

echo printf('0b%b', 10)
" => 0b1010

もちろんフラグにも対応しています。8 桁の 0 埋めパディングをするには以下のようにします。

echo printf('0b%08b', 10)
" => 0b00001010

Vim 8.0 Advent Calendar 10 日目 quickfix に追加された機能

Vim 8.0 では quickfix 周りに便利な機能が追加されました。

quickfix の各項目の場所で Ex コマンドを実行する

quickfix の各項目に対して Ex コマンドを実行する :cdo Ex コマンドが追加されました。この Ex コマンドを使うことで、quickfix に対する柔軟な操作が可能になります。

例えば、プロジェクトの中から単語 foo を探し、それらを全て bar に書き換えるには以下のようにします。

" 単語 foo を探します。結果は quickfix に入ります。
:vimgrep /\<foo\>/ **/*
" 検索結果の各行にて、置換を行い、バッファを保存します。
:cdo s/\<foo\>/bar/g | update

quickfix 内の各ファイルで Ex コマンドを実行する

上記の例において、検索結果の各ファイルについて複数の結果のデータがある場合に、各ファイルについて何か処理がしたい場合があるかもしれません。この場合に :cdo を使ってしまうと、各ファイルにて結果のデータの数だけ処理が実行されてしまいます。 こういった場合のために、cfdo Ex コマンドも追加されています。こちらの Ex コマンドは、quickfix に存在している各ファイルについて繰り返しが行えます。 各ファイルが開かれた際のカーソル位置は、ファイル内の最初の quickfix の項目の位置になります。

quickfix の一番下にスクロールする

Vim の非同期機能が強化されたため、今後は quickfix の内容が非同期で更新されることもあるでしょう。そういった場合に、常に quickfix の一番下の内容が見たい場合もあります。 新しく追加された :cbottom Ex コマンドを実行すると、quickfix ウィンドウへ移動しなくても、quickfix ウィンドウの内容を一番下までスクロールさせることができます。

quickfix の履歴を参照する

quickfix の内容は、最新の 10 個まで履歴が保存されていて、以前の内容に戻すことができます。詳細は :help quickfix-error-lists に記載されています。 この機能は、以前までは実際に :colder などを実行して前に戻ってみないと古いものが存在するかどうかがわかりませんでした。 そこで追加されたのが :chistory Ex コマンドです。この Ex コマンドを実行すると、quickfix にどのような履歴があり、現在どれが参照されているのかが以下のように表示されます(以下は help からの抜粋です)。

  error list 1 of 3; 43 errors
> error list 2 of 3; 0 errors
  error list 3 of 3; 15 errors

ロケーションリスト版

上記の Ex コマンド :cdo :cfdo :cbottom :chistory はそれぞれ、ロケーションリスト版として :ldo :lfdo :lbottom :lhistory が用意されています。

Vim 8.0 Advent Calendar 11 日目 タイムスタンプで管理されるようになった viminfo ファイル

今回は、ユーザーの作業が記録されている viminfo ファイルについてです。

viminfo ファイルの概要

viminfo ファイルは、ユーザーが行った様々な操作を記録しておくファイルです。例えば、レジスタの内容、コマンドラインの履歴、検索文字列の履歴や、ジャンプリストなど、様々な情報が記録されます。これらをファイルに記録することで、次回 Vim を使った際にも、前回の履歴を引き継いで使うことができます。 viminfo ファイルは、基本的には起動時に読み込まれ、終了時にファイルに書き出されます。

viminfo ファイルのマージ

1 つの viminfo ファイルを複数の Vim のセッションで使った場合、viminfo ファイルはマージされます。

どのようにマージが起きるか、例を挙げます。 同時に 2 つの Vim を立ち上げて、それぞれ A B とします。起動時には viminfo は空で、どちらも最初は履歴がありません。 この時、

  1. A で Ex コマンド :echo 1 を実行
  2. B で Ex コマンド :echo 2 を実行
  3. A で Ex コマンド :echo 3 を実行
  4. B で Ex コマンド :echo 4 を実行
  5. B を終了
  6. A を終了

とします。

5 の時点で、B の履歴が viminfo ファイルに書き込まれ、viminfo ファイルには新しいものが上に来るように以下のような順番で記録されます(実際のファイルの形式とは異なります)。

:echo 2
:echo 4

次に 6 で A が終了する際、viminfo ファイルに更新があることを Vim が検出すると、一旦新しくなった viminfo を読み込みます。続いて A の Vim のセッションで記録された履歴を追記し、viminfo ファイルに書き出します。以下のようになります。

:echo 2
:echo 4
:echo 1
:echo 3

このように、コマンドラインヒストリがマージされます。

発生する問題

上記の例で、1 つ問題が発生します。ユーザーは複数の Vim を行き来し、:echo 1 から :echo 4 まで順番に実行したのにも関わらず、履歴の順番はぐちゃぐちゃになってしまっています。 これは特に Vim を長時間起動していた場合、つい先ほど実行したコマンドが履歴の奥深くに潜ってしまうことを意味します。例だと、ユーザーが最後に実行したのは :echo 4 ですが、これは履歴の一番下から 3 番目に来てしまっています。 最近実行したものは、履歴の中でも最近に出てきてくれた方が嬉しいでしょう。

タイムスタンプを使った新しい viminfo ファイル

そこで Vim 8.0 では、コマンドなどの履歴を保存する際に、それらが実行された時間のタイムスタンプも一緒に保存するようになりました。 これによって履歴は読み込まれる時にタイムスタンプ順でソートされ、ユーザーが実行した順番で履歴を参照することができます。 以下のものがタイムスタンプで管理されるようになりました。

Vim 8.0 Advent Calendar 12 日目 連番の生成

Vim で連番と言えば今まででも、マクロを使う方法や Vim script を活用する方法などがありましたが、より手軽な方法が追加されました。

g<C-a> g<C-x> コマンド

今まではノーマルモードにて <C-a> <C-x> を実行することで、カーソル位置の数値を増減できましたが、ビジュアルモード中でも同様の操作が可能になりました。

加えて、連番を作り出すための g<C-a> g<C-x> がビジュアルモードのコマンドに追加されました。

使用例

例えば以下のようなバッファがあった場合:

1
1
1
1
1

2 行目以降をビジュアルモードで選択して、g<C-a> を実行すると、以下のようになります。

1
2
3
4
5

連番を作ることができました。2 行目以降を選択する点に注意してください。

これは見付かった数値に対して、見付かった順に [見付かった回数×count] 分、数値を足します。 例えば 2g<C-a> を実行すると、以下のようになります。

1
3
5
7
9

この操作は行指向で、選択範囲の各行で最初に見付かった数値のみを増減させます。また、数値が見付からなかった行はスキップされます。

1. これは 1 つ目の項目です。
1. これは 2 つ目の項目であり、
   折り返しが含まれています。
1. この項目にも折り返しが含まれて
   います。3 つ目の項目です。
1. 4 つ目の項目です。

このテキストに対して、<C-v> で 2 行目から最終行まで矩形選択を行い、g<C-a> を実行すると、以下のようになります。

1. これは 1 つ目の項目です。
2. これは 2 つ目の項目であり、
   折り返しが含まれています。
3. この項目にも折り返しが含まれて
   います。3 つ目の項目です。
4. 4 つ目の項目です。

数値が存在しない行は飛ばされます。 ここで V で行単位で選択をしてしまうと、3 つ目の項目の中身が含まれてしまってうまくいかなくなります。

1. これは 1 つ目の項目です。
2. これは 2 つ目の項目であり、
   折り返しが含まれています。
3. この項目にも折り返しが含まれて
   います。6 つ目の項目です。
5. 4 つ目の項目です。

'nrformats' オプションに alpha が含まれていれば、アルファベットの増減も可能です。

a. ...
a. ...
a. ...
a. ...
a. ...

a. ...
b. ...
c. ...
d. ...
e. ...

g<C-x> は数値を減らす点以外は g<C-a> と同様に動作します。

Vim 8.0 Advent Calendar 13 日目 undo を分割せずにカーソルを移動

普段はあまり気にしないかもしれませんが、undo の単位は重要です。この undo について、新しい機能が追加されました。

挿入モードでの操作の分割

挿入モードでの操作中に、<Left> などでカーソル位置を動かすと、undo 情報や . でのリピートが壊れてしまいます。 例えば以下のキーマッピングがあった場合、

inoremap ( ()<Left>

挿入モードで foo(bar と入力することで foo(bar) が入力できます。

入力: afoo(bar<Esc>
↓
foo(bar)

しかし、ここで u コマンドで undo を行うとfoo() となってしまいます。また、. コマンドでリピートを実行すると、bar が挿入されます。

foo(bar)
↓
入力: u
↓
foo()
foo(bar)
↓
入力: j.
↓
foo(bar)
bar

これは <Left> により入力情報が途切れてしまうためです。つまり、<Left> を行った時点で、挿入モードを一旦抜けて入り直したのと同じことになります。

挿入モードでの <C-g>U コマンド

この問題を避けるために、<C-g>U コマンドが追加されました。これはカーソルを動かすコマンドの直前で使用します。 このコマンドを使うために、キーマッピングを以下のように書き換えます。

inoremap ( ()<C-g>U<Left>

最初の例と同様に foo(bar を入力し、その後それぞれ u コマンドや . コマンドを実行すると、入力された foo(bar) 全体が 1 つの入力として動作します。

foo(bar)
↓
入力: u
↓
foo(bar)
↓
入力: j.
↓
foo(bar)
foo(bar)

この <C-g>U コマンドは、カーソルが行を跨がない移動をする場合のみ有効です。

<C-g>U コマンドは便利ですが、手で入力するにはちょっと複雑です。今回の例のように、キーマッピングで使うとよいでしょう。

Vim 8.0 Advent Calendar 14 日目 新しいオプション その 1

新オプション紹介その 1 です。追加されたものの中でも便利なオプションについて解説します。

'breakindent' (真偽値) 'breakindentopt' (カンマ区切り文字列)

オンにすると、折り返して表示される行がインデントされて表示されます。 つまり以下のようになります。左が 'breakindent' がオフ、右がオンです。また、'showbreak' オプションの値に > が設定されています。4 行目の折り返しに注目してください。 f:id:thinca:20161230184910p:plain

また、'breakindentopt' オプションで細かい挙動を制御できます。このオプションはカンマ区切りの文字列で、以下の要素を指定できます。

  • min:{n}
    • 深いインデントが短すぎる幅で折り返されないように、1 行の最小の幅を指定します。未指定の場合は 20 になります。
  • shift:{n}
    • 折り返された位置をずらします。正数で右に、負数で左にずらします。未指定の場合は 0 で、ずらしません。
  • sbr
    • インデントの左側に 'showbreak' を表示します。

例えば、shift:4,sbr を指定すると、以下のようになります。

f:id:thinca:20161230184911p:plain

'showbreak' である > 記号が行頭に移動し、折り返しのインデントが if のインデントに比べて右に 4 つずれています。

'fixendofline' (真偽値)

通常、Vim は保存時にファイルの末尾に必ず改行を入れます。これは POSIX にて、テキストは行の集合であり、行は必ず改行で終わるとされているからだと思われます。 このファイル末尾の改行の付与は、ファイル末尾に改行のないファイルを開いて編集し、保存した場合にも行われます。 これは特にチームで作業している場合に困る場合があります。また、一部の CSV の処理系など、ファイル末尾の改行の有無で意味が合わるファイルを扱う場合も困ります。

そこで 'fixendofline' オプションが追加されました。このオプションはデフォルトではオンで、保存時にファイル末尾に改行を追加します。 オフの場合、'endofline' オプションに従って改行を付与します。オンなら改行が付与され、オフなら改行は付与されません。 この 'endofline' オプションは、既存のファイルを開いた際にはファイルの末尾の改行を見て自動でオンオフされます。

つまりまとめると以下のようになります。

  • ファイル末尾の改行をいじって欲しくない場合は、set nofixendofline を vimrc ファイルに書きます。
  • ファイル末尾の改行の有無を操作したい場合は、上記に加えて、都度 'endofline' オプションの値を変更します。

'belloff' (カンマ区切り文字列)

Vim はエラーが発生した際にベルを鳴らします。これを無効化したい場合、以前は以下のような設定を書いていました。

set visualbell t_vb=

新しく追加された 'belloff' オプションを使うと、代わりに以下のように書けます。

set belloff=all

'belloff' オプションは、どんな時にベルを鳴らさないようにしたいのかが細かく指定できます。特定の場合のベルのみ無効にしたい!といったことも可能です。 どのような値が指定可能かは help を参照してください。

Vim 8.0 Advent Calendar 15 日目 新しいオプション その 2

新オプション紹介その 2 です。その 1 に比べると地味なオプション達を簡単に紹介します。それぞれ詳細は help を参照してください。

'renderoptions' (特殊形式文字列)

テキストレンダラの設定です。このオプションを設定することによって、Windows ではレンダリングDirectX を使えます。 また、DirectX に対して様々なオプションを設定できます。

'emoji' (真偽値)

オンにするとユニコード絵文字を全角とみなします。デフォルトはオフです。

'langremap' (真偽値)

元々 'langnoremap' オプションがありました。このオプションは真偽値のオプションであったため、これをオフにしようとすると以下のようになります。

set nolangnoremap

これは二重否定でわかりづらい、ということで追加されたのが 'langremap' オプションです。'langnoremap' オプションは今も互換性のために残されていて、この 2 つのオプションは常に逆の値を指すようになっています。

'signcolumn' (特定文字列)

sign の桁を表示するかどうかを設定します。デフォルトは auto で、sign が存在する場合のみ表示されます。 その他、yes no で常にオン/オフが可能です。

sign は本来の目的以外でも、行全体をハイライトするために hack 的にプラグインから使われる場合があり、このような時に no を設定することで余計な sign カラムを非表示にすることができます。

'tagcase' (特定文字列)

タグファイル内を検索する際の大文字小文字の区別する方法を指定します。 元々はこれは 'ignorecase' オプションの値に依存していましたが、'ignorecase'インタラクティブな検索などで使われることもあり、オンにしている人が多いかと思います。一方、タグファイル内の検索はプログラミング言語の識別子などが入っていることもあり、大文字小文字は区別して欲しい場合が多いです。そこで、それぞれ独立して設定できるようにするために 'tagcase' オプションが追加されました。 デフォルト値は followic で、互換性を保つために 'ignorecase' に追従します。常に大文字小文字を区別して欲しい場合は、match を設定します。他にもいくつか設定できる値があります。

'termguicolors' (真偽値)

オンにすると、ターミナル内でも GUI 用の 24 ビットカラーのカラースキームが使用できます。ただし、ISO-8613-3 互換のターミナルが必要です。 対応していないターミナルでオンにすると残念なことになるので注意してください。

'luadll' 'perldll' 'pythondll' 'pythonthreedll' 'rubydll' 'tcldll' (文字列)

Vim には様々な言語のインターフェースがあり、ビルド時にこれらを指定できます。 ダイナミックリンクも可能でしたが、これまでは、その dll のファイル名はビルド時に指定したものに固定でした。 しかし、タイナミックリンクである以上、ファイル名は環境によって変わる場合があります。そこで、オプションによって dll のファイル名を指定できるようになりました。 'pythonthreedll'Python 3 のためのものです。本来 'python3dll' としたかったようですが、オプション名に数値が使えないという制約が存在したため、このようになっています。

Vim 8.0 Advent Calendar 16 日目 新しい Ex コマンド

Vim 8.0 で利用できる新しい Ex コマンドのうち、まだ紹介していないものを紹介します。

:filter[!] {pat} {command}

{command} の出力のうち、{pat} で指定した正規表現にマッチする行だけを表示します。[!] を指定すると、逆にマッチしない行だけを表示します。 {pat}/foo/ のように / などの記号で囲われた形式です。ただし、パターンが記号などを含まない場合は / は省略できます。

以下に使用例を挙げます。

" マークを記録してあるファイルのうち、.txt で終わるものを表示します。
filter /\.txt$/ oldfiles
" 読み込まれた Vim script のうち、パスに vimrc を含むものを表示します。
filter vimrc scriptnames
" 開かれているバッファのうち、.vim を含むものを表示します。
filter /\.vim/ buffers
" キーマッピングのどこかに <C-r> を含むものを表示します。
execute "filter /\<C-r>/ map"
" 現在のバッファから、foo を含む行を表示します。
filter foo %print
" ↑の例は :global でも実現できます。
global/foo/print

最後に注意点として、この Ex コマンドは全ての出力をフィルタするわけではありません。例えば、:echo の結果はフィルタされません。

:keeppatterns {command}

検索履歴に手を加えずに {command} を実行します。 :substitute (:s///) や :global などの一部の Ex コマンドは、通常は Vim script 中で使った場合でもパターンが検索履歴に追加されてしまいます。これはプラグインの中などで使う場合に問題になります。 そこでこの :keeppatterns Ex コマンドを使って :keeppatterns global/.../ などのようにすることで、検索履歴が変更されてしまうのを防ぐことができます。

これは {command} の実行中はずっと手を加えないということではなく、直接指定した Ex コマンドが検索を使うものだった場合だけ有効です。つまり以下の場合は、検索履歴は変更されてしまいます。

" :execute を挟む
keeppatterns execute "s/\<CR>//ge"

" 関数経由
function! s:work() abort
  s/\e//ge
endfunction

keeppatterns call s:work()

:noswapfile {command}

{command} を実行します。このとき、新しいバッファが開かれた場合はスワップファイルを作成しません。 これはプラグインが仮想バッファを作成する際に便利です。

この Ex コマンドも :keeppatterns Ex コマンドと同様、バッファを開くコマンドを {command} に直接指定した場合のみ有効です。 ただし、:vertical Ex コマンドや :leftabove Ex コマンドなどの、ウィンドウを開く先を指定する修飾子コマンドは含まれていても問題ありません。

:clearjumps

現在のウィンドウのジャンプリストを空にします。

カーソルを大きく移動させるコマンドを実行したり、バッファを移動したりした場合、そのカーソルの移動はジャンプリストに記録され、あとから辿って移動することができます。 これは多くの場合便利ですが、例えばプラグインが仮想バッファを開いた際などに、戻れてしまうと不便な場合もあります。そういった場合にこの Ex コマンドが使えます。

:helpclose

現在のタブページにヘルプウィンドウがあれば、1 つだけ閉じます。 これは現在のウィンドウがヘルプウィンドウではない場合にも動作するので、離れた場所にあるヘルプウィンドウを閉じるのに便利です。 1 つもヘルプウィンドウがない場合は特にエラーにもならず、何も起きません。

Vim 8.0 Advent Calendar 17 日目 新しい関数 ~文字列操作編~

Vim 8.0 では新しく便利な組み込み関数が多数追加されています。今回はその中から、文字列操作に関連するものを紹介します。

matchstrpos({expr}, {pat}[, {start}[, {count}]])

Vim script には元々、指定した文字列から、正規表現にマッチした位置を取り出す match() 関数と、マッチした文字列を取り出す matchstr() という関数があります。 これら関数は便利ですが、マッチした位置とマッチした文字列両方が欲しい場合には少し問題があります。この場合、それぞれの関数を呼び出すことになるのですが、関数を 2 回呼び出すのは手間がかかる上に、同じ正規表現マッチを 2 回行うのはパフォーマンス的にも無駄です。

そこで、matchstrpos() 関数が追加されました。引数は match() 関数や matchstr() 関数と同じで、戻り値が違います。"マッチした文字列"、"マッチした先頭の位置"、"マッチした末尾の位置" の 3 要素の配列を返します。

echo matchstrpos('fizz buzz fizzbuzz', 'b\w\+')
" => ['buzz', 5, 9]

strcharpart({src}, {start}[, {len}])

Vim script には元々、文字列の一部を切り出す strpart() 関数がありますが、これはバイト単位で動作するため、マルチバイト文字に対して使うと文字列のバイトの途中で切り取られてしまうという問題がありました。 そこで strcharpart() が追加されました。これはバイト単位ではなく、文字単位で文字列の一部を切り取ります。

echo strcharpart('あいうえお', 1, 3)
" => いうえ

strgetchar({str}, {index})

文字列内の指定した index の文字の文字コードを取得します。これはバイトではなく文字単位で動作します。文字数はマルチバイトで数えられ、結果はマルチバイト文字の 1 文字の文字コードになります。

let code = strgetchar('あいうえお', 4)
echo printf('0x%x', code)
" => 0x304a
echo nr2char(code)
" => お

byteidxcomp({expr}, {nr})

Vim script には byteidx() という関数があります。これは、マルチバイト文字を考慮して、{expr} に与えた文字列の 0 オリジンで {nr} 番目の文字が、文字列内の何バイト目かを返します。{nr} が文字列の文字数と同じ場合は文字列全体のバイト数を返し、{nr} がそれより大きい場合は -1 を返します。

echo byteidx('あいうえお', 2)
" => 6
echo byteidx('あいうえお', 5)
" => 15
echo byteidx('あいうえお', 6)
" => -1

新しく追加された byteidxcomp() は、合成文字を個別にカウントします。

let s = 'e' . nr2char(0x301)
echo s
" => é
echo byteidx(s, 1)
" => 3   (合成文字を 1 文字とみなし、文字列が 1 文字であるため文字列全体のバイト数を返します)
echo byteidx(s, 2)
" => -1  (合成文字を 1 文字とみなし、{nr} が文字数より大きいため -1 を返します)
echo byteidxcomp(s, 1)
" => 1   (合成文字を別々の文字とみなし、"e" までをカウントして 1 を返します)
echo byteidxcomp(s, 2)
" => 3   (合成文字を別々の文字とみなし、文字列が 2 文字であるため文字列全体のバイト数を返します)

glob2regpat()

glob() 関数などで使われる、いわゆるワイルドカードなどが含まれるファイルパターンを正規表現に変換します。

echo glob2regpat('*.vim')

今のところ、ワイルドカード*** も、正規表現.* に変換されるようです。実際のワイルドカード*ディレクトリを辿らず、/ などのディレクトリ区切り文字にはマッチしないため、若干挙動が異なってしまう点に注意してください。

Vim 8.0 Advent Calendar 18 日目 新しい関数 ~情報取得編~

今回は新しく追加された関数の中から、情報を取得するものを中心に紹介します。

wordcount()

現在バッファの統計情報を辞書で取得します。 この情報は g<C-g> コマンドで表示できるものですが、表示だけだとスクリプトから扱うのが困難であるため、関数が追加されました。

辞書には以下の情報が含まれます。

キー 説明
bytes バッファ内のバイト数です。
chars バッファ内の文字数です。
words バッファ内の単語数です。
cursor_bytes カーソル位置より前のバイト数です。ビジュアルモードでない場合のみ存在します。
cursor_chars カーソル位置より前の文字数です。ビジュアルモードでない場合のみ存在します。
cursor_words カーソル位置より前の単語数です。ビジュアルモードでない場合のみ存在します。
visual_bytes ビジュアル選択領域内のバイト数です。ビジュアルモードの場合のみ存在します。
visual_chars ビジュアル選択領域内の文字数です。ビジュアルモードの場合のみ存在します。
visual_words ビジュアル選択領域内の単語数です。ビジュアルモードの場合のみ存在します。

getbufinfo([{expr}]) getbufinfo([{dict}])

バッファの情報を辞書の配列で取得します。引数を与えない場合、全てのバッファの情報を取得します。 配列の各要素の辞書は、以下のエントリーを持っています。

キー 説明
bufnr バッファ番号です。
changed バッファが変更されているなら('modified' がオンなら) TRUE になります。
changedtick バッファが変更された回数(b:changedtick の値)です。
hidden 隠れバッファであるなら('hidden' がオンなら) TRUE になります。
listed バッファがバッファリストに表示されるなら('buflisted' がオンなら) TRUE になります。
loaded バッファがロード済みなら TRUE になります。
name バッファ名(バッファのファイルのフルパス)です。
signs サインの情報のリストです。リストの各要素は辞書で、以下の要素を持ちます。
キー説明
idサインの ID
lnum行番号
nameサインの名前
variables バッファローカル変数を参照する辞書です。
windows バッファを表示しているウィンドウのウィンドウ ID のリストです。

引数を渡した場合は、取得したいバッファの条件を指定することで絞り込みができます。詳細は help を参照してみてください。

getwininfo([{winid}])

ウィンドウの情報を辞書の配列で取得します。引数を与えない場合、全てのタブページのウィンドウの情報を取得します。 配列の各要素の辞書は、以下のエントリーを持っています。

キー 説明
bufnr ウィンドウが開いているバッファのバッファ番号です。
height ウィンドウの高さです。
loclist このウィンドウがロケーションリストだった場合は 1 です。
quickfix このウィンドウが quickfix ウィンドウだった場合は 1 です。
tabnr ウィンドウがあるタブページのタブページ番号です。
variables ウィンドウローカル変数を参照する辞書です。
width ウィンドウの幅です。
winid ウィンドウ ID です。
winnr ウィンドウ番号です。

引数にウィンドウ ID を渡した場合は、指定したウィンドウ ID の情報のみを含む配列を取得できます。

gettabinfo([{arg}])

タブページの情報を辞書の配列で取得します。引数を与えない場合、全てのタブページのウィンドウの情報を取得します。 配列の各要素の辞書は、以下のエントリーを持っています。

キー 説明
tabnr タブページ番号です。
variables タブページローカル変数を参照する辞書です。
windows タブページで表示されているウィンドウのウィンドウ ID のリストです。

引数にタブページ番号を渡した場合は、指定したタブページ番号の情報のみを含む配列を取得できます。

getcharsearch() setcharsearch({dict})

文字検索の情報を取得、設定できます。文字検索とは、f F t T で行う、指定した文字やその手前に飛ぶ機能のことです。 文字検索の情報を持つ辞書を取得、および設定できます。この辞書は以下の要素を持ちます。

key 説明
char 検索文字です。空文字列にすると、文字検索を解除します。
forward 検索方向です。1 ならば前方、0 ならば後方です。
untill 検索の種類です。1 の場合は、文字の手前(tT)、0 の場合は文字自体(fF) の検索です。

getcmdwintype()

getcmdtype()コマンドラインウィンドウ版です。 q: q/ q?コマンドラインウィンドウを開いている時に、現在のコマンドラインウィンドウがどのタイプかを返します。戻り値は : / ? のいずれかで、コマンドラインウィンドウが開かれていない場合は空文字列を返します。

getcompletion({pat}, {type} [, {filtered}])

コマンドラインの補完の結果を取得できます。{type} は以下のうちのどれかです。

{type} 説明
augroup autocmd のグループ名です。
buffer バッファ名です。
behave :behave Ex コマンドの引数です。
color カラースキームです。
command Ex コマンドです。
compiler :compiler Ex コマンドの引数です。
cscope :cscope Ex コマンドの引数です。
dir ディレクトリ名です。
environment 環境変数です。
event autocmd のイベント名です。
expression Vim の式です。
file ファイル名とディレクトリ名です。
file_in_path 'path' にあるファイル名とディレクトリ名です。
filetype ファイルタイプの名前です。
function 関数名です。
help help の項目です。
highlight ハイライトグループです。
history :history Ex コマンドの引数です。
locale ロケールの名前(locale -a の出力)です。
mapping キーマッピングの名前です。
menu メニューです。
option オプションです。
shellcmd シェルコマンドです。
sign :sign Ex コマンドの引数です。
syntax syntax ファイルのファイル名です。
syntime :syntime Ex コマンドの引数です。
tag tags ファイルから読み取れるタグです。
tag_listfiles tag と同じです。
user ユーザー名です。
var Vim script の変数です。

{pat}候補を絞り込めます。コマンドラインに入力されている文字列を渡します。 {filtered} に 1 を渡すと、'wildignore' オプションを結果に適用します。

arglistid([{winnr} [, {tabnr}]])

Vim には引数リストという機能があります。これはグローバルなものが 1 つあり、それとは別にウィンドウ毎にローカルなものが作成できます。 引数リストの使い方についてはここでは省略しますが、この関数はこの引数リストの ID を取得できます。 指定したウィンドウにローカルな引数リストがなく、グローバルなものが使用されている場合は 0 を返します。引数が無効だった場合は -1 を返します。

Vim 8.0 Advent Calendar 19 日目 新しい関数 ~特殊操作編~

関数紹介編の最後です。特殊な操作をするものや、その他雑多な関数を紹介します。

uniq({list} [, {func} [, {dict}]])

配列内の連続する同じ要素を削除します。 全体から重複を削除したい場合は事前に sort() 関数を使う必要があります。

let new_uniq_list = uniq(sort(copy(list)))

比較にはデフォルトで文字列表現を使います。{func}{dict} を与えることで、sort() 関数と同様に比較方法を指定することが可能です。

execute({command} [, {silent}])

Ex コマンド {command} を実行し、コマンドラインへの出力を結果として返します。 {command} は文字列か、文字列の配列です。

{silent}"" "silent" "silent!" のいずれかで、コマンドのプレフィックスのように使われます。デフォルトは "silent" です。

echo filter(split(execute('scriptnames'), "\n"), { i, line -> line =~# '^\s*1:' })
" =>   1: ~/.vim/vimrc

Ex コマンドの実行中に :redir Ex コマンドを使うことはできません。

matchaddpos({group}, {pos}[, {priority}[, {id}[, {dict}]]])

matchadd() 関数のようにウィンドウ内にハイライトを追加しますが、matchadd() 関数はパターンを指定するのに対し、matchaddpos() 関数は位置を指定します。 {pos} には位置のリストを指定します。位置は以下のうちのいずれかです。

説明
数値 行番号です。行全体を強調表示します。
数値を 1 つ持ったリスト 行番号です。行全体を強調表示します。
数値を 2 つ持ったリスト 行番号と、その行の桁です。単位はバイトで、指定した文字を強調表示します。
数値を 3 つ持ったリスト 行番号、桁、文字列長(バイト単位)です。

一度に指定できる位置は 8 個までです。

setfperm({fname}, {mode})

ファイルのパーミッションを設定します。{mode}getfperm() 関数の戻り値と同様のフォーマットである、"rwxrwxrwx" 形式で指定します。

systemlist({expr} [, {input}])

system() 関数と同様ですが、戻り値は行単位のリストになります。戻り値内の NULL 文字(\0)は改行文字に変換されます。 これにより、結果に NULL 文字が含まれてるコマンドの結果も取得できます。

perleval({expr})

if_perl を使って Perl の式を評価し、結果を Vimデータ形式に変換して返します。 これは pyeval() 関数や luaeval() 関数の Perl 版です。

echo perleval('{"foo" => "bar"}')
" => {'foo': 'bar'}

exepath({expr})

実行コマンドのフルパスを取得します。絶対パス相対パス$PATH の中に存在するファイルが実行ファイルだった場合、そのフルパスを返します。

echo exepath('git')
" => /usr/bin/git

isnan({expr})

{expr}NaN 値であるかを判定します。

echo isnan(0.0 / 0.0)
" => 1

reltimefloat({time})

reltime() 関数の経過時間の戻り値を秒数の Float 値に変換します。

let start = reltime()
sleep 1
echo reltimefloat(reltime(start))
" => 1.000287

以前から経過時間を文字列に変換する reltimestr() 関数がありましたが、こちらは文字列であり、かつ先頭に空白が含まれているため、表示以外の目的で使うには少々扱いづらいという問題がありました。reltimefloat() 関数を使うことで直接 Float 値が得られるため、平均の計算などがやりやすくなります。

Vim 8.0 Advent Calendar 20 日目 新しいイベント

Vim 8.0 では autocmd イベントも新しく追加されています。

TabNew

新しくタブページが開かれた際に発生します。例えば :tabnew Ex コマンドを使うと、以下の順番でイベントが発生します。

  1. WinLeave
  2. TabLeave
  3. WinNew
  4. WinEnter
  5. TabNew
  6. TabEnter

TabClosed

タブページが閉じられた際に発生します。例えば、:tabclose Ex コマンドでカレントタブページを閉じると、以下の順番でイベントが発生します。

  1. BufLeave
  2. WinLeave
  3. TabLeave
  4. TabClosed
  5. WinEnter
  6. TabEnter
  7. BufEnter

WinNew

新しいウィンドウが作成された際に発生します。Vim 起動時に開かれるウィンドウに対しては発生しません。

CmdUndefined

定義されていないユーザー定義コマンドを実行しようとした際に発生します。 パターンはコマンド名に対してマッチングが行われ、<amatch><afile> は実行しようとしたユーザー定義コマンド名に設定されます。しかし、<amatch> は正しく展開されないようなので、<afile> を使うのがよいでしょう。 イベントの実行中に存在しなかったコマンドを定義すれば、イベント終了後にコマンドが実行されます。

以下の例は、Foo で始まる未定義の Ex コマンドを実行すると、その場で自分自身のコマンド名を出力する Ex コマンドを定義します。

augroup example
  autocmd!
  autocmd CmdUndefined Foo* execute 'command! -nargs=*' expand('<afile>') 'echo' string(expand('<afile>'))
augroup END
FooBar
" => FooBar

OptionSet

オプションが設定された際に発生します。 パターンは、常に短縮していないオプション名に対してマッチングが行われ、<amatch> にはオプション名が設定されます。 また、以下の組み込み変数に設定されたオプションの値の情報が格納されます。

組み込み変数名 説明
v:option_old 変更前のオプションの値です。
v:option_new 変更後のオプションの値です。
v:option_type 設定された変数のスコープです。globallocal が入ります。

'key' オプションの場合は、セキュリティのためイベントは発生しません。

TextChanged

ノーマルモードでカレントバッファのテキストが変更された際に発生します。このとき、b:changedtick が更新されます。 このイベントはそれなりの頻度で発生します。重い処理を行う場合は十分注意すべきです。

TextChangedI

挿入モードでカレントバッファのテキストが変更された際に発生します。ただし、補完のポップアップメニューが表示されているときは発生しません。 このイベントは非常に頻繁に発生することに気を付けてください。重い処理を行うと、ユーザー体験が著しく損なわれます。

Vim 8.0 Advent Calendar 21 日目 新しい組み込み変数

今回は新しく追加された組み込み変数を紹介します。

タイプを表す定数

type() 関数を使うと、変数のタイプを得ることができます。ここで得られる値は数値で、各タイプに数値が割り当てられています。 ある変数が特定のタイプであるかどうかを判定したい場合、今までは以下のようにしていました。

" 以下の例では変数 var が文字列かどうかを判定しています。

" 文字列の type の値は 1 なので、これと比較して判定します。
if type(var) == 1
endif

" マジックナンバーを避けるため、以下のようにすることが多いです。
if type(var) == type('')
endif

この方法には、以下のような問題がありました。

  • 若干トリッキーで、慣れないと理解しづらいコードになります。
  • type() 関数を余計に呼ぶため、オーバーヘッドがあります。
  • 関数参照の場合は type(function('type')) のようになり、長い上に function() 関数に渡す関数名が人によってバラバラで統一感がありません。
  • 新しく追加された job 型などの値は気軽に生成できません。

そこで、type() 関数の戻り値を表す定数が新たに追加されました。以下の表が定数の一覧です。

定数 定数の値 型の値の例
数値 v:t_number 0 10
文字列 v:t_string 1 'foo'
関数参照 v:t_func 2 function('type')
リスト v:t_list 3 [0, 1, 2]
辞書 v:t_dict 4 {'one': 1}
浮動小数点数 v:t_float 5 1.23
真偽値 v:t_bool 6 v:true v:false
特殊値 v:t_none 7 v:null v:none
ジョブ v:t_job 8 job_start(cmd)
チャンネル v:t_channel 9 ch_open(host)

v:completed_item

補完された対象を表す変数です。 以前までは、CompleteDone イベントにより補完の完了を知ることはできましたが、どの候補が選択されたかを知ることができませんでした。新しく追加されたこの変数を参照することで、どの候補が選択されたのかわかります。 選択された候補は辞書です。詳しい構造については :help complete-items で説明されています。補完に失敗した場合は空の辞書になります。

v:hlsearch

検索による強調表示が行われているかを表す変数です。 検索のハイライトは 'hlsearch' オプションをオンにして検索を行うことで行われますが、:nohlsearch Ex コマンドを使うことで、一時的にハイライトを無効にできます。このとき 'hlsearch' オプションの値はそのままなので、ハイライトが行われているのか、:nohlsearch Ex コマンドで消されているのかが今まではわかりませんでした。 v:hlsearch 変数は、ハイライトが行われている時は 1、行われていない時は 0 になります。 また、値を書き換えることでハイライトの状態を変更できます。ただし、'hlsearch' オプションがオフの場合はハイライトを有効にできないため、1 を入れても値は 0 のままです。エラーも発生しません。 また、関数の呼び出しは、最後に使用された検索パターンを保存して呼び出し終了後にリストアします。つまり関数内でこの変数を書き換えても、関数の終了時に復元されてしまうので注意してください。

v:progpath

Vim を起動した際のコマンドを示す文字列です。つまり、Vim コマンド自身のコマンドライン引数の 0 番目です。 システムに複数の Vim がある場合に、どの Vim で起動されたかのヒントになります。Vim 内から別の Vim を起動して処理を行いたい場合などに便利です。

echo exepath(v:progpath)

コマンドが相対パスでカレントディレクトリが移動した場合や、$PATH が変更された場合など、確実に起動した Vim が得られるわけではない点に注意してください。

v:vim_did_enter

Vim が起動して、VimEnter イベントが発生する直前までは 0 です。VimEnter イベントが発生する直前に 1 になります。 似たような値に has('vim_starting') があります。こちらの値は逆で、起動中は 1、起動後は 0 になります。値が変わるタイミングは v:vim_did_enter と同じです。両者は完全に代替可能です。 ではなぜこの変数が追加されたのかと言うと、実はこれを追加した際、Bram さんは has('vim_starting') の存在を完全に忘れていました。あとで指摘された際、この変数の追加をリバートすることも検討したようです。 しかし、has() 関数は基本的に Vimコンパイル時に組み込まれている機能を調べるためのもので、一部の例外を除き Vim 実行中に値が変化しないこと、そのような慣習のため、Vim が起動中かどうかを調べる方法が has() 関数にあることはユーザーにとってわかりづらいことなどを Gary さんに指摘され、残すことになったようです。

Vim 8.0 Advent Calendar 22 日目 新しいスタイルのテスト

Vim 8.0 では、Vim 本体のテストのスタイルが新しくなりました。

新しいテストのサンプル

新しいスタイルのテストは Vim 本体のテストのために追加されたものですが、基本的に Vim script の機能であるため、プラグインのテストにも利用できます。 以下に、新しいスタイルで書かれた簡単なテストコードを示します。

" テスト対象の関数
function! Add(a, b) abort
  return a:a + a:b
endfunction

" --------------------------

function! Test_Add() abort
  call assert_equal(5, Add(2, 3))
endfunction

function! s:run_test() abort
  let v:errors = []

  call Test_Add()

  if empty(v:errors)
    echo 'Test Passed!'
  else
    echo 'Test Failed!'
    for error in v:errors
      echo error
    endfor
  endif
endfunction

call s:run_test()

実行すると、以下のようにテストをパスします。

Test Passed!

テストに失敗する例も示します。Test_Add() 関数を以下のように書き換えます。

function! Test_Add() abort
  call assert_equal(10, Add(2, 3))
endfunction

実行すると、以下のようにテストに失敗します。

Test Failed!
function <SNR>1_run_test[3]..Test_Add line 1: Expected 10 but got 5

テストの仕組み

以上の例から見て取れるのは 2 点です。

  • 値のチェックに使っている assert_equal() 関数
  • テストの結果のチェックに使っている v:errors 組み込み変数

新しいテストでは、これらを使ってテストを書きます。

仕組みは単純です。v:errors 組み込み変数は配列です。assert_ で始まるアサート系の関数を呼び出し、アサートに失敗すると、この v:errors に失敗のメッセージが追加されます。

例で行っているように、テスト開始前に v:errors を空にし、いくつかのアサート系の呼び出したあと、最後に v:errors の中身を確認することでテストを行います。

アサート系関数

追加されたアサート系の関数を紹介します。 ほとんどの関数は {msg} 引数を持っており、これを渡すことで v:errors に入るメッセージを指定できます。省略した場合は関数毎に用意されたメッセージが使用されます。

assert_equal({expected}, {actual} [, {msg}])

{actual}{expected} と等しい事をテストします。型の自動変換は行われません。

assert_notequal({expected}, {actual} [, {msg}])

{actual}{expected} と等しくない事をテストします。

assert_inrange({lower}, {upper}, {actual} [, {msg}])

{actual}{lower} 以上 {upper} 以下の数値である事をテストします。

assert_match({pattern}, {actual} [, {msg}])

{actual}正規表現 {pattern} にマッチする事をテストします。

assert_notmatch({pattern}, {actual} [, {msg}])

{actual}正規表現 {pattern} にマッチしない事をテストします。

assert_true({actual} [, {msg}])

{actual} が TRUE である事をテストします。ここでの TRUE は、非ゼロの数値か、v:true です。それ以外の型や値の場合は失敗します。

assert_false({actual} [, {msg}])

{actual} が FALSE である事をテストします。ここでの FALSE は、ゼロの数値か、v:false です。それ以外の型や値の場合は失敗します。

assert_exception({error} [, {msg}])

v:exception に文字列 {error} が含まれている事をテストします。

assert_fails({cmd} [, {error}])

{cmd} を実行した結果、エラーが発生する事をテストします。{error} が渡された場合、v:errmsg に格納されている発生したエラーメッセージに文字列 {error} が含まれている事をテストします。

その他のテスト用関数

assert 系以外で追加されたテストを補助する関数です。ただし、ほとんどの関数は Vim 本体のテストのためのものです。簡単に紹介します。

テスト用関数 説明
test_alloc_fail({id}, {countdown}, {repeat}) メモリの確保を強制的に失敗させます。
test_autochdir({expr}) 起動中に 'autochdir' を有効にします。
test_disable_char_avail() typeahead なしの状態でテストします。
test_garbagecollect_now() 直ちにメモリを解放します。
test_null_channel() null のチャンネルを返します。
test_null_dict() null の辞書を返します。
test_null_job() null の Job を返します。
test_null_list() null のリストを返します。
test_null_partial() null の部分適用関数を返します。
test_null_string() null の文字列を返します。
test_settime({expr}) Vim が使う内部時間を変更します。

Vim 8.0 Advent Calendar 23 日目 雑多な変更

今回は、今までの変更に収まらなかった細かい変更点について見ていきます。

GTK+ 3

GUI として、GTK+ 3 に対応しました。GTK+ 2 と同じように使えます。

+num64

Vim script の整数の型が 64 bit になりました。 利用可能な環境であれば 64 bit になることになっていますが、基本的には一般的なほぼ全ての環境では利用可能かと思います。 has('num64') を使うことで、64 bit が有効かどうかを判定できます。

if has('num64')
  echo 1000000000000000000
  " => 1000000000000000000
endif

これにより、32 bit による桁溢れなどを利用している一部のプラグインがうまく動作しなくなる可能性があります。

ユーザー定義コマンドでの新しい置き換えテキスト <mods>

Vim の Ex コマンドの中には、他の Ex コマンドに前置することで他のコマンドを修飾するものがあります。:aboveleft:hide などがそうで、これらはコマンド修飾子と呼ばれます。 これまでは、ユーザー定義コマンドに対してコマンド修飾子が指定されても、その存在を知ることはできませんでした。そこで追加されたのが <mods> です。

以下のように <mods> を使うことで、指定された修飾コマンドの存在を知ることができます。

command! ShowMods echo split(<q-mods>)

ShowMods
" => []
aboveleft ShowMods
" => ['aboveleft']
hide leftabove ShowMods
" => ['aboveleft', 'hide']

指定された順序に関係なく、決められた順序で <mods> に入ります。対応しているコマンド修飾子は以下です。

  • :aboveleft :leftabove
    • どちらも同じ意味のコマンドで、どちらが指定されても aboveleft が得られます。
  • :belowright :rightbelow
    • どちらも同じ意味のコマンドで、どちらが指定されても belowright が得られます。
  • :botright
  • :browse
  • :confirm
  • :hide
  • :keepalt
  • :keepjumps
  • :keepmarks
  • :keeppatterns
  • :lockmarks
  • :noswapfile
  • :silent
  • :tab
  • :topleft
  • :verbose
  • :vertical

また、以下のコマンド修飾子には対応していません。

型チェックの廃止

以前のバージョンの Vim では、変数に対して、元から入っている値の型に暗黙に変換不可能な型の値を代入しようとすると、エラーになっていました。

" Vim 7.4.1546 より前
let var = 10
let var = []
" => E706: 変数の型が一致しません: var

これは元々意図的にチェックが行われエラーになっていたのですが、静的型付き言語ならともかく、動的型付き言語である Vim script でこれを行ってもわずらわしいだけであったため、このチェックはなくなりました。

" Vim 7.4.1546 以降
let var = 10
let var = []
" => エラーなし

printf('%s') が全ての型を受け入れる

printf() 関数のフォーマット文字列である %s は、文字列を表示します。以前のバージョンの Vim では、文字列に暗黙に変換できない値を渡すとエラーになっていました。

" Vim 7.4.2220 より前
echo printf('%s', [1, 2, 3])
" => E730: リスト型を文字列として扱っています

しかしこれはあまり便利ではないため、文字列型でない場合は string() 関数を適用した結果が使用されるように変更されました。

" Vim 7.4.2220 以降
echo printf('%s', [1, 2, 3])
" => [1, 2, 3]

辞書のキーに空文字列を使える

以前のバージョンの Vim では、辞書のキーに空文字列が使えませんでした。

" Vim 7.4.1707 より前
echo {'': 10}
" => E713: 辞書型に空のキーを使うことはできません

しかし、空文字列を許可しない積極的な理由はなく、空文字列が使えた方が便利であるため、キーに空文字列が使えるようになりました。

" Vim 7.4.1707 より前
echo {'': 10}
" => {'': 10}

正規表現 \%C

新しく追加された正規表現のアトム \%C を使うと、合成文字をスキップすることができます。

let s = 'e' . nr2char(0x301)
echo s
" => é
echo s =~# 'e'
" => 0
echo s =~# 'e\%C'
" => 1

ハイライトグループ EndOfBuffer

新しく追加されたハイライトグループ EndOfBuffer は、ファイルの末尾以降に表示される ~ の文字がある行の部分のハイライトになります。標準では NonText と同じようにハイライトされます。 例えば以下のように背景色と前景色を同じにすることで、ファイル末尾以降を塗り潰す、といったことができます。

highlight EndOfBuffer ctermfg=DarkGray ctermbg=DarkGray guifg=DarkGray guibg=DarkGray

サポートが終了した環境

以下の環境や機能は、使っている人がほぼいない、大きくなった Vim が動作するのにそぐわないなどの理由により、Vim のコードを綺麗に保つためにサポートが終了しました。

これらの環境や機能で Vim が使いたい場合は、古い Vim を使う必要があります。

Vim 8.0 Advent Calendar 24 日目 内部的な変更

今回は、利用者にはあまり影響がない Vim の開発側の変更についてです。

GitHub へ移行

移行当時、割と大きく取り上げられていたので、ご存知の方も多いでしょう。 それまで Vim は、Google Code で Mercurial を使って開発されていました。しかし Google Code のサービス終了に伴い、2015 年 8 月に VimリポジトリGitHub へ移行、リポジトリも Git に変更されました。

https://github.com/vim/vim

また、以前は Vim へのパッチの投稿は vim_dev のメーリングリスト上でのみ行われていましたが、現在では GitHub 上の Pull Request でも受け付けています。貢献への敷居がかなり下がったと言えます。

大規模なソースコードの分割

Vim は長い歴史もあり、かなり巨大なプログラムです。ソースコードもかなりの量があり、eval.c などは 2 万行を超えていました。 Vim の開発が GitHub に移ったりなどさまざまな変化がある中で、コードのテストカバレッジCoveralls で計測するようになりました。 しかし、この時にこの巨大なソースコードが問題になったようで、これを回避するためにソースコードの分割が行われました。 概ね、以下のような分割が行われました。

  • eval.c
    • eval.c
    • list.c
    • dict.c
    • evalfunc.c
    • userfunc.c
  • spell.c
    • spell.c
    • spellfile.c

ANSI-C スタイルの関数定義

Vim 本体のソースコードは、以前までは K&R と呼ばれるスタイルで関数定義を行っていました。以下のような形式です。

/*
 * Add a watcher to a list.
 */
    void
list_add_watch(l, lw)
    list_T  *l;
    listwatch_T *lw;
{
    lw->lw_next = l->lv_watch;
    l->lv_watch = lw;
}

関数の引数部分の、型の宣言が括弧の外に書かれています。C 言語を知っている人の中でも、そもそもこのような書き方ができること自体知らない人もいるのではないでしょうか。 Vim の歴史は古く、そのためこの古い書き方がずっと使われてきました。 しかしこの度、現代で広く使われている ANSI-C スタイルの関数定義に書き換えられました。以下のような形式です。

/*
 * Add a watcher to a list.
 */
    void
list_add_watch(list_T *l, listwatch_T *lw)
{
    lw->lw_next = l->lv_watch;
    l->lv_watch = lw;
}

ビジュアルモードが常に有効

Vim には大きく分けて、Tiny、Small、Normal、Big、Huge の 5 つのビルドがあり、後になるほど多くの機能が含まれるようになっています。 ビジュアルモード(+visual 機能)は、以前までは Small 以上の Vim に含まれる機能でしたが、7.4.200 からは Tiny も含む全ての Vim で有効になるようになりました。

Vim 8.0 Advent Calendar 25 日目 ユーザーをハッピーにする

長かったこの連載もついに最終回です。

Vim ユーザーをハッピーにする」

2015 年の年末、Vim に以下のようなコミットが行われました。

Author: Bram Moolenaar <Bram@vim.org>
Date:   Thu Dec 31 16:10:23 2015 +0100

    patch 7.4.1005
    Problem:    Vim users are not always happy.
    Solution:   Make them happy.

Vim ユーザーをハッピーにする、と書かれたこのコミットは、1 つの Ex コマンドを追加するものでした。

:smile

:help version8 のページ内の :smile Ex コマンドの説明には、コミットメッセージと同様、以下のようにだけ書かれています。

|:smile|                make the user happy

この Ex コマンドを実行しても、何かとても便利なことが起きるわけではありません。しかし、これこそが Vim の願いであると感じます。

一部の例外はあるかもしれませんが、ソフトウェアはユーザーをハッピーにするために存在するものだと思います。少なくとも私はそうであると信じていますし、Vim はそのためにあると思っています。

今後も、Vim はユーザーをハッピーにするために進化を続けます。そして Vim を使った多くのユーザーがハッピーになることを願っています。

f:id:thinca:20161230184917p:plain

Happy Vimming!

Meguro.vim #1 を開催しました

Meguro.vim #1 を開催したので、そのレポートなどをだらだらと。

実を言うと、これまであちこちの Vim 系のイベントに参加したり、開催をけしかけたりしてきた私ですが、私が単独で主催するイベントはこれが初めて。初主催!どきどき! と言うわけでちゃんと終わるまでずっと心配だったのだけど、無事開催することができてホッとしています。

あちこちの会に顔を出しているのもあって、初回だし、割と知っている人ばっかり来るかなーと予想していたのだけど、知らない人にも参加してもらえて嬉しい限り。

会の内容について、可能であれば定期(もしくは不定期)開催にしたいと思っていたので、開催のハードルを下げて内容がほぼないもくもく会と言う形式にしました。Vimもくもく会はすでに色々あるのだけど、最近はどの会も開催されていないっぽかったので、被りとか需要とかあまり気にせずまあやってみるかという感じで開催に至る。こういうのは勢いが大事(たぶん)。

本編は、私自身は何人かとお話しをしたりできたけど、一部あまり話せなかった人がいたのが残念。とは言え全員としゃべるのは割と難しい。

全体としてやって良かったので、次回もそのうちやります。まだ確定ではないけど、2月後半くらいに場所が押さえられたら開催したい。

VimConf 2016 に行ってきた

VimConf 2016 に行ってきたよ!

以下、いつも通り雑に感想などを。

事前に残念な言い訳をすると、私は最後の発表だったので、直前まで資料の見直しなどをしながら発表を聞いていたので、一部ちゃんと聞けなかった。無念。あとで録画見よう。

開始

席が割と埋まる。出席率が高い。Vim への関心の高さが伺える。つまりそういうことだ。

Introduction to Vim 8.0

by Ken Takata

資料: http://www.slideshare.net/k-takata/introduction-to-vim-80

Vim 8.0 の紹介と、Vim の軌跡。

Vim 7.4 からずっと追ってきた身としては、いい振り返りの資料。 私が Vim を使い始めたのは、7.0 以降。7.2 からだったかな…ちゃんと覚えていない。

客観的に数字で見ても日本人コントリビュータの皆さんは本当にすごい。私はパッチ書いてる皆さんと比べるとほぼ何もできてないけど、できる範囲でなんかはやって行きたい。…バグ報告とか。

Vim as the MAIN text editor

by bird_nitryn

資料: https://speakerdeck.com/bird_nitryn/vim-as-the-main-text-editor-number-vimconf2016

VS Code から Vim に転向した話。

Vimmer の人達が、Vim 以外の場所でも Vim を使おうとあちこちにバラまいた Vi 系拡張が、巡り巡って新しい Vimmer を呼び込んでいるというのは中々アツいものがある。

Vim の入口も多様な時代なのでみんな Vim を使おう(適当)。

Denite.nvim ~The next generation of unite~

by Shougo

資料: https://gist.github.com/Shougo/7c78b3a1725f70c1435d004ed14f2558

個人的には unite.vim の開発が終了してしまったのが悲しい。

とは言え、メンテナンスコストなどが大変なのはわかる。残念だけどまあ仕方ない。

Go、C、Pythonのためのdeoplete.nvimのソースの紹介と、Neovim専用にpure Goでvim-goをスクラッチした話

by zchee

資料: http://go-talks.appspot.com/github.com/zchee/talks/vimconf2016.slide#1

補完めっちゃ速いのよい。

入力を阻害しない補完はやはり重要なのだな…環境見直したい。

エディタの壁を越えるGoの開発ツールの文化と作成法

by tenntenn

資料: http://www.slideshare.net/takuyaueda967/go-68228940

Vim の話はほとんど出てこない、けど、テキスト編集にい関わるかなり濃い話。

ツールをしっかり整える Go の戦略はかなり魅力的に感じる。Go 入門したい(かなり前からずっと言ってる)。

vim-mode-plus for Atom editor

by t9md

資料: http://qiita.com/t9md/items/0bc7eaff726d099943eb

着眼点と発想がすごい。ちょっとちゃんと見れなかったので(もったいない)、あとで動画見返す!

Vimの日本語ドキュメント

by MURAOKA Taro

資料: https://drive.google.com/file/d/0ByQIX4Ls1SHlZ3JtOWxHUUkwaDA/view

Vim の日本語ドキュメントの翻訳に参加しよう!しよう!

私もやろうと思いつつ全然できていないので、時間を見付けてやって行きたい。やりたいこと多すぎる…。

Vim script parser written in Go

by haya14busa

資料: https://docs.google.com/presentation/d/1A6_A7XzPoHv_wG5N_R6zbgYKBX2ycii6BCzR-7b-nOw/pub?start=false&loop=false#slide=id.p

本日3つ目の Go 関連の発表。

スライド内でも触れられているけど、Vim と Go の親和性は高く感じる。 というか Vimコマンドラインツールとの親和性が高くなるように作られているので、マルチプラットフォームな1バイナリを生成できる Go は強い。Go 入門したい(2回目)。

僕の友達を紹介するよ

by aiya000

資料: https://aiya000.github.io/Maid/my-vim-friends/my-vim-friends#/

プラグインの紹介。alignta.vim は私も使ってます。たまに必要になるので入ってると便利。

Best practices for building Vim plugins

by thinca

資料: https://gist.github.com/thinca/785171e327e66c48d2d293690dc2f65a

私の発表。予定時間を大幅にオーバーし、運営陣の「巻いて」の合図にも一切気付かず、最後までやってしまった。反省…。

元々時間の調整がしやすいように Tips 集にしたのに、全然活きないというかなり残念な結果に。貧乏性が敗因。

懇親会

最近便利ツールでお世話になっている id:Pocke さんに挨拶できたり、割とあちこちで雑に話をできた気がするので良かった。

今年は食べ物の量が、多すぎず少なすぎず、かなり良かった(運営視点)。

n次会カラオケ

なう。(この記事は深夜のテンションで書いています)

まとめ

来年も VimConf でお合いしましょう!Vim はいいぞ!