go-fuzzyfinder

repository·master·Indexed 19 days ago

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

A Go library providing an interactive, fzf-like terminal user interface for fuzzy-finding items. It includes functions for single item selection (Find) and multiple item selection (FindMulti), with support for preview windows, preselected items, custom matching modes (Smart, CaseSensitive, CaseInsensitive), and hot reloading via sync.Locker.

Tokens
3K
Snippets
13
Records
13
Agent score
67%

What's inside go-fuzzyfinder

  1. Configure a preview window

    master

    To help users distinguish between items with identical text (e.g., different artists with the same track name), use the WithPreviewWindow option. This allows you to render detailed information about the item currently under the cursor in a separate pane.

    // Inside Find or FindMulti options:
    fuzzyfinder.WithPreviewWindow(func(i, w, h int) string {
        if i == -1 {
            return ""
        }
        // Return a formatted string representing the preview content
        return fmt.Sprintf("Detail for item %d", i)
    })
  2. Use Find and FindMulti for fuzzy finding

    master

    The library provides two primary functions for interactive selection:

    1. Find: Used for single item selection.
    2. FindMulti: Used for selecting multiple items (similar to fzf -m).

    Both functions require a slice of items and a display function that converts an item index into a string for the UI. They return the index (or indices) of the selected item(s) and an error.

    // Example of FindMulti with a preview window
    tracks := []Track{...}
    
    idx, err := fuzzyfinder.FindMulti(
        tracks,
        func(i int) string {
            return tracks[i].Name
        },
        fuzzyfinder.WithPreviewWindow(func(i, w, h int) string {
            if i == -1 {
                return ""
            }
            return fmt.Sprintf("Track: %s (%s)\nAlbum: %s",
                tracks[i].Name,
                tracks[i].Artist,
                tracks[i].AlbumName)
        }),
    )
  3. Preselect items in Find and FindMulti

    master

    You can use the WithPreselected option to automatically highlight or select specific items when the UI opens. This works for both single selection (Find) and multi-selection (FindMulti).

    • In Find: The cursor will be positioned on the first item that satisfies the predicate.
    • In FindMulti: All items that satisfy the predicate will be initially selected.
    // Single selection mode: cursor moves to the first matching item
    idx, err := fuzzyfinder.Find(
        tracks,
        func(i int) string { return tracks[i].Name },
        fuzzyfinder.WithPreselected(func(i int) bool {
            return tracks[i].Name == "bar"
        }),
    )
    
    // Multi selection mode: all matching items are selected initially
    idxs, err := fuzzyfinder.FindMulti(
        tracks,
        func(i int) string { return tracks[i].Name },
        fuzzyfinder.WithPreselected(func(i int) bool {
            return tracks[i].Artist == "artist2"
        }),
    )
  4. Configure matching modes with WithMode

    master

    You can specify how the fuzzy finder matches input strings using WithMode(m mode).

    Available modes:

    • ModeSmart: The default mode. It starts as case-insensitive but switches to case-sensitive if an uppercase character is entered.
    • ModeCaseSensitive: Enables case-sensitive matching.
    • ModeCaseInsensitive: Enables case-insensitive matching.
    import "github.com/ktr0731/go-fuzzyfinder"
    
    // Example: Using case-sensitive mode
    finder.Find(items, fuzzyfinder.WithMode(fuzzyfinder.ModeCaseSensitive))
  5. Preselect items with WithPreselected

    master

    Use WithPreselected(f func(i int) bool) to specify which items should be highlighted/selected by default. The function f receives the index i and should return true if the item at that index should be preselected.

    • In Find mode, only the first preselected item is considered.
    • In FindMulti mode, multiple items can be preselected.
    • If used with WithCursorPosition, the cursor will automatically move to the first preselected item.
    fuzzyfinder.WithPreselected(func(i int) bool {
    	return i == 0 // Preselect the first item
    })
  6. Use Find() for single-item fuzzy searching

    master

    The Find function displays a terminal user interface for fuzzy-finding a single item from a provided slice.

    • Arguments:

      • slice: An interface representing the slice to search through. It must be a slice type.
      • itemFunc: A function func(i int) string that returns the string representation of the item at index i.
      • opts: Optional Option functions to configure the finder.
    • Returns:

      • The index of the selected item (int).
      • An error. If the user aborts the search (e.g., via Esc, Ctrl-C, or Ctrl-D) without making a selection, it returns ErrAbort.

    Note: All functions in this package are not goroutine-safe.

    index, err := fuzzyfinder.Find(mySlice, func(i int) string {
    	return mySlice[i]
    })
    if err != nil {
    	if errors.Is(err, fuzzyfinder.ErrAbort) {
    		// User aborted
    	}
    	return err
    }
    // Use index
  7. Enable hot reloading with WithHotReloadLock

    master

    To automatically reload the list when entries are appended to the underlying slice, use WithHotReloadLock(lock sync.Locker).

    Requirements:

    1. You must pass a pointer to the slice (not the slice itself) to the Find or FindMulti function.
    2. You must provide a sync.Locker (e.g., a *sync.RWMutex) to synchronize access to the slice.
    3. Crucial: You MUST NOT lock inside the itemFunc passed to Find or FindMulti, as the fuzzy finder will handle locking.
    4. If using WithPreviewWindow, you MUST use the provided lock only inside the previewFunc.
    import (
    	"sync"
    	"github.com/ktr0731/go-fuzzyfinder"
    )
    
    var ( 
    	items = []string{"a", "b"}
    	mu   = &sync.RWMutex{}
    )
    
    // When calling Find, pass the pointer to items and the lock
    // fuzzyfinder.Find(&items, fuzzyfinder.WithHotReloadLock(mu))
  8. Use FindMulti() for multi-item fuzzy searching

    master

    The FindMulti function displays a terminal user interface that allows users to select multiple items using the Tab key.

    • Arguments:

      • slice: An interface representing the slice to search through. It must be a slice type.
      • itemFunc: A function func(i int) string that returns the string representation of the item at index i.
      • opts: Optional Option functions to configure the finder.
    • Returns:

      • A slice of selected indices ([]int).
      • An error. If the user aborts without any selections, it returns ErrAbort.

    Usage: Users can navigate with arrow keys and toggle selections with Tab before pressing Enter to confirm.

    indices, err := fuzzyfinder.FindMulti(mySlice, func(i int) string {
    	return mySlice[i]
    })
    if err != nil {
    	if errors.Is(err, fuzzyfinder.ErrAbort) {
    		// User aborted
    	}
    	return err
    }
    // Use indices
  9. Enable a preview window with WithPreviewWindow

    master

    The WithPreviewWindow(f func(i, width, height int) string) option enables a preview pane for the currently selected item.

    • The function f receives the current item index i, the terminal width, and the terminal height (both as rune-based lengths).
    • If no item is selected, i is passed as -1.
    • If f is nil, the preview feature is disabled.

    If you are also using WithHotReloadLock, you MUST use the provided lock only within this previewFunc to ensure thread safety when accessing the underlying data.

    fuzzyfinder.WithPreviewWindow(func(i, width, height int) string {
    	if i < 0 {
    		return ""
    	}
    	return fmt.Sprintf("Previewing item %d", i)
    })
  10. Set initial cursor position with WithCursorPosition

    master

    Use WithCursorPosition(position cursorPosition) to define where the cursor starts.

    • CursorPositionTop: Starts at the top.
    • CursorPositionBottom: Starts at the bottom.

    Note: If WithPreselected is also used, the cursor will be positioned at the first preselected item instead of the absolute top/bottom.

    fuzzyfinder.WithCursorPosition(fuzzyfinder.CursorPositionBottom)
  11. Reference: Fuzzyfinder UI Options

    master

    The following functions are used to customize the UI and behavior of the fuzzy finder via the Option type.

    WithMode(m mode) Option             // Sets matching mode (Smart, CaseSensitive, CaseInsensitive)
    WithPreviewWindow(f func(i, width, height int) string) Option // Enables preview pane
    WithHotReloadLock(lock sync.Locker) Option // Enables automatic reloading of the slice
    WithCursorPosition(position cursorPosition) Option // Sets initial cursor position (Top/Bottom)
    WithPromptString(s string) Option    // Changes the prompt string (default: "> ")
    WithHeader(s string) Option          // Sets the header text
    WithContext(ctx context.Context) Option // Allows closing the finder from a parent context
    WithQuery(s string) Option          // Sets the initial search query
    WithSelectOne() Option               // Enables selecting only one item
    WithPreselected(f func(i int) bool) Option // Specifies which items are preselected