Documentation
¶
Index ¶
- Constants
- func ApplyNode(dir string, n Node) (field string, descended bool)
- func AscendPath(field string) string
- func ClampWindow(n, cursor, scroll int, contains func(start, cursor int) bool) (newCursor, newScroll int)
- func CommonPrefix(cands []Candidate) string
- func DisplayDir(dir string) string
- func JumpSection(n, cursor, delta int, groupAt func(i int) string) int
- func Relevant(r Result, queryLen int) bool
- func Snap(n, idx, dir int, selectable func(i int) bool) int
- func SplitPath(field string) (dir, frag string)
- func Update(m Model, ev Event) (Model, []Effect)
- func WindowContainsCursor(n, start, cursor, budget int, groupAt func(i int) (group string, ok bool)) bool
- type CancelListEffect
- type Candidate
- type CloseEffect
- type Closer
- type Crumb
- type Effect
- type Event
- type FileSource
- type Fuzzy
- type Index
- func (ix *Index) Breadcrumb(field string) (segs []Crumb, leaf string, kind LeafKind)
- func (ix *Index) Candidates(dir, frag string) []Candidate
- func (ix *Index) CaseCanonical(field string) string
- func (ix *Index) ChildCount(dir, name string) int
- func (ix *Index) Children(folder string) []Node
- func (ix *Index) Classify(field string) LeafKind
- func (ix *Index) HasEntry(path string) bool
- func (ix *Index) IsFolder(path string) bool
- type Indexer
- type KeyEvent
- type LeafKind
- type ListDirEffect
- type ListLoadedEvent
- type Listing
- type LoadState
- type Matcher
- type Model
- func (m Model) Budget() int
- func (m Model) Canceled() bool
- func (m Model) Cursor() int
- func (m Model) Cwd() string
- func (m Model) Done() bool
- func (m Model) Filtered() []int
- func (m Model) FocusedRow() (Row, bool)
- func (m Model) Gen() uint64
- func (m Model) GroupAt(i int) (string, bool)
- func (m Model) Height() int
- func (m Model) Load(dir string) (Model, []Effect)
- func (m Model) Loading() bool
- func (m Model) MatchTextOf(r Row) string
- func (m Model) Notice() string
- func (m Model) Outcome() (Outcome, bool)
- func (m Model) PrevRows() []Row
- func (m Model) Query() string
- func (m Model) Rows() []Row
- func (m Model) Scroll() int
- func (m Model) State() LoadState
- func (m Model) Width() int
- type Nav
- type NavIntent
- type Node
- type Options
- type Outcome
- type ResizeEvent
- type Result
- type Row
- type Substring
- type SyncLister
- type Validator
Constants ¶
const MinScorePerRune = 12
MinScorePerRune is the average score a match must reach per query rune to be considered relevant (as opposed to merely a valid subsequence). It filters scattered subsequence matches while keeping contiguous and word-boundary ones. Measured boundary/consecutive matches score ~17-26 per rune; scattered ones ~7-9. 12 sits cleanly between, with margin on both sides.
Variables ¶
This section is empty.
Functions ¶
func ApplyNode ¶
ApplyNode fills dir+node into a path field: a folder becomes "dir/name/" (and descends); an entry becomes "dir/name". A name that is both descends, since the user can always backspace to target the entry.
func AscendPath ¶
AscendPath drops the trailing path segment, for shift+tab: "pp/alter-ego/gm" -> "pp/alter-ego/" -> "pp/" -> "". A trailing slash is removed first.
func ClampWindow ¶
func ClampWindow(n, cursor, scroll int, contains func(start, cursor int) bool) (newCursor, newScroll int)
ClampWindow clamps cursor and scroll into [0,n) and advances scroll until the cursor is visible, returning the adjusted (cursor, scroll). contains(start, cursor) reports visibility — typically a WindowContainsCursor closure that captures the current budget. With n == 0 it resets both to 0.
func CommonPrefix ¶
CommonPrefix is the longest common prefix of the matching candidate names, compared case-insensitively but emitted in the first match's canonical case — true shell first-TAB behavior.
func DisplayDir ¶
DisplayDir names a dir (with trailing slash, as SplitPath returns) for a notice; the root reads as "the store root".
func JumpSection ¶
JumpSection returns the cursor position after jumping to the adjacent group boundary in direction delta (negative = previous group, positive = next), mirroring each screen's section jump. groupAt returns the group label of filtered row i ("" when out of range). The caller re-clamps the viewport afterward. With no movement possible it returns cursor unchanged.
func Relevant ¶
Relevant reports whether a match clears the relevance threshold for a query of the given rune length. An empty query is always relevant.
func Snap ¶
Snap returns a selectable filtered index for the cursor: it searches from idx in the travel direction first, then falls back the other way; -1 if no row is selectable. Searching the travel direction fully keeps the cursor moving past a run of non-selectable (reference) rows toward the next selectable one instead of snapping back. selectable(i) reports whether filtered row i may hold the cursor (generalized from passage's dirbrowse reference-row skip).
func SplitPath ¶
SplitPath splits a path field into (dir, frag): dir is the text up to and including the last "/" ("" when there is none); frag is the active segment after it. "pp/al" -> ("pp/","al"); "pp" -> ("","pp"); "pp/" -> ("pp/","").
func Update ¶
Update advances the state machine. It returns the new model and any Effects the adapter must run (list a directory, cancel an in-flight list, close the source). Render then reads the model; the adapter quits when Done reports true.
func WindowContainsCursor ¶
func WindowContainsCursor(n, start, cursor, budget int, groupAt func(i int) (group string, ok bool)) bool
WindowContainsCursor reports whether a viewport starting at filtered row `start` can show the row at `cursor` within `budget` body lines. groupAt returns the group label of filtered row i and ok=false for rows that should be skipped entirely (stale indices), mirroring each screen's bounds guard.
Types ¶
type CancelListEffect ¶
type CancelListEffect struct{ Gen uint64 }
CancelListEffect asks the adapter to cancel the in-flight listing for an older generation (cancel its context, killing a slow sftp subprocess), so a fast host never blocks navigation away.
type Candidate ¶
Candidate is one child of the current folder, tagged with whether it prefix-matches the active fragment (case-insensitively) and, if so, the rune positions to highlight in its name.
func MatchingCandidates ¶
MatchingCandidates is the subset of Candidates that prefix-match — the set TAB acts on.
type CloseEffect ¶
type CloseEffect struct{}
CloseEffect asks the adapter to Close a stateful FileSource (SFTP session teardown) when the browse ends.
type Closer ¶
type Closer interface {
Close() error
}
Closer is an optional capability (detected by type assertion): a stateful source — e.g. an SFTP session multiplexing over one authed socket — tears down here. Sources without session state simply omit it.
type Crumb ¶
Crumb is one committed folder segment of the path for a read-only breadcrumb line, with a case-insensitive existence flag.
type Effect ¶
type Effect interface {
// contains filtered or unexported methods
}
Effect is a side effect the reducer asks the adapter to perform. The core produces these as plain values and never executes them — the adapter runs each inside a tea.Cmd (the only place IO happens), then feeds results back as Events. The sealed isEffect marker keeps the set closed.
type Event ¶
type Event interface {
// contains filtered or unexported methods
}
Event is an input to the reducer. The teax adapter translates each tea.Msg into one of these; the pure core never sees a bubbletea type. The sealed isEvent marker keeps the set closed so Update's type switch is exhaustive.
type FileSource ¶
type FileSource interface {
// Resolve normalizes the opening path once (~ -> abs, Clean, or the
// namespace root) and returns the canonical starting directory.
Resolve(ctx context.Context, path string) (start string, err error)
// List returns ONE directory's children. It is the only IO method.
List(ctx context.Context, dir string) (Listing, error)
}
FileSource is THE injected divergence seam — the direct analogue of termtheme's ThemeConfig.Resolve(base): just as termtheme ships no palettes and the host app supplies the base, termnav ships no transport and the host app supplies the lister. "List one directory" granularity fits a real-FS level, one SFTP round-trip, and one node of an in-memory tree alike, giving every transport a uniform async Loading/Error/Empty contract. The pure core NEVER calls List — only the teax adapter does, inside a tea.Cmd — so the kernel imports neither os nor net and stays PTY-free testable.
type Fuzzy ¶
type Fuzzy struct{ MinScore int }
Fuzzy is the default Matcher: fzf-style subsequence scoring gated by a per-rune relevance floor (the exact behavior both apps' list filters used, MatchFuzzy + Relevant folded into one call). A zero MinScore means "use the package default", so the zero value Fuzzy{} is the canonical filter.
type Index ¶
type Index struct {
// contains filtered or unexported fields
}
Index is the precomputed snapshot. Construct it with BuildIndex.
func BuildIndex ¶
BuildIndex derives the implicit folder tree from a snapshot of entry paths. It is pure and total.
func (*Index) Breadcrumb ¶
Breadcrumb returns the committed folder segments (everything before the last "/") with existence flags, plus the active leaf and its kind — so render can color exists/missing/new without splicing into the cursored field.
func (*Index) Candidates ¶
Candidates returns the children of dir, matches first (preserving folder-first/name order within each group). Matching is case-insensitive prefix — the same lockstep relationship TAB uses — so everything listed as a match is TAB-completable.
func (*Index) CaseCanonical ¶
CaseCanonical returns the canonical (stored) spelling of an entry that the field collides with only by case, for the notice message.
func (*Index) ChildCount ¶
ChildCount is the number of immediate children under the child named `name` of `dir` (with trailing slash), used to show a folder's item count.
func (*Index) Children ¶
Children returns the immediate children of the folder at `folder` (no trailing slash; "" is the root), folders-first then by name. The returned slice must not be mutated.
func (*Index) Classify ¶
Classify is the single source of truth for whether enter may proceed and for the leaf's valence. Precedence: exact entry > exact folder > case collision > new.
type Indexer ¶
type Indexer interface {
Index() *Index
}
Indexer is an optional capability exposing a navigation Index for breadcrumb and shell-TAB completion. A TreeSource exposes its whole index; a real-FS or SFTP source may expose a growing index merged as directories are visited.
type KeyEvent ¶
KeyEvent is a normalized keypress. Key is a SEMANTIC action the per-app KeyMap resolved to — "up", "down", "pgup", "pgdown", "home", "end", "section-up", "section-down", "enter", "backspace", "cancel" — or "" when the press is pure text input, in which case Text holds the printable runes to append to the filter. Separating semantics (here) from raw keys (the KeyMap) is what lets ssherpa's Q=cancel and passage's ctrl+q=cancel coexist without a core change.
type LeafKind ¶
type LeafKind int
LeafKind classifies a typed path for enter-validation and breadcrumb valence.
const ( LeafEmpty LeafKind = iota // nothing typed LeafTrailingSlash // ends with "/", no name yet LeafEmptySegment // "a//b" or "/a" LeafIsFolder // exact existing folder, no trailing name LeafExistingEntry // exact existing entry (overwrite) LeafCaseCollision // matches an existing entry only by case LeafNew // a fresh entry to create )
type ListDirEffect ¶
ListDirEffect asks the adapter to run FileSource.List(Dir) and report the result as a ListLoadedEvent tagged with Gen. Sync hints that a SyncLister may serve it inline (no Loading flash) — the adapter decides.
type ListLoadedEvent ¶
ListLoadedEvent delivers the result of a FileSource.List the adapter ran for generation Gen. A Gen that no longer matches the model is dropped as stale, so a rapid sequence of cd's always settles on the last one. Err non-nil puts the model in the Error state with the message as its notice.
type Listing ¶
Listing is one directory's contents as returned by a FileSource. It is the only thing a transport must produce: the canonical resolved directory, its parent (for the ".." row; "" or == Dir means "at the root, suppress .."), the child rows, and an optional inline, recoverable notice (permission denied, partial listing) that is surfaced without aborting navigation.
type LoadState ¶
type LoadState uint8
LoadState is the async lifecycle of the current directory, driving uniform spinner / error / empty feedback for every transport (instant local read, blocking SFTP round-trip, zero-IO in-memory tree).
type Matcher ¶
Matcher is the pluggable filter strategy a browser uses to decide which rows a query keeps and how their matched runes highlight. A nil Matcher in Options defaults to Fuzzy{MinScorePerRune}. Match returns ok=false to reject a row.
type Model ¶
type Model struct {
// contains filtered or unexported fields
}
Model is the pure browse state. It is a value type — no tea, no IO. Render reads it through the accessors at the bottom of this file.
func New ¶
New returns an Idle model. Call Load (typically from the adapter's Init, after FileSource.Resolve) to request the first directory.
func (Model) Budget ¶
Budget is the list viewport body-line budget: height minus the app's reserved chrome rows, floored at MinRows.
func (Model) Filtered ¶
Filtered returns the indices into Rows that pass the current query, in order.
func (Model) FocusedRow ¶
FocusedRow returns the row under the cursor, ok=false when the list is empty.
func (Model) Gen ¶
Gen is the current navigation generation. The adapter tags each List with it so a stale result (from a superseded directory) is dropped.
func (Model) GroupAt ¶
GroupAt returns the group label of filtered row i and ok=false for an out-of-range index — the exact callback shape the windowing primitives and an app's group-header render loop both consume.
func (Model) Load ¶
Load starts navigating to dir: it bumps the generation, enters Loading (retaining the prior rows for a dimmed frame), and asks the adapter to cancel any in-flight listing and fetch the new one. cwd is updated when the listing actually arrives (it is canonicalized by the source).
func (Model) MatchTextOf ¶
MatchTextOf exposes the configured match string for a row (for a render that wants to recompute highlight positions). Falls back to Row.Title.
func (Model) Notice ¶
Notice is the current inline message (error / permission / blocked commit), or "".
func (Model) Outcome ¶
Outcome returns the committed selection, ok=true once a leaf or container has been chosen. ok=false while still navigating.
func (Model) PrevRows ¶
PrevRows returns the prior directory's rows, retained during Loading so a render can dim the last frame instead of blanking. nil once a listing lands.
func (Model) Rows ¶
Rows returns the assembled rows of the current directory (including the synthetic use/.. rows the source injected). Render must not mutate it.
type Nav ¶
type Nav struct {
}
Nav describes the synthetic navigation rows a source injects ahead of a directory's real children: an optional "Use this folder" row (to commit the container itself) and the ".." parent row (suppressed at the root). It is the small structural piece every transport shares; cosmetic fields stay app-supplied so grouping/badges match each app's existing frames.
type NavIntent ¶
type NavIntent uint8
NavIntent is the canonical, app-independent meaning of a row. Every app's private Kind vocabulary folds into this 5-value superset; render and behavior switch on Intent ONLY, never on Kind. An unknown future intent tolerates and renders as a plain row, exactly as termtheme tolerates an unknown role.
const ( IntentDescend NavIntent = iota // open a child container (a directory) IntentAscend // go to the parent ("..") IntentUseContainer // commit the current folder (synthetic "Use this folder") IntentSelectLeaf // commit a leaf (a file / namespace entry) IntentReference // shown, dimmed, NOT selectable; the cursor skips it )
type Node ¶
Node is one immediate child of a folder in the implicit tree. A name can be both a folder and an entry at once (an "a/b" entry alongside an "a/b/c" entry), so the two flags are independent.
type Options ¶
type Options struct {
// Matcher filters rows against the query. Default Fuzzy{} (fzf scoring with
// the per-rune relevance gate) — the behavior both apps' list filters used.
Matcher Matcher
// MatchText returns the string a row is filtered against. Default Row.Title;
// ssherpa joins Title/Description/Detail/Token/Group/Badge.
MatchText func(Row) string
// Validate gates a commit (Use/Leaf). nil means always commit.
Validate Validator
// ReserveRows is the chrome line count subtracted from height to get the
// list viewport budget (title/steps/footer/meta). The app sets it to match
// its frame so the scroll window stays byte-identical.
ReserveRows int
// MinRows floors the viewport budget. Default 1 (passage); ssherpa uses 4.
MinRows int
// KeepCursorOnFilter keeps the cursor index across a filter change (clamped),
// matching ssherpa; the default snaps the cursor to the first selectable row,
// matching passage.
KeepCursorOnFilter bool
// Start is the initial path passed to FileSource.Resolve.
Start string
}
Options configures a browse. The zero value is a usable single-pane fuzzy browser; every field has a sane default.
type Outcome ¶
Outcome is the terminal result of a browse: the committed row(s) and the intent that committed them, so the caller learns whether a leaf or a container was chosen without switching on Kind strings. Rows holds exactly one element for single-select; the slice shape is forward-compat for an opt-in MultiSelect.
type Result ¶
Result is a successful match: its score and the ascending matched rune indices in the candidate. The field name Score (not Value) is kept so the per-app fuzzy shims can alias this type without touching call sites.
func MatchFuzzy ¶
MatchFuzzy reports whether query matches candidate as an order-preserving subsequence and, if so, the score and matched rune positions. Case handling is smart-case: an all-lowercase query matches case-insensitively; any uppercase rune in the query makes the whole match case-sensitive. An empty query matches everything with score 0 and no positions.
type Row ¶
type Row struct {
Token string // navigable / return value: an absolute path or a pass path
Title string // display text
Description string // secondary text (size, mtime, full path)
Detail string // tertiary text / canonical path for preview
Group string // grouping label ("Directories"/"Files"/...); "" disables grouping
Badge string // short tag ("dir","file","up","use"); cosmetic only
Intent NavIntent
Selectable bool // may the cursor rest on and Enter commit this row
IsContainer bool // folder-ness, independent of Intent (a dual node is folder AND entry)
Positions []int // match-highlight rune indices into Title
IsSymlink bool
LinkTarget string
// Kind preserves the app's private literal (passage "dir"/"file"/...,
// ssherpa "file_dir"/"remote_file"/...) so a value round-trips losslessly
// through the core. Theme/behavior NEVER key off Kind — only off Intent.
Kind string
// Meta carries an opaque app payload (e.g. os.FileInfo, a remote entry) the
// app can recover after selection without the core understanding it.
Meta any
}
Row is one line in a navigation list: a child container to descend into, the parent (".."), a synthetic "use this folder", a selectable leaf, or a reference-only row. It is the shared currency that replaces passage's DirEntry{Title,Path,Kind} and ssherpa's Item{Kind,Token,...}. It is a superset (mirroring termtheme's universal role superset): passage populates a handful of fields, ssherpa fills the rich set, and unused fields render to nothing rather than erroring (forward-compat).
type Substring ¶
type Substring struct{}
Substring is a case-insensitive plain Contains filter with no scoring — the behavior passage's directory browser used (strings.Contains on the lowered title). Positions are left empty (no highlight), matching that surface.
type SyncLister ¶
SyncLister is an optional capability for zero-latency transports (local FS, in-memory tree): when present, the adapter lists inline and skips the Loading state entirely, so an instant read never flashes a spinner.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package render is termnav's themed default renderer, layered over termtheme.
|
Package render is termnav's themed default renderer, layered over termtheme. |
|
Package source ships the concrete FileSource implementations that are generically useful — a local-filesystem lister and a zero-IO in-memory tree — plus a conformance harness any adapter (including an app's own SFTP source) can run.
|
Package source ships the concrete FileSource implementations that are generically useful — a local-filesystem lister and a zero-IO in-memory tree — plus a conformance harness any adapter (including an app's own SFTP source) can run. |
|
Package teax is the Bubble Tea v2 adapter for termnav: the ONLY layer that imports bubbletea, so the kernel stays framework-free and trivially testable.
|
Package teax is the Bubble Tea v2 adapter for termnav: the ONLY layer that imports bubbletea, so the kernel stays framework-free and trivially testable. |