blog.syfm

徒然なるままに考えていることなどを書いていくブログ

go-fuzzyfinder で色をつけられるようになった

個人的にほそぼそと開発をしていた ktr0731/go-fuzzyfinder を使うと fzf のような UI を持つ CLI ツールを非常に簡単につくることができます。go-fuzzyfinder では、Preview Window という、現在選択しているアイテムの詳細情報を知らせるための表示領域を持っています。これは非常に便利な機能ですが、一方で未実装の機能が一つありました。

それが Preview Window 内の文字列にスタイルをつけて表示する機能です。fzf の --preview フラグでは同様に Preview Window を表示できますが、こちらでは色をつけた表示も可能であるため、go-fuzzyfinder もこの機能を導入したいと考えていました。

そして最近になってようやくこの機能を入れることができました。この記事では、この機能をどのように使うことができるのか、そしてどのような設計・実装になっているのかを紹介します。

使い方

API に変更はありません。go-fuzzyfinder を v0.7.0 にアップデートするだけでこの機能を使えるようになります。 PreviewWindow に表示される文字列に ANSI エスケープシーケンス が含まれている場合、go-fuzzyfinder はそれを解釈し、パラメータに応じてスタイルが適用されます。

たとえば、ターミナルで以下のようなコマンドを実行してみます。Hello, の部分はボールドされており、World! の部分は赤色になっています。

$ echo '\x1b[1mHello, \x1b[0;31mWorld!'

go-fuzzyfinder の PreviewWindow でも同じ文字列を表示してみます。

ターミナルで表示したときとまったく同じような表示となりました。

同様に、ファイルを bat で開き、その出力を go-fuzzyfinder の PreviewWindow で表示してみた例です。bat は cat のような振る舞いをしますが、シンタックスハイライト付きのリッチな表示をします。PreviewWindow でも同様にカラフルな表示となりました。

このように、go-fuzzyfinder がエスケープシーケンスを解釈できるようになり、PreviewWindow の表示をよりリッチにできるようになりました。

Select Graphic Rendition

Terminal で色を付けるための機能は Select Graphic Rendition (SGR) と言います。これはANSI エスケープシーケンスの仕様の一つで、色だけでなくボールドやイタリックといった表現もできます。

たとえば先ほどの例では以下のような文字列でした。

$ echo '\x1b[1mHello, \x1b[0;31mWorld!'

