Help us shape the future of Go! Take the 2026 Go Developer Survey

finder

package module
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 27, 2026 License: MIT Imports: 17 Imported by: 0

README

rummage

A TUI file picker, file explorer, and fuzzy finder for Go! Browse, search, and select files or folders from the terminal like the ultimate superuser you are.

CI Go Reference Go Report Card codecov

A cross-platform, terminal-based (TUI) file and folder picker, explorer, and fuzzy finder for Go. Works consistently across Windows, macOS, Linux, BSD, WSL, and Git Bash with zero OS-specific dependencies.

path, err := finder.PickFile()   // one import, one line

Most Go terminal pickers are components you wire into your own Bubble Tea update/view loop. rummage is the batteries-included alternative: a single call that returns the selected path — with fuzzy search, multi-select, a preview pane, and interactive create/delete built in — yet it still embeds as a Bubble Tea sub-model when you want full control.

Why rummage?

rummage bubbles/filepicker huh file field go-fuzzyfinder
One-line standalone API ✅ ❌ ❌ ✅
Embeddable Bubble Tea sub-model ✅ ✅ ➖ form field ❌
Directory tree navigation ✅ ✅ ✅ ❌ list only
Fuzzy search + highlight ✅ ❌ ❌ ✅
Multi-select ✅ ❌ ❌ ✅
File-or-folder (ModeAny) ✅ ➖ ❌ ❌
Interactive create / delete ✅ ❌ ❌ ❌
Preview pane ✅ ❌ ❌ ✅
Custom io/fs.FS backend ✅ ❌ ❌ ➖

See docs/POSITIONING.md for the full landscape analysis.

See it in action

Preview pane — a file's head, a directory's listing, or metadata, beside the cursor:

rummage preview pane showing a README's contents beside the directory list

Multi-select across directories — toggle with space/tab; selections persist as you navigate in and out of folders:

rummage multi-select with two files checked and a '2 selected' counter

Interactive create / delete — make files and folders (or delete entries) without leaving the picker (n / N / d):

rummage interactive mode after creating a new file

All recordings are reproducible — run make demos to regenerate them (see docs/demo/).

Features

  • Four picker modes: single file, single folder, any (file or folder), and multi-select
  • Fuzzy search filtering: press / to filter entries in real time, ranked best-match-first with matched characters highlighted (opt out with WithFuzzySearch(false) for plain substring matching)
  • Interactive mode: create files/folders and delete entries without leaving the picker
  • Glob-based file filtering (*.go, *.txt, etc.)
  • Hidden file support: toggle at runtime, or force-show with distinct dim styling
  • Symlink expansion: optionally resolve symlinks to real paths
  • Pluggable filesystem: browse any io/fs.FS (e.g. embed.FS, fstest.MapFS) via WithFS — defaults to the host OS
  • Preview pane: optional side pane (WithPreview) showing a file's head, a directory's listing, or metadata; customizable via WithPreviewFunc
  • Standalone or embeddable: one-line PickFile() etc., or embed the picker as a Bubble Tea sub-model with New (reports completion via DoneMsg)
  • Smart truncation: long paths and filenames are truncated with … to fit the terminal
  • Fully customizable: override any keybinding or visual style
  • Vim-style navigation (h/j/k/l) plus standard arrow keys
  • WSL path conversion utilities (/mnt/c/... ↔ C:\...)
  • Alt-screen mode: restores terminal on exit
  • No cgo, no external dependencies beyond pure-Go TUI libraries

Install

go get github.com/rummage-dev/rummage@latest

Quick Start

package main

import (
    "fmt"
    finder "github.com/rummage-dev/rummage"
)

func main() {
    path, err := finder.PickFile(
        finder.WithStartDir("~/projects"),
        finder.WithFilter("*.go"),
    )
    if err != nil {
        panic(err)
    }
    fmt.Println("Selected:", path)
}
Pick a folder
dir, err := finder.PickFolder()
Pick a file or folder
path, err := finder.PickAny()
Multi-select
paths, err := finder.PickMultiple(
    finder.WithFilter("*.log", "*.txt"),
    finder.WithHidden(true),
)
Restrict to document types

