Using fzf in your program
2016. 02. 10.

fzf is written in Golang and distributed as an executable binary instead of as a library you can link to your Go program. Some might see this as a limitation, it is, but you can still use fzf for interactive filtering in different programming languages, just like you use it in bash or zsh.

In this post, I will demonstrate how it can be done in a few programming languages that I use regularly.

Ruby

We write with_filter function that takes fzf command as the first argument and a block which produces the input to the command, and returns the selected entries as an array.

def with_filter(command)
  io = IO.popen(command, 'r+')
  begin
    stdout, $stdout = $stdout, io
    yield rescue nil
  ensure
    $stdout = stdout
  end
  io.close_write
  io.readlines.map(&:chomp)
end

with_filter('fzf -m') do
  1000.times do |n|
    puts n
    sleep 0.005
  end
end

The function is generic in the sense you can use it with any other external interactive/non-interactive filters, e.g. fzf-tmux or grep 123.

Clojure

We do the same with Clojure. The code here is a bit more involved, but it will give you a hint on how it can be done in other JVM languages.

(require '[clojure.java.io :as io])
(import 'java.lang.ProcessBuilder$Redirect)

(defmacro with-filter
  [command & forms]
  `(let [sh#  (or (System/getenv "SHELL") "sh")
         pb#  (doto (ProcessBuilder. [sh# "-c" ~command])
                (.redirectError
                  (ProcessBuilder$Redirect/to (io/file "/dev/tty"))))
         p#   (.start pb#)
         in#  (io/reader (.getInputStream p#))
         out# (io/writer (.getOutputStream p#))]
     (binding [*out* out#]
       (try ~@forms (.close out#) (catch Exception e#)))
     (take-while identity (repeatedly #(.readLine in#)))))

(with-filter "fzf -m"
  (dotimes [n 1000]
    (println n)
    (Thread/sleep 5)))

You might have noticed the reference to /dev/tty which makes the code not portable across different platforms. However, fzf only works on Unix environment, so this is a non-issue, at least for now.

Golang

This Go example isn't as elegant as the previous ones as there's no "automagic" stdout redirection for the goroutine that feeds the input, so we have to use fmt.Fprintln instead of fmt.Println.

package main

import (
    "fmt"
    "io"
    "os"
    "os/exec"
    "strings"
    "time"
)

func withFilter(command string, input func(in io.WriteCloser)) []string {
    shell := os.Getenv("SHELL")
    if len(shell) == 0 {
        shell = "sh"
    }
    cmd := exec.Command(shell, "-c", command)
    cmd.Stderr = os.Stderr
    in, _ := cmd.StdinPipe()
    go func() {
        input(in)
        in.Close()
    }()
    result, _ := cmd.Output()
    return strings.Split(string(result), "\n")
}

func main() {
    filtered := withFilter("fzf -m", func(in io.WriteCloser) {
        for i := 0; i < 1000; i++ {
            fmt.Fprintln(in, i)
            time.Sleep(5 * time.Millisecond)
        }
    })
    fmt.Println(filtered)
}

That's all for now. I'd be happy to hear about more examples in different languages that are not listed here.

» capture | close