Go の Fuzzy Finder ライブラリ「go-fzf」の紹介
Go の Fuzzy Finder ライブラリである go-fzf を作りました。
go-fzf を使用すると次のような Fuzzy Finder を簡単に実装することができます。
上の例で実行している main.go
の内容はこれだけです。
package main
import (
"fmt"
"log"
"github.com/koki-develop/go-fzf"
)
func main() {
items := []string{"hello", "world", "foo", "bar"}
f, err := fzf.New()
if err != nil {
log.Fatal(err)
}
idxs, err := f.Find(items, func(i int) string { return items[i] })
if err != nil {
log.Fatal(err)
}
for _, i := range idxs {
fmt.Println(items[i])
}
}
他にもオプションを指定することで複数選択できるようにしたり、大文字 / 小文字の区別や UI のカスタマイズなど、様々なことができます。
この記事では go-fzf の使い方や利用技術について簡単にまとめます。
go-fzf を CLI で試してみる
go-fzf を使うとどのようなことをできるのかを実際に試してみたい場合は gofzf
CLI を使ってみてください。
gofzf
CLI は go-fzf を使用して実装されており、 go-fzf のほとんど全ての機能を利用することができます。
Homebrew を使用している場合は brew install
でインストールすることができます。
$ brew install koki-develop/tap/gofzf
もしくは go install
でインストールすることもできます。
$ go install github.com/koki-develop/go-fzf/cmd/gofzf@latest
gofzf --help
でヘルプを表示します。
$ gofzf --help
gofzf --help
$ gofzf --help
Usage:
gofzf [flags]
Flags:
--limit int maximum number of items to select (default 1)
--no-limit unlimited number of items to select
--case-sensitive case sensitive search
--prompt string (default "> ")
--input-placeholder string (default "Filter...")
--cursor string (default "> ")
--selected-prefix string (default "● ")
--unselected-prefix string (default "◯ ")
--input-position string position of input (top|bottom) (default "top")
--count-view (default true)
--prompt-fg string
--prompt-bg string
--prompt-bold
--prompt-blink
--prompt-italic
--prompt-strike
--prompt-underline
--prompt-faint
--input-placeholder-fg string
--input-placeholder-bg string
--input-placeholder-bold
--input-placeholder-blink
--input-placeholder-italic
--input-placeholder-strike
--input-placeholder-underline
--input-placeholder-faint (default true)
--input-text-fg string
--input-text-bg string
--input-text-bold
--input-text-blink
--input-text-italic
--input-text-strike
--input-text-underline
--input-text-faint
--cursor-fg string (default "#00ADD8")
--cursor-bg string
--cursor-bold
--cursor-blink
--cursor-italic
--cursor-strike
--cursor-underline
--cursor-faint
--cursorline-fg string
--cursorline-bg string
--cursorline-bold (default true)
--cursorline-blink
--cursorline-italic
--cursorline-strke
--cursorline-underline
--cursorline-faint
--selected-prefix-fg string (default "#00ADD8")
--selected-prefix-bg string
--selected-prefix-bold
--selected-prefix-blink
--selected-prefix-italic
--selected-prefix-strke
--selected-prefix-underline
--selected-prefix-faint
--unselected-prefix-fg string
--unselected-prefix-bg string
--unselected-prefix-bold
--unselected-prefix-blink
--unselected-prefix-italic
--unselected-prefix-strke
--unselected-prefix-underline
--unselected-prefix-faint (default true)
--matches-fg string (default "#00ADD8")
--matches-bg string
--matches-bold
--matches-blink
--matches-italic
--matches-strke
--matches-underline
--matches-faint
-h, --help help for gofzf
-v, --version version for gofzf
gofzf
をそのまま実行すると、作業ディレクトリから再帰的にファイルをあいまい検索します。
$ gofzf
検索対象のアイテムを標準入力から改行区切りで渡すこともできます。
$ ls | gofzf
gofzf
CLI の詳しい使い方については CLI の公式ドキュメント ( 日本語 ) をご参照ください。
go-fzf の使い方
インストール
次のコマンドで go-fzf をインストールします。
$ go get -u github.com/koki-develop/go-fzf
その後、プログラムから go-fzf を import してください。
import "github.com/koki-develop/go-fzf"
基本的な使い方
まず fzf.New()
で Fuzzy Finder を初期化します。
f, err := fzf.New()
if err != nil {
// ...
}
初期化した Fuzzy Finder の Find()
メソッドを実行して Fuzzy Finder を起動することができます。
items := []string{"hello", "world", "foo", "bar"}
idxs, err := f.Find(items, func(i int) string { return items[i] })
if err != nil {
// ...
}
- 第 1 引数には任意の slice を渡します。 slice の要素の型はなんでもいいです。
- 第 2 引数にはインデックスを受け取って検索対象の文字列を返す関数を渡します。
今回の例では第 1 引数に渡しているitems
は[]string
型なので、items[i]
を返す関数を渡します。 - 戻り値には選択されたアイテムのインデックスの一覧が
[]int
型として返されます。
プログラムの全体
package main
import (
"fmt"
"log"
"github.com/koki-develop/go-fzf"
)
func main() {
items := []string{"hello", "world", "foo", "bar"}
f, err := fzf.New()
if err != nil {
log.Fatal(err)
}
idxs, err := f.Find(items, func(i int) string { return items[i] })
if err != nil {
log.Fatal(err)
}
for _, i := range idxs {
fmt.Println(items[i])
}
}
オプションを指定する
go-fzf では Functional Options Pattern を使用して様々なオプションを指定することができます。
例えば次のプログラムは、 fzf.WithLimit()
を使用して選択可能なアイテムの数を設定する例です。
f, err := fzf.New(fzf.WithLimit(4))
if err != nil {
// ...
}
プログラムの全体
package main
import (
"fmt"
"log"
"github.com/koki-develop/go-fzf"
)
func main() {
items := []string{"hello", "world", "foo", "bar"}
f, err := fzf.New(fzf.WithLimit(4))
if err != nil {
log.Fatal(err)
}
idxs, err := f.Find(items, func(i int) string { return items[i] })
if err != nil {
log.Fatal(err)
}
for _, i := range idxs {
fmt.Println(items[i])
}
}
もちろん複数のオプションを指定することも可能です。
次のプログラムは fzf.WithNoLimit()
オプションと fzf.WithCaseSensitive()
オプションを指定する例です。
f, err := fzf.New(
fzf.WithNoLimit(true),
fzf.WithCaseSensitive(true),
)
if err != nil {
// ...
}
プログラムの全体
package main
import (
"fmt"
"log"
"github.com/koki-develop/go-fzf"
)
func main() {
items := []string{"HELLO", "Hello", "hello"}
f, err := fzf.New(
fzf.WithNoLimit(true),
fzf.WithCaseSensitive(true),
)
if err != nil {
log.Fatal(err)
}
idxs, err := f.Find(items, func(i int) string { return items[i] })
if err != nil {
log.Fatal(err)
}
for _, i := range idxs {
fmt.Println(items[i])
}
}
UI をカスタマイズする
go-fzf の大きな特徴のひとつは UI のカスタマイズ性の高さです。
例えば fzf.WithCursor()
オプションを使用するとカーソルに表示する文字列を設定することができます。
f, err := fzf.New(fzf.WithCursor("=> "))
if err != nil {
// ...
}
プログラムの全体
package main
import (
"fmt"
"log"
"github.com/koki-develop/go-fzf"
)
func main() {
items := []string{"hello", "world", "foo", "bar"}
f, err := fzf.New(fzf.WithCursor("=> "))
if err != nil {
log.Fatal(err)
}
idxs, err := f.Find(items, func(i int) string { return items[i] })
if err != nil {
log.Fatal(err)
}
for _, i := range idxs {
fmt.Println(items[i])
}
}
また fzf.WithStyles()
オプションを使用すると各コンポーネントの装飾をカスタマイズすることもできます。
例えば次のプログラムはカーソルを太字にして、且つ文字色を赤色にしています。
f, err := fzf.New(
fzf.WithStyles(
fzf.WithStyleCursor(fzf.Style{Bold: true, ForegroundColor: "#ff0000"}),
),
)
if err != nil {
// ...
}
プログラムの全体
package main
import (
"fmt"
"log"
"github.com/koki-develop/go-fzf"
)
func main() {
items := []string{"hello", "world", "foo", "bar"}
f, err := fzf.New(
fzf.WithStyles(
fzf.WithStyleCursor(fzf.Style{Bold: true, ForegroundColor: "#ff0000"}),
),
)
if err != nil {
log.Fatal(err)
}
idxs, err := f.Find(items, func(i int) string { return items[i] })
if err != nil {
log.Fatal(err)
}
for _, i := range idxs {
fmt.Println(items[i])
}
}
他にも次のようなものを始めとした様々なコンポーネントのカスタマイズを行うことができます。
- プロンプト
- カーソル
- 選択中 / 未選択アイテムの接頭辞
- インプットの位置
- インプットのプレースホルダ
- カウントビュー
- プレビューウィンドウ
- etc.
詳しくは公式ドキュメント ( 日本語 ) をご参照ください。
その他
その他の go-fzf の詳しい使い方についてはライブラリの公式ドキュメント ( 日本語 ) をご参照ください。
また、公式の Examples には次のようなものを始めとした様々な使用例が記載されていますので、こちらも合わせてご参照ください。
- 複数選択
- 大文字 / 小文字を区別する
- キーマップ
- ホットリロード
- UI をカスタマイズする
- etc.
利用技術
gofzf
CLI では CLI フレームワークに Cobra 、リリースの自動化に GoReleaser を使用しています。
Fuzzy Finder の UI 実装には Bubble Tea を使用しています。
この辺りは以前公開した次の記事で簡単に紹介しているので、こちらをご参照ください。
まとめ
使ってもらえたら嬉しいです。
Discussion