WithExtensions limits the picker to a set of extensions, matched case-insensitively (so Report.PDF is included). Use it instead of WithFilter("*.pdf") when you want the common "only allow these file types" behavior without worrying about case:

path, err := finder.PickFile(
    finder.WithExtensions("pdf", "docx", "doc"),
)
Interactive mode (create/delete)
path, err := finder.PickFile(
    finder.WithInteractive(true),
)
Custom keybindings and styles
km := finder.DefaultKeyMap()
km.Cancel = key.NewBinding(key.WithKeys("x"), key.WithHelp("x", "exit"))

s := finder.DefaultStyles()
s.Directory = lipgloss.NewStyle().Foreground(lipgloss.Color("82")).Bold(true)

path, err := finder.PickFile(
    finder.WithKeyMap(km),
    finder.WithStyles(s),
)
Embed in your own Bubble Tea app

The Pick* functions run a standalone program. To embed the picker as a sub-model in an existing Bubble Tea program, use New, which returns a model in embedded mode: it never quits your program and reports completion via finder.DoneMsg.

type parent struct{ picker finder.Model }

func (m parent) Init() tea.Cmd { return m.picker.Init() }

func (m parent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    if done, ok := msg.(finder.DoneMsg); ok {
        if done.State == finder.StateSelected {
            // use done.Paths
        }
        return m, tea.Quit // or switch to another view
    }
    updated, cmd := m.picker.Update(msg)
    m.picker = updated.(finder.Model)
    return m, cmd
}

func (m parent) View() string { return m.picker.View() }

// finder.New(finder.WithMode(finder.ModeFile), finder.WithPreview(true))

See examples/embedded for a complete program.

Keybindings

Navigation
Key Action
up / k Move cursor up
down / j Move cursor down
pgup / ctrl+u Page up
pgdn / ctrl+d Page down
g / home Jump to top
G / end Jump to bottom
right / l Open directory
backspace / left / h / esc Go to parent directory
Selection
Key Action Modes
enter Select file / open directory All modes
space / tab Toggle selection / select item Multi-select, Any
ctrl+a Toggle all selections Multi-select
s Select current directory Folder, Any
Other
Key Action
/ Search / filter entries (live)
. Toggle hidden files
n New file (interactive mode)
N New folder (interactive mode)
d / delete Delete entry (interactive mode)
q / ctrl+c Cancel and exit
Search mode
Key Action
Type characters Filter entries live (fuzzy, ranked, matches highlighted)
backspace Remove last character (widens results)
enter Accept filtered results
esc Cancel search, restore full list
Interactive input (new file/folder)
Key Action
Type characters Enter name
enter Create the file or folder
esc Cancel

Typing a name ending with / when creating a file (e.g. mydir/) creates a directory instead.

Examples

# File picker (all flags)
go run ./examples/basic

# Folder picker
go run ./examples/folder

# Multi-select with filters
go run ./examples/multi

# Restrict to file extensions (case-insensitive, opt-in)
go run ./examples/extensions

# Interactive mode (create/delete)
go run ./examples/interactive

# Custom keybindings and styles
go run ./examples/custom

# Preview pane (built-in or custom)
go run ./examples/preview

# Embedded as a Bubble Tea sub-model
go run ./examples/embedded

The basic example supports flags for all options:

go run ./examples/basic -mode folder
go run ./examples/basic -mode multi -filter "*.go"
go run ./examples/basic -hidden
go run ./examples/basic -interactive
go run ./examples/basic -dir /tmp
go run ./examples/basic -expand -dir ~/symlink

Documentation

Requirements

  • Go 1.25+
  • A terminal with ANSI escape code support (virtually all modern terminals)

Acknowledgments

rummage stands on the shoulders of the excellent Charm ecosystem and a few other great libraries:

  • Bubble Tea — the Elm-style TUI framework rummage is built on.
  • Lip Gloss — all styling and layout.
  • Bubbles — its key package powers the overridable keybindings.
  • sahilm/fuzzy — scored fuzzy matching and match highlighting for search (the same matcher bubbles/list uses).
  • golang.org/x/term — terminal size and capability detection.

A huge thank-you to Charm for making terminal UIs in Go a joy.

License

MIT - see LICENSE for details.

Documentation

