Vimに自分の書いたパッチが取り込まれた!

Vim 8.0.0623に私の書いたパッチが取り込まれました。 わーい ∩(>◡<*)∩ わーい!

もともとのきっかけは、自分のプラグインを開発している中で、[\u3000-\u4000]という正規表現に対する挙動がset re=1set re=2で違うことに気がついたのです。 Vim正規表現エンジンを2つ積んでいる恐ろしいエディターなのですが、この2つの正規表現エンジンの挙動に微妙な違いがあることに気がつきました。 新しいNFAエンジンではエラーは出ませんが、古いエンジンではエラーが出ます。 古い正規表現エンジンでは、[a-z] みたいなパターンで、[\u3000-\u4000]のように差が大きすぎるとエラーを吐くのです。 この挙動の違いはまぁいいんです、エンジンの仕組みがぜんぜん違うので… ただ、この時のエラー番号E16でヘルプを引くと、コマンドのrangeに関するエラーなんです。 例えば :0buffer のようなケースですね。 ソースコードを覗いてみると、E16は [z-a] のようなパターンに対するエラーにも使われており、コマンドのrangeに関するエラーと正規表現の文字クラスに関するエラーは分けたほうが良さそうだと気が付きました。

エラー番号E16を正規表現のエラーに使いまわすのはおかしいと報告したところ、Bram氏に「昔はメモリーをあまり使わないよう、エラーメッセージが増えすぎないようにしていたんだ」と教えてもらいました。 「どう変えたらよくなるかを提案してね」ということだったので、エラーメッセージを追加したパッチを送りました。 この最初のパッチでは[z-a][\u3000-\u4000]の2つのエラーを同じメッセージにしていたのですが、Bram「メッセージ2つ分けてちょうだい。あとテストも書いてくれたらうれしいな」とのことだったので、メッセージを分けてテストを書いて送り返しました。 それから半月くらい音沙汰なかったのですが、最近ようやくマージされました。 返事がなくても気長に待つことですね。 あと、ヘルプなどのruntimeファイルはまとめて更新されるはずです。

最新バージョンを入れて動作チェックして、自分が書いたエラーメッセージが表示された時はホッとしました。 VimのE944とE945はな、父さんが作ったんだよって息子に自慢できますね、結婚してないけど。 世界中の開発者が使う道具の中のどこかに自分が書いたコードが入っていると思うとワクワクしますね! そんな感じです!じゃあねっ!

lightline.vimのREADME.mdを書き直しました

lightline.vimVimのステータスラインをいい感じにしてくれるプラグインです。 作って四年弱経つんですね。 おかげさまで多くのユーザーさまに使っていただいています。

itchyny.hatenablog.com

github.com

このREADME.mdを最近書き換えました。

………

それだけかいな!って感じなのですが、いろいろと大変でした… 主に精神的に… つらい…

  • プラグインを作った勢いと使って欲しいという強い思いで、プラグインを作ってすぐにREADME.mdをかなり詳しく書いていた
  • そのために当初の設計や実装時の思いが強く出ていたが、初めてプラグインを触る人にとっては読みにくい文章になってしまっていた
  • パッチをあてたフォントを使わないとプラグインが使えないと誤解されることがあった。実際、スクリーンショットの多くがパッチをあてたフォントを使った状態で撮っていた。
  • スクリーンショットの画質がかなり悪かった

とにかくいろいろと書き換えたいと思いつつ何か月も経ってしまったのですが、なかなか手が進みませんでした。 一応プラグインを作ってから三年以上、沢山の方にREADME.mdを読んでいただいていて、そこそこ機能していました。 そこそこ長い英語の文章で書いてしまったので、これをまた全体を書き直すのが億劫になっていたというのもあります。

いや、ほんと、一旦書いた英語の文章を、いい感じに書き直すの大変ですよ… しかもそれが趣味プロダクトのREADMEで、書き換えるのが必須でもなくて…

なんて思っていたのですが、やはりいろいろとよくないことも多かったので、最近一気に書き直しました。

  • 関数コンポーネントを使うことで、短い設定でいろいろと見た目を変更できることを伝えるようにしました。
  • パッチフォントに関する言及は辞めました。引き続き特殊なフォントのグリフを使うことはできますが、ポータビリティは悪いのであまりオススメはしません。
  • 誤解を生みやすい component_visible_condition の説明はやめました。関数コンポーネントを使えば十分です。
  • スクリーンショットをきれいに撮り直しました。

