Please purchase the course to watch this video.

Full Course
Creating command-line applications that facilitate user interaction can significantly enhance user experience and functionality. One effective method for enabling users to select from a list of options is through the use of the FZF
tool, a command-line fuzzy finder that allows for dynamic and interactive filtering of choices. This approach surpasses basic methods like scanf
, providing not only a more user-friendly interface but also the capability to handle fuzzy searches, making it easier for users to locate options quickly. Installation and configuration of FZF
are straightforward, allowing developers to integrate this powerful tool effortlessly into their applications for improved usability while also offering customizable features such as headers and display height to better suit the needs of the user. The ongoing exploration of file system interactions further emphasizes the versatility and power of command-line tools in developing efficient applications.
No links available for this lesson.
In this lesson, we'll explore another practical application of the exec
package: integrating the powerful fuzzy finder tool fzf
into our Go applications to create more interactive and user-friendly command-line interfaces.
Introduction to Fuzzy Finding
Creating command-line applications that facilitate user interaction can significantly enhance user experience and functionality. One effective method for enabling users to select from a list of options is through the use of the fzf
tool, a command-line fuzzy finder that allows for dynamic and interactive filtering of choices.
This approach surpasses basic methods like scanf
, providing not only a more user-friendly interface but also the capability to handle fuzzy searches, making it easier for users to locate options quickly.
What is fzf?
fzf
is a general-purpose command-line fuzzy finder. It's an interactive Unix filter for command-line that can be used with any list; files, command history, processes, hostnames, bookmarks, git commits, etc.
Example Application
Let's create a simple Go application that uses fzf
to allow users to select from a list of options. First, we need to ensure fzf
is installed on the system:
# On macOS with Homebrew
brew install fzf
# On Ubuntu/Debian
apt-get install fzf
# Or install directly from GitHub
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install
Now, let's write a Go program that generates a list of options and uses fzf
to let the user select one:
package main
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"strings"
)
func main() {
// List of options for the user to select from
options := []string{
"Option 1: Create a new project",
"Option 2: Open an existing project",
"Option 3: Configure settings",
"Option 4: View documentation",
"Option 5: Check for updates",
"Option 6: Exit",
}
selected, err := runFzf(options)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Printf("You selected: %s\n", selected)
}
// runFzf presents the options to the user via fzf and returns their selection
func runFzf(options []string) (string, error) {
// Check if fzf is installed
_, err := exec.LookPath("fzf")
if err != nil {
return "", fmt.Errorf("fzf not found, please install it first: %w", err)
}
// Create the fzf command
cmd := exec.Command("fzf", "--height", "40%", "--border", "--header", "Select an option:")
// Set up pipes for stdin, stdout, and stderr
stdin, err := cmd.StdinPipe()
if err != nil {
return "", err
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return "", err
}
cmd.Stderr = os.Stderr
// Start the command
err = cmd.Start()
if err != nil {
return "", err
}
// Write options to stdin
go func() {
defer stdin.Close()
for _, opt := range options {
fmt.Fprintln(stdin, opt)
}
}()
// Read the selection from stdout
scanner := bufio.NewScanner(stdout)
var selection string
if scanner.Scan() {
selection = scanner.Text()
}
// Wait for the command to finish
err = cmd.Wait()
if err != nil {
// fzf returns exit code 130 when the user cancels with Ctrl-C or Esc
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 130 {
return "", fmt.Errorf("selection was cancelled")
}
return "", err
}
return selection, nil
}
Enhanced Version with Support for Missing fzf
We can enhance our program to handle cases where fzf
isn't installed, falling back to a simple numbered list:
package main
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"strconv"
"strings"
)
func main() {
// List of options for the user to select from
options := []string{
"Create a new project",
"Open an existing project",
"Configure settings",
"View documentation",
"Check for updates",
"Exit",
}
selected, err := selectOption(options)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Printf("You selected: %s\n", selected)
// Handle the selection
switch selected {
case options[0]:
fmt.Println("Creating new project...")
case options[1]:
fmt.Println("Opening project...")
// And so on for other options
}
}
// selectOption tries to use fzf for selection, falls back to simple menu if unavailable
func selectOption(options []string) (string, error) {
// Try using fzf
selected, err := runFzf(options)
if err == nil {
return selected, nil
}
// If fzf fails or isn't installed, fall back to simple menu
if strings.Contains(err.Error(), "fzf not found") {
fmt.Println("Note: Install fzf for an enhanced selection experience")
return runSimpleMenu(options)
}
return "", err // Other error occurred
}
func runFzf(options []string) (string, error) {
// Check if fzf is installed
_, err := exec.LookPath("fzf")
if err != nil {
return "", fmt.Errorf("fzf not found, please install it first: %w", err)
}
// Create the fzf command
cmd := exec.Command("fzf", "--height", "40%", "--border", "--header", "Select an option:")
// Set up pipes for stdin, stdout, and stderr
stdin, err := cmd.StdinPipe()
if err != nil {
return "", err
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return "", err
}
cmd.Stderr = os.Stderr
// Start the command
err = cmd.Start()
if err != nil {
return "", err
}
// Write options to stdin
go func() {
defer stdin.Close()
for _, opt := range options {
fmt.Fprintln(stdin, opt)
}
}()
// Read the selection from stdout
scanner := bufio.NewScanner(stdout)
var selection string
if scanner.Scan() {
selection = scanner.Text()
}
// Wait for the command to finish
err = cmd.Wait()
if err != nil {
// fzf returns exit code 130 when the user cancels with Ctrl-C or Esc
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 130 {
return "", fmt.Errorf("selection was cancelled")
}
return "", err
}
return selection, nil
}
func runSimpleMenu(options []string) (string, error) {
reader := bufio.NewReader(os.Stdin)
// Display the menu
fmt.Println("\nPlease select an option:")
for i, opt := range options {
fmt.Printf("%d) %s\n", i+1, opt)
}
fmt.Print("\nEnter number: ")
// Read the input
input, err := reader.ReadString('\n')
if err != nil {
return "", err
}
// Parse the selection
input = strings.TrimSpace(input)
num, err := strconv.Atoi(input)
if err != nil {
return "", fmt.Errorf("invalid input: please enter a number")
}
// Validate the selection
if num < 1 || num > len(options) {
return "", fmt.Errorf("invalid selection: please enter a number between 1 and %d", len(options))
}
return options[num-1], nil
}
Using fzf for File Selection
Another common use case is allowing users to select files. Here's how we can implement that:
package main
import (
"bufio"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
func main() {
// Directory to search for files
dir := "."
if len(os.Args) > 1 {
dir = os.Args[1]
}
// Select a file
file, err := selectFile(dir)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Selected file: %s\n", file)
// Now we could open the file, display its contents, etc.
}
func selectFile(dir string) (string, error) {
// Find all files recursively
var files []string
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, path)
}
return nil
})
if err != nil {
return "", err
}
if len(files) == 0 {
return "", fmt.Errorf("no files found in %s", dir)
}
// Use fzf to select a file
return runFzf(files)
}
func runFzf(options []string) (string, error) {
// Check if fzf is installed
_, err := exec.LookPath("fzf")
if err != nil {
return "", fmt.Errorf("fzf not found, please install it first: %w", err)
}
// Create the fzf command with preview window for files
cmd := exec.Command("fzf",
"--height", "40%",
"--border",
"--header", "Select a file:",
"--preview", "cat {}", // Show file contents as preview
"--preview-window", "right:50%")
// Connect stdin/stdout/stderr
stdin, err := cmd.StdinPipe()
if err != nil {
return "", err
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return "", err
}
cmd.Stderr = os.Stderr
// Start the command
err = cmd.Start()
if err != nil {
return "", err
}
// Write options to stdin
go func() {
defer stdin.Close()
for _, opt := range options {
fmt.Fprintln(stdin, opt)
}
}()
// Read the selection from stdout
scanner := bufio.NewScanner(stdout)
var selection string
if scanner.Scan() {
selection = scanner.Text()
}
// Wait for the command to finish
err = cmd.Wait()
if err != nil {
// fzf returns exit code 130 when the user cancels with Ctrl-C or Esc
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 130 {
return "", fmt.Errorf("selection was cancelled")
}
return "", err
}
return selection, nil
}
Conclusion
Using fzf
in our Go applications provides a powerful and flexible way to enhance the user experience of our command-line tools. By leveraging external tools like fzf
, we can create more interactive and user-friendly interfaces without having to implement complex UI components ourselves.
The integration of fzf
is just one example of how we can use the exec
package to extend the capabilities of our Go applications by leveraging existing command-line tools. This approach allows us to build more sophisticated and user-friendly applications while still maintaining the simplicity and power of the command-line interface.
In the next module, we'll explore more advanced file system interactions and networking capabilities to further enhance our command-line applications.