Overview

Package finder provides a cross-platform, terminal-based file and folder picker that works consistently across Windows, macOS, Linux, BSD, WSL, and Git Bash without any OS-specific dependencies.

The picker is built on the Bubble Tea TUI framework and uses no cgo, so it cross-compiles cleanly and runs in any environment where a terminal is available — including SSH sessions and Git Bash, where native OS file dialogs are not usable.

Quick Start

The four Pick functions are the most common entry points:

path, err := finder.PickFile(finder.WithStartDir("~/projects"))
dir, err  := finder.PickFolder()
any, err  := finder.PickAny()
paths, err := finder.PickMultiple(finder.WithFilter("*.go"))

Each returns ErrCancelled if the user exits the picker without making a selection. Configuration is applied via functional Option values.

Modes

The Mode constants control selection behavior:

  • ModeFile — select a single file
  • ModeFolder — select a single folder
  • ModeAny — select either a file or folder
  • ModeMultiple — select multiple files and/or folders across directories

Customization

Every keybinding and visual style is overridable through WithKeyMap and WithStyles. See DefaultKeyMap and DefaultStyles for the defaults.

Interactive Mode

Passing WithInteractive enables in-picker file management: press n to create a file, N to create a folder, and d to delete an entry. Typing a new file name ending in "/" creates a directory instead.

Utilities

The package also exposes path utilities that are useful outside the picker itself, including ToWindowsPath, ToWSLPath, IsWSL, ResolvePath, NormalizePath, ParentDir, and FormatSize.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrCancelled = errors.New("picker cancelled")

ErrCancelled is returned when the user cancels the picker without selecting.

Functions

func CreateDir

func CreateDir(dir, name string) error

CreateDir creates a new directory at the given path. Returns an error if the directory already exists, the name is invalid, or it cannot be created.

func CreateFile

func CreateFile(dir, name string) error

CreateFile creates an empty file at the given path. Returns an error if the file already exists, the name is invalid, or it cannot be created.

func DeletePath

func DeletePath(p string) error

DeletePath removes a file or directory (and all contents) at the given path. Refuses to delete filesystem roots or paths that resolve to /.

func FormatSize

func FormatSize(bytes int64) string

FormatSize returns a human-readable file size string.

Example
package main

import (
	"fmt"

	finder "github.com/rummage-dev/rummage"
)

func main() {
	fmt.Println(finder.FormatSize(0))
	fmt.Println(finder.FormatSize(1536))
	fmt.Println(finder.FormatSize(1048576))
	fmt.Println(finder.FormatSize(1073741824))
}
Output:
0 B
1.5 KB
1 MB
1 GB

func IsWSL

func IsWSL() bool

IsWSL returns true if running inside Windows Subsystem for Linux.

func NormalizePath

func NormalizePath(p string) string

NormalizePath converts a path to the OS-native format and resolves it to an absolute path.

func ParentDir

func ParentDir(dir string) string

ParentDir returns the parent directory of the given path.

Example

ParentDir returns the parent of a path, or the path itself if it has no parent (e.g. the filesystem root). The output uses the OS-native path separator, so on Windows /home/user becomes \home — for portable usage, pass paths through filepath.FromSlash first.

package main

import (
	"fmt"

	finder "github.com/rummage-dev/rummage"
)

func main() {
	parent := finder.ParentDir("/home/user/projects")
	fmt.Println(parent)
}

func PickAny

func PickAny(opts ...Option) (string, error)

PickAny opens an interactive picker that allows selecting either a file or a folder. Press enter to select the highlighted item, right/l to navigate into directories, or s to select the current directory. Returns ErrCancelled if the user exits without selecting.

func PickFile

func PickFile(opts ...Option) (string, error)

PickFile opens an interactive file picker and returns the selected file path. Returns ErrCancelled if the user exits without selecting.

Example

Pick a single Go source file from the user's projects directory.

package main

import (
	"fmt"

	finder "github.com/rummage-dev/rummage"
)

func main() {
	path, err := finder.PickFile(
		finder.WithStartDir("~/projects"),
		finder.WithFilter("*.go"),
	)
	if err != nil {
		// finder.ErrCancelled is returned if the user exits without selecting.
		return
	}
	fmt.Println("selected:", path)
}