そんなこんなで少しは読みやすくなったかな〜と思います。

ヘルプの方も近いうちに書き換えていきます。 こんな感じです! じゃあね!

Haskellで10を作るプログラムを書いてみたので動画で公開してみた

最近Rui Ueyamaさんがコーディング動画をアップされているのを見て、私も動画を撮りたくなりました。題材をしばらく考えていたんですが、10を作るプログラムを書いてみることにしました。

www.youtube.com

後から見直すと色々ミスっていて、わりと焦っていることがわかります。なにかの癖で適当に bc -l とかやったのだけど、音声をあてる時は関係ないオプションだと勘違いしてしまいました。確かにglobされていたのはよくなかったけど、 echo '5 / (5 / (5 + 5))' | bc -l とかで考えてみると -l も必要なんですよね。2つの問題が起きていて混乱してしまった…

もともとはProject Euler 93を昔解いたことがあったので、こういう系の問題と分数の扱い方とか括弧の付け方みたいなところはイメージありました。ただ、Project Eulerの問題は式木を表示する必要がなかったので、式の型を作ってなかったんですよね。それで今回改めてコードを書いていたらなんか意外と色んなところでハマってしまいました。

最近のHaskellは少し雲行きが怪しいなという印象がありますね。ここ10年で出てきた新しい言語より勢いがないのは否定できないし、若者があまり興味を示さないのも分かる気がします。私も最近はRustばかり触っているのですが… なんかHaskellの楽しさを直接伝えるものがあまりないんですよね。ブログエントリーのレベルも二分化されてしまっている印象があります。

私は昔、Project EulerCodeforcesの問題を順番に解いていた時期があって、Haskellを書いていて楽しかったなぁと思い出すのはその頃の思い出なのです。まぁ数学のパズルが好きじゃないとささらないのかもしれませんが…

Haskellerの皆さんはどういうところに一番関心がありますか?Haskellを書いていて楽しい時はどういう瞬間でしょうか。若い世代に興味を持ってもらうにはどういうものがあると良いのでしょうか。

追記: 括弧の省略アルゴリズム

括弧を省略しようとして動画では失敗してしまいました。改めて落ち着いて括弧が必要なケースを調べてみると、以下のようになりました。

  • 左辺の括弧が必要なケース: 演算子が乗算か除算で、かつ左辺が加算か減算かのどちらか
  • 右辺の括弧が必要なケース: 演算子が減算か乗算で、かつ右辺が加算か減算、もしくは演算子が除算で右辺が二項演算子

というわけで、次の実装で大丈夫だと思います。

isAddOrSub :: Expr -> Bool
isAddOrSub (NumInt _) = False
isAddOrSub (BinOp (Op op _) _ _) = op == "+" || op == "-"

isBinOp :: Expr -> Bool
isBinOp (NumInt _) = False
isBinOp (BinOp _ _ _) = True

instance Show Expr where
  show (NumInt n) = show n
  show (BinOp op@(Op opstr _) lhs rhs) = lparen (show lhs) ++ " " ++ show op ++ " " ++ rparen (show rhs)
    where lparen | (opstr == "*" || opstr == "/") && isAddOrSub lhs = \str -> "(" ++ str ++ ")"
                 | otherwise = id
          rparen | (opstr == "-" || opstr == "*") && isAddOrSub rhs || opstr == "/" && isBinOp rhs = \str -> "(" ++ str ++ ")"
                 | otherwise = id

後は自明な左辺右辺の入れ替えの除去くらいですかね。いやはやめんどくさそうだ…

Haskellの普及について色々と考えていたんですが、中高生にプログラマーってすごい、なりたいって思わせるのにライブコーディング動画って有用なんじゃないかと思いました。どうせ若い頃に興味を持つ言語はころころ変わるので、あまりそこにこだわってもよくなくて、むしろもう少し若い世代にプログラミングにどう興味をもってもらうかというところに力を入れるとソフトウェア産業も発展していくのではないでしょうか… うまい問題設定のライブコーディング動画って子供にとって結構刺さると思うんですよねぇ。ここまで考えたところでテトリス一時間で実装動画を思い出しました。これは強烈でしたねぇ。

