fsbrowser

package
v0.0.21 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package fsbrowser renders any io/fs.FS as a file browser: one directory at a time as a sortable list, or the tree below the current directory as an outline, with a breadcrumb above, a quick filter, a selection, a keyboard cursor, and host-supplied columns beside name, size and modification time (ADR-0200 §SD2).

What the host owns

The host owns the State — current directory, filter, sort, selection, cursor, the directory cache and the outline's expansion — and the io/fs.FS the browser reads. The widget reads directories on demand through io/fs.ReadDir and caches each listing under the host's Input.CacheKey; a host whose tree cannot change under it (a lading snapshot) keeps the key fixed and never pays a read twice, a host over a live tree calls State.Invalidate when it wants a re-read. A change of CacheKey drops the cache and the selection and keeps the directory, so two snapshots of one mount are browsed at the same path — synchronized browsing is the default the location model gives for free.

What the widget decides, and what it reports

A click selects (ctrl toggles, shift extends from the cursor), a double click or Enter *activates*: a directory is entered (list mode) or toggled (outline mode), a file is reported in Result.Activated for the host to preview or open. Backspace goes up a directory; the arrows, Home, End, Page Up and Page Down move the cursor and the selection with it. Nothing here writes: the browser has no notion of copy, move, rename or delete, which is what makes it safe over a read-only store and over a capability grant.

Modes

ModeList shows the current directory's children in an etable — directories first, then files — and is the mode a file-transfer client opens in. ModeOutline shows the tree under the current directory through widgets/tree (ADR-0176): children load when a node is opened and never before, an unread directory shows a disclosure control because it might have children, and an empty one turns into a leaf once read. Both modes share the State, the columns and the cursor.

Index

Constants

View Source
const (
	// MaxColumnWidth bounds a drag and a stored width.
	MaxColumnWidth float32 = 1200
)

Column-width persistence (ADR-0151). The widget keys its columns by what they show — name, size, modified, then the host's — with the view appended to the type, so a width fitted to the list does not reach the outline, whose name column carries the indent and the disclosure control.

Variables

View Source
var PackageProps = packageprops.Props{
	WASMWASI:         packageprops.WASMBlocked,
	WASMJS:           packageprops.WASMBlocked,
	WASMFreestanding: packageprops.WASMBlocked,
}

PackageProps records this package's curated properties (ADR-0080). Seeded by `boxer code analysis golang wasmsurvey props generate`; curate by hand. The same group's `props verify` reconciles it.

Functions

func MinColumnWidth

func MinColumnWidth(density styletokens.DensityE) float32

MinColumnWidth is the drag floor for the density: content plus both cell insets. A host building a colwidth.Resolver for this widget passes the same number as Opts.MinPoints, so a column cannot be dragged below what will come back on the next load.

Types

type Column

type Column struct {
	Header    string
	Width     float32
	Resizable bool
	// Cell draws the cell for e. Emit labels Selectable(false): the row's
	// click sense sits behind its cells, and a selectable label would take
	// the click.
	Cell func(e Entry)
	// WidthType discriminates this column for width persistence (ADR-0151
	// keys the column tier on name and render type); empty means "host".
	WidthType string
}

Column is one host-supplied column beside the built-in three.

type Entry

type Entry struct {
	// Name is the base name; Path the io/fs path from the root ("a/b/c").
	Name string
	Path string
	// IsDir and IsSymlink come off the entry's type bits — a symlink is
	// reported as recorded, not resolved, which is the Lstat reading an
	// [fs.ReadDirFS] gives.
	IsDir     bool
	IsSymlink bool
	Size      int64
	ModTime   time.Time
	Mode      fs.FileMode
	// InfoErr is the error [fs.DirEntry.Info] returned, when it did; the entry
	// still lists, with zero size and time, because a name that cannot be
	// stat'ed is still a name.
	InfoErr error
	// Ord is the entry's ordinal in its directory's listing, stable for as
	// long as the listing is cached. Widget ids key on it, so sorting and
	// filtering move rows without moving identities.
	Ord int
}

Entry is one directory child as the browser shows it, read from the fs.DirEntry and its fs.FileInfo once per listing.

type Input