func PickFolder

func PickFolder(opts ...Option) (string, error)

PickFolder opens an interactive folder picker and returns the selected directory path. Returns ErrCancelled if the user exits without selecting.

Example

Pick a folder, using the s key inside the picker to confirm the current directory.

package main

import (
	"fmt"

	finder "github.com/rummage-dev/rummage"
)

func main() {
	dir, err := finder.PickFolder()
	if err != nil {
		return
	}
	fmt.Println("folder:", dir)
}

func PickMultiple

func PickMultiple(opts ...Option) ([]string, error)

PickMultiple opens an interactive multi-select file picker and returns all selected paths. Returns ErrCancelled if the user exits without selecting.

Example

Pick multiple files matching a filter, with hidden files visible. Selections persist across directory navigation.

package main

import (
	"fmt"

	finder "github.com/rummage-dev/rummage"
)

func main() {
	paths, err := finder.PickMultiple(
		finder.WithFilter("*.log", "*.txt"),
		finder.WithHidden(true),
	)
	if err != nil {
		return
	}
	for _, p := range paths {
		fmt.Println(p)
	}
}

func ResolvePath

func ResolvePath(p string) string

ResolvePath evaluates symlinks and returns the real absolute path. If the path cannot be resolved, it falls back to filepath.Abs.

func ToWSLPath

func ToWSLPath(p string) string

ToWSLPath converts a Windows path like C:\Users to /mnt/c/Users. If the path is not a Windows-style path, it is returned unchanged.

Example
package main

import (
	"fmt"

	finder "github.com/rummage-dev/rummage"
)

func main() {
	fmt.Println(finder.ToWSLPath(`C:\Users\test`))
	fmt.Println(finder.ToWSLPath(`D:\data`))
	fmt.Println(finder.ToWSLPath("/home/user"))
}
Output:
/mnt/c/Users/test
/mnt/d/data
/home/user

func ToWindowsPath

func ToWindowsPath(p string) string

ToWindowsPath converts a WSL path like /mnt/c/Users to C:\Users. If the path is not a WSL mount path, it is returned unchanged.

Example
package main

import (
	"fmt"

	finder "github.com/rummage-dev/rummage"
)

func main() {
	fmt.Println(finder.ToWindowsPath("/mnt/c/Users/test"))
	fmt.Println(finder.ToWindowsPath("/mnt/d/data"))
	fmt.Println(finder.ToWindowsPath("/home/user"))
}
Output:
C:\Users\test
D:\data
/home/user

Types

type DoneMsg

type DoneMsg struct {
	State State
	Paths []string
}

DoneMsg is emitted (as a tea.Cmd result) when an embedded picker finishes, so a parent program can react in its own Update. It is not sent in the standalone Pick* API, which ends its own program instead.

type FileEntry

type FileEntry struct {
	Name     string
	Path     string
	IsDir    bool
	IsHidden bool
	Size     int64
	Mode     os.FileMode
}

FileEntry represents a single file or directory in a listing.

func ReadDir

func ReadDir(dir string, showHidden bool, filters []string) ([]FileEntry, error)

ReadDir reads the contents of a directory and returns a sorted list of FileEntry values. Directories are sorted before files, and each group is sorted alphabetically (case-insensitive).

type FileSystem

type FileSystem interface {
	// Open opens the named file for reading. This makes a FileSystem usable
	// anywhere an fs.FS is expected (e.g. the preview pane).
	Open(name string) (fs.File, error)
	// ReadDir returns the raw directory entries at the given path.
	ReadDir(name string) ([]fs.DirEntry, error)
	// Parent returns the parent directory of path. For the root it returns
	// path unchanged, which is how the picker detects the top level.
	Parent(path string) string
	// Base returns the final element of path.
	Base(path string) string
	// Join appends name to dir, producing a child path in this filesystem's
	// path space.
	Join(dir, name string) string
	// Display renders path for the breadcrumb shown to the user.
	Display(path string) string
	// Root returns the path the picker starts at when no start directory is
	// given.
	Root() string
}

FileSystem is the backend the picker reads from and navigates. It abstracts both directory access and path manipulation so the picker can operate on the OS filesystem (the default) or on any custom backend such as an embed.FS or a fstest.MapFS supplied via WithFS.