gitのファイル変更日時をファイルのアクセス日時に設定

普段使っているファイラーはファイルのアクセス日時でソートされるように設定しています。大きめのリポジトリをcloneしてコードを読む時に、意外とファイルの最終変更日時が参考になったりします。仕事で使うリポジトリや、定期的にpullしているなら、徐々に変更のないファイルはファイラーの下の方に移動していく (上の方からアクセス日時の降順として) のですが、cloneしたばかりだとこうは行きません。

要はgitリポジトリ内の各ファイルのアクセス日時を、そのファイルのgit履歴上での最終変更日時に戻したいという気持ちになるわけです。そうするとファイラー上でもいい感じにファイルがソートされるのです。

#!/usr/bin/env bash

if command -v gdate >/dev/null 2>&1; then
  DATE=gdate
else
  DATE=date
fi

while IFS= read -r -d '' file; do
  logtime=$(git log -1 --format=%ci "$file")
  mtime=$("$DATE" -d "$logtime" +%Y%m%d%H%M.%S)
  echo "$(printf "%-80s" "$file")"$'\t'"$logtime"$'\t'"$mtime"
  touch -t "$mtime" "$file"
done < <(git ls-files -z)

実行してみるとこういう感じ。

 $ git-touch
.ctags                                                                              2015-08-28 10:47:55 +0900   201508281047.55
.tmux.conf                                                                          2016-02-05 08:43:46 +0900   201602050843.46
.vimrc                                                                              2017-05-01 18:38:35 +0900   201705011838.35
.vimshrc                                                                            2017-03-10 20:45:22 +0900   201703102045.22
.zshrc                                                                              2017-04-20 00:32:39 +0900   201704200032.39
 $ stat -x .zshrc
  File: ".zshrc"
  Size: 10518        FileType: Regular File
  Mode: (0644/-rw-r--r--)         Uid: (  501/ itchyny)  Gid: (   20/   staff)
Device: 1,4   Inode: 203408150    Links: 1
Access: Thu Apr 20 00:32:39 2017
Modify: Thu Apr 20 00:32:39 2017
Change: Sun May  7 01:03:05 2017

atimeとmtimeがgitでのファイル更新時刻になりました。やったー!

git logはタイムゾーン込みで保存しているようなので、touchする前にローカルタイムゾーンに変換しないといけない。最初はsedタイムゾーン取り除いていたけれど、流石に乱暴だったのでdateコマンドを挟むようにしました。

できたーと思ってふと検索してみるといっぱいでてきた… まぁみんなやるよねぇ…

macOSの (というかFreeBSDの) dateコマンドの-dは日付を指定するオプションじゃないのが罠っぽい。date -jfであれこれしたほうが本当はいいけれど、coreutilsのgdateに逃げた。あとスペース含むファイル名の扱いにはご注意を。

dateコマンドのところはPythonでやろうかと思ったけれど、実装してみたら意外とオーバーヘッドが大きかった。まぁオーバーヘッドと言っても二倍時間がかかるくらいなんですが、リポジトリ内のファイルを全部なめて更新していくので、できれば速いほうが嬉しい。速度を取るかポータビリティを取るかはいつも難しい。PythonPythonで2.7と3系両方で動くスクリプトを書ける自信はない。ところで jq fromdateiso8601 を使う手も考えたのですが (ポータビリティ的には良いアイディア)、タイムゾーン部分をパースできなくて (Zしか対応してない)、なーにがISO 8601じゃと思いました。ああ、いい話だなぁ。それじゃーね!ばいばい!

VimプラグインのTravis CIテストを複数のVimのバージョンで動かそう

Vimプラグインにテストがあるのはあたりまえ。 そういう空気になってきたのはここ3年くらいのことでしょうか。

