Fuzzy-finder as a Go library

ktr
4 min readFeb 8, 2019

--

About fuzzy-finders

Today, fuzzy-finders such that fzf, fzy or skim become to more and more popular. These fuzzy-finders are mainly used in a command-line so that we can get powerful benefit using pipes and filters on UNIX/Linux systems. There are many use-cases to use it effectively, for example, looking up an executed command previously from the command history in the current shell, changing the current directory to a Git repository by using ghq and a fuzzy-finder, and so on.

On the other hand, CLI tools are increasing that use a fuzzy-finder as a part of the core features by executing it internally. enhancd is one of the most famous commands that assumes that to use arbitrary fuzzy-finder to select a path which you want to go to. Also, itunes-cli, which I developed is another example. It lists all tracks, which are managed by iTunes, then you can play the selected track. I think it is a good way to incorporate a fuzzy-finder as a part of an application if necessary.

Limits of command-line fuzzy-finders

However, there are limits to deal with fuzzy-finder’s features in several cases.

First, it is hard to distinguish between two or more entities that have the same text. In the example of itunes-cli, it is possible to conflict tracks such that same track names, but different artists. To avoid such conflicts, we have to display the artist names with each track name. It seems like the problem has been solved, but it still has the problem. It is possible to conflict in case of same track names, same artists, but other albums, which each track belongs to.

This problem is difficult to solve because pipes and filters are row-based mechanisms, there are no ways to hold references that indicate list entities.

Another problem of incorporating a fuzzy-finder is the complexity of installation of it. Usually, these tools require that it has been installed one fuzzy-finder as a precondition. In addition, to treat the fuzzy-finder, an environment variable configuration such that export TOOL_NAME_FINDER=fzf is required. It is hassle and complicated.

Treating fuzzy-finders as a library

For these reasons, I wrote a Go library, ktr0731/go-fuzzyfinder that provides fuzzy-finding, which returns the selected entity’s index.

Let’s see actual interfaces. It provides two public functions, Find and FindMulti. Find provides the most basic fuzzy-finding.

func Find(slice interface{}, itemFunc func(i int) string, opts ...option) (int, error)

I assume that we have a Track slice, tracks which is a user-defined type. It has some tracks that are conflicting with other track names such that foo or baz .

type Track struct {
Name string
AlbumName string
Artist string
}
var tracks = []Track{
{"foo", "album1", "artist1"},
{"bar", "album1", "artist1"},
{"foo", "album2", "artist1"},
{"baz", "album2", "artist2"},
{"baz", "album3", "artist2"},
}

To use the fuzzy-finding feature, all you need is calling Find with tracks just like follows. The second argument is a function that returns the name of tracks[i], it is used to display each entity.

idx, err := fuzzyfinder.Find(tracks, func(i int) string {
return tracks[i].Name
})

The output is as follows. It looks work fine, but still, we can’t distinguish tracks have the same name. It is possible to display all information such that track name, album name and artist name in one line simply. However, there is a smarter way to display information associated with each track by the preview window feature.

only entity names

The preview window is one of the most important features of go-fuzzyfinder . It is highly inspired by the fzf’s preview window. The preview window feature enables you to display optimized information related to the currently selected entity. To use the preview window, you have to call Find with WithPreviewWindow option. Example code and its result is as follows. Note that variables w and h are the width and the height of the preview window, so you can adjust the output of the preview window as you think.

idx, _ := fuzzyfinder.Find(
tracks,
func(i int) string {
return tracks[i].Name
},
fuzzyfinder.WithPreviewWindow(func(i, w, h int) string {
if i == -1 {
return ""
}
return fmt.Sprintf("%s\n\nArtist: %s\nAlbum: %s",
tracks[i].Name,
tracks[i].Artist,
tracks[i].AlbumName)
}))
The preview window displays the selected track information

Furthermore, by using ktr0731/go-fuzzyfinder , built tools don’t require any fuzzy-finders.

Conclusion

I wrote a Go library that provides a fuzzy-search with a fzf-like terminal user interface. I think that it makes your tools more expressively!

https://github.com/ktr0731/go-fuzzyfinder

--

--