All path-shaped values the picker stores (the current directory and each FileEntry.Path) are produced by this interface, so the picker never assumes OS path semantics.

type KeyMap

type KeyMap struct {
	Up        key.Binding
	Down      key.Binding
	PageUp    key.Binding
	PageDown  key.Binding
	Home      key.Binding
	End       key.Binding
	Navigate  key.Binding
	Back      key.Binding
	Select    key.Binding
	SelectDir key.Binding
	Toggle    key.Binding
	ToggleAll key.Binding
	Hidden    key.Binding
	Cancel    key.Binding
	Search    key.Binding
	NewFile   key.Binding
	NewFolder key.Binding
	Delete    key.Binding
}

KeyMap defines all keybindings for the file picker.

func DefaultKeyMap

func DefaultKeyMap() KeyMap

DefaultKeyMap returns the default keybindings.

type Mode

type Mode int

Mode defines the picker operation mode.

const (
	// ModeFile allows selecting a single file.
	ModeFile Mode = iota
	// ModeFolder allows selecting a single folder.
	ModeFolder
	// ModeAny allows selecting a single file or folder.
	ModeAny
	// ModeMultiple allows selecting multiple files and/or folders.
	ModeMultiple
)

func (Mode) String

func (m Mode) String() string

String returns the human-readable name of the mode.

Example
package main

import (
	"fmt"

	finder "github.com/rummage-dev/rummage"
)

func main() {
	fmt.Println(finder.ModeFile)
	fmt.Println(finder.ModeFolder)
	fmt.Println(finder.ModeAny)
	fmt.Println(finder.ModeMultiple)
}
Output:
file
folder
any
multiple

type Model

type Model struct {
	// contains filtered or unexported fields
}

Model is the bubbletea model for the file picker.

func New

func New(opts ...Option) Model

New builds a picker Model for embedding in your own Bubble Tea program. The returned model is in embedded mode: it never calls tea.Quit and instead reports completion via a finder.DoneMsg (and its State method).

Wire it like any Bubble Tea sub-model: call Init and run the returned command, forward messages to Update, render with View, and watch for finder.DoneMsg (or poll State) to read the result with SelectedPath / SelectedPaths.

For a standalone picker that owns the terminal, use PickFile/PickFolder/ PickAny/PickMultiple instead.

Example

Embed the picker as a sub-model in your own Bubble Tea program. New returns a model in embedded mode: forward messages to its Update, render with View, and watch for finder.DoneMsg to read the result. The picker never quits your program itself.

package main

import (
	finder "github.com/rummage-dev/rummage"
)

func main() {
	picker := finder.New(
		finder.WithMode(finder.ModeFile),
		finder.WithTitle("Pick a file"),
	)

	// In your parent model's Update, forward messages and handle completion:
	//
	//	func (m parent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	//		if done, ok := msg.(finder.DoneMsg); ok {
	//			if done.State == finder.StateSelected {
	//				m.result = done.Paths
	//			}
	//			return m, nil
	//		}
	//		updated, cmd := m.picker.Update(msg)
	//		m.picker = updated.(finder.Model)
	//		return m, cmd
	//	}
	_ = picker.Init()
}

func NewModel

func NewModel(opts Options) Model

NewModel creates a new picker model with the given options.

func (Model) Done

func (m Model) Done() bool

Done reports whether the user has finished with the picker (either selected or cancelled).

func (Model) Err

func (m Model) Err() error

Err returns any error that occurred during picker operation.

func (Model) Init

func (m Model) Init() tea.Cmd

Init implements tea.Model.

func (Model) SelectedPath

func (m Model) SelectedPath() string

SelectedPath returns the single selected path after the picker closes. Returns empty string if nothing was selected.

func (Model) SelectedPaths

func (m Model) SelectedPaths() []string

SelectedPaths returns all selected paths (for multi-select mode). Paths are returned in sorted order for deterministic results.

func (Model) State

func (m Model) State() State

State returns the picker's lifecycle state. Embedded parents use it (or watch for DoneMsg) to detect when the user has finished.

func (Model) Update

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update implements tea.Model.

