termnav

package module
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 4 Imported by: 0

README

termnav

Part of termsystem — the shared terminal-UI ecosystem (termtheme · termnav · termchrome · termintro powering passage · ssherpa · dangit). The ecosystem map, dependency graph, and the agent guide (AGENTS.md) live there.

termnav is the shared file/namespace navigation engine for terminal TUIs — a sibling to termtheme. It owns exactly the must-agree navigation logic that passage and ssherpa had each been carrying a near-identical copy of: the directory/list browse model, the fuzzy filter with a relevance gate, the group-aware scroll window, cursor-snap over reference rows, the shell-style path-completion index, and a first-class async Loading/Ready/Error/Empty state machine.

It ships none of the parts that legitimately diverge per app — the transport (local FS, remote SFTP, an in-memory namespace), the app's private row vocabulary, badges/labels, dotfile/symlink policy, or the palette. Those are supplied by the caller through a single injected FileSource seam and a thin per-app shim, exactly as termtheme ships no palettes and takes the base from ThemeConfig.Resolve(base).

Layers

termnav            L0  pure kernel — no bubbletea, no os, no net (stdlib only)
  Row, NavIntent, Listing, LoadState, Outcome
  Model + Update(Model, Event) -> (Model, []Effect)   the IO-free reducer
  Matcher (Fuzzy / Substring) + MatchFuzzy/Relevant
  Index (implicit-tree path completion) + Candidates/Classify/Breadcrumb
  Snap / ClampWindow / WindowContainsCursor / JumpSection   windowing
  FileSource + Closer/SyncLister/Indexer  + InjectNav

termnav/source     L1  concrete sources (imports os)
  LocalSource (os.ReadDir), TreeSource (zero-IO tree), StaticSource
  Conformance(t, src, start)  table-driven FileSource contract harness

termnav/teax       L2  Bubble Tea v2 adapter (the ONLY bubbletea importer)
  Model (embeddable) + Run (turnkey)  +  KeyMap / DefaultKey
  generation token + cancel of in-flight listings + SyncLister fast-path

termnav/render     L3  themed default render (the ONLY termtheme importer)
  Render(model, theme, Styler, Chrome)  — color keys off NavIntent, never Kind
  every name routed through termtheme.Sanitize at the render boundary

L0/L1 depend on nothing outside the standard library; only L2 pulls in Bubble Tea and only L3 pulls in termtheme. An app that wants the headless core and its own rendering never compiles bubbletea-free code against a render stack it doesn't use.

The seam

// The one interface an app must implement (or reuse source.LocalSource).
type FileSource interface {
	Resolve(ctx context.Context, path string) (start string, err error)
	List(ctx context.Context, dir string) (Listing, error)
}
// Optional, detected by type assertion:
//   Closer{ Close() }                 stateful session teardown (SFTP)
//   SyncLister{ ListSync(dir) }       instant reads skip the Loading flash
//   Indexer{ Index() *Index }         breadcrumb / shell-TAB completion

List returns one directory's children as []Row, each tagged with a canonical NavIntent (Descend, Ascend, UseContainer, SelectLeaf, Reference). The kernel never calls List — only the teax adapter does, inside a tea.Cmd — so the core is PTY-free and filesystem-free testable.

Turnkey usage

out, ok, err := teax.Run(ctx, teax.Config{
	Source: source.NewLocal(source.LocalOptions{SelectFiles: true}),
	Start:  ".",
	Render: myRenderFunc, // or render.Render via a small closure
}, termnav.Options{}, teax.ProgramIO{Input: os.Stdin, Output: os.Stderr})
if ok {
	chosen := out.Token()
}

Run replaces a bespoke browser program and the caller-driven "list dir → browse → cd → repeat" loop: navigation, async listing, and cancelation all happen inside one program, so the program is no longer torn down and rebuilt on every directory change — and a slow remote listing shows a real loading state and is abortable with Esc instead of freezing the terminal.

Status

Extracted from the duplicated browsers in passage and ssherpa. The Index parity tests are ported verbatim from passage/internal/ui/pathindex_test.go; the windowing primitives are lifted from ssherpa/internal/chrome/listview.go; the fuzzy matcher is the byte-identical copy both apps carried.

License

MIT — see LICENSE.

Documentation

Index

Constants

View Source
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

func ApplyNode(dir string, n Node) (field string, descended bool)

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

func AscendPath(field string) string

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

func CommonPrefix(cands []Candidate) string

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

func DisplayDir(dir string) string

DisplayDir names a dir (with trailing slash, as SplitPath returns) for a notice; the root reads as "the store root".

func JumpSection

func JumpSection(n, cursor, delta int, groupAt func(i int) string) int

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

func Relevant(r Result, queryLen int) bool

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

func Snap(n, idx, dir int, selectable func(i int) bool) int

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

func SplitPath(field string) (dir, frag string)

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

func Update(m Model, ev Event) (Model, []Effect)

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

type Candidate struct {
	Node      Node
	Match     bool
	Positions []int
}

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

func MatchingCandidates(cands []Candidate) []Candidate

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

type Crumb struct {
	Name   string
	Exists bool
}

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.

func (Fuzzy) Match

func (f Fuzzy) Match(query, candidate string) (Result, bool)

type Index

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

Index is the precomputed snapshot. Construct it with BuildIndex.

func BuildIndex

func BuildIndex(entryPaths []string) *Index

BuildIndex derives the implicit folder tree from a snapshot of entry paths. It is pure and total.

func (*Index) Breadcrumb

func (ix *Index) Breadcrumb(field string) (segs []Crumb, leaf string, kind LeafKind)

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

func (ix *Index) Candidates(dir, frag string) []Candidate

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

func (ix *Index) CaseCanonical(field string) string