type Input struct {
	Ids      *c.WidgetIdStack
	ScopeKey string
	// FS is the tree to browse. nil renders a placeholder.
	FS fs.FS
	// RootLabel names the root in the breadcrumb ("/" when empty).
	RootLabel string
	// CacheKey scopes the directory cache; a change drops it and the
	// selection and keeps the directory.
	CacheKey string
	State    *State
	Mode     ModeE
	Columns  []Column
	// ShowHidden includes dot-names.
	ShowHidden bool
	// RowHeight, MaxHeight and Striped are passed to the table; zero means
	// the defaults (one text line; fill; plain).
	RowHeight float32
	MaxHeight float32
	Striped   bool
	// HideBreadcrumb and HideFilter drop those rows when the host draws its
	// own location and filter chrome.
	HideBreadcrumb bool
	HideFilter     bool
	// Widths persists the columns' widths (ADR-0151): the widget resolves
	// them through the resolver before the table is emitted and reports the
	// table's settled widths back after, under WidthTag (ScopeKey when
	// empty). The host flushes the resolver once per frame. nil keeps the
	// defaults and persists nothing.
	Widths   *colwidth.Resolver
	WidthTag string
}

Input is one frame's rendering request.

type ModeE

type ModeE uint8

ModeE selects how the current directory is shown.

const (
	// ModeList is one directory as a sortable table, directories first.
	ModeList ModeE = iota
	// ModeOutline is the tree under the current directory, loaded on expand.
	ModeOutline
)

type Result

type Result struct {
	// Rows is what was shown: the filtered, sorted listing in list mode, one
	// entry per outline node in outline mode. Indices below refer to it. The
	// slice is the State's scratch and is valid until the next Render; a host
	// that keeps entries copies them.
	Rows []Entry
	// Clicked is the row clicked this frame, -1 for none.
	Clicked int
	// Activated is the file row double-clicked or Enter-ed, -1 for none. A
	// directory activation is consumed by the widget (entered or toggled)
	// and reported as Navigated instead.
	Activated int
	// Navigated is true when the current directory changed this frame.
	Navigated bool
	// SelectionChanged is true when the selection or cursor moved.
	SelectionChanged bool
	Err              error
}

Result is what one Render reports.

func Render

func Render(in Input) (res Result)

Render draws the browser for one frame and reports what happened.

type SortByE

type SortByE uint8

SortByE is the column a listing is ordered by. Directories always sort before files whatever the column; the column orders within each group.

const (
	SortByName SortByE = iota
	SortBySize
	SortByModTime
)

type State

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

State is the host-owned view state. Its zero value is a browser at the root with no filter, sorted by name, nothing selected.

func (*State) ClearSelection

func (st *State) ClearSelection()

func (*State) Cursor

func (st *State) Cursor() string

Cursor is the keyboard cursor's path, "" for none.

func (*State) Dir

func (st *State) Dir() string

Dir is the current directory as an io/fs path; "." is the root.

func (*State) Filter

func (st *State) Filter() string

Filter is the quick filter text; SetFilter replaces it.

func (*State) Invalidate

func (st *State) Invalidate()

Invalidate drops every cached listing, so the next frame re-reads what it shows. The selection and the directory stay.

func (*State) IsSelected

func (st *State) IsSelected(p string) bool

IsSelected reports whether p is selected.

func (*State) Select

func (st *State) Select(p string, on bool)

Select sets p's selection; SelectOnly makes p the whole selection and the cursor; ClearSelection empties it.

func (*State) SelectOnly

func (st *State) SelectOnly(p string)

func (*State) Selection

func (st *State) Selection() (paths []string)

Selection is the selected paths, sorted.

func (*State) SetCursor

func (st *State) SetCursor(p string)

func (*State) SetDir

func (st *State) SetDir(dir string)

SetDir changes the current directory and clears the selection and the cursor — selection is per directory, as in every file manager. The path is cleaned; "" and "/" mean the root. It is not checked against the tree: a directory that does not exist lists as an error row.

func (*State) SetFilter

func (st *State) SetFilter(s string)

func (*State) SetSort

func (st *State) SetSort(by SortByE, desc bool)

func (*State) Sort

func (st *State) Sort() (by SortByE, desc bool)

Sort is the current order; SetSort replaces it.

func (*State) Up

func (st *State) Up() bool

Up moves to the parent directory; false at the root.

Jump to

Keyboard shortcuts

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