func (Model) View

func (m Model) View() string

View implements tea.Model. It renders the full picker UI.

type Option

type Option func(*Options)

Option is a functional option for configuring the picker.

func WithEmbedded

func WithEmbedded(enabled bool) Option

WithEmbedded marks the picker for embedding in a larger Bubble Tea program, so it signals completion via DoneMsg / State() instead of quitting. New sets this automatically; use it directly only with NewModel.

func WithExpandSymlinks(enabled bool) Option

WithExpandSymlinks resolves symlinks to their real paths. When enabled, the picker operates on resolved paths so navigating back from a symlinked directory goes to the real parent rather than the symlink's parent.

func WithExtensions

func WithExtensions(exts ...string) Option

WithExtensions limits visible files to the given extensions, matched case-insensitively. Extensions may be given with or without a leading dot ("pdf" or ".pdf"), and an optional leading glob star is tolerated ("*.pdf"), so a file named "Report.PDF" is shown by WithExtensions("pdf"). Directories are always shown regardless of extensions. Blank entries are ignored.

WithExtensions composes with WithFilter: when both are set, a file is shown if it matches either the glob patterns or the extensions.

Example

Restrict the picker to a set of document extensions. Matching is case-insensitive, so "Report.PDF" is shown by WithExtensions("pdf").

package main

import (
	"fmt"

	finder "github.com/rummage-dev/rummage"
)

func main() {
	path, err := finder.PickFile(
		finder.WithExtensions("pdf", "docx", "doc"),
	)
	if err != nil {
		return
	}
	fmt.Println("document:", path)
}

func WithFS

func WithFS(fsys fs.FS) Option

WithFS sets a custom filesystem backend to browse instead of the host OS. The picker navigates the given io/fs.FS in slash-separated path space, starting at its root ("."). Because io/fs is read-only, interactive create/delete is unavailable on a custom filesystem.

To start in a subdirectory, combine with WithStartDir using a relative, slash-separated path (e.g. WithStartDir("docs/api")).

Example

Browse an in-memory filesystem instead of the host OS. Any io/fs.FS works, including embed.FS and fstest.MapFS. Custom filesystems are read-only, so interactive create/delete is disabled for them.

package main

import (
	"fmt"
	"testing/fstest"

	finder "github.com/rummage-dev/rummage"
)

func main() {
	mem := fstest.MapFS{
		"docs/readme.md": {Data: []byte("# hello")},
		"main.go":        {Data: []byte("package main")},
	}
	path, err := finder.PickFile(finder.WithFS(mem))
	if err != nil {
		return
	}
	fmt.Println("selected:", path)
}

func WithFilter

func WithFilter(patterns ...string) Option

WithFilter adds glob patterns to limit which files are shown. Directories are always shown regardless of filters.

func WithFuzzySearch

func WithFuzzySearch(enabled bool) Option

WithFuzzySearch toggles scored fuzzy matching for the search filter. When enabled (the default), results are ranked best-match-first. When disabled, search falls back to a case-insensitive substring match that preserves the original directory ordering.

func WithHeight

func WithHeight(h int) Option

WithHeight sets the maximum visible rows for the file list. A value of 0 means auto-detect from terminal size.

func WithHidden

func WithHidden(show bool) Option

WithHidden controls whether hidden files and directories are shown.

func WithInteractive

func WithInteractive(enabled bool) Option

WithInteractive enables file management actions (create files/folders, delete).

func WithKeyMap

func WithKeyMap(km KeyMap) Option

WithKeyMap overrides the default keybindings. All fields of the KeyMap struct are public, so callers can customize any or all bindings.

Example

Rebind the cancel key from q to x.

package main

import (
	"github.com/charmbracelet/bubbles/key"

	finder "github.com/rummage-dev/rummage"
)

func main() {
	km := finder.DefaultKeyMap()
	km.Cancel = key.NewBinding(
		key.WithKeys("x"),
		key.WithHelp("x", "exit"),
	)
	_, _ = finder.PickFile(finder.WithKeyMap(km))
}

func WithMode

func WithMode(m Mode) Option

WithMode sets the picker mode (file, folder, or multi-select).

func WithPreview

func WithPreview(enabled bool) Option