CaseCanonical returns the canonical (stored) spelling of an entry that the field collides with only by case, for the notice message.

func (*Index) ChildCount

func (ix *Index) ChildCount(dir, name string) int

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

func (ix *Index) Children(folder string) []Node

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

func (ix *Index) Classify(field string) LeafKind

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.

func (*Index) HasEntry

func (ix *Index) HasEntry(path string) bool

HasEntry reports whether `path` is an exact known entry (a leaf), independent of the active filter — the index is a full snapshot, so a filtered-out entry is still completable.

func (*Index) IsFolder

func (ix *Index) IsFolder(path string) bool

IsFolder reports whether `path` (no trailing slash) is a known implicit folder.

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

type KeyEvent struct {
	Key  string
	Text string
}

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

type ListDirEffect struct {
	Gen  uint64
	Dir  string
	Sync bool
}

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

type ListLoadedEvent struct {
	Gen     uint64
	Listing Listing
	Err     error
}

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

type Listing struct {
	Dir    string
	Parent string
	Rows   []Row
	Notice string
}

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).

const (
	Idle    LoadState = iota // nothing requested yet
	Loading                  // a List is in flight
	Ready                    // a listing is shown
	Error                    // the last List failed (Notice holds why)
	Empty                    // the listing succeeded but has no rows
)

func (LoadState) String

func (s LoadState) String() string

type Matcher

type Matcher interface {
	Match(query, candidate string) (Result, bool)
}

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

func New(opts Options) Model

New returns an Idle model. Call Load (typically from the adapter's Init, after FileSource.Resolve) to request the first directory.

func (Model) Budget

func (m Model) Budget() int

Budget is the list viewport body-line budget: height minus the app's reserved chrome rows, floored at MinRows.

func (Model) Canceled

func (m Model) Canceled() bool

Canceled reports whether the user aborted (esc / the app's cancel key).

func (Model) Cursor

func (m Model) Cursor() int

Cursor is the index into Filtered the user is on.

func (Model) Cwd

func (m Model) Cwd() string

Cwd is the canonical current directory (set when a listing arrives).

func (Model) Done

func (m Model) Done() bool

Done reports whether the browse has terminated — either a commit or a cancel.

func (Model) Filtered

func (m Model) Filtered() []int

Filtered returns the indices into Rows that pass the current query, in order.

func (Model) FocusedRow

func (m Model) FocusedRow() (Row, bool)

FocusedRow returns the row under the cursor, ok=false when the list is empty.

func (Model) Gen

func (m Model) Gen() uint64

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

func (m Model) GroupAt(i int) (string, bool)

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) Height

func (m Model) Height() int

func (Model) Load

func (m Model) Load(dir string) (Model, []Effect)

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) Loading

func (m Model) Loading() bool

Loading reports whether a listing is in flight.

func (Model) MatchTextOf

func (m Model) MatchTextOf(r Row) string

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

func (m Model) Notice() string

Notice is the current inline message (error / permission / blocked commit), or "".

func (Model) Outcome

func (m Model) Outcome() (Outcome, bool)

Outcome returns the committed selection, ok=true once a leaf or container has been chosen. ok=false while still navigating.

func (Model) PrevRows

func (m Model) PrevRows() []Row

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) Query

func (m Model) Query() string

Query is the current filter text.

func (Model) Rows

func (m Model) Rows() []Row

Rows returns the assembled rows of the current directory (including the synthetic use/.. rows the source injected). Render must not mutate it.

func (Model) Scroll

func (m Model) Scroll() int

Scroll is the Filtered index at the top of the viewport.

func (Model) State

func (m Model) State() LoadState

State is the async load state of the current directory.

func (Model) Width

func (m Model) Width() int

Width and Height are the last known terminal size.

type Nav struct {
	Parent   string // canonical parent dir; "" or == dir suppresses ".."
	UseRow   bool   // inject "Use this folder"
	UseTitle string // default "Use this folder"
	UseGroup string
	UseBadge string
	UseKind  string // app's private Kind literal for the use row
	UpTitle  string // default ".."
	UpGroup  string
	UpBadge  string
	UpKind   string // app's private Kind literal for the ".." row
}

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 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
)
func (in NavIntent) String() string

String renders an intent for diagnostics and logs (not for the UI).

type Node

type Node struct {
	Name     string
	IsFolder bool
	IsEntry  bool
}

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

type Outcome struct {
	Rows   []Row
	Intent NavIntent
	Notice string
}

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.

func (Outcome) Token

func (o Outcome) Token() string

Token returns the chosen row's token (the navigable/return value), or "" when the outcome is empty.

func (Outcome) Tokens

func (o Outcome) Tokens() []string

Tokens returns every chosen row's token (one element for single-select).

type ResizeEvent

type ResizeEvent struct{ W, H int }

ResizeEvent carries a new terminal size.

type Result

type Result struct {
	Score     int
	Positions []int
}

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

func MatchFuzzy(query, candidate string) (Result, bool)

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).

func InjectNav

func InjectNav(dir string, children []Row, nav Nav) []Row

InjectNav prepends the synthetic [use][..] rows to a directory's children, matching the order both apps already produce (Current, then Directories). children are assumed already sorted (dirs first) by the source.

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.

func (Substring) Match

func (Substring) Match(query, candidate string) (Result, bool)

type SyncLister

type SyncLister interface {
	ListSync(dir string) (Listing, error)
}

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.

type Validator

type Validator func(r Row) (ok bool, notice string)

Validator is an optional post-select gate: given the row the user committed, it returns ok=false plus a notice to block the commit (e.g. passage's classifyLeaf overwrite gate), or ok=true to commit. A nil Validator always commits.

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.

Jump to

Keyboard shortcuts

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