Golang でコマンドライン Fuzzy Finder 「gof」作った。


この記事は Go Advent Calendar 2013 の 10 日目の投稿です。

はじめに

業務のツールや連携させる一部の機能として golang を使い出している方もチラホラ現れ始めました。
golang でウェブアプリケーションを書く人も多いですが、実はコマンドラインアプリケーションやバックグラウンドサーバを書くのに非常に役立ちます。

本記事では、golang を使うとどの様に簡単にバックグラウンドサーバや UI が書けるのかを紹介したいと思います。

非同期処理

例えば長い処理の終了を判定して画面を更新したいとします。長い処理を行っている間、キーボードイベントの判定や別の入出力処理、タイムアウト判定等を実行出来る様にする場合、C言語で書くと結構なコード量になり得ます。
しかし golang を使う事でこういった複雑な処理を簡単に実装出来てしまいます。
golang の特徴と言えば goroutine と channel になるのですが、長い処理を goroutine で追い出して終了時に channel を使って通信します。

done := make(chan bool)
go func() {
    // blocking
    done <-blockingJob()
}()

true/false を返す関数 blockingJob の結果を done という channel を使って外側に引き継ぎます。
メイン処理側ではこの done を select で待ちます。

select {
case <-done:
    // 処理が完了した場合の処理
case time.After(10 * time.Duration):
    // タイムアウト(10秒)
}

time.After は channel を返します。select では複数の channel を一度に問い合わせる事が出来ます。
もちろん、blockingJob を途中で中断するにはソケットを閉じて明示的にエラーを出させたりする必要があります。

UIでの非同期処理

time パッケージには便利な関数が多くあり例えば

  • キーをタイプされたら即座に再描画したい
  • 別の goroutine でファイルを検索し見つかった度に再描画したい

こういったイベントが輻輳する処理に対してタイマを使う事が出来ます。

// 再描画タイマ
timer := time.AfterFunc(0, func() {
    redraw_screen()
})

for {
    select {
    case ev := <-keyTyped: // キーイベント
        // 再描画条件を変更して
        key_typed(ev)
        // すぐさま再描画
        timer.Reset(1 * time.Microsecond)
    case fc := <-fileChanged:
        // ファイルを足して
        add_file(fc)
        // ちょっと後に再描画
        timer.Reset(200 * time.Microsecond)
    }
}

こうしておけば連続でファイルが見つかった場合でも再描画処理による負荷を掛けず、かつキーをタイプした場合には即時に再描画するという UI が出来上がります。
通常、キーボードイベントおよびタイムアウトをそれぞれ検知する必要がありますが、channel を使う事で可読性も高く、拡張性も高い処理になりました。
別のイベントを追加したい場合でも、channel を作って select に追加するだけになります。

Fuzzy Finder "gof"

さて、先日コマンドラインから Fuzzy Finder する fzf というアプリケーションを見つけたのですが、Windows で動かないのでムキーってなったので golang で作ってみました。
今回説明している処理パターンを使っています。

$ vim `gof`

の様にして使えます。単独で

$ gof -e

としてもいいですし

$ gof /tmp | gof

の様にして指定の一覧で絞り込む事も出来ます。おまけ機能として

$ gof -l

でアプリケーションランチャーにもなります(~/.gof-launcher に設定を記述)。

はじめは簡単に書けるか疑問でしたが、意外とすんなりと実装出来ました。

宜しければ使ってみて下さい。Windows でも動きます。