WithPreview enables (or disables) the preview pane shown beside the file list. The pane is hidden automatically when the terminal is too narrow.

Example

Show a preview pane beside the file list. The pane previews the highlighted entry (file head, directory listing, or metadata) and is hidden automatically on narrow terminals.

package main

import (
	"fmt"

	finder "github.com/rummage-dev/rummage"
)

func main() {
	path, err := finder.PickFile(finder.WithPreview(true))
	if err != nil {
		return
	}
	fmt.Println("selected:", path)
}

func WithPreviewFunc

func WithPreviewFunc(fn PreviewFunc) Option

WithPreviewFunc sets a custom preview renderer and enables the preview pane. Pass nil to fall back to the built-in preview while keeping the pane enabled.

Example

Supply a custom preview renderer. The function receives the highlighted entry and the pane dimensions, and returns the text to display.

package main

import (
	"fmt"

	finder "github.com/rummage-dev/rummage"
)

func main() {
	_, _ = finder.PickFile(finder.WithPreviewFunc(
		func(e finder.FileEntry, width, height int) string {
			return fmt.Sprintf("%s\n%s", e.Name, finder.FormatSize(e.Size))
		},
	))
}

func WithStartDir

func WithStartDir(dir string) Option

WithStartDir sets the initial directory for the picker. For the default OS backend the path is resolved to an absolute path when the picker starts; for a custom filesystem (see WithFS) it is interpreted as a relative, slash-separated path within that filesystem.

func WithStyles

func WithStyles(s Styles) Option

WithStyles overrides the default visual styles.

Example

Override visual styles for directory entries.

package main

import (
	"github.com/charmbracelet/lipgloss"

	finder "github.com/rummage-dev/rummage"
)

func main() {
	s := finder.DefaultStyles()
	s.Directory = lipgloss.NewStyle().
		Foreground(lipgloss.Color("82")).
		Bold(true)
	_, _ = finder.PickFile(finder.WithStyles(s))
}

func WithTitle

func WithTitle(title string) Option

WithTitle sets the header text displayed at the top of the picker.

type Options

type Options struct {
	// StartDir is the initial directory shown in the picker.
	StartDir string
	// Filters is a list of glob patterns to filter visible files (e.g. "*.go", "*.txt").
	// Matching is case-sensitive (see filepath.Match). Empty means show all files.
	Filters []string
	// Extensions limits visible files to the given extensions, matched
	// case-insensitively (e.g. ".pdf", ".docx"). Values are normalized to a
	// lowercase leading-dot form. Empty means no extension restriction. When
	// both Filters and Extensions are set, a file is shown if it matches
	// either one.
	Extensions []string
	// ShowHidden controls whether dotfiles/dotfolders are displayed.
	ShowHidden bool
	// Mode determines the picker behavior (file, folder, or multi-select).
	Mode Mode
	// Title is the header text displayed at the top of the picker.
	Title string
	// Height constrains the maximum number of rows in the file list.
	// Zero means auto-detect from terminal size.
	Height int
	// Interactive enables file management actions (create, delete).
	Interactive bool
	// ExpandSymlinks resolves symlinks to their real paths. When enabled,
	// navigating back from a symlinked directory goes to the real parent
	// rather than the symlink's parent.
	ExpandSymlinks bool
	// FuzzySearch enables scored fuzzy matching for the search filter,
	// ranking results best-match-first. When false, search uses a plain
	// case-insensitive substring match that preserves the original order.
	FuzzySearch bool
	// Embedded marks the picker as a sub-model of a larger Bubble Tea program.
	// When set, the picker never emits tea.Quit; instead it signals completion
	// via DoneMsg and its State() method, leaving control of the program to the
	// parent. The Pick* one-liner API leaves this false. See New.
	Embedded bool
	// Preview enables a right-hand pane that previews the highlighted entry
	// (file head, directory listing, or metadata). Off by default.
	Preview bool
	// PreviewFunc customizes how the highlighted entry is previewed. When nil,
	// a built-in preview is used. It receives the entry and the pane's
	// width/height (in cells) and returns the preview text; the result is
	// clipped to fit the pane.
	PreviewFunc PreviewFunc
	// FS is the filesystem backend the picker reads from. Nil means the
	// host operating system. Set it via WithFS to browse a custom io/fs.FS
	// (e.g. an embed.FS or fstest.MapFS); such filesystems are read-only,
	// so interactive create/delete is disabled for them.
	FS FileSystem
	// KeyMap overrides the default keybindings. Nil means use defaults.
	KeyMap *KeyMap
	// Styles overrides the default visual styles. Nil means use defaults.
	Styles *Styles
}