\x1b[CSI (Control Sequence Introducer) と呼ばれるもので、これにより ANSI エスケープシーケンスの開始を示しています。あとに続くセミコロンで区切られた数値はパラメータで、このパラメータによりどのような表示にするかを指定できます。最後に m が付き、これによりパラメータの終了を示します。 今回の例では、先頭にさっそく CSI が現れており、パラメータとして 1 が指定されています。これはボールドを示しており、表示を見ると Hello, が太字になっているのが分かると思います。また、後続の CSI では 0 および 31 が指定されています。0 は既存のスタイルをすべてリセットし、31 は文字列を赤色にします。

色の指定にはいくつかの種類があり、それぞれ指定方法が異なっています。 もっとも基本的なものは 16 色のみの指定ができ、単一の値を取ります。

\x1b[31m

次に 256 色の指定方法があります。パラメータは文字色は 38;5、背景色は 48;5 から始まり、n の値によりどの色を選択するかを指定します。

\x1b[38;5;<n>m
\x1b[48;5;<n>m

次に RGB の指定方法があります。これは True Color とも呼ばれています。パラメータは文字色は 38;2、背景色は 48;2 から始まり、<r><g><b> の値によって色を指定します。 RGB で 16 色および 256 色のそれぞれの色も表現できますが、これらは区別できなければいけません。ターミナルにカスタムテーマが適用されている場合、RGB による指定ではこの設定が無視されます。

\x1b[38;2;<r>;<g>;<b>m
\x1b[48;2;<r>;<g>;<b>m

SGR と gdamore/tcell

go-fuzzyfinder はその描画に gdamore/tcell を利用しています。tcell でもスタイルを適用できますが、SGR をそのまま解釈できるわけではありません。そのため、入力となる文字列をなんらかの形でパースし、tcell の API に適合できるように落とし込んで上げる必要があります。 SGR のシーケンスを含んだ文字列をパースできるライブラリはいくつか存在していました。ただ、一部の機能が不十分だったり、go-fuzzyfinder の既存実装にうまく馴染むものがありませんでした。

そこで、1 からライブラリを実装することにしました。それが ktr0731/go-ansisgr です。

ktr0731/go-ansisgr

go-ansisgr では、NewIterator という関数があり、これが唯一のエントリポイントとなっています。この関数はイテレータを返し、このイテレータエスケープシーケンスを除いた文字と、その文字に適用されているスタイルを返します。

実際の例を見てみましょう。

in := "\x1b[1mHello, \x1b[0;31mWorld!"
iter := ansisgr.NewIterator(in)

for {
        r, style, ok := iter.Next()
        if !ok {
                break
        }

        // do nothing.
}

iter.Next() による 3 つの戻り値があります。

okイテレータの終端を表します。 r は次の文字を表す rune 型の値です。この例ではループごとに Hello …のようになります。

style はその文字に適用されているスタイルです。こちらは少し複雑になっています。ForegroundBackground というメソッドがあり、それぞれ文字色と文字の背景色を表しています。このメソッドの戻り値により、それぞれに色が設定されているかどうかがわかります。

color, ok := style.Foreground()

oktrue だった場合、色が設定されています。color を見ることでどんな色が設定されているかがわかります。 color.Mode() は、その色が 16 色、256 色、RGB のどの形式で指定されているかを表しています。また、color.Value() はその形式におけるパラメータの値です。たとえば \x1m[31m では 31、\x1m[38;5;120m では 120、\x1m[38;2;10;20;30m では 660510 (0x0a141e) となります。 RGB の場合、color.RGB() により R、G、B それぞれの値を取得することもできます。

また、style には Bold()Italic() のような bool を返すメソッドがあり、この値によってどのスタイルが適用されているかを表します。

API の一覧について、より詳しくは ドキュメント を参照してください。

go-fuzzyfinder への組み込み

こうしてできた go-ansisgr を go-fuzzyfinder へ組み込みました。gdamore/tcell は一文字ずつ描画していくため、go-ansisgr のイテレータのようなインターフェースとの組み合わせが良く、go-fuzzyfinder へ組み込んだときも大きなコードの変更なしに導入できました。*1

実際の diff は こちら から見ることができます。既存の描画のためのループ内で iter.Next() を呼び、描画対象文字とそのスタイルを順番に取得しています。

まとめ

go-fuzzyfinder で SGR がサポートされ、スタイルを適用した文字列を PreviewWindow で表示できるようになりました。 この機能の大部分は go-ansisgr によって実装・提供されており、このライブラリは入力となる SGR を含んだ文字列をイテレータ形式で扱うことができ、特に gdamore/tcell のような一文字ずつ描画していくようなライブラリとの相性が良いです。

go-fuzzyfinder も go-ansisgr もぜひお試しください!

*1:そうなるように API を設計したので当たり前といえば当たり前ですが…

ローカルで pkg.go.dev 形式のドキュメントを閲覧する

Go を書くとき公開されている API や、そのコメントに不足や余分がないかチェックするために GoDoc を使っていた。特にプライベートパッケージはインターネットからドキュメントを参照できないため、GoDoc をローカルで起動し、チェックしていた。

syfm.hatenablog.com

ただ、Go modules が登場し、GoDoc から pkg.go.dev へ移行したため、pkg.go.dev と GoDoc のどちらも使っているような状況になっていた。 また、GoDoc には Go modules 関連機能がなかったり、pkg.go.dev にはどんどん新機能が入るのに対し GoDoc には実装されなかったりと違いが大きくなってきた。

そこで godoc コマンドのようにローカルで pkg.go.dev 形式のドキュメントを見ることができないかを調べた結果、golang.org/x/pkgsite に含まれているコマンドで実現できるようだった。

pkg.go.dev

golang.org/x/pkgsiteOSS となったのは 2020 年 6 月頃で、2021 年 2 月頃に pkgsite コマンドが追加されており、簡単にローカルでドキュメントを見ることができるようになっていた。

インストールは go install でできるが、Go のバージョンによっては特定の commit をチェックアウトしてインストールする必要がある。

$ cd pkgsite

# Go 1.18
$ git checkout 9ffe8b928e4fbd3ff7dcf984254629a47f8b6e63

# Go 1.17
$ git checkout 4d836c6a652cde92f433967680dfd6171a91ec12

$ go install ./cmd/pkgsite

使い方は非常にシンプルで、ドキュメントを表示したいモジュールのディレクトリへ移動し、以下のコマンドを実行するだけで良い。 デフォルトで localhost:8080 で起動するが、このあたりの挙動はコマンドライン引数で変更できる。 なお、pkg.go.dev と違い、検索といった一部の機能は利用できない。

$ pkgsite

これで GoDoc と同じように pkg.go.dev 形式でドキュメントを閲覧できるようになり、便利になった 👏

一年を振り返り… (2021)

f:id:ktr_0731:20211228003939j:plain

仕事

今年もメルペイのコード決済チームに在籍しており、主に外部パートナーさまとの接続や、既存のプロダクトの部分的な改善を多く行っていた。今年関わっていたプロジェクトは去年より小粒のものが多く、その点では達成感はやや少なかった。ただ、今年は自分たちのチームが持っているプロダクトを見つめ直す機会が多く、チームでのテックロードマップを策定するといった、プロダクトの将来を見据えて物事を考えるようになってきた。来年頭からは目指すべき将来のために一歩ずつ進んでいく計画をチームで立てているので、それに関わることができるのは楽しみ。

来年からはチームの TechLead を引き継いで、自身が新たな TechLead となるので、これらの技術的な意思決定について、自分もチームメンバーも納得がいくように尽力していきたい。

これに加えて、スポットで副業もやっていた。主に gRPC 周りの機能開発・追加で、普段の業務とはかなり異なるアプリケーションおよびコードベースだったので楽しく開発を行うことができた。

技術

仕事でよく使っていた技術については今年はあまり変化がなかった。相変わらず Google Cloud 関連プロダクトと Kubernetes、Go、gRPC、Protocol Buffers を使っている。Protocol Buffers については、エンコーダとデコーダフルスクラッチする機会があり、より理解が深まったと感じている。

engineering.mercari.com

会社のチーム内で行っている輪読会ではデータ指向アプリケーションデザインを読んでおり、この本からログの有用性について知ることができた。ログの append-only の性質を生かした、LSM ツリーベースのストレージエンジンや、線形化可能なストレージの実装はとても学びが多かった。

プライベートでは去年に引き続き、Web フロントエンド技術を使いつつ、年末に新たに Flutter を触り始めた。もともと初期の Android 開発経験はあったので、今と昔でまったく開発環境が違っていることに驚いた。React と似た記述と高速なライブリロード、複数のプラットフォームへのビルドができる点が非常に便利。Dart も初めて触ったが、文法自体は理解に苦しむところはなかった。今はまだ触っているだけなので、今後なんらかのアプリケーションを作ってみたい。

執筆

去年・一昨年に Software Design へ寄稿した記事を再編集した「エキスパートたちの Go 言語」が出版された。自分の担当分ではあまり差分はなかったが、pkg.go.dev への切り替えや go install についての紹介を追加した。

会社のテックブログでは Merpay Tech Openness Month 2021 というイベントの一環で先程挙げた Protocol Buffers の内部実装に関する記事を執筆した。

個人ブログでは去年よりさらに書けていない…。これはインプットの量が少なかったことに起因しているのは間違いないので、来年はもっとインプット・アウトプットを増やしていきたい。

イベント登壇

Merpay Tech Fest 2021 という完全オンラインのイベントで「共通QRコード決済システムの裏側」という発表をした。これは去年関わっていた、d 払いとメルペイの連携の仕組みについての発表で、いつかどこかで話す機会が欲しいなと感じていたので良い機会だった。

engineering.mercari.com

去年と変わらず新型コロナによってリモート開催のイベントばかりだったので、個人的なイベント登壇については一切行っていなかった。

OSS

Evans と go-fuzzyfinder を今もメンテナンスしている。自ら大きな機能追加を行うことは少なくなっているが、いくつか Issue や Pull Request をもらっており、マイナーアップデートを行っていた。 また、Evans の紹介を社内向けに行ったりもしていた。Evans を使ってくれている人たちが身近にいるというのは言葉では言い表せない喜びがあって、OSS やってて良かったなぁと感じる。

GitHub Sponsors では去年に引き続き 4 名、新しく 3 名の方から支援をいただいた。本当にありがとうございます!

読んだ本

技術書

  • Cloud Native Go
  • モノリスからマイクロサービスへ
  • 詳説データベース

一部はまだ読んでいる途中だが、どれも良い本で学びがある。

小説

最近マグリット表紙の新装版が出たので買った。めちゃめちゃおもしろい。

実用書

  • はじめての多肉植物 育て方&楽しみ方
  • これでうまくいく!よく育つ多肉植物BOOK
  • ハオルチア (NHK趣味の園芸 12か月栽培ナビNEO)
  • カクタスハンドブック 原種サボテンを楽しむ

去年亡くなった祖父は植物栽培が趣味だったので、いくつか植物を自宅へ持ち帰ったら沼にハマってしまった。地元に帰るたびに少しずつ持ち帰っている。

漫画

九条の大罪が結構おもしろくて好き。

旅行

今年も新型コロナによりあまり旅行はできなかったものの、ちょいちょい行っていた。

鎌倉

2 月に日帰りでシュッと行った。コロナ禍ということもあり、観光客は少なく、のんびり歩くことができた。鎌倉で飲むビールは最高。

f:id:ktr_0731:20211228124629p:plain

箱根・強羅

パートナーの誕生日に合わせて行った。初めて 3 万円クラスの部屋に泊まったが、部屋・景色・料理・温泉・接客のすべてにおいて値段以上の価値があって本当に良かった。必ずもう一度泊まりたい。
この旅行ではあまり観光をメインにせず部屋でゆっくり過ごそうと決めており、実際チェックインしてからチェックアウトするまでほとんどの時間を部屋で過ごした。客室露天風呂がついているのが神すぎる。

f:id:ktr_0731:20211230001238j:plain

観たアニメ

友人と定期的にやっているアニメを観る会で観たものは基本的にリアタイではないアニメばかりなのでわりと前のアニメも混ざっている。
個人的に今年はシン・エヴァがダントツで良かった。エヴァは旧エヴァも含めてすべて観ていたので、また EOE のようにならないか心配だったが、実際はそんなことはなく、その上すべてのエヴァンゲリオンにさよならしていたのでエヴァはようやく完結したのだなと感じることができた。

プリンセス・プリンシパル Crown Handler 第1章も無印の雰囲気が存分に出ていて良かった。第2章が公開されているのを唐突に思い出してまだ観ていなかった第1章を観たものの、第2章が観れる劇場がほとんど上映終了していたのでそちらを観るのはあきらめた…。

やったゲーム

JUDGE EYES と LOST JUDGMENT がめちゃめちゃおもしろかった。ストーリーがかなりエグくて良いのと、バンバン人をなぎ倒すことができるので楽しい。

植物

f:id:ktr_0731:20211230010925p:plain

試しに今年は植物についても書いておく。前述したとおり、祖父の家から少しずつ植物を持って帰ったり、自分たちで買ったり、ふるさと納税でもらったりした。植物は毎日少しずつ成長していくので、フィードバックループを回したり仮説検証を行うのが好きなエンジニアと相性が良い趣味だと思う。
ここには写っていないガジュマルと、ハオルチアの姫椿錦がお気に入り。リトープスの生育はかなり難しくて試行錯誤している。

人生

自身に関しては、今年からパートナーとの同棲を始めたので日々の暮らしが非常に充実し、楽しいものとなった。若干の漠然とした不安はあったものの、いざ一緒に住んでみるとほとんどストレスなく生活ができているのでやはり波長があっているんだなと思った。東京の 3LDK 以上の賃貸の少なさや、ペット可賃貸の少なさから、今はマンションを買おうか検討していて、今年は非常に人生をやってきている年だったなぁと感じる。

自分以外のことで言うと、母方の祖母が癌になった。去年は父方の祖父が亡くなっているので、不幸が連続している気がしてしんどい。自分は見守ることしかできないけど早く良くなってほしい。

また、自分の両親の仲についてもあまり良くないのだと感じさせられる年だった。そういったこともあり、楽しい自分自身の生活だけに目を向けて、自分たち以外のことからは目を背けたいと思うことがしばしばあった。

終わりに

今年は嬉しいこと・楽しいことと同じくらい苦しいこと・しんどいことがあった一年だった。毎年毎年、少しずつ、確実に年齢を重ねて老いていっている現実に目を向けると恐ろしくなるが、こればかりはどうしようもない。自分のペースで向き合っていくしかないのだ。

できることなら来年は楽しいことの割合がより増えますように。