私自身、昔はあまりテスト文化に慣れておらず、「Vimプラグインみたいな小さなスクリプトにテストなんているのか?自分のプラグインは普段から使う、バグっていたらすぐ気がつくからテストなんていらないでしょ」と思っていました。 しかし、そういうテストのない自作プラグインがどんどん増えていき、3年4年と経ってしまうと自分のプラグインのコードを触りにくくなってきました。 昔はあまりVimプラグインの書き方に慣れていなかったので、酷いコードが絡み合っているのだけど、普段使う分には普通に便利なプラグイン、しかしリファクタリングしにくいというのがいくつかある状態です。

やっぱり、Vimプラグインもテストを書いたほうがよいですね。

id:thincaさんのvim-themisは、Vimプラグインのテストフレームワークとして広く使われています。 よくできていて、とても気に入っています。 github.com テストの書き方などはvim-themisのドキュメントや、これを使っている他のVimプラグインなどを参照されるとよいでしょう。

ところで、これを読まれているあなたが書かれているVimプラグインは、どのVimのバージョンで動きますか。 Vim 7.4では動きますか?Vim 7.3もサポートしてますか?それともVim 8.0のみのサポートですか?

これはVimプラグイン作者が決めることです。 7.3もサポートしてあげたいと思えばサポートしてあげて下さい。8.0以降しかサポートしないなら、そう書いておきましょう。 これは作者のポリシーなので、8.0以降しかサポートしないのはどうこうという風には思いません。

私はlightline.vimというプラグインを作ってメンテナンスしているのですが、かつてmap()v:keyを使うとVim 7.2で動かないという問い合わせが来たことがありました。 一瞬「そんな古いVimでこれまで動いていたのか」と思いましたが、せっかく自分のプラグインを便利と思って使っていただいている方なので、7.2でも動くようにコードを修正しました。

普段の開発は、新しいバージョンのVimを使っています。 私は毎日Vimをビルドし、最新のバージョンを使うようにしています。 新しいものを使っているとたまにバグにも遭遇しますし、新しい機能もすぐに知ることができます。 しかし、新しい機能ばかり見ていると、古いバージョンで動かない変更をプラグインに入れてしまうかもしれません。 動作確認用に古いバージョンのバイナリーはストックしていますが、手で古いバージョンでのテストを回すのはあまりに面倒です (実はそういうシェルスクリプトは手元にあるのですが…)。

そこで、Travis CIでVimの複数のバージョンでテストを動かそうという発想になるわけです。 lightline.vimも一年くらい前から複数バージョンでのテストを行ってはいましたが、改めて設定を見直してみたところ、次のような感じになりました。

language: generic

sudo: false

install:
  - git clone --depth=1 https://github.com/thinca/vim-themis /tmp/themis
  - (if ! test -d $HOME/vim-$VIM_VERSION/bin; then
       git clone https://github.com/vim/vim $HOME/vim &&
       cd $HOME/vim &&
       git checkout v$VIM_VERSION &&
       ./configure --prefix=$HOME/vim-$VIM_VERSION &&
       make &&
       make install;
     fi)

cache:
  directories:
    - $HOME/vim-$VIM_VERSION

env:
  - VIM_VERSION=8.0.0000
  - VIM_VERSION=7.4
  - VIM_VERSION=7.3

script:
  - export PATH=$HOME/vim-$VIM_VERSION/bin:$PATH
  - vim --version
  - /tmp/themis/bin/themis --reporter spec

VIM_VERSIONの部分は、対応したいバージョンを入れておくと良いでしょう (タグと対応しているのでgit tagで確認)。 かつてはTravisへの知識がなくて、複数のVimのバージョンでのテストを直列にしていたのですが、Environmentを使うと並列になってハッピーになりました。 Travis CIのキャッシュを使うことで、毎回Vimをビルドすることもcloneすることもありません。 Vimリポジトリをgit cloneするだけでも10秒くらいかかっていたので、かなり速くなりました。 あとcache.directoriesの中で環境変数使えるんですね… 便利…

ここまでダラダラと書いてきたけど、あれ実はみんなやってるのかなと思ってググったらaiya000さんの記事が見つかりました。 qiita.com そして自分のidが最後に書いてあった… Twitterでやり取りしたような気がするけどもう覚えてなかった…

まとめ

Vimプラグインもテストを書きましょう。 どのバージョンをサポートするかをきちんと決めましょう。 古いバージョンでもテストを動かしましょう。

みたいな感じ!じゃあね〜!