Options holds the configuration for a picker session.

func DefaultOptions

func DefaultOptions() Options

DefaultOptions returns a new Options struct with sensible defaults.

type PreviewFunc

type PreviewFunc func(e FileEntry, width, height int) string

PreviewFunc renders a preview of the highlighted entry. It is given the entry and the preview pane's width and height in terminal cells, and returns the preview text (which may span multiple lines). The returned text is clipped to the pane's dimensions.

type State

type State int

State describes where the picker is in its lifecycle. It lets a parent program that embeds the picker (see New) detect when the user has finished.

const (
	// StateBrowsing means the user is still navigating and has not finished.
	StateBrowsing State = iota
	// StateSelected means the user confirmed a selection; read it with
	// SelectedPath or SelectedPaths.
	StateSelected
	// StateCancelled means the user dismissed the picker without selecting.
	StateCancelled
)

type Styles

type Styles struct {
	Title         lipgloss.Style
	Path          lipgloss.Style
	Cursor        lipgloss.Style
	Selected      lipgloss.Style
	Directory     lipgloss.Style
	HiddenDir     lipgloss.Style
	File          lipgloss.Style
	HiddenFile    lipgloss.Style
	FileSize      lipgloss.Style
	Permission    lipgloss.Style
	StatusBar     lipgloss.Style
	SearchPrompt  lipgloss.Style
	SearchText    lipgloss.Style
	Help          lipgloss.Style
	HelpKey       lipgloss.Style
	HelpDesc      lipgloss.Style
	HelpSep       lipgloss.Style
	Border        lipgloss.Style
	Preview       lipgloss.Style
	PreviewBorder lipgloss.Style
	Match         lipgloss.Style
}

Styles holds all visual styles for the picker UI.

func DefaultStyles

func DefaultStyles() Styles

DefaultStyles returns the default color scheme and styling.

type WritableFileSystem

type WritableFileSystem interface {
	FileSystem
	CreateFile(dir, name string) error
	CreateDir(dir, name string) error
	Remove(path string) error
}

WritableFileSystem is the optional extension a FileSystem implements to support interactive mode (creating and deleting entries). A FileSystem that does not implement it is treated as read-only: the create/delete keys show a status message instead of acting.

Directories

Path Synopsis
examples
basic command
Basic example demonstrates all rummage picker modes with flag-based configuration.
Basic example demonstrates all rummage picker modes with flag-based configuration.
custom command
Custom example shows how to override keybindings and styles.
Custom example shows how to override keybindings and styles.
embedded command
Embedded example shows how to embed the picker as a sub-model inside your own Bubble Tea program, rather than using the standalone Pick* API.
Embedded example shows how to embed the picker as a sub-model inside your own Bubble Tea program, rather than using the standalone Pick* API.
extensions command
Extensions example shows how to restrict the picker to a set of file extensions, the way many "attach a document" dialogs only allow .pdf, .docx, .doc, and so on.
Extensions example shows how to restrict the picker to a set of file extensions, the way many "attach a document" dialogs only allow .pdf, .docx, .doc, and so on.
folder command
Folder example shows how to use rummage as a directory picker.
Folder example shows how to use rummage as a directory picker.
interactive command
Interactive example shows how to use rummage with file management actions enabled (create files/folders, delete entries).
Interactive example shows how to use rummage with file management actions enabled (create files/folders, delete entries).
multi command
Multi example shows how to use rummage for multi-file selection.
Multi example shows how to use rummage for multi-file selection.
preview command
Preview example shows the optional preview pane, which previews the highlighted entry (a file's head, a directory's listing, or metadata) beside the file list.
Preview example shows the optional preview pane, which previews the highlighted entry (a file's head, a directory's listing, or metadata) beside the file list.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL